golang.org/x/tools/gopls@v0.15.3/internal/test/integration/codelens/codelens_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 codelens
     6  
     7  import (
     8  	"fmt"
     9  	"testing"
    10  
    11  	"golang.org/x/tools/gopls/internal/hooks"
    12  	"golang.org/x/tools/gopls/internal/server"
    13  	"golang.org/x/tools/gopls/internal/test/compare"
    14  	. "golang.org/x/tools/gopls/internal/test/integration"
    15  	"golang.org/x/tools/gopls/internal/util/bug"
    16  
    17  	"golang.org/x/tools/gopls/internal/protocol"
    18  	"golang.org/x/tools/gopls/internal/protocol/command"
    19  	"golang.org/x/tools/internal/testenv"
    20  )
    21  
    22  func TestMain(m *testing.M) {
    23  	bug.PanicOnBugs = true
    24  	Main(m, hooks.Options)
    25  }
    26  
    27  func TestDisablingCodeLens(t *testing.T) {
    28  	const workspace = `
    29  -- go.mod --
    30  module codelens.test
    31  
    32  go 1.12
    33  -- lib.go --
    34  package lib
    35  
    36  type Number int
    37  
    38  const (
    39  	Zero Number = iota
    40  	One
    41  	Two
    42  )
    43  
    44  //` + `go:generate stringer -type=Number
    45  `
    46  	tests := []struct {
    47  		label        string
    48  		enabled      map[string]bool
    49  		wantCodeLens bool
    50  	}{
    51  		{
    52  			label:        "default",
    53  			wantCodeLens: true,
    54  		},
    55  		{
    56  			label:        "generate disabled",
    57  			enabled:      map[string]bool{string(command.Generate): false},
    58  			wantCodeLens: false,
    59  		},
    60  	}
    61  	for _, test := range tests {
    62  		t.Run(test.label, func(t *testing.T) {
    63  			WithOptions(
    64  				Settings{"codelenses": test.enabled},
    65  			).Run(t, workspace, func(t *testing.T, env *Env) {
    66  				env.OpenFile("lib.go")
    67  				lens := env.CodeLens("lib.go")
    68  				if gotCodeLens := len(lens) > 0; gotCodeLens != test.wantCodeLens {
    69  					t.Errorf("got codeLens: %t, want %t", gotCodeLens, test.wantCodeLens)
    70  				}
    71  			})
    72  		})
    73  	}
    74  }
    75  
    76  const proxyWithLatest = `
    77  -- golang.org/x/hello@v1.3.3/go.mod --
    78  module golang.org/x/hello
    79  
    80  go 1.12
    81  -- golang.org/x/hello@v1.3.3/hi/hi.go --
    82  package hi
    83  
    84  var Goodbye error
    85  -- golang.org/x/hello@v1.2.3/go.mod --
    86  module golang.org/x/hello
    87  
    88  go 1.12
    89  -- golang.org/x/hello@v1.2.3/hi/hi.go --
    90  package hi
    91  
    92  var Goodbye error
    93  `
    94  
    95  // This test confirms the full functionality of the code lenses for updating
    96  // dependencies in a go.mod file, when using a go.work file. It checks for the
    97  // code lens that suggests an update and then executes the command associated
    98  // with that code lens. A regression test for golang/go#39446. It also checks
    99  // that these code lenses only affect the diagnostics and contents of the
   100  // containing go.mod file.
   101  func TestUpgradeCodelens_Workspace(t *testing.T) {
   102  	const shouldUpdateDep = `
   103  -- go.work --
   104  go 1.18
   105  
   106  use (
   107  	./a
   108  	./b
   109  )
   110  -- a/go.mod --
   111  module mod.com/a
   112  
   113  go 1.14
   114  
   115  require golang.org/x/hello v1.2.3
   116  -- a/go.sum --
   117  golang.org/x/hello v1.2.3 h1:7Wesfkx/uBd+eFgPrq0irYj/1XfmbvLV8jZ/W7C2Dwg=
   118  golang.org/x/hello v1.2.3/go.mod h1:OgtlzsxVMUUdsdQCIDYgaauCTH47B8T8vofouNJfzgY=
   119  -- a/main.go --
   120  package main
   121  
   122  import "golang.org/x/hello/hi"
   123  
   124  func main() {
   125  	_ = hi.Goodbye
   126  }
   127  -- b/go.mod --
   128  module mod.com/b
   129  
   130  go 1.14
   131  
   132  require golang.org/x/hello v1.2.3
   133  -- b/go.sum --
   134  golang.org/x/hello v1.2.3 h1:7Wesfkx/uBd+eFgPrq0irYj/1XfmbvLV8jZ/W7C2Dwg=
   135  golang.org/x/hello v1.2.3/go.mod h1:OgtlzsxVMUUdsdQCIDYgaauCTH47B8T8vofouNJfzgY=
   136  -- b/main.go --
   137  package main
   138  
   139  import (
   140  	"golang.org/x/hello/hi"
   141  )
   142  
   143  func main() {
   144  	_ = hi.Goodbye
   145  }
   146  `
   147  
   148  	const wantGoModA = `module mod.com/a
   149  
   150  go 1.14
   151  
   152  require golang.org/x/hello v1.3.3
   153  `
   154  	// Applying the diagnostics or running the codelenses for a/go.mod
   155  	// should not change the contents of b/go.mod
   156  	const wantGoModB = `module mod.com/b
   157  
   158  go 1.14
   159  
   160  require golang.org/x/hello v1.2.3
   161  `
   162  
   163  	for _, commandTitle := range []string{
   164  		"Upgrade transitive dependencies",
   165  		"Upgrade direct dependencies",
   166  	} {
   167  		t.Run(commandTitle, func(t *testing.T) {
   168  			WithOptions(
   169  				ProxyFiles(proxyWithLatest),
   170  			).Run(t, shouldUpdateDep, func(t *testing.T, env *Env) {
   171  				env.OpenFile("a/go.mod")
   172  				env.OpenFile("b/go.mod")
   173  				var lens protocol.CodeLens
   174  				var found bool
   175  				for _, l := range env.CodeLens("a/go.mod") {
   176  					if l.Command.Title == commandTitle {
   177  						lens = l
   178  						found = true
   179  					}
   180  				}
   181  				if !found {
   182  					t.Fatalf("found no command with the title %s", commandTitle)
   183  				}
   184  				if _, err := env.Editor.ExecuteCommand(env.Ctx, &protocol.ExecuteCommandParams{
   185  					Command:   lens.Command.Command,
   186  					Arguments: lens.Command.Arguments,
   187  				}); err != nil {
   188  					t.Fatal(err)
   189  				}
   190  				env.AfterChange()
   191  				if got := env.BufferText("a/go.mod"); got != wantGoModA {
   192  					t.Fatalf("a/go.mod upgrade failed:\n%s", compare.Text(wantGoModA, got))
   193  				}
   194  				if got := env.BufferText("b/go.mod"); got != wantGoModB {
   195  					t.Fatalf("b/go.mod changed unexpectedly:\n%s", compare.Text(wantGoModB, got))
   196  				}
   197  			})
   198  		})
   199  	}
   200  	for _, vendoring := range []bool{false, true} {
   201  		t.Run(fmt.Sprintf("Upgrade individual dependency vendoring=%v", vendoring), func(t *testing.T) {
   202  			WithOptions(
   203  				ProxyFiles(proxyWithLatest),
   204  			).Run(t, shouldUpdateDep, func(t *testing.T, env *Env) {
   205  				if vendoring {
   206  					env.RunGoCommandInDirWithEnv("a", []string{"GOWORK=off"}, "mod", "vendor")
   207  				}
   208  				env.AfterChange()
   209  				env.OpenFile("a/go.mod")
   210  				env.OpenFile("b/go.mod")
   211  
   212  				env.ExecuteCodeLensCommand("a/go.mod", command.CheckUpgrades, nil)
   213  				d := &protocol.PublishDiagnosticsParams{}
   214  				env.OnceMet(
   215  					CompletedWork(server.DiagnosticWorkTitle(server.FromCheckUpgrades), 1, true),
   216  					Diagnostics(env.AtRegexp("a/go.mod", `require`), WithMessage("can be upgraded")),
   217  					ReadDiagnostics("a/go.mod", d),
   218  					// We do not want there to be a diagnostic for b/go.mod,
   219  					// but there may be some subtlety in timing here, where this
   220  					// should always succeed, but may not actually test the correct
   221  					// behavior.
   222  					NoDiagnostics(env.AtRegexp("b/go.mod", `require`)),
   223  				)
   224  				// Check for upgrades in b/go.mod and then clear them.
   225  				env.ExecuteCodeLensCommand("b/go.mod", command.CheckUpgrades, nil)
   226  				env.OnceMet(
   227  					CompletedWork(server.DiagnosticWorkTitle(server.FromCheckUpgrades), 2, true),
   228  					Diagnostics(env.AtRegexp("b/go.mod", `require`), WithMessage("can be upgraded")),
   229  				)
   230  				env.ExecuteCodeLensCommand("b/go.mod", command.ResetGoModDiagnostics, nil)
   231  				env.OnceMet(
   232  					CompletedWork(server.DiagnosticWorkTitle(server.FromResetGoModDiagnostics), 1, true),
   233  					NoDiagnostics(ForFile("b/go.mod")),
   234  				)
   235  
   236  				// Apply the diagnostics to a/go.mod.
   237  				env.ApplyQuickFixes("a/go.mod", d.Diagnostics)
   238  				env.AfterChange()
   239  				if got := env.BufferText("a/go.mod"); got != wantGoModA {
   240  					t.Fatalf("a/go.mod upgrade failed:\n%s", compare.Text(wantGoModA, got))
   241  				}
   242  				if got := env.BufferText("b/go.mod"); got != wantGoModB {
   243  					t.Fatalf("b/go.mod changed unexpectedly:\n%s", compare.Text(wantGoModB, got))
   244  				}
   245  			})
   246  		})
   247  	}
   248  }
   249  
   250  func TestUpgradeCodelens_ModVendor(t *testing.T) {
   251  	// This test checks the regression of golang/go#66055. The upgrade codelens
   252  	// should work in a mod vendor context (the test above using a go.work file
   253  	// was not broken).
   254  	testenv.NeedsGo1Point(t, 22)
   255  	const shouldUpdateDep = `
   256  -- go.mod --
   257  module mod.com/a
   258  
   259  go 1.22
   260  
   261  require golang.org/x/hello v1.2.3
   262  -- go.sum --
   263  golang.org/x/hello v1.2.3 h1:7Wesfkx/uBd+eFgPrq0irYj/1XfmbvLV8jZ/W7C2Dwg=
   264  golang.org/x/hello v1.2.3/go.mod h1:OgtlzsxVMUUdsdQCIDYgaauCTH47B8T8vofouNJfzgY=
   265  -- main.go --
   266  package main
   267  
   268  import "golang.org/x/hello/hi"
   269  
   270  func main() {
   271  	_ = hi.Goodbye
   272  }
   273  `
   274  
   275  	const wantGoModA = `module mod.com/a
   276  
   277  go 1.22
   278  
   279  require golang.org/x/hello v1.3.3
   280  `
   281  
   282  	WithOptions(
   283  		ProxyFiles(proxyWithLatest),
   284  	).Run(t, shouldUpdateDep, func(t *testing.T, env *Env) {
   285  		env.RunGoCommand("mod", "vendor")
   286  		env.AfterChange()
   287  		env.OpenFile("go.mod")
   288  
   289  		env.ExecuteCodeLensCommand("go.mod", command.CheckUpgrades, nil)
   290  		d := &protocol.PublishDiagnosticsParams{}
   291  		env.OnceMet(
   292  			CompletedWork(server.DiagnosticWorkTitle(server.FromCheckUpgrades), 1, true),
   293  			Diagnostics(env.AtRegexp("go.mod", `require`), WithMessage("can be upgraded")),
   294  			ReadDiagnostics("go.mod", d),
   295  		)
   296  
   297  		// Apply the diagnostics to a/go.mod.
   298  		env.ApplyQuickFixes("go.mod", d.Diagnostics)
   299  		env.AfterChange()
   300  		if got := env.BufferText("go.mod"); got != wantGoModA {
   301  			t.Fatalf("go.mod upgrade failed:\n%s", compare.Text(wantGoModA, got))
   302  		}
   303  	})
   304  }
   305  
   306  func TestUnusedDependenciesCodelens(t *testing.T) {
   307  	const proxy = `
   308  -- golang.org/x/hello@v1.0.0/go.mod --
   309  module golang.org/x/hello
   310  
   311  go 1.14
   312  -- golang.org/x/hello@v1.0.0/hi/hi.go --
   313  package hi
   314  
   315  var Goodbye error
   316  -- golang.org/x/unused@v1.0.0/go.mod --
   317  module golang.org/x/unused
   318  
   319  go 1.14
   320  -- golang.org/x/unused@v1.0.0/nouse/nouse.go --
   321  package nouse
   322  
   323  var NotUsed error
   324  `
   325  
   326  	const shouldRemoveDep = `
   327  -- go.mod --
   328  module mod.com
   329  
   330  go 1.14
   331  
   332  require golang.org/x/hello v1.0.0
   333  require golang.org/x/unused v1.0.0
   334  -- go.sum --
   335  golang.org/x/hello v1.0.0 h1:qbzE1/qT0/zojAMd/JcPsO2Vb9K4Bkeyq0vB2JGMmsw=
   336  golang.org/x/hello v1.0.0/go.mod h1:WW7ER2MRNXWA6c8/4bDIek4Hc/+DofTrMaQQitGXcco=
   337  golang.org/x/unused v1.0.0 h1:LecSbCn5P3vTcxubungSt1Pn4D/WocCaiWOPDC0y0rw=
   338  golang.org/x/unused v1.0.0/go.mod h1:ihoW8SgWzugwwj0N2SfLfPZCxTB1QOVfhMfB5PWTQ8U=
   339  -- main.go --
   340  package main
   341  
   342  import "golang.org/x/hello/hi"
   343  
   344  func main() {
   345  	_ = hi.Goodbye
   346  }
   347  `
   348  	WithOptions(ProxyFiles(proxy)).Run(t, shouldRemoveDep, func(t *testing.T, env *Env) {
   349  		env.OpenFile("go.mod")
   350  		env.ExecuteCodeLensCommand("go.mod", command.Tidy, nil)
   351  		env.AfterChange()
   352  		got := env.BufferText("go.mod")
   353  		const wantGoMod = `module mod.com
   354  
   355  go 1.14
   356  
   357  require golang.org/x/hello v1.0.0
   358  `
   359  		if got != wantGoMod {
   360  			t.Fatalf("go.mod tidy failed:\n%s", compare.Text(wantGoMod, got))
   361  		}
   362  	})
   363  }
   364  
   365  func TestRegenerateCgo(t *testing.T) {
   366  	testenv.NeedsTool(t, "cgo")
   367  	const workspace = `
   368  -- go.mod --
   369  module example.com
   370  
   371  go 1.12
   372  -- cgo.go --
   373  package x
   374  
   375  /*
   376  int fortythree() { return 42; }
   377  */
   378  import "C"
   379  
   380  func Foo() {
   381  	print(C.fortytwo())
   382  }
   383  `
   384  	Run(t, workspace, func(t *testing.T, env *Env) {
   385  		// Open the file. We have a nonexistant symbol that will break cgo processing.
   386  		env.OpenFile("cgo.go")
   387  		env.AfterChange(
   388  			Diagnostics(env.AtRegexp("cgo.go", ``), WithMessage("go list failed to return CompiledGoFiles")),
   389  		)
   390  
   391  		// Fix the C function name. We haven't regenerated cgo, so nothing should be fixed.
   392  		env.RegexpReplace("cgo.go", `int fortythree`, "int fortytwo")
   393  		env.SaveBuffer("cgo.go")
   394  		env.AfterChange(
   395  			Diagnostics(env.AtRegexp("cgo.go", ``), WithMessage("go list failed to return CompiledGoFiles")),
   396  		)
   397  
   398  		// Regenerate cgo, fixing the diagnostic.
   399  		env.ExecuteCodeLensCommand("cgo.go", command.RegenerateCgo, nil)
   400  		env.OnceMet(
   401  			CompletedWork(server.DiagnosticWorkTitle(server.FromRegenerateCgo), 1, true),
   402  			NoDiagnostics(ForFile("cgo.go")),
   403  		)
   404  	})
   405  }