@jsenv/test 1.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.
Files changed (56) hide show
  1. package/dist/controllable_child_process.mjs +129 -0
  2. package/dist/controllable_worker_thread.mjs +91 -0
  3. package/dist/importmap_node_loader.mjs +551 -0
  4. package/dist/js/execute_using_dynamic_import.js +850 -0
  5. package/dist/js/v8_coverage.js +508 -0
  6. package/dist/jsenv_test.js +5993 -0
  7. package/dist/no_experimental_warnings.cjs +10 -0
  8. package/package.json +56 -0
  9. package/src/coverage/babel_plugin_instrument.js +48 -0
  10. package/src/coverage/coverage_reporter_html_directory.js +32 -0
  11. package/src/coverage/coverage_reporter_json_file.js +17 -0
  12. package/src/coverage/coverage_reporter_text_log.js +19 -0
  13. package/src/coverage/empty_coverage_factory.js +52 -0
  14. package/src/coverage/file_by_file_coverage.js +25 -0
  15. package/src/coverage/istanbul_coverage_composition.js +28 -0
  16. package/src/coverage/istanbul_coverage_map_from_coverage.js +16 -0
  17. package/src/coverage/list_files_not_covered.js +15 -0
  18. package/src/coverage/missing_coverage.js +41 -0
  19. package/src/coverage/report_to_coverage.js +198 -0
  20. package/src/coverage/v8_and_istanbul.js +37 -0
  21. package/src/coverage/v8_coverage.js +26 -0
  22. package/src/coverage/v8_coverage_composition.js +24 -0
  23. package/src/coverage/v8_coverage_node_directory.js +85 -0
  24. package/src/coverage/v8_coverage_to_istanbul.js +99 -0
  25. package/src/execution/execute.js +111 -0
  26. package/src/execution/execute_steps.js +423 -0
  27. package/src/execution/execute_test_plan.js +372 -0
  28. package/src/execution/execution_colors.js +10 -0
  29. package/src/execution/execution_steps.js +65 -0
  30. package/src/execution/gc.js +9 -0
  31. package/src/execution/logs_file_execution.js +427 -0
  32. package/src/execution/logs_file_execution.test.mjs +41 -0
  33. package/src/execution/run.js +160 -0
  34. package/src/execution/web_server_param.js +73 -0
  35. package/src/main.js +10 -0
  36. package/src/runtime_browsers/chromium.js +24 -0
  37. package/src/runtime_browsers/firefox.js +23 -0
  38. package/src/runtime_browsers/middleware_istanbul.js +65 -0
  39. package/src/runtime_browsers/middleware_js_supervisor.js +98 -0
  40. package/src/runtime_browsers/using_playwright.js +573 -0
  41. package/src/runtime_browsers/webkit.js +40 -0
  42. package/src/runtime_node/child_exec_options.js +166 -0
  43. package/src/runtime_node/controllable_child_process.mjs +135 -0
  44. package/src/runtime_node/controllable_worker_thread.mjs +103 -0
  45. package/src/runtime_node/exec_options.js +57 -0
  46. package/src/runtime_node/execute_using_dynamic_import.js +53 -0
  47. package/src/runtime_node/exit_codes.js +9 -0
  48. package/src/runtime_node/importmap_node_loader.mjs +51 -0
  49. package/src/runtime_node/importmap_node_loader_file_url.js +4 -0
  50. package/src/runtime_node/kill_process_tree.js +76 -0
  51. package/src/runtime_node/no_experimental_warnings.cjs +13 -0
  52. package/src/runtime_node/no_experimental_warnings_file_url.js +4 -0
  53. package/src/runtime_node/node_child_process.js +374 -0
  54. package/src/runtime_node/node_execution_performance.js +67 -0
  55. package/src/runtime_node/node_worker_thread.js +301 -0
  56. package/src/runtime_node/profiler_v8_coverage.js +56 -0
