@irtio/testing 0.2.0 → 0.4.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/{chunk-6EHPS7A5.js → chunk-Q44LJTMZ.js} +39 -1
- package/dist/index.d.ts +38 -1
- package/dist/index.js +1 -1
- package/dist/matchers.js +1 -1
- package/package.json +6 -6
|
@@ -189,6 +189,10 @@ var TestHarness = class {
|
|
|
189
189
|
frameLeaks = [];
|
|
190
190
|
spatialSeen = /* @__PURE__ */ new Map();
|
|
191
191
|
rejectionLog = [];
|
|
192
|
+
/** Every handler throw the runtime caught, in order (bug 2). */
|
|
193
|
+
handlerErrorLog = [];
|
|
194
|
+
/** How many of them `reportHandlerErrors` has already raised. */
|
|
195
|
+
reportedErrors = 0;
|
|
192
196
|
/** Reconnects completed per client id — bumped in `handleHello` on a resumed `WELCOME`. */
|
|
193
197
|
reconnectCounts = /* @__PURE__ */ new Map();
|
|
194
198
|
/** Prediction accounting per client id, fed by each room's `correct` events. */
|
|
@@ -204,12 +208,14 @@ var TestHarness = class {
|
|
|
204
208
|
roomId;
|
|
205
209
|
writeIntervalMs;
|
|
206
210
|
latency;
|
|
211
|
+
failOnHandlerError;
|
|
207
212
|
stopped = false;
|
|
208
213
|
constructor(definition, options) {
|
|
209
214
|
this.definition = configure(definition, options);
|
|
210
215
|
this.roomId = options.roomId ?? "test-room";
|
|
211
216
|
this.latency = options.latency;
|
|
212
217
|
this.writeIntervalMs = options.writeIntervalMs;
|
|
218
|
+
this.failOnHandlerError = options.failOnHandlerError ?? true;
|
|
213
219
|
this.rng = new Mulberry32(options.seed ?? 1);
|
|
214
220
|
this.scheduler = clockScheduler(this.clock);
|
|
215
221
|
this.host = new HarnessHost(this.clock);
|
|
@@ -223,6 +229,13 @@ var TestHarness = class {
|
|
|
223
229
|
});
|
|
224
230
|
this.host.core = this.coreRef;
|
|
225
231
|
this.host.serializeForSave = () => this.coreRef.snapshot();
|
|
232
|
+
this.coreRef.onHandlerError = (handler, err) => {
|
|
233
|
+
this.handlerErrorLog.push({
|
|
234
|
+
handler,
|
|
235
|
+
message: err instanceof Error ? err.message : String(err),
|
|
236
|
+
tick: this.coreRef.tick
|
|
237
|
+
});
|
|
238
|
+
};
|
|
226
239
|
this.coreRef.start();
|
|
227
240
|
}
|
|
228
241
|
// -------------------------------------------------------------------------
|
|
@@ -243,6 +256,9 @@ var TestHarness = class {
|
|
|
243
256
|
get rejections() {
|
|
244
257
|
return this.rejectionLog;
|
|
245
258
|
}
|
|
259
|
+
get handlerErrors() {
|
|
260
|
+
return this.handlerErrorLog;
|
|
261
|
+
}
|
|
246
262
|
get dropped() {
|
|
247
263
|
return this.droppedFrames;
|
|
248
264
|
}
|
|
@@ -610,10 +626,32 @@ var TestHarness = class {
|
|
|
610
626
|
const before = this.activity;
|
|
611
627
|
await microtasks();
|
|
612
628
|
this.clock.advance(0);
|
|
613
|
-
if (this.activity === before) return;
|
|
629
|
+
if (this.activity === before) return this.reportHandlerErrors();
|
|
614
630
|
}
|
|
615
631
|
throw new Error("testRoom: the frame pump did not settle");
|
|
616
632
|
}
|
|
633
|
+
/**
|
|
634
|
+
* Bug 2: the runtime catches a throwing handler so the room stays up, which in a test means
|
|
635
|
+
* the failure arrives later, somewhere else, wearing a disguise (the one that found this was a
|
|
636
|
+
* frozen tick counter reported ten seconds on as a `t.until` timeout). Every clock method ends
|
|
637
|
+
* here, so the throw surfaces at the line that ran the tick that broke.
|
|
638
|
+
*/
|
|
639
|
+
reportHandlerErrors() {
|
|
640
|
+
if (!this.failOnHandlerError) return;
|
|
641
|
+
const fresh = this.handlerErrorLog.slice(this.reportedErrors);
|
|
642
|
+
this.reportedErrors = this.handlerErrorLog.length;
|
|
643
|
+
if (fresh.length === 0) return;
|
|
644
|
+
const lines = fresh.map((e) => ` ${e.handler} (tick ${e.tick}): ${e.message}`);
|
|
645
|
+
const what = fresh.length === 1 ? "a handler" : `${fresh.length} handlers`;
|
|
646
|
+
throw new Error(
|
|
647
|
+
[
|
|
648
|
+
`testRoom: the room threw in ${what}`,
|
|
649
|
+
...lines,
|
|
650
|
+
"The runtime caught it and the room kept running, which is what it does in production.",
|
|
651
|
+
"Pass `failOnHandlerError: false` to testRoom if a throwing handler is what this test is about."
|
|
652
|
+
].join("\n")
|
|
653
|
+
);
|
|
654
|
+
}
|
|
617
655
|
async tick(n = 1) {
|
|
618
656
|
for (let i = 0; i < n; i++) {
|
|
619
657
|
this.clock.advance(this.mode === "tick" ? this.intervalMs : 0);
|
package/dist/index.d.ts
CHANGED
|
@@ -58,6 +58,17 @@ interface TestRoomOptions {
|
|
|
58
58
|
readonly roomId?: string;
|
|
59
59
|
/** The clients' owned-write flush window, in ms of fake time. Default 50 (the client's own). */
|
|
60
60
|
readonly writeIntervalMs?: number;
|
|
61
|
+
/**
|
|
62
|
+
* Throw out of `t.tick` / `t.run` / `t.until` / `t.join` when a room handler throws. Default
|
|
63
|
+
* `true`.
|
|
64
|
+
*
|
|
65
|
+
* The runtime guards handlers on purpose: one bad `onJoin` must not take a live room down. In
|
|
66
|
+
* a test that same guarding hides the failure — the room limps on and the test fails ten
|
|
67
|
+
* seconds later on an unrelated `t.until` timeout, pointing at the wrong thing (bug 2). Set
|
|
68
|
+
* `false` for a test whose *subject* is a throwing handler; `t.handlerErrors` records them
|
|
69
|
+
* either way.
|
|
70
|
+
*/
|
|
71
|
+
readonly failOnHandlerError?: boolean;
|
|
61
72
|
}
|
|
62
73
|
interface TestJoinSpec<Role extends string = string> {
|
|
63
74
|
readonly role?: Role;
|
|
@@ -124,6 +135,13 @@ interface PredictionStats {
|
|
|
124
135
|
/** Total pending local writes re-applied over corrections. */
|
|
125
136
|
readonly replayed: number;
|
|
126
137
|
}
|
|
138
|
+
/** One handler throw the runtime caught and the room survived. See `failOnHandlerError`. */
|
|
139
|
+
interface HandlerError {
|
|
140
|
+
/** What was running: `tick`, `physics`, `onJoin`, `room timer`, `alarms.<name>`, … */
|
|
141
|
+
readonly handler: string;
|
|
142
|
+
readonly message: string;
|
|
143
|
+
readonly tick: number;
|
|
144
|
+
}
|
|
127
145
|
/** One `REPLY` that came back with `ok: false`, keyed by the rpc the client had called. */
|
|
128
146
|
interface RejectedCall {
|
|
129
147
|
readonly clientId: string;
|
|
@@ -141,6 +159,12 @@ interface TestRoom<S extends AnySchema> {
|
|
|
141
159
|
readonly trace: readonly TraceEntry[];
|
|
142
160
|
/** Error replies seen so far, for `toHaveRejected`. */
|
|
143
161
|
readonly rejections: readonly RejectedCall[];
|
|
162
|
+
/**
|
|
163
|
+
* Handler throws the runtime caught, in order. Empty in a healthy room; with the default
|
|
164
|
+
* `failOnHandlerError` the first one has already failed the test by the time you could read
|
|
165
|
+
* this, so it is for tests that opted out.
|
|
166
|
+
*/
|
|
167
|
+
readonly handlerErrors: readonly HandlerError[];
|
|
144
168
|
/** Frames the `latency.loss` die dropped. */
|
|
145
169
|
readonly dropped: number;
|
|
146
170
|
/** Escape hatch: the real `RoomCore`. */
|
|
@@ -296,6 +320,10 @@ declare class TestHarness<S extends AnySchema> implements TestRoom<S> {
|
|
|
296
320
|
private readonly frameLeaks;
|
|
297
321
|
private readonly spatialSeen;
|
|
298
322
|
private readonly rejectionLog;
|
|
323
|
+
/** Every handler throw the runtime caught, in order (bug 2). */
|
|
324
|
+
private readonly handlerErrorLog;
|
|
325
|
+
/** How many of them `reportHandlerErrors` has already raised. */
|
|
326
|
+
private reportedErrors;
|
|
299
327
|
/** Reconnects completed per client id — bumped in `handleHello` on a resumed `WELCOME`. */
|
|
300
328
|
private readonly reconnectCounts;
|
|
301
329
|
/** Prediction accounting per client id, fed by each room's `correct` events. */
|
|
@@ -311,6 +339,7 @@ declare class TestHarness<S extends AnySchema> implements TestRoom<S> {
|
|
|
311
339
|
private readonly roomId;
|
|
312
340
|
private readonly writeIntervalMs;
|
|
313
341
|
private readonly latency;
|
|
342
|
+
private readonly failOnHandlerError;
|
|
314
343
|
private stopped;
|
|
315
344
|
constructor(definition: RoomDefinition<S>, options: TestRoomOptions);
|
|
316
345
|
get core(): RoomCore<S>;
|
|
@@ -318,6 +347,7 @@ declare class TestHarness<S extends AnySchema> implements TestRoom<S> {
|
|
|
318
347
|
get plain(): PlainState;
|
|
319
348
|
get trace(): readonly TraceEntry[];
|
|
320
349
|
get rejections(): readonly RejectedCall[];
|
|
350
|
+
get handlerErrors(): readonly HandlerError[];
|
|
321
351
|
get dropped(): number;
|
|
322
352
|
get now(): number;
|
|
323
353
|
get tickCount(): number;
|
|
@@ -369,6 +399,13 @@ declare class TestHarness<S extends AnySchema> implements TestRoom<S> {
|
|
|
369
399
|
private record;
|
|
370
400
|
/** Fires everything due, then lets the clients' promises run, until nothing new is in flight. */
|
|
371
401
|
private settle;
|
|
402
|
+
/**
|
|
403
|
+
* Bug 2: the runtime catches a throwing handler so the room stays up, which in a test means
|
|
404
|
+
* the failure arrives later, somewhere else, wearing a disguise (the one that found this was a
|
|
405
|
+
* frozen tick counter reported ten seconds on as a `t.until` timeout). Every clock method ends
|
|
406
|
+
* here, so the throw surfaces at the line that ran the tick that broke.
|
|
407
|
+
*/
|
|
408
|
+
private reportHandlerErrors;
|
|
372
409
|
tick(n?: number): Promise<void>;
|
|
373
410
|
run(ms: number): Promise<void>;
|
|
374
411
|
/**
|
|
@@ -581,4 +618,4 @@ declare function testRoom<S extends AnySchema>(definition: RoomDefinition<S>, op
|
|
|
581
618
|
/** Starts a schema-less relay room in-process and returns the test handle. See the module docblock. */
|
|
582
619
|
declare function testRelay(options?: TestRelayOptions): Promise<TestRelay>;
|
|
583
620
|
|
|
584
|
-
export { type LatencySpec, type PredictionStats, type RejectedCall, type TestClient, TestHarness, type TestJoinSpec, type TestRelay, type TestRelayClient, TestRelayHarness, type TestRelayJoinSpec, type TestRelayOptions, type TestRoom, type TestRoomOptions, type UntilOptions, clockScheduler, divergence, materialize, testRelay, testRoom };
|
|
621
|
+
export { type HandlerError, type LatencySpec, type PredictionStats, type RejectedCall, type TestClient, TestHarness, type TestJoinSpec, type TestRelay, type TestRelayClient, TestRelayHarness, type TestRelayJoinSpec, type TestRelayOptions, type TestRoom, type TestRoomOptions, type UntilOptions, clockScheduler, divergence, materialize, testRelay, testRoom };
|
package/dist/index.js
CHANGED
package/dist/matchers.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@irtio/testing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "irtio's blessed test API: testRoom() in-process rooms with real client semantics, plus Vitest matchers",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -23,11 +23,11 @@
|
|
|
23
23
|
"dist"
|
|
24
24
|
],
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@irtio/client": "0.
|
|
27
|
-
"@irtio/protocol": "0.
|
|
28
|
-
"@irtio/
|
|
29
|
-
"@irtio/
|
|
30
|
-
"@irtio/
|
|
26
|
+
"@irtio/client": "0.4.0",
|
|
27
|
+
"@irtio/protocol": "0.4.0",
|
|
28
|
+
"@irtio/schema": "0.4.0",
|
|
29
|
+
"@irtio/server": "0.4.0",
|
|
30
|
+
"@irtio/runtime": "0.4.0"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"vitest": ">=3"
|