@systemfsoftware/xstate-test 0.0.0-stage → 2.0.0-beta.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/CHANGELOG.md +9 -0
- package/LICENSE +21 -0
- package/README.md +1821 -2
- package/dist/adapter-Dd4Go0U3.d.ts +58 -0
- package/dist/effect-schema.d.ts +28 -0
- package/dist/effect-schema.mjs +40 -0
- package/dist/finalizerError-DOptAIDs.mjs +8 -0
- package/dist/index-CSbyUxJr.d.ts +2062 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.mjs +178 -0
- package/dist/playwright.d.ts +204 -0
- package/dist/playwright.mjs +287 -0
- package/dist/propertyTest-5bO0RsUu.mjs +4660 -0
- package/dist/propertyTest-BHnBV8ax.d.ts +147 -0
- package/dist/schema.d.ts +59 -0
- package/dist/schema.mjs +822 -0
- package/dist/vitest.d.ts +74 -0
- package/dist/vitest.mjs +139 -0
- package/package.json +88 -3
|
@@ -0,0 +1,2062 @@
|
|
|
1
|
+
import { ActorLogic, AnyActorLogic, AnyTransitionDefinition, EventFromLogic, EventObject, InputFrom, Snapshot, SnapshotFrom } from "@systemfsoftware/xstate";
|
|
2
|
+
import { PathGenerator, StatePath } from "@systemfsoftware/xstate/graph";
|
|
3
|
+
import * as Cause from "effect/Cause";
|
|
4
|
+
import "effect/Logger";
|
|
5
|
+
/**
|
|
6
|
+
* A value generator usable by the generator-neutral path runner.
|
|
7
|
+
*
|
|
8
|
+
* `testPaths()` samples concrete event payloads from a `generate` value before
|
|
9
|
+
* traversal, so a generator only has to produce a value from a pseudo-random
|
|
10
|
+
* number source. `@xstate/test` additionally accepts fast-check arbitraries and
|
|
11
|
+
* adapts them to this shape.
|
|
12
|
+
* @experimental
|
|
13
|
+
*/
|
|
14
|
+
type TestGenerator<T> = ((rng: () => number) => T) | {
|
|
15
|
+
sample: (rng: () => number) => T;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* The descriptor shape both entry points accept for a single event case.
|
|
19
|
+
*
|
|
20
|
+
* @experimental
|
|
21
|
+
*/
|
|
22
|
+
interface AnyTestEventDescriptor<TSnapshot extends Snapshot<unknown>, TEvent extends EventObject> {
|
|
23
|
+
readonly generate?: unknown;
|
|
24
|
+
readonly case?: string;
|
|
25
|
+
readonly weight?: number;
|
|
26
|
+
readonly when?: (context: {
|
|
27
|
+
readonly snapshot: TSnapshot;
|
|
28
|
+
readonly event: TEvent;
|
|
29
|
+
}) => boolean;
|
|
30
|
+
readonly resolve?: (context: {
|
|
31
|
+
readonly snapshot: TSnapshot;
|
|
32
|
+
readonly generated: unknown;
|
|
33
|
+
}) => object | undefined;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The event descriptor `pick()` returns: a shrinkable index in `generate`,
|
|
37
|
+
* and a `resolve` that picks the item at that index from the current snapshot.
|
|
38
|
+
* @experimental
|
|
39
|
+
*/
|
|
40
|
+
interface TestPickDescriptor<TGenerator, TSnapshot extends Snapshot<unknown>, TPayload> {
|
|
41
|
+
readonly generate: TGenerator;
|
|
42
|
+
resolve(context: {
|
|
43
|
+
readonly snapshot: TSnapshot;
|
|
44
|
+
readonly generated: number;
|
|
45
|
+
}): TPayload | undefined;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* One coverage dimension, with its ids grouped by status.
|
|
49
|
+
*
|
|
50
|
+
* @experimental
|
|
51
|
+
*/
|
|
52
|
+
interface TestCoverageDimension {
|
|
53
|
+
/** Hits per id. */
|
|
54
|
+
readonly counts: Readonly<Record<string, number>>;
|
|
55
|
+
/** Ids hit at least once. */
|
|
56
|
+
readonly covered: readonly string[];
|
|
57
|
+
/** Reachable ids that were never hit. */
|
|
58
|
+
readonly uncovered: readonly string[];
|
|
59
|
+
/** Ids that cannot be reached from the start state. */
|
|
60
|
+
readonly unreachable: readonly string[];
|
|
61
|
+
/** Ids whose reachability cannot be decided statically. */
|
|
62
|
+
readonly unknown: readonly string[];
|
|
63
|
+
}
|
|
64
|
+
interface TestTransitionPairCoverageDimension extends TestCoverageDimension {
|
|
65
|
+
/**
|
|
66
|
+
* `true` when the statically enumerable pair universe exceeded
|
|
67
|
+
* `TRANSITION_PAIR_UNIVERSE_LIMIT` and was cut short. Pairs observed at
|
|
68
|
+
* runtime are still reported as covered.
|
|
69
|
+
*/
|
|
70
|
+
readonly truncated: boolean;
|
|
71
|
+
}
|
|
72
|
+
interface TestRequirementCoverageDimension extends TestCoverageDimension {
|
|
73
|
+
/** Requirement id to the state nodes and transitions that declare it. */
|
|
74
|
+
readonly sources: Readonly<Record<string, readonly string[]>>;
|
|
75
|
+
}
|
|
76
|
+
/** @experimental */
|
|
77
|
+
interface TestEventCaseCounts {
|
|
78
|
+
/** Effective relative generation weight for this case. Defaults to `1`. */
|
|
79
|
+
readonly weight: number;
|
|
80
|
+
readonly generated: number;
|
|
81
|
+
readonly applicable: number;
|
|
82
|
+
readonly executed: number;
|
|
83
|
+
readonly ignored: number;
|
|
84
|
+
}
|
|
85
|
+
/** @experimental */
|
|
86
|
+
interface TestExplorationFrontier {
|
|
87
|
+
readonly id: string;
|
|
88
|
+
readonly prefixLength: number;
|
|
89
|
+
readonly runBudget: number | null;
|
|
90
|
+
readonly configuredRuns: number | null;
|
|
91
|
+
readonly completedRuns: number;
|
|
92
|
+
/** Runner creations, including shrink attempts. */
|
|
93
|
+
readonly attemptedRuns: number;
|
|
94
|
+
}
|
|
95
|
+
/** @experimental */
|
|
96
|
+
interface TestExplorationSeed {
|
|
97
|
+
readonly frontierId: string;
|
|
98
|
+
readonly engine?: string | undefined;
|
|
99
|
+
readonly seed?: number | undefined;
|
|
100
|
+
readonly path?: string | undefined;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Swarm testing statistics. Only present when `swarm` was enabled.
|
|
104
|
+
*
|
|
105
|
+
* @experimental
|
|
106
|
+
*/
|
|
107
|
+
interface TestExplorationSwarm {
|
|
108
|
+
/** Runs that were given a swarm subset of the event cases. */
|
|
109
|
+
readonly runs: number;
|
|
110
|
+
/** Mean number of event cases enabled per swarm run. */
|
|
111
|
+
readonly averageEnabled: number;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Targeted-search statistics. Only present when `target` was used.
|
|
115
|
+
*
|
|
116
|
+
* @experimental
|
|
117
|
+
*/
|
|
118
|
+
interface TestExplorationTarget {
|
|
119
|
+
/** The best (highest) observed target value, `-Infinity` when none. */
|
|
120
|
+
readonly best: number;
|
|
121
|
+
/** The label recorded alongside the best value, when one was given. */
|
|
122
|
+
readonly label?: string | undefined;
|
|
123
|
+
/** How many times the best value improved during the campaign. */
|
|
124
|
+
readonly improvements: number;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Why a property campaign stopped running batches.
|
|
128
|
+
*
|
|
129
|
+
* @experimental
|
|
130
|
+
*/
|
|
131
|
+
type TestStoppedBecause = 'until' | 'budget' | 'failure' | 'paths' | 'replay';
|
|
132
|
+
/** @experimental */
|
|
133
|
+
interface TestExplorationBounds {
|
|
134
|
+
/**
|
|
135
|
+
* `'property'` when the campaign generated command sequences,
|
|
136
|
+
* `'paths'` when it executed paths produced by graph traversal.
|
|
137
|
+
*/
|
|
138
|
+
readonly strategy: 'property' | 'paths';
|
|
139
|
+
/** Paths executed. Only present for `strategy: 'paths'`. */
|
|
140
|
+
readonly pathCount?: number;
|
|
141
|
+
/** How the paths were produced. Only present for `strategy: 'paths'`. */
|
|
142
|
+
readonly pathGenerator?: 'shortest' | 'simple' | 'events' | 'custom';
|
|
143
|
+
/**
|
|
144
|
+
* `'pure'` when the campaign stepped the machine through `transition()`,
|
|
145
|
+
* `'executed'` when it drove a real actor on a simulated clock.
|
|
146
|
+
*/
|
|
147
|
+
readonly mode: 'pure' | 'executed';
|
|
148
|
+
readonly configuredRuns: number | null;
|
|
149
|
+
readonly completedRuns: number;
|
|
150
|
+
/** Runner creations, including shrink attempts. */
|
|
151
|
+
readonly attemptedRuns: number;
|
|
152
|
+
/**
|
|
153
|
+
* Runs started after the first failing run, while the adapter shrank the
|
|
154
|
+
* counterexample. Included in `attemptedRuns`; excluded from `labels` and
|
|
155
|
+
* `eventCases`.
|
|
156
|
+
*/
|
|
157
|
+
readonly shrinkRuns: number;
|
|
158
|
+
readonly maximumSequenceLength: number | null;
|
|
159
|
+
readonly maximumObservedSequenceLength: number;
|
|
160
|
+
readonly frontiers: readonly TestExplorationFrontier[];
|
|
161
|
+
readonly seeds: readonly TestExplorationSeed[];
|
|
162
|
+
/** Swarm testing statistics, or `null` when `swarm` was not enabled. */
|
|
163
|
+
readonly swarm: TestExplorationSwarm | null;
|
|
164
|
+
/** Targeted-search statistics. `best` is `-Infinity` when unused. */
|
|
165
|
+
readonly target: TestExplorationTarget;
|
|
166
|
+
readonly truncated: boolean;
|
|
167
|
+
readonly truncationReasons: readonly string[];
|
|
168
|
+
/**
|
|
169
|
+
* `'until'` when a stop condition was met, `'failure'` when a
|
|
170
|
+
* counterexample ended the campaign, `'budget'` when the configured runs
|
|
171
|
+
* were exhausted, `'paths'` when `testPaths()` executed every path, and
|
|
172
|
+
* `'replay'` when `failures.replay` was `'only'` and no campaign ran.
|
|
173
|
+
*/
|
|
174
|
+
readonly stoppedBecause: TestStoppedBecause;
|
|
175
|
+
/**
|
|
176
|
+
* Executed-mode steps that settled while an invoked or spawned actor's
|
|
177
|
+
* asynchronous work was still in flight. Each such timeline entry lists the
|
|
178
|
+
* actors in `pendingActors`. Non-zero means some results may have landed
|
|
179
|
+
* in a later step than the one that started them.
|
|
180
|
+
*/
|
|
181
|
+
readonly pendingActorSteps: number;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Aggregated occurrences of a label recorded with `label()`/`classify()`.
|
|
185
|
+
*
|
|
186
|
+
* @experimental
|
|
187
|
+
*/
|
|
188
|
+
interface TestLabelCoverage {
|
|
189
|
+
/** Total number of times the label was recorded across all runs. */
|
|
190
|
+
readonly count: number;
|
|
191
|
+
/** Occurrences per recorded value. Labels without a value are not listed. */
|
|
192
|
+
readonly values: Readonly<Record<string, number>>;
|
|
193
|
+
/**
|
|
194
|
+
* Runs in which the label was recorded at least once, over attempted runs
|
|
195
|
+
* that were not shrink attempts. Always between `0` and `1`.
|
|
196
|
+
*/
|
|
197
|
+
readonly share: number;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Per-run outcomes of one temporal property.
|
|
201
|
+
*
|
|
202
|
+
* @experimental
|
|
203
|
+
*/
|
|
204
|
+
interface TestTemporalCounts {
|
|
205
|
+
/** Runs in which the property held. */
|
|
206
|
+
readonly satisfied: number;
|
|
207
|
+
/** Runs the property failed. */
|
|
208
|
+
readonly failed: number;
|
|
209
|
+
/** Runs that ended before the property was decided. */
|
|
210
|
+
readonly inconclusive: number;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Temporal-property outcomes across a campaign.
|
|
214
|
+
*
|
|
215
|
+
* @experimental
|
|
216
|
+
*/
|
|
217
|
+
interface TestTemporalCoverage {
|
|
218
|
+
/** Temporal definitions satisfied in at least one run. */
|
|
219
|
+
readonly satisfied: readonly string[];
|
|
220
|
+
/**
|
|
221
|
+
* Temporal definitions that caused a run to fail, and `sometimes` or
|
|
222
|
+
* `reachable` definitions that held in no run of the campaign.
|
|
223
|
+
*/
|
|
224
|
+
readonly failed: readonly string[];
|
|
225
|
+
/**
|
|
226
|
+
* Definitions that were inconclusive in every run that checked them, and
|
|
227
|
+
* satisfied in none.
|
|
228
|
+
*/
|
|
229
|
+
readonly inconclusive: readonly string[];
|
|
230
|
+
/** Per-id run counts. */
|
|
231
|
+
readonly counts: Readonly<Record<string, TestTemporalCounts>>;
|
|
232
|
+
/**
|
|
233
|
+
* Bounded definitions that can never fail with the configured bounds, such
|
|
234
|
+
* as an `eventually` whose `within` exceeds the longest sequence.
|
|
235
|
+
*/
|
|
236
|
+
readonly warnings: readonly string[];
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* What a campaign exercised, resolved by `propertyTest()` and `testPaths()`.
|
|
240
|
+
* Relative to the supplied event cases and bounds; not a claim of complete
|
|
241
|
+
* behavioral coverage.
|
|
242
|
+
* @experimental
|
|
243
|
+
*/
|
|
244
|
+
interface TestCoverage {
|
|
245
|
+
/** Runs attempted, including shrink attempts. */
|
|
246
|
+
readonly runs: number;
|
|
247
|
+
readonly steps: number;
|
|
248
|
+
readonly skipped: number;
|
|
249
|
+
readonly prefixSteps: number;
|
|
250
|
+
readonly generatedSteps: number;
|
|
251
|
+
readonly invariantChecks: number;
|
|
252
|
+
readonly temporalChecks: number;
|
|
253
|
+
readonly clockAdvances: number;
|
|
254
|
+
readonly checkpoints: number;
|
|
255
|
+
readonly stops: number;
|
|
256
|
+
readonly sutComparisons: number;
|
|
257
|
+
readonly oracleComparisons: number;
|
|
258
|
+
/** Serialized snapshots observed during the campaign. */
|
|
259
|
+
readonly states: TestCoverageDimension;
|
|
260
|
+
/** State node ids. */
|
|
261
|
+
readonly stateNodes: TestCoverageDimension;
|
|
262
|
+
/** Sets of simultaneously active state nodes. */
|
|
263
|
+
readonly configurations: TestCoverageDimension;
|
|
264
|
+
/** Snapshot statuses, such as `active` and `done`. */
|
|
265
|
+
readonly statuses: TestCoverageDimension;
|
|
266
|
+
/** Delivered event types. This does not describe payload-domain coverage. */
|
|
267
|
+
readonly eventTypes: TestCoverageDimension;
|
|
268
|
+
/** Lifecycle counts for the event cases supplied to `propertyTest()`. */
|
|
269
|
+
readonly eventCases: Readonly<Record<string, TestEventCaseCounts>>;
|
|
270
|
+
/** Transition definitions, attributed from the microsteps XState took. */
|
|
271
|
+
readonly transitions: TestCoverageDimension;
|
|
272
|
+
/** Pairs of consecutive executed transitions, as `${t1} -> ${t2}`. */
|
|
273
|
+
readonly transitionPairs: TestTransitionPairCoverageDimension;
|
|
274
|
+
/** Requirement ids declared via `meta.requirements`. */
|
|
275
|
+
readonly requirements: TestRequirementCoverageDimension;
|
|
276
|
+
/** Frontier ids from the `frontiers` option. */
|
|
277
|
+
readonly frontiers: TestCoverageDimension;
|
|
278
|
+
/** Labels recorded with `label()`/`classify()`, keyed by label name. */
|
|
279
|
+
readonly labels: Readonly<Record<string, TestLabelCoverage>>;
|
|
280
|
+
/** Temporal property ids by outcome. */
|
|
281
|
+
readonly temporal: TestTemporalCoverage;
|
|
282
|
+
/** The bounds, budgets, and seeds the campaign ran with. */
|
|
283
|
+
readonly exploration: TestExplorationBounds;
|
|
284
|
+
}
|
|
285
|
+
interface Declaration {
|
|
286
|
+
unreachable: boolean;
|
|
287
|
+
unknown: boolean;
|
|
288
|
+
}
|
|
289
|
+
interface MutableDimension {
|
|
290
|
+
counts: Record<string, number>;
|
|
291
|
+
declarations: Map<string, Declaration>;
|
|
292
|
+
}
|
|
293
|
+
interface MutableTestCoverage {
|
|
294
|
+
runs: number;
|
|
295
|
+
steps: number;
|
|
296
|
+
skipped: number;
|
|
297
|
+
prefixSteps: number;
|
|
298
|
+
generatedSteps: number;
|
|
299
|
+
invariantChecks: number;
|
|
300
|
+
temporalChecks: number;
|
|
301
|
+
clockAdvances: number;
|
|
302
|
+
checkpoints: number;
|
|
303
|
+
stops: number;
|
|
304
|
+
sutComparisons: number;
|
|
305
|
+
oracleComparisons: number;
|
|
306
|
+
states: MutableDimension;
|
|
307
|
+
stateNodes: MutableDimension;
|
|
308
|
+
configurations: MutableDimension;
|
|
309
|
+
statuses: MutableDimension;
|
|
310
|
+
eventTypes: MutableDimension;
|
|
311
|
+
eventCases: Record<string, TestEventCaseCounts>;
|
|
312
|
+
transitions: MutableDimension;
|
|
313
|
+
transitionPairs: MutableDimension;
|
|
314
|
+
transitionPairsTruncated: boolean;
|
|
315
|
+
requirements: MutableDimension;
|
|
316
|
+
requirementSources: Record<string, string[]>;
|
|
317
|
+
requirementsByStateNode: Map<string, readonly string[]>;
|
|
318
|
+
requirementsByTransition: Map<string, readonly string[]>;
|
|
319
|
+
previousTransitionIds: readonly string[] | null;
|
|
320
|
+
frontiers: MutableDimension;
|
|
321
|
+
labels: Record<string, {
|
|
322
|
+
count: number;
|
|
323
|
+
values: Record<string, number>;
|
|
324
|
+
runs: number;
|
|
325
|
+
}>;
|
|
326
|
+
temporal: {
|
|
327
|
+
counts: Record<string, {
|
|
328
|
+
satisfied: number;
|
|
329
|
+
failed: number;
|
|
330
|
+
inconclusive: number;
|
|
331
|
+
}>;
|
|
332
|
+
/** Campaign-level failures of `sometimes` and `reachable`. */
|
|
333
|
+
campaignFailed: Set<string>;
|
|
334
|
+
warnings: string[];
|
|
335
|
+
};
|
|
336
|
+
/** Runs started after the first failing run. */
|
|
337
|
+
shrinkRuns: number;
|
|
338
|
+
transitionIds: WeakMap<AnyTransitionDefinition, string>;
|
|
339
|
+
maximumObservedSequenceLength: number;
|
|
340
|
+
pendingActorSteps: number;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Queues and hands out actor outcomes for stubbed invoke sources.
|
|
344
|
+
*
|
|
345
|
+
* A stub actor asks the registry for its outcome when it starts. If an
|
|
346
|
+
* outcome is already queued for its source it resolves immediately; otherwise
|
|
347
|
+
* the stub stays pending until an `outcome` command (or a seeded replay
|
|
348
|
+
* record) supplies one, which is what lets fast-check shrink service results.
|
|
349
|
+
*/
|
|
350
|
+
declare class PropertyOutcomeRegistry {
|
|
351
|
+
private queued;
|
|
352
|
+
private waiting;
|
|
353
|
+
/** Called by a stub actor when it starts. */
|
|
354
|
+
request(src: string): Promise<TestActorOutcome>;
|
|
355
|
+
/** Resolves the oldest pending stub for `src`, or queues for the next one. */
|
|
356
|
+
provide(src: string, outcome: TestActorOutcome): void;
|
|
357
|
+
/** Pre-loads recorded outcomes so a replay never calls a real service. */
|
|
358
|
+
seed(records: readonly TestOutcomeRecord[]): void;
|
|
359
|
+
reset(): void;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* The snapshot, event and input types of the actor logic a helper is
|
|
363
|
+
* parameterized by. Declared once here so the bundled declaration emitter has
|
|
364
|
+
* one declaration to name rather than a `_2`/`$1` suffix per module.
|
|
365
|
+
*
|
|
366
|
+
* @experimental
|
|
367
|
+
*/
|
|
368
|
+
type SnapshotFromSource<TSource> = SnapshotFrom<TSource>;
|
|
369
|
+
/** @experimental */
|
|
370
|
+
type EventFromSource<TSource> = TSource extends ActorLogic<any, infer TEvent, any> ? TEvent : never;
|
|
371
|
+
/** @experimental */
|
|
372
|
+
type InputFromSource<TSource> = InputFrom<TSource>;
|
|
373
|
+
/** @experimental */
|
|
374
|
+
interface PropertyGeneratorKind {
|
|
375
|
+
readonly target: unknown;
|
|
376
|
+
readonly generator: unknown;
|
|
377
|
+
}
|
|
378
|
+
/** @experimental */
|
|
379
|
+
type PropertyGenerator<TKind extends PropertyGeneratorKind, TValue> = (TKind & {
|
|
380
|
+
readonly target: TValue;
|
|
381
|
+
})['generator'];
|
|
382
|
+
/** @experimental */
|
|
383
|
+
interface TestReplayMetadata {
|
|
384
|
+
readonly engine: string;
|
|
385
|
+
readonly engineVersion?: string | undefined;
|
|
386
|
+
readonly seed?: number | undefined;
|
|
387
|
+
readonly path?: string | undefined;
|
|
388
|
+
readonly replayPath?: string | undefined;
|
|
389
|
+
/** How many times the engine shrank the counterexample, when it reports it. */
|
|
390
|
+
readonly numShrinks?: number | undefined;
|
|
391
|
+
readonly data?: unknown;
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* `'pure'` steps the machine through the pure `transition()` path: no effect
|
|
395
|
+
* runs, no invoked or spawned actor starts, no delayed transition fires.
|
|
396
|
+
* `'executed'` drives a real actor on a `SimulatedClock` instead, so
|
|
397
|
+
* invoked/spawned actors run and `after` transitions are reachable through
|
|
398
|
+
* generated `advance` commands.
|
|
399
|
+
* @experimental
|
|
400
|
+
*/
|
|
401
|
+
type TestMode = 'pure' | 'executed';
|
|
402
|
+
/**
|
|
403
|
+
* A resolved actor outcome queued for a stubbed invoke source.
|
|
404
|
+
*
|
|
405
|
+
* @experimental
|
|
406
|
+
*/
|
|
407
|
+
type TestActorOutcome = {
|
|
408
|
+
readonly ok: true;
|
|
409
|
+
readonly output: unknown;
|
|
410
|
+
} | {
|
|
411
|
+
readonly ok: false;
|
|
412
|
+
readonly error: unknown;
|
|
413
|
+
};
|
|
414
|
+
/**
|
|
415
|
+
* An actor outcome observed during an executed-mode run, keyed by invoke
|
|
416
|
+
* source and by how many actors of that source had already resolved.
|
|
417
|
+
* Recorded into replay fixtures so a failure can be replayed against stubbed
|
|
418
|
+
* actors instead of the real ones.
|
|
419
|
+
* @experimental
|
|
420
|
+
*/
|
|
421
|
+
interface TestOutcomeRecord {
|
|
422
|
+
readonly src: string;
|
|
423
|
+
readonly occurrence: number;
|
|
424
|
+
readonly outcome: TestActorOutcome;
|
|
425
|
+
}
|
|
426
|
+
/** @experimental */
|
|
427
|
+
type TestCommand<TEvent extends EventObject = EventObject> = {
|
|
428
|
+
readonly type: 'event';
|
|
429
|
+
readonly event: TEvent;
|
|
430
|
+
readonly phase: 'prefix' | 'generated';
|
|
431
|
+
readonly origin: 'frontier' | 'generator' | 'clock';
|
|
432
|
+
readonly caseId?: string | undefined;
|
|
433
|
+
} | {
|
|
434
|
+
readonly type: 'advance';
|
|
435
|
+
readonly milliseconds: number;
|
|
436
|
+
readonly deliveredEvents: readonly TEvent[];
|
|
437
|
+
} | {
|
|
438
|
+
readonly type: 'checkpoint';
|
|
439
|
+
readonly label?: string | undefined;
|
|
440
|
+
} | {
|
|
441
|
+
/** Queues the next resolution of a stubbed invoke source. */
|
|
442
|
+
readonly type: 'outcome';
|
|
443
|
+
readonly src: string;
|
|
444
|
+
readonly outcome: TestActorOutcome;
|
|
445
|
+
} | {
|
|
446
|
+
readonly type: 'stop';
|
|
447
|
+
};
|
|
448
|
+
/**
|
|
449
|
+
* An event the actor system produced on its own during an executed-mode step:
|
|
450
|
+
* an invoked actor's `onDone`/`onError`/`onSnapshot`, a delayed transition, a
|
|
451
|
+
* `sendTo`/`raise`, or a child actor's own transition.
|
|
452
|
+
* @experimental
|
|
453
|
+
*/
|
|
454
|
+
interface TestActorTimelineEntry<TSnapshot extends Snapshot<unknown>> {
|
|
455
|
+
readonly kind: 'actorEvent';
|
|
456
|
+
readonly index: number;
|
|
457
|
+
/** `'root'` when the tested actor transitioned, `'child'` otherwise. */
|
|
458
|
+
readonly source: 'root' | 'child';
|
|
459
|
+
/** The `id` of the actor that transitioned. */
|
|
460
|
+
readonly actorId: string;
|
|
461
|
+
readonly event: EventObject;
|
|
462
|
+
readonly previousSnapshot: TSnapshot;
|
|
463
|
+
readonly snapshot: TSnapshot;
|
|
464
|
+
readonly effects: readonly unknown[];
|
|
465
|
+
readonly transitionIds: readonly string[];
|
|
466
|
+
/** Never set: reference/SUT comparison happens on the settled step. */
|
|
467
|
+
readonly observation?: undefined;
|
|
468
|
+
}
|
|
469
|
+
/** @experimental */
|
|
470
|
+
interface TestComparedObservation {
|
|
471
|
+
/** The model projection that was compared. */
|
|
472
|
+
readonly model: unknown;
|
|
473
|
+
/** The value observed on the reference oracle or the system under test. */
|
|
474
|
+
readonly observed: unknown;
|
|
475
|
+
}
|
|
476
|
+
/** @experimental */
|
|
477
|
+
interface TestObservation {
|
|
478
|
+
/**
|
|
479
|
+
* The model projection of the reference oracle when one is configured,
|
|
480
|
+
* otherwise the model projection of the system under test. Prefer the
|
|
481
|
+
* explicit `reference` and `sut` fields, which always report the projection
|
|
482
|
+
* they were compared against.
|
|
483
|
+
*/
|
|
484
|
+
readonly model: unknown;
|
|
485
|
+
/** Present when a reference oracle is configured. */
|
|
486
|
+
readonly reference?: TestComparedObservation | undefined;
|
|
487
|
+
/** Present when a system under test is configured. */
|
|
488
|
+
readonly sut?: TestComparedObservation | undefined;
|
|
489
|
+
}
|
|
490
|
+
/** @experimental */
|
|
491
|
+
interface TestEventTimelineEntry<TSnapshot extends Snapshot<unknown>, TEvent extends EventObject> {
|
|
492
|
+
readonly kind: 'event';
|
|
493
|
+
readonly index: number;
|
|
494
|
+
readonly command: Extract<TestCommand<TEvent>, {
|
|
495
|
+
type: 'event';
|
|
496
|
+
}>;
|
|
497
|
+
readonly previousSnapshot: TSnapshot;
|
|
498
|
+
readonly snapshot: TSnapshot;
|
|
499
|
+
readonly effects: readonly unknown[];
|
|
500
|
+
readonly transitionIds: readonly string[];
|
|
501
|
+
readonly activeStateIds: readonly string[];
|
|
502
|
+
readonly observation?: TestObservation;
|
|
503
|
+
/**
|
|
504
|
+
* Executed mode only: invoked or spawned actors whose asynchronous work was
|
|
505
|
+
* still in flight when the step settled. Their results may land in a later
|
|
506
|
+
* step, so the step is not reproducible from the trace alone.
|
|
507
|
+
*/
|
|
508
|
+
readonly pendingActors?: readonly string[];
|
|
509
|
+
}
|
|
510
|
+
/** @experimental */
|
|
511
|
+
interface TestRuntimeTimelineEntry<TSnapshot extends Snapshot<unknown>, TEvent extends EventObject> {
|
|
512
|
+
readonly kind: 'command';
|
|
513
|
+
readonly index: number;
|
|
514
|
+
readonly command: Exclude<TestCommand<TEvent>, {
|
|
515
|
+
type: 'event';
|
|
516
|
+
}>;
|
|
517
|
+
readonly previousSnapshot: TSnapshot;
|
|
518
|
+
readonly snapshot: TSnapshot;
|
|
519
|
+
readonly effects: readonly unknown[];
|
|
520
|
+
readonly transitionIds: readonly string[];
|
|
521
|
+
readonly observation?: TestObservation;
|
|
522
|
+
/** See {@link TestEventTimelineEntry.pendingActors}. */
|
|
523
|
+
readonly pendingActors?: readonly string[];
|
|
524
|
+
}
|
|
525
|
+
/** @experimental */
|
|
526
|
+
type TestTimelineEntry<TSnapshot extends Snapshot<unknown>, TEvent extends EventObject> = TestEventTimelineEntry<TSnapshot, TEvent> | TestRuntimeTimelineEntry<TSnapshot, TEvent> | TestActorTimelineEntry<TSnapshot>;
|
|
527
|
+
/** @experimental */
|
|
528
|
+
interface PortableTestTimelineEntry {
|
|
529
|
+
readonly kind: 'event' | 'command';
|
|
530
|
+
readonly command: TestCommand;
|
|
531
|
+
}
|
|
532
|
+
/** @experimental */
|
|
533
|
+
interface PortableTemporalFailure {
|
|
534
|
+
readonly type: 'eventually' | 'until' | 'always' | 'never' | 'respond';
|
|
535
|
+
readonly id: string;
|
|
536
|
+
readonly description?: string | undefined;
|
|
537
|
+
/** Only present for bounded (`eventually`/`until`/`respond`) properties. */
|
|
538
|
+
readonly within?: number | undefined;
|
|
539
|
+
readonly atStep: number;
|
|
540
|
+
}
|
|
541
|
+
/** @experimental */
|
|
542
|
+
interface TestFixture {
|
|
543
|
+
readonly formatVersion: 2;
|
|
544
|
+
readonly machine?: {
|
|
545
|
+
readonly id?: string | undefined;
|
|
546
|
+
readonly version?: string | undefined;
|
|
547
|
+
} | undefined;
|
|
548
|
+
readonly start: {
|
|
549
|
+
readonly type: 'input';
|
|
550
|
+
readonly input: unknown;
|
|
551
|
+
} | {
|
|
552
|
+
readonly type: 'snapshot';
|
|
553
|
+
readonly snapshot: unknown;
|
|
554
|
+
};
|
|
555
|
+
readonly timeline: readonly PortableTestTimelineEntry[];
|
|
556
|
+
/**
|
|
557
|
+
* The step the recorded run failed at. Absent on fixtures recorded from a
|
|
558
|
+
* passing run, such as the ones an offline property suite is built from.
|
|
559
|
+
*/
|
|
560
|
+
readonly failedAt?: number;
|
|
561
|
+
readonly temporalFailure?: PortableTemporalFailure | undefined;
|
|
562
|
+
/** Event case ids enabled for the run, when swarm testing was used. */
|
|
563
|
+
readonly swarm?: readonly string[];
|
|
564
|
+
/** Executed-mode runs only. See `TestOutcomeRecord`. */
|
|
565
|
+
readonly mode?: TestMode;
|
|
566
|
+
/** Actor outcomes observed during an executed-mode run, in resolution order. */
|
|
567
|
+
readonly outcomes?: readonly TestOutcomeRecord[];
|
|
568
|
+
/**
|
|
569
|
+
* Invoke sources the recorded run replaced with outcome stubs, including
|
|
570
|
+
* ones that never resolved. A replay stubs every one of them.
|
|
571
|
+
*/
|
|
572
|
+
readonly stubs?: readonly string[];
|
|
573
|
+
}
|
|
574
|
+
interface LegacyPortablePropertyReplayFixture {
|
|
575
|
+
readonly formatVersion: 1;
|
|
576
|
+
readonly machine?: {
|
|
577
|
+
readonly id?: string;
|
|
578
|
+
readonly version?: string;
|
|
579
|
+
};
|
|
580
|
+
readonly start: {
|
|
581
|
+
readonly type: 'input';
|
|
582
|
+
readonly input: unknown;
|
|
583
|
+
} | {
|
|
584
|
+
readonly type: 'snapshot';
|
|
585
|
+
readonly snapshot: unknown;
|
|
586
|
+
};
|
|
587
|
+
readonly prefixEvents: readonly unknown[];
|
|
588
|
+
readonly events: readonly unknown[];
|
|
589
|
+
readonly failedAt: number;
|
|
590
|
+
}
|
|
591
|
+
/** @experimental */
|
|
592
|
+
interface TestAdapterResult {
|
|
593
|
+
readonly runs: number;
|
|
594
|
+
readonly exploration: {
|
|
595
|
+
readonly configuredRuns: number | null;
|
|
596
|
+
readonly maximumSequenceLength: number | null;
|
|
597
|
+
readonly engine?: string | undefined;
|
|
598
|
+
readonly seed?: number | undefined;
|
|
599
|
+
readonly path?: string | undefined;
|
|
600
|
+
readonly truncated?: boolean | undefined;
|
|
601
|
+
readonly truncationReasons?: readonly string[] | undefined;
|
|
602
|
+
};
|
|
603
|
+
readonly replay?: TestReplayMetadata;
|
|
604
|
+
readonly error?: unknown;
|
|
605
|
+
/**
|
|
606
|
+
* Engine-specific text appended to the failure message, such as fast-check's
|
|
607
|
+
* report when `verbose` is set.
|
|
608
|
+
*/
|
|
609
|
+
readonly report?: string;
|
|
610
|
+
}
|
|
611
|
+
/** @experimental */
|
|
612
|
+
interface PropertyGeneratedCommand {
|
|
613
|
+
readonly type: 'advance' | 'checkpoint' | 'stop' | 'outcome';
|
|
614
|
+
readonly generator: unknown;
|
|
615
|
+
/** Relative generation weight. `1` unless configured otherwise. */
|
|
616
|
+
readonly weight: number;
|
|
617
|
+
/**
|
|
618
|
+
* The invoke source an `'outcome'` command resolves. Always present for
|
|
619
|
+
* `'outcome'` commands and never present for the others.
|
|
620
|
+
*/
|
|
621
|
+
readonly src?: string;
|
|
622
|
+
}
|
|
623
|
+
/** @experimental */
|
|
624
|
+
interface TestAdapterRequest<TSnapshot extends Snapshot<unknown>, TEvent extends EventObject> {
|
|
625
|
+
readonly events: readonly {
|
|
626
|
+
readonly type: string;
|
|
627
|
+
readonly caseId: string;
|
|
628
|
+
readonly generator: unknown;
|
|
629
|
+
/** Relative generation weight. `1` unless configured otherwise. */
|
|
630
|
+
readonly weight: number;
|
|
631
|
+
}[];
|
|
632
|
+
readonly commands: readonly PropertyGeneratedCommand[];
|
|
633
|
+
readonly runBudget?: number | undefined;
|
|
634
|
+
/**
|
|
635
|
+
* The number of runs already completed by earlier batches of the same
|
|
636
|
+
* campaign. Adapters that derive their seed from a fixed value should offset
|
|
637
|
+
* it by this number so batches explore different sequences.
|
|
638
|
+
*/
|
|
639
|
+
readonly runOffset?: number | undefined;
|
|
640
|
+
readonly createEvent: (type: string, payload: unknown) => TEvent;
|
|
641
|
+
readonly createRunner: (run?: TestAdapterRunContext) => PropertyScenarioRunner<TSnapshot, TEvent>;
|
|
642
|
+
}
|
|
643
|
+
interface TestAdapterRunOutcome {
|
|
644
|
+
readonly passed: boolean;
|
|
645
|
+
readonly swarm: readonly string[] | undefined;
|
|
646
|
+
}
|
|
647
|
+
interface TestAdapterRunContext {
|
|
648
|
+
readonly shrink: boolean;
|
|
649
|
+
readonly swarm: readonly string[] | undefined;
|
|
650
|
+
readonly report: (outcome: TestAdapterRunOutcome) => void;
|
|
651
|
+
}
|
|
652
|
+
/** @experimental */
|
|
653
|
+
interface TestAdapter<TKind extends PropertyGeneratorKind = PropertyGeneratorKind> {
|
|
654
|
+
readonly kind?: TKind;
|
|
655
|
+
run<TSnapshot extends Snapshot<unknown>, TEvent extends EventObject>(request: TestAdapterRequest<TSnapshot, TEvent>): Promise<TestAdapterResult>;
|
|
656
|
+
}
|
|
657
|
+
/** @experimental */
|
|
658
|
+
interface TestSutContext<TSnapshot extends Snapshot<unknown>, TEvent extends EventObject> extends TestLabelRecorders {
|
|
659
|
+
readonly logic: ActorLogic<TSnapshot, TEvent, unknown>;
|
|
660
|
+
readonly input: unknown;
|
|
661
|
+
readonly snapshot: TSnapshot | undefined;
|
|
662
|
+
}
|
|
663
|
+
/** @experimental */
|
|
664
|
+
interface TestSut<TSnapshot extends Snapshot<unknown>, TEvent extends EventObject> {
|
|
665
|
+
/** Creates a fresh session for one run. Called once per run and shrink attempt. */
|
|
666
|
+
readonly create: (context: TestSutContext<TSnapshot, TEvent>) => TestSutSession<TSnapshot, TEvent> | Promise<TestSutSession<TSnapshot, TEvent>>;
|
|
667
|
+
/**
|
|
668
|
+
* Projects the model snapshot onto the shape `read()` returns. Required only
|
|
669
|
+
* when the session reads observations back for comparison.
|
|
670
|
+
*/
|
|
671
|
+
readonly projectModel?: (snapshot: TSnapshot) => unknown;
|
|
672
|
+
/** Normalizes the value `read()` returns before comparison. Defaults to identity. */
|
|
673
|
+
readonly projectSut?: (observed: unknown) => unknown;
|
|
674
|
+
/** Compares the two projections. Defaults to `defaultEquivalent`. */
|
|
675
|
+
readonly equivalent?: (model: unknown, sut: unknown) => boolean | Promise<boolean>;
|
|
676
|
+
/**
|
|
677
|
+
* Called once when the campaign ends, after every session was disposed.
|
|
678
|
+
* `failure` is the error the campaign throws, if any. Use it to publish
|
|
679
|
+
* artifacts collected for the final counterexample.
|
|
680
|
+
*/
|
|
681
|
+
readonly complete?: (result: TestSutCompleteContext) => void | Promise<void>;
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Passed to {@link TestSut.complete}.
|
|
685
|
+
*
|
|
686
|
+
* @experimental
|
|
687
|
+
*/
|
|
688
|
+
interface TestSutCompleteContext {
|
|
689
|
+
readonly passed: boolean;
|
|
690
|
+
/** The error the campaign throws. Usually a {@link ModelTestFailure}. */
|
|
691
|
+
readonly failure?: unknown;
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Passed to {@link TestSutSession.dispose}.
|
|
695
|
+
*
|
|
696
|
+
* @experimental
|
|
697
|
+
*/
|
|
698
|
+
interface TestSutDisposeContext {
|
|
699
|
+
/** `false` when the run ended in a failure. */
|
|
700
|
+
readonly passed: boolean;
|
|
701
|
+
/** The failure the run ended in, when an oracle failed. */
|
|
702
|
+
readonly failure?: ModelTestFailure<any, any>;
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Metadata about the step an event belongs to, passed to `send`.
|
|
706
|
+
*
|
|
707
|
+
* @experimental
|
|
708
|
+
*/
|
|
709
|
+
interface TestSutSendContext<TSnapshot = unknown> {
|
|
710
|
+
/** The model snapshot after the event was applied to the model. */
|
|
711
|
+
readonly snapshot: TSnapshot;
|
|
712
|
+
/**
|
|
713
|
+
* The internal generated event case id, when the event came from a
|
|
714
|
+
* generator. Absent for prefix, clock, and replayed events. It is an opaque
|
|
715
|
+
* string; use {@link TestSutSendContext.case} to identify a case.
|
|
716
|
+
*/
|
|
717
|
+
readonly caseId?: string;
|
|
718
|
+
/**
|
|
719
|
+
* The user-facing descriptor of the generated event case: the event `type`
|
|
720
|
+
* and the configured case `name` (`'default'` when the event was configured
|
|
721
|
+
* without a `case`). Absent whenever `caseId` is.
|
|
722
|
+
*/
|
|
723
|
+
readonly case?: {
|
|
724
|
+
readonly type: string;
|
|
725
|
+
readonly name: string;
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
/**
|
|
729
|
+
* One run's connection to the system under test, returned by `TestSut.create()`.
|
|
730
|
+
*
|
|
731
|
+
* @experimental
|
|
732
|
+
*/
|
|
733
|
+
interface TestSutSession<TSnapshot extends Snapshot<unknown> = Snapshot<unknown>, TEvent extends EventObject = EventObject> {
|
|
734
|
+
/** Performs `event` against the system under test. */
|
|
735
|
+
readonly send: (event: TEvent, context: TestSutSendContext<TSnapshot>) => void | Promise<void>;
|
|
736
|
+
/**
|
|
737
|
+
* Reads the current observable state of the system under test. Compared
|
|
738
|
+
* against `projectModel(snapshot)` after every stable step when both are
|
|
739
|
+
* present.
|
|
740
|
+
*/
|
|
741
|
+
readonly read?: () => unknown | Promise<unknown>;
|
|
742
|
+
/**
|
|
743
|
+
* Per-state assertions run after every stable step, keyed by state value (or
|
|
744
|
+
* `'#id'`), with `'*'` as the fallthrough. Overrides the top-level `states`
|
|
745
|
+
* option when present.
|
|
746
|
+
*/
|
|
747
|
+
readonly states?: TestStateAssertions<TSnapshot, TEvent>;
|
|
748
|
+
/** Waits for the system under test to become quiescent before each comparison. */
|
|
749
|
+
readonly settle?: () => void | Promise<void>;
|
|
750
|
+
/**
|
|
751
|
+
* Advances the system under test's own clock, returning the events it
|
|
752
|
+
* delivered. They are applied to the model before the next comparison.
|
|
753
|
+
*/
|
|
754
|
+
readonly advance?: (milliseconds: number) => readonly TEvent[] | Promise<readonly TEvent[]>;
|
|
755
|
+
/** Handles a `checkpoint` command. */
|
|
756
|
+
readonly checkpoint?: (label?: string) => void | Promise<void>;
|
|
757
|
+
/** Handles a `stop` command. */
|
|
758
|
+
readonly stop?: () => void | Promise<void>;
|
|
759
|
+
/**
|
|
760
|
+
* Checks the system under test after every stable step, after the
|
|
761
|
+
* comparison. Throws to fail the step.
|
|
762
|
+
*/
|
|
763
|
+
readonly check?: () => void | Promise<void>;
|
|
764
|
+
/** Tears the session down at the end of the run, whether it passed or failed. */
|
|
765
|
+
readonly dispose?: (context: TestSutDisposeContext) => void | Promise<void>;
|
|
766
|
+
}
|
|
767
|
+
/** Dotted state-value paths of a machine's `value` type: `'a'`, `'a.b'`. */
|
|
768
|
+
type StateValuePaths<TValue> = TValue extends string ? TValue : TValue extends object ? { [K in keyof TValue & string]: K | `${K}.${StateValuePaths<TValue[K]>}`; }[keyof TValue & string] : never;
|
|
769
|
+
/**
|
|
770
|
+
* The keys a {@link TestStateAssertions} map suggests: every state-value path
|
|
771
|
+
* of the snapshot's `value`, and `'*'`. Any other string, such as a
|
|
772
|
+
* `'#node.id'`, is accepted too.
|
|
773
|
+
* @experimental
|
|
774
|
+
*/
|
|
775
|
+
type TestStateKey<TSnapshot extends Snapshot<unknown>> = '*' | `#${string}` | (TSnapshot extends {
|
|
776
|
+
readonly value: infer TValue;
|
|
777
|
+
} ? StateValuePaths<TValue> : never);
|
|
778
|
+
/**
|
|
779
|
+
* One per-state assertion. See {@link TestStateAssertions}.
|
|
780
|
+
*
|
|
781
|
+
* @experimental
|
|
782
|
+
*/
|
|
783
|
+
type TestStateAssertion<TSnapshot extends Snapshot<unknown>, TEvent extends EventObject = EventObject> = (snapshot: TSnapshot, session: TestSutSession<TSnapshot, TEvent> | undefined) => void | Promise<void>;
|
|
784
|
+
/**
|
|
785
|
+
* Per-state assertions, keyed by state value (`'green'`, `'a.b'`), by state
|
|
786
|
+
* node id (`'#some.id'`), or `'*'` as the fallthrough when nothing else
|
|
787
|
+
* matches. Run after every stable step in both `propertyTest()` and
|
|
788
|
+
* `testPaths()`.
|
|
789
|
+
* @experimental
|
|
790
|
+
*/
|
|
791
|
+
type TestStateAssertions<TSnapshot extends Snapshot<unknown>, TEvent extends EventObject = EventObject> = { readonly [TKey in TestStateKey<TSnapshot>]?: TestStateAssertion<TSnapshot, TEvent>; } & {
|
|
792
|
+
readonly [stateKey: string]: TestStateAssertion<TSnapshot, TEvent> | undefined;
|
|
793
|
+
};
|
|
794
|
+
/** @experimental */
|
|
795
|
+
interface TestReferenceContext<TSnapshot extends Snapshot<unknown>, TEvent extends EventObject> extends TestSutContext<TSnapshot, TEvent> {}
|
|
796
|
+
/**
|
|
797
|
+
* A second implementation of the model's logic, compared with it on every stable step.
|
|
798
|
+
*
|
|
799
|
+
* @experimental
|
|
800
|
+
*/
|
|
801
|
+
interface TestReference<TSnapshot extends Snapshot<unknown>, TEvent extends EventObject> {
|
|
802
|
+
/** Creates a fresh reference session for one run. */
|
|
803
|
+
readonly create: (context: TestReferenceContext<TSnapshot, TEvent>) => TestReferenceSession<TEvent> | Promise<TestReferenceSession<TEvent>>;
|
|
804
|
+
/** Projects the model snapshot onto the shape the reference's `read()` returns. */
|
|
805
|
+
readonly projectModel: (snapshot: TSnapshot) => unknown;
|
|
806
|
+
/** Normalizes the value the reference's `read()` returns. Defaults to identity. */
|
|
807
|
+
readonly projectReference?: (observed: unknown) => unknown;
|
|
808
|
+
readonly equivalent?: (model: unknown, reference: unknown) => boolean | Promise<boolean>;
|
|
809
|
+
}
|
|
810
|
+
/** @experimental */
|
|
811
|
+
interface TestReferenceSession<TEvent extends EventObject> {
|
|
812
|
+
readonly transition: (event: TEvent) => void | Promise<void>;
|
|
813
|
+
readonly read: () => unknown | Promise<unknown>;
|
|
814
|
+
readonly stop?: () => void | Promise<void>;
|
|
815
|
+
readonly dispose?: () => void | Promise<void>;
|
|
816
|
+
}
|
|
817
|
+
type EventType<TEvent extends EventObject> = TEvent['type'] & string;
|
|
818
|
+
type EventForType<TEvent extends EventObject, TType extends EventType<TEvent>> = Extract<TEvent, {
|
|
819
|
+
type: TType;
|
|
820
|
+
}>;
|
|
821
|
+
type EventPayload<TEvent extends EventObject> = Omit<TEvent, 'type'>;
|
|
822
|
+
/**
|
|
823
|
+
* Records a statistic for the current run.
|
|
824
|
+
*
|
|
825
|
+
* @experimental
|
|
826
|
+
*/
|
|
827
|
+
interface TestLabelRecorders {
|
|
828
|
+
/**
|
|
829
|
+
* Records `name` (optionally with `value`) for the current run. Labels are
|
|
830
|
+
* aggregated across the campaign into `coverage.labels`.
|
|
831
|
+
*/
|
|
832
|
+
readonly label: (name: string, value?: string | number | boolean) => void;
|
|
833
|
+
/** Records `name` when `condition` holds. */
|
|
834
|
+
readonly classify: (condition: boolean, name: string) => void;
|
|
835
|
+
/**
|
|
836
|
+
* Records an observation for targeted search. The campaign keeps the best
|
|
837
|
+
* (highest) observed value and steers later batches towards the prefixes
|
|
838
|
+
* that reached it. See `frontiers: { strategy: 'target' }`.
|
|
839
|
+
*/
|
|
840
|
+
readonly target: (observation: number, label?: string) => void;
|
|
841
|
+
}
|
|
842
|
+
/** @experimental */
|
|
843
|
+
interface TestInvariantContext<TSnapshot extends Snapshot<unknown>, TEvent extends EventObject> extends TestLabelRecorders {
|
|
844
|
+
readonly initialSnapshot: TSnapshot;
|
|
845
|
+
readonly previousSnapshot: TSnapshot;
|
|
846
|
+
readonly snapshot: TSnapshot;
|
|
847
|
+
readonly event: TEvent | undefined;
|
|
848
|
+
readonly effects: readonly unknown[];
|
|
849
|
+
readonly step: number;
|
|
850
|
+
}
|
|
851
|
+
/** @experimental */
|
|
852
|
+
type TestInvariant<TSnapshot extends Snapshot<unknown>, TEvent extends EventObject> = (context: TestInvariantContext<TSnapshot, TEvent>) => void | Promise<void>;
|
|
853
|
+
/** @experimental */
|
|
854
|
+
interface TestEventDescriptor<TGenerator, TSnapshot extends Snapshot<unknown>, TEvent extends EventObject> {
|
|
855
|
+
readonly generate: TGenerator;
|
|
856
|
+
readonly case?: string;
|
|
857
|
+
/**
|
|
858
|
+
* Relative generation weight for this case. Must be a positive, finite
|
|
859
|
+
* number. Defaults to `1`.
|
|
860
|
+
*/
|
|
861
|
+
readonly weight?: number;
|
|
862
|
+
readonly when?: (context: {
|
|
863
|
+
readonly snapshot: TSnapshot;
|
|
864
|
+
readonly event: TEvent;
|
|
865
|
+
}) => boolean;
|
|
866
|
+
readonly resolve?: never;
|
|
867
|
+
}
|
|
868
|
+
/** @experimental */
|
|
869
|
+
interface TestResolvedEventDescriptor<TGenerator, TSnapshot extends Snapshot<unknown>, TEvent extends EventObject, TGenerated = unknown> {
|
|
870
|
+
readonly generate: TGenerator;
|
|
871
|
+
readonly case?: string;
|
|
872
|
+
/**
|
|
873
|
+
* Relative generation weight for this case. Must be a positive, finite
|
|
874
|
+
* number. Defaults to `1`.
|
|
875
|
+
*/
|
|
876
|
+
readonly weight?: number;
|
|
877
|
+
/**
|
|
878
|
+
* Resolves a shrinkable symbolic value against the current model snapshot.
|
|
879
|
+
* Returning `undefined` makes the case inapplicable.
|
|
880
|
+
*/
|
|
881
|
+
resolve(context: {
|
|
882
|
+
readonly snapshot: TSnapshot;
|
|
883
|
+
readonly generated: TGenerated;
|
|
884
|
+
}): EventPayload<TEvent> | undefined;
|
|
885
|
+
readonly when?: (context: {
|
|
886
|
+
readonly snapshot: TSnapshot;
|
|
887
|
+
readonly event: TEvent;
|
|
888
|
+
}) => boolean;
|
|
889
|
+
}
|
|
890
|
+
type PropertyEventGenerator<TSnapshot extends Snapshot<unknown>, TEvent extends EventObject, TKind extends PropertyGeneratorKind> = PropertyGenerator<TKind, EventPayload<TEvent>> | TestEventDescriptor<PropertyGenerator<TKind, EventPayload<TEvent>>, TSnapshot, TEvent> | TestResolvedEventDescriptor<PropertyGenerator<TKind, unknown>, TSnapshot, TEvent>;
|
|
891
|
+
/** @experimental */
|
|
892
|
+
type TestEventGenerators<TSnapshot extends Snapshot<unknown>, TEvent extends EventObject, TKind extends PropertyGeneratorKind> = { [TType in EventType<TEvent>]?: PropertyEventGenerator<TSnapshot, EventForType<TEvent, TType>, TKind> | readonly PropertyEventGenerator<TSnapshot, EventForType<TEvent, TType>, TKind>[]; };
|
|
893
|
+
/** @experimental */
|
|
894
|
+
type TestTemporalPredicate<TSnapshot extends Snapshot<unknown>, TEvent extends EventObject> = (context: TestInvariantContext<TSnapshot, TEvent>) => boolean | Promise<boolean>;
|
|
895
|
+
/** @experimental */
|
|
896
|
+
type TestTemporal<TSnapshot extends Snapshot<unknown>, TEvent extends EventObject> = {
|
|
897
|
+
readonly type: 'eventually';
|
|
898
|
+
readonly id: string;
|
|
899
|
+
readonly description?: string;
|
|
900
|
+
/**
|
|
901
|
+
* Fails as soon as this many stable steps elapse without the predicate
|
|
902
|
+
* holding. When omitted, the predicate must hold before the run ends.
|
|
903
|
+
*/
|
|
904
|
+
readonly within?: number;
|
|
905
|
+
readonly predicate: TestTemporalPredicate<TSnapshot, TEvent>;
|
|
906
|
+
} | {
|
|
907
|
+
readonly type: 'until';
|
|
908
|
+
readonly id: string;
|
|
909
|
+
readonly description?: string;
|
|
910
|
+
/**
|
|
911
|
+
* Fails as soon as this many stable steps elapse without `until`
|
|
912
|
+
* holding. When omitted, `until` must hold before the run ends.
|
|
913
|
+
*/
|
|
914
|
+
readonly within?: number;
|
|
915
|
+
readonly hold: TestTemporalPredicate<TSnapshot, TEvent>;
|
|
916
|
+
readonly until: TestTemporalPredicate<TSnapshot, TEvent>;
|
|
917
|
+
} | {
|
|
918
|
+
/** The predicate must hold on every stable step. */
|
|
919
|
+
readonly type: 'always';
|
|
920
|
+
readonly id: string;
|
|
921
|
+
readonly description?: string;
|
|
922
|
+
readonly predicate: TestTemporalPredicate<TSnapshot, TEvent>;
|
|
923
|
+
} | {
|
|
924
|
+
/** The predicate must never hold on any stable step. */
|
|
925
|
+
readonly type: 'never';
|
|
926
|
+
readonly id: string;
|
|
927
|
+
readonly description?: string;
|
|
928
|
+
readonly predicate: TestTemporalPredicate<TSnapshot, TEvent>;
|
|
929
|
+
} | {
|
|
930
|
+
/**
|
|
931
|
+
* The predicate must hold on at least one stable step of at least one
|
|
932
|
+
* run. Checked when the campaign ends: a run in which it never holds
|
|
933
|
+
* does not fail.
|
|
934
|
+
*/
|
|
935
|
+
readonly type: 'sometimes';
|
|
936
|
+
readonly id: string;
|
|
937
|
+
readonly description?: string;
|
|
938
|
+
readonly predicate: TestTemporalPredicate<TSnapshot, TEvent>;
|
|
939
|
+
} | {
|
|
940
|
+
/**
|
|
941
|
+
* Every step on which `trigger` holds must be followed by a step on
|
|
942
|
+
* which `response` holds. The response may hold on the trigger step
|
|
943
|
+
* itself.
|
|
944
|
+
*/
|
|
945
|
+
readonly type: 'respond';
|
|
946
|
+
readonly id: string;
|
|
947
|
+
readonly description?: string;
|
|
948
|
+
/**
|
|
949
|
+
* Fails as soon as this many stable steps elapse after a trigger
|
|
950
|
+
* without a response. When omitted, the response must hold before the
|
|
951
|
+
* run ends.
|
|
952
|
+
*/
|
|
953
|
+
readonly within?: number;
|
|
954
|
+
readonly trigger: TestTemporalPredicate<TSnapshot, TEvent>;
|
|
955
|
+
readonly response: TestTemporalPredicate<TSnapshot, TEvent>;
|
|
956
|
+
};
|
|
957
|
+
/** @experimental */
|
|
958
|
+
interface TestStep<TSnapshot extends Snapshot<unknown>, TEvent extends EventObject> extends TestEventTimelineEntry<TSnapshot, TEvent> {
|
|
959
|
+
readonly phase: 'prefix' | 'generated';
|
|
960
|
+
readonly event: TEvent;
|
|
961
|
+
}
|
|
962
|
+
/** @experimental */
|
|
963
|
+
interface TestTrace<TSnapshot extends Snapshot<unknown>, TEvent extends EventObject> {
|
|
964
|
+
readonly start: {
|
|
965
|
+
readonly type: 'input';
|
|
966
|
+
readonly input: unknown;
|
|
967
|
+
} | {
|
|
968
|
+
readonly type: 'snapshot';
|
|
969
|
+
readonly snapshot: TSnapshot;
|
|
970
|
+
};
|
|
971
|
+
readonly initialSnapshot: TSnapshot;
|
|
972
|
+
readonly initialEffects: readonly unknown[];
|
|
973
|
+
readonly initialTransitionIds: readonly string[];
|
|
974
|
+
readonly timeline: readonly TestTimelineEntry<TSnapshot, TEvent>[];
|
|
975
|
+
readonly prefixEvents: readonly TEvent[];
|
|
976
|
+
readonly events: readonly TEvent[];
|
|
977
|
+
readonly commands: readonly Exclude<TestCommand<TEvent>, {
|
|
978
|
+
type: 'event';
|
|
979
|
+
}>[];
|
|
980
|
+
readonly steps: readonly TestStep<TSnapshot, TEvent>[];
|
|
981
|
+
readonly finalSnapshot: TSnapshot;
|
|
982
|
+
readonly finalObservation?: TestObservation | undefined;
|
|
983
|
+
/** Event case ids enabled for the run, when swarm testing was used. */
|
|
984
|
+
readonly swarm?: readonly string[] | undefined;
|
|
985
|
+
/** The mode the run was recorded in. */
|
|
986
|
+
readonly mode?: TestMode;
|
|
987
|
+
/** Actor outcomes observed during an executed-mode run, in resolution order. */
|
|
988
|
+
readonly outcomes?: readonly TestOutcomeRecord[];
|
|
989
|
+
}
|
|
990
|
+
/**
|
|
991
|
+
* Options that shape how a {@link ModelTestFailure} message is rendered.
|
|
992
|
+
*
|
|
993
|
+
* @experimental
|
|
994
|
+
*/
|
|
995
|
+
interface TestFailureFormatOptions<TSnapshot extends Snapshot<unknown> = Snapshot<unknown>> {
|
|
996
|
+
/**
|
|
997
|
+
* Projects a snapshot onto the value printed in the failure trace. Defaults
|
|
998
|
+
* to `{ value, context }` for machine snapshots, plus `status`, `output`,
|
|
999
|
+
* `error`, and `tags` when they carry information.
|
|
1000
|
+
*/
|
|
1001
|
+
readonly formatSnapshot?: (snapshot: TSnapshot) => unknown;
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* Lines added to a {@link ModelTestFailure} message after it was built.
|
|
1005
|
+
*
|
|
1006
|
+
* @experimental
|
|
1007
|
+
*/
|
|
1008
|
+
interface TestFailureExtras {
|
|
1009
|
+
/** Printed after the `Reproduce:` and `Fixture:` lines, such as `Saved: …`. */
|
|
1010
|
+
readonly notes?: readonly string[];
|
|
1011
|
+
/** Printed after the trace, such as fast-check's verbose report. */
|
|
1012
|
+
readonly report?: string;
|
|
1013
|
+
}
|
|
1014
|
+
/**
|
|
1015
|
+
* Thrown by `propertyTest()`, `testPaths()`, and `replayTest()` when an
|
|
1016
|
+
* oracle fails. Carries the trace, a portable replay fixture, and the coverage
|
|
1017
|
+
* accumulated up to the failure.
|
|
1018
|
+
* @experimental
|
|
1019
|
+
*/
|
|
1020
|
+
declare const ModelTestFailureBase: new (args: {
|
|
1021
|
+
readonly message: string;
|
|
1022
|
+
}) => Cause.YieldableError & {
|
|
1023
|
+
readonly _tag: "@systemfsoftware/xstate-test/engine/propertyTest/ModelTestFailure";
|
|
1024
|
+
} & Readonly<{
|
|
1025
|
+
readonly message: string;
|
|
1026
|
+
}>;
|
|
1027
|
+
declare class ModelTestFailure<TSnapshot extends Snapshot<unknown> = Snapshot<unknown>, TEvent extends EventObject = EventObject> extends ModelTestFailureBase {
|
|
1028
|
+
readonly trace: TestTrace<TSnapshot, TEvent>;
|
|
1029
|
+
readonly cause: unknown;
|
|
1030
|
+
readonly replay?: TestReplayMetadata | undefined;
|
|
1031
|
+
readonly fixture?: TestFixture | undefined;
|
|
1032
|
+
readonly coverage?: TestCoverage | undefined;
|
|
1033
|
+
/** How the message renders snapshots. Kept so rethrows render the same. */
|
|
1034
|
+
readonly format?: TestFailureFormatOptions<TSnapshot> | undefined;
|
|
1035
|
+
/** Extra message lines. Kept so rethrows render the same. */
|
|
1036
|
+
readonly extras?: TestFailureExtras | undefined;
|
|
1037
|
+
/**
|
|
1038
|
+
* `instanceof` narrows to the default type arguments rather than `any`, so
|
|
1039
|
+
* `error.trace` stays typed in a `catch` block.
|
|
1040
|
+
*/
|
|
1041
|
+
static [Symbol.hasInstance](value: unknown): value is ModelTestFailure;
|
|
1042
|
+
/** The short message, without the formatted trace. */
|
|
1043
|
+
readonly summary: string;
|
|
1044
|
+
constructor(summary: string, trace: TestTrace<TSnapshot, TEvent>, cause: unknown, replay?: TestReplayMetadata | undefined, fixture?: TestFixture | undefined, coverage?: TestCoverage | undefined,
|
|
1045
|
+
/** How the message renders snapshots. Kept so rethrows render the same. */
|
|
1046
|
+
format?: TestFailureFormatOptions<TSnapshot> | undefined,
|
|
1047
|
+
/** Extra message lines. Kept so rethrows render the same. */
|
|
1048
|
+
extras?: TestFailureExtras | undefined);
|
|
1049
|
+
}
|
|
1050
|
+
/**
|
|
1051
|
+
* Thrown when a campaign completes without a counterexample but a
|
|
1052
|
+
* campaign-level assertion does not hold: a `sometimes` property that held in
|
|
1053
|
+
* no run, or a `reachable` target that no run entered.
|
|
1054
|
+
* @experimental
|
|
1055
|
+
*/
|
|
1056
|
+
declare const TestCampaignErrorBase: new (args: {
|
|
1057
|
+
readonly message: string;
|
|
1058
|
+
}) => Cause.YieldableError & {
|
|
1059
|
+
readonly _tag: "@systemfsoftware/xstate-test/engine/propertyTest/TestCampaignError";
|
|
1060
|
+
} & Readonly<{
|
|
1061
|
+
readonly message: string;
|
|
1062
|
+
}>;
|
|
1063
|
+
declare class TestCampaignError extends TestCampaignErrorBase {
|
|
1064
|
+
/** One line per assertion that did not hold. */
|
|
1065
|
+
readonly failures: readonly string[];
|
|
1066
|
+
readonly coverage: TestCoverage;
|
|
1067
|
+
readonly name = "TestCampaignError";
|
|
1068
|
+
constructor(
|
|
1069
|
+
/** One line per assertion that did not hold. */
|
|
1070
|
+
failures: readonly string[], coverage: TestCoverage);
|
|
1071
|
+
}
|
|
1072
|
+
/**
|
|
1073
|
+
* One saved failure, as a {@link TestFailureStore} loads it.
|
|
1074
|
+
*
|
|
1075
|
+
* @experimental
|
|
1076
|
+
*/
|
|
1077
|
+
interface TestStoredFailure {
|
|
1078
|
+
readonly fixture: TestFixture;
|
|
1079
|
+
/** Where the failure is stored, such as a file path. Used in messages. */
|
|
1080
|
+
readonly location?: string;
|
|
1081
|
+
}
|
|
1082
|
+
/**
|
|
1083
|
+
* Persists failing fixtures and replays them before the next campaign. Both
|
|
1084
|
+
* entry points accept one as `failures`; `@xstate/test` provides a
|
|
1085
|
+
* file-system implementation.
|
|
1086
|
+
* @experimental
|
|
1087
|
+
*/
|
|
1088
|
+
interface TestFailureStore {
|
|
1089
|
+
/**
|
|
1090
|
+
* The key fixtures are stored under. Defaults to the machine id plus a hash
|
|
1091
|
+
* of the configured event cases and oracles.
|
|
1092
|
+
*/
|
|
1093
|
+
readonly key?: string;
|
|
1094
|
+
/**
|
|
1095
|
+
* `'first'` (the default) replays stored fixtures before the campaign and
|
|
1096
|
+
* fails on the first one that still reproduces. `'only'` replays them and
|
|
1097
|
+
* skips the campaign. `false` replays nothing.
|
|
1098
|
+
*/
|
|
1099
|
+
readonly replay?: 'first' | 'only' | false;
|
|
1100
|
+
/** Loads the fixtures stored under `key`. */
|
|
1101
|
+
readonly load: (key: string) => readonly TestStoredFailure[] | Promise<readonly TestStoredFailure[]>;
|
|
1102
|
+
/**
|
|
1103
|
+
* Stores the fixture of a failing campaign. Returns its location, which is
|
|
1104
|
+
* added to the failure message as `Saved: <location>`.
|
|
1105
|
+
*/
|
|
1106
|
+
readonly onFailure: (fixture: TestFixture, key: string, failure: ModelTestFailure<any, any>) => string | void | Promise<string | void>;
|
|
1107
|
+
/** Removes a stored fixture that no longer reproduces. */
|
|
1108
|
+
readonly remove?: (stored: TestStoredFailure, key: string) => void | Promise<void>;
|
|
1109
|
+
}
|
|
1110
|
+
/**
|
|
1111
|
+
* Executed-mode wiring handed to a {@link PropertyScenarioRunner}.
|
|
1112
|
+
*
|
|
1113
|
+
* @experimental
|
|
1114
|
+
*/
|
|
1115
|
+
interface PropertyExecutionConfig {
|
|
1116
|
+
readonly mode: TestMode;
|
|
1117
|
+
readonly registry: PropertyOutcomeRegistry;
|
|
1118
|
+
/** Outcomes pre-loaded before the run starts (replay). */
|
|
1119
|
+
readonly seededOutcomes?: readonly TestOutcomeRecord[];
|
|
1120
|
+
/**
|
|
1121
|
+
* Invoke sources replaced by an outcome stub. Recorded into replay fixtures
|
|
1122
|
+
* so a replay stubs the same sources, including ones that never resolved.
|
|
1123
|
+
*/
|
|
1124
|
+
readonly stubbedSources?: readonly string[];
|
|
1125
|
+
}
|
|
1126
|
+
/**
|
|
1127
|
+
* A single `target()` observation, recorded against the timeline.
|
|
1128
|
+
*
|
|
1129
|
+
* @experimental
|
|
1130
|
+
*/
|
|
1131
|
+
interface PropertyTargetObservation {
|
|
1132
|
+
readonly value: number;
|
|
1133
|
+
readonly label?: string | undefined;
|
|
1134
|
+
/** Number of timeline entries recorded when the observation was made. */
|
|
1135
|
+
readonly index: number;
|
|
1136
|
+
}
|
|
1137
|
+
/** @experimental */
|
|
1138
|
+
declare class PropertyScenarioRunner<TSnapshot extends Snapshot<unknown>, TEvent extends EventObject> {
|
|
1139
|
+
private readonly logic;
|
|
1140
|
+
private readonly input;
|
|
1141
|
+
private readonly startingSnapshot;
|
|
1142
|
+
private readonly serializeStartingSnapshot;
|
|
1143
|
+
private readonly prefixEvents;
|
|
1144
|
+
private readonly frontierId;
|
|
1145
|
+
private readonly sut;
|
|
1146
|
+
private readonly states;
|
|
1147
|
+
private readonly reference;
|
|
1148
|
+
private readonly invariant;
|
|
1149
|
+
private readonly eventDescriptors;
|
|
1150
|
+
private readonly coverage;
|
|
1151
|
+
private readonly executionConfig?;
|
|
1152
|
+
private snapshot;
|
|
1153
|
+
private initialSnapshot;
|
|
1154
|
+
private initialEffects;
|
|
1155
|
+
private initialTransitionIds;
|
|
1156
|
+
private readonly timeline;
|
|
1157
|
+
private readonly temporal;
|
|
1158
|
+
private stableStep;
|
|
1159
|
+
private started;
|
|
1160
|
+
private finished;
|
|
1161
|
+
private sutSession;
|
|
1162
|
+
private referenceSession;
|
|
1163
|
+
private lastObservation;
|
|
1164
|
+
private readonly inconclusiveTemporalIds;
|
|
1165
|
+
private generatedCommandCount;
|
|
1166
|
+
private readonly labelsSeen;
|
|
1167
|
+
private execution;
|
|
1168
|
+
private swarmCaseIds;
|
|
1169
|
+
private swarmEnabled;
|
|
1170
|
+
private targetFunction;
|
|
1171
|
+
private readonly targetObservations;
|
|
1172
|
+
/** Set for runs the adapter starts while shrinking a counterexample. */
|
|
1173
|
+
private shrinkRun;
|
|
1174
|
+
/** The failure this run ended in, if an oracle failed. */
|
|
1175
|
+
private failure;
|
|
1176
|
+
/** Records a label for this run. */
|
|
1177
|
+
readonly label: (name: string, value?: string | number | boolean) => void;
|
|
1178
|
+
/**
|
|
1179
|
+
* Marks this run as a shrink attempt: its labels and event cases are left
|
|
1180
|
+
* out of the campaign's statistics.
|
|
1181
|
+
*/
|
|
1182
|
+
markShrinkRun(): void;
|
|
1183
|
+
private recordEventCase;
|
|
1184
|
+
/** Records `name` when `condition` holds. */
|
|
1185
|
+
readonly classify: (condition: boolean, name: string) => void;
|
|
1186
|
+
/** Records an observation for targeted search. */
|
|
1187
|
+
readonly target: (observation: number, label?: string) => void;
|
|
1188
|
+
/**
|
|
1189
|
+
* Restricts this run to a subset of the declared event cases. Every other
|
|
1190
|
+
* case reports itself as inapplicable, so a run only exercises the enabled
|
|
1191
|
+
* ones. See the `swarm` option.
|
|
1192
|
+
*/
|
|
1193
|
+
setSwarm(caseIds: readonly string[]): void;
|
|
1194
|
+
private formatOptions;
|
|
1195
|
+
/** Sets how failure messages render snapshots. See `formatSnapshot`. */
|
|
1196
|
+
setFormatSnapshot(formatSnapshot: ((snapshot: TSnapshot) => unknown) | undefined): void;
|
|
1197
|
+
/** Evaluates `target` on every stable step. See the `target` option. */
|
|
1198
|
+
setTargetFunction(targetFunction: (context: TestInvariantContext<TSnapshot, TEvent>) => number): void;
|
|
1199
|
+
/** Outcomes observed while the executed run was alive. */
|
|
1200
|
+
private executionOutcomes;
|
|
1201
|
+
/** Observations recorded with `target()` during this run. */
|
|
1202
|
+
getTargetObservations(): readonly PropertyTargetObservation[];
|
|
1203
|
+
/** `true` when the run completed without a property failure. */
|
|
1204
|
+
isFinished(): boolean;
|
|
1205
|
+
constructor(logic: ActorLogic<TSnapshot, TEvent, unknown>, input: unknown, startingSnapshot: TSnapshot | undefined, serializeStartingSnapshot: ((snapshot: TSnapshot) => unknown) | undefined, prefixEvents: readonly TEvent[], frontierId: string | undefined, sut: TestSut<TSnapshot, TEvent> | undefined, states: TestStateAssertions<TSnapshot, TEvent> | undefined, reference: TestReference<TSnapshot, TEvent> | undefined, invariant: TestInvariant<TSnapshot, TEvent> | undefined, temporal: readonly TestTemporal<TSnapshot, TEvent>[], eventDescriptors: ReadonlyMap<string, AnyTestEventDescriptor<TSnapshot, TEvent>>, coverage: MutableTestCoverage, executionConfig?: PropertyExecutionConfig | undefined);
|
|
1206
|
+
start(): Promise<void>;
|
|
1207
|
+
canRun(event: TEvent, caseId: string): boolean;
|
|
1208
|
+
canRunGenerated(type: string, generated: unknown, caseId: string): boolean;
|
|
1209
|
+
private canRunResolved;
|
|
1210
|
+
private resolveGeneratedEvent;
|
|
1211
|
+
/** `outcome` commands only apply while the executed actor is running. */
|
|
1212
|
+
canRunOutcome(): boolean;
|
|
1213
|
+
canRunCommand(applicable: boolean): boolean;
|
|
1214
|
+
run(event: TEvent, caseId: string): Promise<void>;
|
|
1215
|
+
runGenerated(type: string, generated: unknown, caseId: string): Promise<void>;
|
|
1216
|
+
replay(command: TestCommand<TEvent>): Promise<void>;
|
|
1217
|
+
/** Queues the next resolution of the stubbed invoke source `src`. */
|
|
1218
|
+
outcome(src: string, outcome: TestActorOutcome): Promise<void>;
|
|
1219
|
+
advance(milliseconds: number): Promise<void>;
|
|
1220
|
+
/**
|
|
1221
|
+
* Advances the simulated clock the executed actor runs on, then drains
|
|
1222
|
+
* whatever the elapsed delays produced.
|
|
1223
|
+
*/
|
|
1224
|
+
private advanceExecuted;
|
|
1225
|
+
checkpoint(label?: string): Promise<void>;
|
|
1226
|
+
stop(): Promise<void>;
|
|
1227
|
+
finish(): void;
|
|
1228
|
+
/** Bounded temporal properties that the run ended before deciding. */
|
|
1229
|
+
getInconclusiveTemporalIds(): readonly string[];
|
|
1230
|
+
/** The number of stable steps observed so far. */
|
|
1231
|
+
getStableStep(): number;
|
|
1232
|
+
dispose(): Promise<void>;
|
|
1233
|
+
getSnapshot(): TSnapshot;
|
|
1234
|
+
getTrace(): TestTrace<TSnapshot, TEvent>;
|
|
1235
|
+
private executeEvent;
|
|
1236
|
+
private checkStable;
|
|
1237
|
+
/**
|
|
1238
|
+
* Runs the per-state assertions and any `meta.test` hooks that apply to
|
|
1239
|
+
* `snapshot`. Both entry points reach this on every stable step.
|
|
1240
|
+
*/
|
|
1241
|
+
private checkStateAssertions;
|
|
1242
|
+
private checkTemporal;
|
|
1243
|
+
private failTemporal;
|
|
1244
|
+
private compareObservations;
|
|
1245
|
+
private replaceLastObservation;
|
|
1246
|
+
/**
|
|
1247
|
+
* Records, on an executed step's timeline entry, the child actors whose
|
|
1248
|
+
* asynchronous work was still in flight when the step settled.
|
|
1249
|
+
*/
|
|
1250
|
+
private markPendingActors;
|
|
1251
|
+
/**
|
|
1252
|
+
* Executed mode only: the milliseconds until the tested actor's pending
|
|
1253
|
+
* `xstate.after` timer for `event` is due, or `undefined` when none is
|
|
1254
|
+
* scheduled.
|
|
1255
|
+
*/
|
|
1256
|
+
getAfterTimerRemaining(event: EventObject): number | undefined;
|
|
1257
|
+
/**
|
|
1258
|
+
* Fails the run with `summary`, recording a trace and a replay fixture the
|
|
1259
|
+
* same way an oracle failure does. Used by drivers that detect the run
|
|
1260
|
+
* departed from the sequence they planned.
|
|
1261
|
+
*/
|
|
1262
|
+
diverge(summary: string, cause?: unknown): never;
|
|
1263
|
+
/**
|
|
1264
|
+
* Appends one timeline entry per transition the actor system performed on
|
|
1265
|
+
* its own during an executed step: invoked/spawned actor lifecycle events,
|
|
1266
|
+
* delayed transitions, and relayed sends.
|
|
1267
|
+
*/
|
|
1268
|
+
private pushActorEntries;
|
|
1269
|
+
private recordSnapshot;
|
|
1270
|
+
private recordGeneratedCommand;
|
|
1271
|
+
private getActiveStateIds;
|
|
1272
|
+
private assertStarted;
|
|
1273
|
+
private fail;
|
|
1274
|
+
private getReplayFixture;
|
|
1275
|
+
}
|
|
1276
|
+
/** @experimental */
|
|
1277
|
+
interface PropertyFrontierContext<TSnapshot extends Snapshot<unknown>, TEvent extends EventObject> {
|
|
1278
|
+
readonly frontier: StatePath<TSnapshot, TEvent>;
|
|
1279
|
+
readonly index: number;
|
|
1280
|
+
readonly id: string;
|
|
1281
|
+
}
|
|
1282
|
+
/** @experimental */
|
|
1283
|
+
interface PropertyFrontierOptions<TSnapshot extends Snapshot<unknown>, TEvent extends EventObject> {
|
|
1284
|
+
readonly paths: readonly StatePath<TSnapshot, TEvent>[];
|
|
1285
|
+
readonly select?: (context: PropertyFrontierContext<TSnapshot, TEvent>) => boolean;
|
|
1286
|
+
readonly runsPerFrontier?: number | ((context: PropertyFrontierContext<TSnapshot, TEvent>) => number);
|
|
1287
|
+
}
|
|
1288
|
+
/**
|
|
1289
|
+
* A command generator, optionally paired with a relative generation weight. A
|
|
1290
|
+
* bare generator is equivalent to `{ generate, weight: 1 }`; a `weight` key is
|
|
1291
|
+
* what distinguishes the descriptor form.
|
|
1292
|
+
* @experimental
|
|
1293
|
+
*/
|
|
1294
|
+
interface PropertyCommandDescriptor<TGenerator> {
|
|
1295
|
+
readonly generate: TGenerator;
|
|
1296
|
+
/** Positive, finite relative generation weight. Defaults to `1`. */
|
|
1297
|
+
readonly weight?: number;
|
|
1298
|
+
}
|
|
1299
|
+
/** @experimental */
|
|
1300
|
+
type PropertyCommandGenerator<TKind extends PropertyGeneratorKind, TValue> = PropertyGenerator<TKind, TValue> | PropertyCommandDescriptor<PropertyGenerator<TKind, TValue>>;
|
|
1301
|
+
/**
|
|
1302
|
+
* The options both entry points share: what to send, what to send it to, and
|
|
1303
|
+
* what to assert. `testPaths()` adds its own path options; `propertyTest()`
|
|
1304
|
+
* adds {@link PropertyOptions}.
|
|
1305
|
+
* @experimental
|
|
1306
|
+
*/
|
|
1307
|
+
interface TestOptions<TSnapshot extends Snapshot<unknown>, TEvent extends EventObject, TInput, TKind extends PropertyGeneratorKind = PropertyGeneratorKind> {
|
|
1308
|
+
/**
|
|
1309
|
+
* `'pure'` (the default) steps the machine through `transition()`, so no
|
|
1310
|
+
* effects run. `'executed'` runs a real actor on a `SimulatedClock`:
|
|
1311
|
+
* invoked and spawned actors start, `onDone`/`onError`/`onSnapshot` fire,
|
|
1312
|
+
* and `after` transitions are reached with generated `advance` commands.
|
|
1313
|
+
*/
|
|
1314
|
+
readonly mode?: TestMode;
|
|
1315
|
+
/**
|
|
1316
|
+
* Actor logic substituted for the machine's named invoke/spawn sources
|
|
1317
|
+
* before the campaign runs, via `machine.provide({ actors })`. Executed
|
|
1318
|
+
* mode only.
|
|
1319
|
+
*/
|
|
1320
|
+
readonly actors?: Readonly<Record<string, ActorLogic<any, any, any>>>;
|
|
1321
|
+
/**
|
|
1322
|
+
* Invoke source names whose actors are replaced by a stub that resolves from
|
|
1323
|
+
* an `outcome` command instead of running for real.
|
|
1324
|
+
*
|
|
1325
|
+
* `propertyTest()` generates the outcomes, so the adapter shrinks service
|
|
1326
|
+
* results alongside events; it requires `mode: 'executed'`. `testPaths()`
|
|
1327
|
+
* samples them, the same way it samples event payloads, in either mode: pure
|
|
1328
|
+
* mode sends the sampled `output` or `error` as the payload of the
|
|
1329
|
+
* `xstate.done.actor` / `xstate.error.actor` step the traversal took, and
|
|
1330
|
+
* executed mode resolves the stub with it. In executed mode, a source that a
|
|
1331
|
+
* path resolves but that is not declared here is stubbed with a synthesized
|
|
1332
|
+
* outcome.
|
|
1333
|
+
*/
|
|
1334
|
+
readonly outcomes?: {
|
|
1335
|
+
readonly [src: string]: PropertyCommandGenerator<TKind, TestActorOutcome>;
|
|
1336
|
+
};
|
|
1337
|
+
/**
|
|
1338
|
+
* Event types a run may send, keyed by type: a generator for the payload, an
|
|
1339
|
+
* event descriptor, or an array of either.
|
|
1340
|
+
*/
|
|
1341
|
+
readonly events?: TestEventGenerators<TSnapshot, TEvent, TKind>;
|
|
1342
|
+
/** The system under test, compared with the model after every stable step. */
|
|
1343
|
+
readonly sut?: TestSut<TSnapshot, TEvent>;
|
|
1344
|
+
/**
|
|
1345
|
+
* Per-state assertions run after every stable step. A `states` map on the
|
|
1346
|
+
* SUT session takes precedence over this one.
|
|
1347
|
+
*/
|
|
1348
|
+
readonly states?: TestStateAssertions<TSnapshot, TEvent>;
|
|
1349
|
+
/** A second implementation compared with the model after every stable step. */
|
|
1350
|
+
readonly reference?: TestReference<TSnapshot, TEvent>;
|
|
1351
|
+
/** Machine input for every run. */
|
|
1352
|
+
readonly input?: TInput;
|
|
1353
|
+
/**
|
|
1354
|
+
* Starts every run from `snapshot` instead of the initial state.
|
|
1355
|
+
* `serializeSnapshot` produces the value recorded in replay fixtures.
|
|
1356
|
+
*/
|
|
1357
|
+
readonly start?: {
|
|
1358
|
+
readonly snapshot: TSnapshot;
|
|
1359
|
+
readonly serializeSnapshot: (snapshot: TSnapshot) => unknown;
|
|
1360
|
+
};
|
|
1361
|
+
/**
|
|
1362
|
+
* Evaluated on every stable step. The campaign keeps the best (highest)
|
|
1363
|
+
* observed value, reports it as `coverage.exploration.target`, and, with
|
|
1364
|
+
* `frontiers: { strategy: 'target' }`, replays the prefixes that reached it
|
|
1365
|
+
* as frontiers for the next batch. Equivalent to calling `target()` from
|
|
1366
|
+
* the invariant.
|
|
1367
|
+
*/
|
|
1368
|
+
readonly target?: (context: TestInvariantContext<TSnapshot, TEvent>) => number;
|
|
1369
|
+
/**
|
|
1370
|
+
* Called once per run, after the runner has finished and been disposed.
|
|
1371
|
+
* `passed` is `false` when the run ended in a property failure.
|
|
1372
|
+
*/
|
|
1373
|
+
readonly collect?: (trace: TestTrace<TSnapshot, TEvent>, info: {
|
|
1374
|
+
readonly passed: boolean;
|
|
1375
|
+
readonly runIndex: number;
|
|
1376
|
+
}) => void;
|
|
1377
|
+
/** Checked on every stable step; throws to fail the run. */
|
|
1378
|
+
readonly invariant?: TestInvariant<TSnapshot, TEvent>;
|
|
1379
|
+
/** Temporal properties checked on every stable step. */
|
|
1380
|
+
readonly temporal?: readonly TestTemporal<TSnapshot, TEvent>[];
|
|
1381
|
+
/**
|
|
1382
|
+
* States that at least one run must enter, as state values (`'a.b'`), state
|
|
1383
|
+
* node ids (`'#machine.a'`), or tags. Checked when the campaign ends. Each
|
|
1384
|
+
* target is reported in `coverage.temporal` as `reachable:<target>`.
|
|
1385
|
+
*/
|
|
1386
|
+
readonly reachable?: readonly string[];
|
|
1387
|
+
/** Minimum label frequencies the campaign must reach. */
|
|
1388
|
+
readonly expectLabels?: TestLabelExpectations;
|
|
1389
|
+
/**
|
|
1390
|
+
* Saves the fixture of a failing campaign, and replays saved fixtures
|
|
1391
|
+
* before the next one. See {@link TestFailureStore}.
|
|
1392
|
+
*/
|
|
1393
|
+
readonly failures?: TestFailureStore;
|
|
1394
|
+
/**
|
|
1395
|
+
* Reports `formatTestStatistics(coverage)` after a passing campaign: the
|
|
1396
|
+
* distribution of executed event cases and the share of runs per label.
|
|
1397
|
+
* `true` prints it through the package's console logger; a function
|
|
1398
|
+
* receives the same string once, instead of the print, and is awaited when
|
|
1399
|
+
* it returns a promise; `false`, or leaving it out, reports nothing. A
|
|
1400
|
+
* function that throws or rejects fails the campaign with that error, after
|
|
1401
|
+
* the system under test's `complete` hook ran.
|
|
1402
|
+
*/
|
|
1403
|
+
readonly statistics?: boolean | ((report: string) => void | Promise<void>);
|
|
1404
|
+
/**
|
|
1405
|
+
* Projects a snapshot onto the value printed for each step of a failure
|
|
1406
|
+
* trace. Defaults to `{ value, context }` for machine snapshots.
|
|
1407
|
+
*/
|
|
1408
|
+
readonly formatSnapshot?: (snapshot: TSnapshot) => unknown;
|
|
1409
|
+
}
|
|
1410
|
+
/**
|
|
1411
|
+
* The options only `propertyTest()` accepts.
|
|
1412
|
+
*
|
|
1413
|
+
* @experimental
|
|
1414
|
+
*/
|
|
1415
|
+
interface PropertyOptions<TSnapshot extends Snapshot<unknown>, TEvent extends EventObject, TKind extends PropertyGeneratorKind = PropertyGeneratorKind> {
|
|
1416
|
+
/** The generator engine. `@xstate/test` supplies the fast-check one. */
|
|
1417
|
+
readonly adapter: TestAdapter<TKind>;
|
|
1418
|
+
/**
|
|
1419
|
+
* Generators for runtime commands: `advance` (milliseconds), `checkpoint`
|
|
1420
|
+
* (`{ label? }`), and `stop` (`{}`).
|
|
1421
|
+
*/
|
|
1422
|
+
readonly commands?: {
|
|
1423
|
+
readonly advance?: PropertyCommandGenerator<TKind, number>;
|
|
1424
|
+
readonly checkpoint?: PropertyCommandGenerator<TKind, {
|
|
1425
|
+
readonly label?: string;
|
|
1426
|
+
}>;
|
|
1427
|
+
readonly stop?: PropertyCommandGenerator<TKind, Record<string, never>>;
|
|
1428
|
+
};
|
|
1429
|
+
/**
|
|
1430
|
+
* Prefixes to start runs from: fixed paths, `{ paths, select,
|
|
1431
|
+
* runsPerFrontier }`, `'auto'` (coverage-guided), or
|
|
1432
|
+
* `{ strategy: 'target' }` (targeted search).
|
|
1433
|
+
*/
|
|
1434
|
+
readonly frontiers?: readonly StatePath<TSnapshot, TEvent>[] | PropertyFrontierOptions<TSnapshot, TEvent> | 'auto' | PropertyAutoFrontierOptions | PropertyTargetFrontierOptions;
|
|
1435
|
+
/**
|
|
1436
|
+
* Swarm testing: each run only enables a seeded random subset of the
|
|
1437
|
+
* declared event cases, so rarely-reachable interleavings are not crowded
|
|
1438
|
+
* out by the most common ones. `true` enables at least half the cases.
|
|
1439
|
+
*/
|
|
1440
|
+
readonly swarm?: boolean | PropertySwarmOptions;
|
|
1441
|
+
/**
|
|
1442
|
+
* Stops the campaign as soon as the condition holds. Coverage is
|
|
1443
|
+
* re-evaluated between batches of `batchRuns` runs. Without `until` (and
|
|
1444
|
+
* without `frontiers: 'auto'`) the adapter is invoked exactly once.
|
|
1445
|
+
*/
|
|
1446
|
+
readonly until?: TestStopCondition;
|
|
1447
|
+
/** Runs per batch in a batched campaign. Defaults to 25. */
|
|
1448
|
+
readonly batchRuns?: number;
|
|
1449
|
+
/** Total runs a batched campaign may complete. Defaults to 100. */
|
|
1450
|
+
readonly maxRuns?: number;
|
|
1451
|
+
}
|
|
1452
|
+
/** @experimental */
|
|
1453
|
+
type PropertyTestOptions<TSnapshot extends Snapshot<unknown>, TEvent extends EventObject, TInput, TKind extends PropertyGeneratorKind = PropertyGeneratorKind> = TestOptions<TSnapshot, TEvent, TInput, TKind> & PropertyOptions<TSnapshot, TEvent, TKind>;
|
|
1454
|
+
/**
|
|
1455
|
+
* Minimum frequencies required of labels recorded during the campaign.
|
|
1456
|
+
*
|
|
1457
|
+
* @experimental
|
|
1458
|
+
*/
|
|
1459
|
+
interface TestLabelExpectations {
|
|
1460
|
+
readonly [name: string]: {
|
|
1461
|
+
/** Minimum share of attempted runs that must record the label, `0`..`1`. */
|
|
1462
|
+
readonly min?: number;
|
|
1463
|
+
/** Minimum total occurrences of the label. */
|
|
1464
|
+
readonly minCount?: number;
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1467
|
+
/**
|
|
1468
|
+
* Ratios are `covered / (covered + uncovered)`. Every listed key must hold;
|
|
1469
|
+
* `any` holds when at least one of its conditions does.
|
|
1470
|
+
* @experimental
|
|
1471
|
+
*/
|
|
1472
|
+
interface TestStopConditionObject {
|
|
1473
|
+
readonly stateNodes?: number;
|
|
1474
|
+
readonly transitions?: number;
|
|
1475
|
+
readonly transitionPairs?: number;
|
|
1476
|
+
/** The share of event cases executed at least once. */
|
|
1477
|
+
readonly eventCases?: number;
|
|
1478
|
+
readonly requirements?: number;
|
|
1479
|
+
readonly runs?: number;
|
|
1480
|
+
readonly timeMs?: number;
|
|
1481
|
+
readonly any?: readonly TestStopCondition[];
|
|
1482
|
+
}
|
|
1483
|
+
/** @experimental */
|
|
1484
|
+
type TestStopCondition = TestStopConditionObject | ((coverage: TestCoverage) => boolean);
|
|
1485
|
+
/**
|
|
1486
|
+
* Swarm testing options. See the `swarm` option.
|
|
1487
|
+
*
|
|
1488
|
+
* @experimental
|
|
1489
|
+
*/
|
|
1490
|
+
interface PropertySwarmOptions {
|
|
1491
|
+
/**
|
|
1492
|
+
* The fewest event cases a run may enable. Defaults to half the declared
|
|
1493
|
+
* cases, rounded up.
|
|
1494
|
+
*/
|
|
1495
|
+
readonly minCases?: number;
|
|
1496
|
+
/** Campaign seed the per-run subsets are derived from. Defaults to `0`. */
|
|
1497
|
+
readonly seed?: number;
|
|
1498
|
+
}
|
|
1499
|
+
/**
|
|
1500
|
+
* Targeted search. See `frontiers: { strategy: 'target' }`.
|
|
1501
|
+
*
|
|
1502
|
+
* @experimental
|
|
1503
|
+
*/
|
|
1504
|
+
interface PropertyTargetFrontierOptions {
|
|
1505
|
+
readonly strategy: 'target';
|
|
1506
|
+
/** Best-scoring prefixes carried into the next batch. Defaults to 5. */
|
|
1507
|
+
readonly maxFrontiers?: number;
|
|
1508
|
+
/** Runs per frontier. Defaults to an even split of the batch. */
|
|
1509
|
+
readonly runsPerFrontier?: number;
|
|
1510
|
+
}
|
|
1511
|
+
/**
|
|
1512
|
+
* Coverage-guided exploration. See `frontiers: 'auto'`.
|
|
1513
|
+
*
|
|
1514
|
+
* @experimental
|
|
1515
|
+
*/
|
|
1516
|
+
interface PropertyAutoFrontierOptions {
|
|
1517
|
+
readonly strategy: 'uncovered';
|
|
1518
|
+
/** Frontiers explored per batch. Defaults to 5. */
|
|
1519
|
+
readonly maxFrontiers?: number;
|
|
1520
|
+
/** Runs per frontier. Defaults to an even split of the batch. */
|
|
1521
|
+
readonly runsPerFrontier?: number;
|
|
1522
|
+
/** Traversal limit for the shortest-path search. Defaults to 1000. */
|
|
1523
|
+
readonly limit?: number;
|
|
1524
|
+
}
|
|
1525
|
+
/**
|
|
1526
|
+
* Thrown by `replayTest()` when a fixture recorded from a failing run
|
|
1527
|
+
* replays without reproducing that failure — the regression is fixed, or the
|
|
1528
|
+
* machine no longer behaves the way the fixture recorded.
|
|
1529
|
+
* @experimental
|
|
1530
|
+
*/
|
|
1531
|
+
declare const ReplayNotReproducedErrorBase: new (args: {
|
|
1532
|
+
readonly message: string;
|
|
1533
|
+
}) => Cause.YieldableError & {
|
|
1534
|
+
readonly _tag: "@systemfsoftware/xstate-test/engine/propertyTest/ReplayNotReproducedError";
|
|
1535
|
+
} & Readonly<{
|
|
1536
|
+
readonly message: string;
|
|
1537
|
+
}>;
|
|
1538
|
+
declare class ReplayNotReproducedError extends ReplayNotReproducedErrorBase {
|
|
1539
|
+
/** The step the fixture recorded the failure at. */
|
|
1540
|
+
readonly step: number;
|
|
1541
|
+
readonly name = "ReplayNotReproducedError";
|
|
1542
|
+
constructor(
|
|
1543
|
+
/** The step the fixture recorded the failure at. */
|
|
1544
|
+
step: number);
|
|
1545
|
+
}
|
|
1546
|
+
/**
|
|
1547
|
+
* Replays a {@link TestFixture} without a generator. Resolves with the
|
|
1548
|
+
* replayed trace; see the `expect` option for how failures are reported.
|
|
1549
|
+
* @experimental
|
|
1550
|
+
*/
|
|
1551
|
+
declare const replayTest: {
|
|
1552
|
+
<TSource extends ActorLogic<any, any, any>>(fixture: TestFixture | LegacyPortablePropertyReplayFixture, options: {
|
|
1553
|
+
readonly invariant?: TestInvariant<SnapshotFromSource<TSource>, EventFromSource<TSource>>;
|
|
1554
|
+
readonly temporal?: readonly TestTemporal<SnapshotFromSource<TSource>, EventFromSource<TSource>>[];
|
|
1555
|
+
readonly reference?: TestReference<SnapshotFromSource<TSource>, EventFromSource<TSource>>;
|
|
1556
|
+
readonly sut?: TestSut<SnapshotFromSource<TSource>, EventFromSource<TSource>>;
|
|
1557
|
+
readonly states?: TestStateAssertions<SnapshotFromSource<TSource>, EventFromSource<TSource>>;
|
|
1558
|
+
readonly restoreSnapshot?: (snapshot: unknown) => SnapshotFromSource<TSource>;
|
|
1559
|
+
readonly formatSnapshot?: (snapshot: SnapshotFromSource<TSource>) => unknown;
|
|
1560
|
+
readonly mode?: TestMode;
|
|
1561
|
+
readonly actors?: Readonly<Record<string, ActorLogic<any, any, any>>>;
|
|
1562
|
+
readonly expect?: 'failure' | 'pass';
|
|
1563
|
+
}): (source: TSource) => Promise<TestTrace<SnapshotFromSource<TSource>, EventFromSource<TSource>>>;
|
|
1564
|
+
<TSource extends ActorLogic<any, any, any>>(source: TSource, fixture: TestFixture | LegacyPortablePropertyReplayFixture, options: {
|
|
1565
|
+
readonly invariant?: TestInvariant<SnapshotFromSource<TSource>, EventFromSource<TSource>>;
|
|
1566
|
+
readonly temporal?: readonly TestTemporal<SnapshotFromSource<TSource>, EventFromSource<TSource>>[];
|
|
1567
|
+
readonly reference?: TestReference<SnapshotFromSource<TSource>, EventFromSource<TSource>>;
|
|
1568
|
+
readonly sut?: TestSut<SnapshotFromSource<TSource>, EventFromSource<TSource>>;
|
|
1569
|
+
readonly states?: TestStateAssertions<SnapshotFromSource<TSource>, EventFromSource<TSource>>;
|
|
1570
|
+
readonly restoreSnapshot?: (snapshot: unknown) => SnapshotFromSource<TSource>;
|
|
1571
|
+
readonly formatSnapshot?: (snapshot: SnapshotFromSource<TSource>) => unknown;
|
|
1572
|
+
readonly mode?: TestMode;
|
|
1573
|
+
readonly actors?: Readonly<Record<string, ActorLogic<any, any, any>>>;
|
|
1574
|
+
readonly expect?: 'failure' | 'pass';
|
|
1575
|
+
}): Promise<TestTrace<SnapshotFromSource<TSource>, EventFromSource<TSource>>>;
|
|
1576
|
+
};
|
|
1577
|
+
/**
|
|
1578
|
+
* Converts a trace to JSON-safe data, calling `toJSON()` on snapshots.
|
|
1579
|
+
*
|
|
1580
|
+
* @experimental
|
|
1581
|
+
*/
|
|
1582
|
+
declare function serializeTestTrace<TSnapshot extends Snapshot<unknown>, TEvent extends EventObject>(trace: TestTrace<TSnapshot, TEvent>): unknown;
|
|
1583
|
+
/**
|
|
1584
|
+
* One completed operation of a concurrent history: the event that was sent
|
|
1585
|
+
* (`invocation`), what the system under test answered (`response`), and the
|
|
1586
|
+
* interval during which the operation was in flight. Intervals may overlap.
|
|
1587
|
+
* @experimental
|
|
1588
|
+
*/
|
|
1589
|
+
interface LinearizabilityEntry<TEvent = unknown> {
|
|
1590
|
+
readonly id: string | number;
|
|
1591
|
+
/** The concurrent branch the operation ran on. Informational. */
|
|
1592
|
+
readonly actor?: string;
|
|
1593
|
+
readonly invocation: TEvent;
|
|
1594
|
+
readonly response: unknown;
|
|
1595
|
+
readonly start: number;
|
|
1596
|
+
readonly end: number;
|
|
1597
|
+
}
|
|
1598
|
+
/**
|
|
1599
|
+
* The sequential specification a history is checked against.
|
|
1600
|
+
*
|
|
1601
|
+
* @experimental
|
|
1602
|
+
*/
|
|
1603
|
+
interface LinearizabilityModel<TState, TEvent = unknown> {
|
|
1604
|
+
readonly initial: TState;
|
|
1605
|
+
readonly apply: (state: TState, event: TEvent) => {
|
|
1606
|
+
readonly state: TState;
|
|
1607
|
+
readonly response: unknown;
|
|
1608
|
+
};
|
|
1609
|
+
readonly equalResponse?: ((model: unknown, observed: unknown) => boolean) | undefined;
|
|
1610
|
+
/**
|
|
1611
|
+
* Returns a stable string identity for a state, used to memoize search
|
|
1612
|
+
* branches. Return `undefined` for states that cannot be serialized; those
|
|
1613
|
+
* branches are then explored without memoization. Defaults to
|
|
1614
|
+
* `JSON.stringify`.
|
|
1615
|
+
*/
|
|
1616
|
+
readonly serializeState?: ((state: TState) => string | undefined) | undefined;
|
|
1617
|
+
}
|
|
1618
|
+
/** @experimental */
|
|
1619
|
+
interface LinearizabilityOptions {
|
|
1620
|
+
/**
|
|
1621
|
+
* Maximum number of candidate linearization steps to explore before giving
|
|
1622
|
+
* up. When the cap is hit the result is `linearizable: false` with
|
|
1623
|
+
* `truncated: true`, which means "not proven", not "proven wrong".
|
|
1624
|
+
*/
|
|
1625
|
+
readonly maxExplored?: number | undefined;
|
|
1626
|
+
}
|
|
1627
|
+
/** @experimental */
|
|
1628
|
+
interface LinearizabilityResult<TEvent = unknown> {
|
|
1629
|
+
readonly linearizable: boolean;
|
|
1630
|
+
/** The sequential order that explains the history, when one was found. */
|
|
1631
|
+
readonly witness?: readonly LinearizabilityEntry<TEvent>[] | undefined;
|
|
1632
|
+
readonly explored: number;
|
|
1633
|
+
readonly truncated: boolean;
|
|
1634
|
+
}
|
|
1635
|
+
/**
|
|
1636
|
+
* Decides whether a concurrent `history` is linearizable against a sequential
|
|
1637
|
+
* `model`: whether some total order of the operations, consistent with the
|
|
1638
|
+
* real-time order of non-overlapping operations, produces exactly the observed
|
|
1639
|
+
* responses.
|
|
1640
|
+
*
|
|
1641
|
+
* The search is the Wing–Gong just-in-time linearization: depth-first over the
|
|
1642
|
+
* operations that could come next (those starting no later than the earliest
|
|
1643
|
+
* end time still outstanding), backtracking whenever a response disagrees with
|
|
1644
|
+
* the model, and memoizing `(state, completed set)` pairs so equivalent
|
|
1645
|
+
* branches are explored once.
|
|
1646
|
+
* @experimental
|
|
1647
|
+
*/
|
|
1648
|
+
declare const checkLinearizable: {
|
|
1649
|
+
<TState, TEvent>(model: LinearizabilityModel<TState, TEvent>, options?: LinearizabilityOptions): (history: readonly LinearizabilityEntry<TEvent>[]) => LinearizabilityResult<TEvent>;
|
|
1650
|
+
<TState, TEvent>(history: readonly LinearizabilityEntry<TEvent>[], model: LinearizabilityModel<TState, TEvent>, options?: LinearizabilityOptions): LinearizabilityResult<TEvent>;
|
|
1651
|
+
};
|
|
1652
|
+
/**
|
|
1653
|
+
* The system under test driven by {@link runParallelPropertyCommands}.
|
|
1654
|
+
*
|
|
1655
|
+
* @experimental
|
|
1656
|
+
*/
|
|
1657
|
+
interface ParallelPropertySutSession<TEvent> {
|
|
1658
|
+
/**
|
|
1659
|
+
* Sends an event. The resolved value is the operation's response; when it is
|
|
1660
|
+
* `undefined` the response is read back with `read()`, which makes a
|
|
1661
|
+
* `TestSutSession` usable as-is.
|
|
1662
|
+
*/
|
|
1663
|
+
readonly send: (event: TEvent, context?: unknown) => unknown;
|
|
1664
|
+
readonly read?: () => unknown;
|
|
1665
|
+
readonly dispose?: () => void | Promise<void>;
|
|
1666
|
+
}
|
|
1667
|
+
/** @experimental */
|
|
1668
|
+
interface ParallelPropertySut<TSnapshot, TEvent> {
|
|
1669
|
+
readonly create: (context: TestSutContext<any, any>) => ParallelPropertySutSession<TEvent> | Promise<ParallelPropertySutSession<TEvent>>;
|
|
1670
|
+
/** Projects a model snapshot to the value a response is compared against. */
|
|
1671
|
+
readonly projectModel: (snapshot: TSnapshot) => unknown;
|
|
1672
|
+
/** Projects a raw SUT response before comparison. */
|
|
1673
|
+
readonly projectSut?: (observed: unknown) => unknown;
|
|
1674
|
+
}
|
|
1675
|
+
/** @experimental */
|
|
1676
|
+
interface ParallelPropertyCommandsOptions<TLogic extends AnyActorLogic> {
|
|
1677
|
+
/** Events applied sequentially before the concurrent phase starts. */
|
|
1678
|
+
readonly prefix?: readonly EventFromLogic<TLogic>[];
|
|
1679
|
+
/** Each branch runs its events sequentially, all branches run concurrently. */
|
|
1680
|
+
readonly branches: readonly (readonly EventFromLogic<TLogic>[])[];
|
|
1681
|
+
readonly sut: ParallelPropertySut<SnapshotFrom<TLogic>, EventFromLogic<TLogic>>;
|
|
1682
|
+
readonly input?: unknown;
|
|
1683
|
+
readonly maxExplored?: number;
|
|
1684
|
+
readonly equalResponse?: (model: unknown, observed: unknown) => boolean;
|
|
1685
|
+
/**
|
|
1686
|
+
* Returns a stable string identity for a model snapshot, used to memoize
|
|
1687
|
+
* search branches. Defaults to the snapshot's `value`, `context` and
|
|
1688
|
+
* `status`, which keeps states that merely share a projection distinct.
|
|
1689
|
+
*/
|
|
1690
|
+
readonly serializeState?: (snapshot: SnapshotFrom<TLogic>) => string | undefined;
|
|
1691
|
+
}
|
|
1692
|
+
/** @experimental */
|
|
1693
|
+
interface ParallelPropertyCommandsResult<TEvent> extends LinearizabilityResult<TEvent> {
|
|
1694
|
+
readonly history: readonly LinearizabilityEntry<TEvent>[];
|
|
1695
|
+
}
|
|
1696
|
+
/**
|
|
1697
|
+
* Runs a sequential prefix and then N branches concurrently against a system
|
|
1698
|
+
* under test, records the resulting concurrent history, and checks it against
|
|
1699
|
+
* the machine's own pure `transition()` as the sequential specification.
|
|
1700
|
+
*
|
|
1701
|
+
* This is the analogue of `parallel_commands` in QuickCheck State Machine and
|
|
1702
|
+
* PropEr: the prefix puts the system in an interesting state, the branches
|
|
1703
|
+
* race, and linearizability decides whether the observed responses could have
|
|
1704
|
+
* come from any sequential interleaving.
|
|
1705
|
+
* @experimental
|
|
1706
|
+
*/
|
|
1707
|
+
declare const runParallelPropertyCommands: {
|
|
1708
|
+
<TLogic extends AnyActorLogic>(options: ParallelPropertyCommandsOptions<TLogic>): (logic: TLogic) => Promise<ParallelPropertyCommandsResult<EventFromLogic<TLogic>>>;
|
|
1709
|
+
<TLogic extends AnyActorLogic>(logic: TLogic, options: ParallelPropertyCommandsOptions<TLogic>): Promise<ParallelPropertyCommandsResult<EventFromLogic<TLogic>>>;
|
|
1710
|
+
};
|
|
1711
|
+
/**
|
|
1712
|
+
* Options for {@link formatTestCoverage}.
|
|
1713
|
+
*
|
|
1714
|
+
* @experimental
|
|
1715
|
+
*/
|
|
1716
|
+
interface FormatTestCoverageOptions {
|
|
1717
|
+
/** `'text'` (default) renders plain text; `'markdown'` renders tables. */
|
|
1718
|
+
readonly format?: 'text' | 'markdown';
|
|
1719
|
+
}
|
|
1720
|
+
/**
|
|
1721
|
+
* Options for {@link formatTestCoverageJUnit}.
|
|
1722
|
+
*
|
|
1723
|
+
* @experimental
|
|
1724
|
+
*/
|
|
1725
|
+
interface FormatTestCoverageJUnitOptions {
|
|
1726
|
+
readonly suiteName?: string;
|
|
1727
|
+
}
|
|
1728
|
+
/**
|
|
1729
|
+
* Options for {@link formatTestCoverageHTML}.
|
|
1730
|
+
*
|
|
1731
|
+
* @experimental
|
|
1732
|
+
*/
|
|
1733
|
+
interface FormatTestCoverageHTMLOptions {
|
|
1734
|
+
readonly title?: string;
|
|
1735
|
+
}
|
|
1736
|
+
/**
|
|
1737
|
+
* A JSON-safe summary of a single coverage dimension.
|
|
1738
|
+
*
|
|
1739
|
+
* @experimental
|
|
1740
|
+
*/
|
|
1741
|
+
interface TestCoverageDimensionJSON {
|
|
1742
|
+
readonly total: number;
|
|
1743
|
+
readonly covered: number;
|
|
1744
|
+
readonly ratio: number;
|
|
1745
|
+
readonly counts: Record<string, number>;
|
|
1746
|
+
readonly coveredIds: string[];
|
|
1747
|
+
readonly uncovered: string[];
|
|
1748
|
+
readonly unreachable: string[];
|
|
1749
|
+
readonly unknown: string[];
|
|
1750
|
+
}
|
|
1751
|
+
/**
|
|
1752
|
+
* The stable, versioned JSON representation of a {@link TestCoverage}.
|
|
1753
|
+
*
|
|
1754
|
+
* @experimental
|
|
1755
|
+
*/
|
|
1756
|
+
interface TestCoverageJSON {
|
|
1757
|
+
readonly formatVersion: 1;
|
|
1758
|
+
readonly totals: {
|
|
1759
|
+
readonly runs: number;
|
|
1760
|
+
readonly steps: number;
|
|
1761
|
+
readonly skipped: number;
|
|
1762
|
+
readonly prefixSteps: number;
|
|
1763
|
+
readonly generatedSteps: number;
|
|
1764
|
+
readonly invariantChecks: number;
|
|
1765
|
+
readonly temporalChecks: number;
|
|
1766
|
+
readonly clockAdvances: number;
|
|
1767
|
+
readonly checkpoints: number;
|
|
1768
|
+
readonly stops: number;
|
|
1769
|
+
readonly sutComparisons: number;
|
|
1770
|
+
readonly oracleComparisons: number;
|
|
1771
|
+
};
|
|
1772
|
+
readonly dimensions: Record<string, TestCoverageDimensionJSON>;
|
|
1773
|
+
readonly eventCases: Record<string, TestEventCaseCounts>;
|
|
1774
|
+
readonly temporal: {
|
|
1775
|
+
readonly satisfied: string[];
|
|
1776
|
+
readonly failed: string[];
|
|
1777
|
+
readonly inconclusive: string[];
|
|
1778
|
+
readonly counts: Record<string, {
|
|
1779
|
+
readonly satisfied: number;
|
|
1780
|
+
readonly failed: number;
|
|
1781
|
+
readonly inconclusive: number;
|
|
1782
|
+
}>;
|
|
1783
|
+
readonly warnings: string[];
|
|
1784
|
+
};
|
|
1785
|
+
readonly labels: Record<string, {
|
|
1786
|
+
readonly count: number;
|
|
1787
|
+
readonly values: Record<string, number>;
|
|
1788
|
+
readonly share: number;
|
|
1789
|
+
}>;
|
|
1790
|
+
readonly exploration: {
|
|
1791
|
+
readonly stoppedBecause: string;
|
|
1792
|
+
readonly configuredRuns: number | null;
|
|
1793
|
+
readonly completedRuns: number;
|
|
1794
|
+
readonly attemptedRuns: number;
|
|
1795
|
+
readonly shrinkRuns: number;
|
|
1796
|
+
readonly maximumSequenceLength: number | null;
|
|
1797
|
+
readonly maximumObservedSequenceLength: number;
|
|
1798
|
+
readonly truncated: boolean;
|
|
1799
|
+
readonly truncationReasons: string[];
|
|
1800
|
+
readonly frontiers: {
|
|
1801
|
+
readonly id: string;
|
|
1802
|
+
readonly prefixLength: number;
|
|
1803
|
+
readonly runBudget: number | null;
|
|
1804
|
+
readonly configuredRuns: number | null;
|
|
1805
|
+
readonly completedRuns: number;
|
|
1806
|
+
readonly attemptedRuns: number;
|
|
1807
|
+
}[];
|
|
1808
|
+
readonly seeds: {
|
|
1809
|
+
readonly frontierId: string;
|
|
1810
|
+
readonly engine: string | null;
|
|
1811
|
+
readonly seed: number | null;
|
|
1812
|
+
readonly path: string | null;
|
|
1813
|
+
}[];
|
|
1814
|
+
/** Only present when swarm testing was enabled. */
|
|
1815
|
+
readonly swarm?: {
|
|
1816
|
+
readonly runs: number;
|
|
1817
|
+
readonly averageEnabled: number;
|
|
1818
|
+
};
|
|
1819
|
+
/** Only present when a target observation was recorded. */
|
|
1820
|
+
readonly target?: {
|
|
1821
|
+
readonly best: number;
|
|
1822
|
+
readonly label: string | null;
|
|
1823
|
+
readonly improvements: number;
|
|
1824
|
+
};
|
|
1825
|
+
};
|
|
1826
|
+
}
|
|
1827
|
+
/**
|
|
1828
|
+
* Thresholds accepted by {@link assertTestCoverage}.
|
|
1829
|
+
*
|
|
1830
|
+
* @experimental
|
|
1831
|
+
*/
|
|
1832
|
+
type TestCoverageThresholds = { readonly [K in DimensionKey]?: number; };
|
|
1833
|
+
type DimensionKey = 'states' | 'stateNodes' | 'configurations' | 'statuses' | 'eventTypes' | 'transitions' | 'transitionPairs' | 'requirements' | 'frontiers';
|
|
1834
|
+
/**
|
|
1835
|
+
* Formats a {@link TestCoverage} as human-readable text or markdown.
|
|
1836
|
+
*
|
|
1837
|
+
* @experimental
|
|
1838
|
+
*/
|
|
1839
|
+
declare const formatTestCoverage: {
|
|
1840
|
+
(options?: FormatTestCoverageOptions): (coverage: TestCoverage) => string;
|
|
1841
|
+
(coverage: TestCoverage, options?: FormatTestCoverageOptions): string;
|
|
1842
|
+
};
|
|
1843
|
+
/**
|
|
1844
|
+
* Converts a {@link TestCoverage} to stable, versioned, JSON-safe data.
|
|
1845
|
+
*
|
|
1846
|
+
* @experimental
|
|
1847
|
+
*/
|
|
1848
|
+
declare function testCoverageToJSON(coverage: TestCoverage): TestCoverageJSON;
|
|
1849
|
+
/**
|
|
1850
|
+
* Formats a {@link TestCoverage} as JUnit XML, with one `<testcase>` per
|
|
1851
|
+
* transition and per state node.
|
|
1852
|
+
* @experimental
|
|
1853
|
+
*/
|
|
1854
|
+
declare const formatTestCoverageJUnit: {
|
|
1855
|
+
(options?: FormatTestCoverageJUnitOptions): (coverage: TestCoverage) => string;
|
|
1856
|
+
(coverage: TestCoverage, options?: FormatTestCoverageJUnitOptions): string;
|
|
1857
|
+
};
|
|
1858
|
+
/**
|
|
1859
|
+
* Formats a {@link TestCoverage} as a self-contained HTML document.
|
|
1860
|
+
*
|
|
1861
|
+
* @experimental
|
|
1862
|
+
*/
|
|
1863
|
+
declare const formatTestCoverageHTML: {
|
|
1864
|
+
(options?: FormatTestCoverageHTMLOptions): (coverage: TestCoverage) => string;
|
|
1865
|
+
(coverage: TestCoverage, options?: FormatTestCoverageHTMLOptions): string;
|
|
1866
|
+
};
|
|
1867
|
+
/**
|
|
1868
|
+
* Throws an `Error` containing the formatted coverage text when any dimension's
|
|
1869
|
+
* covered ratio — `covered / (covered + uncovered)` — is below its threshold.
|
|
1870
|
+
* Thresholds are ratios between `0` and `1`.
|
|
1871
|
+
* @experimental
|
|
1872
|
+
*/
|
|
1873
|
+
declare const assertTestCoverage: {
|
|
1874
|
+
(thresholds: TestCoverageThresholds): (coverage: TestCoverage) => void;
|
|
1875
|
+
(coverage: TestCoverage, thresholds: TestCoverageThresholds): void;
|
|
1876
|
+
};
|
|
1877
|
+
/**
|
|
1878
|
+
* Formats the distribution of generated data: how executed events split
|
|
1879
|
+
* across event cases, and the share of runs that recorded each label. Shrink
|
|
1880
|
+
* attempts are not counted. Pass `statistics: true` to `propertyTest()` or
|
|
1881
|
+
* `testPaths()` to print it after a passing campaign, or a function,
|
|
1882
|
+
* `(report) => void | Promise<void>`, to receive the same string instead.
|
|
1883
|
+
* @experimental
|
|
1884
|
+
*/
|
|
1885
|
+
declare function formatTestStatistics(coverage: TestCoverage): string;
|
|
1886
|
+
/** @experimental */
|
|
1887
|
+
interface TestSuite {
|
|
1888
|
+
readonly formatVersion: 1;
|
|
1889
|
+
readonly machineId?: string | undefined;
|
|
1890
|
+
readonly machineVersion?: string | undefined;
|
|
1891
|
+
/** ISO timestamp, only present when `generatedAt` was supplied. */
|
|
1892
|
+
readonly generatedAt?: string | undefined;
|
|
1893
|
+
readonly fixtures: readonly TestFixture[];
|
|
1894
|
+
readonly coverage: TestCoverageJSON;
|
|
1895
|
+
}
|
|
1896
|
+
/** @experimental */
|
|
1897
|
+
interface GenerateTestSuiteOptions<TSnapshot extends Snapshot<unknown>, TEvent extends EventObject, TInput, TKind extends PropertyGeneratorKind> extends PropertyTestOptions<TSnapshot, TEvent, TInput, TKind> {
|
|
1898
|
+
/**
|
|
1899
|
+
* `'minimal'` (the default) keeps the smallest greedy subset of recorded
|
|
1900
|
+
* traces that preserves the campaign's covered set. `'all'` keeps every
|
|
1901
|
+
* distinct trace.
|
|
1902
|
+
*/
|
|
1903
|
+
readonly select?: 'minimal' | 'all';
|
|
1904
|
+
/** Upper bound on the number of fixtures kept. */
|
|
1905
|
+
readonly maxFixtures?: number;
|
|
1906
|
+
/** Recorded verbatim as `generatedAt`. Omit to keep the suite byte-stable. */
|
|
1907
|
+
readonly generatedAt?: string;
|
|
1908
|
+
}
|
|
1909
|
+
/** @experimental */
|
|
1910
|
+
interface ReplayTestSuiteOptions<TSource extends ActorLogic<any, any, any>> {
|
|
1911
|
+
readonly invariant: TestInvariant<SnapshotFromSource<TSource>, EventFromSource<TSource>>;
|
|
1912
|
+
readonly temporal?: readonly TestTemporal<SnapshotFromSource<TSource>, EventFromSource<TSource>>[];
|
|
1913
|
+
readonly reference?: TestReference<SnapshotFromSource<TSource>, EventFromSource<TSource>>;
|
|
1914
|
+
readonly sut?: TestSut<SnapshotFromSource<TSource>, EventFromSource<TSource>>;
|
|
1915
|
+
readonly states?: TestStateAssertions<SnapshotFromSource<TSource>, EventFromSource<TSource>>;
|
|
1916
|
+
readonly restoreSnapshot?: (snapshot: unknown) => SnapshotFromSource<TSource>;
|
|
1917
|
+
}
|
|
1918
|
+
/** @experimental */
|
|
1919
|
+
interface TestSuiteReplayFailure {
|
|
1920
|
+
readonly fixture: TestFixture;
|
|
1921
|
+
readonly index: number;
|
|
1922
|
+
readonly title: string;
|
|
1923
|
+
readonly error: unknown;
|
|
1924
|
+
}
|
|
1925
|
+
/** @experimental */
|
|
1926
|
+
interface TestSuiteReplayResult {
|
|
1927
|
+
readonly passed: number;
|
|
1928
|
+
readonly failed: readonly TestSuiteReplayFailure[];
|
|
1929
|
+
}
|
|
1930
|
+
/**
|
|
1931
|
+
* Replays one suite fixture. Resolves when the fixture still passes and
|
|
1932
|
+
* rejects with the underlying failure when it does not.
|
|
1933
|
+
* @experimental
|
|
1934
|
+
*/
|
|
1935
|
+
declare const replayTestSuiteFixture: {
|
|
1936
|
+
<TSource extends ActorLogic<any, any, any>>(fixture: TestFixture, options: ReplayTestSuiteOptions<TSource>): (source: TSource) => Promise<void>;
|
|
1937
|
+
<TSource extends ActorLogic<any, any, any>>(source: TSource, fixture: TestFixture, options: ReplayTestSuiteOptions<TSource>): Promise<void>;
|
|
1938
|
+
};
|
|
1939
|
+
/**
|
|
1940
|
+
* Replays every fixture in a suite. Each fixture is expected to pass.
|
|
1941
|
+
*
|
|
1942
|
+
* @experimental
|
|
1943
|
+
*/
|
|
1944
|
+
declare const replayTestSuite: {
|
|
1945
|
+
<TSource extends ActorLogic<any, any, any>>(suite: TestSuite, options: ReplayTestSuiteOptions<TSource>): (source: TSource) => Promise<TestSuiteReplayResult>;
|
|
1946
|
+
<TSource extends ActorLogic<any, any, any>>(source: TSource, suite: TestSuite, options: ReplayTestSuiteOptions<TSource>): Promise<TestSuiteReplayResult>;
|
|
1947
|
+
};
|
|
1948
|
+
/** @experimental */
|
|
1949
|
+
interface DescribeTestSuiteOptions<TSource extends ActorLogic<any, any, any>> extends ReplayTestSuiteOptions<TSource> {
|
|
1950
|
+
/** Defaults to the ambient `it`. */
|
|
1951
|
+
readonly it?: (name: string, fn: () => Promise<void> | void) => unknown;
|
|
1952
|
+
/** Defaults to the ambient `describe`, when one exists. */
|
|
1953
|
+
readonly describe?: (name: string, fn: () => void) => unknown;
|
|
1954
|
+
/** The `describe` block name. Defaults to `property suite (<machine id>)`. */
|
|
1955
|
+
readonly name?: string;
|
|
1956
|
+
}
|
|
1957
|
+
/**
|
|
1958
|
+
* Registers one test per suite fixture with a vitest/jest-compatible
|
|
1959
|
+
* `it`/`describe` pair.
|
|
1960
|
+
* @experimental
|
|
1961
|
+
*/
|
|
1962
|
+
declare const describeTestSuite: {
|
|
1963
|
+
<TSource extends ActorLogic<any, any, any>>(source: TSource, options: DescribeTestSuiteOptions<TSource>): (suite: TestSuite) => void;
|
|
1964
|
+
<TSource extends ActorLogic<any, any, any>>(suite: TestSuite, source: TSource, options: DescribeTestSuiteOptions<TSource>): void;
|
|
1965
|
+
};
|
|
1966
|
+
/**
|
|
1967
|
+
* Serializes a suite with stable key ordering.
|
|
1968
|
+
*
|
|
1969
|
+
* @experimental
|
|
1970
|
+
*/
|
|
1971
|
+
declare function serializeTestSuite(suite: TestSuite): string;
|
|
1972
|
+
/**
|
|
1973
|
+
* Parses a serialized suite, rejecting unknown format versions.
|
|
1974
|
+
*
|
|
1975
|
+
* @experimental
|
|
1976
|
+
*/
|
|
1977
|
+
declare function parseTestSuite(json: string): TestSuite;
|
|
1978
|
+
/**
|
|
1979
|
+
* The result of executing one path.
|
|
1980
|
+
*
|
|
1981
|
+
* @experimental
|
|
1982
|
+
*/
|
|
1983
|
+
interface TestPathRunResult<TSnapshot extends Snapshot<unknown>, TEvent extends EventObject> {
|
|
1984
|
+
readonly path: StatePath<TSnapshot, TEvent>;
|
|
1985
|
+
readonly passed: boolean;
|
|
1986
|
+
readonly error?: unknown;
|
|
1987
|
+
}
|
|
1988
|
+
/**
|
|
1989
|
+
* Path-generation options. The counterpart of the property-only options.
|
|
1990
|
+
*
|
|
1991
|
+
* @experimental
|
|
1992
|
+
*/
|
|
1993
|
+
interface PathOptions<TSnapshot extends Snapshot<unknown>, TEvent extends EventObject, TInput> {
|
|
1994
|
+
/**
|
|
1995
|
+
* Paths to execute. Defaults to the shortest paths through the machine, or
|
|
1996
|
+
* to whatever `pathGenerator` produces.
|
|
1997
|
+
*/
|
|
1998
|
+
readonly paths?: readonly StatePath<TSnapshot, TEvent>[];
|
|
1999
|
+
/**
|
|
2000
|
+
* `'shortest'` (the default), `'simple'`, or a custom
|
|
2001
|
+
* {@link PathGenerator}. `'events'` is implied by `fromEvents`.
|
|
2002
|
+
*/
|
|
2003
|
+
readonly pathGenerator?: 'shortest' | 'simple' | PathGenerator<TSnapshot, TEvent, TInput>;
|
|
2004
|
+
/** Executes a single path built from this literal event sequence. */
|
|
2005
|
+
readonly fromEvents?: readonly TEvent[];
|
|
2006
|
+
/**
|
|
2007
|
+
* The most traversal steps path generation takes before it gives up and
|
|
2008
|
+
* throws. Defaults to `10_000`. A machine whose context grows without bound
|
|
2009
|
+
* reaches it, since every distinct context is a distinct state; merge
|
|
2010
|
+
* equivalent states with `serializeState`, or prune with `stopWhen`.
|
|
2011
|
+
*/
|
|
2012
|
+
readonly limit?: number;
|
|
2013
|
+
/** Keeps only the paths that end in a state matching this predicate. */
|
|
2014
|
+
readonly toState?: (snapshot: TSnapshot) => boolean;
|
|
2015
|
+
/** Starts traversal from this snapshot instead of the initial state. */
|
|
2016
|
+
readonly fromState?: TSnapshot;
|
|
2017
|
+
/** Stops expanding a state when this returns `true`. */
|
|
2018
|
+
readonly stopWhen?: (snapshot: TSnapshot) => boolean;
|
|
2019
|
+
/**
|
|
2020
|
+
* Concrete values sampled from each event case's `generate` before
|
|
2021
|
+
* traversal. Must be an integer of at least `1`. Defaults to `3`.
|
|
2022
|
+
*/
|
|
2023
|
+
readonly samples?: number;
|
|
2024
|
+
/**
|
|
2025
|
+
* Seed for the sampling PRNG. Defaults to `0`. Each event case draws from
|
|
2026
|
+
* its own stream, derived from `seed` and the case id, so adding or removing
|
|
2027
|
+
* a case leaves the other cases' payloads unchanged.
|
|
2028
|
+
*/
|
|
2029
|
+
readonly seed?: number;
|
|
2030
|
+
/** Identifies a traversal state. Two snapshots with the same string are one node. */
|
|
2031
|
+
readonly serializeState?: (snapshot: TSnapshot, event: TEvent | undefined, previousSnapshot?: TSnapshot) => string;
|
|
2032
|
+
/** Identifies a traversal event. */
|
|
2033
|
+
readonly serializeEvent?: (event: TEvent) => string;
|
|
2034
|
+
/**
|
|
2035
|
+
* Keeps paths that are prefixes of longer paths instead of dropping them.
|
|
2036
|
+
*
|
|
2037
|
+
* @default false
|
|
2038
|
+
*/
|
|
2039
|
+
readonly allowDuplicatePaths?: boolean;
|
|
2040
|
+
}
|
|
2041
|
+
/**
|
|
2042
|
+
* The generator kind path generation understands natively: a plain
|
|
2043
|
+
* `(rng) => value` function, or an object with a `sample(rng)` method.
|
|
2044
|
+
* `@xstate/test` adapts fast-check arbitraries into this shape before calling
|
|
2045
|
+
* `testPaths()`, so `xstate/graph` never depends on fast-check.
|
|
2046
|
+
* @experimental
|
|
2047
|
+
*/
|
|
2048
|
+
interface SeededGeneratorKind extends PropertyGeneratorKind {
|
|
2049
|
+
readonly generator: TestGenerator<this['target']>;
|
|
2050
|
+
}
|
|
2051
|
+
/** @experimental */
|
|
2052
|
+
type TestPathsOptions<TSnapshot extends Snapshot<unknown>, TEvent extends EventObject, TInput, TKind extends PropertyGeneratorKind = SeededGeneratorKind> = TestOptions<TSnapshot, TEvent, TInput, TKind> & PathOptions<TSnapshot, TEvent, TInput>;
|
|
2053
|
+
/**
|
|
2054
|
+
* What `testPaths()` resolves with.
|
|
2055
|
+
*
|
|
2056
|
+
* @experimental
|
|
2057
|
+
*/
|
|
2058
|
+
interface TestPathsResult<TSnapshot extends Snapshot<unknown>, TEvent extends EventObject> {
|
|
2059
|
+
readonly coverage: TestCoverage;
|
|
2060
|
+
readonly results: readonly TestPathRunResult<TSnapshot, TEvent>[];
|
|
2061
|
+
}
|
|
2062
|
+
export { TestReference as $, ParallelPropertyCommandsResult as A, TestActorOutcome as B, formatTestStatistics as C, LinearizabilityOptions as D, LinearizabilityModel as E, PropertyGeneratorKind as F, TestEventGenerators as G, TestCampaignError as H, PropertyScenarioRunner as I, TestFailureStore as J, TestFailureExtras as K, PropertyTargetObservation as L, runParallelPropertyCommands as M, ModelTestFailure as N, LinearizabilityResult as O, PropertyExecutionConfig as P, TestMode as Q, PropertyTestOptions as R, formatTestCoverageJUnit as S, LinearizabilityEntry as T, TestCommand as U, TestAdapter as V, TestEventDescriptor as W, TestInvariant as X, TestFixture as Y, TestInvariantContext as Z, TestCoverageJSON as _, TestCoverage as _t, GenerateTestSuiteOptions as a, TestStopCondition as at, formatTestCoverage as b, AnyTestEventDescriptor as bt, TestSuiteReplayResult as c, TestSutSendContext as ct, replayTestSuite as d, TestTrace as dt, TestReferenceSession as et, replayTestSuiteFixture as f, replayTest as ft, FormatTestCoverageOptions as g, SnapshotFromSource as gt, FormatTestCoverageJUnitOptions as h, InputFromSource as ht, DescribeTestSuiteOptions as i, TestStep as it, checkLinearizable as j, ParallelPropertyCommandsOptions as k, describeTestSuite as l, TestSutSession as lt, FormatTestCoverageHTMLOptions as m, EventFromSource as mt, TestPathsOptions as n, TestStateAssertion as nt, ReplayTestSuiteOptions as o, TestSut as ot, serializeTestSuite as p, serializeTestTrace as pt, TestFailureFormatOptions as q, TestPathsResult as r, TestStateAssertions as rt, TestSuite as s, TestSutContext as st, TestPathRunResult as t, TestReplayMetadata as tt, parseTestSuite as u, TestTemporal as ut, TestCoverageThresholds as v, TestCoverageDimension as vt, testCoverageToJSON as w, formatTestCoverageHTML as x, TestPickDescriptor as xt, assertTestCoverage as y, TestExplorationBounds as yt, ReplayNotReproducedError as z };
|