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