From 76fa891b88f85bb975ac69c23e620c915e71bf84 Mon Sep 17 00:00:00 2001 From: JiaLiPassion Date: Tue, 14 Mar 2017 02:56:37 +0900 Subject: [PATCH] feat(error): remove zone internal stack frames in error.stack (#632) * feat(error): remove all zone related stack frames from error.stack * fix(error): add more handling for IE --- lib/zone.ts | 307 ++++++++++++++++++++++++++++++++++---- test/common/Error.spec.ts | 199 ++++++++++++++++++++++-- test/node/fs.spec.ts | 4 +- 3 files changed, 469 insertions(+), 41 deletions(-) diff --git a/lib/zone.ts b/lib/zone.ts index aaeb47592..c3d40ca15 100644 --- a/lib/zone.ts +++ b/lib/zone.ts @@ -1571,10 +1571,10 @@ const Zone: ZoneType = (function(global: any) { // Store the frames which should be removed from the stack frames const blackListedStackFrames: {[frame: string]: FrameType} = {}; // We must find the frame where Error was created, otherwise we assume we don't understand stack - let zoneAwareFrame: string; + // the frame will be an array, because Error with new or without new will + // have different stack frames. + let zoneAwareErrorStartFrames: string[] = []; global.Error = ZoneAwareError; - // How should the stack frames be parsed. - let frameParserStrategy = null; const stackRewrite = 'stackRewrite'; // fix #595, create property descriptor @@ -1657,6 +1657,8 @@ const Zone: ZoneType = (function(global: any) { // in NativeError createProperty(props, 'originalStack'); createProperty(props, 'zoneAwareStack'); + // in IE, stack is not in prototype + createProperty(props, 'stack'); // define toString, toSource as method property createMethodProperty(props, 'toString'); @@ -1691,50 +1693,52 @@ const Zone: ZoneType = (function(global: any) { return newProps; }; - /** - * This is ZoneAwareError which processes the stack frame and cleans up extra frames as well as - * adds zone information to it. - */ - function ZoneAwareError() { - // make sure we have a valid this - // if this is undefined(call Error without new) or this is global - // or this is some other objects, we should force to create a - // valid ZoneAwareError by call Object.create() - if (!(this instanceof ZoneAwareError)) { - return ZoneAwareError.apply(Object.create(ZoneAwareError.prototype), arguments); - } - // Create an Error. - let error: Error = NativeError.apply(this, arguments); - this[__symbol__('error')] = error; + // some functions are not easily to be detected here, + // for example Timeout.ZoneTask.invoke, if we want to detect those functions + // by detect zone, we have to run all patched APIs, it is too risky + // so for those functions, just check whether the stack contains the string or not. + const otherZoneAwareFunctionNames = [ + 'ZoneTask.invoke', 'ZoneAware', 'getStacktraceWithUncaughtError', 'new LongStackTrace', + 'long-stack-trace' + ]; + function attachZoneAndRemoveInternalZoneFrames(error: any) { // Save original stack trace error.originalStack = error.stack; - // Process the stack trace and rewrite the frames. if ((ZoneAwareError as any)[stackRewrite] && error.originalStack) { let frames: string[] = error.originalStack.split('\n'); let zoneFrame = _currentZoneFrame; let i = 0; // Find the first frame - while (frames[i] !== zoneAwareFrame && i < frames.length) { + while (i < frames.length && + zoneAwareErrorStartFrames.filter(zf => zf.trim() === frames[i].trim()).length === 0) { i++; } for (; i < frames.length && zoneFrame; i++) { - let frame = frames[i]; - if (frame.trim()) { + // trim here because blackListedStackFrames store the trimmed frames + let frame = frames[i].trim(); + if (frame) { let frameType = blackListedStackFrames.hasOwnProperty(frame) && blackListedStackFrames[frame]; if (frameType === FrameType.blackList) { frames.splice(i, 1); i--; + } else if ( + otherZoneAwareFunctionNames + .filter(f => frame.toLowerCase().indexOf(f.toLowerCase()) !== -1) + .length > 0) { + frames.splice(i, 1); + i--; } else if (frameType === FrameType.transition) { if (zoneFrame.parent) { // This is the special frame where zone changed. Print and process it accordingly - frames[i] += ` [${zoneFrame.parent.zone.name} => ${zoneFrame.zone.name}]`; zoneFrame = zoneFrame.parent; } else { zoneFrame = null; } + frames.splice(i, 1); + i--; } else { frames[i] += ` [${zoneFrame.zone.name}]`; } @@ -1742,9 +1746,41 @@ const Zone: ZoneType = (function(global: any) { } error.stack = error.zoneAwareStack = frames.join('\n'); } + } + + /** + * This is ZoneAwareError which processes the stack frame and cleans up extra frames as well as + * adds zone information to it. + */ + function ZoneAwareError() { + // make sure we have a valid this + // if this is undefined(call Error without new) or this is global + // or this is some other objects, we should force to create a + // valid ZoneAwareError by call Object.create() + if (!(this instanceof ZoneAwareError)) { + return ZoneAwareError.apply(Object.create(ZoneAwareError.prototype), arguments); + } + // Create an Error. + let error: Error = NativeError.apply(this, arguments); + if (!error.stack) { + // in IE, the error.stack will be undefined + // when error was constructed, it will only + // be available when throw + try { + throw error; + } catch (err) { + error = err; + } + } + this[__symbol__('error')] = error; + // 1. attach zone information to stack frame + // 2. remove zone internal stack frames + attachZoneAndRemoveInternalZoneFrames(error); + // use defineProperties here instead of copy property value // because of issue #595 which will break angular2. - Object.defineProperties(this, getErrorPropertiesForPrototype(Object.getPrototypeOf(this))); + const props = getErrorPropertiesForPrototype(Object.getPrototypeOf(this)); + Object.defineProperties(this, props); return this; } @@ -1823,7 +1859,7 @@ const Zone: ZoneType = (function(global: any) { } }); - // Now we need to populet the `blacklistedStackFrames` as well as find the + // Now we need to populate the `blacklistedStackFrames` as well as find the // run/runGuraded/runTask frames. This is done by creating a detect zone and then threading // the execution through all of the above methods so that we can look at the stack trace and // find the frames of interest. @@ -1846,8 +1882,8 @@ const Zone: ZoneType = (function(global: any) { // This check makes sure that we don't filter frames on name only (must have // linenumber) if (/:\d+:\d+/.test(frame)) { - // Get rid of the path so that we don't accidintely find function name in path. - // In chrome the seperator is `(` and `@` in FF and safari + // Get rid of the path so that we don't accidentally find function name in path. + // In chrome the separator is `(` and `@` in FF and safari // Chrome: at Zone.run (zone.js:100) // Chrome: at Zone.run (http://localhost:9876/base/build/lib/zone.js:100:24) // FireFox: Zone.prototype.run@http://localhost:9876/base/build/lib/zone.js:101:24 @@ -1855,7 +1891,10 @@ const Zone: ZoneType = (function(global: any) { let fnName: string = frame.split('(')[0].split('@')[0]; let frameType = FrameType.transition; if (fnName.indexOf('ZoneAwareError') !== -1) { - zoneAwareFrame = frame; + // we found the ZoneAwareError start frame + // the frame will be different when call Error(...) + // and new Error(...), so we store them both + zoneAwareErrorStartFrames.push(frame); } if (fnName.indexOf('runGuarded') !== -1) { runGuardedFrame = true; @@ -1866,7 +1905,7 @@ const Zone: ZoneType = (function(global: any) { } else { frameType = FrameType.blackList; } - blackListedStackFrames[frame] = frameType; + blackListedStackFrames[frame.trim()] = frameType; // Once we find all of the frames we can stop looking. if (runFrame && runGuardedFrame && runTaskFrame) { (ZoneAwareError as any)[stackRewrite] = true; @@ -1880,15 +1919,223 @@ const Zone: ZoneType = (function(global: any) { }) as Zone; // carefully constructor a stack frame which contains all of the frames of interest which // need to be detected and blacklisted. + + // use this method to handle + // 1. IE issue, the error.stack can only be not undefined after throw + // 2. handle Error(...) without new options + const throwError = (message: string, withNew: boolean = true) => { + let error; + try { + if (withNew) { + throw new Error(message); + } else { + throw Error(message); + } + } catch (err) { + error = err; + } + return error; + }; + + const nativeStackTraceLimit = NativeError.stackTraceLimit; + // in some system/browser, some additional stack frames + // will be generated (such as inline function) + // so the the stack frame to check ZoneAwareError Start + // maybe ignored because the frame's number will exceed + // stackTraceLimit, so we just set stackTraceLimit to 100 + // and reset after all detect work is done. + NativeError.stackTraceLimit = 100; let detectRunFn = () => { detectZone.run(() => { detectZone.runGuarded(() => { - throw new Error('blacklistStackFrames'); + throw throwError('blacklistStackFrames'); + }); + }); + }; + + let detectRunWithoutNewFn = () => { + detectZone.run(() => { + detectZone.runGuarded(() => { + throw throwError('blacklistStackFrames', false); }); }); }; // Cause the error to extract the stack frames. detectZone.runTask(detectZone.scheduleMacroTask('detect', detectRunFn, null, () => null, null)); + detectZone.runTask( + detectZone.scheduleMacroTask('detect', detectRunWithoutNewFn, null, () => null, null)); + + function handleDetectError(error: Error) { + let frames = error.stack ? error.stack.split(/\n/) : []; + while (frames.length) { + let frame = frames.shift(); + // On safari it is possible to have stack frame with no line number. + // This check makes sure that we don't filter frames on name only (must have + // linenumber) + const trimmedFrame = frame.trim().split('[')[0].trim(); + if (/:\d+:\d+/.test(trimmedFrame) && !blackListedStackFrames.hasOwnProperty(trimmedFrame)) { + blackListedStackFrames[trimmedFrame] = FrameType.blackList; + } + + // when we found runGuarded or runTask, we should stop + // otherwise we will store some stack frames like + // module.load, require and something like that + let fnName: string = frame.split('(')[0].split('@')[0]; + if (fnName.indexOf('runGuarded') !== -1) { + break; + } else if (fnName.indexOf('runTask') !== -1) { + break; + } + } + } + + const detectEmptyZone = Zone.root.fork({ + name: 'detectEmptyZone', + onHandleError(parentDelegate, currentZone, targetZone, error) { + parentDelegate.handleError(targetZone, error); + handleDetectError(error); + return false; + } + }); + + const detectZoneWithCallbacks = Zone.root.fork({ + name: 'detectCallbackZone', + onFork: (parentDelegate, currentZone, targetZone, zoneSpec) => { + // we need to generate Error with or without new + handleDetectError(throwError('onFork')); + handleDetectError(throwError('onFork', false)); + return parentDelegate.fork(targetZone, zoneSpec); + }, + onIntercept: (parentDelegate, currentZone, targetZone, delegate, source) => { + handleDetectError(throwError('onIntercept')); + handleDetectError(throwError('onIntercept', false)); + return parentDelegate.intercept(targetZone, delegate, source); + }, + onInvoke: + (parentZoneDelegate, currentZone, targetZone, delegate, applyThis, applyArgs, source) => { + handleDetectError(throwError('onInvoke')); + handleDetectError(throwError('onInvoke', false)); + return parentZoneDelegate.invoke(targetZone, delegate, applyThis, applyArgs, source); + }, + onScheduleTask: (parentZoneDelegate, currentZone, targetZone, task) => { + handleDetectError(throwError('onScheduleTask')); + handleDetectError(throwError('onScheduleTask', false)); + return parentZoneDelegate.scheduleTask(targetZone, task); + }, + onInvokeTask: (parentZoneDelegate, currentZone, targetZone, task, applyThis, applyArgs) => { + handleDetectError(throwError('onInvokeTask')); + handleDetectError(throwError('onInvokeTask', false)); + return parentZoneDelegate.invokeTask(targetZone, task, applyThis, applyArgs); + }, + onCancelTask: (parentZoneDelegate, currentZone, targetZone, task) => { + handleDetectError(throwError('onCancelTask')); + handleDetectError(throwError('onCancelTask', false)); + return parentZoneDelegate.cancelTask(targetZone, task); + }, + + onHasTask: (delegate, current, target, hasTaskState) => { + handleDetectError(throwError('onHasTask')); + handleDetectError(throwError('onHasTask', false)); + return delegate.hasTask(target, hasTaskState); + }, + + onHandleError(parentDelegate, currentZone, targetZone, error) { + parentDelegate.handleError(targetZone, error); + handleDetectError(error); + return false; + } + }); + + let detectFn = () => { + throw throwError('zoneAwareFrames'); + }; + + let detectWithoutNewFn = () => { + throw throwError('zoneAwareFrames', false); + }; + + let detectPromiseFn = () => { + new Promise((resolve, reject) => { + reject(throwError('zoneAwareFrames')); + }); + }; + + let detectPromiseWithoutNewFn = () => { + new Promise((resolve, reject) => { + reject(throwError('zoneAwareFrames', false)); + }); + }; + + let detectPromiseCaughtFn = () => { + const p = new Promise((resolve, reject) => { + reject(throwError('zoneAwareFrames')); + }); + p.catch(err => { + throw err; + }); + }; + + let detectPromiseCaughtWithoutNewFn = () => { + const p = new Promise((resolve, reject) => { + reject(throwError('zoneAwareFrames', false)); + }); + p.catch(err => { + throw err; + }); + }; + + // Cause the error to extract the stack frames. + detectEmptyZone.runTask( + detectEmptyZone.scheduleEventTask('detect', detectFn, null, () => null, null)); + detectZoneWithCallbacks.runTask( + detectZoneWithCallbacks.scheduleEventTask('detect', detectFn, null, () => null, null)); + detectEmptyZone.runTask( + detectEmptyZone.scheduleMacroTask('detect', detectFn, null, () => null, null)); + detectZoneWithCallbacks.runTask( + detectZoneWithCallbacks.scheduleMacroTask('detect', detectFn, null, () => null, null)); + detectEmptyZone.runTask(detectEmptyZone.scheduleMicroTask('detect', detectFn, null, () => null)); + detectZoneWithCallbacks.runTask( + detectZoneWithCallbacks.scheduleMicroTask('detect', detectFn, null, () => null)); + + detectEmptyZone.runGuarded(() => { + detectEmptyZone.run(detectFn); + }); + detectZoneWithCallbacks.runGuarded(() => { + detectEmptyZone.run(detectFn); + }); + + detectEmptyZone.runTask( + detectEmptyZone.scheduleEventTask('detect', detectWithoutNewFn, null, () => null, null)); + detectZoneWithCallbacks.runTask(detectZoneWithCallbacks.scheduleEventTask( + 'detect', detectWithoutNewFn, null, () => null, null)); + detectEmptyZone.runTask( + detectEmptyZone.scheduleMacroTask('detect', detectWithoutNewFn, null, () => null, null)); + detectZoneWithCallbacks.runTask(detectZoneWithCallbacks.scheduleMacroTask( + 'detect', detectWithoutNewFn, null, () => null, null)); + detectEmptyZone.runTask( + detectEmptyZone.scheduleMicroTask('detect', detectWithoutNewFn, null, () => null)); + detectZoneWithCallbacks.runTask( + detectZoneWithCallbacks.scheduleMicroTask('detect', detectWithoutNewFn, null, () => null)); + + detectEmptyZone.runGuarded(() => { + detectEmptyZone.run(detectWithoutNewFn); + }); + detectZoneWithCallbacks.runGuarded(() => { + detectEmptyZone.run(detectWithoutNewFn); + }); + + detectEmptyZone.runGuarded(detectPromiseFn); + detectZoneWithCallbacks.runGuarded(detectPromiseFn); + + detectEmptyZone.runGuarded(detectPromiseWithoutNewFn); + detectZoneWithCallbacks.runGuarded(detectPromiseWithoutNewFn); + + detectEmptyZone.runGuarded(detectPromiseCaughtFn); + detectZoneWithCallbacks.runGuarded(detectPromiseCaughtFn); + + detectEmptyZone.runGuarded(detectPromiseCaughtWithoutNewFn); + detectZoneWithCallbacks.runGuarded(detectPromiseCaughtWithoutNewFn); + NativeError.stackTraceLimit = nativeStackTraceLimit; return global['Zone'] = Zone; })(typeof window === 'object' && window || typeof self === 'object' && self || global); diff --git a/test/common/Error.spec.ts b/test/common/Error.spec.ts index 8614cb5d4..17f33cd86 100644 --- a/test/common/Error.spec.ts +++ b/test/common/Error.spec.ts @@ -169,32 +169,60 @@ describe('ZoneAwareError', () => { expect(spy).toHaveBeenCalledWith('test'); }); + it('should always have stack property even without throw', () => { + // in IE, the stack will be undefined without throw + // in ZoneAwareError, we will make stack always be + // there event without throw + const error = new Error('test'); + const errorWithoutNew = Error('test'); + expect(error.stack.split('\n').length > 0).toBeTruthy(); + expect(errorWithoutNew.stack.split('\n').length > 0).toBeTruthy(); + }); + it('should show zone names in stack frames and remove extra frames', () => { - const rootZone = getRootZone(); + const rootZone = Zone.root; const innerZone = rootZone.fork({name: 'InnerZone'}); rootZone.run(testFn); function testFn() { let outside: Error; let inside: Error; + let outsideWithoutNew: Error; + let insideWithoutNew: Error; try { throw new Error('Outside'); } catch (e) { outside = e; } + try { + throw Error('Outside'); + } catch (e) { + outsideWithoutNew = e; + } innerZone.run(function insideRun() { try { throw new Error('Inside'); } catch (e) { inside = e; } + try { + throw Error('Inside'); + } catch (e) { + insideWithoutNew = e; + } }); expect(outside.stack).toEqual(outside.zoneAwareStack); + expect(outsideWithoutNew.stack).toEqual(outsideWithoutNew.zoneAwareStack); expect(inside.stack).toEqual(inside.zoneAwareStack); + expect(insideWithoutNew.stack).toEqual(insideWithoutNew.zoneAwareStack); expect(typeof inside.originalStack).toEqual('string'); + expect(typeof insideWithoutNew.originalStack).toEqual('string'); const outsideFrames = outside.stack.split(/\n/); const insideFrames = inside.stack.split(/\n/); + const outsideWithoutNewFrames = outsideWithoutNew.stack.split(/\n/); + const insideWithoutNewFrames = insideWithoutNew.stack.split(/\n/); + // throw away first line if it contains the error if (/Outside/.test(outsideFrames[0])) { outsideFrames.shift(); @@ -202,6 +230,20 @@ describe('ZoneAwareError', () => { if (/new Error/.test(outsideFrames[0])) { outsideFrames.shift(); } + + if (/Outside/.test(outsideWithoutNewFrames[0])) { + outsideWithoutNewFrames.shift(); + } + if (/new Error/.test(outsideWithoutNewFrames[0])) { + outsideWithoutNewFrames.shift(); + } + if (/Error.ZoneAwareError/.test(outsideWithoutNewFrames[0])) { + outsideWithoutNewFrames.shift(); + } + if (/ZoneAwareError/.test(outsideWithoutNewFrames[0])) { + outsideWithoutNewFrames.shift(); + } + if (/Inside/.test(insideFrames[0])) { insideFrames.shift(); } @@ -209,19 +251,158 @@ describe('ZoneAwareError', () => { insideFrames.shift(); } + if (/Inside/.test(insideWithoutNewFrames[0])) { + insideWithoutNewFrames.shift(); + } + if (/new Error/.test(insideWithoutNewFrames[0])) { + insideWithoutNewFrames.shift(); + } + if (/Error.ZoneAwareError/.test(insideWithoutNewFrames[0])) { + insideWithoutNewFrames.shift(); + } + if (/ZoneAwareError/.test(insideWithoutNewFrames[0])) { + insideWithoutNewFrames.shift(); + } + expect(outsideFrames[0]).toMatch(/testFn.*[]/); expect(insideFrames[0]).toMatch(/insideRun.*[InnerZone]]/); - expect(insideFrames[1]).toMatch(/run.*[ => InnerZone]]/); - expect(insideFrames[2]).toMatch(/testFn.*[]]/); + expect(insideFrames[1]).toMatch(/testFn.*[]]/); + + expect(outsideWithoutNewFrames[0]).toMatch(/testFn.*[]/); + + expect(insideWithoutNewFrames[0]).toMatch(/insideRun.*[InnerZone]]/); + expect(insideWithoutNewFrames[1]).toMatch(/testFn.*[]]/); } }); }); -function getRootZone() { - let zone = Zone.current; - while (zone.parent) { - zone = zone.parent; +const zoneAwareFrames = [ + 'Zone.run', 'Zone.runGuarded', 'Zone.scheduleEventTask', 'Zone.scheduleMicroTask', + 'Zone.scheduleMacroTask', 'Zone.runTask', 'ZoneDelegate.scheduleTask', 'ZoneDelegate.invokeTask', + 'ZoneTask.invoke', 'zoneAwareAddListener', 'drainMicroTaskQueue', 'new LongStackTrace', + 'getStacktraceWithUncaughtError' +]; + +function assertStackDoesNotContainZoneFrames(err: Error) { + const frames = err.stack.split('\n'); + for (let i = 0; i < frames.length; i++) { + expect(zoneAwareFrames.filter(f => frames[i].indexOf(f) !== -1)).toEqual([]); } - return zone; -} \ No newline at end of file +}; + +const errorZoneSpec = { + name: 'errorZone', + done: <() => void>null, + onHandleError: + (parentDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, error: Error) => { + assertStackDoesNotContainZoneFrames(error); + setTimeout(() => { + errorZoneSpec.done && errorZoneSpec.done(); + }, 0); + return false; + } +}; + +const errorZone = Zone.root.fork(errorZoneSpec); + +const assertStackDoesNotContainZoneFramesTest = function(testFn: Function) { + return function(done: () => void) { + errorZoneSpec.done = done; + errorZone.run(testFn); + }; +}; + +describe('Error stack', () => { + it('Error with new which occurs in setTimeout callback should not have zone frames visible', + assertStackDoesNotContainZoneFramesTest(() => { + setTimeout(() => { + throw new Error('timeout test error'); + }, 10); + })); + + it('Error without new which occurs in setTimeout callback should not have zone frames visible', + assertStackDoesNotContainZoneFramesTest(() => { + setTimeout(() => { + throw Error('test error'); + }, 10); + })); + + it('Error with new which cause by promise rejection should not have zone frames visible', + (done) => { + const p = new Promise((resolve, reject) => { + reject(new Error('test error')); + }); + p.catch(err => { + assertStackDoesNotContainZoneFrames(err); + done(); + }); + }); + + it('Error without new which cause by promise rejection should not have zone frames visible', + (done) => { + const p = new Promise((resolve, reject) => { + reject(Error('test error')); + }); + p.catch(err => { + assertStackDoesNotContainZoneFrames(err); + done(); + }); + }); + + it('Error with new which occurs in eventTask callback should not have zone frames visible', + assertStackDoesNotContainZoneFramesTest(() => { + const task = Zone.current.scheduleEventTask('errorEvent', () => { + throw new Error('test error'); + }, null, () => null, null); + task.invoke(); + })); + + it('Error without new which occurs in eventTask callback should not have zone frames visible', + assertStackDoesNotContainZoneFramesTest(() => { + const task = Zone.current.scheduleEventTask('errorEvent', () => { + throw Error('test error'); + }, null, () => null, null); + task.invoke(); + })); + + it('Error with new which occurs in longStackTraceZone should not have zone frames and longStackTraceZone frames visible', + assertStackDoesNotContainZoneFramesTest(() => { + const task = Zone.current.fork((Zone as any)['longStackTraceZoneSpec']) + .scheduleEventTask('errorEvent', () => { + throw new Error('test error'); + }, null, () => null, null); + task.invoke(); + })); + + it('Error without new which occurs in longStackTraceZone should not have zone frames and longStackTraceZone frames visible', + assertStackDoesNotContainZoneFramesTest(() => { + const task = Zone.current.fork((Zone as any)['longStackTraceZoneSpec']) + .scheduleEventTask('errorEvent', () => { + throw Error('test error'); + }, null, () => null, null); + task.invoke(); + })); + + it('stack frames of the callback in user customized zoneSpec should be kept', + assertStackDoesNotContainZoneFramesTest(() => { + const task = Zone.current.fork((Zone as any)['longStackTraceZoneSpec']) + .fork({ + name: 'customZone', + onScheduleTask: (parentDelegate, currentZone, targetZone, task) => { + return parentDelegate.scheduleTask(targetZone, task); + }, + onHandleError: (parentDelegate, currentZone, targetZone, error) => { + parentDelegate.handleError(targetZone, error); + const containsCustomZoneSpecStackTrace = + error.stack.indexOf('onScheduleTask') !== -1; + expect(containsCustomZoneSpecStackTrace).toBeTruthy(); + return false; + } + }) + .scheduleEventTask('errorEvent', () => { + throw new Error('test error'); + }, null, () => null, null); + task.invoke(); + })); +}); diff --git a/test/node/fs.spec.ts b/test/node/fs.spec.ts index 99b9a026c..e52d317ed 100644 --- a/test/node/fs.spec.ts +++ b/test/node/fs.spec.ts @@ -63,7 +63,7 @@ describe('nodejs file system', () => { done(); }); }); - writeFile('testfile', 'test new content'); + writeFile('testfile', 'test new content', () => {}); }); }); }); @@ -83,7 +83,7 @@ describe('nodejs file system', () => { done(); }); }); - writeFile('testfile', 'test new content'); + writeFile('testfile', 'test new content', () => {}); }); }); });