github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/internal/task/selection_test.go (about) 1 package task 2 3 import ( 4 "context" 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/syft/internal/sbomsync" 13 "github.com/anchore/syft/syft/cataloging/pkgcataloging" 14 "github.com/anchore/syft/syft/file" 15 ) 16 17 func dummyTask(name string, tags ...string) Task { 18 return NewTask(name, func(ctx context.Context, resolver file.Resolver, sbom sbomsync.Builder) error { 19 panic("not implemented") 20 }, tags...) 21 } 22 23 // note: this test fixture does not need to be kept up to date here, but makes a great test subject 24 func createDummyTasks() tasks { 25 return []Task{ 26 // OS package installed catalogers 27 dummyTask("alpm-db-cataloger", "directory", "installed", "image", "os", "alpm", "archlinux"), 28 dummyTask("apk-db-cataloger", "directory", "installed", "image", "os", "apk", "alpine"), 29 dummyTask("dpkg-db-cataloger", "directory", "installed", "image", "os", "dpkg", "debian"), 30 dummyTask("portage-cataloger", "directory", "installed", "image", "os", "portage", "gentoo"), 31 dummyTask("rpm-db-cataloger", "directory", "installed", "image", "os", "rpm", "redhat"), 32 33 // OS package declared catalogers 34 dummyTask("rpm-archive-cataloger", "declared", "directory", "os", "rpm", "redhat"), 35 36 // language-specific package installed catalogers 37 dummyTask("conan-info-cataloger", "installed", "image", "language", "cpp", "conan"), 38 dummyTask("javascript-package-cataloger", "installed", "image", "language", "javascript", "node"), 39 dummyTask("php-composer-installed-cataloger", "installed", "image", "language", "php", "composer"), 40 dummyTask("ruby-installed-gemspec-cataloger", "installed", "image", "language", "ruby", "gem", "gemspec"), 41 dummyTask("rust-cargo-lock-cataloger", "installed", "image", "language", "rust", "binary"), 42 43 // language-specific package declared catalogers 44 dummyTask("conan-cataloger", "declared", "directory", "language", "cpp", "conan"), 45 dummyTask("dart-pubspec-lock-cataloger", "declared", "directory", "language", "dart"), 46 dummyTask("dotnet-deps-cataloger", "declared", "directory", "language", "dotnet", "c#"), 47 dummyTask("elixir-mix-lock-cataloger", "declared", "directory", "language", "elixir"), 48 dummyTask("erlang-rebar-lock-cataloger", "declared", "directory", "language", "erlang"), 49 dummyTask("javascript-lock-cataloger", "declared", "directory", "language", "javascript", "node", "npm"), 50 51 // language-specific package for both image and directory scans (but not necessarily declared) 52 dummyTask("dotnet-portable-executable-cataloger", "directory", "installed", "image", "language", "dotnet", "c#"), 53 dummyTask("python-installed-package-cataloger", "directory", "installed", "image", "language", "python"), 54 dummyTask("go-module-binary-cataloger", "directory", "installed", "image", "language", "go", "golang", "gomod", "binary"), 55 dummyTask("java-archive-cataloger", "directory", "installed", "image", "language", "java", "maven"), 56 dummyTask("graalvm-native-image-cataloger", "directory", "installed", "image", "language", "java"), 57 58 // other package catalogers 59 dummyTask("binary-cataloger", "declared", "directory", "image", "binary"), 60 dummyTask("github-actions-usage-cataloger", "declared", "directory", "github", "github-actions"), 61 dummyTask("github-action-workflow-usage-cataloger", "declared", "directory", "github", "github-actions"), 62 dummyTask("sbom-cataloger", "declared", "directory", "image", "sbom"), 63 } 64 } 65 66 func TestSelect(t *testing.T) { 67 68 tests := []struct { 69 name string 70 allTasks []Task 71 basis []string 72 expressions []string 73 wantNames []string 74 wantTokens map[string]TokenSelection 75 wantRequest pkgcataloging.SelectionRequest 76 wantErr assert.ErrorAssertionFunc 77 }{ 78 { 79 name: "empty input", 80 allTasks: []Task{}, 81 basis: []string{}, 82 expressions: []string{}, 83 wantNames: []string{}, 84 wantTokens: map[string]TokenSelection{}, 85 wantRequest: pkgcataloging.SelectionRequest{}, 86 }, 87 { 88 name: "use default tasks", 89 allTasks: createDummyTasks(), 90 basis: []string{ 91 "image", 92 }, 93 expressions: []string{}, 94 wantNames: []string{ 95 "alpm-db-cataloger", 96 "apk-db-cataloger", 97 "dpkg-db-cataloger", 98 "portage-cataloger", 99 "rpm-db-cataloger", 100 "conan-info-cataloger", 101 "javascript-package-cataloger", 102 "php-composer-installed-cataloger", 103 "ruby-installed-gemspec-cataloger", 104 "rust-cargo-lock-cataloger", 105 "dotnet-portable-executable-cataloger", 106 "python-installed-package-cataloger", 107 "go-module-binary-cataloger", 108 "java-archive-cataloger", 109 "graalvm-native-image-cataloger", 110 "binary-cataloger", 111 "sbom-cataloger", 112 }, 113 wantTokens: map[string]TokenSelection{ 114 "alpm-db-cataloger": newTokenSelection([]string{"image"}, nil), 115 "apk-db-cataloger": newTokenSelection([]string{"image"}, nil), 116 "dpkg-db-cataloger": newTokenSelection([]string{"image"}, nil), 117 "portage-cataloger": newTokenSelection([]string{"image"}, nil), 118 "rpm-db-cataloger": newTokenSelection([]string{"image"}, nil), 119 "conan-info-cataloger": newTokenSelection([]string{"image"}, nil), 120 "javascript-package-cataloger": newTokenSelection([]string{"image"}, nil), 121 "php-composer-installed-cataloger": newTokenSelection([]string{"image"}, nil), 122 "ruby-installed-gemspec-cataloger": newTokenSelection([]string{"image"}, nil), 123 "rust-cargo-lock-cataloger": newTokenSelection([]string{"image"}, nil), 124 "dotnet-portable-executable-cataloger": newTokenSelection([]string{"image"}, nil), 125 "python-installed-package-cataloger": newTokenSelection([]string{"image"}, nil), 126 "go-module-binary-cataloger": newTokenSelection([]string{"image"}, nil), 127 "java-archive-cataloger": newTokenSelection([]string{"image"}, nil), 128 "graalvm-native-image-cataloger": newTokenSelection([]string{"image"}, nil), 129 "binary-cataloger": newTokenSelection([]string{"image"}, nil), 130 "sbom-cataloger": newTokenSelection([]string{"image"}, nil), 131 }, 132 wantRequest: pkgcataloging.SelectionRequest{ 133 DefaultNamesOrTags: []string{"image"}, 134 }, 135 }, 136 { 137 name: "select, add, and remove tasks", 138 allTasks: createDummyTasks(), 139 basis: []string{ 140 "image", 141 }, 142 expressions: []string{ 143 "+github-actions-usage-cataloger", 144 "-dpkg", 145 "os", 146 }, 147 wantNames: []string{ 148 "alpm-db-cataloger", 149 "apk-db-cataloger", 150 "portage-cataloger", 151 "rpm-db-cataloger", 152 "github-actions-usage-cataloger", 153 }, 154 wantTokens: map[string]TokenSelection{ 155 // selected 156 "alpm-db-cataloger": newTokenSelection([]string{"image", "os"}, nil), 157 "apk-db-cataloger": newTokenSelection([]string{"image", "os"}, nil), 158 "dpkg-db-cataloger": newTokenSelection([]string{"image", "os"}, []string{"dpkg"}), 159 "portage-cataloger": newTokenSelection([]string{"image", "os"}, nil), 160 "rpm-db-cataloger": newTokenSelection([]string{"image", "os"}, nil), 161 "github-actions-usage-cataloger": newTokenSelection([]string{"github-actions-usage-cataloger"}, nil), 162 163 // ultimately not selected 164 "rpm-archive-cataloger": newTokenSelection([]string{"os"}, nil), 165 "conan-info-cataloger": newTokenSelection([]string{"image"}, nil), 166 "javascript-package-cataloger": newTokenSelection([]string{"image"}, nil), 167 "php-composer-installed-cataloger": newTokenSelection([]string{"image"}, nil), 168 "ruby-installed-gemspec-cataloger": newTokenSelection([]string{"image"}, nil), 169 "rust-cargo-lock-cataloger": newTokenSelection([]string{"image"}, nil), 170 "dotnet-portable-executable-cataloger": newTokenSelection([]string{"image"}, nil), 171 "python-installed-package-cataloger": newTokenSelection([]string{"image"}, nil), 172 "go-module-binary-cataloger": newTokenSelection([]string{"image"}, nil), 173 "java-archive-cataloger": newTokenSelection([]string{"image"}, nil), 174 "graalvm-native-image-cataloger": newTokenSelection([]string{"image"}, nil), 175 "binary-cataloger": newTokenSelection([]string{"image"}, nil), 176 "sbom-cataloger": newTokenSelection([]string{"image"}, nil), 177 }, 178 wantRequest: pkgcataloging.SelectionRequest{ 179 DefaultNamesOrTags: []string{"image"}, 180 SubSelectTags: []string{"os"}, 181 RemoveNamesOrTags: []string{"dpkg"}, 182 AddNames: []string{"github-actions-usage-cataloger"}, 183 }, 184 }, 185 { 186 name: "allow for partial selections", 187 allTasks: createDummyTasks(), 188 basis: []string{ 189 "image", 190 }, 191 expressions: []string{ 192 // valid... 193 "+github-actions-usage-cataloger", 194 "-dpkg", 195 "os", 196 // invalid... 197 "+python", 198 "rust-cargo-lock-cataloger", 199 }, 200 wantNames: []string{ 201 "alpm-db-cataloger", 202 "apk-db-cataloger", 203 "portage-cataloger", 204 "rpm-db-cataloger", 205 "github-actions-usage-cataloger", 206 }, 207 wantTokens: map[string]TokenSelection{ 208 // selected 209 "alpm-db-cataloger": newTokenSelection([]string{"image", "os"}, nil), 210 "apk-db-cataloger": newTokenSelection([]string{"image", "os"}, nil), 211 "dpkg-db-cataloger": newTokenSelection([]string{"image", "os"}, []string{"dpkg"}), 212 "portage-cataloger": newTokenSelection([]string{"image", "os"}, nil), 213 "rpm-db-cataloger": newTokenSelection([]string{"image", "os"}, nil), 214 "github-actions-usage-cataloger": newTokenSelection([]string{"github-actions-usage-cataloger"}, nil), 215 216 // ultimately not selected 217 "rpm-archive-cataloger": newTokenSelection([]string{"os"}, nil), 218 "conan-info-cataloger": newTokenSelection([]string{"image"}, nil), 219 "javascript-package-cataloger": newTokenSelection([]string{"image"}, nil), 220 "php-composer-installed-cataloger": newTokenSelection([]string{"image"}, nil), 221 "ruby-installed-gemspec-cataloger": newTokenSelection([]string{"image"}, nil), 222 "rust-cargo-lock-cataloger": newTokenSelection([]string{"image"}, nil), 223 "dotnet-portable-executable-cataloger": newTokenSelection([]string{"image"}, nil), 224 "python-installed-package-cataloger": newTokenSelection([]string{"image"}, nil), // note: there is no python token used for selection 225 "go-module-binary-cataloger": newTokenSelection([]string{"image"}, nil), 226 "java-archive-cataloger": newTokenSelection([]string{"image"}, nil), 227 "graalvm-native-image-cataloger": newTokenSelection([]string{"image"}, nil), 228 "binary-cataloger": newTokenSelection([]string{"image"}, nil), 229 "sbom-cataloger": newTokenSelection([]string{"image"}, nil), 230 }, 231 wantRequest: pkgcataloging.SelectionRequest{ 232 DefaultNamesOrTags: []string{"image"}, 233 SubSelectTags: []string{"os", "rust-cargo-lock-cataloger"}, 234 RemoveNamesOrTags: []string{"dpkg"}, 235 AddNames: []string{"github-actions-usage-cataloger", "python"}, 236 }, 237 wantErr: assert.Error, // !important! 238 }, 239 { 240 name: "select all tasks", 241 allTasks: createDummyTasks(), 242 basis: []string{ 243 "all", 244 }, 245 expressions: []string{}, 246 wantNames: []string{ 247 "alpm-db-cataloger", 248 "apk-db-cataloger", 249 "dpkg-db-cataloger", 250 "portage-cataloger", 251 "rpm-db-cataloger", 252 "rpm-archive-cataloger", 253 "conan-info-cataloger", 254 "javascript-package-cataloger", 255 "php-composer-installed-cataloger", 256 "ruby-installed-gemspec-cataloger", 257 "rust-cargo-lock-cataloger", 258 "conan-cataloger", 259 "dart-pubspec-lock-cataloger", 260 "dotnet-deps-cataloger", 261 "elixir-mix-lock-cataloger", 262 "erlang-rebar-lock-cataloger", 263 "javascript-lock-cataloger", 264 "dotnet-portable-executable-cataloger", 265 "python-installed-package-cataloger", 266 "go-module-binary-cataloger", 267 "java-archive-cataloger", 268 "graalvm-native-image-cataloger", 269 "binary-cataloger", 270 "github-actions-usage-cataloger", 271 "github-action-workflow-usage-cataloger", 272 "sbom-cataloger", 273 }, 274 wantTokens: map[string]TokenSelection{ 275 "alpm-db-cataloger": newTokenSelection([]string{"all"}, nil), 276 "apk-db-cataloger": newTokenSelection([]string{"all"}, nil), 277 "dpkg-db-cataloger": newTokenSelection([]string{"all"}, nil), 278 "portage-cataloger": newTokenSelection([]string{"all"}, nil), 279 "rpm-db-cataloger": newTokenSelection([]string{"all"}, nil), 280 "rpm-archive-cataloger": newTokenSelection([]string{"all"}, nil), 281 "conan-info-cataloger": newTokenSelection([]string{"all"}, nil), 282 "javascript-package-cataloger": newTokenSelection([]string{"all"}, nil), 283 "php-composer-installed-cataloger": newTokenSelection([]string{"all"}, nil), 284 "ruby-installed-gemspec-cataloger": newTokenSelection([]string{"all"}, nil), 285 "rust-cargo-lock-cataloger": newTokenSelection([]string{"all"}, nil), 286 "conan-cataloger": newTokenSelection([]string{"all"}, nil), 287 "dart-pubspec-lock-cataloger": newTokenSelection([]string{"all"}, nil), 288 "dotnet-deps-cataloger": newTokenSelection([]string{"all"}, nil), 289 "elixir-mix-lock-cataloger": newTokenSelection([]string{"all"}, nil), 290 "erlang-rebar-lock-cataloger": newTokenSelection([]string{"all"}, nil), 291 "javascript-lock-cataloger": newTokenSelection([]string{"all"}, nil), 292 "dotnet-portable-executable-cataloger": newTokenSelection([]string{"all"}, nil), 293 "python-installed-package-cataloger": newTokenSelection([]string{"all"}, nil), 294 "go-module-binary-cataloger": newTokenSelection([]string{"all"}, nil), 295 "java-archive-cataloger": newTokenSelection([]string{"all"}, nil), 296 "graalvm-native-image-cataloger": newTokenSelection([]string{"all"}, nil), 297 "binary-cataloger": newTokenSelection([]string{"all"}, nil), 298 "github-actions-usage-cataloger": newTokenSelection([]string{"all"}, nil), 299 "github-action-workflow-usage-cataloger": newTokenSelection([]string{"all"}, nil), 300 "sbom-cataloger": newTokenSelection([]string{"all"}, nil), 301 }, 302 wantRequest: pkgcataloging.SelectionRequest{ 303 DefaultNamesOrTags: []string{"all"}, 304 }, 305 }, 306 { 307 name: "set default with multiple tags", 308 allTasks: createDummyTasks(), 309 basis: []string{ 310 "gemspec", 311 "python", 312 }, 313 expressions: []string{}, 314 wantNames: []string{ 315 "ruby-installed-gemspec-cataloger", 316 "python-installed-package-cataloger", 317 }, 318 wantTokens: map[string]TokenSelection{ 319 "ruby-installed-gemspec-cataloger": newTokenSelection([]string{"gemspec"}, nil), 320 "python-installed-package-cataloger": newTokenSelection([]string{"python"}, nil), 321 }, 322 wantRequest: pkgcataloging.SelectionRequest{ 323 DefaultNamesOrTags: []string{"gemspec", "python"}, 324 }, 325 }, 326 } 327 for _, tt := range tests { 328 t.Run(tt.name, func(t *testing.T) { 329 if tt.wantErr == nil { 330 tt.wantErr = assert.NoError 331 } 332 333 req := pkgcataloging.NewSelectionRequest().WithDefaults(tt.basis...).WithExpression(tt.expressions...) 334 335 got, gotEvidence, err := Select(tt.allTasks, req) 336 tt.wantErr(t, err) 337 if err != nil { 338 // dev note: this is useful for debugging when needed... 339 //for _, e := range gotEvidence.Request.Expressions { 340 // t.Logf("expression (errors %q): %#v", e.Errors, e) 341 //} 342 343 // note: we DON'T bail early in validations... this is because we should always return the full set of 344 // of selected tasks and surrounding evidence. 345 } 346 347 gotNames := make([]string, 0) 348 for _, g := range got { 349 gotNames = append(gotNames, g.Name()) 350 } 351 352 assert.Equal(t, tt.wantNames, gotNames) 353 354 // names in selection should match all tasks returned 355 require.Len(t, tt.wantNames, gotEvidence.Result.Size(), "selected tasks should match all tasks returned (but does not)") 356 assert.ElementsMatch(t, tt.wantNames, gotEvidence.Result.List(), "selected tasks should match all tasks returned (but does not)") 357 358 setCompare := cmp.Comparer(func(x, y *strset.Set) bool { 359 return x.IsEqual(y) 360 }) 361 362 if d := cmp.Diff(tt.wantTokens, gotEvidence.TokensByTask, setCompare); d != "" { 363 t.Errorf("unexpected tokens by task (-want +got):\n%s", d) 364 } 365 assert.Equal(t, tt.wantRequest, gotEvidence.Request) 366 367 }) 368 } 369 }