Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1089)

Unified Diff: chrome/test/data/webui/test_api.js

Issue 7250009: Added guts to pull call signatures when assertions & expectations fail. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Changes from review. Created 9 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/test/data/webui/test_api.js
diff --git a/chrome/test/data/webui/test_api.js b/chrome/test/data/webui/test_api.js
index dc3a8a0e3abc2652e6926f8682eac1da47fe3934..71d17242dcf3618b37d82f1823ac179fe7e01410 100644
--- a/chrome/test/data/webui/test_api.js
+++ b/chrome/test/data/webui/test_api.js
@@ -74,7 +74,7 @@ var currentTestCase = null;
/**
* When set to a function, will be called in the context of the test
- * generation inside the function, and before any generated C++.
+ * generation inside the function, and after any generated C++.
* @type {function(string,string)}
**/
testGenPostamble: null,
@@ -146,7 +146,12 @@ var currentTestCase = null;
},
/**
- * Runs this test case.
+ * Runs this test case with |this| set to the |fixture|.
+ *
+ * Note: Tests created with TEST_F may depend upon |this| being set to an
+ * instance of this.fixture. The current implementation of TEST creates a
+ * dummy constructor, but tests created with TEST should not rely on |this|
+ * being set.
* @type {Function}
**/
Run: function() {
@@ -217,6 +222,136 @@ var currentTestCase = null;
oldChrome.send.apply(oldChrome, args);
}
+ /**
+ * Provides a mechanism for assert* and expect* methods to fetch the signature
+ * of their caller. Assert* methods should |registerCall| and expect* methods
+ * should set |isExpect| and |expectName| properties to indicate that the
+ * interesting caller is one more level up the stack.
+ **/
+ function CallHelper() {
+ this.__proto__ = CallHelper.prototype;
+ }
+
+ CallHelper.prototype = {
+ /**
+ * Holds the mapping of (callerCallerString, callerName) -> count of times
+ * called.
+ * @type {Object.<string, Object.<string, number>>}
+ **/
+ 'counts_': {},
+
+ /**
+ * This information about the caller is needed from most of the following
+ * routines.
+ * @param {Function} caller the caller of the assert* routine.
+ * @return {{callerName: string, callercallerString: string}} stackInfo
+ **/
+ 'getCallerInfo': function(caller) {
+ var callerName = caller.name;
+ var callerCaller = caller.caller;
+ if (callerCaller['isExpect']) {
+ callerName = callerCaller.expectName;
+ callerCaller = callerCaller.caller;
+ }
+ var callerCallerString = callerCaller.toString();
+ return {
+ callerName: callerName,
+ callerCallerString: callerCallerString,
+ };
+ },
+
+ /**
+ * Register a call to an assertion class.
+ **/
+ 'registerCall': function() {
+ var stackInfo = this.getCallerInfo(arguments.callee.caller);
+ if (!(stackInfo.callerCallerString in this.counts_))
+ this.counts_[stackInfo.callerCallerString] = {};
+ if (!(stackInfo.callerName in this.counts_[stackInfo.callerCallerString]))
+ this.counts_[stackInfo.callerCallerString][stackInfo.callerName] = 0;
+ ++this.counts_[stackInfo.callerCallerString][stackInfo.callerName];
+ },
+
+ /**
+ * @param {String} string the string to match parentheses.
+ * @param {number} index offset to the opening parenthesis.
+ * @return {[number, Array]} the index after matching paren and the args.
+ **/
+ 'matchedParamsIndex': function(string, index) {
+ var args = [];
+ var parenCount = 1;
+ var argStart = index + 1;
+ for(index = argStart;
+ parenCount && index < string.length;
+ ++index) {
+ if (string[index] == '(') {
+ ++parenCount;
+ } else if (string[index] == ')') {
+ if (--parenCount == 0)
+ args.push(string.substring(argStart, index));
+ } else if (string[index] == ',' && parenCount == 1) {
+ args.push(string.substring(argStart, index));
+ argStart = index + 1;
+ }
+ }
+ return [index, args];
+ },
+
+ /**
+ * Get the parameters of this instance of caller's call to this function.
+ * @param {Function} caller_ a particular caller or |undefined|.
+ * @param {number} count_ the number of times this instance was called.
+ * @return {Array.<string>} parameters of caller's call to this function.
+ **/
+ 'getParams': function(caller_, count_) {
+ var caller = (caller_ == undefined) ?
+ arguments.callee.caller : caller_;
+ var stackInfo = this.getCallerInfo(caller);
+ var count = (count_ == undefined) ?
+ this.counts_[stackInfo.callerCallerString][stackInfo.callerName] :
+ count_;
+ var searchStart = 0;
+ var args;
+ for(var i = 0;
+ i < count && searchStart < stackInfo.callerCallerString.length;
+ ++i) {
+ searchStart = stackInfo.callerCallerString.indexOf(
+ stackInfo.callerName, searchStart);
+ if (searchStart == -1) {
+ if (i && count_ == undefined) {
+ return this.getParams(caller, ((count - 1) % i) + 1);
+ } else {
+ console.error('bad count ' + count);
+ return undefined;
+ }
+ }
+ searchStart += stackInfo.callerName.length;
+ var matched = this.matchedParamsIndex(stackInfo.callerCallerString,
+ searchStart);
+ args = matched[1];
+ searchStart = matched[0];
+ }
+ return args;
+ },
+
+ /**
+ * Get the call signature of this instance of the caller's call to this
+ * function.
+ * @return {String} call signature.
+ **/
+ 'getCall': function() {
+ var caller = arguments.callee.caller;
+ var stackInfo = this.getCallerInfo(caller);
+ return stackInfo.callerName + '(' + this.getParams(caller) + ')';
+ },
+ };
+
+ /**
+ * Help register calls for better error reporting.
+ * @type {CallHelper}
+ **/
+ var helper = new CallHelper();
+
// Asserts.
// Use the following assertions to verify a condition within a test.
// If assertion fails, the C++ backend will be immediately notified.
@@ -230,13 +365,9 @@ var currentTestCase = null;
* @throws {Error} upon failure.
**/
function assertBool(test, expected, message) {
- if (test !== expected) {
- if (message)
- message = test + '\n' + message;
- else
- message = test;
- throw new Error(message);
- }
+ helper.registerCall();
+ if (test !== expected)
+ throw new Error('Test Error ' + helper.getCall() + ': ' + test);
}
/**
@@ -246,7 +377,9 @@ var currentTestCase = null;
* @throws {Error} upon failure.
**/
function assertTrue(test, message) {
- assertBool(test, true, message);
+ helper.registerCall();
+ if (test !== true)
+ throw new Error('Test Error ' + helper.getCall() + ': ' + test);
}
/**
@@ -256,7 +389,9 @@ var currentTestCase = null;
* @throws {Error} upon failure.
**/
function assertFalse(test, message) {
- assertBool(test, false, message);
+ helper.registerCall();
+ if (test !== false)
+ throw new Error('Test Error ' + helper.getCall() + ': ' + test);
}
/**
@@ -267,14 +402,15 @@ var currentTestCase = null;
* @throws {Error} upon failure.
**/
function assertEquals(expected, actual, message) {
+ helper.registerCall();
if (expected != actual) {
- throw new Error('Test Error. Actual: ' + actual + '\nExpected: ' +
- expected + '\n' + message);
+ throw new Error('Test Error ' + helper.getCall() + '\nActual: ' + actual +
+ '\nExpected: ' + expected);
}
if (typeof expected != typeof actual) {
- throw new Error('Test Error' +
- ' (type mismatch)\nActual Type: ' + typeof actual +
- '\nExpected Type:' + typeof expected + '\n' + message);
+ throw new Error('Test Error (type mismatch) ' + helper.getCall() +
+ '\nActual Type: ' + typeof actual +
+ '\nExpected Type:' + typeof expected);
}
}
@@ -284,7 +420,8 @@ var currentTestCase = null;
* @throws {Error} always.
**/
function assertNotReached(message) {
- throw new Error(message);
+ helper.registerCall();
+ throw new Error(helper.getCall());
}
/**
@@ -304,13 +441,16 @@ var currentTestCase = null;
* @see runTest
**/
function createExpect(assertFunc) {
- return function() {
+ var expectFunc = function() {
try {
assertFunc.apply(null, arguments);
} catch (e) {
errors.push(e);
}
};
+ expectFunc.isExpect = true;
+ expectFunc.expectName = assertFunc.name.replace(/^assert/, 'expect');
+ return expectFunc;
}
/**
@@ -325,25 +465,31 @@ var currentTestCase = null;
* @see createExpect
**/
function runTest(testFunction, testArguments) {
- errors = [];
+ errors.splice(0, errors.length);
// Avoid eval() if at all possible, since it will not work on pages
// that have enabled content-security-policy.
var testBody = this[testFunction]; // global object -- not a method.
if (typeof testBody === "undefined")
testBody = eval(testFunction);
- if (testBody != RUN_TEST_F)
- console.log('Running test ' + testBody.name);
+ if (testBody != RUN_TEST_F) {
+ var testName =
+ testFunction.name ? testFunction.name : testBody.toString();
+ console.log('Running test ' + testName);
+ }
createExpect(testBody).apply(null, testArguments);
+ var result = [true];
if (errors.length) {
+ var message = '';
for (var i = 0; i < errors.length; ++i) {
- console.log('Failed: ' + testFunction + '(' +
- testArguments.toString() + ')\n' + errors[i].stack);
+ message += 'Failed: ' + testFunction + '(' +
+ testArguments.map(JSON.stringify) +
+ ')\n' + errors[i].stack;
}
- return [false, errors.join('\n')];
- } else {
- return [true];
+ errors.splice(0, errors.length);
+ result = [false, message];
}
+ return result;
}
/**
« chrome/test/data/webui/assertions.js ('K') | « chrome/test/data/webui/assertions.js ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698