1package mage
2
3import (
4	"bytes"
5	"debug/macho"
6	"debug/pe"
7	"flag"
8	"fmt"
9	"go/build"
10	"go/parser"
11	"go/token"
12	"io"
13	"io/ioutil"
14	"log"
15	"os"
16	"os/exec"
17	"path/filepath"
18	"reflect"
19	"regexp"
20	"runtime"
21	"strconv"
22	"strings"
23	"testing"
24	"time"
25
26	"github.com/magefile/mage/internal"
27	"github.com/magefile/mage/mg"
28)
29
30const testExeEnv = "MAGE_TEST_STRING"
31
32func TestMain(m *testing.M) {
33	if s := os.Getenv(testExeEnv); s != "" {
34		fmt.Fprint(os.Stdout, s)
35		os.Exit(0)
36	}
37	os.Exit(testmain(m))
38}
39
40func testmain(m *testing.M) int {
41	// ensure we write our temporary binaries to a directory that we'll delete
42	// after running tests.
43	dir, err := ioutil.TempDir("", "")
44	if err != nil {
45		log.Fatal(err)
46	}
47	defer os.RemoveAll(dir)
48	if err := os.Setenv(mg.CacheEnv, dir); err != nil {
49		log.Fatal(err)
50	}
51	if err := os.Unsetenv(mg.VerboseEnv); err != nil {
52		log.Fatal(err)
53	}
54	if err := os.Unsetenv(mg.DebugEnv); err != nil {
55		log.Fatal(err)
56	}
57	if err := os.Unsetenv(mg.IgnoreDefaultEnv); err != nil {
58		log.Fatal(err)
59	}
60	if err := os.Setenv(mg.CacheEnv, dir); err != nil {
61		log.Fatal(err)
62	}
63	if err := os.Unsetenv(mg.EnableColorEnv); err != nil {
64		log.Fatal(err)
65	}
66	if err := os.Unsetenv(mg.TargetColorEnv); err != nil {
67		log.Fatal(err)
68	}
69	resetTerm()
70	return m.Run()
71}
72
73func resetTerm() {
74	if term, exists := os.LookupEnv("TERM"); exists {
75		log.Printf("Current terminal: %s", term)
76		// unset TERM env var in order to disable color output to make the tests simpler
77		// there is a specific test for colorized output, so all the other tests can use non-colorized one
78		if err := os.Unsetenv("TERM"); err != nil {
79			log.Fatal(err)
80		}
81	}
82	os.Setenv(mg.EnableColorEnv, "false")
83}
84
85func TestTransitiveDepCache(t *testing.T) {
86	cache, err := internal.OutputDebug("go", "env", "GOCACHE")
87	if err != nil {
88		t.Fatal(err)
89	}
90	if cache == "" {
91		t.Skip("skipping gocache tests on go version without cache")
92	}
93	// Test that if we change a transitive dep, that we recompile
94	stdout := &bytes.Buffer{}
95	stderr := &bytes.Buffer{}
96	inv := Invocation{
97		Stderr: stderr,
98		Stdout: stdout,
99		Dir:    "testdata/transitiveDeps",
100		Args:   []string{"Run"},
101	}
102	code := Invoke(inv)
103	if code != 0 {
104		t.Fatalf("got code %v, err: %s", code, stderr)
105	}
106	expected := "woof\n"
107	if actual := stdout.String(); actual != expected {
108		t.Fatalf("expected %q but got %q", expected, actual)
109	}
110	// ok, so baseline, the generated and cached binary should do "woof"
111	// now change out the transitive dependency that does the output
112	// so that it produces different output.
113	if err := os.Rename("testdata/transitiveDeps/dep/dog.go", "testdata/transitiveDeps/dep/dog.notgo"); err != nil {
114		t.Fatal(err)
115	}
116	defer os.Rename("testdata/transitiveDeps/dep/dog.notgo", "testdata/transitiveDeps/dep/dog.go")
117	if err := os.Rename("testdata/transitiveDeps/dep/cat.notgo", "testdata/transitiveDeps/dep/cat.go"); err != nil {
118		t.Fatal(err)
119	}
120	defer os.Rename("testdata/transitiveDeps/dep/cat.go", "testdata/transitiveDeps/dep/cat.notgo")
121	stderr.Reset()
122	stdout.Reset()
123	code = Invoke(inv)
124	if code != 0 {
125		t.Fatalf("got code %v, err: %s", code, stderr)
126	}
127	expected = "meow\n"
128	if actual := stdout.String(); actual != expected {
129		t.Fatalf("expected %q but got %q", expected, actual)
130	}
131}
132
133func TestTransitiveHashFast(t *testing.T) {
134	cache, err := internal.OutputDebug("go", "env", "GOCACHE")
135	if err != nil {
136		t.Fatal(err)
137	}
138	if cache == "" {
139		t.Skip("skipping hashfast tests on go version without cache")
140	}
141
142	// Test that if we change a transitive dep, that we don't recompile.
143	// We intentionally run the first time without hashfast to ensure that
144	// we recompile the binary with the current code.
145	stdout := &bytes.Buffer{}
146	stderr := &bytes.Buffer{}
147	inv := Invocation{
148		Stderr: stderr,
149		Stdout: stdout,
150		Dir:    "testdata/transitiveDeps",
151		Args:   []string{"Run"},
152	}
153	code := Invoke(inv)
154	if code != 0 {
155		t.Fatalf("got code %v, err: %s", code, stderr)
156	}
157	expected := "woof\n"
158	if actual := stdout.String(); actual != expected {
159		t.Fatalf("expected %q but got %q", expected, actual)
160	}
161
162	// ok, so baseline, the generated and cached binary should do "woof"
163	// now change out the transitive dependency that does the output
164	// so that it produces different output.
165	if err := os.Rename("testdata/transitiveDeps/dep/dog.go", "testdata/transitiveDeps/dep/dog.notgo"); err != nil {
166		t.Fatal(err)
167	}
168	defer os.Rename("testdata/transitiveDeps/dep/dog.notgo", "testdata/transitiveDeps/dep/dog.go")
169	if err := os.Rename("testdata/transitiveDeps/dep/cat.notgo", "testdata/transitiveDeps/dep/cat.go"); err != nil {
170		t.Fatal(err)
171	}
172	defer os.Rename("testdata/transitiveDeps/dep/cat.go", "testdata/transitiveDeps/dep/cat.notgo")
173	stderr.Reset()
174	stdout.Reset()
175	inv.HashFast = true
176	code = Invoke(inv)
177	if code != 0 {
178		t.Fatalf("got code %v, err: %s", code, stderr)
179	}
180	// we should still get woof, even though the dependency was changed to
181	// return "meow", because we're only hashing the top level magefiles, not
182	// dependencies.
183	if actual := stdout.String(); actual != expected {
184		t.Fatalf("expected %q but got %q", expected, actual)
185	}
186}
187
188func TestListMagefilesMain(t *testing.T) {
189	buf := &bytes.Buffer{}
190	files, err := Magefiles("testdata/mixed_main_files", "", "", "go", buf, false)
191	if err != nil {
192		t.Errorf("error from magefile list: %v: %s", err, buf)
193	}
194	expected := []string{
195		filepath.FromSlash("testdata/mixed_main_files/mage_helpers.go"),
196		filepath.FromSlash("testdata/mixed_main_files/magefile.go"),
197	}
198	if !reflect.DeepEqual(files, expected) {
199		t.Fatalf("expected %q but got %q", expected, files)
200	}
201}
202
203func TestListMagefilesIgnoresGOOS(t *testing.T) {
204	buf := &bytes.Buffer{}
205	if runtime.GOOS == "windows" {
206		os.Setenv("GOOS", "linux")
207	} else {
208		os.Setenv("GOOS", "windows")
209	}
210	defer os.Setenv("GOOS", runtime.GOOS)
211	files, err := Magefiles("testdata/goos_magefiles", "", "", "go", buf, false)
212	if err != nil {
213		t.Errorf("error from magefile list: %v: %s", err, buf)
214	}
215	var expected []string
216	if runtime.GOOS == "windows" {
217		expected = []string{filepath.FromSlash("testdata/goos_magefiles/magefile_windows.go")}
218	} else {
219		expected = []string{filepath.FromSlash("testdata/goos_magefiles/magefile_nonwindows.go")}
220	}
221	if !reflect.DeepEqual(files, expected) {
222		t.Fatalf("expected %q but got %q", expected, files)
223	}
224}
225
226func TestListMagefilesIgnoresRespectsGOOSArg(t *testing.T) {
227	buf := &bytes.Buffer{}
228	var goos string
229	if runtime.GOOS == "windows" {
230		goos = "linux"
231	} else {
232		goos = "windows"
233	}
234	// Set GOARCH as amd64 because windows is not on all non-x86 architectures.
235	files, err := Magefiles("testdata/goos_magefiles", goos, "amd64", "go", buf, false)
236	if err != nil {
237		t.Errorf("error from magefile list: %v: %s", err, buf)
238	}
239	var expected []string
240	if goos == "windows" {
241		expected = []string{filepath.FromSlash("testdata/goos_magefiles/magefile_windows.go")}
242	} else {
243		expected = []string{filepath.FromSlash("testdata/goos_magefiles/magefile_nonwindows.go")}
244	}
245	if !reflect.DeepEqual(files, expected) {
246		t.Fatalf("expected %q but got %q", expected, files)
247	}
248}
249
250func TestCompileDiffGoosGoarch(t *testing.T) {
251	target, err := ioutil.TempDir("./testdata", "")
252	if err != nil {
253		t.Fatal(err)
254	}
255	defer os.RemoveAll(target)
256
257	// intentionally choose an arch and os to build that are not our current one.
258
259	goos := "windows"
260	if runtime.GOOS == "windows" {
261		goos = "darwin"
262	}
263	goarch := "amd64"
264	if runtime.GOARCH == "amd64" {
265		goarch = "386"
266	}
267	stdout := &bytes.Buffer{}
268	stderr := &bytes.Buffer{}
269	inv := Invocation{
270		Stderr: stderr,
271		Stdout: stdout,
272		Debug:  true,
273		Dir:    "testdata",
274		// this is relative to the Dir above
275		CompileOut: filepath.Join(".", filepath.Base(target), "output"),
276		GOOS:       goos,
277		GOARCH:     goarch,
278	}
279	code := Invoke(inv)
280	if code != 0 {
281		t.Fatalf("got code %v, err: %s", code, stderr)
282	}
283	os, arch, err := fileData(filepath.Join(target, "output"))
284	if err != nil {
285		t.Fatal(err)
286	}
287	if goos == "windows" {
288		if os != winExe {
289			t.Error("ran with GOOS=windows but did not produce a windows exe")
290		}
291	} else {
292		if os != macExe {
293			t.Error("ran with GOOS=darwin but did not a mac exe")
294		}
295	}
296	if goarch == "amd64" {
297		if arch != arch64 {
298			t.Error("ran with GOARCH=amd64 but did not produce a 64 bit exe")
299		}
300	} else {
301		if arch != arch32 {
302			t.Error("rand with GOARCH=386 but did not produce a 32 bit exe")
303		}
304	}
305}
306
307func TestListMagefilesLib(t *testing.T) {
308	buf := &bytes.Buffer{}
309	files, err := Magefiles("testdata/mixed_lib_files", "", "", "go", buf, false)
310	if err != nil {
311		t.Errorf("error from magefile list: %v: %s", err, buf)
312	}
313	expected := []string{
314		filepath.FromSlash("testdata/mixed_lib_files/mage_helpers.go"),
315		filepath.FromSlash("testdata/mixed_lib_files/magefile.go"),
316	}
317	if !reflect.DeepEqual(files, expected) {
318		t.Fatalf("expected %q but got %q", expected, files)
319	}
320}
321
322func TestMixedMageImports(t *testing.T) {
323	resetTerm()
324	stderr := &bytes.Buffer{}
325	stdout := &bytes.Buffer{}
326	inv := Invocation{
327		Dir:    "./testdata/mixed_lib_files",
328		Stdout: stdout,
329		Stderr: stderr,
330		List:   true,
331	}
332	code := Invoke(inv)
333	if code != 0 {
334		t.Errorf("expected to exit with code 0, but got %v, stderr: %s", code, stderr)
335	}
336	expected := "Targets:\n  build    \n"
337	actual := stdout.String()
338	if actual != expected {
339		t.Fatalf("expected %q but got %q", expected, actual)
340	}
341}
342
343func TestGoRun(t *testing.T) {
344	c := exec.Command("go", "run", "main.go")
345	c.Dir = "./testdata"
346	c.Env = os.Environ()
347	b, err := c.CombinedOutput()
348	if err != nil {
349		t.Error("error:", err)
350	}
351	actual := string(b)
352	expected := "stuff\n"
353	if actual != expected {
354		t.Fatalf("expected %q, but got %q", expected, actual)
355	}
356}
357
358func TestVerbose(t *testing.T) {
359	stderr := &bytes.Buffer{}
360	stdout := &bytes.Buffer{}
361	inv := Invocation{
362		Dir:    "./testdata",
363		Stdout: stdout,
364		Stderr: stderr,
365		Args:   []string{"testverbose"},
366	}
367
368	code := Invoke(inv)
369	if code != 0 {
370		t.Errorf("expected to exit with code 0, but got %v", code)
371	}
372	actual := stdout.String()
373	expected := ""
374	if actual != expected {
375		t.Fatalf("expected %q, but got %q", expected, actual)
376	}
377	stderr.Reset()
378	stdout.Reset()
379	inv.Verbose = true
380	code = Invoke(inv)
381	if code != 0 {
382		t.Errorf("expected to exit with code 0, but got %v", code)
383	}
384
385	actual = stderr.String()
386	expected = "Running target: TestVerbose\nhi!\n"
387	if actual != expected {
388		t.Fatalf("expected %q, but got %q", expected, actual)
389	}
390}
391
392func TestVerboseEnv(t *testing.T) {
393	os.Setenv("MAGEFILE_VERBOSE", "true")
394	defer os.Unsetenv("MAGEFILE_VERBOSE")
395	stdout := &bytes.Buffer{}
396	inv, _, err := Parse(ioutil.Discard, stdout, []string{})
397	if err != nil {
398		t.Fatal("unexpected error", err)
399	}
400
401	expected := true
402
403	if inv.Verbose != true {
404		t.Fatalf("expected %t, but got %t ", expected, inv.Verbose)
405	}
406}
407func TestVerboseFalseEnv(t *testing.T) {
408	os.Setenv("MAGEFILE_VERBOSE", "0")
409	defer os.Unsetenv("MAGEFILE_VERBOSE")
410	stdout := &bytes.Buffer{}
411	code := ParseAndRun(ioutil.Discard, stdout, nil, []string{"-d", "testdata", "testverbose"})
412	if code != 0 {
413		t.Fatal("unexpected code", code)
414	}
415
416	if stdout.String() != "" {
417		t.Fatalf("expected no output, but got %s", stdout.String())
418	}
419}
420
421func TestList(t *testing.T) {
422	stdout := &bytes.Buffer{}
423	inv := Invocation{
424		Dir:    "./testdata/list",
425		Stdout: stdout,
426		Stderr: ioutil.Discard,
427		List:   true,
428	}
429
430	code := Invoke(inv)
431	if code != 0 {
432		t.Errorf("expected to exit with code 0, but got %v", code)
433	}
434	actual := stdout.String()
435	expected := `
436This is a comment on the package which should get turned into output with the list of targets.
437
438Targets:
439  somePig*       This is the synopsis for SomePig.
440  testVerbose
441
442* default target
443`[1:]
444
445	if actual != expected {
446		t.Logf("expected: %q", expected)
447		t.Logf("  actual: %q", actual)
448		t.Fatalf("expected:\n%v\n\ngot:\n%v", expected, actual)
449	}
450}
451
452var terminals = []struct {
453	code          string
454	supportsColor bool
455}{
456	{"", true},
457	{"vt100", false},
458	{"cygwin", false},
459	{"xterm-mono", false},
460	{"xterm", true},
461	{"xterm-vt220", true},
462	{"xterm-16color", true},
463	{"xterm-256color", true},
464	{"screen-256color", true},
465}
466
467func TestListWithColor(t *testing.T) {
468	os.Setenv(mg.EnableColorEnv, "true")
469	os.Setenv(mg.TargetColorEnv, mg.Cyan.String())
470
471	expectedPlainText := `
472This is a comment on the package which should get turned into output with the list of targets.
473
474Targets:
475  somePig*       This is the synopsis for SomePig.
476  testVerbose
477
478* default target
479`[1:]
480
481	// NOTE: using the literal string would be complicated because I would need to break it
482	// in the middle and join with a normal string for the target names,
483	// otherwise the single backslash would be taken literally and encoded as \\
484	expectedColorizedText := "" +
485		"This is a comment on the package which should get turned into output with the list of targets.\n" +
486		"\n" +
487		"Targets:\n" +
488		"  \x1b[36msomePig*\x1b[0m       This is the synopsis for SomePig.\n" +
489		"  \x1b[36mtestVerbose\x1b[0m    \n" +
490		"\n" +
491		"* default target\n"
492
493	for _, terminal := range terminals {
494		t.Run(terminal.code, func(t *testing.T) {
495			os.Setenv("TERM", terminal.code)
496
497			stdout := &bytes.Buffer{}
498			inv := Invocation{
499				Dir:    "./testdata/list",
500				Stdout: stdout,
501				Stderr: ioutil.Discard,
502				List:   true,
503			}
504
505			code := Invoke(inv)
506			if code != 0 {
507				t.Errorf("expected to exit with code 0, but got %v", code)
508			}
509			actual := stdout.String()
510			var expected string
511			if terminal.supportsColor {
512				expected = expectedColorizedText
513			} else {
514				expected = expectedPlainText
515			}
516
517			if actual != expected {
518				t.Logf("expected: %q", expected)
519				t.Logf("  actual: %q", actual)
520				t.Fatalf("expected:\n%v\n\ngot:\n%v", expected, actual)
521			}
522		})
523	}
524}
525
526func TestNoArgNoDefaultList(t *testing.T) {
527	resetTerm()
528	stdout := &bytes.Buffer{}
529	stderr := &bytes.Buffer{}
530	inv := Invocation{
531		Dir:    "testdata/no_default",
532		Stdout: stdout,
533		Stderr: stderr,
534	}
535	code := Invoke(inv)
536	if code != 0 {
537		t.Errorf("expected to exit with code 0, but got %v", code)
538	}
539	if err := stderr.String(); err != "" {
540		t.Errorf("unexpected stderr output:\n%s", err)
541	}
542	actual := stdout.String()
543	expected := `
544Targets:
545  bazBuz    Prints out 'BazBuz'.
546  fooBar    Prints out 'FooBar'.
547`[1:]
548	if actual != expected {
549		t.Fatalf("expected:\n%q\n\ngot:\n%q", expected, actual)
550	}
551}
552
553func TestIgnoreDefault(t *testing.T) {
554	stdout := &bytes.Buffer{}
555	stderr := &bytes.Buffer{}
556	inv := Invocation{
557		Dir:    "./testdata/list",
558		Stdout: stdout,
559		Stderr: stderr,
560	}
561	defer os.Unsetenv(mg.IgnoreDefaultEnv)
562	if err := os.Setenv(mg.IgnoreDefaultEnv, "1"); err != nil {
563		t.Fatal(err)
564	}
565	resetTerm()
566
567	code := Invoke(inv)
568	if code != 0 {
569		t.Errorf("expected to exit with code 0, but got %v, stderr:\n%s", code, stderr)
570	}
571	actual := stdout.String()
572	expected := `
573This is a comment on the package which should get turned into output with the list of targets.
574
575Targets:
576  somePig*       This is the synopsis for SomePig.
577  testVerbose
578
579* default target
580`[1:]
581
582	if actual != expected {
583		t.Logf("expected: %q", expected)
584		t.Logf("  actual: %q", actual)
585		t.Fatalf("expected:\n%v\n\ngot:\n%v", expected, actual)
586	}
587}
588
589func TestTargetError(t *testing.T) {
590	stderr := &bytes.Buffer{}
591	inv := Invocation{
592		Dir:    "./testdata",
593		Stdout: ioutil.Discard,
594		Stderr: stderr,
595		Args:   []string{"returnsnonnilerror"},
596	}
597	code := Invoke(inv)
598	if code != 1 {
599		t.Fatalf("expected 1, but got %v", code)
600	}
601	actual := stderr.String()
602	expected := "Error: bang!\n"
603	if actual != expected {
604		t.Fatalf("expected %q, but got %q", expected, actual)
605	}
606}
607
608func TestStdinCopy(t *testing.T) {
609	stdout := &bytes.Buffer{}
610	stdin := strings.NewReader("hi!")
611	inv := Invocation{
612		Dir:    "./testdata",
613		Stderr: ioutil.Discard,
614		Stdout: stdout,
615		Stdin:  stdin,
616		Args:   []string{"CopyStdin"},
617	}
618	code := Invoke(inv)
619	if code != 0 {
620		t.Fatalf("expected 0, but got %v", code)
621	}
622	actual := stdout.String()
623	expected := "hi!"
624	if actual != expected {
625		t.Fatalf("expected %q, but got %q", expected, actual)
626	}
627}
628
629func TestTargetPanics(t *testing.T) {
630	stderr := &bytes.Buffer{}
631	inv := Invocation{
632		Dir:    "./testdata",
633		Stdout: ioutil.Discard,
634		Stderr: stderr,
635		Args:   []string{"panics"},
636	}
637	code := Invoke(inv)
638	if code != 1 {
639		t.Fatalf("expected 1, but got %v", code)
640	}
641	actual := stderr.String()
642	expected := "Error: boom!\n"
643	if actual != expected {
644		t.Fatalf("expected %q, but got %q", expected, actual)
645	}
646}
647
648func TestPanicsErr(t *testing.T) {
649	stderr := &bytes.Buffer{}
650	inv := Invocation{
651		Dir:    "./testdata",
652		Stdout: ioutil.Discard,
653		Stderr: stderr,
654		Args:   []string{"panicserr"},
655	}
656	code := Invoke(inv)
657	if code != 1 {
658		t.Fatalf("expected 1, but got %v", code)
659	}
660	actual := stderr.String()
661	expected := "Error: kaboom!\n"
662	if actual != expected {
663		t.Fatalf("expected %q, but got %q", expected, actual)
664	}
665}
666
667// ensure we include the hash of the mainfile template in determining the
668// executable name to run, so we automatically create a new exe if the template
669// changes.
670func TestHashTemplate(t *testing.T) {
671	templ := mageMainfileTplString
672	defer func() { mageMainfileTplString = templ }()
673	name, err := ExeName("go", mg.CacheDir(), []string{"testdata/func.go", "testdata/command.go"})
674	if err != nil {
675		t.Fatal(err)
676	}
677	mageMainfileTplString = "some other template"
678	changed, err := ExeName("go", mg.CacheDir(), []string{"testdata/func.go", "testdata/command.go"})
679	if err != nil {
680		t.Fatal(err)
681	}
682	if changed == name {
683		t.Fatal("expected executable name to chage if template changed")
684	}
685}
686
687// Test if the -keep flag does keep the mainfile around after running
688func TestKeepFlag(t *testing.T) {
689	buildFile := fmt.Sprintf("./testdata/keep_flag/%s", mainfile)
690	os.Remove(buildFile)
691	defer os.Remove(buildFile)
692	w := tLogWriter{t}
693
694	inv := Invocation{
695		Dir:    "./testdata/keep_flag",
696		Stdout: w,
697		Stderr: w,
698		List:   true,
699		Keep:   true,
700		Force:  true, // need force so we always regenerate
701	}
702	code := Invoke(inv)
703	if code != 0 {
704		t.Fatalf("expected code 0, but got %v", code)
705	}
706
707	if _, err := os.Stat(buildFile); err != nil {
708		t.Fatalf("expected file %q to exist but got err, %v", buildFile, err)
709	}
710}
711
712type tLogWriter struct {
713	*testing.T
714}
715
716func (t tLogWriter) Write(b []byte) (n int, err error) {
717	t.Log(string(b))
718	return len(b), nil
719}
720
721// Test if generated mainfile references anything other than the stdlib
722func TestOnlyStdLib(t *testing.T) {
723	buildFile := fmt.Sprintf("./testdata/onlyStdLib/%s", mainfile)
724	os.Remove(buildFile)
725	defer os.Remove(buildFile)
726
727	w := tLogWriter{t}
728
729	inv := Invocation{
730		Dir:     "./testdata/onlyStdLib",
731		Stdout:  w,
732		Stderr:  w,
733		List:    true,
734		Keep:    true,
735		Force:   true, // need force so we always regenerate
736		Verbose: true,
737	}
738	code := Invoke(inv)
739	if code != 0 {
740		t.Fatalf("expected code 0, but got %v", code)
741	}
742
743	if _, err := os.Stat(buildFile); err != nil {
744		t.Fatalf("expected file %q to exist but got err, %v", buildFile, err)
745	}
746
747	fset := &token.FileSet{}
748	// Parse src but stop after processing the imports.
749	f, err := parser.ParseFile(fset, buildFile, nil, parser.ImportsOnly)
750	if err != nil {
751		fmt.Println(err)
752		return
753	}
754
755	// Print the imports from the file's AST.
756	for _, s := range f.Imports {
757		// the path value comes in as a quoted string, i.e. literally \"context\"
758		path := strings.Trim(s.Path.Value, "\"")
759		pkg, err := build.Default.Import(path, "./testdata/keep_flag", build.FindOnly)
760		if err != nil {
761			t.Fatal(err)
762		}
763		if !filepath.HasPrefix(pkg.Dir, build.Default.GOROOT) {
764			t.Errorf("import of non-stdlib package: %s", s.Path.Value)
765		}
766	}
767}
768
769func TestMultipleTargets(t *testing.T) {
770	var stderr, stdout bytes.Buffer
771	inv := Invocation{
772		Dir:     "./testdata",
773		Stdout:  &stdout,
774		Stderr:  &stderr,
775		Args:    []string{"TestVerbose", "ReturnsNilError"},
776		Verbose: true,
777	}
778	code := Invoke(inv)
779	if code != 0 {
780		t.Errorf("expected 0, but got %v", code)
781	}
782	actual := stderr.String()
783	expected := "Running target: TestVerbose\nhi!\nRunning target: ReturnsNilError\n"
784	if actual != expected {
785		t.Errorf("expected %q, but got %q", expected, actual)
786	}
787	actual = stdout.String()
788	expected = "stuff\n"
789	if actual != expected {
790		t.Errorf("expected %q, but got %q", expected, actual)
791	}
792}
793
794func TestFirstTargetFails(t *testing.T) {
795	var stderr, stdout bytes.Buffer
796	inv := Invocation{
797		Dir:     "./testdata",
798		Stdout:  &stdout,
799		Stderr:  &stderr,
800		Args:    []string{"ReturnsNonNilError", "ReturnsNilError"},
801		Verbose: true,
802	}
803	code := Invoke(inv)
804	if code != 1 {
805		t.Errorf("expected 1, but got %v", code)
806	}
807	actual := stderr.String()
808	expected := "Running target: ReturnsNonNilError\nError: bang!\n"
809	if actual != expected {
810		t.Errorf("expected %q, but got %q", expected, actual)
811	}
812	actual = stdout.String()
813	expected = ""
814	if actual != expected {
815		t.Errorf("expected %q, but got %q", expected, actual)
816	}
817}
818
819func TestBadSecondTargets(t *testing.T) {
820	var stderr, stdout bytes.Buffer
821	inv := Invocation{
822		Dir:    "./testdata",
823		Stdout: &stdout,
824		Stderr: &stderr,
825		Args:   []string{"TestVerbose", "NotGonnaWork"},
826	}
827	code := Invoke(inv)
828	if code != 2 {
829		t.Errorf("expected 2, but got %v", code)
830	}
831	actual := stderr.String()
832	expected := "Unknown target specified: \"NotGonnaWork\"\n"
833	if actual != expected {
834		t.Errorf("expected %q, but got %q", expected, actual)
835	}
836	actual = stdout.String()
837	expected = ""
838	if actual != expected {
839		t.Errorf("expected %q, but got %q", expected, actual)
840	}
841}
842
843func TestParse(t *testing.T) {
844	buf := &bytes.Buffer{}
845	inv, cmd, err := Parse(ioutil.Discard, buf, []string{"-v", "-debug", "-gocmd=foo", "-d", "dir", "build", "deploy"})
846	if err != nil {
847		t.Fatal("unexpected error", err)
848	}
849	if cmd == Init {
850		t.Error("init should be false but was true")
851	}
852	if cmd == Version {
853		t.Error("showVersion should be false but was true")
854	}
855	if inv.Debug != true {
856		t.Error("debug should be true")
857	}
858	if inv.Dir != "dir" {
859		t.Errorf("Expected dir to be \"dir\" but was %q", inv.Dir)
860	}
861	if inv.GoCmd != "foo" {
862		t.Errorf("Expected gocmd to be \"foo\" but was %q", inv.GoCmd)
863	}
864	expected := []string{"build", "deploy"}
865	if !reflect.DeepEqual(inv.Args, expected) {
866		t.Fatalf("expected args to be %q but got %q", expected, inv.Args)
867	}
868	if s := buf.String(); s != "" {
869		t.Fatalf("expected no stdout output but got %q", s)
870	}
871
872}
873
874func TestSetDir(t *testing.T) {
875	stdout := &bytes.Buffer{}
876	stderr := &bytes.Buffer{}
877	code := Invoke(Invocation{
878		Dir:    "testdata/setdir",
879		Stdout: stdout,
880		Stderr: stderr,
881		Args:   []string{"TestCurrentDir"},
882	})
883	if code != 0 {
884		t.Errorf("expected code 0, but got %d. Stdout:\n%s\nStderr:\n%s", code, stdout, stderr)
885	}
886	expected := "setdir.go\n"
887	if out := stdout.String(); out != expected {
888		t.Fatalf("expected list of files to be %q, but was %q", expected, out)
889	}
890}
891
892func TestSetWorkingDir(t *testing.T) {
893	stdout := &bytes.Buffer{}
894	stderr := &bytes.Buffer{}
895	code := Invoke(Invocation{
896		Dir:     "testdata/setworkdir",
897		WorkDir: "testdata/setworkdir/data",
898		Stdout:  stdout,
899		Stderr:  stderr,
900		Args:    []string{"TestWorkingDir"},
901	})
902
903	if code != 0 {
904		t.Errorf(
905			"expected code 0, but got %d. Stdout:\n%s\nStderr:\n%s",
906			code, stdout, stderr,
907		)
908	}
909
910	expected := "file1.txt, file2.txt\n"
911	if out := stdout.String(); out != expected {
912		t.Fatalf("expected list of files to be %q, but was %q", expected, out)
913	}
914}
915
916// Test the timeout option
917func TestTimeout(t *testing.T) {
918	stderr := &bytes.Buffer{}
919	stdout := &bytes.Buffer{}
920	inv := Invocation{
921		Dir:     "testdata/context",
922		Stdout:  stdout,
923		Stderr:  stderr,
924		Args:    []string{"timeout"},
925		Timeout: time.Duration(100 * time.Millisecond),
926	}
927	code := Invoke(inv)
928	if code != 1 {
929		t.Fatalf("expected 1, but got %v, stderr: %q, stdout: %q", code, stderr, stdout)
930	}
931	actual := stderr.String()
932	expected := "Error: context deadline exceeded\n"
933
934	if actual != expected {
935		t.Fatalf("expected %q, but got %q", expected, actual)
936	}
937}
938func TestParseHelp(t *testing.T) {
939	buf := &bytes.Buffer{}
940	_, _, err := Parse(ioutil.Discard, buf, []string{"-h"})
941	if err != flag.ErrHelp {
942		t.Fatal("unexpected error", err)
943	}
944	buf2 := &bytes.Buffer{}
945	_, _, err = Parse(ioutil.Discard, buf2, []string{"--help"})
946	if err != flag.ErrHelp {
947		t.Fatal("unexpected error", err)
948	}
949	s := buf.String()
950	s2 := buf2.String()
951	if s != s2 {
952		t.Fatalf("expected -h and --help to produce same output, but got different.\n\n-h:\n%s\n\n--help:\n%s", s, s2)
953	}
954}
955
956func TestHelpTarget(t *testing.T) {
957	stdout := &bytes.Buffer{}
958	inv := Invocation{
959		Dir:    "./testdata",
960		Stdout: stdout,
961		Stderr: ioutil.Discard,
962		Args:   []string{"panics"},
963		Help:   true,
964	}
965	code := Invoke(inv)
966	if code != 0 {
967		t.Errorf("expected to exit with code 0, but got %v", code)
968	}
969	actual := stdout.String()
970	expected := "Function that panics.\n\nUsage:\n\n\tmage panics\n\n"
971	if actual != expected {
972		t.Fatalf("expected %q, but got %q", expected, actual)
973	}
974}
975
976func TestHelpAlias(t *testing.T) {
977	stdout := &bytes.Buffer{}
978	inv := Invocation{
979		Dir:    "./testdata/alias",
980		Stdout: stdout,
981		Stderr: ioutil.Discard,
982		Args:   []string{"status"},
983		Help:   true,
984	}
985	code := Invoke(inv)
986	if code != 0 {
987		t.Errorf("expected to exit with code 0, but got %v", code)
988	}
989	actual := stdout.String()
990	expected := "Prints status.\n\nUsage:\n\n\tmage status\n\nAliases: st, stat\n\n"
991	if actual != expected {
992		t.Fatalf("expected %q, but got %q", expected, actual)
993	}
994	inv = Invocation{
995		Dir:    "./testdata/alias",
996		Stdout: stdout,
997		Stderr: ioutil.Discard,
998		Args:   []string{"checkout"},
999		Help:   true,
1000	}
1001	stdout.Reset()
1002	code = Invoke(inv)
1003	if code != 0 {
1004		t.Errorf("expected to exit with code 0, but got %v", code)
1005	}
1006	actual = stdout.String()
1007	expected = "Usage:\n\n\tmage checkout\n\nAliases: co\n\n"
1008	if actual != expected {
1009		t.Fatalf("expected %q, but got %q", expected, actual)
1010	}
1011}
1012
1013func TestAlias(t *testing.T) {
1014	stdout := &bytes.Buffer{}
1015	stderr := &bytes.Buffer{}
1016	debug.SetOutput(stderr)
1017	inv := Invocation{
1018		Dir:    "testdata/alias",
1019		Stdout: stdout,
1020		Stderr: ioutil.Discard,
1021		Args:   []string{"status"},
1022		Debug:  true,
1023	}
1024	code := Invoke(inv)
1025	if code != 0 {
1026		t.Errorf("expected to exit with code 0, but got %v\noutput:\n%s\nstderr:\n%s", code, stdout, stderr)
1027	}
1028	actual := stdout.String()
1029	expected := "alias!\n"
1030	if actual != expected {
1031		t.Fatalf("expected %q, but got %q", expected, actual)
1032	}
1033	stdout.Reset()
1034	inv.Args = []string{"st"}
1035	code = Invoke(inv)
1036	if code != 0 {
1037		t.Errorf("expected to exit with code 0, but got %v", code)
1038	}
1039	actual = stdout.String()
1040	if actual != expected {
1041		t.Fatalf("expected %q, but got %q", expected, actual)
1042	}
1043}
1044
1045func TestInvalidAlias(t *testing.T) {
1046	stderr := &bytes.Buffer{}
1047	log.SetOutput(ioutil.Discard)
1048	inv := Invocation{
1049		Dir:    "./testdata/invalid_alias",
1050		Stdout: ioutil.Discard,
1051		Stderr: stderr,
1052		Args:   []string{"co"},
1053	}
1054	code := Invoke(inv)
1055	if code != 2 {
1056		t.Errorf("expected to exit with code 2, but got %v", code)
1057	}
1058	actual := stderr.String()
1059	expected := "Unknown target specified: \"co\"\n"
1060	if actual != expected {
1061		t.Fatalf("expected %q, but got %q", expected, actual)
1062	}
1063}
1064
1065func TestRunCompiledPrintsError(t *testing.T) {
1066	stderr := &bytes.Buffer{}
1067	logger := log.New(stderr, "", 0)
1068	code := RunCompiled(Invocation{}, "thiswon'texist", logger)
1069	if code != 1 {
1070		t.Errorf("expected code 1 but got %v", code)
1071	}
1072
1073	if strings.TrimSpace(stderr.String()) == "" {
1074		t.Fatal("expected to get output to stderr when a run fails, but got nothing.")
1075	}
1076}
1077
1078func TestCompiledFlags(t *testing.T) {
1079	stderr := &bytes.Buffer{}
1080	stdout := &bytes.Buffer{}
1081	dir := "./testdata/compiled"
1082	compileDir, err := ioutil.TempDir(dir, "")
1083	if err != nil {
1084		t.Fatal(err)
1085	}
1086	name := filepath.Join(compileDir, "mage_out")
1087	if runtime.GOOS == "windows" {
1088		name += ".exe"
1089	}
1090	// The CompileOut directory is relative to the
1091	// invocation directory, so chop off the invocation dir.
1092	outName := "./" + name[len(dir)-1:]
1093	defer os.RemoveAll(compileDir)
1094	inv := Invocation{
1095		Dir:        dir,
1096		Stdout:     stdout,
1097		Stderr:     stderr,
1098		CompileOut: outName,
1099	}
1100	code := Invoke(inv)
1101	if code != 0 {
1102		t.Errorf("expected to exit with code 0, but got %v, stderr: %s", code, stderr)
1103	}
1104
1105	run := func(stdout, stderr *bytes.Buffer, filename string, args ...string) error {
1106		stderr.Reset()
1107		stdout.Reset()
1108		cmd := exec.Command(filename, args...)
1109		cmd.Env = os.Environ()
1110		cmd.Stderr = stderr
1111		cmd.Stdout = stdout
1112		if err := cmd.Run(); err != nil {
1113			return fmt.Errorf("running '%s %s' failed with: %v\nstdout: %s\nstderr: %s",
1114				filename, strings.Join(args, " "), err, stdout, stderr)
1115		}
1116		return nil
1117	}
1118
1119	// get help to target with flag -h target
1120	if err := run(stdout, stderr, name, "-h", "deploy"); err != nil {
1121		t.Fatal(err)
1122	}
1123	got := strings.TrimSpace(stdout.String())
1124	want := "This is the synopsis for Deploy. This part shouldn't show up.\n\nUsage:\n\n\t" + filepath.Base(name) + " deploy"
1125	if got != want {
1126		t.Errorf("got %q, want %q", got, want)
1127	}
1128
1129	// run target with verbose flag -v
1130	if err := run(stdout, stderr, name, "-v", "testverbose"); err != nil {
1131		t.Fatal(err)
1132	}
1133	got = stderr.String()
1134	want = "hi!"
1135	if strings.Contains(got, want) == false {
1136		t.Errorf("got %q, does not contain %q", got, want)
1137	}
1138
1139	// pass list flag -l
1140	if err := run(stdout, stderr, name, "-l"); err != nil {
1141		t.Fatal(err)
1142	}
1143	got = stdout.String()
1144	want = "This is the synopsis for Deploy"
1145	if strings.Contains(got, want) == false {
1146		t.Errorf("got %q, does not contain %q", got, want)
1147	}
1148	want = "This is very verbose"
1149	if strings.Contains(got, want) == false {
1150		t.Errorf("got %q, does not contain %q", got, want)
1151	}
1152
1153	// pass flag -t 1ms
1154	err = run(stdout, stderr, name, "-t", "1ms", "sleep")
1155	if err == nil {
1156		t.Fatalf("expected an error because of timeout")
1157	}
1158	got = stdout.String()
1159	want = "context deadline exceeded"
1160	if strings.Contains(got, want) == false {
1161		t.Errorf("got %q, does not contain %q", got, want)
1162	}
1163}
1164
1165func TestCompiledEnvironmentVars(t *testing.T) {
1166	stderr := &bytes.Buffer{}
1167	stdout := &bytes.Buffer{}
1168	dir := "./testdata/compiled"
1169	compileDir, err := ioutil.TempDir(dir, "")
1170	if err != nil {
1171		t.Fatal(err)
1172	}
1173	name := filepath.Join(compileDir, "mage_out")
1174	if runtime.GOOS == "windows" {
1175		name += ".exe"
1176	}
1177	// The CompileOut directory is relative to the
1178	// invocation directory, so chop off the invocation dir.
1179	outName := "./" + name[len(dir)-1:]
1180	defer os.RemoveAll(compileDir)
1181	inv := Invocation{
1182		Dir:        dir,
1183		Stdout:     stdout,
1184		Stderr:     stderr,
1185		CompileOut: outName,
1186	}
1187	code := Invoke(inv)
1188	if code != 0 {
1189		t.Errorf("expected to exit with code 0, but got %v, stderr: %s", code, stderr)
1190	}
1191
1192	run := func(stdout, stderr *bytes.Buffer, filename string, envval string, args ...string) error {
1193		stderr.Reset()
1194		stdout.Reset()
1195		cmd := exec.Command(filename, args...)
1196		cmd.Env = []string{envval}
1197		cmd.Stderr = stderr
1198		cmd.Stdout = stdout
1199		if err := cmd.Run(); err != nil {
1200			return fmt.Errorf("running '%s %s' failed with: %v\nstdout: %s\nstderr: %s",
1201				filename, strings.Join(args, " "), err, stdout, stderr)
1202		}
1203		return nil
1204	}
1205
1206	if err := run(stdout, stderr, name, "MAGEFILE_HELP=1", "deploy"); err != nil {
1207		t.Fatal(err)
1208	}
1209	got := stdout.String()
1210	want := "This is the synopsis for Deploy. This part shouldn't show up.\n\nUsage:\n\n\t" + filepath.Base(name) + " deploy\n\n"
1211	if got != want {
1212		t.Errorf("got %q, want %q", got, want)
1213	}
1214
1215	if err := run(stdout, stderr, name, mg.VerboseEnv+"=1", "testverbose"); err != nil {
1216		t.Fatal(err)
1217	}
1218	got = stderr.String()
1219	want = "hi!"
1220	if strings.Contains(got, want) == false {
1221		t.Errorf("got %q, does not contain %q", got, want)
1222	}
1223
1224	if err := run(stdout, stderr, name, "MAGEFILE_LIST=1"); err != nil {
1225		t.Fatal(err)
1226	}
1227	got = stdout.String()
1228	want = "This is the synopsis for Deploy"
1229	if strings.Contains(got, want) == false {
1230		t.Errorf("got %q, does not contain %q", got, want)
1231	}
1232	want = "This is very verbose"
1233	if strings.Contains(got, want) == false {
1234		t.Errorf("got %q, does not contain %q", got, want)
1235	}
1236
1237	if err := run(stdout, stderr, name, mg.IgnoreDefaultEnv+"=1"); err != nil {
1238		t.Fatal(err)
1239	}
1240	got = stdout.String()
1241	want = "Compiled package description."
1242	if strings.Contains(got, want) == false {
1243		t.Errorf("got %q, does not contain %q", got, want)
1244	}
1245
1246	err = run(stdout, stderr, name, "MAGEFILE_TIMEOUT=1ms", "sleep")
1247	if err == nil {
1248		t.Fatalf("expected an error because of timeout")
1249	}
1250	got = stdout.String()
1251	want = "context deadline exceeded"
1252	if strings.Contains(got, want) == false {
1253		t.Errorf("got %q, does not contain %q", got, want)
1254	}
1255}
1256
1257func TestCompiledVerboseFlag(t *testing.T) {
1258	stderr := &bytes.Buffer{}
1259	stdout := &bytes.Buffer{}
1260	dir := "./testdata/compiled"
1261	compileDir, err := ioutil.TempDir(dir, "")
1262	if err != nil {
1263		t.Fatal(err)
1264	}
1265	filename := filepath.Join(compileDir, "mage_out")
1266	if runtime.GOOS == "windows" {
1267		filename += ".exe"
1268	}
1269	// The CompileOut directory is relative to the
1270	// invocation directory, so chop off the invocation dir.
1271	outName := "./" + filename[len(dir)-1:]
1272	defer os.RemoveAll(compileDir)
1273	inv := Invocation{
1274		Dir:        dir,
1275		Stdout:     stdout,
1276		Stderr:     stderr,
1277		CompileOut: outName,
1278	}
1279	code := Invoke(inv)
1280	if code != 0 {
1281		t.Errorf("expected to exit with code 0, but got %v, stderr: %s", code, stderr)
1282	}
1283
1284	run := func(verboseEnv string, args ...string) string {
1285		var stdout, stderr bytes.Buffer
1286		args = append(args, "printverboseflag")
1287		cmd := exec.Command(filename, args...)
1288		cmd.Env = []string{verboseEnv}
1289		cmd.Stderr = &stderr
1290		cmd.Stdout = &stdout
1291		if err := cmd.Run(); err != nil {
1292			t.Fatalf("running '%s %s' with env %s failed with: %v\nstdout: %s\nstderr: %s",
1293				filename, strings.Join(args, " "), verboseEnv, err, stdout.String(), stderr.String())
1294		}
1295		return strings.TrimSpace(stdout.String())
1296	}
1297
1298	got := run("MAGEFILE_VERBOSE=false")
1299	want := "mg.Verbose()==false"
1300	if got != want {
1301		t.Errorf("got %q, expected %q", got, want)
1302	}
1303
1304	got = run("MAGEFILE_VERBOSE=false", "-v")
1305	want = "mg.Verbose()==true"
1306	if got != want {
1307		t.Errorf("got %q, expected %q", got, want)
1308	}
1309
1310	got = run("MAGEFILE_VERBOSE=true")
1311	want = "mg.Verbose()==true"
1312	if got != want {
1313		t.Errorf("got %q, expected %q", got, want)
1314	}
1315
1316	got = run("MAGEFILE_VERBOSE=true", "-v=false")
1317	want = "mg.Verbose()==false"
1318	if got != want {
1319		t.Errorf("got %q, expected %q", got, want)
1320	}
1321}
1322
1323func TestClean(t *testing.T) {
1324	if err := os.RemoveAll(mg.CacheDir()); err != nil {
1325		t.Error("error removing cache dir:", err)
1326	}
1327	code := ParseAndRun(ioutil.Discard, ioutil.Discard, &bytes.Buffer{}, []string{"-clean"})
1328	if code != 0 {
1329		t.Errorf("expected 0, but got %v", code)
1330	}
1331
1332	TestAlias(t) // make sure we've got something in the CACHE_DIR
1333	files, err := ioutil.ReadDir(mg.CacheDir())
1334	if err != nil {
1335		t.Error("issue reading file:", err)
1336	}
1337
1338	if len(files) < 1 {
1339		t.Error("Need at least 1 cached binaries to test --clean")
1340	}
1341
1342	_, cmd, err := Parse(ioutil.Discard, ioutil.Discard, []string{"-clean"})
1343	if err != nil {
1344		t.Fatal(err)
1345	}
1346	if cmd != Clean {
1347		t.Errorf("Expected 'clean' command but got %v", cmd)
1348	}
1349	buf := &bytes.Buffer{}
1350	code = ParseAndRun(ioutil.Discard, buf, &bytes.Buffer{}, []string{"-clean"})
1351	if code != 0 {
1352		t.Fatalf("expected 0, but got %v: %s", code, buf)
1353	}
1354
1355	infos, err := ioutil.ReadDir(mg.CacheDir())
1356	if err != nil {
1357		t.Fatal(err)
1358	}
1359
1360	var names []string
1361	for _, i := range infos {
1362		if !i.IsDir() {
1363			names = append(names, i.Name())
1364		}
1365	}
1366
1367	if len(names) != 0 {
1368		t.Errorf("expected '-clean' to remove files from CACHE_DIR, but still have %v", names)
1369	}
1370}
1371
1372func TestGoCmd(t *testing.T) {
1373	textOutput := "TestGoCmd"
1374	defer os.Unsetenv(testExeEnv)
1375	if err := os.Setenv(testExeEnv, textOutput); err != nil {
1376		t.Fatal(err)
1377	}
1378
1379	// fake out the compiled file, since the code checks for it.
1380	f, err := ioutil.TempFile("", "")
1381	if err != nil {
1382		t.Fatal(err)
1383	}
1384	name := f.Name()
1385	dir := filepath.Dir(name)
1386	defer os.Remove(name)
1387	f.Close()
1388
1389	buf := &bytes.Buffer{}
1390	stderr := &bytes.Buffer{}
1391	if err := Compile("", "", "", dir, os.Args[0], name, []string{}, false, stderr, buf); err != nil {
1392		t.Log("stderr: ", stderr.String())
1393		t.Fatal(err)
1394	}
1395	if buf.String() != textOutput {
1396		t.Fatalf("We didn't run the custom go cmd. Expected output %q, but got %q", textOutput, buf)
1397	}
1398}
1399
1400var runtimeVer = regexp.MustCompile(`go1\.([0-9]+)`)
1401
1402func TestGoModules(t *testing.T) {
1403	resetTerm()
1404	matches := runtimeVer.FindStringSubmatch(runtime.Version())
1405	if len(matches) < 2 || minorVer(t, matches[1]) < 11 {
1406		t.Skipf("Skipping Go modules test because go version %q is less than go1.11", runtime.Version())
1407	}
1408	dir, err := ioutil.TempDir("", "")
1409	if err != nil {
1410		t.Fatal(err)
1411	}
1412	defer os.RemoveAll(dir)
1413	err = ioutil.WriteFile(filepath.Join(dir, "magefile.go"), []byte(`//+build mage
1414
1415package main
1416
1417import "golang.org/x/text/unicode/norm"
1418
1419func Test() {
1420	print("unicode version: " + norm.Version)
1421}
1422`), 0600)
1423	if err != nil {
1424		t.Fatal(err)
1425	}
1426
1427	stdout := &bytes.Buffer{}
1428	stderr := &bytes.Buffer{}
1429	cmd := exec.Command("go", "mod", "init", "app")
1430	cmd.Dir = dir
1431	cmd.Env = os.Environ()
1432	cmd.Stderr = stderr
1433	cmd.Stdout = stdout
1434	if err := cmd.Run(); err != nil {
1435		t.Fatalf("Error running go mod init: %v\nStdout: %s\nStderr: %s", err, stdout, stderr)
1436	}
1437	stderr.Reset()
1438	stdout.Reset()
1439	code := Invoke(Invocation{
1440		Dir:    dir,
1441		Stderr: stderr,
1442		Stdout: stdout,
1443	})
1444	if code != 0 {
1445		t.Fatalf("exited with code %d. \nStdout: %s\nStderr: %s", code, stdout, stderr)
1446	}
1447	expected := `
1448Targets:
1449  test
1450`[1:]
1451	if output := stdout.String(); output != expected {
1452		t.Fatalf("expected output %q, but got %q", expected, output)
1453	}
1454}
1455
1456func minorVer(t *testing.T, v string) int {
1457	a, err := strconv.Atoi(v)
1458	if err != nil {
1459		t.Fatal("unexpected non-numeric version", v)
1460	}
1461	return a
1462}
1463
1464func TestNamespaceDep(t *testing.T) {
1465	stdout := &bytes.Buffer{}
1466	stderr := &bytes.Buffer{}
1467	inv := Invocation{
1468		Dir:    "./testdata/namespaces",
1469		Stderr: stderr,
1470		Stdout: stdout,
1471		Args:   []string{"TestNamespaceDep"},
1472	}
1473	code := Invoke(inv)
1474	if code != 0 {
1475		t.Fatalf("expected 0, but got %v, stderr:\n%s", code, stderr)
1476	}
1477	expected := "hi!\n"
1478	if stdout.String() != expected {
1479		t.Fatalf("expected %q, but got %q", expected, stdout.String())
1480	}
1481}
1482
1483func TestNamespace(t *testing.T) {
1484	stdout := &bytes.Buffer{}
1485	inv := Invocation{
1486		Dir:    "./testdata/namespaces",
1487		Stderr: ioutil.Discard,
1488		Stdout: stdout,
1489		Args:   []string{"ns:error"},
1490	}
1491	code := Invoke(inv)
1492	if code != 0 {
1493		t.Fatalf("expected 0, but got %v", code)
1494	}
1495	expected := "hi!\n"
1496	if stdout.String() != expected {
1497		t.Fatalf("expected %q, but got %q", expected, stdout.String())
1498	}
1499}
1500
1501func TestNamespaceDefault(t *testing.T) {
1502	stdout := &bytes.Buffer{}
1503	inv := Invocation{
1504		Dir:    "./testdata/namespaces",
1505		Stderr: ioutil.Discard,
1506		Stdout: stdout,
1507	}
1508	code := Invoke(inv)
1509	if code != 0 {
1510		t.Fatalf("expected 0, but got %v", code)
1511	}
1512	expected := "hi!\n"
1513	if stdout.String() != expected {
1514		t.Fatalf("expected %q, but got %q", expected, stdout.String())
1515	}
1516}
1517
1518func TestAliasToImport(t *testing.T) {
1519
1520}
1521
1522func TestWrongDependency(t *testing.T) {
1523	stderr := &bytes.Buffer{}
1524	inv := Invocation{
1525		Dir:    "./testdata/wrong_dep",
1526		Stderr: stderr,
1527		Stdout: ioutil.Discard,
1528	}
1529	code := Invoke(inv)
1530	if code != 1 {
1531		t.Fatalf("expected 1, but got %v", code)
1532	}
1533	expected := "Error: argument 0 (complex128), is not a supported argument type\n"
1534	actual := stderr.String()
1535	if actual != expected {
1536		t.Fatalf("expected %q, but got %q", expected, actual)
1537	}
1538}
1539
1540/// This code liberally borrowed from https://github.com/rsc/goversion/blob/master/version/exe.go
1541
1542type exeType int
1543type archSize int
1544
1545const (
1546	winExe exeType = iota
1547	macExe
1548
1549	arch32 archSize = iota
1550	arch64
1551)
1552
1553// fileData tells us if the given file is mac or windows and if they're 32bit or
1554// 64 bit.  Other exe versions are not supported.
1555func fileData(file string) (exeType, archSize, error) {
1556	f, err := os.Open(file)
1557	if err != nil {
1558		return -1, -1, err
1559	}
1560	defer f.Close()
1561	data := make([]byte, 16)
1562	if _, err := io.ReadFull(f, data); err != nil {
1563		return -1, -1, err
1564	}
1565	if bytes.HasPrefix(data, []byte("MZ")) {
1566		// hello windows exe!
1567		e, err := pe.NewFile(f)
1568		if err != nil {
1569			return -1, -1, err
1570		}
1571		if e.Machine == pe.IMAGE_FILE_MACHINE_AMD64 {
1572			return winExe, arch64, nil
1573		}
1574		return winExe, arch32, nil
1575	}
1576
1577	if bytes.HasPrefix(data, []byte("\xFE\xED\xFA")) || bytes.HasPrefix(data[1:], []byte("\xFA\xED\xFE")) {
1578		// hello mac exe!
1579		fe, err := macho.NewFile(f)
1580		if err != nil {
1581			return -1, -1, err
1582		}
1583		if fe.Cpu&0x01000000 != 0 {
1584			return macExe, arch64, nil
1585		}
1586		return macExe, arch32, nil
1587	}
1588	return -1, -1, fmt.Errorf("unrecognized executable format")
1589}
1590