mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-09 03:17:54 +00:00
* Initial plan * fix(analyze): add WAL auto-checkpoint CLI control and default-off behavior * test(analyze): share lbug auto-checkpoint parsing and align validation * fix(analyze): always enable lbug auto-checkpoint and expose threshold control * refactor(lbug): inline always-on auto-checkpoint constructor arg * fix(analyze): guide checkpoint-threshold on Ladybug WAL checkpoint IO failures * test(analyze): cover checkpoint IO guidance and add integration guard * fix(analyze): tighten checkpoint IO detection and remove test hook * fix(analyze): remove checkpoint test hook and tighten error matching * fix(analyze): rename to wal-checkpoint-threshold, raise default, add manual checkpoint driver with retry Address review feedback on PR #1772: - Rename CLI flag, env var, AnalyzeOptions field, recovery-hint tag, and parser/constants from lbug-* to engine-neutral wal-* (matches the existing WAL_RECOVERY_SUGGESTION / isWalCorruptionError convention). - Raise default threshold from -1 (Ladybug stock ~16 MiB) to 64 MiB so users on the default config no longer hit the original rename/remove race. - Align both READMEs to publish 67108864 (64 MiB) instead of 65536 (which would have made the crash more frequent). - Add wal-checkpoint-driver.ts: a periodic manual CHECKPOINT driver wrapped in a 3-attempt jittered retry (50/200/500 ms), driven from runFullAnalysis. Opt-out via GITNEXUS_WAL_MANUAL_CHECKPOINT=0. Moves the race window into a JS-controllable retry surface while keeping native auto-checkpoint on. - Move LBUG_CHECKPOINT_RENAME_RE / REMOVE_RE plus the predicate (renamed to isLbugCheckpointIoError) into lbug-config.ts alongside isWalCorruptionError. Predicate is now exported. Add a permissive fallback matcher and pin the matched Ladybug version in comments. - Warn instead of silently defaulting when GITNEXUS_WAL_CHECKPOINT_THRESHOLD is set to a non-empty unparseable value (closes the CLI-vs-env asymmetry). - Add a typed RecoveryHint string-literal union in cli-message.ts so future hint tags can't drift. - Add a real integration test under test/integration/ that triggers a Ladybug checkpoint IO failure via a pre-existing directory at the rename target (portable across platforms; no test-only injection hook). - Add small-disk / CI caveat (32 MiB secondary suggestion) to the recovery hint and README env-var rows. - Document CLI/env precedence in the analyze --help block. - Help placeholder: <value> -> <bytes>. - Rename analyze-lbug-auto-checkpoint.test.ts to use the new wal-* token. * chore(lbug): remove dead jitteredDelay helper and apply prettier - Drop unused `jitteredDelay` function flagged by CodeQL in PR #1772; the retry loop already inlines the same calculation with the injectable `randomImpl` so the helper was dead. Move the non-cryptographic-by-design comment next to the actual jitter site. - Apply `prettier --write` to wal-checkpoint-driver.ts and the new integration test to absorb the PR autofix bot's formatting findings. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> Co-authored-by: Test <test@example.com>
99 lines
3.3 KiB
TypeScript
99 lines
3.3 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
const runFullAnalysisMock = vi.fn();
|
|
|
|
vi.mock('../../src/core/run-analyze.js', () => ({
|
|
runFullAnalysis: runFullAnalysisMock,
|
|
}));
|
|
|
|
vi.mock('../../src/core/lbug/lbug-adapter.js', () => ({
|
|
closeLbug: vi.fn(async () => undefined),
|
|
}));
|
|
|
|
vi.mock('../../src/storage/repo-manager.js', () => ({
|
|
getStoragePaths: vi.fn(() => ({ storagePath: '.gitnexus', lbugPath: '.gitnexus/lbug' })),
|
|
getGlobalRegistryPath: vi.fn(() => 'registry.json'),
|
|
RegistryNameCollisionError: class RegistryNameCollisionError extends Error {},
|
|
AnalysisNotFinalizedError: class AnalysisNotFinalizedError extends Error {},
|
|
assertAnalysisFinalized: vi.fn(async () => undefined),
|
|
}));
|
|
|
|
vi.mock('../../src/storage/git.js', () => ({
|
|
getGitRoot: vi.fn(() => '/repo'),
|
|
hasGitDir: vi.fn(() => true),
|
|
}));
|
|
|
|
vi.mock('../../src/core/ingestion/utils/max-file-size.js', () => ({
|
|
getMaxFileSizeBannerMessage: vi.fn(() => null),
|
|
}));
|
|
|
|
describe('analyzeCommand --wal-checkpoint-threshold parsing', () => {
|
|
const ORIGINAL_NODE_OPTIONS = process.env.NODE_OPTIONS;
|
|
const ORIGINAL_THRESHOLD = process.env.GITNEXUS_WAL_CHECKPOINT_THRESHOLD;
|
|
|
|
beforeEach(() => {
|
|
vi.resetModules();
|
|
runFullAnalysisMock.mockReset();
|
|
process.exitCode = undefined;
|
|
process.env.NODE_OPTIONS = `${process.env.NODE_OPTIONS ?? ''} --max-old-space-size=8192`.trim();
|
|
});
|
|
|
|
afterEach(() => {
|
|
if (ORIGINAL_NODE_OPTIONS === undefined) {
|
|
delete process.env.NODE_OPTIONS;
|
|
} else {
|
|
process.env.NODE_OPTIONS = ORIGINAL_NODE_OPTIONS;
|
|
}
|
|
if (ORIGINAL_THRESHOLD === undefined) {
|
|
delete process.env.GITNEXUS_WAL_CHECKPOINT_THRESHOLD;
|
|
} else {
|
|
process.env.GITNEXUS_WAL_CHECKPOINT_THRESHOLD = ORIGINAL_THRESHOLD;
|
|
}
|
|
});
|
|
|
|
it.each(['maybe', '-2', '1.5', ''])(
|
|
'rejects invalid --wal-checkpoint-threshold value %s before analysis starts',
|
|
async (walCheckpointThreshold) => {
|
|
const { _captureLogger } = await import('../../src/core/logger.js');
|
|
const cap = _captureLogger();
|
|
const { analyzeCommand } = await import('../../src/cli/analyze.js');
|
|
|
|
await analyzeCommand(undefined, { walCheckpointThreshold });
|
|
|
|
expect(process.exitCode).toBe(1);
|
|
expect(runFullAnalysisMock).not.toHaveBeenCalled();
|
|
expect(
|
|
cap
|
|
.records()
|
|
.some((r) => r.msg === ' --wal-checkpoint-threshold must be an integer >= -1.\n'),
|
|
).toBe(true);
|
|
cap.restore();
|
|
},
|
|
);
|
|
|
|
it.each([
|
|
['-1', '-1'],
|
|
['0', '0'],
|
|
['1024', '1024'],
|
|
])(
|
|
'sets GITNEXUS_WAL_CHECKPOINT_THRESHOLD=%s during runFullAnalysis and restores afterwards',
|
|
async (cliValue, expectedEnv) => {
|
|
const { analyzeCommand } = await import('../../src/cli/analyze.js');
|
|
let envAtCallTime: string | undefined;
|
|
runFullAnalysisMock.mockImplementation(async () => {
|
|
envAtCallTime = process.env.GITNEXUS_WAL_CHECKPOINT_THRESHOLD;
|
|
return {
|
|
repoName: 'repo',
|
|
repoPath: '/repo',
|
|
stats: {},
|
|
alreadyUpToDate: true,
|
|
};
|
|
});
|
|
|
|
await analyzeCommand(undefined, { walCheckpointThreshold: cliValue });
|
|
|
|
expect(envAtCallTime).toBe(expectedEnv);
|
|
expect(process.env.GITNEXUS_WAL_CHECKPOINT_THRESHOLD).toBe(ORIGINAL_THRESHOLD);
|
|
},
|
|
);
|
|
});
|