github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/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/source" 16 ) 17 18 func BenchmarkImagePackageCatalogers(b *testing.B) { 19 // get the fixture image tar file 20 fixtureImageName := "image-pkg-coverage" 21 imagetest.GetFixtureImage(b, "docker-archive", fixtureImageName) 22 tarPath := imagetest.GetFixtureImageTarPath(b, fixtureImageName) 23 24 // get the source object for the image 25 theSource, err := syft.GetSource(context.Background(), tarPath, syft.DefaultGetSourceConfig().WithSources("docker-archive")) 26 require.NoError(b, err) 27 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.CPP.String()) 55 definedLanguages.Remove(pkg.Haskell.String()) 56 definedLanguages.Remove(pkg.Elixir.String()) 57 definedLanguages.Remove(pkg.Erlang.String()) 58 59 observedPkgs := strset.New() 60 definedPkgs := strset.New() 61 for _, p := range pkg.AllPkgs { 62 definedPkgs.Add(string(p)) 63 } 64 65 // for image scans we should not expect to see any of the following package types 66 definedPkgs.Remove(string(pkg.KbPkg)) 67 definedPkgs.Remove(string(pkg.GoModulePkg)) 68 definedPkgs.Remove(string(pkg.RustPkg)) 69 definedPkgs.Remove(string(pkg.DartPubPkg)) 70 definedPkgs.Remove(string(pkg.ErlangOTPPkg)) 71 definedPkgs.Remove(string(pkg.CocoapodsPkg)) 72 definedPkgs.Remove(string(pkg.ConanPkg)) 73 definedPkgs.Remove(string(pkg.HackagePkg)) 74 definedPkgs.Remove(string(pkg.BinaryPkg)) 75 definedPkgs.Remove(string(pkg.HexPkg)) 76 definedPkgs.Remove(string(pkg.LinuxKernelPkg)) 77 definedPkgs.Remove(string(pkg.LinuxKernelModulePkg)) 78 definedPkgs.Remove(string(pkg.SwiftPkg)) 79 definedPkgs.Remove(string(pkg.GithubActionPkg)) 80 definedPkgs.Remove(string(pkg.GithubActionWorkflowPkg)) 81 82 var cases []testCase 83 cases = append(cases, commonTestCases...) 84 cases = append(cases, imageOnlyTestCases...) 85 86 for _, c := range cases { 87 t.Run(c.name, func(t *testing.T) { 88 pkgCount := 0 89 90 for a := range sbom.Artifacts.Packages.Enumerate(c.pkgType) { 91 if a.Language.String() != "" { 92 observedLanguages.Add(a.Language.String()) 93 } 94 95 observedPkgs.Add(string(a.Type)) 96 expectedVersion, ok := c.pkgInfo[a.Name] 97 if !ok { 98 t.Errorf("unexpected package found: %s", a.Name) 99 } 100 101 if expectedVersion != a.Version { 102 t.Errorf("unexpected package version (pkg=%s): %s, expected: %s", a.Name, a.Version, expectedVersion) 103 } 104 105 if a.Language != c.pkgLanguage { 106 t.Errorf("bad language (pkg=%+v): %+v", a.Name, a.Language) 107 } 108 109 if a.Type != c.pkgType { 110 t.Errorf("bad package type (pkg=%+v): %+v", a.Name, a.Type) 111 } 112 pkgCount++ 113 } 114 115 if pkgCount != len(c.pkgInfo)+c.duplicates { 116 t.Logf("Discovered packages of type %+v", c.pkgType) 117 for a := range sbom.Artifacts.Packages.Enumerate(c.pkgType) { 118 t.Log(" ", a) 119 } 120 t.Fatalf("unexpected package count: %d!=%d", pkgCount, len(c.pkgInfo)) 121 } 122 }) 123 } 124 125 observedLanguages.Remove(pkg.UnknownLanguage.String()) 126 definedLanguages.Remove(pkg.UnknownLanguage.String()) 127 observedPkgs.Remove(string(pkg.UnknownPkg)) 128 definedPkgs.Remove(string(pkg.UnknownPkg)) 129 130 missingLang := strset.Difference(definedLanguages, observedLanguages) 131 extraLang := strset.Difference(observedLanguages, definedLanguages) 132 133 // ensure that integration test cases stay in sync with the available catalogers 134 if missingLang.Size() > 0 || extraLang.Size() > 0 { 135 t.Errorf("language coverage incomplete (languages=%d, coverage=%d)", definedLanguages.Size(), observedLanguages.Size()) 136 t.Errorf("unexpected languages: %s", extraLang.List()) 137 t.Errorf("missing languages: %s", missingLang.List()) 138 } 139 140 missingPkgs := strset.Difference(definedPkgs, observedPkgs) 141 extraPkgs := strset.Difference(observedPkgs, definedPkgs) 142 143 if missingPkgs.Size() > 0 || extraPkgs.Size() > 0 { 144 t.Errorf("package coverage incomplete (packages=%d, coverage=%d)", definedPkgs.Size(), observedPkgs.Size()) 145 t.Errorf("unexpected packages: %s", extraPkgs.List()) 146 t.Errorf("missing packages: %s", missingPkgs.List()) 147 } 148 } 149 150 func TestPkgCoverageDirectory(t *testing.T) { 151 sbom, _ := catalogDirectory(t, "test-fixtures/image-pkg-coverage") 152 153 observedLanguages := strset.New() 154 definedLanguages := strset.New() 155 for _, l := range pkg.AllLanguages { 156 definedLanguages.Add(l.String()) 157 } 158 159 observedPkgs := strset.New() 160 definedPkgs := strset.New() 161 for _, p := range pkg.AllPkgs { 162 definedPkgs.Add(string(p)) 163 } 164 165 var cases []testCase 166 cases = append(cases, commonTestCases...) 167 cases = append(cases, dirOnlyTestCases...) 168 169 for _, test := range cases { 170 t.Run(test.name, func(t *testing.T) { 171 actualPkgCount := 0 172 173 for actualPkg := range sbom.Artifacts.Packages.Enumerate(test.pkgType) { 174 observedLanguages.Add(actualPkg.Language.String()) 175 observedPkgs.Add(string(actualPkg.Type)) 176 177 expectedVersion, ok := test.pkgInfo[actualPkg.Name] 178 if !ok { 179 t.Errorf("unexpected package found: %s", actualPkg.Name) 180 } 181 182 if expectedVersion != actualPkg.Version { 183 t.Errorf("unexpected package version (pkg=%s): %s", actualPkg.Name, actualPkg.Version) 184 } 185 186 var foundLang bool 187 for _, lang := range strings.Split(test.pkgLanguage.String(), ",") { 188 if actualPkg.Language.String() == lang { 189 foundLang = true 190 break 191 } 192 } 193 if !foundLang { 194 t.Errorf("bad language (pkg=%+v): %+v", actualPkg.Name, actualPkg.Language) 195 } 196 197 if actualPkg.Type != test.pkgType { 198 t.Errorf("bad package type (pkg=%+v): %+v", actualPkg.Name, actualPkg.Type) 199 } 200 actualPkgCount++ 201 } 202 203 if actualPkgCount != len(test.pkgInfo)+test.duplicates { 204 for actualPkg := range sbom.Artifacts.Packages.Enumerate(test.pkgType) { 205 t.Log(" ", actualPkg) 206 } 207 t.Fatalf("unexpected package count: %d!=%d", actualPkgCount, len(test.pkgInfo)) 208 } 209 }) 210 } 211 212 observedLanguages.Remove(pkg.UnknownLanguage.String()) 213 definedLanguages.Remove(pkg.UnknownLanguage.String()) 214 definedLanguages.Remove(pkg.R.String()) 215 observedPkgs.Remove(string(pkg.UnknownPkg)) 216 definedPkgs.Remove(string(pkg.BinaryPkg)) 217 definedPkgs.Remove(string(pkg.LinuxKernelPkg)) 218 definedPkgs.Remove(string(pkg.LinuxKernelModulePkg)) 219 definedPkgs.Remove(string(pkg.Rpkg)) 220 definedPkgs.Remove(string(pkg.UnknownPkg)) 221 222 // for directory scans we should not expect to see any of the following package types 223 definedPkgs.Remove(string(pkg.KbPkg)) 224 225 // ensure that integration test commonTestCases stay in sync with the available catalogers 226 if observedLanguages.Size() < definedLanguages.Size() { 227 t.Errorf("language coverage incomplete (languages=%d, coverage=%d)", definedLanguages.Size(), observedLanguages.Size()) 228 } 229 230 if observedPkgs.Size() < definedPkgs.Size() { 231 t.Errorf("package coverage incomplete (packages=%d, coverage=%d)", definedPkgs.Size(), observedPkgs.Size()) 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 }