-
Notifications
You must be signed in to change notification settings - Fork 577
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Re-introduce linux kernel cataloger (#2526)
* re-add linux kernel cataloger Signed-off-by: Alex Goodman <[email protected]> * ensure there is at least a directory or image tag on each task Signed-off-by: Alex Goodman <[email protected]> * fix CLI tests to account for kernel finding (+2) Signed-off-by: Alex Goodman <[email protected]> --------- Signed-off-by: Alex Goodman <[email protected]>
- Loading branch information
Showing
4 changed files
with
213 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
201 changes: 201 additions & 0 deletions
201
test/integration/package_catalogers_represented_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,201 @@ | ||
package integration | ||
|
||
import ( | ||
"bytes" | ||
"go/ast" | ||
"go/parser" | ||
"go/token" | ||
"os" | ||
"os/exec" | ||
"path/filepath" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/scylladb/go-set/strset" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/anchore/syft/internal/task" | ||
"github.com/anchore/syft/syft/cataloging/pkgcataloging" | ||
) | ||
|
||
func TestAllPackageCatalogersReachableInTasks(t *testing.T) { | ||
// we want to see if we can get a task for all package catalogers. This is a bit tricky since we | ||
// don't have a nice way to find all cataloger names in the codebase. Instead, we'll look at the | ||
// count of unique task names from the package task factory set and compare that with the known constructors | ||
// from a source analysis... they should match. | ||
|
||
// additionally, at this time they should either have a "directory" or "image" tag as well. If there is no tag | ||
// on a cataloger task then the test should fail. | ||
|
||
taskFactories := task.DefaultPackageTaskFactories() | ||
taskTagsByName := make(map[string][]string) | ||
for _, factory := range taskFactories { | ||
tsk := factory(task.DefaultCatalogingFactoryConfig()) | ||
if taskTagsByName[tsk.Name()] != nil { | ||
t.Fatalf("duplicate task name: %q", tsk.Name()) | ||
} | ||
|
||
require.NotNil(t, tsk) | ||
if sel, ok := tsk.(task.Selector); ok { | ||
taskTagsByName[tsk.Name()] = sel.Selectors() | ||
} else { | ||
taskTagsByName[tsk.Name()] = []string{} | ||
} | ||
} | ||
|
||
var constructorCount int | ||
constructorsPerPackage := getCatalogerConstructors(t) | ||
for _, constructors := range constructorsPerPackage { | ||
constructorCount += constructors.Size() | ||
} | ||
|
||
assert.Equal(t, len(taskTagsByName), constructorCount, "mismatch in number of cataloger constructors and task names") | ||
|
||
for taskName, tags := range taskTagsByName { | ||
if !strset.New(tags...).HasAny(pkgcataloging.ImageTag, pkgcataloging.DirectoryTag) { | ||
t.Errorf("task %q is missing 'directory' or 'image' a tag", taskName) | ||
} | ||
} | ||
|
||
} | ||
|
||
func TestAllPackageCatalogersRepresentedInSource(t *testing.T) { | ||
// find all functions in syft/pkg/cataloger/** that either: | ||
// - match the name glob "New*Cataloger" | ||
// - are in cataloger.go and match the name glob "New*" | ||
// | ||
// Then: | ||
// - keep track of all packages with cataloger constructors | ||
// - keep track of all constructors | ||
constructorsPerPackage := getCatalogerConstructors(t) | ||
|
||
// look at the source file in internal/task/package_tasks.go: | ||
// - ensure all go packages that have constructors are imported | ||
// - ensure there is a reference to all package constructors | ||
assertAllPackageCatalogersRepresented(t, constructorsPerPackage) | ||
} | ||
|
||
func getCatalogerConstructors(t *testing.T) map[string]*strset.Set { | ||
t.Helper() | ||
root := repoRoot(t) | ||
catalogerPath := filepath.Join(root, "syft", "pkg", "cataloger") | ||
|
||
constructorsPerPackage := make(map[string]*strset.Set) | ||
|
||
err := filepath.Walk(catalogerPath, func(path string, info os.FileInfo, err error) error { | ||
require.NoError(t, err) | ||
|
||
// ignore directories and test files... | ||
if info.IsDir() || strings.HasSuffix(info.Name(), "_test.go") { | ||
return nil | ||
} | ||
|
||
partialResults := getConstructorsFromExpectedFile(t, path, info) | ||
|
||
constructorsPerPackage = mergeConstructors(constructorsPerPackage, partialResults) | ||
|
||
partialResults = getCatalogerConstructorsFromPackage(t, path, info) | ||
|
||
constructorsPerPackage = mergeConstructors(constructorsPerPackage, partialResults) | ||
|
||
return nil | ||
}) | ||
|
||
require.NoError(t, err) | ||
|
||
// remove some exceptions | ||
delete(constructorsPerPackage, "generic") // this is not an actual cataloger | ||
|
||
return constructorsPerPackage | ||
} | ||
|
||
func getConstructorsFromExpectedFile(t *testing.T, path string, info os.FileInfo) map[string][]string { | ||
constructorsPerPackage := make(map[string][]string) | ||
|
||
if !strings.HasSuffix(info.Name(), "cataloger.go") && !strings.HasSuffix(info.Name(), "catalogers.go") { | ||
return nil | ||
} | ||
|
||
fset := token.NewFileSet() | ||
node, err := parser.ParseFile(fset, path, nil, parser.ParseComments) | ||
require.NoError(t, err) | ||
|
||
for _, f := range node.Decls { | ||
fn, ok := f.(*ast.FuncDecl) | ||
if !ok || fn.Recv != nil || !strings.HasPrefix(fn.Name.Name, "New") { | ||
continue | ||
} | ||
|
||
pkg := node.Name.Name | ||
constructorsPerPackage[pkg] = append(constructorsPerPackage[pkg], fn.Name.Name) | ||
} | ||
|
||
return constructorsPerPackage | ||
} | ||
|
||
func getCatalogerConstructorsFromPackage(t *testing.T, path string, info os.FileInfo) map[string][]string { | ||
constructorsPerPackage := make(map[string][]string) | ||
|
||
if info.IsDir() || !strings.HasSuffix(info.Name(), ".go") { | ||
return nil | ||
} | ||
|
||
fset := token.NewFileSet() | ||
node, err := parser.ParseFile(fset, path, nil, parser.ParseComments) | ||
require.NoError(t, err) | ||
|
||
for _, f := range node.Decls { | ||
fn, ok := f.(*ast.FuncDecl) | ||
if !ok || fn.Recv != nil || !strings.HasPrefix(fn.Name.Name, "New") || !strings.HasSuffix(fn.Name.Name, "Cataloger") { | ||
continue | ||
} | ||
|
||
pkg := node.Name.Name | ||
constructorsPerPackage[pkg] = append(constructorsPerPackage[pkg], fn.Name.Name) | ||
} | ||
|
||
return constructorsPerPackage | ||
} | ||
|
||
func assertAllPackageCatalogersRepresented(t *testing.T, constructorsPerPackage map[string]*strset.Set) { | ||
t.Helper() | ||
|
||
contents, err := os.ReadFile(filepath.Join(repoRoot(t), "internal", "task", "package_tasks.go")) | ||
require.NoError(t, err) | ||
|
||
// ensure all packages (keys) are represented in the package_tasks.go file | ||
for pkg, constructors := range constructorsPerPackage { | ||
if !assert.True(t, bytes.Contains(contents, []byte(pkg)), "missing package %q", pkg) { | ||
continue | ||
} | ||
for _, constructor := range constructors.List() { | ||
assert.True(t, bytes.Contains(contents, []byte(constructor)), "missing constructor %q for package %q", constructor, pkg) | ||
} | ||
} | ||
|
||
} | ||
|
||
func repoRoot(t testing.TB) string { | ||
t.Helper() | ||
root, err := exec.Command("git", "rev-parse", "--show-toplevel").Output() | ||
if err != nil { | ||
t.Fatalf("unable to find repo root dir: %+v", err) | ||
} | ||
absRepoRoot, err := filepath.Abs(strings.TrimSpace(string(root))) | ||
if err != nil { | ||
t.Fatal("unable to get abs path to repo root:", err) | ||
} | ||
return absRepoRoot | ||
} | ||
|
||
func mergeConstructors(constructorsPerPackage map[string]*strset.Set, partialResults map[string][]string) map[string]*strset.Set { | ||
for pkg, constructors := range partialResults { | ||
if _, ok := constructorsPerPackage[pkg]; !ok { | ||
constructorsPerPackage[pkg] = strset.New() | ||
} | ||
constructorsPerPackage[pkg].Add(constructors...) | ||
} | ||
|
||
return constructorsPerPackage | ||
} |