@jsenv/test 1.8.1 → 2.1.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/js/v8_coverage.js
CHANGED
|
@@ -39,6 +39,7 @@ const isWindowsPathnameSpecifier = (specifier) => {
|
|
|
39
39
|
const hasScheme = (specifier) => /^[a-zA-Z]+:/.test(specifier);
|
|
40
40
|
|
|
41
41
|
const resolveAssociations = (associations, baseUrl) => {
|
|
42
|
+
if (baseUrl && typeof baseUrl.href === "string") baseUrl = baseUrl.href;
|
|
42
43
|
assertUrlLike(baseUrl, "baseUrl");
|
|
43
44
|
const associationsResolved = {};
|
|
44
45
|
Object.keys(associations).forEach((key) => {
|
|
@@ -122,6 +123,7 @@ const asFlatAssociations = (associations) => {
|
|
|
122
123
|
*/
|
|
123
124
|
const applyPatternMatching = ({ url, pattern }) => {
|
|
124
125
|
assertUrlLike(pattern, "pattern");
|
|
126
|
+
if (url && typeof url.href === "string") url = url.href;
|
|
125
127
|
assertUrlLike(url, "url");
|
|
126
128
|
const { matched, patternIndex, index, groups } = applyMatching(pattern, url);
|
|
127
129
|
const matchGroups = [];
|
|
@@ -369,6 +371,7 @@ const skipUntilMatch = ({ pattern, string, canSkipSlash }) => {
|
|
|
369
371
|
};
|
|
370
372
|
|
|
371
373
|
const applyAssociations = ({ url, associations }) => {
|
|
374
|
+
if (url && typeof url.href === "string") url = url.href;
|
|
372
375
|
assertUrlLike(url);
|
|
373
376
|
const flatAssociations = asFlatAssociations(associations);
|
|
374
377
|
return Object.keys(flatAssociations).reduce((previousValue, pattern) => {
|
|
@@ -418,6 +421,7 @@ const applyAliases = ({ url, aliases }) => {
|
|
|
418
421
|
};
|
|
419
422
|
|
|
420
423
|
const urlChildMayMatch = ({ url, associations, predicate }) => {
|
|
424
|
+
if (url && typeof url.href === "string") url = url.href;
|
|
421
425
|
assertUrlLike(url, "url");
|
|
422
426
|
// the function was meants to be used on url ending with '/'
|
|
423
427
|
if (!url.endsWith("/")) {
|
package/dist/jsenv_test.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import { readdir, chmod, stat, lstat, promises, readFile as readFile$1, writeFile as writeFile$1, writeFileSync as writeFileSync$1, mkdirSync, unlink, openSync, closeSync, rmdir, readFileSync, readdirSync, existsSync } from "node:fs";
|
|
2
3
|
import process$1, { memoryUsage } from "node:process";
|
|
3
4
|
import v8, { takeCoverage } from "node:v8";
|
|
4
5
|
import stripAnsi from "strip-ansi";
|
|
5
6
|
import { URL_META, filterV8Coverage } from "./js/v8_coverage.js";
|
|
6
7
|
import { pathToFileURL, fileURLToPath } from "node:url";
|
|
7
|
-
import crypto from "node:crypto";
|
|
8
8
|
import { dirname } from "node:path";
|
|
9
|
-
import
|
|
9
|
+
import crypto from "node:crypto";
|
|
10
10
|
import tty from "node:tty";
|
|
11
11
|
import stringWidth from "string-width";
|
|
12
12
|
import { readGitHubWorkflowEnv, startGithubCheckRun } from "@jsenv/github-check-run";
|
|
@@ -17,7 +17,7 @@ import { createServer } from "node:net";
|
|
|
17
17
|
import wrapAnsi from "wrap-ansi";
|
|
18
18
|
import { runInNewContext } from "node:vm";
|
|
19
19
|
import { injectSupervisorIntoHTML, supervisorFileUrl } from "@jsenv/plugin-supervisor";
|
|
20
|
-
import {
|
|
20
|
+
import { generateSourcemapDataUrl, SOURCEMAP } from "@jsenv/sourcemap";
|
|
21
21
|
import { findFreePort } from "@jsenv/server";
|
|
22
22
|
import { Worker } from "node:worker_threads";
|
|
23
23
|
|
|
@@ -865,15 +865,158 @@ const assertAndNormalizeFileUrl = (
|
|
|
865
865
|
return value;
|
|
866
866
|
};
|
|
867
867
|
|
|
868
|
-
const
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
868
|
+
const comparePathnames = (leftPathame, rightPathname) => {
|
|
869
|
+
const leftPartArray = leftPathame.split("/");
|
|
870
|
+
const rightPartArray = rightPathname.split("/");
|
|
871
|
+
|
|
872
|
+
const leftLength = leftPartArray.length;
|
|
873
|
+
const rightLength = rightPartArray.length;
|
|
874
|
+
|
|
875
|
+
const maxLength = Math.max(leftLength, rightLength);
|
|
876
|
+
let i = 0;
|
|
877
|
+
while (i < maxLength) {
|
|
878
|
+
const leftPartExists = i in leftPartArray;
|
|
879
|
+
const rightPartExists = i in rightPartArray;
|
|
880
|
+
|
|
881
|
+
// longer comes first
|
|
882
|
+
if (!leftPartExists) {
|
|
883
|
+
return +1;
|
|
884
|
+
}
|
|
885
|
+
if (!rightPartExists) {
|
|
886
|
+
return -1;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
const leftPartIsLast = i === leftPartArray.length - 1;
|
|
890
|
+
const rightPartIsLast = i === rightPartArray.length - 1;
|
|
891
|
+
// folder comes first
|
|
892
|
+
if (leftPartIsLast && !rightPartIsLast) {
|
|
893
|
+
return +1;
|
|
894
|
+
}
|
|
895
|
+
if (!leftPartIsLast && rightPartIsLast) {
|
|
896
|
+
return -1;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
const leftPart = leftPartArray[i];
|
|
900
|
+
const rightPart = rightPartArray[i];
|
|
901
|
+
i++;
|
|
902
|
+
// local comparison comes first
|
|
903
|
+
const comparison = leftPart.localeCompare(rightPart);
|
|
904
|
+
if (comparison !== 0) {
|
|
905
|
+
return comparison;
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
if (leftLength < rightLength) {
|
|
910
|
+
return +1;
|
|
911
|
+
}
|
|
912
|
+
if (leftLength > rightLength) {
|
|
913
|
+
return -1;
|
|
914
|
+
}
|
|
915
|
+
return 0;
|
|
916
|
+
};
|
|
917
|
+
|
|
918
|
+
const isWindows$2 = process.platform === "win32";
|
|
919
|
+
const baseUrlFallback = fileSystemPathToUrl(process.cwd());
|
|
920
|
+
|
|
921
|
+
/**
|
|
922
|
+
* Some url might be resolved or remapped to url without the windows drive letter.
|
|
923
|
+
* For instance
|
|
924
|
+
* new URL('/foo.js', 'file:///C:/dir/file.js')
|
|
925
|
+
* resolves to
|
|
926
|
+
* 'file:///foo.js'
|
|
927
|
+
*
|
|
928
|
+
* But on windows it becomes a problem because we need the drive letter otherwise
|
|
929
|
+
* url cannot be converted to a filesystem path.
|
|
930
|
+
*
|
|
931
|
+
* ensureWindowsDriveLetter ensure a resolved url still contains the drive letter.
|
|
932
|
+
*/
|
|
933
|
+
|
|
934
|
+
const ensureWindowsDriveLetter = (url, baseUrl) => {
|
|
935
|
+
try {
|
|
936
|
+
url = String(new URL(url));
|
|
937
|
+
} catch (e) {
|
|
938
|
+
throw new Error(`absolute url expected but got ${url}`);
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
if (!isWindows$2) {
|
|
942
|
+
return url;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
try {
|
|
946
|
+
baseUrl = String(new URL(baseUrl));
|
|
947
|
+
} catch (e) {
|
|
948
|
+
throw new Error(
|
|
949
|
+
`absolute baseUrl expected but got ${baseUrl} to ensure windows drive letter on ${url}`,
|
|
950
|
+
);
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
if (!url.startsWith("file://")) {
|
|
954
|
+
return url;
|
|
955
|
+
}
|
|
956
|
+
const afterProtocol = url.slice("file://".length);
|
|
957
|
+
// we still have the windows drive letter
|
|
958
|
+
if (extractDriveLetter(afterProtocol)) {
|
|
959
|
+
return url;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
// drive letter was lost, restore it
|
|
963
|
+
const baseUrlOrFallback = baseUrl.startsWith("file://")
|
|
964
|
+
? baseUrl
|
|
965
|
+
: baseUrlFallback;
|
|
966
|
+
const driveLetter = extractDriveLetter(
|
|
967
|
+
baseUrlOrFallback.slice("file://".length),
|
|
968
|
+
);
|
|
969
|
+
if (!driveLetter) {
|
|
970
|
+
throw new Error(
|
|
971
|
+
`drive letter expected on baseUrl but got ${baseUrl} to ensure windows drive letter on ${url}`,
|
|
972
|
+
);
|
|
973
|
+
}
|
|
974
|
+
return `file:///${driveLetter}:${afterProtocol}`;
|
|
975
|
+
};
|
|
976
|
+
|
|
977
|
+
const extractDriveLetter = (resource) => {
|
|
978
|
+
// we still have the windows drive letter
|
|
979
|
+
if (/[a-zA-Z]/.test(resource[1]) && resource[2] === ":") {
|
|
980
|
+
return resource[1];
|
|
981
|
+
}
|
|
982
|
+
return null;
|
|
983
|
+
};
|
|
984
|
+
|
|
985
|
+
const readDirectory = async (url, { emfileMaxWait = 1000 } = {}) => {
|
|
986
|
+
const directoryUrl = assertAndNormalizeDirectoryUrl(url);
|
|
987
|
+
const directoryUrlObject = new URL(directoryUrl);
|
|
988
|
+
const startMs = Date.now();
|
|
989
|
+
let attemptCount = 0;
|
|
990
|
+
|
|
991
|
+
const attempt = async () => {
|
|
992
|
+
try {
|
|
993
|
+
const names = await new Promise((resolve, reject) => {
|
|
994
|
+
readdir(directoryUrlObject, (error, names) => {
|
|
995
|
+
if (error) {
|
|
996
|
+
reject(error);
|
|
997
|
+
} else {
|
|
998
|
+
resolve(names);
|
|
999
|
+
}
|
|
1000
|
+
});
|
|
1001
|
+
});
|
|
1002
|
+
return names.map(encodeURIComponent);
|
|
1003
|
+
} catch (e) {
|
|
1004
|
+
// https://nodejs.org/dist/latest-v13.x/docs/api/errors.html#errors_common_system_errors
|
|
1005
|
+
if (e.code === "EMFILE" || e.code === "ENFILE") {
|
|
1006
|
+
attemptCount++;
|
|
1007
|
+
const nowMs = Date.now();
|
|
1008
|
+
const timeSpentWaiting = nowMs - startMs;
|
|
1009
|
+
if (timeSpentWaiting > emfileMaxWait) {
|
|
1010
|
+
throw e;
|
|
1011
|
+
}
|
|
1012
|
+
await new Promise((resolve) => setTimeout(resolve), attemptCount);
|
|
1013
|
+
return await attempt();
|
|
1014
|
+
}
|
|
1015
|
+
throw e;
|
|
1016
|
+
}
|
|
1017
|
+
};
|
|
1018
|
+
|
|
1019
|
+
return attempt();
|
|
877
1020
|
};
|
|
878
1021
|
|
|
879
1022
|
// https://github.com/coderaiser/cloudcmd/issues/63#issuecomment-195478143
|
|
@@ -996,7 +1139,7 @@ const getPermissionOrComputeDefault = (action, subject, permissions) => {
|
|
|
996
1139
|
*/
|
|
997
1140
|
|
|
998
1141
|
|
|
999
|
-
const isWindows$
|
|
1142
|
+
const isWindows$1 = process.platform === "win32";
|
|
1000
1143
|
|
|
1001
1144
|
const readEntryStat = async (
|
|
1002
1145
|
source,
|
|
@@ -1016,7 +1159,7 @@ const readEntryStat = async (
|
|
|
1016
1159
|
return readStat(sourcePath, {
|
|
1017
1160
|
followLink,
|
|
1018
1161
|
...handleNotFoundOption,
|
|
1019
|
-
...(isWindows$
|
|
1162
|
+
...(isWindows$1
|
|
1020
1163
|
? {
|
|
1021
1164
|
// Windows can EPERM on stat
|
|
1022
1165
|
handlePermissionDeniedError: async (error) => {
|
|
@@ -1081,93 +1224,6 @@ const readStat = (
|
|
|
1081
1224
|
});
|
|
1082
1225
|
};
|
|
1083
1226
|
|
|
1084
|
-
const readDirectory = async (url, { emfileMaxWait = 1000 } = {}) => {
|
|
1085
|
-
const directoryUrl = assertAndNormalizeDirectoryUrl(url);
|
|
1086
|
-
const directoryUrlObject = new URL(directoryUrl);
|
|
1087
|
-
const startMs = Date.now();
|
|
1088
|
-
let attemptCount = 0;
|
|
1089
|
-
|
|
1090
|
-
const attempt = async () => {
|
|
1091
|
-
try {
|
|
1092
|
-
const names = await new Promise((resolve, reject) => {
|
|
1093
|
-
readdir(directoryUrlObject, (error, names) => {
|
|
1094
|
-
if (error) {
|
|
1095
|
-
reject(error);
|
|
1096
|
-
} else {
|
|
1097
|
-
resolve(names);
|
|
1098
|
-
}
|
|
1099
|
-
});
|
|
1100
|
-
});
|
|
1101
|
-
return names.map(encodeURIComponent);
|
|
1102
|
-
} catch (e) {
|
|
1103
|
-
// https://nodejs.org/dist/latest-v13.x/docs/api/errors.html#errors_common_system_errors
|
|
1104
|
-
if (e.code === "EMFILE" || e.code === "ENFILE") {
|
|
1105
|
-
attemptCount++;
|
|
1106
|
-
const nowMs = Date.now();
|
|
1107
|
-
const timeSpentWaiting = nowMs - startMs;
|
|
1108
|
-
if (timeSpentWaiting > emfileMaxWait) {
|
|
1109
|
-
throw e;
|
|
1110
|
-
}
|
|
1111
|
-
await new Promise((resolve) => setTimeout(resolve), attemptCount);
|
|
1112
|
-
return await attempt();
|
|
1113
|
-
}
|
|
1114
|
-
throw e;
|
|
1115
|
-
}
|
|
1116
|
-
};
|
|
1117
|
-
|
|
1118
|
-
return attempt();
|
|
1119
|
-
};
|
|
1120
|
-
|
|
1121
|
-
const comparePathnames = (leftPathame, rightPathname) => {
|
|
1122
|
-
const leftPartArray = leftPathame.split("/");
|
|
1123
|
-
const rightPartArray = rightPathname.split("/");
|
|
1124
|
-
|
|
1125
|
-
const leftLength = leftPartArray.length;
|
|
1126
|
-
const rightLength = rightPartArray.length;
|
|
1127
|
-
|
|
1128
|
-
const maxLength = Math.max(leftLength, rightLength);
|
|
1129
|
-
let i = 0;
|
|
1130
|
-
while (i < maxLength) {
|
|
1131
|
-
const leftPartExists = i in leftPartArray;
|
|
1132
|
-
const rightPartExists = i in rightPartArray;
|
|
1133
|
-
|
|
1134
|
-
// longer comes first
|
|
1135
|
-
if (!leftPartExists) {
|
|
1136
|
-
return +1;
|
|
1137
|
-
}
|
|
1138
|
-
if (!rightPartExists) {
|
|
1139
|
-
return -1;
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
|
-
const leftPartIsLast = i === leftPartArray.length - 1;
|
|
1143
|
-
const rightPartIsLast = i === rightPartArray.length - 1;
|
|
1144
|
-
// folder comes first
|
|
1145
|
-
if (leftPartIsLast && !rightPartIsLast) {
|
|
1146
|
-
return +1;
|
|
1147
|
-
}
|
|
1148
|
-
if (!leftPartIsLast && rightPartIsLast) {
|
|
1149
|
-
return -1;
|
|
1150
|
-
}
|
|
1151
|
-
|
|
1152
|
-
const leftPart = leftPartArray[i];
|
|
1153
|
-
const rightPart = rightPartArray[i];
|
|
1154
|
-
i++;
|
|
1155
|
-
// local comparison comes first
|
|
1156
|
-
const comparison = leftPart.localeCompare(rightPart);
|
|
1157
|
-
if (comparison !== 0) {
|
|
1158
|
-
return comparison;
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
|
|
1162
|
-
if (leftLength < rightLength) {
|
|
1163
|
-
return +1;
|
|
1164
|
-
}
|
|
1165
|
-
if (leftLength > rightLength) {
|
|
1166
|
-
return -1;
|
|
1167
|
-
}
|
|
1168
|
-
return 0;
|
|
1169
|
-
};
|
|
1170
|
-
|
|
1171
1227
|
const collectFiles = async ({
|
|
1172
1228
|
signal = new AbortController().signal,
|
|
1173
1229
|
directoryUrl,
|
|
@@ -1259,39 +1315,130 @@ const collectFiles = async ({
|
|
|
1259
1315
|
}
|
|
1260
1316
|
};
|
|
1261
1317
|
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
)
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1318
|
+
const statsToType = (stats) => {
|
|
1319
|
+
if (stats.isFile()) return "file";
|
|
1320
|
+
if (stats.isDirectory()) return "directory";
|
|
1321
|
+
if (stats.isSymbolicLink()) return "symbolic-link";
|
|
1322
|
+
if (stats.isFIFO()) return "fifo";
|
|
1323
|
+
if (stats.isSocket()) return "socket";
|
|
1324
|
+
if (stats.isCharacterDevice()) return "character-device";
|
|
1325
|
+
if (stats.isBlockDevice()) return "block-device";
|
|
1326
|
+
return undefined;
|
|
1327
|
+
};
|
|
1328
|
+
|
|
1329
|
+
// https://nodejs.org/dist/latest-v13.x/docs/api/fs.html#fs_fspromises_mkdir_path_options
|
|
1330
|
+
const { mkdir } = promises;
|
|
1331
|
+
|
|
1332
|
+
const writeDirectory = async (
|
|
1333
|
+
destination,
|
|
1334
|
+
{ recursive = true, allowUseless = false } = {},
|
|
1335
|
+
) => {
|
|
1336
|
+
const destinationUrl = assertAndNormalizeDirectoryUrl(destination);
|
|
1337
|
+
const destinationPath = urlToFileSystemPath(destinationUrl);
|
|
1338
|
+
|
|
1339
|
+
const destinationStats = await readEntryStat(destinationUrl, {
|
|
1340
|
+
nullIfNotFound: true,
|
|
1341
|
+
followLink: false,
|
|
1342
|
+
});
|
|
1343
|
+
|
|
1344
|
+
if (destinationStats) {
|
|
1345
|
+
if (destinationStats.isDirectory()) {
|
|
1346
|
+
if (allowUseless) {
|
|
1347
|
+
return;
|
|
1348
|
+
}
|
|
1349
|
+
throw new Error(`directory already exists at ${destinationPath}`);
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
const destinationType = statsToType(destinationStats);
|
|
1353
|
+
throw new Error(
|
|
1354
|
+
`cannot write directory at ${destinationPath} because there is a ${destinationType}`,
|
|
1355
|
+
);
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
try {
|
|
1359
|
+
await mkdir(destinationPath, { recursive });
|
|
1360
|
+
} catch (error) {
|
|
1361
|
+
if (allowUseless && error.code === "EEXIST") {
|
|
1362
|
+
return;
|
|
1363
|
+
}
|
|
1364
|
+
throw error;
|
|
1365
|
+
}
|
|
1366
|
+
};
|
|
1367
|
+
|
|
1368
|
+
const ensureParentDirectories = async (destination) => {
|
|
1369
|
+
const destinationUrl = assertAndNormalizeFileUrl(destination);
|
|
1370
|
+
const destinationPath = urlToFileSystemPath(destinationUrl);
|
|
1371
|
+
const destinationParentPath = dirname(destinationPath);
|
|
1372
|
+
|
|
1373
|
+
await writeDirectory(destinationParentPath, {
|
|
1374
|
+
recursive: true,
|
|
1375
|
+
allowUseless: true,
|
|
1376
|
+
});
|
|
1377
|
+
};
|
|
1378
|
+
|
|
1379
|
+
const readFile = async (value, { as = "buffer" } = {}) => {
|
|
1380
|
+
const fileUrl = assertAndNormalizeFileUrl(value);
|
|
1381
|
+
const buffer = await new Promise((resolve, reject) => {
|
|
1382
|
+
readFile$1(new URL(fileUrl), (error, buffer) => {
|
|
1383
|
+
if (error) {
|
|
1384
|
+
reject(error);
|
|
1385
|
+
} else {
|
|
1386
|
+
resolve(buffer);
|
|
1387
|
+
}
|
|
1388
|
+
});
|
|
1389
|
+
});
|
|
1390
|
+
if (as === "buffer") {
|
|
1391
|
+
return buffer;
|
|
1392
|
+
}
|
|
1393
|
+
if (as === "string") {
|
|
1394
|
+
return buffer.toString();
|
|
1395
|
+
}
|
|
1396
|
+
if (as === "json") {
|
|
1397
|
+
return JSON.parse(buffer.toString());
|
|
1398
|
+
}
|
|
1399
|
+
throw new Error(
|
|
1400
|
+
`"as" must be one of "buffer","string","json" received "${as}"`,
|
|
1401
|
+
);
|
|
1402
|
+
};
|
|
1403
|
+
|
|
1404
|
+
const writeFile = async (destination, content = "") => {
|
|
1405
|
+
const destinationUrl = assertAndNormalizeFileUrl(destination);
|
|
1406
|
+
const destinationUrlObject = new URL(destinationUrl);
|
|
1407
|
+
try {
|
|
1408
|
+
await writeFileNaive(destinationUrlObject, content);
|
|
1409
|
+
} catch (error) {
|
|
1410
|
+
if (error.code === "ENOENT") {
|
|
1411
|
+
await ensureParentDirectories(destinationUrl);
|
|
1412
|
+
await writeFileNaive(destinationUrlObject, content);
|
|
1413
|
+
return;
|
|
1283
1414
|
}
|
|
1284
|
-
|
|
1285
|
-
const destinationType = statsToType(destinationStats);
|
|
1286
|
-
throw new Error(
|
|
1287
|
-
`cannot write directory at ${destinationPath} because there is a ${destinationType}`,
|
|
1288
|
-
);
|
|
1415
|
+
throw error;
|
|
1289
1416
|
}
|
|
1417
|
+
};
|
|
1418
|
+
|
|
1419
|
+
const writeFileNaive = (urlObject, content) => {
|
|
1420
|
+
return new Promise((resolve, reject) => {
|
|
1421
|
+
writeFile$1(urlObject, content, (error) => {
|
|
1422
|
+
if (error) {
|
|
1423
|
+
reject(error);
|
|
1424
|
+
} else {
|
|
1425
|
+
resolve();
|
|
1426
|
+
}
|
|
1427
|
+
});
|
|
1428
|
+
});
|
|
1429
|
+
};
|
|
1290
1430
|
|
|
1431
|
+
const writeFileSync = (destination, content = "") => {
|
|
1432
|
+
const destinationUrl = assertAndNormalizeFileUrl(destination);
|
|
1433
|
+
const destinationUrlObject = new URL(destinationUrl);
|
|
1291
1434
|
try {
|
|
1292
|
-
|
|
1435
|
+
writeFileSync$1(destinationUrlObject, content);
|
|
1293
1436
|
} catch (error) {
|
|
1294
|
-
if (
|
|
1437
|
+
if (error.code === "ENOENT") {
|
|
1438
|
+
mkdirSync(new URL("./", destinationUrlObject), {
|
|
1439
|
+
recursive: true,
|
|
1440
|
+
});
|
|
1441
|
+
writeFileSync$1(destinationUrlObject, content);
|
|
1295
1442
|
return;
|
|
1296
1443
|
}
|
|
1297
1444
|
throw error;
|
|
@@ -1543,6 +1690,18 @@ const removeDirectoryNaive = (
|
|
|
1543
1690
|
});
|
|
1544
1691
|
};
|
|
1545
1692
|
|
|
1693
|
+
process.platform === "win32";
|
|
1694
|
+
|
|
1695
|
+
/*
|
|
1696
|
+
* - stats object documentation on Node.js
|
|
1697
|
+
* https://nodejs.org/docs/latest-v13.x/api/fs.html#fs_class_fs_stats
|
|
1698
|
+
*/
|
|
1699
|
+
|
|
1700
|
+
|
|
1701
|
+
process.platform === "win32";
|
|
1702
|
+
|
|
1703
|
+
process.platform === "win32";
|
|
1704
|
+
|
|
1546
1705
|
const ensureEmptyDirectory = async (source) => {
|
|
1547
1706
|
const stats = await readEntryStat(source, {
|
|
1548
1707
|
nullIfNotFound: true,
|
|
@@ -1550,15 +1709,17 @@ const ensureEmptyDirectory = async (source) => {
|
|
|
1550
1709
|
});
|
|
1551
1710
|
if (stats === null) {
|
|
1552
1711
|
// if there is nothing, create a directory
|
|
1553
|
-
|
|
1712
|
+
await writeDirectory(source, { allowUseless: true });
|
|
1713
|
+
return;
|
|
1554
1714
|
}
|
|
1555
1715
|
if (stats.isDirectory()) {
|
|
1556
1716
|
// if there is a directory remove its content and done
|
|
1557
|
-
|
|
1717
|
+
await removeEntry(source, {
|
|
1558
1718
|
allowUseless: true,
|
|
1559
1719
|
recursive: true,
|
|
1560
1720
|
onlyContent: true,
|
|
1561
1721
|
});
|
|
1722
|
+
return;
|
|
1562
1723
|
}
|
|
1563
1724
|
|
|
1564
1725
|
const sourceType = statsToType(stats);
|
|
@@ -1568,159 +1729,10 @@ const ensureEmptyDirectory = async (source) => {
|
|
|
1568
1729
|
);
|
|
1569
1730
|
};
|
|
1570
1731
|
|
|
1571
|
-
const ensureParentDirectories = async (destination) => {
|
|
1572
|
-
const destinationUrl = assertAndNormalizeFileUrl(destination);
|
|
1573
|
-
const destinationPath = urlToFileSystemPath(destinationUrl);
|
|
1574
|
-
const destinationParentPath = dirname(destinationPath);
|
|
1575
|
-
|
|
1576
|
-
return writeDirectory(destinationParentPath, {
|
|
1577
|
-
recursive: true,
|
|
1578
|
-
allowUseless: true,
|
|
1579
|
-
});
|
|
1580
|
-
};
|
|
1581
|
-
|
|
1582
|
-
const isWindows$1 = process.platform === "win32";
|
|
1583
|
-
const baseUrlFallback = fileSystemPathToUrl(process.cwd());
|
|
1584
|
-
|
|
1585
|
-
/**
|
|
1586
|
-
* Some url might be resolved or remapped to url without the windows drive letter.
|
|
1587
|
-
* For instance
|
|
1588
|
-
* new URL('/foo.js', 'file:///C:/dir/file.js')
|
|
1589
|
-
* resolves to
|
|
1590
|
-
* 'file:///foo.js'
|
|
1591
|
-
*
|
|
1592
|
-
* But on windows it becomes a problem because we need the drive letter otherwise
|
|
1593
|
-
* url cannot be converted to a filesystem path.
|
|
1594
|
-
*
|
|
1595
|
-
* ensureWindowsDriveLetter ensure a resolved url still contains the drive letter.
|
|
1596
|
-
*/
|
|
1597
|
-
|
|
1598
|
-
const ensureWindowsDriveLetter = (url, baseUrl) => {
|
|
1599
|
-
try {
|
|
1600
|
-
url = String(new URL(url));
|
|
1601
|
-
} catch (e) {
|
|
1602
|
-
throw new Error(`absolute url expected but got ${url}`);
|
|
1603
|
-
}
|
|
1604
|
-
|
|
1605
|
-
if (!isWindows$1) {
|
|
1606
|
-
return url;
|
|
1607
|
-
}
|
|
1608
|
-
|
|
1609
|
-
try {
|
|
1610
|
-
baseUrl = String(new URL(baseUrl));
|
|
1611
|
-
} catch (e) {
|
|
1612
|
-
throw new Error(
|
|
1613
|
-
`absolute baseUrl expected but got ${baseUrl} to ensure windows drive letter on ${url}`,
|
|
1614
|
-
);
|
|
1615
|
-
}
|
|
1616
|
-
|
|
1617
|
-
if (!url.startsWith("file://")) {
|
|
1618
|
-
return url;
|
|
1619
|
-
}
|
|
1620
|
-
const afterProtocol = url.slice("file://".length);
|
|
1621
|
-
// we still have the windows drive letter
|
|
1622
|
-
if (extractDriveLetter(afterProtocol)) {
|
|
1623
|
-
return url;
|
|
1624
|
-
}
|
|
1625
|
-
|
|
1626
|
-
// drive letter was lost, restore it
|
|
1627
|
-
const baseUrlOrFallback = baseUrl.startsWith("file://")
|
|
1628
|
-
? baseUrl
|
|
1629
|
-
: baseUrlFallback;
|
|
1630
|
-
const driveLetter = extractDriveLetter(
|
|
1631
|
-
baseUrlOrFallback.slice("file://".length),
|
|
1632
|
-
);
|
|
1633
|
-
if (!driveLetter) {
|
|
1634
|
-
throw new Error(
|
|
1635
|
-
`drive letter expected on baseUrl but got ${baseUrl} to ensure windows drive letter on ${url}`,
|
|
1636
|
-
);
|
|
1637
|
-
}
|
|
1638
|
-
return `file:///${driveLetter}:${afterProtocol}`;
|
|
1639
|
-
};
|
|
1640
|
-
|
|
1641
|
-
const extractDriveLetter = (resource) => {
|
|
1642
|
-
// we still have the windows drive letter
|
|
1643
|
-
if (/[a-zA-Z]/.test(resource[1]) && resource[2] === ":") {
|
|
1644
|
-
return resource[1];
|
|
1645
|
-
}
|
|
1646
|
-
return null;
|
|
1647
|
-
};
|
|
1648
|
-
|
|
1649
|
-
process.platform === "win32";
|
|
1650
|
-
|
|
1651
|
-
const readFile = async (value, { as = "buffer" } = {}) => {
|
|
1652
|
-
const fileUrl = assertAndNormalizeFileUrl(value);
|
|
1653
|
-
const buffer = await new Promise((resolve, reject) => {
|
|
1654
|
-
readFile$1(new URL(fileUrl), (error, buffer) => {
|
|
1655
|
-
if (error) {
|
|
1656
|
-
reject(error);
|
|
1657
|
-
} else {
|
|
1658
|
-
resolve(buffer);
|
|
1659
|
-
}
|
|
1660
|
-
});
|
|
1661
|
-
});
|
|
1662
|
-
if (as === "buffer") {
|
|
1663
|
-
return buffer;
|
|
1664
|
-
}
|
|
1665
|
-
if (as === "string") {
|
|
1666
|
-
return buffer.toString();
|
|
1667
|
-
}
|
|
1668
|
-
if (as === "json") {
|
|
1669
|
-
return JSON.parse(buffer.toString());
|
|
1670
|
-
}
|
|
1671
|
-
throw new Error(
|
|
1672
|
-
`"as" must be one of "buffer","string","json" received "${as}"`,
|
|
1673
|
-
);
|
|
1674
|
-
};
|
|
1675
|
-
|
|
1676
1732
|
process.platform === "win32";
|
|
1677
1733
|
|
|
1678
1734
|
process.platform === "linux";
|
|
1679
1735
|
|
|
1680
|
-
const writeFile = async (destination, content = "") => {
|
|
1681
|
-
const destinationUrl = assertAndNormalizeFileUrl(destination);
|
|
1682
|
-
const destinationUrlObject = new URL(destinationUrl);
|
|
1683
|
-
try {
|
|
1684
|
-
await writeFileNaive(destinationUrlObject, content);
|
|
1685
|
-
} catch (error) {
|
|
1686
|
-
if (error.code === "ENOENT") {
|
|
1687
|
-
await ensureParentDirectories(destinationUrl);
|
|
1688
|
-
await writeFileNaive(destinationUrlObject, content);
|
|
1689
|
-
return;
|
|
1690
|
-
}
|
|
1691
|
-
throw error;
|
|
1692
|
-
}
|
|
1693
|
-
};
|
|
1694
|
-
|
|
1695
|
-
const writeFileNaive = (urlObject, content) => {
|
|
1696
|
-
return new Promise((resolve, reject) => {
|
|
1697
|
-
writeFile$1(urlObject, content, (error) => {
|
|
1698
|
-
if (error) {
|
|
1699
|
-
reject(error);
|
|
1700
|
-
} else {
|
|
1701
|
-
resolve();
|
|
1702
|
-
}
|
|
1703
|
-
});
|
|
1704
|
-
});
|
|
1705
|
-
};
|
|
1706
|
-
|
|
1707
|
-
const writeFileSync = (destination, content = "") => {
|
|
1708
|
-
const destinationUrl = assertAndNormalizeFileUrl(destination);
|
|
1709
|
-
const destinationUrlObject = new URL(destinationUrl);
|
|
1710
|
-
try {
|
|
1711
|
-
writeFileSync$1(destinationUrlObject, content);
|
|
1712
|
-
} catch (error) {
|
|
1713
|
-
if (error.code === "ENOENT") {
|
|
1714
|
-
mkdirSync(new URL("./", destinationUrlObject), {
|
|
1715
|
-
recursive: true,
|
|
1716
|
-
});
|
|
1717
|
-
writeFileSync$1(destinationUrlObject, content);
|
|
1718
|
-
return;
|
|
1719
|
-
}
|
|
1720
|
-
throw error;
|
|
1721
|
-
}
|
|
1722
|
-
};
|
|
1723
|
-
|
|
1724
1736
|
const LOG_LEVEL_OFF = "off";
|
|
1725
1737
|
|
|
1726
1738
|
const LOG_LEVEL_DEBUG = "debug";
|
|
@@ -4636,7 +4648,7 @@ const ensureGlobalGc = () => {
|
|
|
4636
4648
|
* @param {Object} testPlanParameters.testPlan Object associating files with runtimes where they will be executed
|
|
4637
4649
|
* @param {boolean} [testPlanParameters.logShortForCompletedExecutions=false] Abbreviate completed execution information to shorten terminal output
|
|
4638
4650
|
* @param {boolean} [testPlanParameters.logMergeForCompletedExecutions=false] Merge completed execution logs to shorten terminal output
|
|
4639
|
-
* @param {number} [testPlanParameters.
|
|
4651
|
+
* @param {boolean|number} [testPlanParameters.concurrency=false] Maximum amount of execution running at the same time
|
|
4640
4652
|
* @param {number} [testPlanParameters.defaultMsAllocatedPerExecution=30000] Milliseconds after which execution is aborted and considered as failed by timeout
|
|
4641
4653
|
* @param {boolean} [testPlanParameters.failFast=false] Fails immediatly when a test execution fails
|
|
4642
4654
|
* @param {number} [testPlanParameters.cooldownBetweenExecutions=0] Millisecond to wait between each execution
|
|
@@ -4663,7 +4675,7 @@ const executeTestPlan = async ({
|
|
|
4663
4675
|
webServer,
|
|
4664
4676
|
testPlan,
|
|
4665
4677
|
updateProcessExitCode = true,
|
|
4666
|
-
|
|
4678
|
+
concurrency = false,
|
|
4667
4679
|
defaultMsAllocatedPerExecution = 30_000,
|
|
4668
4680
|
failFast = false,
|
|
4669
4681
|
// keepRunning: false to ensure runtime is stopped once executed
|
|
@@ -4930,6 +4942,10 @@ To fix this warning:
|
|
|
4930
4942
|
}
|
|
4931
4943
|
}
|
|
4932
4944
|
}
|
|
4945
|
+
|
|
4946
|
+
if (typeof concurrency !== "boolean" && typeof concurrency !== "number") {
|
|
4947
|
+
throw new TypeError(`concurrency must be a boolean or a number`);
|
|
4948
|
+
}
|
|
4933
4949
|
}
|
|
4934
4950
|
|
|
4935
4951
|
logger.debug(
|
|
@@ -4968,6 +4984,25 @@ To fix this warning:
|
|
|
4968
4984
|
}
|
|
4969
4985
|
}
|
|
4970
4986
|
}
|
|
4987
|
+
if (cooldownBetweenExecutions) {
|
|
4988
|
+
if (cooldownBetweenExecutions < 0) {
|
|
4989
|
+
cooldownBetweenExecutions = 0;
|
|
4990
|
+
}
|
|
4991
|
+
if (cooldownBetweenExecutions === Infinity) {
|
|
4992
|
+
cooldownBetweenExecutions = 0;
|
|
4993
|
+
}
|
|
4994
|
+
}
|
|
4995
|
+
if (concurrency === true) {
|
|
4996
|
+
const availableCpus = os.cpus().length;
|
|
4997
|
+
concurrency =
|
|
4998
|
+
availableCpus > 2
|
|
4999
|
+
? // One CPU is used to run this code so we do availableCpus - 1
|
|
5000
|
+
availableCpus - 1
|
|
5001
|
+
: 1;
|
|
5002
|
+
}
|
|
5003
|
+
if (concurrency === false) {
|
|
5004
|
+
concurrency = 1;
|
|
5005
|
+
}
|
|
4971
5006
|
}
|
|
4972
5007
|
|
|
4973
5008
|
testPlan = {
|
|
@@ -5107,7 +5142,7 @@ To fix this warning:
|
|
|
5107
5142
|
const executionLogsEnabled = logger.levels.info;
|
|
5108
5143
|
const executionSpinner =
|
|
5109
5144
|
logRefresh &&
|
|
5110
|
-
|
|
5145
|
+
concurrency === 1 &&
|
|
5111
5146
|
!debugLogsEnabled &&
|
|
5112
5147
|
executionLogsEnabled &&
|
|
5113
5148
|
process.stdout.isTTY &&
|
|
@@ -5130,9 +5165,9 @@ To fix this warning:
|
|
|
5130
5165
|
|
|
5131
5166
|
const callWhenPreviousExecutionAreDone = createCallOrderer();
|
|
5132
5167
|
|
|
5133
|
-
await
|
|
5168
|
+
await executeConcurrently({
|
|
5134
5169
|
multipleExecutionsOperation,
|
|
5135
|
-
|
|
5170
|
+
concurrency,
|
|
5136
5171
|
cooldownBetweenExecutions,
|
|
5137
5172
|
executionSteps,
|
|
5138
5173
|
start: async (paramsFromStep) => {
|
|
@@ -5405,57 +5440,50 @@ const canOverwriteLogGetter = ({
|
|
|
5405
5440
|
return true;
|
|
5406
5441
|
};
|
|
5407
5442
|
|
|
5408
|
-
const
|
|
5443
|
+
const executeConcurrently = async ({
|
|
5409
5444
|
multipleExecutionsOperation,
|
|
5410
|
-
|
|
5445
|
+
concurrency,
|
|
5411
5446
|
cooldownBetweenExecutions,
|
|
5412
5447
|
executionSteps,
|
|
5413
5448
|
start,
|
|
5414
5449
|
}) => {
|
|
5415
|
-
const executionResults = [];
|
|
5416
5450
|
let progressionIndex = 0;
|
|
5417
|
-
let
|
|
5451
|
+
let remainingExecutionToStart = executionSteps.length;
|
|
5418
5452
|
|
|
5419
|
-
const
|
|
5420
|
-
if (
|
|
5453
|
+
const startNext = async () => {
|
|
5454
|
+
if (remainingExecutionToStart === 0) {
|
|
5421
5455
|
return;
|
|
5422
5456
|
}
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
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;
|
|
5457
|
+
remainingExecutionToStart--;
|
|
5458
|
+
const input = executionSteps[progressionIndex];
|
|
5459
|
+
progressionIndex++;
|
|
5460
|
+
await start(input);
|
|
5461
|
+
if (multipleExecutionsOperation.signal.aborted) {
|
|
5462
|
+
return;
|
|
5448
5463
|
}
|
|
5449
5464
|
if (cooldownBetweenExecutions) {
|
|
5450
|
-
await new Promise((resolve) =>
|
|
5451
|
-
setTimeout(
|
|
5452
|
-
|
|
5465
|
+
await new Promise((resolve) => {
|
|
5466
|
+
const timeoutId = setTimeout(() => {
|
|
5467
|
+
removeClearTimeoutOnAbort();
|
|
5468
|
+
resolve();
|
|
5469
|
+
}, cooldownBetweenExecutions);
|
|
5470
|
+
const removeClearTimeoutOnAbort =
|
|
5471
|
+
multipleExecutionsOperation.signal.addAbortCallback(() => {
|
|
5472
|
+
clearTimeout(timeoutId);
|
|
5473
|
+
});
|
|
5474
|
+
});
|
|
5475
|
+
if (multipleExecutionsOperation.signal.aborted) {
|
|
5476
|
+
return;
|
|
5477
|
+
}
|
|
5453
5478
|
}
|
|
5479
|
+
await startNext();
|
|
5454
5480
|
};
|
|
5455
5481
|
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
5482
|
+
const promises = [];
|
|
5483
|
+
while (concurrency--) {
|
|
5484
|
+
promises.push(startNext());
|
|
5485
|
+
}
|
|
5486
|
+
await Promise.all(promises);
|
|
5459
5487
|
};
|
|
5460
5488
|
|
|
5461
5489
|
const memoize = (compute) => {
|
|
@@ -6864,9 +6892,12 @@ const nodeChildProcess = ({
|
|
|
6864
6892
|
);
|
|
6865
6893
|
return;
|
|
6866
6894
|
}
|
|
6895
|
+
if (code === null || code === 0) {
|
|
6896
|
+
result.status = "completed";
|
|
6897
|
+
result.namespace = {};
|
|
6898
|
+
return;
|
|
6899
|
+
}
|
|
6867
6900
|
if (
|
|
6868
|
-
code === null ||
|
|
6869
|
-
code === 0 ||
|
|
6870
6901
|
code === EXIT_CODES.SIGINT ||
|
|
6871
6902
|
code === EXIT_CODES.SIGTERM ||
|
|
6872
6903
|
code === EXIT_CODES.SIGABORT
|
|
@@ -7170,9 +7201,12 @@ const nodeWorkerThread = ({
|
|
|
7170
7201
|
);
|
|
7171
7202
|
return;
|
|
7172
7203
|
}
|
|
7204
|
+
if (code === null || code === 0) {
|
|
7205
|
+
result.status = "completed";
|
|
7206
|
+
result.namespace = {};
|
|
7207
|
+
return;
|
|
7208
|
+
}
|
|
7173
7209
|
if (
|
|
7174
|
-
code === null ||
|
|
7175
|
-
code === 0 ||
|
|
7176
7210
|
code === EXIT_CODES.SIGINT ||
|
|
7177
7211
|
code === EXIT_CODES.SIGTERM ||
|
|
7178
7212
|
code === EXIT_CODES.SIGABORT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/test",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -35,13 +35,13 @@
|
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@jsenv/abort": "4.2.4",
|
|
37
37
|
"@jsenv/ast": "6.0.1",
|
|
38
|
-
"@jsenv/filesystem": "4.
|
|
38
|
+
"@jsenv/filesystem": "4.4.0",
|
|
39
39
|
"@jsenv/log": "3.4.3",
|
|
40
40
|
"@jsenv/plugin-supervisor": "1.3.9",
|
|
41
41
|
"@jsenv/urls": "2.2.1",
|
|
42
|
-
"@jsenv/url-meta": "8.
|
|
42
|
+
"@jsenv/url-meta": "8.2.0",
|
|
43
43
|
"@jsenv/utils": "2.0.1",
|
|
44
|
-
"@jsenv/github-check-run": "0.1.
|
|
44
|
+
"@jsenv/github-check-run": "0.1.5",
|
|
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,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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
569
|
+
await executeConcurrently({
|
|
546
570
|
multipleExecutionsOperation,
|
|
547
|
-
|
|
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
|
|
844
|
+
const executeConcurrently = async ({
|
|
821
845
|
multipleExecutionsOperation,
|
|
822
|
-
|
|
846
|
+
concurrency,
|
|
823
847
|
cooldownBetweenExecutions,
|
|
824
848
|
executionSteps,
|
|
825
849
|
start,
|
|
826
850
|
}) => {
|
|
827
|
-
const executionResults = [];
|
|
828
851
|
let progressionIndex = 0;
|
|
829
|
-
let
|
|
852
|
+
let remainingExecutionToStart = executionSteps.length;
|
|
830
853
|
|
|
831
|
-
const
|
|
832
|
-
if (
|
|
854
|
+
const startNext = async () => {
|
|
855
|
+
if (remainingExecutionToStart === 0) {
|
|
833
856
|
return;
|
|
834
857
|
}
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
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(
|
|
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
|
-
|
|
869
|
-
|
|
870
|
-
|
|
883
|
+
const promises = [];
|
|
884
|
+
while (concurrency--) {
|
|
885
|
+
promises.push(startNext());
|
|
886
|
+
}
|
|
887
|
+
await Promise.all(promises);
|
|
871
888
|
};
|
|
@@ -256,9 +256,12 @@ export const nodeChildProcess = ({
|
|
|
256
256
|
);
|
|
257
257
|
return;
|
|
258
258
|
}
|
|
259
|
+
if (code === null || code === 0) {
|
|
260
|
+
result.status = "completed";
|
|
261
|
+
result.namespace = {};
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
259
264
|
if (
|
|
260
|
-
code === null ||
|
|
261
|
-
code === 0 ||
|
|
262
265
|
code === EXIT_CODES.SIGINT ||
|
|
263
266
|
code === EXIT_CODES.SIGTERM ||
|
|
264
267
|
code === EXIT_CODES.SIGABORT
|
|
@@ -202,9 +202,12 @@ export const nodeWorkerThread = ({
|
|
|
202
202
|
);
|
|
203
203
|
return;
|
|
204
204
|
}
|
|
205
|
+
if (code === null || code === 0) {
|
|
206
|
+
result.status = "completed";
|
|
207
|
+
result.namespace = {};
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
205
210
|
if (
|
|
206
|
-
code === null ||
|
|
207
|
-
code === 0 ||
|
|
208
211
|
code === EXIT_CODES.SIGINT ||
|
|
209
212
|
code === EXIT_CODES.SIGTERM ||
|
|
210
213
|
code === EXIT_CODES.SIGABORT
|