Files
SpineViewer/tests/reconcile-lifecycle.test.js
T
2026-09-10 14:04:53 +08:00

45 lines
2.6 KiB
JavaScript

'use strict';
const assert = require('assert/strict');
const fs = require('fs');
const path = require('path');
const os = require('os');
const crypto = require('crypto');
const { execFile } = require('child_process');
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'spine-worker-lifecycle-'));
const library = path.join(root, 'test.library');
const source = path.join(library, 'images', 'test.info', 'hero.atlas.txt');
const marker = path.join(root, 'completed');
const worker = path.resolve(__dirname, '../js/spine-reconcile-worker.js');
const thumbnail = path.resolve(__dirname, '../thumbnail/spine.js');
const key = crypto.createHash('sha256').update(path.dirname(worker) + library).digest('hex');
const queue = path.join(os.tmpdir(), 'spineviewer-reconcile', key);
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
(async () => {
try {
fs.mkdirSync(path.dirname(source), { recursive: true }); fs.writeFileSync(source, 'atlas');
fs.writeFileSync(path.join(library, 'metadata.json'), '{"folders":[]}');
// Isolate lifecycle behavior from rendering and the user's running Eagle API.
const preload = path.join(root, 'preload.cjs');
fs.writeFileSync(preload, `require.cache[${JSON.stringify(thumbnail)}] = { exports: {
reconcileOnce: async (src, previous) => {
if (previous !== 'stable') return { signature: 'stable', complete: false };
require('fs').appendFileSync(${JSON.stringify(marker)}, String(process.pid) + '\\n');
return { complete: true };
}
}};`);
await new Promise((resolve, reject) => execFile(process.execPath, ['-e', `require(${JSON.stringify(worker)}).enqueue(${JSON.stringify(source)})`],
{ env: { ...process.env, NODE_OPTIONS: '--require=' + preload } }, error => error ? reject(error) : resolve()));
assert(!fs.existsSync(marker), 'launcher returns before reconciliation');
const deadline = Date.now() + 15000;
while ((!fs.existsSync(marker) || fs.existsSync(path.join(queue, 'worker.lock'))) && Date.now() < deadline) await sleep(100);
assert(fs.existsSync(marker), 'worker completes after launching process has exited');
assert(!fs.existsSync(path.join(queue, 'worker.lock')), 'worker releases lock and exits when idle');
assert.equal(fs.readdirSync(queue).filter(name => name.endsWith('.json')).length, 0);
console.log('PASS: detached worker survives callback process exit, completes queued work and releases lock');
} finally {
if (!fs.existsSync(path.join(queue, 'worker.lock'))) {
fs.rmSync(root, { recursive: true, force: true }); fs.rmSync(queue, { recursive: true, force: true });
}
}
})().catch(error => { console.error(error); process.exitCode = 1; });