@elench/testkit 0.1.40 → 0.1.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +27 -13
  2. package/bin/testkit.mjs +6 -1
  3. package/lib/cli/args.mjs +0 -4
  4. package/lib/cli/args.test.mjs +0 -5
  5. package/lib/cli/index.mjs +4 -11
  6. package/lib/config/index.mjs +78 -24
  7. package/lib/database/index.mjs +19 -7
  8. package/lib/database/naming.mjs +2 -2
  9. package/lib/database/naming.test.mjs +2 -2
  10. package/lib/runner/default-runtime-runner.mjs +52 -55
  11. package/lib/runner/execution-config.mjs +31 -70
  12. package/lib/runner/execution-config.test.mjs +30 -74
  13. package/lib/runner/formatting.mjs +0 -15
  14. package/lib/runner/formatting.test.mjs +0 -18
  15. package/lib/runner/lifecycle.mjs +106 -8
  16. package/lib/runner/orchestrator.mjs +16 -10
  17. package/lib/runner/planning.mjs +66 -138
  18. package/lib/runner/planning.test.mjs +101 -167
  19. package/lib/runner/playwright-config.mjs +13 -2
  20. package/lib/runner/playwright-config.test.mjs +26 -6
  21. package/lib/runner/playwright-runner.mjs +50 -56
  22. package/lib/runner/readiness.mjs +2 -2
  23. package/lib/runner/reporting.mjs +4 -3
  24. package/lib/runner/reporting.test.mjs +2 -5
  25. package/lib/runner/results.mjs +1 -1
  26. package/lib/runner/results.test.mjs +1 -1
  27. package/lib/runner/runtime-contexts.mjs +20 -24
  28. package/lib/runner/runtime-manager.mjs +228 -0
  29. package/lib/runner/runtime-manager.test.mjs +206 -0
  30. package/lib/runner/services.mjs +8 -6
  31. package/lib/runner/state.mjs +1 -2
  32. package/lib/runner/state.test.mjs +2 -4
  33. package/lib/runner/template.mjs +90 -60
  34. package/lib/runner/template.test.mjs +59 -27
  35. package/lib/runner/worker-loop.mjs +35 -32
  36. package/lib/setup/index.d.ts +15 -10
  37. package/package.json +1 -1
  38. package/lib/runner/stack-manager.mjs +0 -146
@@ -3,81 +3,67 @@ import path from "path";
3
3
  import { execa } from "execa";
4
4
  import { bundleK6File } from "../bundler/index.mjs";
5
5
  import { resolveK6Binary } from "../config/index.mjs";
6
- import { persistTaskArtifacts } from "./artifacts.mjs";
7
- import { RUNTIME_ARTIFACT_MARKER } from "../runtime-src/k6/artifacts.js";
8
- import { determineDefaultRuntimeFailure } from "./default-runtime-errors.mjs";
9
- import { formatBatchDescriptor } from "./formatting.mjs";
10
- import { buildExecutionEnv } from "./template.mjs";
11
- import { readDatabaseUrl } from "./state-io.mjs";
12
6
  import {
13
7
  buildFileTimeoutEnv,
14
8
  formatFileTimeoutBudgetError,
15
9
  } from "../shared/file-timeout.mjs";
10
+ import { persistTaskArtifacts } from "./artifacts.mjs";
11
+ import { determineDefaultRuntimeFailure } from "./default-runtime-errors.mjs";
12
+ import { RUNTIME_ARTIFACT_MARKER } from "../runtime-src/k6/artifacts.js";
13
+ import { readDatabaseUrl } from "./state-io.mjs";
14
+ import { buildTaskExecutionEnv } from "./template.mjs";
15
+ import { killChildProcess } from "./processes.mjs";
16
16
 
