Skip to content

Commit 2f83202

Browse files
committed
Core: Advance the processQueue when errors are thrown in callbacks
1 parent 371bdad commit 2f83202

File tree

9 files changed

+311
-156
lines changed

9 files changed

+311
-156
lines changed

Gruntfile.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ module.exports = function( grunt ) {
151151
"test/reorderError2.html",
152152
"test/callbacks.html",
153153
"test/callbacks-promises.html",
154+
"test/callbacks-rejected-promises.html",
154155
"test/events.html",
155156
"test/events-in-test.html",
156157
"test/logs.html",

reporter/html.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -858,16 +858,15 @@ export function escapeText( s ) {
858858
} );
859859

860860
QUnit.testDone( function( details ) {
861-
var testTitle, time, testItem, assertList, status,
861+
var testTitle, time, assertList, status,
862862
good, bad, testCounts, skipped, sourceName,
863-
tests = id( "qunit-tests" );
863+
tests = id( "qunit-tests" ),
864+
testItem = id( "qunit-test-output-" + details.testId );
864865

865-
if ( !tests ) {
866+
if ( !tests || !testItem ) {
866867
return;
867868
}
868869

869-
testItem = id( "qunit-test-output-" + details.testId );
870-
871870
removeClass( testItem, "running" );
872871

873872
if ( details.failed > 0 ) {

src/core.js

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ export const globalSuite = new SuiteReport();
2727
// it since each module has a suiteReport associated with it.
2828
config.currentModule.suiteReport = globalSuite;
2929

30-
var globalStartCalled = false;
31-
var runStarted = false;
30+
let globalStartCalled = false;
31+
let runStarted = false;
3232

3333
// Figure out if we're running the tests from a server or not
3434
QUnit.isLocal = !( defined.document && window.location.protocol !== "file:" );
@@ -50,7 +50,7 @@ extend( QUnit, {
5050
only: only,
5151

5252
start: function( count ) {
53-
var globalStartAlreadyCalled = globalStartCalled;
53+
const globalStartAlreadyCalled = globalStartCalled;
5454

5555
if ( !config.current ) {
5656
globalStartCalled = true;
@@ -149,8 +149,7 @@ function unblockAndAdvanceQueue() {
149149
}
150150

151151
export function begin() {
152-
var i, l,
153-
modulesLog = [];
152+
const modulesLog = [];
154153

155154
// If the test run hasn't officially begun yet
156155
if ( !config.started ) {
@@ -164,19 +163,22 @@ export function begin() {
164163
}
165164

166165
// Avoid unnecessary information by not logging modules' test environments
167-
for ( i = 0, l = config.modules.length; i < l; i++ ) {
166+
config.modules.forEach( configModule => {
168167
modulesLog.push( {
169-
name: config.modules[ i ].name,
170-
tests: config.modules[ i ].tests
168+
name: configModule.name,
169+
tests: configModule.tests
171170
} );
172-
}
171+
} );
173172

174173
// The test run is officially beginning now
175174
emit( "runStart", globalSuite.start( true ) );
176175
runLoggingCallbacks( "begin", {
177176
totalTests: Test.count,
178177
modules: modulesLog
179-
} ).then( unblockAndAdvanceQueue );
178+
} ).then( unblockAndAdvanceQueue, function( err ) {
179+
setTimeout( unblockAndAdvanceQueue );
180+
throw err;
181+
} );
180182
} else {
181183
unblockAndAdvanceQueue();
182184
}

src/core/logging.js

Lines changed: 55 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,73 @@
11
import config from "./config";
22
import { objectType } from "./utilities";
33
import Promise from "../promise";
4+
import {
5+
setTimeout
6+
} from "../globals";
47

5-
// Register logging callbacks
6-
export function registerLoggingCallbacks( obj ) {
7-
var i, l, key,
8-
callbackNames = [ "begin", "done", "log", "testStart", "testDone",
9-
"moduleStart", "moduleDone" ];
10-
11-
function registerLoggingCallback( key ) {
12-
var loggingCallback = function( callback ) {
13-
if ( objectType( callback ) !== "function" ) {
14-
throw new Error(
15-
"QUnit logging methods require a callback function as their first parameters."
16-
);
8+
const callbackNames = [
9+
"begin",
10+
"done",
11+
"log",
12+
"testStart",
13+
"testDone",
14+
"moduleStart",
15+
"moduleDone"
16+
];
17+
18+
function _promisfyCallbacksSequentially( callbacks, args ) {
19+
return callbacks.reduce( ( promiseChain, callback ) => {
20+
return promiseChain.then(
21+
() => callback( args ),
22+
( err ) => {
23+
setTimeout( callback( args ) );
24+
throw err;
1725
}
26+
);
27+
}, Promise.resolve( [] ) );
28+
}
1829

19-
config.callbacks[ key ].push( callback );
20-
};
30+
function _registerLoggingCallback( key ) {
31+
const loggingCallback = ( callback ) => {
32+
if ( objectType( callback ) !== "function" ) {
33+
throw new Error(
34+
"QUnit logging methods require a callback function as their first parameters."
35+
);
36+
}
2137

22-
return loggingCallback;
23-
}
38+
config.callbacks[ key ].push( callback );
39+
};
2440

25-
for ( i = 0, l = callbackNames.length; i < l; i++ ) {
26-
key = callbackNames[ i ];
41+
return loggingCallback;
42+
}
43+
44+
/**
45+
* Register logging callbacks to qunit
46+
*
47+
* @export
48+
* @param {Object} qunit
49+
*/
50+
export function registerLoggingCallbacks( qunit ) {
51+
callbackNames.forEach( key => {
2752

2853
// Initialize key collection of logging callback
2954
if ( objectType( config.callbacks[ key ] ) === "undefined" ) {
3055
config.callbacks[ key ] = [];
3156
}
32-
33-
obj[ key ] = registerLoggingCallback( key );
34-
}
57+
qunit[ key ] = _registerLoggingCallback( key );
58+
} );
3559
}
3660

61+
/**
62+
* execute all callbacks from the given key as a sequential list of promises
63+
*
64+
* @export
65+
* @param {String} key
66+
* @param {Object} args
67+
* @returns {Promise}
68+
*/
3769
export function runLoggingCallbacks( key, args ) {
38-
var callbacks = config.callbacks[ key ];
70+
const callbacks = config.callbacks[ key ];
3971

4072
// Handling 'log' callbacks separately. Unlike the other callbacks,
4173
// the log callback is not controlled by the processing queue,
@@ -46,10 +78,5 @@ export function runLoggingCallbacks( key, args ) {
4678
return;
4779
}
4880

49-
// ensure that each callback is executed serially
50-
return callbacks.reduce( ( promiseChain, callback ) => {
51-
return promiseChain.then( () => {
52-
return Promise.resolve( callback( args ) );
53-
} );
54-
}, Promise.resolve( [] ) );
81+
return _promisfyCallbacksSequentially( callbacks, args );
5582
}

src/core/processing-queue.js

Lines changed: 42 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,19 @@ function processTaskQueue( start ) {
6161

6262
if ( !defined.setTimeout || config.updateRate <= 0 || elapsedTime < config.updateRate ) {
6363
const task = taskQueue.shift();
64-
Promise.resolve( task() ).then( function() {
64+
const processNextTaskOrAdvance = () => {
6565
if ( !taskQueue.length ) {
6666
advance();
6767
} else {
6868
processTaskQueue( start );
6969
}
70-
} );
70+
};
71+
const throwAndAdvance = ( err ) => {
72+
setTimeout( advance );
73+
throw err;
74+
};
75+
76+
Promise.resolve( task() ).then( processNextTaskOrAdvance, throwAndAdvance );
7177
} else {
7278
setTimeout( advance );
7379
}
@@ -153,20 +159,9 @@ function unitSamplerGenerator( seed ) {
153159
};
154160
}
155161

156-
/**
157-
* This function is called when the ProcessingQueue is done processing all
158-
* items. It handles emitting the final run events.
159-
*/
160-
function done() {
161-
const storage = config.storage;
162-
163-
ProcessingQueue.finished = true;
164-
165-
const runtime = now() - config.started;
166-
const passed = config.stats.all - config.stats.bad;
167-
162+
// Throws corresponding error if no tests were executed
163+
function throwErrorIfNoTestExecuted() {
168164
if ( config.stats.all === 0 ) {
169-
170165
if ( config.filter && config.filter.length ) {
171166
throw new Error( `No tests matched the filter "${config.filter}".` );
172167
}
@@ -182,29 +177,46 @@ function done() {
182177
if ( config.testId && config.testId.length ) {
183178
throw new Error( `No tests matched the testId "${config.testId}".` );
184179
}
185-
186180
throw new Error( "No tests were run." );
181+
}
182+
}
183+
184+
// Clear own storage items when tests completes
185+
function cleanStorage() {
186+
const storage = config.storage;
187+
if ( storage && config.stats.bad === 0 ) {
188+
for ( let i = storage.length - 1; i >= 0; i-- ) {
189+
const key = storage.key( i );
187190

191+
if ( key.indexOf( "qunit-test-" ) === 0 ) {
192+
storage.removeItem( key );
193+
}
194+
}
195+
}
196+
}
197+
198+
/**
199+
* This function is called when the ProcessingQueue is done processing all
200+
* items. It handles emitting the final run events.
201+
*/
202+
function done() {
203+
ProcessingQueue.finished = true;
204+
205+
try {
206+
throwErrorIfNoTestExecuted();
207+
} catch ( err ) {
208+
throw err;
188209
}
189210

190211
emit( "runEnd", globalSuite.end( true ) );
191212
runLoggingCallbacks( "done", {
192-
passed,
213+
passed: config.stats.all - config.stats.bad,
193214
failed: config.stats.bad,
194215
total: config.stats.all,
195-
runtime
196-
} ).then( () => {
197-
198-
// Clear own storage items if all tests passed
199-
if ( storage && config.stats.bad === 0 ) {
200-
for ( let i = storage.length - 1; i >= 0; i-- ) {
201-
const key = storage.key( i );
202-
203-
if ( key.indexOf( "qunit-test-" ) === 0 ) {
204-
storage.removeItem( key );
205-
}
206-
}
207-
}
216+
runtime: now() - config.started
217+
} ).then( cleanStorage, function( err ) {
218+
cleanStorage();
219+
throw err;
208220
} );
209221
}
210222

0 commit comments

Comments
 (0)