2
2
// Use of this source code is governed by a BSD-style
3
3
// license that can be found in the LICENSE file.
4
4
5
- package main_test
5
+ package main
6
6
7
7
import (
8
8
"bytes"
9
+ "context"
9
10
"fmt"
10
11
"go/build"
11
12
"io/ioutil"
12
13
"net"
13
14
"net/http"
14
15
"os"
15
16
"os/exec"
16
- "path/filepath"
17
17
"regexp"
18
18
"runtime"
19
19
"strings"
20
+ "sync"
20
21
"testing"
21
22
"time"
22
23
23
24
"golang.org/x/tools/go/packages/packagestest"
24
25
"golang.org/x/tools/internal/testenv"
25
26
)
26
27
27
- // buildGodoc builds the godoc executable.
28
- // It returns its path, and a cleanup function.
29
- //
30
- // TODO(adonovan): opt: do this at most once, and do the cleanup
31
- // exactly once. How though? There's no atexit.
32
- func buildGodoc (t * testing.T ) (bin string , cleanup func ()) {
33
- t .Helper ()
34
-
35
- if runtime .GOARCH == "arm" {
36
- t .Skip ("skipping test on arm platforms; too slow" )
37
- }
38
- if runtime .GOOS == "android" {
39
- t .Skipf ("the dependencies are not available on android" )
28
+ func TestMain (m * testing.M ) {
29
+ if os .Getenv ("GODOC_TEST_IS_GODOC" ) != "" {
30
+ main ()
31
+ os .Exit (0 )
40
32
}
41
- testenv .NeedsTool (t , "go" )
42
33
43
- tmp , err := ioutil .TempDir ("" , "godoc-regtest-" )
44
- if err != nil {
45
- t .Fatal (err )
46
- }
47
- defer func () {
48
- if cleanup == nil { // probably, go build failed.
49
- os .RemoveAll (tmp )
50
- }
51
- }()
34
+ // Inform subprocesses that they should run the cmd/godoc main instead of
35
+ // running tests. It's a close approximation to building and running the real
36
+ // command, and much less complicated and expensive to build and clean up.
37
+ os .Setenv ("GODOC_TEST_IS_GODOC" , "1" )
52
38
53
- bin = filepath .Join (tmp , "godoc" )
54
- if runtime .GOOS == "windows" {
55
- bin += ".exe"
56
- }
57
- cmd := exec .Command ("go" , "build" , "-o" , bin )
58
- if err := cmd .Run (); err != nil {
59
- t .Fatalf ("Building godoc: %v" , err )
39
+ os .Exit (m .Run ())
40
+ }
41
+
42
+ var exe struct {
43
+ path string
44
+ err error
45
+ once sync.Once
46
+ }
47
+
48
+ func godocPath (t * testing.T ) string {
49
+ switch runtime .GOOS {
50
+ case "js" , "ios" :
51
+ t .Skipf ("skipping test that requires exec" )
60
52
}
61
53
62
- return bin , func () { os .RemoveAll (tmp ) }
54
+ exe .once .Do (func () {
55
+ exe .path , exe .err = os .Executable ()
56
+ })
57
+ if exe .err != nil {
58
+ t .Fatal (exe .err )
59
+ }
60
+ return exe .path
63
61
}
64
62
65
63
func serverAddress (t * testing.T ) string {
@@ -74,60 +72,42 @@ func serverAddress(t *testing.T) string {
74
72
return ln .Addr ().String ()
75
73
}
76
74
77
- func waitForServerReady (t * testing.T , cmd * exec.Cmd , addr string ) {
78
- ch := make (chan error , 1 )
79
- go func () { ch <- fmt .Errorf ("server exited early: %v" , cmd .Wait ()) }()
80
- go waitForServer (t , ch ,
75
+ func waitForServerReady (t * testing.T , ctx context.Context , cmd * exec.Cmd , addr string ) {
76
+ waitForServer (t , ctx ,
81
77
fmt .Sprintf ("http://%v/" , addr ),
82
78
"Go Documentation Server" ,
83
- 15 * time .Second ,
84
79
false )
85
- if err := <- ch ; err != nil {
86
- t .Skipf ("skipping due to https://go.dev/issue/50014: %v" , err )
87
- }
88
80
}
89
81
90
- func waitForSearchReady (t * testing.T , cmd * exec.Cmd , addr string ) {
91
- ch := make (chan error , 1 )
92
- go func () { ch <- fmt .Errorf ("server exited early: %v" , cmd .Wait ()) }()
93
- go waitForServer (t , ch ,
82
+ func waitForSearchReady (t * testing.T , ctx context.Context , cmd * exec.Cmd , addr string ) {
83
+ waitForServer (t , ctx ,
94
84
fmt .Sprintf ("http://%v/search?q=FALLTHROUGH" , addr ),
95
85
"The list of tokens." ,
96
- 2 * time .Minute ,
97
86
false )
98
- if err := <- ch ; err != nil {
99
- t .Skipf ("skipping due to https://go.dev/issue/50014: %v" , err )
100
- }
101
87
}
102
88
103
- func waitUntilScanComplete (t * testing.T , addr string ) {
104
- ch := make (chan error )
105
- go waitForServer (t , ch ,
89
+ func waitUntilScanComplete (t * testing.T , ctx context.Context , addr string ) {
90
+ waitForServer (t , ctx ,
106
91
fmt .Sprintf ("http://%v/pkg" , addr ),
107
92
"Scan is not yet complete" ,
108
- 2 * time .Minute ,
109
93
// setting reverse as true, which means this waits
110
94
// until the string is not returned in the response anymore
111
- true ,
112
- )
113
- if err := <- ch ; err != nil {
114
- t .Skipf ("skipping due to https://go.dev/issue/50014: %v" , err )
115
- }
95
+ true )
116
96
}
117
97
118
- const pollInterval = 200 * time .Millisecond
98
+ const pollInterval = 50 * time .Millisecond
119
99
120
- // waitForServer waits for server to meet the required condition.
121
- // It sends a single error value to ch, unless the test has failed.
122
- // The error value is nil if the required condition was met within
123
- // timeout, or non-nil otherwise.
124
- func waitForServer (t * testing.T , ch chan <- error , url , match string , timeout time.Duration , reverse bool ) {
125
- deadline := time .Now ().Add (timeout )
126
- for time .Now ().Before (deadline ) {
127
- time .Sleep (pollInterval )
128
- if t .Failed () {
129
- return
100
+ // waitForServer waits for server to meet the required condition,
101
+ // failing the test if ctx is canceled before that occurs.
102
+ func waitForServer (t * testing.T , ctx context.Context , url , match string , reverse bool ) {
103
+ start := time .Now ()
104
+ for {
105
+ if ctx .Err () != nil {
106
+ t .Helper ()
107
+ t .Fatalf ("server failed to respond in %v" , time .Since (start ))
130
108
}
109
+
110
+ time .Sleep (pollInterval )
131
111
res , err := http .Get (url )
132
112
if err != nil {
133
113
continue
@@ -140,11 +120,9 @@ func waitForServer(t *testing.T, ch chan<- error, url, match string, timeout tim
140
120
switch {
141
121
case ! reverse && bytes .Contains (body , []byte (match )),
142
122
reverse && ! bytes .Contains (body , []byte (match )):
143
- ch <- nil
144
123
return
145
124
}
146
125
}
147
- ch <- fmt .Errorf ("server failed to respond in %v" , timeout )
148
126
}
149
127
150
128
// hasTag checks whether a given release tag is contained in the current version
@@ -158,24 +136,18 @@ func hasTag(t string) bool {
158
136
return false
159
137
}
160
138
161
- func killAndWait (cmd * exec.Cmd ) {
162
- cmd .Process .Kill ()
163
- cmd .Process .Wait ()
164
- }
165
-
166
139
func TestURL (t * testing.T ) {
167
140
if runtime .GOOS == "plan9" {
168
141
t .Skip ("skipping on plan9; fails to start up quickly enough" )
169
142
}
170
- bin , cleanup := buildGodoc (t )
171
- defer cleanup ()
143
+ bin := godocPath (t )
172
144
173
145
testcase := func (url string , contents string ) func (t * testing.T ) {
174
146
return func (t * testing.T ) {
175
147
stdout , stderr := new (bytes.Buffer ), new (bytes.Buffer )
176
148
177
149
args := []string {fmt .Sprintf ("-url=%s" , url )}
178
- cmd := exec .Command (bin , args ... )
150
+ cmd := testenv .Command (t , bin , args ... )
179
151
cmd .Stdout = stdout
180
152
cmd .Stderr = stderr
181
153
cmd .Args [0 ] = "godoc"
@@ -205,8 +177,8 @@ func TestURL(t *testing.T) {
205
177
206
178
// Basic integration test for godoc HTTP interface.
207
179
func TestWeb (t * testing.T ) {
208
- bin , cleanup := buildGodoc (t )
209
- defer cleanup ()
180
+ bin := godocPath (t )
181
+
210
182
for _ , x := range packagestest .All {
211
183
t .Run (x .Name (), func (t * testing.T ) {
212
184
testWeb (t , x , bin , false )
@@ -217,17 +189,19 @@ func TestWeb(t *testing.T) {
217
189
// Basic integration test for godoc HTTP interface.
218
190
func TestWebIndex (t * testing.T ) {
219
191
if testing .Short () {
220
- t .Skip ("skipping test in -short mode" )
192
+ t .Skip ("skipping slow test in -short mode" )
221
193
}
222
- bin , cleanup := buildGodoc (t )
223
- defer cleanup ()
194
+ bin := godocPath (t )
224
195
testWeb (t , packagestest .GOPATH , bin , true )
225
196
}
226
197
227
198
// Basic integration test for godoc HTTP interface.
228
199
func testWeb (t * testing.T , x packagestest.Exporter , bin string , withIndex bool ) {
229
- if runtime .GOOS == "plan9" {
230
- t .Skip ("skipping on plan9; fails to start up quickly enough" )
200
+ switch runtime .GOOS {
201
+ case "plan9" :
202
+ t .Skip ("skipping on plan9: fails to start up quickly enough" )
203
+ case "android" , "ios" :
204
+ t .Skip ("skipping on mobile: lacks GOROOT/api in test environment" )
231
205
}
232
206
233
207
// Write a fake GOROOT/GOPATH with some third party packages.
@@ -256,23 +230,39 @@ package a; import _ "godoc.test/repo2/a"; const Name = "repo1a"`,
256
230
if withIndex {
257
231
args = append (args , "-index" , "-index_interval=-1s" )
258
232
}
259
- cmd := exec .Command (bin , args ... )
233
+ cmd := testenv .Command (t , bin , args ... )
260
234
cmd .Dir = e .Config .Dir
261
235
cmd .Env = e .Config .Env
262
- cmd .Stdout = os .Stderr
263
- cmd .Stderr = os .Stderr
236
+ cmdOut := new (strings.Builder )
237
+ cmd .Stdout = cmdOut
238
+ cmd .Stderr = cmdOut
264
239
cmd .Args [0 ] = "godoc"
265
240
266
241
if err := cmd .Start (); err != nil {
267
242
t .Fatalf ("failed to start godoc: %s" , err )
268
243
}
269
- defer killAndWait (cmd )
244
+ ctx , cancel := context .WithCancel (context .Background ())
245
+ go func () {
246
+ err := cmd .Wait ()
247
+ t .Logf ("%v: %v" , cmd , err )
248
+ cancel ()
249
+ }()
250
+ defer func () {
251
+ // Shut down the server cleanly if possible.
252
+ if runtime .GOOS == "windows" {
253
+ cmd .Process .Kill () // Windows doesn't support os.Interrupt.
254
+ } else {
255
+ cmd .Process .Signal (os .Interrupt )
256
+ }
257
+ <- ctx .Done ()
258
+ t .Logf ("server output:\n %s" , cmdOut )
259
+ }()
270
260
271
261
if withIndex {
272
- waitForSearchReady (t , cmd , addr )
262
+ waitForSearchReady (t , ctx , cmd , addr )
273
263
} else {
274
- waitForServerReady (t , cmd , addr )
275
- waitUntilScanComplete (t , addr )
264
+ waitForServerReady (t , ctx , cmd , addr )
265
+ waitUntilScanComplete (t , ctx , addr )
276
266
}
277
267
278
268
tests := []struct {
@@ -454,22 +444,17 @@ func TestNoMainModule(t *testing.T) {
454
444
if runtime .GOOS == "plan9" {
455
445
t .Skip ("skipping on plan9; for consistency with other tests that build godoc binary" )
456
446
}
457
- bin , cleanup := buildGodoc (t )
458
- defer cleanup ()
459
- tempDir , err := ioutil .TempDir ("" , "godoc-test-" )
460
- if err != nil {
461
- t .Fatal (err )
462
- }
463
- defer os .RemoveAll (tempDir )
447
+ bin := godocPath (t )
448
+ tempDir := t .TempDir ()
464
449
465
450
// Run godoc in an empty directory with module mode explicitly on,
466
451
// so that 'go env GOMOD' reports os.DevNull.
467
- cmd := exec .Command (bin , "-url=/" )
452
+ cmd := testenv .Command (t , bin , "-url=/" )
468
453
cmd .Dir = tempDir
469
454
cmd .Env = append (os .Environ (), "GO111MODULE=on" )
470
455
var stderr bytes.Buffer
471
456
cmd .Stderr = & stderr
472
- err = cmd .Run ()
457
+ err : = cmd .Run ()
473
458
if err != nil {
474
459
t .Fatalf ("godoc command failed: %v\n stderr=%q" , err , stderr .String ())
475
460
}
0 commit comments