golang.org/x/tools/gopls@v0.15.3/internal/test/integration/misc/references_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 misc 6 7 import ( 8 "fmt" 9 "os" 10 "path/filepath" 11 "reflect" 12 "sort" 13 "strings" 14 "testing" 15 16 "github.com/google/go-cmp/cmp" 17 "golang.org/x/tools/gopls/internal/protocol" 18 "golang.org/x/tools/gopls/internal/test/integration" 19 . "golang.org/x/tools/gopls/internal/test/integration" 20 ) 21 22 func TestStdlibReferences(t *testing.T) { 23 const files = ` 24 -- go.mod -- 25 module mod.com 26 27 go 1.12 28 -- main.go -- 29 package main 30 31 import "fmt" 32 33 func main() { 34 fmt.Print() 35 } 36 ` 37 38 Run(t, files, func(t *testing.T, env *Env) { 39 env.OpenFile("main.go") 40 loc := env.GoToDefinition(env.RegexpSearch("main.go", `fmt.(Print)`)) 41 refs, err := env.Editor.References(env.Ctx, loc) 42 if err != nil { 43 t.Fatal(err) 44 } 45 if len(refs) != 2 { 46 // TODO(adonovan): make this assertion less maintainer-hostile. 47 t.Fatalf("got %v reference(s), want 2", len(refs)) 48 } 49 // The first reference is guaranteed to be the definition. 50 if got, want := refs[1].URI, env.Sandbox.Workdir.URI("main.go"); got != want { 51 t.Errorf("found reference in %v, wanted %v", got, want) 52 } 53 }) 54 } 55 56 // This is a regression test for golang/go#48400 (a panic). 57 func TestReferencesOnErrorMethod(t *testing.T) { 58 // Ideally this would actually return the correct answer, 59 // instead of merely failing gracefully. 60 const files = ` 61 -- go.mod -- 62 module mod.com 63 64 go 1.12 65 -- main.go -- 66 package main 67 68 type t interface { 69 error 70 } 71 72 type s struct{} 73 74 func (*s) Error() string { 75 return "" 76 } 77 78 func _() { 79 var s s 80 _ = s.Error() 81 } 82 ` 83 Run(t, files, func(t *testing.T, env *Env) { 84 env.OpenFile("main.go") 85 loc := env.GoToDefinition(env.RegexpSearch("main.go", `Error`)) 86 refs, err := env.Editor.References(env.Ctx, loc) 87 if err != nil { 88 t.Fatalf("references on (*s).Error failed: %v", err) 89 } 90 // TODO(adonovan): this test is crying out for marker support in integration tests. 91 var buf strings.Builder 92 for _, ref := range refs { 93 fmt.Fprintf(&buf, "%s %s\n", env.Sandbox.Workdir.URIToPath(ref.URI), ref.Range) 94 } 95 got := buf.String() 96 want := "main.go 8:10-8:15\n" + // (*s).Error decl 97 "main.go 14:7-14:12\n" // s.Error() call 98 if diff := cmp.Diff(want, got); diff != "" { 99 t.Errorf("unexpected references on (*s).Error (-want +got):\n%s", diff) 100 } 101 }) 102 } 103 104 func TestDefsRefsBuiltins(t *testing.T) { 105 // TODO(adonovan): add unsafe.{SliceData,String,StringData} in later go versions. 106 const files = ` 107 -- go.mod -- 108 module example.com 109 go 1.16 110 111 -- a.go -- 112 package a 113 114 import "unsafe" 115 116 const _ = iota 117 var _ error 118 var _ int 119 var _ = append() 120 var _ = unsafe.Pointer(nil) 121 var _ = unsafe.Add(nil, nil) 122 var _ = unsafe.Sizeof(0) 123 var _ = unsafe.Alignof(0) 124 var _ = unsafe.Slice(nil, 0) 125 ` 126 127 Run(t, files, func(t *testing.T, env *Env) { 128 env.OpenFile("a.go") 129 for _, name := range strings.Fields( 130 "iota error int nil append iota Pointer Sizeof Alignof Add Slice") { 131 loc := env.RegexpSearch("a.go", `\b`+name+`\b`) 132 133 // definition -> {builtin,unsafe}.go 134 def := env.GoToDefinition(loc) 135 if (!strings.HasSuffix(string(def.URI), "builtin.go") && 136 !strings.HasSuffix(string(def.URI), "unsafe.go")) || 137 def.Range.Start.Line == 0 { 138 t.Errorf("definition(%q) = %v, want {builtin,unsafe}.go", 139 name, def) 140 } 141 142 // "references to (builtin "Foo"|unsafe.Foo) are not supported" 143 _, err := env.Editor.References(env.Ctx, loc) 144 gotErr := fmt.Sprint(err) 145 if !strings.Contains(gotErr, "references to") || 146 !strings.Contains(gotErr, "not supported") || 147 !strings.Contains(gotErr, name) { 148 t.Errorf("references(%q) error: got %q, want %q", 149 name, gotErr, "references to ... are not supported") 150 } 151 } 152 }) 153 } 154 155 func TestPackageReferences(t *testing.T) { 156 tests := []struct { 157 packageName string 158 wantRefCount int 159 wantFiles []string 160 }{ 161 { 162 "lib1", 163 3, 164 []string{ 165 "main.go", 166 "lib1/a.go", 167 "lib1/b.go", 168 }, 169 }, 170 { 171 "lib2", 172 2, 173 []string{ 174 "main.go", 175 "lib2/a.go", 176 }, 177 }, 178 } 179 180 const files = ` 181 -- go.mod -- 182 module mod.com 183 184 go 1.18 185 -- lib1/a.go -- 186 package lib1 187 188 const A = 1 189 190 -- lib1/b.go -- 191 package lib1 192 193 const B = 1 194 195 -- lib2/a.go -- 196 package lib2 197 198 const C = 1 199 200 -- main.go -- 201 package main 202 203 import ( 204 "mod.com/lib1" 205 "mod.com/lib2" 206 ) 207 208 func main() { 209 println("Hello") 210 } 211 ` 212 Run(t, files, func(t *testing.T, env *Env) { 213 for _, test := range tests { 214 file := fmt.Sprintf("%s/a.go", test.packageName) 215 env.OpenFile(file) 216 loc := env.RegexpSearch(file, test.packageName) 217 refs := env.References(loc) 218 if len(refs) != test.wantRefCount { 219 // TODO(adonovan): make this assertion less maintainer-hostile. 220 t.Fatalf("got %v reference(s), want %d", len(refs), test.wantRefCount) 221 } 222 var refURIs []string 223 for _, ref := range refs { 224 refURIs = append(refURIs, string(ref.URI)) 225 } 226 for _, base := range test.wantFiles { 227 hasBase := false 228 for _, ref := range refURIs { 229 if strings.HasSuffix(ref, base) { 230 hasBase = true 231 break 232 } 233 } 234 if !hasBase { 235 t.Fatalf("got [%v], want reference ends with \"%v\"", strings.Join(refURIs, ","), base) 236 } 237 } 238 } 239 }) 240 } 241 242 // Test for golang/go#43144. 243 // 244 // Verify that we search for references and implementations in intermediate 245 // test variants. 246 func TestReferencesInTestVariants(t *testing.T) { 247 const files = ` 248 -- go.mod -- 249 module foo.mod 250 251 go 1.12 252 -- foo/foo.go -- 253 package foo 254 255 import "foo.mod/bar" 256 257 const Foo = 42 258 259 type T int 260 type InterfaceM interface{ M() } 261 type InterfaceF interface{ F() } 262 263 func _() { 264 _ = bar.Blah 265 } 266 267 -- foo/foo_test.go -- 268 package foo 269 270 type Fer struct{} 271 func (Fer) F() {} 272 273 -- bar/bar.go -- 274 package bar 275 276 var Blah = 123 277 278 -- bar/bar_test.go -- 279 package bar 280 281 type Mer struct{} 282 func (Mer) M() {} 283 284 func TestBar() { 285 _ = Blah 286 } 287 -- bar/bar_x_test.go -- 288 package bar_test 289 290 import ( 291 "foo.mod/bar" 292 "foo.mod/foo" 293 ) 294 295 type Mer struct{} 296 func (Mer) M() {} 297 298 func _() { 299 _ = bar.Blah 300 _ = foo.Foo 301 } 302 ` 303 304 Run(t, files, func(t *testing.T, env *Env) { 305 env.OpenFile("foo/foo.go") 306 307 refTests := []struct { 308 re string 309 wantRefs []string 310 }{ 311 // Blah is referenced: 312 // - inside the foo.mod/bar (ordinary) package 313 // - inside the foo.mod/bar [foo.mod/bar.test] test variant package 314 // - from the foo.mod/bar_test [foo.mod/bar.test] x_test package 315 // - from the foo.mod/foo package 316 {"Blah", []string{"bar/bar.go:3", "bar/bar_test.go:7", "bar/bar_x_test.go:12", "foo/foo.go:12"}}, 317 318 // Foo is referenced in bar_x_test.go via the intermediate test variant 319 // foo.mod/foo [foo.mod/bar.test]. 320 {"Foo", []string{"bar/bar_x_test.go:13", "foo/foo.go:5"}}, 321 } 322 323 for _, test := range refTests { 324 loc := env.RegexpSearch("foo/foo.go", test.re) 325 refs := env.References(loc) 326 327 got := fileLocations(env, refs) 328 if diff := cmp.Diff(test.wantRefs, got); diff != "" { 329 t.Errorf("References(%q) returned unexpected diff (-want +got):\n%s", test.re, diff) 330 } 331 } 332 333 implTests := []struct { 334 re string 335 wantImpls []string 336 }{ 337 // InterfaceM is implemented both in foo.mod/bar [foo.mod/bar.test] (which 338 // doesn't import foo), and in foo.mod/bar_test [foo.mod/bar.test], which 339 // imports the test variant of foo. 340 {"InterfaceM", []string{"bar/bar_test.go:3", "bar/bar_x_test.go:8"}}, 341 342 // A search within the ordinary package to should find implementations 343 // (Fer) within the augmented test package. 344 {"InterfaceF", []string{"foo/foo_test.go:3"}}, 345 } 346 347 for _, test := range implTests { 348 loc := env.RegexpSearch("foo/foo.go", test.re) 349 impls := env.Implementations(loc) 350 351 got := fileLocations(env, impls) 352 if diff := cmp.Diff(test.wantImpls, got); diff != "" { 353 t.Errorf("Implementations(%q) returned unexpected diff (-want +got):\n%s", test.re, diff) 354 } 355 } 356 }) 357 } 358 359 // This is a regression test for Issue #56169, in which interface 360 // implementations in vendored modules were not found. The actual fix 361 // was the same as for #55995; see TestVendoringInvalidatesMetadata. 362 func TestImplementationsInVendor(t *testing.T) { 363 const proxy = ` 364 -- other.com/b@v1.0.0/go.mod -- 365 module other.com/b 366 go 1.14 367 368 -- other.com/b@v1.0.0/b.go -- 369 package b 370 type B int 371 func (B) F() {} 372 ` 373 const src = ` 374 -- go.mod -- 375 module example.com/a 376 go 1.14 377 require other.com/b v1.0.0 378 379 -- go.sum -- 380 other.com/b v1.0.0 h1:9WyCKS+BLAMRQM0CegP6zqP2beP+ShTbPaARpNY31II= 381 other.com/b v1.0.0/go.mod h1:TgHQFucl04oGT+vrUm/liAzukYHNxCwKNkQZEyn3m9g= 382 383 -- a.go -- 384 package a 385 import "other.com/b" 386 type I interface { F() } 387 var _ b.B 388 389 ` 390 WithOptions( 391 ProxyFiles(proxy), 392 Modes(Default), // fails in 'experimental' mode 393 ).Run(t, src, func(t *testing.T, env *Env) { 394 // Enable to debug go.sum mismatch, which may appear as 395 // "module lookup disabled by GOPROXY=off", confusingly. 396 if false { 397 env.DumpGoSum(".") 398 } 399 400 checkVendor := func(locs []protocol.Location, wantVendor bool) { 401 if len(locs) != 1 { 402 t.Errorf("got %d locations, want 1", len(locs)) 403 } else if strings.Contains(string(locs[0].URI), "/vendor/") != wantVendor { 404 t.Errorf("got location %s, wantVendor=%t", locs[0], wantVendor) 405 } 406 } 407 408 env.OpenFile("a.go") 409 refLoc := env.RegexpSearch("a.go", "I") // find "I" reference 410 411 // Initially, a.I has one implementation b.B in 412 // the module cache, not the vendor tree. 413 checkVendor(env.Implementations(refLoc), false) 414 415 // Run 'go mod vendor' outside the editor. 416 env.RunGoCommand("mod", "vendor") 417 418 // Synchronize changes to watched files. 419 env.Await(env.DoneWithChangeWatchedFiles()) 420 421 // Now, b.B is found in the vendor tree. 422 checkVendor(env.Implementations(refLoc), true) 423 424 // Delete the vendor tree. 425 if err := os.RemoveAll(env.Sandbox.Workdir.AbsPath("vendor")); err != nil { 426 t.Fatal(err) 427 } 428 // Notify the server of the deletion. 429 if err := env.Sandbox.Workdir.CheckForFileChanges(env.Ctx); err != nil { 430 t.Fatal(err) 431 } 432 433 // Synchronize again. 434 env.Await(env.DoneWithChangeWatchedFiles()) 435 436 // b.B is once again defined in the module cache. 437 checkVendor(env.Implementations(refLoc), false) 438 }) 439 } 440 441 // This test can't be expressed as a marker test because the marker 442 // test framework opens all files (which is a bit of a hack), creating 443 // a <command-line-arguments> package for packages that otherwise 444 // wouldn't be found from the go.work file. 445 func TestReferencesFromWorkspacePackages59674(t *testing.T) { 446 const src = ` 447 -- a/go.mod -- 448 module example.com/a 449 go 1.12 450 451 -- b/go.mod -- 452 module example.com/b 453 go 1.12 454 455 -- c/go.mod -- 456 module example.com/c 457 go 1.12 458 459 -- lib/go.mod -- 460 module example.com/lib 461 go 1.12 462 463 -- go.work -- 464 use ./a 465 use ./b 466 // don't use ./c 467 use ./lib 468 469 -- a/a.go -- 470 package a 471 472 import "example.com/lib" 473 474 var _ = lib.F // query here 475 476 -- b/b.go -- 477 package b 478 479 import "example.com/lib" 480 481 var _ = lib.F // also found by references 482 483 -- c/c.go -- 484 package c 485 486 import "example.com/lib" 487 488 var _ = lib.F // this reference should not be reported 489 490 -- lib/lib.go -- 491 package lib 492 493 func F() {} // declaration 494 ` 495 Run(t, src, func(t *testing.T, env *Env) { 496 env.OpenFile("a/a.go") 497 refLoc := env.RegexpSearch("a/a.go", "F") 498 got := fileLocations(env, env.References(refLoc)) 499 want := []string{"a/a.go:5", "b/b.go:5", "lib/lib.go:3"} 500 if diff := cmp.Diff(want, got); diff != "" { 501 t.Errorf("incorrect References (-want +got):\n%s", diff) 502 } 503 }) 504 } 505 506 // Test an 'implementation' query on a type that implements 'error'. 507 // (Unfortunately builtin locations cannot be expressed using @loc 508 // in the marker test framework.) 509 func TestImplementationsOfError(t *testing.T) { 510 const src = ` 511 -- go.mod -- 512 module example.com 513 go 1.12 514 515 -- a.go -- 516 package a 517 518 type Error2 interface { 519 Error() string 520 } 521 522 type MyError int 523 func (MyError) Error() string { return "" } 524 525 type MyErrorPtr int 526 func (*MyErrorPtr) Error() string { return "" } 527 ` 528 Run(t, src, func(t *testing.T, env *Env) { 529 env.OpenFile("a.go") 530 531 for _, test := range []struct { 532 re string 533 want []string 534 }{ 535 // error type 536 {"Error2", []string{"a.go:10", "a.go:7", "std:builtin/builtin.go"}}, 537 {"MyError", []string{"a.go:3", "std:builtin/builtin.go"}}, 538 {"MyErrorPtr", []string{"a.go:3", "std:builtin/builtin.go"}}, 539 // error.Error method 540 {"(Error).. string", []string{"a.go:11", "a.go:8", "std:builtin/builtin.go"}}, 541 {"MyError. (Error)", []string{"a.go:4", "std:builtin/builtin.go"}}, 542 {"MyErrorPtr. (Error)", []string{"a.go:4", "std:builtin/builtin.go"}}, 543 } { 544 matchLoc := env.RegexpSearch("a.go", test.re) 545 impls := env.Implementations(matchLoc) 546 got := fileLocations(env, impls) 547 if !reflect.DeepEqual(got, test.want) { 548 t.Errorf("Implementations(%q) = %q, want %q", 549 test.re, got, test.want) 550 } 551 } 552 }) 553 } 554 555 // fileLocations returns a new sorted array of the 556 // relative file name and line number of each location. 557 // Duplicates are not removed. 558 // Standard library filenames are abstracted for robustness. 559 func fileLocations(env *integration.Env, locs []protocol.Location) []string { 560 got := make([]string, 0, len(locs)) 561 for _, loc := range locs { 562 path := env.Sandbox.Workdir.URIToPath(loc.URI) // (slashified) 563 if i := strings.LastIndex(path, "/src/"); i >= 0 && filepath.IsAbs(path) { 564 // Absolute path with "src" segment: assume it's in GOROOT. 565 // Strip directory and don't add line/column since they are fragile. 566 path = "std:" + path[i+len("/src/"):] 567 } else { 568 path = fmt.Sprintf("%s:%d", path, loc.Range.Start.Line+1) 569 } 570 got = append(got, path) 571 } 572 sort.Strings(got) 573 return got 574 }