
committed by
Kara Erickson

parent
7b3bcc23af
commit
5eb7426216
425
packages/zone.js/test/common/Error.spec.ts
Normal file
425
packages/zone.js/test/common/Error.spec.ts
Normal file
@ -0,0 +1,425 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {isBrowser} from '../../lib/common/utils';
|
||||
import {isSafari, zoneSymbol} from '../test-util';
|
||||
|
||||
// simulate @angular/facade/src/error.ts
|
||||
class BaseError extends Error {
|
||||
/** @internal **/
|
||||
_nativeError: Error;
|
||||
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
const nativeError = new Error(message) as any as Error;
|
||||
this._nativeError = nativeError;
|
||||
}
|
||||
|
||||
get message() { return this._nativeError.message; }
|
||||
set message(message) { this._nativeError.message = message; }
|
||||
get name() { return this._nativeError.name; }
|
||||
get stack() { return (this._nativeError as any).stack; }
|
||||
set stack(value) { (this._nativeError as any).stack = value; }
|
||||
toString() { return this._nativeError.toString(); }
|
||||
}
|
||||
|
||||
class WrappedError extends BaseError {
|
||||
originalError: any;
|
||||
|
||||
constructor(message: string, error: any) {
|
||||
super(`${message} caused by: ${error instanceof Error ? error.message : error}`);
|
||||
this.originalError = error;
|
||||
}
|
||||
|
||||
get stack() {
|
||||
return ((this.originalError instanceof Error ? this.originalError : this._nativeError) as any)
|
||||
.stack;
|
||||
}
|
||||
}
|
||||
|
||||
class TestError extends WrappedError {
|
||||
constructor(message: string, error: any) {
|
||||
super(`${message} caused by: ${error instanceof Error ? error.message : error}`, error);
|
||||
}
|
||||
|
||||
get message() { return 'test ' + this.originalError.message; }
|
||||
}
|
||||
|
||||
class TestMessageError extends WrappedError {
|
||||
constructor(message: string, error: any) {
|
||||
super(`${message} caused by: ${error instanceof Error ? error.message : error}`, error);
|
||||
}
|
||||
|
||||
get message() { return 'test ' + this.originalError.message; }
|
||||
|
||||
set message(value) { this.originalError.message = value; }
|
||||
}
|
||||
|
||||
describe('ZoneAwareError', () => {
|
||||
// If the environment does not supports stack rewrites, then these tests will fail
|
||||
// and there is no point in running them.
|
||||
const _global: any = typeof window !== 'undefined' ? window : global;
|
||||
let config: any;
|
||||
const __karma__ = _global.__karma__;
|
||||
if (typeof __karma__ !== 'undefined') {
|
||||
config = __karma__ && (__karma__ as any).config;
|
||||
} else if (typeof process !== 'undefined') {
|
||||
config = process.env;
|
||||
}
|
||||
const policy = (config && config['errorpolicy']) || 'default';
|
||||
if (!(Error as any)['stackRewrite'] && policy !== 'disable') return;
|
||||
|
||||
it('should keep error prototype chain correctly', () => {
|
||||
class MyError extends Error {}
|
||||
const myError = new MyError();
|
||||
expect(myError instanceof Error).toBe(true);
|
||||
expect(myError instanceof MyError).toBe(true);
|
||||
expect(myError.stack).not.toBe(undefined);
|
||||
});
|
||||
|
||||
it('should instanceof error correctly', () => {
|
||||
let myError = Error('myError');
|
||||
expect(myError instanceof Error).toBe(true);
|
||||
let myError1 = Error.call(undefined, 'myError');
|
||||
expect(myError1 instanceof Error).toBe(true);
|
||||
let myError2 = Error.call(global, 'myError');
|
||||
expect(myError2 instanceof Error).toBe(true);
|
||||
let myError3 = Error.call({}, 'myError');
|
||||
expect(myError3 instanceof Error).toBe(true);
|
||||
let myError4 = Error.call({test: 'test'}, 'myError');
|
||||
expect(myError4 instanceof Error).toBe(true);
|
||||
});
|
||||
|
||||
it('should return error itself from constructor', () => {
|
||||
class MyError1 extends Error {
|
||||
constructor() {
|
||||
const err: any = super('MyError1');
|
||||
this.message = err.message;
|
||||
}
|
||||
}
|
||||
let myError1 = new MyError1();
|
||||
expect(myError1.message).toEqual('MyError1');
|
||||
expect(myError1.name).toEqual('Error');
|
||||
});
|
||||
|
||||
it('should return error by calling error directly', () => {
|
||||
let myError = Error('myError');
|
||||
expect(myError.message).toEqual('myError');
|
||||
let myError1 = Error.call(undefined, 'myError');
|
||||
expect(myError1.message).toEqual('myError');
|
||||
let myError2 = Error.call(global, 'myError');
|
||||
expect(myError2.message).toEqual('myError');
|
||||
let myError3 = Error.call({}, 'myError');
|
||||
expect(myError3.message).toEqual('myError');
|
||||
});
|
||||
|
||||
it('should have browser specified property', () => {
|
||||
let myError = new Error('myError');
|
||||
if (Object.prototype.hasOwnProperty.call(Error.prototype, 'description')) {
|
||||
// in IE, error has description property
|
||||
expect((<any>myError).description).toEqual('myError');
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(Error.prototype, 'fileName')) {
|
||||
// in firefox, error has fileName property
|
||||
expect((<any>myError).fileName).toBeTruthy();
|
||||
}
|
||||
});
|
||||
|
||||
it('should not use child Error class get/set in ZoneAwareError constructor', () => {
|
||||
const func = () => {
|
||||
const error = new BaseError('test');
|
||||
expect(error.message).toEqual('test');
|
||||
};
|
||||
|
||||
expect(func).not.toThrow();
|
||||
});
|
||||
|
||||
it('should behave correctly with wrapped error', () => {
|
||||
const error = new TestError('originalMessage', new Error('error message'));
|
||||
expect(error.message).toEqual('test error message');
|
||||
error.originalError.message = 'new error message';
|
||||
expect(error.message).toEqual('test new error message');
|
||||
|
||||
const error1 = new TestMessageError('originalMessage', new Error('error message'));
|
||||
expect(error1.message).toEqual('test error message');
|
||||
error1.message = 'new error message';
|
||||
expect(error1.message).toEqual('test new error message');
|
||||
});
|
||||
|
||||
it('should copy customized NativeError properties to ZoneAwareError', () => {
|
||||
const spy = jasmine.createSpy('errorCustomFunction');
|
||||
const NativeError = (global as any)[(Zone as any).__symbol__('Error')];
|
||||
NativeError.customFunction = function(args: any) { spy(args); };
|
||||
expect((Error as any)['customProperty']).toBe('customProperty');
|
||||
expect(typeof(Error as any)['customFunction']).toBe('function');
|
||||
(Error as any)['customFunction']('test');
|
||||
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', () => {
|
||||
if (policy === 'disable' || !(Error as any)['stackRewrite']) {
|
||||
return;
|
||||
}
|
||||
if (isBrowser && isSafari()) {
|
||||
return;
|
||||
}
|
||||
const rootZone = Zone.root;
|
||||
const innerZone = rootZone.fork({name: 'InnerZone'});
|
||||
|
||||
rootZone.run(testFn);
|
||||
function testFn() {
|
||||
let outside: any;
|
||||
let inside: any;
|
||||
let outsideWithoutNew: any;
|
||||
let insideWithoutNew: any;
|
||||
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;
|
||||
}
|
||||
});
|
||||
|
||||
if (policy === 'lazy') {
|
||||
outside.stack = outside.zoneAwareStack;
|
||||
outsideWithoutNew.stack = outsideWithoutNew.zoneAwareStack;
|
||||
inside.stack = inside.zoneAwareStack;
|
||||
insideWithoutNew.stack = insideWithoutNew.zoneAwareStack;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
if (/Error /.test(outsideFrames[0])) {
|
||||
outsideFrames.shift();
|
||||
}
|
||||
|
||||
if (/Outside/.test(outsideWithoutNewFrames[0])) {
|
||||
outsideWithoutNewFrames.shift();
|
||||
}
|
||||
if (/Error /.test(outsideWithoutNewFrames[0])) {
|
||||
outsideWithoutNewFrames.shift();
|
||||
}
|
||||
|
||||
if (/Inside/.test(insideFrames[0])) {
|
||||
insideFrames.shift();
|
||||
}
|
||||
if (/Error /.test(insideFrames[0])) {
|
||||
insideFrames.shift();
|
||||
}
|
||||
|
||||
if (/Inside/.test(insideWithoutNewFrames[0])) {
|
||||
insideWithoutNewFrames.shift();
|
||||
}
|
||||
if (/Error /.test(insideWithoutNewFrames[0])) {
|
||||
insideWithoutNewFrames.shift();
|
||||
}
|
||||
expect(outsideFrames[0]).toMatch(/testFn.*[<root>]/);
|
||||
|
||||
expect(insideFrames[0]).toMatch(/insideRun.*[InnerZone]]/);
|
||||
expect(insideFrames[1]).toMatch(/testFn.*[<root>]]/);
|
||||
|
||||
expect(outsideWithoutNewFrames[0]).toMatch(/testFn.*[<root>]/);
|
||||
|
||||
expect(insideWithoutNewFrames[0]).toMatch(/insideRun.*[InnerZone]]/);
|
||||
expect(insideWithoutNewFrames[1]).toMatch(/testFn.*[<root>]]/);
|
||||
}
|
||||
});
|
||||
|
||||
const zoneAwareFrames = [
|
||||
'Zone.run', 'Zone.runGuarded', 'Zone.scheduleEventTask', 'Zone.scheduleMicroTask',
|
||||
'Zone.scheduleMacroTask', 'Zone.runTask', 'ZoneDelegate.scheduleTask',
|
||||
'ZoneDelegate.invokeTask', 'zoneAwareAddListener', 'Zone.prototype.run',
|
||||
'Zone.prototype.runGuarded', 'Zone.prototype.scheduleEventTask',
|
||||
'Zone.prototype.scheduleMicroTask', 'Zone.prototype.scheduleMacroTask',
|
||||
'Zone.prototype.runTask', 'ZoneDelegate.prototype.scheduleTask',
|
||||
'ZoneDelegate.prototype.invokeTask', 'ZoneTask.invokeTask'
|
||||
];
|
||||
|
||||
function assertStackDoesNotContainZoneFrames(err: any) {
|
||||
const frames = policy === 'lazy' ? err.zoneAwareStack.split('\n') : err.stack.split('\n');
|
||||
if (policy === 'disable') {
|
||||
let hasZoneStack = false;
|
||||
for (let i = 0; i < frames.length; i++) {
|
||||
if (hasZoneStack) {
|
||||
break;
|
||||
}
|
||||
hasZoneStack = zoneAwareFrames.filter(f => frames[i].indexOf(f) !== -1).length > 0;
|
||||
}
|
||||
if (!hasZoneStack) {
|
||||
console.log('stack', err.originalStack);
|
||||
}
|
||||
expect(hasZoneStack).toBe(true);
|
||||
} else {
|
||||
for (let i = 0; i < frames.length; i++) {
|
||||
expect(zoneAwareFrames.filter(f => frames[i].indexOf(f) !== -1)).toEqual([]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const errorZoneSpec = {
|
||||
name: 'errorZone',
|
||||
done: <(() => void)|null>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) => { setTimeout(() => { 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) => { setTimeout(() => { 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');
|
||||
}, undefined, () => null, undefined);
|
||||
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'); }, undefined, () => null, undefined);
|
||||
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');
|
||||
}, undefined, () => null, undefined);
|
||||
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');
|
||||
}, undefined, () => null, undefined);
|
||||
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');
|
||||
}, undefined, () => null, undefined);
|
||||
task.invoke();
|
||||
}));
|
||||
|
||||
it('should be able to generate zone free stack even NativeError stack is readonly', function() {
|
||||
const _global: any =
|
||||
typeof window === 'object' && window || typeof self === 'object' && self || global;
|
||||
const NativeError = _global[zoneSymbol('Error')];
|
||||
const desc = Object.getOwnPropertyDescriptor(NativeError.prototype, 'stack');
|
||||
if (desc) {
|
||||
const originalSet: ((value: any) => void)|undefined = desc.set;
|
||||
// make stack readonly
|
||||
desc.set = null as any;
|
||||
|
||||
try {
|
||||
const error = new Error('test error');
|
||||
expect(error.stack).toBeTruthy();
|
||||
assertStackDoesNotContainZoneFrames(error);
|
||||
} finally {
|
||||
desc.set = originalSet;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user