syft/internal/task/selection_test.go
Alex Goodman 684b6e3f98
Add file catalogers to selection configuration (#3505)
* add file catalogers to selection configuration

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix typos

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* warn when there is conflicting file cataloging configuration

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* allow for explicit removal of all package and file tasks

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* address PR feedback

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2025-02-03 19:10:17 +00:00

788 lines
35 KiB
Go

package task
import (
"context"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/scylladb/go-set/strset"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/anchore/syft/internal/sbomsync"
"github.com/anchore/syft/syft/cataloging"
"github.com/anchore/syft/syft/file"
)
func dummyTask(name string, tags ...string) Task {
return NewTask(name, func(ctx context.Context, resolver file.Resolver, sbom sbomsync.Builder) error {
panic("not implemented")
}, tags...)
}
// note: this test fixture does not need to be kept up to date here, but makes a great test subject
func createDummyPackageTasks() tasks {
return []Task{
// OS package installed catalogers
dummyTask("alpm-db-cataloger", "package", "directory", "installed", "image", "os", "alpm", "archlinux"),
dummyTask("apk-db-cataloger", "package", "directory", "installed", "image", "os", "apk", "alpine"),
dummyTask("dpkg-db-cataloger", "package", "directory", "installed", "image", "os", "dpkg", "debian"),
dummyTask("portage-cataloger", "package", "directory", "installed", "image", "os", "portage", "gentoo"),
dummyTask("rpm-db-cataloger", "package", "directory", "installed", "image", "os", "rpm", "redhat"),
// OS package declared catalogers
dummyTask("rpm-archive-cataloger", "package", "declared", "directory", "os", "rpm", "redhat"),
// language-specific package installed catalogers
dummyTask("conan-info-cataloger", "package", "installed", "image", "language", "cpp", "conan"),
dummyTask("javascript-package-cataloger", "package", "installed", "image", "language", "javascript", "node"),
dummyTask("php-composer-installed-cataloger", "package", "installed", "image", "language", "php", "composer"),
dummyTask("ruby-installed-gemspec-cataloger", "package", "installed", "image", "language", "ruby", "gem", "gemspec"),
dummyTask("rust-cargo-lock-cataloger", "package", "installed", "image", "language", "rust", "binary"),
// language-specific package declared catalogers
dummyTask("conan-cataloger", "package", "declared", "directory", "language", "cpp", "conan"),
dummyTask("dart-pubspec-lock-cataloger", "package", "declared", "directory", "language", "dart"),
dummyTask("dotnet-deps-cataloger", "package", "declared", "directory", "language", "dotnet", "c#"),
dummyTask("elixir-mix-lock-cataloger", "package", "declared", "directory", "language", "elixir"),
dummyTask("erlang-rebar-lock-cataloger", "package", "declared", "directory", "language", "erlang"),
dummyTask("javascript-lock-cataloger", "package", "declared", "directory", "language", "javascript", "node", "npm"),
// language-specific package for both image and directory scans (but not necessarily declared)
dummyTask("dotnet-portable-executable-cataloger", "package", "directory", "installed", "image", "language", "dotnet", "c#"),
dummyTask("python-installed-package-cataloger", "package", "directory", "installed", "image", "language", "python"),
dummyTask("go-module-binary-cataloger", "package", "directory", "installed", "image", "language", "go", "golang", "gomod", "binary"),
dummyTask("java-archive-cataloger", "package", "directory", "installed", "image", "language", "java", "maven"),
dummyTask("graalvm-native-image-cataloger", "package", "directory", "installed", "image", "language", "java"),
// other package catalogers
dummyTask("binary-cataloger", "package", "declared", "directory", "image", "binary"),
dummyTask("github-actions-usage-cataloger", "package", "declared", "directory", "github", "github-actions"),
dummyTask("github-action-workflow-usage-cataloger", "package", "declared", "directory", "github", "github-actions"),
dummyTask("sbom-cataloger", "package", "declared", "directory", "image", "sbom"),
}
}
func createDummyFileTasks() tasks {
return []Task{
dummyTask("file-content-cataloger", "file", "content"),
dummyTask("file-metadata-cataloger", "file", "metadata"),
dummyTask("file-digest-cataloger", "file", "digest"),
dummyTask("file-executable-cataloger", "file", "binary-metadata"),
}
}
func TestSelect(t *testing.T) {
tests := []struct {
name string
allTasks []Task
basis []string
expressions []string
wantNames []string
wantTokens map[string]TokenSelection
wantRequest cataloging.SelectionRequest
wantErr assert.ErrorAssertionFunc
}{
{
name: "empty input",
allTasks: []Task{},
basis: []string{},
expressions: []string{},
wantNames: []string{},
wantTokens: map[string]TokenSelection{},
wantRequest: cataloging.SelectionRequest{},
},
{
name: "use default tasks",
allTasks: createDummyPackageTasks(),
basis: []string{
"image",
},
expressions: []string{},
wantNames: []string{
"alpm-db-cataloger",
"apk-db-cataloger",
"dpkg-db-cataloger",
"portage-cataloger",
"rpm-db-cataloger",
"conan-info-cataloger",
"javascript-package-cataloger",
"php-composer-installed-cataloger",
"ruby-installed-gemspec-cataloger",
"rust-cargo-lock-cataloger",
"dotnet-portable-executable-cataloger",
"python-installed-package-cataloger",
"go-module-binary-cataloger",
"java-archive-cataloger",
"graalvm-native-image-cataloger",
"binary-cataloger",
"sbom-cataloger",
},
wantTokens: map[string]TokenSelection{
"alpm-db-cataloger": newTokenSelection([]string{"image"}, nil),
"apk-db-cataloger": newTokenSelection([]string{"image"}, nil),
"dpkg-db-cataloger": newTokenSelection([]string{"image"}, nil),
"portage-cataloger": newTokenSelection([]string{"image"}, nil),
"rpm-db-cataloger": newTokenSelection([]string{"image"}, nil),
"conan-info-cataloger": newTokenSelection([]string{"image"}, nil),
"javascript-package-cataloger": newTokenSelection([]string{"image"}, nil),
"php-composer-installed-cataloger": newTokenSelection([]string{"image"}, nil),
"ruby-installed-gemspec-cataloger": newTokenSelection([]string{"image"}, nil),
"rust-cargo-lock-cataloger": newTokenSelection([]string{"image"}, nil),
"dotnet-portable-executable-cataloger": newTokenSelection([]string{"image"}, nil),
"python-installed-package-cataloger": newTokenSelection([]string{"image"}, nil),
"go-module-binary-cataloger": newTokenSelection([]string{"image"}, nil),
"java-archive-cataloger": newTokenSelection([]string{"image"}, nil),
"graalvm-native-image-cataloger": newTokenSelection([]string{"image"}, nil),
"binary-cataloger": newTokenSelection([]string{"image"}, nil),
"sbom-cataloger": newTokenSelection([]string{"image"}, nil),
},
wantRequest: cataloging.SelectionRequest{
DefaultNamesOrTags: []string{"image"},
},
},
{
name: "select, add, and remove tasks",
allTasks: createDummyPackageTasks(),
basis: []string{
"image",
},
expressions: []string{
"+github-actions-usage-cataloger",
"-dpkg",
"os",
},
wantNames: []string{
"alpm-db-cataloger",
"apk-db-cataloger",
"portage-cataloger",
"rpm-db-cataloger",
"github-actions-usage-cataloger",
},
wantTokens: map[string]TokenSelection{
// selected
"alpm-db-cataloger": newTokenSelection([]string{"image", "os"}, nil),
"apk-db-cataloger": newTokenSelection([]string{"image", "os"}, nil),
"dpkg-db-cataloger": newTokenSelection([]string{"image", "os"}, []string{"dpkg"}),
"portage-cataloger": newTokenSelection([]string{"image", "os"}, nil),
"rpm-db-cataloger": newTokenSelection([]string{"image", "os"}, nil),
"github-actions-usage-cataloger": newTokenSelection([]string{"github-actions-usage-cataloger"}, nil),
// ultimately not selected
"rpm-archive-cataloger": newTokenSelection([]string{"os"}, nil),
"conan-info-cataloger": newTokenSelection([]string{"image"}, nil),
"javascript-package-cataloger": newTokenSelection([]string{"image"}, nil),
"php-composer-installed-cataloger": newTokenSelection([]string{"image"}, nil),
"ruby-installed-gemspec-cataloger": newTokenSelection([]string{"image"}, nil),
"rust-cargo-lock-cataloger": newTokenSelection([]string{"image"}, nil),
"dotnet-portable-executable-cataloger": newTokenSelection([]string{"image"}, nil),
"python-installed-package-cataloger": newTokenSelection([]string{"image"}, nil),
"go-module-binary-cataloger": newTokenSelection([]string{"image"}, nil),
"java-archive-cataloger": newTokenSelection([]string{"image"}, nil),
"graalvm-native-image-cataloger": newTokenSelection([]string{"image"}, nil),
"binary-cataloger": newTokenSelection([]string{"image"}, nil),
"sbom-cataloger": newTokenSelection([]string{"image"}, nil),
},
wantRequest: cataloging.SelectionRequest{
DefaultNamesOrTags: []string{"image"},
SubSelectTags: []string{"os"},
RemoveNamesOrTags: []string{"dpkg"},
AddNames: []string{"github-actions-usage-cataloger"},
},
},
{
name: "allow for partial selections",
allTasks: createDummyPackageTasks(),
basis: []string{
"image",
},
expressions: []string{
// valid...
"+github-actions-usage-cataloger",
"-dpkg",
"os",
// invalid...
"+python",
"rust-cargo-lock-cataloger",
},
wantNames: []string{
"alpm-db-cataloger",
"apk-db-cataloger",
"portage-cataloger",
"rpm-db-cataloger",
"github-actions-usage-cataloger",
},
wantTokens: map[string]TokenSelection{
// selected
"alpm-db-cataloger": newTokenSelection([]string{"image", "os"}, nil),
"apk-db-cataloger": newTokenSelection([]string{"image", "os"}, nil),
"dpkg-db-cataloger": newTokenSelection([]string{"image", "os"}, []string{"dpkg"}),
"portage-cataloger": newTokenSelection([]string{"image", "os"}, nil),
"rpm-db-cataloger": newTokenSelection([]string{"image", "os"}, nil),
"github-actions-usage-cataloger": newTokenSelection([]string{"github-actions-usage-cataloger"}, nil),
// ultimately not selected
"rpm-archive-cataloger": newTokenSelection([]string{"os"}, nil),
"conan-info-cataloger": newTokenSelection([]string{"image"}, nil),
"javascript-package-cataloger": newTokenSelection([]string{"image"}, nil),
"php-composer-installed-cataloger": newTokenSelection([]string{"image"}, nil),
"ruby-installed-gemspec-cataloger": newTokenSelection([]string{"image"}, nil),
"rust-cargo-lock-cataloger": newTokenSelection([]string{"image"}, nil),
"dotnet-portable-executable-cataloger": newTokenSelection([]string{"image"}, nil),
"python-installed-package-cataloger": newTokenSelection([]string{"image"}, nil), // note: there is no python token used for selection
"go-module-binary-cataloger": newTokenSelection([]string{"image"}, nil),
"java-archive-cataloger": newTokenSelection([]string{"image"}, nil),
"graalvm-native-image-cataloger": newTokenSelection([]string{"image"}, nil),
"binary-cataloger": newTokenSelection([]string{"image"}, nil),
"sbom-cataloger": newTokenSelection([]string{"image"}, nil),
},
wantRequest: cataloging.SelectionRequest{
DefaultNamesOrTags: []string{"image"},
SubSelectTags: []string{"os", "rust-cargo-lock-cataloger"},
RemoveNamesOrTags: []string{"dpkg"},
AddNames: []string{"github-actions-usage-cataloger", "python"},
},
wantErr: assert.Error, // !important!
},
{
name: "select all tasks",
allTasks: createDummyPackageTasks(),
basis: []string{
"all",
},
expressions: []string{},
wantNames: []string{
"alpm-db-cataloger",
"apk-db-cataloger",
"dpkg-db-cataloger",
"portage-cataloger",
"rpm-db-cataloger",
"rpm-archive-cataloger",
"conan-info-cataloger",
"javascript-package-cataloger",
"php-composer-installed-cataloger",
"ruby-installed-gemspec-cataloger",
"rust-cargo-lock-cataloger",
"conan-cataloger",
"dart-pubspec-lock-cataloger",
"dotnet-deps-cataloger",
"elixir-mix-lock-cataloger",
"erlang-rebar-lock-cataloger",
"javascript-lock-cataloger",
"dotnet-portable-executable-cataloger",
"python-installed-package-cataloger",
"go-module-binary-cataloger",
"java-archive-cataloger",
"graalvm-native-image-cataloger",
"binary-cataloger",
"github-actions-usage-cataloger",
"github-action-workflow-usage-cataloger",
"sbom-cataloger",
},
wantTokens: map[string]TokenSelection{
"alpm-db-cataloger": newTokenSelection([]string{"all"}, nil),
"apk-db-cataloger": newTokenSelection([]string{"all"}, nil),
"dpkg-db-cataloger": newTokenSelection([]string{"all"}, nil),
"portage-cataloger": newTokenSelection([]string{"all"}, nil),
"rpm-db-cataloger": newTokenSelection([]string{"all"}, nil),
"rpm-archive-cataloger": newTokenSelection([]string{"all"}, nil),
"conan-info-cataloger": newTokenSelection([]string{"all"}, nil),
"javascript-package-cataloger": newTokenSelection([]string{"all"}, nil),
"php-composer-installed-cataloger": newTokenSelection([]string{"all"}, nil),
"ruby-installed-gemspec-cataloger": newTokenSelection([]string{"all"}, nil),
"rust-cargo-lock-cataloger": newTokenSelection([]string{"all"}, nil),
"conan-cataloger": newTokenSelection([]string{"all"}, nil),
"dart-pubspec-lock-cataloger": newTokenSelection([]string{"all"}, nil),
"dotnet-deps-cataloger": newTokenSelection([]string{"all"}, nil),
"elixir-mix-lock-cataloger": newTokenSelection([]string{"all"}, nil),
"erlang-rebar-lock-cataloger": newTokenSelection([]string{"all"}, nil),
"javascript-lock-cataloger": newTokenSelection([]string{"all"}, nil),
"dotnet-portable-executable-cataloger": newTokenSelection([]string{"all"}, nil),
"python-installed-package-cataloger": newTokenSelection([]string{"all"}, nil),
"go-module-binary-cataloger": newTokenSelection([]string{"all"}, nil),
"java-archive-cataloger": newTokenSelection([]string{"all"}, nil),
"graalvm-native-image-cataloger": newTokenSelection([]string{"all"}, nil),
"binary-cataloger": newTokenSelection([]string{"all"}, nil),
"github-actions-usage-cataloger": newTokenSelection([]string{"all"}, nil),
"github-action-workflow-usage-cataloger": newTokenSelection([]string{"all"}, nil),
"sbom-cataloger": newTokenSelection([]string{"all"}, nil),
},
wantRequest: cataloging.SelectionRequest{
DefaultNamesOrTags: []string{"all"},
},
},
{
name: "set default with multiple tags",
allTasks: createDummyPackageTasks(),
basis: []string{
"gemspec",
"python",
},
expressions: []string{},
wantNames: []string{
"ruby-installed-gemspec-cataloger",
"python-installed-package-cataloger",
},
wantTokens: map[string]TokenSelection{
"ruby-installed-gemspec-cataloger": newTokenSelection([]string{"gemspec"}, nil),
"python-installed-package-cataloger": newTokenSelection([]string{"python"}, nil),
},
wantRequest: cataloging.SelectionRequest{
DefaultNamesOrTags: []string{"gemspec", "python"},
},
},
{
name: "automatically add file to default tags",
allTasks: createDummyFileTasks(),
basis: []string{},
expressions: []string{},
wantNames: []string{
"file-content-cataloger",
"file-metadata-cataloger",
"file-digest-cataloger",
"file-executable-cataloger",
},
wantTokens: map[string]TokenSelection{
"file-content-cataloger": newTokenSelection([]string{"file"}, nil),
"file-metadata-cataloger": newTokenSelection([]string{"file"}, nil),
"file-digest-cataloger": newTokenSelection([]string{"file"}, nil),
"file-executable-cataloger": newTokenSelection([]string{"file"}, nil),
},
wantRequest: cataloging.SelectionRequest{
DefaultNamesOrTags: []string{"file"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.wantErr == nil {
tt.wantErr = assert.NoError
}
req := cataloging.NewSelectionRequest().WithDefaults(tt.basis...).WithExpression(tt.expressions...)
got, gotEvidence, err := Select(tt.allTasks, req)
tt.wantErr(t, err)
if err != nil {
// dev note: this is useful for debugging when needed...
//for _, e := range gotEvidence.Request.Expressions {
// t.Logf("expression (errors %q): %#v", e.Errors, e)
//}
// note: we DON'T bail early in validations... this is because we should always return the full set of
// of selected tasks and surrounding evidence.
}
gotNames := make([]string, 0)
for _, g := range got {
gotNames = append(gotNames, g.Name())
}
assert.Equal(t, tt.wantNames, gotNames)
// names in selection should match all tasks returned
require.Len(t, tt.wantNames, gotEvidence.Result.Size(), "selected tasks should match all tasks returned (but does not)")
assert.ElementsMatch(t, tt.wantNames, gotEvidence.Result.List(), "selected tasks should match all tasks returned (but does not)")
setCompare := cmp.Comparer(func(x, y *strset.Set) bool {
return x.IsEqual(y)
})
if d := cmp.Diff(tt.wantTokens, gotEvidence.TokensByTask, setCompare); d != "" {
t.Errorf("unexpected tokens by task (-want +got):\n%s", d)
}
assert.Equal(t, tt.wantRequest, gotEvidence.Request)
})
}
}
func TestSelectInGroups(t *testing.T) {
tests := []struct {
name string
taskGroups [][]Task
selectionReq cataloging.SelectionRequest
wantGroups [][]string
wantTokens map[string]TokenSelection
wantRequest cataloging.SelectionRequest
wantErr assert.ErrorAssertionFunc
}{
{
name: "select only within the file tasks (leave package tasks alone)",
taskGroups: [][]Task{
createDummyPackageTasks(),
createDummyFileTasks(),
},
selectionReq: cataloging.NewSelectionRequest().
WithDefaults("image"). // note: file missing
WithSubSelections("content", "digest"),
wantGroups: [][]string{
{
// this is the original, untouched package task list
"alpm-db-cataloger",
"apk-db-cataloger",
"dpkg-db-cataloger",
"portage-cataloger",
"rpm-db-cataloger",
"conan-info-cataloger",
"javascript-package-cataloger",
"php-composer-installed-cataloger",
"ruby-installed-gemspec-cataloger",
"rust-cargo-lock-cataloger",
"dotnet-portable-executable-cataloger",
"python-installed-package-cataloger",
"go-module-binary-cataloger",
"java-archive-cataloger",
"graalvm-native-image-cataloger",
"binary-cataloger",
"sbom-cataloger",
},
{
// this has been filtered based on the request
"file-content-cataloger",
"file-digest-cataloger",
},
},
wantTokens: map[string]TokenSelection{
// packages
"alpm-db-cataloger": newTokenSelection([]string{"image"}, nil),
"apk-db-cataloger": newTokenSelection([]string{"image"}, nil),
"binary-cataloger": newTokenSelection([]string{"image"}, nil),
"conan-info-cataloger": newTokenSelection([]string{"image"}, nil),
"dotnet-portable-executable-cataloger": newTokenSelection([]string{"image"}, nil),
"dpkg-db-cataloger": newTokenSelection([]string{"image"}, nil),
"go-module-binary-cataloger": newTokenSelection([]string{"image"}, nil),
"graalvm-native-image-cataloger": newTokenSelection([]string{"image"}, nil),
"java-archive-cataloger": newTokenSelection([]string{"image"}, nil),
"javascript-package-cataloger": newTokenSelection([]string{"image"}, nil),
"php-composer-installed-cataloger": newTokenSelection([]string{"image"}, nil),
"portage-cataloger": newTokenSelection([]string{"image"}, nil),
"python-installed-package-cataloger": newTokenSelection([]string{"image"}, nil),
"rpm-db-cataloger": newTokenSelection([]string{"image"}, nil),
"ruby-installed-gemspec-cataloger": newTokenSelection([]string{"image"}, nil),
"rust-cargo-lock-cataloger": newTokenSelection([]string{"image"}, nil),
"sbom-cataloger": newTokenSelection([]string{"image"}, nil),
// files
"file-content-cataloger": newTokenSelection([]string{"content", "file"}, nil),
"file-digest-cataloger": newTokenSelection([]string{"digest", "file"}, nil),
"file-executable-cataloger": newTokenSelection([]string{"file"}, nil),
"file-metadata-cataloger": newTokenSelection([]string{"file"}, nil),
},
wantRequest: cataloging.SelectionRequest{
DefaultNamesOrTags: []string{"image", "file"}, // note: file automatically added
SubSelectTags: []string{"content", "digest"},
},
wantErr: assert.NoError,
},
{
name: "select package tasks (leave file tasks alone)",
taskGroups: [][]Task{
createDummyPackageTasks(),
createDummyFileTasks(),
},
selectionReq: cataloging.NewSelectionRequest().WithDefaults("image").WithSubSelections("os"),
wantGroups: [][]string{
{
// filtered based on the request
"alpm-db-cataloger",
"apk-db-cataloger",
"dpkg-db-cataloger",
"portage-cataloger",
"rpm-db-cataloger",
},
{
// this is the original, untouched file task list
"file-content-cataloger",
"file-metadata-cataloger",
"file-digest-cataloger",
"file-executable-cataloger",
},
},
wantTokens: map[string]TokenSelection{
// packages - os
"alpm-db-cataloger": newTokenSelection([]string{"os", "image"}, nil),
"apk-db-cataloger": newTokenSelection([]string{"os", "image"}, nil),
"rpm-archive-cataloger": newTokenSelection([]string{"os"}, nil),
"rpm-db-cataloger": newTokenSelection([]string{"os", "image"}, nil),
"portage-cataloger": newTokenSelection([]string{"os", "image"}, nil),
"dpkg-db-cataloger": newTokenSelection([]string{"os", "image"}, nil),
// packages - remaining
"binary-cataloger": newTokenSelection([]string{"image"}, nil),
"conan-info-cataloger": newTokenSelection([]string{"image"}, nil),
"dotnet-portable-executable-cataloger": newTokenSelection([]string{"image"}, nil),
"go-module-binary-cataloger": newTokenSelection([]string{"image"}, nil),
"graalvm-native-image-cataloger": newTokenSelection([]string{"image"}, nil),
"java-archive-cataloger": newTokenSelection([]string{"image"}, nil),
"javascript-package-cataloger": newTokenSelection([]string{"image"}, nil),
"php-composer-installed-cataloger": newTokenSelection([]string{"image"}, nil),
"python-installed-package-cataloger": newTokenSelection([]string{"image"}, nil),
"ruby-installed-gemspec-cataloger": newTokenSelection([]string{"image"}, nil),
"rust-cargo-lock-cataloger": newTokenSelection([]string{"image"}, nil),
"sbom-cataloger": newTokenSelection([]string{"image"}, nil),
// files
"file-content-cataloger": newTokenSelection([]string{"file"}, nil),
"file-digest-cataloger": newTokenSelection([]string{"file"}, nil),
"file-executable-cataloger": newTokenSelection([]string{"file"}, nil),
"file-metadata-cataloger": newTokenSelection([]string{"file"}, nil),
},
wantRequest: cataloging.SelectionRequest{
DefaultNamesOrTags: []string{"image", "file"},
SubSelectTags: []string{"os"},
},
wantErr: assert.NoError,
},
{
name: "select only file tasks (default)",
taskGroups: [][]Task{
createDummyPackageTasks(),
createDummyFileTasks(),
},
selectionReq: cataloging.NewSelectionRequest().WithDefaults("file"),
wantGroups: [][]string{
// filtered based on the request
nil,
{
// this is the original, untouched file task list
"file-content-cataloger",
"file-metadata-cataloger",
"file-digest-cataloger",
"file-executable-cataloger",
},
},
wantTokens: map[string]TokenSelection{
// files
"file-content-cataloger": newTokenSelection([]string{"file"}, nil),
"file-digest-cataloger": newTokenSelection([]string{"file"}, nil),
"file-executable-cataloger": newTokenSelection([]string{"file"}, nil),
"file-metadata-cataloger": newTokenSelection([]string{"file"}, nil),
},
wantRequest: cataloging.SelectionRequest{
DefaultNamesOrTags: []string{"file"},
},
wantErr: assert.NoError,
},
{
name: "select only file tasks (via removal of package)",
taskGroups: [][]Task{
createDummyPackageTasks(),
createDummyFileTasks(),
},
selectionReq: cataloging.NewSelectionRequest().WithDefaults("file", "image").WithRemovals("package"),
wantGroups: [][]string{
// filtered based on the request
nil,
{
// this is the original, untouched file task list
"file-content-cataloger",
"file-metadata-cataloger",
"file-digest-cataloger",
"file-executable-cataloger",
},
},
wantTokens: map[string]TokenSelection{
// packages
"alpm-db-cataloger": newTokenSelection([]string{"image"}, []string{"package"}),
"apk-db-cataloger": newTokenSelection([]string{"image"}, []string{"package"}),
"binary-cataloger": newTokenSelection([]string{"image"}, []string{"package"}),
"conan-info-cataloger": newTokenSelection([]string{"image"}, []string{"package"}),
"dotnet-portable-executable-cataloger": newTokenSelection([]string{"image"}, []string{"package"}),
"dpkg-db-cataloger": newTokenSelection([]string{"image"}, []string{"package"}),
"go-module-binary-cataloger": newTokenSelection([]string{"image"}, []string{"package"}),
"graalvm-native-image-cataloger": newTokenSelection([]string{"image"}, []string{"package"}),
"java-archive-cataloger": newTokenSelection([]string{"image"}, []string{"package"}),
"javascript-package-cataloger": newTokenSelection([]string{"image"}, []string{"package"}),
"php-composer-installed-cataloger": newTokenSelection([]string{"image"}, []string{"package"}),
"portage-cataloger": newTokenSelection([]string{"image"}, []string{"package"}),
"python-installed-package-cataloger": newTokenSelection([]string{"image"}, []string{"package"}),
"rpm-db-cataloger": newTokenSelection([]string{"image"}, []string{"package"}),
"ruby-installed-gemspec-cataloger": newTokenSelection([]string{"image"}, []string{"package"}),
"rust-cargo-lock-cataloger": newTokenSelection([]string{"image"}, []string{"package"}),
"sbom-cataloger": newTokenSelection([]string{"image"}, []string{"package"}),
"rpm-archive-cataloger": newTokenSelection(nil, []string{"package"}),
"conan-cataloger": newTokenSelection(nil, []string{"package"}),
"dart-pubspec-lock-cataloger": newTokenSelection(nil, []string{"package"}),
"dotnet-deps-cataloger": newTokenSelection(nil, []string{"package"}),
"elixir-mix-lock-cataloger": newTokenSelection(nil, []string{"package"}),
"erlang-rebar-lock-cataloger": newTokenSelection(nil, []string{"package"}),
"javascript-lock-cataloger": newTokenSelection(nil, []string{"package"}),
"github-actions-usage-cataloger": newTokenSelection(nil, []string{"package"}),
"github-action-workflow-usage-cataloger": newTokenSelection(nil, []string{"package"}),
// files
"file-content-cataloger": newTokenSelection([]string{"file"}, nil),
"file-digest-cataloger": newTokenSelection([]string{"file"}, nil),
"file-executable-cataloger": newTokenSelection([]string{"file"}, nil),
"file-metadata-cataloger": newTokenSelection([]string{"file"}, nil),
},
wantRequest: cataloging.SelectionRequest{
DefaultNamesOrTags: []string{"file", "image"},
RemoveNamesOrTags: []string{"package"},
},
wantErr: assert.NoError,
},
{
name: "select file and package tasks",
taskGroups: [][]Task{
createDummyPackageTasks(),
createDummyFileTasks(),
},
selectionReq: cataloging.NewSelectionRequest().
WithDefaults("image").
WithSubSelections("os", "content", "digest"),
wantGroups: [][]string{
{
// filtered based on the request
"alpm-db-cataloger",
"apk-db-cataloger",
"dpkg-db-cataloger",
"portage-cataloger",
"rpm-db-cataloger",
},
{
// filtered based on the request
"file-content-cataloger",
"file-digest-cataloger",
},
},
wantTokens: map[string]TokenSelection{
// packages - os
"alpm-db-cataloger": newTokenSelection([]string{"os", "image"}, nil),
"apk-db-cataloger": newTokenSelection([]string{"os", "image"}, nil),
"rpm-archive-cataloger": newTokenSelection([]string{"os"}, nil),
"rpm-db-cataloger": newTokenSelection([]string{"os", "image"}, nil),
"portage-cataloger": newTokenSelection([]string{"os", "image"}, nil),
"dpkg-db-cataloger": newTokenSelection([]string{"os", "image"}, nil),
// packages - remaining
"binary-cataloger": newTokenSelection([]string{"image"}, nil),
"conan-info-cataloger": newTokenSelection([]string{"image"}, nil),
"dotnet-portable-executable-cataloger": newTokenSelection([]string{"image"}, nil),
"go-module-binary-cataloger": newTokenSelection([]string{"image"}, nil),
"graalvm-native-image-cataloger": newTokenSelection([]string{"image"}, nil),
"java-archive-cataloger": newTokenSelection([]string{"image"}, nil),
"javascript-package-cataloger": newTokenSelection([]string{"image"}, nil),
"php-composer-installed-cataloger": newTokenSelection([]string{"image"}, nil),
"python-installed-package-cataloger": newTokenSelection([]string{"image"}, nil),
"ruby-installed-gemspec-cataloger": newTokenSelection([]string{"image"}, nil),
"rust-cargo-lock-cataloger": newTokenSelection([]string{"image"}, nil),
"sbom-cataloger": newTokenSelection([]string{"image"}, nil),
// files
"file-content-cataloger": newTokenSelection([]string{"file", "content"}, nil), // note extra tags
"file-digest-cataloger": newTokenSelection([]string{"file", "digest"}, nil), // note extra tags
"file-executable-cataloger": newTokenSelection([]string{"file"}, nil),
"file-metadata-cataloger": newTokenSelection([]string{"file"}, nil),
},
wantRequest: cataloging.SelectionRequest{
DefaultNamesOrTags: []string{"image", "file"},
SubSelectTags: []string{"os", "content", "digest"},
},
wantErr: assert.NoError,
},
{
name: "complex selection with multiple operators across groups",
taskGroups: [][]Task{
createDummyPackageTasks(),
createDummyFileTasks(),
},
selectionReq: cataloging.NewSelectionRequest().
WithDefaults("os"). // note: no file tag present
WithExpression("+github-actions-usage-cataloger", "-dpkg", "-digest", "content", "+file-metadata-cataloger", "-declared"),
wantGroups: [][]string{
{
"alpm-db-cataloger",
"apk-db-cataloger",
"portage-cataloger",
"rpm-db-cataloger",
"github-actions-usage-cataloger",
},
{
"file-content-cataloger",
"file-metadata-cataloger",
},
},
wantTokens: map[string]TokenSelection{
// selected package tasks
"alpm-db-cataloger": newTokenSelection([]string{"os"}, nil),
"apk-db-cataloger": newTokenSelection([]string{"os"}, nil),
"dpkg-db-cataloger": newTokenSelection([]string{"os"}, []string{"dpkg"}),
"portage-cataloger": newTokenSelection([]string{"os"}, nil),
"rpm-archive-cataloger": newTokenSelection([]string{"os"}, []string{"declared"}),
"rpm-db-cataloger": newTokenSelection([]string{"os"}, nil),
"github-actions-usage-cataloger": newTokenSelection([]string{"github-actions-usage-cataloger"}, []string{"declared"}),
// selected file tasks
"file-content-cataloger": newTokenSelection([]string{"content", "file"}, nil),
"file-metadata-cataloger": newTokenSelection([]string{"file-metadata-cataloger", "file"}, nil),
// removed package tasks
"binary-cataloger": newTokenSelection(nil, []string{"declared"}),
"conan-cataloger": newTokenSelection(nil, []string{"declared"}),
"dart-pubspec-lock-cataloger": newTokenSelection(nil, []string{"declared"}),
"dotnet-deps-cataloger": newTokenSelection(nil, []string{"declared"}),
"elixir-mix-lock-cataloger": newTokenSelection(nil, []string{"declared"}),
"erlang-rebar-lock-cataloger": newTokenSelection(nil, []string{"declared"}),
"github-action-workflow-usage-cataloger": newTokenSelection(nil, []string{"declared"}),
"javascript-lock-cataloger": newTokenSelection(nil, []string{"declared"}),
"sbom-cataloger": newTokenSelection(nil, []string{"declared"}),
// removed file tasks
"file-executable-cataloger": newTokenSelection([]string{"file"}, nil),
"file-digest-cataloger": newTokenSelection([]string{"file"}, []string{"digest"}),
},
wantRequest: cataloging.SelectionRequest{
DefaultNamesOrTags: []string{"os", "file"}, // note: file added automatically
SubSelectTags: []string{"content"},
RemoveNamesOrTags: []string{"dpkg", "digest", "declared"},
AddNames: []string{"github-actions-usage-cataloger", "file-metadata-cataloger"},
},
wantErr: assert.NoError,
},
{
name: "invalid tag",
taskGroups: [][]Task{
createDummyPackageTasks(),
createDummyFileTasks(),
},
selectionReq: cataloging.NewSelectionRequest().WithDefaults("invalid"),
wantGroups: nil,
wantTokens: nil,
wantRequest: cataloging.SelectionRequest{
DefaultNamesOrTags: []string{"invalid", "file"},
},
wantErr: assert.Error,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.wantErr == nil {
tt.wantErr = assert.NoError
}
gotGroups, gotSelection, err := SelectInGroups(tt.taskGroups, tt.selectionReq)
tt.wantErr(t, err)
if err != nil {
// dev note: this is useful for debugging when needed...
//for _, e := range gotEvidence.Request.Expressions {
// t.Logf("expression (errors %q): %#v", e.Errors, e)
//}
// note: we DON'T bail early in validations... this is because we should always return the full set of
// of selected tasks and surrounding evidence.
}
var gotGroupNames [][]string
for _, group := range gotGroups {
var names []string
for _, task := range group {
names = append(names, task.Name())
}
gotGroupNames = append(gotGroupNames, names)
}
assert.Equal(t, tt.wantGroups, gotGroupNames)
assert.Equal(t, tt.wantTokens, gotSelection.TokensByTask)
assert.Equal(t, tt.wantRequest, gotSelection.Request)
})
}
}