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