github.com/kastenhq/syft@v0.0.0-20230821225854-0710af25cdbe/test/integration/catalog_packages_test.go (about)

     1  package integration
     2  
     3  import (
     4  	"strings"
     5  	"testing"
     6  
     7  	"github.com/google/go-cmp/cmp"
     8  	"github.com/scylladb/go-set/strset"
     9  	"github.com/stretchr/testify/assert"
    10  	"github.com/stretchr/testify/require"
    11  
    12  	"github.com/anchore/stereoscope/pkg/imagetest"
    13  	"github.com/kastenhq/syft/internal"
    14  	"github.com/kastenhq/syft/syft/linux"
    15  	"github.com/kastenhq/syft/syft/pkg"
    16  	"github.com/kastenhq/syft/syft/pkg/cataloger"
    17  	"github.com/kastenhq/syft/syft/source"
    18  )
    19  
    20  func BenchmarkImagePackageCatalogers(b *testing.B) {
    21  	fixtureImageName := "image-pkg-coverage"
    22  	imagetest.GetFixtureImage(b, "docker-archive", fixtureImageName)
    23  	tarPath := imagetest.GetFixtureImageTarPath(b, fixtureImageName)
    24  
    25  	var pc *pkg.Collection
    26  	for _, c := range cataloger.ImageCatalogers(defaultConfig()) {
    27  		// in case of future alteration where state is persisted, assume no dependency is safe to reuse
    28  		userInput := "docker-archive:" + tarPath
    29  		detection, err := source.Detect(userInput, source.DefaultDetectConfig())
    30  		require.NoError(b, err)
    31  		theSource, err := detection.NewSource(source.DefaultDetectionSourceConfig())
    32  		if err != nil {
    33  			b.Fatalf("unable to get source: %+v", err)
    34  		}
    35  		b.Cleanup(func() {
    36  			theSource.Close()
    37  		})
    38  
    39  		resolver, err := theSource.FileResolver(source.SquashedScope)
    40  		if err != nil {
    41  			b.Fatalf("unable to get resolver: %+v", err)
    42  		}
    43  
    44  		theDistro := linux.IdentifyRelease(resolver)
    45  
    46  		b.Run(c.Name(), func(b *testing.B) {
    47  			for i := 0; i < b.N; i++ {
    48  				pc, _, err = cataloger.Catalog(resolver, theDistro, 1, c)
    49  				if err != nil {
    50  					b.Fatalf("failure during benchmark: %+v", err)
    51  				}
    52  			}
    53  		})
    54  
    55  		b.Logf("catalog for %q number of packages: %d", c.Name(), pc.PackageCount())
    56  	}
    57  }
    58  
    59  func TestPkgCoverageImage(t *testing.T) {
    60  	sbom, _ := catalogFixtureImage(t, "image-pkg-coverage", source.SquashedScope, nil)
    61  
    62  	observedLanguages := internal.NewStringSet()
    63  	definedLanguages := internal.NewStringSet()
    64  	for _, l := range pkg.AllLanguages {
    65  		definedLanguages.Add(l.String())
    66  	}
    67  
    68  	// for image scans we should not expect to see any of the following package types
    69  	definedLanguages.Remove(pkg.Go.String())
    70  	definedLanguages.Remove(pkg.Rust.String())
    71  	definedLanguages.Remove(pkg.Dart.String())
    72  	definedLanguages.Remove(pkg.Dotnet.String())
    73  	definedLanguages.Remove(pkg.Swift.String())
    74  	definedLanguages.Remove(pkg.CPP.String())
    75  	definedLanguages.Remove(pkg.Haskell.String())
    76  	definedLanguages.Remove(pkg.Erlang.String())
    77  	definedLanguages.Remove(pkg.Elixir.String())
    78  
    79  	observedPkgs := internal.NewStringSet()
    80  	definedPkgs := internal.NewStringSet()
    81  	for _, p := range pkg.AllPkgs {
    82  		definedPkgs.Add(string(p))
    83  	}
    84  
    85  	// for image scans we should not expect to see any of the following package types
    86  	definedPkgs.Remove(string(pkg.KbPkg))
    87  	definedPkgs.Remove(string(pkg.GoModulePkg))
    88  	definedPkgs.Remove(string(pkg.RustPkg))
    89  	definedPkgs.Remove(string(pkg.DartPubPkg))
    90  	definedPkgs.Remove(string(pkg.DotnetPkg))
    91  	definedPkgs.Remove(string(pkg.CocoapodsPkg))
    92  	definedPkgs.Remove(string(pkg.ConanPkg))
    93  	definedPkgs.Remove(string(pkg.HackagePkg))
    94  	definedPkgs.Remove(string(pkg.BinaryPkg))
    95  	definedPkgs.Remove(string(pkg.HexPkg))
    96  	definedPkgs.Remove(string(pkg.LinuxKernelPkg))
    97  	definedPkgs.Remove(string(pkg.LinuxKernelModulePkg))
    98  	definedPkgs.Remove(string(pkg.SwiftPkg))
    99  
   100  	var cases []testCase
   101  	cases = append(cases, commonTestCases...)
   102  	cases = append(cases, imageOnlyTestCases...)
   103  
   104  	for _, c := range cases {
   105  		t.Run(c.name, func(t *testing.T) {
   106  			pkgCount := 0
   107  
   108  			for a := range sbom.Artifacts.Packages.Enumerate(c.pkgType) {
   109  				if a.Language.String() != "" {
   110  					observedLanguages.Add(a.Language.String())
   111  				}
   112  
   113  				observedPkgs.Add(string(a.Type))
   114  				expectedVersion, ok := c.pkgInfo[a.Name]
   115  				if !ok {
   116  					t.Errorf("unexpected package found: %s", a.Name)
   117  				}
   118  
   119  				if expectedVersion != a.Version {
   120  					t.Errorf("unexpected package version (pkg=%s): %s, expected: %s", a.Name, a.Version, expectedVersion)
   121  				}
   122  
   123  				if a.Language != c.pkgLanguage {
   124  					t.Errorf("bad language (pkg=%+v): %+v", a.Name, a.Language)
   125  				}
   126  
   127  				if a.Type != c.pkgType {
   128  					t.Errorf("bad package type (pkg=%+v): %+v", a.Name, a.Type)
   129  				}
   130  				pkgCount++
   131  			}
   132  
   133  			if pkgCount != len(c.pkgInfo)+c.duplicates {
   134  				t.Logf("Discovered packages of type %+v", c.pkgType)
   135  				for a := range sbom.Artifacts.Packages.Enumerate(c.pkgType) {
   136  					t.Log("   ", a)
   137  				}
   138  				t.Fatalf("unexpected package count: %d!=%d", pkgCount, len(c.pkgInfo))
   139  			}
   140  
   141  		})
   142  	}
   143  
   144  	observedLanguages.Remove(pkg.UnknownLanguage.String())
   145  	definedLanguages.Remove(pkg.UnknownLanguage.String())
   146  	observedPkgs.Remove(string(pkg.UnknownPkg))
   147  	definedPkgs.Remove(string(pkg.UnknownPkg))
   148  
   149  	// ensure that integration test cases stay in sync with the available catalogers
   150  	if diff := cmp.Diff(definedLanguages, observedLanguages); diff != "" {
   151  		t.Errorf("language coverage incomplete (languages=%d, coverage=%d)", len(definedLanguages), len(observedLanguages))
   152  		t.Errorf("definedLanguages mismatch observedLanguages (-want +got):\n%s", diff)
   153  	}
   154  
   155  	if diff := cmp.Diff(definedPkgs, observedPkgs); diff != "" {
   156  		t.Errorf("package coverage incomplete (packages=%d, coverage=%d)", len(definedPkgs), len(observedPkgs))
   157  		t.Errorf("definedPkgs mismatch observedPkgs (-want +got):\n%s", diff)
   158  	}
   159  }
   160  
   161  func TestPkgCoverageDirectory(t *testing.T) {
   162  	sbom, _ := catalogDirectory(t, "test-fixtures/image-pkg-coverage")
   163  
   164  	observedLanguages := internal.NewStringSet()
   165  	definedLanguages := internal.NewStringSet()
   166  	for _, l := range pkg.AllLanguages {
   167  		definedLanguages.Add(l.String())
   168  	}
   169  
   170  	observedPkgs := internal.NewStringSet()
   171  	definedPkgs := internal.NewStringSet()
   172  	for _, p := range pkg.AllPkgs {
   173  		definedPkgs.Add(string(p))
   174  	}
   175  
   176  	var cases []testCase
   177  	cases = append(cases, commonTestCases...)
   178  	cases = append(cases, dirOnlyTestCases...)
   179  
   180  	for _, test := range cases {
   181  		t.Run(test.name, func(t *testing.T) {
   182  			actualPkgCount := 0
   183  
   184  			for actualPkg := range sbom.Artifacts.Packages.Enumerate(test.pkgType) {
   185  				observedLanguages.Add(actualPkg.Language.String())
   186  				observedPkgs.Add(string(actualPkg.Type))
   187  
   188  				expectedVersion, ok := test.pkgInfo[actualPkg.Name]
   189  				if !ok {
   190  					t.Errorf("unexpected package found: %s", actualPkg.Name)
   191  				}
   192  
   193  				if expectedVersion != actualPkg.Version {
   194  					t.Errorf("unexpected package version (pkg=%s): %s", actualPkg.Name, actualPkg.Version)
   195  				}
   196  
   197  				var foundLang bool
   198  				for _, lang := range strings.Split(test.pkgLanguage.String(), ",") {
   199  					if actualPkg.Language.String() == lang {
   200  						foundLang = true
   201  						break
   202  					}
   203  				}
   204  				if !foundLang {
   205  					t.Errorf("bad language (pkg=%+v): %+v", actualPkg.Name, actualPkg.Language)
   206  				}
   207  
   208  				if actualPkg.Type != test.pkgType {
   209  					t.Errorf("bad package type (pkg=%+v): %+v", actualPkg.Name, actualPkg.Type)
   210  				}
   211  				actualPkgCount++
   212  			}
   213  
   214  			if actualPkgCount != len(test.pkgInfo)+test.duplicates {
   215  				for actualPkg := range sbom.Artifacts.Packages.Enumerate(test.pkgType) {
   216  					t.Log("   ", actualPkg)
   217  				}
   218  				t.Fatalf("unexpected package count: %d!=%d", actualPkgCount, len(test.pkgInfo))
   219  			}
   220  
   221  		})
   222  	}
   223  
   224  	observedLanguages.Remove(pkg.UnknownLanguage.String())
   225  	definedLanguages.Remove(pkg.UnknownLanguage.String())
   226  	definedLanguages.Remove(pkg.R.String())
   227  	observedPkgs.Remove(string(pkg.UnknownPkg))
   228  	definedPkgs.Remove(string(pkg.BinaryPkg))
   229  	definedPkgs.Remove(string(pkg.LinuxKernelPkg))
   230  	definedPkgs.Remove(string(pkg.LinuxKernelModulePkg))
   231  	definedPkgs.Remove(string(pkg.Rpkg))
   232  	definedPkgs.Remove(string(pkg.UnknownPkg))
   233  
   234  	// for directory scans we should not expect to see any of the following package types
   235  	definedPkgs.Remove(string(pkg.KbPkg))
   236  
   237  	// ensure that integration test commonTestCases stay in sync with the available catalogers
   238  	if len(observedLanguages) < len(definedLanguages) {
   239  		t.Errorf("language coverage incomplete (languages=%d, coverage=%d)", len(definedLanguages), len(observedLanguages))
   240  	}
   241  
   242  	if len(observedPkgs) < len(definedPkgs) {
   243  		t.Errorf("package coverage incomplete (packages=%d, coverage=%d)", len(definedPkgs), len(observedPkgs))
   244  	}
   245  }
   246  
   247  func TestPkgCoverageCatalogerConfiguration(t *testing.T) {
   248  	// Check that cataloger configuration can be used to run a cataloger on a source
   249  	// for which that cataloger isn't enabled by defauly
   250  	sbom, _ := catalogFixtureImage(t, "image-pkg-coverage", source.SquashedScope, []string{"rust"})
   251  
   252  	observedLanguages := internal.NewStringSet()
   253  	definedLanguages := internal.NewStringSet()
   254  	definedLanguages.Add("rust")
   255  
   256  	for actualPkg := range sbom.Artifacts.Packages.Enumerate() {
   257  		observedLanguages.Add(actualPkg.Language.String())
   258  	}
   259  
   260  	assert.Equal(t, definedLanguages, observedLanguages)
   261  
   262  	// Verify that rust isn't actually an image cataloger
   263  	c := defaultConfig()
   264  	c.Catalogers = []string{"rust"}
   265  	assert.Len(t, cataloger.ImageCatalogers(c), 0)
   266  }
   267  
   268  func TestPkgCoverageImage_HasEvidence(t *testing.T) {
   269  	sbom, _ := catalogFixtureImage(t, "image-pkg-coverage", source.SquashedScope, nil)
   270  
   271  	var cases []testCase
   272  	cases = append(cases, commonTestCases...)
   273  	cases = append(cases, imageOnlyTestCases...)
   274  
   275  	pkgTypesMissingEvidence := strset.New()
   276  
   277  	for _, c := range cases {
   278  		t.Run(c.name, func(t *testing.T) {
   279  
   280  			for a := range sbom.Artifacts.Packages.Enumerate(c.pkgType) {
   281  				assert.NotEmpty(t, a.Locations.ToSlice(), "package %q has no locations (type=%q)", a.Name, a.Type)
   282  				for _, l := range a.Locations.ToSlice() {
   283  					if _, exists := l.Annotations[pkg.EvidenceAnnotationKey]; !exists {
   284  						pkgTypesMissingEvidence.Add(string(a.Type))
   285  						t.Errorf("missing evidence annotation (pkg=%s type=%s)", a.Name, a.Type)
   286  					}
   287  				}
   288  			}
   289  
   290  		})
   291  	}
   292  
   293  	if pkgTypesMissingEvidence.Size() > 0 {
   294  		t.Log("Package types missing evidence annotations (img resolver): ", pkgTypesMissingEvidence.List())
   295  	}
   296  }
   297  
   298  func TestPkgCoverageDirectory_HasEvidence(t *testing.T) {
   299  	sbom, _ := catalogDirectory(t, "test-fixtures/image-pkg-coverage")
   300  
   301  	var cases []testCase
   302  	cases = append(cases, commonTestCases...)
   303  	cases = append(cases, imageOnlyTestCases...)
   304  
   305  	pkgTypesMissingEvidence := strset.New()
   306  
   307  	for _, c := range cases {
   308  		t.Run(c.name, func(t *testing.T) {
   309  
   310  			for a := range sbom.Artifacts.Packages.Enumerate(c.pkgType) {
   311  				assert.NotEmpty(t, a.Locations.ToSlice(), "package %q has no locations (type=%q)", a.Name, a.Type)
   312  				for _, l := range a.Locations.ToSlice() {
   313  					if _, exists := l.Annotations[pkg.EvidenceAnnotationKey]; !exists {
   314  						pkgTypesMissingEvidence.Add(string(a.Type))
   315  						t.Errorf("missing evidence annotation (pkg=%s type=%s)", a.Name, a.Type)
   316  					}
   317  				}
   318  			}
   319  
   320  		})
   321  	}
   322  
   323  	if pkgTypesMissingEvidence.Size() > 0 {
   324  		t.Log("Package types missing evidence annotations (dir resolver): ", pkgTypesMissingEvidence.List())
   325  	}
   326  }