2022-03-07 22:10:51 +00:00
|
|
|
import { expect } from 'chai';
|
2022-08-22 18:43:54 +00:00
|
|
|
import { promises as fs, mkdirSync } from 'fs';
|
|
|
|
import { testfs, TestFs } from 'mocha-pod';
|
|
|
|
import * as os from 'os';
|
|
|
|
import * as path from 'path';
|
|
|
|
import { stub } from 'sinon';
|
2022-08-17 23:35:08 +00:00
|
|
|
import * as lockfile from '~/lib/lockfile';
|
|
|
|
import * as fsUtils from '~/lib/fs-utils';
|
2022-03-07 22:10:51 +00:00
|
|
|
|
2022-08-22 18:43:54 +00:00
|
|
|
const NOBODY_UID = 65534;
|
2022-03-07 22:10:51 +00:00
|
|
|
|
2022-08-22 18:43:54 +00:00
|
|
|
describe('lib/lockfile', () => {
|
|
|
|
const lockdir = '/tmp/lockdir';
|
2022-03-07 22:10:51 +00:00
|
|
|
|
2022-08-22 18:43:54 +00:00
|
|
|
let testFs: TestFs.Enabled;
|
2022-04-07 04:49:01 +00:00
|
|
|
|
2022-08-22 18:43:54 +00:00
|
|
|
beforeEach(async () => {
|
|
|
|
testFs = await testfs(
|
|
|
|
{
|
|
|
|
[lockdir]: {
|
|
|
|
'other.lock': testfs.file({ uid: NOBODY_UID }),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{ cleanup: [path.join(lockdir, '**.lock')] },
|
|
|
|
).enable();
|
2022-03-07 22:10:51 +00:00
|
|
|
});
|
|
|
|
|
2022-03-08 00:56:11 +00:00
|
|
|
afterEach(async () => {
|
2022-08-22 18:43:54 +00:00
|
|
|
await testFs.restore();
|
|
|
|
});
|
2022-03-08 00:56:11 +00:00
|
|
|
|
2022-08-22 18:43:54 +00:00
|
|
|
it('should create a lockfile as the current user by default', async () => {
|
|
|
|
const lock = path.join(lockdir, 'updates.lock');
|
2022-03-08 00:56:11 +00:00
|
|
|
|
2022-08-22 18:43:54 +00:00
|
|
|
// Take the lock passing a uid
|
|
|
|
await expect(lockfile.lock(lock)).to.not.be.rejected;
|
|
|
|
|
|
|
|
// The file should exist
|
|
|
|
await expect(fs.access(lock)).to.not.be.rejected;
|
|
|
|
|
|
|
|
// Verify lockfile UID
|
|
|
|
expect((await fs.stat(lock)).uid).to.equal(os.userInfo().uid);
|
2022-03-07 22:10:51 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
it('should create a lockfile as the `nobody` user at target path', async () => {
|
2022-08-22 18:43:54 +00:00
|
|
|
const lock = path.join(lockdir, 'updates.lock');
|
2022-03-07 22:10:51 +00:00
|
|
|
|
2022-08-22 18:43:54 +00:00
|
|
|
// Take the lock passing a uid
|
|
|
|
await expect(lockfile.lock(lock, NOBODY_UID)).to.not.be.rejected;
|
2022-03-07 22:10:51 +00:00
|
|
|
|
2022-08-22 18:43:54 +00:00
|
|
|
// The file should exist
|
|
|
|
await expect(fs.access(lock)).to.not.be.rejected;
|
2022-03-07 22:10:51 +00:00
|
|
|
|
|
|
|
// Verify lockfile UID
|
2022-08-22 18:43:54 +00:00
|
|
|
expect((await fs.stat(lock)).uid).to.equal(NOBODY_UID);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should not be able to take the lock if it already exists', async () => {
|
|
|
|
const lock = path.join(lockdir, 'updates.lock');
|
|
|
|
|
|
|
|
// Take the lock passing a uid
|
|
|
|
await expect(lockfile.lock(lock, NOBODY_UID)).to.not.be.rejected;
|
|
|
|
|
|
|
|
// The file should exist
|
|
|
|
await expect(fs.access(lock)).to.not.be.rejected;
|
|
|
|
|
|
|
|
// Trying to take the lock again should fail
|
|
|
|
await expect(lockfile.lock(lock, NOBODY_UID)).to.be.rejected;
|
2022-03-07 22:10:51 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
it('should create a lockfile with the provided `uid` if specified', async () => {
|
2022-08-22 18:43:54 +00:00
|
|
|
const lock = path.join(lockdir, 'updates.lock');
|
2022-03-07 22:10:51 +00:00
|
|
|
|
2022-08-22 18:43:54 +00:00
|
|
|
// Take the lock passing a uid
|
|
|
|
await expect(lockfile.lock(lock, 2)).to.not.be.rejected;
|
2022-03-07 22:10:51 +00:00
|
|
|
|
2022-08-22 18:43:54 +00:00
|
|
|
// The file should exist
|
|
|
|
await expect(fs.access(lock)).to.not.be.rejected;
|
2022-03-07 22:10:51 +00:00
|
|
|
|
|
|
|
// Verify lockfile UID
|
2022-08-22 18:43:54 +00:00
|
|
|
expect((await fs.stat(lock)).uid).to.equal(2);
|
2022-03-07 22:10:51 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
it('should not create a lockfile if `lock` throws', async () => {
|
2022-08-22 18:43:54 +00:00
|
|
|
// Stub the call to exec.
|
|
|
|
// WARNING: This is relying on internal knowledge of the function
|
|
|
|
// which is generally not a good testing practice, but I'm not sure
|
|
|
|
// how to do it otherwise
|
|
|
|
const execStub = stub(fsUtils, 'exec').throws(
|
|
|
|
new Error('Something bad happened'),
|
|
|
|
);
|
2022-03-07 22:10:51 +00:00
|
|
|
|
2022-08-22 18:43:54 +00:00
|
|
|
const lock = path.join(lockdir, 'updates.lock');
|
2022-03-07 22:10:51 +00:00
|
|
|
|
2022-08-22 18:43:54 +00:00
|
|
|
// Take the lock passing a uid
|
|
|
|
await expect(lockfile.lock(lock, NOBODY_UID)).to.be.rejected;
|
2022-03-07 22:10:51 +00:00
|
|
|
|
2022-08-22 18:43:54 +00:00
|
|
|
// The file should not have been created
|
|
|
|
await expect(fs.access(lock)).to.be.rejected;
|
2022-03-07 22:10:51 +00:00
|
|
|
|
2022-08-22 18:43:54 +00:00
|
|
|
// Restore the stub
|
|
|
|
execStub.restore();
|
2022-03-07 22:10:51 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
it('should asynchronously unlock a lockfile', async () => {
|
2022-08-22 18:43:54 +00:00
|
|
|
const lock = path.join(lockdir, 'updates.lock');
|
|
|
|
|
|
|
|
// Take the lock passing a uid
|
|
|
|
await expect(lockfile.lock(lock)).to.not.be.rejected;
|
|
|
|
|
|
|
|
// The file should exist
|
|
|
|
await expect(fs.access(lock)).to.not.be.rejected;
|
|
|
|
|
|
|
|
// Unlock should never throw
|
|
|
|
await expect(lockfile.unlock(lock)).to.not.be.rejected;
|
|
|
|
|
|
|
|
// The file should no longer exist
|
|
|
|
await expect(fs.access(lock)).to.be.rejected;
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should asynchronously unlock a lock directory', async () => {
|
|
|
|
const lock = path.join(lockdir, 'updates.lock');
|
|
|
|
|
|
|
|
// Crete a lock directory
|
|
|
|
await fs.mkdir(lock, { recursive: true });
|
2022-03-07 22:10:51 +00:00
|
|
|
|
2022-08-22 18:43:54 +00:00
|
|
|
// The directory should exist
|
|
|
|
await expect(fs.access(lock)).to.not.be.rejected;
|
2022-03-07 22:10:51 +00:00
|
|
|
|
2022-08-22 18:43:54 +00:00
|
|
|
// Unlock should never throw
|
|
|
|
await expect(lockfile.unlock(lock)).to.not.be.rejected;
|
2022-03-07 22:10:51 +00:00
|
|
|
|
2022-08-22 18:43:54 +00:00
|
|
|
// The file should no longer exist
|
|
|
|
await expect(fs.access(lock)).to.be.rejected;
|
2022-03-07 22:10:51 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
it('should not error on async unlock if lockfile does not exist', async () => {
|
2022-08-22 18:43:54 +00:00
|
|
|
const lock = path.join(lockdir, 'updates.lock');
|
2022-03-07 22:10:51 +00:00
|
|
|
|
2022-08-22 18:43:54 +00:00
|
|
|
// The file should not exist before
|
|
|
|
await expect(fs.access(lock)).to.be.rejected;
|
|
|
|
|
|
|
|
// Unlock should never throw
|
|
|
|
await expect(lockfile.unlock(lock)).to.not.be.rejected;
|
2022-03-07 22:10:51 +00:00
|
|
|
|
2022-08-22 18:43:54 +00:00
|
|
|
// The file should still not exist
|
|
|
|
await expect(fs.access(lock)).to.be.rejected;
|
2022-03-07 22:10:51 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
it('should synchronously unlock a lockfile', () => {
|
2022-08-22 18:43:54 +00:00
|
|
|
const lock = path.join(lockdir, 'other.lock');
|
2022-03-07 22:10:51 +00:00
|
|
|
|
2022-08-22 18:43:54 +00:00
|
|
|
lockfile.unlockSync(lock);
|
2022-03-07 22:10:51 +00:00
|
|
|
|
|
|
|
// Verify lockfile does not exist
|
2022-08-22 18:43:54 +00:00
|
|
|
return expect(fs.access(lock)).to.be.rejected;
|
2022-03-07 22:10:51 +00:00
|
|
|
});
|
2022-03-08 00:56:11 +00:00
|
|
|
|
2022-08-22 18:43:54 +00:00
|
|
|
it('should synchronously unlock a lockfile dir', () => {
|
|
|
|
const lock = path.join(lockdir, 'update.lock');
|
|
|
|
|
|
|
|
mkdirSync(lock, { recursive: true });
|
|
|
|
|
|
|
|
lockfile.unlockSync(lock);
|
2022-03-08 00:56:11 +00:00
|
|
|
|
2022-08-22 18:43:54 +00:00
|
|
|
// Verify lockfile does not exist
|
|
|
|
return expect(fs.access(lock)).to.be.rejected;
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should try to clean up existing locks on process exit', async () => {
|
|
|
|
// Create lockfiles
|
|
|
|
const lockOne = path.join(lockdir, 'updates.lock');
|
|
|
|
const lockTwo = path.join(lockdir, 'two.lock');
|
|
|
|
await expect(lockfile.lock(lockOne)).to.not.be.rejected;
|
|
|
|
await expect(lockfile.lock(lockTwo, NOBODY_UID)).to.not.be.rejected;
|
2022-03-08 00:56:11 +00:00
|
|
|
|
2022-09-19 15:33:52 +00:00
|
|
|
// @ts-expect-error
|
2022-03-08 00:56:11 +00:00
|
|
|
process.emit('exit');
|
|
|
|
|
2022-04-07 04:49:01 +00:00
|
|
|
// Verify lockfile removal regardless of appId / appUuid
|
2022-08-22 18:43:54 +00:00
|
|
|
await expect(fs.access(lockOne)).to.be.rejected;
|
|
|
|
await expect(fs.access(lockTwo)).to.be.rejected;
|
2022-04-07 04:49:01 +00:00
|
|
|
});
|
|
|
|
|
2022-08-22 18:43:54 +00:00
|
|
|
it('allows to list locks taken according to a filter function', async () => {
|
|
|
|
// Create multiple lockfiles
|
|
|
|
const lockOne = path.join(lockdir, 'updates.lock');
|
|
|
|
const lockTwo = path.join(lockdir, 'two.lock');
|
|
|
|
await expect(lockfile.lock(lockOne)).to.not.be.rejected;
|
|
|
|
await expect(lockfile.lock(lockTwo, NOBODY_UID)).to.not.be.rejected;
|
2022-04-07 04:49:01 +00:00
|
|
|
|
|
|
|
expect(
|
2022-08-22 18:43:54 +00:00
|
|
|
lockfile.getLocksTaken((filepath) => filepath.includes('lockdir')),
|
|
|
|
).to.have.members([lockOne, lockTwo]);
|
2022-04-07 04:49:01 +00:00
|
|
|
expect(
|
2022-08-22 18:43:54 +00:00
|
|
|
lockfile.getLocksTaken((filepath) => filepath.includes('two')),
|
|
|
|
).to.have.members([lockTwo]);
|
|
|
|
expect(lockfile.getLocksTaken()).to.have.members([lockOne, lockTwo]);
|
2022-03-08 00:56:11 +00:00
|
|
|
});
|
2022-03-07 22:10:51 +00:00
|
|
|
});
|