From e44a7e37b6c7b5961adaffc62b9042b8d442938e Mon Sep 17 00:00:00 2001 From: mensonge Date: Thu, 13 Nov 2008 09:49:11 +0000 Subject: New feature: basic Ajax suggestion for tags and implementation of Dojo toolkit git-svn-id: https://semanticscuttle.svn.sourceforge.net/svnroot/semanticscuttle/trunk@151 b3834d28-1941-0410-a4f8-b48e95affb8f --- includes/js/util/doh/runner.js | 948 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 948 insertions(+) create mode 100644 includes/js/util/doh/runner.js (limited to 'includes/js/util/doh/runner.js') diff --git a/includes/js/util/doh/runner.js b/includes/js/util/doh/runner.js new file mode 100644 index 0000000..f5e47e1 --- /dev/null +++ b/includes/js/util/doh/runner.js @@ -0,0 +1,948 @@ +// FIXME: need to add async tests +// FIXME: need to handle URL wrapping and test registration/running from URLs + +// package system gunk. +try{ + dojo.provide("doh.runner"); +}catch(e){ + if(!this["doh"]){ + doh = {}; + } +} + +// +// Utility Functions and Classes +// + +doh.selfTest = false; + +doh.hitch = function(/*Object*/thisObject, /*Function|String*/method /*, ...*/){ + var args = []; + for(var x=2; x= 0)) { + this._fire(); + } + }, + + _continue: function(res){ + this._resback(res); + this._unpause(); + }, + + _resback: function(res){ + this.fired = ((res instanceof Error) ? 1 : 0); + this.results[this.fired] = res; + this._fire(); + }, + + _check: function(){ + if(this.fired != -1){ + if(!this.silentlyCancelled){ + throw new Error("already called!"); + } + this.silentlyCancelled = false; + return; + } + }, + + callback: function(res){ + this._check(); + this._resback(res); + }, + + errback: function(res){ + this._check(); + if(!(res instanceof Error)){ + res = new Error(res); + } + this._resback(res); + }, + + addBoth: function(cb, cbfn){ + var enclosed = this.getFunctionFromArgs(cb, cbfn); + if(arguments.length > 2){ + enclosed = doh.hitch(null, enclosed, arguments, 2); + } + return this.addCallbacks(enclosed, enclosed); + }, + + addCallback: function(cb, cbfn){ + var enclosed = this.getFunctionFromArgs(cb, cbfn); + if(arguments.length > 2){ + enclosed = doh.hitch(null, enclosed, arguments, 2); + } + return this.addCallbacks(enclosed, null); + }, + + addErrback: function(cb, cbfn){ + var enclosed = this.getFunctionFromArgs(cb, cbfn); + if(arguments.length > 2){ + enclosed = doh.hitch(null, enclosed, arguments, 2); + } + return this.addCallbacks(null, enclosed); + }, + + addCallbacks: function(cb, eb){ + this.chain.push([cb, eb]) + if(this.fired >= 0){ + this._fire(); + } + return this; + }, + + _fire: function(){ + var chain = this.chain; + var fired = this.fired; + var res = this.results[fired]; + var self = this; + var cb = null; + while (chain.length > 0 && this.paused == 0){ + // Array + var pair = chain.shift(); + var f = pair[fired]; + if(f == null){ + continue; + } + try { + res = f(res); + fired = ((res instanceof Error) ? 1 : 0); + if(res instanceof doh.Deferred){ + cb = function(res){ + self._continue(res); + } + this._pause(); + } + }catch(err){ + fired = 1; + res = err; + } + } + this.fired = fired; + this.results[fired] = res; + if((cb)&&(this.paused)){ + res.addBoth(cb); + } + } +}); + +// +// State Keeping and Reporting +// + +doh._testCount = 0; +doh._groupCount = 0; +doh._errorCount = 0; +doh._failureCount = 0; +doh._currentGroup = null; +doh._currentTest = null; +doh._paused = true; + +doh._init = function(){ + this._currentGroup = null; + this._currentTest = null; + this._errorCount = 0; + this._failureCount = 0; + this.debug(this._testCount, "tests to run in", this._groupCount, "groups"); +} + +// doh._urls = []; +doh._groups = {}; + +// +// Test Registration +// + +doh.registerTestNs = function(/*String*/ group, /*Object*/ ns){ + // summary: + // adds the passed namespace object to the list of objects to be + // searched for test groups. Only "public" functions (not prefixed + // with "_") will be added as tests to be run. If you'd like to use + // fixtures (setUp(), tearDown(), and runTest()), please use + // registerTest() or registerTests(). + for(var x in ns){ + if( (x.charAt(0) != "_") && + (typeof ns[x] == "function") ){ + this.registerTest(group, ns[x]); + } + } +} + +doh._testRegistered = function(group, fixture){ + // slot to be filled in +} + +doh._groupStarted = function(group){ + // slot to be filled in +} + +doh._groupFinished = function(group, success){ + // slot to be filled in +} + +doh._testStarted = function(group, fixture){ + // slot to be filled in +} + +doh._testFinished = function(group, fixture, success){ + // slot to be filled in +} + +doh.registerGroup = function( /*String*/ group, + /*Array||Function||Object*/ tests, + /*Function*/ setUp, + /*Function*/ tearDown){ + // summary: + // registers an entire group of tests at once and provides a setUp and + // tearDown facility for groups. If you call this method with only + // setUp and tearDown parameters, they will replace previously + // installed setUp or tearDown functions for the group with the new + // methods. + // group: + // string name of the group + // tests: + // either a function or an object or an array of functions/objects. If + // an object, it must contain at *least* a "runTest" method, and may + // also contain "setUp" and "tearDown" methods. These will be invoked + // on either side of the "runTest" method (respectively) when the test + // is run. If an array, it must contain objects matching the above + // description or test functions. + // setUp: a function for initializing the test group + // tearDown: a function for initializing the test group + if(tests){ + this.register(group, tests); + } + if(setUp){ + this._groups[group].setUp = setUp; + } + if(tearDown){ + this._groups[group].tearDown = tearDown; + } +} + +doh._getTestObj = function(group, test){ + var tObj = test; + if(typeof test == "string"){ + if(test.substr(0, 4)=="url:"){ + return this.registerUrl(group, test); + }else{ + tObj = { + name: test.replace("/\s/g", "_") + }; + tObj.runTest = new Function("t", test); + } + }else if(typeof test == "function"){ + // if we didn't get a fixture, wrap the function + tObj = { "runTest": test }; + if(test["name"]){ + tObj.name = test.name; + }else{ + try{ + var fStr = "function "; + var ts = tObj.runTest+""; + if(0 <= ts.indexOf(fStr)){ + tObj.name = ts.split(fStr)[1].split("(", 1)[0]; + } + // doh.debug(tObj.runTest.toSource()); + }catch(e){ + } + } + // FIXME: try harder to get the test name here + } + return tObj; +} + +doh.registerTest = function(/*String*/ group, /*Function||Object*/ test){ + // summary: + // add the provided test function or fixture object to the specified + // test group. + // group: + // string name of the group to add the test to + // test: + // either a function or an object. If an object, it must contain at + // *least* a "runTest" method, and may also contain "setUp" and + // "tearDown" methods. These will be invoked on either side of the + // "runTest" method (respectively) when the test is run. + if(!this._groups[group]){ + this._groupCount++; + this._groups[group] = []; + this._groups[group].inFlight = 0; + } + var tObj = this._getTestObj(group, test); + if(!tObj){ return; } + this._groups[group].push(tObj); + this._testCount++; + this._testRegistered(group, tObj); + return tObj; +} + +doh.registerTests = function(/*String*/ group, /*Array*/ testArr){ + // summary: + // registers a group of tests, treating each element of testArr as + // though it were being (along with group) passed to the registerTest + // method. + for(var x=0; x 1) ? "s" : "")+" to run"); +} + +doh._handleFailure = function(groupName, fixture, e){ + // this.debug("FAILED test:", fixture.name); + // mostly borrowed from JUM + this._groups[groupName].failures++; + var out = ""; + if(e instanceof this._AssertFailure){ + this._failureCount++; + if(e["fileName"]){ out += e.fileName + ':'; } + if(e["lineNumber"]){ out += e.lineNumber + ' '; } + out += e+": "+e.message; + this.debug("\t_AssertFailure:", out); + }else{ + this._errorCount++; + } + this.debug(e); + if(fixture.runTest["toSource"]){ + var ss = fixture.runTest.toSource(); + this.debug("\tERROR IN:\n\t\t", ss); + }else{ + this.debug("\tERROR IN:\n\t\t", fixture.runTest); + } + + if(e.rhinoException){ + e.rhinoException.printStackTrace(); + }else if(e.javaException){ + e.javaException.printStackTrace(); + } +} + +try{ + setTimeout(function(){}, 0); +}catch(e){ + setTimeout = function(func){ + return func(); + } +} + +doh._runFixture = function(groupName, fixture){ + var tg = this._groups[groupName]; + this._testStarted(groupName, fixture); + var threw = false; + var err = null; + // run it, catching exceptions and reporting them + try{ + // let doh reference "this.group.thinger..." which can be set by + // another test or group-level setUp function + fixture.group = tg; + // only execute the parts of the fixture we've got + if(fixture["setUp"]){ fixture.setUp(this); } + if(fixture["runTest"]){ // should we error out of a fixture doesn't have a runTest? + fixture.startTime = new Date(); + var ret = fixture.runTest(this); + fixture.endTime = new Date(); + // if we get a deferred back from the test runner, we know we're + // gonna wait for an async result. It's up to the test code to trap + // errors and give us an errback or callback. + if(ret instanceof doh.Deferred){ + + tg.inFlight++; + ret.groupName = groupName; + ret.fixture = fixture; + + ret.addErrback(function(err){ + doh._handleFailure(groupName, fixture, err); + }); + + var retEnd = function(){ + if(fixture["tearDown"]){ fixture.tearDown(doh); } + tg.inFlight--; + if((!tg.inFlight)&&(tg.iterated)){ + doh._groupFinished(groupName, (!tg.failures)); + } + doh._testFinished(groupName, fixture, ret.results[0]); + if(doh._paused){ + doh.run(); + } + } + + var timer = setTimeout(function(){ + // ret.cancel(); + // retEnd(); + ret.errback(new Error("test timeout in "+fixture.name.toString())); + }, fixture["timeout"]||1000); + + ret.addBoth(function(arg){ + clearTimeout(timer); + retEnd(); + }); + if(ret.fired < 0){ + doh.pause(); + } + return ret; + } + } + if(fixture["tearDown"]){ fixture.tearDown(this); } + }catch(e){ + threw = true; + err = e; + if(!fixture.endTime){ + fixture.endTime = new Date(); + } + } + var d = new doh.Deferred(); + setTimeout(this.hitch(this, function(){ + if(threw){ + this._handleFailure(groupName, fixture, err); + } + this._testFinished(groupName, fixture, (!threw)); + + if((!tg.inFlight)&&(tg.iterated)){ + doh._groupFinished(groupName, (!tg.failures)); + }else if(tg.inFlight > 0){ + setTimeout(this.hitch(this, function(){ + doh.runGroup(groupName); // , idx); + }), 100); + this._paused = true; + } + if(doh._paused){ + doh.run(); + } + }), 30); + doh.pause(); + return d; +} + +doh._testId = 0; +doh.runGroup = function(/*String*/ groupName, /*Integer*/ idx){ + // summary: + // runs the specified test group + + // the general structure of the algorithm is to run through the group's + // list of doh, checking before and after each of them to see if we're in + // a paused state. This can be caused by the test returning a deferred or + // the user hitting the pause button. In either case, we want to halt + // execution of the test until something external to us restarts it. This + // means we need to pickle off enough state to pick up where we left off. + + // FIXME: need to make fixture execution async!! + + var tg = this._groups[groupName]; + if(tg.skip === true){ return; } + if(this._isArray(tg)){ + if(idx<=tg.length){ + if((!tg.inFlight)&&(tg.iterated == true)){ + if(tg["tearDown"]){ tg.tearDown(this); } + doh._groupFinished(groupName, (!tg.failures)); + return; + } + } + if(!idx){ + tg.inFlight = 0; + tg.iterated = false; + tg.failures = 0; + } + doh._groupStarted(groupName); + if(!idx){ + this._setupGroupForRun(groupName, idx); + if(tg["setUp"]){ tg.setUp(this); } + } + for(var y=(idx||0); y