summaryrefslogtreecommitdiffhomepage
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/README.md4
-rw-r--r--test/benchmarks/base/size_test.go1
-rw-r--r--test/benchmarks/base/startup_test.go3
-rw-r--r--test/benchmarks/network/nginx_test.go47
-rw-r--r--test/benchmarks/network/static_server.go1
-rw-r--r--test/benchmarks/tcp/tcp_proxy.go4
-rw-r--r--test/iptables/iptables_test.go43
-rw-r--r--test/packetimpact/runner/dut.go4
-rw-r--r--test/packetimpact/tests/BUILD10
-rw-r--r--test/packetimpact/tests/tcp_linger_test.go17
-rw-r--r--test/packetimpact/tests/tcp_rcv_buf_space_test.go80
-rw-r--r--test/root/root.go2
-rw-r--r--test/runtimes/exclude/java11.csv2
-rw-r--r--test/runtimes/proctor/BUILD24
-rw-r--r--test/runtimes/proctor/lib/BUILD24
-rw-r--r--test/runtimes/proctor/lib/go.go (renamed from test/runtimes/proctor/go.go)4
-rw-r--r--test/runtimes/proctor/lib/java.go (renamed from test/runtimes/proctor/java.go)2
-rw-r--r--test/runtimes/proctor/lib/lib.go (renamed from test/runtimes/proctor/proctor.go)78
-rw-r--r--test/runtimes/proctor/lib/lib_test.go (renamed from test/runtimes/proctor/proctor_test.go)6
-rw-r--r--test/runtimes/proctor/lib/nodejs.go (renamed from test/runtimes/proctor/nodejs.go)4
-rw-r--r--test/runtimes/proctor/lib/php.go (renamed from test/runtimes/proctor/php.go)4
-rw-r--r--test/runtimes/proctor/lib/python.go (renamed from test/runtimes/proctor/python.go)2
-rw-r--r--test/runtimes/proctor/main.go85
-rw-r--r--test/runtimes/runner/BUILD15
-rw-r--r--test/runtimes/runner/lib/BUILD22
-rw-r--r--test/runtimes/runner/lib/exclude_test.go (renamed from test/runtimes/runner/exclude_test.go)6
-rw-r--r--test/runtimes/runner/lib/lib.go185
-rw-r--r--test/runtimes/runner/main.go169
-rw-r--r--test/syscalls/BUILD2
-rw-r--r--test/syscalls/linux/BUILD1
-rw-r--r--test/syscalls/linux/inotify.cc126
-rw-r--r--test/syscalls/linux/ip6tables.cc26
-rw-r--r--test/syscalls/linux/iptables.cc26
-rw-r--r--test/syscalls/linux/kcov.cc5
-rw-r--r--test/syscalls/linux/mknod.cc9
-rw-r--r--test/syscalls/linux/proc.cc23
-rw-r--r--test/syscalls/linux/tcp_socket.cc30
37 files changed, 693 insertions, 403 deletions
diff --git a/test/README.md b/test/README.md
index 02bbf42ff..15b0f4c33 100644
--- a/test/README.md
+++ b/test/README.md
@@ -24,11 +24,11 @@ also used to run these tests in `kokoro`.
To run image and integration tests, run:
-`./scripts/docker_tests.sh`
+`make docker-tests`
To run root tests, run:
-`./scripts/root_tests.sh`
+`make root-tests`
There are a few other interesting variations for image and integration tests:
diff --git a/test/benchmarks/base/size_test.go b/test/benchmarks/base/size_test.go
index 3c1364faf..7d3877459 100644
--- a/test/benchmarks/base/size_test.go
+++ b/test/benchmarks/base/size_test.go
@@ -105,6 +105,7 @@ func BenchmarkSizeNginx(b *testing.B) {
machine: machine,
port: port,
runOpts: runOpts,
+ cmd: []string{"nginx", "-c", "/etc/nginx/nginx_gofer.conf"},
})
defer cleanUpContainers(ctx, servers)
diff --git a/test/benchmarks/base/startup_test.go b/test/benchmarks/base/startup_test.go
index 4628a0a41..c36a544db 100644
--- a/test/benchmarks/base/startup_test.go
+++ b/test/benchmarks/base/startup_test.go
@@ -64,6 +64,7 @@ func BenchmarkStartupNginx(b *testing.B) {
machine: machine,
runOpts: runOpts,
port: 80,
+ cmd: []string{"nginx", "-c", "/etc/nginx/nginx_gofer.conf"},
})
}
@@ -123,8 +124,6 @@ func redisInstance(ctx context.Context, b *testing.B, machine harness.Machine) (
// runServerWorkload runs a server workload defined by 'runOpts' and 'cmd'.
// 'clientMachine' is used to connect to the server on 'serverMachine'.
func runServerWorkload(ctx context.Context, b *testing.B, args serverArgs) {
- b.Helper()
-
b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := func() error {
diff --git a/test/benchmarks/network/nginx_test.go b/test/benchmarks/network/nginx_test.go
index 036fd666f..9ec70369b 100644
--- a/test/benchmarks/network/nginx_test.go
+++ b/test/benchmarks/network/nginx_test.go
@@ -36,50 +36,63 @@ var nginxDocs = map[string]string{
func BenchmarkNginxConcurrency(b *testing.B) {
concurrency := []int{1, 25, 100, 1000}
for _, c := range concurrency {
- b.Run(fmt.Sprintf("%d", c), func(b *testing.B) {
- hey := &tools.Hey{
- Requests: c * b.N,
- Concurrency: c,
- Doc: nginxDocs["10kb"], // see Dockerfile '//images/benchmarks/nginx' and httpd_test.
+ for _, tmpfs := range []bool{true, false} {
+ fs := "Gofer"
+ if tmpfs {
+ fs = "Tmpfs"
}
- runNginx(b, hey, false /* reverse */)
- })
+ name := fmt.Sprintf("%d_%s", c, fs)
+ b.Run(name, func(b *testing.B) {
+ hey := &tools.Hey{
+ Requests: c * b.N,
+ Concurrency: c,
+ Doc: nginxDocs["10kb"], // see Dockerfile '//images/benchmarks/nginx' and httpd_test.
+ }
+ runNginx(b, hey, false /* reverse */, tmpfs /* tmpfs */)
+ })
+ }
+
}
}
// BenchmarkNginxDocSize iterates over different sized payloads, testing how
// well the runtime handles sending different payload sizes.
func BenchmarkNginxDocSize(b *testing.B) {
- benchmarkHttpdDocSize(b, false /* reverse */)
+ benchmarkNginxDocSize(b, false /* reverse */, true /* tmpfs */)
+ benchmarkNginxDocSize(b, false /* reverse */, false /* tmpfs */)
}
// BenchmarkReverseNginxDocSize iterates over different sized payloads, testing
// how well the runtime handles receiving different payload sizes.
func BenchmarkReverseNginxDocSize(b *testing.B) {
- benchmarkHttpdDocSize(b, true /* reverse */)
+ benchmarkNginxDocSize(b, true /* reverse */, true /* tmpfs */)
}
// benchmarkNginxDocSize iterates through all doc sizes, running subbenchmarks
// for each size.
-func benchmarkNginxDocSize(b *testing.B, reverse bool) {
- b.Helper()
+func benchmarkNginxDocSize(b *testing.B, reverse, tmpfs bool) {
for name, filename := range nginxDocs {
concurrency := []int{1, 25, 50, 100, 1000}
for _, c := range concurrency {
- b.Run(fmt.Sprintf("%s_%d", name, c), func(b *testing.B) {
+ fs := "Gofer"
+ if tmpfs {
+ fs = "Tmpfs"
+ }
+ benchName := fmt.Sprintf("%s_%d_%s", name, c, fs)
+ b.Run(benchName, func(b *testing.B) {
hey := &tools.Hey{
Requests: c * b.N,
Concurrency: c,
Doc: filename,
}
- runNginx(b, hey, reverse)
+ runNginx(b, hey, reverse, tmpfs)
})
}
}
}
// runNginx configures the static serving methods to run httpd.
-func runNginx(b *testing.B, hey *tools.Hey, reverse bool) {
+func runNginx(b *testing.B, hey *tools.Hey, reverse, tmpfs bool) {
// nginx runs on port 80.
port := 80
nginxRunOpts := dockerutil.RunOpts{
@@ -87,7 +100,11 @@ func runNginx(b *testing.B, hey *tools.Hey, reverse bool) {
Ports: []int{port},
}
+ nginxCmd := []string{"nginx", "-c", "/etc/nginx/nginx_gofer.conf"}
+ if tmpfs {
+ nginxCmd = []string{"sh", "-c", "mkdir -p /tmp/html && cp -a /local/* /tmp/html && nginx -c /etc/nginx/nginx.conf"}
+ }
+
// Command copies nginxDocs to tmpfs serving directory and runs nginx.
- nginxCmd := []string{"sh", "-c", "mkdir -p /tmp/html && cp -a /local/* /tmp/html && nginx"}
runStaticServer(b, nginxRunOpts, nginxCmd, port, hey, reverse)
}
diff --git a/test/benchmarks/network/static_server.go b/test/benchmarks/network/static_server.go
index 3ef62a71f..e747a1395 100644
--- a/test/benchmarks/network/static_server.go
+++ b/test/benchmarks/network/static_server.go
@@ -25,7 +25,6 @@ import (
// runStaticServer runs static serving workloads (httpd, nginx).
func runStaticServer(b *testing.B, serverOpts dockerutil.RunOpts, serverCmd []string, port int, hey *tools.Hey, reverse bool) {
- b.Helper()
ctx := context.Background()
// Get two machines: a client and server.
diff --git a/test/benchmarks/tcp/tcp_proxy.go b/test/benchmarks/tcp/tcp_proxy.go
index 6cabfb451..5afe10f69 100644
--- a/test/benchmarks/tcp/tcp_proxy.go
+++ b/test/benchmarks/tcp/tcp_proxy.go
@@ -174,8 +174,8 @@ func newNetstackImpl(mode string) (impl, error) {
}
// Create a new network stack.
- netProtos := []stack.NetworkProtocol{ipv4.NewProtocol(), arp.NewProtocol()}
- transProtos := []stack.TransportProtocol{tcp.NewProtocol(), udp.NewProtocol()}
+ netProtos := []stack.NetworkProtocolFactory{ipv4.NewProtocol, arp.NewProtocol}
+ transProtos := []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol}
s := stack.New(stack.Options{
NetworkProtocols: netProtos,
TransportProtocols: transProtos,
diff --git a/test/iptables/iptables_test.go b/test/iptables/iptables_test.go
index 398f70ecd..834f7615f 100644
--- a/test/iptables/iptables_test.go
+++ b/test/iptables/iptables_test.go
@@ -48,13 +48,6 @@ func singleTest(t *testing.T, test TestCase) {
}
}
-// TODO(gvisor.dev/issue/3549): IPv6 NAT support.
-func ipv4Test(t *testing.T, test TestCase) {
- t.Run("IPv4", func(t *testing.T) {
- iptablesTest(t, test, false)
- })
-}
-
func iptablesTest(t *testing.T, test TestCase, ipv6 bool) {
if _, ok := Tests[test.Name()]; !ok {
t.Fatalf("no test found with name %q. Has it been registered?", test.Name())
@@ -325,66 +318,66 @@ func TestFilterOutputInvertDestination(t *testing.T) {
}
func TestNATPreRedirectUDPPort(t *testing.T) {
- ipv4Test(t, NATPreRedirectUDPPort{})
+ singleTest(t, NATPreRedirectUDPPort{})
}
func TestNATPreRedirectTCPPort(t *testing.T) {
- ipv4Test(t, NATPreRedirectTCPPort{})
+ singleTest(t, NATPreRedirectTCPPort{})
}
func TestNATPreRedirectTCPOutgoing(t *testing.T) {
- ipv4Test(t, NATPreRedirectTCPOutgoing{})
+ singleTest(t, NATPreRedirectTCPOutgoing{})
}
func TestNATOutRedirectTCPIncoming(t *testing.T) {
- ipv4Test(t, NATOutRedirectTCPIncoming{})
+ singleTest(t, NATOutRedirectTCPIncoming{})
}
func TestNATOutRedirectUDPPort(t *testing.T) {
- ipv4Test(t, NATOutRedirectUDPPort{})
+ singleTest(t, NATOutRedirectUDPPort{})
}
func TestNATOutRedirectTCPPort(t *testing.T) {
- ipv4Test(t, NATOutRedirectTCPPort{})
+ singleTest(t, NATOutRedirectTCPPort{})
}
func TestNATDropUDP(t *testing.T) {
- ipv4Test(t, NATDropUDP{})
+ singleTest(t, NATDropUDP{})
}
func TestNATAcceptAll(t *testing.T) {
- ipv4Test(t, NATAcceptAll{})
+ singleTest(t, NATAcceptAll{})
}
func TestNATOutRedirectIP(t *testing.T) {
- ipv4Test(t, NATOutRedirectIP{})
+ singleTest(t, NATOutRedirectIP{})
}
func TestNATOutDontRedirectIP(t *testing.T) {
- ipv4Test(t, NATOutDontRedirectIP{})
+ singleTest(t, NATOutDontRedirectIP{})
}
func TestNATOutRedirectInvert(t *testing.T) {
- ipv4Test(t, NATOutRedirectInvert{})
+ singleTest(t, NATOutRedirectInvert{})
}
func TestNATPreRedirectIP(t *testing.T) {
- ipv4Test(t, NATPreRedirectIP{})
+ singleTest(t, NATPreRedirectIP{})
}
func TestNATPreDontRedirectIP(t *testing.T) {
- ipv4Test(t, NATPreDontRedirectIP{})
+ singleTest(t, NATPreDontRedirectIP{})
}
func TestNATPreRedirectInvert(t *testing.T) {
- ipv4Test(t, NATPreRedirectInvert{})
+ singleTest(t, NATPreRedirectInvert{})
}
func TestNATRedirectRequiresProtocol(t *testing.T) {
- ipv4Test(t, NATRedirectRequiresProtocol{})
+ singleTest(t, NATRedirectRequiresProtocol{})
}
func TestNATLoopbackSkipsPrerouting(t *testing.T) {
- ipv4Test(t, NATLoopbackSkipsPrerouting{})
+ singleTest(t, NATLoopbackSkipsPrerouting{})
}
func TestInputSource(t *testing.T) {
@@ -421,9 +414,9 @@ func TestFilterAddrs(t *testing.T) {
}
func TestNATPreOriginalDst(t *testing.T) {
- ipv4Test(t, NATPreOriginalDst{})
+ singleTest(t, NATPreOriginalDst{})
}
func TestNATOutOriginalDst(t *testing.T) {
- ipv4Test(t, NATOutOriginalDst{})
+ singleTest(t, NATOutOriginalDst{})
}
diff --git a/test/packetimpact/runner/dut.go b/test/packetimpact/runner/dut.go
index 96a0fb6c8..59bb68eb1 100644
--- a/test/packetimpact/runner/dut.go
+++ b/test/packetimpact/runner/dut.go
@@ -69,8 +69,8 @@ func RegisterFlags(fs *flag.FlagSet) {
fs.BoolVar(&native, "native", false, "whether the test should be run natively")
fs.StringVar(&testbenchBinary, "testbench_binary", "", "path to the testbench binary")
fs.BoolVar(&tshark, "tshark", false, "use more verbose tshark in logs instead of tcpdump")
- flag.Var(&extraTestArgs, "extra_test_arg", "extra arguments to pass to the testbench")
- flag.BoolVar(&expectFailure, "expect_failure", false, "expect that the test will fail when run")
+ fs.Var(&extraTestArgs, "extra_test_arg", "extra arguments to pass to the testbench")
+ fs.BoolVar(&expectFailure, "expect_failure", false, "expect that the test will fail when run")
}
// CtrlPort is the port that posix_server listens on.
diff --git a/test/packetimpact/tests/BUILD b/test/packetimpact/tests/BUILD
index fbfea61e1..94731c64b 100644
--- a/test/packetimpact/tests/BUILD
+++ b/test/packetimpact/tests/BUILD
@@ -340,3 +340,13 @@ packetimpact_go_test(
"@org_golang_x_sys//unix:go_default_library",
],
)
+
+packetimpact_go_test(
+ name = "tcp_rcv_buf_space",
+ srcs = ["tcp_rcv_buf_space_test.go"],
+ deps = [
+ "//pkg/tcpip/header",
+ "//test/packetimpact/testbench",
+ "@org_golang_x_sys//unix:go_default_library",
+ ],
+)
diff --git a/test/packetimpact/tests/tcp_linger_test.go b/test/packetimpact/tests/tcp_linger_test.go
index 913e49e06..b9a0409aa 100644
--- a/test/packetimpact/tests/tcp_linger_test.go
+++ b/test/packetimpact/tests/tcp_linger_test.go
@@ -251,3 +251,20 @@ func TestTCPLingerShutdownSendNonZeroTimeout(t *testing.T) {
})
}
}
+
+func TestTCPLingerNonEstablished(t *testing.T) {
+ dut := testbench.NewDUT(t)
+ newFD := dut.Socket(t, unix.AF_INET, unix.SOCK_STREAM, unix.IPPROTO_TCP)
+ dut.SetSockLingerOption(t, newFD, lingerDuration, true)
+
+ // As the socket is in the initial state, Close() should not linger
+ // and return immediately.
+ start := time.Now()
+ dut.CloseWithErrno(context.Background(), t, newFD)
+ diff := time.Since(start)
+
+ if diff > lingerDuration {
+ t.Errorf("expected close to return within %s, but returned after %s", lingerDuration, diff)
+ }
+ dut.TearDown()
+}
diff --git a/test/packetimpact/tests/tcp_rcv_buf_space_test.go b/test/packetimpact/tests/tcp_rcv_buf_space_test.go
new file mode 100644
index 000000000..cfbba1e8e
--- /dev/null
+++ b/test/packetimpact/tests/tcp_rcv_buf_space_test.go
@@ -0,0 +1,80 @@
+// 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 tcp_rcv_buf_space_test
+
+import (
+ "context"
+ "flag"
+ "syscall"
+ "testing"
+
+ "golang.org/x/sys/unix"
+ "gvisor.dev/gvisor/pkg/tcpip/header"
+ "gvisor.dev/gvisor/test/packetimpact/testbench"
+)
+
+func init() {
+ testbench.RegisterFlags(flag.CommandLine)
+}
+
+// TestReduceRecvBuf tests that a packet within window is still dropped
+// if the available buffer space drops below the size of the incoming
+// segment.
+func TestReduceRecvBuf(t *testing.T) {
+ dut := testbench.NewDUT(t)
+ defer dut.TearDown()
+ listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
+ defer dut.Close(t, listenFd)
+ conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
+ defer conn.Close(t)
+
+ conn.Connect(t)
+ acceptFd, _ := dut.Accept(t, listenFd)
+ defer dut.Close(t, acceptFd)
+
+ // Set a small receive buffer for the test.
+ const rcvBufSz = 4096
+ dut.SetSockOptInt(t, acceptFd, unix.SOL_SOCKET, unix.SO_RCVBUF, rcvBufSz)
+
+ // Retrieve the actual buffer.
+ bufSz := dut.GetSockOptInt(t, acceptFd, unix.SOL_SOCKET, unix.SO_RCVBUF)
+
+ // Generate a payload of 1 more than the actual buffer size used by the
+ // DUT.
+ sampleData := testbench.GenerateRandomPayload(t, int(bufSz)+1)
+ // Send and receive sample data to the dut.
+ const pktSize = 1400
+ for payload := sampleData; len(payload) != 0; {
+ payloadBytes := pktSize
+ if l := len(payload); l < payloadBytes {
+ payloadBytes = l
+ }
+
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, []testbench.Layer{&testbench.Payload{Bytes: payload[:payloadBytes]}}...)
+ payload = payload[payloadBytes:]
+ }
+
+ // First read should read < len(sampleData)
+ if ret, _, err := dut.RecvWithErrno(context.Background(), t, acceptFd, int32(len(sampleData)), 0); ret == -1 || int(ret) == len(sampleData) {
+ t.Fatalf("dut.RecvWithErrno(ctx, t, %d, %d, 0) = %d,_, %s", acceptFd, int32(len(sampleData)), ret, err)
+ }
+
+ // Second read should return EAGAIN as the last segment should have been
+ // dropped due to it exceeding the receive buffer space available in the
+ // socket.
+ if ret, got, err := dut.RecvWithErrno(context.Background(), t, acceptFd, int32(len(sampleData)), syscall.MSG_DONTWAIT); got != nil || ret != -1 || err != syscall.EAGAIN {
+ t.Fatalf("expected no packets but got: %s", got)
+ }
+}
diff --git a/test/root/root.go b/test/root/root.go
index 0f1d29faf..441fa5e2e 100644
--- a/test/root/root.go
+++ b/test/root/root.go
@@ -17,5 +17,5 @@
// docker, containerd, and crictl installed. To run these tests from the
// project root directory:
//
-// ./scripts/root_tests.sh
+// make root-tests
package root
diff --git a/test/runtimes/exclude/java11.csv b/test/runtimes/exclude/java11.csv
index 997a29cad..f779df8d5 100644
--- a/test/runtimes/exclude/java11.csv
+++ b/test/runtimes/exclude/java11.csv
@@ -1,9 +1,11 @@
test name,bug id,comment
com/sun/crypto/provider/Cipher/PBE/PKCS12Cipher.java,,Fails in Docker
+com/sun/jdi/InvokeHangTest.java,https://bugs.openjdk.java.net/browse/JDK-8218463,
com/sun/jdi/NashornPopFrameTest.java,,
com/sun/jdi/ProcessAttachTest.java,,
com/sun/management/HotSpotDiagnosticMXBean/CheckOrigin.java,,Fails in Docker
com/sun/management/OperatingSystemMXBean/GetCommittedVirtualMemorySize.java,,
+com/sun/management/ThreadMXBean/ThreadCpuTimeArray.java,,Test assumes high CPU clock precision
com/sun/management/UnixOperatingSystemMXBean/GetMaxFileDescriptorCount.sh,,
com/sun/tools/attach/AttachSelf.java,,
com/sun/tools/attach/BasicTests.java,,
diff --git a/test/runtimes/proctor/BUILD b/test/runtimes/proctor/BUILD
index d1935cbe8..fdc6d3173 100644
--- a/test/runtimes/proctor/BUILD
+++ b/test/runtimes/proctor/BUILD
@@ -1,29 +1,11 @@
-load("//tools:defs.bzl", "go_binary", "go_test")
+load("//tools:defs.bzl", "go_binary")
package(licenses = ["notice"])
go_binary(
name = "proctor",
- srcs = [
- "go.go",
- "java.go",
- "nodejs.go",
- "php.go",
- "proctor.go",
- "python.go",
- ],
+ srcs = ["main.go"],
pure = True,
visibility = ["//test/runtimes:__pkg__"],
-)
-
-go_test(
- name = "proctor_test",
- size = "small",
- srcs = ["proctor_test.go"],
- library = ":proctor",
- nogo = False, # FIXME(gvisor.dev/issue/3374): Not working with all build systems.
- pure = True,
- deps = [
- "//pkg/test/testutil",
- ],
+ deps = ["//test/runtimes/proctor/lib"],
)
diff --git a/test/runtimes/proctor/lib/BUILD b/test/runtimes/proctor/lib/BUILD
new file mode 100644
index 000000000..0c8367dfe
--- /dev/null
+++ b/test/runtimes/proctor/lib/BUILD
@@ -0,0 +1,24 @@
+load("//tools:defs.bzl", "go_library", "go_test")
+
+package(licenses = ["notice"])
+
+go_library(
+ name = "lib",
+ srcs = [
+ "go.go",
+ "java.go",
+ "lib.go",
+ "nodejs.go",
+ "php.go",
+ "python.go",
+ ],
+ visibility = ["//test/runtimes/proctor:__pkg__"],
+)
+
+go_test(
+ name = "lib_test",
+ size = "small",
+ srcs = ["lib_test.go"],
+ library = ":lib",
+ deps = ["//pkg/test/testutil"],
+)
diff --git a/test/runtimes/proctor/go.go b/test/runtimes/proctor/lib/go.go
index d0ae844e6..5c48fb60b 100644
--- a/test/runtimes/proctor/go.go
+++ b/test/runtimes/proctor/lib/go.go
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package main
+package lib
import (
"fmt"
@@ -59,7 +59,7 @@ func (goRunner) ListTests() ([]string, error) {
}
// Go tests on disk.
- diskSlice, err := search(goTestDir, goTestRegEx)
+ diskSlice, err := Search(goTestDir, goTestRegEx)
if err != nil {
return nil, err
}
diff --git a/test/runtimes/proctor/java.go b/test/runtimes/proctor/lib/java.go
index d456fa681..3105011ff 100644
--- a/test/runtimes/proctor/java.go
+++ b/test/runtimes/proctor/lib/java.go
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package main
+package lib
import (
"fmt"
diff --git a/test/runtimes/proctor/proctor.go b/test/runtimes/proctor/lib/lib.go
index 9e0642424..f2ba82498 100644
--- a/test/runtimes/proctor/proctor.go
+++ b/test/runtimes/proctor/lib/lib.go
@@ -12,20 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-// Binary proctor runs the test for a particular runtime. It is meant to be
-// included in Docker images for all runtime tests.
-package main
+// Package lib contains proctor functions.
+package lib
import (
- "flag"
"fmt"
- "log"
"os"
"os/exec"
"os/signal"
"path/filepath"
"regexp"
- "strings"
"syscall"
)
@@ -42,66 +38,8 @@ type TestRunner interface {
TestCmds(tests []string) []*exec.Cmd
}
-var (
- runtime = flag.String("runtime", "", "name of runtime")
- list = flag.Bool("list", false, "list all available tests")
- testNames = flag.String("tests", "", "run a subset of the available tests")
- pause = flag.Bool("pause", false, "cause container to pause indefinitely, reaping any zombie children")
-)
-
-func main() {
- flag.Parse()
-
- if *pause {
- pauseAndReap()
- panic("pauseAndReap should never return")
- }
-
- if *runtime == "" {
- log.Fatalf("runtime flag must be provided")
- }
-
- tr, err := testRunnerForRuntime(*runtime)
- if err != nil {
- log.Fatalf("%v", err)
- }
-
- // List tests.
- if *list {
- tests, err := tr.ListTests()
- if err != nil {
- log.Fatalf("failed to list tests: %v", err)
- }
- for _, test := range tests {
- fmt.Println(test)
- }
- return
- }
-
- var tests []string
- if *testNames == "" {
- // Run every test.
- tests, err = tr.ListTests()
- if err != nil {
- log.Fatalf("failed to get all tests: %v", err)
- }
- } else {
- // Run subset of test.
- tests = strings.Split(*testNames, ",")
- }
-
- // Run tests.
- cmds := tr.TestCmds(tests)
- for _, cmd := range cmds {
- cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
- if err := cmd.Run(); err != nil {
- log.Fatalf("FAIL: %v", err)
- }
- }
-}
-
-// testRunnerForRuntime returns a new TestRunner for the given runtime.
-func testRunnerForRuntime(runtime string) (TestRunner, error) {
+// TestRunnerForRuntime returns a new TestRunner for the given runtime.
+func TestRunnerForRuntime(runtime string) (TestRunner, error) {
switch runtime {
case "go":
return goRunner{}, nil
@@ -117,8 +55,8 @@ func testRunnerForRuntime(runtime string) (TestRunner, error) {
return nil, fmt.Errorf("invalid runtime %q", runtime)
}
-// pauseAndReap is like init. It runs forever and reaps any children.
-func pauseAndReap() {
+// PauseAndReap is like init. It runs forever and reaps any children.
+func PauseAndReap() {
// Get notified of any new children.
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGCHLD)
@@ -138,9 +76,9 @@ func pauseAndReap() {
}
}
-// search is a helper function to find tests in the given directory that match
+// Search is a helper function to find tests in the given directory that match
// the regex.
-func search(root string, testFilter *regexp.Regexp) ([]string, error) {
+func Search(root string, testFilter *regexp.Regexp) ([]string, error) {
var testSlice []string
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
diff --git a/test/runtimes/proctor/proctor_test.go b/test/runtimes/proctor/lib/lib_test.go
index 6ef2de085..1193d2e28 100644
--- a/test/runtimes/proctor/proctor_test.go
+++ b/test/runtimes/proctor/lib/lib_test.go
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package main
+package lib
import (
"io/ioutil"
@@ -47,7 +47,7 @@ func TestSearchEmptyDir(t *testing.T) {
var want []string
testFilter := regexp.MustCompile(`^test-[^-].+\.tc$`)
- got, err := search(td, testFilter)
+ got, err := Search(td, testFilter)
if err != nil {
t.Errorf("search error: %v", err)
}
@@ -116,7 +116,7 @@ func TestSearch(t *testing.T) {
}
testFilter := regexp.MustCompile(`^test-[^-].+\.tc$`)
- got, err := search(td, testFilter)
+ got, err := Search(td, testFilter)
if err != nil {
t.Errorf("search error: %v", err)
}
diff --git a/test/runtimes/proctor/nodejs.go b/test/runtimes/proctor/lib/nodejs.go
index dead5af4f..320597aa5 100644
--- a/test/runtimes/proctor/nodejs.go
+++ b/test/runtimes/proctor/lib/nodejs.go
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package main
+package lib
import (
"os/exec"
@@ -32,7 +32,7 @@ var _ TestRunner = nodejsRunner{}
// ListTests implements TestRunner.ListTests.
func (nodejsRunner) ListTests() ([]string, error) {
- testSlice, err := search(nodejsTestDir, nodejsTestRegEx)
+ testSlice, err := Search(nodejsTestDir, nodejsTestRegEx)
if err != nil {
return nil, err
}
diff --git a/test/runtimes/proctor/php.go b/test/runtimes/proctor/lib/php.go
index 6a83d64e3..b67a60a97 100644
--- a/test/runtimes/proctor/php.go
+++ b/test/runtimes/proctor/lib/php.go
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package main
+package lib
import (
"os/exec"
@@ -29,7 +29,7 @@ var _ TestRunner = phpRunner{}
// ListTests implements TestRunner.ListTests.
func (phpRunner) ListTests() ([]string, error) {
- testSlice, err := search(".", phpTestRegEx)
+ testSlice, err := Search(".", phpTestRegEx)
if err != nil {
return nil, err
}
diff --git a/test/runtimes/proctor/python.go b/test/runtimes/proctor/lib/python.go
index 7c598801b..429bfd850 100644
--- a/test/runtimes/proctor/python.go
+++ b/test/runtimes/proctor/lib/python.go
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package main
+package lib
import (
"fmt"
diff --git a/test/runtimes/proctor/main.go b/test/runtimes/proctor/main.go
new file mode 100644
index 000000000..e5607ac92
--- /dev/null
+++ b/test/runtimes/proctor/main.go
@@ -0,0 +1,85 @@
+// Copyright 2019 The gVisor Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Binary proctor runs the test for a particular runtime. It is meant to be
+// included in Docker images for all runtime tests.
+package main
+
+import (
+ "flag"
+ "fmt"
+ "log"
+ "os"
+ "strings"
+
+ "gvisor.dev/gvisor/test/runtimes/proctor/lib"
+)
+
+var (
+ runtime = flag.String("runtime", "", "name of runtime")
+ list = flag.Bool("list", false, "list all available tests")
+ testNames = flag.String("tests", "", "run a subset of the available tests")
+ pause = flag.Bool("pause", false, "cause container to pause indefinitely, reaping any zombie children")
+)
+
+func main() {
+ flag.Parse()
+
+ if *pause {
+ lib.PauseAndReap()
+ panic("pauseAndReap should never return")
+ }
+
+ if *runtime == "" {
+ log.Fatalf("runtime flag must be provided")
+ }
+
+ tr, err := lib.TestRunnerForRuntime(*runtime)
+ if err != nil {
+ log.Fatalf("%v", err)
+ }
+
+ // List tests.
+ if *list {
+ tests, err := tr.ListTests()
+ if err != nil {
+ log.Fatalf("failed to list tests: %v", err)
+ }
+ for _, test := range tests {
+ fmt.Println(test)
+ }
+ return
+ }
+
+ var tests []string
+ if *testNames == "" {
+ // Run every test.
+ tests, err = tr.ListTests()
+ if err != nil {
+ log.Fatalf("failed to get all tests: %v", err)
+ }
+ } else {
+ // Run subset of test.
+ tests = strings.Split(*testNames, ",")
+ }
+
+ // Run tests.
+ cmds := tr.TestCmds(tests)
+ for _, cmd := range cmds {
+ cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
+ if err := cmd.Run(); err != nil {
+ log.Fatalf("FAIL: %v", err)
+ }
+ }
+}
diff --git a/test/runtimes/runner/BUILD b/test/runtimes/runner/BUILD
index dc0d5d5b4..70cc01594 100644
--- a/test/runtimes/runner/BUILD
+++ b/test/runtimes/runner/BUILD
@@ -1,4 +1,4 @@
-load("//tools:defs.bzl", "go_binary", "go_test")
+load("//tools:defs.bzl", "go_binary")
package(licenses = ["notice"])
@@ -7,16 +7,5 @@ go_binary(
testonly = 1,
srcs = ["main.go"],
visibility = ["//test/runtimes:__pkg__"],
- deps = [
- "//pkg/log",
- "//pkg/test/dockerutil",
- "//pkg/test/testutil",
- ],
-)
-
-go_test(
- name = "exclude_test",
- size = "small",
- srcs = ["exclude_test.go"],
- library = ":runner",
+ deps = ["//test/runtimes/runner/lib"],
)
diff --git a/test/runtimes/runner/lib/BUILD b/test/runtimes/runner/lib/BUILD
new file mode 100644
index 000000000..d308f41b0
--- /dev/null
+++ b/test/runtimes/runner/lib/BUILD
@@ -0,0 +1,22 @@
+load("//tools:defs.bzl", "go_library", "go_test")
+
+package(licenses = ["notice"])
+
+go_library(
+ name = "lib",
+ testonly = 1,
+ srcs = ["lib.go"],
+ visibility = ["//test/runtimes/runner:__pkg__"],
+ deps = [
+ "//pkg/log",
+ "//pkg/test/dockerutil",
+ "//pkg/test/testutil",
+ ],
+)
+
+go_test(
+ name = "lib_test",
+ size = "small",
+ srcs = ["exclude_test.go"],
+ library = ":lib",
+)
diff --git a/test/runtimes/runner/exclude_test.go b/test/runtimes/runner/lib/exclude_test.go
index 67c2170c8..f996e895b 100644
--- a/test/runtimes/runner/exclude_test.go
+++ b/test/runtimes/runner/lib/exclude_test.go
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package main
+package lib
import (
"flag"
@@ -20,6 +20,8 @@ import (
"testing"
)
+var excludeFile = flag.String("exclude_file", "", "file to test (standard format)")
+
func TestMain(m *testing.M) {
flag.Parse()
os.Exit(m.Run())
@@ -27,7 +29,7 @@ func TestMain(m *testing.M) {
// Test that the exclude file parses without error.
func TestExcludelist(t *testing.T) {
- ex, err := getExcludes()
+ ex, err := getExcludes(*excludeFile)
if err != nil {
t.Fatalf("error parsing exclude file: %v", err)
}
diff --git a/test/runtimes/runner/lib/lib.go b/test/runtimes/runner/lib/lib.go
new file mode 100644
index 000000000..78285cb0e
--- /dev/null
+++ b/test/runtimes/runner/lib/lib.go
@@ -0,0 +1,185 @@
+// Copyright 2019 The gVisor Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package lib provides utilities for runner.
+package lib
+
+import (
+ "context"
+ "encoding/csv"
+ "fmt"
+ "io"
+ "os"
+ "sort"
+ "strings"
+ "testing"
+ "time"
+
+ "gvisor.dev/gvisor/pkg/log"
+ "gvisor.dev/gvisor/pkg/test/dockerutil"
+ "gvisor.dev/gvisor/pkg/test/testutil"
+)
+
+// RunTests is a helper that is called by main. It exists so that we can run
+// defered functions before exiting. It returns an exit code that should be
+// passed to os.Exit.
+func RunTests(lang, image, excludeFile string, batchSize int, timeout time.Duration) int {
+ // Get tests to exclude..
+ excludes, err := getExcludes(excludeFile)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error getting exclude list: %s\n", err.Error())
+ return 1
+ }
+
+ // Construct the shared docker instance.
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, testutil.DefaultLogger(lang))
+ defer d.CleanUp(ctx)
+
+ if err := testutil.TouchShardStatusFile(); err != nil {
+ fmt.Fprintf(os.Stderr, "error touching status shard file: %v\n", err)
+ return 1
+ }
+
+ // Get a slice of tests to run. This will also start a single Docker
+ // container that will be used to run each test. The final test will
+ // stop the Docker container.
+ tests, err := getTests(ctx, d, lang, image, batchSize, timeout, excludes)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "%s\n", err.Error())
+ return 1
+ }
+
+ m := testing.MainStart(testDeps{}, tests, nil, nil)
+ return m.Run()
+}
+
+// getTests executes all tests as table tests.
+func getTests(ctx context.Context, d *dockerutil.Container, lang, image string, batchSize int, timeout time.Duration, excludes map[string]struct{}) ([]testing.InternalTest, error) {
+ // Start the container.
+ opts := dockerutil.RunOpts{
+ Image: fmt.Sprintf("runtimes/%s", image),
+ }
+ d.CopyFiles(&opts, "/proctor", "test/runtimes/proctor/proctor")
+ if err := d.Spawn(ctx, opts, "/proctor/proctor", "--pause"); err != nil {
+ return nil, fmt.Errorf("docker run failed: %v", err)
+ }
+
+ // Get a list of all tests in the image.
+ list, err := d.Exec(ctx, dockerutil.ExecOpts{}, "/proctor/proctor", "--runtime", lang, "--list")
+ if err != nil {
+ return nil, fmt.Errorf("docker exec failed: %v", err)
+ }
+
+ // Calculate a subset of tests to run corresponding to the current
+ // shard.
+ tests := strings.Fields(list)
+ sort.Strings(tests)
+ indices, err := testutil.TestIndicesForShard(len(tests))
+ if err != nil {
+ return nil, fmt.Errorf("TestsForShard() failed: %v", err)
+ }
+
+ var itests []testing.InternalTest
+ for i := 0; i < len(indices); i += batchSize {
+ var tcs []string
+ end := i + batchSize
+ if end > len(indices) {
+ end = len(indices)
+ }
+ for _, tc := range indices[i:end] {
+ // Add test if not excluded.
+ if _, ok := excludes[tests[tc]]; ok {
+ log.Infof("Skipping test case %s\n", tests[tc])
+ continue
+ }
+ tcs = append(tcs, tests[tc])
+ }
+ itests = append(itests, testing.InternalTest{
+ Name: strings.Join(tcs, ", "),
+ F: func(t *testing.T) {
+ var (
+ now = time.Now()
+ done = make(chan struct{})
+ output string
+ err error
+ )
+
+ go func() {
+ fmt.Printf("RUNNING the following in a batch\n%s\n", strings.Join(tcs, "\n"))
+ output, err = d.Exec(ctx, dockerutil.ExecOpts{}, "/proctor/proctor", "--runtime", lang, "--tests", strings.Join(tcs, ","))
+ close(done)
+ }()
+
+ select {
+ case <-done:
+ if err == nil {
+ fmt.Printf("PASS: (%v)\n\n", time.Since(now))
+ return
+ }
+ t.Errorf("FAIL: (%v):\n%s\n", time.Since(now), output)
+ case <-time.After(timeout):
+ t.Errorf("TIMEOUT: (%v):\n%s\n", time.Since(now), output)
+ }
+ },
+ })
+ }
+
+ return itests, nil
+}
+
+// getBlacklist reads the exclude file and returns a set of test names to
+// exclude.
+func getExcludes(excludeFile string) (map[string]struct{}, error) {
+ excludes := make(map[string]struct{})
+ if excludeFile == "" {
+ return excludes, nil
+ }
+ f, err := os.Open(excludeFile)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+
+ r := csv.NewReader(f)
+
+ // First line is header. Skip it.
+ if _, err := r.Read(); err != nil {
+ return nil, err
+ }
+
+ for {
+ record, err := r.Read()
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ return nil, err
+ }
+ excludes[record[0]] = struct{}{}
+ }
+ return excludes, nil
+}
+
+// testDeps implements testing.testDeps (an unexported interface), and is
+// required to use testing.MainStart.
+type testDeps struct{}
+
+func (f testDeps) MatchString(a, b string) (bool, error) { return a == b, nil }
+func (f testDeps) StartCPUProfile(io.Writer) error { return nil }
+func (f testDeps) StopCPUProfile() {}
+func (f testDeps) WriteProfileTo(string, io.Writer, int) error { return nil }
+func (f testDeps) ImportPath() string { return "" }
+func (f testDeps) StartTestLog(io.Writer) {}
+func (f testDeps) StopTestLog() error { return nil }
diff --git a/test/runtimes/runner/main.go b/test/runtimes/runner/main.go
index 948e7cf9c..ec79a22c2 100644
--- a/test/runtimes/runner/main.go
+++ b/test/runtimes/runner/main.go
@@ -16,20 +16,12 @@
package main
import (
- "context"
- "encoding/csv"
"flag"
"fmt"
- "io"
"os"
- "sort"
- "strings"
- "testing"
"time"
- "gvisor.dev/gvisor/pkg/log"
- "gvisor.dev/gvisor/pkg/test/dockerutil"
- "gvisor.dev/gvisor/pkg/test/testutil"
+ "gvisor.dev/gvisor/test/runtimes/runner/lib"
)
var (
@@ -37,169 +29,14 @@ var (
image = flag.String("image", "", "docker image with runtime tests")
excludeFile = flag.String("exclude_file", "", "file containing list of tests to exclude, in CSV format with fields: test name, bug id, comment")
batchSize = flag.Int("batch", 50, "number of test cases run in one command")
+ timeout = flag.Duration("timeout", 90*time.Minute, "batch timeout")
)
-// Wait time for each test to run.
-const timeout = 90 * time.Minute
-
func main() {
flag.Parse()
if *lang == "" || *image == "" {
fmt.Fprintf(os.Stderr, "lang and image flags must not be empty\n")
os.Exit(1)
}
- os.Exit(runTests())
-}
-
-// runTests is a helper that is called by main. It exists so that we can run
-// defered functions before exiting. It returns an exit code that should be
-// passed to os.Exit.
-func runTests() int {
- // Get tests to exclude..
- excludes, err := getExcludes()
- if err != nil {
- fmt.Fprintf(os.Stderr, "Error getting exclude list: %s\n", err.Error())
- return 1
- }
-
- // Construct the shared docker instance.
- ctx := context.Background()
- d := dockerutil.MakeContainer(ctx, testutil.DefaultLogger(*lang))
- defer d.CleanUp(ctx)
-
- if err := testutil.TouchShardStatusFile(); err != nil {
- fmt.Fprintf(os.Stderr, "error touching status shard file: %v\n", err)
- return 1
- }
-
- // Get a slice of tests to run. This will also start a single Docker
- // container that will be used to run each test. The final test will
- // stop the Docker container.
- tests, err := getTests(ctx, d, excludes)
- if err != nil {
- fmt.Fprintf(os.Stderr, "%s\n", err.Error())
- return 1
- }
-
- m := testing.MainStart(testDeps{}, tests, nil, nil)
- return m.Run()
-}
-
-// getTests executes all tests as table tests.
-func getTests(ctx context.Context, d *dockerutil.Container, excludes map[string]struct{}) ([]testing.InternalTest, error) {
- // Start the container.
- opts := dockerutil.RunOpts{
- Image: fmt.Sprintf("runtimes/%s", *image),
- }
- d.CopyFiles(&opts, "/proctor", "test/runtimes/proctor/proctor")
- if err := d.Spawn(ctx, opts, "/proctor/proctor", "--pause"); err != nil {
- return nil, fmt.Errorf("docker run failed: %v", err)
- }
-
- // Get a list of all tests in the image.
- list, err := d.Exec(ctx, dockerutil.ExecOpts{}, "/proctor/proctor", "--runtime", *lang, "--list")
- if err != nil {
- return nil, fmt.Errorf("docker exec failed: %v", err)
- }
-
- // Calculate a subset of tests to run corresponding to the current
- // shard.
- tests := strings.Fields(list)
- sort.Strings(tests)
- indices, err := testutil.TestIndicesForShard(len(tests))
- if err != nil {
- return nil, fmt.Errorf("TestsForShard() failed: %v", err)
- }
-
- var itests []testing.InternalTest
- for i := 0; i < len(indices); i += *batchSize {
- var tcs []string
- end := i + *batchSize
- if end > len(indices) {
- end = len(indices)
- }
- for _, tc := range indices[i:end] {
- // Add test if not excluded.
- if _, ok := excludes[tests[tc]]; ok {
- log.Infof("Skipping test case %s\n", tests[tc])
- continue
- }
- tcs = append(tcs, tests[tc])
- }
- itests = append(itests, testing.InternalTest{
- Name: strings.Join(tcs, ", "),
- F: func(t *testing.T) {
- var (
- now = time.Now()
- done = make(chan struct{})
- output string
- err error
- )
-
- go func() {
- fmt.Printf("RUNNING the following in a batch\n%s\n", strings.Join(tcs, "\n"))
- output, err = d.Exec(ctx, dockerutil.ExecOpts{}, "/proctor/proctor", "--runtime", *lang, "--tests", strings.Join(tcs, ","))
- close(done)
- }()
-
- select {
- case <-done:
- if err == nil {
- fmt.Printf("PASS: (%v)\n\n", time.Since(now))
- return
- }
- t.Errorf("FAIL: (%v):\n%s\n", time.Since(now), output)
- case <-time.After(timeout):
- t.Errorf("TIMEOUT: (%v):\n%s\n", time.Since(now), output)
- }
- },
- })
- }
-
- return itests, nil
+ os.Exit(lib.RunTests(*lang, *image, *excludeFile, *batchSize, *timeout))
}
-
-// getBlacklist reads the exclude file and returns a set of test names to
-// exclude.
-func getExcludes() (map[string]struct{}, error) {
- excludes := make(map[string]struct{})
- if *excludeFile == "" {
- return excludes, nil
- }
- f, err := os.Open(*excludeFile)
- if err != nil {
- return nil, err
- }
- defer f.Close()
-
- r := csv.NewReader(f)
-
- // First line is header. Skip it.
- if _, err := r.Read(); err != nil {
- return nil, err
- }
-
- for {
- record, err := r.Read()
- if err == io.EOF {
- break
- }
- if err != nil {
- return nil, err
- }
- excludes[record[0]] = struct{}{}
- }
- return excludes, nil
-}
-
-// testDeps implements testing.testDeps (an unexported interface), and is
-// required to use testing.MainStart.
-type testDeps struct{}
-
-func (f testDeps) MatchString(a, b string) (bool, error) { return a == b, nil }
-func (f testDeps) StartCPUProfile(io.Writer) error { return nil }
-func (f testDeps) StopCPUProfile() {}
-func (f testDeps) WriteProfileTo(string, io.Writer, int) error { return nil }
-func (f testDeps) ImportPath() string { return "" }
-func (f testDeps) StartTestLog(io.Writer) {}
-func (f testDeps) StopTestLog() error { return nil }
diff --git a/test/syscalls/BUILD b/test/syscalls/BUILD
index f949bc0e3..96a775456 100644
--- a/test/syscalls/BUILD
+++ b/test/syscalls/BUILD
@@ -238,7 +238,7 @@ syscall_test(
syscall_test(
size = "medium",
- add_overlay = False, # TODO(gvisor.dev/issue/317): enable when fixed.
+ add_overlay = True,
test = "//test/syscalls/linux:inotify_test",
)
diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD
index c775a6d75..d9dbe2267 100644
--- a/test/syscalls/linux/BUILD
+++ b/test/syscalls/linux/BUILD
@@ -1667,6 +1667,7 @@ cc_binary(
"//test/util:cleanup",
"//test/util:file_descriptor",
"//test/util:fs_util",
+ "@com_google_absl//absl/container:node_hash_set",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/synchronization",
"@com_google_absl//absl/time",
diff --git a/test/syscalls/linux/inotify.cc b/test/syscalls/linux/inotify.cc
index a5c421118..e4392a450 100644
--- a/test/syscalls/linux/inotify.cc
+++ b/test/syscalls/linux/inotify.cc
@@ -465,7 +465,9 @@ TEST(Inotify, ConcurrentFileDeletionAndWatchRemoval) {
for (int i = 0; i < 100; ++i) {
FileDescriptor file_fd =
ASSERT_NO_ERRNO_AND_VALUE(Open(filename, O_CREAT, S_IRUSR | S_IWUSR));
- file_fd.reset(); // Close before unlinking (although save is disabled).
+ // Close before unlinking (although S/R is disabled). Some filesystems
+ // cannot restore an open fd on an unlinked file.
+ file_fd.reset();
EXPECT_THAT(unlink(filename.c_str()), SyscallSucceeds());
}
};
@@ -1256,10 +1258,7 @@ TEST(Inotify, MknodGeneratesCreateEvent) {
InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
const TempPath file1(root.path() + "/file1");
- const int rc = mknod(file1.path().c_str(), S_IFREG, 0);
- // mknod(2) is only supported on tmpfs in the sandbox.
- SKIP_IF(IsRunningOnGvisor() && rc != 0);
- ASSERT_THAT(rc, SyscallSucceeds());
+ ASSERT_THAT(mknod(file1.path().c_str(), S_IFREG, 0), SyscallSucceeds());
const std::vector<Event> events =
ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
@@ -1289,6 +1288,10 @@ TEST(Inotify, SymlinkGeneratesCreateEvent) {
}
TEST(Inotify, LinkGeneratesAttribAndCreateEvents) {
+ // Inotify does not work properly with hard links in gofer and overlay fs.
+ SKIP_IF(IsRunningOnGvisor() &&
+ !ASSERT_NO_ERRNO_AND_VALUE(IsTmpfs(GetAbsoluteTestTmpdir())));
+
const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
const TempPath file1 =
ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
@@ -1301,11 +1304,8 @@ TEST(Inotify, LinkGeneratesAttribAndCreateEvents) {
const int file1_wd = ASSERT_NO_ERRNO_AND_VALUE(
InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS));
- const int rc = link(file1.path().c_str(), link1.path().c_str());
- // NOTE(b/34861058): link(2) is only supported on tmpfs in the sandbox.
- SKIP_IF(IsRunningOnGvisor() && rc != 0 &&
- (errno == EPERM || errno == ENOENT));
- ASSERT_THAT(rc, SyscallSucceeds());
+ ASSERT_THAT(link(file1.path().c_str(), link1.path().c_str()),
+ SyscallSucceeds());
const std::vector<Event> events =
ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
@@ -1334,68 +1334,70 @@ TEST(Inotify, UtimesGeneratesAttribEvent) {
}
TEST(Inotify, HardlinksReuseSameWatch) {
+ // Inotify does not work properly with hard links in gofer and overlay fs.
+ SKIP_IF(IsRunningOnGvisor() &&
+ !ASSERT_NO_ERRNO_AND_VALUE(IsTmpfs(GetAbsoluteTestTmpdir())));
+
const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- TempPath file1 =
+ TempPath file =
ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
- TempPath link1(root.path() + "/link1");
- const int rc = link(file1.path().c_str(), link1.path().c_str());
- // link(2) is only supported on tmpfs in the sandbox.
- SKIP_IF(IsRunningOnGvisor() && rc != 0 &&
- (errno == EPERM || errno == ENOENT));
- ASSERT_THAT(rc, SyscallSucceeds());
+
+ TempPath file2(root.path() + "/file2");
+ ASSERT_THAT(link(file.path().c_str(), file2.path().c_str()),
+ SyscallSucceeds());
const FileDescriptor fd =
ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
const int root_wd = ASSERT_NO_ERRNO_AND_VALUE(
InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
- const int file1_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS));
- const int link1_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), link1.path(), IN_ALL_EVENTS));
+ const int file_wd = ASSERT_NO_ERRNO_AND_VALUE(
+ InotifyAddWatch(fd.get(), file.path(), IN_ALL_EVENTS));
+ const int file2_wd = ASSERT_NO_ERRNO_AND_VALUE(
+ InotifyAddWatch(fd.get(), file2.path(), IN_ALL_EVENTS));
// The watch descriptors for watches on different links to the same file
// should be identical.
- EXPECT_NE(root_wd, file1_wd);
- EXPECT_EQ(file1_wd, link1_wd);
+ EXPECT_NE(root_wd, file_wd);
+ EXPECT_EQ(file_wd, file2_wd);
- FileDescriptor file1_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_WRONLY));
+ FileDescriptor file_fd =
+ ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_WRONLY));
std::vector<Event> events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
ASSERT_THAT(events,
- AreUnordered({Event(IN_OPEN, root_wd, Basename(file1.path())),
- Event(IN_OPEN, file1_wd)}));
+ AreUnordered({Event(IN_OPEN, root_wd, Basename(file.path())),
+ Event(IN_OPEN, file_wd)}));
// For the next step, we want to ensure all fds to the file are closed. Do
// that now and drain the resulting events.
- file1_fd.reset();
+ file_fd.reset();
events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
ASSERT_THAT(
events,
- AreUnordered({Event(IN_CLOSE_WRITE, root_wd, Basename(file1.path())),
- Event(IN_CLOSE_WRITE, file1_wd)}));
+ AreUnordered({Event(IN_CLOSE_WRITE, root_wd, Basename(file.path())),
+ Event(IN_CLOSE_WRITE, file_wd)}));
// Try removing the link and let's see what events show up. Note that after
// this, we still have a link to the file so the watch shouldn't be
// automatically removed.
- const std::string link1_path = link1.reset();
+ const std::string file2_path = file2.reset();
events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
ASSERT_THAT(events,
- AreUnordered({Event(IN_ATTRIB, link1_wd),
- Event(IN_DELETE, root_wd, Basename(link1_path))}));
+ AreUnordered({Event(IN_ATTRIB, file2_wd),
+ Event(IN_DELETE, root_wd, Basename(file2_path))}));
// Now remove the other link. Since this is the last link to the file, the
// watch should be automatically removed.
- const std::string file1_path = file1.reset();
+ const std::string file_path = file.reset();
events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
ASSERT_THAT(
events,
- AreUnordered({Event(IN_ATTRIB, file1_wd), Event(IN_DELETE_SELF, file1_wd),
- Event(IN_IGNORED, file1_wd),
- Event(IN_DELETE, root_wd, Basename(file1_path))}));
+ AreUnordered({Event(IN_ATTRIB, file_wd), Event(IN_DELETE_SELF, file_wd),
+ Event(IN_IGNORED, file_wd),
+ Event(IN_DELETE, root_wd, Basename(file_path))}));
}
// Calling mkdir within "parent/child" should generate an event for child, but
@@ -1806,17 +1808,17 @@ TEST(Inotify, SpliceOnInotifyFD) {
// Watches on a parent should not be triggered by actions on a hard link to one
// of its children that has a different parent.
TEST(Inotify, LinkOnOtherParent) {
+ // Inotify does not work properly with hard links in gofer and overlay fs.
+ SKIP_IF(IsRunningOnGvisor() &&
+ !ASSERT_NO_ERRNO_AND_VALUE(IsTmpfs(GetAbsoluteTestTmpdir())));
+
const TempPath dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
const TempPath dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
const TempPath file =
ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir1.path()));
std::string link_path = NewTempAbsPathInDir(dir2.path());
- const int rc = link(file.path().c_str(), link_path.c_str());
- // NOTE(b/34861058): link(2) is only supported on tmpfs in the sandbox.
- SKIP_IF(IsRunningOnGvisor() && rc != 0 &&
- (errno == EPERM || errno == ENOENT));
- ASSERT_THAT(rc, SyscallSucceeds());
+ ASSERT_THAT(link(file.path().c_str(), link_path.c_str()), SyscallSucceeds());
const FileDescriptor inotify_fd =
ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
@@ -1825,13 +1827,18 @@ TEST(Inotify, LinkOnOtherParent) {
// Perform various actions on the link outside of dir1, which should trigger
// no inotify events.
- const FileDescriptor fd =
+ FileDescriptor fd =
ASSERT_NO_ERRNO_AND_VALUE(Open(link_path.c_str(), O_RDWR));
int val = 0;
ASSERT_THAT(write(fd.get(), &val, sizeof(val)), SyscallSucceeds());
ASSERT_THAT(read(fd.get(), &val, sizeof(val)), SyscallSucceeds());
ASSERT_THAT(ftruncate(fd.get(), 12345), SyscallSucceeds());
+
+ // Close before unlinking; some filesystems cannot restore an open fd on an
+ // unlinked file.
+ fd.reset();
ASSERT_THAT(unlink(link_path.c_str()), SyscallSucceeds());
+
const std::vector<Event> events =
ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
EXPECT_THAT(events, Are({}));
@@ -2055,21 +2062,21 @@ TEST(Inotify, ExcludeUnlinkDirectory_NoRandomSave) {
// We need to disable S/R because there are filesystems where we cannot re-open
// fds to an unlinked file across S/R, e.g. gofer-backed filesytems.
TEST(Inotify, ExcludeUnlinkMultipleChildren_NoRandomSave) {
- const DisableSave ds;
+ // Inotify does not work properly with hard links in gofer and overlay fs.
+ SKIP_IF(IsRunningOnGvisor() &&
+ !ASSERT_NO_ERRNO_AND_VALUE(IsTmpfs(GetAbsoluteTestTmpdir())));
// TODO(gvisor.dev/issue/1624): This test fails on VFS1.
SKIP_IF(IsRunningWithVFS1());
+ const DisableSave ds;
+
const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
const TempPath file =
ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path()));
std::string path1 = file.path();
std::string path2 = NewTempAbsPathInDir(dir.path());
+ ASSERT_THAT(link(path1.c_str(), path2.c_str()), SyscallSucceeds());
- const int rc = link(path1.c_str(), path2.c_str());
- // NOTE(b/34861058): link(2) is only supported on tmpfs in the sandbox.
- SKIP_IF(IsRunningOnGvisor() && rc != 0 &&
- (errno == EPERM || errno == ENOENT));
- ASSERT_THAT(rc, SyscallSucceeds());
const FileDescriptor fd1 =
ASSERT_NO_ERRNO_AND_VALUE(Open(path1.c_str(), O_RDWR));
const FileDescriptor fd2 =
@@ -2101,6 +2108,15 @@ TEST(Inotify, ExcludeUnlinkMultipleChildren_NoRandomSave) {
// We need to disable S/R because there are filesystems where we cannot re-open
// fds to an unlinked file across S/R, e.g. gofer-backed filesytems.
TEST(Inotify, ExcludeUnlinkInodeEvents_NoRandomSave) {
+ // TODO(gvisor.dev/issue/1624): Fails on VFS1.
+ SKIP_IF(IsRunningWithVFS1());
+
+ // NOTE(gvisor.dev/issue/3654): In the gofer filesystem, we do not allow
+ // setting attributes through an fd if the file at the open path has been
+ // deleted.
+ SKIP_IF(IsRunningOnGvisor() &&
+ !ASSERT_NO_ERRNO_AND_VALUE(IsTmpfs(GetAbsoluteTestTmpdir())));
+
const DisableSave ds;
const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
@@ -2110,18 +2126,6 @@ TEST(Inotify, ExcludeUnlinkInodeEvents_NoRandomSave) {
const FileDescriptor fd =
ASSERT_NO_ERRNO_AND_VALUE(Open(file.path().c_str(), O_RDWR));
- // NOTE(b/157163751): Create another link before unlinking. This is needed for
- // the gofer filesystem in gVisor, where open fds will not work once the link
- // count hits zero. In VFS2, we end up skipping the gofer test anyway, because
- // hard links are not supported for gofer fs.
- if (IsRunningOnGvisor()) {
- std::string link_path = NewTempAbsPath();
- const int rc = link(file.path().c_str(), link_path.c_str());
- // NOTE(b/34861058): link(2) is only supported on tmpfs in the sandbox.
- SKIP_IF(rc != 0 && (errno == EPERM || errno == ENOENT));
- ASSERT_THAT(rc, SyscallSucceeds());
- }
-
const FileDescriptor inotify_fd =
ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
const int dir_wd = ASSERT_NO_ERRNO_AND_VALUE(InotifyAddWatch(
diff --git a/test/syscalls/linux/ip6tables.cc b/test/syscalls/linux/ip6tables.cc
index 97297ee2b..de0a1c114 100644
--- a/test/syscalls/linux/ip6tables.cc
+++ b/test/syscalls/linux/ip6tables.cc
@@ -82,6 +82,32 @@ TEST(IP6TablesBasic, GetEntriesErrorPrecedence) {
SyscallFailsWithErrno(EINVAL));
}
+TEST(IP6TablesBasic, GetRevision) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ int sock;
+ ASSERT_THAT(sock = socket(AF_INET6, SOCK_RAW, IPPROTO_RAW),
+ SyscallSucceeds());
+
+ struct xt_get_revision rev = {
+ .name = "REDIRECT",
+ .revision = 0,
+ };
+ socklen_t rev_len = sizeof(rev);
+
+ // Revision 0 exists.
+ EXPECT_THAT(
+ getsockopt(sock, SOL_IPV6, IP6T_SO_GET_REVISION_TARGET, &rev, &rev_len),
+ SyscallSucceeds());
+ EXPECT_EQ(rev.revision, 0);
+
+ // Revisions > 0 don't exist.
+ rev.revision = 1;
+ EXPECT_THAT(
+ getsockopt(sock, SOL_IPV6, IP6T_SO_GET_REVISION_TARGET, &rev, &rev_len),
+ SyscallFailsWithErrno(EPROTONOSUPPORT));
+}
+
// This tests the initial state of a machine with empty ip6tables via
// getsockopt(IP6T_SO_GET_INFO). We don't have a guarantee that the iptables are
// empty when running in native, but we can test that gVisor has the same
diff --git a/test/syscalls/linux/iptables.cc b/test/syscalls/linux/iptables.cc
index 83b6a164a..7ee10bbde 100644
--- a/test/syscalls/linux/iptables.cc
+++ b/test/syscalls/linux/iptables.cc
@@ -117,6 +117,32 @@ TEST(IPTablesBasic, OriginalDstErrors) {
SyscallFailsWithErrno(ENOTCONN));
}
+TEST(IPTablesBasic, GetRevision) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ int sock;
+ ASSERT_THAT(sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP),
+ SyscallSucceeds());
+
+ struct xt_get_revision rev = {
+ .name = "REDIRECT",
+ .revision = 0,
+ };
+ socklen_t rev_len = sizeof(rev);
+
+ // Revision 0 exists.
+ EXPECT_THAT(
+ getsockopt(sock, SOL_IP, IPT_SO_GET_REVISION_TARGET, &rev, &rev_len),
+ SyscallSucceeds());
+ EXPECT_EQ(rev.revision, 0);
+
+ // Revisions > 0 don't exist.
+ rev.revision = 1;
+ EXPECT_THAT(
+ getsockopt(sock, SOL_IP, IPT_SO_GET_REVISION_TARGET, &rev, &rev_len),
+ SyscallFailsWithErrno(EPROTONOSUPPORT));
+}
+
// Fixture for iptables tests.
class IPTablesTest : public ::testing::Test {
protected:
diff --git a/test/syscalls/linux/kcov.cc b/test/syscalls/linux/kcov.cc
index f3c30444e..6afcb4e75 100644
--- a/test/syscalls/linux/kcov.cc
+++ b/test/syscalls/linux/kcov.cc
@@ -36,12 +36,13 @@ TEST(KcovTest, Kcov) {
constexpr int kSize = 4096;
constexpr int KCOV_INIT_TRACE = 0x80086301;
constexpr int KCOV_ENABLE = 0x6364;
+ constexpr int KCOV_DISABLE = 0x6365;
int fd;
ASSERT_THAT(fd = open("/sys/kernel/debug/kcov", O_RDWR),
AnyOf(SyscallSucceeds(), SyscallFailsWithErrno(ENOENT)));
- // Kcov not enabled.
+ // Kcov not available.
SKIP_IF(errno == ENOENT);
ASSERT_THAT(ioctl(fd, KCOV_INIT_TRACE, kSize), SyscallSucceeds());
@@ -62,6 +63,8 @@ TEST(KcovTest, Kcov) {
// Verify that PCs are in the standard kernel range.
EXPECT_GT(area[i], 0xffffffff7fffffffL);
}
+
+ ASSERT_THAT(ioctl(fd, KCOV_DISABLE, 0), SyscallSucceeds());
}
} // namespace
diff --git a/test/syscalls/linux/mknod.cc b/test/syscalls/linux/mknod.cc
index 89e4564e8..ae65d366b 100644
--- a/test/syscalls/linux/mknod.cc
+++ b/test/syscalls/linux/mknod.cc
@@ -105,11 +105,13 @@ TEST(MknodTest, UnimplementedTypesReturnError) {
}
TEST(MknodTest, Socket) {
+ SKIP_IF(IsRunningOnGvisor() && IsRunningWithVFS1());
+
ASSERT_THAT(chdir(GetAbsoluteTestTmpdir().c_str()), SyscallSucceeds());
- SKIP_IF(IsRunningOnGvisor() && IsRunningWithVFS1());
+ auto filename = NewTempRelPath();
- ASSERT_THAT(mknod("./file0", S_IFSOCK | S_IRUSR | S_IWUSR, 0),
+ ASSERT_THAT(mknod(filename.c_str(), S_IFSOCK | S_IRUSR | S_IWUSR, 0),
SyscallSucceeds());
int sk;
@@ -117,9 +119,10 @@ TEST(MknodTest, Socket) {
FileDescriptor fd(sk);
struct sockaddr_un addr = {.sun_family = AF_UNIX};
- absl::SNPrintF(addr.sun_path, sizeof(addr.sun_path), "./file0");
+ absl::SNPrintF(addr.sun_path, sizeof(addr.sun_path), "%s", filename.c_str());
ASSERT_THAT(connect(sk, (struct sockaddr *)&addr, sizeof(addr)),
SyscallFailsWithErrno(ECONNREFUSED));
+ ASSERT_THAT(unlink(filename.c_str()), SyscallSucceeds());
}
TEST(MknodTest, Fifo) {
diff --git a/test/syscalls/linux/proc.cc b/test/syscalls/linux/proc.cc
index c1488b06b..e8fcc4439 100644
--- a/test/syscalls/linux/proc.cc
+++ b/test/syscalls/linux/proc.cc
@@ -47,6 +47,7 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
+#include "absl/container/node_hash_set.h"
#include "absl/strings/ascii.h"
#include "absl/strings/match.h"
#include "absl/strings/numbers.h"
@@ -721,8 +722,8 @@ static void CheckFdDirGetdentsDuplicates(const std::string& path) {
EXPECT_GE(newfd, 1024);
auto fd_closer = Cleanup([newfd]() { close(newfd); });
auto fd_files = ASSERT_NO_ERRNO_AND_VALUE(ListDir(path.c_str(), false));
- std::unordered_set<std::string> fd_files_dedup(fd_files.begin(),
- fd_files.end());
+ absl::node_hash_set<std::string> fd_files_dedup(fd_files.begin(),
+ fd_files.end());
EXPECT_EQ(fd_files.size(), fd_files_dedup.size());
}
@@ -779,8 +780,12 @@ TEST(ProcSelfFdInfo, Flags) {
}
TEST(ProcSelfExe, Absolute) {
- auto exe = ASSERT_NO_ERRNO_AND_VALUE(
- ReadLink(absl::StrCat("/proc/", getpid(), "/exe")));
+ auto exe = ASSERT_NO_ERRNO_AND_VALUE(ReadLink("/proc/self/exe"));
+ EXPECT_EQ(exe[0], '/');
+}
+
+TEST(ProcSelfCwd, Absolute) {
+ auto exe = ASSERT_NO_ERRNO_AND_VALUE(ReadLink("/proc/self/cwd"));
EXPECT_EQ(exe[0], '/');
}
@@ -1472,6 +1477,16 @@ TEST(ProcPidExe, Subprocess) {
EXPECT_EQ(actual, expected_absolute_path);
}
+// /proc/PID/cwd points to the correct directory.
+TEST(ProcPidCwd, Subprocess) {
+ auto want = ASSERT_NO_ERRNO_AND_VALUE(GetCWD());
+
+ char got[PATH_MAX + 1] = {};
+ ASSERT_THAT(ReadlinkWhileRunning("cwd", got, sizeof(got)),
+ SyscallSucceedsWithValue(Gt(0)));
+ EXPECT_EQ(got, want);
+}
+
// Test whether /proc/PID/ files can be read for a running process.
TEST(ProcPidFile, SubprocessRunning) {
char buf[1];
diff --git a/test/syscalls/linux/tcp_socket.cc b/test/syscalls/linux/tcp_socket.cc
index ab731db1d..e0981e28a 100644
--- a/test/syscalls/linux/tcp_socket.cc
+++ b/test/syscalls/linux/tcp_socket.cc
@@ -1643,6 +1643,36 @@ TEST_P(SimpleTcpSocketTest, GetSocketDetachFilter) {
SyscallFailsWithErrno(ENOPROTOOPT));
}
+TEST_P(SimpleTcpSocketTest, CloseNonConnectedLingerOption) {
+ FileDescriptor s =
+ ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP));
+
+ constexpr int kLingerTimeout = 10; // Seconds.
+
+ // Set the SO_LINGER option.
+ struct linger sl = {
+ .l_onoff = 1,
+ .l_linger = kLingerTimeout,
+ };
+ ASSERT_THAT(setsockopt(s.get(), SOL_SOCKET, SO_LINGER, &sl, sizeof(sl)),
+ SyscallSucceeds());
+
+ struct pollfd poll_fd = {
+ .fd = s.get(),
+ .events = POLLHUP,
+ };
+ constexpr int kPollTimeoutMs = 0;
+ ASSERT_THAT(RetryEINTR(poll)(&poll_fd, 1, kPollTimeoutMs),
+ SyscallSucceedsWithValue(1));
+
+ auto const start_time = absl::Now();
+ EXPECT_THAT(close(s.release()), SyscallSucceeds());
+ auto const end_time = absl::Now();
+
+ // Close() should not linger and return immediately.
+ ASSERT_LT((end_time - start_time), absl::Seconds(kLingerTimeout));
+}
+
INSTANTIATE_TEST_SUITE_P(AllInetTests, SimpleTcpSocketTest,
::testing::Values(AF_INET, AF_INET6));