1// Copyright 2011 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	"internal/testenv"
9	"io"
10	"os"
11	"path/filepath"
12	"reflect"
13	"runtime"
14	"strings"
15	"testing"
16)
17
18func TestMatch(t *testing.T) {
19	ctxt := Default
20	what := "default"
21	match := func(tag string, want map[string]bool) {
22		m := make(map[string]bool)
23		if !ctxt.match(tag, m) {
24			t.Errorf("%s context should match %s, does not", what, tag)
25		}
26		if !reflect.DeepEqual(m, want) {
27			t.Errorf("%s tags = %v, want %v", tag, m, want)
28		}
29	}
30	nomatch := func(tag string, want map[string]bool) {
31		m := make(map[string]bool)
32		if ctxt.match(tag, m) {
33			t.Errorf("%s context should NOT match %s, does", what, tag)
34		}
35		if !reflect.DeepEqual(m, want) {
36			t.Errorf("%s tags = %v, want %v", tag, m, want)
37		}
38	}
39
40	match(runtime.GOOS+","+runtime.GOARCH, map[string]bool{runtime.GOOS: true, runtime.GOARCH: true})
41	match(runtime.GOOS+","+runtime.GOARCH+",!foo", map[string]bool{runtime.GOOS: true, runtime.GOARCH: true, "foo": true})
42	nomatch(runtime.GOOS+","+runtime.GOARCH+",foo", map[string]bool{runtime.GOOS: true, runtime.GOARCH: true, "foo": true})
43
44	what = "modified"
45	ctxt.BuildTags = []string{"foo"}
46	match(runtime.GOOS+","+runtime.GOARCH, map[string]bool{runtime.GOOS: true, runtime.GOARCH: true})
47	match(runtime.GOOS+","+runtime.GOARCH+",foo", map[string]bool{runtime.GOOS: true, runtime.GOARCH: true, "foo": true})
48	nomatch(runtime.GOOS+","+runtime.GOARCH+",!foo", map[string]bool{runtime.GOOS: true, runtime.GOARCH: true, "foo": true})
49	match(runtime.GOOS+","+runtime.GOARCH+",!bar", map[string]bool{runtime.GOOS: true, runtime.GOARCH: true, "bar": true})
50	nomatch(runtime.GOOS+","+runtime.GOARCH+",bar", map[string]bool{runtime.GOOS: true, runtime.GOARCH: true, "bar": true})
51	nomatch("!", map[string]bool{})
52}
53
54func TestDotSlashImport(t *testing.T) {
55	p, err := ImportDir("testdata/other", 0)
56	if err != nil {
57		t.Fatal(err)
58	}
59	if len(p.Imports) != 1 || p.Imports[0] != "./file" {
60		t.Fatalf("testdata/other: Imports=%v, want [./file]", p.Imports)
61	}
62
63	p1, err := Import("./file", "testdata/other", 0)
64	if err != nil {
65		t.Fatal(err)
66	}
67	if p1.Name != "file" {
68		t.Fatalf("./file: Name=%q, want %q", p1.Name, "file")
69	}
70	dir := filepath.Clean("testdata/other/file") // Clean to use \ on Windows
71	if p1.Dir != dir {
72		t.Fatalf("./file: Dir=%q, want %q", p1.Name, dir)
73	}
74}
75
76func TestEmptyImport(t *testing.T) {
77	p, err := Import("", Default.GOROOT, FindOnly)
78	if err == nil {
79		t.Fatal(`Import("") returned nil error.`)
80	}
81	if p == nil {
82		t.Fatal(`Import("") returned nil package.`)
83	}
84	if p.ImportPath != "" {
85		t.Fatalf("ImportPath=%q, want %q.", p.ImportPath, "")
86	}
87}
88
89func TestEmptyFolderImport(t *testing.T) {
90	_, err := Import(".", "testdata/empty", 0)
91	if _, ok := err.(*NoGoError); !ok {
92		t.Fatal(`Import("testdata/empty") did not return NoGoError.`)
93	}
94}
95
96func TestMultiplePackageImport(t *testing.T) {
97	_, err := Import(".", "testdata/multi", 0)
98	mpe, ok := err.(*MultiplePackageError)
99	if !ok {
100		t.Fatal(`Import("testdata/multi") did not return MultiplePackageError.`)
101	}
102	want := &MultiplePackageError{
103		Dir:      filepath.FromSlash("testdata/multi"),
104		Packages: []string{"main", "test_package"},
105		Files:    []string{"file.go", "file_appengine.go"},
106	}
107	if !reflect.DeepEqual(mpe, want) {
108		t.Errorf("got %#v; want %#v", mpe, want)
109	}
110}
111
112func TestLocalDirectory(t *testing.T) {
113	t.Skip("does not work with gccgo")
114	if runtime.GOOS == "darwin" {
115		switch runtime.GOARCH {
116		case "arm", "arm64":
117			t.Skipf("skipping on %s/%s, no valid GOROOT", runtime.GOOS, runtime.GOARCH)
118		}
119	}
120
121	cwd, err := os.Getwd()
122	if err != nil {
123		t.Fatal(err)
124	}
125
126	p, err := ImportDir(cwd, 0)
127	if err != nil {
128		t.Fatal(err)
129	}
130	if p.ImportPath != "go/build" {
131		t.Fatalf("ImportPath=%q, want %q", p.ImportPath, "go/build")
132	}
133}
134
135func TestShouldBuild(t *testing.T) {
136	const file1 = "// +build tag1\n\n" +
137		"package main\n"
138	want1 := map[string]bool{"tag1": true}
139
140	const file2 = "// +build cgo\n\n" +
141		"// This package implements parsing of tags like\n" +
142		"// +build tag1\n" +
143		"package build"
144	want2 := map[string]bool{"cgo": true}
145
146	const file3 = "// Copyright The Go Authors.\n\n" +
147		"package build\n\n" +
148		"// shouldBuild checks tags given by lines of the form\n" +
149		"// +build tag\n" +
150		"func shouldBuild(content []byte)\n"
151	want3 := map[string]bool{}
152
153	ctx := &Context{BuildTags: []string{"tag1"}}
154	m := map[string]bool{}
155	if !ctx.shouldBuild([]byte(file1), m, nil) {
156		t.Errorf("shouldBuild(file1) = false, want true")
157	}
158	if !reflect.DeepEqual(m, want1) {
159		t.Errorf("shouldBuild(file1) tags = %v, want %v", m, want1)
160	}
161
162	m = map[string]bool{}
163	if ctx.shouldBuild([]byte(file2), m, nil) {
164		t.Errorf("shouldBuild(file2) = true, want false")
165	}
166	if !reflect.DeepEqual(m, want2) {
167		t.Errorf("shouldBuild(file2) tags = %v, want %v", m, want2)
168	}
169
170	m = map[string]bool{}
171	ctx = &Context{BuildTags: nil}
172	if !ctx.shouldBuild([]byte(file3), m, nil) {
173		t.Errorf("shouldBuild(file3) = false, want true")
174	}
175	if !reflect.DeepEqual(m, want3) {
176		t.Errorf("shouldBuild(file3) tags = %v, want %v", m, want3)
177	}
178}
179
180type readNopCloser struct {
181	io.Reader
182}
183
184func (r readNopCloser) Close() error {
185	return nil
186}
187
188var (
189	ctxtP9      = Context{GOARCH: "arm", GOOS: "plan9"}
190	ctxtAndroid = Context{GOARCH: "arm", GOOS: "android"}
191)
192
193var matchFileTests = []struct {
194	ctxt  Context
195	name  string
196	data  string
197	match bool
198}{
199	{ctxtP9, "foo_arm.go", "", true},
200	{ctxtP9, "foo1_arm.go", "// +build linux\n\npackage main\n", false},
201	{ctxtP9, "foo_darwin.go", "", false},
202	{ctxtP9, "foo.go", "", true},
203	{ctxtP9, "foo1.go", "// +build linux\n\npackage main\n", false},
204	{ctxtP9, "foo.badsuffix", "", false},
205	{ctxtAndroid, "foo_linux.go", "", true},
206	{ctxtAndroid, "foo_android.go", "", true},
207	{ctxtAndroid, "foo_plan9.go", "", false},
208	{ctxtAndroid, "android.go", "", true},
209	{ctxtAndroid, "plan9.go", "", true},
210	{ctxtAndroid, "plan9_test.go", "", true},
211	{ctxtAndroid, "arm.s", "", true},
212	{ctxtAndroid, "amd64.s", "", true},
213}
214
215func TestMatchFile(t *testing.T) {
216	for _, tt := range matchFileTests {
217		ctxt := tt.ctxt
218		ctxt.OpenFile = func(path string) (r io.ReadCloser, err error) {
219			if path != "x+"+tt.name {
220				t.Fatalf("OpenFile asked for %q, expected %q", path, "x+"+tt.name)
221			}
222			return &readNopCloser{strings.NewReader(tt.data)}, nil
223		}
224		ctxt.JoinPath = func(elem ...string) string {
225			return strings.Join(elem, "+")
226		}
227		match, err := ctxt.MatchFile("x", tt.name)
228		if match != tt.match || err != nil {
229			t.Fatalf("MatchFile(%q) = %v, %v, want %v, nil", tt.name, match, err, tt.match)
230		}
231	}
232}
233
234func TestImportCmd(t *testing.T) {
235	t.Skip("does not work with gccgo")
236	if runtime.GOOS == "darwin" {
237		switch runtime.GOARCH {
238		case "arm", "arm64":
239			t.Skipf("skipping on %s/%s, no valid GOROOT", runtime.GOOS, runtime.GOARCH)
240		}
241	}
242
243	p, err := Import("cmd/internal/objfile", "", 0)
244	if err != nil {
245		t.Fatal(err)
246	}
247	if !strings.HasSuffix(filepath.ToSlash(p.Dir), "src/cmd/internal/objfile") {
248		t.Fatalf("Import cmd/internal/objfile returned Dir=%q, want %q", filepath.ToSlash(p.Dir), ".../src/cmd/internal/objfile")
249	}
250}
251
252var (
253	expandSrcDirPath = filepath.Join(string(filepath.Separator)+"projects", "src", "add")
254)
255
256var expandSrcDirTests = []struct {
257	input, expected string
258}{
259	{"-L ${SRCDIR}/libs -ladd", "-L /projects/src/add/libs -ladd"},
260	{"${SRCDIR}/add_linux_386.a -pthread -lstdc++", "/projects/src/add/add_linux_386.a -pthread -lstdc++"},
261	{"Nothing to expand here!", "Nothing to expand here!"},
262	{"$", "$"},
263	{"$$", "$$"},
264	{"${", "${"},
265	{"$}", "$}"},
266	{"$FOO ${BAR}", "$FOO ${BAR}"},
267	{"Find me the $SRCDIRECTORY.", "Find me the $SRCDIRECTORY."},
268	{"$SRCDIR is missing braces", "$SRCDIR is missing braces"},
269}
270
271func TestExpandSrcDir(t *testing.T) {
272	for _, test := range expandSrcDirTests {
273		output, _ := expandSrcDir(test.input, expandSrcDirPath)
274		if output != test.expected {
275			t.Errorf("%q expands to %q with SRCDIR=%q when %q is expected", test.input, output, expandSrcDirPath, test.expected)
276		} else {
277			t.Logf("%q expands to %q with SRCDIR=%q", test.input, output, expandSrcDirPath)
278		}
279	}
280}
281
282func TestShellSafety(t *testing.T) {
283	tests := []struct {
284		input, srcdir, expected string
285		result                  bool
286	}{
287		{"-I${SRCDIR}/../include", "/projects/src/issue 11868", "-I/projects/src/issue 11868/../include", true},
288		{"-I${SRCDIR}", "wtf$@%", "-Iwtf$@%", true},
289		{"-X${SRCDIR}/1,${SRCDIR}/2", "/projects/src/issue 11868", "-X/projects/src/issue 11868/1,/projects/src/issue 11868/2", true},
290		{"-I/tmp -I/tmp", "/tmp2", "-I/tmp -I/tmp", true},
291		{"-I/tmp", "/tmp/[0]", "-I/tmp", true},
292		{"-I${SRCDIR}/dir", "/tmp/[0]", "-I/tmp/[0]/dir", false},
293		{"-I${SRCDIR}/dir", "/tmp/go go", "-I/tmp/go go/dir", true},
294		{"-I${SRCDIR}/dir dir", "/tmp/go", "-I/tmp/go/dir dir", true},
295	}
296	for _, test := range tests {
297		output, ok := expandSrcDir(test.input, test.srcdir)
298		if ok != test.result {
299			t.Errorf("Expected %t while %q expands to %q with SRCDIR=%q; got %t", test.result, test.input, output, test.srcdir, ok)
300		}
301		if output != test.expected {
302			t.Errorf("Expected %q while %q expands with SRCDIR=%q; got %q", test.expected, test.input, test.srcdir, output)
303		}
304	}
305}
306
307// Want to get a "cannot find package" error when directory for package does not exist.
308// There should be valid partial information in the returned non-nil *Package.
309func TestImportDirNotExist(t *testing.T) {
310	testenv.MustHaveGoBuild(t) // really must just have source
311	ctxt := Default
312	ctxt.GOPATH = ""
313
314	tests := []struct {
315		label        string
316		path, srcDir string
317		mode         ImportMode
318	}{
319		{"Import(full, 0)", "go/build/doesnotexist", "", 0},
320		{"Import(local, 0)", "./doesnotexist", filepath.Join(ctxt.GOROOT, "src/go/build"), 0},
321		{"Import(full, FindOnly)", "go/build/doesnotexist", "", FindOnly},
322		{"Import(local, FindOnly)", "./doesnotexist", filepath.Join(ctxt.GOROOT, "src/go/build"), FindOnly},
323	}
324	for _, test := range tests {
325		p, err := ctxt.Import(test.path, test.srcDir, test.mode)
326		if err == nil || !strings.HasPrefix(err.Error(), "cannot find package") {
327			t.Errorf(`%s got error: %q, want "cannot find package" error`, test.label, err)
328		}
329		// If an error occurs, build.Import is documented to return
330		// a non-nil *Package containing partial information.
331		if p == nil {
332			t.Fatalf(`%s got nil p, want non-nil *Package`, test.label)
333		}
334		// Verify partial information in p.
335		if p.ImportPath != "go/build/doesnotexist" {
336			t.Errorf(`%s got p.ImportPath: %q, want "go/build/doesnotexist"`, test.label, p.ImportPath)
337		}
338	}
339}
340
341func TestImportVendor(t *testing.T) {
342	testenv.MustHaveGoBuild(t) // really must just have source
343	ctxt := Default
344	ctxt.GOPATH = ""
345	p, err := ctxt.Import("golang_org/x/net/http2/hpack", filepath.Join(ctxt.GOROOT, "src/net/http"), 0)
346	if err != nil {
347		t.Fatalf("cannot find vendored golang_org/x/net/http2/hpack from net/http directory: %v", err)
348	}
349	want := "vendor/golang_org/x/net/http2/hpack"
350	if p.ImportPath != want {
351		t.Fatalf("Import succeeded but found %q, want %q", p.ImportPath, want)
352	}
353}
354
355func TestImportVendorFailure(t *testing.T) {
356	testenv.MustHaveGoBuild(t) // really must just have source
357	ctxt := Default
358	ctxt.GOPATH = ""
359	p, err := ctxt.Import("x.com/y/z", filepath.Join(ctxt.GOROOT, "src/net/http"), 0)
360	if err == nil {
361		t.Fatalf("found made-up package x.com/y/z in %s", p.Dir)
362	}
363
364	e := err.Error()
365	if !strings.Contains(e, " (vendor tree)") {
366		t.Fatalf("error on failed import does not mention GOROOT/src/vendor directory:\n%s", e)
367	}
368}
369
370func TestImportVendorParentFailure(t *testing.T) {
371	testenv.MustHaveGoBuild(t) // really must just have source
372	ctxt := Default
373	ctxt.GOPATH = ""
374	// This import should fail because the vendor/golang.org/x/net/http2 directory has no source code.
375	p, err := ctxt.Import("golang_org/x/net/http2", filepath.Join(ctxt.GOROOT, "src/net/http"), 0)
376	if err == nil {
377		t.Fatalf("found empty parent in %s", p.Dir)
378	}
379	if p != nil && p.Dir != "" {
380		t.Fatalf("decided to use %s", p.Dir)
381	}
382	e := err.Error()
383	if !strings.Contains(e, " (vendor tree)") {
384		t.Fatalf("error on failed import does not mention GOROOT/src/vendor directory:\n%s", e)
385	}
386}
387
388func TestImportDirTarget(t *testing.T) {
389	testenv.MustHaveGoBuild(t) // really must just have source
390	ctxt := Default
391	ctxt.GOPATH = ""
392	p, err := ctxt.ImportDir(filepath.Join(ctxt.GOROOT, "src/path"), 0)
393	if err != nil {
394		t.Fatal(err)
395	}
396	if p.PkgTargetRoot == "" || p.PkgObj == "" {
397		t.Errorf("p.PkgTargetRoot == %q, p.PkgObj == %q, want non-empty", p.PkgTargetRoot, p.PkgObj)
398	}
399}
400