@jsenv/test 1.8.1 → 2.0.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.
@@ -1,3 +1,4 @@
1
+ import os from "node:os";
1
2
  import { chmod, stat, lstat, readdir, promises, unlink, openSync, closeSync, rmdir, readFile as readFile$1, writeFile as writeFile$1, writeFileSync as writeFileSync$1, mkdirSync, readFileSync, readdirSync, existsSync } from "node:fs";
2
3
  import process$1, { memoryUsage } from "node:process";
3
4
  import v8, { takeCoverage } from "node:v8";
@@ -6,7 +7,6 @@ import { URL_META, filterV8Coverage } from "./js/v8_coverage.js";
6
7
  import { pathToFileURL, fileURLToPath } from "node:url";
7
8
  import crypto from "node:crypto";
8
9
  import { dirname } from "node:path";
9
- import os from "node:os";
10
10
  import tty from "node:tty";
11
11
  import stringWidth from "string-width";
12
12
  import { readGitHubWorkflowEnv, startGithubCheckRun } from "@jsenv/github-check-run";
@@ -4636,7 +4636,7 @@ const ensureGlobalGc = () => {
4636
4636
  * @param {Object} testPlanParameters.testPlan Object associating files with runtimes where they will be executed
4637
4637
  * @param {boolean} [testPlanParameters.logShortForCompletedExecutions=false] Abbreviate completed execution information to shorten terminal output
4638
4638
  * @param {boolean} [testPlanParameters.logMergeForCompletedExecutions=false] Merge completed execution logs to shorten terminal output
4639
- * @param {number} [testPlanParameters.maxExecutionsInParallel=1] Maximum amount of execution in parallel
4639
+ * @param {boolean|number} [testPlanParameters.concurrency=false] Maximum amount of execution running at the same time
4640
4640
  * @param {number} [testPlanParameters.defaultMsAllocatedPerExecution=30000] Milliseconds after which execution is aborted and considered as failed by timeout
4641
4641
  * @param {boolean} [testPlanParameters.failFast=false] Fails immediatly when a test execution fails
4642
4642
  * @param {number} [testPlanParameters.cooldownBetweenExecutions=0] Millisecond to wait between each execution
@@ -4663,7 +4663,7 @@ const executeTestPlan = async ({
4663
4663
  webServer,
4664
4664
  testPlan,
4665
4665
  updateProcessExitCode = true,
4666
- maxExecutionsInParallel = 1,
4666
+ concurrency = false,
4667
4667
  defaultMsAllocatedPerExecution = 30_000,
4668
4668
  failFast = false,
4669
4669
  // keepRunning: false to ensure runtime is stopped once executed
@@ -4930,6 +4930,10 @@ To fix this warning:
4930
4930
  }
4931
4931
  }
4932
4932
  }
4933
+
4934
+ if (typeof concurrency !== "boolean" && typeof concurrency !== "number") {
4935
+ throw new TypeError(`concurrency must be a boolean or a number`);
4936
+ }
4933
4937
  }
4934
4938
 
4935
4939
  logger.debug(
@@ -4968,6 +4972,25 @@ To fix this warning:
4968
4972
  }
4969
4973
  }
4970
4974
  }
4975
+ if (cooldownBetweenExecutions) {
4976
+ if (cooldownBetweenExecutions < 0) {
4977
+ cooldownBetweenExecutions = 0;
4978
+ }
4979
+ if (cooldownBetweenExecutions === Infinity) {
4980
+ cooldownBetweenExecutions = 0;
4981
+ }
4982
+ }
4983
+ if (concurrency === true) {
4984
+ const availableCpus = os.cpus().length;
4985
+ concurrency =
4986
+ availableCpus > 2
4987
+ ? // One CPU is used to run this code so we do availableCpus - 1
4988
+ availableCpus - 1
4989
+ : 1;
4990
+ }
4991
+ if (concurrency === false) {
4992
+ concurrency = 1;
4993
+ }
4971
4994
  }
4972
4995
 
