github.com/anchore/syft@v1.38.2/cmd/syft/internal/test/integration/catalog_packages_test.go (about) 1 package integration 2 3 import ( 4 "context" 5 "strings" 6 "testing" 7 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/syft" 14 "github.com/anchore/syft/syft/pkg" 15 "github.com/anchore/syft/syft/sbom" 16 "github.com/anchore/syft/syft/source" 17 ) 18 19 func BenchmarkImagePackageCatalogers(b *testing.B) { 20 // get the fixture image tar file 21 fixtureImageName := "image-pkg-coverage" 22 imagetest.GetFixtureImage(b, "docker-archive", fixtureImageName) 23 tarPath := imagetest.GetFixtureImageTarPath(b, fixtureImageName) 24 25 // get the source object for the image 26 theSource, err := syft.GetSource(context.Background(), tarPath, syft.DefaultGetSourceConfig().WithSources("docker-archive")) 27 require.NoError(b, err) 28 b.Cleanup(func() { 29 require.NoError(b, theSource.Close()) 30 }) 31 32 // build the SBOM 33 s, err := syft.CreateSBOM(context.Background(), theSource, syft.DefaultCreateSBOMConfig()) 34 35 // did it work? 36 require.NoError(b, err) 37 require.NotNil(b, s) 38 } 39 40 func TestPkgCoverageImage(t *testing.T) { 41 sbom, _ := catalogFixtureImage(t, "image-pkg-coverage", source.SquashedScope) 42 43 observedLanguages := strset.New() 44 definedLanguages := strset.New() 45 for _, l := range pkg.AllLanguages { 46 definedLanguages.Add(l.String()) 47 } 48 49 // for image scans we should not expect to see any of the following package types 50 definedLanguages.Remove(pkg.Go.String()) 51 definedLanguages.Remove(pkg.Rust.String()) 52 definedLanguages.Remove(pkg.Dart.String()) 53 definedLanguages.Remove(pkg.Swift.String()) 54 definedLanguages.Remove(pkg.Swipl.String()) 55 definedLanguages.Remove(pkg.OCaml.String()) 56 definedLanguages.Remove(pkg.CPP.String()) 57 definedLanguages.Remove(pkg.Haskell.String()) 58 definedLanguages.Remove(pkg.Elixir.String()) 59 definedLanguages.Remove(pkg.Erlang.String()) 60 61 observedPkgs := strset.New() 62 definedPkgs := strset.New() 63 for _, p := range pkg.AllPkgs { 64 definedPkgs.Add(string(p)) 65 } 66 67 // for image scans we should not expect to see any of the following package types 68 definedPkgs.Remove(string(pkg.KbPkg)) 69 definedPkgs.Remove(string(pkg.GoModulePkg)) 70 definedPkgs.Remove(string(pkg.RustPkg)) 71 definedPkgs.Remove(string(pkg.DartPubPkg)) 72 definedPkgs.Remove(string(pkg.ErlangOTPPkg)) 73 definedPkgs.Remove(string(pkg.CocoapodsPkg)) 74 definedPkgs.Remove(string(pkg.ConanPkg)) 75 definedPkgs.Remove(string(pkg.HackagePkg)) 76 definedPkgs.Remove(string(pkg.BinaryPkg)) 77 definedPkgs.Remove(string(pkg.BitnamiPkg)) 78 definedPkgs.Remove(string(pkg.GraalVMNativeImagePkg)) 79 definedPkgs.Remove(string(pkg.HexPkg)) 80 definedPkgs.Remove(string(pkg.LinuxKernelPkg)) 81 definedPkgs.Remove(string(pkg.LinuxKernelModulePkg)) 82 definedPkgs.Remove(string(pkg.SwiftPkg)) 83 definedPkgs.Remove(string(pkg.SwiplPackPkg)) 84 definedPkgs.Remove(string(pkg.OpamPkg)) 85 definedPkgs.Remove(string(pkg.GithubActionPkg)) 86 definedPkgs.Remove(string(pkg.GithubActionWorkflowPkg)) 87 definedPkgs.Remove(string(pkg.TerraformPkg)) 88 definedPkgs.Remove(string(pkg.PhpPeclPkg)) // we have coverage for pear instead 89 definedPkgs.Remove(string(pkg.CondaPkg)) 90 definedPkgs.Remove(string(pkg.ModelPkg)) 91 92 var cases []testCase 93 cases = append(cases, commonTestCases...) 94 cases = append(cases, imageOnlyTestCases...) 95 96 for _, c := range cases { 97 t.Run(c.name, func(t *testing.T) { 98 assertPackages(t, sbom, c, observedLanguages, observedPkgs) 99 }) 100 } 101 102 observedLanguages.Remove(pkg.UnknownLanguage.String()) 103 definedLanguages.Remove(pkg.UnknownLanguage.String()) 104 observedPkgs.Remove(string(pkg.UnknownPkg)) 105 definedPkgs.Remove(string(pkg.UnknownPkg)) 106 107 missingLang := strset.Difference(definedLanguages, observedLanguages) 108 extraLang := strset.Difference(observedLanguages, definedLanguages) 109 110 // ensure that integration test cases stay in sync with the available catalogers 111 if missingLang.Size() > 0 || extraLang.Size() > 0 { 112 t.Errorf("language coverage incomplete (languages=%d, coverage=%d)", definedLanguages.Size(), observedLanguages.Size()) 113 t.Errorf("unexpected languages: %s", extraLang.List()) 114 t.Errorf("missing languages: %s", missingLang.List()) 115 } 116 117 missingPkgs := strset.Difference(definedPkgs, observedPkgs) 118 extraPkgs := strset.Difference(observedPkgs, definedPkgs) 119 120 if missingPkgs.Size() > 0 || extraPkgs.Size() > 0 { 121 t.Errorf("package coverage incomplete (packages=%d, coverage=%d)", definedPkgs.Size(), observedPkgs.Size()) 122 t.Errorf("unexpected packages: %s", extraPkgs.List()) 123 t.Errorf("missing packages: %s", missingPkgs.List()) 124 } 125 } 126 127 func TestPkgCoverageDirectory(t *testing.T) { 128 sbom, _ := catalogDirectory(t, "test-fixtures/image-pkg-coverage") 129 130 observedLanguages := strset.New() 131 definedLanguages := strset.New() 132 for _, l := range pkg.AllLanguages { 133 definedLanguages.Add(l.String()) 134 } 135 136 observedPkgs := strset.New() 137 definedPkgs := strset.New() 138 for _, p := range pkg.AllPkgs { 139 definedPkgs.Add(string(p)) 140 } 141 142 var cases []testCase 143 cases = append(cases, commonTestCases...) 144 cases = append(cases, dirOnlyTestCases...) 145 146 for _, test := range cases { 147 t.Run(test.name, func(t *testing.T) { 148 assertPackages(t, sbom, test, observedLanguages, observedPkgs) 149 }) 150 } 151 152 observedLanguages.Remove(pkg.UnknownLanguage.String()) 153 definedLanguages.Remove(pkg.UnknownLanguage.String()) 154 definedLanguages.Remove(pkg.R.String()) 155 observedPkgs.Remove(string(pkg.UnknownPkg)) 156 definedPkgs.Remove(string(pkg.BinaryPkg)) 157 definedPkgs.Remove(string(pkg.BitnamiPkg)) 158 definedPkgs.Remove(string(pkg.GraalVMNativeImagePkg)) 159 definedPkgs.Remove(string(pkg.LinuxKernelPkg)) 160 definedPkgs.Remove(string(pkg.LinuxKernelModulePkg)) 161 definedPkgs.Remove(string(pkg.Rpkg)) 162 definedPkgs.Remove(string(pkg.UnknownPkg)) 163 definedPkgs.Remove(string(pkg.CondaPkg)) 164 definedPkgs.Remove(string(pkg.PhpPeclPkg)) // this is covered as pear packages 165 definedPkgs.Remove(string(pkg.ModelPkg)) 166 167 // for directory scans we should not expect to see any of the following package types 168 definedPkgs.Remove(string(pkg.KbPkg)) 169 170 // ensure that integration test commonTestCases stay in sync with the available catalogers 171 if observedLanguages.Size() < definedLanguages.Size() { 172 t.Errorf("language coverage incomplete (languages=%d, coverage=%d)", definedLanguages.Size(), observedLanguages.Size()) 173 } 174 175 if observedPkgs.Size() < definedPkgs.Size() { 176 t.Errorf("package coverage incomplete (packages=%d, coverage=%d)", definedPkgs.Size(), observedPkgs.Size()) 177 } 178 } 179 180 func assertPackages(t *testing.T, sbom sbom.SBOM, test testCase, observedLanguages *strset.Set, observedPkgs *strset.Set) { 181 actualPkgCount := 0 182 183 for actualPkg := range sbom.Artifacts.Packages.Enumerate(test.pkgType) { 184 observedLanguages.Add(actualPkg.Language.String()) 185 observedPkgs.Add(string(actualPkg.Type)) 186 187 expectedVersion, ok := test.pkgInfo[actualPkg.Name] 188 if !ok { 189 t.Errorf("unexpected package found: %s", actualPkg.Name) 190 } 191 192 if expectedVersion != actualPkg.Version { 193 t.Errorf("unexpected package version (pkg=%s): %s", actualPkg.Name, actualPkg.Version) 194 } 195 196 var foundLang bool 197 for _, lang := range strings.Split(test.pkgLanguage.String(), ",") { 198 if actualPkg.Language.String() == lang { 199 foundLang = true 200 break 201 } 202 } 203 if !foundLang { 204 t.Errorf("bad language (pkg=%+v): %+v", actualPkg.Name, actualPkg.Language) 205 } 206 207 if actualPkg.Type != test.pkgType { 208 t.Errorf("bad package type (pkg=%+v): %+v", actualPkg.Name, actualPkg.Type) 209 } 210 actualPkgCount++ 211 212 // all packages should have at least one location associated with it, and of those locations at least one should be primary evidence 213 locs := actualPkg.Locations.ToSlice() 214 assert.NotEmpty(t, locs, "package %q has no locations (type=%q)", actualPkg.Name, actualPkg.Type) 215 var primaryEvidenceFound bool 216 for _, l := range locs { 217 if _, exists := l.Annotations[pkg.EvidenceAnnotationKey]; !exists { 218 t.Errorf("missing evidence annotation (pkg=%s type=%s)", actualPkg.Name, actualPkg.Type) 219 } 220 if l.Annotations[pkg.EvidenceAnnotationKey] == pkg.PrimaryEvidenceAnnotation { 221 primaryEvidenceFound = true 222 } 223 } 224 assert.True(t, primaryEvidenceFound, "no primary evidence found for package %q", actualPkg.Name) 225 } 226 227 if actualPkgCount != len(test.pkgInfo)+test.duplicates { 228 for actualPkg := range sbom.Artifacts.Packages.Enumerate(test.pkgType) { 229 t.Log(" ", actualPkg) 230 } 231 t.Fatalf("unexpected package count: %d!=%d", actualPkgCount, len(test.pkgInfo)) 232 } 233 } 234 235 func TestPkgCoverageImage_HasEvidence(t *testing.T) { 236 sbom, _ := catalogFixtureImage(t, "image-pkg-coverage", source.SquashedScope) 237 238 var cases []testCase 239 cases = append(cases, commonTestCases...) 240 cases = append(cases, imageOnlyTestCases...) 241 242 pkgTypesMissingEvidence := strset.New() 243 244 for _, c := range cases { 245 t.Run(c.name, func(t *testing.T) { 246 for a := range sbom.Artifacts.Packages.Enumerate(c.pkgType) { 247 assert.NotEmpty(t, a.Locations.ToSlice(), "package %q has no locations (type=%q)", a.Name, a.Type) 248 for _, l := range a.Locations.ToSlice() { 249 if _, exists := l.Annotations[pkg.EvidenceAnnotationKey]; !exists { 250 pkgTypesMissingEvidence.Add(string(a.Type)) 251 t.Errorf("missing evidence annotation (pkg=%s type=%s)", a.Name, a.Type) 252 } 253 } 254 } 255 }) 256 } 257 258 if pkgTypesMissingEvidence.Size() > 0 { 259 t.Log("Package types missing evidence annotations (img resolver): ", pkgTypesMissingEvidence.List()) 260 } 261 } 262 263 func TestPkgCoverageDirectory_HasEvidence(t *testing.T) { 264 sbom, _ := catalogDirectory(t, "test-fixtures/image-pkg-coverage") 265 266 var cases []testCase 267 cases = append(cases, commonTestCases...) 268 cases = append(cases, imageOnlyTestCases...) 269 270 pkgTypesMissingEvidence := strset.New() 271 272 for _, c := range cases { 273 t.Run(c.name, func(t *testing.T) { 274 for a := range sbom.Artifacts.Packages.Enumerate(c.pkgType) { 275 assert.NotEmpty(t, a.Locations.ToSlice(), "package %q has no locations (type=%q)", a.Name, a.Type) 276 for _, l := range a.Locations.ToSlice() { 277 if _, exists := l.Annotations[pkg.EvidenceAnnotationKey]; !exists { 278 pkgTypesMissingEvidence.Add(string(a.Type)) 279 t.Errorf("missing evidence annotation (pkg=%s type=%s)", a.Name, a.Type) 280 } 281 } 282 } 283 }) 284 } 285 286 if pkgTypesMissingEvidence.Size() > 0 { 287 t.Log("Package types missing evidence annotations (dir resolver): ", pkgTypesMissingEvidence.List()) 288 } 289 }