summaryrefslogtreecommitdiffhomepage
path: root/test/runtimes/proctor
diff options
context:
space:
mode:
authorAdin Scannell <ascannell@google.com>2020-04-23 11:32:08 -0700
committergVisor bot <gvisor-bot@google.com>2020-04-23 11:33:30 -0700
commit1481499fe27157ad2716c00682f6ad819115a6c7 (patch)
treef89a7643f5e7272494ccd3de5681e3445e0ed00a /test/runtimes/proctor
parente0c67014cb2200ad58cd28b12fddb3f55652a21b (diff)
Simplify Docker test infrastructure.
This change adds a layer of abstraction around the internal Docker APIs, and eliminates all direct dependencies on Dockerfiles in the infrastructure. A subsequent change will automated the generation of local images (with efficient caching). Note that this change drops the use of bazel container rules, as that experiment does not seem to be viable. PiperOrigin-RevId: 308095430
Diffstat (limited to 'test/runtimes/proctor')
-rw-r--r--test/runtimes/proctor/BUILD27
-rw-r--r--test/runtimes/proctor/go.go90
-rw-r--r--test/runtimes/proctor/java.go71
-rw-r--r--test/runtimes/proctor/nodejs.go46
-rw-r--r--test/runtimes/proctor/php.go42
-rw-r--r--test/runtimes/proctor/proctor.go163
-rw-r--r--test/runtimes/proctor/proctor_test.go127
-rw-r--r--test/runtimes/proctor/python.go49
8 files changed, 615 insertions, 0 deletions
diff --git a/test/runtimes/proctor/BUILD b/test/runtimes/proctor/BUILD
new file mode 100644
index 000000000..50a26d182
--- /dev/null
+++ b/test/runtimes/proctor/BUILD
@@ -0,0 +1,27 @@
+load("//tools:defs.bzl", "go_binary", "go_test")
+
+package(licenses = ["notice"])
+
+go_binary(
+ name = "proctor",
+ srcs = [
+ "go.go",
+ "java.go",
+ "nodejs.go",
+ "php.go",
+ "proctor.go",
+ "python.go",
+ ],
+ pure = True,
+ visibility = ["//test/runtimes:__pkg__"],
+)
+
+go_test(
+ name = "proctor_test",
+ size = "small",
+ srcs = ["proctor_test.go"],
+ library = ":proctor",
+ deps = [
+ "//pkg/test/testutil",
+ ],
+)
diff --git a/test/runtimes/proctor/go.go b/test/runtimes/proctor/go.go
new file mode 100644
index 000000000..3e2d5d8db
--- /dev/null
+++ b/test/runtimes/proctor/go.go
@@ -0,0 +1,90 @@
+// Copyright 2019 The gVisor Authors.
+//
+// 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 main
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+ "regexp"
+ "strings"
+)
+
+var (
+ goTestRegEx = regexp.MustCompile(`^.+\.go$`)
+
+ // Directories with .dir contain helper files for tests.
+ // Exclude benchmarks and stress tests.
+ goDirFilter = regexp.MustCompile(`^(bench|stress)\/.+$|^.+\.dir.+$`)
+)
+
+// Location of Go tests on disk.
+const goTestDir = "/usr/local/go/test"
+
+// goRunner implements TestRunner for Go.
+//
+// There are two types of Go tests: "Go tool tests" and "Go tests on disk".
+// "Go tool tests" are found and executed using `go tool dist test`. "Go tests
+// on disk" are found in the /usr/local/go/test directory and are executed
+// using `go run run.go`.
+type goRunner struct{}
+
+var _ TestRunner = goRunner{}
+
+// ListTests implements TestRunner.ListTests.
+func (goRunner) ListTests() ([]string, error) {
+ // Go tool dist test tests.
+ args := []string{"tool", "dist", "test", "-list"}
+ cmd := exec.Command("go", args...)
+ cmd.Stderr = os.Stderr
+ out, err := cmd.Output()
+ if err != nil {
+ return nil, fmt.Errorf("failed to list: %v", err)
+ }
+ var toolSlice []string
+ for _, test := range strings.Split(string(out), "\n") {
+ toolSlice = append(toolSlice, test)
+ }
+
+ // Go tests on disk.
+ diskSlice, err := search(goTestDir, goTestRegEx)
+ if err != nil {
+ return nil, err
+ }
+ // Remove items from /bench/, /stress/ and .dir files
+ diskFiltered := diskSlice[:0]
+ for _, file := range diskSlice {
+ if !goDirFilter.MatchString(file) {
+ diskFiltered = append(diskFiltered, file)
+ }
+ }
+
+ return append(toolSlice, diskFiltered...), nil
+}
+
+// TestCmd implements TestRunner.TestCmd.
+func (goRunner) TestCmd(test string) *exec.Cmd {
+ // Check if test exists on disk by searching for file of the same name.
+ // This will determine whether or not it is a Go test on disk.
+ if strings.HasSuffix(test, ".go") {
+ // Test has suffix ".go" which indicates a disk test, run it as such.
+ cmd := exec.Command("go", "run", "run.go", "-v", "--", test)
+ cmd.Dir = goTestDir
+ return cmd
+ }
+
+ // No ".go" suffix, run as a tool test.
+ return exec.Command("go", "tool", "dist", "test", "-run", test)
+}
diff --git a/test/runtimes/proctor/java.go b/test/runtimes/proctor/java.go
new file mode 100644
index 000000000..8b362029d
--- /dev/null
+++ b/test/runtimes/proctor/java.go
@@ -0,0 +1,71 @@
+// Copyright 2019 The gVisor Authors.
+//
+// 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 main
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+ "regexp"
+ "strings"
+)
+
+// Directories to exclude from tests.
+var javaExclDirs = regexp.MustCompile(`(^(sun\/security)|(java\/util\/stream)|(java\/time)| )`)
+
+// Location of java tests.
+const javaTestDir = "/root/test/jdk"
+
+// javaRunner implements TestRunner for Java.
+type javaRunner struct{}
+
+var _ TestRunner = javaRunner{}
+
+// ListTests implements TestRunner.ListTests.
+func (javaRunner) ListTests() ([]string, error) {
+ args := []string{
+ "-dir:" + javaTestDir,
+ "-ignore:quiet",
+ "-a",
+ "-listtests",
+ ":jdk_core",
+ ":jdk_svc",
+ ":jdk_sound",
+ ":jdk_imageio",
+ }
+ cmd := exec.Command("jtreg", args...)
+ cmd.Stderr = os.Stderr
+ out, err := cmd.Output()
+ if err != nil {
+ return nil, fmt.Errorf("jtreg -listtests : %v", err)
+ }
+ var testSlice []string
+ for _, test := range strings.Split(string(out), "\n") {
+ if !javaExclDirs.MatchString(test) {
+ testSlice = append(testSlice, test)
+ }
+ }
+ return testSlice, nil
+}
+
+// TestCmd implements TestRunner.TestCmd.
+func (javaRunner) TestCmd(test string) *exec.Cmd {
+ args := []string{
+ "-noreport",
+ "-dir:" + javaTestDir,
+ test,
+ }
+ return exec.Command("jtreg", args...)
+}
diff --git a/test/runtimes/proctor/nodejs.go b/test/runtimes/proctor/nodejs.go
new file mode 100644
index 000000000..bd57db444
--- /dev/null
+++ b/test/runtimes/proctor/nodejs.go
@@ -0,0 +1,46 @@
+// Copyright 2019 The gVisor Authors.
+//
+// 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 main
+
+import (
+ "os/exec"
+ "path/filepath"
+ "regexp"
+)
+
+var nodejsTestRegEx = regexp.MustCompile(`^test-[^-].+\.js$`)
+
+// Location of nodejs tests relative to working dir.
+const nodejsTestDir = "test"
+
+// nodejsRunner implements TestRunner for NodeJS.
+type nodejsRunner struct{}
+
+var _ TestRunner = nodejsRunner{}
+
+// ListTests implements TestRunner.ListTests.
+func (nodejsRunner) ListTests() ([]string, error) {
+ testSlice, err := search(nodejsTestDir, nodejsTestRegEx)
+ if err != nil {
+ return nil, err
+ }
+ return testSlice, nil
+}
+
+// TestCmd implements TestRunner.TestCmd.
+func (nodejsRunner) TestCmd(test string) *exec.Cmd {
+ args := []string{filepath.Join("tools", "test.py"), test}
+ return exec.Command("/usr/bin/python", args...)
+}
diff --git a/test/runtimes/proctor/php.go b/test/runtimes/proctor/php.go
new file mode 100644
index 000000000..9115040e1
--- /dev/null
+++ b/test/runtimes/proctor/php.go
@@ -0,0 +1,42 @@
+// Copyright 2019 The gVisor Authors.
+//
+// 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 main
+
+import (
+ "os/exec"
+ "regexp"
+)
+
+var phpTestRegEx = regexp.MustCompile(`^.+\.phpt$`)
+
+// phpRunner implements TestRunner for PHP.
+type phpRunner struct{}
+
+var _ TestRunner = phpRunner{}
+
+// ListTests implements TestRunner.ListTests.
+func (phpRunner) ListTests() ([]string, error) {
+ testSlice, err := search(".", phpTestRegEx)
+ if err != nil {
+ return nil, err
+ }
+ return testSlice, nil
+}
+
+// TestCmd implements TestRunner.TestCmd.
+func (phpRunner) TestCmd(test string) *exec.Cmd {
+ args := []string{"test", "TESTS=" + test}
+ return exec.Command("make", args...)
+}
diff --git a/test/runtimes/proctor/proctor.go b/test/runtimes/proctor/proctor.go
new file mode 100644
index 000000000..b54abe434
--- /dev/null
+++ b/test/runtimes/proctor/proctor.go
@@ -0,0 +1,163 @@
+// Copyright 2019 The gVisor Authors.
+//
+// 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.
+
+// Binary proctor runs the test for a particular runtime. It is meant to be
+// included in Docker images for all runtime tests.
+package main
+
+import (
+ "flag"
+ "fmt"
+ "log"
+ "os"
+ "os/exec"
+ "os/signal"
+ "path/filepath"
+ "regexp"
+ "syscall"
+)
+
+// TestRunner is an interface that must be implemented for each runtime
+// integrated with proctor.
+type TestRunner interface {
+ // ListTests returns a string slice of tests available to run.
+ ListTests() ([]string, error)
+
+ // TestCmd returns an *exec.Cmd that will run the given test.
+ TestCmd(test string) *exec.Cmd
+}
+
+var (
+ runtime = flag.String("runtime", "", "name of runtime")
+ list = flag.Bool("list", false, "list all available tests")
+ testName = flag.String("test", "", "run a single test from the list of available tests")
+ pause = flag.Bool("pause", false, "cause container to pause indefinitely, reaping any zombie children")
+)
+
+func main() {
+ flag.Parse()
+
+ if *pause {
+ pauseAndReap()
+ panic("pauseAndReap should never return")
+ }
+
+ if *runtime == "" {
+ log.Fatalf("runtime flag must be provided")
+ }
+
+ tr, err := testRunnerForRuntime(*runtime)
+ if err != nil {
+ log.Fatalf("%v", err)
+ }
+
+ // List tests.
+ if *list {
+ tests, err := tr.ListTests()
+ if err != nil {
+ log.Fatalf("failed to list tests: %v", err)
+ }
+ for _, test := range tests {
+ fmt.Println(test)
+ }
+ return
+ }
+
+ var tests []string
+ if *testName == "" {
+ // Run every test.
+ tests, err = tr.ListTests()
+ if err != nil {
+ log.Fatalf("failed to get all tests: %v", err)
+ }
+ } else {
+ // Run a single test.
+ tests = []string{*testName}
+ }
+ for _, test := range tests {
+ cmd := tr.TestCmd(test)
+ cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
+ if err := cmd.Run(); err != nil {
+ log.Fatalf("FAIL: %v", err)
+ }
+ }
+}
+
+// testRunnerForRuntime returns a new TestRunner for the given runtime.
+func testRunnerForRuntime(runtime string) (TestRunner, error) {
+ switch runtime {
+ case "go":
+ return goRunner{}, nil
+ case "java":
+ return javaRunner{}, nil
+ case "nodejs":
+ return nodejsRunner{}, nil
+ case "php":
+ return phpRunner{}, nil
+ case "python":
+ return pythonRunner{}, nil
+ }
+ return nil, fmt.Errorf("invalid runtime %q", runtime)
+}
+
+// pauseAndReap is like init. It runs forever and reaps any children.
+func pauseAndReap() {
+ // Get notified of any new children.
+ ch := make(chan os.Signal, 1)
+ signal.Notify(ch, syscall.SIGCHLD)
+
+ for {
+ if _, ok := <-ch; !ok {
+ // Channel closed. This should not happen.
+ panic("signal channel closed")
+ }
+
+ // Reap the child.
+ for {
+ if cpid, _ := syscall.Wait4(-1, nil, syscall.WNOHANG, nil); cpid < 1 {
+ break
+ }
+ }
+ }
+}
+
+// search is a helper function to find tests in the given directory that match
+// the regex.
+func search(root string, testFilter *regexp.Regexp) ([]string, error) {
+ var testSlice []string
+
+ err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+
+ name := filepath.Base(path)
+
+ if info.IsDir() || !testFilter.MatchString(name) {
+ return nil
+ }
+
+ relPath, err := filepath.Rel(root, path)
+ if err != nil {
+ return err
+ }
+ testSlice = append(testSlice, relPath)
+ return nil
+ })
+ if err != nil {
+ return nil, fmt.Errorf("walking %q: %v", root, err)
+ }
+
+ return testSlice, nil
+}
diff --git a/test/runtimes/proctor/proctor_test.go b/test/runtimes/proctor/proctor_test.go
new file mode 100644
index 000000000..6ef2de085
--- /dev/null
+++ b/test/runtimes/proctor/proctor_test.go
@@ -0,0 +1,127 @@
+// Copyright 2019 The gVisor Authors.
+//
+// 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 main
+
+import (
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "reflect"
+ "regexp"
+ "strings"
+ "testing"
+
+ "gvisor.dev/gvisor/pkg/test/testutil"
+)
+
+func touch(t *testing.T, name string) {
+ t.Helper()
+ f, err := os.Create(name)
+ if err != nil {
+ t.Fatalf("error creating file %q: %v", name, err)
+ }
+ if err := f.Close(); err != nil {
+ t.Fatalf("error closing file %q: %v", name, err)
+ }
+}
+
+func TestSearchEmptyDir(t *testing.T) {
+ td, err := ioutil.TempDir(testutil.TmpDir(), "searchtest")
+ if err != nil {
+ t.Fatalf("error creating searchtest: %v", err)
+ }
+ defer os.RemoveAll(td)
+
+ var want []string
+
+ testFilter := regexp.MustCompile(`^test-[^-].+\.tc$`)
+ got, err := search(td, testFilter)
+ if err != nil {
+ t.Errorf("search error: %v", err)
+ }
+
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("Found %#v; want %#v", got, want)
+ }
+}
+
+func TestSearch(t *testing.T) {
+ td, err := ioutil.TempDir(testutil.TmpDir(), "searchtest")
+ if err != nil {
+ t.Fatalf("error creating searchtest: %v", err)
+ }
+ defer os.RemoveAll(td)
+
+ // Creating various files similar to the test filter regex.
+ files := []string{
+ "emp/",
+ "tee/",
+ "test-foo.tc",
+ "test-foo.tc",
+ "test-bar.tc",
+ "test-sam.tc",
+ "Test-que.tc",
+ "test-brett",
+ "test--abc.tc",
+ "test---xyz.tc",
+ "test-bool.TC",
+ "--test-gvs.tc",
+ " test-pew.tc",
+ "dir/test_baz.tc",
+ "dir/testsnap.tc",
+ "dir/test-luk.tc",
+ "dir/nest/test-ok.tc",
+ "dir/dip/diz/goog/test-pack.tc",
+ "dir/dip/diz/wobble/thud/test-cas.e",
+ "dir/dip/diz/wobble/thud/test-cas.tc",
+ }
+ want := []string{
+ "dir/dip/diz/goog/test-pack.tc",
+ "dir/dip/diz/wobble/thud/test-cas.tc",
+ "dir/nest/test-ok.tc",
+ "dir/test-luk.tc",
+ "test-bar.tc",
+ "test-foo.tc",
+ "test-sam.tc",
+ }
+
+ for _, item := range files {
+ if strings.HasSuffix(item, "/") {
+ // This item is a directory, create it.
+ if err := os.MkdirAll(filepath.Join(td, item), 0755); err != nil {
+ t.Fatalf("error making directory: %v", err)
+ }
+ } else {
+ // This item is a file, create the directory and touch file.
+ // Create directory in which file should be created
+ fullDirPath := filepath.Join(td, filepath.Dir(item))
+ if err := os.MkdirAll(fullDirPath, 0755); err != nil {
+ t.Fatalf("error making directory: %v", err)
+ }
+ // Create file with full path to file.
+ touch(t, filepath.Join(td, item))
+ }
+ }
+
+ testFilter := regexp.MustCompile(`^test-[^-].+\.tc$`)
+ got, err := search(td, testFilter)
+ if err != nil {
+ t.Errorf("search error: %v", err)
+ }
+
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("Found %#v; want %#v", got, want)
+ }
+}
diff --git a/test/runtimes/proctor/python.go b/test/runtimes/proctor/python.go
new file mode 100644
index 000000000..b9e0fbe6f
--- /dev/null
+++ b/test/runtimes/proctor/python.go
@@ -0,0 +1,49 @@
+// Copyright 2019 The gVisor Authors.
+//
+// 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 main
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+ "strings"
+)
+
+// pythonRunner implements TestRunner for Python.
+type pythonRunner struct{}
+
+var _ TestRunner = pythonRunner{}
+
+// ListTests implements TestRunner.ListTests.
+func (pythonRunner) ListTests() ([]string, error) {
+ args := []string{"-m", "test", "--list-tests"}
+ cmd := exec.Command("./python", args...)
+ cmd.Stderr = os.Stderr
+ out, err := cmd.Output()
+ if err != nil {
+ return nil, fmt.Errorf("failed to list: %v", err)
+ }
+ var toolSlice []string
+ for _, test := range strings.Split(string(out), "\n") {
+ toolSlice = append(toolSlice, test)
+ }
+ return toolSlice, nil
+}
+
+// TestCmd implements TestRunner.TestCmd.
+func (pythonRunner) TestCmd(test string) *exec.Cmd {
+ args := []string{"-m", "test", test}
+ return exec.Command("./python", args...)
+}