// Copyright 2008 The Closure Library Authors. 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. /** * @fileoverview Helper class to allow for expected unit test failures. * * @author robbyw@google.com (Robby Walker) */ goog.setTestOnly('goog.testing.ExpectedFailures'); goog.provide('goog.testing.ExpectedFailures'); goog.require('goog.asserts'); goog.require('goog.debug.DivConsole'); goog.require('goog.dom'); goog.require('goog.dom.TagName'); goog.require('goog.events'); goog.require('goog.events.EventType'); goog.require('goog.log'); goog.require('goog.style'); goog.require('goog.testing.JsUnitException'); goog.require('goog.testing.TestCase'); goog.require('goog.testing.asserts'); /** * Helper class for allowing some unit tests to fail, particularly designed to * mark tests that should be fixed on a given browser. * *
 * var expectedFailures = new goog.testing.ExpectedFailures();
 *
 * function tearDown() {
 *   expectedFailures.handleTearDown();
 * }
 *
 * function testSomethingThatBreaksInWebKit() {
 *   expectedFailures.expectFailureFor(goog.userAgent.WEBKIT);
 *
 *   try {
 *     ...
 *     assert(somethingThatFailsInWebKit);
 *     ...
 *   } catch (e) {
 *     expectedFailures.handleException(e);
 *   }
 * }
 * 
* * @constructor * @final */ goog.testing.ExpectedFailures = function() { goog.testing.ExpectedFailures.setUpConsole_(); this.reset_(); }; /** * The lazily created debugging console. * @type {goog.debug.DivConsole?} * @private */ goog.testing.ExpectedFailures.console_ = null; /** * Logger for the expected failures. * @type {goog.log.Logger} * @private */ goog.testing.ExpectedFailures.prototype.logger_ = goog.log.getLogger('goog.testing.ExpectedFailures'); /** * Whether or not we are expecting failure. * @type {boolean} * @private */ goog.testing.ExpectedFailures.prototype.expectingFailure_; /** * The string to emit upon an expected failure. * @type {string} * @private */ goog.testing.ExpectedFailures.prototype.failureMessage_; /** * An array of suppressed failures. * @type {Array} * @private */ goog.testing.ExpectedFailures.prototype.suppressedFailures_; /** * Sets up the debug console, if it isn't already set up. * @private */ goog.testing.ExpectedFailures.setUpConsole_ = function() { if (!goog.testing.ExpectedFailures.console_) { var xButton = goog.dom.createDom( goog.dom.TagName.DIV, { 'style': 'position: absolute; border-left:1px solid #333;' + 'border-bottom:1px solid #333; right: 0; top: 0; width: 1em;' + 'height: 1em; cursor: pointer; background-color: #cde;' + 'text-align: center; color: black' }, 'X'); var div = goog.dom.createDom( goog.dom.TagName.DIV, { 'style': 'position: absolute; border: 1px solid #333; right: 10px;' + 'top : 10px; width: 400px; display: none' }, xButton); document.body.appendChild(div); goog.events.listen(xButton, goog.events.EventType.CLICK, function() { goog.style.setElementShown(div, false); }); goog.testing.ExpectedFailures.console_ = new goog.debug.DivConsole(div); goog.log.addHandler( goog.testing.ExpectedFailures.prototype.logger_, goog.bind(goog.style.setElementShown, null, div, true)); goog.log.addHandler( goog.testing.ExpectedFailures.prototype.logger_, goog.bind( goog.testing.ExpectedFailures.console_.addLogRecord, goog.testing.ExpectedFailures.console_)); } }; /** * Register to expect failure for the given condition. Multiple calls to this * function act as a boolean OR. The first applicable message will be used. * @param {boolean} condition Whether to expect failure. * @param {string=} opt_message Descriptive message of this expected failure. */ goog.testing.ExpectedFailures.prototype.expectFailureFor = function( condition, opt_message) { this.expectingFailure_ = this.expectingFailure_ || condition; if (condition) { this.failureMessage_ = this.failureMessage_ || opt_message || ''; } }; /** * Determines if the given exception was expected. * @param {Object} ex The exception to check. * @return {boolean} Whether the exception was expected. */ goog.testing.ExpectedFailures.prototype.isExceptionExpected = function(ex) { return this.expectingFailure_ && ex instanceof goog.testing.JsUnitException; }; /** * Handle an exception, suppressing it if it is a unit test failure that we * expected. * @param {Error} ex The exception to handle. */ goog.testing.ExpectedFailures.prototype.handleException = function(ex) { if (this.isExceptionExpected(ex)) { goog.asserts.assertInstanceof(ex, goog.testing.JsUnitException); goog.log.info( this.logger_, 'Suppressing test failure in ' + goog.testing.TestCase.currentTestName + ':' + (this.failureMessage_ ? '\n(' + this.failureMessage_ + ')' : ''), ex); this.suppressedFailures_.push(ex); goog.testing.TestCase.invalidateAssertionException(ex); return; } // Rethrow the exception if we weren't expecting it or if it is a normal // exception. throw ex; }; /** * Run the given function, catching any expected failures. * @param {Function} func The function to run. * @param {boolean=} opt_lenient Whether to ignore if the expected failures * didn't occur. In this case a warning will be logged in handleTearDown. */ goog.testing.ExpectedFailures.prototype.run = function(func, opt_lenient) { try { func(); } catch (ex) { this.handleException(ex); } if (!opt_lenient && this.expectingFailure_ && !this.suppressedFailures_.length) { fail(this.getExpectationMessage_()); } }; /** * @return {string} A warning describing an expected failure that didn't occur. * @private */ goog.testing.ExpectedFailures.prototype.getExpectationMessage_ = function() { return 'Expected a test failure in \'' + goog.testing.TestCase.currentTestName + '\' but the test passed.'; }; /** * Handle the tearDown phase of a test, alerting the user if an expected test * was not suppressed. */ goog.testing.ExpectedFailures.prototype.handleTearDown = function() { if (this.expectingFailure_ && !this.suppressedFailures_.length) { goog.log.warning(this.logger_, this.getExpectationMessage_()); } this.reset_(); }; /** * Reset internal state. * @private */ goog.testing.ExpectedFailures.prototype.reset_ = function() { this.expectingFailure_ = false; this.failureMessage_ = ''; this.suppressedFailures_ = []; };