Subversion Repositories Applications.papyrus

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
2150 mathias 1
// FIXME: need to add async tests
2
// FIXME: need to handle URL wrapping and test registration/running from URLs
3
 
4
// package system gunk.
5
try{
6
	dojo.provide("doh.runner");
7
}catch(e){
8
	if(!this["doh"]){
9
		doh = {};
10
	}
11
}
12
 
13
//
14
// Utility Functions and Classes
15
//
16
 
17
doh.selfTest = false;
18
 
19
doh.hitch = function(/*Object*/thisObject, /*Function|String*/method /*, ...*/){
20
	var args = [];
21
	for(var x=2; x<arguments.length; x++){
22
		args.push(arguments[x]);
23
	}
24
	var fcn = ((typeof method == "string") ? thisObject[method] : method) || function(){};
25
	return function(){
26
		var ta = args.concat([]); // make a copy
27
		for(var x=0; x<arguments.length; x++){
28
			ta.push(arguments[x]);
29
		}
30
		return fcn.apply(thisObject, ta); // Function
31
	};
32
}
33
 
34
doh._mixin = function(/*Object*/ obj, /*Object*/ props){
35
	// summary:
36
	//		Adds all properties and methods of props to obj. This addition is
37
	//		"prototype extension safe", so that instances of objects will not
38
	//		pass along prototype defaults.
39
	var tobj = {};
40
	for(var x in props){
41
		// the "tobj" condition avoid copying properties in "props"
42
		// inherited from Object.prototype.  For example, if obj has a custom
43
		// toString() method, don't overwrite it with the toString() method
44
		// that props inherited from Object.protoype
45
		if((typeof tobj[x] == "undefined") || (tobj[x] != props[x])){
46
			obj[x] = props[x];
47
		}
48
	}
49
	// IE doesn't recognize custom toStrings in for..in
50
	if(	this["document"]
51
		&& document.all
52
		&& (typeof props["toString"] == "function")
53
		&& (props["toString"] != obj["toString"])
54
		&& (props["toString"] != tobj["toString"])
55
	){
56
		obj.toString = props.toString;
57
	}
58
	return obj; // Object
59
}
60
 
61
doh.mixin = function(/*Object*/obj, /*Object...*/props){
62
	// summary:	Adds all properties and methods of props to obj.
63
	for(var i=1, l=arguments.length; i<l; i++){
64
		doh._mixin(obj, arguments[i]);
65
	}
66
	return obj; // Object
67
}
68
 
69
doh.extend = function(/*Object*/ constructor, /*Object...*/ props){
70
	// summary:
71
	//		Adds all properties and methods of props to constructor's
72
	//		prototype, making them available to all instances created with
73
	//		constructor.
74
	for(var i=1, l=arguments.length; i<l; i++){
75
		doh._mixin(constructor.prototype, arguments[i]);
76
	}
77
	return constructor; // Object
78
}
79
 
80
 
81
doh._line = "------------------------------------------------------------";
82
 
83
/*
84
doh._delegate = function(obj, props){
85
	// boodman-crockford delegation
86
	function TMP(){};
87
	TMP.prototype = obj;
88
	var tmp = new TMP();
89
	if(props){
90
		dojo.lang.mixin(tmp, props);
91
	}
92
	return tmp;
93
}
94
*/
95
 
96
doh.debug = function(){
97
	// summary:
98
	//		takes any number of arguments and sends them to whatever debugging
99
	//		or logging facility is available in this environment
100
 
101
	// YOUR TEST RUNNER NEEDS TO IMPLEMENT THIS
102
}
103
 
104
doh._AssertFailure = function(msg){
105
	// idea for this as way of dis-ambiguating error types is from JUM.
106
	// The JUM is dead! Long live the JUM!
107
 
108
	if(!(this instanceof doh._AssertFailure)){
109
		return new doh._AssertFailure(msg);
110
	}
111
	this.message = new String(msg||"");
112
	return this;
113
}
114
doh._AssertFailure.prototype = new Error();
115
doh._AssertFailure.prototype.constructor = doh._AssertFailure;
116
doh._AssertFailure.prototype.name = "doh._AssertFailure";
117
 
