catdad/grandma

View on GitHub
lib/run.js

Summary

Maintainability
A
1 hr
Test Coverage
A
100%
var util = require('util');

var _ = require('lodash');
var async = require('async');
var Duration = require('duration-js');

var isStream = require('./is-stream.js');
var forkmaster = require('./forkmaster.js');

function validateNumberProp(opts, prop) {
    if (opts[prop] !== undefined && (!_.isNumber(opts[prop]) || opts[prop] === 0)) {
        throw new Error(prop + ' is invalid');
    }
}

function validateDurationProp(opts, prop, required) {
    var val = opts[prop];
    var isValidString = _.isString(val) && /\d+(ms|[smhdw])$/.test(val);
    var isNumber = _.isNumber(val);

    if (isValidString || isNumber) {
        opts[prop] = (new Duration(val)).milliseconds();
    } else if (required) {
        throw new Error(val === undefined ?
            util.format('%s is not defined', prop) :
            util.format('%s is invalid', prop));
    } else {
        opts[prop] = undefined;
    }
}

function validateOpts(options) {
    // really, this should be using garry,
    // but it is not written yet

    var objectMode = false;

    try {
        isStream.assertObjectStream(options.output, 'writable', '');
        // yay, and object stream... so we will use object mode
        objectMode = true;
    } catch(e) {
        // Ignore the above error, we are only using it to validate
        // object mode. We'll still check for a regular writable stream
        // here and use it anyway.
        isStream.assertStream(options.output, 'writable', 'no writable output stream defined');
    }

    validateDurationProp(options, 'duration', true);
    validateDurationProp(options, 'timeout', false);

    if (options.rate === undefined && options.concurrent === undefined) {
        throw new Error('either options.rate or options.concurrent is required');
    }

    validateNumberProp(options, 'rate');
    validateNumberProp(options, 'concurrent');

    var isLowDuration = options.rate < 0 ||
        Math.floor(options.rate * (options.duration / 1000)) < 1;

    if (isLowDuration && options.duration !== 0) {
        throw new Error('duration is too low');
    }

    if (options.concurrent) {
        delete options.rate;
    }

    options._objectMode = objectMode;

    return options;
}

function runSingleTest(options, callback) {
    delete options.tests;

    return forkmaster(_.merge({
        filepath: options.test.path,
        testname: options.test.name,
        threads: 1
    }, options), callback);
}

function run(options, callback) {

    try {
        options = validateOpts(options);
    } catch(e) {
        return async.setImmediate(callback.bind(undefined, e));
    }

    return runSingleTest(options, callback);
}

module.exports = run;