@jsenv/test 3.7.54 → 3.7.56

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.
@@ -5731,6 +5731,11 @@ const isV8Coverage = (coverage) => Boolean(coverage.result);
5731
5731
  *
5732
5732
  * Only the start order is affected: executions keep the index they got from the
5733
5733
  * filesystem, so they are still reported in that order.
5734
+ *
5735
+ * The duration of the whole run is remembered too, so the logs can tell how
5736
+ * long the wait will likely be. It is kept per run name (what the run was
5737
+ * narrowed to): the duration of every test in the repository says nothing about
5738
+ * the duration of the tests of one package.
5734
5739
  */
5735
5740
 
5736
5741
 
@@ -5739,10 +5744,24 @@ const MS_IN_A_DAY = 24 * 60 * 60 * 1000;
5739
5744
  const ENTRY_MAX_AGE_MS = 30 * MS_IN_A_DAY;
5740
5745
 
5741
5746
  const createExecutionTimings = ({ fileUrl }) => {
5742
- const previousEntryMap = readEntries(fileUrl);
5747
+ const { previousEntryMap, previousRunEntryMap } = readMemory(fileUrl);
5743
5748
  const entryMap = new Map();
5749
+ const runEntryMap = new Map();
5744
5750
 
5745
5751
  return {
5752
+ getRunDurationMs: (runName) => {
5753
+ const previousRunEntry = previousRunEntryMap.get(runName);
5754
+ if (!previousRunEntry) {
5755
+ return null;
5756
+ }
5757
+ return previousRunEntry.durationMs;
5758
+ },
5759
+ recordRun: (runName, durationMs) => {
5760
+ runEntryMap.set(runName, {
5761
+ durationMs,
5762
+ updatedAt: Date.now(),
5763
+ });
5764
+ },
5746
5765
  getAllocatedMsRequested: (executionName) => {
5747
5766
  const previousEntry = previousEntryMap.get(executionName);
5748
5767
  if (!previousEntry) {
@@ -5803,47 +5822,69 @@ const createExecutionTimings = ({ fileUrl }) => {
5803
5822
  },
5804
5823
  write: () => {
5805
5824
  const nowMs = Date.now();
5806
- const executions = {};
5807
- // executions not part of this run keep what was known about them
5808
- for (const [executionName, entry] of previousEntryMap) {
5809
- if (nowMs - entry.updatedAt < ENTRY_MAX_AGE_MS) {
5810
- executions[executionName] = entry;
5811
- }
5812
- }
5813
- for (const [executionName, entry] of entryMap) {
5814
- executions[executionName] = entry;
5815
- }
5816
- writeFileSync(fileUrl, JSON.stringify({ executions }, null, " "));
5825
+ writeFileSync(
5826
+ fileUrl,
5827
+ JSON.stringify(
5828
+ {
5829
+ // what is not part of this run keeps what was known about it
5830
+ executions: mergeEntries(previousEntryMap, entryMap, nowMs),
5831
+ runs: mergeEntries(previousRunEntryMap, runEntryMap, nowMs),
5832
+ },
5833
+ null,
5834
+ " ",
5835
+ ),
5836
+ );
5817
5837
  },
5818
5838
  };
5819
5839
  };
5820
5840
 
5821
- const readEntries = (fileUrl) => {
5822
- const entryMap = new Map();
5841
+ const mergeEntries = (previousEntryMap, entryMap, nowMs) => {
5842
+ const entries = {};
5843
+ for (const [name, entry] of previousEntryMap) {
5844
+ if (nowMs - entry.updatedAt < ENTRY_MAX_AGE_MS) {
5845
+ entries[name] = entry;
5846
+ }
5847
+ }
5848
+ for (const [name, entry] of entryMap) {
5849
+ entries[name] = entry;
5850
+ }
5851
+ return entries;
5852
+ };
5853
+
5854
+ const readMemory = (fileUrl) => {
5855
+ const previousEntryMap = new Map();
5856
+ const previousRunEntryMap = new Map();
5857
+ const memory = { previousEntryMap, previousRunEntryMap };
5823
5858
  let fileContent;
5824
5859
  try {
5825
5860
  fileContent = readFileSync(new URL(fileUrl), "utf8");
5826
5861
  } catch {
5827
5862
  // no memory of a previous run: every execution is an unknown
5828
- return entryMap;
5863
+ return memory;
5829
5864
  }
5830
5865
  let executions;
5866
+ let runs;
5831
5867
  try {
5832
- ({ executions } = JSON.parse(fileContent));
5868
+ ({ executions, runs } = JSON.parse(fileContent));
5833
5869
  } catch {
5834
5870
  // file was truncated by a run killed while writing it
5835
- return entryMap;
5871
+ return memory;
5836
5872
  }
5837
- if (!executions) {
5838
- return entryMap;
5873
+ fillEntryMap(previousEntryMap, executions);
5874
+ fillEntryMap(previousRunEntryMap, runs);
5875
+ return memory;
5876
+ };
5877
+
5878
+ const fillEntryMap = (entryMap, entries) => {
5879
+ if (!entries) {
5880
+ return;
5839
5881
  }
5840
- for (const executionName of Object.keys(executions)) {
5841
- const entry = executions[executionName];
5882
+ for (const name of Object.keys(entries)) {
5883
+ const entry = entries[name];
5842
5884
  if (typeof entry.durationMs === "number") {
5843
- entryMap.set(executionName, entry);
5885
+ entryMap.set(name, entry);
5844
5886
  }
5845
5887
  }
5846
- return entryMap;
5847
5888
  };
5848
5889
 
5849
5890
  const githubAnnotationFromError = (
@@ -6486,11 +6527,19 @@ const reporterList = ({
6486
6527
 
6487
6528
  const msEllapsed = Date.now() - startMs;
6488
6529
  const infos = [];
6489
- const duration = humanizeDuration(msEllapsed, {
6530
+ let duration = humanizeDuration(msEllapsed, {
6490
6531
  short: true,
6491
6532
  decimals: 0,
6492
6533
  rounded: false,
6493
6534
  });
6535
+ const { previousRunEnd } = testPlanResult.timings;
6536
+ if (previousRunEnd !== null) {
6537
+ duration += ` of ~${humanizeDuration(previousRunEnd, {
6538
+ short: true,
6539
+ decimals: 0,
6540
+ rounded: false,
6541
+ })}`;
6542
+ }
6494
6543
  infos.push(ANSI.color(duration, ANSI.GREY));
6495
6544
  let memoryUsageColor = ANSI.GREY;
6496
6545
  const memoryHeapUsed = memoryUsage().heapUsed;
@@ -6680,6 +6729,16 @@ const renderIntro = (testPlanResult, logOptions) => {
6680
6729
  `fragment: ${testPlanResult.fragment}, executing only ${testPlanResult.fragmentStart}:${testPlanResult.fragmentEnd}`,
6681
6730
  );
6682
6731
  }
6732
+ const { previousRunEnd } = testPlanResult.timings;
6733
+ if (previousRunEnd !== null) {
6734
+ lines.push(
6735
+ `previous run: ${
6736
+ logOptions.mockFluctuatingValues
6737
+ ? "<mock>s"
6738
+ : humanizeDuration(previousRunEnd, { short: true })
6739
+ }`,
6740
+ );
6741
+ }
6683
6742
  if (logOptions.platformInfo) {
6684
6743
  {
6685
6744
  let osLine = `os: `;
@@ -7866,7 +7925,7 @@ const ensureWebServerIsStarted = async (
7866
7925
  * @param {Object} [testPlanParameters.webServer] Web server info; required when executing test on browsers
7867
7926
  * @param {Object} testPlanParameters.testPlan Object associating files with runtimes where they will be executed
7868
7927
  * @param {Object|false} [testPlanParameters.parallel] Maximum amount of execution running at the same time
7869
- * @param {Object|false} [testPlanParameters.executionTimings=false] Remembers how long executions took; on the next run "parallel.maxAhead" uses it to start the longest ones ahead of their turn
7928
+ * @param {Object|false} [testPlanParameters.executionTimings=false] Remembers how long executions took; on the next run "parallel.maxAhead" uses it to start the longest ones ahead of their turn, and the logs show how long the previous run took
7870
7929
  * @param {number} [testPlanParameters.defaultMsAllocatedPerExecution=30000] Milliseconds after which execution is aborted and considered as failed by timeout
7871
7930
  * @param {boolean} [testPlanParameters.failFast=false] Fails immediatly when a test execution fails
7872
7931
  * @param {Object|false} [testPlanParameters.coverage=false] Controls if coverage is collected during files executions
@@ -8047,6 +8106,8 @@ const executeTestPlan = async ({
8047
8106
  teardownEnd: null,
8048
8107
  coverageTeardownEnd: null,
8049
8108
  end: null,
8109
+ // "end" of the previous run with the same name (see "executionTimings")
8110
+ previousRunEnd: null,
8050
8111
  },
8051
8112
  rootDirectoryUrl: String(rootDirectoryUrl),
8052
8113
  patterns: Object.keys(testPlan),
@@ -8126,6 +8187,7 @@ const executeTestPlan = async ({
8126
8187
  const afterAllCallbackSet = new Set();
8127
8188
  let finalizeCoverage;
8128
8189
  let timingsMemory;
8190
+ let runName;
8129
8191
 
8130
8192
  try {
8131
8193
  let logger;
@@ -8570,6 +8632,13 @@ To fix this warning:
8570
8632
  onlyWithinUrl = new URL(onlyWithin, rootDirectoryUrl);
8571
8633
  }
8572
8634
  }
8635
+ // what the run is narrowed to decides how long it takes
8636
+ runName = onlyWithinUrl
8637
+ ? urlToRelativeUrl(onlyWithinUrl, rootDirectoryUrl) || "./"
8638
+ : "./";
8639
+ if (fragment) {
8640
+ runName += `?fragment=${fragment}`;
8641
+ }
8573
8642
 
8574
8643
  let index = 0;
8575
8644
  let lastExecution;
@@ -8770,6 +8839,9 @@ To fix this warning:
8770
8839
  countersInOrder.remaining =
8771
8840
  countersInOrder.waiting =
8772
8841
  executionPlanifiedArray.length;
8842
+ if (timingsMemory) {
8843
+ timings.previousRunEnd = timingsMemory.getRunDurationMs(runName);
8844
+ }
8773
8845
  if (githubCheck) {
8774
8846
  const githubCheckRun = await startGithubCheckRun({
8775
8847
  logLevel: githubCheck.logLevel,
@@ -9186,16 +9258,21 @@ To fix this warning:
9186
9258
  }
9187
9259
  timings.teardownEnd = takeTiming();
9188
9260
 
9189
- if (timingsMemory) {
9190
- timingsMemory.write();
9191
- }
9192
-
9193
9261
  if (finalizeCoverage) {
9194
9262
  await finalizeCoverage();
9195
9263
  }
9196
9264
  timings.coverageTeardownEnd = takeTiming();
9197
9265
  timings.end = takeTiming();
9198
9266
 
9267
+ if (timingsMemory) {
9268
+ // a run stopped before its end (aborted, failFast, an error) says nothing
9269
+ // about how long the next one takes
9270
+ if (timings.executionEnd !== null && !testPlanResult.aborted) {
9271
+ timingsMemory.recordRun(runName, timings.end);
9272
+ }
9273
+ timingsMemory.write();
9274
+ }
9275
+
9199
9276
  osMemoryUsageMonitoring.end();
9200
9277
  processMemoryUsageMonitoring.end();
9201
9278
  osCpuUsageMonitoring.end();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/test",
3
- "version": "3.7.54",
3
+ "version": "3.7.56",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -31,9 +31,9 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "@c88/v8-coverage": "0.1.1",
34
- "@jsenv/ast": "6.10.12",
35
- "@jsenv/plugin-supervisor": "1.8.31",
36
- "@jsenv/sourcemap": "1.4.15",
34
+ "@jsenv/ast": "6.10.13",
35
+ "@jsenv/plugin-supervisor": "1.8.32",
36
+ "@jsenv/sourcemap": "1.4.16",
37
37
  "he": "1.2.0",
38
38
  "istanbul-lib-coverage": "3.2.2",
39
39
  "istanbul-lib-instrument": "6.0.3",
@@ -27,7 +27,7 @@ import {
27
27
  startMonitoringMemoryUsage,
28
28
  } from "@jsenv/os-metrics";
29
29
  import { URL_META } from "@jsenv/url-meta";
30
- import { urlIsOrIsInsideOf } from "@jsenv/urls";
30
+ import { urlIsOrIsInsideOf, urlToRelativeUrl } from "@jsenv/urls";
31
31
  import { existsSync } from "node:fs";
32
32
  import { takeCoverage } from "node:v8";
33
33
  import stripAnsi from "strip-ansi";
@@ -47,7 +47,7 @@ import { assertAndNormalizeWebServer } from "./web_server_param.js";
47
47
  * @param {Object} [testPlanParameters.webServer] Web server info; required when executing test on browsers
48
48
  * @param {Object} testPlanParameters.testPlan Object associating files with runtimes where they will be executed
49
49
  * @param {Object|false} [testPlanParameters.parallel] Maximum amount of execution running at the same time
50
- * @param {Object|false} [testPlanParameters.executionTimings=false] Remembers how long executions took; on the next run "parallel.maxAhead" uses it to start the longest ones ahead of their turn
50
+ * @param {Object|false} [testPlanParameters.executionTimings=false] Remembers how long executions took; on the next run "parallel.maxAhead" uses it to start the longest ones ahead of their turn, and the logs show how long the previous run took
51
51
  * @param {number} [testPlanParameters.defaultMsAllocatedPerExecution=30000] Milliseconds after which execution is aborted and considered as failed by timeout
52
52
  * @param {boolean} [testPlanParameters.failFast=false] Fails immediatly when a test execution fails
53
53
  * @param {Object|false} [testPlanParameters.coverage=false] Controls if coverage is collected during files executions
@@ -228,6 +228,8 @@ export const executeTestPlan = async ({
228
228
  teardownEnd: null,
229
229
  coverageTeardownEnd: null,
230
230
  end: null,
231
+ // "end" of the previous run with the same name (see "executionTimings")
232
+ previousRunEnd: null,
231
233
  },
232
234
  rootDirectoryUrl: String(rootDirectoryUrl),
233
235
  patterns: Object.keys(testPlan),
@@ -307,6 +309,7 @@ export const executeTestPlan = async ({
307
309
  const afterAllCallbackSet = new Set();
308
310
  let finalizeCoverage;
309
311
  let timingsMemory;
312
+ let runName;
310
313
 
311
314
  try {
312
315
  let logger;
@@ -751,6 +754,13 @@ To fix this warning:
751
754
  onlyWithinUrl = new URL(onlyWithin, rootDirectoryUrl);
752
755
  }
753
756
  }
757
+ // what the run is narrowed to decides how long it takes
758
+ runName = onlyWithinUrl
759
+ ? urlToRelativeUrl(onlyWithinUrl, rootDirectoryUrl) || "./"
760
+ : "./";
761
+ if (fragment) {
762
+ runName += `?fragment=${fragment}`;
763
+ }
754
764
 
755
765
  let index = 0;
756
766
  let lastExecution;
@@ -951,6 +961,9 @@ To fix this warning:
951
961
  countersInOrder.remaining =
952
962
  countersInOrder.waiting =
953
963
  executionPlanifiedArray.length;
964
+ if (timingsMemory) {
965
+ timings.previousRunEnd = timingsMemory.getRunDurationMs(runName);
966
+ }
954
967
  if (githubCheck) {
955
968
  const githubCheckRun = await startGithubCheckRun({
956
969
  logLevel: githubCheck.logLevel,
@@ -1368,16 +1381,21 @@ To fix this warning:
1368
1381
  }
1369
1382
  timings.teardownEnd = takeTiming();
1370
1383
 
1371
- if (timingsMemory) {
1372
- timingsMemory.write();
1373
- }
1374
-
1375
1384
  if (finalizeCoverage) {
1376
1385
  await finalizeCoverage();
1377
1386
  }
1378
1387
  timings.coverageTeardownEnd = takeTiming();
1379
1388
  timings.end = takeTiming();
1380
1389
 
1390
+ if (timingsMemory) {
1391
+ // a run stopped before its end (aborted, failFast, an error) says nothing
1392
+ // about how long the next one takes
1393
+ if (timings.executionEnd !== null && !testPlanResult.aborted) {
1394
+ timingsMemory.recordRun(runName, timings.end);
1395
+ }
1396
+ timingsMemory.write();
1397
+ }
1398
+
1381
1399
  osMemoryUsageMonitoring.end();
1382
1400
  processMemoryUsageMonitoring.end();
1383
1401
  osCpuUsageMonitoring.end();
@@ -10,6 +10,11 @@
10
10
  *
11
11
  * Only the start order is affected: executions keep the index they got from the
12
12
  * filesystem, so they are still reported in that order.
13
+ *
14
+ * The duration of the whole run is remembered too, so the logs can tell how
15
+ * long the wait will likely be. It is kept per run name (what the run was
16
+ * narrowed to): the duration of every test in the repository says nothing about
17
+ * the duration of the tests of one package.
13
18
  */
14
19
 
15
20
  import { writeFileSync } from "@jsenv/filesystem";
@@ -20,10 +25,24 @@ const MS_IN_A_DAY = 24 * 60 * 60 * 1000;
20
25
  const ENTRY_MAX_AGE_MS = 30 * MS_IN_A_DAY;
21
26
 
22
27
  export const createExecutionTimings = ({ fileUrl }) => {
23
- const previousEntryMap = readEntries(fileUrl);
28
+ const { previousEntryMap, previousRunEntryMap } = readMemory(fileUrl);
24
29
  const entryMap = new Map();
30
+ const runEntryMap = new Map();
25
31
 
26
32
  return {
33
+ getRunDurationMs: (runName) => {
34
+ const previousRunEntry = previousRunEntryMap.get(runName);
35
+ if (!previousRunEntry) {
36
+ return null;
37
+ }
38
+ return previousRunEntry.durationMs;
39
+ },
40
+ recordRun: (runName, durationMs) => {
41
+ runEntryMap.set(runName, {
42
+ durationMs,
43
+ updatedAt: Date.now(),
44
+ });
45
+ },
27
46
  getAllocatedMsRequested: (executionName) => {
28
47
  const previousEntry = previousEntryMap.get(executionName);
29
48
  if (!previousEntry) {
@@ -84,45 +103,67 @@ export const createExecutionTimings = ({ fileUrl }) => {
84
103
  },
85
104
  write: () => {
86
105
  const nowMs = Date.now();
87
- const executions = {};
88
- // executions not part of this run keep what was known about them
89
- for (const [executionName, entry] of previousEntryMap) {
90
- if (nowMs - entry.updatedAt < ENTRY_MAX_AGE_MS) {
91
- executions[executionName] = entry;
92
- }
93
- }
94
- for (const [executionName, entry] of entryMap) {
95
- executions[executionName] = entry;
96
- }
97
- writeFileSync(fileUrl, JSON.stringify({ executions }, null, " "));
106
+ writeFileSync(
107
+ fileUrl,
108
+ JSON.stringify(
109
+ {
110
+ // what is not part of this run keeps what was known about it
111
+ executions: mergeEntries(previousEntryMap, entryMap, nowMs),
112
+ runs: mergeEntries(previousRunEntryMap, runEntryMap, nowMs),
113
+ },
114
+ null,
115
+ " ",
116
+ ),
117
+ );
98
118
  },
99
119
  };
100
120
  };
101
121
 
102
- const readEntries = (fileUrl) => {
103
- const entryMap = new Map();
122
+ const mergeEntries = (previousEntryMap, entryMap, nowMs) => {
123
+ const entries = {};
124
+ for (const [name, entry] of previousEntryMap) {
125
+ if (nowMs - entry.updatedAt < ENTRY_MAX_AGE_MS) {
126
+ entries[name] = entry;
127
+ }
128
+ }
129
+ for (const [name, entry] of entryMap) {
130
+ entries[name] = entry;
131
+ }
132
+ return entries;
133
+ };
134
+
135
+ const readMemory = (fileUrl) => {
136
+ const previousEntryMap = new Map();
137
+ const previousRunEntryMap = new Map();
138
+ const memory = { previousEntryMap, previousRunEntryMap };
104
139
  let fileContent;
105
140
  try {
106
141
  fileContent = readFileSync(new URL(fileUrl), "utf8");
107
142
  } catch {
108
143
  // no memory of a previous run: every execution is an unknown
109
- return entryMap;
144
+ return memory;
110
145
  }
111
146
  let executions;
147
+ let runs;
112
148
  try {
113
- ({ executions } = JSON.parse(fileContent));
149
+ ({ executions, runs } = JSON.parse(fileContent));
114
150
  } catch {
115
151
  // file was truncated by a run killed while writing it
116
- return entryMap;
152
+ return memory;
117
153
  }
118
- if (!executions) {
119
- return entryMap;
154
+ fillEntryMap(previousEntryMap, executions);
155
+ fillEntryMap(previousRunEntryMap, runs);
156
+ return memory;
157
+ };
158
+
159
+ const fillEntryMap = (entryMap, entries) => {
160
+ if (!entries) {
161
+ return;
120
162
  }
121
- for (const executionName of Object.keys(executions)) {
122
- const entry = executions[executionName];
163
+ for (const name of Object.keys(entries)) {
164
+ const entry = entries[name];
123
165
  if (typeof entry.durationMs === "number") {
124
- entryMap.set(executionName, entry);
166
+ entryMap.set(name, entry);
125
167
  }
126
168
  }
127
- return entryMap;
128
169
  };
@@ -137,11 +137,19 @@ export const reporterList = ({
137
137
 
138
138
  const msEllapsed = Date.now() - startMs;
139
139
  const infos = [];
140
- const duration = humanizeDuration(msEllapsed, {
140
+ let duration = humanizeDuration(msEllapsed, {
141
141
  short: true,
142
142
  decimals: 0,
143
143
  rounded: false,
144
144
  });
145
+ const { previousRunEnd } = testPlanResult.timings;
146
+ if (previousRunEnd !== null) {
147
+ duration += ` of ~${humanizeDuration(previousRunEnd, {
148
+ short: true,
149
+ decimals: 0,
150
+ rounded: false,
151
+ })}`;
152
+ }
145
153
  infos.push(ANSI.color(duration, ANSI.GREY));
146
154
  let memoryUsageColor = ANSI.GREY;
147
155
  const memoryHeapUsed = processMemoryUsage().heapUsed;
@@ -331,6 +339,16 @@ const renderIntro = (testPlanResult, logOptions) => {
331
339
  `fragment: ${testPlanResult.fragment}, executing only ${testPlanResult.fragmentStart}:${testPlanResult.fragmentEnd}`,
332
340
  );
333
341
  }
342
+ const { previousRunEnd } = testPlanResult.timings;
343
+ if (previousRunEnd !== null) {
344
+ lines.push(
345
+ `previous run: ${
346
+ logOptions.mockFluctuatingValues
347
+ ? "<mock>s"
348
+ : humanizeDuration(previousRunEnd, { short: true })
349
+ }`,
350
+ );
351
+ }
334
352
  if (logOptions.platformInfo) {
335
353
  os_line: {
336
354
  let osLine = `os: `;