Skip to content

Commit e071239

Browse files
committed
Core: ensure process-queue advance with rejected promises
1 parent 371bdad commit e071239

File tree

8 files changed

+203
-71
lines changed

8 files changed

+203
-71
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: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,10 @@ export function begin() {
176176
runLoggingCallbacks( "begin", {
177177
totalTests: Test.count,
178178
modules: modulesLog
179-
} ).then( unblockAndAdvanceQueue );
179+
} ).then( unblockAndAdvanceQueue, function( err ) {
180+
setTimeout( unblockAndAdvanceQueue );
181+
throw err;
182+
} );
180183
} else {
181184
unblockAndAdvanceQueue();
182185
}

src/core/logging.js

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,42 @@
11
import config from "./config";
22
import { objectType } from "./utilities";
33
import Promise from "../promise";
4+
import {
5+
setTimeout
6+
} from "../globals";
7+
8+
function _promisfyCallbacksSequentially( callbacks, args ) {
9+
return callbacks.reduce( ( promiseChain, callback ) => {
10+
const executeCallback = () => callback( args );
11+
return promiseChain.then(
12+
executeCallback,
13+
( err ) => {
14+
setTimeout( executeCallback );
15+
throw err;
16+
}
17+
);
18+
}, Promise.resolve( [] ) );
19+
}
20+
21+
function _registerLoggingCallback( key ) {
22+
const loggingCallback = ( callback ) => {
23+
if ( objectType( callback ) !== "function" ) {
24+
throw new Error(
25+
"QUnit logging methods require a callback function as their first parameters."
26+
);
27+
}
28+
29+
config.callbacks[ key ].push( callback );
30+
};
31+
32+
return loggingCallback;
33+
}
434

5-
// Register logging callbacks
635
export function registerLoggingCallbacks( obj ) {
736
var i, l, key,
837
callbackNames = [ "begin", "done", "log", "testStart", "testDone",
938
"moduleStart", "moduleDone" ];
1039

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-
);
17-
}
18-
19-
config.callbacks[ key ].push( callback );
20-
};
21-
22-
return loggingCallback;
23-
}
24-
2540
for ( i = 0, l = callbackNames.length; i < l; i++ ) {
2641
key = callbackNames[ i ];
2742

@@ -30,7 +45,7 @@ export function registerLoggingCallbacks( obj ) {
3045
config.callbacks[ key ] = [];
3146
}
3247

33-
obj[ key ] = registerLoggingCallback( key );
48+
obj[ key ] = _registerLoggingCallback( key );
3449
}
3550
}
3651

@@ -46,10 +61,5 @@ export function runLoggingCallbacks( key, args ) {
4661
return;
4762
}
4863

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( [] ) );
64+
return _promisfyCallbacksSequentially( callbacks, args );
5565
}

src/core/processing-queue.js

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,23 @@ 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+
// Without throwAndAdvance, qunit does not continue advanceing the processing queue if
77+
// task() throws an error
78+
Promise.resolve( task() )
79+
.then( processNextTaskOrAdvance, throwAndAdvance )
80+
.catch( throwAndAdvance );
7181
} else {
7282
setTimeout( advance );
7383
}
@@ -153,13 +163,25 @@ function unitSamplerGenerator( seed ) {
153163
};
154164
}
155165

166+
// Clear own storage items when tests completes
167+
function cleanStorage() {
168+
const storage = config.storage;
169+
if ( storage && config.stats.bad === 0 ) {
170+
for ( let i = storage.length - 1; i >= 0; i-- ) {
171+
const key = storage.key( i );
172+
173+
if ( key.indexOf( "qunit-test-" ) === 0 ) {
174+
storage.removeItem( key );
175+
}
176+
}
177+
}
178+
}
179+
156180
/**
157181
* This function is called when the ProcessingQueue is done processing all
158182
* items. It handles emitting the final run events.
159183
*/
160184
function done() {
161-
const storage = config.storage;
162-
163185
ProcessingQueue.finished = true;
164186

165187
const runtime = now() - config.started;
@@ -193,18 +215,9 @@ function done() {
193215
failed: config.stats.bad,
194216
total: config.stats.all,
195217
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-
}
218+
} ).then( cleanStorage, function( err ) {
219+
cleanStorage();
220+
throw err;
208221
} );
209222
}
210223

src/test.js

