github.com/v2fly/tools@v0.100.0/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/v2fly/tools/internal/lsp/cache" 18 "github.com/v2fly/tools/internal/lsp/diff" 19 "github.com/v2fly/tools/internal/lsp/diff/myers" 20 "github.com/v2fly/tools/internal/lsp/fuzzy" 21 "github.com/v2fly/tools/internal/lsp/protocol" 22 "github.com/v2fly/tools/internal/lsp/source" 23 "github.com/v2fly/tools/internal/lsp/source/completion" 24 "github.com/v2fly/tools/internal/lsp/tests" 25 "github.com/v2fly/tools/internal/span" 26 "github.com/v2fly/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 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 opts.LiteralCompletions = strings.Contains(string(src.URI()), "literal") 181 opts.ExperimentalPostfixCompletions = strings.Contains(string(src.URI()), "postfix") 182 }) 183 got = tests.FilterBuiltins(src, got) 184 if diff := tests.DiffCompletionItems(want, got); diff != "" { 185 t.Errorf("%s: %s", src, diff) 186 } 187 } 188 189 func (r *runner) CompletionSnippet(t *testing.T, src span.Span, expected tests.CompletionSnippet, placeholders bool, items tests.CompletionItems) { 190 _, list := r.callCompletion(t, src, func(opts *source.Options) { 191 opts.UsePlaceholders = placeholders 192 opts.DeepCompletion = true 193 opts.CompleteUnimported = false 194 }) 195 got := tests.FindItem(list, *items[expected.CompletionItem]) 196 want := expected.PlainSnippet 197 if placeholders { 198 want = expected.PlaceholderSnippet 199 } 200 if diff := tests.DiffSnippets(want, got); diff != "" { 201 t.Errorf("%s: %s", src, diff) 202 } 203 } 204 205 func (r *runner) UnimportedCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) { 206 var want []protocol.CompletionItem 207 for _, pos := range test.CompletionItems { 208 want = append(want, tests.ToProtocolCompletionItem(*items[pos])) 209 } 210 _, got := r.callCompletion(t, src, func(opts *source.Options) {}) 211 got = tests.FilterBuiltins(src, got) 212 if diff := tests.CheckCompletionOrder(want, got, false); diff != "" { 213 t.Errorf("%s: %s", src, diff) 214 } 215 } 216 217 func (r *runner) DeepCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) { 218 var want []protocol.CompletionItem 219 for _, pos := range test.CompletionItems { 220 want = append(want, tests.ToProtocolCompletionItem(*items[pos])) 221 } 222 prefix, list := r.callCompletion(t, src, func(opts *source.Options) { 223 opts.DeepCompletion = true 224 opts.Matcher = source.CaseInsensitive 225 opts.CompleteUnimported = false 226 }) 227 list = tests.FilterBuiltins(src, list) 228 fuzzyMatcher := fuzzy.NewMatcher(prefix) 229 var got []protocol.CompletionItem 230 for _, item := range list { 231 if fuzzyMatcher.Score(item.Label) <= 0 { 232 continue 233 } 234 got = append(got, item) 235 } 236 if msg := tests.DiffCompletionItems(want, got); msg != "" { 237 t.Errorf("%s: %s", src, msg) 238 } 239 } 240 241 func (r *runner) FuzzyCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) { 242 var want []protocol.CompletionItem 243 for _, pos := range test.CompletionItems { 244 want = append(want, tests.ToProtocolCompletionItem(*items[pos])) 245 } 246 _, got := r.callCompletion(t, src, func(opts *source.Options) { 247 opts.DeepCompletion = true 248 opts.Matcher = source.Fuzzy 249 opts.CompleteUnimported = false 250 }) 251 got = tests.FilterBuiltins(src, got) 252 if msg := tests.DiffCompletionItems(want, got); msg != "" { 253 t.Errorf("%s: %s", src, msg) 254 } 255 } 256 257 func (r *runner) CaseSensitiveCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) { 258 var want []protocol.CompletionItem 259 for _, pos := range test.CompletionItems { 260 want = append(want, tests.ToProtocolCompletionItem(*items[pos])) 261 } 262 _, list := r.callCompletion(t, src, func(opts *source.Options) { 263 opts.Matcher = source.CaseSensitive 264 opts.CompleteUnimported = false 265 }) 266 list = tests.FilterBuiltins(src, list) 267 if diff := tests.DiffCompletionItems(want, list); diff != "" { 268 t.Errorf("%s: %s", src, diff) 269 } 270 } 271 272 func (r *runner) RankCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) { 273 var want []protocol.CompletionItem 274 for _, pos := range test.CompletionItems { 275 want = append(want, tests.ToProtocolCompletionItem(*items[pos])) 276 } 277 _, got := r.callCompletion(t, src, func(opts *source.Options) { 278 opts.DeepCompletion = true 279 opts.Matcher = source.Fuzzy 280 opts.ExperimentalPostfixCompletions = true 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: uint32(src.Start().Line() - 1), 306 Character: uint32(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 diff := tests.Diff(t, want, got); diff != "" { 380 t.Errorf("%s: foldingRanges failed for %s, diff:\n%v", tag, uri.Filename(), diff) 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 diff := tests.Diff(t, want, got); diff != "" { 407 t.Errorf("%s: failed for %s, diff:\n%v", tag, uri.Filename(), diff) 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, err := myers.ComputeEdits(spn.URI(), want, got) 537 if err != nil { 538 t.Fatal(err) 539 } 540 t.Errorf("import failed for %s: %s", spn.URI().Filename(), diff.ToUnified("want", "got", want, d)) 541 } 542 } 543 544 func (r *runner) Definition(t *testing.T, spn span.Span, d tests.Definition) { 545 _, srcRng, err := spanToRange(r.data, d.Src) 546 if err != nil { 547 t.Fatal(err) 548 } 549 fh, err := r.snapshot.GetFile(r.ctx, spn.URI()) 550 if err != nil { 551 t.Fatal(err) 552 } 553 ident, err := source.Identifier(r.ctx, r.snapshot, fh, srcRng.Start) 554 if err != nil { 555 t.Fatalf("failed for %v: %v", d.Src, err) 556 } 557 h, err := source.HoverIdentifier(r.ctx, ident) 558 if err != nil { 559 t.Fatalf("failed for %v: %v", d.Src, err) 560 } 561 hover, err := source.FormatHover(h, r.view.Options()) 562 if err != nil { 563 t.Fatal(err) 564 } 565 rng, err := ident.Declaration.MappedRange[0].Range() 566 if err != nil { 567 t.Fatal(err) 568 } 569 if d.IsType { 570 rng, err = ident.Type.Range() 571 if err != nil { 572 t.Fatal(err) 573 } 574 hover = "" 575 } 576 didSomething := false 577 if hover != "" { 578 didSomething = true 579 tag := fmt.Sprintf("%s-hover", d.Name) 580 expectHover := string(r.data.Golden(tag, d.Src.URI().Filename(), func() ([]byte, error) { 581 return []byte(hover), nil 582 })) 583 if hover != expectHover { 584 t.Errorf("hover for %s failed:\n%s", d.Src, tests.Diff(t, expectHover, hover)) 585 } 586 } 587 if !d.OnlyHover { 588 didSomething = true 589 if _, defRng, err := spanToRange(r.data, d.Def); err != nil { 590 t.Fatal(err) 591 } else if rng != defRng { 592 t.Errorf("for %v got %v want %v", d.Src, rng, defRng) 593 } 594 } 595 if !didSomething { 596 t.Errorf("no tests ran for %s", d.Src.URI()) 597 } 598 } 599 600 func (r *runner) Implementation(t *testing.T, spn span.Span, impls []span.Span) { 601 sm, err := r.data.Mapper(spn.URI()) 602 if err != nil { 603 t.Fatal(err) 604 } 605 loc, err := sm.Location(spn) 606 if err != nil { 607 t.Fatalf("failed for %v: %v", spn, err) 608 } 609 fh, err := r.snapshot.GetFile(r.ctx, spn.URI()) 610 if err != nil { 611 t.Fatal(err) 612 } 613 locs, err := source.Implementation(r.ctx, r.snapshot, fh, loc.Range.Start) 614 if err != nil { 615 t.Fatalf("failed for %v: %v", spn, err) 616 } 617 if len(locs) != len(impls) { 618 t.Fatalf("got %d locations for implementation, expected %d", len(locs), len(impls)) 619 } 620 var results []span.Span 621 for i := range locs { 622 locURI := locs[i].URI.SpanURI() 623 lm, err := r.data.Mapper(locURI) 624 if err != nil { 625 t.Fatal(err) 626 } 627 imp, err := lm.Span(locs[i]) 628 if err != nil { 629 t.Fatalf("failed for %v: %v", locs[i], err) 630 } 631 results = append(results, imp) 632 } 633 // Sort results and expected to make tests deterministic. 634 sort.SliceStable(results, func(i, j int) bool { 635 return span.Compare(results[i], results[j]) == -1 636 }) 637 sort.SliceStable(impls, func(i, j int) bool { 638 return span.Compare(impls[i], impls[j]) == -1 639 }) 640 for i := range results { 641 if results[i] != impls[i] { 642 t.Errorf("for %dth implementation of %v got %v want %v", i, spn, results[i], impls[i]) 643 } 644 } 645 } 646 647 func (r *runner) Highlight(t *testing.T, src span.Span, locations []span.Span) { 648 ctx := r.ctx 649 m, srcRng, err := spanToRange(r.data, src) 650 if err != nil { 651 t.Fatal(err) 652 } 653 fh, err := r.snapshot.GetFile(r.ctx, src.URI()) 654 if err != nil { 655 t.Fatal(err) 656 } 657 highlights, err := source.Highlight(ctx, r.snapshot, fh, srcRng.Start) 658 if err != nil { 659 t.Errorf("highlight failed for %s: %v", src.URI(), err) 660 } 661 if len(highlights) != len(locations) { 662 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)) 663 } 664 // Check to make sure highlights have a valid range. 665 var results []span.Span 666 for i := range highlights { 667 h, err := m.RangeSpan(highlights[i]) 668 if err != nil { 669 t.Fatalf("failed for %v: %v", highlights[i], err) 670 } 671 results = append(results, h) 672 } 673 // Sort results to make tests deterministic since DocumentHighlight uses a map. 674 sort.SliceStable(results, func(i, j int) bool { 675 return span.Compare(results[i], results[j]) == -1 676 }) 677 // Check to make sure all the expected highlights are found. 678 for i := range results { 679 if results[i] != locations[i] { 680 t.Errorf("want %v, got %v\n", locations[i], results[i]) 681 } 682 } 683 } 684 685 func (r *runner) References(t *testing.T, src span.Span, itemList []span.Span) { 686 ctx := r.ctx 687 _, srcRng, err := spanToRange(r.data, src) 688 if err != nil { 689 t.Fatal(err) 690 } 691 snapshot := r.snapshot 692 fh, err := snapshot.GetFile(r.ctx, src.URI()) 693 if err != nil { 694 t.Fatal(err) 695 } 696 for _, includeDeclaration := range []bool{true, false} { 697 t.Run(fmt.Sprintf("refs-declaration-%v", includeDeclaration), func(t *testing.T) { 698 want := make(map[span.Span]bool) 699 for i, pos := range itemList { 700 // We don't want the first result if we aren't including the declaration. 701 if i == 0 && !includeDeclaration { 702 continue 703 } 704 want[pos] = true 705 } 706 refs, err := source.References(ctx, snapshot, fh, srcRng.Start, includeDeclaration) 707 if err != nil { 708 t.Fatalf("failed for %s: %v", src, err) 709 } 710 got := make(map[span.Span]bool) 711 for _, refInfo := range refs { 712 refSpan, err := refInfo.Span() 713 if err != nil { 714 t.Fatal(err) 715 } 716 got[refSpan] = true 717 } 718 if len(got) != len(want) { 719 t.Errorf("references failed: different lengths got %v want %v", len(got), len(want)) 720 } 721 for spn := range got { 722 if !want[spn] { 723 t.Errorf("references failed: incorrect references got %v want locations %v", got, want) 724 } 725 } 726 }) 727 } 728 } 729 730 func (r *runner) Rename(t *testing.T, spn span.Span, newText string) { 731 tag := fmt.Sprintf("%s-rename", newText) 732 733 _, srcRng, err := spanToRange(r.data, spn) 734 if err != nil { 735 t.Fatal(err) 736 } 737 fh, err := r.snapshot.GetFile(r.ctx, spn.URI()) 738 if err != nil { 739 t.Fatal(err) 740 } 741 changes, err := source.Rename(r.ctx, r.snapshot, fh, srcRng.Start, newText) 742 if err != nil { 743 renamed := string(r.data.Golden(tag, spn.URI().Filename(), func() ([]byte, error) { 744 return []byte(err.Error()), nil 745 })) 746 if err.Error() != renamed { 747 t.Errorf("rename failed for %s, expected:\n%v\ngot:\n%v\n", newText, renamed, err) 748 } 749 return 750 } 751 752 var res []string 753 for editURI, edits := range changes { 754 fh, err := r.snapshot.GetFile(r.ctx, editURI) 755 if err != nil { 756 t.Fatal(err) 757 } 758 data, err := fh.Read() 759 if err != nil { 760 t.Fatal(err) 761 } 762 m, err := r.data.Mapper(fh.URI()) 763 if err != nil { 764 t.Fatal(err) 765 } 766 diffEdits, err := source.FromProtocolEdits(m, edits) 767 if err != nil { 768 t.Fatal(err) 769 } 770 contents := applyEdits(string(data), diffEdits) 771 if len(changes) > 1 { 772 filename := filepath.Base(editURI.Filename()) 773 contents = fmt.Sprintf("%s:\n%s", filename, contents) 774 } 775 res = append(res, contents) 776 } 777 778 // Sort on filename 779 sort.Strings(res) 780 781 var got string 782 for i, val := range res { 783 if i != 0 { 784 got += "\n" 785 } 786 got += val 787 } 788 789 renamed := string(r.data.Golden(tag, spn.URI().Filename(), func() ([]byte, error) { 790 return []byte(got), nil 791 })) 792 793 if renamed != got { 794 t.Errorf("rename failed for %s, expected:\n%v\ngot:\n%v", newText, renamed, got) 795 } 796 } 797 798 func applyEdits(contents string, edits []diff.TextEdit) string { 799 res := contents 800 801 // Apply the edits from the end of the file forward 802 // to preserve the offsets 803 for i := len(edits) - 1; i >= 0; i-- { 804 edit := edits[i] 805 start := edit.Span.Start().Offset() 806 end := edit.Span.End().Offset() 807 tmp := res[0:start] + edit.NewText 808 res = tmp + res[end:] 809 } 810 return res 811 } 812 813 func (r *runner) PrepareRename(t *testing.T, src span.Span, want *source.PrepareItem) { 814 _, srcRng, err := spanToRange(r.data, src) 815 if err != nil { 816 t.Fatal(err) 817 } 818 // Find the identifier at the position. 819 fh, err := r.snapshot.GetFile(r.ctx, src.URI()) 820 if err != nil { 821 t.Fatal(err) 822 } 823 item, _, err := source.PrepareRename(r.ctx, r.snapshot, fh, srcRng.Start) 824 if err != nil { 825 if want.Text != "" { // expected an ident. 826 t.Errorf("prepare rename failed for %v: got error: %v", src, err) 827 } 828 return 829 } 830 if item == nil { 831 if want.Text != "" { 832 t.Errorf("prepare rename failed for %v: got nil", src) 833 } 834 return 835 } 836 if want.Text == "" { 837 t.Errorf("prepare rename failed for %v: expected nil, got %v", src, item) 838 return 839 } 840 if item.Range.Start == item.Range.End { 841 // Special case for 0-length ranges. Marks can't specify a 0-length range, 842 // so just compare the start. 843 if item.Range.Start != want.Range.Start { 844 t.Errorf("prepare rename failed: incorrect point, got %v want %v", item.Range.Start, want.Range.Start) 845 } 846 } else { 847 if protocol.CompareRange(item.Range, want.Range) != 0 { 848 t.Errorf("prepare rename failed: incorrect range got %v want %v", item.Range, want.Range) 849 } 850 } 851 } 852 853 func (r *runner) Symbols(t *testing.T, uri span.URI, expectedSymbols []protocol.DocumentSymbol) { 854 fh, err := r.snapshot.GetFile(r.ctx, uri) 855 if err != nil { 856 t.Fatal(err) 857 } 858 symbols, err := source.DocumentSymbols(r.ctx, r.snapshot, fh) 859 if err != nil { 860 t.Errorf("symbols failed for %s: %v", uri, err) 861 } 862 if len(symbols) != len(expectedSymbols) { 863 t.Errorf("want %d top-level symbols in %v, got %d", len(expectedSymbols), uri, len(symbols)) 864 return 865 } 866 if diff := tests.DiffSymbols(t, uri, expectedSymbols, symbols); diff != "" { 867 t.Error(diff) 868 } 869 } 870 871 func (r *runner) WorkspaceSymbols(t *testing.T, uri span.URI, query string, typ tests.WorkspaceSymbolsTestType) { 872 r.callWorkspaceSymbols(t, uri, query, typ) 873 } 874 875 func (r *runner) callWorkspaceSymbols(t *testing.T, uri span.URI, query string, typ tests.WorkspaceSymbolsTestType) { 876 t.Helper() 877 878 matcher := tests.WorkspaceSymbolsTestTypeToMatcher(typ) 879 gotSymbols, err := source.WorkspaceSymbols(r.ctx, matcher, r.view.Options().SymbolStyle, []source.View{r.view}, query) 880 if err != nil { 881 t.Fatal(err) 882 } 883 got, err := tests.WorkspaceSymbolsString(r.ctx, r.data, uri, gotSymbols) 884 if err != nil { 885 t.Fatal(err) 886 } 887 got = filepath.ToSlash(tests.Normalize(got, r.normalizers)) 888 want := string(r.data.Golden(fmt.Sprintf("workspace_symbol-%s-%s", strings.ToLower(string(matcher)), query), uri.Filename(), func() ([]byte, error) { 889 return []byte(got), nil 890 })) 891 if diff := tests.Diff(t, want, got); diff != "" { 892 t.Error(diff) 893 } 894 } 895 896 func (r *runner) SignatureHelp(t *testing.T, spn span.Span, want *protocol.SignatureHelp) { 897 _, rng, err := spanToRange(r.data, spn) 898 if err != nil { 899 t.Fatal(err) 900 } 901 fh, err := r.snapshot.GetFile(r.ctx, spn.URI()) 902 if err != nil { 903 t.Fatal(err) 904 } 905 gotSignature, gotActiveParameter, err := source.SignatureHelp(r.ctx, r.snapshot, fh, rng.Start) 906 if err != nil { 907 // Only fail if we got an error we did not expect. 908 if want != nil { 909 t.Fatalf("failed for %v: %v", spn, err) 910 } 911 return 912 } 913 if gotSignature == nil { 914 if want != nil { 915 t.Fatalf("got nil signature, but expected %v", want) 916 } 917 return 918 } 919 got := &protocol.SignatureHelp{ 920 Signatures: []protocol.SignatureInformation{*gotSignature}, 921 ActiveParameter: uint32(gotActiveParameter), 922 } 923 diff, err := tests.DiffSignatures(spn, want, got) 924 if err != nil { 925 t.Fatal(err) 926 } 927 if diff != "" { 928 t.Error(diff) 929 } 930 } 931 932 // These are pure LSP features, no source level functionality to be tested. 933 func (r *runner) Link(t *testing.T, uri span.URI, wantLinks []tests.Link) {} 934 935 func (r *runner) SuggestedFix(t *testing.T, spn span.Span, actionKinds []string, expectedActions int) { 936 } 937 func (r *runner) FunctionExtraction(t *testing.T, start span.Span, end span.Span) {} 938 func (r *runner) CodeLens(t *testing.T, uri span.URI, want []protocol.CodeLens) {} 939 940 func spanToRange(data *tests.Data, spn span.Span) (*protocol.ColumnMapper, protocol.Range, error) { 941 m, err := data.Mapper(spn.URI()) 942 if err != nil { 943 return nil, protocol.Range{}, err 944 } 945 srcRng, err := m.Range(spn) 946 if err != nil { 947 return nil, protocol.Range{}, err 948 } 949 return m, srcRng, nil 950 }