1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
|
(function(root){
'use strict';
// testharness doesn't know about async test queues,
// so this wrapper takes care of that
/* USAGE:
runParallelAsyncHarness({
// list of data to test, must be array of objects.
// each object must contain a "name" property to describe the test
// besides name, the object can contain whatever data you need
tests: [
{name: "name of test 1", custom: "data"},
{name: "name of test 2", custom: "data"},
// ...
],
// number of tests (tests, not test-cases!) to run concurrently
testsPerSlice: 100,
// time in milliseconds a test-run takes
duration: 1000,
// test-cases to run for for the test - there must be at least one
// each case creates its separate async_test() instance
cases: {
// test case named "test1"
test1: {
// run as a async_test.step() this callback contains your primary assertions
start: function(testCaseKey, data, options){},
// run as a async_test.step() this callback contains assertions to be run
// when the test ended, immediately before teardown
done: function(testCaseKey, data, options){}
},
// ...
}
// all callbacks are optional:
// invoked for individual test before it starts so you can setup the environment
// like DOM, CSS, adding event listeners and such
setup: function(data, options){},
// invoked after a test ended, so you can clean up the environment
// like DOM, CSS, removing event listeners and such
teardown: function(data, options){},
// invoked before a batch of tests ("slice") are run concurrently
// tests is an array of test data objects
sliceStart: function(options, tests)
// invoked after a batch of tests ("slice") were run concurrently
// tests is an array of test data objects
sliceDone: function(options, tests)
// invoked once all tests are done
done: function(options){}
})
*/
root.runParallelAsyncHarness = function(options) {
if (!options.cases) {
throw new Error("Options don't contain test cases!");
}
var noop = function(){};
// add a 100ms buffer to the test timeout, just in case
var duration = Math.ceil(options.duration + 100);
// names of individual tests
var cases = Object.keys(options.cases);
// run tests in a batch of slices
// primarily not to overload weak devices (tablets, phones, …)
// with too many tests running simultaneously
var iteration = -1;
var testPerSlice = options.testsPerSlice || 100;
var slices = Math.ceil(options.tests.length / testPerSlice);
// initialize all async test cases
// Note: satisfying testharness.js needs to know all async tests before load-event
options.tests.forEach(function(data, index) {
data.cases = {};
cases.forEach(function(name) {
data.cases[name] = async_test(data.name + " / " + name, {timeout: options.timeout || 60000});
});
});
function runLoop() {
iteration++;
if (iteration >= slices) {
// no more slice, we're done
(options.done || noop)(options);
return;
}
// grab a slice of testss and initialize them
var offset = iteration * testPerSlice;
var tests = options.tests.slice(offset, offset + testPerSlice);
tests.forEach(function(data) {
(options.setup || noop)(data, options);
});
// kick off the current slice of tests
(options.sliceStart || noop)(options, tests);
// perform individual "start" test-case
tests.forEach(function(data) {
cases.forEach(function(name) {
data.cases[name].step(function() {
(options.cases[name].start || noop)(data.cases[name], data, options);
});
});
});
// conclude test (possibly abort)
setTimeout(function() {
tests.forEach(function(data) {
// perform individual "done" test-case
cases.forEach(function(name) {
data.cases[name].step(function() {
(options.cases[name].done || noop)(data.cases[name], data, options);
});
});
// clean up after individual test
(options.teardown || noop)(data, options);
// tell harness we're done with individual test-cases
cases.forEach(function(name) {
data.cases[name].done();
});
});
// finish the test for current slice of tests
(options.sliceDone || noop)(options, tests);
// next test please, give the browser 50ms to do catch its breath
setTimeout(runLoop, 50);
}, duration);
}
// allow DOMContentLoaded before actually doing something
setTimeout(runLoop, 100);
};
})(window);
|