17
- export async function runHttpK6Batch(targetConfig, batch, lifecycle) {
17
+ export async function runHttpK6Task(targetConfig, task, lifecycle, lease) {
18
18
  const baseUrl = targetConfig.testkit.local?.baseUrl;
19
19
  if (!baseUrl) {
20
20
  throw new Error(`Service "${targetConfig.name}" requires local.baseUrl for HTTP suites`);
21
21
  }
22
22
 
23
- console.log(
24
- `\n── ${targetConfig.stackLabel} ${batch.type}:${batch.tasks[0].suiteName}${formatBatchDescriptor(batch)} ──`
25
- );
26
-
27
- return Promise.all(
28
- batch.tasks.map((task) => runHttpK6Task(targetConfig, task, baseUrl, lifecycle))
29
- );
30
- }
31
-
32
- export async function runDalBatch(targetConfig, batch, lifecycle) {
33
- const databaseUrl = readDatabaseUrl(targetConfig.stateDir);
34
- if (!databaseUrl) {
35
- throw new Error(`Service "${targetConfig.name}" requires a database for DAL suites`);
36
- }
37
-
38
- console.log(
39
- `\n── ${targetConfig.stackLabel} ${batch.type}:${batch.tasks[0].suiteName}${formatBatchDescriptor(batch)} ──`
40
- );
41
-
42
- return Promise.all(
43
- batch.tasks.map((task) => runDalTask(targetConfig, task, databaseUrl, lifecycle))
44
- );
45
- }
46
-
47
- async function runHttpK6Task(targetConfig, task, baseUrl, lifecycle) {
48
23
  const bundledFile = await bundleK6File({
49
24
  productDir: targetConfig.productDir,
50
25
  serviceName: targetConfig.name,
51
26
  sourceFile: path.join(targetConfig.productDir, task.file),
52
27
  });
53
- console.log(`·· ${targetConfig.stackLabel}:${task.suiteName} → ${task.file}`);
28
+ if (lifecycle.isStopRequested()) {
29
+ throw new Error(`testkit run interrupted before starting ${task.file}`);
30
+ }
54
31
  return runDefaultRuntimeTask(
55
32
  targetConfig,
56
33
  task,
34
+ lease,
57
35
  ["run", "--address", "127.0.0.1:0", "-e", `BASE_URL=${baseUrl}`, bundledFile],
58
36
  lifecycle
59
37
  );
60
38
  }
61
39
 
62
- async function runDalTask(targetConfig, task, databaseUrl, lifecycle) {
40
+ export async function runDalTask(targetConfig, task, lifecycle, lease) {
41
+ const databaseUrl = readDatabaseUrl(targetConfig.stateDir);
42
+ if (!databaseUrl) {
43
+ throw new Error(`Service "${targetConfig.name}" requires a database for DAL suites`);
44
+ }
45
+
63
46
  const bundledFile = await bundleK6File({
64
47
  productDir: targetConfig.productDir,
65
48
  serviceName: targetConfig.name,
66
49
  sourceFile: path.join(targetConfig.productDir, task.file),
67
50
  });
68
- console.log(`·· ${targetConfig.stackLabel}:${task.suiteName} → ${task.file}`);
51
+ if (lifecycle.isStopRequested()) {
52
+ throw new Error(`testkit run interrupted before starting ${task.file}`);
53
+ }
69
54
  return runDefaultRuntimeTask(
70
55
  targetConfig,
71
56
  task,
57
+ lease,
72
58
  ["run", "--address", "127.0.0.1:0", "-e", `DATABASE_URL=${databaseUrl}`, bundledFile],
73
59
  lifecycle
74
60
  );
75
61
  }
76
62
 
77
- export async function runDefaultRuntimeTask(targetConfig, task, args, lifecycle, firstLine) {
63
+ export async function runDefaultRuntimeTask(targetConfig, task, lease, args, lifecycle, firstLine) {
78
64
  const k6Binary = resolveK6Binary();
79
65
  const getFirstLine = firstLine || defaultFirstLine;
80
- const summaryFile = buildDefaultRuntimeSummaryPath(targetConfig, task);
66
+ const summaryFile = buildDefaultRuntimeSummaryPath(lease, task);
81
67
  fs.mkdirSync(path.dirname(summaryFile), { recursive: true });
82
68
  const startedAt = Date.now();
83
69
  const fileTimeoutSeconds = targetConfig.testkit.execution.fileTimeoutSeconds;
@@ -86,17 +72,32 @@ export async function runDefaultRuntimeTask(targetConfig, task, args, lifecycle,
86
72
  [...args.slice(0, 1), "--summary-export", summaryFile, ...args.slice(1)],
87
73
  {
88
74
  cwd: targetConfig.productDir,
89
- env: buildExecutionEnv(
75
+ env: buildTaskExecutionEnv(
90
76
  targetConfig,
77
+ lease,
91
78
  buildFileTimeoutEnv(fileTimeoutSeconds, startedAt),
92
79
  process.env
93
80
  ),
94
81
  reject: false,
95
- cancelSignal: lifecycle.signal,
96
82
  forceKillAfterDelay: 5_000,
97
83
  }
98
84
  );
99
- const { result, timedOut } = await settleDefaultRuntimeProcess(subprocess, fileTimeoutSeconds);
85
+ lifecycle.registerProcess(subprocess, () => {
86
+ killChildProcess(subprocess, "SIGINT");
87
+ });
88
+ if (lifecycle.isStopRequested()) {
89
+ const interruptSubprocess = () => killChildProcess(subprocess, "SIGINT");
90
+ if (subprocess.pid) interruptSubprocess();
91
+ else subprocess.once?.("spawn", interruptSubprocess);
92
+ }
93
+ console.log(`·· ${targetConfig.runtimeLabel}:${task.suiteName} → ${task.file}`);
94
+ let result;
95
+ let timedOut;
96
+ try {
97
+ ({ result, timedOut } = await settleSubprocess(subprocess, fileTimeoutSeconds));
98
+ } finally {
99
+ lifecycle.unregisterProcess(subprocess.pid);
100
+ }
100
101
 
101
102
  const stdout = parseDefaultRuntimeOutput(result.stdout || "");
102
103
  const stderr = parseDefaultRuntimeOutput(result.stderr || "");
@@ -125,14 +126,7 @@ export async function runDefaultRuntimeTask(targetConfig, task, args, lifecycle,
125
126
  };
126
127
  }
127
128
 
128
- function defaultFirstLine(output) {
129
- return String(output || "")
130
- .split(/\r?\n/)
131
- .map((line) => line.trim())
132
- .find(Boolean) || null;
133
- }
134
-
135
- async function settleDefaultRuntimeProcess(subprocess, fileTimeoutSeconds) {
129
+ export async function settleSubprocess(subprocess, fileTimeoutSeconds) {
136
130
  const timeoutMs = fileTimeoutSeconds * 1000 + 1_000;
137
131
  let timeoutHandle = null;
138
132
  let timedOut = false;
@@ -143,7 +137,7 @@ async function settleDefaultRuntimeProcess(subprocess, fileTimeoutSeconds) {
143
137
  new Promise((resolve) => {
144
138
  timeoutHandle = setTimeout(async () => {
145
139
  timedOut = true;
146
- subprocess.kill("SIGTERM");
140
+ killChildProcess(subprocess, "SIGTERM");
147
141
  const result = await subprocess.catch((error) => error);
148
142
  resolve({ result, timedOut: true });
149
143
  }, timeoutMs);
@@ -154,12 +148,8 @@ async function settleDefaultRuntimeProcess(subprocess, fileTimeoutSeconds) {
154
148
  }
155
149
  }
156
150
 
157
- export function buildDefaultRuntimeSummaryPath(targetConfig, task) {
158
- return path.join(
159
- targetConfig.stateDir || path.join(targetConfig.productDir, ".testkit"),
160
- "_runtime",
161
- `task-${task.id}.summary.json`
162
- );
151
+ export function buildDefaultRuntimeSummaryPath(lease, task) {
152
+ return path.join(lease.leaseDir, "default-runtime", `task-${task.id}.summary.json`);
163
153
  }
164
154
 
165
155
  export function readDefaultRuntimeSummary(filePath) {
@@ -170,6 +160,13 @@ export function readDefaultRuntimeSummary(filePath) {
170
160
  }
171
161
  }
172
162
 
163
+ function defaultFirstLine(output) {
164
+ return String(output || "")
165
+ .split(/\r?\n/)
166
+ .map((line) => line.trim())
167
+ .find(Boolean) || null;
168
+ }
169
+
173
170
  function parseDefaultRuntimeOutput(output) {
174
171
  if (!output) {
175
172
  return {
@@ -4,7 +4,7 @@ import {
4
4
  parseFileTimeoutOption,
5
5
  } from "../shared/file-timeout.mjs";
6
6
 
7
- export const STACK_MODES = new Set(["shared", "pooled", "isolated"]);
7
+ export const DATABASE_BINDINGS = new Set(["shared", "per-runtime"]);
8
8
 
9
9
  export { DEFAULT_FILE_TIMEOUT_SECONDS, parseFileTimeoutOption };
10
10
 
@@ -12,19 +12,36 @@ export function parseWorkersOption(value) {
12
12
  return parsePositiveInteger(value, "--workers");
13
13
  }
14
14
 
15
- export function parseStackCountOption(value) {
16
- return parsePositiveInteger(value, "--stack-count");
15
+ export function parseRuntimeInstancesOption(value, label = "runtime.instances") {
16
+ return parsePositiveInteger(value, label);
17
17
  }
18
18
 
19
- export function parseStackModeOption(value) {
20
- return normalizeStackModeValue(value, "--stack-mode");
19
+ export function normalizeRuntimeInstances(value, label = "runtime.instances") {
20
+ return normalizePositiveInteger(value, label);
21
21
  }
22
22
 
23
- export function normalizeStackModeValue(value, label = "execution.stackMode") {
23
+ export function parseRuntimeMaxConcurrentTasksOption(
24
+ value,
25
+ label = "runtime.maxConcurrentTasks"
26
+ ) {
27
+ return parsePositiveInteger(value, label);
28
+ }
29
+
30
+ export function normalizeRuntimeMaxConcurrentTasks(
31
+ value,
32
+ label = "runtime.maxConcurrentTasks"
33
+ ) {
34
+ if (value === undefined || value === null) {
35
+ return Number.POSITIVE_INFINITY;
36
+ }
37
+ return normalizePositiveInteger(value, label);
38
+ }
39
+
40
+ export function normalizeDatabaseBinding(value, label = "database.binding") {
24
41
  const normalized = String(value || "").trim();
25
- if (!STACK_MODES.has(normalized)) {
42
+ if (!DATABASE_BINDINGS.has(normalized)) {
26
43
  throw new Error(
27
- `Invalid ${label} value "${value}". Expected one of: shared, pooled, isolated.`
44
+ `Invalid ${label} value "${value}". Expected one of: shared, per-runtime.`
28
45
  );
29
46
  }
30
47
  return normalized;
@@ -33,78 +50,22 @@ export function normalizeStackModeValue(value, label = "execution.stackMode") {
33
50
  export function resolveExecutionConfig({ cli = {}, repo = {} } = {}) {
34
51
  return normalizeExecutionConfig({
35
52
  workers: cli.workers ?? repo.workers ?? 1,
36
- stackMode: cli.stackMode ?? repo.stackMode ?? "isolated",
37
- stackCount: cli.stackCount ?? repo.stackCount ?? null,
38
53
  fileTimeoutSeconds:
39
54
  cli.fileTimeoutSeconds ?? repo.fileTimeoutSeconds ?? DEFAULT_FILE_TIMEOUT_SECONDS,
40
55
  });
41
56
  }
42
57
 
43
58
  export function normalizeExecutionConfig(input = {}) {
44
- const workers = normalizePositiveInteger(input.workers, "execution.workers");
45
- const stackMode = normalizeStackMode(input.stackMode);
46
- const explicitStackCount =
47
- input.stackCount == null ? null : normalizePositiveInteger(input.stackCount, "execution.stackCount");
48
- const fileTimeoutSeconds = normalizeFileTimeoutSeconds(
49
- input.fileTimeoutSeconds ?? DEFAULT_FILE_TIMEOUT_SECONDS
50
- );
51
-
52
- if (stackMode === "shared") {
53
- if (explicitStackCount !== null && explicitStackCount !== 1) {
54
- throw new Error(`execution.stackCount must be 1 when stackMode is "shared".`);
55
- }
56
- return {
57
- workers,
58
- stackMode,
59
- stackCount: 1,
60
- fileTimeoutSeconds,
61
- };
62
- }
63
-
64
- if (stackMode === "pooled") {
65
- return {
66
- workers,
67
- stackMode,
68
- stackCount: explicitStackCount ?? 1,
69
- fileTimeoutSeconds,
70
- };
71
- }
72
-
73
- if (explicitStackCount !== null && explicitStackCount !== workers) {
74
- throw new Error(
75
- `execution.stackCount must equal execution.workers when stackMode is "isolated".`
76
- );
77
- }
78
59
  return {
79
- workers,
80
- stackMode,
81
- stackCount: workers,
82
- fileTimeoutSeconds,
60
+ workers: normalizePositiveInteger(input.workers ?? 1, "execution.workers"),
61
+ fileTimeoutSeconds: normalizeFileTimeoutSeconds(
62
+ input.fileTimeoutSeconds ?? DEFAULT_FILE_TIMEOUT_SECONDS
63
+ ),
83
64
  };
84
65
  }
85
66
 
86
- export function buildStackIds(execution) {
87
- if (execution.stackMode === "shared") {
88
- return ["shared"];
89
- }
90
-
91
- return Array.from({ length: execution.stackCount }, (_unused, index) => `stack-${index + 1}`);
92
- }
93
-
94
- export function resolveBatchStackMode(defaultMode, suiteMode = null) {
95
- if (suiteMode == null) return defaultMode;
96
- return normalizeStackMode(suiteMode);
97
- }
98
-
99
- export function resolveBatchAccessMode({ framework, type, stackMode }) {
100
- if (stackMode === "isolated") return "exclusive";
101
- if ((framework || "k6") === "playwright") return "exclusive";
102
- if (type === "dal") return "exclusive";
103
- return "shared";
104
- }
105
-
106
- function normalizeStackMode(value) {
107
- return normalizeStackModeValue(value, "execution.stackMode");
67
+ export function buildRuntimeIds(count) {
68
+ return Array.from({ length: count }, (_unused, index) => `runtime-${index + 1}`);
108
69
  }
109
70
 
110
71
  function parsePositiveInteger(value, label) {
@@ -1,111 +1,67 @@
1
1
  import { describe, expect, it } from "vitest";
2
2
  import {
3
- buildStackIds,
3
+ buildRuntimeIds,
4
+ DATABASE_BINDINGS,
4
5
  DEFAULT_FILE_TIMEOUT_SECONDS,
6
+ normalizeDatabaseBinding,
5
7
  normalizeExecutionConfig,
8
+ normalizeRuntimeMaxConcurrentTasks,
9
+ normalizeRuntimeInstances,
6
10
  parseFileTimeoutOption,
7
- parseStackCountOption,
8
- parseStackModeOption,
11
+ parseRuntimeMaxConcurrentTasksOption,
12
+ parseRuntimeInstancesOption,
9
13
  parseWorkersOption,
10
- resolveBatchAccessMode,
11
- resolveBatchStackMode,
12
14
  resolveExecutionConfig,
13
15
  } from "./execution-config.mjs";
14
16
 
15
17
  describe("execution-config", () => {
16
- it("parses worker and stack CLI options", () => {
18
+ it("parses worker and runtime-instance options", () => {
17
19
  expect(parseWorkersOption("8")).toBe(8);
20
+ expect(parseRuntimeInstancesOption("2")).toBe(2);
21
+ expect(parseRuntimeMaxConcurrentTasksOption("4")).toBe(4);
18
22
  expect(parseFileTimeoutOption("45")).toBe(45);
19
- expect(parseStackCountOption("2")).toBe(2);
20
- expect(parseStackModeOption("pooled")).toBe("pooled");
21
23
  expect(() => parseWorkersOption("0")).toThrow('Invalid --workers value "0"');
24
+ expect(() => parseRuntimeInstancesOption("0")).toThrow('Invalid runtime.instances value "0"');
25
+ expect(() => parseRuntimeMaxConcurrentTasksOption("0")).toThrow(
26
+ 'Invalid runtime.maxConcurrentTasks value "0"'
27
+ );
22
28
  expect(() => parseFileTimeoutOption("0")).toThrow(
23
29
  'Invalid --file-timeout-seconds value "0"'
24
30
  );
25
- expect(() => parseStackModeOption("legacy")).toThrow('Invalid --stack-mode value "legacy"');
26
31
  });
27
32
 
28
- it("normalizes shared, pooled, and isolated execution shapes", () => {
29
- expect(normalizeExecutionConfig({ workers: 8, stackMode: "shared" })).toEqual({
30
- workers: 8,
31
- fileTimeoutSeconds: DEFAULT_FILE_TIMEOUT_SECONDS,
32
- stackMode: "shared",
33
- stackCount: 1,
34
- });
35
-
36
- expect(normalizeExecutionConfig({ workers: 8, stackMode: "pooled", stackCount: 3 })).toEqual({
33
+ it("normalizes execution defaults", () => {
34
+ expect(normalizeExecutionConfig({ workers: 8 })).toEqual({
37
35
  workers: 8,
38
36
  fileTimeoutSeconds: DEFAULT_FILE_TIMEOUT_SECONDS,
39
- stackMode: "pooled",
40
- stackCount: 3,
41
37
  });
42
-
43
- expect(normalizeExecutionConfig({ workers: 8, stackMode: "isolated" })).toEqual({
44
- workers: 8,
45
- fileTimeoutSeconds: DEFAULT_FILE_TIMEOUT_SECONDS,
46
- stackMode: "isolated",
47
- stackCount: 8,
48
- });
49
- });
50
-
51
- it("rejects invalid stack-count combinations", () => {
52
- expect(() =>
53
- normalizeExecutionConfig({ workers: 8, stackMode: "shared", stackCount: 2 })
54
- ).toThrow('execution.stackCount must be 1 when stackMode is "shared"');
55
-
56
- expect(() =>
57
- normalizeExecutionConfig({ workers: 8, stackMode: "isolated", stackCount: 2 })
58
- ).toThrow('execution.stackCount must equal execution.workers when stackMode is "isolated"');
59
38
  });
60
39
 
61
40
  it("applies CLI values over repo execution defaults", () => {
62
41
  expect(
63
42
  resolveExecutionConfig({
64
- repo: { workers: 3, stackMode: "shared" },
65
- cli: { workers: 6, fileTimeoutSeconds: 90, stackMode: "pooled", stackCount: 2 },
43
+ repo: { workers: 3, fileTimeoutSeconds: 30 },
44
+ cli: { workers: 6, fileTimeoutSeconds: 90 },
66
45
  })
67
46
  ).toEqual({
68
47
  workers: 6,
69
48
  fileTimeoutSeconds: 90,
70
- stackMode: "pooled",
71
- stackCount: 2,
72
49
  });
73
50
  });
74
51
 
75
- it("builds stack ids from the normalized execution shape", () => {
76
- expect(buildStackIds({ stackMode: "shared", stackCount: 1 })).toEqual(["shared"]);
77
- expect(buildStackIds({ stackMode: "pooled", stackCount: 2 })).toEqual([
78
- "stack-1",
79
- "stack-2",
80
- ]);
52
+ it("builds runtime ids from an instance count", () => {
53
+ expect(buildRuntimeIds(3)).toEqual(["runtime-1", "runtime-2", "runtime-3"]);
81
54
  });
82
55
 
83
- it("resolves batch stack and access modes", () => {
84
- expect(resolveBatchStackMode("shared", null)).toBe("shared");
85
- expect(resolveBatchStackMode("shared", "isolated")).toBe("isolated");
86
-
87
- expect(
88
- resolveBatchAccessMode({
89
- framework: "k6",
90
- type: "integration",
91
- stackMode: "shared",
92
- })
93
- ).toBe("shared");
94
-
95
- expect(
96
- resolveBatchAccessMode({
97
- framework: "playwright",
98
- type: "e2e",
99
- stackMode: "pooled",
100
- })
101
- ).toBe("exclusive");
102
-
103
- expect(
104
- resolveBatchAccessMode({
105
- framework: "k6",
106
- type: "dal",
107
- stackMode: "shared",
108
- })
109
- ).toBe("exclusive");
56
+ it("normalizes runtime instances and database bindings", () => {
57
+ expect(normalizeRuntimeInstances(2)).toBe(2);
58
+ expect(normalizeRuntimeMaxConcurrentTasks(undefined)).toBe(Number.POSITIVE_INFINITY);
59
+ expect(normalizeRuntimeMaxConcurrentTasks(3)).toBe(3);
60
+ for (const binding of DATABASE_BINDINGS) {
61
+ expect(normalizeDatabaseBinding(binding)).toBe(binding);
62
+ }
63
+ expect(() => normalizeDatabaseBinding("legacy")).toThrow(
64
+ 'Invalid database.binding value "legacy"'
65
+ );
110
66
  });
111
67
  });
@@ -36,21 +36,6 @@ export function longestServiceName(results) {
36
36
  return results.reduce((max, result) => Math.max(max, result.name.length), 4);
37
37
  }
38
38
 
39
- export function formatBatchDescriptor(batch) {
40
- const fileLabel = `${batch.tasks.length} file${batch.tasks.length === 1 ? "" : "s"}`;
41
- const frameworkLabel = formatFrameworkLabel(batch.framework);
42
- return frameworkLabel ? ` (${frameworkLabel}, ${fileLabel})` : ` (${fileLabel})`;
43
- }
44
-
45
- export function formatPlaywrightBatchFiles(batch) {
46
- if (!batch?.tasks?.length) return "";
47
- const files = batch.tasks.map((task) => task.file);
48
- if (files.length === 1) return ` · ${files[0]}`;
49
- const preview = files.slice(0, 3).join(", ");
50
- const suffix = files.length > 3 ? `, +${files.length - 3} more` : "";
51
- return ` · ${preview}${suffix}`;
52
- }
53
-
54
39
  export function formatFrameworkLabel(framework) {
55
40
  if (!framework || framework === "k6") return "";
56
41
  return framework;
@@ -1,11 +1,9 @@
1
1
  import { describe, expect, it } from "vitest";
2
2
  import {
3
3
  buildRunSummaryLines,
4
- formatBatchDescriptor,
5
4
  formatDuration,
6
5
  formatError,
7
6
  formatFrameworkLabel,
8
- formatPlaywrightBatchFiles,
9
7
  formatServiceSummary,
10
8
  formatSuiteFramework,
11
9
  longestServiceName,
@@ -53,22 +51,6 @@ describe("runner formatting", () => {
53
51
  ).toBe("1/2 suites passed, 1/3 files passed, 1 suite skipped");
54
52
  });
55
53
 
56
- it("formats batch descriptors", () => {
57
- expect(formatBatchDescriptor({ framework: "k6", tasks: [{}, {}] })).toBe(" (2 files)");
58
- expect(formatBatchDescriptor({ framework: "playwright", tasks: [{}] })).toBe(
59
- " (playwright, 1 file)"
60
- );
61
- });
62
-
63
- it("formats Playwright file previews", () => {
64
- expect(formatPlaywrightBatchFiles({ tasks: [{ file: "a" }] })).toBe(" · a");
65
- expect(
66
- formatPlaywrightBatchFiles({
67
- tasks: [{ file: "a" }, { file: "b" }, { file: "c" }, { file: "d" }],
68
- })
69
- ).toBe(" · a, b, c, +1 more");
70
- });
71
-
72
54
  it("formats framework labels", () => {
73
55
  expect(formatFrameworkLabel("k6")).toBe("");
74
56
  expect(formatFrameworkLabel("playwright")).toBe("playwright");