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
89 lines
2.6 KiB
Go
89 lines
2.6 KiB
Go
package resolver
|
|
|
|
import (
|
|
"fmt"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/zzet/gortex/internal/graph"
|
|
)
|
|
|
|
// TestResolveAll_ConcurrentEdits is the safety gate for the chunked master
|
|
// (same-repo) resolve. It runs Resolver.ResolveAll while an editor goroutine
|
|
// evicts and re-indexes caller files under the same resolve mutex an
|
|
// interactive edit takes — the interleaving the chunked path enables. Without
|
|
// the apply / guardCrossPackageCallEdges liveness guards this corrupts the
|
|
// graph (ReindexEdge half-resurrects an evicted edge and later panics with an
|
|
// index-out-of-range during eviction). Run with -race -count=N.
|
|
func TestResolveAll_ConcurrentEdits(t *testing.T) {
|
|
const (
|
|
files = 40
|
|
callersPerFile = 600 // 24000 pending -> ~12 chunks at the default 2048
|
|
)
|
|
g := graph.New()
|
|
callerFile := func(k int) string { return fmt.Sprintf("r/a%d.go", k) }
|
|
|
|
// Same-repo targets, one unique Helper per (file,caller) slot.
|
|
for k := 0; k < files; k++ {
|
|
for i := 0; i < callersPerFile; i++ {
|
|
g.AddNode(&graph.Node{
|
|
ID: fmt.Sprintf("r/b%d_%d.go::Helper%d_%d", k, i, k, i), Kind: graph.KindFunction,
|
|
Name: fmt.Sprintf("Helper%d_%d", k, i), FilePath: fmt.Sprintf("r/b%d_%d.go", k, i),
|
|
Language: "go", RepoPrefix: "r",
|
|
})
|
|
}
|
|
}
|
|
|
|
addCallerFile := func(k int) {
|
|
for i := 0; i < callersPerFile; i++ {
|
|
g.AddNode(&graph.Node{
|
|
ID: fmt.Sprintf("%s::Caller%d_%d", callerFile(k), k, i), Kind: graph.KindFunction,
|
|
Name: fmt.Sprintf("Caller%d_%d", k, i), FilePath: callerFile(k), Language: "go", RepoPrefix: "r",
|
|
})
|
|
g.AddEdge(&graph.Edge{
|
|
From: fmt.Sprintf("%s::Caller%d_%d", callerFile(k), k, i),
|
|
To: fmt.Sprintf("unresolved::Helper%d_%d", k, i),
|
|
Kind: graph.EdgeCalls, FilePath: callerFile(k), Line: i + 1,
|
|
})
|
|
}
|
|
}
|
|
for k := 0; k < files; k++ {
|
|
addCallerFile(k)
|
|
}
|
|
|
|
var resolveDone atomic.Bool
|
|
var wg sync.WaitGroup
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
New(g).ResolveAll()
|
|
resolveDone.Store(true)
|
|
}()
|
|
|
|
mu := g.ResolveMutex()
|
|
var edits int
|
|
for k := 0; !resolveDone.Load(); k = (k + 1) % files {
|
|
mu.Lock()
|
|
g.EvictFile(callerFile(k))
|
|
addCallerFile(k)
|
|
mu.Unlock()
|
|
edits++
|
|
runtime.Gosched()
|
|
}
|
|
wg.Wait()
|
|
|
|
require.Greater(t, edits, 0, "editor never interleaved — increase the work size")
|
|
|
|
for _, e := range g.AllEdges() {
|
|
if e == nil || strings.HasPrefix(e.To, "unresolved::") || isSyntheticResolveTarget(e.To) {
|
|
continue
|
|
}
|
|
require.NotNilf(t, g.GetNode(e.To),
|
|
"edge %s -> %s resolved to a node not in the graph (dangling)", e.From, e.To)
|
|
}
|
|
}
|