Files
zzet--gortex/cmd/gortex/daemon_warmup_summary_test.go
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

149 lines
5.1 KiB
Go

package main
import (
"os"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
"github.com/zzet/gortex/internal/config"
"github.com/zzet/gortex/internal/graph"
"github.com/zzet/gortex/internal/indexer"
"github.com/zzet/gortex/internal/parser"
"github.com/zzet/gortex/internal/parser/languages"
)
// TestReconcileFileCount pins the filesReindexed arithmetic: a full retrack
// counts its whole FileCount (the incremental fields are meaningless on that
// path — see the ReconcileRepoCtx fullRetrack comment), an incremental
// reconcile counts only the files it actually touched.
func TestReconcileFileCount(t *testing.T) {
cases := []struct {
name string
res *indexer.IndexResult
want int
}{
{"nil result", nil, 0},
{
"full retrack uses FileCount, ignores stale/deleted",
&indexer.IndexResult{FullRetrack: true, FileCount: 42, StaleFileCount: 1, DeletedFileCount: 1},
42,
},
{
"incremental reconcile sums stale + deleted",
&indexer.IndexResult{FullRetrack: false, FileCount: 999, StaleFileCount: 3, DeletedFileCount: 2},
5,
},
{
"incremental reconcile with no changes",
&indexer.IndexResult{FullRetrack: false, StaleFileCount: 0, DeletedFileCount: 0},
0,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got := reconcileFileCount(tc.res)
assert.Equal(t, tc.want, got)
})
}
}
// TestLogWarmupSummary asserts the one-line warmup recap is emitted exactly
// once, carrying every field the log-joining problem this feature solves
// requires: the per-phase durations, the queryable/total elapsed, and the
// repo/file/enrichment counters.
func TestLogWarmupSummary(t *testing.T) {
core, logs := observer.New(zap.InfoLevel)
logger := zap.New(core)
warmup := &warmupTimings{
parse: 1500 * time.Millisecond,
resolve: 250 * time.Millisecond,
globalResolve: 80 * time.Millisecond,
endBatch: 40 * time.Millisecond,
reposChanged: 3,
filesReindexed: 17,
enrichScheduled: 2,
}
queryable := 1900 * time.Millisecond
total := 5200 * time.Millisecond
logWarmupSummary(logger, warmup, queryable, total)
entries := logs.FilterMessage("daemon: warmup summary").All()
require.Len(t, entries, 1, "expected exactly one warmup summary line")
fields := entries[0].ContextMap()
assert.InDelta(t, 1.5, fields["parse_s"], 0.001)
assert.InDelta(t, 0.25, fields["resolve_s"], 0.001)
assert.InDelta(t, 0.08, fields["global_resolve_s"], 0.001)
assert.InDelta(t, 0.04, fields["end_batch_s"], 0.001)
assert.InDelta(t, 1.9, fields["queryable_s"], 0.001)
assert.InDelta(t, 5.2, fields["total_s"], 0.001)
assert.EqualValues(t, 3, fields["repos_changed"])
assert.EqualValues(t, 17, fields["files_reindexed"])
assert.EqualValues(t, 2, fields["enrich_scheduled"])
}
// TestLogWarmupSummary_NilGuards confirms the helper no-ops instead of
// panicking when called without a logger or timings — defensive, since a
// warmup path that returns early hands back a non-nil *warmupTimings but a
// caller could still pass a nil logger in a degraded startup.
func TestLogWarmupSummary_NilGuards(t *testing.T) {
assert.NotPanics(t, func() {
logWarmupSummary(nil, &warmupTimings{}, 0, 0)
logWarmupSummary(zap.NewNop(), nil, 0, 0)
})
}
// TestWarmupDaemonState_TimingsAggregation exercises warmupDaemonState
// end-to-end against the in-memory backend with two freshly-tracked repos
// (no prior snapshot, so both take the cold TrackRepoCtx path). Confirms
// reposChanged and filesReindexed reflect real per-repo file counts, not
// just "something happened".
func TestWarmupDaemonState_TimingsAggregation(t *testing.T) {
dir := t.TempDir()
repoA := filepath.Join(dir, "repo-a")
require.NoError(t, os.MkdirAll(repoA, 0o755))
require.NoError(t, os.WriteFile(filepath.Join(repoA, "main.go"),
[]byte("package main\n\nfunc main() {}\n"), 0o644))
repoB := filepath.Join(dir, "repo-b")
require.NoError(t, os.MkdirAll(repoB, 0o755))
require.NoError(t, os.WriteFile(filepath.Join(repoB, "lib.go"),
[]byte("package lib\n\nfunc Foo() {}\n"), 0o644))
g := graph.New()
reg := parser.NewRegistry()
languages.RegisterAll(reg)
cm, err := config.NewConfigManager(filepath.Join(dir, "config.yaml"))
require.NoError(t, err)
require.NoError(t, cm.Global().AddRepo(config.RepoEntry{Path: repoA}))
require.NoError(t, cm.Global().AddRepo(config.RepoEntry{Path: repoB}))
idx := indexer.New(g, reg, config.Default().Index, zap.NewNop())
mi := indexer.NewMultiIndexer(g, reg, idx.Search(), cm, zap.NewNop())
state := &daemonState{
graph: g,
indexer: idx,
multiIndexer: mi,
configManager: cm,
}
_, timings := warmupDaemonState(state, zap.NewNop(), func() {})
require.NotNil(t, timings)
assert.Equal(t, 2, timings.reposChanged, "both freshly-tracked repos count as changed")
assert.Equal(t, 2, timings.filesReindexed, "one file per repo, both cold-tracked")
assert.Equal(t, 0, timings.enrichScheduled, "no semantic providers configured")
assert.GreaterOrEqual(t, timings.parse.Nanoseconds(), int64(0))
}