gristlabs_grist-core/test/utils.js

170 lines
5.7 KiB
JavaScript
Raw Normal View History

/* global location */
var _ = require('underscore');
var Chance = require('chance');
var assert = require('chai').assert;
function mod(r) { return function(x) { return x%r; }; }
exports.mod = mod;
/**
* Runs the given function for the specified number of iterations and returns the total time taken.
* This function has no side effects.
* @param {Function} func - function to apply
* @param {object} context - this
* @param {Array} args - array of arguments to apply on the function
* @param {Integer} options.iters - number of iterations to apply the given function
* @param {Boolean} options.avg - if true, return the avg iteration time, else return the total time
*/
function time(func, context, args, options) {
console.assert(options.iters > 0, "Number of iterations must be greater than 0");
var start, copy;
var elapsed = 0;
// Apply the function on a copy of the context on each iteration to avoid side effects
for (var i = 0; i < options.iters; i++) {
copy = _.clone(context);
start = Date.now();
func.apply(copy, args);
elapsed += Date.now() - start;
}
if (options.avg) return elapsed/options.iters;
else return elapsed;
}
exports.time = time;
/**
* Repeats running the given function on the given arguments count times, returning the last
* result.
*/
function repeat(count, func, varArgs) {
var ret, args = Array.prototype.slice.call(arguments, 2);
for (var i = 0; i < count; i++) {
ret = func.apply(null, args);
}
return ret;
}
exports.repeat = repeat;
/**
* Defines a test suite for running timing tests. See documentation for exports.timing.
*/
function timingDescribe(desc, func) {
// If under Node, non-empty ENABLE_TIMING_TESTS environment variable turns on the timing tests.
// If under the Browser, we look for 'timing=1' among URL params, set by test/browser.js.
var enableTimingTests = (process.browser ?
(location.search.substr(1).split("&").indexOf("timing=1") !== -1) :
process.env.ENABLE_TIMING_TESTS);
function body() {
func();
// We collect the tests, then check if any of them exceeded the expected timing. We do it in
// one pass in after() (rather than in afterEach()) to allow them all to run, since it's
// useful to see all their timings.
var testsToCheck = [];
afterEach(function() {
testsToCheck.push(this.currentTest);
});
after(function() {
testsToCheck.forEach(function(test) {
if (test.expectedDuration) {
assert.isBelow(test.duration, test.expectedDuration * 1.5, "Test took longer than expected");
}
});
});
}
if (enableTimingTests) {
return describe(desc, body);
} else {
return describe.skip(desc + " (skipping timing test)", body);
}
}
/**
* Defines a test case for a timing test. This should be used in place of it() for timing test
* cases created inside utils.timing.describe(). See documentation for exports.timing.
*/
function timingTest(expectedMs, desc, testFunc) {
var test = it(desc + " (exp ~" + expectedMs + "ms)", testFunc);
test.slow(expectedMs * 1.5);
test.timeout(expectedMs * 5 + 2000);
test.expectedDuration = expectedMs;
}
/**
* To write timing tests, the following pattern is recommended:
*
* (1) Use utils.timing.describe() in place of describe().
* (2) Use utils.timing.it() in place of it(). It takes an extra first parameter with the number
* of expected milliseconds. The test will fail if it takes more than 1.5x longer.
* (3) Place only the code to be timed in utils.timing.it(), and do all setup in before() and all
* non-trivial post-test assertions in after().
*
* These tests only run when ENABLE_TIMING_TESTS environment variable is non-empty. It enables
* timing tests both under Node and running in the browser under Selenium. To enable timing tests
* in the browser when running /test.html manually, go to /test.html?timing=1.
*/
exports.timing = {
describe: timingDescribe,
it: timingTest
};
// Dummy object used for tests
function TestPerson(last, first, age, year, month, day) {
this.last = last;
this.first = first;
this.age = age;
this.year = year;
this.month = month;
this.day = day;
}
/**
* Returns a list of randomly generated TestPersons.
* @param {integer} num - length of people list to return
*/
function genPeople(num, seed) {
if (typeof seed === 'undefined') seed = 0;
var ageOpts = {min: 0, max: 90};
var monthOpts = {min:1, max:12};
var dayOpts = {min:1, max:30};
var people = [];
var chance = new Chance(seed);
for (var i = 0; i < num; i++) {
people.push(new TestPerson(chance.last(),
chance.first(),
chance.integer(ageOpts),
parseInt(chance.year()),
chance.integer(monthOpts),
chance.integer(dayOpts)
));
}
return people;
}
exports.genPeople = genPeople;
/**
* Generates a list of items denoted by the given chanceFunc string.
* Ex : genItems('integers', 10, {min:0, max:20}) generates a list of 10 integers between 0 and 20
* : genItems('string', 10, {length: 6}) generates a list of 10 strings of length 6
* @param {string} chanceFunc - string name of a chance.js function
* @param {integer} num - length of item list to return
* @param {object} options - object denoting options for the given chance.js function
*/
function genItems(chanceFunc, num, options, seed) {
if (typeof seed === 'undefined') seed = 0;
console.assert(typeof new Chance()[chanceFunc] === 'function');
var chance = new Chance(seed);
var items = [];
for (var i = 0; i < num; i++) {
items.push(chance[chanceFunc](options));
}
return items;
}
exports.genItems = genItems;