Regres: Make it work on windows. Change-Id: Iff9400cabd5cbf8e7fcffb042c993e29a3acfdc7 Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/40608 Kokoro-Presubmit: kokoro <noreply+kokoro@google.com> Reviewed-by: Nicolas Capens <nicolascapens@google.com> Tested-by: Ben Clayton <bclayton@google.com>
diff --git a/tests/regres/cmd/regres/main.go b/tests/regres/cmd/regres/main.go index 9ab0735..0b55569 100644 --- a/tests/regres/cmd/regres/main.go +++ b/tests/regres/cmd/regres/main.go
@@ -83,10 +83,6 @@ ) func main() { - if runtime.GOOS != "linux" { - log.Fatal("regres only currently runs on linux") - } - flag.ErrHelp = errors.New("regres is a tool to detect regressions between versions of SwiftShader") flag.Parse()
diff --git a/tests/regres/cmd/run_testlist/main.go b/tests/regres/cmd/run_testlist/main.go index f489adb..212aef8 100644 --- a/tests/regres/cmd/run_testlist/main.go +++ b/tests/regres/cmd/run_testlist/main.go
@@ -25,7 +25,6 @@ "flag" "fmt" "io/ioutil" - "log" "os" "runtime" "sort" @@ -95,10 +94,6 @@ } func main() { - if runtime.GOOS != "linux" { - log.Fatal("regres only currently runs on linux") - } - flag.ErrHelp = errors.New("regres is a tool to detect regressions between versions of SwiftShader") flag.Parse() if err := runTests(); err != nil {
diff --git a/tests/regres/run.bat b/tests/regres/run.bat new file mode 100644 index 0000000..e014bbd --- /dev/null +++ b/tests/regres/run.bat
@@ -0,0 +1 @@ +go run %~dp0cmd\regres\main.go %* \ No newline at end of file
diff --git a/tests/regres/run_testlist.bat b/tests/regres/run_testlist.bat new file mode 100644 index 0000000..ae9aaf1 --- /dev/null +++ b/tests/regres/run_testlist.bat
@@ -0,0 +1 @@ +go run %~dp0cmd\run_testlist\main.go %* \ No newline at end of file
diff --git a/tests/regres/shell/shell.go b/tests/regres/shell/shell.go index 7622295..0b6f84c 100644 --- a/tests/regres/shell/shell.go +++ b/tests/regres/shell/shell.go
@@ -16,73 +16,17 @@ package shell import ( - "bytes" "fmt" - "log" - "os" - "os/exec" - "os/signal" - "strconv" - "syscall" "time" "../cause" ) -// MaxProcMemory is the maximum virtual memory per child process +// MaxProcMemory is the maximum virtual memory per child process. +// Note: This is not used on Windows, as there is no sensible way to limit +// process memory. var MaxProcMemory uint64 = 4 * 1024 * 1024 * 1024 // 4GB -func init() { - // As we are going to be running a number of tests concurrently, we need to - // limit the amount of virtual memory each test uses, otherwise memory - // hungry tests can bring the whole system down into a swapping apocalypse. - // - // Linux has the setrlimit() function to limit a process (and child's) - // virtual memory usage - but we cannot call this from the regres process - // as this process may need more memory than the limit allows. - // - // Unfortunately golang has no native support for setting rlimits for child - // processes (https://github.com/golang/go/issues/6603), so we instead wrap - // the exec to the test executable with another child regres process using a - // special --exec mode: - // - // [regres] -> [regres --exec <test-exe N args...>] -> [test-exe] - // ^^^^ - // (calls rlimit() with memory limit of N bytes) - - if len(os.Args) > 3 && os.Args[1] == "--exec" { - exe := os.Args[2] - limit, err := strconv.ParseUint(os.Args[3], 10, 64) - if err != nil { - log.Fatalf("Expected memory limit as 3rd argument. %v\n", err) - } - if limit > 0 { - if err := syscall.Setrlimit(syscall.RLIMIT_AS, &syscall.Rlimit{Cur: limit, Max: limit}); err != nil { - log.Fatalln(cause.Wrap(err, "Setrlimit").Error()) - } - } - cmd := exec.Command(exe, os.Args[4:]...) - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - if err := cmd.Start(); err != nil { - os.Stderr.WriteString(err.Error()) - os.Exit(1) - } - // Forward signals to the child process - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt) - go func() { - for sig := range c { - cmd.Process.Signal(sig) - } - }() - cmd.Wait() - close(c) - os.Exit(cmd.ProcessState.ExitCode()) - } -} - // Shell runs the executable exe with the given arguments, in the working // directory wd. // If the process does not finish within timeout a errTimeout will be returned. @@ -93,41 +37,6 @@ return nil } -// Exec runs the executable exe with the given arguments, in the working -// directory wd, with the custom environment flags. -// If the process does not finish within timeout a errTimeout will be returned. -func Exec(timeout time.Duration, exe, wd string, env []string, args ...string) ([]byte, error) { - // Shell via regres: --exec N <exe> <args...> - // See main() for details. - args = append([]string{"--exec", exe, fmt.Sprintf("%v", MaxProcMemory)}, args...) - b := bytes.Buffer{} - c := exec.Command(os.Args[0], args...) - c.Dir = wd - c.Env = env - c.Stdout = &b - c.Stderr = &b - - if err := c.Start(); err != nil { - return nil, err - } - - res := make(chan error) - go func() { res <- c.Wait() }() - - select { - case <-time.NewTimer(timeout).C: - c.Process.Signal(syscall.SIGINT) - time.Sleep(time.Second * 3) - if !c.ProcessState.Exited() { - log.Printf("Process %v still has not exited, killing\n", c.Process.Pid) - syscall.Kill(-c.Process.Pid, syscall.SIGKILL) - } - return b.Bytes(), ErrTimeout{exe, timeout} - case err := <-res: - return b.Bytes(), err - } -} - // ErrTimeout is the error returned when a process does not finish with its // permitted time. type ErrTimeout struct {
diff --git a/tests/regres/shell/shell_unix.go b/tests/regres/shell/shell_unix.go new file mode 100644 index 0000000..447098b --- /dev/null +++ b/tests/regres/shell/shell_unix.go
@@ -0,0 +1,128 @@ +// Copyright 2019 The SwiftShader Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build darwin linux + +package shell + +import ( + "bytes" + "fmt" + "log" + "os" + "os/exec" + "os/signal" + "strconv" + "syscall" + "time" + + "../cause" +) + +func init() { + // As we are going to be running a number of tests concurrently, we need to + // limit the amount of virtual memory each test uses, otherwise memory + // hungry tests can bring the whole system down into a swapping apocalypse. + // + // Linux has the setrlimit() function to limit a process (and child's) + // virtual memory usage - but we cannot call this from the regres process + // as this process may need more memory than the limit allows. + // + // Unfortunately golang has no native support for setting rlimits for child + // processes (https://github.com/golang/go/issues/6603), so we instead wrap + // the exec to the test executable with another child regres process using a + // special --exec mode: + // + // [regres] -> [regres --exec <test-exe N args...>] -> [test-exe] + // ^^^^ + // (calls rlimit() with memory limit of N bytes) + + if len(os.Args) > 3 && os.Args[1] == "--exec" { + exe := os.Args[2] + limit, err := strconv.ParseUint(os.Args[3], 10, 64) + if err != nil { + log.Fatalf("Expected memory limit as 3rd argument. %v\n", err) + } + if limit > 0 { + if err := syscall.Setrlimit(syscall.RLIMIT_AS, &syscall.Rlimit{Cur: limit, Max: limit}); err != nil { + log.Fatalln(cause.Wrap(err, "Setrlimit").Error()) + } + } + cmd := exec.Command(exe, os.Args[4:]...) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Start(); err != nil { + os.Stderr.WriteString(err.Error()) + os.Exit(1) + } + // Forward signals to the child process + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + go func() { + for sig := range c { + cmd.Process.Signal(sig) + } + }() + cmd.Wait() + close(c) + os.Exit(cmd.ProcessState.ExitCode()) + } +} + +// Exec runs the executable exe with the given arguments, in the working +// directory wd, with the custom environment flags. +// If the process does not finish within timeout a errTimeout will be returned. +func Exec(timeout time.Duration, exe, wd string, env []string, args ...string) ([]byte, error) { + // Shell via regres: --exec N <exe> <args...> + // See main() for details. + args = append([]string{"--exec", exe, fmt.Sprintf("%v", MaxProcMemory)}, args...) + b := bytes.Buffer{} + c := exec.Command(os.Args[0], args...) + c.Dir = wd + c.Env = env + c.Stdout = &b + c.Stderr = &b + + if err := c.Start(); err != nil { + return nil, err + } + + res := make(chan error) + go func() { res <- c.Wait() }() + + select { + case <-time.NewTimer(timeout).C: + c.Process.Signal(syscall.SIGINT) + time.Sleep(time.Second * 3) + if !c.ProcessState.Exited() { + log.Printf("Process %v still has not exited, killing\n", c.Process.Pid) + syscall.Kill(-c.Process.Pid, syscall.SIGKILL) + } + return b.Bytes(), ErrTimeout{exe, timeout} + case err := <-res: + return b.Bytes(), err + } +} + +// ErrTimeout is the error returned when a process does not finish with its +// permitted time. +type ErrTimeout struct { + process string + timeout time.Duration +} + +func (e ErrTimeout) Error() string { + return fmt.Sprintf("'%v' did not return after %v", e.process, e.timeout) +}
diff --git a/tests/regres/shell/shell_windows.go b/tests/regres/shell/shell_windows.go new file mode 100644 index 0000000..cf193d4 --- /dev/null +++ b/tests/regres/shell/shell_windows.go
@@ -0,0 +1,48 @@ +// Copyright 2019 The SwiftShader Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + "bytes" + "os/exec" + "time" +) + +// Exec runs the executable exe with the given arguments, in the working +// directory wd, with the custom environment flags. +// If the process does not finish within timeout a errTimeout will be returned. +func Exec(timeout time.Duration, exe, wd string, env []string, args ...string) ([]byte, error) { + b := bytes.Buffer{} + c := exec.Command(exe, args...) + c.Dir = wd + c.Env = env + c.Stdout = &b + c.Stderr = &b + + if err := c.Start(); err != nil { + return nil, err + } + + res := make(chan error) + go func() { res <- c.Wait() }() + + select { + case <-time.NewTimer(timeout).C: + c.Process.Kill() + return b.Bytes(), ErrTimeout{exe, timeout} + case err := <-res: + return b.Bytes(), err + } +}