[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
This commit is contained in:
parent
8e85f4e0bd
commit
a9645151b6
|
@ -69,6 +69,7 @@ This module provides the core of the OpenERP Web Client.
|
|||
"static/src/xml/*.xml",
|
||||
],
|
||||
'test': [
|
||||
"static/test/testing.js",
|
||||
"static/test/class.js",
|
||||
"static/test/registry.js",
|
||||
"static/test/form.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) {
|
||||
|
|
|
@ -0,0 +1,253 @@
|
|||
openerp.testing.section('testing.stack', function (test) {
|
||||
// I heard you like tests, so I put tests in your testing infrastructure,
|
||||
// so you can test what you test
|
||||
var reject = function () {
|
||||
// utility function, rejects a success
|
||||
var args = _.toArray(arguments);
|
||||
return $.Deferred(function (d) {
|
||||
d.reject.apply(d, ["unexpected success"].concat(args));
|
||||
});
|
||||
};
|
||||
test('direct, value, success', {asserts: 1}, function () {
|
||||
var s = openerp.testing.Stack();
|
||||
return s.execute(function () {
|
||||
return 42;
|
||||
}).then(function (val) {
|
||||
strictEqual(val, 42, "should return the handler value");
|
||||
});
|
||||
});
|
||||
test('direct, deferred, success', {asserts: 1}, function () {
|
||||
var s = openerp.testing.Stack();
|
||||
return s.execute(function () {
|
||||
return $.when(42);
|
||||
}).then(function (val) {
|
||||
strictEqual(val, 42, "should return the handler value")
|
||||
});
|
||||
});
|
||||
test('direct, value, error', {asserts: 1}, function () {
|
||||
var s = openerp.testing.Stack();
|
||||
return s.execute(function () {
|
||||
throw new Error("foo");
|
||||
}).pipe(reject, function (f) {
|
||||
strictEqual(f.message, "foo", "should reject with exception");
|
||||
return $.when();
|
||||
});
|
||||
});
|
||||
test('direct, deferred, failure', {asserts: 1}, function () {
|
||||
var s = openerp.testing.Stack();
|
||||
return s.execute(function () {
|
||||
return $.Deferred(function (d) {
|
||||
d.reject("failed");
|
||||
});
|
||||
}).pipe(reject, function (f) {
|
||||
strictEqual(f, "failed", "should propagate failure");
|
||||
return $.when();
|
||||
});
|
||||
});
|
||||
|
||||
test('successful setup', {asserts: 2}, function () {
|
||||
var setup_done = false;
|
||||
var s = openerp.testing.Stack();
|
||||
return s.push(function () {
|
||||
return $.Deferred(function (d) {
|
||||
setTimeout(function () {
|
||||
setup_done = true;
|
||||
d.resolve(2);
|
||||
}, 50);
|
||||
});
|
||||
}).execute(function () {
|
||||
return 42;
|
||||
}).then(function (val) {
|
||||
ok(setup_done, "should have executed setup");
|
||||
strictEqual(val, 42, "should return executed function value (not setup)");
|
||||
});
|
||||
});
|
||||
test('successful teardown', {asserts: 2}, function () {
|
||||
var teardown = false;
|
||||
var s = openerp.testing.Stack();
|
||||
return s.push(null, function () {
|
||||
return $.Deferred(function (d) {
|
||||
setTimeout(function () {
|
||||
teardown = true;
|
||||
d.resolve(2);
|
||||
}, 50);
|
||||
});
|
||||
}).execute(function () {
|
||||
return 42;
|
||||
}).then(function (val) {
|
||||
ok(teardown, "should have executed teardown");
|
||||
strictEqual(val, 42, "should return executed function value (not setup)");
|
||||
});
|
||||
});
|
||||
test('successful setup and teardown', {asserts: 3}, function () {
|
||||
var setup = false, teardown = false;
|
||||
var s = openerp.testing.Stack();
|
||||
return s.push(function () {
|
||||
return $.Deferred(function (d) {
|
||||
setTimeout(function () {
|
||||
setup = true;
|
||||
d.resolve(2);
|
||||
}, 50);
|
||||
});
|
||||
}, function () {
|
||||
return $.Deferred(function (d) {
|
||||
setTimeout(function () {
|
||||
teardown = true;
|
||||
d.resolve(2);
|
||||
}, 50);
|
||||
});
|
||||
}).execute(function () {
|
||||
return 42;
|
||||
}).then(function (val) {
|
||||
ok(setup, "should have executed setup");
|
||||
ok(teardown, "should have executed teardown");
|
||||
strictEqual(val, 42, "should return executed function value (not setup)");
|
||||
});
|
||||
});
|
||||
|
||||
test('multiple setups', {asserts: 2}, function () {
|
||||
var setups = 0;
|
||||
var s = openerp.testing.Stack();
|
||||
return s.push(function () {
|
||||
setups++;
|
||||
}).push(function () {
|
||||
setups++;
|
||||
}).push(function () {
|
||||
setups++;
|
||||
}).push(function () {
|
||||
setups++;
|
||||
}).execute(function () {
|
||||
return 42;
|
||||
}).then(function (val) {
|
||||
strictEqual(setups, 4, "should have executed all setups of stack");
|
||||
strictEqual(val, 42);
|
||||
});
|
||||
});
|
||||
test('multiple teardowns', {asserts: 2}, function () {
|
||||
var teardowns = 0;
|
||||
var s = openerp.testing.Stack();
|
||||
return s.push(null, function () {
|
||||
teardowns++;
|
||||
}).push(null, function () {
|
||||
teardowns++;
|
||||
}).push(null, function () {
|
||||
teardowns++;
|
||||
}).push(null, function () {
|
||||
teardowns++;
|
||||
}).execute(function () {
|
||||
return 42;
|
||||
}).then(function (val) {
|
||||
strictEqual(teardowns, 4, "should have executed all teardowns of stack");
|
||||
strictEqual(val, 42);
|
||||
});
|
||||
});
|
||||
test('holes in setups', {asserts: 2}, function () {
|
||||
var setups = [];
|
||||
var s = openerp.testing.Stack();
|
||||
return s.push(function () {
|
||||
setups.push(0);
|
||||
}).push().push().push(function () {
|
||||
setups.push(3);
|
||||
}).push(function () {
|
||||
setups.push(4);
|
||||
}).push().push(function () {
|
||||
setups.push(6);
|
||||
}).execute(function () {
|
||||
return 42;
|
||||
}).then(function (val) {
|
||||
deepEqual(setups, [0, 3, 4, 6],
|
||||
"should have executed setups in correct order");
|
||||
strictEqual(val, 42);
|
||||
});
|
||||
});
|
||||
test('holes in teardowns', {asserts: 2}, function () {
|
||||
var teardowns = [];
|
||||
var s = openerp.testing.Stack();
|
||||
return s.push(null, function () {
|
||||
teardowns.push(0);
|
||||
}).push().push().push(null, function () {
|
||||
teardowns.push(3);
|
||||
}).push(null, function () {
|
||||
teardowns.push(4);
|
||||
}).push().push(null, function () {
|
||||
teardowns.push(6);
|
||||
}).execute(function () {
|
||||
return 42;
|
||||
}).then(function (val) {
|
||||
deepEqual(teardowns, [6, 4, 3, 0],
|
||||
"should have executed teardowns in correct order");
|
||||
strictEqual(val, 42);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
test('failed setup', {asserts: 5}, function () {
|
||||
var setup, teardown, teardown2, code;
|
||||
return openerp.testing.Stack().push(function () {
|
||||
setup = true;
|
||||
}, function () {
|
||||
teardown = true;
|
||||
}).push(function () {
|
||||
return $.Deferred().reject("Fail!");
|
||||
}, function () {
|
||||
teardown2 = true;
|
||||
}).execute(function () {
|
||||
code = true;
|
||||
return 42;
|
||||
}).pipe(reject, function (m) {
|
||||
ok(setup, "should have executed first setup function");
|
||||
ok(teardown, "should have executed first teardown function");
|
||||
ok(!teardown2, "should not have executed second teardown function");
|
||||
strictEqual(m, "Fail!", "should return setup failure message");
|
||||
ok(!code, "should not have executed callback");
|
||||
return $.when();
|
||||
});
|
||||
});
|
||||
test('failed teardown', {asserts: 2}, function () {
|
||||
var teardowns = 0;
|
||||
return openerp.testing.Stack().push(null, function () {
|
||||
teardowns++;
|
||||
return $.Deferred().reject('Fail 1');
|
||||
}).push(null, function () {
|
||||
teardowns++;
|
||||
}).push(null, function () {
|
||||
teardowns++;
|
||||
return $.Deferred().reject('Fail 3');
|
||||
}).execute(function () {
|
||||
return 42;
|
||||
}).pipe(reject, function (m) {
|
||||
strictEqual(teardowns, 3,
|
||||
"should have tried executing all teardowns");
|
||||
strictEqual(m, "Fail 3", "should return first failure message");
|
||||
return $.when();
|
||||
});
|
||||
});
|
||||
test('failed call + teardown', {asserts: 2}, function () {
|
||||
var teardowns = 0;
|
||||
return openerp.testing.Stack().push(null, function () {
|
||||
teardowns++;
|
||||
}).push(null, function () {
|
||||
teardowns++;
|
||||
return $.Deferred().reject('Fail 2');
|
||||
}).execute(function () {
|
||||
return $.Deferred().reject("code");
|
||||
}).pipe(reject, function (m) {
|
||||
strictEqual(teardowns, 2,
|
||||
"should have tried executing all teardowns");
|
||||
strictEqual(m, "code", "should return first failure message");
|
||||
return $.when();
|
||||
});
|
||||
});
|
||||
|
||||
test('arguments passing', {asserts: 9}, function () {
|
||||
var asserter = function (a, b, c) {
|
||||
strictEqual(a, 1);
|
||||
strictEqual(b, "foo");
|
||||
deepEqual(c, {bar: "baz", qux: 42});
|
||||
};
|
||||
|
||||
return openerp.testing.Stack()
|
||||
.push(asserter, asserter)
|
||||
.execute(asserter, 1, "foo", {bar: 'baz', qux: 42});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue