source: Dev/branches/rest-dojo-ui/client/util/doh/runner.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

File size: 44.6 KB
Line 
1define("doh/runner", ["dojo"], function(dojo) {
2var doh= dojo.mixin({}, dojo);
3
4// intentionally define global tests and global doh symbols
5// TODO: scrub these globals from tests and remove this pollution
6tests = doh;
7this.doh= doh;
8
9doh._line = "------------------------------------------------------------";
10
11doh.debug = function(){
12        // summary:
13        //              takes any number of arguments and sends them to whatever debugging
14        //              or logging facility is available in this environment
15
16        // YOUR TEST RUNNER NEEDS TO IMPLEMENT THIS
17};
18
19doh.error = function(){
20        // summary:
21        //              logging method to be used to send Error objects, so that
22        //              whatever debugging or logging facility you have can decide to treat it
23        //              as an Error object and show additional information - such as stack trace
24
25        // YOUR TEST RUNNER NEEDS TO IMPLEMENT THIS
26};
27
28doh._AssertFailure = function(msg, hint){
29        if (doh.breakOnError) {
30                debugger;
31        }
32        if(!(this instanceof doh._AssertFailure)){
33                return new doh._AssertFailure(msg, hint);
34        }
35        if(hint){
36                msg = (new String(msg||""))+" with hint: \n\t\t"+(new String(hint)+"\n");
37        }
38        this.message = new String(msg||"");
39        return this;
40};
41doh._AssertFailure.prototype = new Error();
42doh._AssertFailure.prototype.constructor = doh._AssertFailure;
43doh._AssertFailure.prototype.name = "doh._AssertFailure";
44
45doh.Deferred = function(canceller){
46        this.chain = [];
47        this.id = this._nextId();
48        this.fired = -1;
49        this.paused = 0;
50        this.results = [null, null];
51        this.canceller = canceller;
52        this.silentlyCancelled = false;
53};
54
55doh.extend(doh.Deferred, {
56        getTestErrback: function(cb, scope){
57                // summary: Replaces outer getTextCallback's in nested situations to avoid multiple callback(true)'s
58                var _this = this;
59                return function(){
60                        try{
61                                cb.apply(scope||doh.global||_this, arguments);
62                        }catch(e){
63                                _this.errback(e);
64                        }
65                };
66        },
67
68        getTestCallback: function(cb, scope){
69                var _this = this;
70                return function(){
71                        try{
72                                cb.apply(scope||doh.global||_this, arguments);
73                        }catch(e){
74                                _this.errback(e);
75                                return;
76                        }
77                        _this.callback(true);
78                };
79        },
80
81        getFunctionFromArgs: function(){
82                //TODO: this looks like dojo.hitch? remove and replace?
83                var a = arguments;
84                if((a[0])&&(!a[1])){
85                        if(typeof a[0] == "function"){
86                                return a[0];
87                        }else if(typeof a[0] == "string"){
88                                return doh.global[a[0]];
89                        }
90                }else if((a[0])&&(a[1])){
91                        return doh.hitch(a[0], a[1]);
92                }
93                return null;
94        },
95
96        makeCalled: function() {
97                var deferred = new doh.Deferred();
98                deferred.callback();
99                return deferred;
100        },
101
102        _nextId: (function(){
103                var n = 1;
104                return function(){ return n++; };
105        })(),
106
107        cancel: function(){
108                if(this.fired == -1){
109                        if (this.canceller){
110                                this.canceller(this);
111                        }else{
112                                this.silentlyCancelled = true;
113                        }
114                        if(this.fired == -1){
115                                this.errback(new Error("Deferred(unfired)"));
116                        }
117                }else if(this.fired == 0 && this.results[0] && this.results[0].cancel){
118                        this.results[0].cancel();
119                }
120        },
121
122        _pause: function(){
123                this.paused++;
124        },
125
126        _unpause: function(){
127                this.paused--;
128                if ((this.paused == 0) && (this.fired >= 0)) {
129                        this._fire();
130                }
131        },
132
133        _continue: function(res){
134                this._resback(res);
135                this._unpause();
136        },
137
138        _resback: function(res){
139                this.fired = ((res instanceof Error) ? 1 : 0);
140                this.results[this.fired] = res;
141                this._fire();
142        },
143
144        _check: function(){
145                if(this.fired != -1){
146                        if(!this.silentlyCancelled){
147                                throw new Error("already called!");
148                        }
149                        this.silentlyCancelled = false;
150                        return;
151                }
152        },
153
154        callback: function(res){
155                this._check();
156                this._resback(res);
157        },
158
159        errback: function(res){
160                this._check();
161                if(!(res instanceof Error)){
162                        res = new Error(res);
163                }
164                this._resback(res);
165        },
166
167        addBoth: function(cb, cbfn){
168                //TODO: this looks like dojo.hitch? remove and replace?
169                var enclosed = this.getFunctionFromArgs(cb, cbfn);
170                if(arguments.length > 2){
171                        enclosed = doh.hitch(null, enclosed, arguments, 2);
172                }
173                return this.addCallbacks(enclosed, enclosed);
174        },
175
176        addCallback: function(cb, cbfn){
177                //TODO: this looks like dojo.hitch? remove and replace?
178                var enclosed = this.getFunctionFromArgs(cb, cbfn);
179                if(arguments.length > 2){
180                        enclosed = doh.hitch(null, enclosed, arguments, 2);
181                }
182                return this.addCallbacks(enclosed, null);
183        },
184
185        addErrback: function(cb, cbfn){
186                //TODO: this looks like dojo.hitch? remove and replace?
187                var enclosed = this.getFunctionFromArgs(cb, cbfn);
188                if(arguments.length > 2){
189                        enclosed = doh.hitch(null, enclosed, arguments, 2);
190                }
191                return this.addCallbacks(null, enclosed);
192        },
193
194        addCallbacks: function(cb, eb){
195                this.chain.push([cb, eb]);
196                if(this.fired >= 0){
197                        this._fire();
198                }
199                return this;
200        },
201
202        _fire: function(){
203                var chain = this.chain;
204                var fired = this.fired;
205                var res = this.results[fired];
206                var self = this;
207                var cb = null;
208                while(chain.length > 0 && this.paused == 0){
209                        // Array
210                        var pair = chain.shift();
211                        var f = pair[fired];
212                        if(f == null){
213                                continue;
214                        }
215                        try {
216                                res = f(res);
217                                fired = ((res instanceof Error) ? 1 : 0);
218                                if(res && res.addCallback){
219                                        cb = function(res){
220                                                self._continue(res);
221                                        };
222                                        this._pause();
223                                }
224                        }catch(err){
225                                fired = 1;
226                                res = err;
227                        }
228                }
229                this.fired = fired;
230                this.results[fired] = res;
231                if((cb)&&(this.paused)){
232                        res.addBoth(cb);
233                }
234        }
235});
236
237//
238// State Keeping and Reporting
239//
240
241doh._testCount = 0;
242doh._groupCount = 0;
243doh._errorCount = 0;
244doh._failureCount = 0;
245doh._currentGroup = null;
246doh._currentTest = null;
247doh._paused = true;
248
249doh._init = function(){
250        this._currentGroup = null;
251        this._currentTest = null;
252        this._errorCount = 0;
253        this._failureCount = 0;
254        this.debug(this._testCount, "tests to run in", this._groupCount, "groups");
255};
256
257doh._groups = {};
258
259//
260// Test Types
261//
262doh._testTypes= {};
263
264doh.registerTestType= function(name, initProc){
265        // summary:
266        //       Adds a test type and associates a function used to initialize each test of the given type
267        // name: String
268        //       The name of the type.
269        // initProc: Function
270        //       Type specific test initializer; called after the test object is created.
271        doh._testTypes[name]= initProc;
272};
273
274doh.registerTestType("perf", function(group, tObj, type){
275        //Augment the test with some specific options to make it identifiable as a
276        //particular type of test so it can be executed properly.
277        if(type === "perf" || tObj.testType === "perf"){
278                tObj.testType = "perf";
279
280                //Build an object on the root DOH class to contain all the test results.
281                //Cache it on the test object for quick lookup later for results storage.
282                if(!doh.perfTestResults){
283                        doh.perfTestResults = {};
284                        doh.perfTestResults[group] = {};
285                }
286                if(!doh.perfTestResults[group]){
287                        doh.perfTestResults[group] = {};
288                }
289                if(!doh.perfTestResults[group][tObj.name]){
290                        doh.perfTestResults[group][tObj.name] = {};
291                }
292                tObj.results = doh.perfTestResults[group][tObj.name];
293
294                //If it's not set, then set the trial duration; default to 100ms.
295                if(!("trialDuration" in tObj)){
296                        tObj.trialDuration = 100;
297                }
298
299                //If it's not set, then set the delay between trial runs to 100ms
300                //default to 100ms to allow for GC and to make IE happy.
301                if(!("trialDelay" in tObj)){
302                        tObj.trialDelay = 100;
303                }
304
305                //If it's not set, then set number of times a trial is run to 10.
306                if(!("trialIterations" in tObj)){
307                        tObj.trialIterations = 10;
308                }
309        }
310});
311
312
313//
314// Test Registration
315//
316var
317        createFixture= function(group, test, type){
318                // test is a function, string, or fixture object
319                var tObj = test;
320                if(dojo.isString(test)){
321                        tObj = {
322                                name: test.replace("/\s/g", "_"), // FIXME: bad escapement
323                                runTest: new Function("t", test)
324                        };
325                }else if(dojo.isFunction(test)){
326                        // if we didn't get a fixture, wrap the function
327                        tObj = { "runTest": test };
328                        if(test["name"]){
329                                tObj.name = test.name;
330                        }else{
331                                try{
332                                        var fStr = "function ";
333                                        var ts = tObj.runTest+"";
334                                        if(0 <= ts.indexOf(fStr)){
335                                                tObj.name = ts.split(fStr)[1].split("(", 1)[0];
336                                        }
337                                        // doh.debug(tObj.runTest.toSource());
338                                }catch(e){
339                                }
340                        }
341                        // FIXME: try harder to get the test name here
342                }else if(dojo.isString(tObj.runTest)){
343                        tObj.runTest= new Function("t", tObj.runTest);
344                }
345                if(!tObj.runTest){
346                        return 0;
347                }
348
349                // if the test is designated as a particular type, do type-specific initialization
350                var testType= doh._testTypes[type] || doh._testTypes[tObj.testType];
351                if(testType){
352                        testType(group, tObj);
353                }
354
355                // add the test to this group
356                doh._groups[group].push(tObj);
357                doh._testCount++;
358                doh._testRegistered(group, tObj);
359
360                return tObj;
361        },
362
363        dumpArg= function(arg){
364                if(dojo.isString(arg)){
365                        return "string(" + arg + ")";
366                } else {
367                        return typeof arg;
368                }
369        },
370
371        illegalRegister= function(args, testArgPosition){
372                var hint= "\targuments: ";
373                for(var i= 0; i<5; i++){
374                        hint+= dumpArg(args[i]);
375                };
376                doh.debug("ERROR:");
377                if(testArgPosition){
378                        doh.debug("\tillegal arguments provided to dojo.register; the test at argument " + testArgPosition + " wasn't a test.");
379                }else{
380                        doh.debug("\tillegal arguments provided to dojo.register");
381                }
382                doh.debug(hint);
383        },
384
385        isUrl= function(arg){
386                return dojo.isString(arg) && (/^url\:/.test(arg) || !/\(/.test(arg));
387        };
388
389
390doh._testRegistered = function(group, fixture){
391        // slot to be filled in
392};
393
394doh._groupStarted = function(group){
395        // slot to be filled in
396};
397
398doh._groupFinished = function(group, success){
399        // slot to be filled in
400};
401
402doh._testStarted = function(group, fixture){
403        // slot to be filled in
404};
405
406doh._testFinished = function(group, fixture, success){
407        // slot to be filled in
408};
409
410doh._registerTest = function(group, test, type){
411        // summary:
412        //              add the provided test function or fixture object to the specified
413        //              test group.
414        // group: String
415        //              string name of the group to add the test to
416        // test: Function||String||Object
417        //              TODOC
418        // type: String?
419        //              An identifier denoting the type of testing that the test performs, such
420        //              as a performance test. If falsy, defaults to test.type.
421
422        // get, possibly create, the group object
423
424        var groupObj= this._groups[group];
425        if(!groupObj){
426                this._groupCount++;
427                groupObj= this._groups[group] = [];
428                groupObj.inFlight = 0;
429        }
430        if(!test){
431                return groupObj;
432        }
433
434        // create the test fixture
435        var tObj;
436        if(dojo.isFunction(test) || dojo.isString(test) || "runTest" in test){
437                return createFixture(group, test, type) ? groupObj : 0;
438        }else if(dojo.isArray(test)){
439                // a vector of tests...
440                for(var i= 0; i<test.length; i++){
441                        tObj = createFixture(group, test[i], type);
442                        if(!tObj){
443                                this.debug("ERROR:");
444                                this.debug("\tillegal test is test array; more information follows...");
445                                return null;
446                        }
447                }
448                return groupObj;
449        }else{
450                // a hash of tests...
451                for(var testName in test){
452                        var theTest= test[testName];
453                        if(dojo.isFunction(theTest) || dojo.isString(theTest)){
454                                tObj = createFixture(group, {name:testName, runTest:theTest}, type);
455                        }else{
456                                // should be an object
457                                theTest.name= theTest.name || testName;
458                                tObj = createFixture(group, theTest, type);
459                        }
460                        if(!tObj){
461                                this.debug("ERROR:");
462                                this.debug("\tillegal test is test hash; more information follows...");
463                                return null;
464                        }
465                }
466                return groupObj;
467        }
468};
469
470doh._registerTestAndCheck= function(groupId, test, type, testArgPosition, args, setUp, tearDown){
471        var amdMid= 0;
472        if(groupId){
473                if(type){
474                        // explicitly provided type; therefore don't try to get type from groupId
475                        var match= groupId.match(/([^\!]+)\!(.+)/);
476                        if(match){
477                                amdMid= match[1];
478                                groupId= match[2];
479                        }
480                }else{
481                        var parts= groupId && groupId.split("!");
482                        if(parts.length==3){
483                                amdMid= parts[0];
484                                groupId= parts[1];
485                                type= parts[2];
486                        }else if(parts.length==2){
487                                // either (amdMid, group) or (group, type)
488                                if(parts[1] in doh._testTypes){
489                                        groupId= parts[0];
490                                        type= parts[1];
491                                }else{
492                                        amdMid= parts[0];
493                                        groupId= parts[1];
494                                }
495                        } // else, no ! and just a groupId
496                }
497        }
498
499        var group= doh._registerTest(groupId, test, type);
500        if(group){
501                if(amdMid){
502                        group.amdMid= amdMid;
503                }
504                if(setUp){
505                        group.setUp= setUp;
506                }
507                if(tearDown){
508                        group.tearDown= tearDown;
509                }
510        }else{
511                illegalRegister(arguments, testArgPosition);
512        }
513};
514
515doh._registerUrl = function(/*String*/ group, /*String*/ url, /*Integer*/ timeout, /*String*/ type, /*object*/ dohArgs){
516        // slot to be filled in
517        this.debug("ERROR:");
518        this.debug("\tNO registerUrl() METHOD AVAILABLE.");
519};
520
521var typeSigs= (function(){
522        // Generate machinery to decode the many register signatures; these are the possible signatures.
523
524        var sigs= [
525                // note: to===timeout, up===setUp, down===tearDown
526
527                // 1 arg
528                "test", function(args, a1){doh._registerTestAndCheck("ungrouped", a1, 0, 0, args, 0, 0);},
529                "url", function(args, a1){doh._registerUrl("ungrouped", a1);},
530
531                // 2 args
532                "group-test", function(args, a1, a2){doh._registerTestAndCheck(a1, a2, 0, 0, args, 0, 0);},
533                "test-type", function(args, a1, a2){doh._registerTestAndCheck("ungrouped", a1, a2, 1, args, 0, 0);},
534                "test-up", function(args, a1, a2){doh._registerTestAndCheck("ungrouped", a1, 0, 0, args, a2, 0);},
535                "group-url", function(args, a1, a2){doh._registerUrl(a1, a2);},
536                "url-to", function(args, a1, a2){doh._registerUrl("ungrouped", a1, a2);},
537                "url-type", function(args, a1, a2){doh._registerUrl("ungrouped", a1, undefined, a2);},
538                "url-args", function(args, a1, a2){doh._registerUrl("ungrouped", a1, undefined, 0, a2);},
539
540                // 3 args
541                "group-test-type", function(args, a1, a2, a3){doh._registerTestAndCheck(a1, a2, a3, 2, args, 0, 0);},
542                "group-test-up", function(args, a1, a2, a3){doh._registerTestAndCheck(a1, a2, 0, 2, args, a3, 0);},
543                "test-type-up", function(args, a1, a2, a3){doh._registerTestAndCheck("ungrouped", a1, a2, 0, args, a3, 0);},
544                "test-up-down", function(args, a1, a2, a3){doh._registerTestAndCheck("ungrouped", a1, 0, 0, args, a2, a3);},
545                "group-url-to", function(args, a1, a2, a3){doh._registerUrl(a1, a2, a3);},
546                "group-url-type", function(args, a1, a2, a3){doh._registerUrl(a1, a2, undefined, a3);},
547                "group-url-args", function(args, a1, a2, a3){doh._registerUrl(a1, a2, undefined, 0, a3);},
548                "url-to-type", function(args, a1, a2, a3){doh._registerUrl("ungrouped", a1, a2, a3);},
549                "url-to-args", function(args, a1, a2, a3){doh._registerUrl("ungrouped", a1, a2, 0, a3);},
550                "url-type-args", function(args, a1, a2, a3){doh._registerUrl("ungrouped", a1, undefined, a2, a3);},
551
552                // 4 args
553                "group-test-type-up", function(args, a1, a2, a3, a4){doh._registerTestAndCheck(a1, a2, a3, 2, args, a3, 0);},
554                "group-test-up-down", function(args, a1, a2, a3, a4){doh._registerTestAndCheck(a1, a2, 0, 2, args, a3, a4);},
555                "test-type-up-down", function(args, a1, a2, a3, a4){doh._registerTestAndCheck("ungrouped", a1, 2, 0, args, a3, a4);},
556                "group-url-to-type", function(args, a1, a2, a3, a4){doh._registerUrl(a1, a2, a3, a4);},
557                "group-url-to-args", function(args, a1, a2, a3, a4){doh._registerUrl(a1, a2, a3, 0, a4);},
558                "group-url-type-args", function(args, a1, a2, a3, a4){doh._registerUrl(a1, a2, undefined, a3, a4);},
559                "url-to-type-args", function(args, a1, a2, a3, a4){doh._registerUrl("ungrouped", a1, a2, a3, a4);},
560
561                // 5 args
562                "group-test-type-up-down", function(args, a1, a2, a3, a4, a5){doh._registerTestAndCheck(a1, a2, a3, 2, args, a4, a5);},
563                "group-url-to-type-args", function(args, a1, a2, a3, a4){doh._registerUrl(a1, a2, a3, a4, a5);}
564        ];
565
566        // type-ids
567        // a - array
568        // st - string, possible type
569        // sf - string, possible function
570        // s - string not a type or function
571        // o - object
572        // f - function
573        // n - number
574    // see getTypeId inside doh.register
575        var argTypes= {
576                group:"st.sf.s",
577                test:"a.sf.o.f",
578                type:"st",
579                up:"f",
580                down:"f",
581                url:"s",
582                to:"n",
583                args:"o"
584        };
585        for(var p in argTypes){
586                argTypes[p]= argTypes[p].split(".");
587        };
588
589        function generateTypeSignature(sig, pattern, dest, func){
590                for(var nextPattern, reducedSig= sig.slice(1), typeList= argTypes[sig[0]], i= 0; i<typeList.length; i++){
591                        nextPattern=  pattern + (pattern ? "-" : "") + typeList[i];
592                        if(reducedSig.length){
593                                generateTypeSignature(reducedSig, nextPattern, dest, func);
594                        }else{
595                                dest.push(nextPattern, func);
596                        }
597                }
598        }
599
600        var typeSigs= [];
601        for(var sig, func, dest, i= 0; i<sigs.length; i++){
602                sig= sigs[i++].split("-");
603                func= sigs[i];
604                dest= typeSigs[sig.length-1] || (typeSigs[sig.length-1]= []);
605                generateTypeSignature(sig, "", dest, func);
606        }
607        return typeSigs;
608})();
609
610
611doh.register = function(a1, a2, a3, a4, a5){
612        /*=====
613        doh.register = function(groupId, testOrTests, timeoutOrSetUp, tearDown){
614        // summary:
615        //       Add a test or group of tests.
616        // groupId: String?
617        //              The name of the group, optionally with an AMD module identifier prefix and/or
618        //              test type suffix. The value character set for group names and AMD module indentifiers
619        //              is given by [A-Za-z0-9_/.-]. If provided, prefix and suffix are denoted by "!". If
620        //              provided, type must be a valid test type.
621        // testOrTests: Array||Function||Object||String||falsy
622        //              When a function, implies a function that defines a single test. DOH passes the
623        //              DOH object to the function as the sole argument when the test is executed. When
624        //              a string, implies the definition of a single test given by `new Function("t", testOrTests)`.
625        //              When an object that contains the method `runTest` (which *must* be a function),
626        //              implies a single test given by the value of the property `runTest`. In this case,
627        //              the object may also contain the methods `setup` and `tearDown`, and, if provided, these
628        //              will be invoked on either side of the test function. Otherwise when an object (that is,
629        //              an object that does not contain the method `runTest`), then a hash from test name to
630        //              test function (either a function or string as described above); any names that begin
631        //              with "_" are ignored. When an array, the array must exclusively contain functions,
632        //              strings, and/or objects as described above and each item is added to the group as
633        //              per the items semantics.
634        // timeoutOrSetUp: integer||Function?
635        //              If tests is a URL, then must be an integer giving the number milliseconds to wait for the test
636        //              page to load before signaling an error; otherwise, a function for initializing the test group.
637        //      If a tearDown function is given, then a setup function must also be given.
638        // tearDown: Function?
639        //              A function for deinitializing the test group.
640        // decription:
641        //       Adds the test or tests given by testsOrUrl to the group given by group (if any). For URL tests, unless
642        //       a group is explicitly provided the group given by the URL until the document arrives at which
643        //       point the group is renamed to the title of the document. For non-URL tests, if groupId is
644        //       not provided, then tests are added to the group "ungrouped"; otherwise if the given groupId does not
645        //       exist, it is created; otherwise, tests are added to the already-existing group.
646        //
647        //       groupIds may contain embedded AMD module identifiers as prefixes and/or test types as suffixes. Prefixes
648        //       and suffixes are denoted by a "!". For example
649        // example:
650        // | `"myTest/MyGroup"`                                                  // just a group, group ids need not include a slash
651        // | `"myTest/MyGroup!perf"`                                     // group with test type
652        // | `"path/to/amd/module!myTest/MyGroup"`               // group with AMD module identifier
653        // | `"path/to/amd/module!myTest/MyGroup!perf"`  // group with both AMD module identifier and test type
654        //
655        //       Groups associated with AMD module identifiers may be unloaded/reloaded if using an AMD loader with
656        //       reload support (dojo's AMD loader includes such support). If no AMD module identifier is given,
657        //       the loader supports reloading, and the user demands a reload, then the groupId will be used
658        //       as the AMD module identifier.
659        //
660        //       For URL tests, the groupId is changed to the document title (if any) upon document arrival. The
661        //       title may include a test type suffix denoted with a "!" as described above.
662        //
663        //       For URL tests, if timeout is a number, then sets the timeout for loading
664        //       the particular URL; otherwise, timeout is set to DOH.iframeTimeout.
665        //
666        //       For non-URL tests, if setUp and/or tearDown are provided, then any previous setUp and/or
667        //       tearDown functions for the group are replaced as given. You may affect just setUp and/or tearDown
668        //       for a group and not provide/add any tests by providing falsy for the test argument.
669        // example:
670        // | var
671        // |     t1= function(t) {
672        // |             // this is a test
673        // |             // t will be set to DOH when the test is executed by DOH
674        // |             // etc.
675        // |     },
676        // |
677        // |     t2= {
678        // |             // this is a test fixture and may be passed as a test
679        // |
680        // |             // runTest is always required...
681        // |             runTest:function(t){
682        // |                     //the test...
683        // |             },
684        // |
685        // |             // name is optional, but recommended...
686        // |             name:"myTest",
687        // |
688        // |             // preamble is optional...
689        // |             setUp:function(){
690        // |                     // will be executed by DOH prior to executing the test
691        // |             },
692        // |
693        // |             // postscript is optional...
694        // |             tearDown:function(){ //op
695        // |                     // will be executed by DOH after executing the test
696        // |             }
697        // |     }
698        // |
699        // |     t3= [
700        // |             // this is a vector of tests...
701        // |             t1, t2
702        // |     ],
703        // |
704        // |     t4= {
705        // |             // this is a map from test name to test or test fixture
706        // |             t5:function(t){
707        // |                     //etc.
708        // |             },
709        // |
710        // |             t6:{
711        // |                     runTest:function(t){
712        // |                            //etc.
713        // |                     }
714        // |                     // name will be automatically added as "t6"
715        // |             }
716        // |     },
717        // |
718        // |     aSetup:function(){
719        // |             // etc.
720        // |     },
721        // |
722        // |     aTearDown:function(){
723        // |             // etc.
724        // |     };
725        // | // (test); note, can't provide setup/tearDown without a group
726        // | doh.register(t1);
727        // |
728        // | // (group, test, setUp, tearDown) test and/or setUp and/or tearDown can be missing
729        // | doh.register("myGroup", 0, aSetUp, aTearDown);
730        // | doh.register("myGroup", t1, aSetUp, aTearDown);
731        // | doh.register("myGroup", t1, aSetUp);
732        // | doh.register("myGroup", t1, 0, aTearDown);
733        // | doh.register("myGroup", t1);
734        // |
735        // | // various kinds of test arguments are allowed
736        // | doh.register("myGroup", t2);
737        // | doh.register("myGroup", t3);
738        // | doh.register("myGroup", t4);
739        // |
740        // | // add a perf test
741        // | doh.register("myGroup!perf", t1);
742        // |
743        // | // add a perf test with an AMD module identifier
744        // | doh.register("path/to/my/module!myGroup!perf", t1);
745        //
746        //       doh.register also supports Dojo, v1.6- signature (group, test, type), although this signature is deprecated.
747        }
748        =====*/
749
750        function getTypeId(a){
751                if(a instanceof Array){
752                        return "a";
753                }else if(typeof a == "function"){
754                        return "f";
755                }else if(typeof a == "number"){
756                        return "n";
757                }else if(typeof a == "string"){
758                        if(a in doh._testTypes){
759                                return "st";
760                        }else if(/\(/.test(a)){
761                                return "sf";
762                        }else{
763                                return "s";
764                        }
765                }else{
766                        return "o";
767                }
768        }
769
770        var
771                arity= arguments.length,
772                search= typeSigs[arity-1],
773                sig= [],
774                i;
775        for(i= 0; i<arity; i++){
776                sig.push(getTypeId(arguments[i]));
777        }
778        sig= sig.join("-");
779        for(i= 0; i<search.length; i+= 2){
780                if(search[i]==sig){
781                        search[i+1](arguments, a1, a2, a3, a4, a5);
782                        return;
783                }
784        }
785        illegalRegister(arguments);
786};
787
788doh.registerDocTests = function(module){
789        //      summary:
790        //              Get all the doctests from the given module and register each of them
791        //              as a single test case here.
792        //
793        var docTest = new dojox.testing.DocTest();
794        var docTests = docTest.getTests(module);
795        var len = docTests.length;
796        var tests = [];
797        for (var i=0; i<len; i++){
798                var test = docTests[i];
799                // Extract comment on first line and add to test name.
800                var comment = "";
801                if (test.commands.length && test.commands[0].indexOf("//")!=-1) {
802                        var parts = test.commands[0].split("//");
803                        comment = ", "+parts[parts.length-1]; // Get all after the last //, so we dont get trapped by http:// or alikes :-).
804                }
805                tests.push({
806                        runTest: (function(test){
807                                return function(t){
808                                        var r = docTest.runTest(test.commands, test.expectedResult);
809                                        t.assertTrue(r.success);
810                                };
811                        })(test),
812                        name:"Line "+test.line+comment
813                }
814                );
815        }
816        this.register("DocTests: "+module, tests);
817};
818
819//
820// depricated v1.6- register API follows
821//
822
823doh.registerTest = function(/*String*/ group, /*Array||Function||Object*/ test, /*String*/ type){
824        // summary:
825        //              Deprecated.      Use doh.register(group/type, test) instead
826        doh.register(group + (type ? "!" + type : ""), test);
827};
828
829doh.registerGroup = function(/*String*/ group, /*Array||Function||Object*/ tests, /*Function*/ setUp, /*Function*/ tearDown, /*String*/ type){
830        // summary:
831        //              Deprecated.      Use doh.register(group/type, tests, setUp, tearDown) instead
832        var args= [(group ? group : "") + (type ? "!" + type : ""), tests];
833        setUp && args.push(setUp);
834        tearDown && args.push(tearDown);
835        doh.register.apply(doh, args);
836}
837
838doh.registerTestNs = function(/*String*/ group, /*Object*/ ns){
839        // summary:
840        //              Deprecated.      Use doh.register(group, ns) instead
841        doh.register(group, ns);
842};
843
844doh.registerTests = function(/*String*/ group, /*Array*/ testArr, /*String*/ type){
845        // summary:
846        //              Deprecated.      Use doh.register(group/type, testArr) instead
847        doh.register(group + (type ? "!" + type : ""), testArr);
848}
849
850doh.registerUrl = function(/*String*/ group, /*String*/ url, /*Integer*/ timeout, /*String*/ type, /*Object*/ args){
851        // summary:
852        //              Deprecated.      Use doh.register(group/type, url, timeout) instead
853        doh.register(group + (type ? "!" + type : ""), url+"", timeout || 10000, args || {});
854};
855
856//
857// Assertions and In-Test Utilities
858//
859doh.t = doh.assertTrue = function(/*Object*/ condition, /*String?*/ hint){
860        // summary:
861        //              is the passed item "truthy"?
862        if(arguments.length < 1){
863                throw new doh._AssertFailure("assertTrue failed because it was not passed at least 1 argument");
864        }
865        //if(dojo.isString(condition) && condition.length){
866        //      return true;
867        //}
868        if(!eval(condition)){
869                throw new doh._AssertFailure("assertTrue('" + condition + "') failed", hint);
870        }
871};
872
873doh.f = doh.assertFalse = function(/*Object*/ condition, /*String?*/ hint){
874        // summary:
875        //              is the passed item "falsey"?
876        if(arguments.length < 1){
877                throw new doh._AssertFailure("assertFalse failed because it was not passed at least 1 argument");
878        }
879        if(eval(condition)){
880                throw new doh._AssertFailure("assertFalse('" + condition + "') failed", hint);
881        }
882};
883
884doh.e = doh.assertError = function(/*Error object*/expectedError, /*Object*/scope, /*String*/functionName, /*Array*/args, /*String?*/ hint){
885        //      summary:
886        //              Test for a certain error to be thrown by the given function.
887        //      example:
888        //              t.assertError(dojox.data.QueryReadStore.InvalidAttributeError, store, "getValue", [item, "NOT THERE"]);
889        //              t.assertError(dojox.data.QueryReadStore.InvalidItemError, store, "getValue", ["not an item", "NOT THERE"]);
890        try{
891                scope[functionName].apply(scope, args);
892        }catch (e){
893                if(e instanceof expectedError){
894
895                        return true;
896                }else{
897                        throw new doh._AssertFailure("assertError() failed:\n\texpected error\n\t\t"+expectedError+"\n\tbut got\n\t\t"+e+"\n\n", hint);
898                }
899        }
900        throw new doh._AssertFailure("assertError() failed:\n\texpected error\n\t\t"+expectedError+"\n\tbut no error caught\n\n", hint);
901};
902
903doh.is = doh.assertEqual = function(/*Object*/ expected, /*Object*/ actual, /*String?*/ hint, doNotThrow){
904        // summary:
905        //              are the passed expected and actual objects/values deeply
906        //              equivalent?
907
908        // Compare undefined always with three equal signs, because undefined==null
909        // is true, but undefined===null is false.
910        if((expected === undefined)&&(actual === undefined)){
911                return true;
912        }
913        if(arguments.length < 2){
914                throw doh._AssertFailure("assertEqual failed because it was not passed 2 arguments");
915        }
916        if((expected === actual)||(expected == actual)||
917                                ( typeof expected == "number" && typeof actual == "number" && isNaN(expected) && isNaN(actual) )){
918
919                return true;
920        }
921        if( (this.isArray(expected) && this.isArray(actual))&&
922                (this._arrayEq(expected, actual)) ){
923                return true;
924        }
925        if( ((typeof expected == "object")&&((typeof actual == "object")))&&
926                (this._objPropEq(expected, actual)) ){
927                return true;
928        }
929        if (doNotThrow) {
930                return false;
931        }
932        throw new doh._AssertFailure("assertEqual() failed:\n\texpected\n\t\t"+expected+"\n\tbut got\n\t\t"+actual+"\n\n", hint);
933};
934
935doh.isNot = doh.assertNotEqual = function(/*Object*/ notExpected, /*Object*/ actual, /*String?*/ hint){
936        // summary:
937        //              are the passed notexpected and actual objects/values deeply
938        //              not equivalent?
939
940        // Compare undefined always with three equal signs, because undefined==null
941        // is true, but undefined===null is false.
942        if((notExpected === undefined)&&(actual === undefined)){
943                                throw new doh._AssertFailure("assertNotEqual() failed: not expected |"+notExpected+"| but got |"+actual+"|", hint);
944        }
945        if(arguments.length < 2){
946                throw doh._AssertFailure("assertEqual failed because it was not passed 2 arguments");
947        }
948        if((notExpected === actual)||(notExpected == actual)){
949                                throw new doh._AssertFailure("assertNotEqual() failed: not expected |"+notExpected+"| but got |"+actual+"|", hint);
950        }
951        if( (this.isArray(notExpected) && this.isArray(actual))&&
952                (this._arrayEq(notExpected, actual)) ){
953                throw new doh._AssertFailure("assertNotEqual() failed: not expected |"+notExpected+"| but got |"+actual+"|", hint);
954        }
955        if( ((typeof notExpected == "object")&&((typeof actual == "object"))) ){
956                var isequal = false;
957                try{
958                        isequal = this._objPropEq(notExpected, actual);
959                }catch(e){
960                        if(!(e instanceof doh._AssertFailure)){
961                                throw e; //other exceptions, just throw it
962                        }
963                }
964                if(isequal){
965                                throw new doh._AssertFailure("assertNotEqual() failed: not expected |"+notExpected+"| but got |"+actual+"|", hint);
966                }
967        }
968        return true;
969};
970
971doh._arrayEq = function(expected, actual){
972        if(expected.length != actual.length){ return false; }
973        // FIXME: we're not handling circular refs. Do we care?
974        for(var x=0; x<expected.length; x++){
975                if(!doh.assertEqual(expected[x], actual[x], 0, true)){ return false; }
976        }
977        return true;
978};
979
980doh._objPropEq = function(expected, actual){
981        // Degenerate case: if they are both null, then their "properties" are equal.
982        if(expected === null && actual === null){
983                return true;
984        }
985        // If only one is null, they aren't equal.
986        if(expected === null || actual === null){
987                return false;
988        }
989        if(expected instanceof Date){
990                return actual instanceof Date && expected.getTime()==actual.getTime();
991        }
992        var x;
993        // Make sure ALL THE SAME properties are in both objects!
994        for(x in actual){ // Lets check "actual" here, expected is checked below.
995                if(expected[x] === undefined){
996                        return false;
997                }
998        };
999
1000        for(x in expected){
1001                if(!doh.assertEqual(expected[x], actual[x], 0, true)){
1002                        return false;
1003                }
1004        }
1005        return true;
1006};
1007
1008//
1009// Runner-Wrapper
1010//
1011
1012doh._setupGroupForRun = function(/*String*/ groupName){
1013        var tg = this._groups[groupName];
1014        this.debug(this._line);
1015        this.debug("GROUP", "\""+groupName+"\"", "has", tg.length, "test"+((tg.length > 1) ? "s" : "")+" to run");
1016        doh._groupStarted(groupName);
1017};
1018
1019doh._handleFailure = function(groupName, fixture, e){
1020        // this.debug("FAILED test:", fixture.name);
1021        // mostly borrowed from JUM
1022        this._groups[groupName].failures++;
1023        var out = "";
1024        if(e instanceof this._AssertFailure){
1025                this._failureCount++;
1026                if(e["fileName"]){ out += e.fileName + ':'; }
1027                if(e["lineNumber"]){ out += e.lineNumber + ' '; }
1028                out += e.message;
1029                this.error("\t_AssertFailure:", out);
1030        }else{
1031                this._errorCount++;
1032                this.error("\tError:", e.message || e); // printing Error on IE9 (and other browsers?) yields "[Object Error]"
1033        }
1034        if(fixture.runTest["toSource"]){
1035                var ss = fixture.runTest.toSource();
1036                this.debug("\tERROR IN:\n\t\t", ss);
1037        }else{
1038                this.debug("\tERROR IN:\n\t\t", fixture.runTest);
1039        }
1040        if(e.rhinoException){
1041                e.rhinoException.printStackTrace();
1042        }else if(e.javaException){
1043                e.javaException.printStackTrace();
1044        }
1045};
1046
1047doh._runPerfFixture = function(/*String*/groupName, /*Object*/fixture){
1048        //      summary:
1049        //              This function handles how to execute a 'performance' test
1050        //              which is different from a straight UT style test.        These
1051        //              will often do numerous iterations of the same operation and
1052        //              gather execution statistics about it, like max, min, average,
1053        //              etc.    It makes use of the already in place DOH deferred test
1054        //              handling since it is a good idea to put a pause inbetween each
1055        //              iteration to allow for GC cleanup and the like.
1056        //
1057        //      groupName:
1058        //              The test group that contains this performance test.
1059        //      fixture:
1060        //              The performance test fixture.
1061        var tg = this._groups[groupName];
1062        fixture.startTime = new Date();
1063
1064        //Perf tests always need to act in an async manner as there is a
1065        //number of iterations to flow through.
1066        var def = new doh.Deferred();
1067        tg.inFlight++;
1068        def.groupName = groupName;
1069        def.fixture = fixture;
1070
1071        def.addErrback(function(err){
1072                doh._handleFailure(groupName, fixture, err);
1073        });
1074
1075        //Set up the finalizer.
1076        var retEnd = function(){
1077                if(fixture["tearDown"]){ fixture.tearDown(doh); }
1078                tg.inFlight--;
1079                if((!tg.inFlight)&&(tg.iterated)){
1080                        doh._groupFinished(groupName, !tg.failures);
1081                }
1082                doh._testFinished(groupName, fixture, def.results[0]);
1083                if(doh._paused){
1084                        doh.run();
1085                }
1086        };
1087
1088        //Since these can take who knows how long, we don't want to timeout
1089        //unless explicitly set
1090        var timer;
1091        var to = fixture.timeout;
1092        if(to > 0) {
1093                timer = setTimeout(function(){
1094                        // ret.cancel();
1095                        // retEnd();
1096                        def.errback(new Error("test timeout in "+fixture.name.toString()));
1097                }, to);
1098        }
1099
1100        //Set up the end calls to the test into the deferred we'll return.
1101        def.addBoth(function(arg){
1102                if(timer){
1103                        clearTimeout(timer);
1104                }
1105                retEnd();
1106        });
1107
1108        //Okay, now set up the timing loop for the actual test.
1109        //This is down as an async type test where there is a delay
1110        //between each execution to allow for GC time, etc, so the GC
1111        //has less impact on the tests.
1112        var res = fixture.results;
1113        res.trials = [];
1114
1115        //Try to figure out how many calls are needed to hit a particular threshold.
1116        var itrDef = doh._calcTrialIterations(groupName, fixture);
1117        itrDef.addErrback(function(err){
1118                fixture.endTime = new Date();
1119                def.errback(err);
1120        });
1121
1122        //Blah, since tests can be deferred, the actual run has to be deferred until after
1123        //we know how many iterations to run.    This is just plain ugly.
1124        itrDef.addCallback(function(iterations){
1125                if(iterations){
1126                        var countdown = fixture.trialIterations;
1127                        doh.debug("TIMING TEST: [" + fixture.name +
1128                                                "]\n\t\tITERATIONS PER TRIAL: " +
1129                                                iterations + "\n\tTRIALS: " +
1130                                                countdown);
1131
1132                        //Figure out how many times we want to run our 'trial'.
1133                        //Where each trial consists of 'iterations' of the test.
1134
1135                        var trialRunner = function() {
1136                                //Set up our function to execute a block of tests
1137                                var start = new Date();
1138                                var tTimer = new doh.Deferred();
1139                                var tCountdown = iterations;
1140
1141                                var tState = {
1142                                        countdown: iterations
1143                                };
1144                                var testRunner = function(state){
1145                                        while(state){
1146                                                try{
1147                                                        state.countdown--;
1148                                                        if(state.countdown){
1149                                                                var ret = fixture.runTest(doh);
1150                                                                if(ret && ret.addCallback){
1151                                                                        //Deferreds have to be handled async,
1152                                                                        //otherwise we just keep looping.
1153                                                                        var atState = {
1154                                                                                countdown: state.countdown
1155                                                                        };
1156                                                                        ret.addCallback(function(){
1157                                                                                testRunner(atState);
1158                                                                        });
1159                                                                        ret.addErrback(function(err) {
1160                                                                                doh._handleFailure(groupName, fixture, err);
1161                                                                                fixture.endTime = new Date();
1162                                                                                def.errback(err);
1163                                                                        });
1164                                                                        state = null;
1165                                                                }
1166                                                        }else{
1167                                                                tTimer.callback(new Date());
1168                                                                state = null;
1169                                                        }
1170                                                }catch(err){
1171                                                        fixture.endTime = new Date();
1172                                                        tTimer.errback(err);
1173                                                }
1174                                        }
1175                                };
1176                                tTimer.addCallback(function(end){
1177                                        //Figure out the results and try to factor out function call costs.
1178                                        var tResults = {
1179                                                trial: (fixture.trialIterations - countdown),
1180                                                testIterations: iterations,
1181                                                executionTime: (end.getTime() - start.getTime()),
1182                                                average: (end.getTime() - start.getTime())/iterations
1183                                        };
1184                                        res.trials.push(tResults);
1185                                        doh.debug("\n\t\tTRIAL #: " +
1186                                                                tResults.trial + "\n\tTIME: " +
1187                                                                tResults.executionTime + "ms.\n\tAVG TEST TIME: " +
1188                                                                (tResults.executionTime/tResults.testIterations) + "ms.");
1189
1190                                        //Okay, have we run all the trials yet?
1191                                        countdown--;
1192                                        if(countdown){
1193                                                setTimeout(trialRunner, fixture.trialDelay);
1194                                        }else{
1195                                                //Okay, we're done, lets compute some final performance results.
1196                                                var t = res.trials;
1197
1198
1199
1200                                                //We're done.
1201                                                fixture.endTime = new Date();
1202                                                def.callback(true);
1203                                        }
1204                                });
1205                                tTimer.addErrback(function(err){
1206                                        fixture.endTime = new Date();
1207                                        def.errback(err);
1208                                });
1209                                testRunner(tState);
1210                        };
1211                        trialRunner();
1212                }
1213        });
1214
1215        //Set for a pause, returned the deferred.
1216        if(def.fired < 0){
1217                doh.pause();
1218        }
1219        return def;
1220};
1221
1222doh._calcTrialIterations =      function(/*String*/ groupName, /*Object*/ fixture){
1223        //      summary:
1224        //              This function determines the rough number of iterations to
1225        //              use to reach a particular MS threshold.  This returns a deferred
1226        //              since tests can theoretically by async.  Async tests aren't going to
1227        //              give great perf #s, though.
1228        //              The callback is passed the # of iterations to hit the requested
1229        //              threshold.
1230        //
1231        //      fixture:
1232        //              The test fixture we want to calculate iterations for.
1233        var def = new doh.Deferred();
1234        var calibrate = function () {
1235                var testFunc = doh.hitch(fixture, fixture.runTest);
1236
1237                //Set the initial state.        We have to do this as a loop instead
1238                //of a recursive function.      Otherwise, it blows the call stack
1239                //on some browsers.
1240                var iState = {
1241                        start: new Date(),
1242                        curIter: 0,
1243                        iterations: 5
1244                };
1245                var handleIteration = function(state){
1246                        while(state){
1247                                if(state.curIter < state.iterations){
1248                                        try{
1249                                                var ret = testFunc(doh);
1250                                                if(ret && ret.addCallback){
1251                                                        var aState = {
1252                                                                start: state.start,
1253                                                                curIter: state.curIter + 1,
1254                                                                iterations: state.iterations
1255                                                        };
1256                                                        ret.addCallback(function(){
1257                                                                handleIteration(aState);
1258                                                        });
1259                                                        ret.addErrback(function(err) {
1260                                                                fixture.endTime = new Date();
1261                                                                def.errback(err);
1262                                                        });
1263                                                        state = null;
1264                                                }else{
1265                                                        state.curIter++;
1266                                                }
1267                                        }catch(err){
1268                                                fixture.endTime = new Date();
1269                                                def.errback(err);
1270                                                return;
1271                                        }
1272                                }else{
1273                                        var end = new Date();
1274                                        var totalTime = (end.getTime() - state.start.getTime());
1275                                        if(totalTime < fixture.trialDuration){
1276                                                var nState = {
1277                                                        iterations: state.iterations * 2,
1278                                                        curIter: 0
1279                                                };
1280                                                state = null;
1281                                                setTimeout(function(){
1282                                                        nState.start = new Date();
1283                                                        handleIteration(nState);
1284                                                }, 50);
1285                                        }else{
1286                                                var itrs = state.iterations;
1287                                                setTimeout(function(){def.callback(itrs)}, 50);
1288                                                state = null;
1289                                        }
1290                                }
1291                        }
1292                };
1293                handleIteration(iState);
1294        };
1295        setTimeout(calibrate, 10);
1296        return def;
1297};
1298
1299doh._runRegFixture = function(/*String*/groupName, /*Object*/fixture){
1300        //      summary:
1301        //              Function to run a generic doh test.      These are not
1302        //              specialized tests, like performance groups and such.
1303        //
1304        //      groupName:
1305        //              The groupName of the test.
1306        //      fixture:
1307        //              The test fixture to execute.
1308        var tg = this._groups[groupName];
1309        fixture.startTime = new Date();
1310        var ret = fixture.runTest(this);
1311        fixture.endTime = new Date();
1312        // if we get a deferred back from the test runner, we know we're
1313        // gonna wait for an async result. It's up to the test code to trap
1314        // errors and give us an errback or callback.
1315        if(ret && ret.addCallback){
1316                tg.inFlight++;
1317                ret.groupName = groupName;
1318                ret.fixture = fixture;
1319
1320                ret.addErrback(function(err){
1321                        doh._handleFailure(groupName, fixture, err);
1322                });
1323
1324                var retEnd = function(){
1325
1326                        if(fixture["tearDown"]){ fixture.tearDown(doh); }
1327                        tg.inFlight--;
1328                        doh._testFinished(groupName, fixture, ret.results[0]);
1329                        if((!tg.inFlight)&&(tg.iterated)){
1330                                doh._groupFinished(groupName, !tg.failures);
1331                        }
1332                        if(doh._paused){
1333                                doh.run();
1334                        }
1335                };
1336
1337                var timeoutFunction = function(){
1338                        fixture.endTime = new Date();
1339                        ret.errback(new Error("test timeout in "+fixture.name.toString()));
1340                };
1341
1342                var timer = setTimeout(function(){ timeoutFunction(); }, fixture["timeout"]||1000);
1343
1344                ret.addBoth(function(arg){
1345                        timeoutFunction = function(){}; // in IE8, the clearTimeout does not always stop the timer, so clear the function as well
1346                        clearTimeout(timer);
1347                        fixture.endTime = new Date();
1348                        retEnd();
1349                });
1350                if(ret.fired < 0){
1351                        doh.pause();
1352                }
1353                return ret;
1354        }
1355};
1356
1357doh._runFixture = function(groupName, fixture){
1358        var tg = this._groups[groupName];
1359        this._testStarted(groupName, fixture);
1360        var threw = false;
1361        var err = null;
1362        // run it, catching exceptions and reporting them
1363        try{
1364                // let doh reference "this.group.thinger..." which can be set by
1365                // another test or group-level setUp function
1366                fixture.group = tg;
1367                // only execute the parts of the fixture we've got
1368
1369                if(fixture["setUp"]){ fixture.setUp(this); }
1370                if(fixture["runTest"]){  // should we error out of a fixture doesn't have a runTest?
1371                        if(fixture.testType === "perf"){
1372                                //Always async deferred, so return it.
1373                                return doh._runPerfFixture(groupName, fixture);
1374                        }else{
1375                                //May or may not by async.
1376                                var ret = doh._runRegFixture(groupName, fixture);
1377                                if(ret){
1378                                        return ret;
1379                                }
1380                        }
1381                }
1382                if(fixture["tearDown"]){ fixture.tearDown(this); }
1383        }catch(e){
1384                threw = true;
1385                err = e;
1386                if(!fixture.endTime){
1387                        fixture.endTime = new Date();
1388                }
1389        }
1390        var d = new doh.Deferred();
1391        setTimeout(this.hitch(this, function(){
1392                if(threw){
1393                        this._handleFailure(groupName, fixture, err);
1394                }
1395                this._testFinished(groupName, fixture, !threw);
1396
1397                if((!tg.inFlight)&&(tg.iterated)){
1398                        doh._groupFinished(groupName, !tg.failures);
1399                }else if(tg.inFlight > 0){
1400                        setTimeout(this.hitch(this, function(){
1401                                doh.runGroup(groupName);
1402                        }), 100);
1403                        this._paused = true;
1404                }
1405                if(doh._paused){
1406                        doh.run();
1407                }
1408        }), 30);
1409        doh.pause();
1410        return d;
1411};
1412
1413doh.runGroup = function(/*String*/ groupName, /*Integer*/ idx){
1414        // summary:
1415        //              runs the specified test group
1416
1417        // the general structure of the algorithm is to run through the group's
1418        // list of doh, checking before and after each of them to see if we're in
1419        // a paused state. This can be caused by the test returning a deferred or
1420        // the user hitting the pause button. In either case, we want to halt
1421        // execution of the test until something external to us restarts it. This
1422        // means we need to pickle off enough state to pick up where we left off.
1423
1424        // FIXME: need to make fixture execution async!!
1425
1426        idx= idx || 0;
1427        var tg = this._groups[groupName];
1428        if(tg.skip === true){ return; }
1429        if(this.isArray(tg)){
1430                if(tg.iterated===undefined){
1431                        tg.iterated = false;
1432                        tg.inFlight = 0;
1433                        tg.failures = 0;
1434                        this._setupGroupForRun(groupName);
1435                        if(tg["setUp"]){ tg.setUp(this); }
1436                }
1437                for(var y=idx; y<tg.length; y++){
1438                        if(this._paused){
1439                                this._currentTest = y;
1440                                // this.debug("PAUSED at:", tg[y].name, this._currentGroup, this._currentTest);
1441                                return;
1442                        }
1443                        doh._runFixture(groupName, tg[y]);
1444                        if(this._paused){
1445                                this._currentTest = y+1;
1446                                if(this._currentTest == tg.length){ //RCG--don't think we need this; the next time through it will be taken care of
1447                                        tg.iterated = true;
1448                                }
1449                                // this.debug("PAUSED at:", tg[y].name, this._currentGroup, this._currentTest);
1450                                return;
1451                        }
1452                }
1453                tg.iterated = true;
1454                if(!tg.inFlight){
1455                        if(tg["tearDown"]){ tg.tearDown(this); }
1456                        doh._groupFinished(groupName, !tg.failures);
1457                }
1458        }
1459};
1460
1461doh._onEnd = function(){};
1462
1463doh._report = function(){
1464        // summary:
1465        //              a private method to be implemented/replaced by the "locally
1466        //              appropriate" test runner
1467
1468        // this.debug("ERROR:");
1469        // this.debug("\tNO REPORTING OUTPUT AVAILABLE.");
1470        // this.debug("\tIMPLEMENT doh._report() IN YOUR TEST RUNNER");
1471
1472        this.debug(this._line);
1473        this.debug("| TEST SUMMARY:");
1474        this.debug(this._line);
1475        this.debug("\t", this._testCount, "tests in", this._groupCount, "groups");
1476        this.debug("\t", this._errorCount, "errors");
1477        this.debug("\t", this._failureCount, "failures");
1478};
1479
1480doh.togglePaused = function(){
1481        this[(this._paused) ? "run" : "pause"]();
1482};
1483
1484doh.pause = function(){
1485        // summary:
1486        //              halt test run. Can be resumed.
1487        this._paused = true;
1488};
1489
1490doh.run = function(){
1491        // summary:
1492        //              begins or resumes the test process.
1493        // this.debug("STARTING");
1494        this._paused = false;
1495        var cg = this._currentGroup;
1496        var ct = this._currentTest;
1497        var found = false;
1498        if(!cg){
1499                this._init(); // we weren't paused
1500                found = true;
1501        }
1502        this._currentGroup = null;
1503        this._currentTest = null;
1504        for(var x in this._groups){
1505                if(
1506                        ( (!found)&&(x == cg) )||( found )
1507                ){
1508                        if(this._paused){ return; }
1509                        this._currentGroup = x;
1510                        if(!found){
1511                                found = true;
1512                                this.runGroup(x, ct);
1513                        }else{
1514                                this.runGroup(x);
1515                        }
1516                        if(this._paused){ return; }
1517                }
1518        }
1519        this._currentGroup = null;
1520        this._currentTest = null;
1521        this._paused = false;
1522        this._onEnd();
1523        this._report();
1524};
1525
1526doh.runOnLoad = function(){
1527        dojo.ready(doh, "run");
1528};
1529
1530return doh;
1531
1532});
1533
1534// backcompat hack: if in the browser, then loading doh/runner implies loading doh/_browserRunner. This is the
1535// behavior of 1.6- and is leveraged on many test documents that dojo.require("doh.runner"). Note that this
1536// hack will only work in synchronous mode; but if you're not in synchronous mode, you don't care about this.
1537if (typeof window!="undefined" && typeof location!="undefined" && typeof document!="undefined" && window.location==location && window.document==document) {
1538        require(["doh/_browserRunner"]);
1539}
Note: See TracBrowser for help on using the repository browser.