golang.org/x/tools/gopls@v0.15.3/internal/test/integration/workspace/workspace_test.go (about)

     1  // Copyright 2020 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 workspace
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  	"sort"
    11  	"strings"
    12  	"testing"
    13  
    14  	"github.com/google/go-cmp/cmp"
    15  	"golang.org/x/tools/gopls/internal/hooks"
    16  	"golang.org/x/tools/gopls/internal/protocol"
    17  	"golang.org/x/tools/gopls/internal/test/integration/fake"
    18  	"golang.org/x/tools/gopls/internal/util/bug"
    19  	"golang.org/x/tools/gopls/internal/util/goversion"
    20  	"golang.org/x/tools/internal/gocommand"
    21  	"golang.org/x/tools/internal/testenv"
    22  
    23  	. "golang.org/x/tools/gopls/internal/test/integration"
    24  )
    25  
    26  func TestMain(m *testing.M) {
    27  	bug.PanicOnBugs = true
    28  	Main(m, hooks.Options)
    29  }
    30  
    31  const workspaceProxy = `
    32  -- example.com@v1.2.3/go.mod --
    33  module example.com
    34  
    35  go 1.12
    36  -- example.com@v1.2.3/blah/blah.go --
    37  package blah
    38  
    39  import "fmt"
    40  
    41  func SaySomething() {
    42  	fmt.Println("something")
    43  }
    44  -- random.org@v1.2.3/go.mod --
    45  module random.org
    46  
    47  go 1.12
    48  -- random.org@v1.2.3/bye/bye.go --
    49  package bye
    50  
    51  func Goodbye() {
    52  	println("Bye")
    53  }
    54  `
    55  
    56  // TODO: Add a replace directive.
    57  const workspaceModule = `
    58  -- pkg/go.mod --
    59  module mod.com
    60  
    61  go 1.14
    62  
    63  require (
    64  	example.com v1.2.3
    65  	random.org v1.2.3
    66  )
    67  -- pkg/go.sum --
    68  example.com v1.2.3 h1:veRD4tUnatQRgsULqULZPjeoBGFr2qBhevSCZllD2Ds=
    69  example.com v1.2.3/go.mod h1:Y2Rc5rVWjWur0h3pd9aEvK5Pof8YKDANh9gHA2Maujo=
    70  random.org v1.2.3 h1:+JE2Fkp7gS0zsHXGEQJ7hraom3pNTlkxC4b2qPfA+/Q=
    71  random.org v1.2.3/go.mod h1:E9KM6+bBX2g5ykHZ9H27w16sWo3QwgonyjM44Dnej3I=
    72  -- pkg/main.go --
    73  package main
    74  
    75  import (
    76  	"example.com/blah"
    77  	"mod.com/inner"
    78  	"random.org/bye"
    79  )
    80  
    81  func main() {
    82  	blah.SaySomething()
    83  	inner.Hi()
    84  	bye.Goodbye()
    85  }
    86  -- pkg/main2.go --
    87  package main
    88  
    89  import "fmt"
    90  
    91  func _() {
    92  	fmt.Print("%s")
    93  }
    94  -- pkg/inner/inner.go --
    95  package inner
    96  
    97  import "example.com/blah"
    98  
    99  func Hi() {
   100  	blah.SaySomething()
   101  }
   102  -- goodbye/bye/bye.go --
   103  package bye
   104  
   105  func Bye() {}
   106  -- goodbye/go.mod --
   107  module random.org
   108  
   109  go 1.12
   110  `
   111  
   112  // Confirm that find references returns all of the references in the module,
   113  // regardless of what the workspace root is.
   114  func TestReferences(t *testing.T) {
   115  	for _, tt := range []struct {
   116  		name, rootPath string
   117  	}{
   118  		{
   119  			name:     "module root",
   120  			rootPath: "pkg",
   121  		},
   122  		{
   123  			name:     "subdirectory",
   124  			rootPath: "pkg/inner",
   125  		},
   126  	} {
   127  		t.Run(tt.name, func(t *testing.T) {
   128  			opts := []RunOption{ProxyFiles(workspaceProxy)}
   129  			if tt.rootPath != "" {
   130  				opts = append(opts, WorkspaceFolders(tt.rootPath))
   131  			}
   132  			WithOptions(opts...).Run(t, workspaceModule, func(t *testing.T, env *Env) {
   133  				f := "pkg/inner/inner.go"
   134  				env.OpenFile(f)
   135  				locations := env.References(env.RegexpSearch(f, `SaySomething`))
   136  				want := 3
   137  				if got := len(locations); got != want {
   138  					t.Fatalf("expected %v locations, got %v", want, got)
   139  				}
   140  			})
   141  		})
   142  	}
   143  }
   144  
   145  // Make sure that analysis diagnostics are cleared for the whole package when
   146  // the only opened file is closed. This test was inspired by the experience in
   147  // VS Code, where clicking on a reference result triggers a
   148  // textDocument/didOpen without a corresponding textDocument/didClose.
   149  func TestClearAnalysisDiagnostics(t *testing.T) {
   150  	WithOptions(
   151  		ProxyFiles(workspaceProxy),
   152  		WorkspaceFolders("pkg/inner"),
   153  	).Run(t, workspaceModule, func(t *testing.T, env *Env) {
   154  		env.OpenFile("pkg/main.go")
   155  		env.AfterChange(
   156  			Diagnostics(env.AtRegexp("pkg/main2.go", "fmt.Print")),
   157  		)
   158  		env.CloseBuffer("pkg/main.go")
   159  		env.AfterChange(
   160  			NoDiagnostics(ForFile("pkg/main2.go")),
   161  		)
   162  	})
   163  }
   164  
   165  // TestReloadOnlyOnce checks that changes to the go.mod file do not result in
   166  // redundant package loads (golang/go#54473).
   167  //
   168  // Note that this test may be fragile, as it depends on specific structure to
   169  // log messages around reinitialization. Nevertheless, it is important for
   170  // guarding against accidentally duplicate reloading.
   171  func TestReloadOnlyOnce(t *testing.T) {
   172  	WithOptions(
   173  		ProxyFiles(workspaceProxy),
   174  		WorkspaceFolders("pkg"),
   175  	).Run(t, workspaceModule, func(t *testing.T, env *Env) {
   176  		dir := env.Sandbox.Workdir.URI("goodbye").Path()
   177  		goModWithReplace := fmt.Sprintf(`%s
   178  replace random.org => %s
   179  `, env.ReadWorkspaceFile("pkg/go.mod"), dir)
   180  		env.WriteWorkspaceFile("pkg/go.mod", goModWithReplace)
   181  		env.Await(
   182  			LogMatching(protocol.Info, `packages\.Load #\d+\n`, 2, false),
   183  		)
   184  	})
   185  }
   186  
   187  const workspaceModuleProxy = `
   188  -- example.com@v1.2.3/go.mod --
   189  module example.com
   190  
   191  go 1.12
   192  -- example.com@v1.2.3/blah/blah.go --
   193  package blah
   194  
   195  import "fmt"
   196  
   197  func SaySomething() {
   198  	fmt.Println("something")
   199  }
   200  -- b.com@v1.2.3/go.mod --
   201  module b.com
   202  
   203  go 1.12
   204  -- b.com@v1.2.3/b/b.go --
   205  package b
   206  
   207  func Hello() {}
   208  `
   209  
   210  const multiModule = `
   211  -- moda/a/go.mod --
   212  module a.com
   213  
   214  require b.com v1.2.3
   215  -- moda/a/go.sum --
   216  b.com v1.2.3 h1:tXrlXP0rnjRpKNmkbLYoWBdq0ikb3C3bKK9//moAWBI=
   217  b.com v1.2.3/go.mod h1:D+J7pfFBZK5vdIdZEFquR586vKKIkqG7Qjw9AxG5BQ8=
   218  -- moda/a/a.go --
   219  package a
   220  
   221  import (
   222  	"b.com/b"
   223  )
   224  
   225  func main() {
   226  	var x int
   227  	_ = b.Hello()
   228  }
   229  -- modb/go.mod --
   230  module b.com
   231  
   232  -- modb/b/b.go --
   233  package b
   234  
   235  func Hello() int {
   236  	var x int
   237  }
   238  `
   239  
   240  func TestAutomaticWorkspaceModule_Interdependent(t *testing.T) {
   241  	WithOptions(
   242  		ProxyFiles(workspaceModuleProxy),
   243  	).Run(t, multiModule, func(t *testing.T, env *Env) {
   244  		env.RunGoCommand("work", "init")
   245  		env.RunGoCommand("work", "use", "-r", ".")
   246  		env.AfterChange(
   247  			Diagnostics(env.AtRegexp("moda/a/a.go", "x")),
   248  			Diagnostics(env.AtRegexp("modb/b/b.go", "x")),
   249  			NoDiagnostics(env.AtRegexp("moda/a/a.go", `"b.com/b"`)),
   250  		)
   251  	})
   252  }
   253  
   254  func TestWorkspaceVendoring(t *testing.T) {
   255  	testenv.NeedsGo1Point(t, 22)
   256  	WithOptions(
   257  		ProxyFiles(workspaceModuleProxy),
   258  	).Run(t, multiModule, func(t *testing.T, env *Env) {
   259  		env.RunGoCommand("work", "init")
   260  		env.RunGoCommand("work", "use", "moda/a")
   261  		env.AfterChange()
   262  		env.OpenFile("moda/a/a.go")
   263  		env.RunGoCommand("work", "vendor")
   264  		env.AfterChange()
   265  		loc := env.GoToDefinition(env.RegexpSearch("moda/a/a.go", "b.(Hello)"))
   266  		const want = "vendor/b.com/b/b.go"
   267  		if got := env.Sandbox.Workdir.URIToPath(loc.URI); got != want {
   268  			t.Errorf("Definition: got location %q, want %q", got, want)
   269  		}
   270  	})
   271  }
   272  
   273  func TestModuleWithExclude(t *testing.T) {
   274  	const proxy = `
   275  -- c.com@v1.2.3/go.mod --
   276  module c.com
   277  
   278  go 1.12
   279  
   280  require b.com v1.2.3
   281  -- c.com@v1.2.3/blah/blah.go --
   282  package blah
   283  
   284  import "fmt"
   285  
   286  func SaySomething() {
   287  	fmt.Println("something")
   288  }
   289  -- b.com@v1.2.3/go.mod --
   290  module b.com
   291  
   292  go 1.12
   293  -- b.com@v1.2.4/b/b.go --
   294  package b
   295  
   296  func Hello() {}
   297  -- b.com@v1.2.4/go.mod --
   298  module b.com
   299  
   300  go 1.12
   301  `
   302  	const files = `
   303  -- go.mod --
   304  module a.com
   305  
   306  require c.com v1.2.3
   307  
   308  exclude b.com v1.2.3
   309  -- go.sum --
   310  c.com v1.2.3 h1:n07Dz9fYmpNqvZMwZi5NEqFcSHbvLa9lacMX+/g25tw=
   311  c.com v1.2.3/go.mod h1:/4TyYgU9Nu5tA4NymP5xyqE8R2VMzGD3TbJCwCOvHAg=
   312  -- main.go --
   313  package a
   314  
   315  func main() {
   316  	var x int
   317  }
   318  `
   319  	WithOptions(
   320  		ProxyFiles(proxy),
   321  	).Run(t, files, func(t *testing.T, env *Env) {
   322  		env.OnceMet(
   323  			InitialWorkspaceLoad,
   324  			Diagnostics(env.AtRegexp("main.go", "x")),
   325  		)
   326  	})
   327  }
   328  
   329  // This change tests that the version of the module used changes after it has
   330  // been deleted from the workspace.
   331  //
   332  // TODO(golang/go#55331): delete this placeholder along with experimental
   333  // workspace module.
   334  func TestDeleteModule_Interdependent(t *testing.T) {
   335  	const multiModule = `
   336  -- go.work --
   337  go 1.18
   338  
   339  use (
   340  	moda/a
   341  	modb
   342  )
   343  -- moda/a/go.mod --
   344  module a.com
   345  
   346  require b.com v1.2.3
   347  -- moda/a/go.sum --
   348  b.com v1.2.3 h1:tXrlXP0rnjRpKNmkbLYoWBdq0ikb3C3bKK9//moAWBI=
   349  b.com v1.2.3/go.mod h1:D+J7pfFBZK5vdIdZEFquR586vKKIkqG7Qjw9AxG5BQ8=
   350  -- moda/a/a.go --
   351  package a
   352  
   353  import (
   354  	"b.com/b"
   355  )
   356  
   357  func main() {
   358  	var x int
   359  	_ = b.Hello()
   360  }
   361  -- modb/go.mod --
   362  module b.com
   363  
   364  -- modb/b/b.go --
   365  package b
   366  
   367  func Hello() int {
   368  	var x int
   369  }
   370  `
   371  	WithOptions(
   372  		ProxyFiles(workspaceModuleProxy),
   373  	).Run(t, multiModule, func(t *testing.T, env *Env) {
   374  		env.OpenFile("moda/a/a.go")
   375  		env.Await(env.DoneWithOpen())
   376  
   377  		originalLoc := env.GoToDefinition(env.RegexpSearch("moda/a/a.go", "Hello"))
   378  		original := env.Sandbox.Workdir.URIToPath(originalLoc.URI)
   379  		if want := "modb/b/b.go"; !strings.HasSuffix(original, want) {
   380  			t.Errorf("expected %s, got %v", want, original)
   381  		}
   382  		env.CloseBuffer(original)
   383  		env.AfterChange()
   384  
   385  		env.RemoveWorkspaceFile("modb/b/b.go")
   386  		env.RemoveWorkspaceFile("modb/go.mod")
   387  		env.WriteWorkspaceFile("go.work", "go 1.18\nuse moda/a")
   388  		env.AfterChange()
   389  
   390  		gotLoc := env.GoToDefinition(env.RegexpSearch("moda/a/a.go", "Hello"))
   391  		got := env.Sandbox.Workdir.URIToPath(gotLoc.URI)
   392  		if want := "b.com@v1.2.3/b/b.go"; !strings.HasSuffix(got, want) {
   393  			t.Errorf("expected %s, got %v", want, got)
   394  		}
   395  	})
   396  }
   397  
   398  // Tests that the version of the module used changes after it has been added
   399  // to the workspace.
   400  func TestCreateModule_Interdependent(t *testing.T) {
   401  	const multiModule = `
   402  -- go.work --
   403  go 1.18
   404  
   405  use (
   406  	moda/a
   407  )
   408  -- moda/a/go.mod --
   409  module a.com
   410  
   411  require b.com v1.2.3
   412  -- moda/a/go.sum --
   413  b.com v1.2.3 h1:tXrlXP0rnjRpKNmkbLYoWBdq0ikb3C3bKK9//moAWBI=
   414  b.com v1.2.3/go.mod h1:D+J7pfFBZK5vdIdZEFquR586vKKIkqG7Qjw9AxG5BQ8=
   415  -- moda/a/a.go --
   416  package a
   417  
   418  import (
   419  	"b.com/b"
   420  )
   421  
   422  func main() {
   423  	var x int
   424  	_ = b.Hello()
   425  }
   426  `
   427  	WithOptions(
   428  		ProxyFiles(workspaceModuleProxy),
   429  	).Run(t, multiModule, func(t *testing.T, env *Env) {
   430  		env.OpenFile("moda/a/a.go")
   431  		loc := env.GoToDefinition(env.RegexpSearch("moda/a/a.go", "Hello"))
   432  		original := env.Sandbox.Workdir.URIToPath(loc.URI)
   433  		if want := "b.com@v1.2.3/b/b.go"; !strings.HasSuffix(original, want) {
   434  			t.Errorf("expected %s, got %v", want, original)
   435  		}
   436  		env.CloseBuffer(original)
   437  		env.WriteWorkspaceFiles(map[string]string{
   438  			"go.work": `go 1.18
   439  
   440  use (
   441  	moda/a
   442  	modb
   443  )
   444  `,
   445  			"modb/go.mod": "module b.com",
   446  			"modb/b/b.go": `package b
   447  
   448  func Hello() int {
   449  	var x int
   450  }
   451  `,
   452  		})
   453  		env.AfterChange(Diagnostics(env.AtRegexp("modb/b/b.go", "x")))
   454  		gotLoc := env.GoToDefinition(env.RegexpSearch("moda/a/a.go", "Hello"))
   455  		got := env.Sandbox.Workdir.URIToPath(gotLoc.URI)
   456  		if want := "modb/b/b.go"; !strings.HasSuffix(got, want) {
   457  			t.Errorf("expected %s, got %v", want, original)
   458  		}
   459  	})
   460  }
   461  
   462  // This test confirms that a gopls workspace can recover from initialization
   463  // with one invalid module.
   464  func TestOneBrokenModule(t *testing.T) {
   465  	const multiModule = `
   466  -- go.work --
   467  go 1.18
   468  
   469  use (
   470  	moda/a
   471  	modb
   472  )
   473  -- moda/a/go.mod --
   474  module a.com
   475  
   476  require b.com v1.2.3
   477  
   478  -- moda/a/a.go --
   479  package a
   480  
   481  import (
   482  	"b.com/b"
   483  )
   484  
   485  func main() {
   486  	var x int
   487  	_ = b.Hello()
   488  }
   489  -- modb/go.mod --
   490  modul b.com // typo here
   491  
   492  -- modb/b/b.go --
   493  package b
   494  
   495  func Hello() int {
   496  	var x int
   497  }
   498  `
   499  	WithOptions(
   500  		ProxyFiles(workspaceModuleProxy),
   501  	).Run(t, multiModule, func(t *testing.T, env *Env) {
   502  		env.OpenFile("modb/go.mod")
   503  		env.AfterChange(
   504  			Diagnostics(AtPosition("modb/go.mod", 0, 0)),
   505  		)
   506  		env.RegexpReplace("modb/go.mod", "modul", "module")
   507  		env.SaveBufferWithoutActions("modb/go.mod")
   508  		env.AfterChange(
   509  			Diagnostics(env.AtRegexp("modb/b/b.go", "x")),
   510  		)
   511  	})
   512  }
   513  
   514  // TestBadGoWork exercises the panic from golang/vscode-go#2121.
   515  func TestBadGoWork(t *testing.T) {
   516  	const files = `
   517  -- go.work --
   518  use ./bar
   519  -- bar/go.mod --
   520  module example.com/bar
   521  `
   522  	Run(t, files, func(t *testing.T, env *Env) {
   523  		env.OpenFile("go.work")
   524  	})
   525  }
   526  
   527  func TestUseGoWork(t *testing.T) {
   528  	// This test validates certain functionality related to using a go.work
   529  	// file to specify workspace modules.
   530  	const multiModule = `
   531  -- moda/a/go.mod --
   532  module a.com
   533  
   534  require b.com v1.2.3
   535  -- moda/a/go.sum --
   536  b.com v1.2.3 h1:tXrlXP0rnjRpKNmkbLYoWBdq0ikb3C3bKK9//moAWBI=
   537  b.com v1.2.3/go.mod h1:D+J7pfFBZK5vdIdZEFquR586vKKIkqG7Qjw9AxG5BQ8=
   538  -- moda/a/a.go --
   539  package a
   540  
   541  import (
   542  	"b.com/b"
   543  )
   544  
   545  func main() {
   546  	var x int
   547  	_ = b.Hello()
   548  }
   549  -- modb/go.mod --
   550  module b.com
   551  
   552  require example.com v1.2.3
   553  -- modb/go.sum --
   554  example.com v1.2.3 h1:Yryq11hF02fEf2JlOS2eph+ICE2/ceevGV3C9dl5V/c=
   555  example.com v1.2.3/go.mod h1:Y2Rc5rVWjWur0h3pd9aEvK5Pof8YKDANh9gHA2Maujo=
   556  -- modb/b/b.go --
   557  package b
   558  
   559  func Hello() int {
   560  	var x int
   561  }
   562  -- go.work --
   563  go 1.17
   564  
   565  use (
   566  	./moda/a
   567  )
   568  `
   569  	WithOptions(
   570  		ProxyFiles(workspaceModuleProxy),
   571  		Settings{
   572  			"subdirWatchPatterns": "on",
   573  		},
   574  	).Run(t, multiModule, func(t *testing.T, env *Env) {
   575  		// Initially, the go.work should cause only the a.com module to be loaded,
   576  		// so we shouldn't get any file watches for modb. Further validate this by
   577  		// jumping to a definition in b.com and ensuring that we go to the module
   578  		// cache.
   579  		env.OnceMet(
   580  			InitialWorkspaceLoad,
   581  			NoFileWatchMatching("modb"),
   582  		)
   583  		env.OpenFile("moda/a/a.go")
   584  		env.Await(env.DoneWithOpen())
   585  
   586  		// To verify which modules are loaded, we'll jump to the definition of
   587  		// b.Hello.
   588  		checkHelloLocation := func(want string) error {
   589  			loc := env.GoToDefinition(env.RegexpSearch("moda/a/a.go", "Hello"))
   590  			file := env.Sandbox.Workdir.URIToPath(loc.URI)
   591  			if !strings.HasSuffix(file, want) {
   592  				return fmt.Errorf("expected %s, got %v", want, file)
   593  			}
   594  			return nil
   595  		}
   596  
   597  		// Initially this should be in the module cache, as b.com is not replaced.
   598  		if err := checkHelloLocation("b.com@v1.2.3/b/b.go"); err != nil {
   599  			t.Fatal(err)
   600  		}
   601  
   602  		// Now, modify the go.work file on disk to activate the b.com module in
   603  		// the workspace.
   604  		env.WriteWorkspaceFile("go.work", `
   605  go 1.17
   606  
   607  use (
   608  	./moda/a
   609  	./modb
   610  )
   611  `)
   612  
   613  		// As of golang/go#54069, writing go.work to the workspace triggers a
   614  		// workspace reload, and new file watches.
   615  		env.AfterChange(
   616  			Diagnostics(env.AtRegexp("modb/b/b.go", "x")),
   617  			// TODO(golang/go#60340): we don't get a file watch yet, because
   618  			// updateWatchedDirectories runs before snapshot.load. Instead, we get it
   619  			// after the next change (the didOpen below).
   620  			// FileWatchMatching("modb"),
   621  		)
   622  
   623  		// Jumping to definition should now go to b.com in the workspace.
   624  		if err := checkHelloLocation("modb/b/b.go"); err != nil {
   625  			t.Fatal(err)
   626  		}
   627  
   628  		// Now, let's modify the go.work *overlay* (not on disk), and verify that
   629  		// this change is only picked up once it is saved.
   630  		env.OpenFile("go.work")
   631  		env.AfterChange(
   632  			// TODO(golang/go#60340): delete this expectation in favor of
   633  			// the commented-out expectation above, once we fix the evaluation order
   634  			// of file watches. We should not have to wait for a second change to get
   635  			// the correct watches.
   636  			FileWatchMatching("modb"),
   637  		)
   638  		env.SetBufferContent("go.work", `go 1.17
   639  
   640  use (
   641  	./moda/a
   642  )`)
   643  
   644  		// Simply modifying the go.work file does not cause a reload, so we should
   645  		// still jump within the workspace.
   646  		//
   647  		// TODO: should editing the go.work above cause modb diagnostics to be
   648  		// suppressed?
   649  		env.Await(env.DoneWithChange())
   650  		if err := checkHelloLocation("modb/b/b.go"); err != nil {
   651  			t.Fatal(err)
   652  		}
   653  
   654  		// Saving should reload the workspace.
   655  		env.SaveBufferWithoutActions("go.work")
   656  		if err := checkHelloLocation("b.com@v1.2.3/b/b.go"); err != nil {
   657  			t.Fatal(err)
   658  		}
   659  
   660  		// This fails if guarded with a OnceMet(DoneWithSave(), ...), because it is
   661  		// delayed (and therefore not synchronous with the change).
   662  		//
   663  		// Note: this check used to assert on NoDiagnostics, but with zero-config
   664  		// gopls we still have diagnostics.
   665  		env.Await(Diagnostics(ForFile("modb/go.mod"), WithMessage("example.com is not used")))
   666  
   667  		// Test Formatting.
   668  		env.SetBufferContent("go.work", `go 1.18
   669    use      (
   670  
   671  
   672  
   673  		./moda/a
   674  )
   675  `) // TODO(matloob): For some reason there's a "start position 7:0 is out of bounds" error when the ")" is on the last character/line in the file. Rob probably knows what's going on.
   676  		env.SaveBuffer("go.work")
   677  		env.Await(env.DoneWithSave())
   678  		gotWorkContents := env.ReadWorkspaceFile("go.work")
   679  		wantWorkContents := `go 1.18
   680  
   681  use (
   682  	./moda/a
   683  )
   684  `
   685  		if gotWorkContents != wantWorkContents {
   686  			t.Fatalf("formatted contents of workspace: got %q; want %q", gotWorkContents, wantWorkContents)
   687  		}
   688  	})
   689  }
   690  
   691  func TestUseGoWorkDiagnosticMissingModule(t *testing.T) {
   692  	const files = `
   693  -- go.work --
   694  go 1.18
   695  
   696  use ./foo
   697  -- bar/go.mod --
   698  module example.com/bar
   699  `
   700  	Run(t, files, func(t *testing.T, env *Env) {
   701  		env.OpenFile("go.work")
   702  		env.AfterChange(
   703  			Diagnostics(env.AtRegexp("go.work", "use"), WithMessage("directory ./foo does not contain a module")),
   704  		)
   705  		// The following tests is a regression test against an issue where we weren't
   706  		// copying the workFile struct field on workspace when a new one was created in
   707  		// (*workspace).invalidate. Set the buffer content to a working file so that
   708  		// invalidate recognizes the workspace to be change and copies over the workspace
   709  		// struct, and then set the content back to the old contents to make sure
   710  		// the diagnostic still shows up.
   711  		env.SetBufferContent("go.work", "go 1.18 \n\n use ./bar\n")
   712  		env.AfterChange(
   713  			NoDiagnostics(env.AtRegexp("go.work", "use")),
   714  		)
   715  		env.SetBufferContent("go.work", "go 1.18 \n\n use ./foo\n")
   716  		env.AfterChange(
   717  			Diagnostics(env.AtRegexp("go.work", "use"), WithMessage("directory ./foo does not contain a module")),
   718  		)
   719  	})
   720  }
   721  
   722  func TestUseGoWorkDiagnosticSyntaxError(t *testing.T) {
   723  	const files = `
   724  -- go.work --
   725  go 1.18
   726  
   727  usa ./foo
   728  replace
   729  `
   730  	Run(t, files, func(t *testing.T, env *Env) {
   731  		env.OpenFile("go.work")
   732  		env.AfterChange(
   733  			Diagnostics(env.AtRegexp("go.work", "usa"), WithMessage("unknown directive: usa")),
   734  			Diagnostics(env.AtRegexp("go.work", "replace"), WithMessage("usage: replace")),
   735  		)
   736  	})
   737  }
   738  
   739  func TestUseGoWorkHover(t *testing.T) {
   740  	const files = `
   741  -- go.work --
   742  go 1.18
   743  
   744  use ./foo
   745  use (
   746  	./bar
   747  	./bar/baz
   748  )
   749  -- foo/go.mod --
   750  module example.com/foo
   751  -- bar/go.mod --
   752  module example.com/bar
   753  -- bar/baz/go.mod --
   754  module example.com/bar/baz
   755  `
   756  	Run(t, files, func(t *testing.T, env *Env) {
   757  		env.OpenFile("go.work")
   758  
   759  		tcs := map[string]string{
   760  			`\./foo`:      "example.com/foo",
   761  			`(?m)\./bar$`: "example.com/bar",
   762  			`\./bar/baz`:  "example.com/bar/baz",
   763  		}
   764  
   765  		for hoverRE, want := range tcs {
   766  			got, _ := env.Hover(env.RegexpSearch("go.work", hoverRE))
   767  			if got.Value != want {
   768  				t.Errorf(`hover on %q: got %q, want %q`, hoverRE, got, want)
   769  			}
   770  		}
   771  	})
   772  }
   773  
   774  func TestExpandToGoWork(t *testing.T) {
   775  	const workspace = `
   776  -- moda/a/go.mod --
   777  module a.com
   778  
   779  require b.com v1.2.3
   780  -- moda/a/a.go --
   781  package a
   782  
   783  import (
   784  	"b.com/b"
   785  )
   786  
   787  func main() {
   788  	var x int
   789  	_ = b.Hello()
   790  }
   791  -- modb/go.mod --
   792  module b.com
   793  
   794  require example.com v1.2.3
   795  -- modb/b/b.go --
   796  package b
   797  
   798  func Hello() int {
   799  	var x int
   800  }
   801  -- go.work --
   802  go 1.17
   803  
   804  use (
   805  	./moda/a
   806  	./modb
   807  )
   808  `
   809  	WithOptions(
   810  		WorkspaceFolders("moda/a"),
   811  	).Run(t, workspace, func(t *testing.T, env *Env) {
   812  		env.OpenFile("moda/a/a.go")
   813  		env.Await(env.DoneWithOpen())
   814  		loc := env.GoToDefinition(env.RegexpSearch("moda/a/a.go", "Hello"))
   815  		file := env.Sandbox.Workdir.URIToPath(loc.URI)
   816  		want := "modb/b/b.go"
   817  		if !strings.HasSuffix(file, want) {
   818  			t.Errorf("expected %s, got %v", want, file)
   819  		}
   820  	})
   821  }
   822  
   823  func TestInnerGoWork(t *testing.T) {
   824  	// This test checks that gopls honors a go.work file defined
   825  	// inside a go module (golang/go#63917).
   826  	const workspace = `
   827  -- go.mod --
   828  module a.com
   829  
   830  require b.com v1.2.3
   831  -- a/go.work --
   832  go 1.18
   833  
   834  use (
   835  	..
   836  	../b
   837  )
   838  -- a/a.go --
   839  package a
   840  
   841  import "b.com/b"
   842  
   843  var _ = b.B
   844  -- b/go.mod --
   845  module b.com/b
   846  
   847  -- b/b.go --
   848  package b
   849  
   850  const B = 0
   851  `
   852  	WithOptions(
   853  		// This doesn't work if we open the outer module. I'm not sure it should,
   854  		// since the go.work file does not apply to the entire module, just a
   855  		// subdirectory.
   856  		WorkspaceFolders("a"),
   857  	).Run(t, workspace, func(t *testing.T, env *Env) {
   858  		env.OpenFile("a/a.go")
   859  		loc := env.GoToDefinition(env.RegexpSearch("a/a.go", "b.(B)"))
   860  		got := env.Sandbox.Workdir.URIToPath(loc.URI)
   861  		want := "b/b.go"
   862  		if got != want {
   863  			t.Errorf("Definition(b.B): got %q, want %q", got, want)
   864  		}
   865  	})
   866  }
   867  
   868  func TestNonWorkspaceFileCreation(t *testing.T) {
   869  	const files = `
   870  -- work/go.mod --
   871  module mod.com
   872  
   873  go 1.12
   874  -- work/x.go --
   875  package x
   876  `
   877  
   878  	const code = `
   879  package foo
   880  import "fmt"
   881  var _ = fmt.Printf
   882  `
   883  	WithOptions(
   884  		WorkspaceFolders("work"), // so that outside/... is outside the workspace
   885  	).Run(t, files, func(t *testing.T, env *Env) {
   886  		env.CreateBuffer("outside/foo.go", "")
   887  		env.EditBuffer("outside/foo.go", fake.NewEdit(0, 0, 0, 0, code))
   888  		env.GoToDefinition(env.RegexpSearch("outside/foo.go", `Printf`))
   889  	})
   890  }
   891  
   892  func TestGoWork_V2Module(t *testing.T) {
   893  	// When using a go.work, we must have proxy content even if it is replaced.
   894  	const proxy = `
   895  -- b.com/v2@v2.1.9/go.mod --
   896  module b.com/v2
   897  
   898  go 1.12
   899  -- b.com/v2@v2.1.9/b/b.go --
   900  package b
   901  
   902  func Ciao()() int {
   903  	return 0
   904  }
   905  `
   906  
   907  	const multiModule = `
   908  -- go.work --
   909  go 1.18
   910  
   911  use (
   912  	moda/a
   913  	modb
   914  	modb/v2
   915  	modc
   916  )
   917  -- moda/a/go.mod --
   918  module a.com
   919  
   920  require b.com/v2 v2.1.9
   921  -- moda/a/a.go --
   922  package a
   923  
   924  import (
   925  	"b.com/v2/b"
   926  )
   927  
   928  func main() {
   929  	var x int
   930  	_ = b.Hi()
   931  }
   932  -- modb/go.mod --
   933  module b.com
   934  
   935  -- modb/b/b.go --
   936  package b
   937  
   938  func Hello() int {
   939  	var x int
   940  }
   941  -- modb/v2/go.mod --
   942  module b.com/v2
   943  
   944  -- modb/v2/b/b.go --
   945  package b
   946  
   947  func Hi() int {
   948  	var x int
   949  }
   950  -- modc/go.mod --
   951  module gopkg.in/yaml.v1 // test gopkg.in versions
   952  -- modc/main.go --
   953  package main
   954  
   955  func main() {
   956  	var x int
   957  }
   958  `
   959  
   960  	WithOptions(
   961  		ProxyFiles(proxy),
   962  	).Run(t, multiModule, func(t *testing.T, env *Env) {
   963  		env.OnceMet(
   964  			InitialWorkspaceLoad,
   965  			// TODO(rfindley): assert on the full set of diagnostics here. We
   966  			// should ensure that we don't have a diagnostic at b.Hi in a.go.
   967  			Diagnostics(env.AtRegexp("moda/a/a.go", "x")),
   968  			Diagnostics(env.AtRegexp("modb/b/b.go", "x")),
   969  			Diagnostics(env.AtRegexp("modb/v2/b/b.go", "x")),
   970  			Diagnostics(env.AtRegexp("modc/main.go", "x")),
   971  		)
   972  	})
   973  }
   974  
   975  // Confirm that a fix for a tidy module will correct all modules in the
   976  // workspace.
   977  func TestMultiModule_OneBrokenModule(t *testing.T) {
   978  	// In the earlier 'experimental workspace mode', gopls would aggregate go.sum
   979  	// entries for the workspace module, allowing it to correctly associate
   980  	// missing go.sum with diagnostics. With go.work files, this doesn't work:
   981  	// the go.command will happily write go.work.sum.
   982  	t.Skip("golang/go#57509: go.mod diagnostics do not work in go.work mode")
   983  	const files = `
   984  -- go.work --
   985  go 1.18
   986  
   987  use (
   988  	a
   989  	b
   990  )
   991  -- go.work.sum --
   992  -- a/go.mod --
   993  module a.com
   994  
   995  go 1.12
   996  -- a/main.go --
   997  package main
   998  -- b/go.mod --
   999  module b.com
  1000  
  1001  go 1.12
  1002  
  1003  require (
  1004  	example.com v1.2.3
  1005  )
  1006  -- b/go.sum --
  1007  -- b/main.go --
  1008  package b
  1009  
  1010  import "example.com/blah"
  1011  
  1012  func main() {
  1013  	blah.Hello()
  1014  }
  1015  `
  1016  	WithOptions(
  1017  		ProxyFiles(workspaceProxy),
  1018  	).Run(t, files, func(t *testing.T, env *Env) {
  1019  		params := &protocol.PublishDiagnosticsParams{}
  1020  		env.OpenFile("b/go.mod")
  1021  		env.AfterChange(
  1022  			Diagnostics(
  1023  				env.AtRegexp("go.mod", `example.com v1.2.3`),
  1024  				WithMessage("go.sum is out of sync"),
  1025  			),
  1026  			ReadDiagnostics("b/go.mod", params),
  1027  		)
  1028  		for _, d := range params.Diagnostics {
  1029  			if !strings.Contains(d.Message, "go.sum is out of sync") {
  1030  				continue
  1031  			}
  1032  			actions := env.GetQuickFixes("b/go.mod", []protocol.Diagnostic{d})
  1033  			if len(actions) != 2 {
  1034  				t.Fatalf("expected 2 code actions, got %v", len(actions))
  1035  			}
  1036  			env.ApplyQuickFixes("b/go.mod", []protocol.Diagnostic{d})
  1037  		}
  1038  		env.AfterChange(
  1039  			NoDiagnostics(ForFile("b/go.mod")),
  1040  		)
  1041  	})
  1042  }
  1043  
  1044  // Tests the fix for golang/go#52500.
  1045  func TestChangeTestVariant_Issue52500(t *testing.T) {
  1046  	const src = `
  1047  -- go.mod --
  1048  module mod.test
  1049  
  1050  go 1.12
  1051  -- main_test.go --
  1052  package main_test
  1053  
  1054  type Server struct{}
  1055  
  1056  const mainConst = otherConst
  1057  -- other_test.go --
  1058  package main_test
  1059  
  1060  const otherConst = 0
  1061  
  1062  func (Server) Foo() {}
  1063  `
  1064  
  1065  	Run(t, src, func(t *testing.T, env *Env) {
  1066  		env.OpenFile("other_test.go")
  1067  		env.RegexpReplace("other_test.go", "main_test", "main")
  1068  
  1069  		// For this test to function, it is necessary to wait on both of the
  1070  		// expectations below: the bug is that when switching the package name in
  1071  		// other_test.go from main->main_test, metadata for main_test is not marked
  1072  		// as invalid. So we need to wait for the metadata of main_test.go to be
  1073  		// updated before moving other_test.go back to the main_test package.
  1074  		env.Await(
  1075  			Diagnostics(env.AtRegexp("other_test.go", "Server")),
  1076  			Diagnostics(env.AtRegexp("main_test.go", "otherConst")),
  1077  		)
  1078  		env.RegexpReplace("other_test.go", "main", "main_test")
  1079  		env.AfterChange(
  1080  			NoDiagnostics(ForFile("other_test.go")),
  1081  			NoDiagnostics(ForFile("main_test.go")),
  1082  		)
  1083  
  1084  		// This will cause a test failure if other_test.go is not in any package.
  1085  		_ = env.GoToDefinition(env.RegexpSearch("other_test.go", "Server"))
  1086  	})
  1087  }
  1088  
  1089  // Test for golang/go#48929.
  1090  func TestClearNonWorkspaceDiagnostics(t *testing.T) {
  1091  	const ws = `
  1092  -- go.work --
  1093  go 1.18
  1094  
  1095  use (
  1096          ./b
  1097  )
  1098  -- a/go.mod --
  1099  module a
  1100  
  1101  go 1.17
  1102  -- a/main.go --
  1103  package main
  1104  
  1105  func main() {
  1106     var V string
  1107  }
  1108  -- b/go.mod --
  1109  module b
  1110  
  1111  go 1.17
  1112  -- b/main.go --
  1113  package b
  1114  
  1115  import (
  1116          _ "fmt"
  1117  )
  1118  `
  1119  	Run(t, ws, func(t *testing.T, env *Env) {
  1120  		env.OpenFile("b/main.go")
  1121  		env.AfterChange(
  1122  			NoDiagnostics(ForFile("a/main.go")),
  1123  		)
  1124  		env.OpenFile("a/main.go")
  1125  		env.AfterChange(
  1126  			Diagnostics(env.AtRegexp("a/main.go", "V"), WithMessage("not used")),
  1127  		)
  1128  		// Here, diagnostics are added because of zero-config gopls.
  1129  		// In the past, they were added simply due to diagnosing changed files.
  1130  		// (see TestClearNonWorkspaceDiagnostics_NoView below for a
  1131  		// reimplementation of that test).
  1132  		if got, want := len(env.Views()), 2; got != want {
  1133  			t.Errorf("after opening a/main.go, got %d views, want %d", got, want)
  1134  		}
  1135  		env.CloseBuffer("a/main.go")
  1136  		env.AfterChange(
  1137  			NoDiagnostics(ForFile("a/main.go")),
  1138  		)
  1139  		if got, want := len(env.Views()), 1; got != want {
  1140  			t.Errorf("after closing a/main.go, got %d views, want %d", got, want)
  1141  		}
  1142  	})
  1143  }
  1144  
  1145  // This test is like TestClearNonWorkspaceDiagnostics, but bypasses the
  1146  // zero-config algorithm by opening a nested workspace folder.
  1147  //
  1148  // We should still compute diagnostics correctly for open packages.
  1149  func TestClearNonWorkspaceDiagnostics_NoView(t *testing.T) {
  1150  	const ws = `
  1151  -- a/go.mod --
  1152  module example.com/a
  1153  
  1154  go 1.18
  1155  
  1156  require example.com/b v1.2.3
  1157  
  1158  replace example.com/b => ../b
  1159  
  1160  -- a/a.go --
  1161  package a
  1162  
  1163  import "example.com/b"
  1164  
  1165  func _() {
  1166  	V := b.B // unused
  1167  }
  1168  
  1169  -- b/go.mod --
  1170  module b
  1171  
  1172  go 1.18
  1173  
  1174  -- b/b.go --
  1175  package b
  1176  
  1177  const B = 2
  1178  
  1179  func _() {
  1180  	var V int // unused
  1181  }
  1182  
  1183  -- b/b2.go --
  1184  package b
  1185  
  1186  const B2 = B
  1187  
  1188  -- c/c.go --
  1189  package main
  1190  
  1191  func main() {
  1192  	var V int // unused
  1193  }
  1194  `
  1195  	WithOptions(
  1196  		WorkspaceFolders("a"),
  1197  	).Run(t, ws, func(t *testing.T, env *Env) {
  1198  		env.OpenFile("a/a.go")
  1199  		env.AfterChange(
  1200  			Diagnostics(env.AtRegexp("a/a.go", "V"), WithMessage("not used")),
  1201  			NoDiagnostics(ForFile("b/b.go")),
  1202  			NoDiagnostics(ForFile("c/c.go")),
  1203  		)
  1204  		env.OpenFile("b/b.go")
  1205  		env.AfterChange(
  1206  			Diagnostics(env.AtRegexp("a/a.go", "V"), WithMessage("not used")),
  1207  			Diagnostics(env.AtRegexp("b/b.go", "V"), WithMessage("not used")),
  1208  			NoDiagnostics(ForFile("c/c.go")),
  1209  		)
  1210  
  1211  		// Opening b/b.go should not result in a new view, because b is not
  1212  		// contained in a workspace folder.
  1213  		//
  1214  		// Yet we should get diagnostics for b, because it is open.
  1215  		if got, want := len(env.Views()), 1; got != want {
  1216  			t.Errorf("after opening b/b.go, got %d views, want %d", got, want)
  1217  		}
  1218  		env.CloseBuffer("b/b.go")
  1219  		env.AfterChange(
  1220  			Diagnostics(env.AtRegexp("a/a.go", "V"), WithMessage("not used")),
  1221  			NoDiagnostics(ForFile("b/b.go")),
  1222  			NoDiagnostics(ForFile("c/c.go")),
  1223  		)
  1224  
  1225  		// We should get references in the b package.
  1226  		bUse := env.RegexpSearch("a/a.go", `b\.(B)`)
  1227  		refs := env.References(bUse)
  1228  		wantRefs := []string{"a/a.go", "b/b.go", "b/b2.go"}
  1229  		var gotRefs []string
  1230  		for _, ref := range refs {
  1231  			gotRefs = append(gotRefs, env.Sandbox.Workdir.URIToPath(ref.URI))
  1232  		}
  1233  		sort.Strings(gotRefs)
  1234  		if diff := cmp.Diff(wantRefs, gotRefs); diff != "" {
  1235  			t.Errorf("references(b.B) mismatch (-want +got)\n%s", diff)
  1236  		}
  1237  
  1238  		// Opening c/c.go should also not result in a new view, yet we should get
  1239  		// orphaned file diagnostics.
  1240  		env.OpenFile("c/c.go")
  1241  		env.AfterChange(
  1242  			Diagnostics(env.AtRegexp("a/a.go", "V"), WithMessage("not used")),
  1243  			NoDiagnostics(ForFile("b/b.go")),
  1244  			Diagnostics(env.AtRegexp("c/c.go", "V"), WithMessage("not used")),
  1245  		)
  1246  		if got, want := len(env.Views()), 1; got != want {
  1247  			t.Errorf("after opening b/b.go, got %d views, want %d", got, want)
  1248  		}
  1249  
  1250  		env.CloseBuffer("c/c.go")
  1251  		env.AfterChange(
  1252  			Diagnostics(env.AtRegexp("a/a.go", "V"), WithMessage("not used")),
  1253  			NoDiagnostics(ForFile("b/b.go")),
  1254  			NoDiagnostics(ForFile("c/c.go")),
  1255  		)
  1256  		env.CloseBuffer("a/a.go")
  1257  		env.AfterChange(
  1258  			Diagnostics(env.AtRegexp("a/a.go", "V"), WithMessage("not used")),
  1259  			NoDiagnostics(ForFile("b/b.go")),
  1260  			NoDiagnostics(ForFile("c/c.go")),
  1261  		)
  1262  	})
  1263  }
  1264  
  1265  // Test that we don't get a version warning when the Go version in PATH is
  1266  // supported.
  1267  func TestOldGoNotification_SupportedVersion(t *testing.T) {
  1268  	v := goVersion(t)
  1269  	if v < goversion.OldestSupported() {
  1270  		t.Skipf("go version 1.%d is unsupported", v)
  1271  	}
  1272  
  1273  	Run(t, "", func(t *testing.T, env *Env) {
  1274  		env.OnceMet(
  1275  			InitialWorkspaceLoad,
  1276  			NoShownMessage("upgrade"),
  1277  		)
  1278  	})
  1279  }
  1280  
  1281  // Test that we do get a version warning when the Go version in PATH is
  1282  // unsupported, though this test may never execute if we stop running CI at
  1283  // legacy Go versions (see also TestOldGoNotification_Fake)
  1284  func TestOldGoNotification_UnsupportedVersion(t *testing.T) {
  1285  	v := goVersion(t)
  1286  	if v >= goversion.OldestSupported() {
  1287  		t.Skipf("go version 1.%d is supported", v)
  1288  	}
  1289  
  1290  	Run(t, "", func(t *testing.T, env *Env) {
  1291  		env.Await(
  1292  			// Note: cannot use OnceMet(InitialWorkspaceLoad, ...) here, as the
  1293  			// upgrade message may race with the IWL.
  1294  			ShownMessage("Please upgrade"),
  1295  		)
  1296  	})
  1297  }
  1298  
  1299  func TestOldGoNotification_Fake(t *testing.T) {
  1300  	// Get the Go version from path, and make sure it's unsupported.
  1301  	//
  1302  	// In the future we'll stop running CI on legacy Go versions. By mutating the
  1303  	// oldest supported Go version here, we can at least ensure that the
  1304  	// ShowMessage pop-up works.
  1305  	ctx := context.Background()
  1306  	version, err := gocommand.GoVersion(ctx, gocommand.Invocation{}, &gocommand.Runner{})
  1307  	if err != nil {
  1308  		t.Fatal(err)
  1309  	}
  1310  	defer func(t []goversion.Support) {
  1311  		goversion.Supported = t
  1312  	}(goversion.Supported)
  1313  	goversion.Supported = []goversion.Support{
  1314  		{GoVersion: version, InstallGoplsVersion: "v1.0.0"},
  1315  	}
  1316  
  1317  	Run(t, "", func(t *testing.T, env *Env) {
  1318  		env.Await(
  1319  			// Note: cannot use OnceMet(InitialWorkspaceLoad, ...) here, as the
  1320  			// upgrade message may race with the IWL.
  1321  			ShownMessage("Please upgrade"),
  1322  		)
  1323  	})
  1324  }
  1325  
  1326  // goVersion returns the version of the Go command in PATH.
  1327  func goVersion(t *testing.T) int {
  1328  	t.Helper()
  1329  	ctx := context.Background()
  1330  	goversion, err := gocommand.GoVersion(ctx, gocommand.Invocation{}, &gocommand.Runner{})
  1331  	if err != nil {
  1332  		t.Fatal(err)
  1333  	}
  1334  	return goversion
  1335  }
  1336  
  1337  func TestGoworkMutation(t *testing.T) {
  1338  	WithOptions(
  1339  		ProxyFiles(workspaceModuleProxy),
  1340  	).Run(t, multiModule, func(t *testing.T, env *Env) {
  1341  		env.RunGoCommand("work", "init")
  1342  		env.RunGoCommand("work", "use", "-r", ".")
  1343  		env.AfterChange(
  1344  			Diagnostics(env.AtRegexp("moda/a/a.go", "x")),
  1345  			Diagnostics(env.AtRegexp("modb/b/b.go", "x")),
  1346  			NoDiagnostics(env.AtRegexp("moda/a/a.go", `b\.Hello`)),
  1347  		)
  1348  		env.RunGoCommand("work", "edit", "-dropuse", "modb")
  1349  		env.Await(
  1350  			Diagnostics(env.AtRegexp("moda/a/a.go", "x")),
  1351  			NoDiagnostics(env.AtRegexp("modb/b/b.go", "x")),
  1352  			Diagnostics(env.AtRegexp("moda/a/a.go", `b\.Hello`)),
  1353  		)
  1354  	})
  1355  }