Skip to content

Commit

Permalink
Refactor Runner
Browse files Browse the repository at this point in the history
* Don't mix option chain with the prototype, simply expose it on the
instance. Actual code only exposed the `test` method to the user, and
that happens to be the default starting point anyway

* `run()` doesn't need to return stats

* Ensure `run()` always returns a promise, and does not throw

* Simplify `buildStats()`

* Derive chainable methods from the actual chain when generating the
TypeScript definition

* Don't bother prefixing methods / properties with underscores
  • Loading branch information
novemberborn committed Apr 2, 2017
1 parent f23c17d commit 48b892a
Show file tree
Hide file tree
Showing 5 changed files with 273 additions and 260 deletions.
6 changes: 3 additions & 3 deletions lib/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ function exit() {
// Reference the IPC channel now that tests have finished running.
adapter.ipcChannel.ref();

const stats = runner._buildStats();
const stats = runner.buildStats();
adapter.send('results', {stats});
}

Expand Down Expand Up @@ -89,9 +89,9 @@ globals.setImmediate(() => {
});
});

module.exports = runner.test;
module.exports = runner.chain;

// TypeScript imports the `default` property for
// an ES2015 default import (`import test from 'ava'`)
// See: https://github.com/Microsoft/TypeScript/issues/2242#issuecomment-83694181
module.exports.default = runner.test;
module.exports.default = runner.chain;
167 changes: 81 additions & 86 deletions lib/runner.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';
const EventEmitter = require('events');
const Promise = require('bluebird');
const Bluebird = require('bluebird');
const optionChain = require('option-chain');
const matcher = require('matcher');
const TestCollection = require('./test-collection');
Expand Down Expand Up @@ -45,18 +45,57 @@ class Runner extends EventEmitter {

options = options || {};

this.match = options.match || [];
this.serial = options.serial;

this.hasStarted = false;
this.results = [];
this.tests = new TestCollection({
bail: options.bail,
failWithoutAssertions: options.failWithoutAssertions
});
this.hasStarted = false;
this._serial = options.serial;
this._match = options.match || [];
this._addTestResult = this._addTestResult.bind(this);
this._buildStats = this._buildStats.bind(this);

this.chain = optionChain(chainableMethods, (opts, args) => {
let title;
let fn;
let macroArgIndex;

if (this.hasStarted) {
throw new Error('All tests and hooks must be declared synchronously in your ' +
'test file, and cannot be nested within other tests or hooks.');
}

if (typeof args[0] === 'string') {
title = args[0];
fn = args[1];
macroArgIndex = 2;
} else {
fn = args[0];
title = null;
macroArgIndex = 1;
}

if (this.serial) {
opts.serial = true;
}

if (args.length > macroArgIndex) {
args = args.slice(macroArgIndex);
} else {
args = null;
}

if (Array.isArray(fn)) {
fn.forEach(fn => {
this.addTest(title, opts, fn, args);
});
} else {
this.addTest(title, opts, fn, args);
}
});
}
_addTest(title, opts, fn, args) {

addTest(title, metadata, fn, args) {
if (args) {
if (fn.title) {
title = fn.title.apply(fn, [title || ''].concat(args));
Expand All @@ -65,22 +104,23 @@ class Runner extends EventEmitter {
fn = wrapFunction(fn, args);
}

if (opts.type === 'test' && this._match.length > 0) {
opts.exclusive = title !== null && matcher([title], this._match).length === 1;
if (metadata.type === 'test' && this.match.length > 0) {
metadata.exclusive = title !== null && matcher([title], this.match).length === 1;
}

const validationError = validateTest(title, fn, opts);
const validationError = validateTest(title, fn, metadata);
if (validationError !== null) {
throw new TypeError(validationError);
}

this.tests.add({
metadata: opts,
metadata,
fn,
title
});
}
_addTestResult(result) {

addTestResult(result) {
const test = result.result;
const props = {
duration: test.duration,
Expand All @@ -95,103 +135,58 @@ class Runner extends EventEmitter {
this.results.push(result);
this.emit('test', props);
}
_buildStats() {

buildStats() {
const stats = {
testCount: 0,
failCount: 0,
knownFailureCount: 0,
passCount: 0,
skipCount: 0,
testCount: 0,
todoCount: 0
};

this.results
.map(result => {
return result.result;
})
.filter(test => {
return test.metadata.type === 'test';
})
.forEach(test => {
for (const result of this.results) {
if (!result.passed) {
// Includes hooks
stats.failCount++;
}

const metadata = result.result.metadata;
if (metadata.type === 'test') {
stats.testCount++;

if (test.metadata.skipped) {
if (metadata.skipped) {
stats.skipCount++;
}

if (test.metadata.todo) {
} else if (metadata.todo) {
stats.todoCount++;
} else if (result.passed) {
if (metadata.failing) {
stats.knownFailureCount++;
} else {
stats.passCount++;
}
}
});

stats.failCount = this.results
.filter(result => {
return result.passed === false;
})
.length;

stats.knownFailureCount = this.results
.filter(result => {
return result.passed === true && result.result.metadata.failing;
})
.length;

stats.passCount = stats.testCount - stats.failCount - stats.skipCount - stats.todoCount;
}
}

return stats;
}

run(options) {
if (options.runOnlyExclusive && !this.tests.hasExclusive) {
return Promise.resolve(null);
}

this.tests.on('test', this._addTestResult);

this.hasStarted = true;

return Promise.resolve(this.tests.build().run()).then(this._buildStats);
this.tests.on('test', result => {
this.addTestResult(result);
});
return Bluebird.try(() => this.tests.build().run());
}
attributeLeakedError(err) {
return this.tests.attributeLeakedError(err);
}
}

optionChain(chainableMethods, function (opts, args) {
let title;
let fn;
let macroArgIndex;

if (this.hasStarted) {
throw new Error('All tests and hooks must be declared synchronously in your ' +
'test file, and cannot be nested within other tests or hooks.');
}

if (typeof args[0] === 'string') {
title = args[0];
fn = args[1];
macroArgIndex = 2;
} else {
fn = args[0];
title = null;
macroArgIndex = 1;
}

if (this._serial) {
opts.serial = true;
}

if (args.length > macroArgIndex) {
args = args.slice(macroArgIndex);
} else {
args = null;
}

if (Array.isArray(fn)) {
fn.forEach(function (fn) {
this._addTest(title, opts, fn, args);
}, this);
} else {
this._addTest(title, opts, fn, args);
}
}, Runner.prototype);

Runner._chainableMethods = chainableMethods.chainableMethods;

module.exports = Runner;
Loading

0 comments on commit 48b892a

Please sign in to comment.