Update the license headers throughout the repository to reference Google LLC rather than Google Inc, for the required license headers. PR Close #37205
		
			
				
	
	
		
			461 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			461 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
/**
 | 
						|
 * @license
 | 
						|
 * Copyright Google LLC 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;
 | 
						|
        }
 | 
						|
      }
 | 
						|
    });
 | 
						|
  });
 | 
						|
});
 |