118
doh.Deferred = function(canceller){
119
	this.chain = [];
120
	this.id = this._nextId();
121
	this.fired = -1;
122
	this.paused = 0;
123
	this.results = [null, null];
124
	this.canceller = canceller;
125
	this.silentlyCancelled = false;
126
};
127
 
128
doh.extend(doh.Deferred, {
129
	getTestCallback: function(cb, scope){
130
		var _this = this;
131
		return function(){
132
			try{
133
				cb.apply(scope||dojo.global||_this, arguments);
134
			}catch(e){
135
				_this.errback(e);
136
				return;
137
			}
138
			_this.callback(true);
139
		}
140
	},
141
 
142
	getFunctionFromArgs: function(){
143
		var a = arguments;
144
		if((a[0])&&(!a[1])){
145
			if(typeof a[0] == "function"){
146
				return a[0];
147
			}else if(typeof a[0] == "string"){
148
				return dojo.global[a[0]];
149
			}
150
		}else if((a[0])&&(a[1])){
151
			return doh.hitch(a[0], a[1]);
152
		}
153
		return null;
154
	},
155
 
156
	makeCalled: function() {
157
		var deferred = new doh.Deferred();
158
		deferred.callback();
159
		return deferred;
160
	},
161
 
162
	_nextId: (function(){
163
		var n = 1;
164
		return function(){ return n++; };
165
	})(),
166
 
167
	cancel: function(){
168
		if(this.fired == -1){
169
			if (this.canceller){
170
				this.canceller(this);
171
			}else{
172
				this.silentlyCancelled = true;
173
			}
174
			if(this.fired == -1){
175
				this.errback(new Error("Deferred(unfired)"));
176
			}
177
		}else if(	(this.fired == 0)&&
178
					(this.results[0] instanceof doh.Deferred)){
179
			this.results[0].cancel();
180
		}
181
	},
182
 
183
 
184
	_pause: function(){
185
		this.paused++;
186
	},
187
 
188
	_unpause: function(){
189
		this.paused--;
190
		if ((this.paused == 0) && (this.fired >= 0)) {
191
			this._fire();
192
		}
193
	},
194
 
195
	_continue: function(res){
196
		this._resback(res);
197
		this._unpause();
198
	},
199
 
200
	_resback: function(res){
201
		this.fired = ((res instanceof Error) ? 1 : 0);
202
		this.results[this.fired] = res;
203
		this._fire();
204
	},
205
 
206
	_check: function(){
207
		if(this.fired != -1){
208
			if(!this.silentlyCancelled){
209
				throw new Error("already called!");
210
			}
211
			this.silentlyCancelled = false;
212
			return;
213
		}
214
	},
215
 
216
	callback: function(res){
217
		this._check();
218
		this._resback(res);
219
	},
220
 
221
	errback: function(res){
222
		this._check();
223
		if(!(res instanceof Error)){
224
			res = new Error(res);
225
		}
226
		this._resback(res);
227
	},
228
 
229
	addBoth: function(cb, cbfn){
230
		var enclosed = this.getFunctionFromArgs(cb, cbfn);
231
		if(arguments.length > 2){
232
			enclosed = doh.hitch(null, enclosed, arguments, 2);
233
		}
234
		return this.addCallbacks(enclosed, enclosed);
235
	},
236
 
237
	addCallback: function(cb, cbfn){
238
		var enclosed = this.getFunctionFromArgs(cb, cbfn);
239
		if(arguments.length > 2){
240
			enclosed = doh.hitch(null, enclosed, arguments, 2);
241
		}
242
		return this.addCallbacks(enclosed, null);
243
	},
244
 
245
	addErrback: function(cb, cbfn){
246
		var enclosed = this.getFunctionFromArgs(cb, cbfn);
247
		if(arguments.length > 2){
248
			enclosed = doh.hitch(null, enclosed, arguments, 2);
249
		}
250
		return this.addCallbacks(null, enclosed);
251
	},
252
 
253
	addCallbacks: function(cb, eb){
254
		this.chain.push([cb, eb])
255
		if(this.fired >= 0){
256
			this._fire();
257
		}
258
		return this;
259
	},
260
 
261
	_fire: function(){
262
		var chain = this.chain;
263
		var fired = this.fired;
264
		var res = this.results[fired];
265
		var self = this;
266
		var cb = null;
267
		while (chain.length > 0 && this.paused == 0) {
268
			// Array
269
			var pair = chain.shift();
270
			var f = pair[fired];
271
			if(f == null){
272
				continue;
273
			}
274
			try {
275
				res = f(res);
276
				fired = ((res instanceof Error) ? 1 : 0);
277
				if(res instanceof doh.Deferred){
278
					cb = function(res){
279
						self._continue(res);
280
					}
281
					this._pause();
282
				}
283
			}catch(err){
284
				fired = 1;
285
				res = err;
286
			}
287
		}
288
		this.fired = fired;
289
		this.results[fired] = res;
290
		if((cb)&&(this.paused)){
291
			res.addBoth(cb);
292
		}
293
	}
294
});
295
 
