Skip to content
Open
Changes from 18 commits
Commits
Show all changes
30 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
7578a3f
Merge branch 'master' into JonathanOppenheimer/import-testing
JonathanOppenheimer Dec 9, 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
168 changes: 168 additions & 0 deletions vms/evm/imports_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package tests

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)
// - graft/subnet-evm cannot be imported anywhere EXCEPT vms/evm/emulate
Copy link
Contributor

Choose a reason for hiding this comment

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

Why are these the rules? I understand emulate being allowed to import, but not the other limitations.

Rule of thumb for all code, not just this instance: comments should explain "why", not "what" nor "how".

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 added a longer comment explaining why - is it satisfactory?

func TestEnforceGraftImportBoundaries(t *testing.T) {
graftRegex := regexp.MustCompile(`^github\.com/ava-labs/avalanchego/graft(/|$)`)

// Find all graft imports in the entire repository
foundImports, err := findImportsMatchingPattern("..", graftRegex, func(path string, importPath string, _ *ast.ImportSpec) bool {
// Skip generated files and test-specific files
filename := filepath.Base(path)
if strings.HasPrefix(filename, "gen_") ||
strings.Contains(path, "graft/*/core/main_test.go") ||
strings.Contains(path, "graft/*/tempextrastest/") {
return true
}

// Skip files in the graft directory itself - they can import from graft
if strings.Contains(path, "/graft/") {
return true
}

isInEmulate := strings.Contains(path, "/vms/evm/emulate/")
isInVmsEvm := strings.Contains(path, "/vms/evm/")
isCoreth := strings.Contains(importPath, "/graft/coreth")
isSubnetEVM := strings.Contains(importPath, "/graft/subnet-evm")
Copy link
Contributor

Choose a reason for hiding this comment

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

(No action required) Not a biggie, but would prefer to see subnet-evm specific stuff as part of the migration not in advance.

But also, why is testing for these independent grafts conflated? Maybe implement 2 tests that use the same helpers, one for each graft location?

Copy link
Member Author

Choose a reason for hiding this comment

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

@ARR4N suggested that all of this be combined into a single import violation test. Would you still like to see the tests separated out? I see the benefit of both ways

  • single test: less code, everything in one place
  • two tests: separation makes more sense semantically

I could also do a single test with two subtests?


// graft/coreth: illegal in vms/evm except vms/evm/emulate
if isCoreth && isInVmsEvm && !isInEmulate {
return false
}

// graft/subnet-evm: illegal everywhere except vms/evm/emulate
if isSubnetEVM && !isInEmulate {
return false
}

return true
})
require.NoError(t, err, "Failed to find graft imports")

if len(foundImports) == 0 {
return // no violations found
}

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

Choose a reason for hiding this comment

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

(No action required) If it's libevm-internal only, why isn't the package called internal so that the restriction is enforced automatically by golang?

Copy link
Member Author

Choose a reason for hiding this comment

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

@ARR4N would this be possible?

// the libevm/pseudo package, which is for internal libevm use only.
func TestDoNotImportLibevmPseudo(t *testing.T) {
// 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).
// 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,
) (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 {
if err != nil || !strings.HasSuffix(path, ".go") {
return err
}

node, err := parser.ParseFile(token.NewFileSet(), path, nil, parser.ParseComments)
if err != nil {
return fmt.Errorf("failed to parse %s: %w", path, err)
}

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) {
continue
}

if _, exists := imports[importPath]; !exists {
imports[importPath] = set.Set[string]{}
}
fileSet := imports[importPath]
fileSet.Add(path)
imports[importPath] = fileSet
}
return nil
})
if err != nil {
return nil, err
}

return imports, nil
}