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

75 lines
2.9 KiB
Go

package indexer
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"github.com/zzet/gortex/internal/config"
"github.com/zzet/gortex/internal/graph"
)
// TestCountResolvedFileEdges proves the resolved-edge count excludes stub
// (`unresolved::`) targets — the signal countFileEdges cannot give, since an
// edge demoted from a concrete target to a stub keeps the incident-edge total
// identical.
func TestCountResolvedFileEdges(t *testing.T) {
idx, _ := newToggleIndexer(t)
g := idx.graph
g.AddBatch([]*graph.Node{
{ID: "a.go", Kind: graph.KindFile, Name: "a.go", FilePath: "a.go"},
{ID: "a.go::Foo", Kind: graph.KindFunction, Name: "Foo", FilePath: "a.go"},
}, []*graph.Edge{
{From: "a.go::Foo", To: "b.go::Bar", Kind: graph.EdgeCalls, FilePath: "a.go"},
{From: "a.go::Foo", To: "unresolved::Baz", Kind: graph.EdgeCalls, FilePath: "a.go"},
})
w, err := NewWatcher(idx, config.WatchConfig{Enabled: true}, zap.NewNop())
require.NoError(t, err)
assert.Equal(t, 1, w.countResolvedFileEdges(g.GetFileNodes("a.go")),
"only the resolved out-edge counts; the unresolved stub does not")
}
// TestGuardResolvedEdgeRegression covers the F4.1 shape-degradation guard: a
// modify that kept its symbols but lost more than half its resolved edges
// enqueues a forced scoped re-resolve and bumps the regression counter, while
// below-floor / symbol-removal / modest-drop cases stay quiet.
func TestGuardResolvedEdgeRegression(t *testing.T) {
idx, _ := newToggleIndexer(t)
w, err := NewWatcher(idx, config.WatchConfig{Enabled: true, DebounceMs: 10}, zap.NewNop())
require.NoError(t, err)
reresolved := make(chan map[string]struct{}, 4)
w.reconcileMu.Lock()
w.reresolveFn = func(files map[string]struct{}) { reresolved <- files }
w.reconcileMu.Unlock()
// Resolved edges more than halved, symbols intact, above the floor: fires.
before := ResolutionRegressions()
w.guardResolvedEdgeRegression("a.go", 5, 5, 10, 2, 0, 0)
select {
case files := <-reresolved:
_, ok := files["a.go"]
assert.True(t, ok, "the degraded file must be enqueued for a forced re-resolve")
case <-time.After(2 * time.Second):
t.Fatal("a resolved-edge collapse must enqueue a forced re-resolve")
}
assert.Equal(t, before+1, ResolutionRegressions(), "the regression counter must increment")
// None of the negatives may fire.
base := ResolutionRegressions()
w.guardResolvedEdgeRegression("below-floor.go", 5, 5, 3, 0, 0, 0) // resolvedBefore < floor
w.guardResolvedEdgeRegression("symbol-removed.go", 5, 3, 10, 2, 0, 0)
w.guardResolvedEdgeRegression("modest-drop.go", 5, 5, 10, 6, 0, 0) // dropped <= 50%
select {
case f := <-reresolved:
t.Fatalf("a non-regression must not enqueue a re-resolve, got %v", f)
case <-time.After(150 * time.Millisecond):
}
assert.Equal(t, base, ResolutionRegressions(), "non-regressions must not bump the counter")
}