@mark1russell7/service 0.5.0 → 0.5.2
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/README.md +95 -4
- package/build/{builder-B_q-JeW8.d.ts → builder-BzjhWk2E.d.ts} +9 -2
- package/build/http.d.ts +1 -1
- package/build/http.js +3 -10
- package/build/http.js.map +1 -1
- package/build/index.d.ts +244 -7
- package/build/index.js +806 -25
- package/build/index.js.map +1 -1
- package/build/knobs-BxG-c7Zb.js +39 -0
- package/build/knobs-BxG-c7Zb.js.map +1 -0
- package/build/{lifecycle-DIj4YGmO.d.ts → lifecycle-DjEpgpRk.d.ts} +57 -8
- package/build/redis.d.ts +2 -2
- package/build/testing.d.ts +8 -2
- package/build/testing.js +23 -4
- package/build/testing.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { convertible, valueIn } from "@mark1russell7/quantity";
|
|
2
|
+
//#region src/knobs.ts
|
|
3
|
+
/**
|
|
4
|
+
* This function gives a time knob in milliseconds, or infinity for a time without a bound. The
|
|
5
|
+
* runtime has no defaults, thus a knob that is absent throws. A knob that is not a time, or that is
|
|
6
|
+
* less than zero, also throws.
|
|
7
|
+
*/
|
|
8
|
+
const millisecondsOf = (knobs, name, owner) => {
|
|
9
|
+
const amount = knobs[name];
|
|
10
|
+
if (amount === void 0) throw new Error(`${owner} needs the knob ${name}: it has no defaults`);
|
|
11
|
+
if (!convertible(amount.unit, "s")) throw new Error(`the knob ${name} is not a time`);
|
|
12
|
+
if (amount.value === "infinite") return Number.POSITIVE_INFINITY;
|
|
13
|
+
const value = valueIn(amount, "ms");
|
|
14
|
+
if (!(value >= 0)) throw new Error(`the knob ${name} is less than zero`);
|
|
15
|
+
return value;
|
|
16
|
+
};
|
|
17
|
+
/** This function gives a time knob in milliseconds, and it throws for a time without a bound. */
|
|
18
|
+
const boundedMillisecondsOf = (knobs, name, owner) => {
|
|
19
|
+
const value = millisecondsOf(knobs, name, owner);
|
|
20
|
+
if (!Number.isFinite(value)) throw new Error(`the knob ${name} of ${owner} has no bound: it needs a time`);
|
|
21
|
+
return value;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* This function gives a count knob (the unit `count`) as a whole number above zero, or infinity
|
|
25
|
+
* for a count without a bound. A knob that is absent, not a count, or not a whole number above zero throws.
|
|
26
|
+
*/
|
|
27
|
+
const countOf = (knobs, name, owner) => {
|
|
28
|
+
const amount = knobs[name];
|
|
29
|
+
if (amount === void 0) throw new Error(`${owner} needs the knob ${name}: it has no defaults`);
|
|
30
|
+
if (!convertible(amount.unit, "count")) throw new Error(`the knob ${name} is not a count`);
|
|
31
|
+
if (amount.value === "infinite") return Number.POSITIVE_INFINITY;
|
|
32
|
+
const value = valueIn(amount, "count");
|
|
33
|
+
if (!(Number.isInteger(value) && value >= 1)) throw new Error(`the knob ${name} is ${value}, not a whole number above zero`);
|
|
34
|
+
return value;
|
|
35
|
+
};
|
|
36
|
+
//#endregion
|
|
37
|
+
export { countOf as n, millisecondsOf as r, boundedMillisecondsOf as t };
|
|
38
|
+
|
|
39
|
+
//# sourceMappingURL=knobs-BxG-c7Zb.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"knobs-BxG-c7Zb.js","names":[],"sources":["../src/knobs.ts"],"sourcesContent":["import type { Extent } from \"@mark1russell7/quantity\";\nimport { convertible, valueIn } from \"@mark1russell7/quantity\";\n\n/** A knob record is the knobs of a part, by name. A knob that is absent is undefined. */\nexport type KnobRecord = Readonly<Record<string, Extent | undefined>>;\n\n/**\n * This function gives a time knob in milliseconds, or infinity for a time without a bound. The\n * runtime has no defaults, thus a knob that is absent throws. A knob that is not a time, or that is\n * less than zero, also throws.\n */\nexport const millisecondsOf = (knobs: KnobRecord, name: string, owner: string): number => {\n const amount = knobs[name];\n if (amount === undefined) throw new Error(`${owner} needs the knob ${name}: it has no defaults`);\n if (!convertible(amount.unit, \"s\")) throw new Error(`the knob ${name} is not a time`);\n if (amount.value === \"infinite\") return Number.POSITIVE_INFINITY;\n const value = valueIn(amount, \"ms\");\n if (!(value >= 0)) throw new Error(`the knob ${name} is less than zero`);\n return value;\n};\n\n/** This function gives a time knob in milliseconds, and it throws for a time without a bound. */\nexport const boundedMillisecondsOf = (knobs: KnobRecord, name: string, owner: string): number => {\n const value = millisecondsOf(knobs, name, owner);\n if (!Number.isFinite(value)) throw new Error(`the knob ${name} of ${owner} has no bound: it needs a time`);\n return value;\n};\n\n/**\n * This function gives a count knob (the unit `count`) as a whole number above zero, or infinity\n * for a count without a bound. A knob that is absent, not a count, or not a whole number above zero throws.\n */\nexport const countOf = (knobs: KnobRecord, name: string, owner: string): number => {\n const amount = knobs[name];\n if (amount === undefined) throw new Error(`${owner} needs the knob ${name}: it has no defaults`);\n if (!convertible(amount.unit, \"count\")) throw new Error(`the knob ${name} is not a count`);\n if (amount.value === \"infinite\") return Number.POSITIVE_INFINITY;\n const value = valueIn(amount, \"count\");\n if (!(Number.isInteger(value) && value >= 1)) throw new Error(`the knob ${name} is ${value}, not a whole number above zero`);\n return value;\n};\n"],"mappings":";;;;;;;AAWA,MAAa,kBAAkB,OAAmB,MAAc,UAA0B;CACxF,MAAM,SAAS,MAAM;CACrB,IAAI,WAAW,KAAA,GAAW,MAAM,IAAI,MAAM,GAAG,MAAM,kBAAkB,KAAK,qBAAqB;CAC/F,IAAI,CAAC,YAAY,OAAO,MAAM,GAAG,GAAG,MAAM,IAAI,MAAM,YAAY,KAAK,eAAe;CACpF,IAAI,OAAO,UAAU,YAAY,OAAO,OAAO;CAC/C,MAAM,QAAQ,QAAQ,QAAQ,IAAI;CAClC,IAAI,EAAE,SAAS,IAAI,MAAM,IAAI,MAAM,YAAY,KAAK,mBAAmB;CACvE,OAAO;AACT;;AAGA,MAAa,yBAAyB,OAAmB,MAAc,UAA0B;CAC/F,MAAM,QAAQ,eAAe,OAAO,MAAM,KAAK;CAC/C,IAAI,CAAC,OAAO,SAAS,KAAK,GAAG,MAAM,IAAI,MAAM,YAAY,KAAK,MAAM,MAAM,+BAA+B;CACzG,OAAO;AACT;;;;;AAMA,MAAa,WAAW,OAAmB,MAAc,UAA0B;CACjF,MAAM,SAAS,MAAM;CACrB,IAAI,WAAW,KAAA,GAAW,MAAM,IAAI,MAAM,GAAG,MAAM,kBAAkB,KAAK,qBAAqB;CAC/F,IAAI,CAAC,YAAY,OAAO,MAAM,OAAO,GAAG,MAAM,IAAI,MAAM,YAAY,KAAK,gBAAgB;CACzF,IAAI,OAAO,UAAU,YAAY,OAAO,OAAO;CAC/C,MAAM,QAAQ,QAAQ,QAAQ,OAAO;CACrC,IAAI,EAAE,OAAO,UAAU,KAAK,KAAK,SAAS,IAAI,MAAM,IAAI,MAAM,YAAY,KAAK,MAAM,MAAM,gCAAgC;CAC3H,OAAO;AACT"}
|
|
@@ -39,8 +39,10 @@ declare const EXIT_SIGNALS: readonly ExitSignal[];
|
|
|
39
39
|
* - `stdin-close`: the standard input ends or closes, as when the parent of a stdio child goes away.
|
|
40
40
|
* The process must read its input, as a stdio transport does, or Node does not see the end.
|
|
41
41
|
* - `ipc-close`: the IPC channel to the parent process closes.
|
|
42
|
+
* - `ipc-message`: a message on the IPC channel matches a declared shape. Windows cannot give a
|
|
43
|
+
* child a SIGTERM that the child can catch, thus a parent sends a message.
|
|
42
44
|
*/
|
|
43
|
-
type ProcessTrigger = "stdin-close" | "ipc-close";
|
|
45
|
+
type ProcessTrigger = "stdin-close" | "ipc-close" | "ipc-message";
|
|
44
46
|
/** These are the process triggers that a declaration can opt in to. */
|
|
45
47
|
declare const PROCESS_TRIGGERS: readonly ProcessTrigger[];
|
|
46
48
|
/** An exit trigger is an event that starts the shutdown: an exit signal, or a declared process trigger. */
|
|
@@ -49,6 +51,18 @@ type ExitTrigger = ExitSignal | ProcessTrigger;
|
|
|
49
51
|
declare const isExitCode: (code: number) => boolean;
|
|
50
52
|
/** This function tells if a trigger is an exit signal. A repeat signal obeys `onRepeatSignal`. */
|
|
51
53
|
declare const isExitSignal: (trigger: string) => trigger is ExitSignal;
|
|
54
|
+
/**
|
|
55
|
+
* A message shape is a value of JSON that a message of the IPC channel can match. A string, a
|
|
56
|
+
* number, a boolean or null matches an equal message. An object matches a message that has each of
|
|
57
|
+
* its fields, and each value of the field matches. An array matches an array of the same length, item by item.
|
|
58
|
+
*/
|
|
59
|
+
type MessageShape = string | number | boolean | null | readonly MessageShape[] | {
|
|
60
|
+
readonly [field: string]: MessageShape;
|
|
61
|
+
};
|
|
62
|
+
/** This function tells if a value is a message shape: a value of JSON, with no function and no undefined. */
|
|
63
|
+
declare const isMessageShape: (value: unknown) => value is MessageShape;
|
|
64
|
+
/** This function tells if a message of the IPC channel matches a shape. */
|
|
65
|
+
declare const matchesMessage: (shape: MessageShape, message: unknown) => boolean;
|
|
52
66
|
/**
|
|
53
67
|
* A process port is the part of the process that the runtime uses: its exit triggers, its output
|
|
54
68
|
* and its exit. The runtime owns the handlers, because Node as PID 1 without a handler ignores
|
|
@@ -58,14 +72,21 @@ declare const isExitSignal: (trigger: string) => trigger is ExitSignal;
|
|
|
58
72
|
interface ProcessPort {
|
|
59
73
|
/**
|
|
60
74
|
* This method adds a handler for a trigger, and gives the function that removes it. The runtime
|
|
61
|
-
* asks for a process trigger only when the declaration opts in to it.
|
|
75
|
+
* asks for a process trigger only when the declaration opts in to it. For `ipc-message`, the
|
|
76
|
+
* handler gets each message of the IPC channel, and the runtime matches it.
|
|
62
77
|
*/
|
|
63
|
-
on(trigger: ExitTrigger, handler: () => void): () => void;
|
|
78
|
+
on(trigger: ExitTrigger, handler: (message?: unknown) => void): () => void;
|
|
64
79
|
/**
|
|
65
80
|
* This method writes each buffered line of the output, and its promise ends when the output is
|
|
66
81
|
* written. The stage `flush the output` uses it, in the cap of that stage.
|
|
67
82
|
*/
|
|
68
83
|
flush?(): Promise<void> | void;
|
|
84
|
+
/**
|
|
85
|
+
* This method keeps the process alive, and gives the function that releases it. The lifecycle
|
|
86
|
+
* holds its process from its start to its exit, as a listening socket does. Thus a process whose
|
|
87
|
+
* timers do not keep it alive does not end in the middle of its work. It keeps its teardown and its exit code.
|
|
88
|
+
*/
|
|
89
|
+
hold?(): () => void;
|
|
69
90
|
/** This method ends the process with a code: the code of the exit request, 1 after an error, or 0. */
|
|
70
91
|
exit(code: number): void;
|
|
71
92
|
}
|
|
@@ -78,10 +99,10 @@ interface HostStream {
|
|
|
78
99
|
on(event: string, handler: () => void): unknown;
|
|
79
100
|
off(event: string, handler: () => void): unknown;
|
|
80
101
|
}
|
|
81
|
-
/** A process host is the part of the process of Node that the port uses. */
|
|
102
|
+
/** A process host is the part of the process of Node that the port uses. Its event `message` gives the message of the IPC channel. */
|
|
82
103
|
interface ProcessHost {
|
|
83
|
-
on(signal: string, handler: () => void): unknown;
|
|
84
|
-
off(signal: string, handler: () => void): unknown;
|
|
104
|
+
on(signal: string, handler: (message?: unknown) => void): unknown;
|
|
105
|
+
off(signal: string, handler: (message?: unknown) => void): unknown;
|
|
85
106
|
exit(code: number): unknown;
|
|
86
107
|
readonly stdin?: HostStream | undefined;
|
|
87
108
|
readonly stdout?: HostStream | undefined;
|
|
@@ -94,6 +115,21 @@ interface ProcessHost {
|
|
|
94
115
|
declare const processPortOf: (find: () => ProcessHost | undefined) => ProcessPort;
|
|
95
116
|
/** This port is the process of Node. */
|
|
96
117
|
declare const nodeProcess: ProcessPort;
|
|
118
|
+
/** An embedded process port records the exit code of a lifecycle that does not own its process. */
|
|
119
|
+
interface EmbeddedProcess extends ProcessPort {
|
|
120
|
+
/** This code is the code of the exit request of the lifecycle, or null before that request. */
|
|
121
|
+
readonly exitCode: number | null;
|
|
122
|
+
/** This promise ends with the exit code when the lifecycle asks for its exit. */
|
|
123
|
+
readonly exited: Promise<number>;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* This function gives a process port for a lifecycle in a process that another part owns. That
|
|
127
|
+
* part is a script, a test of an application, or a library entry. The port adds no handler of a signal or of
|
|
128
|
+
* a trigger, it does not hold the process, and its `exit` only records the code. The caller stops
|
|
129
|
+
* the lifecycle with `life.shutdown()`. A process with a service of its own gives its parts to
|
|
130
|
+
* that service, and does not use this port.
|
|
131
|
+
*/
|
|
132
|
+
declare const embeddedProcess: () => EmbeddedProcess;
|
|
97
133
|
//#endregion
|
|
98
134
|
//#region src/routes.d.ts
|
|
99
135
|
/**
|
|
@@ -287,6 +323,17 @@ type RepeatSignal = "ignore" | "exit";
|
|
|
287
323
|
type CooperativeFloor = "half" | "none";
|
|
288
324
|
/** These exit codes replace the code 0 of a clean shutdown, by the trigger of the shutdown. */
|
|
289
325
|
type ExitCodes = Readonly<Partial<Record<ExitTrigger, number>>>;
|
|
326
|
+
/**
|
|
327
|
+
* A message trigger is a message of the IPC channel that starts the shutdown, for the trigger
|
|
328
|
+
* `ipc-message`. With `as`, the message is that exit signal: it has its exit code and the policy of a
|
|
329
|
+
* repeat signal. Without `as`, the message is the trigger `ipc-message`.
|
|
330
|
+
*/
|
|
331
|
+
interface MessageTrigger {
|
|
332
|
+
/** This shape is the message that starts the shutdown, for example `"shutdown"`. */
|
|
333
|
+
readonly match: MessageShape;
|
|
334
|
+
/** This signal is the exit signal that the message replaces, for example `SIGTERM` on Windows. */
|
|
335
|
+
readonly as?: ExitSignal | undefined;
|
|
336
|
+
}
|
|
290
337
|
/** A service description is the whole graph of a service, as data. */
|
|
291
338
|
interface ServiceDescription {
|
|
292
339
|
readonly format: typeof FORMAT;
|
|
@@ -314,6 +361,8 @@ interface ServiceDescription {
|
|
|
314
361
|
readonly onRepeatSignal: RepeatSignal;
|
|
315
362
|
/** These triggers start the shutdown, in addition to the exit signals. Each one is opt-in. */
|
|
316
363
|
readonly triggers?: readonly ProcessTrigger[] | undefined;
|
|
364
|
+
/** These messages of the IPC channel start the shutdown. The trigger `ipc-message` needs one at the least. */
|
|
365
|
+
readonly messages?: readonly MessageTrigger[] | undefined;
|
|
317
366
|
/** These codes replace the exit code 0 by the trigger. Without a code, a clean shutdown exits 0. */
|
|
318
367
|
readonly exitCodes?: ExitCodes | undefined;
|
|
319
368
|
};
|
|
@@ -646,5 +695,5 @@ interface Lifecycle {
|
|
|
646
695
|
keepAlive(): boolean;
|
|
647
696
|
}
|
|
648
697
|
//#endregion
|
|
649
|
-
export {
|
|
650
|
-
//# sourceMappingURL=lifecycle-
|
|
698
|
+
export { failSpan as $, LifecycleEvent as A, Gate as B, ProbeAnswer as C, Operation as Ct, Outcome as D, CheckFn as E, CooperativeFloor as F, Step as G, Phase as H, Coupling as I, Poll as J, Transport as K, Dependency as L, Recorder as M, Check as N, Result as O, Controller as P, asWindow as Q, ExitCodes as R, HealthVerdict as S, Idempotency as St, StartOptions as T, RepeatSignal as U, MessageTrigger as V, ServiceDescription as W, Timing as X, Span as Y, asDebounce as Z, RequestOptions as _, isExitSignal as _t, PhaseContext as a, EXIT_SIGNALS as at, CheckVerdict as b, nodeProcess as bt, STAGES as c, ExitTrigger as ct, TransportImpl as d, PROCESS_TRIGGERS as dt, heartbeatDetection as et, cooperativeAbortAt as f, ProcessHost as ft, RequestFn as g, isExitCode as gt, RequestEvent as h, embeddedProcess as ht, LifecycleState as i, RouteRequest as it, MachineName as j, Trace as k, ShutdownOptions as l, HostStream as lt, RequestEnd as m, ProcessTrigger as mt, HealthServer as n, OperationOf as nt, PhaseWork as o, EmbeddedProcess as ot, stageRank as p, ProcessPort as pt, FailsBy as q, Lifecycle as r, RoutePattern as rt, RunOptions as s, ExitSignal as st, DependencyHandle as t, recoverSpan as tt, Stage as u, MessageShape as ut, RequestStream as v, isMessageShape as vt, ProbeKind as w, mayReissue as wt, Health as x, processPortOf as xt, RequestWork as y, matchesMessage as yt, FORMAT as z };
|
|
699
|
+
//# sourceMappingURL=lifecycle-DjEpgpRk.d.ts.map
|
package/build/redis.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as Backoff } from "./backoff-B-jGWVDm.js";
|
|
2
2
|
import { n as Clock } from "./clock-D-38uH_z.js";
|
|
3
|
-
import {
|
|
4
|
-
import { n as ServiceBuilder } from "./builder-
|
|
3
|
+
import { B as Gate, Ct as Operation, E as CheckFn, G as Step, J as Poll, L as Dependency, M as Recorder, N as Check, W as ServiceDescription, et as heartbeatDetection, t as DependencyHandle } from "./lifecycle-DjEpgpRk.js";
|
|
4
|
+
import { n as ServiceBuilder } from "./builder-BzjhWk2E.js";
|
|
5
5
|
import { Extent, Quantity } from "@mark1russell7/quantity";
|
|
6
6
|
import { Machine } from "@mark1russell7/algebra";
|
|
7
7
|
//#region src/redis/connection.d.ts
|
package/build/testing.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { n as Clock } from "./clock-D-38uH_z.js";
|
|
2
|
-
import { D as Outcome, E as CheckFn,
|
|
3
|
-
import { n as ServiceBuilder } from "./builder-
|
|
2
|
+
import { D as Outcome, E as CheckFn, ct as ExitTrigger, d as TransportImpl, pt as ProcessPort, r as Lifecycle, st as ExitSignal } from "./lifecycle-DjEpgpRk.js";
|
|
3
|
+
import { n as ServiceBuilder } from "./builder-BzjhWk2E.js";
|
|
4
4
|
//#region src/fakes.d.ts
|
|
5
5
|
/** A fake clock moves only when a test tells it to. */
|
|
6
6
|
interface FakeClock extends Clock {
|
|
@@ -22,6 +22,8 @@ interface FakeProcess extends ProcessPort {
|
|
|
22
22
|
signal(name: ExitSignal): void;
|
|
23
23
|
/** This method sends a trigger to each handler of it: a signal, or a process trigger, for example `stdin-close`. */
|
|
24
24
|
trigger(name: ExitTrigger): void;
|
|
25
|
+
/** This method sends a message of the IPC channel to each handler of the trigger `ipc-message`. */
|
|
26
|
+
message(value: unknown): void;
|
|
25
27
|
/** These are the exit codes, in sequence. A correct runtime exits one time. */
|
|
26
28
|
readonly exits: readonly number[];
|
|
27
29
|
/** This count is the number of handlers that are registered. */
|
|
@@ -32,6 +34,10 @@ interface FakeProcess extends ProcessPort {
|
|
|
32
34
|
readonly log: readonly string[];
|
|
33
35
|
/** This count is the number of flushes of the output. */
|
|
34
36
|
readonly flushes: number;
|
|
37
|
+
/** This count is the number of holds of the process that are not released. */
|
|
38
|
+
readonly holds: number;
|
|
39
|
+
/** These counts are the holds that were not released at each exit, in sequence. */
|
|
40
|
+
readonly heldAtExit: readonly number[];
|
|
35
41
|
}
|
|
36
42
|
/** These options shape a fake process. */
|
|
37
43
|
interface FakeProcessOptions {
|
package/build/testing.js
CHANGED
|
@@ -60,11 +60,13 @@ const fakeProcess = (options = {}) => {
|
|
|
60
60
|
const exits = [];
|
|
61
61
|
const log = [];
|
|
62
62
|
let flushes = 0;
|
|
63
|
+
let holds = 0;
|
|
64
|
+
const heldAtExit = [];
|
|
63
65
|
const stamp = (what) => {
|
|
64
66
|
log.push(options.clock === void 0 ? what : `${options.clock.now()} ${what}`);
|
|
65
67
|
};
|
|
66
|
-
const send = (name) => {
|
|
67
|
-
for (const handler of [...handlers.get(name) ?? []]) handler();
|
|
68
|
+
const send = (name, message) => {
|
|
69
|
+
for (const handler of [...handlers.get(name) ?? []]) handler(message);
|
|
68
70
|
};
|
|
69
71
|
return {
|
|
70
72
|
on(trigger, handler) {
|
|
@@ -81,12 +83,23 @@ const fakeProcess = (options = {}) => {
|
|
|
81
83
|
flushes += 1;
|
|
82
84
|
stamp("flushed");
|
|
83
85
|
},
|
|
86
|
+
hold() {
|
|
87
|
+
holds += 1;
|
|
88
|
+
let held = true;
|
|
89
|
+
return () => {
|
|
90
|
+
if (!held) return;
|
|
91
|
+
held = false;
|
|
92
|
+
holds -= 1;
|
|
93
|
+
};
|
|
94
|
+
},
|
|
84
95
|
exit(code) {
|
|
85
96
|
stamp(`exit ${code}`);
|
|
97
|
+
heldAtExit.push(holds);
|
|
86
98
|
exits.push(code);
|
|
87
99
|
},
|
|
88
|
-
signal: send,
|
|
89
|
-
trigger: send,
|
|
100
|
+
signal: (name) => send(name),
|
|
101
|
+
trigger: (name) => send(name),
|
|
102
|
+
message: (value) => send("ipc-message", value),
|
|
90
103
|
get exits() {
|
|
91
104
|
return [...exits];
|
|
92
105
|
},
|
|
@@ -103,6 +116,12 @@ const fakeProcess = (options = {}) => {
|
|
|
103
116
|
},
|
|
104
117
|
get flushes() {
|
|
105
118
|
return flushes;
|
|
119
|
+
},
|
|
120
|
+
get holds() {
|
|
121
|
+
return holds;
|
|
122
|
+
},
|
|
123
|
+
get heldAtExit() {
|
|
124
|
+
return [...heldAtExit];
|
|
106
125
|
}
|
|
107
126
|
};
|
|
108
127
|
};
|
package/build/testing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.js","names":[],"sources":["../src/fakes.ts","../src/contract.ts","../src/conformance.ts","../src/testing.ts"],"sourcesContent":["// The fakes of the runtime for tests: a fake clock gives exact, repeatable timing, a seeded random\n// gives the same jitter on each run, and a fake process takes signals and keeps the exit codes.\n\nimport type { Cancel, Clock } from \"./clock.ts\";\nimport type { ExitSignal, ExitTrigger, ProcessPort } from \"./process.ts\";\n\n/** A fake clock moves only when a test tells it to. */\nexport interface FakeClock extends Clock {\n /** This method moves the clock forward by `ms`, and it starts each timer that comes due, in sequence. */\n advance(ms: number): Promise<void>;\n /**\n * This method moves the clock forward by `ms` and starts no timer, as a blocked event loop does.\n * The next `advance` then starts each timer that came due during the block, late.\n */\n block(ms: number): void;\n /** This count is the number of timers that are not stopped and not called. */\n readonly pending: number;\n}\n\n/** This function lets the promises that are ready continue, before the clock moves again. */\nconst settle = async (): Promise<void> => {\n for (let turn = 0; turn < 64; turn += 1) await Promise.resolve();\n};\n\n/** This function makes a fake clock. Its time starts at `start` milliseconds. */\nexport const fakeClock = (start = 0): FakeClock => {\n let now = start;\n let sequence = 0;\n const timers: Array<{ at: number; order: number; run: () => void; live: boolean }> = [];\n const due = (end: number) => {\n let best: (typeof timers)[number] | undefined;\n for (const timer of timers)\n if (timer.live && timer.at <= end && (best === undefined || timer.at < best.at || (timer.at === best.at && timer.order < best.order)))\n best = timer;\n return best;\n };\n return {\n now: () => now,\n after(ms, run): Cancel {\n const timer = { at: now + Math.max(0, ms), order: sequence++, run, live: true };\n timers.push(timer);\n return () => {\n timer.live = false;\n };\n },\n async advance(ms) {\n // a monotonic clock does not go back\n const end = now + Math.max(0, ms);\n await settle();\n for (;;) {\n const timer = due(end);\n if (timer === undefined) break;\n timer.live = false;\n // a timer that came due during a block runs late, at the time of the clock\n now = Math.max(now, timer.at);\n timer.run();\n await settle();\n }\n now = Math.max(now, end);\n for (let at = timers.length - 1; at >= 0; at -= 1) if (!timers[at]!.live) timers.splice(at, 1);\n await settle();\n },\n block(ms) {\n now += Math.max(0, ms);\n },\n get pending() {\n return timers.filter((timer) => timer.live).length;\n },\n };\n};\n\n/** A fake process receives triggers from a test, and keeps each exit code in place of an exit. */\nexport interface FakeProcess extends ProcessPort {\n /** This method sends a signal to each handler of it. */\n signal(name: ExitSignal): void;\n /** This method sends a trigger to each handler of it: a signal, or a process trigger, for example `stdin-close`. */\n trigger(name: ExitTrigger): void;\n /** These are the exit codes, in sequence. A correct runtime exits one time. */\n readonly exits: readonly number[];\n /** This count is the number of handlers that are registered. */\n readonly handlers: number;\n /** These are the triggers that have a handler, in the sequence of their first handler. */\n readonly triggers: readonly ExitTrigger[];\n /** These are the times of the clock of the options at each flush and each exit, when the options give a clock. */\n readonly log: readonly string[];\n /** This count is the number of flushes of the output. */\n readonly flushes: number;\n}\n\n/** These options shape a fake process. */\nexport interface FakeProcessOptions {\n /** This function is the flush of the output, for example a wait on a fake clock. Without it, a flush ends at once. */\n readonly flush?: (() => Promise<void> | void) | undefined;\n /** This clock stamps each line of the log. */\n readonly clock?: Clock | undefined;\n}\n\n/** This function makes a fake process. */\nexport const fakeProcess = (options: FakeProcessOptions = {}): FakeProcess => {\n const handlers = new Map<ExitTrigger, Set<() => void>>();\n const exits: number[] = [];\n const log: string[] = [];\n let flushes = 0;\n const stamp = (what: string): void => {\n log.push(options.clock === undefined ? what : `${options.clock.now()} ${what}`);\n };\n const send = (name: ExitTrigger): void => {\n for (const handler of [...(handlers.get(name) ?? [])]) handler();\n };\n return {\n on(trigger, handler) {\n const set = handlers.get(trigger) ?? new Set();\n set.add(handler);\n handlers.set(trigger, set);\n return () => {\n set.delete(handler);\n };\n },\n async flush() {\n stamp(\"flush\");\n await options.flush?.();\n flushes += 1;\n stamp(\"flushed\");\n },\n exit(code) {\n stamp(`exit ${code}`);\n exits.push(code);\n },\n signal: send,\n trigger: send,\n get exits() {\n return [...exits];\n },\n get handlers() {\n let count = 0;\n for (const set of handlers.values()) count += set.size;\n return count;\n },\n get triggers() {\n return [...handlers.keys()];\n },\n get log() {\n return [...log];\n },\n get flushes() {\n return flushes;\n },\n };\n};\n\n/** This function gives a seeded random number generator (mulberry32), with numbers in [0, 1). */\nexport const seeded = (seed: number): (() => number) => {\n let state = seed >>> 0;\n return () => {\n state = (state + 0x6d2b79f5) >>> 0;\n let t = state;\n t = Math.imul(t ^ (t >>> 15), t | 1);\n t ^= t + Math.imul(t ^ (t >>> 7), t | 61);\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n };\n};\n","import { valueIn } from \"@mark1russell7/quantity\";\nimport type { ServiceBuilder } from \"./builder.ts\";\nimport type { Check, ServiceDescription } from \"./description.ts\";\nimport type { FakeClock } from \"./fakes.ts\";\nimport { fakeClock, seeded } from \"./fakes.ts\";\nimport type { Health, HealthVerdict, StartOptions } from \"./health.ts\";\nimport { failSpan, recoverSpan } from \"./timing.ts\";\n\n/** A contract case is one generated case of the contract test of a declaration. It throws on a failure. */\nexport interface ContractCase {\n readonly name: string;\n run(): Promise<void>;\n}\n\n/** These options tune the contract test. */\nexport interface ContractOptions {\n /** This number is the latency that a degraded dependency adds to each check, in milliseconds. The default of the contract is 100. */\n readonly degradeBy?: number | undefined;\n /** This number is the seed of the jitter. */\n readonly seed?: number | undefined;\n}\n\nconst ms = (amount: { readonly value: number; readonly unit: string }): number => valueIn(amount, \"ms\");\n\n/** The part of a verdict that the contract compares: what a caller and the platform see. */\nconst visible = (verdict: HealthVerdict) => ({\n ready: verdict.ready,\n alive: verdict.alive,\n controllers: verdict.controllers,\n dependencies: verdict.dependencies,\n});\n\nconst same = (expected: unknown, actual: unknown): boolean => JSON.stringify(expected) === JSON.stringify(actual);\n\nconst failure = (what: string, expected: unknown, actual: unknown): Error =>\n new Error(`${what}: expected ${JSON.stringify(expected)}, but the verdict was ${JSON.stringify(actual)}`);\n\n/**\n * This function generates the contract test of a declaration. `declare` gives a new builder with\n * the declaration and the function of each check, each time. The checks must pass at rest. The\n * cases are:\n * - The service at rest: each controller and each dependency is ready, and the service is alive.\n * - Each dependency unavailable: its polls fail at once (the force-fail list). The dependency and\n * each controller that needs it become not ready, and the other controllers stay ready. Liveness\n * stays, unless a liveness check exercises the dependency.\n * - Each dependency degraded: each call of its checks takes the latency longer. Nothing can fail.\n * - Each dependency recovers: after it is unavailable, the service is again at rest.\n * - A negative control: a client that the declaration does not name fails, and nothing changes.\n *\n * A dependency that no check exercises is a finding: the service cannot see it fail.\n */\nexport const contractCases = (declare: () => ServiceBuilder, options: ContractOptions = {}): ContractCase[] => {\n const degradeBy = options.degradeBy ?? 100;\n const seed = options.seed ?? 1;\n const description: ServiceDescription = declare().describe();\n const service = description.name;\n const gating = description.checks.filter((check) => check.gates !== null);\n const timingOf = (check: Check) => description.policies[check.gates!];\n const failMax = (check: Check): number => ms(failSpan(timingOf(check), check.poll, \"error\").max);\n const recoverMax = (check: Check): number => ms(recoverSpan(timingOf(check), check.poll).max);\n const most = (values: readonly number[]): number => values.reduce((a, b) => Math.max(a, b), 0);\n const settleTime = most(gating.filter((check) => check.gates === \"readiness\").map(recoverMax)) + 1;\n const quietTime = most(gating.map(failMax)) + 1;\n\n /** This function starts the declaration on a fake clock, and moves it to rest. */\n const boot = async (\n extra: (clock: FakeClock) => Partial<StartOptions> = () => ({}),\n ): Promise<{ health: Health; clock: FakeClock; rest: ReturnType<typeof visible> }> => {\n const clock = fakeClock();\n // the warm-up sweep can wait on the clock (a degraded check), thus the clock moves while it starts\n const starting = declare().start({ clock, random: seeded(seed), ...extra(clock) });\n let started = false;\n void starting.then(\n () => (started = true),\n () => (started = true),\n );\n for (let step = 0; step < 1000 && !started; step += 1) await clock.advance(10);\n const health = await starting;\n await clock.advance(settleTime);\n return { health, clock, rest: visible(health.verdict()) };\n };\n const stop = (health: Health): void => health.stop();\n\n const cases: ContractCase[] = [\n {\n name: `${service}: at rest, each controller and each dependency is ready, and the service is alive`,\n async run() {\n const { health, rest } = await boot();\n try {\n const expected = {\n ready: true,\n alive: true,\n controllers: Object.fromEntries(description.controllers.map((each) => [each.name, true])),\n dependencies: Object.fromEntries(description.dependencies.map((each) => [each.name, true])),\n };\n if (!same(expected, rest)) throw failure(\"at rest\", expected, rest);\n } finally {\n stop(health);\n }\n },\n },\n ];\n\n for (const dependency of description.dependencies) {\n const on = gating.filter((check) => check.dependency === dependency.name);\n const readiness = on.filter((check) => check.gates === \"readiness\");\n const liveness = on.filter((check) => check.gates === \"liveness\");\n const unavailable = (rest: ReturnType<typeof visible>) => ({\n ready: readiness.length === 0 ? rest.ready : false,\n alive: liveness.length === 0 ? rest.alive : false,\n controllers: Object.fromEntries(\n description.controllers.map((each) => [\n each.name,\n each.requires.includes(dependency.name) && readiness.length > 0 ? false : rest.controllers[each.name],\n ]),\n ),\n dependencies: { ...rest.dependencies, [dependency.name]: readiness.length === 0 },\n });\n\n cases.push({\n name: `${service}: ${dependency.name} unavailable`,\n async run() {\n if (on.length === 0)\n throw new Error(`no check that gates a policy exercises ${dependency.name}: the service cannot see it fail`);\n const { health, clock, rest } = await boot();\n try {\n health.forceFail(dependency.name, true);\n await clock.advance(most(on.map(failMax)) + 1);\n const now = visible(health.verdict());\n const expected = unavailable(rest);\n if (!same(expected, now)) throw failure(`${dependency.name} unavailable`, expected, now);\n } finally {\n stop(health);\n }\n },\n });\n\n cases.push({\n name: `${service}: ${dependency.name} degraded by ${degradeBy} ms, and nothing fails`,\n async run() {\n const tight = on.filter((check) => ms(check.poll.timeout) <= degradeBy);\n if (tight.length > 0)\n throw new Error(\n `${tight.map((check) => `${check.name} times out in ${ms(check.poll.timeout)} ms`).join(\", \")}: a latency of ${degradeBy} ms fails it`,\n );\n const { health, clock, rest } = await boot((at) => ({\n wrapCheck: (check, run) =>\n check.dependency !== dependency.name\n ? run\n : (signal) => new Promise((resolve) => at.after(degradeBy, () => resolve(run(signal)))),\n }));\n try {\n await clock.advance(quietTime + most(on.map((check) => ms(check.poll.interval))));\n const now = visible(health.verdict());\n if (!same(rest, now)) throw failure(`${dependency.name} degraded`, rest, now);\n } finally {\n stop(health);\n }\n },\n });\n\n cases.push({\n name: `${service}: ${dependency.name} recovers`,\n async run() {\n if (on.length === 0)\n throw new Error(`no check that gates a policy exercises ${dependency.name}: the service cannot see it recover`);\n const { health, clock, rest } = await boot();\n try {\n health.forceFail(dependency.name, true);\n await clock.advance(most(on.map(failMax)) + 1);\n health.forceFail(dependency.name, false);\n await clock.advance(most(on.map(recoverMax)) + 1);\n const now = visible(health.verdict());\n if (!same(rest, now)) throw failure(`${dependency.name} recovered`, rest, now);\n } finally {\n stop(health);\n }\n },\n });\n }\n\n const never = `${service}.client-never-registered`;\n cases.push({\n name: `${service}: a client never registered fails, and nothing changes (the negative control)`,\n async run() {\n if (description.dependencies.some((each) => each.name === never))\n throw new Error(`the declaration has a dependency named ${never}`);\n const { health, clock, rest } = await boot();\n try {\n health.forceFail(never, true);\n await clock.advance(quietTime);\n const now = visible(health.verdict());\n if (!same(rest, now)) throw failure(\"the negative control\", rest, now);\n } finally {\n stop(health);\n }\n // and a check on a client never registered is refused before the start\n let refused = false;\n try {\n declare()\n .check({\n name: `${never}.check`,\n dependency: never,\n step: \"round-trip\",\n ownConnection: true,\n gates: null,\n poll: gating[0]?.poll ?? { interval: { value: 1, unit: \"s\" }, jitter: 0, timeout: { value: 0.5, unit: \"s\" } },\n })\n .describe();\n } catch {\n refused = true;\n }\n if (!refused) throw new Error(`the declaration accepted a check on ${never}, which is not a dependency`);\n },\n });\n\n return cases;\n};\n","import type { Clock } from \"./clock.ts\";\nimport { realClock } from \"./clock.ts\";\nimport type { ContractCase } from \"./contract.ts\";\nimport { fakeClock } from \"./fakes.ts\";\nimport type { Health } from \"./health.ts\";\nimport type { Lifecycle, TransportImpl } from \"./lifecycle.ts\";\n\n/** A held work is one unit of work that stays in flight until the test releases it. */\nexport interface HeldWork {\n /** This method lets the work end. Its promise ends when the caller of the work has its answer, or its error. */\n release(): Promise<void>;\n /** This flag tells if the work got the cooperative abort of the drain. */\n readonly aborted: boolean;\n}\n\n/**\n * A transport subject is a transport and the client side of its work. The kit offers work through\n * it, holds work in flight through it, and reads the count of the transport.\n */\nexport interface TransportSubject {\n readonly impl: TransportImpl;\n /** This lifecycle goes to `start()`. Without it, the kit gives a detached lifecycle. */\n readonly life?: Lifecycle | undefined;\n /** This method offers one unit of new work. It gives true when the transport took it, and false when it refused it. */\n offer(): Promise<boolean>;\n /** This method starts one unit of work that stays in flight until its release. The promise ends when the transport has it. */\n hold(): Promise<HeldWork>;\n /** This method frees what the subject holds outside the transport: its clients and their sockets. */\n dispose?(): Promise<void> | void;\n}\n\n/**\n * This function gives a detached lifecycle: a lifecycle with no service, for the start of a\n * transport in a test. Its state is `running`, it keeps each connection, and nothing is in flight. Each request\n * and each use of its health throws, because no service declares a route.\n */\nexport const detachedLifecycle = (): Lifecycle => {\n const none = (): never => {\n throw new Error(\"a detached lifecycle has no service: give the transport the lifecycle of a service\");\n };\n const health = { verdict: none, probe: none, subscribe: none, forceFail: none, drain: none, stop: none } as Health;\n return {\n health,\n state: \"running\",\n exitCode: null,\n errors: [],\n started: Promise.resolve(),\n done: new Promise<number>(() => {}),\n signal: new AbortController().signal,\n shutdown: none,\n request: none,\n inFlight: () => 0,\n keepAlive: () => true,\n };\n};\n\n/** A subject factory makes a new subject for each case, on the clock of the case. */\nexport type SubjectFactory = (clock: Clock) => TransportSubject | Promise<TransportSubject>;\n\n/** A conformance record is one measurement of a case, for the raw records of a run in CI. */\nexport interface ConformanceRecord {\n readonly case: string;\n readonly what: string;\n /** This time is the limit of the contract, in milliseconds. */\n readonly expected: number;\n /** This time is what the case measured, in milliseconds of the real clock. */\n readonly measured: number;\n}\n\n/** These options tune the conformance kit. */\nexport interface ConformanceOptions {\n /** This name goes before the name of each case. */\n readonly name?: string | undefined;\n /** This clock moves the cases: `fake` gives exact and fast cases, and `real` measures the timing. The default is `fake`. */\n readonly clock?: \"fake\" | \"real\" | undefined;\n /** This time is the cap of each drain of the cases, in milliseconds. The default is 1000. */\n readonly cap?: number | undefined;\n /** This time is the most that a close can take, in milliseconds of the real clock. The default is 1000. */\n readonly closeBound?: number | undefined;\n /** This time is the most that an event can take to reach the transport, in real milliseconds. The default is 100. */\n readonly slack?: number | undefined;\n /** False tells that the work of the transport cannot see the cooperative abort. The default is true. */\n readonly cooperative?: boolean | undefined;\n /** This count is the number of works that the count case holds. The default is 3. */\n readonly holds?: number | undefined;\n /** This function receives each measurement, for the raw records of a run. */\n readonly record?: ((record: ConformanceRecord) => void) | undefined;\n}\n\ninterface Time {\n readonly clock: Clock;\n pass(ms: number): Promise<void>;\n}\n\ninterface Tracked {\n readonly settled: boolean;\n readonly error: unknown;\n readonly failed: boolean;\n}\n\nconst realNow = (): number => performance.now();\nconst turn = (): Promise<void> => new Promise((resolve) => setTimeout(resolve, 0));\nconst sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));\n\n/** This function waits on the real clock, until the test is true or the bound ends. It gives the test at the end. */\nconst within = async (bound: number, test: () => boolean): Promise<boolean> => {\n const end = realNow() + bound;\n for (;;) {\n if (test()) return true;\n if (realNow() > end) return test();\n await turn();\n }\n};\n\n/** This function lets the events that are ready reach the transport, with no move of the clock of the case. */\nconst quiet = async (): Promise<void> => {\n for (let at = 0; at < 8; at += 1) await turn();\n};\n\n/** This function uses a method of the transport, and gives a throw of the method as a rejection. */\nconst attempt = (call: () => Promise<void> | void): Promise<void> => {\n try {\n return Promise.resolve(call());\n } catch (error) {\n return Promise.reject(error);\n }\n};\n\nconst track = (promise: Promise<unknown>): Tracked => {\n const state = { settled: false, error: undefined as unknown, failed: false };\n promise.then(\n () => {\n state.settled = true;\n },\n (error: unknown) => {\n state.settled = true;\n state.failed = true;\n state.error = error;\n },\n );\n return state;\n};\n\n/**\n * This function generates the conformance cases of a transport (S6 A9). `make` gives a new subject\n * for each case. Each case throws on a failure, and its message names the part of the contract.\n * - The start: nothing is in flight, and the transport takes work.\n * - The count: it follows each work from its events, with no move of the clock.\n * - `stopIntake` refuses new work, keeps the work in flight, and a second stop has no effect.\n * - `drain` ends at once with no work, ends with the last work, and holds to its deadline and not\n * past it. It gives the cooperative abort to the work, and it does not throw.\n * - `close` ends the work in flight in its bound, and a second close has no effect.\n * - The stages in their sequence, as the lifecycle does them.\n */\nexport const transportConformance = (make: SubjectFactory, options: ConformanceOptions = {}): ContractCase[] => {\n const name = options.name ?? \"transport\";\n const fake = (options.clock ?? \"fake\") === \"fake\";\n const cap = options.cap ?? 1000;\n const closeBound = options.closeBound ?? 1000;\n const slack = options.slack ?? 100;\n const cooperative = options.cooperative ?? true;\n const holds = options.holds ?? 3;\n\n const timeOf = (): Time => {\n if (!fake) return { clock: realClock, pass: sleep };\n const clock = fakeClock();\n return { clock, pass: (ms) => clock.advance(ms) };\n };\n\n const check = (ok: boolean, what: string): void => {\n if (!ok) throw new Error(`${name}: ${what}`);\n };\n\n const each = (title: string, body: (subject: TransportSubject, time: Time, title: string) => Promise<void>): ContractCase => ({\n name: `${name}: ${title}`,\n async run() {\n const time = timeOf();\n const subject = await make(time.clock);\n try {\n await subject.impl.start(new AbortController().signal, subject.life ?? detachedLifecycle());\n await body(subject, time, title);\n } finally {\n try {\n await subject.impl.close();\n } finally {\n await subject.dispose?.();\n }\n }\n },\n });\n\n const count = (subject: TransportSubject, expected: number, what: string): Promise<void> =>\n within(slack, () => subject.impl.inFlight() === expected).then((ok) =>\n check(ok, `${what}: inFlight() gives ${subject.impl.inFlight()}, not ${expected}`),\n );\n\n const closes = async (subject: TransportSubject, title: string): Promise<void> => {\n const began = realNow();\n const closed = track(attempt(() => subject.impl.close()));\n const inBound = await within(closeBound, () => closed.settled);\n const took = realNow() - began;\n options.record?.({ case: title, what: \"close\", expected: closeBound, measured: took });\n check(inBound, `close did not end in its bound of ${closeBound} ms`);\n check(!closed.failed, `close threw: ${String(closed.error)}`);\n };\n\n const cases: ContractCase[] = [\n each(\"nothing is in flight after the start, and it takes new work\", async (subject) => {\n check(subject.impl.inFlight() === 0, `inFlight() gives ${subject.impl.inFlight()} after the start, not 0`);\n check(await subject.offer(), \"the transport refused new work after its start\");\n await count(subject, 0, \"the work ended\");\n }),\n\n each(\"the count follows each work in flight from its events, with no move of the clock\", async (subject) => {\n const held: HeldWork[] = [];\n for (let at = 1; at <= holds; at += 1) {\n held.push(await subject.hold());\n await count(subject, at, `${at} works are in flight`);\n }\n for (let at = holds - 1; at >= 0; at -= 1) {\n await held[at]!.release();\n await count(subject, at, `${at} works are in flight after a release`);\n }\n }),\n\n each(\"stopIntake refuses new work, keeps the work in flight, and a second stop has no effect\", async (subject) => {\n const work = await subject.hold();\n await subject.impl.stopIntake();\n check(!(await subject.offer()), \"the transport took new work after stopIntake\");\n await count(subject, 1, \"stopIntake cut the work in flight\");\n await subject.impl.stopIntake();\n check(!(await subject.offer()), \"the transport took new work after a second stopIntake\");\n await work.release();\n await count(subject, 0, \"the work in flight ended after stopIntake\");\n }),\n\n each(\"drain ends at once when nothing is in flight\", async (subject, time) => {\n await subject.impl.stopIntake();\n const drained = track(attempt(() => subject.impl.drain(time.clock.now() + cap, new AbortController().signal)));\n check(await within(slack, () => drained.settled), \"drain did not end with nothing in flight\");\n check(!drained.failed, `drain threw: ${String(drained.error)}`);\n }),\n\n each(\"drain ends when the last work ends, before its deadline\", async (subject, time) => {\n const work = await subject.hold();\n await subject.impl.stopIntake();\n const deadline = time.clock.now() + cap;\n const drained = track(attempt(() => subject.impl.drain(deadline, new AbortController().signal)));\n await time.pass(cap / 4);\n await quiet();\n check(!drained.settled, \"drain ended while work was in flight, before its deadline\");\n await work.release();\n check(await within(slack, () => drained.settled), \"drain did not end when the last work ended\");\n check(!drained.failed, `drain threw: ${String(drained.error)}`);\n check(time.clock.now() < deadline, \"drain waited until its deadline after the last work ended\");\n }),\n\n each(\"drain holds to its deadline and not past it, and does not throw\", async (subject, time, title) => {\n await subject.hold();\n await subject.impl.stopIntake();\n const began = realNow();\n const deadline = time.clock.now() + cap;\n const drained = track(attempt(() => subject.impl.drain(deadline, new AbortController().signal)));\n if (fake) {\n await time.pass(cap - 1);\n await quiet();\n check(!drained.settled, \"drain ended before its deadline, with work in flight\");\n await time.pass(1);\n check(await within(slack, () => drained.settled), \"drain did not end at its deadline\");\n } else {\n const ended = await within(cap + slack, () => drained.settled);\n const took = realNow() - began;\n options.record?.({ case: title, what: \"drain to its deadline\", expected: cap, measured: took });\n check(ended, `drain did not end in ${cap + slack} ms, with a deadline at ${cap} ms`);\n check(took >= cap - slack, `drain ended after ${Math.round(took)} ms, before its deadline at ${cap} ms`);\n }\n check(!drained.failed, `drain threw at its deadline: ${String(drained.error)}`);\n }),\n\n ...(cooperative\n ? [\n each(\"drain gives the cooperative abort to the work in flight\", async (subject, time) => {\n const work = await subject.hold();\n await subject.impl.stopIntake();\n const abort = new AbortController();\n const drained = track(attempt(() => subject.impl.drain(time.clock.now() + cap, abort.signal)));\n await time.pass(cap / 2);\n await quiet();\n check(!work.aborted, \"the work got an abort before the signal of the drain aborted\");\n abort.abort(new Error(\"the cooperative abort of the conformance kit\"));\n check(await within(slack, () => work.aborted), \"the work did not get the cooperative abort\");\n await time.pass(cap / 2);\n check(await within(slack, () => drained.settled), \"drain did not end at its deadline after the cooperative abort\");\n check(!drained.failed, `drain threw after the cooperative abort: ${String(drained.error)}`);\n }),\n ]\n : []),\n\n each(\"drain with an aborted signal and a deadline in the past ends at once\", async (subject, time) => {\n const work = await subject.hold();\n await subject.impl.stopIntake();\n const abort = new AbortController();\n abort.abort(new Error(\"the cooperative abort of the conformance kit\"));\n const drained = track(attempt(() => subject.impl.drain(time.clock.now() - 1, abort.signal)));\n check(await within(slack, () => drained.settled), \"drain did not end at once with its deadline in the past\");\n check(!drained.failed, `drain threw: ${String(drained.error)}`);\n if (cooperative) check(await within(slack, () => work.aborted), \"the work did not get an abort that came before the drain\");\n }),\n\n each(\"close ends the work in flight, in its bound\", async (subject, _time, title) => {\n for (let at = 0; at < holds; at += 1) await subject.hold();\n await count(subject, holds, \"the works are in flight before the close\");\n await closes(subject, title);\n await count(subject, 0, \"close left work in flight\");\n }),\n\n each(\"close is idempotent: a second close has no effect\", async (subject, _time, title) => {\n await subject.hold();\n const first = track(attempt(() => subject.impl.close()));\n const second = track(attempt(() => subject.impl.close()));\n check(await within(closeBound, () => first.settled && second.settled), \"two closes did not end in the bound\");\n check(!first.failed && !second.failed, `a close threw: ${String(first.error ?? second.error)}`);\n await closes(subject, title);\n await count(subject, 0, \"the closes left work in flight\");\n }),\n\n each(\"close right after the start ends in its bound\", async (subject, _time, title) => {\n await closes(subject, title);\n check(subject.impl.inFlight() === 0, \"inFlight() is not 0 after a close with no work\");\n }),\n\n each(\"the stages in their sequence: stop intake, drain with its cooperative abort, close\", async (subject, time, title) => {\n const short = await subject.hold();\n const long = await subject.hold();\n await subject.impl.stopIntake();\n check(!(await subject.offer()), \"the transport took new work after stopIntake\");\n const abort = new AbortController();\n const drained = track(attempt(() => subject.impl.drain(time.clock.now() + cap, abort.signal)));\n await short.release();\n await count(subject, 1, \"one work ended in the drain\");\n await time.pass(cap / 2);\n abort.abort(new Error(\"the cooperative abort of the conformance kit\"));\n if (cooperative) check(await within(slack, () => long.aborted), \"the long work did not get the cooperative abort\");\n await time.pass(cap / 2);\n check(await within(slack, () => drained.settled), \"drain did not end at its deadline\");\n await closes(subject, title);\n await count(subject, 0, \"close left work in flight\");\n }),\n ];\n return cases;\n};\n","// @mark1russell7/service/testing: the seams of the runtime for tests. A fake clock gives exact,\n// repeatable timing. A seeded random gives the same jitter on each run. A fake process takes\n// signals and keeps the exit codes. A quiet server answers until it goes quiet, and then it holds\n// its connections open without an answer. `contractCases` generates the contract test of a\n// declaration, and `transportConformance` generates the conformance test of a transport.\n\nimport type { AddressInfo, Server, Socket } from \"node:net\";\nimport { createServer } from \"node:net\";\nimport type { Outcome } from \"./latch.ts\";\nimport type { CheckFn } from \"./poller.ts\";\n\nexport type { FakeClock, FakeProcess, FakeProcessOptions } from \"./fakes.ts\";\nexport { fakeClock, fakeProcess, seeded } from \"./fakes.ts\";\nexport type { ContractCase, ContractOptions } from \"./contract.ts\";\nexport { contractCases } from \"./contract.ts\";\nexport type {\n ConformanceOptions,\n ConformanceRecord,\n HeldWork,\n SubjectFactory,\n TransportSubject,\n} from \"./conformance.ts\";\nexport { detachedLifecycle, transportConformance } from \"./conformance.ts\";\n\n/**\n * A behavior is what a scripted check does when the runtime starts it:\n * - `\"ok\"`, `\"error\"` and `\"indeterminate\"` give that outcome at once.\n * - `\"throw\"` throws at once.\n * - `\"timeout\"` gives no answer until the runtime aborts the use at the timeout of the poll.\n * - `\"hang\"` gives no answer, and it ignores the abort, until `heal()`.\n */\nexport type Behavior = Outcome | \"throw\" | \"timeout\" | \"hang\";\n\n/** A scripted check is a check function whose behavior a test sets. */\nexport interface ScriptedCheck {\n readonly run: CheckFn;\n /** This behavior applies to each later call. */\n behavior: Behavior;\n /** This count is the number of calls of the check. */\n readonly calls: number;\n /** This count is the number of calls that are pending. */\n readonly hung: number;\n /** This method ends each hung call with a pass, as when a driver gets its answer late. */\n heal(): void;\n}\n\n/** This function makes a scripted check with a behavior at the start. */\nexport const scriptedCheck = (behavior: Behavior = \"ok\"): ScriptedCheck => {\n let calls = 0;\n const hung = new Set<() => void>();\n const script: ScriptedCheck = {\n behavior,\n get calls() {\n return calls;\n },\n get hung() {\n return hung.size;\n },\n heal() {\n for (const end of [...hung]) end();\n },\n run: (signal) => {\n calls += 1;\n const now = script.behavior;\n if (now === \"throw\") throw new Error(\"the scripted check threw\");\n if (now === \"timeout\")\n return new Promise<Outcome>((_, reject) =>\n signal.addEventListener(\"abort\", () => reject(new Error(\"aborted at the timeout\"))),\n );\n if (now === \"hang\")\n return new Promise<Outcome>((resolve) => {\n const end = (): void => {\n hung.delete(end);\n resolve(\"ok\");\n };\n hung.add(end);\n });\n return now;\n },\n };\n return script;\n};\n\n/** A quiet server is a TCP echo server that can stop its answers and keep its connections open. */\nexport interface QuietServer {\n readonly port: number;\n /** This method stops the answers. The connections stay open, and the server reads and discards the data. */\n quiet(): void;\n /** This method starts the answers again. */\n speak(): void;\n /** This count is the number of open connections. */\n readonly connections: number;\n close(): Promise<void>;\n}\n\n/**\n * This function starts a quiet server on the loopback address. A check against it passes while it\n * answers. When it goes quiet, the check sees a connection that is open but silent. A frozen peer\n * and a black hole that keeps the connection look the same.\n */\nexport const quietServer = async (): Promise<QuietServer> => {\n let silent = false;\n const sockets = new Set<Socket>();\n const server: Server = createServer((socket) => {\n sockets.add(socket);\n socket.on(\"close\", () => sockets.delete(socket));\n socket.on(\"error\", () => sockets.delete(socket));\n socket.on(\"data\", (chunk) => {\n if (!silent) socket.write(chunk);\n });\n });\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(0, \"127.0.0.1\", () => resolve());\n });\n return {\n port: (server.address() as AddressInfo).port,\n quiet: () => {\n silent = true;\n },\n speak: () => {\n silent = false;\n },\n get connections() {\n return sockets.size;\n },\n close: () =>\n new Promise<void>((resolve) => {\n for (const socket of sockets) socket.destroy();\n server.close(() => resolve());\n }),\n };\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"testing.js","names":[],"sources":["../src/fakes.ts","../src/contract.ts","../src/conformance.ts","../src/testing.ts"],"sourcesContent":["// The fakes of the runtime for tests: a fake clock gives exact, repeatable timing, a seeded random\n// gives the same jitter on each run, and a fake process takes signals and keeps the exit codes.\n\nimport type { Cancel, Clock } from \"./clock.ts\";\nimport type { ExitSignal, ExitTrigger, ProcessPort } from \"./process.ts\";\n\n/** A fake clock moves only when a test tells it to. */\nexport interface FakeClock extends Clock {\n /** This method moves the clock forward by `ms`, and it starts each timer that comes due, in sequence. */\n advance(ms: number): Promise<void>;\n /**\n * This method moves the clock forward by `ms` and starts no timer, as a blocked event loop does.\n * The next `advance` then starts each timer that came due during the block, late.\n */\n block(ms: number): void;\n /** This count is the number of timers that are not stopped and not called. */\n readonly pending: number;\n}\n\n/** This function lets the promises that are ready continue, before the clock moves again. */\nconst settle = async (): Promise<void> => {\n for (let turn = 0; turn < 64; turn += 1) await Promise.resolve();\n};\n\n/** This function makes a fake clock. Its time starts at `start` milliseconds. */\nexport const fakeClock = (start = 0): FakeClock => {\n let now = start;\n let sequence = 0;\n const timers: Array<{ at: number; order: number; run: () => void; live: boolean }> = [];\n const due = (end: number) => {\n let best: (typeof timers)[number] | undefined;\n for (const timer of timers)\n if (timer.live && timer.at <= end && (best === undefined || timer.at < best.at || (timer.at === best.at && timer.order < best.order)))\n best = timer;\n return best;\n };\n return {\n now: () => now,\n after(ms, run): Cancel {\n const timer = { at: now + Math.max(0, ms), order: sequence++, run, live: true };\n timers.push(timer);\n return () => {\n timer.live = false;\n };\n },\n async advance(ms) {\n // a monotonic clock does not go back\n const end = now + Math.max(0, ms);\n await settle();\n for (;;) {\n const timer = due(end);\n if (timer === undefined) break;\n timer.live = false;\n // a timer that came due during a block runs late, at the time of the clock\n now = Math.max(now, timer.at);\n timer.run();\n await settle();\n }\n now = Math.max(now, end);\n for (let at = timers.length - 1; at >= 0; at -= 1) if (!timers[at]!.live) timers.splice(at, 1);\n await settle();\n },\n block(ms) {\n now += Math.max(0, ms);\n },\n get pending() {\n return timers.filter((timer) => timer.live).length;\n },\n };\n};\n\n/** A fake process receives triggers from a test, and keeps each exit code in place of an exit. */\nexport interface FakeProcess extends ProcessPort {\n /** This method sends a signal to each handler of it. */\n signal(name: ExitSignal): void;\n /** This method sends a trigger to each handler of it: a signal, or a process trigger, for example `stdin-close`. */\n trigger(name: ExitTrigger): void;\n /** This method sends a message of the IPC channel to each handler of the trigger `ipc-message`. */\n message(value: unknown): void;\n /** These are the exit codes, in sequence. A correct runtime exits one time. */\n readonly exits: readonly number[];\n /** This count is the number of handlers that are registered. */\n readonly handlers: number;\n /** These are the triggers that have a handler, in the sequence of their first handler. */\n readonly triggers: readonly ExitTrigger[];\n /** These are the times of the clock of the options at each flush and each exit, when the options give a clock. */\n readonly log: readonly string[];\n /** This count is the number of flushes of the output. */\n readonly flushes: number;\n /** This count is the number of holds of the process that are not released. */\n readonly holds: number;\n /** These counts are the holds that were not released at each exit, in sequence. */\n readonly heldAtExit: readonly number[];\n}\n\n/** These options shape a fake process. */\nexport interface FakeProcessOptions {\n /** This function is the flush of the output, for example a wait on a fake clock. Without it, a flush ends at once. */\n readonly flush?: (() => Promise<void> | void) | undefined;\n /** This clock stamps each line of the log. */\n readonly clock?: Clock | undefined;\n}\n\n/** This function makes a fake process. */\nexport const fakeProcess = (options: FakeProcessOptions = {}): FakeProcess => {\n const handlers = new Map<ExitTrigger, Set<(message?: unknown) => void>>();\n const exits: number[] = [];\n const log: string[] = [];\n let flushes = 0;\n let holds = 0;\n const heldAtExit: number[] = [];\n const stamp = (what: string): void => {\n log.push(options.clock === undefined ? what : `${options.clock.now()} ${what}`);\n };\n const send = (name: ExitTrigger, message?: unknown): void => {\n for (const handler of [...(handlers.get(name) ?? [])]) handler(message);\n };\n return {\n on(trigger, handler) {\n const set = handlers.get(trigger) ?? new Set();\n set.add(handler);\n handlers.set(trigger, set);\n return () => {\n set.delete(handler);\n };\n },\n async flush() {\n stamp(\"flush\");\n await options.flush?.();\n flushes += 1;\n stamp(\"flushed\");\n },\n hold() {\n holds += 1;\n let held = true;\n return () => {\n if (!held) return;\n held = false;\n holds -= 1;\n };\n },\n exit(code) {\n stamp(`exit ${code}`);\n heldAtExit.push(holds);\n exits.push(code);\n },\n signal: (name) => send(name),\n trigger: (name) => send(name),\n message: (value) => send(\"ipc-message\", value),\n get exits() {\n return [...exits];\n },\n get handlers() {\n let count = 0;\n for (const set of handlers.values()) count += set.size;\n return count;\n },\n get triggers() {\n return [...handlers.keys()];\n },\n get log() {\n return [...log];\n },\n get flushes() {\n return flushes;\n },\n get holds() {\n return holds;\n },\n get heldAtExit() {\n return [...heldAtExit];\n },\n };\n};\n\n/** This function gives a seeded random number generator (mulberry32), with numbers in [0, 1). */\nexport const seeded = (seed: number): (() => number) => {\n let state = seed >>> 0;\n return () => {\n state = (state + 0x6d2b79f5) >>> 0;\n let t = state;\n t = Math.imul(t ^ (t >>> 15), t | 1);\n t ^= t + Math.imul(t ^ (t >>> 7), t | 61);\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n };\n};\n","import { valueIn } from \"@mark1russell7/quantity\";\nimport type { ServiceBuilder } from \"./builder.ts\";\nimport type { Check, ServiceDescription } from \"./description.ts\";\nimport type { FakeClock } from \"./fakes.ts\";\nimport { fakeClock, seeded } from \"./fakes.ts\";\nimport type { Health, HealthVerdict, StartOptions } from \"./health.ts\";\nimport { failSpan, recoverSpan } from \"./timing.ts\";\n\n/** A contract case is one generated case of the contract test of a declaration. It throws on a failure. */\nexport interface ContractCase {\n readonly name: string;\n run(): Promise<void>;\n}\n\n/** These options tune the contract test. */\nexport interface ContractOptions {\n /** This number is the latency that a degraded dependency adds to each check, in milliseconds. The default of the contract is 100. */\n readonly degradeBy?: number | undefined;\n /** This number is the seed of the jitter. */\n readonly seed?: number | undefined;\n}\n\nconst ms = (amount: { readonly value: number; readonly unit: string }): number => valueIn(amount, \"ms\");\n\n/** The part of a verdict that the contract compares: what a caller and the platform see. */\nconst visible = (verdict: HealthVerdict) => ({\n ready: verdict.ready,\n alive: verdict.alive,\n controllers: verdict.controllers,\n dependencies: verdict.dependencies,\n});\n\nconst same = (expected: unknown, actual: unknown): boolean => JSON.stringify(expected) === JSON.stringify(actual);\n\nconst failure = (what: string, expected: unknown, actual: unknown): Error =>\n new Error(`${what}: expected ${JSON.stringify(expected)}, but the verdict was ${JSON.stringify(actual)}`);\n\n/**\n * This function generates the contract test of a declaration. `declare` gives a new builder with\n * the declaration and the function of each check, each time. The checks must pass at rest. The\n * cases are:\n * - The service at rest: each controller and each dependency is ready, and the service is alive.\n * - Each dependency unavailable: its polls fail at once (the force-fail list). The dependency and\n * each controller that needs it become not ready, and the other controllers stay ready. Liveness\n * stays, unless a liveness check exercises the dependency.\n * - Each dependency degraded: each call of its checks takes the latency longer. Nothing can fail.\n * - Each dependency recovers: after it is unavailable, the service is again at rest.\n * - A negative control: a client that the declaration does not name fails, and nothing changes.\n *\n * A dependency that no check exercises is a finding: the service cannot see it fail.\n */\nexport const contractCases = (declare: () => ServiceBuilder, options: ContractOptions = {}): ContractCase[] => {\n const degradeBy = options.degradeBy ?? 100;\n const seed = options.seed ?? 1;\n const description: ServiceDescription = declare().describe();\n const service = description.name;\n const gating = description.checks.filter((check) => check.gates !== null);\n const timingOf = (check: Check) => description.policies[check.gates!];\n const failMax = (check: Check): number => ms(failSpan(timingOf(check), check.poll, \"error\").max);\n const recoverMax = (check: Check): number => ms(recoverSpan(timingOf(check), check.poll).max);\n const most = (values: readonly number[]): number => values.reduce((a, b) => Math.max(a, b), 0);\n const settleTime = most(gating.filter((check) => check.gates === \"readiness\").map(recoverMax)) + 1;\n const quietTime = most(gating.map(failMax)) + 1;\n\n /** This function starts the declaration on a fake clock, and moves it to rest. */\n const boot = async (\n extra: (clock: FakeClock) => Partial<StartOptions> = () => ({}),\n ): Promise<{ health: Health; clock: FakeClock; rest: ReturnType<typeof visible> }> => {\n const clock = fakeClock();\n // the warm-up sweep can wait on the clock (a degraded check), thus the clock moves while it starts\n const starting = declare().start({ clock, random: seeded(seed), ...extra(clock) });\n let started = false;\n void starting.then(\n () => (started = true),\n () => (started = true),\n );\n for (let step = 0; step < 1000 && !started; step += 1) await clock.advance(10);\n const health = await starting;\n await clock.advance(settleTime);\n return { health, clock, rest: visible(health.verdict()) };\n };\n const stop = (health: Health): void => health.stop();\n\n const cases: ContractCase[] = [\n {\n name: `${service}: at rest, each controller and each dependency is ready, and the service is alive`,\n async run() {\n const { health, rest } = await boot();\n try {\n const expected = {\n ready: true,\n alive: true,\n controllers: Object.fromEntries(description.controllers.map((each) => [each.name, true])),\n dependencies: Object.fromEntries(description.dependencies.map((each) => [each.name, true])),\n };\n if (!same(expected, rest)) throw failure(\"at rest\", expected, rest);\n } finally {\n stop(health);\n }\n },\n },\n ];\n\n for (const dependency of description.dependencies) {\n const on = gating.filter((check) => check.dependency === dependency.name);\n const readiness = on.filter((check) => check.gates === \"readiness\");\n const liveness = on.filter((check) => check.gates === \"liveness\");\n const unavailable = (rest: ReturnType<typeof visible>) => ({\n ready: readiness.length === 0 ? rest.ready : false,\n alive: liveness.length === 0 ? rest.alive : false,\n controllers: Object.fromEntries(\n description.controllers.map((each) => [\n each.name,\n each.requires.includes(dependency.name) && readiness.length > 0 ? false : rest.controllers[each.name],\n ]),\n ),\n dependencies: { ...rest.dependencies, [dependency.name]: readiness.length === 0 },\n });\n\n cases.push({\n name: `${service}: ${dependency.name} unavailable`,\n async run() {\n if (on.length === 0)\n throw new Error(`no check that gates a policy exercises ${dependency.name}: the service cannot see it fail`);\n const { health, clock, rest } = await boot();\n try {\n health.forceFail(dependency.name, true);\n await clock.advance(most(on.map(failMax)) + 1);\n const now = visible(health.verdict());\n const expected = unavailable(rest);\n if (!same(expected, now)) throw failure(`${dependency.name} unavailable`, expected, now);\n } finally {\n stop(health);\n }\n },\n });\n\n cases.push({\n name: `${service}: ${dependency.name} degraded by ${degradeBy} ms, and nothing fails`,\n async run() {\n const tight = on.filter((check) => ms(check.poll.timeout) <= degradeBy);\n if (tight.length > 0)\n throw new Error(\n `${tight.map((check) => `${check.name} times out in ${ms(check.poll.timeout)} ms`).join(\", \")}: a latency of ${degradeBy} ms fails it`,\n );\n const { health, clock, rest } = await boot((at) => ({\n wrapCheck: (check, run) =>\n check.dependency !== dependency.name\n ? run\n : (signal) => new Promise((resolve) => at.after(degradeBy, () => resolve(run(signal)))),\n }));\n try {\n await clock.advance(quietTime + most(on.map((check) => ms(check.poll.interval))));\n const now = visible(health.verdict());\n if (!same(rest, now)) throw failure(`${dependency.name} degraded`, rest, now);\n } finally {\n stop(health);\n }\n },\n });\n\n cases.push({\n name: `${service}: ${dependency.name} recovers`,\n async run() {\n if (on.length === 0)\n throw new Error(`no check that gates a policy exercises ${dependency.name}: the service cannot see it recover`);\n const { health, clock, rest } = await boot();\n try {\n health.forceFail(dependency.name, true);\n await clock.advance(most(on.map(failMax)) + 1);\n health.forceFail(dependency.name, false);\n await clock.advance(most(on.map(recoverMax)) + 1);\n const now = visible(health.verdict());\n if (!same(rest, now)) throw failure(`${dependency.name} recovered`, rest, now);\n } finally {\n stop(health);\n }\n },\n });\n }\n\n const never = `${service}.client-never-registered`;\n cases.push({\n name: `${service}: a client never registered fails, and nothing changes (the negative control)`,\n async run() {\n if (description.dependencies.some((each) => each.name === never))\n throw new Error(`the declaration has a dependency named ${never}`);\n const { health, clock, rest } = await boot();\n try {\n health.forceFail(never, true);\n await clock.advance(quietTime);\n const now = visible(health.verdict());\n if (!same(rest, now)) throw failure(\"the negative control\", rest, now);\n } finally {\n stop(health);\n }\n // and a check on a client never registered is refused before the start\n let refused = false;\n try {\n declare()\n .check({\n name: `${never}.check`,\n dependency: never,\n step: \"round-trip\",\n ownConnection: true,\n gates: null,\n poll: gating[0]?.poll ?? { interval: { value: 1, unit: \"s\" }, jitter: 0, timeout: { value: 0.5, unit: \"s\" } },\n })\n .describe();\n } catch {\n refused = true;\n }\n if (!refused) throw new Error(`the declaration accepted a check on ${never}, which is not a dependency`);\n },\n });\n\n return cases;\n};\n","import type { Clock } from \"./clock.ts\";\nimport { realClock } from \"./clock.ts\";\nimport type { ContractCase } from \"./contract.ts\";\nimport { fakeClock } from \"./fakes.ts\";\nimport type { Health } from \"./health.ts\";\nimport type { Lifecycle, TransportImpl } from \"./lifecycle.ts\";\n\n/** A held work is one unit of work that stays in flight until the test releases it. */\nexport interface HeldWork {\n /** This method lets the work end. Its promise ends when the caller of the work has its answer, or its error. */\n release(): Promise<void>;\n /** This flag tells if the work got the cooperative abort of the drain. */\n readonly aborted: boolean;\n}\n\n/**\n * A transport subject is a transport and the client side of its work. The kit offers work through\n * it, holds work in flight through it, and reads the count of the transport.\n */\nexport interface TransportSubject {\n readonly impl: TransportImpl;\n /** This lifecycle goes to `start()`. Without it, the kit gives a detached lifecycle. */\n readonly life?: Lifecycle | undefined;\n /** This method offers one unit of new work. It gives true when the transport took it, and false when it refused it. */\n offer(): Promise<boolean>;\n /** This method starts one unit of work that stays in flight until its release. The promise ends when the transport has it. */\n hold(): Promise<HeldWork>;\n /** This method frees what the subject holds outside the transport: its clients and their sockets. */\n dispose?(): Promise<void> | void;\n}\n\n/**\n * This function gives a detached lifecycle: a lifecycle with no service, for the start of a\n * transport in a test. Its state is `running`, it keeps each connection, and nothing is in flight. Each request\n * and each use of its health throws, because no service declares a route.\n */\nexport const detachedLifecycle = (): Lifecycle => {\n const none = (): never => {\n throw new Error(\"a detached lifecycle has no service: give the transport the lifecycle of a service\");\n };\n const health = { verdict: none, probe: none, subscribe: none, forceFail: none, drain: none, stop: none } as Health;\n return {\n health,\n state: \"running\",\n exitCode: null,\n errors: [],\n started: Promise.resolve(),\n done: new Promise<number>(() => {}),\n signal: new AbortController().signal,\n shutdown: none,\n request: none,\n inFlight: () => 0,\n keepAlive: () => true,\n };\n};\n\n/** A subject factory makes a new subject for each case, on the clock of the case. */\nexport type SubjectFactory = (clock: Clock) => TransportSubject | Promise<TransportSubject>;\n\n/** A conformance record is one measurement of a case, for the raw records of a run in CI. */\nexport interface ConformanceRecord {\n readonly case: string;\n readonly what: string;\n /** This time is the limit of the contract, in milliseconds. */\n readonly expected: number;\n /** This time is what the case measured, in milliseconds of the real clock. */\n readonly measured: number;\n}\n\n/** These options tune the conformance kit. */\nexport interface ConformanceOptions {\n /** This name goes before the name of each case. */\n readonly name?: string | undefined;\n /** This clock moves the cases: `fake` gives exact and fast cases, and `real` measures the timing. The default is `fake`. */\n readonly clock?: \"fake\" | \"real\" | undefined;\n /** This time is the cap of each drain of the cases, in milliseconds. The default is 1000. */\n readonly cap?: number | undefined;\n /** This time is the most that a close can take, in milliseconds of the real clock. The default is 1000. */\n readonly closeBound?: number | undefined;\n /** This time is the most that an event can take to reach the transport, in real milliseconds. The default is 100. */\n readonly slack?: number | undefined;\n /** False tells that the work of the transport cannot see the cooperative abort. The default is true. */\n readonly cooperative?: boolean | undefined;\n /** This count is the number of works that the count case holds. The default is 3. */\n readonly holds?: number | undefined;\n /** This function receives each measurement, for the raw records of a run. */\n readonly record?: ((record: ConformanceRecord) => void) | undefined;\n}\n\ninterface Time {\n readonly clock: Clock;\n pass(ms: number): Promise<void>;\n}\n\ninterface Tracked {\n readonly settled: boolean;\n readonly error: unknown;\n readonly failed: boolean;\n}\n\nconst realNow = (): number => performance.now();\nconst turn = (): Promise<void> => new Promise((resolve) => setTimeout(resolve, 0));\nconst sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));\n\n/** This function waits on the real clock, until the test is true or the bound ends. It gives the test at the end. */\nconst within = async (bound: number, test: () => boolean): Promise<boolean> => {\n const end = realNow() + bound;\n for (;;) {\n if (test()) return true;\n if (realNow() > end) return test();\n await turn();\n }\n};\n\n/** This function lets the events that are ready reach the transport, with no move of the clock of the case. */\nconst quiet = async (): Promise<void> => {\n for (let at = 0; at < 8; at += 1) await turn();\n};\n\n/** This function uses a method of the transport, and gives a throw of the method as a rejection. */\nconst attempt = (call: () => Promise<void> | void): Promise<void> => {\n try {\n return Promise.resolve(call());\n } catch (error) {\n return Promise.reject(error);\n }\n};\n\nconst track = (promise: Promise<unknown>): Tracked => {\n const state = { settled: false, error: undefined as unknown, failed: false };\n promise.then(\n () => {\n state.settled = true;\n },\n (error: unknown) => {\n state.settled = true;\n state.failed = true;\n state.error = error;\n },\n );\n return state;\n};\n\n/**\n * This function generates the conformance cases of a transport (S6 A9). `make` gives a new subject\n * for each case. Each case throws on a failure, and its message names the part of the contract.\n * - The start: nothing is in flight, and the transport takes work.\n * - The count: it follows each work from its events, with no move of the clock.\n * - `stopIntake` refuses new work, keeps the work in flight, and a second stop has no effect.\n * - `drain` ends at once with no work, ends with the last work, and holds to its deadline and not\n * past it. It gives the cooperative abort to the work, and it does not throw.\n * - `close` ends the work in flight in its bound, and a second close has no effect.\n * - The stages in their sequence, as the lifecycle does them.\n */\nexport const transportConformance = (make: SubjectFactory, options: ConformanceOptions = {}): ContractCase[] => {\n const name = options.name ?? \"transport\";\n const fake = (options.clock ?? \"fake\") === \"fake\";\n const cap = options.cap ?? 1000;\n const closeBound = options.closeBound ?? 1000;\n const slack = options.slack ?? 100;\n const cooperative = options.cooperative ?? true;\n const holds = options.holds ?? 3;\n\n const timeOf = (): Time => {\n if (!fake) return { clock: realClock, pass: sleep };\n const clock = fakeClock();\n return { clock, pass: (ms) => clock.advance(ms) };\n };\n\n const check = (ok: boolean, what: string): void => {\n if (!ok) throw new Error(`${name}: ${what}`);\n };\n\n const each = (title: string, body: (subject: TransportSubject, time: Time, title: string) => Promise<void>): ContractCase => ({\n name: `${name}: ${title}`,\n async run() {\n const time = timeOf();\n const subject = await make(time.clock);\n try {\n await subject.impl.start(new AbortController().signal, subject.life ?? detachedLifecycle());\n await body(subject, time, title);\n } finally {\n try {\n await subject.impl.close();\n } finally {\n await subject.dispose?.();\n }\n }\n },\n });\n\n const count = (subject: TransportSubject, expected: number, what: string): Promise<void> =>\n within(slack, () => subject.impl.inFlight() === expected).then((ok) =>\n check(ok, `${what}: inFlight() gives ${subject.impl.inFlight()}, not ${expected}`),\n );\n\n const closes = async (subject: TransportSubject, title: string): Promise<void> => {\n const began = realNow();\n const closed = track(attempt(() => subject.impl.close()));\n const inBound = await within(closeBound, () => closed.settled);\n const took = realNow() - began;\n options.record?.({ case: title, what: \"close\", expected: closeBound, measured: took });\n check(inBound, `close did not end in its bound of ${closeBound} ms`);\n check(!closed.failed, `close threw: ${String(closed.error)}`);\n };\n\n const cases: ContractCase[] = [\n each(\"nothing is in flight after the start, and it takes new work\", async (subject) => {\n check(subject.impl.inFlight() === 0, `inFlight() gives ${subject.impl.inFlight()} after the start, not 0`);\n check(await subject.offer(), \"the transport refused new work after its start\");\n await count(subject, 0, \"the work ended\");\n }),\n\n each(\"the count follows each work in flight from its events, with no move of the clock\", async (subject) => {\n const held: HeldWork[] = [];\n for (let at = 1; at <= holds; at += 1) {\n held.push(await subject.hold());\n await count(subject, at, `${at} works are in flight`);\n }\n for (let at = holds - 1; at >= 0; at -= 1) {\n await held[at]!.release();\n await count(subject, at, `${at} works are in flight after a release`);\n }\n }),\n\n each(\"stopIntake refuses new work, keeps the work in flight, and a second stop has no effect\", async (subject) => {\n const work = await subject.hold();\n await subject.impl.stopIntake();\n check(!(await subject.offer()), \"the transport took new work after stopIntake\");\n await count(subject, 1, \"stopIntake cut the work in flight\");\n await subject.impl.stopIntake();\n check(!(await subject.offer()), \"the transport took new work after a second stopIntake\");\n await work.release();\n await count(subject, 0, \"the work in flight ended after stopIntake\");\n }),\n\n each(\"drain ends at once when nothing is in flight\", async (subject, time) => {\n await subject.impl.stopIntake();\n const drained = track(attempt(() => subject.impl.drain(time.clock.now() + cap, new AbortController().signal)));\n check(await within(slack, () => drained.settled), \"drain did not end with nothing in flight\");\n check(!drained.failed, `drain threw: ${String(drained.error)}`);\n }),\n\n each(\"drain ends when the last work ends, before its deadline\", async (subject, time) => {\n const work = await subject.hold();\n await subject.impl.stopIntake();\n const deadline = time.clock.now() + cap;\n const drained = track(attempt(() => subject.impl.drain(deadline, new AbortController().signal)));\n await time.pass(cap / 4);\n await quiet();\n check(!drained.settled, \"drain ended while work was in flight, before its deadline\");\n await work.release();\n check(await within(slack, () => drained.settled), \"drain did not end when the last work ended\");\n check(!drained.failed, `drain threw: ${String(drained.error)}`);\n check(time.clock.now() < deadline, \"drain waited until its deadline after the last work ended\");\n }),\n\n each(\"drain holds to its deadline and not past it, and does not throw\", async (subject, time, title) => {\n await subject.hold();\n await subject.impl.stopIntake();\n const began = realNow();\n const deadline = time.clock.now() + cap;\n const drained = track(attempt(() => subject.impl.drain(deadline, new AbortController().signal)));\n if (fake) {\n await time.pass(cap - 1);\n await quiet();\n check(!drained.settled, \"drain ended before its deadline, with work in flight\");\n await time.pass(1);\n check(await within(slack, () => drained.settled), \"drain did not end at its deadline\");\n } else {\n const ended = await within(cap + slack, () => drained.settled);\n const took = realNow() - began;\n options.record?.({ case: title, what: \"drain to its deadline\", expected: cap, measured: took });\n check(ended, `drain did not end in ${cap + slack} ms, with a deadline at ${cap} ms`);\n check(took >= cap - slack, `drain ended after ${Math.round(took)} ms, before its deadline at ${cap} ms`);\n }\n check(!drained.failed, `drain threw at its deadline: ${String(drained.error)}`);\n }),\n\n ...(cooperative\n ? [\n each(\"drain gives the cooperative abort to the work in flight\", async (subject, time) => {\n const work = await subject.hold();\n await subject.impl.stopIntake();\n const abort = new AbortController();\n const drained = track(attempt(() => subject.impl.drain(time.clock.now() + cap, abort.signal)));\n await time.pass(cap / 2);\n await quiet();\n check(!work.aborted, \"the work got an abort before the signal of the drain aborted\");\n abort.abort(new Error(\"the cooperative abort of the conformance kit\"));\n check(await within(slack, () => work.aborted), \"the work did not get the cooperative abort\");\n await time.pass(cap / 2);\n check(await within(slack, () => drained.settled), \"drain did not end at its deadline after the cooperative abort\");\n check(!drained.failed, `drain threw after the cooperative abort: ${String(drained.error)}`);\n }),\n ]\n : []),\n\n each(\"drain with an aborted signal and a deadline in the past ends at once\", async (subject, time) => {\n const work = await subject.hold();\n await subject.impl.stopIntake();\n const abort = new AbortController();\n abort.abort(new Error(\"the cooperative abort of the conformance kit\"));\n const drained = track(attempt(() => subject.impl.drain(time.clock.now() - 1, abort.signal)));\n check(await within(slack, () => drained.settled), \"drain did not end at once with its deadline in the past\");\n check(!drained.failed, `drain threw: ${String(drained.error)}`);\n if (cooperative) check(await within(slack, () => work.aborted), \"the work did not get an abort that came before the drain\");\n }),\n\n each(\"close ends the work in flight, in its bound\", async (subject, _time, title) => {\n for (let at = 0; at < holds; at += 1) await subject.hold();\n await count(subject, holds, \"the works are in flight before the close\");\n await closes(subject, title);\n await count(subject, 0, \"close left work in flight\");\n }),\n\n each(\"close is idempotent: a second close has no effect\", async (subject, _time, title) => {\n await subject.hold();\n const first = track(attempt(() => subject.impl.close()));\n const second = track(attempt(() => subject.impl.close()));\n check(await within(closeBound, () => first.settled && second.settled), \"two closes did not end in the bound\");\n check(!first.failed && !second.failed, `a close threw: ${String(first.error ?? second.error)}`);\n await closes(subject, title);\n await count(subject, 0, \"the closes left work in flight\");\n }),\n\n each(\"close right after the start ends in its bound\", async (subject, _time, title) => {\n await closes(subject, title);\n check(subject.impl.inFlight() === 0, \"inFlight() is not 0 after a close with no work\");\n }),\n\n each(\"the stages in their sequence: stop intake, drain with its cooperative abort, close\", async (subject, time, title) => {\n const short = await subject.hold();\n const long = await subject.hold();\n await subject.impl.stopIntake();\n check(!(await subject.offer()), \"the transport took new work after stopIntake\");\n const abort = new AbortController();\n const drained = track(attempt(() => subject.impl.drain(time.clock.now() + cap, abort.signal)));\n await short.release();\n await count(subject, 1, \"one work ended in the drain\");\n await time.pass(cap / 2);\n abort.abort(new Error(\"the cooperative abort of the conformance kit\"));\n if (cooperative) check(await within(slack, () => long.aborted), \"the long work did not get the cooperative abort\");\n await time.pass(cap / 2);\n check(await within(slack, () => drained.settled), \"drain did not end at its deadline\");\n await closes(subject, title);\n await count(subject, 0, \"close left work in flight\");\n }),\n ];\n return cases;\n};\n","// @mark1russell7/service/testing: the seams of the runtime for tests. A fake clock gives exact,\n// repeatable timing. A seeded random gives the same jitter on each run. A fake process takes\n// signals and keeps the exit codes. A quiet server answers until it goes quiet, and then it holds\n// its connections open without an answer. `contractCases` generates the contract test of a\n// declaration, and `transportConformance` generates the conformance test of a transport.\n\nimport type { AddressInfo, Server, Socket } from \"node:net\";\nimport { createServer } from \"node:net\";\nimport type { Outcome } from \"./latch.ts\";\nimport type { CheckFn } from \"./poller.ts\";\n\nexport type { FakeClock, FakeProcess, FakeProcessOptions } from \"./fakes.ts\";\nexport { fakeClock, fakeProcess, seeded } from \"./fakes.ts\";\nexport type { ContractCase, ContractOptions } from \"./contract.ts\";\nexport { contractCases } from \"./contract.ts\";\nexport type {\n ConformanceOptions,\n ConformanceRecord,\n HeldWork,\n SubjectFactory,\n TransportSubject,\n} from \"./conformance.ts\";\nexport { detachedLifecycle, transportConformance } from \"./conformance.ts\";\n\n/**\n * A behavior is what a scripted check does when the runtime starts it:\n * - `\"ok\"`, `\"error\"` and `\"indeterminate\"` give that outcome at once.\n * - `\"throw\"` throws at once.\n * - `\"timeout\"` gives no answer until the runtime aborts the use at the timeout of the poll.\n * - `\"hang\"` gives no answer, and it ignores the abort, until `heal()`.\n */\nexport type Behavior = Outcome | \"throw\" | \"timeout\" | \"hang\";\n\n/** A scripted check is a check function whose behavior a test sets. */\nexport interface ScriptedCheck {\n readonly run: CheckFn;\n /** This behavior applies to each later call. */\n behavior: Behavior;\n /** This count is the number of calls of the check. */\n readonly calls: number;\n /** This count is the number of calls that are pending. */\n readonly hung: number;\n /** This method ends each hung call with a pass, as when a driver gets its answer late. */\n heal(): void;\n}\n\n/** This function makes a scripted check with a behavior at the start. */\nexport const scriptedCheck = (behavior: Behavior = \"ok\"): ScriptedCheck => {\n let calls = 0;\n const hung = new Set<() => void>();\n const script: ScriptedCheck = {\n behavior,\n get calls() {\n return calls;\n },\n get hung() {\n return hung.size;\n },\n heal() {\n for (const end of [...hung]) end();\n },\n run: (signal) => {\n calls += 1;\n const now = script.behavior;\n if (now === \"throw\") throw new Error(\"the scripted check threw\");\n if (now === \"timeout\")\n return new Promise<Outcome>((_, reject) =>\n signal.addEventListener(\"abort\", () => reject(new Error(\"aborted at the timeout\"))),\n );\n if (now === \"hang\")\n return new Promise<Outcome>((resolve) => {\n const end = (): void => {\n hung.delete(end);\n resolve(\"ok\");\n };\n hung.add(end);\n });\n return now;\n },\n };\n return script;\n};\n\n/** A quiet server is a TCP echo server that can stop its answers and keep its connections open. */\nexport interface QuietServer {\n readonly port: number;\n /** This method stops the answers. The connections stay open, and the server reads and discards the data. */\n quiet(): void;\n /** This method starts the answers again. */\n speak(): void;\n /** This count is the number of open connections. */\n readonly connections: number;\n close(): Promise<void>;\n}\n\n/**\n * This function starts a quiet server on the loopback address. A check against it passes while it\n * answers. When it goes quiet, the check sees a connection that is open but silent. A frozen peer\n * and a black hole that keeps the connection look the same.\n */\nexport const quietServer = async (): Promise<QuietServer> => {\n let silent = false;\n const sockets = new Set<Socket>();\n const server: Server = createServer((socket) => {\n sockets.add(socket);\n socket.on(\"close\", () => sockets.delete(socket));\n socket.on(\"error\", () => sockets.delete(socket));\n socket.on(\"data\", (chunk) => {\n if (!silent) socket.write(chunk);\n });\n });\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(0, \"127.0.0.1\", () => resolve());\n });\n return {\n port: (server.address() as AddressInfo).port,\n quiet: () => {\n silent = true;\n },\n speak: () => {\n silent = false;\n },\n get connections() {\n return sockets.size;\n },\n close: () =>\n new Promise<void>((resolve) => {\n for (const socket of sockets) socket.destroy();\n server.close(() => resolve());\n }),\n };\n};\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package",
|
|
3
3
|
"name": "@mark1russell7/service",
|
|
4
|
-
"version": "0.5.
|
|
4
|
+
"version": "0.5.2",
|
|
5
5
|
"description": "A service declared as data before it starts, and its runtime: checks on jittered loops, a latch for each policy, probes from the cache, the startup in its sequence, and a staged, capped drain. The timer-ordering law checks the declaration. Its clock, heartbeat and backoff also work alone, in a browser too.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"service",
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
},
|
|
86
86
|
"devDependencies": {
|
|
87
87
|
"@mark1russell7/cue": "github:mark1russell7/cue",
|
|
88
|
-
"@mark1russell7/harness": "^0.11.
|
|
88
|
+
"@mark1russell7/harness": "^0.11.4",
|
|
89
89
|
"@types/node": "^26.6.4",
|
|
90
90
|
"ioredis": "6.0.0",
|
|
91
91
|
"testcontainers": "12.2.0",
|