Skip to content

Commit

Permalink
Improve Cause Rendering (Vitest) (#2747)
Browse files Browse the repository at this point in the history
Co-authored-by: Tim <hello@timsmart.co>
  • Loading branch information
mikearnaldi and tim-smart committed May 20, 2024
1 parent 963b4e7 commit 64c9414
Show file tree
Hide file tree
Showing 8 changed files with 78 additions and 13 deletions.
6 changes: 6 additions & 0 deletions .changeset/gorgeous-students-jog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"effect": minor
"@effect/vitest": minor
---

Improve causal rendering in vitest by rethrowing pretty errors
5 changes: 5 additions & 0 deletions .changeset/soft-moons-raise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@effect/vitest": patch
---

Throw plain error object derived from fiber failure
5 changes: 5 additions & 0 deletions .changeset/tough-buses-brush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"effect": patch
---

Consider Generator.next a cutpoint
21 changes: 15 additions & 6 deletions packages/effect/src/internal/cause.ts
Original file line number Diff line number Diff line change
Expand Up @@ -977,12 +977,25 @@ export const pretty = <E>(cause: Cause.Cause<E>): string => {
}

class PrettyError extends globalThis.Error implements Cause.PrettyError {
span: undefined | Span = undefined
constructor(originalError: unknown) {
const prevLimit = Error.stackTraceLimit
Error.stackTraceLimit = 0
super(prettyErrorMessage(originalError), { cause: originalError })
super(prettyErrorMessage(originalError))
Error.stackTraceLimit = prevLimit

this.name = originalError instanceof Error ? originalError.name : "Error"
if (typeof originalError === "object" && originalError !== null) {
if (spanSymbol in originalError) {
this.span = originalError[spanSymbol] as Span
}
Object.keys(originalError).forEach((key) => {
if (!(key in this)) {
// @ts-expect-error
this[key] = originalError[key]
}
})
}
this.stack = prettyErrorStack(
this.message,
originalError instanceof Error && originalError.stack
Expand All @@ -992,10 +1005,6 @@ class PrettyError extends globalThis.Error implements Cause.PrettyError {
)
}

get span(): Span | undefined {
return hasProperty(this.cause, spanSymbol) ? this.cause[spanSymbol] as Span : undefined
}

toJSON() {
const out: any = { message: this.message, stack: this.stack }
if (this.span) {
Expand Down Expand Up @@ -1048,7 +1057,7 @@ const prettyErrorStack = (message: string, stack: string, span?: Span | undefine
const lines = stack.split("\n")

for (let i = 1; i < lines.length; i++) {
if (lines[i].includes("effect_cutpoint")) {
if (lines[i].includes("effect_cutpoint") || lines[i].includes("Generator.next")) {
break
}
out.push(
Expand Down
2 changes: 1 addition & 1 deletion packages/effect/src/internal/fiberRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1335,7 +1335,7 @@ export class FiberRuntime<in out A, in out E = never> implements Fiber.RuntimeFi
internalCause.sequential(internalCause.die(e), internalCause.interrupt(FiberId.none))
)
} else {
cur = core.exitFailCause(internalCause.die(e))
cur = core.die(e)
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions packages/effect/src/internal/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,10 @@ class FiberFailureImpl extends Error implements Runtime.FiberFailure {
}

this.name = `(FiberFailure) ${this.name}`

if (this.message === undefined || this.message.length === 0) {
this.message = "An error has occurred"
}
}

toJSON(): unknown {
Expand All @@ -187,6 +191,7 @@ class FiberFailureImpl extends Error implements Runtime.FiberFailure {
cause: this[FiberFailureCauseId].toJSON()
}
}

toString(): string {
return "(FiberFailure) " + (this.stack ?? this.message)
}
Expand Down
17 changes: 17 additions & 0 deletions packages/effect/test/Effect/cause-rendering.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,4 +117,21 @@ describe("Effect", () => {
assert.include(error.stack, "span-123")
assert.include(error.stack, "cause-rendering.test.ts:110")
}))

it.effect("includes span name in stack", () =>
Effect.gen(function*() {
const fn = Effect.functionWithSpan({
options: (n) => ({ name: `fn-${n}` }),
body: (a: number) =>
Effect.sync(() => {
assert.strictEqual(a, 2)
})
})
const cause = yield* fn(0).pipe(
Effect.sandbox,
Effect.flip
)
const prettyErrors = Cause.prettyErrors(cause)
assert.include(prettyErrors[0].stack ?? "", "at fn-0 ")
}))
})
30 changes: 24 additions & 6 deletions packages/vitest/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
* @since 1.0.0
*/
import type { Tester, TesterContext } from "@vitest/expect"
import * as Cause from "effect/Cause"
import * as Duration from "effect/Duration"
import * as Effect from "effect/Effect"
import * as Equal from "effect/Equal"
import * as Exit from "effect/Exit"
import { pipe } from "effect/Function"
import * as Layer from "effect/Layer"
import * as Logger from "effect/Logger"
Expand All @@ -16,6 +18,22 @@ import * as Utils from "effect/Utils"
import type { TestAPI } from "vitest"
import * as V from "vitest"

const runTest = <E, A>(effect: Effect.Effect<A, E>) =>
Effect.gen(function*() {
const exit: Exit.Exit<A, E> = yield* Effect.exit(effect)
if (Exit.isSuccess(exit)) {
return () => {}
} else {
const errors = Cause.prettyErrors(exit.cause)
for (let i = 1; i < errors.length; i++) {
yield* Effect.logError(errors[i])
}
return () => {
throw errors[0]
}
}
}).pipe(Effect.runPromise).then((f) => f())

/**
* @since 1.0.0
*/
Expand Down Expand Up @@ -58,7 +76,7 @@ export const effect = (() => {
pipe(
Effect.suspend(() => self(c)),
Effect.provide(TestEnv),
Effect.runPromise
runTest
),
timeout
)
Expand All @@ -74,7 +92,7 @@ export const effect = (() => {
pipe(
Effect.suspend(() => self(c)),
Effect.provide(TestEnv),
Effect.runPromise
runTest
),
timeout
),
Expand All @@ -89,7 +107,7 @@ export const effect = (() => {
pipe(
Effect.suspend(() => self(c)),
Effect.provide(TestEnv),
Effect.runPromise
runTest
),
timeout
)
Expand All @@ -109,7 +127,7 @@ export const live = <E, A>(
(c) =>
pipe(
Effect.suspend(() => self(c)),
Effect.runPromise
runTest
),
timeout
)
Expand Down Expand Up @@ -150,7 +168,7 @@ export const scoped = <E, A>(
Effect.suspend(() => self(c)),
Effect.scoped,
Effect.provide(TestEnv),
Effect.runPromise
runTest
),
timeout
)
Expand All @@ -169,7 +187,7 @@ export const scopedLive = <E, A>(
pipe(
Effect.suspend(() => self(c)),
Effect.scoped,
Effect.runPromise
runTest
),
timeout
)
Expand Down

0 comments on commit 64c9414

Please sign in to comment.