From 99a2dd3938c7d7d4b7e9c724ec8acbb5eeedf35f Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 25 Oct 2012 17:47:45 +0200 Subject: [ADD] first 90% of non-sucky testing API, last 90% left also all existing tests shoud now be completely broken and need to be fixed bzr revid: xmo@openerp.com-20121025154745-rw2gktfd6tp68k2m --- addons/web/static/src/js/testing.js | 179 ++++++++++++++++++++++++++++++++++++ 1 file changed, 179 insertions(+) create mode 100644 addons/web/static/src/js/testing.js (limited to 'addons/web/static/src/js/testing.js') diff --git a/addons/web/static/src/js/testing.js b/addons/web/static/src/js/testing.js new file mode 100644 index 00000000000..4eadc3a5415 --- /dev/null +++ b/addons/web/static/src/js/testing.js @@ -0,0 +1,179 @@ +// Test support structures and methods for OpenERP +openerp.testing = {}; +(function (testing) { + var dependencies = { + corelib: [], + coresetup: ['corelib'], + data: ['corelib', 'coresetup'], + dates: [], + formats: ['coresetup', 'dates'], + chrome: ['corelib', 'coresetup'], + views: ['corelib', 'coresetup', 'data', 'chrome'], + search: ['data', 'coresetup', 'formats'], + list: ['views', 'data'], + form: ['data', 'views', 'list', 'formats'], + list_editable: ['list', 'form', 'data'], + }; + + testing.dependencies = window['oe_all_dependencies'] || []; + testing.current_module = null; + testing.templates = { }; + testing.add_template = function (name) { + var xhr = QWeb2.Engine.prototype.get_xhr(); + xhr.open('GET', name, false); + xhr.send(null); + (testing.templates[testing.current_module] = + testing.templates[testing.current_module] || []) + .push(xhr.responseXML); + }; + /** + * Function which does not do anything + */ + testing.noop = function () { }; + /** + * Alter provided instance's ``session`` attribute to make response + * mockable: + * + * * The ``responses`` parameter can be used to provide a map of (RPC) + * paths (e.g. ``/web/view/load``) to a function returning a response + * to the query. + * * ``instance.session`` grows a ``responses`` attribute which is + * a map of the same (and is in fact initialized to the ``responses`` + * parameter if one is provided) + * + * Note that RPC requests to un-mocked URLs will be rejected with an + * error message: only explicitly specified urls will get a response. + * + * Mocked sessions will *never* perform an actual RPC connection. + * + * @param instance openerp instance being initialized + * @param {Object} [responses] + */ + testing.mockifyRPC = function (instance, responses) { + var session = instance.session; + session.responses = responses || {}; + session.rpc_function = function (url, payload) { + var fn, params; + var needle = payload.params.model + ':' + payload.params.method; + if (url.url === '/web/dataset/call_kw' + && needle in this.responses) { + fn = this.responses[needle]; + params = [ + payload.params.args || [], + payload.params.kwargs || {} + ]; + } else { + fn = this.responses[url.url]; + params = [payload]; + } + + if (!fn) { + return $.Deferred().reject({}, 'failed', + _.str.sprintf("Url %s not found in mock responses, with arguments %s", + url.url, JSON.stringify(payload.params)) + ).promise(); + } + try { + return $.when(fn.apply(null, params)).pipe(function (result) { + // Wrap for RPC layer unwrapper thingy + return {result: result}; + }); + } catch (e) { + // not sure why this looks like that + return $.Deferred().reject({}, 'failed', String(e)); + } + }; + }; + + var _load = function (instance, module, loaded) { + if (!loaded) { loaded = []; } + + var deps = dependencies[module]; + if (!deps) { throw new Error("Unknown dependencies for " + module); } + + var to_load = _.difference(deps, loaded); + while (!_.isEmpty(to_load)) { + _load(instance, to_load[0], loaded); + to_load = _.difference(deps, loaded); + } + openerp.web[module](instance); + loaded.push(module); + }; + + testing.section = function (name, body) { + QUnit.module(testing.current_module + '.' + name); + body(testing.case); + }; + testing.case = function (name, options, callback) { + if (_.isFunction(options)) { + callback = options; + options = {}; + } + + var module = testing.current_module; + var module_index = _.indexOf(testing.dependencies, module); + var module_deps = testing.dependencies.slice( + // If module not in deps (because only tests, no JS) -> indexOf + // returns -1 -> index becomes 0 -> replace with ``undefined`` so + // Array#slice returns a full copy + 0, module_index + 1 || undefined); + QUnit.test(name, function (env) { + var instance = openerp.init(module_deps); + if (_.isNumber(options.asserts)) { + expect(options.asserts) + } + + if (options.templates) { + for(var i=0; i Date: Fri, 26 Oct 2012 10:45:43 +0200 Subject: [IMP] port search test to new framework, add handling of web.{submodule} dependencies bzr revid: xmo@openerp.com-20121026084543-fobkc3ta5q2jc3q6 --- addons/web/static/src/js/testing.js | 111 +++++++++++++++++++++++++++--------- 1 file changed, 84 insertions(+), 27 deletions(-) (limited to 'addons/web/static/src/js/testing.js') diff --git a/addons/web/static/src/js/testing.js b/addons/web/static/src/js/testing.js index 4eadc3a5415..e4f327bf0c3 100644 --- a/addons/web/static/src/js/testing.js +++ b/addons/web/static/src/js/testing.js @@ -85,23 +85,17 @@ openerp.testing = {}; }; }; - var _load = function (instance, module, loaded) { - if (!loaded) { loaded = []; } - - var deps = dependencies[module]; - if (!deps) { throw new Error("Unknown dependencies for " + module); } - - var to_load = _.difference(deps, loaded); - while (!_.isEmpty(to_load)) { - _load(instance, to_load[0], loaded); - to_load = _.difference(deps, loaded); + testing.section = function (name, options, body) { + if (_.isFunction(options)) { + body = options; + options = {}; } - openerp.web[module](instance); - loaded.push(module); - }; + _.defaults(options, { + setup: testing.noop, + teardown: testing.noop + }); - testing.section = function (name, body) { - QUnit.module(testing.current_module + '.' + name); + QUnit.module(testing.current_module + '.' + name, {_oe: options}); body(testing.case); }; testing.case = function (name, options, callback) { @@ -109,6 +103,10 @@ 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); @@ -117,13 +115,66 @@ openerp.testing = {}; // returns -1 -> index becomes 0 -> replace with ``undefined`` so // Array#slice returns a full copy 0, module_index + 1 || undefined); - QUnit.test(name, function (env) { - var instance = openerp.init(module_deps); - if (_.isNumber(options.asserts)) { - expect(options.asserts) + QUnit.test(name, function () { + // module testing environment + var self = this; + var opts = _.defaults({ + // section setup + // case setup + // test + // case teardown + // section teardown + setup: function () { + if (self._oe.setup.apply(null, arguments)) { + throw new Error("Asynchronous setup not implemented"); + } + if (options.setup.apply(null, arguments)) { + throw new Error("Asynchronous setup not implemented"); + } + }, + teardown: function () { + if (options.teardown.apply(null, arguments)) { + throw new Error("Asynchronous teardown not implemented"); + } + if (self._oe.teardown(null, arguments)) { + throw new Error("Asynchronous teardown not implemented"); + } + } + }, options, this._oe); + + var instance; + if (!opts.dependencies) { + instance = openerp.init(module_deps); + } else { + // empty-but-specified dependencies actually allow running + // without loading any module into the instance + + // TODO: clean up this mess + var d = opts.dependencies.slice(); + var di = 0; + while (di < d.length) { + var m = /^web\.(\w+)$/.exec(d[di]); + if (m) { + d[di] = m[1]; + } + d.splice.apply(d, [di+1, 0].concat( + _(dependencies[d[di]]).reverse())); + ++di; + } + + instance = openerp.init("fuck your shit, don't load anything you cunt"); + _(d).chain() + .reverse() + .uniq() + .each(function (module) { + openerp.web[module](instance); + }); + } + if (_.isNumber(opts.asserts)) { + expect(opts.asserts); } - if (options.templates) { + if (opts.templates) { for(var i=0; i Date: Fri, 26 Oct 2012 11:21:44 +0200 Subject: [IMP] port basic/existing tests to new framework, apart from editor stuff bzr revid: xmo@openerp.com-20121026092144-0arxx2v9oi28e3d6 --- addons/web/static/src/js/testing.js | 3 +++ 1 file changed, 3 insertions(+) (limited to 'addons/web/static/src/js/testing.js') diff --git a/addons/web/static/src/js/testing.js b/addons/web/static/src/js/testing.js index e4f327bf0c3..45728902e7f 100644 --- a/addons/web/static/src/js/testing.js +++ b/addons/web/static/src/js/testing.js @@ -151,6 +151,9 @@ openerp.testing = {}; // TODO: clean up this mess var d = opts.dependencies.slice(); + // dependencies list should be in deps order, reverse to make + // loading order from last + d.reverse(); var di = 0; while (di < d.length) { var m = /^web\.(\w+)$/.exec(d[di]); -- cgit v1.2.3 From bf4a26d3e8613c24b3a360013525cfef28458176 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 29 Oct 2012 12:01:54 +0100 Subject: [ADD] actual-RPC tests, with UI. Bump QUnit timeout to 10s so RPC can run at all bzr revid: xmo@openerp.com-20121029110154-5927gaix8k0ijl0c --- addons/web/static/src/js/testing.js | 182 ++++++++++++++++++++++++++---------- 1 file changed, 131 insertions(+), 51 deletions(-) (limited to 'addons/web/static/src/js/testing.js') diff --git a/addons/web/static/src/js/testing.js b/addons/web/static/src/js/testing.js index 45728902e7f..744bd9fa2cd 100644 --- a/addons/web/static/src/js/testing.js +++ b/addons/web/static/src/js/testing.js @@ -85,6 +85,7 @@ openerp.testing = {}; }; }; + var db = window['oe_db_info'] || undefined; testing.section = function (name, options, body) { if (_.isFunction(options)) { body = options; @@ -115,32 +116,69 @@ openerp.testing = {}; // returns -1 -> index becomes 0 -> replace with ``undefined`` so // Array#slice returns a full copy 0, module_index + 1 || undefined); + + // 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); + // FIXME: if this test is ignored, will still query + if (opts.rpc === 'rpc' && !db) { + QUnit.config.autostart = false; + db = { + source: null, + supadmin: null, + password: null + }; + var $msg = $('
') + .append('

