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