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  }