2013-07-17 01:32:45 -06:00
|
|
|
// Copyright 2013 The Go Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package main_test
|
|
|
|
|
|
|
|
import (
|
2014-10-27 19:25:46 -06:00
|
|
|
"bufio"
|
2014-03-16 14:17:13 -06:00
|
|
|
"bytes"
|
|
|
|
"fmt"
|
2018-07-25 01:31:31 -06:00
|
|
|
"go/build"
|
2014-10-27 19:25:46 -06:00
|
|
|
"io"
|
2013-07-17 01:32:45 -06:00
|
|
|
"io/ioutil"
|
2014-03-16 14:17:13 -06:00
|
|
|
"net"
|
|
|
|
"net/http"
|
2013-07-17 01:32:45 -06:00
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"path/filepath"
|
|
|
|
"regexp"
|
2013-07-18 18:31:12 -06:00
|
|
|
"runtime"
|
2014-02-26 11:21:44 -07:00
|
|
|
"strings"
|
2013-07-17 01:32:45 -06:00
|
|
|
"testing"
|
2014-03-16 14:17:13 -06:00
|
|
|
"time"
|
2013-07-17 01:32:45 -06:00
|
|
|
)
|
|
|
|
|
2014-03-16 14:17:13 -06:00
|
|
|
// buildGodoc builds the godoc executable.
|
|
|
|
// It returns its path, and a cleanup function.
|
|
|
|
//
|
|
|
|
// TODO(adonovan): opt: do this at most once, and do the cleanup
|
|
|
|
// exactly once. How though? There's no atexit.
|
|
|
|
func buildGodoc(t *testing.T) (bin string, cleanup func()) {
|
2015-03-12 23:21:50 -06:00
|
|
|
if runtime.GOARCH == "arm" {
|
|
|
|
t.Skip("skipping test on arm platforms; too slow")
|
|
|
|
}
|
2013-07-17 01:32:45 -06:00
|
|
|
tmp, err := ioutil.TempDir("", "godoc-regtest-")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2015-01-15 11:16:57 -07:00
|
|
|
defer func() {
|
|
|
|
if cleanup == nil { // probably, go build failed.
|
|
|
|
os.RemoveAll(tmp)
|
|
|
|
}
|
|
|
|
}()
|
2013-07-17 01:32:45 -06:00
|
|
|
|
2014-03-16 14:17:13 -06:00
|
|
|
bin = filepath.Join(tmp, "godoc")
|
2013-07-18 18:31:12 -06:00
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
bin += ".exe"
|
|
|
|
}
|
2013-07-17 01:32:45 -06:00
|
|
|
cmd := exec.Command("go", "build", "-o", bin)
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
|
|
t.Fatalf("Building godoc: %v", err)
|
|
|
|
}
|
|
|
|
|
2014-03-16 14:17:13 -06:00
|
|
|
return bin, func() { os.RemoveAll(tmp) }
|
|
|
|
}
|
|
|
|
|
2017-04-25 22:16:31 -06:00
|
|
|
var isGo19 bool // godoc19_test.go sets it to true.
|
|
|
|
|
2014-03-16 14:17:13 -06:00
|
|
|
// Basic regression test for godoc command-line tool.
|
|
|
|
func TestCLI(t *testing.T) {
|
|
|
|
bin, cleanup := buildGodoc(t)
|
|
|
|
defer cleanup()
|
2017-04-25 22:16:31 -06:00
|
|
|
|
|
|
|
// condStr returns s if cond is true, otherwise empty string.
|
|
|
|
condStr := func(cond bool, s string) string {
|
|
|
|
if !cond {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
args []string
|
|
|
|
matches []string // regular expressions
|
|
|
|
dontmatch []string // regular expressions
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
args: []string{"fmt"},
|
|
|
|
matches: []string{
|
|
|
|
`import "fmt"`,
|
|
|
|
`Package fmt implements formatted I/O`,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
args: []string{"io", "WriteString"},
|
|
|
|
matches: []string{
|
|
|
|
`func WriteString\(`,
|
|
|
|
`WriteString writes the contents of the string s to w`,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
args: []string{"nonexistingpkg"},
|
|
|
|
matches: []string{
|
|
|
|
`cannot find package` +
|
|
|
|
// TODO: Remove this when support for Go 1.8 is dropped.
|
|
|
|
condStr(!isGo19,
|
|
|
|
// For Go 1.8 and older, because it doesn't have CL 33158 change applied to go/build.
|
|
|
|
// The last pattern (does not e) is for plan9:
|
|
|
|
// http://build.golang.org/log/2d8e5e14ed365bfa434b37ec0338cd9e6f8dd9bf
|
|
|
|
`|no such file or directory|does not exist|cannot find the file|(?:' does not e)`),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
args: []string{"fmt", "NonexistentSymbol"},
|
|
|
|
matches: []string{
|
|
|
|
`No match found\.`,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
args: []string{"-src", "syscall", "Open"},
|
|
|
|
matches: []string{
|
|
|
|
`func Open\(`,
|
|
|
|
},
|
|
|
|
dontmatch: []string{
|
|
|
|
`No match found\.`,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, test := range tests {
|
2013-07-17 01:32:45 -06:00
|
|
|
cmd := exec.Command(bin, test.args...)
|
|
|
|
cmd.Args[0] = "godoc"
|
|
|
|
out, err := cmd.CombinedOutput()
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Running with args %#v: %v", test.args, err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
for _, pat := range test.matches {
|
|
|
|
re := regexp.MustCompile(pat)
|
|
|
|
if !re.Match(out) {
|
2014-02-26 11:21:44 -07:00
|
|
|
t.Errorf("godoc %v =\n%s\nwanted /%v/", strings.Join(test.args, " "), out, pat)
|
2013-07-17 01:32:45 -06:00
|
|
|
}
|
|
|
|
}
|
2014-03-14 08:00:10 -06:00
|
|
|
for _, pat := range test.dontmatch {
|
|
|
|
re := regexp.MustCompile(pat)
|
|
|
|
if re.Match(out) {
|
|
|
|
t.Errorf("godoc %v =\n%s\ndid not want /%v/", strings.Join(test.args, " "), out, pat)
|
|
|
|
}
|
|
|
|
}
|
2013-07-17 01:32:45 -06:00
|
|
|
}
|
|
|
|
}
|
2014-03-16 14:17:13 -06:00
|
|
|
|
|
|
|
func serverAddress(t *testing.T) string {
|
|
|
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
|
|
|
if err != nil {
|
|
|
|
ln, err = net.Listen("tcp6", "[::1]:0")
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer ln.Close()
|
|
|
|
return ln.Addr().String()
|
|
|
|
}
|
|
|
|
|
2015-07-30 23:02:02 -06:00
|
|
|
func waitForServerReady(t *testing.T, addr string) {
|
|
|
|
waitForServer(t,
|
|
|
|
fmt.Sprintf("http://%v/", addr),
|
|
|
|
"The Go Programming Language",
|
2018-02-09 21:29:54 -07:00
|
|
|
15*time.Second,
|
|
|
|
false)
|
2015-07-30 23:02:02 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
func waitForSearchReady(t *testing.T, addr string) {
|
|
|
|
waitForServer(t,
|
|
|
|
fmt.Sprintf("http://%v/search?q=FALLTHROUGH", addr),
|
|
|
|
"The list of tokens.",
|
2018-02-09 21:29:54 -07:00
|
|
|
2*time.Minute,
|
|
|
|
false)
|
|
|
|
}
|
|
|
|
|
|
|
|
func waitUntilScanComplete(t *testing.T, addr string) {
|
|
|
|
waitForServer(t,
|
|
|
|
fmt.Sprintf("http://%v/pkg", addr),
|
|
|
|
"Scan is not yet complete",
|
|
|
|
2*time.Minute,
|
|
|
|
true,
|
|
|
|
)
|
|
|
|
// setting reverse as true, which means this waits
|
|
|
|
// until the string is not returned in the response anymore
|
2015-07-30 23:02:02 -06:00
|
|
|
}
|
2015-03-10 05:29:27 -06:00
|
|
|
|
2015-07-30 23:02:02 -06:00
|
|
|
const pollInterval = 200 * time.Millisecond
|
2015-03-10 05:29:27 -06:00
|
|
|
|
2018-02-09 21:29:54 -07:00
|
|
|
func waitForServer(t *testing.T, url, match string, timeout time.Duration, reverse bool) {
|
2015-03-10 05:29:27 -06:00
|
|
|
// "health check" duplicated from x/tools/cmd/tipgodoc/tip.go
|
2015-07-30 23:02:02 -06:00
|
|
|
deadline := time.Now().Add(timeout)
|
2015-03-10 05:29:27 -06:00
|
|
|
for time.Now().Before(deadline) {
|
|
|
|
time.Sleep(pollInterval)
|
2015-07-30 23:02:02 -06:00
|
|
|
res, err := http.Get(url)
|
2014-03-16 14:17:13 -06:00
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
2015-03-10 05:29:27 -06:00
|
|
|
rbody, err := ioutil.ReadAll(res.Body)
|
|
|
|
res.Body.Close()
|
2018-02-09 21:29:54 -07:00
|
|
|
if err == nil && res.StatusCode == http.StatusOK {
|
|
|
|
if bytes.Contains(rbody, []byte(match)) && !reverse {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if !bytes.Contains(rbody, []byte(match)) && reverse {
|
|
|
|
return
|
|
|
|
}
|
2015-03-10 05:29:27 -06:00
|
|
|
}
|
2014-03-16 14:17:13 -06:00
|
|
|
}
|
2015-07-30 23:02:02 -06:00
|
|
|
t.Fatalf("Server failed to respond in %v", timeout)
|
2014-03-16 14:17:13 -06:00
|
|
|
}
|
|
|
|
|
2018-07-25 01:31:31 -06:00
|
|
|
// hasTag checks whether a given release tag is contained in the current version
|
|
|
|
// of the go binary.
|
|
|
|
func hasTag(t string) bool {
|
|
|
|
for _, v := range build.Default.ReleaseTags {
|
|
|
|
if t == v {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2014-10-27 23:59:58 -06:00
|
|
|
func killAndWait(cmd *exec.Cmd) {
|
|
|
|
cmd.Process.Kill()
|
|
|
|
cmd.Wait()
|
|
|
|
}
|
|
|
|
|
2014-03-16 14:17:13 -06:00
|
|
|
// Basic integration test for godoc HTTP interface.
|
|
|
|
func TestWeb(t *testing.T) {
|
2015-07-30 23:02:02 -06:00
|
|
|
testWeb(t, false)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Basic integration test for godoc HTTP interface.
|
|
|
|
func TestWebIndex(t *testing.T) {
|
|
|
|
if testing.Short() {
|
|
|
|
t.Skip("skipping test in -short mode")
|
|
|
|
}
|
|
|
|
testWeb(t, true)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Basic integration test for godoc HTTP interface.
|
|
|
|
func testWeb(t *testing.T, withIndex bool) {
|
2017-11-29 12:51:57 -07:00
|
|
|
if runtime.GOOS == "plan9" {
|
|
|
|
t.Skip("skipping on plan9; files to start up quickly enough")
|
|
|
|
}
|
2014-03-16 14:17:13 -06:00
|
|
|
bin, cleanup := buildGodoc(t)
|
|
|
|
defer cleanup()
|
|
|
|
addr := serverAddress(t)
|
2015-07-30 23:02:02 -06:00
|
|
|
args := []string{fmt.Sprintf("-http=%s", addr)}
|
|
|
|
if withIndex {
|
|
|
|
args = append(args, "-index", "-index_interval=-1s")
|
|
|
|
}
|
|
|
|
cmd := exec.Command(bin, args...)
|
2014-03-16 14:17:13 -06:00
|
|
|
cmd.Stdout = os.Stderr
|
|
|
|
cmd.Stderr = os.Stderr
|
|
|
|
cmd.Args[0] = "godoc"
|
2018-03-23 05:59:17 -06:00
|
|
|
|
|
|
|
// Set GOPATH variable to non-existing path.
|
|
|
|
// We cannot just unset GOPATH variable because godoc would default it to ~/go.
|
|
|
|
// (We don't want the indexer looking at the local workspace during tests.)
|
|
|
|
cmd.Env = append(os.Environ(), "GOPATH=does_not_exist")
|
|
|
|
|
2014-03-16 14:17:13 -06:00
|
|
|
if err := cmd.Start(); err != nil {
|
|
|
|
t.Fatalf("failed to start godoc: %s", err)
|
|
|
|
}
|
2014-10-27 23:59:58 -06:00
|
|
|
defer killAndWait(cmd)
|
2015-07-30 23:02:02 -06:00
|
|
|
|
|
|
|
if withIndex {
|
|
|
|
waitForSearchReady(t, addr)
|
|
|
|
} else {
|
|
|
|
waitForServerReady(t, addr)
|
2018-02-09 21:29:54 -07:00
|
|
|
waitUntilScanComplete(t, addr)
|
2015-07-30 23:02:02 -06:00
|
|
|
}
|
|
|
|
|
2014-10-13 10:47:02 -06:00
|
|
|
tests := []struct {
|
2018-07-17 15:23:18 -06:00
|
|
|
path string
|
|
|
|
contains []string // substring
|
|
|
|
match []string // regexp
|
|
|
|
notContains []string
|
|
|
|
needIndex bool
|
2018-07-25 01:31:31 -06:00
|
|
|
releaseTag string // optional release tag that must be in go/build.ReleaseTags
|
2014-10-13 10:47:02 -06:00
|
|
|
}{
|
|
|
|
{
|
2018-07-17 15:23:18 -06:00
|
|
|
path: "/",
|
|
|
|
contains: []string{"Go is an open source programming language"},
|
2014-10-13 10:47:02 -06:00
|
|
|
},
|
|
|
|
{
|
2018-07-17 15:23:18 -06:00
|
|
|
path: "/pkg/fmt/",
|
|
|
|
contains: []string{"Package fmt implements formatted I/O"},
|
2014-10-13 10:47:02 -06:00
|
|
|
},
|
|
|
|
{
|
2018-07-17 15:23:18 -06:00
|
|
|
path: "/src/fmt/",
|
|
|
|
contains: []string{"scan_test.go"},
|
2014-10-13 10:47:02 -06:00
|
|
|
},
|
|
|
|
{
|
2018-07-17 15:23:18 -06:00
|
|
|
path: "/src/fmt/print.go",
|
|
|
|
contains: []string{"// Println formats using"},
|
2014-10-13 10:47:02 -06:00
|
|
|
},
|
|
|
|
{
|
|
|
|
path: "/pkg",
|
2018-07-17 15:23:18 -06:00
|
|
|
contains: []string{
|
2014-10-13 10:47:02 -06:00
|
|
|
"Standard library",
|
|
|
|
"Package fmt implements formatted I/O",
|
|
|
|
},
|
2018-07-17 15:23:18 -06:00
|
|
|
notContains: []string{
|
2014-10-13 10:47:02 -06:00
|
|
|
"internal/syscall",
|
|
|
|
"cmd/gc",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
path: "/pkg/?m=all",
|
2018-07-17 15:23:18 -06:00
|
|
|
contains: []string{
|
2014-10-13 10:47:02 -06:00
|
|
|
"Standard library",
|
|
|
|
"Package fmt implements formatted I/O",
|
2017-01-18 11:52:36 -07:00
|
|
|
"internal/syscall/?m=all",
|
2014-10-13 10:47:02 -06:00
|
|
|
},
|
2018-07-17 15:23:18 -06:00
|
|
|
notContains: []string{
|
2014-10-13 10:47:02 -06:00
|
|
|
"cmd/gc",
|
|
|
|
},
|
|
|
|
},
|
2015-03-10 05:29:27 -06:00
|
|
|
{
|
2016-11-03 12:58:50 -06:00
|
|
|
path: "/search?q=ListenAndServe",
|
2018-07-17 15:23:18 -06:00
|
|
|
contains: []string{
|
2015-03-10 05:29:27 -06:00
|
|
|
"/src",
|
|
|
|
},
|
2018-07-17 15:23:18 -06:00
|
|
|
notContains: []string{
|
2015-03-10 05:29:27 -06:00
|
|
|
"/pkg/bootstrap",
|
|
|
|
},
|
2015-07-30 23:02:02 -06:00
|
|
|
needIndex: true,
|
2015-03-10 05:29:27 -06:00
|
|
|
},
|
2015-08-27 19:00:04 -06:00
|
|
|
{
|
|
|
|
path: "/pkg/strings/",
|
2018-07-17 15:23:18 -06:00
|
|
|
contains: []string{
|
2015-08-27 19:00:04 -06:00
|
|
|
`href="/src/strings/strings.go"`,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
path: "/cmd/compile/internal/amd64/",
|
2018-07-17 15:23:18 -06:00
|
|
|
contains: []string{
|
2017-03-21 16:45:56 -06:00
|
|
|
`href="/src/cmd/compile/internal/amd64/ssa.go"`,
|
2015-08-27 19:00:04 -06:00
|
|
|
},
|
|
|
|
},
|
2017-12-22 18:30:39 -07:00
|
|
|
{
|
|
|
|
path: "/pkg/math/bits/",
|
2018-07-17 15:23:18 -06:00
|
|
|
contains: []string{
|
2017-12-22 18:30:39 -07:00
|
|
|
`Added in Go 1.9`,
|
|
|
|
},
|
|
|
|
},
|
2018-07-17 15:23:18 -06:00
|
|
|
{
|
|
|
|
path: "/pkg/net/",
|
|
|
|
contains: []string{
|
|
|
|
`// IPv6 scoped addressing zone; added in Go 1.1`,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
path: "/pkg/net/http/httptrace/",
|
|
|
|
match: []string{
|
|
|
|
`Got1xxResponse.*// Go 1\.11`,
|
|
|
|
},
|
2018-07-25 01:31:31 -06:00
|
|
|
releaseTag: "go1.11",
|
2018-07-17 15:23:18 -06:00
|
|
|
},
|
|
|
|
// Verify we don't add version info to a struct field added the same time
|
|
|
|
// as the struct itself:
|
|
|
|
{
|
|
|
|
path: "/pkg/net/http/httptrace/",
|
|
|
|
match: []string{
|
|
|
|
`(?m)GotFirstResponseByte func\(\)\s*$`,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// Remove trailing periods before adding semicolons:
|
|
|
|
{
|
|
|
|
path: "/pkg/database/sql/",
|
|
|
|
contains: []string{
|
|
|
|
"The number of connections currently in use; added in Go 1.11",
|
|
|
|
"The number of idle connections; added in Go 1.11",
|
|
|
|
},
|
2018-07-25 01:31:31 -06:00
|
|
|
releaseTag: "go1.11",
|
2018-07-17 15:23:18 -06:00
|
|
|
},
|
2014-03-16 14:17:13 -06:00
|
|
|
}
|
|
|
|
for _, test := range tests {
|
2015-07-30 23:02:02 -06:00
|
|
|
if test.needIndex && !withIndex {
|
|
|
|
continue
|
|
|
|
}
|
2014-03-16 14:17:13 -06:00
|
|
|
url := fmt.Sprintf("http://%s%s", addr, test.path)
|
|
|
|
resp, err := http.Get(url)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("GET %s failed: %s", url, err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
body, err := ioutil.ReadAll(resp.Body)
|
2018-07-17 15:23:18 -06:00
|
|
|
strBody := string(body)
|
2014-03-16 14:17:13 -06:00
|
|
|
resp.Body.Close()
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("GET %s: failed to read body: %s (response: %v)", url, err, resp)
|
|
|
|
}
|
2014-10-13 10:47:02 -06:00
|
|
|
isErr := false
|
2018-07-17 15:23:18 -06:00
|
|
|
for _, substr := range test.contains {
|
2018-07-25 01:31:31 -06:00
|
|
|
if test.releaseTag != "" && !hasTag(test.releaseTag) {
|
|
|
|
continue
|
|
|
|
}
|
2014-10-13 10:47:02 -06:00
|
|
|
if !bytes.Contains(body, []byte(substr)) {
|
|
|
|
t.Errorf("GET %s: wanted substring %q in body", url, substr)
|
|
|
|
isErr = true
|
|
|
|
}
|
|
|
|
}
|
2018-07-17 15:23:18 -06:00
|
|
|
for _, re := range test.match {
|
2018-07-25 01:31:31 -06:00
|
|
|
if test.releaseTag != "" && !hasTag(test.releaseTag) {
|
|
|
|
continue
|
|
|
|
}
|
2018-07-17 15:23:18 -06:00
|
|
|
if ok, err := regexp.MatchString(re, strBody); !ok || err != nil {
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Bad regexp %q: %v", re, err)
|
|
|
|
}
|
|
|
|
t.Errorf("GET %s: wanted to match %s in body", url, re)
|
|
|
|
isErr = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, substr := range test.notContains {
|
2014-10-13 10:47:02 -06:00
|
|
|
if bytes.Contains(body, []byte(substr)) {
|
|
|
|
t.Errorf("GET %s: didn't want substring %q in body", url, substr)
|
|
|
|
isErr = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if isErr {
|
|
|
|
t.Errorf("GET %s: got:\n%s", url, body)
|
2014-03-16 14:17:13 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-04-17 13:51:27 -06:00
|
|
|
|
|
|
|
// Basic integration test for godoc -analysis=type (via HTTP interface).
|
|
|
|
func TestTypeAnalysis(t *testing.T) {
|
2015-07-31 16:26:00 -06:00
|
|
|
if runtime.GOOS == "plan9" {
|
|
|
|
t.Skip("skipping test on plan9 (issue #11974)") // see comment re: Plan 9 below
|
|
|
|
}
|
|
|
|
|
2014-04-17 13:51:27 -06:00
|
|
|
// Write a fake GOROOT/GOPATH.
|
|
|
|
tmpdir, err := ioutil.TempDir("", "godoc-analysis")
|
|
|
|
if err != nil {
|
2014-05-19 09:47:28 -06:00
|
|
|
t.Fatalf("ioutil.TempDir failed: %s", err)
|
2014-04-17 13:51:27 -06:00
|
|
|
}
|
|
|
|
defer os.RemoveAll(tmpdir)
|
|
|
|
for _, f := range []struct{ file, content string }{
|
2014-09-10 07:02:54 -06:00
|
|
|
{"goroot/src/lib/lib.go", `
|
2014-04-17 13:51:27 -06:00
|
|
|
package lib
|
|
|
|
type T struct{}
|
|
|
|
const C = 3
|
|
|
|
var V T
|
|
|
|
func (T) F() int { return C }
|
|
|
|
`},
|
|
|
|
{"gopath/src/app/main.go", `
|
|
|
|
package main
|
|
|
|
import "lib"
|
|
|
|
func main() { print(lib.V) }
|
|
|
|
`},
|
|
|
|
} {
|
|
|
|
file := filepath.Join(tmpdir, f.file)
|
|
|
|
if err := os.MkdirAll(filepath.Dir(file), 0755); err != nil {
|
|
|
|
t.Fatalf("MkdirAll(%s) failed: %s", filepath.Dir(file), err)
|
|
|
|
}
|
|
|
|
if err := ioutil.WriteFile(file, []byte(f.content), 0644); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start the server.
|
|
|
|
bin, cleanup := buildGodoc(t)
|
|
|
|
defer cleanup()
|
|
|
|
addr := serverAddress(t)
|
|
|
|
cmd := exec.Command(bin, fmt.Sprintf("-http=%s", addr), "-analysis=type")
|
2018-03-23 05:59:17 -06:00
|
|
|
cmd.Env = os.Environ()
|
2014-05-13 20:39:20 -06:00
|
|
|
cmd.Env = append(cmd.Env, fmt.Sprintf("GOROOT=%s", filepath.Join(tmpdir, "goroot")))
|
|
|
|
cmd.Env = append(cmd.Env, fmt.Sprintf("GOPATH=%s", filepath.Join(tmpdir, "gopath")))
|
2014-04-17 13:51:27 -06:00
|
|
|
cmd.Stdout = os.Stderr
|
2014-10-27 19:25:46 -06:00
|
|
|
stderr, err := cmd.StderrPipe()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2014-04-17 13:51:27 -06:00
|
|
|
cmd.Args[0] = "godoc"
|
|
|
|
if err := cmd.Start(); err != nil {
|
|
|
|
t.Fatalf("failed to start godoc: %s", err)
|
|
|
|
}
|
2014-10-27 23:59:58 -06:00
|
|
|
defer killAndWait(cmd)
|
2015-07-30 23:02:02 -06:00
|
|
|
waitForServerReady(t, addr)
|
2014-04-17 13:51:27 -06:00
|
|
|
|
2014-10-27 19:25:46 -06:00
|
|
|
// Wait for type analysis to complete.
|
|
|
|
reader := bufio.NewReader(stderr)
|
|
|
|
for {
|
2015-07-31 16:26:00 -06:00
|
|
|
s, err := reader.ReadString('\n') // on Plan 9 this fails
|
2014-10-27 19:25:46 -06:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
fmt.Fprint(os.Stderr, s)
|
|
|
|
if strings.Contains(s, "Type analysis complete.") {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
go io.Copy(os.Stderr, reader)
|
|
|
|
|
2014-04-21 15:56:06 -06:00
|
|
|
t0 := time.Now()
|
|
|
|
|
2014-04-17 13:51:27 -06:00
|
|
|
// Make an HTTP request and check for a regular expression match.
|
|
|
|
// The patterns are very crude checks that basic type information
|
|
|
|
// has been annotated onto the source view.
|
2014-04-21 15:56:06 -06:00
|
|
|
tryagain:
|
2014-04-17 13:51:27 -06:00
|
|
|
for _, test := range []struct{ url, pattern string }{
|
2014-09-10 07:02:54 -06:00
|
|
|
{"/src/lib/lib.go", "L2.*package .*Package docs for lib.*/lib"},
|
|
|
|
{"/src/lib/lib.go", "L3.*type .*type info for T.*struct"},
|
|
|
|
{"/src/lib/lib.go", "L5.*var V .*type T struct"},
|
|
|
|
{"/src/lib/lib.go", "L6.*func .*type T struct.*T.*return .*const C untyped int.*C"},
|
2014-04-17 13:51:27 -06:00
|
|
|
|
2014-09-10 07:02:54 -06:00
|
|
|
{"/src/app/main.go", "L2.*package .*Package docs for app"},
|
|
|
|
{"/src/app/main.go", "L3.*import .*Package docs for lib.*lib"},
|
|
|
|
{"/src/app/main.go", "L4.*func main.*package lib.*lib.*var lib.V lib.T.*V"},
|
2014-04-17 13:51:27 -06:00
|
|
|
} {
|
|
|
|
url := fmt.Sprintf("http://%s%s", addr, test.url)
|
|
|
|
resp, err := http.Get(url)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("GET %s failed: %s", url, err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
|
|
resp.Body.Close()
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("GET %s: failed to read body: %s (response: %v)", url, err, resp)
|
|
|
|
continue
|
|
|
|
}
|
2014-04-21 15:56:06 -06:00
|
|
|
|
|
|
|
if !bytes.Contains(body, []byte("Static analysis features")) {
|
|
|
|
// Type analysis results usually become available within
|
|
|
|
// ~4ms after godoc startup (for this input on my machine).
|
|
|
|
if elapsed := time.Since(t0); elapsed > 500*time.Millisecond {
|
|
|
|
t.Fatalf("type analysis results still unavailable after %s", elapsed)
|
|
|
|
}
|
|
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
goto tryagain
|
|
|
|
}
|
|
|
|
|
2014-04-17 13:51:27 -06:00
|
|
|
match, err := regexp.Match(test.pattern, body)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("regexp.Match(%q) failed: %s", test.pattern, err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if !match {
|
|
|
|
// This is a really ugly failure message.
|
|
|
|
t.Errorf("GET %s: body doesn't match %q, got:\n%s",
|
|
|
|
url, test.pattern, string(body))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|