|
1 | 1 | import { describe, expect, it, vi } from "vitest"; |
2 | 2 |
|
3 | 3 | import type { AdoRest } from "../../executor-e2e/ado-rest.js"; |
4 | | -import { runScenario } from "../runner.js"; |
| 4 | +import { runAll, runScenario, scenarioConcurrency } from "../runner.js"; |
5 | 5 | import { SkipError } from "../scenario.js"; |
6 | 6 | import type { TriggerContext, TriggerScenario } from "../scenario.js"; |
7 | 7 |
|
@@ -142,3 +142,71 @@ describe("runScenario", () => { |
142 | 142 | expect(rest.cancelBuild).not.toHaveBeenCalled(); |
143 | 143 | }); |
144 | 144 | }); |
| 145 | + |
| 146 | +describe("scenarioConcurrency", () => { |
| 147 | + it("defaults to four, including for an unexpanded ADO macro", () => { |
| 148 | + expect(scenarioConcurrency({})).toBe(4); |
| 149 | + expect(scenarioConcurrency({ TRIGGER_E2E_CONCURRENCY: "$(TRIGGER_E2E_CONCURRENCY)" })).toBe(4); |
| 150 | + }); |
| 151 | + |
| 152 | + it("accepts an explicit bounded integer", () => { |
| 153 | + expect(scenarioConcurrency({ TRIGGER_E2E_CONCURRENCY: "6" })).toBe(6); |
| 154 | + }); |
| 155 | + |
| 156 | + it.each(["0", "9", "1.5", "many"])("rejects invalid value %s", (value) => { |
| 157 | + expect(() => scenarioConcurrency({ TRIGGER_E2E_CONCURRENCY: value })).toThrow( |
| 158 | + "TRIGGER_E2E_CONCURRENCY", |
| 159 | + ); |
| 160 | + }); |
| 161 | +}); |
| 162 | + |
| 163 | +describe("runAll", () => { |
| 164 | + it("runs scenarios concurrently while preserving declaration order", async () => { |
| 165 | + const { ctx } = makeCtx({ result: "succeeded" }); |
| 166 | + let active = 0; |
| 167 | + let maxActive = 0; |
| 168 | + |
| 169 | + const delayed = (id: string, delayMs: number): TriggerScenario<string> => |
| 170 | + scenario({ |
| 171 | + id, |
| 172 | + setup: async () => { |
| 173 | + active += 1; |
| 174 | + maxActive = Math.max(maxActive, active); |
| 175 | + await new Promise((resolve) => setTimeout(resolve, delayMs)); |
| 176 | + active -= 1; |
| 177 | + return id; |
| 178 | + }, |
| 179 | + }); |
| 180 | + |
| 181 | + const results = await runAll( |
| 182 | + ctx, |
| 183 | + [delayed("first", 30), delayed("second", 5), delayed("third", 5)], |
| 184 | + 2, |
| 185 | + ); |
| 186 | + |
| 187 | + expect(maxActive).toBe(2); |
| 188 | + expect(results.map((result) => result.id)).toEqual(["first", "second", "third"]); |
| 189 | + expect(results.every((result) => result.ok)).toBe(true); |
| 190 | + }); |
| 191 | + |
| 192 | + it("never exceeds the requested concurrency", async () => { |
| 193 | + const { ctx } = makeCtx({ result: "succeeded" }); |
| 194 | + let active = 0; |
| 195 | + let maxActive = 0; |
| 196 | + const scenarios = Array.from({ length: 7 }, (_, index) => |
| 197 | + scenario({ |
| 198 | + id: `s-${index}`, |
| 199 | + setup: async () => { |
| 200 | + active += 1; |
| 201 | + maxActive = Math.max(maxActive, active); |
| 202 | + await new Promise((resolve) => setTimeout(resolve, 10)); |
| 203 | + active -= 1; |
| 204 | + return "state"; |
| 205 | + }, |
| 206 | + }), |
| 207 | + ); |
| 208 | + |
| 209 | + await runAll(ctx, scenarios, 3); |
| 210 | + expect(maxActive).toBe(3); |
| 211 | + }); |
| 212 | +}); |
0 commit comments