Skip to content
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

add expectedError method #6581

Merged
merged 4 commits into from
Dec 20, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions spec/amp-errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ The following fields are reported:
The "expected" errors are marked with `&ex=1`.

An expected error is identified by the `.expected` property on the `Error` object being `true`.
You can use the `Log.prototype.expectedError` method to create an error that is marked
as expected.

An "expected" error is still an error, i.e. some features are disabled or not
functioning fully because of it. However, it's an expected error. E.g. as is the
Expand Down
45 changes: 43 additions & 2 deletions src/log.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,15 +179,44 @@ export class Log {
* asynchronously.
* @param {string} tag
* @param {...*} var_args
* @return {!Error|undefined}
* @private
*/
error(tag, var_args) {
error_(tag, var_args) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mark private.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

if (this.level_ >= LogLevel.ERROR) {
this.msg_(tag, 'ERROR', Array.prototype.slice.call(arguments, 1));
} else {
const error = createErrorVargs.apply(null,
Array.prototype.slice.call(arguments, 1));
this.prepareError_(error);
this.win.setTimeout(() => {throw error;});
return error;
}
}

/**
* Reports an error message.
* @param {string} tag
* @param {...*} var_args
* @return {!Error|undefined}
*/
error(tag, var_args) {
const error = this.error_.apply(this, arguments);
if (error) {
this.win.setTimeout(() => {throw /** @type {!Error} */ (error);});
}
}

/**
* Reports an error message and marks with an expected property. If the
* logging is disabled, the error is rethrown asynchronously.
* @param {string} tag
* @param {...*} var_args
*/
expectedError(tag, var_args) {
const error = this.error_.apply(this, arguments);
if (error) {
error.expected = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While correct, it's a bit weird to set flag on an error after it's been reported. Maybe better refactor call into a private method and call from both methods with different flags?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah i can do that, that was actually the original code i had written.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

this.win.setTimeout(() => {throw /** @type {!Error} */ (error);});
}
}

Expand All @@ -202,6 +231,18 @@ export class Log {
return error;
}

/**
* Creates an error object with its expected property set to true.
* @param {...*} var_args
* @return {!Error}
*/
createExpectedError(var_args) {
const error = createErrorVargs.apply(null, arguments);
this.prepareError_(error);
error.expected = true;
return error;
}

/**
* Throws an error if the first argument isn't trueish.
*
Expand Down
5 changes: 2 additions & 3 deletions src/service/storage-impl.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export class Storage {
this.storePromise_ = this.binding_.loadBlob(this.origin_)
.then(blob => blob ? JSON.parse(atob(blob)) : {})
.catch(reason => {
dev().error(TAG, 'Failed to load store: ', reason);
dev().expectedError(TAG, 'Failed to load store: ', reason);
return {};
})
.then(obj => new Store(obj));
Expand Down Expand Up @@ -290,8 +290,7 @@ export class LocalStorageBinding {

if (!this.isLocalStorageSupported_) {
const error = new Error('localStorage not supported.');
error.expected = true;
dev().error(TAG, error);
dev().expectedError(TAG, error);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/service/viewer-impl.js
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ export class Viewer {
} else {
resolve(this.win.document.referrer);
if (this.unconfirmedReferrerUrl_ != this.win.document.referrer) {
dev().error(TAG_, 'Untrusted viewer referrer override: ' +
dev().expectedError(TAG_, 'Untrusted viewer referrer override: ' +
this.unconfirmedReferrerUrl_ + ' at ' +
this.messagingOrigin_);
this.unconfirmedReferrerUrl_ = this.win.document.referrer;
Expand Down
8 changes: 4 additions & 4 deletions src/service/xhr-impl.js
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ export function fetchPolyfill(input, opt_init) {
}
if (xhr.status < 100 || xhr.status > 599) {
xhr.onreadystatechange = null;
reject(new Error(`Unknown HTTP status ${xhr.status}`));
reject(user().createExpectedError(`Unknown HTTP status ${xhr.status}`));
return;
}

Expand All @@ -365,10 +365,10 @@ export function fetchPolyfill(input, opt_init) {
}
};
xhr.onerror = () => {
reject(new Error('Network failure'));
reject(user().createExpectedError('Network failure'));
};
xhr.onabort = () => {
reject(new Error('Request aborted'));
reject(user().createExpectedError('Request aborted'));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dvoytenko please review

};

if (init.method == 'POST') {
Expand All @@ -394,7 +394,7 @@ function createXhrRequest(method, url) {
xhr = new XDomainRequest();
xhr.open(method, url);
} else {
throw new Error('CORS is not supported');
throw dev().createExpectedError('CORS is not supported');
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dvoytenko can you review this?

}
return xhr;
}
Expand Down
31 changes: 31 additions & 0 deletions test/functional/test-log.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,10 +178,34 @@ describe('Logging', () => {
} catch (e) {
expect(e).to.be.instanceof(Error);
expect(e.message).to.match(/intended\: test/);
expect(e.expected).to.be.undefined;
expect(isUserErrorMessage(e.message)).to.be.false;
}
});

it('should report ERROR and mark with expected flag', () => {
const log = new Log(win, RETURNS_OFF);
expect(log.level_).to.equal(LogLevel.OFF);
win.setTimeout = () => {};
let timeoutCallback;
const timeoutStub = sandbox.stub(win, 'setTimeout', callback => {
timeoutCallback = callback;
});

log.expectedError('TAG', 'intended', new Error('test'));

expect(logSpy.callCount).to.equal(0);
expect(timeoutStub.callCount).to.equal(1);
expect(timeoutCallback).to.exist;
try {
timeoutCallback();
} catch (e) {
expect(e).to.be.instanceof(Error);
expect(e.message).to.match(/intended\: test/);
expect(e.expected).to.be.true;
}
});

it('should report ERROR when OFF from a single message', () => {
const log = new Log(win, RETURNS_OFF);
expect(log.level_).to.equal(LogLevel.OFF);
Expand Down Expand Up @@ -382,8 +406,15 @@ describe('Logging', () => {
expect(false).to.be.true;
});

it('should create expected error from message', () => {
const error = log.createExpectedError('test');
expect(error).to.be.instanceof(Error);
expect(error.expected).to.be.true;
});

it('should create suffixed errors from message', () => {
const error = log.createError('test');
expect(error.expected).to.be.undefined;
expect(error).to.be.instanceof(Error);
expect(isUserErrorMessage(error.message)).to.be.true;
expect(error.message).to.contain('test');
Expand Down
3 changes: 1 addition & 2 deletions test/functional/test-storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@ describe('LocalStorageBinding', () => {
});

it('should throw if localStorage is not supported', () => {
const errorSpy = sandbox.spy(dev(), 'error');
const errorSpy = sandbox.spy(dev(), 'expectedError');

expect(errorSpy.callCount).to.equal(0);
new LocalStorageBinding(windowApi);
Expand All @@ -456,7 +456,6 @@ describe('LocalStorageBinding', () => {
new LocalStorageBinding(windowApi);
expect(errorSpy.callCount).to.equal(1);
expect(errorSpy.args[0][1].message).to.match(/localStorage not supported/);
expect(errorSpy.args[0][1].expected).to.be.true;
});

it('should load store when available', () => {
Expand Down
6 changes: 4 additions & 2 deletions test/functional/test-viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ describe('Viewer', () => {
let clock;
let events;
let errorStub;
let expectedErrorStub;

function changeVisibility(vis) {
windowApi.document.hidden = vis !== 'visible';
Expand Down Expand Up @@ -83,6 +84,7 @@ describe('Viewer', () => {
installTimerService(windowApi);
events = {};
errorStub = sandbox.stub(dev(), 'error');
expectedErrorStub = sandbox.stub(dev(), 'expectedError');
windowMock = sandbox.mock(windowApi);
viewer = new Viewer(ampdoc);
});
Expand Down Expand Up @@ -1051,8 +1053,8 @@ describe('Viewer', () => {
// Unconfirmed referrer is reset. Async error is thrown.
expect(viewer.getUnconfirmedReferrerUrl())
.to.equal('https://acme.org/docref');
expect(errorStub.callCount).to.equal(1);
expect(errorStub.calledWith('Viewer',
expect(expectedErrorStub.callCount).to.equal(1);
expect(expectedErrorStub.calledWith('Viewer',
sinon.match(arg => {
return !!arg.match(/Untrusted viewer referrer override/);
}))).to.be.true;
Expand Down