-
Notifications
You must be signed in to change notification settings - Fork 837
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 all 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
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,118 @@ | ||||||||
| // 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 evm | ||||||||
|
|
||||||||
| import ( | ||||||||
| "fmt" | ||||||||
| "go/parser" | ||||||||
| "go/token" | ||||||||
| "io/fs" | ||||||||
| "path/filepath" | ||||||||
| "slices" | ||||||||
| "strings" | ||||||||
| "testing" | ||||||||
|
|
||||||||
| "github.com/stretchr/testify/require" | ||||||||
| ) | ||||||||
|
|
||||||||
| // TestImportViolations ensures proper import rules: | ||||||||
| // - 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 can only be imported within graft/subnet-evm itself and vms/evm/emulate | ||||||||
| // - github.com/ava-labs/libevm/libevm/pseudo cannot be imported anywhere | ||||||||
| // | ||||||||
| // The rationale for these rules are as follows: | ||||||||
| // | ||||||||
| // coreth can be imported in AvalancheGo, because it was already imported by Avalanche prior to | ||||||||
| // grafting | ||||||||
| // | ||||||||
| // coreth can NOT be imported in the vms/evm package, because the goal is that vms/evm should | ||||||||
| // only contain the 'clean' properly uplifted code, that meets AvalancheGo quality standards. | ||||||||
| // | ||||||||
| // subnet-evm can NOT be imported anywhere in AvalancheGo besides graft/subnet-evm itself, | ||||||||
| // because it must not become a direct dependency of AvalancheGo or mix directly with coreth. | ||||||||
| // | ||||||||
| // both coreth and subnet-evm can be imported in the vms/evm/emulate package, because it | ||||||||
| // allows consumers to use both coreth and subnet-evm registration at the same time. | ||||||||
| // | ||||||||
| // github.com/ava-labs/libevm/libevm/pseudo cannot be imported anywhere, without review from any of | ||||||||
| // @StephenButtolph, @ARR4N, or @joshua-kim. There is almost certainly a better option, and it exists | ||||||||
| // only because libevm can't pollute the code base with generic type parameters. | ||||||||
| // | ||||||||
| // TODO(jonathanoppenheimer): remove the graft functionality once the graft package will be removed. | ||||||||
| func TestImportViolations(t *testing.T) { | ||||||||
| const root = ".." | ||||||||
| repoRoot, err := filepath.Abs(root) | ||||||||
| require.NoError(t, err) | ||||||||
|
|
||||||||
| graftDir := filepath.Join(repoRoot, "graft") | ||||||||
| graftSubnetEVMDir := filepath.Join(repoRoot, "graft", "subnet-evm") | ||||||||
| emulateDir := filepath.Join(repoRoot, "vms", "evm", "emulate") | ||||||||
| vmsEVMDir := filepath.Join(repoRoot, "vms", "evm") | ||||||||
|
|
||||||||
| var violations []string | ||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why add the layer of indirection? Just report the violation when it arises. FWIW this is why forcing use of
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This also will catch all the violations no? Fill up the array first, and then check the array at the end -- so it won't have to be rerun a ton of times.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assume he would prefer that the use of inline assertions so as to avoid the requirement to accumulate failures and output them at the end? FWIW, I'm sympathetic to revisiting the
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yah, I understand. If we'd like to revisiting the ban, that totally makes sense, as it would certainly enable additional testing patterns, but I don't think this is the PR to do that in 😂. For retro! |
||||||||
|
|
||||||||
| err = filepath.Walk(root, func(file string, _ fs.FileInfo, err error) error { | ||||||||
| if err != nil { | ||||||||
| return err | ||||||||
| } | ||||||||
| if strings.ToLower(filepath.Ext(file)) != ".go" { | ||||||||
| return nil | ||||||||
| } | ||||||||
|
|
||||||||
| absFile, err := filepath.Abs(file) | ||||||||
| if err != nil { | ||||||||
| return err | ||||||||
| } | ||||||||
|
|
||||||||
| node, err := parser.ParseFile(token.NewFileSet(), file, nil, parser.ImportsOnly) | ||||||||
| if err != nil { | ||||||||
| return fmt.Errorf("parser.ParseFile(..., %q, ...): %w", file, err) | ||||||||
| } | ||||||||
|
|
||||||||
| for _, spec := range node.Imports { | ||||||||
| if spec.Path == nil { | ||||||||
| continue | ||||||||
| } | ||||||||
| importPath := strings.Trim(spec.Path.Value, `"`) | ||||||||
|
|
||||||||
| inGraft := strings.HasPrefix(absFile, graftDir) | ||||||||
| inGraftSubnetEVM := strings.HasPrefix(absFile, graftSubnetEVMDir) | ||||||||
| inEmulate := strings.HasPrefix(absFile, emulateDir) | ||||||||
| inVMsEVM := strings.HasPrefix(absFile, vmsEVMDir) | ||||||||
| importsPseudo := isImportIn(importPath, "github.com/ava-labs/libevm/libevm/pseudo") | ||||||||
| importsCoreth := isImportIn(importPath, "github.com/ava-labs/avalanchego/graft/coreth") | ||||||||
| importsSubnetEVM := isImportIn(importPath, "github.com/ava-labs/avalanchego/graft/subnet-evm") | ||||||||
|
|
||||||||
| hasViolation := []bool{ | ||||||||
| importsPseudo, | ||||||||
| !inGraft && importsCoreth && inVMsEVM && !inEmulate, | ||||||||
| !inGraftSubnetEVM && importsSubnetEVM && !inEmulate, | ||||||||
| } | ||||||||
| if slices.Contains(hasViolation, true) { | ||||||||
| violations = append(violations, fmt.Sprintf("File %q imports %q", file, importPath)) | ||||||||
| } | ||||||||
| } | ||||||||
| return nil | ||||||||
| }) | ||||||||
|
|
||||||||
| require.NoErrorf(t, err, "filepath.Walk(%q)", root) | ||||||||
| require.Empty(t, violations, "import violations found") | ||||||||
| } | ||||||||
|
|
||||||||
| func isImportIn(importPath, targetedImport string) bool { | ||||||||
| if importPath == targetedImport { | ||||||||
| return true | ||||||||
| } | ||||||||
|
|
||||||||
| // importPath must be at least len(targetedImport) + 1 to be a subpackage | ||||||||
| // (e.g., "github.com/foo/x" is len("github.com/foo") + 2, minimum subpackage length) | ||||||||
| if len(importPath) < len(targetedImport)+1 { | ||||||||
| return false | ||||||||
| } | ||||||||
|
|
||||||||
| // Check if importPath is a subpackage by ensuring it has the targetedImport prefix | ||||||||
| // AND the next character is '/'. This is to prevent false positives where one | ||||||||
| // package name is a prefix of another like "github.com/foo" vs "github.com/foobar". | ||||||||
|
Comment on lines
+114
to
+116
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
This just says what the code does so shouldn't be there as it's redundant and risks being or becoming incorrect. Heuristic: only explain "why" in inline comments, not how nor what. If a how or what is necessary then the code probably needs to be refactored.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the what is necessary as Maru requested an explanation of the intent behind the code here: #4537 (comment). |
||||||||
| return strings.HasPrefix(importPath, targetedImport) && importPath[len(targetedImport)] == '/' | ||||||||
| } | ||||||||
Uh oh!
There was an error while loading. Please reload this page.