@@ -12,6 +12,8 @@ import (
12
12
"os/exec"
13
13
"os/signal"
14
14
"runtime"
15
+ "strings"
16
+ "sync"
15
17
"syscall"
16
18
"testing"
17
19
"time"
@@ -66,31 +68,36 @@ func TestMain(m *testing.M) {
66
68
67
69
var topologies = []integration.TestTopology {
68
70
{
69
- Name : "BasicLoopback " ,
71
+ Name : "BasicLoopbackDERP " ,
70
72
SetupNetworking : integration .SetupNetworkingLoopback ,
71
73
StartServer : integration .StartServerBasic ,
72
74
StartClient : integration .StartClientBasic ,
73
- RunTests : func (t * testing.T , log slog.Logger , serverURL * url.URL , myID , peerID uuid.UUID , conn * tailnet.Conn ) {
74
- // Test basic connectivity
75
- peerIP := tailnet .IPFromUUID (peerID )
76
- _ , _ , _ , err := conn .Ping (testutil .Context (t , testutil .WaitLong ), peerIP )
77
- require .NoError (t , err , "ping peer" )
78
- },
75
+ RunTests : integration .TestSuite ,
76
+ },
77
+ {
78
+ Name : "EasyNATDERP" ,
79
+ SetupNetworking : integration .SetupNetworkingEasyNAT ,
80
+ StartServer : integration .StartServerBasic ,
81
+ StartClient : integration .StartClientBasic ,
82
+ RunTests : integration .TestSuite ,
79
83
},
80
84
}
81
85
82
- //nolint:paralleltest
86
+ //nolint:paralleltest,tparallel
83
87
func TestIntegration (t * testing.T ) {
84
88
if * isSubprocess {
85
89
handleTestSubprocess (t )
86
90
return
87
91
}
88
92
89
93
for _ , topo := range topologies {
90
- //nolint:paralleltest
94
+ topo := topo
91
95
t .Run (topo .Name , func (t * testing.T ) {
92
- log := slogtest .Make (t , nil ).Leveled (slog .LevelDebug )
96
+ // These can run in parallel because every test should be in an
97
+ // isolated NetNS.
98
+ t .Parallel ()
93
99
100
+ log := slogtest .Make (t , nil ).Leveled (slog .LevelDebug )
94
101
networking := topo .SetupNetworking (t , log )
95
102
96
103
// Fork the three child processes.
@@ -100,13 +107,13 @@ func TestIntegration(t *testing.T) {
100
107
client2ErrCh , closeClient2 := startClientSubprocess (t , topo .Name , networking , 2 )
101
108
102
109
// Wait for client1 to exit.
103
- require .NoError (t , <- client1ErrCh )
110
+ require .NoError (t , <- client1ErrCh , "client 1 exited" )
104
111
105
112
// Close client2 and the server.
106
113
closeClient2 ()
107
- require .NoError (t , <- client2ErrCh )
114
+ require .NoError (t , <- client2ErrCh , "client 2 exited" )
108
115
closeServer ()
109
- require .NoError (t , <- serverErrCh )
116
+ require .NoError (t , <- serverErrCh , "server exited" )
110
117
})
111
118
}
112
119
}
@@ -152,8 +159,14 @@ func handleTestSubprocess(t *testing.T) {
152
159
conn := topo .StartClient (t , log , serverURL , myID , peerID )
153
160
154
161
if * clientRunTests {
162
+ // Wait for connectivity.
163
+ peerIP := tailnet .IPFromUUID (peerID )
164
+ if ! conn .AwaitReachable (testutil .Context (t , testutil .WaitLong ), peerIP ) {
165
+ t .Fatalf ("peer %v did not become reachable" , peerIP )
166
+ }
167
+
155
168
topo .RunTests (t , log , serverURL , myID , peerID , conn )
156
- // and exit
169
+ // then exit
157
170
return
158
171
}
159
172
}
@@ -194,7 +207,7 @@ func waitForServerAvailable(t *testing.T, serverURL *url.URL) {
194
207
}
195
208
196
209
func startServerSubprocess (t * testing.T , topologyName string , networking integration.TestNetworking ) (<- chan error , func ()) {
197
- return startSubprocess (t , networking .ProcessServer .NetNSFd , []string {
210
+ return startSubprocess (t , "server" , networking .ProcessServer .NetNS , []string {
198
211
"--subprocess" ,
199
212
"--test-name=" + topologyName ,
200
213
"--role=server" ,
@@ -210,10 +223,12 @@ func startClientSubprocess(t *testing.T, topologyName string, networking integra
210
223
myID = integration .Client1ID
211
224
peerID = integration .Client2ID
212
225
accessURL = networking .ServerAccessURLClient1
226
+ netNS = networking .ProcessClient1 .NetNS
213
227
)
214
228
if clientNumber == 2 {
215
229
myID , peerID = peerID , myID
216
230
accessURL = networking .ServerAccessURLClient2
231
+ netNS = networking .ProcessClient2 .NetNS
217
232
}
218
233
219
234
flags := []string {
@@ -229,14 +244,15 @@ func startClientSubprocess(t *testing.T, topologyName string, networking integra
229
244
flags = append (flags , "--client-run-tests" )
230
245
}
231
246
232
- return startSubprocess (t , networking . ProcessClient1 . NetNSFd , flags )
247
+ return startSubprocess (t , clientName , netNS , flags )
233
248
}
234
249
235
- func startSubprocess (t * testing.T , netNSFd int , flags []string ) (<- chan error , func ()) {
250
+ func startSubprocess (t * testing.T , processName string , netNS * os. File , flags []string ) (<- chan error , func ()) {
236
251
name := os .Args [0 ]
237
- args := append (os .Args [1 :], flags ... )
252
+ // Always use verbose mode since it gets piped to the parent test anyways.
253
+ args := append (os .Args [1 :], append ([]string {"-test.v=true" }, flags ... )... )
238
254
239
- if netNSFd > 0 {
255
+ if netNS != nil {
240
256
// We use nsenter to enter the namespace.
241
257
// We can't use `setns` easily from Golang in the parent process because
242
258
// you can't execute the syscall in the forked child thread before it
@@ -249,11 +265,17 @@ func startSubprocess(t *testing.T, netNSFd int, flags []string) (<-chan error, f
249
265
}
250
266
251
267
cmd := exec .Command (name , args ... )
252
- if netNSFd > 0 {
253
- cmd .ExtraFiles = []* os.File {os .NewFile (uintptr (netNSFd ), "" )}
268
+ if netNS != nil {
269
+ cmd .ExtraFiles = []* os.File {netNS }
270
+ }
271
+
272
+ out := & testWriter {
273
+ name : processName ,
274
+ t : t ,
254
275
}
255
- cmd .Stdout = os .Stdout
256
- cmd .Stderr = os .Stderr
276
+ t .Cleanup (out .Flush )
277
+ cmd .Stdout = out
278
+ cmd .Stderr = out
257
279
cmd .SysProcAttr = & syscall.SysProcAttr {
258
280
Pdeathsig : syscall .SIGTERM ,
259
281
}
@@ -293,3 +315,43 @@ func startSubprocess(t *testing.T, netNSFd int, flags []string) (<-chan error, f
293
315
294
316
return waitErr , closeFn
295
317
}
318
+
319
+ type testWriter struct {
320
+ mut sync.Mutex
321
+ name string
322
+ t * testing.T
323
+
324
+ capturedLines []string
325
+ }
326
+
327
+ func (w * testWriter ) Write (p []byte ) (n int , err error ) {
328
+ w .mut .Lock ()
329
+ defer w .mut .Unlock ()
330
+ str := string (p )
331
+ split := strings .Split (str , "\n " )
332
+ for _ , s := range split {
333
+ if s == "" {
334
+ continue
335
+ }
336
+
337
+ // If a line begins with "\s*--- (PASS|FAIL)" or is just PASS or FAIL,
338
+ // then it's a test result line. We want to capture it and log it later.
339
+ trimmed := strings .TrimSpace (s )
340
+ if strings .HasPrefix (trimmed , "--- PASS" ) || strings .HasPrefix (trimmed , "--- FAIL" ) || trimmed == "PASS" || trimmed == "FAIL" {
341
+ w .capturedLines = append (w .capturedLines , s )
342
+ continue
343
+ }
344
+
345
+ w .t .Logf ("%s output: \t %s" , w .name , s )
346
+ }
347
+ return len (p ), nil
348
+ }
349
+
350
+ func (w * testWriter ) Flush () {
351
+ w .mut .Lock ()
352
+ defer w .mut .Unlock ()
353
+ for _ , s := range w .capturedLines {
354
+ w .t .Logf ("%s output: \t %s" , w .name , s )
355
+ }
356
+ w .capturedLines = nil
357
+ }
0 commit comments