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
210 lines
8.1 KiB
Go
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")
|
|
}
|
|
}
|