Skip to content

Commit

Permalink
💥 Error with cause by default (#4489)
Browse files Browse the repository at this point in the history
**💥 Breaking change**

Until now, fast-check have been merging the content and stack of the original Error that caused the property to fail into its own Error. With the recent (2022) introduction of cause on Errors, this complex logic can be dropped in favor of the native cause mechanism.

This PR makes cause mode the default. Before this PR, toggling it was possible via `errorWithCause: true` on `fc.assert`.

Given not all test runners properly support causes attached to the Error, we offer a fallback for users willing to preserve the old behaviour. It can be toggled via `includeErrorInReport: true` on `fc.assert`.

Related to #4416.
  • Loading branch information
dubzzz committed May 17, 2024
1 parent 596ef0a commit 9ee8387
Show file tree
Hide file tree
Showing 16 changed files with 64 additions and 773 deletions.
14 changes: 8 additions & 6 deletions packages/fast-check/src/check/runner/configuration/Parameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,13 +192,15 @@ export interface Parameters<T = void> {
*/
asyncReporter?: (runDetails: RunDetails<T>) => Promise<void>;
/**
* Should the thrown Error include a cause leading to the original Error?
* By default the Error causing the failure of the predicate will not be directly exposed within the message
* of the Error thown by fast-check. It will be exposed by a cause field attached to the Error.
*
* In such case the original Error will disappear from the message of the Error thrown by fast-check
* and only appear within the cause part of it.
* The Error with cause has been supported by Node since 16.14.0 and is properly supported in many test runners.
*
* Remark: At the moment, only node (≥16.14.0) and vitest seem to properly display such errors.
* Others will just discard the cause at display time.
* But if the original Error fails to appear within your test runner,
* Or if you prefer the Error to be included directly as part of the message of the resulted Error,
* you can toggle this flag and the Error produced by fast-check in case of failure will expose the source Error
* as part of the message and not as a cause.
*/
errorWithCause?: boolean;
includeErrorInReport?: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export class QualifiedParameters<T> {
ignoreEqualValues: boolean;
reporter: ((runDetails: RunDetails<T>) => void) | null;
asyncReporter: ((runDetails: RunDetails<T>) => Promise<void>) | null;
errorWithCause: boolean;
includeErrorInReport: boolean;

constructor(op?: Parameters<T>) {
const p = op || {};
Expand Down Expand Up @@ -66,7 +66,7 @@ export class QualifiedParameters<T> {
this.endOnFailure = QualifiedParameters.readBoolean(p, 'endOnFailure');
this.reporter = QualifiedParameters.readOrDefault(p, 'reporter', null);
this.asyncReporter = QualifiedParameters.readOrDefault(p, 'asyncReporter', null);
this.errorWithCause = QualifiedParameters.readBoolean(p, 'errorWithCause');
this.includeErrorInReport = QualifiedParameters.readBoolean(p, 'includeErrorInReport');
}

toParameters(): Parameters<T> {
Expand All @@ -90,7 +90,7 @@ export class QualifiedParameters<T> {
endOnFailure: this.endOnFailure,
reporter: orUndefined(this.reporter),
asyncReporter: orUndefined(this.asyncReporter),
errorWithCause: this.errorWithCause,
includeErrorInReport: this.includeErrorInReport,
};
return parameters;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,10 @@ function prettyError(errorInstance: unknown) {

/** @internal */
function preFormatFailure<Ts>(out: RunDetailsFailureProperty<Ts>, stringifyOne: (value: Ts) => string) {
const noErrorInMessage = out.runConfiguration.errorWithCause;
const messageErrorPart = noErrorInMessage
? ''
: `\nGot ${safeReplace(prettyError(out.errorInstance), /^Error: /, 'error: ')}`;
const includeErrorInReport = out.runConfiguration.includeErrorInReport;
const messageErrorPart = includeErrorInReport
? `\nGot ${safeReplace(prettyError(out.errorInstance), /^Error: /, 'error: ')}`
: '';
const message = `Property failed after ${out.numRuns} tests\n{ seed: ${out.seed}, path: "${
out.counterexamplePath
}", endOnFailure: true }\nCounterexample: ${stringifyOne(out.counterexample)}\nShrunk ${
Expand Down Expand Up @@ -279,7 +279,7 @@ async function asyncDefaultReportMessage<Ts>(out: RunDetails<Ts>): Promise<strin

/** @internal */
function buildError<Ts>(errorMessage: string | undefined, out: RunDetails<Ts> & { failed: true }) {
if (!out.runConfiguration.errorWithCause) {
if (out.runConfiguration.includeErrorInReport) {
throw new Error(errorMessage);
}
const ErrorWithCause: new (message: string | undefined, options: { cause: unknown }) => Error = Error;
Expand Down
12 changes: 6 additions & 6 deletions packages/fast-check/test/e2e/NoRegressionStack.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ describe(`NoRegressionStack`, () => {
fc.property(fc.nat(), fc.nat(), (a, b) => {
return a >= b;
}),
settings,
{ ...settings, includeErrorInReport: true },
),
),
).toThrowErrorMatchingSnapshot();
Expand All @@ -25,7 +25,7 @@ describe(`NoRegressionStack`, () => {
fc.property(fc.nat(), fc.nat(), (a, b) => {
return a >= b;
}),
{ ...settings, errorWithCause: true },
settings,
),
),
).toThrowErrorMatchingSnapshot();
Expand All @@ -39,7 +39,7 @@ describe(`NoRegressionStack`, () => {
throw new Error('a must be >= b');
}
}),
settings,
{ ...settings, includeErrorInReport: true },
),
),
).toThrowErrorMatchingSnapshot();
Expand All @@ -54,7 +54,7 @@ describe(`NoRegressionStack`, () => {
throw new Error('a must be >= b');
}
}),
{ ...settings, errorWithCause: true },
settings,
),
),
).toThrowErrorMatchingSnapshot();
Expand All @@ -67,7 +67,7 @@ describe(`NoRegressionStack`, () => {
fc.property(fc.nat(), (v) => {
(v as any)();
}),
settings,
{ ...settings, includeErrorInReport: true },
),
),
).toThrowErrorMatchingSnapshot();
Expand All @@ -80,7 +80,7 @@ describe(`NoRegressionStack`, () => {
fc.property(fc.nat(), (v) => {
(v as any)();
}),
{ ...settings, errorWithCause: true },
settings,
),
),
).toThrowErrorMatchingSnapshot();
Expand Down
Loading

0 comments on commit 9ee8387

Please sign in to comment.