@jsenv/test 2.1.3 → 3.0.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.
Files changed (46) hide show
  1. package/README.md +2 -1
  2. package/dist/js/exception.js +908 -0
  3. package/dist/js/execute_using_dynamic_import.js +76 -167
  4. package/dist/js/v8_coverage.js +4 -482
  5. package/dist/jsenv_test.js +4608 -2917
  6. package/dist/node_child_process_controlled.mjs +95 -76
  7. package/dist/node_worker_thread_controlled.mjs +72 -54
  8. package/package.json +23 -14
  9. package/src/coverage/generate_coverage.js +186 -0
  10. package/src/coverage/list_files_not_covered.js +2 -2
  11. package/src/coverage/missing_coverage.js +2 -2
  12. package/src/coverage/report_coverage_as_html.js +45 -0
  13. package/src/coverage/report_coverage_as_json.js +20 -0
  14. package/src/coverage/{coverage_reporter_text_log.js → report_coverage_in_console.js} +13 -7
  15. package/src/coverage/v8_and_istanbul.js +4 -4
  16. package/src/coverage/v8_coverage.js +2 -2
  17. package/src/execution/exception.js +151 -68
  18. package/src/execution/execute.js +24 -11
  19. package/src/execution/execute_test_plan.js +834 -750
  20. package/src/execution/format_error_for_terminal.js +187 -0
  21. package/src/execution/github_annotation_from_error.js +3 -3
  22. package/src/execution/junit_xml_file/report_as_junit_xml.js +149 -0
  23. package/src/execution/junit_xml_file/xml_generator.js +141 -0
  24. package/src/execution/reporters/reporter_list.js +687 -0
  25. package/src/execution/run.js +92 -30
  26. package/src/execution/web_server/start_using_command.js +2 -2
  27. package/src/execution/web_server_param.js +5 -5
  28. package/src/helpers/cpu_usage.js +142 -0
  29. package/src/helpers/cpu_usage.test_manual.js +7 -0
  30. package/src/main.js +8 -0
  31. package/src/runtime_browsers/chromium.js +1 -3
  32. package/src/runtime_browsers/firefox.js +0 -3
  33. package/src/runtime_browsers/middleware_istanbul.js +2 -2
  34. package/src/runtime_browsers/using_playwright.js +296 -220
  35. package/src/runtime_browsers/webkit.js +0 -3
  36. package/src/runtime_node/execute_using_dynamic_import.js +75 -28
  37. package/src/runtime_node/node_child_process.js +250 -139
  38. package/src/runtime_node/node_child_process_controlled.mjs +94 -75
  39. package/src/runtime_node/node_worker_thread.js +206 -120
  40. package/src/runtime_node/node_worker_thread_controlled.mjs +70 -53
  41. package/src/coverage/coverage_reporter_html_directory.js +0 -32
  42. package/src/coverage/coverage_reporter_json_file.js +0 -17
  43. package/src/coverage/report_to_coverage.js +0 -198
  44. package/src/execution/execution_colors.js +0 -10
  45. package/src/execution/execution_steps.js +0 -65
  46. package/src/execution/logs_file_execution.js +0 -495
@@ -14,6 +14,8 @@ import crypto from "node:crypto";
14
14
  import { Abort, raceCallbacks } from "@jsenv/abort";
15
15
  import { ensureParentDirectories } from "@jsenv/filesystem";
16
16
 
