github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/syft/pkg/cataloger/generic/cataloger_test.go (about)

     1  package generic
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/require"
    12  
    13  	"github.com/anchore/syft/syft/artifact"
    14  	"github.com/anchore/syft/syft/file"
    15  	"github.com/anchore/syft/syft/pkg"
    16  )
    17  
    18  func Test_Cataloger(t *testing.T) {
    19  	allParsedPaths := make(map[string]bool)
    20  	parser := func(_ context.Context, resolver file.Resolver, env *Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
    21  		allParsedPaths[reader.Path()] = true
    22  		contents, err := io.ReadAll(reader)
    23  		require.NoError(t, err)
    24  
    25  		if len(contents) == 0 {
    26  			return nil, nil, nil
    27  		}
    28  
    29  		p := pkg.Package{
    30  			Name:      string(contents),
    31  			Locations: file.NewLocationSet(reader.Location),
    32  		}
    33  		r := artifact.Relationship{
    34  			From: p,
    35  			To:   p,
    36  			Type: artifact.ContainsRelationship,
    37  		}
    38  
    39  		return []pkg.Package{p}, []artifact.Relationship{r}, nil
    40  	}
    41  
    42  	upstream := "some-other-cataloger"
    43  
    44  	expectedSelection := []string{"test-fixtures/last/path.txt", "test-fixtures/another-path.txt", "test-fixtures/a-path.txt", "test-fixtures/empty.txt"}
    45  	resolver := file.NewMockResolverForPaths(expectedSelection...)
    46  	cataloger := NewCataloger(upstream).
    47  		WithParserByPath(parser, "test-fixtures/another-path.txt", "test-fixtures/last/path.txt").
    48  		WithParserByGlobs(parser, "**/a-path.txt", "**/empty.txt")
    49  
    50  	actualPkgs, relationships, err := cataloger.Catalog(context.Background(), resolver)
    51  	assert.NoError(t, err)
    52  
    53  	expectedPkgs := make(map[string]pkg.Package)
    54  	for _, path := range expectedSelection {
    55  		require.True(t, allParsedPaths[path])
    56  		if path == "test-fixtures/empty.txt" {
    57  			continue // note: empty.txt won't become a package
    58  		}
    59  		expectedPkgs[path] = pkg.Package{
    60  			FoundBy: upstream,
    61  			Name:    fmt.Sprintf("%s file contents!", path),
    62  		}
    63  	}
    64  
    65  	assert.Len(t, allParsedPaths, len(expectedSelection))
    66  	assert.Len(t, actualPkgs, len(expectedPkgs))
    67  	assert.Len(t, relationships, len(actualPkgs))
    68  
    69  	for _, p := range actualPkgs {
    70  		ls := p.Locations.ToSlice()
    71  		require.NotEmpty(t, ls)
    72  		ref := ls[0]
    73  		exP, ok := expectedPkgs[ref.RealPath]
    74  		if !ok {
    75  			t.Errorf("missing expected pkg: ref=%+v", ref)
    76  			continue
    77  		}
    78  
    79  		// assigned by the generic cataloger
    80  		if p.FoundBy != exP.FoundBy {
    81  			t.Errorf("bad upstream: %s", p.FoundBy)
    82  		}
    83  
    84  		// assigned by the parser
    85  		if exP.Name != p.Name {
    86  			t.Errorf("bad contents mapping: %+v", p.Locations)
    87  		}
    88  	}
    89  }
    90  
    91  type spyReturningFileResolver struct {
    92  	m *file.MockResolver
    93  	s *spyingIoReadCloser
    94  }
    95  
    96  type spyingIoReadCloser struct {
    97  	rc     io.ReadCloser
    98  	closed bool
    99  }
   100  
   101  func newSpyReturningFileResolver(s *spyingIoReadCloser, paths ...string) file.Resolver {
   102  	m := file.NewMockResolverForPaths(paths...)
   103  	return spyReturningFileResolver{
   104  		m: m,
   105  		s: s,
   106  	}
   107  }
   108  
   109  func (s *spyingIoReadCloser) Read(p []byte) (n int, err error) {
   110  	return s.Read(p)
   111  }
   112  
   113  func (s *spyingIoReadCloser) Close() error {
   114  	s.closed = true
   115  	return s.rc.Close()
   116  }
   117  
   118  var _ io.ReadCloser = (*spyingIoReadCloser)(nil)
   119  
   120  func (m spyReturningFileResolver) FileContentsByLocation(location file.Location) (io.ReadCloser, error) {
   121  	return m.s, nil
   122  }
   123  
   124  func (m spyReturningFileResolver) HasPath(path string) bool {
   125  	return m.m.HasPath(path)
   126  }
   127  
   128  func (m spyReturningFileResolver) FilesByPath(paths ...string) ([]file.Location, error) {
   129  	return m.m.FilesByPath(paths...)
   130  }
   131  
   132  func (m spyReturningFileResolver) FilesByGlob(patterns ...string) ([]file.Location, error) {
   133  	return m.m.FilesByGlob(patterns...)
   134  }
   135  
   136  func (m spyReturningFileResolver) FilesByMIMEType(types ...string) ([]file.Location, error) {
   137  	return m.m.FilesByMIMEType(types...)
   138  }
   139  
   140  func (m spyReturningFileResolver) RelativeFileByPath(f file.Location, path string) *file.Location {
   141  	return m.m.RelativeFileByPath(f, path)
   142  }
   143  
   144  func (m spyReturningFileResolver) AllLocations(ctx context.Context) <-chan file.Location {
   145  	return m.m.AllLocations(ctx)
   146  }
   147  
   148  func (m spyReturningFileResolver) FileMetadataByLocation(location file.Location) (file.Metadata, error) {
   149  	return m.m.FileMetadataByLocation(location)
   150  }
   151  
   152  var _ file.Resolver = (*spyReturningFileResolver)(nil)
   153  
   154  func TestClosesFileOnParserPanic(t *testing.T) {
   155  	rc := io.NopCloser(strings.NewReader("some string"))
   156  	spy := spyingIoReadCloser{
   157  		rc: rc,
   158  	}
   159  	resolver := newSpyReturningFileResolver(&spy, "test-fixtures/another-path.txt")
   160  	ctx := context.TODO()
   161  
   162  	processors := []requester{
   163  		func(resolver file.Resolver, env Environment) []request {
   164  			return []request{
   165  				{
   166  					Location: file.Location{
   167  						LocationData: file.LocationData{
   168  							Coordinates: file.Coordinates{},
   169  							AccessPath:  "/some/access/path",
   170  						},
   171  					},
   172  					Parser: func(context.Context, file.Resolver, *Environment, file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
   173  						panic("panic!")
   174  					},
   175  				},
   176  			}
   177  		},
   178  	}
   179  
   180  	c := Cataloger{
   181  		requesters:        processors,
   182  		upstreamCataloger: "unit-test-cataloger",
   183  	}
   184  
   185  	assert.PanicsWithValue(t, "panic!", func() {
   186  		_, _, _ = c.Catalog(ctx, resolver)
   187  	})
   188  	require.True(t, spy.closed)
   189  }