Skip to content
This repository has been archived by the owner on Feb 26, 2024. It is now read-only.

Commit

Permalink
feat(error): add a helper method to get non zone aware stack trace
Browse files Browse the repository at this point in the history
  • Loading branch information
JiaLiPassion committed Feb 25, 2017
1 parent 14c7a6f commit 4df13d3
Show file tree
Hide file tree
Showing 5 changed files with 253 additions and 13 deletions.
1 change: 1 addition & 0 deletions karma-dist.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ module.exports = function (config) {
config.files.push('dist/sync-test.js');
config.files.push('dist/task-tracking.js');
config.files.push('dist/wtf.js');
config.files.push('dist/zone-helper.js');
config.files.push('build/test/main.js');
};
4 changes: 2 additions & 2 deletions lib/zone-spec/long-stack-trace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ function getStacktraceWithCaughtError(): Error {
// Some implementations of exception handling don't create a stack trace if the exception
// isn't thrown, however it's faster not to actually throw the exception.
const error = getStacktraceWithUncaughtError();
const coughtError = getStacktraceWithCaughtError();
const caughtError = getStacktraceWithCaughtError();
const getStacktrace = error.stack ?
getStacktraceWithUncaughtError :
(coughtError.stack ? getStacktraceWithCaughtError : getStacktraceWithUncaughtError);
(caughtError.stack ? getStacktraceWithCaughtError : getStacktraceWithUncaughtError);

function getFrames(error: Error): string[] {
return error.stack ? error.stack.split(NEWLINE) : [];
Expand Down
153 changes: 151 additions & 2 deletions lib/zone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1814,8 +1814,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
Expand Down Expand Up @@ -1858,5 +1858,154 @@ const Zone: ZoneType = (function(global: any) {
// Cause the error to extract the stack frames.
detectZone.runTask(detectZone.scheduleMacroTask('detect', detectRunFn, null, () => null, null));

// detect zone aware frames for output simple stack
const zoneAwareStackFrames = {};

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)
if (/:\d+:\d+/.test(frame)) {
const f = frame.split(' [')[0];
zoneAwareStackFrames[f] = f;
}
}
}

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) => {
handleDetectError(Error('onFork'));
return parentDelegate.fork(targetZone, zoneSpec);
},
onIntercept: (parentDelegate, currentZone, targetZone, delegate, source) => {
handleDetectError(Error('onIntercept'));
return parentDelegate.intercept(targetZone, delegate, source);
},
onInvoke:
(parentZoneDelegate, currentZone, targetZone, delegate, applyThis, applyArgs, source) => {
handleDetectError(Error('onInvoke'));
return parentZoneDelegate.invoke(targetZone, delegate, applyThis, applyArgs, source);
},
onScheduleTask: (parentZoneDelegate, currentZone, targetZone, task) => {
handleDetectError(Error('onScheduleTask'));
return parentZoneDelegate.scheduleTask(targetZone, task);
},
onInvokeTask: (parentZoneDelegate, currentZone, targetZone, task, applyThis, applyArgs) => {
handleDetectError(Error('onInvokeTask'));
return parentZoneDelegate.invokeTask(targetZone, task, applyThis, applyArgs);
},
onCancelTask: (parentZoneDelegate, currentZone, targetZone, task) => {
handleDetectError(Error('onCancelTask'));
return parentZoneDelegate.cancelTask(targetZone, task);
},

onHasTask: (delegate, current, target, hasTaskState) => {
handleDetectError(Error('onHasTask'));
return delegate.hasTask(target, hasTaskState);
},

onHandleError(parentDelegate, currentZone, targetZone, error) {
parentDelegate.handleError(targetZone, error);
handleDetectError(error);
return false;
}
});

let detectFn = () => {
throw new Error('zoneAwareFrames');
};

let detectPromiseFn = () => {
new Promise((resolve, reject) => {
reject(new Error('zoneAwareFrames'));
});
};

let detectPromiseCaughtFn = () => {
const p = new Promise((resolve, reject) => {
reject(new Error('zoneAwareFrames'));
});
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.runGuarded(detectPromiseFn);
detectZoneWithCallbacks.runGuarded(detectPromiseFn);

detectEmptyZone.runGuarded(detectPromiseCaughtFn);
detectZoneWithCallbacks.runGuarded(detectPromiseCaughtFn);

// 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'
];

