github.com/goreleaser/goreleaser@v1.25.1/internal/artifact/artifact_test.go (about)

     1  package artifact
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  	"testing"
     9  
    10  	"github.com/goreleaser/goreleaser/internal/golden"
    11  	"github.com/goreleaser/goreleaser/pkg/config"
    12  	"github.com/stretchr/testify/assert"
    13  	"github.com/stretchr/testify/require"
    14  	"golang.org/x/sync/errgroup"
    15  )
    16  
    17  // ensure Type implements the stringer interface...
    18  var _ fmt.Stringer = Type(0)
    19  
    20  func TestAdd(t *testing.T) {
    21  	var g errgroup.Group
    22  	artifacts := New()
    23  	wd, _ := os.Getwd()
    24  	for _, a := range []*Artifact{
    25  		{
    26  			Name: " whitespaces .zip",
    27  			Type: UploadableArchive,
    28  			Path: filepath.Join(wd, "/foo/bar.tgz"),
    29  		},
    30  		{
    31  			Name: "bar",
    32  			Type: Binary,
    33  		},
    34  		{
    35  			Name: " whitespaces ",
    36  			Type: UploadableBinary,
    37  		},
    38  		{
    39  			Name: "foobar",
    40  			Type: DockerImage,
    41  		},
    42  		{
    43  			Name: "check",
    44  			Type: Checksum,
    45  		},
    46  	} {
    47  		a := a
    48  		g.Go(func() error {
    49  			artifacts.Add(a)
    50  			return nil
    51  		})
    52  	}
    53  	require.NoError(t, g.Wait())
    54  	require.Len(t, artifacts.List(), 5)
    55  	archives := artifacts.Filter(ByType(UploadableArchive)).List()
    56  	require.Len(t, archives, 1)
    57  	require.Equal(t, "whitespaces.zip", archives[0].Name)
    58  	binaries := artifacts.Filter(ByType(UploadableBinary)).List()
    59  	require.Len(t, binaries, 1)
    60  	require.Equal(t, "whitespaces", binaries[0].Name)
    61  }
    62  
    63  func TestFilter(t *testing.T) {
    64  	data := []*Artifact{
    65  		{
    66  			Name:   "foo",
    67  			Goos:   "linux",
    68  			Goarch: "arm",
    69  		},
    70  		{
    71  			Name:    "bar",
    72  			Goarch:  "amd64",
    73  			Goamd64: "v1",
    74  		},
    75  		{
    76  			Name:    "bar",
    77  			Goarch:  "amd64",
    78  			Goamd64: "v2",
    79  		},
    80  		{
    81  			Name:    "bar",
    82  			Goarch:  "amd64",
    83  			Goamd64: "v3",
    84  		},
    85  		{
    86  			Name:    "bar",
    87  			Goarch:  "amd64",
    88  			Goamd64: "v4",
    89  		},
    90  		{
    91  			Name:  "foobar",
    92  			Goarm: "6",
    93  		},
    94  		{
    95  			Name: "check",
    96  			Type: Checksum,
    97  		},
    98  		{
    99  			Name: "checkzumm",
   100  			Type: Checksum,
   101  		},
   102  		{
   103  			Name:   "unibin-replaces",
   104  			Goos:   "darwin",
   105  			Goarch: "all",
   106  			Extra: map[string]interface{}{
   107  				ExtraReplaces: true,
   108  			},
   109  		},
   110  		{
   111  			Name:   "unibin-noreplace",
   112  			Goos:   "darwin",
   113  			Goarch: "all",
   114  			Extra: map[string]interface{}{
   115  				ExtraReplaces: false,
   116  			},
   117  		},
   118  	}
   119  	artifacts := New()
   120  	for _, a := range data {
   121  		artifacts.Add(a)
   122  	}
   123  
   124  	require.Len(t, artifacts.Filter(ByGoos("linux")).items, 1)
   125  	require.Len(t, artifacts.Filter(ByGoos("darwin")).items, 2)
   126  
   127  	require.Len(t, artifacts.Filter(ByGoarch("amd64")).items, 4)
   128  	require.Empty(t, artifacts.Filter(ByGoarch("386")).items)
   129  
   130  	require.Len(t, artifacts.Filter(ByGoamd64("v1")).items, 1)
   131  	require.Len(t, artifacts.Filter(ByGoamd64("v2")).items, 1)
   132  	require.Len(t, artifacts.Filter(ByGoamd64("v3")).items, 1)
   133  	require.Len(t, artifacts.Filter(ByGoamd64("v4")).items, 1)
   134  
   135  	require.Len(t, artifacts.Filter(ByGoarm("6")).items, 1)
   136  	require.Empty(t, artifacts.Filter(ByGoarm("7")).items)
   137  
   138  	require.Len(t, artifacts.Filter(ByType(Checksum)).items, 2)
   139  	require.Empty(t, artifacts.Filter(ByType(Binary)).items)
   140  
   141  	require.Len(t, artifacts.Filter(OnlyReplacingUnibins).items, 9)
   142  	require.Len(t, artifacts.Filter(And(OnlyReplacingUnibins, ByGoos("darwin"))).items, 1)
   143  
   144  	require.Len(t, artifacts.Filter(nil).items, 10)
   145  
   146  	require.Len(t, artifacts.Filter(
   147  		And(
   148  			ByType(Checksum),
   149  			func(a *Artifact) bool {
   150  				return a.Name == "checkzumm"
   151  			},
   152  		),
   153  	).List(), 1)
   154  
   155  	require.Len(t, artifacts.Filter(
   156  		Or(
   157  			ByType(Checksum),
   158  			And(
   159  				ByGoos("linux"),
   160  				ByGoarm("arm"),
   161  			),
   162  		),
   163  	).List(), 2)
   164  }
   165  
   166  func TestRemove(t *testing.T) {
   167  	data := []*Artifact{
   168  		{
   169  			Name:   "foo",
   170  			Goos:   "linux",
   171  			Goarch: "arm",
   172  			Type:   Binary,
   173  		},
   174  		{
   175  			Name:   "universal",
   176  			Goos:   "darwin",
   177  			Goarch: "all",
   178  			Type:   UniversalBinary,
   179  		},
   180  		{
   181  			Name:   "bar",
   182  			Goarch: "amd64",
   183  		},
   184  		{
   185  			Name: "checks",
   186  			Type: Checksum,
   187  		},
   188  	}
   189  
   190  	t.Run("null filter", func(t *testing.T) {
   191  		artifacts := New()
   192  		for _, a := range data {
   193  			artifacts.Add(a)
   194  		}
   195  		require.NoError(t, artifacts.Remove(nil))
   196  		require.Len(t, artifacts.List(), len(data))
   197  	})
   198  
   199  	t.Run("removing", func(t *testing.T) {
   200  		artifacts := New()
   201  		for _, a := range data {
   202  			artifacts.Add(a)
   203  		}
   204  		require.NoError(t, artifacts.Remove(
   205  			Or(
   206  				ByType(Checksum),
   207  				ByType(UniversalBinary),
   208  				And(
   209  					ByGoos("linux"),
   210  					ByGoarch("arm"),
   211  				),
   212  			),
   213  		))
   214  
   215  		require.Len(t, artifacts.List(), 1)
   216  	})
   217  }
   218  
   219  func TestGroupByID(t *testing.T) {
   220  	data := []*Artifact{
   221  		{
   222  			Name: "foo",
   223  			Extra: map[string]interface{}{
   224  				ExtraID: "foo",
   225  			},
   226  		},
   227  		{
   228  			Name: "bar",
   229  			Extra: map[string]interface{}{
   230  				ExtraID: "foo",
   231  			},
   232  		},
   233  		{
   234  			Name: "foobar",
   235  			Goos: "linux",
   236  			Extra: map[string]interface{}{
   237  				ExtraID: "foovar",
   238  			},
   239  		},
   240  		{
   241  			Name: "foobar",
   242  			Goos: "linux",
   243  			Extra: map[string]interface{}{
   244  				ExtraID: "foovar",
   245  			},
   246  		},
   247  		{
   248  			Name: "foobar",
   249  			Goos: "linux",
   250  			Extra: map[string]interface{}{
   251  				ExtraID: "foobar",
   252  			},
   253  		},
   254  		{
   255  			Name: "check",
   256  			Type: Checksum,
   257  		},
   258  	}
   259  	artifacts := New()
   260  	for _, a := range data {
   261  		artifacts.Add(a)
   262  	}
   263  
   264  	groups := artifacts.GroupByID()
   265  	require.Len(t, groups["foo"], 2)
   266  	require.Len(t, groups["foobar"], 1)
   267  	require.Len(t, groups["foovar"], 2)
   268  	require.Len(t, groups, 3)
   269  }
   270  
   271  func TestGroupByPlatform(t *testing.T) {
   272  	data := []*Artifact{
   273  		{
   274  			Name:    "foo",
   275  			Goos:    "linux",
   276  			Goarch:  "amd64",
   277  			Goamd64: "v2",
   278  		},
   279  		{
   280  			Name:    "bar",
   281  			Goos:    "linux",
   282  			Goarch:  "amd64",
   283  			Goamd64: "v2",
   284  		},
   285  		{
   286  			Name:    "bar",
   287  			Goos:    "linux",
   288  			Goarch:  "amd64",
   289  			Goamd64: "v3",
   290  		},
   291  		{
   292  			Name:   "foobar",
   293  			Goos:   "linux",
   294  			Goarch: "arm",
   295  			Goarm:  "6",
   296  		},
   297  		{
   298  			Name:   "foobar",
   299  			Goos:   "linux",
   300  			Goarch: "mips",
   301  			Goarm:  "softfloat",
   302  		},
   303  		{
   304  			Name:   "foobar",
   305  			Goos:   "linux",
   306  			Goarch: "mips",
   307  			Goarm:  "hardfloat",
   308  		},
   309  		{
   310  			Name: "check",
   311  			Type: Checksum,
   312  		},
   313  	}
   314  	artifacts := New()
   315  	for _, a := range data {
   316  		artifacts.Add(a)
   317  	}
   318  
   319  	groups := artifacts.GroupByPlatform()
   320  	require.Len(t, groups["linuxamd64v2"], 2)
   321  	require.Len(t, groups["linuxamd64v3"], 1)
   322  	require.Len(t, groups["linuxarm6"], 1)
   323  	require.Len(t, groups["linuxmipssoftfloat"], 1)
   324  	require.Len(t, groups["linuxmipshardfloat"], 1)
   325  }
   326  
   327  func TestChecksum(t *testing.T) {
   328  	folder := t.TempDir()
   329  	file := filepath.Join(folder, "subject")
   330  	require.NoError(t, os.WriteFile(file, []byte("lorem ipsum"), 0o644))
   331  
   332  	artifact := Artifact{
   333  		Path: file,
   334  	}
   335  
   336  	for algo, result := range map[string]string{
   337  		"sha256": "5e2bf57d3f40c4b6df69daf1936cb766f832374b4fc0259a7cbff06e2f70f269",
   338  		"sha512": "f80eebd9aabb1a15fb869ed568d858a5c0dca3d5da07a410e1bd988763918d973e344814625f7c844695b2de36ffd27af290d0e34362c51dee5947d58d40527a",
   339  		"sha1":   "bfb7759a67daeb65410490b4d98bb9da7d1ea2ce",
   340  		"crc32":  "72d7748e",
   341  		"md5":    "80a751fde577028640c419000e33eba6",
   342  		"sha224": "e191edf06005712583518ced92cc2ac2fac8d6e4623b021a50736a91",
   343  		"sha384": "597493a6cf1289757524e54dfd6f68b332c7214a716a3358911ef5c09907adc8a654a18c1d721e183b0025f996f6e246",
   344  	} {
   345  		t.Run(algo, func(t *testing.T) {
   346  			sum, err := artifact.Checksum(algo)
   347  			require.NoError(t, err)
   348  			require.Equal(t, result, sum)
   349  		})
   350  	}
   351  }
   352  
   353  func TestChecksumFileDoesntExist(t *testing.T) {
   354  	file := filepath.Join(t.TempDir(), "nope")
   355  	artifact := Artifact{
   356  		Path: file,
   357  	}
   358  	sum, err := artifact.Checksum("sha1")
   359  	require.ErrorIs(t, err, os.ErrNotExist)
   360  	require.Empty(t, sum)
   361  }
   362  
   363  func TestInvalidAlgorithm(t *testing.T) {
   364  	f, err := os.CreateTemp(t.TempDir(), "")
   365  	require.NoError(t, err)
   366  	require.NoError(t, f.Close())
   367  	artifact := Artifact{
   368  		Path: f.Name(),
   369  	}
   370  	sum, err := artifact.Checksum("sha1ssss")
   371  	require.EqualError(t, err, `invalid algorithm: sha1ssss`)
   372  	require.Empty(t, sum)
   373  }
   374  
   375  func TestExtra(t *testing.T) {
   376  	a := Artifact{
   377  		Extra: map[string]interface{}{
   378  			"Foo": "foo",
   379  			"docker": config.Docker{
   380  				ID:  "id",
   381  				Use: "docker",
   382  			},
   383  			"fail-plz": config.Homebrew{
   384  				Plist: "aaaa",
   385  			},
   386  			"unsupported": func() {},
   387  			"binaries":    []string{"foo", "bar"},
   388  		},
   389  	}
   390  
   391  	t.Run("string", func(t *testing.T) {
   392  		foo, err := Extra[string](a, "Foo")
   393  		require.NoError(t, err)
   394  		require.Equal(t, "foo", foo)
   395  		require.Equal(t, "foo", ExtraOr(a, "Foo", "bar"))
   396  	})
   397  
   398  	t.Run("missing field", func(t *testing.T) {
   399  		bar, err := Extra[string](a, "Foobar")
   400  		require.NoError(t, err)
   401  		require.Equal(t, "", bar)
   402  		require.Equal(t, "bar", ExtraOr(a, "Foobar", "bar"))
   403  	})
   404  
   405  	t.Run("complex", func(t *testing.T) {
   406  		docker, err := Extra[config.Docker](a, "docker")
   407  		require.NoError(t, err)
   408  		require.Equal(t, "id", docker.ID)
   409  	})
   410  
   411  	t.Run("array", func(t *testing.T) {
   412  		binaries, err := Extra[[]string](a, "binaries")
   413  		require.NoError(t, err)
   414  		require.Equal(t, []string{"foo", "bar"}, binaries)
   415  		require.Equal(t, []string{"foo", "bar"}, ExtraOr(a, "binaries", []string{}))
   416  	})
   417  
   418  	t.Run("unmarshal error", func(t *testing.T) {
   419  		_, err := Extra[config.Docker](a, "fail-plz")
   420  		require.EqualError(t, err, "json: unknown field \"repository\"")
   421  	})
   422  
   423  	t.Run("marshal error", func(t *testing.T) {
   424  		_, err := Extra[config.Docker](a, "unsupported")
   425  		require.EqualError(t, err, "json: unsupported type: func()")
   426  	})
   427  }
   428  
   429  func TestByIDs(t *testing.T) {
   430  	data := []*Artifact{
   431  		{
   432  			Name: "foo",
   433  			Extra: map[string]interface{}{
   434  				ExtraID: "foo",
   435  			},
   436  		},
   437  		{
   438  			Name: "bar",
   439  			Extra: map[string]interface{}{
   440  				ExtraID: "bar",
   441  			},
   442  		},
   443  		{
   444  			Name: "foobar",
   445  			Extra: map[string]interface{}{
   446  				ExtraID: "foo",
   447  			},
   448  		},
   449  		{
   450  			Name: "check",
   451  			Extra: map[string]interface{}{
   452  				ExtraID: "check",
   453  			},
   454  		},
   455  		{
   456  			Name: "checksum",
   457  			Type: Checksum,
   458  		},
   459  	}
   460  	artifacts := New()
   461  	for _, a := range data {
   462  		artifacts.Add(a)
   463  	}
   464  
   465  	require.Len(t, artifacts.Filter(ByIDs("check")).items, 2)
   466  	require.Len(t, artifacts.Filter(ByIDs("foo")).items, 3)
   467  	require.Len(t, artifacts.Filter(ByIDs("foo", "bar")).items, 4)
   468  }
   469  
   470  func TestByExts(t *testing.T) {
   471  	data := []*Artifact{
   472  		{
   473  			Name: "foo",
   474  			Extra: map[string]interface{}{
   475  				ExtraExt: "deb",
   476  			},
   477  		},
   478  		{
   479  			Name: "bar",
   480  			Extra: map[string]interface{}{
   481  				ExtraExt: "deb",
   482  			},
   483  		},
   484  		{
   485  			Name: "foobar",
   486  			Extra: map[string]interface{}{
   487  				ExtraExt: "rpm",
   488  			},
   489  		},
   490  		{
   491  			Name:  "check",
   492  			Extra: map[string]interface{}{},
   493  		},
   494  	}
   495  	artifacts := New()
   496  	for _, a := range data {
   497  		artifacts.Add(a)
   498  	}
   499  
   500  	require.Len(t, artifacts.Filter(ByExt("deb")).items, 2)
   501  	require.Len(t, artifacts.Filter(ByExt("rpm")).items, 1)
   502  	require.Len(t, artifacts.Filter(ByExt("rpm", "deb")).items, 3)
   503  	require.Empty(t, artifacts.Filter(ByExt("foo")).items)
   504  }
   505  
   506  func TestByFormats(t *testing.T) {
   507  	data := []*Artifact{
   508  		{
   509  			Name: "foo",
   510  			Extra: map[string]interface{}{
   511  				ExtraFormat: "zip",
   512  			},
   513  		},
   514  		{
   515  			Name: "bar",
   516  			Extra: map[string]interface{}{
   517  				ExtraFormat: "tar.gz",
   518  			},
   519  		},
   520  		{
   521  			Name: "foobar",
   522  			Extra: map[string]interface{}{
   523  				ExtraFormat: "zip",
   524  			},
   525  		},
   526  		{
   527  			Name: "bin",
   528  			Extra: map[string]interface{}{
   529  				ExtraFormat: "binary",
   530  			},
   531  		},
   532  	}
   533  	artifacts := New()
   534  	for _, a := range data {
   535  		artifacts.Add(a)
   536  	}
   537  
   538  	require.Len(t, artifacts.Filter(ByFormats("binary")).items, 1)
   539  	require.Len(t, artifacts.Filter(ByFormats("zip")).items, 2)
   540  	require.Len(t, artifacts.Filter(ByFormats("zip", "tar.gz")).items, 3)
   541  }
   542  
   543  func TestPaths(t *testing.T) {
   544  	paths := []string{"a/b", "b/c", "d/e", "f/g"}
   545  	artifacts := New()
   546  	for _, a := range paths {
   547  		artifacts.Add(&Artifact{
   548  			Path: a,
   549  		})
   550  	}
   551  	require.ElementsMatch(t, paths, artifacts.Paths())
   552  }
   553  
   554  func TestRefresher(t *testing.T) {
   555  	t.Run("ok", func(t *testing.T) {
   556  		artifacts := New()
   557  		path := filepath.Join(t.TempDir(), "f")
   558  		artifacts.Add(&Artifact{
   559  			Name: "f",
   560  			Path: path,
   561  			Type: Checksum,
   562  			Extra: map[string]interface{}{
   563  				"Refresh": func() error {
   564  					return os.WriteFile(path, []byte("hello"), 0o765)
   565  				},
   566  			},
   567  		})
   568  		artifacts.Add(&Artifact{
   569  			Name: "no refresh",
   570  			Type: Checksum,
   571  		})
   572  
   573  		require.NoError(t, artifacts.Refresh())
   574  
   575  		bts, err := os.ReadFile(path)
   576  		require.NoError(t, err)
   577  		require.Equal(t, "hello", string(bts))
   578  	})
   579  
   580  	t.Run("nok", func(t *testing.T) {
   581  		artifacts := New()
   582  		artifacts.Add(&Artifact{
   583  			Name: "fail",
   584  			Type: Checksum,
   585  			Extra: map[string]interface{}{
   586  				"ID": "nok",
   587  				"Refresh": func() error {
   588  					return fmt.Errorf("fake err")
   589  				},
   590  			},
   591  		})
   592  
   593  		for _, item := range artifacts.List() {
   594  			require.EqualError(t, item.Refresh(), `failed to refresh "fail": fake err`)
   595  		}
   596  	})
   597  
   598  	t.Run("not a checksum", func(t *testing.T) {
   599  		artifacts := New()
   600  		artifacts.Add(&Artifact{
   601  			Name: "will be ignored",
   602  			Type: Binary,
   603  			Extra: map[string]interface{}{
   604  				"ID": "ignored",
   605  				"Refresh": func() error {
   606  					return fmt.Errorf("err that should not happen")
   607  				},
   608  			},
   609  		})
   610  
   611  		for _, item := range artifacts.List() {
   612  			require.NoError(t, item.Refresh())
   613  		}
   614  	})
   615  }
   616  
   617  func TestVisit(t *testing.T) {
   618  	artifacts := New()
   619  	artifacts.Add(&Artifact{
   620  		Name: "foo",
   621  		Type: Checksum,
   622  	})
   623  	artifacts.Add(&Artifact{
   624  		Name: "foo",
   625  		Type: Binary,
   626  	})
   627  
   628  	t.Run("ok", func(t *testing.T) {
   629  		require.NoError(t, artifacts.Visit(func(a *Artifact) error {
   630  			require.Equal(t, "foo", a.Name)
   631  			return nil
   632  		}))
   633  	})
   634  
   635  	t.Run("nok", func(t *testing.T) {
   636  		require.EqualError(t, artifacts.Visit(func(_ *Artifact) error {
   637  			return fmt.Errorf("fake err")
   638  		}), `fake err`)
   639  	})
   640  }
   641  
   642  func TestMarshalJSON(t *testing.T) {
   643  	artifacts := New()
   644  	artifacts.Add(&Artifact{
   645  		Name: "foo",
   646  		Type: Binary,
   647  		Extra: map[string]interface{}{
   648  			ExtraID: "adsad",
   649  		},
   650  	})
   651  	artifacts.Add(&Artifact{
   652  		Name: "foo",
   653  		Type: UploadableArchive,
   654  		Extra: map[string]interface{}{
   655  			ExtraID: "adsad",
   656  		},
   657  	})
   658  	artifacts.Add(&Artifact{
   659  		Name: "foo",
   660  		Type: Checksum,
   661  		Extra: map[string]interface{}{
   662  			ExtraRefresh: func() error { return nil },
   663  		},
   664  	})
   665  	bts, err := json.Marshal(artifacts.List())
   666  	require.NoError(t, err)
   667  	golden.RequireEqualJSON(t, bts)
   668  }
   669  
   670  func Test_ByBinaryLikeArtifacts(t *testing.T) {
   671  	tests := []struct {
   672  		name     string
   673  		initial  []*Artifact
   674  		expected []*Artifact
   675  	}{
   676  		{
   677  			name: "keep all unique paths",
   678  			initial: []*Artifact{
   679  				{
   680  					Path: "binary-path",
   681  					Type: Binary,
   682  				},
   683  				{
   684  					Path: "uploadable-binary-path",
   685  					Type: UploadableBinary,
   686  				},
   687  				{
   688  					Path: "universal-binary-path",
   689  					Type: UniversalBinary,
   690  				},
   691  			},
   692  			expected: []*Artifact{
   693  				{
   694  					Path: "binary-path",
   695  					Type: Binary,
   696  				},
   697  				{
   698  					Path: "uploadable-binary-path",
   699  					Type: UploadableBinary,
   700  				},
   701  				{
   702  					Path: "universal-binary-path",
   703  					Type: UniversalBinary,
   704  				},
   705  			},
   706  		},
   707  		{
   708  			name: "duplicate path between binaries ignored (odd configuration)",
   709  			initial: []*Artifact{
   710  				{
   711  					Path: "!!!duplicate!!!",
   712  					Type: Binary,
   713  				},
   714  				{
   715  					Path: "uploadable-binary-path",
   716  					Type: UploadableBinary,
   717  				},
   718  				{
   719  					Path: "!!!duplicate!!!",
   720  					Type: UniversalBinary,
   721  				},
   722  			},
   723  			expected: []*Artifact{
   724  				{
   725  					Path: "!!!duplicate!!!",
   726  					Type: Binary,
   727  				},
   728  				{
   729  					Path: "uploadable-binary-path",
   730  					Type: UploadableBinary,
   731  				},
   732  				{
   733  					Path: "!!!duplicate!!!",
   734  					Type: UniversalBinary,
   735  				},
   736  			},
   737  		},
   738  		{
   739  			name: "remove duplicate binary",
   740  			initial: []*Artifact{
   741  				{
   742  					Path: "!!!duplicate!!!",
   743  					Type: Binary,
   744  				},
   745  				{
   746  					Path: "!!!duplicate!!!",
   747  					Type: UploadableBinary,
   748  				},
   749  				{
   750  					Path: "universal-binary-path",
   751  					Type: UniversalBinary,
   752  				},
   753  			},
   754  			expected: []*Artifact{
   755  				{
   756  					Path: "!!!duplicate!!!",
   757  					Type: UploadableBinary,
   758  				},
   759  				{
   760  					Path: "universal-binary-path",
   761  					Type: UniversalBinary,
   762  				},
   763  			},
   764  		},
   765  		{
   766  			name: "remove duplicate universal binary",
   767  			initial: []*Artifact{
   768  				{
   769  					Path: "binary-path",
   770  					Type: Binary,
   771  				},
   772  				{
   773  					Path: "!!!duplicate!!!",
   774  					Type: UploadableBinary,
   775  				},
   776  				{
   777  					Path: "!!!duplicate!!!",
   778  					Type: UniversalBinary,
   779  				},
   780  			},
   781  			expected: []*Artifact{
   782  				{
   783  					Path: "binary-path",
   784  					Type: Binary,
   785  				},
   786  				{
   787  					Path: "!!!duplicate!!!",
   788  					Type: UploadableBinary,
   789  				},
   790  			},
   791  		},
   792  		{
   793  			name: "remove multiple duplicates",
   794  			initial: []*Artifact{
   795  				{
   796  					Path: "!!!duplicate!!!",
   797  					Type: Binary,
   798  				},
   799  				{
   800  					Path: "!!!duplicate!!!",
   801  					Type: Binary,
   802  				},
   803  				{
   804  					Path: "!!!duplicate!!!",
   805  					Type: UploadableBinary,
   806  				},
   807  				{
   808  					Path: "!!!duplicate!!!",
   809  					Type: UniversalBinary,
   810  				},
   811  				{
   812  					Path: "!!!duplicate!!!",
   813  					Type: UniversalBinary,
   814  				},
   815  			},
   816  			expected: []*Artifact{
   817  				{
   818  					Path: "!!!duplicate!!!",
   819  					Type: UploadableBinary,
   820  				},
   821  			},
   822  		},
   823  		{
   824  			name: "keep duplicate uploadable binaries (odd configuration)",
   825  			initial: []*Artifact{
   826  				{
   827  					Path: "!!!duplicate!!!",
   828  					Type: Binary,
   829  				},
   830  				{
   831  					Path: "!!!duplicate!!!",
   832  					Type: Binary,
   833  				},
   834  				{
   835  					Path: "!!!duplicate!!!",
   836  					Type: UploadableBinary,
   837  				},
   838  				{
   839  					Path: "!!!duplicate!!!",
   840  					Type: UploadableBinary,
   841  				},
   842  				{
   843  					Path: "!!!duplicate!!!",
   844  					Type: UniversalBinary,
   845  				},
   846  				{
   847  					Path: "!!!duplicate!!!",
   848  					Type: UniversalBinary,
   849  				},
   850  			},
   851  			expected: []*Artifact{
   852  				{
   853  					Path: "!!!duplicate!!!",
   854  					Type: UploadableBinary,
   855  				},
   856  				{
   857  					Path: "!!!duplicate!!!",
   858  					Type: UploadableBinary,
   859  				},
   860  			},
   861  		},
   862  		{
   863  			name: "keeps duplicates when there is no uploadable binary",
   864  			initial: []*Artifact{
   865  				{
   866  					Path: "!!!duplicate!!!",
   867  					Type: Binary,
   868  				},
   869  				{
   870  					Path: "!!!duplicate!!!",
   871  					Type: Binary,
   872  				},
   873  				{
   874  					Path: "!!!duplicate!!!",
   875  					Type: UniversalBinary,
   876  				},
   877  				{
   878  					Path: "!!!duplicate!!!",
   879  					Type: UniversalBinary,
   880  				},
   881  			},
   882  			expected: []*Artifact{
   883  				{
   884  					Path: "!!!duplicate!!!",
   885  					Type: Binary,
   886  				},
   887  				{
   888  					Path: "!!!duplicate!!!",
   889  					Type: Binary,
   890  				},
   891  				{
   892  					Path: "!!!duplicate!!!",
   893  					Type: UniversalBinary,
   894  				},
   895  				{
   896  					Path: "!!!duplicate!!!",
   897  					Type: UniversalBinary,
   898  				},
   899  			},
   900  		},
   901  	}
   902  	for _, tt := range tests {
   903  		t.Run(tt.name, func(t *testing.T) {
   904  			arts := New()
   905  			for _, a := range tt.initial {
   906  				arts.Add(a)
   907  			}
   908  			actual := arts.Filter(ByBinaryLikeArtifacts(arts)).List()
   909  			expected := New()
   910  			for _, a := range tt.expected {
   911  				expected.Add(a)
   912  			}
   913  			assert.Equal(t, expected.List(), actual)
   914  
   915  			if t.Failed() {
   916  				t.Log("expected:")
   917  				for _, a := range tt.expected {
   918  					t.Logf("   %s: %s", a.Type.String(), a.Path)
   919  				}
   920  
   921  				t.Log("got:")
   922  				for _, a := range actual {
   923  					t.Logf("   %s: %s", a.Type.String(), a.Path)
   924  				}
   925  			}
   926  		})
   927  	}
   928  }
   929  
   930  func TestArtifactStringer(t *testing.T) {
   931  	require.Equal(t, "foobar", Artifact{
   932  		Name: "foobar",
   933  	}.String())
   934  }
   935  
   936  func TestArtifactTypeStringer(t *testing.T) {
   937  	for i := 1; i <= 30; i++ {
   938  		t.Run(fmt.Sprintf("type-%d-%s", i, Type(i).String()), func(t *testing.T) {
   939  			require.NotEqual(t, "unknown", Type(i).String())
   940  		})
   941  	}
   942  
   943  	t.Run("unknown", func(t *testing.T) {
   944  		require.Equal(t, "unknown", Type(99999).String())
   945  	})
   946  }