github.com/goreleaser/nfpm/v2@v2.44.0/files/files_test.go (about)

     1  package files_test
     2  
     3  import (
     4  	"fmt"
     5  	"io/fs"
     6  	"os"
     7  	"path/filepath"
     8  	"runtime"
     9  	"strconv"
    10  	"strings"
    11  	"sync"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/goreleaser/nfpm/v2/files"
    16  	"github.com/stretchr/testify/assert"
    17  	"github.com/stretchr/testify/require"
    18  	"gopkg.in/yaml.v3"
    19  )
    20  
    21  var mtime = time.Date(2023, 11, 5, 23, 15, 17, 0, time.UTC)
    22  
    23  type testStruct struct {
    24  	Contents files.Contents `yaml:"contents"`
    25  }
    26  
    27  func TestBasicDecode(t *testing.T) {
    28  	var config testStruct
    29  	dec := yaml.NewDecoder(strings.NewReader(`---
    30  contents:
    31  - src: a
    32    dst: b
    33  - src: a
    34    dst: b
    35    type: "config|noreplace"
    36    packager: "rpm"
    37    file_info:
    38      mode: 0644
    39      mtime: 2008-01-02T15:04:05Z
    40  `))
    41  	dec.KnownFields(true)
    42  	err := dec.Decode(&config)
    43  	require.NoError(t, err)
    44  	require.Len(t, config.Contents, 2)
    45  	for _, f := range config.Contents {
    46  		t.Logf("%+#v\n", f)
    47  		require.Equal(t, "a", f.Source)
    48  		require.Equal(t, "b", f.Destination)
    49  	}
    50  }
    51  
    52  func TestDeepPathsWithGlobAndUmask(t *testing.T) {
    53  	path := filepath.Join(t.TempDir(), "foo", "bar", "zaz", "file.txt")
    54  	// create a bunch of files with bad permissions
    55  	require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o777))
    56  	require.NoError(t, os.WriteFile(path, nil, 0o777))
    57  	var config testStruct
    58  	dec := yaml.NewDecoder(strings.NewReader(`---
    59  contents:
    60  - src: testdata/globtest/**/*
    61    dst: /bla
    62    file_info:
    63      mode: 0644
    64      mtime: 2008-01-02T15:04:05Z
    65  - src: testdata/deep-paths/
    66    dst: /bar
    67  - src: ` + path + `
    68    dst: /foo/file.txt
    69  `))
    70  	dec.KnownFields(true)
    71  	err := dec.Decode(&config)
    72  	require.NoError(t, err)
    73  	require.Len(t, config.Contents, 3)
    74  	parsedContents, err := files.PrepareForPackager(
    75  		config.Contents,
    76  		0o133,
    77  		"",
    78  		false,
    79  		mtime,
    80  	)
    81  	require.NoError(t, err)
    82  	for _, c := range parsedContents {
    83  		switch c.Source {
    84  		case "testdata/globtest/nested/b.txt":
    85  			require.Equal(t, "/bla/nested/b.txt", c.Destination)
    86  			require.Equal(t, "-rw-r--r--", c.Mode().String())
    87  		case "testdata/globtest/multi-nested/subdir/c.txt":
    88  			require.Equal(t, "/bla/multi-nested/subdir/c.txt", c.Destination)
    89  			require.Equal(t, "-rw-r--r--", c.Mode().String())
    90  		case "testdata/deep-paths/nested1/nested2/a.txt":
    91  			require.Equal(t, "/bar/nested1/nested2/a.txt", c.Destination)
    92  			require.Equal(t, "-rw-r--r--", c.Mode().String())
    93  		case path:
    94  			require.Equal(t, "/foo/file.txt", c.Destination)
    95  			require.Equal(t, "-rw-r--r--", c.Mode().String())
    96  		}
    97  	}
    98  }
    99  
   100  func TestDeepPathsWithoutGlob(t *testing.T) {
   101  	var config testStruct
   102  	dec := yaml.NewDecoder(strings.NewReader(`---
   103  contents:
   104  - src: testdata/deep-paths/
   105    dst: /bla
   106  `))
   107  	dec.KnownFields(true)
   108  	err := dec.Decode(&config)
   109  	require.NoError(t, err)
   110  	require.Len(t, config.Contents, 1)
   111  	parsedContents, err := files.PrepareForPackager(
   112  		config.Contents,
   113  		0,
   114  		"",
   115  		true,
   116  		mtime,
   117  	)
   118  	require.NoError(t, err)
   119  	present := false
   120  
   121  	for _, f := range parsedContents {
   122  		switch f.Source {
   123  		case "testdata/deep-paths/nested1/nested2/a.txt":
   124  			present = true
   125  			require.Equal(t, "/bla/nested1/nested2/a.txt", f.Destination)
   126  		case "":
   127  			continue
   128  		default:
   129  			t.Errorf("unknown source %s for content %#v", f.Source, f)
   130  		}
   131  	}
   132  
   133  	require.True(t, present)
   134  }
   135  
   136  func TestFileInfoDefault(t *testing.T) {
   137  	var config testStruct
   138  	dec := yaml.NewDecoder(strings.NewReader(`---
   139  contents:
   140  - src: files_test.go
   141    dst: b
   142  `))
   143  	dec.KnownFields(true)
   144  	err := dec.Decode(&config)
   145  	require.NoError(t, err)
   146  
   147  	config.Contents, err = files.PrepareForPackager(
   148  		config.Contents,
   149  		0,
   150  		"",
   151  		true,
   152  		mtime,
   153  	)
   154  	require.NoError(t, err)
   155  	require.Len(t, config.Contents, 1)
   156  
   157  	fi, err := os.Stat("files_test.go")
   158  	require.NoError(t, err)
   159  
   160  	f := config.Contents[0]
   161  	require.Equal(t, "files_test.go", f.Source)
   162  	require.Equal(t, "/b", f.Destination)
   163  	require.Equal(t, f.FileInfo.Mode, fi.Mode())
   164  	require.Equal(t, f.FileInfo.MTime, mtime)
   165  }
   166  
   167  func TestFileInfo(t *testing.T) {
   168  	var config testStruct
   169  	dec := yaml.NewDecoder(strings.NewReader(`---
   170  contents:
   171  - src: files_test.go
   172    dst: b
   173    type: "config|noreplace"
   174    packager: "rpm"
   175    file_info:
   176      mode: 0123
   177      mtime: 2008-01-02T15:04:05Z
   178  `))
   179  	dec.KnownFields(true)
   180  	err := dec.Decode(&config)
   181  	require.NoError(t, err)
   182  
   183  	config.Contents, err = files.PrepareForPackager(
   184  		config.Contents,
   185  		0,
   186  		"rpm",
   187  		true,
   188  		mtime,
   189  	)
   190  	require.NoError(t, err)
   191  	require.Len(t, config.Contents, 1)
   192  
   193  	ct, err := time.Parse(time.RFC3339, "2008-01-02T15:04:05Z")
   194  	require.NoError(t, err)
   195  
   196  	f := config.Contents[0]
   197  	require.Equal(t, "files_test.go", f.Source)
   198  	require.Equal(t, "/b", f.Destination)
   199  	require.Equal(t, f.FileInfo.Mode, os.FileMode(0o123))
   200  	require.Equal(t, f.FileInfo.MTime, ct)
   201  }
   202  
   203  func TestSymlinksInDirectory(t *testing.T) {
   204  	var config testStruct
   205  	dec := yaml.NewDecoder(strings.NewReader(`---
   206  contents:
   207  - src: testdata/symlinks/subdir
   208    dst: /bla
   209  - src: testdata/symlinks/link-1
   210    dst: /
   211  - src: testdata/symlinks/link-2
   212    dst: /
   213  - src: existent
   214    dst: /bla/link-3
   215    type: symlink
   216  `))
   217  	dec.KnownFields(true)
   218  	err := dec.Decode(&config)
   219  	require.NoError(t, err)
   220  
   221  	config.Contents, err = files.PrepareForPackager(
   222  		config.Contents,
   223  		0,
   224  		"",
   225  		true,
   226  		mtime,
   227  	)
   228  	require.NoError(t, err)
   229  	config.Contents = withoutFileInfo(config.Contents)
   230  
   231  	expected := files.Contents{
   232  		{
   233  			Source:      "",
   234  			Destination: "/bla/",
   235  			Type:        files.TypeImplicitDir,
   236  		},
   237  		{
   238  			Source:      "testdata/symlinks/subdir/existent",
   239  			Destination: "/bla/existent",
   240  			Type:        files.TypeFile,
   241  		},
   242  		{
   243  			Source:      "non-existent",
   244  			Destination: "/bla/link-1",
   245  			Type:        files.TypeSymlink,
   246  		},
   247  		{
   248  			Source:      "existent",
   249  			Destination: "/bla/link-2",
   250  			Type:        files.TypeSymlink,
   251  		},
   252  		{
   253  			Source:      "existent",
   254  			Destination: "/bla/link-3",
   255  			Type:        files.TypeSymlink,
   256  		},
   257  		{
   258  			Source:      "broken",
   259  			Destination: "/link-1",
   260  			Type:        files.TypeSymlink,
   261  		},
   262  		{
   263  			Source:      "bla",
   264  			Destination: "/link-2",
   265  			Type:        files.TypeSymlink,
   266  		},
   267  	}
   268  
   269  	require.Equal(t, expected, config.Contents)
   270  }
   271  
   272  func TestRace(t *testing.T) {
   273  	var config testStruct
   274  	dec := yaml.NewDecoder(strings.NewReader(`---
   275  contents:
   276  - src: a
   277    dst: b
   278    type: symlink
   279  `))
   280  	err := dec.Decode(&config)
   281  	require.NoError(t, err)
   282  	errs := make(chan error, 10)
   283  	t.Cleanup(func() { close(errs) })
   284  	var wg sync.WaitGroup
   285  	for range 10 {
   286  		wg.Add(1)
   287  		go func() {
   288  			defer wg.Done()
   289  			_, err := files.PrepareForPackager(
   290  				config.Contents,
   291  				0,
   292  				"",
   293  				false,
   294  				mtime,
   295  			)
   296  			errs <- err
   297  		}()
   298  	}
   299  	wg.Wait()
   300  
   301  	for range 10 {
   302  		require.NoError(t, <-errs)
   303  	}
   304  }
   305  
   306  func TestCollision(t *testing.T) {
   307  	t.Run("collision between files for all packagers", func(t *testing.T) {
   308  		configuredFiles := []*files.Content{
   309  			{Source: "../testdata/whatever.conf", Destination: "/samedestination"},
   310  			{Source: "../testdata/whatever2.conf", Destination: "/samedestination"},
   311  		}
   312  
   313  		_, err := files.PrepareForPackager(
   314  			configuredFiles,
   315  			0,
   316  			"",
   317  			true,
   318  			mtime,
   319  		)
   320  		require.ErrorIs(t, err, files.ErrContentCollision)
   321  	})
   322  
   323  	t.Run("no collision due to different per-file packagers", func(t *testing.T) {
   324  		configuredFiles := []*files.Content{
   325  			{Source: "../testdata/whatever.conf", Destination: "/samedestination", Packager: "foo"},
   326  			{Source: "../testdata/whatever2.conf", Destination: "/samedestination", Packager: "bar"},
   327  		}
   328  
   329  		_, err := files.PrepareForPackager(
   330  			configuredFiles,
   331  			0,
   332  			"foo",
   333  			true,
   334  			mtime,
   335  		)
   336  		require.NoError(t, err)
   337  	})
   338  
   339  	t.Run("collision between file for all packagers and file for specific packager", func(t *testing.T) {
   340  		configuredFiles := []*files.Content{
   341  			{Source: "../testdata/whatever.conf", Destination: "/samedestination", Packager: "foo"},
   342  			{Source: "../testdata/whatever2.conf", Destination: "/samedestination", Packager: ""},
   343  		}
   344  
   345  		_, err := files.PrepareForPackager(
   346  			configuredFiles,
   347  			0,
   348  			"foo",
   349  			true,
   350  			mtime,
   351  		)
   352  		require.ErrorIs(t, err, files.ErrContentCollision)
   353  	})
   354  }
   355  
   356  func TestDisableGlobbing(t *testing.T) {
   357  	testCases := []files.Content{
   358  		{
   359  			Source:      "testdata/{test}/bar",
   360  			Destination: "/etc/{test}/bar",
   361  		},
   362  		{
   363  			Source:      "testdata/{test}/[f]oo",
   364  			Destination: "testdata/{test}/[f]oo",
   365  		},
   366  		{
   367  			Source:      "testdata/globtest/a.txt",
   368  			Destination: "testdata/globtest/a.txt",
   369  		},
   370  		{
   371  			Source:      "testdata/globtest/a.txt",
   372  			Destination: "/etc/a.txt",
   373  		},
   374  	}
   375  
   376  	disableGlobbing := true
   377  
   378  	for i, testCase := range testCases {
   379  		content := testCase
   380  
   381  		t.Run(strconv.Itoa(i), func(t *testing.T) {
   382  			result, err := files.PrepareForPackager(
   383  				files.Contents{&content},
   384  				0,
   385  				"",
   386  				disableGlobbing,
   387  				mtime,
   388  			)
   389  			if err != nil {
   390  				t.Fatalf("expand content globs: %v", err)
   391  			}
   392  
   393  			result = withoutImplicitDirs(result)
   394  
   395  			if len(result) != 1 {
   396  				t.Fatalf("unexpected result length: %d, expected one", len(result))
   397  			}
   398  
   399  			actualContent := result[0]
   400  
   401  			// we expect the result content to be identical to the input content
   402  			if actualContent.Source != content.Source {
   403  				t.Fatalf("unexpected content source: %q, expected %q", actualContent.Source, content.Source)
   404  			}
   405  
   406  			if strings.TrimLeft(actualContent.Destination, "./") != strings.TrimLeft(content.Destination, "/") {
   407  				t.Fatalf("unexpected content destination: %q, expected %q",
   408  					strings.TrimLeft(actualContent.Destination, "./"), strings.TrimLeft(content.Destination, "/"))
   409  			}
   410  		})
   411  	}
   412  }
   413  
   414  func withoutImplicitDirs(contents files.Contents) files.Contents {
   415  	filtered := make(files.Contents, 0, len(contents))
   416  
   417  	for _, c := range contents {
   418  		if c.Type != files.TypeImplicitDir {
   419  			filtered = append(filtered, c)
   420  		}
   421  	}
   422  
   423  	return filtered
   424  }
   425  
   426  func TestGlobbingWhenFilesHaveBrackets(t *testing.T) {
   427  	if runtime.GOOS == "windows" {
   428  		t.Skip("doesn't work on windows")
   429  	}
   430  	result, err := files.PrepareForPackager(
   431  		files.Contents{
   432  			{
   433  				Source:      "./testdata/\\{test\\}/",
   434  				Destination: ".",
   435  			},
   436  		},
   437  		0,
   438  		"",
   439  		false,
   440  		mtime,
   441  	)
   442  	if err != nil {
   443  		t.Fatalf("expand content globs: %v", err)
   444  	}
   445  
   446  	expected := files.Contents{
   447  		{
   448  			Source:      "testdata/{test}/[f]oo",
   449  			Destination: "/[f]oo",
   450  		},
   451  		{
   452  			Source:      "testdata/{test}/bar",
   453  			Destination: "/bar",
   454  		},
   455  	}
   456  
   457  	if len(result) != 2 {
   458  		t.Fatalf("unexpected result length: %d, expected one", len(result))
   459  	}
   460  
   461  	for i, r := range result {
   462  		ex := expected[i]
   463  		if ex.Source != r.Source {
   464  			t.Fatalf("unexpected content source: %q, expected %q", r.Source, ex.Source)
   465  		}
   466  		if ex.Destination != r.Destination {
   467  			t.Fatalf("unexpected content destination: %q, expected %q",
   468  				ex.Destination, r.Destination)
   469  		}
   470  	}
   471  }
   472  
   473  func TestGlobbingFilesWithDifferentSizesWithFileInfo(t *testing.T) {
   474  	result, err := files.PrepareForPackager(
   475  		files.Contents{
   476  			{
   477  				Source:      "./testdata/globtest/different-sizes/**/*",
   478  				Destination: ".",
   479  				FileInfo: &files.ContentFileInfo{
   480  					Mode: 0o777,
   481  				},
   482  			},
   483  		},
   484  		0,
   485  		"",
   486  		false,
   487  		mtime,
   488  	)
   489  	if err != nil {
   490  		t.Fatalf("expand content globs: %v", err)
   491  	}
   492  
   493  	result = withoutImplicitDirs(result)
   494  
   495  	if len(result) != 2 {
   496  		t.Fatalf("unexpected result length: %d, expected 2", len(result))
   497  	}
   498  
   499  	if result[0].FileInfo.Size == result[1].FileInfo.Size {
   500  		t.Fatal("test FileInfos have the same size, expected different")
   501  	}
   502  }
   503  
   504  func TestDestEndsWithSlash(t *testing.T) {
   505  	result, err := files.PrepareForPackager(
   506  		files.Contents{
   507  			{
   508  				Source:      "./testdata/globtest/a.txt",
   509  				Destination: "./foo/",
   510  			},
   511  		},
   512  		0,
   513  		"",
   514  		false,
   515  		mtime,
   516  	)
   517  	result = withoutImplicitDirs(result)
   518  	require.NoError(t, err)
   519  	require.Len(t, result, 1)
   520  	require.Equal(t, "/foo/a.txt", result[0].Destination)
   521  }
   522  
   523  func TestInvalidFileType(t *testing.T) {
   524  	var config testStruct
   525  	dec := yaml.NewDecoder(strings.NewReader(`---
   526  contents:
   527  - src: testdata/globtest/**/*
   528    dst: /bla
   529    type: filr
   530  `))
   531  	dec.KnownFields(true)
   532  	require.NoError(t, dec.Decode(&config))
   533  	_, err := files.PrepareForPackager(
   534  		config.Contents,
   535  		0,
   536  		"",
   537  		false,
   538  		mtime,
   539  	)
   540  	require.EqualError(t, err, "invalid content type: filr")
   541  }
   542  
   543  func TestValidFileTypes(t *testing.T) {
   544  	var config testStruct
   545  	dec := yaml.NewDecoder(strings.NewReader(`---
   546  contents:
   547  - src: testdata/globtest/a.txt
   548    dst: /f1.txt
   549  - src: testdata/globtest/a.txt
   550    dst: /f2.txt
   551    type: file
   552  - src: testdata/globtest/a.txt
   553    dst: /f3.txt
   554    type: config
   555  - src: testdata/globtest/a.txt
   556    dst: /f4.txt
   557    type: config|noreplace
   558  - src: testdata/globtest/a.txt
   559    dst: /f5.txt
   560    type: symlink
   561  - src: testdata/globtest/a.txt
   562    dst: /f6.txt
   563    type: dir
   564  - src: testdata/globtest/a.txt
   565    dst: /f7.txt
   566    type: ghost
   567  `))
   568  	dec.KnownFields(true)
   569  	require.NoError(t, dec.Decode(&config))
   570  	_, err := files.PrepareForPackager(
   571  		config.Contents,
   572  		0,
   573  		"",
   574  		false,
   575  		mtime,
   576  	)
   577  	require.NoError(t, err)
   578  }
   579  
   580  func TestImplicitDirectories(t *testing.T) {
   581  	results, err := files.PrepareForPackager(
   582  		files.Contents{
   583  			{
   584  				Source:      "./testdata/globtest/a.txt",
   585  				Destination: "./foo/bar/baz",
   586  			},
   587  		},
   588  		0,
   589  		"",
   590  		false,
   591  		mtime,
   592  	)
   593  	require.NoError(t, err)
   594  
   595  	expected := files.Contents{
   596  		{
   597  			Source:      "",
   598  			Destination: "/foo/",
   599  			Type:        files.TypeImplicitDir,
   600  		},
   601  		{
   602  			Source:      "",
   603  			Destination: "/foo/bar/",
   604  			Type:        files.TypeImplicitDir,
   605  		},
   606  		{
   607  			Source:      "testdata/globtest/a.txt",
   608  			Destination: "/foo/bar/baz",
   609  			Type:        files.TypeFile,
   610  		},
   611  	}
   612  
   613  	require.Equal(t, expected, withoutFileInfo(results))
   614  }
   615  
   616  func TestRelevantFiles(t *testing.T) {
   617  	contents := files.Contents{
   618  		{
   619  			Source:      "./testdata/globtest/a.txt",
   620  			Destination: "/1allpackagers",
   621  		},
   622  		{
   623  			Source:      "./testdata/globtest/a.txt",
   624  			Destination: "/2onlyrpm",
   625  			Packager:    "rpm",
   626  		},
   627  		{
   628  			Source:      "./testdata/globtest/a.txt",
   629  			Destination: "/3onlydeb",
   630  			Packager:    "deb",
   631  		},
   632  		{
   633  			Source:      "./testdata/globtest/a.txt",
   634  			Destination: "/4debchangelog",
   635  			Type:        files.TypeDebChangelog,
   636  		},
   637  		{
   638  			Source:      "./testdata/globtest/a.txt",
   639  			Destination: "/5ghost",
   640  			Type:        files.TypeRPMGhost,
   641  		},
   642  		{
   643  			Source:      "./testdata/globtest/a.txt",
   644  			Destination: "/6doc",
   645  			Type:        files.TypeRPMDoc,
   646  		},
   647  		{
   648  			Source:      "./testdata/globtest/a.txt",
   649  			Destination: "/7licence",
   650  			Type:        files.TypeRPMLicence,
   651  		},
   652  		{
   653  			Source:      "./testdata/globtest/a.txt",
   654  			Destination: "/8license",
   655  			Type:        files.TypeRPMLicense,
   656  		},
   657  		{
   658  			Source:      "./testdata/globtest/a.txt",
   659  			Destination: "/9readme",
   660  			Type:        files.TypeRPMReadme,
   661  		},
   662  	}
   663  
   664  	t.Run("deb", func(t *testing.T) {
   665  		results, err := files.PrepareForPackager(
   666  			contents,
   667  			0,
   668  			"deb",
   669  			false,
   670  			mtime,
   671  		)
   672  		require.NoError(t, err)
   673  		require.Equal(t, files.Contents{
   674  			{
   675  				Source:      "testdata/globtest/a.txt",
   676  				Destination: "/1allpackagers",
   677  				Type:        files.TypeFile,
   678  			},
   679  			{
   680  				Source:      "testdata/globtest/a.txt",
   681  				Destination: "/3onlydeb",
   682  				Packager:    "deb",
   683  				Type:        files.TypeFile,
   684  			},
   685  			{
   686  				Source:      "testdata/globtest/a.txt",
   687  				Destination: "/4debchangelog",
   688  				Type:        files.TypeDebChangelog,
   689  			},
   690  		}, withoutFileInfo(results))
   691  	})
   692  
   693  	t.Run("rpm", func(t *testing.T) {
   694  		results, err := files.PrepareForPackager(
   695  			contents,
   696  			0,
   697  			"rpm",
   698  			false,
   699  			mtime,
   700  		)
   701  		require.NoError(t, err)
   702  		require.Equal(t, files.Contents{
   703  			{
   704  				Source:      "testdata/globtest/a.txt",
   705  				Destination: "/1allpackagers",
   706  				Type:        files.TypeFile,
   707  			},
   708  			{
   709  				Source:      "testdata/globtest/a.txt",
   710  				Destination: "/2onlyrpm",
   711  				Packager:    "rpm",
   712  				Type:        files.TypeFile,
   713  			},
   714  			{
   715  				Source:      "testdata/globtest/a.txt",
   716  				Destination: "/5ghost",
   717  				Type:        files.TypeRPMGhost,
   718  			},
   719  			{
   720  				Source:      "testdata/globtest/a.txt",
   721  				Destination: "/6doc",
   722  				Type:        files.TypeRPMDoc,
   723  			},
   724  			{
   725  				Source:      "testdata/globtest/a.txt",
   726  				Destination: "/7licence",
   727  				Type:        files.TypeRPMLicence,
   728  			},
   729  			{
   730  				Source:      "testdata/globtest/a.txt",
   731  				Destination: "/8license",
   732  				Type:        files.TypeRPMLicense,
   733  			},
   734  			{
   735  				Source:      "testdata/globtest/a.txt",
   736  				Destination: "/9readme",
   737  				Type:        files.TypeRPMReadme,
   738  			},
   739  		}, withoutFileInfo(results))
   740  	})
   741  
   742  	t.Run("apk", func(t *testing.T) {
   743  		results, err := files.PrepareForPackager(
   744  			contents,
   745  			0,
   746  			"apk",
   747  			false,
   748  			mtime,
   749  		)
   750  		require.NoError(t, err)
   751  		require.Equal(t, files.Contents{
   752  			{
   753  				Source:      "testdata/globtest/a.txt",
   754  				Destination: "/1allpackagers",
   755  				Type:        files.TypeFile,
   756  			},
   757  		}, withoutFileInfo(results))
   758  	})
   759  }
   760  
   761  func TestTreeOwner(t *testing.T) {
   762  	results, err := files.PrepareForPackager(
   763  		files.Contents{
   764  			{
   765  				Source:      filepath.Join("testdata", "tree"),
   766  				Destination: "/usr/share/foo",
   767  				Type:        files.TypeTree,
   768  				FileInfo: &files.ContentFileInfo{
   769  					Owner: "goreleaser",
   770  					Group: "goreleaser",
   771  					MTime: mtime,
   772  				},
   773  			},
   774  		},
   775  		0,
   776  		"",
   777  		false,
   778  		mtime,
   779  	)
   780  	require.NoError(t, err)
   781  
   782  	for _, f := range results {
   783  		if f.Destination == "/usr/" || f.Destination == "/usr/share/" {
   784  			require.Equal(t, "root", f.FileInfo.Owner, f.Destination)
   785  			require.Equal(t, "root", f.FileInfo.Group, f.Destination)
   786  			continue
   787  		}
   788  		require.Equal(t, "goreleaser", f.FileInfo.Owner, f.Destination)
   789  		require.Equal(t, "goreleaser", f.FileInfo.Group, f.Destination)
   790  	}
   791  
   792  	require.Equal(t, files.Contents{
   793  		{
   794  			Source:      "",
   795  			Destination: "/usr/",
   796  			Type:        files.TypeImplicitDir,
   797  		},
   798  		{
   799  			Source:      "",
   800  			Destination: "/usr/share/",
   801  			Type:        files.TypeImplicitDir,
   802  		},
   803  		{
   804  			Source:      "",
   805  			Destination: "/usr/share/foo/",
   806  			Type:        files.TypeDir,
   807  		},
   808  		{
   809  			Source:      "",
   810  			Destination: "/usr/share/foo/files/",
   811  			Type:        files.TypeDir,
   812  		},
   813  		{
   814  			Source:      filepath.Join("testdata", "tree", "files", "a"),
   815  			Destination: "/usr/share/foo/files/a",
   816  			Type:        files.TypeFile,
   817  		},
   818  		{
   819  			Source:      "",
   820  			Destination: "/usr/share/foo/files/b/",
   821  			Type:        files.TypeDir,
   822  		},
   823  		{
   824  			Source:      filepath.Join("testdata", "tree", "files", "b", "c"),
   825  			Destination: "/usr/share/foo/files/b/c",
   826  			Type:        files.TypeFile,
   827  		},
   828  		{
   829  			Source:      "",
   830  			Destination: "/usr/share/foo/symlinks/",
   831  			Type:        files.TypeDir,
   832  		},
   833  		{
   834  			Source:      "/etc/foo",
   835  			Destination: "/usr/share/foo/symlinks/link1",
   836  			Type:        files.TypeSymlink,
   837  		},
   838  		{
   839  			Source:      "../files/a",
   840  			Destination: "/usr/share/foo/symlinks/link2",
   841  			Type:        files.TypeSymlink,
   842  		},
   843  	}, withoutFileInfo(results))
   844  }
   845  
   846  func TestTree(t *testing.T) {
   847  	results, err := files.PrepareForPackager(
   848  		files.Contents{
   849  			{
   850  				Source:      filepath.Join("testdata", "tree"),
   851  				Destination: "/base",
   852  				Type:        files.TypeTree,
   853  			},
   854  		},
   855  		0,
   856  		"",
   857  		false,
   858  		mtime,
   859  	)
   860  	require.NoError(t, err)
   861  
   862  	require.Equal(t, files.Contents{
   863  		{
   864  			Source:      "",
   865  			Destination: "/base/",
   866  			Type:        files.TypeDir,
   867  		},
   868  		{
   869  			Source:      "",
   870  			Destination: "/base/files/",
   871  			Type:        files.TypeDir,
   872  		},
   873  		{
   874  			Source:      filepath.Join("testdata", "tree", "files", "a"),
   875  			Destination: "/base/files/a",
   876  			Type:        files.TypeFile,
   877  		},
   878  		{
   879  			Source:      "",
   880  			Destination: "/base/files/b/",
   881  			Type:        files.TypeDir,
   882  		},
   883  		{
   884  			Source:      filepath.Join("testdata", "tree", "files", "b", "c"),
   885  			Destination: "/base/files/b/c",
   886  			Type:        files.TypeFile,
   887  		},
   888  		{
   889  			Source:      "",
   890  			Destination: "/base/symlinks/",
   891  			Type:        files.TypeDir,
   892  		},
   893  		{
   894  			Source:      "/etc/foo",
   895  			Destination: "/base/symlinks/link1",
   896  			Type:        files.TypeSymlink,
   897  		},
   898  		{
   899  			Source:      "../files/a",
   900  			Destination: "/base/symlinks/link2",
   901  			Type:        files.TypeSymlink,
   902  		},
   903  	}, withoutFileInfo(results))
   904  }
   905  
   906  func TestTreeMode(t *testing.T) {
   907  	results, err := files.PrepareForPackager(
   908  		files.Contents{
   909  			{
   910  				Source:      filepath.Join("testdata", "tree"),
   911  				Destination: "/usr/share/foo",
   912  				Type:        files.TypeTree,
   913  				FileInfo: &files.ContentFileInfo{
   914  					Mode: 0o777,
   915  				},
   916  			},
   917  		},
   918  		0,
   919  		"",
   920  		false,
   921  		mtime,
   922  	)
   923  	require.NoError(t, err)
   924  
   925  	for _, f := range results {
   926  		if f.Destination == "/usr/" || f.Destination == "/usr/share/" || f.Type == files.TypeSymlink {
   927  			require.NotEqual(t, fs.FileMode(0o777).String(), f.FileInfo.Mode.String(), f.Destination)
   928  			continue
   929  		}
   930  		require.Equal(t, fs.FileMode(0o777).String(), f.FileInfo.Mode.String(), "%s: %s", f.Destination, f.Type)
   931  	}
   932  }
   933  
   934  func withoutFileInfo(contents files.Contents) files.Contents {
   935  	filtered := make(files.Contents, 0, len(contents))
   936  
   937  	for _, c := range contents {
   938  		cc := *c
   939  		cc.FileInfo = nil
   940  		filtered = append(filtered, &cc)
   941  	}
   942  
   943  	return filtered
   944  }
   945  
   946  func TestAsRelativePath(t *testing.T) {
   947  	sep := fmt.Sprintf("%c", filepath.Separator)
   948  	testCases := map[string]string{
   949  		"/etc/foo/":         "etc/foo/",
   950  		"./etc/foo":         "etc/foo",
   951  		"./././foo/../bar/": "bar/",
   952  		sep:                 "",
   953  		sep + sep:           "",
   954  		sep + strings.Join([]string{"foo", "bar", "zazz"}, sep): "foo/bar/zazz",
   955  	}
   956  
   957  	for input, expected := range testCases {
   958  		assert.Equal(t, expected, files.AsRelativePath(input))
   959  	}
   960  }
   961  
   962  func TestAsExplicitRelativePath(t *testing.T) {
   963  	sep := fmt.Sprintf("%c", filepath.Separator)
   964  	testCases := map[string]string{
   965  		"/etc/foo/":         "./etc/foo/",
   966  		"./etc/foo":         "./etc/foo",
   967  		"./././foo/../bar/": "./bar/",
   968  		sep:                 "./",
   969  		sep:                 "./",
   970  		sep + strings.Join([]string{"foo", "bar", "zazz"}, sep): "./foo/bar/zazz",
   971  	}
   972  
   973  	for input, expected := range testCases {
   974  		assert.Equal(t, expected, files.AsExplicitRelativePath(input))
   975  	}
   976  }
   977  
   978  func TestIssue954(t *testing.T) {
   979  	tmpDir := t.TempDir()
   980  	contentDir := filepath.Join(tmpDir, "content")
   981  	require.NoError(t, os.MkdirAll(filepath.Join(contentDir, "subdir1"), 0o755))
   982  	require.NoError(t, os.MkdirAll(filepath.Join(contentDir, "subdir2"), 0o755))
   983  	require.NoError(t, os.WriteFile(filepath.Join(contentDir, "subdir1", "file1.txt"), []byte("test"), 0o644))
   984  	require.NoError(t, os.WriteFile(filepath.Join(contentDir, "subdir2", "file2.txt"), []byte("test"), 0o644))
   985  
   986  	results, err := files.PrepareForPackager(
   987  		files.Contents{
   988  			{
   989  				Source:      contentDir + "/",
   990  				Destination: "/srv/www",
   991  				FileInfo: &files.ContentFileInfo{
   992  					Owner: "www-data",
   993  					Group: "www-data",
   994  					Mode:  0o775,
   995  				},
   996  			},
   997  		},
   998  		0,
   999  		"",
  1000  		false,
  1001  		mtime,
  1002  	)
  1003  	require.NoError(t, err)
  1004  
  1005  	found := []string{}
  1006  	for _, f := range results {
  1007  		found = append(found, f.Destination)
  1008  		if f.Destination == "/srv/" {
  1009  			require.Equal(t, "root", f.FileInfo.Owner, f.Destination)
  1010  			require.Equal(t, "root", f.FileInfo.Group, f.Destination)
  1011  			continue
  1012  		}
  1013  		require.Contains(t, f.Destination, "/srv/www")
  1014  		require.Equal(t, "www-data", f.FileInfo.Owner, f.Destination)
  1015  		require.Equal(t, "www-data", f.FileInfo.Group, f.Destination)
  1016  
  1017  	}
  1018  	require.ElementsMatch(t, []string{
  1019  		"/srv/",
  1020  		"/srv/www/",
  1021  		"/srv/www/subdir1/",
  1022  		"/srv/www/subdir1/file1.txt",
  1023  		"/srv/www/subdir2/",
  1024  		"/srv/www/subdir2/file2.txt",
  1025  	}, found)
  1026  }
  1027  
  1028  func TestIssue952(t *testing.T) {
  1029  	contents := files.Contents{
  1030  		{
  1031  			Source:      "/source",
  1032  			Destination: "/dest",
  1033  			Type:        files.TypeSymlink,
  1034  		},
  1035  	}
  1036  
  1037  	result, err := files.PrepareForPackager(contents, 0, "", false, mtime)
  1038  	require.NoError(t, err)
  1039  	require.Len(t, result, 1)
  1040  
  1041  	symlink := result[0]
  1042  	require.Equal(t, files.TypeSymlink, symlink.Type)
  1043  	require.Equal(t, "/dest", symlink.Destination)
  1044  	require.Equal(t, mtime, symlink.FileInfo.MTime)
  1045  }
  1046  
  1047  func TestIssue829(t *testing.T) {
  1048  	var config testStruct
  1049  	dec := yaml.NewDecoder(strings.NewReader(`---
  1050  contents:
  1051    # Implementation.
  1052    - src: ./testdata/issue829/lib/
  1053      dst: /usr/lib/
  1054      type: tree
  1055    - src: ./testdata/issue829/usr/
  1056      dst: /usr/
  1057      type: tree
  1058    - src: ./testdata/issue829/var/
  1059      dst: /var/
  1060      type: tree
  1061  
  1062    # Configuration.
  1063    - src: ./testdata/issue829/etc/logrotate.d/acme
  1064      dst: /etc/logrotate.d/acme
  1065      type: config|noreplace
  1066    - src: ./testdata/issue829/etc/sysconfig/acme
  1067      dst: /etc/sysconfig/acme
  1068      type: config|noreplace
  1069  `))
  1070  	dec.KnownFields(true)
  1071  	require.NoError(t, dec.Decode(&config))
  1072  	files, err := files.PrepareForPackager(
  1073  		config.Contents,
  1074  		0,
  1075  		"",
  1076  		false,
  1077  		mtime,
  1078  	)
  1079  	require.NoError(t, err)
  1080  
  1081  	expect := map[string]string{
  1082  		"/etc/":                                "implicit dir",
  1083  		"/etc/logrotate.d/":                    "implicit dir",
  1084  		"/etc/logrotate.d/acme":                "config|noreplace",
  1085  		"/etc/sysconfig/":                      "implicit dir",
  1086  		"/etc/sysconfig/acme":                  "config|noreplace",
  1087  		"/usr/lib/":                            "implicit dir",
  1088  		"/usr/lib/systemd/":                    "implicit dir",
  1089  		"/usr/lib/systemd/system/":             "implicit dir",
  1090  		"/usr/lib/systemd/system/acme.service": "file",
  1091  		"/usr/":                                "implicit dir",
  1092  		"/usr/bin/":                            "implicit dir",
  1093  		"/usr/bin/acme":                        "file",
  1094  		"/usr/share/":                          "implicit dir",
  1095  		"/usr/share/doc/":                      "implicit dir",
  1096  		"/usr/share/doc/acme/":                 "dir",
  1097  		"/usr/share/doc/acme/readme.md":        "file",
  1098  		"/usr/share/man/":                      "implicit dir",
  1099  		"/usr/share/man/man1/":                 "implicit dir",
  1100  		"/usr/share/man/man1/acme.1.gz":        "file",
  1101  		"/var/":                                "implicit dir",
  1102  		"/var/log/":                            "implicit dir",
  1103  		"/var/log/acme/":                       "dir",
  1104  		"/var/log/acme/.gitkeep":               "file",
  1105  	}
  1106  
  1107  	for _, file := range files {
  1108  		require.Equal(t, expect[file.Destination], file.Type, "invalid type for %s", file.Destination)
  1109  	}
  1110  }