Skip to content

Commit 92898f1

Browse files
authored
integration test cleanup (#206)
Cleanup and simplify integration tests and test framework. - collect full stdout/sterr output into a buffer to allow matching on complete output. This simplifies the output matchers (in particular, `NoPanic`) and allows to write out much more helpful error messages. Unfortunately, the os/exec package has some quirks when capturing stdout/stderr of grand-child processes, requiring some workarounds. This is described in the new helper function `InputPipeScript`, used (only) in the sensorapp integration test. - add `DefaultIAPairs` function which returns shortened list of test pairs with reasonable subset of relevant cases. - simplify table-driven tests with single case entry - netcat: completely overhaul integration test setup. Needs new -q flag. - bwtester: provide duration in test parameters to shorten run - camerapp: change regex to allow 'T' (timeout with re-try) in output - change `appnet.Listen` to ensure "ReadySignal" is only printed when Listen is successful - remove various quirks with Chdir around that are not/no longer needed - call `integration.Init` from `TestMain` in integration tests instead of during the test functions. Previously, flags were not be parsed correctly and repeated invocation with `go test --count` would panic. - Makefile: run `go test` with `--count=1` to ensure integration tests are re-run when test files are unchanged.
1 parent d4a3805 commit 92898f1

File tree

11 files changed

+519
-785
lines changed

11 files changed

+519
-785
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ install: all
5050
cp -t $(DESTDIR) $(BIN)/scion-*
5151

5252
integration: build
53-
go test -v -tags=integration,$(TAGS) ./... ./_examples/helloworld/
53+
go test -tags=integration,$(TAGS) --count=1 ./... ./_examples/helloworld/
5454

5555
.PHONY: scion-bat
5656
scion-bat:

_examples/helloworld/helloworld_integration_test.go

Lines changed: 19 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -17,67 +17,38 @@
1717
package main
1818

1919
import (
20-
"strings"
2120
"testing"
2221

2322
"github.com/netsec-ethz/scion-apps/pkg/integration"
2423
)
2524

2625
const (
27-
name = "helloworld"
28-
bin = "example-helloworld"
26+
bin = "example-helloworld"
2927
)
3028

29+
func TestMain(m *testing.M) {
30+
integration.TestMain(m)
31+
}
32+
3133
func TestHelloworldSample(t *testing.T) {
32-
if err := integration.Init(name); err != nil {
33-
t.Fatalf("Failed to init: %s\n", err)
34-
}
3534
cmd := integration.AppBinPath(bin)
36-
// Common arguments
37-
cmnArgs := []string{}
3835
// Server
3936
serverPort := "12345"
4037
serverArgs := []string{"-port", serverPort}
41-
serverArgs = append(serverArgs, cmnArgs...)
42-
43-
testCases := []struct {
44-
Name string
45-
Args []string
46-
ServerOutMatchFun func(bool, string) bool
47-
ServerErrMatchFun func(bool, string) bool
48-
ClientOutMatchFun func(bool, string) bool
49-
ClientErrMatchFun func(bool, string) bool
50-
}{
51-
{
52-
"client_hello",
53-
append([]string{"-remote", integration.DstAddrPattern + ":" + serverPort}, cmnArgs...),
54-
func(prev bool, line string) bool {
55-
res := strings.Contains(line, "hello world")
56-
return prev || res // return true if any output line contains the string
57-
},
58-
nil,
59-
integration.Contains("Done. Wrote 11 bytes."),
60-
nil,
61-
},
62-
}
6338

64-
for _, tc := range testCases {
65-
in := integration.NewAppsIntegration(name, tc.Name, cmd, cmd, tc.Args, serverArgs, true)
66-
in.ServerStdout(tc.ServerOutMatchFun)
67-
in.ServerStderr(tc.ServerErrMatchFun)
68-
in.ClientStdout(tc.ClientOutMatchFun)
69-
in.ClientStderr(tc.ClientErrMatchFun)
70-
// Host address pattern
71-
hostAddr := integration.HostAddr
72-
// Cartesian product of src and dst IAs, is a random permutation
73-
// can be restricted to a subset to reduce the number of tests to run without significant
74-
// loss of coverage
75-
IAPairs := integration.IAPairs(hostAddr)
76-
IAPairs = IAPairs[:len(IAPairs)/2]
77-
// Run the tests to completion or until a test fails,
78-
// increase the client timeout if clients need more time to start
79-
if err := integration.RunTests(in, IAPairs, integration.DefaultClientTimeout, 0); err != nil {
80-
t.Fatalf("Error during tests err: %v", err)
81-
}
39+
// Client
40+
clientArgs := []string{"-remote", integration.DstAddrPattern + ":" + serverPort}
41+
42+
in := integration.NewAppsIntegration(cmd, cmd, clientArgs, serverArgs)
43+
in.ServerOutMatch = integration.Contains("hello world")
44+
in.ClientOutMatch = integration.Contains("Done. Wrote 11 bytes.")
45+
// Cartesian product of src and dst IAs, a random permutation
46+
// restricted to a subset to reduce the number of tests to run without significant
47+
// loss of coverage
48+
iaPairs := integration.DefaultIAPairs()
49+
// Run the tests to completion or until a test fails,
50+
// increase the ClientTimeout if clients need more time to start
51+
if err := in.Run(t, iaPairs); err != nil {
52+
t.Error(err)
8253
}
8354
}

bwtester/bwtestclient/bwtestclient_integration_test.go

Lines changed: 16 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -17,66 +17,36 @@
1717
package main
1818

1919
import (
20-
"github.com/netsec-ethz/scion-apps/pkg/integration"
21-
"strings"
2220
"testing"
21+
22+
"github.com/netsec-ethz/scion-apps/pkg/integration"
2323
)
2424

2525
const (
26-
name = "bwtester"
2726
clientBin = "scion-bwtestclient"
2827
serverBin = "scion-bwtestserver"
2928
)
3029

30+
func TestMain(m *testing.M) {
31+
integration.TestMain(m)
32+
}
33+
3134
func TestIntegrationBwtestclient(t *testing.T) {
32-
if err := integration.Init(name); err != nil {
33-
t.Fatalf("Failed to init: %s\n", err)
34-
}
35-
clientCmd := integration.AppBinPath(clientBin)
36-
serverCmd := integration.AppBinPath(serverBin)
35+
clientCmd := integration.AppBinPath(clientBin)
36+
serverCmd := integration.AppBinPath(serverBin)
3737

38-
// Common arguments
39-
cmnArgs := []string{}
4038
// Server
4139
serverPort := "40002"
4240
serverArgs := []string{"-p", serverPort}
43-
serverArgs = append(serverArgs, cmnArgs...)
44-
45-
testCases := []struct {
46-
Name string
47-
Args []string
48-
ServerOutMatchFun func(bool, string) bool
49-
ServerErrMatchFun func(bool, string) bool
50-
ClientOutMatchFun func(bool, string) bool
51-
ClientErrMatchFun func(bool, string) bool
52-
}{
53-
{
54-
"bandwidth_client",
55-
append([]string{"-s", integration.DstAddrPattern + ":" + serverPort, "-cs", "1Mbps"}, cmnArgs...),
56-
func(prev bool, line string) bool {
57-
res := strings.Contains(line, "Received request")
58-
return prev || res // return true if any output line contains the string
59-
},
60-
nil,
61-
integration.RegExp("^Achieved bandwidth: \\d+ bps / \\d+.\\d+ [Mk]bps$"),
62-
nil,
63-
},
64-
}
65-
66-
for _, tc := range testCases {
67-
in := integration.NewAppsIntegration(name, tc.Name, clientCmd, serverCmd, tc.Args, serverArgs, true)
68-
in.ServerStdout(tc.ServerOutMatchFun)
69-
in.ServerStderr(tc.ServerErrMatchFun)
70-
in.ClientStdout(tc.ClientOutMatchFun)
71-
in.ClientStderr(tc.ClientErrMatchFun)
72-
73-
hostAddr := integration.HostAddr
41+
// Client
42+
clientArgs := []string{"-s", integration.DstAddrPattern + ":" + serverPort, "-cs", "1,?,?,1Mbps"}
7443

75-
IAPairs := integration.IAPairs(hostAddr)
76-
IAPairs = IAPairs[:1]
44+
in := integration.NewAppsIntegration(clientCmd, serverCmd, clientArgs, serverArgs)
45+
in.ServerOutMatch = integration.Contains("Received request")
46+
in.ClientOutMatch = integration.RegExp("(?m)^Achieved bandwidth: \\d+ bps / \\d+.\\d+ [Mk]bps$")
7747

78-
if err := integration.RunTests(in, IAPairs, integration.DefaultClientTimeout, 0); err != nil {
79-
t.Fatalf("Error during tests err: %v", err)
80-
}
48+
iaPairs := integration.DefaultIAPairs()
49+
if err := in.Run(t, iaPairs); err != nil {
50+
t.Error(err)
8151
}
8252
}

camerapp/camerapp_integration_test.go

Lines changed: 27 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ package main
1818

1919
import (
2020
"fmt"
21-
"io/ioutil"
22-
"os"
2321
"os/exec"
2422
"path"
2523
"regexp"
@@ -29,85 +27,51 @@ import (
2927
)
3028

3129
const (
32-
name = "camerapp"
3330
clientBin = "scion-imagefetcher"
3431
serverBin = "scion-imageserver"
3532
)
3633

34+
func TestMain(m *testing.M) {
35+
integration.TestMain(m)
36+
}
37+
3738
func TestIntegrationImagefetcher(t *testing.T) {
38-
if err := integration.Init(name); err != nil {
39-
t.Fatalf("Failed to init: %s\n", err)
40-
}
4139
clientCmd := integration.AppBinPath(clientBin)
4240
serverCmd := integration.AppBinPath(serverBin)
4341

44-
// Common arguments
45-
cmnArgs := []string{}
4642
// Server
4743
serverPort := "42002"
4844
serverArgs := []string{"-p", serverPort, "-d", "testdata"}
49-
serverArgs = append(serverArgs, cmnArgs...)
5045

5146
// Sample file path
5247
sample := path.Join("testdata", "logo.jpg")
53-
// Image fetcher output directory
54-
sampleOutputDir, err := ioutil.TempDir("", fmt.Sprintf("%s_integration_output", name))
55-
sampleOuput := path.Join(sampleOutputDir, "download.jpg")
56-
if err != nil {
57-
t.Fatalf("Error during setup err: %v", err)
58-
}
59-
defer os.RemoveAll(sampleOutputDir)
6048

61-
testCases := []struct {
62-
Name string
63-
Args []string
64-
ServerOutMatchFun func(bool, string) bool
65-
ServerErrMatchFun func(bool, string) bool
66-
ClientOutMatchFun func(bool, string) bool
67-
ClientErrMatchFun func(bool, string) bool
68-
}{
69-
{
70-
"fetch_image",
71-
append([]string{"-s", integration.DstAddrPattern + ":" + serverPort, "-output", sampleOuput}, cmnArgs...),
72-
nil,
73-
nil,
74-
func(prev bool, line string) bool {
75-
if !prev {
76-
matched, err := regexp.MatchString("^r+[.r]+$", line)
77-
if err == nil {
78-
return matched
79-
}
80-
} else {
81-
matched, err := regexp.MatchString("^Done, exiting. Total duration \\d+\\.\\d+m?s$", line)
82-
if err != nil || !matched {
83-
return false
84-
}
85-
// The image was downloaded, compare it with the source
86-
cmd := exec.Command("cmp", "-l", sampleOuput, sample)
87-
// cmp exits with 0 exit status if the files are identical, and err is nil if the exit status is 0
88-
err = cmd.Run()
89-
return err == nil
90-
}
91-
return prev
92-
},
93-
nil,
94-
},
95-
}
96-
97-
for _, tc := range testCases {
98-
in := integration.NewAppsIntegration(name, tc.Name, clientCmd, serverCmd, tc.Args, serverArgs, true)
99-
in.ServerStdout(tc.ServerOutMatchFun)
100-
in.ServerStderr(tc.ServerErrMatchFun)
101-
in.ClientStdout(tc.ClientOutMatchFun)
102-
in.ClientStderr(tc.ClientErrMatchFun)
49+
// Client
50+
// Image fetcher output directory
51+
outputDir := t.TempDir()
52+
sampleOutput := path.Join(outputDir, "download.jpg")
10353

104-
hostAddr := integration.HostAddr
54+
clientArgs := []string{"-s", integration.DstAddrPattern + ":" + serverPort, "-output", sampleOutput}
10555

106-
IAPairs := integration.IAPairs(hostAddr)
107-
IAPairs = IAPairs[:1]
56+
in := integration.NewAppsIntegration(clientCmd, serverCmd, clientArgs, serverArgs)
57+
in.ClientOutMatch = func(out string) error {
58+
re := regexp.MustCompile(`^r[.rT]+\nDone, exiting. Total duration \d+\.\d+m?s\n$`)
59+
if !re.MatchString(out) {
60+
return fmt.Errorf("does not match regexp '%s'", re)
61+
}
10862

109-
if err := integration.RunTests(in, IAPairs, integration.DefaultClientTimeout, 0); err != nil {
110-
t.Fatalf("Error during tests err: %v", err)
63+
// The image was downloaded, compare it with the source
64+
cmd := exec.Command("cmp", "--verbose", sampleOutput, sample)
65+
// cmp exits with 0 exit status if the files are identical, and err is nil if the exit status is 0
66+
if out, err := cmd.CombinedOutput(); err != nil {
67+
return fmt.Errorf("comparing downloaded to ground truth: %w\ncommand:\n%s\noutput:\n%s\n",
68+
err, cmd, out)
11169
}
70+
return nil
71+
}
72+
73+
iaPairs := integration.DefaultIAPairs()
74+
if err := in.Run(t, iaPairs); err != nil {
75+
t.Error(err)
11276
}
11377
}

0 commit comments

Comments
 (0)