Object.defineProperty(ZoneAwareError, 'getNonZoneAwareStack', {
value: function(err: Error) {
if (err.stack) {
let frames = err.stack.split('\n');
const simplifiedFrames: string[] = [];
for (let i = 0; i < frames.length; i++) {
const frame = frames[i].split(' [')[0];
const frameWithoutZone = frame.split(' [')[0];
if (zoneAwareStackFrames.hasOwnProperty(frameWithoutZone) &&
zoneAwareStackFrames[frameWithoutZone]) {
frames.splice(i, 1);
i--;
} else if (
otherZoneAwareFunctionNames
.filter(f => frame.toLowerCase().indexOf(f.toLowerCase()) !== -1)
.length > 0) {
frames.splice(i, 1);
i--;
} else {
// we still need the zone information on each stack frame
simplifiedFrames.push(frames[i]);
}
}
return simplifiedFrames.join('\n');
}
return err.stack;
}
});

return global['Zone'] = Zone;
})(typeof window === 'object' && window || typeof self === 'object' && self || global);
104 changes: 97 additions & 7 deletions test/common/Error.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ describe('ZoneAwareError', () => {
});

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);
Expand Down Expand Up @@ -206,10 +206,100 @@ describe('ZoneAwareError', () => {
});
});

function getRootZone() {
let zone = Zone.current;
while (zone.parent) {
zone = zone.parent;
const getNonZoneAwareStack: (err: Error) => string = Error['getNonZoneAwareStack'];
const zoneAwareFrames = [
'Zone.run', 'Zone.runGuarded', 'Zone.scheduleEventTask', 'Zone.scheduleMicroTask',
'Zone.scheduleMacroTask', 'Zone.runTask', 'ZoneDelegate.scheduleTask', 'ZoneDelegate.invokeTask',
'ZoneTask.invoke', 'zoneAwareAddListener', 'drainMicroTaskQueue', 'LongStackTrace',
'getStacktraceWithUncaughtError'
];

function assertStackDoesNotContainZoneFrames(err: Error) {
const simpleStack = getNonZoneAwareStack(err);
if (!simpleStack) {
return;
}
const frames = simpleStack.split('\n');
for (let i = 0; i < frames.length; i++) {
expect(zoneAwareFrames.filter(f => frames[i].indexOf(f) !== -1)).toEqual([]);
}
};

const errorZoneSpec = {
name: 'errorZone',
done: null,
onHandleError: (parentDelegate, currentZone, targetZone, error) => {
assertStackDoesNotContainZoneFrames(error);
setTimeout(() => {
errorZoneSpec.done && errorZoneSpec.done();
}, 0);
return false;
}
return zone;
}
};

const errorZone = Zone.root.fork(errorZoneSpec);

const assertStackDoesNotContainZoneFramesTest = function(testFn: Function) {
return function(done) {
errorZoneSpec.done = done;
errorZone.run(testFn);
};
};

describe('Error getSimpleStack', () => {
it('error which occurs in setTimeout callback should not have zone frames visible',
assertStackDoesNotContainZoneFramesTest(() => {
setTimeout(() => {
throw new Error('test error');
}, 10);
}));

it('error 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 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 which occurs in longStackTraceZone should not have zone frames and longStackTraceZone frames visible',
assertStackDoesNotContainZoneFramesTest(() => {
const task =
Zone.current.fork(Zone['longStackTraceZoneSpec']).scheduleEventTask('errorEvent', () => {
throw new 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['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 =
getNonZoneAwareStack(error).indexOf('onScheduleTask') !== -1;
expect(containsCustomZoneSpecStackTrace).toBeTruthy();
return false;
}
})
.scheduleEventTask('errorEvent', () => {
throw new Error('test error');
}, null, () => null, null);
task.invoke();
}));
});
4 changes: 2 additions & 2 deletions test/node/fs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ describe('nodejs file system', () => {
done();
});
});
writeFile('testfile', 'test new content');
writeFile('testfile', 'test new content', () => {});
});
});
});
Expand All @@ -81,7 +81,7 @@ describe('nodejs file system', () => {
done();
});
});
writeFile('testfile', 'test new content');
writeFile('testfile', 'test new content', () => {});
});
});
});
Expand Down

0 comments on commit 4df13d3

Please sign in to comment.