Files
maka/scripts/run-desktop-e2e-parallel.test.mjs
NekoPunch 87e1f71b22 perf(ci): isolate Desktop e2e workers on Xvfb (#5250)
The tier ran one worker because windows on a shared X server contend for native input state. Dedicated servers let the tier run in parallel while guaranteeing their retirement before later CI steps.

The planner treats the runner and the fixture environment helper as e2e
drivers, so a change to either selects the tier it controls. Two of the four
idle Message size tests are gone: the bound is monotonic, so one point on each
side proves it, and every point cost a Host.

Generated-by: Claude Code
Generated-by: Codex
2026-09-16 16:41:50 +08:00

144 lines
5.5 KiB
JavaScript

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import assert from 'node:assert/strict';
import { EventEmitter } from 'node:events';
import { describe, it } from 'node:test';
import { displaysFor, parseCliArgs, runDesktopE2eParallel } from './run-desktop-e2e-parallel.mjs';
/**
* @param {{ answering?: Set<string>, stubborn?: Set<string>, suiteExitCode?: number, unspawnable?: Set<string> }} [options]
*/
function fakeSpawn(options = {}) {
const answering = options.answering ?? new Set();
const stubborn = options.stubborn ?? new Set();
const unspawnable = options.unspawnable ?? new Set();
const suiteExitCode = options.suiteExitCode ?? 0;
const calls = [];
const killed = [];
const spawn = (command, args, spawnOptions) => {
calls.push({ command, args, options: spawnOptions });
const child = new EventEmitter();
child.kill = (signal = 'SIGTERM') => {
killed.push({ display: args[0], signal });
if (!stubborn.has(args[0]) || signal === 'SIGKILL') {
queueMicrotask(() => child.emit('close', null, signal));
}
};
if (command === 'xdpyinfo') {
queueMicrotask(() => child.emit('close', answering.has(args[1]) ? 0 : 1, null));
} else if (command === 'Xvfb') {
if (unspawnable.has(args[0])) {
queueMicrotask(() => child.emit('error', new Error(`spawn Xvfb ENOENT ${args[0]}`)));
} else {
// A started server answers from then on.
answering.add(args[0]);
}
} else {
queueMicrotask(() => child.emit('close', suiteExitCode, null));
}
return child;
};
return { spawn, calls, killed };
}
describe('parseCliArgs', () => {
it('defaults to the serial shape', () => {
assert.deepEqual(parseCliArgs([]), { workers: 1, displayBase: 90 });
});
it('accepts both argument spellings', () => {
assert.deepEqual(parseCliArgs(['--workers=3', '--display-base=40']), {
workers: 3,
displayBase: 40,
});
assert.deepEqual(parseCliArgs(['--workers', '2', '--display-base', '7']), {
workers: 2,
displayBase: 7,
});
});
it('rejects a worker count that would leave a server unused or unassigned', () => {
assert.throws(() => parseCliArgs(['--workers=0']), /positive integer/u);
assert.throws(() => parseCliArgs(['--workers=2.5']), /positive integer/u);
assert.throws(() => parseCliArgs(['--nope']), /Unknown option/u);
});
});
describe('displaysFor', () => {
it('gives every worker a display of its own', () => {
assert.deepEqual(displaysFor(3, 90), [':90', ':91', ':92']);
});
});
describe('runDesktopE2eParallel', () => {
it('starts one server per worker and tells the suite where they are', async () => {
const { spawn, calls } = fakeSpawn();
assert.equal(await runDesktopE2eParallel({ workers: 3, spawn }), 0);
assert.deepEqual(
calls.filter((call) => call.command === 'Xvfb').map((call) => call.args[0]),
[':90', ':91', ':92'],
);
const suite = calls.find((call) => call.command === 'npm');
assert.equal(suite.options.env.MAKA_E2E_DISPLAY_BASE, '90');
assert.equal(suite.options.env.DISPLAY, ':90');
assert.deepEqual(suite.args.slice(-2), ['--workers', '3']);
});
it('retires every server even when the suite fails', async () => {
const { spawn, calls, killed } = fakeSpawn({ suiteExitCode: 1 });
assert.equal(await runDesktopE2eParallel({ workers: 2, spawn }), 1);
assert.deepEqual(killed, [
{ display: ':90', signal: 'SIGTERM' },
{ display: ':91', signal: 'SIGTERM' },
]);
assert.ok(calls.some((call) => call.command === 'npm'));
});
it('retires the servers already started when Xvfb cannot be spawned', async () => {
// An unhandled 'error' event throws past try/finally rather than raising,
// so without a listener the first server would outlive this call.
const { spawn, killed } = fakeSpawn({ unspawnable: new Set([':91']) });
await assert.rejects(
runDesktopE2eParallel({ workers: 2, spawn, readinessTimeoutMs: 20 }),
/:91 never accepted clients/u,
);
assert.deepEqual(killed, [
{ display: ':90', signal: 'SIGTERM' },
{ display: ':91', signal: 'SIGTERM' },
]);
});
it('forces a server down when graceful retirement expires', async () => {
const { spawn, killed } = fakeSpawn({ stubborn: new Set([':90']) });
assert.equal(await runDesktopE2eParallel({ workers: 1, spawn, retirementGraceMs: 0 }), 0);
assert.deepEqual(killed, [
{ display: ':90', signal: 'SIGTERM' },
{ display: ':90', signal: 'SIGKILL' },
]);
});
it('refuses a display another server already answers on', async () => {
// Reusing it would serve two workers from one focus, which reads as flake.
const { spawn } = fakeSpawn({ answering: new Set([':91']) });
await assert.rejects(runDesktopE2eParallel({ workers: 3, spawn }), /:91 is already in use/u);
});
});