github.com/ahmet2mir/goreleaser@v0.180.3-0.20210927151101-8e5ee5a9b8c5/internal/builders/golang/build_test.go (about)

     1  package golang
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"os/exec"
     7  	"path/filepath"
     8  	"runtime"
     9  	"strings"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/goreleaser/goreleaser/internal/artifact"
    14  	"github.com/goreleaser/goreleaser/internal/testlib"
    15  	"github.com/goreleaser/goreleaser/internal/tmpl"
    16  	api "github.com/goreleaser/goreleaser/pkg/build"
    17  	"github.com/goreleaser/goreleaser/pkg/config"
    18  	"github.com/goreleaser/goreleaser/pkg/context"
    19  	"github.com/stretchr/testify/require"
    20  )
    21  
    22  var runtimeTarget = runtime.GOOS + "_" + runtime.GOARCH
    23  
    24  func TestWithDefaults(t *testing.T) {
    25  	for name, testcase := range map[string]struct {
    26  		build    config.Build
    27  		targets  []string
    28  		goBinary string
    29  	}{
    30  		"full": {
    31  			build: config.Build{
    32  				ID:     "foo",
    33  				Binary: "foo",
    34  				Goos: []string{
    35  					"linux",
    36  					"windows",
    37  					"darwin",
    38  				},
    39  				Goarch: []string{
    40  					"amd64",
    41  					"arm",
    42  					"mips",
    43  				},
    44  				Goarm: []string{
    45  					"6",
    46  				},
    47  				Gomips: []string{
    48  					"softfloat",
    49  				},
    50  				GoBinary: "go1.2.3",
    51  			},
    52  			targets: []string{
    53  				"linux_amd64",
    54  				"linux_mips_softfloat",
    55  				"darwin_amd64",
    56  				"windows_amd64",
    57  				"windows_arm_6",
    58  				"linux_arm_6",
    59  			},
    60  			goBinary: "go1.2.3",
    61  		},
    62  		"empty": {
    63  			build: config.Build{
    64  				ID:     "foo2",
    65  				Binary: "foo",
    66  			},
    67  			targets: []string{
    68  				"linux_amd64",
    69  				"linux_386",
    70  				"linux_arm64",
    71  				"darwin_amd64",
    72  				"darwin_arm64",
    73  			},
    74  			goBinary: "go",
    75  		},
    76  		"custom targets": {
    77  			build: config.Build{
    78  				ID:     "foo3",
    79  				Binary: "foo",
    80  				Targets: []string{
    81  					"linux_386",
    82  					"darwin_amd64",
    83  				},
    84  			},
    85  			targets: []string{
    86  				"linux_386",
    87  				"darwin_amd64",
    88  			},
    89  			goBinary: "go",
    90  		},
    91  		"empty with custom dir": {
    92  			build: config.Build{
    93  				ID:     "foo2",
    94  				Binary: "foo",
    95  				Dir:    "./testdata",
    96  			},
    97  			targets: []string{
    98  				"linux_amd64",
    99  				"linux_386",
   100  				"linux_arm64",
   101  				"darwin_amd64",
   102  				"darwin_arm64",
   103  			},
   104  			goBinary: "go",
   105  		},
   106  		"empty with custom dir that doest exist": {
   107  			build: config.Build{
   108  				ID:     "foo2",
   109  				Binary: "foo",
   110  				Dir:    "./nope",
   111  			},
   112  			targets: []string{
   113  				"linux_amd64",
   114  				"linux_386",
   115  				"linux_arm64",
   116  				"darwin_amd64",
   117  				"darwin_arm64",
   118  			},
   119  			goBinary: "go",
   120  		},
   121  	} {
   122  		t.Run(name, func(t *testing.T) {
   123  			if testcase.build.GoBinary != "" && testcase.build.GoBinary != "go" {
   124  				createFakeGoBinaryWithVersion(t, testcase.build.GoBinary, "go1.17")
   125  			}
   126  			config := config.Project{
   127  				Builds: []config.Build{
   128  					testcase.build,
   129  				},
   130  			}
   131  			ctx := context.New(config)
   132  			ctx.Git.CurrentTag = "5.6.7"
   133  			build, err := Default.WithDefaults(ctx.Config.Builds[0])
   134  			require.NoError(t, err)
   135  			require.ElementsMatch(t, build.Targets, testcase.targets)
   136  			require.EqualValues(t, testcase.goBinary, build.GoBinary)
   137  		})
   138  	}
   139  }
   140  
   141  // createFakeGoBinaryWithVersion creates a temporary executable with the
   142  // given name, which will output a go version string with the given version.
   143  //  The temporary directory created by this function will be placed in the PATH
   144  // variable for the duration of (and cleaned up at the end of) the
   145  // current test run.
   146  func createFakeGoBinaryWithVersion(tb testing.TB, name, version string) {
   147  	tb.Helper()
   148  	d := tb.TempDir()
   149  
   150  	require.NoError(tb, os.WriteFile(
   151  		filepath.Join(d, name),
   152  		[]byte(fmt.Sprintf("#!/bin/sh\necho %s", version)),
   153  		0o755,
   154  	))
   155  
   156  	currentPath := os.Getenv("PATH")
   157  	tb.Cleanup(func() {
   158  		require.NoError(tb, os.Setenv("PATH", currentPath))
   159  	})
   160  
   161  	path := fmt.Sprintf("%s%c%s", d, os.PathListSeparator, currentPath)
   162  	require.NoError(tb, os.Setenv("PATH", path))
   163  }
   164  
   165  func TestInvalidTargets(t *testing.T) {
   166  	type testcase struct {
   167  		build       config.Build
   168  		expectedErr string
   169  	}
   170  	for s, tc := range map[string]testcase{
   171  		"goos": {
   172  			build: config.Build{
   173  				Goos: []string{"darwin", "darwim"},
   174  			},
   175  			expectedErr: "invalid goos: darwim",
   176  		},
   177  		"goarch": {
   178  			build: config.Build{
   179  				Goarch: []string{"amd64", "i386", "386"},
   180  			},
   181  			expectedErr: "invalid goarch: i386",
   182  		},
   183  		"goarm": {
   184  			build: config.Build{
   185  				Goarch: []string{"arm"},
   186  				Goarm:  []string{"6", "9", "8", "7"},
   187  			},
   188  			expectedErr: "invalid goarm: 9",
   189  		},
   190  		"gomips": {
   191  			build: config.Build{
   192  				Goarch: []string{"mips"},
   193  				Gomips: []string{"softfloat", "mehfloat", "hardfloat"},
   194  			},
   195  			expectedErr: "invalid gomips: mehfloat",
   196  		},
   197  	} {
   198  		t.Run(s, func(t *testing.T) {
   199  			config := config.Project{
   200  				Builds: []config.Build{
   201  					tc.build,
   202  				},
   203  			}
   204  			ctx := context.New(config)
   205  			_, err := Default.WithDefaults(ctx.Config.Builds[0])
   206  			require.EqualError(t, err, tc.expectedErr)
   207  		})
   208  	}
   209  }
   210  
   211  func TestBuild(t *testing.T) {
   212  	folder := testlib.Mktmp(t)
   213  	writeGoodMain(t, folder)
   214  	config := config.Project{
   215  		Builds: []config.Build{
   216  			{
   217  				ID:     "foo",
   218  				Env:    []string{"GO111MODULE=off"},
   219  				Binary: "bin/foo-{{ .Version }}",
   220  				Targets: []string{
   221  					"linux_amd64",
   222  					"darwin_amd64",
   223  					"windows_amd64",
   224  					"linux_arm_6",
   225  					"js_wasm",
   226  					"linux_mips_softfloat",
   227  					"linux_mips64le_softfloat",
   228  				},
   229  				Asmflags: []string{".=", "all="},
   230  				Gcflags:  []string{"all="},
   231  				Flags:    []string{"{{.Env.GO_FLAGS}}"},
   232  				Tags:     []string{"osusergo", "netgo", "static_build"},
   233  				GoBinary: "go",
   234  			},
   235  		},
   236  	}
   237  	ctx := context.New(config)
   238  	ctx.Env["GO_FLAGS"] = "-v"
   239  	ctx.Git.CurrentTag = "5.6.7"
   240  	ctx.Version = "v" + ctx.Git.CurrentTag
   241  	build := ctx.Config.Builds[0]
   242  	for _, target := range build.Targets {
   243  		var ext string
   244  		if strings.HasPrefix(target, "windows") {
   245  			ext = ".exe"
   246  		} else if target == "js_wasm" {
   247  			ext = ".wasm"
   248  		}
   249  		bin, terr := tmpl.New(ctx).Apply(build.Binary)
   250  		require.NoError(t, terr)
   251  
   252  		// injecting some delay here to force inconsistent mod times on bins
   253  		time.Sleep(2 * time.Second)
   254  
   255  		parts := strings.Split(target, "_")
   256  		goos := parts[0]
   257  		goarch := parts[1]
   258  		goarm := ""
   259  		gomips := ""
   260  		if len(parts) > 2 {
   261  			if strings.Contains(goarch, "arm") {
   262  				goarm = parts[2]
   263  			}
   264  			if strings.Contains(goarch, "mips") {
   265  				gomips = parts[2]
   266  			}
   267  		}
   268  		err := Default.Build(ctx, build, api.Options{
   269  			Target: target,
   270  			Name:   bin + ext,
   271  			Path:   filepath.Join(folder, "dist", target, bin+ext),
   272  			Goos:   goos,
   273  			Goarch: goarch,
   274  			Goarm:  goarm,
   275  			Gomips: gomips,
   276  			Ext:    ext,
   277  		})
   278  		require.NoError(t, err)
   279  	}
   280  	require.ElementsMatch(t, ctx.Artifacts.List(), []*artifact.Artifact{
   281  		{
   282  			Name:   "bin/foo-v5.6.7",
   283  			Path:   filepath.Join(folder, "dist", "linux_amd64", "bin", "foo-v5.6.7"),
   284  			Goos:   "linux",
   285  			Goarch: "amd64",
   286  			Type:   artifact.Binary,
   287  			Extra: map[string]interface{}{
   288  				"Ext":    "",
   289  				"Binary": "foo-v5.6.7",
   290  				"ID":     "foo",
   291  			},
   292  		},
   293  		{
   294  			Name:   "bin/foo-v5.6.7",
   295  			Path:   filepath.Join(folder, "dist", "linux_mips_softfloat", "bin", "foo-v5.6.7"),
   296  			Goos:   "linux",
   297  			Goarch: "mips",
   298  			Gomips: "softfloat",
   299  			Type:   artifact.Binary,
   300  			Extra: map[string]interface{}{
   301  				"Ext":    "",
   302  				"Binary": "foo-v5.6.7",
   303  				"ID":     "foo",
   304  			},
   305  		},
   306  		{
   307  			Name:   "bin/foo-v5.6.7",
   308  			Path:   filepath.Join(folder, "dist", "linux_mips64le_softfloat", "bin", "foo-v5.6.7"),
   309  			Goos:   "linux",
   310  			Goarch: "mips64le",
   311  			Gomips: "softfloat",
   312  			Type:   artifact.Binary,
   313  			Extra: map[string]interface{}{
   314  				"Ext":    "",
   315  				"Binary": "foo-v5.6.7",
   316  				"ID":     "foo",
   317  			},
   318  		},
   319  		{
   320  			Name:   "bin/foo-v5.6.7",
   321  			Path:   filepath.Join(folder, "dist", "darwin_amd64", "bin", "foo-v5.6.7"),
   322  			Goos:   "darwin",
   323  			Goarch: "amd64",
   324  			Type:   artifact.Binary,
   325  			Extra: map[string]interface{}{
   326  				"Ext":    "",
   327  				"Binary": "foo-v5.6.7",
   328  				"ID":     "foo",
   329  			},
   330  		},
   331  		{
   332  			Name:   "bin/foo-v5.6.7",
   333  			Path:   filepath.Join(folder, "dist", "linux_arm_6", "bin", "foo-v5.6.7"),
   334  			Goos:   "linux",
   335  			Goarch: "arm",
   336  			Goarm:  "6",
   337  			Type:   artifact.Binary,
   338  			Extra: map[string]interface{}{
   339  				"Ext":    "",
   340  				"Binary": "foo-v5.6.7",
   341  				"ID":     "foo",
   342  			},
   343  		},
   344  		{
   345  			Name:   "bin/foo-v5.6.7.exe",
   346  			Path:   filepath.Join(folder, "dist", "windows_amd64", "bin", "foo-v5.6.7.exe"),
   347  			Goos:   "windows",
   348  			Goarch: "amd64",
   349  			Type:   artifact.Binary,
   350  			Extra: map[string]interface{}{
   351  				"Ext":    ".exe",
   352  				"Binary": "foo-v5.6.7",
   353  				"ID":     "foo",
   354  			},
   355  		},
   356  		{
   357  			Name:   "bin/foo-v5.6.7.wasm",
   358  			Path:   filepath.Join(folder, "dist", "js_wasm", "bin", "foo-v5.6.7.wasm"),
   359  			Goos:   "js",
   360  			Goarch: "wasm",
   361  			Type:   artifact.Binary,
   362  			Extra: map[string]interface{}{
   363  				"Ext":    ".wasm",
   364  				"Binary": "foo-v5.6.7",
   365  				"ID":     "foo",
   366  			},
   367  		},
   368  	})
   369  
   370  	modTimes := map[time.Time]bool{}
   371  	for _, bin := range ctx.Artifacts.List() {
   372  		if bin.Type != artifact.Binary {
   373  			continue
   374  		}
   375  
   376  		fi, err := os.Stat(bin.Path)
   377  		require.NoError(t, err)
   378  
   379  		// make this a suitable map key, per docs: https://golang.org/pkg/time/#Time
   380  		modTime := fi.ModTime().UTC().Round(0)
   381  
   382  		if modTimes[modTime] {
   383  			t.Fatal("duplicate modified time found, times should be different by default")
   384  		}
   385  		modTimes[modTime] = true
   386  	}
   387  }
   388  
   389  func TestBuildCodeInSubdir(t *testing.T) {
   390  	folder := testlib.Mktmp(t)
   391  	subdir := filepath.Join(folder, "bar")
   392  	err := os.Mkdir(subdir, 0o755)
   393  	require.NoError(t, err)
   394  	writeGoodMain(t, subdir)
   395  	config := config.Project{
   396  		Builds: []config.Build{
   397  			{
   398  				ID:     "foo",
   399  				Env:    []string{"GO111MODULE=off"},
   400  				Dir:    "bar",
   401  				Binary: "foo",
   402  				Targets: []string{
   403  					runtimeTarget,
   404  				},
   405  				GoBinary: "go",
   406  			},
   407  		},
   408  	}
   409  	ctx := context.New(config)
   410  	ctx.Git.CurrentTag = "5.6.7"
   411  	build := ctx.Config.Builds[0]
   412  	err = Default.Build(ctx, build, api.Options{
   413  		Target: runtimeTarget,
   414  		Name:   build.Binary,
   415  		Path:   filepath.Join(folder, "dist", runtimeTarget, build.Binary),
   416  		Ext:    "",
   417  	})
   418  	require.NoError(t, err)
   419  }
   420  
   421  func TestBuildWithDotGoDir(t *testing.T) {
   422  	folder := testlib.Mktmp(t)
   423  	require.NoError(t, os.Mkdir(filepath.Join(folder, ".go"), 0o755))
   424  	writeGoodMain(t, folder)
   425  	config := config.Project{
   426  		Builds: []config.Build{
   427  			{
   428  				ID:       "foo",
   429  				Env:      []string{"GO111MODULE=off"},
   430  				Binary:   "foo",
   431  				Targets:  []string{runtimeTarget},
   432  				GoBinary: "go",
   433  			},
   434  		},
   435  	}
   436  	ctx := context.New(config)
   437  	ctx.Git.CurrentTag = "5.6.7"
   438  	build := ctx.Config.Builds[0]
   439  	require.NoError(t, Default.Build(ctx, build, api.Options{
   440  		Target: runtimeTarget,
   441  		Name:   build.Binary,
   442  		Path:   filepath.Join(folder, "dist", runtimeTarget, build.Binary),
   443  		Ext:    "",
   444  	}))
   445  }
   446  
   447  func TestBuildFailed(t *testing.T) {
   448  	folder := testlib.Mktmp(t)
   449  	writeGoodMain(t, folder)
   450  	config := config.Project{
   451  		Builds: []config.Build{
   452  			{
   453  				ID:    "buildid",
   454  				Flags: []string{"-flag-that-dont-exists-to-force-failure"},
   455  				Targets: []string{
   456  					runtimeTarget,
   457  				},
   458  				GoBinary: "go",
   459  			},
   460  		},
   461  	}
   462  	ctx := context.New(config)
   463  	ctx.Git.CurrentTag = "5.6.7"
   464  	err := Default.Build(ctx, ctx.Config.Builds[0], api.Options{
   465  		Target: "darwin_amd64",
   466  	})
   467  	assertContainsError(t, err, `flag provided but not defined: -flag-that-dont-exists-to-force-failure`)
   468  	require.Empty(t, ctx.Artifacts.List())
   469  }
   470  
   471  func TestRunInvalidAsmflags(t *testing.T) {
   472  	folder := testlib.Mktmp(t)
   473  	writeGoodMain(t, folder)
   474  	config := config.Project{
   475  		Builds: []config.Build{
   476  			{
   477  				Binary:   "nametest",
   478  				Asmflags: []string{"{{.Version}"},
   479  				Targets: []string{
   480  					runtimeTarget,
   481  				},
   482  			},
   483  		},
   484  	}
   485  	ctx := context.New(config)
   486  	ctx.Git.CurrentTag = "5.6.7"
   487  	err := Default.Build(ctx, ctx.Config.Builds[0], api.Options{
   488  		Target: runtimeTarget,
   489  	})
   490  	require.EqualError(t, err, `template: tmpl:1: unexpected "}" in operand`)
   491  }
   492  
   493  func TestRunInvalidGcflags(t *testing.T) {
   494  	folder := testlib.Mktmp(t)
   495  	writeGoodMain(t, folder)
   496  	config := config.Project{
   497  		Builds: []config.Build{
   498  			{
   499  				Binary:  "nametest",
   500  				Gcflags: []string{"{{.Version}"},
   501  				Targets: []string{
   502  					runtimeTarget,
   503  				},
   504  			},
   505  		},
   506  	}
   507  	ctx := context.New(config)
   508  	ctx.Git.CurrentTag = "5.6.7"
   509  	err := Default.Build(ctx, ctx.Config.Builds[0], api.Options{
   510  		Target: runtimeTarget,
   511  	})
   512  	require.EqualError(t, err, `template: tmpl:1: unexpected "}" in operand`)
   513  }
   514  
   515  func TestRunInvalidLdflags(t *testing.T) {
   516  	folder := testlib.Mktmp(t)
   517  	writeGoodMain(t, folder)
   518  	config := config.Project{
   519  		Builds: []config.Build{
   520  			{
   521  				Binary:  "nametest",
   522  				Flags:   []string{"-v"},
   523  				Ldflags: []string{"-s -w -X main.version={{.Version}"},
   524  				Targets: []string{
   525  					runtimeTarget,
   526  				},
   527  			},
   528  		},
   529  	}
   530  	ctx := context.New(config)
   531  	ctx.Git.CurrentTag = "5.6.7"
   532  	err := Default.Build(ctx, ctx.Config.Builds[0], api.Options{
   533  		Target: runtimeTarget,
   534  	})
   535  	require.EqualError(t, err, `template: tmpl:1: unexpected "}" in operand`)
   536  }
   537  
   538  func TestRunInvalidFlags(t *testing.T) {
   539  	folder := testlib.Mktmp(t)
   540  	writeGoodMain(t, folder)
   541  	config := config.Project{
   542  		Builds: []config.Build{
   543  			{
   544  				Binary: "nametest",
   545  				Flags:  []string{"{{.Env.GOOS}"},
   546  				Targets: []string{
   547  					runtimeTarget,
   548  				},
   549  			},
   550  		},
   551  	}
   552  	ctx := context.New(config)
   553  	err := Default.Build(ctx, ctx.Config.Builds[0], api.Options{
   554  		Target: runtimeTarget,
   555  	})
   556  	require.EqualError(t, err, `template: tmpl:1: unexpected "}" in operand`)
   557  }
   558  
   559  func TestRunPipeWithoutMainFunc(t *testing.T) {
   560  	newCtx := func(t *testing.T) *context.Context {
   561  		t.Helper()
   562  		folder := testlib.Mktmp(t)
   563  		writeMainWithoutMainFunc(t, folder)
   564  		config := config.Project{
   565  			Builds: []config.Build{
   566  				{
   567  					Binary: "no-main",
   568  					Hooks:  config.HookConfig{},
   569  					Targets: []string{
   570  						runtimeTarget,
   571  					},
   572  				},
   573  			},
   574  		}
   575  		ctx := context.New(config)
   576  		ctx.Git.CurrentTag = "5.6.7"
   577  		return ctx
   578  	}
   579  	t.Run("empty", func(t *testing.T) {
   580  		ctx := newCtx(t)
   581  		ctx.Config.Builds[0].Main = ""
   582  		require.EqualError(t, Default.Build(ctx, ctx.Config.Builds[0], api.Options{
   583  			Target: runtimeTarget,
   584  		}), `build for no-main does not contain a main function`)
   585  	})
   586  	t.Run("not main.go", func(t *testing.T) {
   587  		ctx := newCtx(t)
   588  		ctx.Config.Builds[0].Main = "foo.go"
   589  		require.EqualError(t, Default.Build(ctx, ctx.Config.Builds[0], api.Options{
   590  			Target: runtimeTarget,
   591  		}), `couldn't find main file: stat foo.go: no such file or directory`)
   592  	})
   593  	t.Run("glob", func(t *testing.T) {
   594  		ctx := newCtx(t)
   595  		ctx.Config.Builds[0].Main = "."
   596  		require.EqualError(t, Default.Build(ctx, ctx.Config.Builds[0], api.Options{
   597  			Target: runtimeTarget,
   598  		}), `build for no-main does not contain a main function`)
   599  	})
   600  	t.Run("fixed main.go", func(t *testing.T) {
   601  		ctx := newCtx(t)
   602  		ctx.Config.Builds[0].Main = "main.go"
   603  		require.EqualError(t, Default.Build(ctx, ctx.Config.Builds[0], api.Options{
   604  			Target: runtimeTarget,
   605  		}), `build for no-main does not contain a main function`)
   606  	})
   607  	t.Run("using gomod.proxy", func(t *testing.T) {
   608  		ctx := newCtx(t)
   609  		ctx.Config.GoMod.Proxy = true
   610  		ctx.Config.Builds[0].Dir = "dist/proxy/test"
   611  		ctx.Config.Builds[0].Main = "github.com/caarlos0/test"
   612  		ctx.Config.Builds[0].UnproxiedDir = "."
   613  		ctx.Config.Builds[0].UnproxiedMain = "."
   614  		require.EqualError(t, Default.Build(ctx, ctx.Config.Builds[0], api.Options{
   615  			Target: runtimeTarget,
   616  		}), `build for no-main does not contain a main function`)
   617  	})
   618  }
   619  
   620  func TestRunPipeWithProxiedRepo(t *testing.T) {
   621  	folder := testlib.Mktmp(t)
   622  	out, err := exec.Command("git", "clone", "https://github.com/goreleaser/goreleaser", "-b", "v0.161.1", "--depth=1", ".").CombinedOutput()
   623  	require.NoError(t, err, string(out))
   624  
   625  	proxied := filepath.Join(folder, "dist/proxy/default")
   626  	require.NoError(t, os.MkdirAll(proxied, 0o750))
   627  	require.NoError(t, os.WriteFile(
   628  		filepath.Join(proxied, "main.go"),
   629  		[]byte(`// +build main
   630  package main
   631  
   632  import _ "github.com/goreleaser/goreleaser"
   633  `),
   634  		0o666,
   635  	))
   636  	require.NoError(t, os.WriteFile(
   637  		filepath.Join(proxied, "go.mod"),
   638  		[]byte("module foo\nrequire github.com/goreleaser/goreleaser v0.161.1"),
   639  		0o666,
   640  	))
   641  
   642  	cmd := exec.Command("go", "mod", "tidy")
   643  	cmd.Dir = proxied
   644  	require.NoError(t, cmd.Run())
   645  
   646  	config := config.Project{
   647  		GoMod: config.GoMod{
   648  			Proxy: true,
   649  		},
   650  		Builds: []config.Build{
   651  			{
   652  				Binary:        "foo",
   653  				Main:          "github.com/goreleaser/goreleaser",
   654  				Dir:           proxied,
   655  				UnproxiedMain: ".",
   656  				UnproxiedDir:  ".",
   657  				Targets: []string{
   658  					runtimeTarget,
   659  				},
   660  				GoBinary: "go",
   661  			},
   662  		},
   663  	}
   664  	ctx := context.New(config)
   665  
   666  	require.NoError(t, Default.Build(ctx, ctx.Config.Builds[0], api.Options{
   667  		Target: runtimeTarget,
   668  	}))
   669  }
   670  
   671  func TestRunPipeWithMainFuncNotInMainGoFile(t *testing.T) {
   672  	folder := testlib.Mktmp(t)
   673  	require.NoError(t, os.WriteFile(
   674  		filepath.Join(folder, "foo.go"),
   675  		[]byte("package main\nfunc main() {println(0)}"),
   676  		0o644,
   677  	))
   678  	config := config.Project{
   679  		Builds: []config.Build{
   680  			{
   681  				Env:    []string{"GO111MODULE=off"},
   682  				Binary: "foo",
   683  				Hooks:  config.HookConfig{},
   684  				Targets: []string{
   685  					runtimeTarget,
   686  				},
   687  				GoBinary: "go",
   688  			},
   689  		},
   690  	}
   691  	ctx := context.New(config)
   692  	ctx.Git.CurrentTag = "5.6.7"
   693  	t.Run("empty", func(t *testing.T) {
   694  		ctx.Config.Builds[0].Main = ""
   695  		require.NoError(t, Default.Build(ctx, ctx.Config.Builds[0], api.Options{
   696  			Target: runtimeTarget,
   697  		}))
   698  	})
   699  	t.Run("foo.go", func(t *testing.T) {
   700  		ctx.Config.Builds[0].Main = "foo.go"
   701  		require.NoError(t, Default.Build(ctx, ctx.Config.Builds[0], api.Options{
   702  			Target: runtimeTarget,
   703  		}))
   704  	})
   705  	t.Run("glob", func(t *testing.T) {
   706  		ctx.Config.Builds[0].Main = "."
   707  		require.NoError(t, Default.Build(ctx, ctx.Config.Builds[0], api.Options{
   708  			Target: runtimeTarget,
   709  		}))
   710  	})
   711  }
   712  
   713  func TestLdFlagsFullTemplate(t *testing.T) {
   714  	run := time.Now().UTC()
   715  	commit := time.Now().AddDate(-1, 0, 0)
   716  
   717  	ctx := &context.Context{
   718  		Git: context.GitInfo{
   719  			CurrentTag: "v1.2.3",
   720  			Commit:     "123",
   721  			CommitDate: commit,
   722  		},
   723  		Date:    run,
   724  		Version: "1.2.3",
   725  		Env:     map[string]string{"FOO": "123"},
   726  	}
   727  	artifact := &artifact.Artifact{Goarch: "amd64"}
   728  	flags, err := tmpl.New(ctx).WithArtifact(artifact, map[string]string{}).
   729  		Apply(`-s -w -X main.version={{.Version}} -X main.tag={{.Tag}} -X main.date={{.Date}} -X main.commit={{.Commit}} -X "main.foo={{.Env.FOO}}" -X main.time={{ time "20060102" }} -X main.arch={{.Arch}} -X main.commitDate={{.CommitDate}}`)
   730  	require.NoError(t, err)
   731  	require.Contains(t, flags, "-s -w")
   732  	require.Contains(t, flags, "-X main.version=1.2.3")
   733  	require.Contains(t, flags, "-X main.tag=v1.2.3")
   734  	require.Contains(t, flags, "-X main.commit=123")
   735  	require.Contains(t, flags, fmt.Sprintf("-X main.date=%d", run.Year()))
   736  	require.Contains(t, flags, fmt.Sprintf("-X main.time=%d", run.Year()))
   737  	require.Contains(t, flags, `-X "main.foo=123"`)
   738  	require.Contains(t, flags, `-X main.arch=amd64`)
   739  	require.Contains(t, flags, fmt.Sprintf("-X main.commitDate=%d", commit.Year()))
   740  }
   741  
   742  func TestInvalidTemplate(t *testing.T) {
   743  	for template, eerr := range map[string]string{
   744  		"{{ .Nope }":    `template: tmpl:1: unexpected "}" in operand`,
   745  		"{{.Env.NOPE}}": `template: tmpl:1:6: executing "tmpl" at <.Env.NOPE>: map has no entry for key "NOPE"`,
   746  	} {
   747  		t.Run(template, func(t *testing.T) {
   748  			ctx := context.New(config.Project{})
   749  			ctx.Git.CurrentTag = "3.4.1"
   750  			flags, err := tmpl.New(ctx).Apply(template)
   751  			require.EqualError(t, err, eerr)
   752  			require.Empty(t, flags)
   753  		})
   754  	}
   755  }
   756  
   757  func TestProcessFlags(t *testing.T) {
   758  	ctx := &context.Context{
   759  		Version: "1.2.3",
   760  	}
   761  	ctx.Git.CurrentTag = "5.6.7"
   762  
   763  	artifact := &artifact.Artifact{
   764  		Name:   "name",
   765  		Goos:   "darwin",
   766  		Goarch: "amd64",
   767  		Goarm:  "7",
   768  		Extra: map[string]interface{}{
   769  			"Binary": "binary",
   770  		},
   771  	}
   772  
   773  	source := []string{
   774  		"flag",
   775  		"{{.Version}}",
   776  		"{{.Os}}",
   777  		"{{.Arch}}",
   778  		"{{.Arm}}",
   779  		"{{.Binary}}",
   780  		"{{.ArtifactName}}",
   781  	}
   782  
   783  	expected := []string{
   784  		"-testflag=flag",
   785  		"-testflag=1.2.3",
   786  		"-testflag=darwin",
   787  		"-testflag=amd64",
   788  		"-testflag=7",
   789  		"-testflag=binary",
   790  		"-testflag=name",
   791  	}
   792  
   793  	flags, err := processFlags(ctx, artifact, []string{}, source, "-testflag=")
   794  	require.NoError(t, err)
   795  	require.Len(t, flags, 7)
   796  	require.Equal(t, expected, flags)
   797  }
   798  
   799  func TestProcessFlagsInvalid(t *testing.T) {
   800  	ctx := &context.Context{}
   801  
   802  	source := []string{
   803  		"{{.Version}",
   804  	}
   805  
   806  	expected := `template: tmpl:1: unexpected "}" in operand`
   807  
   808  	flags, err := processFlags(ctx, &artifact.Artifact{}, []string{}, source, "-testflag=")
   809  	require.EqualError(t, err, expected)
   810  	require.Nil(t, flags)
   811  }
   812  
   813  func TestBuildModTimestamp(t *testing.T) {
   814  	// round to seconds since this will be a unix timestamp
   815  	modTime := time.Now().AddDate(-1, 0, 0).Round(1 * time.Second).UTC()
   816  
   817  	folder := testlib.Mktmp(t)
   818  	writeGoodMain(t, folder)
   819  
   820  	config := config.Project{
   821  		Builds: []config.Build{
   822  			{
   823  				ID:     "foo",
   824  				Env:    []string{"GO111MODULE=off"},
   825  				Binary: "bin/foo-{{ .Version }}",
   826  				Targets: []string{
   827  					"linux_amd64",
   828  					"darwin_amd64",
   829  					"windows_amd64",
   830  					"linux_arm_6",
   831  					"js_wasm",
   832  					"linux_mips_softfloat",
   833  					"linux_mips64le_softfloat",
   834  				},
   835  				Asmflags:     []string{".=", "all="},
   836  				Gcflags:      []string{"all="},
   837  				Flags:        []string{"{{.Env.GO_FLAGS}}"},
   838  				ModTimestamp: fmt.Sprintf("%d", modTime.Unix()),
   839  				GoBinary:     "go",
   840  			},
   841  		},
   842  	}
   843  	ctx := context.New(config)
   844  	ctx.Env["GO_FLAGS"] = "-v"
   845  	ctx.Git.CurrentTag = "5.6.7"
   846  	ctx.Version = "v" + ctx.Git.CurrentTag
   847  	build := ctx.Config.Builds[0]
   848  	for _, target := range build.Targets {
   849  		var ext string
   850  		if strings.HasPrefix(target, "windows") {
   851  			ext = ".exe"
   852  		} else if target == "js_wasm" {
   853  			ext = ".wasm"
   854  		}
   855  		bin, terr := tmpl.New(ctx).Apply(build.Binary)
   856  		require.NoError(t, terr)
   857  
   858  		// injecting some delay here to force inconsistent mod times on bins
   859  		time.Sleep(2 * time.Second)
   860  
   861  		err := Default.Build(ctx, build, api.Options{
   862  			Target: target,
   863  			Name:   bin + ext,
   864  			Path:   filepath.Join(folder, "dist", target, bin+ext),
   865  			Ext:    ext,
   866  		})
   867  		require.NoError(t, err)
   868  	}
   869  
   870  	for _, bin := range ctx.Artifacts.List() {
   871  		if bin.Type != artifact.Binary {
   872  			continue
   873  		}
   874  
   875  		fi, err := os.Stat(bin.Path)
   876  		require.NoError(t, err)
   877  		require.True(t, modTime.Equal(fi.ModTime()), "inconsistent mod times found when specifying ModTimestamp")
   878  	}
   879  }
   880  
   881  func TestBuildGoBuildLine(t *testing.T) {
   882  	requireEqualCmd := func(tb testing.TB, build config.Build, expected []string) {
   883  		tb.Helper()
   884  		config := config.Project{
   885  			Builds: []config.Build{build},
   886  		}
   887  		ctx := context.New(config)
   888  		ctx.Version = "v1.2.3"
   889  		ctx.Git.Commit = "aaa"
   890  
   891  		line, err := buildGoBuildLine(ctx, config.Builds[0], api.Options{Path: "foo"}, &artifact.Artifact{}, []string{})
   892  		require.NoError(t, err)
   893  		require.Equal(t, expected, line)
   894  	}
   895  
   896  	t.Run("full", func(t *testing.T) {
   897  		requireEqualCmd(t, config.Build{
   898  			Main:     ".",
   899  			Asmflags: []string{"asmflag1", "asmflag2"},
   900  			Gcflags:  []string{"gcflag1", "gcflag2"},
   901  			Flags:    []string{"-flag1", "-flag2"},
   902  			Tags:     []string{"tag1", "tag2"},
   903  			Ldflags:  []string{"ldflag1", "ldflag2"},
   904  			GoBinary: "go",
   905  		}, []string{
   906  			"go", "build",
   907  			"-flag1", "-flag2",
   908  			"-asmflags=asmflag1", "-asmflags=asmflag2",
   909  			"-gcflags=gcflag1", "-gcflags=gcflag2",
   910  			"-tags=tag1,tag2",
   911  			"-ldflags=ldflag1 ldflag2",
   912  			"-o", "foo", ".",
   913  		})
   914  	})
   915  
   916  	t.Run("simple", func(t *testing.T) {
   917  		requireEqualCmd(t, config.Build{
   918  			Main:     ".",
   919  			GoBinary: "go",
   920  		}, strings.Fields("go build -o foo ."))
   921  	})
   922  
   923  	t.Run("ldflags1", func(t *testing.T) {
   924  		requireEqualCmd(t, config.Build{
   925  			Main:     ".",
   926  			Ldflags:  []string{"-s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.builtBy=goreleaser"},
   927  			GoBinary: "go",
   928  		}, []string{
   929  			"go", "build",
   930  			"-ldflags=-s -w -X main.version=v1.2.3 -X main.commit=aaa -X main.builtBy=goreleaser",
   931  			"-o", "foo", ".",
   932  		})
   933  	})
   934  
   935  	t.Run("ldflags2", func(t *testing.T) {
   936  		requireEqualCmd(t, config.Build{
   937  			Main:     ".",
   938  			Ldflags:  []string{"-s -w", "-X main.version={{.Version}}"},
   939  			GoBinary: "go",
   940  		}, []string{"go", "build", "-ldflags=-s -w -X main.version=v1.2.3", "-o", "foo", "."})
   941  	})
   942  }
   943  
   944  //
   945  // Helpers
   946  //
   947  
   948  func writeMainWithoutMainFunc(t *testing.T, folder string) {
   949  	t.Helper()
   950  	require.NoError(t, os.WriteFile(
   951  		filepath.Join(folder, "main.go"),
   952  		[]byte("package main\nconst a = 2\nfunc notMain() {println(0)}"),
   953  		0o644,
   954  	))
   955  }
   956  
   957  func writeGoodMain(t *testing.T, folder string) {
   958  	t.Helper()
   959  	require.NoError(t, os.WriteFile(
   960  		filepath.Join(folder, "main.go"),
   961  		[]byte("package main\nvar a = 1\nfunc main() {println(0)}"),
   962  		0o644,
   963  	))
   964  }
   965  
   966  func assertContainsError(t *testing.T, err error, s string) {
   967  	t.Helper()
   968  	require.Error(t, err)
   969  	require.Contains(t, err.Error(), s)
   970  }