A test needs to clone a database

') + .append('

Please provide the source clone information

') + .append(' Source DB: ').append('').append('
') + .append(' DB Password: ').append('').append('
') + .append('Admin Password: ').append('').append('
') + .append('') + .submit(function (e) { + e.preventDefault(); + e.stopPropagation(); + db.source = $msg.find('input[name=source]').val(); + db.supadmin = $msg.find('input[name=supadmin]').val(); + db.password = $msg.find('input[name=password]').val(); + QUnit.start(); + $.unblockUI(); + }); + $.blockUI({ + message: $msg, + css: { + fontFamily: 'monospace', + textAlign: 'left', + whiteSpace: 'pre-wrap', + cursor: 'default' + } + }); + } + QUnit.test(name, function () { // module testing environment var self = this; - var opts = _.defaults({ - // section setup - // case setup - // test - // case teardown - // section teardown - setup: function () { - if (self._oe.setup.apply(null, arguments)) { - throw new Error("Asynchronous setup not implemented"); - } - if (options.setup.apply(null, arguments)) { - throw new Error("Asynchronous setup not implemented"); - } - }, - teardown: function () { - if (options.teardown.apply(null, arguments)) { - throw new Error("Asynchronous teardown not implemented"); - } - if (self._oe.teardown(null, arguments)) { - throw new Error("Asynchronous teardown not implemented"); - } - } - }, options, this._oe); var instance; if (!opts.dependencies) { @@ -202,38 +240,80 @@ openerp.testing = {}; break; case 'rpc': async = true; + (function (setup, teardown) { + // Bunch of random base36 characters + var dbname = 'test_' + Math.random().toString(36).slice(2); + opts.setup = function (instance, $s) { + // 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; + instance.session.session_bind(); + return instance.session.rpc('/web/database/duplicate', { + fields: [ + {name: 'super_admin_pwd', value: db.supadmin}, + {name: 'db_original_name', value: db.source}, + {name: 'db_name', value: dbname} + ] + }).pipe(function (result) { + if (result.error) { + return $.Deferred().reject(result.error).promise(); + } + 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', { + fields: [ + {name: 'drop_pwd', value: db.supadmin}, + {name: 'drop_db', value: db.dbname} + ] + }); + }).pipe(function (result) { + if (result.error) { + return $.Deferred().reject(result.error).promise(); + } + return result; + }); + }; + })(opts.setup, opts.teardown); } - // TODO: async setup/teardown - opts.setup(instance, $fixture, mock); - - var result = callback(instance, $fixture, mock); - - // TODO: cleanup which works on errors - if (!(result && _.isFunction(result.then))) { - if (async) { - ok(false, "asynchronous test cases must return a promise"); - } - opts.teardown(instance, $fixture, mock); - return; - } - + // Always execute tests asynchronously stop(); - if (!_.isNumber(opts.asserts)) { - ok(false, "asynchronous test cases must specify the " - + "number of assertions they expect"); - } - result.always(function () { - start(); - opts.teardown(instance, $fixture, mock); - }).fail(function (error) { - if (options.fail_on_rejection === false) { - return; + $.when(opts.setup(instance, $fixture, mock)) + .pipe(function () { + var result = callback(instance, $fixture, mock); + if (!(result && _.isFunction(result.then))) { + if (async) { + ok(false, "asynchronous test cases must return a promise"); + } + } else { + if (!_.isNumber(opts.asserts)) { + ok(false, "asynchronous test cases must specify the " + + "number of assertions they expect"); + } } - ok(false, typeof error === 'object' && error.message - ? error.message - : JSON.stringify([].slice.call(arguments))); - }) + return $.when(result).fail(function (error) { + if (options.fail_on_rejection === false) { + return; + } + ok(false, typeof error === 'object' && error.message + ? error.message + : JSON.stringify([].slice.call(arguments))); + }) + }).pipe(function () { + return opts.teardown(instance, $fixture, mock); + }, function () { + return opts.teardown(instance, $fixture, mock); + }).always(function () { + start(); + }); }); }; })(openerp.testing); -- cgit v1.2.3 From 523f4a6e03e909156082f528acf4e50024e7b486 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 30 Oct 2012 08:43:25 +0100 Subject: [FIX] db name being used to drop databases after rpc tests xmo@openerp.com-20121029110154-5927gaix8k0ijl0c introduced a regression which wasn't caught by deferred piping (?) and caused databases to not be dropped after rpc tests bzr revid: xmo@openerp.com-20121030074325-wh39s75vj2zob9e0 --- addons/web/static/src/js/testing.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'addons/web/static/src/js/testing.js') diff --git a/addons/web/static/src/js/testing.js b/addons/web/static/src/js/testing.js index 744bd9fa2cd..de6b7aab1a4 100644 --- a/addons/web/static/src/js/testing.js +++ b/addons/web/static/src/js/testing.js @@ -271,7 +271,7 @@ openerp.testing = {}; return instance.session.rpc('/web/database/drop', { fields: [ {name: 'drop_pwd', value: db.supadmin}, - {name: 'drop_db', value: db.dbname} + {name: 'drop_db', value: dbname} ] }); }).pipe(function (result) { -- cgit v1.2.3 From d2cf844e7b2e447ce0c1502aa3a514056a6d7fb1 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 30 Oct 2012 08:46:15 +0100 Subject: [IMP] allow providing db-cloning info directly in URL for test runner bzr revid: xmo@openerp.com-20121030074615-lt517w5t9fd9ppvh --- addons/web/static/src/js/testing.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'addons/web/static/src/js/testing.js') diff --git a/addons/web/static/src/js/testing.js b/addons/web/static/src/js/testing.js index de6b7aab1a4..983c8650e31 100644 --- a/addons/web/static/src/js/testing.js +++ b/addons/web/static/src/js/testing.js @@ -85,7 +85,7 @@ openerp.testing = {}; }; }; - var db = window['oe_db_info'] || undefined; + var db = window['oe_db_info']; testing.section = function (name, options, body) { if (_.isFunction(options)) { body = options; -- cgit v1.2.3 From 5b1990bd18c50fad1aa96e4b8a93eb319dbd8646 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 30 Oct 2012 09:15:25 +0100 Subject: [IMP] make failure of rpc cleanup fail the owning test bzr revid: xmo@openerp.com-20121030081525-i611as22kjlhcex5 --- addons/web/static/src/js/testing.js | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) (limited to 'addons/web/static/src/js/testing.js') diff --git a/addons/web/static/src/js/testing.js b/addons/web/static/src/js/testing.js index 983c8650e31..32a3624eb93 100644 --- a/addons/web/static/src/js/testing.js +++ b/addons/web/static/src/js/testing.js @@ -299,20 +299,29 @@ openerp.testing = {}; + "number of assertions they expect"); } } - return $.when(result).fail(function (error) { - if (options.fail_on_rejection === false) { - return; - } - ok(false, typeof error === 'object' && error.message - ? error.message - : JSON.stringify([].slice.call(arguments))); - }) + return $.when(result); }).pipe(function () { return opts.teardown(instance, $fixture, mock); }, function () { return opts.teardown(instance, $fixture, mock); }).always(function () { start(); + }).fail(function (error) { + if (options.fail_on_rejection === false) { + return; + } + var message; + if (typeof error !== 'object' + || typeof error.message !== 'string') { + message = JSON.stringify([].slice.apply(arguments)); + } else { + message = error.message; + if (error.data && error.data.debug) { + message += '\n\n' + error.data.debug; + } + } + + ok(false, message); }); }); }; -- cgit v1.2.3 From f24cbe0795353a6c8355b31ac86d54af61f85cff Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 30 Oct 2012 09:22:17 +0100 Subject: [FIX] don't apply teardown when setup fails bzr revid: xmo@openerp.com-20121030082217-yjbh7j4apszsyg8l --- addons/web/static/src/js/testing.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'addons/web/static/src/js/testing.js') diff --git a/addons/web/static/src/js/testing.js b/addons/web/static/src/js/testing.js index 32a3624eb93..e124897f063 100644 --- a/addons/web/static/src/js/testing.js +++ b/addons/web/static/src/js/testing.js @@ -286,6 +286,9 @@ openerp.testing = {}; // Always execute tests asynchronously stop(); + var teardown = function () { + return opts.teardown(instance, $fixture, mock) + }; $.when(opts.setup(instance, $fixture, mock)) .pipe(function () { var result = callback(instance, $fixture, mock); @@ -299,11 +302,7 @@ openerp.testing = {}; + "number of assertions they expect"); } } - return $.when(result); - }).pipe(function () { - return opts.teardown(instance, $fixture, mock); - }, function () { - return opts.teardown(instance, $fixture, mock); + return $.when(result).pipe(teardown, teardown); }).always(function () { start(); }).fail(function (error) { -- cgit v1.2.3 From 8e85f4e0bd0b1fbbf401686b85d7282f8991454d Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 15 Nov 2012 10:15:01 +0100 Subject: [IMP] timeout discipline * Global timeout significantly increased to correctly handle e.g. db creation and teardown * Per-test timeout brought back down to 2s bzr revid: xmo@openerp.com-20121115091501-ys8opo0ccr3dq29h --- addons/web/static/src/js/testing.js | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) (limited to 'addons/web/static/src/js/testing.js') diff --git a/addons/web/static/src/js/testing.js b/addons/web/static/src/js/testing.js index e124897f063..7686499b939 100644 --- a/addons/web/static/src/js/testing.js +++ b/addons/web/static/src/js/testing.js @@ -286,6 +286,7 @@ openerp.testing = {}; // Always execute tests asynchronously stop(); + var timeout; var teardown = function () { return opts.teardown(instance, $fixture, mock) }; @@ -302,8 +303,25 @@ openerp.testing = {}; + "number of assertions they expect"); } } - return $.when(result).pipe(teardown, teardown); + var d = $.Deferred(); + $.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); + } + return d.pipe(teardown, teardown); }).always(function () { + if (timeout) { + clearTimeout(timeout); + } start(); }).fail(function (error) { if (options.fail_on_rejection === false) { @@ -320,6 +338,7 @@ openerp.testing = {}; } } + ok(false, "failed!") ok(false, message); }); }); -- cgit v1.2.3 From a9645151b6f070d23d2f35810aee562c1354aba9 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Fri, 16 Nov 2012 08:17:12 +0100 Subject: [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 --- addons/web/static/src/js/testing.js | 184 +++++++++++++++++++++++------------- 1 file changed, 119 insertions(+), 65 deletions(-) (limited to 'addons/web/static/src/js/testing.js') 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) { -- cgit v1.2.3 From d632471f647a1678b7cdb9c5b383a0fd9903d7df Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Fri, 16 Nov 2012 08:29:06 +0100 Subject: [IMP] web test runner: pass execute's arguments to the function to avoid duplication bzr revid: xmo@openerp.com-20121116072906-0di2j83p602a2l9i --- addons/web/static/src/js/testing.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'addons/web/static/src/js/testing.js') diff --git a/addons/web/static/src/js/testing.js b/addons/web/static/src/js/testing.js index 10fe7ecd778..fa68051f27d 100644 --- a/addons/web/static/src/js/testing.js +++ b/addons/web/static/src/js/testing.js @@ -347,7 +347,7 @@ openerp.testing = {}; stop(); var timeout; case_stack.execute(function () { - var result = callback(instance, $fixture, mock); + var result = callback.apply(null, arguments); if (!(result && _.isFunction(result.then))) { if (async) { ok(false, "asynchronous test cases must return a promise"); -- cgit v1.2.3 From 09037e8a42a7810bf9e9dea2206b3c00381ca6e7 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Fri, 16 Nov 2012 08:37:38 +0100 Subject: [REM] failing test code committed by mistake bzr revid: xmo@openerp.com-20121116073738-9f43bm0q6eojns3v --- addons/web/static/src/js/testing.js | 1 - 1 file changed, 1 deletion(-) (limited to 'addons/web/static/src/js/testing.js') diff --git a/addons/web/static/src/js/testing.js b/addons/web/static/src/js/testing.js index fa68051f27d..fb8ebc25044 100644 --- a/addons/web/static/src/js/testing.js +++ b/addons/web/static/src/js/testing.js @@ -392,7 +392,6 @@ openerp.testing = {}; } } - ok(false, "failed!") ok(false, message); }); }); -- cgit v1.2.3 From 796e80ec2dafbd05e35dd7baf00f215393e9260c Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Fri, 16 Nov 2012 11:14:17 +0100 Subject: [FIX] deferred.pipe -> deferred.then bzr revid: xmo@openerp.com-20121116101417-mkcjrdtocl6w7408 --- addons/web/static/src/js/testing.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'addons/web/static/src/js/testing.js') diff --git a/addons/web/static/src/js/testing.js b/addons/web/static/src/js/testing.js index fb8ebc25044..b0d744ae006 100644 --- a/addons/web/static/src/js/testing.js +++ b/addons/web/static/src/js/testing.js @@ -74,7 +74,7 @@ openerp.testing = {}; ).promise(); } try { - return $.when(fn.apply(null, params)).pipe(function (result) { + return $.when(fn.apply(null, params)).then(function (result) { // Wrap for RPC layer unwrapper thingy return {result: result}; }); @@ -121,7 +121,7 @@ openerp.testing = {}; } catch (e) { actual_call = $.Deferred().reject(e); } - actual_call.pipe(success, failure); + actual_call.then(success, failure); } }; var teardown = function () { @@ -320,7 +320,7 @@ openerp.testing = {}; {name: 'db_original_name', value: db.source}, {name: 'db_name', value: dbname} ] - }).pipe(function (result) { + }).then(function (result) { if (result.error) { return $.Deferred().reject(result.error).promise(); } @@ -333,7 +333,7 @@ openerp.testing = {}; {name: 'drop_pwd', value: db.supadmin}, {name: 'drop_db', value: dbname} ] - }).pipe(function (result) { + }).then(function (result) { if (result.error) { return $.Deferred().reject(result.error).promise(); } -- cgit v1.2.3 From e1e4c48c181319b01e44c78d94a999e91c515ba0 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Fri, 16 Nov 2012 11:55:54 +0100 Subject: [REM] test runner feature which probably caused more trouble than it caught avoid catching exception in executed callback, it just hinders debugging by generated completely bonkers traces bzr revid: xmo@openerp.com-20121116105554-hu45anetrj1a8tl6 --- addons/web/static/src/js/testing.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) (limited to 'addons/web/static/src/js/testing.js') diff --git a/addons/web/static/src/js/testing.js b/addons/web/static/src/js/testing.js index b0d744ae006..bf39f543734 100644 --- a/addons/web/static/src/js/testing.js +++ b/addons/web/static/src/js/testing.js @@ -115,13 +115,7 @@ openerp.testing = {}; setup(); }, failure); } else { - var actual_call; - try { - actual_call = $.when(fn.apply(null, args)) - } catch (e) { - actual_call = $.Deferred().reject(e); - } - actual_call.then(success, failure); + $.when(fn.apply(null, args)).then(success, failure); } }; var teardown = function () { -- cgit v1.2.3