summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/css/css-transitions/support/runParallelAsyncHarness.js
blob: de9b7837900b4daa3884babe6545b57ea901da15 (plain)
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
146
147
148
(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);
        });
    });

    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 slice (possibly abort)
        var concludeSlice = 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);
        }

        // wait on RAF before cleanup to make sure all queued event handlers have run
        setTimeout(function() {requestAnimationFrame(concludeSlice)},duration);
    }

    // allow DOMContentLoaded before actually doing something
    setTimeout(runLoop, 100);
};

})(window);