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  }