1package mage
2
3import (
4	"bytes"
5	"flag"
6	"fmt"
7	"go/build"
8	"go/parser"
9	"go/token"
10	"io/ioutil"
11	"log"
12	"os"
13	"os/exec"
14	"path/filepath"
15	"reflect"
16	"regexp"
17	"runtime"
18	"strconv"
19	"strings"
20	"testing"
21	"time"
22
23	"github.com/magefile/mage/mg"
24)
25
26const testExeEnv = "MAGE_TEST_STRING"
27
28func TestMain(m *testing.M) {
29	if s := os.Getenv(testExeEnv); s != "" {
30		fmt.Fprint(os.Stdout, s)
31		os.Exit(0)
32	}
33	os.Exit(testmain(m))
34}
35
36func testmain(m *testing.M) int {
37	// ensure we write our temporary binaries to a directory that we'll delete
38	// after running tests.
39	dir, err := ioutil.TempDir("", "")
40	if err != nil {
41		log.Fatal(err)
42	}
43	defer os.RemoveAll(dir)
44	if err := os.Setenv(mg.CacheEnv, dir); err != nil {
45		log.Fatal(err)
46	}
47	if err := os.Unsetenv(mg.VerboseEnv); err != nil {
48		log.Fatal(err)
49	}
50	if err := os.Unsetenv(mg.DebugEnv); err != nil {
51		log.Fatal(err)
52	}
53	if err := os.Unsetenv(mg.IgnoreDefaultEnv); err != nil {
54		log.Fatal(err)
55	}
56	return m.Run()
57}
58
59func TestTransitiveDepCache(t *testing.T) {
60	cache, err := outputDebug("go", "env", "gocache")
61	if err != nil {
62		t.Fatal(err)
63	}
64	if cache == "" {
65		t.Skip("skipping gocache tests on go version without cache")
66	}
67	// Test that if we change a transitive dep, that we recompile
68	stdout := &bytes.Buffer{}
69	stderr := &bytes.Buffer{}
70	inv := Invocation{
71		Stderr: stderr,
72		Stdout: stdout,
73		Dir:    "testdata/transitiveDeps",
74		Args:   []string{"Run"},
75	}
76	code := Invoke(inv)
77	if code != 0 {
78		t.Fatalf("got code %v, err: %s", code, stderr)
79	}
80	expected := "woof\n"
81	if actual := stdout.String(); actual != expected {
82		t.Fatalf("expected %q but got %q", expected, actual)
83	}
84	// ok, so baseline, the generated and cached binary should do "woof"
85	// now change out the transitive dependency that does the output
86	// so that it produces different output.
87	if err := os.Rename("testdata/transitiveDeps/dep/dog.go", "testdata/transitiveDeps/dep/dog.notgo"); err != nil {
88		t.Fatal(err)
89	}
90	defer os.Rename("testdata/transitiveDeps/dep/dog.notgo", "testdata/transitiveDeps/dep/dog.go")
91	if err := os.Rename("testdata/transitiveDeps/dep/cat.notgo", "testdata/transitiveDeps/dep/cat.go"); err != nil {
92		t.Fatal(err)
93	}
94	defer os.Rename("testdata/transitiveDeps/dep/cat.go", "testdata/transitiveDeps/dep/cat.notgo")
95	stderr.Reset()
96	stdout.Reset()
97	code = Invoke(inv)
98	if code != 0 {
99		t.Fatalf("got code %v, err: %s", code, stderr)
100	}
101	expected = "meow\n"
102	if actual := stdout.String(); actual != expected {
103		t.Fatalf("expected %q but got %q", expected, actual)
104	}
105}
106
107func TestListMagefilesMain(t *testing.T) {
108	buf := &bytes.Buffer{}
109	files, err := Magefiles("testdata/mixed_main_files", "go", buf, false)
110	if err != nil {
111		t.Errorf("error from magefile list: %v: %s", err, buf)
112	}
113	expected := []string{"testdata/mixed_main_files/mage_helpers.go", "testdata/mixed_main_files/magefile.go"}
114	if !reflect.DeepEqual(files, expected) {
115		t.Fatalf("expected %q but got %q", expected, files)
116	}
117}
118
119func TestListMagefilesLib(t *testing.T) {
120	buf := &bytes.Buffer{}
121	files, err := Magefiles("testdata/mixed_lib_files", "go", buf, false)
122	if err != nil {
123		t.Errorf("error from magefile list: %v: %s", err, buf)
124	}
125	expected := []string{"testdata/mixed_lib_files/mage_helpers.go", "testdata/mixed_lib_files/magefile.go"}
126	if !reflect.DeepEqual(files, expected) {
127		t.Fatalf("expected %q but got %q", expected, files)
128	}
129}
130
131func TestGoRun(t *testing.T) {
132	c := exec.Command("go", "run", "main.go")
133	c.Dir = "./testdata"
134	c.Env = os.Environ()
135	b, err := c.CombinedOutput()
136	if err != nil {
137		t.Error("error:", err)
138	}
139	actual := string(b)
140	expected := "stuff\n"
141	if actual != expected {
142		t.Fatalf("expected %q, but got %q", expected, actual)
143	}
144}
145
146func TestVerbose(t *testing.T) {
147	stderr := &bytes.Buffer{}
148	stdout := &bytes.Buffer{}
149	inv := Invocation{
150		Dir:    "./testdata",
151		Stdout: stdout,
152		Stderr: stderr,
153		Args:   []string{"testverbose"},
154	}
155
156	code := Invoke(inv)
157	if code != 0 {
158		t.Errorf("expected to exit with code 0, but got %v", code)
159	}
160	actual := stdout.String()
161	expected := ""
162	if actual != expected {
163		t.Fatalf("expected %q, but got %q", expected, actual)
164	}
165	stderr.Reset()
166	stdout.Reset()
167	inv.Verbose = true
168	code = Invoke(inv)
169	if code != 0 {
170		t.Errorf("expected to exit with code 0, but got %v", code)
171	}
172
173	actual = stderr.String()
174	expected = "Running target: TestVerbose\nhi!\n"
175	if actual != expected {
176		t.Fatalf("expected %q, but got %q", expected, actual)
177	}
178}
179
180func TestVerboseEnv(t *testing.T) {
181	os.Setenv("MAGEFILE_VERBOSE", "true")
182	defer os.Unsetenv("MAGEFILE_VERBOSE")
183	stdout := &bytes.Buffer{}
184	inv, _, err := Parse(ioutil.Discard, stdout, []string{})
185	if err != nil {
186		t.Fatal("unexpected error", err)
187	}
188
189	expected := true
190
191	if inv.Verbose != true {
192		t.Fatalf("expected %t, but got %t ", expected, inv.Verbose)
193	}
194}
195func TestVerboseFalseEnv(t *testing.T) {
196	os.Setenv("MAGEFILE_VERBOSE", "0")
197	defer os.Unsetenv("MAGEFILE_VERBOSE")
198	stdout := &bytes.Buffer{}
199	code := ParseAndRun(ioutil.Discard, stdout, nil, []string{"-d", "testdata", "testverbose"})
200	if code != 0 {
201		t.Fatal("unexpected code", code)
202	}
203
204	if stdout.String() != "" {
205		t.Fatalf("expected no output, but got %s", stdout.String())
206	}
207}
208
209func TestList(t *testing.T) {
210	stdout := &bytes.Buffer{}
211	inv := Invocation{
212		Dir:    "./testdata/list",
213		Stdout: stdout,
214		Stderr: ioutil.Discard,
215		List:   true,
216	}
217
218	code := Invoke(inv)
219	if code != 0 {
220		t.Errorf("expected to exit with code 0, but got %v", code)
221	}
222	actual := stdout.String()
223	expected := `
224This is a comment on the package which should get turned into output with the list of targets.
225
226Targets:
227  somePig*       This is the synopsis for SomePig.
228  testVerbose
229
230* default target
231`[1:]
232
233	if actual != expected {
234		t.Logf("expected: %q", expected)
235		t.Logf("  actual: %q", actual)
236		t.Fatalf("expected:\n%v\n\ngot:\n%v", expected, actual)
237	}
238}
239
240func TestNoArgNoDefaultList(t *testing.T) {
241	stdout := &bytes.Buffer{}
242	stderr := &bytes.Buffer{}
243	inv := Invocation{
244		Dir:    "testdata/no_default",
245		Stdout: stdout,
246		Stderr: stderr,
247	}
248	code := Invoke(inv)
249	if code != 0 {
250		t.Errorf("expected to exit with code 0, but got %v", code)
251	}
252	if err := stderr.String(); err != "" {
253		t.Errorf("unexpected stderr output:\n%s", err)
254	}
255	actual := stdout.String()
256	expected := `
257Targets:
258  bazBuz    Prints out 'BazBuz'.
259  fooBar    Prints out 'FooBar'.
260`[1:]
261	if actual != expected {
262		t.Fatalf("expected:\n%q\n\ngot:\n%q", expected, actual)
263	}
264}
265
266func TestIgnoreDefault(t *testing.T) {
267	stdout := &bytes.Buffer{}
268	stderr := &bytes.Buffer{}
269	inv := Invocation{
270		Dir:    "./testdata/list",
271		Stdout: stdout,
272		Stderr: stderr,
273	}
274	defer os.Setenv(mg.IgnoreDefaultEnv, os.Getenv(mg.IgnoreDefaultEnv))
275	if err := os.Setenv(mg.IgnoreDefaultEnv, "1"); err != nil {
276		t.Fatal(err)
277	}
278
279	code := Invoke(inv)
280	if code != 0 {
281		t.Errorf("expected to exit with code 0, but got %v, stderr:\n%s", code, stderr)
282	}
283	actual := stdout.String()
284	expected := `
285This is a comment on the package which should get turned into output with the list of targets.
286
287Targets:
288  somePig*       This is the synopsis for SomePig.
289  testVerbose
290
291* default target
292`[1:]
293
294	if actual != expected {
295		t.Logf("expected: %q", expected)
296		t.Logf("  actual: %q", actual)
297		t.Fatalf("expected:\n%v\n\ngot:\n%v", expected, actual)
298	}
299}
300
301func TestTargetError(t *testing.T) {
302	stderr := &bytes.Buffer{}
303	inv := Invocation{
304		Dir:    "./testdata",
305		Stdout: ioutil.Discard,
306		Stderr: stderr,
307		Args:   []string{"returnsnonnilerror"},
308	}
309	code := Invoke(inv)
310	if code != 1 {
311		t.Fatalf("expected 1, but got %v", code)
312	}
313	actual := stderr.String()
314	expected := "Error: bang!\n"
315	if actual != expected {
316		t.Fatalf("expected %q, but got %q", expected, actual)
317	}
318}
319
320func TestStdinCopy(t *testing.T) {
321	stdout := &bytes.Buffer{}
322	stdin := strings.NewReader("hi!")
323	inv := Invocation{
324		Dir:    "./testdata",
325		Stderr: ioutil.Discard,
326		Stdout: stdout,
327		Stdin:  stdin,
328		Args:   []string{"CopyStdin"},
329	}
330	code := Invoke(inv)
331	if code != 0 {
332		t.Fatalf("expected 0, but got %v", code)
333	}
334	actual := stdout.String()
335	expected := "hi!"
336	if actual != expected {
337		t.Fatalf("expected %q, but got %q", expected, actual)
338	}
339}
340
341func TestTargetPanics(t *testing.T) {
342	stderr := &bytes.Buffer{}
343	inv := Invocation{
344		Dir:    "./testdata",
345		Stdout: ioutil.Discard,
346		Stderr: stderr,
347		Args:   []string{"panics"},
348	}
349	code := Invoke(inv)
350	if code != 1 {
351		t.Fatalf("expected 1, but got %v", code)
352	}
353	actual := stderr.String()
354	expected := "Error: boom!\n"
355	if actual != expected {
356		t.Fatalf("expected %q, but got %q", expected, actual)
357	}
358}
359
360func TestPanicsErr(t *testing.T) {
361	stderr := &bytes.Buffer{}
362	inv := Invocation{
363		Dir:    "./testdata",
364		Stdout: ioutil.Discard,
365		Stderr: stderr,
366		Args:   []string{"panicserr"},
367	}
368	code := Invoke(inv)
369	if code != 1 {
370		t.Fatalf("expected 1, but got %v", code)
371	}
372	actual := stderr.String()
373	expected := "Error: kaboom!\n"
374	if actual != expected {
375		t.Fatalf("expected %q, but got %q", expected, actual)
376	}
377}
378
379// ensure we include the hash of the mainfile template in determining the
380// executable name to run, so we automatically create a new exe if the template
381// changes.
382func TestHashTemplate(t *testing.T) {
383	templ := tpl
384	defer func() { tpl = templ }()
385	name, err := ExeName("go", mg.CacheDir(), []string{"testdata/func.go", "testdata/command.go"})
386	if err != nil {
387		t.Fatal(err)
388	}
389	tpl = "some other template"
390	changed, err := ExeName("go", mg.CacheDir(), []string{"testdata/func.go", "testdata/command.go"})
391	if err != nil {
392		t.Fatal(err)
393	}
394	if changed == name {
395		t.Fatal("expected executable name to chage if template changed")
396	}
397}
398
399// Test if the -keep flag does keep the mainfile around after running
400func TestKeepFlag(t *testing.T) {
401	buildFile := fmt.Sprintf("./testdata/keep_flag/%s", mainfile)
402	os.Remove(buildFile)
403	defer os.Remove(buildFile)
404	w := tLogWriter{t}
405
406	inv := Invocation{
407		Dir:    "./testdata/keep_flag",
408		Stdout: w,
409		Stderr: w,
410		List:   true,
411		Keep:   true,
412		Force:  true, // need force so we always regenerate
413	}
414	code := Invoke(inv)
415	if code != 0 {
416		t.Fatalf("expected code 0, but got %v", code)
417	}
418
419	if _, err := os.Stat(buildFile); err != nil {
420		t.Fatalf("expected file %q to exist but got err, %v", buildFile, err)
421	}
422}
423
424type tLogWriter struct {
425	*testing.T
426}
427
428func (t tLogWriter) Write(b []byte) (n int, err error) {
429	t.Log(string(b))
430	return len(b), nil
431}
432
433// Test if generated mainfile references anything other than the stdlib
434func TestOnlyStdLib(t *testing.T) {
435	buildFile := fmt.Sprintf("./testdata/onlyStdLib/%s", mainfile)
436	os.Remove(buildFile)
437	defer os.Remove(buildFile)
438
439	w := tLogWriter{t}
440
441	inv := Invocation{
442		Dir:     "./testdata/onlyStdLib",
443		Stdout:  w,
444		Stderr:  w,
445		List:    true,
446		Keep:    true,
447		Force:   true, // need force so we always regenerate
448		Verbose: true,
449	}
450	code := Invoke(inv)
451	if code != 0 {
452		t.Fatalf("expected code 0, but got %v", code)
453	}
454
455	if _, err := os.Stat(buildFile); err != nil {
456		t.Fatalf("expected file %q to exist but got err, %v", buildFile, err)
457	}
458
459	fset := &token.FileSet{}
460	// Parse src but stop after processing the imports.
461	f, err := parser.ParseFile(fset, buildFile, nil, parser.ImportsOnly)
462	if err != nil {
463		fmt.Println(err)
464		return
465	}
466
467	// Print the imports from the file's AST.
468	for _, s := range f.Imports {
469		// the path value comes in as a quoted string, i.e. literally \"context\"
470		path := strings.Trim(s.Path.Value, "\"")
471		pkg, err := build.Default.Import(path, "./testdata/keep_flag", build.FindOnly)
472		if err != nil {
473			t.Fatal(err)
474		}
475		if !filepath.HasPrefix(pkg.Dir, build.Default.GOROOT) {
476			t.Errorf("import of non-stdlib package: %s", s.Path.Value)
477		}
478	}
479}
480
481func TestMultipleTargets(t *testing.T) {
482	var stderr, stdout bytes.Buffer
483	inv := Invocation{
484		Dir:     "./testdata",
485		Stdout:  &stdout,
486		Stderr:  &stderr,
487		Args:    []string{"TestVerbose", "ReturnsNilError"},
488		Verbose: true,
489	}
490	code := Invoke(inv)
491	if code != 0 {
492		t.Errorf("expected 0, but got %v", code)
493	}
494	actual := stderr.String()
495	expected := "Running target: TestVerbose\nhi!\nRunning target: ReturnsNilError\n"
496	if actual != expected {
497		t.Errorf("expected %q, but got %q", expected, actual)
498	}
499	actual = stdout.String()
500	expected = "stuff\n"
501	if actual != expected {
502		t.Errorf("expected %q, but got %q", expected, actual)
503	}
504}
505
506func TestFirstTargetFails(t *testing.T) {
507	var stderr, stdout bytes.Buffer
508	inv := Invocation{
509		Dir:     "./testdata",
510		Stdout:  &stdout,
511		Stderr:  &stderr,
512		Args:    []string{"ReturnsNonNilError", "ReturnsNilError"},
513		Verbose: true,
514	}
515	code := Invoke(inv)
516	if code != 1 {
517		t.Errorf("expected 1, but got %v", code)
518	}
519	actual := stderr.String()
520	expected := "Running target: ReturnsNonNilError\nError: bang!\n"
521	if actual != expected {
522		t.Errorf("expected %q, but got %q", expected, actual)
523	}
524	actual = stdout.String()
525	expected = ""
526	if actual != expected {
527		t.Errorf("expected %q, but got %q", expected, actual)
528	}
529}
530
531func TestBadSecondTargets(t *testing.T) {
532	var stderr, stdout bytes.Buffer
533	inv := Invocation{
534		Dir:    "./testdata",
535		Stdout: &stdout,
536		Stderr: &stderr,
537		Args:   []string{"TestVerbose", "NotGonnaWork"},
538	}
539	code := Invoke(inv)
540	if code != 2 {
541		t.Errorf("expected 0, but got %v", code)
542	}
543	actual := stderr.String()
544	expected := "Unknown target specified: NotGonnaWork\n"
545	if actual != expected {
546		t.Errorf("expected %q, but got %q", expected, actual)
547	}
548	actual = stdout.String()
549	expected = ""
550	if actual != expected {
551		t.Errorf("expected %q, but got %q", expected, actual)
552	}
553}
554
555func TestParse(t *testing.T) {
556	buf := &bytes.Buffer{}
557	inv, cmd, err := Parse(ioutil.Discard, buf, []string{"-v", "-debug", "-gocmd=foo", "-d", "dir", "build", "deploy"})
558	if err != nil {
559		t.Fatal("unexpected error", err)
560	}
561	if cmd == Init {
562		t.Error("init should be false but was true")
563	}
564	if cmd == Version {
565		t.Error("showVersion should be false but was true")
566	}
567	if inv.Debug != true {
568		t.Error("debug should be true")
569	}
570	if inv.Dir != "dir" {
571		t.Errorf("Expected dir to be \"dir\" but was %q", inv.Dir)
572	}
573	if inv.GoCmd != "foo" {
574		t.Errorf("Expected gocmd to be \"foo\" but was %q", inv.GoCmd)
575	}
576	expected := []string{"build", "deploy"}
577	if !reflect.DeepEqual(inv.Args, expected) {
578		t.Fatalf("expected args to be %q but got %q", expected, inv.Args)
579	}
580	if s := buf.String(); s != "" {
581		t.Fatalf("expected no stdout output but got %q", s)
582	}
583
584}
585
586func TestSetDir(t *testing.T) {
587	stdout := &bytes.Buffer{}
588	stderr := &bytes.Buffer{}
589	code := Invoke(Invocation{
590		Dir:    "testdata/setdir",
591		Stdout: stdout,
592		Stderr: stderr,
593		Args:   []string{"TestCurrentDir"},
594	})
595	if code != 0 {
596		t.Errorf("expected code 0, but got %d. Stdout:\n%s\nStderr:\n%s", code, stdout, stderr)
597	}
598	expected := "setdir.go\n"
599	if out := stdout.String(); out != expected {
600		t.Fatalf("expected list of files to be %q, but was %q", expected, out)
601	}
602}
603
604// Test the timeout option
605func TestTimeout(t *testing.T) {
606	stderr := &bytes.Buffer{}
607	stdout := &bytes.Buffer{}
608	inv := Invocation{
609		Dir:     "testdata/context",
610		Stdout:  stdout,
611		Stderr:  stderr,
612		Args:    []string{"timeout"},
613		Timeout: time.Duration(100 * time.Millisecond),
614	}
615	code := Invoke(inv)
616	if code != 1 {
617		t.Fatalf("expected 1, but got %v, stderr: %q, stdout: %q", code, stderr, stdout)
618	}
619	actual := stderr.String()
620	expected := "Error: context deadline exceeded\n"
621
622	if actual != expected {
623		t.Fatalf("expected %q, but got %q", expected, actual)
624	}
625}
626func TestParseHelp(t *testing.T) {
627	buf := &bytes.Buffer{}
628	_, _, err := Parse(ioutil.Discard, buf, []string{"-h"})
629	if err != flag.ErrHelp {
630		t.Fatal("unexpected error", err)
631	}
632	buf2 := &bytes.Buffer{}
633	_, _, err = Parse(ioutil.Discard, buf2, []string{"--help"})
634	if err != flag.ErrHelp {
635		t.Fatal("unexpected error", err)
636	}
637	s := buf.String()
638	s2 := buf2.String()
639	if s != s2 {
640		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)
641	}
642}
643
644func TestHelpTarget(t *testing.T) {
645	stdout := &bytes.Buffer{}
646	inv := Invocation{
647		Dir:    "./testdata",
648		Stdout: stdout,
649		Stderr: ioutil.Discard,
650		Args:   []string{"panics"},
651		Help:   true,
652	}
653	code := Invoke(inv)
654	if code != 0 {
655		t.Errorf("expected to exit with code 0, but got %v", code)
656	}
657	actual := stdout.String()
658	expected := "mage panics:\n\nFunction that panics.\n\n"
659	if actual != expected {
660		t.Fatalf("expected %q, but got %q", expected, actual)
661	}
662}
663
664func TestHelpAlias(t *testing.T) {
665	stdout := &bytes.Buffer{}
666	inv := Invocation{
667		Dir:    "./testdata/alias",
668		Stdout: stdout,
669		Stderr: ioutil.Discard,
670		Args:   []string{"status"},
671		Help:   true,
672	}
673	code := Invoke(inv)
674	if code != 0 {
675		t.Errorf("expected to exit with code 0, but got %v", code)
676	}
677	actual := stdout.String()
678	expected := "mage status:\n\nPrints status.\n\nAliases: st, stat\n\n"
679	if actual != expected {
680		t.Fatalf("expected %q, but got %q", expected, actual)
681	}
682	inv = Invocation{
683		Dir:    "./testdata/alias",
684		Stdout: stdout,
685		Stderr: ioutil.Discard,
686		Args:   []string{"checkout"},
687		Help:   true,
688	}
689	stdout.Reset()
690	code = Invoke(inv)
691	if code != 0 {
692		t.Errorf("expected to exit with code 0, but got %v", code)
693	}
694	actual = stdout.String()
695	expected = "mage checkout:\n\nAliases: co\n\n"
696	if actual != expected {
697		t.Fatalf("expected %q, but got %q", expected, actual)
698	}
699}
700
701func TestAlias(t *testing.T) {
702	stdout := &bytes.Buffer{}
703	stderr := &bytes.Buffer{}
704	debug.SetOutput(stderr)
705	inv := Invocation{
706		Dir:    "testdata/alias",
707		Stdout: stdout,
708		Stderr: ioutil.Discard,
709		Args:   []string{"status"},
710		Debug:  true,
711	}
712	code := Invoke(inv)
713	if code != 0 {
714		t.Errorf("expected to exit with code 0, but got %v\noutput:\n%s\nstderr:\n%s", code, stdout, stderr)
715	}
716	actual := stdout.String()
717	expected := "alias!\n"
718	if actual != expected {
719		t.Fatalf("expected %q, but got %q", expected, actual)
720	}
721	stdout.Reset()
722	inv.Args = []string{"st"}
723	code = Invoke(inv)
724	if code != 0 {
725		t.Errorf("expected to exit with code 0, but got %v", code)
726	}
727	actual = stdout.String()
728	if actual != expected {
729		t.Fatalf("expected %q, but got %q", expected, actual)
730	}
731}
732
733func TestInvalidAlias(t *testing.T) {
734	stderr := &bytes.Buffer{}
735	log.SetOutput(ioutil.Discard)
736	inv := Invocation{
737		Dir:    "./testdata/invalid_alias",
738		Stdout: ioutil.Discard,
739		Stderr: stderr,
740		Args:   []string{"co"},
741	}
742	code := Invoke(inv)
743	if code != 1 {
744		t.Errorf("expected to exit with code 1, but got %v", code)
745	}
746	actual := stderr.String()
747	expected := "Unknown target: \"co\"\n"
748	if actual != expected {
749		t.Fatalf("expected %q, but got %q", expected, actual)
750	}
751}
752
753func TestRunCompiledPrintsError(t *testing.T) {
754	stderr := &bytes.Buffer{}
755	logger := log.New(stderr, "", 0)
756	code := RunCompiled(Invocation{}, "thiswon'texist", logger)
757	if code != 1 {
758		t.Errorf("expected code 1 but got %v", code)
759	}
760
761	if strings.TrimSpace(stderr.String()) == "" {
762		t.Fatal("expected to get output to stderr when a run fails, but got nothing.")
763	}
764}
765
766func TestClean(t *testing.T) {
767	if err := os.RemoveAll(mg.CacheDir()); err != nil {
768		t.Error("error removing cache dir:", err)
769	}
770	code := ParseAndRun(ioutil.Discard, ioutil.Discard, &bytes.Buffer{}, []string{"-clean"})
771	if code != 0 {
772		t.Errorf("expected 0, but got %v", code)
773	}
774
775	TestAlias(t) // make sure we've got something in the CACHE_DIR
776	files, err := ioutil.ReadDir(mg.CacheDir())
777	if err != nil {
778		t.Error("issue reading file:", err)
779	}
780
781	if len(files) < 1 {
782		t.Error("Need at least 1 cached binaries to test --clean")
783	}
784
785	_, cmd, err := Parse(ioutil.Discard, ioutil.Discard, []string{"-clean"})
786	if err != nil {
787		t.Fatal(err)
788	}
789	if cmd != Clean {
790		t.Errorf("Expected 'clean' command but got %v", cmd)
791	}
792	buf := &bytes.Buffer{}
793	code = ParseAndRun(ioutil.Discard, buf, &bytes.Buffer{}, []string{"-clean"})
794	if code != 0 {
795		t.Fatalf("expected 0, but got %v: %s", code, buf)
796	}
797
798	infos, err := ioutil.ReadDir(mg.CacheDir())
799	if err != nil {
800		t.Fatal(err)
801	}
802
803	var names []string
804	for _, i := range infos {
805		if !i.IsDir() {
806			names = append(names, i.Name())
807		}
808	}
809
810	if len(names) != 0 {
811		t.Errorf("expected '-clean' to remove files from CACHE_DIR, but still have %v", names)
812	}
813}
814
815func TestGoCmd(t *testing.T) {
816	textOutput := "TestGoCmd"
817	if err := os.Setenv(testExeEnv, textOutput); err != nil {
818		t.Fatal(err)
819	}
820	defer os.Unsetenv(testExeEnv)
821
822	// fake out the compiled file, since the code checks for it.
823	f, err := ioutil.TempFile("", "")
824	if err != nil {
825		t.Fatal(err)
826	}
827	name := f.Name()
828	dir := filepath.Dir(name)
829	defer os.Remove(name)
830	f.Close()
831
832	buf := &bytes.Buffer{}
833	stderr := &bytes.Buffer{}
834	if err := Compile(dir, os.Args[0], name, []string{}, false, stderr, buf); err != nil {
835		t.Log("stderr: ", stderr.String())
836		t.Fatal(err)
837	}
838	if buf.String() != textOutput {
839		t.Fatalf("We didn't run the custom go cmd. Expected output %q, but got %q", textOutput, buf)
840	}
841}
842
843var runtimeVer = regexp.MustCompile(`go1\.([0-9]+)`)
844
845func TestGoModules(t *testing.T) {
846	matches := runtimeVer.FindStringSubmatch(runtime.Version())
847	if len(matches) < 2 || minorVer(t, matches[1]) < 11 {
848		t.Skipf("Skipping Go modules test because go version %q is less than go1.11", runtime.Version())
849	}
850	dir, err := ioutil.TempDir("", "")
851	if err != nil {
852		t.Fatal(err)
853	}
854	defer os.RemoveAll(dir)
855	err = ioutil.WriteFile(filepath.Join(dir, "magefile.go"), []byte(`//+build mage
856
857package main
858
859import "golang.org/x/text/unicode/norm"
860
861func Test() {
862	print("unicode version: " + norm.Version)
863}
864`), 0600)
865	if err != nil {
866		t.Fatal(err)
867	}
868
869	stdout := &bytes.Buffer{}
870	stderr := &bytes.Buffer{}
871	cmd := exec.Command("go", "mod", "init", "app")
872	cmd.Dir = dir
873	cmd.Env = os.Environ()
874	cmd.Stderr = stderr
875	cmd.Stdout = stdout
876	if err := cmd.Run(); err != nil {
877		t.Fatalf("Error running go mod init: %v\nStdout: %s\nStderr: %s", err, stdout, stderr)
878	}
879	stderr.Reset()
880	stdout.Reset()
881	code := Invoke(Invocation{
882		Dir:    dir,
883		Stderr: stderr,
884		Stdout: stdout,
885	})
886	if code != 0 {
887		t.Fatalf("exited with code %d. \nStdout: %s\nStderr: %s", code, stdout, stderr)
888	}
889	expected := `
890Targets:
891  test
892`[1:]
893	if output := stdout.String(); output != expected {
894		t.Fatalf("expected output %q, but got %q", expected, output)
895	}
896}
897
898func minorVer(t *testing.T, v string) int {
899	a, err := strconv.Atoi(v)
900	if err != nil {
901		t.Fatal("unexpected non-numeric version", v)
902	}
903	return a
904}
905
906func TestNamespaceDep(t *testing.T) {
907	stdout := &bytes.Buffer{}
908	stderr := &bytes.Buffer{}
909	inv := Invocation{
910		Dir:    "./testdata/namespaces",
911		Stderr: stderr,
912		Stdout: stdout,
913		Args:   []string{"TestNamespaceDep"},
914	}
915	code := Invoke(inv)
916	if code != 0 {
917		t.Fatalf("expected 0, but got %v, stderr:\n%s", code, stderr)
918	}
919	expected := "hi!\n"
920	if stdout.String() != expected {
921		t.Fatalf("expected %q, but got %q", expected, stdout.String())
922	}
923}
924
925func TestNamespace(t *testing.T) {
926	stdout := &bytes.Buffer{}
927	inv := Invocation{
928		Dir:    "./testdata/namespaces",
929		Stderr: ioutil.Discard,
930		Stdout: stdout,
931		Args:   []string{"ns:error"},
932	}
933	code := Invoke(inv)
934	if code != 0 {
935		t.Fatalf("expected 0, but got %v", code)
936	}
937	expected := "hi!\n"
938	if stdout.String() != expected {
939		t.Fatalf("expected %q, but got %q", expected, stdout.String())
940	}
941}
942