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

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

Issue 7645007: WebUI Testing: async support - global mocking, deferred runs, continued run. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Added ability to defer RunTest. Created 9 years, 4 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 4e554bd8722b8d8d7051966d576dba1cdbb20ed3..ba99df67131b0d57313ea0a693b0c683f37aa921 100644
--- a/chrome/test/data/webui/test_api.js
+++ b/chrome/test/data/webui/test_api.js
@@ -99,12 +99,25 @@ var currentTestArguments = null;
/**
* This should be initialized by the test fixture and can be referenced
- * during the test run.
- * @type {Mock4JS.Mock}
+ * during the test run. It holds any mocked handler methods.
+ * @type {?Mock4JS.Mock}
**/
mockHandler: null,
/**
+ * This should be initialized by the test fixture and can be referenced
+ * during the test run. It holds any mocked global functions.
+ * @type {?Mock4JS.Mock}
+ **/
+ mockGlobals: null,
+
+ /**
+ * Value is passed through call to C++ RunJavascriptF to invoke this test.
+ * @type {boolean}
+ **/
+ isAsync: false,
+
+ /**
* Override this method to perform initialization during preload (such as
* creating mocks and registering handlers).
* @type {Function}
@@ -125,7 +138,42 @@ var currentTestArguments = null;
**/
TearDown: function() {
Mock4JS.verifyAllMocks();
- }
+ },
+
+ /**
+ * Called to run the body from the perspective of this fixture.
+ * @type {Function}
+ */
+ RunTest: function(testBody) {
+ testBody.call(this);
+ },
+
+ /**
+ * @param {boolean} alwaysDone When true, always call testDone().
+ * @param {Function} completion The function to call to complete the test.
+ * @return {function(): void} Return a function, bound to this test fixture,
+ * which continues the test.
+ **/
+ continueTest: function(alwaysDone, completion) {
+ var completionAction = new CallFunctionAction(
+ this, null, completion, null);
+ var runAll = new RunAllAction(
+ true, alwaysDone, [completionAction]);
+ return function() {
+ runAll.invoke();
+ };
+ },
+
+ /**
+ * Call this during SetUp to defer the call to RunTest() until later. The
+ * caller must call the returned function at some point to run the test.
+ * @type {Function}
+ * @return {function(): void} A function which will run the current body of
+ * the currentTestCase.
+ */
+ deferRunTest: function() {
+ return currentTestCase.deferRunTest();
+ },
};
/**
@@ -143,11 +191,32 @@ var currentTestArguments = null;
}
TestCase.prototype = {
+ /**
+ * The name of this test.
+ * @type {string}
+ */
name: null,
+
+ /**
+ * The test fixture to set |this| to when running the test |body|.
+ * @type {testing.Test}
+ */
fixture: null,
+
+ /**
+ * The test body to execute in RunTest().
+ * @type {Function}
+ **/
body: null,
/**
+ * True when the test fixture will run the test later.
+ * @type {boolean}
+ * @private
+ */
+ deferred_: false,
+
+ /**
* Called at preload time, proxies to the fixture.
* @type {Function}
**/
@@ -157,6 +226,30 @@ var currentTestArguments = null;
},
/**
+ * Called before a test runs.
+ **/
+ SetUp: function() {
+ if (this.fixture)
+ this.fixture.SetUp();
+ },
+
+ /**
+ * Called before a test is torn down (by testDone()).
+ **/
+ TearDown: function() {
+ if (this.fixture)
+ this.fixture.TearDown();
+ },
+
+ /**
+ * Called to run this test's body.
+ **/
+ RunTest: function() {
+ if (this.body && this.fixture)
+ this.fixture.RunTest(this.body);
+ },
+
+ /**
* Runs this test case with |this| set to the |fixture|.
*
* Note: Tests created with TEST_F may depend upon |this| being set to an
@@ -166,13 +259,36 @@ var currentTestArguments = null;
* @type {Function}
**/
Run: function() {
- if (this.fixture)
- this.fixture.SetUp();
- if (this.body)
- this.body.call(this.fixture);
- if (this.fixture)
- this.fixture.TearDown();
+ try {
+ this.SetUp();
+ } catch(e) {
+ console.error(e.stack);
+ }
+
+ if (!this.deferred_)
+ this.RunTest();
+
+ // TearDown called by testDone().
+ },
+
+ /**
+ * Cause this TestCase to be deferred (don't call RunTest()) until the
+ * returned function is called.
+ * @type {Function}
+ * @return {function(): void} A function thatwill run this TestCase when
+ * called.
+ */
+ deferRunTest: function() {
+ this.deferred_ = true;
+ var completionAction = new CallFunctionAction(
+ this, null, this.RunTest, null);
+ var runAll = new RunAllAction(
+ true, true, [completionAction]);
+ return function() {
+ runAll.invoke();
+ };
},
+
};
/**
@@ -184,7 +300,7 @@ var currentTestArguments = null;
/**
* Registers the message, object and callback for {@code chrome.send}
* @param {string} name The name of the message to route to this |callback|.
- * @param {Object} messageHAndler Pass as |this| when calling the |callback|.
+ * @param {Object} messageHandler Pass as |this| when calling the |callback|.
* @param {function(...)} callback Called by {@code chrome.send}.
* @see sendCallbacks
**/
@@ -202,11 +318,46 @@ var currentTestArguments = null;
**/
function registerMockMessageCallbacks(mockObject, mockClass) {
var mockProxy = mockObject.proxy();
- for (func in mockClass.prototype) {
+ for (var func in mockClass.prototype) {
if (typeof(mockClass.prototype[func]) == 'function') {
- registerMessageCallback(func,
- mockProxy,
- mockProxy[func]);
+ registerMessageCallback(func, mockProxy, mockProxy[func]);
+ }
+ }
+ }
+
+ /**
+ * Holds the mapping of name -> global override information.
+ * @type {Object}
+ **/
+ var globalOverrides = {};
+
+ /**
+ * Registers the global function name, object and callback.
+ * @param {string} name The name of the message to route to this |callback|.
+ * @param {Object} object Pass as |this| when calling the |callback|.
+ * @param {function(...)} callback Called by {@code chrome.send}.
+ **/
+ function registerMockGlobal(name, object, callback) {
+ assertEquals(undefined, globalOverrides[name]);
+ globalOverrides[name] = {
+ object: object,
+ callback: callback,
+ };
+ }
+
+ /**
+ * Register all methods of {@code mockClass.prototype} as overrides to global
+ * functions of the same name as the method, using the proxy of the
+ * |mockObject| to handle the functions.
+ * @param {Mock4JS.Mock} mockObject The mock to register callbacks against.
+ * @param {function(new:Object)} mockClAss Constructor for the mocked class.
+ * @see registerMessageCallback
+ **/
+ function registerMockGlobals(mockObject, mockClass) {
+ var mockProxy = mockObject.proxy();
+ for (var func in mockClass.prototype) {
+ if (typeof(mockClass.prototype[func]) == 'function') {
arv (Not doing code reviews) 2011/08/22 20:57:18 typeof should not have parentheses
Sheridan Rawlins 2011/08/23 01:06:15 Done.
+ registerMockGlobal(func, mockProxy, mockProxy[func]);
}
}
}
@@ -348,6 +499,10 @@ var currentTestArguments = null;
function testDone(result) {
if (!testIsDone) {
testIsDone = true;
+ if (currentTestCase) {
+ currentTestCase.TearDown();
+ currentTestCase = null;
+ }
chrome.send('testResult', result ? result : testResult());
errors.splice(0, errors.length);
} else {
@@ -616,6 +771,16 @@ var currentTestArguments = null;
__proto__: oldChrome,
send: send,
};
+
+ // Override globals at load time so they will be defined.
+ for (var func in globalOverrides) {
+ assertNotEquals(undefined, this[func]);
+ var globalOverride = globalOverrides[func];
+ assertNotEquals(undefined, globalOverride);
+ assertEquals(undefined, globalOverride.original);
+ globalOverride.original = this[func];
+ this[func] = globalOverride.callback.bind(globalOverride.object);
+ }
});
currentTestCase = createTestCase(testFixture, testName);
currentTestCase.PreLoad();
@@ -676,92 +841,315 @@ var currentTestArguments = null;
* @see runTest
**/
function RUN_TEST_F(testFixture, testName) {
- try {
- if (!currentTestCase)
- currentTestCase = createTestCase(testFixture, testName);
- assertEquals(currentTestCase.name, testName);
- assertEquals(currentTestCase.fixture.name, testFixture);
- console.log('Running TestCase ' + testFixture + '.' + testName);
- currentTestCase.Run();
- } finally {
- currentTestCase = null;
- }
+ if (!currentTestCase)
+ currentTestCase = createTestCase(testFixture, testName);
+ assertEquals(currentTestCase.name, testName);
+ assertEquals(currentTestCase.fixture.name, testFixture);
+ console.log('Running TestCase ' + testFixture + '.' + testName);
+ currentTestCase.Run();
}
/**
+ * Matcher which pushes the |actualArgument| of argumentMatches() onto
+ * |arguments|, then proxies the return result to the |realMatcher|. Because
+ * |realMatcher| can be any type, this constructor overrides argumentMatches,
+ * then forces |this| to be an instance of |realMatcher|.
mmenke 2011/08/22 16:26:44 You're defining a class in terms of one of its met
Sheridan Rawlins 2011/08/23 01:06:15 I rewrote this with composition instead of hacky i
+ * @param {Array} arguments The arguments to push |actualArgument| onto.
+ * @param {Object} realMatcher The real matcher check arguments with.
+ * @constructor
+ * @extends {realMatcher}
arv (Not doing code reviews) 2011/08/22 20:57:18 This annotation is a bit strange
Sheridan Rawlins 2011/08/23 01:06:15 See new version. On 2011/08/22 20:57:18, arv wrot
+ **/
+ function SaveArgumentMatcher(arguments, realMatcher) {
+ this.arguments_ = arguments;
+ this.argumentMatches = this.argumentMatches;
mmenke 2011/08/22 16:26:44 ?
Sheridan Rawlins 2011/08/23 01:06:15 Yeah, this was weird - it could have been this.arg
+ this.__proto__ = realMatcher;
arv (Not doing code reviews) 2011/08/22 20:57:18 This seems a bit hacky. How about? var obj = Obje
Sheridan Rawlins 2011/08/23 01:06:15 See new version with composition instead of the ha
+ }
+
+ SaveArgumentMatcher.prototype = {
+ /**
+ * Holds the arguments to push each |actualArgument| onto.
+ * @type {Array}
+ * @private
+ **/
+ arguments_: null,
+
+ /**
+ * Pushes |actualArgument| onto |arguments_| for later use. Clears
+ * |arguments_| on non-match.
+ * @param {*} actualArgument The argument to match and save.
+ * @return {boolean} value of calling the |realMatcher|.
+ * @override
+ **/
+ argumentMatches: function(actualArgument) {
+ this.arguments_.push(actualArgument);
+ var match = this.__proto__.argumentMatches(actualArgument);
+ if (!match)
+ this.arguments_.splice(0, this.arguments_.length);
+
+ return match;
+ },
+ };
+
+ /**
+ * Allow mock stubs() and expects() to know what arguments were passed to the
+ * |realMatcher|.
mmenke 2011/08/22 16:26:44 This isn't terribly illuminating for someone tryin
Sheridan Rawlins 2011/08/23 01:06:15 OK. Added more context to comments. On 2011/08/2
+ * @param {!Object} realMatcher The real matcher to perform matching with.
+ * @constructor
+ **/
+ function SaveMockArguments() {
+ this.arguments = [];
+ }
+
+ SaveMockArguments.prototype = {
+ /**
+ * Wraps the |realMatcher| with an object which will push its argument onto
+ * |arguments| and call realMatcher.
+ * @param {Object} realMatcher A Mock4JS matcher object for this argument.
+ * @return {SaveArgumentMatcher} A new matcher which will push its argument
+ * onto |arguments|.
+ **/
+ match: function(realMatcher) {
+ return new SaveArgumentMatcher(this.arguments, realMatcher);
+ },
+
+ /**
+ * Remember the argument passed to this stub invocation.
+ * @type {Array}
+ **/
+ arguments: null,
+ };
+
+ /**
* CallFunctionAction is provided to allow mocks to have side effects.
+ * @param {Object} obj The object to set |this| to when calling |func_|.
+ * @param {?SaveMockArguments} savedArgs when non-null, saved arguments are
+ * passed to |func|.
* @param {Function} func The function to call.
* @param {Array} args Any arguments to pass to func.
* @constructor
**/
- function CallFunctionAction(func, args) {
- this._func = func;
- this._args = args;
+ function CallFunctionAction(obj, savedArgs, func, args) {
+ this.obj_ = obj;
+ this.savedArgs_ = savedArgs;
+ this.func_ = func;
+ this.args_ = args;
}
CallFunctionAction.prototype = {
+ /**
+ * Set |this| to |obj_| when calling |func_|.
+ * @type {?Object}
+ **/
+ obj_: null,
+
+ /**
+ * The SaveMockArguments to hold arguments when invoking |func_|.
+ * @type {?SaveMockArguments}
+ * @private
+ **/
+ savedArgs_: null,
+
+ /**
+ * The function to call when invoked.
+ * @type {!Function}
+ * @private
+ **/
+ func_: null,
+
+ /**
+ * Arguments to pass to |func_| when invoked.
+ * @type {!Array}
+ **/
+ args_: null,
+
+ /**
+ * Accessor for |func_|.
+ * @return {Function} The function to invoke.
+ **/
+ get func() {
+ return this.func_;
+ },
+
+ /**
+ * Called by Mock4JS when using .will() to specify actions for stubs() or
+ * expects(). Clears |savedArgs_| so it can be reused.
+ * @return The results of calling |func_| with the concatenation of
+ * |savedArgs_| and |args_|.
+ **/
invoke: function() {
- return this._func.apply(null, this._args);
+ var prependArgs = [];
+ if (this.savedArgs_) {
+ prependArgs = this.savedArgs_.arguments.splice(
+ 0, this.savedArgs_.arguments.length);
+ }
+ return this.func.apply(this.obj_, prependArgs.concat(this.args_));
},
+
+ /**
+ * Describe this action to Mock4JS.
+ * @return {string} A description of this action.
+ **/
describe: function() {
- return 'calls the given function with arguments ' + this._args;
+ return 'calls the given function with saved arguments and ' + this.args_;
}
};
/**
* Syntactic sugar for will() on a Mock4JS.Mock.
mmenke 2011/08/22 16:26:44 nit: "for use with" make it clearer that this sho
Sheridan Rawlins 2011/08/23 01:06:15 Done.
- * @param {Function} func the function to call when the method is invoked.
- * @param {...*} var_args arguments to pass when calling func.
+ * @param {Function} func The function to call when the method is invoked.
+ * @param {...*} var_args Arguments to pass when calling func.
+ * @return {CallFunctionAction} Action for use in will.
**/
function callFunction(func) {
- return new CallFunctionAction(func,
- Array.prototype.slice.call(arguments, 1));
+ return new CallFunctionAction(
+ null, null, func, Array.prototype.slice.call(arguments, 1));
}
/**
- * Allow mock stubs() and expects() to know what arguments were passed to the
- * |realMatcher|.
- * @param {!Object} realMatcher The real matcher to perform matching with.
+ * Syntactic sugar for will() on a Mock4JS.Mock.
mmenke 2011/08/22 16:26:44 nit: "for use with"
Sheridan Rawlins 2011/08/23 01:06:15 Done.
+ * @param {SaveMockArguments} savedArgs Arguments saved with this object
+ * are passed to |func|.
+ * @param {Function} func The function to call when the method is invoked.
+ * @param {...*} var_args Arguments to pass when calling func.
+ * @return {CallFunctionAction} Action for use in will.
+ **/
+ function callFunctionWithSavedArgs(savedArgs, func) {
+ return new CallFunctionAction(
+ null, savedArgs, func, Array.prototype.slice.call(arguments, 2));
+ }
+
+ /**
+ * CallGlobalAction is provided to help override global function with mocking
+ * expectations and still call the global function.
mmenke 2011/08/22 16:26:44 nit: Say what it does, not just why it exists.
Sheridan Rawlins 2011/08/23 01:06:15 Done.
+ * @param {?SaveMockArguments} savedArgs when non-null, saved arguments are
+ * passed to the global function |funcName|.
+ * @param {Function} funcName The name of the global function to call.
+ * @param {Array} args Any arguments to pass to func.
* @constructor
+ * @extends {CallFunctionAction}
+ * @see globalOverrides
**/
- function SaveArgumentsMatcher(realMatcher) {
- this.realMatcher_ = realMatcher;
+ function CallGlobalAction(savedArgs, funcName, args) {
+ CallFunctionAction.call(this, null, savedArgs, funcName, args);
}
- SaveArgumentsMatcher.prototype = {
+ CallGlobalAction.prototype = {
+ __proto__: CallFunctionAction.prototype,
+
/**
- * Remember the argument passed to this stub invocation.
- * @type {*}
+ * Fetch and return the original global function to call.
+ * @return {Function} The global function to invoke.
+ * @override
**/
- argument: undefined,
+ get func() {
+ var func = globalOverrides[this.func_].original;
+ assertNotEquals(undefined, func);
+ return func;
+ },
+ };
+ /**
+ * Syntactic sugar for will() on a Mock4JS.Mock.
mmenke 2011/08/22 16:26:44 nit: For use with.
Sheridan Rawlins 2011/08/23 01:06:15 Done.
+ * @param {SaveMockArguments} savedArgs Arguments saved with this object
+ * are passed to the global function |funcName|.
+ * @param {Function} funcName The name of the global function to call when the
+ * method is invoked.
mmenke 2011/08/22 16:26:44 nit: Name of a registered mock global function.
Sheridan Rawlins 2011/08/23 01:06:15 Done.
+ * @param {...*} var_args Arguments to pass when calling func.
+ * @return {CallGlobalAction} Action for use in will.
+ **/
+ function callGlobalWithSavedArgs(savedArgs, funcName) {
+ return new CallGlobalAction(
+ savedArgs, funcName, Array.prototype.slice.call(arguments, 2));
+ }
+
+ /**
+ * Runs all |actions|.
+ * @param {boolean} isAsync When true, call testDone() on Errors.
+ * @param {boolean} alwaysDone When true, always call testDone().
+ * @param {Array.<Object>} actions Actions to run.
+ * @constructor
+ **/
+ function RunAllAction(isAsync, alwaysDone, actions) {
+ this.isAsync_ = isAsync;
+ this.alwaysDone_ = alwaysDone;
+ this.actions_ = actions;
+ }
+
+ RunAllAction.prototype = {
/**
- * @type {Object} the object performing the real match.
+ * When true, call testDone() on Errors.
+ * @type {boolean}
* @private
- */
- realMatcher_: null,
+ **/
+ isAsync_: false,
/**
- * Saves |actualArgument| for later use by the mock stub or expect.
- * @param {*} actualArgument The argument to match and save.
- * @return {boolean} value of calling the |realMatcher_|.
+ * When true, always call testDone().
+ * @type {boolean}
+ * @private
**/
- argumentMatches: function(actualArgument) {
- this.argument = actualArgument;
- return this.realMatcher_.argumentMatches.call(this.realMatcher_,
- actualArgument);
+ alwaysDone_: false,
+
+ /**
+ * Holds the actions to execute when invoked.
+ * @type {Array}
+ * @private
+ **/
+ actions_: null,
+
+ /**
+ * Runs all |actions_|, returning the last one.
+ **/
+ invoke: function() {
+ try {
+ var result;
+ for(var i = 0; i < this.actions_.length; ++i) {
+ result = this.actions_[i].invoke.apply(
+ this.actions_[i], Array.prototype.slice.call(arguments));
+ }
+ if (this.alwaysDone_)
+ testDone();
+
+ return result;
+ } catch (e) {
+ if (!this.isAsync_)
+ throw e;
+
+ errors.push(e);
+ testDone();
mmenke 2011/08/22 16:26:44 You should also call testDone on expect failures,
Sheridan Rawlins 2011/08/23 01:06:15 I don't think so. There should be a distinction be
mmenke 2011/08/23 01:36:09 Then should probably add a way to tell if there's
Sheridan Rawlins 2011/08/23 05:52:18 Ok. I added a whenTestDone enum to describe when
mmenke 2011/08/23 15:26:29 Seems reasonable to me.
+ }
},
/**
- * Generic description for this Matcher object.
- * @return {string} description of the matcher for this argument.
+ * Describe this action to Mock4JS.
+ * @return {string} A description of this action.
**/
describe: function() {
- return 'SaveArguments(' +
- this.realMatcher_.describe.call(this.realMatcher_) + ')';
+ return 'Calls all actions: ' + this.actions_;
},
};
+ /**
+ * Syntactic sugar for will() on a Mock4JS.Mock.
+ * @param {...Object} var_actions Actions to run.
+ * @return {RunAllAction} Action for use in will.
+ **/
+ function runAllActions() {
+ return new RunAllAction(false, false,
+ Array.prototype.slice.call(arguments));
+ }
+
+ /**
+ * Syntactic sugar for will() on a Mock4JS.Mock.
+ * @param {boolean} alwaysDone When true, always call testDone() otherwise,
+ * call it only when Errors are thrown (from asserts).
+ * @return {RunAllAction} Action for use in will.
+ **/
+ function runAllActionsAsync(alwaysDone) {
+ return new RunAllAction(true, alwaysDone,
+ Array.prototype.slice.call(arguments));
+ }
+
// Exports.
testing.Test = Test;
window.testDone = testDone;
@@ -775,6 +1163,8 @@ var currentTestArguments = null;
window.assertNotEquals = assertNotEquals;
window.assertNotReached = assertNotReached;
window.callFunction = callFunction;
+ window.callFunctionWithSavedArgs = callFunctionWithSavedArgs;
+ window.callGlobalWithSavedArgs = callGlobalWithSavedArgs;
window.expectTrue = createExpect(assertTrue);
window.expectFalse = createExpect(assertFalse);
window.expectGE = createExpect(assertGE);
@@ -786,11 +1176,14 @@ var currentTestArguments = null;
window.expectNotReached = createExpect(assertNotReached);
window.preloadJavascriptLibraries = preloadJavascriptLibraries;
window.registerMessageCallback = registerMessageCallback;
+ window.registerMockGlobals = registerMockGlobals;
window.registerMockMessageCallbacks = registerMockMessageCallbacks;
window.resetTestState = resetTestState;
+ window.runAllActions = runAllActions;
+ window.runAllActionsAsync = runAllActionsAsync;
window.runTest = runTest;
window.runTestFunction = runTestFunction;
- window.SaveArgumentsMatcher = SaveArgumentsMatcher;
+ window.SaveMockArguments = SaveMockArguments;
window.TEST = TEST;
window.TEST_F = TEST_F;
window.GEN = GEN;
« chrome/test/data/webui/async_gen.js ('K') | « chrome/test/data/webui/print_preview.js ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698