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  }