1// Copyright 2019 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 5package tests 6 7import ( 8 "context" 9 "flag" 10 "go/ast" 11 "go/token" 12 "io/ioutil" 13 "path/filepath" 14 "sort" 15 "strings" 16 "testing" 17 18 "golang.org/x/tools/go/packages" 19 "golang.org/x/tools/go/packages/packagestest" 20 "golang.org/x/tools/internal/lsp/source" 21 "golang.org/x/tools/internal/span" 22 "golang.org/x/tools/internal/txtar" 23) 24 25// We hardcode the expected number of test cases to ensure that all tests 26// are being executed. If a test is added, this number must be changed. 27const ( 28 ExpectedCompletionsCount = 137 29 ExpectedCompletionSnippetCount = 15 30 ExpectedDiagnosticsCount = 17 31 ExpectedFormatCount = 5 32 ExpectedImportCount = 2 33 ExpectedDefinitionsCount = 38 34 ExpectedTypeDefinitionsCount = 2 35 ExpectedHighlightsCount = 2 36 ExpectedReferencesCount = 2 37 ExpectedRenamesCount = 8 38 ExpectedSymbolsCount = 1 39 ExpectedSignaturesCount = 20 40 ExpectedLinksCount = 2 41) 42 43const ( 44 overlayFileSuffix = ".overlay" 45 goldenFileSuffix = ".golden" 46 inFileSuffix = ".in" 47 testModule = "golang.org/x/tools/internal/lsp" 48) 49 50var updateGolden = flag.Bool("golden", false, "Update golden files") 51 52type Diagnostics map[span.URI][]source.Diagnostic 53type CompletionItems map[token.Pos]*source.CompletionItem 54type Completions map[span.Span][]token.Pos 55type CompletionSnippets map[span.Span]CompletionSnippet 56type Formats []span.Span 57type Imports []span.Span 58type Definitions map[span.Span]Definition 59type Highlights map[string][]span.Span 60type References map[span.Span][]span.Span 61type Renames map[span.Span]string 62type Symbols map[span.URI][]source.Symbol 63type SymbolsChildren map[string][]source.Symbol 64type Signatures map[span.Span]source.SignatureInformation 65type Links map[span.URI][]Link 66 67type Data struct { 68 Config packages.Config 69 Exported *packagestest.Exported 70 Diagnostics Diagnostics 71 CompletionItems CompletionItems 72 Completions Completions 73 CompletionSnippets CompletionSnippets 74 Formats Formats 75 Imports Imports 76 Definitions Definitions 77 Highlights Highlights 78 References References 79 Renames Renames 80 Symbols Symbols 81 symbolsChildren SymbolsChildren 82 Signatures Signatures 83 Links Links 84 85 t testing.TB 86 fragments map[string]string 87 dir string 88 golden map[string]*Golden 89} 90 91type Tests interface { 92 Diagnostics(*testing.T, Diagnostics) 93 Completion(*testing.T, Completions, CompletionSnippets, CompletionItems) 94 Format(*testing.T, Formats) 95 Import(*testing.T, Imports) 96 Definition(*testing.T, Definitions) 97 Highlight(*testing.T, Highlights) 98 Reference(*testing.T, References) 99 Rename(*testing.T, Renames) 100 Symbol(*testing.T, Symbols) 101 SignatureHelp(*testing.T, Signatures) 102 Link(*testing.T, Links) 103} 104 105type Definition struct { 106 Name string 107 Src span.Span 108 IsType bool 109 OnlyHover bool 110 Def span.Span 111} 112 113type CompletionSnippet struct { 114 CompletionItem token.Pos 115 PlainSnippet string 116 PlaceholderSnippet string 117} 118 119type Link struct { 120 Src span.Span 121 Target string 122} 123 124type Golden struct { 125 Filename string 126 Archive *txtar.Archive 127 Modified bool 128} 129 130func Load(t testing.TB, exporter packagestest.Exporter, dir string) *Data { 131 t.Helper() 132 133 data := &Data{ 134 Diagnostics: make(Diagnostics), 135 CompletionItems: make(CompletionItems), 136 Completions: make(Completions), 137 CompletionSnippets: make(CompletionSnippets), 138 Definitions: make(Definitions), 139 Highlights: make(Highlights), 140 References: make(References), 141 Renames: make(Renames), 142 Symbols: make(Symbols), 143 symbolsChildren: make(SymbolsChildren), 144 Signatures: make(Signatures), 145 Links: make(Links), 146 147 t: t, 148 dir: dir, 149 fragments: map[string]string{}, 150 golden: map[string]*Golden{}, 151 } 152 153 files := packagestest.MustCopyFileTree(dir) 154 overlays := map[string][]byte{} 155 for fragment, operation := range files { 156 if trimmed := strings.TrimSuffix(fragment, goldenFileSuffix); trimmed != fragment { 157 delete(files, fragment) 158 goldFile := filepath.Join(dir, fragment) 159 archive, err := txtar.ParseFile(goldFile) 160 if err != nil { 161 t.Fatalf("could not read golden file %v: %v", fragment, err) 162 } 163 data.golden[trimmed] = &Golden{ 164 Filename: goldFile, 165 Archive: archive, 166 } 167 } else if trimmed := strings.TrimSuffix(fragment, inFileSuffix); trimmed != fragment { 168 delete(files, fragment) 169 files[trimmed] = operation 170 } else if index := strings.Index(fragment, overlayFileSuffix); index >= 0 { 171 delete(files, fragment) 172 partial := fragment[:index] + fragment[index+len(overlayFileSuffix):] 173 contents, err := ioutil.ReadFile(filepath.Join(dir, fragment)) 174 if err != nil { 175 t.Fatal(err) 176 } 177 overlays[partial] = contents 178 } 179 } 180 modules := []packagestest.Module{ 181 { 182 Name: testModule, 183 Files: files, 184 Overlay: overlays, 185 }, 186 } 187 data.Exported = packagestest.Export(t, exporter, modules) 188 for fragment, _ := range files { 189 filename := data.Exported.File(testModule, fragment) 190 data.fragments[filename] = fragment 191 } 192 193 // Merge the exported.Config with the view.Config. 194 data.Config = *data.Exported.Config 195 data.Config.Fset = token.NewFileSet() 196 data.Config.Context = context.Background() 197 data.Config.ParseFile = func(fset *token.FileSet, filename string, src []byte) (*ast.File, error) { 198 panic("ParseFile should not be called") 199 } 200 201 // Do a first pass to collect special markers for completion. 202 if err := data.Exported.Expect(map[string]interface{}{ 203 "item": func(name string, r packagestest.Range, _, _ string) { 204 data.Exported.Mark(name, r) 205 }, 206 }); err != nil { 207 t.Fatal(err) 208 } 209 210 // Collect any data that needs to be used by subsequent tests. 211 if err := data.Exported.Expect(map[string]interface{}{ 212 "diag": data.collectDiagnostics, 213 "item": data.collectCompletionItems, 214 "complete": data.collectCompletions, 215 "format": data.collectFormats, 216 "import": data.collectImports, 217 "godef": data.collectDefinitions, 218 "typdef": data.collectTypeDefinitions, 219 "hover": data.collectHoverDefinitions, 220 "highlight": data.collectHighlights, 221 "refs": data.collectReferences, 222 "rename": data.collectRenames, 223 "symbol": data.collectSymbols, 224 "signature": data.collectSignatures, 225 "snippet": data.collectCompletionSnippets, 226 "link": data.collectLinks, 227 }); err != nil { 228 t.Fatal(err) 229 } 230 for _, symbols := range data.Symbols { 231 for i := range symbols { 232 children := data.symbolsChildren[symbols[i].Name] 233 symbols[i].Children = children 234 } 235 } 236 // Collect names for the entries that require golden files. 237 if err := data.Exported.Expect(map[string]interface{}{ 238 "godef": data.collectDefinitionNames, 239 "hover": data.collectDefinitionNames, 240 }); err != nil { 241 t.Fatal(err) 242 } 243 return data 244} 245 246func Run(t *testing.T, tests Tests, data *Data) { 247 t.Helper() 248 t.Run("Completion", func(t *testing.T) { 249 t.Helper() 250 if len(data.Completions) != ExpectedCompletionsCount { 251 t.Errorf("got %v completions expected %v", len(data.Completions), ExpectedCompletionsCount) 252 } 253 if len(data.CompletionSnippets) != ExpectedCompletionSnippetCount { 254 t.Errorf("got %v snippets expected %v", len(data.CompletionSnippets), ExpectedCompletionSnippetCount) 255 } 256 tests.Completion(t, data.Completions, data.CompletionSnippets, data.CompletionItems) 257 }) 258 259 t.Run("Diagnostics", func(t *testing.T) { 260 t.Helper() 261 diagnosticsCount := 0 262 for _, want := range data.Diagnostics { 263 diagnosticsCount += len(want) 264 } 265 if diagnosticsCount != ExpectedDiagnosticsCount { 266 t.Errorf("got %v diagnostics expected %v", diagnosticsCount, ExpectedDiagnosticsCount) 267 } 268 tests.Diagnostics(t, data.Diagnostics) 269 }) 270 271 t.Run("Format", func(t *testing.T) { 272 t.Helper() 273 if len(data.Formats) != ExpectedFormatCount { 274 t.Errorf("got %v formats expected %v", len(data.Formats), ExpectedFormatCount) 275 } 276 tests.Format(t, data.Formats) 277 }) 278 279 t.Run("Import", func(t *testing.T) { 280 t.Helper() 281 if len(data.Imports) != ExpectedImportCount { 282 t.Errorf("got %v imports expected %v", len(data.Imports), ExpectedImportCount) 283 } 284 tests.Import(t, data.Imports) 285 }) 286 287 t.Run("Definition", func(t *testing.T) { 288 t.Helper() 289 if len(data.Definitions) != ExpectedDefinitionsCount { 290 t.Errorf("got %v definitions expected %v", len(data.Definitions), ExpectedDefinitionsCount) 291 } 292 tests.Definition(t, data.Definitions) 293 }) 294 295 t.Run("Highlight", func(t *testing.T) { 296 t.Helper() 297 if len(data.Highlights) != ExpectedHighlightsCount { 298 t.Errorf("got %v highlights expected %v", len(data.Highlights), ExpectedHighlightsCount) 299 } 300 tests.Highlight(t, data.Highlights) 301 }) 302 303 t.Run("References", func(t *testing.T) { 304 t.Helper() 305 if len(data.References) != ExpectedReferencesCount { 306 t.Errorf("got %v references expected %v", len(data.References), ExpectedReferencesCount) 307 } 308 tests.Reference(t, data.References) 309 }) 310 311 t.Run("Renames", func(t *testing.T) { 312 t.Helper() 313 if len(data.Renames) != ExpectedRenamesCount { 314 t.Errorf("got %v renames expected %v", len(data.Renames), ExpectedRenamesCount) 315 } 316 tests.Rename(t, data.Renames) 317 }) 318 319 t.Run("Symbols", func(t *testing.T) { 320 t.Helper() 321 if len(data.Symbols) != ExpectedSymbolsCount { 322 t.Errorf("got %v symbols expected %v", len(data.Symbols), ExpectedSymbolsCount) 323 } 324 tests.Symbol(t, data.Symbols) 325 }) 326 327 t.Run("SignatureHelp", func(t *testing.T) { 328 t.Helper() 329 if len(data.Signatures) != ExpectedSignaturesCount { 330 t.Errorf("got %v signatures expected %v", len(data.Signatures), ExpectedSignaturesCount) 331 } 332 tests.SignatureHelp(t, data.Signatures) 333 }) 334 335 t.Run("Link", func(t *testing.T) { 336 t.Helper() 337 linksCount := 0 338 for _, want := range data.Links { 339 linksCount += len(want) 340 } 341 if linksCount != ExpectedLinksCount { 342 t.Errorf("got %v links expected %v", linksCount, ExpectedLinksCount) 343 } 344 tests.Link(t, data.Links) 345 }) 346 347 if *updateGolden { 348 for _, golden := range data.golden { 349 if !golden.Modified { 350 continue 351 } 352 sort.Slice(golden.Archive.Files, func(i, j int) bool { 353 return golden.Archive.Files[i].Name < golden.Archive.Files[j].Name 354 }) 355 if err := ioutil.WriteFile(golden.Filename, txtar.Format(golden.Archive), 0666); err != nil { 356 t.Fatal(err) 357 } 358 } 359 } 360} 361 362func (data *Data) Golden(tag string, target string, update func() ([]byte, error)) []byte { 363 data.t.Helper() 364 fragment, found := data.fragments[target] 365 if !found { 366 if filepath.IsAbs(target) { 367 data.t.Fatalf("invalid golden file fragment %v", target) 368 } 369 fragment = target 370 } 371 golden := data.golden[fragment] 372 if golden == nil { 373 if !*updateGolden { 374 data.t.Fatalf("could not find golden file %v: %v", fragment, tag) 375 } 376 golden = &Golden{ 377 Filename: filepath.Join(data.dir, fragment+goldenFileSuffix), 378 Archive: &txtar.Archive{}, 379 Modified: true, 380 } 381 data.golden[fragment] = golden 382 } 383 var file *txtar.File 384 for i := range golden.Archive.Files { 385 f := &golden.Archive.Files[i] 386 if f.Name == tag { 387 file = f 388 break 389 } 390 } 391 if *updateGolden { 392 if file == nil { 393 golden.Archive.Files = append(golden.Archive.Files, txtar.File{ 394 Name: tag, 395 }) 396 file = &golden.Archive.Files[len(golden.Archive.Files)-1] 397 } 398 contents, err := update() 399 if err != nil { 400 data.t.Fatalf("could not update golden file %v: %v", fragment, err) 401 } 402 file.Data = append(contents, '\n') // add trailing \n for txtar 403 golden.Modified = true 404 } 405 if file == nil { 406 data.t.Fatalf("could not find golden contents %v: %v", fragment, tag) 407 } 408 return file.Data[:len(file.Data)-1] // drop the trailing \n 409} 410 411func (data *Data) collectDiagnostics(spn span.Span, msgSource, msg string) { 412 if _, ok := data.Diagnostics[spn.URI()]; !ok { 413 data.Diagnostics[spn.URI()] = []source.Diagnostic{} 414 } 415 // If a file has an empty diagnostic message, return. This allows us to 416 // avoid testing diagnostics in files that may have a lot of them. 417 if msg == "" { 418 return 419 } 420 severity := source.SeverityError 421 if strings.Contains(string(spn.URI()), "analyzer") { 422 severity = source.SeverityWarning 423 } 424 want := source.Diagnostic{ 425 Span: spn, 426 Severity: severity, 427 Source: msgSource, 428 Message: msg, 429 } 430 data.Diagnostics[spn.URI()] = append(data.Diagnostics[spn.URI()], want) 431} 432 433func (data *Data) collectCompletions(src span.Span, expected []token.Pos) { 434 data.Completions[src] = expected 435} 436 437func (data *Data) collectCompletionItems(pos token.Pos, label, detail, kind string) { 438 data.CompletionItems[pos] = &source.CompletionItem{ 439 Label: label, 440 Detail: detail, 441 Kind: source.ParseCompletionItemKind(kind), 442 } 443} 444 445func (data *Data) collectFormats(spn span.Span) { 446 data.Formats = append(data.Formats, spn) 447} 448 449func (data *Data) collectImports(spn span.Span) { 450 data.Imports = append(data.Imports, spn) 451} 452 453func (data *Data) collectDefinitions(src, target span.Span) { 454 data.Definitions[src] = Definition{ 455 Src: src, 456 Def: target, 457 } 458} 459 460func (data *Data) collectHoverDefinitions(src, target span.Span) { 461 data.Definitions[src] = Definition{ 462 Src: src, 463 Def: target, 464 OnlyHover: true, 465 } 466} 467 468func (data *Data) collectTypeDefinitions(src, target span.Span) { 469 data.Definitions[src] = Definition{ 470 Src: src, 471 Def: target, 472 IsType: true, 473 } 474} 475 476func (data *Data) collectDefinitionNames(src span.Span, name string) { 477 d := data.Definitions[src] 478 d.Name = name 479 data.Definitions[src] = d 480} 481 482func (data *Data) collectHighlights(name string, rng span.Span) { 483 data.Highlights[name] = append(data.Highlights[name], rng) 484} 485 486func (data *Data) collectReferences(src span.Span, expected []span.Span) { 487 data.References[src] = expected 488} 489 490func (data *Data) collectRenames(src span.Span, newText string) { 491 data.Renames[src] = newText 492} 493 494func (data *Data) collectSymbols(name string, spn span.Span, kind string, parentName string) { 495 sym := source.Symbol{ 496 Name: name, 497 Kind: source.ParseSymbolKind(kind), 498 SelectionSpan: spn, 499 } 500 if parentName == "" { 501 data.Symbols[spn.URI()] = append(data.Symbols[spn.URI()], sym) 502 } else { 503 data.symbolsChildren[parentName] = append(data.symbolsChildren[parentName], sym) 504 } 505} 506 507func (data *Data) collectSignatures(spn span.Span, signature string, activeParam int64) { 508 data.Signatures[spn] = source.SignatureInformation{ 509 Label: signature, 510 ActiveParameter: int(activeParam), 511 } 512} 513 514func (data *Data) collectCompletionSnippets(spn span.Span, item token.Pos, plain, placeholder string) { 515 data.CompletionSnippets[spn] = CompletionSnippet{ 516 CompletionItem: item, 517 PlainSnippet: plain, 518 PlaceholderSnippet: placeholder, 519 } 520} 521 522func (data *Data) collectLinks(spn span.Span, link string) { 523 uri := spn.URI() 524 data.Links[uri] = append(data.Links[uri], Link{ 525 Src: spn, 526 Target: link, 527 }) 528} 529