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