@jsenv/test 1.7.8 → 1.8.0
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.
- package/dist/jsenv_test.js +1913 -1991
- package/package.json +3 -3
- package/src/execution/execute_test_plan.js +403 -70
- package/src/execution/logs_file_execution.js +2 -2
- package/src/execution/execute_steps.js +0 -409
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/test",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -34,10 +34,10 @@
|
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@jsenv/abort": "4.2.4",
|
|
37
|
-
"@jsenv/ast": "6.0.
|
|
37
|
+
"@jsenv/ast": "6.0.1",
|
|
38
38
|
"@jsenv/filesystem": "4.3.2",
|
|
39
39
|
"@jsenv/log": "3.4.2",
|
|
40
|
-
"@jsenv/plugin-supervisor": "1.3.
|
|
40
|
+
"@jsenv/plugin-supervisor": "1.3.9",
|
|
41
41
|
"@jsenv/urls": "2.2.1",
|
|
42
42
|
"@jsenv/url-meta": "8.1.0",
|
|
43
43
|
"@jsenv/utils": "2.0.1",
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
+
import { memoryUsage } from "node:process";
|
|
3
|
+
import { takeCoverage } from "node:v8";
|
|
2
4
|
import stripAnsi from "strip-ansi";
|
|
3
5
|
import { Abort, raceProcessTeardownEvents } from "@jsenv/abort";
|
|
4
6
|
import { URL_META } from "@jsenv/url-meta";
|
|
@@ -7,22 +9,35 @@ import {
|
|
|
7
9
|
ensureEmptyDirectory,
|
|
8
10
|
assertAndNormalizeDirectoryUrl,
|
|
9
11
|
assertAndNormalizeFileUrl,
|
|
12
|
+
writeFileSync,
|
|
10
13
|
} from "@jsenv/filesystem";
|
|
11
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
createLogger,
|
|
16
|
+
createDetailedMessage,
|
|
17
|
+
UNICODE,
|
|
18
|
+
createLog,
|
|
19
|
+
startSpinner,
|
|
20
|
+
} from "@jsenv/log";
|
|
12
21
|
import {
|
|
13
22
|
startGithubCheckRun,
|
|
14
23
|
readGitHubWorkflowEnv,
|
|
15
24
|
} from "@jsenv/github-check-run";
|
|
16
25
|
|
|
17
26
|
import { createTeardown } from "../helpers/teardown.js";
|
|
27
|
+
import { reportToCoverage } from "../coverage/report_to_coverage.js";
|
|
18
28
|
import { generateCoverageJsonFile } from "../coverage/coverage_reporter_json_file.js";
|
|
19
29
|
import { generateCoverageHtmlDirectory } from "../coverage/coverage_reporter_html_directory.js";
|
|
20
30
|
import { generateCoverageTextLog } from "../coverage/coverage_reporter_text_log.js";
|
|
21
31
|
import { assertAndNormalizeWebServer } from "./web_server_param.js";
|
|
22
32
|
import { executionStepsFromTestPlan } from "./execution_steps.js";
|
|
23
|
-
import {
|
|
24
|
-
|
|
33
|
+
import {
|
|
34
|
+
createExecutionLog,
|
|
35
|
+
formatSummaryLog,
|
|
36
|
+
formatSummary,
|
|
37
|
+
} from "./logs_file_execution.js";
|
|
25
38
|
import { githubAnnotationFromError } from "./github_annotation_from_error.js";
|
|
39
|
+
import { run } from "./run.js";
|
|
40
|
+
import { ensureGlobalGc } from "./gc.js";
|
|
26
41
|
|
|
27
42
|
/**
|
|
28
43
|
* Execute a list of files and log how it goes.
|
|
@@ -74,7 +89,7 @@ export const executeTestPlan = async ({
|
|
|
74
89
|
githubCheckEnabled = Boolean(process.env.GITHUB_WORKFLOW),
|
|
75
90
|
githubCheckLogLevel,
|
|
76
91
|
githubCheckName = "Jsenv tests",
|
|
77
|
-
githubCheckTitle,
|
|
92
|
+
githubCheckTitle = "Tests executions",
|
|
78
93
|
githubCheckToken,
|
|
79
94
|
githubCheckRepositoryOwner,
|
|
80
95
|
githubCheckRepositoryName,
|
|
@@ -119,6 +134,10 @@ export const executeTestPlan = async ({
|
|
|
119
134
|
coverageReportJsonFileUrl,
|
|
120
135
|
coverageReportHtml = !process.env.CI,
|
|
121
136
|
coverageReportHtmlDirectoryUrl,
|
|
137
|
+
|
|
138
|
+
beforeExecutionCallback = () => {},
|
|
139
|
+
afterExecutionCallback = () => {},
|
|
140
|
+
afterAllExecutionCallback = () => {},
|
|
122
141
|
...rest
|
|
123
142
|
}) => {
|
|
124
143
|
const teardown = createTeardown();
|
|
@@ -362,15 +381,12 @@ To fix this warning:
|
|
|
362
381
|
"**/.jsenv/": null,
|
|
363
382
|
};
|
|
364
383
|
logger.debug(`Generate executions`);
|
|
365
|
-
|
|
384
|
+
let executionSteps = await executionStepsFromTestPlan({
|
|
366
385
|
signal,
|
|
367
386
|
testPlan,
|
|
368
387
|
rootDirectoryUrl,
|
|
369
388
|
});
|
|
370
389
|
logger.debug(`${executionSteps.length} executions planned`);
|
|
371
|
-
let beforeExecutionCallback;
|
|
372
|
-
let afterExecutionCallback;
|
|
373
|
-
let afterAllExecutionCallback = () => {};
|
|
374
390
|
if (githubCheckEnabled) {
|
|
375
391
|
const githubCheckRun = await startGithubCheckRun({
|
|
376
392
|
logLevel: githubCheckLogLevel,
|
|
@@ -379,11 +395,13 @@ To fix this warning:
|
|
|
379
395
|
repositoryName: githubCheckRepositoryName,
|
|
380
396
|
commitSha: githubCheckCommitSha,
|
|
381
397
|
checkName: githubCheckName,
|
|
382
|
-
checkTitle:
|
|
398
|
+
checkTitle: githubCheckTitle,
|
|
383
399
|
checkSummary: `${executionSteps.length} files will be executed`,
|
|
384
400
|
});
|
|
385
401
|
const annotations = [];
|
|
402
|
+
const afterExecutionCallbackPrevious = afterExecutionCallback;
|
|
386
403
|
afterExecutionCallback = (afterExecutionInfo) => {
|
|
404
|
+
afterExecutionCallbackPrevious(afterExecutionInfo);
|
|
387
405
|
const { executionResult } = afterExecutionInfo;
|
|
388
406
|
const { errors = [] } = executionResult;
|
|
389
407
|
for (const error of errors) {
|
|
@@ -394,78 +412,328 @@ To fix this warning:
|
|
|
394
412
|
annotations.push(annotation);
|
|
395
413
|
}
|
|
396
414
|
};
|
|
397
|
-
|
|
415
|
+
const afterAllExecutionCallbackPrevious = afterAllExecutionCallback;
|
|
416
|
+
afterAllExecutionCallback = async (returnValue) => {
|
|
417
|
+
afterAllExecutionCallbackPrevious(returnValue);
|
|
418
|
+
const { summary } = returnValue;
|
|
398
419
|
const title = "Jsenv test results";
|
|
399
|
-
const
|
|
400
|
-
if (
|
|
401
|
-
testPlanSummary.counters.total !== testPlanSummary.counters.completed
|
|
402
|
-
) {
|
|
420
|
+
const summaryText = stripAnsi(formatSummary(summary));
|
|
421
|
+
if (summary.counters.total !== summary.counters.completed) {
|
|
403
422
|
await githubCheckRun.fail({
|
|
404
423
|
title,
|
|
405
|
-
summary,
|
|
424
|
+
summary: summaryText,
|
|
406
425
|
annotations,
|
|
407
426
|
});
|
|
408
427
|
return;
|
|
409
428
|
}
|
|
410
429
|
await githubCheckRun.pass({
|
|
411
430
|
title,
|
|
412
|
-
summary,
|
|
431
|
+
summary: summaryText,
|
|
413
432
|
annotations,
|
|
414
433
|
});
|
|
415
434
|
};
|
|
416
435
|
}
|
|
417
436
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
gcBetweenExecutions
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
437
|
+
executionSteps = executionSteps.filter(
|
|
438
|
+
(executionStep) => !executionStep.runtime?.disabled,
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
const returnValue = {
|
|
442
|
+
aborted: false,
|
|
443
|
+
summary: null,
|
|
444
|
+
report: null,
|
|
445
|
+
coverage: null,
|
|
446
|
+
};
|
|
447
|
+
const report = {};
|
|
448
|
+
const callbacks = [];
|
|
449
|
+
|
|
450
|
+
const multipleExecutionsOperation = Abort.startOperation();
|
|
451
|
+
multipleExecutionsOperation.addAbortSignal(signal);
|
|
452
|
+
const failFastAbortController = new AbortController();
|
|
453
|
+
if (failFast) {
|
|
454
|
+
multipleExecutionsOperation.addAbortSignal(failFastAbortController.signal);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
try {
|
|
458
|
+
if (gcBetweenExecutions) {
|
|
459
|
+
ensureGlobalGc();
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (coverageEnabled) {
|
|
463
|
+
// when runned multiple times, we don't want to keep previous files in this directory
|
|
464
|
+
await ensureEmptyDirectory(coverageTempDirectoryUrl);
|
|
465
|
+
callbacks.push(async () => {
|
|
466
|
+
if (multipleExecutionsOperation.signal.aborted) {
|
|
467
|
+
// don't try to do the coverage stuff
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
try {
|
|
471
|
+
if (coverageMethodForNodeJs === "NODE_V8_COVERAGE") {
|
|
472
|
+
takeCoverage();
|
|
473
|
+
// conceptually we don't need coverage anymore so it would be
|
|
474
|
+
// good to call v8.stopCoverage()
|
|
475
|
+
// but it logs a strange message about "result is not an object"
|
|
476
|
+
}
|
|
477
|
+
const coverage = await reportToCoverage(report, {
|
|
478
|
+
signal: multipleExecutionsOperation.signal,
|
|
479
|
+
logger,
|
|
480
|
+
rootDirectoryUrl,
|
|
481
|
+
coverageConfig,
|
|
482
|
+
coverageIncludeMissing,
|
|
483
|
+
coverageMethodForNodeJs,
|
|
484
|
+
coverageV8ConflictWarning,
|
|
485
|
+
});
|
|
486
|
+
returnValue.coverage = coverage;
|
|
487
|
+
} catch (e) {
|
|
488
|
+
if (Abort.isAbortError(e)) {
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
throw e;
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const runtimeParams = {
|
|
497
|
+
rootDirectoryUrl,
|
|
498
|
+
webServer,
|
|
499
|
+
|
|
500
|
+
coverageEnabled,
|
|
501
|
+
coverageConfig,
|
|
502
|
+
coverageMethodForBrowsers,
|
|
503
|
+
coverageMethodForNodeJs,
|
|
504
|
+
isTestPlan: true,
|
|
505
|
+
teardown,
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
if (logMergeForCompletedExecutions && !process.stdout.isTTY) {
|
|
509
|
+
logMergeForCompletedExecutions = false;
|
|
510
|
+
logger.debug(
|
|
511
|
+
`Force logMergeForCompletedExecutions to false because process.stdout.isTTY is false`,
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
const debugLogsEnabled = logger.levels.debug;
|
|
515
|
+
const executionLogsEnabled = logger.levels.info;
|
|
516
|
+
const executionSpinner =
|
|
517
|
+
logRefresh &&
|
|
518
|
+
!debugLogsEnabled &&
|
|
519
|
+
executionLogsEnabled &&
|
|
520
|
+
process.stdout.isTTY &&
|
|
521
|
+
// if there is an error during execution npm will mess up the output
|
|
522
|
+
// (happens when npm runs several command in a workspace)
|
|
523
|
+
// so we enable spinner only when !process.exitCode (no error so far)
|
|
524
|
+
process.exitCode !== 1;
|
|
525
|
+
|
|
526
|
+
const startMs = Date.now();
|
|
527
|
+
let rawOutput = "";
|
|
528
|
+
let executionLog = createLog({ newLine: "" });
|
|
529
|
+
const counters = {
|
|
530
|
+
total: executionSteps.length,
|
|
531
|
+
aborted: 0,
|
|
532
|
+
timedout: 0,
|
|
533
|
+
failed: 0,
|
|
534
|
+
completed: 0,
|
|
535
|
+
done: 0,
|
|
536
|
+
};
|
|
537
|
+
await executeInParallel({
|
|
538
|
+
multipleExecutionsOperation,
|
|
539
|
+
maxExecutionsInParallel,
|
|
540
|
+
cooldownBetweenExecutions,
|
|
541
|
+
executionSteps,
|
|
542
|
+
start: async (paramsFromStep) => {
|
|
543
|
+
const executionIndex = executionSteps.indexOf(paramsFromStep);
|
|
544
|
+
const { executionName, fileRelativeUrl, runtime } = paramsFromStep;
|
|
545
|
+
const runtimeType = runtime.type;
|
|
546
|
+
const runtimeName = runtime.name;
|
|
547
|
+
const runtimeVersion = runtime.version;
|
|
548
|
+
const executionParams = {
|
|
549
|
+
measurePerformance: false,
|
|
550
|
+
collectPerformance: false,
|
|
551
|
+
collectConsole: true,
|
|
552
|
+
allocatedMs: defaultMsAllocatedPerExecution,
|
|
553
|
+
...paramsFromStep,
|
|
554
|
+
runtimeParams: {
|
|
555
|
+
fileRelativeUrl,
|
|
556
|
+
...paramsFromStep.runtimeParams,
|
|
557
|
+
},
|
|
558
|
+
};
|
|
559
|
+
const beforeExecutionInfo = {
|
|
560
|
+
fileRelativeUrl,
|
|
561
|
+
runtimeType,
|
|
562
|
+
runtimeName,
|
|
563
|
+
runtimeVersion,
|
|
564
|
+
executionIndex,
|
|
565
|
+
executionParams,
|
|
566
|
+
startMs: Date.now(),
|
|
567
|
+
executionResult: {
|
|
568
|
+
status: "executing",
|
|
569
|
+
},
|
|
570
|
+
counters,
|
|
571
|
+
timeEllapsed: Date.now() - startMs,
|
|
572
|
+
memoryHeap: memoryUsage().heapUsed,
|
|
573
|
+
};
|
|
574
|
+
if (typeof executionParams.allocatedMs === "function") {
|
|
575
|
+
executionParams.allocatedMs =
|
|
576
|
+
executionParams.allocatedMs(beforeExecutionInfo);
|
|
577
|
+
}
|
|
578
|
+
let spinner;
|
|
579
|
+
if (executionSpinner) {
|
|
580
|
+
spinner = startSpinner({
|
|
581
|
+
log: executionLog,
|
|
582
|
+
render: () => {
|
|
583
|
+
return createExecutionLog(beforeExecutionInfo, {
|
|
584
|
+
logRuntime,
|
|
585
|
+
logEachDuration,
|
|
586
|
+
logTimeUsage,
|
|
587
|
+
logMemoryHeapUsage,
|
|
588
|
+
});
|
|
589
|
+
},
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
beforeExecutionCallback(beforeExecutionInfo);
|
|
593
|
+
|
|
594
|
+
const fileUrl = `${rootDirectoryUrl}${fileRelativeUrl}`;
|
|
595
|
+
let executionResult;
|
|
596
|
+
if (existsSync(new URL(fileUrl))) {
|
|
597
|
+
executionResult = await run({
|
|
598
|
+
signal: multipleExecutionsOperation.signal,
|
|
599
|
+
logger,
|
|
600
|
+
allocatedMs: executionParams.allocatedMs,
|
|
601
|
+
keepRunning,
|
|
602
|
+
mirrorConsole: false, // file are executed in parallel, log would be a mess to read
|
|
603
|
+
collectConsole: executionParams.collectConsole,
|
|
604
|
+
coverageEnabled,
|
|
605
|
+
coverageTempDirectoryUrl,
|
|
606
|
+
runtime: executionParams.runtime,
|
|
607
|
+
runtimeParams: {
|
|
608
|
+
...runtimeParams,
|
|
609
|
+
...executionParams.runtimeParams,
|
|
610
|
+
},
|
|
611
|
+
});
|
|
612
|
+
} else {
|
|
613
|
+
executionResult = {
|
|
614
|
+
status: "failed",
|
|
615
|
+
errors: [
|
|
616
|
+
new Error(
|
|
617
|
+
`No file at ${fileRelativeUrl} for execution "${executionName}"`,
|
|
618
|
+
),
|
|
619
|
+
],
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
counters.done++;
|
|
623
|
+
const fileReport = report[fileRelativeUrl];
|
|
624
|
+
if (fileReport) {
|
|
625
|
+
fileReport[executionName] = executionResult;
|
|
626
|
+
} else {
|
|
627
|
+
report[fileRelativeUrl] = {
|
|
628
|
+
[executionName]: executionResult,
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
const afterExecutionInfo = {
|
|
633
|
+
...beforeExecutionInfo,
|
|
634
|
+
runtimeVersion: runtime.version,
|
|
635
|
+
endMs: Date.now(),
|
|
636
|
+
executionResult,
|
|
637
|
+
};
|
|
638
|
+
|
|
639
|
+
if (executionResult.status === "aborted") {
|
|
640
|
+
counters.aborted++;
|
|
641
|
+
} else if (executionResult.status === "timedout") {
|
|
642
|
+
counters.timedout++;
|
|
643
|
+
} else if (executionResult.status === "failed") {
|
|
644
|
+
counters.failed++;
|
|
645
|
+
} else if (executionResult.status === "completed") {
|
|
646
|
+
counters.completed++;
|
|
647
|
+
}
|
|
648
|
+
if (gcBetweenExecutions) {
|
|
649
|
+
global.gc();
|
|
650
|
+
}
|
|
651
|
+
if (executionLogsEnabled) {
|
|
652
|
+
const log = createExecutionLog(afterExecutionInfo, {
|
|
653
|
+
logShortForCompletedExecutions,
|
|
654
|
+
logRuntime,
|
|
655
|
+
logEachDuration,
|
|
656
|
+
...(logTimeUsage
|
|
657
|
+
? {
|
|
658
|
+
timeEllapsed: Date.now() - startMs,
|
|
659
|
+
}
|
|
660
|
+
: {}),
|
|
661
|
+
...(logMemoryHeapUsage
|
|
662
|
+
? { memoryHeap: memoryUsage().heapUsed }
|
|
663
|
+
: {}),
|
|
664
|
+
});
|
|
665
|
+
// replace spinner with this execution result
|
|
666
|
+
if (spinner) spinner.stop();
|
|
667
|
+
executionLog.write(log);
|
|
668
|
+
rawOutput += stripAnsi(log);
|
|
669
|
+
|
|
670
|
+
const canOverwriteLog = canOverwriteLogGetter({
|
|
671
|
+
logMergeForCompletedExecutions,
|
|
672
|
+
executionResult,
|
|
673
|
+
});
|
|
674
|
+
if (canOverwriteLog) {
|
|
675
|
+
// nothing to do, we reuse the current executionLog object
|
|
676
|
+
} else {
|
|
677
|
+
executionLog.destroy();
|
|
678
|
+
executionLog = createLog({ newLine: "" });
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
afterExecutionCallback(afterExecutionInfo);
|
|
682
|
+
const isLastExecutionLog = executionIndex === executionSteps.length - 1;
|
|
683
|
+
const cancelRemaining =
|
|
684
|
+
failFast &&
|
|
685
|
+
executionResult.status !== "completed" &&
|
|
686
|
+
counters.done < counters.total;
|
|
687
|
+
if (isLastExecutionLog && logger.levels.info) {
|
|
688
|
+
executionLog.write("\n");
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
if (cancelRemaining) {
|
|
692
|
+
logger.info(`"failFast" enabled -> cancel remaining executions`);
|
|
693
|
+
failFastAbortController.abort();
|
|
694
|
+
}
|
|
695
|
+
},
|
|
696
|
+
});
|
|
697
|
+
if (!keepRunning) {
|
|
698
|
+
logger.debug("trigger test plan teardown");
|
|
699
|
+
await teardown.trigger();
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
counters.cancelled = counters.total - counters.done;
|
|
703
|
+
const summary = {
|
|
704
|
+
counters,
|
|
705
|
+
// when execution is aborted, the remaining executions are "cancelled"
|
|
706
|
+
duration: Date.now() - startMs,
|
|
707
|
+
};
|
|
708
|
+
if (logSummary) {
|
|
709
|
+
const summaryLog = formatSummaryLog(summary);
|
|
710
|
+
rawOutput += stripAnsi(summaryLog);
|
|
711
|
+
logger.info(summaryLog);
|
|
712
|
+
}
|
|
713
|
+
if (summary.counters.total !== summary.counters.completed) {
|
|
714
|
+
const logFileUrl = new URL(logFileRelativeUrl, rootDirectoryUrl).href;
|
|
715
|
+
writeFileSync(logFileUrl, rawOutput);
|
|
716
|
+
logger.info(`-> ${urlToFileSystemPath(logFileUrl)}`);
|
|
717
|
+
}
|
|
718
|
+
returnValue.aborted = multipleExecutionsOperation.signal.aborted;
|
|
719
|
+
returnValue.summary = summary;
|
|
720
|
+
returnValue.report = report;
|
|
721
|
+
for (const callback of callbacks) {
|
|
722
|
+
await callback();
|
|
723
|
+
}
|
|
724
|
+
} finally {
|
|
725
|
+
await multipleExecutionsOperation.end();
|
|
726
|
+
}
|
|
460
727
|
|
|
461
728
|
const hasFailed =
|
|
462
|
-
|
|
729
|
+
returnValue.summary.counters.total !==
|
|
730
|
+
returnValue.summary.counters.completed;
|
|
463
731
|
if (updateProcessExitCode && hasFailed) {
|
|
464
732
|
process.exitCode = 1;
|
|
465
733
|
}
|
|
466
|
-
const
|
|
734
|
+
const coverage = returnValue.coverage;
|
|
467
735
|
// planCoverage can be null when execution is aborted
|
|
468
|
-
if (
|
|
736
|
+
if (coverage) {
|
|
469
737
|
const promises = [];
|
|
470
738
|
// keep this one first because it does ensureEmptyDirectory
|
|
471
739
|
// and in case coverage json file gets written in the same directory
|
|
@@ -477,7 +745,7 @@ To fix this warning:
|
|
|
477
745
|
`-> ${urlToFileSystemPath(htmlCoverageDirectoryIndexFileUrl)}`,
|
|
478
746
|
);
|
|
479
747
|
promises.push(
|
|
480
|
-
generateCoverageHtmlDirectory(
|
|
748
|
+
generateCoverageHtmlDirectory(coverage, {
|
|
481
749
|
rootDirectoryUrl,
|
|
482
750
|
coverageHtmlDirectoryRelativeUrl: urlToRelativeUrl(
|
|
483
751
|
coverageReportHtmlDirectoryUrl,
|
|
@@ -491,7 +759,7 @@ To fix this warning:
|
|
|
491
759
|
if (coverageEnabled && coverageReportJson) {
|
|
492
760
|
promises.push(
|
|
493
761
|
generateCoverageJsonFile({
|
|
494
|
-
coverage
|
|
762
|
+
coverage,
|
|
495
763
|
coverageJsonFileUrl: coverageReportJsonFileUrl,
|
|
496
764
|
logger,
|
|
497
765
|
}),
|
|
@@ -499,7 +767,7 @@ To fix this warning:
|
|
|
499
767
|
}
|
|
500
768
|
if (coverageEnabled && coverageReportTextLog) {
|
|
501
769
|
promises.push(
|
|
502
|
-
generateCoverageTextLog(
|
|
770
|
+
generateCoverageTextLog(coverage, {
|
|
503
771
|
coverageReportSkipEmpty,
|
|
504
772
|
coverageReportSkipFull,
|
|
505
773
|
}),
|
|
@@ -508,12 +776,77 @@ To fix this warning:
|
|
|
508
776
|
await Promise.all(promises);
|
|
509
777
|
}
|
|
510
778
|
|
|
511
|
-
const returnValue = {
|
|
512
|
-
testPlanAborted: result.aborted,
|
|
513
|
-
testPlanSummary: result.planSummary,
|
|
514
|
-
testPlanReport: result.planReport,
|
|
515
|
-
testPlanCoverage: planCoverage,
|
|
516
|
-
};
|
|
517
779
|
await afterAllExecutionCallback(returnValue);
|
|
518
780
|
return returnValue;
|
|
519
781
|
};
|
|
782
|
+
|
|
783
|
+
const canOverwriteLogGetter = ({
|
|
784
|
+
logMergeForCompletedExecutions,
|
|
785
|
+
executionResult,
|
|
786
|
+
}) => {
|
|
787
|
+
if (!logMergeForCompletedExecutions) {
|
|
788
|
+
return false;
|
|
789
|
+
}
|
|
790
|
+
if (executionResult.status === "aborted") {
|
|
791
|
+
return true;
|
|
792
|
+
}
|
|
793
|
+
if (executionResult.status !== "completed") {
|
|
794
|
+
return false;
|
|
795
|
+
}
|
|
796
|
+
const { consoleCalls = [] } = executionResult;
|
|
797
|
+
if (consoleCalls.length > 0) {
|
|
798
|
+
return false;
|
|
799
|
+
}
|
|
800
|
+
return true;
|
|
801
|
+
};
|
|
802
|
+
|
|
803
|
+
const executeInParallel = async ({
|
|
804
|
+
multipleExecutionsOperation,
|
|
805
|
+
maxExecutionsInParallel,
|
|
806
|
+
cooldownBetweenExecutions,
|
|
807
|
+
executionSteps,
|
|
808
|
+
start,
|
|
809
|
+
}) => {
|
|
810
|
+
const executionResults = [];
|
|
811
|
+
let progressionIndex = 0;
|
|
812
|
+
let remainingExecutionCount = executionSteps.length;
|
|
813
|
+
|
|
814
|
+
const nextChunk = async () => {
|
|
815
|
+
if (multipleExecutionsOperation.signal.aborted) {
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
const outputPromiseArray = [];
|
|
819
|
+
while (
|
|
820
|
+
remainingExecutionCount > 0 &&
|
|
821
|
+
outputPromiseArray.length < maxExecutionsInParallel
|
|
822
|
+
) {
|
|
823
|
+
remainingExecutionCount--;
|
|
824
|
+
const outputPromise = executeOne(progressionIndex);
|
|
825
|
+
progressionIndex++;
|
|
826
|
+
outputPromiseArray.push(outputPromise);
|
|
827
|
+
}
|
|
828
|
+
if (outputPromiseArray.length) {
|
|
829
|
+
await Promise.all(outputPromiseArray);
|
|
830
|
+
if (remainingExecutionCount > 0) {
|
|
831
|
+
await nextChunk();
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
};
|
|
835
|
+
|
|
836
|
+
const executeOne = async (index) => {
|
|
837
|
+
const input = executionSteps[index];
|
|
838
|
+
const output = await start(input);
|
|
839
|
+
if (!multipleExecutionsOperation.signal.aborted) {
|
|
840
|
+
executionResults[index] = output;
|
|
841
|
+
}
|
|
842
|
+
if (cooldownBetweenExecutions) {
|
|
843
|
+
await new Promise((resolve) =>
|
|
844
|
+
setTimeout(resolve, cooldownBetweenExecutions),
|
|
845
|
+
);
|
|
846
|
+
}
|
|
847
|
+
};
|
|
848
|
+
|
|
849
|
+
await nextChunk();
|
|
850
|
+
|
|
851
|
+
return executionResults;
|
|
852
|
+
};
|