diff --git a/.travis.yml b/.travis.yml index 93c1c1f27..1e16c2e58 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,8 +39,12 @@ script: - node_modules/.bin/karma start karma-build-sauce-mocha.conf.js --single-run - node_modules/.bin/karma start karma-dist-sauce-selenium3-jasmine.conf.js --single-run - node_modules/.bin/karma start karma-build-sauce-selenium3-mocha.conf.js --single-run + - node_modules/.bin/karma start karma-dist-sauce-jasmine3.conf.js --single-run --errorpolicy=disable + - node_modules/.bin/karma start karma-dist-sauce-jasmine3.conf.js --single-run --errorpolicy=lazy - node_modules/.bin/gulp test/node - node_modules/.bin/gulp test/bluebird + - node_modules/.bin/gulp test/node/disableerror + - node_modules/.bin/gulp test/node/lazyerror - node simple-server.js 2>&1> server.log& - node ./test/webdriver/test.sauce.js - yarn add jasmine@3.0.0 jasmine-core@3.0.0 mocha@5.0.1 diff --git a/MODULE.md b/MODULE.md index 377f66fa9..51519a03e 100644 --- a/MODULE.md +++ b/MODULE.md @@ -76,6 +76,57 @@ you can do like this. ``` +- Error + +By default, `zone.js/dist/zone-error` will not be loaded for performance concern. +This package will provide following functionality. + + 1. Error inherit: handle `extend Error` issue. + ``` + class MyError extends Error {} + const myError = new MyError(); + console.log('is MyError instanceof Error', (myError instanceof Error)); + ``` + + without `zone-error` patch, the example above will output `false`, with the patch, the reuslt will be `true`. + + 2. BlacklistZoneStackFrames: remove zone.js stack from `stackTrace`, and add `zone` information. Without this patch, a lot of `zone.js` invocation stack will be shown + in stack frames. + + ``` + at zone.run (polyfill.bundle.js: 3424) + at zoneDelegate.invokeTask (polyfill.bundle.js: 3424) + at zoneDelegate.runTask (polyfill.bundle.js: 3424) + at zone.drainMicroTaskQueue (polyfill.bundle.js: 3424) + at a.b.c (vendor.bundle.js: 12345 ) + at d.e.f (main.bundle.js: 23456) + ``` + + with this patch, those zone frames will be removed, + and the zone information `/` will be added + + ``` + at a.b.c (vendor.bundle.js: 12345 ) + at d.e.f (main.bundle.js: 23456 ) + ``` + + The second feature will slow down the `Error` performance, so `zone.js` provide a flag to let you be able to control the behavior. + The flag is `__Zone_Error_BlacklistedStackFrames_policy`. And the available options is: + + 1. default: this is the default one, if you load `zone.js/dist/zone-error` without + setting the flag, `default` will be used, and `BlackListStackFrames` will be available + when `new Error()`, you can get a `error.stack` which is `zone stack free`. But this + will slow down `new Error()` a little bit. + + 2. disable: this will disable `BlackListZoneStackFrame` feature, and if you load + `zone.js/dist/zone-error`, you will only get a `wrapped Error` which can handle + `Error inherit` issue. + + 3. lazy: this is a feature to let you be able to get `BlackListZoneStackFrame` feature, + but not impact performance. But as a trade off, you can't get the `zone free stack + frames` by access `error.stack`. You can only get it by access `error.zoneAwareStack`. + + - Angular(2+) Angular uses zone.js to manage async operations and decide when to perform change detection. Thus, in Angular, diff --git a/gulpfile.js b/gulpfile.js index 2bab13d6f..aa6e0f9a1 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -221,19 +221,23 @@ gulp.task('build/zone-patch-socket-io.min.js', ['compile-esm'], function(cb) { }); gulp.task('build/zone-patch-promise-testing.js', ['compile-esm'], function(cb) { - return generateScript('./lib/testing/promise-testing.ts', 'zone-patch-promise-test.js', false, cb); + return generateScript( + './lib/testing/promise-testing.ts', 'zone-patch-promise-test.js', false, cb); }); gulp.task('build/zone-patch-promise-testing.min.js', ['compile-esm'], function(cb) { - return generateScript('./lib/testing/promise-testing.ts', 'zone-patch-promise-test.min.js', true, cb); + return generateScript( + './lib/testing/promise-testing.ts', 'zone-patch-promise-test.min.js', true, cb); }); gulp.task('build/zone-patch-resize-observer.js', ['compile-esm'], function(cb) { - return generateScript('./lib/browser/webapis-resize-observer.ts', 'zone-patch-resize-observer.js', false, cb); + return generateScript( + './lib/browser/webapis-resize-observer.ts', 'zone-patch-resize-observer.js', false, cb); }); gulp.task('build/zone-patch-resize-observer.min.js', ['compile-esm'], function(cb) { - return generateScript('./lib/browser/webapis-resize-observer.ts', 'zone-patch-resize-observer.min.js', true, cb); + return generateScript( + './lib/browser/webapis-resize-observer.ts', 'zone-patch-resize-observer.min.js', true, cb); }); gulp.task('build/bluebird.js', ['compile-esm'], function(cb) { @@ -245,11 +249,11 @@ gulp.task('build/bluebird.min.js', ['compile-esm'], function(cb) { }); gulp.task('build/zone-patch-jsonp.js', ['compile-esm'], function(cb) { - return generateScript('./lib/extra/jsonp.ts', 'zone-patch-jsonp.js', false, cb); + return generateScript('./lib/extra/jsonp.ts', 'zone-patch-jsonp.js', false, cb); }); gulp.task('build/zone-patch-jsonp.min.js', ['compile-esm'], function(cb) { - return generateScript('./lib/extra/jsonp.ts', 'zone-patch-jsonp.min.js', true, cb); + return generateScript('./lib/extra/jsonp.ts', 'zone-patch-jsonp.min.js', true, cb); }); gulp.task('build/jasmine-patch.js', ['compile-esm'], function(cb) { @@ -323,11 +327,13 @@ gulp.task('build/rxjs.min.js', ['compile-esm'], function(cb) { }); gulp.task('build/rxjs-fake-async.js', ['compile-esm'], function(cb) { - return generateScript('./lib/rxjs/rxjs-fake-async.ts', 'zone-patch-rxjs-fake-async.js', false, cb); + return generateScript( + './lib/rxjs/rxjs-fake-async.ts', 'zone-patch-rxjs-fake-async.js', false, cb); }); gulp.task('build/rxjs-fake-async.min.js', ['compile-esm'], function(cb) { - return generateScript('./lib/rxjs/rxjs-fake-async.ts', 'zone-patch-rxjs-fake-async.min.js', true, cb); + return generateScript( + './lib/rxjs/rxjs-fake-async.ts', 'zone-patch-rxjs-fake-async.min.js', true, cb); }); gulp.task('build/closure.js', function() { @@ -427,6 +433,18 @@ gulp.task('test/bluebird', ['compile-node'], function(cb) { nodeTest(specFiles, cb); }); +gulp.task('test/node/disableerror', ['compile-node'], function(cb) { + process.env.errorpolicy = 'disable'; + var specFiles = ['build/test/node_error_entry_point.js']; + nodeTest(specFiles, cb); +}); + +gulp.task('test/node/lazyerror', ['compile-node'], function(cb) { + process.env.errorpolicy = 'lazy'; + var specFiles = ['build/test/node_error_entry_point.js']; + nodeTest(specFiles, cb); +}); + // Check the coding standards and programming errors gulp.task('lint', () => { const tslint = require('gulp-tslint'); diff --git a/karma-base.conf.js b/karma-base.conf.js index 26ffa771a..8a5a7fd2e 100644 --- a/karma-base.conf.js +++ b/karma-base.conf.js @@ -6,40 +6,35 @@ * found in the LICENSE file at https://angular.io/license */ -module.exports = function (config) { +module.exports = function(config) { config.set({ basePath: '', + client: {errorpolicy: config.errorpolicy}, files: [ - 'node_modules/systemjs/dist/system-polyfills.js', - 'node_modules/systemjs/dist/system.src.js', + 'node_modules/systemjs/dist/system-polyfills.js', 'node_modules/systemjs/dist/system.src.js', 'node_modules/whatwg-fetch/fetch.js', - {pattern: 'node_modules/rxjs/**/**/*.js', included: false, watched: false }, - {pattern: 'node_modules/rxjs/**/**/*.js.map', included: false, watched: false }, - {pattern: 'node_modules/rxjs/**/*.js', included: false, watched: false }, - {pattern: 'node_modules/es6-promise/**/*.js', included: false, watched: false }, - {pattern: 'node_modules/rxjs/**/*.js.map', included: false, watched: false }, + {pattern: 'node_modules/rxjs/**/**/*.js', included: false, watched: false}, + {pattern: 'node_modules/rxjs/**/**/*.js.map', included: false, watched: false}, + {pattern: 'node_modules/rxjs/**/*.js', included: false, watched: false}, + {pattern: 'node_modules/es6-promise/**/*.js', included: false, watched: false}, + {pattern: 'node_modules/rxjs/**/*.js.map', included: false, watched: false}, {pattern: 'test/assets/**/*.*', watched: true, served: true, included: false}, {pattern: 'build/**/*.js.map', watched: true, served: true, included: false}, {pattern: 'build/**/*.js', watched: true, served: true, included: false} ], plugins: [ - require('karma-chrome-launcher'), - require('karma-firefox-launcher'), + require('karma-chrome-launcher'), require('karma-firefox-launcher'), require('karma-sourcemap-loader') ], - preprocessors: { - '**/*.js': ['sourcemap'] - }, + preprocessors: {'**/*.js': ['sourcemap']}, - exclude: [ - 'test/microtasks.spec.ts' - ], + exclude: ['test/microtasks.spec.ts'], reporters: ['progress'], - //port: 9876, + // port: 9876, colors: true, logLevel: config.LOG_INFO, diff --git a/karma-build.conf.js b/karma-build.conf.js index 87c87a5ea..aa2d3113c 100644 --- a/karma-build.conf.js +++ b/karma-build.conf.js @@ -6,12 +6,11 @@ * found in the LICENSE file at https://angular.io/license */ -module.exports = function (config) { +module.exports = function(config) { require('./karma-base.conf.js')(config); config.files.push('build/test/wtf_mock.js'); config.files.push('build/test/test_fake_polyfill.js'); config.files.push('build/lib/zone.js'); config.files.push('build/lib/common/promise.js'); - config.files.push('build/lib/common/error-rewrite.js'); config.files.push('build/test/main.js'); }; diff --git a/karma-dist.conf.js b/karma-dist.conf.js index a96fe0993..5843b478d 100644 --- a/karma-dist.conf.js +++ b/karma-dist.conf.js @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -module.exports = function (config) { +module.exports = function(config) { require('./karma-base.conf.js')(config); config.files.push('build/test/wtf_mock.js'); config.files.push('build/test/test_fake_polyfill.js'); diff --git a/lib/common/error-rewrite.ts b/lib/common/error-rewrite.ts index dcd5b3970..4bcb32176 100644 --- a/lib/common/error-rewrite.ts +++ b/lib/common/error-rewrite.ts @@ -46,10 +46,71 @@ Zone.__load_patch('Error', (global: any, Zone: ZoneType, api: _ZonePrivate) => { // We must find the frame where Error was created, otherwise we assume we don't understand stack let zoneAwareFrame1: string; let zoneAwareFrame2: string; + let zoneAwareFrame1WithoutNew: string; + let zoneAwareFrame2WithoutNew: string; + let zoneAwareFrame3WithoutNew: string; global['Error'] = ZoneAwareError; const stackRewrite = 'stackRewrite'; + type BlackListedStackFramesPolicy = 'default'|'disable'|'lazy'; + const blackListedStackFramesPolicy: BlackListedStackFramesPolicy = + global['__Zone_Error_BlacklistedStackFrames_policy'] || 'default'; + + interface ZoneFrameName { + zoneName: string; + parent?: ZoneFrameName; + } + + function buildZoneFrameNames(zoneFrame: _ZoneFrame) { + let zoneFrameName: ZoneFrameName = {zoneName: zoneFrame.zone.name}; + let result = zoneFrameName; + while (zoneFrame.parent) { + zoneFrame = zoneFrame.parent; + const parentZoneFrameName = {zoneName: zoneFrame.zone.name}; + zoneFrameName.parent = parentZoneFrameName; + zoneFrameName = parentZoneFrameName; + } + return result; + } + + function buildZoneAwareStackFrames( + originalStack: string, zoneFrame: _ZoneFrame|ZoneFrameName, isZoneFrame = true) { + let frames: string[] = originalStack.split('\n'); + let i = 0; + // Find the first frame + while (!(frames[i] === zoneAwareFrame1 || frames[i] === zoneAwareFrame2 || + frames[i] === zoneAwareFrame1WithoutNew || frames[i] === zoneAwareFrame2WithoutNew || + frames[i] === zoneAwareFrame3WithoutNew) && + i < frames.length) { + i++; + } + for (; i < frames.length && zoneFrame; i++) { + let frame = frames[i]; + if (frame.trim()) { + switch (blackListedStackFrames[frame]) { + case FrameType.blackList: + frames.splice(i, 1); + i--; + break; + case FrameType.transition: + if (zoneFrame.parent) { + // This is the special frame where zone changed. Print and process it accordingly + zoneFrame = zoneFrame.parent; + } else { + zoneFrame = null; + } + frames.splice(i, 1); + i--; + break; + default: + frames[i] += isZoneFrame ? ` [${(zoneFrame as _ZoneFrame).zone.name}]` : + ` [${(zoneFrame as ZoneFrameName).zoneName}]`; + } + } + } + return frames.join('\n'); + } /** * This is ZoneAwareError which processes the stack frame and cleans up extra frames as well as * adds zone information to it. @@ -62,42 +123,17 @@ Zone.__load_patch('Error', (global: any, Zone: ZoneType, api: _ZonePrivate) => { // Process the stack trace and rewrite the frames. if ((ZoneAwareError as any)[stackRewrite] && originalStack) { - let frames: string[] = originalStack.split('\n'); let zoneFrame = api.currentZoneFrame(); - let i = 0; - // Find the first frame - while (!(frames[i] === zoneAwareFrame1 || frames[i] === zoneAwareFrame2) && - i < frames.length) { - i++; - } - for (; i < frames.length && zoneFrame; i++) { - let frame = frames[i]; - if (frame.trim()) { - switch (blackListedStackFrames[frame]) { - case FrameType.blackList: - frames.splice(i, 1); - i--; - break; - case FrameType.transition: - if (zoneFrame.parent) { - // This is the special frame where zone changed. Print and process it accordingly - zoneFrame = zoneFrame.parent; - } else { - zoneFrame = null; - } - frames.splice(i, 1); - i--; - break; - default: - frames[i] += ` [${zoneFrame.zone.name}]`; - } + if (blackListedStackFramesPolicy === 'lazy') { + // don't handle stack trace now + (error as any)[api.symbol('zoneFrameNames')] = buildZoneFrameNames(zoneFrame); + } else { + try { + error.stack = error.zoneAwareStack = buildZoneAwareStackFrames(originalStack, zoneFrame); + } catch (e) { + // ignore as some browsers don't allow overriding of stack } } - try { - error.stack = error.zoneAwareStack = frames.join('\n'); - } catch (e) { - // ignore as some browsers don't allow overriding of stack - } } if (this instanceof NativeError && this.constructor != NativeError) { @@ -123,6 +159,29 @@ Zone.__load_patch('Error', (global: any, Zone: ZoneType, api: _ZonePrivate) => { (ZoneAwareError as any)[blacklistedStackFramesSymbol] = blackListedStackFrames; (ZoneAwareError as any)[stackRewrite] = false; + const zoneAwareStackSymbol = api.symbol('zoneAwareStack'); + + // try to define zoneAwareStack property when blackListed + // policy is delay + if (blackListedStackFramesPolicy === 'lazy') { + Object.defineProperty(ZoneAwareError.prototype, 'zoneAwareStack', { + configurable: true, + enumerable: true, + get: function() { + if (!this[zoneAwareStackSymbol]) { + this[zoneAwareStackSymbol] = buildZoneAwareStackFrames( + this.originalStack, this[api.symbol('zoneFrameNames')], false); + } + return this[zoneAwareStackSymbol]; + }, + set: function(newStack: string) { + this.originalStack = newStack; + this[zoneAwareStackSymbol] = buildZoneAwareStackFrames( + this.originalStack, this[api.symbol('zoneFrameNames')], false); + } + }); + } + // those properties need special handling const specialPropertyNames = ['stackTraceLimit', 'captureStackTrace', 'prepareStackTrace']; // those properties of NativeError should be set to ZoneAwareError @@ -194,65 +253,71 @@ Zone.__load_patch('Error', (global: any, Zone: ZoneType, api: _ZonePrivate) => { } }); + if (blackListedStackFramesPolicy === 'disable') { + // don't need to run detectZone to populate + // blacklisted stack frames + return; + } // Now we need to populate the `blacklistedStackFrames` as well as find the // run/runGuarded/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. - const ZONE_AWARE_ERROR = 'ZoneAwareError'; - const ERROR_DOT = 'Error.'; - const EMPTY = ''; - const RUN_GUARDED = 'runGuarded'; - const RUN_TASK = 'runTask'; - const RUN = 'run'; - const BRACKETS = '('; - const AT = '@'; let detectZone: Zone = Zone.current.fork({ name: 'detect', - onHandleError: function(parentZD: ZoneDelegate, current: Zone, target: Zone, error: any): - boolean { - if (error.originalStack && Error === ZoneAwareError) { - let frames = error.originalStack.split(/\n/); - let runFrame = false, runGuardedFrame = false, runTaskFrame = false; - 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 - // line number) - if (/:\d+:\d+/.test(frame)) { - // 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 - // Safari: run@http://localhost:9876/base/build/lib/zone.js:101:24 - let fnName: string = frame.split(BRACKETS)[0].split(AT)[0]; - let frameType = FrameType.transition; - if (fnName.indexOf(ZONE_AWARE_ERROR) !== -1) { - zoneAwareFrame1 = frame; - zoneAwareFrame2 = frame.replace(ERROR_DOT, EMPTY); - blackListedStackFrames[zoneAwareFrame2] = FrameType.blackList; - } - if (fnName.indexOf(RUN_GUARDED) !== -1) { - runGuardedFrame = true; - } else if (fnName.indexOf(RUN_TASK) !== -1) { - runTaskFrame = true; - } else if (fnName.indexOf(RUN) !== -1) { - runFrame = true; - } else { - frameType = FrameType.blackList; - } - blackListedStackFrames[frame] = frameType; - // Once we find all of the frames we can stop looking. - if (runFrame && runGuardedFrame && runTaskFrame) { - (ZoneAwareError as any)[stackRewrite] = true; - break; + onHandleError: function( + parentZD: ZoneDelegate, current: Zone, target: Zone, error: any): boolean { + if (error.originalStack && Error === ZoneAwareError) { + let frames = error.originalStack.split(/\n/); + let runFrame = false, runGuardedFrame = false, runTaskFrame = false; + 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 + // line number or exact equals to `ZoneAwareError`) + if (/:\d+:\d+/.test(frame) || frame === 'ZoneAwareError') { + // 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 + // Safari: run@http://localhost:9876/base/build/lib/zone.js:101:24 + let fnName: string = frame.split('(')[0].split('@')[0]; + let frameType = FrameType.transition; + if (fnName.indexOf('ZoneAwareError') !== -1) { + if (fnName.indexOf('new ZoneAwareError') !== -1) { + zoneAwareFrame1 = frame; + zoneAwareFrame2 = frame.replace('new ZoneAwareError', 'new Error.ZoneAwareError'); + } else { + zoneAwareFrame1WithoutNew = frame; + zoneAwareFrame2WithoutNew = frame.replace('Error.', ''); + if (frame.indexOf('Error.ZoneAwareError') === -1) { + zoneAwareFrame3WithoutNew = + frame.replace('ZoneAwareError', 'Error.ZoneAwareError'); } } + blackListedStackFrames[zoneAwareFrame2] = FrameType.blackList; + } + if (fnName.indexOf('runGuarded') !== -1) { + runGuardedFrame = true; + } else if (fnName.indexOf('runTask') !== -1) { + runTaskFrame = true; + } else if (fnName.indexOf('run') !== -1) { + runFrame = true; + } else { + frameType = FrameType.blackList; + } + blackListedStackFrames[frame] = frameType; + // Once we find all of the frames we can stop looking. + if (runFrame && runGuardedFrame && runTaskFrame) { + (ZoneAwareError as any)[stackRewrite] = true; + break; } } - return false; } + } + return false; + } }) as Zone; // carefully constructor a stack frame which contains all of the frames of interest which // need to be detected and blacklisted. @@ -293,7 +358,17 @@ Zone.__load_patch('Error', (global: any, Zone: ZoneType, api: _ZonePrivate) => { childDetectZone.scheduleMicroTask( blacklistedStackFramesSymbol, () => { - throw new (ZoneAwareError as any)(ZoneAwareError, NativeError); + throw new Error(); + }, + null, + (t: Task) => { + (t as any)._transitionTo = fakeTransitionTo; + t.invoke(); + }); + childDetectZone.scheduleMicroTask( + blacklistedStackFramesSymbol, + () => { + throw Error(); }, null, (t: Task) => { @@ -316,5 +391,6 @@ Zone.__load_patch('Error', (global: any, Zone: ZoneType, api: _ZonePrivate) => { () => {}); }); }); + Error.stackTraceLimit = originalStackTraceLimit; }); diff --git a/test/common/Error.spec.ts b/test/common/Error.spec.ts index 1f4727b8d..52195bc62 100644 --- a/test/common/Error.spec.ts +++ b/test/common/Error.spec.ts @@ -78,7 +78,15 @@ class TestMessageError extends WrappedError { describe('ZoneAwareError', () => { // If the environment does not supports stack rewrites, then these tests will fail // and there is no point in running them. - if (!(Error as any)['stackRewrite']) return; + const _global: any = typeof window !== 'undefined' ? window : global; + let config: any; + 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 {} @@ -180,6 +188,9 @@ describe('ZoneAwareError', () => { }); it('should show zone names in stack frames and remove extra frames', () => { + if (policy === 'disable' || !(Error as any)['stackRewrite']) { + return; + } const rootZone = Zone.root; const innerZone = rootZone.fork({name: 'InnerZone'}); @@ -212,6 +223,13 @@ describe('ZoneAwareError', () => { } }); + 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); @@ -251,7 +269,6 @@ describe('ZoneAwareError', () => { if (/Error /.test(insideWithoutNewFrames[0])) { insideWithoutNewFrames.shift(); } - expect(outsideFrames[0]).toMatch(/testFn.*[]/); expect(insideFrames[0]).toMatch(/insideRun.*[InnerZone]]/); @@ -267,13 +284,28 @@ describe('ZoneAwareError', () => { const zoneAwareFrames = [ 'Zone.run', 'Zone.runGuarded', 'Zone.scheduleEventTask', 'Zone.scheduleMicroTask', 'Zone.scheduleMacroTask', 'Zone.runTask', 'ZoneDelegate.scheduleTask', - 'ZoneDelegate.invokeTask', 'zoneAwareAddListener' + '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: 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([]); + 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; + } + expect(hasZoneStack).toBe(true); + } else { + for (let i = 0; i < frames.length; i++) { + expect(zoneAwareFrames.filter(f => frames[i].indexOf(f) !== -1)).toEqual([]); + } } }; diff --git a/test/main.ts b/test/main.ts index f8c33f3ea..6a895c789 100644 --- a/test/main.ts +++ b/test/main.ts @@ -13,6 +13,14 @@ declare const __karma__: { }; __karma__.loaded = function() {}; + +if (typeof __karma__ !== 'undefined') { + (window as any)['__Zone_Error_BlacklistedStackFrames_policy'] = + (__karma__ as any).config.errorpolicy; +} else if (typeof process !== 'undefined') { + (window as any)['__Zone_Error_BlacklistedStackFrames_policy'] = process.env.errorpolicy; +} + (window as any).global = window; System.config({ defaultJSExtensions: true, @@ -37,13 +45,15 @@ browserPatchedPromise.then(() => { '/base/build/test/test-env-setup-jasmine'; // Setup test environment System.import(testFrameworkPatch).then(() => { - System.import('/base/build/test/browser_entry_point') - .then( - () => { - __karma__.start(); - }, - (error) => { - console.error(error.stack || error); - }); + System.import('/base/build/lib/common/error-rewrite').then(() => { + System.import('/base/build/test/browser_entry_point') + .then( + () => { + __karma__.start(); + }, + (error) => { + console.error(error.stack || error); + }); + }); }); }); diff --git a/test/node_entry_point.ts b/test/node_entry_point.ts index 27ba367ca..fb7c5dcf9 100644 --- a/test/node_entry_point.ts +++ b/test/node_entry_point.ts @@ -14,6 +14,15 @@ import './test_fake_polyfill'; import '../lib/zone'; import '../lib/common/promise'; import '../lib/common/to-string'; + +if (typeof __karma__ !== 'undefined') { + (global as any)['__Zone_Error_BlacklistedStackFrames_policy'] = + (__karma__ as any).config.errorpolicy; +} else if (typeof process !== 'undefined') { + (global as any)['__Zone_Error_BlacklistedStackFrames_policy'] = process.env.errorpolicy; +} + +import '../lib/common/error-rewrite'; import '../lib/node/node'; import '../lib/zone-spec/async-test'; import '../lib/zone-spec/fake-async-test'; diff --git a/test/node_error_entry_point.ts b/test/node_error_entry_point.ts new file mode 100644 index 000000000..f70682ed9 --- /dev/null +++ b/test/node_error_entry_point.ts @@ -0,0 +1,41 @@ +/** + * @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 + */ + +// Must be loaded before zone loads, so that zone can detect WTF. +import './wtf_mock'; +import './test_fake_polyfill'; + +// Setup tests for Zone without microtask support +import '../lib/zone'; +import '../lib/common/promise'; +import '../lib/common/to-string'; + +if (typeof __karma__ !== 'undefined') { + (global as any)['__Zone_Error_BlacklistedStackFrames_policy'] = + (__karma__ as any).config.errorpolicy; +} else if (typeof process !== 'undefined') { + (global as any)['__Zone_Error_BlacklistedStackFrames_policy'] = process.env.errorpolicy; +} + +import '../lib/common/error-rewrite'; +import '../lib/node/node'; +import '../lib/zone-spec/async-test'; +import '../lib/zone-spec/fake-async-test'; +import '../lib/zone-spec/long-stack-trace'; +import '../lib/zone-spec/proxy'; +import '../lib/zone-spec/sync-test'; +import '../lib/zone-spec/task-tracking'; +import '../lib/zone-spec/wtf'; +import '../lib/rxjs/rxjs'; + +import '../lib/testing/promise-testing'; +// Setup test environment +import './test-env-setup-jasmine'; + +// List all tests here: +import './common/Error.spec';