github.com/triarius/goreleaser@v1.12.5/internal/pipe/universalbinary/universalbinary_test.go (about)

     1  package universalbinary
     2  
     3  import (
     4  	"debug/macho"
     5  	"os"
     6  	"os/exec"
     7  	"path/filepath"
     8  	"strings"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/triarius/goreleaser/internal/artifact"
    13  	"github.com/triarius/goreleaser/internal/testlib"
    14  	"github.com/triarius/goreleaser/pkg/config"
    15  	"github.com/triarius/goreleaser/pkg/context"
    16  	"github.com/stretchr/testify/require"
    17  )
    18  
    19  func TestDescription(t *testing.T) {
    20  	require.NotEmpty(t, Pipe{}.String())
    21  }
    22  
    23  func TestDefault(t *testing.T) {
    24  	t.Run("empty", func(t *testing.T) {
    25  		ctx := &context.Context{
    26  			Config: config.Project{
    27  				ProjectName: "proj",
    28  				UniversalBinaries: []config.UniversalBinary{
    29  					{},
    30  				},
    31  			},
    32  		}
    33  		require.NoError(t, Pipe{}.Default(ctx))
    34  		require.Equal(t, config.UniversalBinary{
    35  			ID:           "proj",
    36  			IDs:          []string{"proj"},
    37  			NameTemplate: "{{ .ProjectName }}",
    38  		}, ctx.Config.UniversalBinaries[0])
    39  	})
    40  
    41  	t.Run("given ids", func(t *testing.T) {
    42  		ctx := &context.Context{
    43  			Config: config.Project{
    44  				ProjectName: "proj",
    45  				UniversalBinaries: []config.UniversalBinary{
    46  					{IDs: []string{"foo"}},
    47  				},
    48  			},
    49  		}
    50  		require.NoError(t, Pipe{}.Default(ctx))
    51  		require.Equal(t, config.UniversalBinary{
    52  			ID:           "proj",
    53  			IDs:          []string{"foo"},
    54  			NameTemplate: "{{ .ProjectName }}",
    55  		}, ctx.Config.UniversalBinaries[0])
    56  	})
    57  
    58  	t.Run("given id", func(t *testing.T) {
    59  		ctx := &context.Context{
    60  			Config: config.Project{
    61  				ProjectName: "proj",
    62  				UniversalBinaries: []config.UniversalBinary{
    63  					{ID: "foo"},
    64  				},
    65  			},
    66  		}
    67  		require.NoError(t, Pipe{}.Default(ctx))
    68  		require.Equal(t, config.UniversalBinary{
    69  			ID:           "foo",
    70  			IDs:          []string{"foo"},
    71  			NameTemplate: "{{ .ProjectName }}",
    72  		}, ctx.Config.UniversalBinaries[0])
    73  	})
    74  
    75  	t.Run("given name", func(t *testing.T) {
    76  		ctx := &context.Context{
    77  			Config: config.Project{
    78  				ProjectName: "proj",
    79  				UniversalBinaries: []config.UniversalBinary{
    80  					{NameTemplate: "foo"},
    81  				},
    82  			},
    83  		}
    84  		require.NoError(t, Pipe{}.Default(ctx))
    85  		require.Equal(t, config.UniversalBinary{
    86  			ID:           "proj",
    87  			IDs:          []string{"proj"},
    88  			NameTemplate: "foo",
    89  		}, ctx.Config.UniversalBinaries[0])
    90  	})
    91  
    92  	t.Run("duplicated ids", func(t *testing.T) {
    93  		ctx := &context.Context{
    94  			Config: config.Project{
    95  				ProjectName: "proj",
    96  				UniversalBinaries: []config.UniversalBinary{
    97  					{ID: "foo"},
    98  					{ID: "foo"},
    99  				},
   100  			},
   101  		}
   102  		require.EqualError(t, Pipe{}.Default(ctx), `found 2 universal_binaries with the ID 'foo', please fix your config`)
   103  	})
   104  }
   105  
   106  func TestSkip(t *testing.T) {
   107  	t.Run("skip", func(t *testing.T) {
   108  		require.True(t, Pipe{}.Skip(context.New(config.Project{})))
   109  	})
   110  
   111  	t.Run("dont skip", func(t *testing.T) {
   112  		ctx := context.New(config.Project{
   113  			UniversalBinaries: []config.UniversalBinary{{}},
   114  		})
   115  		require.False(t, Pipe{}.Skip(ctx))
   116  	})
   117  }
   118  
   119  func TestRun(t *testing.T) {
   120  	dist := t.TempDir()
   121  
   122  	src := filepath.Join("testdata", "fake", "main.go")
   123  	paths := map[string]string{
   124  		"amd64": filepath.Join(dist, "fake_darwin_amd64/fake"),
   125  		"arm64": filepath.Join(dist, "fake_darwin_arm64/fake"),
   126  	}
   127  
   128  	pre := filepath.Join(dist, "pre")
   129  	post := filepath.Join(dist, "post")
   130  	cfg := config.Project{
   131  		Dist: dist,
   132  		UniversalBinaries: []config.UniversalBinary{
   133  			{
   134  				ID:           "foo",
   135  				IDs:          []string{"foo"},
   136  				NameTemplate: "foo",
   137  				Replace:      true,
   138  			},
   139  		},
   140  	}
   141  	ctx1 := context.New(cfg)
   142  
   143  	ctx2 := context.New(config.Project{
   144  		Dist: dist,
   145  		UniversalBinaries: []config.UniversalBinary{
   146  			{
   147  				ID:           "foo",
   148  				IDs:          []string{"foo"},
   149  				NameTemplate: "foo",
   150  			},
   151  		},
   152  	})
   153  
   154  	ctx3 := context.New(config.Project{
   155  		Dist: dist,
   156  		UniversalBinaries: []config.UniversalBinary{
   157  			{
   158  				ID:           "notfoo",
   159  				IDs:          []string{"notfoo"},
   160  				NameTemplate: "notfoo",
   161  			},
   162  		},
   163  	})
   164  
   165  	ctx4 := context.New(config.Project{
   166  		Dist: dist,
   167  		UniversalBinaries: []config.UniversalBinary{
   168  			{
   169  				ID:           "foo",
   170  				IDs:          []string{"foo"},
   171  				NameTemplate: "foo",
   172  			},
   173  		},
   174  	})
   175  
   176  	ctx5 := context.New(config.Project{
   177  		Dist: dist,
   178  		UniversalBinaries: []config.UniversalBinary{
   179  			{
   180  				ID:           "foo",
   181  				IDs:          []string{"foo"},
   182  				NameTemplate: "foo",
   183  				Hooks: config.BuildHookConfig{
   184  					Pre: []config.Hook{
   185  						{Cmd: "touch " + pre},
   186  					},
   187  					Post: []config.Hook{
   188  						{Cmd: "touch " + post},
   189  						{Cmd: `sh -c 'echo "{{ .Name }} {{ .Os }} {{ .Arch }} {{ .Arm }} {{ .Target }} {{ .Ext }}" > {{ .Path }}.post'`, Output: true},
   190  					},
   191  				},
   192  			},
   193  		},
   194  	})
   195  
   196  	ctx6 := context.New(config.Project{
   197  		Dist: dist,
   198  		UniversalBinaries: []config.UniversalBinary{
   199  			{
   200  				ID:           "foobar",
   201  				IDs:          []string{"foo"},
   202  				NameTemplate: "foo",
   203  			},
   204  		},
   205  	})
   206  
   207  	for arch, path := range paths {
   208  		cmd := exec.Command("go", "build", "-o", path, src)
   209  		cmd.Env = append(os.Environ(), "GOOS=darwin", "GOARCH="+arch)
   210  		_, err := cmd.CombinedOutput()
   211  		require.NoError(t, err)
   212  
   213  		modTime := time.Unix(0, 0)
   214  		require.NoError(t, os.Chtimes(path, modTime, modTime))
   215  
   216  		art := artifact.Artifact{
   217  			Name:   "fake",
   218  			Path:   path,
   219  			Goos:   "darwin",
   220  			Goarch: arch,
   221  			Type:   artifact.Binary,
   222  			Extra: map[string]interface{}{
   223  				artifact.ExtraBinary: "fake",
   224  				artifact.ExtraID:     "foo",
   225  			},
   226  		}
   227  		ctx1.Artifacts.Add(&art)
   228  		ctx2.Artifacts.Add(&art)
   229  		ctx5.Artifacts.Add(&art)
   230  		ctx6.Artifacts.Add(&art)
   231  		ctx4.Artifacts.Add(&artifact.Artifact{
   232  			Name:   "fake",
   233  			Path:   path + "wrong",
   234  			Goos:   "darwin",
   235  			Goarch: arch,
   236  			Type:   artifact.Binary,
   237  			Extra: map[string]interface{}{
   238  				artifact.ExtraBinary: "fake",
   239  				artifact.ExtraID:     "foo",
   240  			},
   241  		})
   242  	}
   243  
   244  	t.Run("ensure new artifact id", func(t *testing.T) {
   245  		require.NoError(t, Pipe{}.Run(ctx6))
   246  		unis := ctx6.Artifacts.Filter(artifact.ByType(artifact.UniversalBinary)).List()
   247  		require.Len(t, unis, 1)
   248  		checkUniversalBinary(t, unis[0])
   249  		require.Equal(t, "foobar", unis[0].ID())
   250  	})
   251  
   252  	t.Run("replacing", func(t *testing.T) {
   253  		require.NoError(t, Pipe{}.Run(ctx1))
   254  		require.Len(t, ctx1.Artifacts.Filter(artifact.ByType(artifact.Binary)).List(), 0)
   255  		unis := ctx1.Artifacts.Filter(artifact.ByType(artifact.UniversalBinary)).List()
   256  		require.Len(t, unis, 1)
   257  		checkUniversalBinary(t, unis[0])
   258  		require.True(t, artifact.ExtraOr(*unis[0], artifact.ExtraReplaces, false))
   259  	})
   260  
   261  	t.Run("keeping", func(t *testing.T) {
   262  		require.NoError(t, Pipe{}.Run(ctx2))
   263  		require.Len(t, ctx2.Artifacts.Filter(artifact.ByType(artifact.Binary)).List(), 2)
   264  		unis := ctx2.Artifacts.Filter(artifact.ByType(artifact.UniversalBinary)).List()
   265  		require.Len(t, unis, 1)
   266  		checkUniversalBinary(t, unis[0])
   267  		require.False(t, artifact.ExtraOr(*unis[0], artifact.ExtraReplaces, true))
   268  	})
   269  
   270  	t.Run("bad template", func(t *testing.T) {
   271  		testlib.RequireTemplateError(t, Pipe{}.Run(context.New(config.Project{
   272  			UniversalBinaries: []config.UniversalBinary{
   273  				{
   274  					NameTemplate: "{{.Name}",
   275  				},
   276  			},
   277  		})))
   278  	})
   279  
   280  	t.Run("no darwin builds", func(t *testing.T) {
   281  		require.EqualError(t, Pipe{}.Run(ctx3), `no darwin binaries found with id "notfoo"`)
   282  	})
   283  
   284  	t.Run("fail to open", func(t *testing.T) {
   285  		require.ErrorIs(t, Pipe{}.Run(ctx4), os.ErrNotExist)
   286  	})
   287  
   288  	t.Run("hooks", func(t *testing.T) {
   289  		require.NoError(t, Pipe{}.Run(ctx5))
   290  		require.FileExists(t, pre)
   291  		require.FileExists(t, post)
   292  		post := filepath.Join(dist, "foo_darwin_all/foo.post")
   293  		require.FileExists(t, post)
   294  		bts, err := os.ReadFile(post)
   295  		require.NoError(t, err)
   296  		require.Equal(t, "foo darwin all  darwin_all \n", string(bts))
   297  	})
   298  
   299  	t.Run("failing pre-hook", func(t *testing.T) {
   300  		ctx := ctx5
   301  		ctx.Config.UniversalBinaries[0].Hooks.Pre = []config.Hook{{Cmd: "exit 1"}}
   302  		ctx.Config.UniversalBinaries[0].Hooks.Post = []config.Hook{{Cmd: "echo post"}}
   303  		require.EqualError(t, Pipe{}.Run(ctx), "pre hook failed: failed to run 'exit 1': exec: \"exit\": executable file not found in $PATH")
   304  	})
   305  
   306  	t.Run("failing post-hook", func(t *testing.T) {
   307  		ctx := ctx5
   308  		ctx.Config.UniversalBinaries[0].Hooks.Pre = []config.Hook{{Cmd: "echo pre"}}
   309  		ctx.Config.UniversalBinaries[0].Hooks.Post = []config.Hook{{Cmd: "exit 1"}}
   310  		require.EqualError(t, Pipe{}.Run(ctx), "post hook failed: failed to run 'exit 1': exec: \"exit\": executable file not found in $PATH")
   311  	})
   312  
   313  	t.Run("hook with env tmpl", func(t *testing.T) {
   314  		ctx := ctx5
   315  		ctx.Config.UniversalBinaries[0].Hooks.Pre = []config.Hook{{
   316  			Cmd: "echo {{.Env.FOO}}",
   317  			Env: []string{"FOO=foo-{{.Tag}}"},
   318  		}}
   319  		ctx.Config.UniversalBinaries[0].Hooks.Post = []config.Hook{}
   320  		require.NoError(t, Pipe{}.Run(ctx))
   321  	})
   322  
   323  	t.Run("hook with bad env tmpl", func(t *testing.T) {
   324  		ctx := ctx5
   325  		ctx.Config.UniversalBinaries[0].Hooks.Pre = []config.Hook{{
   326  			Cmd: "echo blah",
   327  			Env: []string{"FOO=foo-{{.Tag}"},
   328  		}}
   329  		ctx.Config.UniversalBinaries[0].Hooks.Post = []config.Hook{}
   330  		testlib.RequireTemplateError(t, Pipe{}.Run(ctx))
   331  	})
   332  
   333  	t.Run("hook with bad dir tmpl", func(t *testing.T) {
   334  		ctx := ctx5
   335  		ctx.Config.UniversalBinaries[0].Hooks.Pre = []config.Hook{{
   336  			Cmd: "echo blah",
   337  			Dir: "{{.Tag}",
   338  		}}
   339  		ctx.Config.UniversalBinaries[0].Hooks.Post = []config.Hook{}
   340  		testlib.RequireTemplateError(t, Pipe{}.Run(ctx))
   341  	})
   342  
   343  	t.Run("hook with bad cmd tmpl", func(t *testing.T) {
   344  		ctx := ctx5
   345  		ctx.Config.UniversalBinaries[0].Hooks.Pre = []config.Hook{{
   346  			Cmd: "echo blah-{{.Tag }",
   347  		}}
   348  		ctx.Config.UniversalBinaries[0].Hooks.Post = []config.Hook{}
   349  		testlib.RequireTemplateError(t, Pipe{}.Run(ctx))
   350  	})
   351  }
   352  
   353  func checkUniversalBinary(tb testing.TB, unibin *artifact.Artifact) {
   354  	tb.Helper()
   355  
   356  	require.True(tb, strings.HasSuffix(unibin.Path, unibin.ID()+"_darwin_all/foo"))
   357  	f, err := macho.OpenFat(unibin.Path)
   358  	require.NoError(tb, err)
   359  	require.Len(tb, f.Arches, 2)
   360  }