nlocks/__tests__/file-lock.test.js

146 lines
4.3 KiB
JavaScript

const FileLock = require('../file-lock');
const fs = require('fs');
const path = require('path');
describe('FileLock', () => {
let lock;
const testFilePath = path.join(__dirname, 'test-file.txt');
beforeEach(() => {
lock = new FileLock();
// Clean up any leftover lock files
const lockFilePath = `${testFilePath}.lock`;
if (fs.existsSync(lockFilePath)) {
fs.unlinkSync(lockFilePath);
}
// Create test file
fs.writeFileSync(testFilePath, 'test content');
});
afterEach(() => {
// Clean up files
const lockFilePath = `${testFilePath}.lock`;
if (fs.existsSync(lockFilePath)) {
fs.unlinkSync(lockFilePath);
}
if (fs.existsSync(testFilePath)) {
fs.unlinkSync(testFilePath);
}
});
describe('acquire and release', () => {
test('should acquire lock without waiting when not locked', async () => {
const startTime = Date.now();
await lock.acquire(testFilePath);
const endTime = Date.now();
// Should resolve almost immediately (no waiting)
expect(endTime - startTime).toBeLessThan(100);
lock.release(testFilePath);
});
test('should protect file access without waiting when not locked', async () => {
let protectedFunctionCalled = false;
const protectedFunction = async () => {
await lock.acquire(testFilePath);
protectedFunctionCalled = true;
// Simulate some async work
await new Promise(resolve => setTimeout(resolve, 10));
lock.release(testFilePath);
return 'done';
};
const result = await protectedFunction();
expect(protectedFunctionCalled).toBe(true);
expect(result).toBe('done');
});
test('should allow consecutive acquisitions and releases without queueing', async () => {
// First acquisition
await lock.acquire(testFilePath);
lock.release(testFilePath);
// Second acquisition
await lock.acquire(testFilePath);
lock.release(testFilePath);
// Third acquisition
await lock.acquire(testFilePath);
lock.release(testFilePath);
});
test('should handle multiple concurrent acquisitions correctly', async () => {
let counter = 0;
const incrementCounter = async () => {
await lock.acquire(testFilePath);
const temp = counter;
// Simulate some async work that could cause race condition
await new Promise(resolve => setTimeout(resolve, 1));
counter = temp + 1;
lock.release(testFilePath);
};
// Run multiple concurrent operations
await Promise.all([
incrementCounter(),
incrementCounter(),
incrementCounter()
]);
expect(counter).toBe(3);
});
test('should handle case where release is called without any pending acquirers', () => {
// Calling release on unlocked lock should not error
expect(() => lock.release(testFilePath)).not.toThrow();
});
test('should properly queue multiple concurrent requests', async () => {
let executionOrder = [];
const task = async (id) => {
await lock.acquire(testFilePath);
executionOrder.push(id);
// Simulate work
await new Promise(resolve => setTimeout(resolve, 10));
lock.release(testFilePath);
};
// Start multiple concurrent tasks
const tasks = [
task(1),
task(2),
task(3)
];
await Promise.all(tasks);
// All tasks should execute in order
expect(executionOrder).toEqual([1, 2, 3]);
});
test('should create lock file when acquiring lock', async () => {
await lock.acquire(testFilePath);
const lockFilePath = `${testFilePath}.lock`;
expect(fs.existsSync(lockFilePath)).toBe(true);
expect(fs.readFileSync(lockFilePath, 'utf8')).toBe(process.pid.toString());
lock.release(testFilePath);
});
test('should remove lock file when releasing lock', async () => {
await lock.acquire(testFilePath);
const lockFilePath = `${testFilePath}.lock`;
expect(fs.existsSync(lockFilePath)).toBe(true);
lock.release(testFilePath);
expect(fs.existsSync(lockFilePath)).toBe(false);
});
});
});