Skip to content

Commit

Permalink
fix: improve error serialization (#1921)
Browse files Browse the repository at this point in the history
  • Loading branch information
sheremet-va committed Aug 26, 2022
1 parent 88d5764 commit 5b190e8
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 4 deletions.
29 changes: 25 additions & 4 deletions packages/vitest/src/runtime/error.ts
Expand Up @@ -5,6 +5,14 @@ import { deepClone, getType } from '../utils'

const OBJECT_PROTO = Object.getPrototypeOf({})

function getUnserializableMessage(err: unknown) {
if (err instanceof Error)
return `<unserializable>: ${err.message}`
if (typeof err === 'string')
return `<unserializable>: ${err}`
return '<unserializable>'
}

// https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
export function serializeError(val: any, seen = new WeakMap()): any {
if (!val || typeof val === 'string')
Expand All @@ -13,7 +21,7 @@ export function serializeError(val: any, seen = new WeakMap()): any {
return `Function<${val.name}>`
if (typeof val !== 'object')
return val
if (val instanceof Promise || 'then' in val || (val.constructor && val.constructor.prototype === 'AsyncFunction'))
if (val instanceof Promise || (val.constructor && val.constructor.prototype === 'AsyncFunction'))
return 'Promise'
if (typeof Element !== 'undefined' && val instanceof Element)
return val.tagName
Expand All @@ -27,7 +35,12 @@ export function serializeError(val: any, seen = new WeakMap()): any {
const clone: any[] = new Array(val.length)
seen.set(val, clone)
val.forEach((e, i) => {
clone[i] = serializeError(e, seen)
try {
clone[i] = serializeError(e, seen)
}
catch (err) {
clone[i] = getUnserializableMessage(err)
}
})
return clone
}
Expand All @@ -40,8 +53,16 @@ export function serializeError(val: any, seen = new WeakMap()): any {
let obj = val
while (obj && obj !== OBJECT_PROTO) {
Object.getOwnPropertyNames(obj).forEach((key) => {
if (!(key in clone))
if ((key in clone))
return
try {
clone[key] = serializeError(obj[key], seen)
}
catch (err) {
// delete in case it has a setter from prototype that might throw
delete clone[key]
clone[key] = getUnserializableMessage(err)
}
})
obj = Object.getPrototypeOf(obj)
}
Expand All @@ -54,7 +75,7 @@ function normalizeErrorMessage(message: string) {
}

export function processError(err: any) {
if (!err)
if (!err || typeof err !== 'object')
return err
// stack is not serialized in worker communication
// we stringify it first
Expand Down
32 changes: 32 additions & 0 deletions test/core/test/serialize.test.ts
Expand Up @@ -93,4 +93,36 @@ describe('error serialize', () => {
toString: 'Function<toString>',
})
})

it('Should not fail on errored getters/setters', () => {
const error = new Error('test')
Object.defineProperty(error, 'unserializable', {
get() {
throw new Error('I am unserializable')
},
set() {
throw new Error('I am unserializable')
},
})
Object.defineProperty(error, 'array', {
value: [{
get name() {
throw new Error('name cannnot be accessed')
},
}],
})
expect(serializeError(error)).toEqual({
array: [
{
name: '<unserializable>: name cannnot be accessed',
},
],
constructor: 'Function<Error>',
message: 'test',
name: 'Error',
stack: expect.stringContaining('Error: test'),
toString: 'Function<toString>',
unserializable: '<unserializable>: I am unserializable',
})
})
})

0 comments on commit 5b190e8

Please sign in to comment.