296
//
297
// State Keeping and Reporting
298
//
299
 
300
doh._testCount = 0;
301
doh._groupCount = 0;
302
doh._errorCount = 0;
303
doh._failureCount = 0;
304
doh._currentGroup = null;
305
doh._currentTest = null;
306
doh._paused = true;
307
 
308
doh._init = function(){
309
	this._currentGroup = null;
310
	this._currentTest = null;
311
	this._errorCount = 0;
312
	this._failureCount = 0;
313
	this.debug(this._testCount, "tests to run in", this._groupCount, "groups");
314
}
315
 
316
// doh._urls = [];
317
doh._groups = {};
318
 
319
//
320
// Test Registration
321
//
322
 
323
doh.registerTestNs = function(/*String*/ group, /*Object*/ ns){
324
	// summary:
325
	//		adds the passed namespace object to the list of objects to be
326
	//		searched for test groups. Only "public" functions (not prefixed
327
	//		with "_") will be added as tests to be run. If you'd like to use
328
	//		fixtures (setUp(), tearDown(), and runTest()), please use
329
	//		registerTest() or registerTests().
330
	for(var x in ns){
331
		if(	(x.charAt(0) == "_") &&
332
			(typeof ns[x] == "function") ){
333
			this.registerTest(group, ns[x]);
334
		}
335
	}
336
}
337
 
338
doh._testRegistered = function(group, fixture){
339
	// slot to be filled in
340
}
341
 
342
doh._groupStarted = function(group){
343
	// slot to be filled in
344
}
345
 
346
doh._groupFinished = function(group, success){
347
	// slot to be filled in
348
}
349
 
350
doh._testStarted = function(group, fixture){
351
	// slot to be filled in
352
}
353
 
354
doh._testFinished = function(group, fixture, success){
355
	// slot to be filled in
356
}
357
 
358
doh.registerGroup = function(	/*String*/ group,
359
								/*Array||Function||Object*/ tests,
360
								/*Function*/ setUp,
361
								/*Function*/ tearDown){
362
	// summary:
363
	//		registers an entire group of tests at once and provides a setUp and
364
	//		tearDown facility for groups. If you call this method with only
365
	//		setUp and tearDown parameters, they will replace previously
366
	//		installed setUp or tearDown functions for the group with the new
367
	//		methods.
368
	// group:
369
	//		string name of the group
370
	// tests:
371
	//		either a function or an object or an array of functions/objects. If
372
	//		an object, it must contain at *least* a "runTest" method, and may
373
	//		also contain "setUp" and "tearDown" methods. These will be invoked
374
	//		on either side of the "runTest" method (respectively) when the test
375
	//		is run. If an array, it must contain objects matching the above
376
	//		description or test functions.
377
	// setUp: a function for initializing the test group
378
	// tearDown: a function for initializing the test group
379
	if(tests){
380
		this.register(group, tests);
381
	}
382
	if(setUp){
383
		this._groups[group].setUp = setUp;
384
	}
385
	if(tearDown){
386
		this._groups[group].tearDown = tearDown;
387
	}
388
}
389
 
