summaryrefslogtreecommitdiffhomepage
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/benchmarks/database/BUILD1
-rw-r--r--test/benchmarks/database/redis_test.go86
-rw-r--r--test/benchmarks/fs/BUILD1
-rw-r--r--test/benchmarks/fs/fio_test.go257
-rw-r--r--test/benchmarks/network/BUILD2
-rw-r--r--test/benchmarks/network/httpd_test.go204
-rw-r--r--test/benchmarks/network/iperf_test.go49
-rw-r--r--test/benchmarks/network/nginx_test.go104
-rw-r--r--test/benchmarks/network/node_test.go148
-rw-r--r--test/benchmarks/tools/BUILD29
-rw-r--r--test/benchmarks/tools/ab.go94
-rw-r--r--test/benchmarks/tools/ab_test.go90
-rw-r--r--test/benchmarks/tools/fio.go124
-rw-r--r--test/benchmarks/tools/fio_test.go122
-rw-r--r--test/benchmarks/tools/hey.go75
-rw-r--r--test/benchmarks/tools/hey_test.go81
-rw-r--r--test/benchmarks/tools/iperf.go56
-rw-r--r--test/benchmarks/tools/iperf_test.go34
-rw-r--r--test/benchmarks/tools/redis.go64
-rw-r--r--test/benchmarks/tools/redis_test.go87
-rw-r--r--test/benchmarks/tools/tools.go17
-rw-r--r--test/fuse/README.md25
-rw-r--r--test/fuse/linux/BUILD2
-rw-r--r--test/fuse/linux/fuse_base.cc2
-rw-r--r--test/iptables/BUILD1
-rw-r--r--test/iptables/iptables_test.go8
-rw-r--r--test/iptables/iptables_unsafe.go63
-rw-r--r--test/iptables/iptables_util.go51
-rw-r--r--test/iptables/nat.go152
-rw-r--r--test/runtimes/BUILD1
-rw-r--r--test/runtimes/defs.bzl6
-rw-r--r--test/runtimes/exclude_java11.csv93
-rw-r--r--test/runtimes/exclude_nodejs12.4.0.csv70
-rw-r--r--test/runtimes/proctor/java.go7
-rw-r--r--test/runtimes/proctor/nodejs.go2
-rw-r--r--test/runtimes/runner/main.go2
-rw-r--r--test/syscalls/linux/inotify.cc55
-rw-r--r--test/syscalls/linux/mount.cc14
-rw-r--r--test/syscalls/linux/raw_socket_hdrincl.cc4
-rw-r--r--test/syscalls/linux/socket_netlink_route.cc5
40 files changed, 1572 insertions, 716 deletions
diff --git a/test/benchmarks/database/BUILD b/test/benchmarks/database/BUILD
index 5e33465cd..572db665f 100644
--- a/test/benchmarks/database/BUILD
+++ b/test/benchmarks/database/BUILD
@@ -24,5 +24,6 @@ go_test(
deps = [
"//pkg/test/dockerutil",
"//test/benchmarks/harness",
+ "//test/benchmarks/tools",
],
)
diff --git a/test/benchmarks/database/redis_test.go b/test/benchmarks/database/redis_test.go
index 6d39f4d66..394fce820 100644
--- a/test/benchmarks/database/redis_test.go
+++ b/test/benchmarks/database/redis_test.go
@@ -16,15 +16,12 @@ package database
import (
"context"
- "fmt"
- "regexp"
- "strconv"
- "strings"
"testing"
"time"
"gvisor.dev/gvisor/pkg/test/dockerutil"
"gvisor.dev/gvisor/test/benchmarks/harness"
+ "gvisor.dev/gvisor/test/benchmarks/tools"
)
// All possible operations from redis. Note: "ping" will
@@ -99,16 +96,10 @@ func BenchmarkRedis(b *testing.B) {
b.Fatalf("failed to start redis with: %v", err)
}
- // runs redis benchmark -t operation for 100K requests against server.
- cmd := strings.Split(
- fmt.Sprintf("redis-benchmark --csv -t %s -h %s -p %d", operation, ip, serverPort), " ")
-
- // There is no -t PING_BULK for redis-benchmark, so adjust the command in that case.
- // Note that "ping" will run both PING_INLINE and PING_BULK.
- if operation == "PING_BULK" {
- cmd = strings.Split(
- fmt.Sprintf("redis-benchmark --csv -t ping -h %s -p %d", ip, serverPort), " ")
+ redis := tools.Redis{
+ Operation: operation,
}
+
// Reset profiles and timer to begin the measurement.
server.RestartProfiles()
b.ResetTimer()
@@ -117,81 +108,16 @@ func BenchmarkRedis(b *testing.B) {
defer client.CleanUp(ctx)
out, err := client.Run(ctx, dockerutil.RunOpts{
Image: "benchmarks/redis",
- }, cmd...)
+ }, redis.MakeCmd(ip, serverPort)...)
if err != nil {
b.Fatalf("redis-benchmark failed with: %v", err)
}
// Stop time while we parse results.
b.StopTimer()
- result, err := parseOperation(operation, out)
- if err != nil {
- b.Fatalf("parsing result %s failed with err: %v", out, err)
- }
- b.ReportMetric(result, operation) // operations per second
+ redis.Report(b, out)
b.StartTimer()
}
})
}
}
-
-// parseOperation grabs the metric operations per second from redis-benchmark output.
-func parseOperation(operation, data string) (float64, error) {
- re := regexp.MustCompile(fmt.Sprintf(`"%s( .*)?","(\d*\.\d*)"`, operation))
- match := re.FindStringSubmatch(data)
- // If no match, simply don't add it to the result map.
- if len(match) < 3 {
- return 0.0, fmt.Errorf("could not find %s in %s", operation, data)
- }
- return strconv.ParseFloat(match[2], 64)
-}
-
-// TestParser tests the parser on sample data.
-func TestParser(t *testing.T) {
- sampleData := `
- "PING_INLINE","48661.80"
- "PING_BULK","50301.81"
- "SET","48923.68"
- "GET","49382.71"
- "INCR","49975.02"
- "LPUSH","49875.31"
- "RPUSH","50276.52"
- "LPOP","50327.12"
- "RPOP","50556.12"
- "SADD","49504.95"
- "HSET","49504.95"
- "SPOP","50025.02"
- "LPUSH (needed to benchmark LRANGE)","48875.86"
- "LRANGE_100 (first 100 elements)","33955.86"
- "LRANGE_300 (first 300 elements)","16550.81"
- "LRANGE_500 (first 450 elements)","13653.74"
- "LRANGE_600 (first 600 elements)","11219.57"
- "MSET (10 keys)","44682.75"
- `
- wants := map[string]float64{
- "PING_INLINE": 48661.80,
- "PING_BULK": 50301.81,
- "SET": 48923.68,
- "GET": 49382.71,
- "INCR": 49975.02,
- "LPUSH": 49875.31,
- "RPUSH": 50276.52,
- "LPOP": 50327.12,
- "RPOP": 50556.12,
- "SADD": 49504.95,
- "HSET": 49504.95,
- "SPOP": 50025.02,
- "LRANGE_100": 33955.86,
- "LRANGE_300": 16550.81,
- "LRANGE_500": 13653.74,
- "LRANGE_600": 11219.57,
- "MSET": 44682.75,
- }
- for op, want := range wants {
- if got, err := parseOperation(op, sampleData); err != nil {
- t.Fatalf("failed to parse %s: %v", op, err)
- } else if want != got {
- t.Fatalf("wanted %f for op %s, got %f", want, op, got)
- }
- }
-}
diff --git a/test/benchmarks/fs/BUILD b/test/benchmarks/fs/BUILD
index 79327b57c..20654d88f 100644
--- a/test/benchmarks/fs/BUILD
+++ b/test/benchmarks/fs/BUILD
@@ -25,6 +25,7 @@ go_test(
deps = [
"//pkg/test/dockerutil",
"//test/benchmarks/harness",
+ "//test/benchmarks/tools",
"@com_github_docker_docker//api/types/mount:go_default_library",
],
)
diff --git a/test/benchmarks/fs/fio_test.go b/test/benchmarks/fs/fio_test.go
index 75d52726a..65874ed8b 100644
--- a/test/benchmarks/fs/fio_test.go
+++ b/test/benchmarks/fs/fio_test.go
@@ -15,72 +15,47 @@ package fs
import (
"context"
- "encoding/json"
"fmt"
"path/filepath"
- "strconv"
"strings"
"testing"
"github.com/docker/docker/api/types/mount"
"gvisor.dev/gvisor/pkg/test/dockerutil"
"gvisor.dev/gvisor/test/benchmarks/harness"
+ "gvisor.dev/gvisor/test/benchmarks/tools"
)
-type fioTestCase struct {
- test string // test to run: read, write, randread, randwrite.
- size string // total size to be read/written of format N[GMK] (e.g. 5G).
- blocksize string // blocksize to be read/write of format N[GMK] (e.g. 4K).
- iodepth int // iodepth for reads/writes.
- time int // time to run the test in seconds, usually for rand(read/write).
-}
-
-// makeCmdFromTestcase makes a fio command.
-func (f *fioTestCase) makeCmdFromTestcase(filename string) []string {
- cmd := []string{"fio", "--output-format=json", "--ioengine=sync"}
- cmd = append(cmd, fmt.Sprintf("--name=%s", f.test))
- cmd = append(cmd, fmt.Sprintf("--size=%s", f.size))
- cmd = append(cmd, fmt.Sprintf("--blocksize=%s", f.blocksize))
- cmd = append(cmd, fmt.Sprintf("--filename=%s", filename))
- cmd = append(cmd, fmt.Sprintf("--iodepth=%d", f.iodepth))
- cmd = append(cmd, fmt.Sprintf("--rw=%s", f.test))
- if f.time != 0 {
- cmd = append(cmd, "--time_based")
- cmd = append(cmd, fmt.Sprintf("--runtime=%d", f.time))
- }
- return cmd
-}
-
// BenchmarkFio runs fio on the runtime under test. There are 4 basic test
// cases each run on a tmpfs mount and a bind mount. Fio requires root so that
// caches can be dropped.
func BenchmarkFio(b *testing.B) {
- testCases := []fioTestCase{
- fioTestCase{
- test: "write",
- size: "5G",
- blocksize: "1M",
- iodepth: 4,
+ testCases := []tools.Fio{
+ tools.Fio{
+ Test: "write",
+ Size: "5G",
+ Blocksize: "1M",
+ Iodepth: 4,
},
- fioTestCase{
- test: "read",
- size: "5G",
- blocksize: "1M",
- iodepth: 4,
+ tools.Fio{
+ Test: "read",
+ Size: "5G",
+ Blocksize: "1M",
+ Iodepth: 4,
},
- fioTestCase{
- test: "randwrite",
- size: "5G",
- blocksize: "4K",
- iodepth: 4,
- time: 30,
+ tools.Fio{
+ Test: "randwrite",
+ Size: "5G",
+ Blocksize: "4K",
+ Iodepth: 4,
+ Time: 30,
},
- fioTestCase{
- test: "randread",
- size: "5G",
- blocksize: "4K",
- iodepth: 4,
- time: 30,
+ tools.Fio{
+ Test: "randread",
+ Size: "5G",
+ Blocksize: "4K",
+ Iodepth: 4,
+ Time: 30,
},
}
@@ -92,7 +67,7 @@ func BenchmarkFio(b *testing.B) {
for _, fsType := range []mount.Type{mount.TypeBind, mount.TypeTmpfs} {
for _, tc := range testCases {
- testName := strings.Title(tc.test) + strings.Title(string(fsType))
+ testName := strings.Title(tc.Test) + strings.Title(string(fsType))
b.Run(testName, func(b *testing.B) {
ctx := context.Background()
container := machine.GetContainer(ctx, b)
@@ -109,7 +84,6 @@ func BenchmarkFio(b *testing.B) {
b.Fatalf("failed to make mount: %v", err)
}
defer mountCleanup()
- cmd := tc.makeCmdFromTestcase(outfile)
// Start the container with the mount.
if err := container.Spawn(
@@ -127,8 +101,8 @@ func BenchmarkFio(b *testing.B) {
}
// For reads, we need a file to read so make one inside the container.
- if strings.Contains(tc.test, "read") {
- fallocateCmd := fmt.Sprintf("fallocate -l %s %s", tc.size, outfile)
+ if strings.Contains(tc.Test, "read") {
+ fallocateCmd := fmt.Sprintf("fallocate -l %s %s", tc.Size, outfile)
if out, err := container.Exec(ctx, dockerutil.ExecOpts{},
strings.Split(fallocateCmd, " ")...); err != nil {
b.Fatalf("failed to create readable file on mount: %v, %s", err, out)
@@ -139,6 +113,7 @@ func BenchmarkFio(b *testing.B) {
if err := harness.DropCaches(machine); err != nil {
b.Skipf("failed to drop caches with %v. You probably need root.", err)
}
+ cmd := tc.MakeCmd(outfile)
container.RestartProfiles()
b.ResetTimer()
for i := 0; i < b.N; i++ {
@@ -148,19 +123,7 @@ func BenchmarkFio(b *testing.B) {
b.Fatalf("failed to run cmd %v: %v", cmd, err)
}
b.StopTimer()
- // Parse the output and report the metrics.
- isRead := strings.Contains(tc.test, "read")
- bw, err := parseBandwidth(data, isRead)
- if err != nil {
- b.Fatalf("failed to parse bandwidth from %s with: %v", data, err)
- }
- b.ReportMetric(bw, "bandwidth") // in b/s.
-
- iops, err := parseIOps(data, isRead)
- if err != nil {
- b.Fatalf("failed to parse iops from %s with: %v", data, err)
- }
- b.ReportMetric(iops, "iops")
+ tc.Report(b, data)
// If b.N is used (i.e. we run for an hour), we should drop caches
// after each run.
if err := harness.DropCaches(machine); err != nil {
@@ -205,165 +168,3 @@ func makeMount(machine harness.Machine, mountType mount.Type, target string) (mo
return mount.Mount{}, func() {}, fmt.Errorf("illegal mount time not supported: %v", mountType)
}
}
-
-// parseBandwidth reports the bandwidth in b/s.
-func parseBandwidth(data string, isRead bool) (float64, error) {
- if isRead {
- result, err := parseFioJSON(data, "read", "bw")
- if err != nil {
- return 0, err
- }
- return 1024 * result, nil
- }
- result, err := parseFioJSON(data, "write", "bw")
- if err != nil {
- return 0, err
- }
- return 1024 * result, nil
-}
-
-// parseIOps reports the write IO per second metric.
-func parseIOps(data string, isRead bool) (float64, error) {
- if isRead {
- return parseFioJSON(data, "read", "iops")
- }
- return parseFioJSON(data, "write", "iops")
-}
-
-// fioResult is for parsing FioJSON.
-type fioResult struct {
- Jobs []fioJob
-}
-
-// fioJob is for parsing FioJSON.
-type fioJob map[string]json.RawMessage
-
-// fioMetrics is for parsing FioJSON.
-type fioMetrics map[string]json.RawMessage
-
-// parseFioJSON parses data and grabs "op" (read or write) and "metric"
-// (bw or iops) from the JSON.
-func parseFioJSON(data, op, metric string) (float64, error) {
- var result fioResult
- if err := json.Unmarshal([]byte(data), &result); err != nil {
- return 0, fmt.Errorf("could not unmarshal data: %v", err)
- }
-
- if len(result.Jobs) < 1 {
- return 0, fmt.Errorf("no jobs present to parse")
- }
-
- var metrics fioMetrics
- if err := json.Unmarshal(result.Jobs[0][op], &metrics); err != nil {
- return 0, fmt.Errorf("could not unmarshal jobs: %v", err)
- }
-
- if _, ok := metrics[metric]; !ok {
- return 0, fmt.Errorf("no metric found for op: %s", op)
- }
- return strconv.ParseFloat(string(metrics[metric]), 64)
-}
-
-// TestParsers tests that the parsers work on sampleData.
-func TestParsers(t *testing.T) {
- sampleData := `
-{
- "fio version" : "fio-3.1",
- "timestamp" : 1554837456,
- "timestamp_ms" : 1554837456621,
- "time" : "Tue Apr 9 19:17:36 2019",
- "jobs" : [
- {
- "jobname" : "test",
- "groupid" : 0,
- "error" : 0,
- "eta" : 2147483647,
- "elapsed" : 1,
- "job options" : {
- "name" : "test",
- "ioengine" : "sync",
- "size" : "1073741824",
- "filename" : "/disk/file.dat",
- "iodepth" : "4",
- "bs" : "4096",
- "rw" : "write"
- },
- "read" : {
- "io_bytes" : 0,
- "io_kbytes" : 0,
- "bw" : 123456,
- "iops" : 1234.5678,
- "runtime" : 0,
- "total_ios" : 0,
- "short_ios" : 0,
- "bw_min" : 0,
- "bw_max" : 0,
- "bw_agg" : 0.000000,
- "bw_mean" : 0.000000,
- "bw_dev" : 0.000000,
- "bw_samples" : 0,
- "iops_min" : 0,
- "iops_max" : 0,
- "iops_mean" : 0.000000,
- "iops_stddev" : 0.000000,
- "iops_samples" : 0
- },
- "write" : {
- "io_bytes" : 1073741824,
- "io_kbytes" : 1048576,
- "bw" : 1753471,
- "iops" : 438367.892977,
- "runtime" : 598,
- "total_ios" : 262144,
- "bw_min" : 1731120,
- "bw_max" : 1731120,
- "bw_agg" : 98.725328,
- "bw_mean" : 1731120.000000,
- "bw_dev" : 0.000000,
- "bw_samples" : 1,
- "iops_min" : 432780,
- "iops_max" : 432780,
- "iops_mean" : 432780.000000,
- "iops_stddev" : 0.000000,
- "iops_samples" : 1
- }
- }
- ]
-}
-`
- // WriteBandwidth.
- got, err := parseBandwidth(sampleData, false)
- var want float64 = 1753471.0 * 1024
- if err != nil {
- t.Fatalf("parse failed with err: %v", err)
- } else if got != want {
- t.Fatalf("got: %f, want: %f", got, want)
- }
-
- // ReadBandwidth.
- got, err = parseBandwidth(sampleData, true)
- want = 123456 * 1024
- if err != nil {
- t.Fatalf("parse failed with err: %v", err)
- } else if got != want {
- t.Fatalf("got: %f, want: %f", got, want)
- }
-
- // WriteIOps.
- got, err = parseIOps(sampleData, false)
- want = 438367.892977
- if err != nil {
- t.Fatalf("parse failed with err: %v", err)
- } else if got != want {
- t.Fatalf("got: %f, want: %f", got, want)
- }
-
- // ReadIOps.
- got, err = parseIOps(sampleData, true)
- want = 1234.5678
- if err != nil {
- t.Fatalf("parse failed with err: %v", err)
- } else if got != want {
- t.Fatalf("got: %f, want: %f", got, want)
- }
-}
diff --git a/test/benchmarks/network/BUILD b/test/benchmarks/network/BUILD
index b47400590..d15cd55ee 100644
--- a/test/benchmarks/network/BUILD
+++ b/test/benchmarks/network/BUILD
@@ -15,6 +15,7 @@ go_test(
srcs = [
"httpd_test.go",
"iperf_test.go",
+ "nginx_test.go",
"node_test.go",
],
library = ":network",
@@ -27,5 +28,6 @@ go_test(
"//pkg/test/dockerutil",
"//pkg/test/testutil",
"//test/benchmarks/harness",
+ "//test/benchmarks/tools",
],
)
diff --git a/test/benchmarks/network/httpd_test.go b/test/benchmarks/network/httpd_test.go
index fe23ca949..336e04c91 100644
--- a/test/benchmarks/network/httpd_test.go
+++ b/test/benchmarks/network/httpd_test.go
@@ -16,12 +16,11 @@ package network
import (
"context"
"fmt"
- "regexp"
- "strconv"
"testing"
"gvisor.dev/gvisor/pkg/test/dockerutil"
"gvisor.dev/gvisor/test/benchmarks/harness"
+ "gvisor.dev/gvisor/test/benchmarks/tools"
)
// see Dockerfile '//images/benchmarks/httpd'.
@@ -52,20 +51,35 @@ func BenchmarkHttpdConcurrency(b *testing.B) {
defer serverMachine.CleanUp()
// The test iterates over client concurrency, so set other parameters.
- requests := 10000
- concurrency := []int{1, 5, 10, 25}
- doc := docs["10Kb"]
+ concurrency := []int{1, 25, 50, 100, 1000}
for _, c := range concurrency {
b.Run(fmt.Sprintf("%d", c), func(b *testing.B) {
- runHttpd(b, clientMachine, serverMachine, doc, requests, c)
+ hey := &tools.Hey{
+ Requests: 10000,
+ Concurrency: c,
+ Doc: docs["10Kb"],
+ }
+ runHttpd(b, clientMachine, serverMachine, hey, false /* reverse */)
})
}
}
// BenchmarkHttpdDocSize iterates over different sized payloads, testing how
-// well the runtime handles different payload sizes.
+// well the runtime handles sending different payload sizes.
func BenchmarkHttpdDocSize(b *testing.B) {
+ benchmarkHttpdDocSize(b, false /* reverse */)
+}
+
+// BenchmarkReverseHttpdDocSize iterates over different sized payloads, testing
+// how well the runtime handles receiving different payload sizes.
+func BenchmarkReverseHttpdDocSize(b *testing.B) {
+ benchmarkHttpdDocSize(b, true /* reverse */)
+}
+
+func benchmarkHttpdDocSize(b *testing.B, reverse bool) {
+ b.Helper()
+
clientMachine, err := h.GetMachine()
if err != nil {
b.Fatalf("failed to get machine: %v", err)
@@ -78,31 +92,42 @@ func BenchmarkHttpdDocSize(b *testing.B) {
}
defer serverMachine.CleanUp()
- requests := 10000
- concurrency := 1
-
for name, filename := range docs {
- b.Run(name, func(b *testing.B) {
- runHttpd(b, clientMachine, serverMachine, filename, requests, concurrency)
- })
+ concurrency := []int{1, 25, 50, 100, 1000}
+ for _, c := range concurrency {
+ b.Run(fmt.Sprintf("%s_%d", name, c), func(b *testing.B) {
+ hey := &tools.Hey{
+ Requests: 10000,
+ Concurrency: c,
+ Doc: filename,
+ }
+ runHttpd(b, clientMachine, serverMachine, hey, reverse)
+ })
+ }
}
}
// runHttpd runs a single test run.
-func runHttpd(b *testing.B, clientMachine, serverMachine harness.Machine, doc string, requests, concurrency int) {
+func runHttpd(b *testing.B, clientMachine, serverMachine harness.Machine, hey *tools.Hey, reverse bool) {
b.Helper()
// Grab a container from the server.
ctx := context.Background()
- server := serverMachine.GetContainer(ctx, b)
+ var server *dockerutil.Container
+ if reverse {
+ server = serverMachine.GetNativeContainer(ctx, b)
+ } else {
+ server = serverMachine.GetContainer(ctx, b)
+ }
+
defer server.CleanUp(ctx)
// Copy the docs to /tmp and serve from there.
- cmd := "mkdir -p /tmp/html; cp -r /local /tmp/html/.; apache2 -X"
+ cmd := "mkdir -p /tmp/html; cp -r /local/* /tmp/html/.; apache2 -X"
port := 80
// Start the server.
- server.Spawn(ctx, dockerutil.RunOpts{
+ if err := server.Spawn(ctx, dockerutil.RunOpts{
Image: "benchmarks/httpd",
Ports: []int{port},
Env: []string{
@@ -113,7 +138,9 @@ func runHttpd(b *testing.B, clientMachine, serverMachine harness.Machine, doc st
"APACHE_LOG_DIR=/tmp",
"APACHE_PID_FILE=/tmp/apache.pid",
},
- }, "sh", "-c", cmd)
+ }, "sh", "-c", cmd); err != nil {
+ b.Fatalf("failed to start server: %v", err)
+ }
ip, err := serverMachine.IPAddress()
if err != nil {
@@ -128,150 +155,27 @@ func runHttpd(b *testing.B, clientMachine, serverMachine harness.Machine, doc st
// Check the server is serving.
harness.WaitUntilServing(ctx, clientMachine, ip, servingPort)
+ var client *dockerutil.Container
// Grab a client.
- client := clientMachine.GetNativeContainer(ctx, b)
+ if reverse {
+ client = clientMachine.GetContainer(ctx, b)
+ } else {
+ client = clientMachine.GetNativeContainer(ctx, b)
+ }
defer client.CleanUp(ctx)
- path := fmt.Sprintf("http://%s:%d/%s", ip, servingPort, doc)
- // See apachebench (ab) for flags.
- cmd = fmt.Sprintf("ab -n %d -c %d %s", requests, concurrency, path)
-
b.ResetTimer()
server.RestartProfiles()
for i := 0; i < b.N; i++ {
out, err := client.Run(ctx, dockerutil.RunOpts{
- Image: "benchmarks/ab",
- }, "sh", "-c", cmd)
+ Image: "benchmarks/hey",
+ }, hey.MakeCmd(ip, servingPort)...)
if err != nil {
b.Fatalf("run failed with: %v", err)
}
b.StopTimer()
-
- // Parse and report custom metrics.
- transferRate, err := parseTransferRate(out)
- if err != nil {
- b.Logf("failed to parse transferrate: %v", err)
- }
- b.ReportMetric(transferRate*1024, "transfer_rate") // Convert from Kb/s to b/s.
-
- latency, err := parseLatency(out)
- if err != nil {
- b.Logf("failed to parse latency: %v", err)
- }
- b.ReportMetric(latency/1000, "mean_latency") // Convert from ms to s.
-
- reqPerSecond, err := parseRequestsPerSecond(out)
- if err != nil {
- b.Logf("failed to parse requests per second: %v", err)
- }
- b.ReportMetric(reqPerSecond, "requests_per_second")
-
+ hey.Report(b, out)
b.StartTimer()
}
}
-
-var transferRateRE = regexp.MustCompile(`Transfer rate:\s+(\d+\.?\d+?)\s+\[Kbytes/sec\]\s+received`)
-
-// parseTransferRate parses transfer rate from apachebench output.
-func parseTransferRate(data string) (float64, error) {
- match := transferRateRE.FindStringSubmatch(data)
- if len(match) < 2 {
- return 0, fmt.Errorf("failed get bandwidth: %s", data)
- }
- return strconv.ParseFloat(match[1], 64)
-}
-
-var latencyRE = regexp.MustCompile(`Total:\s+\d+\s+(\d+)\s+(\d+\.?\d+?)\s+\d+\s+\d+\s`)
-
-// parseLatency parses latency from apachebench output.
-func parseLatency(data string) (float64, error) {
- match := latencyRE.FindStringSubmatch(data)
- if len(match) < 2 {
- return 0, fmt.Errorf("failed get bandwidth: %s", data)
- }
- return strconv.ParseFloat(match[1], 64)
-}
-
-var requestsPerSecondRE = regexp.MustCompile(`Requests per second:\s+(\d+\.?\d+?)\s+`)
-
-// parseRequestsPerSecond parses requests per second from apachebench output.
-func parseRequestsPerSecond(data string) (float64, error) {
- match := requestsPerSecondRE.FindStringSubmatch(data)
- if len(match) < 2 {
- return 0, fmt.Errorf("failed get bandwidth: %s", data)
- }
- return strconv.ParseFloat(match[1], 64)
-}
-
-// Sample output from apachebench.
-const sampleData = `This is ApacheBench, Version 2.3 <$Revision: 1826891 $>
-Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
-Licensed to The Apache Software Foundation, http://www.apache.org/
-
-Benchmarking 10.10.10.10 (be patient).....done
-
-
-Server Software: Apache/2.4.38
-Server Hostname: 10.10.10.10
-Server Port: 80
-
-Document Path: /latin10k.txt
-Document Length: 210 bytes
-
-Concurrency Level: 1
-Time taken for tests: 0.180 seconds
-Complete requests: 100
-Failed requests: 0
-Non-2xx responses: 100
-Total transferred: 38800 bytes
-HTML transferred: 21000 bytes
-Requests per second: 556.44 [#/sec] (mean)
-Time per request: 1.797 [ms] (mean)
-Time per request: 1.797 [ms] (mean, across all concurrent requests)
-Transfer rate: 210.84 [Kbytes/sec] received
-
-Connection Times (ms)
- min mean[+/-sd] median max
-Connect: 0 0 0.2 0 2
-Processing: 1 2 1.0 1 8
-Waiting: 1 1 1.0 1 7
-Total: 1 2 1.2 1 10
-
-Percentage of the requests served within a certain time (ms)
- 50% 1
- 66% 2
- 75% 2
- 80% 2
- 90% 2
- 95% 3
- 98% 7
- 99% 10
- 100% 10 (longest request)`
-
-// TestParsers checks the parsers work.
-func TestParsers(t *testing.T) {
- want := 210.84
- got, err := parseTransferRate(sampleData)
- if err != nil {
- t.Fatalf("failed to parse transfer rate with error: %v", err)
- } else if got != want {
- t.Fatalf("parseTransferRate got: %f, want: %f", got, want)
- }
-
- want = 2.0
- got, err = parseLatency(sampleData)
- if err != nil {
- t.Fatalf("failed to parse transfer rate with error: %v", err)
- } else if got != want {
- t.Fatalf("parseLatency got: %f, want: %f", got, want)
- }
-
- want = 556.44
- got, err = parseRequestsPerSecond(sampleData)
- if err != nil {
- t.Fatalf("failed to parse transfer rate with error: %v", err)
- } else if got != want {
- t.Fatalf("parseRequestsPerSecond got: %f, want: %f", got, want)
- }
-}
diff --git a/test/benchmarks/network/iperf_test.go b/test/benchmarks/network/iperf_test.go
index a5e198e14..b8ab7dfb8 100644
--- a/test/benchmarks/network/iperf_test.go
+++ b/test/benchmarks/network/iperf_test.go
@@ -15,19 +15,18 @@ package network
import (
"context"
- "fmt"
- "regexp"
- "strconv"
- "strings"
"testing"
"gvisor.dev/gvisor/pkg/test/dockerutil"
"gvisor.dev/gvisor/pkg/test/testutil"
"gvisor.dev/gvisor/test/benchmarks/harness"
+ "gvisor.dev/gvisor/test/benchmarks/tools"
)
func BenchmarkIperf(b *testing.B) {
- const time = 10 // time in seconds to run the client.
+ iperf := tools.Iperf{
+ Time: 10, // time in seconds to run client.
+ }
clientMachine, err := h.GetMachine()
if err != nil {
@@ -92,10 +91,6 @@ func BenchmarkIperf(b *testing.B) {
if err := harness.WaitUntilServing(ctx, clientMachine, ip, servingPort); err != nil {
b.Fatalf("failed to wait for server: %v", err)
}
-
- // iperf report in Kb realtime
- cmd := fmt.Sprintf("iperf -f K --realtime --time %d -c %s -p %d", time, ip.String(), servingPort)
-
// Run the client.
b.ResetTimer()
@@ -105,46 +100,14 @@ func BenchmarkIperf(b *testing.B) {
for i := 0; i < b.N; i++ {
out, err := client.Run(ctx, dockerutil.RunOpts{
Image: "benchmarks/iperf",
- }, strings.Split(cmd, " ")...)
+ }, iperf.MakeCmd(ip, servingPort)...)
if err != nil {
b.Fatalf("failed to run client: %v", err)
}
b.StopTimer()
-
- // Parse bandwidth and report it.
- bW, err := bandwidth(out)
- if err != nil {
- b.Fatalf("failed to parse bandwitdth from %s: %v", out, err)
- }
- b.ReportMetric(bW*1024, "bandwidth") // Convert from Kb/s to b/s.
+ iperf.Report(b, out)
b.StartTimer()
}
})
}
}
-
-// bandwidth parses the Bandwidth number from an iperf report. A sample is below.
-func bandwidth(data string) (float64, error) {
- re := regexp.MustCompile(`\[\s*\d+\][^\n]+\s+(\d+\.?\d*)\s+KBytes/sec`)
- match := re.FindStringSubmatch(data)
- if len(match) < 1 {
- return 0, fmt.Errorf("failed get bandwidth: %s", data)
- }
- return strconv.ParseFloat(match[1], 64)
-}
-
-func TestParser(t *testing.T) {
- sampleData := `
-------------------------------------------------------------
-Client connecting to 10.138.15.215, TCP port 32779
-TCP window size: 45.0 KByte (default)
-------------------------------------------------------------
-[ 3] local 10.138.15.216 port 32866 connected with 10.138.15.215 port 32779
-[ ID] Interval Transfer Bandwidth
-[ 3] 0.0-10.0 sec 459520 KBytes 45900 KBytes/sec
-`
- bandwidth, err := bandwidth(sampleData)
- if err != nil || bandwidth != 45900 {
- t.Fatalf("failed with: %v and %f", err, bandwidth)
- }
-}
diff --git a/test/benchmarks/network/nginx_test.go b/test/benchmarks/network/nginx_test.go
new file mode 100644
index 000000000..5965652a5
--- /dev/null
+++ b/test/benchmarks/network/nginx_test.go
@@ -0,0 +1,104 @@
+// Copyright 2020 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 network
+
+import (
+ "context"
+ "fmt"
+ "testing"
+
+ "gvisor.dev/gvisor/pkg/test/dockerutil"
+ "gvisor.dev/gvisor/test/benchmarks/harness"
+ "gvisor.dev/gvisor/test/benchmarks/tools"
+)
+
+// BenchmarkNginxConcurrency iterates the concurrency argument and tests
+// how well the runtime under test handles requests in parallel.
+// TODO(zkoopmans): Update with different doc sizes like Httpd.
+func BenchmarkNginxConcurrency(b *testing.B) {
+ // Grab a machine for the client and server.
+ clientMachine, err := h.GetMachine()
+ if err != nil {
+ b.Fatalf("failed to get client: %v", err)
+ }
+ defer clientMachine.CleanUp()
+
+ serverMachine, err := h.GetMachine()
+ if err != nil {
+ b.Fatalf("failed to get server: %v", err)
+ }
+ defer serverMachine.CleanUp()
+
+ concurrency := []int{1, 5, 10, 25}
+ for _, c := range concurrency {
+ b.Run(fmt.Sprintf("%d", c), func(b *testing.B) {
+ hey := &tools.Hey{
+ Requests: 10000,
+ Concurrency: c,
+ }
+ runNginx(b, clientMachine, serverMachine, hey)
+ })
+ }
+}
+
+// runHttpd runs a single test run.
+func runNginx(b *testing.B, clientMachine, serverMachine harness.Machine, hey *tools.Hey) {
+ b.Helper()
+
+ // Grab a container from the server.
+ ctx := context.Background()
+ server := serverMachine.GetContainer(ctx, b)
+ defer server.CleanUp(ctx)
+
+ port := 80
+ // Start the server.
+ if err := server.Spawn(ctx,
+ dockerutil.RunOpts{
+ Image: "benchmarks/nginx",
+ Ports: []int{port},
+ }); err != nil {
+ b.Fatalf("server failed to start: %v", err)
+ }
+
+ ip, err := serverMachine.IPAddress()
+ if err != nil {
+ b.Fatalf("failed to find server ip: %v", err)
+ }
+
+ servingPort, err := server.FindPort(ctx, port)
+ if err != nil {
+ b.Fatalf("failed to find server port %d: %v", port, err)
+ }
+
+ // Check the server is serving.
+ harness.WaitUntilServing(ctx, clientMachine, ip, servingPort)
+
+ // Grab a client.
+ client := clientMachine.GetNativeContainer(ctx, b)
+ defer client.CleanUp(ctx)
+
+ b.ResetTimer()
+ server.RestartProfiles()
+ for i := 0; i < b.N; i++ {
+ out, err := client.Run(ctx, dockerutil.RunOpts{
+ Image: "benchmarks/hey",
+ }, hey.MakeCmd(ip, servingPort)...)
+ if err != nil {
+ b.Fatalf("run failed with: %v", err)
+ }
+ b.StopTimer()
+ hey.Report(b, out)
+ b.StartTimer()
+ }
+}
diff --git a/test/benchmarks/network/node_test.go b/test/benchmarks/network/node_test.go
index f9278ab66..5b568cfe5 100644
--- a/test/benchmarks/network/node_test.go
+++ b/test/benchmarks/network/node_test.go
@@ -16,14 +16,12 @@ package network
import (
"context"
"fmt"
- "regexp"
- "strconv"
- "strings"
"testing"
"time"
"gvisor.dev/gvisor/pkg/test/dockerutil"
"gvisor.dev/gvisor/test/benchmarks/harness"
+ "gvisor.dev/gvisor/test/benchmarks/tools"
)
// BenchmarkNode runs 10K requests using 'hey' against a Node server run on
@@ -36,13 +34,17 @@ func BenchmarkNode(b *testing.B) {
for _, c := range concurrency {
b.Run(fmt.Sprintf("Concurrency%d", c), func(b *testing.B) {
- runNode(b, requests, c)
+ hey := &tools.Hey{
+ Requests: requests,
+ Concurrency: c,
+ }
+ runNode(b, hey)
})
}
}
// runNode runs the test for a given # of requests and concurrency.
-func runNode(b *testing.B, requests, concurrency int) {
+func runNode(b *testing.B, hey *tools.Hey) {
b.Helper()
// The machine to hold Redis and the Node Server.
@@ -106,7 +108,7 @@ func runNode(b *testing.B, requests, concurrency int) {
// Wait until the Client sees the server as up.
harness.WaitUntilServing(ctx, clientMachine, servingIP, servingPort)
- heyCmd := strings.Split(fmt.Sprintf("hey -n %d -c %d http://%s:%d/", requests, concurrency, servingIP, servingPort), " ")
+ heyCmd := hey.MakeCmd(servingIP, servingPort)
nodeApp.RestartProfiles()
b.ResetTimer()
@@ -123,139 +125,7 @@ func runNode(b *testing.B, requests, concurrency int) {
// Stop the timer to parse the data and report stats.
b.StopTimer()
- requests, err := parseHeyRequestsPerSecond(out)
- if err != nil {
- b.Fatalf("failed to parse requests per second: %v", err)
- }
- b.ReportMetric(requests, "requests_per_second")
-
- bw, err := parseHeyBandwidth(out)
- if err != nil {
- b.Fatalf("failed to parse bandwidth: %v", err)
- }
- b.ReportMetric(bw, "bandwidth")
-
- ave, err := parseHeyAverageLatency(out)
- if err != nil {
- b.Fatalf("failed to parse average latency: %v", err)
- }
- b.ReportMetric(ave, "average_latency")
+ hey.Report(b, out)
b.StartTimer()
}
}
-
-var heyReqPerSecondRE = regexp.MustCompile(`Requests/sec:\s*(\d+\.?\d+?)\s+`)
-
-// parseHeyRequestsPerSecond finds requests per second from hey output.
-func parseHeyRequestsPerSecond(data string) (float64, error) {
- match := heyReqPerSecondRE.FindStringSubmatch(data)
- if len(match) < 2 {
- return 0, fmt.Errorf("failed get bandwidth: %s", data)
- }
- return strconv.ParseFloat(match[1], 64)
-}
-
-var heyAverageLatencyRE = regexp.MustCompile(`Average:\s*(\d+\.?\d+?)\s+secs`)
-
-// parseHeyAverageLatency finds Average Latency in seconds form hey output.
-func parseHeyAverageLatency(data string) (float64, error) {
- match := heyAverageLatencyRE.FindStringSubmatch(data)
- if len(match) < 2 {
- return 0, fmt.Errorf("failed get average latency match%d : %s", len(match), data)
- }
- return strconv.ParseFloat(match[1], 64)
-}
-
-var heySizePerRequestRE = regexp.MustCompile(`Size/request:\s*(\d+\.?\d+?)\s+bytes`)
-
-// parseHeyBandwidth computes bandwidth from request/sec * bytes/request
-// and reports in bytes/second.
-func parseHeyBandwidth(data string) (float64, error) {
- match := heyReqPerSecondRE.FindStringSubmatch(data)
- if len(match) < 2 {
- return 0, fmt.Errorf("failed get requests per second: %s", data)
- }
- reqPerSecond, err := strconv.ParseFloat(match[1], 64)
- if err != nil {
- return 0, fmt.Errorf("failed to convert %s to float", match[1])
- }
-
- match = heySizePerRequestRE.FindStringSubmatch(data)
- if len(match) < 2 {
- return 0, fmt.Errorf("failed get average latency: %s", data)
- }
- requestSize, err := strconv.ParseFloat(match[1], 64)
- return requestSize * reqPerSecond, err
-}
-
-// TestHeyParsers tests that the parsers work with sample output.
-func TestHeyParsers(t *testing.T) {
- sampleData := `
- Summary:
- Total: 2.2391 secs
- Slowest: 1.6292 secs
- Fastest: 0.0066 secs
- Average: 0.5351 secs
- Requests/sec: 89.3202
-
- Total data: 841200 bytes
- Size/request: 4206 bytes
-
- Response time histogram:
- 0.007 [1] |
- 0.169 [0] |
- 0.331 [149] |â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– 
- 0.493 [0] |
- 0.656 [0] |
- 0.818 [0] |
- 0.980 [0] |
- 1.142 [0] |
- 1.305 [0] |
- 1.467 [49] |â– â– â– â– â– â– â– â– â– â– â– â– â– 
- 1.629 [1] |
-
-
- Latency distribution:
- 10% in 0.2149 secs
- 25% in 0.2449 secs
- 50% in 0.2703 secs
- 75% in 1.3315 secs
- 90% in 1.4045 secs
- 95% in 1.4232 secs
- 99% in 1.4362 secs
-
- Details (average, fastest, slowest):
- DNS+dialup: 0.0002 secs, 0.0066 secs, 1.6292 secs
- DNS-lookup: 0.0000 secs, 0.0000 secs, 0.0000 secs
- req write: 0.0000 secs, 0.0000 secs, 0.0012 secs
- resp wait: 0.5225 secs, 0.0064 secs, 1.4346 secs
- resp read: 0.0122 secs, 0.0001 secs, 0.2006 secs
-
- Status code distribution:
- [200] 200 responses
- `
- want := 89.3202
- got, err := parseHeyRequestsPerSecond(sampleData)
- if err != nil {
- t.Fatalf("failed to parse request per second with: %v", err)
- } else if got != want {
- t.Fatalf("got: %f, want: %f", got, want)
- }
-
- want = 89.3202 * 4206
- got, err = parseHeyBandwidth(sampleData)
- if err != nil {
- t.Fatalf("failed to parse bandwidth with: %v", err)
- } else if got != want {
- t.Fatalf("got: %f, want: %f", got, want)
- }
-
- want = 0.5351
- got, err = parseHeyAverageLatency(sampleData)
- if err != nil {
- t.Fatalf("failed to parse average latency with: %v", err)
- } else if got != want {
- t.Fatalf("got: %f, want: %f", got, want)
- }
-
-}
diff --git a/test/benchmarks/tools/BUILD b/test/benchmarks/tools/BUILD
new file mode 100644
index 000000000..4358551bc
--- /dev/null
+++ b/test/benchmarks/tools/BUILD
@@ -0,0 +1,29 @@
+load("//tools:defs.bzl", "go_library", "go_test")
+
+package(licenses = ["notice"])
+
+go_library(
+ name = "tools",
+ srcs = [
+ "ab.go",
+ "fio.go",
+ "hey.go",
+ "iperf.go",
+ "redis.go",
+ "tools.go",
+ ],
+ visibility = ["//:sandbox"],
+)
+
+go_test(
+ name = "tools_test",
+ size = "small",
+ srcs = [
+ "ab_test.go",
+ "fio_test.go",
+ "hey_test.go",
+ "iperf_test.go",
+ "redis_test.go",
+ ],
+ library = ":tools",
+)
diff --git a/test/benchmarks/tools/ab.go b/test/benchmarks/tools/ab.go
new file mode 100644
index 000000000..4cc9c3bce
--- /dev/null
+++ b/test/benchmarks/tools/ab.go
@@ -0,0 +1,94 @@
+// Copyright 2020 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 tools
+
+import (
+ "fmt"
+ "net"
+ "regexp"
+ "strconv"
+ "testing"
+)
+
+// ApacheBench is for the client application ApacheBench.
+type ApacheBench struct {
+ Requests int
+ Concurrency int
+ Doc string
+ // TODO(zkoopmans): support KeepAlive and pass option to enable.
+}
+
+// MakeCmd makes an ApacheBench command.
+func (a *ApacheBench) MakeCmd(ip net.IP, port int) []string {
+ path := fmt.Sprintf("http://%s:%d/%s", ip, port, a.Doc)
+ // See apachebench (ab) for flags.
+ cmd := fmt.Sprintf("ab -n %d -c %d %s", a.Requests, a.Concurrency, path)
+ return []string{"sh", "-c", cmd}
+}
+
+// Report parses and reports metrics from ApacheBench output.
+func (a *ApacheBench) Report(b *testing.B, output string) {
+ // Parse and report custom metrics.
+ transferRate, err := a.parseTransferRate(output)
+ if err != nil {
+ b.Logf("failed to parse transferrate: %v", err)
+ }
+ b.ReportMetric(transferRate*1024, "transfer_rate_b/s") // Convert from Kb/s to b/s.
+
+ latency, err := a.parseLatency(output)
+ if err != nil {
+ b.Logf("failed to parse latency: %v", err)
+ }
+ b.ReportMetric(latency/1000, "mean_latency_secs") // Convert from ms to s.
+
+ reqPerSecond, err := a.parseRequestsPerSecond(output)
+ if err != nil {
+ b.Logf("failed to parse requests per second: %v", err)
+ }
+ b.ReportMetric(reqPerSecond, "requests_per_second")
+}
+
+var transferRateRE = regexp.MustCompile(`Transfer rate:\s+(\d+\.?\d+?)\s+\[Kbytes/sec\]\s+received`)
+
+// parseTransferRate parses transfer rate from ApacheBench output.
+func (a *ApacheBench) parseTransferRate(data string) (float64, error) {
+ match := transferRateRE.FindStringSubmatch(data)
+ if len(match) < 2 {
+ return 0, fmt.Errorf("failed get bandwidth: %s", data)
+ }
+ return strconv.ParseFloat(match[1], 64)
+}
+
+var latencyRE = regexp.MustCompile(`Total:\s+\d+\s+(\d+)\s+(\d+\.?\d+?)\s+\d+\s+\d+\s`)
+
+// parseLatency parses latency from ApacheBench output.
+func (a *ApacheBench) parseLatency(data string) (float64, error) {
+ match := latencyRE.FindStringSubmatch(data)
+ if len(match) < 2 {
+ return 0, fmt.Errorf("failed get bandwidth: %s", data)
+ }
+ return strconv.ParseFloat(match[1], 64)
+}
+
+var requestsPerSecondRE = regexp.MustCompile(`Requests per second:\s+(\d+\.?\d+?)\s+`)
+
+// parseRequestsPerSecond parses requests per second from ApacheBench output.
+func (a *ApacheBench) parseRequestsPerSecond(data string) (float64, error) {
+ match := requestsPerSecondRE.FindStringSubmatch(data)
+ if len(match) < 2 {
+ return 0, fmt.Errorf("failed get bandwidth: %s", data)
+ }
+ return strconv.ParseFloat(match[1], 64)
+}
diff --git a/test/benchmarks/tools/ab_test.go b/test/benchmarks/tools/ab_test.go
new file mode 100644
index 000000000..28ee66ec1
--- /dev/null
+++ b/test/benchmarks/tools/ab_test.go
@@ -0,0 +1,90 @@
+// Copyright 2020 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 tools
+
+import "testing"
+
+// TestApacheBench checks the ApacheBench parsers on sample output.
+func TestApacheBench(t *testing.T) {
+ // Sample output from apachebench.
+ sampleData := `This is ApacheBench, Version 2.3 <$Revision: 1826891 $>
+Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
+Licensed to The Apache Software Foundation, http://www.apache.org/
+
+Benchmarking 10.10.10.10 (be patient).....done
+
+
+Server Software: Apache/2.4.38
+Server Hostname: 10.10.10.10
+Server Port: 80
+
+Document Path: /latin10k.txt
+Document Length: 210 bytes
+
+Concurrency Level: 1
+Time taken for tests: 0.180 seconds
+Complete requests: 100
+Failed requests: 0
+Non-2xx responses: 100
+Total transferred: 38800 bytes
+HTML transferred: 21000 bytes
+Requests per second: 556.44 [#/sec] (mean)
+Time per request: 1.797 [ms] (mean)
+Time per request: 1.797 [ms] (mean, across all concurrent requests)
+Transfer rate: 210.84 [Kbytes/sec] received
+
+Connection Times (ms)
+ min mean[+/-sd] median max
+Connect: 0 0 0.2 0 2
+Processing: 1 2 1.0 1 8
+Waiting: 1 1 1.0 1 7
+Total: 1 2 1.2 1 10
+
+Percentage of the requests served within a certain time (ms)
+ 50% 1
+ 66% 2
+ 75% 2
+ 80% 2
+ 90% 2
+ 95% 3
+ 98% 7
+ 99% 10
+ 100% 10 (longest request)`
+
+ ab := ApacheBench{}
+ want := 210.84
+ got, err := ab.parseTransferRate(sampleData)
+ if err != nil {
+ t.Fatalf("failed to parse transfer rate with error: %v", err)
+ } else if got != want {
+ t.Fatalf("parseTransferRate got: %f, want: %f", got, want)
+ }
+
+ want = 2.0
+ got, err = ab.parseLatency(sampleData)
+ if err != nil {
+ t.Fatalf("failed to parse transfer rate with error: %v", err)
+ } else if got != want {
+ t.Fatalf("parseLatency got: %f, want: %f", got, want)
+ }
+
+ want = 556.44
+ got, err = ab.parseRequestsPerSecond(sampleData)
+ if err != nil {
+ t.Fatalf("failed to parse transfer rate with error: %v", err)
+ } else if got != want {
+ t.Fatalf("parseRequestsPerSecond got: %f, want: %f", got, want)
+ }
+}
diff --git a/test/benchmarks/tools/fio.go b/test/benchmarks/tools/fio.go
new file mode 100644
index 000000000..20000db16
--- /dev/null
+++ b/test/benchmarks/tools/fio.go
@@ -0,0 +1,124 @@
+// Copyright 2020 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 tools
+
+import (
+ "encoding/json"
+ "fmt"
+ "strconv"
+ "strings"
+ "testing"
+)
+
+// Fio makes 'fio' commands and parses their output.
+type Fio struct {
+ Test string // test to run: read, write, randread, randwrite.
+ Size string // total size to be read/written of format N[GMK] (e.g. 5G).
+ Blocksize string // blocksize to be read/write of format N[GMK] (e.g. 4K).
+ Iodepth int // iodepth for reads/writes.
+ Time int // time to run the test in seconds, usually for rand(read/write).
+}
+
+// MakeCmd makes a 'fio' command.
+func (f *Fio) MakeCmd(filename string) []string {
+ cmd := []string{"fio", "--output-format=json", "--ioengine=sync"}
+ cmd = append(cmd, fmt.Sprintf("--name=%s", f.Test))
+ cmd = append(cmd, fmt.Sprintf("--size=%s", f.Size))
+ cmd = append(cmd, fmt.Sprintf("--blocksize=%s", f.Blocksize))
+ cmd = append(cmd, fmt.Sprintf("--filename=%s", filename))
+ cmd = append(cmd, fmt.Sprintf("--iodepth=%d", f.Iodepth))
+ cmd = append(cmd, fmt.Sprintf("--rw=%s", f.Test))
+ if f.Time != 0 {
+ cmd = append(cmd, "--time_based")
+ cmd = append(cmd, fmt.Sprintf("--runtime=%d", f.Time))
+ }
+ return cmd
+}
+
+// Report reports metrics based on output from an 'fio' command.
+func (f *Fio) Report(b *testing.B, output string) {
+ b.Helper()
+ // Parse the output and report the metrics.
+ isRead := strings.Contains(f.Test, "read")
+ bw, err := f.parseBandwidth(output, isRead)
+ if err != nil {
+ b.Fatalf("failed to parse bandwidth from %s with: %v", output, err)
+ }
+ b.ReportMetric(bw, "bandwidth_b/s") // in b/s.
+
+ iops, err := f.parseIOps(output, isRead)
+ if err != nil {
+ b.Fatalf("failed to parse iops from %s with: %v", output, err)
+ }
+ b.ReportMetric(iops, "iops")
+}
+
+// parseBandwidth reports the bandwidth in b/s.
+func (f *Fio) parseBandwidth(data string, isRead bool) (float64, error) {
+ if isRead {
+ result, err := f.parseFioJSON(data, "read", "bw")
+ if err != nil {
+ return 0, err
+ }
+ return 1024 * result, nil
+ }
+ result, err := f.parseFioJSON(data, "write", "bw")
+ if err != nil {
+ return 0, err
+ }
+ return 1024 * result, nil
+}
+
+// parseIOps reports the write IO per second metric.
+func (f *Fio) parseIOps(data string, isRead bool) (float64, error) {
+ if isRead {
+ return f.parseFioJSON(data, "read", "iops")
+ }
+ return f.parseFioJSON(data, "write", "iops")
+}
+
+// fioResult is for parsing FioJSON.
+type fioResult struct {
+ Jobs []fioJob
+}
+
+// fioJob is for parsing FioJSON.
+type fioJob map[string]json.RawMessage
+
+// fioMetrics is for parsing FioJSON.
+type fioMetrics map[string]json.RawMessage
+
+// parseFioJSON parses data and grabs "op" (read or write) and "metric"
+// (bw or iops) from the JSON.
+func (f *Fio) parseFioJSON(data, op, metric string) (float64, error) {
+ var result fioResult
+ if err := json.Unmarshal([]byte(data), &result); err != nil {
+ return 0, fmt.Errorf("could not unmarshal data: %v", err)
+ }
+
+ if len(result.Jobs) < 1 {
+ return 0, fmt.Errorf("no jobs present to parse")
+ }
+
+ var metrics fioMetrics
+ if err := json.Unmarshal(result.Jobs[0][op], &metrics); err != nil {
+ return 0, fmt.Errorf("could not unmarshal jobs: %v", err)
+ }
+
+ if _, ok := metrics[metric]; !ok {
+ return 0, fmt.Errorf("no metric found for op: %s", op)
+ }
+ return strconv.ParseFloat(string(metrics[metric]), 64)
+}
diff --git a/test/benchmarks/tools/fio_test.go b/test/benchmarks/tools/fio_test.go
new file mode 100644
index 000000000..a98277150
--- /dev/null
+++ b/test/benchmarks/tools/fio_test.go
@@ -0,0 +1,122 @@
+// Copyright 2020 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 tools
+
+import "testing"
+
+// TestFio checks the Fio parsers on sample output.
+func TestFio(t *testing.T) {
+ sampleData := `
+{
+ "fio version" : "fio-3.1",
+ "timestamp" : 1554837456,
+ "timestamp_ms" : 1554837456621,
+ "time" : "Tue Apr 9 19:17:36 2019",
+ "jobs" : [
+ {
+ "jobname" : "test",
+ "groupid" : 0,
+ "error" : 0,
+ "eta" : 2147483647,
+ "elapsed" : 1,
+ "job options" : {
+ "name" : "test",
+ "ioengine" : "sync",
+ "size" : "1073741824",
+ "filename" : "/disk/file.dat",
+ "iodepth" : "4",
+ "bs" : "4096",
+ "rw" : "write"
+ },
+ "read" : {
+ "io_bytes" : 0,
+ "io_kbytes" : 0,
+ "bw" : 123456,
+ "iops" : 1234.5678,
+ "runtime" : 0,
+ "total_ios" : 0,
+ "short_ios" : 0,
+ "bw_min" : 0,
+ "bw_max" : 0,
+ "bw_agg" : 0.000000,
+ "bw_mean" : 0.000000,
+ "bw_dev" : 0.000000,
+ "bw_samples" : 0,
+ "iops_min" : 0,
+ "iops_max" : 0,
+ "iops_mean" : 0.000000,
+ "iops_stddev" : 0.000000,
+ "iops_samples" : 0
+ },
+ "write" : {
+ "io_bytes" : 1073741824,
+ "io_kbytes" : 1048576,
+ "bw" : 1753471,
+ "iops" : 438367.892977,
+ "runtime" : 598,
+ "total_ios" : 262144,
+ "bw_min" : 1731120,
+ "bw_max" : 1731120,
+ "bw_agg" : 98.725328,
+ "bw_mean" : 1731120.000000,
+ "bw_dev" : 0.000000,
+ "bw_samples" : 1,
+ "iops_min" : 432780,
+ "iops_max" : 432780,
+ "iops_mean" : 432780.000000,
+ "iops_stddev" : 0.000000,
+ "iops_samples" : 1
+ }
+ }
+ ]
+}
+`
+ fio := Fio{}
+ // WriteBandwidth.
+ got, err := fio.parseBandwidth(sampleData, false)
+ var want float64 = 1753471.0 * 1024
+ if err != nil {
+ t.Fatalf("parse failed with err: %v", err)
+ } else if got != want {
+ t.Fatalf("got: %f, want: %f", got, want)
+ }
+
+ // ReadBandwidth.
+ got, err = fio.parseBandwidth(sampleData, true)
+ want = 123456 * 1024
+ if err != nil {
+ t.Fatalf("parse failed with err: %v", err)
+ } else if got != want {
+ t.Fatalf("got: %f, want: %f", got, want)
+ }
+
+ // WriteIOps.
+ got, err = fio.parseIOps(sampleData, false)
+ want = 438367.892977
+ if err != nil {
+ t.Fatalf("parse failed with err: %v", err)
+ } else if got != want {
+ t.Fatalf("got: %f, want: %f", got, want)
+ }
+
+ // ReadIOps.
+ got, err = fio.parseIOps(sampleData, true)
+ want = 1234.5678
+ if err != nil {
+ t.Fatalf("parse failed with err: %v", err)
+ } else if got != want {
+ t.Fatalf("got: %f, want: %f", got, want)
+ }
+}
diff --git a/test/benchmarks/tools/hey.go b/test/benchmarks/tools/hey.go
new file mode 100644
index 000000000..699497c64
--- /dev/null
+++ b/test/benchmarks/tools/hey.go
@@ -0,0 +1,75 @@
+// Copyright 2020 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 tools
+
+import (
+ "fmt"
+ "net"
+ "regexp"
+ "strconv"
+ "strings"
+ "testing"
+)
+
+// Hey is for the client application 'hey'.
+type Hey struct {
+ Requests int
+ Concurrency int
+ Doc string
+}
+
+// MakeCmd returns a 'hey' command.
+func (h *Hey) MakeCmd(ip net.IP, port int) []string {
+ return strings.Split(fmt.Sprintf("hey -n %d -c %d http://%s:%d/%s",
+ h.Requests, h.Concurrency, ip, port, h.Doc), " ")
+}
+
+// Report parses output from 'hey' and reports metrics.
+func (h *Hey) Report(b *testing.B, output string) {
+ b.Helper()
+ requests, err := h.parseRequestsPerSecond(output)
+ if err != nil {
+ b.Fatalf("failed to parse requests per second: %v", err)
+ }
+ b.ReportMetric(requests, "requests_per_second")
+
+ ave, err := h.parseAverageLatency(output)
+ if err != nil {
+ b.Fatalf("failed to parse average latency: %v", err)
+ }
+ b.ReportMetric(ave, "average_latency_secs")
+}
+
+var heyReqPerSecondRE = regexp.MustCompile(`Requests/sec:\s*(\d+\.?\d+?)\s+`)
+
+// parseRequestsPerSecond finds requests per second from 'hey' output.
+func (h *Hey) parseRequestsPerSecond(data string) (float64, error) {
+ match := heyReqPerSecondRE.FindStringSubmatch(data)
+ if len(match) < 2 {
+ return 0, fmt.Errorf("failed get bandwidth: %s", data)
+ }
+ return strconv.ParseFloat(match[1], 64)
+}
+
+var heyAverageLatencyRE = regexp.MustCompile(`Average:\s*(\d+\.?\d+?)\s+secs`)
+
+// parseHeyAverageLatency finds Average Latency in seconds form 'hey' output.
+func (h *Hey) parseAverageLatency(data string) (float64, error) {
+ match := heyAverageLatencyRE.FindStringSubmatch(data)
+ if len(match) < 2 {
+ return 0, fmt.Errorf("failed get average latency match%d : %s", len(match), data)
+ }
+ return strconv.ParseFloat(match[1], 64)
+}
diff --git a/test/benchmarks/tools/hey_test.go b/test/benchmarks/tools/hey_test.go
new file mode 100644
index 000000000..e0cab1f52
--- /dev/null
+++ b/test/benchmarks/tools/hey_test.go
@@ -0,0 +1,81 @@
+// Copyright 2020 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 tools
+
+import "testing"
+
+// TestHey checks the Hey parsers on sample output.
+func TestHey(t *testing.T) {
+ sampleData := `
+ Summary:
+ Total: 2.2391 secs
+ Slowest: 1.6292 secs
+ Fastest: 0.0066 secs
+ Average: 0.5351 secs
+ Requests/sec: 89.3202
+
+ Total data: 841200 bytes
+ Size/request: 4206 bytes
+
+ Response time histogram:
+ 0.007 [1] |
+ 0.169 [0] |
+ 0.331 [149] |â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– 
+ 0.493 [0] |
+ 0.656 [0] |
+ 0.818 [0] |
+ 0.980 [0] |
+ 1.142 [0] |
+ 1.305 [0] |
+ 1.467 [49] |â– â– â– â– â– â– â– â– â– â– â– â– â– 
+ 1.629 [1] |
+
+
+ Latency distribution:
+ 10% in 0.2149 secs
+ 25% in 0.2449 secs
+ 50% in 0.2703 secs
+ 75% in 1.3315 secs
+ 90% in 1.4045 secs
+ 95% in 1.4232 secs
+ 99% in 1.4362 secs
+
+ Details (average, fastest, slowest):
+ DNS+dialup: 0.0002 secs, 0.0066 secs, 1.6292 secs
+ DNS-lookup: 0.0000 secs, 0.0000 secs, 0.0000 secs
+ req write: 0.0000 secs, 0.0000 secs, 0.0012 secs
+ resp wait: 0.5225 secs, 0.0064 secs, 1.4346 secs
+ resp read: 0.0122 secs, 0.0001 secs, 0.2006 secs
+
+ Status code distribution:
+ [200] 200 responses
+ `
+ hey := Hey{}
+ want := 89.3202
+ got, err := hey.parseRequestsPerSecond(sampleData)
+ if err != nil {
+ t.Fatalf("failed to parse request per second with: %v", err)
+ } else if got != want {
+ t.Fatalf("got: %f, want: %f", got, want)
+ }
+
+ want = 0.5351
+ got, err = hey.parseAverageLatency(sampleData)
+ if err != nil {
+ t.Fatalf("failed to parse average latency with: %v", err)
+ } else if got != want {
+ t.Fatalf("got: %f, want: %f", got, want)
+ }
+}
diff --git a/test/benchmarks/tools/iperf.go b/test/benchmarks/tools/iperf.go
new file mode 100644
index 000000000..df3d9349b
--- /dev/null
+++ b/test/benchmarks/tools/iperf.go
@@ -0,0 +1,56 @@
+// Copyright 2020 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 tools
+
+import (
+ "fmt"
+ "net"
+ "regexp"
+ "strconv"
+ "strings"
+ "testing"
+)
+
+// Iperf is for the client side of `iperf`.
+type Iperf struct {
+ Time int
+}
+
+// MakeCmd returns a iperf client command.
+func (i *Iperf) MakeCmd(ip net.IP, port int) []string {
+ // iperf report in Kb realtime
+ return strings.Split(fmt.Sprintf("iperf -f K --realtime --time %d -c %s -p %d", i.Time, ip, port), " ")
+}
+
+// Report parses output from iperf client and reports metrics.
+func (i *Iperf) Report(b *testing.B, output string) {
+ b.Helper()
+ // Parse bandwidth and report it.
+ bW, err := i.bandwidth(output)
+ if err != nil {
+ b.Fatalf("failed to parse bandwitdth from %s: %v", output, err)
+ }
+ b.ReportMetric(bW*1024, "bandwidth_b/s") // Convert from Kb/s to b/s.
+}
+
+// bandwidth parses the Bandwidth number from an iperf report. A sample is below.
+func (i *Iperf) bandwidth(data string) (float64, error) {
+ re := regexp.MustCompile(`\[\s*\d+\][^\n]+\s+(\d+\.?\d*)\s+KBytes/sec`)
+ match := re.FindStringSubmatch(data)
+ if len(match) < 1 {
+ return 0, fmt.Errorf("failed get bandwidth: %s", data)
+ }
+ return strconv.ParseFloat(match[1], 64)
+}
diff --git a/test/benchmarks/tools/iperf_test.go b/test/benchmarks/tools/iperf_test.go
new file mode 100644
index 000000000..03bb30d05
--- /dev/null
+++ b/test/benchmarks/tools/iperf_test.go
@@ -0,0 +1,34 @@
+// Copyright 2020 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 tools
+
+import "testing"
+
+// TestIperf checks the Iperf parsers on sample output.
+func TestIperf(t *testing.T) {
+ sampleData := `
+------------------------------------------------------------
+Client connecting to 10.138.15.215, TCP port 32779
+TCP window size: 45.0 KByte (default)
+------------------------------------------------------------
+[ 3] local 10.138.15.216 port 32866 connected with 10.138.15.215 port 32779
+[ ID] Interval Transfer Bandwidth
+[ 3] 0.0-10.0 sec 459520 KBytes 45900 KBytes/sec
+`
+ i := Iperf{}
+ bandwidth, err := i.bandwidth(sampleData)
+ if err != nil || bandwidth != 45900 {
+ t.Fatalf("failed with: %v and %f", err, bandwidth)
+ }
+}
diff --git a/test/benchmarks/tools/redis.go b/test/benchmarks/tools/redis.go
new file mode 100644
index 000000000..db32460ec
--- /dev/null
+++ b/test/benchmarks/tools/redis.go
@@ -0,0 +1,64 @@
+// Copyright 2020 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 tools
+
+import (
+ "fmt"
+ "net"
+ "regexp"
+ "strconv"
+ "strings"
+ "testing"
+)
+
+// Redis is for the client 'redis-benchmark'.
+type Redis struct {
+ Operation string
+}
+
+// MakeCmd returns a redis-benchmark client command.
+func (r *Redis) MakeCmd(ip net.IP, port int) []string {
+ // There is no -t PING_BULK for redis-benchmark, so adjust the command in that case.
+ // Note that "ping" will run both PING_INLINE and PING_BULK.
+ if r.Operation == "PING_BULK" {
+ return strings.Split(
+ fmt.Sprintf("redis-benchmark --csv -t ping -h %s -p %d", ip, port), " ")
+ }
+
+ // runs redis-benchmark -t operation for 100K requests against server.
+ return strings.Split(
+ fmt.Sprintf("redis-benchmark --csv -t %s -h %s -p %d", r.Operation, ip, port), " ")
+}
+
+// Report parses output from redis-benchmark client and reports metrics.
+func (r *Redis) Report(b *testing.B, output string) {
+ b.Helper()
+ result, err := r.parseOperation(output)
+ if err != nil {
+ b.Fatalf("parsing result %s failed with err: %v", output, err)
+ }
+ b.ReportMetric(result, r.Operation) // operations per second
+}
+
+// parseOperation grabs the metric operations per second from redis-benchmark output.
+func (r *Redis) parseOperation(data string) (float64, error) {
+ re := regexp.MustCompile(fmt.Sprintf(`"%s( .*)?","(\d*\.\d*)"`, r.Operation))
+ match := re.FindStringSubmatch(data)
+ // If no match, simply don't add it to the result map.
+ if len(match) < 3 {
+ return 0.0, fmt.Errorf("could not find %s in %s", r.Operation, data)
+ }
+ return strconv.ParseFloat(match[2], 64)
+}
diff --git a/test/benchmarks/tools/redis_test.go b/test/benchmarks/tools/redis_test.go
new file mode 100644
index 000000000..4bafda66f
--- /dev/null
+++ b/test/benchmarks/tools/redis_test.go
@@ -0,0 +1,87 @@
+// Copyright 2020 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 tools
+
+import (
+ "testing"
+)
+
+// TestRedis checks the Redis parsers on sample output.
+func TestRedis(t *testing.T) {
+ sampleData := `
+ "PING_INLINE","48661.80"
+ "PING_BULK","50301.81"
+ "SET","48923.68"
+ "GET","49382.71"
+ "INCR","49975.02"
+ "LPUSH","49875.31"
+ "RPUSH","50276.52"
+ "LPOP","50327.12"
+ "RPOP","50556.12"
+ "SADD","49504.95"
+ "HSET","49504.95"
+ "SPOP","50025.02"
+ "LPUSH (needed to benchmark LRANGE)","48875.86"
+ "LRANGE_100 (first 100 elements)","33955.86"
+ "LRANGE_300 (first 300 elements)","16550.81"// Copyright 2020 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 tools
+
+ "LRANGE_500 (first 450 elements)","13653.74"
+ "LRANGE_600 (first 600 elements)","11219.57"
+ "MSET (10 keys)","44682.75"
+ `
+ wants := map[string]float64{
+ "PING_INLINE": 48661.80,
+ "PING_BULK": 50301.81,
+ "SET": 48923.68,
+ "GET": 49382.71,
+ "INCR": 49975.02,
+ "LPUSH": 49875.31,
+ "RPUSH": 50276.52,
+ "LPOP": 50327.12,
+ "RPOP": 50556.12,
+ "SADD": 49504.95,
+ "HSET": 49504.95,
+ "SPOP": 50025.02,
+ "LRANGE_100": 33955.86,
+ "LRANGE_300": 16550.81,
+ "LRANGE_500": 13653.74,
+ "LRANGE_600": 11219.57,
+ "MSET": 44682.75,
+ }
+ for op, want := range wants {
+ redis := Redis{
+ Operation: op,
+ }
+ if got, err := redis.parseOperation(sampleData); err != nil {
+ t.Fatalf("failed to parse %s: %v", op, err)
+ } else if want != got {
+ t.Fatalf("wanted %f for op %s, got %f", want, op, got)
+ }
+ }
+}
diff --git a/test/benchmarks/tools/tools.go b/test/benchmarks/tools/tools.go
new file mode 100644
index 000000000..eb61c0136
--- /dev/null
+++ b/test/benchmarks/tools/tools.go
@@ -0,0 +1,17 @@
+// Copyright 2020 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 tools holds tooling to couple command formatting and output parsers
+// together.
+package tools
diff --git a/test/fuse/README.md b/test/fuse/README.md
index a899f2826..734c3a4e3 100644
--- a/test/fuse/README.md
+++ b/test/fuse/README.md
@@ -75,18 +75,19 @@ $ bazel test --test_tag_filters=fuse //test/fuse/...
## Writing a new FUSE test
-1. Add test targets in `BUILD` and `linux/BUILD`.
-2. Inherit your test from `FuseTest` base class. It allows you to:
- - Run a fake FUSE server in background during each test setup.
- - Create pipes for communication and provide utility functions.
- - Stop FUSE server after test completes.
-3. Customize your comparison function for request assessment in FUSE server.
-4. Add the mapping of the size of structs if you are working on new FUSE opcode.
- - Please update `FuseTest::GetPayloadSize()` for each new FUSE opcode.
-5. Build the expected request-response pair of your FUSE operation.
-6. Call `SetExpected()` function to inject the expected reaction.
-7. Check the response and/or errors.
-8. Finally call `WaitCompleted()` to ensure the FUSE server acts correctly.
+1. Add test targets in `BUILD` and `linux/BUILD`.
+2. Inherit your test from `FuseTest` base class. It allows you to:
+ - Run a fake FUSE server in background during each test setup.
+ - Create pipes for communication and provide utility functions.
+ - Stop FUSE server after test completes.
+3. Customize your comparison function for request assessment in FUSE server.
+4. Add the mapping of the size of structs if you are working on new FUSE
+ opcode.
+ - Please update `FuseTest::GetPayloadSize()` for each new FUSE opcode.
+5. Build the expected request-response pair of your FUSE operation.
+6. Call `SetExpected()` function to inject the expected reaction.
+7. Check the response and/or errors.
+8. Finally call `WaitCompleted()` to ensure the FUSE server acts correctly.
A few customized matchers used in syscalls test are encouraged to test the
outcome of filesystem operations. Such as:
diff --git a/test/fuse/linux/BUILD b/test/fuse/linux/BUILD
index cc3383741..49dc96c20 100644
--- a/test/fuse/linux/BUILD
+++ b/test/fuse/linux/BUILD
@@ -9,8 +9,8 @@ cc_library(
name = "fuse_base",
testonly = 1,
srcs = [
- "fuse_base.h",
"fuse_base.cc",
+ "fuse_base.h",
],
deps = [
gtest,
diff --git a/test/fuse/linux/fuse_base.cc b/test/fuse/linux/fuse_base.cc
index 87092b515..6c8432fd0 100644
--- a/test/fuse/linux/fuse_base.cc
+++ b/test/fuse/linux/fuse_base.cc
@@ -25,9 +25,9 @@
#include <iostream>
-#include "absl/strings/str_format.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
+#include "absl/strings/str_format.h"
#include "test/util/posix_error.h"
#include "test/util/test_util.h"
diff --git a/test/iptables/BUILD b/test/iptables/BUILD
index 40b63ebbe..66453772a 100644
--- a/test/iptables/BUILD
+++ b/test/iptables/BUILD
@@ -9,6 +9,7 @@ go_library(
"filter_input.go",
"filter_output.go",
"iptables.go",
+ "iptables_unsafe.go",
"iptables_util.go",
"nat.go",
],
diff --git a/test/iptables/iptables_test.go b/test/iptables/iptables_test.go
index 550b6198a..fda5f694f 100644
--- a/test/iptables/iptables_test.go
+++ b/test/iptables/iptables_test.go
@@ -371,3 +371,11 @@ func TestFilterAddrs(t *testing.T) {
}
}
}
+
+func TestNATPreOriginalDst(t *testing.T) {
+ singleTest(t, NATPreOriginalDst{})
+}
+
+func TestNATOutOriginalDst(t *testing.T) {
+ singleTest(t, NATOutOriginalDst{})
+}
diff --git a/test/iptables/iptables_unsafe.go b/test/iptables/iptables_unsafe.go
new file mode 100644
index 000000000..bd85a8fea
--- /dev/null
+++ b/test/iptables/iptables_unsafe.go
@@ -0,0 +1,63 @@
+// Copyright 2020 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 iptables
+
+import (
+ "fmt"
+ "syscall"
+ "unsafe"
+)
+
+type originalDstError struct {
+ errno syscall.Errno
+}
+
+func (e originalDstError) Error() string {
+ return fmt.Sprintf("errno (%d) when calling getsockopt(SO_ORIGINAL_DST): %v", int(e.errno), e.errno.Error())
+}
+
+// SO_ORIGINAL_DST gets the original destination of a redirected packet via
+// getsockopt.
+const SO_ORIGINAL_DST = 80
+
+func originalDestination4(connfd int) (syscall.RawSockaddrInet4, error) {
+ var addr syscall.RawSockaddrInet4
+ var addrLen uint32 = syscall.SizeofSockaddrInet4
+ if errno := originalDestination(connfd, syscall.SOL_IP, unsafe.Pointer(&addr), &addrLen); errno != 0 {
+ return syscall.RawSockaddrInet4{}, originalDstError{errno}
+ }
+ return addr, nil
+}
+
+func originalDestination6(connfd int) (syscall.RawSockaddrInet6, error) {
+ var addr syscall.RawSockaddrInet6
+ var addrLen uint32 = syscall.SizeofSockaddrInet6
+ if errno := originalDestination(connfd, syscall.SOL_IPV6, unsafe.Pointer(&addr), &addrLen); errno != 0 {
+ return syscall.RawSockaddrInet6{}, originalDstError{errno}
+ }
+ return addr, nil
+}
+
+func originalDestination(connfd int, level uintptr, optval unsafe.Pointer, optlen *uint32) syscall.Errno {
+ _, _, errno := syscall.Syscall6(
+ syscall.SYS_GETSOCKOPT,
+ uintptr(connfd),
+ level,
+ SO_ORIGINAL_DST,
+ uintptr(optval),
+ uintptr(unsafe.Pointer(optlen)),
+ 0)
+ return errno
+}
diff --git a/test/iptables/iptables_util.go b/test/iptables/iptables_util.go
index ca80a4b5f..5125fe47b 100644
--- a/test/iptables/iptables_util.go
+++ b/test/iptables/iptables_util.go
@@ -15,6 +15,8 @@
package iptables
import (
+ "encoding/binary"
+ "errors"
"fmt"
"net"
"os/exec"
@@ -218,17 +220,58 @@ func filterAddrs(addrs []string, ipv6 bool) []string {
// getInterfaceName returns the name of the interface other than loopback.
func getInterfaceName() (string, bool) {
- var ifname string
+ iface, ok := getNonLoopbackInterface()
+ if !ok {
+ return "", false
+ }
+ return iface.Name, true
+}
+
+func getInterfaceAddrs(ipv6 bool) ([]net.IP, error) {
+ iface, ok := getNonLoopbackInterface()
+ if !ok {
+ return nil, errors.New("no non-loopback interface found")
+ }
+ addrs, err := iface.Addrs()
+ if err != nil {
+ return nil, err
+ }
+
+ // Get only IPv4 or IPv6 addresses.
+ ips := make([]net.IP, 0, len(addrs))
+ for _, addr := range addrs {
+ parts := strings.Split(addr.String(), "/")
+ var ip net.IP
+ // To16() returns IPv4 addresses as IPv4-mapped IPv6 addresses.
+ // So we check whether To4() returns nil to test whether the
+ // address is v4 or v6.
+ if v4 := net.ParseIP(parts[0]).To4(); ipv6 && v4 == nil {
+ ip = net.ParseIP(parts[0]).To16()
+ } else {
+ ip = v4
+ }
+ if ip != nil {
+ ips = append(ips, ip)
+ }
+ }
+ return ips, nil
+}
+
+func getNonLoopbackInterface() (net.Interface, bool) {
if interfaces, err := net.Interfaces(); err == nil {
for _, intf := range interfaces {
if intf.Name != "lo" {
- ifname = intf.Name
- break
+ return intf, true
}
}
}
+ return net.Interface{}, false
+}
- return ifname, ifname != ""
+func htons(x uint16) uint16 {
+ buf := make([]byte, 2)
+ binary.BigEndian.PutUint16(buf, x)
+ return binary.LittleEndian.Uint16(buf)
}
func localIP(ipv6 bool) string {
diff --git a/test/iptables/nat.go b/test/iptables/nat.go
index ac0d91bb2..b7fea2527 100644
--- a/test/iptables/nat.go
+++ b/test/iptables/nat.go
@@ -18,12 +18,11 @@ import (
"errors"
"fmt"
"net"
+ "syscall"
"time"
)
-const (
- redirectPort = 42
-)
+const redirectPort = 42
func init() {
RegisterTestCase(NATPreRedirectUDPPort{})
@@ -42,6 +41,8 @@ func init() {
RegisterTestCase(NATOutRedirectInvert{})
RegisterTestCase(NATRedirectRequiresProtocol{})
RegisterTestCase(NATLoopbackSkipsPrerouting{})
+ RegisterTestCase(NATPreOriginalDst{})
+ RegisterTestCase(NATOutOriginalDst{})
}
// NATPreRedirectUDPPort tests that packets are redirected to different port.
@@ -471,6 +472,151 @@ func (NATLoopbackSkipsPrerouting) LocalAction(ip net.IP, ipv6 bool) error {
return nil
}
+// NATPreOriginalDst tests that SO_ORIGINAL_DST returns the pre-NAT destination
+// of PREROUTING NATted packets.
+type NATPreOriginalDst struct{}
+
+// Name implements TestCase.Name.
+func (NATPreOriginalDst) Name() string {
+ return "NATPreOriginalDst"
+}
+
+// ContainerAction implements TestCase.ContainerAction.
+func (NATPreOriginalDst) ContainerAction(ip net.IP, ipv6 bool) error {
+ // Redirect incoming TCP connections to acceptPort.
+ if err := natTable(ipv6, "-A", "PREROUTING",
+ "-p", "tcp",
+ "--destination-port", fmt.Sprintf("%d", dropPort),
+ "-j", "REDIRECT", "--to-port", fmt.Sprintf("%d", acceptPort)); err != nil {
+ return err
+ }
+
+ addrs, err := getInterfaceAddrs(ipv6)
+ if err != nil {
+ return err
+ }
+ return listenForRedirectedConn(ipv6, addrs)
+}
+
+// LocalAction implements TestCase.LocalAction.
+func (NATPreOriginalDst) LocalAction(ip net.IP, ipv6 bool) error {
+ return connectTCP(ip, dropPort, sendloopDuration)
+}
+
+// NATOutOriginalDst tests that SO_ORIGINAL_DST returns the pre-NAT destination
+// of OUTBOUND NATted packets.
+type NATOutOriginalDst struct{}
+
+// Name implements TestCase.Name.
+func (NATOutOriginalDst) Name() string {
+ return "NATOutOriginalDst"
+}
+
+// ContainerAction implements TestCase.ContainerAction.
+func (NATOutOriginalDst) ContainerAction(ip net.IP, ipv6 bool) error {
+ // Redirect incoming TCP connections to acceptPort.
+ if err := natTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "-j", "REDIRECT", "--to-port", fmt.Sprintf("%d", acceptPort)); err != nil {
+ return err
+ }
+
+ connCh := make(chan error)
+ go func() {
+ connCh <- connectTCP(ip, dropPort, sendloopDuration)
+ }()
+
+ if err := listenForRedirectedConn(ipv6, []net.IP{ip}); err != nil {
+ return err
+ }
+ return <-connCh
+}
+
+// LocalAction implements TestCase.LocalAction.
+func (NATOutOriginalDst) LocalAction(ip net.IP, ipv6 bool) error {
+ // No-op.
+ return nil
+}
+
+func listenForRedirectedConn(ipv6 bool, originalDsts []net.IP) error {
+ // The net package doesn't give guarantee access to the connection's
+ // underlying FD, and thus we cannot call getsockopt. We have to use
+ // traditional syscalls for SO_ORIGINAL_DST.
+
+ // Create the listening socket, bind, listen, and accept.
+ family := syscall.AF_INET
+ if ipv6 {
+ family = syscall.AF_INET6
+ }
+ sockfd, err := syscall.Socket(family, syscall.SOCK_STREAM, 0)
+ if err != nil {
+ return err
+ }
+ defer syscall.Close(sockfd)
+
+ var bindAddr syscall.Sockaddr
+ if ipv6 {
+ bindAddr = &syscall.SockaddrInet6{
+ Port: acceptPort,
+ Addr: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // in6addr_any
+ }
+ } else {
+ bindAddr = &syscall.SockaddrInet4{
+ Port: acceptPort,
+ Addr: [4]byte{0, 0, 0, 0}, // INADDR_ANY
+ }
+ }
+ if err := syscall.Bind(sockfd, bindAddr); err != nil {
+ return err
+ }
+
+ if err := syscall.Listen(sockfd, 1); err != nil {
+ return err
+ }
+
+ connfd, _, err := syscall.Accept(sockfd)
+ if err != nil {
+ return err
+ }
+ defer syscall.Close(connfd)
+
+ // Verify that, despite listening on acceptPort, SO_ORIGINAL_DST
+ // indicates the packet was sent to originalDst:dropPort.
+ if ipv6 {
+ got, err := originalDestination6(connfd)
+ if err != nil {
+ return err
+ }
+ // The original destination could be any of our IPs.
+ for _, dst := range originalDsts {
+ want := syscall.RawSockaddrInet6{
+ Family: syscall.AF_INET6,
+ Port: htons(dropPort),
+ }
+ copy(want.Addr[:], dst.To16())
+ if got == want {
+ return nil
+ }
+ }
+ return fmt.Errorf("SO_ORIGINAL_DST returned %+v, but wanted one of %+v (note: port numbers are in network byte order)", got, originalDsts)
+ } else {
+ got, err := originalDestination4(connfd)
+ if err != nil {
+ return err
+ }
+ // The original destination could be any of our IPs.
+ for _, dst := range originalDsts {
+ want := syscall.RawSockaddrInet4{
+ Family: syscall.AF_INET,
+ Port: htons(dropPort),
+ }
+ copy(want.Addr[:], dst.To4())
+ if got == want {
+ return nil
+ }
+ }
+ return fmt.Errorf("SO_ORIGINAL_DST returned %+v, but wanted one of %+v (note: port numbers are in network byte order)", got, originalDsts)
+ }
+}
+
// loopbackTests runs an iptables rule and ensures that packets sent to
// dest:dropPort are received by localhost:acceptPort.
func loopbackTest(ipv6 bool, dest net.IP, args ...string) error {
diff --git a/test/runtimes/BUILD b/test/runtimes/BUILD
index 1728161ce..3be123d94 100644
--- a/test/runtimes/BUILD
+++ b/test/runtimes/BUILD
@@ -11,6 +11,7 @@ runtime_test(
runtime_test(
name = "java11",
+ batch = 100,
exclude_file = "exclude_java11.csv",
lang = "java",
shard_count = 20,
diff --git a/test/runtimes/defs.bzl b/test/runtimes/defs.bzl
index 5779b9591..db22029a8 100644
--- a/test/runtimes/defs.bzl
+++ b/test/runtimes/defs.bzl
@@ -9,6 +9,8 @@ def _runtime_test_impl(ctx):
ctx.attr.lang,
"--image",
ctx.attr.image,
+ "--batch",
+ str(ctx.attr.batch),
]
if ctx.attr.exclude_file:
args += [
@@ -47,6 +49,10 @@ _runtime_test = rule(
mandatory = False,
allow_single_file = True,
),
+ "batch": attr.int(
+ default = 50,
+ mandatory = False,
+ ),
"_runner": attr.label(
default = "//test/runtimes/runner:runner",
),
diff --git a/test/runtimes/exclude_java11.csv b/test/runtimes/exclude_java11.csv
index c012e5a56..4d62f7d3a 100644
--- a/test/runtimes/exclude_java11.csv
+++ b/test/runtimes/exclude_java11.csv
@@ -15,10 +15,9 @@ java/lang/Character/CheckScript.java,,Fails in Docker
java/lang/Character/CheckUnicode.java,,Fails in Docker
java/lang/Class/GetPackageBootLoaderChildLayer.java,,
java/lang/ClassLoader/nativeLibrary/NativeLibraryTest.java,,Fails in Docker
+java/lang/module/ModuleDescriptorTest.java,,
java/lang/String/nativeEncoding/StringPlatformChars.java,,
-java/net/DatagramSocket/ReuseAddressTest.java,,
-java/net/DatagramSocket/SendDatagramToBadAddress.java,b/78473345,
-java/net/Inet4Address/PingThis.java,,
+java/net/CookieHandler/B6791927.java,,java.lang.RuntimeException: Expiration date shouldn't be 0
java/net/InterfaceAddress/NetworkPrefixLength.java,b/78507103,
java/net/MulticastSocket/MulticastTTL.java,,
java/net/MulticastSocket/Promiscuous.java,,
@@ -29,23 +28,16 @@ java/net/MulticastSocket/TestDefaults.java,,
java/net/MulticastSocket/TimeToLive.java,,
java/net/NetworkInterface/NetworkInterfaceStreamTest.java,,
java/net/Socket/SetSoLinger.java,b/78527327,SO_LINGER is not yet supported
-java/net/Socket/TrafficClass.java,b/78527818,Not supported on gVisor
java/net/Socket/UrgentDataTest.java,b/111515323,
-java/net/Socket/setReuseAddress/Basic.java,b/78519214,SO_REUSEADDR enabled by default
java/net/SocketOption/OptionsTest.java,,Fails in Docker
-java/net/SocketOption/TcpKeepAliveTest.java,,
java/net/SocketPermission/SocketPermissionTest.java,,
java/net/URLConnection/6212146/TestDriver.java,,Fails in Docker
java/net/httpclient/RequestBuilderTest.java,,Fails in Docker
-java/net/httpclient/ShortResponseBody.java,,
-java/net/httpclient/ShortResponseBodyWithRetry.java,,
-java/nio/channels/AsyncCloseAndInterrupt.java,,
-java/nio/channels/AsynchronousServerSocketChannel/Basic.java,,
-java/nio/channels/AsynchronousSocketChannel/Basic.java,b/77921528,SO_KEEPALIVE is not settable
java/nio/channels/DatagramChannel/BasicMulticastTests.java,,
-java/nio/channels/DatagramChannel/SocketOptionTests.java,,Fails in Docker
+java/nio/channels/DatagramChannel/SocketOptionTests.java,,java.net.SocketException: Invalid argument
java/nio/channels/DatagramChannel/UseDGWithIPv6.java,,
java/nio/channels/FileChannel/directio/DirectIOTest.java,,Fails in Docker
+java/nio/channels/FileChannel/directio/PwriteDirect.java,,java.io.IOException: Invalid argument
java/nio/channels/Selector/OutOfBand.java,,
java/nio/channels/Selector/SelectWithConsumer.java,,Flaky
java/nio/channels/ServerSocketChannel/SocketOptionTests.java,,
@@ -59,15 +51,89 @@ java/text/Format/NumberFormat/CurrencyFormat.java,,Fails in Docker
java/util/Calendar/JapaneseEraNameTest.java,,
java/util/Currency/CurrencyTest.java,,Fails in Docker
java/util/Currency/ValidateISO4217.java,,Fails in Docker
+java/util/EnumSet/BogusEnumSet.java,,"java.io.InvalidClassException: java.util.EnumSet; local class incompatible: stream classdesc serialVersionUID = -2409567991088730183, local class serialVersionUID = 1009687484059888093"
+java/util/Locale/Bug8040211.java,,java.lang.RuntimeException: Failed.
java/util/Locale/LSRDataTest.java,,
+java/util/Properties/CompatibilityTest.java,,"java.lang.RuntimeException: jdk.internal.org.xml.sax.SAXParseException; Internal DTD subset is not allowed. The Properties XML document must have the following DOCTYPE declaration: <!DOCTYPE properties SYSTEM ""http://java.sun.com/dtd/properties.dtd"">"
+java/util/ResourceBundle/Control/XMLResourceBundleTest.java,,java.util.MissingResourceException: Can't find bundle for base name XmlRB locale
+java/util/ResourceBundle/modules/xmlformat/xmlformat.sh,,Timeout reached: 60000. Process is not alive!
+java/util/TimeZone/TimeZoneTest.java,,Uncaught exception thrown in test method TestShortZoneIDs
java/util/concurrent/locks/Lock/TimedAcquireLeak.java,,
java/util/jar/JarFile/mrjar/MultiReleaseJarAPI.java,,Fails in Docker
java/util/logging/LogManager/Configuration/updateConfiguration/SimpleUpdateConfigWithInputStreamTest.java,,
java/util/logging/TestLoggerWeakRefLeak.java,,
+java/util/spi/ResourceBundleControlProvider/UserDefaultControlTest.java,,java.util.MissingResourceException: Can't find bundle for base name com.foo.XmlRB locale
javax/imageio/AppletResourceTest.java,,
+javax/imageio/plugins/jpeg/JPEGsNotAcceleratedTest.java,,java.awt.HeadlessException: No X11 DISPLAY variable was set but this program performed an operation which requires it.
javax/management/security/HashedPasswordFileTest.java,,
+javax/net/ssl/DTLS/DTLSBufferOverflowUnderflowTest.java,,Compilation failed
+javax/net/ssl/DTLS/DTLSDataExchangeTest.java,,Compilation failed
+javax/net/ssl/DTLS/DTLSEnginesClosureTest.java,,Compilation failed
+javax/net/ssl/DTLS/DTLSHandshakeTest.java,,Compilation failed
+javax/net/ssl/DTLS/DTLSHandshakeWithReplicatedPacketsTest.java,,Compilation failed
+javax/net/ssl/DTLS/DTLSIncorrectAppDataTest.java,,Compilation failed
+javax/net/ssl/DTLS/DTLSMFLNTest.java,,Compilation failed
+javax/net/ssl/DTLS/DTLSNotEnabledRC4Test.java,,Compilation failed
+javax/net/ssl/DTLS/DTLSRehandshakeTest.java,,Compilation failed
+javax/net/ssl/DTLS/DTLSRehandshakeWithCipherChangeTest.java,,Compilation failed
+javax/net/ssl/DTLS/DTLSRehandshakeWithDataExTest.java,,Compilation failed
+javax/net/ssl/DTLS/DTLSSequenceNumberTest.java,,Compilation failed
+javax/net/ssl/DTLS/DTLSUnsupportedCiphersTest.java,,Compilation failed
+javax/net/ssl/DTLSv10/DTLSv10BufferOverflowUnderflowTest.java,,Compilation failed
+javax/net/ssl/DTLSv10/DTLSv10DataExchangeTest.java,,Compilation failed
+javax/net/ssl/DTLSv10/DTLSv10EnginesClosureTest.java,,Compilation failed
+javax/net/ssl/DTLSv10/DTLSv10HandshakeTest.java,,Compilation failed
+javax/net/ssl/DTLSv10/DTLSv10HandshakeWithReplicatedPacketsTest.java,,Compilation failed
+javax/net/ssl/DTLSv10/DTLSv10IncorrectAppDataTest.java,,Compilation failed
+javax/net/ssl/DTLSv10/DTLSv10MFLNTest.java,,Compilation failed
+javax/net/ssl/DTLSv10/DTLSv10NotEnabledRC4Test.java,,Compilation failed
+javax/net/ssl/DTLSv10/DTLSv10RehandshakeTest.java,,Compilation failed
+javax/net/ssl/DTLSv10/DTLSv10RehandshakeWithCipherChangeTest.java,,Compilation failed
+javax/net/ssl/DTLSv10/DTLSv10RehandshakeWithDataExTest.java,,Compilation failed
+javax/net/ssl/DTLSv10/DTLSv10SequenceNumberTest.java,,Compilation failed
+javax/net/ssl/DTLSv10/DTLSv10UnsupportedCiphersTest.java,,Compilation failed
javax/net/ssl/SSLSession/JSSERenegotiate.java,,Fails in Docker
+javax/net/ssl/TLS/TLSDataExchangeTest.java,,Compilation failed
+javax/net/ssl/TLS/TLSEnginesClosureTest.java,,Compilation failed
+javax/net/ssl/TLS/TLSHandshakeTest.java,,Compilation failed
+javax/net/ssl/TLS/TLSMFLNTest.java,,Compilation failed
+javax/net/ssl/TLS/TLSNotEnabledRC4Test.java,,Compilation failed
+javax/net/ssl/TLS/TLSRehandshakeTest.java,,Compilation failed
+javax/net/ssl/TLS/TLSRehandshakeWithCipherChangeTest.java,,Compilation failed
+javax/net/ssl/TLS/TLSRehandshakeWithDataExTest.java,,Compilation failed
+javax/net/ssl/TLS/TLSUnsupportedCiphersTest.java,,Compilation failed
+javax/net/ssl/TLSv1/TLSDataExchangeTest.java,,Compilation failed
+javax/net/ssl/TLSv1/TLSEnginesClosureTest.java,,Compilation failed
+javax/net/ssl/TLSv1/TLSHandshakeTest.java,,Compilation failed
+javax/net/ssl/TLSv1/TLSMFLNTest.java,,Compilation failed
+javax/net/ssl/TLSv1/TLSNotEnabledRC4Test.java,,Compilation failed
+javax/net/ssl/TLSv1/TLSRehandshakeTest.java,,Compilation failed
+javax/net/ssl/TLSv1/TLSRehandshakeWithCipherChangeTest.java,,Compilation failed
+javax/net/ssl/TLSv1/TLSRehandshakeWithDataExTest.java,,Compilation failed
+javax/net/ssl/TLSv1/TLSUnsupportedCiphersTest.java,,Compilation failed
+javax/net/ssl/TLSv11/TLSDataExchangeTest.java,,Compilation failed
+javax/net/ssl/TLSv11/TLSEnginesClosureTest.java,,Compilation failed
+javax/net/ssl/TLSv11/TLSHandshakeTest.java,,Compilation failed
+javax/net/ssl/TLSv11/TLSMFLNTest.java,,Compilation failed
+javax/net/ssl/TLSv11/TLSNotEnabledRC4Test.java,,Compilation failed
+javax/net/ssl/TLSv11/TLSRehandshakeTest.java,,Compilation failed
+javax/net/ssl/TLSv11/TLSRehandshakeWithCipherChangeTest.java,,Compilation failed
+javax/net/ssl/TLSv11/TLSRehandshakeWithDataExTest.java,,Compilation failed
+javax/net/ssl/TLSv11/TLSUnsupportedCiphersTest.java,,Compilation failed
+javax/net/ssl/TLSv12/TLSEnginesClosureTest.java,,Compilation failed
javax/sound/sampled/AudioInputStream/FrameLengthAfterConversion.java,,
+jdk/jfr/cmd/TestHelp.java,,java.lang.RuntimeException: 'Available commands are:' missing from stdout/stderr
+jdk/jfr/cmd/TestPrint.java,,Missing file' missing from stdout/stderr
+jdk/jfr/cmd/TestPrintDefault.java,,java.lang.RuntimeException: 'JVMInformation' missing from stdout/stderr
+jdk/jfr/cmd/TestPrintJSON.java,,javax.script.ScriptException: <eval>:1:17 Expected an operand but found eof var jsonObject = ^ in <eval> at line number 1 at column number 17
+jdk/jfr/cmd/TestPrintXML.java,,org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 1; Premature end of file.
+jdk/jfr/cmd/TestReconstruct.java,,java.lang.RuntimeException: 'Too few arguments' missing from stdout/stderr
+jdk/jfr/cmd/TestSplit.java,,java.lang.RuntimeException: 'Missing file' missing from stdout/stderr
+jdk/jfr/cmd/TestSummary.java,,java.lang.RuntimeException: 'Missing file' missing from stdout/stderr
+jdk/jfr/event/compiler/TestCompilerStats.java,,java.lang.RuntimeException: Field nmetodsSize not in event
+jdk/jfr/event/metadata/TestDefaultConfigurations.java,,Setting 'threshold' in event 'jdk.SecurityPropertyModification' was not configured in the configuration 'default'
+jdk/jfr/event/runtime/TestActiveSettingEvent.java,,java.lang.Exception: Could not find setting with name jdk.X509Validation#threshold
+jdk/jfr/event/runtime/TestModuleEvents.java,,java.lang.RuntimeException: assertEquals: expected jdk.proxy1 to equal java.base
jdk/jfr/event/runtime/TestNetworkUtilizationEvent.java,,
jdk/jfr/event/runtime/TestThreadParkEvent.java,,
jdk/jfr/event/sampling/TestNative.java,,
@@ -86,6 +152,7 @@ jdk/jfr/jcmd/TestJcmdStartStopDefault.java,,
jdk/jfr/jcmd/TestJcmdStartWithOptions.java,,
jdk/jfr/jcmd/TestJcmdStartWithSettings.java,,
jdk/jfr/jcmd/TestJcmdStopInvalidFile.java,,
+jdk/jfr/jvm/TestGetAllEventClasses.java,,Compilation failed
jdk/jfr/jvm/TestJfrJavaBase.java,,
jdk/jfr/startupargs/TestStartRecording.java,,
jdk/modules/incubator/ImageModules.java,,
@@ -123,4 +190,6 @@ tools/jlink/JLinkTest.java,,
tools/jlink/plugins/IncludeLocalesPluginTest.java,,
tools/jmod/hashes/HashesTest.java,,
tools/launcher/BigJar.java,b/111611473,
+tools/launcher/HelpFlagsTest.java,,java.lang.AssertionError: HelpFlagsTest failed: Tool jfr not covered by this test. Add specification to jdkTools array!
+tools/launcher/VersionCheck.java,,java.lang.AssertionError: VersionCheck failed: testToolVersion: [jfr];
tools/launcher/modules/patch/systemmodules/PatchSystemModules.java,,
diff --git a/test/runtimes/exclude_nodejs12.4.0.csv b/test/runtimes/exclude_nodejs12.4.0.csv
index 4eb0a4807..9aaf1e647 100644
--- a/test/runtimes/exclude_nodejs12.4.0.csv
+++ b/test/runtimes/exclude_nodejs12.4.0.csv
@@ -1,51 +1,49 @@
test name,bug id,comment
benchmark/test-benchmark-fs.js,,
-benchmark/test-benchmark-module.js,,
benchmark/test-benchmark-napi.js,,
doctool/test-make-doc.js,b/68848110,Expected to fail.
-fixtures/test-error-first-line-offset.js,,
-fixtures/test-fs-readfile-error.js,,
-fixtures/test-fs-stat-sync-overflow.js,,
-internet/test-dgram-broadcast-multi-process.js,,
-internet/test-dgram-multicast-multi-process.js,,
-internet/test-dgram-multicast-set-interface-lo.js,,
+internet/test-dgram-multicast-set-interface-lo.js,b/162798882,
internet/test-doctool-versions.js,,
internet/test-uv-threadpool-schedule.js,,
parallel/test-cluster-dgram-reuse.js,b/64024294,
parallel/test-dgram-bind-fd.js,b/132447356,
-parallel/test-dgram-create-socket-handle-fd.js,b/132447238,
-parallel/test-dgram-createSocket-type.js,b/68847739,
parallel/test-dgram-socket-buffer-size.js,b/68847921,
+parallel/test-dns-channel-timeout.js,b/161893056,
parallel/test-fs-access.js,,
-parallel/test-fs-write-stream-double-close.js,,
+parallel/test-fs-watchfile.js,,Flaky - File already exists error
parallel/test-fs-write-stream-throw-type-error.js,b/110226209,
-parallel/test-fs-write-stream.js,,
-parallel/test-http2-respond-file-error-pipe-offset.js,,
-parallel/test-os.js,,
+parallel/test-http-writable-true-after-close.js,,Flaky - Mismatched <anonymous> function calls. Expected exactly 1 actual 2
+parallel/test-os.js,b/63997097,
parallel/test-process-uid-gid.js,,
-pseudo-tty/test-assert-colors.js,,
-pseudo-tty/test-assert-no-color.js,,
-pseudo-tty/test-assert-position-indicator.js,,
-pseudo-tty/test-async-wrap-getasyncid-tty.js,,
-pseudo-tty/test-fatal-error.js,,
-pseudo-tty/test-handle-wrap-isrefed-tty.js,,
-pseudo-tty/test-readable-tty-keepalive.js,,
-pseudo-tty/test-set-raw-mode-reset-process-exit.js,,
-pseudo-tty/test-set-raw-mode-reset-signal.js,,
-pseudo-tty/test-set-raw-mode-reset.js,,
-pseudo-tty/test-stderr-stdout-handle-sigwinch.js,,
-pseudo-tty/test-stdout-read.js,,
-pseudo-tty/test-tty-color-support.js,,
-pseudo-tty/test-tty-isatty.js,,
-pseudo-tty/test-tty-stdin-call-end.js,,
-pseudo-tty/test-tty-stdin-end.js,,
-pseudo-tty/test-stdin-write.js,,
-pseudo-tty/test-tty-stdout-end.js,,
-pseudo-tty/test-tty-stdout-resize.js,,
-pseudo-tty/test-tty-stream-constructors.js,,
-pseudo-tty/test-tty-window-size.js,,
-pseudo-tty/test-tty-wrap.js,,
+parallel/test-tls-cli-min-version-1.0.js,,Flaky - EADDRINUSE
+parallel/test-tls-cli-min-version-1.3.js,,Flaky - EADDRINUSE
+parallel/test-tls-cli-max-version-1.2,,Flaky - EADDRINUSE
+parallel/test-tls-min-max-version.js,,Flaky - EADDRINUSE
+pseudo-tty/test-assert-colors.js,b/162801321,
+pseudo-tty/test-assert-no-color.js,b/162801321,
+pseudo-tty/test-assert-position-indicator.js,b/162801321,
+pseudo-tty/test-async-wrap-getasyncid-tty.js,b/162801321,
+pseudo-tty/test-fatal-error.js,b/162801321,
+pseudo-tty/test-handle-wrap-isrefed-tty.js,b/162801321,
+pseudo-tty/test-readable-tty-keepalive.js,b/162801321,
+pseudo-tty/test-set-raw-mode-reset-process-exit.js,b/162801321,
+pseudo-tty/test-set-raw-mode-reset-signal.js,b/162801321,
+pseudo-tty/test-set-raw-mode-reset.js,b/162801321,
+pseudo-tty/test-stderr-stdout-handle-sigwinch.js,b/162801321,
+pseudo-tty/test-stdout-read.js,b/162801321,
+pseudo-tty/test-tty-color-support.js,b/162801321,
+pseudo-tty/test-tty-isatty.js,b/162801321,
+pseudo-tty/test-tty-stdin-call-end.js,b/162801321,
+pseudo-tty/test-tty-stdin-end.js,b/162801321,
+pseudo-tty/test-stdin-write.js,b/162801321,
+pseudo-tty/test-tty-stdout-end.js,b/162801321,
+pseudo-tty/test-tty-stdout-resize.js,b/162801321,
+pseudo-tty/test-tty-stream-constructors.js,b/162801321,
+pseudo-tty/test-tty-window-size.js,b/162801321,
+pseudo-tty/test-tty-wrap.js,b/162801321,
pummel/test-net-pingpong.js,,
-pummel/test-vm-memleak.js,,
+pummel/test-vm-memleak.js,b/162799436,
+sequential/test-child-process-pass-fd.js,b/63926391,Flaky
+sequential/test-https-connect-localport.js,,Flaky - EADDRINUSE
sequential/test-net-bytes-per-incoming-chunk-overhead.js,,flaky - timeout
tick-processor/test-tick-processor-builtin.js,,
diff --git a/test/runtimes/proctor/java.go b/test/runtimes/proctor/java.go
index 737fbe23e..d456fa681 100644
--- a/test/runtimes/proctor/java.go
+++ b/test/runtimes/proctor/java.go
@@ -64,8 +64,11 @@ func (javaRunner) ListTests() ([]string, error) {
func (javaRunner) TestCmds(tests []string) []*exec.Cmd {
args := append(
[]string{
- "-noreport",
- "-dir:" + javaTestDir,
+ "-agentvm", // Execute each action using a pool of reusable JVMs.
+ "-dir:" + javaTestDir, // Base directory for test files and directories.
+ "-noreport", // Do not generate a final report.
+ "-timeoutFactor:20", // Extend the default timeout (2 min) of all tests by this factor.
+ "-verbose:nopass", // Verbose output but supress it for tests that passed.
},
tests...,
)
diff --git a/test/runtimes/proctor/nodejs.go b/test/runtimes/proctor/nodejs.go
index 23d6edc72..dead5af4f 100644
--- a/test/runtimes/proctor/nodejs.go
+++ b/test/runtimes/proctor/nodejs.go
@@ -41,6 +41,6 @@ func (nodejsRunner) ListTests() ([]string, error) {
// TestCmds implements TestRunner.TestCmds.
func (nodejsRunner) TestCmds(tests []string) []*exec.Cmd {
- args := append([]string{filepath.Join("tools", "test.py")}, tests...)
+ args := append([]string{filepath.Join("tools", "test.py"), "--timeout=180"}, tests...)
return []*exec.Cmd{exec.Command("/usr/bin/python", args...)}
}
diff --git a/test/runtimes/runner/main.go b/test/runtimes/runner/main.go
index e230912c9..948e7cf9c 100644
--- a/test/runtimes/runner/main.go
+++ b/test/runtimes/runner/main.go
@@ -40,7 +40,7 @@ var (
)
// Wait time for each test to run.
-const timeout = 45 * time.Minute
+const timeout = 90 * time.Minute
func main() {
flag.Parse()
diff --git a/test/syscalls/linux/inotify.cc b/test/syscalls/linux/inotify.cc
index 220874aeb..6cf398097 100644
--- a/test/syscalls/linux/inotify.cc
+++ b/test/syscalls/linux/inotify.cc
@@ -18,6 +18,7 @@
#include <sys/epoll.h>
#include <sys/inotify.h>
#include <sys/ioctl.h>
+#include <sys/sendfile.h>
#include <sys/time.h>
#include <sys/xattr.h>
@@ -1681,6 +1682,60 @@ TEST(Inotify, EpollNoDeadlock) {
}
}
+TEST(Inotify, Fallocate) {
+ const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
+ const FileDescriptor fd =
+ ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR));
+
+ const FileDescriptor inotify_fd =
+ ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
+ const int wd = ASSERT_NO_ERRNO_AND_VALUE(
+ InotifyAddWatch(inotify_fd.get(), file.path(), IN_ALL_EVENTS));
+
+ // Do an arbitrary modification with fallocate.
+ ASSERT_THAT(fallocate(fd.get(), 0, 0, 123), SyscallSucceeds());
+ std::vector<Event> events =
+ ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
+ EXPECT_THAT(events, Are({Event(IN_MODIFY, wd)}));
+}
+
+TEST(Inotify, Sendfile) {
+ SKIP_IF(IsRunningWithVFS1());
+
+ const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+ const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(
+ TempPath::CreateFileWith(root.path(), "x", 0644));
+ const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
+ const FileDescriptor in =
+ ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY));
+ const FileDescriptor out =
+ ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_WRONLY));
+
+ // Create separate inotify instances for the in and out fds. If both watches
+ // were on the same instance, we would have discrepancies between Linux and
+ // gVisor (order of events, duplicate events), which is not that important
+ // since inotify is asynchronous anyway.
+ const FileDescriptor in_inotify =
+ ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
+ const FileDescriptor out_inotify =
+ ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
+ const int in_wd = ASSERT_NO_ERRNO_AND_VALUE(
+ InotifyAddWatch(in_inotify.get(), in_file.path(), IN_ALL_EVENTS));
+ const int out_wd = ASSERT_NO_ERRNO_AND_VALUE(
+ InotifyAddWatch(out_inotify.get(), out_file.path(), IN_ALL_EVENTS));
+
+ ASSERT_THAT(sendfile(out.get(), in.get(), /*offset=*/nullptr, 1),
+ SyscallSucceeds());
+
+ // Expect a single access event and a single modify event.
+ std::vector<Event> in_events =
+ ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(in_inotify.get()));
+ std::vector<Event> out_events =
+ ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(out_inotify.get()));
+ EXPECT_THAT(in_events, Are({Event(IN_ACCESS, in_wd)}));
+ EXPECT_THAT(out_events, Are({Event(IN_MODIFY, out_wd)}));
+}
+
// On Linux, inotify behavior is not very consistent with splice(2). We try our
// best to emulate Linux for very basic calls to splice.
TEST(Inotify, SpliceOnWatchTarget) {
diff --git a/test/syscalls/linux/mount.cc b/test/syscalls/linux/mount.cc
index 97e8d0f7e..46b6f38db 100644
--- a/test/syscalls/linux/mount.cc
+++ b/test/syscalls/linux/mount.cc
@@ -326,6 +326,14 @@ TEST(MountTest, MountFuseFilesystemNoDevice) {
SKIP_IF(IsRunningOnGvisor() && !IsFUSEEnabled());
auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+
+ // Before kernel version 4.16-rc6, FUSE mount is protected by
+ // capable(CAP_SYS_ADMIN). After this version, it uses
+ // ns_capable(CAP_SYS_ADMIN) to protect. Before the 4.16 kernel, it was not
+ // allowed to mount fuse file systems without the global CAP_SYS_ADMIN.
+ int res = mount("", dir.path().c_str(), "fuse", 0, "");
+ SKIP_IF(!IsRunningOnGvisor() && res == -1 && errno == EPERM);
+
EXPECT_THAT(mount("", dir.path().c_str(), "fuse", 0, ""),
SyscallFailsWithErrno(EINVAL));
}
@@ -339,6 +347,12 @@ TEST(MountTest, MountFuseFilesystem) {
std::string mopts = "fd=" + std::to_string(fd.get());
auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+
+ // See comments in MountFuseFilesystemNoDevice for the reason why we skip
+ // EPERM when running on Linux.
+ int res = mount("", dir.path().c_str(), "fuse", 0, "");
+ SKIP_IF(!IsRunningOnGvisor() && res == -1 && errno == EPERM);
+
auto const mount =
ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), "fuse", 0, mopts, 0));
}
diff --git a/test/syscalls/linux/raw_socket_hdrincl.cc b/test/syscalls/linux/raw_socket_hdrincl.cc
index 97f0467aa..2f25aceb2 100644
--- a/test/syscalls/linux/raw_socket_hdrincl.cc
+++ b/test/syscalls/linux/raw_socket_hdrincl.cc
@@ -178,7 +178,7 @@ TEST_F(RawHDRINCL, ConnectToLoopback) {
}
TEST_F(RawHDRINCL, SendWithoutConnectSucceeds) {
- // FIXME(github.dev/issue/3159): Test currently flaky.
+ // FIXME(gvisor.dev/issue/3159): Test currently flaky.
SKIP_IF(true);
struct iphdr hdr = LoopbackHeader();
@@ -284,7 +284,7 @@ TEST_F(RawHDRINCL, SendAndReceive) {
// Send and receive a packet where the sendto address is not the same as the
// provided destination.
TEST_F(RawHDRINCL, SendAndReceiveDifferentAddress) {
- // FIXME(github.dev/issue/3160): Test currently flaky.
+ // FIXME(gvisor.dev/issue/3160): Test currently flaky.
SKIP_IF(true);
int port = 40000;
diff --git a/test/syscalls/linux/socket_netlink_route.cc b/test/syscalls/linux/socket_netlink_route.cc
index e6647a1c3..b3fcf8e7c 100644
--- a/test/syscalls/linux/socket_netlink_route.cc
+++ b/test/syscalls/linux/socket_netlink_route.cc
@@ -577,7 +577,10 @@ TEST(NetlinkRouteTest, GetRouteDump) {
std::cout << std::endl;
- if (msg->rtm_table == RT_TABLE_MAIN) {
+ // If the test is running in a new network namespace, it will have only
+ // the local route table.
+ if (msg->rtm_table == RT_TABLE_MAIN ||
+ (!IsRunningOnGvisor() && msg->rtm_table == RT_TABLE_LOCAL)) {
routeFound = true;
dstFound = rtDstFound && dstFound;
}