-
Notifications
You must be signed in to change notification settings - Fork 842
test: add import compliance tests #4537
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 18 commits
21b5aea
7e33965
404acd5
dec282e
234a039
9a39d6b
5017e1e
e490151
f411817
84fbb35
498915c
f4fbfac
69e8354
be5228a
d607b78
99ed2ce
e9cc48b
cbcd663
f482da4
c4b31fc
7d513d4
40e5904
b6541d1
70f33bc
57f083c
63a88d9
249e7cd
fee0356
d24963b
7578a3f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,168 @@ | ||
| // Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. | ||
maru-ava marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // See the file LICENSE for licensing terms. | ||
|
|
||
| package tests | ||
JonathanOppenheimer marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| import ( | ||
| "fmt" | ||
| "go/ast" | ||
| "go/parser" | ||
| "go/token" | ||
| "os" | ||
| "path/filepath" | ||
| "regexp" | ||
| "slices" | ||
| "strings" | ||
| "testing" | ||
|
|
||
| "github.com/stretchr/testify/require" | ||
|
|
||
| "github.com/ava-labs/avalanchego/utils/set" | ||
| ) | ||
|
|
||
| // TestDoNotImportFromGraft ensures proper import rules for graft packages: | ||
| // - graft/coreth can be imported anywhere EXCEPT vms/evm (but vms/evm/emulate is an exception) | ||
maru-ava marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // - graft/subnet-evm cannot be imported anywhere EXCEPT vms/evm/emulate | ||
|
||
| func TestEnforceGraftImportBoundaries(t *testing.T) { | ||
| graftRegex := regexp.MustCompile(`^github\.com/ava-labs/avalanchego/graft(/|$)`) | ||
JonathanOppenheimer marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| // Find all graft imports in the entire repository | ||
JonathanOppenheimer marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| foundImports, err := findImportsMatchingPattern("..", graftRegex, func(path string, importPath string, _ *ast.ImportSpec) bool { | ||
JonathanOppenheimer marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| // Skip generated files and test-specific files | ||
JonathanOppenheimer marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| filename := filepath.Base(path) | ||
| if strings.HasPrefix(filename, "gen_") || | ||
JonathanOppenheimer marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| strings.Contains(path, "graft/*/core/main_test.go") || | ||
| strings.Contains(path, "graft/*/tempextrastest/") { | ||
JonathanOppenheimer marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return true | ||
JonathanOppenheimer marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| // Skip files in the graft directory itself - they can import from graft | ||
JonathanOppenheimer marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if strings.Contains(path, "/graft/") { | ||
| return true | ||
JonathanOppenheimer marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| isInEmulate := strings.Contains(path, "/vms/evm/emulate/") | ||
JonathanOppenheimer marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| isInVmsEvm := strings.Contains(path, "/vms/evm/") | ||
| isCoreth := strings.Contains(importPath, "/graft/coreth") | ||
| isSubnetEVM := strings.Contains(importPath, "/graft/subnet-evm") | ||
|
||
|
|
||
| // graft/coreth: illegal in vms/evm except vms/evm/emulate | ||
JonathanOppenheimer marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if isCoreth && isInVmsEvm && !isInEmulate { | ||
| return false | ||
JonathanOppenheimer marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| // graft/subnet-evm: illegal everywhere except vms/evm/emulate | ||
JonathanOppenheimer marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if isSubnetEVM && !isInEmulate { | ||
| return false | ||
JonathanOppenheimer marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| return true | ||
JonathanOppenheimer marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| }) | ||
| require.NoError(t, err, "Failed to find graft imports") | ||
|
|
||
| if len(foundImports) == 0 { | ||
| return // no violations found | ||
JonathanOppenheimer marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| header := "Graft import rules violated!\n" + | ||
| "Rules:\n" + | ||
| " - graft/coreth can be imported anywhere EXCEPT vms/evm (but vms/evm/emulate is an exception)\n" + | ||
| " - graft/subnet-evm cannot be imported anywhere EXCEPT vms/evm/emulate\n\n" | ||
| require.Fail(t, formatImportViolations(foundImports, header)) | ||
| } | ||
|
|
||
| // TestDoNotImportLibevmPseudo ensures that no code in the repository imports | ||
|
||
| // the libevm/pseudo package, which is for internal libevm use only. | ||
| func TestDoNotImportLibevmPseudo(t *testing.T) { | ||
JonathanOppenheimer marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
JonathanOppenheimer marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| // Find imports from the forbidden libevm/pseudo package | ||
| pseudoRegex := regexp.MustCompile(`^github\.com/ava-labs/libevm/libevm/pseudo`) | ||
| foundImports, err := findImportsMatchingPattern("../..", pseudoRegex, nil) | ||
| require.NoError(t, err, "Failed to scan for libevm/pseudo imports") | ||
|
|
||
| if len(foundImports) == 0 { | ||
| return // no violations found | ||
| } | ||
|
|
||
| header := "Files must not import libevm/pseudo!\n" + | ||
| "The pseudo package is for internal libevm use only.\n\n" | ||
| require.Fail(t, formatImportViolations(foundImports, header)) | ||
| } | ||
|
|
||
| // formatImportViolations formats a map of import violations into an error message | ||
| func formatImportViolations(violations map[string]set.Set[string], header string) string { | ||
| sortedViolations := make([]string, 0, len(violations)) | ||
| for importPath := range violations { | ||
| sortedViolations = append(sortedViolations, importPath) | ||
| } | ||
| slices.Sort(sortedViolations) | ||
|
|
||
| var errorMsg strings.Builder | ||
| errorMsg.WriteString(header) | ||
| errorMsg.WriteString("Violations:\n\n") | ||
|
|
||
| for _, importPath := range sortedViolations { | ||
| files := violations[importPath] | ||
| fileList := files.List() | ||
| slices.Sort(fileList) | ||
|
|
||
| errorMsg.WriteString(fmt.Sprintf("- %s\n", importPath)) | ||
| errorMsg.WriteString(fmt.Sprintf(" Used in %d file(s):\n", len(fileList))) | ||
| for _, file := range fileList { | ||
| errorMsg.WriteString(fmt.Sprintf(" • %s\n", file)) | ||
| } | ||
| errorMsg.WriteString("\n") | ||
| } | ||
|
|
||
| return errorMsg.String() | ||
| } | ||
|
|
||
| // findImportsMatchingPattern is a generalized function that finds all imports | ||
| // matching a given regex pattern in the specified directory. | ||
| // The filterFunc can be used to skip certain files or imports (return true to skip). | ||
JonathanOppenheimer marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| // Returns a map of import paths to the set of files that contain them. | ||
| func findImportsMatchingPattern( | ||
| rootDir string, | ||
| importRegex *regexp.Regexp, | ||
| filterFunc func(filePath string, importPath string, importSpec *ast.ImportSpec) bool, | ||
JonathanOppenheimer marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ) (map[string]set.Set[string], error) { | ||
| imports := make(map[string]set.Set[string]) | ||
|
|
||
| err := filepath.Walk(rootDir, func(path string, _ os.FileInfo, err error) error { | ||
JonathanOppenheimer marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if err != nil || !strings.HasSuffix(path, ".go") { | ||
| return err | ||
| } | ||
JonathanOppenheimer marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| node, err := parser.ParseFile(token.NewFileSet(), path, nil, parser.ParseComments) | ||
JonathanOppenheimer marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if err != nil { | ||
| return fmt.Errorf("failed to parse %s: %w", path, err) | ||
JonathanOppenheimer marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| for _, imp := range node.Imports { | ||
| if imp.Path == nil { | ||
| continue | ||
| } | ||
|
|
||
| importPath := strings.Trim(imp.Path.Value, `"`) | ||
| if !importRegex.MatchString(importPath) { | ||
| continue | ||
| } | ||
|
|
||
| if filterFunc != nil && filterFunc(path, importPath, imp) { | ||
JonathanOppenheimer marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| continue | ||
| } | ||
|
|
||
| if _, exists := imports[importPath]; !exists { | ||
| imports[importPath] = set.Set[string]{} | ||
| } | ||
| fileSet := imports[importPath] | ||
| fileSet.Add(path) | ||
| imports[importPath] = fileSet | ||
JonathanOppenheimer marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| return nil | ||
| }) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| return imports, nil | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.