390
doh._getTestObj = function(group, test){
391
	var tObj = test;
392
	if(typeof test == "string"){
393
		if(test.substr(0, 4)=="url:"){
394
			return this.registerUrl(group, test);
395
		}else{
396
			tObj = {
397
				name: test.replace("/\s/g", "_")
398
			};
399
			tObj.runTest = new Function("t", test);
400
		}
401
	}else if(typeof test == "function"){
402
		// if we didn't get a fixture, wrap the function
403
		tObj = { "runTest": test };
404
		if(test["name"]){
405
			tObj.name = test.name;
406
		}else{
407
			try{
408
				var fStr = "function ";
409
				var ts = tObj.runTest+"";
410
				if(0 <= ts.indexOf(fStr)){
411
					tObj.name = ts.split(fStr)[1].split("(", 1)[0];
412
				}
413
				// doh.debug(tObj.runTest.toSource());
414
			}catch(e){
415
			}
416
		}
417
		// FIXME: try harder to get the test name here
418
	}
419
	return tObj;
420
}
421
 
422
doh.registerTest = function(/*String*/ group, /*Function||Object*/ test){
423
	// summary:
424
	//		add the provided test function or fixture object to the specified
425
	//		test group.
426
	// group:
427
	//		string name of the group to add the test to
428
	// test:
429
	//		either a function or an object. If an object, it must contain at
430
	//		*least* a "runTest" method, and may also contain "setUp" and
431
	//		"tearDown" methods. These will be invoked on either side of the
432
	//		"runTest" method (respectively) when the test is run.
433
	if(!this._groups[group]){
434
		this._groupCount++;
435
		this._groups[group] = [];
436
		this._groups[group].inFlight = 0;
437
	}
438
	var tObj = this._getTestObj(group, test);
439
	if(!tObj){ return; }
440
	this._groups[group].push(tObj);
441
	this._testCount++;
442
	this._testRegistered(group, tObj);
443
	return tObj;
444
}
445
 
446
doh.registerTests = function(/*String*/ group, /*Array*/ testArr){
447
	// summary:
448
	//		registers a group of tests, treating each element of testArr as
449
	//		though it were being (along with group) passed to the registerTest
450
	//		method.
451
	for(var x=0; x<testArr.length; x++){
452
		this.registerTest(group, testArr[x]);
453
	}
454
}
455
 
456
// FIXME: move implementation to _browserRunner?
457
doh.registerUrl = function(	/*String*/ group,
458
								/*String*/ url,
459
								/*Integer*/ timeout){
460
	this.debug("ERROR:");
461
	this.debug("\tNO registerUrl() METHOD AVAILABLE.");
462
	// this._urls.push(url);
463
}
464
 
465
doh.registerString = function(group, str){
466
}
467
 
468
// FIXME: remove the doh.add alias SRTL.
469
doh.register = doh.add = function(groupOrNs, testOrNull){
470
	// summary:
471
	// 		"magical" variant of registerTests, registerTest, and
472
	// 		registerTestNs. Will accept the calling arguments of any of these
473
	// 		methods and will correctly guess the right one to register with.
474
	if(	(arguments.length == 1)&&
475
		(typeof groupOrNs == "string") ){
476
		if(groupOrNs.substr(0, 4)=="url:"){
477
			this.registerUrl(groupOrNs);
478
		}else{
479
			this.registerTest("ungrouped", groupOrNs);
480
		}
481
	}
482
	if(arguments.length == 1){
483
		this.debug("invalid args passed to doh.register():", groupOrNs, ",", testOrNull);
484
		return;
485
	}
486
	if(typeof testOrNull == "string"){
487
		if(testOrNull.substr(0, 4)=="url:"){
488
			this.registerUrl(testOrNull);
489
		}else{
490
			this.registerTest(groupOrNs, testOrNull);
491
		}
492
		// this.registerTestNs(groupOrNs, testOrNull);
493
		return;
494
	}
495
	if(doh._isArray(testOrNull)){
496
		this.registerTests(groupOrNs, testOrNull);
497
		return;
498
	}
499
	this.registerTest(groupOrNs, testOrNull);
500
}
501
 