4973
4996
  testPlan = {
@@ -5107,7 +5130,7 @@ To fix this warning:
5107
5130
  const executionLogsEnabled = logger.levels.info;
5108
5131
  const executionSpinner =
5109
5132
  logRefresh &&
5110
- maxExecutionsInParallel === 1 &&
5133
+ concurrency === 1 &&
5111
5134
  !debugLogsEnabled &&
5112
5135
  executionLogsEnabled &&
5113
5136
  process.stdout.isTTY &&
@@ -5130,9 +5153,9 @@ To fix this warning:
5130
5153
 
5131
5154
  const callWhenPreviousExecutionAreDone = createCallOrderer();
5132
5155
 
5133
- await executeInParallel({
5156
+ await executeConcurrently({
5134
5157
  multipleExecutionsOperation,
5135
- maxExecutionsInParallel,
5158
+ concurrency,
5136
5159
  cooldownBetweenExecutions,
5137
5160
  executionSteps,
5138
5161
  start: async (paramsFromStep) => {
@@ -5405,57 +5428,50 @@ const canOverwriteLogGetter = ({
5405
5428
  return true;
5406
5429
  };
5407
5430
 
5408
- const executeInParallel = async ({
5431
+ const executeConcurrently = async ({
5409
5432
  multipleExecutionsOperation,
5410
- maxExecutionsInParallel,
5433
+ concurrency,
5411
5434
  cooldownBetweenExecutions,
5412
5435
  executionSteps,
5413
5436
  start,
5414
5437
  }) => {
5415
- const executionResults = [];
5416
5438
  let progressionIndex = 0;
5417
- let remainingExecutionCount = executionSteps.length;
5439
+ let remainingExecutionToStart = executionSteps.length;
5418
5440
 
5419
- const nextChunk = async () => {
5420
- if (multipleExecutionsOperation.signal.aborted) {
5441
+ const startNext = async () => {
5442
+ if (remainingExecutionToStart === 0) {
5421
5443
  return;
5422
5444
  }
5423
- const outputPromiseArray = [];
5424
- let previousExecPromise = Promise.resolve();
5425
- while (
5426
- remainingExecutionCount > 0 &&
5427
- outputPromiseArray.length < maxExecutionsInParallel
5428
- ) {
5429
- remainingExecutionCount--;
5430
- const outputPromise = executeOne(progressionIndex, previousExecPromise);
5431
- previousExecPromise = outputPromise;
5432
- progressionIndex++;
5433
- outputPromiseArray.push(outputPromise);
5434
- }
5435
- if (outputPromiseArray.length) {
5436
- await Promise.all(outputPromiseArray);
5437
- if (remainingExecutionCount > 0) {
5438
- await nextChunk();
5439
- }
5440
- }
5441
- };
5442
-
5443
- const executeOne = async (index, previousExecPromise) => {
5444
- const input = executionSteps[index];
5445
- const output = await start(input, previousExecPromise);
5446
- if (!multipleExecutionsOperation.signal.aborted) {
5447
- executionResults[index] = output;
5445
+ remainingExecutionToStart--;
5446
+ const input = executionSteps[progressionIndex];
5447
+ progressionIndex++;
5448
+ await start(input);
5449
+ if (multipleExecutionsOperation.signal.aborted) {
5450
+ return;
5448
5451
  }
5449
5452
  if (cooldownBetweenExecutions) {
5450
- await new Promise((resolve) =>
5451
- setTimeout(resolve, cooldownBetweenExecutions),
5452
- );
5453
+ await new Promise((resolve) => {
5454
+ const timeoutId = setTimeout(() => {
5455
+ removeClearTimeoutOnAbort();
5456
+ resolve();
5457
+ }, cooldownBetweenExecutions);
5458
+ const removeClearTimeoutOnAbort =
5459
+ multipleExecutionsOperation.signal.addAbortCallback(() => {
5460
+ clearTimeout(timeoutId);
5461
+ });
5462
+ });
5463
+ if (multipleExecutionsOperation.signal.aborted) {
5464
+ return;
5465
+ }
5453
5466
  }
5467
+ await startNext();
5454
5468
  };
5455
5469
 
5456
- await nextChunk();
5457
-
5458
- return executionResults;
5470
+ const promises = [];
5471
+ while (concurrency--) {
5472
+ promises.push(startNext());
5473
+ }
5474
+ await Promise.all(promises);
5459
5475
  };
5460
5476
 
5461
5477
  const memoize = (compute) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/test",
3
- "version": "1.8.1",
3
+ "version": "2.0.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,3 +1,4 @@
1
+ import os from "node:os";
1
2
  import { existsSync } from "node:fs";
2
3
  import { memoryUsage } from "node:process";
3
4
  import { takeCoverage } from "node:v8";
@@ -48,7 +49,7 @@ import { ensureGlobalGc } from "./gc.js";
48
49
  * @param {Object} testPlanParameters.testPlan Object associating files with runtimes where they will be executed
49
50
  * @param {boolean} [testPlanParameters.logShortForCompletedExecutions=false] Abbreviate completed execution information to shorten terminal output
50
51
  * @param {boolean} [testPlanParameters.logMergeForCompletedExecutions=false] Merge completed execution logs to shorten terminal output
51
- * @param {number} [testPlanParameters.maxExecutionsInParallel=1] Maximum amount of execution in parallel
52
+ * @param {boolean|number} [testPlanParameters.concurrency=false] Maximum amount of execution running at the same time
52
53
  * @param {number} [testPlanParameters.defaultMsAllocatedPerExecution=30000] Milliseconds after which execution is aborted and considered as failed by timeout
53
54
  * @param {boolean} [testPlanParameters.failFast=false] Fails immediatly when a test execution fails
54
55
  * @param {number} [testPlanParameters.cooldownBetweenExecutions=0] Millisecond to wait between each execution
@@ -75,7 +76,7 @@ export const executeTestPlan = async ({
75
76
  webServer,
76
77
  testPlan,
77
78
  updateProcessExitCode = true,
78
- maxExecutionsInParallel = 1,
79
+ concurrency = false,
79
80
  defaultMsAllocatedPerExecution = 30_000,
80
81
  failFast = false,
81
82
  // keepRunning: false to ensure runtime is stopped once executed
@@ -342,6 +343,10 @@ To fix this warning:
342
343
  }
343
344
  }
344
345
  }
346
+
347
+ if (typeof concurrency !== "boolean" && typeof concurrency !== "number") {
348
+ throw new TypeError(`concurrency must be a boolean or a number`);
349
+ }
345
350
  }
346
351
 
347
352
  logger.debug(
@@ -380,6 +385,25 @@ To fix this warning:
380
385
  }
381
386
  }
382
387
  }
388
+ if (cooldownBetweenExecutions) {
389
+ if (cooldownBetweenExecutions < 0) {
390
+ cooldownBetweenExecutions = 0;
391
+ }
392
+ if (cooldownBetweenExecutions === Infinity) {
393
+ cooldownBetweenExecutions = 0;
394
+ }
395
+ }
396
+ if (concurrency === true) {
397
+ const availableCpus = os.cpus().length;
398
+ concurrency =
399
+ availableCpus > 2
400
+ ? // One CPU is used to run this code so we do availableCpus - 1
401
+ availableCpus - 1
402
+ : 1;
403
+ }
404
+ if (concurrency === false) {
405
+ concurrency = 1;
406
+ }
383
407
  }
384
408
 
385
409
  testPlan = {
@@ -519,7 +543,7 @@ To fix this warning:
519
543
  const executionLogsEnabled = logger.levels.info;
520
544
  const executionSpinner =
521
545
  logRefresh &&
522
- maxExecutionsInParallel === 1 &&
546
+ concurrency === 1 &&
523
547
  !debugLogsEnabled &&
524
548
  executionLogsEnabled &&
525
549
  process.stdout.isTTY &&
@@ -542,9 +566,9 @@ To fix this warning:
542
566
 
543
567
  const callWhenPreviousExecutionAreDone = createCallOrderer();
544
568
 
545
- await executeInParallel({
569
+ await executeConcurrently({
546
570
  multipleExecutionsOperation,
547
- maxExecutionsInParallel,
571
+ concurrency,
548
572
  cooldownBetweenExecutions,
549
573
  executionSteps,
550
574
  start: async (paramsFromStep) => {
@@ -817,55 +841,48 @@ const canOverwriteLogGetter = ({
817
841
  return true;
818
842
  };
819
843
 
820
- const executeInParallel = async ({
844
+ const executeConcurrently = async ({
821
845
  multipleExecutionsOperation,
822
- maxExecutionsInParallel,
846
+ concurrency,
823
847
  cooldownBetweenExecutions,
824
848
  executionSteps,
825
849
  start,
826
850
  }) => {
827
- const executionResults = [];
828
851
  let progressionIndex = 0;
829
- let remainingExecutionCount = executionSteps.length;
852
+ let remainingExecutionToStart = executionSteps.length;
830
853
 
831
- const nextChunk = async () => {
832
- if (multipleExecutionsOperation.signal.aborted) {
854
+ const startNext = async () => {
855
+ if (remainingExecutionToStart === 0) {
833
856
  return;
834
857
  }
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;
858
+ remainingExecutionToStart--;
859
+ const input = executionSteps[progressionIndex];
860
+ progressionIndex++;
861
+ await start(input);
862
+ if (multipleExecutionsOperation.signal.aborted) {
863
+ return;
860
864
  }
861
865
  if (cooldownBetweenExecutions) {
862
- await new Promise((resolve) =>
863
- setTimeout(resolve, cooldownBetweenExecutions),
864
- );
866
+ await new Promise((resolve) => {
867
+ const timeoutId = setTimeout(() => {
868
+ removeClearTimeoutOnAbort();
869
+ resolve();
870
+ }, cooldownBetweenExecutions);
871
+ const removeClearTimeoutOnAbort =
872
+ multipleExecutionsOperation.signal.addAbortCallback(() => {
873
+ clearTimeout(timeoutId);
874
+ });
875
+ });
876
+ if (multipleExecutionsOperation.signal.aborted) {
877
+ return;
878
+ }
865
879
  }
880
+ await startNext();
866
881
  };
867
882
 
868
- await nextChunk();
869
-
870
- return executionResults;
883
+ const promises = [];
884
+ while (concurrency--) {
885
+ promises.push(startNext());
886
+ }
887
+ await Promise.all(promises);
871
888
  };