@systemfsoftware/vitest 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/dist/engine-ZbxyPNiN.d.ts +166 -0
- package/dist/failure-DC_SJoxW.mjs +1293 -0
- package/dist/failure.d.ts +204 -0
- package/dist/failure.mjs +4 -0
- package/dist/index.d.ts +3 -154
- package/dist/index.mjs +56 -529
- package/dist/integration.mjs +1 -1
- package/dist/{test-context-hvSoKLtw.mjs → test-context-D_-acRSc.mjs} +122 -5
- package/package.json +17 -4
package/dist/index.mjs
CHANGED
|
@@ -1,31 +1,23 @@
|
|
|
1
1
|
|
|
2
|
+
import { a as replayOfFailure, d as providedRoot, f as InvalidBudget, h as Slop, i as makeProperty, l as testIdentityOf, m as NonBooleanVerdict, o as isFailureRecordError, p as LeakedState, r as createSpanRecorder, u as throwFailureRecord } from "./failure-DC_SJoxW.mjs";
|
|
3
|
+
import { a as isCheck, i as checksFor, n as vitestTestContextKey$1, o as makeLedger, r as Asserted, t as VitestTestContext$1 } from "./test-context-D_-acRSc.mjs";
|
|
2
4
|
import { _ as refuseScopedLiveLane, d as refuseAsyncBody, f as refuseEffectLane, g as refuseScopedLane, i as markTask, l as noCheckRefusal, o as isRefusal$1, p as refuseHook, u as refusalOf$1, v as refuseSyncBody, x as unprovidedText, y as refuseUnyielded } from "./guard-BocOhXt2.mjs";
|
|
3
|
-
import { a as isCheck, i as checksFor, n as vitestTestContextKey$1, o as makeLedger, r as Asserted, t as VitestTestContext$1 } from "./test-context-hvSoKLtw.mjs";
|
|
4
5
|
import * as V from "vitest";
|
|
5
|
-
import { afterAll, assertType, beforeAll, expectTypeOf, inject,
|
|
6
|
+
import { afterAll, assertType, beforeAll, expectTypeOf, inject, onTestFailed, onTestFinished, recordArtifact, vi, vitest } from "vitest";
|
|
6
7
|
import * as Cause from "effect/Cause";
|
|
7
8
|
import * as Duration from "effect/Duration";
|
|
8
9
|
import * as Effect from "effect/Effect";
|
|
10
|
+
import * as Equal from "effect/Equal";
|
|
9
11
|
import * as Exit from "effect/Exit";
|
|
10
12
|
import * as Function from "effect/Function";
|
|
11
13
|
import * as Layer from "effect/Layer";
|
|
12
14
|
import * as Schedule from "effect/Schedule";
|
|
13
|
-
import * as Schema
|
|
15
|
+
import * as Schema from "effect/Schema";
|
|
14
16
|
import * as Scope from "effect/Scope";
|
|
15
17
|
import * as TestClock from "effect/testing/TestClock";
|
|
16
18
|
import * as TestConsole from "effect/testing/TestConsole";
|
|
17
|
-
import * as Equal from "effect/Equal";
|
|
18
|
-
import { Schema } from "effect";
|
|
19
|
-
import * as Arbitrary from "effect/unstable/arbitrary/Arbitrary";
|
|
20
|
-
import * as Data from "effect/Data";
|
|
21
19
|
import * as Clock from "effect/Clock";
|
|
22
20
|
import * as Scheduler from "effect/Scheduler";
|
|
23
|
-
//#region src/internal/property/defaults.ts
|
|
24
|
-
/** @internal The `test.provide` key the shared config fills with this run's tier's check options. */
|
|
25
|
-
const checkDefaultsKey = "@systemfsoftware/vitest:property-check";
|
|
26
|
-
/** @internal The configured property-check defaults, or `undefined` when the run provided none. */
|
|
27
|
-
const providedCheckDefaults = () => inject$1(checkDefaultsKey);
|
|
28
|
-
//#endregion
|
|
29
21
|
//#region src/internal/driver.ts
|
|
30
22
|
/**
|
|
31
23
|
* The driver: one Effect that steps the body's generator in the fiber the test runs in.
|
|
@@ -92,509 +84,6 @@ const driven = (body, args, ledger) => Effect.provideService(driveBody(body, arg
|
|
|
92
84
|
*/
|
|
93
85
|
const drive = Function.dual(3, driven);
|
|
94
86
|
//#endregion
|
|
95
|
-
//#region src/internal/equal.ts
|
|
96
|
-
let registered = false;
|
|
97
|
-
/** @internal */
|
|
98
|
-
const registerEqualTester = () => {
|
|
99
|
-
if (registered) return;
|
|
100
|
-
registered = true;
|
|
101
|
-
V.expect.addEqualityTesters([equalTester]);
|
|
102
|
-
};
|
|
103
|
-
const equalTester = (first, second) => equalTogether(first, second) ? true : void 0;
|
|
104
|
-
const equalTogether = (first, second) => bothEqualable(first, second) && Equal.equals(first, second);
|
|
105
|
-
const bothEqualable = (first, second) => Equal.isEqual(first) && Equal.isEqual(second);
|
|
106
|
-
//#endregion
|
|
107
|
-
//#region src/internal/errors.schema.ts
|
|
108
|
-
/** @internal */
|
|
109
|
-
var NonBooleanVerdict = class extends Schema.TaggedError()("NonBooleanVerdict", { detail: Schema.String }) {
|
|
110
|
-
get message() {
|
|
111
|
-
return this.detail;
|
|
112
|
-
}
|
|
113
|
-
};
|
|
114
|
-
/** @internal */
|
|
115
|
-
var LeakedState = class extends Schema.TaggedError()("LeakedState", { detail: Schema.String }) {
|
|
116
|
-
get message() {
|
|
117
|
-
return this.detail;
|
|
118
|
-
}
|
|
119
|
-
};
|
|
120
|
-
/** @internal */
|
|
121
|
-
var Slop = class extends Schema.TaggedError()("Slop", { detail: Schema.String }) {
|
|
122
|
-
get message() {
|
|
123
|
-
return this.detail;
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
/** @internal */
|
|
127
|
-
var InvalidBudget = class extends Schema.TaggedError()("InvalidBudget", { detail: Schema.String }) {
|
|
128
|
-
get message() {
|
|
129
|
-
return this.detail;
|
|
130
|
-
}
|
|
131
|
-
};
|
|
132
|
-
//#endregion
|
|
133
|
-
//#region src/internal/property/coverage.ts
|
|
134
|
-
/**
|
|
135
|
-
* @internal The R14 coverage classes: labelled input predicates, each with a minimum share of runs.
|
|
136
|
-
*
|
|
137
|
-
* A class fails only when QuickCheck's sequential test (`stdConfidence`) is confident its share is below
|
|
138
|
-
* the minimum, and is satisfied once the test is confident the share is at least 0.9 of the minimum
|
|
139
|
-
* (`TOLERANCE` at a certainty of 10^9). The tolerance is what makes the test terminate: a class sitting
|
|
140
|
-
* exactly at its minimum would otherwise draw forever.
|
|
141
|
-
*/
|
|
142
|
-
const CERTAINTY = 10 ** 9;
|
|
143
|
-
const TOLERANCE = .9;
|
|
144
|
-
const FIRST_BATCH = 64;
|
|
145
|
-
const BATCH_CAP = 4096;
|
|
146
|
-
const DRAW_CAP = 1e5;
|
|
147
|
-
const FAILURE_HEAD = "coverage";
|
|
148
|
-
/** The certainty's two-sided tail probability: `alpha / 2` for `alpha = 1 / CERTAINTY`. */
|
|
149
|
-
const TAIL_PROBABILITY = 1 / (2 * CERTAINTY);
|
|
150
|
-
const C1 = -.007784894002430293;
|
|
151
|
-
const C2 = -.3223964580411365;
|
|
152
|
-
const C3 = -2.400758277161838;
|
|
153
|
-
const C4 = -2.549732539343734;
|
|
154
|
-
const C5 = 4.374664141464968;
|
|
155
|
-
const C6 = 2.938163982698783;
|
|
156
|
-
const D1 = .007784695709041462;
|
|
157
|
-
const D2 = .3224671290700398;
|
|
158
|
-
const D3 = 2.445134137142996;
|
|
159
|
-
const D4 = 3.754408661907416;
|
|
160
|
-
const TAIL_NUMERATOR = [
|
|
161
|
-
C1,
|
|
162
|
-
C2,
|
|
163
|
-
C3,
|
|
164
|
-
C4,
|
|
165
|
-
C5,
|
|
166
|
-
C6
|
|
167
|
-
];
|
|
168
|
-
const TAIL_DENOMINATOR = [
|
|
169
|
-
D1,
|
|
170
|
-
D2,
|
|
171
|
-
D3,
|
|
172
|
-
D4
|
|
173
|
-
];
|
|
174
|
-
/** Horner's rule over a coefficient list, highest power first. */
|
|
175
|
-
const poly = (coefficients, x) => coefficients.reduceRight((total, coefficient) => total * x + coefficient, 0);
|
|
176
|
-
const tailSpread = (p) => Math.sqrt(-2 * Math.log(p));
|
|
177
|
-
const tailInverse = (spread) => poly(TAIL_NUMERATOR, spread) / poly(TAIL_DENOMINATOR, spread);
|
|
178
|
-
/** The inverse standard-normal CDF at the tail probability the certainty asks for. */
|
|
179
|
-
const TAIL_Z = tailInverse(tailSpread(TAIL_PROBABILITY));
|
|
180
|
-
const wilson = (hits, runs, z) => {
|
|
181
|
-
const share = hits / runs;
|
|
182
|
-
const zz = z * z;
|
|
183
|
-
return (share + zz / (2 * runs) + z * Math.sqrt(share * (1 - share) / runs + zz / (4 * runs * runs))) / (1 + zz / runs);
|
|
184
|
-
};
|
|
185
|
-
/**
|
|
186
|
-
* Thrown when a coverage class is confidently below its minimum share of runs.
|
|
187
|
-
*
|
|
188
|
-
* @internal
|
|
189
|
-
*/
|
|
190
|
-
var CoverageBelowMinimum = class extends Data.Error {};
|
|
191
|
-
const wilsonLower = (entry, runs) => wilson(entry.hits, runs, -TAIL_Z);
|
|
192
|
-
const wilsonUpper = (entry, runs) => wilson(entry.hits, runs, TAIL_Z);
|
|
193
|
-
const covered = (entry, runs) => wilsonLower(entry, runs) >= TOLERANCE * entry.minimum;
|
|
194
|
-
const uncovered = (entry, runs) => wilsonUpper(entry, runs) < entry.minimum;
|
|
195
|
-
const decided = (entry, runs) => covered(entry, runs) || uncovered(entry, runs);
|
|
196
|
-
const settled = (entry, runs) => runs === 0 ? false : decided(entry, runs);
|
|
197
|
-
const entriesOf = (classes) => Object.keys(classes).map((label) => entryAt(classes, label));
|
|
198
|
-
const entryAt = (classes, label) => [label, classOf(classes, label)];
|
|
199
|
-
const classOf = (classes, label) => classes[label] ?? {
|
|
200
|
-
hits: 0,
|
|
201
|
-
minimum: 0
|
|
202
|
-
};
|
|
203
|
-
const judgeOf = (classes, runs) => ({
|
|
204
|
-
classes: new Map(entriesOf(classes)),
|
|
205
|
-
runs
|
|
206
|
-
});
|
|
207
|
-
const openLabels = (judge) => [...judge.classes].filter(([, entry]) => !settled(entry, judge.runs)).map(([label]) => label);
|
|
208
|
-
const failedEntries = (judge) => [...judge.classes].filter(([, entry]) => uncovered(entry, judge.runs));
|
|
209
|
-
const describeShare = (hits, runs) => `${String(hits)}/${String(runs)}`;
|
|
210
|
-
const describeFailure = (label, entry, runs) => `${FAILURE_HEAD} ${label}: ${describeShare(entry.hits, runs)} of runs, below the required ${String(entry.minimum)}`;
|
|
211
|
-
const reportFailures = (judge) => failedEntries(judge).map(([label, entry]) => describeFailure(label, entry, judge.runs)).join("\n");
|
|
212
|
-
/** @internal */
|
|
213
|
-
const countHit = Function.dual(2, (classes, label) => {
|
|
214
|
-
const prior = classes.get(label);
|
|
215
|
-
if (prior !== void 0) classes.set(label, {
|
|
216
|
-
hits: prior.hits + 1,
|
|
217
|
-
minimum: prior.minimum
|
|
218
|
-
});
|
|
219
|
-
});
|
|
220
|
-
const countLabels = (classes, labels) => {
|
|
221
|
-
for (const label of labels) countHit(classes, label);
|
|
222
|
-
return classes;
|
|
223
|
-
};
|
|
224
|
-
const countDraws = (classes, drawn) => drawn.reduce(countLabels, new Map(classes));
|
|
225
|
-
const withDraws = (judge, drawn) => ({
|
|
226
|
-
classes: countDraws(judge.classes, drawn),
|
|
227
|
-
runs: judge.runs + drawn.length
|
|
228
|
-
});
|
|
229
|
-
const drawBatch = (judge, draw, batch) => withDraws(judge, draw.more(sizeOf(judge, batch)));
|
|
230
|
-
const sizeOf = (judge, batch) => Math.min(batch, DRAW_CAP - judge.runs);
|
|
231
|
-
const grown = (batch) => Math.min(batch * 2, BATCH_CAP);
|
|
232
|
-
const keepDrawing = (judge) => openLabels(judge).length > 0 && judge.runs < DRAW_CAP;
|
|
233
|
-
const toppedUp = (judge, draw, batch) => keepDrawing(judge) ? toppedUp(drawBatch(judge, draw, batch), draw, grown(batch)) : judge;
|
|
234
|
-
const reportOf$1 = (judge) => failedEntries(judge).length === 0 ? void 0 : reportFailures(judge);
|
|
235
|
-
/**
|
|
236
|
-
* Runs R14's sequential draw: tops up the undecided classes until every class is decided or the draw cap
|
|
237
|
-
* is reached, then reports the confidently under-covered classes, naming each label and its observed share.
|
|
238
|
-
*
|
|
239
|
-
* @internal
|
|
240
|
-
*/
|
|
241
|
-
const judgeCoverage = (options) => reportOf$1(toppedUp(judgeOf(options.classes, options.runs), options.draw, FIRST_BATCH));
|
|
242
|
-
//#endregion
|
|
243
|
-
//#region src/internal/property/error.schema.ts
|
|
244
|
-
/**
|
|
245
|
-
* Thrown at file end when no property in the file refuted a subject's constant impostor (R12).
|
|
246
|
-
*
|
|
247
|
-
* @internal
|
|
248
|
-
*/
|
|
249
|
-
var VacuousProperty = class extends Schema$1.TaggedError()("VacuousProperty", { detail: Schema$1.String }) {
|
|
250
|
-
get message() {
|
|
251
|
-
return this.detail;
|
|
252
|
-
}
|
|
253
|
-
};
|
|
254
|
-
/**
|
|
255
|
-
* Thrown when a law kind is given a record `of`, which cannot be spread into its subject.
|
|
256
|
-
*
|
|
257
|
-
* @internal
|
|
258
|
-
*/
|
|
259
|
-
var NonTupleOf = class extends Schema$1.TaggedError()("NonTupleOf", { detail: Schema$1.String }) {
|
|
260
|
-
get message() {
|
|
261
|
-
return this.detail;
|
|
262
|
-
}
|
|
263
|
-
};
|
|
264
|
-
//#endregion
|
|
265
|
-
//#region src/internal/property/impostor.ts
|
|
266
|
-
const SUBJECT_KEY = "the subject";
|
|
267
|
-
const NO_VALUE = "";
|
|
268
|
-
const isCallable = (value) => typeof value === "function";
|
|
269
|
-
const isCallableSubject = (target) => typeof target === "function";
|
|
270
|
-
const describeValue = (value) => isCallable(value) ? "a function" : String(value);
|
|
271
|
-
const describeEntry = (key, value) => `${key} always returned ${describeValue(value)}`;
|
|
272
|
-
const emptyState = () => ({ outputs: /* @__PURE__ */ new Map() });
|
|
273
|
-
const frozenAt = (state, key, compute) => state.outputs.has(key) ? state.outputs.get(key) : remember(state, key, compute());
|
|
274
|
-
const remember = (state, key, value) => {
|
|
275
|
-
state.outputs.set(key, value);
|
|
276
|
-
return value;
|
|
277
|
-
};
|
|
278
|
-
const callOutput = (state, fn, thisArg, args) => frozenAt(state, SUBJECT_KEY, () => Reflect.apply(fn, thisArg, args));
|
|
279
|
-
const memberWrapper = (state, key, fn) => (...values) => frozenAt(state, key, () => Reflect.apply(fn, void 0, values));
|
|
280
|
-
const memberWrapperOf = (state, fn, key) => fn === void 0 ? void 0 : memberWrapper(state, key, fn);
|
|
281
|
-
const appliedImpostor = (state, target, thisArg, args) => isCallableSubject(target) ? callOutput(state, target, thisArg, args) : void 0;
|
|
282
|
-
const memberImpostor = (state, target, key) => memberWrapperOf(state, target[key], key);
|
|
283
|
-
const readImpostor = (state, target, property) => isCallableSubject(target) ? Reflect.get(target, property, target) : memberRead(state, target, property);
|
|
284
|
-
const memberRead = (state, target, property) => typeof property === "string" ? memberImpostor(state, target, property) : void 0;
|
|
285
|
-
const impostorHandler = (state) => ({
|
|
286
|
-
apply: (target, thisArg, args) => appliedImpostor(state, target, thisArg, args),
|
|
287
|
-
get: (target, property) => readImpostor(state, target, property)
|
|
288
|
-
});
|
|
289
|
-
const describeFrozen = (state) => [...state.outputs].map(([key, value]) => describeEntry(key, value)).join("; ");
|
|
290
|
-
/**
|
|
291
|
-
* A constant impostor of `subject`: a function returns its first output forever, and so does each member
|
|
292
|
-
* of a record.
|
|
293
|
-
*
|
|
294
|
-
* @internal
|
|
295
|
-
*/
|
|
296
|
-
const impostorOf = (subject) => {
|
|
297
|
-
const state = emptyState();
|
|
298
|
-
return {
|
|
299
|
-
impostor: new Proxy(subject, impostorHandler(state)),
|
|
300
|
-
frozen: () => describeFrozen(state)
|
|
301
|
-
};
|
|
302
|
-
};
|
|
303
|
-
/** @internal */
|
|
304
|
-
const subjectLabel = (subject) => isCallableSubject(subject) ? `a function of ${String(subject.length)} argument(s)` : `a record of ${Object.keys(subject).sort().join(", ")}`;
|
|
305
|
-
const judgeFor = (subject) => ({
|
|
306
|
-
label: subjectLabel(subject),
|
|
307
|
-
properties: [],
|
|
308
|
-
refuters: [],
|
|
309
|
-
frozen: NO_VALUE,
|
|
310
|
-
runs: 0
|
|
311
|
-
});
|
|
312
|
-
const trackProperty = (judge, property) => judge.properties.includes(property) ? judge : {
|
|
313
|
-
...judge,
|
|
314
|
-
properties: [...judge.properties, property]
|
|
315
|
-
};
|
|
316
|
-
const trackRefuter = (judge, property) => judge.refuters.includes(property) ? judge : {
|
|
317
|
-
...judge,
|
|
318
|
-
refuters: [...judge.refuters, property]
|
|
319
|
-
};
|
|
320
|
-
const trackVerdict = (judge, property, verdict) => {
|
|
321
|
-
const named = trackProperty(judge, property);
|
|
322
|
-
return verdict.refuted ? trackRefuter(named, property) : {
|
|
323
|
-
...named,
|
|
324
|
-
frozen: verdict.frozen,
|
|
325
|
-
runs: verdict.runs
|
|
326
|
-
};
|
|
327
|
-
};
|
|
328
|
-
const recordJudge = (state, subject, property, verdict) => {
|
|
329
|
-
const prior = state.judges.get(subject) ?? judgeFor(subject);
|
|
330
|
-
state.judges.set(subject, trackVerdict(prior, property, verdict));
|
|
331
|
-
};
|
|
332
|
-
const isVacuous = (judge) => judge.refuters.length === 0;
|
|
333
|
-
const describeVacuous = (judge) => `${judge.properties.join(", ")}: no property in this file refuted the constant impostor of this subject (${judge.label}), so nothing here pins it down. It held for ${judge.runs} run(s) against a fake that ${judge.frozen === NO_VALUE ? "was never called" : judge.frozen}, whatever the input. Laws that only relate outputs to each other (additivity, idempotence, commutativity, round trips through the subject) hold for such constants. Pin the output to the input: compare against an independent model (\`subject(x)\` equals a straightforward reimplementation), or conjoin a base case (\`subject([one])\` equals its known value). Also check that the body calls the \`subject\` it was given, not the imported implementation.`;
|
|
334
|
-
const finaliseJudges = (state, onProblems) => {
|
|
335
|
-
const vacuous = [...state.judges.values()].filter(isVacuous).map(describeVacuous);
|
|
336
|
-
if (vacuous.length === 0) return;
|
|
337
|
-
throw onProblems(vacuous.join("\n\n"));
|
|
338
|
-
};
|
|
339
|
-
const ledgerOf = (state, onProblems) => ({
|
|
340
|
-
record: (subject, property, verdict) => recordJudge(state, subject, property, verdict),
|
|
341
|
-
finalise: () => finaliseJudges(state, onProblems)
|
|
342
|
-
});
|
|
343
|
-
/**
|
|
344
|
-
* Collects the impostor verdicts of every property in the file and decides, at file end, which subjects
|
|
345
|
-
* nothing refuted.
|
|
346
|
-
*
|
|
347
|
-
* @internal
|
|
348
|
-
*/
|
|
349
|
-
const makeFileLedger = (onProblems) => ledgerOf({ judges: /* @__PURE__ */ new Map() }, onProblems);
|
|
350
|
-
//#endregion
|
|
351
|
-
//#region src/internal/property/kinds.ts
|
|
352
|
-
/**
|
|
353
|
-
* @internal The R13 law kinds: `model`, `metamorphic`, `roundTrip` and `invariant` are sugar over the
|
|
354
|
-
* gated property (R11/R12); `idempotent` and `deterministic` are declared exempt — they skip the impostor
|
|
355
|
-
* gate, are reported as exempt, and are the only exemptions.
|
|
356
|
-
*
|
|
357
|
-
* Each kind owns the comparison, so there is nowhere to write `return true`: the author hands over
|
|
358
|
-
* functions, and the kind relates their outputs with Effect's structural `Equal`.
|
|
359
|
-
*/
|
|
360
|
-
/**
|
|
361
|
-
* The generated values as the argument list a law subject is called with.
|
|
362
|
-
*
|
|
363
|
-
* @internal
|
|
364
|
-
*/
|
|
365
|
-
const spreadValues = (values) => Array.isArray(values) ? values : refuseRecordOf();
|
|
366
|
-
const refuseRecordOf = () => {
|
|
367
|
-
throw new NonTupleOf({ detail: "a law kind spreads the generated values into its subject, so `of` must be a tuple; a record `of` holds functions of the record, which only it.prop reaches" });
|
|
368
|
-
};
|
|
369
|
-
const call = (subject, values) => subject(...spreadValues(values));
|
|
370
|
-
const equalOutputs = (original, transformed) => Equal.equals(original, transformed);
|
|
371
|
-
/** @internal */
|
|
372
|
-
const modelHolds = (oracle) => (subject, values) => Equal.equals(call(subject, values), call(oracle, values));
|
|
373
|
-
/** @internal */
|
|
374
|
-
const metamorphicHolds = (relation) => (subject, values) => (relation.relate ?? equalOutputs)(call(subject, values), call(subject, relation.transform(...spreadValues(values))));
|
|
375
|
-
/** @internal */
|
|
376
|
-
const roundTripHolds = (decode) => (subject, values) => Equal.equals(decode(call(subject, values)), values);
|
|
377
|
-
/** @internal */
|
|
378
|
-
const invariantHolds = (holds) => (subject, values) => holds(call(subject, values), ...spreadValues(values));
|
|
379
|
-
const firstOf = (values) => values[0];
|
|
380
|
-
const sameTwice = (subject, value) => value === void 0 ? false : Equal.equals(subject(subject(value)), subject(value));
|
|
381
|
-
/** @internal */
|
|
382
|
-
const idempotentHolds = () => (subject, values) => sameTwice(subject, firstOf(spreadValues(values)));
|
|
383
|
-
/** @internal */
|
|
384
|
-
const deterministicHolds = () => (subject, values) => Equal.equals(call(subject, values), call(subject, values));
|
|
385
|
-
//#endregion
|
|
386
|
-
//#region src/internal/property/engine.ts
|
|
387
|
-
/**
|
|
388
|
-
* @internal The lawful property engine (R11-R15): a property names the function under test, takes a budget
|
|
389
|
-
* that merges its own fields over the configured default over `runs: 100`, accepts only a literal boolean
|
|
390
|
-
* verdict (or an `Effect` of one), judges coverage classes with QuickCheck's sequential test, and defers the
|
|
391
|
-
* constant-impostor verdict to the end of the file.
|
|
392
|
-
*
|
|
393
|
-
* `of` accepts what upstream accepts — a tuple or record of Schemas or Arbitraries (KTD10) — and `holds`
|
|
394
|
-
* receives the generated values exactly as drawn, typed by the gens that produced them.
|
|
395
|
-
*/
|
|
396
|
-
const REWRITE = "it.prop(name, { of, subject, runs }, holds)";
|
|
397
|
-
const DEFAULT_RUNS = 100;
|
|
398
|
-
const isPositiveInteger = (runs) => Number.isInteger(runs) && runs > 0;
|
|
399
|
-
const describeRuns = (runs) => Number.isInteger(runs) ? `${String(runs)}` : "a non-integer";
|
|
400
|
-
const invalidPropertyBudget = (name, runs) => `${name}: pass a positive integer \`runs\`; received ${describeRuns(runs)}. Write ${REWRITE}.`;
|
|
401
|
-
const invalidProvidedBudget = (runs) => `the configured property budget (${checkDefaultsKey}) supplies \`runs\`: ${describeRuns(runs)}; it must be a positive integer.`;
|
|
402
|
-
const requirePositiveRuns = (message, runs) => {
|
|
403
|
-
if (isInvalidRuns(runs)) throw new InvalidBudget({ detail: message(runs) });
|
|
404
|
-
};
|
|
405
|
-
const isInvalidRuns = (runs) => runs !== void 0 && isPositiveInteger(runs) === false;
|
|
406
|
-
/** The property's own explicit fields, a property-level `runs` outranking the same field in `arbitrary`. */
|
|
407
|
-
const explicitOptions = (spec) => spec.runs === void 0 ? { ...spec.arbitrary } : {
|
|
408
|
-
...spec.arbitrary,
|
|
409
|
-
runs: spec.runs
|
|
410
|
-
};
|
|
411
|
-
const mergedOptions = (provided, explicit) => ({
|
|
412
|
-
runs: DEFAULT_RUNS,
|
|
413
|
-
...provided,
|
|
414
|
-
...explicit
|
|
415
|
-
});
|
|
416
|
-
const resolvedRuns = (options) => options.runs ?? DEFAULT_RUNS;
|
|
417
|
-
/**
|
|
418
|
-
* The effective check options: the property's own fields over the configured default over `runs: 100`,
|
|
419
|
-
* merged field by field so a property that sets only `runs` still inherits the configured size and caps.
|
|
420
|
-
*/
|
|
421
|
-
const resolveBudget = (name, spec, provided) => {
|
|
422
|
-
const explicit = explicitOptions(spec);
|
|
423
|
-
requirePositiveRuns((runs) => invalidPropertyBudget(name, runs), explicit.runs);
|
|
424
|
-
requirePositiveRuns(invalidProvidedBudget, provided?.runs);
|
|
425
|
-
const options = mergedOptions(provided, explicit);
|
|
426
|
-
return {
|
|
427
|
-
runs: resolvedRuns(options),
|
|
428
|
-
options
|
|
429
|
-
};
|
|
430
|
-
};
|
|
431
|
-
const toArbitrary = (input) => Schema$1.isSchema(input) ? Arbitrary.schema(input) : input;
|
|
432
|
-
const assignArbitrary = (built, of, key) => {
|
|
433
|
-
const input = of[key];
|
|
434
|
-
if (input !== void 0) built[key] = toArbitrary(input);
|
|
435
|
-
};
|
|
436
|
-
const recordArbitraries = (of) => {
|
|
437
|
-
const built = {};
|
|
438
|
-
for (const key of Object.keys(of)) assignArbitrary(built, of, key);
|
|
439
|
-
return built;
|
|
440
|
-
};
|
|
441
|
-
const isTupleOf = (of) => Array.isArray(of);
|
|
442
|
-
function arbitraryOf(of) {
|
|
443
|
-
return isTupleOf(of) ? Arbitrary.all(of.map(toArbitrary)) : Arbitrary.all(recordArbitraries(of));
|
|
444
|
-
}
|
|
445
|
-
const minimumOf = (entry) => entry[1];
|
|
446
|
-
const seedClasses = (entries) => {
|
|
447
|
-
const classes = /* @__PURE__ */ new Map();
|
|
448
|
-
for (const [label, entry] of entries) classes.set(label, {
|
|
449
|
-
hits: 0,
|
|
450
|
-
minimum: minimumOf(entry)
|
|
451
|
-
});
|
|
452
|
-
return classes;
|
|
453
|
-
};
|
|
454
|
-
const satisfiedLabels = (entries, values) => entries.filter(([, entry]) => entry[0](...spreadValues(values))).map(([label]) => label);
|
|
455
|
-
const observeRun = (classes, counter, entries, values) => {
|
|
456
|
-
counter.runs = counter.runs + 1;
|
|
457
|
-
for (const label of satisfiedLabels(entries, values)) countHit(classes, label);
|
|
458
|
-
};
|
|
459
|
-
const drawFor = (entries, arbitrary) => ({ more: (count) => Effect.runSync(Arbitrary.sampleEffect(arbitrary, { count }).pipe(Effect.orDie)).map((values) => satisfiedLabels(entries, values)) });
|
|
460
|
-
const noCoverage = () => ({
|
|
461
|
-
observe: () => void 0,
|
|
462
|
-
judge: () => void 0
|
|
463
|
-
});
|
|
464
|
-
const liveCoverage = (cover, arbitrary) => {
|
|
465
|
-
const entries = Object.entries(cover);
|
|
466
|
-
const classes = seedClasses(entries);
|
|
467
|
-
const counter = { runs: 0 };
|
|
468
|
-
return {
|
|
469
|
-
observe: (values) => observeRun(classes, counter, entries, values),
|
|
470
|
-
judge: () => judgeCoverage({
|
|
471
|
-
classes: Object.fromEntries(classes),
|
|
472
|
-
runs: counter.runs,
|
|
473
|
-
draw: drawFor(entries, arbitrary)
|
|
474
|
-
})
|
|
475
|
-
};
|
|
476
|
-
};
|
|
477
|
-
const makeCoverage = (cover, arbitrary) => cover === void 0 ? noCoverage() : liveCoverage(cover, arbitrary);
|
|
478
|
-
const describeVerdict = (verdict) => Effect.isEffect(verdict) ? "an Effect, which this lane never runs" : `a ${typeof verdict}`;
|
|
479
|
-
const nonBoolean = (verdict, violations) => {
|
|
480
|
-
violations.add(describeVerdict(verdict));
|
|
481
|
-
return true;
|
|
482
|
-
};
|
|
483
|
-
const literalVerdict = (verdict, violations) => typeof verdict === "boolean" ? verdict : nonBoolean(verdict, violations);
|
|
484
|
-
const effectVerdict = (lane, verdict, violations) => lane === "sync" ? Effect.succeed(nonBoolean(verdict, violations)) : Effect.map(verdict, (value) => literalVerdict(value, violations));
|
|
485
|
-
const verdictFor = (run, values, violations) => {
|
|
486
|
-
run.observe(values);
|
|
487
|
-
const verdict = run.holds(run.subject, values);
|
|
488
|
-
return Effect.isEffect(verdict) ? effectVerdict(run.lane, verdict, violations) : Effect.succeed(literalVerdict(verdict, violations));
|
|
489
|
-
};
|
|
490
|
-
const tolerateInterruption = (cause) => Cause.hasInterrupts(cause) ? Effect.interrupt : Effect.fail(cause);
|
|
491
|
-
const guardedVerdict = (run, violations) => (values) => Effect.catchCause(Effect.suspend(() => verdictFor(run, values, violations)), tolerateInterruption);
|
|
492
|
-
const violationMessage = (name, violations) => `${name}: the property returned no boolean; it returned ${violations.join(", ")}. A property must return a literal boolean verdict — asserting with expect(), or returning an Option, Result or object, is not a verdict; write ${REWRITE}.`;
|
|
493
|
-
const runCheck = (run) => {
|
|
494
|
-
const violations = /* @__PURE__ */ new Set();
|
|
495
|
-
return Arbitrary.checkEffect(run.arbitrary, guardedVerdict(run, violations), run.options).pipe(Effect.map((result) => ({
|
|
496
|
-
violations,
|
|
497
|
-
result
|
|
498
|
-
})));
|
|
499
|
-
};
|
|
500
|
-
const reportOf = (checked) => Arbitrary.formatCheckFailure(checked.result);
|
|
501
|
-
const violationOf = (name, checked) => checked.violations.size === 0 ? void 0 : violationMessage(name, [...checked.violations]);
|
|
502
|
-
const isRefuted = (checked) => checked.violations.size > 0 || reportOf(checked) !== void 0;
|
|
503
|
-
const dieViolation = (detail) => Effect.die(new NonBooleanVerdict({ detail }));
|
|
504
|
-
const dieReported = (name, report) => Effect.die(/* @__PURE__ */ new Error(`${name}: the property was falsified. ${report}`));
|
|
505
|
-
const dieUncovered = (failure) => Effect.die(new CoverageBelowMinimum({ message: failure }));
|
|
506
|
-
const impostorHolds = (holds, impostor) => (_subject, values) => holds(impostor.impostor, values);
|
|
507
|
-
const recordImpostor = (run, budget, impostor, checked) => {
|
|
508
|
-
const verdict = {
|
|
509
|
-
refuted: isRefuted(checked),
|
|
510
|
-
frozen: impostor.frozen(),
|
|
511
|
-
runs: budget.runs
|
|
512
|
-
};
|
|
513
|
-
gatedLedger.record(run.subject, run.name, verdict);
|
|
514
|
-
};
|
|
515
|
-
const impostorRun = (run, budget) => {
|
|
516
|
-
const impostor = impostorOf(run.subject);
|
|
517
|
-
const retry = {
|
|
518
|
-
...run,
|
|
519
|
-
holds: impostorHolds(run.holds, impostor),
|
|
520
|
-
observe: noObserve
|
|
521
|
-
};
|
|
522
|
-
return runCheck(retry).pipe(Effect.map((checked) => recordImpostor(retry, budget, impostor, checked)));
|
|
523
|
-
};
|
|
524
|
-
const noObserve = () => void 0;
|
|
525
|
-
const gateRun = (run, budget, gate) => gate ? impostorRun(run, budget) : Effect.void;
|
|
526
|
-
const finishPassed = (run, budget, coverage, gate) => {
|
|
527
|
-
const failure = coverage.judge();
|
|
528
|
-
return failure === void 0 ? gateRun(run, budget, gate) : dieUncovered(`${run.name}: ${failure}`);
|
|
529
|
-
};
|
|
530
|
-
const settleReport = (run, budget, coverage, gate, report) => report === void 0 ? finishPassed(run, budget, coverage, gate) : dieReported(run.name, report);
|
|
531
|
-
const settle = (registration, run, budget, coverage, checked) => {
|
|
532
|
-
const violation = violationOf(registration.name, checked);
|
|
533
|
-
return violation === void 0 ? settleReport(run, budget, coverage, registration.gate, reportOf(checked)) : dieViolation(violation);
|
|
534
|
-
};
|
|
535
|
-
const checkOf = (registration, arbitrary, budget, observe) => ({
|
|
536
|
-
name: registration.name,
|
|
537
|
-
lane: registration.lane,
|
|
538
|
-
subject: registration.spec.subject,
|
|
539
|
-
holds: registration.holds,
|
|
540
|
-
arbitrary,
|
|
541
|
-
observe,
|
|
542
|
-
options: budget.options
|
|
543
|
-
});
|
|
544
|
-
const program = (registration) => Effect.gen(function* () {
|
|
545
|
-
const budget = resolveBudget(registration.name, registration.spec, providedCheckDefaults());
|
|
546
|
-
const arbitrary = arbitraryOf(registration.spec.of);
|
|
547
|
-
const coverage = makeCoverage(registration.spec.cover, arbitrary);
|
|
548
|
-
const run = checkOf(registration, arbitrary, budget, coverage.observe);
|
|
549
|
-
const checked = yield* runCheck(run);
|
|
550
|
-
yield* settle(registration, run, budget, coverage, checked);
|
|
551
|
-
});
|
|
552
|
-
const programOf = (registration) => () => registration.runtime.provide(program(registration));
|
|
553
|
-
const selfModelMessage = (name) => `${name}: the model is the subject itself; a model law must compare against an independent oracle.`;
|
|
554
|
-
const gatedLedger = makeFileLedger((message) => new VacuousProperty({ detail: message }));
|
|
555
|
-
V.afterAll(() => {
|
|
556
|
-
gatedLedger.finalise();
|
|
557
|
-
});
|
|
558
|
-
const registeredName = (name, kind, exempt) => exempt ? `${name} [exempt: ${kind}]` : name;
|
|
559
|
-
const isSelfModel = (subject, oracle) => Object.is(subject, oracle);
|
|
560
|
-
/**
|
|
561
|
-
* The property surface over one runtime: `prop`, `effectProp`, and the R13 law kinds routed through the gate.
|
|
562
|
-
*
|
|
563
|
-
* @internal
|
|
564
|
-
*/
|
|
565
|
-
const makeProperty = (runtime) => {
|
|
566
|
-
const body = (name, spec, holds, lane, gate) => {
|
|
567
|
-
const registration = {
|
|
568
|
-
runtime,
|
|
569
|
-
name,
|
|
570
|
-
spec,
|
|
571
|
-
holds,
|
|
572
|
-
lane,
|
|
573
|
-
gate
|
|
574
|
-
};
|
|
575
|
-
runtime.register(name, programOf(registration));
|
|
576
|
-
};
|
|
577
|
-
const refuseSelfModel = (name) => {
|
|
578
|
-
runtime.register(name, () => Effect.die(new VacuousProperty({ detail: selfModelMessage(name) })));
|
|
579
|
-
};
|
|
580
|
-
const gated = (name, spec, holds, kind, exempt) => body(registeredName(name, kind, exempt), spec, holds, "sync", exempt === false);
|
|
581
|
-
const model = (name, spec, oracle) => isSelfModel(spec.subject, oracle) ? refuseSelfModel(name) : gated(name, spec, modelHolds(oracle), "model", false);
|
|
582
|
-
const idempotent = (name, spec) => gated(name, spec, idempotentHolds(), "idempotent", true);
|
|
583
|
-
const deterministic = (name, spec) => gated(name, spec, deterministicHolds(), "deterministic", true);
|
|
584
|
-
return {
|
|
585
|
-
prop: (name, spec, holds) => body(name, spec, holds, "sync", true),
|
|
586
|
-
effectProp: (name, spec, holds) => body(name, spec, holds, "effect", true),
|
|
587
|
-
law: {
|
|
588
|
-
model,
|
|
589
|
-
metamorphic: (name, spec, relation) => gated(name, spec, metamorphicHolds(relation), "metamorphic", false),
|
|
590
|
-
roundTrip: (name, spec, decode) => gated(name, spec, roundTripHolds(decode), "roundTrip", false),
|
|
591
|
-
invariant: (name, spec, holds) => gated(name, spec, invariantHolds(holds), "invariant", false),
|
|
592
|
-
idempotent,
|
|
593
|
-
deterministic
|
|
594
|
-
}
|
|
595
|
-
};
|
|
596
|
-
};
|
|
597
|
-
//#endregion
|
|
598
87
|
//#region src/internal/virtual-time.ts
|
|
599
88
|
const roundNanos = (millis) => BigInt(Math.round(millis * 1e6));
|
|
600
89
|
const byWakeOrder = (first, second) => {
|
|
@@ -779,9 +268,18 @@ const virtualClockOf = (runtime) => {
|
|
|
779
268
|
* `@systemfsoftware/vitest/guard` can tell the fork's tests from a test registered by Vitest itself.
|
|
780
269
|
*/
|
|
781
270
|
const getCurrentSuite = V.TestRunner.getCurrentSuite;
|
|
271
|
+
let equalTestersRegistered = false;
|
|
272
|
+
const equalTester = (first, second) => equalTogether(first, second) ? true : void 0;
|
|
273
|
+
const equalTogether = (first, second) => bothEqualable(first, second) && Equal.equals(first, second);
|
|
274
|
+
const bothEqualable = (first, second) => Equal.isEqual(first) && Equal.isEqual(second);
|
|
275
|
+
const registerEqualTesters = () => {
|
|
276
|
+
if (equalTestersRegistered) return;
|
|
277
|
+
equalTestersRegistered = true;
|
|
278
|
+
V.expect.addEqualityTesters([equalTester]);
|
|
279
|
+
};
|
|
782
280
|
/** @internal */
|
|
783
281
|
const addEqualityTesters$1 = () => {
|
|
784
|
-
|
|
282
|
+
registerEqualTesters();
|
|
785
283
|
};
|
|
786
284
|
const numbered = (timeout) => ({ timeout });
|
|
787
285
|
const givenOptions = (timeout) => timeout ?? {};
|
|
@@ -819,7 +317,11 @@ const perRun = () => ({
|
|
|
819
317
|
owned: true
|
|
820
318
|
});
|
|
821
319
|
const propertyEnv = realTime;
|
|
822
|
-
const
|
|
320
|
+
const payloadOf = (reason) => Cause.isFailReason(reason) ? reason.error : reasonDefectOf(reason);
|
|
321
|
+
const reasonDefectOf = (reason) => Cause.isDieReason(reason) ? reason.defect : void 0;
|
|
322
|
+
const isLoggable = (thrown) => (reason) => !Cause.isInterruptReason(reason) && payloadOf(reason) !== thrown;
|
|
323
|
+
const unthrownOf = (cause) => cause.pipe(Cause.squash, isLoggable, (loggable) => Cause.fromReasons(cause.reasons.filter(loggable)));
|
|
324
|
+
const logUnthrownErrors = (cause) => cause.pipe(unthrownOf, Cause.prettyErrors, Effect.forEach((error) => Effect.logError(error), { discard: true }));
|
|
823
325
|
const signalOf = (ctx) => ctx?.signal;
|
|
824
326
|
const signalOnly = (signal) => signal === void 0 ? {} : { signal };
|
|
825
327
|
const schedulerOf = (env) => env.runtime === void 0 ? {} : { scheduler: env.runtime.scheduler };
|
|
@@ -838,12 +340,15 @@ const runExit = (effect, ctx, env) => Effect.runPromiseExit(wrapOwned(effect, ow
|
|
|
838
340
|
* of as a raw defect.
|
|
839
341
|
*/
|
|
840
342
|
const isUnprovided = (error) => error instanceof Error && error.message.startsWith("Service not found");
|
|
343
|
+
const refuseUnprovided = (failure) => {
|
|
344
|
+
if (isUnprovided(failure)) throw new Slop({ detail: unprovidedText });
|
|
345
|
+
};
|
|
841
346
|
const rethrowSquashed = (cause) => {
|
|
842
347
|
const error = Cause.squash(cause);
|
|
843
|
-
|
|
348
|
+
refuseUnprovided(error);
|
|
844
349
|
throw error;
|
|
845
350
|
};
|
|
846
|
-
const failExit = (cause) =>
|
|
351
|
+
const failExit = (cause) => logUnthrownErrors(cause).pipe(Effect.runPromise).then(() => rethrowSquashed(cause));
|
|
847
352
|
const reportExit = (exit) => Exit.isSuccess(exit) ? Promise.resolve(exit.value) : failExit(exit.cause);
|
|
848
353
|
const runPromise = (effect, ctx, env = realTime) => runExit(effect, ctx, env).then(reportExit);
|
|
849
354
|
const noop = () => {};
|
|
@@ -858,9 +363,31 @@ const trackAbort = (ctx, promise) => {
|
|
|
858
363
|
promise.then(() => detach(ctx, onAbort), () => detach(ctx, onAbort));
|
|
859
364
|
if (ctx.signal.aborted) onAbort();
|
|
860
365
|
};
|
|
861
|
-
const
|
|
862
|
-
|
|
863
|
-
|
|
366
|
+
const rethrowRendered = (failure) => {
|
|
367
|
+
if (isFailureRecordError(failure)) throw failure;
|
|
368
|
+
};
|
|
369
|
+
const throwRecorded = (cause, recorder, ctx) => {
|
|
370
|
+
const failure = Cause.squash(cause);
|
|
371
|
+
rethrowRendered(failure);
|
|
372
|
+
refuseUnprovided(failure);
|
|
373
|
+
return throwFailureRecord({
|
|
374
|
+
failure,
|
|
375
|
+
spans: recorder.spans,
|
|
376
|
+
identity: testIdentityOf(ctx.task),
|
|
377
|
+
replay: replayOfFailure(failure),
|
|
378
|
+
root: providedRoot()
|
|
379
|
+
});
|
|
380
|
+
};
|
|
381
|
+
const failRecorded = (cause, recorder, ctx) => logUnthrownErrors(cause).pipe(Effect.runPromise).then(() => throwRecorded(cause, recorder, ctx));
|
|
382
|
+
const reportRecorded = (exit, recorder, ctx) => Exit.isSuccess(exit) ? Promise.resolve(exit.value) : failRecorded(exit.cause, recorder, ctx);
|
|
383
|
+
/**
|
|
384
|
+
* One test's run, recorded: its own in-memory tracer collects the spans the record is rendered from, and a failure
|
|
385
|
+
* leaves as the rendered record instead of a raw Cause (KTD2, R2, R7, R8).
|
|
386
|
+
*/
|
|
387
|
+
const runRecorded = (effect, ctx, env) => {
|
|
388
|
+
const recorder = createSpanRecorder();
|
|
389
|
+
const promise = runExit(effect.pipe(Effect.withTracer(recorder.tracer)), ctx, env).then((exit) => reportRecorded(exit, recorder, ctx));
|
|
390
|
+
trackAbort(ctx, promise);
|
|
864
391
|
return promise;
|
|
865
392
|
};
|
|
866
393
|
const clockLayerOf = (env) => env.runtime === void 0 ? TestClock.layer() : virtualClockLayer(env.runtime);
|
|
@@ -904,16 +431,16 @@ const rowBodyOf = (row, body) => (checks) => typeof body === "string" ? refuseBo
|
|
|
904
431
|
/** One test's run: a fresh ledger, and the lane's own services around the program the driver built. */
|
|
905
432
|
const runLaned = (ctx, mapEffect, env, body) => {
|
|
906
433
|
const ledger = makeLedger(ctx);
|
|
907
|
-
return
|
|
434
|
+
return runRecorded(mapEffect(bindRun(drive(bodyOf(ledger, body), [], ledger), ctx), env), ctx, env);
|
|
908
435
|
};
|
|
909
436
|
const outcomeOf = (promise) => promise.then(() => void 0, (error) => error);
|
|
910
|
-
const isSlop = (error) => Schema
|
|
437
|
+
const isSlop = (error) => Schema.is(Slop)(error);
|
|
911
438
|
/** Every failure the fork itself names: the branded refusals, plus the Schema-tagged families. */
|
|
912
439
|
const otherRefusals = [
|
|
913
440
|
isSlop,
|
|
914
|
-
Schema
|
|
915
|
-
Schema
|
|
916
|
-
Schema
|
|
441
|
+
Schema.is(InvalidBudget),
|
|
442
|
+
Schema.is(NonBooleanVerdict),
|
|
443
|
+
Schema.is(LeakedState)
|
|
917
444
|
];
|
|
918
445
|
const isRefusal = (error) => isRefusal$1(error) || otherRefusals.some((matches) => matches(error));
|
|
919
446
|
const clipped = (message) => message.length > 400 ? message.slice(0, 400) : message;
|
|
@@ -939,7 +466,7 @@ const isClean = (ctx) => presentErrors(ctx.task.result?.errors).length === 0;
|
|
|
939
466
|
* Runs a property's program as the test that registered it, under the same context the generator lanes use. The
|
|
940
467
|
* env is the bare `propertyEnv`: a `false` verdict is the property's own shrink path, so nothing interrupts it.
|
|
941
468
|
*/
|
|
942
|
-
const runProperty = (ctx, program) =>
|
|
469
|
+
const runProperty = (ctx, program) => runRecorded(bindRun(Effect.suspend(program), ctx), ctx, propertyEnv);
|
|
943
470
|
const property = makeProperty({
|
|
944
471
|
register: (name, program) => marked(() => V.it(name, (ctx) => runProperty(ctx, program))),
|
|
945
472
|
provide: (effect) => effect
|
package/dist/integration.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { r as Asserted, t as VitestTestContext } from "./test-context-
|
|
2
|
+
import { r as Asserted, t as VitestTestContext } from "./test-context-D_-acRSc.mjs";
|
|
3
3
|
import * as Effect from "effect/Effect";
|
|
4
4
|
import * as Option from "effect/Option";
|
|
5
5
|
//#region src/integration.ts
|