github.com/anchore/syft@v1.38.2/cmd/syft/internal/test/integration/catalog_packages_test.go (about)

     1  package integration
     2  
     3  import (
     4  	"context"
     5  	"strings"
     6  	"testing"
     7  
     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/syft"
    14  	"github.com/anchore/syft/syft/pkg"
    15  	"github.com/anchore/syft/syft/sbom"
    16  	"github.com/anchore/syft/syft/source"
    17  )
    18  
    19  func BenchmarkImagePackageCatalogers(b *testing.B) {
    20  	// get the fixture image tar file
    21  	fixtureImageName := "image-pkg-coverage"
    22  	imagetest.GetFixtureImage(b, "docker-archive", fixtureImageName)
    23  	tarPath := imagetest.GetFixtureImageTarPath(b, fixtureImageName)
    24  
    25  	// get the source object for the image
    26  	theSource, err := syft.GetSource(context.Background(), tarPath, syft.DefaultGetSourceConfig().WithSources("docker-archive"))
    27  	require.NoError(b, err)
    28  	b.Cleanup(func() {
    29  		require.NoError(b, theSource.Close())
    30  	})
    31  
    32  	// build the SBOM
    33  	s, err := syft.CreateSBOM(context.Background(), theSource, syft.DefaultCreateSBOMConfig())
    34  
    35  	// did it work?
    36  	require.NoError(b, err)
    37  	require.NotNil(b, s)
    38  }
    39  
    40  func TestPkgCoverageImage(t *testing.T) {
    41  	sbom, _ := catalogFixtureImage(t, "image-pkg-coverage", source.SquashedScope)
    42  
    43  	observedLanguages := strset.New()
    44  	definedLanguages := strset.New()
    45  	for _, l := range pkg.AllLanguages {
    46  		definedLanguages.Add(l.String())
    47  	}
    48  
    49  	// for image scans we should not expect to see any of the following package types
    50  	definedLanguages.Remove(pkg.Go.String())
    51  	definedLanguages.Remove(pkg.Rust.String())
    52  	definedLanguages.Remove(pkg.Dart.String())
    53  	definedLanguages.Remove(pkg.Swift.String())
    54  	definedLanguages.Remove(pkg.Swipl.String())
    55  	definedLanguages.Remove(pkg.OCaml.String())
    56  	definedLanguages.Remove(pkg.CPP.String())
    57  	definedLanguages.Remove(pkg.Haskell.String())
    58  	definedLanguages.Remove(pkg.Elixir.String())
    59  	definedLanguages.Remove(pkg.Erlang.String())
    60  
    61  	observedPkgs := strset.New()
    62  	definedPkgs := strset.New()
    63  	for _, p := range pkg.AllPkgs {
    64  		definedPkgs.Add(string(p))
    65  	}
    66  
    67  	// for image scans we should not expect to see any of the following package types
    68  	definedPkgs.Remove(string(pkg.KbPkg))
    69  	definedPkgs.Remove(string(pkg.GoModulePkg))
    70  	definedPkgs.Remove(string(pkg.RustPkg))
    71  	definedPkgs.Remove(string(pkg.DartPubPkg))
    72  	definedPkgs.Remove(string(pkg.ErlangOTPPkg))
    73  	definedPkgs.Remove(string(pkg.CocoapodsPkg))
    74  	definedPkgs.Remove(string(pkg.ConanPkg))
    75  	definedPkgs.Remove(string(pkg.HackagePkg))
    76  	definedPkgs.Remove(string(pkg.BinaryPkg))
    77  	definedPkgs.Remove(string(pkg.BitnamiPkg))
    78  	definedPkgs.Remove(string(pkg.GraalVMNativeImagePkg))
    79  	definedPkgs.Remove(string(pkg.HexPkg))
    80  	definedPkgs.Remove(string(pkg.LinuxKernelPkg))
    81  	definedPkgs.Remove(string(pkg.LinuxKernelModulePkg))
    82  	definedPkgs.Remove(string(pkg.SwiftPkg))
    83  	definedPkgs.Remove(string(pkg.SwiplPackPkg))
    84  	definedPkgs.Remove(string(pkg.OpamPkg))
    85  	definedPkgs.Remove(string(pkg.GithubActionPkg))
    86  	definedPkgs.Remove(string(pkg.GithubActionWorkflowPkg))
    87  	definedPkgs.Remove(string(pkg.TerraformPkg))
    88  	definedPkgs.Remove(string(pkg.PhpPeclPkg)) // we have coverage for pear instead
    89  	definedPkgs.Remove(string(pkg.CondaPkg))
    90  	definedPkgs.Remove(string(pkg.ModelPkg))
    91  
    92  	var cases []testCase
    93  	cases = append(cases, commonTestCases...)
    94  	cases = append(cases, imageOnlyTestCases...)
    95  
    96  	for _, c := range cases {
    97  		t.Run(c.name, func(t *testing.T) {
    98  			assertPackages(t, sbom, c, observedLanguages, observedPkgs)
    99  		})
   100  	}
   101  
   102  	observedLanguages.Remove(pkg.UnknownLanguage.String())
   103  	definedLanguages.Remove(pkg.UnknownLanguage.String())
   104  	observedPkgs.Remove(string(pkg.UnknownPkg))
   105  	definedPkgs.Remove(string(pkg.UnknownPkg))
   106  
   107  	missingLang := strset.Difference(definedLanguages, observedLanguages)
   108  	extraLang := strset.Difference(observedLanguages, definedLanguages)
   109  
   110  	// ensure that integration test cases stay in sync with the available catalogers
   111  	if missingLang.Size() > 0 || extraLang.Size() > 0 {
   112  		t.Errorf("language coverage incomplete (languages=%d, coverage=%d)", definedLanguages.Size(), observedLanguages.Size())
   113  		t.Errorf("unexpected languages: %s", extraLang.List())
   114  		t.Errorf("missing languages: %s", missingLang.List())
   115  	}
   116  
   117  	missingPkgs := strset.Difference(definedPkgs, observedPkgs)
   118  	extraPkgs := strset.Difference(observedPkgs, definedPkgs)
   119  
   120  	if missingPkgs.Size() > 0 || extraPkgs.Size() > 0 {
   121  		t.Errorf("package coverage incomplete (packages=%d, coverage=%d)", definedPkgs.Size(), observedPkgs.Size())
   122  		t.Errorf("unexpected packages: %s", extraPkgs.List())
   123  		t.Errorf("missing packages: %s", missingPkgs.List())
   124  	}
   125  }
   126  
   127  func TestPkgCoverageDirectory(t *testing.T) {
   128  	sbom, _ := catalogDirectory(t, "test-fixtures/image-pkg-coverage")
   129  
   130  	observedLanguages := strset.New()
   131  	definedLanguages := strset.New()
   132  	for _, l := range pkg.AllLanguages {
   133  		definedLanguages.Add(l.String())
   134  	}
   135  
   136  	observedPkgs := strset.New()
   137  	definedPkgs := strset.New()
   138  	for _, p := range pkg.AllPkgs {
   139  		definedPkgs.Add(string(p))
   140  	}
   141  
   142  	var cases []testCase
   143  	cases = append(cases, commonTestCases...)
   144  	cases = append(cases, dirOnlyTestCases...)
   145  
   146  	for _, test := range cases {
   147  		t.Run(test.name, func(t *testing.T) {
   148  			assertPackages(t, sbom, test, observedLanguages, observedPkgs)
   149  		})
   150  	}
   151  
   152  	observedLanguages.Remove(pkg.UnknownLanguage.String())
   153  	definedLanguages.Remove(pkg.UnknownLanguage.String())
   154  	definedLanguages.Remove(pkg.R.String())
   155  	observedPkgs.Remove(string(pkg.UnknownPkg))
   156  	definedPkgs.Remove(string(pkg.BinaryPkg))
   157  	definedPkgs.Remove(string(pkg.BitnamiPkg))
   158  	definedPkgs.Remove(string(pkg.GraalVMNativeImagePkg))
   159  	definedPkgs.Remove(string(pkg.LinuxKernelPkg))
   160  	definedPkgs.Remove(string(pkg.LinuxKernelModulePkg))
   161  	definedPkgs.Remove(string(pkg.Rpkg))
   162  	definedPkgs.Remove(string(pkg.UnknownPkg))
   163  	definedPkgs.Remove(string(pkg.CondaPkg))
   164  	definedPkgs.Remove(string(pkg.PhpPeclPkg)) // this is covered as pear packages
   165  	definedPkgs.Remove(string(pkg.ModelPkg))
   166  
   167  	// for directory scans we should not expect to see any of the following package types
   168  	definedPkgs.Remove(string(pkg.KbPkg))
   169  
   170  	// ensure that integration test commonTestCases stay in sync with the available catalogers
   171  	if observedLanguages.Size() < definedLanguages.Size() {
   172  		t.Errorf("language coverage incomplete (languages=%d, coverage=%d)", definedLanguages.Size(), observedLanguages.Size())
   173  	}
   174  
   175  	if observedPkgs.Size() < definedPkgs.Size() {
   176  		t.Errorf("package coverage incomplete (packages=%d, coverage=%d)", definedPkgs.Size(), observedPkgs.Size())
   177  	}
   178  }
   179  
   180  func assertPackages(t *testing.T, sbom sbom.SBOM, test testCase, observedLanguages *strset.Set, observedPkgs *strset.Set) {
   181  	actualPkgCount := 0
   182  
   183  	for actualPkg := range sbom.Artifacts.Packages.Enumerate(test.pkgType) {
   184  		observedLanguages.Add(actualPkg.Language.String())
   185  		observedPkgs.Add(string(actualPkg.Type))
   186  
   187  		expectedVersion, ok := test.pkgInfo[actualPkg.Name]
   188  		if !ok {
   189  			t.Errorf("unexpected package found: %s", actualPkg.Name)
   190  		}
   191  
   192  		if expectedVersion != actualPkg.Version {
   193  			t.Errorf("unexpected package version (pkg=%s): %s", actualPkg.Name, actualPkg.Version)
   194  		}
   195  
   196  		var foundLang bool
   197  		for _, lang := range strings.Split(test.pkgLanguage.String(), ",") {
   198  			if actualPkg.Language.String() == lang {
   199  				foundLang = true
   200  				break
   201  			}
   202  		}
   203  		if !foundLang {
   204  			t.Errorf("bad language (pkg=%+v): %+v", actualPkg.Name, actualPkg.Language)
   205  		}
   206  
   207  		if actualPkg.Type != test.pkgType {
   208  			t.Errorf("bad package type (pkg=%+v): %+v", actualPkg.Name, actualPkg.Type)
   209  		}
   210  		actualPkgCount++
   211  
   212  		// all packages should have at least one location associated with it, and of those locations at least one should be primary evidence
   213  		locs := actualPkg.Locations.ToSlice()
   214  		assert.NotEmpty(t, locs, "package %q has no locations (type=%q)", actualPkg.Name, actualPkg.Type)
   215  		var primaryEvidenceFound bool
   216  		for _, l := range locs {
   217  			if _, exists := l.Annotations[pkg.EvidenceAnnotationKey]; !exists {
   218  				t.Errorf("missing evidence annotation (pkg=%s type=%s)", actualPkg.Name, actualPkg.Type)
   219  			}
   220  			if l.Annotations[pkg.EvidenceAnnotationKey] == pkg.PrimaryEvidenceAnnotation {
   221  				primaryEvidenceFound = true
   222  			}
   223  		}
   224  		assert.True(t, primaryEvidenceFound, "no primary evidence found for package %q", actualPkg.Name)
   225  	}
   226  
   227  	if actualPkgCount != len(test.pkgInfo)+test.duplicates {
   228  		for actualPkg := range sbom.Artifacts.Packages.Enumerate(test.pkgType) {
   229  			t.Log("   ", actualPkg)
   230  		}
   231  		t.Fatalf("unexpected package count: %d!=%d", actualPkgCount, len(test.pkgInfo))
   232  	}
   233  }
   234  
   235  func TestPkgCoverageImage_HasEvidence(t *testing.T) {
   236  	sbom, _ := catalogFixtureImage(t, "image-pkg-coverage", source.SquashedScope)
   237  
   238  	var cases []testCase
   239  	cases = append(cases, commonTestCases...)
   240  	cases = append(cases, imageOnlyTestCases...)
   241  
   242  	pkgTypesMissingEvidence := strset.New()
   243  
   244  	for _, c := range cases {
   245  		t.Run(c.name, func(t *testing.T) {
   246  			for a := range sbom.Artifacts.Packages.Enumerate(c.pkgType) {
   247  				assert.NotEmpty(t, a.Locations.ToSlice(), "package %q has no locations (type=%q)", a.Name, a.Type)
   248  				for _, l := range a.Locations.ToSlice() {
   249  					if _, exists := l.Annotations[pkg.EvidenceAnnotationKey]; !exists {
   250  						pkgTypesMissingEvidence.Add(string(a.Type))
   251  						t.Errorf("missing evidence annotation (pkg=%s type=%s)", a.Name, a.Type)
   252  					}
   253  				}
   254  			}
   255  		})
   256  	}
   257  
   258  	if pkgTypesMissingEvidence.Size() > 0 {
   259  		t.Log("Package types missing evidence annotations (img resolver): ", pkgTypesMissingEvidence.List())
   260  	}
   261  }
   262  
   263  func TestPkgCoverageDirectory_HasEvidence(t *testing.T) {
   264  	sbom, _ := catalogDirectory(t, "test-fixtures/image-pkg-coverage")
   265  
   266  	var cases []testCase
   267  	cases = append(cases, commonTestCases...)
   268  	cases = append(cases, imageOnlyTestCases...)
   269  
   270  	pkgTypesMissingEvidence := strset.New()
   271  
   272  	for _, c := range cases {
   273  		t.Run(c.name, func(t *testing.T) {
   274  			for a := range sbom.Artifacts.Packages.Enumerate(c.pkgType) {
   275  				assert.NotEmpty(t, a.Locations.ToSlice(), "package %q has no locations (type=%q)", a.Name, a.Type)
   276  				for _, l := range a.Locations.ToSlice() {
   277  					if _, exists := l.Annotations[pkg.EvidenceAnnotationKey]; !exists {
   278  						pkgTypesMissingEvidence.Add(string(a.Type))
   279  						t.Errorf("missing evidence annotation (pkg=%s type=%s)", a.Name, a.Type)
   280  					}
   281  				}
   282  			}
   283  		})
   284  	}
   285  
   286  	if pkgTypesMissingEvidence.Size() > 0 {
   287  		t.Log("Package types missing evidence annotations (dir resolver): ", pkgTypesMissingEvidence.List())
   288  	}
   289  }