@jsenv/test 2.1.3 → 3.0.1

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 (46) hide show
  1. package/README.md +2 -1
  2. package/dist/js/exception.js +908 -0
  3. package/dist/js/execute_using_dynamic_import.js +76 -167
  4. package/dist/js/v8_coverage.js +4 -482
  5. package/dist/jsenv_test.js +4608 -2917
  6. package/dist/node_child_process_controlled.mjs +95 -76
  7. package/dist/node_worker_thread_controlled.mjs +72 -54
  8. package/package.json +23 -14
  9. package/src/coverage/generate_coverage.js +186 -0
  10. package/src/coverage/list_files_not_covered.js +2 -2
  11. package/src/coverage/missing_coverage.js +2 -2
  12. package/src/coverage/report_coverage_as_html.js +45 -0
  13. package/src/coverage/report_coverage_as_json.js +20 -0
  14. package/src/coverage/{coverage_reporter_text_log.js → report_coverage_in_console.js} +13 -7
  15. package/src/coverage/v8_and_istanbul.js +4 -4
  16. package/src/coverage/v8_coverage.js +2 -2
  17. package/src/execution/exception.js +151 -68
  18. package/src/execution/execute.js +24 -11
  19. package/src/execution/execute_test_plan.js +834 -750
  20. package/src/execution/format_error_for_terminal.js +187 -0
  21. package/src/execution/github_annotation_from_error.js +3 -3
  22. package/src/execution/junit_xml_file/report_as_junit_xml.js +149 -0
  23. package/src/execution/junit_xml_file/xml_generator.js +141 -0
  24. package/src/execution/reporters/reporter_list.js +687 -0
  25. package/src/execution/run.js +92 -30
  26. package/src/execution/web_server/start_using_command.js +2 -2
  27. package/src/execution/web_server_param.js +5 -5
  28. package/src/helpers/cpu_usage.js +142 -0
  29. package/src/helpers/cpu_usage.test_manual.js +7 -0
  30. package/src/main.js +8 -0
  31. package/src/runtime_browsers/chromium.js +1 -3
  32. package/src/runtime_browsers/firefox.js +0 -3
  33. package/src/runtime_browsers/middleware_istanbul.js +2 -2
  34. package/src/runtime_browsers/using_playwright.js +296 -220
  35. package/src/runtime_browsers/webkit.js +0 -3
  36. package/src/runtime_node/execute_using_dynamic_import.js +75 -28
  37. package/src/runtime_node/node_child_process.js +250 -139
  38. package/src/runtime_node/node_child_process_controlled.mjs +94 -75
  39. package/src/runtime_node/node_worker_thread.js +206 -120
  40. package/src/runtime_node/node_worker_thread_controlled.mjs +70 -53
  41. package/src/coverage/coverage_reporter_html_directory.js +0 -32
  42. package/src/coverage/coverage_reporter_json_file.js +0 -17
  43. package/src/coverage/report_to_coverage.js +0 -198
  44. package/src/execution/execution_colors.js +0 -10
  45. package/src/execution/execution_steps.js +0 -65
  46. package/src/execution/logs_file_execution.js +0 -495
