Skip to content

Commit

Permalink
Core: Advance the processQueue when errors are thrown in callbacks
Browse files Browse the repository at this point in the history
  • Loading branch information
step2yeung committed Apr 29, 2019
1 parent 371bdad commit 2b36e3f
Show file tree
Hide file tree
Showing 9 changed files with 269 additions and 107 deletions.
1 change: 1 addition & 0 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ module.exports = function( grunt ) {
"test/reorderError2.html",
"test/callbacks.html",
"test/callbacks-promises.html",
"test/callbacks-rejected-promises.html",
"test/events.html",
"test/events-in-test.html",
"test/logs.html",
Expand Down
9 changes: 4 additions & 5 deletions reporter/html.js
Original file line number Diff line number Diff line change
Expand Up @@ -858,16 +858,15 @@ export function escapeText( s ) {
} );

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

if ( !tests ) {
if ( !tests || !testItem ) {
return;
}

testItem = id( "qunit-test-output-" + details.testId );

removeClass( testItem, "running" );

if ( details.failed > 0 ) {
Expand Down
5 changes: 4 additions & 1 deletion src/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,10 @@ export function begin() {
runLoggingCallbacks( "begin", {
totalTests: Test.count,
modules: modulesLog
} ).then( unblockAndAdvanceQueue );
} ).then( unblockAndAdvanceQueue, function( err ) {
setTimeout( unblockAndAdvanceQueue );
throw err;
} );
} else {
unblockAndAdvanceQueue();
}
Expand Down
83 changes: 55 additions & 28 deletions src/core/logging.js
Original file line number Diff line number Diff line change
@@ -1,41 +1,73 @@
import config from "./config";
import { objectType } from "./utilities";
import Promise from "../promise";
import {
setTimeout
} from "../globals";

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

function registerLoggingCallback( key ) {
var loggingCallback = function( callback ) {
if ( objectType( callback ) !== "function" ) {
throw new Error(
"QUnit logging methods require a callback function as their first parameters."
);
const callbackNames = [
"begin",
"done",
"log",
"testStart",
"testDone",
"moduleStart",
"moduleDone"
];

function _promisfyCallbacksSequentially( callbacks, args ) {
return callbacks.reduce( ( promiseChain, callback ) => {
return promiseChain.then(
() => callback( args ),
( err ) => {
setTimeout( callback( args ) );
throw err;
}
);
}, Promise.resolve( [] ) );
}

config.callbacks[ key ].push( callback );
};
function _registerLoggingCallback( key ) {
const loggingCallback = ( callback ) => {
if ( objectType( callback ) !== "function" ) {
throw new Error(
"QUnit logging methods require a callback function as their first parameters."
);
}

return loggingCallback;
}
config.callbacks[ key ].push( callback );
};

for ( i = 0, l = callbackNames.length; i < l; i++ ) {
key = callbackNames[ i ];
return loggingCallback;
}

/**
* Register logging callbacks to qunit
*
* @export
* @param {Object} qunit
*/
export function registerLoggingCallbacks( qunit ) {
callbackNames.forEach( key => {

// Initialize key collection of logging callback
if ( objectType( config.callbacks[ key ] ) === "undefined" ) {
config.callbacks[ key ] = [];
}

obj[ key ] = registerLoggingCallback( key );
}
qunit[ key ] = _registerLoggingCallback( key );
} );
}

/**
* execute all callbacks from the given key as a sequential list of promises
*
* @export
* @param {String} key
* @param {Object} args
* @returns {Promise}
*/
export function runLoggingCallbacks( key, args ) {
var callbacks = config.callbacks[ key ];
const callbacks = config.callbacks[ key ];

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

// ensure that each callback is executed serially
return callbacks.reduce( ( promiseChain, callback ) => {
return promiseChain.then( () => {
return Promise.resolve( callback( args ) );
} );
}, Promise.resolve( [] ) );
return _promisfyCallbacksSequentially( callbacks, args );
}
72 changes: 42 additions & 30 deletions src/core/processing-queue.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,19 @@ function processTaskQueue( start ) {

if ( !defined.setTimeout || config.updateRate <= 0 || elapsedTime < config.updateRate ) {
const task = taskQueue.shift();
Promise.resolve( task() ).then( function() {
const processNextTaskOrAdvance = () => {
if ( !taskQueue.length ) {
advance();
} else {
processTaskQueue( start );
}
} );
};
const throwAndAdvance = ( err ) => {
setTimeout( advance );
throw new Error( err );
};

Promise.resolve( task() ).then( processNextTaskOrAdvance, throwAndAdvance );
} else {
setTimeout( advance );
}
Expand Down Expand Up @@ -153,20 +159,9 @@ function unitSamplerGenerator( seed ) {
};
}

/**
* This function is called when the ProcessingQueue is done processing all
* items. It handles emitting the final run events.
*/
function done() {
const storage = config.storage;

ProcessingQueue.finished = true;

const runtime = now() - config.started;
const passed = config.stats.all - config.stats.bad;

// Throws corresponding error if no tests were executed
function throwErrorIfNoTestExecuted() {
if ( config.stats.all === 0 ) {

if ( config.filter && config.filter.length ) {
throw new Error( `No tests matched the filter "${config.filter}".` );
}
Expand All @@ -182,29 +177,46 @@ function done() {
if ( config.testId && config.testId.length ) {
throw new Error( `No tests matched the testId "${config.testId}".` );
}

throw new Error( "No tests were run." );
}
}

// Clear own storage items when tests completes
function cleanStorage() {
const storage = config.storage;
if ( storage && config.stats.bad === 0 ) {
for ( let i = storage.length - 1; i >= 0; i-- ) {
const key = storage.key( i );

if ( key.indexOf( "qunit-test-" ) === 0 ) {
storage.removeItem( key );
}
}
}
}

/**
* This function is called when the ProcessingQueue is done processing all
* items. It handles emitting the final run events.
*/
function done() {
ProcessingQueue.finished = true;

try {
throwErrorIfNoTestExecuted();
} catch ( err ) {
throw err;
}

emit( "runEnd", globalSuite.end( true ) );
runLoggingCallbacks( "done", {
passed,
passed: config.stats.all - config.stats.bad,
failed: config.stats.bad,
total: config.stats.all,
runtime
} ).then( () => {

// Clear own storage items if all tests passed
if ( storage && config.stats.bad === 0 ) {
for ( let i = storage.length - 1; i >= 0; i-- ) {
const key = storage.key( i );

if ( key.indexOf( "qunit-test-" ) === 0 ) {
storage.removeItem( key );
}
}
}
runtime: now() - config.started
} ).then( cleanStorage, function( err ) {
cleanStorage();
throw err;
} );
}

Expand Down
Loading

0 comments on commit 2b36e3f

Please sign in to comment.