Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.0.0-20201105173854-bc9fc8d8c4bc / cmd / godoc / godoc_test.go
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         "go/build"
12         "io"
13         "io/ioutil"
14         "net"
15         "net/http"
16         "os"
17         "os/exec"
18         "path/filepath"
19         "regexp"
20         "runtime"
21         "strings"
22         "testing"
23         "time"
24
25         "golang.org/x/tools/go/packages/packagestest"
26         "golang.org/x/tools/internal/testenv"
27 )
28
29 // buildGodoc builds the godoc executable.
30 // It returns its path, and a cleanup function.
31 //
32 // TODO(adonovan): opt: do this at most once, and do the cleanup
33 // exactly once.  How though?  There's no atexit.
34 func buildGodoc(t *testing.T) (bin string, cleanup func()) {
35         t.Helper()
36
37         if runtime.GOARCH == "arm" {
38                 t.Skip("skipping test on arm platforms; too slow")
39         }
40         if runtime.GOOS == "android" {
41                 t.Skipf("the dependencies are not available on android")
42         }
43         testenv.NeedsTool(t, "go")
44
45         tmp, err := ioutil.TempDir("", "godoc-regtest-")
46         if err != nil {
47                 t.Fatal(err)
48         }
49         defer func() {
50                 if cleanup == nil { // probably, go build failed.
51                         os.RemoveAll(tmp)
52                 }
53         }()
54
55         bin = filepath.Join(tmp, "godoc")
56         if runtime.GOOS == "windows" {
57                 bin += ".exe"
58         }
59         cmd := exec.Command("go", "build", "-o", bin)
60         if err := cmd.Run(); err != nil {
61                 t.Fatalf("Building godoc: %v", err)
62         }
63
64         return bin, func() { os.RemoveAll(tmp) }
65 }
66
67 func serverAddress(t *testing.T) string {
68         ln, err := net.Listen("tcp", "127.0.0.1:0")
69         if err != nil {
70                 ln, err = net.Listen("tcp6", "[::1]:0")
71         }
72         if err != nil {
73                 t.Fatal(err)
74         }
75         defer ln.Close()
76         return ln.Addr().String()
77 }
78
79 func waitForServerReady(t *testing.T, cmd *exec.Cmd, addr string) {
80         ch := make(chan error, 1)
81         go func() { ch <- fmt.Errorf("server exited early: %v", cmd.Wait()) }()
82         go waitForServer(t, ch,
83                 fmt.Sprintf("http://%v/", addr),
84                 "Go Documentation Server",
85                 15*time.Second,
86                 false)
87         if err := <-ch; err != nil {
88                 t.Fatal(err)
89         }
90 }
91
92 func waitForSearchReady(t *testing.T, cmd *exec.Cmd, addr string) {
93         ch := make(chan error, 1)
94         go func() { ch <- fmt.Errorf("server exited early: %v", cmd.Wait()) }()
95         go waitForServer(t, ch,
96                 fmt.Sprintf("http://%v/search?q=FALLTHROUGH", addr),
97                 "The list of tokens.",
98                 2*time.Minute,
99                 false)
100         if err := <-ch; err != nil {
101                 t.Fatal(err)
102         }
103 }
104
105 func waitUntilScanComplete(t *testing.T, addr string) {
106         ch := make(chan error)
107         go waitForServer(t, ch,
108                 fmt.Sprintf("http://%v/pkg", addr),
109                 "Scan is not yet complete",
110                 2*time.Minute,
111                 // setting reverse as true, which means this waits
112                 // until the string is not returned in the response anymore
113                 true,
114         )
115         if err := <-ch; err != nil {
116                 t.Fatal(err)
117         }
118 }
119
120 const pollInterval = 200 * time.Millisecond
121
122 // waitForServer waits for server to meet the required condition.
123 // It sends a single error value to ch, unless the test has failed.
124 // The error value is nil if the required condition was met within
125 // timeout, or non-nil otherwise.
126 func waitForServer(t *testing.T, ch chan<- error, url, match string, timeout time.Duration, reverse bool) {
127         deadline := time.Now().Add(timeout)
128         for time.Now().Before(deadline) {
129                 time.Sleep(pollInterval)
130                 if t.Failed() {
131                         return
132                 }
133                 res, err := http.Get(url)
134                 if err != nil {
135                         continue
136                 }
137                 body, err := ioutil.ReadAll(res.Body)
138                 res.Body.Close()
139                 if err != nil || res.StatusCode != http.StatusOK {
140                         continue
141                 }
142                 switch {
143                 case !reverse && bytes.Contains(body, []byte(match)),
144                         reverse && !bytes.Contains(body, []byte(match)):
145                         ch <- nil
146                         return
147                 }
148         }
149         ch <- fmt.Errorf("server failed to respond in %v", timeout)
150 }
151
152 // hasTag checks whether a given release tag is contained in the current version
153 // of the go binary.
154 func hasTag(t string) bool {
155         for _, v := range build.Default.ReleaseTags {
156                 if t == v {
157                         return true
158                 }
159         }
160         return false
161 }
162
163 func killAndWait(cmd *exec.Cmd) {
164         cmd.Process.Kill()
165         cmd.Process.Wait()
166 }
167
168 func TestURL(t *testing.T) {
169         if runtime.GOOS == "plan9" {
170                 t.Skip("skipping on plan9; fails to start up quickly enough")
171         }
172         bin, cleanup := buildGodoc(t)
173         defer cleanup()
174
175         testcase := func(url string, contents string) func(t *testing.T) {
176                 return func(t *testing.T) {
177                         stdout, stderr := new(bytes.Buffer), new(bytes.Buffer)
178
179                         args := []string{fmt.Sprintf("-url=%s", url)}
180                         cmd := exec.Command(bin, args...)
181                         cmd.Stdout = stdout
182                         cmd.Stderr = stderr
183                         cmd.Args[0] = "godoc"
184
185                         // Set GOPATH variable to a non-existing absolute path
186                         // and GOPROXY=off to disable module fetches.
187                         // We cannot just unset GOPATH variable because godoc would default it to ~/go.
188                         // (We don't want the indexer looking at the local workspace during tests.)
189                         cmd.Env = append(os.Environ(),
190                                 "GOPATH=/does_not_exist",
191                                 "GOPROXY=off",
192                                 "GO111MODULE=off")
193
194                         if err := cmd.Run(); err != nil {
195                                 t.Fatalf("failed to run godoc -url=%q: %s\nstderr:\n%s", url, err, stderr)
196                         }
197
198                         if !strings.Contains(stdout.String(), contents) {
199                                 t.Errorf("did not find substring %q in output of godoc -url=%q:\n%s", contents, url, stdout)
200                         }
201                 }
202         }
203
204         t.Run("index", testcase("/", "These packages are part of the Go Project but outside the main Go tree."))
205         t.Run("fmt", testcase("/pkg/fmt", "Package fmt implements formatted I/O"))
206 }
207
208 // Basic integration test for godoc HTTP interface.
209 func TestWeb(t *testing.T) {
210         bin, cleanup := buildGodoc(t)
211         defer cleanup()
212         for _, x := range packagestest.All {
213                 t.Run(x.Name(), func(t *testing.T) {
214                         testWeb(t, x, bin, false)
215                 })
216         }
217 }
218
219 // Basic integration test for godoc HTTP interface.
220 func TestWebIndex(t *testing.T) {
221         if testing.Short() {
222                 t.Skip("skipping test in -short mode")
223         }
224         bin, cleanup := buildGodoc(t)
225         defer cleanup()
226         testWeb(t, packagestest.GOPATH, bin, true)
227 }
228
229 // Basic integration test for godoc HTTP interface.
230 func testWeb(t *testing.T, x packagestest.Exporter, bin string, withIndex bool) {
231         if runtime.GOOS == "plan9" {
232                 t.Skip("skipping on plan9; fails to start up quickly enough")
233         }
234
235         // Write a fake GOROOT/GOPATH with some third party packages.
236         e := packagestest.Export(t, x, []packagestest.Module{
237                 {
238                         Name: "godoc.test/repo1",
239                         Files: map[string]interface{}{
240                                 "a/a.go": `// Package a is a package in godoc.test/repo1.
241 package a; import _ "godoc.test/repo2/a"; const Name = "repo1a"`,
242                                 "b/b.go": `package b; const Name = "repo1b"`,
243                         },
244                 },
245                 {
246                         Name: "godoc.test/repo2",
247                         Files: map[string]interface{}{
248                                 "a/a.go": `package a; const Name = "repo2a"`,
249                                 "b/b.go": `package b; const Name = "repo2b"`,
250                         },
251                 },
252         })
253         defer e.Cleanup()
254
255         // Start the server.
256         addr := serverAddress(t)
257         args := []string{fmt.Sprintf("-http=%s", addr)}
258         if withIndex {
259                 args = append(args, "-index", "-index_interval=-1s")
260         }
261         cmd := exec.Command(bin, args...)
262         cmd.Dir = e.Config.Dir
263         cmd.Env = e.Config.Env
264         cmd.Stdout = os.Stderr
265         cmd.Stderr = os.Stderr
266         cmd.Args[0] = "godoc"
267
268         if err := cmd.Start(); err != nil {
269                 t.Fatalf("failed to start godoc: %s", err)
270         }
271         defer killAndWait(cmd)
272
273         if withIndex {
274                 waitForSearchReady(t, cmd, addr)
275         } else {
276                 waitForServerReady(t, cmd, addr)
277                 waitUntilScanComplete(t, addr)
278         }
279
280         tests := []struct {
281                 path        string
282                 contains    []string // substring
283                 match       []string // regexp
284                 notContains []string
285                 needIndex   bool
286                 releaseTag  string // optional release tag that must be in go/build.ReleaseTags
287         }{
288                 {
289                         path: "/",
290                         contains: []string{
291                                 "Go Documentation Server",
292                                 "Standard library",
293                                 "These packages are part of the Go Project but outside the main Go tree.",
294                         },
295                 },
296                 {
297                         path:     "/pkg/fmt/",
298                         contains: []string{"Package fmt implements formatted I/O"},
299                 },
300                 {
301                         path:     "/src/fmt/",
302                         contains: []string{"scan_test.go"},
303                 },
304                 {
305                         path:     "/src/fmt/print.go",
306                         contains: []string{"// Println formats using"},
307                 },
308                 {
309                         path: "/pkg",
310                         contains: []string{
311                                 "Standard library",
312                                 "Package fmt implements formatted I/O",
313                                 "Third party",
314                                 "Package a is a package in godoc.test/repo1.",
315                         },
316                         notContains: []string{
317                                 "internal/syscall",
318                                 "cmd/gc",
319                         },
320                 },
321                 {
322                         path: "/pkg/?m=all",
323                         contains: []string{
324                                 "Standard library",
325                                 "Package fmt implements formatted I/O",
326                                 "internal/syscall/?m=all",
327                         },
328                         notContains: []string{
329                                 "cmd/gc",
330                         },
331                 },
332                 {
333                         path: "/search?q=ListenAndServe",
334                         contains: []string{
335                                 "/src",
336                         },
337                         notContains: []string{
338                                 "/pkg/bootstrap",
339                         },
340                         needIndex: true,
341                 },
342                 {
343                         path: "/pkg/strings/",
344                         contains: []string{
345                                 `href="/src/strings/strings.go"`,
346                         },
347                 },
348                 {
349                         path: "/cmd/compile/internal/amd64/",
350                         contains: []string{
351                                 `href="/src/cmd/compile/internal/amd64/ssa.go"`,
352                         },
353                 },
354                 {
355                         path: "/pkg/math/bits/",
356                         contains: []string{
357                                 `Added in Go 1.9`,
358                         },
359                 },
360                 {
361                         path: "/pkg/net/",
362                         contains: []string{
363                                 `// IPv6 scoped addressing zone; added in Go 1.1`,
364                         },
365                 },
366                 {
367                         path: "/pkg/net/http/httptrace/",
368                         match: []string{
369                                 `Got1xxResponse.*// Go 1\.11`,
370                         },
371                         releaseTag: "go1.11",
372                 },
373                 // Verify we don't add version info to a struct field added the same time
374                 // as the struct itself:
375                 {
376                         path: "/pkg/net/http/httptrace/",
377                         match: []string{
378                                 `(?m)GotFirstResponseByte func\(\)\s*$`,
379                         },
380                 },
381                 // Remove trailing periods before adding semicolons:
382                 {
383                         path: "/pkg/database/sql/",
384                         contains: []string{
385                                 "The number of connections currently in use; added in Go 1.11",
386                                 "The number of idle connections; added in Go 1.11",
387                         },
388                         releaseTag: "go1.11",
389                 },
390
391                 // Third party packages.
392                 {
393                         path:     "/pkg/godoc.test/repo1/a",
394                         contains: []string{`const <span id="Name">Name</span> = &#34;repo1a&#34;`},
395                 },
396                 {
397                         path:     "/pkg/godoc.test/repo2/b",
398                         contains: []string{`const <span id="Name">Name</span> = &#34;repo2b&#34;`},
399                 },
400         }
401         for _, test := range tests {
402                 if test.needIndex && !withIndex {
403                         continue
404                 }
405                 url := fmt.Sprintf("http://%s%s", addr, test.path)
406                 resp, err := http.Get(url)
407                 if err != nil {
408                         t.Errorf("GET %s failed: %s", url, err)
409                         continue
410                 }
411                 body, err := ioutil.ReadAll(resp.Body)
412                 strBody := string(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                 }
417                 isErr := false
418                 for _, substr := range test.contains {
419                         if test.releaseTag != "" && !hasTag(test.releaseTag) {
420                                 continue
421                         }
422                         if !bytes.Contains(body, []byte(substr)) {
423                                 t.Errorf("GET %s: wanted substring %q in body", url, substr)
424                                 isErr = true
425                         }
426                 }
427                 for _, re := range test.match {
428                         if test.releaseTag != "" && !hasTag(test.releaseTag) {
429                                 continue
430                         }
431                         if ok, err := regexp.MatchString(re, strBody); !ok || err != nil {
432                                 if err != nil {
433                                         t.Fatalf("Bad regexp %q: %v", re, err)
434                                 }
435                                 t.Errorf("GET %s: wanted to match %s in body", url, re)
436                                 isErr = true
437                         }
438                 }
439                 for _, substr := range test.notContains {
440                         if bytes.Contains(body, []byte(substr)) {
441                                 t.Errorf("GET %s: didn't want substring %q in body", url, substr)
442                                 isErr = true
443                         }
444                 }
445                 if isErr {
446                         t.Errorf("GET %s: got:\n%s", url, body)
447                 }
448         }
449 }
450
451 // Test for golang.org/issue/35476.
452 func TestNoMainModule(t *testing.T) {
453         if testing.Short() {
454                 t.Skip("skipping test in -short mode")
455         }
456         if runtime.GOOS == "plan9" {
457                 t.Skip("skipping on plan9; for consistency with other tests that build godoc binary")
458         }
459         bin, cleanup := buildGodoc(t)
460         defer cleanup()
461         tempDir, err := ioutil.TempDir("", "godoc-test-")
462         if err != nil {
463                 t.Fatal(err)
464         }
465         defer os.RemoveAll(tempDir)
466
467         // Run godoc in an empty directory with module mode explicitly on,
468         // so that 'go env GOMOD' reports os.DevNull.
469         cmd := exec.Command(bin, "-url=/")
470         cmd.Dir = tempDir
471         cmd.Env = append(os.Environ(), "GO111MODULE=on")
472         var stderr bytes.Buffer
473         cmd.Stderr = &stderr
474         err = cmd.Run()
475         if err != nil {
476                 t.Fatalf("godoc command failed: %v\nstderr=%q", err, stderr.String())
477         }
478         if strings.Contains(stderr.String(), "go mod download") {
479                 t.Errorf("stderr contains 'go mod download', is that intentional?\nstderr=%q", stderr.String())
480         }
481 }
482
483 // Basic integration test for godoc -analysis=type (via HTTP interface).
484 func TestTypeAnalysis(t *testing.T) {
485         bin, cleanup := buildGodoc(t)
486         defer cleanup()
487         testTypeAnalysis(t, packagestest.GOPATH, bin)
488         // TODO(golang.org/issue/34473): Add support for type, pointer
489         // analysis in module mode, then enable its test coverage here.
490 }
491 func testTypeAnalysis(t *testing.T, x packagestest.Exporter, bin string) {
492         if runtime.GOOS == "plan9" {
493                 t.Skip("skipping test on plan9 (issue #11974)") // see comment re: Plan 9 below
494         }
495
496         // Write a fake GOROOT/GOPATH.
497         // TODO(golang.org/issue/34473): This test uses import paths without a dot in first
498         // path element. This is not viable in module mode; import paths will need to change.
499         e := packagestest.Export(t, x, []packagestest.Module{
500                 {
501                         Name: "app",
502                         Files: map[string]interface{}{
503                                 "main.go": `
504 package main
505 import "lib"
506 func main() { print(lib.V) }
507 `,
508                         },
509                 },
510                 {
511                         Name: "lib",
512                         Files: map[string]interface{}{
513                                 "lib.go": `
514 package lib
515 type T struct{}
516 const C = 3
517 var V T
518 func (T) F() int { return C }
519 `,
520                         },
521                 },
522         })
523         goroot := filepath.Join(e.Temp(), "goroot")
524         if err := os.Mkdir(goroot, 0755); err != nil {
525                 t.Fatalf("os.Mkdir(%q) failed: %v", goroot, err)
526         }
527         defer e.Cleanup()
528
529         // Start the server.
530         addr := serverAddress(t)
531         cmd := exec.Command(bin, fmt.Sprintf("-http=%s", addr), "-analysis=type")
532         cmd.Dir = e.Config.Dir
533         // Point to an empty GOROOT directory to speed things up
534         // by not doing type analysis for the entire real GOROOT.
535         // TODO(golang.org/issue/34473): This test optimization may not be viable in module mode.
536         cmd.Env = append(e.Config.Env, fmt.Sprintf("GOROOT=%s", goroot))
537         cmd.Stdout = os.Stderr
538         stderr, err := cmd.StderrPipe()
539         if err != nil {
540                 t.Fatal(err)
541         }
542         cmd.Args[0] = "godoc"
543         if err := cmd.Start(); err != nil {
544                 t.Fatalf("failed to start godoc: %s", err)
545         }
546         defer killAndWait(cmd)
547         waitForServerReady(t, cmd, addr)
548
549         // Wait for type analysis to complete.
550         reader := bufio.NewReader(stderr)
551         for {
552                 s, err := reader.ReadString('\n') // on Plan 9 this fails
553                 if err != nil {
554                         t.Fatal(err)
555                 }
556                 fmt.Fprint(os.Stderr, s)
557                 if strings.Contains(s, "Type analysis complete.") {
558                         break
559                 }
560         }
561         go io.Copy(os.Stderr, reader)
562
563         t0 := time.Now()
564
565         // Make an HTTP request and check for a regular expression match.
566         // The patterns are very crude checks that basic type information
567         // has been annotated onto the source view.
568 tryagain:
569         for _, test := range []struct{ url, pattern string }{
570                 {"/src/lib/lib.go", "L2.*package .*Package docs for lib.*/lib"},
571                 {"/src/lib/lib.go", "L3.*type .*type info for T.*struct"},
572                 {"/src/lib/lib.go", "L5.*var V .*type T struct"},
573                 {"/src/lib/lib.go", "L6.*func .*type T struct.*T.*return .*const C untyped int.*C"},
574
575                 {"/src/app/main.go", "L2.*package .*Package docs for app"},
576                 {"/src/app/main.go", "L3.*import .*Package docs for lib.*lib"},
577                 {"/src/app/main.go", "L4.*func main.*package lib.*lib.*var lib.V lib.T.*V"},
578         } {
579                 url := fmt.Sprintf("http://%s%s", addr, test.url)
580                 resp, err := http.Get(url)
581                 if err != nil {
582                         t.Errorf("GET %s failed: %s", url, err)
583                         continue
584                 }
585                 body, err := ioutil.ReadAll(resp.Body)
586                 resp.Body.Close()
587                 if err != nil {
588                         t.Errorf("GET %s: failed to read body: %s (response: %v)", url, err, resp)
589                         continue
590                 }
591
592                 if !bytes.Contains(body, []byte("Static analysis features")) {
593                         // Type analysis results usually become available within
594                         // ~4ms after godoc startup (for this input on my machine).
595                         if elapsed := time.Since(t0); elapsed > 500*time.Millisecond {
596                                 t.Fatalf("type analysis results still unavailable after %s", elapsed)
597                         }
598                         time.Sleep(10 * time.Millisecond)
599                         goto tryagain
600                 }
601
602                 match, err := regexp.Match(test.pattern, body)
603                 if err != nil {
604                         t.Errorf("regexp.Match(%q) failed: %s", test.pattern, err)
605                         continue
606                 }
607                 if !match {
608                         // This is a really ugly failure message.
609                         t.Errorf("GET %s: body doesn't match %q, got:\n%s",
610                                 url, test.pattern, string(body))
611                 }
612         }
613 }