1// Copyright 2012 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 build
6
7import (
8	"fmt"
9	"go/token"
10	"io"
11	"strings"
12	"testing"
13)
14
15const quote = "`"
16
17type readTest struct {
18	// Test input contains ℙ where readGoInfo should stop.
19	in  string
20	err string
21}
22
23var readGoInfoTests = []readTest{
24	{
25		`package p`,
26		"",
27	},
28	{
29		`package p; import "x"`,
30		"",
31	},
32	{
33		`package p; import . "x"`,
34		"",
35	},
36	{
37		`package p; import "x";ℙvar x = 1`,
38		"",
39	},
40	{
41		`package p
42
43		// comment
44
45		import "x"
46		import _ "x"
47		import a "x"
48
49		/* comment */
50
51		import (
52			"x" /* comment */
53			_ "x"
54			a "x" // comment
55			` + quote + `x` + quote + `
56			_ /*comment*/ ` + quote + `x` + quote + `
57			a ` + quote + `x` + quote + `
58		)
59		import (
60		)
61		import ()
62		import()import()import()
63		import();import();import()
64
65var x = 1
66		`,
67		"",
68	},
69	{
70		"\ufeff��" + `package p; import "x";ℙvar x = 1`,
71		"",
72	},
73}
74
75var readCommentsTests = []readTest{
76	{
77		`ℙpackage p`,
78		"",
79	},
80	{
81		`ℙpackage p; import "x"`,
82		"",
83	},
84	{
85		`ℙpackage p; import . "x"`,
86		"",
87	},
88	{
89		"\ufeff��" + `ℙpackage p; import . "x"`,
90		"",
91	},
92	{
93		`// foo
94
95		/* bar */
96
97		/* quux */ // baz
98
99		/*/ zot */
100
101		// asdf
102Hello, world`,
103		"",
104	},
105	{
106		"\ufeff��" + `// foo
107
108		/* bar */
109
110		/* quux */ // baz
111
112		/*/ zot */
113
114		// asdf
115Hello, world`,
116		"",
117	},
118}
119
120func testRead(t *testing.T, tests []readTest, read func(io.Reader) ([]byte, error)) {
121	for i, tt := range tests {
122		var in, testOut string
123		j := strings.Index(tt.in, "ℙ")
124		if j < 0 {
125			in = tt.in
126			testOut = tt.in
127		} else {
128			in = tt.in[:j] + tt.in[j+len("ℙ"):]
129			testOut = tt.in[:j]
130		}
131		d := strings.Index(tt.in, "��")
132		if d >= 0 {
133			in = in[:d] + in[d+len("��"):]
134			testOut = testOut[d+len("��"):]
135		}
136		r := strings.NewReader(in)
137		buf, err := read(r)
138		if err != nil {
139			if tt.err == "" {
140				t.Errorf("#%d: err=%q, expected success (%q)", i, err, string(buf))
141			} else if !strings.Contains(err.Error(), tt.err) {
142				t.Errorf("#%d: err=%q, expected %q", i, err, tt.err)
143			}
144			continue
145		}
146		if tt.err != "" {
147			t.Errorf("#%d: success, expected %q", i, tt.err)
148			continue
149		}
150
151		out := string(buf)
152		if out != testOut {
153			t.Errorf("#%d: wrong output:\nhave %q\nwant %q\n", i, out, testOut)
154		}
155	}
156}
157
158func TestReadGoInfo(t *testing.T) {
159	testRead(t, readGoInfoTests, func(r io.Reader) ([]byte, error) {
160		var info fileInfo
161		err := readGoInfo(r, &info)
162		return info.header, err
163	})
164}
165
166func TestReadComments(t *testing.T) {
167	testRead(t, readCommentsTests, readComments)
168}
169
170var readFailuresTests = []readTest{
171	{
172		`package`,
173		"syntax error",
174	},
175	{
176		"package p\n\x00\nimport `math`\n",
177		"unexpected NUL in input",
178	},
179	{
180		`package p; import`,
181		"syntax error",
182	},
183	{
184		`package p; import "`,
185		"syntax error",
186	},
187	{
188		"package p; import ` \n\n",
189		"syntax error",
190	},
191	{
192		`package p; import "x`,
193		"syntax error",
194	},
195	{
196		`package p; import _`,
197		"syntax error",
198	},
199	{
200		`package p; import _ "`,
201		"syntax error",
202	},
203	{
204		`package p; import _ "x`,
205		"syntax error",
206	},
207	{
208		`package p; import .`,
209		"syntax error",
210	},
211	{
212		`package p; import . "`,
213		"syntax error",
214	},
215	{
216		`package p; import . "x`,
217		"syntax error",
218	},
219	{
220		`package p; import (`,
221		"syntax error",
222	},
223	{
224		`package p; import ("`,
225		"syntax error",
226	},
227	{
228		`package p; import ("x`,
229		"syntax error",
230	},
231	{
232		`package p; import ("x"`,
233		"syntax error",
234	},
235}
236
237func TestReadFailuresIgnored(t *testing.T) {
238	// Syntax errors should not be reported (false arg to readImports).
239	// Instead, entire file should be the output and no error.
240	// Convert tests not to return syntax errors.
241	tests := make([]readTest, len(readFailuresTests))
242	copy(tests, readFailuresTests)
243	for i := range tests {
244		tt := &tests[i]
245		if !strings.Contains(tt.err, "NUL") {
246			tt.err = ""
247		}
248	}
249	testRead(t, tests, func(r io.Reader) ([]byte, error) {
250		var info fileInfo
251		err := readGoInfo(r, &info)
252		return info.header, err
253	})
254}
255
256var readEmbedTests = []struct {
257	in, out string
258}{
259	{
260		"package p\n",
261		"",
262	},
263	{
264		"package p\nimport \"embed\"\nvar i int\n//go:embed x y z\nvar files embed.FS",
265		`test:4:12:x
266		 test:4:14:y
267		 test:4:16:z`,
268	},
269	{
270		"package p\nimport \"embed\"\nvar i int\n//go:embed x \"\\x79\" `z`\nvar files embed.FS",
271		`test:4:12:x
272		 test:4:14:y
273		 test:4:21:z`,
274	},
275	{
276		"package p\nimport \"embed\"\nvar i int\n//go:embed x y\n//go:embed z\nvar files embed.FS",
277		`test:4:12:x
278		 test:4:14:y
279		 test:5:12:z`,
280	},
281	{
282		"package p\nimport \"embed\"\nvar i int\n\t //go:embed x y\n\t //go:embed z\n\t var files embed.FS",
283		`test:4:14:x
284		 test:4:16:y
285		 test:5:14:z`,
286	},
287	{
288		"package p\nimport \"embed\"\n//go:embed x y z\nvar files embed.FS",
289		`test:3:12:x
290		 test:3:14:y
291		 test:3:16:z`,
292	},
293	{
294		"\ufeffpackage p\nimport \"embed\"\n//go:embed x y z\nvar files embed.FS",
295		`test:3:12:x
296		 test:3:14:y
297		 test:3:16:z`,
298	},
299	{
300		"package p\nimport \"embed\"\nvar s = \"/*\"\n//go:embed x\nvar files embed.FS",
301		`test:4:12:x`,
302	},
303	{
304		`package p
305		 import "embed"
306		 var s = "\"\\\\"
307		 //go:embed x
308		 var files embed.FS`,
309		`test:4:15:x`,
310	},
311	{
312		"package p\nimport \"embed\"\nvar s = `/*`\n//go:embed x\nvar files embed.FS",
313		`test:4:12:x`,
314	},
315	{
316		"package p\nimport \"embed\"\nvar s = z/ *y\n//go:embed pointer\nvar pointer embed.FS",
317		"test:4:12:pointer",
318	},
319	{
320		"package p\n//go:embed x y z\n", // no import, no scan
321		"",
322	},
323	{
324		"package p\n//go:embed x y z\nvar files embed.FS", // no import, no scan
325		"",
326	},
327	{
328		"\ufeffpackage p\n//go:embed x y z\nvar files embed.FS", // no import, no scan
329		"",
330	},
331}
332
333func TestReadEmbed(t *testing.T) {
334	fset := token.NewFileSet()
335	for i, tt := range readEmbedTests {
336		info := fileInfo{
337			name: "test",
338			fset: fset,
339		}
340		err := readGoInfo(strings.NewReader(tt.in), &info)
341		if err != nil {
342			t.Errorf("#%d: %v", i, err)
343			continue
344		}
345		b := &strings.Builder{}
346		sep := ""
347		for _, emb := range info.embeds {
348			fmt.Fprintf(b, "%s%v:%s", sep, emb.pos, emb.pattern)
349			sep = "\n"
350		}
351		got := b.String()
352		want := strings.Join(strings.Fields(tt.out), "\n")
353		if got != want {
354			t.Errorf("#%d: embeds:\n%s\nwant:\n%s", i, got, want)
355		}
356	}
357}
358