@@ -0,0 +1,687 @@
1
+ import { memoryUsage } from "node:process";
2
+ import stripAnsi from "strip-ansi";
3
+ import { writeFileSync } from "@jsenv/filesystem";
4
+ import { inspectDuration, inspectMemoryUsage } from "@jsenv/inspect";
5
+ import { createDynamicLog, ANSI, UNICODE } from "@jsenv/log";
6
+
7
+ import { formatErrorForTerminal } from "../format_error_for_terminal.js";
8
+
9
+ /*
10
+ * label
11
+ * ┌───────────────────────┴────────────────────────────────┐
12
+ * │ │ │
13
+ * description runtime info │
14
+ * ┌────────┴─────────┐ ┌─────┴───────┐ │
15
+ * │ │ │ │ │ │
16
+ * icon number file group duration memory intermediate summary
17
+ * ┌┴┐┌───┴─┐ ┌────────┴─────────┐ ┌───┴────┐┌─┴─┐ ┌─┴──┐ ┌──────────┴──────────┐
18
+ * ✔ 001/100 tests/file.test.html [chromium/10.4s/14.5MB] (2 completed, 1 failed)
19
+ * ------- console (i1 ✖1) -------
20
+ * i info
21
+ * ✖ error
22
+ * -------------------------------
23
+ * ---------- error -------
24
+ * 1 | throw new Error("test");
25
+ * ^
26
+ * Error: test
27
+ * at file://demo/file.test.js:1:1
28
+ * ------------------------
29
+ */
30
+
31
+ export const reporterList = ({
32
+ animated = true,
33
+ mockFluctuatingValues, // used for snapshot testing logs
34
+ showMemoryUsage = true,
35
+ spy = () => {
36
+ return {
37
+ write: (log) => {
38
+ process.stdout.write(log);
39
+ },
40
+ end: () => {},
41
+ };
42
+ },
43
+ fileUrl,
44
+ }) => {
45
+ const animatedLogEnabled =
46
+ animated &&
47
+ // canEraseProcessStdout
48
+ process.stdout.isTTY &&
49
+ // if there is an error during execution npm will mess up the output
50
+ // (happens when npm runs several command in a workspace)
51
+ // so we enable hot replace only when !process.exitCode (no error so far)
52
+ process.exitCode !== 1;
53
+
54
+ const logOptions = {
55
+ showMemoryUsage,
56
+ mockFluctuatingValues,
57
+ group: false,
58
+ intermediateSummary: !animatedLogEnabled,
59
+ };
60
+
61
+ let startMs = Date.now();
62
+
63
+ const reporters = [
64
+ {
65
+ reporter: "list",
66
+ beforeAll: async (testPlanResult) => {
67
+ let spyReturnValue = await spy();
68
+ let write = spyReturnValue.write;
69
+ let end = spyReturnValue.end;
70
+ spyReturnValue = undefined;
71
+
72
+ logOptions.group = Object.keys(testPlanResult.groups).length > 1;
73
+ write(renderIntro(testPlanResult, logOptions));
74
+ if (!animatedLogEnabled) {
75
+ return {
76
+ afterEachInOrder: (execution) => {
77
+ const log = renderExecutionLog(execution, logOptions);
78
+ write(log);
79
+ },
80
+ afterAll: async () => {
81
+ await write(renderOutro(testPlanResult, logOptions));
82
+ write = undefined;
83
+ if (end) {
84
+ await end();
85
+ end = undefined;
86
+ }
87
+ },
88
+ };
89
+ }
90
+
91
+ let dynamicLog = createDynamicLog({
92
+ stream: { write },
93
+ });
94
+ const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
95
+ let frameIndex = 0;
96
+ const renderDynamicLog = (testPlanResult) => {
97
+ frameIndex = frameIndex === frames.length - 1 ? 0 : frameIndex + 1;
98
+ let dynamicLogContent = "";
99
+ dynamicLogContent += `${frames[frameIndex]} `;
100
+ dynamicLogContent += renderStatusRepartition(
101
+ testPlanResult.counters,
102
+ {
103
+ showProgression: true,
104
+ },
105
+ );
106
+
107
+ const msEllapsed = Date.now() - startMs;
108
+ const infos = [];
109
+ const duration = inspectDuration(msEllapsed, {
110
+ short: true,
111
+ decimals: 0,
112
+ rounded: false,
113
+ });
114
+ infos.push(ANSI.color(duration, ANSI.GREY));
115
+ const memoryHeapUsed = memoryUsage().heapUsed;
116
+ const memoryHeapUsedFormatted = inspectMemoryUsage(memoryHeapUsed, {
117
+ short: true,
118
+ decimals: 0,
119
+ });
120
+ infos.push(ANSI.color(memoryHeapUsedFormatted, ANSI.GREY));
121
+
122
+ const infoFormatted = infos.join(ANSI.color(`/`, ANSI.GREY));
123
+ dynamicLogContent += ` ${ANSI.color(
124
+ "[",
125
+ ANSI.GREY,
126
+ )}${infoFormatted}${ANSI.color("]", ANSI.GREY)}`;
127
+
128
+ dynamicLogContent = `\n${dynamicLogContent}\n`;
129
+ return dynamicLogContent;
130
+ };
131
+ dynamicLog.update(renderDynamicLog(testPlanResult));
132
+ const interval = setInterval(() => {
133
+ dynamicLog.update(renderDynamicLog(testPlanResult));
134
+ }, 150);
135
+
136
+ return {
137
+ afterEachInOrder: (execution) => {
138
+ dynamicLog.clearDuringFunctionCall(() => {
139
+ const log = renderExecutionLog(execution, logOptions);
140
+ write(log);
141
+ });
142
+ },
143
+ afterAll: async () => {
144
+ dynamicLog.update("");
145
+ dynamicLog.destroy();
146
+ dynamicLog = null;
147
+ clearInterval(interval);
148
+ await write(renderOutro(testPlanResult, logOptions));
149
+ write = undefined;
150
+ await end();
151
+ end = undefined;
152
+ },
153
+ };
154
+ },
155
+ },
156
+ ];
157
+
158
+ if (animated && fileUrl) {
159
+ reporters.push(
160
+ reporterList({
161
+ animated: false,
162
+ mockFluctuatingValues, // used for snapshot testing logs
163
+ showMemoryUsage,
164
+ spy: () => {
165
+ let rawOutput = "";
166
+
167
+ return {
168
+ write: (log) => {
169
+ rawOutput += stripAnsi(log);
170
+ writeFileSync(fileUrl, rawOutput);
171
+ },
172
+ afterAll: () => {},
173
+ };
174
+ },
175
+ }),
176
+ );
177
+ }
178
+
179
+ return reporters;
180
+ };
181
+
182
+ const renderIntro = (testPlanResult, logOptions) => {
183
+ const { counters } = testPlanResult;
184
+ const planified = counters.planified;
185
+ const { groups } = testPlanResult;
186
+ const groupNames = Object.keys(groups);
187
+
188
+ if (planified === 0) {
189
+ return `${renderBigSection({
190
+ title: "nothing to execute",
191
+ content: "",
192
+ })}\n\n`;
193
+ }
194
+ if (planified === 1) {
195
+ const groupName = groupNames[0];
196
+ const groupInfo = groups[groupName];
197
+ return `${renderBigSection({
198
+ title: "1 execution to run",
199
+ content: `${groupName} (${getGroupRenderedName(groupInfo, logOptions)})`,
200
+ })}\n\n`;
201
+ }
202
+ if (groupNames.length === 1) {
203
+ const groupName = groupNames[0];
204
+ const groupInfo = groups[groupName];
205
+ return `${renderBigSection({
206
+ title: `${planified} executions to run`,
207
+ content: `${groupName} (${getGroupRenderedName(groupInfo, logOptions)})`,
208
+ })}\n\n`;
209
+ }
210
+
211
+ let introLines = [];
212
+ for (const groupName of groupNames) {
213
+ const groupInfo = groups[groupName];
214
+ introLines.push(
215
+ `${groupInfo.count} with ${groupName} (${getGroupRenderedName(
216
+ groupInfo,
217
+ logOptions,
218
+ )})`,
219
+ );
220
+ }
221
+ return `${renderBigSection({
222
+ title: `${planified} executions to run`,
223
+ content: introLines.join("\n"),
224
+ })}\n\n`;
225
+ };
226
+
227
+ const getGroupRenderedName = (groupInfo, logOptions) => {
228
+ let { runtimeName, runtimeVersion } = groupInfo;
229
+ if (logOptions.mockFluctuatingValues && groupInfo.runtimeType === "node") {
230
+ runtimeVersion = "<mock>";
231
+ }
232
+ return `${runtimeName}@${runtimeVersion}`;
233
+ };
234
+
235
+ const renderExecutionLog = (execution, logOptions) => {
236
+ let log = "";
237
+ // label
238
+ {
239
+ const label = renderExecutionLabel(execution, logOptions);
240
+ log += label;
241
+ }
242
+ // console calls
243
+ {
244
+ const { consoleCalls = [] } = execution.result;
245
+ const consoleOutput = renderConsole(consoleCalls);
246
+ if (consoleOutput) {
247
+ log += `\n${consoleOutput}`;
248
+ }
249
+ }
250
+ // errors
251
+ {
252
+ const errorOutput = renderErrors(execution, logOptions);
253
+ if (errorOutput) {
254
+ log += `\n${errorOutput}`;
255
+ }
256
+ }
257
+ // const { columns = 80 } = process.stdout;
258
+ // log = wrapAnsi(log, columns, {
259
+ // trim: false,
260
+ // hard: true,
261
+ // wordWrap: false,
262
+ // });
263
+ return `${log}\n`;
264
+ };
265
+
266
+ const renderExecutionLabel = (execution, logOptions) => {
267
+ let label = "";
268
+
269
+ // description
270
+ {
271
+ const description =
272
+ descriptionFormatters[execution.result.status](execution);
273
+ label += description;
274
+ }
275
+ // runtimeInfo
276
+ {
277
+ const infos = [];
278
+ if (logOptions.group) {
279
+ infos.push(ANSI.color(execution.group, ANSI.GREY));
280
+ }
281
+ const { timings, memoryUsage } = execution.result;
282
+ if (timings) {
283
+ const duration = timings.executionEnd - timings.executionStart;
284
+ const durationFormatted = logOptions.mockFluctuatingValues
285
+ ? `<mock>ms`
286
+ : inspectDuration(duration, { short: true });
287
+ infos.push(ANSI.color(durationFormatted, ANSI.GREY));
288
+ }
289
+ if (logOptions.showMemoryUsage && typeof memoryUsage === "number") {
290
+ const memoryUsageFormatted = logOptions.mockFluctuatingValues
291
+ ? `<mock>MB`
292
+ : inspectMemoryUsage(memoryUsage, { short: true });
293
+ infos.push(ANSI.color(memoryUsageFormatted, ANSI.GREY));
294
+ }
295
+ if (infos.length) {
296
+ const runtimeInfo = infos.join(ANSI.color(`/`, ANSI.GREY));
297
+ label += ` ${ANSI.color("[", ANSI.GREY)}${runtimeInfo}${ANSI.color(
298
+ "]",
299
+ ANSI.GREY,
300
+ )}`;
301
+ }
302
+ }
303
+ // intersummary
304
+ if (logOptions.intermediateSummary) {
305
+ let intermediateSummary = "";
306
+ intermediateSummary += renderStatusRepartition(execution.countersInOrder);
307
+ label += ` (${intermediateSummary})`;
308
+ }
309
+
310
+ return label;
311
+ };
312
+ const descriptionFormatters = {
313
+ executing: ({ fileRelativeUrl }) => {
314
+ return ANSI.color(`${fileRelativeUrl}`, COLOR_EXECUTING);
315
+ },
316
+ aborted: ({ index, countersInOrder, fileRelativeUrl }) => {
317
+ const total = countersInOrder.planified;
318
+ const number = fillLeft(index + 1, total, "0");
319
+
320
+ return ANSI.color(
321
+ `${UNICODE.FAILURE_RAW} ${number}/${total} ${fileRelativeUrl}`,
322
+ COLOR_ABORTED,
323
+ );
324
+ },
325
+ timedout: ({ index, countersInOrder, fileRelativeUrl, params }) => {
326
+ const total = countersInOrder.planified;
327
+ const number = fillLeft(index + 1, total, "0");
328
+
329
+ return ANSI.color(
330
+ `${UNICODE.FAILURE_RAW} ${number}/${total} ${fileRelativeUrl} timeout after ${params.allocatedMs}ms`,
331
+ COLOR_TIMEOUT,
332
+ );
333
+ },
334
+ failed: ({ index, countersInOrder, fileRelativeUrl }) => {
335
+ const total = countersInOrder.planified;
336
+ const number = fillLeft(index + 1, total, "0");
337
+
338
+ return ANSI.color(
339
+ `${UNICODE.FAILURE_RAW} ${number}/${total} ${fileRelativeUrl}`,
340
+ COLOR_FAILED,
341
+ );
342
+ },
343
+ completed: ({ index, countersInOrder, fileRelativeUrl }) => {
344
+ const total = countersInOrder.planified;
345
+ const number = fillLeft(index + 1, total, "0");
346
+
347
+ return ANSI.color(
348
+ `${UNICODE.OK_RAW} ${number}/${total} ${fileRelativeUrl}`,
349
+ COLOR_COMPLETED,
350
+ );
351
+ },
352
+ cancelled: ({ index, countersInOrder, fileRelativeUrl }) => {
353
+ const total = countersInOrder.planified;
354
+ const number = fillLeft(index + 1, total, "0");
355
+
356
+ return ANSI.color(
357
+ `${UNICODE.FAILURE_RAW} ${number}/${total} ${fileRelativeUrl}`,
358
+ COLOR_CANCELLED,
359
+ );
360
+ },
361
+ };
362
+
363
+ const COLOR_EXECUTING = ANSI.BLUE;
364
+ const COLOR_ABORTED = ANSI.MAGENTA;
365
+ const COLOR_TIMEOUT = ANSI.MAGENTA;
366
+ const COLOR_FAILED = ANSI.RED;
367
+ const COLOR_COMPLETED = ANSI.GREEN;
368
+ const COLOR_CANCELLED = ANSI.GREY;
369
+ const fillLeft = (value, biggestValue, char = " ") => {
370
+ const width = String(value).length;
371
+ const biggestWidth = String(biggestValue).length;
372
+ let missingWidth = biggestWidth - width;
373
+ let padded = "";
374
+ while (missingWidth--) {
375
+ padded += char;
376
+ }
377
+ padded += value;
378
+ return padded;
379
+ };
380
+ // const fillRight = (value, biggestValue, char = " ") => {
381
+ // const width = String(value).length;
382
+ // const biggestWidth = String(biggestValue).length;
383
+ // let missingWidth = biggestWidth - width;
384
+ // let padded = "";
385
+ // padded += value;
386
+ // while (missingWidth--) {
387
+ // padded += char;
388
+ // }
389
+ // return padded;
390
+ // };
391
+
392
+ const renderConsole = (consoleCalls) => {
393
+ if (consoleCalls.length === 0) {
394
+ return "";
395
+ }
396
+ const consoleRepartition = {
397
+ debug: 0,
398
+ info: 0,
399
+ warning: 0,
400
+ error: 0,
401
+ log: 0,
402
+ };
403
+ consoleCalls.forEach((consoleCall) => {
404
+ consoleRepartition[consoleCall.type]++;
405
+ });
406
+ const consoleOutput = renderConsoleOutput(consoleCalls);
407
+ const consoleSummary = renderConsoleSummary(consoleRepartition);
408
+ return renderSection({
409
+ title: consoleSummary,
410
+ content: consoleOutput,
411
+ });
412
+ };
413
+ const renderConsoleSummary = (consoleRepartition) => {
414
+ const { debug, info, warning, error } = consoleRepartition;
415
+ const parts = [];
416
+ if (error) {
417
+ parts.push(`${CONSOLE_ICONS.error} ${error}`);
418
+ }
419
+ if (warning) {
420
+ parts.push(`${CONSOLE_ICONS.warning} ${warning}`);
421
+ }
422
+ if (info) {
423
+ parts.push(`${CONSOLE_ICONS.info} ${info}`);
424
+ }
425
+ if (debug) {
426
+ parts.push(`${CONSOLE_ICONS.debug} ${debug}`);
427
+ }
428
+ if (parts.length === 0) {
429
+ return `console`;
430
+ }
431
+ return `console (${parts.join(" ")})`;
432
+ };
433
+ const renderConsoleOutput = (consoleCalls) => {
434
+ // inside Node.js you can do process.stdout.write()
435
+ // and in that case the consoleCall is not suffixed with "\n"
436
+ // we want to keep these calls together in the output
437
+ const regroupedCalls = [];
438
+ consoleCalls.forEach((consoleCall, index) => {
439
+ if (index === 0) {
440
+ regroupedCalls.push(consoleCall);
441
+ return;
442
+ }
443
+ const previousCall = consoleCalls[index - 1];
444
+ if (previousCall.type !== consoleCall.type) {
445
+ regroupedCalls.push(consoleCall);
446
+ return;
447
+ }
448
+ if (previousCall.text.endsWith("\n")) {
449
+ regroupedCalls.push(consoleCall);
450
+ return;
451
+ }
452
+ if (previousCall.text.endsWith("\r")) {
453
+ regroupedCalls.push(consoleCall);
454
+ return;
455
+ }
456
+ const previousRegroupedCallIndex = regroupedCalls.length - 1;
457
+ const previousRegroupedCall = regroupedCalls[previousRegroupedCallIndex];
458
+ previousRegroupedCall.text = `${previousRegroupedCall.text}${consoleCall.text}`;
459
+ });
460
+
461
+ let consoleOutput = ``;
462
+ regroupedCalls.forEach((regroupedCall, index) => {
463
+ const text = regroupedCall.text;
464
+ const textFormatted = prefixFirstAndIndentRemainingLines({
465
+ prefix: CONSOLE_ICONS[regroupedCall.type],
466
+ text,
467
+ trimLines: true,
468
+ trimLastLine: index === regroupedCalls.length - 1,
469
+ });
470
+ consoleOutput += textFormatted;
471
+ });
472
+ return consoleOutput;
473
+ };
474
+ const prefixFirstAndIndentRemainingLines = ({
475
+ prefix,
476
+ indentation = " ",
477
+ text,
478
+ trimLines,
479
+ trimLastLine,
480
+ }) => {
481
+ const lines = text.split(/\r?\n/);
482
+ const firstLine = lines.shift();
483
+ let result = `${prefix} ${firstLine}`;
484
+ let i = 0;
485
+ while (i < lines.length) {
486
+ const line = trimLines ? lines[i].trim() : lines[i];
487
+ i++;
488
+ result += line.length
489
+ ? `\n${indentation}${line}`
490
+ : trimLastLine && i === lines.length
491
+ ? ""
492
+ : `\n`;
493
+ }
494
+ return result;
495
+ };
496
+ const CONSOLE_ICONS = {
497
+ get debug() {
498
+ return UNICODE.DEBUG;
499
+ },
500
+ get info() {
501
+ return UNICODE.INFO;
502
+ },
503
+ get warning() {
504
+ return UNICODE.WARNING;
505
+ },
506
+ get error() {
507
+ return UNICODE.FAILURE;
508
+ },
509
+ log: " ",
510
+ };
511
+
512
+ const renderErrors = (execution, logOptions) => {
513
+ const { errors = [] } = execution.result;
514
+ if (errors.length === 0) {
515
+ return "";
516
+ }
517
+
518
+ if (errors.length === 1) {
519
+ return renderSection({
520
+ dashColor: ANSI.GREY,
521
+ title: "error",
522
+ content: formatErrorForTerminal(errors[0], {
523
+ rootDirectoryUrl: execution.rootDirectoryUrl,
524
+ mainFileRelativeUrl: execution.fileRelativeUrl,
525
+ mockFluctuatingValues: logOptions.mockFluctuatingValues,
526
+ tryColors: true,
527
+ }),
528
+ });
529
+ }
530
+
531
+ let output = [];
532
+ errors.forEach((error) => {
533
+ output.push(
534
+ prefixFirstAndIndentRemainingLines({
535
+ prefix: `${UNICODE.CIRCLE_CROSS} `,
536
+ indentation: " ",
537
+ text: formatErrorForTerminal(error, {
538
+ rootDirectoryUrl: execution.rootDirectoryUrl,
539
+ mainFileRelativeUrl: execution.fileRelativeUrl,
540
+ mockFluctuatingValues: logOptions.mockFluctuatingValues,
541
+ tryColors: true,
542
+ }),
543
+ }),
544
+ );
545
+ });
546
+ return renderSection({
547
+ dashColor: ANSI.GREY,
548
+ title: `errors (${errors.length})`,
549
+ content: output.join(`\n`),
550
+ });
551
+ };
552
+
553
+ export const renderOutro = (testPlanResult, logOptions = {}) => {
554
+ let finalSummary = "";
555
+ const { counters } = testPlanResult;
556
+ const { planified } = counters;
557
+
558
+ if (planified === 1) {
559
+ finalSummary += `1 execution: `;
560
+ } else {
561
+ finalSummary += `${planified} executions: `;
562
+ }
563
+ finalSummary += renderStatusRepartition(counters);
564
+
565
+ let durationLog = ``;
566
+ const { timings } = testPlanResult;
567
+ if (logOptions.mockFluctuatingValues) {
568
+ durationLog += "<mock>s";
569
+ } else {
570
+ durationLog += inspectDuration(timings.end, { short: true });
571
+ const namedTimings = {
572
+ setup: timings.executionStart,
573
+ execution: timings.executionEnd,
574
+ teardown: timings.teardownEnd - timings.executionEnd,
575
+ ...(testPlanResult.coverage
576
+ ? { coverage: timings.coverageTeardownEnd - timings.teardownEnd }
577
+ : {}),
578
+ };
579
+ const timingDetails = [];
580
+ for (const key of Object.keys(namedTimings)) {
581
+ const value = namedTimings[key];
582
+ const timingDuration = inspectDuration(value, { short: true });
583
+ const timingString = `${ANSI.color(`${key}:`, ANSI.GREY)} ${ANSI.color(
584
+ timingDuration,
585
+ ANSI.GREY,
586
+ )}`;
587
+ timingDetails.push(ANSI.color(timingString, ANSI.GREY));
588
+ }
589
+ if (timingDetails.length) {
590
+ durationLog += ` ${ANSI.color("(", ANSI.GREY)}${timingDetails.join(
591
+ ANSI.color(", ", ANSI.GREY),
592
+ )}${ANSI.color(")", ANSI.GREY)}`;
593
+ }
594
+ }
595
+ finalSummary += `\nduration: ${durationLog}`;
596
+
597
+ return `\n${renderBigSection({ title: "summary", content: finalSummary })}\n`;
598
+ };
599
+
600
+ const renderStatusRepartition = (counters, { showProgression } = {}) => {
601
+ if (counters.planified === 0) {
602
+ return `nothing to run`;
603
+ }
604
+ if (counters.aborted === counters.planified) {
605
+ return `all ${ANSI.color(`aborted`, COLOR_ABORTED)}`;
606
+ }
607
+ if (counters.timedout === counters.planified) {
608
+ return `all ${ANSI.color(`timed out`, COLOR_TIMEOUT)}`;
609
+ }
610
+ if (counters.failed === counters.planified) {
611
+ return `all ${ANSI.color(`failed`, COLOR_FAILED)}`;
612
+ }
613
+ if (counters.completed === counters.planified) {
614
+ return `all ${ANSI.color(`completed`, COLOR_COMPLETED)}`;
615
+ }
616
+ if (counters.cancelled === counters.planified) {
617
+ return `all ${ANSI.color(`cancelled`, COLOR_CANCELLED)}`;
618
+ }
619
+ const parts = [];
620
+ if (counters.timedout) {
621
+ parts.push(
622
+ `${counters.timedout} ${ANSI.color(`timed out`, COLOR_TIMEOUT)}`,
623
+ );
624
+ }
625
+ if (counters.failed) {
626
+ parts.push(`${counters.failed} ${ANSI.color(`failed`, COLOR_FAILED)}`);
627
+ }
628
+ if (counters.completed) {
629
+ parts.push(
630
+ `${counters.completed} ${ANSI.color(`completed`, COLOR_COMPLETED)}`,
631
+ );
632
+ }
633
+ if (counters.aborted) {
634
+ parts.push(`${counters.aborted} ${ANSI.color(`aborted`, COLOR_ABORTED)}`);
635
+ }
636
+ if (counters.cancelled) {
637
+ parts.push(
638
+ `${counters.cancelled} ${ANSI.color(`cancelled`, COLOR_CANCELLED)}`,
639
+ );
640
+ }
641
+ if (showProgression) {
642
+ if (counters.executing) {
643
+ parts.push(`${counters.executing} executing`);
644
+ }
645
+ if (counters.waiting) {
646
+ parts.push(`${counters.waiting} waiting`);
647
+ }
648
+ }
649
+ return `${parts.join(", ")}`;
650
+ };
651
+
652
+ const renderBigSection = (params) => {
653
+ return renderSection({
654
+ width: 45,
655
+ ...params,
656
+ });
657
+ };
658
+
659
+ const renderSection = ({
660
+ title,
661
+ content,
662
+ dashColor = ANSI.GREY,
663
+ width = 38,
664
+ }) => {
665
+ let section = "";
666
+
667
+ const titleWidth = stripAnsi(title).length;
668
+ const minWidthRequired = `--- … ---`.length;
669
+ const needsTruncate = titleWidth + minWidthRequired >= width;
670
+ if (needsTruncate) {
671
+ const titleTruncated = title.slice(0, width - minWidthRequired);
672
+ const leftDashes = ANSI.color("---", dashColor);
673
+ const rightDashes = ANSI.color("---", dashColor);
674
+ section += `${leftDashes} ${titleTruncated}… ${rightDashes}`;
675
+ } else {
676
+ const remainingWidth = width - titleWidth - 2; // 2 for spaces around the title
677
+ const dashLeftCount = Math.floor(remainingWidth / 2);
678
+ const dashRightCount = remainingWidth - dashLeftCount;
679
+ const leftDashes = ANSI.color("-".repeat(dashLeftCount), dashColor);
680
+ const rightDashes = ANSI.color("-".repeat(dashRightCount), dashColor);
681
+ section += `${leftDashes} ${title} ${rightDashes}`;
682
+ }
683
+ section += `\n${content}\n`;
684
+ const bottomDashes = ANSI.color(`-`.repeat(width), dashColor);
685
+ section += bottomDashes;
686
+ return section;
687
+ };