17
+ import { createException } from "./exception.js";
18
+
17
19
  export const run = async ({
18
20
  signal = new AbortController().signal,
19
21
  logger,
@@ -21,19 +23,43 @@ export const run = async ({
21
23
  keepRunning = false,
22
24
  mirrorConsole = false,
23
25
  collectConsole = false,
26
+ measureMemoryUsage = false,
27
+ onMeasureMemoryAvailable,
28
+ collectPerformance = false,
24
29
  coverageEnabled = false,
25
30
  coverageTempDirectoryUrl,
26
- collectPerformance = false,
27
31
  runtime,
28
32
  runtimeParams,
29
33
  }) => {
34
+ if (keepRunning) {
35
+ allocatedMs = Infinity;
36
+ }
37
+ if (allocatedMs === Infinity) {
38
+ allocatedMs = 0;
39
+ }
40
+
41
+ const timingOrigin = Date.now();
30
42
  const result = {
31
43
  status: "pending",
32
44
  errors: [],
33
45
  namespace: null,
46
+ consoleCalls: null,
47
+ timings: {
48
+ origin: timingOrigin,
49
+ start: 0,
50
+ runtimeStart: null,
51
+ executionStart: null,
52
+ executionEnd: null,
53
+ runtimeEnd: null,
54
+ end: null,
55
+ },
56
+ memoryUsage: null,
57
+ performance: null,
58
+ coverageFileUrl: null,
59
+ };
60
+ const takeTiming = (ms = Date.now()) => {
61
+ return ms - timingOrigin;
34
62
  };
35
- const callbacks = [];
36
-
37
63
  const onConsoleRef = { current: () => {} };
38
64
  const stopSignal = { notify: () => {} };
39
65
  const runtimeLabel = `${runtime.name}/${runtime.version}`;
@@ -41,13 +67,7 @@ export const run = async ({
41
67
  const runOperation = Abort.startOperation();
42
68
  runOperation.addAbortSignal(signal);
43
69
  let timeoutAbortSource;
44
- if (
45
- // ideally we would rather log than the timeout is ignored
46
- // when keepRunning is true
47
- !keepRunning &&
48
- typeof allocatedMs === "number" &&
49
- allocatedMs !== Infinity
50
- ) {
70
+ if (allocatedMs) {
51
71
  timeoutAbortSource = runOperation.timeout(allocatedMs);
52
72
  }
53
73
  const consoleCalls = [];
@@ -77,19 +97,12 @@ export const run = async ({
77
97
  coverageTempDirectoryUrl,
78
98
  ).href;
79
99
  await ensureParentDirectories(coverageFileUrl);
80
- if (coverageEnabled) {
81
- result.coverageFileUrl = coverageFileUrl;
82
- // written within the child_process/worker_thread or during runtime.run()
83
- // for browsers
84
- // (because it takes time to serialize and transfer the coverage object)
85
- }
100
+ result.coverageFileUrl = coverageFileUrl;
101
+ // written within the child_process/worker_thread or during runtime.run()
102
+ // for browsers
103
+ // (because it takes time to serialize and transfer the coverage object)
86
104
  }
87
105
 
88
- const startMs = Date.now();
89
- callbacks.push(() => {
90
- result.duration = Date.now() - startMs;
91
- });
92
-
93
106
  try {
94
107
  logger.debug(`run() ${runtimeLabel}`);
95
108
  runOperation.throwIfAborted();
@@ -103,17 +116,29 @@ export const run = async ({
103
116
  };
104
117
  },
105
118
  runned: async (cb) => {
119
+ let runtimeStatus = "starting";
106
120
  try {
107
121
  const runResult = await runtime.run({
108
122
  signal: runOperation.signal,
109
123
  logger,
110
124
  ...runtimeParams,
111
125
  collectConsole,
126
+ measureMemoryUsage,
127
+ onMeasureMemoryAvailable,
112
128
  collectPerformance,
113
129
  coverageFileUrl,
114
130
  keepRunning,
115
131
  stopSignal,
116
132
  onConsole: (log) => onConsoleRef.current(log),
133
+ onRuntimeStarted: () => {
134
+ runtimeStatus = "started";
135
+ result.timings.runtimeStart = takeTiming();
136
+ },
137
+ onRuntimeStopped: () => {
138
+ if (runtimeStatus === "stopped") return; // ignore double calls
139
+ runtimeStatus = "stopped";
140
+ result.timings.runtimeEnd = takeTiming();
141
+ },
117
142
  });
118
143
  cb(runResult);
119
144
  } catch (e) {
@@ -131,13 +156,35 @@ export const run = async ({
131
156
  if (winner.name === "aborted") {
132
157
  runOperation.throwIfAborted();
133
158
  }
134
- const { status, namespace, errors, performance } = winner.data;
159
+ const {
160
+ status,
161
+ errors,
162
+ namespace,
163
+ timings = {},
164
+ memoryUsage,
165
+ performance,
166
+ } = winner.data;
135
167
  result.status = status;
136
- result.errors.push(...errors);
168
+ for (let error of errors) {
169
+ const errorProxy = normalizeRuntimeError(error);
170
+ result.errors.push(errorProxy);
171
+ }
137
172
  result.namespace = namespace;
138
- if (collectPerformance) {
139
- result.performance = performance;
173
+ if (timings) {
174
+ if (timings.start) {
175
+ result.timings.executionStart = Math.max(takeTiming(timings.start), 0);
176
+ }
177
+ if (timings.end) {
178
+ result.timings.executionEnd = Math.max(takeTiming(timings.end), 0);
179
+ }
140
180
  }
181
+ result.memoryUsage =
182
+ typeof memoryUsage === "number"
183
+ ? memoryUsage < 0
184
+ ? 0
185
+ : memoryUsage
186
+ : memoryUsage;
187
+ result.performance = performance;
141
188
  } catch (e) {
142
189
  if (Abort.isAbortError(e)) {
143
190
  if (timeoutAbortSource && timeoutAbortSource.signal.aborted) {
@@ -151,10 +198,25 @@ export const run = async ({
151
198
  }
152
199
  } finally {
153
200
  await runOperation.end();
201
+ result.timings.end = takeTiming();
202
+ return result;
154
203
  }
155
-
156
- callbacks.forEach((callback) => {
157
- callback();
158
- });
159
- return result;
204
+ };
205
+ const normalizeRuntimeError = (runtimeError) => {
206
+ // the goal here is to obtain a "regular error"
207
+ // that can be thrown using "throw" keyword
208
+ // so we wrap the error hapenning inside the runtime
209
+ // into "errorProxy" and put .stack property on it
210
+ // the other properties are set by defineProperty so they are not enumerable
211
+ // otherwise they would pollute the error displayed by Node.js
212
+ const errorProxy = new Error(runtimeError.message);
213
+ const exception = createException(runtimeError);
214
+ for (const ownPropertyName of Object.getOwnPropertyNames(exception)) {
215
+ Object.defineProperty(errorProxy, ownPropertyName, {
216
+ writable: true,
217
+ configurable: true,
218
+ value: runtimeError[ownPropertyName],
219
+ });
220
+ }
221
+ return errorProxy;
160
222
  };
@@ -5,7 +5,7 @@ import { pingServer } from "../../helpers/ping_server.js";
5
5
 
6
6
  export const startServerUsingCommand = async (
7
7
  webServer,
8
- { signal, allocatedMs, logger, teardown },
8
+ { signal, allocatedMs, logger, teardownCallbackSet },
9
9
  ) => {
10
10
  const spawnedProcess = spawn(webServer.command, [], {
11
11
  // On non-windows platforms, `detached: true` makes child process a leader of a new
@@ -92,7 +92,7 @@ export const startServerUsingCommand = async (
92
92
  startOperation.addAbortSignal(signal);
93
93
  const timeoutAbortSource = startOperation.timeout(allocatedMs);
94
94
  startOperation.addAbortCallback(killProcess);
95
- teardown.addCallback(killProcess);
95
+ teardownCallbackSet.add(killProcess);
96
96
 
97
97
  const startedPromise = (async () => {
98
98
  const logScale = [100, 250, 500];
@@ -7,7 +7,7 @@ import { basicFetch } from "../helpers/basic_fetch.js";
7
7
 
8
8
  export const assertAndNormalizeWebServer = async (
9
9
  webServer,
10
- { signal, logger, teardown },
10
+ { signal, logger, teardownCallbackSet },
11
11
  ) => {
12
12
  if (!webServer) {
13
13
  throw new TypeError(
@@ -35,7 +35,7 @@ export const assertAndNormalizeWebServer = async (
35
35
  }
36
36
  await ensureWebServerIsStarted(webServer, {
37
37
  signal,
38
- teardown,
38
+ teardownCallbackSet,
39
39
  logger,
40
40
  });
41
41
  const { headers } = await basicFetch(webServer.origin, {
@@ -69,7 +69,7 @@ export const assertAndNormalizeWebServer = async (
69
69
 
70
70
  export const ensureWebServerIsStarted = async (
71
71
  webServer,
72
- { signal, teardown, logger, allocatedMs = 5_000 },
72
+ { signal, teardownCallbackSet, logger, allocatedMs = 5_000 },
73
73
  ) => {
74
74
  const aServerIsListening = await pingServer(webServer.origin);
75
75
  if (aServerIsListening) {
@@ -79,7 +79,7 @@ export const ensureWebServerIsStarted = async (
79
79
  await startServerUsingModuleUrl(webServer, {
80
80
  signal,
81
81
  allocatedMs,
82
- teardown,
82
+ teardownCallbackSet,
83
83
  logger,
84
84
  });
85
85
  return;
@@ -88,7 +88,7 @@ export const ensureWebServerIsStarted = async (
88
88
  await startServerUsingCommand(webServer, {
89
89
  signal,
90
90
  allocatedMs,
91
- teardown,
91
+ teardownCallbackSet,
92
92
  logger,
93
93
  });
94
94
  return;
@@ -0,0 +1,142 @@
1
+ // https://gist.github.com/GaetanoPiazzolla/c40e1ebb9f709d091208e89baf9f4e00
2
+
3
+ import { cpus } from "node:os";
4
+
5
+ export const startMeasuringCpuUsage = () => {
6
+ let previousCpuArray = cpus();
7
+ let previousMs = Date.now();
8
+
9
+ const overall = {
10
+ inactive: 100,
11
+ active: 0,
12
+ system: 0,
13
+ user: 0,
14
+ };
15
+ const details = previousCpuArray.map(() => {
16
+ return {
17
+ inactive: 100,
18
+ active: 0,
19
+ system: 0,
20
+ user: 0,
21
+ };
22
+ });
23
+
24
+ const samples = [];
25
+ const interval = setInterval(() => {
26
+ let cpuArray = cpus();
27
+ const ms = Date.now();
28
+ const ellapsedMs = ms - previousMs;
29
+ const cpuUsagesSample = [];
30
+ let overallSystemMs = 0;
31
+ let overallUserMs = 0;
32
+ let overallInactiveMs = 0;
33
+ let overallActiveMs = 0;
34
+ let overallMsEllapsed = 0;
35
+ let index = 0;
36
+ for (const cpu of cpuArray) {
37
+ const previousCpuTimes = previousCpuArray[index].times;
38
+ const cpuTimes = cpu.times;
39
+ const systemMs = cpuTimes.sys - previousCpuTimes.sys;
40
+ const userMs = cpuTimes.user - previousCpuTimes.user;
41
+ const activeMs = systemMs + userMs;
42
+ const inactiveMs = ellapsedMs - activeMs;
43
+ const cpuUsageSample = {
44
+ inactive: inactiveMs / ellapsedMs,
45
+ active: activeMs / ellapsedMs,
46
+ system: systemMs / ellapsedMs,
47
+ user: userMs / ellapsedMs,
48
+ };
49
+ cpuUsagesSample.push(cpuUsageSample);
50
+
51
+ overallSystemMs += systemMs;
52
+ overallUserMs += userMs;
53
+ overallInactiveMs += inactiveMs;
54
+ overallActiveMs += activeMs;
55
+ overallMsEllapsed += ellapsedMs;
56
+ index++;
57
+ }
58
+ const overallUsageSample = {
59
+ inactive: overallInactiveMs / overallMsEllapsed,
60
+ active: overallActiveMs / overallMsEllapsed,
61
+ system: overallSystemMs / overallMsEllapsed,
62
+ user: overallUserMs / overallMsEllapsed,
63
+ };
64
+ previousCpuArray = cpuArray;
65
+ previousMs = ms;
66
+ samples.push({
67
+ cpuUsagesSample,
68
+ overallUsageSample,
69
+ });
70
+
71
+ if (samples.length === 10) {
72
+ let index = 0;
73
+ for (const detail of details) {
74
+ let systemSum = 0;
75
+ let userSum = 0;
76
+ let inactiveSum = 0;
77
+ let activeSum = 0;
78
+ for (const sample of samples) {
79
+ const { cpuUsagesSample } = sample;
80
+ const cpuUsageSample = cpuUsagesSample[index];
81
+ inactiveSum += cpuUsageSample.inactive;
82
+ activeSum += cpuUsageSample.active;
83
+ systemSum += cpuUsageSample.system;
84
+ userSum += cpuUsageSample.user;
85
+ }
86
+ Object.assign(detail, {
87
+ inactive: inactiveSum / samples.length,
88
+ active: activeSum / samples.length,
89
+ system: systemSum / samples.length,
90
+ user: userSum / samples.length,
91
+ });
92
+ index++;
93
+ }
94
+
95
+ let overallSystemSum = 0;
96
+ let overallUserSum = 0;
97
+ let overallInactiveSum = 0;
98
+ let overallActiveSum = 0;
99
+ for (const sample of samples) {
100
+ const { overallUsageSample } = sample;
101
+ overallSystemSum += overallUsageSample.system;
102
+ overallUserSum += overallUsageSample.user;
103
+ overallInactiveSum += overallUsageSample.inactive;
104
+ overallActiveSum += overallUsageSample.active;
105
+ }
106
+ Object.assign(overall, {
107
+ inactive: overallInactiveSum / samples.length,
108
+ active: overallActiveSum / samples.length,
109
+ system: overallSystemSum / samples.length,
110
+ user: overallUserSum / samples.length,
111
+ });
112
+ // console.log(formatUsage(globalUsageInfo));
113
+ samples.length = 0;
114
+ }
115
+ }, 15);
116
+ interval.unref();
117
+
118
+ return {
119
+ overall,
120
+ details,
121
+ stop: () => {
122
+ clearInterval(interval);
123
+ },
124
+ };
125
+ };
126
+
127
+ export const formatUsage = (usageInfo) => {
128
+ const { active, inactive, system, user } = usageInfo;
129
+
130
+ return `${ratioAsPercentage(active)} (system: ${ratioAsPercentage(
131
+ system,
132
+ )}, user: ${ratioAsPercentage(user)}), inactive: ${ratioAsPercentage(
133
+ inactive,
134
+ )}`;
135
+ };
136
+
137
+ export const ratioAsPercentage = (ratio) => {
138
+ const percentageAsNumber = ratio * 100;
139
+ const percentageAsNumberRounded = Math.round(percentageAsNumber);
140
+ const percentage = `${percentageAsNumberRounded}%`;
141
+ return percentage;
142
+ };
@@ -0,0 +1,7 @@
1
+ import { startMeasuringCpuUsage, formatUsage } from "./cpu_usage.js";
2
+
3
+ const cpuUsage = startMeasuringCpuUsage();
4
+
5
+ setInterval(() => {
6
+ console.log(formatUsage(cpuUsage.overall));
7
+ }, 400);
package/src/main.js CHANGED
@@ -6,5 +6,13 @@ export { firefox, firefoxIsolatedTab } from "./runtime_browsers/firefox.js";
6
6
  export { webkit, webkitIsolatedTab } from "./runtime_browsers/webkit.js";
7
7
  export { nodeChildProcess } from "./runtime_node/node_child_process.js";
8
8
  export { nodeWorkerThread } from "./runtime_node/node_worker_thread.js";
9
+ // coverage
10
+ export { reportCoverageInConsole } from "./coverage/report_coverage_in_console.js";
11
+ export { reportCoverageAsJson } from "./coverage/report_coverage_as_json.js";
12
+ export { reportCoverageAsHtml } from "./coverage/report_coverage_as_html.js";
13
+ // reporters
14
+ export { reporterList } from "./execution/reporters/reporter_list.js";
15
+ // other
16
+ export { reportAsJunitXml } from "./execution/junit_xml_file/report_as_junit_xml.js";
9
17
  // advanced
10
18
  export { execute } from "./execution/execute.js";
@@ -14,10 +14,8 @@ export const chromiumIsolatedTab = (params) => {
14
14
  const createChromiumRuntine = (params) => {
15
15
  return createRuntimeUsingPlaywright({
16
16
  browserName: "chromium",
17
- // browserVersion will be set by "browser._initializer.version"
18
- // see also https://github.com/microsoft/playwright/releases
19
- browserVersion: "unset",
20
17
  coveragePlaywrightAPIAvailable: true,
18
+ memoryUsageAPIAvailable: true,
21
19
  ...params,
22
20
  });
23
21
  };
@@ -34,9 +34,6 @@ To ignore potential flakyness, use disableOnWindowsBecauseFlaky: false`,
34
34
 
35
35
  return createRuntimeUsingPlaywright({
36
36
  browserName: "firefox",
37
- // browserVersion will be set by "browser._initializer.version"
38
- // see also https://github.com/microsoft/playwright/releases
39
- browserVersion: "unset",
40
37
  isolatedTab: true,
41
38
  ...params,
42
39
  });
@@ -7,10 +7,10 @@ import { babelPluginInstrument } from "../coverage/babel_plugin_instrument.js";
7
7
 
8
8
  export const initIstanbulMiddleware = async (
9
9
  page,
10
- { webServer, rootDirectoryUrl, coverageConfig },
10
+ { webServer, rootDirectoryUrl, coverageInclude },
11
11
  ) => {
12
12
  const associations = URL_META.resolveAssociations(
13
- { cover: coverageConfig },
13
+ { cover: coverageInclude },
14
14
  rootDirectoryUrl,
15
15
  );
16
16
  await page.route("**", async (route) => {