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
651 lines
19 KiB
Go
651 lines
19 KiB
Go
package indexer
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"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"
|
|
"github.com/zzet/gortex/internal/parser"
|
|
"github.com/zzet/gortex/internal/parser/languages"
|
|
)
|
|
|
|
func setupTestDir(t *testing.T) string {
|
|
t.Helper()
|
|
dir := t.TempDir()
|
|
|
|
// main.go
|
|
writeFile(t, filepath.Join(dir, "main.go"), `package main
|
|
|
|
import "fmt"
|
|
|
|
func main() {
|
|
fmt.Println("hello")
|
|
helper()
|
|
}
|
|
|
|
func helper() {}
|
|
`)
|
|
|
|
// pkg/util.go
|
|
pkgDir := filepath.Join(dir, "pkg")
|
|
require.NoError(t, os.MkdirAll(pkgDir, 0o755))
|
|
writeFile(t, filepath.Join(pkgDir, "util.go"), `package pkg
|
|
|
|
type Config struct {
|
|
Port int
|
|
}
|
|
|
|
func NewConfig() *Config {
|
|
return &Config{Port: 8080}
|
|
}
|
|
`)
|
|
|
|
// vendor/ should be excluded.
|
|
vendorDir := filepath.Join(dir, "vendor")
|
|
require.NoError(t, os.MkdirAll(vendorDir, 0o755))
|
|
writeFile(t, filepath.Join(vendorDir, "lib.go"), `package vendor
|
|
|
|
func Ignored() {}
|
|
`)
|
|
|
|
return dir
|
|
}
|
|
|
|
func writeFile(t *testing.T, path, content string) {
|
|
t.Helper()
|
|
require.NoError(t, os.WriteFile(path, []byte(content), 0o644))
|
|
}
|
|
|
|
func newTestIndexer(g graph.Store) *Indexer {
|
|
reg := parser.NewRegistry()
|
|
reg.Register(languages.NewGoExtractor())
|
|
cfg := config.Default().Index
|
|
cfg.Workers = 2
|
|
return New(g, reg, cfg, zap.NewNop())
|
|
}
|
|
|
|
// newTestIndexerGoJava registers both the Go and Java extractors — used by
|
|
// the cross-language Temporal join tests.
|
|
func newTestIndexerGoJava(g graph.Store) *Indexer {
|
|
reg := parser.NewRegistry()
|
|
reg.Register(languages.NewGoExtractor())
|
|
reg.Register(languages.NewJavaExtractor())
|
|
cfg := config.Default().Index
|
|
cfg.Workers = 2
|
|
return New(g, reg, cfg, zap.NewNop())
|
|
}
|
|
|
|
func TestIndex_SingleGoFile(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writeFile(t, filepath.Join(dir, "main.go"), `package main
|
|
|
|
func Hello() {}
|
|
`)
|
|
|
|
g := graph.New()
|
|
idx := newTestIndexer(g)
|
|
result, err := idx.Index(dir)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, 1, result.FileCount)
|
|
assert.Greater(t, result.NodeCount, 0)
|
|
assert.Greater(t, result.EdgeCount, 0)
|
|
}
|
|
|
|
// TestIndexCtx_FlagsFullReindexForEnrichForce locks the wiring the enrichment
|
|
// completion marker depends on: a whole-repo IndexCtx re-parse evicts and
|
|
// re-creates every node/edge (dropping the repo's LSP hover-enrichment edges),
|
|
// so it must flag fullReindexed. runDeferredEnrich threads that into
|
|
// RepoEnrichState.Force to re-run enrichment past the marker gate even at an
|
|
// unchanged clean HEAD — without it a full re-track durably drops the repo's
|
|
// enrichment edges.
|
|
func TestIndexCtx_FlagsFullReindexForEnrichForce(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writeFile(t, filepath.Join(dir, "main.go"), "package main\n\nfunc Hello() {}\n")
|
|
|
|
g := graph.New()
|
|
idx := newTestIndexer(g)
|
|
require.False(t, idx.fullReindexed.Load(), "a fresh indexer has re-parsed nothing")
|
|
|
|
_, err := idx.Index(dir)
|
|
require.NoError(t, err)
|
|
assert.True(t, idx.fullReindexed.Load(),
|
|
"a whole-repo re-parse must flag fullReindexed so deferred enrichment forces past the completion marker")
|
|
}
|
|
|
|
// TestIncrementalReindexPaths_LeavesFullReindexClear is the boundary: a scoped
|
|
// incremental re-parse touches only the changed files and leaves every other
|
|
// file's enrichment edges intact, so it must NOT force a whole-repo re-enrich
|
|
// (which would re-run the multi-minute hover pass on every scoped save). It
|
|
// must, however, flag reparsedThisRun — the re-parsed file's own hover edges
|
|
// WERE evicted, so the deferred pass still has to run past the completion
|
|
// marker, just scoped to that file rather than the whole repo.
|
|
func TestIncrementalReindexPaths_LeavesFullReindexClear(t *testing.T) {
|
|
dir := t.TempDir()
|
|
main := filepath.Join(dir, "main.go")
|
|
writeFile(t, main, "package main\n\nfunc Hello() {}\n")
|
|
|
|
g := graph.New()
|
|
idx := newTestIndexer(g)
|
|
result, err := idx.IncrementalReindexPaths(dir, []string{main})
|
|
require.NoError(t, err)
|
|
require.Positive(t, result.StaleFileCount, "the fresh file must re-parse")
|
|
assert.False(t, idx.fullReindexed.Load(),
|
|
"a scoped incremental re-parse must leave fullReindexed clear")
|
|
assert.True(t, idx.reparsedThisRun.Load(),
|
|
"a scoped incremental re-parse still evicted the changed file's hover edges, so it must flag reparsedThisRun to force the deferred enrich past the completion marker")
|
|
}
|
|
|
|
// TestIncrementalReindexPaths_MtimeBumpFlagsReparse mirrors the durable-loss
|
|
// scenario: after a repo is indexed and its enrichment marker recorded at a
|
|
// clean HEAD, a git checkout-and-back / stash-pop / no-op save bumps a file's
|
|
// mtime without changing its content or moving HEAD. The scoped warm-restart
|
|
// route re-parses the mtime-drifted file (dropping its hover edges), so it must
|
|
// flag reparsedThisRun — otherwise runDeferredEnrich builds Force=false, the
|
|
// completion marker still matches on the clean tree, and the re-parsed file's
|
|
// LSP edges stay durably gone.
|
|
func TestIncrementalReindexPaths_MtimeBumpFlagsReparse(t *testing.T) {
|
|
dir := t.TempDir()
|
|
main := filepath.Join(dir, "main.go")
|
|
const content = "package main\n\nfunc Hello() {}\n"
|
|
writeFile(t, main, content)
|
|
|
|
g := graph.New()
|
|
idx := newTestIndexer(g)
|
|
_, err := idx.Index(dir)
|
|
require.NoError(t, err)
|
|
require.False(t, idx.reparsedThisRun.Load(),
|
|
"reparsedThisRun is a scoped-path flag; a full Index uses fullReindexed instead")
|
|
|
|
// Same bytes, later mtime — a touch / checkout-roundtrip, content unchanged.
|
|
bumpMtime(t, main, content)
|
|
result, err := idx.IncrementalReindexPaths(dir, []string{main})
|
|
require.NoError(t, err)
|
|
require.Positive(t, result.StaleFileCount,
|
|
"an mtime bump must classify the content-identical file as stale and re-parse it")
|
|
assert.True(t, idx.reparsedThisRun.Load(),
|
|
"a scoped re-parse of an mtime-drifted file must force the deferred enrich past the completion marker")
|
|
}
|
|
|
|
// TestIncrementalReindex_MtimeBumpFlagsReparse is the whole-root sibling of
|
|
// TestIncrementalReindexPaths_MtimeBumpFlagsReparse: the non-scoped incremental
|
|
// path also re-parses mtime-drifted files and must flag reparsedThisRun.
|
|
func TestIncrementalReindex_MtimeBumpFlagsReparse(t *testing.T) {
|
|
dir := t.TempDir()
|
|
main := filepath.Join(dir, "main.go")
|
|
const content = "package main\n\nfunc Hello() {}\n"
|
|
writeFile(t, main, content)
|
|
|
|
g := graph.New()
|
|
idx := newTestIndexer(g)
|
|
_, err := idx.Index(dir)
|
|
require.NoError(t, err)
|
|
require.False(t, idx.reparsedThisRun.Load())
|
|
|
|
bumpMtime(t, main, content)
|
|
result, err := idx.IncrementalReindex(dir)
|
|
require.NoError(t, err)
|
|
require.Positive(t, result.StaleFileCount)
|
|
assert.True(t, idx.reparsedThisRun.Load(),
|
|
"a whole-root incremental re-parse must also force the deferred enrich past the completion marker")
|
|
}
|
|
|
|
// TestIncrementalReindex_NoChangeLeavesReparseClear guards the other side: a
|
|
// zero-change reconcile re-parses nothing, so reparsedThisRun stays clear and an
|
|
// unchanged repo at a current marker is legitimately skipped.
|
|
func TestIncrementalReindex_NoChangeLeavesReparseClear(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writeFile(t, filepath.Join(dir, "main.go"), "package main\n\nfunc Hello() {}\n")
|
|
|
|
g := graph.New()
|
|
idx := newTestIndexer(g)
|
|
_, err := idx.Index(dir)
|
|
require.NoError(t, err)
|
|
|
|
result, err := idx.IncrementalReindex(dir)
|
|
require.NoError(t, err)
|
|
require.Zero(t, result.StaleFileCount, "nothing changed on disk")
|
|
assert.False(t, idx.reparsedThisRun.Load(),
|
|
"a zero-change reconcile must not force re-enrichment")
|
|
}
|
|
|
|
func TestIndex_MultipleFiles(t *testing.T) {
|
|
dir := setupTestDir(t)
|
|
|
|
g := graph.New()
|
|
idx := newTestIndexer(g)
|
|
result, err := idx.Index(dir)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, 2, result.FileCount) // main.go + pkg/util.go (vendor excluded)
|
|
assert.Greater(t, result.NodeCount, 4)
|
|
}
|
|
|
|
func TestIndex_ExcludePatterns(t *testing.T) {
|
|
dir := setupTestDir(t)
|
|
|
|
g := graph.New()
|
|
idx := newTestIndexer(g)
|
|
_, err := idx.Index(dir)
|
|
require.NoError(t, err)
|
|
|
|
// Vendor file should not be indexed.
|
|
nodes := g.FindNodesByName("Ignored")
|
|
assert.Empty(t, nodes)
|
|
}
|
|
|
|
func TestIndex_RipgrepIgnoreFiles(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writeFile(t, filepath.Join(dir, "main.go"), "package main\n\nfunc Kept() {}\n")
|
|
writeFile(t, filepath.Join(dir, "gen.go"), "package main\n\nfunc FromGen() {}\n")
|
|
writeFile(t, filepath.Join(dir, ".ignore"), "gen.go\n")
|
|
|
|
// A nested directory carries its own ripgrep ignore file; it must
|
|
// scope to that subtree only.
|
|
sub := filepath.Join(dir, "sub")
|
|
require.NoError(t, os.MkdirAll(sub, 0o755))
|
|
writeFile(t, filepath.Join(sub, "keep.go"), "package sub\n\nfunc SubKept() {}\n")
|
|
writeFile(t, filepath.Join(sub, "drop.go"), "package sub\n\nfunc SubDropped() {}\n")
|
|
writeFile(t, filepath.Join(sub, ".rgignore"), "drop.go\n")
|
|
|
|
g := graph.New()
|
|
idx := newTestIndexer(g)
|
|
_, err := idx.Index(dir)
|
|
require.NoError(t, err)
|
|
|
|
assert.NotEmpty(t, g.FindNodesByName("Kept"), "main.go should be indexed")
|
|
assert.NotEmpty(t, g.FindNodesByName("SubKept"), "sub/keep.go should be indexed")
|
|
assert.Empty(t, g.FindNodesByName("FromGen"), ".ignore should exclude gen.go")
|
|
assert.Empty(t, g.FindNodesByName("SubDropped"), "sub/.rgignore should exclude sub/drop.go")
|
|
}
|
|
|
|
func TestEdgeSanityViolated(t *testing.T) {
|
|
// A populated index with no edges trips the check.
|
|
broken := &IndexResult{FileCount: 5, NodeCount: 40, EdgeCount: 0}
|
|
assert.True(t, broken.EdgeSanityViolated(),
|
|
"files+nodes but zero edges should violate the edge-sanity check")
|
|
|
|
// A healthy index does not.
|
|
healthy := &IndexResult{FileCount: 5, NodeCount: 40, EdgeCount: 60}
|
|
assert.False(t, healthy.EdgeSanityViolated(),
|
|
"an index with edges must not trip the check")
|
|
|
|
// An empty repo (no files) is not a violation — nothing to index.
|
|
empty := &IndexResult{FileCount: 0, NodeCount: 0, EdgeCount: 0}
|
|
assert.False(t, empty.EdgeSanityViolated(),
|
|
"an empty repo must not trip the check")
|
|
|
|
// nil is safe.
|
|
var nilResult *IndexResult
|
|
assert.False(t, nilResult.EdgeSanityViolated(), "nil result must not trip the check")
|
|
}
|
|
|
|
func TestGrepText(t *testing.T) {
|
|
dir := setupTestDir(t)
|
|
g := graph.New()
|
|
idx := newTestIndexer(g)
|
|
_, err := idx.Index(dir)
|
|
require.NoError(t, err)
|
|
|
|
// A literal present in main.go.
|
|
hits := idx.GrepText("func helper", 0)
|
|
require.NotEmpty(t, hits, "func helper should be found")
|
|
found := false
|
|
for _, h := range hits {
|
|
if h.Path == "main.go" {
|
|
found = true
|
|
}
|
|
}
|
|
assert.True(t, found, "func helper should be located in main.go")
|
|
|
|
// A literal present nowhere.
|
|
assert.Empty(t, idx.GrepText("nonexistent_zzz_literal", 0))
|
|
|
|
// After an incremental reindex the warm cache is rebuilt, so a
|
|
// freshly added literal is visible.
|
|
bumpMtime(t, filepath.Join(dir, "main.go"),
|
|
"package main\n\nfunc main() {}\n\nfunc BrandNew() {}\n")
|
|
_, err = idx.IncrementalReindex(dir)
|
|
require.NoError(t, err)
|
|
assert.NotEmpty(t, idx.GrepText("func BrandNew", 0),
|
|
"GrepText must reflect content added by an incremental reindex")
|
|
}
|
|
|
|
func TestIndex_EdgeSanityHolds(t *testing.T) {
|
|
// A real index of even a tiny repo produces edges, so the
|
|
// edge-sanity check passes — guards the invariant against false
|
|
// positives on legitimate indexes.
|
|
dir := setupTestDir(t)
|
|
g := graph.New()
|
|
idx := newTestIndexer(g)
|
|
result, err := idx.Index(dir)
|
|
require.NoError(t, err)
|
|
assert.False(t, result.EdgeSanityViolated(),
|
|
"a normal index must not trip the edge-sanity check (files=%d nodes=%d edges=%d)",
|
|
result.FileCount, result.NodeCount, result.EdgeCount)
|
|
|
|
// A graph built only through the indexer's sanctioned mutation
|
|
// paths is provenance-consistent: every edge's out- and in-edge
|
|
// adjacency views agree on its identity hash.
|
|
require.NoError(t, g.VerifyEdgeIdentities(),
|
|
"a normally-indexed graph must have internally consistent edge identities")
|
|
}
|
|
|
|
func TestIndex_UnsupportedFile(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writeFile(t, filepath.Join(dir, "readme.txt"), "hello world")
|
|
|
|
g := graph.New()
|
|
idx := newTestIndexer(g)
|
|
result, err := idx.Index(dir)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, 0, result.FileCount)
|
|
}
|
|
|
|
func TestIndexFile_SingleFile(t *testing.T) {
|
|
dir := t.TempDir()
|
|
filePath := filepath.Join(dir, "main.go")
|
|
writeFile(t, filePath, `package main
|
|
|
|
func Original() {}
|
|
`)
|
|
|
|
g := graph.New()
|
|
idx := newTestIndexer(g)
|
|
_, err := idx.Index(dir)
|
|
require.NoError(t, err)
|
|
|
|
origNodes := g.FindNodesByName("Original")
|
|
require.Len(t, origNodes, 1)
|
|
|
|
// Modify and re-index single file.
|
|
writeFile(t, filePath, `package main
|
|
|
|
func Replaced() {}
|
|
`)
|
|
require.NoError(t, idx.IndexFile(filePath))
|
|
|
|
assert.Empty(t, g.FindNodesByName("Original"))
|
|
assert.Len(t, g.FindNodesByName("Replaced"), 1)
|
|
}
|
|
|
|
func TestMtimeTracking(t *testing.T) {
|
|
dir := t.TempDir()
|
|
goFile := filepath.Join(dir, "main.go")
|
|
writeFile(t, goFile, `package main
|
|
|
|
func Hello() {}
|
|
`)
|
|
|
|
g := graph.New()
|
|
idx := newTestIndexer(g)
|
|
result, err := idx.Index(dir)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, result.FileCount)
|
|
|
|
// FileMtimes should be populated with the indexed file.
|
|
mtimes := idx.FileMtimes()
|
|
assert.NotEmpty(t, mtimes, "fileMtimes should be populated after Index()")
|
|
assert.Contains(t, mtimes, "main.go", "fileMtimes should contain the indexed file")
|
|
assert.Greater(t, mtimes["main.go"], int64(0), "mtime should be a positive unix nano value")
|
|
}
|
|
|
|
func TestMtimeIsStale_FreshFile(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writeFile(t, filepath.Join(dir, "main.go"), `package main
|
|
|
|
func Hello() {}
|
|
`)
|
|
|
|
g := graph.New()
|
|
idx := newTestIndexer(g)
|
|
_, err := idx.Index(dir)
|
|
require.NoError(t, err)
|
|
|
|
// A just-indexed file should not be stale.
|
|
assert.False(t, idx.IsStale("main.go"), "file should not be stale immediately after indexing")
|
|
}
|
|
|
|
func TestMtimeIsStale_ModifiedFile(t *testing.T) {
|
|
dir := t.TempDir()
|
|
goFile := filepath.Join(dir, "main.go")
|
|
writeFile(t, goFile, `package main
|
|
|
|
func Hello() {}
|
|
`)
|
|
|
|
g := graph.New()
|
|
idx := newTestIndexer(g)
|
|
_, err := idx.Index(dir)
|
|
require.NoError(t, err)
|
|
|
|
// Modify the file after indexing — ensure mtime actually changes.
|
|
// Some filesystems have coarse mtime resolution, so we sleep briefly.
|
|
time.Sleep(50 * time.Millisecond)
|
|
writeFile(t, goFile, `package main
|
|
|
|
func HelloModified() {}
|
|
`)
|
|
|
|
assert.True(t, idx.IsStale("main.go"), "file should be stale after modification")
|
|
}
|
|
|
|
func TestMtimeIsStale_UnknownFile(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writeFile(t, filepath.Join(dir, "main.go"), `package main
|
|
|
|
func Hello() {}
|
|
`)
|
|
|
|
g := graph.New()
|
|
idx := newTestIndexer(g)
|
|
_, err := idx.Index(dir)
|
|
require.NoError(t, err)
|
|
|
|
// A file not in the index should be treated as stale.
|
|
assert.True(t, idx.IsStale("unknown.go"), "unknown file should be treated as stale")
|
|
}
|
|
|
|
func TestMtimeUpdatedAfterIndexFile(t *testing.T) {
|
|
dir := t.TempDir()
|
|
goFile := filepath.Join(dir, "main.go")
|
|
writeFile(t, goFile, `package main
|
|
|
|
func Original() {}
|
|
`)
|
|
|
|
g := graph.New()
|
|
idx := newTestIndexer(g)
|
|
_, err := idx.Index(dir)
|
|
require.NoError(t, err)
|
|
|
|
originalMtime := idx.FileMtimes()["main.go"]
|
|
|
|
// Modify and re-index the single file.
|
|
time.Sleep(50 * time.Millisecond)
|
|
writeFile(t, goFile, `package main
|
|
|
|
func Replaced() {}
|
|
`)
|
|
require.NoError(t, idx.IndexFile(goFile))
|
|
|
|
updatedMtime := idx.FileMtimes()["main.go"]
|
|
assert.Greater(t, updatedMtime, originalMtime, "mtime should be updated after IndexFile")
|
|
assert.False(t, idx.IsStale("main.go"), "file should not be stale after re-indexing")
|
|
}
|
|
|
|
func TestEvictFile(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writeFile(t, filepath.Join(dir, "main.go"), `package main
|
|
|
|
func Foo() {}
|
|
`)
|
|
|
|
g := graph.New()
|
|
idx := newTestIndexer(g)
|
|
_, err := idx.Index(dir)
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, g.FindNodesByName("Foo"))
|
|
|
|
n, e := idx.EvictFile(filepath.Join(dir, "main.go"))
|
|
assert.Greater(t, n, 0)
|
|
assert.Greater(t, e, 0)
|
|
assert.Empty(t, g.FindNodesByName("Foo"))
|
|
}
|
|
|
|
func TestIndex_WithRepoPrefix(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writeFile(t, filepath.Join(dir, "main.go"), `package main
|
|
|
|
func Hello() {}
|
|
`)
|
|
|
|
g := graph.New()
|
|
idx := newTestIndexer(g)
|
|
idx.SetRepoPrefix("myrepo")
|
|
|
|
result, err := idx.Index(dir)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, result.FileCount)
|
|
assert.Greater(t, result.NodeCount, 0)
|
|
|
|
// Verify node IDs are prefixed.
|
|
nodes := g.FindNodesByName("Hello")
|
|
require.Len(t, nodes, 1)
|
|
assert.Equal(t, "myrepo/main.go::Hello", nodes[0].ID)
|
|
assert.Equal(t, "myrepo/main.go", nodes[0].FilePath)
|
|
assert.Equal(t, "myrepo", nodes[0].RepoPrefix)
|
|
|
|
// Verify file node is also prefixed.
|
|
fileNodes := g.GetFileNodes("myrepo/main.go")
|
|
assert.NotEmpty(t, fileNodes)
|
|
|
|
// Verify repo index is populated.
|
|
repoNodes := g.GetRepoNodes("myrepo")
|
|
assert.NotEmpty(t, repoNodes)
|
|
for _, n := range repoNodes {
|
|
assert.Equal(t, "myrepo", n.RepoPrefix)
|
|
}
|
|
}
|
|
|
|
func TestIndex_WithoutRepoPrefix_BackwardCompatible(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writeFile(t, filepath.Join(dir, "main.go"), `package main
|
|
|
|
func Hello() {}
|
|
`)
|
|
|
|
g := graph.New()
|
|
idx := newTestIndexer(g)
|
|
// No SetRepoPrefix — single-repo mode.
|
|
|
|
result, err := idx.Index(dir)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, result.FileCount)
|
|
|
|
nodes := g.FindNodesByName("Hello")
|
|
require.Len(t, nodes, 1)
|
|
assert.Equal(t, "main.go::Hello", nodes[0].ID)
|
|
assert.Equal(t, "main.go", nodes[0].FilePath)
|
|
assert.Equal(t, "", nodes[0].RepoPrefix)
|
|
}
|
|
|
|
func TestIndexFile_WithRepoPrefix(t *testing.T) {
|
|
dir := t.TempDir()
|
|
filePath := filepath.Join(dir, "main.go")
|
|
writeFile(t, filePath, `package main
|
|
|
|
func Original() {}
|
|
`)
|
|
|
|
g := graph.New()
|
|
idx := newTestIndexer(g)
|
|
idx.SetRepoPrefix("myrepo")
|
|
|
|
_, err := idx.Index(dir)
|
|
require.NoError(t, err)
|
|
|
|
origNodes := g.FindNodesByName("Original")
|
|
require.Len(t, origNodes, 1)
|
|
assert.Equal(t, "myrepo/main.go::Original", origNodes[0].ID)
|
|
assert.Equal(t, "myrepo", origNodes[0].RepoPrefix)
|
|
|
|
// Modify and re-index single file.
|
|
writeFile(t, filePath, `package main
|
|
|
|
func Replaced() {}
|
|
`)
|
|
require.NoError(t, idx.IndexFile(filePath))
|
|
|
|
assert.Empty(t, g.FindNodesByName("Original"))
|
|
replaced := g.FindNodesByName("Replaced")
|
|
require.Len(t, replaced, 1)
|
|
assert.Equal(t, "myrepo/main.go::Replaced", replaced[0].ID)
|
|
assert.Equal(t, "myrepo", replaced[0].RepoPrefix)
|
|
}
|
|
|
|
func TestEvictFile_WithRepoPrefix(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writeFile(t, filepath.Join(dir, "main.go"), `package main
|
|
|
|
func Foo() {}
|
|
`)
|
|
|
|
g := graph.New()
|
|
idx := newTestIndexer(g)
|
|
idx.SetRepoPrefix("myrepo")
|
|
|
|
_, err := idx.Index(dir)
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, g.FindNodesByName("Foo"))
|
|
|
|
n, e := idx.EvictFile(filepath.Join(dir, "main.go"))
|
|
assert.Greater(t, n, 0)
|
|
assert.Greater(t, e, 0)
|
|
assert.Empty(t, g.FindNodesByName("Foo"))
|
|
}
|
|
|
|
func TestRepoPrefix_EdgesArePrefixed(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writeFile(t, filepath.Join(dir, "main.go"), `package main
|
|
|
|
func Hello() {}
|
|
`)
|
|
|
|
g := graph.New()
|
|
idx := newTestIndexer(g)
|
|
idx.SetRepoPrefix("myrepo")
|
|
|
|
_, err := idx.Index(dir)
|
|
require.NoError(t, err)
|
|
|
|
// All edges should have prefixed From/To and FilePath.
|
|
edges := g.AllEdges()
|
|
for _, e := range edges {
|
|
assert.True(t, strings.HasPrefix(e.From, "myrepo/"),
|
|
"edge From should be prefixed: %s", e.From)
|
|
assert.True(t, strings.HasPrefix(e.To, "myrepo/"),
|
|
"edge To should be prefixed: %s", e.To)
|
|
assert.True(t, strings.HasPrefix(e.FilePath, "myrepo/"),
|
|
"edge FilePath should be prefixed: %s", e.FilePath)
|
|
}
|
|
}
|
|
|
|
func TestRepoPrefix_SetterGetter(t *testing.T) {
|
|
g := graph.New()
|
|
idx := newTestIndexer(g)
|
|
|
|
assert.Equal(t, "", idx.RepoPrefix())
|
|
idx.SetRepoPrefix("myrepo")
|
|
assert.Equal(t, "myrepo", idx.RepoPrefix())
|
|
}
|