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

198 lines
5.4 KiB
Go

package main
import (
"os"
"path/filepath"
"testing"
"github.com/zzet/gortex/internal/graph"
)
// TestRunRepo_LocalFixture is the end-to-end smoke test: runs the
// full bench against the in-tree nestjs fixture and asserts the
// shape of the resulting row. Uses a private cache dir so it doesn't
// touch the developer's real ~/.cache/gortex/bench tree.
func TestRunRepo_LocalFixture(t *testing.T) {
fixturePath, err := filepath.Abs(filepath.Join("..", "fixtures", "di", "nestjs"))
if err != nil {
t.Fatal(err)
}
if _, err := os.Stat(fixturePath); err != nil {
t.Skipf("fixture not available: %v", err)
}
// Run with a tiny query set to keep the test fast.
queries := []string{"AppModule", "ConfigService", "auth handler"}
row := runRepo(repoSpec{
Slug: "nestjs-fixture",
Path: fixturePath,
Local: true,
}, queries, budgets{
// Generous budgets so a slow CI doesn't flake — the real
// validation lives in main.go's strict-mode path.
ImpactP95Ms: 50.0,
SearchP95Ms: 100.0,
})
if row.Error != "" {
t.Fatalf("runRepo error: %s", row.Error)
}
if row.Files == 0 {
t.Errorf("expected indexed files, got 0")
}
if row.Nodes == 0 {
t.Errorf("expected graph nodes, got 0")
}
if row.ColdIndexMs <= 0 {
t.Errorf("cold-index time should be > 0, got %.3f", row.ColdIndexMs)
}
if row.SearchP95Ms <= 0 {
t.Errorf("search p95 should be > 0, got %.3f", row.SearchP95Ms)
}
if row.IncrementalMs <= 0 {
t.Errorf("incremental should be > 0, got %.3f", row.IncrementalMs)
}
if row.DBBytes <= 0 {
t.Errorf("DB size should be > 0, got %d", row.DBBytes)
}
if row.BudgetViolations > 0 {
t.Errorf("generous budgets shouldn't violate, got %d", row.BudgetViolations)
}
}
func TestPickImpactSeeds_RespectsLimit(t *testing.T) {
g := graph.New()
for i := range 5 {
g.AddNode(&graph.Node{
ID: funcID(i),
Name: funcName(i),
Kind: graph.KindFunction,
})
}
seeds := pickImpactSeeds(g, 3)
if len(seeds) != 3 {
t.Errorf("len(seeds)=%d, want 3 (n < pool)", len(seeds))
}
// Pool smaller than n: return all.
seeds = pickImpactSeeds(g, 10)
if len(seeds) != 5 {
t.Errorf("len(seeds)=%d, want 5 (n > pool)", len(seeds))
}
}
func TestPickImpactSeeds_FunctionsAndMethodsOnly(t *testing.T) {
g := graph.New()
g.AddNode(&graph.Node{ID: "f.go::F", Name: "F", Kind: graph.KindFunction})
g.AddNode(&graph.Node{ID: "f.go::M", Name: "M", Kind: graph.KindMethod})
g.AddNode(&graph.Node{ID: "f.go::T", Name: "T", Kind: graph.KindType})
g.AddNode(&graph.Node{ID: "f.go::V", Name: "V", Kind: graph.KindVariable})
seeds := pickImpactSeeds(g, 10)
if len(seeds) != 2 {
t.Errorf("got %d seeds %v, want 2 (function + method only)", len(seeds), seeds)
}
}
func TestPickIncrementalFiles_RespectsLimit(t *testing.T) {
g := graph.New()
for i := range 8 {
g.AddNode(&graph.Node{
ID: fileID(i),
Kind: graph.KindFile,
FilePath: fileName(i),
})
}
files := pickIncrementalFiles(g, 3)
if len(files) != 3 {
t.Errorf("len(files)=%d, want 3", len(files))
}
for _, f := range files {
if f == "" {
t.Errorf("empty file path returned")
}
}
}
func TestTouchFile_MissingFileSilent(t *testing.T) {
if err := touchFile(filepath.Join(t.TempDir(), "missing.go")); err != nil {
t.Errorf("missing file should not error, got %v", err)
}
}
func TestTouchFile_BumpsMtime(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "x.go")
if err := os.WriteFile(path, []byte("// x"), 0o644); err != nil {
t.Fatal(err)
}
st1, _ := os.Stat(path)
// Sleep to ensure mtime granularity is exceeded.
old := st1.ModTime()
if err := touchFile(path); err != nil {
t.Fatal(err)
}
st2, _ := os.Stat(path)
if !st2.ModTime().After(old) && !st2.ModTime().Equal(old) {
t.Errorf("mtime regressed: %v → %v", old, st2.ModTime())
}
}
func TestEstimateDBSize_ScalesWithGraph(t *testing.T) {
small := graph.New()
small.AddNode(&graph.Node{ID: "s1", Name: "s1", Kind: graph.KindFunction})
smallSize := estimateDBSize(small)
large := graph.New()
for i := range 100 {
large.AddNode(&graph.Node{ID: funcID(i), Name: funcName(i), Kind: graph.KindFunction})
}
largeSize := estimateDBSize(large)
if smallSize <= 0 || largeSize <= 0 {
t.Errorf("DB size estimates should be positive, got small=%d large=%d", smallSize, largeSize)
}
if largeSize <= smallSize {
t.Errorf("larger graph should yield larger estimate, got small=%d large=%d", smallSize, largeSize)
}
}
func TestDefaultRepoSet_IncludeLinuxFlag(t *testing.T) {
without := defaultRepoSet(false)
with := defaultRepoSet(true)
if len(without) != 3 || len(with) != 4 {
t.Errorf("expected 3 without linux + 4 with, got %d / %d", len(without), len(with))
}
// linux must be in the includeLinux=true set, NOT in the false set.
hasLinux := func(rs []repoSpec) bool {
for _, r := range rs {
if r.Slug == "linux" {
return true
}
}
return false
}
if hasLinux(without) {
t.Error("defaultRepoSet(false) should not include linux")
}
if !hasLinux(with) {
t.Error("defaultRepoSet(true) should include linux")
}
}
// --- helpers --------------------------------------------------------
func funcID(i int) string { return "f.go::F" + itoa(i) }
func funcName(i int) string { return "F" + itoa(i) }
func fileID(i int) string { return "file" + itoa(i) + ".go" }
func fileName(i int) string { return "file" + itoa(i) + ".go" }
func itoa(i int) string {
if i == 0 {
return "0"
}
var out []byte
for i > 0 {
out = append([]byte{byte('0' + i%10)}, out...)
i /= 10
}
return string(out)
}