github.com/noqcks/syft@v0.0.0-20230920222752-a9e2c4e288e5/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/anchore/syft/internal"
    14  	"github.com/anchore/syft/syft/linux"
    15  	"github.com/anchore/syft/syft/pkg"
    16  	"github.com/anchore/syft/syft/pkg/cataloger"
    17  	"github.com/anchore/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(cataloger.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  	definedPkgs.Remove(string(pkg.GithubActionPkg))
   100  	definedPkgs.Remove(string(pkg.GithubActionWorkflowPkg))
   101  
   102  	var cases []testCase
   103  	cases = append(cases, commonTestCases...)
   104  	cases = append(cases, imageOnlyTestCases...)
   105  
   106  	for _, c := range cases {
   107  		t.Run(c.name, func(t *testing.T) {
   108  			pkgCount := 0
   109  
   110  			for a := range sbom.Artifacts.Packages.Enumerate(c.pkgType) {
   111  				if a.Language.String() != "" {
   112  					observedLanguages.Add(a.Language.String())
   113  				}
   114  
   115  				observedPkgs.Add(string(a.Type))
   116  				expectedVersion, ok := c.pkgInfo[a.Name]
   117  				if !ok {
   118  					t.Errorf("unexpected package found: %s", a.Name)
   119  				}
   120  
   121  				if expectedVersion != a.Version {
   122  					t.Errorf("unexpected package version (pkg=%s): %s, expected: %s", a.Name, a.Version, expectedVersion)
   123  				}
   124  
   125  				if a.Language != c.pkgLanguage {
   126  					t.Errorf("bad language (pkg=%+v): %+v", a.Name, a.Language)
   127  				}
   128  
   129  				if a.Type != c.pkgType {
   130  					t.Errorf("bad package type (pkg=%+v): %+v", a.Name, a.Type)
   131  				}
   132  				pkgCount++
   133  			}
   134  
   135  			if pkgCount != len(c.pkgInfo)+c.duplicates {
   136  				t.Logf("Discovered packages of type %+v", c.pkgType)
   137  				for a := range sbom.Artifacts.Packages.Enumerate(c.pkgType) {
   138  					t.Log("   ", a)
   139  				}
   140  				t.Fatalf("unexpected package count: %d!=%d", pkgCount, len(c.pkgInfo))
   141  			}
   142  
   143  		})
   144  	}
   145  
   146  	observedLanguages.Remove(pkg.UnknownLanguage.String())
   147  	definedLanguages.Remove(pkg.UnknownLanguage.String())
   148  	observedPkgs.Remove(string(pkg.UnknownPkg))
   149  	definedPkgs.Remove(string(pkg.UnknownPkg))
   150  
   151  	// ensure that integration test cases stay in sync with the available catalogers
   152  	if diff := cmp.Diff(definedLanguages, observedLanguages); diff != "" {
   153  		t.Errorf("language coverage incomplete (languages=%d, coverage=%d)", len(definedLanguages), len(observedLanguages))
   154  		t.Errorf("definedLanguages mismatch observedLanguages (-want +got):\n%s", diff)
   155  	}
   156  
   157  	if diff := cmp.Diff(definedPkgs, observedPkgs); diff != "" {
   158  		t.Errorf("package coverage incomplete (packages=%d, coverage=%d)", len(definedPkgs), len(observedPkgs))
   159  		t.Errorf("definedPkgs mismatch observedPkgs (-want +got):\n%s", diff)
   160  	}
   161  }
   162  
   163  func TestPkgCoverageDirectory(t *testing.T) {
   164  	sbom, _ := catalogDirectory(t, "test-fixtures/image-pkg-coverage")
   165  
   166  	observedLanguages := internal.NewStringSet()
   167  	definedLanguages := internal.NewStringSet()
   168  	for _, l := range pkg.AllLanguages {
   169  		definedLanguages.Add(l.String())
   170  	}
   171  
   172  	observedPkgs := internal.NewStringSet()
   173  	definedPkgs := internal.NewStringSet()
   174  	for _, p := range pkg.AllPkgs {
   175  		definedPkgs.Add(string(p))
   176  	}
   177  
   178  	var cases []testCase
   179  	cases = append(cases, commonTestCases...)
   180  	cases = append(cases, dirOnlyTestCases...)
   181  
   182  	for _, test := range cases {
   183  		t.Run(test.name, func(t *testing.T) {
   184  			actualPkgCount := 0
   185  
   186  			for actualPkg := range sbom.Artifacts.Packages.Enumerate(test.pkgType) {
   187  				observedLanguages.Add(actualPkg.Language.String())
   188  				observedPkgs.Add(string(actualPkg.Type))
   189  
   190  				expectedVersion, ok := test.pkgInfo[actualPkg.Name]
   191  				if !ok {
   192  					t.Errorf("unexpected package found: %s", actualPkg.Name)
   193  				}
   194  
   195  				if expectedVersion != actualPkg.Version {
   196  					t.Errorf("unexpected package version (pkg=%s): %s", actualPkg.Name, actualPkg.Version)
   197  				}
   198  
   199  				var foundLang bool
   200  				for _, lang := range strings.Split(test.pkgLanguage.String(), ",") {
   201  					if actualPkg.Language.String() == lang {
   202  						foundLang = true
   203  						break
   204  					}
   205  				}
   206  				if !foundLang {
   207  					t.Errorf("bad language (pkg=%+v): %+v", actualPkg.Name, actualPkg.Language)
   208  				}
   209  
   210  				if actualPkg.Type != test.pkgType {
   211  					t.Errorf("bad package type (pkg=%+v): %+v", actualPkg.Name, actualPkg.Type)
   212  				}
   213  				actualPkgCount++
   214  			}
   215  
   216  			if actualPkgCount != len(test.pkgInfo)+test.duplicates {
   217  				for actualPkg := range sbom.Artifacts.Packages.Enumerate(test.pkgType) {
   218  					t.Log("   ", actualPkg)
   219  				}
   220  				t.Fatalf("unexpected package count: %d!=%d", actualPkgCount, len(test.pkgInfo))
   221  			}
   222  
   223  		})
   224  	}
   225  
   226  	observedLanguages.Remove(pkg.UnknownLanguage.String())
   227  	definedLanguages.Remove(pkg.UnknownLanguage.String())
   228  	definedLanguages.Remove(pkg.R.String())
   229  	observedPkgs.Remove(string(pkg.UnknownPkg))
   230  	definedPkgs.Remove(string(pkg.BinaryPkg))
   231  	definedPkgs.Remove(string(pkg.LinuxKernelPkg))
   232  	definedPkgs.Remove(string(pkg.LinuxKernelModulePkg))
   233  	definedPkgs.Remove(string(pkg.Rpkg))
   234  	definedPkgs.Remove(string(pkg.UnknownPkg))
   235  
   236  	// for directory scans we should not expect to see any of the following package types
   237  	definedPkgs.Remove(string(pkg.KbPkg))
   238  
   239  	// ensure that integration test commonTestCases stay in sync with the available catalogers
   240  	if len(observedLanguages) < len(definedLanguages) {
   241  		t.Errorf("language coverage incomplete (languages=%d, coverage=%d)", len(definedLanguages), len(observedLanguages))
   242  	}
   243  
   244  	if len(observedPkgs) < len(definedPkgs) {
   245  		t.Errorf("package coverage incomplete (packages=%d, coverage=%d)", len(definedPkgs), len(observedPkgs))
   246  	}
   247  }
   248  
   249  func TestPkgCoverageCatalogerConfiguration(t *testing.T) {
   250  	// Check that cataloger configuration can be used to run a cataloger on a source
   251  	// for which that cataloger isn't enabled by defauly
   252  	sbom, _ := catalogFixtureImage(t, "image-pkg-coverage", source.SquashedScope, []string{"rust"})
   253  
   254  	observedLanguages := internal.NewStringSet()
   255  	definedLanguages := internal.NewStringSet()
   256  	definedLanguages.Add("rust")
   257  
   258  	for actualPkg := range sbom.Artifacts.Packages.Enumerate() {
   259  		observedLanguages.Add(actualPkg.Language.String())
   260  	}
   261  
   262  	assert.Equal(t, definedLanguages, observedLanguages)
   263  
   264  	// Verify that rust isn't actually an image cataloger
   265  	c := cataloger.DefaultConfig()
   266  	c.Catalogers = []string{"rust"}
   267  	assert.Len(t, cataloger.ImageCatalogers(c), 0)
   268  }
   269  
   270  func TestPkgCoverageImage_HasEvidence(t *testing.T) {
   271  	sbom, _ := catalogFixtureImage(t, "image-pkg-coverage", source.SquashedScope, nil)
   272  
   273  	var cases []testCase
   274  	cases = append(cases, commonTestCases...)
   275  	cases = append(cases, imageOnlyTestCases...)
   276  
   277  	pkgTypesMissingEvidence := strset.New()
   278  
   279  	for _, c := range cases {
   280  		t.Run(c.name, func(t *testing.T) {
   281  
   282  			for a := range sbom.Artifacts.Packages.Enumerate(c.pkgType) {
   283  				assert.NotEmpty(t, a.Locations.ToSlice(), "package %q has no locations (type=%q)", a.Name, a.Type)
   284  				for _, l := range a.Locations.ToSlice() {
   285  					if _, exists := l.Annotations[pkg.EvidenceAnnotationKey]; !exists {
   286  						pkgTypesMissingEvidence.Add(string(a.Type))
   287  						t.Errorf("missing evidence annotation (pkg=%s type=%s)", a.Name, a.Type)
   288  					}
   289  				}
   290  			}
   291  
   292  		})
   293  	}
   294  
   295  	if pkgTypesMissingEvidence.Size() > 0 {
   296  		t.Log("Package types missing evidence annotations (img resolver): ", pkgTypesMissingEvidence.List())
   297  	}
   298  }
   299  
   300  func TestPkgCoverageDirectory_HasEvidence(t *testing.T) {
   301  	sbom, _ := catalogDirectory(t, "test-fixtures/image-pkg-coverage")
   302  
   303  	var cases []testCase
   304  	cases = append(cases, commonTestCases...)
   305  	cases = append(cases, imageOnlyTestCases...)
   306  
   307  	pkgTypesMissingEvidence := strset.New()
   308  
   309  	for _, c := range cases {
   310  		t.Run(c.name, func(t *testing.T) {
   311  
   312  			for a := range sbom.Artifacts.Packages.Enumerate(c.pkgType) {
   313  				assert.NotEmpty(t, a.Locations.ToSlice(), "package %q has no locations (type=%q)", a.Name, a.Type)
   314  				for _, l := range a.Locations.ToSlice() {
   315  					if _, exists := l.Annotations[pkg.EvidenceAnnotationKey]; !exists {
   316  						pkgTypesMissingEvidence.Add(string(a.Type))
   317  						t.Errorf("missing evidence annotation (pkg=%s type=%s)", a.Name, a.Type)
   318  					}
   319  				}
   320  			}
   321  
   322  		})
   323  	}
   324  
   325  	if pkgTypesMissingEvidence.Size() > 0 {
   326  		t.Log("Package types missing evidence annotations (dir resolver): ", pkgTypesMissingEvidence.List())
   327  	}
   328  }