blob: db395ee8dd0178a2b5b4b457df4edc4bb2f0935e [file]
import assert from 'node:assert/strict';
import { EventEmitter } from 'node:events';
import { mkdtempSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { test } from 'node:test';
import {
SERIAL_WORKSPACE_DIRS,
loadWorkspaceDirs,
nameForDir,
partitionWorkspaces,
runWorkspaceTests,
} from './run-workspace-tests-parallel.mjs';
function makeSpawn(plan) {
const calls = [];
const spawn = (command, options) => {
const child = new EventEmitter();
const index = calls.length;
calls.push({ command, cwd: options.cwd, shell: options.shell });
queueMicrotask(() => {
const step = plan[index] ?? { close: 0 };
if (step.error) {
child.emit(
'error',
step.error instanceof Error ? step.error : new Error(String(step.error)),
);
return;
}
child.emit('close', step.close ?? 0);
});
return child;
};
return { spawn, calls };
}
test('loadWorkspaceDirs reads root package.json workspaces', () => {
const dir = mkdtempSync(join(tmpdir(), 'maka-ws-load-'));
writeFileSync(
join(dir, 'package.json'),
JSON.stringify({ workspaces: ['packages/core', 'packages/headless', 'apps/desktop'] }),
);
assert.deepEqual(loadWorkspaceDirs(dir), ['packages/core', 'packages/headless', 'apps/desktop']);
});
test('partitionWorkspaces keeps serial packages out of the parallel batch', () => {
const { parallel, serial } = partitionWorkspaces(
['packages/core', 'packages/headless', 'apps/desktop', 'packages/ui'],
SERIAL_WORKSPACE_DIRS,
);
assert.deepEqual(parallel, ['packages/core', 'apps/desktop', 'packages/ui']);
assert.deepEqual(serial, ['packages/headless']);
});
test('nameForDir strips packages/ and apps/ prefixes', () => {
assert.equal(nameForDir('packages/headless'), 'headless');
assert.equal(nameForDir('apps/desktop'), 'desktop');
});
test('serial mode runs every workspace via package-owned npm run test:dist', async () => {
const repoRoot = '/repo';
const workspaceDirs = ['packages/core', 'packages/headless', 'apps/desktop'];
const { spawn, calls } = makeSpawn(workspaceDirs.map(() => ({ close: 0 })));
await runWorkspaceTests({
repoRoot,
workspaceDirs,
serial: true,
spawn,
});
assert.equal(calls.length, 3);
for (const call of calls) {
assert.equal(call.command, 'npm run test:dist');
assert.equal(call.shell, true);
}
assert.deepEqual(
calls.map((c) => c.cwd),
[
join(repoRoot, 'packages/core'),
join(repoRoot, 'packages/headless'),
join(repoRoot, 'apps/desktop'),
],
);
});
test('parallel mode runs non-serial workspaces first, then serial ones', async () => {
const repoRoot = '/repo';
const workspaceDirs = ['packages/core', 'packages/headless', 'packages/ui'];
const order = [];
const { spawn, calls } = makeSpawn([{ close: 0 }, { close: 0 }, { close: 0 }]);
const trackingSpawn = (command, options) => {
order.push(options.cwd);
return spawn(command, options);
};
await runWorkspaceTests({
repoRoot,
workspaceDirs,
serial: false,
spawn: trackingSpawn,
});
assert.equal(calls.length, 3);
// headless must not be in the first concurrent wave's completion-before-serial
// guarantee: all parallel finish before serial starts. Order among parallel
// may interleave; serial headless is last among recorded close-driven starts
// only if we track start order — start order for parallel is simultaneous.
// Assert set membership instead of full order for the parallel wave.
const parallelCwds = new Set([join(repoRoot, 'packages/core'), join(repoRoot, 'packages/ui')]);
assert.equal(parallelCwds.has(order[0]) && parallelCwds.has(order[1]), true);
assert.equal(order[2], join(repoRoot, 'packages/headless'));
});
test('parallel mode aggregates every failed workspace name', async () => {
const repoRoot = '/repo';
const workspaceDirs = ['packages/core', 'packages/ui', 'packages/headless'];
const { spawn } = makeSpawn([{ close: 1 }, { close: 2 }, { close: 0 }]);
await assert.rejects(
() =>
runWorkspaceTests({
repoRoot,
workspaceDirs,
serial: false,
spawn,
}),
(err) => {
assert.match(err.message, /\[core\] failed with code 1/);
assert.match(err.message, /\[ui\] failed with code 2/);
return true;
},
);
});
test('spawn errors are reported with the workspace name', async () => {
const repoRoot = '/repo';
const { spawn } = makeSpawn([{ error: new Error('ENOENT') }]);
await assert.rejects(
() =>
runWorkspaceTests({
repoRoot,
workspaceDirs: ['packages/core'],
serial: true,
spawn,
}),
/\[core\] spawn failed: ENOENT/,
);
});