github.com/amane3/goreleaser@v0.182.0/internal/pipe/build/build_test.go (about)

     1  package build
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"testing"
    10  
    11  	"github.com/amane3/goreleaser/internal/artifact"
    12  	"github.com/amane3/goreleaser/internal/testlib"
    13  	"github.com/amane3/goreleaser/internal/tmpl"
    14  	api "github.com/amane3/goreleaser/pkg/build"
    15  	"github.com/amane3/goreleaser/pkg/config"
    16  	"github.com/amane3/goreleaser/pkg/context"
    17  	"github.com/stretchr/testify/require"
    18  )
    19  
    20  var errFailedBuild = errors.New("fake builder failed")
    21  var errFailedDefault = errors.New("fake builder defaults failed")
    22  
    23  type fakeBuilder struct {
    24  	fail        bool
    25  	failDefault bool
    26  }
    27  
    28  func (f *fakeBuilder) WithDefaults(build config.Build) (config.Build, error) {
    29  	if f.failDefault {
    30  		return build, errFailedDefault
    31  	}
    32  	return build, nil
    33  }
    34  
    35  func (f *fakeBuilder) Build(ctx *context.Context, build config.Build, options api.Options) error {
    36  	if f.fail {
    37  		return errFailedBuild
    38  	}
    39  	if err := os.MkdirAll(filepath.Dir(options.Path), 0755); err != nil {
    40  		return err
    41  	}
    42  	if err := ioutil.WriteFile(options.Path, []byte("foo"), 0755); err != nil {
    43  		return err
    44  	}
    45  	ctx.Artifacts.Add(&artifact.Artifact{
    46  		Name: options.Name,
    47  	})
    48  	return nil
    49  }
    50  
    51  func init() {
    52  	api.Register("fake", &fakeBuilder{})
    53  	api.Register("fakeFail", &fakeBuilder{
    54  		fail: true,
    55  	})
    56  	api.Register("fakeFailDefault", &fakeBuilder{
    57  		failDefault: true,
    58  	})
    59  }
    60  
    61  func TestPipeDescription(t *testing.T) {
    62  	require.NotEmpty(t, Pipe{}.String())
    63  }
    64  
    65  func TestBuild(t *testing.T) {
    66  	var folder = testlib.Mktmp(t)
    67  	var config = config.Project{
    68  		Dist: folder,
    69  		Builds: []config.Build{
    70  			{
    71  				Lang:   "fake",
    72  				Binary: "testing.v{{.Version}}",
    73  				Flags:  []string{"-n"},
    74  				Env:    []string{"BLAH=1"},
    75  			},
    76  		},
    77  	}
    78  	var ctx = &context.Context{
    79  		Artifacts: artifact.New(),
    80  		Git: context.GitInfo{
    81  			CurrentTag: "v1.2.3",
    82  			Commit:     "123",
    83  		},
    84  		Version: "1.2.3",
    85  		Config:  config,
    86  	}
    87  	opts, err := buildOptionsForTarget(ctx, ctx.Config.Builds[0], "darwin_amd64")
    88  	require.NoError(t, err)
    89  	error := doBuild(ctx, ctx.Config.Builds[0], *opts)
    90  	require.NoError(t, error)
    91  }
    92  
    93  func TestRunPipe(t *testing.T) {
    94  	var folder = testlib.Mktmp(t)
    95  	var config = config.Project{
    96  		Dist: folder,
    97  		Builds: []config.Build{
    98  			{
    99  				Lang:    "fake",
   100  				Binary:  "testing",
   101  				Flags:   []string{"-v"},
   102  				Ldflags: []string{"-X main.test=testing"},
   103  				Targets: []string{"whatever"},
   104  			},
   105  		},
   106  	}
   107  	var ctx = context.New(config)
   108  	ctx.Git.CurrentTag = "2.4.5"
   109  	require.NoError(t, Pipe{}.Run(ctx))
   110  	require.Equal(t, ctx.Artifacts.List(), []*artifact.Artifact{{
   111  		Name: "testing",
   112  	}})
   113  }
   114  
   115  func TestRunFullPipe(t *testing.T) {
   116  	var folder = testlib.Mktmp(t)
   117  	var pre = filepath.Join(folder, "pre")
   118  	var post = filepath.Join(folder, "post")
   119  	var config = config.Project{
   120  		Builds: []config.Build{
   121  			{
   122  				ID:      "build1",
   123  				Lang:    "fake",
   124  				Binary:  "testing",
   125  				Flags:   []string{"-v"},
   126  				Ldflags: []string{"-X main.test=testing"},
   127  				Hooks: config.HookConfig{
   128  					Pre: []config.BuildHook{
   129  						{Cmd: "touch " + pre},
   130  					},
   131  					Post: []config.BuildHook{
   132  						{Cmd: "touch " + post},
   133  					},
   134  				},
   135  				Targets: []string{"whatever"},
   136  			},
   137  		},
   138  		Dist: folder,
   139  	}
   140  	var ctx = context.New(config)
   141  	ctx.Git.CurrentTag = "2.4.5"
   142  	require.NoError(t, Pipe{}.Run(ctx))
   143  	require.Equal(t, ctx.Artifacts.List(), []*artifact.Artifact{{
   144  		Name: "testing",
   145  	}})
   146  	require.FileExists(t, post)
   147  	require.FileExists(t, pre)
   148  	require.FileExists(t, filepath.Join(folder, "build1_whatever", "testing"))
   149  }
   150  
   151  func TestRunFullPipeFail(t *testing.T) {
   152  	var folder = testlib.Mktmp(t)
   153  	var pre = filepath.Join(folder, "pre")
   154  	var post = filepath.Join(folder, "post")
   155  	var config = config.Project{
   156  		Dist: folder,
   157  		Builds: []config.Build{
   158  			{
   159  				Lang:    "fakeFail",
   160  				Binary:  "testing",
   161  				Flags:   []string{"-v"},
   162  				Ldflags: []string{"-X main.test=testing"},
   163  				Hooks: config.HookConfig{
   164  					Pre: []config.BuildHook{
   165  						{Cmd: "touch " + pre},
   166  					},
   167  					Post: []config.BuildHook{
   168  						{Cmd: "touch " + post},
   169  					},
   170  				},
   171  				Targets: []string{"whatever"},
   172  			},
   173  		},
   174  	}
   175  	var ctx = context.New(config)
   176  	ctx.Git.CurrentTag = "2.4.5"
   177  	require.EqualError(t, Pipe{}.Run(ctx), errFailedBuild.Error())
   178  	require.Empty(t, ctx.Artifacts.List())
   179  	require.FileExists(t, pre)
   180  }
   181  
   182  func TestRunPipeFailingHooks(t *testing.T) {
   183  	var folder = testlib.Mktmp(t)
   184  	var cfg = config.Project{
   185  		Dist: folder,
   186  		Builds: []config.Build{
   187  			{
   188  				Lang:    "fake",
   189  				Binary:  "hooks",
   190  				Hooks:   config.HookConfig{},
   191  				Targets: []string{"whatever"},
   192  			},
   193  		},
   194  	}
   195  	t.Run("pre-hook", func(t *testing.T) {
   196  		var ctx = context.New(cfg)
   197  		ctx.Git.CurrentTag = "2.3.4"
   198  		ctx.Config.Builds[0].Hooks.Pre = []config.BuildHook{{Cmd: "exit 1"}}
   199  		ctx.Config.Builds[0].Hooks.Post = []config.BuildHook{{Cmd: "echo post"}}
   200  		require.EqualError(t, Pipe{}.Run(ctx), `pre hook failed: "": exec: "exit": executable file not found in $PATH`)
   201  	})
   202  	t.Run("post-hook", func(t *testing.T) {
   203  		var ctx = context.New(cfg)
   204  		ctx.Git.CurrentTag = "2.3.4"
   205  		ctx.Config.Builds[0].Hooks.Pre = []config.BuildHook{{Cmd: "echo pre"}}
   206  		ctx.Config.Builds[0].Hooks.Post = []config.BuildHook{{Cmd: "exit 1"}}
   207  		require.EqualError(t, Pipe{}.Run(ctx), `post hook failed: "": exec: "exit": executable file not found in $PATH`)
   208  	})
   209  }
   210  
   211  func TestDefaultNoBuilds(t *testing.T) {
   212  	var ctx = &context.Context{
   213  		Config: config.Project{},
   214  	}
   215  	require.NoError(t, Pipe{}.Default(ctx))
   216  }
   217  
   218  func TestDefaultFail(t *testing.T) {
   219  	var folder = testlib.Mktmp(t)
   220  	var config = config.Project{
   221  		Dist: folder,
   222  		Builds: []config.Build{
   223  			{
   224  				Lang: "fakeFailDefault",
   225  			},
   226  		},
   227  	}
   228  	var ctx = context.New(config)
   229  	require.EqualError(t, Pipe{}.Default(ctx), errFailedDefault.Error())
   230  	require.Empty(t, ctx.Artifacts.List())
   231  }
   232  
   233  func TestDefaultExpandEnv(t *testing.T) {
   234  	require.NoError(t, os.Setenv("XBAR", "FOOBAR"))
   235  	var ctx = &context.Context{
   236  		Config: config.Project{
   237  			Builds: []config.Build{
   238  				{
   239  					Env: []string{
   240  						"XFOO=bar_$XBAR",
   241  					},
   242  				},
   243  			},
   244  		},
   245  	}
   246  	require.NoError(t, Pipe{}.Default(ctx))
   247  	var env = ctx.Config.Builds[0].Env[0]
   248  	require.Equal(t, "XFOO=bar_FOOBAR", env)
   249  }
   250  
   251  func TestDefaultEmptyBuild(t *testing.T) {
   252  	var ctx = &context.Context{
   253  		Config: config.Project{
   254  			ProjectName: "foo",
   255  			Builds: []config.Build{
   256  				{},
   257  			},
   258  		},
   259  	}
   260  	require.NoError(t, Pipe{}.Default(ctx))
   261  	var build = ctx.Config.Builds[0]
   262  	require.Equal(t, ctx.Config.ProjectName, build.ID)
   263  	require.Equal(t, ctx.Config.ProjectName, build.Binary)
   264  	require.Equal(t, ".", build.Dir)
   265  	require.Equal(t, ".", build.Main)
   266  	require.Equal(t, []string{"linux", "darwin"}, build.Goos)
   267  	require.Equal(t, []string{"amd64", "386"}, build.Goarch)
   268  	require.Equal(t, []string{"6"}, build.Goarm)
   269  	require.Len(t, build.Ldflags, 1)
   270  	require.Equal(t, "-s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} -X main.builtBy=goreleaser", build.Ldflags[0])
   271  }
   272  
   273  func TestDefaultBuildID(t *testing.T) {
   274  	var ctx = &context.Context{
   275  		Config: config.Project{
   276  			ProjectName: "foo",
   277  			Builds: []config.Build{
   278  				{
   279  					Binary: "{{.Env.FOO}}",
   280  				},
   281  				{
   282  					Binary: "bar",
   283  				},
   284  			},
   285  		},
   286  	}
   287  	require.EqualError(t, Pipe{}.Default(ctx), "found 2 builds with the ID 'foo', please fix your config")
   288  	var build = ctx.Config.Builds[0]
   289  	require.Equal(t, ctx.Config.ProjectName, build.ID)
   290  }
   291  
   292  func TestSeveralBuildsWithTheSameID(t *testing.T) {
   293  	var ctx = &context.Context{
   294  		Config: config.Project{
   295  			Builds: []config.Build{
   296  				{
   297  					ID:     "a",
   298  					Binary: "bar",
   299  				},
   300  				{
   301  					ID:     "a",
   302  					Binary: "foo",
   303  				},
   304  			},
   305  		},
   306  	}
   307  	require.EqualError(t, Pipe{}.Default(ctx), "found 2 builds with the ID 'a', please fix your config")
   308  }
   309  
   310  func TestDefaultPartialBuilds(t *testing.T) {
   311  	var ctx = &context.Context{
   312  		Config: config.Project{
   313  			Builds: []config.Build{
   314  				{
   315  					ID:     "build1",
   316  					Binary: "bar",
   317  					Goos:   []string{"linux"},
   318  					Main:   "./cmd/main.go",
   319  				},
   320  				{
   321  					ID:      "build2",
   322  					Binary:  "foo",
   323  					Dir:     "baz",
   324  					Ldflags: []string{"-s -w"},
   325  					Goarch:  []string{"386"},
   326  				},
   327  			},
   328  		},
   329  	}
   330  	require.NoError(t, Pipe{}.Default(ctx))
   331  	t.Run("build0", func(t *testing.T) {
   332  		var build = ctx.Config.Builds[0]
   333  		require.Equal(t, "bar", build.Binary)
   334  		require.Equal(t, ".", build.Dir)
   335  		require.Equal(t, "./cmd/main.go", build.Main)
   336  		require.Equal(t, []string{"linux"}, build.Goos)
   337  		require.Equal(t, []string{"amd64", "386"}, build.Goarch)
   338  		require.Equal(t, []string{"6"}, build.Goarm)
   339  		require.Len(t, build.Ldflags, 1)
   340  		require.Equal(t, "-s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} -X main.builtBy=goreleaser", build.Ldflags[0])
   341  	})
   342  	t.Run("build1", func(t *testing.T) {
   343  		var build = ctx.Config.Builds[1]
   344  		require.Equal(t, "foo", build.Binary)
   345  		require.Equal(t, ".", build.Main)
   346  		require.Equal(t, "baz", build.Dir)
   347  		require.Equal(t, []string{"linux", "darwin"}, build.Goos)
   348  		require.Equal(t, []string{"386"}, build.Goarch)
   349  		require.Equal(t, []string{"6"}, build.Goarm)
   350  		require.Len(t, build.Ldflags, 1)
   351  		require.Equal(t, "-s -w", build.Ldflags[0])
   352  	})
   353  }
   354  
   355  func TestDefaultFillSingleBuild(t *testing.T) {
   356  	testlib.Mktmp(t)
   357  
   358  	var ctx = &context.Context{
   359  		Config: config.Project{
   360  			ProjectName: "foo",
   361  			SingleBuild: config.Build{
   362  				Main: "testreleaser",
   363  			},
   364  		},
   365  	}
   366  	require.NoError(t, Pipe{}.Default(ctx))
   367  	require.Len(t, ctx.Config.Builds, 1)
   368  	require.Equal(t, ctx.Config.Builds[0].Binary, "foo")
   369  }
   370  
   371  func TestDefaultFailSingleBuild(t *testing.T) {
   372  	var folder = testlib.Mktmp(t)
   373  	var config = config.Project{
   374  		Dist: folder,
   375  		SingleBuild: config.Build{
   376  			Lang: "fakeFailDefault",
   377  		},
   378  	}
   379  	var ctx = context.New(config)
   380  	require.EqualError(t, Pipe{}.Default(ctx), errFailedDefault.Error())
   381  	require.Empty(t, ctx.Artifacts.List())
   382  }
   383  
   384  func TestSkipBuild(t *testing.T) {
   385  	var folder = testlib.Mktmp(t)
   386  	var config = config.Project{
   387  		Dist: folder,
   388  		Builds: []config.Build{
   389  			{
   390  				Skip: true,
   391  			},
   392  		},
   393  	}
   394  	var ctx = context.New(config)
   395  	ctx.Git.CurrentTag = "2.4.5"
   396  	require.NoError(t, Pipe{}.Run(ctx))
   397  	require.Len(t, ctx.Artifacts.List(), 0)
   398  }
   399  
   400  func TestExtWindows(t *testing.T) {
   401  	require.Equal(t, ".exe", extFor("windows_amd64", config.FlagArray{}))
   402  	require.Equal(t, ".exe", extFor("windows_386", config.FlagArray{}))
   403  	require.Equal(t, ".exe", extFor("windows_amd64", config.FlagArray{"-tags=dev", "-v"}))
   404  	require.Equal(t, ".dll", extFor("windows_amd64", config.FlagArray{"-tags=dev", "-v", "-buildmode=c-shared"}))
   405  	require.Equal(t, ".dll", extFor("windows_386", config.FlagArray{"-buildmode=c-shared"}))
   406  	require.Equal(t, ".lib", extFor("windows_amd64", config.FlagArray{"-buildmode=c-archive"}))
   407  	require.Equal(t, ".lib", extFor("windows_386", config.FlagArray{"-tags=dev", "-v", "-buildmode=c-archive"}))
   408  }
   409  
   410  func TestExtWasm(t *testing.T) {
   411  	require.Equal(t, ".wasm", extFor("js_wasm", config.FlagArray{}))
   412  }
   413  
   414  func TestExtOthers(t *testing.T) {
   415  	require.Empty(t, "", extFor("linux_amd64", config.FlagArray{}))
   416  	require.Empty(t, "", extFor("linuxwin_386", config.FlagArray{}))
   417  	require.Empty(t, "", extFor("winasdasd_sad", config.FlagArray{}))
   418  }
   419  
   420  func TestTemplate(t *testing.T) {
   421  	var ctx = context.New(config.Project{})
   422  	ctx.Git = context.GitInfo{
   423  		CurrentTag: "v1.2.3",
   424  		Commit:     "123",
   425  	}
   426  	ctx.Version = "1.2.3"
   427  	ctx.Env = map[string]string{"FOO": "123"}
   428  	binary, err := tmpl.New(ctx).
   429  		Apply(`-s -w -X main.version={{.Version}} -X main.tag={{.Tag}} -X main.date={{.Date}} -X main.commit={{.Commit}} -X "main.foo={{.Env.FOO}}"`)
   430  	require.NoError(t, err)
   431  	require.Contains(t, binary, "-s -w")
   432  	require.Contains(t, binary, "-X main.version=1.2.3")
   433  	require.Contains(t, binary, "-X main.tag=v1.2.3")
   434  	require.Contains(t, binary, "-X main.commit=123")
   435  	require.Contains(t, binary, "-X main.date=")
   436  	require.Contains(t, binary, `-X "main.foo=123"`)
   437  }
   438  
   439  func TestRunHookEnvs(t *testing.T) {
   440  	var tmp = testlib.Mktmp(t)
   441  
   442  	var build = config.Build{
   443  		Env: []string{
   444  			fmt.Sprintf("FOO=%s/foo", tmp),
   445  			fmt.Sprintf("BAR=%s/bar", tmp),
   446  		},
   447  	}
   448  
   449  	var opts = api.Options{
   450  		Name:   "binary-name",
   451  		Path:   "./binary-name",
   452  		Target: "darwin_amd64",
   453  	}
   454  
   455  	simpleHook := func(cmd string) config.BuildHooks {
   456  		return []config.BuildHook{{Cmd: cmd}}
   457  	}
   458  
   459  	t.Run("valid cmd template with ctx env", func(t *testing.T) {
   460  		var err = runHook(context.New(config.Project{
   461  			Builds: []config.Build{
   462  				build,
   463  			},
   464  			Env: []string{
   465  				fmt.Sprintf("CTXFOO=%s/foo", tmp),
   466  			},
   467  		}), opts, []string{}, simpleHook("touch {{ .Env.CTXFOO }}"))
   468  		require.NoError(t, err)
   469  		require.FileExists(t, filepath.Join(tmp, "foo"))
   470  	})
   471  
   472  	t.Run("valid cmd template with build env", func(t *testing.T) {
   473  		var err = runHook(context.New(config.Project{
   474  			Builds: []config.Build{
   475  				build,
   476  			},
   477  		}), opts, build.Env, simpleHook("touch {{ .Env.FOO }}"))
   478  		require.NoError(t, err)
   479  		require.FileExists(t, filepath.Join(tmp, "foo"))
   480  	})
   481  
   482  	t.Run("valid cmd template with hook env", func(t *testing.T) {
   483  		var err = runHook(context.New(config.Project{
   484  			Builds: []config.Build{
   485  				build,
   486  			},
   487  		}), opts, []string{}, []config.BuildHook{{
   488  			Cmd: "touch {{ .Env.HOOK_ONLY_FOO }}",
   489  			Env: []string{
   490  				fmt.Sprintf("HOOK_ONLY_FOO=%s/hook_only", tmp),
   491  			},
   492  		}})
   493  		require.NoError(t, err)
   494  		require.FileExists(t, filepath.Join(tmp, "hook_only"))
   495  	})
   496  
   497  	t.Run("valid cmd template with ctx and build env", func(t *testing.T) {
   498  		var err = runHook(context.New(config.Project{
   499  			Builds: []config.Build{
   500  				build,
   501  			},
   502  			Env: []string{
   503  				fmt.Sprintf("OVER_FOO=%s/ctx_over_build", tmp),
   504  			},
   505  		}), opts, []string{
   506  			fmt.Sprintf("OVER_FOO=%s/build_over_ctx", tmp),
   507  		}, simpleHook("touch {{ .Env.OVER_FOO }}"))
   508  		require.NoError(t, err)
   509  
   510  		require.FileExists(t, filepath.Join(tmp, "build_over_ctx"))
   511  		require.NoFileExists(t, filepath.Join(tmp, "ctx_over_build"))
   512  	})
   513  
   514  	t.Run("valid cmd template with ctx and hook env", func(t *testing.T) {
   515  		var err = runHook(context.New(config.Project{
   516  			Builds: []config.Build{
   517  				build,
   518  			},
   519  			Env: []string{
   520  				fmt.Sprintf("CTX_OR_HOOK_FOO=%s/ctx_over_hook", tmp),
   521  			},
   522  		}), opts, []string{}, []config.BuildHook{{
   523  			Cmd: "touch {{ .Env.CTX_OR_HOOK_FOO }}",
   524  			Env: []string{
   525  				fmt.Sprintf("CTX_OR_HOOK_FOO=%s/hook_over_ctx", tmp),
   526  			},
   527  		}})
   528  		require.NoError(t, err)
   529  		require.FileExists(t, filepath.Join(tmp, "hook_over_ctx"))
   530  		require.NoFileExists(t, filepath.Join(tmp, "ctx_over_hook"))
   531  	})
   532  
   533  	t.Run("valid cmd template with build and hook env", func(t *testing.T) {
   534  		var err = runHook(context.New(config.Project{
   535  			Builds: []config.Build{
   536  				build,
   537  			},
   538  		}), opts, []string{
   539  			fmt.Sprintf("BUILD_OR_HOOK_FOO=%s/build_over_hook", tmp),
   540  		}, []config.BuildHook{{
   541  			Cmd: "touch {{ .Env.BUILD_OR_HOOK_FOO }}",
   542  			Env: []string{
   543  				fmt.Sprintf("BUILD_OR_HOOK_FOO=%s/hook_over_build", tmp),
   544  			},
   545  		}})
   546  		require.NoError(t, err)
   547  		require.FileExists(t, filepath.Join(tmp, "hook_over_build"))
   548  		require.NoFileExists(t, filepath.Join(tmp, "build_over_hook"))
   549  	})
   550  
   551  	t.Run("valid cmd template with ctx, build and hook env", func(t *testing.T) {
   552  		var err = runHook(context.New(config.Project{
   553  			Builds: []config.Build{
   554  				build,
   555  			},
   556  			Env: []string{
   557  				fmt.Sprintf("CTX_OR_BUILD_OR_HOOK_FOO=%s/ctx_wins", tmp),
   558  			},
   559  		}), opts, []string{
   560  			fmt.Sprintf("CTX_OR_BUILD_OR_HOOK_FOO=%s/build_wins", tmp),
   561  		}, []config.BuildHook{{
   562  			Cmd: "touch {{ .Env.CTX_OR_BUILD_OR_HOOK_FOO }}",
   563  			Env: []string{
   564  				fmt.Sprintf("CTX_OR_BUILD_OR_HOOK_FOO=%s/hook_wins", tmp),
   565  			},
   566  		}})
   567  		require.NoError(t, err)
   568  		require.FileExists(t, filepath.Join(tmp, "hook_wins"))
   569  		require.NoFileExists(t, filepath.Join(tmp, "ctx_wins"))
   570  		require.NoFileExists(t, filepath.Join(tmp, "build_wins"))
   571  	})
   572  
   573  	t.Run("invalid cmd template", func(t *testing.T) {
   574  		var err = runHook(context.New(config.Project{
   575  			Builds: []config.Build{
   576  				build,
   577  			},
   578  		}), opts, build.Env, simpleHook("touch {{ .Env.FOOss }}"))
   579  		require.EqualError(t, err, `template: tmpl:1:13: executing "tmpl" at <.Env.FOOss>: map has no entry for key "FOOss"`)
   580  	})
   581  
   582  	t.Run("invalid dir template", func(t *testing.T) {
   583  		var err = runHook(context.New(config.Project{
   584  			Builds: []config.Build{
   585  				build,
   586  			},
   587  		}), opts, build.Env, []config.BuildHook{{
   588  			Cmd: "echo something",
   589  			Dir: "{{ .Env.INVALID_ENV }}",
   590  		}})
   591  		require.EqualError(t, err, `template: tmpl:1:7: executing "tmpl" at <.Env.INVALID_ENV>: map has no entry for key "INVALID_ENV"`)
   592  	})
   593  
   594  	t.Run("invalid hook env template", func(t *testing.T) {
   595  		var err = runHook(context.New(config.Project{
   596  			Builds: []config.Build{
   597  				build,
   598  			},
   599  		}), opts, build.Env, []config.BuildHook{{
   600  			Cmd: "echo something",
   601  			Env: []string{
   602  				"TEST={{ .Env.MISSING_ENV }}",
   603  			},
   604  		}})
   605  		require.EqualError(t, err, `template: tmpl:1:12: executing "tmpl" at <.Env.MISSING_ENV>: map has no entry for key "MISSING_ENV"`)
   606  	})
   607  
   608  	t.Run("build env inside shell", func(t *testing.T) {
   609  		var shell = `#!/bin/sh -e
   610  touch "$BAR"`
   611  		err := ioutil.WriteFile(filepath.Join(tmp, "test.sh"), []byte(shell), 0750)
   612  		require.NoError(t, err)
   613  		err = runHook(context.New(config.Project{
   614  			Builds: []config.Build{
   615  				build,
   616  			},
   617  		}), opts, build.Env, simpleHook("sh test.sh"))
   618  		require.NoError(t, err)
   619  		require.FileExists(t, filepath.Join(tmp, "bar"))
   620  	})
   621  }
   622  
   623  func TestBuild_hooksKnowGoosGoarch(t *testing.T) {
   624  	var tmpDir = testlib.Mktmp(t)
   625  	build := config.Build{
   626  		Lang:   "fake",
   627  		Goarch: []string{"amd64"},
   628  		Goos:   []string{"linux"},
   629  		Binary: "testing-goos-goarch.v{{.Version}}",
   630  		Targets: []string{
   631  			"linux_amd64",
   632  		},
   633  		Hooks: config.HookConfig{
   634  			Pre: []config.BuildHook{
   635  				{Cmd: "touch pre-hook-{{.Arch}}-{{.Os}}", Dir: tmpDir},
   636  			},
   637  			Post: config.BuildHooks{
   638  				{Cmd: "touch post-hook-{{.Arch}}-{{.Os}}", Dir: tmpDir},
   639  			},
   640  		},
   641  	}
   642  
   643  	ctx := context.New(config.Project{
   644  		Builds: []config.Build{
   645  			build,
   646  		},
   647  	})
   648  	err := runPipeOnBuild(ctx, build)
   649  	require.NoError(t, err)
   650  	require.FileExists(t, filepath.Join(tmpDir, "pre-hook-amd64-linux"))
   651  	require.FileExists(t, filepath.Join(tmpDir, "post-hook-amd64-linux"))
   652  }
   653  
   654  func TestPipeOnBuild_hooksRunPerTarget(t *testing.T) {
   655  	var tmpDir = testlib.Mktmp(t)
   656  
   657  	build := config.Build{
   658  		Lang:   "fake",
   659  		Binary: "testing.v{{.Version}}",
   660  		Targets: []string{
   661  			"linux_amd64",
   662  			"darwin_amd64",
   663  			"windows_amd64",
   664  		},
   665  		Hooks: config.HookConfig{
   666  			Pre: []config.BuildHook{
   667  				{Cmd: "touch pre-hook-{{.Target}}", Dir: tmpDir},
   668  			},
   669  			Post: config.BuildHooks{
   670  				{Cmd: "touch post-hook-{{.Target}}", Dir: tmpDir},
   671  			},
   672  		},
   673  	}
   674  	ctx := context.New(config.Project{
   675  		Builds: []config.Build{
   676  			build,
   677  		},
   678  	})
   679  	err := runPipeOnBuild(ctx, build)
   680  	require.NoError(t, err)
   681  	require.FileExists(t, filepath.Join(tmpDir, "pre-hook-linux_amd64"))
   682  	require.FileExists(t, filepath.Join(tmpDir, "pre-hook-darwin_amd64"))
   683  	require.FileExists(t, filepath.Join(tmpDir, "pre-hook-windows_amd64"))
   684  	require.FileExists(t, filepath.Join(tmpDir, "post-hook-linux_amd64"))
   685  	require.FileExists(t, filepath.Join(tmpDir, "post-hook-darwin_amd64"))
   686  	require.FileExists(t, filepath.Join(tmpDir, "post-hook-windows_amd64"))
   687  }
   688  
   689  func TestPipeOnBuild_invalidBinaryTpl(t *testing.T) {
   690  	build := config.Build{
   691  		Lang:   "fake",
   692  		Binary: "testing.v{{.XYZ}}",
   693  		Targets: []string{
   694  			"linux_amd64",
   695  		},
   696  	}
   697  	ctx := context.New(config.Project{
   698  		Builds: []config.Build{
   699  			build,
   700  		},
   701  	})
   702  	err := runPipeOnBuild(ctx, build)
   703  	require.EqualError(t, err, `template: tmpl:1:11: executing "tmpl" at <.XYZ>: map has no entry for key "XYZ"`)
   704  }
   705  
   706  func TestBuildOptionsForTarget(t *testing.T) {
   707  	var tmpDir = testlib.Mktmp(t)
   708  
   709  	build := config.Build{
   710  		ID:     "testid",
   711  		Binary: "testbinary",
   712  		Targets: []string{
   713  			"linux_amd64",
   714  			"darwin_amd64",
   715  			"windows_amd64",
   716  		},
   717  	}
   718  	ctx := context.New(config.Project{
   719  		Dist:   tmpDir,
   720  		Builds: []config.Build{build},
   721  	})
   722  	opts, err := buildOptionsForTarget(ctx, build, "linux_amd64")
   723  	require.NoError(t, err)
   724  	require.Equal(t, &api.Options{
   725  		Name:   "testbinary",
   726  		Path:   filepath.Join(tmpDir, "testid_linux_amd64", "testbinary"),
   727  		Target: "linux_amd64",
   728  		Os:     "linux",
   729  		Arch:   "amd64",
   730  	}, opts)
   731  }
   732  
   733  func TestHookComplex(t *testing.T) {
   734  	var tmp = testlib.Mktmp(t)
   735  
   736  	require.NoError(t, runHook(context.New(config.Project{}), api.Options{}, []string{}, config.BuildHooks{
   737  		{
   738  			Cmd: `bash -c "touch foo"`,
   739  		},
   740  		{
   741  			Cmd: `bash -c "touch \"bar\""`,
   742  		},
   743  	}))
   744  
   745  	require.FileExists(t, filepath.Join(tmp, "foo"))
   746  	require.FileExists(t, filepath.Join(tmp, "bar"))
   747  }
   748  
   749  func TestHookInvalidShelCommand(t *testing.T) {
   750  	require.Error(t, runHook(context.New(config.Project{}), api.Options{}, []string{}, config.BuildHooks{
   751  		{
   752  			Cmd: `bash -c "echo \"unterminated command\"`,
   753  		},
   754  	}))
   755  }
   756  
   757  func TestRunHookFailWithLogs(t *testing.T) {
   758  	var folder = testlib.Mktmp(t)
   759  	var config = config.Project{
   760  		Dist: folder,
   761  		Builds: []config.Build{
   762  			{
   763  				Lang:   "fakeFail",
   764  				Binary: "testing",
   765  				Flags:  []string{"-v"},
   766  				Hooks: config.HookConfig{
   767  					Pre: []config.BuildHook{
   768  						{Cmd: "sh -c 'echo foo; exit 1'"},
   769  					},
   770  				},
   771  				Targets: []string{"whatever"},
   772  			},
   773  		},
   774  	}
   775  	var ctx = context.New(config)
   776  	ctx.Git.CurrentTag = "2.4.5"
   777  	require.EqualError(t, Pipe{}.Run(ctx), "pre hook failed: \"foo\\n\": exit status 1")
   778  	require.Empty(t, ctx.Artifacts.List())
   779  }