diff --git a/index.js b/index.js index bcc8ee72a..1bf726602 100644 --- a/index.js +++ b/index.js @@ -70,11 +70,4 @@ setImmediate(function () { runner.run().then(exit); }); -module.exports = runner.addTest.bind(runner); -module.exports.serial = runner.addSerialTest.bind(runner); -module.exports.before = runner.addBeforeHook.bind(runner); -module.exports.after = runner.addAfterHook.bind(runner); -module.exports.beforeEach = runner.addBeforeEachHook.bind(runner); -module.exports.afterEach = runner.addAfterEachHook.bind(runner); -module.exports.skip = runner.addSkippedTest.bind(runner); -module.exports.only = runner.addOnlyTest.bind(runner); +module.exports = runner.test; diff --git a/lib/hook.js b/lib/hook.js new file mode 100644 index 000000000..ed22a4705 --- /dev/null +++ b/lib/hook.js @@ -0,0 +1,24 @@ +var Test = require('./test'); + +module.exports = Hook; + +function Hook(title, fn) { + if (!(this instanceof Hook)) { + return new Hook(title, fn); + } + + if (typeof title === 'function') { + fn = title; + title = null; + } + + this.title = title; + this.fn = fn; +} + +Hook.prototype.test = function (testTitle) { + var title = this.title || (this.metadata.type + ' for "' + testTitle + '"'); + var test = new Test(title, this.fn); + test.metadata = this.metadata; + return test; +}; diff --git a/lib/runner.js b/lib/runner.js index 15b017e43..9273ea178 100644 --- a/lib/runner.js +++ b/lib/runner.js @@ -4,7 +4,9 @@ var util = require('util'); var Promise = require('bluebird'); var hasFlag = require('has-flag'); var Test = require('./test'); +var Hook = require('./hook'); var send = require('./send'); +var objectAssign = require('object-assign'); function noop() {} @@ -13,7 +15,7 @@ function each(items, fn, context) { } function eachSeries(items, fn, context) { - return Promise.resolve(items).each(fn.bind(context)); + return Promise.each(items, fn.bind(context)); } function Runner(opts) { @@ -25,84 +27,63 @@ function Runner(opts) { this.results = []; - this.stats = { - failCount: 0, - passCount: 0, - testCount: 0 - }; + this.tests = []; - this.tests = { - concurrent: [], - serial: [], - only: [], - before: [], - after: [], - beforeEach: [], - afterEach: [] - }; + this.test = makeChain({ + type: 'test', + serial: false, + exclusive: false, + skipped: false + }, this._addFn.bind(this)); } util.inherits(Runner, EventEmitter); module.exports = Runner; -Runner.prototype.addTest = function (title, cb) { - this.stats.testCount++; - this.tests.concurrent.push(new Test(title, cb)); -}; - -Runner.prototype.addSerialTest = function (title, cb) { - this.stats.testCount++; - this.tests.serial.push(new Test(title, cb)); -}; - -Runner.prototype.addBeforeHook = function (title, cb) { - var test = new Test(title, cb); - test.type = 'hook'; - - this.tests.before.push(test); -}; - -Runner.prototype.addAfterHook = function (title, cb) { - var test = new Test(title, cb); - test.type = 'hook'; - - this.tests.after.push(test); +var chainableFunctions = { + serial: {serial: true}, + before: {type: 'before'}, + after: {type: 'after'}, + skip: {skipped: true}, + only: {exclusive: true}, + beforeEach: {type: 'beforeEach'}, + afterEach: {type: 'afterEach'} }; -Runner.prototype.addBeforeEachHook = function (title, cb) { - if (!cb) { - cb = title; - title = undefined; +function makeChain(defaults, parentAdd) { + function fn(title, fn) { + parentAdd(defaults, title, fn); } - this.tests.beforeEach.push({ - title: title, - fn: cb - }); -}; - -Runner.prototype.addAfterEachHook = function (title, cb) { - if (!cb) { - cb = title; - title = undefined; + function add(opts, title, fn) { + opts = objectAssign({}, defaults, opts); + parentAdd(objectAssign({}, defaults, opts), title, fn); } - this.tests.afterEach.push({ - title: title, - fn: cb + Object.keys(chainableFunctions).forEach(function (key) { + Object.defineProperty(fn, key, { + get: function () { + return makeChain(objectAssign({}, defaults, chainableFunctions[key]), add); + } + }); }); -}; -Runner.prototype.addSkippedTest = function (title, cb) { - var test = new Test(title, cb); - test.skip = true; + return fn; +} - this.tests.concurrent.push(test); -}; +Object.keys(chainableFunctions).forEach(function (key) { + Object.defineProperty(Runner.prototype, key, { + get: function () { + return this.test[key]; + } + }); +}); -Runner.prototype.addOnlyTest = function (title, cb) { - this.stats.testCount++; - this.tests.only.push(new Test(title, cb)); +Runner.prototype._addFn = function (opts, title, fn) { + var Constructor = (opts && /Each/.test(opts.type)) ? Hook : Test; + var test = new Constructor(title, fn); + test.metadata = objectAssign({}, opts); + this.tests.push(test); }; Runner.prototype._runTestWithHooks = function (test) { @@ -110,27 +91,13 @@ Runner.prototype._runTestWithHooks = function (test) { return this._addTestResult(test); } - var beforeHooks = this.tests.beforeEach.map(function (hook) { - var title = hook.title || 'beforeEach for "' + test.title + '"'; - hook = new Test(title, hook.fn); - hook.type = 'eachHook'; - - return hook; - }); - - var afterHooks = this.tests.afterEach.map(function (hook) { - var title = hook.title || 'afterEach for "' + test.title + '"'; - hook = new Test(title, hook.fn); - hook.type = 'eachHook'; - - return hook; - }); - - var tests = []; + function hookToTest(hook) { + return hook.test(test.title); + } - tests.push.apply(tests, beforeHooks); + var tests = this.select({type: 'beforeEach', skipped: false}).map(hookToTest); tests.push(test); - tests.push.apply(tests, afterHooks); + tests.push.apply(tests, this.select({type: 'afterEach', skipped: false}).map(hookToTest)); var context = {}; @@ -158,15 +125,15 @@ Runner.prototype._runTest = function (test) { }); }; -Runner.prototype.concurrent = function (tests) { +Runner.prototype._runConcurrent = function (tests) { if (hasFlag('serial')) { - return this.serial(tests); + return this._runSerial(tests); } return each(tests, this._runTestWithHooks, this); }; -Runner.prototype.serial = function (tests) { +Runner.prototype._runSerial = function (tests) { return eachSeries(tests, this._runTestWithHooks, this); }; @@ -179,7 +146,7 @@ Runner.prototype._addTestResult = function (test) { duration: test.duration, title: test.title, error: test.assertError, - type: test.type, + type: test.metadata.type, skip: test.skip }; @@ -188,18 +155,23 @@ Runner.prototype._addTestResult = function (test) { }; Runner.prototype.run = function () { - var tests = this.tests; - var stats = this.stats; var self = this; + var hasExclusive = Boolean(this.select({exclusive: true, skipped: false, type: 'test'}).length); + var serial = this.select({exclusive: hasExclusive, skipped: false, serial: true, type: 'test'}); + var concurrent = this.select({exclusive: hasExclusive, skipped: false, serial: false, type: 'test'}); - var hasOnlyTests = tests.only.length > 0; + var stats = this.stats = { + failCount: 0, + passCount: 0, + testCount: serial.length + concurrent.length + }; // Runner is executed directly in tests, in that case process.send() == undefined if (process.send) { send('stats', stats); } - return eachSeries(tests.before, this._runTest, this) + return eachSeries(this.select({type: 'before', skipped: false}), this._runTest, this) .catch(noop) .then(function () { if (stats.failCount > 0) { @@ -207,24 +179,24 @@ Runner.prototype.run = function () { } }) .then(function () { - return self.concurrent(tests.only); - }) - .then(function () { - if (!hasOnlyTests) { - return self.serial(tests.serial); - } + return self._runSerial(serial); }) .then(function () { - if (!hasOnlyTests) { - return self.concurrent(tests.concurrent); - } + return self._runConcurrent(concurrent); }) .then(function () { - return eachSeries(tests.after, self._runTest, self); + return eachSeries(self.select({type: 'after', skipped: false}), self._runTest, self); }) .catch(noop) .then(function () { - stats.testCount = tests.only.length || stats.testCount; stats.passCount = stats.testCount - stats.failCount; }); }; + +Runner.prototype.select = function (filter) { + return this.tests.filter(function (test) { + return Object.keys(filter).every(function (key) { + return filter[key] === test.metadata[key]; + }); + }); +}; diff --git a/lib/test.js b/lib/test.js index 05161a6cf..222f4d23a 100644 --- a/lib/test.js +++ b/lib/test.js @@ -28,9 +28,6 @@ function Test(title, fn) { this.duration = null; this.assertError = undefined; - // test type, can be: test, hook, eachHook - this.type = 'test'; - // store the time point before test execution // to calculate the total time spent in test this._timeStart = null; diff --git a/package.json b/package.json index e58ba7602..c33099c91 100644 --- a/package.json +++ b/package.json @@ -93,6 +93,7 @@ "loud-rejection": "^1.2.0", "max-timeout": "^1.0.0", "meow": "^3.6.0", + "object-assign": "^4.0.1", "observable-to-promise": "^0.1.0", "plur": "^2.0.0", "power-assert-formatter": "^1.3.0", diff --git a/readme.md b/readme.md index a658f5307..0dd12eae3 100644 --- a/readme.md +++ b/readme.md @@ -274,6 +274,20 @@ test(t => { }); ``` +### Chaining test modifiers + +You can chain test modifiers together in the following ways: + +```js +test.before.skip([title], testFn); +test.skip.after(....); +test.serial.only(...); +test.only.serial(...); +``` + +This is especially helpful temporarily using `skip` or `only` on a test, without losing the information + and behavior the other modifiers provide. + ### Custom assertion module You can use any assertion module instead or in addition to the one that comes with AVA, but you won't be able to use the `.plan()` method, [yet](https://github.com/sindresorhus/ava/issues/25). diff --git a/test/hooks.js b/test/hooks.js index ddfc16096..5dee6a107 100644 --- a/test/hooks.js +++ b/test/hooks.js @@ -10,12 +10,12 @@ test('before', function (t) { var runner = new Runner(); var arr = []; - runner.addBeforeHook(function (a) { + runner.before(function (a) { arr.push('a'); a.end(); }); - runner.addTest(function (a) { + runner.test(function (a) { arr.push('b'); a.end(); }); @@ -31,12 +31,12 @@ test('after', function (t) { var runner = new Runner(); var arr = []; - runner.addAfterHook(function (a) { + runner.after(function (a) { arr.push('b'); a.end(); }); - runner.addTest(function (a) { + runner.test(function (a) { arr.push('a'); a.end(); }); @@ -53,16 +53,16 @@ test('stop if before hooks failed', function (t) { var runner = new Runner(); var arr = []; - runner.addBeforeHook(function (a) { + runner.before(function (a) { arr.push('a'); a.end(); }); - runner.addBeforeHook(function () { + runner.before(function () { throw new Error('something went wrong'); }); - runner.addTest(function (a) { + runner.test(function (a) { arr.push('b'); a.end(); }); @@ -81,22 +81,22 @@ test('before each with concurrent tests', function (t) { var i = 0; var k = 0; - runner.addBeforeEachHook(function (a) { + runner.beforeEach(function (a) { arr[i++].push('a'); a.end(); }); - runner.addBeforeEachHook(function (a) { + runner.beforeEach(function (a) { arr[k++].push('b'); a.end(); }); - runner.addTest(function (a) { + runner.test(function (a) { arr[0].push('c'); a.end(); }); - runner.addTest(function (a) { + runner.test(function (a) { arr[1].push('d'); a.end(); }); @@ -113,22 +113,22 @@ test('before each with serial tests', function (t) { var runner = new Runner(); var arr = []; - runner.addBeforeEachHook(function (a) { + runner.beforeEach(function (a) { arr.push('a'); a.end(); }); - runner.addBeforeEachHook(function (a) { + runner.beforeEach(function (a) { arr.push('b'); a.end(); }); - runner.addSerialTest(function (a) { + runner.serial(function (a) { arr.push('c'); a.end(); }); - runner.addSerialTest(function (a) { + runner.serial(function (a) { arr.push('d'); a.end(); }); @@ -145,13 +145,13 @@ test('fail if beforeEach hook fails', function (t) { var runner = new Runner(); var arr = []; - runner.addBeforeEachHook(function (a) { + runner.beforeEach(function (a) { arr.push('a'); a.fail(); a.end(); }); - runner.addTest(function (a) { + runner.test(function (a) { arr.push('b'); a.pass(); a.end(); @@ -172,22 +172,22 @@ test('after each with concurrent tests', function (t) { var i = 0; var k = 0; - runner.addAfterEachHook(function (a) { + runner.afterEach(function (a) { arr[i++].push('a'); a.end(); }); - runner.addAfterEachHook(function (a) { + runner.afterEach(function (a) { arr[k++].push('b'); a.end(); }); - runner.addTest(function (a) { + runner.test(function (a) { arr[0].push('c'); a.end(); }); - runner.addTest(function (a) { + runner.test(function (a) { arr[1].push('d'); a.end(); }); @@ -204,22 +204,22 @@ test('after each with serial tests', function (t) { var runner = new Runner(); var arr = []; - runner.addAfterEachHook(function (a) { + runner.afterEach(function (a) { arr.push('a'); a.end(); }); - runner.addAfterEachHook(function (a) { + runner.afterEach(function (a) { arr.push('b'); a.end(); }); - runner.addSerialTest(function (a) { + runner.serial(function (a) { arr.push('c'); a.end(); }); - runner.addSerialTest(function (a) { + runner.serial(function (a) { arr.push('d'); a.end(); }); @@ -236,27 +236,27 @@ test('ensure hooks run only around tests', function (t) { var runner = new Runner(); var arr = []; - runner.addBeforeEachHook(function (a) { + runner.beforeEach(function (a) { arr.push('beforeEach'); a.end(); }); - runner.addBeforeHook(function (a) { + runner.before(function (a) { arr.push('before'); a.end(); }); - runner.addAfterEachHook(function (a) { + runner.afterEach(function (a) { arr.push('afterEach'); a.end(); }); - runner.addAfterHook(function (a) { + runner.after(function (a) { arr.push('after'); a.end(); }); - runner.addTest(function (a) { + runner.test(function (a) { arr.push('test'); a.end(); }); @@ -272,29 +272,29 @@ test('shared context', function (t) { var runner = new Runner(); - runner.addBeforeHook(function (a) { + runner.before(function (a) { a.is(a.context, undefined); a.context = {arr: []}; a.end(); }); - runner.addAfterHook(function (a) { + runner.after(function (a) { a.is(a.context, undefined); a.end(); }); - runner.addBeforeEachHook(function (a) { + runner.beforeEach(function (a) { a.context.arr = ['a']; a.end(); }); - runner.addTest(function (a) { + runner.test(function (a) { a.context.arr.push('b'); a.same(a.context.arr, ['a', 'b']); a.end(); }); - runner.addAfterEachHook(function (a) { + runner.afterEach(function (a) { a.context.arr.push('c'); a.same(a.context.arr, ['a', 'b', 'c']); a.end(); @@ -311,12 +311,12 @@ test('shared context of any type', function (t) { var runner = new Runner(); - runner.addBeforeEachHook(function (a) { + runner.beforeEach(function (a) { a.context = 'foo'; a.end(); }); - runner.addTest(function (a) { + runner.test(function (a) { a.is(a.context, 'foo'); a.end(); }); diff --git a/test/runner.js b/test/runner.js index 56afdb8fe..e912211e8 100644 --- a/test/runner.js +++ b/test/runner.js @@ -11,73 +11,131 @@ test('returns new instance of runner without "new"', function (t) { t.end(); }); -test('runner.addTest adds a new test', function (t) { +test('runner.test adds a new test', function (t) { var runner = new Runner(); - runner.addTest(mockTitle, noop); - t.is(runner.stats.testCount, 1); - t.is(runner.tests.concurrent.length, 1); - t.true(runner.tests.concurrent[0] instanceof Test); + runner.test(mockTitle, noop); + t.is(runner.tests.length, 1); + t.true(runner.tests[0] instanceof Test); + t.false(runner.tests[0].metadata.serial); t.end(); }); -test('runner.addSerialTest adds a new serial test', function (t) { +test('runner.serial adds a new serial test', function (t) { var runner = new Runner(); - runner.addSerialTest(mockTitle, noop); - t.is(runner.stats.testCount, 1); - t.is(runner.tests.serial.length, 1); - t.true(runner.tests.serial[0] instanceof Test); + runner.serial(mockTitle, noop); + t.is(runner.tests.length, 1); + t.true(runner.tests[0] instanceof Test); + t.true(runner.tests[0].metadata.serial); t.end(); }); -test('runner.addBeforeHook adds a new before hook', function (t) { +test('runner.before adds a new before hook', function (t) { var runner = new Runner(); - runner.addBeforeHook(mockTitle, noop); - t.is(runner.tests.before.length, 1); - t.true(runner.tests.before[0] instanceof Test); - t.is(runner.tests.before[0].type, 'hook'); + runner.before(mockTitle, noop); + t.is(runner.tests.length, 1); + t.true(runner.tests[0] instanceof Test); + t.is(runner.tests[0].metadata.type, 'before'); t.end(); }); -test('runner.addAfterHook adds a new after hook', function (t) { +test('runner.after adds a new after hook', function (t) { var runner = new Runner(); - runner.addAfterHook(mockTitle, noop); - t.is(runner.tests.after.length, 1); - t.true(runner.tests.after[0] instanceof Test); - t.is(runner.tests.after[0].type, 'hook'); + runner.after(mockTitle, noop); + t.is(runner.tests.length, 1); + t.true(runner.tests[0] instanceof Test); + t.is(runner.tests[0].metadata.type, 'after'); t.end(); }); -test('runner.addBeforeEachHook adds a new before hook', function (t) { +test('runner.beforeEach adds a new beforeEach hook', function (t) { var runner = new Runner(); - runner.addBeforeEachHook(mockTitle, noop); - t.is(runner.tests.beforeEach.length, 1); - t.is(runner.tests.beforeEach[0].title, mockTitle); - t.is(runner.tests.beforeEach[0].fn, noop); + runner.beforeEach(mockTitle, noop); + t.is(runner.tests.length, 1); + t.is(runner.tests[0].title, mockTitle); + t.is(runner.tests[0].fn, noop); + t.is(runner.tests[0].metadata.type, 'beforeEach'); t.end(); }); -test('runner.addAfterEachHook adds a new after hook', function (t) { +test('runner.beforeEach title is optional', function (t) { + function doThisFirst() {} var runner = new Runner(); - runner.addAfterEachHook(mockTitle, noop); - t.is(runner.tests.afterEach.length, 1); - t.is(runner.tests.afterEach[0].title, mockTitle); - t.is(runner.tests.afterEach[0].fn, noop); + runner.beforeEach(doThisFirst); + t.is(runner.tests.length, 1); + // TODO(jamestalmage): Make `title` logic common between Hook and Test + t.is(runner.tests[0].title, null); + t.is(runner.tests[0].fn, doThisFirst); + t.is(runner.tests[0].metadata.type, 'beforeEach'); t.end(); }); -test('runner.addSkippedTest adds a new skipped test', function (t) { +test('runner.afterEach adds a new afterEach hook', function (t) { var runner = new Runner(); - runner.addSkippedTest(mockTitle, noop); - t.is(runner.tests.concurrent.length, 1); - t.true(runner.tests.concurrent[0] instanceof Test); - t.is(runner.tests.concurrent[0].skip, true); + runner.afterEach(mockTitle, noop); + t.is(runner.tests.length, 1); + t.is(runner.tests[0].title, mockTitle); + t.is(runner.tests[0].fn, noop); + t.is(runner.tests[0].metadata.type, 'afterEach'); t.end(); }); -test('runner have test event', function (t) { +test('runner.skip adds a new skipped test', function (t) { var runner = new Runner(); + runner.skip(mockTitle, noop); + t.is(runner.tests.length, 1); + t.true(runner.tests[0] instanceof Test); + t.is(runner.tests[0].title, mockTitle); + t.is(runner.tests[0].metadata.skipped, true); + t.end(); +}); + +test('runner.skip - title is optional', function (t) { + var runner = new Runner(); + runner.skip(noop); + t.is(runner.tests.length, 1); + t.true(runner.tests[0] instanceof Test); + t.is(runner.tests[0].title, '[anonymous]'); + t.is(runner.tests[0].metadata.skipped, true); + t.end(); +}); + +test('methods are chainable: serial.skip', function (t) { + var runner = new Runner(); + runner.serial.skip(noop); + t.is(runner.tests.length, 1); + t.is(runner.tests[0].metadata.type, 'test'); + t.true(runner.tests[0].metadata.serial); + t.false(runner.tests[0].metadata.exclusive); + t.true(runner.tests[0].metadata.skipped); + t.end(); +}); + +test('methods are chainable: beforeEach.skip', function (t) { + var runner = new Runner(); + runner.beforeEach.skip(noop); + t.is(runner.tests.length, 1); + t.is(runner.tests[0].metadata.type, 'beforeEach'); + t.false(runner.tests[0].metadata.serial); + t.false(runner.tests[0].metadata.exclusive); + t.true(runner.tests[0].metadata.skipped); + t.end(); +}); + +test('methods are chainable: serial.only', function (t) { + var runner = new Runner(); + runner.serial.only(noop); + t.is(runner.tests.length, 1); + t.is(runner.tests[0].metadata.type, 'test'); + t.true(runner.tests[0].metadata.serial); + t.true(runner.tests[0].metadata.exclusive); + t.false(runner.tests[0].metadata.skipped); + t.end(); +}); - runner.addTest(function foo(a) { +test('runner emits a "test" event', function (t) { + var runner = new Runner(); + + runner.test(function foo(a) { a.end(); }); @@ -95,17 +153,17 @@ test('run serial tests before concurrent ones', function (t) { var runner = new Runner(); var arr = []; - runner.addTest(function (a) { + runner.test(function (a) { arr.push('c'); a.end(); }); - runner.addSerialTest(function (a) { + runner.serial(function (a) { arr.push('a'); a.end(); }); - runner.addSerialTest(function (a) { + runner.serial(function (a) { arr.push('b'); a.end(); }); @@ -116,26 +174,71 @@ test('run serial tests before concurrent ones', function (t) { }); }); +test('anything can be skipped', function (t) { + var runner = new Runner(); + var arr = []; + + function pusher(title) { + return function (a) { + arr.push(title); + a.end(); + }; + } + + runner.after(pusher('after')); + runner.after.skip(pusher('after.skip')); + + runner.afterEach(pusher('afterEach')); + runner.afterEach.skip(pusher('afterEach.skip')); + + runner.before(pusher('before')); + runner.before.skip(pusher('before.skip')); + + runner.beforeEach(pusher('beforeEach')); + runner.beforeEach.skip(pusher('beforeEach.skip')); + + runner.test(pusher('concurrent')); + runner.test.skip(pusher('concurrent.skip')); + + runner.serial(pusher('serial')); + runner.serial.skip(pusher('serial.skip')); + + runner.run().then(function () { + // Note that afterEach and beforeEach run twice because there are two actual tests - "serial" and "concurrent" + t.same(arr, [ + 'before', + 'beforeEach', + 'serial', + 'afterEach', + 'beforeEach', + 'concurrent', + 'afterEach', + 'after' + ]); + t.end(); + }); +}); + test('test types and titles', function (t) { t.plan(10); var runner = new Runner(); - runner.addBeforeHook(pass); - runner.addBeforeEachHook(pass); - runner.addAfterHook(pass); - runner.addAfterEachHook(pass); - runner.addTest('test', pass); + runner.before(pass); + runner.beforeEach(pass); + runner.after(pass); + runner.afterEach(pass); + runner.test('test', pass); function pass(a) { a.end(); } var tests = [ - {type: 'hook', title: 'pass'}, - {type: 'eachHook', title: 'beforeEach for "test"'}, + {type: 'before', title: 'pass'}, + {type: 'beforeEach', title: 'beforeEach for "test"'}, {type: 'test', title: 'test'}, - {type: 'eachHook', title: 'afterEach for "test"'}, - {type: 'hook', title: 'pass'} + {type: 'afterEach', title: 'afterEach for "test"'}, + {type: 'after', title: 'pass'} ]; runner.on('test', function (props) { @@ -154,12 +257,12 @@ test('skip test', function (t) { var runner = new Runner(); var arr = []; - runner.addTest(function (a) { + runner.test(function (a) { arr.push('a'); a.end(); }); - runner.addSkippedTest(function (a) { + runner.skip(function (a) { arr.push('b'); a.end(); }); @@ -178,12 +281,12 @@ test('only test', function (t) { var runner = new Runner(); var arr = []; - runner.addTest(function (a) { + runner.test(function (a) { arr.push('a'); a.end(); }); - runner.addOnlyTest(function (a) { + runner.only(function (a) { arr.push('b'); a.end(); });