-
Notifications
You must be signed in to change notification settings - Fork 9.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
tests: add basic sentry tests #6308
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,126 @@ | ||||||||||||||
/** | ||||||||||||||
* @license Copyright 2018 Google Inc. All Rights Reserved. | ||||||||||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 | ||||||||||||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. | ||||||||||||||
*/ | ||||||||||||||
'use strict'; | ||||||||||||||
|
||||||||||||||
jest.mock('raven'); | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what's the unmocking behavior for this? The docs don't really make it clear. Does it rely on the tests being run in different processes so that other requires won't get a mock version? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. even in the same process There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ah yeah, less worried about sentry (not) leaking in other tests than just how they work in general for future tests :) |
||||||||||||||
|
||||||||||||||
const raven = require('raven'); | ||||||||||||||
const Sentry = require('../../lib/sentry'); | ||||||||||||||
|
||||||||||||||
/* eslint-env jest */ | ||||||||||||||
|
||||||||||||||
describe('Sentry', () => { | ||||||||||||||
let configPayload; | ||||||||||||||
let originalSentry; | ||||||||||||||
|
||||||||||||||
beforeEach(() => { | ||||||||||||||
configPayload = { | ||||||||||||||
url: 'http://example.com', | ||||||||||||||
flags: {enableErrorReporting: true}, | ||||||||||||||
environmentData: {}, | ||||||||||||||
}; | ||||||||||||||
|
||||||||||||||
// We need to save the Sentry delegate object because every call to `.init` mutates the methods. | ||||||||||||||
// We want to have a fresh state for every test. | ||||||||||||||
originalSentry = {...Sentry}; | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. seems like sentry isn't modified anywhere? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. although, overriding Math.random is also kind of horrifying :) Maybe we should extract a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the Sentry object is mutated everytime you call
|
||||||||||||||
|
||||||||||||||
raven.config = jest.fn().mockReturnValue({install: jest.fn()}); | ||||||||||||||
raven.mergeContext = jest.fn(); | ||||||||||||||
raven.captureException = jest.fn().mockImplementation((err, opts, cb) => cb()); | ||||||||||||||
Sentry._shouldSample = jest.fn().mockReturnValue(true); | ||||||||||||||
}); | ||||||||||||||
|
||||||||||||||
afterEach(() => { | ||||||||||||||
// Reset the methods on the Sentry object, see note above. | ||||||||||||||
Object.assign(Sentry, originalSentry); | ||||||||||||||
}); | ||||||||||||||
|
||||||||||||||
describe('.init', () => { | ||||||||||||||
it('should noop when !enableErrorReporting', () => { | ||||||||||||||
Sentry.init({url: 'http://example.com', flags: {}}); | ||||||||||||||
expect(raven.config).not.toHaveBeenCalled(); | ||||||||||||||
Sentry.init({url: 'http://example.com', flags: {enableErrorReporting: false}}); | ||||||||||||||
expect(raven.config).not.toHaveBeenCalled(); | ||||||||||||||
}); | ||||||||||||||
|
||||||||||||||
it('should noop when not picked for sampling', () => { | ||||||||||||||
Sentry._shouldSample.mockReturnValue(false); | ||||||||||||||
Sentry.init({url: 'http://example.com', flags: {enableErrorReporting: true}}); | ||||||||||||||
expect(raven.config).not.toHaveBeenCalled(); | ||||||||||||||
}); | ||||||||||||||
|
||||||||||||||
it('should initialize the raven client when enableErrorReporting', () => { | ||||||||||||||
Sentry.init({ | ||||||||||||||
url: 'http://example.com', | ||||||||||||||
flags: { | ||||||||||||||
enableErrorReporting: true, | ||||||||||||||
emulatedFormFactor: 'desktop', | ||||||||||||||
throttlingMethod: 'devtools', | ||||||||||||||
}, | ||||||||||||||
environmentData: {}, | ||||||||||||||
}); | ||||||||||||||
|
||||||||||||||
expect(raven.config).toHaveBeenCalled(); | ||||||||||||||
expect(raven.mergeContext).toHaveBeenCalled(); | ||||||||||||||
expect(raven.mergeContext.mock.calls[0][0]).toEqual({ | ||||||||||||||
extra: { | ||||||||||||||
url: 'http://example.com', | ||||||||||||||
deviceEmulation: true, | ||||||||||||||
emulatedFormFactor: 'desktop', | ||||||||||||||
throttlingMethod: 'devtools', | ||||||||||||||
}, | ||||||||||||||
}); | ||||||||||||||
}); | ||||||||||||||
}); | ||||||||||||||
|
||||||||||||||
describe('.captureException', () => { | ||||||||||||||
it('should forward exceptions to raven client', async () => { | ||||||||||||||
Sentry.init(configPayload); | ||||||||||||||
const error = new Error('oops'); | ||||||||||||||
await Sentry.captureException(error); | ||||||||||||||
|
||||||||||||||
expect(raven.captureException).toHaveBeenCalled(); | ||||||||||||||
expect(raven.captureException.mock.calls[0][0]).toBe(error); | ||||||||||||||
}); | ||||||||||||||
|
||||||||||||||
it('should skip expected errors', async () => { | ||||||||||||||
Sentry.init(configPayload); | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what is an example of an expected error? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. An audit that fails because its artifact errored. In that case we'd essentially be double reporting. The real error is the one that failed in the gatherer, so we just report that one. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. lighthouse/lighthouse-core/runner.js Lines 269 to 274 in 3d5037a
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @hoten it's pretty much only for controlling error reporting to Sentry. Basically it means this is an error, but we expect it often enough and it might be something the user needs to fix (or in the example @patrickhulce linked, we already reported it when it was a gatherer error, no reason to report it again). |
||||||||||||||
const error = new Error('oops'); | ||||||||||||||
error.expected = true; | ||||||||||||||
await Sentry.captureException(error); | ||||||||||||||
|
||||||||||||||
expect(raven.captureException).not.toHaveBeenCalled(); | ||||||||||||||
}); | ||||||||||||||
|
||||||||||||||
it('should skip duplicate audit errors', async () => { | ||||||||||||||
Sentry.init(configPayload); | ||||||||||||||
const error = new Error('A'); | ||||||||||||||
await Sentry.captureException(error, {tags: {audit: 'my-audit'}}); | ||||||||||||||
await Sentry.captureException(error, {tags: {audit: 'my-audit'}}); | ||||||||||||||
|
||||||||||||||
expect(raven.captureException).toHaveBeenCalledTimes(1); | ||||||||||||||
}); | ||||||||||||||
|
||||||||||||||
it('should still allow different audit errors', async () => { | ||||||||||||||
Sentry.init(configPayload); | ||||||||||||||
const errorA = new Error('A'); | ||||||||||||||
const errorB = new Error('B'); | ||||||||||||||
await Sentry.captureException(errorA, {tags: {audit: 'my-audit'}}); | ||||||||||||||
await Sentry.captureException(errorB, {tags: {audit: 'my-audit'}}); | ||||||||||||||
|
||||||||||||||
expect(raven.captureException).toHaveBeenCalledTimes(2); | ||||||||||||||
}); | ||||||||||||||
|
||||||||||||||
it('should skip duplicate gatherer errors', async () => { | ||||||||||||||
Sentry.init(configPayload); | ||||||||||||||
const error = new Error('A'); | ||||||||||||||
await Sentry.captureException(error, {tags: {gatherer: 'my-gatherer'}}); | ||||||||||||||
await Sentry.captureException(error, {tags: {gatherer: 'my-gatherer'}}); | ||||||||||||||
|
||||||||||||||
expect(raven.captureException).toHaveBeenCalledTimes(1); | ||||||||||||||
}); | ||||||||||||||
}); | ||||||||||||||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
heh. check out
sampleRate
on https://docs.sentry.io/clients/node/config/I think they added it a while ago. I wonder why we never saw it.
Anyway, I think it's fine to use our solution for now. :)