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