1// Copyright 2013 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 godoc
6
7import (
8	"bytes"
9	"reflect"
10	"sort"
11	"strings"
12	"testing"
13
14	"golang.org/x/tools/godoc/vfs/mapfs"
15)
16
17func newCorpus(t *testing.T) *Corpus {
18	c := NewCorpus(mapfs.New(map[string]string{
19		"src/foo/foo.go": `// Package foo is an example.
20package foo
21
22import "bar"
23
24const Pi = 3.1415
25
26var Foos []Foo
27
28// Foo is stuff.
29type Foo struct{}
30
31func New() *Foo {
32   return new(Foo)
33}
34`,
35		"src/bar/bar.go": `// Package bar is another example to test races.
36package bar
37`,
38		"src/other/bar/bar.go": `// Package bar is another bar package.
39package bar
40func X() {}
41`,
42		"src/skip/skip.go": `// Package skip should be skipped.
43package skip
44func Skip() {}
45`,
46		"src/bar/readme.txt": `Whitelisted text file.
47`,
48		"src/bar/baz.zzz": `Text file not whitelisted.
49`,
50	}))
51	c.IndexEnabled = true
52	c.IndexDirectory = func(dir string) bool {
53		return !strings.Contains(dir, "skip")
54	}
55
56	if err := c.Init(); err != nil {
57		t.Fatal(err)
58	}
59	return c
60}
61
62func TestIndex(t *testing.T) {
63	for _, docs := range []bool{true, false} {
64		for _, goCode := range []bool{true, false} {
65			for _, fullText := range []bool{true, false} {
66				c := newCorpus(t)
67				c.IndexDocs = docs
68				c.IndexGoCode = goCode
69				c.IndexFullText = fullText
70				c.UpdateIndex()
71				ix, _ := c.CurrentIndex()
72				if ix == nil {
73					t.Fatal("no index")
74				}
75				t.Logf("docs, goCode, fullText = %v,%v,%v", docs, goCode, fullText)
76				testIndex(t, c, ix)
77			}
78		}
79	}
80}
81
82func TestIndexWriteRead(t *testing.T) {
83	type key struct {
84		docs, goCode, fullText bool
85	}
86	type val struct {
87		buf *bytes.Buffer
88		c   *Corpus
89	}
90	m := map[key]val{}
91
92	for _, docs := range []bool{true, false} {
93		for _, goCode := range []bool{true, false} {
94			for _, fullText := range []bool{true, false} {
95				k := key{docs, goCode, fullText}
96				c := newCorpus(t)
97				c.IndexDocs = docs
98				c.IndexGoCode = goCode
99				c.IndexFullText = fullText
100				c.UpdateIndex()
101				ix, _ := c.CurrentIndex()
102				if ix == nil {
103					t.Fatal("no index")
104				}
105				var buf bytes.Buffer
106				nw, err := ix.WriteTo(&buf)
107				if err != nil {
108					t.Fatalf("Index.WriteTo: %v", err)
109				}
110				m[k] = val{bytes.NewBuffer(buf.Bytes()), c}
111				ix2 := new(Index)
112				nr, err := ix2.ReadFrom(&buf)
113				if err != nil {
114					t.Fatalf("Index.ReadFrom: %v", err)
115				}
116				if nr != nw {
117					t.Errorf("Wrote %d bytes to index but read %d", nw, nr)
118				}
119				testIndex(t, c, ix)
120			}
121		}
122	}
123	// Test CompatibleWith
124	for k1, v1 := range m {
125		ix := new(Index)
126		if _, err := ix.ReadFrom(v1.buf); err != nil {
127			t.Fatalf("Index.ReadFrom: %v", err)
128		}
129		for k2, v2 := range m {
130			if got, want := ix.CompatibleWith(v2.c), k1 == k2; got != want {
131				t.Errorf("CompatibleWith = %v; want %v for %v, %v", got, want, k1, k2)
132			}
133		}
134	}
135}
136
137func testIndex(t *testing.T, c *Corpus, ix *Index) {
138	if _, ok := ix.words["Skip"]; ok {
139		t.Errorf("the word Skip was found; expected it to be skipped")
140	}
141	checkStats(t, c, ix)
142	checkImportCount(t, c, ix)
143	checkPackagePath(t, c, ix)
144	checkExports(t, c, ix)
145	checkIdents(t, c, ix)
146}
147
148// checkStats checks the Index's statistics.
149// Some statistics are only set when we're indexing Go code.
150func checkStats(t *testing.T, c *Corpus, ix *Index) {
151	want := Statistics{}
152	if c.IndexFullText {
153		want.Bytes = 314
154		want.Files = 4
155		want.Lines = 21
156	} else if c.IndexDocs || c.IndexGoCode {
157		want.Bytes = 291
158		want.Files = 3
159		want.Lines = 20
160	}
161	if c.IndexGoCode {
162		want.Words = 8
163		want.Spots = 12
164	}
165	if got := ix.Stats(); !reflect.DeepEqual(got, want) {
166		t.Errorf("Stats = %#v; want %#v", got, want)
167	}
168}
169
170// checkImportCount checks the Index's import count map.
171// It is only set when we're indexing Go code.
172func checkImportCount(t *testing.T, c *Corpus, ix *Index) {
173	want := map[string]int{}
174	if c.IndexGoCode {
175		want = map[string]int{
176			"bar": 1,
177		}
178	}
179	if got := ix.ImportCount(); !reflect.DeepEqual(got, want) {
180		t.Errorf("ImportCount = %v; want %v", got, want)
181	}
182}
183
184// checkPackagePath checks the Index's package path map.
185// It is set if at least one of the indexing options is enabled.
186func checkPackagePath(t *testing.T, c *Corpus, ix *Index) {
187	want := map[string]map[string]bool{}
188	if c.IndexDocs || c.IndexGoCode || c.IndexFullText {
189		want = map[string]map[string]bool{
190			"foo": {
191				"foo": true,
192			},
193			"bar": {
194				"bar":       true,
195				"other/bar": true,
196			},
197		}
198	}
199	if got := ix.PackagePath(); !reflect.DeepEqual(got, want) {
200		t.Errorf("PackagePath = %v; want %v", got, want)
201	}
202}
203
204// checkExports checks the Index's exports map.
205// It is only set when we're indexing Go code.
206func checkExports(t *testing.T, c *Corpus, ix *Index) {
207	want := map[string]map[string]SpotKind{}
208	if c.IndexGoCode {
209		want = map[string]map[string]SpotKind{
210			"foo": {
211				"Pi":   ConstDecl,
212				"Foos": VarDecl,
213				"Foo":  TypeDecl,
214				"New":  FuncDecl,
215			},
216			"other/bar": {
217				"X": FuncDecl,
218			},
219		}
220	}
221	if got := ix.Exports(); !reflect.DeepEqual(got, want) {
222		t.Errorf("Exports = %v; want %v", got, want)
223	}
224}
225
226// checkIdents checks the Index's indents map.
227// It is only set when we're indexing documentation.
228func checkIdents(t *testing.T, c *Corpus, ix *Index) {
229	want := map[SpotKind]map[string][]Ident{}
230	if c.IndexDocs {
231		want = map[SpotKind]map[string][]Ident{
232			PackageClause: {
233				"bar": {
234					{"bar", "bar", "bar", "Package bar is another example to test races."},
235					{"other/bar", "bar", "bar", "Package bar is another bar package."},
236				},
237				"foo":   {{"foo", "foo", "foo", "Package foo is an example."}},
238				"other": {{"other/bar", "bar", "bar", "Package bar is another bar package."}},
239			},
240			ConstDecl: {
241				"Pi": {{"foo", "foo", "Pi", ""}},
242			},
243			VarDecl: {
244				"Foos": {{"foo", "foo", "Foos", ""}},
245			},
246			TypeDecl: {
247				"Foo": {{"foo", "foo", "Foo", "Foo is stuff."}},
248			},
249			FuncDecl: {
250				"New": {{"foo", "foo", "New", ""}},
251				"X":   {{"other/bar", "bar", "X", ""}},
252			},
253		}
254	}
255	if got := ix.Idents(); !reflect.DeepEqual(got, want) {
256		t.Errorf("Idents = %v; want %v", got, want)
257	}
258}
259
260func TestIdentResultSort(t *testing.T) {
261	ic := map[string]int{
262		"/a/b/pkg1": 10,
263		"/a/b/pkg2": 2,
264		"/b/d/pkg3": 20,
265	}
266	for _, tc := range []struct {
267		ir  []Ident
268		exp []Ident
269	}{
270		{
271			ir: []Ident{
272				{"/a/b/pkg2", "pkg2", "MyFunc2", ""},
273				{"/b/d/pkg3", "pkg3", "MyFunc3", ""},
274				{"/a/b/pkg1", "pkg1", "MyFunc1", ""},
275			},
276			exp: []Ident{
277				{"/b/d/pkg3", "pkg3", "MyFunc3", ""},
278				{"/a/b/pkg1", "pkg1", "MyFunc1", ""},
279				{"/a/b/pkg2", "pkg2", "MyFunc2", ""},
280			},
281		},
282		{
283			ir: []Ident{
284				{"/a/a/pkg1", "pkg1", "MyFunc1", ""},
285				{"/a/b/pkg1", "pkg1", "MyFunc1", ""},
286			},
287			exp: []Ident{
288				{"/a/b/pkg1", "pkg1", "MyFunc1", ""},
289				{"/a/a/pkg1", "pkg1", "MyFunc1", ""},
290			},
291		},
292	} {
293		if sort.Sort(byImportCount{tc.ir, ic}); !reflect.DeepEqual(tc.ir, tc.exp) {
294			t.Errorf("got: %v, want %v", tc.ir, tc.exp)
295		}
296	}
297}
298
299func TestIdentFilter(t *testing.T) {
300	ic := map[string]int{}
301	for _, tc := range []struct {
302		ir  []Ident
303		pak string
304		exp []Ident
305	}{
306		{
307			ir: []Ident{
308				{"/a/b/pkg2", "pkg2", "MyFunc2", ""},
309				{"/b/d/pkg3", "pkg3", "MyFunc3", ""},
310				{"/a/b/pkg1", "pkg1", "MyFunc1", ""},
311			},
312			pak: "pkg2",
313			exp: []Ident{
314				{"/a/b/pkg2", "pkg2", "MyFunc2", ""},
315			},
316		},
317	} {
318		res := byImportCount{tc.ir, ic}.filter(tc.pak)
319		if !reflect.DeepEqual(res, tc.exp) {
320			t.Errorf("got: %v, want %v", res, tc.exp)
321		}
322	}
323}
324