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
216 lines
8.1 KiB
Go
216 lines
8.1 KiB
Go
package indexer
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/zzet/gortex/internal/graph"
|
|
)
|
|
|
|
// TestIncrementalReindexPaths_EmptyPathsFallsBackToWholeRoot verifies
|
|
// that passing no paths makes IncrementalReindexPaths behave exactly
|
|
// like a whole-root IncrementalReindex: a changed file anywhere in the
|
|
// tree is picked up.
|
|
func TestIncrementalReindexPaths_EmptyPathsFallsBackToWholeRoot(t *testing.T) {
|
|
dir := t.TempDir()
|
|
require.NoError(t, os.MkdirAll(filepath.Join(dir, "pkg"), 0o755))
|
|
writeFile(t, filepath.Join(dir, "main.go"), "package main\n\nfunc Main() {}\n")
|
|
writeFile(t, filepath.Join(dir, "pkg", "util.go"), "package pkg\n\nfunc Util() {}\n")
|
|
|
|
g := graph.New()
|
|
idx := newTestIndexer(g)
|
|
_, err := idx.Index(dir)
|
|
require.NoError(t, err)
|
|
|
|
bumpMtime(t, filepath.Join(dir, "pkg", "util.go"),
|
|
"package pkg\n\nfunc Util() {}\n\nfunc UtilTwo() {}\n")
|
|
|
|
res, err := idx.IncrementalReindexPaths(dir, nil)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, res)
|
|
assert.Equal(t, 1, res.StaleFileCount,
|
|
"empty paths must scan the whole root and pick up the changed file")
|
|
assert.NotEmpty(t, g.FindNodesByName("UtilTwo"),
|
|
"the whole-root pass must re-index the changed file")
|
|
}
|
|
|
|
// TestIncrementalReindexPaths_ScopesToDirectory checks that a directory
|
|
// path scopes the pass: a stale file inside the scoped directory is
|
|
// re-indexed, while a stale file outside it is left untouched.
|
|
func TestIncrementalReindexPaths_ScopesToDirectory(t *testing.T) {
|
|
dir := t.TempDir()
|
|
require.NoError(t, os.MkdirAll(filepath.Join(dir, "in"), 0o755))
|
|
require.NoError(t, os.MkdirAll(filepath.Join(dir, "out"), 0o755))
|
|
writeFile(t, filepath.Join(dir, "in", "a.go"), "package in\n\nfunc A() {}\n")
|
|
writeFile(t, filepath.Join(dir, "out", "b.go"), "package out\n\nfunc B() {}\n")
|
|
|
|
g := graph.New()
|
|
idx := newTestIndexer(g)
|
|
_, err := idx.Index(dir)
|
|
require.NoError(t, err)
|
|
|
|
// Make BOTH files stale, then scope the reindex to the "in" dir.
|
|
bumpMtime(t, filepath.Join(dir, "in", "a.go"),
|
|
"package in\n\nfunc A() {}\n\nfunc AScoped() {}\n")
|
|
bumpMtime(t, filepath.Join(dir, "out", "b.go"),
|
|
"package out\n\nfunc B() {}\n\nfunc BUnscoped() {}\n")
|
|
|
|
res, err := idx.IncrementalReindexPaths(dir, []string{filepath.Join(dir, "in")})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, res.StaleFileCount,
|
|
"only the file inside the scoped directory should be re-indexed")
|
|
assert.NotEmpty(t, g.FindNodesByName("AScoped"),
|
|
"the scoped file's new symbol must be in the graph")
|
|
assert.Empty(t, g.FindNodesByName("BUnscoped"),
|
|
"a file outside the scoped directory must NOT be re-indexed")
|
|
}
|
|
|
|
// TestIncrementalReindexPaths_RelativePathScope verifies that scoped
|
|
// paths may be supplied relative to the repository root, not just
|
|
// absolute.
|
|
func TestIncrementalReindexPaths_RelativePathScope(t *testing.T) {
|
|
dir := t.TempDir()
|
|
require.NoError(t, os.MkdirAll(filepath.Join(dir, "sub"), 0o755))
|
|
writeFile(t, filepath.Join(dir, "sub", "c.go"), "package sub\n\nfunc C() {}\n")
|
|
writeFile(t, filepath.Join(dir, "root.go"), "package main\n\nfunc Root() {}\n")
|
|
|
|
g := graph.New()
|
|
idx := newTestIndexer(g)
|
|
_, err := idx.Index(dir)
|
|
require.NoError(t, err)
|
|
|
|
bumpMtime(t, filepath.Join(dir, "sub", "c.go"),
|
|
"package sub\n\nfunc C() {}\n\nfunc CRel() {}\n")
|
|
bumpMtime(t, filepath.Join(dir, "root.go"),
|
|
"package main\n\nfunc Root() {}\n\nfunc RootRel() {}\n")
|
|
|
|
// "sub" is repo-root-relative.
|
|
res, err := idx.IncrementalReindexPaths(dir, []string{"sub"})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, res.StaleFileCount)
|
|
assert.NotEmpty(t, g.FindNodesByName("CRel"))
|
|
assert.Empty(t, g.FindNodesByName("RootRel"),
|
|
"the root-level file is outside the scoped relative path")
|
|
}
|
|
|
|
// TestIncrementalReindexPaths_ScopesToSingleFile checks that a single
|
|
// file path scopes the pass to exactly that file.
|
|
func TestIncrementalReindexPaths_ScopesToSingleFile(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writeFile(t, filepath.Join(dir, "one.go"), "package main\n\nfunc One() {}\n")
|
|
writeFile(t, filepath.Join(dir, "two.go"), "package main\n\nfunc Two() {}\n")
|
|
|
|
g := graph.New()
|
|
idx := newTestIndexer(g)
|
|
_, err := idx.Index(dir)
|
|
require.NoError(t, err)
|
|
|
|
bumpMtime(t, filepath.Join(dir, "one.go"),
|
|
"package main\n\nfunc One() {}\n\nfunc OneEdited() {}\n")
|
|
bumpMtime(t, filepath.Join(dir, "two.go"),
|
|
"package main\n\nfunc Two() {}\n\nfunc TwoEdited() {}\n")
|
|
|
|
res, err := idx.IncrementalReindexPaths(dir, []string{filepath.Join(dir, "one.go")})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, res.StaleFileCount)
|
|
assert.Equal(t, 1, res.FileCount,
|
|
"FileCount for a single-file scope is the one in-scope file")
|
|
assert.NotEmpty(t, g.FindNodesByName("OneEdited"))
|
|
assert.Empty(t, g.FindNodesByName("TwoEdited"),
|
|
"the unscoped file must not be re-indexed")
|
|
}
|
|
|
|
// TestIncrementalReindexPaths_EvictsDeletedFileInScope verifies that a
|
|
// file deleted from disk under a scoped path is evicted, while a
|
|
// deletion outside the scope is left alone.
|
|
func TestIncrementalReindexPaths_EvictsDeletedFileInScope(t *testing.T) {
|
|
dir := t.TempDir()
|
|
require.NoError(t, os.MkdirAll(filepath.Join(dir, "in"), 0o755))
|
|
require.NoError(t, os.MkdirAll(filepath.Join(dir, "out"), 0o755))
|
|
inGone := filepath.Join(dir, "in", "gone.go")
|
|
outGone := filepath.Join(dir, "out", "gone.go")
|
|
writeFile(t, inGone, "package in\n\nfunc InGone() {}\n")
|
|
writeFile(t, outGone, "package out\n\nfunc OutGone() {}\n")
|
|
|
|
g := graph.New()
|
|
idx := newTestIndexer(g)
|
|
_, err := idx.Index(dir)
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, g.FindNodesByName("InGone"))
|
|
require.NotEmpty(t, g.FindNodesByName("OutGone"))
|
|
|
|
require.NoError(t, os.Remove(inGone))
|
|
require.NoError(t, os.Remove(outGone))
|
|
|
|
_, err = idx.IncrementalReindexPaths(dir, []string{filepath.Join(dir, "in")})
|
|
require.NoError(t, err)
|
|
|
|
assert.Empty(t, g.FindNodesByName("InGone"),
|
|
"a file deleted under the scoped path must be evicted")
|
|
assert.NotEmpty(t, g.FindNodesByName("OutGone"),
|
|
"a deletion outside the scoped path must NOT be evicted by a scoped pass")
|
|
}
|
|
|
|
// TestIncrementalReindexPaths_RejectsPathOutsideRoot verifies that a
|
|
// scoped path escaping the repository root is rejected — scoping is a
|
|
// narrowing operation, never an escape hatch.
|
|
func TestIncrementalReindexPaths_RejectsPathOutsideRoot(t *testing.T) {
|
|
root := t.TempDir()
|
|
outside := t.TempDir()
|
|
writeFile(t, filepath.Join(root, "main.go"), "package main\n\nfunc Main() {}\n")
|
|
|
|
g := graph.New()
|
|
idx := newTestIndexer(g)
|
|
_, err := idx.Index(root)
|
|
require.NoError(t, err)
|
|
|
|
_, err = idx.IncrementalReindexPaths(root, []string{outside})
|
|
require.Error(t, err, "a path outside the repo root must be rejected")
|
|
assert.Contains(t, err.Error(), "outside repository root")
|
|
}
|
|
|
|
// TestIncrementalReindexPaths_ConvergesForScopedFile is the consistency
|
|
// invariant for the scoped path: editing a file and reconciling it with
|
|
// a file-scoped IncrementalReindexPaths must leave that file's symbols
|
|
// identical to a full index of the same disk state.
|
|
func TestIncrementalReindexPaths_ConvergesForScopedFile(t *testing.T) {
|
|
build := func(dir string) {
|
|
writeFile(t, filepath.Join(dir, "main.go"),
|
|
"package main\n\nfunc main() { helper() }\n\nfunc helper() {}\n")
|
|
writeFile(t, filepath.Join(dir, "other.go"),
|
|
"package main\n\nfunc Other() {}\n")
|
|
}
|
|
|
|
dirA := t.TempDir()
|
|
build(dirA)
|
|
gA := graph.New()
|
|
idxA := newTestIndexer(gA)
|
|
_, err := idxA.Index(dirA)
|
|
require.NoError(t, err)
|
|
|
|
bumpMtime(t, filepath.Join(dirA, "main.go"),
|
|
"package main\n\nfunc main() { helper(); helper() }\n\nfunc helper() {}\n")
|
|
_, err = idxA.IncrementalReindexPaths(dirA, []string{filepath.Join(dirA, "main.go")})
|
|
require.NoError(t, err)
|
|
|
|
dirB := t.TempDir()
|
|
build(dirB)
|
|
writeFile(t, filepath.Join(dirB, "main.go"),
|
|
"package main\n\nfunc main() { helper(); helper() }\n\nfunc helper() {}\n")
|
|
gB := graph.New()
|
|
idxB := newTestIndexer(gB)
|
|
_, err = idxB.Index(dirB)
|
|
require.NoError(t, err)
|
|
|
|
// main.go's symbols must match between the scoped-incremental and
|
|
// the full-index graphs.
|
|
assert.Len(t, gA.FindNodesByName("main"), len(gB.FindNodesByName("main")))
|
|
assert.Len(t, gA.FindNodesByName("helper"), len(gB.FindNodesByName("helper")))
|
|
assert.NotEmpty(t, gA.FindNodesByName("Other"),
|
|
"a scoped reindex must leave the untouched file's nodes intact")
|
|
}
|