summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js/testing.js
diff options
context:
space:
mode:
authorXavier Morel <xmo@openerp.com>2012-11-16 08:17:12 +0100
committerXavier Morel <xmo@openerp.com>2012-11-16 08:17:12 +0100
commita9645151b6f070d23d2f35810aee562c1354aba9 (patch)
tree04a4265b454d75291001a49ee4285b1faab63f92 /addons/web/static/src/js/testing.js
parent8e85f4e0bd0b1fbbf401686b85d7282f8991454d (diff)
[IMP] setup & teardown of test runner
simplify code and make setup & teardown processes more reliable add testing.Stack tools which stacks promise-returning functions around the actual promise-returning function to execute (the test case itself). testing.Stack returns an object with 3 methods, ``push([setup][, teardown])``, ``unshift([setup][, teardown])`` and ``execute(fn, *args)``. ``push`` and ``unshift`` create a new stack with the provided setup and teardown added respectively at the top and bottom of the stack. ``execute`` will walk the stack from bottom to top executing ``setup`` handlers (and waiting on their result), if all setup handlers execute without failure the ``fn`` callback gets executed (and waited on) then *all* ``teardown`` handlers are executed from top to bottom: | setup | setup | setup | setup | fn | teardown | teardown | teardown V teardown If a ``setup`` handler fails (the promise is rejected), teardowns will start executing *from the previous level* (so the ``teardown`` matching the failed ``setup`` will *not* be run), but all ``teardowns`` below that will be run regardless, even if one fails the next one will still be executed. The stack will either ultimately return a promise rejection using the *first* rejection it got (a rejection may be cascading, so the rejection of a setup may also lead to or be linked to a teardown being rejected later), or will return the resolution from ``fn``. If ``execute`` is passed further arguments, those arguments will in turn be forwarded to ``fn`` as well as all ``setup`` and ``teardown`` handlers. bzr revid: xmo@openerp.com-20121116071712-zuld957icellezum
Diffstat (limited to 'addons/web/static/src/js/testing.js')
-rw-r--r--addons/web/static/src/js/testing.js184
1 files changed, 119 insertions, 65 deletions
diff --git a/addons/web/static/src/js/testing.js b/addons/web/static/src/js/testing.js
index 7686499b939..10fe7ecd778 100644
--- a/addons/web/static/src/js/testing.js
+++ b/addons/web/static/src/js/testing.js
@@ -85,6 +85,90 @@ openerp.testing = {};
};
};
+ var StackProto = {
+ execute: function (fn) {
+ var args = [].slice.call(arguments, 1);
+ // Warning: here be dragons
+ var i = 0, setups = this.setups, teardowns = this.teardowns;
+ var d = $.Deferred();
+
+ var succeeded, failed;
+ var success = function () {
+ succeeded = _.toArray(arguments);
+ return teardown();
+ };
+ var failure = function () {
+ // save first failure
+ if (!failed) {
+ failed = _.toArray(arguments);
+ }
+ // chain onto next teardown
+ return teardown();
+ };
+
+ var setup = function () {
+ // if setup to execute
+ if (i < setups.length) {
+ var f = setups[i] || testing.noop;
+ $.when(f.apply(null, args)).then(function () {
+ ++i;
+ setup();
+ }, failure);
+ } else {
+ var actual_call;
+ try {
+ actual_call = $.when(fn.apply(null, args))
+ } catch (e) {
+ actual_call = $.Deferred().reject(e);
+ }
+ actual_call.pipe(success, failure);
+ }
+ };
+ var teardown = function () {
+ // if teardown to execute
+ if (i > 0) {
+ var f = teardowns[--i] || testing.noop;
+ $.when(f.apply(null, args)).then(teardown, failure);
+ } else {
+ if (failed) {
+ d.reject.apply(d, failed);
+ } else if (succeeded) {
+ d.resolve.apply(d, succeeded);
+ } else {
+ throw new Error("Didn't succeed or fail?");
+ }
+ }
+ };
+ setup();
+
+ return d;
+ },
+ push: function (setup, teardown) {
+ return _.extend(Object.create(StackProto), {
+ setups: this.setups.concat([setup]),
+ teardowns: this.teardowns.concat([teardown])
+ });
+ },
+ unshift: function (setup, teardown) {
+ return _.extend(Object.create(StackProto), {
+ setups: [setup].concat(this.setups),
+ teardowns: [teardown].concat(this.teardowns)
+ });
+ }
+ };
+ /**
+ *
+ * @param {Function} [setup]
+ * @param {Function} [teardown]
+ * @return {*}
+ */
+ testing.Stack = function (setup, teardown) {
+ return _.extend(Object.create(StackProto), {
+ setups: setup ? [setup] : [],
+ teardowns: teardown ? [teardown] : []
+ });
+ };
+
var db = window['oe_db_info'];
testing.section = function (name, options, body) {
if (_.isFunction(options)) {
@@ -104,10 +188,6 @@ openerp.testing = {};
callback = options;
options = {};
}
- _.defaults(options, {
- setup: testing.noop,
- teardown: testing.noop
- });
var module = testing.current_module;
var module_index = _.indexOf(testing.dependencies, module);
@@ -120,27 +200,15 @@ openerp.testing = {};
// Serialize options for this precise test case
// WARNING: typo is from jquery, do not fix!
var env = QUnit.config.currentModuleTestEnviroment;
- var opts = _.defaults({
- // section setup
- // case setup
- // test
- // case teardown
- // section teardown
- setup: function () {
- var args = [].slice.call(arguments);
- return $.when(env._oe.setup.apply(null, args))
- .pipe(function () {
- return options.setup.apply(null, args);
- });
- },
- teardown: function () {
- var args = [].slice.call(arguments);
- return $.when(options.teardown.apply(null, args))
- .pipe(function () {
- return env._oe.teardown.apply(null, args);
- });
- }
- }, options, env._oe);
+ // section setup
+ // case setup
+ // test
+ // case teardown
+ // section teardown
+ var case_stack = testing.Stack()
+ .push(env._oe.setup, env._oe.teardown)
+ .push(options.setup, options.teardown);
+ var opts = _.defaults({}, options, env._oe);
// FIXME: if this test is ignored, will still query
if (opts.rpc === 'rpc' && !db) {
QUnit.config.autostart = false;
@@ -177,9 +245,6 @@ openerp.testing = {};
}
QUnit.test(name, function () {
- // module testing environment
- var self = this;
-
var instance;
if (!opts.dependencies) {
instance = openerp.init(module_deps);
@@ -240,10 +305,11 @@ openerp.testing = {};
break;
case 'rpc':
async = true;
- (function (setup, teardown) {
+ (function () {
// Bunch of random base36 characters
var dbname = 'test_' + Math.random().toString(36).slice(2);
- opts.setup = function (instance, $s) {
+ // Add db setup/teardown at the start of the stack
+ case_stack = case_stack.unshift(function (instance) {
// FIXME hack: don't want the session to go through shitty loading process of everything
instance.session.session_init = testing.noop;
instance.session.load_modules = testing.noop;
@@ -260,38 +326,27 @@ openerp.testing = {};
}
return instance.session.session_authenticate(
dbname, 'admin', db.password, true);
- }).pipe(function () {
- return setup(instance, $s);
});
- };
- opts.teardown = function (instance, $s) {
- return $.when(teardown(instance, $s)).pipe(function () {
- return instance.session.session_logout()
- }).pipe(function () {
- return instance.session.rpc('/web/database/drop', {
+ }, function (instance) {
+ return instance.session.rpc('/web/database/drop', {
fields: [
{name: 'drop_pwd', value: db.supadmin},
{name: 'drop_db', value: dbname}
]
- });
- }).pipe(function (result) {
+ }).pipe(function (result) {
if (result.error) {
return $.Deferred().reject(result.error).promise();
}
return result;
});
- };
- })(opts.setup, opts.teardown);
+ });
+ })();
}
// Always execute tests asynchronously
stop();
var timeout;
- var teardown = function () {
- return opts.teardown(instance, $fixture, mock)
- };
- $.when(opts.setup(instance, $fixture, mock))
- .pipe(function () {
+ case_stack.execute(function () {
var result = callback(instance, $fixture, mock);
if (!(result && _.isFunction(result.then))) {
if (async) {
@@ -303,25 +358,24 @@ openerp.testing = {};
+ "number of assertions they expect");
}
}
- var d = $.Deferred();
- $.when(result).then(function () {
- d.resolve.apply(d, arguments)
- }, function () {
- d.reject.apply(d, arguments);
+
+ return $.Deferred(function (d) {
+ $.when(result).then(function () {
+ d.resolve.apply(d, arguments)
+ }, function () {
+ d.reject.apply(d, arguments);
+ });
+ if (async || (result && result.then)) {
+ // async test can be either implicit async (rpc) or
+ // promise-returning
+ timeout = setTimeout(function () {
+ QUnit.config.semaphore = 1;
+ d.reject({message: "Test timed out"});
+ }, 2000);
+ }
});
- if (async || (result && result.then)) {
- // async test can be either implicit async (rpc) or
- // promise-returning
- timeout = setTimeout(function () {
- QUnit.config.semaphore = 1;
- d.reject({message: "Test timed out"});
- }, 2000);
- }
- return d.pipe(teardown, teardown);
- }).always(function () {
- if (timeout) {
- clearTimeout(timeout);
- }
+ }, instance, $fixture, mock).always(function () {
+ if (timeout) { clearTimeout(timeout); }
start();
}).fail(function (error) {
if (options.fail_on_rejection === false) {