502
//
503
// Assertions and In-Test Utilities
504
//
505
 
506
doh.t = doh.assertTrue = function(/*Object*/ condition){
507
	// summary:
508
	//		is the passed item "truthy"?
509
	if(arguments.length != 1){
510
		throw doh._AssertFailure("assertTrue failed because it was not passed exactly 1 argument");
511
	}
512
	if(!eval(condition)){
513
		throw doh._AssertFailure("assertTrue('" + condition + "') failed");
514
	}
515
}
516
 
517
doh.f = doh.assertFalse = function(/*Object*/ condition){
518
	// summary:
519
	//		is the passed item "falsey"?
520
	if(arguments.length != 1){
521
		throw doh._AssertFailure("assertFalse failed because it was not passed exactly 1 argument");
522
	}
523
	if(eval(condition)){
524
		throw doh._AssertFailure("assertFalse('" + condition + "') failed");
525
	}
526
}
527
 
528
doh.e = doh.assertError = function(/*Error object*/expectedError, /*Object*/scope, /*String*/functionName, /*Array*/args){
529
	//	summary:
530
	//		Test for a certain error to be thrown by the given function.
531
	//	example:
532
	//		t.assertError(dojox.data.QueryReadStore.InvalidAttributeError, store, "getValue", [item, "NOT THERE"]);
533
	//		t.assertError(dojox.data.QueryReadStore.InvalidItemError, store, "getValue", ["not an item", "NOT THERE"]);
534
	try{
535
		scope[functionName].apply(scope, args);
536
	}catch (e){
537
		if(e instanceof expectedError){
538
			return true;
539
		}else{
540
			throw new doh._AssertFailure("assertError() failed: expected error |"+expectedError+"| but got |"+e+"|");
541
		}
542
	}
543
	throw new doh._AssertFailure("assertError() failed: expected error |"+expectedError+"| but no error caught.");
544
}
545
 
546
 
547
doh.is = doh.assertEqual = function(/*Object*/ expected, /*Object*/ actual){
548
	// summary:
549
	//		are the passed expected and actual objects/values deeply
550
	//		equivalent?
551
 
552
	// Compare undefined always with three equal signs, because undefined==null
553
	// is true, but undefined===null is false.
554
	if((expected === undefined)&&(actual === undefined)){
555
		return true;
556
	}
557
	if(arguments.length < 2){
558
		throw doh._AssertFailure("assertEqual failed because it was not passed 2 arguments");
559
	}
560
	if((expected === actual)||(expected == actual)){
561
		return true;
562
	}
563
	if(	(this._isArray(expected) && this._isArray(actual))&&
564
		(this._arrayEq(expected, actual)) ){
565
		return true;
566
	}
567
	if( ((typeof expected == "object")&&((typeof actual == "object")))&&
568
		(this._objPropEq(expected, actual)) ){
569
		return true;
570
	}
571
	throw new doh._AssertFailure("assertEqual() failed: expected |"+expected+"| but got |"+actual+"|");
572
}
573
 
574
doh._arrayEq = function(expected, actual){
575
	if(expected.length != actual.length){ return false; }
576
	// FIXME: we're not handling circular refs. Do we care?
577
	for(var x=0; x<expected.length; x++){
578
		if(!doh.assertEqual(expected[x], actual[x])){ return false; }
579
	}
580
	return true;
581
}
582
 
583
doh._objPropEq = function(expected, actual){
584
	for(var x in expected){
585
		if(!doh.assertEqual(expected[x], actual[x])){
586
			return false;
587
		}
588
	}
589
	return true;
590
}
591
 
