@uniflowed/test 0.0.0-alpha.9 → 0.2.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/app-browser.js +6 -0
- package/app.js +175 -0
- package/browser-worker.js +334 -0
- package/browser.js +59 -0
- package/bun/index.js +305 -0
- package/in-source.js +169 -0
- package/index.js +11 -1
- package/internal/axe.js +362 -0
- package/internal/browser/cdp.js +364 -0
- package/internal/browser/node.js +375 -0
- package/internal/browser/page-transport.js +36 -0
- package/internal/browser/page.js +258 -0
- package/internal/browser/screenshots.js +102 -0
- package/internal/browser/server.js +893 -0
- package/internal/browser/transport.js +2 -0
- package/internal/expect.js +506 -100
- package/internal/frames.js +178 -0
- package/internal/isolation.js +231 -0
- package/internal/namespace.js +22 -10
- package/internal/native-globals.js +14 -0
- package/internal/native-host.js +97 -0
- package/internal/output.js +25 -7
- package/internal/registry.js +102 -17
- package/internal/run.js +159 -24
- package/internal/timers.js +2 -2
- package/package.json +41 -5
- package/worker.js +115 -10
package/worker.js
CHANGED
|
@@ -49,14 +49,29 @@
|
|
|
49
49
|
|
|
50
50
|
import * as output from "./internal/output.js";
|
|
51
51
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
52
|
+
import process from "node:process";
|
|
52
53
|
import { writeChangedSnapshots } from "./internal/snapshot.js";
|
|
53
54
|
import { createInterface } from "node:readline";
|
|
54
55
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
55
56
|
|
|
56
|
-
import {
|
|
57
|
-
import {
|
|
57
|
+
import { installInSourceTests } from "./in-source.js";
|
|
58
|
+
import { restoreSharedState } from "./internal/isolation.js";
|
|
58
59
|
import { run } from "./internal/run.js";
|
|
59
60
|
|
|
61
|
+
// `process` is imported rather than read off the global because this file is
|
|
62
|
+
// the process entry point, and an entry point that names what it depends on is
|
|
63
|
+
// one a reader does not have to trust.
|
|
64
|
+
//
|
|
65
|
+
// It used to be put *on* the global as well. Deno 1.x exposed the object as
|
|
66
|
+
// `node:process` and nothing as `globalThis.process`, and every module this
|
|
67
|
+
// worker reaches that wants the process reads the global: `internal/output.js`
|
|
68
|
+
// replaces `process.stdout.write`, `internal/namespace.js` reads
|
|
69
|
+
// `process.env` for `stubEnv`, and `internal/axe.js` reads `UF_AXE`. The Deno
|
|
70
|
+
// uf starts is 2.8 or newer — `@uniflowed/host/deno-preload` is built on that
|
|
71
|
+
// release's `registerHooks`, and `uf test` refuses anything older — and Deno 2
|
|
72
|
+
// has the global like Node and Bun. A shim for a host uf no longer starts would
|
|
73
|
+
// be a line claiming to be load-bearing while holding nothing up.
|
|
74
|
+
|
|
60
75
|
/** What `uf` sends for one file. */
|
|
61
76
|
type Request = {|
|
|
62
77
|
readonly file: string,
|
|
@@ -137,18 +152,80 @@ function write(event: { readonly [string]: mixed }): void {
|
|
|
137
152
|
* the `serving` store this runs inside — it is what every event written from
|
|
138
153
|
* this file, or from anything this file leaves behind, is stamped with.
|
|
139
154
|
*/
|
|
155
|
+
/**
|
|
156
|
+
* Whether this is a run of the benchmarks, `uf test --bench`.
|
|
157
|
+
*
|
|
158
|
+
* Read from the environment, as snapshot updates are: it is a property of the
|
|
159
|
+
* run, set once on the worker by `uf`, and not something each request says.
|
|
160
|
+
*/
|
|
161
|
+
function benching(): boolean {
|
|
162
|
+
const value = (globalThis: $FlowFixMe).process?.env?.UF_TEST_BENCH;
|
|
163
|
+
return value != null && value !== "" && value !== "0";
|
|
164
|
+
}
|
|
165
|
+
|
|
140
166
|
async function runFile(request: Request, generation: number): Promise<void> {
|
|
141
167
|
const started = performance.now();
|
|
142
|
-
|
|
143
|
-
//
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
|
|
168
|
+
// Everything the previous file changed and this package shares with it goes
|
|
169
|
+
// back: the registry, the stubbed environment and globals, the clock, the
|
|
170
|
+
// module stand-ins, and the document. What each of those is and why it is on
|
|
171
|
+
// the list is `internal/isolation.js`, which is one place rather than five
|
|
172
|
+
// calls here — a worker serves many files out of one process, `uf test` fans
|
|
173
|
+
// files across workers by size, and a leak therefore makes the *result* of a
|
|
174
|
+
// suite a function of the timings file rather than of the code under test.
|
|
175
|
+
// See ubugeeei-prod/uf#417, #581 and #607, which are that sentence three
|
|
176
|
+
// times over.
|
|
177
|
+
//
|
|
178
|
+
// Before the import rather than after the run, so a file that throws while
|
|
179
|
+
// loading still hands the next one a clean process.
|
|
180
|
+
restoreSharedState();
|
|
181
|
+
// Not a restore, and so not on that list: this is the output budget for the
|
|
182
|
+
// file about to run, rather than something the previous file left behind.
|
|
148
183
|
output.startFile();
|
|
149
184
|
|
|
185
|
+
// In-source blocks in *this* file get uf's test API; the same blocks in
|
|
186
|
+
// every module this file imports get `undefined` and do not register. See
|
|
187
|
+
// `./in-source.js` for why the marker is a call rather than a constant.
|
|
188
|
+
const url = pathToFileURL(request.file).href;
|
|
189
|
+
const uninstallInSourceTests = installInSourceTests(url);
|
|
190
|
+
try {
|
|
191
|
+
await runImportedFile(request, generation, url, started);
|
|
192
|
+
} finally {
|
|
193
|
+
// For the whole file rather than only for its import. A block reads the
|
|
194
|
+
// marker at module scope, but a *case body* may read it too — `const
|
|
195
|
+
// { uft } = import.meta.uf.test` inside an `it` is an ordinary thing to
|
|
196
|
+
// write — and a marker that answered during the import and not during the
|
|
197
|
+
// run would be a value that changed under the file that read it.
|
|
198
|
+
//
|
|
199
|
+
// Taking it away earlier would not buy the isolation it looks like it
|
|
200
|
+
// buys: work a finished file left behind can register through a binding it
|
|
201
|
+
// captured just as easily as through this global, so the thing that keeps
|
|
202
|
+
// a straggler out of the next file is the registry reset at the top of
|
|
203
|
+
// this function and the generation stamped on every event, not the
|
|
204
|
+
// lifetime of one accessor.
|
|
205
|
+
uninstallInSourceTests();
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* The half of [`runFile`] that has a file to run.
|
|
211
|
+
*
|
|
212
|
+
* Split out so the caller's `finally` covers the import *and* the run without
|
|
213
|
+
* either of the two `return`s below escaping it.
|
|
214
|
+
*/
|
|
215
|
+
async function runImportedFile(
|
|
216
|
+
request: Request,
|
|
217
|
+
generation: number,
|
|
218
|
+
url: string,
|
|
219
|
+
started: number,
|
|
220
|
+
): Promise<void> {
|
|
221
|
+
let closeNative: (() => Promise<void>) | null = null;
|
|
150
222
|
try {
|
|
151
|
-
|
|
223
|
+
if (process.env.UF_TEST_TARGET === "react-native") {
|
|
224
|
+
const { loadNativeFile } = await import("./internal/native-host.js");
|
|
225
|
+
closeNative = await loadNativeFile(request.file);
|
|
226
|
+
} else {
|
|
227
|
+
await import(`${url}?uf-run=${generation}`);
|
|
228
|
+
}
|
|
152
229
|
} catch (thrown) {
|
|
153
230
|
const error = thrown instanceof Error ? thrown : new Error(String(thrown));
|
|
154
231
|
write({
|
|
@@ -164,7 +241,12 @@ async function runFile(request: Request, generation: number): Promise<void> {
|
|
|
164
241
|
try {
|
|
165
242
|
const absolute = fileURLToPath(pathToFileURL(request.file).href);
|
|
166
243
|
await run(
|
|
167
|
-
{
|
|
244
|
+
{
|
|
245
|
+
filter: request.filter ?? null,
|
|
246
|
+
timeoutMs: request.timeoutMs,
|
|
247
|
+
file: absolute,
|
|
248
|
+
bench: benching(),
|
|
249
|
+
},
|
|
168
250
|
(result) => {
|
|
169
251
|
write({
|
|
170
252
|
event: "test",
|
|
@@ -180,12 +262,18 @@ async function runFile(request: Request, generation: number): Promise<void> {
|
|
|
180
262
|
// with forty snapshots would otherwise rewrite its snapshot file forty
|
|
181
263
|
// times, and a crash halfway through would leave a partial one.
|
|
182
264
|
writeChangedSnapshots();
|
|
265
|
+
await closeNative?.();
|
|
183
266
|
write({
|
|
184
267
|
event: "file",
|
|
185
268
|
status: "completed",
|
|
186
269
|
durationMicros: Math.round((performance.now() - started) * 1000),
|
|
187
270
|
});
|
|
188
271
|
} catch (thrown) {
|
|
272
|
+
try {
|
|
273
|
+
await closeNative?.();
|
|
274
|
+
} catch {
|
|
275
|
+
// Preserve the original run or teardown failure.
|
|
276
|
+
}
|
|
189
277
|
const error = thrown instanceof Error ? thrown : new Error(String(thrown));
|
|
190
278
|
write({
|
|
191
279
|
event: "file",
|
|
@@ -267,4 +355,21 @@ process.on("unhandledRejection", (reason: mixed) => {
|
|
|
267
355
|
process.exit(1);
|
|
268
356
|
});
|
|
269
357
|
|
|
358
|
+
// The same for an exception nothing caught — a server's `error` event with no
|
|
359
|
+
// listener, a throw from a timer callback. Node's default prints the stack to
|
|
360
|
+
// stderr and exits, and all `uf` could then say was that the worker had died:
|
|
361
|
+
// the file that did it went unnamed, and the message was somewhere above the
|
|
362
|
+
// report, unattributed. Reported the way a rejection is, it is the failure of
|
|
363
|
+
// the file whose work threw, with its message and stack in the file's result.
|
|
364
|
+
process.on("uncaughtException", (thrown: mixed) => {
|
|
365
|
+
const error = thrown instanceof Error ? thrown : new Error(String(thrown));
|
|
366
|
+
write({
|
|
367
|
+
event: "file",
|
|
368
|
+
status: "run-failed",
|
|
369
|
+
message: `uncaught exception: ${error.message}`,
|
|
370
|
+
stack: error.stack ?? null,
|
|
371
|
+
});
|
|
372
|
+
process.exit(1);
|
|
373
|
+
});
|
|
374
|
+
|
|
270
375
|
serve();
|