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 }