[Pkg-javascript-commits] [node-tap] 03/19: New upstream version 7.1.2

Jérémy Lal kapouer at moszumanska.debian.org
Sat Nov 12 01:03:55 UTC 2016


This is an automated email from the git hooks/post-receive script.

kapouer pushed a commit to branch master
in repository node-tap.

commit b0b244cc411138c1773e97cc61a64feb01546ab2
Author: Jérémy Lal <kapouer at melix.org>
Date:   Fri Oct 21 16:25:50 2016 +0200

    New upstream version 7.1.2
---
 .travis.yml                                        |   3 +-
 AUTHORS                                            |  12 -
 CHANGELOG.md                                       | 100 +--
 CONTRIBUTING.md                                    |  13 +-
 README.md                                          | 783 +--------------------
 TODO.md                                            |   8 -
 appveyor.yml                                       |  19 +
 bin/is-exe.js                                      |  23 -
 bin/run.js                                         | 374 +++++++---
 bin/usage.txt                                      | 104 ++-
 example/mocha-example.js                           |  88 +++
 lib/assert.js                                      |  17 +-
 lib/mocha.js                                       |   4 +-
 lib/root.js                                        | 104 ++-
 lib/stack.js                                       | 262 +------
 lib/synonyms.js                                    |   8 +-
 lib/test.js                                        | 723 +++++++++++++------
 package.json                                       |  38 +-
 scripts/generate-test-test.js                      |  13 +-
 test/asserts.js                                    |   3 +
 test/coverage-checks.js                            |  79 +++
 test/coverage-export.js                            |  25 -
 test/coverage-html-no-browser.js                   |  49 ++
 test/debug-test.js                                 |   9 +-
 test/executable-scripts.js                         |  61 ++
 test/executed.sh                                   |   4 -
 test/expose-gc-test.js                             |  34 +-
 test/fixtures/dump-args.js                         |   8 +
 test/fixtures/invalid-rc-file.yml                  |   1 +
 test/fixtures/valid-rc-file.yml                    |   5 +
 test/independent-timeouts.js                       |  29 +-
 test/not-executed.sh                               |   5 -
 test/only-non-tap-output.js                        |   4 +-
 test/rcfiles.js                                    |  84 +++
 test/require-hooks.js                              |   2 +-
 test/root-no-tests.js                              |  90 +++
 test/runner-bailout-args.js                        |  61 ++
 test/runner-colors.js                              |  63 ++
 test/runner-dashdash.js                            |  25 +
 test/runner-epipe.js                               |  68 ++
 test/runner-no-cov-args.js                         |  31 +
 test/runner-non-zero-exit.js                       |  32 +
 test/runner-nyc-args.js                            |  42 ++
 test/runner-path-globbing.js                       |  29 +
 test/runner-read-stdin.js                          | 141 ++++
 test/runner-save-file.js                           | 106 +++
 test/runner-test-args.js                           |  36 +
 test/runner-timeout.js                             |  52 ++
 test/runner-unknown-arg.js                         |  44 ++
 test/runner-usage.js                               |  55 ++
 test/runner-version.js                             |  37 +
 test/runner-warn-covering-stdin.js                 |  34 +
 test/runner.js                                     | 510 --------------
 test/segv.c                                        |   4 +
 test/segv.js                                       |   6 +-
 test/simple-harness-test-with-plan.js              |  16 -
 test/simple-harness-test.js                        |  13 -
 test/spawn-failures.js                             |  62 ++
 test/test-args.js                                  |  30 +
 test/test-output-am.js                             |   2 +
 test/test-output-nz.js                             |   2 +
 test/test-test.js                                  | 168 +++++
 test/test.js                                       | 162 +++--
 test/test/assert-at-bail.tap                       |   9 +
 test/test/assert-at.js                             |  33 +
 test/test/assert-at.tap                            |  29 +
 test/test/assert-todo-skip-bail.tap                |   2 +-
 test/test/assert-todo-skip.tap                     |   2 +-
 test/test/async-bail.tap                           |   4 +-
 test/test/async.tap                                |   4 +-
 test/test/bail-child-bail.tap                      |   4 +-
 test/test/bail-child.tap                           |   4 +-
 test/test/bail-fail-spawn-bail.tap                 |  10 +-
 test/test/bail-fail-spawn.tap                      |  10 +-
 test/test/bail-teardown-async-bail.tap             |  12 +
 test/test/bail-teardown-async.js                   |  20 +
 test/test/bail-teardown-async.tap                  |  12 +
 test/test/bail-teardown-bail.tap                   |   9 +
 test/test/bail-teardown.js                         |  10 +
 test/test/bail-teardown.tap                        |   9 +
 test/test/bailout-bail.tap                         |   7 +-
 test/test/bailout.tap                              |   9 +-
 test/test/before-after-each-async-bail.tap         |  28 +
 test/test/before-after-each-async.js               |  34 +
 test/test/before-after-each-async.tap              |  28 +
 test/test/before-after-each-bail.tap               |  28 +
 test/test/before-after-each-plan-bail.tap          |  27 +
 test/test/before-after-each-plan.js                |  35 +
 test/test/before-after-each-plan.tap               |  27 +
 test/test/before-after-each-promise-bail.tap       |  19 +
 test/test/before-after-each-promise.js             |  44 ++
 test/test/before-after-each-promise.tap            |  44 ++
 test/test/before-after-each-raise-bail.tap         |  18 +
 test/test/before-after-each-raise.js               |  37 +
 test/test/before-after-each-raise.tap              |  44 ++
 test/test/before-after-each-throw-bail.tap         |  18 +
 test/test/before-after-each-throw.js               |  39 +
 test/test/before-after-each-throw.tap              |  44 ++
 test/test/before-after-each.js                     |  34 +
 test/test/before-after-each.tap                    |  28 +
 test/test/catch-tap-throws-bail.tap                |  12 +
 test/test/catch-tap-throws.js                      |  33 +
 test/test/catch-tap-throws.tap                     |  32 +
 test/test/child-sigterm-after-end-bail.tap         |  16 +
 test/test/child-sigterm-after-end.js               |  22 +
 test/test/child-sigterm-after-end.tap              |  16 +
 test/test/console-log-bail.tap                     |  11 +-
 test/test/console-log.tap                          |  11 +-
 test/test/deferred-comment-bail.tap                |   9 +
 test/test/deferred-comment.js                      |   7 +
 test/test/deferred-comment.tap                     |   9 +
 test/test/empty-bail.tap                           |   3 -
 test/test/empty.tap                                |   3 -
 test/test/end-end-bail.tap                         |   2 +-
 test/test/end-end.tap                              |  28 +-
 test/test/end-event-timing-bail.tap                |   4 +-
 test/test/end-event-timing.tap                     |   4 +-
 test/test/end-exception-bail.tap                   |   2 +-
 test/test/end-exception.tap                        |   2 +-
 test/test/equivalent-bail.tap                      |   2 +-
 test/test/equivalent.tap                           |   2 +-
 test/test/exit-on-bailout-bail.tap                 |   4 +-
 test/test/exit-on-bailout.tap                      |   4 +-
 test/test/mochalike-bail.tap                       |  20 +-
 test/test/mochalike-ok-bail.tap                    |  16 +-
 test/test/mochalike-ok.tap                         |  16 +-
 test/test/mochalike.tap                            |  26 +-
 test/test/nesting-bail.tap                         |   6 +-
 test/test/nesting.tap                              |   9 +-
 test/test/no-diags-bail.tap                        |   4 +
 test/test/no-diags.js                              |   6 +
 test/test/no-diags.tap                             |  12 +
 test/test/not-ok-nested-bail.tap                   |   4 +-
 test/test/not-ok-nested.tap                        |   5 +-
 test/test/ok-bail.tap                              |   9 +-
 test/test/ok-diags-bail.tap                        |  16 +
 test/test/ok-diags.js                              |  13 +
 test/test/ok-diags.tap                             |  16 +
 test/test/ok.tap                                   |   9 +-
 test/test/pending-handles-bail.tap                 |  10 +-
 test/test/pending-handles.tap                      |   8 +-
 test/test/plan-async-bail.tap                      |  13 +
 test/test/plan-async.js                            |   9 +
 test/test/plan-async.tap                           |  13 +
 test/test/plan-failures-bail.tap                   |   2 +-
 test/test/plan-failures.tap                        |  24 +-
 test/test/plan-too-many-bail.tap                   |   4 +-
 test/test/plan-too-many.tap                        |   4 +-
 test/test/pragma-bail.tap                          |   5 +-
 test/test/pragma.js                                |  12 +-
 test/test/pragma.tap                               |  12 +-
 test/test/promise-bail.tap                         |  80 +++
 test/test/promise-fails-bail.tap                   |  12 +
 test/test/promise-fails.js                         |  40 ++
 test/test/promise-fails.tap                        |  81 +++
 test/test/promise-plan-bail.tap                    |  34 +
 test/test/promise-plan.js                          |  76 ++
 test/test/promise-plan.tap                         |  71 ++
 test/test/promise-return-bail.tap                  |  12 +-
 test/test/promise-return-mocha-bail.tap            |  14 +-
 test/test/promise-return-mocha.tap                 |  16 +-
 test/test/promise-return.tap                       |   8 +-
 test/test/promise.js                               |  45 ++
 test/test/promise.tap                              |  80 +++
 test/test/root-teardown-bail.tap                   |   2 +-
 test/test/root-teardown.tap                        |   2 +-
 test/test/spawn-bail.tap                           |   8 +-
 test/test/spawn-empty-bail.tap                     |   2 +-
 test/test/spawn-empty.tap                          |   2 +-
 test/test/spawn-failures-bail.tap                  |   9 +
 test/test/spawn-failures.js                        |  40 ++
 test/test/spawn-failures.tap                       |  30 +
 test/test/spawn-stderr-bail.tap                    |   2 +-
 test/test/spawn-stderr.tap                         |   2 +-
 test/test/spawn.tap                                |  22 +-
 test/test/sync-timeout-bail.tap                    |  10 +
 test/test/sync-timeout.js                          |  25 +
 test/test/sync-timeout.tap                         |  27 +
 test/test/throw-bail.tap                           |   8 +-
 test/test/throw-twice-bail.tap                     |  12 +
 test/test/throw-twice.js                           |  12 +
 test/test/throw-twice.tap                          |  14 +
 test/test/throw.tap                                |  12 +-
 test/test/throws-and-plans-bail.tap                |   4 +-
 test/test/throws-and-plans.tap                     |  18 +-
 test/test/throws-bail.tap                          |   2 +-
 test/test/throws.tap                               |   2 +-
 test/test/timeout-bail.tap                         |   8 +-
 test/test/timeout-via-runner-bail.tap              |  16 +
 .../timeout-via-runner-ignore-sigterm-bail.tap     |  18 +
 test/test/timeout-via-runner-ignore-sigterm.js     |  20 +
 test/test/timeout-via-runner-ignore-sigterm.tap    |  31 +
 test/test/timeout-via-runner-no-plan-bail.tap      |  15 +
 test/test/timeout-via-runner-no-plan.js            |  17 +
 test/test/timeout-via-runner-no-plan.tap           |  31 +
 test/test/timeout-via-runner.js                    |  17 +
 test/test/timeout-via-runner.tap                   |  31 +
 test/test/timeout.tap                              |   8 +-
 test/test/todo-bail.tap                            |   8 +-
 test/test/todo.tap                                 |   8 +-
 test/test/unfinished-bail.tap                      |   4 +-
 test/test/unfinished.tap                           |  14 +-
 test/throw-after-end.js                            |  27 +
 test/throws-arg-ordering.js                        |  60 ++
 test/timeout.js                                    |  45 ++
 test/trivial-success.js                            |   2 -
 206 files changed, 5068 insertions(+), 2483 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index 7f22ad5..c070d37 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,5 +2,6 @@ sudo: false
 language: node_js
 node_js:
   - '0.10'
-  - '0.12'
   - '4'
+  - '5'
+  - '6'
diff --git a/AUTHORS b/AUTHORS
deleted file mode 100644
index dffc361..0000000
--- a/AUTHORS
+++ /dev/null
@@ -1,12 +0,0 @@
-# contributors sorted by whether or not they're me
-Isaac Z. Schlueter <i at izs.me>
-baudehlo <helpme+github at gmail.com>
-James Halliday <mail at substack.net>
-Jason Smith (air) <jhs at iriscouch.com>
-Pedro P. Candel <kusorbox at gmail.com>
-Stein Martin Hustad <stein at hustad.com>
-Trent Mick <trentm at gmail.com>
-Corey Richardson <kb1pkl at aim.com>
-Raynos <raynos2 at gmail.com>
-Siddharth Mahendraker <siddharth_mahen at me.com>
-Ryan Graham <r.m.graham at gmail.com>
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4b1fa57..1ce0c07 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,98 +1,2 @@
-## 4.0
-
-Raise an error if `t.end()` is explicitly called more than once.  This
-is a breaking change, because it can cause previously-passing tests to
-fail, if they did `t.end()` in multiple places.
-
-Support promises returned by mochalike functions.
-
-## 3.1
-
-Support sending coverage output to both codecov.io and coveralls.io.
-
-## 3.0
-
-Upgrade to nyc 5.  This means that `config.nyc.exclude` arrays in
-`package.json` now take globs instead of regular expressions.
-
-## 2.3
-
-Use the name of the function supplied to `t.test(fn)` as the test name
-if a string name is not provided.
-
-Better support for sparse arrays.
-
-## 2.2
-
-Add support for Codecov.io as well as Coveralls.io.
-
-Catch failures that come after an otherwise successful test set.
-
-Fix timing of `t.on('end')` so that event fires *before* the next
-child test is started, instead of immediately after it.
-
-`t.throws()` can now be supplied a regexp for the expected Error
-message.
-
-## 2.1
-
-Exit in failure on root test bailout.
-
-Support promises returned by `t.test(fn)` function.
-
-## 2.0
-
-Update matching behavior using [tmatch](http://npm.im/tmatch).  This
-is a breaking change to `t.match`, `t.similar`, `t.has`, etc., but
-brings them more in line with what people epirically seem to expect
-these functions to do.
-
-Deal with pending handles left open when a child process gets a
-`SIGTERM` on timeout.
-
-Remove domains in favor of more reliable and less invasive state and
-error-catching bookkeeping.
-
-## 1.4
-
-Add `t.contains()` alias for `t.match()`.
-
-Use `deeper` for deep object similarity testing.
-
-Treat unfinished tests as failures.
-
-Add support for pragmas in TAP output.
-
-## 1.3
-
-Bind all Test methods to object.
-
-Add `t.tearDown()`, `t.autoend()`, so that the root export is Just
-Another Test Object, which just happens to be piping to stdout.
-
-Support getting an error object in bailout()
-
-## 1.2
-
-Better support for exit status codes.
-
-## 1.1
-
-Add coverage using nyc.
-
-If a `COVERALLS_REPO_TOKEN` is provided, then run tests with coverage,
-and pipe to coveralls.
-
-## 1.0
-
-Complete rewrite from 0.x.
-
-Child tests implemented as nested TAP output, similar to Perl's `Test::More`.
-
-## 0.x
-
-The 0.x versions used a "flattened" approach to child tests, which
-requires some bookkeeping.
-
-It worked, mostly, but its primary success was inspiring
-[tape](http://npm.im/tape) and tap v1 and beyond.
+Please see [the tap website](http://www.node-tap.org/changelog/) for
+the curated changelog.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 0c278d9..52451f0 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,14 +1,15 @@
-- Check the `TODO.md` file to see stuff that is likely to be accepted.
+- Check the [issues](https://github.com/tapjs/node-tap/issues) to see
+  stuff that is likely to be accepted.
 - Every patch should have a new test that fails without the patch and
   passes with the patch.
 - All tests should pass on Node 0.8 and above.  If some tests have to
   be skipped for very old Node versions that's fine, but the
   functionality should still work as intended.
-- Run `node scripts/generate-test-test.js test/test/*.js` to
-  re-generate the output tests whenever output is changed.  However,
-  when you do this, make sure to check the change to ensure that it's
-  what you intended, and that it didn't cause any other inadvertent
-  changes.
+- Run `npm run regen-fixtures` to re-generate the output tests
+  whenever output is changed.  However, when you do this, make sure to
+  check the change to ensure that it's what you intended, and that it
+  didn't cause any other inadvertent changes.
 - Prefer adding cases to an existing test rather than writing a new
   one from scratch.  For example, add a new test in `test/test/*.js`
   rather than create a new test that validates test output.
+- Docs should be changed on the `gh-pages` branch
diff --git a/README.md b/README.md
index 8a6c61c..6d79d41 100644
--- a/README.md
+++ b/README.md
@@ -3,775 +3,20 @@
 A <abbr title="Test Anything Protocol">TAP</abbr> test framework for
 Node.js.
 
+[![Build Status](https://travis-ci.org/tapjs/node-tap.svg?branch=master)](https://travis-ci.org/tapjs/node-tap) [![Build Status](https://ci.appveyor.com/api/projects/status/913p1ypf21gf4leu?svg=true)](https://ci.appveyor.com/project/isaacs/node-tap) [![Coverage Status](https://coveralls.io/repos/tapjs/node-tap/badge.svg?branch=master&service=github)](https://coveralls.io/github/tapjs/node-tap?branch=master)
+
 It includes a command line test runner for consuming TAP-generating
 test scripts, and a JavaScript framework for writing such scripts.
 
-Built-in support for code coverage (including instrumenting child
-processes).  Coverage is printed to the command line in a terse table
-by default, but tap can also open up your web browser to a pretty
-report if you add `--coverage-report=lcov` to the command.
-
-Works with all exception-throwing assertion libraries (chai, should,
-node's built-in `require('assert')`, or just throwing yourself) but
-also has a [huge library of built-in assertions](#asserts) that you
-can use if you want to have each one reported as successes.
-
-Outputs in a wide variety of formats using the
-[tap-mocha-reporter](http://npm.im/tap-mocha-reporter) module.  (That
-is, you can get spec output by doing `-Rspec`.  The default output is
-called 'classic', based on tap 0.x's output, but with color and timing
-info.)
-
-[![Build Status](https://travis-ci.org/isaacs/node-tap.svg?branch=master)](https://travis-ci.org/isaacs/node-tap) [![Coverage Status](https://coveralls.io/repos/isaacs/node-tap/badge.svg?branch=master)](https://coveralls.io/r/isaacs/node-tap?branch=master)
-
-## USAGE
-
-Write your tests in JavaScript
-
-```javascript
-var tap = require('tap')
-
-// you can test stuff just using the top level object.
-// no suites or subtests required.
-
-tap.equal(1, 1, 'check if numbers still work')
-tap.notEqual(1, 2, '1 should not equal 2')
-
-// also you can group things into sub-tests.
-// Sub-tests will be run in sequential order always,
-// so they're great for async things.
-
-tap.test('first stuff', function (t) {
-  t.ok(true, 'true is ok')
-  t.similar({a: [1,2,3]}, {a: [1,2,3]})
-  // call t.end() when you're done
-  t.end()
-})
-
-// If you have a bunch of setup stuff that MUST work or else
-// the rest of the tests are not worth running, then you can
-// pass `{ bail: true }` to make it bail out on failure.
-
-tap.test('must succeed or all is lost', { bail: true }, function (t) {
-  db = new DataBorscht()
-  t.ok(db, 'borscht setup must succeed')
-  t.end()
-})
-
-// You can also bail out based on specific conditions, or with a
-// different error message of your choosing.
-tap.test('must mostly succeed or all is lost', function (t) {
-  db = new DataBorscht()
-
-  t.ok(db, 'borscht setup')
-  if (!db) {
-    t.bailout('the borscht is lost.  I cannot continue.')
-    return
-  }
-
-  t.ok(db.connection, 'db must have connection')
-  t.ok(db.username, 'db must have username')
-  t.equal(db.color, 'red', 'borscht should be red')
-  if (!t.passing())
-    t.bailout('something weird with the data borscht.')
-
-  t.end()
-})
-
-// you can specify a 'plan' if you know how many
-// tests there will be in advance. Handy when
-// order is irrelevant and things happen in parallel.
-
-// Note that the function name is used if no name is provided!
-tap.test(function planned (t) {
-  t.plan(2)
-  setTimeout(function () {
-    t.ok(true, 'a timeout')
-  })
-  setTimeout(function () {
-    t.ok(true, 'b timeout')
-  })
-})
-
-// you can do `var test = require('tap').test` if you like
-// it's pre-bound to the root tap object.
-
-var test = require('tap').test
-
-// subtests can have subtests
-test(function parent (t) {
-  t.test(function child (tt) {
-    tt.throws(function () {
-      throw new Error('fooblz')
-    }, {
-      message: 'fooblz'
-    }, 'throw a fooblz')
-
-    // throws also uses function name if no name provided
-    tt.throws(function throw_whatever () { throw 1 })
-
-    tt.end()
-  })
-
-  t.end()
-})
-
-// thrown errors just fail the current test, so you can
-// also use your own assert library if you like.
-// Of course, this means it won't be able to print out the
-// number of passing asserts, since passes will be silent.
-
-test('my favorite assert lib', function (t) {
-  var assert = require('assert')
-  assert.ok(true, 'true is ok')
-  assert.equal(1, 1, 'math works')
-
-  // Since it can't read the plan, using a custom assert lib
-  // means that you MUST use t.end()
-  t.end()
-})
-
-// You can mark tests as 'todo' either using a conf object,
-// or simply by omitting the callback.
-test('solve halting problem')
-test('prove p=np', { todo: true }, function (t) {
-  // i guess stuff goes here
-  t.fail('traveling salesmen must pack their own bags')
-  t.end()
-})
-
-// Prefer mocha/rspec/lab style global objects?
-// Got you covered.  This is a little experimental,
-// patches definitely welcome.
-tap.mochaGlobals()
-describe('suite ride bro', function () {
-  it('should have a wheel', function () {
-    assert.ok(thingie.wheel, 'wheel')
-  })
-  it('can happen async', function (done) {
-    setTimeout(function () {
-      assert.ok('ok')
-      done()
-    })
-  })
-})
-
-// Read on for a complete list of asserts, methods, etc.
-```
-
-You can run tests using the `tap` executable.  Put this in your
-package.json file:
-
-```json
-{
-  "scripts": {
-    "test": "tap test/*.js"
-  }
-}
-```
-
-and then you can run `npm test` to run your test scripts.
-
-Command line behavior and flags:
-
-```
-$ tap -h
-Usage:
-  tap [options] <files>
-
-Executes all the files and interprets their output as TAP
-formatted test result data.
-
-To parse TAP data from stdin, specify "-" as a filename.
-
-Short options are parsed gnu-style, so for example '-bCRspec' would be
-equivalent to '--bail --no-color --reporter=spec'
-
-Options:
-
-  -c --color                  Use colors (Default for TTY)
-
-  -C --no-color               Do not use colors (Default for non-TTY)
-
-  -b --bail                   Bail out on first failure
-
-  -B --no-bail                Do not bail out on first failure (Default)
-
-  -R<type> --reporter=<type>  Use the specified reporter.  Defaults to
-                              'classic' when colors are in use, or 'tap'
-                              when colors are disabled.
-
-                              Available reporters:
-                              classic doc dot dump html htmlcov json
-                              jsoncov jsonstream landing list markdown
-                              min nyan progress silent spec tap xunit
-
-  -s<file> --save=<file>      If <file> exists, then it should be a line-
-                              delimited list of test files to run.  If
-                              <file> is not present, then all command-line
-                              positional arguments are run.
-
-                              After the set of test files are run, any
-                              failed test files are written back to the
-                              save file.
-
-                              This way, repeated runs with -s<file> will
-                              re-run failures until all the failures are
-                              passing, and then once again run all tests.
-
-                              It's a good idea to .gitignore the file
-                              used for this purpose, as it will churn a
-                              lot.
-
-  --coverage --cov            Capture coverage information using 'nyc'
-
-                              If a COVERALLS_REPO_TOKEN environment
-                              variable is set, then coverage is
-                              captured by default and sent to the
-                              coveralls.io service. If a CODECOV_TOKEN
-                              environment variable is set, then coverage is
-                              captured by default and sent to the
-                              codecov.io service.
-
-  --no-coverage --no-cov      Do not capture coverage information.
-                              Note that if nyc is already loaded, then
-                              the coverage info will still be captured.
-
-  --coverage-report=<type>    Output coverage information using the
-                              specified istanbul/nyc reporter type.
-
-                              Default is 'text' when running on the
-                              command line, or 'text-lcov' when piping
-                              to coveralls or codecov.
-
-                              If 'lcov' is used, then the report will
-                              be opened in a web browser after running.
-
-                              This can be run on its own at any time
-                              after a test run that included coverage.
-
-  -t<n> --timeout=<n>         Time out test files after <n> seconds.
-                              Defaults to 30, or the value of the
-                              TAP_TIMEOUT environment variable.
-
-  -h --help                   print this thing you're looking at
-
-  -v --version                show the version of this program
-
-  -gc --expose-gc             Expose the gc() function to Node tests
-
-  --debug                     Run JavaScript tests with node --debug
-
-  --debug-brk                 Run JavaScript tests with node --debug-brk
-
-  --harmony                   Enable all Harmony flags in JavaScript tests
-
-  --strict                    Run JS tests in 'use strict' mode
-
-  --                          Stop parsing flags, and treat any additional
-                              command line arguments as filenames.
-```
-
-## Coverage
-
-This module uses [nyc](http://npm.im/nyc) to track code coverage, even
-across subprocess boundaries.  It is included by default, and there's
-nothing you need to do but enable it.  Adding coverage *will* make
-your tests run slightly slower, but that's to be expected.
-
-To generate coverage information, run your tests with the `--cov`
-argument.
-
-To specify a report format, you can use `--coverage-report=<type>`.
-The default type is `text`, which produces a pretty text-only table on
-the terminal.  If you specify `--coverage-report=lcov`, then tap will
-attempt to open a web browser to view the report after the test run.
-
-If you use this a lot, you may want to add `coverage` and
-`.nyc_output` to your `.gitignore` and/or `.npmignore` files.
-
-### Travis-CI and Coveralls.io/CodeCov.io Integration
-
-You can very easily take advantage of continuous test coverage reports
-by using [Travis-CI](https://travis-ci.org) and
-[Coveralls](https://coveralls.io).
-
-1. Enable Travis-CI by signing up, enabling tests on your repo, and
-   adding a `.travis.yml` file to your repo.  You can use [this
-   module's .travis.yml file as an
-   example](https://github.com/isaacs/node-tap/blob/master/.travis.yml)
-2. Enable Coveralls.io or CodeCov.io by signing up, and adding the
-   repo.  Note the repo API token.
-3. Back at Travis-CI, add a private environment variable.  The name of
-   the environment variable is `COVERALLS_REPO_TOKEN` for Coveralls,
-   or `CODECOV_TOKEN` for CodeCov.io, and the value is the token you
-   got from Coveralls or CodeCov.
-4. When that token is set in the environment variable, `tap` will
-   automatically generate coverage information and send it to the
-   appropriate place.
-
-## API
-
-### tap = require('tap')
-
-The root `tap` object is an instance of the Test class with a few
-slight modifications.
-
-1. The `tearDown()`, `plan()`, and `test()` methods are pre-bound onto
-   the root object, so that you don't have to call them as methods.
-2. By default, it pipes to stdout, so running a test directly just
-   dumps the TAP data for inspection.  (You can of course
-   `tap.unpipe(process.stdout)` if you want to direct it elsewhere.)
-3. Various other things are hung onto it for convenience, since it is
-   the main package export.
-4. The test ends automatically when `process.on('exit')` fires, so
-   there is no need to call `tap.end()` explicitly.
-5. Adding a `tearDown` function triggers `autoend` behavior.
-   Otherwise, the `end` would potentially never arrive, if for example
-   `tearDown` is used to close a server or cancel some long-running
-   process, because `process.on('exit')` would never fire of its own
-   accord.
-
-### tap.synonyms
-
-A list of all of the canonical assert methods and their synonyms.
-
-### tap.mochaGlobals()
-
-Method that injects `describe()` and `it()` into the global
-environment for mocha-like BDD style test definition.
-
-This feature is incomplete, experimental, and may change drastically
-in the future.  Feedback is welcome.
-
-### tap.Test
-
-The `Test` class is the main thing you'll be touching when you use
-this module.
-
-The most common way to instantiate a `Test` object by calling the
-`test` method on the root or any other `Test` object.  The callback
-passed to `test(name, fn)` will receive a child `Test` object as its
-argument.
-
-A `Test` object is a Readable Stream.  Child tests automatically send
-their data to their parent, and the root `require('tap')` object pipes
-to stdout by default.  However, you can instantiate a `Test` object
-and then pipe it wherever you like.  The only limit is your imagination.
-
-#### t.test([name], [options], [function])
-
-Create a subtest.
-
-If the function is omitted, then it will be marked as a "todo" or
-"pending" test.
-
-If the function has a name, and no name is provided, then the function
-name will be used as the test name.  If no test name is provided, then
-the name will be `(unnamed test)`.
-
-The function gets a Test object as its only argument.  From there, you
-can call the `t.end()` method on that object to end the test, or use
-the `t.plan()` method to specify how many child tests or asserts the
-test will have.
-
-If the function returns a `Promise` object (that is, an object with a
-`then` method), then when the promise is rejected or fulfilled, the
-test will be either ended or failed.
-
-If the function is not provided, then this will be treated as a `todo`
-test.
-
-The options object is the same as would be passed to any assert, with
-two additional fields that are only relevant for child tests:
-
-* `timeout`: The number of ms to allow the test to run.
-* `bail`: Set to `true` to bail out on the first test failure.
-* `autoend`: Automatically `end()` the test on the next turn of the
-  event loop after its internal queue is drained.
-
-#### t.tearDown(function)
-
-Run the supplied function when `t.end()` is called, or when the `plan`
-is met.
-
-Note that when called on the root `tap` export, this also triggers
-`autoend` behavior.
-
-#### t.autoend()
-
-Automatically end the test as soon as there is nothing pending in its
-queue.
-
-The automatic end is deferred with a `setTimeout`, and any new action
-will cancel and re-schedule the timer.  Nonetheless, calling this
-method means that any slow asynchronous behavior may be lost, if it
-comes after the `end()` is auto-triggered.
-
-This behavior is triggered on the root `tap` object when
-`tap.tearDown()` is called.
-
-#### t.plan(number)
-
-Specify that a given number of tests are going to be run.
-
-This may only be called *before* running any asserts or child tests.
-
-#### t.end()
-
-Call when tests are done running.  This is not necessary if `t.plan()`
-was used, or if the test function returns a Promise.
-
-If you call `t.end()` explicitly more than once, an error will be
-raised.
-
-#### t.bailout([reason])
-
-Pull the proverbial ejector seat.
-
-Use this when things are severely broken, and cannot be reasonably
-handled.  Immediately terminates the entire test run.
-
-#### t.passing()
-
-Return true if everything so far is ok.
-
-Note that all assert methods also return `true` if they pass.
-
-#### t.comment(message)
-
-Print the supplied message as a TAP comment.
-
-Note that you can always use `console.error()` for debugging (or
-`console.log()` as long as the message doesn't look like TAP formatted
-data).
-
-#### t.fail(message, extra)
-
-Emit a failing test point.  This method, and `pass()`, are the basic
-building blocks of all fancier assertions.
-
-#### t.pass(message)
-
-Emit a passing test point.  This method, and `fail()`, are the basic
-building blocks of all fancier assertions.
-
-#### t.pragma(set)
-
-Sets a `pragma` switch for a set of boolean keys in the argument.
-
-The only pragma currently supported by the TAP parser is `strict`,
-which tells the parser to treat non-TAP output as a failure.
-
-Example:
-
-```
-var t = require('tap')
-console.log('this non-TAP output is ok')
-t.pragma({ strict: true })
-console.log('but this will cause a failure')
-```
-
-### Asserts
-
-The `Test` object has a collection of assertion methods, many of which
-are given several synonyms for compatibility with other test runners
-and the vagaries of human expectations and spelling.  When a synonym
-is multi-word in `camelCase` the corresponding lower case and
-`snake_case` versions are also created as synonyms.
-
-All assertion methods take optional `message` and `extra` arguments as
-the last two params.  The `message` is the name of the test.  The
-`extra` argument can contain any arbitrary data about the test, but
-the following fields are "special".
-
-* `todo` Set to boolean `true` or a String to mark this as pending
-* `skip` Set to boolean `true` or a String to mark this as skipped
-* `at` Generated by the framework.  The location where the assertion
-  was called.  Do not set this field.
-* `stack` Generated by the framework.  The stack trace to the point
-  where the assertion was called.  Do not set this field.
-
-#### t.ok(obj, message, extra)
-
-Verifies that the object is truthy.
-
-Synonyms: `t.true`, `t.assert`
-
-#### t.notOk(obj, message, extra)
-
-Verifies that the object is not truthy.
-
-Synonyms: `t.false`, `t.assertNot`
-
-#### t.error(obj, message, extra)
-
-If the object is an error, then the assertion fails.
-
-Note: if an error is encountered unexpectedly, it's often better to
-simply throw it.  The Test object will handle this as a failure.
-
-Synonyms: `t.ifErr`, `t.ifError`
-
-#### t.throws(fn, [expectedError], message, extra)
-
-Expect the function to throw an error.  If an expected error is
-provided, then also verify that the thrown error matches the expected
-error.
-
-If the function has a name, and the message is not provided, then the
-function name will be used as the message.
-
-If the function is not provided, then this will be treated as a `todo`
-test.
-
-Caveat: if you pass a `extra` object to t.throws, then you MUST also
-pass in an expected error, or else it will read the diag object as the
-expected error, and get upset when your thrown error doesn't match
-`{skip:true}` or whatever.
-
-For example, this will not work as expected:
-
-```javascript
-t.throws(function() {throw new Error('x')}, { skip: true })
-```
-
-But this is fine:
-
-```javascript
-// note the empty 'expected error' object.
-// since it has no fields, it'll only verify that the thrown thing is
-// an object, not the value of any properties
-t.throws(function() {throw new Error('x')}, {}, { skip: true })
-```
-
-The expected error is tested against the throw error using `t.match`,
-so regular expressions and the like are fine.  If the expected error
-is an `Error` object, then the `stack` field is ignored, since that
-will generally never match.
-
-Synonyms: `t.throw`
-
-#### t.doesNotThrow(fn, message, extra)
-
-Verify that the provided function does not throw.
-
-If the function has a name, and the message is not provided, then the
-function name will be used as the message.
-
-If the function is not provided, then this will be treated as a `todo`
-test.
-
-Note: if an error is encountered unexpectedly, it's often better to
-simply throw it.  The Test object will handle this as a failure.
-
-Synonyms: `t.notThrow`
-
-#### t.equal(found, wanted, message, extra)
-
-Verify that the object found is exactly the same (that is, `===`) to
-the object that is wanted.
-
-Synonyms: `t.equals`, `t.isEqual`, `t.is`, `t.strictEqual`,
-`t.strictEquals`, `t.strictIs`, `t.isStrict`, `t.isStrictly`
-
-#### t.notEqual(found, notWanted, message, extra)
-
-Inverse of `t.equal()`.
-
-Verify that the object found is not exactly the same (that is, `!==`) as
-the object that is wanted.
-
-Synonyms: `t.inequal`, `t.notEqual`, `t.notEquals`,
-`t.notStrictEqual`, `t.notStrictEquals`, `t.isNotEqual`, `t.isNot`,
-`t.doesNotEqual`, `t.isInequal`
-
-#### t.same(found, wanted, message, extra)
-
-Verify that the found object is deeply equivalent to the wanted
-object.  Use non-strict equality for scalars (ie, `==`).
-
-Synonyms: `t.equivalent`, `t.looseEqual`, `t.looseEquals`,
-`t.deepEqual`, `t.deepEquals`, `t.isLoose`, `t.looseIs`
-
-#### t.notSame(found, notWanted, message, extra)
-
-Inverse of `t.same()`.
-
-Verify that the found object is not deeply equivalent to the
-unwanted object.  Uses non-strict inequality (ie, `!=`) for scalars.
-
-Synonyms: `t.inequivalent`, `t.looseInequal`, `t.notDeep`,
-`t.deepInequal`, `t.notLoose`, `t.looseNot`
-
-#### t.strictSame(found, wanted, message, extra)
-
-Strict version of `t.same()`.
-
-Verify that the found object is deeply equivalent to the wanted
-object.  Use strict equality for scalars (ie, `===`).
-
-Synonyms: `t.strictEquivalent`, `t.strictDeepEqual`, `t.sameStrict`,
-`t.deepIs`, `t.isDeeply`, `t.isDeep`, `t.strictDeepEquals`
-
-#### t.strictNotSame(found, notWanted, message, extra)
-
-Inverse of `t.strictSame()`.
-
-Verify that the found object is not deeply equivalent to the unwanted
-object.  Use strict equality for scalars (ie, `===`).
-
-Synonyms: `t.strictInequivalent`, `t.strictDeepInequal`,
-`t.notSameStrict`, `t.deepNot`, `t.notDeeply`, `t.strictDeepInequals`,
-`t.notStrictSame`
-
-#### t.match(found, pattern, message, extra)
-
-Verify that the found object matches the pattern provided.
-
-If pattern is a regular expression, and found is a string, then verify
-that the string matches the pattern.
-
-If the pattern is a string, and found is a string, then verify that
-the pattern occurs within the string somewhere.
-
-If pattern is an object, then verify that all of the (enumerable)
-fields in the pattern match the corresponding fields in the object
-using this same algorithm.  For example, the pattern `{x:/a[sdf]{3}/}`
-would successfully match `{x:'asdf',y:'z'}`.
-
-This is useful when you want to verify that an object has a certain
-set of required fields, but additional fields are ok.
-
-Synonyms: `t.has`, `t.hasFields`, `t.matches`, `t.similar`, `t.like`,
-`t.isLike`, `t.includes`, `t.include`, `t.contains`
-
-#### t.notMatch(found, pattern, message, extra)
-
-Interse of `match()`
-
-Verify that the found object does not match the pattern provided.
-
-Synonyms: `t.dissimilar`, `t.unsimilar`, `t.notSimilar`, `t.unlike`,
-`t.isUnlike`, `t.notLike`, `t.isNotLike`, `t.doesNotHave`,
-`t.isNotSimilar`, `t.isDissimilar`
-
-#### t.type(object, type, message, extra)
-
-Verify that the object is of the type provided.
-
-Type can be a string that matches the `typeof` value of the object, or
-the string name of any constructor in the object's prototype chain, or
-a constructor function in the object's prototype chain.
-
-For example, all the following will pass:
-
-```javascript
-t.type(new Date(), 'object')
-t.type(new Date(), 'Date')
-t.type(new Date(), Date)
-```
-
-Synonyms: `t.isa`, `t.isA`
-
-### Advanced Usage
-
-These methods are primarily for internal use, but can be handy in some
-unusual situations.  If you find yourself using them frequently, you
-*may* be Doing It Wrong.  However, if you find them useful, you should
-feel perfectly comfortable using them.
-
-Please [let us know](https://github.com/isaacs/node-tap/issues) if you
-frequently encounter situations requiring advanced usage, because this
-may indicate a shortcoming in the "non-advanced" API.
-
-#### t.stdin()
-
-Parse standard input as if it was a child test named `/dev/stdin`.
-
-This is primarily for use in the test runner, so that you can do
-`some-tap-emitting-program | tap other-file.js - -Rnyan`.
-
-#### t.spawn(command, arguments, [options], [name], [extra])
-
-Sometimes, instead of running a child test directly inline, you might
-want to run a TAP producting test as a child process, and treat its
-standard output as the TAP stream.
-
-That's what this method does.
-
-It is primarily used by the executable runner, to run all of the
-filename arguments provided on the command line.
-
-The `options` object is passed to `child_process.spawn`, and can
-contain stuff like stdio directives and environment vars.
-
-The `extra` arg is the same that would be passed to any assertion or
-child test, with the addition of the following fields:
-
-* `bail`: Set to `true` to bail out on the first failure.  This is
-  done by checking the output and then forcibly killing the process,
-  but also sets the `TAP_BAIL` environment variable, which node-tap
-  uses to set this field internally as well.
-* `timeout`: The number of ms to allow the child process to continue.
-  If it goes beyond this time, the child process will be forcibly
-  killed.
-
-#### t.addAssert(name, length, fn)
-
-This is used for creating assertion methods on the `Test` class.
-
-It's a little bit advanced, but it's also super handy sometimes.  All
-of the assert methods below are created using this function, and it
-can be used to create application-specific assertions in your tests.
-
-The name is the method name that will be created.  The length is the
-number of arguments the assertion operates on.  (The `message` and
-`extra` arguments will alwasy be appended to the end.)
-
-For example, you could have a file at `test/setup.js` that does the
-following:
-
-```javascript
-var tap = require('tap')
-
-// convenience
-if (module === require.main) {
-  tap.pass('ok')
-  return
-}
-
-// Add an assertion that a string is in Title Case
-// It takes one argument (the string to be tested)
-tap.Test.prototype.addAssert('titleCase', 1, function (str, message, extra) {
-  message = message || 'should be in Title Case'
-  // the string in Title Case
-  // A fancier implementation would avoid capitalizing little words
-  // to get `Silence of the Lambs` instead of `Silence Of The Lambs`
-  // But whatever, it's just an example.
-  var tc = str.toLowerCase().replace(/\b./, function (match) {
-    return match.toUpperCase()
-  })
-
-  // should always return another assert call, or
-  // this.pass(message) or this.fail(message, extra)
-  return this.equal(str, tc, message, extra)
-})
-```
-
-Then in your individual tests, you'd do this:
-
-```javascript
-require('./setup.js') // adds the assert
-var tap = require('tap')
-tap.titleCase('This Passes')
-tap.titleCase('however, tHis tOTaLLy faILS')
-```
-
-#### t.endAll()
-
-Call the `end()` method on all child tests, and then on this one.
-
-#### t.current()
-
-Return the currently active test.
+* [Getting started guide](http://www.node-tap.org/basics/)
+* Built-in [test coverage](http://www.node-tap.org/coverage/)
+* Many [reporter formats](http://www.node-tap.org/reporting/)
+* Extensive [API](http://www.node-tap.org/api/) featuring:
+  * Great [promise support](http://www.node-tap.org/promises/)
+  * Comprehensive [assert library](http://www.node-tap.org/asserts/)
+  * Other [advanced stuff](http://www.node-tap.org/advanced/)
+* [Command-line interface](http://www.node-tap.org/cli/) for running
+  tests (whether they use node-tap or not)
+
+All this is too much to manage in a single README file, so head over
+to [the website](http://www.node-tap.org/) to learn more.
diff --git a/TODO.md b/TODO.md
deleted file mode 100644
index a0ec681..0000000
--- a/TODO.md
+++ /dev/null
@@ -1,8 +0,0 @@
-- fix the markdown reporter
-- make it faster (seems like mostly this is just node spawn() overhead)
-- read a config file at ~/.taprc for setting default colors,
-  reporters, etc
-- make colors (and diff colors) configurable
-- tests for reporter output
-- split lib/stack.js out into a separate module
-- Add more of the mocha globals when you call tap.mochaGlobals()
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 0000000..da8c6db
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,19 @@
+environment:
+  matrix:
+    - nodejs_version: '5'
+    - nodejs_version: '4'
+    - nodejs_version: '0.12'
+install:
+  - ps: Install-Product node $env:nodejs_version
+  - set CI=true
+  - npm -g install npm at latest
+  - set PATH=%APPDATA%\npm;%PATH%
+  - npm install
+matrix:
+  fast_finish: true
+build: off
+version: '{build}'
+shallow_clone: true
+clone_depth: 1
+test_script:
+  - npm test -- -Rclassic --no-coverage --timeout=3600
diff --git a/bin/is-exe.js b/bin/is-exe.js
deleted file mode 100644
index 4f2d5b3..0000000
--- a/bin/is-exe.js
+++ /dev/null
@@ -1,23 +0,0 @@
-if (process.platform === 'win32') {
-  // On windows, there is no good way to check that a file is executable
-  module.exports = function isExe () {
-    return true
-  }
-} else {
-  module.exports = function isExe (stat) {
-    var mod = stat.mode
-    var uid = stat.uid
-    var gid = stat.gid
-    var u = parseInt('100', 8)
-    var g = parseInt('010', 8)
-    var o = parseInt('001', 8)
-    var ug = u | g
-
-    var ret = (mod & o) ||
-      (mod & g) && process.getgid && gid === process.getgid() ||
-      (mod & u) && process.getuid && uid === process.getuid() ||
-      (mod & ug) && process.getuid && process.getuid() === 0
-
-    return ret
-  }
-}
diff --git a/bin/run.js b/bin/run.js
index 7cc3c1b..e83a6b5 100755
--- a/bin/run.js
+++ b/bin/run.js
@@ -3,35 +3,40 @@ var node = process.execPath
 var fs = require('fs')
 var spawn = require('child_process').spawn
 var fg = require('foreground-child')
-var signalExit = require('signal-exit')
 var opener = require('opener')
-var supportsColor = require('supports-color')
+var colorSupport = require('color-support')
 var nycBin = require.resolve('nyc/bin/nyc.js')
 var glob = require('glob')
-var isExe = require('./is-exe.js')
+var isexe = require('isexe')
+var osHomedir = require('os-homedir')
+var yaml = require('js-yaml')
 
 var coverageServiceTest = process.env.COVERAGE_SERVICE_TEST === 'true'
 
+if (process.env._TAP_COVERAGE_ === '1') {
+  // NYC will not wrap a module in node_modules.
+  // So, we need to tell the child proc when it's been added.
+  global.__coverage__ = global.__coverage__ || {}
+}
+
 // console.error(process.argv.join(' '))
 // console.error('CST=%j', process.env.COVERAGE_SERVICE_TEST)
 // console.error('CRT=%j', process.env.COVERALLS_REPO_TOKEN)
-// console.error('CCT=%j', process.env.CODECOV_TOKEN)
 if (coverageServiceTest) {
   console.log('COVERAGE_SERVICE_TEST')
 }
 
 // Add new coverage services here.
 // it'll check for the environ named and pipe appropriately.
+//
+// Currently only Coveralls is supported, but the infrastructure
+// is left in place in case some noble soul fixes the codecov
+// module in the future.  See https://github.com/tapjs/node-tap/issues/270
 var coverageServices = [
   {
     env: 'COVERALLS_REPO_TOKEN',
     bin: require.resolve('coveralls/bin/coveralls.js'),
     name: 'Coveralls'
-  },
-  {
-    env: 'CODECOV_TOKEN',
-    bin: require.resolve('codecov.io/bin/codecov.io.js'),
-    name: 'Codecov'
   }
 ]
 
@@ -45,7 +50,25 @@ function main () {
     process.exit(1)
   }
 
-  var options = parseArgs(args)
+  // set default args
+  var defaults = constructDefaultArgs()
+
+  // parse dotfile
+  var rcFile = process.env.TAP_RCFILE || (osHomedir() + '/.taprc')
+  var rcOptions = parseRcFile(rcFile)
+
+  // supplement defaults with parsed rc options
+  if (rcOptions) {
+    Object.keys(rcOptions).forEach(function (k) {
+      defaults[k] = rcOptions[k]
+    })
+  }
+
+  defaults.rcFile = rcFile
+
+  // parse args
+  var options = parseArgs(args, defaults)
+
   if (!options) {
     return
   }
@@ -58,58 +81,77 @@ function main () {
     }
   })
 
-  // By definition, the next block cannot be covered, because
-  // they are only relevant when coverage is turned off.
-
-  /* istanbul ignore if */
-  if (options.coverage && !global.__coverage__) {
-    respawnWithCoverage(options)
-    return
-  }
+  options.files = globFiles(options.files)
 
-  /* istanbul ignore if */
-  if (options.coverageReport &&
-    (!global.__coverage__ || coverageServiceTest) &&
-    options.files.length === 0) {
+  if ((options.coverageReport || options.checkCoverage) &&
+      options.files.length === 0) {
     runCoverageReport(options)
     return
   }
 
-  setupTapEnv(options)
-
-  options.files = globFiles(options.files)
-
   if (options.files.length === 0) {
     console.error('Reading TAP data from stdin (use "-" argument to suppress)')
     options.files.push('-')
   }
 
   if (options.files.length === 1 && options.files[0] === '-') {
+    if (options.coverage) {
+      console.error('Coverage disabled because stdin cannot be instrumented')
+    }
     stdinOnly(options)
     return
   }
 
+  // By definition, the next block cannot be covered, because
+  // they are only relevant when coverage is turned off.
+
+  /* istanbul ignore if */
+  if (options.coverage && !global.__coverage__) {
+    respawnWithCoverage(options)
+    return
+  }
+
+  setupTapEnv(options)
+
   runTests(options)
 }
 
-function parseArgs (args) {
-  var options = {}
-
-  options.nodeArgs = []
-  options.timeout = process.env.TAP_TIMEOUT || 30
-  // coverage tools run slow.
+function constructDefaultArgs () {
+  var defaultTimeout = 30
   if (global.__coverage__) {
-    options.timeout = 240
+    defaultTimeout = 240
+  }
+
+  var defaultArgs = {
+    nodeArgs: [],
+    nycArgs: [],
+    testArgs: [],
+    timeout: +process.env.TAP_TIMEOUT || defaultTimeout,
+    color: !!colorSupport.level,
+    reporter: null,
+    files: [],
+    bail: false,
+    saveFile: null,
+    pipeToService: false,
+    coverageReport: null,
+    browser: true,
+    coverage: undefined,
+    checkCoverage: false,
+    branches: 0,
+    functions: 0,
+    lines: 0,
+    statements: 0
   }
 
-  options.color = supportsColor
   if (process.env.TAP_COLORS !== undefined) {
-    options.color = !!(+process.env.TAP_COLORS)
+    defaultArgs.color = !!(+process.env.TAP_COLORS)
   }
-  options.reporter = null
-  options.files = []
-  options.bail = false
-  options.saveFile = null
+
+  return defaultArgs
+}
+
+function parseArgs (args, defaults) {
+  var options = defaults || {}
 
   var singleFlags = {
     b: 'bail',
@@ -128,15 +170,14 @@ function parseArgs (args) {
 
   // If we're running under Travis-CI with a Coveralls.io token,
   // then it's a safe bet that we ought to output coverage.
-  options.pipeToService = false
   for (var i = 0; i < coverageServices.length && !options.pipeToService; i++) {
     if (process.env[coverageServices[i].env]) {
       options.pipeToService = true
     }
   }
-  options.coverage = options.pipeToService
 
-  options.coverageReport = null
+  var defaultCoverage = options.pipeToService
+  var dumpConfig = false
 
   for (i = 0; i < args.length; i++) {
     var arg = args[i]
@@ -185,25 +226,36 @@ function parseArgs (args) {
         console.log(usage())
         return null
 
+      case '--dump-config':
+        dumpConfig = true
+        continue
+
+      case '--nyc-help':
+        nycHelp()
+        return null
+
+      case '--nyc-version':
+        nycVersion()
+        return null
+
       case '--version':
         console.log(require('../package.json').version)
         return null
 
-      case '--__coverage__':
-        // NYC will not wrap a module in node_modules.
-        // So, we need to tell the child proc when it's been added.
-        global.__coverage__ = global.__coverage__ || {}
-        continue
-
       case '--coverage-report':
         options.coverageReport = val || args[++i]
-        if (!options.coverageReport) {
-          if (options.pipeToService || coverageServiceTest) {
-            options.coverageReport = 'text-lcov'
-          } else {
-            options.coverageReport = 'text'
-          }
+        if (options.coverageReport === 'html') {
+          options.coverageReport = 'lcov'
         }
+        defaultCoverage = true
+        continue
+
+      case '--no-browser':
+        options.browser = false
+        continue
+
+      case '--no-coverage-report':
+        options.coverageReport = false
         continue
 
       case '--no-cov': case '--no-coverage':
@@ -244,6 +296,51 @@ function parseArgs (args) {
         options.nodeArgs.push('--harmony')
         continue
 
+      case '--node-arg':
+        val = val || args[++i]
+        if (val !== undefined) {
+          options.nodeArgs.push(val)
+        }
+        continue
+
+      case '--check-coverage':
+        defaultCoverage = true
+        options.checkCoverage = true
+        continue
+
+      case '--test-arg':
+        val = val || args[++i]
+        if (val !== undefined) {
+          options.testArgs.push(val)
+        }
+        continue
+
+      case '--nyc-arg':
+        val = val || args[++i]
+        if (val !== undefined) {
+          options.nycArgs.push(val)
+        }
+        continue
+
+      case '--100':
+        defaultCoverage = true
+        options.checkCoverage = true
+        options.branches = 100
+        options.functions = 100
+        options.lines = 100
+        options.statements = 100
+        continue
+
+      case '--branches':
+      case '--functions':
+      case '--lines':
+      case '--statements':
+        defaultCoverage = true
+        val = val || args[++i]
+        options.checkCoverage = true
+        options[key.slice(2)] = val
+        continue
+
       case '--color':
         options.color = true
         continue
@@ -275,6 +372,10 @@ function parseArgs (args) {
     }
   }
 
+  if (options.coverage === undefined) {
+    options.coverage = defaultCoverage
+  }
+
   if (process.env.TAP === '1') {
     options.reporter = 'tap'
   }
@@ -284,34 +385,30 @@ function parseArgs (args) {
     options.reporter = options.color ? 'classic' : 'tap'
   }
 
+  if (dumpConfig) {
+    console.log(JSON.stringify(options, null, 2))
+    return false
+  }
+
   return options
 }
 
-/* istanbul ignore function */
+/* istanbul ignore next */
 function respawnWithCoverage (options) {
   // console.error('respawn with coverage')
   // Re-spawn with coverage
   var args = [nycBin].concat(
     '--silent',
+    options.nycArgs,
+    '--',
     process.execArgv,
-    process.argv.slice(1),
-    '--__coverage__'
+    process.argv.slice(1)
   )
+  process.env._TAP_COVERAGE_ = '1'
   var child = fg(node, args)
   child.removeAllListeners('close')
   child.on('close', function (code, signal) {
-    if (signal) {
-      return process.kill(process.pid, signal)
-    }
-    if (code) {
-      return process.exit(code)
-    }
-    // console.error('run coverage report')
-    args = [__filename, '--no-coverage', '--coverage-report']
-    if (options.coverageReport) {
-      args.push(options.coverageReport)
-    }
-    fg(node, args)
+    runCoverageReport(options, code, signal)
   })
 }
 
@@ -354,38 +451,95 @@ function pipeToCoverageService (service, options, child) {
     if (signal) {
       process.kill(process.pid, signal)
     } else if (code) {
-      process.exit(code)
+      console.log('Error piping coverage to ' + service.name)
     } else {
       console.log('Successfully piped to ' + service.name)
     }
   })
+}
 
-  signalExit(function (code, signal) {
-    child.kill('SIGHUP')
-    ca.kill('SIGHUP')
-  })
+function runCoverageReport (options, code, signal) {
+  if (options.checkCoverage) {
+    runCoverageCheck(options, code, signal)
+  } else {
+    runCoverageReportOnly(options, code, signal)
+  }
 }
 
-/* istanbul ignore function */
-function runCoverageReport (options) {
+function runCoverageReportOnly (options, code, signal) {
+  if (options.coverageReport === false) {
+    return close(code, signal)
+  }
+
+  if (!options.coverageReport) {
+    if (options.pipeToService || coverageServiceTest) {
+      options.coverageReport = 'text-lcov'
+    } else {
+      options.coverageReport = 'text'
+    }
+  }
+
   var args = [nycBin, 'report', '--reporter', options.coverageReport]
   // console.error('run coverage report', args)
 
-  // automatically hook into coveralls and/or codecov
+  var child
+  // automatically hook into coveralls
   if (options.coverageReport === 'text-lcov' && options.pipeToService) {
     // console.error('pipeToService')
-    var child = spawn(node, args, { stdio: [ 0, 'pipe', 2 ] })
-
+    child = spawn(node, args, { stdio: [ 0, 'pipe', 2 ] })
     pipeToCoverageServices(options, child)
   } else {
     // otherwise just run the reporter
     child = fg(node, args)
-    if (options.coverageReport === 'lcov') {
+    if (options.coverageReport === 'lcov' && options.browser) {
       child.on('exit', function () {
         opener('coverage/lcov-report/index.html')
       })
     }
   }
+
+  if (code || signal) {
+    child.removeAllListeners('close')
+    child.on('close', close)
+  }
+
+  function close (c, s) {
+    if (signal || s) {
+      setTimeout(function () {}, 200)
+      return process.kill(process.pid, signal || s)
+    }
+    if (code || c) {
+      return process.exit(code || c)
+    }
+  }
+}
+
+function coverageCheckArgs (options) {
+  var args = []
+  if (options.branches) {
+    args.push('--branches', options.branches)
+  }
+  if (options.functions) {
+    args.push('--functions', options.functions)
+  }
+  if (options.lines) {
+    args.push('--lines', options.lines)
+  }
+  if (options.statements) {
+    args.push('--statements', options.statements)
+  }
+
+  return args
+}
+
+function runCoverageCheck (options, code, signal) {
+  var args = [nycBin, 'check-coverage'].concat(coverageCheckArgs(options))
+
+  var child = fg(node, args)
+  child.removeAllListeners('close')
+  child.on('close', function (c, s) {
+    runCoverageReportOnly(options, code || c, signal || s)
+  })
 }
 
 function usage () {
@@ -394,6 +548,14 @@ function usage () {
     .join(getReporters())
 }
 
+function nycHelp () {
+  fg(node, [nycBin, '--help'])
+}
+
+function nycVersion () {
+  console.log(require('nyc/package.json').version)
+}
+
 function getReporters () {
   var types = require('tap-mocha-reporter').types
   types = types.reduce(function (str, t) {
@@ -436,13 +598,17 @@ function globFiles (files) {
   }, [])
 }
 
-function stdinOnly (options) {
+function makeReporter (options) {
   var TMR = require('tap-mocha-reporter')
+  return new TMR(options.reporter)
+}
+
+function stdinOnly (options) {
   // if we didn't specify any files, then just passthrough
   // to the reporter, so we don't get '/dev/stdin' in the suite list.
   // We have to pause() before piping to switch streams2 into old-mode
   process.stdin.pause()
-  var reporter = new TMR(options.reporter)
+  var reporter = makeReporter(options)
   process.stdin.pipe(reporter)
   process.stdin.resume()
 }
@@ -461,15 +627,28 @@ function saveFails (options, tap) {
     return
   }
   var fails = []
+  var successes = []
   tap.on('result', function (res) {
     // we will continue to re-run todo tests, even though they're
     // not technically "failures".
     if (!res.ok && !res.extra.skip) {
       fails.push(res.extra.file)
+    } else {
+      successes.push(res.extra.file)
     }
   })
 
-  tap.on('end', function () {
+  tap.on('bailout', function (reason) {
+    // add any pending test files to the fails list.
+    fails.push.apply(fails, options.files.filter(function (file) {
+      return successes.indexOf(file) === -1
+    }))
+    save()
+  })
+
+  tap.on('end', save)
+
+  function save () {
     if (!fails.length) {
       try {
         fs.unlinkSync(options.saveFile)
@@ -479,7 +658,7 @@ function saveFails (options, tap) {
         fs.writeFileSync(options.saveFile, fails.join('\n') + '\n')
       } catch (er) {}
     }
-  })
+  }
 }
 
 function runAllFiles (options, saved, tap) {
@@ -495,11 +674,12 @@ function runAllFiles (options, saved, tap) {
     })
   }
 
+  options.files = options.files.filter(function (file) {
+    return saved.indexOf(file) !== -1
+  })
+
   for (var i = 0; i < options.files.length; i++) {
     var file = options.files[i]
-    if (saved.indexOf(file) === -1) {
-      continue
-    }
 
     // Pick up stdin after all the other files are handled.
     if (file === '-') {
@@ -516,14 +696,15 @@ function runAllFiles (options, saved, tap) {
     extra.file = file
 
     if (file.match(/\.js$/)) {
-      tap.spawn(node, options.nodeArgs.concat(file), opt, file, extra)
+      var args = options.nodeArgs.concat(file).concat(options.testArgs)
+      tap.spawn(node, args, opt, file, extra)
     } else if (st.isDirectory()) {
       var dir = fs.readdirSync(file).map(function (f) {
         return file + '/' + f
       })
       options.files.push.apply(options.files, dir)
-    } else if (isExe(st)) {
-      tap.spawn(options.files[i], [], opt, file, extra)
+    } else if (isexe.sync(options.files[i])) {
+      tap.spawn(options.files[i], options.testArgs, opt, file, extra)
     }
   }
 
@@ -541,9 +722,8 @@ function runTests (options) {
 
   // if not -Rtap, then output what the user wants.
   if (options.reporter !== 'tap') {
-    var TMR = require('tap-mocha-reporter')
     tap.unpipe(process.stdout)
-    tap.pipe(new TMR(options.reporter))
+    tap.pipe(makeReporter(options))
   }
 
   saveFails(options, tap)
@@ -552,3 +732,13 @@ function runTests (options) {
 
   tap.end()
 }
+
+function parseRcFile (path) {
+  try {
+    var contents = fs.readFileSync(path, 'utf8')
+    return yaml.safeLoad(contents) || {}
+  } catch(er) {
+    // if no dotfile exists, or invalid yaml, fail gracefully
+    return {}
+  }
+}
diff --git a/bin/usage.txt b/bin/usage.txt
index 8ca733b..f3c2c6f 100644
--- a/bin/usage.txt
+++ b/bin/usage.txt
@@ -9,6 +9,12 @@ To parse TAP data from stdin, specify "-" as a filename.
 Short options are parsed gnu-style, so for example '-bCRspec' would be
 equivalent to '--bail --no-color --reporter=spec'
 
+If the --check-coverage or --coverage-report options are provided, but
+no test files are specified, then a coverage report or coverage check
+will be run on the data from the last test run.
+
+Coverage is never enabled for stdin.
+
 Options:
 
   -c --color                  Use colors (Default for TTY)
@@ -48,10 +54,7 @@ Options:
                               If a COVERALLS_REPO_TOKEN environment
                               variable is set, then coverage is
                               captured by default and sent to the
-                              coveralls.io service. If a CODECOV_TOKEN
-                              environment variable is set, then coverage is
-                              captured by default and sent to the
-                              codecov.io service.
+                              coveralls.io service.
 
   --no-coverage --no-cov      Do not capture coverage information.
                               Note that if nyc is already loaded, then
@@ -62,14 +65,19 @@ Options:
 
                               Default is 'text' when running on the
                               command line, or 'text-lcov' when piping
-                              to coveralls or codecov.
+                              to coveralls.
 
-                              If 'lcov' is used, then the report will
+                              If 'html' is used, then the report will
                               be opened in a web browser after running.
 
                               This can be run on its own at any time
                               after a test run that included coverage.
 
+  --no-coverage-report        Do not output a coverage report.
+
+  --no-browser                Do not open a web browser after
+                              generating an html coverage report.
+
   -t<n> --timeout=<n>         Time out test files after <n> seconds.
                               Defaults to 30, or the value of the
                               TAP_TIMEOUT environment variable.
@@ -78,6 +86,12 @@ Options:
 
   -v --version                show the version of this program
 
+  --node-arg=<arg>            Pass an argument to Node binary in all
+                              child processes.  Run 'node --help' to
+                              see a list of all relevant arguments.
+                              This can be specified multiple times to
+                              pass multiple args to Node.
+
   -gc --expose-gc             Expose the gc() function to Node tests
 
   --debug                     Run JavaScript tests with node --debug
@@ -88,6 +102,84 @@ Options:
 
   --strict                    Run JS tests in 'use strict' mode
 
+  --test-arg=<arg>            Pass an argument to test files spawned
+                              by the tap command line executable.
+                              This can be specified multiple times to
+                              pass multiple args to test scripts.
+
+  --nyc-arg=<arg>             Pass an argument to nyc when running
+                              child processes with coverage enabled.
+                              This can be specified multiple times to
+                              pass multiple args to nyc.
+
+  --check-coverage            Check whether coverage is within
+                              thresholds provided.  Setting this
+                              explicitly will default --coverage to
+                              true.
+
+                              This can be run on its own any time
+                              after a test run that included coverage.
+
+  --branches                  what % of branches must be covered?
+                              Setting this will default both
+                              --check-coverage and --coverage to true.
+                              [default: 0]
+
+  --functions                 what % of functions must be covered?
+                              Setting this explicitly will default both
+                              --check-coverage and --coverage to true.
+                              [default: 0]
+
+  --lines                     what % of lines must be covered?
+                              Setting this explicitly will default both
+                              --check-coverage and --coverage to true.
+                              [default: 90]
+
+  --statements                what % of statements must be covered?
+                              Setting this explicitly will default both
+                              --check-coverage and --coverage to true.
+                              [default: 0]
+
+  --100                       Full coverage, 100%.
+                              Sets branches, statements, functions,
+                              and lines to 100.
+
+  --nyc-help                  Print nyc usage banner.  Useful for
+                              viewing options for --nyc-arg.
+
+  --nyc-version               Print version of nyc used by tap.
+
+  --dump-config               Dump the config options in JSON format.
+
   --                          Stop parsing flags, and treat any additional
                               command line arguments as filenames.
 
+Environment Variables:
+
+  TAP_RCFILE                  A yaml formatted file which can set any
+                              of the above options.  Defaults to
+                              $HOME/.taprc
+
+  TAP_TIMEOUT                 Default value for --timeout option.
+
+  TAP_COLORS                  Set to '1' to force color output, or '0'
+                              to prevent color output.
+
+  TAP_BAIL                    Bail out on the first test failure.
+                              Used internally when '--bailout' is set.
+
+  TAP                         Set to '1' to force standard TAP output,
+                              and suppress any reporters.  Used when
+                              running child tests so that their output
+                              is parseable by the test harness.
+
+  _TAP_COVERAGE_              Reserved for internal use.
+
+Config Files:
+
+You can create a yaml file with any of the options above.  By default,
+the file at ~/.taprc will be loaded, but the TAP_RCFILE environment
+variable can modify this.
+
+Run 'tap --dump-config' for a listing of what can be set in that file.
+Each of the keys corresponds to one of the options above.
diff --git a/example/mocha-example.js b/example/mocha-example.js
new file mode 100644
index 0000000..7d6b96f
--- /dev/null
+++ b/example/mocha-example.js
@@ -0,0 +1,88 @@
+/* standard ignore next */
+describe('parent', function () {
+
+  before('name', function () {
+    console.error('before')
+  });
+
+  after(function () {
+    console.error('after')
+  });
+
+  beforeEach(function () {
+    console.error('beforeEach')
+  });
+
+  afterEach('after each name', function () {
+    console.error('afterEach')
+  });
+
+  it('first', function () {
+    console.error('first it')
+  })
+  it('second', function () {
+    console.error('second it')
+  })
+
+  describe('child 1', function () {
+    console.error('in child 1')
+    before(function () {
+      console.error('before 2')
+    });
+
+    after(function () {
+      console.error('after 2')
+    });
+
+    beforeEach(function () {
+      console.error('beforeEach 2')
+    });
+
+    afterEach(function () {
+      console.error('afterEach 2')
+    });
+
+    it('first x', function () {
+      console.error('first it')
+    })
+    it('second', function (done) {
+      console.error('second it')
+      setTimeout(done)
+    })
+    describe('gc 1', function () {
+      it('first y', function () {
+        console.error('first it')
+      })
+      it('second', function (done) {
+        console.error('second it')
+        setTimeout(done)
+      })
+      it('third', function (done) {
+        console.error('third it')
+        done()
+      })
+    })
+    it('third after gc 1', function () {
+      console.error('second it')
+    })
+  })
+
+  describe('child 2', function () {
+    console.error('in child 2')
+    it('first z', function () {
+      console.error('first it')
+    })
+    it('second', function (done) {
+      console.error('second it')
+      setTimeout(done)
+    })
+    it('third', function (done) {
+      console.error('third it')
+      done()
+    })
+  })
+
+  it('third', function () {
+    console.error('second it')
+  })
+})
diff --git a/lib/assert.js b/lib/assert.js
index 2b20b8b..e0c985e 100644
--- a/lib/assert.js
+++ b/lib/assert.js
@@ -169,7 +169,11 @@ function decorate (t) {
     for (var i = 0; i < arguments.length - 1; i++) {
       var arg = arguments[i]
       if (typeof arg === 'function') {
-        fn = arg
+        if (arg === Error || arg.prototype instanceof Error) {
+          wanted = arg
+        } else if (!fn) {
+          fn = arg
+        }
       } else if (typeof arg === 'string' && arg) {
         m = arg
       } else if (typeof arg === 'object') {
@@ -181,9 +185,10 @@ function decorate (t) {
       }
     }
 
-    for (i in e__) {
+    // Copy local properties of the 'extra' object, like 'skip' etc
+    Object.keys(e__).forEach(function (i) {
       e[i] = e__[i]
-    }
+    })
 
     if (!m) {
       m = fn && fn.name || 'expected to throw'
@@ -198,6 +203,8 @@ function decorate (t) {
           w.name = wanted.name
         }
 
+        // intentionally copying non-local properties, since this
+        // is an Error object, and those are funky.
         for (i in wanted) {
           w[i] = wanted[i]
         }
@@ -208,10 +215,6 @@ function decorate (t) {
         if (e !== wanted) {
           e.wanted = wanted
         }
-      } else if (typeof wanted === 'string') {
-        wanted = {
-          message: wanted
-        }
       }
     }
 
diff --git a/lib/mocha.js b/lib/mocha.js
index c0637ba..c4a1ff1 100644
--- a/lib/mocha.js
+++ b/lib/mocha.js
@@ -7,9 +7,9 @@ var stack = require('./stack.js')
 exports.it = it
 exports.describe = describe
 exports.global = function () {
-  for (var g in exports) {
+  Object.keys(exports).forEach(function (g) {
     global[g] = exports[g]
-  }
+  })
 }
 
 var t = require('./root.js')
diff --git a/lib/root.js b/lib/root.js
index 9622c87..ed4bf24 100644
--- a/lib/root.js
+++ b/lib/root.js
@@ -10,45 +10,84 @@ if (tap._timer && tap._timer.unref) {
 tap._name = 'TAP'
 
 // No sense continuing after bailout!
-tap.on('bailout', function () {
+// allow 1 tick for any other bailout handlers to run first.
+tap.bailout = function (reason) {
+  Test.prototype.bailout.apply(tap, arguments)
   process.exit(1)
-})
+}
+
+// need to autoend if a teardown is added.
+// otherwise we may never see process.on('exit')!
+tap.tearDown = function (fn) {
+  var ret = Test.prototype.tearDown.apply(tap, arguments)
+  tap.autoend()
+  return ret
+}
+
+var didPipe = false
 
 process.on('exit', function (code) {
-  tap.endAll()
+  if (didPipe) {
+    tap.endAll()
+  }
 
   if (!tap._ok && code === 0) {
     process.exit(1)
   }
 })
 
-// need to autoend if a teardown is added.
-// otherwise we may never see process.on('exit')!
-tap.tearDown = function (fn) {
-  var ret = Test.prototype.tearDown.apply(tap, arguments)
-  tap.autoend()
-  return ret
+tap.pipe = function () {
+  didPipe = true
+  tap.push = Test.prototype.push
+  tap.pipe = Test.prototype.pipe
+  process.on('uncaughtException', onUncaught)
+  return Test.prototype.pipe.apply(tap, arguments)
+}
+
+function pipe () {
+  if (didPipe) {
+    return
+  }
+
+  tap.pipe(process.stdout)
 }
 
-tap.pipe(process.stdout)
+tap.push = function push () {
+  pipe()
+  return tap.push.apply(tap, arguments)
+}
 
 tap.mocha = require('./mocha.js')
 tap.mochaGlobals = tap.mocha.global
 tap.Test = Test
 tap.synonyms = require('./synonyms.js')
 
-process.on('uncaughtException', function onUncaught (er) {
+function onUncaught (er) {
   var child = tap
   while (child._currentChild && child._currentChild instanceof Test) {
     child = child._currentChild
   }
   child.threw(er)
+  child._tryResumeEnd()
+}
+
+// remove any and all zombie processes on quit
+function dezombie () {
+  if (tap._currentChild && tap._currentChild.kill) {
+    tap._currentChild.kill('SIGKILL')
+  }
+}
+
+tap.on('end', function () {
+  process.removeListener('uncaughtException', onUncaught)
 })
 
 // SIGTERM means being forcibly killed, almost always by timeout
 var onExit = require('signal-exit')
 onExit(function (code, signal) {
-  if (signal !== 'SIGTERM') {
+  dezombie()
+
+  if (signal !== 'SIGTERM' || !didPipe) {
     return
   }
 
@@ -58,16 +97,25 @@ onExit(function (code, signal) {
     h !== process.stderr
   })
   var requests = process._getActiveRequests()
-  var msg = 'received SIGTERM with pending event queue activity'
-  tap.fail(msg, {
-    requests: requests.map(function (r) {
+
+  // Ignore this because it's really hard to test cover in a way
+  // that isn't inconsistent and unpredictable.
+  /* istanbul ignore next */
+  var extra = {
+    at: null,
+    signal: signal
+  }
+  if (requests.length) {
+    extra.requests = requests.map(function (r) {
       var ret = { type: r.constructor.name }
       if (r.context) {
         ret.context = r.context
       }
       return ret
-    }),
-    handles: handles.map(function (h) {
+    })
+  }
+  if (handles.length) {
+    extra.handles = handles.map(function (h) {
       var ret = { type: h.constructor.name }
       if (h.msecs) {
         ret.msecs = h.msecs
@@ -82,9 +130,23 @@ onExit(function (code, signal) {
         ret.connectionKey = h._connectionKey
       }
       return ret
-    }),
-    at: null
-  })
+    })
+  }
 
-  tap.end()
+  if (!tap._ended) {
+    tap._onTimeout(extra)
+  } else {
+    console.error('possible timeout: SIGTERM received after tap end')
+    if (extra.handles || extra.requests) {
+      delete extra.signal
+      if (!extra.at) {
+        delete extra.at
+      }
+      var yaml = require('js-yaml')
+      console.error('  ---\n  ' +
+               yaml.safeDump(extra).split('\n').join('\n  ').trim() +
+               '\n  ...\n')
+    }
+    process.exit(1)
+  }
 })
diff --git a/lib/stack.js b/lib/stack.js
index 2767267..6e5049b 100644
--- a/lib/stack.js
+++ b/lib/stack.js
@@ -1,252 +1,10 @@
-exports.capture = capture
-exports.captureString = captureString
-exports.at = at
-exports.parseLine = parseLine
-exports.clean = clean
-
-var cwd = process.cwd()
-// var methods =[
-//   'getThis',
-//   'getFunction',
-//   'getTypeName',
-//   'getFunctionName',
-//   'getMethodName',
-//   'getFileName',
-//   'getLineNumber',
-//   'getColumnNumber',
-//   'getEvalOrigin',
-//   'isToplevel',
-//   'isEval',
-//   'isNative',
-//   'isConstructor'
-// ]
-
-function clean (stack) {
-  if (!Array.isArray(stack)) {
-    stack = stack.split('\n')
-  }
-
-  var internals = [
-    /\(domain.js:[0-9]+:[0-9]+\)$/,
-    /\(events.js:[0-9]+:[0-9]+\)$/,
-    /\(node.js:[0-9]+:[0-9]+\)$/,
-    /\(timers.js:[0-9]+:[0-9]+\)$/,
-    /\(module.js:[0-9]+:[0-9]+\)$/,
-    /GeneratorFunctionPrototype.next \(native\)/,
-    /node_modules[\\\/]tap[\\\/](.*?)\.js:[0-9]:[0-9]\)?$/
-  ]
-
-  if (!(/^\s*at /.test(stack[0])) &&
-    (/^\s*at /.test(stack[1]))) {
-    stack = stack.slice(1)
-  }
-
-  stack = stack.map(function (st) {
-    var isInternal = internals.some(function (internal) {
-      return internal.test(st)
-    })
-
-    if (isInternal) {
-      return null
-    }
-
-    return st.trim()
-      .replace(/^\s*at /, '')
-      .replace(cwd + '/', '')
-      .replace(cwd + '\\', '')
-  }).filter(function (st) {
-    return st
-  }).join('\n').trim()
-
-  if (stack) {
-    return stack + '\n'
-  } else {
-    return null
-  }
-}
-
-function captureString (limit, fn) {
-  if (typeof limit === 'function') {
-    fn = limit
-    limit = Infinity
-  }
-  if (!fn) {
-    fn = captureString
-  }
-
-  var limitBefore = Error.stackTraceLimit
-  if (limit) {
-    Error.stackTraceLimit = limit
-  }
-
-  var obj = {}
-
-  Error.captureStackTrace(obj, fn)
-  var stack = obj.stack
-  Error.stackTraceLimit = limitBefore
-
-  return clean(stack)
-}
-
-function capture (limit, fn) {
-  if (typeof limit === 'function') {
-    fn = limit
-    limit = Infinity
-  }
-  if (!fn) {
-    fn = capture
-  }
-  var prepBefore = Error.prepareStackTrace
-  var limitBefore = Error.stackTraceLimit
-
-  Error.prepareStackTrace = function (obj, site) {
-    return site
-  }
-
-  if (limit) {
-    Error.stackTraceLimit = limit
-  }
-
-  var obj = {}
-  Error.captureStackTrace(obj, fn)
-  var stack = obj.stack
-  Error.prepareStackTrace = prepBefore
-  Error.stackTraceLimit = limitBefore
-
-  return stack
-}
-
-function at (fn) {
-  if (!fn) {
-    fn = at
-  }
-
-  var site = capture(1, fn)[0]
-
-  if (!site) {
-    return {}
-  }
-
-  var res = {
-    file: site.getFileName(),
-    line: site.getLineNumber(),
-    column: site.getColumnNumber()
-  }
-
-  if (res.file.indexOf(cwd + '/') === 0 ||
-    res.file.indexOf(cwd + '\\') === 0) {
-    res.file = res.file.substr(cwd.length + 1)
-  }
-
-  if (site.isConstructor()) {
-    res.constructor = true
-  }
-
-  if (site.isEval()) {
-    res.evalOrigin = site.getEvalOrigin()
-  }
-
-  if (site.isNative()) {
-    res.native = true
-  }
-
-  try {
-    var typename = site.getTypeName()
-  } catch (er) {}
-
-  if (typename &&
-    typename !== 'Object' &&
-    typename !== '[object Object]') {
-    res.type = typename
-  }
-
-  var fname = site.getFunctionName()
-  if (fname) {
-    res.function = fname
-  }
-
-  var meth = site.getMethodName()
-  if (meth && fname !== meth) {
-    res.method = meth
-  }
-
-  return res
-}
-
-var re = new RegExp(
-  '^' +
-  // Sometimes we strip out the '    at' because it's noisy
-  '(?:\\s*at )?' +
-  // $1 = ctor if 'new'
-  '(?:(new) )?' +
-  // Object.method [as foo] (, maybe
-  // $2 = function name
-  // $3 = method name
-  '(?:([^\\(\\[]*)(?: \\[as ([^\\]]+)\\])? \\()?' +
-  // (eval at <anonymous> (file.js:1:1),
-  // $4 = eval origin
-  // $5:$6:$7 are eval file/line/col, but not normally reported
-  '(?:eval at ([^ ]+) \\(([^\\)]+):([0-9]+):([0-9]+)\\), )?' +
-  // file:line:col
-  // $8:$9:$10
-  // $11 = 'native' if native
-  '(?:([^\\)]+):([0-9]+):([0-9]+)|(native))' +
-  // maybe close the paren, then end
-  '\\)?$'
-)
-
-function parseLine (line) {
-  var match = line && line.match(re)
-  if (!match) {
-    return null
-  }
-
-  var ctor = match[1] === 'new'
-  var fname = match[2]
-  var meth = match[3]
-  var evalOrigin = match[4]
-  var evalFile = match[5]
-  var evalLine = +match[6]
-  var evalCol = +match[7]
-  var file = match[8]
-  var lnum = match[9]
-  var col = match[10]
-  var native = match[11] === 'native'
-
-  var res = {
-    file: file,
-    line: +lnum,
-    column: +col
-  }
-
-  if (res.file &&
-    (res.file.indexOf(cwd + '/') === 0 ||
-    res.file.indexOf(cwd + '\\') === 0)) {
-    res.file = res.file.substr(cwd.length + 1)
-  }
-
-  if (ctor) {
-    res.constructor = true
-  }
-
-  if (evalOrigin) {
-    res.evalOrigin = evalOrigin
-    res.evalLine = evalLine
-    res.evalColumn = evalCol
-    res.evalFile = evalFile
-  }
-
-  if (native) {
-    res.native = true
-  }
-
-  if (fname) {
-    res.function = fname
-  }
-
-  if (meth && fname !== meth) {
-    res.method = meth
-  }
-
-  return res
-}
+var StackUtils = require('stack-utils')
+
+// Ignore tap if it's a dependency, or anything
+// in this lib folder.
+module.exports = new StackUtils({
+  internals: StackUtils.nodeInternals().concat([
+    /node_modules[\/\\]tap[\/\\]/,
+    new RegExp(__dirname + '\\b', 'i')
+  ])
+})
diff --git a/lib/synonyms.js b/lib/synonyms.js
index 5a8e369..cb0a31d 100644
--- a/lib/synonyms.js
+++ b/lib/synonyms.js
@@ -64,7 +64,7 @@ module.exports = multiword({
 })
 
 function multiword (obj) {
-  for (var i in obj) {
+  Object.keys(obj).forEach(function (i) {
     var list = obj[i]
     var res = [ multiword_(i) ].concat(list.map(multiword_))
     res = res.reduce(function (set, i) {
@@ -72,7 +72,7 @@ function multiword (obj) {
       return set
     }, [])
     obj[i] = res
-  }
+  })
   return obj
 }
 
@@ -86,7 +86,3 @@ function multiword_ (str) {
   }
   return res
 }
-
-if (require.main === module) {
-  console.log(module.exports)
-}
diff --git a/lib/test.js b/lib/test.js
index 76951cf..aec3cee 100644
--- a/lib/test.js
+++ b/lib/test.js
@@ -1,10 +1,22 @@
 module.exports = Test
 
 var Readable = require('stream').Readable
+/* istanbul ignore if */
 if (!Readable || process.version.match(/^v0\.10/)) {
   Readable = require('readable-stream').Readable
 }
 
+var Promise = require('bluebird')
+
+function Deferred () {
+  this.resolve = null
+  this.reject = null
+  this.promise = new Promise(function (resolve, reject) {
+    this.reject = reject
+    this.resolve = resolve
+  }.bind(this))
+}
+
 var util = require('util')
 util.inherits(Test, Readable)
 
@@ -21,6 +33,7 @@ var Parser = require('tap-parser')
 var path = require('path')
 var Module = require('module').Module
 var fs = require('fs')
+var cleanYamlObject = require('clean-yaml-object')
 var binpath = path.resolve(__dirname, '../bin')
 
 function hasOwn (obj, key) {
@@ -34,8 +47,13 @@ function Test (options) {
     return new Test(options)
   }
 
+  this.assertStack = null
+  this.assertAt = null
+
+  this._lineBuf = ''
+  this._deferred = null
   this._autoend = !!options.autoend
-  this._name = ''
+  this._name = options.name || '(unnamed test)'
   this._ok = true
   this._pass = 0
   this._fail = 0
@@ -46,6 +64,7 @@ function Test (options) {
   this._endEmitted = false
   this._explicitEnded = false
   this._multiEndThrew = false
+  this._ranAfterEach = false
 
   if (Object.prototype.hasOwnProperty.call(options, 'bail')) {
     this._bail = !!options.bail
@@ -58,12 +77,16 @@ function Test (options) {
   this._skips = []
   this._todos = []
 
+  this._beforeEach = []
+  this._afterEach = []
+
   this._plan = -1
   this._queue = []
   this._currentChild = null
   this._ending = false
   this._ended = false
-  this._planFinished = false
+  this._mustDeferEnd = false
+  this._deferredEnd = null
 
   this._parent = null
   this._printedVersion = false
@@ -112,6 +135,7 @@ Test.prototype.setTimeout = function (n) {
   if (n === Infinity) {
     if (this._timer) {
       clearTimeout(this._timer)
+      this._timer = null
     }
     this._timeout = 0
     return
@@ -132,7 +156,10 @@ Test.prototype.setTimeout = function (n) {
   }, n)
 }
 
-Test.prototype._onTimeout = function () {
+Test.prototype._onTimeout = function (extra) {
+  clearTimeout(this._timer)
+  this._timer = null
+  // anything that was pending will have to wait.
   var s = this
   while (s._currentChild && (s._currentChild instanceof Test)) {
     s._queue = []
@@ -140,12 +167,15 @@ Test.prototype._onTimeout = function () {
     s = s._currentChild
   }
 
-  // anything that was pending will have to wait.
-  s.fail('timeout!', {
-    expired: this._name,
-    timeout: this._timeout,
-    at: this._calledAt
-  })
+  extra = extra || {}
+  if (this._parent) {
+    extra.expired = this._name
+  }
+  if (this._timeout) {
+    extra.timeout = this._timeout
+  }
+  extra.at = this._calledAt
+  s.fail('timeout!', extra)
   s.end(IMPLICIT)
 
   this.endAll()
@@ -282,10 +312,6 @@ Test.prototype.threw = function threw (er, extra, proxy) {
     er.test = this._name
   }
 
-  if (!extra) {
-    extra = this._extraFromError(er)
-  }
-
   // If we've already ended, then try to pass this up the chain
   // Presumably, eventually the root harness will catch it and
   // deal with it, since that only ends on process exit.
@@ -297,7 +323,16 @@ Test.prototype.threw = function threw (er, extra, proxy) {
     }
   }
 
+  if (!extra) {
+    extra = this._extraFromError(er)
+  }
+
   this.fail(extra.message || er.message, extra)
+
+  // thrown errors cut to the front of the queue.
+  if (this._currentChild && this._queue.length) {
+    this._queue.unshift(this._queue.pop())
+  }
   if (!proxy) {
     this.end(IMPLICIT)
   }
@@ -313,9 +348,9 @@ Test.prototype.pragma = function (set) {
     return
   }
 
-  for (var i in set) {
+  Object.keys(set).forEach(function (i) {
     this.push('pragma ' + (set[i] ? '+' : '-') + i + '\n')
-  }
+  }, this)
 }
 
 Test.prototype.plan = function (n, comment) {
@@ -333,7 +368,7 @@ Test.prototype.plan = function (n, comment) {
   }
 
   if (typeof n !== 'number' || n < 0) {
-    throw new Error('plan must be a number')
+    throw new TypeError('plan must be a number')
   }
 
   // Cannot get any tests after a trailing plan, or a plan of 0
@@ -351,37 +386,61 @@ Test.prototype.plan = function (n, comment) {
   }
 }
 
-Test.prototype.test = function test (name, extra, cb) {
-  if (this._bailedOut) {
-    return
-  }
-
-  if (this._autoendTimer) {
-    clearTimeout(this._autoendTimer)
-  }
-
+Test.prototype._parseTestArgs = function (name, extra, cb) {
   if (typeof name === 'function') {
     cb = name
     name = ''
     extra = {}
+  } else if (typeof name === 'object') {
+    cb = extra
+    extra = name
+    name = ''
   } else if (typeof extra === 'function') {
     cb = extra
     extra = {}
   }
 
+  if (!extra) {
+    extra = {}
+  }
+
   if (!cb) {
-    extra = extra || {}
     extra.todo = true
   } else if (typeof cb !== 'function') {
-    throw new Error('test() requires a callback')
+    throw new TypeError('test() callback must be function if provided')
   }
 
   if (!name && cb && cb.name) {
     name = cb.name
   }
 
+  name = name || '(unnamed test)'
+  return [name, extra, cb]
+}
+
+Test.prototype.test = function test (name, extra, cb, deferred) {
+  if (!deferred) {
+    deferred = new Deferred()
+  }
+
+  if (this._bailedOut) {
+    deferred.resolve(this)
+    return deferred.promise
+  }
+
+  if (this._autoendTimer) {
+    clearTimeout(this._autoendTimer)
+  }
+
+  var args = this._parseTestArgs(name, extra, cb)
+  name = args[0]
+  extra = args[1]
+  cb = args[2]
+
   if (extra.skip || extra.todo) {
-    return this.pass(name, extra)
+    this.pass(name, extra)
+    deferred.resolve(this)
+    return deferred.promise
   }
 
   // will want this captured now in case child fails.
@@ -390,34 +449,94 @@ Test.prototype.test = function test (name, extra, cb) {
   }
 
   if (this._currentChild) {
-    this._queue.push(['test', name, extra, cb])
-    return
+    this._queue.push(['test', name, extra, cb, deferred])
+    return deferred.promise
   }
 
   var child = new Test(extra)
-  name = name || '(unnamed test)'
+  this.comment('Subtest: ' + name)
 
   child._name = name
   child._parent = this
-  if (this._bail) {
+  if (!Object.prototype.hasOwnProperty.call(extra, 'bail')) {
     child._bail = this._bail
   }
 
   this._currentChild = child
+
   var self = this
   childStream(self, child)
+  self._level = child
+  child._deferred = deferred
+  this._runBeforeEach(child, function () {
+    self._runChild(child, name, extra, cb)
+  })
+  return deferred.promise
+}
+
+Test.prototype._runBeforeEach = function (who, cb) {
+  var self = this
+  if (this._parent) {
+    this._parent._runBeforeEach(who, function () {
+      loop(who, self._beforeEach, cb)
+    })
+  } else {
+    loop(who, self._beforeEach, cb)
+  }
+}
+
+Test.prototype._runAfterEach = function (who, cb) {
+  var self = this
+  loop(who, self._afterEach, function () {
+    if (self._parent) {
+      self._parent._runAfterEach(who, cb)
+    } else {
+      cb()
+    }
+  })
+}
+
+function loop (self, arr, cb, i) {
+  if (!i) {
+    i = 0
+  }
+
+  // this weird little engine is to loop if the cb's keep getting
+  // called synchronously, since that's faster and makes shallower
+  // stack traces, but recurse if any of them don't fire this tick
+  var running = false
+  while (i < arr.length && !running) {
+    running = true
+    var sync = true
+    var ret = arr[i].call(self, next)
+    if (ret && typeof ret.then === 'function') {
+      ret.then(next, self.threw)
+    }
+    sync = false
+    i++
+  }
+
+  function next (er) {
+    if (er) {
+      return self.threw(er)
+    } else if (!sync) {
+      return loop(self, arr, cb, i + 1)
+    }
+    running = false
+  }
+
+  if (i >= arr.length && !running) {
+    return cb()
+  }
+}
+
+Test.prototype._runChild = function runChild (child, name, extra, cb) {
+  var self = this
   var results
   child.on('complete', function (res) {
     results = pruneFailures(res)
   })
   child.on('end', function () {
-    if (child._threw && child._ok) {
-      child._ok = false
-      extra.error = child._threw
-      if (extra.error.stack) {
-        extra.error.stack = stack.clean(extra.error.stack)
-      }
-    }
     extra.results = results
     self._currentChild = null
     if (results) {
@@ -433,20 +552,29 @@ Test.prototype.test = function test (name, extra, cb) {
   })
 
   // still need try/catch for synchronous errors
-  self._level = child
-  child.comment('Subtest: ' + name)
   try {
+    child._mustDeferEnd = true
     var cbRet = cb(child)
     if (cbRet && typeof cbRet.then === 'function') {
       // promise
       cbRet.then(function () {
-        child.end(IMPLICIT)
-      }, function (reason) {
-        child.fail(reason.message, child._extraFromError(reason))
-        child.end(IMPLICIT)
+        child._mustDeferEnd = false
+        if (!child._tryResumeEnd()) {
+          child.end(IMPLICIT)
+        }
+      }, function (err) {
+        child._mustDeferEnd = false
+        // raise the error, even if the test is done
+        child.threw(err)
+        child._tryResumeEnd()
       })
+    } else {
+      child._mustDeferEnd = false
+      child._tryResumeEnd()
     }
   } catch (er) {
+    child._mustDeferEnd = false
+    child._tryResumeEnd()
     child.threw(er)
   }
   self._level = self
@@ -466,7 +594,7 @@ Test.prototype.current = function () {
 // This is most often used by the runner when - is passed
 // as an arg, to run a reporter on a previous run.
 // We DO however need to parse it to set the exit failure.
-Test.prototype.stdin = function (name, extra) {
+Test.prototype.stdin = function (name, extra, deferred) {
   if (typeof name === 'object') {
     extra = name
     name = null
@@ -484,32 +612,29 @@ Test.prototype.stdin = function (name, extra) {
     extra.at = stack.at(stdin)
   }
 
+  if (!deferred) {
+    deferred = new Deferred()
+  }
+
   if (this._currentChild) {
-    this._queue.push(['stdin', name, extra])
-    return
+    this._queue.push(['stdin', name, extra, deferred])
+    return deferred.promise
   }
 
   if (extra.skip) {
-    return this.pass(name, extra)
+    this.pass(name, extra)
+    deferred.resolve(this)
+    return deferred.promise
   }
 
   var stdin = process.stdin
+  this.comment('Subtest: ' + name)
   this._currentChild = stdin
   var start = process.hrtime()
-  var parser = new Parser()
+  var parser = new Parser({ preserveWhitespace: true })
   var self = this
 
-  childStream(self, stdin)
-
-  stdin.on('data', function (c) {
-    parser.write(c)
-  })
-
-  stdin.emit('data', '# Subtest: ' + name + '\n')
-
-  stdin.on('end', function () {
-    parser.end()
-  })
+  childStream(self, stdin, parser)
 
   parser.on('complete', function (res) {
     self._currentChild = null
@@ -521,6 +646,7 @@ Test.prototype.stdin = function (name, extra) {
     if (!self._ended) {
       self.push('\n')
     }
+    deferred.resolve(self)
     self._processQueue()
   })
 
@@ -529,6 +655,7 @@ Test.prototype.stdin = function (name, extra) {
   })
 
   process.stdin.resume()
+  return deferred.promise
 }
 
 function pruneFailures (res) {
@@ -552,43 +679,66 @@ function rootBail (self, message) {
   p.bailout(message)
 }
 
-function childStream (self, child) {
+function childStream (self, child, parser) {
   var bailedOut = false
-  var linebuf = ''
-  child.on('data', function (c) {
-    if (bailedOut) {
-      return
+  var emitter = parser || child
+
+  if (parser) {
+    if (self._bail) {
+      bailOnFail(self, child, parser)
     }
-    linebuf += c
-    var lines = linebuf.split('\n')
-    linebuf = lines.pop()
-    lines.forEach(function (line) {
-      if (bailedOut) {
-        return
-      }
-      if (line.match(/^\s*Bail out!/)) {
-        bailedOut = true
-      }
-      if (line.match(/^\s*TAP version \d+$/)) {
-        return
-      }
-      if (line.trim()) {
-        line = '    ' + line
+
+    // The only thing that's actually *required* to be a valid TAP output
+    // is a plan and/or at least one ok/not-ok line.  If we don't get any
+    // of those, then emit a bogus 1..0 so empty TAP streams read as a
+    // skipped test, instead of error.
+    var sawTests = false
+
+    parser.once('plan', function () {
+      sawTests = true
+    })
+
+    parser.once('assert', function () {
+      sawTests = true
+    })
+
+    child.on('data', function (c) {
+      parser.write(c)
+    })
+
+    child.on('end', function () {
+      if (!sawTests) {
+        parser.write('1..0\n')
       }
-      self.push(line + '\n')
+      parser.end()
     })
+  }
+
+  emitter.on('bailout', function (reason) {
+    rootBail(self, reason)
+    bailedOut = true
   })
-  child.on('end', function () {
+
+  emitter.on('line', function (line) {
     if (bailedOut) {
       return
     }
-    if (linebuf) {
-      self.push('    ' + linebuf + '\n')
+
+    if (line.match(/^TAP version \d+\n$/)) {
+      return
+    }
+
+    if (line.trim()) {
+      line = '    ' + line
+    } else {
+      line = '\n'
     }
+
+    self.push(line)
   })
 }
 
-Test.prototype.spawn = function spawnTest (cmd, args, options, name, extra) {
+Test.prototype.spawn = function spawnTest (cmd, args, options, name, extra, deferred) {
   if (typeof args === 'string') {
     args = [ args ]
   }
@@ -605,7 +755,7 @@ Test.prototype.spawn = function spawnTest (cmd, args, options, name, extra) {
     if (cmd === process.execPath) {
       name = args.map(function (a) {
         if (a.indexOf(process.cwd()) === 0) {
-          return './' + a.substr(process.cwd().length + 1)
+          return './' + a.substr(process.cwd().length + 1).replace(/\\/g, '/')
         } else {
           return a
         }
@@ -641,13 +791,19 @@ Test.prototype.spawn = function spawnTest (cmd, args, options, name, extra) {
     clearTimeout(this._autoendTimer)
   }
 
+  if (!deferred) {
+    deferred = new Deferred()
+  }
+
   if (this._currentChild) {
-    this._queue.push(['spawn', cmd, args, options, name, extra])
-    return
+    this._queue.push(['spawn', cmd, args, options, name, extra, deferred])
+    return deferred.promise
   }
 
   if (extra.skip || extra.todo) {
-    return this.pass(name, extra)
+    this.pass(name, extra)
+    deferred.resolve(this)
+    return deferred.promise
   }
 
   if (this._bail || options.bail) {
@@ -661,41 +817,35 @@ Test.prototype.spawn = function spawnTest (cmd, args, options, name, extra) {
   }
 
   var start = process.hrtime()
-  var child = spawn(cmd, args, options)
-  var parser = new Parser()
-  var self = this
-  this._currentChild = child
-
-  childStream(self, child.stdout)
+  try {
+    var child = spawn(cmd, args, options)
+  } catch (er) {
+    this.threw(er)
+    deferred.resolve(this)
+    return deferred.promise
+  }
 
-  child.stdout.on('data', function (c) {
-    parser.write(c)
-  })
+  child.on('error', function (er) {
+    this.threw(er)
 
-  if (this._bail) {
-    bailOnFail(this, child.stdout, parser)
-  }
+    // unhook entirely
+    child.stdout.removeAllListeners('data')
+    child.stdout.removeAllListeners('end')
+    child.removeAllListeners('close')
+    this._currentChild = null
+    deferred.resolve(this)
+    this._processQueue()
 
-  child.stdout.emit('data', '# Subtest: ' + name + '\n')
-  // The only thing that's actually *required* to be a valid TAP output
-  // is a plan and/or at least one ok/not-ok line.  If we don't get any
-  // of those, then emit a bogus 1..0 so we read it as a skip.
-  var sawTests = false
-  parser.once('assert', function () {
-    sawTests = true
-  })
+    // just to be safe, kill the process
+    child.kill('SIGKILL')
+  }.bind(this))
 
-  parser.once('plan', function () {
-    sawTests = true
-  })
+  var parser = new Parser({ preserveWhitespace: true })
+  var self = this
+  this.comment('Subtest: ' + name)
+  this._currentChild = child
 
-  child.stdout.on('end', function () {
-    parser.write('\n')
-    if (!sawTests) {
-      child.stdout.emit('data', '1..0\n')
-    }
-    parser.end()
-  })
+  childStream(self, child.stdout, parser)
 
   var results
   parser.on('complete', function (res) {
@@ -706,11 +856,16 @@ Test.prototype.spawn = function spawnTest (cmd, args, options, name, extra) {
     var timer = setTimeout(function () {
       extra.failure = 'timeout'
       child.kill('SIGTERM')
-      // give it 1 more second to finish up
-      timer = setTimeout(function () {
-        child.stdout.emit('data', '\n\nnot ok - timeout\n\n')
-        child.kill('SIGKILL')
-      }, 1000)
+      if (process.platform === 'win32') {
+        // Sigterm is not catchable on Windows, so report timeout
+        reportTimeout(child.stdout, parser, extra)
+      } else {
+        // give it 1 more second to catch the SIGTERM and finish up
+        timer = setTimeout(function () {
+          reportTimeout(child.stdout, parser, extra)
+          child.kill('SIGKILL')
+        }, 1000)
+      }
     }, extra.timeout)
   }
 
@@ -746,12 +901,12 @@ Test.prototype.spawn = function spawnTest (cmd, args, options, name, extra) {
     if (!self._ended) {
       self.push('\n')
     }
+    deferred.resolve(self)
     self._processQueue()
   })
 
   parser.on('bailout', function (message) {
     child.kill('SIGTERM')
-    rootBail(self, message)
   })
 
   if (child.stderr) {
@@ -759,18 +914,60 @@ Test.prototype.spawn = function spawnTest (cmd, args, options, name, extra) {
       process.stderr.write(c)
     })
   }
+
+  return deferred.promise
+}
+
+// On Unix systems, a SIGTERM can be caught, and the child can
+// thus be trusted to report timeouts appropriately.
+//
+// However, on Windows, SIGTERM cannot be caught, so the child
+// stream won't be able to report what was happening when the
+// test timed out.  On Unix systems, a SIGTERM will only be caught and
+// reported if it's fatal, so if a test does their own stuff with it,
+// we send a SIGKILL a second later.
+//
+// In both cases, this function prevents "plan not found" errors
+// resulting from the output being abruptly cut off.
+function reportTimeout (stream, parser, extra, ind) {
+  ind = ind || ''
+  if (parser.child) {
+    reportTimeout(stream, parser.child, extra, ind + '    ')
+    if (parser.child.bailedOut) {
+      return
+    }
+  }
+  var count = (parser.count || 0) + 1
+  if (parser.current) {
+    count += 1
+  }
+
+  var y = yaml.safeDump(extra).split('\n').map(function (l) {
+    return l.trim() ? '  ' + l : l.trim()
+  }).join('\n')
+  y = ind + '  ---\n' + ind + y.split('\n').join('\n' + ind) + '  ...\n'
+
+  var timeoutfail = '\n' + ind + 'not ok ' + count + ' - timeout\n' + y
+
+  stream.emit('data', timeoutfail)
+  if (parser.planEnd === -1) {
+    stream.emit('data', '\n' + ind + '1..' + count + ' # timeout\n')
+  }
 }
 
-function bailOnFail (self, stream, parser) {
+function bailOnFail (self, stream, parser, root) {
+  root = root || parser
+
   parser.on('child', function (c) {
-    bailOnFail(self, stream, c)
+    bailOnFail(self, stream, c, root)
   })
 
   parser.on('assert', function (res) {
     if (!res.todo && !res.skip && !res.ok) {
-      var ind = new Array(parser.level * 4 + 1).join(' ')
-      parser.buffer = ''
-      stream.emit('data', ind + 'Bail out! # ' + res.name + '\n')
+      var ind = new Array(parser.level + 1).join('    ')
+      var line = ind + 'Bail out! # ' + res.name + '\n'
+      root.buffer = ''
+      root.write(line)
     }
   })
 }
@@ -781,7 +978,15 @@ Test.prototype.done = Test.prototype.end = function end (implicit) {
   }
 
   if (this._currentChild) {
-    this._queue.push(['end'])
+    this._queue.push(['end', implicit])
+    return
+  }
+
+  // Repeated end() calls should be run once any deferred ends have finished.
+  if (this._deferredEnd) {
+    this._deferredEnd.after.push(function () {
+      this.end(implicit)
+    })
     return
   }
 
@@ -798,7 +1003,49 @@ Test.prototype.done = Test.prototype.end = function end (implicit) {
     return
   }
 
-  clearTimeout(this._timer)
+  // If neccessary defer the end until the child callback has returned.
+  if (this._mustDeferEnd) {
+    this._deferredEnd = {
+      implicit: implicit,
+      plan: this._plan === -1 ? this._count : this._plan,
+      after: []
+    }
+    return
+  }
+
+  this._finishEnd(implicit)
+}
+
+Test.prototype._finishEnd = function (implicit) {
+  // If the afterEach function throws, then we can end up back here
+  if (this._ranAfterEach || !this._parent) {
+    this._end(implicit)
+    return
+  }
+
+  this._ranAfterEach = true
+  var self = this
+  this._parent._runAfterEach(this, function () {
+    self._end(implicit)
+  })
+}
+
+Test.prototype._maybeTimeout = function () {
+  if (this._timer) {
+    var dur = process.hrtime(this._startTime)
+    var time = Math.round(dur[0] * 1e6 + dur[1] / 1e3) / 1e3
+    if (time > this._timeout) {
+      this._onTimeout()
+    } else {
+      clearTimeout(this._timer)
+      this._timer = null
+    }
+  }
+}
+
+Test.prototype._end = function (implicit) {
+  this._maybeTimeout()
+
   // Emiting the missing tests can trigger a call to end()
   // guard against that with the 'ending' flag
   this._ending = true
@@ -866,14 +1113,39 @@ Test.prototype.done = Test.prototype.end = function end (implicit) {
 
   this.emit('complete', final)
   if (!this._endEmitted) {
+    if (this._lineBuf) {
+      this.emit('line', this._lineBuf + '\n')
+      this._lineBuf = ''
+    }
+
     this._endEmitted = true
     this.emit('end')
+    if (this._deferred) {
+      this._deferred.resolve(this._parent)
+    }
     if (this._parent) {
       this._parent._processQueue()
     }
   }
 }
 
+Test.prototype._tryResumeEnd = function () {
+  if (!this._deferredEnd) {
+    return false
+  }
+
+  var implicit = this._deferredEnd.implicit
+  var after = this._deferredEnd.after
+  this._deferredEnd = null
+
+  this._finishEnd(implicit)
+  after.forEach(function (fn) {
+    fn.call(this)
+  }, this)
+
+  return true
+}
+
 Test.prototype._processQueue = function () {
   if (this._bailedOut) {
     return
@@ -952,28 +1224,55 @@ Test.prototype.printResult = function printResult (ok, message, extra) {
   extra = extra || {}
 
   var n = this._count + 1
-  if (this._plan !== -1 && n > this._plan) {
+  if (this._plan !== -1 && n > this._plan || this._deferredEnd) {
     // Don't over-report failures.
     // it's already had problems, just ignore this.
-    if (!this._ok) {
+    if (!this._ok || this._deferredEnd && this._deferredEnd.after.length > 0) {
       return
     }
 
-    var er = new Error('test count exceeds plan')
+    var failMessage
+    if (this._explicitEnded) {
+      failMessage = 'test after end() was called'
+    } else {
+      failMessage = 'test count exceeds plan'
+    }
+
+    var er = new Error(failMessage)
     Error.captureStackTrace(er, this._currentAssert || printResult)
     var c = this
     var name = this._name
-    for (c = c._parent; c; c = c.parent) {
+    for (c = c._parent; c && c.parent; c = c.parent) {
       name += ' < ' + (c._name || '(unnamed test)')
     }
     er.test = name
     er.count = n
-    er.plan = this._plan
-    throw er
+    // Only throw the failure once the deferred end has finished.
+    if (this._deferredEnd) {
+      er.plan = this._deferredEnd.plan
+      extra = this._extraFromError(er)
+      this._deferredEnd.after.push(function () {
+        this._parent.threw(er, extra, true)
+      })
+    } else {
+      er.plan = this._plan
+      this.threw(er)
+    }
+    return
   }
 
-  if (this._ended) {
-    throw new Error('test after end() was called')
+  if (this.assertAt) {
+    extra.at = this.assertAt
+    this.assertAt = null
+  }
+
+  if (this.assertStack) {
+    extra.stack = this.assertStack
+    this.assertStack = null
+  }
+
+  if (hasOwn(extra, 'stack') && !hasOwn(extra, 'at')) {
+    extra.at = stack.parseLine(extra.stack.split('\n')[0])
   }
 
   var fn = this._currentAssert
@@ -1054,7 +1353,7 @@ Test.prototype.printResult = function printResult (ok, message, extra) {
   this.push('\n')
 
   // If we're skipping, no need for diags.
-  if (!ok && !extra.skip) {
+  if (!ok && !extra.skip || extra.diagnostic) {
     this.writeDiags(extra)
   }
 
@@ -1069,92 +1368,27 @@ Test.prototype.printResult = function printResult (ok, message, extra) {
   this._maybeAutoend()
 }
 
-function cleanYamlObj (object, isRoot, seen) {
-  if (object === undefined) {
-    return null
-  }
-
-  if (typeof object === 'function') {
-    return object.toString()
+function yamlFilter (propertyName, isRoot, source, target) {
+  if (!isRoot) {
+    return true
   }
 
-  if (Buffer.isBuffer(object)) {
-    return 'Buffer\n' + object.toString('hex').split('').reduce(function (set, c) {
-      if (set.length && set[set.length - 1].length === 1) {
-        set[set.length - 1] += c
-        if (set.length && set.length % 20 === 0) {
-          set[set.length - 1] += '\n'
-        } else {
-          set[set.length - 1] += ' '
-        }
-      } else {
-        set.push(c)
-      }
-      return set
-    }, []).join('').trim()
+  if (propertyName === 'stack') {
+    target.stack = source.stack
+    return false
   }
 
-  if (object && typeof object === 'object') {
-    if (object instanceof RegExp) {
-      return object.toString()
-    }
-
-    var isArray = Array.isArray(object)
-
-    // Fill in any holes.  This means we lose expandos,
-    // but we were gonna lose those anyway.
-    if (isArray) {
-      object = Array.apply(null, object)
-    }
-
-    var set = isArray ? [] : {}
-    var keys = Object.getOwnPropertyNames(object)
-    var stack = null
-    var newObj = keys.reduce(function (set, k) {
-      if (isRoot && (k === 'todo' || k === 'skip')) {
-        return set
-      }
-
-      // magic property!
-      if (isArray && k === 'length') {
-        return set
-      }
-
-      // Don't dump massive EventEmitter and Domain
-      // objects onto the output, that's never friendly.
-      if (object &&
-        typeof object === 'object' &&
-        object instanceof Error &&
-        /^domain/.test(k)) {
-        return set
-      }
-
-      if (isRoot && k === 'at' && !object[k]) {
-        return set
-      }
-
-      if (isRoot && k === 'stack') {
-        stack = object[k]
-        return set
-      }
-
-      if (seen.indexOf(object[k]) !== -1) {
-        set[k] = '[Circular]'
-      } else {
-        set[k] = cleanYamlObj(object[k], false, seen.concat([object]))
-      }
-      return set
-    }, set)
-    if (stack) {
-      newObj.stack = stack
-    }
-    return newObj
-  }
-
-  return object
+  return !(propertyName === 'todo' ||
+  propertyName === 'skip' ||
+  propertyName === 'diagnostic' ||
+  (propertyName === 'at' && !source[propertyName]))
 }
 
 Test.prototype.writeDiags = function (extra) {
+  if (extra.diagnostic === false) {
+    return
+  }
+
   var file = extra.at && extra.at.file && path.resolve(extra.at.file)
   if (file && (file.indexOf(__dirname) === 0 || file.indexOf(binpath) === 0)) {
     delete extra.at
@@ -1175,7 +1409,7 @@ Test.prototype.writeDiags = function (extra) {
   }
 
   // some objects are not suitable for yaml.
-  var obj = cleanYamlObj(extra, true, [])
+  var obj = cleanYamlObject(extra, yamlFilter)
 
   if (obj && typeof obj === 'object' && Object.keys(obj).length) {
     var y = yaml.safeDump(obj).split('\n').map(function (l) {
@@ -1219,7 +1453,11 @@ Test.prototype.fail = function fail (message, extra) {
 
   var ret = true
   if (!extra.todo && !extra.skip) {
-    this._ok = ret = false
+    ret = false
+    // Don't modify test state when there's a deferred end.
+    if (!this._deferredEnd) {
+      this._ok = false
+    }
   }
 
   return ret
@@ -1267,12 +1505,13 @@ Test.prototype.comment = function () {
     return
   }
 
+  var message = util.format.apply(util, arguments)
+
   if (this._currentChild) {
     this._queue.push(['comment', message])
     return
   }
 
-  var message = util.format.apply(util, arguments)
   message = '# ' + message.split(/\r?\n/).join('\n# ')
 
   this.push(message + '\n')
@@ -1283,6 +1522,8 @@ Test.prototype.push = function (c, e) {
     return true
   }
 
+  assert.equal(typeof c, 'string')
+
   // We *always* want data coming out immediately.  Test runners have a
   // very special relationship with zalgo. It's more important to ensure
   // that any console.log() lines that appear in the midst of tests are
@@ -1293,13 +1534,35 @@ Test.prototype.push = function (c, e) {
 
   if (!this._printedVersion && !this._parent) {
     this._printedVersion = true
+    this.emit('line', 'TAP version 13\n')
     Readable.prototype.push.call(this, 'TAP version 13\n')
   }
 
+  this._lineBuf += c
+  var lines = this._lineBuf.split('\n')
+  this._lineBuf = lines.pop()
+  lines.forEach(function (line) {
+    if (!line.trim())
+      line = ''
+
+    this.emit('line', line + '\n')
+  }, this)
+
   return Readable.prototype.push.call(this, c, e)
 }
 
 Test.prototype.bailout = function (message) {
+  if (this._parent && (this._ended || this._bailedOut)) {
+    this._parent.bailout(message)
+    return
+  }
+
+  if (this._currentChild && this._currentChild.bailout) {
+    var child = this._currentChild
+    this._currentChild = null
+    child.bailout(message)
+  }
+
   message = message ? ' # ' + ('' + message).trim() : ''
   message = message.replace(/^( #)+ /, ' # ')
   message = message.replace(/[\r\n]/g, ' ')
@@ -1310,5 +1573,17 @@ Test.prototype.bailout = function (message) {
   this.push(null)
 }
 
+Test.prototype._addMoment = function (which, fn) {
+  this[which].push(fn)
+}
+
+Test.prototype.beforeEach = function (fn) {
+  this._addMoment('_beforeEach', fn)
+}
+
+Test.prototype.afterEach = function (fn) {
+  this._addMoment('_afterEach', fn)
+}
+
 // Add all the asserts
 tapAsserts.decorate(Test.prototype)
diff --git a/package.json b/package.json
index db3ba7c..518d19a 100644
--- a/package.json
+++ b/package.json
@@ -1,8 +1,9 @@
 {
   "name": "tap",
-  "version": "4.0.0",
+  "version": "7.1.2",
   "author": "Isaac Z. Schlueter <i at izs.me> (http://blog.izs.me)",
   "description": "A Test-Anything-Protocol library",
+  "homepage": "http://node-tap.org/",
   "bin": {
     "tap": "bin/run.js"
   },
@@ -11,22 +12,25 @@
     "node": ">=0.8"
   },
   "dependencies": {
-    "codecov.io": "^0.1.6",
+    "bluebird": "^3.3.1",
+    "clean-yaml-object": "^0.1.0",
+    "color-support": "^1.1.0",
     "coveralls": "^2.11.2",
     "deeper": "^2.1.0",
-    "foreground-child": "^1.2.0",
-    "glob": "^6.0.1",
+    "foreground-child": "^1.3.3",
+    "glob": "^7.0.0",
+    "isexe": "^1.0.0",
     "js-yaml": "^3.3.1",
-    "mkdirp": "^0.5.0",
-    "nyc": "^5.1.0",
+    "nyc": "^7.1.0",
     "only-shallow": "^1.0.2",
     "opener": "^1.4.1",
+    "os-homedir": "1.0.1",
     "readable-stream": "^2.0.2",
-    "signal-exit": "^2.0.0",
-    "supports-color": "^1.3.1",
-    "tap-mocha-reporter": "0.0 || 1",
-    "tap-parser": "^1.2.2",
-    "tmatch": "^1.0.2"
+    "signal-exit": "^3.0.0",
+    "stack-utils": "^0.4.0",
+    "tap-mocha-reporter": "^2.0.0",
+    "tap-parser": "^2.2.0",
+    "tmatch": "^2.0.1"
   },
   "keywords": [
     "assert",
@@ -34,15 +38,17 @@
     "tap"
   ],
   "license": "ISC",
-  "repository": "https://github.com/isaacs/node-tap.git",
+  "repository": "https://github.com/tapjs/node-tap.git",
   "scripts": {
     "regen-fixtures": "node scripts/generate-test-test.js test/test/*.js",
-    "test": "node bin/run.js test/*.* --coverage",
-    "posttest": "standard"
+    "test": "node bin/run.js test/*.* --coverage -t3600",
+    "smoke": "node test/test.js \"*.js\" | node bin/run.js -",
+    "posttest": "standard lib test"
   },
   "devDependencies": {
-    "bluebird": "^2.9.34",
-    "standard": "^5.4.1",
+    "mkdirp": "^0.5.1",
+    "rimraf": "^2.5.4",
+    "standard": "^7.1.0",
     "which": "^1.1.1"
   },
   "files": [
diff --git a/scripts/generate-test-test.js b/scripts/generate-test-test.js
index 94c2ece..117491f 100644
--- a/scripts/generate-test-test.js
+++ b/scripts/generate-test-test.js
@@ -53,8 +53,8 @@ function generate (file, bail) {
     output = output.split(node).join('\0N2\0')
     output = output.split(path.basename(node)).join('\0N2\0')
 
-    output = output.split('\0N1\0').join('___/.*(node|iojs)')
-    output = output.split('\0N2\0').join('___/.*(node|iojs)/~~~')
+    output = output.split('\0N1\0').join('___/.*(node|iojs)(\.exe)?')
+    output = output.split('\0N2\0').join('___/.*(node|iojs)(\.exe)?/~~~')
 
     output = yamlishToJson(output)
 
@@ -70,11 +70,20 @@ function generate (file, bail) {
 
 function deStackify (data) {
   return Object.keys(data).sort().reduce(function (res, k) {
+    // a few keys vary across node versions, or based on the machine
+    // or specific run.  Just throw those out.
     if (k === 'stack' && typeof data[k] === 'string') {
       return res
     } else if (k === 'time' && typeof data[k] === 'number') {
       return res
+    } else if (k === 'msecs' && typeof data[k] === 'number') {
+      return res
+    } else if (k === 'function' && typeof data[k] === 'string' && data[k].indexOf('._onTimeout') !== -1) {
+      return res
     } else if (typeof data[k] === 'object' && data[k]) {
+      if (k === 'at') {
+        delete data[k].type
+      }
       res[k] = deStackify(data[k])
     } else {
       res[k] = data[k]
diff --git a/test/asserts.js b/test/asserts.js
index bfe689c..cb470d7 100644
--- a/test/asserts.js
+++ b/test/asserts.js
@@ -82,6 +82,7 @@ test('doesNotThrow expects not to catch', function (t) {
     throw 'x' // eslint-disable-line
   }))
   t.doesNotThrow(function () {})
+  t.doesNotThrow('this does not throw', function () {})
   t.end()
 })
 
@@ -170,6 +171,8 @@ test('match is pattern matching', function (t) {
   t.match('asdf', 'asdf')
   t.match('1', 1)
   t.match(1, '1')
+  t.match([1, 2, 3], [1])
+  t.match([1, 2, 3], [1, Number, '3'])
 
   // XXX debatable?
   t.notOk(tt.match(1, [1]))
diff --git a/test/coverage-checks.js b/test/coverage-checks.js
new file mode 100644
index 0000000..1f14f6f
--- /dev/null
+++ b/test/coverage-checks.js
@@ -0,0 +1,79 @@
+var cp = require('child_process')
+var spawn = cp.spawn
+var exec = cp.execFile
+var node = process.execPath
+var run = require.resolve('../bin/run.js')
+var ok = require.resolve('./test/ok.js')
+var t = require('../')
+
+if (process.version.match(/^v0\.10\./)) {
+  t.plan(0, 'coverage check failure exit does not work on 0.10')
+  process.exit()
+}
+
+t.test('generate some coverage data', function (tt) {
+  spawn(node, [run, ok, '--coverage', '--no-coverage-report'], {
+    stdio: 'ignore'
+  }).on('close', function (code, signal) {
+    tt.equal(code, 0)
+    tt.equal(signal, null)
+    tt.end()
+  })
+})
+
+var passes = [
+  '--lines=1',
+  '--lines=1 --check-coverage',
+  '--lines 1 --statements 1 --functions 1 --branches 1'
+]
+
+var fails = [
+  '--100',
+  '--branches 1', // default lines is 90
+  '--branches=100 --lines=0',
+  '--check-coverage'
+]
+
+var failPattern = new RegExp(
+  'ERROR: Coverage for (lines|branches|statements|functions) ' +
+  '\\([0-9\.]+%\\) ' +
+  'does not meet global threshold \\([0-9]+%\\)'
+)
+var banner =
+  '--------------|----------|----------|----------|' +
+  '----------|----------------|\n' +
+  'File          |  % Stmts | % Branch |  % Funcs |' +
+  '  % Lines |Uncovered Lines |\n' +
+  '--------------|----------|----------|----------|' +
+  '----------|----------------|\n'
+
+t.test('fails', function (t) {
+  t.plan(fails.length)
+  fails.forEach(function (f) {
+    var args = [run].concat(f.split(' '))
+    t.test(f, function (t) {
+      exec(node, args, { env: {} }, function (err, stdout, stderr) {
+        t.ok(err)
+        t.equal(err.code, 1)
+        t.match(stderr, failPattern)
+        t.match(stdout, banner)
+        t.end()
+      })
+    })
+  })
+})
+
+t.test('passes', function (t) {
+  t.plan(passes.length)
+  passes.forEach(function (p) {
+    t.test(p, function (t) {
+      var args = [run].concat(p.split(' '))
+      exec(node, args, { env: {} }, function (err, stdout, stderr) {
+        t.notOk(err)
+        t.notMatch(stderr, failPattern)
+        t.match(stdout, banner)
+        t.end()
+      })
+    })
+  })
+})
diff --git a/test/coverage-export.js b/test/coverage-export.js
index de74dbe..c7b3e3c 100644
--- a/test/coverage-export.js
+++ b/test/coverage-export.js
@@ -19,7 +19,6 @@ t.test('generate some coverage data', function (tt) {
 
 t.test('no coverage export when no tokens in env', function (tt) {
   doTest(tt, {}, function (actual) {
-    tt.notMatch(actual, /Codecov:codecov_token\n/)
     tt.notMatch(actual, /Coveralls:coveralls_token\n/)
     tt.end()
   })
@@ -30,30 +29,6 @@ t.test('coverage to Coveralls', function (tt) {
     COVERALLS_REPO_TOKEN: 'coveralls_token'
   }
   doTest(tt, env, function (actual) {
-    tt.notMatch(actual, /Codecov:codecov_token\n/)
-    tt.match(actual, /Coveralls:coveralls_token\n/)
-    tt.end()
-  })
-})
-
-t.test('coverage to Codecov', function (tt) {
-  var env = {
-    CODECOV_TOKEN: 'codecov_token'
-  }
-  doTest(tt, env, function (actual) {
-    tt.match(actual, /Codecov:codecov_token\n/)
-    tt.notMatch(actual, /Coveralls:coveralls_token\n/)
-    tt.end()
-  })
-})
-
-t.test('coverage to both Codecov and Coveralls', function (tt) {
-  var env = {
-    COVERALLS_REPO_TOKEN: 'coveralls_token',
-    CODECOV_TOKEN: 'codecov_token'
-  }
-  doTest(tt, env, function (actual) {
-    tt.match(actual, /Codecov:codecov_token\n/)
     tt.match(actual, /Coveralls:coveralls_token\n/)
     tt.end()
   })
diff --git a/test/coverage-html-no-browser.js b/test/coverage-html-no-browser.js
new file mode 100644
index 0000000..6f2d171
--- /dev/null
+++ b/test/coverage-html-no-browser.js
@@ -0,0 +1,49 @@
+var cp = require('child_process')
+var spawn = cp.spawn
+var exec = cp.execFile
+var node = process.execPath
+var run = require.resolve('../bin/run.js')
+var ok = require.resolve('./test/ok.js')
+var t = require('../')
+
+var fs = require('fs')
+var mkdirp = require('mkdirp')
+var rimraf = require('rimraf')
+
+var dir = __dirname + '/coverage-html-no-browser'
+var htmlfile = dir + '/coverage/lcov-report/bin/run.js.html'
+
+t.test('setup a working dir', function (t) {
+  mkdirp.sync(dir)
+  t.end()
+})
+
+t.test('generate some coverage data', function (t) {
+  spawn(node, [run, ok, '--coverage', '--no-coverage-report'], {
+    cwd: dir,
+    stdio: 'ignore'
+  }).on('close', function (code, signal) {
+    t.equal(code, 0)
+    t.equal(signal, null)
+    t.end()
+  })
+})
+
+t.test('generate html, but do not open in a browser', function (t) {
+  spawn(node, [run, '--coverage-report=html', '--no-browser'], {
+    cwd: dir,
+    stdio: 'ignore'
+  }).on('close', function (code, signal) {
+    var output = fs.readFileSync(htmlfile, 'utf8')
+    t.match(output, /^<!doctype html>/)
+    t.match(output, /Code coverage report for bin[\\\/]run\.js/)
+    t.equal(code, 0)
+    t.equal(signal, null)
+    t.end()
+  })
+})
+
+t.test('cleanup', function (t) {
+  rimraf.sync(dir)
+  t.end()
+})
diff --git a/test/debug-test.js b/test/debug-test.js
index 68dcf6d..0f6f10c 100644
--- a/test/debug-test.js
+++ b/test/debug-test.js
@@ -10,14 +10,7 @@ var stde = ''
 var done = false
 child.stderr.on('data', function (c) {
   stde += c
-  if (stde.indexOf('ebugger listening on port') !== -1) {
-    t.pass('Should output debugger message')
-    done = true
-    child.kill()
-  }
 })
 child.stderr.on('end', function () {
-  if (!done) {
-    t.fail('did not find debugger message')
-  }
+  t.match(stde, /debugger listening on/i, 'got debugger message')
 })
diff --git a/test/executable-scripts.js b/test/executable-scripts.js
new file mode 100644
index 0000000..830b604
--- /dev/null
+++ b/test/executable-scripts.js
@@ -0,0 +1,61 @@
+var t = require('../')
+
+if (process.platform === 'win32') {
+  t.plan(0, 'shebangs and exe bits are unix only')
+  process.exit(0)
+}
+
+var spawn = require('child_process').spawn
+var path = require('path')
+var fs = require('fs')
+var fixdir = path.resolve(__dirname, 'executable-scripts')
+var executed = path.resolve(fixdir, 'executed.sh')
+var notExecuted = path.resolve(fixdir, 'not-executed.sh')
+var run = require.resolve('../bin/run.js')
+var node = process.execPath
+
+function cleanup () {
+  try { fs.unlinkSync(executed) } catch (e) {}
+  try { fs.unlinkSync(notExecuted) } catch (e) {}
+  try { fs.rmdirSync(fixdir) } catch (e) {}
+}
+
+t.test('setup', function (t) {
+  cleanup()
+  fs.mkdirSync(fixdir, '0755')
+  fs.writeFileSync(
+    executed,
+    '#!/bin/sh\n' +
+    'echo 1..1\n' +
+    'echo ok 1 File with executable bit should be executed\n'
+  )
+  fs.chmodSync(executed, '0755')
+  fs.writeFileSync(
+    notExecuted,
+    '#!/bin/sh\n' +
+    'echo 1..1\n' +
+    'echo not ok 1 File without executable bit should not be run\n' +
+    'exit 1\n'
+  )
+  fs.chmodSync(notExecuted, '0644')
+  t.end()
+})
+
+t.test('run tap bin on shell scripts', function (t) {
+  var args = [run, fixdir, '--bail', '--no-color', '-Rtap']
+  var child = spawn(node, args)
+  var output = ''
+  child.stdout.on('data', function (c) {
+    output += c
+  })
+
+  child.on('close', function (code, signal) {
+    t.equal(code, 0, 'exit 0')
+    t.equal(signal, null, 'no signal exit')
+    t.notMatch(output, /not-executed\.sh/, 'not-executed.sh not seen')
+    t.match(output, /executed\.sh/, 'executed.sh was seen')
+    t.end()
+  })
+})
+
+t.tearDown(cleanup)
diff --git a/test/executed.sh b/test/executed.sh
deleted file mode 100755
index 7300937..0000000
--- a/test/executed.sh
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/bin/sh
-
-echo "1..1"
-echo "ok 1 File with executable bit should be executed"
diff --git a/test/expose-gc-test.js b/test/expose-gc-test.js
index b7b864b..325bff8 100644
--- a/test/expose-gc-test.js
+++ b/test/expose-gc-test.js
@@ -1,34 +1,38 @@
 var tap = require('../')
 var cp = require('child_process')
+var node = process.execPath
+var run = require.resolve('../bin/run.js')
 var path = require('path')
 var dir = path.resolve(__dirname, '..')
+var gcScript = require.resolve('./fixtures/gc-script.js')
 var opt = { cwd: dir }
 
 tap.test("gc test when the gc isn't there", function (t) {
   t.plan(1)
-  cp.exec('bin/run.js test/fixtures/gc-script.js', opt, function (err, stdo, stde) {
+  var args = [run, gcScript]
+  cp.execFile(node, args, opt, function (err, stdo, stde) {
     if (err) throw err
     t.equal(stde, 'false\n')
   })
 })
 
 tap.test('gc test when the gc should be there', function (t) {
-  t.plan(2)
-  t.test('test for gc using --gc', function (t) {
-    t.plan(1)
+  var options = [
+    '--gc',
+    '--expose-gc',
+    '--node-arg=--expose_gc'
+  ]
 
-    cp.exec('bin/run.js --gc test/fixtures/gc-script.js', opt, function (err, stdo, stde) {
-      if (err) throw err
-      t.equal(stde, 'true\n')
-    })
-  })
-
-  t.test('test for gc using --expose-gc', function (t) {
-    t.plan(1)
+  t.plan(options.length)
+  options.forEach(function (option) {
+    t.test('test for gc using ' + option, function (t) {
+      t.plan(1)
 
-    cp.exec('bin/run.js --expose-gc test/fixtures/gc-script.js', opt, function (err, stdo, stde) {
-      if (err) throw err
-      t.equal(stde, 'true\n')
+      var args = [run, option, gcScript]
+      cp.execFile(node, args, opt, function (err, stdo, stde) {
+        if (err) throw err
+        t.equal(stde, 'true\n')
+      })
     })
   })
 })
diff --git a/test/fixtures/dump-args.js b/test/fixtures/dump-args.js
new file mode 100644
index 0000000..7b27b8d
--- /dev/null
+++ b/test/fixtures/dump-args.js
@@ -0,0 +1,8 @@
+var t = require('../..')
+
+function s (k) {
+  return k.map(JSON.stringify).join(' ').trim()
+}
+
+t.pass(process.execPath.replace(/\.exe$/i, '') + ' ' +
+  s(process.execArgv.concat(process.argv.slice(1))))
diff --git a/test/fixtures/invalid-rc-file.yml b/test/fixtures/invalid-rc-file.yml
new file mode 100644
index 0000000..cf53dd4
--- /dev/null
+++ b/test/fixtures/invalid-rc-file.yml
@@ -0,0 +1 @@
+asdf: asdf: asdf
diff --git a/test/fixtures/valid-rc-file.yml b/test/fixtures/valid-rc-file.yml
new file mode 100644
index 0000000..27fd32b
--- /dev/null
+++ b/test/fixtures/valid-rc-file.yml
@@ -0,0 +1,5 @@
+# this is a comment
+timeout: 9999
+coverage: false
+coverageReport: false
+reporter: 'classic'
diff --git a/test/independent-timeouts.js b/test/independent-timeouts.js
index 6408234..3bab548 100644
--- a/test/independent-timeouts.js
+++ b/test/independent-timeouts.js
@@ -4,27 +4,44 @@ var tap = require('../')
 var test = tap.test
 var Test = tap.Test
 
-test('finishes in time', {timeout: 100}, function (t) {
+var isCI = !!process.env.CI
+var long = 100
+var med = 60
+var short = 50
+
+if (process.env.CI) {
+  long *= 10
+  med *= 10
+  short *= 10
+}
+
+if (process.env.APPVEYOR) {
+  long *= 2
+  med *= 2
+  short *= 2
+}
+
+test('finishes in time', {timeout: long}, function (t) {
   setTimeout(function () {
     t.end()
-  }, 60)
+  }, med)
 })
 
-test('finishes in time too', {timeout: 100}, function (t) {
+test('finishes in time too', {timeout: long}, function (t) {
   setTimeout(function () {
     t.end()
-  }, 60)
+  }, med)
 })
 
 test('does not finish in time', function (t) {
   t.plan(1)
   var tt = new Test()
-  tt.test('timeout', { timeout: 50 }, function (ttt) {
+  tt.test('timeout', { timeout: short }, function (ttt) {
     setTimeout(function () {
       ttt.fail('shouldve timed out')
       ttt.end()
       t.notOk(tt._ok)
-    }, 60)
+    }, med)
   })
   tt.end()
 })
diff --git a/test/not-executed.sh b/test/not-executed.sh
deleted file mode 100644
index ad00863..0000000
--- a/test/not-executed.sh
+++ /dev/null
@@ -1,5 +0,0 @@
-#!/bin/sh
-
-echo "1..1"
-echo "not ok 1 File without executable bit should not be run"
-exit 1
diff --git a/test/only-non-tap-output.js b/test/only-non-tap-output.js
index f86d70a..5d3af6e 100644
--- a/test/only-non-tap-output.js
+++ b/test/only-non-tap-output.js
@@ -20,7 +20,7 @@ if (process.argv[2] === 'child') {
     t.has(tt._skips, [
       {
         ok: true,
-        message: './test/only-non-tap-output.js child',
+        message: /\.[\\\/]test[\\\/]only-non-tap-output.js child/,
         extra: {
           at: {},
           results: {},
@@ -31,7 +31,7 @@ if (process.argv[2] === 'child') {
       },
       {
         ok: true,
-        message: './test/only-non-tap-output.js silent',
+        message: /\.[\\\/]test[\\\/]only-non-tap-output.js silent/,
         extra: {
           at: {},
           results: {},
diff --git a/test/rcfiles.js b/test/rcfiles.js
new file mode 100644
index 0000000..976ea5c
--- /dev/null
+++ b/test/rcfiles.js
@@ -0,0 +1,84 @@
+var fs = require('fs')
+var t = require('../')
+var spawn = require('child_process').spawn
+var node = process.execPath
+var run = require.resolve('../bin/run.js')
+
+// fake this one in case you have some weird stuff in ~/.taprc
+var path = require('path')
+process.env.HOME = path.resolve(__dirname, 'fixtures')
+var osHomedir = require('os-homedir')
+
+var defaults = {
+  nodeArgs: [],
+  nycArgs: [],
+  testArgs: [],
+  timeout: 30,
+  color: false,
+  reporter: 'tap',
+  files: [],
+  bail: false,
+  saveFile: null,
+  pipeToService: false,
+  coverageReport: null,
+  browser: true,
+  coverage: false,
+  checkCoverage: false,
+  branches: 0,
+  functions: 0,
+  lines: 0,
+  statements: 0,
+  rcFile: osHomedir() + '/.taprc'
+}
+
+function runTest (rcFile, expect) { return function (t) {
+  var env = {
+    HOME: process.env.HOME,
+    TAP_TIMEOUT: 30
+  }
+
+  if (rcFile) {
+    env.TAP_RCFILE = rcFile
+    expect.rcFile = rcFile
+  }
+
+  var child = spawn(node, [ run, '--dump-config' ], { env: env })
+
+  var out = ''
+  child.stdout.on('data', function (c) {
+    out += c
+  })
+
+  child.stderr.pipe(process.stderr)
+
+  t.plan(3)
+  child.on('close', function (code, sig) {
+    t.equal(code, 0)
+    t.equal(sig, null)
+    Object.keys(defaults).forEach(function (k) {
+      if (!expect.hasOwnProperty(k)) {
+        expect[k] = defaults[k]
+      }
+    })
+
+    t.strictSame(JSON.parse(out), expect)
+  })
+}}
+
+t.test('parseRcFile', function (t) {
+  t.test('nonexistent rc file uses defaults', runTest('./does/not/exist', {}))
+  t.test('invalid rc file uses defaults',
+         runTest('./test/fixtures/invalid-rc-file.yml', {}))
+
+  t.test('parses when valid yaml',
+    runTest('./test/fixtures/valid-rc-file.yml', {
+      timeout: 9999,
+      coverage: false,
+      coverageReport: false,
+      reporter: 'classic'
+    }))
+
+  t.test('uses homedir rcfile when none provided', runTest(null, {}))
+
+  t.end()
+})
diff --git a/test/require-hooks.js b/test/require-hooks.js
index f8b93c0..3013d25 100644
--- a/test/require-hooks.js
+++ b/test/require-hooks.js
@@ -14,7 +14,7 @@ test('compile-to-js require hook', function (t) {
 
     function verifyOutput (err, stdout, stderr) {
       t.ok(!!err, 'Should have failed to run')
-      t.match(stdout, 'file: test/fixtures/using-require-hook.faux',
+      t.match(stdout, /file: .*[\\\/]using-require-hook\.faux/,
         'error happened in the *.faux file')
       t.notMatch(stdout, 'source:',
         'omits the source because the line cannot be resolved')
diff --git a/test/root-no-tests.js b/test/root-no-tests.js
new file mode 100644
index 0000000..c491248
--- /dev/null
+++ b/test/root-no-tests.js
@@ -0,0 +1,90 @@
+// verify that just loading tap doesn't cause it to
+// print out some TAP stuff, unless an actual thing happens.
+var t = require('../')
+var spawn = require('child_process').spawn
+
+switch (process.argv[2]) {
+  case 'child-plan':
+    childPlan()
+    break
+
+  case 'child-pragma':
+    childPragma()
+    break
+
+  case 'child-pipe':
+    childPipe()
+    break
+
+  case 'child-bail':
+    childBail()
+    break
+
+  case 'child-assert':
+    childAssert()
+    break
+
+  case 'child-nothing':
+    childNothing()
+    break
+
+  case undefined:
+    parent()
+    break
+
+  default:
+    throw new Error('oops')
+}
+
+function parent () {
+  t.test('non-zero plan has output', runTest('child-plan', true))
+  t.test('bailout has output', runTest('child-bail', true))
+  t.test('explicit pipe has output', runTest('child-pipe', true))
+  t.test('pragma has output', runTest('child-pragma', true))
+  t.test('assert has output', runTest('child-assert', true))
+  t.test('loading tap has no output', runTest('child-nothing', false))
+}
+
+function childPragma () {
+  t.pragma({ fine: true, ok: false })
+}
+
+function childPipe () {
+  t.pipe(process.stdout)
+}
+
+function childAssert () {
+  t.pass('this is fine')
+}
+
+function childBail () {
+  t.bailout('bo')
+}
+
+function childPlan () {
+  t.plan(2)
+}
+
+function childNothing () {
+  // nothing to see here
+}
+
+function runTest (arg, expectOutput) { return function (t) {
+  var node = process.execPath
+  var args = [__filename, arg]
+  var child = spawn(node, args)
+  var output = ''
+
+  child.stdout.on('data', function (c) {
+    output += c
+  })
+
+  child.on('close', function () {
+    if (expectOutput) {
+      t.notEqual(output.trim(), '')
+    } else {
+      t.equal(output, '')
+    }
+    t.end()
+  })
+}}
diff --git a/test/runner-bailout-args.js b/test/runner-bailout-args.js
new file mode 100644
index 0000000..fbfb0ff
--- /dev/null
+++ b/test/runner-bailout-args.js
@@ -0,0 +1,61 @@
+var t = require('../')
+var cp = require('child_process')
+var spawn = cp.spawn
+var execFile = cp.execFile
+var node = process.execPath
+var run = require.resolve('../bin/run.js')
+var ok = require.resolve('./test/ok.js')
+var notok = require.resolve('./test/not-ok.js')
+var colorRe = new RegExp('\u001b\\[[0-9;]+m') // eslint-disable-line
+var bailRe = new RegExp('^Bail out! # this is not ok$', 'm')
+var okre = new RegExp('test[\\\\/]test[/\\\\]ok\\.js \\.+ 10/10( [0-9\.]+m?s)?$', 'm')
+var notokre = new RegExp('test[\\\\/]test[/\\\\]not-ok\\.js \\.+ 0/[12]( [0-9\.]+m?s)?$', 'm')
+var fs = require('fs')
+var which = require('which')
+
+t.test('bailout args', function (t) {
+  function bailTest (args, env, bail) {
+    return function (t) {
+      var out = ''
+      env = env || {}
+      env.TAP = 0
+      env.TAP_COLORS = 0
+      args = [run, notok, ok, '-C', '-Rclassic'].concat(args || [])
+      var child = spawn(node, args, { env: env })
+      child.stdout.on('data', function (o) {
+        out += o
+      })
+      child.on('close', function (code) {
+        t.equal(code, 1, 'code should be 1')
+        if (bail) {
+          t.match(out, bailRe, 'should show bail out')
+          t.notMatch(out, okre, 'should not run second test')
+        } else {
+          t.notMatch(out, bailRe, 'should not bail out')
+          t.match(out, okre, 'should run second test')
+        }
+        t.end()
+      })
+    }
+  }
+
+  t.test('force bailout with -b or --bail', function (t) {
+    t.test('-b', bailTest(['-b'], {}, true))
+    t.test('-Bb', bailTest(['-Bb'], {}, true))
+    t.test('--bail', bailTest(['--bail'], {}, true))
+    t.test('--no-bail --bail', bailTest(['--no-bail', '--bail'], {}, true))
+    t.test('TAP_BAIL=1', bailTest([], { TAP_BAIL: 1 }, true))
+    t.end()
+  })
+
+  t.test('do not bail out with -B or --no-bail', function (t) {
+    t.test('-B', bailTest(['-B'], {}, false))
+    t.test('-bB', bailTest(['-bB'], {}, false))
+    t.test('--no-bail', bailTest(['--no-bail'], {}, false))
+    t.test('--bail --no-bail', bailTest(['--bail', '--no-bail'], {}, false))
+    t.test('TAP_BAIL=0', bailTest([], { TAP_BAIL: 0 }, false))
+    t.end()
+  })
+
+  t.end()
+})
diff --git a/test/runner-colors.js b/test/runner-colors.js
new file mode 100644
index 0000000..f3aa3d0
--- /dev/null
+++ b/test/runner-colors.js
@@ -0,0 +1,63 @@
+var t = require('../')
+var cp = require('child_process')
+var spawn = cp.spawn
+var execFile = cp.execFile
+var node = process.execPath
+var run = require.resolve('../bin/run.js')
+var ok = require.resolve('./test/ok.js')
+var notok = require.resolve('./test/not-ok.js')
+var colorRe = new RegExp('\u001b\\[[0-9;]+m') // eslint-disable-line
+var bailRe = new RegExp('^Bail out! # this is not ok$', 'm')
+var okre = new RegExp('test[\\\\/]test[/\\\\]ok\\.js \\.+ 10/10( [0-9\.]+m?s)?$', 'm')
+var notokre = new RegExp('test[\\\\/]test[/\\\\]not-ok\\.js \\.+ 0/[12]( [0-9\.]+m?s)?$', 'm')
+var fs = require('fs')
+var which = require('which')
+
+t.test('colors', function (t) {
+  function colorTest (args, env, hasColor) {
+    return function (t) {
+      var out = ''
+      env = env || {}
+      env.TAP = 0
+      args = [run, ok].concat(args || [])
+      var child = spawn(node, args, { env: env })
+      child.stdout.on('data', function (o) {
+        out += o
+      })
+      child.on('close', function (code) {
+        t.equal(code, 0, 'code should be 0')
+        if (hasColor) {
+          t.match(out, colorRe)
+        } else {
+          t.notMatch(out, colorRe)
+        }
+        t.end()
+      })
+    }
+  }
+
+  t.test('no colors by default for non-TTY',
+    colorTest([], {}, false))
+
+  t.test('force colors with -c or --color', function (t) {
+    ;[ '-c', '--color' ].forEach(function (c) {
+      t.test(c, colorTest([c], {}, true))
+    })
+    t.end()
+  })
+
+  t.test('force no colors with -C or --no-color', function (t) {
+    ;[ '-C', '--no-color' ].forEach(function (c) {
+      t.test(c, colorTest([c], {}, false))
+    })
+    t.end()
+  })
+
+  t.test('env.TAP_COLORS', function (t) {
+    t.test('0', colorTest([], { TAP_COLORS: 0 }, false))
+    t.test('1', colorTest([], { TAP_COLORS: 1 }, true))
+    t.end()
+  })
+
+  t.end()
+})
diff --git a/test/runner-dashdash.js b/test/runner-dashdash.js
new file mode 100644
index 0000000..76ae1e1
--- /dev/null
+++ b/test/runner-dashdash.js
@@ -0,0 +1,25 @@
+var t = require('../')
+var cp = require('child_process')
+var spawn = cp.spawn
+var execFile = cp.execFile
+var node = process.execPath
+var run = require.resolve('../bin/run.js')
+var ok = require.resolve('./test/ok.js')
+var notok = require.resolve('./test/not-ok.js')
+var colorRe = new RegExp('\u001b\\[[0-9;]+m') // eslint-disable-line
+var bailRe = new RegExp('^Bail out! # this is not ok$', 'm')
+var okre = new RegExp('test[\\\\/]test[/\\\\]ok\\.js \\.+ 10/10( [0-9\.]+m?s)?$', 'm')
+var notokre = new RegExp('test[\\\\/]test[/\\\\]not-ok\\.js \\.+ 0/[12]( [0-9\.]+m?s)?$', 'm')
+var fs = require('fs')
+var which = require('which')
+
+t.test('separate filename args with --', function (t) {
+  var args = [ run, '--', '-xyz', ok ]
+  var child = spawn(node, args)
+  child.on('close', function (code, signal) {
+    t.equal(code, 0)
+    t.equal(signal, null)
+    t.end()
+  })
+})
+
diff --git a/test/runner-epipe.js b/test/runner-epipe.js
new file mode 100644
index 0000000..5e298ee
--- /dev/null
+++ b/test/runner-epipe.js
@@ -0,0 +1,68 @@
+var t = require('../')
+
+if (process.env.TRAVIS) {
+  t.plan(0, 'skip on travis because this test is very timing dependent')
+  process.exit()
+}
+
+if (process.version.match(/^0\.1[02]\./)) {
+  t.plan(0, 'skip on old versions of node where child proc fds are flaky')
+  process.exit()
+}
+
+var cp = require('child_process')
+var spawn = cp.spawn
+var execFile = cp.execFile
+var node = process.execPath
+var run = require.resolve('../bin/run.js')
+var ok = require.resolve('./test/ok.js')
+var notok = require.resolve('./test/not-ok.js')
+var colorRe = new RegExp('\u001b\\[[0-9;]+m') // eslint-disable-line
+var bailRe = new RegExp('^Bail out! # this is not ok$', 'm')
+var okre = new RegExp('test[\\\\/]test[/\\\\]ok\\.js \\.+ 10/10( [0-9\.]+m?s)?$', 'm')
+var notokre = new RegExp('test[\\\\/]test[/\\\\]not-ok\\.js \\.+ 0/[12]( [0-9\.]+m?s)?$', 'm')
+var fs = require('fs')
+var which = require('which')
+
+t.test('handle EPIPE gracefully', function (t) {
+  if (process.platform === 'win32') {
+    t.plan(0, 'signals on windows are weird')
+    return
+  }
+
+  t.comment('start epipe test')
+  var nodeHead = [
+    'var lines = 0',
+    'var buf = ""',
+    'process.stdin.on("data", function (c) {',
+    '  c = (buf + c).split(/\\n|\\r/)',
+    '  buf += c.pop()',
+    '  for (var i = 0; i < c.length; i++) {',
+    '    console.log(c[i])',
+    '    if (++lines > 5) {',
+    '      process.exit()',
+    '    }',
+    '  }',
+    '})'
+  ].join('\n')
+  var head = spawn(node, ['-e', nodeHead], {
+    stdio: [ 'pipe', 'pipe', 2 ]
+  })
+  head.stdout.on('data', function (c) {
+    t.comment('got output from head bin: %j', c.toString())
+  })
+  t.comment('start child')
+  var child = spawn(node, [run, ok], {
+    stdio: [ 0, head.stdin, 'pipe' ]
+  })
+  var err = ''
+  child.stderr.on('data', function (c) {
+    console.error('got er data', c+'')
+    err += c
+  })
+  child.on('close', function (code, signal) {
+    t.equal(err, '', 'should not spew errors')
+    head.kill('SIGKILL')
+    t.end()
+  })
+})
diff --git a/test/runner-no-cov-args.js b/test/runner-no-cov-args.js
new file mode 100644
index 0000000..e30b1de
--- /dev/null
+++ b/test/runner-no-cov-args.js
@@ -0,0 +1,31 @@
+var t = require('../')
+var cp = require('child_process')
+var spawn = cp.spawn
+var execFile = cp.execFile
+var node = process.execPath
+var run = require.resolve('../bin/run.js')
+var ok = require.resolve('./test/ok.js')
+var notok = require.resolve('./test/not-ok.js')
+var colorRe = new RegExp('\u001b\\[[0-9;]+m') // eslint-disable-line
+var bailRe = new RegExp('^Bail out! # this is not ok$', 'm')
+var okre = new RegExp('test[\\\\/]test[/\\\\]ok\\.js \\.+ 10/10( [0-9\.]+m?s)?$', 'm')
+var notokre = new RegExp('test[\\\\/]test[/\\\\]not-ok\\.js \\.+ 0/[12]( [0-9\.]+m?s)?$', 'm')
+var fs = require('fs')
+var which = require('which')
+
+t.test('--no-cov args', function (t) {
+  // this is a weird test, because we want to cover the
+  // --no-cov case, but still get coverage for it, so
+  // we test with --no-cov --coverage so it switches back.
+  var args = [
+    run, ok,
+    '--no-cov', '--no-coverage',
+    '--cov', '--coverage'
+  ]
+  spawn(node, args).on('close', function (code, signal) {
+    t.equal(code, 0)
+    t.equal(signal, null)
+    t.end()
+  })
+})
+
diff --git a/test/runner-non-zero-exit.js b/test/runner-non-zero-exit.js
new file mode 100644
index 0000000..b1ea721
--- /dev/null
+++ b/test/runner-non-zero-exit.js
@@ -0,0 +1,32 @@
+var t = require('../')
+var cp = require('child_process')
+var spawn = cp.spawn
+var execFile = cp.execFile
+var node = process.execPath
+var run = require.resolve('../bin/run.js')
+var ok = require.resolve('./test/ok.js')
+var notok = require.resolve('./test/not-ok.js')
+var colorRe = new RegExp('\u001b\\[[0-9;]+m') // eslint-disable-line
+var bailRe = new RegExp('^Bail out! # this is not ok$', 'm')
+var okre = new RegExp('test[\\\\/]test[/\\\\]ok\\.js \\.+ 10/10( [0-9\.]+m?s)?$', 'm')
+var notokre = new RegExp('test[\\\\/]test[/\\\\]not-ok\\.js \\.+ 0/[12]( [0-9\.]+m?s)?$', 'm')
+var fs = require('fs')
+var which = require('which')
+
+t.test('non-zero exit is reported as failure', function (t) {
+  var file = require.resolve('./test/ok-exit-fail.js')
+  var args = [run, file, '-Rclassic']
+  var child = spawn(node, args, { env: { TAP: 0 } })
+  var out = ''
+  child.stdout.on('data', function (c) {
+    out += c
+  })
+  child.on('close', function (code, signal) {
+    t.equal(code, 1)
+    t.equal(signal, null)
+    t.match(out, '  not ok ' + file)
+    t.match(out, /\n+\s+exitCode: 1\n/)
+    t.end()
+  })
+})
+
diff --git a/test/runner-nyc-args.js b/test/runner-nyc-args.js
new file mode 100644
index 0000000..8813d83
--- /dev/null
+++ b/test/runner-nyc-args.js
@@ -0,0 +1,42 @@
+var t = require('../')
+var cp = require('child_process')
+var spawn = cp.spawn
+var execFile = cp.execFile
+var node = process.execPath
+var run = require.resolve('../bin/run.js')
+var ok = require.resolve('./test/ok.js')
+var notok = require.resolve('./test/not-ok.js')
+var colorRe = new RegExp('\u001b\\[[0-9;]+m') // eslint-disable-line
+var bailRe = new RegExp('^Bail out! # this is not ok$', 'm')
+var okre = new RegExp('test[\\\\/]test[/\\\\]ok\\.js \\.+ 10/10( [0-9\.]+m?s)?$', 'm')
+var notokre = new RegExp('test[\\\\/]test[/\\\\]not-ok\\.js \\.+ 0/[12]( [0-9\.]+m?s)?$', 'm')
+var fs = require('fs')
+var which = require('which')
+
+t.test('--nyc stuff', function (t) {
+  t.test('--nyc-version', function (t) {
+    var expect = require('nyc/package.json').version + '\n'
+    execFile(node, [run, '--nyc-version'], function (err, stdout, stderr) {
+      if (err) {
+        throw err
+      }
+      t.equal(stderr, '')
+      t.equal(stdout, expect)
+      t.end()
+    })
+  })
+
+  t.test('--nyc-help', function (t) {
+    execFile(node, [run, '--nyc-help'], function (err, stdout, stderr) {
+      if (err) {
+        throw err
+      }
+      t.equal(stderr, '')
+      t.match(stdout, /check-coverage/)
+      t.end()
+    })
+  })
+
+  t.end()
+})
+
diff --git a/test/runner-path-globbing.js b/test/runner-path-globbing.js
new file mode 100644
index 0000000..c1f964c
--- /dev/null
+++ b/test/runner-path-globbing.js
@@ -0,0 +1,29 @@
+var t = require('../')
+var cp = require('child_process')
+var spawn = cp.spawn
+var execFile = cp.execFile
+var node = process.execPath
+var run = require.resolve('../bin/run.js')
+var ok = require.resolve('./test/ok.js')
+var notok = require.resolve('./test/not-ok.js')
+var colorRe = new RegExp('\u001b\\[[0-9;]+m') // eslint-disable-line
+var bailRe = new RegExp('^Bail out! # this is not ok$', 'm')
+var okre = new RegExp('test[\\\\/]test[/\\\\]ok\\.js \\.+ 10/10( [0-9\.]+m?s)?$', 'm')
+var notokre = new RegExp('test[\\\\/]test[/\\\\]not-ok\\.js \\.+ 0/[12]( [0-9\.]+m?s)?$', 'm')
+var fs = require('fs')
+var which = require('which')
+
+t.test('path globbing', function (t) {
+  var glob = 'fixtures/*-success.js'
+  var opt = { env: { TAP: 1 }, cwd: __dirname }
+  var child = spawn(node, [run, glob], opt)
+  var out = ''
+  child.stdout.on('data', function (c) {
+    out += c
+  })
+  child.on('close', function (code) {
+    t.equal(code, 0, 'exits successfully')
+    t.match(out, /trivial-success.js/g, 'includes a matched file')
+    t.end()
+  })
+})
diff --git a/test/runner-read-stdin.js b/test/runner-read-stdin.js
new file mode 100644
index 0000000..76a9daa
--- /dev/null
+++ b/test/runner-read-stdin.js
@@ -0,0 +1,141 @@
+var t = require('../')
+var cp = require('child_process')
+var spawn = cp.spawn
+var execFile = cp.execFile
+var node = process.execPath
+var run = require.resolve('../bin/run.js')
+var ok = require.resolve('./test/ok.js')
+var notok = require.resolve('./test/not-ok.js')
+var colorRe = new RegExp('\u001b\\[[0-9;]+m') // eslint-disable-line
+var bailRe = new RegExp('^Bail out! # this is not ok$', 'm')
+var okre = new RegExp('test[\\\\/]test[/\\\\]ok\\.js \\.+ 10/10( [0-9\.]+m?s)?$', 'm')
+var notokre = new RegExp('test[\\\\/]test[/\\\\]not-ok\\.js \\.+ 0/[12]( [0-9\.]+m?s)?$', 'm')
+var fs = require('fs')
+var which = require('which')
+
+t.test('read from stdin', { skip: process.platform === 'win32' && 'skip stdin test on windows' }, function (t) {
+  function stripTime (s) {
+    return s.split(ok).join('test/test/ok.js')
+      .replace(/[0-9\.]+m?s/g, '{{TIME}}')
+      .replace(/\n\r/g, '\n')
+  }
+
+  var expect = ''
+  t.test('generated expected output', function (t) {
+    var args = [run, ok, '--reporter', 'spec']
+    var child = spawn(node, args, {
+      env: {
+        TAP: 0,
+        TAP_BAIL: 0
+      }
+    })
+    child.stdout.on('data', function (c) {
+      expect += c
+    })
+    child.on('close', function (code, signal) {
+      expect = stripTime(expect)
+      t.equal(code, 0)
+      t.equal(signal, null)
+      t.end()
+    })
+  })
+
+  function pipeTest (t, warn, repArgs) {
+    var args = [run, ok]
+    var err = ''
+    var out = ''
+    var expectError = ''
+    if (warn) {
+      expectError = 'Reading TAP data from stdin (use "-" argument to suppress)\n'
+    }
+
+    var repClosed = false
+    var runClosed = true
+    var repChild = spawn(node, repArgs, {
+      env: {
+        TAP: 0,
+        TAP_BAIL: 0
+      }
+    })
+    var runChild = spawn(node, args, {
+      stdio: [ 0, repChild.stdin, 2 ],
+      env: {
+        TAP_BAIL: 0
+      }
+    })
+    repChild.stderr.on('data', function (c) {
+      err += c
+    })
+    repChild.stdout.on('data', function (c) {
+      out += c
+    })
+    runChild.on('exit', function (code, signal) {
+      t.equal(code, 0)
+      t.equal(signal, null)
+      t.notOk(repClosed)
+      repChild.stdin.end()
+      runClosed = true
+    })
+    repChild.on('close', function (code, signal) {
+      repClosed = true
+      t.ok(runClosed)
+      t.equal(code, 0)
+      t.equal(signal, null)
+      t.equal(err, expectError)
+      t.equal(stripTime(out), expect)
+      t.end()
+    })
+  }
+
+  t.test('warns if - is not an arg', function (t) {
+    pipeTest(t, true, [run, '--reporter=spec'])
+  })
+
+  t.test('does not warn if - is present', function (t) {
+    pipeTest(t, false, [run, '--reporter=spec', '-'])
+  })
+
+  t.test('stdin along with files', function (t) {
+    expect = '\n' +
+      'test/test/ok.js\n' +
+      '  nesting\n' +
+      '    first\n' +
+      '      ✓ true is ok\n' +
+      '      ✓ doag is also okay\n' +
+      '    second\n' +
+      '      ✓ but that is ok\n' +
+      '      ✓ this passes\n' +
+      '      ✓ nested ok\n' +
+      '\n' +
+      '  ✓ this passes\n' +
+      '  ✓ this passes too\n' +
+      '  async kid\n' +
+      '    ✓ timeout\n' +
+      '    ✓ timeout\n' +
+      '\n' +
+      '  ✓ pass after async kid\n' +
+      '/dev/stdin\n' +
+      '  test/test/ok.js\n' +
+      '    nesting\n' +
+      '      first\n' +
+      '        ✓ true is ok\n' +
+      '        ✓ doag is also okay\n' +
+      '      second\n' +
+      '        ✓ but that is ok\n' +
+      '        ✓ this passes\n' +
+      '        ✓ nested ok\n' +
+      '    ✓ this passes\n' +
+      '    ✓ this passes too\n' +
+      '    async kid\n' +
+      '      ✓ timeout\n' +
+      '      ✓ timeout\n' +
+      '    ✓ pass after async kid\n' +
+      '\n' +
+      '\n' +
+      '  20 passing ({{TIME}})\n'
+
+    pipeTest(t, false, [run, '--reporter', 'spec', '-', ok])
+  })
+  t.end()
+})
+
diff --git a/test/runner-save-file.js b/test/runner-save-file.js
new file mode 100644
index 0000000..d09d132
--- /dev/null
+++ b/test/runner-save-file.js
@@ -0,0 +1,106 @@
+var t = require('../')
+var cp = require('child_process')
+var spawn = cp.spawn
+var execFile = cp.execFile
+var node = process.execPath
+var run = require.resolve('../bin/run.js')
+var ok = require.resolve('./test/ok.js')
+var notok = require.resolve('./test/not-ok.js')
+var colorRe = new RegExp('\u001b\\[[0-9;]+m') // eslint-disable-line
+var bailRe = new RegExp('^Bail out! # this is not ok$', 'm')
+var okre = new RegExp('test[\\\\/]test[/\\\\]ok\\.js \\.+ 10/10( [0-9\.]+m?s)?$', 'm')
+var notokre = new RegExp('test[\\\\/]test[/\\\\]not-ok\\.js \\.+ 0/[12]( [0-9\.]+m?s)?$', 'm')
+var fs = require('fs')
+var which = require('which')
+
+t.test('save-file', function (t) {
+  var bailoutOpts = [true, false]
+  t.plan(2)
+
+  bailoutOpts.forEach(function (b) {
+    t.test('bailout=' + b, runTest.bind(null, b))
+  })
+
+  function runTest (bailout, t) {
+    var saveFile = 'runner-save-test-' + process.pid
+    var n = 0
+    function saveFileTest (cb) {
+      var args = [run, '-s' + saveFile, notok, ok, '-CRclassic']
+      if (bailout) {
+        args.push('-b')
+      }
+
+      // also test the expanded versions for added coverage
+      if (++n === 1) {
+        args = [
+          run, '--save', saveFile,
+          notok, ok, '-C', '--reporter', 'classic'
+        ]
+      }
+
+      var child = spawn(node, args, { env: { TAP: 0 }, stdio: [0, 'pipe', 2] })
+      var out = ''
+      child.stdout.on('data', function (c) {
+        out += c
+      })
+      child.on('close', function (code) {
+        cb(code, out)
+      })
+    }
+
+    t.test('run with "ok.js" in save file', function (t) {
+      fs.writeFileSync(saveFile, ok + '\n')
+      saveFileTest(function (code, out) {
+        t.equal(code, 0, 'should exit successfully')
+        t.match(out, okre, 'should run ok.js test')
+        t.notMatch(out, notokre, 'should not run not-ok.js test')
+        t.throws(function () {
+          fs.statSync(saveFile)
+        }, 'should delete save file')
+        t.end()
+      })
+    })
+
+    t.test('run with empty save file', function (t) {
+      saveFileTest(function (code, out) {
+        t.equal(code, 1, 'should fail test')
+        if (!bailout) {
+          t.match(out, okre, 'should run ok.js test')
+        } else {
+          t.notMatch(out, okre, 'should not run ok.js test')
+        }
+        t.match(out, notokre, 'should run not-ok.js test')
+        var saveRes = fs.readFileSync(saveFile, 'utf8')
+        if (!bailout) {
+          t.equal(saveRes, notok + '\n', 'should save not-ok.js')
+        } else {
+          t.equal(saveRes, notok + '\n' + ok + '\n', 'should save both files')
+        }
+
+        t.end()
+      })
+    })
+
+    t.test('run with "not-ok.js" in save file', function (t) {
+      saveFileTest(function (code, out) {
+        t.equal(code, 1, 'should fail test')
+        t.notMatch(out, okre, 'should not run ok.js test')
+        t.match(out, notokre, 'should run not-ok.js test')
+        var saveRes = fs.readFileSync(saveFile, 'utf8')
+        if (!bailout) {
+          t.equal(saveRes, notok + '\n', 'should save not-ok.js')
+        } else {
+          t.equal(saveRes, notok + '\n' + ok + '\n', 'should save both files')
+        }
+        t.end()
+      })
+    })
+
+    t.test('cleanup', function (t) {
+      fs.unlinkSync(saveFile)
+      t.end()
+    })
+
+    t.end()
+  }
+})
diff --git a/test/runner-test-args.js b/test/runner-test-args.js
new file mode 100644
index 0000000..052cc72
--- /dev/null
+++ b/test/runner-test-args.js
@@ -0,0 +1,36 @@
+var t = require('../')
+var cp = require('child_process')
+var spawn = cp.spawn
+var execFile = cp.execFile
+var node = process.execPath
+var run = require.resolve('../bin/run.js')
+var ok = require.resolve('./test/ok.js')
+var notok = require.resolve('./test/not-ok.js')
+var colorRe = new RegExp('\u001b\\[[0-9;]+m') // eslint-disable-line
+var bailRe = new RegExp('^Bail out! # this is not ok$', 'm')
+var okre = new RegExp('test[\\\\/]test[/\\\\]ok\\.js \\.+ 10/10( [0-9\.]+m?s)?$', 'm')
+var notokre = new RegExp('test[\\\\/]test[/\\\\]not-ok\\.js \\.+ 0/[12]( [0-9\.]+m?s)?$', 'm')
+var fs = require('fs')
+var which = require('which')
+
+t.test('--test-args', function (t) {
+  var file = require.resolve('./fixtures/dump-args.js')
+  var args = [
+    run,
+    '--test-arg=--x=y',
+    '--test-arg=-q',
+    '--test-arg=x',
+    file
+  ]
+
+  execFile(node, args, function (err, stdout, stderr) {
+    if (err) {
+      throw err
+    }
+    t.equal(stderr, '')
+    var re = /ok 1 - .*[\/\\]node ".*[\\\/]dump-args.js" "--x=y" "-q" "x"$/im
+    t.match(stdout, re)
+    t.match(stdout, /^ok 1 - .*[\\\/]dump-args.js/m)
+    t.end()
+  })
+})
diff --git a/test/runner-timeout.js b/test/runner-timeout.js
new file mode 100644
index 0000000..ad55d75
--- /dev/null
+++ b/test/runner-timeout.js
@@ -0,0 +1,52 @@
+var t = require('../')
+var cp = require('child_process')
+var spawn = cp.spawn
+var execFile = cp.execFile
+var node = process.execPath
+var run = require.resolve('../bin/run.js')
+var ok = require.resolve('./test/ok.js')
+var notok = require.resolve('./test/not-ok.js')
+var colorRe = new RegExp('\u001b\\[[0-9;]+m') // eslint-disable-line
+var bailRe = new RegExp('^Bail out! # this is not ok$', 'm')
+var okre = new RegExp('test[\\\\/]test[/\\\\]ok\\.js \\.+ 10/10( [0-9\.]+m?s)?$', 'm')
+var notokre = new RegExp('test[\\\\/]test[/\\\\]not-ok\\.js \\.+ 0/[12]( [0-9\.]+m?s)?$', 'm')
+var fs = require('fs')
+var which = require('which')
+
+t.test('-t or --timeout to set timeout', function (t) {
+  var nf = require.resolve('./fixtures/never-finish.js')
+  var args = [run, nf]
+  var dur = '.2'
+  if (global.__coverage__) {
+    dur = '.9'
+  }
+  var timers = [
+    '-t' + dur,
+    ['-t', '0' + dur],
+    '-t=' + dur,
+    '--timeout=' + dur,
+    ['--timeout', dur]
+  ]
+  timers.forEach(function (timer) {
+    t.test([].concat(timer).join(' '), function (t) {
+      var child = spawn(node, args.concat(timer))
+      var out = ''
+      child.stdout.on('data', function (c) {
+        out += c
+      })
+      child.on('close', function (code, signal) {
+        var skip = process.platform === 'win32' && 'SIGTERM on windows is weird'
+        t.equal(code, 1)
+        t.equal(signal, null)
+        t.match(
+          out,
+          /signal: SIG(TERM|KILL)/,
+          { skip: skip }
+        )
+        t.end()
+      })
+    })
+  })
+  t.end()
+})
+
diff --git a/test/runner-unknown-arg.js b/test/runner-unknown-arg.js
new file mode 100644
index 0000000..398966d
--- /dev/null
+++ b/test/runner-unknown-arg.js
@@ -0,0 +1,44 @@
+var t = require('../')
+var cp = require('child_process')
+var spawn = cp.spawn
+var execFile = cp.execFile
+var node = process.execPath
+var run = require.resolve('../bin/run.js')
+var ok = require.resolve('./test/ok.js')
+var notok = require.resolve('./test/not-ok.js')
+var colorRe = new RegExp('\u001b\\[[0-9;]+m') // eslint-disable-line
+var bailRe = new RegExp('^Bail out! # this is not ok$', 'm')
+var okre = new RegExp('test[\\\\/]test[/\\\\]ok\\.js \\.+ 10/10( [0-9\.]+m?s)?$', 'm')
+var notokre = new RegExp('test[\\\\/]test[/\\\\]not-ok\\.js \\.+ 0/[12]( [0-9\.]+m?s)?$', 'm')
+var fs = require('fs')
+var which = require('which')
+
+t.test('unknown arg throws', function (t) {
+  // { arg: unknown }
+  var cases = {
+    '--wtf': '--wtf',
+    '-Bcav': '-a',
+    '-wtf': '-w'
+  }
+  Object.keys(cases).forEach(function (c) {
+    t.test(c, function (t) {
+      badArgTest(t, c, cases[c])
+    })
+  })
+  t.end()
+
+  function badArgTest (t, arg, error) {
+    var expectCode = process.version.match(/^v0\.10/) ? 8 : 1
+    var child = spawn(node, [run, arg])
+    var err = ''
+    child.stderr.on('data', function (c) {
+      err += c
+    })
+    child.on('close', function (code, signal) {
+      t.match(err, new RegExp('Error: Unknown argument: ' + error))
+      t.equal(code, expectCode)
+      t.equal(signal, null)
+      t.end()
+    })
+  }
+})
diff --git a/test/runner-usage.js b/test/runner-usage.js
new file mode 100644
index 0000000..72b0fc5
--- /dev/null
+++ b/test/runner-usage.js
@@ -0,0 +1,55 @@
+var t = require('../')
+var cp = require('child_process')
+var spawn = cp.spawn
+var execFile = cp.execFile
+var node = process.execPath
+var run = require.resolve('../bin/run.js')
+var ok = require.resolve('./test/ok.js')
+var notok = require.resolve('./test/not-ok.js')
+var colorRe = new RegExp('\u001b\\[[0-9;]+m') // eslint-disable-line
+var bailRe = new RegExp('^Bail out! # this is not ok$', 'm')
+var okre = new RegExp('test[\\\\/]test[/\\\\]ok\\.js \\.+ 10/10( [0-9\.]+m?s)?$', 'm')
+var notokre = new RegExp('test[\\\\/]test[/\\\\]not-ok\\.js \\.+ 0/[12]( [0-9\.]+m?s)?$', 'm')
+var fs = require('fs')
+var which = require('which')
+
+t.test('usage', function (t) {
+  function usageTest (t, child, c) {
+    var out = ''
+    var std = c === 0 ? 'stdout' : 'stderr'
+    child[std].on('data', function (o) {
+      out += o
+    })
+    child.on('close', function (code) {
+      t.equal(code, c, 'code should be ' + c)
+      t.match(out, /^Usage:\r?\n/, 'should print usage')
+      t.end()
+    })
+  }
+
+  var stdin = 0
+  if (!process.stdin.isTTY) {
+    try {
+      stdin = fs.openSync('/dev/tty', 'r')
+    } catch (er) {
+      stdin = null
+    }
+  }
+  var opt = { skip: stdin === null ? 'could not load tty' : false }
+  t.test('shows usage when stdin is a tty', opt, function (t) {
+    var child = spawn(node, [run], { stdio: [ stdin, 'pipe', 'pipe' ] })
+    usageTest(t, child, 1)
+  })
+
+  t.test('shows usage with -h (even with file)', function (t) {
+    var child = spawn(node, [run, '-h', __filename])
+    usageTest(t, child, 0)
+  })
+
+  t.test('shows usage with --help (even with file)', function (t) {
+    var child = spawn(node, [run, '--help', __filename])
+    usageTest(t, child, 0)
+  })
+
+  t.end()
+})
diff --git a/test/runner-version.js b/test/runner-version.js
new file mode 100644
index 0000000..5ffad8a
--- /dev/null
+++ b/test/runner-version.js
@@ -0,0 +1,37 @@
+var t = require('../')
+var cp = require('child_process')
+var spawn = cp.spawn
+var execFile = cp.execFile
+var node = process.execPath
+var run = require.resolve('../bin/run.js')
+var ok = require.resolve('./test/ok.js')
+var notok = require.resolve('./test/not-ok.js')
+var colorRe = new RegExp('\u001b\\[[0-9;]+m') // eslint-disable-line
+var bailRe = new RegExp('^Bail out! # this is not ok$', 'm')
+var okre = new RegExp('test[\\\\/]test[/\\\\]ok\\.js \\.+ 10/10( [0-9\.]+m?s)?$', 'm')
+var notokre = new RegExp('test[\\\\/]test[/\\\\]not-ok\\.js \\.+ 0/[12]( [0-9\.]+m?s)?$', 'm')
+var fs = require('fs')
+var which = require('which')
+
+t.test('version', function (t) {
+  var version = require('../package.json').version
+  function versionTest (arg) {
+    return function (t) {
+      var child = spawn(node, [run].concat(arg))
+      var out = ''
+      child.stdout.on('data', function (o) {
+        out += o
+      })
+      child.on('close', function (code, signal) {
+        t.equal(code, 0)
+        t.equal(signal, null)
+        t.equal(out, version + '\n')
+        t.end()
+      })
+    }
+  }
+  t.test('-v', versionTest('-v'))
+  t.test('--version', versionTest('--version'))
+  t.test('--version', versionTest(['--version', __filename]))
+  t.end()
+})
diff --git a/test/runner-warn-covering-stdin.js b/test/runner-warn-covering-stdin.js
new file mode 100644
index 0000000..664977c
--- /dev/null
+++ b/test/runner-warn-covering-stdin.js
@@ -0,0 +1,34 @@
+var t = require('../')
+var cp = require('child_process')
+var spawn = cp.spawn
+var execFile = cp.execFile
+var node = process.execPath
+var run = require.resolve('../bin/run.js')
+var ok = require.resolve('./test/ok.js')
+var notok = require.resolve('./test/not-ok.js')
+var colorRe = new RegExp('\u001b\\[[0-9;]+m') // eslint-disable-line
+var bailRe = new RegExp('^Bail out! # this is not ok$', 'm')
+var okre = new RegExp('test[\\\\/]test[/\\\\]ok\\.js \\.+ 10/10( [0-9\.]+m?s)?$', 'm')
+var notokre = new RegExp('test[\\\\/]test[/\\\\]not-ok\\.js \\.+ 0/[12]( [0-9\.]+m?s)?$', 'm')
+var fs = require('fs')
+var which = require('which')
+
+t.test('warns when trying to cover stdin', function (t) {
+  var args = [run, '-', '--coverage']
+  var out = ''
+  var err = ''
+  var expect = 'Coverage disabled because stdin cannot be instrumented\n'
+  var child = spawn(node, args)
+  child.stdout.on('data', function (c) {
+    out += c
+  })
+  child.stderr.on('data', function (c) {
+    err += c
+  })
+  child.on('close', function (code, signal) {
+    t.equal(err, expect)
+    t.end()
+  })
+  child.stdin.end()
+})
+
diff --git a/test/runner.js b/test/runner.js
deleted file mode 100644
index f518cba..0000000
--- a/test/runner.js
+++ /dev/null
@@ -1,510 +0,0 @@
-var t = require('../')
-var spawn = require('child_process').spawn
-var node = process.execPath
-var run = require.resolve('../bin/run.js')
-var ok = require.resolve('./test/ok.js')
-var notok = require.resolve('./test/not-ok.js')
-var colorRe = new RegExp('\u001b\\[[0-9;]+m') // eslint-disable-line
-var bailRe = new RegExp('^Bail out! # this is not ok$', 'm')
-var okre = new RegExp('test[\\\\/]test[/\\\\]ok\\.js \\.+ 10/10( [0-9\.]+m?s)?$', 'm')
-var notokre = new RegExp('test[\\\\/]test[/\\\\]not-ok\\.js \\.+ 0/1( [0-9\.]+m?s)?$', 'm')
-var fs = require('fs')
-var which = require('which')
-
-t.test('usage', function (t) {
-  function usageTest (t, child, c) {
-    var out = ''
-    var std = c === 0 ? 'stdout' : 'stderr'
-    child[std].on('data', function (o) {
-      out += o
-    })
-    child.on('close', function (code) {
-      t.equal(code, c, 'code should be ' + c)
-      t.match(out, /^Usage:\n/, 'should print usage')
-      t.end()
-    })
-  }
-
-  var stdin = 0
-  if (!process.stdin.isTTY) {
-    try {
-      stdin = fs.openSync('/dev/tty', 'r')
-    } catch (er) {
-      stdin = null
-    }
-  }
-  var opt = { skip: stdin === null ? 'could not load tty' : false }
-  t.test('shows usage when stdin is a tty', opt, function (t) {
-    var child = spawn(node, [run], { stdio: [ stdin, 'pipe', 'pipe' ] })
-    usageTest(t, child, 1)
-  })
-
-  t.test('shows usage with -h (even with file)', function (t) {
-    var child = spawn(node, [run, '-h', __filename])
-    usageTest(t, child, 0)
-  })
-
-  t.test('shows usage with --help (even with file)', function (t) {
-    var child = spawn(node, [run, '--help', __filename])
-    usageTest(t, child, 0)
-  })
-
-  t.end()
-})
-
-t.test('colors', function (t) {
-  function colorTest (args, env, hasColor) {
-    return function (t) {
-      var out = ''
-      env = env || {}
-      env.TAP = 0
-      args = [run, ok].concat(args || [])
-      var child = spawn(node, args, { env: env })
-      child.stdout.on('data', function (o) {
-        out += o
-      })
-      child.on('close', function (code) {
-        t.equal(code, 0, 'code should be 0')
-        if (hasColor) {
-          t.match(out, colorRe)
-        } else {
-          t.notMatch(out, colorRe)
-        }
-        t.end()
-      })
-    }
-  }
-
-  t.test('no colors by default for non-TTY',
-    colorTest([], {}, false))
-
-  t.test('force colors with -c or --color', function (t) {
-    ;[ '-c', '--color' ].forEach(function (c) {
-      t.test(c, colorTest([c], {}, true))
-    })
-    t.end()
-  })
-
-  t.test('force no colors with -C or --no-color', function (t) {
-    ;[ '-C', '--no-color' ].forEach(function (c) {
-      t.test(c, colorTest([c], {}, false))
-    })
-    t.end()
-  })
-
-  t.test('env.TAP_COLORS', function (t) {
-    t.test('0', colorTest([], { TAP_COLORS: 0 }, false))
-    t.test('1', colorTest([], { TAP_COLORS: 1 }, true))
-    t.end()
-  })
-
-  t.end()
-})
-
-t.test('bailout args', function (t) {
-  function bailTest (args, env, bail) {
-    return function (t) {
-      var out = ''
-      env = env || {}
-      env.TAP = 0
-      env.TAP_COLORS = 0
-      args = [run, notok, ok, '-C', '-Rclassic'].concat(args || [])
-      var child = spawn(node, args, { env: env })
-      child.stdout.on('data', function (o) {
-        out += o
-      })
-      child.on('close', function (code) {
-        t.equal(code, 1, 'code should be 1')
-        if (bail) {
-          t.match(out, bailRe, 'should show bail out')
-          t.notMatch(out, okre, 'should not run second test')
-        } else {
-          t.notMatch(out, bailRe, 'should not bail out')
-          t.match(out, okre, 'should run second test')
-        }
-        t.end()
-      })
-    }
-  }
-
-  t.test('force bailout with -b or --bail', function (t) {
-    t.test('-b', bailTest(['-b'], {}, true))
-    t.test('-Bb', bailTest(['-Bb'], {}, true))
-    t.test('--bail', bailTest(['--bail'], {}, true))
-    t.test('--no-bail --bail', bailTest(['--no-bail', '--bail'], {}, true))
-    t.test('TAP_BAIL=1', bailTest([], { TAP_BAIL: 1 }, true))
-    t.end()
-  })
-
-  t.test('do not bail out with -B or --no-bail', function (t) {
-    t.test('-B', bailTest(['-B'], {}, false))
-    t.test('-bB', bailTest(['-bB'], {}, false))
-    t.test('--no-bail', bailTest(['--no-bail'], {}, false))
-    t.test('--bail --no-bail', bailTest(['--bail', '--no-bail'], {}, false))
-    t.test('TAP_BAIL=0', bailTest([], { TAP_BAIL: 0 }, false))
-    t.end()
-  })
-
-  t.end()
-})
-
-t.test('path globbing', function (t) {
-  var glob = 'fixtures/*-success.js'
-  var opt = { env: { TAP: 1 }, cwd: __dirname }
-  var child = spawn(node, [run, glob], opt)
-  var out = ''
-  child.stdout.on('data', function (c) {
-    out += c
-  })
-  child.on('close', function (code) {
-    t.equal(code, 0, 'exits successfully')
-    t.match(out, /trivial-success.js/g, 'includes a matched file')
-    t.end()
-  })
-})
-
-t.test('save-file', function (t) {
-  var saveFile = 'runner-save-test-' + process.pid
-  var n = 0
-  function saveFileTest (cb) {
-    var args = [run, '-s' + saveFile, ok, notok, '-CRclassic']
-    // also test the expanded versions for added coverage
-    if (++n === 1) {
-      args = [
-        run, '--save', saveFile,
-        ok, notok, '-C', '--reporter', 'classic'
-      ]
-    }
-
-    var child = spawn(node, args, { env: { TAP: 0 }, stdio: [0, 'pipe', 2] })
-    var out = ''
-    child.stdout.on('data', function (c) {
-      out += c
-    })
-    child.on('close', function (code) {
-      cb(code, out)
-    })
-  }
-
-  t.test('run with "ok.js" in save file', function (t) {
-    fs.writeFileSync(saveFile, ok + '\n')
-    saveFileTest(function (code, out) {
-      t.equal(code, 0, 'should exit successfully')
-      t.match(out, okre, 'should run ok.js test')
-      t.notMatch(out, notokre, 'should not run not-ok.js test')
-      t.throws(function () {
-        fs.statSync(saveFile)
-      }, 'should delete save file')
-      t.end()
-    })
-  })
-
-  t.test('run with empty save file', function (t) {
-    saveFileTest(function (code, out) {
-      t.equal(code, 1, 'should fail test')
-      t.match(out, okre, 'should run ok.js test')
-      t.match(out, notokre, 'should run not-ok.js test')
-      t.equal(fs.readFileSync(saveFile, 'utf8'), notok + '\n',
-        'should save not-ok.js')
-      t.end()
-    })
-  })
-
-  t.test('run with "not-ok.js" in save file', function (t) {
-    saveFileTest(function (code, out) {
-      t.equal(code, 1, 'should fail test')
-      t.notMatch(out, okre, 'should not run ok.js test')
-      t.match(out, notokre, 'should run not-ok.js test')
-      t.equal(fs.readFileSync(saveFile, 'utf8'), notok + '\n',
-        'should save not-ok.js')
-      t.end()
-    })
-  })
-
-  t.test('cleanup', function (t) {
-    fs.unlinkSync(saveFile)
-    t.end()
-  })
-
-  t.end()
-})
-
-t.test('version', function (t) {
-  var version = require('../package.json').version
-  function versionTest (arg) {
-    return function (t) {
-      var child = spawn(node, [run].concat(arg))
-      var out = ''
-      child.stdout.on('data', function (o) {
-        out += o
-      })
-      child.on('close', function (code, signal) {
-        t.equal(code, 0)
-        t.equal(signal, null)
-        t.equal(out, version + '\n')
-        t.end()
-      })
-    }
-  }
-  t.test('-v', versionTest('-v'))
-  t.test('--version', versionTest('--version'))
-  t.test('--version', versionTest(['--version', __filename]))
-  t.end()
-})
-
-;(function () {
-  try {
-    var headBin = which.sync('head')
-  } catch (er) {}
-
-  var skip = false
-  if (!headBin) {
-    skip = 'head program not available'
-  }
-
-  t.test('handle EPIPE gracefully', { skip: skip }, function (t) {
-    var head = spawn(headBin, ['-5'])
-    var child = spawn(node, [run, ok], {
-      stdio: [ 0, head.stdin, 'pipe' ]
-    })
-    var err = ''
-    child.stderr.on('data', function (c) {
-      err += c
-    })
-    child.on('close', function (code, signal) {
-      t.equal(err, '', 'should not spew errors')
-      t.end()
-    })
-  })
-})()
-
-t.test('--no-cov args', function (t) {
-  // this is a weird test, because we want to cover the
-  // --no-cov case, but still get coverage for it, so
-  // we test with --no-cov --coverage so it switches back.
-  var args = [
-    run, ok,
-    '--no-cov', '--no-coverage',
-    '--cov', '--coverage'
-  ]
-  spawn(node, args).on('close', function (code, signal) {
-    t.equal(code, 0)
-    t.equal(signal, null)
-    t.end()
-  })
-})
-
-t.test('unknown arg throws', function (t) {
-  // { arg: unknown }
-  var cases = {
-    '--wtf': '--wtf',
-    '-Bcav': '-a',
-    '-wtf': '-w'
-  }
-  Object.keys(cases).forEach(function (c) {
-    t.test(c, function (t) {
-      badArgTest(t, c, cases[c])
-    })
-  })
-  t.end()
-
-  function badArgTest (t, arg, error) {
-    var expectCode = process.version.match(/^v0\.10/) ? 8 : 1
-    var child = spawn(node, [run, arg])
-    var err = ''
-    child.stderr.on('data', function (c) {
-      err += c
-    })
-    child.on('close', function (code, signal) {
-      t.match(err, new RegExp('Error: Unknown argument: ' + error))
-      t.equal(code, expectCode)
-      t.equal(signal, null)
-      t.end()
-    })
-  }
-})
-
-t.test('read from stdin', function (t) {
-  function stripTime (s) {
-    return s.split(ok).join('test/test/ok.js')
-      .replace(/[0-9\.]+m?s/g, '{{TIME}}')
-      .replace(/\n\r/g, '\n')
-  }
-
-  var expect = ''
-  t.test('generated expected output', function (t) {
-    var args = [run, ok, '--reporter', 'spec']
-    var child = spawn(node, args, {
-      env: {
-        TAP: 0,
-        TAP_BAIL: 0
-      }
-    })
-    child.stdout.on('data', function (c) {
-      expect += c
-    })
-    child.on('close', function (code, signal) {
-      expect = stripTime(expect)
-      t.equal(code, 0)
-      t.equal(signal, null)
-      t.end()
-    })
-  })
-
-  function pipeTest (t, warn, repArgs) {
-    var args = [run, ok]
-    var err = ''
-    var out = ''
-    var expectError = ''
-    if (warn) {
-      expectError = 'Reading TAP data from stdin (use "-" argument to suppress)\n'
-    }
-
-    var repClosed = false
-    var runClosed = true
-    var repChild = spawn(node, repArgs, {
-      env: {
-        TAP: 0,
-        TAP_BAIL: 0
-      }
-    })
-    var runChild = spawn(node, args, {
-      stdio: [ 0, repChild.stdin, 2 ],
-      env: {
-        TAP_BAIL: 0
-      }
-    })
-    repChild.stderr.on('data', function (c) {
-      err += c
-    })
-    repChild.stdout.on('data', function (c) {
-      out += c
-    })
-    runChild.on('exit', function (code, signal) {
-      t.equal(code, 0)
-      t.equal(signal, null)
-      t.notOk(repClosed)
-      repChild.stdin.end()
-      runClosed = true
-    })
-    repChild.on('close', function (code, signal) {
-      repClosed = true
-      t.ok(runClosed)
-      t.equal(code, 0)
-      t.equal(signal, null)
-      t.equal(err, expectError)
-      t.equal(stripTime(out), expect)
-      t.end()
-    })
-  }
-
-  t.test('warns if - is not an arg', function (t) {
-    pipeTest(t, true, [run, '--reporter=spec'])
-  })
-
-  t.test('does not warn if - is present', function (t) {
-    pipeTest(t, false, [run, '--reporter=spec', '-'])
-  })
-
-  t.test('stdin along with files', function (t) {
-    expect = '\n' +
-      'test/test/ok.js\n' +
-      '  nesting\n' +
-      '    first\n' +
-      '      ✓ true is ok\n' +
-      '      ✓ doag is also okay\n' +
-      '    second\n' +
-      '      ✓ but that is ok\n' +
-      '      ✓ this passes\n' +
-      '      ✓ nested ok\n' +
-      '\n' +
-      '  ✓ this passes\n' +
-      '  ✓ this passes too\n' +
-      '  async kid\n' +
-      '    ✓ timeout\n' +
-      '    ✓ timeout\n' +
-      '\n' +
-      '  ✓ pass after async kid\n' +
-      '/dev/stdin\n' +
-      '  test/test/ok.js\n' +
-      '    nesting\n' +
-      '      first\n' +
-      '        ✓ true is ok\n' +
-      '        ✓ doag is also okay\n' +
-      '      second\n' +
-      '        ✓ but that is ok\n' +
-      '        ✓ this passes\n' +
-      '        ✓ nested ok\n' +
-      '    ✓ this passes\n' +
-      '    ✓ this passes too\n' +
-      '    async kid\n' +
-      '      ✓ timeout\n' +
-      '      ✓ timeout\n' +
-      '    ✓ pass after async kid\n' +
-      '\n' +
-      '\n' +
-      '  20 passing ({{TIME}})\n'
-
-    pipeTest(t, false, [run, '--reporter', 'spec', '-', ok])
-  })
-  t.end()
-})
-
-t.test('separate filename args with --', function (t) {
-  var args = [ run, '--', '-xyz', ok ]
-  var child = spawn(node, args)
-  child.on('close', function (code, signal) {
-    t.equal(code, 0)
-    t.equal(signal, null)
-    t.end()
-  })
-})
-
-t.test('-t or --timeout to set timeout', function (t) {
-  var nf = require.resolve('./fixtures/never-finish.js')
-  var args = [run, nf]
-  var dur = '.2'
-  if (global.__coverage__) {
-    dur = '.9'
-  }
-  var timers = [
-    '-t' + dur,
-    ['-t', '0' + dur],
-    '-t=' + dur,
-    '--timeout=' + dur,
-    ['--timeout', dur]
-  ]
-  timers.forEach(function (timer) {
-    t.test([].concat(timer).join(' '), function (t) {
-      var child = spawn(node, args.concat(timer))
-      var out = ''
-      child.stdout.on('data', function (c) {
-        out += c
-      })
-      child.on('close', function (code, signal) {
-        t.equal(code, 1)
-        t.equal(signal, null)
-        t.match(out, /received SIGTERM with pending event queue activity/)
-        t.end()
-      })
-    })
-  })
-  t.end()
-})
-
-t.test('non-zero exit is reported as failure', function (t) {
-  var file = require.resolve('./test/ok-exit-fail.js')
-  var args = [run, file, '-Rclassic']
-  var child = spawn(node, args, { env: { TAP: 0 } })
-  var out = ''
-  child.stdout.on('data', function (c) {
-    out += c
-  })
-  child.on('close', function (code, signal) {
-    t.equal(code, 1)
-    t.equal(signal, null)
-    t.match(out, '  not ok ' + file)
-    t.match(out, /\n+\s+exitCode: 1\n/)
-    t.end()
-  })
-})
diff --git a/test/segv.c b/test/segv.c
new file mode 100644
index 0000000..626071e
--- /dev/null
+++ b/test/segv.c
@@ -0,0 +1,4 @@
+int main (void) {
+   char *s = "hello world";
+   *s = 'H';
+}
diff --git a/test/segv.js b/test/segv.js
index ab2499e..9508791 100644
--- a/test/segv.js
+++ b/test/segv.js
@@ -1,4 +1,8 @@
 var tap = require('../')
+if (process.platform === 'win32') {
+  tap.plan(0, 'skip on windows')
+  process.exit()
+}
 var test = tap.test
 var Test = tap.Test
 var fs = require('fs')
@@ -16,7 +20,7 @@ var segv =
 // in different order.
 var expect =
 ('TAP version 13\n' +
-'    # Subtest: ./segv\n' +
+'# Subtest: ./segv\n' +
 '    1..0\n' +
 'not ok 1 - ./segv  # time=\n' +
 '  ---\n' +
diff --git a/test/simple-harness-test-with-plan.js b/test/simple-harness-test-with-plan.js
deleted file mode 100644
index 6c81831..0000000
--- a/test/simple-harness-test-with-plan.js
+++ /dev/null
@@ -1,16 +0,0 @@
-var tap = require('../')
-var test = tap.test
-var plan = tap.plan
-
-plan(2)
-
-test('trivial success', function (t) {
-  t.ok(true, 'it works')
-  t.end()
-})
-
-test('two tests', function (t) {
-  t.equal(255, 0xFF, 'math should work')
-  t.notOk(false, 'false should not be ok')
-  t.end()
-})
diff --git a/test/simple-harness-test.js b/test/simple-harness-test.js
deleted file mode 100644
index db1bbe4..0000000
--- a/test/simple-harness-test.js
+++ /dev/null
@@ -1,13 +0,0 @@
-var tap = require('../')
-var test = tap.test
-
-test('trivial success', function (t) {
-  t.ok(true, 'it works')
-  t.end()
-})
-
-test('two tests', function (t) {
-  t.equal(255, 0xFF, 'math should work')
-  t.notOk(false, 'false should not be ok')
-  t.end()
-})
diff --git a/test/spawn-failures.js b/test/spawn-failures.js
new file mode 100644
index 0000000..34d7714
--- /dev/null
+++ b/test/spawn-failures.js
@@ -0,0 +1,62 @@
+var cp = require('child_process')
+var spawn = cp.spawn
+cp.spawn = hijackedSpawn
+
+var throwNow = false
+var throwLater = false
+function hijackedSpawn (cmd, args, options) {
+  if (throwNow) {
+    throw throwNow
+  }
+  var child = spawn.call(cp, cmd, args, options)
+  if (throwLater) {
+    setTimeout(function () {
+      child.emit('error', throwLater)
+    })
+  }
+  return child
+}
+
+var t = require('../')
+var Test = t.Test
+var ok = require.resolve('./test/ok.js')
+
+t.test('handle throws from spawn()', function (t) {
+  throwNow = new Error('now is fine')
+
+  var output = ''
+  var tt = new Test()
+  tt.on('data', function (c) {
+    output += c
+  })
+  t.doesNotThrow(function spawn_throw_now () {
+    tt.spawn(process.execPath, [ok])
+  })
+  tt.end()
+  throwNow = false
+
+  t.comment(output)
+  t.notOk(tt.passing(), 'a failed spawn should fail the test')
+  t.end()
+})
+
+t.test('handle child process error event', function (t) {
+  throwLater = new Error('later is fine')
+
+  var output = ''
+  var tt = new Test()
+  tt.on('data', function (c) {
+    output += c
+  })
+  t.doesNotThrow(function spawn_throw_later () {
+    tt.spawn(process.execPath, [ok])
+  })
+  tt.end()
+
+  setTimeout(function () {
+    throwLater = false
+    t.comment(output)
+    t.notOk(tt.passing(), 'a failed spawn should fail the test')
+    t.end()
+  }, 100)
+})
diff --git a/test/test-args.js b/test/test-args.js
new file mode 100644
index 0000000..495af5a
--- /dev/null
+++ b/test/test-args.js
@@ -0,0 +1,30 @@
+var t = require('../')
+
+function namedFunction () {}
+var fn = function () {}
+var obj = { thisIsMyObject: true }
+var objTodo = { thisIsMyObject: true, todo: true }
+
+function runTest (args, expect) {
+  delete obj.todo
+
+  var result = t._parseTestArgs.apply(t, args)
+  t.match(result[0], expect[0])
+  t.same(result[1], expect[1])
+  t.equal(result[2], expect[2])
+}
+
+runTest(['name', obj, fn], ['name', obj, fn])
+runTest(['name', fn], ['name', {}, fn])
+runTest([obj, fn], [/\(unnamed test\)|fn/, obj, fn])
+runTest([obj, namedFunction], ['namedFunction', obj, namedFunction])
+runTest(['name', obj], ['name', objTodo])
+runTest(['name'], ['name', { todo: true }])
+runTest([obj], [/\(unnamed test\)|fn/, objTodo])
+runTest([fn], [/\(unnamed test\)|fn/, {}, fn])
+runTest([namedFunction], ['namedFunction', {}, namedFunction])
+runTest([], [/\(unnamed test\)|fn/, { todo: true }])
+
+t.throws(function () {
+  runTest(['name', obj, 99], [])
+})
diff --git a/test/test-output-am.js b/test/test-output-am.js
new file mode 100644
index 0000000..e455fe3
--- /dev/null
+++ b/test/test-output-am.js
@@ -0,0 +1,2 @@
+var runTests = require('./test.js')
+runTests('[a-m]*.js')
diff --git a/test/test-output-nz.js b/test/test-output-nz.js
new file mode 100644
index 0000000..f290377
--- /dev/null
+++ b/test/test-output-nz.js
@@ -0,0 +1,2 @@
+var runTests = require('./test.js')
+runTests('[n-z]*.js')
diff --git a/test/test-test.js b/test/test-test.js
index 5aa9cbd..d5b74a5 100644
--- a/test/test-test.js
+++ b/test/test-test.js
@@ -79,3 +79,171 @@ test('testing the test object', function (t) {
   })
   t.end()
 })
+
+test('plan stuff', function (t) {
+  t.throws(function () {
+    var tt = new Test()
+    tt.plan(1)
+    tt.plan(1)
+  }, new Error('Cannot set plan more than once'))
+  t.throws(function () {
+    var tt = new Test()
+    tt.plan('foo')
+  }, new TypeError('plan must be a number'))
+  t.throws(function () {
+    var tt = new Test()
+    tt.plan(-1)
+  }, new TypeError('plan must be a number'))
+
+  t.end()
+})
+
+test('do nothing after bailout', function (t) {
+  var tt = new Test()
+  var out = ''
+  tt.on('data', function (c) {
+    out += c
+  })
+  tt.bailout('this is fine')
+  tt.plan(9999)
+  tt.test('nope', function (t) {
+    throw new Error('should not get here')
+  })
+  tt.throws(function () {}, 'did not throw')
+  tt.end()
+  tt.plan(100)
+  t.equal(out, 'TAP version 13\nBail out! # this is fine\n')
+  t.end()
+})
+
+test('subtest without arguments', function (t) {
+  var tt = new Test()
+  var out = ''
+  tt.on('data', function (c) {
+    out += c
+  })
+  tt.test()
+  tt.end()
+
+  t.match(out, /^TAP version 13\nok 1 - \(unnamed test\) # TODO\n1\.\.1\n# time=[^\n]+\n$/)
+
+  t.end()
+})
+
+test('subtest with only a name', function (t) {
+  var tt = new Test()
+  var out = ''
+  tt.on('data', function (c) {
+    out += c
+  })
+  tt.test('name only')
+  tt.end()
+
+  t.match(out, /^TAP version 13\nok 1 - name only # TODO\n1\.\.1\n# time=[^\n]+\n$/)
+
+  t.end()
+})
+
+test('subtest with only options', function (t) {
+  var tt = new Test()
+  var out = ''
+  tt.on('data', function (c) {
+    out += c
+  })
+  tt.test({skip: true})
+  tt.end()
+
+  t.match(out, /^TAP version 13\nok 1 - \(unnamed test\) # SKIP\n1\.\.1\n# time=[^\n]+\n$/)
+
+  t.end()
+})
+
+test('subtest with only a function', function (t) {
+  var tt = new Test()
+  var out = ''
+  tt.on('data', function (c) {
+    out += c
+  })
+  tt.test(function (){})
+  tt.end()
+  
+  t.equal(out, 'TAP version 13\n# Subtest: (unnamed test)\n');
+
+  t.end()
+})
+
+test('subtest with name and options', function (t) {
+  var tt = new Test()
+  var out = ''
+  tt.on('data', function (c) {
+    out += c
+  })
+  tt.test('name', {skip: false})
+  tt.end()
+  
+  t.match(out, /^TAP version 13\nok 1 - name # TODO\n1\.\.1\n# time=[^\n]+\n$/)
+
+  t.end()
+})
+
+test('subtest with name and function', function (t) {
+  var tt = new Test()
+  var out = ''
+  tt.on('data', function (c) {
+    out += c
+  })
+  tt.test('name', function (){})
+  tt.end()
+
+  t.equal(out, 'TAP version 13\n# Subtest: name\n');
+
+  t.end()
+})
+
+test('subtest with options and function', function (t) {
+  var tt = new Test()
+  var out = ''
+  tt.on('data', function (c) {
+    out += c
+  })
+  tt.test({skip: false}, function (){})
+  tt.end()
+
+  t.equal(out, 'TAP version 13\n# Subtest: (unnamed test)\n');
+
+  t.end()
+})
+
+test('invalid test arguments', function (t) {
+  t.throws(function () {
+    var tt = new Test()
+    tt.test('name', { skip: false }, 'not a function')
+  }, new TypeError('test() callback must be function if provided'))
+
+  t.end()
+})
+
+test('throws type', function (t)  {
+  t.throws(function() {
+    throw new TypeError('some type error');
+  }, TypeError, 'should throw a TypeError');
+
+  var tt = new Test()
+  t.notOk(tt.throws(function () {
+    throw new RangeError('x')
+  }, TypeError))
+
+  t.notOk(tt.throws(function () {
+    throw new RangeError('x')
+  }, new TypeError('x')))
+
+  t.throws(function () {
+    throw new SyntaxError('y')
+  }, { message: 'y' })
+
+  t.throws(function () {
+    throw new RangeError('x')
+  }, new Error('x'))
+
+  t.end()
+})
diff --git a/test/test.js b/test/test.js
index 645fb2a..c379a91 100644
--- a/test/test.js
+++ b/test/test.js
@@ -11,96 +11,108 @@ function regEsc (str) {
   return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&')
 }
 
-if (process.argv[2]) {
-  var file = path.resolve(dir, process.argv[2])
-  runTest(file, false)
-  runTest(file, true)
-} else {
-  glob.sync(dir + '*.js').forEach(function (file) {
-    runTest(file, false)
-    runTest(file, true)
-  })
+module.exports = function (pattern) {
+  glob.sync(dir + pattern).forEach(runTests)
 }
 
-function runTest (file, bail) {
+if (module === require.main) {
+  if (process.argv[2]) {
+    module.exports(process.argv[2])
+  } else {
+    t.pass('just a common file')
+  }
+}
+
+function runTests (file) {
   var skip = false
-  if (file.match(/\bpending-handles.js$/) && process.env.TRAVIS) {
-    skip = 'pending handles test is too timing dependent for Travis'
+  if (file.match(/\b(timeout.*|pending-handles)\.js$/)) {
+    if (process.env.TRAVIS) {
+      skip = 'timeout and handles tests too timing dependent for Travis'
+    } else if (process.platform === 'win32') {
+      skip = 'timeout and handles tests rely on sinals windows cannot do'
+    }
   }
 
-  var resfile = file.replace(/\.js$/, (bail ? '-bail' : '') + '.tap')
-  try {
-    var want = fs.readFileSync(resfile, 'utf8').split('\n')
-  } catch (er) {
-    console.error(er)
-    console.error(file)
-    console.error(resfile)
-    return
+  if (file.match(/\bsigterm\b/) && process.version.match(/^v0\.10\./)) {
+    skip = 'sigterm handling test does not work on 0.10'
   }
 
   var f = file.substr(dir.length)
-  t.test(f + (bail ? ' bail' : ''), { skip: skip }, function (t) {
-    var child = spawn(node, [file], {
-      stdio: [ 0, 'pipe', 'pipe' ],
-      env: {
-        TAP_BAIL: bail ? 1 : 0
-      }
+  t.test(f, { skip: skip }, function (t) {
+    t.test('bail=false', function (t) {
+      runTest(t, false, file)
+    })
+    t.test('bail=true', function (t) {
+      runTest(t, true, file)
     })
+    t.end()
+  })
+}
 
-    var found = ''
+function runTest (t, bail, file) {
+  var resfile = file.replace(/\.js$/, (bail ? '-bail' : '') + '.tap')
+  var want = fs.readFileSync(resfile, 'utf8').split(/\r?\n/)
 
-    child.stdout.setEncoding('utf8')
-    child.stdout.on('data', function (c) {
-      found += c
-    })
-    child.on('close', function (er) {
-      found = found.split('\n')
-      var inyaml = false
-      var startlen = 0
-      var y = ''
-
-      // walk line by line so yamlish (json) can be handled
-      // otherwise making any changes in this lib would hurt
-      for (var f = 0, w = 0;
-           f < found.length && w < want.length;
-           f++, w++) {
-        var wline = want[w]
-        var fline = found[f]
-        var wdata = false
-
-        if (inyaml) {
-          if (fline.match(/^\s*\.\.\.$/) && fline.length === startlen) {
-            var data = yaml.safeLoad(y)
-            inyaml = false
-            y = ''
-            wdata = JSON.parse(wline)
-            patternify(wdata)
-            t.match(data, wdata)
-            f--
-          } else {
-            y += fline + '\n'
-            w--
-          }
-          continue
+  var child = spawn(node, [file], {
+    stdio: [ 0, 'pipe', 'pipe' ],
+    env: {
+      TAP_BAIL: bail ? 1 : 0
+    }
+  })
+
+  var found = ''
+
+  child.stdout.setEncoding('utf8')
+  child.stdout.on('data', function (c) {
+    found += c
+  })
+  child.on('close', function (er) {
+    found = found.split(/\r?\n/)
+    var inyaml = false
+    var startlen = 0
+    var y = ''
+
+    // walk line by line so yamlish (json) can be handled
+    // otherwise making any changes in this lib would hurt
+    for (var f = 0, w = 0;
+         f < found.length && w < want.length;
+         f++, w++) {
+      var wline = want[w]
+      var fline = found[f]
+      var wdata = false
+
+      if (inyaml) {
+        if (fline.match(/^\s*\.\.\.$/) && fline.length === startlen) {
+          var data = yaml.safeLoad(y)
+          inyaml = false
+          y = ''
+          wdata = JSON.parse(wline)
+          patternify(wdata)
+          t.match(data, wdata)
+          f--
         } else {
-          t.match(fline, patternify(wline),
-                  'line ' + f + ' ' +
-                  wline.replace(/# (todo|skip)/gi, '- $1'),
-                  { test: f })
-
-          if (fline.match(/^\s*\-\-\-$/)) {
-            startlen = fline.length
-            inyaml = true
-            y = ''
-          }
+          y += fline + '\n'
+          w--
         }
-
-        if (!t.passing()) {
-          return t.end()
+        continue
+      } else {
+        t.match(fline, patternify(wline),
+                'line ' + f + ' ' +
+                wline.replace(/# (todo|skip)/gi, '- $1'),
+                { test: f })
+
+        if (fline.match(/^\s*\-\-\-$/)) {
+          startlen = fline.length
+          inyaml = true
+          y = ''
         }
       }
-      t.end()
-    })
+
+      if (!t.passing()) {
+        return t.end()
+      }
+    }
+    t.end()
   })
 }
 
diff --git a/test/test/assert-at-bail.tap b/test/test/assert-at-bail.tap
new file mode 100644
index 0000000..45bfdda
--- /dev/null
+++ b/test/test/assert-at-bail.tap
@@ -0,0 +1,9 @@
+TAP version 13
+# Subtest: foo
+    not ok 1 - baz
+      ---
+      {"at":{"column":3,"file":"test/test/assert-at.js","function":"baz","line":14},"source":"blo(t)\n"}
+      ...
+    Bail out! # baz
+Bail out! # baz
+
diff --git a/test/test/assert-at.js b/test/test/assert-at.js
new file mode 100644
index 0000000..20c5e4e
--- /dev/null
+++ b/test/test/assert-at.js
@@ -0,0 +1,33 @@
+// verify that stacks 
+var t = require('../..')
+var stack = require('stack-utils')
+
+function foo (t) {
+  bar(t)
+}
+
+function bar (t) {
+  baz(t)
+}
+
+function baz (t) {
+  blo(t)
+}
+
+function blo (t) {
+  t.assertStack = stack.captureString(blo)
+  t.assertAt = stack.at(blo)
+  bler(t)
+}
+
+function bler (t) {
+  t.fail('baz')
+  t.fail('bler')
+  t.assertAt = stack.at(baz)
+  t.fail('bar')
+  t.assertStack = stack.captureString(baz)
+  t.fail('bar stack')
+  t.end()
+}
+
+t.test(foo)
diff --git a/test/test/assert-at.tap b/test/test/assert-at.tap
new file mode 100644
index 0000000..cb41561
--- /dev/null
+++ b/test/test/assert-at.tap
@@ -0,0 +1,29 @@
+TAP version 13
+# Subtest: foo
+    not ok 1 - baz
+      ---
+      {"at":{"column":3,"file":"test/test/assert-at.js","function":"baz","line":14},"source":"blo(t)\n"}
+      ...
+    not ok 2 - bler
+      ---
+      {"at":{"column":5,"file":"test/test/assert-at.js","function":"bler","line":25},"source":"t.fail('bler')\n"}
+      ...
+    not ok 3 - bar
+      ---
+      {"at":{"column":3,"file":"test/test/assert-at.js","function":"bar","line":10},"source":"baz(t)\n"}
+      ...
+    not ok 4 - bar stack
+      ---
+      {"at":{"column":3,"file":"test/test/assert-at.js","function":"bar","line":10},"source":"baz(t)\n"}
+      ...
+    1..4
+    # failed 4 of 4 tests
+not ok 1 - foo ___/# time=[0-9.]+(ms)?/~~~
+  ---
+  {"at":{"column":3,"file":"test/test/assert-at.js","line":33},"results":{"count":4,"fail":4,"ok":false,"pass":0,"plan":{"end":4,"start":1}},"source":"t.test(foo)\n"}
+  ...
+
+1..1
+# failed 1 of 1 tests
+___/# time=[0-9.]+(ms)?/~~~
+
diff --git a/test/test/assert-todo-skip-bail.tap b/test/test/assert-todo-skip-bail.tap
index 2f2ecce..0e8eb00 100644
--- a/test/test/assert-todo-skip-bail.tap
+++ b/test/test/assert-todo-skip-bail.tap
@@ -1,5 +1,5 @@
 TAP version 13
-    # Subtest: not much
+# Subtest: not much
     ok 1 - always passes # SKIP skip it good
     not ok 2 - false # SKIP always fails
     ok 3 - bonus # TODO remove todo directive
diff --git a/test/test/assert-todo-skip.tap b/test/test/assert-todo-skip.tap
index 2f2ecce..0e8eb00 100644
--- a/test/test/assert-todo-skip.tap
+++ b/test/test/assert-todo-skip.tap
@@ -1,5 +1,5 @@
 TAP version 13
-    # Subtest: not much
+# Subtest: not much
     ok 1 - always passes # SKIP skip it good
     not ok 2 - false # SKIP always fails
     ok 3 - bonus # TODO remove todo directive
diff --git a/test/test/async-bail.tap b/test/test/async-bail.tap
index 075711f..d3a0d35 100644
--- a/test/test/async-bail.tap
+++ b/test/test/async-bail.tap
@@ -1,10 +1,10 @@
 TAP version 13
-    # Subtest: first test
+# Subtest: first test
     ok 1 - this is ok
     1..1
 ok 1 - first test ___/# time=[0-9.]+(ms)?/~~~
 
-    # Subtest: second test (async)
+# Subtest: second test (async)
     ok 1 - this is ok
     1..1
 ok 2 - second test (async) ___/# time=[0-9.]+(ms)?/~~~
diff --git a/test/test/async.tap b/test/test/async.tap
index 075711f..d3a0d35 100644
--- a/test/test/async.tap
+++ b/test/test/async.tap
@@ -1,10 +1,10 @@
 TAP version 13
-    # Subtest: first test
+# Subtest: first test
     ok 1 - this is ok
     1..1
 ok 1 - first test ___/# time=[0-9.]+(ms)?/~~~
 
-    # Subtest: second test (async)
+# Subtest: second test (async)
     ok 1 - this is ok
     1..1
 ok 2 - second test (async) ___/# time=[0-9.]+(ms)?/~~~
diff --git a/test/test/bail-child-bail.tap b/test/test/bail-child-bail.tap
index 06b4b0a..c711084 100644
--- a/test/test/bail-child-bail.tap
+++ b/test/test/bail-child-bail.tap
@@ -1,6 +1,6 @@
 TAP version 13
-    # Subtest: bail fail
-        # Subtest: failer
+# Subtest: bail fail
+    # Subtest: failer
         not ok 1 - this fails
           ---
           {"at":{"column":7,"file":"test/test/bail-child.js","line":7},"source":"t.fail('this fails')\n"}
diff --git a/test/test/bail-child.tap b/test/test/bail-child.tap
index 06b4b0a..c711084 100644
--- a/test/test/bail-child.tap
+++ b/test/test/bail-child.tap
@@ -1,6 +1,6 @@
 TAP version 13
-    # Subtest: bail fail
-        # Subtest: failer
+# Subtest: bail fail
+    # Subtest: failer
         not ok 1 - this fails
           ---
           {"at":{"column":7,"file":"test/test/bail-child.js","line":7},"source":"t.fail('this fails')\n"}
diff --git a/test/test/bail-fail-spawn-bail.tap b/test/test/bail-fail-spawn-bail.tap
index d382356..1286ed3 100644
--- a/test/test/bail-fail-spawn-bail.tap
+++ b/test/test/bail-fail-spawn-bail.tap
@@ -1,15 +1,15 @@
 TAP version 13
-    # Subtest: bail fail
-        # Subtest: ./test/test/nesting.js
-            # Subtest: nesting
+# Subtest: bail fail
+    # Subtest: ./test/test/nesting.js
+        # Subtest: nesting
             1..2
-                # Subtest: first
+            # Subtest: first
                 1..2
                 ok 1 - true is ok
                 ok 2 - doag is also okay
             ok 1 - first ___/# time=[0-9.]+(ms)?/~~~
 
-                # Subtest: second
+            # Subtest: second
                 ok 1 - but that is ok
                 ok 2 - this passes
                 not ok 3 - nested failure
diff --git a/test/test/bail-fail-spawn.tap b/test/test/bail-fail-spawn.tap
index d382356..1286ed3 100644
--- a/test/test/bail-fail-spawn.tap
+++ b/test/test/bail-fail-spawn.tap
@@ -1,15 +1,15 @@
 TAP version 13
-    # Subtest: bail fail
-        # Subtest: ./test/test/nesting.js
-            # Subtest: nesting
+# Subtest: bail fail
+    # Subtest: ./test/test/nesting.js
+        # Subtest: nesting
             1..2
-                # Subtest: first
+            # Subtest: first
                 1..2
                 ok 1 - true is ok
                 ok 2 - doag is also okay
             ok 1 - first ___/# time=[0-9.]+(ms)?/~~~
 
-                # Subtest: second
+            # Subtest: second
                 ok 1 - but that is ok
                 ok 2 - this passes
                 not ok 3 - nested failure
diff --git a/test/test/bail-teardown-async-bail.tap b/test/test/bail-teardown-async-bail.tap
new file mode 100644
index 0000000..49f902c
--- /dev/null
+++ b/test/test/bail-teardown-async-bail.tap
@@ -0,0 +1,12 @@
+TAP version 13
+# Subtest: foobar test
+    1..0
+ok 1 - foobar test ___/# time=[0-9.]+(ms)?/~~~
+
+# Subtest: barfoo
+    # Subtest: barfoo2
+        # Subtest: barf3
+            # Subtest: b3rf
+                Bail out! # teardown fail
+Bail out! # teardown fail
+
diff --git a/test/test/bail-teardown-async.js b/test/test/bail-teardown-async.js
new file mode 100644
index 0000000..16262ef
--- /dev/null
+++ b/test/test/bail-teardown-async.js
@@ -0,0 +1,20 @@
+var t = require('../..')
+
+t.test('foobar test', function (t) {
+  t.tearDown(function () {
+    setTimeout(function () {
+      t.bailout('teardown fail')
+    })
+  })
+  t.end()
+})
+
+t.test('barfoo', function (t) {
+  return t.test('barfoo2', function (t) {
+    return t.test('barf3', function (t) {
+      return t.test('b3rf', function (t) {
+        setTimeout(t.end, 1000)
+      })
+    })
+  })
+})
diff --git a/test/test/bail-teardown-async.tap b/test/test/bail-teardown-async.tap
new file mode 100644
index 0000000..49f902c
--- /dev/null
+++ b/test/test/bail-teardown-async.tap
@@ -0,0 +1,12 @@
+TAP version 13
+# Subtest: foobar test
+    1..0
+ok 1 - foobar test ___/# time=[0-9.]+(ms)?/~~~
+
+# Subtest: barfoo
+    # Subtest: barfoo2
+        # Subtest: barf3
+            # Subtest: b3rf
+                Bail out! # teardown fail
+Bail out! # teardown fail
+
diff --git a/test/test/bail-teardown-bail.tap b/test/test/bail-teardown-bail.tap
new file mode 100644
index 0000000..c9a1a83
--- /dev/null
+++ b/test/test/bail-teardown-bail.tap
@@ -0,0 +1,9 @@
+TAP version 13
+ok 1 - this is fine
+# Subtest: child
+    ok 1 - child test point
+    1..1
+ok 2 - child ___/# time=[0-9.]+(ms)?/~~~
+
+Bail out! # i did not want to be torn down
+
diff --git a/test/test/bail-teardown.js b/test/test/bail-teardown.js
new file mode 100644
index 0000000..8c09134
--- /dev/null
+++ b/test/test/bail-teardown.js
@@ -0,0 +1,10 @@
+var t = require('../..')
+
+t.pass('this is fine')
+t.test(function child (t) {
+  t.pass('child test point')
+  t.tearDown(function () {
+    t.bailout('i did not want to be torn down')
+  })
+  t.end()
+})
diff --git a/test/test/bail-teardown.tap b/test/test/bail-teardown.tap
new file mode 100644
index 0000000..c9a1a83
--- /dev/null
+++ b/test/test/bail-teardown.tap
@@ -0,0 +1,9 @@
+TAP version 13
+ok 1 - this is fine
+# Subtest: child
+    ok 1 - child test point
+    1..1
+ok 2 - child ___/# time=[0-9.]+(ms)?/~~~
+
+Bail out! # i did not want to be torn down
+
diff --git a/test/test/bailout-bail.tap b/test/test/bailout-bail.tap
index 8a95934..32ee91b 100644
--- a/test/test/bailout-bail.tap
+++ b/test/test/bailout-bail.tap
@@ -1,17 +1,18 @@
 TAP version 13
-    # Subtest: nesting
+# Subtest: nesting
     1..2
-        # Subtest: first
+    # Subtest: first
         1..2
         ok 1 - true is ok
         ok 2 - doag is also okay
     ok 1 - first ___/# time=[0-9.]+(ms)?/~~~
 
-        # Subtest: second
+    # Subtest: second
         ok 1 - but that is ok
         ok 2 - this passes
         1..2
     ok 2 - second ___/# time=[0-9.]+(ms)?/~~~
+
 ok 1 - nesting ___/# time=[0-9.]+(ms)?/~~~
 
 ok 2 - this passes
diff --git a/test/test/bailout.tap b/test/test/bailout.tap
index 52fd84e..a20e97c 100644
--- a/test/test/bailout.tap
+++ b/test/test/bailout.tap
@@ -1,17 +1,18 @@
 TAP version 13
-    # Subtest: nesting
+# Subtest: nesting
     1..2
-        # Subtest: first
+    # Subtest: first
         1..2
         ok 1 - true is ok
         ok 2 - doag is also okay
     ok 1 - first ___/# time=[0-9.]+(ms)?/~~~
 
-        # Subtest: second
+    # Subtest: second
         ok 1 - but that is ok
         ok 2 - this passes
         1..2
     ok 2 - second ___/# time=[0-9.]+(ms)?/~~~
+
 ok 1 - nesting ___/# time=[0-9.]+(ms)?/~~~
 
 ok 2 - this passes
@@ -19,7 +20,7 @@ not ok 3 - this fails
   ---
   {"at":{"column":3,"file":"test/test/bailout.js","line":18},"source":"t.fail('this fails')\n"}
   ...
-    # Subtest: async kid
+# Subtest: async kid
     1..2
     Bail out! # cannot continue
 Bail out! # cannot continue
diff --git a/test/test/before-after-each-async-bail.tap b/test/test/before-after-each-async-bail.tap
new file mode 100644
index 0000000..9b81ad9
--- /dev/null
+++ b/test/test/before-after-each-async-bail.tap
@@ -0,0 +1,28 @@
+TAP version 13
+# Subtest: parent
+before 1 parent
+done
+    # Subtest: child
+before 1 child
+before 2 child
+        # Subtest: grandchild
+before 1 grandchild
+before 2 grandchild
+            ok 1 - the only actual assertion
+after 2 grandchild
+after 1 grandchild
+            1..1
+        ok 1 - grandchild ___/# time=[0-9.]+(ms)?/~~~
+
+after 2 child
+after 1 child
+        1..1
+    ok 1 - child ___/# time=[0-9.]+(ms)?/~~~
+
+after 1 parent
+    1..1
+ok 1 - parent ___/# time=[0-9.]+(ms)?/~~~
+
+1..1
+___/# time=[0-9.]+(ms)?/~~~
+
diff --git a/test/test/before-after-each-async.js b/test/test/before-after-each-async.js
new file mode 100644
index 0000000..0ef428a
--- /dev/null
+++ b/test/test/before-after-each-async.js
@@ -0,0 +1,34 @@
+var t = require('../..')
+
+t.beforeEach(function (cb) {
+  console.log('before 1', this._name)
+  process.nextTick(cb)
+})
+
+t.afterEach(function (cb) {
+  console.log('after 1', this._name)
+  process.nextTick(cb)
+})
+
+t.test('parent', function (t) {
+  t.beforeEach(function (cb) {
+    console.log('before 2', this._name)
+    process.nextTick(cb)
+  })
+
+  t.afterEach(function (cb) {
+    console.log('after 2', this._name)
+    process.nextTick(cb)
+  })
+
+  t.test('child', function (t) {
+    t.test('grandchild', function (t) {
+      t.pass('the only actual assertion')
+      t.end()
+    })
+    t.end()
+  })
+  t.end()
+})
+
+console.log('done')
diff --git a/test/test/before-after-each-async.tap b/test/test/before-after-each-async.tap
new file mode 100644
index 0000000..9b81ad9
--- /dev/null
+++ b/test/test/before-after-each-async.tap
@@ -0,0 +1,28 @@
+TAP version 13
+# Subtest: parent
+before 1 parent
+done
+    # Subtest: child
+before 1 child
+before 2 child
+        # Subtest: grandchild
+before 1 grandchild
+before 2 grandchild
+            ok 1 - the only actual assertion
+after 2 grandchild
+after 1 grandchild
+            1..1
+        ok 1 - grandchild ___/# time=[0-9.]+(ms)?/~~~
+
+after 2 child
+after 1 child
+        1..1
+    ok 1 - child ___/# time=[0-9.]+(ms)?/~~~
+
+after 1 parent
+    1..1
+ok 1 - parent ___/# time=[0-9.]+(ms)?/~~~
+
+1..1
+___/# time=[0-9.]+(ms)?/~~~
+
diff --git a/test/test/before-after-each-bail.tap b/test/test/before-after-each-bail.tap
new file mode 100644
index 0000000..bcc91ea
--- /dev/null
+++ b/test/test/before-after-each-bail.tap
@@ -0,0 +1,28 @@
+TAP version 13
+# Subtest: parent
+before 1 parent
+    # Subtest: child
+before 1 child
+before 2 child
+        # Subtest: grandchild
+before 1 grandchild
+before 2 grandchild
+            ok 1 - the only actual assertion
+after 2 grandchild
+after 1 grandchild
+            1..1
+        ok 1 - grandchild ___/# time=[0-9.]+(ms)?/~~~
+
+after 2 child
+after 1 child
+        1..1
+    ok 1 - child ___/# time=[0-9.]+(ms)?/~~~
+
+after 1 parent
+    1..1
+ok 1 - parent ___/# time=[0-9.]+(ms)?/~~~
+
+done
+1..1
+___/# time=[0-9.]+(ms)?/~~~
+
diff --git a/test/test/before-after-each-plan-bail.tap b/test/test/before-after-each-plan-bail.tap
new file mode 100644
index 0000000..e3315d6
--- /dev/null
+++ b/test/test/before-after-each-plan-bail.tap
@@ -0,0 +1,27 @@
+TAP version 13
+1..1
+# Subtest: parent
+before 1 parent
+    1..1
+    # Subtest: child
+before 1 child
+before 2 child
+        1..1
+        # Subtest: grandchild
+before 1 grandchild
+before 2 grandchild
+            1..1
+            ok 1 - the only actual assertion
+after 2 grandchild
+after 1 grandchild
+        ok 1 - grandchild ___/# time=[0-9.]+(ms)?/~~~
+
+after 2 child
+after 1 child
+    ok 1 - child ___/# time=[0-9.]+(ms)?/~~~
+
+after 1 parent
+ok 1 - parent ___/# time=[0-9.]+(ms)?/~~~
+___/# time=[0-9.]+(ms)?/~~~
+done
+
diff --git a/test/test/before-after-each-plan.js b/test/test/before-after-each-plan.js
new file mode 100644
index 0000000..1c011ce
--- /dev/null
+++ b/test/test/before-after-each-plan.js
@@ -0,0 +1,35 @@
+var t = require('../..')
+t.plan(1)
+
+t.beforeEach(function (cb) {
+  console.log('before 1', this._name)
+  cb()
+})
+
+t.afterEach(function (cb) {
+  console.log('after 1', this._name)
+  cb()
+})
+
+t.test('parent', function (t) {
+  t.plan(1)
+  t.beforeEach(function (cb) {
+    console.log('before 2', this._name)
+    cb()
+  })
+
+  t.afterEach(function (cb) {
+    console.log('after 2', this._name)
+    cb()
+  })
+
+  t.test('child', function (t) {
+    t.plan(1)
+    t.test('grandchild', function (t) {
+      t.plan(1)
+      t.pass('the only actual assertion')
+    })
+  })
+})
+
+console.log('done')
diff --git a/test/test/before-after-each-plan.tap b/test/test/before-after-each-plan.tap
new file mode 100644
index 0000000..e3315d6
--- /dev/null
+++ b/test/test/before-after-each-plan.tap
@@ -0,0 +1,27 @@
+TAP version 13
+1..1
+# Subtest: parent
+before 1 parent
+    1..1
+    # Subtest: child
+before 1 child
+before 2 child
+        1..1
+        # Subtest: grandchild
+before 1 grandchild
+before 2 grandchild
+            1..1
+            ok 1 - the only actual assertion
+after 2 grandchild
+after 1 grandchild
+        ok 1 - grandchild ___/# time=[0-9.]+(ms)?/~~~
+
+after 2 child
+after 1 child
+    ok 1 - child ___/# time=[0-9.]+(ms)?/~~~
+
+after 1 parent
+ok 1 - parent ___/# time=[0-9.]+(ms)?/~~~
+___/# time=[0-9.]+(ms)?/~~~
+done
+
diff --git a/test/test/before-after-each-promise-bail.tap b/test/test/before-after-each-promise-bail.tap
new file mode 100644
index 0000000..91ad9d1
--- /dev/null
+++ b/test/test/before-after-each-promise-bail.tap
@@ -0,0 +1,19 @@
+TAP version 13
+# Subtest: parent
+before 1 parent
+done
+    # Subtest: child
+before 1 child
+before 2 child
+        # Subtest: grandchild
+before 1 grandchild
+before 2 grandchild
+            ok 1 - the only actual assertion
+after 2 undefined
+            not ok 2 - Error: this is fine
+              ---
+              {"at":{"column":23,"file":"test/test/before-after-each-promise.js","line":28},"message":"Error: this is fine","source":"return reject(new Error('this is fine'))\n","test":"grandchild"}
+              ...
+            Bail out! # Error: this is fine
+Bail out! # Error: this is fine
+
diff --git a/test/test/before-after-each-promise.js b/test/test/before-after-each-promise.js
new file mode 100644
index 0000000..dbcf76d
--- /dev/null
+++ b/test/test/before-after-each-promise.js
@@ -0,0 +1,44 @@
+var t = require('../..')
+var Promise = require('bluebird')
+
+t.beforeEach(function (cb) {
+  var self = this
+  return new Promise(function (resolve, reject) {
+    console.log('before 1', self._name)
+    process.nextTick(resolve)
+  })
+})
+
+t.afterEach(function (cb) {
+  console.log('after 1', this._name)
+  cb()
+})
+
+t.test('parent', function (t) {
+  t.beforeEach(function (cb) {
+    console.log('before 2', this._name)
+    cb()
+  })
+
+  t.afterEach(function (cb) {
+    var self = this
+    return new Promise(function (resolve, reject) {
+      console.log('after 2', this._name)
+      if (self._name === 'grandchild') {
+        return reject(new Error('this is fine'))
+      }
+      resolve()
+    })
+  })
+
+  t.test('child', function (t) {
+    t.test('grandchild', function (t) {
+      t.pass('the only actual assertion')
+      t.end()
+    })
+    t.end()
+  })
+  t.end()
+})
+
+console.log('done')
diff --git a/test/test/before-after-each-promise.tap b/test/test/before-after-each-promise.tap
new file mode 100644
index 0000000..460ce76
--- /dev/null
+++ b/test/test/before-after-each-promise.tap
@@ -0,0 +1,44 @@
+TAP version 13
+# Subtest: parent
+before 1 parent
+done
+    # Subtest: child
+before 1 child
+before 2 child
+        # Subtest: grandchild
+before 1 grandchild
+before 2 grandchild
+            ok 1 - the only actual assertion
+after 2 undefined
+            not ok 2 - Error: this is fine
+              ---
+              {"at":{"column":23,"file":"test/test/before-after-each-promise.js","line":28},"message":"Error: this is fine","source":"return reject(new Error('this is fine'))\n","test":"grandchild"}
+              ...
+            1..2
+            # failed 1 of 2 tests
+        not ok 1 - grandchild ___/# time=[0-9.]+(ms)?/~~~
+          ---
+          {"at":{"column":7,"file":"test/test/before-after-each-promise.js","line":35},"results":{"count":2,"fail":1,"ok":false,"pass":1,"plan":{"end":2,"start":1}},"source":"t.test('grandchild', function (t) {\n"}
+          ...
+
+after 2 undefined
+after 1 child
+        1..1
+        # failed 1 of 1 tests
+    not ok 1 - child ___/# time=[0-9.]+(ms)?/~~~
+      ---
+      {"at":{"column":5,"file":"test/test/before-after-each-promise.js","line":34},"results":{"count":1,"fail":1,"ok":false,"pass":0,"plan":{"end":1,"start":1}},"source":"t.test('child', function (t) {\n"}
+      ...
+
+after 1 parent
+    1..1
+    # failed 1 of 1 tests
+not ok 1 - parent ___/# time=[0-9.]+(ms)?/~~~
+  ---
+  {"at":{"column":3,"file":"test/test/before-after-each-promise.js","line":17},"results":{"count":1,"fail":1,"ok":false,"pass":0,"plan":{"end":1,"start":1}},"source":"t.test('parent', function (t) {\n"}
+  ...
+
+1..1
+# failed 1 of 1 tests
+___/# time=[0-9.]+(ms)?/~~~
+
diff --git a/test/test/before-after-each-raise-bail.tap b/test/test/before-after-each-raise-bail.tap
new file mode 100644
index 0000000..9f1fc64
--- /dev/null
+++ b/test/test/before-after-each-raise-bail.tap
@@ -0,0 +1,18 @@
+TAP version 13
+# Subtest: parent
+before 1 parent
+    # Subtest: child
+before 1 child
+before 2 child
+        # Subtest: grandchild
+before 1 grandchild
+before 2 grandchild
+            ok 1 - the only actual assertion
+after 2 grandchild
+            not ok 2 - Error: this is fine
+              ---
+              {"at":{"column":17,"file":"test/test/before-after-each-raise.js","function":"Test.<anonymous>","line":22},"message":"Error: this is fine","source":"return cb(new Error('this is fine'))\n","test":"grandchild"}
+              ...
+            Bail out! # Error: this is fine
+Bail out! # Error: this is fine
+
diff --git a/test/test/before-after-each-raise.js b/test/test/before-after-each-raise.js
new file mode 100644
index 0000000..6f625ca
--- /dev/null
+++ b/test/test/before-after-each-raise.js
@@ -0,0 +1,37 @@
+var t = require('../..')
+
+t.beforeEach(function (cb) {
+  console.log('before 1', this._name)
+  cb()
+})
+
+t.afterEach(function (cb) {
+  console.log('after 1', this._name)
+  cb()
+})
+
+t.test('parent', function (t) {
+  t.beforeEach(function (cb) {
+    console.log('before 2', this._name)
+    cb()
+  })
+
+  t.afterEach(function (cb) {
+    console.log('after 2', this._name)
+    if (this._name === 'grandchild') {
+      return cb(new Error('this is fine'))
+    }
+    cb()
+  })
+
+  t.test('child', function (t) {
+    t.test('grandchild', function (t) {
+      t.pass('the only actual assertion')
+      t.end()
+    })
+    t.end()
+  })
+  t.end()
+})
+
+console.log('done')
diff --git a/test/test/before-after-each-raise.tap b/test/test/before-after-each-raise.tap
new file mode 100644
index 0000000..17e5d22
--- /dev/null
+++ b/test/test/before-after-each-raise.tap
@@ -0,0 +1,44 @@
+TAP version 13
+# Subtest: parent
+before 1 parent
+    # Subtest: child
+before 1 child
+before 2 child
+        # Subtest: grandchild
+before 1 grandchild
+before 2 grandchild
+            ok 1 - the only actual assertion
+after 2 grandchild
+            not ok 2 - Error: this is fine
+              ---
+              {"at":{"column":17,"file":"test/test/before-after-each-raise.js","function":"Test.<anonymous>","line":22},"message":"Error: this is fine","source":"return cb(new Error('this is fine'))\n","test":"grandchild"}
+              ...
+            1..2
+            # failed 1 of 2 tests
+        not ok 1 - grandchild ___/# time=[0-9.]+(ms)?/~~~
+          ---
+          {"at":{"column":7,"file":"test/test/before-after-each-raise.js","line":28},"results":{"count":2,"fail":1,"ok":false,"pass":1,"plan":{"end":2,"start":1}},"source":"t.test('grandchild', function (t) {\n"}
+          ...
+
+after 2 child
+after 1 child
+        1..1
+        # failed 1 of 1 tests
+    not ok 1 - child ___/# time=[0-9.]+(ms)?/~~~
+      ---
+      {"at":{"column":5,"file":"test/test/before-after-each-raise.js","line":27},"results":{"count":1,"fail":1,"ok":false,"pass":0,"plan":{"end":1,"start":1}},"source":"t.test('child', function (t) {\n"}
+      ...
+
+after 1 parent
+    1..1
+    # failed 1 of 1 tests
+not ok 1 - parent ___/# time=[0-9.]+(ms)?/~~~
+  ---
+  {"at":{"column":3,"file":"test/test/before-after-each-raise.js","line":13},"results":{"count":1,"fail":1,"ok":false,"pass":0,"plan":{"end":1,"start":1}},"source":"t.test('parent', function (t) {\n"}
+  ...
+
+done
+1..1
+# failed 1 of 1 tests
+___/# time=[0-9.]+(ms)?/~~~
+
diff --git a/test/test/before-after-each-throw-bail.tap b/test/test/before-after-each-throw-bail.tap
new file mode 100644
index 0000000..647e621
--- /dev/null
+++ b/test/test/before-after-each-throw-bail.tap
@@ -0,0 +1,18 @@
+TAP version 13
+# Subtest: parent
+before 1 parent
+    # Subtest: child
+before 1 child
+before 2 child
+        # Subtest: grandchild
+before 1 grandchild
+before 2 grandchild
+            ok 1 - the only actual assertion
+after 2 grandchild
+            not ok 2 - Error: this is fine
+              ---
+              {"at":{"column":16,"file":"test/test/before-after-each-throw.js","function":"Test.<anonymous>","line":22},"message":"Error: this is fine","source":"var er = new Error('this is fine')\n","test":"grandchild"}
+              ...
+            Bail out! # Error: this is fine
+Bail out! # Error: this is fine
+
diff --git a/test/test/before-after-each-throw.js b/test/test/before-after-each-throw.js
new file mode 100644
index 0000000..1363a88
--- /dev/null
+++ b/test/test/before-after-each-throw.js
@@ -0,0 +1,39 @@
+var t = require('../..')
+
+t.beforeEach(function (cb) {
+  console.log('before 1', this._name)
+  cb()
+})
+
+t.afterEach(function (cb) {
+  console.log('after 1', this._name)
+  cb()
+})
+
+t.test('parent', function (t) {
+  t.beforeEach(function (cb) {
+    console.log('before 2', this._name)
+    cb()
+  })
+
+  t.afterEach(function (cb) {
+    console.log('after 2', this._name)
+    if (this._name === 'grandchild') {
+      var er = new Error('this is fine')
+      console.error(er.stack)
+      throw er
+    }
+    cb()
+  })
+
+  t.test('child', function (t) {
+    t.test('grandchild', function (t) {
+      t.pass('the only actual assertion')
+      t.end()
+    })
+    t.end()
+  })
+  t.end()
+})
+
+console.log('done')
diff --git a/test/test/before-after-each-throw.tap b/test/test/before-after-each-throw.tap
new file mode 100644
index 0000000..cddb3c8
--- /dev/null
+++ b/test/test/before-after-each-throw.tap
@@ -0,0 +1,44 @@
+TAP version 13
+# Subtest: parent
+before 1 parent
+    # Subtest: child
+before 1 child
+before 2 child
+        # Subtest: grandchild
+before 1 grandchild
+before 2 grandchild
+            ok 1 - the only actual assertion
+after 2 grandchild
+            not ok 2 - Error: this is fine
+              ---
+              {"at":{"column":16,"file":"test/test/before-after-each-throw.js","function":"Test.<anonymous>","line":22},"message":"Error: this is fine","source":"var er = new Error('this is fine')\n","test":"grandchild"}
+              ...
+            1..2
+            # failed 1 of 2 tests
+        not ok 1 - grandchild ___/# time=[0-9.]+(ms)?/~~~
+          ---
+          {"at":{"column":7,"file":"test/test/before-after-each-throw.js","line":30},"results":{"count":2,"fail":1,"ok":false,"pass":1,"plan":{"end":2,"start":1}},"source":"t.test('grandchild', function (t) {\n"}
+          ...
+
+after 2 child
+after 1 child
+        1..1
+        # failed 1 of 1 tests
+    not ok 1 - child ___/# time=[0-9.]+(ms)?/~~~
+      ---
+      {"at":{"column":5,"file":"test/test/before-after-each-throw.js","line":29},"results":{"count":1,"fail":1,"ok":false,"pass":0,"plan":{"end":1,"start":1}},"source":"t.test('child', function (t) {\n"}
+      ...
+
+after 1 parent
+    1..1
+    # failed 1 of 1 tests
+not ok 1 - parent ___/# time=[0-9.]+(ms)?/~~~
+  ---
+  {"at":{"column":3,"file":"test/test/before-after-each-throw.js","line":13},"results":{"count":1,"fail":1,"ok":false,"pass":0,"plan":{"end":1,"start":1}},"source":"t.test('parent', function (t) {\n"}
+  ...
+
+done
+1..1
+# failed 1 of 1 tests
+___/# time=[0-9.]+(ms)?/~~~
+
diff --git a/test/test/before-after-each.js b/test/test/before-after-each.js
new file mode 100644
index 0000000..b65850c
--- /dev/null
+++ b/test/test/before-after-each.js
@@ -0,0 +1,34 @@
+var t = require('../..')
+
+t.beforeEach(function (cb) {
+  console.log('before 1', this._name)
+  cb()
+})
+
+t.afterEach(function (cb) {
+  console.log('after 1', this._name)
+  cb()
+})
+
+t.test('parent', function (t) {
+  t.beforeEach(function (cb) {
+    console.log('before 2', this._name)
+    cb()
+  })
+
+  t.afterEach(function (cb) {
+    console.log('after 2', this._name)
+    cb()
+  })
+
+  t.test('child', function (t) {
+    t.test('grandchild', function (t) {
+      t.pass('the only actual assertion')
+      t.end()
+    })
+    t.end()
+  })
+  t.end()
+})
+
+console.log('done')
diff --git a/test/test/before-after-each.tap b/test/test/before-after-each.tap
new file mode 100644
index 0000000..bcc91ea
--- /dev/null
+++ b/test/test/before-after-each.tap
@@ -0,0 +1,28 @@
+TAP version 13
+# Subtest: parent
+before 1 parent
+    # Subtest: child
+before 1 child
+before 2 child
+        # Subtest: grandchild
+before 1 grandchild
+before 2 grandchild
+            ok 1 - the only actual assertion
+after 2 grandchild
+after 1 grandchild
+            1..1
+        ok 1 - grandchild ___/# time=[0-9.]+(ms)?/~~~
+
+after 2 child
+after 1 child
+        1..1
+    ok 1 - child ___/# time=[0-9.]+(ms)?/~~~
+
+after 1 parent
+    1..1
+ok 1 - parent ___/# time=[0-9.]+(ms)?/~~~
+
+done
+1..1
+___/# time=[0-9.]+(ms)?/~~~
+
diff --git a/test/test/catch-tap-throws-bail.tap b/test/test/catch-tap-throws-bail.tap
new file mode 100644
index 0000000..b15eb43
--- /dev/null
+++ b/test/test/catch-tap-throws-bail.tap
@@ -0,0 +1,12 @@
+TAP version 13
+# Subtest: exceed_plan
+    1..1
+    ok 1 - (unnamed test)
+ok 1 - exceed_plan ___/# time=[0-9.]+(ms)?/~~~
+
+not ok 2 - Error: test count exceeds plan
+  ---
+  {"at":{"column":7,"file":"test/test/catch-tap-throws.js","function":"exceed_plan","line":9},"count":2,"message":"Error: test count exceeds plan","plan":1,"source":"t.pass()\n","test":"exceed_plan"}
+  ...
+Bail out! # Error: test count exceeds plan
+
diff --git a/test/test/catch-tap-throws.js b/test/test/catch-tap-throws.js
new file mode 100644
index 0000000..2389e25
--- /dev/null
+++ b/test/test/catch-tap-throws.js
@@ -0,0 +1,33 @@
+var tap = require('../..')
+
+// tap failures should be uncatchable
+
+tap.test(function exceed_plan (t) {
+  t.plan(1)
+  try {
+    t.pass()
+    t.pass()
+  } catch (e) {
+    // noop
+  }
+})
+
+tap.test(function multiple_end (t) {
+  try {
+    t.pass()
+    t.end()
+    t.end()
+  } catch (e) {
+    // noop
+  }
+})
+
+tap.test(function assert_after_end (t) {
+  try {
+    t.pass()
+    t.end()
+    t.pass()
+  } catch (e) {
+    // noop
+  }
+})
diff --git a/test/test/catch-tap-throws.tap b/test/test/catch-tap-throws.tap
new file mode 100644
index 0000000..3a1de38
--- /dev/null
+++ b/test/test/catch-tap-throws.tap
@@ -0,0 +1,32 @@
+TAP version 13
+# Subtest: exceed_plan
+    1..1
+    ok 1 - (unnamed test)
+ok 1 - exceed_plan ___/# time=[0-9.]+(ms)?/~~~
+
+not ok 2 - Error: test count exceeds plan
+  ---
+  {"at":{"column":7,"file":"test/test/catch-tap-throws.js","function":"exceed_plan","line":9},"count":2,"message":"Error: test count exceeds plan","plan":1,"source":"t.pass()\n","test":"exceed_plan"}
+  ...
+# Subtest: multiple_end
+    ok 1 - (unnamed test)
+    1..1
+ok 3 - multiple_end ___/# time=[0-9.]+(ms)?/~~~
+
+not ok 4 - Error: test end() method called more than once
+  ---
+  {"message":"Error: test end() method called more than once","test":"multiple_end"}
+  ...
+# Subtest: assert_after_end
+    ok 1 - (unnamed test)
+    1..1
+ok 5 - assert_after_end ___/# time=[0-9.]+(ms)?/~~~
+
+not ok 6 - Error: test after end() was called
+  ---
+  {"at":{"column":7,"file":"test/test/catch-tap-throws.js","function":"assert_after_end","line":29},"count":2,"message":"Error: test after end() was called","plan":1,"source":"t.pass()\n","test":"assert_after_end"}
+  ...
+1..6
+# failed 3 of 6 tests
+___/# time=[0-9.]+(ms)?/~~~
+
diff --git a/test/test/child-sigterm-after-end-bail.tap b/test/test/child-sigterm-after-end-bail.tap
new file mode 100644
index 0000000..2191d46
--- /dev/null
+++ b/test/test/child-sigterm-after-end-bail.tap
@@ -0,0 +1,16 @@
+TAP version 13
+# child start
+TAP version 13
+ok 1 - this is fine
+1..1
+___/# time=[0-9.]+(ms)?/~~~
+possible timeout: SIGTERM received after tap end
+  ---
+  {"handles":[{"type":"Timer"}]}
+  ...
+
+# child end code=1 signal=null
+ok 1 - should not be equal
+1..1
+___/# time=[0-9.]+(ms)?/~~~
+
diff --git a/test/test/child-sigterm-after-end.js b/test/test/child-sigterm-after-end.js
new file mode 100644
index 0000000..4c187a7
--- /dev/null
+++ b/test/test/child-sigterm-after-end.js
@@ -0,0 +1,22 @@
+var t = require('../..')
+if (process.argv[2] === 'child') {
+  setInterval(function () {}, 10000)
+  t.pass('this is fine')
+  t.end()
+  // use ipc so that we're not waiting longer than necessary
+  process.send({ do: 'it' })
+} else {
+  t.comment('child start')
+  var spawn = require('child_process').spawn
+  var child = spawn(process.execPath, [__filename, 'child'], {
+    stdio: [ 0, 1, 1, 'ipc']
+  })
+  child.on('message', function () {
+    child.kill('SIGTERM')
+  })
+  child.on('exit', function (code, signal) {
+    t.comment('child end code=%j signal=%j', code, signal)
+    t.notEqual(code, 0)
+    t.end()
+  })
+}
diff --git a/test/test/child-sigterm-after-end.tap b/test/test/child-sigterm-after-end.tap
new file mode 100644
index 0000000..2191d46
--- /dev/null
+++ b/test/test/child-sigterm-after-end.tap
@@ -0,0 +1,16 @@
+TAP version 13
+# child start
+TAP version 13
+ok 1 - this is fine
+1..1
+___/# time=[0-9.]+(ms)?/~~~
+possible timeout: SIGTERM received after tap end
+  ---
+  {"handles":[{"type":"Timer"}]}
+  ...
+
+# child end code=1 signal=null
+ok 1 - should not be equal
+1..1
+___/# time=[0-9.]+(ms)?/~~~
+
diff --git a/test/test/console-log-bail.tap b/test/test/console-log-bail.tap
index bc9e33a..a529f24 100644
--- a/test/test/console-log-bail.tap
+++ b/test/test/console-log-bail.tap
@@ -1,8 +1,8 @@
 >>>> before any tests
 TAP version 13
-    # Subtest: nesting
+# Subtest: nesting
     1..2
-        # Subtest: first
+    # Subtest: first
         1..3
         ok 1 - true is ok
 >>>> an object is { a: 'thing', to: [ 'inspect' ] }
@@ -11,21 +11,22 @@ TAP version 13
     ok 1 - first ___/# time=[0-9.]+(ms)?/~~~
 
 >>>> after first child
-        # Subtest: second
+    # Subtest: second
         ok 1 - but that is ok
         ok 2 - this passes
         ok 3 - nested ok
         ok 4 - nested ok second
         1..4
     ok 2 - second ___/# time=[0-9.]+(ms)?/~~~
-ok 1 - nesting ___/# time=[0-9.]+(ms)?/~~~
 
 >>>> after second child
+ok 1 - nesting ___/# time=[0-9.]+(ms)?/~~~
+
 >>>> after child test
 ok 2 - this passes
 ok 3 - this passes too
 >>>> after pass() calls
-    # Subtest: async kid
+# Subtest: async kid
 >>>> in async kid, before plan
     1..2
 >>>> in async kid, after plan
diff --git a/test/test/console-log.tap b/test/test/console-log.tap
index bc9e33a..a529f24 100644
--- a/test/test/console-log.tap
+++ b/test/test/console-log.tap
@@ -1,8 +1,8 @@
 >>>> before any tests
 TAP version 13
-    # Subtest: nesting
+# Subtest: nesting
     1..2
-        # Subtest: first
+    # Subtest: first
         1..3
         ok 1 - true is ok
 >>>> an object is { a: 'thing', to: [ 'inspect' ] }
@@ -11,21 +11,22 @@ TAP version 13
     ok 1 - first ___/# time=[0-9.]+(ms)?/~~~
 
 >>>> after first child
-        # Subtest: second
+    # Subtest: second
         ok 1 - but that is ok
         ok 2 - this passes
         ok 3 - nested ok
         ok 4 - nested ok second
         1..4
     ok 2 - second ___/# time=[0-9.]+(ms)?/~~~
-ok 1 - nesting ___/# time=[0-9.]+(ms)?/~~~
 
 >>>> after second child
+ok 1 - nesting ___/# time=[0-9.]+(ms)?/~~~
+
 >>>> after child test
 ok 2 - this passes
 ok 3 - this passes too
 >>>> after pass() calls
-    # Subtest: async kid
+# Subtest: async kid
 >>>> in async kid, before plan
     1..2
 >>>> in async kid, after plan
diff --git a/test/test/deferred-comment-bail.tap b/test/test/deferred-comment-bail.tap
new file mode 100644
index 0000000..06e2862
--- /dev/null
+++ b/test/test/deferred-comment-bail.tap
@@ -0,0 +1,9 @@
+TAP version 13
+# Subtest: child
+    1..0
+ok 1 - child ___/# time=[0-9.]+(ms)?/~~~
+
+# this is {"some":true} json
+1..1
+___/# time=[0-9.]+(ms)?/~~~
+
diff --git a/test/test/deferred-comment.js b/test/test/deferred-comment.js
new file mode 100644
index 0000000..7dc0839
--- /dev/null
+++ b/test/test/deferred-comment.js
@@ -0,0 +1,7 @@
+var t = require('../..')
+t.test('child', function (t) {
+  setTimeout(function () {
+    t.end()
+  })
+})
+t.comment('this is %j json', { some: true })
diff --git a/test/test/deferred-comment.tap b/test/test/deferred-comment.tap
new file mode 100644
index 0000000..06e2862
--- /dev/null
+++ b/test/test/deferred-comment.tap
@@ -0,0 +1,9 @@
+TAP version 13
+# Subtest: child
+    1..0
+ok 1 - child ___/# time=[0-9.]+(ms)?/~~~
+
+# this is {"some":true} json
+1..1
+___/# time=[0-9.]+(ms)?/~~~
+
diff --git a/test/test/empty-bail.tap b/test/test/empty-bail.tap
index e71e248..8b13789 100644
--- a/test/test/empty-bail.tap
+++ b/test/test/empty-bail.tap
@@ -1,4 +1 @@
-TAP version 13
-1..0
-___/# time=[0-9.]+(ms)?/~~~
 
diff --git a/test/test/empty.tap b/test/test/empty.tap
index e71e248..8b13789 100644
--- a/test/test/empty.tap
+++ b/test/test/empty.tap
@@ -1,4 +1 @@
-TAP version 13
-1..0
-___/# time=[0-9.]+(ms)?/~~~
 
diff --git a/test/test/end-end-bail.tap b/test/test/end-end-bail.tap
index 546f663..4a63eac 100644
--- a/test/test/end-end-bail.tap
+++ b/test/test/end-end-bail.tap
@@ -1,5 +1,5 @@
 TAP version 13
-    # Subtest: end end
+# Subtest: end end
     1..0
 ok 1 - end end ___/# time=[0-9.]+(ms)?/~~~
 
diff --git a/test/test/end-end.tap b/test/test/end-end.tap
index 178a167..dcd05a8 100644
--- a/test/test/end-end.tap
+++ b/test/test/end-end.tap
@@ -1,5 +1,5 @@
 TAP version 13
-    # Subtest: end end
+# Subtest: end end
     1..0
 ok 1 - end end ___/# time=[0-9.]+(ms)?/~~~
 
@@ -7,36 +7,36 @@ not ok 2 - Error: test end() method called more than once
   ---
   {"message":"Error: test end() method called more than once","test":"end end"}
   ...
-    # Subtest: end then async end
+# Subtest: end then async end
     1..0
 ok 3 - end then async end ___/# time=[0-9.]+(ms)?/~~~
 
-    # Subtest: double async end
+# Subtest: double async end
     1..0
 ok 4 - double async end ___/# time=[0-9.]+(ms)?/~~~
 
-    # Subtest: plan end
+not ok 5 - Error: test end() method called more than once
+  ---
+  {"message":"Error: test end() method called more than once","test":"end then async end"}
+  ...
+# Subtest: plan end
     1..1
     ok 1 - this is fine
-ok 5 - plan end ___/# time=[0-9.]+(ms)?/~~~
+ok 6 - plan end ___/# time=[0-9.]+(ms)?/~~~
 
-    # Subtest: plan then async end
+# Subtest: plan then async end
     1..1
     ok 1 - this is fine
-ok 6 - plan then async end ___/# time=[0-9.]+(ms)?/~~~
+ok 7 - plan then async end ___/# time=[0-9.]+(ms)?/~~~
 
-    # Subtest: plan end end
+# Subtest: plan end end
     1..1
     ok 1 - this is fine
-ok 7 - plan end end ___/# time=[0-9.]+(ms)?/~~~
+ok 8 - plan end end ___/# time=[0-9.]+(ms)?/~~~
 
-not ok 8 - Error: test end() method called more than once
-  ---
-  {"message":"Error: test end() method called more than once","test":"plan end end"}
-  ...
 not ok 9 - Error: test end() method called more than once
   ---
-  {"message":"Error: test end() method called more than once","test":"end then async end"}
+  {"message":"Error: test end() method called more than once","test":"plan end end"}
   ...
 not ok 10 - Error: test end() method called more than once
   ---
diff --git a/test/test/end-event-timing-bail.tap b/test/test/end-event-timing-bail.tap
index b239042..96cf073 100644
--- a/test/test/end-event-timing-bail.tap
+++ b/test/test/end-event-timing-bail.tap
@@ -1,12 +1,12 @@
 TAP version 13
-    # Subtest: first
+# Subtest: first
 > start first
 > before ending first
     1..0
 ok 1 - first ___/# time=[0-9.]+(ms)?/~~~
 
 > first end event
-    # Subtest: second
+# Subtest: second
 > start second
 > after ending first
 > before ending second
diff --git a/test/test/end-event-timing.tap b/test/test/end-event-timing.tap
index b239042..96cf073 100644
--- a/test/test/end-event-timing.tap
+++ b/test/test/end-event-timing.tap
@@ -1,12 +1,12 @@
 TAP version 13
-    # Subtest: first
+# Subtest: first
 > start first
 > before ending first
     1..0
 ok 1 - first ___/# time=[0-9.]+(ms)?/~~~
 
 > first end event
-    # Subtest: second
+# Subtest: second
 > start second
 > after ending first
 > before ending second
diff --git a/test/test/end-exception-bail.tap b/test/test/end-exception-bail.tap
index 16176f1..ee58097 100644
--- a/test/test/end-exception-bail.tap
+++ b/test/test/end-exception-bail.tap
@@ -1,5 +1,5 @@
 TAP version 13
-    # Subtest: (unnamed test)
+# Subtest: (unnamed test)
     1..1
     ok 1 - should be equal
 ok 1 - (unnamed test) ___/# time=[0-9.]+(ms)?/~~~
diff --git a/test/test/end-exception.tap b/test/test/end-exception.tap
index 3f3af72..b2beb66 100644
--- a/test/test/end-exception.tap
+++ b/test/test/end-exception.tap
@@ -1,5 +1,5 @@
 TAP version 13
-    # Subtest: (unnamed test)
+# Subtest: (unnamed test)
     1..1
     ok 1 - should be equal
 ok 1 - (unnamed test) ___/# time=[0-9.]+(ms)?/~~~
diff --git a/test/test/equivalent-bail.tap b/test/test/equivalent-bail.tap
index 1bfe331..dfa24fb 100644
--- a/test/test/equivalent-bail.tap
+++ b/test/test/equivalent-bail.tap
@@ -1,5 +1,5 @@
 TAP version 13
-    # Subtest: child test
+# Subtest: child test
     not ok 1 - should be equal
       ---
       {"at":{"column":5,"file":"test/test/equivalent.js","line":3},"compare":"===","found":"foo\nbaz\nbar\n","source":"t.equal('foo\\nbaz\\nbar\\n', 'foo\\nblerb\\nbar\\n')\n","wanted":"foo\nblerb\nbar\n"}
diff --git a/test/test/equivalent.tap b/test/test/equivalent.tap
index bb6c926..b16205e 100644
--- a/test/test/equivalent.tap
+++ b/test/test/equivalent.tap
@@ -1,5 +1,5 @@
 TAP version 13
-    # Subtest: child test
+# Subtest: child test
     not ok 1 - should be equal
       ---
       {"at":{"column":5,"file":"test/test/equivalent.js","line":3},"compare":"===","found":"foo\nbaz\nbar\n","source":"t.equal('foo\\nbaz\\nbar\\n', 'foo\\nblerb\\nbar\\n')\n","wanted":"foo\nblerb\nbar\n"}
diff --git a/test/test/exit-on-bailout-bail.tap b/test/test/exit-on-bailout-bail.tap
index ec40ad4..e99fadb 100644
--- a/test/test/exit-on-bailout-bail.tap
+++ b/test/test/exit-on-bailout-bail.tap
@@ -1,8 +1,8 @@
 log in root, before child
 TAP version 13
-    # Subtest: child
+# Subtest: child
 log in child, before grandchild
-        # Subtest: grandchild
+    # Subtest: grandchild
 log in grandchild, before bailout
         Bail out! # cannot continue
 Bail out! # cannot continue
diff --git a/test/test/exit-on-bailout.tap b/test/test/exit-on-bailout.tap
index ec40ad4..e99fadb 100644
--- a/test/test/exit-on-bailout.tap
+++ b/test/test/exit-on-bailout.tap
@@ -1,8 +1,8 @@
 log in root, before child
 TAP version 13
-    # Subtest: child
+# Subtest: child
 log in child, before grandchild
-        # Subtest: grandchild
+    # Subtest: grandchild
 log in grandchild, before bailout
         Bail out! # cannot continue
 Bail out! # cannot continue
diff --git a/test/test/mochalike-bail.tap b/test/test/mochalike-bail.tap
index 52321c4..2fc2aba 100644
--- a/test/test/mochalike-bail.tap
+++ b/test/test/mochalike-bail.tap
@@ -1,11 +1,11 @@
 TAP version 13
-    # Subtest: a set of tests to be done later
+# Subtest: a set of tests to be done later
     ok 1 - should have a thingie # TODO
     ok 2 - should have a second whoosits also # TODO
-        # Subtest: the subset
+    # Subtest: the subset
         ok 1 - should be a child thingie # TODO
         ok 2 - should also be a whoosits # TODO
-            # Subtest: has some of these things
+        # Subtest: has some of these things
             1..0
         ok 3 - has some of these things ___/# time=[0-9.]+(ms)?/~~~
 
@@ -16,33 +16,33 @@ TAP version 13
 ok 1 - a set of tests to be done later ___/# time=[0-9.]+(ms)?/~~~
 
 ok 2 - describe todo # TODO
-    # Subtest: another set of tests
+# Subtest: another set of tests
     ok 1 - is a second set # TODO
     ok 2 - looks like english # TODO
     ok 3 - is marked TODO # TODO
     1..3
 ok 3 - another set of tests ___/# time=[0-9.]+(ms)?/~~~
 
-    # Subtest: reasonably indented things
-        # Subtest: first subset
+# Subtest: reasonably indented things
+    # Subtest: first subset
         ok 1 - has no asserts, only fails to throw
         ok 2 - is todo # TODO
-            # Subtest: is async
+        # Subtest: is async
             1..0
         ok 3 - is async ___/# time=[0-9.]+(ms)?/~~~
 
         1..3
     ok 1 - first subset ___/# time=[0-9.]+(ms)?/~~~
 
-        # Subtest: second subset
+    # Subtest: second subset
         1..0
     ok 2 - second subset ___/# time=[0-9.]+(ms)?/~~~
 
     1..2
 ok 4 - reasonably indented things ___/# time=[0-9.]+(ms)?/~~~
 
-    # Subtest: failing indented things
-        # Subtest: first subset
+# Subtest: failing indented things
+    # Subtest: first subset
         not ok 1 - has no asserts, only throws
           ---
           {"actual":false,"at":{"column":7,"file":"test/test/mochalike.js","line":55},"expected":true,"generatedMessage":false,"message":"AssertionError: false is not true on line 50","name":"AssertionError","operator":"==","source":"ok(false, 'false is not true on line 50')\n"}
diff --git a/test/test/mochalike-ok-bail.tap b/test/test/mochalike-ok-bail.tap
index 69002eb..4c3606e 100644
--- a/test/test/mochalike-ok-bail.tap
+++ b/test/test/mochalike-ok-bail.tap
@@ -1,11 +1,11 @@
 TAP version 13
-    # Subtest: a set of tests to be done later
+# Subtest: a set of tests to be done later
     ok 1 - should have a thingie # TODO
     ok 2 - should have a second whoosits also # TODO
-        # Subtest: the subset
+    # Subtest: the subset
         ok 1 - should be a child thingie # TODO
         ok 2 - should also be a whoosits # TODO
-            # Subtest: has some of these things
+        # Subtest: has some of these things
             1..0
         ok 3 - has some of these things ___/# time=[0-9.]+(ms)?/~~~
 
@@ -16,25 +16,25 @@ TAP version 13
 ok 1 - a set of tests to be done later ___/# time=[0-9.]+(ms)?/~~~
 
 ok 2 - describe todo # TODO
-    # Subtest: another set of tests
+# Subtest: another set of tests
     ok 1 - is a second set # TODO
     ok 2 - looks like english # TODO
     ok 3 - is marked TODO # TODO
     1..3
 ok 3 - another set of tests ___/# time=[0-9.]+(ms)?/~~~
 
-    # Subtest: reasonably indented things
-        # Subtest: first subset
+# Subtest: reasonably indented things
+    # Subtest: first subset
         ok 1 - has no asserts, only fails to throw
         ok 2 - is todo # TODO
-            # Subtest: is async
+        # Subtest: is async
             1..0
         ok 3 - is async ___/# time=[0-9.]+(ms)?/~~~
 
         1..3
     ok 1 - first subset ___/# time=[0-9.]+(ms)?/~~~
 
-        # Subtest: second subset
+    # Subtest: second subset
         1..0
     ok 2 - second subset ___/# time=[0-9.]+(ms)?/~~~
 
diff --git a/test/test/mochalike-ok.tap b/test/test/mochalike-ok.tap
index 69002eb..4c3606e 100644
--- a/test/test/mochalike-ok.tap
+++ b/test/test/mochalike-ok.tap
@@ -1,11 +1,11 @@
 TAP version 13
-    # Subtest: a set of tests to be done later
+# Subtest: a set of tests to be done later
     ok 1 - should have a thingie # TODO
     ok 2 - should have a second whoosits also # TODO
-        # Subtest: the subset
+    # Subtest: the subset
         ok 1 - should be a child thingie # TODO
         ok 2 - should also be a whoosits # TODO
-            # Subtest: has some of these things
+        # Subtest: has some of these things
             1..0
         ok 3 - has some of these things ___/# time=[0-9.]+(ms)?/~~~
 
@@ -16,25 +16,25 @@ TAP version 13
 ok 1 - a set of tests to be done later ___/# time=[0-9.]+(ms)?/~~~
 
 ok 2 - describe todo # TODO
-    # Subtest: another set of tests
+# Subtest: another set of tests
     ok 1 - is a second set # TODO
     ok 2 - looks like english # TODO
     ok 3 - is marked TODO # TODO
     1..3
 ok 3 - another set of tests ___/# time=[0-9.]+(ms)?/~~~
 
-    # Subtest: reasonably indented things
-        # Subtest: first subset
+# Subtest: reasonably indented things
+    # Subtest: first subset
         ok 1 - has no asserts, only fails to throw
         ok 2 - is todo # TODO
-            # Subtest: is async
+        # Subtest: is async
             1..0
         ok 3 - is async ___/# time=[0-9.]+(ms)?/~~~
 
         1..3
     ok 1 - first subset ___/# time=[0-9.]+(ms)?/~~~
 
-        # Subtest: second subset
+    # Subtest: second subset
         1..0
     ok 2 - second subset ___/# time=[0-9.]+(ms)?/~~~
 
diff --git a/test/test/mochalike.tap b/test/test/mochalike.tap
index 2c15607..9602d47 100644
--- a/test/test/mochalike.tap
+++ b/test/test/mochalike.tap
@@ -1,11 +1,11 @@
 TAP version 13
-    # Subtest: a set of tests to be done later
+# Subtest: a set of tests to be done later
     ok 1 - should have a thingie # TODO
     ok 2 - should have a second whoosits also # TODO
-        # Subtest: the subset
+    # Subtest: the subset
         ok 1 - should be a child thingie # TODO
         ok 2 - should also be a whoosits # TODO
-            # Subtest: has some of these things
+        # Subtest: has some of these things
             1..0
         ok 3 - has some of these things ___/# time=[0-9.]+(ms)?/~~~
 
@@ -16,33 +16,33 @@ TAP version 13
 ok 1 - a set of tests to be done later ___/# time=[0-9.]+(ms)?/~~~
 
 ok 2 - describe todo # TODO
-    # Subtest: another set of tests
+# Subtest: another set of tests
     ok 1 - is a second set # TODO
     ok 2 - looks like english # TODO
     ok 3 - is marked TODO # TODO
     1..3
 ok 3 - another set of tests ___/# time=[0-9.]+(ms)?/~~~
 
-    # Subtest: reasonably indented things
-        # Subtest: first subset
+# Subtest: reasonably indented things
+    # Subtest: first subset
         ok 1 - has no asserts, only fails to throw
         ok 2 - is todo # TODO
-            # Subtest: is async
+        # Subtest: is async
             1..0
         ok 3 - is async ___/# time=[0-9.]+(ms)?/~~~
 
         1..3
     ok 1 - first subset ___/# time=[0-9.]+(ms)?/~~~
 
-        # Subtest: second subset
+    # Subtest: second subset
         1..0
     ok 2 - second subset ___/# time=[0-9.]+(ms)?/~~~
 
     1..2
 ok 4 - reasonably indented things ___/# time=[0-9.]+(ms)?/~~~
 
-    # Subtest: failing indented things
-        # Subtest: first subset
+# Subtest: failing indented things
+    # Subtest: first subset
         not ok 1 - has no asserts, only throws
           ---
           {"actual":false,"at":{"column":7,"file":"test/test/mochalike.js","line":55},"expected":true,"generatedMessage":false,"message":"AssertionError: false is not true on line 50","name":"AssertionError","operator":"==","source":"ok(false, 'false is not true on line 50')\n"}
@@ -56,7 +56,7 @@ ok 4 - reasonably indented things ___/# time=[0-9.]+(ms)?/~~~
       {"at":{"column":3,"file":"test/test/mochalike.js","line":52},"results":{"count":2,"fail":1,"ok":false,"pass":1,"plan":{"end":2,"start":1},"todo":2},"source":"describe('first subset', function () {\n"}
       ...
 
-        # Subtest: second subset
+    # Subtest: second subset
         not ok 1 - AssertionError: objectify the truthiness
           ---
           {"actual":false,"at":{"column":5,"file":"test/test/mochalike.js","line":61},"expected":true,"generatedMessage":false,"message":"AssertionError: objectify the truthiness","name":"AssertionError","operator":"==","source":"ok(!{}, 'objectify the truthiness')\n","test":"second subset"}
@@ -75,8 +75,8 @@ not ok 5 - failing indented things ___/# time=[0-9.]+(ms)?/~~~
   {"at":{"column":1,"file":"test/test/mochalike.js","line":51},"results":{"count":2,"fail":2,"ok":false,"pass":0,"plan":{"end":2,"start":1}},"source":"describe('failing indented things', function () {\n"}
   ...
 
-    # Subtest: a test passing an error to done() callback
-        # Subtest: is marked as failed
+# Subtest: a test passing an error to done() callback
+    # Subtest: is marked as failed
         not ok 1 - Error: error arg
           ---
           {"at":{"column":12,"file":"test/test/mochalike.js","line":68},"message":"Error: error arg","source":"done(new Error('error arg'))\n","test":"is marked as failed"}
diff --git a/test/test/nesting-bail.tap b/test/test/nesting-bail.tap
index e9947a8..4305649 100644
--- a/test/test/nesting-bail.tap
+++ b/test/test/nesting-bail.tap
@@ -1,13 +1,13 @@
 TAP version 13
-    # Subtest: nesting
+# Subtest: nesting
     1..2
-        # Subtest: first
+    # Subtest: first
         1..2
         ok 1 - true is ok
         ok 2 - doag is also okay
     ok 1 - first ___/# time=[0-9.]+(ms)?/~~~
 
-        # Subtest: second
+    # Subtest: second
         ok 1 - but that is ok
         ok 2 - this passes
         not ok 3 - nested failure
diff --git a/test/test/nesting.tap b/test/test/nesting.tap
index a74dbf8..77180e7 100644
--- a/test/test/nesting.tap
+++ b/test/test/nesting.tap
@@ -1,13 +1,13 @@
 TAP version 13
-    # Subtest: nesting
+# Subtest: nesting
     1..2
-        # Subtest: first
+    # Subtest: first
         1..2
         ok 1 - true is ok
         ok 2 - doag is also okay
     ok 1 - first ___/# time=[0-9.]+(ms)?/~~~
 
-        # Subtest: second
+    # Subtest: second
         ok 1 - but that is ok
         ok 2 - this passes
         not ok 3 - nested failure
@@ -20,6 +20,7 @@ TAP version 13
       ---
       {"at":{"column":5,"file":"test/test/nesting.js","line":10},"results":{"count":3,"fail":1,"ok":false,"pass":2,"plan":{"end":3,"start":1}},"source":"t.test('second', function (tt) {\n"}
       ...
+
     # failed 1 of 2 tests
 not ok 1 - nesting ___/# time=[0-9.]+(ms)?/~~~
   ---
@@ -31,7 +32,7 @@ not ok 3 - this fails
   ---
   {"at":{"column":3,"file":"test/test/nesting.js","line":28},"source":"t.fail('this fails')\n"}
   ...
-    # Subtest: async kid
+# Subtest: async kid
     1..2
     ok 1 - second timeout
     not ok 2 - first timeout
diff --git a/test/test/no-diags-bail.tap b/test/test/no-diags-bail.tap
new file mode 100644
index 0000000..de0d613
--- /dev/null
+++ b/test/test/no-diags-bail.tap
@@ -0,0 +1,4 @@
+TAP version 13
+not ok 1 - this is not ok
+Bail out! # this is not ok
+
diff --git a/test/test/no-diags.js b/test/test/no-diags.js
new file mode 100644
index 0000000..7e4d75d
--- /dev/null
+++ b/test/test/no-diags.js
@@ -0,0 +1,6 @@
+var t = require('../..')
+t.fail('this is not ok', { diagnostic: false })
+t.test('not ok subtest', { diagnostic: false }, function (t) {
+  t.fail('diagnostics not ok', { diagnostic: false })
+  t.end()
+})
diff --git a/test/test/no-diags.tap b/test/test/no-diags.tap
new file mode 100644
index 0000000..001b6e8
--- /dev/null
+++ b/test/test/no-diags.tap
@@ -0,0 +1,12 @@
+TAP version 13
+not ok 1 - this is not ok
+# Subtest: not ok subtest
+    not ok 1 - diagnostics not ok
+    1..1
+    # failed 1 of 1 tests
+not ok 2 - not ok subtest ___/# time=[0-9.]+(ms)?/~~~
+
+1..2
+# failed 2 of 2 tests
+___/# time=[0-9.]+(ms)?/~~~
+
diff --git a/test/test/not-ok-nested-bail.tap b/test/test/not-ok-nested-bail.tap
index 701ad1c..4cab97f 100644
--- a/test/test/not-ok-nested-bail.tap
+++ b/test/test/not-ok-nested-bail.tap
@@ -1,7 +1,7 @@
 TAP version 13
-    # Subtest: gp
+# Subtest: gp
     1..1
-        # Subtest: parent
+    # Subtest: parent
         1..1
         not ok 1 - fail
           ---
diff --git a/test/test/not-ok-nested.tap b/test/test/not-ok-nested.tap
index fa3a135..9968026 100644
--- a/test/test/not-ok-nested.tap
+++ b/test/test/not-ok-nested.tap
@@ -1,7 +1,7 @@
 TAP version 13
-    # Subtest: gp
+# Subtest: gp
     1..1
-        # Subtest: parent
+    # Subtest: parent
         1..1
         not ok 1 - fail
           ---
@@ -12,6 +12,7 @@ TAP version 13
       ---
       {"at":{"column":5,"file":"test/test/not-ok-nested.js","line":5},"results":{"count":1,"fail":1,"ok":false,"pass":0,"plan":{"end":1,"start":1}},"source":"t.test('parent', function (t) {\n"}
       ...
+
     # failed 1 of 1 tests
 not ok 1 - gp ___/# time=[0-9.]+(ms)?/~~~
   ---
diff --git a/test/test/ok-bail.tap b/test/test/ok-bail.tap
index e78dfaf..9252c9d 100644
--- a/test/test/ok-bail.tap
+++ b/test/test/ok-bail.tap
@@ -1,23 +1,24 @@
 TAP version 13
-    # Subtest: nesting
+# Subtest: nesting
     1..2
-        # Subtest: first
+    # Subtest: first
         1..2
         ok 1 - true is ok
         ok 2 - doag is also okay
     ok 1 - first ___/# time=[0-9.]+(ms)?/~~~
 
-        # Subtest: second
+    # Subtest: second
         ok 1 - but that is ok
         ok 2 - this passes
         ok 3 - nested ok
         1..3
     ok 2 - second ___/# time=[0-9.]+(ms)?/~~~
+
 ok 1 - nesting ___/# time=[0-9.]+(ms)?/~~~
 
 ok 2 - this passes
 ok 3 - this passes too
-    # Subtest: async kid
+# Subtest: async kid
     1..2
     ok 1 - timeout
     ok 2 - timeout
diff --git a/test/test/ok-diags-bail.tap b/test/test/ok-diags-bail.tap
new file mode 100644
index 0000000..ea78298
--- /dev/null
+++ b/test/test/ok-diags-bail.tap
@@ -0,0 +1,16 @@
+TAP version 13
+ok 1 - this is fine
+  ---
+  {"foo":{"bar":"baz"}}
+  ...
+# Subtest: child
+    1..1
+    ok 1 - this is fine
+ok 2 - child ___/# time=[0-9.]+(ms)?/~~~
+  ---
+  {"at":{"column":3,"file":"test/test/ok-diags.js","line":10},"results":{"count":1,"ok":true,"pass":1,"plan":{"end":1,"start":1}},"source":"t.test('child', { diagnostic: true }, function (t) {\n"}
+  ...
+
+1..2
+___/# time=[0-9.]+(ms)?/~~~
+
diff --git a/test/test/ok-diags.js b/test/test/ok-diags.js
new file mode 100644
index 0000000..f87cc09
--- /dev/null
+++ b/test/test/ok-diags.js
@@ -0,0 +1,13 @@
+var t = require('../..')
+
+t.pass('this is fine',{
+  diagnostic: true,
+  foo: {
+    bar: 'baz'
+  }
+})
+
+t.test('child', { diagnostic: true }, function (t) {
+  t.plan(1)
+  t.pass('this is fine')
+})
diff --git a/test/test/ok-diags.tap b/test/test/ok-diags.tap
new file mode 100644
index 0000000..ea78298
--- /dev/null
+++ b/test/test/ok-diags.tap
@@ -0,0 +1,16 @@
+TAP version 13
+ok 1 - this is fine
+  ---
+  {"foo":{"bar":"baz"}}
+  ...
+# Subtest: child
+    1..1
+    ok 1 - this is fine
+ok 2 - child ___/# time=[0-9.]+(ms)?/~~~
+  ---
+  {"at":{"column":3,"file":"test/test/ok-diags.js","line":10},"results":{"count":1,"ok":true,"pass":1,"plan":{"end":1,"start":1}},"source":"t.test('child', { diagnostic: true }, function (t) {\n"}
+  ...
+
+1..2
+___/# time=[0-9.]+(ms)?/~~~
+
diff --git a/test/test/ok.tap b/test/test/ok.tap
index e78dfaf..9252c9d 100644
--- a/test/test/ok.tap
+++ b/test/test/ok.tap
@@ -1,23 +1,24 @@
 TAP version 13
-    # Subtest: nesting
+# Subtest: nesting
     1..2
-        # Subtest: first
+    # Subtest: first
         1..2
         ok 1 - true is ok
         ok 2 - doag is also okay
     ok 1 - first ___/# time=[0-9.]+(ms)?/~~~
 
-        # Subtest: second
+    # Subtest: second
         ok 1 - but that is ok
         ok 2 - this passes
         ok 3 - nested ok
         1..3
     ok 2 - second ___/# time=[0-9.]+(ms)?/~~~
+
 ok 1 - nesting ___/# time=[0-9.]+(ms)?/~~~
 
 ok 2 - this passes
 ok 3 - this passes too
-    # Subtest: async kid
+# Subtest: async kid
     1..2
     ok 1 - timeout
     ok 2 - timeout
diff --git a/test/test/pending-handles-bail.tap b/test/test/pending-handles-bail.tap
index 540652c..887764f 100644
--- a/test/test/pending-handles-bail.tap
+++ b/test/test/pending-handles-bail.tap
@@ -1,10 +1,10 @@
 TAP version 13
-    # Subtest: ___/.*/~~~pending-handles.js child
+# Subtest: ___/.*/~~~pending-handles.js child
     ok 1 - this is ok
-    not ok 2 - received SIGTERM with pending event queue activity
+    not ok 2 - timeout!
       ---
-      {"handles":[{"msecs":100000,"type":"Timer"}],"requests":[]}
+      {"handles":[{"type":"Timer"}],"signal":"SIGTERM"}
       ...
-    Bail out! # received SIGTERM with pending event queue activity
-Bail out! # received SIGTERM with pending event queue activity
+    Bail out! # timeout!
+Bail out! # timeout!
 
diff --git a/test/test/pending-handles.tap b/test/test/pending-handles.tap
index 6e3f102..01cdf2d 100644
--- a/test/test/pending-handles.tap
+++ b/test/test/pending-handles.tap
@@ -1,16 +1,16 @@
 TAP version 13
-    # Subtest: ___/.*/~~~pending-handles.js child
+# Subtest: ___/.*/~~~pending-handles.js child
     ok 1 - this is ok
-    not ok 2 - received SIGTERM with pending event queue activity
+    not ok 2 - timeout!
       ---
-      {"handles":[{"msecs":100000,"type":"Timer"}],"requests":[]}
+      {"handles":[{"type":"Timer"}],"signal":"SIGTERM"}
       ...
     1..2
     # failed 1 of 2 tests
     ___/# time=[0-9.]+(ms)?/~~~
 not ok 1 - ___/.*/~~~pending-handles.js child ___/# time=[0-9.]+(ms)?/~~~
   ---
-  {"arguments":["___/.*/~~~pending-handles.js","child"],"at":{"column":5,"file":"test/test/pending-handles.js","line":7},"command":"___/.*(node|iojs)/~~~","failure":"timeout","results":{"count":2,"fail":1,"ok":false,"pass":1,"plan":{"end":2,"start":1}},"signal":"SIGTERM","source":"t.spawn(process.execPath, [__filename, 'child'], {}, '', {\n","timeout":900}
+  {"arguments":["___/.*/~~~pending-handles.js","child"],"at":{"column":5,"file":"test/test/pending-handles.js","line":7},"command":"___/.*(node|iojs)(.exe)?/~~~","failure":"timeout","results":{"count":2,"fail":1,"ok":false,"pass":1,"plan":{"end":2,"start":1}},"signal":"SIGTERM","source":"t.spawn(process.execPath, [__filename, 'child'], {}, '', {\n","timeout":900}
   ...
 
 1..1
diff --git a/test/test/plan-async-bail.tap b/test/test/plan-async-bail.tap
new file mode 100644
index 0000000..0a72431
--- /dev/null
+++ b/test/test/plan-async-bail.tap
@@ -0,0 +1,13 @@
+TAP version 13
+# Subtest: post-plan async
+    # Subtest: child test
+        ok 1 - this is fine
+        1..1
+    ok 1 - child test ___/# time=[0-9.]+(ms)?/~~~
+
+    1..1
+ok 1 - post-plan async ___/# time=[0-9.]+(ms)?/~~~
+
+1..1
+___/# time=[0-9.]+(ms)?/~~~
+
diff --git a/test/test/plan-async.js b/test/test/plan-async.js
new file mode 100644
index 0000000..76e75ff
--- /dev/null
+++ b/test/test/plan-async.js
@@ -0,0 +1,9 @@
+var t = require('../..')
+
+t.test('post-plan async', function (t) {
+  t.test('child test', function (t) {
+    t.pass('this is fine')
+    setTimeout(t.end)
+  })
+  t.plan(1)
+})
diff --git a/test/test/plan-async.tap b/test/test/plan-async.tap
new file mode 100644
index 0000000..0a72431
--- /dev/null
+++ b/test/test/plan-async.tap
@@ -0,0 +1,13 @@
+TAP version 13
+# Subtest: post-plan async
+    # Subtest: child test
+        ok 1 - this is fine
+        1..1
+    ok 1 - child test ___/# time=[0-9.]+(ms)?/~~~
+
+    1..1
+ok 1 - post-plan async ___/# time=[0-9.]+(ms)?/~~~
+
+1..1
+___/# time=[0-9.]+(ms)?/~~~
+
diff --git a/test/test/plan-failures-bail.tap b/test/test/plan-failures-bail.tap
index 4cef963..ed851d7 100644
--- a/test/test/plan-failures-bail.tap
+++ b/test/test/plan-failures-bail.tap
@@ -1,5 +1,5 @@
 TAP version 13
-    # Subtest: pass then pass plan()
+# Subtest: pass then pass plan()
     1..1
     ok 1 - ok
 ok 1 - pass then pass plan() ___/# time=[0-9.]+(ms)?/~~~
diff --git a/test/test/plan-failures.tap b/test/test/plan-failures.tap
index 804e419..76e9a07 100644
--- a/test/test/plan-failures.tap
+++ b/test/test/plan-failures.tap
@@ -1,5 +1,5 @@
 TAP version 13
-    # Subtest: pass then pass plan()
+# Subtest: pass then pass plan()
     1..1
     ok 1 - ok
 ok 1 - pass then pass plan() ___/# time=[0-9.]+(ms)?/~~~
@@ -8,16 +8,16 @@ not ok 2 - Error: test count exceeds plan
   ---
   {"at":{"column":12,"file":"test/test/plan-failures.js","function":"test","line":11},"count":2,"message":"Error: test count exceeds plan","plan":1,"source":"t[second]('extra')\n","test":"pass then pass plan()"}
   ...
-    # Subtest: pass then pass end()
+# Subtest: pass then pass end()
     ok 1 - ok
     1..1
 ok 3 - pass then pass end() ___/# time=[0-9.]+(ms)?/~~~
 
-not ok 4 - Error: test count exceeds plan
+not ok 4 - Error: test after end() was called
   ---
-  {"at":{"column":12,"file":"test/test/plan-failures.js","function":"test","line":11},"count":2,"message":"Error: test count exceeds plan","plan":1,"source":"t[second]('extra')\n","test":"pass then pass end()"}
+  {"at":{"column":12,"file":"test/test/plan-failures.js","function":"test","line":11},"count":2,"message":"Error: test after end() was called","plan":1,"source":"t[second]('extra')\n","test":"pass then pass end()"}
   ...
-    # Subtest: pass then fail plan()
+# Subtest: pass then fail plan()
     1..1
     ok 1 - ok
 ok 5 - pass then fail plan() ___/# time=[0-9.]+(ms)?/~~~
@@ -26,16 +26,16 @@ not ok 6 - Error: test count exceeds plan
   ---
   {"at":{"column":12,"file":"test/test/plan-failures.js","function":"test","line":11},"count":2,"message":"Error: test count exceeds plan","plan":1,"source":"t[second]('extra')\n","test":"pass then fail plan()"}
   ...
-    # Subtest: pass then fail end()
+# Subtest: pass then fail end()
     ok 1 - ok
     1..1
 ok 7 - pass then fail end() ___/# time=[0-9.]+(ms)?/~~~
 
-not ok 8 - Error: test count exceeds plan
+not ok 8 - Error: test after end() was called
   ---
-  {"at":{"column":12,"file":"test/test/plan-failures.js","function":"test","line":11},"count":2,"message":"Error: test count exceeds plan","plan":1,"source":"t[second]('extra')\n","test":"pass then fail end()"}
+  {"at":{"column":12,"file":"test/test/plan-failures.js","function":"test","line":11},"count":2,"message":"Error: test after end() was called","plan":1,"source":"t[second]('extra')\n","test":"pass then fail end()"}
   ...
-    # Subtest: fail then pass plan()
+# Subtest: fail then pass plan()
     1..1
     not ok 1 - ok
       ---
@@ -47,7 +47,7 @@ not ok 9 - fail then pass plan() ___/# time=[0-9.]+(ms)?/~~~
   {"at":{"column":7,"file":"test/test/plan-failures.js","line":18},"results":{"count":1,"fail":1,"ok":false,"pass":0,"plan":{"end":1,"start":1}},"source":"t.test(first + ' then ' + second + ' plan()', function (t) {\n"}
   ...
 
-    # Subtest: fail then pass end()
+# Subtest: fail then pass end()
     not ok 1 - ok
       ---
       {"at":{"column":11,"file":"test/test/plan-failures.js","function":"test","line":7},"source":"t[first]('ok')\n"}
@@ -59,7 +59,7 @@ not ok 10 - fail then pass end() ___/# time=[0-9.]+(ms)?/~~~
   {"at":{"column":7,"file":"test/test/plan-failures.js","line":21},"results":{"count":1,"fail":1,"ok":false,"pass":0,"plan":{"end":1,"start":1}},"source":"t.test(first + ' then ' + second + ' end()', function (t) {\n"}
   ...
 
-    # Subtest: fail then fail plan()
+# Subtest: fail then fail plan()
     1..1
     not ok 1 - ok
       ---
@@ -71,7 +71,7 @@ not ok 11 - fail then fail plan() ___/# time=[0-9.]+(ms)?/~~~
   {"at":{"column":7,"file":"test/test/plan-failures.js","line":18},"results":{"count":1,"fail":1,"ok":false,"pass":0,"plan":{"end":1,"start":1}},"source":"t.test(first + ' then ' + second + ' plan()', function (t) {\n"}
   ...
 
-    # Subtest: fail then fail end()
+# Subtest: fail then fail end()
     not ok 1 - ok
       ---
       {"at":{"column":11,"file":"test/test/plan-failures.js","function":"test","line":7},"source":"t[first]('ok')\n"}
diff --git a/test/test/plan-too-many-bail.tap b/test/test/plan-too-many-bail.tap
index 72d89ac..20abc57 100644
--- a/test/test/plan-too-many-bail.tap
+++ b/test/test/plan-too-many-bail.tap
@@ -1,9 +1,9 @@
 TAP version 13
-    # Subtest: children plan too big
+# Subtest: children plan too big
     1..9
     ok 1 - this is ok
     ok 2 - i am ok with how this is proceeding
-        # Subtest: grandchild
+    # Subtest: grandchild
         1..8
         ok 1 - i am planning big things
         not ok 2 - test unfinished: grandchild
diff --git a/test/test/plan-too-many.tap b/test/test/plan-too-many.tap
index bc06283..9656426 100644
--- a/test/test/plan-too-many.tap
+++ b/test/test/plan-too-many.tap
@@ -1,9 +1,9 @@
 TAP version 13
-    # Subtest: children plan too big
+# Subtest: children plan too big
     1..9
     ok 1 - this is ok
     ok 2 - i am ok with how this is proceeding
-        # Subtest: grandchild
+    # Subtest: grandchild
         1..8
         ok 1 - i am planning big things
         not ok 2 - test unfinished: grandchild
diff --git a/test/test/pragma-bail.tap b/test/test/pragma-bail.tap
index b6b751a..f2d5d5a 100644
--- a/test/test/pragma-bail.tap
+++ b/test/test/pragma-bail.tap
@@ -1,5 +1,6 @@
 TAP version 13
-    # Subtest: ___/.*/~~~pragma.js child
+pragma +child
+# Subtest: ___/.*/~~~pragma.js child
     this is not tap but it is ok
     ok 1 - an ok test
     pragma +strict
@@ -11,7 +12,7 @@ TAP version 13
     ___/# time=[0-9.]+(ms)?/~~~
 not ok 1 - ___/.*/~~~pragma.js child ___/# time=[0-9.]+(ms)?/~~~
   ---
-  {"arguments":["___/.*/~~~pragma.js","child"],"at":{"column":5,"file":"test/test/pragma.js","line":4},"command":"___/.*(node|iojs)/~~~","results":{"count":2,"failures":[{"data":"this is not tap and it is not ok\n","tapError":"Non-TAP data encountered in strict mode"}],"ok":false,"pass":2,"plan":{"end":2,"start":1}},"source":"t.spawn(process.execPath, [__filename, 'child'])\n"}
+  {"arguments":["___/.*/~~~pragma.js","child"],"at":{"column":5,"file":"test/test/pragma.js","line":6},"command":"___/.*(node|iojs)(.exe)?/~~~","results":{"count":2,"fail":1,"failures":[{"data":"this is not tap and it is not ok\n","tapError":"Non-TAP data encountered in strict mode"}],"ok":false,"pass":2,"plan":{"end":2,"start":1}},"source":"t.spawn(process.execPath, [__filename, 'child'])\n"}
   ...
 Bail out! # ___/.*/~~~pragma.js child ___/# time=[0-9.]+(ms)?/~~~
 
diff --git a/test/test/pragma.js b/test/test/pragma.js
index 9b66d4c..ecd46f6 100644
--- a/test/test/pragma.js
+++ b/test/test/pragma.js
@@ -1,8 +1,14 @@
 var t = require('../..')
 
 if (process.argv[2] !== 'child') {
+  // +child does nothing, just verifying that it gets delayed
+  t.pragma({ child: true })
   t.spawn(process.execPath, [__filename, 'child'])
-} else {
+  t.pragma({ child: false })
+  t.spawn(process.execPath, [__filename, 'child', 'bailout'])
+  // this one should be skipped entirely
+  t.pragma({ bailedout: true })
+} else if (process.argv[3] !== 'bailout') {
   console.log('this is not tap but it is ok')
   t.pass('an ok test')
   t.pragma({ strict: true })
@@ -11,4 +17,8 @@ if (process.argv[2] !== 'child') {
   console.log('more ok non-tap')
   t.pass('ending now')
   t.end()
+} else {
+  t.removeAllListeners('bailout')
+  t.bailout('no pragmas come after bailout')
+  t.pragma({bailout: true })
 }
diff --git a/test/test/pragma.tap b/test/test/pragma.tap
index 600924f..49d1b58 100644
--- a/test/test/pragma.tap
+++ b/test/test/pragma.tap
@@ -1,5 +1,6 @@
 TAP version 13
-    # Subtest: ___/.*/~~~pragma.js child
+pragma +child
+# Subtest: ___/.*/~~~pragma.js child
     this is not tap but it is ok
     ok 1 - an ok test
     pragma +strict
@@ -11,10 +12,11 @@ TAP version 13
     ___/# time=[0-9.]+(ms)?/~~~
 not ok 1 - ___/.*/~~~pragma.js child ___/# time=[0-9.]+(ms)?/~~~
   ---
-  {"arguments":["___/.*/~~~pragma.js","child"],"at":{"column":5,"file":"test/test/pragma.js","line":4},"command":"___/.*(node|iojs)/~~~","results":{"count":2,"failures":[{"data":"this is not tap and it is not ok\n","tapError":"Non-TAP data encountered in strict mode"}],"ok":false,"pass":2,"plan":{"end":2,"start":1}},"source":"t.spawn(process.execPath, [__filename, 'child'])\n"}
+  {"arguments":["___/.*/~~~pragma.js","child"],"at":{"column":5,"file":"test/test/pragma.js","line":6},"command":"___/.*(node|iojs)(.exe)?/~~~","results":{"count":2,"fail":1,"failures":[{"data":"this is not tap and it is not ok\n","tapError":"Non-TAP data encountered in strict mode"}],"ok":false,"pass":2,"plan":{"end":2,"start":1}},"source":"t.spawn(process.execPath, [__filename, 'child'])\n"}
   ...
 
-1..1
-# failed 1 of 1 tests
-___/# time=[0-9.]+(ms)?/~~~
+pragma -child
+# Subtest: ___/.*/~~~pragma.js child bailout
+    Bail out! # no pragmas come after bailout
+Bail out! # no pragmas come after bailout
 
diff --git a/test/test/promise-bail.tap b/test/test/promise-bail.tap
new file mode 100644
index 0000000..784be2a
--- /dev/null
+++ b/test/test/promise-bail.tap
@@ -0,0 +1,80 @@
+TAP version 13
+# Subtest: one
+    # Subtest: two
+        # Subtest: child of 2
+            # Subtest: grandchild of 2
+                ok 1 - this is fine
+                1..1
+            ok 1 - grandchild of 2 ___/# time=[0-9.]+(ms)?/~~~
+
+            1..1
+        ok 1 - child of 2 ___/# time=[0-9.]+(ms)?/~~~
+
+        1..1
+    ok 1 - two ___/# time=[0-9.]+(ms)?/~~~
+
+    # Subtest: second child of 2
+        1..0
+    ok 2 - second child of 2 ___/# time=[0-9.]+(ms)?/~~~
+
+    1..2
+ok 1 - one ___/# time=[0-9.]+(ms)?/~~~
+
+# Subtest: three
+    1..0
+ok 2 - three ___/# time=[0-9.]+(ms)?/~~~
+
+# Subtest: some_function_name
+    ok 1 - resolved to 1
+    1..1
+ok 3 - some_function_name ___/# time=[0-9.]+(ms)?/~~~
+
+# Subtest: ___/.*/~~~promise.js child
+    # Subtest: one
+        # Subtest: two
+            # Subtest: child of 2
+                # Subtest: grandchild of 2
+                    ok 1 - this is fine
+                    1..1
+                ok 1 - grandchild of 2 ___/# time=[0-9.]+(ms)?/~~~
+
+                1..1
+            ok 1 - child of 2 ___/# time=[0-9.]+(ms)?/~~~
+
+            1..1
+        ok 1 - two ___/# time=[0-9.]+(ms)?/~~~
+
+        # Subtest: second child of 2
+            1..0
+        ok 2 - second child of 2 ___/# time=[0-9.]+(ms)?/~~~
+
+        1..2
+    ok 1 - one ___/# time=[0-9.]+(ms)?/~~~
+
+    # Subtest: three
+        1..0
+    ok 2 - three ___/# time=[0-9.]+(ms)?/~~~
+
+    # Subtest: some_function_name
+        ok 1 - resolved to 1
+        1..1
+    ok 3 - some_function_name ___/# time=[0-9.]+(ms)?/~~~
+
+    # Subtest: spawned
+        ok 1 - in the spawned child
+        1..1
+    ok 4 - spawned ___/# time=[0-9.]+(ms)?/~~~
+
+    ok 5 - is root tap test
+    ok 6 - has no parent
+    ok 7 - this is fine
+    1..7
+    ___/# time=[0-9.]+(ms)?/~~~
+ok 4 - ___/.*/~~~promise.js child ___/# time=[0-9.]+(ms)?/~~~
+
+ok 5 - is root tap test
+ok 6 - has no parent
+ok 7 - this is fine
+1..7
+___/# time=[0-9.]+(ms)?/~~~
+
diff --git a/test/test/promise-fails-bail.tap b/test/test/promise-fails-bail.tap
new file mode 100644
index 0000000..4fa4ff3
--- /dev/null
+++ b/test/test/promise-fails-bail.tap
@@ -0,0 +1,12 @@
+TAP version 13
+# Subtest: one
+    # Subtest: two
+        # Subtest: child of 2
+            # Subtest: grandchild of 2
+                not ok 1 - Error: fail 1
+                  ---
+                  {"at":{"column":15,"file":"test/test/promise-fails.js","line":7},"message":"Error: fail 1","source":"throw new Error('fail 1')\n","test":"grandchild of 2"}
+                  ...
+                Bail out! # Error: fail 1
+Bail out! # Error: fail 1
+
diff --git a/test/test/promise-fails.js b/test/test/promise-fails.js
new file mode 100644
index 0000000..1d310fe
--- /dev/null
+++ b/test/test/promise-fails.js
@@ -0,0 +1,40 @@
+var t = require('../..')
+var P = typeof Promise === 'undefined' ? require('bluebird') : Promise
+t.test('one', function (t) {
+  return t.test('two', function (t) {
+    return t.test('child of 2', function (t) {
+      return t.test('grandchild of 2', function (t) {
+        throw new Error('fail 1')
+      })
+    })
+  }).then(function (t) {
+    return t.test('second child of 2', function (t) {
+      throw new Error('fail 2')
+    })
+  })
+}).then(function (t) {
+  return t.test('three', function (t) {
+    setTimeout(function () {
+      throw new Error('fail 3')
+    })
+  })
+}).then(function (t) {
+  return new P(function (resolve, reject) {
+    setTimeout(function () {
+      resolve(1)
+    })
+  }).then(function (x) {
+    return t.test(function some_function_name (t) {
+      process.nextTick(function () {
+        throw new Error('fail 4')
+      })
+    })
+  })
+}).then(function (t) {
+  throw new Error('fail 5')
+}).then(function (t) {
+  t.equal(t._name, 'TAP', 'is root tap test')
+  t.notOk(t._parent, 'has no parent')
+  t.pass('this is fine')
+  t.end()
+}).catch(t.threw)
diff --git a/test/test/promise-fails.tap b/test/test/promise-fails.tap
new file mode 100644
index 0000000..c4ac0b6
--- /dev/null
+++ b/test/test/promise-fails.tap
@@ -0,0 +1,81 @@
+TAP version 13
+# Subtest: one
+    # Subtest: two
+        # Subtest: child of 2
+            # Subtest: grandchild of 2
+                not ok 1 - Error: fail 1
+                  ---
+                  {"at":{"column":15,"file":"test/test/promise-fails.js","line":7},"message":"Error: fail 1","source":"throw new Error('fail 1')\n","test":"grandchild of 2"}
+                  ...
+                1..1
+                # failed 1 of 1 tests
+            not ok 1 - grandchild of 2 ___/# time=[0-9.]+(ms)?/~~~
+              ---
+              {"at":{"column":16,"file":"test/test/promise-fails.js","line":6},"results":{"count":1,"fail":1,"ok":false,"pass":0,"plan":{"end":1,"start":1}},"source":"return t.test('grandchild of 2', function (t) {\n"}
+              ...
+
+            1..1
+            # failed 1 of 1 tests
+        not ok 1 - child of 2 ___/# time=[0-9.]+(ms)?/~~~
+          ---
+          {"at":{"column":14,"file":"test/test/promise-fails.js","line":5},"results":{"count":1,"fail":1,"ok":false,"pass":0,"plan":{"end":1,"start":1}},"source":"return t.test('child of 2', function (t) {\n"}
+          ...
+
+        1..1
+        # failed 1 of 1 tests
+    not ok 1 - two ___/# time=[0-9.]+(ms)?/~~~
+      ---
+      {"at":{"column":12,"file":"test/test/promise-fails.js","line":4},"results":{"count":1,"fail":1,"ok":false,"pass":0,"plan":{"end":1,"start":1}},"source":"return t.test('two', function (t) {\n"}
+      ...
+
+    # Subtest: second child of 2
+        not ok 1 - Error: fail 2
+          ---
+          {"at":{"column":13,"file":"test/test/promise-fails.js","line":12},"message":"Error: fail 2","source":"throw new Error('fail 2')\n","test":"second child of 2"}
+          ...
+        1..1
+        # failed 1 of 1 tests
+    not ok 2 - second child of 2 ___/# time=[0-9.]+(ms)?/~~~
+      ---
+      {"at":{"column":14,"file":"test/test/promise-fails.js","line":11},"results":{"count":1,"fail":1,"ok":false,"pass":0,"plan":{"end":1,"start":1}},"source":"return t.test('second child of 2', function (t) {\n"}
+      ...
+
+    1..2
+    # failed 2 of 2 tests
+not ok 1 - one ___/# time=[0-9.]+(ms)?/~~~
+  ---
+  {"at":{"column":3,"file":"test/test/promise-fails.js","line":3},"results":{"count":2,"fail":2,"ok":false,"pass":0,"plan":{"end":2,"start":1}},"source":"t.test('one', function (t) {\n"}
+  ...
+
+# Subtest: three
+    not ok 1 - Error: fail 3
+      ---
+      {"at":{"column":13,"file":"test/test/promise-fails.js","line":18},"message":"Error: fail 3","source":"throw new Error('fail 3')\n","test":"three"}
+      ...
+    1..1
+    # failed 1 of 1 tests
+not ok 2 - three ___/# time=[0-9.]+(ms)?/~~~
+  ---
+  {"at":{"column":12,"file":"test/test/promise-fails.js","line":16},"results":{"count":1,"fail":1,"ok":false,"pass":0,"plan":{"end":1,"start":1}},"source":"return t.test('three', function (t) {\n"}
+  ...
+
+# Subtest: some_function_name
+    not ok 1 - Error: fail 4
+      ---
+      {"at":{"column":15,"file":"test/test/promise-fails.js","line":29},"message":"Error: fail 4","source":"throw new Error('fail 4')\n","test":"some_function_name"}
+      ...
+    1..1
+    # failed 1 of 1 tests
+not ok 3 - some_function_name ___/# time=[0-9.]+(ms)?/~~~
+  ---
+  {"at":{"column":14,"file":"test/test/promise-fails.js","line":27},"results":{"count":1,"fail":1,"ok":false,"pass":0,"plan":{"end":1,"start":1}},"source":"return t.test(function some_function_name (t) {\n"}
+  ...
+
+not ok 4 - Error: fail 5
+  ---
+  {"at":{"column":9,"file":"test/test/promise-fails.js","line":34},"message":"Error: fail 5","source":"throw new Error('fail 5')\n","test":"TAP"}
+  ...
+1..4
+# failed 4 of 4 tests
+___/# time=[0-9.]+(ms)?/~~~
+
diff --git a/test/test/promise-plan-bail.tap b/test/test/promise-plan-bail.tap
new file mode 100644
index 0000000..c7bffe1
--- /dev/null
+++ b/test/test/promise-plan-bail.tap
@@ -0,0 +1,34 @@
+TAP version 13
+# Subtest: one
+running one
+    1..1
+    ok 1 - done one
+after plan fulfilled
+after one block
+one promise was fulfilled
+ok 1 - one ___/# time=[0-9.]+(ms)?/~~~
+
+one end
+# Subtest: two
+running two
+    1..1
+    ok 1 - done two
+two promise was fulfilled
+ok 2 - two ___/# time=[0-9.]+(ms)?/~~~
+
+# Subtest: three
+running three
+    1..0
+ok 3 - three ___/# time=[0-9.]+(ms)?/~~~
+
+# Subtest: broken promises
+    1..2
+    # Subtest: end()
+        1..0
+    not ok 1 - end() ___/# time=[0-9.]+(ms)?/~~~
+      ---
+      {"at":{"column":5,"file":"test/test/promise-plan.js","line":42},"results":{"count":0,"ok":false,"pass":0,"plan":{"end":0,"start":1}},"source":"t.test('end()', function (t) {\n"}
+      ...
+    Bail out! # end() ___/# time=[0-9.]+(ms)?/~~~
+Bail out! # end() ___/# time=[0-9.]+(ms)?/~~~
+
diff --git a/test/test/promise-plan.js b/test/test/promise-plan.js
new file mode 100644
index 0000000..62b72f0
--- /dev/null
+++ b/test/test/promise-plan.js
@@ -0,0 +1,76 @@
+var t = require('../..')
+var P = typeof Promise === 'undefined' ? require('bluebird') : Promise
+
+t.test('one', function (t) {
+  console.log('running one')
+  t.plan(1)
+
+  t.on('end', function () {
+    console.log('one end')
+  })
+
+  return new P(function (resolve) {
+    setTimeout(resolve, 50)
+    t.pass('done one')
+    console.log('after plan fulfilled')
+  }).then(function () {
+    console.log('one promise was fulfilled')
+  })
+})
+
+console.log('after one block')
+
+t.test('two', function (t) {
+  console.log('running two')
+  t.plan(1)
+
+  return new P(function (resolve) {
+    setTimeout(resolve, 50)
+  }).then(function () {
+    t.pass('done two')
+    console.log('two promise was fulfilled')
+  })
+})
+
+t.test('three', function (t) {
+  console.log('running three')
+  t.end()
+})
+
+t.test('broken promises', function (t) {
+  t.plan(2)
+  t.test('end()', function (t) {
+    t.end()
+    return new P(function () {
+      throw new Error('wtf')
+    })
+  })
+  t.test('plan', function (t) {
+    t.plan(1)
+    t.pass('this is fine')
+    return new P(function () {
+      throw new Error('wtf')
+    })
+  })
+})
+
+t.test('thrown with timeouts', function (t) {
+  t.plan(2)
+  t.test('end()', function (t) {
+    t.end()
+    return new P(function () {
+      setTimeout(function () {
+        throw new Error('wtf')
+      })
+    })
+  })
+  t.test('plan', function (t) {
+    t.plan(1)
+    t.pass('this is fine')
+    return new P(function () {
+      setTimeout(function () {
+        throw new Error('wtf')
+      })
+    })
+  })
+})
diff --git a/test/test/promise-plan.tap b/test/test/promise-plan.tap
new file mode 100644
index 0000000..db462e3
--- /dev/null
+++ b/test/test/promise-plan.tap
@@ -0,0 +1,71 @@
+TAP version 13
+# Subtest: one
+running one
+    1..1
+    ok 1 - done one
+after plan fulfilled
+after one block
+one promise was fulfilled
+ok 1 - one ___/# time=[0-9.]+(ms)?/~~~
+
+one end
+# Subtest: two
+running two
+    1..1
+    ok 1 - done two
+two promise was fulfilled
+ok 2 - two ___/# time=[0-9.]+(ms)?/~~~
+
+# Subtest: three
+running three
+    1..0
+ok 3 - three ___/# time=[0-9.]+(ms)?/~~~
+
+# Subtest: broken promises
+    1..2
+    # Subtest: end()
+        1..0
+    not ok 1 - end() ___/# time=[0-9.]+(ms)?/~~~
+      ---
+      {"at":{"column":5,"file":"test/test/promise-plan.js","line":42},"results":{"count":0,"ok":false,"pass":0,"plan":{"end":0,"start":1}},"source":"t.test('end()', function (t) {\n"}
+      ...
+
+    # Subtest: plan
+        1..1
+        ok 1 - this is fine
+    not ok 2 - plan ___/# time=[0-9.]+(ms)?/~~~
+      ---
+      {"at":{"column":5,"file":"test/test/promise-plan.js","line":48},"results":{"count":1,"ok":false,"pass":1,"plan":{"end":1,"start":1}},"source":"t.test('plan', function (t) {\n"}
+      ...
+    # failed 2 of 2 tests
+not ok 4 - broken promises ___/# time=[0-9.]+(ms)?/~~~
+  ---
+  {"at":{"column":3,"file":"test/test/promise-plan.js","line":40},"results":{"count":2,"fail":2,"ok":false,"pass":0,"plan":{"end":2,"start":1}},"source":"t.test('broken promises', function (t) {\n"}
+  ...
+
+# Subtest: thrown with timeouts
+    1..2
+    # Subtest: end()
+        1..0
+    not ok 1 - end() ___/# time=[0-9.]+(ms)?/~~~
+      ---
+      {"at":{"column":5,"file":"test/test/promise-plan.js","line":59},"results":{"count":0,"ok":false,"pass":0,"plan":{"end":0,"start":1}},"source":"t.test('end()', function (t) {\n"}
+      ...
+
+    # Subtest: plan
+        1..1
+        ok 1 - this is fine
+    not ok 2 - plan ___/# time=[0-9.]+(ms)?/~~~
+      ---
+      {"at":{"column":5,"file":"test/test/promise-plan.js","line":67},"results":{"count":1,"ok":false,"pass":1,"plan":{"end":1,"start":1}},"source":"t.test('plan', function (t) {\n"}
+      ...
+    # failed 2 of 2 tests
+not ok 5 - thrown with timeouts ___/# time=[0-9.]+(ms)?/~~~
+  ---
+  {"at":{"column":3,"file":"test/test/promise-plan.js","line":57},"results":{"count":2,"fail":2,"ok":false,"pass":0,"plan":{"end":2,"start":1}},"source":"t.test('thrown with timeouts', function (t) {\n"}
+  ...
+
+1..5
+# failed 2 of 5 tests
+___/# time=[0-9.]+(ms)?/~~~
+
diff --git a/test/test/promise-return-bail.tap b/test/test/promise-return-bail.tap
index 60c26ad..4c6fc2d 100644
--- a/test/test/promise-return-bail.tap
+++ b/test/test/promise-return-bail.tap
@@ -1,14 +1,14 @@
 TAP version 13
-    # Subtest: auto-end on resolve
+# Subtest: auto-end on resolve
     ok 1 - true is ok
     1..1
 ok 1 - auto-end on resolve ___/# time=[0-9.]+(ms)?/~~~
 
-    # Subtest: rejected
-    not ok 1 - expected error
+# Subtest: rejected
+    not ok 1 - Error: expected error
       ---
-      {"at":{"column":34,"file":"test/test/promise-return.js","line":22},"message":"Error: expected error","source":"setTimeout(reject.bind(null, new Error('expected error'), 150))\n"}
+      {"at":{"column":34,"file":"test/test/promise-return.js","line":22},"message":"Error: expected error","source":"setTimeout(reject.bind(null, new Error('expected error'), 150))\n","test":"rejected"}
       ...
-    Bail out! # expected error
-Bail out! # expected error
+    Bail out! # Error: expected error
+Bail out! # Error: expected error
 
diff --git a/test/test/promise-return-mocha-bail.tap b/test/test/promise-return-mocha-bail.tap
index cf7640e..26e92ab 100644
--- a/test/test/promise-return-mocha-bail.tap
+++ b/test/test/promise-return-mocha-bail.tap
@@ -1,17 +1,17 @@
 TAP version 13
-    # Subtest: auto-end on resolve
+# Subtest: auto-end on resolve
     1..0
 ok 1 - auto-end on resolve ___/# time=[0-9.]+(ms)?/~~~
 
-    # Subtest: auto-end on resolve without cb
+# Subtest: auto-end on resolve without cb
     1..0
 ok 2 - auto-end on resolve without cb ___/# time=[0-9.]+(ms)?/~~~
 
-    # Subtest: rejected
-    not ok 1 - expected error
+# Subtest: rejected
+    not ok 1 - Error: expected error
       ---
-      {"at":{"column":34,"file":"test/test/promise-return-mocha.js","line":31},"message":"Error: expected error","source":"setTimeout(reject.bind(null, new Error('expected error'), 150))\n"}
+      {"at":{"column":34,"file":"test/test/promise-return-mocha.js","line":31},"message":"Error: expected error","source":"setTimeout(reject.bind(null, new Error('expected error'), 150))\n","test":"rejected"}
       ...
-    Bail out! # expected error
-Bail out! # expected error
+    Bail out! # Error: expected error
+Bail out! # Error: expected error
 
diff --git a/test/test/promise-return-mocha.tap b/test/test/promise-return-mocha.tap
index d1eab96..8a816a5 100644
--- a/test/test/promise-return-mocha.tap
+++ b/test/test/promise-return-mocha.tap
@@ -1,16 +1,16 @@
 TAP version 13
-    # Subtest: auto-end on resolve
+# Subtest: auto-end on resolve
     1..0
 ok 1 - auto-end on resolve ___/# time=[0-9.]+(ms)?/~~~
 
-    # Subtest: auto-end on resolve without cb
+# Subtest: auto-end on resolve without cb
     1..0
 ok 2 - auto-end on resolve without cb ___/# time=[0-9.]+(ms)?/~~~
 
-    # Subtest: rejected
-    not ok 1 - expected error
+# Subtest: rejected
+    not ok 1 - Error: expected error
       ---
-      {"at":{"column":34,"file":"test/test/promise-return-mocha.js","line":31},"message":"Error: expected error","source":"setTimeout(reject.bind(null, new Error('expected error'), 150))\n"}
+      {"at":{"column":34,"file":"test/test/promise-return-mocha.js","line":31},"message":"Error: expected error","source":"setTimeout(reject.bind(null, new Error('expected error'), 150))\n","test":"rejected"}
       ...
     1..1
     # failed 1 of 1 tests
@@ -19,10 +19,10 @@ not ok 3 - rejected ___/# time=[0-9.]+(ms)?/~~~
   {"at":{"column":1,"file":"test/test/promise-return-mocha.js","line":29},"results":{"count":1,"fail":1,"ok":false,"pass":0,"plan":{"end":1,"start":1}},"source":"describe('rejected', function (done) {\n"}
   ...
 
-    # Subtest: rejected without cb
-    not ok 1 - expected error
+# Subtest: rejected without cb
+    not ok 1 - Error: expected error
       ---
-      {"at":{"column":34,"file":"test/test/promise-return-mocha.js","line":37},"message":"Error: expected error","source":"setTimeout(reject.bind(null, new Error('expected error'), 150))\n"}
+      {"at":{"column":34,"file":"test/test/promise-return-mocha.js","line":37},"message":"Error: expected error","source":"setTimeout(reject.bind(null, new Error('expected error'), 150))\n","test":"rejected without cb"}
       ...
     1..1
     # failed 1 of 1 tests
diff --git a/test/test/promise-return.tap b/test/test/promise-return.tap
index 8ef4786..a3adda0 100644
--- a/test/test/promise-return.tap
+++ b/test/test/promise-return.tap
@@ -1,13 +1,13 @@
 TAP version 13
-    # Subtest: auto-end on resolve
+# Subtest: auto-end on resolve
     ok 1 - true is ok
     1..1
 ok 1 - auto-end on resolve ___/# time=[0-9.]+(ms)?/~~~
 
-    # Subtest: rejected
-    not ok 1 - expected error
+# Subtest: rejected
+    not ok 1 - Error: expected error
       ---
-      {"at":{"column":34,"file":"test/test/promise-return.js","line":22},"message":"Error: expected error","source":"setTimeout(reject.bind(null, new Error('expected error'), 150))\n"}
+      {"at":{"column":34,"file":"test/test/promise-return.js","line":22},"message":"Error: expected error","source":"setTimeout(reject.bind(null, new Error('expected error'), 150))\n","test":"rejected"}
       ...
     1..1
     # failed 1 of 1 tests
diff --git a/test/test/promise.js b/test/test/promise.js
new file mode 100644
index 0000000..c624b0e
--- /dev/null
+++ b/test/test/promise.js
@@ -0,0 +1,45 @@
+var t = require('../..')
+var P = typeof Promise === 'undefined' ? require('bluebird') : Promise
+t.test('one', function (t) {
+  return t.test('two', function (t) {
+    return t.test('child of 2', function (t) {
+      return t.test('grandchild of 2', function (t) {
+        t.pass('this is fine')
+        t.end()
+      })
+    })
+  }).then(function (t) {
+    return t.test('second child of 2', function (t) {
+      t.end()
+    })
+  })
+}).then(function (t) {
+  return t.test('three', function (t) {
+    setTimeout(function () {
+      t.end()
+    })
+  })
+}).then(function (t) {
+  return new P(function (resolve, reject) {
+    setTimeout(function () {
+      resolve(1)
+    })
+  }).then(function (x) {
+    return t.test(function some_function_name (t) {
+      t.equal(x, 1, 'resolved to 1')
+      t.end()
+    })
+  })
+}).then(function (t) {
+  return (process.argv[2] === 'child') ?
+    t.test(function spawned (t) {
+      t.pass('in the spawned child')
+      t.end()
+    }) :
+    t.spawn(process.execPath, [__filename, 'child'])
+}).then(function (t) {
+  t.equal(t._name, 'TAP', 'is root tap test')
+  t.notOk(t._parent, 'has no parent')
+  t.pass('this is fine')
+  t.end()
+})
diff --git a/test/test/promise.tap b/test/test/promise.tap
new file mode 100644
index 0000000..784be2a
--- /dev/null
+++ b/test/test/promise.tap
@@ -0,0 +1,80 @@
+TAP version 13
+# Subtest: one
+    # Subtest: two
+        # Subtest: child of 2
+            # Subtest: grandchild of 2
+                ok 1 - this is fine
+                1..1
+            ok 1 - grandchild of 2 ___/# time=[0-9.]+(ms)?/~~~
+
+            1..1
+        ok 1 - child of 2 ___/# time=[0-9.]+(ms)?/~~~
+
+        1..1
+    ok 1 - two ___/# time=[0-9.]+(ms)?/~~~
+
+    # Subtest: second child of 2
+        1..0
+    ok 2 - second child of 2 ___/# time=[0-9.]+(ms)?/~~~
+
+    1..2
+ok 1 - one ___/# time=[0-9.]+(ms)?/~~~
+
+# Subtest: three
+    1..0
+ok 2 - three ___/# time=[0-9.]+(ms)?/~~~
+
+# Subtest: some_function_name
+    ok 1 - resolved to 1
+    1..1
+ok 3 - some_function_name ___/# time=[0-9.]+(ms)?/~~~
+
+# Subtest: ___/.*/~~~promise.js child
+    # Subtest: one
+        # Subtest: two
+            # Subtest: child of 2
+                # Subtest: grandchild of 2
+                    ok 1 - this is fine
+                    1..1
+                ok 1 - grandchild of 2 ___/# time=[0-9.]+(ms)?/~~~
+
+                1..1
+            ok 1 - child of 2 ___/# time=[0-9.]+(ms)?/~~~
+
+            1..1
+        ok 1 - two ___/# time=[0-9.]+(ms)?/~~~
+
+        # Subtest: second child of 2
+            1..0
+        ok 2 - second child of 2 ___/# time=[0-9.]+(ms)?/~~~
+
+        1..2
+    ok 1 - one ___/# time=[0-9.]+(ms)?/~~~
+
+    # Subtest: three
+        1..0
+    ok 2 - three ___/# time=[0-9.]+(ms)?/~~~
+
+    # Subtest: some_function_name
+        ok 1 - resolved to 1
+        1..1
+    ok 3 - some_function_name ___/# time=[0-9.]+(ms)?/~~~
+
+    # Subtest: spawned
+        ok 1 - in the spawned child
+        1..1
+    ok 4 - spawned ___/# time=[0-9.]+(ms)?/~~~
+
+    ok 5 - is root tap test
+    ok 6 - has no parent
+    ok 7 - this is fine
+    1..7
+    ___/# time=[0-9.]+(ms)?/~~~
+ok 4 - ___/.*/~~~promise.js child ___/# time=[0-9.]+(ms)?/~~~
+
+ok 5 - is root tap test
+ok 6 - has no parent
+ok 7 - this is fine
+1..7
+___/# time=[0-9.]+(ms)?/~~~
+
diff --git a/test/test/root-teardown-bail.tap b/test/test/root-teardown-bail.tap
index a45e0ef..1baac92 100644
--- a/test/test/root-teardown-bail.tap
+++ b/test/test/root-teardown-bail.tap
@@ -1,5 +1,5 @@
 TAP version 13
-    # Subtest: child test
+# Subtest: child test
     ok 1 - this is ok
     1..1
 ok 1 - child test ___/# time=[0-9.]+(ms)?/~~~
diff --git a/test/test/root-teardown.tap b/test/test/root-teardown.tap
index a45e0ef..1baac92 100644
--- a/test/test/root-teardown.tap
+++ b/test/test/root-teardown.tap
@@ -1,5 +1,5 @@
 TAP version 13
-    # Subtest: child test
+# Subtest: child test
     ok 1 - this is ok
     1..1
 ok 1 - child test ___/# time=[0-9.]+(ms)?/~~~
diff --git a/test/test/spawn-bail.tap b/test/test/spawn-bail.tap
index dbe5f0a..4c963cc 100644
--- a/test/test/spawn-bail.tap
+++ b/test/test/spawn-bail.tap
@@ -1,14 +1,14 @@
 TAP version 13
-    # Subtest: ___/.*/~~~spawn.js child
-        # Subtest: nesting
+# Subtest: ___/.*/~~~spawn.js child
+    # Subtest: nesting
         1..2
-            # Subtest: first
+        # Subtest: first
             1..2
             ok 1 - true is ok
             ok 2 - doag is also okay
         ok 1 - first ___/# time=[0-9.]+(ms)?/~~~
 
-            # Subtest: second
+        # Subtest: second
             ok 1 - but that is ok
             ok 2 - this passes
             not ok 3 - nested failure
diff --git a/test/test/spawn-empty-bail.tap b/test/test/spawn-empty-bail.tap
index 2f29bea..e591999 100644
--- a/test/test/spawn-empty-bail.tap
+++ b/test/test/spawn-empty-bail.tap
@@ -1,5 +1,5 @@
 TAP version 13
-    # Subtest: ___/.*/~~~spawn-empty.js child
+# Subtest: ___/.*/~~~spawn-empty.js child
     1..0
 ok 1 - ___/.*/~~~spawn-empty.js child # SKIP No tests found
 
diff --git a/test/test/spawn-empty.tap b/test/test/spawn-empty.tap
index 2f29bea..e591999 100644
--- a/test/test/spawn-empty.tap
+++ b/test/test/spawn-empty.tap
@@ -1,5 +1,5 @@
 TAP version 13
-    # Subtest: ___/.*/~~~spawn-empty.js child
+# Subtest: ___/.*/~~~spawn-empty.js child
     1..0
 ok 1 - ___/.*/~~~spawn-empty.js child # SKIP No tests found
 
diff --git a/test/test/spawn-failures-bail.tap b/test/test/spawn-failures-bail.tap
new file mode 100644
index 0000000..2b5cc2d
--- /dev/null
+++ b/test/test/spawn-failures-bail.tap
@@ -0,0 +1,9 @@
+TAP version 13
+# Subtest: spawn that throws
+    not ok 1 - Error: now is fine
+      ---
+      {"at":{"column":14,"file":"test/test/spawn-failures.js","line":25},"message":"Error: now is fine","source":"throwNow = new Error('now is fine')\n","test":"spawn that throws"}
+      ...
+    Bail out! # Error: now is fine
+Bail out! # Error: now is fine
+
diff --git a/test/test/spawn-failures.js b/test/test/spawn-failures.js
new file mode 100644
index 0000000..b26d517
--- /dev/null
+++ b/test/test/spawn-failures.js
@@ -0,0 +1,40 @@
+var cp = require('child_process')
+var spawn = cp.spawn
+cp.spawn = hijackedSpawn
+
+var throwNow = false
+var throwLater = false
+function hijackedSpawn (cmd, args, options) {
+  if (throwNow) {
+    throw throwNow
+  }
+  var child = spawn.call(cp, cmd, args, options)
+  if (throwLater) {
+    setTimeout(function () {
+      child.emit('error', throwLater)
+    })
+  }
+  return child
+}
+
+var t = require('../..')
+var ok = require.resolve('./ok.js')
+var node = process.execPath
+
+t.test('spawn that throws', function (t) {
+  throwNow = new Error('now is fine')
+  t.tearDown(function () {
+    throwNow = false
+  })
+  t.spawn(node, [ok])
+  t.end()
+})
+
+t.test('spawn that throws', function (t) {
+  throwLater = new Error('later is fine')
+  t.tearDown(function () {
+    throwLater = false
+  })
+  t.spawn(node, [ok])
+  t.end()
+})
diff --git a/test/test/spawn-failures.tap b/test/test/spawn-failures.tap
new file mode 100644
index 0000000..f073d30
--- /dev/null
+++ b/test/test/spawn-failures.tap
@@ -0,0 +1,30 @@
+TAP version 13
+# Subtest: spawn that throws
+    not ok 1 - Error: now is fine
+      ---
+      {"at":{"column":14,"file":"test/test/spawn-failures.js","line":25},"message":"Error: now is fine","source":"throwNow = new Error('now is fine')\n","test":"spawn that throws"}
+      ...
+    1..1
+    # failed 1 of 1 tests
+not ok 1 - spawn that throws ___/# time=[0-9.]+(ms)?/~~~
+  ---
+  {"at":{"column":3,"file":"test/test/spawn-failures.js","line":24},"results":{"count":1,"fail":1,"ok":false,"pass":0,"plan":{"end":1,"start":1}},"source":"t.test('spawn that throws', function (t) {\n"}
+  ...
+
+# Subtest: spawn that throws
+    # Subtest: ./test/test/ok.js
+    not ok 1 - Error: later is fine
+      ---
+      {"at":{"column":16,"file":"test/test/spawn-failures.js","line":34},"message":"Error: later is fine","source":"throwLater = new Error('later is fine')\n","test":"spawn that throws"}
+      ...
+    1..1
+    # failed 1 of 1 tests
+not ok 2 - spawn that throws ___/# time=[0-9.]+(ms)?/~~~
+  ---
+  {"at":{"column":3,"file":"test/test/spawn-failures.js","line":33},"results":{"count":1,"fail":1,"ok":false,"pass":0,"plan":{"end":1,"start":1}},"source":"t.test('spawn that throws', function (t) {\n"}
+  ...
+
+1..2
+# failed 2 of 2 tests
+___/# time=[0-9.]+(ms)?/~~~
+
diff --git a/test/test/spawn-stderr-bail.tap b/test/test/spawn-stderr-bail.tap
index 2bb908e..1b55af6 100644
--- a/test/test/spawn-stderr-bail.tap
+++ b/test/test/spawn-stderr-bail.tap
@@ -1,5 +1,5 @@
 TAP version 13
-    # Subtest: ___/.*/~~~spawn-stderr.js child
+# Subtest: ___/.*/~~~spawn-stderr.js child
     stdout
     ok 1 - this is ok
     1..1
diff --git a/test/test/spawn-stderr.tap b/test/test/spawn-stderr.tap
index 2bb908e..1b55af6 100644
--- a/test/test/spawn-stderr.tap
+++ b/test/test/spawn-stderr.tap
@@ -1,5 +1,5 @@
 TAP version 13
-    # Subtest: ___/.*/~~~spawn-stderr.js child
+# Subtest: ___/.*/~~~spawn-stderr.js child
     stdout
     ok 1 - this is ok
     1..1
diff --git a/test/test/spawn.tap b/test/test/spawn.tap
index 8d4b660..f550cac 100644
--- a/test/test/spawn.tap
+++ b/test/test/spawn.tap
@@ -1,14 +1,14 @@
 TAP version 13
-    # Subtest: ___/.*/~~~spawn.js child
-        # Subtest: nesting
+# Subtest: ___/.*/~~~spawn.js child
+    # Subtest: nesting
         1..2
-            # Subtest: first
+        # Subtest: first
             1..2
             ok 1 - true is ok
             ok 2 - doag is also okay
         ok 1 - first ___/# time=[0-9.]+(ms)?/~~~
 
-            # Subtest: second
+        # Subtest: second
             ok 1 - but that is ok
             ok 2 - this passes
             not ok 3 - nested failure
@@ -21,6 +21,7 @@ TAP version 13
           ---
           {"at":{"column":5,"file":"test/test/spawn.js","line":18},"results":{"count":3,"fail":1,"ok":false,"pass":2,"plan":{"end":3,"start":1}},"source":"t.test('second', function (tt) {\n"}
           ...
+
         # failed 1 of 2 tests
     not ok 1 - nesting ___/# time=[0-9.]+(ms)?/~~~
       ---
@@ -32,7 +33,7 @@ TAP version 13
       ---
       {"at":{"column":3,"file":"test/test/spawn.js","line":36},"source":"t.fail('this fails')\n"}
       ...
-        # Subtest: async kid
+    # Subtest: async kid
         1..2
         ok 1 - second timeout
         not ok 2 - first timeout
@@ -51,18 +52,18 @@ TAP version 13
     ___/# time=[0-9.]+(ms)?/~~~
 not ok 1 - ___/.*/~~~spawn.js child ___/# time=[0-9.]+(ms)?/~~~
   ---
-  {"arguments":["___/.*/~~~spawn.js","child"],"at":{"column":5,"file":"test/test/spawn.js","line":8},"command":"___/.*(node|iojs)/~~~","results":{"count":5,"fail":3,"ok":false,"pass":2,"plan":{"end":5,"start":1}},"source":"t.spawn(process.execPath, [__filename, 'child'])\n"}
+  {"arguments":["___/.*/~~~spawn.js","child"],"at":{"column":5,"file":"test/test/spawn.js","line":8},"command":"___/.*(node|iojs)(.exe)?/~~~","results":{"count":5,"fail":3,"ok":false,"pass":2,"plan":{"end":5,"start":1}},"source":"t.spawn(process.execPath, [__filename, 'child'])\n"}
   ...
 
-    # Subtest: nesting
+# Subtest: nesting
     1..2
-        # Subtest: first
+    # Subtest: first
         1..2
         ok 1 - true is ok
         ok 2 - doag is also okay
     ok 1 - first ___/# time=[0-9.]+(ms)?/~~~
 
-        # Subtest: second
+    # Subtest: second
         ok 1 - but that is ok
         ok 2 - this passes
         not ok 3 - nested failure
@@ -75,6 +76,7 @@ not ok 1 - ___/.*/~~~spawn.js child ___/# time=[0-9.]+(ms)?/~~~
       ---
       {"at":{"column":5,"file":"test/test/spawn.js","line":18},"results":{"count":3,"fail":1,"ok":false,"pass":2,"plan":{"end":3,"start":1}},"source":"t.test('second', function (tt) {\n"}
       ...
+
     # failed 1 of 2 tests
 not ok 2 - nesting ___/# time=[0-9.]+(ms)?/~~~
   ---
@@ -86,7 +88,7 @@ not ok 4 - this fails
   ---
   {"at":{"column":3,"file":"test/test/spawn.js","line":36},"source":"t.fail('this fails')\n"}
   ...
-    # Subtest: async kid
+# Subtest: async kid
     1..2
     ok 1 - second timeout
     not ok 2 - first timeout
diff --git a/test/test/sync-timeout-bail.tap b/test/test/sync-timeout-bail.tap
new file mode 100644
index 0000000..1b73fdc
--- /dev/null
+++ b/test/test/sync-timeout-bail.tap
@@ -0,0 +1,10 @@
+TAP version 13
+# Subtest: t.end()
+    ok 1 - did not timeout
+    not ok 2 - timeout!
+      ---
+      {"at":{"column":3,"file":"test/test/sync-timeout.js","line":15},"expired":"t.end()","source":"t.test('t.end()', opt, function (t) {\n","timeout":50}
+      ...
+    Bail out! # timeout!
+Bail out! # timeout!
+
diff --git a/test/test/sync-timeout.js b/test/test/sync-timeout.js
new file mode 100644
index 0000000..26f3323
--- /dev/null
+++ b/test/test/sync-timeout.js
@@ -0,0 +1,25 @@
+var t = require('../..')
+
+var short = 50
+var long = 100
+
+var opt = { timeout: short }
+
+function sleep () {
+  var start = Date.now()
+  while (Date.now() - start < long) {
+    // (-.-)
+  }
+}
+
+t.test('t.end()', opt, function (t) {
+  sleep()
+  t.pass('did not timeout')
+  t.end()
+})
+
+t.test('plan()', opt, function (t) {
+  t.plan(1)
+  sleep()
+  t.pass('did not time out')
+})
diff --git a/test/test/sync-timeout.tap b/test/test/sync-timeout.tap
new file mode 100644
index 0000000..2353977
--- /dev/null
+++ b/test/test/sync-timeout.tap
@@ -0,0 +1,27 @@
+TAP version 13
+# Subtest: t.end()
+    ok 1 - did not timeout
+    not ok 2 - timeout!
+      ---
+      {"at":{"column":3,"file":"test/test/sync-timeout.js","line":15},"expired":"t.end()","source":"t.test('t.end()', opt, function (t) {\n","timeout":50}
+      ...
+    1..2
+    # failed 1 of 2 tests
+not ok 1 - t.end() ___/# time=[0-9.]+(ms)?/~~~
+  ---
+  {"at":{"column":3,"file":"test/test/sync-timeout.js","line":15},"results":{"count":2,"fail":1,"ok":false,"pass":1,"plan":{"end":2,"start":1}},"source":"t.test('t.end()', opt, function (t) {\n","timeout":50}
+  ...
+
+    # failed 1 of 2 tests
+# Subtest: plan()
+    1..1
+    ok 1 - did not time out
+not ok 2 - plan() ___/# time=[0-9.]+(ms)?/~~~
+  ---
+  {"at":{"column":3,"file":"test/test/sync-timeout.js","line":15},"results":{"count":1,"ok":false,"pass":1,"plan":{"end":1,"start":1}},"source":"t.test('t.end()', opt, function (t) {\n","timeout":50}
+  ...
+
+1..2
+# failed 2 of 2 tests
+___/# time=[0-9.]+(ms)?/~~~
+
diff --git a/test/test/throw-bail.tap b/test/test/throw-bail.tap
index 5d22d0f..b8590c3 100644
--- a/test/test/throw-bail.tap
+++ b/test/test/throw-bail.tap
@@ -1,16 +1,16 @@
 TAP version 13
-    # Subtest: nesting
+# Subtest: nesting
     1..3
-        # Subtest: first
+    # Subtest: first
         1..2
         ok 1 - true is ok
         ok 2 - doag is also okay
     ok 1 - first ___/# time=[0-9.]+(ms)?/~~~
 
-        # Subtest: async thrower
+    # Subtest: async thrower
         not ok 1 - Error: THINK FAST! and also lines so many
           ---
-          {"at":{"column":16,"file":"test/test/throw.js","function":"null._onTimeout","line":12},"jerk":true,"message":"Error: THINK FAST!\nand also lines\nso many","source":"var er = new Error('THINK FAST!\\nand also lines\\nso many')\n","test":"async thrower"}
+          {"at":{"column":16,"file":"test/test/throw.js","line":12},"jerk":true,"message":"Error: THINK FAST!\nand also lines\nso many","source":"var er = new Error('THINK FAST!\\nand also lines\\nso many')\n","test":"async thrower"}
           ...
         Bail out! # Error: THINK FAST! and also lines so many
 Bail out! # Error: THINK FAST! and also lines so many
diff --git a/test/test/throw-twice-bail.tap b/test/test/throw-twice-bail.tap
new file mode 100644
index 0000000..b26d3cd
--- /dev/null
+++ b/test/test/throw-twice-bail.tap
@@ -0,0 +1,12 @@
+TAP version 13
+# Subtest: fine
+    ok 1 - fine
+    1..1
+ok 1 - fine ___/# time=[0-9.]+(ms)?/~~~
+
+not ok 2 - Error: this one
+  ---
+  {"at":{"column":20,"file":"test/test/throw-twice.js","line":7},"message":"Error: this one","source":"throw { stack: new Error('this one').stack }\n","test":"TAP"}
+  ...
+Bail out! # Error: this one
+
diff --git a/test/test/throw-twice.js b/test/test/throw-twice.js
new file mode 100644
index 0000000..7a66e54
--- /dev/null
+++ b/test/test/throw-twice.js
@@ -0,0 +1,12 @@
+var t = require('../..')
+
+t.test('fine', function (t) {
+  t.pass('fine')
+  t.end()
+  setTimeout(function () {
+    throw { stack: new Error('this one').stack }
+  })
+  setTimeout(function () {
+    throw new Error('not this one')
+  }, 100)
+})
diff --git a/test/test/throw-twice.tap b/test/test/throw-twice.tap
new file mode 100644
index 0000000..b1161b8
--- /dev/null
+++ b/test/test/throw-twice.tap
@@ -0,0 +1,14 @@
+TAP version 13
+# Subtest: fine
+    ok 1 - fine
+    1..1
+ok 1 - fine ___/# time=[0-9.]+(ms)?/~~~
+
+not ok 2 - Error: this one
+  ---
+  {"at":{"column":20,"file":"test/test/throw-twice.js","line":7},"message":"Error: this one","source":"throw { stack: new Error('this one').stack }\n","test":"TAP"}
+  ...
+1..2
+# failed 1 of 2 tests
+___/# time=[0-9.]+(ms)?/~~~
+
diff --git a/test/test/throw.tap b/test/test/throw.tap
index cd0e14c..26b23d4 100644
--- a/test/test/throw.tap
+++ b/test/test/throw.tap
@@ -1,16 +1,16 @@
 TAP version 13
-    # Subtest: nesting
+# Subtest: nesting
     1..3
-        # Subtest: first
+    # Subtest: first
         1..2
         ok 1 - true is ok
         ok 2 - doag is also okay
     ok 1 - first ___/# time=[0-9.]+(ms)?/~~~
 
-        # Subtest: async thrower
+    # Subtest: async thrower
         not ok 1 - Error: THINK FAST! and also lines so many
           ---
-          {"at":{"column":16,"file":"test/test/throw.js","function":"null._onTimeout","line":12},"jerk":true,"message":"Error: THINK FAST!\nand also lines\nso many","source":"var er = new Error('THINK FAST!\\nand also lines\\nso many')\n","test":"async thrower"}
+          {"at":{"column":16,"file":"test/test/throw.js","line":12},"jerk":true,"message":"Error: THINK FAST!\nand also lines\nso many","source":"var er = new Error('THINK FAST!\\nand also lines\\nso many')\n","test":"async thrower"}
           ...
         1..1
         # failed 1 of 1 tests
@@ -19,7 +19,7 @@ TAP version 13
       {"at":{"column":5,"file":"test/test/throw.js","line":10},"results":{"count":1,"fail":1,"ok":false,"pass":0,"plan":{"end":1,"start":1}},"source":"t.test('async thrower', function (tt) {\n"}
       ...
 
-        # Subtest: thrower
+    # Subtest: thrower
         not ok 1 - Error: here hold this for a second
           ---
           {"at":{"column":14,"file":"test/test/throw.js","line":18},"message":"Error: here hold this for a second","source":"var er = new Error('here hold this for a second')\n","syscall":"ring ring","test":"thrower"}
@@ -41,7 +41,7 @@ not ok 3 - this fails
   ---
   {"at":{"column":3,"file":"test/test/throw.js","line":25},"source":"t.fail('this fails')\n"}
   ...
-    # Subtest: async kid
+# Subtest: async kid
     1..2
     Bail out! # cannot continue
 Bail out! # cannot continue
diff --git a/test/test/throws-and-plans-bail.tap b/test/test/throws-and-plans-bail.tap
index b5f9332..034a38a 100644
--- a/test/test/throws-and-plans-bail.tap
+++ b/test/test/throws-and-plans-bail.tap
@@ -1,8 +1,8 @@
 TAP version 13
 ok 1 - expect truthy value
-    # Subtest: plans of 1
+# Subtest: plans of 1
     ok 1 - before sync thrower
-        # Subtest: sync thrower
+    # Subtest: sync thrower
         1..1
         ok 1 - before the bomb
     ok 2 - sync thrower ___/# time=[0-9.]+(ms)?/~~~
diff --git a/test/test/throws-and-plans.tap b/test/test/throws-and-plans.tap
index a0e3d19..99bc19f 100644
--- a/test/test/throws-and-plans.tap
+++ b/test/test/throws-and-plans.tap
@@ -1,8 +1,8 @@
 TAP version 13
 ok 1 - expect truthy value
-    # Subtest: plans of 1
+# Subtest: plans of 1
     ok 1 - before sync thrower
-        # Subtest: sync thrower
+    # Subtest: sync thrower
         1..1
         ok 1 - before the bomb
     ok 2 - sync thrower ___/# time=[0-9.]+(ms)?/~~~
@@ -11,7 +11,7 @@ ok 1 - expect truthy value
       ---
       {"at":{"column":11,"file":"test/test/throws-and-plans.js","line":10},"message":"Error: pwnSync","source":"throw new Error('pwnSync')\n","test":"sync thrower"}
       ...
-        # Subtest: async thrower
+    # Subtest: async thrower
         1..3
         ok 1 - before set the bomb
         ok 2 - after set the bomb
@@ -26,7 +26,7 @@ not ok 2 - plans of 1 ___/# time=[0-9.]+(ms)?/~~~
   {"at":{"column":3,"file":"test/test/throws-and-plans.js","line":4},"results":{"count":5,"fail":1,"ok":false,"pass":4,"plan":{"end":5,"start":1}},"source":"t.test('plans of 1', function (t) {\n"}
   ...
 
-    # Subtest: no assert only throw
+# Subtest: no assert only throw
     not ok 1 - AssertionError: false is truthy right?
       ---
       {"actual":false,"at":{"column":3,"file":"test/test/throws-and-plans.js","line":29},"expected":true,"generatedMessage":false,"message":"AssertionError: false is truthy right?","name":"AssertionError","operator":"==","source":"assert(false, 'false is truthy right?')\n","test":"no assert only throw"}
@@ -38,9 +38,9 @@ not ok 3 - no assert only throw ___/# time=[0-9.]+(ms)?/~~~
   {"at":{"column":3,"file":"test/test/throws-and-plans.js","line":26},"results":{"count":1,"fail":1,"ok":false,"pass":0,"plan":{"end":1,"start":1}},"source":"t.test('no assert only throw', function (t) {\n"}
   ...
 
-    # Subtest: plans of 8
+# Subtest: plans of 8
     ok 1 - before child
-        # Subtest: sync thrower
+    # Subtest: sync thrower
         1..8
         ok 1 - before the bomb
         not ok 2 - Error: pwnSync
@@ -59,13 +59,13 @@ not ok 3 - no assert only throw ___/# time=[0-9.]+(ms)?/~~~
       {"at":{"column":5,"file":"test/test/throws-and-plans.js","line":35},"results":{"count":8,"fail":7,"ok":false,"pass":1,"plan":{"end":8,"start":1}},"source":"t.test('sync thrower', function (tt) {\n"}
       ...
 
-        # Subtest: async thrower
+    # Subtest: async thrower
         1..8
         ok 1 - before set the bomb
         ok 2 - after set the bomb
         not ok 3 - Error: pwn
           ---
-          {"at":{"column":13,"file":"test/test/throws-and-plans.js","function":"null._onTimeout","line":18},"message":"Error: pwn","source":"throw new Error('pwn')\n","test":"async thrower"}
+          {"at":{"column":13,"file":"test/test/throws-and-plans.js","line":18},"message":"Error: pwn","source":"throw new Error('pwn')\n","test":"async thrower"}
           ...
         not ok 4 - missing test
         not ok 5 - missing test
@@ -88,7 +88,7 @@ not ok 4 - plans of 8 ___/# time=[0-9.]+(ms)?/~~~
 
 not ok 5 - Error: pwn
   ---
-  {"at":{"column":13,"file":"test/test/throws-and-plans.js","function":"null._onTimeout","line":51},"message":"Error: pwn","source":"throw new Error('pwn')\n","test":"TAP"}
+  {"at":{"column":13,"file":"test/test/throws-and-plans.js","line":51},"message":"Error: pwn","source":"throw new Error('pwn')\n","test":"TAP"}
   ...
 1..5
 # failed 4 of 5 tests
diff --git a/test/test/throws-bail.tap b/test/test/throws-bail.tap
index 1936582..74bf51c 100644
--- a/test/test/throws-bail.tap
+++ b/test/test/throws-bail.tap
@@ -1,5 +1,5 @@
 TAP version 13
-    # Subtest: throws should match a regex
+# Subtest: throws should match a regex
     1..2
     ok 1 - passing_thrower
     not ok 2 - failing_thrower
diff --git a/test/test/throws.tap b/test/test/throws.tap
index f7d72b9..f6b4e5a 100644
--- a/test/test/throws.tap
+++ b/test/test/throws.tap
@@ -1,5 +1,5 @@
 TAP version 13
-    # Subtest: throws should match a regex
+# Subtest: throws should match a regex
     1..2
     ok 1 - passing_thrower
     not ok 2 - failing_thrower
diff --git a/test/test/timeout-bail.tap b/test/test/timeout-bail.tap
index d324b73..6788089 100644
--- a/test/test/timeout-bail.tap
+++ b/test/test/timeout-bail.tap
@@ -1,8 +1,8 @@
 TAP version 13
-    # Subtest: parent of timeout test
-        # Subtest: timeout test
-            # Subtest: this never completes
-                # Subtest: baby
+# Subtest: parent of timeout test
+    # Subtest: timeout test
+        # Subtest: this never completes
+            # Subtest: baby
                 ok 1 - expect truthy value
                 not ok 2 - timeout!
                   ---
diff --git a/test/test/timeout-via-runner-bail.tap b/test/test/timeout-via-runner-bail.tap
new file mode 100644
index 0000000..b52d819
--- /dev/null
+++ b/test/test/timeout-via-runner-bail.tap
@@ -0,0 +1,16 @@
+TAP version 13
+# Subtest: child
+    ok 1 - this is fine 1
+    ok 2 - this is fine 2
+    ok 3 - this is fine 3
+    # Subtest: child test
+        1..3
+        ok 1 - this is fine 4
+        ok 2 - this is fine 5
+        not ok 3 - timeout!
+          ---
+          {"handles":[{"type":"Timer"}],"signal":"SIGTERM"}
+          ...
+        Bail out! # timeout!
+Bail out! # timeout!
+
diff --git a/test/test/timeout-via-runner-ignore-sigterm-bail.tap b/test/test/timeout-via-runner-ignore-sigterm-bail.tap
new file mode 100644
index 0000000..813dad8
--- /dev/null
+++ b/test/test/timeout-via-runner-ignore-sigterm-bail.tap
@@ -0,0 +1,18 @@
+TAP version 13
+# Subtest: child
+    ok 1 - this is fine 1
+    ok 2 - this is fine 2
+    ok 3 - this is fine 3
+    # Subtest: child test
+        1..3
+        ok 1 - this is fine 4
+        ok 2 - this is fine 5
+    yolo
+
+        not ok 3 - timeout
+          ---
+          {"at":{"column":5,"file":"test/test/timeout-via-runner-ignore-sigterm.js","line":19},"failure":"timeout","timeout":1000}
+          ...
+        Bail out! # timeout
+Bail out! # timeout
+
diff --git a/test/test/timeout-via-runner-ignore-sigterm.js b/test/test/timeout-via-runner-ignore-sigterm.js
new file mode 100644
index 0000000..14fac29
--- /dev/null
+++ b/test/test/timeout-via-runner-ignore-sigterm.js
@@ -0,0 +1,20 @@
+var t = require('../..')
+
+if (process.argv[2] === 'child') {
+  process.on('SIGTERM', function () {
+    console.log('yolo')
+  })
+  t.pass('this is fine 1')
+  t.pass('this is fine 2')
+  t.pass('this is fine 3')
+  t.test('child test', function (t) {
+    t.plan(3)
+    t.pass('this is fine 4')
+    t.pass('this is fine 5')
+    setTimeout(function (res) {
+      t.pass('request complete')
+    }, 20000)
+  })
+} else {
+  t.spawn(process.execPath, [__filename, 'child'], {}, 'child', { timeout: 1000 })
+}
diff --git a/test/test/timeout-via-runner-ignore-sigterm.tap b/test/test/timeout-via-runner-ignore-sigterm.tap
new file mode 100644
index 0000000..83ffd80
--- /dev/null
+++ b/test/test/timeout-via-runner-ignore-sigterm.tap
@@ -0,0 +1,31 @@
+TAP version 13
+# Subtest: child
+    ok 1 - this is fine 1
+    ok 2 - this is fine 2
+    ok 3 - this is fine 3
+    # Subtest: child test
+        1..3
+        ok 1 - this is fine 4
+        ok 2 - this is fine 5
+    yolo
+
+        not ok 3 - timeout
+          ---
+          {"at":{"column":5,"file":"test/test/timeout-via-runner-ignore-sigterm.js","line":19},"failure":"timeout","timeout":1000}
+          ...
+
+    not ok 4 - timeout
+      ---
+      {"at":{"column":5,"file":"test/test/timeout-via-runner-ignore-sigterm.js","line":19},"failure":"timeout","timeout":1000}
+      ...
+
+    1..4 # timeout
+not ok 1 - child ___/# time=[0-9.]+(ms)?/~~~
+  ---
+  {"arguments":["___/.*/~~~timeout-via-runner-ignore-sigterm.js","child"],"at":{"column":5,"file":"test/test/timeout-via-runner-ignore-sigterm.js","line":19},"command":"___/.*(node|iojs)(.exe)?/~~~","failure":"timeout","results":{"count":4,"fail":1,"ok":false,"pass":3,"plan":{"end":4,"start":1}},"signal":"SIGKILL","source":"t.spawn(process.execPath, [__filename, 'child'], {}, 'child', { timeout: 1000 })\n","timeout":1000}
+  ...
+
+1..1
+# failed 1 of 1 tests
+___/# time=[0-9.]+(ms)?/~~~
+
diff --git a/test/test/timeout-via-runner-no-plan-bail.tap b/test/test/timeout-via-runner-no-plan-bail.tap
new file mode 100644
index 0000000..e13f28e
--- /dev/null
+++ b/test/test/timeout-via-runner-no-plan-bail.tap
@@ -0,0 +1,15 @@
+TAP version 13
+# Subtest: child
+    ok 1 - this is fine 1
+    ok 2 - this is fine 2
+    ok 3 - this is fine 3
+    # Subtest: child test
+        ok 1 - this is fine 4
+        ok 2 - this is fine 5
+        not ok 3 - timeout!
+          ---
+          {"handles":[{"type":"Timer"}],"signal":"SIGTERM"}
+          ...
+        Bail out! # timeout!
+Bail out! # timeout!
+
diff --git a/test/test/timeout-via-runner-no-plan.js b/test/test/timeout-via-runner-no-plan.js
new file mode 100644
index 0000000..4e43635
--- /dev/null
+++ b/test/test/timeout-via-runner-no-plan.js
@@ -0,0 +1,17 @@
+var t = require('../..')
+
+if (process.argv[2] === 'child') {
+  t.pass('this is fine 1')
+  t.pass('this is fine 2')
+  t.pass('this is fine 3')
+  t.test('child test', function (t) {
+    t.pass('this is fine 4')
+    t.pass('this is fine 5')
+    setTimeout(function (res) {
+      t.pass('request complete')
+      t.end()
+    }, 20000)
+  })
+} else {
+  t.spawn(process.execPath, [__filename, 'child'], {}, 'child', { timeout: 1000 })
+}
diff --git a/test/test/timeout-via-runner-no-plan.tap b/test/test/timeout-via-runner-no-plan.tap
new file mode 100644
index 0000000..a5cc2f4
--- /dev/null
+++ b/test/test/timeout-via-runner-no-plan.tap
@@ -0,0 +1,31 @@
+TAP version 13
+# Subtest: child
+    ok 1 - this is fine 1
+    ok 2 - this is fine 2
+    ok 3 - this is fine 3
+    # Subtest: child test
+        ok 1 - this is fine 4
+        ok 2 - this is fine 5
+        not ok 3 - timeout!
+          ---
+          {"handles":[{"type":"Timer"}],"signal":"SIGTERM"}
+          ...
+        1..3
+        # failed 1 of 3 tests
+    not ok 4 - child test ___/# time=[0-9.]+(ms)?/~~~
+      ---
+      {"at":{"column":5,"file":"test/test/timeout-via-runner-no-plan.js","line":7},"results":{"count":3,"fail":1,"ok":false,"pass":2,"plan":{"end":3,"start":1}},"source":"t.test('child test', function (t) {\n"}
+      ...
+
+    1..4
+    # failed 1 of 4 tests
+    ___/# time=[0-9.]+(ms)?/~~~
+not ok 1 - child ___/# time=[0-9.]+(ms)?/~~~
+  ---
+  {"arguments":["___/.*/~~~timeout-via-runner-no-plan.js","child"],"at":{"column":5,"file":"test/test/timeout-via-runner-no-plan.js","line":16},"command":"___/.*(node|iojs)(.exe)?/~~~","failure":"timeout","results":{"count":4,"fail":1,"ok":false,"pass":3,"plan":{"end":4,"start":1}},"signal":"SIGTERM","source":"t.spawn(process.execPath, [__filename, 'child'], {}, 'child', { timeout: 1000 })\n","timeout":1000}
+  ...
+
+1..1
+# failed 1 of 1 tests
+___/# time=[0-9.]+(ms)?/~~~
+
diff --git a/test/test/timeout-via-runner.js b/test/test/timeout-via-runner.js
new file mode 100644
index 0000000..87da262
--- /dev/null
+++ b/test/test/timeout-via-runner.js
@@ -0,0 +1,17 @@
+var t = require('../..')
+
+if (process.argv[2] === 'child') {
+  t.pass('this is fine 1')
+  t.pass('this is fine 2')
+  t.pass('this is fine 3')
+  t.test('child test', function (t) {
+    t.plan(3)
+    t.pass('this is fine 4')
+    t.pass('this is fine 5')
+    setTimeout(function (res) {
+      t.pass('request complete')
+    }, 20000)
+  })
+} else {
+  t.spawn(process.execPath, [__filename, 'child'], {}, 'child', { timeout: 1000 })
+}
diff --git a/test/test/timeout-via-runner.tap b/test/test/timeout-via-runner.tap
new file mode 100644
index 0000000..007fb13
--- /dev/null
+++ b/test/test/timeout-via-runner.tap
@@ -0,0 +1,31 @@
+TAP version 13
+# Subtest: child
+    ok 1 - this is fine 1
+    ok 2 - this is fine 2
+    ok 3 - this is fine 3
+    # Subtest: child test
+        1..3
+        ok 1 - this is fine 4
+        ok 2 - this is fine 5
+        not ok 3 - timeout!
+          ---
+          {"handles":[{"type":"Timer"}],"signal":"SIGTERM"}
+          ...
+        # failed 1 of 3 tests
+    not ok 4 - child test ___/# time=[0-9.]+(ms)?/~~~
+      ---
+      {"at":{"column":5,"file":"test/test/timeout-via-runner.js","line":7},"results":{"count":3,"fail":1,"ok":false,"pass":2,"plan":{"end":3,"start":1}},"source":"t.test('child test', function (t) {\n"}
+      ...
+
+    1..4
+    # failed 1 of 4 tests
+    ___/# time=[0-9.]+(ms)?/~~~
+not ok 1 - child ___/# time=[0-9.]+(ms)?/~~~
+  ---
+  {"arguments":["___/.*/~~~timeout-via-runner.js","child"],"at":{"column":5,"file":"test/test/timeout-via-runner.js","line":16},"command":"___/.*(node|iojs)(.exe)?/~~~","failure":"timeout","results":{"count":4,"fail":1,"ok":false,"pass":3,"plan":{"end":4,"start":1}},"signal":"SIGTERM","source":"t.spawn(process.execPath, [__filename, 'child'], {}, 'child', { timeout: 1000 })\n","timeout":1000}
+  ...
+
+1..1
+# failed 1 of 1 tests
+___/# time=[0-9.]+(ms)?/~~~
+
diff --git a/test/test/timeout.tap b/test/test/timeout.tap
index 3a19fb9..9492b30 100644
--- a/test/test/timeout.tap
+++ b/test/test/timeout.tap
@@ -1,8 +1,8 @@
 TAP version 13
-    # Subtest: parent of timeout test
-        # Subtest: timeout test
-            # Subtest: this never completes
-                # Subtest: baby
+# Subtest: parent of timeout test
+    # Subtest: timeout test
+        # Subtest: this never completes
+            # Subtest: baby
                 ok 1 - expect truthy value
                 not ok 2 - timeout!
                   ---
diff --git a/test/test/todo-bail.tap b/test/test/todo-bail.tap
index d753ebd..f1bdb3a 100644
--- a/test/test/todo-bail.tap
+++ b/test/test/todo-bail.tap
@@ -1,11 +1,11 @@
 TAP version 13
-    # Subtest: a set of tests to be done later
+# Subtest: a set of tests to be done later
     ok 1 - should have a thingie # TODO
     ok 2 - should have a second whoosits also # TODO
-        # Subtest: the subset
+    # Subtest: the subset
         ok 1 - should be a child thingie # TODO
         ok 2 - should also be a whoosits # TODO
-            # Subtest: has some of these things
+        # Subtest: has some of these things
             ok 1 - true is truthy
             ok 2 - ten is also truthy
             1..2
@@ -18,7 +18,7 @@ TAP version 13
 ok 1 - a set of tests to be done later ___/# time=[0-9.]+(ms)?/~~~
 
 ok 2 - (unnamed test) # TODO
-    # Subtest: another set of tests
+# Subtest: another set of tests
     ok 1 - is a second set # TODO
     ok 2 - looks like english # TODO
     ok 3 - is marked TODO # TODO
diff --git a/test/test/todo.tap b/test/test/todo.tap
index d753ebd..f1bdb3a 100644
--- a/test/test/todo.tap
+++ b/test/test/todo.tap
@@ -1,11 +1,11 @@
 TAP version 13
-    # Subtest: a set of tests to be done later
+# Subtest: a set of tests to be done later
     ok 1 - should have a thingie # TODO
     ok 2 - should have a second whoosits also # TODO
-        # Subtest: the subset
+    # Subtest: the subset
         ok 1 - should be a child thingie # TODO
         ok 2 - should also be a whoosits # TODO
-            # Subtest: has some of these things
+        # Subtest: has some of these things
             ok 1 - true is truthy
             ok 2 - ten is also truthy
             1..2
@@ -18,7 +18,7 @@ TAP version 13
 ok 1 - a set of tests to be done later ___/# time=[0-9.]+(ms)?/~~~
 
 ok 2 - (unnamed test) # TODO
-    # Subtest: another set of tests
+# Subtest: another set of tests
     ok 1 - is a second set # TODO
     ok 2 - looks like english # TODO
     ok 3 - is marked TODO # TODO
diff --git a/test/test/unfinished-bail.tap b/test/test/unfinished-bail.tap
index 8fa635d..981ff88 100644
--- a/test/test/unfinished-bail.tap
+++ b/test/test/unfinished-bail.tap
@@ -1,6 +1,6 @@
 TAP version 13
-    # Subtest: t1
-        # Subtest: t11
+# Subtest: t1
+    # Subtest: t11
         1..1
         not ok 1 - test unfinished: t11
           ---
diff --git a/test/test/unfinished.tap b/test/test/unfinished.tap
index 2543ec1..35b8ef2 100644
--- a/test/test/unfinished.tap
+++ b/test/test/unfinished.tap
@@ -1,6 +1,6 @@
 TAP version 13
-    # Subtest: t1
-        # Subtest: t11
+# Subtest: t1
+    # Subtest: t11
         1..1
         not ok 1 - test unfinished: t11
           ---
@@ -37,17 +37,17 @@ not ok 4 - test point left in queue: not ok - failsome
   ...
 not ok 5 - spawn left in queue: spawny
   ---
-  {"args":["___/.*/~~~unfinished.js"],"at":{"column":5,"file":"test/test/unfinished.js","line":25},"command":"___/.*(node|iojs)/~~~","options":{},"rar":"grr","source":"tap.spawn('___/.*(node|iojs)/~~~', [__filename], {}, 'spawny', { rar: 'grr' })\n"}
+  {"args":["___/.*/~~~unfinished.js"],"at":{"column":5,"file":"test/test/unfinished.js","line":25},"command":"___/.*(node|iojs)(.exe)?/~~~","options":{},"rar":"grr","source":"tap.spawn('___/.*(node|iojs)(.exe)?/~~~', [__filename], {}, 'spawny', { rar: 'grr' })\n"}
   ...
-not ok 6 - spawn left in queue: ___/.*(node|iojs)/~~~ --version
+not ok 6 - spawn left in queue: ___/.*(node|iojs)(.exe)?/~~~ --version
   ---
-  {"args":["--version"],"at":{"column":5,"file":"test/test/unfinished.js","line":26},"command":"___/.*(node|iojs)/~~~","options":{},"rar":"grr","source":"tap.spawn('___/.*(node|iojs)/~~~', ['--version'], {}, '', { rar: 'grr' })\n"}
+  {"args":["--version"],"at":{"column":5,"file":"test/test/unfinished.js","line":26},"command":"___/.*(node|iojs)(.exe)?/~~~","options":{},"rar":"grr","source":"tap.spawn('___/.*(node|iojs)(.exe)?/~~~', ['--version'], {}, '', { rar: 'grr' })\n"}
   ...
-not ok 7 - child test left in queue: (unnamed)
+not ok 7 - child test left in queue: (unnamed test)
   ---
   {"at":{"column":5,"file":"test/test/unfinished.js","line":28},"source":"tap.test(function (t) {\n"}
   ...
-not ok 8 - child test left in queue: (unnamed)
+not ok 8 - child test left in queue: (unnamed test)
   ---
   {"at":{"column":5,"file":"test/test/unfinished.js","line":32},"source":"tap.test('', function (t) {\n"}
   ...
diff --git a/test/throw-after-end.js b/test/throw-after-end.js
new file mode 100644
index 0000000..25d896a
--- /dev/null
+++ b/test/throw-after-end.js
@@ -0,0 +1,27 @@
+var t = require('../')
+var Test = t.Test
+
+var tt = new Test({ bail: false })
+tt._name = 'one'
+tt.pass('this is fine')
+tt.end()
+t.throws(function () {
+  tt.threw(new Error('whoops'))
+}, { message: 'whoops', stack: /^Error: whoops\n/ })
+
+var out = ''
+tt = new Test({ bail: false })
+tt._name = 'two'
+
+tt.on('data', function (c) {
+  out += c
+})
+tt.test('child', function (tt) {
+  tt.threw(new Error('whoops'))
+  tt.end()
+})
+tt.end()
+
+t.match(out, '\n    not ok 1 - Error: whoops\n')
+t.match(out, '\n        tt.threw(new Error(\'whoops\'))\n')
+t.match(out, '\nnot ok 1 - child # time=')
diff --git a/test/throws-arg-ordering.js b/test/throws-arg-ordering.js
new file mode 100644
index 0000000..a4ee97e
--- /dev/null
+++ b/test/throws-arg-ordering.js
@@ -0,0 +1,60 @@
+var t = require('../')
+
+var wanted = [
+  Error,
+  { message: 'hello' },
+  new Error('hello'),
+  TypeError,
+  TypeError('hello'),
+  new TypeError('hello'),
+  new Error('hello'),
+  { code: 123, syscall: 'hi' },
+  /[g-i]ell.$/
+]
+
+var extra = [
+  { skip: false },
+  {},
+  null
+]
+
+var message = [
+  'the thing throws',
+  null,
+  ''
+]
+
+function thrower () {
+  var er = new TypeError('hello')
+  er.code = 123
+  er.syscall = 'hi'
+  throw er
+}
+
+wanted.forEach(function (wanted) {
+  extra.forEach(function (extra) {
+    message.forEach(function (message) {
+      // The wanted error object always has to come before the
+      // 'extra', or else it'll think that you want an error
+      // matching something like {skip:blah...}
+      // Any other ordering should work fine tho.
+      var a = [ thrower, wanted, extra, message ]
+      t.throws(a[0], a[1], a[2], a[3])
+      t.throws(a[0], a[1], a[3], a[2])
+      t.throws(a[0], a[3], a[1], a[2])
+
+      t.throws(a[1], a[0], a[2], a[3])
+      t.throws(a[1], a[0], a[3], a[2])
+      t.throws(a[1], a[2], a[0], a[3])
+      t.throws(a[1], a[2], a[3], a[0])
+      t.throws(a[1], a[3], a[0], a[2])
+      t.throws(a[1], a[3], a[2], a[0])
+
+      t.throws(a[3], a[1], a[2], a[0])
+      t.throws(a[3], a[1], a[0], a[2])
+      t.throws(a[3], a[0], a[1], a[2])
+    })
+  })
+})
+
+t.end()
diff --git a/test/timeout.js b/test/timeout.js
index 988862a..012499d 100644
--- a/test/timeout.js
+++ b/test/timeout.js
@@ -1,4 +1,5 @@
 var tap = require('../')
+var Test = tap.Test
 
 tap.test('timeout test with plan only', function (t) {
   t.plan(2)
@@ -27,3 +28,47 @@ tap.test('timeout test with plan and end', function (t) {
     }
   }, 100)
 })
+
+tap.test('t.setTimeout()', function (t) {
+  t.throws(function () {
+    var tt = new Test()
+    tt.setTimeout(-1)
+  }, {}, { message: 'setTimeout: number > 0 required' })
+
+  t.throws(function () {
+    var tt = new Test()
+    tt.setTimeout('not a number')
+  }, {}, { message: 'setTimeout: number > 0 required' })
+
+  var tt = new Test({ timeout: 1000, bail: false })
+  tt.setTimeout(2000)
+  tt.setTimeout(Infinity)
+  t.notOk(tt._timeout)
+  t.notOk(tt._timer)
+
+  var expect = new RegExp('^TAP version 13\n' +
+    '# Subtest: child test\n' +
+    '    ok 1 - this is fine\n' +
+    '    not ok 2 - timeout!\n' +
+    '      ---\n' +
+    '      timeout: 1\n' +
+    '      \\.\\.\\.\n' +
+    '    1..2\n' +
+    '    # failed 1 of 2 tests\n' +
+    'not ok 1 - child test # time=')
+
+  tt = new Test({ name: 'some name', bail: false })
+  tt.test('child test', { bail: false }, function (tt) {
+    tt.pass('this is fine')
+  })
+  tt.setTimeout(1)
+  var out = ''
+  tt.on('data', function (c) {
+    out += c
+  })
+  setTimeout(function () {
+    t.notOk(tt.passing())
+    t.match(out, expect)
+    t.end()
+  }, 100)
+})
diff --git a/test/trivial-success.js b/test/trivial-success.js
deleted file mode 100644
index a77d7fd..0000000
--- a/test/trivial-success.js
+++ /dev/null
@@ -1,2 +0,0 @@
-console.log('1..1')
-console.log('ok')

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-tap.git



More information about the Pkg-javascript-commits mailing list