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