@factorialco/shadowdog
Version:
<img src="https://raw.githubusercontent.com/factorialco/shadowdog/refs/heads/main/logo.png" alt="drawing" width="100"/>
184 lines (183 loc) • 8.98 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const fs = __importStar(require("fs-extra"));
const vitest_1 = require("vitest");
const events_1 = require("../events");
vitest_1.vi.mock('fs-extra', () => ({
default: {
existsSync: vitest_1.vi.fn(),
readFileSync: vitest_1.vi.fn(),
writeFileSync: vitest_1.vi.fn(),
unlinkSync: vitest_1.vi.fn(),
mkdirpSync: vitest_1.vi.fn(),
},
existsSync: vitest_1.vi.fn(),
readFileSync: vitest_1.vi.fn(),
writeFileSync: vitest_1.vi.fn(),
unlinkSync: vitest_1.vi.fn(),
mkdirpSync: vitest_1.vi.fn(),
}));
(0, vitest_1.describe)('shadowdog-lock', () => {
const mockFs = vitest_1.vi.mocked(fs);
const mockExit = vitest_1.vi.spyOn(process, 'exit').mockImplementation(() => undefined);
const lockPath = '/tmp/shadowdog/lock';
const options = { path: lockPath };
const eventEmitter = new events_1.ShadowdogEventEmitter();
let shadowdogLock;
(0, vitest_1.beforeEach)(async () => {
vitest_1.vi.clearAllMocks();
vitest_1.vi.resetModules();
shadowdogLock = (await Promise.resolve().then(() => __importStar(require('./shadowdog-lock')))).default;
});
(0, vitest_1.describe)('middleware', () => {
(0, vitest_1.it)('should abort if lock exists and is not owned by current process', async () => {
mockFs.existsSync.mockReturnValue(true);
mockFs.readFileSync.mockReturnValue('other-pid');
const next = vitest_1.vi.fn();
const abort = vitest_1.vi.fn();
await shadowdogLock.middleware({
next,
abort,
options,
files: [],
invalidators: { files: [], environment: [] },
config: { command: '', workingDirectory: '', tags: [], artifacts: [] },
eventEmitter,
});
(0, vitest_1.expect)(mockExit).toHaveBeenCalledWith(1);
(0, vitest_1.expect)(next).not.toHaveBeenCalled();
});
(0, vitest_1.it)('should continue if lock exists but is owned by current process', async () => {
mockFs.existsSync.mockReturnValue(true);
mockFs.readFileSync.mockReturnValue(process.pid.toString());
const next = vitest_1.vi.fn();
const abort = vitest_1.vi.fn();
await shadowdogLock.middleware({
next,
abort,
options,
files: [],
invalidators: { files: [], environment: [] },
config: { command: '', workingDirectory: '', tags: [], artifacts: [] },
eventEmitter,
});
(0, vitest_1.expect)(abort).not.toHaveBeenCalled();
(0, vitest_1.expect)(next).toHaveBeenCalled();
});
(0, vitest_1.it)('should continue if lock does not exist', async () => {
mockFs.existsSync.mockReturnValue(false);
const next = vitest_1.vi.fn();
const abort = vitest_1.vi.fn();
await shadowdogLock.middleware({
next,
abort,
options,
files: [],
invalidators: { files: [], environment: [] },
config: { command: '', workingDirectory: '', tags: [], artifacts: [] },
eventEmitter,
});
(0, vitest_1.expect)(abort).not.toHaveBeenCalled();
(0, vitest_1.expect)(next).toHaveBeenCalled();
});
});
(0, vitest_1.describe)('listener', () => {
(0, vitest_1.it)('should create lock file on begin event if it does not exist', () => {
mockFs.existsSync.mockReturnValue(false);
shadowdogLock.listener(eventEmitter, options);
eventEmitter.emit('begin', { artifacts: [] });
(0, vitest_1.expect)(mockFs.writeFileSync).toHaveBeenCalledWith(lockPath, process.pid.toString());
});
(0, vitest_1.it)('should remove lock file on exit event if owned by current process', () => {
mockFs.existsSync.mockReturnValue(true);
mockFs.readFileSync.mockReturnValue(process.pid.toString());
shadowdogLock.listener(eventEmitter, options);
eventEmitter.emit('exit');
(0, vitest_1.expect)(mockFs.unlinkSync).toHaveBeenCalledWith(lockPath);
});
(0, vitest_1.it)('should not remove lock file on exit event if not owned by current process', () => {
mockFs.existsSync.mockReturnValue(true);
mockFs.readFileSync.mockReturnValue('other-pid');
shadowdogLock.listener(eventEmitter, options);
eventEmitter.emit('exit');
(0, vitest_1.expect)(mockFs.unlinkSync).not.toHaveBeenCalled();
});
(0, vitest_1.it)('should remove lock file on end event when counter reaches zero', () => {
mockFs.existsSync.mockReturnValue(true);
mockFs.readFileSync.mockReturnValue(process.pid.toString());
shadowdogLock.listener(eventEmitter, options);
eventEmitter.emit('begin', { artifacts: [] });
eventEmitter.emit('end', { artifacts: [] });
(0, vitest_1.expect)(mockFs.unlinkSync).toHaveBeenCalledWith(lockPath);
});
(0, vitest_1.it)('should not remove lock file on end event when counter is greater than zero', () => {
mockFs.existsSync.mockReturnValue(true);
mockFs.readFileSync.mockReturnValue(process.pid.toString());
shadowdogLock.listener(eventEmitter, options);
eventEmitter.emit('begin', { artifacts: [] });
eventEmitter.emit('begin', { artifacts: [] });
eventEmitter.emit('end', { artifacts: [] });
(0, vitest_1.expect)(mockFs.unlinkSync).not.toHaveBeenCalled();
});
});
(0, vitest_1.describe)('shadowdog-lock error handling', () => {
(0, vitest_1.beforeEach)(() => {
shadowdogLock.listener(eventEmitter, options);
});
(0, vitest_1.it)('should decrement counter and remove lock file on error when counter reaches 0', () => {
// Simulate two begin events
mockFs.existsSync.mockReturnValue(true);
mockFs.readFileSync.mockReturnValue(process.pid.toString());
eventEmitter.emit('begin', { artifacts: [] });
eventEmitter.emit('begin', { artifacts: [] });
(0, vitest_1.expect)(mockFs.unlinkSync).not.toHaveBeenCalled();
// Simulate error events
eventEmitter.emit('error', { artifacts: [], errorMessage: 'test error' });
(0, vitest_1.expect)(mockFs.unlinkSync).not.toHaveBeenCalled(); // Lock file should still exist when counter > 0
eventEmitter.emit('error', { artifacts: [], errorMessage: 'test error' });
(0, vitest_1.expect)(mockFs.unlinkSync).toHaveBeenCalledWith(lockPath); // Lock file should be removed when counter reaches 0
});
(0, vitest_1.it)('should not remove lock file on error if not lock owner', () => {
// Create lock file with different PID
mockFs.existsSync.mockReturnValue(true);
mockFs.readFileSync.mockReturnValue('99999');
eventEmitter.emit('begin', { artifacts: [] });
eventEmitter.emit('error', { artifacts: [], errorMessage: 'test error' });
(0, vitest_1.expect)(mockFs.unlinkSync).not.toHaveBeenCalled();
(0, vitest_1.expect)(mockFs.readFileSync(lockPath, 'utf-8')).toBe('99999');
});
});
});