mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-15 12:20:44 +00:00
95 lines
3.2 KiB
TypeScript
95 lines
3.2 KiB
TypeScript
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);
|
|
});
|
|
});
|