mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 08:17:11 +00:00
test(management): cover plugin layout sync lock
This commit is contained in:
@@ -234,7 +234,9 @@ class ProfileContextSyncLock {
|
||||
|
||||
const until = Date.now() + retryDelayMs;
|
||||
while (Date.now() < until) {
|
||||
// Busy wait only during rare lock contention.
|
||||
// Sync callers need a synchronous retry path here.
|
||||
// This lock only guards short local filesystem normalization work, so
|
||||
// contention should be brief and limited to profile/bootstrap edges.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
|
||||
import { createHash } from 'crypto';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import ProfileContextSyncLock from '../../src/management/profile-context-sync-lock';
|
||||
|
||||
describe('ProfileContextSyncLock', () => {
|
||||
let tempRoot = '';
|
||||
let instancesDir = '';
|
||||
|
||||
const getLockPath = (lockName: string): string => {
|
||||
const safeName = lockName.replace(/[^a-zA-Z0-9_-]/g, '-').toLowerCase();
|
||||
const profileHash = createHash('sha1').update(lockName).digest('hex').slice(0, 8);
|
||||
return path.join(instancesDir, '.locks', `${safeName}-${profileHash}.lock`);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-context-lock-test-'));
|
||||
instancesDir = path.join(tempRoot, 'instances');
|
||||
fs.mkdirSync(instancesDir, { recursive: true });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (tempRoot && fs.existsSync(tempRoot)) {
|
||||
fs.rmSync(tempRoot, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it('acquires and releases synchronous named locks', () => {
|
||||
const lock = new ProfileContextSyncLock(instancesDir);
|
||||
const lockPath = getLockPath('__plugin-layout__');
|
||||
|
||||
let sawLockInsideCallback = false;
|
||||
const result = lock.withNamedLockSync('__plugin-layout__', () => {
|
||||
sawLockInsideCallback = fs.existsSync(lockPath);
|
||||
expect(fs.readFileSync(lockPath, 'utf8')).toContain(`"pid":${process.pid}`);
|
||||
return 'ok';
|
||||
});
|
||||
|
||||
expect(result).toBe('ok');
|
||||
expect(sawLockInsideCallback).toBe(true);
|
||||
expect(fs.existsSync(lockPath)).toBe(false);
|
||||
});
|
||||
|
||||
it('releases synchronous named locks when the callback throws', () => {
|
||||
const lock = new ProfileContextSyncLock(instancesDir);
|
||||
const lockPath = getLockPath('__plugin-layout__');
|
||||
|
||||
expect(() =>
|
||||
lock.withNamedLockSync('__plugin-layout__', () => {
|
||||
throw new Error('boom');
|
||||
})
|
||||
).toThrow('boom');
|
||||
expect(fs.existsSync(lockPath)).toBe(false);
|
||||
});
|
||||
|
||||
it('reclaims dead-owner locks before entering the callback', () => {
|
||||
const lock = new ProfileContextSyncLock(instancesDir);
|
||||
const lockPath = getLockPath('__plugin-layout__');
|
||||
|
||||
fs.mkdirSync(path.dirname(lockPath), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
lockPath,
|
||||
JSON.stringify({
|
||||
version: 1,
|
||||
pid: 999999,
|
||||
nonce: 'dead-owner',
|
||||
acquiredAtMs: Date.now() - 1000,
|
||||
}),
|
||||
'utf8'
|
||||
);
|
||||
|
||||
const result = lock.withNamedLockSync('__plugin-layout__', () => 'reclaimed');
|
||||
|
||||
expect(result).toBe('reclaimed');
|
||||
expect(fs.existsSync(lockPath)).toBe(false);
|
||||
});
|
||||
|
||||
it('reclaims malformed stale locks before entering the callback', () => {
|
||||
const lock = new ProfileContextSyncLock(instancesDir);
|
||||
const lockPath = getLockPath('__plugin-layout__');
|
||||
const staleDate = new Date(Date.now() - 60_000);
|
||||
|
||||
fs.mkdirSync(path.dirname(lockPath), { recursive: true });
|
||||
fs.writeFileSync(lockPath, 'not-json', 'utf8');
|
||||
fs.utimesSync(lockPath, staleDate, staleDate);
|
||||
|
||||
const result = lock.withNamedLockSync('__plugin-layout__', () => 'stale-reclaimed');
|
||||
|
||||
expect(result).toBe('stale-reclaimed');
|
||||
expect(fs.existsSync(lockPath)).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user