Skip to content

Commit

Permalink
Core: Extract processing logic into ProcessingQueue
Browse files Browse the repository at this point in the history
  • Loading branch information
Trent Willis committed Mar 24, 2017
1 parent c65a374 commit b4d311f
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 139 deletions.
64 changes: 5 additions & 59 deletions src/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,22 @@ import { window, setTimeout } from "./globals";
import equiv from "./equiv";
import dump from "./dump";
import Assert from "./assert";
import Test, { test, skip, only, todo, pushFailure, generateHash } from "./test";
import Test, { test, skip, only, todo, pushFailure } from "./test";
import exportQUnit from "./export";

import config from "./core/config";
import { defined, extend, objectType, is, now } from "./core/utilities";
import { defined, extend, objectType, is, now, generateHash } from "./core/utilities";
import { registerLoggingCallbacks, runLoggingCallbacks } from "./core/logging";
import { sourceFromStacktrace } from "./core/stacktrace";
import ProcessingQueue from "./core/processing-queue";

import SuiteReport from "./reports/suite";

import { on, emit } from "./events";
import onError from "./core/onerror";

const QUnit = {};
const globalSuite = new SuiteReport();
export const globalSuite = new SuiteReport();

// The initial "currentModule" represents the global (or top-level) module that
// is not explicitly defined by the user, therefore we add the "globalSuite" to
Expand Down Expand Up @@ -240,62 +241,7 @@ export function begin() {
}

config.blocking = false;
process( true );
}

export function process( last ) {
function next() {
process( last );
}
var start = now();
config.depth = ( config.depth || 0 ) + 1;

while ( config.queue.length && !config.blocking ) {
if ( !defined.setTimeout || config.updateRate <= 0 ||
( ( now() - start ) < config.updateRate ) ) {
if ( config.current ) {

// Reset async tracking for each phase of the Test lifecycle
config.current.usedAsync = false;
}
config.queue.shift()();
} else {
setTimeout( next, 13 );
break;
}
}
config.depth--;
if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
done();
}
}

function done() {
var runtime, passed, i, key,
storage = config.storage;

internalState.autorun = true;

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

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

// Clear own storage items if all tests passed
if ( storage && config.stats.bad === 0 ) {
for ( i = storage.length - 1; i >= 0; i-- ) {
key = storage.key( i );
if ( key.indexOf( "qunit-test-" ) === 0 ) {
storage.removeItem( key );
}
}
}
ProcessingQueue.advance( true );
}

function setHook( module, hookName ) {
Expand Down
155 changes: 155 additions & 0 deletions src/core/processing-queue.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import config from "./config";
import {
defined,
generateHash,
now,
objectType
} from "./utilities";
import {
runLoggingCallbacks
} from "./logging";

import {
internalState,
globalSuite
} from "../core";
import {
emit
} from "../events";
import {
setTimeout
} from "../globals";

let priorityCount = 0;
let unitSampler;

/**
* Advances the ProcessingQueue to the next item if it is ready.
* @param {Boolean} last
*/
function advance( last ) {
function next() {
advance( last );
}

const start = now();
config.depth = ( config.depth || 0 ) + 1;

while ( config.queue.length && !config.blocking ) {
const elapsedTime = now() - start;

if ( !defined.setTimeout || config.updateRate <= 0 || elapsedTime < config.updateRate ) {
if ( config.current ) {

// Reset async tracking for each phase of the Test lifecycle
config.current.usedAsync = false;
}

config.queue.shift()();
} else {
setTimeout( next, 13 );
break;
}
}

config.depth--;

if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
done();
}
}

/**
* Adds a function to the ProcessingQueue for execution.
* @param {Function|Array} callback
* @param {Boolean} priority
* @param {String} seed
*/
function addToQueue( callback, priority, seed ) {
const last = !priority;

if ( objectType( callback ) === "array" ) {
while ( callback.length ) {
addToQueue( callback.shift() );
}

return;
}

if ( priority ) {
config.queue.splice( priorityCount++, 0, callback );
} else if ( seed ) {
if ( !unitSampler ) {
unitSampler = unitSamplerGenerator( seed );
}

// Insert into a random position after all priority items
const index = Math.floor( unitSampler() * ( config.queue.length - priorityCount + 1 ) );
config.queue.splice( priorityCount + index, 0, callback );
} else {
config.queue.push( callback );
}

if ( internalState.autorun && !config.blocking ) {
advance( last );
}
}

/**
* Creates a seeded "sample" generator which is used for randomizing tests.
*/
function unitSamplerGenerator( seed ) {

// 32-bit xorshift, requires only a nonzero seed
// http://excamera.com/sphinx/article-xorshift.html
let sample = parseInt( generateHash( seed ), 16 ) || -1;
return function() {
sample ^= sample << 13;
sample ^= sample >>> 17;
sample ^= sample << 5;

// ECMAScript has no unsigned number type
if ( sample < 0 ) {
sample += 0x100000000;
}

return sample / 0x100000000;
};
}

/**
* 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;

internalState.autorun = true;

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

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

// 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 );
}
}
}
}

export default {
add: addToQueue,
advance
};
21 changes: 21 additions & 0 deletions src/core/utilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,24 @@ export function objectType( obj ) {
export function is( type, obj ) {
return objectType( obj ) === type;
}

// Based on Java's String.hashCode, a simple but not
// rigorously collision resistant hashing function
export function generateHash( module, testName ) {
const str = module + "\x1C" + testName;
let hash = 0;

for ( let i = 0; i < str.length; i++ ) {
hash = ( ( hash << 5 ) - hash ) + str.charCodeAt( i );
hash |= 0;
}

// Convert the possibly negative integer hash code into an 8 character hex string, which isn't
// strictly necessary but increases user understanding that the id is a SHA-like hash
let hex = ( 0x100000000 + hash ).toString( 16 );
if ( hex.length < 8 ) {
hex = "0000000" + hex;
}

return hex.slice( -8 );
}
Loading

0 comments on commit b4d311f

Please sign in to comment.