@typeonce/effect-machine 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +410 -17
- package/dist/Machine.d.ts +1191 -510
- package/dist/Machine.d.ts.map +1 -1
- package/dist/Machine.js +371 -565
- package/dist/Machine.js.map +1 -1
- package/dist/internal/machine/activities.d.ts +57 -0
- package/dist/internal/machine/activities.d.ts.map +1 -0
- package/dist/internal/machine/activities.js +49 -0
- package/dist/internal/machine/activities.js.map +1 -0
- package/dist/internal/machine/atom.d.ts +69 -0
- package/dist/internal/machine/atom.d.ts.map +1 -0
- package/dist/{AtomMachine.js → internal/machine/atom.js} +20 -95
- package/dist/internal/machine/atom.js.map +1 -0
- package/dist/internal/machine/cluster.d.ts +63 -0
- package/dist/internal/machine/cluster.d.ts.map +1 -0
- package/dist/{ClusterMachine.js → internal/machine/cluster.js} +15 -119
- package/dist/internal/machine/cluster.js.map +1 -0
- package/dist/internal/machine/command.d.ts +16 -0
- package/dist/internal/machine/command.d.ts.map +1 -0
- package/dist/internal/machine/command.js +45 -0
- package/dist/internal/machine/command.js.map +1 -0
- package/dist/internal/machine/commandRuntime.d.ts +13 -0
- package/dist/internal/machine/commandRuntime.d.ts.map +1 -0
- package/dist/internal/machine/commandRuntime.js +16 -0
- package/dist/internal/machine/commandRuntime.js.map +1 -0
- package/dist/internal/{machineModel.d.ts → machine/configuration.d.ts} +12 -61
- package/dist/internal/machine/configuration.d.ts.map +1 -0
- package/dist/internal/{machineModel.js → machine/configuration.js} +250 -545
- package/dist/internal/machine/configuration.js.map +1 -0
- package/dist/internal/{machineErrors.d.ts → machine/errors.d.ts} +8 -8
- package/dist/internal/machine/errors.d.ts.map +1 -0
- package/dist/internal/{machineErrors.js → machine/errors.js} +8 -8
- package/dist/internal/machine/errors.js.map +1 -0
- package/dist/internal/machine/executionPlan.d.ts +49 -0
- package/dist/internal/machine/executionPlan.d.ts.map +1 -0
- package/dist/internal/machine/executionPlan.js +676 -0
- package/dist/internal/machine/executionPlan.js.map +1 -0
- package/dist/internal/machine/invocation.d.ts +23 -0
- package/dist/internal/machine/invocation.d.ts.map +1 -0
- package/dist/internal/machine/invocation.js +77 -0
- package/dist/internal/machine/invocation.js.map +1 -0
- package/dist/internal/machine/machine.d.ts +183 -0
- package/dist/internal/machine/machine.d.ts.map +1 -0
- package/dist/internal/machine/machine.js +552 -0
- package/dist/internal/machine/machine.js.map +1 -0
- package/dist/internal/machine/planner.d.ts +182 -0
- package/dist/internal/machine/planner.d.ts.map +1 -0
- package/dist/internal/machine/planner.js +1082 -0
- package/dist/internal/machine/planner.js.map +1 -0
- package/dist/internal/{machineProcess.d.ts → machine/process.d.ts} +11 -5
- package/dist/internal/machine/process.d.ts.map +1 -0
- package/dist/internal/machine/process.js +498 -0
- package/dist/internal/machine/process.js.map +1 -0
- package/dist/internal/machine/protocol.d.ts +34 -0
- package/dist/internal/machine/protocol.d.ts.map +1 -0
- package/dist/internal/machine/protocol.js +182 -0
- package/dist/internal/machine/protocol.js.map +1 -0
- package/dist/internal/machine/readiness.d.ts +4 -0
- package/dist/internal/machine/readiness.d.ts.map +1 -0
- package/dist/internal/machine/readiness.js +2 -0
- package/dist/internal/machine/readiness.js.map +1 -0
- package/dist/internal/machine/runtime.d.ts +255 -0
- package/dist/internal/machine/runtime.d.ts.map +1 -0
- package/dist/internal/machine/runtime.js +1489 -0
- package/dist/internal/machine/runtime.js.map +1 -0
- package/dist/internal/machine/serialization.d.ts +14 -0
- package/dist/internal/machine/serialization.d.ts.map +1 -0
- package/dist/internal/machine/serialization.js +338 -0
- package/dist/internal/machine/serialization.js.map +1 -0
- package/dist/internal/machine/stateDefinition.d.ts +15 -0
- package/dist/internal/machine/stateDefinition.d.ts.map +1 -0
- package/dist/internal/machine/stateDefinition.js +210 -0
- package/dist/internal/machine/stateDefinition.js.map +1 -0
- package/dist/internal/machine/symbols.d.ts +3 -0
- package/dist/internal/machine/symbols.d.ts.map +1 -0
- package/dist/internal/machine/symbols.js +3 -0
- package/dist/internal/machine/symbols.js.map +1 -0
- package/dist/internal/machine/topology.d.ts +98 -0
- package/dist/internal/machine/topology.d.ts.map +1 -0
- package/dist/internal/machine/topology.js +347 -0
- package/dist/internal/machine/topology.js.map +1 -0
- package/dist/internal/testing/machine/arbitrary.d.ts +36 -0
- package/dist/internal/testing/machine/arbitrary.d.ts.map +1 -0
- package/dist/internal/testing/machine/arbitrary.js +68 -0
- package/dist/internal/testing/machine/arbitrary.js.map +1 -0
- package/dist/internal/testing/machine/exploration.d.ts +30 -0
- package/dist/internal/testing/machine/exploration.d.ts.map +1 -0
- package/dist/internal/testing/machine/exploration.js +237 -0
- package/dist/internal/testing/machine/exploration.js.map +1 -0
- package/dist/internal/testing/machine/finiteModel.d.ts +322 -0
- package/dist/internal/testing/machine/finiteModel.d.ts.map +1 -0
- package/dist/internal/testing/machine/finiteModel.js +976 -0
- package/dist/internal/testing/machine/finiteModel.js.map +1 -0
- package/dist/internal/testing/machine/invariant.d.ts +31 -0
- package/dist/internal/testing/machine/invariant.d.ts.map +1 -0
- package/dist/internal/testing/machine/invariant.js +214 -0
- package/dist/internal/testing/machine/invariant.js.map +1 -0
- package/dist/internal/testing/machine/probe.d.ts +26 -0
- package/dist/internal/testing/machine/probe.d.ts.map +1 -0
- package/dist/internal/testing/machine/probe.js +45 -0
- package/dist/internal/testing/machine/probe.js.map +1 -0
- package/dist/internal/testing/machine/referenceModel.d.ts +203 -0
- package/dist/internal/testing/machine/referenceModel.d.ts.map +1 -0
- package/dist/internal/testing/machine/referenceModel.js +1012 -0
- package/dist/internal/testing/machine/referenceModel.js.map +1 -0
- package/dist/internal/testing/machine/runtime.d.ts +631 -0
- package/dist/internal/testing/machine/runtime.d.ts.map +1 -0
- package/dist/internal/testing/machine/runtime.js +726 -0
- package/dist/internal/testing/machine/runtime.js.map +1 -0
- package/dist/internal/testing/machine/runtimeInvariant.d.ts +32 -0
- package/dist/internal/testing/machine/runtimeInvariant.d.ts.map +1 -0
- package/dist/internal/testing/machine/runtimeInvariant.js +334 -0
- package/dist/internal/testing/machine/runtimeInvariant.js.map +1 -0
- package/dist/internal/testing/machine/trace.d.ts +17 -0
- package/dist/internal/testing/machine/trace.d.ts.map +1 -0
- package/dist/internal/testing/machine/trace.js +94 -0
- package/dist/internal/testing/machine/trace.js.map +1 -0
- package/dist/internal/testing/machine/verification.d.ts +36 -0
- package/dist/internal/testing/machine/verification.d.ts.map +1 -0
- package/dist/internal/testing/machine/verification.js +1375 -0
- package/dist/internal/testing/machine/verification.js.map +1 -0
- package/dist/testing/MachineTest.d.ts +1588 -0
- package/dist/testing/MachineTest.d.ts.map +1 -0
- package/dist/testing/MachineTest.js +406 -0
- package/dist/testing/MachineTest.js.map +1 -0
- package/dist/testing/index.d.ts +7 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +7 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/{ClusterMachine.d.ts → unstable/cluster/ClusterMachine.d.ts} +56 -45
- package/dist/unstable/cluster/ClusterMachine.d.ts.map +1 -0
- package/dist/unstable/cluster/ClusterMachine.js +144 -0
- package/dist/unstable/cluster/ClusterMachine.js.map +1 -0
- package/dist/{cluster.js → unstable/cluster/index.d.ts} +1 -1
- package/dist/unstable/cluster/index.d.ts.map +1 -0
- package/dist/{cluster.d.ts → unstable/cluster/index.js} +1 -1
- package/dist/unstable/cluster/index.js.map +1 -0
- package/dist/{AtomMachine.d.ts → unstable/reactivity/AtomMachine.d.ts} +116 -50
- package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -0
- package/dist/unstable/reactivity/AtomMachine.js +164 -0
- package/dist/unstable/reactivity/AtomMachine.js.map +1 -0
- package/dist/{reactivity.js → unstable/reactivity/index.d.ts} +1 -1
- package/dist/unstable/reactivity/index.d.ts.map +1 -0
- package/dist/{reactivity.d.ts → unstable/reactivity/index.js} +1 -1
- package/dist/unstable/reactivity/index.js.map +1 -0
- package/docs/agent-guide.md +316 -31
- package/package.json +28 -10
- package/dist/AtomMachine.d.ts.map +0 -1
- package/dist/AtomMachine.js.map +0 -1
- package/dist/ClusterMachine.d.ts.map +0 -1
- package/dist/ClusterMachine.js.map +0 -1
- package/dist/cluster.d.ts.map +0 -1
- package/dist/cluster.js.map +0 -1
- package/dist/internal/machineErrors.d.ts.map +0 -1
- package/dist/internal/machineErrors.js.map +0 -1
- package/dist/internal/machineModel.d.ts.map +0 -1
- package/dist/internal/machineModel.js.map +0 -1
- package/dist/internal/machinePlanner.d.ts +0 -74
- package/dist/internal/machinePlanner.d.ts.map +0 -1
- package/dist/internal/machinePlanner.js +0 -797
- package/dist/internal/machinePlanner.js.map +0 -1
- package/dist/internal/machineProcess.d.ts.map +0 -1
- package/dist/internal/machineProcess.js +0 -181
- package/dist/internal/machineProcess.js.map +0 -1
- package/dist/internal/machineRuntime.d.ts +0 -125
- package/dist/internal/machineRuntime.d.ts.map +0 -1
- package/dist/internal/machineRuntime.js +0 -365
- package/dist/internal/machineRuntime.js.map +0 -1
- package/dist/reactivity.d.ts.map +0 -1
- package/dist/reactivity.js.map +0 -1
|
@@ -0,0 +1,726 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Effect-native command-model testing for live machine references.
|
|
3
|
+
*
|
|
4
|
+
* @since 0.4.0
|
|
5
|
+
*/
|
|
6
|
+
import * as Cause from "effect/Cause";
|
|
7
|
+
import * as Data from "effect/Data";
|
|
8
|
+
import * as Deferred from "effect/Deferred";
|
|
9
|
+
import * as Duration from "effect/Duration";
|
|
10
|
+
import * as Effect from "effect/Effect";
|
|
11
|
+
import * as Inspectable from "effect/Inspectable";
|
|
12
|
+
import * as Queue from "effect/Queue";
|
|
13
|
+
import * as Stream from "effect/Stream";
|
|
14
|
+
import { FastCheck, TestClock } from "effect/testing";
|
|
15
|
+
import * as Machine from "../../../Machine.js";
|
|
16
|
+
import { toArbitraryWithReport } from "./arbitrary.js";
|
|
17
|
+
import { assertRuntimeInvariants } from "./runtimeInvariant.js";
|
|
18
|
+
/**
|
|
19
|
+
* Constructs a command that sends one public event.
|
|
20
|
+
*
|
|
21
|
+
* @category constructors
|
|
22
|
+
* @since 0.4.0
|
|
23
|
+
*/
|
|
24
|
+
export const sendCommand = (event) => ({
|
|
25
|
+
_tag: "Send",
|
|
26
|
+
event
|
|
27
|
+
});
|
|
28
|
+
/**
|
|
29
|
+
* Constructs a command that advances Effect's `TestClock`.
|
|
30
|
+
*
|
|
31
|
+
* @category constructors
|
|
32
|
+
* @since 0.4.0
|
|
33
|
+
*/
|
|
34
|
+
export const advanceCommand = (duration) => ({
|
|
35
|
+
_tag: "Advance",
|
|
36
|
+
duration
|
|
37
|
+
});
|
|
38
|
+
/**
|
|
39
|
+
* Constructs an idempotent command that stops the machine.
|
|
40
|
+
*
|
|
41
|
+
* @category constructors
|
|
42
|
+
* @since 0.4.0
|
|
43
|
+
*/
|
|
44
|
+
export const stopCommand = () => ({ _tag: "Stop" });
|
|
45
|
+
/**
|
|
46
|
+
* Constructs a no-op command used to synchronize with work enqueued by earlier
|
|
47
|
+
* commands. Its behavior is selected by the reference-model step.
|
|
48
|
+
*
|
|
49
|
+
* @category constructors
|
|
50
|
+
* @since 0.4.0
|
|
51
|
+
*/
|
|
52
|
+
export const checkpointCommand = (label) => ({
|
|
53
|
+
_tag: "Checkpoint",
|
|
54
|
+
label
|
|
55
|
+
});
|
|
56
|
+
/**
|
|
57
|
+
* Constructors for runtime synchronization policies.
|
|
58
|
+
*
|
|
59
|
+
* @category constructors
|
|
60
|
+
* @since 0.4.0
|
|
61
|
+
*/
|
|
62
|
+
export const RuntimeSynchronization = {
|
|
63
|
+
none: { _tag: "None" },
|
|
64
|
+
current: { _tag: "Current" },
|
|
65
|
+
next: { _tag: "Next" },
|
|
66
|
+
until: (predicate) => ({ _tag: "Until", predicate })
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Failure raised when an expected public change stream observation is absent.
|
|
70
|
+
*
|
|
71
|
+
* @category errors
|
|
72
|
+
* @since 0.4.0
|
|
73
|
+
*/
|
|
74
|
+
export class RuntimeObservationError extends Data.TaggedError("MachineTestRuntimeObservationError") {
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* A typed command-model failure retaining the successfully checked prefix.
|
|
78
|
+
*
|
|
79
|
+
* @category errors
|
|
80
|
+
* @since 0.4.0
|
|
81
|
+
*/
|
|
82
|
+
export class RuntimeCommandFailure extends Data.TaggedError("MachineTestRuntimeCommandFailure") {
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* A typed causal command-model failure retaining the successfully checked
|
|
86
|
+
* prefix and exact attempted command.
|
|
87
|
+
*
|
|
88
|
+
* @category errors
|
|
89
|
+
* @since 0.4.0
|
|
90
|
+
*/
|
|
91
|
+
export class CausalRuntimeCommandFailure extends Data.TaggedError("MachineTestCausalRuntimeCommandFailure") {
|
|
92
|
+
}
|
|
93
|
+
const makeFailure = (options) => new RuntimeCommandFailure({
|
|
94
|
+
...options,
|
|
95
|
+
prefix: options.prefix.slice(),
|
|
96
|
+
attempted: options.attempted
|
|
97
|
+
});
|
|
98
|
+
const executeCommand = (ref, command) => {
|
|
99
|
+
switch (command._tag) {
|
|
100
|
+
case "Send":
|
|
101
|
+
return ref.send(command.event).pipe(Effect.match({
|
|
102
|
+
onFailure: (error) => ({ _tag: "SendRejected", error }),
|
|
103
|
+
onSuccess: () => ({ _tag: "SendAccepted" })
|
|
104
|
+
}));
|
|
105
|
+
case "Advance":
|
|
106
|
+
return TestClock.adjust(command.duration).pipe(Effect.as({ _tag: "ClockAdvanced" }));
|
|
107
|
+
case "Stop":
|
|
108
|
+
return ref.stop.pipe(Effect.as({ _tag: "Stopped" }));
|
|
109
|
+
case "Checkpoint":
|
|
110
|
+
return Effect.succeed({ _tag: "Checkpoint" });
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
const synchronize = (ref, queue, policy, index, timeout) => {
|
|
114
|
+
const wait = (synchronization, effect) => TestClock.withLive(effect.pipe(Effect.timeout(timeout))).pipe(Effect.mapError((cause) => cause instanceof RuntimeObservationError
|
|
115
|
+
? cause
|
|
116
|
+
: new RuntimeObservationError({
|
|
117
|
+
index,
|
|
118
|
+
synchronization,
|
|
119
|
+
reason: "timeout",
|
|
120
|
+
message: `timed out after ${Duration.toMillis(timeout)}ms waiting for the expected published snapshot`
|
|
121
|
+
})));
|
|
122
|
+
switch (policy._tag) {
|
|
123
|
+
case "None":
|
|
124
|
+
return Effect.succeed({ snapshot: undefined, published: [] });
|
|
125
|
+
case "Current":
|
|
126
|
+
return ref.snapshot.pipe(Effect.map((snapshot) => ({ snapshot, published: [] })));
|
|
127
|
+
case "Next":
|
|
128
|
+
return wait("Next", Queue.take(queue).pipe(Effect.flatMap((entry) => entry._tag === "Snapshot"
|
|
129
|
+
? Effect.succeed({ snapshot: entry.snapshot, published: [entry.snapshot] })
|
|
130
|
+
: Effect.fail(new RuntimeObservationError({
|
|
131
|
+
index,
|
|
132
|
+
synchronization: "Next",
|
|
133
|
+
reason: "ended",
|
|
134
|
+
message: "the machine changes stream ended before publishing the expected snapshot"
|
|
135
|
+
})))));
|
|
136
|
+
case "Until":
|
|
137
|
+
return wait("Until", Effect.gen(function* () {
|
|
138
|
+
const published = [];
|
|
139
|
+
while (true) {
|
|
140
|
+
const entry = yield* Queue.take(queue);
|
|
141
|
+
if (entry._tag === "End") {
|
|
142
|
+
return yield* Effect.fail(new RuntimeObservationError({
|
|
143
|
+
index,
|
|
144
|
+
synchronization: "Until",
|
|
145
|
+
reason: "ended",
|
|
146
|
+
message: "the machine changes stream ended before a published snapshot matched the predicate"
|
|
147
|
+
}));
|
|
148
|
+
}
|
|
149
|
+
published.push(entry.snapshot);
|
|
150
|
+
if (policy.predicate(entry.snapshot)) {
|
|
151
|
+
return { snapshot: entry.snapshot, published };
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}));
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
const executeCausalCommand = (probe, command) => {
|
|
158
|
+
switch (command._tag) {
|
|
159
|
+
case "Send":
|
|
160
|
+
return Effect.matchEffect(probe.sendAndAwait(command.event), {
|
|
161
|
+
onFailure: (error) => error instanceof Machine.StoppedError
|
|
162
|
+
? probe.ref.snapshot.pipe(Effect.flatMap((snapshot) => snapshot.status === "stopped"
|
|
163
|
+
? Effect.succeed({ _tag: "SendRejected", error })
|
|
164
|
+
: Effect.fail(error)))
|
|
165
|
+
: Effect.fail(error),
|
|
166
|
+
onSuccess: (step) => Effect.succeed({ _tag: "SendProcessed", step })
|
|
167
|
+
});
|
|
168
|
+
case "Advance":
|
|
169
|
+
return TestClock.adjust(command.duration).pipe(Effect.as({ _tag: "ClockAdvanced" }));
|
|
170
|
+
case "Stop":
|
|
171
|
+
return probe.ref.stop.pipe(Effect.as({ _tag: "Stopped" }));
|
|
172
|
+
case "Checkpoint":
|
|
173
|
+
return Effect.succeed({ _tag: "Checkpoint" });
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
const awaitCausal = (ref, policy, index, timeout) => {
|
|
177
|
+
if (policy._tag === "None") {
|
|
178
|
+
return ref.snapshot.pipe(Effect.map((snapshot) => ({ snapshot, awaited: [] })));
|
|
179
|
+
}
|
|
180
|
+
const observation = Effect.scoped(Effect.gen(function* () {
|
|
181
|
+
const changes = yield* Queue.unbounded();
|
|
182
|
+
yield* ref.changes.pipe(Stream.runForEach((snapshot) => Queue.offer(changes, { _tag: "Snapshot", snapshot })), Effect.ensuring(Queue.offer(changes, { _tag: "End" })), Effect.forkScoped({ startImmediately: true }));
|
|
183
|
+
const awaited = [];
|
|
184
|
+
while (true) {
|
|
185
|
+
const entry = yield* Queue.take(changes);
|
|
186
|
+
if (entry._tag === "End") {
|
|
187
|
+
return yield* Effect.fail(new RuntimeObservationError({
|
|
188
|
+
index,
|
|
189
|
+
synchronization: "Until",
|
|
190
|
+
reason: "ended",
|
|
191
|
+
message: "the machine changes stream ended before an awaited snapshot matched the predicate"
|
|
192
|
+
}));
|
|
193
|
+
}
|
|
194
|
+
awaited.push(entry.snapshot);
|
|
195
|
+
if (policy.predicate(entry.snapshot))
|
|
196
|
+
return { snapshot: entry.snapshot, awaited };
|
|
197
|
+
}
|
|
198
|
+
}));
|
|
199
|
+
return TestClock.withLive(observation.pipe(Effect.timeout(timeout))).pipe(Effect.mapError((cause) => cause instanceof RuntimeObservationError
|
|
200
|
+
? cause
|
|
201
|
+
: new RuntimeObservationError({
|
|
202
|
+
index,
|
|
203
|
+
synchronization: "Until",
|
|
204
|
+
reason: "timeout",
|
|
205
|
+
message: `timed out after ${Duration.toMillis(timeout)}ms waiting for an awaited runtime snapshot`
|
|
206
|
+
})));
|
|
207
|
+
};
|
|
208
|
+
/**
|
|
209
|
+
* Runs typed commands against a live `MachineRef` and checks them against a
|
|
210
|
+
* supplied Effect-native reference model.
|
|
211
|
+
*
|
|
212
|
+
* The runner observes only public `MachineRef` behavior. In particular, a
|
|
213
|
+
* successful send means enqueue acceptance, not completed processing. A model
|
|
214
|
+
* must request `Next`/`Until` only when it predicts a publication, or use an
|
|
215
|
+
* explicit checkpoint to drain previously enqueued work. Machine emissions can
|
|
216
|
+
* be captured by the runtime service used by the machine and returned from the
|
|
217
|
+
* optional `inspect` effect.
|
|
218
|
+
*
|
|
219
|
+
* Typed failures and defects from model transitions, command execution,
|
|
220
|
+
* inspection, and assertions are retained as full `Cause` values. A cause
|
|
221
|
+
* containing only interruption is propagated as interruption so cancelling a
|
|
222
|
+
* property run cannot be mistaken for a machine counterexample.
|
|
223
|
+
*
|
|
224
|
+
* @category constructors
|
|
225
|
+
* @since 0.4.0
|
|
226
|
+
*/
|
|
227
|
+
export const runEnqueuedCommands = (ref, commands, options) => Effect.scoped(Effect.gen(function* () {
|
|
228
|
+
const sequence = Array.from(commands);
|
|
229
|
+
const observationTimeout = options.observationTimeout ?? "1 second";
|
|
230
|
+
const observationTimeoutMillis = Duration.toMillis(observationTimeout);
|
|
231
|
+
if (!Number.isFinite(observationTimeoutMillis) || observationTimeoutMillis < 0) {
|
|
232
|
+
return yield* Effect.die(new Error("MachineTest.runEnqueuedCommands expected observationTimeout to be a finite non-negative duration"));
|
|
233
|
+
}
|
|
234
|
+
const changes = yield* Queue.unbounded();
|
|
235
|
+
const ready = yield* Deferred.make();
|
|
236
|
+
yield* ref.changes.pipe(Stream.runForEach((snapshot) => Queue.offer(changes, { _tag: "Snapshot", snapshot }).pipe(Effect.andThen(Deferred.succeed(ready, undefined)), Effect.asVoid)), Effect.ensuring(Deferred.succeed(ready, undefined).pipe(Effect.andThen(Queue.offer(changes, { _tag: "End" })), Effect.asVoid)), Effect.forkScoped({ startImmediately: true }));
|
|
237
|
+
yield* Deferred.await(ready);
|
|
238
|
+
const initialEntry = yield* Queue.take(changes);
|
|
239
|
+
const initial = initialEntry._tag === "Snapshot" ? initialEntry.snapshot : yield* ref.snapshot;
|
|
240
|
+
const records = [];
|
|
241
|
+
let model = options.initialModel;
|
|
242
|
+
let lastSynchronized = initial;
|
|
243
|
+
let outstandingWorkUnknown = false;
|
|
244
|
+
const capture = (options) => Effect.catchCause(Effect.suspend(options.effect), (cause) => Cause.hasInterruptsOnly(cause)
|
|
245
|
+
? Effect.failCause(cause)
|
|
246
|
+
: Effect.fail(makeFailure({
|
|
247
|
+
phase: options.phase,
|
|
248
|
+
index: options.index,
|
|
249
|
+
command: options.command,
|
|
250
|
+
cause,
|
|
251
|
+
prefix: records,
|
|
252
|
+
...(options.attempted === undefined ? {} : { attempted: options.attempted })
|
|
253
|
+
})));
|
|
254
|
+
for (let index = 0; index < sequence.length; index++) {
|
|
255
|
+
const command = sequence[index];
|
|
256
|
+
const step = yield* capture({
|
|
257
|
+
phase: "model",
|
|
258
|
+
index,
|
|
259
|
+
command,
|
|
260
|
+
effect: () => options.transition(model, command, index)
|
|
261
|
+
});
|
|
262
|
+
model = step.model;
|
|
263
|
+
const result = yield* capture({
|
|
264
|
+
phase: "execution",
|
|
265
|
+
index,
|
|
266
|
+
command,
|
|
267
|
+
effect: () => executeCommand(ref, command)
|
|
268
|
+
});
|
|
269
|
+
const attemptedBeforeObservation = {
|
|
270
|
+
index,
|
|
271
|
+
command,
|
|
272
|
+
model,
|
|
273
|
+
expected: step.expected,
|
|
274
|
+
actual: {
|
|
275
|
+
result,
|
|
276
|
+
snapshot: undefined,
|
|
277
|
+
published: [],
|
|
278
|
+
inspected: undefined
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
const synchronized = yield* capture({
|
|
282
|
+
phase: "observation",
|
|
283
|
+
index,
|
|
284
|
+
command,
|
|
285
|
+
effect: () => synchronize(ref, changes, step.synchronize, index, observationTimeout),
|
|
286
|
+
attempted: attemptedBeforeObservation
|
|
287
|
+
});
|
|
288
|
+
const terminal = synchronized.snapshot !== undefined && synchronized.snapshot.status !== "active";
|
|
289
|
+
const previouslyOutstanding = outstandingWorkUnknown;
|
|
290
|
+
switch (step.synchronize._tag) {
|
|
291
|
+
case "None":
|
|
292
|
+
if (command._tag === "Advance" || command._tag === "Stop" ||
|
|
293
|
+
(command._tag === "Send" && result._tag === "SendAccepted"))
|
|
294
|
+
outstandingWorkUnknown = true;
|
|
295
|
+
break;
|
|
296
|
+
case "Next":
|
|
297
|
+
if (synchronized.snapshot !== undefined)
|
|
298
|
+
lastSynchronized = synchronized.snapshot;
|
|
299
|
+
outstandingWorkUnknown = terminal
|
|
300
|
+
? false
|
|
301
|
+
: previouslyOutstanding || command._tag === "Advance" ||
|
|
302
|
+
(command._tag === "Send" && result._tag === "SendAccepted");
|
|
303
|
+
break;
|
|
304
|
+
case "Until":
|
|
305
|
+
if (synchronized.snapshot !== undefined)
|
|
306
|
+
lastSynchronized = synchronized.snapshot;
|
|
307
|
+
outstandingWorkUnknown = false;
|
|
308
|
+
break;
|
|
309
|
+
case "Current":
|
|
310
|
+
if (terminal)
|
|
311
|
+
outstandingWorkUnknown = false;
|
|
312
|
+
if (!outstandingWorkUnknown && synchronized.snapshot !== undefined)
|
|
313
|
+
lastSynchronized = synchronized.snapshot;
|
|
314
|
+
break;
|
|
315
|
+
}
|
|
316
|
+
const inspectionContext = {
|
|
317
|
+
index,
|
|
318
|
+
command,
|
|
319
|
+
result,
|
|
320
|
+
ref,
|
|
321
|
+
snapshot: synchronized.snapshot,
|
|
322
|
+
published: synchronized.published
|
|
323
|
+
};
|
|
324
|
+
const attemptedBeforeInspection = {
|
|
325
|
+
...attemptedBeforeObservation,
|
|
326
|
+
actual: {
|
|
327
|
+
result,
|
|
328
|
+
snapshot: synchronized.snapshot,
|
|
329
|
+
published: synchronized.published,
|
|
330
|
+
inspected: undefined
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
const inspected = options.inspect === undefined
|
|
334
|
+
? undefined
|
|
335
|
+
: yield* capture({
|
|
336
|
+
phase: "inspection",
|
|
337
|
+
index,
|
|
338
|
+
command,
|
|
339
|
+
effect: () => options.inspect(inspectionContext),
|
|
340
|
+
attempted: attemptedBeforeInspection
|
|
341
|
+
});
|
|
342
|
+
const actual = {
|
|
343
|
+
result,
|
|
344
|
+
snapshot: synchronized.snapshot,
|
|
345
|
+
published: synchronized.published,
|
|
346
|
+
inspected
|
|
347
|
+
};
|
|
348
|
+
const record = {
|
|
349
|
+
index,
|
|
350
|
+
command,
|
|
351
|
+
model,
|
|
352
|
+
expected: step.expected,
|
|
353
|
+
actual
|
|
354
|
+
};
|
|
355
|
+
yield* capture({
|
|
356
|
+
phase: "assertion",
|
|
357
|
+
index,
|
|
358
|
+
command,
|
|
359
|
+
effect: () => options.assert({
|
|
360
|
+
...inspectionContext,
|
|
361
|
+
model,
|
|
362
|
+
expected: step.expected,
|
|
363
|
+
actual
|
|
364
|
+
}),
|
|
365
|
+
attempted: record
|
|
366
|
+
});
|
|
367
|
+
records.push(record);
|
|
368
|
+
}
|
|
369
|
+
return {
|
|
370
|
+
commands: sequence,
|
|
371
|
+
initial,
|
|
372
|
+
records,
|
|
373
|
+
finalModel: model,
|
|
374
|
+
final: lastSynchronized,
|
|
375
|
+
synchronized: !outstandingWorkUnknown
|
|
376
|
+
};
|
|
377
|
+
}));
|
|
378
|
+
/**
|
|
379
|
+
* Compatibility alias for enqueue-oriented runtime command execution.
|
|
380
|
+
*
|
|
381
|
+
* @deprecated Use `runEnqueuedCommands`. This compatibility name does not
|
|
382
|
+
* expose whether sends are merely enqueued or causally processed.
|
|
383
|
+
*
|
|
384
|
+
* @category constructors
|
|
385
|
+
* @since 0.4.0
|
|
386
|
+
*/
|
|
387
|
+
export const runRuntimeCommands = runEnqueuedCommands;
|
|
388
|
+
/**
|
|
389
|
+
* Runs typed commands against a probe and checks them against an Effect-native
|
|
390
|
+
* reference model.
|
|
391
|
+
*
|
|
392
|
+
* Every accepted `Send` completes its exact managed runtime macrostep before
|
|
393
|
+
* inspection, assertion, and the next command. Use `probe.await.until` only
|
|
394
|
+
* for later asynchronous work such as timer, invoke, or child delivery.
|
|
395
|
+
* Processing failures are attributed to the exact submitted command and retain
|
|
396
|
+
* the successfully checked prefix for FastCheck shrinking and replay.
|
|
397
|
+
*
|
|
398
|
+
* Use `runEnqueuedCommands` instead when the behavior under test intentionally
|
|
399
|
+
* depends on burst enqueueing or outstanding mailbox work.
|
|
400
|
+
*
|
|
401
|
+
* @category constructors
|
|
402
|
+
* @since 0.4.0
|
|
403
|
+
*/
|
|
404
|
+
export const runCausalCommands = (probe, commands, options) => Effect.gen(function* () {
|
|
405
|
+
const sequence = Array.from(commands);
|
|
406
|
+
const observationTimeout = options.observationTimeout ?? "1 second";
|
|
407
|
+
const observationTimeoutMillis = Duration.toMillis(observationTimeout);
|
|
408
|
+
if (!Number.isFinite(observationTimeoutMillis) || observationTimeoutMillis < 0) {
|
|
409
|
+
return yield* Effect.die(new Error("MachineTest.runCausalCommands expected observationTimeout to be a finite non-negative duration"));
|
|
410
|
+
}
|
|
411
|
+
const initial = yield* probe.ref.snapshot;
|
|
412
|
+
const records = [];
|
|
413
|
+
let model = options.initialModel;
|
|
414
|
+
let final = initial;
|
|
415
|
+
const capture = (captureOptions) => Effect.catchCause(Effect.suspend(captureOptions.effect), (cause) => Cause.hasInterruptsOnly(cause)
|
|
416
|
+
? Effect.failCause(cause)
|
|
417
|
+
: Effect.fail(new CausalRuntimeCommandFailure({
|
|
418
|
+
phase: captureOptions.phase,
|
|
419
|
+
index: captureOptions.index,
|
|
420
|
+
command: captureOptions.command,
|
|
421
|
+
cause,
|
|
422
|
+
prefix: records.slice(),
|
|
423
|
+
attempted: captureOptions.attempted
|
|
424
|
+
})));
|
|
425
|
+
for (let index = 0; index < sequence.length; index++) {
|
|
426
|
+
const command = sequence[index];
|
|
427
|
+
const step = yield* capture({
|
|
428
|
+
phase: "model",
|
|
429
|
+
index,
|
|
430
|
+
command,
|
|
431
|
+
effect: () => options.transition(model, command, index)
|
|
432
|
+
});
|
|
433
|
+
model = step.model;
|
|
434
|
+
const attemptedBeforeExecution = {
|
|
435
|
+
index,
|
|
436
|
+
command,
|
|
437
|
+
model,
|
|
438
|
+
expected: step.expected,
|
|
439
|
+
result: undefined,
|
|
440
|
+
snapshot: undefined,
|
|
441
|
+
awaited: [],
|
|
442
|
+
inspected: undefined
|
|
443
|
+
};
|
|
444
|
+
const result = yield* capture({
|
|
445
|
+
phase: "execution",
|
|
446
|
+
index,
|
|
447
|
+
command,
|
|
448
|
+
effect: () => executeCausalCommand(probe, command),
|
|
449
|
+
attempted: attemptedBeforeExecution
|
|
450
|
+
});
|
|
451
|
+
const attemptedAfterExecution = {
|
|
452
|
+
...attemptedBeforeExecution,
|
|
453
|
+
result
|
|
454
|
+
};
|
|
455
|
+
const observation = yield* capture({
|
|
456
|
+
phase: "observation",
|
|
457
|
+
index,
|
|
458
|
+
command,
|
|
459
|
+
effect: () => awaitCausal(probe.ref, step.await ?? { _tag: "None" }, index, observationTimeout),
|
|
460
|
+
attempted: attemptedAfterExecution
|
|
461
|
+
});
|
|
462
|
+
final = observation.snapshot;
|
|
463
|
+
const inspectionContext = {
|
|
464
|
+
index,
|
|
465
|
+
command,
|
|
466
|
+
result,
|
|
467
|
+
probe,
|
|
468
|
+
ref: probe.ref,
|
|
469
|
+
snapshot: observation.snapshot,
|
|
470
|
+
awaited: observation.awaited
|
|
471
|
+
};
|
|
472
|
+
const attemptedAfterObservation = {
|
|
473
|
+
...attemptedAfterExecution,
|
|
474
|
+
snapshot: observation.snapshot,
|
|
475
|
+
awaited: observation.awaited
|
|
476
|
+
};
|
|
477
|
+
const inspected = options.inspect === undefined
|
|
478
|
+
? undefined
|
|
479
|
+
: yield* capture({
|
|
480
|
+
phase: "inspection",
|
|
481
|
+
index,
|
|
482
|
+
command,
|
|
483
|
+
effect: () => options.inspect(inspectionContext),
|
|
484
|
+
attempted: attemptedAfterObservation
|
|
485
|
+
});
|
|
486
|
+
const actual = {
|
|
487
|
+
result,
|
|
488
|
+
snapshot: observation.snapshot,
|
|
489
|
+
awaited: observation.awaited,
|
|
490
|
+
inspected
|
|
491
|
+
};
|
|
492
|
+
const record = {
|
|
493
|
+
index,
|
|
494
|
+
command,
|
|
495
|
+
model,
|
|
496
|
+
expected: step.expected,
|
|
497
|
+
actual
|
|
498
|
+
};
|
|
499
|
+
yield* capture({
|
|
500
|
+
phase: "assertion",
|
|
501
|
+
index,
|
|
502
|
+
command,
|
|
503
|
+
effect: () => options.assert({
|
|
504
|
+
...inspectionContext,
|
|
505
|
+
model,
|
|
506
|
+
expected: step.expected,
|
|
507
|
+
actual
|
|
508
|
+
}),
|
|
509
|
+
attempted: {
|
|
510
|
+
...attemptedAfterObservation,
|
|
511
|
+
inspected
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
records.push(record);
|
|
515
|
+
}
|
|
516
|
+
return {
|
|
517
|
+
commands: sequence,
|
|
518
|
+
initial,
|
|
519
|
+
records,
|
|
520
|
+
finalModel: model,
|
|
521
|
+
final
|
|
522
|
+
};
|
|
523
|
+
});
|
|
524
|
+
/**
|
|
525
|
+
* Causally executes commands and checks reusable runtime invariants without
|
|
526
|
+
* requiring a dummy reference model. Use `runCausalCommands` when exact
|
|
527
|
+
* expected results come from an application model.
|
|
528
|
+
*
|
|
529
|
+
* @category constructors
|
|
530
|
+
* @since 0.4.0
|
|
531
|
+
*/
|
|
532
|
+
export const verifyCausalCommands = (probe, commands, options) => Effect.gen(function* () {
|
|
533
|
+
const transcript = yield* runCausalCommands(probe, commands, {
|
|
534
|
+
initialModel: undefined,
|
|
535
|
+
...(options.observationTimeout === undefined ? {} : { observationTimeout: options.observationTimeout }),
|
|
536
|
+
transition: (_model, command, index) => Effect.sync(() => ({
|
|
537
|
+
model: undefined,
|
|
538
|
+
expected: undefined,
|
|
539
|
+
...(options.await === undefined ? {} : { await: options.await({ index, command, probe }) })
|
|
540
|
+
})),
|
|
541
|
+
assert: () => Effect.void
|
|
542
|
+
});
|
|
543
|
+
const evidence = {
|
|
544
|
+
commands: transcript.commands,
|
|
545
|
+
initial: transcript.initial,
|
|
546
|
+
records: transcript.records.map(({ actual, command, index }) => ({ index, command, actual })),
|
|
547
|
+
final: transcript.final
|
|
548
|
+
};
|
|
549
|
+
yield* assertRuntimeInvariants(probe.machine, evidence, options.invariants);
|
|
550
|
+
return evidence;
|
|
551
|
+
});
|
|
552
|
+
const validateCommandLength = (name, value) => {
|
|
553
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
554
|
+
throw new Error(`MachineTest.runtimeCommands expected ${name} to be a non-negative safe integer`);
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
/**
|
|
558
|
+
* Derives a shrinkable command sequence from public event schemas and explicit
|
|
559
|
+
* clock/stop/checkpoint command choices.
|
|
560
|
+
*
|
|
561
|
+
* This deliberately returns ordinary Effect FastCheck arbitraries instead of
|
|
562
|
+
* adapting the runner through `asyncModelRun`: the latter requires Promise
|
|
563
|
+
* callbacks and would erase Effect error and service channels.
|
|
564
|
+
*
|
|
565
|
+
* @category constructors
|
|
566
|
+
* @since 0.4.0
|
|
567
|
+
*/
|
|
568
|
+
export const runtimeCommands = (machine, options = {}) => {
|
|
569
|
+
const minCommands = options.minCommands ?? 0;
|
|
570
|
+
const maxCommands = options.maxCommands ?? 50;
|
|
571
|
+
validateCommandLength("minCommands", minCommands);
|
|
572
|
+
validateCommandLength("maxCommands", maxCommands);
|
|
573
|
+
if (minCommands > maxCommands) {
|
|
574
|
+
throw new Error("MachineTest.runtimeCommands expected minCommands to be less than or equal to maxCommands");
|
|
575
|
+
}
|
|
576
|
+
const reports = [];
|
|
577
|
+
const eventArbitraries = options.eventArbitrary === undefined
|
|
578
|
+
? machine.events.map((schema) => {
|
|
579
|
+
const derived = toArbitraryWithReport(schema);
|
|
580
|
+
reports.push(derived.report);
|
|
581
|
+
return derived.value;
|
|
582
|
+
})
|
|
583
|
+
: [];
|
|
584
|
+
const eventArbitrary = options.eventArbitrary ?? (eventArbitraries.length === 0
|
|
585
|
+
? undefined
|
|
586
|
+
: FastCheck.oneof(...eventArbitraries));
|
|
587
|
+
const commandArbitraries = [];
|
|
588
|
+
if (eventArbitrary !== undefined)
|
|
589
|
+
commandArbitraries.push(eventArbitrary.map(sendCommand));
|
|
590
|
+
if (options.includeAdvance !== false) {
|
|
591
|
+
const advanceArbitrary = options.advanceArbitrary ?? FastCheck.nat({ max: 60_000 });
|
|
592
|
+
commandArbitraries.push(advanceArbitrary.map(advanceCommand));
|
|
593
|
+
}
|
|
594
|
+
if (options.includeStop !== false)
|
|
595
|
+
commandArbitraries.push(FastCheck.constant(stopCommand()));
|
|
596
|
+
if (options.includeCheckpoint !== false)
|
|
597
|
+
commandArbitraries.push(FastCheck.constant(checkpointCommand()));
|
|
598
|
+
commandArbitraries.push(...options.additionalCommands ?? []);
|
|
599
|
+
if (commandArbitraries.length === 0) {
|
|
600
|
+
if (minCommands > 0) {
|
|
601
|
+
throw new Error("MachineTest.runtimeCommands cannot generate a non-empty command sequence without commands");
|
|
602
|
+
}
|
|
603
|
+
return {
|
|
604
|
+
arbitrary: FastCheck.constant([]),
|
|
605
|
+
diagnostics: {
|
|
606
|
+
events: options.eventArbitrary !== undefined ? "override" : eventArbitraries.length === 0 ? "none" : "schema",
|
|
607
|
+
schemaReports: reports,
|
|
608
|
+
includesAdvance: false,
|
|
609
|
+
includesStop: false,
|
|
610
|
+
includesCheckpoint: false
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
return {
|
|
615
|
+
arbitrary: FastCheck.array(FastCheck.oneof(...commandArbitraries), {
|
|
616
|
+
minLength: minCommands,
|
|
617
|
+
maxLength: maxCommands
|
|
618
|
+
}),
|
|
619
|
+
diagnostics: {
|
|
620
|
+
events: options.eventArbitrary !== undefined ? "override" : eventArbitraries.length === 0 ? "none" : "schema",
|
|
621
|
+
schemaReports: reports,
|
|
622
|
+
includesAdvance: options.includeAdvance !== false,
|
|
623
|
+
includesStop: options.includeStop !== false,
|
|
624
|
+
includesCheckpoint: options.includeCheckpoint !== false
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
};
|
|
628
|
+
/**
|
|
629
|
+
* Formats a runtime transcript or failure as replayable line-oriented evidence.
|
|
630
|
+
*
|
|
631
|
+
* @category formatting
|
|
632
|
+
* @since 0.4.0
|
|
633
|
+
*/
|
|
634
|
+
export const formatEnqueuedTranscript = (value) => {
|
|
635
|
+
const failure = value instanceof RuntimeCommandFailure;
|
|
636
|
+
const records = failure ? value.prefix : value.records;
|
|
637
|
+
const lines = [
|
|
638
|
+
`commands: ${Inspectable.toStringUnknown(failure ?
|
|
639
|
+
[
|
|
640
|
+
...value.prefix.map((record) => record.command),
|
|
641
|
+
value.command
|
|
642
|
+
] :
|
|
643
|
+
value.commands, 0)}`
|
|
644
|
+
];
|
|
645
|
+
for (const record of records) {
|
|
646
|
+
lines.push(`command ${record.index}: command=${Inspectable.toStringUnknown(record.command, 0)} ` +
|
|
647
|
+
`model=${Inspectable.toStringUnknown(record.model, 0)} ` +
|
|
648
|
+
`expected=${Inspectable.toStringUnknown(record.expected, 0)} ` +
|
|
649
|
+
`result=${Inspectable.toStringUnknown(record.actual.result, 0)} ` +
|
|
650
|
+
`snapshot=${Inspectable.toStringUnknown(record.actual.snapshot, 0)} ` +
|
|
651
|
+
`published=${Inspectable.toStringUnknown(record.actual.published, 0)} ` +
|
|
652
|
+
`inspected=${Inspectable.toStringUnknown(record.actual.inspected, 0)}`);
|
|
653
|
+
}
|
|
654
|
+
if (failure) {
|
|
655
|
+
if (value.attempted !== undefined) {
|
|
656
|
+
lines.push(`attempted ${value.attempted.index}: command=${Inspectable.toStringUnknown(value.attempted.command, 0)} ` +
|
|
657
|
+
`model=${Inspectable.toStringUnknown(value.attempted.model, 0)} ` +
|
|
658
|
+
`expected=${Inspectable.toStringUnknown(value.attempted.expected, 0)} ` +
|
|
659
|
+
`result=${Inspectable.toStringUnknown(value.attempted.actual.result, 0)} ` +
|
|
660
|
+
`snapshot=${Inspectable.toStringUnknown(value.attempted.actual.snapshot, 0)} ` +
|
|
661
|
+
`published=${Inspectable.toStringUnknown(value.attempted.actual.published, 0)} ` +
|
|
662
|
+
`inspected=${Inspectable.toStringUnknown(value.attempted.actual.inspected, 0)}`);
|
|
663
|
+
}
|
|
664
|
+
lines.push(`failure: phase=${value.phase} index=${value.index} command=${Inspectable.toStringUnknown(value.command, 0)} ` +
|
|
665
|
+
`cause=${Inspectable.toStringUnknown(value.cause, 0)}`);
|
|
666
|
+
}
|
|
667
|
+
else {
|
|
668
|
+
lines.push(`final: synchronized=${String(value.synchronized)} snapshot=${Inspectable.toStringUnknown(value.final, 0)}`);
|
|
669
|
+
}
|
|
670
|
+
return lines.join("\n");
|
|
671
|
+
};
|
|
672
|
+
/**
|
|
673
|
+
* Compatibility alias for enqueue-oriented transcript formatting.
|
|
674
|
+
*
|
|
675
|
+
* @deprecated Use `formatEnqueuedTranscript`.
|
|
676
|
+
*
|
|
677
|
+
* @category formatting
|
|
678
|
+
* @since 0.4.0
|
|
679
|
+
*/
|
|
680
|
+
export const formatRuntimeTranscript = formatEnqueuedTranscript;
|
|
681
|
+
/**
|
|
682
|
+
* Formats a causal runtime transcript or failure as replayable line-oriented
|
|
683
|
+
* evidence, including exact probe steps and explicit asynchronous observations.
|
|
684
|
+
*
|
|
685
|
+
* @category formatting
|
|
686
|
+
* @since 0.4.0
|
|
687
|
+
*/
|
|
688
|
+
export const formatCausalTranscript = (value) => {
|
|
689
|
+
const failure = value instanceof CausalRuntimeCommandFailure;
|
|
690
|
+
const records = failure ? value.prefix : value.records;
|
|
691
|
+
const lines = [
|
|
692
|
+
`commands: ${Inspectable.toStringUnknown(failure
|
|
693
|
+
? [
|
|
694
|
+
...value.prefix.map((record) => record.command),
|
|
695
|
+
value.command
|
|
696
|
+
]
|
|
697
|
+
: value.commands, 0)}`
|
|
698
|
+
];
|
|
699
|
+
for (const record of records) {
|
|
700
|
+
lines.push(`command ${record.index}: command=${Inspectable.toStringUnknown(record.command, 0)} ` +
|
|
701
|
+
`model=${Inspectable.toStringUnknown(record.model, 0)} ` +
|
|
702
|
+
`expected=${Inspectable.toStringUnknown(record.expected, 0)} ` +
|
|
703
|
+
`result=${Inspectable.toStringUnknown(record.actual.result, 0)} ` +
|
|
704
|
+
`snapshot=${Inspectable.toStringUnknown(record.actual.snapshot, 0)} ` +
|
|
705
|
+
`awaited=${Inspectable.toStringUnknown(record.actual.awaited, 0)} ` +
|
|
706
|
+
`inspected=${Inspectable.toStringUnknown(record.actual.inspected, 0)}`);
|
|
707
|
+
}
|
|
708
|
+
if (failure) {
|
|
709
|
+
if (value.attempted !== undefined) {
|
|
710
|
+
lines.push(`attempted ${value.attempted.index}: command=${Inspectable.toStringUnknown(value.attempted.command, 0)} ` +
|
|
711
|
+
`model=${Inspectable.toStringUnknown(value.attempted.model, 0)} ` +
|
|
712
|
+
`expected=${Inspectable.toStringUnknown(value.attempted.expected, 0)} ` +
|
|
713
|
+
`result=${Inspectable.toStringUnknown(value.attempted.result, 0)} ` +
|
|
714
|
+
`snapshot=${Inspectable.toStringUnknown(value.attempted.snapshot, 0)} ` +
|
|
715
|
+
`awaited=${Inspectable.toStringUnknown(value.attempted.awaited, 0)} ` +
|
|
716
|
+
`inspected=${Inspectable.toStringUnknown(value.attempted.inspected, 0)}`);
|
|
717
|
+
}
|
|
718
|
+
lines.push(`failure: phase=${value.phase} index=${value.index} command=${Inspectable.toStringUnknown(value.command, 0)} ` +
|
|
719
|
+
`cause=${Inspectable.toStringUnknown(value.cause, 0)}`);
|
|
720
|
+
}
|
|
721
|
+
else {
|
|
722
|
+
lines.push(`final: snapshot=${Inspectable.toStringUnknown(value.final, 0)}`);
|
|
723
|
+
}
|
|
724
|
+
return lines.join("\n");
|
|
725
|
+
};
|
|
726
|
+
//# sourceMappingURL=runtime.js.map
|