Skip to content

Commit a308e3b

Browse files
committed
fix(qa): serialize channel-driver isolated smoke workers
1 parent 95ebb3d commit a308e3b

2 files changed

Lines changed: 100 additions & 2 deletions

File tree

extensions/qa-lab/src/suite-launch.runtime.test.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,92 @@ describe("qa suite runtime launcher", () => {
317317
expect(runQaTestFileScenarios).toHaveBeenCalledTimes(1);
318318
});
319319

320+
it("serializes channel-driver isolated flow workers under explicit concurrency", async () => {
321+
const repoRoot = await makeTempRepo("qa-suite-crabline-isolated-");
322+
const defaultFlowImplementation = runQaFlowSuite.getMockImplementation();
323+
if (!defaultFlowImplementation) {
324+
throw new Error("expected default QA flow suite mock implementation");
325+
}
326+
const isolatedScenarioIds = new Set([
327+
"runtime-tool-image-generate",
328+
"runtime-inventory-drift-check",
329+
"session-memory-ranking",
330+
]);
331+
let activeIsolatedWorkers = 0;
332+
let maxActiveIsolatedWorkers = 0;
333+
runQaFlowSuite.mockImplementation(
334+
async (
335+
params:
336+
| { outputDir?: string; scenarioIds?: string[]; writeEvidenceFile?: boolean }
337+
| undefined,
338+
) => {
339+
const scenarioIds = params?.scenarioIds ?? [];
340+
const isolatedWorker = scenarioIds.some((scenarioId) =>
341+
isolatedScenarioIds.has(scenarioId),
342+
);
343+
if (!isolatedWorker) {
344+
return await defaultFlowImplementation(params);
345+
}
346+
activeIsolatedWorkers += 1;
347+
maxActiveIsolatedWorkers = Math.max(maxActiveIsolatedWorkers, activeIsolatedWorkers);
348+
await new Promise<void>((resolve) => {
349+
setTimeout(resolve, 1);
350+
});
351+
try {
352+
return await defaultFlowImplementation(params);
353+
} finally {
354+
activeIsolatedWorkers -= 1;
355+
}
356+
},
357+
);
358+
359+
await runQaSuite({
360+
repoRoot,
361+
outputDir: ".artifacts/qa-e2e/crabline-isolated",
362+
channelDriverSelection: {
363+
capabilityMatrixPath: "crabline-fake-provider-capabilities.json",
364+
channel: "telegram",
365+
channelDriver: "crabline",
366+
smokeArtifactPath: "crabline-fake-provider-smoke.json",
367+
},
368+
concurrency: 8,
369+
scenarioIds: [
370+
"channel-chat-baseline",
371+
"runtime-tool-image-generate",
372+
"runtime-inventory-drift-check",
373+
"session-memory-ranking",
374+
"control-ui-chat-flow-playwright",
375+
],
376+
});
377+
378+
const outputDir = path.join(repoRoot, ".artifacts", "qa-e2e", "crabline-isolated");
379+
expect(runQaFlowSuite).toHaveBeenCalledTimes(4);
380+
expect(runQaFlowSuite).toHaveBeenNthCalledWith(
381+
1,
382+
expect.objectContaining({
383+
outputDir: path.join(outputDir, "flow", "shared"),
384+
concurrency: 1,
385+
scenarioIds: ["channel-chat-baseline"],
386+
}),
387+
);
388+
for (const [index, scenarioId] of [
389+
"runtime-tool-image-generate",
390+
"runtime-inventory-drift-check",
391+
"session-memory-ranking",
392+
].entries()) {
393+
expect(runQaFlowSuite).toHaveBeenNthCalledWith(
394+
index + 2,
395+
expect.objectContaining({
396+
outputDir: path.join(outputDir, "flow", `isolated-${index + 1}`),
397+
concurrency: 1,
398+
scenarioIds: [scenarioId],
399+
}),
400+
);
401+
}
402+
expect(runQaTestFileScenarios).toHaveBeenCalledTimes(1);
403+
expect(maxActiveIsolatedWorkers).toBe(1);
404+
});
405+
320406
it("respects serial concurrency across unified suite partitions", async () => {
321407
const repoRoot = await makeTempRepo("qa-suite-serial-");
322408
let releaseFlow!: () => void;

extensions/qa-lab/src/suite-launch.runtime.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -460,9 +460,18 @@ async function runUnifiedQaSuite(params: {
460460
scenarioRequiresIsolatedQaSuiteWorker,
461461
);
462462
const sharedFlowPartitions = partitionSharedFlowScenarios(sharedFlowScenarios, concurrency);
463+
// Channel-driver flow workers each launch a gateway plus transport harness.
464+
// Serializing their isolated workers keeps state-mutating smoke checks from
465+
// flaking under concurrent child gateways while preserving non-driver speed.
466+
const channelDriverFlowRequiresExclusiveWorkers = Boolean(
467+
params.runParams?.channelDriverSelection,
468+
);
469+
const isolatedFlowConcurrencyLimit = channelDriverFlowRequiresExclusiveWorkers
470+
? 1
471+
: MAX_ISOLATED_FLOW_CONCURRENCY;
463472
const isolatedFlowConcurrency = Math.min(
464473
concurrency,
465-
MAX_ISOLATED_FLOW_CONCURRENCY,
474+
isolatedFlowConcurrencyLimit,
466475
isolatedFlowScenarios.length,
467476
);
468477
const isolatedFlowPartitions =
@@ -492,7 +501,10 @@ async function runUnifiedQaSuite(params: {
492501
const isolatedPartition =
493502
partition.kind === "isolated" || partition.kind.startsWith("isolated-");
494503
const task = {
495-
weight: partition.concurrency,
504+
weight:
505+
isolatedPartition && channelDriverFlowRequiresExclusiveWorkers
506+
? concurrency
507+
: partition.concurrency,
496508
run: async () => {
497509
const result = await runFlowSuite({
498510
...params.runParams,

0 commit comments

Comments
 (0)