golang.org/x/tools/gopls@v0.15.3/internal/cmd/integration_test.go (about)

     1  // Copyright 2023 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 cmdtest contains the test suite for the command line behavior of gopls.
     6  package cmd_test
     7  
     8  // This file defines integration tests of each gopls subcommand that
     9  // fork+exec the command in a separate process.
    10  //
    11  // (Rather than execute 'go build gopls' during the test, we reproduce
    12  // the main entrypoint in the test executable.)
    13  //
    14  // The purpose of this test is to exercise client-side logic such as
    15  // argument parsing and formatting of LSP RPC responses, not server
    16  // behavior; see lsp_test for that.
    17  //
    18  // All tests run in parallel.
    19  //
    20  // TODO(adonovan):
    21  // - Use markers to represent positions in the input and in assertions.
    22  // - Coverage of cross-cutting things like cwd, environ, span parsing, etc.
    23  // - Subcommands that accept -write and -diff flags implement them
    24  //   consistently; factor their tests.
    25  // - Add missing test for 'vulncheck' subcommand.
    26  // - Add tests for client-only commands: serve, bug, help, api-json, licenses.
    27  
    28  import (
    29  	"bytes"
    30  	"context"
    31  	"encoding/json"
    32  	"fmt"
    33  	"math/rand"
    34  	"os"
    35  	"os/exec"
    36  	"path/filepath"
    37  	"regexp"
    38  	"strings"
    39  	"testing"
    40  
    41  	"golang.org/x/tools/gopls/internal/cmd"
    42  	"golang.org/x/tools/gopls/internal/debug"
    43  	"golang.org/x/tools/gopls/internal/hooks"
    44  	"golang.org/x/tools/gopls/internal/protocol"
    45  	"golang.org/x/tools/gopls/internal/util/bug"
    46  	"golang.org/x/tools/gopls/internal/version"
    47  	"golang.org/x/tools/internal/testenv"
    48  	"golang.org/x/tools/internal/tool"
    49  	"golang.org/x/tools/txtar"
    50  )
    51  
    52  // TestVersion tests the 'version' subcommand (../info.go).
    53  func TestVersion(t *testing.T) {
    54  	t.Parallel()
    55  
    56  	tree := writeTree(t, "")
    57  
    58  	// There's not much we can robustly assert about the actual version.
    59  	want := version.Version() // e.g. "master"
    60  
    61  	// basic
    62  	{
    63  		res := gopls(t, tree, "version")
    64  		res.checkExit(true)
    65  		res.checkStdout(want)
    66  	}
    67  
    68  	// basic, with version override
    69  	{
    70  		res := goplsWithEnv(t, tree, []string{"TEST_GOPLS_VERSION=v1.2.3"}, "version")
    71  		res.checkExit(true)
    72  		res.checkStdout(`v1\.2\.3`)
    73  	}
    74  
    75  	// -json flag
    76  	{
    77  		res := gopls(t, tree, "version", "-json")
    78  		res.checkExit(true)
    79  		var v debug.ServerVersion
    80  		if res.toJSON(&v) {
    81  			if v.Version != want {
    82  				t.Errorf("expected Version %q, got %q (%v)", want, v.Version, res)
    83  			}
    84  		}
    85  	}
    86  }
    87  
    88  // TestCheck tests the 'check' subcommand (../check.go).
    89  func TestCheck(t *testing.T) {
    90  	t.Parallel()
    91  
    92  	tree := writeTree(t, `
    93  -- go.mod --
    94  module example.com
    95  go 1.18
    96  
    97  -- a.go --
    98  package a
    99  import "fmt"
   100  var _ = fmt.Sprintf("%s", 123)
   101  
   102  -- b.go --
   103  package a
   104  import "fmt"
   105  var _ = fmt.Sprintf("%d", "123")
   106  `)
   107  
   108  	// no files
   109  	{
   110  		res := gopls(t, tree, "check")
   111  		res.checkExit(true)
   112  		if res.stdout != "" {
   113  			t.Errorf("unexpected output: %v", res)
   114  		}
   115  	}
   116  
   117  	// one file
   118  	{
   119  		res := gopls(t, tree, "check", "./a.go")
   120  		res.checkExit(true)
   121  		res.checkStdout("fmt.Sprintf format %s has arg 123 of wrong type int")
   122  	}
   123  
   124  	// two files
   125  	{
   126  		res := gopls(t, tree, "check", "./a.go", "./b.go")
   127  		res.checkExit(true)
   128  		res.checkStdout(`a.go:.* fmt.Sprintf format %s has arg 123 of wrong type int`)
   129  		res.checkStdout(`b.go:.* fmt.Sprintf format %d has arg "123" of wrong type string`)
   130  	}
   131  }
   132  
   133  // TestCallHierarchy tests the 'call_hierarchy' subcommand (../call_hierarchy.go).
   134  func TestCallHierarchy(t *testing.T) {
   135  	t.Parallel()
   136  
   137  	tree := writeTree(t, `
   138  -- go.mod --
   139  module example.com
   140  go 1.18
   141  
   142  -- a.go --
   143  package a
   144  func f() {}
   145  func g() {
   146  	f()
   147  }
   148  func h() {
   149  	f()
   150  	f()
   151  }
   152  `)
   153  	// missing position
   154  	{
   155  		res := gopls(t, tree, "call_hierarchy")
   156  		res.checkExit(false)
   157  		res.checkStderr("expects 1 argument")
   158  	}
   159  	// wrong place
   160  	{
   161  		res := gopls(t, tree, "call_hierarchy", "a.go:1")
   162  		res.checkExit(false)
   163  		res.checkStderr("identifier not found")
   164  	}
   165  	// f is called once from g and twice from h.
   166  	{
   167  		res := gopls(t, tree, "call_hierarchy", "a.go:2:6")
   168  		res.checkExit(true)
   169  		// We use regexp '.' as an OS-agnostic path separator.
   170  		res.checkStdout("ranges 7:2-3, 8:2-3 in ..a.go from/to function h in ..a.go:6:6-7")
   171  		res.checkStdout("ranges 4:2-3 in ..a.go from/to function g in ..a.go:3:6-7")
   172  		res.checkStdout("identifier: function f in ..a.go:2:6-7")
   173  	}
   174  }
   175  
   176  // TestCodeLens tests the 'codelens' subcommand (../codelens.go).
   177  func TestCodeLens(t *testing.T) {
   178  	t.Parallel()
   179  
   180  	tree := writeTree(t, `
   181  -- go.mod --
   182  module example.com
   183  go 1.18
   184  
   185  -- a/a.go --
   186  package a
   187  -- a/a_test.go --
   188  package a_test
   189  import "testing"
   190  func TestPass(t *testing.T) {}
   191  func TestFail(t *testing.T) { t.Fatal("fail") }
   192  `)
   193  	// missing position
   194  	{
   195  		res := gopls(t, tree, "codelens")
   196  		res.checkExit(false)
   197  		res.checkStderr("requires a file name")
   198  	}
   199  	// list code lenses
   200  	{
   201  		res := gopls(t, tree, "codelens", "./a/a_test.go")
   202  		res.checkExit(true)
   203  		res.checkStdout(`a_test.go:3: "run test" \[gopls.test\]`)
   204  		res.checkStdout(`a_test.go:4: "run test" \[gopls.test\]`)
   205  	}
   206  	// no codelens with title/position
   207  	{
   208  		res := gopls(t, tree, "codelens", "-exec", "./a/a_test.go:1", "nope")
   209  		res.checkExit(false)
   210  		res.checkStderr(`no code lens at .* with title "nope"`)
   211  	}
   212  	// run the passing test
   213  	{
   214  		res := gopls(t, tree, "codelens", "-exec", "./a/a_test.go:3", "run test")
   215  		res.checkExit(true)
   216  		res.checkStderr(`PASS: TestPass`)         // from go test
   217  		res.checkStderr("Info: all tests passed") // from gopls.test
   218  	}
   219  	// run the failing test
   220  	{
   221  		res := gopls(t, tree, "codelens", "-exec", "./a/a_test.go:4", "run test")
   222  		res.checkExit(false)
   223  		res.checkStderr(`FAIL	example.com/a`)
   224  		res.checkStderr("Info: 1 / 1 tests failed")
   225  	}
   226  }
   227  
   228  // TestDefinition tests the 'definition' subcommand (../definition.go).
   229  func TestDefinition(t *testing.T) {
   230  	t.Parallel()
   231  
   232  	tree := writeTree(t, `
   233  -- go.mod --
   234  module example.com
   235  go 1.18
   236  
   237  -- a.go --
   238  package a
   239  import "fmt"
   240  func f() {
   241  	fmt.Println()
   242  }
   243  func g() {
   244  	f()
   245  }
   246  `)
   247  	// missing position
   248  	{
   249  		res := gopls(t, tree, "definition")
   250  		res.checkExit(false)
   251  		res.checkStderr("expects 1 argument")
   252  	}
   253  	// intra-package
   254  	{
   255  		res := gopls(t, tree, "definition", "a.go:7:2") // "f()"
   256  		res.checkExit(true)
   257  		res.checkStdout("a.go:3:6-7: defined here as func f")
   258  	}
   259  	// cross-package
   260  	{
   261  		res := gopls(t, tree, "definition", "a.go:4:7") // "Println"
   262  		res.checkExit(true)
   263  		res.checkStdout("print.go.* defined here as func fmt.Println")
   264  		res.checkStdout("Println formats using the default formats for its operands")
   265  	}
   266  	// -json and -markdown
   267  	{
   268  		res := gopls(t, tree, "definition", "-json", "-markdown", "a.go:4:7")
   269  		res.checkExit(true)
   270  		var defn cmd.Definition
   271  		if res.toJSON(&defn) {
   272  			if !strings.HasPrefix(defn.Description, "```go\nfunc fmt.Println") {
   273  				t.Errorf("Description does not start with markdown code block. Got: %s", defn.Description)
   274  			}
   275  		}
   276  	}
   277  }
   278  
   279  // TestExecute tests the 'execute' subcommand (../execute.go).
   280  func TestExecute(t *testing.T) {
   281  	t.Parallel()
   282  
   283  	tree := writeTree(t, `
   284  -- go.mod --
   285  module example.com
   286  go 1.18
   287  
   288  -- hello.go --
   289  package a
   290  func main() {}
   291  
   292  -- hello_test.go --
   293  package a
   294  import "testing"
   295  func TestHello(t *testing.T) {
   296  	t.Fatal("oops")
   297  }
   298  `)
   299  	// missing command name
   300  	{
   301  		res := gopls(t, tree, "execute")
   302  		res.checkExit(false)
   303  		res.checkStderr("requires a command")
   304  	}
   305  	// bad command
   306  	{
   307  		res := gopls(t, tree, "execute", "gopls.foo")
   308  		res.checkExit(false)
   309  		res.checkStderr("unrecognized command: gopls.foo")
   310  	}
   311  	// too few arguments
   312  	{
   313  		res := gopls(t, tree, "execute", "gopls.run_tests")
   314  		res.checkExit(false)
   315  		res.checkStderr("expected 1 input arguments, got 0")
   316  	}
   317  	// too many arguments
   318  	{
   319  		res := gopls(t, tree, "execute", "gopls.run_tests", "null", "null")
   320  		res.checkExit(false)
   321  		res.checkStderr("expected 1 input arguments, got 2")
   322  	}
   323  	// argument is not JSON
   324  	{
   325  		res := gopls(t, tree, "execute", "gopls.run_tests", "hello")
   326  		res.checkExit(false)
   327  		res.checkStderr("argument 1 is not valid JSON: invalid character 'h'")
   328  	}
   329  	// add import, show diff
   330  	hello := "file://" + filepath.ToSlash(tree) + "/hello.go"
   331  	{
   332  		res := gopls(t, tree, "execute", "-d", "gopls.add_import", `{"ImportPath": "fmt", "URI": "`+hello+`"}`)
   333  		res.checkExit(true)
   334  		res.checkStdout(`[+]import "fmt"`)
   335  	}
   336  	// list known packages (has a result)
   337  	{
   338  		res := gopls(t, tree, "execute", "gopls.list_known_packages", `{"URI": "`+hello+`"}`)
   339  		res.checkExit(true)
   340  		res.checkStdout(`"fmt"`)
   341  		res.checkStdout(`"encoding/json"`)
   342  	}
   343  	// run tests
   344  	{
   345  		helloTest := "file://" + filepath.ToSlash(tree) + "/hello_test.go"
   346  		res := gopls(t, tree, "execute", "gopls.run_tests", `{"URI": "`+helloTest+`", "Tests": ["TestHello"]}`)
   347  		res.checkExit(false)
   348  		res.checkStderr(`hello_test.go:4: oops`)
   349  		res.checkStderr(`1 / 1 tests failed`)
   350  	}
   351  }
   352  
   353  // TestFoldingRanges tests the 'folding_ranges' subcommand (../folding_range.go).
   354  func TestFoldingRanges(t *testing.T) {
   355  	t.Parallel()
   356  
   357  	tree := writeTree(t, `
   358  -- go.mod --
   359  module example.com
   360  go 1.18
   361  
   362  -- a.go --
   363  package a
   364  func f(x int) {
   365  	// hello
   366  }
   367  `)
   368  	// missing filename
   369  	{
   370  		res := gopls(t, tree, "folding_ranges")
   371  		res.checkExit(false)
   372  		res.checkStderr("expects 1 argument")
   373  	}
   374  	// success
   375  	{
   376  		res := gopls(t, tree, "folding_ranges", "a.go")
   377  		res.checkExit(true)
   378  		res.checkStdout("2:8-2:13") // params (x int)
   379  		res.checkStdout("2:16-4:1") //   body { ... }
   380  	}
   381  }
   382  
   383  // TestFormat tests the 'format' subcommand (../format.go).
   384  func TestFormat(t *testing.T) {
   385  	t.Parallel()
   386  
   387  	tree := writeTree(t, `
   388  -- a.go --
   389  package a ;  func f ( ) { }
   390  `)
   391  	const want = `package a
   392  
   393  func f() {}
   394  `
   395  
   396  	// no files => nop
   397  	{
   398  		res := gopls(t, tree, "format")
   399  		res.checkExit(true)
   400  	}
   401  	// default => print formatted result
   402  	{
   403  		res := gopls(t, tree, "format", "a.go")
   404  		res.checkExit(true)
   405  		if res.stdout != want {
   406  			t.Errorf("format: got <<%s>>, want <<%s>>", res.stdout, want)
   407  		}
   408  	}
   409  	// start/end position not supported (unless equal to start/end of file)
   410  	{
   411  		res := gopls(t, tree, "format", "a.go:1-2")
   412  		res.checkExit(false)
   413  		res.checkStderr("only full file formatting supported")
   414  	}
   415  	// -list: show only file names
   416  	{
   417  		res := gopls(t, tree, "format", "-list", "a.go")
   418  		res.checkExit(true)
   419  		res.checkStdout("a.go")
   420  	}
   421  	// -diff prints a unified diff
   422  	{
   423  		res := gopls(t, tree, "format", "-diff", "a.go")
   424  		res.checkExit(true)
   425  		// We omit the filenames as they vary by OS.
   426  		want := `
   427  -package a ;  func f ( ) { }
   428  +package a
   429  +
   430  +func f() {}
   431  `
   432  		res.checkStdout(regexp.QuoteMeta(want))
   433  	}
   434  	// -write updates the file
   435  	{
   436  		res := gopls(t, tree, "format", "-write", "a.go")
   437  		res.checkExit(true)
   438  		res.checkStdout("^$") // empty
   439  		checkContent(t, filepath.Join(tree, "a.go"), want)
   440  	}
   441  }
   442  
   443  // TestHighlight tests the 'highlight' subcommand (../highlight.go).
   444  func TestHighlight(t *testing.T) {
   445  	t.Parallel()
   446  
   447  	tree := writeTree(t, `
   448  -- a.go --
   449  package a
   450  import "fmt"
   451  func f() {
   452  	fmt.Println()
   453  	fmt.Println()
   454  }
   455  `)
   456  
   457  	// no arguments
   458  	{
   459  		res := gopls(t, tree, "highlight")
   460  		res.checkExit(false)
   461  		res.checkStderr("expects 1 argument")
   462  	}
   463  	// all occurrences of Println
   464  	{
   465  		res := gopls(t, tree, "highlight", "a.go:4:7")
   466  		res.checkExit(true)
   467  		res.checkStdout("a.go:4:6-13")
   468  		res.checkStdout("a.go:5:6-13")
   469  	}
   470  }
   471  
   472  // TestImplementations tests the 'implementation' subcommand (../implementation.go).
   473  func TestImplementations(t *testing.T) {
   474  	t.Parallel()
   475  
   476  	tree := writeTree(t, `
   477  -- a.go --
   478  package a
   479  import "fmt"
   480  type T int
   481  func (T) String() string { return "" }
   482  `)
   483  
   484  	// no arguments
   485  	{
   486  		res := gopls(t, tree, "implementation")
   487  		res.checkExit(false)
   488  		res.checkStderr("expects 1 argument")
   489  	}
   490  	// T.String
   491  	{
   492  		res := gopls(t, tree, "implementation", "a.go:4:10")
   493  		res.checkExit(true)
   494  		// TODO(adonovan): extract and check the content of the reported ranges?
   495  		// We use regexp '.' as an OS-agnostic path separator.
   496  		res.checkStdout("fmt.print.go:")     // fmt.Stringer.String
   497  		res.checkStdout("runtime.error.go:") // runtime.stringer.String
   498  	}
   499  }
   500  
   501  // TestImports tests the 'imports' subcommand (../imports.go).
   502  func TestImports(t *testing.T) {
   503  	t.Parallel()
   504  
   505  	tree := writeTree(t, `
   506  -- a.go --
   507  package a
   508  func _() {
   509  	fmt.Println()
   510  }
   511  `)
   512  
   513  	want := `
   514  package a
   515  
   516  import "fmt"
   517  func _() {
   518  	fmt.Println()
   519  }
   520  `[1:]
   521  
   522  	// no arguments
   523  	{
   524  		res := gopls(t, tree, "imports")
   525  		res.checkExit(false)
   526  		res.checkStderr("expects 1 argument")
   527  	}
   528  	// default: print with imports
   529  	{
   530  		res := gopls(t, tree, "imports", "a.go")
   531  		res.checkExit(true)
   532  		if res.stdout != want {
   533  			t.Errorf("imports: got <<%s>>, want <<%s>>", res.stdout, want)
   534  		}
   535  	}
   536  	// -diff: show a unified diff
   537  	{
   538  		res := gopls(t, tree, "imports", "-diff", "a.go")
   539  		res.checkExit(true)
   540  		res.checkStdout(regexp.QuoteMeta(`+import "fmt"`))
   541  	}
   542  	// -write: update file
   543  	{
   544  		res := gopls(t, tree, "imports", "-write", "a.go")
   545  		res.checkExit(true)
   546  		checkContent(t, filepath.Join(tree, "a.go"), want)
   547  	}
   548  }
   549  
   550  // TestLinks tests the 'links' subcommand (../links.go).
   551  func TestLinks(t *testing.T) {
   552  	t.Parallel()
   553  
   554  	tree := writeTree(t, `
   555  -- a.go --
   556  // Link in package doc: https://pkg.go.dev/
   557  package a
   558  
   559  // Link in internal comment: https://go.dev/cl
   560  
   561  // Doc comment link: https://blog.go.dev/
   562  func f() {}
   563  `)
   564  	// no arguments
   565  	{
   566  		res := gopls(t, tree, "links")
   567  		res.checkExit(false)
   568  		res.checkStderr("expects 1 argument")
   569  	}
   570  	// success
   571  	{
   572  		res := gopls(t, tree, "links", "a.go")
   573  		res.checkExit(true)
   574  		res.checkStdout("https://go.dev/cl")
   575  		res.checkStdout("https://pkg.go.dev")
   576  		res.checkStdout("https://blog.go.dev/")
   577  	}
   578  	// -json
   579  	{
   580  		res := gopls(t, tree, "links", "-json", "a.go")
   581  		res.checkExit(true)
   582  		res.checkStdout("https://pkg.go.dev")
   583  		res.checkStdout("https://go.dev/cl")
   584  		res.checkStdout("https://blog.go.dev/") // at 5:21-5:41
   585  		var links []protocol.DocumentLink
   586  		if res.toJSON(&links) {
   587  			// Check just one of the three locations.
   588  			if got, want := fmt.Sprint(links[2].Range), "5:21-5:41"; got != want {
   589  				t.Errorf("wrong link location: got %v, want %v", got, want)
   590  			}
   591  		}
   592  	}
   593  }
   594  
   595  // TestReferences tests the 'references' subcommand (../references.go).
   596  func TestReferences(t *testing.T) {
   597  	t.Parallel()
   598  
   599  	tree := writeTree(t, `
   600  -- go.mod --
   601  module example.com
   602  go 1.18
   603  
   604  -- a.go --
   605  package a
   606  import "fmt"
   607  func f() {
   608  	fmt.Println()
   609  }
   610  
   611  -- b.go --
   612  package a
   613  import "fmt"
   614  func g() {
   615  	fmt.Println()
   616  }
   617  `)
   618  	// no arguments
   619  	{
   620  		res := gopls(t, tree, "references")
   621  		res.checkExit(false)
   622  		res.checkStderr("expects 1 argument")
   623  	}
   624  	// fmt.Println
   625  	{
   626  		res := gopls(t, tree, "references", "a.go:4:10")
   627  		res.checkExit(true)
   628  		res.checkStdout("a.go:4:6-13")
   629  		res.checkStdout("b.go:4:6-13")
   630  	}
   631  }
   632  
   633  // TestSignature tests the 'signature' subcommand (../signature.go).
   634  func TestSignature(t *testing.T) {
   635  	t.Parallel()
   636  
   637  	tree := writeTree(t, `
   638  -- go.mod --
   639  module example.com
   640  go 1.18
   641  
   642  -- a.go --
   643  package a
   644  import "fmt"
   645  func f() {
   646  	fmt.Println(123)
   647  }
   648  `)
   649  	// no arguments
   650  	{
   651  		res := gopls(t, tree, "signature")
   652  		res.checkExit(false)
   653  		res.checkStderr("expects 1 argument")
   654  	}
   655  	// at 123 inside fmt.Println() call
   656  	{
   657  		res := gopls(t, tree, "signature", "a.go:4:15")
   658  		res.checkExit(true)
   659  		res.checkStdout("Println\\(a ...")
   660  		res.checkStdout("Println formats using the default formats...")
   661  	}
   662  }
   663  
   664  // TestPrepareRename tests the 'prepare_rename' subcommand (../prepare_rename.go).
   665  func TestPrepareRename(t *testing.T) {
   666  	t.Parallel()
   667  
   668  	tree := writeTree(t, `
   669  -- go.mod --
   670  module example.com
   671  go 1.18
   672  
   673  -- a.go --
   674  package a
   675  func oldname() {}
   676  `)
   677  	// no arguments
   678  	{
   679  		res := gopls(t, tree, "prepare_rename")
   680  		res.checkExit(false)
   681  		res.checkStderr("expects 1 argument")
   682  	}
   683  	// in 'package' keyword
   684  	{
   685  		res := gopls(t, tree, "prepare_rename", "a.go:1:3")
   686  		res.checkExit(false)
   687  		res.checkStderr("request is not valid at the given position")
   688  	}
   689  	// in 'package' identifier (not supported by client)
   690  	{
   691  		res := gopls(t, tree, "prepare_rename", "a.go:1:9")
   692  		res.checkExit(false)
   693  		res.checkStderr("can't rename package")
   694  	}
   695  	// in func oldname
   696  	{
   697  		res := gopls(t, tree, "prepare_rename", "a.go:2:9")
   698  		res.checkExit(true)
   699  		res.checkStdout("a.go:2:6-13") // all of "oldname"
   700  	}
   701  }
   702  
   703  // TestRename tests the 'rename' subcommand (../rename.go).
   704  func TestRename(t *testing.T) {
   705  	t.Parallel()
   706  
   707  	tree := writeTree(t, `
   708  -- go.mod --
   709  module example.com
   710  go 1.18
   711  
   712  -- a.go --
   713  package a
   714  func oldname() {}
   715  `)
   716  	// no arguments
   717  	{
   718  		res := gopls(t, tree, "rename")
   719  		res.checkExit(false)
   720  		res.checkStderr("expects 2 arguments")
   721  	}
   722  	// missing newname
   723  	{
   724  		res := gopls(t, tree, "rename", "a.go:1:3")
   725  		res.checkExit(false)
   726  		res.checkStderr("expects 2 arguments")
   727  	}
   728  	// in 'package' keyword
   729  	{
   730  		res := gopls(t, tree, "rename", "a.go:1:3", "newname")
   731  		res.checkExit(false)
   732  		res.checkStderr("no identifier found")
   733  	}
   734  	// in 'package' identifier
   735  	{
   736  		res := gopls(t, tree, "rename", "a.go:1:9", "newname")
   737  		res.checkExit(false)
   738  		res.checkStderr(`cannot rename package: module path .* same as the package path, so .* no effect`)
   739  	}
   740  	// success, func oldname (and -diff)
   741  	{
   742  		res := gopls(t, tree, "rename", "-diff", "a.go:2:9", "newname")
   743  		res.checkExit(true)
   744  		res.checkStdout(regexp.QuoteMeta("-func oldname() {}"))
   745  		res.checkStdout(regexp.QuoteMeta("+func newname() {}"))
   746  	}
   747  }
   748  
   749  // TestSymbols tests the 'symbols' subcommand (../symbols.go).
   750  func TestSymbols(t *testing.T) {
   751  	t.Parallel()
   752  
   753  	tree := writeTree(t, `
   754  -- go.mod --
   755  module example.com
   756  go 1.18
   757  
   758  -- a.go --
   759  package a
   760  func f()
   761  var v int
   762  const c = 0
   763  `)
   764  	// no files
   765  	{
   766  		res := gopls(t, tree, "symbols")
   767  		res.checkExit(false)
   768  		res.checkStderr("expects 1 argument")
   769  	}
   770  	// success
   771  	{
   772  		res := gopls(t, tree, "symbols", "a.go:123:456") // (line/col ignored)
   773  		res.checkExit(true)
   774  		res.checkStdout("f Function 2:6-2:7")
   775  		res.checkStdout("v Variable 3:5-3:6")
   776  		res.checkStdout("c Constant 4:7-4:8")
   777  	}
   778  }
   779  
   780  // TestSemtok tests the 'semtok' subcommand (../semantictokens.go).
   781  func TestSemtok(t *testing.T) {
   782  	t.Parallel()
   783  
   784  	tree := writeTree(t, `
   785  -- go.mod --
   786  module example.com
   787  go 1.18
   788  
   789  -- a.go --
   790  package a
   791  func f()
   792  var v int
   793  const c = 0
   794  `)
   795  	// no files
   796  	{
   797  		res := gopls(t, tree, "semtok")
   798  		res.checkExit(false)
   799  		res.checkStderr("expected one file name")
   800  	}
   801  	// success
   802  	{
   803  		res := gopls(t, tree, "semtok", "a.go")
   804  		res.checkExit(true)
   805  		got := res.stdout
   806  		want := `
   807  /*⇒7,keyword,[]*/package /*⇒1,namespace,[]*/a
   808  /*⇒4,keyword,[]*/func /*⇒1,function,[definition]*/f()
   809  /*⇒3,keyword,[]*/var /*⇒1,variable,[definition]*/v /*⇒3,type,[defaultLibrary]*/int
   810  /*⇒5,keyword,[]*/const /*⇒1,variable,[definition readonly]*/c = /*⇒1,number,[]*/0
   811  `[1:]
   812  		if got != want {
   813  			t.Errorf("semtok: got <<%s>>, want <<%s>>", got, want)
   814  		}
   815  	}
   816  }
   817  
   818  func TestStats(t *testing.T) {
   819  	t.Parallel()
   820  
   821  	tree := writeTree(t, `
   822  -- go.mod --
   823  module example.com
   824  go 1.18
   825  
   826  -- a.go --
   827  package a
   828  -- b/b.go --
   829  package b
   830  -- testdata/foo.go --
   831  package foo
   832  `)
   833  
   834  	// Trigger a bug report with a distinctive string
   835  	// and check that it was durably recorded.
   836  	oops := fmt.Sprintf("oops-%d", rand.Int())
   837  	{
   838  		env := []string{"TEST_GOPLS_BUG=" + oops}
   839  		res := goplsWithEnv(t, tree, env, "bug")
   840  		res.checkExit(true)
   841  	}
   842  
   843  	res := gopls(t, tree, "stats")
   844  	res.checkExit(true)
   845  
   846  	var stats cmd.GoplsStats
   847  	if err := json.Unmarshal([]byte(res.stdout), &stats); err != nil {
   848  		t.Fatalf("failed to unmarshal JSON output of stats command: %v", err)
   849  	}
   850  
   851  	// a few sanity checks
   852  	checks := []struct {
   853  		field string
   854  		got   int
   855  		want  int
   856  	}{
   857  		{
   858  			"WorkspaceStats.Views[0].WorkspaceModules",
   859  			stats.WorkspaceStats.Views[0].WorkspacePackages.Modules,
   860  			1,
   861  		},
   862  		{
   863  			"WorkspaceStats.Views[0].WorkspacePackages",
   864  			stats.WorkspaceStats.Views[0].WorkspacePackages.Packages,
   865  			2,
   866  		},
   867  		{"DirStats.Files", stats.DirStats.Files, 4},
   868  		{"DirStats.GoFiles", stats.DirStats.GoFiles, 2},
   869  		{"DirStats.ModFiles", stats.DirStats.ModFiles, 1},
   870  		{"DirStats.TestdataFiles", stats.DirStats.TestdataFiles, 1},
   871  	}
   872  	for _, check := range checks {
   873  		if check.got != check.want {
   874  			t.Errorf("stats.%s = %d, want %d", check.field, check.got, check.want)
   875  		}
   876  	}
   877  
   878  	// Check that we got a BugReport with the expected message.
   879  	{
   880  		got := fmt.Sprint(stats.BugReports)
   881  		wants := []string{
   882  			"cmd/info.go", // File containing call to bug.Report
   883  			oops,          // Description
   884  		}
   885  		for _, want := range wants {
   886  			if !strings.Contains(got, want) {
   887  				t.Errorf("BugReports does not contain %q. Got:<<%s>>", want, got)
   888  				break
   889  			}
   890  		}
   891  	}
   892  
   893  	// Check that -anon suppresses fields containing user information.
   894  	{
   895  		res2 := gopls(t, tree, "stats", "-anon")
   896  		res2.checkExit(true)
   897  
   898  		var stats2 cmd.GoplsStats
   899  		if err := json.Unmarshal([]byte(res2.stdout), &stats2); err != nil {
   900  			t.Fatalf("failed to unmarshal JSON output of stats command: %v", err)
   901  		}
   902  		if got := len(stats2.BugReports); got > 0 {
   903  			t.Errorf("Got %d bug reports with -anon, want 0. Reports:%+v", got, stats2.BugReports)
   904  		}
   905  		var stats2AsMap map[string]any
   906  		if err := json.Unmarshal([]byte(res2.stdout), &stats2AsMap); err != nil {
   907  			t.Fatalf("failed to unmarshal JSON output of stats command: %v", err)
   908  		}
   909  		// GOPACKAGESDRIVER is user information, but is ok to print zero value.
   910  		if v, ok := stats2AsMap["GOPACKAGESDRIVER"]; ok && v != "" {
   911  			t.Errorf(`Got GOPACKAGESDRIVER=(%v, %v); want ("", true(found))`, v, ok)
   912  		}
   913  	}
   914  
   915  	// Check that -anon suppresses fields containing non-zero user information.
   916  	{
   917  		res3 := goplsWithEnv(t, tree, []string{"GOPACKAGESDRIVER=off"}, "stats", "-anon")
   918  		res3.checkExit(true)
   919  
   920  		var statsAsMap3 map[string]interface{}
   921  		if err := json.Unmarshal([]byte(res3.stdout), &statsAsMap3); err != nil {
   922  			t.Fatalf("failed to unmarshal JSON output of stats command: %v", err)
   923  		}
   924  		// GOPACKAGESDRIVER is user information, want non-empty value to be omitted.
   925  		if v, ok := statsAsMap3["GOPACKAGESDRIVER"]; ok {
   926  			t.Errorf(`Got GOPACKAGESDRIVER=(%q, %v); want ("", false(not found))`, v, ok)
   927  		}
   928  	}
   929  }
   930  
   931  // TestFix tests the 'fix' subcommand (../suggested_fix.go).
   932  func TestFix(t *testing.T) {
   933  	t.Parallel()
   934  
   935  	tree := writeTree(t, `
   936  -- go.mod --
   937  module example.com
   938  go 1.18
   939  
   940  -- a.go --
   941  package a
   942  type T int
   943  func f() (int, string) { return }
   944  
   945  -- b.go --
   946  package a
   947  import "io"
   948  var _ io.Reader = C{}
   949  type C struct{}
   950  `)
   951  
   952  	// no arguments
   953  	{
   954  		res := gopls(t, tree, "fix")
   955  		res.checkExit(false)
   956  		res.checkStderr("expects at least 1 argument")
   957  	}
   958  	// success with default kinds, {quickfix}.
   959  	// -a is always required because no fix is currently "preferred" (!)
   960  	{
   961  		res := gopls(t, tree, "fix", "-a", "a.go")
   962  		res.checkExit(true)
   963  		got := res.stdout
   964  		want := `
   965  package a
   966  type T int
   967  func f() (int, string) { return 0, "" }
   968  
   969  `[1:]
   970  		if got != want {
   971  			t.Errorf("fix: got <<%s>>, want <<%s>>\nstderr:\n%s", got, want, res.stderr)
   972  		}
   973  	}
   974  	// success, with explicit CodeAction kind and diagnostics span.
   975  	{
   976  		res := gopls(t, tree, "fix", "-a", "b.go:#40", "quickfix")
   977  		res.checkExit(true)
   978  		got := res.stdout
   979  		want := `
   980  package a
   981  
   982  import "io"
   983  
   984  var _ io.Reader = C{}
   985  
   986  type C struct{}
   987  
   988  // Read implements io.Reader.
   989  func (c C) Read(p []byte) (n int, err error) {
   990  	panic("unimplemented")
   991  }
   992  `[1:]
   993  		if got != want {
   994  			t.Errorf("fix: got <<%s>>, want <<%s>>\nstderr:\n%s", got, want, res.stderr)
   995  		}
   996  	}
   997  }
   998  
   999  // TestWorkspaceSymbol tests the 'workspace_symbol' subcommand (../workspace_symbol.go).
  1000  func TestWorkspaceSymbol(t *testing.T) {
  1001  	t.Parallel()
  1002  
  1003  	tree := writeTree(t, `
  1004  -- go.mod --
  1005  module example.com
  1006  go 1.18
  1007  
  1008  -- a.go --
  1009  package a
  1010  func someFunctionName()
  1011  `)
  1012  	// no files
  1013  	{
  1014  		res := gopls(t, tree, "workspace_symbol")
  1015  		res.checkExit(false)
  1016  		res.checkStderr("expects 1 argument")
  1017  	}
  1018  	// success
  1019  	{
  1020  		res := gopls(t, tree, "workspace_symbol", "meFun")
  1021  		res.checkExit(true)
  1022  		res.checkStdout("a.go:2:6-22 someFunctionName Function")
  1023  	}
  1024  }
  1025  
  1026  // -- test framework --
  1027  
  1028  func TestMain(m *testing.M) {
  1029  	switch os.Getenv("ENTRYPOINT") {
  1030  	case "goplsMain":
  1031  		goplsMain()
  1032  	default:
  1033  		os.Exit(m.Run())
  1034  	}
  1035  }
  1036  
  1037  // This function is a stand-in for gopls.main in ../../../../main.go.
  1038  func goplsMain() {
  1039  	// Panic on bugs (unlike the production gopls command),
  1040  	// except in tests that inject calls to bug.Report.
  1041  	if os.Getenv("TEST_GOPLS_BUG") == "" {
  1042  		bug.PanicOnBugs = true
  1043  	}
  1044  
  1045  	if v := os.Getenv("TEST_GOPLS_VERSION"); v != "" {
  1046  		version.VersionOverride = v
  1047  	}
  1048  
  1049  	tool.Main(context.Background(), cmd.New(hooks.Options), os.Args[1:])
  1050  }
  1051  
  1052  // writeTree extracts a txtar archive into a new directory and returns its path.
  1053  func writeTree(t *testing.T, archive string) string {
  1054  	root := t.TempDir()
  1055  
  1056  	// This unfortunate step is required because gopls output
  1057  	// expands symbolic links in its input file names (arguably it
  1058  	// should not), and on macOS the temp dir is in /var -> private/var.
  1059  	root, err := filepath.EvalSymlinks(root)
  1060  	if err != nil {
  1061  		t.Fatal(err)
  1062  	}
  1063  
  1064  	for _, f := range txtar.Parse([]byte(archive)).Files {
  1065  		filename := filepath.Join(root, f.Name)
  1066  		if err := os.MkdirAll(filepath.Dir(filename), 0777); err != nil {
  1067  			t.Fatal(err)
  1068  		}
  1069  		if err := os.WriteFile(filename, f.Data, 0666); err != nil {
  1070  			t.Fatal(err)
  1071  		}
  1072  	}
  1073  	return root
  1074  }
  1075  
  1076  // gopls executes gopls in a child process.
  1077  func gopls(t *testing.T, dir string, args ...string) *result {
  1078  	return goplsWithEnv(t, dir, nil, args...)
  1079  }
  1080  
  1081  func goplsWithEnv(t *testing.T, dir string, env []string, args ...string) *result {
  1082  	testenv.NeedsTool(t, "go")
  1083  
  1084  	// Catch inadvertent use of dir=".", which would make
  1085  	// the ReplaceAll below unpredictable.
  1086  	if !filepath.IsAbs(dir) {
  1087  		t.Fatalf("dir is not absolute: %s", dir)
  1088  	}
  1089  
  1090  	goplsCmd := exec.Command(os.Args[0], args...)
  1091  	goplsCmd.Env = append(os.Environ(), "ENTRYPOINT=goplsMain")
  1092  	goplsCmd.Env = append(goplsCmd.Env, "GOPACKAGESDRIVER=off")
  1093  	goplsCmd.Env = append(goplsCmd.Env, env...)
  1094  	goplsCmd.Dir = dir
  1095  	goplsCmd.Stdout = new(bytes.Buffer)
  1096  	goplsCmd.Stderr = new(bytes.Buffer)
  1097  
  1098  	cmdErr := goplsCmd.Run()
  1099  
  1100  	stdout := strings.ReplaceAll(fmt.Sprint(goplsCmd.Stdout), dir, ".")
  1101  	stderr := strings.ReplaceAll(fmt.Sprint(goplsCmd.Stderr), dir, ".")
  1102  	exitcode := 0
  1103  	if cmdErr != nil {
  1104  		if exitErr, ok := cmdErr.(*exec.ExitError); ok {
  1105  			exitcode = exitErr.ExitCode()
  1106  		} else {
  1107  			stderr = cmdErr.Error() // (execve failure)
  1108  			exitcode = -1
  1109  		}
  1110  	}
  1111  	res := &result{
  1112  		t:        t,
  1113  		command:  "gopls " + strings.Join(args, " "),
  1114  		exitcode: exitcode,
  1115  		stdout:   stdout,
  1116  		stderr:   stderr,
  1117  	}
  1118  	if false {
  1119  		t.Log(res)
  1120  	}
  1121  	return res
  1122  }
  1123  
  1124  // A result holds the result of a gopls invocation, and provides assertion helpers.
  1125  type result struct {
  1126  	t              *testing.T
  1127  	command        string
  1128  	exitcode       int
  1129  	stdout, stderr string
  1130  }
  1131  
  1132  func (res *result) String() string {
  1133  	return fmt.Sprintf("%s: exit=%d stdout=<<%s>> stderr=<<%s>>",
  1134  		res.command, res.exitcode, res.stdout, res.stderr)
  1135  }
  1136  
  1137  // checkExit asserts that gopls returned the expected exit code.
  1138  func (res *result) checkExit(success bool) {
  1139  	res.t.Helper()
  1140  	if (res.exitcode == 0) != success {
  1141  		res.t.Errorf("%s: exited with code %d, want success: %t (%s)",
  1142  			res.command, res.exitcode, success, res)
  1143  	}
  1144  }
  1145  
  1146  // checkStdout asserts that the gopls standard output matches the pattern.
  1147  func (res *result) checkStdout(pattern string) {
  1148  	res.t.Helper()
  1149  	res.checkOutput(pattern, "stdout", res.stdout)
  1150  }
  1151  
  1152  // checkStderr asserts that the gopls standard error matches the pattern.
  1153  func (res *result) checkStderr(pattern string) {
  1154  	res.t.Helper()
  1155  	res.checkOutput(pattern, "stderr", res.stderr)
  1156  }
  1157  
  1158  func (res *result) checkOutput(pattern, name, content string) {
  1159  	res.t.Helper()
  1160  	if match, err := regexp.MatchString(pattern, content); err != nil {
  1161  		res.t.Errorf("invalid regexp: %v", err)
  1162  	} else if !match {
  1163  		res.t.Errorf("%s: %s does not match [%s]; got <<%s>>",
  1164  			res.command, name, pattern, content)
  1165  	}
  1166  }
  1167  
  1168  // toJSON decodes res.stdout as JSON into to *ptr and reports its success.
  1169  func (res *result) toJSON(ptr interface{}) bool {
  1170  	if err := json.Unmarshal([]byte(res.stdout), ptr); err != nil {
  1171  		res.t.Errorf("invalid JSON %v", err)
  1172  		return false
  1173  	}
  1174  	return true
  1175  }
  1176  
  1177  // checkContent checks that the contents of the file are as expected.
  1178  func checkContent(t *testing.T, filename, want string) {
  1179  	data, err := os.ReadFile(filename)
  1180  	if err != nil {
  1181  		t.Error(err)
  1182  		return
  1183  	}
  1184  	if got := string(data); got != want {
  1185  		t.Errorf("content of %s is <<%s>>, want <<%s>>", filename, got, want)
  1186  	}
  1187  }