Lines changed: 49 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -118,19 +118,25 @@ Test.prototype = {
118118

119119
// ensure the callbacks are executed serially for each module
120120
var callbackPromises = notStartedModules.reduce( ( promiseChain, startModule ) => {
121-
return promiseChain.then( () => {
121+
const moduleStartCallback = () => {
122122
startModule.stats = { all: 0, bad: 0, started: now() };
123123
emit( "suiteStart", startModule.suiteReport.start( true ) );
124124
return runLoggingCallbacks( "moduleStart", {
125125
name: startModule.name,
126126
tests: startModule.tests
127127
} );
128-
} );
128+
};
129+
130+
return promiseChain.then( moduleStartCallback, moduleStartCallback );
129131
}, Promise.resolve( [] ) );
130132

131-
return callbackPromises.then( () => {
133+
const testStartHandler = () => {
132134
config.current = this;
133-
135+
const testStartResolvedHandler = () => {
136+
if ( !config.pollution ) {
137+
saveGlobal();
138+
}
139+
};
134140
this.testEnvironment = extend( {}, module.testEnvironment );
135141

136142
this.started = now();
@@ -140,10 +146,16 @@ Test.prototype = {
140146
module: module.name,
141147
testId: this.testId,
142148
previousFailure: this.previousFailure
143-
} ).then( () => {
144-
if ( !config.pollution ) {
145-
saveGlobal();
146-
}
149+
} ).then( testStartResolvedHandler, function( err ) {
150+
setTimeout( testStartResolvedHandler );
151+
throw err;
152+
} );
153+
};
154+
155+
return callbackPromises.then( testStartHandler, ( err ) => {
156+
return Promise.reject( err ).catch( ( err ) => {
157+
setTimeout( testStartHandler );
158+
throw err;
147159
} );
148160
} );
149161
},
@@ -319,23 +331,7 @@ Test.prototype = {
319331
emit( "testEnd", this.testReport.end( true ) );
320332
this.testReport.slimAssertions();
321333

322-
return runLoggingCallbacks( "testDone", {
323-
name: testName,
324-
module: moduleName,
325-
skipped: skipped,
326-
todo: todo,
327-
failed: bad,
328-
passed: this.assertions.length - bad,
329-
total: this.assertions.length,
330-
runtime: skipped ? 0 : this.runtime,
331-
332-
// HTML Reporter use
333-
assertions: this.assertions,
334-
testId: this.testId,
335-
336-
// Source of Test
337-
source: this.stack
338-
} ).then( function() {
334+
const testDoneResolvedHandler = function() {
339335
if ( module.testsRun === numberOfTests( module ) ) {
340336
const completedModules = [ module ];
341337

@@ -353,8 +349,35 @@ Test.prototype = {
353349
} );
354350
}, Promise.resolve( [] ) );
355351
}
356-
} ).then( function() {
352+
};
353+
354+
return runLoggingCallbacks( "testDone", {
355+
name: testName,
356+
module: moduleName,
357+
skipped: skipped,
358+
todo: todo,
359+
failed: bad,
360+
passed: this.assertions.length - bad,
361+
total: this.assertions.length,
362+
runtime: skipped ? 0 : this.runtime,
363+
364+
// HTML Reporter use
365+
assertions: this.assertions,
366+
testId: this.testId,
367+
368+
// Source of Test
369+
source: this.stack
370+
} ).then(
371+
testDoneResolvedHandler,
372+
function( err ) {
373+
setTimeout( testDoneResolvedHandler );
374+
throw err;
375+
}
376+
).then( function() {
377+
config.current = undefined;
378+
}, function( err ) {
357379
config.current = undefined;
380+
throw err;
358381
} );
359382

360383
function logSuiteEnd( module ) {
@@ -660,7 +683,6 @@ function checkPollution() {
660683
if ( newGlobals.length > 0 ) {
661684
pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) );
662685
}
663-
664686
deletedGlobals = diff( old, config.pollution );
665687
if ( deletedGlobals.length > 0 ) {
666688
pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) );
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="UTF-8">
5+
<title>QUnit Callbacks Test Suite</title>
6+
<link rel="stylesheet" href="../dist/qunit.css">
7+
<script src="../dist/qunit.js"></script>
8+
<script src="callbacks-rejected-promises.js"></script>
9+
</head>
10+
<body>
11+
<div id="qunit"></div>
12+
</body>
13+
</html>

0 commit comments

Comments
 (0)