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

     1  package filedigest
     2  
     3  import (
     4  	"context"
     5  	"crypto"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"path/filepath"
    10  	"testing"
    11  
    12  	"github.com/stretchr/testify/assert"
    13  	"github.com/stretchr/testify/require"
    14  
    15  	stereoscopeFile "github.com/anchore/stereoscope/pkg/file"
    16  	"github.com/anchore/stereoscope/pkg/imagetest"
    17  	intFile "github.com/anchore/syft/internal/file"
    18  	"github.com/anchore/syft/syft/file"
    19  	"github.com/anchore/syft/syft/source"
    20  	"github.com/anchore/syft/syft/source/directorysource"
    21  	"github.com/anchore/syft/syft/source/stereoscopesource"
    22  )
    23  
    24  func testDigests(t testing.TB, root string, files []string, hashes ...crypto.Hash) map[file.Coordinates][]file.Digest {
    25  	digests := make(map[file.Coordinates][]file.Digest)
    26  
    27  	for _, f := range files {
    28  		fh, err := os.Open(filepath.Join(root, f))
    29  		if err != nil {
    30  			t.Fatalf("could not open %q : %+v", f, err)
    31  		}
    32  		b, err := io.ReadAll(fh)
    33  		if err != nil {
    34  			t.Fatalf("could not read %q : %+v", f, err)
    35  		}
    36  
    37  		if len(b) == 0 {
    38  			// we don't keep digests for empty files
    39  			digests[file.NewLocation(f).Coordinates] = []file.Digest{}
    40  			continue
    41  		}
    42  
    43  		for _, hash := range hashes {
    44  			h := hash.New()
    45  			h.Write(b)
    46  			digests[file.NewLocation(f).Coordinates] = append(digests[file.NewLocation(f).Coordinates], file.Digest{
    47  				Algorithm: intFile.CleanDigestAlgorithmName(hash.String()),
    48  				Value:     fmt.Sprintf("%x", h.Sum(nil)),
    49  			})
    50  		}
    51  	}
    52  
    53  	return digests
    54  }
    55  
    56  func TestDigestsCataloger(t *testing.T) {
    57  
    58  	tests := []struct {
    59  		name     string
    60  		digests  []crypto.Hash
    61  		files    []string
    62  		expected map[file.Coordinates][]file.Digest
    63  	}{
    64  		{
    65  			name:     "md5",
    66  			digests:  []crypto.Hash{crypto.MD5},
    67  			files:    []string{"test-fixtures/last/empty/empty", "test-fixtures/last/path.txt"},
    68  			expected: testDigests(t, "test-fixtures/last", []string{"empty/empty", "path.txt"}, crypto.MD5),
    69  		},
    70  		{
    71  			name:     "md5-sha1-sha256",
    72  			digests:  []crypto.Hash{crypto.MD5, crypto.SHA1, crypto.SHA256},
    73  			files:    []string{"test-fixtures/last/empty/empty", "test-fixtures/last/path.txt"},
    74  			expected: testDigests(t, "test-fixtures/last", []string{"empty/empty", "path.txt"}, crypto.MD5, crypto.SHA1, crypto.SHA256),
    75  		},
    76  	}
    77  
    78  	for _, test := range tests {
    79  		t.Run(test.name, func(t *testing.T) {
    80  			c := NewCataloger(test.digests)
    81  
    82  			src, err := directorysource.NewFromPath("test-fixtures/last/")
    83  			require.NoError(t, err)
    84  
    85  			resolver, err := src.FileResolver(source.SquashedScope)
    86  			require.NoError(t, err)
    87  
    88  			actual, err := c.Catalog(context.Background(), resolver)
    89  			require.NoError(t, err)
    90  
    91  			assert.Equal(t, test.expected, actual, "mismatched digests")
    92  		})
    93  	}
    94  }
    95  
    96  func TestDigestsCataloger_MixFileTypes(t *testing.T) {
    97  	testImage := "image-file-type-mix"
    98  
    99  	img := imagetest.GetFixtureImage(t, "docker-archive", testImage)
   100  
   101  	src := stereoscopesource.New(img, stereoscopesource.ImageConfig{
   102  		Reference: testImage,
   103  	})
   104  
   105  	resolver, err := src.FileResolver(source.SquashedScope)
   106  	if err != nil {
   107  		t.Fatalf("could not create resolver: %+v", err)
   108  	}
   109  
   110  	tests := []struct {
   111  		path     string
   112  		expected string
   113  	}{
   114  		{
   115  			path:     "/file-1.txt",
   116  			expected: "888c139e550867814eb7c33b84d76e4d",
   117  		},
   118  		// this is difficult to reproduce in a cross-platform way
   119  		//{
   120  		//	path: "/hardlink-1",
   121  		//},
   122  		{
   123  			path: "/symlink-1",
   124  		},
   125  		{
   126  			path: "/char-device-1",
   127  		},
   128  		{
   129  			path: "/block-device-1",
   130  		},
   131  		{
   132  			path: "/fifo-1",
   133  		},
   134  		{
   135  			path: "/bin",
   136  		},
   137  	}
   138  
   139  	for _, test := range tests {
   140  		t.Run(test.path, func(t *testing.T) {
   141  			c := NewCataloger([]crypto.Hash{crypto.MD5})
   142  
   143  			actual, err := c.Catalog(context.Background(), resolver)
   144  			if err != nil {
   145  				t.Fatalf("could not catalog: %+v", err)
   146  			}
   147  
   148  			_, ref, err := img.SquashedTree().File(stereoscopeFile.Path(test.path))
   149  			if err != nil {
   150  				t.Fatalf("unable to get file=%q : %+v", test.path, err)
   151  			}
   152  			l := file.NewLocationFromImage(test.path, *ref.Reference, img)
   153  
   154  			if len(actual[l.Coordinates]) == 0 {
   155  				if test.expected != "" {
   156  					t.Fatalf("no digest found, but expected one")
   157  				}
   158  
   159  			} else {
   160  				assert.Equal(t, actual[l.Coordinates][0].Value, test.expected, "mismatched digests")
   161  			}
   162  		})
   163  	}
   164  }
   165  
   166  func TestFileDigestCataloger_GivenCoordinates(t *testing.T) {
   167  	testImage := "image-file-type-mix"
   168  
   169  	img := imagetest.GetFixtureImage(t, "docker-archive", testImage)
   170  
   171  	c := NewCataloger([]crypto.Hash{crypto.SHA256})
   172  
   173  	src := stereoscopesource.New(img, stereoscopesource.ImageConfig{
   174  		Reference: testImage,
   175  	})
   176  
   177  	resolver, err := src.FileResolver(source.SquashedScope)
   178  	require.NoError(t, err)
   179  
   180  	tests := []struct {
   181  		path     string
   182  		exists   bool
   183  		expected string
   184  	}{
   185  		{
   186  			path:     "/file-1.txt",
   187  			exists:   true,
   188  			expected: "b089629781f05ef805b4511e93717f2ffa4c9d991771d5cbfa4b7242b4ef5fff",
   189  		},
   190  	}
   191  
   192  	for _, test := range tests {
   193  		t.Run(test.path, func(t *testing.T) {
   194  			_, ref, err := img.SquashedTree().File(stereoscopeFile.Path(test.path))
   195  			require.NoError(t, err)
   196  
   197  			l := file.NewLocationFromImage(test.path, *ref.Reference, img)
   198  
   199  			// note: an important difference between this test and the previous is that this test is using a list
   200  			// of specific coordinates to catalog
   201  			actual, err := c.Catalog(context.Background(), resolver, l.Coordinates)
   202  			require.NoError(t, err)
   203  			require.Len(t, actual, 1)
   204  
   205  			assert.Equal(t, test.expected, actual[l.Coordinates][0].Value, "mismatched digests")
   206  		})
   207  	}
   208  
   209  }