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