Files
zzet--gortex/internal/resolver/warm_cache_parallel_test.go
T
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

127 lines
4.4 KiB
Go

package resolver
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zzet/gortex/internal/graph"
)
// seedWarmCacheGraph builds a graph with enough distinct ids and shared names
// to push the parallel warm-cache helpers past their single-batch threshold
// (minLookupWarmBatch) and to exercise the per-name []*Node bucketing that the
// batch merge must preserve. Returns the graph plus the full id / name key
// sets the helpers will be asked to load.
func seedWarmCacheGraph(t *testing.T, n int) (graph.Store, []string, []string) {
t.Helper()
g := graph.New()
ids := make([]string, 0, n)
nameSet := make(map[string]struct{})
for i := 0; i < n; i++ {
// ~4 nodes share each name so the name lookup returns multi-element
// buckets — the case the merge must not reorder or drop.
name := fmt.Sprintf("sym%d", i%(n/4))
id := fmt.Sprintf("pkg/f%d.go::%s", i, name)
g.AddNode(&graph.Node{
ID: id,
Kind: graph.KindFunction,
Name: name,
FilePath: fmt.Sprintf("pkg/f%d.go", i),
})
ids = append(ids, id)
nameSet[name] = struct{}{}
}
names := make([]string, 0, len(nameSet))
for nm := range nameSet {
names = append(names, nm)
}
return g, ids, names
}
// TestWarmCacheParallel_GetNodesByIDsMatchesSerial pins the parallel id-batch
// helper to the serial Store call on a seeded fixture: the merged result must
// be byte-identical (same nodes, same absent keys for the injected misses).
func TestWarmCacheParallel_GetNodesByIDsMatchesSerial(t *testing.T) {
g, ids, _ := seedWarmCacheGraph(t, 6000)
// Interleave ids that don't exist so both paths agree on absent keys.
query := make([]string, 0, len(ids)+4)
query = append(query, ids...)
query = append(query, "pkg/missing1.go::ghost", "pkg/missing2.go::ghost", "", "pkg/f0.go::sym0")
require.Greater(t, lookupWarmBatches(len(query)), 0)
r := New(g)
serial := g.GetNodesByIDs(query)
parallel := r.parallelGetNodesByIDs(query)
assert.Equal(t, serial, parallel)
assert.Len(t, parallel, len(ids)) // every real id present, ghosts + "" absent
}
// TestWarmCacheParallel_FindNodesByNamesMatchesSerial pins the parallel
// name-batch helper to the serial Store call, including the multi-node buckets
// and the injected missing names.
func TestWarmCacheParallel_FindNodesByNamesMatchesSerial(t *testing.T) {
g, _, names := seedWarmCacheGraph(t, 6000)
query := make([]string, 0, len(names)+3)
query = append(query, names...)
query = append(query, "nosuchname_a", "nosuchname_b", "")
r := New(g)
serial := g.FindNodesByNames(query)
parallel := r.parallelFindNodesByNames(query)
assert.Equal(t, serial, parallel)
// Each present name bucketed all four of its nodes.
for _, nm := range names {
assert.Len(t, parallel[nm], 4, "name %q", nm)
}
_, missA := parallel["nosuchname_a"]
assert.False(t, missA, "absent name must not appear in the result")
}
// TestWarmCacheParallel_WarmLookupCacheContents drives the full warmLookupCache
// over a seeded pending set and checks the assembled caches: every endpoint id
// and target name is present (or recorded as an authoritative negative), which
// is exactly what the parallel batching must preserve for the worker fast path.
func TestWarmCacheParallel_WarmLookupCacheContents(t *testing.T) {
g, ids, _ := seedWarmCacheGraph(t, 6000)
// One pending call edge per node, each From an existing node and To a bare
// unresolved target name. Half the targets exist as node names, half don't
// (authoritative negatives).
pending := make([]*graph.Edge, 0, len(ids))
for i, id := range ids {
target := fmt.Sprintf("sym%d", i%(len(ids)/4))
if i%2 == 0 {
target = fmt.Sprintf("ghost%d", i) // no such node
}
pending = append(pending, &graph.Edge{
From: id,
To: "unresolved::" + target,
Kind: graph.EdgeCalls,
})
}
r := New(g)
r.warmLookupCache(pending)
defer r.clearLookupCache()
// Every edge endpoint id is cached to its node.
for _, id := range ids {
n, ok := r.nodeByID[id]
require.True(t, ok, "id %q missing from warm id cache", id)
require.NotNil(t, n)
assert.Equal(t, id, n.ID)
}
// Existing target names resolve; ghost names are cached as authoritative
// negatives (present key, nil/empty slice) so the worker never falls
// through to a per-edge store scan.
assert.NotEmpty(t, r.nodesByName["sym1"])
neg, ok := r.nodesByName["ghost0"]
require.True(t, ok, "authoritative negative must be recorded for a missing target name")
assert.Empty(t, neg)
}