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 }