summaryrefslogtreecommitdiffhomepage
path: root/test/syscalls/syscall_test_runner.go
diff options
context:
space:
mode:
Diffstat (limited to 'test/syscalls/syscall_test_runner.go')
-rw-r--r--test/syscalls/syscall_test_runner.go289
1 files changed, 289 insertions, 0 deletions
diff --git a/test/syscalls/syscall_test_runner.go b/test/syscalls/syscall_test_runner.go
new file mode 100644
index 000000000..9ee0361ee
--- /dev/null
+++ b/test/syscalls/syscall_test_runner.go
@@ -0,0 +1,289 @@
+// Copyright 2018 Google LLC
+//
+// 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 syscall_test_runner runs the syscall test suites in gVisor
+// containers and on the host platform.
+package main
+
+import (
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "math"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "syscall"
+ "testing"
+
+ "golang.org/x/sys/unix"
+ "gvisor.googlesource.com/gvisor/pkg/log"
+ "gvisor.googlesource.com/gvisor/runsc/boot"
+ "gvisor.googlesource.com/gvisor/runsc/container"
+ "gvisor.googlesource.com/gvisor/runsc/specutils"
+ "gvisor.googlesource.com/gvisor/runsc/test/testutil"
+ "gvisor.googlesource.com/gvisor/test/syscalls/gtest"
+)
+
+// Location of syscall tests, relative to the repo root.
+const testDir = "test/syscalls/linux"
+
+var (
+ testName = flag.String("test-name", "", "name of test binary to run")
+ debug = flag.Bool("debug", false, "enable debug logs")
+ strace = flag.Bool("strace", false, "enable strace logs")
+ platform = flag.String("platform", "ptrace", "platform to run on")
+ parallel = flag.Bool("parallel", false, "run tests in parallel")
+)
+
+// runTestCaseNative runs the test case directly on the host machine.
+func runTestCaseNative(testBin string, tc gtest.TestCase, t *testing.T) {
+ // These tests might be running in parallel, so make sure they have a
+ // unique test temp dir.
+ tmpDir, err := ioutil.TempDir(testutil.TmpDir(), "")
+ if err != nil {
+ t.Fatalf("could not create temp dir: %v", err)
+ }
+ defer os.RemoveAll(tmpDir)
+
+ // Replace TEST_TMPDIR in the current environment with something
+ // unique.
+ env := os.Environ()
+ newEnvVar := "TEST_TMPDIR=" + tmpDir
+ var found bool
+ for i, kv := range env {
+ if strings.HasPrefix(kv, "TEST_TMPDIR=") {
+ env[i] = newEnvVar
+ found = true
+ break
+ }
+ }
+ if !found {
+ env = append(env, newEnvVar)
+ }
+ // Remove env variables that cause the gunit binary to write output
+ // files, since they will stomp on eachother, and on the output files
+ // from this go test.
+ env = filterEnv(env, []string{"GUNIT_OUTPUT", "TEST_PREMATURE_EXIT_FILE", "XML_OUTPUT_FILE"})
+
+ // Remove shard env variables so that the gunit binary does not try to
+ // intepret them.
+ env = filterEnv(env, []string{"TEST_SHARD_INDEX", "TEST_TOTAL_SHARDS"})
+
+ cmd := exec.Command(testBin, gtest.FilterTestFlag+"="+tc.FullName())
+ cmd.Env = env
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ if err := cmd.Run(); err != nil {
+ ws := err.(*exec.ExitError).Sys().(syscall.WaitStatus)
+ t.Errorf("test %q exited with status %d, want 0", tc.FullName(), ws.ExitStatus())
+ }
+}
+
+// runsTestCaseRunsc runs the test case in runsc.
+func runTestCaseRunsc(testBin string, tc gtest.TestCase, t *testing.T) {
+ rootDir, err := testutil.SetupRootDir()
+ if err != nil {
+ t.Fatalf("SetupRootDir failed: %v", err)
+ }
+ defer os.RemoveAll(rootDir)
+
+ conf := testutil.TestConfig()
+ conf.RootDir = rootDir
+ conf.Debug = *debug
+ conf.Strace = *strace
+ p, err := boot.MakePlatformType(*platform)
+ if err != nil {
+ t.Fatalf("error getting platform %q: %v", *platform, err)
+ }
+ conf.Platform = p
+
+ // Run a new container with the test executable and filter for the
+ // given test suite and name.
+ spec := testutil.NewSpecWithArgs(testBin, gtest.FilterTestFlag+"="+tc.FullName())
+
+ // Mark the root as writeable, as some tests attempt to
+ // write to the rootfs, and expect EACCES, not EROFS.
+ spec.Root.Readonly = false
+
+ // Set environment variable that indicates we are
+ // running in gVisor and with the given platform.
+ platformVar := "TEST_ON_GVISOR"
+ env := append(os.Environ(), platformVar+"="+*platform)
+
+ // Remove env variables that cause the gunit binary to write output
+ // files, since they will stomp on eachother, and on the output files
+ // from this go test.
+ env = filterEnv(env, []string{"GUNIT_OUTPUT", "TEST_PREMATURE_EXIT_FILE", "XML_OUTPUT_FILE"})
+
+ // Remove shard env variables so that the gunit binary does not try to
+ // intepret them.
+ env = filterEnv(env, []string{"TEST_SHARD_INDEX", "TEST_TOTAL_SHARDS"})
+
+ // Set TEST_TMPDIR to /tmp, as some of the syscall tests require it to
+ // be backed by tmpfs.
+ for i, kv := range env {
+ if strings.HasPrefix(kv, "TEST_TMPDIR=") {
+ env[i] = "TEST_TMPDIR=/tmp"
+ break
+ }
+ }
+
+ spec.Process.Env = env
+
+ bundleDir, err := testutil.SetupBundleDir(spec)
+ if err != nil {
+ t.Fatalf("SetupBundleDir failed: %v", err)
+ }
+ defer os.RemoveAll(bundleDir)
+
+ id := testutil.UniqueContainerID()
+ log.Infof("Running test %q in container %q", tc.FullName(), id)
+ specutils.LogSpec(spec)
+ ws, err := container.Run(id, spec, conf, bundleDir, "", "", "")
+ if err != nil {
+ t.Fatalf("container.Run failed: %v", err)
+ }
+ if got := ws.ExitStatus(); got != 0 {
+ t.Errorf("test %q exited with status %d, want 0", tc.FullName(), ws.ExitStatus())
+ }
+}
+
+// filterEnv returns an environment with the blacklisted variables removed.
+func filterEnv(env, blacklist []string) []string {
+ var out []string
+ for _, kv := range env {
+ ok := true
+ for _, k := range blacklist {
+ if strings.HasPrefix(kv, k+"=") {
+ ok = false
+ break
+ }
+ }
+ if ok {
+ out = append(out, kv)
+ }
+ }
+ return out
+}
+
+func fatalf(s string, args ...interface{}) {
+ fmt.Fprintf(os.Stderr, s+"\n", args...)
+ os.Exit(1)
+}
+
+func matchString(a, b string) (bool, error) {
+ return a == b, nil
+}
+
+func main() {
+ flag.Parse()
+ if *testName == "" {
+ fatalf("test-name flag must be provided")
+ }
+
+ log.SetLevel(log.Warning)
+ if *debug {
+ log.SetLevel(log.Debug)
+ }
+
+ if *platform != "native" {
+ if err := testutil.ConfigureExePath(); err != nil {
+ panic(err.Error())
+ }
+ // The native tests don't expect to be running as root, but
+ // runsc requires it.
+ testutil.RunAsRoot()
+ }
+
+ // Make sure stdout and stderr are opened with O_APPEND, otherwise logs
+ // from outside the sandbox can (and will) stomp on logs from inside
+ // the sandbox.
+ for _, f := range []*os.File{os.Stdout, os.Stderr} {
+ flags, err := unix.FcntlInt(f.Fd(), unix.F_GETFL, 0)
+ if err != nil {
+ fatalf("error getting file flags for %v: %v", f, err)
+ }
+ if flags&unix.O_APPEND == 0 {
+ flags |= unix.O_APPEND
+ if _, err := unix.FcntlInt(f.Fd(), unix.F_SETFL, flags); err != nil {
+ fatalf("error setting file flags for %v: %v", f, err)
+ }
+ }
+ }
+
+ // Get path to test binary.
+ fullTestName := filepath.Join(testDir, *testName)
+ testBin, err := testutil.FindFile(fullTestName)
+ if err != nil {
+ fatalf("FindFile(%q) failed: %v", fullTestName, err)
+ }
+
+ // Get all test cases in each binary.
+ testCases, err := gtest.ParseTestCases(testBin)
+ if err != nil {
+ fatalf("ParseTestCases(%q) failed: %v", testBin, err)
+ }
+
+ // If sharding, then get the subset of tests to run based on the shard index.
+ if indexStr, totalStr := os.Getenv("TEST_SHARD_INDEX"), os.Getenv("TEST_TOTAL_SHARDS"); indexStr != "" && totalStr != "" {
+ // Parse index and total to ints.
+ index, err := strconv.Atoi(indexStr)
+ if err != nil {
+ fatalf("invalid TEST_SHARD_INDEX %q: %v", indexStr, err)
+ }
+ total, err := strconv.Atoi(totalStr)
+ if err != nil {
+ fatalf("invalid TEST_TOTAL_SHARDS %q: %v", totalStr, err)
+ }
+ // Calculate subslice of tests to run.
+ shardSize := int(math.Ceil(float64(len(testCases)) / float64(total)))
+ begin := index * shardSize
+ end := ((index + 1) * shardSize) - 1
+ if begin > len(testCases) {
+ // Nothing to run.
+ return
+ }
+ if end > len(testCases) {
+ end = len(testCases)
+ }
+ testCases = testCases[begin:end]
+ }
+
+ var tests []testing.InternalTest
+ for _, tc := range testCases {
+ // Capture tc.
+ tc := tc
+ testName := fmt.Sprintf("%s_%s", tc.Suite, tc.Name)
+ tests = append(tests, testing.InternalTest{
+ Name: testName,
+ F: func(t *testing.T) {
+ if *parallel {
+ t.Parallel()
+ }
+ if *platform == "native" {
+ // Run the test case on host.
+ runTestCaseNative(testBin, tc, t)
+ } else {
+ // Run the test case in runsc.
+ runTestCaseRunsc(testBin, tc, t)
+ }
+ },
+ })
+ }
+
+ testing.Main(matchString, tests, nil, nil)
+}