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 }