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 }