diff --git a/cmd/godoc/godoc_test.go b/cmd/godoc/godoc_test.go index 4eec5894ea..b75c07174a 100644 --- a/cmd/godoc/godoc_test.go +++ b/cmd/godoc/godoc_test.go @@ -5,7 +5,11 @@ package main_test import ( + "bytes" + "fmt" "io/ioutil" + "net" + "net/http" "os" "os/exec" "path/filepath" @@ -13,6 +17,7 @@ import ( "runtime" "strings" "testing" + "time" ) var godocTests = []struct { @@ -57,15 +62,18 @@ var godocTests = []struct { }, } -// Basic regression test for godoc command-line tool. -func TestGodoc(t *testing.T) { +// 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()) { tmp, err := ioutil.TempDir("", "godoc-regtest-") if err != nil { t.Fatal(err) } - defer os.RemoveAll(tmp) - bin := filepath.Join(tmp, "godoc") + bin = filepath.Join(tmp, "godoc") if runtime.GOOS == "windows" { bin += ".exe" } @@ -74,6 +82,13 @@ func TestGodoc(t *testing.T) { t.Fatalf("Building godoc: %v", err) } + return bin, func() { os.RemoveAll(tmp) } +} + +// Basic regression test for godoc command-line tool. +func TestCLI(t *testing.T) { + bin, cleanup := buildGodoc(t) + defer cleanup() for _, test := range godocTests { cmd := exec.Command(bin, test.args...) cmd.Args[0] = "godoc" @@ -96,3 +111,68 @@ func TestGodoc(t *testing.T) { } } } + +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() +} + +func waitForServer(t *testing.T, address string) { + // Poll every 50ms for a total of 5s. + for i := 0; i < 100; i++ { + time.Sleep(50 * time.Millisecond) + conn, err := net.Dial("tcp", address) + if err != nil { + continue + } + conn.Close() + return + } + t.Fatalf("Server %q failed to respond in 5 seconds", address) +} + +// Basic integration test for godoc HTTP interface. +func TestWeb(t *testing.T) { + bin, cleanup := buildGodoc(t) + defer cleanup() + addr := serverAddress(t) + cmd := exec.Command(bin, fmt.Sprintf("-http=%s", addr)) + cmd.Stdout = os.Stderr + cmd.Stderr = os.Stderr + cmd.Args[0] = "godoc" + if err := cmd.Start(); err != nil { + t.Fatalf("failed to start godoc: %s", err) + } + defer cmd.Process.Kill() + waitForServer(t, addr) + tests := []struct{ path, substr string }{ + {"/", "Go is an open source programming language"}, + {"/pkg/fmt/", "Package fmt implements formatted I/O"}, + {"/src/pkg/fmt/", "scan_test.go"}, + {"/src/pkg/fmt/print.go", "// Println formats using"}, + } + for _, test := range tests { + 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) + resp.Body.Close() + if err != nil { + t.Errorf("GET %s: failed to read body: %s (response: %v)", url, err, resp) + } + if bytes.Index(body, []byte(test.substr)) < 0 { + t.Errorf("GET %s: want substring %q in body, got:\n%s", + url, test.substr, string(body)) + } + } +} diff --git a/godoc/analysis/analysis.go b/godoc/analysis/analysis.go index 3cadd89af0..32a8d357a1 100644 --- a/godoc/analysis/analysis.go +++ b/godoc/analysis/analysis.go @@ -218,6 +218,9 @@ func (res *Result) fileInfo(url string) *fileInfo { res.mu.Lock() fi, ok := res.fileInfos[url] if !ok { + if res.fileInfos == nil { + res.fileInfos = make(map[string]*fileInfo) + } fi = new(fileInfo) res.fileInfos[url] = fi } @@ -240,6 +243,9 @@ func (res *Result) pkgInfo(importPath string) *pkgInfo { res.mu.Lock() pi, ok := res.pkgInfos[importPath] if !ok { + if res.pkgInfos == nil { + res.pkgInfos = make(map[string]*pkgInfo) + } pi = new(pkgInfo) res.pkgInfos[importPath] = pi } @@ -297,9 +303,6 @@ func (a *analysis) posURL(pos token.Pos, len int) string { // enabled by the pta flag. // func Run(pta bool, result *Result) { - result.fileInfos = make(map[string]*fileInfo) - result.pkgInfos = make(map[string]*pkgInfo) - conf := loader.Config{ SourceImports: true, AllowTypeErrors: true,