mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-07 08:26:11 +00:00
fix(test): retry Windows temp cleanup in cli-e2e teardown (#1688)
This commit is contained in:
parent
33f18ceaa2
commit
c9199b654f
2 changed files with 69 additions and 33 deletions
|
|
@ -4,7 +4,8 @@
|
|||
* Creates temporary directories for tests and provides cleanup that tolerates
|
||||
* LadybugDB's known Windows handle-release lag after retries.
|
||||
*/
|
||||
import fs from 'fs/promises';
|
||||
import fs from 'fs';
|
||||
import fsp from 'fs/promises';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
|
|
@ -13,22 +14,56 @@ export interface TestDBHandle {
|
|||
cleanup: () => Promise<void>;
|
||||
}
|
||||
|
||||
const CLEANUP_MAX_ATTEMPTS = 5;
|
||||
const WINDOWS_NATIVE_LOCK_CODES = new Set(['EBUSY', 'EPERM', 'EACCES', 'ENOTEMPTY']);
|
||||
|
||||
export async function cleanupTempDir(tmpDir: string): Promise<void> {
|
||||
const cleanupBackoffMs = (attempt: number): number => 100 * (attempt + 1);
|
||||
|
||||
const shouldSwallowCleanupError = (err: unknown): boolean => {
|
||||
const code = (err as NodeJS.ErrnoException | undefined)?.code;
|
||||
return process.platform === 'win32' && WINDOWS_NATIVE_LOCK_CODES.has(code ?? '');
|
||||
};
|
||||
|
||||
const sleepSync = (ms: number): void => {
|
||||
const view = new Int32Array(new SharedArrayBuffer(4));
|
||||
Atomics.wait(view, 0, 0, ms);
|
||||
};
|
||||
|
||||
export function cleanupTempDirSync(tmpDir: string): void {
|
||||
let lastError: unknown;
|
||||
for (let attempt = 0; attempt < 5; attempt++) {
|
||||
for (let attempt = 0; attempt < CLEANUP_MAX_ATTEMPTS; attempt++) {
|
||||
try {
|
||||
await fs.rm(tmpDir, { recursive: true, force: true });
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
return;
|
||||
} catch (err) {
|
||||
lastError = err;
|
||||
await new Promise((resolve) => setTimeout(resolve, 100 * (attempt + 1)));
|
||||
if (attempt < CLEANUP_MAX_ATTEMPTS - 1) {
|
||||
sleepSync(cleanupBackoffMs(attempt));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const code = (lastError as NodeJS.ErrnoException | undefined)?.code;
|
||||
if (process.platform === 'win32' && WINDOWS_NATIVE_LOCK_CODES.has(code ?? '')) {
|
||||
if (shouldSwallowCleanupError(lastError)) {
|
||||
return;
|
||||
}
|
||||
throw lastError;
|
||||
}
|
||||
|
||||
export async function cleanupTempDir(tmpDir: string): Promise<void> {
|
||||
let lastError: unknown;
|
||||
for (let attempt = 0; attempt < CLEANUP_MAX_ATTEMPTS; attempt++) {
|
||||
try {
|
||||
await fsp.rm(tmpDir, { recursive: true, force: true });
|
||||
return;
|
||||
} catch (err) {
|
||||
lastError = err;
|
||||
if (attempt < CLEANUP_MAX_ATTEMPTS - 1) {
|
||||
await new Promise((resolve) => setTimeout(resolve, cleanupBackoffMs(attempt)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldSwallowCleanupError(lastError)) {
|
||||
return;
|
||||
}
|
||||
throw lastError;
|
||||
|
|
@ -46,7 +81,7 @@ export async function cleanupTempDir(tmpDir: string): Promise<void> {
|
|||
* return.
|
||||
*/
|
||||
export async function createTempDir(prefix: string = 'gitnexus-test-'): Promise<TestDBHandle> {
|
||||
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
|
||||
const tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), prefix));
|
||||
return {
|
||||
dbPath: tmpDir,
|
||||
cleanup: async () => {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import os from 'os';
|
|||
import { fileURLToPath, pathToFileURL } from 'url';
|
||||
|
||||
import { createRequire } from 'module';
|
||||
import { cleanupTempDirSync } from '../helpers/test-db.js';
|
||||
|
||||
const testDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
const repoRoot = path.resolve(testDir, '../..');
|
||||
|
|
@ -75,10 +76,10 @@ afterAll(() => {
|
|||
// Entire tmp copy goes away — no selective cleanup needed. The shared
|
||||
// `test/fixtures/mini-repo/` source was never touched.
|
||||
if (tmpParent) {
|
||||
fs.rmSync(tmpParent, { recursive: true, force: true });
|
||||
cleanupTempDirSync(tmpParent);
|
||||
}
|
||||
if (suiteGitnexusHome) {
|
||||
fs.rmSync(suiteGitnexusHome, { recursive: true, force: true });
|
||||
cleanupTempDirSync(suiteGitnexusHome);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -268,8 +269,8 @@ describe('CLI end-to-end', () => {
|
|||
`registry has no entry for ${repo}; entries: ${JSON.stringify(entries.map((e) => e.path))}`,
|
||||
).toBe(true);
|
||||
} finally {
|
||||
fs.rmSync(gnHome, { recursive: true, force: true });
|
||||
fs.rmSync(repoParent, { recursive: true, force: true });
|
||||
cleanupTempDirSync(gnHome);
|
||||
cleanupTempDirSync(repoParent);
|
||||
}
|
||||
}, 60_000);
|
||||
|
||||
|
|
@ -310,8 +311,8 @@ describe('CLI end-to-end', () => {
|
|||
expect(`${second.stdout}${second.stderr}`).toMatch(/registry entry/i);
|
||||
expect(second.status).toBe(1);
|
||||
} finally {
|
||||
fs.rmSync(gnHome, { recursive: true, force: true });
|
||||
fs.rmSync(repoParent, { recursive: true, force: true });
|
||||
cleanupTempDirSync(gnHome);
|
||||
cleanupTempDirSync(repoParent);
|
||||
}
|
||||
}, 60_000);
|
||||
|
||||
|
|
@ -457,12 +458,12 @@ describe('CLI end-to-end', () => {
|
|||
const afterStep4 = JSON.parse(fs.readFileSync(registryPath, 'utf-8'));
|
||||
expect(afterStep4).toHaveLength(2);
|
||||
} finally {
|
||||
fs.rmSync(parentC, { recursive: true, force: true });
|
||||
cleanupTempDirSync(parentC);
|
||||
}
|
||||
} finally {
|
||||
fs.rmSync(gnHome, { recursive: true, force: true });
|
||||
fs.rmSync(parentA, { recursive: true, force: true });
|
||||
fs.rmSync(parentB, { recursive: true, force: true });
|
||||
cleanupTempDirSync(gnHome);
|
||||
cleanupTempDirSync(parentA);
|
||||
cleanupTempDirSync(parentB);
|
||||
}
|
||||
}, 360000); // 6-min outer budget (4 × ~60s analyze calls + fixture setup)
|
||||
});
|
||||
|
|
@ -571,8 +572,8 @@ describe('CLI end-to-end', () => {
|
|||
expect(r4.status).toBe(0);
|
||||
expect(`${r4.stdout}${r4.stderr}`).toMatch(/Nothing to remove/i);
|
||||
} finally {
|
||||
fs.rmSync(gnHome, { recursive: true, force: true });
|
||||
fs.rmSync(parentA, { recursive: true, force: true });
|
||||
cleanupTempDirSync(gnHome);
|
||||
cleanupTempDirSync(parentA);
|
||||
}
|
||||
}, 180000); // 3-min outer budget (1 × ~60s analyze + 3 × fast remove calls)
|
||||
|
||||
|
|
@ -675,9 +676,9 @@ describe('CLI end-to-end', () => {
|
|||
// And it's NOT the one we just removed.
|
||||
expect(finalEntries[0].path).not.toBe(repoAEntry.path);
|
||||
} finally {
|
||||
fs.rmSync(gnHome, { recursive: true, force: true });
|
||||
fs.rmSync(parentA, { recursive: true, force: true });
|
||||
fs.rmSync(parentB, { recursive: true, force: true });
|
||||
cleanupTempDirSync(gnHome);
|
||||
cleanupTempDirSync(parentA);
|
||||
cleanupTempDirSync(parentB);
|
||||
}
|
||||
}, 240000); // 4-min outer budget (2 × ~60s analyze + 2 × fast remove)
|
||||
|
||||
|
|
@ -759,8 +760,8 @@ describe('CLI end-to-end', () => {
|
|||
expect(afterRegistry).toHaveLength(1);
|
||||
expect(afterRegistry[0].storagePath).toBe(repo); // still poisoned (we did that)
|
||||
} finally {
|
||||
fs.rmSync(gnHome, { recursive: true, force: true });
|
||||
fs.rmSync(parent, { recursive: true, force: true });
|
||||
cleanupTempDirSync(gnHome);
|
||||
cleanupTempDirSync(parent);
|
||||
}
|
||||
}, 120000); // 2-min budget (1 × ~60s analyze + 1 × fast remove-refused)
|
||||
});
|
||||
|
|
@ -864,9 +865,9 @@ describe('CLI end-to-end', () => {
|
|||
expect(afterRegistry).toHaveLength(1);
|
||||
expect(afterRegistry[0].name).toBe('bad-alias');
|
||||
} finally {
|
||||
fs.rmSync(gnHome, { recursive: true, force: true });
|
||||
fs.rmSync(parentBad, { recursive: true, force: true });
|
||||
fs.rmSync(parentGood, { recursive: true, force: true });
|
||||
cleanupTempDirSync(gnHome);
|
||||
cleanupTempDirSync(parentBad);
|
||||
cleanupTempDirSync(parentGood);
|
||||
}
|
||||
}, 240000); // 4-min budget (2 × ~60s analyze + 1 × fast clean --all)
|
||||
});
|
||||
|
|
@ -954,7 +955,7 @@ describe('CLI end-to-end', () => {
|
|||
expect(result.status).toBe(0);
|
||||
expect(result.stdout).toMatch(/Repository not indexed/);
|
||||
} finally {
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
cleanupTempDirSync(tmpDir);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -968,7 +969,7 @@ describe('CLI end-to-end', () => {
|
|||
expect(result.status).toBe(0);
|
||||
expect(result.stdout).toMatch(/Not a git repository/);
|
||||
} finally {
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
cleanupTempDirSync(tmpDir);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -984,7 +985,7 @@ describe('CLI end-to-end', () => {
|
|||
expect(result.status).toBe(1);
|
||||
expect(result.stdout).toMatch(/not.*git repository/i);
|
||||
} finally {
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
cleanupTempDirSync(tmpDir);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -1014,7 +1015,7 @@ describe('CLI end-to-end', () => {
|
|||
expect(result.status).toBe(1);
|
||||
expect(result.stdout).toMatch(/not.*git repository/i);
|
||||
} finally {
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
cleanupTempDirSync(tmpDir);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -1051,7 +1052,7 @@ describe('CLI end-to-end', () => {
|
|||
expect(result.status).toBe(1);
|
||||
expect(result.stdout).toMatch(/No GitNexus index found/);
|
||||
} finally {
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
cleanupTempDirSync(tmpDir);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue