Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

210 lines
8.1 KiB
Go

package indexer
import (
"testing"
"github.com/zzet/gortex/internal/graph"
)
// TestRunnerPropagation_ParserStamped — the parser already stamped
// Meta["test_runner"] on the file node (JS / TS path). The global pass
// must copy it to every is_test function/method in that file, but not
// to any production function.
func TestRunnerPropagation_ParserStamped(t *testing.T) {
g := graph.New()
fileID := "src/foo.test.ts"
g.AddNode(&graph.Node{
ID: fileID,
Kind: graph.KindFile,
Name: fileID,
FilePath: fileID,
Language: "typescript",
Meta: map[string]any{"test_runner": "bun-test"},
})
g.AddNode(&graph.Node{
ID: fileID + "::renders",
Kind: graph.KindFunction,
Name: "renders",
FilePath: fileID,
Language: "typescript",
})
// A production file with a same-shaped function — must NOT be marked.
g.AddNode(&graph.Node{ID: "src/prod.ts", Kind: graph.KindFile, FilePath: "src/prod.ts", Language: "typescript"})
g.AddNode(&graph.Node{ID: "src/prod.ts::renders", Kind: graph.KindFunction, Name: "renders", FilePath: "src/prod.ts", Language: "typescript"})
marked, _ := markTestSymbolsAndEmitEdges(g)
if marked != 1 {
t.Fatalf("marked = %d, want 1", marked)
}
testFn := g.GetNode(fileID + "::renders")
if got, _ := testFn.Meta["test_runner"].(string); got != "bun-test" {
t.Fatalf("test_runner on test fn = %q, want %q", got, "bun-test")
}
if got, _ := testFn.Meta["test_role"].(string); got != "test" {
t.Fatalf("test_role on test fn = %q, want %q", got, "test")
}
prod := g.GetNode("src/prod.ts::renders")
if _, ok := prod.Meta["test_runner"]; ok {
t.Fatalf("production fn must not carry test_runner: %+v", prod.Meta)
}
}
// TestRunnerPropagation_GoDefault — every Go test file gets `gotest`
// without any parser hint, because `go test` is the only Go runner.
func TestRunnerPropagation_GoDefault(t *testing.T) {
g := graph.New()
g.AddNode(&graph.Node{ID: "pkg/foo_test.go", Kind: graph.KindFile, FilePath: "pkg/foo_test.go", Language: "go"})
g.AddNode(&graph.Node{ID: "pkg/foo_test.go::TestFoo", Kind: graph.KindFunction, Name: "TestFoo", FilePath: "pkg/foo_test.go", Language: "go"})
markTestSymbolsAndEmitEdges(g)
testFn := g.GetNode("pkg/foo_test.go::TestFoo")
if got, _ := testFn.Meta["test_runner"].(string); got != "gotest" {
t.Fatalf("Go test_runner = %q, want %q", got, "gotest")
}
}
// TestRunnerPropagation_PythonFromImports — Python files classify by
// outgoing import edges. unittest beats pytest only when no pytest
// import is present.
func TestRunnerPropagation_PythonFromImports(t *testing.T) {
cases := []struct {
name string
importPath string
wantRunner string
}{
{"pytest import", "pytest", "pytest"},
{"pytest submodule", "pytest.mark", "pytest"},
{"unittest import", "unittest", "unittest"},
{"unittest.mock import", "unittest.mock", "unittest"},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
g := graph.New()
fileID := "tests/test_x.py"
g.AddNode(&graph.Node{ID: fileID, Kind: graph.KindFile, FilePath: fileID, Language: "python"})
g.AddNode(&graph.Node{ID: fileID + "::test_x", Kind: graph.KindFunction, Name: "test_x", FilePath: fileID, Language: "python"})
g.AddEdge(&graph.Edge{From: fileID, To: "unresolved::import::" + tc.importPath, Kind: graph.EdgeImports, FilePath: fileID, Line: 1})
markTestSymbolsAndEmitEdges(g)
fn := g.GetNode(fileID + "::test_x")
if got, _ := fn.Meta["test_runner"].(string); got != tc.wantRunner {
t.Fatalf("test_runner = %q, want %q", got, tc.wantRunner)
}
})
}
}
// TestRunnerPropagation_PythonDefault — a test file with no pytest /
// unittest import still defaults to `pytest`, because pytest's
// auto-discovery is the de-facto Python runner.
func TestRunnerPropagation_PythonDefault(t *testing.T) {
g := graph.New()
fileID := "tests/test_y.py"
g.AddNode(&graph.Node{ID: fileID, Kind: graph.KindFile, FilePath: fileID, Language: "python"})
g.AddNode(&graph.Node{ID: fileID + "::test_y", Kind: graph.KindFunction, Name: "test_y", FilePath: fileID, Language: "python"})
markTestSymbolsAndEmitEdges(g)
fn := g.GetNode(fileID + "::test_y")
if got, _ := fn.Meta["test_runner"].(string); got != "pytest" {
t.Fatalf("Python default test_runner = %q, want pytest", got)
}
}
// TestRunnerPropagation_RubyFileSuffix — Ruby falls back to `_spec.rb`
// → rspec, `_test.rb` → minitest with no import signal.
func TestRunnerPropagation_RubyFileSuffix(t *testing.T) {
cases := []struct{ file, want string }{
{"spec/user_spec.rb", "rspec"},
{"test/user_test.rb", "minitest"},
}
for _, tc := range cases {
t.Run(tc.file, func(t *testing.T) {
g := graph.New()
g.AddNode(&graph.Node{ID: tc.file, Kind: graph.KindFile, FilePath: tc.file, Language: "ruby"})
g.AddNode(&graph.Node{ID: tc.file + "::test_x", Kind: graph.KindFunction, Name: "test_x", FilePath: tc.file, Language: "ruby"})
markTestSymbolsAndEmitEdges(g)
fn := g.GetNode(tc.file + "::test_x")
if got, _ := fn.Meta["test_runner"].(string); got != tc.want {
t.Fatalf("test_runner = %q, want %q", got, tc.want)
}
})
}
}
// TestRunnerPropagation_JSImportEdgeFallback — when the parser didn't
// stamp the file (e.g. an older index built before this change), the
// global pass still derives the runner from outgoing import edges.
func TestRunnerPropagation_JSImportEdgeFallback(t *testing.T) {
cases := []struct {
name string
path string
imp string
want string
}{
{"bun-test via edge", "src/x.test.ts", "bun:test", "bun-test"},
{"vitest via edge", "src/x.test.ts", "vitest", "vitest"},
{"jest via edge", "src/x.test.ts", "@jest/globals", "jest"},
{"mocha via edge", "test/x.js", "mocha", "mocha"},
{"node-test via edge", "test/x.mjs", "node:test", "node-test"},
{"playwright via edge", "e2e/x.spec.ts", "@playwright/test", "playwright"},
{"cypress via edge", "cypress/foo.spec.js", "cypress", "cypress"},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
g := graph.New()
g.AddNode(&graph.Node{ID: tc.path, Kind: graph.KindFile, FilePath: tc.path, Language: "typescript"})
g.AddNode(&graph.Node{ID: tc.path + "::it_x", Kind: graph.KindFunction, Name: "it_x", FilePath: tc.path, Language: "typescript"})
g.AddEdge(&graph.Edge{From: tc.path, To: "unresolved::import::" + tc.imp, Kind: graph.EdgeImports, FilePath: tc.path, Line: 1})
markTestSymbolsAndEmitEdges(g)
fn := g.GetNode(tc.path + "::it_x")
if got, _ := fn.Meta["test_runner"].(string); got != tc.want {
t.Fatalf("test_runner = %q, want %q", got, tc.want)
}
})
}
}
// TestRunnerPropagation_ProductionFileUnchanged — a non-test file with
// a test_runner-looking import edge must not be classified. Only test
// files (per IsTestFile) get a runner stamped on their functions.
func TestRunnerPropagation_ProductionFileUnchanged(t *testing.T) {
g := graph.New()
g.AddNode(&graph.Node{ID: "src/api.ts", Kind: graph.KindFile, FilePath: "src/api.ts", Language: "typescript"})
g.AddNode(&graph.Node{ID: "src/api.ts::serve", Kind: graph.KindFunction, Name: "serve", FilePath: "src/api.ts", Language: "typescript"})
g.AddEdge(&graph.Edge{From: "src/api.ts", To: "unresolved::import::mocha", Kind: graph.EdgeImports, FilePath: "src/api.ts", Line: 1})
markTestSymbolsAndEmitEdges(g)
fn := g.GetNode("src/api.ts::serve")
if _, ok := fn.Meta["test_runner"]; ok {
t.Fatalf("production fn must not carry test_runner: %+v", fn.Meta)
}
}
// TestRunnerPropagation_FileNodeMetaStamped — the file node itself
// also gets Meta["test_runner"], so file-level analyses can group
// tests by runner without walking functions.
func TestRunnerPropagation_FileNodeMetaStamped(t *testing.T) {
g := graph.New()
fileID := "src/x.test.ts"
g.AddNode(&graph.Node{
ID: fileID, Kind: graph.KindFile, FilePath: fileID, Language: "typescript",
Meta: map[string]any{"test_runner": "vitest"},
})
markTestSymbolsAndEmitEdges(g)
fn := g.GetNode(fileID)
if got, _ := fn.Meta["test_runner"].(string); got != "vitest" {
t.Fatalf("file node test_runner = %q, want vitest", got)
}
if v, _ := fn.Meta["is_test_file"].(bool); !v {
t.Fatalf("file node is_test_file must be true")
}
}