golang.org/x/tools/gopls@v0.15.3/internal/test/integration/watch/watch_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 watch
     6  
     7  import (
     8  	"testing"
     9  
    10  	"golang.org/x/tools/gopls/internal/hooks"
    11  	. "golang.org/x/tools/gopls/internal/test/integration"
    12  	"golang.org/x/tools/gopls/internal/util/bug"
    13  
    14  	"golang.org/x/tools/gopls/internal/protocol"
    15  	"golang.org/x/tools/gopls/internal/test/integration/fake"
    16  )
    17  
    18  func TestMain(m *testing.M) {
    19  	bug.PanicOnBugs = true
    20  	Main(m, hooks.Options)
    21  }
    22  
    23  func TestEditFile(t *testing.T) {
    24  	const pkg = `
    25  -- go.mod --
    26  module mod.com
    27  
    28  go 1.14
    29  -- a/a.go --
    30  package a
    31  
    32  func _() {
    33  	var x int
    34  }
    35  `
    36  	// Edit the file when it's *not open* in the workspace, and check that
    37  	// diagnostics are updated.
    38  	t.Run("unopened", func(t *testing.T) {
    39  		Run(t, pkg, func(t *testing.T, env *Env) {
    40  			env.OnceMet(
    41  				InitialWorkspaceLoad,
    42  				Diagnostics(env.AtRegexp("a/a.go", "x")),
    43  			)
    44  			env.WriteWorkspaceFile("a/a.go", `package a; func _() {};`)
    45  			env.AfterChange(
    46  				NoDiagnostics(ForFile("a/a.go")),
    47  			)
    48  		})
    49  	})
    50  
    51  	// Edit the file when it *is open* in the workspace, and check that
    52  	// diagnostics are *not* updated.
    53  	t.Run("opened", func(t *testing.T) {
    54  		Run(t, pkg, func(t *testing.T, env *Env) {
    55  			env.OpenFile("a/a.go")
    56  			// Insert a trivial edit so that we don't automatically update the buffer
    57  			// (see CL 267577).
    58  			env.EditBuffer("a/a.go", fake.NewEdit(0, 0, 0, 0, " "))
    59  			env.AfterChange()
    60  			env.WriteWorkspaceFile("a/a.go", `package a; func _() {};`)
    61  			env.AfterChange(
    62  				Diagnostics(env.AtRegexp("a/a.go", "x")),
    63  			)
    64  		})
    65  	})
    66  }
    67  
    68  // Edit a dependency on disk and expect a new diagnostic.
    69  func TestEditDependency(t *testing.T) {
    70  	const pkg = `
    71  -- go.mod --
    72  module mod.com
    73  
    74  go 1.14
    75  -- b/b.go --
    76  package b
    77  
    78  func B() int { return 0 }
    79  -- a/a.go --
    80  package a
    81  
    82  import (
    83  	"mod.com/b"
    84  )
    85  
    86  func _() {
    87  	_ = b.B()
    88  }
    89  `
    90  	Run(t, pkg, func(t *testing.T, env *Env) {
    91  		env.OpenFile("a/a.go")
    92  		env.AfterChange()
    93  		env.WriteWorkspaceFile("b/b.go", `package b; func B() {};`)
    94  		env.AfterChange(
    95  			Diagnostics(env.AtRegexp("a/a.go", "b.B")),
    96  		)
    97  	})
    98  }
    99  
   100  // Edit both the current file and one of its dependencies on disk and
   101  // expect diagnostic changes.
   102  func TestEditFileAndDependency(t *testing.T) {
   103  	const pkg = `
   104  -- go.mod --
   105  module mod.com
   106  
   107  go 1.14
   108  -- b/b.go --
   109  package b
   110  
   111  func B() int { return 0 }
   112  -- a/a.go --
   113  package a
   114  
   115  import (
   116  	"mod.com/b"
   117  )
   118  
   119  func _() {
   120  	var x int
   121  	_ = b.B()
   122  }
   123  `
   124  	Run(t, pkg, func(t *testing.T, env *Env) {
   125  		env.OnceMet(
   126  			InitialWorkspaceLoad,
   127  			Diagnostics(env.AtRegexp("a/a.go", "x")),
   128  		)
   129  		env.WriteWorkspaceFiles(map[string]string{
   130  			"b/b.go": `package b; func B() {};`,
   131  			"a/a.go": `package a
   132  
   133  import "mod.com/b"
   134  
   135  func _() {
   136  	b.B()
   137  }`,
   138  		})
   139  		env.AfterChange(
   140  			NoDiagnostics(ForFile("a/a.go")),
   141  			NoDiagnostics(ForFile("b/b.go")),
   142  		)
   143  	})
   144  }
   145  
   146  // Delete a dependency and expect a new diagnostic.
   147  func TestDeleteDependency(t *testing.T) {
   148  	const pkg = `
   149  -- go.mod --
   150  module mod.com
   151  
   152  go 1.14
   153  -- b/b.go --
   154  package b
   155  
   156  func B() int { return 0 }
   157  -- a/a.go --
   158  package a
   159  
   160  import (
   161  	"mod.com/b"
   162  )
   163  
   164  func _() {
   165  	_ = b.B()
   166  }
   167  `
   168  	Run(t, pkg, func(t *testing.T, env *Env) {
   169  		env.OpenFile("a/a.go")
   170  		env.AfterChange()
   171  		env.RemoveWorkspaceFile("b/b.go")
   172  		env.AfterChange(
   173  			Diagnostics(env.AtRegexp("a/a.go", "\"mod.com/b\"")),
   174  		)
   175  	})
   176  }
   177  
   178  // Create a dependency on disk and expect the diagnostic to go away.
   179  func TestCreateDependency(t *testing.T) {
   180  	const missing = `
   181  -- go.mod --
   182  module mod.com
   183  
   184  go 1.14
   185  -- b/b.go --
   186  package b
   187  
   188  func B() int { return 0 }
   189  -- a/a.go --
   190  package a
   191  
   192  import (
   193  	"mod.com/c"
   194  )
   195  
   196  func _() {
   197  	c.C()
   198  }
   199  `
   200  	Run(t, missing, func(t *testing.T, env *Env) {
   201  		env.OnceMet(
   202  			InitialWorkspaceLoad,
   203  			Diagnostics(env.AtRegexp("a/a.go", "\"mod.com/c\"")),
   204  		)
   205  		env.WriteWorkspaceFile("c/c.go", `package c; func C() {};`)
   206  		env.AfterChange(
   207  			NoDiagnostics(ForFile("a/a.go")),
   208  		)
   209  	})
   210  }
   211  
   212  // Create a new dependency and add it to the file on disk.
   213  // This is similar to what might happen if you switch branches.
   214  func TestCreateAndAddDependency(t *testing.T) {
   215  	const original = `
   216  -- go.mod --
   217  module mod.com
   218  
   219  go 1.14
   220  -- a/a.go --
   221  package a
   222  
   223  func _() {}
   224  `
   225  	Run(t, original, func(t *testing.T, env *Env) {
   226  		env.WriteWorkspaceFile("c/c.go", `package c; func C() {};`)
   227  		env.WriteWorkspaceFile("a/a.go", `package a; import "mod.com/c"; func _() { c.C() }`)
   228  		env.AfterChange(
   229  			NoDiagnostics(ForFile("a/a.go")),
   230  		)
   231  	})
   232  }
   233  
   234  // Create a new file that defines a new symbol, in the same package.
   235  func TestCreateFile(t *testing.T) {
   236  	const pkg = `
   237  -- go.mod --
   238  module mod.com
   239  
   240  go 1.14
   241  -- a/a.go --
   242  package a
   243  
   244  func _() {
   245  	hello()
   246  }
   247  `
   248  	Run(t, pkg, func(t *testing.T, env *Env) {
   249  		env.OnceMet(
   250  			InitialWorkspaceLoad,
   251  			Diagnostics(env.AtRegexp("a/a.go", "hello")),
   252  		)
   253  		env.WriteWorkspaceFile("a/a2.go", `package a; func hello() {};`)
   254  		env.AfterChange(
   255  			NoDiagnostics(ForFile("a/a.go")),
   256  		)
   257  	})
   258  }
   259  
   260  // Add a new method to an interface and implement it.
   261  // Inspired by the structure of internal/golang and internal/cache.
   262  func TestCreateImplementation(t *testing.T) {
   263  	const pkg = `
   264  -- go.mod --
   265  module mod.com
   266  
   267  go 1.14
   268  -- b/b.go --
   269  package b
   270  
   271  type B interface{
   272  	Hello() string
   273  }
   274  
   275  func SayHello(bee B) {
   276  	println(bee.Hello())
   277  }
   278  -- a/a.go --
   279  package a
   280  
   281  import "mod.com/b"
   282  
   283  type X struct {}
   284  
   285  func (_ X) Hello() string {
   286  	return ""
   287  }
   288  
   289  func _() {
   290  	x := X{}
   291  	b.SayHello(x)
   292  }
   293  `
   294  	const newMethod = `package b
   295  type B interface{
   296  	Hello() string
   297  	Bye() string
   298  }
   299  
   300  func SayHello(bee B) {
   301  	println(bee.Hello())
   302  }`
   303  	const implementation = `package a
   304  
   305  import "mod.com/b"
   306  
   307  type X struct {}
   308  
   309  func (_ X) Hello() string {
   310  	return ""
   311  }
   312  
   313  func (_ X) Bye() string {
   314  	return ""
   315  }
   316  
   317  func _() {
   318  	x := X{}
   319  	b.SayHello(x)
   320  }`
   321  
   322  	// Add the new method before the implementation. Expect diagnostics.
   323  	t.Run("method before implementation", func(t *testing.T) {
   324  		Run(t, pkg, func(t *testing.T, env *Env) {
   325  			env.WriteWorkspaceFile("b/b.go", newMethod)
   326  			env.AfterChange(
   327  				Diagnostics(AtPosition("a/a.go", 12, 12)),
   328  			)
   329  			env.WriteWorkspaceFile("a/a.go", implementation)
   330  			env.AfterChange(
   331  				NoDiagnostics(ForFile("a/a.go")),
   332  			)
   333  		})
   334  	})
   335  	// Add the new implementation before the new method. Expect no diagnostics.
   336  	t.Run("implementation before method", func(t *testing.T) {
   337  		Run(t, pkg, func(t *testing.T, env *Env) {
   338  			env.WriteWorkspaceFile("a/a.go", implementation)
   339  			env.AfterChange(
   340  				NoDiagnostics(ForFile("a/a.go")),
   341  			)
   342  			env.WriteWorkspaceFile("b/b.go", newMethod)
   343  			env.AfterChange(
   344  				NoDiagnostics(ForFile("a/a.go")),
   345  			)
   346  		})
   347  	})
   348  	// Add both simultaneously. Expect no diagnostics.
   349  	t.Run("implementation and method simultaneously", func(t *testing.T) {
   350  		Run(t, pkg, func(t *testing.T, env *Env) {
   351  			env.WriteWorkspaceFiles(map[string]string{
   352  				"a/a.go": implementation,
   353  				"b/b.go": newMethod,
   354  			})
   355  			env.AfterChange(
   356  				NoDiagnostics(ForFile("a/a.go")),
   357  				NoDiagnostics(ForFile("b/b.go")),
   358  			)
   359  		})
   360  	})
   361  }
   362  
   363  // Tests golang/go#38498. Delete a file and then force a reload.
   364  // Assert that we no longer try to load the file.
   365  func TestDeleteFiles(t *testing.T) {
   366  	const pkg = `
   367  -- go.mod --
   368  module mod.com
   369  
   370  go 1.14
   371  -- a/a.go --
   372  package a
   373  
   374  func _() {
   375  	var _ int
   376  }
   377  -- a/a_unneeded.go --
   378  package a
   379  `
   380  	t.Run("close then delete", func(t *testing.T) {
   381  		WithOptions(
   382  			Settings{"verboseOutput": true},
   383  		).Run(t, pkg, func(t *testing.T, env *Env) {
   384  			env.OpenFile("a/a.go")
   385  			env.OpenFile("a/a_unneeded.go")
   386  			env.Await(
   387  				// Log messages are asynchronous to other events on the LSP stream, so we
   388  				// can't use OnceMet or AfterChange here.
   389  				LogMatching(protocol.Info, "a_unneeded.go", 1, false),
   390  			)
   391  
   392  			// Close and delete the open file, mimicking what an editor would do.
   393  			env.CloseBuffer("a/a_unneeded.go")
   394  			env.RemoveWorkspaceFile("a/a_unneeded.go")
   395  			env.RegexpReplace("a/a.go", "var _ int", "fmt.Println(\"\")")
   396  			env.AfterChange(
   397  				Diagnostics(env.AtRegexp("a/a.go", "fmt")),
   398  			)
   399  			env.SaveBuffer("a/a.go")
   400  			env.Await(
   401  				// There should only be one log message containing
   402  				// a_unneeded.go, from the initial workspace load, which we
   403  				// check for earlier. If there are more, there's a bug.
   404  				LogMatching(protocol.Info, "a_unneeded.go", 1, false),
   405  				NoDiagnostics(ForFile("a/a.go")),
   406  			)
   407  		})
   408  	})
   409  
   410  	t.Run("delete then close", func(t *testing.T) {
   411  		WithOptions(
   412  			Settings{"verboseOutput": true},
   413  		).Run(t, pkg, func(t *testing.T, env *Env) {
   414  			env.OpenFile("a/a.go")
   415  			env.OpenFile("a/a_unneeded.go")
   416  			env.Await(
   417  				LogMatching(protocol.Info, "a_unneeded.go", 1, false),
   418  			)
   419  
   420  			// Delete and then close the file.
   421  			env.RemoveWorkspaceFile("a/a_unneeded.go")
   422  			env.CloseBuffer("a/a_unneeded.go")
   423  			env.RegexpReplace("a/a.go", "var _ int", "fmt.Println(\"\")")
   424  			env.AfterChange(
   425  				Diagnostics(env.AtRegexp("a/a.go", "fmt")),
   426  			)
   427  			env.SaveBuffer("a/a.go")
   428  			env.Await(
   429  				// There should only be one log message containing
   430  				// a_unneeded.go, from the initial workspace load, which we
   431  				// check for earlier. If there are more, there's a bug.
   432  				LogMatching(protocol.Info, "a_unneeded.go", 1, false),
   433  				NoDiagnostics(ForFile("a/a.go")),
   434  			)
   435  		})
   436  	})
   437  }
   438  
   439  // This change reproduces the behavior of switching branches, with multiple
   440  // files being created and deleted. The key change here is the movement of a
   441  // symbol from one file to another in a given package through a deletion and
   442  // creation. To reproduce an issue with metadata invalidation in batched
   443  // changes, the last change in the batch is an on-disk file change that doesn't
   444  // require metadata invalidation.
   445  func TestMoveSymbol(t *testing.T) {
   446  	const pkg = `
   447  -- go.mod --
   448  module mod.com
   449  
   450  go 1.14
   451  -- main.go --
   452  package main
   453  
   454  import "mod.com/a"
   455  
   456  func main() {
   457  	var x int
   458  	x = a.Hello
   459  	println(x)
   460  }
   461  -- a/a1.go --
   462  package a
   463  
   464  var Hello int
   465  -- a/a2.go --
   466  package a
   467  
   468  func _() {}
   469  `
   470  	Run(t, pkg, func(t *testing.T, env *Env) {
   471  		env.WriteWorkspaceFile("a/a3.go", "package a\n\nvar Hello int\n")
   472  		env.RemoveWorkspaceFile("a/a1.go")
   473  		env.WriteWorkspaceFile("a/a2.go", "package a; func _() {};")
   474  		env.AfterChange(
   475  			NoDiagnostics(ForFile("main.go")),
   476  		)
   477  	})
   478  }
   479  
   480  // Reproduce golang/go#40456.
   481  func TestChangeVersion(t *testing.T) {
   482  	const proxy = `
   483  -- example.com@v1.2.3/go.mod --
   484  module example.com
   485  
   486  go 1.12
   487  -- example.com@v1.2.3/blah/blah.go --
   488  package blah
   489  
   490  const Name = "Blah"
   491  
   492  func X(x int) {}
   493  -- example.com@v1.2.2/go.mod --
   494  module example.com
   495  
   496  go 1.12
   497  -- example.com@v1.2.2/blah/blah.go --
   498  package blah
   499  
   500  const Name = "Blah"
   501  
   502  func X() {}
   503  -- random.org@v1.2.3/go.mod --
   504  module random.org
   505  
   506  go 1.12
   507  -- random.org@v1.2.3/blah/blah.go --
   508  package hello
   509  
   510  const Name = "Hello"
   511  `
   512  	const mod = `
   513  -- go.mod --
   514  module mod.com
   515  
   516  go 1.12
   517  
   518  require example.com v1.2.2
   519  -- go.sum --
   520  example.com v1.2.3 h1:OnPPkx+rW63kj9pgILsu12MORKhSlnFa3DVRJq1HZ7g=
   521  example.com v1.2.3/go.mod h1:Y2Rc5rVWjWur0h3pd9aEvK5Pof8YKDANh9gHA2Maujo=
   522  -- main.go --
   523  package main
   524  
   525  import "example.com/blah"
   526  
   527  func main() {
   528  	blah.X()
   529  }
   530  `
   531  	WithOptions(ProxyFiles(proxy)).Run(t, mod, func(t *testing.T, env *Env) {
   532  		env.WriteWorkspaceFiles(map[string]string{
   533  			"go.mod": `module mod.com
   534  
   535  go 1.12
   536  
   537  require example.com v1.2.3
   538  `,
   539  			"main.go": `package main
   540  
   541  import (
   542  	"example.com/blah"
   543  )
   544  
   545  func main() {
   546  	blah.X(1)
   547  }
   548  `,
   549  		})
   550  		env.AfterChange(
   551  			env.DoneWithChangeWatchedFiles(),
   552  			NoDiagnostics(ForFile("main.go")),
   553  		)
   554  	})
   555  }
   556  
   557  // Reproduces golang/go#40340.
   558  func TestSwitchFromGOPATHToModuleMode(t *testing.T) {
   559  	const files = `
   560  -- foo/blah/blah.go --
   561  package blah
   562  
   563  const Name = ""
   564  -- main.go --
   565  package main
   566  
   567  import "foo/blah"
   568  
   569  func main() {
   570  	_ = blah.Name
   571  }
   572  `
   573  	WithOptions(
   574  		InGOPATH(),
   575  		Modes(Default), // golang/go#57521: this test is temporarily failing in 'experimental' mode
   576  		EnvVars{"GO111MODULE": "auto"},
   577  	).Run(t, files, func(t *testing.T, env *Env) {
   578  		env.OpenFile("main.go")
   579  		env.AfterChange(
   580  			NoDiagnostics(ForFile("main.go")),
   581  		)
   582  		if err := env.Sandbox.RunGoCommand(env.Ctx, "", "mod", []string{"init", "mod.com"}, nil, true); err != nil {
   583  			t.Fatal(err)
   584  		}
   585  
   586  		// TODO(golang/go#57558, golang/go#57512): file watching is asynchronous,
   587  		// and we must wait for the view to be reconstructed before touching
   588  		// main.go, so that the new view "knows" about main.go. This is a bug, but
   589  		// awaiting the change here avoids it.
   590  		env.AfterChange()
   591  
   592  		env.RegexpReplace("main.go", `"foo/blah"`, `"mod.com/foo/blah"`)
   593  		env.AfterChange(
   594  			NoDiagnostics(ForFile("main.go")),
   595  		)
   596  	})
   597  }
   598  
   599  // Reproduces golang/go#40487.
   600  func TestSwitchFromModulesToGOPATH(t *testing.T) {
   601  	const files = `
   602  -- foo/go.mod --
   603  module mod.com
   604  
   605  go 1.14
   606  -- foo/blah/blah.go --
   607  package blah
   608  
   609  const Name = ""
   610  -- foo/main.go --
   611  package main
   612  
   613  import "mod.com/blah"
   614  
   615  func main() {
   616  	_ = blah.Name
   617  }
   618  `
   619  	WithOptions(
   620  		InGOPATH(),
   621  		EnvVars{"GO111MODULE": "auto"},
   622  	).Run(t, files, func(t *testing.T, env *Env) {
   623  		env.OpenFile("foo/main.go")
   624  		env.RemoveWorkspaceFile("foo/go.mod")
   625  		env.AfterChange(
   626  			Diagnostics(env.AtRegexp("foo/main.go", `"mod.com/blah"`)),
   627  		)
   628  		env.RegexpReplace("foo/main.go", `"mod.com/blah"`, `"foo/blah"`)
   629  		env.AfterChange(
   630  			NoDiagnostics(ForFile("foo/main.go")),
   631  		)
   632  	})
   633  }
   634  
   635  func TestNewSymbolInTestVariant(t *testing.T) {
   636  	const files = `
   637  -- go.mod --
   638  module mod.com
   639  
   640  go 1.12
   641  -- a/a.go --
   642  package a
   643  
   644  func bob() {}
   645  -- a/a_test.go --
   646  package a
   647  
   648  import "testing"
   649  
   650  func TestBob(t *testing.T) {
   651  	bob()
   652  }
   653  `
   654  	Run(t, files, func(t *testing.T, env *Env) {
   655  		// Add a new symbol to the package under test and use it in the test
   656  		// variant. Expect no diagnostics.
   657  		env.WriteWorkspaceFiles(map[string]string{
   658  			"a/a.go": `package a
   659  
   660  func bob() {}
   661  func george() {}
   662  `,
   663  			"a/a_test.go": `package a
   664  
   665  import "testing"
   666  
   667  func TestAll(t *testing.T) {
   668  	bob()
   669  	george()
   670  }
   671  `,
   672  		})
   673  		env.AfterChange(
   674  			NoDiagnostics(ForFile("a/a.go")),
   675  			NoDiagnostics(ForFile("a/a_test.go")),
   676  		)
   677  		// Now, add a new file to the test variant and use its symbol in the
   678  		// original test file. Expect no diagnostics.
   679  		env.WriteWorkspaceFiles(map[string]string{
   680  			"a/a_test.go": `package a
   681  
   682  import "testing"
   683  
   684  func TestAll(t *testing.T) {
   685  	bob()
   686  	george()
   687  	hi()
   688  }
   689  `,
   690  			"a/a2_test.go": `package a
   691  
   692  import "testing"
   693  
   694  func hi() {}
   695  
   696  func TestSomething(t *testing.T) {}
   697  `,
   698  		})
   699  		env.AfterChange(
   700  			NoDiagnostics(ForFile("a/a_test.go")),
   701  			NoDiagnostics(ForFile("a/a2_test.go")),
   702  		)
   703  	})
   704  }