@@ -0,0 +1,551 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { pathToFileURL } from "node:url";
3
+
4
+ // duplicated from @jsenv/log to avoid the dependency
5
+ const createDetailedMessage = (message, details = {}) => {
6
+ let string = `${message}`;
7
+ Object.keys(details).forEach(key => {
8
+ const value = details[key];
9
+ string += `
10
+ --- ${key} ---
11
+ ${Array.isArray(value) ? value.join(`
12
+ `) : value}`;
13
+ });
14
+ return string;
15
+ };
16
+
17
+ const assertImportMap = value => {
18
+ if (value === null) {
19
+ throw new TypeError(`an importMap must be an object, got null`);
20
+ }
21
+ const type = typeof value;
22
+ if (type !== "object") {
23
+ throw new TypeError(`an importMap must be an object, received ${value}`);
24
+ }
25
+ if (Array.isArray(value)) {
26
+ throw new TypeError(`an importMap must be an object, received array ${value}`);
27
+ }
28
+ };
29
+
30
+ const hasScheme = string => {
31
+ return /^[a-zA-Z]{2,}:/.test(string);
32
+ };
33
+
34
+ const urlToScheme = urlString => {
35
+ const colonIndex = urlString.indexOf(":");
36
+ if (colonIndex === -1) return "";
37
+ return urlString.slice(0, colonIndex);
38
+ };
39
+
40
+ const urlToPathname = urlString => {
41
+ return ressourceToPathname(urlToRessource(urlString));
42
+ };
43
+ const urlToRessource = urlString => {
44
+ const scheme = urlToScheme(urlString);
45
+ if (scheme === "file") {
46
+ return urlString.slice("file://".length);
47
+ }
48
+ if (scheme === "https" || scheme === "http") {
49
+ // remove origin
50
+ const afterProtocol = urlString.slice(scheme.length + "://".length);
51
+ const pathnameSlashIndex = afterProtocol.indexOf("/", "://".length);
52
+ return afterProtocol.slice(pathnameSlashIndex);
53
+ }
54
+ return urlString.slice(scheme.length + 1);
55
+ };
56
+ const ressourceToPathname = ressource => {
57
+ const searchSeparatorIndex = ressource.indexOf("?");
58
+ return searchSeparatorIndex === -1 ? ressource : ressource.slice(0, searchSeparatorIndex);
59
+ };
60
+
61
+ const urlToOrigin = urlString => {
62
+ const scheme = urlToScheme(urlString);
63
+ if (scheme === "file") {
64
+ return "file://";
65
+ }
66
+ if (scheme === "http" || scheme === "https") {
67
+ const secondProtocolSlashIndex = scheme.length + "://".length;
68
+ const pathnameSlashIndex = urlString.indexOf("/", secondProtocolSlashIndex);
69
+ if (pathnameSlashIndex === -1) return urlString;
70
+ return urlString.slice(0, pathnameSlashIndex);
71
+ }
72
+ return urlString.slice(0, scheme.length + 1);
73
+ };
74
+
75
+ const pathnameToParentPathname = pathname => {
76
+ const slashLastIndex = pathname.lastIndexOf("/");
77
+ if (slashLastIndex === -1) {
78
+ return "/";
79
+ }
80
+ return pathname.slice(0, slashLastIndex + 1);
81
+ };
82
+
83
+ // could be useful: https://url.spec.whatwg.org/#url-miscellaneous
84
+ const resolveUrl = (specifier, baseUrl) => {
85
+ if (baseUrl) {
86
+ if (typeof baseUrl !== "string") {
87
+ throw new TypeError(writeBaseUrlMustBeAString({
88
+ baseUrl,
89
+ specifier
90
+ }));
91
+ }
92
+ if (!hasScheme(baseUrl)) {
93
+ throw new Error(writeBaseUrlMustBeAbsolute({
94
+ baseUrl,
95
+ specifier
96
+ }));
97
+ }
98
+ }
99
+ if (hasScheme(specifier)) {
100
+ return specifier;
101
+ }
102
+ if (!baseUrl) {
103
+ throw new Error(writeBaseUrlRequired({
104
+ baseUrl,
105
+ specifier
106
+ }));
107
+ }
108
+
109
+ // scheme relative
110
+ if (specifier.slice(0, 2) === "//") {
111
+ return `${urlToScheme(baseUrl)}:${specifier}`;
112
+ }
113
+
114
+ // origin relative
115
+ if (specifier[0] === "/") {
116
+ return `${urlToOrigin(baseUrl)}${specifier}`;
117
+ }
118
+ const baseOrigin = urlToOrigin(baseUrl);
119
+ const basePathname = urlToPathname(baseUrl);
120
+ if (specifier === ".") {
121
+ const baseDirectoryPathname = pathnameToParentPathname(basePathname);
122
+ return `${baseOrigin}${baseDirectoryPathname}`;
123
+ }
124
+
125
+ // pathname relative inside
126
+ if (specifier.slice(0, 2) === "./") {
127
+ const baseDirectoryPathname = pathnameToParentPathname(basePathname);
128
+ return `${baseOrigin}${baseDirectoryPathname}${specifier.slice(2)}`;
129
+ }
130
+
131
+ // pathname relative outside
132
+ if (specifier.slice(0, 3) === "../") {
133
+ let unresolvedPathname = specifier;
134
+ const importerFolders = basePathname.split("/");
135
+ importerFolders.pop();
136
+ while (unresolvedPathname.slice(0, 3) === "../") {
137
+ unresolvedPathname = unresolvedPathname.slice(3);
138
+ // when there is no folder left to resolved
139
+ // we just ignore '../'
140
+ if (importerFolders.length) {
141
+ importerFolders.pop();
142
+ }
143
+ }
144
+ const resolvedPathname = `${importerFolders.join("/")}/${unresolvedPathname}`;
145
+ return `${baseOrigin}${resolvedPathname}`;
146
+ }
147
+
148
+ // bare
149
+ if (basePathname === "") {
150
+ return `${baseOrigin}/${specifier}`;
151
+ }
152
+ if (basePathname[basePathname.length] === "/") {
153
+ return `${baseOrigin}${basePathname}${specifier}`;
154
+ }
155
+ return `${baseOrigin}${pathnameToParentPathname(basePathname)}${specifier}`;
156
+ };
157
+ const writeBaseUrlMustBeAString = ({
158
+ baseUrl,
159
+ specifier
160
+ }) => `baseUrl must be a string.
161
+ --- base url ---
162
+ ${baseUrl}
163
+ --- specifier ---
164
+ ${specifier}`;
165
+ const writeBaseUrlMustBeAbsolute = ({
166
+ baseUrl,
167
+ specifier
168
+ }) => `baseUrl must be absolute.
169
+ --- base url ---
170
+ ${baseUrl}
171
+ --- specifier ---
172
+ ${specifier}`;
173
+ const writeBaseUrlRequired = ({
174
+ baseUrl,
175
+ specifier
176
+ }) => `baseUrl required to resolve relative specifier.
177
+ --- base url ---
178
+ ${baseUrl}
179
+ --- specifier ---
180
+ ${specifier}`;
181
+
182
+ const tryUrlResolution = (string, url) => {
183
+ const result = resolveUrl(string, url);
184
+ return hasScheme(result) ? result : null;
185
+ };
186
+
187
+ const resolveSpecifier = (specifier, importer) => {
188
+ if (specifier === "." || specifier[0] === "/" || specifier.startsWith("./") || specifier.startsWith("../")) {
189
+ return resolveUrl(specifier, importer);
190
+ }
191
+ if (hasScheme(specifier)) {
192
+ return specifier;
193
+ }
194
+ return null;
195
+ };
196
+
197
+ const applyImportMap = ({
198
+ importMap,
199
+ specifier,
200
+ importer,
201
+ createBareSpecifierError = ({
202
+ specifier,
203
+ importer
204
+ }) => {
205
+ return new Error(createDetailedMessage(`Unmapped bare specifier.`, {
206
+ specifier,
207
+ importer
208
+ }));
209
+ },
210
+ onImportMapping = () => {}
211
+ }) => {
212
+ assertImportMap(importMap);
213
+ if (typeof specifier !== "string") {
214
+ throw new TypeError(createDetailedMessage("specifier must be a string.", {
215
+ specifier,
216
+ importer
217
+ }));
218
+ }
219
+ if (importer) {
220
+ if (typeof importer !== "string") {
221
+ throw new TypeError(createDetailedMessage("importer must be a string.", {
222
+ importer,
223
+ specifier
224
+ }));
225
+ }
226
+ if (!hasScheme(importer)) {
227
+ throw new Error(createDetailedMessage(`importer must be an absolute url.`, {
228
+ importer,
229
+ specifier
230
+ }));
231
+ }
232
+ }
233
+ const specifierUrl = resolveSpecifier(specifier, importer);
234
+ const specifierNormalized = specifierUrl || specifier;
235
+ const {
236
+ scopes
237
+ } = importMap;
238
+ if (scopes && importer) {
239
+ const scopeSpecifierMatching = Object.keys(scopes).find(scopeSpecifier => {
240
+ return scopeSpecifier === importer || specifierIsPrefixOf(scopeSpecifier, importer);
241
+ });
242
+ if (scopeSpecifierMatching) {
243
+ const scopeMappings = scopes[scopeSpecifierMatching];
244
+ const mappingFromScopes = applyMappings(scopeMappings, specifierNormalized, scopeSpecifierMatching, onImportMapping);
245
+ if (mappingFromScopes !== null) {
246
+ return mappingFromScopes;
247
+ }
248
+ }
249
+ }
250
+ const {
251
+ imports
252
+ } = importMap;
253
+ if (imports) {
254
+ const mappingFromImports = applyMappings(imports, specifierNormalized, undefined, onImportMapping);
255
+ if (mappingFromImports !== null) {
256
+ return mappingFromImports;
257
+ }
258
+ }
259
+ if (specifierUrl) {
260
+ return specifierUrl;
261
+ }
262
+ throw createBareSpecifierError({
263
+ specifier,
264
+ importer
265
+ });
266
+ };
267
+ const applyMappings = (mappings, specifierNormalized, scope, onImportMapping) => {
268
+ const specifierCandidates = Object.keys(mappings);
269
+ let i = 0;
270
+ while (i < specifierCandidates.length) {
271
+ const specifierCandidate = specifierCandidates[i];
272
+ i++;
273
+ if (specifierCandidate === specifierNormalized) {
274
+ const address = mappings[specifierCandidate];
275
+ onImportMapping({
276
+ scope,
277
+ from: specifierCandidate,
278
+ to: address,
279
+ before: specifierNormalized,
280
+ after: address
281
+ });
282
+ return address;
283
+ }
284
+ if (specifierIsPrefixOf(specifierCandidate, specifierNormalized)) {
285
+ const address = mappings[specifierCandidate];
286
+ const afterSpecifier = specifierNormalized.slice(specifierCandidate.length);
287
+ const addressFinal = tryUrlResolution(afterSpecifier, address);
288
+ onImportMapping({
289
+ scope,
290
+ from: specifierCandidate,
291
+ to: address,
292
+ before: specifierNormalized,
293
+ after: addressFinal
294
+ });
295
+ return addressFinal;
296
+ }
297
+ }
298
+ return null;
299
+ };
300
+ const specifierIsPrefixOf = (specifierHref, href) => {
301
+ return specifierHref[specifierHref.length - 1] === "/" && href.startsWith(specifierHref);
302
+ };
303
+
304
+ const sortImports = imports => {
305
+ const mappingsSorted = {};
306
+ Object.keys(imports).sort(compareLengthOrLocaleCompare).forEach(name => {
307
+ mappingsSorted[name] = imports[name];
308
+ });
309
+ return mappingsSorted;
310
+ };
311
+ const sortScopes = scopes => {
312
+ const scopesSorted = {};
313
+ Object.keys(scopes).sort(compareLengthOrLocaleCompare).forEach(scopeSpecifier => {
314
+ scopesSorted[scopeSpecifier] = sortImports(scopes[scopeSpecifier]);
315
+ });
316
+ return scopesSorted;
317
+ };
318
+ const compareLengthOrLocaleCompare = (a, b) => {
319
+ return b.length - a.length || a.localeCompare(b);
320
+ };
321
+
322
+ const normalizeImportMap = (importMap, baseUrl) => {
323
+ assertImportMap(importMap);
324
+ if (!isStringOrUrl(baseUrl)) {
325
+ throw new TypeError(formulateBaseUrlMustBeStringOrUrl({
326
+ baseUrl
327
+ }));
328
+ }
329
+ const {
330
+ imports,
331
+ scopes
332
+ } = importMap;
333
+ return {
334
+ imports: imports ? normalizeMappings(imports, baseUrl) : undefined,
335
+ scopes: scopes ? normalizeScopes(scopes, baseUrl) : undefined
336
+ };
337
+ };
338
+ const isStringOrUrl = value => {
339
+ if (typeof value === "string") {
340
+ return true;
341
+ }
342
+ if (typeof URL === "function" && value instanceof URL) {
343
+ return true;
344
+ }
345
+ return false;
346
+ };
347
+ const normalizeMappings = (mappings, baseUrl) => {
348
+ const mappingsNormalized = {};
349
+ Object.keys(mappings).forEach(specifier => {
350
+ const address = mappings[specifier];
351
+ if (typeof address !== "string") {
352
+ console.warn(formulateAddressMustBeAString({
353
+ address,
354
+ specifier
355
+ }));
356
+ return;
357
+ }
358
+ const specifierResolved = resolveSpecifier(specifier, baseUrl) || specifier;
359
+ const addressUrl = tryUrlResolution(address, baseUrl);
360
+ if (addressUrl === null) {
361
+ console.warn(formulateAdressResolutionFailed({
362
+ address,
363
+ baseUrl,
364
+ specifier
365
+ }));
366
+ return;
367
+ }
368
+ if (specifier.endsWith("/") && !addressUrl.endsWith("/")) {
369
+ console.warn(formulateAddressUrlRequiresTrailingSlash({
370
+ addressUrl,
371
+ address,
372
+ specifier
373
+ }));
374
+ return;
375
+ }
376
+ mappingsNormalized[specifierResolved] = addressUrl;
377
+ });
378
+ return sortImports(mappingsNormalized);
379
+ };
380
+ const normalizeScopes = (scopes, baseUrl) => {
381
+ const scopesNormalized = {};
382
+ Object.keys(scopes).forEach(scopeSpecifier => {
383
+ const scopeMappings = scopes[scopeSpecifier];
384
+ const scopeUrl = tryUrlResolution(scopeSpecifier, baseUrl);
385
+ if (scopeUrl === null) {
386
+ console.warn(formulateScopeResolutionFailed({
387
+ scope: scopeSpecifier,
388
+ baseUrl
389
+ }));
390
+ return;
391
+ }
392
+ const scopeValueNormalized = normalizeMappings(scopeMappings, baseUrl);
393
+ scopesNormalized[scopeUrl] = scopeValueNormalized;
394
+ });
395
+ return sortScopes(scopesNormalized);
396
+ };
397
+ const formulateBaseUrlMustBeStringOrUrl = ({
398
+ baseUrl
399
+ }) => `baseUrl must be a string or an url.
400
+ --- base url ---
401
+ ${baseUrl}`;
402
+ const formulateAddressMustBeAString = ({
403
+ specifier,
404
+ address
405
+ }) => `Address must be a string.
406
+ --- address ---
407
+ ${address}
408
+ --- specifier ---
409
+ ${specifier}`;
410
+ const formulateAdressResolutionFailed = ({
411
+ address,
412
+ baseUrl,
413
+ specifier
414
+ }) => `Address url resolution failed.
415
+ --- address ---
416
+ ${address}
417
+ --- base url ---
418
+ ${baseUrl}
419
+ --- specifier ---
420
+ ${specifier}`;
421
+ const formulateAddressUrlRequiresTrailingSlash = ({
422
+ addressURL,
423
+ address,
424
+ specifier
425
+ }) => `Address must end with /.
426
+ --- address url ---
427
+ ${addressURL}
428
+ --- address ---
429
+ ${address}
430
+ --- specifier ---
431
+ ${specifier}`;
432
+ const formulateScopeResolutionFailed = ({
433
+ scope,
434
+ baseUrl
435
+ }) => `Scope url resolution failed.
436
+ --- scope ---
437
+ ${scope}
438
+ --- base url ---
439
+ ${baseUrl}`;
440
+
441
+ const pathnameToExtension = pathname => {
442
+ const slashLastIndex = pathname.lastIndexOf("/");
443
+ if (slashLastIndex !== -1) {
444
+ pathname = pathname.slice(slashLastIndex + 1);
445
+ }
446
+ const dotLastIndex = pathname.lastIndexOf(".");
447
+ if (dotLastIndex === -1) return "";
448
+ // if (dotLastIndex === pathname.length - 1) return ""
449
+ return pathname.slice(dotLastIndex);
450
+ };
451
+
452
+ const resolveImport = ({
453
+ specifier,
454
+ importer,
455
+ importMap,
456
+ defaultExtension = false,
457
+ createBareSpecifierError,
458
+ onImportMapping = () => {}
459
+ }) => {
460
+ let url;
461
+ if (importMap) {
462
+ url = applyImportMap({
463
+ importMap,
464
+ specifier,
465
+ importer,
466
+ createBareSpecifierError,
467
+ onImportMapping
468
+ });
469
+ } else {
470
+ url = resolveUrl(specifier, importer);
471
+ }
472
+ if (defaultExtension) {
473
+ url = applyDefaultExtension({
474
+ url,
475
+ importer,
476
+ defaultExtension
477
+ });
478
+ }
479
+ return url;
480
+ };
481
+ const applyDefaultExtension = ({
482
+ url,
483
+ importer,
484
+ defaultExtension
485
+ }) => {
486
+ if (urlToPathname(url).endsWith("/")) {
487
+ return url;
488
+ }
489
+ if (typeof defaultExtension === "string") {
490
+ const extension = pathnameToExtension(url);
491
+ if (extension === "") {
492
+ return `${url}${defaultExtension}`;
493
+ }
494
+ return url;
495
+ }
496
+ if (defaultExtension === true) {
497
+ const extension = pathnameToExtension(url);
498
+ if (extension === "" && importer) {
499
+ const importerPathname = urlToPathname(importer);
500
+ const importerExtension = pathnameToExtension(importerPathname);
501
+ return `${url}${importerExtension}`;
502
+ }
503
+ }
504
+ return url;
505
+ };
506
+
507
+ let importMap;
508
+ const cwdUrl = `${String(pathToFileURL(process.cwd()))}/`;
509
+ if (process.env.IMPORT_MAP) {
510
+ importMap = JSON.parse(process.env.IMPORT_MAP);
511
+ } else if (process.env.IMPORT_MAP_PATH) {
512
+ const importmapFileUrl = pathToFileURL(process.env.IMPORT_MAP_PATH);
513
+ const importmapFileContentAsString = readFileSync(importmapFileUrl, "utf8");
514
+ importMap = JSON.parse(importmapFileContentAsString);
515
+ } else {
516
+ const importmapFileUrl = new URL("./import_map.json", cwdUrl);
517
+ const importmapFileContentAsString = readFileSync(importmapFileUrl, "utf8");
518
+ importMap = JSON.parse(importmapFileContentAsString);
519
+ }
520
+ const importMapBaseUrl = process.env.IMPORT_MAP_BASE_URL || cwdUrl;
521
+ importMap = normalizeImportMap(importMap, importMapBaseUrl);
522
+ const resolve = (specifier, context, nextResolve) => {
523
+ try {
524
+ let mapped;
525
+ const importer = context.parentURL ? String(context.parentURL) : undefined;
526
+ const resolved = resolveImport({
527
+ specifier,
528
+ importer,
529
+ importMap,
530
+ defaultExtension: ".js",
531
+ onImportMapping: () => {
532
+ mapped = true;
533
+ }
534
+ });
535
+ if (mapped) {
536
+ return {
537
+ shortCircuit: true,
538
+ url: resolved
539
+ };
540
+ }
541
+ } catch (e) {
542
+ if (e.message.includes("bare specifier")) {
543
+ return nextResolve(specifier, context);
544
+ }
545
+ console.error(e);
546
+ return nextResolve(specifier, context);
547
+ }
548
+ return nextResolve(specifier, context);
549
+ };
550
+
551
+ export { resolve };