Skip to content
Open
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
21b5aea
test: add import compliance tests
JonathanOppenheimer Nov 21, 2025
7e33965
test: tweak violation wording
JonathanOppenheimer Nov 21, 2025
404acd5
test: add violation to show failure example
JonathanOppenheimer Nov 21, 2025
dec282e
test: remove violation
JonathanOppenheimer Nov 21, 2025
234a039
test: only enforce on vms/evm directory
JonathanOppenheimer Nov 21, 2025
9a39d6b
test: clarify boundaries
JonathanOppenheimer Nov 22, 2025
5017e1e
test: rename test
JonathanOppenheimer Nov 22, 2025
e490151
test: clarify comments
JonathanOppenheimer Nov 22, 2025
f411817
chore: lint
JonathanOppenheimer Nov 22, 2025
84fbb35
test: add license violation test
JonathanOppenheimer Nov 22, 2025
498915c
test: inline once used type
JonathanOppenheimer Nov 22, 2025
f4fbfac
tests: rename libevm test
JonathanOppenheimer Nov 22, 2025
69e8354
chore: fix typo
JonathanOppenheimer Nov 22, 2025
be5228a
test: remove licensing test (excessive)
JonathanOppenheimer Nov 24, 2025
d607b78
Merge branch 'master' into JonathanOppenheimer/import-testing
JonathanOppenheimer Nov 24, 2025
99ed2ce
test: check both evm code locations
JonathanOppenheimer Nov 24, 2025
e9cc48b
test: move import tests to evm directory
JonathanOppenheimer Nov 24, 2025
cbcd663
test: reconsider boundary tests
JonathanOppenheimer Nov 24, 2025
f482da4
Update vms/evm/imports_test.go
JonathanOppenheimer Dec 1, 2025
c4b31fc
test: use Arran's rewritten test
JonathanOppenheimer Dec 1, 2025
7d513d4
fix: add .go
JonathanOppenheimer Dec 1, 2025
40e5904
Merge branch 'master' into JonathanOppenheimer/import-testing
JonathanOppenheimer Dec 1, 2025
b6541d1
chore: lint
JonathanOppenheimer Dec 1, 2025
70f33bc
chore: move comment
JonathanOppenheimer Dec 1, 2025
57f083c
docs: document functions
JonathanOppenheimer Dec 1, 2025
63a88d9
Update vms/evm/imports_test.go
JonathanOppenheimer Dec 2, 2025
249e7cd
Update vms/evm/imports_test.go
JonathanOppenheimer Dec 2, 2025
fee0356
Update vms/evm/imports_test.go
JonathanOppenheimer Dec 2, 2025
d24963b
test: use absolute file paths
JonathanOppenheimer Dec 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 118 additions & 0 deletions vms/evm/imports_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved.
// 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)
// - 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
Copy link
Contributor

Choose a reason for hiding this comment

The 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 require instead of judicious use of assert/require as appropriate is a bad idea. If multiple violations occur then it forces whack-a-mole fixing and rerunning of the test every time. Strict adherence to a style guide is not a valid reason to write more complicated code as it's antithetical to the purpose of a guide.

Copy link
Contributor

@maru-ava maru-ava Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assert was banned because in too many instances it was assumed to be equivalent to require (fail-fast) and this resulted in buggy tests. For those instances where multiple violations are possible, t.Log is always an option.

Copy link
Member Author

@JonathanOppenheimer JonathanOppenheimer Dec 2, 2025

Choose a reason for hiding this comment

The 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.

Copy link
Contributor

Choose a reason for hiding this comment

The 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 assert ban, maybe something to bring up in retro? I would hope that we now have enough review maturity to avoid misuse going forward.

Copy link
Member Author

Choose a reason for hiding this comment

The 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// 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".

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.

More details.

Copy link
Member Author

Choose a reason for hiding this comment

The 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)] == '/'
}