592
doh._isArray = function(it){
593
	return (it && it instanceof Array || typeof it == "array" || ((typeof dojo["NodeList"] != "undefined") && (it instanceof dojo.NodeList)));
594
}
595
 
596
//
597
// Runner-Wrapper
598
//
599
 
600
doh._setupGroupForRun = function(/*String*/ groupName, /*Integer*/ idx){
601
	var tg = this._groups[groupName];
602
	this.debug(this._line);
603
	this.debug("GROUP", "\""+groupName+"\"", "has", tg.length, "test"+((tg.length > 1) ? "s" : "")+" to run");
604
}
605
 
606
doh._handleFailure = function(groupName, fixture, e){
607
	// this.debug("FAILED test:", fixture.name);
608
	// mostly borrowed from JUM
609
	this._groups[groupName].failures++;
610
	var out = "";
611
	if(e instanceof this._AssertFailure){
612
		this._failureCount++;
613
		if(e["fileName"]){ out += e.fileName + ':'; }
614
		if(e["lineNumber"]){ out += e.lineNumber + ' '; }
615
		out += e+": "+e.message;
616
		this.debug("\t_AssertFailure:", out);
617
	}else{
618
		this._errorCount++;
619
	}
620
	this.debug(e);
621
	if(fixture.runTest["toSource"]){
622
		var ss = fixture.runTest.toSource();
623
		this.debug("\tERROR IN:\n\t\t", ss);
624
	}else{
625
		this.debug("\tERROR IN:\n\t\t", fixture.runTest);
626
	}
627
}
628
 
629
try{
630
	setTimeout(function(){}, 0);
631
}catch(e){
632
	setTimeout = function(func){
633
		return func();
634
	}
635
}
636
 
637
doh._runFixture = function(groupName, fixture){
638
	var tg = this._groups[groupName];
639
	this._testStarted(groupName, fixture);
640
	var threw = false;
641
	var err = null;
642
	// run it, catching exceptions and reporting them
643
	try{
644
		// let doh reference "this.group.thinger..." which can be set by
645
		// another test or group-level setUp function
646
		fixture.group = tg;
647
		// only execute the parts of the fixture we've got
648
		if(fixture["setUp"]){ fixture.setUp(this); }
649
		if(fixture["runTest"]){  // should we error out of a fixture doesn't have a runTest?
650
			fixture.startTime = new Date();
651
			var ret = fixture.runTest(this);
652
			fixture.endTime = new Date();
653
			// if we get a deferred back from the test runner, we know we're
654
			// gonna wait for an async result. It's up to the test code to trap
655
			// errors and give us an errback or callback.
656
			if(ret instanceof doh.Deferred){
657
 
658
				tg.inFlight++;
659
				ret.groupName = groupName;
660
				ret.fixture = fixture;
661
 
662
				ret.addErrback(function(err){
663
					doh._handleFailure(groupName, fixture, err);
664
				});
665
 
666
				var retEnd = function(){
667
					if(fixture["tearDown"]){ fixture.tearDown(doh); }
668
					tg.inFlight--;
669
					if((!tg.inFlight)&&(tg.iterated)){
670
						doh._groupFinished(groupName, (!tg.failures));
671
					}
672
					doh._testFinished(groupName, fixture, ret.results[0]);
673
					if(doh._paused){
674
						doh.run();
675
					}
676
				}
677
 
678
				var timer = setTimeout(function(){
679
					// ret.cancel();
680
					// retEnd();
681
					ret.errback(new Error("test timeout in "+fixture.name.toString()));
682
				}, fixture["timeout"]||1000);
683
 
684
				ret.addBoth(function(arg){
685
					clearTimeout(timer);
686
					retEnd();
687
				});
688
				if(ret.fired < 0){
689
					doh.pause();
690
				}
691
				return ret;
692
			}
693
		}
694
		if(fixture["tearDown"]){ fixture.tearDown(this); }
695
	}catch(e){
696
		threw = true;
697
		err = e;
698
		if(!fixture.endTime){
699
			fixture.endTime = new Date();
700
		}
701
	}
702
	var d = new doh.Deferred();
703
	setTimeout(this.hitch(this, function(){
704
		if(threw){
705
			this._handleFailure(groupName, fixture, err);
706
		}
707
		this._testFinished(groupName, fixture, (!threw));
708
 
709
		if((!tg.inFlight)&&(tg.iterated)){
710
			doh._groupFinished(groupName, (!tg.failures));
711
		}else if(tg.inFlight > 0){
712
			setTimeout(this.hitch(this, function(){
713
				doh.runGroup(groupName); // , idx);
714
			}), 100);
715
			this._paused = true;
716
		}
717
		if(doh._paused){
718
			doh.run();
719
		}
720
	}), 30);
721
	doh.pause();
722
	return d;
723
}
724
 
