github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/tools/cmd/godoc/godoc_test.go (about) 1 // Copyright 2013 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package main_test 6 7 import ( 8 "bufio" 9 "bytes" 10 "fmt" 11 "io" 12 "io/ioutil" 13 "net" 14 "net/http" 15 "os" 16 "os/exec" 17 "path/filepath" 18 "regexp" 19 "runtime" 20 "strings" 21 "testing" 22 "time" 23 ) 24 25 var godocTests = []struct { 26 args []string 27 matches []string // regular expressions 28 dontmatch []string // regular expressions 29 }{ 30 { 31 args: []string{"fmt"}, 32 matches: []string{ 33 `import "fmt"`, 34 `Package fmt implements formatted I/O`, 35 }, 36 }, 37 { 38 args: []string{"io", "WriteString"}, 39 matches: []string{ 40 `func WriteString\(`, 41 `WriteString writes the contents of the string s to w`, 42 }, 43 }, 44 { 45 args: []string{"nonexistingpkg"}, 46 matches: []string{ 47 `no such file or directory|does not exist|cannot find the file`, 48 }, 49 }, 50 { 51 args: []string{"fmt", "NonexistentSymbol"}, 52 matches: []string{ 53 `No match found\.`, 54 }, 55 }, 56 { 57 args: []string{"-src", "syscall", "Open"}, 58 matches: []string{ 59 `func Open\(`, 60 }, 61 dontmatch: []string{ 62 `No match found\.`, 63 }, 64 }, 65 } 66 67 // buildGodoc builds the godoc executable. 68 // It returns its path, and a cleanup function. 69 // 70 // TODO(adonovan): opt: do this at most once, and do the cleanup 71 // exactly once. How though? There's no atexit. 72 func buildGodoc(t *testing.T) (bin string, cleanup func()) { 73 if runtime.GOARCH == "arm" { 74 t.Skip("skipping test on arm platforms; too slow") 75 } 76 tmp, err := ioutil.TempDir("", "godoc-regtest-") 77 if err != nil { 78 t.Fatal(err) 79 } 80 defer func() { 81 if cleanup == nil { // probably, go build failed. 82 os.RemoveAll(tmp) 83 } 84 }() 85 86 bin = filepath.Join(tmp, "godoc") 87 if runtime.GOOS == "windows" { 88 bin += ".exe" 89 } 90 cmd := exec.Command("go", "build", "-o", bin) 91 if err := cmd.Run(); err != nil { 92 t.Fatalf("Building godoc: %v", err) 93 } 94 95 return bin, func() { os.RemoveAll(tmp) } 96 } 97 98 // Basic regression test for godoc command-line tool. 99 func TestCLI(t *testing.T) { 100 bin, cleanup := buildGodoc(t) 101 defer cleanup() 102 for _, test := range godocTests { 103 cmd := exec.Command(bin, test.args...) 104 cmd.Args[0] = "godoc" 105 out, err := cmd.CombinedOutput() 106 if err != nil { 107 t.Errorf("Running with args %#v: %v", test.args, err) 108 continue 109 } 110 for _, pat := range test.matches { 111 re := regexp.MustCompile(pat) 112 if !re.Match(out) { 113 t.Errorf("godoc %v =\n%s\nwanted /%v/", strings.Join(test.args, " "), out, pat) 114 } 115 } 116 for _, pat := range test.dontmatch { 117 re := regexp.MustCompile(pat) 118 if re.Match(out) { 119 t.Errorf("godoc %v =\n%s\ndid not want /%v/", strings.Join(test.args, " "), out, pat) 120 } 121 } 122 } 123 } 124 125 func serverAddress(t *testing.T) string { 126 ln, err := net.Listen("tcp", "127.0.0.1:0") 127 if err != nil { 128 ln, err = net.Listen("tcp6", "[::1]:0") 129 } 130 if err != nil { 131 t.Fatal(err) 132 } 133 defer ln.Close() 134 return ln.Addr().String() 135 } 136 137 func waitForServerReady(t *testing.T, addr string) { 138 waitForServer(t, 139 fmt.Sprintf("http://%v/", addr), 140 "The Go Programming Language", 141 5*time.Second) 142 } 143 144 func waitForSearchReady(t *testing.T, addr string) { 145 waitForServer(t, 146 fmt.Sprintf("http://%v/search?q=FALLTHROUGH", addr), 147 "The list of tokens.", 148 2*time.Minute) 149 } 150 151 const pollInterval = 200 * time.Millisecond 152 153 func waitForServer(t *testing.T, url, match string, timeout time.Duration) { 154 // "health check" duplicated from x/tools/cmd/tipgodoc/tip.go 155 deadline := time.Now().Add(timeout) 156 for time.Now().Before(deadline) { 157 time.Sleep(pollInterval) 158 res, err := http.Get(url) 159 if err != nil { 160 continue 161 } 162 rbody, err := ioutil.ReadAll(res.Body) 163 res.Body.Close() 164 if err == nil && res.StatusCode == http.StatusOK && 165 bytes.Contains(rbody, []byte(match)) { 166 return 167 } 168 } 169 t.Fatalf("Server failed to respond in %v", timeout) 170 } 171 172 func killAndWait(cmd *exec.Cmd) { 173 cmd.Process.Kill() 174 cmd.Wait() 175 } 176 177 // Basic integration test for godoc HTTP interface. 178 func TestWeb(t *testing.T) { 179 testWeb(t, false) 180 } 181 182 // Basic integration test for godoc HTTP interface. 183 func TestWebIndex(t *testing.T) { 184 if testing.Short() { 185 t.Skip("skipping test in -short mode") 186 } 187 testWeb(t, true) 188 } 189 190 // Basic integration test for godoc HTTP interface. 191 func testWeb(t *testing.T, withIndex bool) { 192 bin, cleanup := buildGodoc(t) 193 defer cleanup() 194 addr := serverAddress(t) 195 args := []string{fmt.Sprintf("-http=%s", addr)} 196 if withIndex { 197 args = append(args, "-index", "-index_interval=-1s") 198 } 199 cmd := exec.Command(bin, args...) 200 cmd.Stdout = os.Stderr 201 cmd.Stderr = os.Stderr 202 cmd.Args[0] = "godoc" 203 cmd.Env = godocEnv() 204 if err := cmd.Start(); err != nil { 205 t.Fatalf("failed to start godoc: %s", err) 206 } 207 defer killAndWait(cmd) 208 209 if withIndex { 210 waitForSearchReady(t, addr) 211 } else { 212 waitForServerReady(t, addr) 213 } 214 215 tests := []struct { 216 path string 217 match []string 218 dontmatch []string 219 needIndex bool 220 }{ 221 { 222 path: "/", 223 match: []string{"Go is an open source programming language"}, 224 }, 225 { 226 path: "/pkg/fmt/", 227 match: []string{"Package fmt implements formatted I/O"}, 228 }, 229 { 230 path: "/src/fmt/", 231 match: []string{"scan_test.go"}, 232 }, 233 { 234 path: "/src/fmt/print.go", 235 match: []string{"// Println formats using"}, 236 }, 237 { 238 path: "/pkg", 239 match: []string{ 240 "Standard library", 241 "Package fmt implements formatted I/O", 242 }, 243 dontmatch: []string{ 244 "internal/syscall", 245 "cmd/gc", 246 }, 247 }, 248 { 249 path: "/pkg/?m=all", 250 match: []string{ 251 "Standard library", 252 "Package fmt implements formatted I/O", 253 "internal/syscall", 254 }, 255 dontmatch: []string{ 256 "cmd/gc", 257 }, 258 }, 259 { 260 path: "/search?q=notwithstanding", 261 match: []string{ 262 "/src", 263 }, 264 dontmatch: []string{ 265 "/pkg/bootstrap", 266 }, 267 needIndex: true, 268 }, 269 { 270 path: "/pkg/strings/", 271 match: []string{ 272 `href="/src/strings/strings.go"`, 273 }, 274 }, 275 { 276 path: "/cmd/compile/internal/amd64/", 277 match: []string{ 278 `href="/src/cmd/compile/internal/amd64/reg.go"`, 279 }, 280 }, 281 } 282 for _, test := range tests { 283 if test.needIndex && !withIndex { 284 continue 285 } 286 url := fmt.Sprintf("http://%s%s", addr, test.path) 287 resp, err := http.Get(url) 288 if err != nil { 289 t.Errorf("GET %s failed: %s", url, err) 290 continue 291 } 292 body, err := ioutil.ReadAll(resp.Body) 293 resp.Body.Close() 294 if err != nil { 295 t.Errorf("GET %s: failed to read body: %s (response: %v)", url, err, resp) 296 } 297 isErr := false 298 for _, substr := range test.match { 299 if !bytes.Contains(body, []byte(substr)) { 300 t.Errorf("GET %s: wanted substring %q in body", url, substr) 301 isErr = true 302 } 303 } 304 for _, substr := range test.dontmatch { 305 if bytes.Contains(body, []byte(substr)) { 306 t.Errorf("GET %s: didn't want substring %q in body", url, substr) 307 isErr = true 308 } 309 } 310 if isErr { 311 t.Errorf("GET %s: got:\n%s", url, body) 312 } 313 } 314 } 315 316 // Basic integration test for godoc -analysis=type (via HTTP interface). 317 func TestTypeAnalysis(t *testing.T) { 318 if runtime.GOOS == "plan9" { 319 t.Skip("skipping test on plan9 (issue #11974)") // see comment re: Plan 9 below 320 } 321 322 // Write a fake GOROOT/GOPATH. 323 tmpdir, err := ioutil.TempDir("", "godoc-analysis") 324 if err != nil { 325 t.Fatalf("ioutil.TempDir failed: %s", err) 326 } 327 defer os.RemoveAll(tmpdir) 328 for _, f := range []struct{ file, content string }{ 329 {"goroot/src/lib/lib.go", ` 330 package lib 331 type T struct{} 332 const C = 3 333 var V T 334 func (T) F() int { return C } 335 `}, 336 {"gopath/src/app/main.go", ` 337 package main 338 import "lib" 339 func main() { print(lib.V) } 340 `}, 341 } { 342 file := filepath.Join(tmpdir, f.file) 343 if err := os.MkdirAll(filepath.Dir(file), 0755); err != nil { 344 t.Fatalf("MkdirAll(%s) failed: %s", filepath.Dir(file), err) 345 } 346 if err := ioutil.WriteFile(file, []byte(f.content), 0644); err != nil { 347 t.Fatal(err) 348 } 349 } 350 351 // Start the server. 352 bin, cleanup := buildGodoc(t) 353 defer cleanup() 354 addr := serverAddress(t) 355 cmd := exec.Command(bin, fmt.Sprintf("-http=%s", addr), "-analysis=type") 356 cmd.Env = append(cmd.Env, fmt.Sprintf("GOROOT=%s", filepath.Join(tmpdir, "goroot"))) 357 cmd.Env = append(cmd.Env, fmt.Sprintf("GOPATH=%s", filepath.Join(tmpdir, "gopath"))) 358 for _, e := range os.Environ() { 359 if strings.HasPrefix(e, "GOROOT=") || strings.HasPrefix(e, "GOPATH=") { 360 continue 361 } 362 cmd.Env = append(cmd.Env, e) 363 } 364 cmd.Stdout = os.Stderr 365 stderr, err := cmd.StderrPipe() 366 if err != nil { 367 t.Fatal(err) 368 } 369 cmd.Args[0] = "godoc" 370 if err := cmd.Start(); err != nil { 371 t.Fatalf("failed to start godoc: %s", err) 372 } 373 defer killAndWait(cmd) 374 waitForServerReady(t, addr) 375 376 // Wait for type analysis to complete. 377 reader := bufio.NewReader(stderr) 378 for { 379 s, err := reader.ReadString('\n') // on Plan 9 this fails 380 if err != nil { 381 t.Fatal(err) 382 } 383 fmt.Fprint(os.Stderr, s) 384 if strings.Contains(s, "Type analysis complete.") { 385 break 386 } 387 } 388 go io.Copy(os.Stderr, reader) 389 390 t0 := time.Now() 391 392 // Make an HTTP request and check for a regular expression match. 393 // The patterns are very crude checks that basic type information 394 // has been annotated onto the source view. 395 tryagain: 396 for _, test := range []struct{ url, pattern string }{ 397 {"/src/lib/lib.go", "L2.*package .*Package docs for lib.*/lib"}, 398 {"/src/lib/lib.go", "L3.*type .*type info for T.*struct"}, 399 {"/src/lib/lib.go", "L5.*var V .*type T struct"}, 400 {"/src/lib/lib.go", "L6.*func .*type T struct.*T.*return .*const C untyped int.*C"}, 401 402 {"/src/app/main.go", "L2.*package .*Package docs for app"}, 403 {"/src/app/main.go", "L3.*import .*Package docs for lib.*lib"}, 404 {"/src/app/main.go", "L4.*func main.*package lib.*lib.*var lib.V lib.T.*V"}, 405 } { 406 url := fmt.Sprintf("http://%s%s", addr, test.url) 407 resp, err := http.Get(url) 408 if err != nil { 409 t.Errorf("GET %s failed: %s", url, err) 410 continue 411 } 412 body, err := ioutil.ReadAll(resp.Body) 413 resp.Body.Close() 414 if err != nil { 415 t.Errorf("GET %s: failed to read body: %s (response: %v)", url, err, resp) 416 continue 417 } 418 419 if !bytes.Contains(body, []byte("Static analysis features")) { 420 // Type analysis results usually become available within 421 // ~4ms after godoc startup (for this input on my machine). 422 if elapsed := time.Since(t0); elapsed > 500*time.Millisecond { 423 t.Fatalf("type analysis results still unavailable after %s", elapsed) 424 } 425 time.Sleep(10 * time.Millisecond) 426 goto tryagain 427 } 428 429 match, err := regexp.Match(test.pattern, body) 430 if err != nil { 431 t.Errorf("regexp.Match(%q) failed: %s", test.pattern, err) 432 continue 433 } 434 if !match { 435 // This is a really ugly failure message. 436 t.Errorf("GET %s: body doesn't match %q, got:\n%s", 437 url, test.pattern, string(body)) 438 } 439 } 440 } 441 442 // godocEnv returns the process environment without the GOPATH variable. 443 // (We don't want the indexer looking at the local workspace during tests.) 444 func godocEnv() (env []string) { 445 for _, v := range os.Environ() { 446 if strings.HasPrefix(v, "GOPATH=") { 447 continue 448 } 449 env = append(env, v) 450 } 451 return 452 }