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 }