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 }