@systemfsoftware/vitest 2.0.0 → 4.1.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 +3 -59
- package/README.md +17 -1
- package/dist/{checks-DiuzHhQM.d.ts → checks-BHOX6fGC.d.ts} +2 -2
- package/dist/{engine-D7Q6-BxD.d.ts → engine-CYGdHvr8.d.ts} +68 -107
- package/dist/{failure-nvq5TwWX.mjs → failure-B094O43o.mjs} +189 -47
- package/dist/failure.d.ts +2 -2
- package/dist/failure.mjs +2 -2
- package/dist/{guard-BocOhXt2.mjs → guard-Ci0TyBYv.mjs} +18 -6
- package/dist/guard.mjs +2 -2
- package/dist/index.d.ts +20 -70
- package/dist/index.mjs +25 -76
- package/dist/integration.d.ts +1 -1
- package/dist/integration.mjs +1 -1
- package/dist/property-defaults.d.ts +11 -0
- package/dist/property-defaults.mjs +15 -0
- package/dist/{test-context-D_-acRSc.mjs → test-context-DelFkLBL.mjs} +10 -6
- package/dist/tiers-W0BWq1uh.mjs +36 -0
- package/package.json +16 -20
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
|
|
2
|
-
import {
|
|
2
|
+
import { n as providedBudgetOf } from "./tiers-W0BWq1uh.mjs";
|
|
3
|
+
import { a as isCheck, d as isUserFrame, f as siteOfFrame, i as checksFor, o as makeLedger, p as withRaisingFrame, r as Asserted, s as callFrame, t as VitestTestContext, u as framesOf$1 } from "./test-context-DelFkLBL.mjs";
|
|
4
|
+
import { l as noCheckRefusal, u as refusalOf, y as refuseUnyielded } from "./guard-Ci0TyBYv.mjs";
|
|
3
5
|
import { TestRunner, inject } from "vitest";
|
|
4
6
|
import * as Cause from "effect/Cause";
|
|
5
7
|
import * as Effect$1 from "effect/Effect";
|
|
@@ -12,8 +14,7 @@ import * as Option$1 from "effect/Option";
|
|
|
12
14
|
import * as Match$1 from "effect/Match";
|
|
13
15
|
import * as Tracer from "effect/Tracer";
|
|
14
16
|
import { Effect, Match, Option, Schema, SchemaGetter, SchemaIssue } from "effect";
|
|
15
|
-
import
|
|
16
|
-
import { layer as layer$1 } from "@effect/platform-node/NodePath";
|
|
17
|
+
import * as Predicate from "effect/Predicate";
|
|
17
18
|
import * as Arbitrary from "effect/Arbitrary";
|
|
18
19
|
import * as Config from "effect/Config";
|
|
19
20
|
import * as ConfigProvider from "effect/ConfigProvider";
|
|
@@ -25,8 +26,12 @@ import * as Path from "effect/Path";
|
|
|
25
26
|
//#region src/internal/property/defaults.ts
|
|
26
27
|
/** @internal The `test.provide` key the shared config fills with this run's tier's check options. */
|
|
27
28
|
const checkDefaultsKey = "@systemfsoftware/vitest:property-check";
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
const processEnvironment = () => ({ ...typeof process === "undefined" ? void 0 : process.env });
|
|
30
|
+
/**
|
|
31
|
+
* @internal The property-check defaults a run provided through `test.provide`, else the tier's budget for this
|
|
32
|
+
* process's environment, read from the same table the setup entry uses.
|
|
33
|
+
*/
|
|
34
|
+
const providedCheckDefaults = () => inject("@systemfsoftware/vitest:property-check") ?? providedBudgetOf(processEnvironment());
|
|
30
35
|
//#endregion
|
|
31
36
|
//#region src/replay.schema.ts
|
|
32
37
|
/**
|
|
@@ -40,15 +45,15 @@ const providedCheckDefaults = () => inject(checkDefaultsKey);
|
|
|
40
45
|
* refuted run, so the engine rebuilds the generator's replay token. Several entries are joined by `|`, which no
|
|
41
46
|
* entry can contain: every key is lowercase letters and every value is digits, commas or a failure tag.
|
|
42
47
|
*
|
|
43
|
-
* A text that names neither form is not a replay. The seed and every decision are branded whole numbers
|
|
44
|
-
*
|
|
48
|
+
* A text that names neither form is not a replay. The seed and every decision are branded whole numbers, and
|
|
49
|
+
* the text is a codec rooted at `Schema.String`, so one declaration serves both the writer and the
|
|
45
50
|
* reader instead of a pattern each.
|
|
46
51
|
*
|
|
47
52
|
* @since 4.0.0
|
|
48
53
|
*/
|
|
49
|
-
/** The seed a replay names: the whole number a generator chose, never negative (CONST-
|
|
54
|
+
/** The seed a replay names: the whole number a generator chose, never negative (CONST-D1). */
|
|
50
55
|
const ReplaySeed = Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0)), Schema.brand("ReplaySeed"));
|
|
51
|
-
/** One decision along a replay path: the index the scheduler picked at that step
|
|
56
|
+
/** One decision along a replay path: the index the scheduler picked at that step. */
|
|
52
57
|
const ReplayStep = Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0)), Schema.brand("ReplayStep"));
|
|
53
58
|
/** The replay a generator chose: its seed and the decisions it took, in order (R6). */
|
|
54
59
|
const Replay = Schema.Struct({
|
|
@@ -57,7 +62,7 @@ const Replay = Schema.Struct({
|
|
|
57
62
|
});
|
|
58
63
|
/** A property's identity hash: the unsigned whole number `identityHash` derives from its identity (KTD4). */
|
|
59
64
|
const PropertyHash = Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0)), Schema.brand("PropertyHash"));
|
|
60
|
-
/** The count of runs a property entry names, never negative (CONST-
|
|
65
|
+
/** The count of runs a property entry names, never negative (CONST-D1). */
|
|
61
66
|
const ReplayRunCount = Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0)), Schema.brand("ReplayRunCount"));
|
|
62
67
|
/** The failure kind effect's falsification runner records in its replay token. */
|
|
63
68
|
const ReplayFailureTag = Schema.Literals(["ReturnedFalse", "PropertyError"]);
|
|
@@ -317,7 +322,7 @@ const WorkspaceRoot = Schema.NonEmptyString.pipe(Schema.brand("WorkspaceRoot"));
|
|
|
317
322
|
* line then drops its package filter, and paths print absolute (R6, KTD5).
|
|
318
323
|
*
|
|
319
324
|
* The values are branded (`provided.schema.ts`): a package name and a workspace root are not interchangeable
|
|
320
|
-
* text
|
|
325
|
+
* text.
|
|
321
326
|
*
|
|
322
327
|
* @since 4.0.0
|
|
323
328
|
*/
|
|
@@ -832,11 +837,13 @@ const copyDiffFields = (error, failure) => {
|
|
|
832
837
|
*/
|
|
833
838
|
const PROPERTY_FAILURE_TAGS = [
|
|
834
839
|
"PropertyRefuted",
|
|
840
|
+
"PropertyExhausted",
|
|
835
841
|
"NonBooleanVerdict",
|
|
836
842
|
"CoverageBelowMinimum",
|
|
837
843
|
"SelfModelLaw",
|
|
838
844
|
"SeedStoreUnreadable",
|
|
839
845
|
"ReplayUnreadable",
|
|
846
|
+
"ReplayNoLongerReproduces",
|
|
840
847
|
"VacuousProperty"
|
|
841
848
|
];
|
|
842
849
|
const isPropertyFailureLayer = (value) => {
|
|
@@ -911,6 +918,74 @@ const currentTask = () => TestRunner.getCurrentTest() ?? {};
|
|
|
911
918
|
/** @internal */
|
|
912
919
|
const testIdentityOf = (task) => identityOf(task ?? currentTask());
|
|
913
920
|
//#endregion
|
|
921
|
+
//#region src/internal/driver.ts
|
|
922
|
+
/**
|
|
923
|
+
* The driver: one Effect that steps the body's generator in the fiber the test runs in.
|
|
924
|
+
*
|
|
925
|
+
* A `yield*` is a step, so the runner sees every one of them (KTD1):
|
|
926
|
+
* - a branded check is judged against this test's ledger, so it counts, and a failed judgement fails the check
|
|
927
|
+
* itself and stops the test right there (R8);
|
|
928
|
+
* - anything else opens a new observed state and then runs, which is what makes a second check on one state a
|
|
929
|
+
* second check in one window (R5), and a check inside a loop the same window as its siblings.
|
|
930
|
+
*
|
|
931
|
+
* The steps run in the same fiber as the test, so layers, virtual time, concurrency, the second run and scope
|
|
932
|
+
* finalizers keep working through the runner unchanged. A step that fails, and the test being interrupted, both
|
|
933
|
+
* return the generator: its `finally` blocks run and the steps after it never start (R8). When the body returns,
|
|
934
|
+
* the gate refuses what the body wrote and never judged, then a body that judged nothing at all (R4).
|
|
935
|
+
*/
|
|
936
|
+
/**
|
|
937
|
+
* A value the body yielded, as the driver runs it. What a body needs of its environment belongs to the layer the
|
|
938
|
+
* runner provides around this whole Effect, so the driver erases the channels here, exactly as the runner erases
|
|
939
|
+
* a body's own (`outputIsEffect` in `runner.ts`): the layer block is where a requirement is checked, and a body
|
|
940
|
+
* that needs a service nothing provides is refused by the runner at run time (R9).
|
|
941
|
+
*/
|
|
942
|
+
const isRunnable = (value) => Effect$1.isEffect(value);
|
|
943
|
+
/** A body that yielded something the driver cannot run ran nothing, so nothing in the test could fail. */
|
|
944
|
+
const refuseUnsteppable = "✗ the body yielded a value that is not an Effect. Yield the Effect itself, or a check: yield* expect(actual).toEqual(expected).";
|
|
945
|
+
/**
|
|
946
|
+
* A yielded Effect: a branded check judges itself, and anything else opens a new observed state before it runs,
|
|
947
|
+
* which is what resets the ledger's one-check window (R5).
|
|
948
|
+
*/
|
|
949
|
+
const runEffect = (value, ledger) => isCheck(value) ? value : Effect$1.andThen(Effect$1.sync(() => ledger.asserted.step()), value);
|
|
950
|
+
/** One step: anything the driver cannot run is refused, because then no step of the test ran at all. */
|
|
951
|
+
const runStep = (value, ledger) => isRunnable(value) ? runEffect(value, ledger) : Effect$1.die(refusalOf(refuseUnsteppable));
|
|
952
|
+
/** Steps the iterator to its end, feeding each yielded Effect's result back into the body. */
|
|
953
|
+
const stepThrough = (steps, ledger, input) => Effect$1.suspend(() => {
|
|
954
|
+
const state = steps.next(input);
|
|
955
|
+
if (state.done === true) return Effect$1.void;
|
|
956
|
+
return Effect$1.flatMap(runStep(state.value, ledger), (value) => stepThrough(steps, ledger, value));
|
|
957
|
+
});
|
|
958
|
+
/**
|
|
959
|
+
* After the body returns: a check written and never yielded is refused first, then a body that judged none.
|
|
960
|
+
* Both carry the same text the refusal carries on its compile channel, branded by `refusalOf` so the runner's
|
|
961
|
+
* second run re-throws them as the refusals they are.
|
|
962
|
+
*/
|
|
963
|
+
const gate = (ledger) => Effect$1.andThen(refusingUnyielded(ledger), judgedNone(ledger));
|
|
964
|
+
/** @internal */
|
|
965
|
+
const refusingUnyielded = (ledger) => Effect$1.suspend(() => ledger.unyielded() > 0 ? refuse(refuseUnyielded) : Effect$1.void);
|
|
966
|
+
const judgedNone = (ledger) => Effect$1.suspend(() => ledger.judged() === 0 ? refuse(noCheckRefusal) : Effect$1.void);
|
|
967
|
+
const refuse = (text) => Effect$1.die(refusalOf(text));
|
|
968
|
+
/**
|
|
969
|
+
* A failure or an interruption returns the generator, so its `finally` blocks run and the step after the failed
|
|
970
|
+
* check never does (R8).
|
|
971
|
+
*/
|
|
972
|
+
const unwind = (steps) => Effect$1.sync(() => {
|
|
973
|
+
steps.return?.(void 0);
|
|
974
|
+
});
|
|
975
|
+
const driveBody = (body, args, ledger) => Effect$1.suspend(() => {
|
|
976
|
+
const steps = body(...args);
|
|
977
|
+
return Effect$1.onError(Effect$1.flatMap(stepThrough(steps, ledger, void 0), () => gate(ledger)), () => unwind(steps));
|
|
978
|
+
});
|
|
979
|
+
/** The body's steps, with this test's ledger provided as the `Asserted` every check in it reaches for. */
|
|
980
|
+
const driven = (body, args, ledger) => Effect$1.provideService(driveBody(body, args, ledger), Asserted, ledger.asserted);
|
|
981
|
+
/**
|
|
982
|
+
* Runs one test's body: `body` called with `args`, stepped against `ledger`, with the ledger's `asserted`
|
|
983
|
+
* provided as `Asserted` so every check in the body — including one yielded inside a forked fiber — reaches it.
|
|
984
|
+
*
|
|
985
|
+
* @internal
|
|
986
|
+
*/
|
|
987
|
+
const drive = Function.dual(3, driven);
|
|
988
|
+
//#endregion
|
|
914
989
|
//#region src/internal/property/coverage.ts
|
|
915
990
|
/**
|
|
916
991
|
* @internal The R14 coverage classes: labelled input predicates, each with a minimum share of runs.
|
|
@@ -1020,7 +1095,7 @@ const judgeCoverage = (options) => failedFailures(toppedUp(judgeOf(options.class
|
|
|
1020
1095
|
//#region src/internal/property/error.schema.ts
|
|
1021
1096
|
/**
|
|
1022
1097
|
* The closed set of property-channel failures (KTD1, R1): refuted, non-boolean, under-covered and self-model, plus
|
|
1023
|
-
* the vacuous impostor verdict
|
|
1098
|
+
* the vacuous impostor verdict, the seed store's read failure and a stale explicit replay. Every variant is a `Schema.TaggedError` carrying
|
|
1024
1099
|
* its own data — the property it names, a rendered-and-projected witness, coverage classes, the law that refused
|
|
1025
1100
|
* itself — and a zero-argument `message` getter computed only from those fields. Wording is free; nothing reads a
|
|
1026
1101
|
* message back.
|
|
@@ -1028,13 +1103,13 @@ const judgeCoverage = (options) => failedFailures(toppedUp(judgeOf(options.class
|
|
|
1028
1103
|
* @since 4.0.0
|
|
1029
1104
|
*/
|
|
1030
1105
|
/**
|
|
1031
|
-
* A property's seed: a whole number a generator chose, never negative (CONST-
|
|
1106
|
+
* A property's seed: a whole number a generator chose, never negative (CONST-D1).
|
|
1032
1107
|
*
|
|
1033
1108
|
* @internal
|
|
1034
1109
|
*/
|
|
1035
1110
|
const PropertySeed = Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0)), Schema.brand("PropertySeed"));
|
|
1036
1111
|
/**
|
|
1037
|
-
* The count of runs a property's check was configured with, never negative (CONST-
|
|
1112
|
+
* The count of runs a property's check was configured with, never negative (CONST-D1).
|
|
1038
1113
|
*
|
|
1039
1114
|
* @internal
|
|
1040
1115
|
*/
|
|
@@ -1093,6 +1168,21 @@ var PropertyRefuted = class extends Schema.TaggedError()("PropertyRefuted", {
|
|
|
1093
1168
|
return `${this.property.name}: the property was falsified within ${String(this.property.runs)} run(s), and ${String(this.shrinks)} shrink(s) settled on its input. Shrunk input: ${this.counterexample.rendered}`;
|
|
1094
1169
|
}
|
|
1095
1170
|
};
|
|
1171
|
+
/** @internal */
|
|
1172
|
+
var PropertyExhausted = class extends Schema.TaggedError()("PropertyExhausted", {
|
|
1173
|
+
property: PropertyRun,
|
|
1174
|
+
discards: PropertyRunCount,
|
|
1175
|
+
budget: Schema.Struct({
|
|
1176
|
+
runs: PropertyRunCount,
|
|
1177
|
+
maxDiscards: Schema.NullOr(PropertyRunCount)
|
|
1178
|
+
}),
|
|
1179
|
+
replay: Schema.String
|
|
1180
|
+
}) {
|
|
1181
|
+
get message() {
|
|
1182
|
+
const cap = this.budget.maxDiscards === null ? "no configured discard cap" : `a discard cap of ${String(this.budget.maxDiscards)}`;
|
|
1183
|
+
return `${this.property.name}: the generator discarded ${String(this.discards)} draw(s) under ${cap} within ${String(this.budget.runs)} run(s), so the property was never evaluated. Narrow the generator so it draws admissible values, or raise \`maxDiscards\`.`;
|
|
1184
|
+
}
|
|
1185
|
+
};
|
|
1096
1186
|
/**
|
|
1097
1187
|
* A verdict that was not a literal boolean: the property that ran, the first drawn values whose verdict was not a
|
|
1098
1188
|
* boolean, and every kind of value the property returned (R4).
|
|
@@ -1142,11 +1232,11 @@ var CoverageBelowMinimum = class extends Schema.TaggedError()("CoverageBelowMini
|
|
|
1142
1232
|
* @internal
|
|
1143
1233
|
*/
|
|
1144
1234
|
var SelfModelLaw = class extends Schema.TaggedError()("SelfModelLaw", {
|
|
1145
|
-
|
|
1235
|
+
law: Schema.String,
|
|
1146
1236
|
site: Schema.NullOr(Schema.String)
|
|
1147
1237
|
}) {
|
|
1148
1238
|
get message() {
|
|
1149
|
-
return `${this.
|
|
1239
|
+
return `${this.law}: the model is the subject itself; a model law must compare against an independent oracle.`;
|
|
1150
1240
|
}
|
|
1151
1241
|
};
|
|
1152
1242
|
/**
|
|
@@ -1242,6 +1332,21 @@ var ReplayUnreadable = class extends Schema.TaggedError()("ReplayUnreadable", {
|
|
|
1242
1332
|
}
|
|
1243
1333
|
};
|
|
1244
1334
|
/**
|
|
1335
|
+
* An explicit `CONFORMANCE_REPLAY` replay whose recorded failure no longer reproduces: after the root re-check,
|
|
1336
|
+
* the replay still mismatched, so no draw reproduces the failure the text names. The recorded seed-store path is
|
|
1337
|
+
* not this — there the entry is one candidate among the novel draws, and only a draw that still fails refutes.
|
|
1338
|
+
*
|
|
1339
|
+
* @internal
|
|
1340
|
+
*/
|
|
1341
|
+
var ReplayNoLongerReproduces = class extends Schema.TaggedError()("ReplayNoLongerReproduces", {
|
|
1342
|
+
property: PropertyRun,
|
|
1343
|
+
replay: Schema.String
|
|
1344
|
+
}) {
|
|
1345
|
+
get message() {
|
|
1346
|
+
return `${this.property.name}: the CONFORMANCE_REPLAY replay ${this.replay} no longer reproduces a failure for this property; the case it named is gone or its generator changed.`;
|
|
1347
|
+
}
|
|
1348
|
+
};
|
|
1349
|
+
/**
|
|
1245
1350
|
* Thrown when a law kind is given a record `of`, which cannot be spread into its subject.
|
|
1246
1351
|
*
|
|
1247
1352
|
* @internal
|
|
@@ -1271,15 +1376,6 @@ const ReplayToken = Schema$1.fromJsonString(Schema$1.Tuple([
|
|
|
1271
1376
|
]));
|
|
1272
1377
|
//#endregion
|
|
1273
1378
|
//#region src/internal/property/replay.ts
|
|
1274
|
-
/**
|
|
1275
|
-
* The property replay channel (KTD5, R9-R12): the text a property failure carries, the generator replay token a
|
|
1276
|
-
* refuted entry rebuilds, and the entry the engine selects for a property's identity hash.
|
|
1277
|
-
*
|
|
1278
|
-
* The token `effect`'s falsification runner writes is decoded here through its codec — never hand-parsed — into the
|
|
1279
|
-
* parts a refuted entry names, and rebuilt from them the way `makeReplay` reads them back.
|
|
1280
|
-
*
|
|
1281
|
-
* @since 4.0.0
|
|
1282
|
-
*/
|
|
1283
1379
|
const NUMERIC_SEED = 0;
|
|
1284
1380
|
/** @internal */
|
|
1285
1381
|
const tokenOfReplay = (entry) => JSON.stringify([
|
|
@@ -1290,6 +1386,27 @@ const tokenOfReplay = (entry) => JSON.stringify([
|
|
|
1290
1386
|
entry.path,
|
|
1291
1387
|
entry.failure
|
|
1292
1388
|
]);
|
|
1389
|
+
const FAILURE_CLASSES = {
|
|
1390
|
+
ReturnedFalse: true,
|
|
1391
|
+
PropertyError: true
|
|
1392
|
+
};
|
|
1393
|
+
/**
|
|
1394
|
+
* The tokens that replay only the root draw of `token`, one per failure class: the recorded attempt with an empty
|
|
1395
|
+
* shrink path. Empty for an absent or unreadable token.
|
|
1396
|
+
*
|
|
1397
|
+
* @internal
|
|
1398
|
+
*/
|
|
1399
|
+
const rootTokensOf = (token) => Option$1.match(Option$1.flatMap(Option$1.fromNullishOr(token), Schema$1.decodeOption(ReplayToken)), {
|
|
1400
|
+
onNone: () => [],
|
|
1401
|
+
onSome: (decoded) => Object.keys(FAILURE_CLASSES).map((failure) => JSON.stringify([
|
|
1402
|
+
decoded[0],
|
|
1403
|
+
`${decoded[1]}`,
|
|
1404
|
+
decoded[2],
|
|
1405
|
+
decoded[3],
|
|
1406
|
+
[],
|
|
1407
|
+
failure
|
|
1408
|
+
]))
|
|
1409
|
+
});
|
|
1293
1410
|
/** @internal */
|
|
1294
1411
|
const refutedReplayTextOf = (identity) => Option$1.getOrUndefined(Option$1.map(Schema$1.decodeOption(ReplayToken)(identity.token), (decoded) => refutedReplayText$1({
|
|
1295
1412
|
property: identity.property,
|
|
@@ -1757,14 +1874,30 @@ const verdictFor = (run, values, violations) => {
|
|
|
1757
1874
|
};
|
|
1758
1875
|
const tolerateInterruption = (cause) => Cause.hasInterrupts(cause) ? Effect$1.interrupt : Effect$1.fail(cause);
|
|
1759
1876
|
const guardedVerdict = (run, violations) => (values) => Effect$1.catchCause(Effect$1.suspend(() => verdictFor(run, values, violations)), tolerateInterruption);
|
|
1877
|
+
const checkWith = (run, violations, options) => Arbitrary.checkEffect(run.arbitrary, guardedVerdict(run, violations), options);
|
|
1878
|
+
const isFalsified = Predicate.isTagged("Falsified");
|
|
1879
|
+
const isReplayMismatch = Predicate.isTagged("ReplayMismatch");
|
|
1880
|
+
/**
|
|
1881
|
+
* A replayed draw whose recorded shrink path no longer replays (`ReplayMismatch`) is re-checked at its root under
|
|
1882
|
+
* each failure class, so a root that still falsifies the property refutes it with the root as its counterexample.
|
|
1883
|
+
* The classes are tried in order and the check stops at the first that falsifies; when none does, the mismatch
|
|
1884
|
+
* stands: the recorded failure no longer reproduces.
|
|
1885
|
+
*/
|
|
1886
|
+
const rootCheckOf = (run, mismatch) => rootTokensOf(run.options.replay).reduce((previous, replay) => Effect$1.filterOrElse(previous, (found) => isFalsified(found), () => checkWith({
|
|
1887
|
+
...run,
|
|
1888
|
+
observe: noObserve
|
|
1889
|
+
}, newViolations(), {
|
|
1890
|
+
...run.options,
|
|
1891
|
+
replay
|
|
1892
|
+
})), Effect$1.succeed(mismatch));
|
|
1760
1893
|
const runCheck = (run) => {
|
|
1761
1894
|
const violations = newViolations();
|
|
1762
|
-
return
|
|
1895
|
+
return checkWith(run, violations, run.options).pipe(Effect$1.filterOrElse((result) => !isReplayMismatch(result), (mismatch) => rootCheckOf(run, mismatch)), Effect$1.map((result) => ({
|
|
1763
1896
|
violations,
|
|
1764
1897
|
result
|
|
1765
1898
|
})));
|
|
1766
1899
|
};
|
|
1767
|
-
const reportOf = (checked) => Arbitrary.formatCheckFailure(checked.result);
|
|
1900
|
+
const reportOf = (checked) => isReplayMismatch(checked.result) ? void 0 : Arbitrary.formatCheckFailure(checked.result);
|
|
1768
1901
|
const seeded = (seed) => Option$1.getOrThrow(Schema$1.decodeOption(PropertySeed)(seed));
|
|
1769
1902
|
const runCounted = (runs) => Option$1.getOrThrow(Schema$1.decodeOption(PropertyRunCount)(runs));
|
|
1770
1903
|
const propertyRunOf = (run, budget) => ({
|
|
@@ -1829,20 +1962,23 @@ const violationOf = (run, budget, checked) => checked.violations.kinds.length ==
|
|
|
1829
1962
|
const isRefuted = (checked) => checked.violations.kinds.length > 0 || reportOf(checked) !== void 0;
|
|
1830
1963
|
const dieWithSite = (error, site) => Effect$1.die(withRaisingFrame(error, site));
|
|
1831
1964
|
const shrinkCounted = (shrinks) => Option$1.getOrThrow(Schema$1.decodeOption(PropertyShrinkCount)(shrinks));
|
|
1832
|
-
const
|
|
1833
|
-
|
|
1834
|
-
shrinks: shrinkCounted(0)
|
|
1835
|
-
};
|
|
1836
|
-
const falsificationOf = (falsified) => falsified === void 0 ? NO_FALSIFICATION : {
|
|
1837
|
-
counterexample: witnessOf(falsified.shrunkInput),
|
|
1838
|
-
shrinks: shrinkCounted(falsified.shrinks)
|
|
1839
|
-
};
|
|
1840
|
-
const refutedOf = (property, checked, replay) => new PropertyRefuted({
|
|
1965
|
+
const countOrNull = (value) => Option$1.getOrNull(Option$1.flatMap(Option$1.fromUndefinedOr(value), (count) => Schema$1.decodeOption(PropertyRunCount)(count)));
|
|
1966
|
+
const refutedOf = (property, falsified, replay) => new PropertyRefuted({
|
|
1841
1967
|
property,
|
|
1842
|
-
|
|
1968
|
+
counterexample: witnessOf(falsified.shrunkInput),
|
|
1969
|
+
shrinks: shrinkCounted(falsified.shrinks),
|
|
1843
1970
|
replay
|
|
1844
1971
|
});
|
|
1845
|
-
const
|
|
1972
|
+
const dieRefuted = (run, budget, checked, falsified, site) => dieWithSite(refutedOf(propertyRunOf(run, budget), falsified, refutedFailureReplay(run, budget, checked)), site);
|
|
1973
|
+
const dieExhausted = (run, budget, exhausted, site) => dieWithSite(new PropertyExhausted({
|
|
1974
|
+
property: propertyRunOf(run, budget),
|
|
1975
|
+
discards: runCounted(exhausted.discards),
|
|
1976
|
+
budget: {
|
|
1977
|
+
runs: runCounted(budget.runs),
|
|
1978
|
+
maxDiscards: countOrNull(budget.options.maxDiscards)
|
|
1979
|
+
},
|
|
1980
|
+
replay: plainFailureReplay(run, budget)
|
|
1981
|
+
}), site);
|
|
1846
1982
|
const dieUncovered = (run, budget, classes, site) => dieWithSite(new CoverageBelowMinimum({
|
|
1847
1983
|
property: propertyRunOf(run, budget),
|
|
1848
1984
|
classes,
|
|
@@ -1901,12 +2037,12 @@ const refutedCandidate = (store, budget, checked) => Option$1.flatMap(Option$1.f
|
|
|
1901
2037
|
seed: budget.seed,
|
|
1902
2038
|
token
|
|
1903
2039
|
}));
|
|
1904
|
-
const recordedReport = (registration, run, budget, checked) =>
|
|
2040
|
+
const recordedReport = (registration, run, budget, checked) => Match$1.value(checked.result).pipe(Match$1.tag("Falsified", (falsified) => dieRefuted(run, budget, checked, falsified, registration.site)), Match$1.tag("Exhausted", (exhausted) => dieExhausted(run, budget, exhausted, registration.site)), Match$1.orElse(() => Effect$1.void));
|
|
1905
2041
|
const settleRecorded = (registration, run, budget, checked) => Option$1.match(Option$1.fromNullishOr(violationOf(run, budget, checked)), {
|
|
1906
2042
|
onNone: () => recordedReport(registration, run, budget, checked),
|
|
1907
2043
|
onSome: (violation) => dieWithSite(violation, registration.site)
|
|
1908
2044
|
});
|
|
1909
|
-
const settleRefuted = (registration, run, budget, coverage, checked, store) =>
|
|
2045
|
+
const settleRefuted = (registration, run, budget, coverage, checked, store) => Match$1.value(checked.result).pipe(Match$1.tag("Falsified", (falsified) => appendThenDie(store, refutedCandidate(store, budget, checked), dieRefuted(run, budget, checked, falsified, registration.site))), Match$1.tag("Exhausted", (exhausted) => appendThenDie(store, Option$1.none(), dieExhausted(run, budget, exhausted, registration.site))), Match$1.orElse(() => finishPassed(run, budget, coverage, registration.gate, [...store.recorded, budget])));
|
|
1910
2046
|
const settle = (registration, run, budget, coverage, checked, store) => Option$1.match(Option$1.fromNullishOr(violationOf(run, budget, checked)), {
|
|
1911
2047
|
onNone: () => settleRefuted(registration, run, budget, coverage, checked, store),
|
|
1912
2048
|
onSome: (violation) => appendThenDie(store, nonBooleanEntryOf({
|
|
@@ -1947,11 +2083,14 @@ const runRecordedChecks = (registration, arbitrary, hash, recorded) => Effect$1.
|
|
|
1947
2083
|
concurrency: 1,
|
|
1948
2084
|
discard: true
|
|
1949
2085
|
});
|
|
1950
|
-
const replayRun = (registration, baseBudget, arbitrary, hash, entry) => {
|
|
2086
|
+
const replayRun = (registration, baseBudget, arbitrary, hash, entry, text) => {
|
|
1951
2087
|
const budget = applyReplayEntry(baseBudget, entry);
|
|
1952
2088
|
const coverage = makeCoverage(registration.spec.cover, arbitrary, budget.seed);
|
|
1953
2089
|
const run = checkOf(registration, arbitrary, budget, coverage.observe, hash);
|
|
1954
|
-
return Effect$1.flatMap(runCheck(run), (checked) =>
|
|
2090
|
+
return Effect$1.flatMap(runCheck(run), (checked) => isReplayMismatch(checked.result) ? dieWithSite(new ReplayNoLongerReproduces({
|
|
2091
|
+
property: propertyRunOf(run, budget),
|
|
2092
|
+
replay: text
|
|
2093
|
+
}), registration.site) : settle(registration, run, budget, coverage, checked, noStore(registration.name)));
|
|
1955
2094
|
};
|
|
1956
2095
|
const storeRun = (registration, task, baseBudget, arbitrary, hash) => Effect$1.gen(function* () {
|
|
1957
2096
|
const entries = yield* readTaskStore(task, registration.site);
|
|
@@ -1969,7 +2108,10 @@ const storeRun = (registration, task, baseBudget, arbitrary, hash) => Effect$1.g
|
|
|
1969
2108
|
budget: task.budget
|
|
1970
2109
|
});
|
|
1971
2110
|
});
|
|
1972
|
-
const propertyPlatform = Layer.
|
|
2111
|
+
const propertyPlatform = Layer.unwrap(Effect$1.promise(async () => {
|
|
2112
|
+
const [fileSystem, path] = await Promise.all([import("@effect/platform-node/NodeFileSystem"), import("@effect/platform-node/NodePath")]);
|
|
2113
|
+
return Layer.merge(fileSystem.layer, path.layer);
|
|
2114
|
+
}));
|
|
1973
2115
|
const program = (registration, task) => Effect$1.provide(Effect$1.gen(function* () {
|
|
1974
2116
|
const fresh = (yield* Random.nextInt) >>> 0;
|
|
1975
2117
|
const baseBudget = resolveBudget(registration.name, registration.spec, task, fresh);
|
|
@@ -1979,7 +2121,7 @@ const program = (registration, task) => Effect$1.provide(Effect$1.gen(function*
|
|
|
1979
2121
|
const arbitrary = arbitraryOf(registration.spec.of);
|
|
1980
2122
|
yield* Option$1.match(Option$1.fromNullishOr(entry), {
|
|
1981
2123
|
onNone: () => storeRun(registration, task, baseBudget, arbitrary, hash),
|
|
1982
|
-
onSome: (replay) => replayRun(registration, baseBudget, arbitrary, hash, replay)
|
|
2124
|
+
onSome: (replay) => replayRun(registration, baseBudget, arbitrary, hash, replay, Option$1.getOrThrow(replayText))
|
|
1983
2125
|
});
|
|
1984
2126
|
}), propertyPlatform);
|
|
1985
2127
|
const programOf = (registration) => (task) => registration.runtime.provide(program(registration, task));
|
|
@@ -2005,7 +2147,7 @@ const makeProperty = (runtime) => {
|
|
|
2005
2147
|
const refuseSelfModel = (name) => {
|
|
2006
2148
|
const site = callFrame();
|
|
2007
2149
|
runtime.register(name, (_task) => dieWithSite(new SelfModelLaw({
|
|
2008
|
-
name,
|
|
2150
|
+
law: name,
|
|
2009
2151
|
site: site ?? null
|
|
2010
2152
|
}), site));
|
|
2011
2153
|
};
|
|
@@ -2077,7 +2219,7 @@ const provideRun = (effect, ledger, ctx) => effect.pipe(Effect$1.provideService(
|
|
|
2077
2219
|
const runRecorded = (ctx, program) => {
|
|
2078
2220
|
const ledger = makeLedger(ctx);
|
|
2079
2221
|
const recorder = createSpanRecorder();
|
|
2080
|
-
return Effect$1.runPromiseExit(provideRun(program(checksFor(ledger)), ledger, ctx).pipe(Effect$1.withTracer(recorder.tracer))).then((exit) => outcomeOf(exit, recorder, testIdentityOf()));
|
|
2222
|
+
return Effect$1.runPromiseExit(provideRun(Effect$1.tap(program(checksFor(ledger)), () => refusingUnyielded(ledger)), ledger, ctx).pipe(Effect$1.withTracer(recorder.tracer))).then((exit) => outcomeOf(exit, recorder, testIdentityOf()));
|
|
2081
2223
|
};
|
|
2082
2224
|
/**
|
|
2083
2225
|
* Runs one program as the runner runs a lane, with a fresh ledger and its own recorder, and resolves with the
|
|
@@ -2149,4 +2291,4 @@ const recordOfFile = Function.dual((args) => typeof args[0] === "function", (reg
|
|
|
2149
2291
|
}));
|
|
2150
2292
|
});
|
|
2151
2293
|
//#endregion
|
|
2152
|
-
export {
|
|
2294
|
+
export { providedCheckDefaults as A, providedRoot as C, Replay as D, Slop as E, ReplayFromText as O, witnessOf as S, LeakedState as T, isFailureRecordError as _, makeProperty as a, testIdentityOf as b, NonBooleanVerdict as c, ReplayNoLongerReproduces as d, ReplayUnreadable as f, drive as g, VacuousProperty as h, createSpanRecorder as i, replayOfText as k, PropertyExhausted as l, SelfModelLaw as m, recordOfProperty as n, makeFileLedger as o, SeedStoreUnreadable as p, recordOfRun as r, CoverageBelowMinimum as s, recordOfFile as t, PropertyRefuted as u, renderFailureRecord as v, InvalidBudget as w, throwFailureRecord as x, summaryOf as y };
|
package/dist/failure.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
|
-
import { A as VacuousProperty,
|
|
3
|
-
import { i as Checks, t as Asserted } from "./checks-
|
|
2
|
+
import { A as VacuousProperty, M as Breach, _ as renderFailureRecord, a as PropertySubject, b as throwFailureRecord, d as AttributeValue, f as FailureRecord, g as TestIdentity, h as ReplayValue, i as PropertySpec, j as Witness, l as ProvidedCheckDefaults, m as JsonValue, n as Gens, o as Values, p as FailureRecordInput, r as PropApi, v as summaryOf, x as witnessOf, y as testIdentityOf } from "./engine-CYGdHvr8.js";
|
|
3
|
+
import { i as Checks, t as Asserted } from "./checks-BHOX6fGC.js";
|
|
4
4
|
import * as Effect$1 from "effect/Effect";
|
|
5
5
|
import * as Tracer from "effect/Tracer";
|
|
6
6
|
import { Option, Schema } from "effect";
|
package/dist/failure.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
|
|
2
|
-
import {
|
|
3
|
-
import { c as callFrameOutside, l as callSite, p as withRaisingFrame } from "./test-context-
|
|
2
|
+
import { C as providedRoot, D as Replay, O as ReplayFromText, S as witnessOf, b as testIdentityOf, i as createSpanRecorder, k as replayOfText, n as recordOfProperty, r as recordOfRun, t as recordOfFile, v as renderFailureRecord, x as throwFailureRecord, y as summaryOf } from "./failure-B094O43o.mjs";
|
|
3
|
+
import { c as callFrameOutside, l as callSite, p as withRaisingFrame } from "./test-context-DelFkLBL.mjs";
|
|
4
4
|
export { Replay, ReplayFromText, callFrameOutside, callSite, createSpanRecorder, providedRoot as providedWorkspaceRoot, recordOfFile, recordOfProperty, recordOfRun, renderFailureRecord, replayOfText, summaryOf, testIdentityOf, throwFailureRecord, withRaisingFrame, witnessOf };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
import { chai } from "vitest";
|
|
3
|
+
import * as Predicate from "effect/Predicate";
|
|
3
4
|
//#region src/internal/refusals.ts
|
|
4
5
|
/**
|
|
5
6
|
* Every refusal the fork prints, as the one text it uses on both channels: the `this` type (or parameter union)
|
|
@@ -108,9 +109,13 @@ const refuseRawIt = "✗ this test was registered with vitest's it; import it fr
|
|
|
108
109
|
* `new Assertion(...)` on the same class (`chai/index.js:3367`, `:3394`).
|
|
109
110
|
*
|
|
110
111
|
* So this module wraps that prototype once, and every method, chainable method and property on it refuses
|
|
111
|
-
* unless a check opened the window with {@link authorize} for the duration of its matcher call.
|
|
112
|
-
*
|
|
113
|
-
*
|
|
112
|
+
* unless a check opened the window with {@link authorize} for the duration of its matcher call. A matcher that
|
|
113
|
+
* returns a promise keeps its window open until that promise settles, so an async matcher such as
|
|
114
|
+
* `toMatchScreenshot` can build chai assertions after it awaits. Each call closes exactly the window it opened,
|
|
115
|
+
* so the depth counter stays nested-safe: chai's own matchers build further assertions inside the one call.
|
|
116
|
+
* The counter is global to the worker, not scoped to the test that opened it: while an async matcher is
|
|
117
|
+
* pending, a raw `expect` from a concurrently running test in the same worker is let through too. Once the
|
|
118
|
+
* promise settles the window closes and a raw `expect` is refused again.
|
|
114
119
|
*
|
|
115
120
|
* The window, the install flag and the wrapper marks live on `globalThis` under `Symbol.for` keys rather than
|
|
116
121
|
* in module scope. A worker can hold two copies of this file at once — the setup file resolves through Node's
|
|
@@ -151,13 +156,20 @@ const authorized = () => state.depth > 0;
|
|
|
151
156
|
const refuse = () => {
|
|
152
157
|
throw refusalOf(refuseRawExpect);
|
|
153
158
|
};
|
|
159
|
+
const close = () => {
|
|
160
|
+
state.depth -= 1;
|
|
161
|
+
};
|
|
162
|
+
const closeWhenSettled = (outcome) => Predicate.isPromiseLike(outcome) ? void Promise.resolve(outcome).then(close, close) : close();
|
|
154
163
|
/** @internal */
|
|
155
164
|
const authorize = (run) => {
|
|
156
165
|
state.depth += 1;
|
|
157
166
|
try {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
167
|
+
const outcome = run();
|
|
168
|
+
closeWhenSettled(outcome);
|
|
169
|
+
return outcome;
|
|
170
|
+
} catch (failure) {
|
|
171
|
+
close();
|
|
172
|
+
throw failure;
|
|
161
173
|
}
|
|
162
174
|
};
|
|
163
175
|
/** @internal */
|
package/dist/guard.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { h as refuseRawIt, n as installGuard, r as isMarked, u as refusalOf } from "./guard-
|
|
2
|
+
import { h as refuseRawIt, n as installGuard, r as isMarked, u as refusalOf } from "./guard-Ci0TyBYv.mjs";
|
|
3
3
|
import { beforeEach } from "vitest";
|
|
4
4
|
//#region src/guard.ts
|
|
5
5
|
/**
|
|
@@ -14,6 +14,6 @@ import { beforeEach } from "vitest";
|
|
|
14
14
|
installGuard();
|
|
15
15
|
beforeEach((context) => {
|
|
16
16
|
if (!isMarked(context.task)) throw refusalOf(refuseRawIt);
|
|
17
|
-
});
|
|
17
|
+
}, 0);
|
|
18
18
|
//#endregion
|
|
19
19
|
export {};
|