From 16fb680b1552c4c0623ffbeed93947922fe4204f Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Wed, 29 Oct 2025 11:55:02 -0400 Subject: [PATCH] fix tests and linting Signed-off-by: Alex Goodman --- .../generate/cataloger_config_linking_test.go | 180 +++------------ .../generate/completeness_test.go | 52 ++++- .../generate/discover_app_config.go | 49 ++-- .../generate/discover_app_config_test.go | 46 ---- .../generate/discover_cataloger_configs.go | 19 +- .../discover_cataloger_configs_test.go | 216 +++++++++--------- .../generate/discover_catalogers.go | 14 +- .../generate/discover_catalogers_test.go | 92 ++++---- .../conflicting-names/duplicate/cataloger1.go | 19 -- .../conflicting-names/duplicate/cataloger2.go | 19 -- .../imported-config-type/kernel/cataloger.go | 15 -- .../mixed-naming-patterns/ruby/cataloger.go | 19 -- .../binary/cataloger.go | 17 -- .../rust/cataloger.go | 16 -- .../golang/cataloger.go | 19 -- .../cataloger}/python/cataloger.go | 8 +- .../cataloger/duplicate1/cataloger.go | 23 ++ .../cataloger/duplicate2/cataloger.go | 23 ++ .../cataloger}/dotnet/cataloger.go | 0 .../cataloger}/dotnet/types.go | 6 +- .../cataloger}/java/cataloger.go | 6 +- .../cataloger/kernel/cataloger.go | 19 ++ .../cataloger}/kernel/config.go | 0 .../cataloger/ruby/cataloger.go | 23 ++ .../cataloger}/javascript/cataloger.go | 6 +- .../cataloger/binary/cataloger.go | 21 ++ .../cataloger/rust/cataloger.go | 24 ++ .../cataloger/golang/cataloger.go | 23 ++ .../multiple-configs/cataloger/java/config.go | 8 + .../cataloger/python/config.go | 8 + .../nested-config/cataloger/golang/config.go | 22 ++ .../cataloger/javascript/config.go | 10 + .../simple-config/cataloger/golang/config.go | 16 ++ 33 files changed, 513 insertions(+), 525 deletions(-) delete mode 100644 internal/capabilities/generate/test-fixtures/config-linking/conflicting-names/duplicate/cataloger1.go delete mode 100644 internal/capabilities/generate/test-fixtures/config-linking/conflicting-names/duplicate/cataloger2.go delete mode 100644 internal/capabilities/generate/test-fixtures/config-linking/imported-config-type/kernel/cataloger.go delete mode 100644 internal/capabilities/generate/test-fixtures/config-linking/mixed-naming-patterns/ruby/cataloger.go delete mode 100644 internal/capabilities/generate/test-fixtures/config-linking/non-config-first-param/binary/cataloger.go delete mode 100644 internal/capabilities/generate/test-fixtures/config-linking/selector-expression-config/rust/cataloger.go delete mode 100644 internal/capabilities/generate/test-fixtures/config-linking/simple-generic-cataloger/golang/cataloger.go rename internal/capabilities/generate/{test-fixtures/config-linking/cataloger-with-constant => testdata/cataloger/cataloger-with-constant/cataloger}/python/cataloger.go (52%) create mode 100644 internal/capabilities/generate/testdata/cataloger/conflicting-names/cataloger/duplicate1/cataloger.go create mode 100644 internal/capabilities/generate/testdata/cataloger/conflicting-names/cataloger/duplicate2/cataloger.go rename internal/capabilities/generate/{test-fixtures/config-linking/custom-cataloger-different-file => testdata/cataloger/custom-cataloger-different-file/cataloger}/dotnet/cataloger.go (100%) rename internal/capabilities/generate/{test-fixtures/config-linking/custom-cataloger-different-file => testdata/cataloger/custom-cataloger-different-file/cataloger}/dotnet/types.go (58%) rename internal/capabilities/generate/{test-fixtures/config-linking/custom-cataloger-same-file => testdata/cataloger/custom-cataloger-same-file/cataloger}/java/cataloger.go (67%) create mode 100644 internal/capabilities/generate/testdata/cataloger/imported-config-type/cataloger/kernel/cataloger.go rename internal/capabilities/generate/{test-fixtures/config-linking/imported-config-type => testdata/cataloger/imported-config-type/cataloger}/kernel/config.go (100%) create mode 100644 internal/capabilities/generate/testdata/cataloger/mixed-naming-patterns/cataloger/ruby/cataloger.go rename internal/capabilities/generate/{test-fixtures/config-linking/no-config-cataloger => testdata/cataloger/no-config-cataloger/cataloger}/javascript/cataloger.go (54%) create mode 100644 internal/capabilities/generate/testdata/cataloger/non-config-first-param/cataloger/binary/cataloger.go create mode 100644 internal/capabilities/generate/testdata/cataloger/selector-expression-config/cataloger/rust/cataloger.go create mode 100644 internal/capabilities/generate/testdata/cataloger/simple-generic-cataloger/cataloger/golang/cataloger.go create mode 100644 internal/capabilities/generate/testdata/config-discovery/multiple-configs/cataloger/java/config.go create mode 100644 internal/capabilities/generate/testdata/config-discovery/multiple-configs/cataloger/python/config.go create mode 100644 internal/capabilities/generate/testdata/config-discovery/nested-config/cataloger/golang/config.go create mode 100644 internal/capabilities/generate/testdata/config-discovery/no-annotations/cataloger/javascript/config.go create mode 100644 internal/capabilities/generate/testdata/config-discovery/simple-config/cataloger/golang/config.go diff --git a/internal/capabilities/generate/cataloger_config_linking_test.go b/internal/capabilities/generate/cataloger_config_linking_test.go index aae014686..5b2bce7c6 100644 --- a/internal/capabilities/generate/cataloger_config_linking_test.go +++ b/internal/capabilities/generate/cataloger_config_linking_test.go @@ -10,128 +10,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestLinkCatalogersToConfigs(t *testing.T) { - if testing.Short() { - t.Skip("skipping integration test in short mode") - } - - repoRoot, err := RepoRoot() - require.NoError(t, err) - - linkages, err := LinkCatalogersToConfigs(repoRoot) - require.NoError(t, err) - - // verify we discovered multiple catalogers - require.NotEmpty(t, linkages, "should discover at least one cataloger linkage") - - // test cases for known catalogers with configs - // NOTE: Some catalogers may not be detected if their Name() method is in a different file - // than the constructor function. This is a known limitation. - tests := []struct { - catalogerName string - wantConfig string - optional bool // set to true if detection may not work due to cross-file Name() methods - }{ - { - catalogerName: "go-module-binary-cataloger", - wantConfig: "golang.CatalogerConfig", - }, - { - catalogerName: "go-module-file-cataloger", - wantConfig: "golang.CatalogerConfig", - }, - { - catalogerName: "python-package-cataloger", - wantConfig: "python.CatalogerConfig", - }, - { - catalogerName: "java-archive-cataloger", - wantConfig: "java.ArchiveCatalogerConfig", - }, - { - catalogerName: "java-pom-cataloger", - wantConfig: "java.ArchiveCatalogerConfig", - optional: true, // Name() method in different file - }, - { - catalogerName: "dotnet-deps-binary-cataloger", - wantConfig: "dotnet.CatalogerConfig", - optional: true, // Name() method in different file - }, - { - catalogerName: "javascript-lock-cataloger", - wantConfig: "javascript.CatalogerConfig", - }, - { - catalogerName: "linux-kernel-cataloger", - wantConfig: "kernel.LinuxKernelCatalogerConfig", - }, - { - catalogerName: "nix-cataloger", - wantConfig: "nix.Config", - optional: true, // Name() method in different file - }, - } - - for _, tt := range tests { - t.Run(tt.catalogerName, func(t *testing.T) { - config, ok := linkages[tt.catalogerName] - if tt.optional && !ok { - t.Skipf("cataloger %s not detected (expected due to cross-file Name() method)", tt.catalogerName) - return - } - require.True(t, ok, "should find linkage for cataloger: %s", tt.catalogerName) - require.Equal(t, tt.wantConfig, config, "config type should match for cataloger: %s", tt.catalogerName) - }) - } - - // test catalogers without configs (should have empty string) - catalogersWithoutConfig := []string{ - "python-installed-package-cataloger", - "java-gradle-lockfile-cataloger", - "java-jvm-cataloger", - "dotnet-packages-lock-cataloger", - "javascript-package-cataloger", - } - - for _, catalogerName := range catalogersWithoutConfig { - t.Run(catalogerName+"_no_config", func(t *testing.T) { - config, ok := linkages[catalogerName] - if ok { - require.Empty(t, config, "cataloger %s should have empty config", catalogerName) - } - }) - } - - // print summary for manual inspection - t.Logf("Discovered %d cataloger-to-config linkages:", len(linkages)) - - // separate into catalogers with and without configs - withConfig := make(map[string]string) - withoutConfig := make([]string, 0) - - for name, config := range linkages { - if config != "" { - withConfig[name] = config - } else { - withoutConfig = append(withoutConfig, name) - } - } - - t.Logf("Catalogers with configs (%d):", len(withConfig)) - for name, config := range withConfig { - t.Logf(" %s -> %s", name, config) - } - - t.Logf("Catalogers without configs (%d):", len(withoutConfig)) - for _, name := range withoutConfig { - t.Logf(" %s", name) - } - - // ensure we found at least some catalogers with configs - require.GreaterOrEqual(t, len(withConfig), 6, "should find at least 6 catalogers with configs") -} - func TestLinkCatalogersToConfigsFromPath(t *testing.T) { tests := []struct { name string @@ -161,8 +39,8 @@ func TestLinkCatalogersToConfigsFromPath(t *testing.T) { }, }, { - name: "custom cataloger with Name() in different file - not detected", - fixturePath: "custom-cataloger-different-file", + name: "custom cataloger with Name() in different file - not detected", + fixturePath: "custom-cataloger-different-file", expectedLinkages: map[string]string{ // empty - current limitation, cannot detect cross-file Names }, @@ -204,7 +82,7 @@ func TestLinkCatalogersToConfigsFromPath(t *testing.T) { name: "selector expression config", fixturePath: "selector-expression-config", expectedLinkages: map[string]string{ - "rust-cataloger": "cargo.CatalogerConfig", + "rust-cataloger": "rust.CatalogerConfig", }, }, } @@ -215,8 +93,9 @@ func TestLinkCatalogersToConfigsFromPath(t *testing.T) { tt.wantErr = require.NoError } - fixtureDir := filepath.Join("test-fixtures", "config-linking", tt.fixturePath) - linkages, err := LinkCatalogersToConfigsFromPath(fixtureDir, fixtureDir) + fixtureDir := filepath.Join("testdata", "cataloger", tt.fixturePath) + catalogerRoot := filepath.Join(fixtureDir, "cataloger") + linkages, err := LinkCatalogersToConfigsFromPath(catalogerRoot, fixtureDir) tt.wantErr(t, err) if err != nil { @@ -229,51 +108,64 @@ func TestLinkCatalogersToConfigsFromPath(t *testing.T) { } func TestExtractConfigTypeName(t *testing.T) { - if testing.Short() { - t.Skip("skipping integration test in short mode") - } - tests := []struct { name string + fixturePath string catalogerName string expectedConfig string expectedNoConfig bool }{ { name: "golang config", - catalogerName: "go-module-binary-cataloger", + fixturePath: "simple-generic-cataloger", + catalogerName: "go-module-cataloger", expectedConfig: "golang.CatalogerConfig", }, { - name: "python config", + name: "python config with constant", + fixturePath: "cataloger-with-constant", catalogerName: "python-package-cataloger", expectedConfig: "python.CatalogerConfig", }, { - name: "java archive config", - catalogerName: "java-archive-cataloger", + name: "java archive config same file", + fixturePath: "custom-cataloger-same-file", + catalogerName: "java-pom-cataloger", expectedConfig: "java.ArchiveCatalogerConfig", }, { - name: "kernel config", + name: "kernel config imported type", + fixturePath: "imported-config-type", catalogerName: "linux-kernel-cataloger", expectedConfig: "kernel.LinuxKernelCatalogerConfig", }, { - name: "python installed - no config", - catalogerName: "python-installed-package-cataloger", + name: "javascript - no config", + fixturePath: "no-config-cataloger", + catalogerName: "javascript-cataloger", expectedNoConfig: true, }, + { + name: "ruby with mixed naming", + fixturePath: "mixed-naming-patterns", + catalogerName: "ruby-cataloger", + expectedConfig: "ruby.Config", + }, + { + name: "rust with selector expression", + fixturePath: "selector-expression-config", + catalogerName: "rust-cataloger", + expectedConfig: "rust.CatalogerConfig", + }, } - repoRoot, err := RepoRoot() - require.NoError(t, err) - - linkages, err := LinkCatalogersToConfigs(repoRoot) - require.NoError(t, err) - for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + fixtureDir := filepath.Join("testdata", "cataloger", tt.fixturePath) + catalogerRoot := filepath.Join(fixtureDir, "cataloger") + linkages, err := LinkCatalogersToConfigsFromPath(catalogerRoot, fixtureDir) + require.NoError(t, err) + config, ok := linkages[tt.catalogerName] if tt.expectedNoConfig { diff --git a/internal/capabilities/generate/completeness_test.go b/internal/capabilities/generate/completeness_test.go index ba11708d7..841d15a1b 100644 --- a/internal/capabilities/generate/completeness_test.go +++ b/internal/capabilities/generate/completeness_test.go @@ -58,6 +58,8 @@ var observationExceptions = map[string]*strset.Set{ "linux-kernel-cataloger": strset.New("relationships"), } +// TestCatalogersInSync ensures that all catalogers from the syft binary are documented in packages.yaml +// and vice versa, and that all capability fields are properly filled without TODOs or null values. func TestCatalogersInSync(t *testing.T) { // get canonical list from syft binary catalogersInBinary := getCatalogerNamesFromBinary(t) @@ -141,6 +143,8 @@ func validateCapabilitiesFilled(t *testing.T, catalogers []capabilities.Cataloge } } +// TestPackageTypeCoverage ensures that every package type defined in pkg.AllPkgs is represented in at least +// one cataloger's capabilities, preventing orphaned package types that are defined but never documented. func TestPackageTypeCoverage(t *testing.T) { // load catalogers from embedded YAML catalogerEntries, err := capabilities.Packages() @@ -184,6 +188,8 @@ func TestPackageTypeCoverage(t *testing.T) { missingTypesWithoutExceptions) } +// TestMetadataTypeCoverage ensures that every metadata type defined in packagemetadata.AllTypes() is represented +// in at least one cataloger's capabilities, preventing orphaned metadata types that are defined but never produced. func TestMetadataTypeCoverage(t *testing.T) { // load catalogers from embedded YAML catalogerEntries, err := capabilities.Packages() @@ -231,6 +237,9 @@ func TestMetadataTypeCoverage(t *testing.T) { missingTypesWithoutExceptions) } +// TestCatalogerStructure validates that catalogers follow structural conventions: generic catalogers must have +// parsers and parser-level capabilities, custom catalogers must have detectors and cataloger-level capabilities, +// and all catalogers must have an ecosystem set. func TestCatalogerStructure(t *testing.T) { // load catalogers from embedded YAML catalogerEntries, err := capabilities.Packages() @@ -269,6 +278,8 @@ func TestCatalogerStructure(t *testing.T) { } } +// TestCatalogerDataQuality checks for data integrity issues in packages.yaml, including duplicate cataloger +// names, duplicate parser functions within catalogers, and validates that detector definitions are well-formed. func TestCatalogerDataQuality(t *testing.T) { // load catalogers from embedded YAML catalogerEntries, err := capabilities.Packages() @@ -345,7 +356,8 @@ func TestCatalogerDataQuality(t *testing.T) { }) } -// TestCapabilitiesAreUpToDate verifies that regeneration runs successfully +// TestCapabilitiesAreUpToDate verifies that packages.yaml is up to date by running regeneration and checking +// for uncommitted changes. This test only runs in CI to catch cases where code changed but capabilities weren't regenerated. func TestCapabilitiesAreUpToDate(t *testing.T) { if os.Getenv("CI") == "" { t.Skip("skipping regeneration test in local environment") @@ -367,8 +379,9 @@ func TestCapabilitiesAreUpToDate(t *testing.T) { require.NoError(t, err, "packages.yaml has uncommitted changes after regeneration. Run 'go generate ./internal/capabilities' locally and commit the changes.") } -// TestCatalogersHaveTestObservations verifies that all catalogers have test observations, -// ensuring they are using the pkgtest helpers +// TestCatalogersHaveTestObservations ensures that all custom catalogers (and optionally parsers) have +// test observations recorded in test-fixtures/test-observations.json, which proves they are using the +// pkgtest.CatalogTester helpers and have test coverage. func TestCatalogersHaveTestObservations(t *testing.T) { repoRoot, err := RepoRoot() require.NoError(t, err) @@ -486,6 +499,9 @@ func extractPackageName(catalogerName string) string { return catalogerName } +// TestConfigCompleteness validates the integrity of config references in packages.yaml, ensuring that all +// configs in the configs section are referenced by at least one cataloger, all cataloger config references exist, +// and all app-key references in config fields exist in the application section. func TestConfigCompleteness(t *testing.T) { repoRoot, err := RepoRoot() require.NoError(t, err) @@ -543,6 +559,8 @@ func TestConfigCompleteness(t *testing.T) { } } +// TestAppConfigFieldsHaveDescriptions ensures that all application config fields discovered from the +// options package have descriptions, which are required for user-facing documentation. func TestAppConfigFieldsHaveDescriptions(t *testing.T) { repoRoot, err := RepoRoot() require.NoError(t, err) @@ -561,6 +579,8 @@ func TestAppConfigFieldsHaveDescriptions(t *testing.T) { require.Empty(t, missingDescriptions, "the following configs are missing descriptions: %v", missingDescriptions) } +// TestAppConfigKeyFormat validates that all application config keys follow the expected naming convention +// of "ecosystem.field-name" using kebab-case (lowercase with hyphens, no underscores or spaces). func TestAppConfigKeyFormat(t *testing.T) { repoRoot, err := RepoRoot() require.NoError(t, err) @@ -579,8 +599,9 @@ func TestAppConfigKeyFormat(t *testing.T) { } } -// TestCapabilityConfigFieldReferences validates that config field names referenced in CapabilitiesV2 -// conditions actually exist in the cataloger's config struct +// TestCapabilityConfigFieldReferences validates that config field names referenced in capability conditions +// actually exist in the cataloger's config struct, preventing typos and ensuring capability conditions can +// be properly evaluated at runtime. func TestCapabilityConfigFieldReferences(t *testing.T) { repoRoot, err := RepoRoot() require.NoError(t, err) @@ -697,7 +718,9 @@ func TestCapabilityConfigFieldReferences(t *testing.T) { } } -// TestCapabilityFieldNaming validates that capability field names follow known patterns +// TestCapabilityFieldNaming validates that all capability field names follow known patterns +// (e.g., "license", "dependency.depth", "package_manager.files.listing"), catching typos and ensuring +// consistency across catalogers. func TestCapabilityFieldNaming(t *testing.T) { repoRoot, err := RepoRoot() require.NoError(t, err) @@ -754,7 +777,9 @@ func TestCapabilityFieldNaming(t *testing.T) { } } -// TestCapabilityValueTypes validates that capability field values match expected types +// TestCapabilityValueTypes validates that capability field values match their expected types based on the +// field name (e.g., boolean fields like "license" must have bool values, array fields like "dependency.depth" +// must have []string values), preventing type mismatches that would cause runtime errors. func TestCapabilityValueTypes(t *testing.T) { repoRoot, err := RepoRoot() require.NoError(t, err) @@ -870,8 +895,9 @@ func validateCapabilityValueType(fieldPath string, value interface{}) error { return nil } -// TestMetadataTypesHaveJSONSchemaTypes validates that metadata_types and json_schema_types are synchronized -// in packages.yaml - every metadata type should have a corresponding json_schema_type with correct conversion +// TestMetadataTypesHaveJSONSchemaTypes validates that metadata_types and json_schema_types arrays are synchronized +// in packages.yaml, ensuring every metadata type (e.g., "pkg.AlpmDBEntry") has a corresponding json_schema_type +// (e.g., "AlpmDbEntry") with correct conversion, which is required for JSON schema generation. func TestMetadataTypesHaveJSONSchemaTypes(t *testing.T) { repoRoot, err := RepoRoot() require.NoError(t, err) @@ -1230,7 +1256,8 @@ func validateFieldPath(repoRoot, structName string, fieldPath []string) error { } // TestCapabilityEvidenceFieldReferences validates that evidence field references in capabilities -// actually exist on their corresponding metadata structs +// (e.g., "AlpmDBEntry.Files[].Digests") actually exist on their corresponding metadata structs by using +// AST parsing to verify the field paths, preventing broken references when structs are refactored. func TestCapabilityEvidenceFieldReferences(t *testing.T) { repoRoot, err := RepoRoot() require.NoError(t, err) @@ -1305,8 +1332,9 @@ func TestCapabilityEvidenceFieldReferences(t *testing.T) { } } -// TestDetectorConfigFieldReferences validates that config field names referenced in detector -// conditions actually exist in the cataloger's config struct +// TestDetectorConfigFieldReferences validates that config field names referenced in detector conditions +// actually exist in the cataloger's config struct, ensuring that conditional detectors can properly +// evaluate their activation conditions based on configuration. func TestDetectorConfigFieldReferences(t *testing.T) { repoRoot, err := RepoRoot() require.NoError(t, err) diff --git a/internal/capabilities/generate/discover_app_config.go b/internal/capabilities/generate/discover_app_config.go index 8c14b7a02..0604d4c6d 100644 --- a/internal/capabilities/generate/discover_app_config.go +++ b/internal/capabilities/generate/discover_app_config.go @@ -156,25 +156,25 @@ func extractConfigStructTypes(filePath string) ([]string, error) { // 1. Finding files with cataloger imports in options directory // 2. Extracting ecosystem config fields from Catalog struct // 3. Matching file structs against Catalog fields -// Returns a map of file path to top-level YAML key -func discoverCatalogerConfigs(repoRoot string) (map[string]string, error) { +// Returns a map of file path to top-level YAML key and a reverse lookup map of YAML key to struct name +func discoverCatalogerConfigs(repoRoot string) (map[string]string, map[string]string, error) { optionsDir := filepath.Join(repoRoot, "cmd", "syft", "internal", "options") catalogFilePath := filepath.Join(optionsDir, "catalog.go") // get ecosystem config fields from Catalog struct ecosystemConfigs, err := extractEcosystemConfigFieldsFromCatalog(catalogFilePath) if err != nil { - return nil, err + return nil, nil, err } if len(ecosystemConfigs) == 0 { - return nil, fmt.Errorf("no ecosystem config fields found in Catalog struct") + return nil, nil, fmt.Errorf("no ecosystem config fields found in Catalog struct") } // find files with cataloger imports candidateFiles, err := findFilesWithCatalogerImports(optionsDir) if err != nil { - return nil, err + return nil, nil, err } // match candidate files against Catalog ecosystem fields @@ -184,7 +184,7 @@ func discoverCatalogerConfigs(repoRoot string) (map[string]string, error) { for _, filePath := range candidateFiles { structTypes, err := extractConfigStructTypes(filePath) if err != nil { - return nil, err + return nil, nil, err } // check if any struct type matches an ecosystem config @@ -207,17 +207,23 @@ func discoverCatalogerConfigs(repoRoot string) (map[string]string, error) { if len(missingConfigs) > 0 { sort.Strings(missingConfigs) - return nil, fmt.Errorf("could not find files for ecosystem configs: %s", strings.Join(missingConfigs, ", ")) + return nil, nil, fmt.Errorf("could not find files for ecosystem configs: %s", strings.Join(missingConfigs, ", ")) } - return fileToKey, nil + // build reverse lookup map (yamlKey -> structName) + keyToStruct := make(map[string]string) + for structName, yamlKey := range ecosystemConfigs { + keyToStruct[yamlKey] = structName + } + + return fileToKey, keyToStruct, nil } // DiscoverAppConfigs discovers all application-level cataloger configuration fields // from the options package func DiscoverAppConfigs(repoRoot string) ([]AppConfigField, error) { // discover cataloger config files dynamically - configFiles, err := discoverCatalogerConfigs(repoRoot) + configFiles, keyToStruct, err := discoverCatalogerConfigs(repoRoot) if err != nil { return nil, fmt.Errorf("failed to discover cataloger configs: %w", err) } @@ -225,7 +231,7 @@ func DiscoverAppConfigs(repoRoot string) ([]AppConfigField, error) { // extract configuration fields from each discovered file var configs []AppConfigField for filePath, topLevelKey := range configFiles { - fields, err := extractAppConfigFields(filePath, topLevelKey) + fields, err := extractAppConfigFields(filePath, topLevelKey, keyToStruct) if err != nil { return nil, fmt.Errorf("failed to extract config from %s: %w", filePath, err) } @@ -241,7 +247,7 @@ func DiscoverAppConfigs(repoRoot string) ([]AppConfigField, error) { } // extractAppConfigFields extracts config fields from an options file -func extractAppConfigFields(filePath, topLevelKey string) ([]AppConfigField, error) { +func extractAppConfigFields(filePath, topLevelKey string, keyToStruct map[string]string) ([]AppConfigField, error) { fset := token.NewFileSet() f, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments) if err != nil { @@ -251,7 +257,7 @@ func extractAppConfigFields(filePath, topLevelKey string) ([]AppConfigField, err var configs []AppConfigField // find the main config struct (not nested ones) - configStruct, descriptions := findAppConfigStructAndDescriptions(f, topLevelKey) + configStruct, descriptions := findAppConfigStructAndDescriptions(f, topLevelKey, keyToStruct) if configStruct == nil { return nil, fmt.Errorf("no config struct found in %s", filePath) } @@ -302,26 +308,13 @@ func extractAppConfigFields(filePath, topLevelKey string) ([]AppConfigField, err // findAppConfigStructAndDescriptions finds the main config struct and extracts field descriptions // from the DescribeFields method -func findAppConfigStructAndDescriptions(f *ast.File, topLevelKey string) (*ast.StructType, map[string]string) { - expectedName := determineExpectedConfigName(topLevelKey) - configStruct := findConfigStruct(f, expectedName) +func findAppConfigStructAndDescriptions(f *ast.File, topLevelKey string, keyToStruct map[string]string) (*ast.StructType, map[string]string) { + structName := keyToStruct[topLevelKey] + configStruct := findConfigStruct(f, structName) descriptions := extractDescriptionsFromDescribeFields(f) return configStruct, descriptions } -// determineExpectedConfigName maps the top-level key to the expected config struct name -func determineExpectedConfigName(topLevelKey string) string { - // handle special cases first - switch topLevelKey { - case "linux-kernel": - return "linuxKernelConfig" - case "javascript": - return "javaScriptConfig" - default: - return topLevelKey + "Config" - } -} - // findConfigStruct searches for the config struct with the expected name in the AST func findConfigStruct(f *ast.File, expectedName string) *ast.StructType { for _, decl := range f.Decls { diff --git a/internal/capabilities/generate/discover_app_config_test.go b/internal/capabilities/generate/discover_app_config_test.go index de353571f..8d5729599 100644 --- a/internal/capabilities/generate/discover_app_config_test.go +++ b/internal/capabilities/generate/discover_app_config_test.go @@ -9,52 +9,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestDetermineExpectedConfigName(t *testing.T) { - tests := []struct { - name string - topLevelKey string - wantName string - }{ - { - name: "linux-kernel special case", - topLevelKey: "linux-kernel", - wantName: "linuxKernelConfig", - }, - { - name: "javascript special case", - topLevelKey: "javascript", - wantName: "javaScriptConfig", - }, - { - name: "standard config golang", - topLevelKey: "golang", - wantName: "golangConfig", - }, - { - name: "standard config python", - topLevelKey: "python", - wantName: "pythonConfig", - }, - { - name: "standard config java", - topLevelKey: "java", - wantName: "javaConfig", - }, - { - name: "standard config dotnet", - topLevelKey: "dotnet", - wantName: "dotnetConfig", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := determineExpectedConfigName(tt.topLevelKey) - require.Equal(t, tt.wantName, got) - }) - } -} - func TestCleanDescription(t *testing.T) { tests := []struct { name string diff --git a/internal/capabilities/generate/discover_cataloger_configs.go b/internal/capabilities/generate/discover_cataloger_configs.go index f1d6edf2f..174694f31 100644 --- a/internal/capabilities/generate/discover_cataloger_configs.go +++ b/internal/capabilities/generate/discover_cataloger_configs.go @@ -33,8 +33,13 @@ var appConfigAnnotationPattern = regexp.MustCompile(`^//\s*app-config:\s*(.+)$`) // Returns map where key is "packageName.StructName" (e.g., "golang.CatalogerConfig") func DiscoverConfigs(repoRoot string) (map[string]ConfigInfo, error) { catalogerRoot := filepath.Join(repoRoot, "syft", "pkg", "cataloger") + return DiscoverConfigsFromPath(catalogerRoot) +} - // find all .go files under syft/pkg/cataloger/ recursively +// DiscoverConfigsFromPath walks the given directory and discovers all configuration structs +// Returns map where key is "packageName.StructName" (e.g., "golang.CatalogerConfig") +func DiscoverConfigsFromPath(catalogerRoot string) (map[string]ConfigInfo, error) { + // find all .go files under the directory recursively var files []string err := filepath.Walk(catalogerRoot, func(path string, info os.FileInfo, err error) error { if err != nil { @@ -52,7 +57,7 @@ func DiscoverConfigs(repoRoot string) (map[string]ConfigInfo, error) { discovered := make(map[string]ConfigInfo) for _, file := range files { - configs, err := discoverConfigsInFile(file, repoRoot) + configs, err := discoverConfigsInFile(file) if err != nil { return nil, fmt.Errorf("failed to parse %s: %w", file, err) } @@ -68,19 +73,15 @@ func DiscoverConfigs(repoRoot string) (map[string]ConfigInfo, error) { return discovered, nil } -func discoverConfigsInFile(path, repoRoot string) (map[string]ConfigInfo, error) { +func discoverConfigsInFile(path string) (map[string]ConfigInfo, error) { fset := token.NewFileSet() f, err := parser.ParseFile(fset, path, nil, parser.ParseComments) if err != nil { return nil, err } - // extract package name from file path - relPath, err := filepath.Rel(repoRoot, path) - if err != nil { - relPath = path - } - packageName := extractPackageNameFromPath(relPath) + // extract package name from file path (use absolute path, not relative) + packageName := extractPackageNameFromPath(path) if packageName == "" { return nil, nil } diff --git a/internal/capabilities/generate/discover_cataloger_configs_test.go b/internal/capabilities/generate/discover_cataloger_configs_test.go index fadcae887..50a0765c5 100644 --- a/internal/capabilities/generate/discover_cataloger_configs_test.go +++ b/internal/capabilities/generate/discover_cataloger_configs_test.go @@ -2,129 +2,131 @@ package main import ( "go/ast" + "path/filepath" "testing" - "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/require" ) -// expected config structs that should be discovered with app-config annotations -var expectedCatalogConfigs = []string{ - "golang.CatalogerConfig", - "golang.MainModuleVersionConfig", - "java.ArchiveCatalogerConfig", - "python.CatalogerConfig", - "dotnet.CatalogerConfig", - "kernel.LinuxKernelCatalogerConfig", - "javascript.CatalogerConfig", - "nix.Config", -} - func TestDiscoverConfigs(t *testing.T) { - repoRoot, err := RepoRoot() - require.NoError(t, err) + tests := []struct { + name string + fixturePath string + expectedConfigs []string + verifyConfig func(t *testing.T, configs map[string]ConfigInfo) + }{ + { + name: "simple config with annotations", + fixturePath: "simple-config", + expectedConfigs: []string{ + "golang.CatalogerConfig", + }, + verifyConfig: func(t *testing.T, configs map[string]ConfigInfo) { + golangConfig := configs["golang.CatalogerConfig"] + require.Equal(t, "golang", golangConfig.PackageName) + require.Equal(t, "CatalogerConfig", golangConfig.StructName) + require.Len(t, golangConfig.Fields, 3, "should have 3 annotated fields") - configs, err := DiscoverConfigs(repoRoot) - require.NoError(t, err) + // verify specific field + var foundSearchLocalModCache bool + for _, field := range golangConfig.Fields { + if field.Name == "SearchLocalModCacheLicenses" { + foundSearchLocalModCache = true + require.Equal(t, "bool", field.Type) + require.Equal(t, "golang.search-local-mod-cache-licenses", field.AppKey) + require.Contains(t, field.Description, "searching for go package licenses") + } + } + require.True(t, foundSearchLocalModCache, "should find SearchLocalModCacheLicenses field") + }, + }, + { + name: "nested config struct", + fixturePath: "nested-config", + expectedConfigs: []string{ + "golang.CatalogerConfig", + "golang.MainModuleVersionConfig", + }, + verifyConfig: func(t *testing.T, configs map[string]ConfigInfo) { + // verify main config + golangConfig := configs["golang.CatalogerConfig"] + require.Equal(t, "golang", golangConfig.PackageName) + require.Equal(t, "CatalogerConfig", golangConfig.StructName) - // verify we discovered multiple config structs - require.NotEmpty(t, configs, "should discover at least one config struct") + // verify nested config + mainModuleConfig := configs["golang.MainModuleVersionConfig"] + require.Equal(t, "golang", mainModuleConfig.PackageName) + require.Equal(t, "MainModuleVersionConfig", mainModuleConfig.StructName) + require.Len(t, mainModuleConfig.Fields, 2, "should have 2 annotated fields") - // check for known config structs that have app-config annotations - for _, expected := range expectedCatalogConfigs { - config, ok := configs[expected] - require.True(t, ok, "should discover config: %s", expected) - require.NotEmpty(t, config.Fields, "config %s should have fields", expected) - require.Equal(t, expected, config.PackageName+"."+config.StructName) + // check for specific nested field + var foundFromLDFlags bool + for _, field := range mainModuleConfig.Fields { + if field.Name == "FromLDFlags" { + foundFromLDFlags = true + require.Equal(t, "bool", field.Type) + require.Equal(t, "golang.main-module-version.from-ld-flags", field.AppKey) + require.Contains(t, field.Description, "extract version from LD flags") + } + } + require.True(t, foundFromLDFlags, "should find FromLDFlags field") + }, + }, + { + name: "multiple configs in different packages", + fixturePath: "multiple-configs", + expectedConfigs: []string{ + "python.CatalogerConfig", + "java.ArchiveCatalogerConfig", + }, + verifyConfig: func(t *testing.T, configs map[string]ConfigInfo) { + // verify python config + pythonConfig := configs["python.CatalogerConfig"] + require.Equal(t, "python", pythonConfig.PackageName) + require.Len(t, pythonConfig.Fields, 1) + + // verify java config + javaConfig := configs["java.ArchiveCatalogerConfig"] + require.Equal(t, "java", javaConfig.PackageName) + require.Len(t, javaConfig.Fields, 1) + }, + }, + { + name: "config without annotations", + fixturePath: "no-annotations", + expectedConfigs: []string{}, + verifyConfig: func(t *testing.T, configs map[string]ConfigInfo) { + // should not discover any configs without annotations + require.Empty(t, configs, "should not discover configs without app-config annotations") + }, + }, } - // verify golang.CatalogerConfig structure - golangConfig := configs["golang.CatalogerConfig"] - wantGolangConfig := ConfigInfo{ - PackageName: "golang", - StructName: "CatalogerConfig", - } - if diff := cmp.Diff(wantGolangConfig.PackageName, golangConfig.PackageName); diff != "" { - t.Errorf("golang.CatalogerConfig.PackageName mismatch (-want +got):\n%s", diff) - } - if diff := cmp.Diff(wantGolangConfig.StructName, golangConfig.StructName); diff != "" { - t.Errorf("golang.CatalogerConfig.StructName mismatch (-want +got):\n%s", diff) - } - require.NotEmpty(t, golangConfig.Fields) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fixtureDir := filepath.Join("testdata", "config-discovery", tt.fixturePath, "cataloger") + configs, err := DiscoverConfigsFromPath(fixtureDir) + require.NoError(t, err) - // check for specific field - var foundSearchLocalModCache bool - for _, field := range golangConfig.Fields { - if field.Name == "SearchLocalModCacheLicenses" { - foundSearchLocalModCache = true - wantField := ConfigField{ - Name: "SearchLocalModCacheLicenses", - Type: "bool", - AppKey: "golang.search-local-mod-cache-licenses", + // Debug: log what was discovered + t.Logf("Discovered %d configs:", len(configs)) + for key, config := range configs { + t.Logf(" %s: %d fields", key, len(config.Fields)) } - if diff := cmp.Diff(wantField.Name, field.Name); diff != "" { - t.Errorf("SearchLocalModCacheLicenses field Name mismatch (-want +got):\n%s", diff) - } - if diff := cmp.Diff(wantField.Type, field.Type); diff != "" { - t.Errorf("SearchLocalModCacheLicenses field Type mismatch (-want +got):\n%s", diff) - } - if diff := cmp.Diff(wantField.AppKey, field.AppKey); diff != "" { - t.Errorf("SearchLocalModCacheLicenses field AppKey mismatch (-want +got):\n%s", diff) - } - require.NotEmpty(t, field.Description) - require.Contains(t, field.Description, "searching for go package licenses") - } - } - require.True(t, foundSearchLocalModCache, "should find SearchLocalModCacheLicenses field") - // verify nested config struct - golangMainModuleConfig := configs["golang.MainModuleVersionConfig"] - wantMainModuleConfig := ConfigInfo{ - PackageName: "golang", - StructName: "MainModuleVersionConfig", - } - if diff := cmp.Diff(wantMainModuleConfig.PackageName, golangMainModuleConfig.PackageName); diff != "" { - t.Errorf("golang.MainModuleVersionConfig.PackageName mismatch (-want +got):\n%s", diff) - } - if diff := cmp.Diff(wantMainModuleConfig.StructName, golangMainModuleConfig.StructName); diff != "" { - t.Errorf("golang.MainModuleVersionConfig.StructName mismatch (-want +got):\n%s", diff) - } - require.NotEmpty(t, golangMainModuleConfig.Fields) + // verify expected configs were discovered + for _, expected := range tt.expectedConfigs { + config, ok := configs[expected] + require.True(t, ok, "should discover config: %s", expected) + require.NotEmpty(t, config.Fields, "config %s should have fields", expected) + require.Equal(t, expected, config.PackageName+"."+config.StructName) + } - // check for specific nested field - var foundFromLDFlags bool - for _, field := range golangMainModuleConfig.Fields { - if field.Name == "FromLDFlags" { - foundFromLDFlags = true - wantField := ConfigField{ - Name: "FromLDFlags", - Type: "bool", - AppKey: "golang.main-module-version.from-ld-flags", + // run custom verification + if tt.verifyConfig != nil { + tt.verifyConfig(t, configs) } - if diff := cmp.Diff(wantField.Name, field.Name); diff != "" { - t.Errorf("FromLDFlags field Name mismatch (-want +got):\n%s", diff) - } - if diff := cmp.Diff(wantField.Type, field.Type); diff != "" { - t.Errorf("FromLDFlags field Type mismatch (-want +got):\n%s", diff) - } - if diff := cmp.Diff(wantField.AppKey, field.AppKey); diff != "" { - t.Errorf("FromLDFlags field AppKey mismatch (-want +got):\n%s", diff) - } - require.NotEmpty(t, field.Description) - } - } - require.True(t, foundFromLDFlags, "should find FromLDFlags field in MainModuleVersionConfig") - - // print summary for manual inspection - t.Logf("Discovered %d config structs:", len(configs)) - for key, config := range configs { - t.Logf(" %s: %d fields", key, len(config.Fields)) - for _, field := range config.Fields { - t.Logf(" - %s (%s): %s", field.Name, field.Type, field.AppKey) - if diff := cmp.Diff("", field.Description); diff == "" { - t.Logf(" WARNING: field %s has no description", field.Name) - } - } + }) } } diff --git a/internal/capabilities/generate/discover_catalogers.go b/internal/capabilities/generate/discover_catalogers.go index f66618875..bf4f7c63b 100644 --- a/internal/capabilities/generate/discover_catalogers.go +++ b/internal/capabilities/generate/discover_catalogers.go @@ -215,14 +215,20 @@ func parseGenericCatalogerFunction(funcDecl *ast.FuncDecl, filePath, repoRoot st func extractPackageNameFromPath(filePath string) string { parts := strings.Split(filePath, string(filepath.Separator)) - // find the index of "cataloger" in the path + // find the LAST occurrence of "cataloger" in the path + // (to handle test fixtures with multiple "cataloger" segments) + lastCatalogerIndex := -1 for i, part := range parts { - if part == "cataloger" && i+1 < len(parts) { - // return the next segment after "cataloger" - return parts[i+1] + if part == "cataloger" { + lastCatalogerIndex = i } } + if lastCatalogerIndex != -1 && lastCatalogerIndex+1 < len(parts) { + // return the next segment after the last "cataloger" + return parts[lastCatalogerIndex+1] + } + return "" } diff --git a/internal/capabilities/generate/discover_catalogers_test.go b/internal/capabilities/generate/discover_catalogers_test.go index 0f0c5d605..0672e9f6f 100644 --- a/internal/capabilities/generate/discover_catalogers_test.go +++ b/internal/capabilities/generate/discover_catalogers_test.go @@ -9,52 +9,6 @@ import ( "github.com/stretchr/testify/require" ) -// test helper functions - -// parseFuncDecl parses a function declaration from a code string -func parseFuncDecl(t *testing.T, code string) *ast.FuncDecl { - t.Helper() - fset := token.NewFileSet() - file, err := parser.ParseFile(fset, "", "package test\n"+code, 0) - require.NoError(t, err) - require.Len(t, file.Decls, 1, "expected exactly one declaration") - funcDecl, ok := file.Decls[0].(*ast.FuncDecl) - require.True(t, ok, "expected declaration to be a function") - return funcDecl -} - -// parseCallExpr parses a call expression from a code string -func parseCallExpr(t *testing.T, code string) *ast.CallExpr { - t.Helper() - expr, err := parser.ParseExpr(code) - require.NoError(t, err) - callExpr, ok := expr.(*ast.CallExpr) - require.True(t, ok, "expected expression to be a call expression") - return callExpr -} - -// parseCompositeLit parses a composite literal from a code string -func parseCompositeLit(t *testing.T, code string) *ast.CompositeLit { - t.Helper() - expr, err := parser.ParseExpr(code) - require.NoError(t, err) - lit, ok := expr.(*ast.CompositeLit) - require.True(t, ok, "expected expression to be a composite literal") - return lit -} - -// parseConstDecl parses a const declaration from a code string and returns the GenDecl -func parseConstDecl(t *testing.T, code string) *ast.GenDecl { - t.Helper() - fset := token.NewFileSet() - file, err := parser.ParseFile(fset, "", "package test\n"+code, 0) - require.NoError(t, err) - require.Len(t, file.Decls, 1, "expected exactly one declaration") - genDecl, ok := file.Decls[0].(*ast.GenDecl) - require.True(t, ok, "expected declaration to be a general declaration") - return genDecl -} - func TestReturnsPackageCataloger(t *testing.T) { tests := []struct { name string @@ -387,3 +341,49 @@ func TestResolveImportPath(t *testing.T) { }) } } + +// test helper functions + +// parseFuncDecl parses a function declaration from a code string +func parseFuncDecl(t *testing.T, code string) *ast.FuncDecl { + t.Helper() + fset := token.NewFileSet() + file, err := parser.ParseFile(fset, "", "package test\n"+code, 0) + require.NoError(t, err) + require.Len(t, file.Decls, 1, "expected exactly one declaration") + funcDecl, ok := file.Decls[0].(*ast.FuncDecl) + require.True(t, ok, "expected declaration to be a function") + return funcDecl +} + +// parseCallExpr parses a call expression from a code string +func parseCallExpr(t *testing.T, code string) *ast.CallExpr { + t.Helper() + expr, err := parser.ParseExpr(code) + require.NoError(t, err) + callExpr, ok := expr.(*ast.CallExpr) + require.True(t, ok, "expected expression to be a call expression") + return callExpr +} + +// parseCompositeLit parses a composite literal from a code string +func parseCompositeLit(t *testing.T, code string) *ast.CompositeLit { + t.Helper() + expr, err := parser.ParseExpr(code) + require.NoError(t, err) + lit, ok := expr.(*ast.CompositeLit) + require.True(t, ok, "expected expression to be a composite literal") + return lit +} + +// parseConstDecl parses a const declaration from a code string and returns the GenDecl +func parseConstDecl(t *testing.T, code string) *ast.GenDecl { + t.Helper() + fset := token.NewFileSet() + file, err := parser.ParseFile(fset, "", "package test\n"+code, 0) + require.NoError(t, err) + require.Len(t, file.Decls, 1, "expected exactly one declaration") + genDecl, ok := file.Decls[0].(*ast.GenDecl) + require.True(t, ok, "expected declaration to be a general declaration") + return genDecl +} diff --git a/internal/capabilities/generate/test-fixtures/config-linking/conflicting-names/duplicate/cataloger1.go b/internal/capabilities/generate/test-fixtures/config-linking/conflicting-names/duplicate/cataloger1.go deleted file mode 100644 index 496172064..000000000 --- a/internal/capabilities/generate/test-fixtures/config-linking/conflicting-names/duplicate/cataloger1.go +++ /dev/null @@ -1,19 +0,0 @@ -package duplicate - -import ( - "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/pkg/cataloger/generic" -) - -type Config1 struct { - Option1 bool -} - -func NewDuplicateCataloger1(cfg Config1) pkg.Cataloger { - return generic.NewCataloger("duplicate-cataloger"). - WithParserByGlobs(parse1, "**/*.txt") -} - -func parse1(path string, reader any) ([]pkg.Package, []pkg.Relationship, error) { - return nil, nil, nil -} diff --git a/internal/capabilities/generate/test-fixtures/config-linking/conflicting-names/duplicate/cataloger2.go b/internal/capabilities/generate/test-fixtures/config-linking/conflicting-names/duplicate/cataloger2.go deleted file mode 100644 index 0c563c99a..000000000 --- a/internal/capabilities/generate/test-fixtures/config-linking/conflicting-names/duplicate/cataloger2.go +++ /dev/null @@ -1,19 +0,0 @@ -package duplicate - -import ( - "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/pkg/cataloger/generic" -) - -type Config2 struct { - Option2 string -} - -func NewDuplicateCataloger2(cfg Config2) pkg.Cataloger { - return generic.NewCataloger("duplicate-cataloger"). - WithParserByGlobs(parse2, "**/*.json") -} - -func parse2(path string, reader any) ([]pkg.Package, []pkg.Relationship, error) { - return nil, nil, nil -} diff --git a/internal/capabilities/generate/test-fixtures/config-linking/imported-config-type/kernel/cataloger.go b/internal/capabilities/generate/test-fixtures/config-linking/imported-config-type/kernel/cataloger.go deleted file mode 100644 index 99b181854..000000000 --- a/internal/capabilities/generate/test-fixtures/config-linking/imported-config-type/kernel/cataloger.go +++ /dev/null @@ -1,15 +0,0 @@ -package kernel - -import ( - "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/pkg/cataloger/generic" -) - -func NewLinuxKernelCataloger(cfg LinuxKernelCatalogerConfig) pkg.Cataloger { - return generic.NewCataloger("linux-kernel-cataloger"). - WithParserByGlobs(parse, "**/vmlinuz") -} - -func parse(path string, reader any) ([]pkg.Package, []pkg.Relationship, error) { - return nil, nil, nil -} diff --git a/internal/capabilities/generate/test-fixtures/config-linking/mixed-naming-patterns/ruby/cataloger.go b/internal/capabilities/generate/test-fixtures/config-linking/mixed-naming-patterns/ruby/cataloger.go deleted file mode 100644 index cddb73dda..000000000 --- a/internal/capabilities/generate/test-fixtures/config-linking/mixed-naming-patterns/ruby/cataloger.go +++ /dev/null @@ -1,19 +0,0 @@ -package ruby - -import ( - "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/pkg/cataloger/generic" -) - -type Config struct { - Setting bool -} - -func NewRubyCataloger(opts Config) pkg.Cataloger { - return generic.NewCataloger("ruby-cataloger"). - WithParserByGlobs(parse, "**/Gemfile") -} - -func parse(path string, reader any) ([]pkg.Package, []pkg.Relationship, error) { - return nil, nil, nil -} diff --git a/internal/capabilities/generate/test-fixtures/config-linking/non-config-first-param/binary/cataloger.go b/internal/capabilities/generate/test-fixtures/config-linking/non-config-first-param/binary/cataloger.go deleted file mode 100644 index f2b32e082..000000000 --- a/internal/capabilities/generate/test-fixtures/config-linking/non-config-first-param/binary/cataloger.go +++ /dev/null @@ -1,17 +0,0 @@ -package binary - -import ( - "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/pkg/cataloger/generic" -) - -type Parser struct{} - -func NewBinaryCataloger(parser Parser) pkg.Cataloger { - return generic.NewCataloger("binary-cataloger"). - WithParserByGlobs(parse, "**/*") -} - -func parse(path string, reader any) ([]pkg.Package, []pkg.Relationship, error) { - return nil, nil, nil -} diff --git a/internal/capabilities/generate/test-fixtures/config-linking/selector-expression-config/rust/cataloger.go b/internal/capabilities/generate/test-fixtures/config-linking/selector-expression-config/rust/cataloger.go deleted file mode 100644 index 88a481e0c..000000000 --- a/internal/capabilities/generate/test-fixtures/config-linking/selector-expression-config/rust/cataloger.go +++ /dev/null @@ -1,16 +0,0 @@ -package rust - -import ( - "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/pkg/cataloger/generic" - "github.com/test/cargo" -) - -func NewRustCataloger(cfg cargo.CatalogerConfig) pkg.Cataloger { - return generic.NewCataloger("rust-cataloger"). - WithParserByGlobs(parse, "**/Cargo.toml") -} - -func parse(path string, reader any) ([]pkg.Package, []pkg.Relationship, error) { - return nil, nil, nil -} diff --git a/internal/capabilities/generate/test-fixtures/config-linking/simple-generic-cataloger/golang/cataloger.go b/internal/capabilities/generate/test-fixtures/config-linking/simple-generic-cataloger/golang/cataloger.go deleted file mode 100644 index 47a0d7c85..000000000 --- a/internal/capabilities/generate/test-fixtures/config-linking/simple-generic-cataloger/golang/cataloger.go +++ /dev/null @@ -1,19 +0,0 @@ -package golang - -import ( - "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/pkg/cataloger/generic" -) - -type CatalogerConfig struct { - SomeOption bool -} - -func NewGoModuleCataloger(cfg CatalogerConfig) pkg.Cataloger { - return generic.NewCataloger("go-module-cataloger"). - WithParserByGlobs(parseGoMod, "**/go.mod") -} - -func parseGoMod(path string, reader any) ([]pkg.Package, []pkg.Relationship, error) { - return nil, nil, nil -} diff --git a/internal/capabilities/generate/test-fixtures/config-linking/cataloger-with-constant/python/cataloger.go b/internal/capabilities/generate/testdata/cataloger/cataloger-with-constant/cataloger/python/cataloger.go similarity index 52% rename from internal/capabilities/generate/test-fixtures/config-linking/cataloger-with-constant/python/cataloger.go rename to internal/capabilities/generate/testdata/cataloger/cataloger-with-constant/cataloger/python/cataloger.go index fd76c5cb0..ff98a2653 100644 --- a/internal/capabilities/generate/test-fixtures/config-linking/cataloger-with-constant/python/cataloger.go +++ b/internal/capabilities/generate/testdata/cataloger/cataloger-with-constant/cataloger/python/cataloger.go @@ -1,6 +1,10 @@ package python import ( + "context" + + "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" ) @@ -11,11 +15,11 @@ type CatalogerConfig struct { Setting string } -func NewPythonCataloger(cfg CatalogerConfig) pkg.Cataloger { +func NewPythonCataloger(_ CatalogerConfig) pkg.Cataloger { return generic.NewCataloger(catalogerName). WithParserByGlobs(parse, "**/*.py") } -func parse(path string, reader any) ([]pkg.Package, []pkg.Relationship, error) { +func parse(_ context.Context, _ file.Resolver, _ *generic.Environment, _ file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { return nil, nil, nil } diff --git a/internal/capabilities/generate/testdata/cataloger/conflicting-names/cataloger/duplicate1/cataloger.go b/internal/capabilities/generate/testdata/cataloger/conflicting-names/cataloger/duplicate1/cataloger.go new file mode 100644 index 000000000..0ebcb3272 --- /dev/null +++ b/internal/capabilities/generate/testdata/cataloger/conflicting-names/cataloger/duplicate1/cataloger.go @@ -0,0 +1,23 @@ +package duplicate1 + +import ( + "context" + + "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/pkg" + "github.com/anchore/syft/syft/pkg/cataloger/generic" +) + +type Config struct { + Option1 bool +} + +func NewDuplicateCataloger(_ Config) pkg.Cataloger { + return generic.NewCataloger("duplicate-cataloger"). + WithParserByGlobs(parse, "**/*.txt") +} + +func parse(_ context.Context, _ file.Resolver, _ *generic.Environment, _ file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { + return nil, nil, nil +} diff --git a/internal/capabilities/generate/testdata/cataloger/conflicting-names/cataloger/duplicate2/cataloger.go b/internal/capabilities/generate/testdata/cataloger/conflicting-names/cataloger/duplicate2/cataloger.go new file mode 100644 index 000000000..370ec2025 --- /dev/null +++ b/internal/capabilities/generate/testdata/cataloger/conflicting-names/cataloger/duplicate2/cataloger.go @@ -0,0 +1,23 @@ +package duplicate2 + +import ( + "context" + + "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/pkg" + "github.com/anchore/syft/syft/pkg/cataloger/generic" +) + +type Config struct { + Option2 string +} + +func NewDuplicateCataloger(_ Config) pkg.Cataloger { + return generic.NewCataloger("duplicate-cataloger"). + WithParserByGlobs(parse, "**/*.json") +} + +func parse(_ context.Context, _ file.Resolver, _ *generic.Environment, _ file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { + return nil, nil, nil +} diff --git a/internal/capabilities/generate/test-fixtures/config-linking/custom-cataloger-different-file/dotnet/cataloger.go b/internal/capabilities/generate/testdata/cataloger/custom-cataloger-different-file/cataloger/dotnet/cataloger.go similarity index 100% rename from internal/capabilities/generate/test-fixtures/config-linking/custom-cataloger-different-file/dotnet/cataloger.go rename to internal/capabilities/generate/testdata/cataloger/custom-cataloger-different-file/cataloger/dotnet/cataloger.go diff --git a/internal/capabilities/generate/test-fixtures/config-linking/custom-cataloger-different-file/dotnet/types.go b/internal/capabilities/generate/testdata/cataloger/custom-cataloger-different-file/cataloger/dotnet/types.go similarity index 58% rename from internal/capabilities/generate/test-fixtures/config-linking/custom-cataloger-different-file/dotnet/types.go rename to internal/capabilities/generate/testdata/cataloger/custom-cataloger-different-file/cataloger/dotnet/types.go index d32d318b7..931f65742 100644 --- a/internal/capabilities/generate/test-fixtures/config-linking/custom-cataloger-different-file/dotnet/types.go +++ b/internal/capabilities/generate/testdata/cataloger/custom-cataloger-different-file/cataloger/dotnet/types.go @@ -1,6 +1,10 @@ package dotnet import ( + "context" + + "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" ) @@ -18,6 +22,6 @@ func (d dotnetCataloger) Name() string { return catalogerName } -func (d dotnetCataloger) Catalog(resolver any) ([]pkg.Package, []pkg.Relationship, error) { +func (d dotnetCataloger) Catalog(_ context.Context, _ file.Resolver) ([]pkg.Package, []artifact.Relationship, error) { return nil, nil, nil } diff --git a/internal/capabilities/generate/test-fixtures/config-linking/custom-cataloger-same-file/java/cataloger.go b/internal/capabilities/generate/testdata/cataloger/custom-cataloger-same-file/cataloger/java/cataloger.go similarity index 67% rename from internal/capabilities/generate/test-fixtures/config-linking/custom-cataloger-same-file/java/cataloger.go rename to internal/capabilities/generate/testdata/cataloger/custom-cataloger-same-file/cataloger/java/cataloger.go index a3530e6d7..06bc16107 100644 --- a/internal/capabilities/generate/test-fixtures/config-linking/custom-cataloger-same-file/java/cataloger.go +++ b/internal/capabilities/generate/testdata/cataloger/custom-cataloger-same-file/cataloger/java/cataloger.go @@ -1,6 +1,10 @@ package java import ( + "context" + + "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" ) @@ -18,7 +22,7 @@ func (p pomXMLCataloger) Name() string { return pomCatalogerName } -func (p pomXMLCataloger) Catalog(resolver any) ([]pkg.Package, []pkg.Relationship, error) { +func (p pomXMLCataloger) Catalog(_ context.Context, _ file.Resolver) ([]pkg.Package, []artifact.Relationship, error) { return nil, nil, nil } diff --git a/internal/capabilities/generate/testdata/cataloger/imported-config-type/cataloger/kernel/cataloger.go b/internal/capabilities/generate/testdata/cataloger/imported-config-type/cataloger/kernel/cataloger.go new file mode 100644 index 000000000..71fca9477 --- /dev/null +++ b/internal/capabilities/generate/testdata/cataloger/imported-config-type/cataloger/kernel/cataloger.go @@ -0,0 +1,19 @@ +package kernel + +import ( + "context" + + "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/pkg" + "github.com/anchore/syft/syft/pkg/cataloger/generic" +) + +func NewLinuxKernelCataloger(_ LinuxKernelCatalogerConfig) pkg.Cataloger { + return generic.NewCataloger("linux-kernel-cataloger"). + WithParserByGlobs(parse, "**/vmlinuz") +} + +func parse(_ context.Context, _ file.Resolver, _ *generic.Environment, _ file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { + return nil, nil, nil +} diff --git a/internal/capabilities/generate/test-fixtures/config-linking/imported-config-type/kernel/config.go b/internal/capabilities/generate/testdata/cataloger/imported-config-type/cataloger/kernel/config.go similarity index 100% rename from internal/capabilities/generate/test-fixtures/config-linking/imported-config-type/kernel/config.go rename to internal/capabilities/generate/testdata/cataloger/imported-config-type/cataloger/kernel/config.go diff --git a/internal/capabilities/generate/testdata/cataloger/mixed-naming-patterns/cataloger/ruby/cataloger.go b/internal/capabilities/generate/testdata/cataloger/mixed-naming-patterns/cataloger/ruby/cataloger.go new file mode 100644 index 000000000..c6ffa1aee --- /dev/null +++ b/internal/capabilities/generate/testdata/cataloger/mixed-naming-patterns/cataloger/ruby/cataloger.go @@ -0,0 +1,23 @@ +package ruby + +import ( + "context" + + "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/pkg" + "github.com/anchore/syft/syft/pkg/cataloger/generic" +) + +type Config struct { + Setting bool +} + +func NewRubyCataloger(_ Config) pkg.Cataloger { + return generic.NewCataloger("ruby-cataloger"). + WithParserByGlobs(parse, "**/Gemfile") +} + +func parse(_ context.Context, _ file.Resolver, _ *generic.Environment, _ file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { + return nil, nil, nil +} diff --git a/internal/capabilities/generate/test-fixtures/config-linking/no-config-cataloger/javascript/cataloger.go b/internal/capabilities/generate/testdata/cataloger/no-config-cataloger/cataloger/javascript/cataloger.go similarity index 54% rename from internal/capabilities/generate/test-fixtures/config-linking/no-config-cataloger/javascript/cataloger.go rename to internal/capabilities/generate/testdata/cataloger/no-config-cataloger/cataloger/javascript/cataloger.go index c1668a8f9..c450f5ebb 100644 --- a/internal/capabilities/generate/test-fixtures/config-linking/no-config-cataloger/javascript/cataloger.go +++ b/internal/capabilities/generate/testdata/cataloger/no-config-cataloger/cataloger/javascript/cataloger.go @@ -1,6 +1,10 @@ package javascript import ( + "context" + + "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" ) @@ -10,6 +14,6 @@ func NewJavaScriptCataloger() pkg.Cataloger { WithParserByGlobs(parse, "**/*.js") } -func parse(path string, reader any) ([]pkg.Package, []pkg.Relationship, error) { +func parse(_ context.Context, _ file.Resolver, _ *generic.Environment, _ file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { return nil, nil, nil } diff --git a/internal/capabilities/generate/testdata/cataloger/non-config-first-param/cataloger/binary/cataloger.go b/internal/capabilities/generate/testdata/cataloger/non-config-first-param/cataloger/binary/cataloger.go new file mode 100644 index 000000000..f2affe4d0 --- /dev/null +++ b/internal/capabilities/generate/testdata/cataloger/non-config-first-param/cataloger/binary/cataloger.go @@ -0,0 +1,21 @@ +package binary + +import ( + "context" + + "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/pkg" + "github.com/anchore/syft/syft/pkg/cataloger/generic" +) + +type Parser struct{} + +func NewBinaryCataloger(_ Parser) pkg.Cataloger { + return generic.NewCataloger("binary-cataloger"). + WithParserByGlobs(parse, "**/*") +} + +func parse(_ context.Context, _ file.Resolver, _ *generic.Environment, _ file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { + return nil, nil, nil +} diff --git a/internal/capabilities/generate/testdata/cataloger/selector-expression-config/cataloger/rust/cataloger.go b/internal/capabilities/generate/testdata/cataloger/selector-expression-config/cataloger/rust/cataloger.go new file mode 100644 index 000000000..ed04ec546 --- /dev/null +++ b/internal/capabilities/generate/testdata/cataloger/selector-expression-config/cataloger/rust/cataloger.go @@ -0,0 +1,24 @@ +package rust + +import ( + "context" + + "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/pkg" + "github.com/anchore/syft/syft/pkg/cataloger/generic" +) + +// CatalogerConfig is imported from a selector expression in the real code +type CatalogerConfig struct { + SomeOption bool +} + +func NewRustCataloger(_ CatalogerConfig) pkg.Cataloger { + return generic.NewCataloger("rust-cataloger"). + WithParserByGlobs(parse, "**/Cargo.toml") +} + +func parse(_ context.Context, _ file.Resolver, _ *generic.Environment, _ file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { + return nil, nil, nil +} diff --git a/internal/capabilities/generate/testdata/cataloger/simple-generic-cataloger/cataloger/golang/cataloger.go b/internal/capabilities/generate/testdata/cataloger/simple-generic-cataloger/cataloger/golang/cataloger.go new file mode 100644 index 000000000..8792d8be9 --- /dev/null +++ b/internal/capabilities/generate/testdata/cataloger/simple-generic-cataloger/cataloger/golang/cataloger.go @@ -0,0 +1,23 @@ +package golang + +import ( + "context" + + "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/pkg" + "github.com/anchore/syft/syft/pkg/cataloger/generic" +) + +type CatalogerConfig struct { + SomeOption bool +} + +func NewGoModuleCataloger(_ CatalogerConfig) pkg.Cataloger { + return generic.NewCataloger("go-module-cataloger"). + WithParserByGlobs(parseGoMod, "**/go.mod") +} + +func parseGoMod(_ context.Context, _ file.Resolver, _ *generic.Environment, _ file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { + return nil, nil, nil +} diff --git a/internal/capabilities/generate/testdata/config-discovery/multiple-configs/cataloger/java/config.go b/internal/capabilities/generate/testdata/config-discovery/multiple-configs/cataloger/java/config.go new file mode 100644 index 000000000..bfe140ad6 --- /dev/null +++ b/internal/capabilities/generate/testdata/config-discovery/multiple-configs/cataloger/java/config.go @@ -0,0 +1,8 @@ +package java + +// ArchiveCatalogerConfig contains configuration for the java archive cataloger +type ArchiveCatalogerConfig struct { + // include archive contents in catalog + // app-config: java.use-network + UseNetwork bool +} diff --git a/internal/capabilities/generate/testdata/config-discovery/multiple-configs/cataloger/python/config.go b/internal/capabilities/generate/testdata/config-discovery/multiple-configs/cataloger/python/config.go new file mode 100644 index 000000000..b760edc8a --- /dev/null +++ b/internal/capabilities/generate/testdata/config-discovery/multiple-configs/cataloger/python/config.go @@ -0,0 +1,8 @@ +package python + +// CatalogerConfig contains configuration for the python cataloger +type CatalogerConfig struct { + // guess unpinned python package requirements + // app-config: python.guess-unpinned-requirements + GuessUnpinnedRequirements bool +} diff --git a/internal/capabilities/generate/testdata/config-discovery/nested-config/cataloger/golang/config.go b/internal/capabilities/generate/testdata/config-discovery/nested-config/cataloger/golang/config.go new file mode 100644 index 000000000..c62bcb9d7 --- /dev/null +++ b/internal/capabilities/generate/testdata/config-discovery/nested-config/cataloger/golang/config.go @@ -0,0 +1,22 @@ +package golang + +// MainModuleVersionConfig contains nested configuration for main module version detection +type MainModuleVersionConfig struct { + // extract version from LD flags + // app-config: golang.main-module-version.from-ld-flags + FromLDFlags bool + + // extract version from build info + // app-config: golang.main-module-version.from-build-info + FromBuildInfo bool +} + +// CatalogerConfig contains configuration for the golang cataloger +type CatalogerConfig struct { + // enable searching for go package licenses in the local mod cache + // app-config: golang.search-local-mod-cache-licenses + SearchLocalModCacheLicenses bool + + // main module version configuration + MainModuleVersion MainModuleVersionConfig +} diff --git a/internal/capabilities/generate/testdata/config-discovery/no-annotations/cataloger/javascript/config.go b/internal/capabilities/generate/testdata/config-discovery/no-annotations/cataloger/javascript/config.go new file mode 100644 index 000000000..c80e799a4 --- /dev/null +++ b/internal/capabilities/generate/testdata/config-discovery/no-annotations/cataloger/javascript/config.go @@ -0,0 +1,10 @@ +package javascript + +// CatalogerConfig contains configuration for the javascript cataloger (no annotations) +type CatalogerConfig struct { + // this field has no app-config annotation + SearchRemoteLicenses bool + + // this field also has no annotation + IncludeDevDependencies bool +} diff --git a/internal/capabilities/generate/testdata/config-discovery/simple-config/cataloger/golang/config.go b/internal/capabilities/generate/testdata/config-discovery/simple-config/cataloger/golang/config.go new file mode 100644 index 000000000..34f522836 --- /dev/null +++ b/internal/capabilities/generate/testdata/config-discovery/simple-config/cataloger/golang/config.go @@ -0,0 +1,16 @@ +package golang + +// CatalogerConfig contains configuration for the golang cataloger +type CatalogerConfig struct { + // enable searching for go package licenses in the local mod cache + // app-config: golang.search-local-mod-cache-licenses + SearchLocalModCacheLicenses bool + + // base URL for npm registry + // app-config: golang.npm-base-url + NpmBaseURL string + + // list of globs to search for go.mod files + // app-config: golang.search-patterns + SearchPatterns []string +}