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