725
doh._testId = 0;
726
doh.runGroup = function(/*String*/ groupName, /*Integer*/ idx){
727
	// summary:
728
	//		runs the specified test group
729
 
730
	// the general structure of the algorithm is to run through the group's
731
	// list of doh, checking before and after each of them to see if we're in
732
	// a paused state. This can be caused by the test returning a deferred or
733
	// the user hitting the pause button. In either case, we want to halt
734
	// execution of the test until something external to us restarts it. This
735
	// means we need to pickle off enough state to pick up where we left off.
736
 
737
	// FIXME: need to make fixture execution async!!
738
 
739
	var tg = this._groups[groupName];
740
	if(tg.skip === true){ return; }
741
	if(this._isArray(tg)){
742
		if(idx<=tg.length){
743
			if((!tg.inFlight)&&(tg.iterated == true)){
744
				if(tg["tearDown"]){ tg.tearDown(this); }
745
				doh._groupFinished(groupName, (!tg.failures));
746
				return;
747
			}
748
		}
749
		if(!idx){
750
			tg.inFlight = 0;
751
			tg.iterated = false;
752
			tg.failures = 0;
753
		}
754
		doh._groupStarted(groupName);
755
		if(!idx){
756
			this._setupGroupForRun(groupName, idx);
757
			if(tg["setUp"]){ tg.setUp(this); }
758
		}
759
		for(var y=(idx||0); y<tg.length; y++){
760
			if(this._paused){
761
				this._currentTest = y;
762
				// this.debug("PAUSED at:", tg[y].name, this._currentGroup, this._currentTest);
763
				return;
764
			}
765
			doh._runFixture(groupName, tg[y]);
766
			if(this._paused){
767
				this._currentTest = y+1;
768
				if(this._currentTest == tg.length){
769
					tg.iterated = true;
770
				}
771
				// this.debug("PAUSED at:", tg[y].name, this._currentGroup, this._currentTest);
772
				return;
773
			}
774
		}
775
		tg.iterated = true;
776
		if(!tg.inFlight){
777
			if(tg["tearDown"]){ tg.tearDown(this); }
778
			doh._groupFinished(groupName, (!tg.failures));
779
		}
780
	}
781
}
782
 
783
doh._onEnd = function(){}
784
 
785
doh._report = function(){
786
	// summary:
787
	//		a private method to be implemented/replaced by the "locally
788
	//		appropriate" test runner
789
 
790
	// this.debug("ERROR:");
791
	// this.debug("\tNO REPORTING OUTPUT AVAILABLE.");
792
	// this.debug("\tIMPLEMENT doh._report() IN YOUR TEST RUNNER");
793
 
794
	this.debug(this._line);
795
	this.debug("| TEST SUMMARY:");
796
	this.debug(this._line);
797
	this.debug("\t", this._testCount, "tests in", this._groupCount, "groups");
798
	this.debug("\t", this._errorCount, "errors");
799
	this.debug("\t", this._failureCount, "failures");
800
}
801
 
802
doh.togglePaused = function(){
803
	this[(this._paused) ? "run" : "pause"]();
804
}
805
 
