add tests around new file metadata cataloger

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
This commit is contained in:
Alex Goodman
2021-03-23 11:00:59 -04:00
parent 40199096e9
commit d420368ba9
17 changed files with 407 additions and 24 deletions
+8 -10
View File
@@ -13,8 +13,7 @@ import (
var supportedHashAlgorithms = make(map[string]crypto.Hash)
type DigestsCataloger struct {
resolver source.FileResolver
hashes []crypto.Hash
hashes []crypto.Hash
}
func init() {
@@ -27,7 +26,7 @@ func init() {
}
}
func NewDigestsCataloger(resolver source.FileResolver, hashAlgorithms []string) (*DigestsCataloger, error) {
func NewDigestsCataloger(hashAlgorithms []string) (*DigestsCataloger, error) {
var hashes []crypto.Hash
for _, hashStr := range hashAlgorithms {
name := cleanAlgorithmName(hashStr)
@@ -39,15 +38,14 @@ func NewDigestsCataloger(resolver source.FileResolver, hashAlgorithms []string)
}
return &DigestsCataloger{
resolver: resolver,
hashes: hashes,
hashes: hashes,
}, nil
}
func (i *DigestsCataloger) Catalog() (map[source.Location][]Digest, error) {
func (i *DigestsCataloger) Catalog(resolver source.FileResolver) (map[source.Location][]Digest, error) {
results := make(map[source.Location][]Digest)
for location := range i.resolver.AllLocations() {
result, err := i.catalogLocation(location)
for location := range resolver.AllLocations() {
result, err := i.catalogLocation(resolver, location)
if err != nil {
return nil, err
}
@@ -56,8 +54,8 @@ func (i *DigestsCataloger) Catalog() (map[source.Location][]Digest, error) {
return results, nil
}
func (i *DigestsCataloger) catalogLocation(location source.Location) ([]Digest, error) {
contentReader, err := i.resolver.FileContentsByLocation(location)
func (i *DigestsCataloger) catalogLocation(resolver source.FileResolver, location source.Location) ([]Digest, error) {
contentReader, err := resolver.FileContentsByLocation(location)
if err != nil {
return nil, err
}
+98
View File
@@ -0,0 +1,98 @@
package file
import (
"crypto"
"fmt"
"io/ioutil"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/anchore/syft/syft/source"
)
func testDigests(t testing.TB, files []string, hashes ...crypto.Hash) map[source.Location][]Digest {
digests := make(map[source.Location][]Digest)
for _, f := range files {
fh, err := os.Open(f)
if err != nil {
t.Fatalf("could not open %q : %+v", f, err)
}
b, err := ioutil.ReadAll(fh)
if err != nil {
t.Fatalf("could not read %q : %+v", f, err)
}
for _, hash := range hashes {
h := hash.New()
h.Write(b)
digests[source.NewLocation(f)] = append(digests[source.NewLocation(f)], Digest{
Algorithm: cleanAlgorithmName(hash.String()),
Value: fmt.Sprintf("%x", h.Sum(nil)),
})
}
}
return digests
}
func TestDigestsCataloger(t *testing.T) {
files := []string{"test-fixtures/last/path.txt", "test-fixtures/another-path.txt", "test-fixtures/a-path.txt"}
tests := []struct {
name string
algorithms []string
expected map[source.Location][]Digest
constructorErr bool
catalogErr bool
}{
{
name: "bad algorithm",
algorithms: []string{"sha-nothing"},
constructorErr: true,
},
{
name: "unsupported algorithm",
algorithms: []string{"sha512"},
constructorErr: true,
},
{
name: "md5-sha1-sha256",
algorithms: []string{"md5"},
expected: testDigests(t, files, crypto.MD5),
},
{
name: "md5-sha1-sha256",
algorithms: []string{"md5", "sha1", "sha256"},
expected: testDigests(t, files, crypto.MD5, crypto.SHA1, crypto.SHA256),
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
c, err := NewDigestsCataloger(test.algorithms)
if err != nil && !test.constructorErr {
t.Fatalf("could not create cataloger (but should have been able to): %+v", err)
} else if err == nil && test.constructorErr {
t.Fatalf("expected constructor error but did not get one")
} else if test.constructorErr && err != nil {
return
}
resolver := source.NewMockResolverForPaths(files...)
actual, err := c.Catalog(resolver)
if err != nil && !test.catalogErr {
t.Fatalf("could not catalog (but should have been able to): %+v", err)
} else if err == nil && test.catalogErr {
t.Fatalf("expected catalog error but did not get one")
} else if test.catalogErr && err != nil {
return
}
assert.Equal(t, actual, test.expected, "mismatched digests")
})
}
}
+5 -8
View File
@@ -5,19 +5,16 @@ import (
)
type MetadataCataloger struct {
resolver source.FileResolver
}
func NewMetadataCataloger(resolver source.FileResolver) *MetadataCataloger {
return &MetadataCataloger{
resolver: resolver,
}
func NewMetadataCataloger() *MetadataCataloger {
return &MetadataCataloger{}
}
func (i *MetadataCataloger) Catalog() (map[source.Location]source.FileMetadata, error) {
func (i *MetadataCataloger) Catalog(resolver source.FileResolver) (map[source.Location]source.FileMetadata, error) {
results := make(map[source.Location]source.FileMetadata)
for location := range i.resolver.AllLocations() {
metadata, err := i.resolver.FileMetadataByLocation(location)
for location := range resolver.AllLocations() {
metadata, err := resolver.FileMetadataByLocation(location)
if err != nil {
return nil, err
}
+125
View File
@@ -0,0 +1,125 @@
package file
import (
"os"
"testing"
"github.com/anchore/stereoscope/pkg/file"
"github.com/anchore/stereoscope/pkg/imagetest"
"github.com/anchore/syft/syft/source"
"github.com/stretchr/testify/assert"
)
func TestFileMetadataFetch(t *testing.T) {
img := imagetest.GetFixtureImage(t, "docker-archive", "image-file-type-mix")
c := NewMetadataCataloger()
src, err := source.NewFromImage(img, "---")
if err != nil {
t.Fatalf("could not create source: %+v", err)
}
resolver, err := src.FileResolver(source.SquashedScope)
if err != nil {
t.Fatalf("could not create resolver: %+v", err)
}
actual, err := c.Catalog(resolver)
if err != nil {
t.Fatalf("could not catalog: %+v", err)
}
tests := []struct {
path string
exists bool
expected source.FileMetadata
err bool
}{
{
path: "/file-1.txt",
exists: true,
expected: source.FileMetadata{
Mode: 0644,
Type: "regularFile",
UserID: 1,
GroupID: 2,
},
},
{
path: "/hardlink-1",
exists: true,
expected: source.FileMetadata{
Mode: 0644,
Type: "hardLink",
UserID: 1,
GroupID: 2,
},
},
{
path: "/symlink-1",
exists: true,
expected: source.FileMetadata{
Mode: 0777 | os.ModeSymlink,
Type: "symbolicLink",
UserID: 0,
GroupID: 0,
},
},
{
path: "/char-device-1",
exists: true,
expected: source.FileMetadata{
Mode: 0644 | os.ModeDevice | os.ModeCharDevice,
Type: "characterDevice",
UserID: 0,
GroupID: 0,
},
},
{
path: "/block-device-1",
exists: true,
expected: source.FileMetadata{
Mode: 0644 | os.ModeDevice,
Type: "blockDevice",
UserID: 0,
GroupID: 0,
},
},
{
path: "/fifo-1",
exists: true,
expected: source.FileMetadata{
Mode: 0644 | os.ModeNamedPipe,
Type: "fifoNode",
UserID: 0,
GroupID: 0,
},
},
{
path: "/bin",
exists: true,
expected: source.FileMetadata{
Mode: 0755 | os.ModeDir,
Type: "directory",
UserID: 0,
GroupID: 0,
},
},
}
for _, test := range tests {
t.Run(test.path, func(t *testing.T) {
_, ref, err := img.SquashedTree().File(file.Path(test.path))
if err != nil {
t.Fatalf("unable to get file: %+v", err)
}
l := source.NewLocationFromImage(test.path, *ref, img)
assert.Equal(t, actual[l], test.expected, "mismatched metadata")
})
}
}
+1
View File
@@ -0,0 +1 @@
test-fixtures/a-path.txt file contents!
+1
View File
@@ -0,0 +1 @@
test-fixtures/another-path.txt file contents!
@@ -0,0 +1,10 @@
FROM busybox:latest
ADD file-1.txt .
RUN chmod 644 file-1.txt
RUN chown 1:2 file-1.txt
RUN ln -s file-1.txt symlink-1
RUN ln file-1.txt hardlink-1
RUN mknod char-device-1 c 89 1
RUN mknod block-device-1 b 0 1
RUN mknod fifo-1 p
@@ -0,0 +1 @@
file 1!
+1
View File
@@ -0,0 +1 @@
test-fixtures/last/path.txt file contents!
+1 -1
View File
@@ -194,7 +194,7 @@ func (r *allLayersResolver) AllLocations() <-chan Location {
defer close(results)
for _, layerIdx := range r.layers {
tree := r.img.Layers[layerIdx].Tree
for _, ref := range tree.AllFiles() {
for _, ref := range tree.AllFiles(file.AllTypes...) {
results <- NewLocationFromImage(string(ref.RealPath), ref, r.img)
}
}
+122
View File
@@ -0,0 +1,122 @@
package source
import (
"os"
"testing"
"github.com/anchore/stereoscope/pkg/file"
"github.com/anchore/stereoscope/pkg/imagetest"
"github.com/stretchr/testify/assert"
)
func TestFileMetadataFetch(t *testing.T) {
img := imagetest.GetFixtureImage(t, "docker-archive", "image-file-type-mix")
tests := []struct {
path string
exists bool
expected FileMetadata
err bool
}{
{
path: "/file-1.txt",
exists: true,
expected: FileMetadata{
Mode: 0644,
Type: "regularFile",
UserID: 1,
GroupID: 2,
},
},
{
path: "/hardlink-1",
exists: true,
expected: FileMetadata{
Mode: 0644,
Type: "hardLink",
UserID: 1,
GroupID: 2,
},
},
{
path: "/symlink-1",
exists: true,
expected: FileMetadata{
Mode: 0777 | os.ModeSymlink,
Type: "symbolicLink",
UserID: 0,
GroupID: 0,
},
},
{
path: "/char-device-1",
exists: true,
expected: FileMetadata{
Mode: 0644 | os.ModeDevice | os.ModeCharDevice,
Type: "characterDevice",
UserID: 0,
GroupID: 0,
},
},
{
path: "/block-device-1",
exists: true,
expected: FileMetadata{
Mode: 0644 | os.ModeDevice,
Type: "blockDevice",
UserID: 0,
GroupID: 0,
},
},
{
path: "/fifo-1",
exists: true,
expected: FileMetadata{
Mode: 0644 | os.ModeNamedPipe,
Type: "fifoNode",
UserID: 0,
GroupID: 0,
},
},
{
path: "/bin",
exists: true,
expected: FileMetadata{
Mode: 0755 | os.ModeDir,
Type: "directory",
UserID: 0,
GroupID: 0,
},
},
}
for _, test := range tests {
t.Run(test.path, func(t *testing.T) {
exists, ref, err := img.SquashedTree().File(file.Path(test.path))
if err != nil {
t.Fatalf("unable to get file: %+v", err)
}
if exists && !test.exists {
t.Fatalf("file=%q exists but shouldn't", test.path)
} else if !exists && test.exists {
t.Fatalf("file=%q does not exist but should", test.path)
} else if !exists && !test.exists {
return
}
actual, err := fileMetadataByLocation(img, NewLocationFromImage(test.path, *ref, img))
if err != nil && !test.err {
t.Fatalf("could not fetch (but should have been able to): %+v", err)
} else if err == nil && test.err {
t.Fatalf("expected fetch error but did not get one")
} else if test.err && err != nil {
return
}
assert.Equal(t, test.expected, actual, "file metadata mismatch")
})
}
}
+1 -1
View File
@@ -144,7 +144,7 @@ func (r *imageSquashResolver) AllLocations() <-chan Location {
results := make(chan Location)
go func() {
defer close(results)
for _, ref := range r.img.SquashedTree().AllFiles() {
for _, ref := range r.img.SquashedTree().AllFiles(file.AllTypes...) {
results <- NewLocationFromImage(string(ref.RealPath), ref, r.img)
}
}()
+18 -1
View File
@@ -15,6 +15,7 @@ var _ FileResolver = (*MockResolver)(nil)
// paths, which are typically paths to test fixtures.
type MockResolver struct {
Locations []Location
Metadata map[Location]FileMetadata
}
// NewMockResolverForPaths creates a new MockResolver, where the only resolvable
@@ -28,6 +29,15 @@ func NewMockResolverForPaths(paths ...string) *MockResolver {
return &MockResolver{Locations: locations}
}
func NewMockResolverForPathsWithMetadata(metadata map[Location]FileMetadata) *MockResolver {
var locations []Location
for p := range metadata {
locations = append(locations, p)
}
return &MockResolver{Locations: locations, Metadata: metadata}
}
// HasPath indicates if the given path exists in the underlying source.
func (r MockResolver) HasPath(path string) bool {
for _, l := range r.Locations {
@@ -98,7 +108,14 @@ func (r MockResolver) RelativeFileByPath(_ Location, path string) *Location {
}
func (r MockResolver) AllLocations() <-chan Location {
panic("not implemented")
results := make(chan Location)
go func() {
defer close(results)
for _, l := range r.Locations {
results <- l
}
}()
return results
}
func (r MockResolver) FileMetadataByLocation(Location) (FileMetadata, error) {
@@ -0,0 +1,10 @@
FROM busybox:latest
ADD file-1.txt .
RUN chmod 644 file-1.txt
RUN chown 1:2 file-1.txt
RUN ln -s file-1.txt symlink-1
RUN ln file-1.txt hardlink-1
RUN mknod char-device-1 c 89 1
RUN mknod block-device-1 b 0 1
RUN mknod fifo-1 p
@@ -0,0 +1 @@
file 1!