github.com/nextlinux/gosbom@v0.81.1-0.20230627115839-1ff50c281391/gosbom/source/source_test.go (about)

     1  //go:build !windows
     2  // +build !windows
     3  
     4  package source
     5  
     6  import (
     7  	"io"
     8  	"io/fs"
     9  	"os"
    10  	"os/exec"
    11  	"path"
    12  	"path/filepath"
    13  	"sort"
    14  	"strings"
    15  	"syscall"
    16  	"testing"
    17  	"time"
    18  
    19  	"github.com/google/go-cmp/cmp"
    20  	"github.com/nextlinux/gosbom/gosbom/artifact"
    21  	"github.com/nextlinux/gosbom/gosbom/internal/fileresolver"
    22  	"github.com/stretchr/testify/assert"
    23  	"github.com/stretchr/testify/require"
    24  
    25  	"github.com/anchore/stereoscope/pkg/image"
    26  	"github.com/anchore/stereoscope/pkg/imagetest"
    27  )
    28  
    29  func TestParseInput(t *testing.T) {
    30  	tests := []struct {
    31  		name     string
    32  		input    string
    33  		platform string
    34  		expected Scheme
    35  		errFn    require.ErrorAssertionFunc
    36  	}{
    37  		{
    38  			name:     "ParseInput parses a file input",
    39  			input:    "test-fixtures/image-simple/file-1.txt",
    40  			expected: FileScheme,
    41  		},
    42  		{
    43  			name:     "errors out when using platform for non-image scheme",
    44  			input:    "test-fixtures/image-simple/file-1.txt",
    45  			platform: "arm64",
    46  			errFn:    require.Error,
    47  		},
    48  	}
    49  
    50  	for _, test := range tests {
    51  		t.Run(test.name, func(t *testing.T) {
    52  			if test.errFn == nil {
    53  				test.errFn = require.NoError
    54  			}
    55  			sourceInput, err := ParseInput(test.input, test.platform)
    56  			test.errFn(t, err)
    57  			if test.expected != "" {
    58  				require.NotNil(t, sourceInput)
    59  				assert.Equal(t, sourceInput.Scheme, test.expected)
    60  			}
    61  		})
    62  	}
    63  }
    64  
    65  func TestNewFromImageFails(t *testing.T) {
    66  	t.Run("no image given", func(t *testing.T) {
    67  		_, err := NewFromImage(nil, "")
    68  		if err == nil {
    69  			t.Errorf("expected an error condition but none was given")
    70  		}
    71  	})
    72  }
    73  
    74  func TestSetID(t *testing.T) {
    75  	layer := image.NewLayer(nil)
    76  	layer.Metadata = image.LayerMetadata{
    77  		Digest: "sha256:6f4fb385d4e698647bf2a450749dfbb7bc2831ec9a730ef4046c78c08d468e89",
    78  	}
    79  	img := image.Image{
    80  		Layers: []*image.Layer{layer},
    81  	}
    82  
    83  	tests := []struct {
    84  		name     string
    85  		input    *Source
    86  		expected artifact.ID
    87  	}{
    88  		{
    89  			name: "source.SetID sets the ID for FileScheme",
    90  			input: &Source{
    91  				Metadata: Metadata{
    92  					Scheme: FileScheme,
    93  					Path:   "test-fixtures/image-simple/file-1.txt",
    94  				},
    95  			},
    96  			expected: artifact.ID("55096713247489add592ce977637be868497132b36d1e294a3831925ec64319a"),
    97  		},
    98  		{
    99  			name: "source.SetID sets the ID for ImageScheme",
   100  			input: &Source{
   101  				Image: &img,
   102  				Metadata: Metadata{
   103  					Scheme: ImageScheme,
   104  				},
   105  			},
   106  			expected: artifact.ID("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"),
   107  		},
   108  		{
   109  			name: "source.SetID sets the ID for DirectoryScheme",
   110  			input: &Source{
   111  				Image: &img,
   112  				Metadata: Metadata{
   113  					Scheme: DirectoryScheme,
   114  					Path:   "test-fixtures/image-simple",
   115  				},
   116  			},
   117  			expected: artifact.ID("91db61e5e0ae097ef764796ce85e442a93f2a03e5313d4c7307e9b413f62e8c4"),
   118  		},
   119  		{
   120  			name: "source.SetID sets the ID for UnknownScheme",
   121  			input: &Source{
   122  				Image: &img,
   123  				Metadata: Metadata{
   124  					Scheme: UnknownScheme,
   125  					Path:   "test-fixtures/image-simple",
   126  				},
   127  			},
   128  			expected: artifact.ID("9ee9e786412d6ae5"),
   129  		},
   130  	}
   131  
   132  	for _, test := range tests {
   133  		t.Run(test.name, func(t *testing.T) {
   134  			test.input.SetID()
   135  			assert.Equal(t, test.expected, test.input.ID())
   136  		})
   137  	}
   138  }
   139  
   140  func TestNewFromImage(t *testing.T) {
   141  	layer := image.NewLayer(nil)
   142  	img := image.Image{
   143  		Layers: []*image.Layer{layer},
   144  	}
   145  
   146  	t.Run("create a new source object from image", func(t *testing.T) {
   147  		_, err := NewFromImage(&img, "")
   148  		if err != nil {
   149  			t.Errorf("unexpected error when creating a new Locations from img: %+v", err)
   150  		}
   151  	})
   152  }
   153  
   154  func TestNewFromDirectory(t *testing.T) {
   155  	testCases := []struct {
   156  		desc         string
   157  		input        string
   158  		expString    string
   159  		inputPaths   []string
   160  		expectedRefs int
   161  		expectedErr  bool
   162  	}{
   163  		{
   164  			desc:        "no paths exist",
   165  			input:       "foobar/",
   166  			inputPaths:  []string{"/opt/", "/other"},
   167  			expectedErr: true,
   168  		},
   169  		{
   170  			desc:         "path detected",
   171  			input:        "test-fixtures",
   172  			inputPaths:   []string{"path-detected/.vimrc"},
   173  			expectedRefs: 1,
   174  		},
   175  		{
   176  			desc:         "directory ignored",
   177  			input:        "test-fixtures",
   178  			inputPaths:   []string{"path-detected"},
   179  			expectedRefs: 0,
   180  		},
   181  		{
   182  			desc:         "no files-by-path detected",
   183  			input:        "test-fixtures",
   184  			inputPaths:   []string{"no-path-detected"},
   185  			expectedRefs: 0,
   186  		},
   187  	}
   188  	for _, test := range testCases {
   189  		t.Run(test.desc, func(t *testing.T) {
   190  			src, err := NewFromDirectory(test.input)
   191  			require.NoError(t, err)
   192  			assert.Equal(t, test.input, src.Metadata.Path)
   193  
   194  			res, err := src.FileResolver(SquashedScope)
   195  			if test.expectedErr {
   196  				if err == nil {
   197  					t.Fatal("expected an error when making the resolver but got none")
   198  				}
   199  				return
   200  			} else {
   201  				require.NoError(t, err)
   202  			}
   203  
   204  			refs, err := res.FilesByPath(test.inputPaths...)
   205  			if err != nil {
   206  				t.Errorf("FilesByPath call produced an error: %+v", err)
   207  			}
   208  			if len(refs) != test.expectedRefs {
   209  				t.Errorf("unexpected number of refs returned: %d != %d", len(refs), test.expectedRefs)
   210  
   211  			}
   212  
   213  		})
   214  	}
   215  }
   216  
   217  func TestNewFromFile(t *testing.T) {
   218  	testCases := []struct {
   219  		desc       string
   220  		input      string
   221  		expString  string
   222  		inputPaths []string
   223  		expRefs    int
   224  	}{
   225  		{
   226  			desc:       "path detected",
   227  			input:      "test-fixtures/path-detected",
   228  			inputPaths: []string{"/.vimrc"},
   229  			expRefs:    1,
   230  		},
   231  	}
   232  	for _, test := range testCases {
   233  		t.Run(test.desc, func(t *testing.T) {
   234  			src, cleanup := NewFromFile(test.input)
   235  			if cleanup != nil {
   236  				t.Cleanup(cleanup)
   237  			}
   238  
   239  			assert.Equal(t, test.input, src.Metadata.Path)
   240  			assert.Equal(t, src.Metadata.Path, src.path)
   241  
   242  			res, err := src.FileResolver(SquashedScope)
   243  			require.NoError(t, err)
   244  
   245  			refs, err := res.FilesByPath(test.inputPaths...)
   246  			require.NoError(t, err)
   247  			assert.Len(t, refs, test.expRefs)
   248  
   249  		})
   250  	}
   251  }
   252  
   253  func TestNewFromFile_WithArchive(t *testing.T) {
   254  	testCases := []struct {
   255  		desc       string
   256  		input      string
   257  		expString  string
   258  		inputPaths []string
   259  		expRefs    int
   260  		layer2     bool
   261  		contents   string
   262  	}{
   263  		{
   264  			desc:       "path detected",
   265  			input:      "test-fixtures/path-detected",
   266  			inputPaths: []string{"/.vimrc"},
   267  			expRefs:    1,
   268  		},
   269  		{
   270  			desc:       "lest entry for duplicate paths",
   271  			input:      "test-fixtures/path-detected",
   272  			inputPaths: []string{"/.vimrc"},
   273  			expRefs:    1,
   274  			layer2:     true,
   275  			contents:   "Another .vimrc file",
   276  		},
   277  	}
   278  	for _, test := range testCases {
   279  		t.Run(test.desc, func(t *testing.T) {
   280  			archivePath := setupArchiveTest(t, test.input, test.layer2)
   281  
   282  			src, cleanup := NewFromFile(archivePath)
   283  			if cleanup != nil {
   284  				t.Cleanup(cleanup)
   285  			}
   286  
   287  			assert.Equal(t, archivePath, src.Metadata.Path)
   288  			assert.NotEqual(t, src.Metadata.Path, src.path)
   289  
   290  			res, err := src.FileResolver(SquashedScope)
   291  			require.NoError(t, err)
   292  
   293  			refs, err := res.FilesByPath(test.inputPaths...)
   294  			require.NoError(t, err)
   295  			assert.Len(t, refs, test.expRefs)
   296  
   297  			if test.contents != "" {
   298  				reader, err := res.FileContentsByLocation(refs[0])
   299  				require.NoError(t, err)
   300  
   301  				data, err := io.ReadAll(reader)
   302  				require.NoError(t, err)
   303  
   304  				assert.Equal(t, test.contents, string(data))
   305  			}
   306  
   307  		})
   308  	}
   309  }
   310  
   311  func TestNewFromDirectoryShared(t *testing.T) {
   312  	testCases := []struct {
   313  		desc       string
   314  		input      string
   315  		expString  string
   316  		notExist   string
   317  		inputPaths []string
   318  		expRefs    int
   319  	}{
   320  		{
   321  			desc:       "path detected",
   322  			input:      "test-fixtures",
   323  			notExist:   "foobar/",
   324  			inputPaths: []string{"path-detected/.vimrc"},
   325  			expRefs:    1,
   326  		},
   327  		{
   328  			desc:       "directory ignored",
   329  			input:      "test-fixtures",
   330  			notExist:   "foobar/",
   331  			inputPaths: []string{"path-detected"},
   332  			expRefs:    0,
   333  		},
   334  		{
   335  			desc:       "no files-by-path detected",
   336  			input:      "test-fixtures",
   337  			notExist:   "foobar/",
   338  			inputPaths: []string{"no-path-detected"},
   339  			expRefs:    0,
   340  		},
   341  	}
   342  	for _, test := range testCases {
   343  		t.Run(test.desc, func(t *testing.T) {
   344  			src, err := NewFromDirectory(test.input)
   345  
   346  			if err != nil {
   347  				t.Errorf("could not create NewDirScope: %+v", err)
   348  			}
   349  			if src.Metadata.Path != test.input {
   350  				t.Errorf("mismatched stringer: '%s' != '%s'", src.Metadata.Path, test.input)
   351  			}
   352  
   353  			_, err = src.FileResolver(SquashedScope)
   354  			assert.NoError(t, err)
   355  
   356  			src.Metadata.Path = test.notExist
   357  			resolver, err := src.FileResolver(SquashedScope)
   358  			assert.NoError(t, err)
   359  
   360  			refs, err := resolver.FilesByPath(test.inputPaths...)
   361  			if err != nil {
   362  				t.Errorf("FilesByPath call produced an error: %+v", err)
   363  			}
   364  			if len(refs) != test.expRefs {
   365  				t.Errorf("unexpected number of refs returned: %d != %d", len(refs), test.expRefs)
   366  
   367  			}
   368  
   369  		})
   370  	}
   371  }
   372  
   373  func TestFilesByPathDoesNotExist(t *testing.T) {
   374  	testCases := []struct {
   375  		desc     string
   376  		input    string
   377  		path     string
   378  		expected string
   379  	}{
   380  		{
   381  			input: "test-fixtures/path-detected",
   382  			desc:  "path does not exist",
   383  			path:  "foo",
   384  		},
   385  	}
   386  	for _, test := range testCases {
   387  		t.Run(test.desc, func(t *testing.T) {
   388  			src, err := NewFromDirectory(test.input)
   389  			if err != nil {
   390  				t.Errorf("could not create NewDirScope: %+v", err)
   391  			}
   392  			res, err := src.FileResolver(SquashedScope)
   393  			if err != nil {
   394  				t.Errorf("could not get resolver error: %+v", err)
   395  			}
   396  			refs, err := res.FilesByPath(test.path)
   397  			if err != nil {
   398  				t.Errorf("could not get file references from path: %s, %v", test.path, err)
   399  			}
   400  
   401  			if len(refs) != 0 {
   402  				t.Errorf("didnt' expect a ref, but got: %d", len(refs))
   403  			}
   404  
   405  		})
   406  	}
   407  }
   408  
   409  func TestFilesByGlob(t *testing.T) {
   410  	testCases := []struct {
   411  		desc     string
   412  		input    string
   413  		glob     string
   414  		expected int
   415  	}{
   416  		{
   417  			input:    "test-fixtures",
   418  			desc:     "no matches",
   419  			glob:     "bar/foo",
   420  			expected: 0,
   421  		},
   422  		{
   423  			input:    "test-fixtures/path-detected",
   424  			desc:     "a single match",
   425  			glob:     "**/*vimrc",
   426  			expected: 1,
   427  		},
   428  		{
   429  			input:    "test-fixtures/path-detected",
   430  			desc:     "multiple matches",
   431  			glob:     "**",
   432  			expected: 2,
   433  		},
   434  	}
   435  	for _, test := range testCases {
   436  		t.Run(test.desc, func(t *testing.T) {
   437  			src, err := NewFromDirectory(test.input)
   438  			if err != nil {
   439  				t.Errorf("could not create NewDirScope: %+v", err)
   440  			}
   441  			res, err := src.FileResolver(SquashedScope)
   442  			if err != nil {
   443  				t.Errorf("could not get resolver error: %+v", err)
   444  			}
   445  			contents, err := res.FilesByGlob(test.glob)
   446  			if err != nil {
   447  				t.Errorf("could not get files by glob: %s+v", err)
   448  			}
   449  			if len(contents) != test.expected {
   450  				t.Errorf("unexpected number of files found by glob (%s): %d != %d", test.glob, len(contents), test.expected)
   451  			}
   452  
   453  		})
   454  	}
   455  }
   456  
   457  func TestDirectoryExclusions(t *testing.T) {
   458  	testCases := []struct {
   459  		desc       string
   460  		input      string
   461  		glob       string
   462  		expected   []string
   463  		exclusions []string
   464  		err        bool
   465  	}{
   466  		{
   467  			input:      "test-fixtures/system_paths",
   468  			desc:       "exclude everything",
   469  			glob:       "**",
   470  			expected:   nil,
   471  			exclusions: []string{"**/*"},
   472  		},
   473  		{
   474  			input: "test-fixtures/image-simple",
   475  			desc:  "a single path excluded",
   476  			glob:  "**",
   477  			expected: []string{
   478  				"Dockerfile",
   479  				"file-1.txt",
   480  				"file-2.txt",
   481  			},
   482  			exclusions: []string{"**/target/**"},
   483  		},
   484  		{
   485  			input: "test-fixtures/image-simple",
   486  			desc:  "exclude explicit directory relative to the root",
   487  			glob:  "**",
   488  			expected: []string{
   489  				"Dockerfile",
   490  				"file-1.txt",
   491  				"file-2.txt",
   492  				//"target/really/nested/file-3.txt", // explicitly skipped
   493  			},
   494  			exclusions: []string{"./target"},
   495  		},
   496  		{
   497  			input: "test-fixtures/image-simple",
   498  			desc:  "exclude explicit file relative to the root",
   499  			glob:  "**",
   500  			expected: []string{
   501  				"Dockerfile",
   502  				//"file-1.txt",  // explicitly skipped
   503  				"file-2.txt",
   504  				"target/really/nested/file-3.txt",
   505  			},
   506  			exclusions: []string{"./file-1.txt"},
   507  		},
   508  		{
   509  			input: "test-fixtures/image-simple",
   510  			desc:  "exclude wildcard relative to the root",
   511  			glob:  "**",
   512  			expected: []string{
   513  				"Dockerfile",
   514  				//"file-1.txt",  // explicitly skipped
   515  				//"file-2.txt", // explicitly skipped
   516  				"target/really/nested/file-3.txt",
   517  			},
   518  			exclusions: []string{"./*.txt"},
   519  		},
   520  		{
   521  			input: "test-fixtures/image-simple",
   522  			desc:  "exclude files deeper",
   523  			glob:  "**",
   524  			expected: []string{
   525  				"Dockerfile",
   526  				"file-1.txt",
   527  				"file-2.txt",
   528  				//"target/really/nested/file-3.txt", // explicitly skipped
   529  			},
   530  			exclusions: []string{"**/really/**"},
   531  		},
   532  		{
   533  			input: "test-fixtures/image-simple",
   534  			desc:  "files excluded with extension",
   535  			glob:  "**",
   536  			expected: []string{
   537  				"Dockerfile",
   538  				//"file-1.txt",  // explicitly skipped
   539  				//"file-2.txt", // explicitly skipped
   540  				//"target/really/nested/file-3.txt", // explicitly skipped
   541  			},
   542  			exclusions: []string{"**/*.txt"},
   543  		},
   544  		{
   545  			input: "test-fixtures/image-simple",
   546  			desc:  "keep files with different extensions",
   547  			glob:  "**",
   548  			expected: []string{
   549  				"Dockerfile",
   550  				"file-1.txt",
   551  				"file-2.txt",
   552  				"target/really/nested/file-3.txt",
   553  			},
   554  			exclusions: []string{"**/target/**/*.jar"},
   555  		},
   556  		{
   557  			input: "test-fixtures/path-detected",
   558  			desc:  "file directly excluded",
   559  			glob:  "**",
   560  			expected: []string{
   561  				".vimrc",
   562  			},
   563  			exclusions: []string{"**/empty"},
   564  		},
   565  		{
   566  			input: "test-fixtures/path-detected",
   567  			desc:  "pattern error containing **/",
   568  			glob:  "**",
   569  			expected: []string{
   570  				".vimrc",
   571  			},
   572  			exclusions: []string{"/**/empty"},
   573  			err:        true,
   574  		},
   575  		{
   576  			input: "test-fixtures/path-detected",
   577  			desc:  "pattern error incorrect start",
   578  			glob:  "**",
   579  			expected: []string{
   580  				".vimrc",
   581  			},
   582  			exclusions: []string{"empty"},
   583  			err:        true,
   584  		},
   585  		{
   586  			input: "test-fixtures/path-detected",
   587  			desc:  "pattern error starting with /",
   588  			glob:  "**",
   589  			expected: []string{
   590  				".vimrc",
   591  			},
   592  			exclusions: []string{"/empty"},
   593  			err:        true,
   594  		},
   595  	}
   596  	registryOpts := &image.RegistryOptions{}
   597  	for _, test := range testCases {
   598  		t.Run(test.desc, func(t *testing.T) {
   599  			sourceInput, err := ParseInput("dir:"+test.input, "")
   600  			require.NoError(t, err)
   601  			src, fn, err := New(*sourceInput, registryOpts, test.exclusions)
   602  			defer fn()
   603  
   604  			if test.err {
   605  				_, err = src.FileResolver(SquashedScope)
   606  				if err == nil {
   607  					t.Errorf("expected an error for patterns: %s", strings.Join(test.exclusions, " or "))
   608  				}
   609  				return
   610  			}
   611  
   612  			if err != nil {
   613  				t.Errorf("could not create NewDirScope: %+v", err)
   614  			}
   615  			res, err := src.FileResolver(SquashedScope)
   616  			if err != nil {
   617  				t.Errorf("could not get resolver error: %+v", err)
   618  			}
   619  			locations, err := res.FilesByGlob(test.glob)
   620  			if err != nil {
   621  				t.Errorf("could not get files by glob: %s+v", err)
   622  			}
   623  			var actual []string
   624  			for _, l := range locations {
   625  				actual = append(actual, l.RealPath)
   626  			}
   627  
   628  			sort.Strings(test.expected)
   629  			sort.Strings(actual)
   630  
   631  			assert.Equal(t, test.expected, actual, "diff \n"+cmp.Diff(test.expected, actual))
   632  		})
   633  	}
   634  }
   635  
   636  func TestImageExclusions(t *testing.T) {
   637  	testCases := []struct {
   638  		desc       string
   639  		input      string
   640  		glob       string
   641  		expected   int
   642  		exclusions []string
   643  	}{
   644  		// NOTE: in the Dockerfile, /target is moved to /, which makes /really a top-level dir
   645  		{
   646  			input:      "image-simple",
   647  			desc:       "a single path excluded",
   648  			glob:       "**",
   649  			expected:   2,
   650  			exclusions: []string{"/really/**"},
   651  		},
   652  		{
   653  			input:      "image-simple",
   654  			desc:       "a directly referenced directory is excluded",
   655  			glob:       "**",
   656  			expected:   2,
   657  			exclusions: []string{"/really"},
   658  		},
   659  		{
   660  			input:      "image-simple",
   661  			desc:       "a partial directory is not excluded",
   662  			glob:       "**",
   663  			expected:   3,
   664  			exclusions: []string{"/reall"},
   665  		},
   666  		{
   667  			input:      "image-simple",
   668  			desc:       "exclude files deeper",
   669  			glob:       "**",
   670  			expected:   2,
   671  			exclusions: []string{"**/nested/**"},
   672  		},
   673  		{
   674  			input:      "image-simple",
   675  			desc:       "files excluded with extension",
   676  			glob:       "**",
   677  			expected:   2,
   678  			exclusions: []string{"**/*1.txt"},
   679  		},
   680  		{
   681  			input:      "image-simple",
   682  			desc:       "keep files with different extensions",
   683  			glob:       "**",
   684  			expected:   3,
   685  			exclusions: []string{"**/target/**/*.jar"},
   686  		},
   687  		{
   688  			input:      "image-simple",
   689  			desc:       "file directly excluded",
   690  			glob:       "**",
   691  			expected:   2,
   692  			exclusions: []string{"**/somefile-1.txt"}, // file-1 renamed to somefile-1 in Dockerfile
   693  		},
   694  	}
   695  	registryOpts := &image.RegistryOptions{}
   696  	for _, test := range testCases {
   697  		t.Run(test.desc, func(t *testing.T) {
   698  			archiveLocation := imagetest.PrepareFixtureImage(t, "docker-archive", test.input)
   699  			sourceInput, err := ParseInput(archiveLocation, "")
   700  			require.NoError(t, err)
   701  			src, fn, err := New(*sourceInput, registryOpts, test.exclusions)
   702  			defer fn()
   703  
   704  			if err != nil {
   705  				t.Errorf("could not create NewDirScope: %+v", err)
   706  			}
   707  			res, err := src.FileResolver(SquashedScope)
   708  			if err != nil {
   709  				t.Errorf("could not get resolver error: %+v", err)
   710  			}
   711  			contents, err := res.FilesByGlob(test.glob)
   712  			if err != nil {
   713  				t.Errorf("could not get files by glob: %s+v", err)
   714  			}
   715  			if len(contents) != test.expected {
   716  				t.Errorf("wrong number of files after exclusions (%s): %d != %d", test.glob, len(contents), test.expected)
   717  			}
   718  		})
   719  	}
   720  }
   721  
   722  type dummyInfo struct {
   723  	isDir bool
   724  }
   725  
   726  func (d dummyInfo) Name() string {
   727  	//TODO implement me
   728  	panic("implement me")
   729  }
   730  
   731  func (d dummyInfo) Size() int64 {
   732  	//TODO implement me
   733  	panic("implement me")
   734  }
   735  
   736  func (d dummyInfo) Mode() fs.FileMode {
   737  	//TODO implement me
   738  	panic("implement me")
   739  }
   740  
   741  func (d dummyInfo) ModTime() time.Time {
   742  	//TODO implement me
   743  	panic("implement me")
   744  }
   745  
   746  func (d dummyInfo) IsDir() bool {
   747  	return d.isDir
   748  }
   749  
   750  func (d dummyInfo) Sys() any {
   751  	//TODO implement me
   752  	panic("implement me")
   753  }
   754  
   755  func Test_crossPlatformExclusions(t *testing.T) {
   756  	testCases := []struct {
   757  		desc     string
   758  		root     string
   759  		path     string
   760  		finfo    os.FileInfo
   761  		exclude  string
   762  		walkHint error
   763  	}{
   764  		{
   765  			desc:     "directory exclusion",
   766  			root:     "/",
   767  			path:     "/usr/var/lib",
   768  			exclude:  "**/var/lib",
   769  			finfo:    dummyInfo{isDir: true},
   770  			walkHint: fs.SkipDir,
   771  		},
   772  		{
   773  			desc:     "no file info",
   774  			root:     "/",
   775  			path:     "/usr/var/lib",
   776  			exclude:  "**/var/lib",
   777  			walkHint: fileresolver.ErrSkipPath,
   778  		},
   779  		// linux specific tests...
   780  		{
   781  			desc:     "linux doublestar",
   782  			root:     "/usr",
   783  			path:     "/usr/var/lib/etc.txt",
   784  			exclude:  "**/*.txt",
   785  			finfo:    dummyInfo{isDir: false},
   786  			walkHint: fileresolver.ErrSkipPath,
   787  		},
   788  		{
   789  			desc:    "linux relative",
   790  			root:    "/usr/var/lib",
   791  			path:    "/usr/var/lib/etc.txt",
   792  			exclude: "./*.txt",
   793  			finfo:   dummyInfo{isDir: false},
   794  
   795  			walkHint: fileresolver.ErrSkipPath,
   796  		},
   797  		{
   798  			desc:     "linux one level",
   799  			root:     "/usr",
   800  			path:     "/usr/var/lib/etc.txt",
   801  			exclude:  "*/*.txt",
   802  			finfo:    dummyInfo{isDir: false},
   803  			walkHint: nil,
   804  		},
   805  		// NOTE: since these tests will run in linux and macOS, the windows paths will be
   806  		// considered relative if they do not start with a forward slash and paths with backslashes
   807  		// won't be modified by the filepath.ToSlash call, so these are emulating the result of
   808  		// filepath.ToSlash usage
   809  
   810  		// windows specific tests...
   811  		{
   812  			desc:     "windows doublestar",
   813  			root:     "/C:/User/stuff",
   814  			path:     "/C:/User/stuff/thing.txt",
   815  			exclude:  "**/*.txt",
   816  			finfo:    dummyInfo{isDir: false},
   817  			walkHint: fileresolver.ErrSkipPath,
   818  		},
   819  		{
   820  			desc:     "windows relative",
   821  			root:     "/C:/User/stuff",
   822  			path:     "/C:/User/stuff/thing.txt",
   823  			exclude:  "./*.txt",
   824  			finfo:    dummyInfo{isDir: false},
   825  			walkHint: fileresolver.ErrSkipPath,
   826  		},
   827  		{
   828  			desc:     "windows one level",
   829  			root:     "/C:/User/stuff",
   830  			path:     "/C:/User/stuff/thing.txt",
   831  			exclude:  "*/*.txt",
   832  			finfo:    dummyInfo{isDir: false},
   833  			walkHint: nil,
   834  		},
   835  	}
   836  
   837  	for _, test := range testCases {
   838  		t.Run(test.desc, func(t *testing.T) {
   839  			fns, err := getDirectoryExclusionFunctions(test.root, []string{test.exclude})
   840  			require.NoError(t, err)
   841  
   842  			for _, f := range fns {
   843  				result := f(test.path, test.finfo, nil)
   844  				require.Equal(t, test.walkHint, result)
   845  			}
   846  		})
   847  	}
   848  }
   849  
   850  // createArchive creates a new archive file at destinationArchivePath based on the directory found at sourceDirPath.
   851  func createArchive(t testing.TB, sourceDirPath, destinationArchivePath string, layer2 bool) {
   852  	t.Helper()
   853  
   854  	cwd, err := os.Getwd()
   855  	if err != nil {
   856  		t.Fatalf("unable to get cwd: %+v", err)
   857  	}
   858  
   859  	cmd := exec.Command("./generate-tar-fixture-from-source-dir.sh", destinationArchivePath, path.Base(sourceDirPath))
   860  	cmd.Dir = filepath.Join(cwd, "test-fixtures")
   861  
   862  	if err := cmd.Start(); err != nil {
   863  		t.Fatalf("unable to start generate zip fixture script: %+v", err)
   864  	}
   865  
   866  	if err := cmd.Wait(); err != nil {
   867  		if exiterr, ok := err.(*exec.ExitError); ok {
   868  			// The program has exited with an exit code != 0
   869  
   870  			// This works on both Unix and Windows. Although package
   871  			// syscall is generally platform dependent, WaitStatus is
   872  			// defined for both Unix and Windows and in both cases has
   873  			// an ExitStatus() method with the same signature.
   874  			if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
   875  				if status.ExitStatus() != 0 {
   876  					t.Fatalf("failed to generate fixture: rc=%d", status.ExitStatus())
   877  				}
   878  			}
   879  		} else {
   880  			t.Fatalf("unable to get generate fixture script result: %+v", err)
   881  		}
   882  	}
   883  
   884  	if layer2 {
   885  		cmd = exec.Command("tar", "-rvf", destinationArchivePath, ".")
   886  		cmd.Dir = filepath.Join(cwd, "test-fixtures", path.Base(sourceDirPath+"-2"))
   887  		if err := cmd.Start(); err != nil {
   888  			t.Fatalf("unable to start tar appending fixture script: %+v", err)
   889  		}
   890  		_ = cmd.Wait()
   891  	}
   892  }
   893  
   894  // setupArchiveTest encapsulates common test setup work for tar file tests. It returns a cleanup function,
   895  // which should be called (typically deferred) by the caller, the path of the created tar archive, and an error,
   896  // which should trigger a fatal test failure in the consuming test. The returned cleanup function will never be nil
   897  // (even if there's an error), and it should always be called.
   898  func setupArchiveTest(t testing.TB, sourceDirPath string, layer2 bool) string {
   899  	t.Helper()
   900  
   901  	archivePrefix, err := os.CreateTemp(t.TempDir(), "gosbom-archive-TEST-")
   902  	require.NoError(t, err)
   903  
   904  	destinationArchiveFilePath := archivePrefix.Name() + ".tar"
   905  	t.Logf("archive path: %s", destinationArchiveFilePath)
   906  	createArchive(t, sourceDirPath, destinationArchiveFilePath, layer2)
   907  
   908  	cwd, err := os.Getwd()
   909  	require.NoError(t, err)
   910  
   911  	t.Logf("running from: %s", cwd)
   912  
   913  	return destinationArchiveFilePath
   914  }
   915  
   916  func assertNoError(t testing.TB, fn func() error) func() {
   917  	return func() {
   918  		assert.NoError(t, fn())
   919  	}
   920  }