github.com/lineaje-labs/syft@v0.98.1-0.20231227153149-9e393f60ff1b/test/integration/catalog_packages_test.go (about)

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