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  }