nlocks/__tests__/namedpipe-lock.test.js

236 lines
7.3 KiB
JavaScript

const NamedPipeLockServer = require('../lock.namedpipe');
const NamedPipeRWLock = require('../lock-client.namedpipe');
const fs = require('fs');
const os = require('os');
const path = require('path');
jest.setTimeout(30000);
// Helper function to create a unique pipe path for testing
const createTestPipePath = () => {
if (process.platform === 'win32') {
return `\\\\.\\pipe\\rwlock-test-${Date.now()}`;
} else {
return path.join(os.tmpdir(), `rwlock-test-${Date.now()}.sock`);
}
};
describe('NamedPipeLock', () => {
let server;
let pipePath;
// 在所有测试之前启动服务器
beforeAll(async () => {
pipePath = createTestPipePath();
server = new NamedPipeLockServer(pipePath);
server.start();
});
// 在所有测试完成后停止服务器
afterAll(() => {
server.stop();
// 清理管道文件 (仅限Unix系统)
if (process.platform !== 'win32' && fs.existsSync(pipePath)) {
fs.unlinkSync(pipePath);
}
});
describe('Basic Lock Operations', () => {
test('should acquire and release read lock without waiting when not locked', async () => {
const lock = new NamedPipeRWLock('resource1', pipePath);
const startTime = Date.now();
await lock.readLock();
const lockAcquiredTime = Date.now();
// 应该几乎立即获得锁(无需等待)
expect(lockAcquiredTime - startTime).toBeLessThan(100);
await lock.unlock();
lock.close();
});
test('should acquire and release write lock without waiting when not locked', async () => {
const lock = new NamedPipeRWLock('resource2', pipePath);
const startTime = Date.now();
await lock.writeLock();
const lockAcquiredTime = Date.now();
// 应该几乎立即获得锁(无需等待)
expect(lockAcquiredTime - startTime).toBeLessThan(100);
await lock.unlock();
lock.close();
});
test('should allow consecutive acquisitions and releases without queueing', async () => {
const lock = new NamedPipeRWLock('resource3', pipePath);
// 第一次获取
await lock.readLock();
expect(lock.isLocked).toBe(true);
await lock.unlock();
// 第二次获取
await lock.writeLock();
expect(lock.isLocked).toBe(true);
await lock.unlock();
// 第三次获取
await lock.readLock();
expect(lock.isLocked).toBe(true);
await lock.unlock();
lock.close();
});
});
describe('Multiple Clients', () => {
test('should handle multiple concurrent read locks', async () => {
const lock1 = new NamedPipeRWLock('sharedResource', pipePath);
const lock2 = new NamedPipeRWLock('sharedResource', pipePath);
const lock3 = new NamedPipeRWLock('sharedResource', pipePath);
// 所有读锁应该能够同时获取
await Promise.all([
lock1.readLock(),
lock2.readLock(),
lock3.readLock()
]);
expect(lock1.isLocked).toBe(true);
expect(lock2.isLocked).toBe(true);
expect(lock3.isLocked).toBe(true);
// 释放所有锁
await Promise.all([
lock1.unlock(),
lock2.unlock(),
lock3.unlock()
]);
lock1.close();
lock2.close();
lock3.close();
});
test('should queue write lock when read locks exist', async () => {
const readLock1 = new NamedPipeRWLock('queuedResource', pipePath);
const readLock2 = new NamedPipeRWLock('queuedResource', pipePath);
const writeLock = new NamedPipeRWLock('queuedResource', pipePath);
// 先获取两个读锁
await readLock1.readLock();
await readLock2.readLock();
// 尝试获取写锁,应该会被阻塞
let writeLockAcquired = false;
const writeLockPromise = writeLock.writeLock().then(() => {
writeLockAcquired = true;
});
// 等待一小段时间,写锁不应该被获取
await new Promise(resolve => setTimeout(resolve, 50));
expect(writeLockAcquired).toBe(false);
// 释放一个读锁
await readLock1.unlock();
// 等待一小段时间,写锁仍然不应该被获取(还有一个读锁)
await new Promise(resolve => setTimeout(resolve, 50));
expect(writeLockAcquired).toBe(false);
// 释放最后一个读锁
await readLock2.unlock();
// 现在写锁应该能被获取
await new Promise(resolve => setTimeout(resolve, 100));
expect(writeLockAcquired).toBe(true);
// 释放写锁
await writeLock.unlock();
readLock1.close();
readLock2.close();
writeLock.close();
});
test('should queue read locks when write lock exists', async () => {
const writeLock = new NamedPipeRWLock('queuedResource2', pipePath);
const readLock1 = new NamedPipeRWLock('queuedResource2', pipePath);
const readLock2 = new NamedPipeRWLock('queuedResource2', pipePath);
// 先获取写锁
await writeLock.writeLock();
// 尝试获取读锁,应该会被阻塞
let readLockAcquired = false;
const readLockPromise = readLock1.readLock().then(() => {
readLockAcquired = true;
});
// 等待一小段时间,读锁不应该被获取
await new Promise(resolve => setTimeout(resolve, 50));
expect(readLockAcquired).toBe(false);
// 再尝试获取另一个读锁,也应该被阻塞
let readLock2Acquired = false;
const readLock2Promise = readLock2.readLock().then(() => {
readLock2Acquired = true;
});
// 等待一小段时间,第二个读锁也不应该被获取
await new Promise(resolve => setTimeout(resolve, 50));
expect(readLock2Acquired).toBe(false);
// 释放写锁
await writeLock.unlock();
// 现在读锁应该能被获取
await new Promise(resolve => setTimeout(resolve, 100));
expect(readLockAcquired).toBe(true);
expect(readLock2Acquired).toBe(true);
// 释放读锁
await Promise.all([
readLock1.unlock(),
readLock2.unlock()
]);
writeLock.close();
readLock1.close();
readLock2.close();
});
});
describe('Error Handling', () => {
test('should reject when trying to acquire lock while already holding one', async () => {
const lock = new NamedPipeRWLock('errorResource', pipePath);
await lock.readLock();
// 尝试在已经持有锁的情况下再获取锁
await expect(lock.writeLock()).rejects.toThrow('Lock already held');
await lock.unlock();
lock.close();
});
test('should handle lock acquisition timeout', async () => {
const lock = new NamedPipeRWLock('timeoutResource', pipePath, {
timeout: 100 // 设置很短的超时时间
});
// 模拟一个永远不会释放的锁场景
const blockingLock = new NamedPipeRWLock('timeoutResource', pipePath);
await blockingLock.writeLock();
// 尝试获取已经被占用的锁,应该会超时
await expect(lock.writeLock()).rejects.toThrow(/timeout/);
await blockingLock.unlock();
blockingLock.close();
lock.close();
});
});
});