mirror of
https://github.com/anchore/syft.git
synced 2025-11-17 16:33:21 +01:00
* always close ELF cataloger file handles The elf-binary-package-cataloger does its own file IO to account for the possibility of a logical ELF package being broken across multiple physical files. However, this casued it to skip the normal invocation pattern in the generic cataloger code that prevented file leaks. Ensure this cataloger always closes its file handles. Signed-off-by: Will Murphy <will.murphy@anchore.com> * defer closing of generic cataloger file handles Otherwise, a panicking cataloger could leak file handles. Signed-off-by: Will Murphy <will.murphy@anchore.com> * add unit test for file closed on panic parser Signed-off-by: Will Murphy <will.murphy@anchore.com> * make invoke parser a static function Signed-off-by: Will Murphy <will.murphy@anchore.com> * push error logging down into invoke parser Signed-off-by: Will Murphy <will.murphy@anchore.com> --------- Signed-off-by: Will Murphy <will.murphy@anchore.com>
190 lines
4.9 KiB
Go
190 lines
4.9 KiB
Go
package generic
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/anchore/syft/syft/artifact"
|
|
"github.com/anchore/syft/syft/file"
|
|
"github.com/anchore/syft/syft/pkg"
|
|
)
|
|
|
|
func Test_Cataloger(t *testing.T) {
|
|
allParsedPaths := make(map[string]bool)
|
|
parser := func(_ context.Context, resolver file.Resolver, env *Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
|
allParsedPaths[reader.Path()] = true
|
|
contents, err := io.ReadAll(reader)
|
|
require.NoError(t, err)
|
|
|
|
if len(contents) == 0 {
|
|
return nil, nil, nil
|
|
}
|
|
|
|
p := pkg.Package{
|
|
Name: string(contents),
|
|
Locations: file.NewLocationSet(reader.Location),
|
|
}
|
|
r := artifact.Relationship{
|
|
From: p,
|
|
To: p,
|
|
Type: artifact.ContainsRelationship,
|
|
}
|
|
|
|
return []pkg.Package{p}, []artifact.Relationship{r}, nil
|
|
}
|
|
|
|
upstream := "some-other-cataloger"
|
|
|
|
expectedSelection := []string{"test-fixtures/last/path.txt", "test-fixtures/another-path.txt", "test-fixtures/a-path.txt", "test-fixtures/empty.txt"}
|
|
resolver := file.NewMockResolverForPaths(expectedSelection...)
|
|
cataloger := NewCataloger(upstream).
|
|
WithParserByPath(parser, "test-fixtures/another-path.txt", "test-fixtures/last/path.txt").
|
|
WithParserByGlobs(parser, "**/a-path.txt", "**/empty.txt")
|
|
|
|
actualPkgs, relationships, err := cataloger.Catalog(context.Background(), resolver)
|
|
assert.NoError(t, err)
|
|
|
|
expectedPkgs := make(map[string]pkg.Package)
|
|
for _, path := range expectedSelection {
|
|
require.True(t, allParsedPaths[path])
|
|
if path == "test-fixtures/empty.txt" {
|
|
continue // note: empty.txt won't become a package
|
|
}
|
|
expectedPkgs[path] = pkg.Package{
|
|
FoundBy: upstream,
|
|
Name: fmt.Sprintf("%s file contents!", path),
|
|
}
|
|
}
|
|
|
|
assert.Len(t, allParsedPaths, len(expectedSelection))
|
|
assert.Len(t, actualPkgs, len(expectedPkgs))
|
|
assert.Len(t, relationships, len(actualPkgs))
|
|
|
|
for _, p := range actualPkgs {
|
|
ls := p.Locations.ToSlice()
|
|
require.NotEmpty(t, ls)
|
|
ref := ls[0]
|
|
exP, ok := expectedPkgs[ref.RealPath]
|
|
if !ok {
|
|
t.Errorf("missing expected pkg: ref=%+v", ref)
|
|
continue
|
|
}
|
|
|
|
// assigned by the generic cataloger
|
|
if p.FoundBy != exP.FoundBy {
|
|
t.Errorf("bad upstream: %s", p.FoundBy)
|
|
}
|
|
|
|
// assigned by the parser
|
|
if exP.Name != p.Name {
|
|
t.Errorf("bad contents mapping: %+v", p.Locations)
|
|
}
|
|
}
|
|
}
|
|
|
|
type spyReturningFileResolver struct {
|
|
m *file.MockResolver
|
|
s *spyingIoReadCloser
|
|
}
|
|
|
|
type spyingIoReadCloser struct {
|
|
rc io.ReadCloser
|
|
closed bool
|
|
}
|
|
|
|
func newSpyReturningFileResolver(s *spyingIoReadCloser, paths ...string) file.Resolver {
|
|
m := file.NewMockResolverForPaths(paths...)
|
|
return spyReturningFileResolver{
|
|
m: m,
|
|
s: s,
|
|
}
|
|
}
|
|
|
|
func (s *spyingIoReadCloser) Read(p []byte) (n int, err error) {
|
|
return s.Read(p)
|
|
}
|
|
|
|
func (s *spyingIoReadCloser) Close() error {
|
|
s.closed = true
|
|
return s.rc.Close()
|
|
}
|
|
|
|
var _ io.ReadCloser = (*spyingIoReadCloser)(nil)
|
|
|
|
func (m spyReturningFileResolver) FileContentsByLocation(location file.Location) (io.ReadCloser, error) {
|
|
return m.s, nil
|
|
}
|
|
|
|
func (m spyReturningFileResolver) HasPath(path string) bool {
|
|
return m.m.HasPath(path)
|
|
}
|
|
|
|
func (m spyReturningFileResolver) FilesByPath(paths ...string) ([]file.Location, error) {
|
|
return m.m.FilesByPath(paths...)
|
|
}
|
|
|
|
func (m spyReturningFileResolver) FilesByGlob(patterns ...string) ([]file.Location, error) {
|
|
return m.m.FilesByGlob(patterns...)
|
|
}
|
|
|
|
func (m spyReturningFileResolver) FilesByMIMEType(types ...string) ([]file.Location, error) {
|
|
return m.m.FilesByMIMEType(types...)
|
|
}
|
|
|
|
func (m spyReturningFileResolver) RelativeFileByPath(f file.Location, path string) *file.Location {
|
|
return m.m.RelativeFileByPath(f, path)
|
|
}
|
|
|
|
func (m spyReturningFileResolver) AllLocations(ctx context.Context) <-chan file.Location {
|
|
return m.m.AllLocations(ctx)
|
|
}
|
|
|
|
func (m spyReturningFileResolver) FileMetadataByLocation(location file.Location) (file.Metadata, error) {
|
|
return m.m.FileMetadataByLocation(location)
|
|
}
|
|
|
|
var _ file.Resolver = (*spyReturningFileResolver)(nil)
|
|
|
|
func TestClosesFileOnParserPanic(t *testing.T) {
|
|
rc := io.NopCloser(strings.NewReader("some string"))
|
|
spy := spyingIoReadCloser{
|
|
rc: rc,
|
|
}
|
|
resolver := newSpyReturningFileResolver(&spy, "test-fixtures/another-path.txt")
|
|
ctx := context.TODO()
|
|
|
|
processors := []processor{
|
|
func(resolver file.Resolver, env Environment) []request {
|
|
return []request{
|
|
{
|
|
Location: file.Location{
|
|
LocationData: file.LocationData{
|
|
Coordinates: file.Coordinates{},
|
|
AccessPath: "/some/access/path",
|
|
},
|
|
},
|
|
Parser: func(context.Context, file.Resolver, *Environment, file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
|
panic("panic!")
|
|
},
|
|
},
|
|
}
|
|
},
|
|
}
|
|
|
|
c := Cataloger{
|
|
processor: processors,
|
|
upstreamCataloger: "unit-test-cataloger",
|
|
}
|
|
|
|
assert.PanicsWithValue(t, "panic!", func() {
|
|
_, _, _ = c.Catalog(ctx, resolver)
|
|
})
|
|
require.True(t, spy.closed)
|
|
}
|