@elench/testkit 0.1.32 → 0.1.34

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.
@@ -32,13 +32,6 @@ export function buildServiceTrackers(servicePlans, startedAt) {
32
32
  completedFileCount: 0,
33
33
  failedFiles: [],
34
34
  failedFileSet: new Set(),
35
- fileResults: suite.files.map((file) => ({
36
- path: normalizePathSeparators(file),
37
- failed: false,
38
- durationMs: 0,
39
- error: null,
40
- status: "not_run",
41
- })),
42
35
  fileResultsByPath: new Map(
43
36
  suite.files.map((file) => {
44
37
  const normalizedPath = normalizePathSeparators(file);
@@ -57,9 +50,6 @@ export function buildServiceTrackers(servicePlans, startedAt) {
57
50
  durationMs: 0,
58
51
  error: null,
59
52
  }));
60
- for (const suite of suites) {
61
- suite.fileResults = [...suite.fileResultsByPath.values()];
62
- }
63
53
 
64
54
  trackers.set(plan.config.name, {
65
55
  name: plan.config.name,
@@ -112,15 +102,13 @@ export function recordTaskOutcome(trackers, task, outcome, finishedAt = Date.now
112
102
  existingFileResult.error = outcome.error;
113
103
  existingFileResult.status = outcome.failed ? "failed" : "passed";
114
104
  } else {
115
- const fileResult = {
105
+ suite.fileResultsByPath.set(normalizedPath, {
116
106
  path: normalizedPath,
117
107
  failed: outcome.failed,
118
108
  durationMs: outcomeDurationMs,
119
109
  error: outcome.error,
120
110
  status: outcome.failed ? "failed" : "passed",
121
- };
122
- suite.fileResultsByPath.set(normalizedPath, fileResult);
123
- suite.fileResults.push(fileResult);
111
+ });
124
112
  }
125
113
  if (outcome.failed && !suite.failedFileSet.has(task.file)) {
126
114
  suite.failedFileSet.add(task.file);
@@ -164,25 +152,18 @@ export function finalizeServiceResult(tracker, startedAt, finishedAt) {
164
152
  };
165
153
  }
166
154
 
167
- const suites = [...tracker.suites].sort(
168
- (a, b) => a.orderIndex - b.orderIndex || a.name.localeCompare(b.name)
169
- );
155
+ const suites = [...tracker.suites]
156
+ .sort((a, b) => a.orderIndex - b.orderIndex || a.name.localeCompare(b.name))
157
+ .map((suite) => finalizeSuite(suite));
158
+
170
159
  const completedSuiteCount = suites.filter(
171
160
  (suite) => suite.completedFileCount === suite.fileCount
172
161
  ).length;
173
- const failedSuiteCount = suites.filter((suite) => suite.failedFiles.length > 0).length;
162
+ const failedSuiteCount = suites.filter((suite) => suite.failedFileCount > 0).length;
174
163
  const totalFileCount = suites.reduce((sum, suite) => sum + suite.fileCount, 0);
175
- const completedFileCount = suites.reduce(
176
- (sum, suite) => sum + suite.completedFileCount,
177
- 0
178
- );
179
- const failedFileCount = suites.reduce((sum, suite) => sum + suite.failedFiles.length, 0);
180
- const passedFileCount = suites.reduce(
181
- (sum, suite) =>
182
- sum +
183
- suite.fileResults.filter((file) => file.status === "passed").length,
184
- 0
185
- );
164
+ const completedFileCount = suites.reduce((sum, suite) => sum + suite.completedFileCount, 0);
165
+ const failedFileCount = suites.reduce((sum, suite) => sum + suite.failedFileCount, 0);
166
+ const passedFileCount = suites.reduce((sum, suite) => sum + suite.passedFileCount, 0);
186
167
  const notRunFileCount = totalFileCount - completedFileCount;
187
168
  const totalTaskDurationMs =
188
169
  tracker.totalTaskDurationMs || suites.reduce((sum, suite) => sum + suite.durationMs, 0);
@@ -209,207 +190,11 @@ export function finalizeServiceResult(tracker, startedAt, finishedAt) {
209
190
  notRunFileCount,
210
191
  durationMs,
211
192
  totalTaskDurationMs,
212
- suites: suites.map((suite) => ({
213
- name: suite.name,
214
- type: suite.type,
215
- framework: formatFrameworkForArtifact(suite.framework),
216
- failed: suite.failedFiles.length > 0,
217
- fileCount: suite.fileCount,
218
- completedFileCount: suite.completedFileCount,
219
- passedFileCount: suite.fileResults.filter((file) => file.status === "passed").length,
220
- failedFileCount: suite.failedFiles.length,
221
- notRunFileCount: suite.fileCount - suite.completedFileCount,
222
- failedFiles: suite.failedFiles,
223
- durationMs: suite.durationMs,
224
- error: suite.error,
225
- files: suite.fileResults
226
- .slice()
227
- .sort((a, b) => a.path.localeCompare(b.path))
228
- .map((file) => ({
229
- path: file.path,
230
- failed: file.failed,
231
- status: file.status,
232
- durationMs: file.durationMs,
233
- error: file.error,
234
- })),
235
- })),
193
+ suites,
236
194
  errors: tracker.errors,
237
195
  };
238
196
  }
239
197
 
240
- export function buildStatusArtifact({
241
- productDir,
242
- results,
243
- suiteType,
244
- suiteNames,
245
- fileNames,
246
- framework,
247
- shard,
248
- serviceFilter,
249
- metadata,
250
- }) {
251
- const executedResults = results.filter((result) => !result.skipped);
252
- const tests = [];
253
-
254
- for (const result of executedResults) {
255
- for (const suite of result.suites) {
256
- for (const file of suite.files) {
257
- tests.push({
258
- service: result.name,
259
- type: suite.type,
260
- path: file.path,
261
- status: file.status,
262
- });
263
- }
264
- }
265
- }
266
-
267
- tests.sort(
268
- (left, right) =>
269
- left.service.localeCompare(right.service) ||
270
- left.type.localeCompare(right.type) ||
271
- left.path.localeCompare(right.path)
272
- );
273
-
274
- const summary = {
275
- services: {
276
- total: executedResults.length,
277
- passed: executedResults.filter((result) => !result.failed).length,
278
- failed: executedResults.filter((result) => result.failed).length,
279
- },
280
- tests: {
281
- total: tests.length,
282
- passed: tests.filter((test) => test.status === "passed").length,
283
- failed: tests.filter((test) => test.status === "failed").length,
284
- notRun: tests.filter((test) => test.status === "not_run").length,
285
- },
286
- };
287
-
288
- const scope = {
289
- suiteType,
290
- suiteNames: [...(suiteNames || [])].sort(),
291
- fileNames: [...(fileNames || [])].sort(),
292
- framework: formatFrameworkForArtifact(framework || "all"),
293
- shard: shard || null,
294
- serviceFilter: serviceFilter || null,
295
- };
296
- scope.isFullRun =
297
- scope.suiteNames.length === 0 &&
298
- scope.fileNames.length === 0 &&
299
- scope.framework === "all" &&
300
- scope.shard === null &&
301
- scope.serviceFilter === null;
302
-
303
- return {
304
- schemaVersion: 1,
305
- source: "testkit",
306
- notice: "Generated file. Do not edit manually.",
307
- product: {
308
- name: path.basename(productDir),
309
- },
310
- git: {
311
- branch: metadata.git?.branch || null,
312
- commitSha: metadata.git?.commitSha || null,
313
- },
314
- testkitVersion: metadata.testkitVersion,
315
- scope,
316
- summary,
317
- tests,
318
- };
319
- }
320
-
321
- export function buildRunArtifact({
322
- productDir,
323
- results,
324
- startedAt,
325
- finishedAt,
326
- requestedJobs,
327
- workerCount,
328
- suiteType,
329
- suiteNames,
330
- fileNames,
331
- framework,
332
- shard,
333
- serviceFilter,
334
- metadata,
335
- }) {
336
- const executed = results.filter((result) => !result.skipped);
337
- const failedServices = executed.filter((result) => result.failed);
338
- const totalSuites = executed.reduce((sum, result) => sum + result.suiteCount, 0);
339
- const completedSuites = executed.reduce((sum, result) => sum + result.completedSuiteCount, 0);
340
- const failedSuites = executed.reduce((sum, result) => sum + result.failedSuiteCount, 0);
341
- const totalFiles = executed.reduce((sum, result) => sum + (result.totalFileCount || 0), 0);
342
- const passedFiles = executed.reduce((sum, result) => sum + (result.passedFileCount || 0), 0);
343
- const failedFiles = executed.reduce((sum, result) => sum + (result.failedFileCount || 0), 0);
344
- const notRunFiles = executed.reduce((sum, result) => sum + (result.notRunFileCount || 0), 0);
345
- const dbBackend = summarizeDbBackend(results);
346
-
347
- return {
348
- schemaVersion: 1,
349
- source: "testkit",
350
- generatedAt: new Date(finishedAt).toISOString(),
351
- product: {
352
- name: path.basename(productDir),
353
- directory: productDir,
354
- },
355
- git: metadata.git,
356
- host: metadata.host,
357
- run: {
358
- status: failedServices.length > 0 ? "failed" : "passed",
359
- startedAt: new Date(startedAt).toISOString(),
360
- finishedAt: new Date(finishedAt).toISOString(),
361
- durationMs: finishedAt - startedAt,
362
- requestedJobs,
363
- workerCount,
364
- dbBackend,
365
- suiteType,
366
- suiteNames,
367
- fileNames,
368
- framework: formatFrameworkForArtifact(framework),
369
- shard,
370
- serviceFilter,
371
- testkitVersion: metadata.testkitVersion,
372
- },
373
- summary: {
374
- services: {
375
- total: executed.length,
376
- passed: executed.length - failedServices.length,
377
- failed: failedServices.length,
378
- },
379
- suites: {
380
- total: totalSuites,
381
- completed: completedSuites,
382
- passed: completedSuites - failedSuites,
383
- failed: failedSuites,
384
- },
385
- files: {
386
- total: totalFiles,
387
- passed: passedFiles,
388
- failed: failedFiles,
389
- notRun: notRunFiles,
390
- },
391
- },
392
- services: results.map((result) => ({
393
- name: result.name,
394
- failed: result.failed,
395
- skipped: result.skipped,
396
- suiteCount: result.suiteCount,
397
- completedSuiteCount: result.completedSuiteCount,
398
- failedSuiteCount: result.failedSuiteCount,
399
- totalFileCount: result.totalFileCount,
400
- completedFileCount: result.completedFileCount,
401
- passedFileCount: result.passedFileCount,
402
- failedFileCount: result.failedFileCount,
403
- notRunFileCount: result.notRunFileCount,
404
- durationMs: result.durationMs,
405
- totalTaskDurationMs: result.totalTaskDurationMs,
406
- dbBackend: result.dbBackend,
407
- suites: result.suites,
408
- errors: result.errors,
409
- })),
410
- };
411
- }
412
-
413
198
  export function summarizeDbBackend(results) {
414
199
  const values = [...new Set(results.map((result) => result.dbBackend).filter(Boolean))];
415
200
  if (values.length === 0) return null;
@@ -417,36 +202,32 @@ export function summarizeDbBackend(results) {
417
202
  return "mixed";
418
203
  }
419
204
 
420
- export function formatDuration(durationMs) {
421
- const totalSeconds = Math.max(0, Math.round(durationMs / 1000));
422
- const minutes = Math.floor(totalSeconds / 60);
423
- const seconds = totalSeconds % 60;
424
- if (minutes === 0) return `${seconds}s`;
425
- return `${minutes}m ${String(seconds).padStart(2, "0")}s`;
426
- }
427
-
428
- export function formatServiceSummary(result) {
429
- const passedSuites = result.completedSuiteCount - result.failedSuiteCount;
430
- const notRunSuites = result.suiteCount - result.completedSuiteCount;
431
- let detail = `${passedSuites}/${result.suiteCount} suites passed`;
432
- if ((result.totalFileCount || 0) > 0) {
433
- detail += `, ${result.passedFileCount}/${result.totalFileCount} files passed`;
434
- }
435
- if (notRunSuites > 0) {
436
- detail += `, ${notRunSuites} ${pluralize(notRunSuites, "suite", "suites")} not run`;
437
- } else if ((result.notRunFileCount || 0) > 0) {
438
- detail += `, ${result.notRunFileCount} ${pluralize(result.notRunFileCount, "file", "files")} not run`;
439
- }
440
- return detail;
441
- }
442
-
443
- export function formatError(error) {
444
- if (error instanceof Error) return sanitizeErrorMessage(error.message);
445
- return sanitizeErrorMessage(String(error));
446
- }
205
+ function finalizeSuite(suite) {
206
+ const files = [...suite.fileResultsByPath.values()]
207
+ .sort((a, b) => a.path.localeCompare(b.path))
208
+ .map((file) => ({
209
+ path: file.path,
210
+ failed: file.failed,
211
+ status: file.status,
212
+ durationMs: file.durationMs,
213
+ error: file.error,
214
+ }));
447
215
 
448
- export function longestServiceName(results) {
449
- return results.reduce((max, result) => Math.max(max, result.name.length), 4);
216
+ return {
217
+ name: suite.name,
218
+ type: suite.type,
219
+ framework: formatFrameworkForArtifact(suite.framework),
220
+ failed: suite.failedFiles.length > 0,
221
+ fileCount: suite.fileCount,
222
+ completedFileCount: suite.completedFileCount,
223
+ passedFileCount: files.filter((file) => file.status === "passed").length,
224
+ failedFileCount: suite.failedFiles.length,
225
+ notRunFileCount: suite.fileCount - suite.completedFileCount,
226
+ failedFiles: suite.failedFiles,
227
+ durationMs: suite.durationMs,
228
+ error: suite.error,
229
+ files,
230
+ };
450
231
  }
451
232
 
452
233
  function normalizePathSeparators(filePath) {
@@ -457,14 +238,3 @@ function formatFrameworkForArtifact(framework) {
457
238
  if (framework === "k6") return "default";
458
239
  return framework;
459
240
  }
460
-
461
- function sanitizeErrorMessage(message) {
462
- return message
463
- .replace(/Command failed with exit code (\d+): .*?[\\/]vendor[\\/]k6 run\b/g, "Default runtime failed with exit code $1:")
464
- .replace(/Command failed with exit code (\d+): k6 run\b/g, "Default runtime failed with exit code $1:")
465
- .replace(/[\\/]vendor[\\/]k6\b/g, "default-runtime");
466
- }
467
-
468
- function pluralize(value, singular, plural) {
469
- return value === 1 ? singular : plural;
470
- }
@@ -1,19 +1,14 @@
1
1
  import { describe, expect, it } from "vitest";
2
2
  import {
3
3
  addTrackerError,
4
- buildRunArtifact,
5
- buildStatusArtifact,
6
4
  buildServiceTrackers,
7
5
  finalizeServiceResult,
8
- formatDuration,
9
- formatError,
10
- formatServiceSummary,
11
6
  recordGraphError,
12
7
  recordTaskOutcome,
13
8
  summarizeDbBackend,
14
9
  } from "./results.mjs";
15
10
 
16
- describe("runner-results", () => {
11
+ describe("runner results", () => {
17
12
  it("tracks task outcomes and graph errors", () => {
18
13
  const trackers = buildServiceTrackers(
19
14
  [
@@ -198,204 +193,9 @@ describe("runner-results", () => {
198
193
  expect(result.totalTaskDurationMs).toBe(3_000);
199
194
  });
200
195
 
201
- it("builds run artifacts and formatting helpers", () => {
202
- const results = [
203
- {
204
- name: "api",
205
- failed: false,
206
- skipped: false,
207
- suiteCount: 1,
208
- completedSuiteCount: 1,
209
- failedSuiteCount: 0,
210
- totalFileCount: 3,
211
- completedFileCount: 3,
212
- passedFileCount: 3,
213
- failedFileCount: 0,
214
- notRunFileCount: 0,
215
- durationMs: 1200,
216
- totalTaskDurationMs: 2400,
217
- dbBackend: "local",
218
- suites: [],
219
- errors: [],
220
- },
221
- {
222
- name: "frontend",
223
- failed: false,
224
- skipped: true,
225
- suiteCount: 0,
226
- completedSuiteCount: 0,
227
- failedSuiteCount: 0,
228
- totalFileCount: 0,
229
- completedFileCount: 0,
230
- passedFileCount: 0,
231
- failedFileCount: 0,
232
- notRunFileCount: 0,
233
- durationMs: 0,
234
- totalTaskDurationMs: 0,
235
- dbBackend: null,
236
- suites: [],
237
- errors: [],
238
- },
239
- ];
240
-
241
- const artifact = buildRunArtifact({
242
- productDir: "/tmp/my-product",
243
- results,
244
- startedAt: 1000,
245
- finishedAt: 4000,
246
- requestedJobs: 2,
247
- workerCount: 1,
248
- suiteType: "all",
249
- suiteNames: [],
250
- fileNames: [],
251
- framework: "all",
252
- shard: null,
253
- serviceFilter: null,
254
- metadata: {
255
- git: {
256
- branch: "main",
257
- commitSha: "abc",
258
- repoRoot: "/tmp",
259
- },
260
- host: {
261
- hostname: "local",
262
- username: "dev",
263
- },
264
- testkitVersion: "0.1.17",
265
- },
266
- });
267
-
268
- expect(artifact.product.name).toBe("my-product");
269
- expect(artifact.summary.services.total).toBe(1);
270
- expect(artifact.summary.files).toEqual({
271
- total: 3,
272
- passed: 3,
273
- failed: 0,
274
- notRun: 0,
275
- });
276
- expect(artifact.services[0].durationMs).toBe(1200);
277
- expect(artifact.services[0].totalTaskDurationMs).toBe(2400);
278
- expect(summarizeDbBackend(results)).toBe("local");
279
- expect(formatDuration(65_000)).toBe("1m 05s");
196
+ it("summarizes mixed db backends", () => {
280
197
  expect(
281
- formatServiceSummary({
282
- completedSuiteCount: 2,
283
- failedSuiteCount: 1,
284
- suiteCount: 3,
285
- totalFileCount: 6,
286
- passedFileCount: 5,
287
- notRunFileCount: 1,
288
- })
289
- ).toBe("1/3 suites passed, 5/6 files passed, 1 suite not run");
290
- expect(formatError(new Error("boom"))).toBe("boom");
291
- });
292
-
293
- it("builds deterministic status artifacts", () => {
294
- const status = buildStatusArtifact({
295
- productDir: "/tmp/my-product",
296
- results: [
297
- {
298
- name: "api",
299
- failed: true,
300
- skipped: false,
301
- suites: [
302
- {
303
- name: "health",
304
- type: "integration",
305
- framework: "k6",
306
- files: [
307
- { path: "tests/api/integration/a.int.testkit.ts", status: "passed" },
308
- { path: "tests/api/integration/b.int.testkit.ts", status: "failed" },
309
- ],
310
- },
311
- ],
312
- },
313
- ],
314
- suiteType: "int",
315
- suiteNames: ["health"],
316
- fileNames: ["tests/api/integration/b.int.testkit.ts"],
317
- framework: "default",
318
- shard: null,
319
- serviceFilter: "api",
320
- metadata: {
321
- git: {
322
- branch: "main",
323
- commitSha: "abc123",
324
- },
325
- testkitVersion: "0.1.20",
326
- },
327
- });
328
-
329
- expect(status).toEqual({
330
- schemaVersion: 1,
331
- source: "testkit",
332
- notice: "Generated file. Do not edit manually.",
333
- product: {
334
- name: "my-product",
335
- },
336
- git: {
337
- branch: "main",
338
- commitSha: "abc123",
339
- },
340
- testkitVersion: "0.1.20",
341
- scope: {
342
- suiteType: "int",
343
- suiteNames: ["health"],
344
- fileNames: ["tests/api/integration/b.int.testkit.ts"],
345
- framework: "default",
346
- shard: null,
347
- serviceFilter: "api",
348
- isFullRun: false,
349
- },
350
- summary: {
351
- services: {
352
- total: 1,
353
- passed: 0,
354
- failed: 1,
355
- },
356
- tests: {
357
- total: 2,
358
- passed: 1,
359
- failed: 1,
360
- notRun: 0,
361
- },
362
- },
363
- tests: [
364
- {
365
- service: "api",
366
- type: "integration",
367
- path: "tests/api/integration/a.int.testkit.ts",
368
- status: "passed",
369
- },
370
- {
371
- service: "api",
372
- type: "integration",
373
- path: "tests/api/integration/b.int.testkit.ts",
374
- status: "failed",
375
- },
376
- ],
377
- });
378
- });
379
-
380
- it("marks unfiltered status artifacts as full runs", () => {
381
- const status = buildStatusArtifact({
382
- productDir: "/tmp/my-product",
383
- results: [],
384
- suiteType: "all",
385
- suiteNames: [],
386
- fileNames: [],
387
- framework: "all",
388
- shard: null,
389
- serviceFilter: null,
390
- metadata: {
391
- git: {
392
- branch: "main",
393
- commitSha: "abc123",
394
- },
395
- testkitVersion: "0.1.20",
396
- },
397
- });
398
-
399
- expect(status.scope.isFullRun).toBe(true);
198
+ summarizeDbBackend([{ dbBackend: "local" }, { dbBackend: "neon" }])
199
+ ).toBe("mixed");
400
200
  });
401
201
  });