From 6029763ca438ee6ca310f46238b6307c68667d90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Og=C3=B3rek?= Date: Thu, 15 Mar 2018 16:21:27 +0100 Subject: [PATCH 1/2] fix: Handle Schrodingers Error --- src/raven.js | 14 +++++++------- test/raven.test.js | 25 +++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/raven.js b/src/raven.js index f1d60855b4ed..7e8ed72e127b 100644 --- a/src/raven.js +++ b/src/raven.js @@ -461,18 +461,18 @@ Raven.prototype = { captureException: function(ex, options) { options = objectMerge({trimHeadFrames: 0}, options ? options : {}); - if (isPlainObject(ex)) { - // If it is plain Object, serialize it manually and extract options - // This will allow us to group events based on top-level keys - // which is much better than creating new group when any key/value change - options = this._getCaptureExceptionOptionsFromPlainObject(options, ex); - ex = new Error(options.message); - } else if (isErrorEvent(ex) && ex.error) { + if (isErrorEvent(ex) && ex.error) { // If it is an ErrorEvent with `error` property, extract it to get actual Error ex = ex.error; } else if (isError(ex)) { // we have a real Error object ex = ex; + } else if (isPlainObject(ex)) { + // If it is plain Object, serialize it manually and extract options + // This will allow us to group events based on top-level keys + // which is much better than creating new group when any key/value change + options = this._getCaptureExceptionOptionsFromPlainObject(options, ex); + ex = new Error(options.message); } else { // If none of previous checks were valid, then it means that // it's not a plain Object diff --git a/test/raven.test.js b/test/raven.test.js index 3ac7e82b93e6..9e9fdee1f2d9 100644 --- a/test/raven.test.js +++ b/test/raven.test.js @@ -3066,8 +3066,10 @@ describe('Raven (public API)', function() { var error = new ErrorEvent('pickleRick', {error: new Error('pickleRick')}); this.sinon.stub(Raven, 'isSetup').returns(true); this.sinon.stub(Raven, '_handleStackInfo'); + this.sinon.spy(Raven, '_getCaptureExceptionOptionsFromPlainObject'); Raven.captureException(error, {foo: 'bar'}); assert.isTrue(Raven._handleStackInfo.calledOnce); + assert.isFalse(Raven._getCaptureExceptionOptionsFromPlainObject.called); }); it('should send ErrorEvents without Errors as messages', function() { @@ -3079,6 +3081,29 @@ describe('Raven (public API)', function() { }); } + it("should treat Schrodinger's Error in the same way as regular Error", function() { + // Schrodinger's Error is an object that is and is not an Error at the same time + // Like... error, but not really. + // But error. + // + // function Foo() {}; + // Foo.prototype = new Error(); + // var foo = new Foo(); + // console.log(isPlainObject(foo)); // true + // console.log(isError(foo)); // true + // console.log(foo instanceof Error); // true + + function SchrodingersError() {} + SchrodingersError.prototype = new Error("Schrödinger's cat was here"); + var error = new SchrodingersError(); + this.sinon.stub(Raven, 'isSetup').returns(true); + this.sinon.stub(Raven, '_handleStackInfo'); + this.sinon.spy(Raven, '_getCaptureExceptionOptionsFromPlainObject'); + Raven.captureException(error, {foo: 'bar'}); + assert.isTrue(Raven._handleStackInfo.calledOnce); + assert.isFalse(Raven._getCaptureExceptionOptionsFromPlainObject.called); + }); + it('should call handleStackInfo', function() { var error = new Error('pickleRick'); this.sinon.stub(Raven, 'isSetup').returns(true); From 28529634a229cbc8db2c3367fb79d6ae5026217c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Og=C3=B3rek?= Date: Thu, 15 Mar 2018 19:18:23 +0100 Subject: [PATCH 2/2] ref: Create better description for Schrodinger's Error --- test/raven.test.js | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/test/raven.test.js b/test/raven.test.js index 9e9fdee1f2d9..9585759af91c 100644 --- a/test/raven.test.js +++ b/test/raven.test.js @@ -3086,12 +3086,27 @@ describe('Raven (public API)', function() { // Like... error, but not really. // But error. // - // function Foo() {}; - // Foo.prototype = new Error(); + // To be more exact, it's an object literal or an instance of constructor function + // that has it's prototype set to the Error object itself. + // When using `isPlanObject`, which makes a call to `Object.prototype.toString`, + // it returns `[object Object]`, because any instance created with `new X` + // where X is a custom constructor like `function X () {}`, it's return value + // is an object literal. + // However, because it has it's prototype set to an Error object, + // when using `instanceof Error` check, it returns `true`, because calls + // like this, are always going up the prototype chain and will verify + // all possible constructors. For example: + // + // class Foo extends Bar {} + // class Bar extends Error {} + // // var foo = new Foo(); - // console.log(isPlainObject(foo)); // true - // console.log(isError(foo)); // true - // console.log(foo instanceof Error); // true + // + // and now `foo` is instance of every "extension" ever created in the chain + // + // foo instanceof Foo; // true + // foo instanceof Bar; // true (because Foo extends Bar) + // foo instanceof Error; // true (because Foo extends Bar that extends Error) function SchrodingersError() {} SchrodingersError.prototype = new Error("Schrödinger's cat was here");