github.com/jhump/golang-x-tools@v0.0.0-20220218190644-4958d6d39439/internal/lsp/source/source_test.go (about)

     1  // Copyright 2018 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 source_test
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  	"os"
    11  	"os/exec"
    12  	"path/filepath"
    13  	"sort"
    14  	"strings"
    15  	"testing"
    16  
    17  	"github.com/jhump/golang-x-tools/internal/lsp/cache"
    18  	"github.com/jhump/golang-x-tools/internal/lsp/diff"
    19  	"github.com/jhump/golang-x-tools/internal/lsp/diff/myers"
    20  	"github.com/jhump/golang-x-tools/internal/lsp/fuzzy"
    21  	"github.com/jhump/golang-x-tools/internal/lsp/protocol"
    22  	"github.com/jhump/golang-x-tools/internal/lsp/source"
    23  	"github.com/jhump/golang-x-tools/internal/lsp/source/completion"
    24  	"github.com/jhump/golang-x-tools/internal/lsp/tests"
    25  	"github.com/jhump/golang-x-tools/internal/span"
    26  	"github.com/jhump/golang-x-tools/internal/testenv"
    27  	errors "golang.org/x/xerrors"
    28  )
    29  
    30  func TestMain(m *testing.M) {
    31  	testenv.ExitIfSmallMachine()
    32  	os.Exit(m.Run())
    33  }
    34  
    35  func TestSource(t *testing.T) {
    36  	tests.RunTests(t, "../testdata", true, testSource)
    37  }
    38  
    39  type runner struct {
    40  	snapshot    source.Snapshot
    41  	view        source.View
    42  	data        *tests.Data
    43  	ctx         context.Context
    44  	normalizers []tests.Normalizer
    45  }
    46  
    47  func testSource(t *testing.T, datum *tests.Data) {
    48  	ctx := tests.Context(t)
    49  
    50  	cache := cache.New(nil)
    51  	session := cache.NewSession(ctx)
    52  	options := source.DefaultOptions().Clone()
    53  	tests.DefaultOptions(options)
    54  	options.SetEnvSlice(datum.Config.Env)
    55  	view, _, release, err := session.NewView(ctx, "source_test", span.URIFromPath(datum.Config.Dir), options)
    56  	release()
    57  	if err != nil {
    58  		t.Fatal(err)
    59  	}
    60  	defer view.Shutdown(ctx)
    61  
    62  	// Enable type error analyses for tests.
    63  	// TODO(golang/go#38212): Delete this once they are enabled by default.
    64  	tests.EnableAllAnalyzers(view, options)
    65  	view.SetOptions(ctx, options)
    66  
    67  	var modifications []source.FileModification
    68  	for filename, content := range datum.Config.Overlay {
    69  		if filepath.Ext(filename) != ".go" {
    70  			continue
    71  		}
    72  		modifications = append(modifications, source.FileModification{
    73  			URI:        span.URIFromPath(filename),
    74  			Action:     source.Open,
    75  			Version:    -1,
    76  			Text:       content,
    77  			LanguageID: "go",
    78  		})
    79  	}
    80  	if err := session.ModifyFiles(ctx, modifications); err != nil {
    81  		t.Fatal(err)
    82  	}
    83  	snapshot, release := view.Snapshot(ctx)
    84  	defer release()
    85  	r := &runner{
    86  		view:        view,
    87  		snapshot:    snapshot,
    88  		data:        datum,
    89  		ctx:         ctx,
    90  		normalizers: tests.CollectNormalizers(datum.Exported),
    91  	}
    92  	tests.Run(t, r, datum)
    93  }
    94  
    95  func (r *runner) CallHierarchy(t *testing.T, spn span.Span, expectedCalls *tests.CallHierarchyResult) {
    96  	mapper, err := r.data.Mapper(spn.URI())
    97  	if err != nil {
    98  		t.Fatal(err)
    99  	}
   100  	loc, err := mapper.Location(spn)
   101  	if err != nil {
   102  		t.Fatalf("failed for %v: %v", spn, err)
   103  	}
   104  	fh, err := r.snapshot.GetFile(r.ctx, spn.URI())
   105  	if err != nil {
   106  		t.Fatal(err)
   107  	}
   108  
   109  	items, err := source.PrepareCallHierarchy(r.ctx, r.snapshot, fh, loc.Range.Start)
   110  	if err != nil {
   111  		t.Fatal(err)
   112  	}
   113  	if len(items) == 0 {
   114  		t.Fatalf("expected call hierarchy item to be returned for identifier at %v\n", loc.Range)
   115  	}
   116  
   117  	callLocation := protocol.Location{
   118  		URI:   items[0].URI,
   119  		Range: items[0].Range,
   120  	}
   121  	if callLocation != loc {
   122  		t.Fatalf("expected source.PrepareCallHierarchy to return identifier at %v but got %v\n", loc, callLocation)
   123  	}
   124  
   125  	incomingCalls, err := source.IncomingCalls(r.ctx, r.snapshot, fh, loc.Range.Start)
   126  	if err != nil {
   127  		t.Error(err)
   128  	}
   129  	var incomingCallItems []protocol.CallHierarchyItem
   130  	for _, item := range incomingCalls {
   131  		incomingCallItems = append(incomingCallItems, item.From)
   132  	}
   133  	msg := tests.DiffCallHierarchyItems(incomingCallItems, expectedCalls.IncomingCalls)
   134  	if msg != "" {
   135  		t.Error(fmt.Sprintf("incoming calls differ: %s", msg))
   136  	}
   137  
   138  	outgoingCalls, err := source.OutgoingCalls(r.ctx, r.snapshot, fh, loc.Range.Start)
   139  	if err != nil {
   140  		t.Error(err)
   141  	}
   142  	var outgoingCallItems []protocol.CallHierarchyItem
   143  	for _, item := range outgoingCalls {
   144  		outgoingCallItems = append(outgoingCallItems, item.To)
   145  	}
   146  	msg = tests.DiffCallHierarchyItems(outgoingCallItems, expectedCalls.OutgoingCalls)
   147  	if msg != "" {
   148  		t.Error(fmt.Sprintf("outgoing calls differ: %s", msg))
   149  	}
   150  }
   151  
   152  func (r *runner) Diagnostics(t *testing.T, uri span.URI, want []*source.Diagnostic) {
   153  	fileID, got, err := source.FileDiagnostics(r.ctx, r.snapshot, uri)
   154  	if err != nil {
   155  		t.Fatal(err)
   156  	}
   157  	// A special case to test that there are no diagnostics for a file.
   158  	if len(want) == 1 && want[0].Source == "no_diagnostics" {
   159  		if len(got) != 0 {
   160  			t.Errorf("expected no diagnostics for %s, got %v", uri, got)
   161  		}
   162  		return
   163  	}
   164  	if diff := tests.DiffDiagnostics(fileID.URI, want, got); diff != "" {
   165  		t.Error(diff)
   166  	}
   167  }
   168  
   169  func (r *runner) Completion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
   170  	var want []protocol.CompletionItem
   171  	for _, pos := range test.CompletionItems {
   172  		want = append(want, tests.ToProtocolCompletionItem(*items[pos]))
   173  	}
   174  	_, got := r.callCompletion(t, src, func(opts *source.Options) {
   175  		opts.Matcher = source.CaseInsensitive
   176  		opts.DeepCompletion = false
   177  		opts.CompleteUnimported = false
   178  		opts.InsertTextFormat = protocol.SnippetTextFormat
   179  		opts.LiteralCompletions = strings.Contains(string(src.URI()), "literal")
   180  		opts.ExperimentalPostfixCompletions = strings.Contains(string(src.URI()), "postfix")
   181  	})
   182  	got = tests.FilterBuiltins(src, got)
   183  	if diff := tests.DiffCompletionItems(want, got); diff != "" {
   184  		t.Errorf("%s: %s", src, diff)
   185  	}
   186  }
   187  
   188  func (r *runner) CompletionSnippet(t *testing.T, src span.Span, expected tests.CompletionSnippet, placeholders bool, items tests.CompletionItems) {
   189  	_, list := r.callCompletion(t, src, func(opts *source.Options) {
   190  		opts.UsePlaceholders = placeholders
   191  		opts.DeepCompletion = true
   192  		opts.CompleteUnimported = false
   193  	})
   194  	got := tests.FindItem(list, *items[expected.CompletionItem])
   195  	want := expected.PlainSnippet
   196  	if placeholders {
   197  		want = expected.PlaceholderSnippet
   198  	}
   199  	if diff := tests.DiffSnippets(want, got); diff != "" {
   200  		t.Errorf("%s: %s", src, diff)
   201  	}
   202  }
   203  
   204  func (r *runner) UnimportedCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
   205  	var want []protocol.CompletionItem
   206  	for _, pos := range test.CompletionItems {
   207  		want = append(want, tests.ToProtocolCompletionItem(*items[pos]))
   208  	}
   209  	_, got := r.callCompletion(t, src, func(opts *source.Options) {})
   210  	got = tests.FilterBuiltins(src, got)
   211  	if diff := tests.CheckCompletionOrder(want, got, false); diff != "" {
   212  		t.Errorf("%s: %s", src, diff)
   213  	}
   214  }
   215  
   216  func (r *runner) DeepCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
   217  	var want []protocol.CompletionItem
   218  	for _, pos := range test.CompletionItems {
   219  		want = append(want, tests.ToProtocolCompletionItem(*items[pos]))
   220  	}
   221  	prefix, list := r.callCompletion(t, src, func(opts *source.Options) {
   222  		opts.DeepCompletion = true
   223  		opts.Matcher = source.CaseInsensitive
   224  		opts.CompleteUnimported = false
   225  	})
   226  	list = tests.FilterBuiltins(src, list)
   227  	fuzzyMatcher := fuzzy.NewMatcher(prefix)
   228  	var got []protocol.CompletionItem
   229  	for _, item := range list {
   230  		if fuzzyMatcher.Score(item.Label) <= 0 {
   231  			continue
   232  		}
   233  		got = append(got, item)
   234  	}
   235  	if msg := tests.DiffCompletionItems(want, got); msg != "" {
   236  		t.Errorf("%s: %s", src, msg)
   237  	}
   238  }
   239  
   240  func (r *runner) FuzzyCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
   241  	var want []protocol.CompletionItem
   242  	for _, pos := range test.CompletionItems {
   243  		want = append(want, tests.ToProtocolCompletionItem(*items[pos]))
   244  	}
   245  	_, got := r.callCompletion(t, src, func(opts *source.Options) {
   246  		opts.DeepCompletion = true
   247  		opts.Matcher = source.Fuzzy
   248  		opts.CompleteUnimported = false
   249  	})
   250  	got = tests.FilterBuiltins(src, got)
   251  	if msg := tests.DiffCompletionItems(want, got); msg != "" {
   252  		t.Errorf("%s: %s", src, msg)
   253  	}
   254  }
   255  
   256  func (r *runner) CaseSensitiveCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
   257  	var want []protocol.CompletionItem
   258  	for _, pos := range test.CompletionItems {
   259  		want = append(want, tests.ToProtocolCompletionItem(*items[pos]))
   260  	}
   261  	_, list := r.callCompletion(t, src, func(opts *source.Options) {
   262  		opts.Matcher = source.CaseSensitive
   263  		opts.CompleteUnimported = false
   264  	})
   265  	list = tests.FilterBuiltins(src, list)
   266  	if diff := tests.DiffCompletionItems(want, list); diff != "" {
   267  		t.Errorf("%s: %s", src, diff)
   268  	}
   269  }
   270  
   271  func (r *runner) RankCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
   272  	var want []protocol.CompletionItem
   273  	for _, pos := range test.CompletionItems {
   274  		want = append(want, tests.ToProtocolCompletionItem(*items[pos]))
   275  	}
   276  	_, got := r.callCompletion(t, src, func(opts *source.Options) {
   277  		opts.DeepCompletion = true
   278  		opts.Matcher = source.Fuzzy
   279  		opts.ExperimentalPostfixCompletions = true
   280  	})
   281  	if msg := tests.CheckCompletionOrder(want, got, true); msg != "" {
   282  		t.Errorf("%s: %s", src, msg)
   283  	}
   284  }
   285  
   286  func (r *runner) callCompletion(t *testing.T, src span.Span, options func(*source.Options)) (string, []protocol.CompletionItem) {
   287  	fh, err := r.snapshot.GetFile(r.ctx, src.URI())
   288  	if err != nil {
   289  		t.Fatal(err)
   290  	}
   291  	original := r.view.Options()
   292  	modified := original.Clone()
   293  	options(modified)
   294  	newView, err := r.view.SetOptions(r.ctx, modified)
   295  	if newView != r.view {
   296  		t.Fatalf("options change unexpectedly created new view")
   297  	}
   298  	if err != nil {
   299  		t.Fatal(err)
   300  	}
   301  	defer r.view.SetOptions(r.ctx, original)
   302  
   303  	list, surrounding, err := completion.Completion(r.ctx, r.snapshot, fh, protocol.Position{
   304  		Line:      uint32(src.Start().Line() - 1),
   305  		Character: uint32(src.Start().Column() - 1),
   306  	}, protocol.CompletionContext{})
   307  	if err != nil && !errors.As(err, &completion.ErrIsDefinition{}) {
   308  		t.Fatalf("failed for %v: %v", src, err)
   309  	}
   310  	var prefix string
   311  	if surrounding != nil {
   312  		prefix = strings.ToLower(surrounding.Prefix())
   313  	}
   314  
   315  	var numDeepCompletionsSeen int
   316  	var items []completion.CompletionItem
   317  	// Apply deep completion filtering.
   318  	for _, item := range list {
   319  		if item.Depth > 0 {
   320  			if !modified.DeepCompletion {
   321  				continue
   322  			}
   323  			if numDeepCompletionsSeen >= completion.MaxDeepCompletions {
   324  				continue
   325  			}
   326  			numDeepCompletionsSeen++
   327  		}
   328  		items = append(items, item)
   329  	}
   330  	return prefix, tests.ToProtocolCompletionItems(items)
   331  }
   332  
   333  func (r *runner) FoldingRanges(t *testing.T, spn span.Span) {
   334  	uri := spn.URI()
   335  
   336  	fh, err := r.snapshot.GetFile(r.ctx, spn.URI())
   337  	if err != nil {
   338  		t.Fatal(err)
   339  	}
   340  	data, err := fh.Read()
   341  	if err != nil {
   342  		t.Error(err)
   343  		return
   344  	}
   345  
   346  	// Test all folding ranges.
   347  	ranges, err := source.FoldingRange(r.ctx, r.snapshot, fh, false)
   348  	if err != nil {
   349  		t.Error(err)
   350  		return
   351  	}
   352  	r.foldingRanges(t, "foldingRange", uri, string(data), ranges)
   353  
   354  	// Test folding ranges with lineFoldingOnly
   355  	ranges, err = source.FoldingRange(r.ctx, r.snapshot, fh, true)
   356  	if err != nil {
   357  		t.Error(err)
   358  		return
   359  	}
   360  	r.foldingRanges(t, "foldingRange-lineFolding", uri, string(data), ranges)
   361  }
   362  
   363  func (r *runner) foldingRanges(t *testing.T, prefix string, uri span.URI, data string, ranges []*source.FoldingRangeInfo) {
   364  	t.Helper()
   365  	// Fold all ranges.
   366  	nonOverlapping := nonOverlappingRanges(t, ranges)
   367  	for i, rngs := range nonOverlapping {
   368  		got, err := foldRanges(string(data), rngs)
   369  		if err != nil {
   370  			t.Error(err)
   371  			continue
   372  		}
   373  		tag := fmt.Sprintf("%s-%d", prefix, i)
   374  		want := string(r.data.Golden(tag, uri.Filename(), func() ([]byte, error) {
   375  			return []byte(got), nil
   376  		}))
   377  
   378  		if diff := tests.Diff(t, want, got); diff != "" {
   379  			t.Errorf("%s: foldingRanges failed for %s, diff:\n%v", tag, uri.Filename(), diff)
   380  		}
   381  	}
   382  
   383  	// Filter by kind.
   384  	kinds := []protocol.FoldingRangeKind{protocol.Imports, protocol.Comment}
   385  	for _, kind := range kinds {
   386  		var kindOnly []*source.FoldingRangeInfo
   387  		for _, fRng := range ranges {
   388  			if fRng.Kind == kind {
   389  				kindOnly = append(kindOnly, fRng)
   390  			}
   391  		}
   392  
   393  		nonOverlapping := nonOverlappingRanges(t, kindOnly)
   394  		for i, rngs := range nonOverlapping {
   395  			got, err := foldRanges(string(data), rngs)
   396  			if err != nil {
   397  				t.Error(err)
   398  				continue
   399  			}
   400  			tag := fmt.Sprintf("%s-%s-%d", prefix, kind, i)
   401  			want := string(r.data.Golden(tag, uri.Filename(), func() ([]byte, error) {
   402  				return []byte(got), nil
   403  			}))
   404  
   405  			if diff := tests.Diff(t, want, got); diff != "" {
   406  				t.Errorf("%s: failed for %s, diff:\n%v", tag, uri.Filename(), diff)
   407  			}
   408  		}
   409  
   410  	}
   411  }
   412  
   413  func nonOverlappingRanges(t *testing.T, ranges []*source.FoldingRangeInfo) (res [][]*source.FoldingRangeInfo) {
   414  	for _, fRng := range ranges {
   415  		setNum := len(res)
   416  		for i := 0; i < len(res); i++ {
   417  			canInsert := true
   418  			for _, rng := range res[i] {
   419  				if conflict(t, rng, fRng) {
   420  					canInsert = false
   421  					break
   422  				}
   423  			}
   424  			if canInsert {
   425  				setNum = i
   426  				break
   427  			}
   428  		}
   429  		if setNum == len(res) {
   430  			res = append(res, []*source.FoldingRangeInfo{})
   431  		}
   432  		res[setNum] = append(res[setNum], fRng)
   433  	}
   434  	return res
   435  }
   436  
   437  func conflict(t *testing.T, a, b *source.FoldingRangeInfo) bool {
   438  	arng, err := a.Range()
   439  	if err != nil {
   440  		t.Fatal(err)
   441  	}
   442  	brng, err := b.Range()
   443  	if err != nil {
   444  		t.Fatal(err)
   445  	}
   446  	// a start position is <= b start positions
   447  	return protocol.ComparePosition(arng.Start, brng.Start) <= 0 && protocol.ComparePosition(arng.End, brng.Start) > 0
   448  }
   449  
   450  func foldRanges(contents string, ranges []*source.FoldingRangeInfo) (string, error) {
   451  	foldedText := "<>"
   452  	res := contents
   453  	// Apply the folds from the end of the file forward
   454  	// to preserve the offsets.
   455  	for i := len(ranges) - 1; i >= 0; i-- {
   456  		fRange := ranges[i]
   457  		spn, err := fRange.Span()
   458  		if err != nil {
   459  			return "", err
   460  		}
   461  		start := spn.Start().Offset()
   462  		end := spn.End().Offset()
   463  
   464  		tmp := res[0:start] + foldedText
   465  		res = tmp + res[end:]
   466  	}
   467  	return res, nil
   468  }
   469  
   470  func (r *runner) Format(t *testing.T, spn span.Span) {
   471  	gofmted := string(r.data.Golden("gofmt", spn.URI().Filename(), func() ([]byte, error) {
   472  		cmd := exec.Command("gofmt", spn.URI().Filename())
   473  		out, _ := cmd.Output() // ignore error, sometimes we have intentionally ungofmt-able files
   474  		return out, nil
   475  	}))
   476  	fh, err := r.snapshot.GetFile(r.ctx, spn.URI())
   477  	if err != nil {
   478  		t.Fatal(err)
   479  	}
   480  	edits, err := source.Format(r.ctx, r.snapshot, fh)
   481  	if err != nil {
   482  		if gofmted != "" {
   483  			t.Error(err)
   484  		}
   485  		return
   486  	}
   487  	data, err := fh.Read()
   488  	if err != nil {
   489  		t.Fatal(err)
   490  	}
   491  	m, err := r.data.Mapper(spn.URI())
   492  	if err != nil {
   493  		t.Fatal(err)
   494  	}
   495  	diffEdits, err := source.FromProtocolEdits(m, edits)
   496  	if err != nil {
   497  		t.Error(err)
   498  	}
   499  	got := diff.ApplyEdits(string(data), diffEdits)
   500  	if gofmted != got {
   501  		t.Errorf("format failed for %s, expected:\n%v\ngot:\n%v", spn.URI().Filename(), gofmted, got)
   502  	}
   503  }
   504  
   505  func (r *runner) SemanticTokens(t *testing.T, spn span.Span) {
   506  	t.Skip("nothing to test in source")
   507  }
   508  
   509  func (r *runner) Import(t *testing.T, spn span.Span) {
   510  	fh, err := r.snapshot.GetFile(r.ctx, spn.URI())
   511  	if err != nil {
   512  		t.Fatal(err)
   513  	}
   514  	edits, _, err := source.AllImportsFixes(r.ctx, r.snapshot, fh)
   515  	if err != nil {
   516  		t.Error(err)
   517  	}
   518  	data, err := fh.Read()
   519  	if err != nil {
   520  		t.Fatal(err)
   521  	}
   522  	m, err := r.data.Mapper(fh.URI())
   523  	if err != nil {
   524  		t.Fatal(err)
   525  	}
   526  	diffEdits, err := source.FromProtocolEdits(m, edits)
   527  	if err != nil {
   528  		t.Error(err)
   529  	}
   530  	got := diff.ApplyEdits(string(data), diffEdits)
   531  	want := string(r.data.Golden("goimports", spn.URI().Filename(), func() ([]byte, error) {
   532  		return []byte(got), nil
   533  	}))
   534  	if want != got {
   535  		d, err := myers.ComputeEdits(spn.URI(), want, got)
   536  		if err != nil {
   537  			t.Fatal(err)
   538  		}
   539  		t.Errorf("import failed for %s: %s", spn.URI().Filename(), diff.ToUnified("want", "got", want, d))
   540  	}
   541  }
   542  
   543  func (r *runner) Definition(t *testing.T, spn span.Span, d tests.Definition) {
   544  	_, srcRng, err := spanToRange(r.data, d.Src)
   545  	if err != nil {
   546  		t.Fatal(err)
   547  	}
   548  	fh, err := r.snapshot.GetFile(r.ctx, spn.URI())
   549  	if err != nil {
   550  		t.Fatal(err)
   551  	}
   552  	ident, err := source.Identifier(r.ctx, r.snapshot, fh, srcRng.Start)
   553  	if err != nil {
   554  		t.Fatalf("failed for %v: %v", d.Src, err)
   555  	}
   556  	h, err := source.HoverIdentifier(r.ctx, ident)
   557  	if err != nil {
   558  		t.Fatalf("failed for %v: %v", d.Src, err)
   559  	}
   560  	hover, err := source.FormatHover(h, r.view.Options())
   561  	if err != nil {
   562  		t.Fatal(err)
   563  	}
   564  	rng, err := ident.Declaration.MappedRange[0].Range()
   565  	if err != nil {
   566  		t.Fatal(err)
   567  	}
   568  	if d.IsType {
   569  		rng, err = ident.Type.Range()
   570  		if err != nil {
   571  			t.Fatal(err)
   572  		}
   573  		hover = ""
   574  	}
   575  	didSomething := false
   576  	if hover != "" {
   577  		didSomething = true
   578  		tag := fmt.Sprintf("%s-hoverdef", d.Name)
   579  		expectHover := string(r.data.Golden(tag, d.Src.URI().Filename(), func() ([]byte, error) {
   580  			return []byte(hover), nil
   581  		}))
   582  		hover = tests.StripSubscripts(hover)
   583  		expectHover = tests.StripSubscripts(expectHover)
   584  		if hover != expectHover {
   585  			t.Errorf("hoverdef for %s failed:\n%s", d.Src, tests.Diff(t, expectHover, hover))
   586  		}
   587  	}
   588  	if !d.OnlyHover {
   589  		didSomething = true
   590  		if _, defRng, err := spanToRange(r.data, d.Def); err != nil {
   591  			t.Fatal(err)
   592  		} else if rng != defRng {
   593  			t.Errorf("for %v got %v want %v", d.Src, rng, defRng)
   594  		}
   595  	}
   596  	if !didSomething {
   597  		t.Errorf("no tests ran for %s", d.Src.URI())
   598  	}
   599  }
   600  
   601  func (r *runner) Implementation(t *testing.T, spn span.Span, impls []span.Span) {
   602  	sm, err := r.data.Mapper(spn.URI())
   603  	if err != nil {
   604  		t.Fatal(err)
   605  	}
   606  	loc, err := sm.Location(spn)
   607  	if err != nil {
   608  		t.Fatalf("failed for %v: %v", spn, err)
   609  	}
   610  	fh, err := r.snapshot.GetFile(r.ctx, spn.URI())
   611  	if err != nil {
   612  		t.Fatal(err)
   613  	}
   614  	locs, err := source.Implementation(r.ctx, r.snapshot, fh, loc.Range.Start)
   615  	if err != nil {
   616  		t.Fatalf("failed for %v: %v", spn, err)
   617  	}
   618  	if len(locs) != len(impls) {
   619  		t.Fatalf("got %d locations for implementation, expected %d", len(locs), len(impls))
   620  	}
   621  	var results []span.Span
   622  	for i := range locs {
   623  		locURI := locs[i].URI.SpanURI()
   624  		lm, err := r.data.Mapper(locURI)
   625  		if err != nil {
   626  			t.Fatal(err)
   627  		}
   628  		imp, err := lm.Span(locs[i])
   629  		if err != nil {
   630  			t.Fatalf("failed for %v: %v", locs[i], err)
   631  		}
   632  		results = append(results, imp)
   633  	}
   634  	// Sort results and expected to make tests deterministic.
   635  	sort.SliceStable(results, func(i, j int) bool {
   636  		return span.Compare(results[i], results[j]) == -1
   637  	})
   638  	sort.SliceStable(impls, func(i, j int) bool {
   639  		return span.Compare(impls[i], impls[j]) == -1
   640  	})
   641  	for i := range results {
   642  		if results[i] != impls[i] {
   643  			t.Errorf("for %dth implementation of %v got %v want %v", i, spn, results[i], impls[i])
   644  		}
   645  	}
   646  }
   647  
   648  func (r *runner) Highlight(t *testing.T, src span.Span, locations []span.Span) {
   649  	ctx := r.ctx
   650  	m, srcRng, err := spanToRange(r.data, src)
   651  	if err != nil {
   652  		t.Fatal(err)
   653  	}
   654  	fh, err := r.snapshot.GetFile(r.ctx, src.URI())
   655  	if err != nil {
   656  		t.Fatal(err)
   657  	}
   658  	highlights, err := source.Highlight(ctx, r.snapshot, fh, srcRng.Start)
   659  	if err != nil {
   660  		t.Errorf("highlight failed for %s: %v", src.URI(), err)
   661  	}
   662  	if len(highlights) != len(locations) {
   663  		t.Fatalf("got %d highlights for highlight at %v:%v:%v, expected %d", len(highlights), src.URI().Filename(), src.Start().Line(), src.Start().Column(), len(locations))
   664  	}
   665  	// Check to make sure highlights have a valid range.
   666  	var results []span.Span
   667  	for i := range highlights {
   668  		h, err := m.RangeSpan(highlights[i])
   669  		if err != nil {
   670  			t.Fatalf("failed for %v: %v", highlights[i], err)
   671  		}
   672  		results = append(results, h)
   673  	}
   674  	// Sort results to make tests deterministic since DocumentHighlight uses a map.
   675  	sort.SliceStable(results, func(i, j int) bool {
   676  		return span.Compare(results[i], results[j]) == -1
   677  	})
   678  	// Check to make sure all the expected highlights are found.
   679  	for i := range results {
   680  		if results[i] != locations[i] {
   681  			t.Errorf("want %v, got %v\n", locations[i], results[i])
   682  		}
   683  	}
   684  }
   685  
   686  func (r *runner) Hover(t *testing.T, src span.Span, text string) {
   687  	ctx := r.ctx
   688  	_, srcRng, err := spanToRange(r.data, src)
   689  	if err != nil {
   690  		t.Fatal(err)
   691  	}
   692  	fh, err := r.snapshot.GetFile(r.ctx, src.URI())
   693  	if err != nil {
   694  		t.Fatal(err)
   695  	}
   696  	hover, err := source.Hover(ctx, r.snapshot, fh, srcRng.Start)
   697  	if err != nil {
   698  		t.Errorf("hover failed for %s: %v", src.URI(), err)
   699  	}
   700  	if text == "" {
   701  		if hover != nil {
   702  			t.Errorf("want nil, got %v\n", hover)
   703  		}
   704  	} else {
   705  		if hover == nil {
   706  			t.Fatalf("want hover result to not be nil")
   707  		}
   708  		if got := hover.Contents.Value; got != text {
   709  			t.Errorf("want %v, got %v\n", got, text)
   710  		}
   711  		if want, got := srcRng, hover.Range; want != got {
   712  			t.Errorf("want range %v, got %v instead", want, got)
   713  		}
   714  	}
   715  }
   716  
   717  func (r *runner) References(t *testing.T, src span.Span, itemList []span.Span) {
   718  	ctx := r.ctx
   719  	_, srcRng, err := spanToRange(r.data, src)
   720  	if err != nil {
   721  		t.Fatal(err)
   722  	}
   723  	snapshot := r.snapshot
   724  	fh, err := snapshot.GetFile(r.ctx, src.URI())
   725  	if err != nil {
   726  		t.Fatal(err)
   727  	}
   728  	for _, includeDeclaration := range []bool{true, false} {
   729  		t.Run(fmt.Sprintf("refs-declaration-%v", includeDeclaration), func(t *testing.T) {
   730  			want := make(map[span.Span]bool)
   731  			for i, pos := range itemList {
   732  				// We don't want the first result if we aren't including the declaration.
   733  				if i == 0 && !includeDeclaration {
   734  					continue
   735  				}
   736  				want[pos] = true
   737  			}
   738  			refs, err := source.References(ctx, snapshot, fh, srcRng.Start, includeDeclaration)
   739  			if err != nil {
   740  				t.Fatalf("failed for %s: %v", src, err)
   741  			}
   742  			got := make(map[span.Span]bool)
   743  			for _, refInfo := range refs {
   744  				refSpan, err := refInfo.Span()
   745  				if err != nil {
   746  					t.Fatal(err)
   747  				}
   748  				got[refSpan] = true
   749  			}
   750  			if len(got) != len(want) {
   751  				t.Errorf("references failed: different lengths got %v want %v", len(got), len(want))
   752  			}
   753  			for spn := range got {
   754  				if !want[spn] {
   755  					t.Errorf("references failed: incorrect references got %v want locations %v", got, want)
   756  				}
   757  			}
   758  		})
   759  	}
   760  }
   761  
   762  func (r *runner) Rename(t *testing.T, spn span.Span, newText string) {
   763  	tag := fmt.Sprintf("%s-rename", newText)
   764  
   765  	_, srcRng, err := spanToRange(r.data, spn)
   766  	if err != nil {
   767  		t.Fatal(err)
   768  	}
   769  	fh, err := r.snapshot.GetFile(r.ctx, spn.URI())
   770  	if err != nil {
   771  		t.Fatal(err)
   772  	}
   773  	changes, err := source.Rename(r.ctx, r.snapshot, fh, srcRng.Start, newText)
   774  	if err != nil {
   775  		renamed := string(r.data.Golden(tag, spn.URI().Filename(), func() ([]byte, error) {
   776  			return []byte(err.Error()), nil
   777  		}))
   778  		if err.Error() != renamed {
   779  			t.Errorf("rename failed for %s, expected:\n%v\ngot:\n%v\n", newText, renamed, err)
   780  		}
   781  		return
   782  	}
   783  
   784  	var res []string
   785  	for editURI, edits := range changes {
   786  		fh, err := r.snapshot.GetFile(r.ctx, editURI)
   787  		if err != nil {
   788  			t.Fatal(err)
   789  		}
   790  		data, err := fh.Read()
   791  		if err != nil {
   792  			t.Fatal(err)
   793  		}
   794  		m, err := r.data.Mapper(fh.URI())
   795  		if err != nil {
   796  			t.Fatal(err)
   797  		}
   798  		diffEdits, err := source.FromProtocolEdits(m, edits)
   799  		if err != nil {
   800  			t.Fatal(err)
   801  		}
   802  		contents := applyEdits(string(data), diffEdits)
   803  		if len(changes) > 1 {
   804  			filename := filepath.Base(editURI.Filename())
   805  			contents = fmt.Sprintf("%s:\n%s", filename, contents)
   806  		}
   807  		res = append(res, contents)
   808  	}
   809  
   810  	// Sort on filename
   811  	sort.Strings(res)
   812  
   813  	var got string
   814  	for i, val := range res {
   815  		if i != 0 {
   816  			got += "\n"
   817  		}
   818  		got += val
   819  	}
   820  
   821  	renamed := string(r.data.Golden(tag, spn.URI().Filename(), func() ([]byte, error) {
   822  		return []byte(got), nil
   823  	}))
   824  
   825  	if renamed != got {
   826  		t.Errorf("rename failed for %s, expected:\n%v\ngot:\n%v", newText, renamed, got)
   827  	}
   828  }
   829  
   830  func applyEdits(contents string, edits []diff.TextEdit) string {
   831  	res := contents
   832  
   833  	// Apply the edits from the end of the file forward
   834  	// to preserve the offsets
   835  	for i := len(edits) - 1; i >= 0; i-- {
   836  		edit := edits[i]
   837  		start := edit.Span.Start().Offset()
   838  		end := edit.Span.End().Offset()
   839  		tmp := res[0:start] + edit.NewText
   840  		res = tmp + res[end:]
   841  	}
   842  	return res
   843  }
   844  
   845  func (r *runner) PrepareRename(t *testing.T, src span.Span, want *source.PrepareItem) {
   846  	_, srcRng, err := spanToRange(r.data, src)
   847  	if err != nil {
   848  		t.Fatal(err)
   849  	}
   850  	// Find the identifier at the position.
   851  	fh, err := r.snapshot.GetFile(r.ctx, src.URI())
   852  	if err != nil {
   853  		t.Fatal(err)
   854  	}
   855  	item, _, err := source.PrepareRename(r.ctx, r.snapshot, fh, srcRng.Start)
   856  	if err != nil {
   857  		if want.Text != "" { // expected an ident.
   858  			t.Errorf("prepare rename failed for %v: got error: %v", src, err)
   859  		}
   860  		return
   861  	}
   862  	if item == nil {
   863  		if want.Text != "" {
   864  			t.Errorf("prepare rename failed for %v: got nil", src)
   865  		}
   866  		return
   867  	}
   868  	if want.Text == "" {
   869  		t.Errorf("prepare rename failed for %v: expected nil, got %v", src, item)
   870  		return
   871  	}
   872  	if item.Range.Start == item.Range.End {
   873  		// Special case for 0-length ranges. Marks can't specify a 0-length range,
   874  		// so just compare the start.
   875  		if item.Range.Start != want.Range.Start {
   876  			t.Errorf("prepare rename failed: incorrect point, got %v want %v", item.Range.Start, want.Range.Start)
   877  		}
   878  	} else {
   879  		if protocol.CompareRange(item.Range, want.Range) != 0 {
   880  			t.Errorf("prepare rename failed: incorrect range got %v want %v", item.Range, want.Range)
   881  		}
   882  	}
   883  }
   884  
   885  func (r *runner) Symbols(t *testing.T, uri span.URI, expectedSymbols []protocol.DocumentSymbol) {
   886  	fh, err := r.snapshot.GetFile(r.ctx, uri)
   887  	if err != nil {
   888  		t.Fatal(err)
   889  	}
   890  	symbols, err := source.DocumentSymbols(r.ctx, r.snapshot, fh)
   891  	if err != nil {
   892  		t.Errorf("symbols failed for %s: %v", uri, err)
   893  	}
   894  	if len(symbols) != len(expectedSymbols) {
   895  		t.Errorf("want %d top-level symbols in %v, got %d", len(expectedSymbols), uri, len(symbols))
   896  		return
   897  	}
   898  	if diff := tests.DiffSymbols(t, uri, expectedSymbols, symbols); diff != "" {
   899  		t.Error(diff)
   900  	}
   901  }
   902  
   903  func (r *runner) WorkspaceSymbols(t *testing.T, uri span.URI, query string, typ tests.WorkspaceSymbolsTestType) {
   904  	r.callWorkspaceSymbols(t, uri, query, typ)
   905  }
   906  
   907  func (r *runner) callWorkspaceSymbols(t *testing.T, uri span.URI, query string, typ tests.WorkspaceSymbolsTestType) {
   908  	t.Helper()
   909  
   910  	matcher := tests.WorkspaceSymbolsTestTypeToMatcher(typ)
   911  	gotSymbols, err := source.WorkspaceSymbols(r.ctx, matcher, r.view.Options().SymbolStyle, []source.View{r.view}, query)
   912  	if err != nil {
   913  		t.Fatal(err)
   914  	}
   915  	got, err := tests.WorkspaceSymbolsString(r.ctx, r.data, uri, gotSymbols)
   916  	if err != nil {
   917  		t.Fatal(err)
   918  	}
   919  	got = filepath.ToSlash(tests.Normalize(got, r.normalizers))
   920  	want := string(r.data.Golden(fmt.Sprintf("workspace_symbol-%s-%s", strings.ToLower(string(matcher)), query), uri.Filename(), func() ([]byte, error) {
   921  		return []byte(got), nil
   922  	}))
   923  	if diff := tests.Diff(t, want, got); diff != "" {
   924  		t.Error(diff)
   925  	}
   926  }
   927  
   928  func (r *runner) SignatureHelp(t *testing.T, spn span.Span, want *protocol.SignatureHelp) {
   929  	_, rng, err := spanToRange(r.data, spn)
   930  	if err != nil {
   931  		t.Fatal(err)
   932  	}
   933  	fh, err := r.snapshot.GetFile(r.ctx, spn.URI())
   934  	if err != nil {
   935  		t.Fatal(err)
   936  	}
   937  	gotSignature, gotActiveParameter, err := source.SignatureHelp(r.ctx, r.snapshot, fh, rng.Start)
   938  	if err != nil {
   939  		// Only fail if we got an error we did not expect.
   940  		if want != nil {
   941  			t.Fatalf("failed for %v: %v", spn, err)
   942  		}
   943  		return
   944  	}
   945  	if gotSignature == nil {
   946  		if want != nil {
   947  			t.Fatalf("got nil signature, but expected %v", want)
   948  		}
   949  		return
   950  	}
   951  	got := &protocol.SignatureHelp{
   952  		Signatures:      []protocol.SignatureInformation{*gotSignature},
   953  		ActiveParameter: uint32(gotActiveParameter),
   954  	}
   955  	diff, err := tests.DiffSignatures(spn, want, got)
   956  	if err != nil {
   957  		t.Fatal(err)
   958  	}
   959  	if diff != "" {
   960  		t.Error(diff)
   961  	}
   962  }
   963  
   964  // These are pure LSP features, no source level functionality to be tested.
   965  func (r *runner) Link(t *testing.T, uri span.URI, wantLinks []tests.Link) {}
   966  
   967  func (r *runner) SuggestedFix(t *testing.T, spn span.Span, actionKinds []string, expectedActions int) {
   968  }
   969  func (r *runner) FunctionExtraction(t *testing.T, start span.Span, end span.Span) {}
   970  func (r *runner) MethodExtraction(t *testing.T, start span.Span, end span.Span)   {}
   971  func (r *runner) CodeLens(t *testing.T, uri span.URI, want []protocol.CodeLens)   {}
   972  func (r *runner) AddImport(t *testing.T, uri span.URI, expectedImport string)     {}
   973  
   974  func spanToRange(data *tests.Data, spn span.Span) (*protocol.ColumnMapper, protocol.Range, error) {
   975  	m, err := data.Mapper(spn.URI())
   976  	if err != nil {
   977  		return nil, protocol.Range{}, err
   978  	}
   979  	srcRng, err := m.Range(spn)
   980  	if err != nil {
   981  		return nil, protocol.Range{}, err
   982  	}
   983  	return m, srcRng, nil
   984  }