]> git.pond.sub.org Git - eow/blobdiff - static/dojo-release-1.1.1/util/doh/runner.js
add Dojo 1.1.1
[eow] / static / dojo-release-1.1.1 / util / doh / runner.js
diff --git a/static/dojo-release-1.1.1/util/doh/runner.js b/static/dojo-release-1.1.1/util/doh/runner.js
new file mode 100644 (file)
index 0000000..f5e47e1
--- /dev/null
@@ -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<arguments.length; x++){
+               args.push(arguments[x]);
+       }
+       var fcn = ((typeof method == "string") ? thisObject[method] : method) || function(){};
+       return function(){
+               var ta = args.concat([]); // make a copy
+               for(var x=0; x<arguments.length; x++){
+                       ta.push(arguments[x]);
+               }
+               return fcn.apply(thisObject, ta); // Function
+       };
+}
+
+doh._mixin = function(/*Object*/ obj, /*Object*/ props){
+       // summary:
+       //              Adds all properties and methods of props to obj. This addition is
+       //              "prototype extension safe", so that instances of objects will not
+       //              pass along prototype defaults.
+       var tobj = {};
+       for(var x in props){
+               // the "tobj" condition avoid copying properties in "props"
+               // inherited from Object.prototype.  For example, if obj has a custom
+               // toString() method, don't overwrite it with the toString() method
+               // that props inherited from Object.protoype
+               if((typeof tobj[x] == "undefined") || (tobj[x] != props[x])){
+                       obj[x] = props[x];
+               }
+       }
+       // IE doesn't recognize custom toStrings in for..in
+       if(     this["document"] 
+               && document.all
+               && (typeof props["toString"] == "function")
+               && (props["toString"] != obj["toString"])
+               && (props["toString"] != tobj["toString"])
+       ){
+               obj.toString = props.toString;
+       }
+       return obj; // Object
+}
+
+doh.mixin = function(/*Object*/obj, /*Object...*/props){
+       // summary:     Adds all properties and methods of props to obj. 
+       for(var i=1, l=arguments.length; i<l; i++){
+               doh._mixin(obj, arguments[i]);
+       }
+       return obj; // Object
+}
+
+doh.extend = function(/*Object*/ constructor, /*Object...*/ props){
+       // summary:
+       //              Adds all properties and methods of props to constructor's
+       //              prototype, making them available to all instances created with
+       //              constructor.
+       for(var i=1, l=arguments.length; i<l; i++){
+               doh._mixin(constructor.prototype, arguments[i]);
+       }
+       return constructor; // Object
+}
+
+
+doh._line = "------------------------------------------------------------";
+
+/*
+doh._delegate = function(obj, props){
+       // boodman-crockford delegation
+       function TMP(){};
+       TMP.prototype = obj;
+       var tmp = new TMP();
+       if(props){
+               dojo.lang.mixin(tmp, props);
+       }
+       return tmp;
+}
+*/
+
+doh.debug = function(){
+       // summary:
+       //              takes any number of arguments and sends them to whatever debugging
+       //              or logging facility is available in this environment
+
+       // YOUR TEST RUNNER NEEDS TO IMPLEMENT THIS
+}
+
+doh._AssertFailure = function(msg){
+       // idea for this as way of dis-ambiguating error types is from JUM. 
+       // The JUM is dead! Long live the JUM!
+
+       if(!(this instanceof doh._AssertFailure)){
+               return new doh._AssertFailure(msg);
+       }
+       this.message = new String(msg||"");
+       return this;
+}
+doh._AssertFailure.prototype = new Error();
+doh._AssertFailure.prototype.constructor = doh._AssertFailure;
+doh._AssertFailure.prototype.name = "doh._AssertFailure";
+
+doh.Deferred = function(canceller){
+       this.chain = [];
+       this.id = this._nextId();
+       this.fired = -1;
+       this.paused = 0;
+       this.results = [null, null];
+       this.canceller = canceller;
+       this.silentlyCancelled = false;
+};
+
+doh.extend(doh.Deferred, {
+       getTestCallback: function(cb, scope){
+               var _this = this;
+               return function(){
+                       try{
+                               cb.apply(scope||dojo.global||_this, arguments);
+                       }catch(e){
+                               _this.errback(e);
+                               return;
+                       }
+                       _this.callback(true);
+               }
+       },
+
+       getFunctionFromArgs: function(){
+               var a = arguments;
+               if((a[0])&&(!a[1])){
+                       if(typeof a[0] == "function"){
+                               return a[0];
+                       }else if(typeof a[0] == "string"){
+                               return dojo.global[a[0]];
+                       }
+               }else if((a[0])&&(a[1])){
+                       return doh.hitch(a[0], a[1]);
+               }
+               return null;
+       },
+
+       makeCalled: function() {
+               var deferred = new doh.Deferred();
+               deferred.callback();
+               return deferred;
+       },
+
+       _nextId: (function(){
+               var n = 1;
+               return function(){ return n++; };
+       })(),
+
+       cancel: function(){
+               if(this.fired == -1){
+                       if (this.canceller){
+                               this.canceller(this);
+                       }else{
+                               this.silentlyCancelled = true;
+                       }
+                       if(this.fired == -1){
+                               this.errback(new Error("Deferred(unfired)"));
+                       }
+               }else if(       (this.fired == 0)&&
+                                       (this.results[0] instanceof doh.Deferred)){
+                       this.results[0].cancel();
+               }
+       },
+                       
+
+       _pause: function(){
+               this.paused++;
+       },
+
+       _unpause: function(){
+               this.paused--;
+               if ((this.paused == 0) && (this.fired >= 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<testArr.length; x++){
+               this.registerTest(group, testArr[x]);
+       }
+}
+
+// FIXME: move implementation to _browserRunner?
+doh.registerUrl = function(    /*String*/ group, 
+                                                               /*String*/ url, 
+                                                               /*Integer*/ timeout){
+       this.debug("ERROR:");
+       this.debug("\tNO registerUrl() METHOD AVAILABLE.");
+       // this._urls.push(url);
+}
+
+doh.registerString = function(group, str){
+}
+
+// FIXME: remove the doh.add alias SRTL.
+doh.register = doh.add = function(groupOrNs, testOrNull){
+       // summary:
+       //              "magical" variant of registerTests, registerTest, and
+       //              registerTestNs. Will accept the calling arguments of any of these
+       //              methods and will correctly guess the right one to register with.
+       if(     (arguments.length == 1)&&
+               (typeof groupOrNs == "string") ){
+               if(groupOrNs.substr(0, 4)=="url:"){
+                       this.registerUrl(groupOrNs);
+               }else{
+                       this.registerTest("ungrouped", groupOrNs);
+               }
+       }
+       if(arguments.length == 1){
+               this.debug("invalid args passed to doh.register():", groupOrNs, ",", testOrNull);
+               return;
+       }
+       if(typeof testOrNull == "string"){
+               if(testOrNull.substr(0, 4)=="url:"){
+                       this.registerUrl(testOrNull);
+               }else{
+                       this.registerTest(groupOrNs, testOrNull);
+               }
+               // this.registerTestNs(groupOrNs, testOrNull);
+               return;
+       }
+       if(doh._isArray(testOrNull)){
+               this.registerTests(groupOrNs, testOrNull);
+               return;
+       }
+       this.registerTest(groupOrNs, testOrNull);
+}
+
+//
+// Assertions and In-Test Utilities
+//
+
+doh.t = doh.assertTrue = function(/*Object*/ condition){
+       // summary:
+       //              is the passed item "truthy"?
+       if(arguments.length != 1){ 
+               throw doh._AssertFailure("assertTrue failed because it was not passed exactly 1 argument"); 
+       } 
+       if(!eval(condition)){
+               throw doh._AssertFailure("assertTrue('" + condition + "') failed");
+       }
+}
+
+doh.f = doh.assertFalse = function(/*Object*/ condition){
+       // summary:
+       //              is the passed item "falsey"?
+       if(arguments.length != 1){ 
+               throw doh._AssertFailure("assertFalse failed because it was not passed exactly 1 argument"); 
+       } 
+       if(eval(condition)){
+               throw doh._AssertFailure("assertFalse('" + condition + "') failed");
+       }
+}
+
+doh.e = doh.assertError = function(/*Error object*/expectedError, /*Object*/scope, /*String*/functionName, /*Array*/args){
+       //      summary:
+       //              Test for a certain error to be thrown by the given function.
+       //      example:
+       //              t.assertError(dojox.data.QueryReadStore.InvalidAttributeError, store, "getValue", [item, "NOT THERE"]);
+       //              t.assertError(dojox.data.QueryReadStore.InvalidItemError, store, "getValue", ["not an item", "NOT THERE"]);
+       try{
+               scope[functionName].apply(scope, args);
+       }catch (e){
+               if(e instanceof expectedError){
+                       return true;
+               }else{
+                       throw new doh._AssertFailure("assertError() failed:\n\texpected error\n\t\t"+expectedError+"\n\tbut got\n\t\t"+e+"\n\n");
+               }
+       }
+       throw new doh._AssertFailure("assertError() failed:\n\texpected error\n\t\t"+expectedError+"\n\tbut no error caught\n\n");
+}
+
+
+doh.is = doh.assertEqual = function(/*Object*/ expected, /*Object*/ actual){
+       // summary:
+       //              are the passed expected and actual objects/values deeply
+       //              equivalent?
+
+       // Compare undefined always with three equal signs, because undefined==null
+       // is true, but undefined===null is false. 
+       if((expected === undefined)&&(actual === undefined)){ 
+               return true;
+       }
+       if(arguments.length < 2){ 
+               throw doh._AssertFailure("assertEqual failed because it was not passed 2 arguments"); 
+       } 
+       if((expected === actual)||(expected == actual)){ 
+               return true;
+       }
+       if(     (this._isArray(expected) && this._isArray(actual))&&
+               (this._arrayEq(expected, actual)) ){
+               return true;
+       }
+       if( ((typeof expected == "object")&&((typeof actual == "object")))&&
+               (this._objPropEq(expected, actual)) ){
+               return true;
+       }
+       throw new doh._AssertFailure("assertEqual() failed:\n\texpected\n\t\t"+expected+"\n\tbut got\n\t\t"+actual+"\n\n");
+}
+
+doh._arrayEq = function(expected, actual){
+       if(expected.length != actual.length){ return false; }
+       // FIXME: we're not handling circular refs. Do we care?
+       for(var x=0; x<expected.length; x++){
+               if(!doh.assertEqual(expected[x], actual[x])){ return false; }
+       }
+       return true;
+}
+
+doh._objPropEq = function(expected, actual){
+       if(expected instanceof Date){
+               return actual instanceof Date && expected.getTime()==actual.getTime();
+       }
+       // Make sure ALL THE SAME properties are in both objects!
+       for(var x in actual){ // Lets check "actual" here, expected is checked below.
+               if(expected[x] === undefined){
+                       return false;
+               }
+       };
+
+       for(var x in expected){
+               if(!doh.assertEqual(expected[x], actual[x])){
+                       return false;
+               }
+       }
+       return true;
+}
+
+doh._isArray = function(it){
+       return (it && it instanceof Array || typeof it == "array" || (dojo["NodeList"] !== undefined && it instanceof dojo.NodeList));
+}
+
+//
+// Runner-Wrapper
+//
+
+doh._setupGroupForRun = function(/*String*/ groupName, /*Integer*/ idx){
+       var tg = this._groups[groupName];
+       this.debug(this._line);
+       this.debug("GROUP", "\""+groupName+"\"", "has", tg.length, "test"+((tg.length > 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<tg.length; y++){
+                       if(this._paused){
+                               this._currentTest = y;
+                               // this.debug("PAUSED at:", tg[y].name, this._currentGroup, this._currentTest);
+                               return;
+                       }
+                       doh._runFixture(groupName, tg[y]);
+                       if(this._paused){
+                               this._currentTest = y+1;
+                               if(this._currentTest == tg.length){
+                                       tg.iterated = true;
+                               }
+                               // this.debug("PAUSED at:", tg[y].name, this._currentGroup, this._currentTest);
+                               return;
+                       }
+               }
+               tg.iterated = true;
+               if(!tg.inFlight){
+                       if(tg["tearDown"]){ tg.tearDown(this); }
+                       doh._groupFinished(groupName, (!tg.failures));
+               }
+       }
+}
+
+doh._onEnd = function(){}
+
+doh._report = function(){
+       // summary:
+       //              a private method to be implemented/replaced by the "locally
+       //              appropriate" test runner
+
+       // this.debug("ERROR:");
+       // this.debug("\tNO REPORTING OUTPUT AVAILABLE.");
+       // this.debug("\tIMPLEMENT doh._report() IN YOUR TEST RUNNER");
+
+       this.debug(this._line);
+       this.debug("| TEST SUMMARY:");
+       this.debug(this._line);
+       this.debug("\t", this._testCount, "tests in", this._groupCount, "groups");
+       this.debug("\t", this._errorCount, "errors");
+       this.debug("\t", this._failureCount, "failures");
+}
+
+doh.togglePaused = function(){
+       this[(this._paused) ? "run" : "pause"]();
+}
+
+doh.pause = function(){
+       // summary:
+       //              halt test run. Can be resumed.
+       this._paused = true;
+}
+
+doh.run = function(){
+       // summary:
+       //              begins or resumes the test process.
+       // this.debug("STARTING");
+       this._paused = false;
+       var cg = this._currentGroup;
+       var ct = this._currentTest;
+       var found = false;
+       if(!cg){
+               this._init(); // we weren't paused
+               found = true;
+       }
+       this._currentGroup = null;
+       this._currentTest = null;
+
+       for(var x in this._groups){
+               if(
+                       ( (!found)&&(x == cg) )||( found )
+               ){
+                       if(this._paused){ return; }
+                       this._currentGroup = x;
+                       if(!found){
+                               found = true;
+                               this.runGroup(x, ct);
+                       }else{
+                               this.runGroup(x);
+                       }
+                       if(this._paused){ return; }
+               }
+       }
+       this._currentGroup = null;
+       this._currentTest = null;
+       this._paused = false;
+       this._onEnd();
+       this._report();
+}
+
+tests = doh;
+
+(function(){
+       // scop protection
+       try{
+               if(typeof dojo != "undefined"){
+                       dojo.platformRequire({
+                               browser: ["doh._browserRunner"],
+                               rhino: ["doh._rhinoRunner"],
+                               spidermonkey: ["doh._rhinoRunner"]
+                       });
+                       var _shouldRequire = (dojo.isBrowser) ? (dojo.global == dojo.global["parent"]) : true;
+                       if(_shouldRequire){
+                               if(dojo.isBrowser){
+                                       dojo.addOnLoad(function(){
+                                               if(dojo.byId("testList")){
+                                                       var _tm = ( (dojo.global.testModule && dojo.global.testModule.length) ? dojo.global.testModule : "dojo.tests.module");
+                                                       dojo.forEach(_tm.split(","), dojo.require, dojo);
+                                                       setTimeout(function(){
+                                                               doh.run();
+                                                       }, 500);
+                                               }
+                                       });
+                               }else{
+                                       // dojo.require("doh._base");
+                               }
+                       }
+               }else{
+                       if(
+                               (typeof load == "function")&&
+                               (       (typeof Packages == "function")||
+                                       (typeof Packages == "object")   )
+                       ){
+                               throw new Error();
+                       }else if(typeof load == "function"){
+                               throw new Error();
+                       }
+               }
+       }catch(e){
+               print("\n"+doh._line);
+               print("The Dojo Unit Test Harness, $Rev$");
+               print("Copyright (c) 2007, The Dojo Foundation, All Rights Reserved");
+               print(doh._line, "\n");
+
+               load("_rhinoRunner.js");
+
+               try{
+                       var dojoUrl = "../../dojo/dojo.js";
+                       var testUrl = "";
+                       var testModule = "dojo.tests.module";
+                       for(var x=0; x<arguments.length; x++){
+                               if(arguments[x].indexOf("=") > 0){
+                                       var tp = arguments[x].split("=");
+                                       if(tp[0] == "dojoUrl"){
+                                               dojoUrl = tp[1];
+                                       }
+                                       if(tp[0] == "testUrl"){
+                                               testUrl = tp[1];
+                                       }
+                                       if(tp[0] == "testModule"){
+                                               testModule = tp[1];
+                                       }
+                               }
+                       }
+                       if(dojoUrl.length){
+                               if(!this["djConfig"]){
+                                       djConfig = {};
+                               }
+                               djConfig.baseUrl = dojoUrl.split("dojo.js")[0];
+                               load(dojoUrl);
+                       }
+                       if(testUrl.length){
+                               load(testUrl);
+                       }
+                       if(testModule.length){
+                               dojo.forEach(testModule.split(","), dojo.require, dojo);
+                       }
+               }catch(e){
+                       print("An exception occurred: " + e);
+               }
+
+               doh.run();
+       }
+}).apply(this, typeof arguments != "undefined" ? arguments : [null]);