806
doh.pause = function(){
807
	// summary:
808
	//		halt test run. Can be resumed.
809
	this._paused = true;
810
}
811
 
812
doh.run = function(){
813
	// summary:
814
	//		begins or resumes the test process.
815
	// this.debug("STARTING");
816
	this._paused = false;
817
	var cg = this._currentGroup;
818
	var ct = this._currentTest;
819
	var found = false;
820
	if(!cg){
821
		this._init(); // we weren't paused
822
		found = true;
823
	}
824
	this._currentGroup = null;
825
	this._currentTest = null;
826
 
827
	for(var x in this._groups){
828
		if(
829
			( (!found)&&(x == cg) )||( found )
830
		){
831
			if(this._paused){ return; }
832
			this._currentGroup = x;
833
			if(!found){
834
				found = true;
835
				this.runGroup(x, ct);
836
			}else{
837
				this.runGroup(x);
838
			}
839
			if(this._paused){ return; }
840
		}
841
	}
842
	this._currentGroup = null;
843
	this._currentTest = null;
844
	this._paused = false;
845
	this._onEnd();
846
	this._report();
847
}
848
 
849
tests = doh;
850
 
851
(function(){
852
	// scop protection
853
	try{
854
		if(typeof dojo != "undefined"){
855
			dojo.platformRequire({
856
				browser: ["doh._browserRunner"],
857
				rhino: ["doh._rhinoRunner"],
858
				spidermonkey: ["doh._rhinoRunner"]
859
			});
860
			var _shouldRequire = (dojo.isBrowser) ? (dojo.global == dojo.global["parent"]) : true;
861
			if(_shouldRequire){
862
				if(dojo.isBrowser){
863
					dojo.addOnLoad(function(){
864
						if(dojo.byId("testList")){
865
							var _tm = ( (dojo.global.testModule && dojo.global.testModule.length) ? dojo.global.testModule : "dojo.tests.module");
866
							dojo.forEach(_tm.split(","), dojo.require, dojo);
867
							setTimeout(function(){
868
								doh.run();
869
							}, 500);
870
						}
871
					});
872
				}else{
873
					// dojo.require("doh._base");
874
				}
875
			}
876
		}else{
877
			if(
878
				(typeof load == "function")&&
879
				(	(typeof Packages == "function")||
880
					(typeof Packages == "object")	)
881
			){
882
				throw new Error();
883
			}else if(typeof load == "function"){
884
				throw new Error();
885
			}
886
		}
887
	}catch(e){
888
		print("\n"+doh._line);
889
		print("The Dojo Unit Test Harness, $Rev$");
890
		print("Copyright (c) 2007, The Dojo Foundation, All Rights Reserved");
891
		print(doh._line, "\n");
892
 
893
		load("_rhinoRunner.js");
894
 
895
		try{
896
			var dojoUrl = "../../dojo/dojo.js";
897
			var testUrl = "";
898
			var testModule = "dojo.tests.module";
899
			for(var x=0; x<arguments.length; x++){
900
				if(arguments[x].indexOf("=") > 0){
901
					var tp = arguments[x].split("=");
902
					if(tp[0] == "dojoUrl"){
903
						dojoUrl = tp[1];
904
					}
905
					if(tp[0] == "testUrl"){
906
						testUrl = tp[1];
907
					}
908
					if(tp[0] == "testModule"){
909
						testModule = tp[1];
910
					}
911
				}
912
			}
913
			if(dojoUrl.length){
914
				if(!this["djConfig"]){
915
					djConfig = {};
916
				}
917
				djConfig.baseUrl = dojoUrl.split("dojo.js")[0];
918
				load(dojoUrl);
919
			}
920
			if(testUrl.length){
921
				load(testUrl);
922
			}
923
			if(testModule.length){
924
				dojo.forEach(testModule.split(","), dojo.require, dojo);
925
			}
926
		}catch(e){
927
		}
928
 
929
		doh.run();
930
	}
931
})();