github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/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/source"
    16  )
    17  
    18  func BenchmarkImagePackageCatalogers(b *testing.B) {
    19  	// get the fixture image tar file
    20  	fixtureImageName := "image-pkg-coverage"
    21  	imagetest.GetFixtureImage(b, "docker-archive", fixtureImageName)
    22  	tarPath := imagetest.GetFixtureImageTarPath(b, fixtureImageName)
    23  
    24  	// get the source object for the image
    25  	theSource, err := syft.GetSource(context.Background(), tarPath, syft.DefaultGetSourceConfig().WithSources("docker-archive"))
    26  	require.NoError(b, err)
    27  
    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.CPP.String())
    55  	definedLanguages.Remove(pkg.Haskell.String())
    56  	definedLanguages.Remove(pkg.Elixir.String())
    57  	definedLanguages.Remove(pkg.Erlang.String())
    58  
    59  	observedPkgs := strset.New()
    60  	definedPkgs := strset.New()
    61  	for _, p := range pkg.AllPkgs {
    62  		definedPkgs.Add(string(p))
    63  	}
    64  
    65  	// for image scans we should not expect to see any of the following package types
    66  	definedPkgs.Remove(string(pkg.KbPkg))
    67  	definedPkgs.Remove(string(pkg.GoModulePkg))
    68  	definedPkgs.Remove(string(pkg.RustPkg))
    69  	definedPkgs.Remove(string(pkg.DartPubPkg))
    70  	definedPkgs.Remove(string(pkg.ErlangOTPPkg))
    71  	definedPkgs.Remove(string(pkg.CocoapodsPkg))
    72  	definedPkgs.Remove(string(pkg.ConanPkg))
    73  	definedPkgs.Remove(string(pkg.HackagePkg))
    74  	definedPkgs.Remove(string(pkg.BinaryPkg))
    75  	definedPkgs.Remove(string(pkg.HexPkg))
    76  	definedPkgs.Remove(string(pkg.LinuxKernelPkg))
    77  	definedPkgs.Remove(string(pkg.LinuxKernelModulePkg))
    78  	definedPkgs.Remove(string(pkg.SwiftPkg))
    79  	definedPkgs.Remove(string(pkg.GithubActionPkg))
    80  	definedPkgs.Remove(string(pkg.GithubActionWorkflowPkg))
    81  
    82  	var cases []testCase
    83  	cases = append(cases, commonTestCases...)
    84  	cases = append(cases, imageOnlyTestCases...)
    85  
    86  	for _, c := range cases {
    87  		t.Run(c.name, func(t *testing.T) {
    88  			pkgCount := 0
    89  
    90  			for a := range sbom.Artifacts.Packages.Enumerate(c.pkgType) {
    91  				if a.Language.String() != "" {
    92  					observedLanguages.Add(a.Language.String())
    93  				}
    94  
    95  				observedPkgs.Add(string(a.Type))
    96  				expectedVersion, ok := c.pkgInfo[a.Name]
    97  				if !ok {
    98  					t.Errorf("unexpected package found: %s", a.Name)
    99  				}
   100  
   101  				if expectedVersion != a.Version {
   102  					t.Errorf("unexpected package version (pkg=%s): %s, expected: %s", a.Name, a.Version, expectedVersion)
   103  				}
   104  
   105  				if a.Language != c.pkgLanguage {
   106  					t.Errorf("bad language (pkg=%+v): %+v", a.Name, a.Language)
   107  				}
   108  
   109  				if a.Type != c.pkgType {
   110  					t.Errorf("bad package type (pkg=%+v): %+v", a.Name, a.Type)
   111  				}
   112  				pkgCount++
   113  			}
   114  
   115  			if pkgCount != len(c.pkgInfo)+c.duplicates {
   116  				t.Logf("Discovered packages of type %+v", c.pkgType)
   117  				for a := range sbom.Artifacts.Packages.Enumerate(c.pkgType) {
   118  					t.Log("   ", a)
   119  				}
   120  				t.Fatalf("unexpected package count: %d!=%d", pkgCount, len(c.pkgInfo))
   121  			}
   122  		})
   123  	}
   124  
   125  	observedLanguages.Remove(pkg.UnknownLanguage.String())
   126  	definedLanguages.Remove(pkg.UnknownLanguage.String())
   127  	observedPkgs.Remove(string(pkg.UnknownPkg))
   128  	definedPkgs.Remove(string(pkg.UnknownPkg))
   129  
   130  	missingLang := strset.Difference(definedLanguages, observedLanguages)
   131  	extraLang := strset.Difference(observedLanguages, definedLanguages)
   132  
   133  	// ensure that integration test cases stay in sync with the available catalogers
   134  	if missingLang.Size() > 0 || extraLang.Size() > 0 {
   135  		t.Errorf("language coverage incomplete (languages=%d, coverage=%d)", definedLanguages.Size(), observedLanguages.Size())
   136  		t.Errorf("unexpected languages: %s", extraLang.List())
   137  		t.Errorf("missing languages: %s", missingLang.List())
   138  	}
   139  
   140  	missingPkgs := strset.Difference(definedPkgs, observedPkgs)
   141  	extraPkgs := strset.Difference(observedPkgs, definedPkgs)
   142  
   143  	if missingPkgs.Size() > 0 || extraPkgs.Size() > 0 {
   144  		t.Errorf("package coverage incomplete (packages=%d, coverage=%d)", definedPkgs.Size(), observedPkgs.Size())
   145  		t.Errorf("unexpected packages: %s", extraPkgs.List())
   146  		t.Errorf("missing packages: %s", missingPkgs.List())
   147  	}
   148  }
   149  
   150  func TestPkgCoverageDirectory(t *testing.T) {
   151  	sbom, _ := catalogDirectory(t, "test-fixtures/image-pkg-coverage")
   152  
   153  	observedLanguages := strset.New()
   154  	definedLanguages := strset.New()
   155  	for _, l := range pkg.AllLanguages {
   156  		definedLanguages.Add(l.String())
   157  	}
   158  
   159  	observedPkgs := strset.New()
   160  	definedPkgs := strset.New()
   161  	for _, p := range pkg.AllPkgs {
   162  		definedPkgs.Add(string(p))
   163  	}
   164  
   165  	var cases []testCase
   166  	cases = append(cases, commonTestCases...)
   167  	cases = append(cases, dirOnlyTestCases...)
   168  
   169  	for _, test := range cases {
   170  		t.Run(test.name, func(t *testing.T) {
   171  			actualPkgCount := 0
   172  
   173  			for actualPkg := range sbom.Artifacts.Packages.Enumerate(test.pkgType) {
   174  				observedLanguages.Add(actualPkg.Language.String())
   175  				observedPkgs.Add(string(actualPkg.Type))
   176  
   177  				expectedVersion, ok := test.pkgInfo[actualPkg.Name]
   178  				if !ok {
   179  					t.Errorf("unexpected package found: %s", actualPkg.Name)
   180  				}
   181  
   182  				if expectedVersion != actualPkg.Version {
   183  					t.Errorf("unexpected package version (pkg=%s): %s", actualPkg.Name, actualPkg.Version)
   184  				}
   185  
   186  				var foundLang bool
   187  				for _, lang := range strings.Split(test.pkgLanguage.String(), ",") {
   188  					if actualPkg.Language.String() == lang {
   189  						foundLang = true
   190  						break
   191  					}
   192  				}
   193  				if !foundLang {
   194  					t.Errorf("bad language (pkg=%+v): %+v", actualPkg.Name, actualPkg.Language)
   195  				}
   196  
   197  				if actualPkg.Type != test.pkgType {
   198  					t.Errorf("bad package type (pkg=%+v): %+v", actualPkg.Name, actualPkg.Type)
   199  				}
   200  				actualPkgCount++
   201  			}
   202  
   203  			if actualPkgCount != len(test.pkgInfo)+test.duplicates {
   204  				for actualPkg := range sbom.Artifacts.Packages.Enumerate(test.pkgType) {
   205  					t.Log("   ", actualPkg)
   206  				}
   207  				t.Fatalf("unexpected package count: %d!=%d", actualPkgCount, len(test.pkgInfo))
   208  			}
   209  		})
   210  	}
   211  
   212  	observedLanguages.Remove(pkg.UnknownLanguage.String())
   213  	definedLanguages.Remove(pkg.UnknownLanguage.String())
   214  	definedLanguages.Remove(pkg.R.String())
   215  	observedPkgs.Remove(string(pkg.UnknownPkg))
   216  	definedPkgs.Remove(string(pkg.BinaryPkg))
   217  	definedPkgs.Remove(string(pkg.LinuxKernelPkg))
   218  	definedPkgs.Remove(string(pkg.LinuxKernelModulePkg))
   219  	definedPkgs.Remove(string(pkg.Rpkg))
   220  	definedPkgs.Remove(string(pkg.UnknownPkg))
   221  
   222  	// for directory scans we should not expect to see any of the following package types
   223  	definedPkgs.Remove(string(pkg.KbPkg))
   224  
   225  	// ensure that integration test commonTestCases stay in sync with the available catalogers
   226  	if observedLanguages.Size() < definedLanguages.Size() {
   227  		t.Errorf("language coverage incomplete (languages=%d, coverage=%d)", definedLanguages.Size(), observedLanguages.Size())
   228  	}
   229  
   230  	if observedPkgs.Size() < definedPkgs.Size() {
   231  		t.Errorf("package coverage incomplete (packages=%d, coverage=%d)", definedPkgs.Size(), observedPkgs.Size())
   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  }