chore: import upstream snapshot with attribution
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
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
This commit is contained in:
@@ -0,0 +1,272 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/zzet/gortex/internal/excludes"
|
||||
)
|
||||
|
||||
func newTestConfigManager(t *testing.T) *ConfigManager {
|
||||
t.Helper()
|
||||
cm, err := NewConfigManager("/tmp/nonexistent-gortex-cache-test/config.yaml")
|
||||
require.NoError(t, err)
|
||||
return cm
|
||||
}
|
||||
|
||||
func writeGitignore(t *testing.T, dir, content string) {
|
||||
t.Helper()
|
||||
require.NoError(t, os.WriteFile(filepath.Join(dir, ".gitignore"), []byte(content), 0o644))
|
||||
}
|
||||
|
||||
func writeWorkspace(t *testing.T, dir, content string) {
|
||||
t.Helper()
|
||||
require.NoError(t, os.WriteFile(filepath.Join(dir, ".gortex.yaml"), []byte(content), 0o644))
|
||||
}
|
||||
|
||||
// bumpMtime forces a stat-visible change so invalidation fires regardless
|
||||
// of the filesystem's mtime resolution.
|
||||
func bumpMtime(t *testing.T, path string) {
|
||||
t.Helper()
|
||||
future := time.Now().Add(2 * time.Second)
|
||||
require.NoError(t, os.Chtimes(path, future, future))
|
||||
}
|
||||
|
||||
// countingReader wires a call counter into the cache's file reader so a
|
||||
// test can assert that a cache hit never re-opens the `.gitignore`.
|
||||
func countingReader(cm *ConfigManager) *int64 {
|
||||
var reads int64
|
||||
cm.excludeCache.readFn = func(p string) []string {
|
||||
atomic.AddInt64(&reads, 1)
|
||||
return loadRepoGitignore(p)
|
||||
}
|
||||
return &reads
|
||||
}
|
||||
|
||||
func TestExcludeCache_CacheHitDoesNotReReadGitignore(t *testing.T) {
|
||||
cm := newTestConfigManager(t)
|
||||
repoDir := t.TempDir()
|
||||
writeGitignore(t, repoDir, "node_modules/\n*.log\n")
|
||||
cm.LoadWorkspaceConfig("r", repoDir)
|
||||
|
||||
reads := countingReader(cm)
|
||||
|
||||
first := cm.EffectiveExclude("r")
|
||||
second := cm.EffectiveExclude("r")
|
||||
|
||||
assert.Contains(t, first, "node_modules/")
|
||||
assert.Contains(t, second, "node_modules/")
|
||||
assert.Contains(t, second, "*.log")
|
||||
assert.Equal(t, first, second, "cache hit must return the same layered list")
|
||||
assert.Equal(t, int64(1), atomic.LoadInt64(reads),
|
||||
"the second call must be a cache hit, not a re-read")
|
||||
}
|
||||
|
||||
func TestExcludeCache_IdenticalMatchingColdVsWarm(t *testing.T) {
|
||||
cm := newTestConfigManager(t)
|
||||
repoDir := t.TempDir()
|
||||
writeGitignore(t, repoDir, "build/\n*.tmp\ndata/raw/\n")
|
||||
cm.LoadWorkspaceConfig("r", repoDir)
|
||||
|
||||
cases := []struct {
|
||||
path string
|
||||
exclude bool
|
||||
}{
|
||||
{"build/output.o", true},
|
||||
{"src/main.go", false},
|
||||
{"scratch.tmp", true},
|
||||
{"data/raw/big.bin", true},
|
||||
{"data/clean/ok.csv", false},
|
||||
{".git/config", true}, // builtin baseline
|
||||
{"README.md", false},
|
||||
}
|
||||
|
||||
assertMatches := func(list []string, label string) {
|
||||
m := excludes.New(list)
|
||||
for _, c := range cases {
|
||||
assert.Equalf(t, c.exclude, m.MatchRel(c.path),
|
||||
"%s: path %q", label, c.path)
|
||||
}
|
||||
}
|
||||
|
||||
assertMatches(cm.EffectiveExclude("r"), "cold") // populates the cache
|
||||
assertMatches(cm.EffectiveExclude("r"), "warm") // cache hit
|
||||
}
|
||||
|
||||
func TestExcludeCache_InvalidatesOnGitignoreChange(t *testing.T) {
|
||||
cm := newTestConfigManager(t)
|
||||
repoDir := t.TempDir()
|
||||
writeGitignore(t, repoDir, "old-only/\n")
|
||||
cm.LoadWorkspaceConfig("r", repoDir)
|
||||
|
||||
reads := countingReader(cm)
|
||||
|
||||
first := cm.EffectiveExclude("r")
|
||||
assert.Contains(t, first, "old-only/")
|
||||
|
||||
// Different content (also a different byte size) plus a bumped mtime so
|
||||
// the change is detected even on a coarse-resolution filesystem.
|
||||
writeGitignore(t, repoDir, "new-only/\nsecond-new/\n")
|
||||
bumpMtime(t, filepath.Join(repoDir, ".gitignore"))
|
||||
|
||||
second := cm.EffectiveExclude("r")
|
||||
assert.Contains(t, second, "new-only/", "new patterns must take effect after the edit")
|
||||
assert.Contains(t, second, "second-new/")
|
||||
assert.NotContains(t, second, "old-only/", "stale patterns must be dropped")
|
||||
assert.Equal(t, int64(2), atomic.LoadInt64(reads),
|
||||
"the changed file must be re-read exactly once")
|
||||
}
|
||||
|
||||
func TestExcludeCache_InvalidatesOnWorkspaceReload(t *testing.T) {
|
||||
cm := newTestConfigManager(t)
|
||||
repoDir := t.TempDir()
|
||||
writeWorkspace(t, repoDir, "exclude:\n - \"first/**\"\n")
|
||||
cm.LoadWorkspaceConfig("r", repoDir)
|
||||
|
||||
first := cm.EffectiveExclude("r")
|
||||
assert.Contains(t, first, "first/**")
|
||||
assert.NotContains(t, first, "second/**")
|
||||
|
||||
// Reloading swaps the cached *Config pointer, which must invalidate the
|
||||
// merged entry even though the `.gitignore` (absent) is unchanged.
|
||||
writeWorkspace(t, repoDir, "exclude:\n - \"second/**\"\n")
|
||||
cm.LoadWorkspaceConfig("r", repoDir)
|
||||
|
||||
second := cm.EffectiveExclude("r")
|
||||
assert.Contains(t, second, "second/**", "workspace edit must be reflected")
|
||||
assert.NotContains(t, second, "first/**", "stale workspace patterns must be dropped")
|
||||
}
|
||||
|
||||
func TestExcludeCache_NoGitignoreNeverReads(t *testing.T) {
|
||||
cm := newTestConfigManager(t)
|
||||
repoDir := t.TempDir()
|
||||
cm.LoadWorkspaceConfig("r", repoDir)
|
||||
|
||||
reads := countingReader(cm)
|
||||
|
||||
got := cm.EffectiveExclude("r")
|
||||
_ = cm.EffectiveExclude("r")
|
||||
|
||||
assert.Equal(t, excludes.Builtin, got, "absent .gitignore yields the builtin baseline")
|
||||
assert.Equal(t, int64(0), atomic.LoadInt64(reads),
|
||||
"an absent .gitignore must not trigger a file read")
|
||||
}
|
||||
|
||||
func TestExcludeCache_ReturnedSliceIsClippedAndAppendSafe(t *testing.T) {
|
||||
cm := newTestConfigManager(t)
|
||||
repoDir := t.TempDir()
|
||||
writeGitignore(t, repoDir, "z-marker/\n")
|
||||
cm.LoadWorkspaceConfig("r", repoDir)
|
||||
|
||||
first := cm.EffectiveExclude("r")
|
||||
require.Equal(t, len(first), cap(first),
|
||||
"returned slice must be clipped so append reallocates")
|
||||
|
||||
// Appending to the returned slice must not disturb the shared cached
|
||||
// value handed to every other reader.
|
||||
_ = append(first, "mutant/**") //nolint:staticcheck // intentional: exercise append safety
|
||||
|
||||
second := cm.EffectiveExclude("r")
|
||||
assert.NotContains(t, second, "mutant/**",
|
||||
"appending to the returned slice must not corrupt the cache")
|
||||
assert.Equal(t, first, second)
|
||||
}
|
||||
|
||||
// uncachedEffectiveExclude reproduces the pre-cache EffectiveExclude body —
|
||||
// a fresh `.gitignore` read plus a fresh merge on every call — so the
|
||||
// benchmark can quote the baseline the cache replaces.
|
||||
func uncachedEffectiveExclude(gc *GlobalConfig, ws *Config, repoPrefix, repoPath string) []string {
|
||||
out := make([]string, 0, 32)
|
||||
out = append(out, excludes.Builtin...)
|
||||
if shouldRespectGitignore(ws) && repoPath != "" {
|
||||
out = append(out, loadRepoGitignore(repoPath)...)
|
||||
}
|
||||
if gc != nil {
|
||||
out = append(out, gc.Exclude...)
|
||||
if entry := gc.FindRepoByPrefix(repoPrefix); entry != nil {
|
||||
out = append(out, entry.Exclude...)
|
||||
}
|
||||
}
|
||||
if ws != nil {
|
||||
out = append(out, ws.Exclude...)
|
||||
if len(ws.Exclude) == 0 {
|
||||
out = append(out, ws.Index.Exclude...)
|
||||
out = append(out, ws.Watch.Exclude...)
|
||||
}
|
||||
}
|
||||
if ws != nil {
|
||||
for _, inc := range ws.Include {
|
||||
inc = strings.TrimSpace(inc)
|
||||
if inc == "" {
|
||||
continue
|
||||
}
|
||||
if !strings.HasPrefix(inc, "!") {
|
||||
inc = "!" + inc
|
||||
}
|
||||
out = append(out, inc)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
const benchGitignore = `# build output
|
||||
build/
|
||||
dist/
|
||||
*.log
|
||||
*.tmp
|
||||
node_modules/
|
||||
vendor/
|
||||
coverage/
|
||||
.cache/
|
||||
target/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.venv/
|
||||
data/raw/
|
||||
data/interim/
|
||||
testdata/large/
|
||||
*.bin
|
||||
*.gz
|
||||
.DS_Store
|
||||
`
|
||||
|
||||
func benchSetup(b *testing.B) *ConfigManager {
|
||||
b.Helper()
|
||||
cm, err := NewConfigManager("/tmp/nonexistent-gortex-cache-bench/config.yaml")
|
||||
require.NoError(b, err)
|
||||
repoDir := b.TempDir()
|
||||
require.NoError(b, os.WriteFile(filepath.Join(repoDir, ".gitignore"), []byte(benchGitignore), 0o644))
|
||||
cm.LoadWorkspaceConfig("r", repoDir)
|
||||
return cm
|
||||
}
|
||||
|
||||
func BenchmarkEffectiveExclude(b *testing.B) {
|
||||
cm := benchSetup(b)
|
||||
cm.EffectiveExclude("r") // warm the cache
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = cm.EffectiveExclude("r")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEffectiveExcludeUncached(b *testing.B) {
|
||||
cm := benchSetup(b)
|
||||
cm.mu.RLock()
|
||||
gc := cm.global
|
||||
ws := cm.workspace["r"]
|
||||
repoPath := cm.workspacePaths["r"]
|
||||
cm.mu.RUnlock()
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = uncachedEffectiveExclude(gc, ws, "r", repoPath)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user