github.com/anchore/syft@v1.38.2/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" 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 createDummyPackageTasks() tasks { 25 return []Task{ 26 // OS package installed catalogers 27 dummyTask("alpm-db-cataloger", "package", "directory", "installed", "image", "os", "alpm", "archlinux"), 28 dummyTask("apk-db-cataloger", "package", "directory", "installed", "image", "os", "apk", "alpine"), 29 dummyTask("dpkg-db-cataloger", "package", "directory", "installed", "image", "os", "dpkg", "debian"), 30 dummyTask("portage-cataloger", "package", "directory", "installed", "image", "os", "portage", "gentoo"), 31 dummyTask("rpm-db-cataloger", "package", "directory", "installed", "image", "os", "rpm", "redhat"), 32 33 // OS package declared catalogers 34 dummyTask("rpm-archive-cataloger", "package", "declared", "directory", "os", "rpm", "redhat"), 35 36 // language-specific package installed catalogers 37 dummyTask("conan-info-cataloger", "package", "installed", "image", "language", "cpp", "conan"), 38 dummyTask("javascript-package-cataloger", "package", "installed", "image", "language", "javascript", "node"), 39 dummyTask("php-composer-installed-cataloger", "package", "installed", "image", "language", "php", "composer"), 40 dummyTask("ruby-installed-gemspec-cataloger", "package", "installed", "image", "language", "ruby", "gem", "gemspec"), 41 dummyTask("rust-cargo-lock-cataloger", "package", "installed", "image", "language", "rust", "binary"), 42 43 // language-specific package declared catalogers 44 dummyTask("conan-cataloger", "package", "declared", "directory", "language", "cpp", "conan"), 45 dummyTask("dart-pubspec-lock-cataloger", "package", "declared", "directory", "language", "dart"), 46 dummyTask("dotnet-deps-cataloger", "package", "declared", "directory", "language", "dotnet", "c#"), 47 dummyTask("elixir-mix-lock-cataloger", "package", "declared", "directory", "language", "elixir"), 48 dummyTask("erlang-rebar-lock-cataloger", "package", "declared", "directory", "language", "erlang"), 49 dummyTask("javascript-lock-cataloger", "package", "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", "package", "directory", "installed", "image", "language", "dotnet", "c#"), 53 dummyTask("python-installed-package-cataloger", "package", "directory", "installed", "image", "language", "python"), 54 dummyTask("go-module-binary-cataloger", "package", "directory", "installed", "image", "language", "go", "golang", "gomod", "binary"), 55 dummyTask("java-archive-cataloger", "package", "directory", "installed", "image", "language", "java", "maven"), 56 dummyTask("graalvm-native-image-cataloger", "package", "directory", "installed", "image", "language", "java"), 57 58 // other package catalogers 59 dummyTask("binary-cataloger", "package", "declared", "directory", "image", "binary"), 60 dummyTask("github-actions-usage-cataloger", "package", "declared", "directory", "github", "github-actions"), 61 dummyTask("github-action-workflow-usage-cataloger", "package", "declared", "directory", "github", "github-actions"), 62 dummyTask("sbom-cataloger", "package", "declared", "directory", "image", "sbom"), 63 } 64 } 65 66 func createDummyFileTasks() tasks { 67 return []Task{ 68 dummyTask("file-content-cataloger", "file", "content"), 69 dummyTask("file-metadata-cataloger", "file", "metadata"), 70 dummyTask("file-digest-cataloger", "file", "digest"), 71 dummyTask("file-executable-cataloger", "file", "binary-metadata"), 72 } 73 } 74 75 func TestSelect(t *testing.T) { 76 77 tests := []struct { 78 name string 79 allTasks []Task 80 basis []string 81 expressions []string 82 wantNames []string 83 wantTokens map[string]TokenSelection 84 wantRequest cataloging.SelectionRequest 85 wantErr assert.ErrorAssertionFunc 86 }{ 87 { 88 name: "empty input", 89 allTasks: []Task{}, 90 basis: []string{}, 91 expressions: []string{}, 92 wantNames: []string{}, 93 wantTokens: map[string]TokenSelection{}, 94 wantRequest: cataloging.SelectionRequest{}, 95 }, 96 { 97 name: "use default tasks", 98 allTasks: createDummyPackageTasks(), 99 basis: []string{ 100 "image", 101 }, 102 expressions: []string{}, 103 wantNames: []string{ 104 "alpm-db-cataloger", 105 "apk-db-cataloger", 106 "dpkg-db-cataloger", 107 "portage-cataloger", 108 "rpm-db-cataloger", 109 "conan-info-cataloger", 110 "javascript-package-cataloger", 111 "php-composer-installed-cataloger", 112 "ruby-installed-gemspec-cataloger", 113 "rust-cargo-lock-cataloger", 114 "dotnet-portable-executable-cataloger", 115 "python-installed-package-cataloger", 116 "go-module-binary-cataloger", 117 "java-archive-cataloger", 118 "graalvm-native-image-cataloger", 119 "binary-cataloger", 120 "sbom-cataloger", 121 }, 122 wantTokens: map[string]TokenSelection{ 123 "alpm-db-cataloger": newTokenSelection([]string{"image"}, nil), 124 "apk-db-cataloger": newTokenSelection([]string{"image"}, nil), 125 "dpkg-db-cataloger": newTokenSelection([]string{"image"}, nil), 126 "portage-cataloger": newTokenSelection([]string{"image"}, nil), 127 "rpm-db-cataloger": newTokenSelection([]string{"image"}, nil), 128 "conan-info-cataloger": newTokenSelection([]string{"image"}, nil), 129 "javascript-package-cataloger": newTokenSelection([]string{"image"}, nil), 130 "php-composer-installed-cataloger": newTokenSelection([]string{"image"}, nil), 131 "ruby-installed-gemspec-cataloger": newTokenSelection([]string{"image"}, nil), 132 "rust-cargo-lock-cataloger": newTokenSelection([]string{"image"}, nil), 133 "dotnet-portable-executable-cataloger": newTokenSelection([]string{"image"}, nil), 134 "python-installed-package-cataloger": newTokenSelection([]string{"image"}, nil), 135 "go-module-binary-cataloger": newTokenSelection([]string{"image"}, nil), 136 "java-archive-cataloger": newTokenSelection([]string{"image"}, nil), 137 "graalvm-native-image-cataloger": newTokenSelection([]string{"image"}, nil), 138 "binary-cataloger": newTokenSelection([]string{"image"}, nil), 139 "sbom-cataloger": newTokenSelection([]string{"image"}, nil), 140 }, 141 wantRequest: cataloging.SelectionRequest{ 142 DefaultNamesOrTags: []string{"image"}, 143 }, 144 }, 145 { 146 name: "select, add, and remove tasks", 147 allTasks: createDummyPackageTasks(), 148 basis: []string{ 149 "image", 150 }, 151 expressions: []string{ 152 "+github-actions-usage-cataloger", 153 "-dpkg", 154 "os", 155 }, 156 wantNames: []string{ 157 "alpm-db-cataloger", 158 "apk-db-cataloger", 159 "portage-cataloger", 160 "rpm-db-cataloger", 161 "github-actions-usage-cataloger", 162 }, 163 wantTokens: map[string]TokenSelection{ 164 // selected 165 "alpm-db-cataloger": newTokenSelection([]string{"image", "os"}, nil), 166 "apk-db-cataloger": newTokenSelection([]string{"image", "os"}, nil), 167 "dpkg-db-cataloger": newTokenSelection([]string{"image", "os"}, []string{"dpkg"}), 168 "portage-cataloger": newTokenSelection([]string{"image", "os"}, nil), 169 "rpm-db-cataloger": newTokenSelection([]string{"image", "os"}, nil), 170 "github-actions-usage-cataloger": newTokenSelection([]string{"github-actions-usage-cataloger"}, nil), 171 172 // ultimately not selected 173 "rpm-archive-cataloger": newTokenSelection([]string{"os"}, nil), 174 "conan-info-cataloger": newTokenSelection([]string{"image"}, nil), 175 "javascript-package-cataloger": newTokenSelection([]string{"image"}, nil), 176 "php-composer-installed-cataloger": newTokenSelection([]string{"image"}, nil), 177 "ruby-installed-gemspec-cataloger": newTokenSelection([]string{"image"}, nil), 178 "rust-cargo-lock-cataloger": newTokenSelection([]string{"image"}, nil), 179 "dotnet-portable-executable-cataloger": newTokenSelection([]string{"image"}, nil), 180 "python-installed-package-cataloger": newTokenSelection([]string{"image"}, nil), 181 "go-module-binary-cataloger": newTokenSelection([]string{"image"}, nil), 182 "java-archive-cataloger": newTokenSelection([]string{"image"}, nil), 183 "graalvm-native-image-cataloger": newTokenSelection([]string{"image"}, nil), 184 "binary-cataloger": newTokenSelection([]string{"image"}, nil), 185 "sbom-cataloger": newTokenSelection([]string{"image"}, nil), 186 }, 187 wantRequest: cataloging.SelectionRequest{ 188 DefaultNamesOrTags: []string{"image"}, 189 SubSelectTags: []string{"os"}, 190 RemoveNamesOrTags: []string{"dpkg"}, 191 AddNames: []string{"github-actions-usage-cataloger"}, 192 }, 193 }, 194 { 195 name: "allow for partial selections", 196 allTasks: createDummyPackageTasks(), 197 basis: []string{ 198 "image", 199 }, 200 expressions: []string{ 201 // valid... 202 "+github-actions-usage-cataloger", 203 "-dpkg", 204 "os", 205 // invalid... 206 "+python", 207 "rust-cargo-lock-cataloger", 208 }, 209 wantNames: []string{ 210 "alpm-db-cataloger", 211 "apk-db-cataloger", 212 "portage-cataloger", 213 "rpm-db-cataloger", 214 "github-actions-usage-cataloger", 215 }, 216 wantTokens: map[string]TokenSelection{ 217 // selected 218 "alpm-db-cataloger": newTokenSelection([]string{"image", "os"}, nil), 219 "apk-db-cataloger": newTokenSelection([]string{"image", "os"}, nil), 220 "dpkg-db-cataloger": newTokenSelection([]string{"image", "os"}, []string{"dpkg"}), 221 "portage-cataloger": newTokenSelection([]string{"image", "os"}, nil), 222 "rpm-db-cataloger": newTokenSelection([]string{"image", "os"}, nil), 223 "github-actions-usage-cataloger": newTokenSelection([]string{"github-actions-usage-cataloger"}, nil), 224 225 // ultimately not selected 226 "rpm-archive-cataloger": newTokenSelection([]string{"os"}, nil), 227 "conan-info-cataloger": newTokenSelection([]string{"image"}, nil), 228 "javascript-package-cataloger": newTokenSelection([]string{"image"}, nil), 229 "php-composer-installed-cataloger": newTokenSelection([]string{"image"}, nil), 230 "ruby-installed-gemspec-cataloger": newTokenSelection([]string{"image"}, nil), 231 "rust-cargo-lock-cataloger": newTokenSelection([]string{"image"}, nil), 232 "dotnet-portable-executable-cataloger": newTokenSelection([]string{"image"}, nil), 233 "python-installed-package-cataloger": newTokenSelection([]string{"image"}, nil), // note: there is no python token used for selection 234 "go-module-binary-cataloger": newTokenSelection([]string{"image"}, nil), 235 "java-archive-cataloger": newTokenSelection([]string{"image"}, nil), 236 "graalvm-native-image-cataloger": newTokenSelection([]string{"image"}, nil), 237 "binary-cataloger": newTokenSelection([]string{"image"}, nil), 238 "sbom-cataloger": newTokenSelection([]string{"image"}, nil), 239 }, 240 wantRequest: cataloging.SelectionRequest{ 241 DefaultNamesOrTags: []string{"image"}, 242 SubSelectTags: []string{"os", "rust-cargo-lock-cataloger"}, 243 RemoveNamesOrTags: []string{"dpkg"}, 244 AddNames: []string{"github-actions-usage-cataloger", "python"}, 245 }, 246 wantErr: assert.Error, // !important! 247 }, 248 { 249 name: "select all tasks", 250 allTasks: createDummyPackageTasks(), 251 basis: []string{ 252 "all", 253 }, 254 expressions: []string{}, 255 wantNames: []string{ 256 "alpm-db-cataloger", 257 "apk-db-cataloger", 258 "dpkg-db-cataloger", 259 "portage-cataloger", 260 "rpm-db-cataloger", 261 "rpm-archive-cataloger", 262 "conan-info-cataloger", 263 "javascript-package-cataloger", 264 "php-composer-installed-cataloger", 265 "ruby-installed-gemspec-cataloger", 266 "rust-cargo-lock-cataloger", 267 "conan-cataloger", 268 "dart-pubspec-lock-cataloger", 269 "dotnet-deps-cataloger", 270 "elixir-mix-lock-cataloger", 271 "erlang-rebar-lock-cataloger", 272 "javascript-lock-cataloger", 273 "dotnet-portable-executable-cataloger", 274 "python-installed-package-cataloger", 275 "go-module-binary-cataloger", 276 "java-archive-cataloger", 277 "graalvm-native-image-cataloger", 278 "binary-cataloger", 279 "github-actions-usage-cataloger", 280 "github-action-workflow-usage-cataloger", 281 "sbom-cataloger", 282 }, 283 wantTokens: map[string]TokenSelection{ 284 "alpm-db-cataloger": newTokenSelection([]string{"all"}, nil), 285 "apk-db-cataloger": newTokenSelection([]string{"all"}, nil), 286 "dpkg-db-cataloger": newTokenSelection([]string{"all"}, nil), 287 "portage-cataloger": newTokenSelection([]string{"all"}, nil), 288 "rpm-db-cataloger": newTokenSelection([]string{"all"}, nil), 289 "rpm-archive-cataloger": newTokenSelection([]string{"all"}, nil), 290 "conan-info-cataloger": newTokenSelection([]string{"all"}, nil), 291 "javascript-package-cataloger": newTokenSelection([]string{"all"}, nil), 292 "php-composer-installed-cataloger": newTokenSelection([]string{"all"}, nil), 293 "ruby-installed-gemspec-cataloger": newTokenSelection([]string{"all"}, nil), 294 "rust-cargo-lock-cataloger": newTokenSelection([]string{"all"}, nil), 295 "conan-cataloger": newTokenSelection([]string{"all"}, nil), 296 "dart-pubspec-lock-cataloger": newTokenSelection([]string{"all"}, nil), 297 "dotnet-deps-cataloger": newTokenSelection([]string{"all"}, nil), 298 "elixir-mix-lock-cataloger": newTokenSelection([]string{"all"}, nil), 299 "erlang-rebar-lock-cataloger": newTokenSelection([]string{"all"}, nil), 300 "javascript-lock-cataloger": newTokenSelection([]string{"all"}, nil), 301 "dotnet-portable-executable-cataloger": newTokenSelection([]string{"all"}, nil), 302 "python-installed-package-cataloger": newTokenSelection([]string{"all"}, nil), 303 "go-module-binary-cataloger": newTokenSelection([]string{"all"}, nil), 304 "java-archive-cataloger": newTokenSelection([]string{"all"}, nil), 305 "graalvm-native-image-cataloger": newTokenSelection([]string{"all"}, nil), 306 "binary-cataloger": newTokenSelection([]string{"all"}, nil), 307 "github-actions-usage-cataloger": newTokenSelection([]string{"all"}, nil), 308 "github-action-workflow-usage-cataloger": newTokenSelection([]string{"all"}, nil), 309 "sbom-cataloger": newTokenSelection([]string{"all"}, nil), 310 }, 311 wantRequest: cataloging.SelectionRequest{ 312 DefaultNamesOrTags: []string{"all"}, 313 }, 314 }, 315 { 316 name: "set default with multiple tags", 317 allTasks: createDummyPackageTasks(), 318 basis: []string{ 319 "gemspec", 320 "python", 321 }, 322 expressions: []string{}, 323 wantNames: []string{ 324 "ruby-installed-gemspec-cataloger", 325 "python-installed-package-cataloger", 326 }, 327 wantTokens: map[string]TokenSelection{ 328 "ruby-installed-gemspec-cataloger": newTokenSelection([]string{"gemspec"}, nil), 329 "python-installed-package-cataloger": newTokenSelection([]string{"python"}, nil), 330 }, 331 wantRequest: cataloging.SelectionRequest{ 332 DefaultNamesOrTags: []string{"gemspec", "python"}, 333 }, 334 }, 335 { 336 name: "automatically add file to default tags", 337 allTasks: createDummyFileTasks(), 338 basis: []string{}, 339 expressions: []string{}, 340 wantNames: []string{ 341 "file-content-cataloger", 342 "file-metadata-cataloger", 343 "file-digest-cataloger", 344 "file-executable-cataloger", 345 }, 346 wantTokens: map[string]TokenSelection{ 347 "file-content-cataloger": newTokenSelection([]string{"file"}, nil), 348 "file-metadata-cataloger": newTokenSelection([]string{"file"}, nil), 349 "file-digest-cataloger": newTokenSelection([]string{"file"}, nil), 350 "file-executable-cataloger": newTokenSelection([]string{"file"}, nil), 351 }, 352 wantRequest: cataloging.SelectionRequest{ 353 DefaultNamesOrTags: []string{"file"}, 354 }, 355 }, 356 } 357 for _, tt := range tests { 358 t.Run(tt.name, func(t *testing.T) { 359 if tt.wantErr == nil { 360 tt.wantErr = assert.NoError 361 } 362 363 req := cataloging.NewSelectionRequest().WithDefaults(tt.basis...).WithExpression(tt.expressions...) 364 365 got, gotEvidence, err := Select(tt.allTasks, req) 366 tt.wantErr(t, err) 367 if err != nil { 368 // dev note: this is useful for debugging when needed... 369 //for _, e := range gotEvidence.Request.Expressions { 370 // t.Logf("expression (errors %q): %#v", e.Errors, e) 371 //} 372 373 // note: we DON'T bail early in validations... this is because we should always return the full set of 374 // of selected tasks and surrounding evidence. 375 } 376 377 gotNames := make([]string, 0) 378 for _, g := range got { 379 gotNames = append(gotNames, g.Name()) 380 } 381 382 assert.Equal(t, tt.wantNames, gotNames) 383 384 // names in selection should match all tasks returned 385 require.Len(t, tt.wantNames, gotEvidence.Result.Size(), "selected tasks should match all tasks returned (but does not)") 386 assert.ElementsMatch(t, tt.wantNames, gotEvidence.Result.List(), "selected tasks should match all tasks returned (but does not)") 387 388 setCompare := cmp.Comparer(func(x, y *strset.Set) bool { 389 return x.IsEqual(y) 390 }) 391 392 if d := cmp.Diff(tt.wantTokens, gotEvidence.TokensByTask, setCompare); d != "" { 393 t.Errorf("unexpected tokens by task (-want +got):\n%s", d) 394 } 395 assert.Equal(t, tt.wantRequest, gotEvidence.Request) 396 397 }) 398 } 399 } 400 401 func TestSelectInGroups(t *testing.T) { 402 tests := []struct { 403 name string 404 taskGroups [][]Task 405 selectionReq cataloging.SelectionRequest 406 wantGroups [][]string 407 wantTokens map[string]TokenSelection 408 wantRequest cataloging.SelectionRequest 409 wantErr assert.ErrorAssertionFunc 410 }{ 411 { 412 name: "select only within the file tasks (leave package tasks alone)", 413 taskGroups: [][]Task{ 414 createDummyPackageTasks(), 415 createDummyFileTasks(), 416 }, 417 selectionReq: cataloging.NewSelectionRequest(). 418 WithDefaults("image"). // note: file missing 419 WithSubSelections("content", "digest"), 420 wantGroups: [][]string{ 421 { 422 // this is the original, untouched package task list 423 "alpm-db-cataloger", 424 "apk-db-cataloger", 425 "dpkg-db-cataloger", 426 "portage-cataloger", 427 "rpm-db-cataloger", 428 "conan-info-cataloger", 429 "javascript-package-cataloger", 430 "php-composer-installed-cataloger", 431 "ruby-installed-gemspec-cataloger", 432 "rust-cargo-lock-cataloger", 433 "dotnet-portable-executable-cataloger", 434 "python-installed-package-cataloger", 435 "go-module-binary-cataloger", 436 "java-archive-cataloger", 437 "graalvm-native-image-cataloger", 438 "binary-cataloger", 439 "sbom-cataloger", 440 }, 441 { 442 // this has been filtered based on the request 443 "file-content-cataloger", 444 "file-digest-cataloger", 445 }, 446 }, 447 wantTokens: map[string]TokenSelection{ 448 // packages 449 "alpm-db-cataloger": newTokenSelection([]string{"image"}, nil), 450 "apk-db-cataloger": newTokenSelection([]string{"image"}, nil), 451 "binary-cataloger": newTokenSelection([]string{"image"}, nil), 452 "conan-info-cataloger": newTokenSelection([]string{"image"}, nil), 453 "dotnet-portable-executable-cataloger": newTokenSelection([]string{"image"}, nil), 454 "dpkg-db-cataloger": newTokenSelection([]string{"image"}, nil), 455 "go-module-binary-cataloger": newTokenSelection([]string{"image"}, nil), 456 "graalvm-native-image-cataloger": newTokenSelection([]string{"image"}, nil), 457 "java-archive-cataloger": newTokenSelection([]string{"image"}, nil), 458 "javascript-package-cataloger": newTokenSelection([]string{"image"}, nil), 459 "php-composer-installed-cataloger": newTokenSelection([]string{"image"}, nil), 460 "portage-cataloger": newTokenSelection([]string{"image"}, nil), 461 "python-installed-package-cataloger": newTokenSelection([]string{"image"}, nil), 462 "rpm-db-cataloger": newTokenSelection([]string{"image"}, nil), 463 "ruby-installed-gemspec-cataloger": newTokenSelection([]string{"image"}, nil), 464 "rust-cargo-lock-cataloger": newTokenSelection([]string{"image"}, nil), 465 "sbom-cataloger": newTokenSelection([]string{"image"}, nil), 466 // files 467 "file-content-cataloger": newTokenSelection([]string{"content", "file"}, nil), 468 "file-digest-cataloger": newTokenSelection([]string{"digest", "file"}, nil), 469 "file-executable-cataloger": newTokenSelection([]string{"file"}, nil), 470 "file-metadata-cataloger": newTokenSelection([]string{"file"}, nil), 471 }, 472 wantRequest: cataloging.SelectionRequest{ 473 DefaultNamesOrTags: []string{"image", "file"}, // note: file automatically added 474 SubSelectTags: []string{"content", "digest"}, 475 }, 476 wantErr: assert.NoError, 477 }, 478 { 479 name: "select package tasks (leave file tasks alone)", 480 taskGroups: [][]Task{ 481 createDummyPackageTasks(), 482 createDummyFileTasks(), 483 }, 484 selectionReq: cataloging.NewSelectionRequest().WithDefaults("image").WithSubSelections("os"), 485 wantGroups: [][]string{ 486 { 487 // filtered based on the request 488 "alpm-db-cataloger", 489 "apk-db-cataloger", 490 "dpkg-db-cataloger", 491 "portage-cataloger", 492 "rpm-db-cataloger", 493 }, 494 { 495 // this is the original, untouched file task list 496 "file-content-cataloger", 497 "file-metadata-cataloger", 498 "file-digest-cataloger", 499 "file-executable-cataloger", 500 }, 501 }, 502 wantTokens: map[string]TokenSelection{ 503 // packages - os 504 "alpm-db-cataloger": newTokenSelection([]string{"os", "image"}, nil), 505 "apk-db-cataloger": newTokenSelection([]string{"os", "image"}, nil), 506 "rpm-archive-cataloger": newTokenSelection([]string{"os"}, nil), 507 "rpm-db-cataloger": newTokenSelection([]string{"os", "image"}, nil), 508 "portage-cataloger": newTokenSelection([]string{"os", "image"}, nil), 509 "dpkg-db-cataloger": newTokenSelection([]string{"os", "image"}, nil), 510 // packages - remaining 511 "binary-cataloger": newTokenSelection([]string{"image"}, nil), 512 "conan-info-cataloger": newTokenSelection([]string{"image"}, nil), 513 "dotnet-portable-executable-cataloger": newTokenSelection([]string{"image"}, nil), 514 "go-module-binary-cataloger": newTokenSelection([]string{"image"}, nil), 515 "graalvm-native-image-cataloger": newTokenSelection([]string{"image"}, nil), 516 "java-archive-cataloger": newTokenSelection([]string{"image"}, nil), 517 "javascript-package-cataloger": newTokenSelection([]string{"image"}, nil), 518 "php-composer-installed-cataloger": newTokenSelection([]string{"image"}, nil), 519 "python-installed-package-cataloger": newTokenSelection([]string{"image"}, nil), 520 "ruby-installed-gemspec-cataloger": newTokenSelection([]string{"image"}, nil), 521 "rust-cargo-lock-cataloger": newTokenSelection([]string{"image"}, nil), 522 "sbom-cataloger": newTokenSelection([]string{"image"}, nil), 523 // files 524 "file-content-cataloger": newTokenSelection([]string{"file"}, nil), 525 "file-digest-cataloger": newTokenSelection([]string{"file"}, nil), 526 "file-executable-cataloger": newTokenSelection([]string{"file"}, nil), 527 "file-metadata-cataloger": newTokenSelection([]string{"file"}, nil), 528 }, 529 wantRequest: cataloging.SelectionRequest{ 530 DefaultNamesOrTags: []string{"image", "file"}, 531 SubSelectTags: []string{"os"}, 532 }, 533 wantErr: assert.NoError, 534 }, 535 { 536 name: "select only file tasks (default)", 537 taskGroups: [][]Task{ 538 createDummyPackageTasks(), 539 createDummyFileTasks(), 540 }, 541 selectionReq: cataloging.NewSelectionRequest().WithDefaults("file"), 542 wantGroups: [][]string{ 543 // filtered based on the request 544 nil, 545 { 546 // this is the original, untouched file task list 547 "file-content-cataloger", 548 "file-metadata-cataloger", 549 "file-digest-cataloger", 550 "file-executable-cataloger", 551 }, 552 }, 553 wantTokens: map[string]TokenSelection{ 554 // files 555 "file-content-cataloger": newTokenSelection([]string{"file"}, nil), 556 "file-digest-cataloger": newTokenSelection([]string{"file"}, nil), 557 "file-executable-cataloger": newTokenSelection([]string{"file"}, nil), 558 "file-metadata-cataloger": newTokenSelection([]string{"file"}, nil), 559 }, 560 wantRequest: cataloging.SelectionRequest{ 561 DefaultNamesOrTags: []string{"file"}, 562 }, 563 wantErr: assert.NoError, 564 }, 565 { 566 name: "select only file tasks (via removal of package)", 567 taskGroups: [][]Task{ 568 createDummyPackageTasks(), 569 createDummyFileTasks(), 570 }, 571 selectionReq: cataloging.NewSelectionRequest().WithDefaults("file", "image").WithRemovals("package"), 572 wantGroups: [][]string{ 573 // filtered based on the request 574 nil, 575 { 576 // this is the original, untouched file task list 577 "file-content-cataloger", 578 "file-metadata-cataloger", 579 "file-digest-cataloger", 580 "file-executable-cataloger", 581 }, 582 }, 583 wantTokens: map[string]TokenSelection{ 584 // packages 585 "alpm-db-cataloger": newTokenSelection([]string{"image"}, []string{"package"}), 586 "apk-db-cataloger": newTokenSelection([]string{"image"}, []string{"package"}), 587 "binary-cataloger": newTokenSelection([]string{"image"}, []string{"package"}), 588 "conan-info-cataloger": newTokenSelection([]string{"image"}, []string{"package"}), 589 "dotnet-portable-executable-cataloger": newTokenSelection([]string{"image"}, []string{"package"}), 590 "dpkg-db-cataloger": newTokenSelection([]string{"image"}, []string{"package"}), 591 "go-module-binary-cataloger": newTokenSelection([]string{"image"}, []string{"package"}), 592 "graalvm-native-image-cataloger": newTokenSelection([]string{"image"}, []string{"package"}), 593 "java-archive-cataloger": newTokenSelection([]string{"image"}, []string{"package"}), 594 "javascript-package-cataloger": newTokenSelection([]string{"image"}, []string{"package"}), 595 "php-composer-installed-cataloger": newTokenSelection([]string{"image"}, []string{"package"}), 596 "portage-cataloger": newTokenSelection([]string{"image"}, []string{"package"}), 597 "python-installed-package-cataloger": newTokenSelection([]string{"image"}, []string{"package"}), 598 "rpm-db-cataloger": newTokenSelection([]string{"image"}, []string{"package"}), 599 "ruby-installed-gemspec-cataloger": newTokenSelection([]string{"image"}, []string{"package"}), 600 "rust-cargo-lock-cataloger": newTokenSelection([]string{"image"}, []string{"package"}), 601 "sbom-cataloger": newTokenSelection([]string{"image"}, []string{"package"}), 602 "rpm-archive-cataloger": newTokenSelection(nil, []string{"package"}), 603 "conan-cataloger": newTokenSelection(nil, []string{"package"}), 604 "dart-pubspec-lock-cataloger": newTokenSelection(nil, []string{"package"}), 605 "dotnet-deps-cataloger": newTokenSelection(nil, []string{"package"}), 606 "elixir-mix-lock-cataloger": newTokenSelection(nil, []string{"package"}), 607 "erlang-rebar-lock-cataloger": newTokenSelection(nil, []string{"package"}), 608 "javascript-lock-cataloger": newTokenSelection(nil, []string{"package"}), 609 "github-actions-usage-cataloger": newTokenSelection(nil, []string{"package"}), 610 "github-action-workflow-usage-cataloger": newTokenSelection(nil, []string{"package"}), 611 // files 612 "file-content-cataloger": newTokenSelection([]string{"file"}, nil), 613 "file-digest-cataloger": newTokenSelection([]string{"file"}, nil), 614 "file-executable-cataloger": newTokenSelection([]string{"file"}, nil), 615 "file-metadata-cataloger": newTokenSelection([]string{"file"}, nil), 616 }, 617 wantRequest: cataloging.SelectionRequest{ 618 DefaultNamesOrTags: []string{"file", "image"}, 619 RemoveNamesOrTags: []string{"package"}, 620 }, 621 wantErr: assert.NoError, 622 }, 623 { 624 name: "select file and package tasks", 625 taskGroups: [][]Task{ 626 createDummyPackageTasks(), 627 createDummyFileTasks(), 628 }, 629 selectionReq: cataloging.NewSelectionRequest(). 630 WithDefaults("image"). 631 WithSubSelections("os", "content", "digest"), 632 wantGroups: [][]string{ 633 { 634 // filtered based on the request 635 "alpm-db-cataloger", 636 "apk-db-cataloger", 637 "dpkg-db-cataloger", 638 "portage-cataloger", 639 "rpm-db-cataloger", 640 }, 641 { 642 // filtered based on the request 643 "file-content-cataloger", 644 "file-digest-cataloger", 645 }, 646 }, 647 wantTokens: map[string]TokenSelection{ 648 // packages - os 649 "alpm-db-cataloger": newTokenSelection([]string{"os", "image"}, nil), 650 "apk-db-cataloger": newTokenSelection([]string{"os", "image"}, nil), 651 "rpm-archive-cataloger": newTokenSelection([]string{"os"}, nil), 652 "rpm-db-cataloger": newTokenSelection([]string{"os", "image"}, nil), 653 "portage-cataloger": newTokenSelection([]string{"os", "image"}, nil), 654 "dpkg-db-cataloger": newTokenSelection([]string{"os", "image"}, nil), 655 // packages - remaining 656 "binary-cataloger": newTokenSelection([]string{"image"}, nil), 657 "conan-info-cataloger": newTokenSelection([]string{"image"}, nil), 658 "dotnet-portable-executable-cataloger": newTokenSelection([]string{"image"}, nil), 659 "go-module-binary-cataloger": newTokenSelection([]string{"image"}, nil), 660 "graalvm-native-image-cataloger": newTokenSelection([]string{"image"}, nil), 661 "java-archive-cataloger": newTokenSelection([]string{"image"}, nil), 662 "javascript-package-cataloger": newTokenSelection([]string{"image"}, nil), 663 "php-composer-installed-cataloger": newTokenSelection([]string{"image"}, nil), 664 "python-installed-package-cataloger": newTokenSelection([]string{"image"}, nil), 665 "ruby-installed-gemspec-cataloger": newTokenSelection([]string{"image"}, nil), 666 "rust-cargo-lock-cataloger": newTokenSelection([]string{"image"}, nil), 667 "sbom-cataloger": newTokenSelection([]string{"image"}, nil), 668 // files 669 "file-content-cataloger": newTokenSelection([]string{"file", "content"}, nil), // note extra tags 670 "file-digest-cataloger": newTokenSelection([]string{"file", "digest"}, nil), // note extra tags 671 "file-executable-cataloger": newTokenSelection([]string{"file"}, nil), 672 "file-metadata-cataloger": newTokenSelection([]string{"file"}, nil), 673 }, 674 wantRequest: cataloging.SelectionRequest{ 675 DefaultNamesOrTags: []string{"image", "file"}, 676 SubSelectTags: []string{"os", "content", "digest"}, 677 }, 678 wantErr: assert.NoError, 679 }, 680 { 681 name: "complex selection with multiple operators across groups", 682 taskGroups: [][]Task{ 683 createDummyPackageTasks(), 684 createDummyFileTasks(), 685 }, 686 selectionReq: cataloging.NewSelectionRequest(). 687 WithDefaults("os"). // note: no file tag present 688 WithExpression("+github-actions-usage-cataloger", "-dpkg", "-digest", "content", "+file-metadata-cataloger", "-declared"), 689 wantGroups: [][]string{ 690 { 691 "alpm-db-cataloger", 692 "apk-db-cataloger", 693 "portage-cataloger", 694 "rpm-db-cataloger", 695 "github-actions-usage-cataloger", 696 }, 697 { 698 "file-content-cataloger", 699 "file-metadata-cataloger", 700 }, 701 }, 702 wantTokens: map[string]TokenSelection{ 703 // selected package tasks 704 "alpm-db-cataloger": newTokenSelection([]string{"os"}, nil), 705 "apk-db-cataloger": newTokenSelection([]string{"os"}, nil), 706 "dpkg-db-cataloger": newTokenSelection([]string{"os"}, []string{"dpkg"}), 707 "portage-cataloger": newTokenSelection([]string{"os"}, nil), 708 "rpm-archive-cataloger": newTokenSelection([]string{"os"}, []string{"declared"}), 709 "rpm-db-cataloger": newTokenSelection([]string{"os"}, nil), 710 "github-actions-usage-cataloger": newTokenSelection([]string{"github-actions-usage-cataloger"}, []string{"declared"}), 711 712 // selected file tasks 713 "file-content-cataloger": newTokenSelection([]string{"content", "file"}, nil), 714 "file-metadata-cataloger": newTokenSelection([]string{"file-metadata-cataloger", "file"}, nil), 715 716 // removed package tasks 717 "binary-cataloger": newTokenSelection(nil, []string{"declared"}), 718 "conan-cataloger": newTokenSelection(nil, []string{"declared"}), 719 "dart-pubspec-lock-cataloger": newTokenSelection(nil, []string{"declared"}), 720 "dotnet-deps-cataloger": newTokenSelection(nil, []string{"declared"}), 721 "elixir-mix-lock-cataloger": newTokenSelection(nil, []string{"declared"}), 722 "erlang-rebar-lock-cataloger": newTokenSelection(nil, []string{"declared"}), 723 "github-action-workflow-usage-cataloger": newTokenSelection(nil, []string{"declared"}), 724 "javascript-lock-cataloger": newTokenSelection(nil, []string{"declared"}), 725 "sbom-cataloger": newTokenSelection(nil, []string{"declared"}), 726 727 // removed file tasks 728 "file-executable-cataloger": newTokenSelection([]string{"file"}, nil), 729 "file-digest-cataloger": newTokenSelection([]string{"file"}, []string{"digest"}), 730 }, 731 wantRequest: cataloging.SelectionRequest{ 732 DefaultNamesOrTags: []string{"os", "file"}, // note: file added automatically 733 SubSelectTags: []string{"content"}, 734 RemoveNamesOrTags: []string{"dpkg", "digest", "declared"}, 735 AddNames: []string{"github-actions-usage-cataloger", "file-metadata-cataloger"}, 736 }, 737 wantErr: assert.NoError, 738 }, 739 { 740 name: "invalid tag", 741 taskGroups: [][]Task{ 742 createDummyPackageTasks(), 743 createDummyFileTasks(), 744 }, 745 selectionReq: cataloging.NewSelectionRequest().WithDefaults("invalid"), 746 wantGroups: nil, 747 wantTokens: nil, 748 wantRequest: cataloging.SelectionRequest{ 749 DefaultNamesOrTags: []string{"invalid", "file"}, 750 }, 751 wantErr: assert.Error, 752 }, 753 } 754 755 for _, tt := range tests { 756 t.Run(tt.name, func(t *testing.T) { 757 if tt.wantErr == nil { 758 tt.wantErr = assert.NoError 759 } 760 761 gotGroups, gotSelection, err := SelectInGroups(tt.taskGroups, tt.selectionReq) 762 tt.wantErr(t, err) 763 if err != nil { 764 // dev note: this is useful for debugging when needed... 765 //for _, e := range gotEvidence.Request.Expressions { 766 // t.Logf("expression (errors %q): %#v", e.Errors, e) 767 //} 768 769 // note: we DON'T bail early in validations... this is because we should always return the full set of 770 // of selected tasks and surrounding evidence. 771 } 772 773 var gotGroupNames [][]string 774 for _, group := range gotGroups { 775 var names []string 776 for _, task := range group { 777 names = append(names, task.Name()) 778 } 779 gotGroupNames = append(gotGroupNames, names) 780 } 781 782 assert.Equal(t, tt.wantGroups, gotGroupNames) 783 assert.Equal(t, tt.wantTokens, gotSelection.TokensByTask) 784 assert.Equal(t, tt.wantRequest, gotSelection.Request) 785 }) 786 } 787 }