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
147 lines
5.4 KiB
Go
147 lines
5.4 KiB
Go
package indexer
|
|
|
|
import (
|
|
"context"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"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"
|
|
"github.com/zzet/gortex/internal/search"
|
|
)
|
|
|
|
func indexSingleRepoForTest(t *testing.T) (*MultiIndexer, string) {
|
|
t.Helper()
|
|
dir := setupRepoDir(t, "myrepo")
|
|
|
|
tmpCfg := filepath.Join(t.TempDir(), "config.yaml")
|
|
gc := &config.GlobalConfig{
|
|
Repos: []config.RepoEntry{{Path: dir, Name: "myrepo"}},
|
|
}
|
|
gc.SetConfigPath(tmpCfg)
|
|
require.NoError(t, gc.Save())
|
|
|
|
cm, err := config.NewConfigManager(tmpCfg)
|
|
require.NoError(t, err)
|
|
|
|
g := graph.New()
|
|
mi := NewMultiIndexer(g, newTestRegistry(), search.NewBM25(), cm, zap.NewNop())
|
|
_, err = mi.IndexAll()
|
|
require.NoError(t, err)
|
|
return mi, dir
|
|
}
|
|
|
|
func indexTwoReposForTest(t *testing.T) (*MultiIndexer, string, string) {
|
|
t.Helper()
|
|
repoA := setupRepoDir(t, "repo-a")
|
|
repoB := setupRepoDir(t, "repo-b")
|
|
|
|
tmpCfg := filepath.Join(t.TempDir(), "config.yaml")
|
|
gc := &config.GlobalConfig{
|
|
Repos: []config.RepoEntry{
|
|
{Path: repoA, Name: "repo-a"},
|
|
{Path: repoB, Name: "repo-b"},
|
|
},
|
|
}
|
|
gc.SetConfigPath(tmpCfg)
|
|
require.NoError(t, gc.Save())
|
|
|
|
cm, err := config.NewConfigManager(tmpCfg)
|
|
require.NoError(t, err)
|
|
|
|
g := graph.New()
|
|
mi := NewMultiIndexer(g, newTestRegistry(), search.NewBM25(), cm, zap.NewNop())
|
|
_, err = mi.IndexAll()
|
|
require.NoError(t, err)
|
|
return mi, repoA, repoB
|
|
}
|
|
|
|
// Single-repo mode indexes nodes and file paths without a repo prefix
|
|
// while registering the repo's metadata under its real prefix. The empty
|
|
// prefix must therefore resolve to the lone repo — otherwise every node
|
|
// the single-repo indexer mints is unresolvable (no source reads, no
|
|
// savings recording, no editing by graph path).
|
|
func TestMultiIndexer_RepoRoot_EmptyPrefixResolvesLoneRepo(t *testing.T) {
|
|
mi, dir := indexSingleRepoForTest(t)
|
|
|
|
root, ok := mi.RepoRoot("")
|
|
require.True(t, ok, "empty prefix must resolve when exactly one repo is tracked")
|
|
assert.Equal(t, dir, root)
|
|
|
|
// The real prefix keeps working.
|
|
root, ok = mi.RepoRoot("myrepo")
|
|
require.True(t, ok)
|
|
assert.Equal(t, dir, root)
|
|
|
|
// Unknown prefixes still miss.
|
|
_, ok = mi.RepoRoot("nope")
|
|
assert.False(t, ok)
|
|
}
|
|
|
|
func TestMultiIndexer_RepoRoot_EmptyPrefixAmbiguousInMultiRepo(t *testing.T) {
|
|
mi, _, _ := indexTwoReposForTest(t)
|
|
|
|
_, ok := mi.RepoRoot("")
|
|
assert.False(t, ok, "empty prefix is ambiguous with two tracked repos")
|
|
}
|
|
|
|
func TestMultiIndexer_ResolveFilePath_UnprefixedSingleRepo(t *testing.T) {
|
|
mi, dir := indexSingleRepoForTest(t)
|
|
|
|
// Unprefixed path (the form single-repo nodes carry) anchors to the
|
|
// lone repo root.
|
|
assert.Equal(t, filepath.Join(dir, "main.go"), mi.ResolveFilePath("main.go"))
|
|
|
|
// The prefixed form keeps working too.
|
|
assert.Equal(t, filepath.Join(dir, "main.go"), mi.ResolveFilePath("myrepo/main.go"))
|
|
}
|
|
|
|
func TestMultiIndexer_ResolveFilePath_UnprefixedMultiRepoStaysEmpty(t *testing.T) {
|
|
mi, _, _ := indexTwoReposForTest(t)
|
|
|
|
assert.Empty(t, mi.ResolveFilePath("main.go"),
|
|
"bare path with two tracked repos is ambiguous and must not resolve")
|
|
}
|
|
|
|
// TestMultiIndexer_RunPreEnrichResolve_BindsInboundCrossRepoEdge is the
|
|
// warm-restart completeness regression: on a partial restart where only the
|
|
// provider repo re-indexed, RunPreEnrichResolve scopes the same-repo master
|
|
// resolve to that repo but must still bind an unchanged consumer repo's inbound
|
|
// cross-repo reference — before the daemon flips ready. Scoping the cross-repo
|
|
// pass to the changed provider's own out-edges left the consumer-owned inbound
|
|
// edge unresolved for the whole enrichment window despite a "ready" daemon.
|
|
func TestMultiIndexer_RunPreEnrichResolve_BindsInboundCrossRepoEdge(t *testing.T) {
|
|
tmpCfg := filepath.Join(t.TempDir(), "config.yaml")
|
|
gc := &config.GlobalConfig{}
|
|
gc.SetConfigPath(tmpCfg)
|
|
require.NoError(t, gc.Save())
|
|
cm, err := config.NewConfigManager(tmpCfg)
|
|
require.NoError(t, err)
|
|
|
|
g := graph.New()
|
|
// Unchanged consumer repo-a: a call with no local definition (bare
|
|
// unresolved), in a shared workspace so the cross-repo boundary is open.
|
|
g.AddNode(&graph.Node{ID: "repo-a/a.go::Caller", Kind: graph.KindFunction, Name: "Caller", FilePath: "repo-a/a.go", Language: "go", RepoPrefix: "repo-a", WorkspaceID: "shared"})
|
|
// Changed provider repo-b: just added Foo, plus its file node for import
|
|
// reachability.
|
|
g.AddNode(&graph.Node{ID: "repo-b/b.go::Foo", Kind: graph.KindFunction, Name: "Foo", FilePath: "repo-b/b.go", Language: "go", RepoPrefix: "repo-b", WorkspaceID: "shared"})
|
|
g.AddNode(&graph.Node{ID: "repo-b/b.go", Kind: graph.KindFile, Name: "repo-b/b.go", FilePath: "repo-b/b.go", Language: "go", RepoPrefix: "repo-b", WorkspaceID: "shared"})
|
|
g.AddEdge(&graph.Edge{From: "repo-a/a.go", To: "repo-b/b.go", Kind: graph.EdgeImports, FilePath: "repo-a/a.go", Line: 1})
|
|
|
|
inbound := &graph.Edge{From: "repo-a/a.go::Caller", To: "unresolved::Foo", Kind: graph.EdgeCalls, FilePath: "repo-a/a.go", Line: 5}
|
|
g.AddEdge(inbound)
|
|
|
|
mi := NewMultiIndexer(g, newTestRegistry(), search.NewBM25(), cm, zap.NewNop())
|
|
|
|
// Scoped warm restart: only the provider repo-b re-indexed.
|
|
mi.RunPreEnrichResolve(context.Background(), map[string]struct{}{"repo-b": {}})
|
|
|
|
assert.Equal(t, "repo-b/b.go::Foo", inbound.To,
|
|
"pre-enrich resolve must bind the unchanged consumer's inbound edge into the changed provider before ready")
|
|
assert.True(t, inbound.CrossRepo)
|
|
}
|