GoPLS Viewer

Home|gopls/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
5package main
6
7import (
8    "bytes"
9    "context"
10    "fmt"
11    "go/build"
12    "io/ioutil"
13    "net"
14    "net/http"
15    "os"
16    "os/exec"
17    "regexp"
18    "runtime"
19    "strings"
20    "sync"
21    "testing"
22    "time"
23
24    "golang.org/x/tools/go/packages/packagestest"
25    "golang.org/x/tools/internal/testenv"
26)
27
28func TestMain(m *testing.M) {
29    if os.Getenv("GODOC_TEST_IS_GODOC") != "" {
30        main()
31        os.Exit(0)
32    }
33
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")
38
39    os.Exit(m.Run())
40}
41
42var exe struct {
43    path string
44    err  error
45    once sync.Once
46}
47
48func godocPath(t *testing.Tstring {
49    switch runtime.GOOS {
50    case "js""ios":
51        t.Skipf("skipping test that requires exec")
52    }
53
54    exe.once.Do(func() {
55        exe.pathexe.err = os.Executable()
56    })
57    if exe.err != nil {
58        t.Fatal(exe.err)
59    }
60    return exe.path
61}
62
63func serverAddress(t *testing.Tstring {
64    lnerr := net.Listen("tcp""127.0.0.1:0")
65    if err != nil {
66        lnerr = net.Listen("tcp6""[::1]:0")
67    }
68    if err != nil {
69        t.Fatal(err)
70    }
71    defer ln.Close()
72    return ln.Addr().String()
73}
74
75func waitForServerReady(t *testing.Tctx context.Contextcmd *exec.Cmdaddr string) {
76    waitForServer(tctx,
77        fmt.Sprintf("http://%v/"addr),
78        "Go Documentation Server",
79        false)
80}
81
82func waitForSearchReady(t *testing.Tctx context.Contextcmd *exec.Cmdaddr string) {
83    waitForServer(tctx,
84        fmt.Sprintf("http://%v/search?q=FALLTHROUGH"addr),
85        "The list of tokens.",
86        false)
87}
88
89func waitUntilScanComplete(t *testing.Tctx context.Contextaddr string) {
90    waitForServer(tctx,
91        fmt.Sprintf("http://%v/pkg"addr),
92        "Scan is not yet complete",
93        // setting reverse as true, which means this waits
94        // until the string is not returned in the response anymore
95        true)
96}
97
98const pollInterval = 50 * time.Millisecond
99
100// waitForServer waits for server to meet the required condition,
101// failing the test if ctx is canceled before that occurs.
102func waitForServer(t *testing.Tctx context.Contexturlmatch stringreverse 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))
108        }
109
110        time.Sleep(pollInterval)
111        reserr := http.Get(url)
112        if err != nil {
113            continue
114        }
115        bodyerr := ioutil.ReadAll(res.Body)
116        res.Body.Close()
117        if err != nil || res.StatusCode != http.StatusOK {
118            continue
119        }
120        switch {
121        case !reverse && bytes.Contains(body, []byte(match)),
122            reverse && !bytes.Contains(body, []byte(match)):
123            return
124        }
125    }
126}
127
128// hasTag checks whether a given release tag is contained in the current version
129// of the go binary.
130func hasTag(t stringbool {
131    for _v := range build.Default.ReleaseTags {
132        if t == v {
133            return true
134        }
135    }
136    return false
137}
138
139func TestURL(t *testing.T) {
140    if runtime.GOOS == "plan9" {
141        t.Skip("skipping on plan9; fails to start up quickly enough")
142    }
143    bin := godocPath(t)
144
145    testcase := func(url stringcontents string) func(t *testing.T) {
146        return func(t *testing.T) {
147            stdoutstderr := new(bytes.Buffer), new(bytes.Buffer)
148
149            args := []string{fmt.Sprintf("-url=%s"url)}
150            cmd := testenv.Command(tbinargs...)
151            cmd.Stdout = stdout
152            cmd.Stderr = stderr
153            cmd.Args[0] = "godoc"
154
155            // Set GOPATH variable to a non-existing absolute path
156            // and GOPROXY=off to disable module fetches.
157            // We cannot just unset GOPATH variable because godoc would default it to ~/go.
158            // (We don't want the indexer looking at the local workspace during tests.)
159            cmd.Env = append(os.Environ(),
160                "GOPATH=/does_not_exist",
161                "GOPROXY=off",
162                "GO111MODULE=off")
163
164            if err := cmd.Run(); err != nil {
165                t.Fatalf("failed to run godoc -url=%q: %s\nstderr:\n%s"urlerrstderr)
166            }
167
168            if !strings.Contains(stdout.String(), contents) {
169                t.Errorf("did not find substring %q in output of godoc -url=%q:\n%s"contentsurlstdout)
170            }
171        }
172    }
173
174    t.Run("index"testcase("/""These packages are part of the Go Project but outside the main Go tree."))
175    t.Run("fmt"testcase("/pkg/fmt""Package fmt implements formatted I/O"))
176}
177
178// Basic integration test for godoc HTTP interface.
179func TestWeb(t *testing.T) {
180    bin := godocPath(t)
181
182    for _x := range packagestest.All {
183        t.Run(x.Name(), func(t *testing.T) {
184            testWeb(txbinfalse)
185        })
186    }
187}
188
189// Basic integration test for godoc HTTP interface.
190func TestWebIndex(t *testing.T) {
191    if testing.Short() {
192        t.Skip("skipping slow test in -short mode")
193    }
194    bin := godocPath(t)
195    testWeb(tpackagestest.GOPATHbintrue)
196}
197
198// Basic integration test for godoc HTTP interface.
199func testWeb(t *testing.Tx packagestest.Exporterbin stringwithIndex bool) {
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")
205    }
206
207    // Write a fake GOROOT/GOPATH with some third party packages.
208    e := packagestest.Export(tx, []packagestest.Module{
209        {
210            Name"godoc.test/repo1",
211            Files: map[string]interface{}{
212                "a/a.go"`// Package a is a package in godoc.test/repo1.
213package a; import _ "godoc.test/repo2/a"; const Name = "repo1a"`,
214                "b/b.go"`package b; const Name = "repo1b"`,
215            },
216        },
217        {
218            Name"godoc.test/repo2",
219            Files: map[string]interface{}{
220                "a/a.go"`package a; const Name = "repo2a"`,
221                "b/b.go"`package b; const Name = "repo2b"`,
222            },
223        },
224    })
225    defer e.Cleanup()
226
227    // Start the server.
228    addr := serverAddress(t)
229    args := []string{fmt.Sprintf("-http=%s"addr)}
230    if withIndex {
231        args = append(args"-index""-index_interval=-1s")
232    }
233    cmd := testenv.Command(tbinargs...)
234    cmd.Dir = e.Config.Dir
235    cmd.Env = e.Config.Env
236    cmdOut := new(strings.Builder)
237    cmd.Stdout = cmdOut
238    cmd.Stderr = cmdOut
239    cmd.Args[0] = "godoc"
240
241    if err := cmd.Start(); err != nil {
242        t.Fatalf("failed to start godoc: %s"err)
243    }
244    ctxcancel := context.WithCancel(context.Background())
245    go func() {
246        err := cmd.Wait()
247        t.Logf("%v: %v"cmderr)
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    }()
260
261    if withIndex {
262        waitForSearchReady(tctxcmdaddr)
263    } else {
264        waitForServerReady(tctxcmdaddr)
265        waitUntilScanComplete(tctxaddr)
266    }
267
268    tests := []struct {
269        path        string
270        contains    []string // substring
271        match       []string // regexp
272        notContains []string
273        needIndex   bool
274        releaseTag  string // optional release tag that must be in go/build.ReleaseTags
275    }{
276        {
277            path"/",
278            contains: []string{
279                "Go Documentation Server",
280                "Standard library",
281                "These packages are part of the Go Project but outside the main Go tree.",
282            },
283        },
284        {
285            path:     "/pkg/fmt/",
286            contains: []string{"Package fmt implements formatted I/O"},
287        },
288        {
289            path:     "/src/fmt/",
290            contains: []string{"scan_test.go"},
291        },
292        {
293            path:     "/src/fmt/print.go",
294            contains: []string{"// Println formats using"},
295        },
296        {
297            path"/pkg",
298            contains: []string{
299                "Standard library",
300                "Package fmt implements formatted I/O",
301                "Third party",
302                "Package a is a package in godoc.test/repo1.",
303            },
304            notContains: []string{
305                "internal/syscall",
306                "cmd/gc",
307            },
308        },
309        {
310            path"/pkg/?m=all",
311            contains: []string{
312                "Standard library",
313                "Package fmt implements formatted I/O",
314                "internal/syscall/?m=all",
315            },
316            notContains: []string{
317                "cmd/gc",
318            },
319        },
320        {
321            path"/search?q=ListenAndServe",
322            contains: []string{
323                "/src",
324            },
325            notContains: []string{
326                "/pkg/bootstrap",
327            },
328            needIndextrue,
329        },
330        {
331            path"/pkg/strings/",
332            contains: []string{
333                `href="/src/strings/strings.go"`,
334            },
335        },
336        {
337            path"/cmd/compile/internal/amd64/",
338            contains: []string{
339                `href="/src/cmd/compile/internal/amd64/ssa.go"`,
340            },
341        },
342        {
343            path"/pkg/math/bits/",
344            contains: []string{
345                `Added in Go 1.9`,
346            },
347        },
348        {
349            path"/pkg/net/",
350            contains: []string{
351                `// IPv6 scoped addressing zone; added in Go 1.1`,
352            },
353        },
354        {
355            path"/pkg/net/http/httptrace/",
356            match: []string{
357                `Got1xxResponse.*// Go 1\.11`,
358            },
359            releaseTag"go1.11",
360        },
361        // Verify we don't add version info to a struct field added the same time
362        // as the struct itself:
363        {
364            path"/pkg/net/http/httptrace/",
365            match: []string{
366                `(?m)GotFirstResponseByte func\(\)\s*$`,
367            },
368        },
369        // Remove trailing periods before adding semicolons:
370        {
371            path"/pkg/database/sql/",
372            contains: []string{
373                "The number of connections currently in use; added in Go 1.11",
374                "The number of idle connections; added in Go 1.11",
375            },
376            releaseTag"go1.11",
377        },
378
379        // Third party packages.
380        {
381            path:     "/pkg/godoc.test/repo1/a",
382            contains: []string{`const <span id="Name">Name</span> = &#34;repo1a&#34;`},
383        },
384        {
385            path:     "/pkg/godoc.test/repo2/b",
386            contains: []string{`const <span id="Name">Name</span> = &#34;repo2b&#34;`},
387        },
388    }
389    for _test := range tests {
390        if test.needIndex && !withIndex {
391            continue
392        }
393        url := fmt.Sprintf("http://%s%s"addrtest.path)
394        resperr := http.Get(url)
395        if err != nil {
396            t.Errorf("GET %s failed: %s"urlerr)
397            continue
398        }
399        bodyerr := ioutil.ReadAll(resp.Body)
400        strBody := string(body)
401        resp.Body.Close()
402        if err != nil {
403            t.Errorf("GET %s: failed to read body: %s (response: %v)"urlerrresp)
404        }
405        isErr := false
406        for _substr := range test.contains {
407            if test.releaseTag != "" && !hasTag(test.releaseTag) {
408                continue
409            }
410            if !bytes.Contains(body, []byte(substr)) {
411                t.Errorf("GET %s: wanted substring %q in body"urlsubstr)
412                isErr = true
413            }
414        }
415        for _re := range test.match {
416            if test.releaseTag != "" && !hasTag(test.releaseTag) {
417                continue
418            }
419            if okerr := regexp.MatchString(restrBody); !ok || err != nil {
420                if err != nil {
421                    t.Fatalf("Bad regexp %q: %v"reerr)
422                }
423                t.Errorf("GET %s: wanted to match %s in body"urlre)
424                isErr = true
425            }
426        }
427        for _substr := range test.notContains {
428            if bytes.Contains(body, []byte(substr)) {
429                t.Errorf("GET %s: didn't want substring %q in body"urlsubstr)
430                isErr = true
431            }
432        }
433        if isErr {
434            t.Errorf("GET %s: got:\n%s"urlbody)
435        }
436    }
437}
438
439// Test for golang.org/issue/35476.
440func TestNoMainModule(t *testing.T) {
441    if testing.Short() {
442        t.Skip("skipping test in -short mode")
443    }
444    if runtime.GOOS == "plan9" {
445        t.Skip("skipping on plan9; for consistency with other tests that build godoc binary")
446    }
447    bin := godocPath(t)
448    tempDir := t.TempDir()
449
450    // Run godoc in an empty directory with module mode explicitly on,
451    // so that 'go env GOMOD' reports os.DevNull.
452    cmd := testenv.Command(tbin"-url=/")
453    cmd.Dir = tempDir
454    cmd.Env = append(os.Environ(), "GO111MODULE=on")
455    var stderr bytes.Buffer
456    cmd.Stderr = &stderr
457    err := cmd.Run()
458    if err != nil {
459        t.Fatalf("godoc command failed: %v\nstderr=%q"errstderr.String())
460    }
461    if strings.Contains(stderr.String(), "go mod download") {
462        t.Errorf("stderr contains 'go mod download', is that intentional?\nstderr=%q"stderr.String())
463    }
464}
465
MembersX
waitForServer.url
TestURL.BlockStmt.BlockStmt.stderr
testWeb.t
runtime
waitForSearchReady.ctx
waitForSearchReady.addr
waitUntilScanComplete
waitUntilScanComplete.addr
waitForServer.t
waitForServer.reverse
TestWebIndex.t
testWeb.RangeStmt_9368.BlockStmt.RangeStmt_9814.substr
testWeb.RangeStmt_9368.BlockStmt.RangeStmt_10404.substr
time
godocPath.t
serverAddress
waitForServer.BlockStmt.res
TestWeb.t
testWeb.BlockStmt.err
testWeb.RangeStmt_9368.BlockStmt.body
bytes
build
exec
testenv
TestMain
godocPath
TestURL.BlockStmt.BlockStmt.args
TestURL.BlockStmt.BlockStmt.cmd
testWeb.cmd
testWeb.RangeStmt_9368.BlockStmt.strBody
testWeb.RangeStmt_9368.BlockStmt.RangeStmt_10068.re
fmt
TestURL.t
TestNoMainModule.stderr
sync
waitUntilScanComplete.t
waitForServer.match
TestWeb.RangeStmt_4449.x
ioutil
testing
waitForServerReady.cmd
waitForSearchReady.t
testWeb.e
testWeb.cancel
testWeb.tests
testWeb.RangeStmt_9368.BlockStmt.url
testWeb.RangeStmt_9368.BlockStmt.err
testWeb.RangeStmt_9368.BlockStmt.RangeStmt_10068.BlockStmt.err
TestNoMainModule
TestNoMainModule.tempDir
waitForServerReady.addr
hasTag
hasTag.RangeStmt_2931.v
testWeb.withIndex
TestNoMainModule.cmd
os
regexp
serverAddress.t
waitForSearchReady
waitForServer.BlockStmt.err
TestURL.BlockStmt.BlockStmt.err
testWeb.x
testWeb.err
context
serverAddress.ln
waitForSearchReady.cmd
waitForServer.ctx
TestURL.bin
testWeb.ctx
testWeb.RangeStmt_9368.BlockStmt.isErr
waitForServer.start
waitForServer.BlockStmt.body
testWeb.bin
net
waitForServerReady
hasTag.t
TestWeb
TestWebIndex.bin
testWeb.RangeStmt_9368.test
TestMain.m
serverAddress.err
waitForServerReady.t
waitForServerReady.ctx
TestWeb.bin
testWeb
http
waitUntilScanComplete.ctx
TestURL
TestNoMainModule.err
strings
waitForServer
testWeb.RangeStmt_9368.BlockStmt.resp
TestNoMainModule.t
TestNoMainModule.bin
packagestest
TestURL.BlockStmt.BlockStmt.stdout
TestWebIndex
testWeb.addr
testWeb.args
testWeb.cmdOut
testWeb.RangeStmt_9368.BlockStmt.RangeStmt_10068.BlockStmt.ok
Members
X