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