[Pkg-javascript-commits] [node-tap] 01/01: Imported Upstream version 0.4.13

Jérémy Lal kapouer at moszumanska.debian.org
Sun Oct 19 21:52:28 UTC 2014


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

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

commit 7a530b5a0ab2b6771493124e4ebecbb34188ba5b
Author: Jérémy Lal <kapouer at melix.org>
Date:   Sun Oct 19 23:43:21 2014 +0200

    Imported Upstream version 0.4.13
---
 .gitignore                            |   3 +
 .npmignore                            |   0
 AUTHORS                               |  11 +
 LICENSE                               |  23 ++
 README.md                             |  86 ++++++
 bin/tap-http.js                       |  19 ++
 bin/tap-reader.js                     |  33 +++
 bin/tap.js                            | 144 ++++++++++
 coverage-example/lib/bar.js           |  15 +
 coverage-example/lib/foo.js           |  15 +
 coverage-example/test/bar.test.js     |  20 ++
 coverage-example/test/baz.test.js     |  29 ++
 coverage-example/test/foo.test.js     |  20 ++
 example/lib/math.js                   |   1 +
 example/test/test-example.js          | 237 ++++++++++++++++
 lib/main.js                           |  16 ++
 lib/tap-assert.js                     | 445 ++++++++++++++++++++++++++++++
 lib/tap-browser-harness.js            |  63 +++++
 lib/tap-consumer.js                   | 246 +++++++++++++++++
 lib/tap-cov-html.js                   |  78 ++++++
 lib/tap-global-harness.js             |  68 +++++
 lib/tap-harness.js                    | 224 +++++++++++++++
 lib/tap-producer.js                   | 131 +++++++++
 lib/tap-results.js                    |  71 +++++
 lib/tap-runner.js                     | 501 ++++++++++++++++++++++++++++++++++
 lib/tap-test.js                       | 110 ++++++++
 package.json                          |  41 +++
 test-disabled/bailout.js              |  36 +++
 test-disabled/foo.js                  |   1 +
 test-disabled/t.js                    |  16 ++
 test/buffer_compare.js                |  11 +
 test/common.js                        |  32 +++
 test/debug-test.js                    |  16 ++
 test/deep.js                          |  43 +++
 test/end-exception/t.js               |  12 +
 test/executed.sh                      |   4 +
 test/expose-gc-test.js                |  46 ++++
 test/independent-timeouts.js          |  16 ++
 test/isolated-conf-test.js            |  16 ++
 test/meta-test.js                     |  73 +++++
 test/nested-test.js                   |  23 ++
 test/non-tap-output.js                |  12 +
 test/not-executed.sh                  |   4 +
 test/output-childtest-description.js  |  50 ++++
 test/result-trap.js                   |  25 ++
 test/segv.js                          |  69 +++++
 test/simple-harness-test-with-plan.js |  16 ++
 test/simple-harness-test.js           |  13 +
 test/test-test.js                     |  91 ++++++
 test/timeout.js                       |  33 +++
 test/trivial-success.js               |   0
 test/undefined_indented.js            |  27 ++
 test/valid-command.js                 |  37 +++
 53 files changed, 3372 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..300a374
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+node_modules/
+coverage/
+coverage-example/coverage/
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..e69de29
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..b7f6eb2
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,11 @@
+# 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>
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..05a4010
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,23 @@
+Copyright 2009, 2010, 2011 Isaac Z. Schlueter.
+All rights reserved.
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..d6a0440
--- /dev/null
+++ b/README.md
@@ -0,0 +1,86 @@
+This is a mix-and-match set of utilities that you can use to write test
+harnesses and frameworks that communicate with one another using the
+Test Anything Protocol.
+
+If you don't yet know what TAP is, [you better ask
+somebody](http://testanything.org/).
+
+Default Usage:
+
+1. Make a directory.  Maybe call it 'test'.  That'd be nice and obvious.
+2. Put a bunch of test scripts in there.  If they're node programs, then
+   they should be ".js".  Anything else is assumed to be some kind of shell
+   script, which should have a shebang line.
+3. `npm install tap`
+4. Update package.json scripts.test to include `tap ./test` [example
+   gist](https://gist.github.com/4469613)
+5. `npm test`
+
+The output will be TAP-compliant.
+
+For extra special bonus points, you can do something like this:
+
+    var test = require("tap").test
+    test("make sure the thingie is a thing", function (t) {
+      t.equal(thingie, "thing", "thingie should be thing")
+      t.deepEqual(array, ["foo", "bar"], "array has foo and bar elements")
+      t.deepEqual(object, {foo: 42}, "object has foo property")
+      t.type(thingie, "string", "type of thingie is string")
+      t.ok(true, "this is always true")
+      t.notOk(false, "this is never true")
+      t.test("a child test", function (t) {
+        t.equal(this, superEasy, "right!?")
+        t.similar(7, 2, "ever notice 7 is kinda like 2?", {todo: true})
+        t.test("so skippable", {skip: true}, function (t) {
+          t.plan(1) // only one test in this block
+          t.ok(true, "but when the flag changes, it'll pass")
+          // no need to end, since we had a plan.
+        })
+        t.end()
+      })
+      t.ok(99, "can also skip individual assertions", {skip: true})
+      // end lets it know it's over.
+      t.end()
+    })
+    test("another one", function (t) {
+      t.plan(1)
+      t.ok(true, "It's ok to plan, and also end.  Watch.")
+      t.end() // but it must match the plan!
+    })
+
+Node-tap is actually a collection of several modules, any of which may be
+mixed and matched however you please.
+
+If you don't like this test framework, and think you can do much much
+better, *I strongly encourage you to do so!*  If you use this library,
+however, at least to output TAP-compliant results when `process.env.TAP`
+is set, then the data coming out of your framework will be much more
+consumable by machines.
+
+You can also use this to build programs that *consume* the TAP data, so
+this is very useful for CI systems and such.
+
+* tap-assert: A collection of assert functions that return TAP result
+  objects.
+* tap-consumer: A stream interface for consuming TAP data.
+* tap-producer: A class that produces a TAP stream by taking in result
+  objects.
+* tap-results: A class for keeping track of TAP result objects as they
+  pass by, counting up skips, passes, fails, and so on.
+* tap-runner: A program that runs through a directory running all the
+  tests in it.  (Tests which may or may not be TAP-outputting tests.  But
+  it's better if they are.)
+* tap-test: A class for actually running tests.
+* tap-harness: A class that runs tests.  (Tests are also Harnesses,
+  which is how sub-tests run.)
+* tap-global-harness: A default harness that provides the top-level
+  support for running TAP tests.
+
+## Experimental Code Coverage with runforcover & bunker:
+
+```
+TAP_COV=1 tap ./test [--cover=./lib,foo.js] [--coverage-dir=./coverage]
+```
+
+This feature is experimental, and will most likely change somewhat
+before being finalized.  Feedback welcome.
diff --git a/bin/tap-http.js b/bin/tap-http.js
new file mode 100644
index 0000000..296910f
--- /dev/null
+++ b/bin/tap-http.js
@@ -0,0 +1,19 @@
+#!/usr/bin/env node
+
+// just an example, really
+// Run with `node tap-http.js path/to/tests/`
+
+var argv = process.argv.slice(2)
+  , path = require("path")
+  , Runner = require("../lib/tap-runner")
+
+  , http = require("http")
+  , server = http.createServer(function (req, res) {
+    // it'd be nice to return a non-200 if the tests fail, but we don't
+    // know the status until it's done, so that would mean not being able
+    // to pipe the output
+    res.writeHead(200, {'content-type': 'text/plain'})
+    new Runner(argv, null).pipe(res)
+  })
+
+server.listen(1337)
diff --git a/bin/tap-reader.js b/bin/tap-reader.js
new file mode 100755
index 0000000..b196cc2
--- /dev/null
+++ b/bin/tap-reader.js
@@ -0,0 +1,33 @@
+#!/usr/bin/env node
+
+// read a tap stream from stdin.
+
+var TapConsumer = require("../lib/tap-consumer")
+  , TapProducer = require("../lib/tap-producer")
+
+var tc = new TapConsumer
+  , tp = new TapProducer
+
+//process.stdin.pipe(tc)
+process.stdin.on("data", function (c) {
+  c = c + ""
+  // console.error(JSON.stringify(c).substr(0, 100))
+  tc.write(c)
+})
+process.stdin.on("end", function () { tc.end() })
+process.stdin.resume()
+//tc.pipe(tp)
+tc.on("data", function (c) {
+  tp.write(c)
+})
+tc.on("end", function () { tp.end() })
+
+tp.on("data", function (c) {
+  console.error(["output write", c])
+  process.stdout.write(c)
+})
+
+tp.on("end", function (er, total, ok) {
+  if (er) throw er
+  process.exit(total - ok)
+})
diff --git a/bin/tap.js b/bin/tap.js
new file mode 100755
index 0000000..0a9bbeb
--- /dev/null
+++ b/bin/tap.js
@@ -0,0 +1,144 @@
+#!/usr/bin/env node
+
+var argv = process.argv.slice(2)
+  , path = require("path")
+  , Runner = require("../lib/tap-runner")
+
+  , nopt = require("nopt")
+
+  , knownOpts =
+    { cover: [path, false]
+    , "cover-dir": path
+    , stderr: Boolean
+    , stdout: Boolean
+    , diag: Boolean
+    , version: Boolean
+    , tap: Boolean
+    , timeout: Number
+    , gc: Boolean
+    , debug: Boolean
+    , "debug-brk": Boolean
+    , strict: Boolean
+    , harmony: Boolean
+    }
+
+  , shorthands =
+    // debugging 1: show stderr
+    { d: ["--stderr"]
+    // debugging 2: show stderr and tap
+    , dd: ["--stderr", "--tap"]
+    // debugging 3: show stderr, tap, AND always show diagnostics.
+    , ddd: ["--stderr", "--tap", "--diag"]
+    , "expose-gc": ["--gc"]
+    , g: ["--gc"]
+    , e: ["--stderr"]
+    , t: ["--timeout"]
+    , o: ["--tap"]
+    , c: ["--cover"]
+    , v: ["--version"]
+    , "?": ["--help"]
+    , h: ["--help"]
+    }
+
+  , defaults =
+    { cover: "./lib"
+    , "cover-dir": "./coverage"
+    , stderr: process.env.TAP_STDERR !== '0'
+    , tap: process.env.TAP
+    , diag: process.env.TAP_DIAG
+    , timeout: +process.env.TAP_TIMEOUT || 30
+    , gc: false
+    , debug: false
+    , "debug-brk": false
+    , strict: false
+    , harmony: false
+    , version: false
+    , help: false }
+
+  , options = nopt(knownOpts, shorthands)
+
+if (options.version) {
+  console.log(require("../package.json").version)
+  process.exit(0)
+}
+
+if (options.help) {
+  console.log(function(){/*
+
+Usage:
+    tap <options> <files>
+
+    Run the files as tap tests, parse the output, and report the results
+
+Options:
+
+    --stderr     Print standard error output of tests to standard error.
+    --tap        Print raw tap output.
+    --diag       Print diagnostic output for passed tests, as well as failed.
+                 (Implies --tap)
+    --gc         Expose the garbage collector to tests.
+    --timeout    Maximum time to wait for a subtest, in seconds. Default: 30
+    --debug      Pass the '--debug' flag to node for debugging
+    --debug-brk  Pass the '--debug-brk' flag to node for debugging
+    --strict     Enforce strict mode when running tests.
+    --harmony    Enable harmony features for tests.
+    --version    Print the version of node tap.
+    --help       Print this help.
+
+Please report bugs!  https://github.com/isaacs/node-tap/issues
+
+*/}.toString().split(/\n/).slice(1, -1).join("\n"))
+  process.exit(0)
+}
+
+
+Object.keys(defaults).forEach(function (k) {
+  if (!options.hasOwnProperty(k)) options[k] = defaults[k]
+})
+
+// other tests that might rely on these
+if (options.diag) process.env.TAP_DIAG = true
+if (options.tap) process.env.TAP = true
+if (options.timeout) process.env.TAP_TIMEOUT = options.timeout
+
+var r = new Runner(options)
+  , TapProducer = require("../lib/tap-producer")
+
+if (options.tap || options.diag) {
+  r.pipe(process.stdout)
+} else {
+  r.on("file", function (file, results, details) {
+    var s = (details.ok ? "" : "not ") + "ok "+results.name
+      , n = details.pass + "/" + details.testsTotal
+      , dots = new Array(Math.max(1, 60 - s.length - n.length)).join(".")
+    console.log("%s %s %s", s, dots, n)
+    if (details.ok) {
+      if (details.skip) {
+        console.log("  skipped: %s", details.skipTotal)
+      }
+    } else {
+      // console.error(details)
+      console.log("    Command: %s", results.command)
+      console.log("    " + TapProducer.encode(details.list)
+                  .split(/\n/).join("\n    "))
+    }
+  })
+  r.on("end", function () {
+    //console.log(r)
+    var s = "total"
+      , n = r.results.pass + "/" + r.results.testsTotal
+      , dots = new Array(60 - s.length - n.length).join(".")
+      , ok = r.results.ok ? "ok" : "not ok"
+    console.log("%s %s %s\n\n%s", s, dots, n, ok)
+    if (r.doCoverage) {
+      console.error( "\nCoverage: %s\n"
+                   , path.resolve(r.coverageOutDir, "index.html") )
+    }
+  })
+}
+
+
+
+r.on("end", function () {
+  process.exit(r.results.tests - r.results.pass)
+})
diff --git a/coverage-example/lib/bar.js b/coverage-example/lib/bar.js
new file mode 100644
index 0000000..e7cb7ad
--- /dev/null
+++ b/coverage-example/lib/bar.js
@@ -0,0 +1,15 @@
+var Bar = module.exports = function(str) {
+  this.bar = str;
+  this.str = str;
+};
+
+Bar.prototype.foo = function() {
+  var self = this;
+  return self.bar;
+};
+
+Bar.prototype.baz = function() {
+  var self = this;
+  return self.str;
+};
+
diff --git a/coverage-example/lib/foo.js b/coverage-example/lib/foo.js
new file mode 100644
index 0000000..cb9ee8f
--- /dev/null
+++ b/coverage-example/lib/foo.js
@@ -0,0 +1,15 @@
+var Foo = module.exports = function(str) {
+  this.foo = str;
+  this.str = str;
+};
+
+Foo.prototype.bar = function() {
+  var self = this;
+  return self.foo;
+};
+
+Foo.prototype.baz = function() {
+  var self = this;
+  return self.str;
+};
+
diff --git a/coverage-example/test/bar.test.js b/coverage-example/test/bar.test.js
new file mode 100644
index 0000000..91e4bc2
--- /dev/null
+++ b/coverage-example/test/bar.test.js
@@ -0,0 +1,20 @@
+var test = require('tap').test,
+    Bar = require('../lib/bar'),
+    bar;
+
+test('setup', function(t) {
+  bar = new Bar('baz');
+  t.ok(bar);
+  t.end();
+});
+
+test('bar', function(t) {
+  t.equal('baz', bar.foo());
+  t.end();
+});
+
+test('teardown', function(t) {
+  t.ok(true);
+  t.end();
+});
+
diff --git a/coverage-example/test/baz.test.js b/coverage-example/test/baz.test.js
new file mode 100644
index 0000000..fae22d8
--- /dev/null
+++ b/coverage-example/test/baz.test.js
@@ -0,0 +1,29 @@
+var test = require('tap').test,
+    Foo = require('../lib/foo'),
+    Bar = require('../lib/bar'),
+    foo, bar;
+
+test('setup', function(t) {
+  foo = new Foo('baz');
+  t.ok(foo);
+  bar = new Bar('baz');
+  t.ok(bar);
+  t.end();
+});
+
+test('baz from Foo', function(t) {
+  t.equal('baz', foo.baz());
+  t.end();
+});
+
+test('baz from Bar', function(t) {
+  t.equal('baz', bar.baz());
+  t.end();
+});
+
+
+test('teardown', function(t) {
+  t.ok(true);
+  t.end();
+});
+
diff --git a/coverage-example/test/foo.test.js b/coverage-example/test/foo.test.js
new file mode 100644
index 0000000..2aed8fd
--- /dev/null
+++ b/coverage-example/test/foo.test.js
@@ -0,0 +1,20 @@
+var test = require('tap').test,
+    Foo = require('../lib/foo'),
+    foo;
+
+test('setup', function(t) {
+  foo = new Foo('baz');
+  t.ok(foo);
+  t.end();
+});
+
+test('bar', function(t) {
+  t.equal('baz', foo.bar());
+  t.end();
+});
+
+test('teardown', function(t) {
+  t.ok(true);
+  t.end();
+});
+
diff --git a/example/lib/math.js b/example/lib/math.js
new file mode 100644
index 0000000..f798626
--- /dev/null
+++ b/example/lib/math.js
@@ -0,0 +1 @@
+module.exports = Math
diff --git a/example/test/test-example.js b/example/test/test-example.js
new file mode 100644
index 0000000..cd2549b
--- /dev/null
+++ b/example/test/test-example.js
@@ -0,0 +1,237 @@
+var tap = require("tap")
+  , test = tap.test
+  , plan = tap.plan
+  , math
+
+test("load sut", function (t) {
+  math = require("../lib/math")
+  t.ok(math, "object loaded")
+  t.end()
+})
+
+test("validate constants", function (t) {
+  t.equal(math.LN10, 2.302585092994046, "ln 10")
+  t.equal(math.PI, 3.141592653589793, "pi")
+  t.equal(math.E, 2.718281828459045, "e")
+  t.equal(math.LOG10E, 0.4342944819032518, "log 10 e")
+  t.equal(math.SQRT2, 1.4142135623730951, "sqrt 2")
+  t.equal(math.SQRT1_2, 0.7071067811865476, "sqrt 1/2")
+  t.equal(math.LN2, 0.6931471805599453, "ln2")
+  t.end()
+})
+
+test("using this", function (t) {
+  // this also works.
+  this.equal(t, this, "call in scope of test obj")
+  this.end()
+})
+
+// test setTimeout, just a trivial example.
+test("setTimeout", function (t) {
+  var start = Date.now()
+  setTimeout(function () {
+    t.ok(Date.now() >= start + 50, "timeout fired after delay")
+    t.end()
+  }, 50)
+})
+
+// another way to do the same, using a plan.
+// this is more robust, but annoying when you have a long list
+// of tests for something.  For async stuff, it's generally better,
+// since there's a higher risk of the control flowing off to lala land.
+test("setTimeout planned", function (t) {
+  t.plan(1)
+  var start = Date.now()
+  setTimeout(function () {
+    t.ok(Date.now() >= start + 50, "timeout fired after delay")
+  }, 50)
+})
+
+// plans also are good for cases where things may fire in a non-deterministic
+// order, since it won't be as obvious when everything is done.
+test("setTimeout parallel", function (t) {
+  t.plan(2)
+  var start = Date.now()
+  setTimeout(function A () {
+    t.ok(Date.now() >= start + 50, "timeout A fired after delay")
+  }, 50)
+  setTimeout(function B () {
+    t.ok(Date.now() >= start + 50, "timeout B fired after delay")
+  }, 50)
+})
+
+// something slightly less hello worldy
+test("async test", function (t) {
+  t.plan(4)
+  var fs = require("fs")
+  t.ok(fs, "fs library should load")
+  var rs = fs.createReadStream(__filename)
+  t.ok(rs, "read stream should start fine.")
+  rs.on("open", function (fd) {
+    t.type(fd, "number", "file descriptor should be a number")
+    t.equal(fd, rs.fd, "fd should match stream fd")
+  })
+})
+
+// you can bail out of the entire everything if something is just
+// Not Right (db not installed, etc.)
+test("tarp", function (parent) {
+  if (7 === 5) {
+    parent.bailout("math is broken")
+  }
+  // bailout bubbles up a bit like "error" events
+  // if unhandled, then the parent will bail, as well.
+  parent.test("child bailouts", function (child) {
+    child.on("bailout", function (s) {
+      parent.fail("children shouldn't bail.")
+    })
+    child.bailout("try to bail out, but instead just fail a test")
+  })
+
+  parent.test("child bailout 2", function (child) {
+    child.bailout("this one will bail out")
+  })
+})
+
+// tests marked "todo" can fail without counting against the overall score
+// never ever ever write tests to "verify" incorrect behavior!
+test("unfinished test", function (t) {
+  t.equal(math.cos(math.PI), -1, "cos(PI)")
+  t.equal(math.sin(math.PI),  0, "sin(PI)")
+  t.equal(math.face, "your face", "math.face should be your face # TODO")
+  t.end()
+})
+
+// tests can have children.
+test("http server", function (t) {
+  // one test plus 4 children.
+  t.plan(5)
+
+  var http = require("http")
+    , PORT = 12346
+
+  t.ok(http, "http module should load")
+  var server
+
+  t.test("set up server", function (t) {
+    t.plan(2)
+    server = http.createServer(function (req, res) {
+      t.comment("Request: "+req.url)
+      res.writeHead(200, {})
+      res.end(req.method + " " + req.url)
+    })
+    t.ok(server, "createServer should create a server")
+    server.listen(PORT, t.cb("listen should fire callback"))
+  })
+
+  // set the "parallel" flag on this one.
+  // That signals the harness to proceed immediately to the next test,
+  // and run them in parallel.
+  // Default behavior is to wait for each test to complete before proceeding
+  // to the next one.
+  // The first not-parallel test encountered will cause it to wait for that
+  // test, as well as all the parallel tests before it.
+  // A, B', C', D', E (where ' means "parallel")
+  // Runs A, and then B, C, and D in parallel, and then E.
+  t.test("testing POST", {parallel: true}, function (t) {
+    t.plan(1)
+    http.request("POST", { method: "POST"
+                         , host: "localhost"
+                         , path: "/foo"
+                         , port: PORT }).on("response", function (res) {
+      t.bufferStream(res, function (s) { t.equal(s, "POST /foo") })
+    }).end()
+  })
+
+  t.test("testing GET", {parallel: true}, function (t) {
+    t.plan(1)
+    http.request("POST", { method: "GET"
+                         , host: "localhost"
+                         , path: "/foo"
+                         , port: PORT }).on("response", function (res) {
+      t.bufferStream(res, function (s) { t.equal(s, "GET /foo") })
+    }).end()
+  })
+
+  // wrap in a test so that if this throws, it'll log as a failed test.
+  t.test("teardown", function (t) {
+    server.close()
+    t.end()
+  })
+})
+
+// yo dawg!
+test("meta-tests", function (t) {
+  t.plan(5)
+
+  // t.fails() wraps a child test and succeeds if it fails.
+  t.fails(t.test("this should fail", function (t) {
+    t.ok(false, "assert false")
+    t.end()
+  }))
+
+  // t.timesOut() wraps a child test and succeeds if it times out.
+  // if t.end() is called, or if a plan is completed, then it fails.
+  // set the timeout really low so that it will not take forever.
+  t.timesOut(t.test("this should timeout", { timeout: 1 }, function (t) {
+    t.ok(true, "assert true")
+    // t.end() never called.
+  }))
+
+  // t.incomplete() wraps a child test and succeeds if it ends before
+  // the plan is finished.
+  t.incomplete(t.test("this should be incomplete", function (t) {
+    t.plan(100)
+    t.ok(true, "assert true")
+    // calling end prematurely.
+    t.end()
+  }))
+
+  // t.bailsOut() wraps a child test and succeeds if it calls bailout()
+  t.bailsOut(t.test("this should bailout", function (t) {
+    t.bailout("oh noes, bailing out!")
+  }))
+
+  // low-level analysis of subtests
+  t.test("verifying test success/failure expectations", function (t) {
+    t.once("end", function () {
+      var res = t.results
+        , is = t.equal
+      // hijack!
+      t.clear()
+      is(res.ok,         false, "ok")
+
+      is(res.bailedOut,  false, "bailed out")
+
+      is(res.skip,       2, "skips")
+      is(res.skipPass,   1, "skip that passed")
+      is(res.skipFail,   1, "skip that failed")
+
+      is(res.todo,       2, "todos")
+      is(res.todoPass,   1, "todo that passed")
+      is(res.todoFail,   1, "todo that failed")
+
+      is(res.failTotal,  3, "failures total")
+      is(res.fail,       1, "relevant failure")
+
+      is(res.passTotal,  3, "passes total")
+      is(res.pass,       1, "relevant pass")
+
+      is(res.testsTotal, 6, "total tests")
+      is(res.tests,      2, "should be 2 relevant tests")
+
+      t.end()
+    })
+
+    // run the metatest.
+    // *this* is the actual SUT in this case.
+    t.ok(false, "failing todo #todo")
+    // can also set #todo or #skip explicitly
+    t.ok(true, "succeeding todo", {todo: true})
+    t.ok(false, "failing skip #skip", {skip: true})
+    t.ok(true, "suceeding skip #skip")
+    t.ok(false, "failing test")
+    t.ok(true, "succeeding test")
+    t.end()
+  })
+})
diff --git a/lib/main.js b/lib/main.js
new file mode 100644
index 0000000..a9a520a
--- /dev/null
+++ b/lib/main.js
@@ -0,0 +1,16 @@
+
+var GlobalHarness = require("./tap-global-harness")
+
+// this lets you do stuff like:
+// var test = require("tap").test
+// test(...)
+// to run stuff in the global harness.
+exports = module.exports = new GlobalHarness()
+
+exports.createProducer = exports.Producer = require("./tap-producer")
+exports.createConsumer = exports.Consumer = require("./tap-consumer")
+exports.yamlish = require("yamlish")
+exports.createTest = exports.Test = require("./tap-test")
+exports.createHarness = exports.Harness = require("./tap-harness")
+exports.createRunner = exports.Runner = require("./tap-runner")
+exports.assert = require("./tap-assert")
diff --git a/lib/tap-assert.js b/lib/tap-assert.js
new file mode 100644
index 0000000..60203a0
--- /dev/null
+++ b/lib/tap-assert.js
@@ -0,0 +1,445 @@
+// an assert module that returns tappable data for each assertion.
+var difflet = require('difflet')
+  , deepEqual = require('deep-equal')
+  , bufferEqual = require('buffer-equal')
+  , Buffer = require('buffer').Buffer
+
+module.exports = assert
+
+var syns = {}
+  , id = 1
+
+function assert (ok, message, extra) {
+  if (extra && extra.skip) return assert.skip(message, extra)
+
+  //console.error("assert %j", [ok, message, extra])
+  //if (extra && extra.skip) return assert.skip(message, extra)
+  //console.error("assert", [ok, message, extra])
+  ok = !!ok
+  var res = { id : id ++, ok: ok }
+
+  var caller = getCaller(extra && extra.error)
+  if (extra && extra.error) {
+    res.type = extra.error.name
+    res.message = extra.error.message
+    res.code = extra.error.code
+             || extra.error.type
+    res.errno = extra.error.errno
+    delete extra.error
+  }
+  if (caller.file) {
+    res.file = caller.file
+    res.line = +caller.line
+    res.column = +caller.column
+  }
+  res.stack = caller.stack
+
+  res.name = message || "(unnamed assert)"
+
+  if (extra) Object.keys(extra).forEach(function (k) {
+    if (!res.hasOwnProperty(k)) res[k] = extra[k]
+  })
+
+  // strings and objects are hard to diff by eye
+  if (!ok &&
+      res.hasOwnProperty("found") &&
+      res.hasOwnProperty("wanted") &&
+      res.found !== res.wanted) {
+    if (typeof res.wanted !== typeof res.found ||
+        typeof res.wanted === "object" && (!res.found || !res.wanted)) {
+      res.type = { found: typeof found
+                 , wanted: typeof wanted }
+    } else if (typeof res.wanted === "string") {
+      res.diff = diffString(res.found, res.wanted)
+    } else if (typeof res.wanted === "object") {
+      res.diff = diffObject(res.found, res.wanted)
+    }
+  }
+
+  //console.error("assert return", res)
+
+  return res
+}
+assert.ok = assert
+syns.ok = [ "true", "assert" ]
+
+
+function notOk (ok, message, extra) {
+  return assert(!ok, message, extra)
+}
+assert.notOk = notOk
+syns.notOk = [ "false", "notok" ]
+
+function error (er, message, extra) {
+  if (!er) {
+    // just like notOk(er)
+    return assert(!er, message, extra)
+  }
+  message = message || er.message
+  extra = extra || {}
+  extra.error = er
+  return assert.fail(message, extra)
+}
+assert.error = error
+syns.error = [ "ifError", "ifErr", "iferror" ]
+
+
+function pass (message, extra) {
+  return assert(true, message, extra)
+}
+assert.pass = pass
+
+function fail (message, extra) {
+  //console.error("assert.fail", [message, extra])
+  //if (extra && extra.skip) return assert.skip(message, extra)
+  return assert(false, message, extra)
+}
+assert.fail = fail
+
+function skip (message, extra) {
+  //console.error("assert.skip", message, extra)
+  if (!extra) extra = {}
+  return { id: id ++, skip: true, name: message || "" }
+}
+assert.skip = skip
+
+function throws (fn, wanted, message, extra) {
+  if (typeof wanted === "string") {
+    extra = message
+    message = wanted
+    wanted = null
+  }
+
+  if (extra && extra.skip) return assert.skip(message, extra)
+
+  var found = null
+  try {
+    fn()
+  } catch (e) {
+    found = { name: e.name, message: e.message }
+  }
+
+  extra = extra || {}
+
+  extra.found = found
+  if (wanted) {
+    wanted = { name: wanted.name, message: wanted.message }
+    extra.wanted = wanted
+  }
+
+  if (!message) {
+    message = "Expected to throw"
+    if (wanted) message += ": "+wanted.name + " " + wanted.message
+  }
+
+  return (wanted) ? assert.similar(found, wanted, message, extra)
+                  : assert.ok(found, message, extra)
+}
+assert.throws = throws
+
+
+function doesNotThrow (fn, message, extra) {
+  if (extra && extra.skip) return assert.skip(message, extra)
+  var found = null
+  try {
+    fn()
+  } catch (e) {
+    found = {name: e.name, message: e.message}
+  }
+  message = message || "Should not throw"
+
+  return assert.equal(found, null, message, extra)
+}
+assert.doesNotThrow = doesNotThrow
+
+
+function equal (a, b, message, extra) {
+  if (extra && extra.skip) return assert.skip(message, extra)
+  extra = extra || {}
+  message = message || "should be equal"
+  extra.found = a
+  extra.wanted = b
+  return assert(a === b, message, extra)
+}
+assert.equal = equal
+syns.equal = ["equals"
+             ,"isEqual"
+             ,"is"
+             ,"strictEqual"
+             ,"strictEquals"]
+
+
+function equivalent (a, b, message, extra) {
+  if (extra && extra.skip) return assert.skip(message, extra)
+  var extra = extra || {}
+  message = message || "should be equivalent"
+  extra.found = a
+  extra.wanted = b
+
+  if (Buffer.isBuffer(a) && Buffer.isBuffer(b)) {
+    return assert(bufferEqual(a, b), message, extra)
+  } else {
+    return assert(deepEqual(a, b), message, extra)
+  }
+}
+assert.equivalent = equivalent
+syns.equivalent = ["isEquivalent"
+                  ,"looseEqual"
+                  ,"looseEquals"
+                  ,"isDeeply"
+                  ,"same"
+                  ,"deepEqual"
+                  ,"deepEquals"]
+
+
+function inequal (a, b, message, extra) {
+  if (extra && extra.skip) return assert.skip(message, extra)
+  extra = extra || {}
+  message = message || "should not be equal"
+  extra.found = a
+  extra.doNotWant = b
+  return assert(a !== b, message, extra)
+}
+assert.inequal = inequal
+syns.inequal = ["notEqual"
+               ,"notEquals"
+               ,"notStrictEqual"
+               ,"notStrictEquals"
+               ,"isNotEqual"
+               ,"isNot"
+               ,"not"
+               ,"doesNotEqual"
+               ,"isInequal"]
+
+
+function inequivalent (a, b, message, extra) {
+  if (extra && extra.skip) return assert.skip(message, extra)
+  extra = extra || {}
+  message = message || "should not be equivalent"
+  extra.found = a
+  extra.doNotWant = b
+  
+  if (Buffer.isBuffer(a) && Buffer.isBuffer(b)) {
+    return assert(!bufferEqual(a, b), message, extra)
+  } else {
+    return assert(!deepEqual(a, b), message, extra)
+  }
+}
+assert.inequivalent = inequivalent
+syns.inequivalent = ["notEquivalent"
+                    ,"notDeepEqual"
+                    ,"notDeeply"
+                    ,"notSame"
+                    ,"isNotDeepEqual"
+                    ,"isNotDeeply"
+                    ,"isNotEquivalent"
+                    ,"isInequivalent"]
+
+function similar (a, b, message, extra, flip) {
+  if (extra && extra.skip) return assert.skip(message, extra)
+  // test that a has all the fields in b
+  message = message || "should be similar"
+
+  if (typeof a === "string" &&
+      (Object.prototype.toString.call(b) === "[object RegExp]")) {
+    extra = extra || {}
+    extra.pattern = b
+    extra.string = a
+    var ok = a.match(b)
+    extra.match = ok
+    if (flip) ok = !ok
+    return assert.ok(ok, message, extra)
+  }
+
+  var isObj = assert(a && typeof a === "object", message, extra)
+  if (!isObj.ok) {
+    // not an object
+    if (a == b) isObj.ok = true
+    if (flip) isObj.ok = !isObj.ok
+    return isObj
+  }
+
+  var eq = flip ? inequivalent : equivalent
+  return eq(selectFields(a, b), b, message, extra)
+}
+assert.similar = similar
+syns.similar = ["isSimilar"
+               ,"has"
+               ,"hasFields"
+               ,"like"
+               ,"isLike"]
+
+function dissimilar (a, b, message, extra) {
+  if (extra && extra.skip) return assert.skip(message, extra)
+  message = message || "should be dissimilar"
+  return similar(a, b, message, extra, true)
+}
+assert.dissimilar = dissimilar
+syns.dissimilar = ["unsimilar"
+                  ,"notSimilar"
+                  ,"unlike"
+                  ,"isUnlike"
+                  ,"notLike"
+                  ,"isNotLike"
+                  ,"doesNotHave"
+                  ,"isNotSimilar"
+                  ,"isDissimilar"]
+
+function type (thing, t, message, extra) {
+  if (extra && extra.skip) return assert.skip(message, extra)
+  var name = t
+  if (typeof name === "function") name = name.name || "(anonymous ctor)"
+  //console.error("name=%s", name)
+  message = message || "type is "+name
+  var type = typeof thing
+  //console.error("type=%s", type)
+  if (!thing && type === "object") type = "null"
+  if (type === "object" && t !== "object") {
+    if (typeof t === "function") {
+      //console.error("it is a function!")
+      extra = extra || {}
+      extra.found = Object.getPrototypeOf(thing).constructor.name
+      extra.wanted = name
+      //console.error(thing instanceof t, name)
+      return assert.ok(thing instanceof t, message, extra)
+    }
+
+    //console.error("check prototype chain")
+    // check against classnames or objects in prototype chain, as well.
+    // type(new Error("asdf"), "Error")
+    // type(Object.create(foo), foo)
+    var p = thing
+    while (p = Object.getPrototypeOf(p)) {
+      if (p === t || p.constructor && p.constructor.name === t) {
+        type = name
+        break
+      }
+    }
+  }
+  //console.error(type, name, type === name)
+  return assert.equal(type, name, message, extra)
+}
+assert.type = type
+syns.type = ["isa"]
+
+// synonyms are helpful.
+Object.keys(syns).forEach(function (c) {
+  syns[c].forEach(function (s) {
+    Object.defineProperty(assert, s, { value: assert[c], enumerable: false })
+  })
+})
+
+// helpers below
+
+function selectFields (a, b) {
+  // get the values in A of the fields in B
+  var ret = Array.isArray(b) ? [] : {}
+  Object.keys(b).forEach(function (k) {
+    if (!a.hasOwnProperty(k)) return
+    var v = b[k]
+      , av = a[k]
+    if (v && av && typeof v === "object" && typeof av === "object"
+       && !(v instanceof Date)
+       && !(v instanceof RegExp)
+       && !(v instanceof String)
+       && !(v instanceof Boolean)
+       && !(v instanceof Number)
+       && !(Array.isArray(v))) {
+      ret[k] = selectFields(av, v)
+    } else ret[k] = av
+  })
+  return ret
+}
+
+function sortObject (obj) {
+  if (typeof obj !== 'object' || Array.isArray(obj) || obj === null) {
+    return obj
+  }
+   
+  return Object.keys(obj).sort().reduce(function (acc, key) {
+    acc[key] = sortObject(obj[key])
+    return acc
+  }, {})
+}
+
+function stringify (a) {
+  return JSON.stringify(sortObject(a), (function () {
+    var seen = []
+      , keys = []
+    return function (key, val) {
+      var s = seen.indexOf(val)
+      if (s !== -1) {
+        return "[Circular: "+keys[s]+"]"
+      }
+      if (val && typeof val === "object" || typeof val === "function") {
+        seen.push(val)
+        keys.push(val["!"] || val.name || key || "<root>")
+        if (typeof val === "function") {
+          return val.toString().split(/\n/)[0]
+        } else if (typeof val.toUTCString === "function") {
+          return val.toUTCString()
+        }
+      }
+      return val
+  }})())
+}
+
+function diffString (f, w) {
+  if (w === f) return null
+  var p = 0
+    , l = w.length
+  while (p < l && w.charAt(p) === f.charAt(p)) p ++
+  w = stringify(w).substr(1).replace(/"$/, "")
+  f = stringify(f).substr(1).replace(/"$/, "")
+  return diff(f, w, p)
+}
+
+function diffObject (f, w) {
+  return difflet({ indent : 2, comment : true }).compare(w, f)
+}
+
+function diff (f, w, p) {
+  if (w === f) return null
+  var i = p || 0 // it's going to be at least p. JSON can only be bigger.
+    , l = w.length
+  while (i < l && w.charAt(i) === f.charAt(i)) i ++
+  var pos = Math.max(0, i - 20)
+  w = w.substr(pos, 40)
+  f = f.substr(pos, 40)
+  var pointer = i - pos
+  return "FOUND:  "+f+"\n"
+       + "WANTED: "+w+"\n"
+       + (new Array(pointer + 9).join(" "))
+       + "^ (at position = "+p+")"
+}
+
+function getCaller (er) {
+  // get the first file/line that isn't this file.
+  if (!er) er = new Error
+  var stack = er.stack || ""
+  stack = stack.split(/\n/)
+  for (var i = 1, l = stack.length; i < l; i ++) {
+    var s = stack[i].match(/\(([^):]+):([0-9]+):([0-9]+)\)$/)
+    if (!s) continue
+    var file = s[1]
+      , line = +s[2]
+      , col = +s[3]
+    if (file.indexOf(__dirname) === 0) continue
+    if (file.match(/tap-test\/test.js$/)) continue
+    else break
+  }
+  var res = {}
+  if (file && file !== __filename && !file.match(/tap-test\/test.js$/)) {
+    res.file = file
+    res.line = line
+    res.column = col
+  }
+
+  res.stack = stack.slice(1).map(function (s) {
+    return s.replace(/^\s*at\s*/, "")
+  })
+
+  return res
+}
+
+
diff --git a/lib/tap-browser-harness.js b/lib/tap-browser-harness.js
new file mode 100644
index 0000000..9eaa0e4
--- /dev/null
+++ b/lib/tap-browser-harness.js
@@ -0,0 +1,63 @@
+// this is just a harness that pipes to stdout.
+// It's the default one.
+module.exports = BrowserHarness
+
+var BrowserHarness = global.TAP_Browser_Harness
+  , inherits = require("inherits")
+  , Results = require("./tap-results")
+  , Harness = require("./tap-harness")
+  , Test = require("./tap-test")
+
+inherits(BrowserHarness, Harness)
+function BrowserHarness (outPipe) {
+  //console.error("calling BrowserHarness")
+  if (browserHarness) return browserHarness
+  if (!(this instanceof BrowserHarness)) {
+    return browserHarness = new BrowserHarness
+  }
+  browserHarness = global.TAP_Browser_Harness = this
+  Harness.call(this, Test)
+
+  if (outPipe) this.output.pipe(outPipe)
+
+  this.test = this.test.bind(this)
+
+  this.plan = this.plan.bind(this)
+
+  var output = this.output
+  this.on("childEnd", function (child) {
+    //console.error("childEnd in global harness")
+    //console.error(child.results)
+    // write out the stuff for this child.
+    //console.error("child.conf", child.conf)
+
+    // maybe write some other stuff about the number of tests in this
+    // thing, etc.  I dunno.
+    //console.error("child results", child.results)
+    this.results.list.forEach(function (res) {
+      //delete res.error
+      //console.error("child resuilt", res)
+      output.write(res)
+    })
+    //console.error("wrote child results")
+    this.results.list.length = 0
+  })
+
+  var streamEnded = false
+  this.on("end", function () {
+    //console.error("global ending the stream")
+    if (!streamEnded) {
+      this.results.list.forEach(function (res) {
+        output.write(res)
+      })
+      this.results.list.length = 0
+      output.end()
+      streamEnded = true
+    }
+  })
+
+  // TODO: handle global errors
+  // process.on("unhandledException", function (e) {
+  //   this.bailout("unhandled exception: " + e.message)
+  // })
+}
diff --git a/lib/tap-consumer.js b/lib/tap-consumer.js
new file mode 100644
index 0000000..0b991a5
--- /dev/null
+++ b/lib/tap-consumer.js
@@ -0,0 +1,246 @@
+module.exports = TapConsumer
+
+// pipe a stream into this that's emitting tap-formatted data,
+// and it'll emit "data" events with test objects or comment strings
+// and an "end" event with the final results.
+
+var yamlish = require("yamlish")
+  , Results = require("./tap-results")
+  , inherits = require("inherits")
+
+TapConsumer.decode = TapConsumer.parse = function (str) {
+  var tc = new TapConsumer
+    , list = []
+  tc.on("data", function (res) {
+    list.push(res)
+  })
+  tc.end(str)
+  tc.results.list = list
+  return tc.results
+}
+
+var Stream = require("stream").Stream
+inherits(TapConsumer, Stream)
+function TapConsumer () {
+  if (!(this instanceof TapConsumer)) {
+    return new TapConsumer
+  }
+
+  Stream.call(this)
+  this.results = new Results
+  this.readable = this.writable = true
+
+  this.on("data", function (res) {
+    if (typeof res === "object") this.results.add(res)
+  })
+
+  this._plan = null
+  this._buffer = ""
+  this._indent = []
+  this._current = null
+  this._actualCount = 0
+  this._passed = []
+  this._failed = []
+  //console.error("TapConsumer ctor done")
+}
+
+TapConsumer.prototype.bailedOut = false
+
+TapConsumer.prototype.write = function (chunk) {
+  if (!this.writable) this.emit("error", new Error("not writable"))
+  if (this.bailedOut) return true
+
+  this._buffer = this._buffer + chunk
+  // split it up into lines.
+  var lines = this._buffer.split(/\r?\n/)
+  // ignore the last line, since it might be incomplete.
+  this._buffer = lines.pop()
+
+  for (var i = 0, l = lines.length; i < l; i ++) {
+    //console.error([i, lines[i]])
+    // see if it's indented.
+    var line = lines[i]
+      , spaces = (this._indent.length && !line.trim())
+               || line.match(/^\s/)
+    // at this level, only interested in fully undented stuff.
+    if (spaces) {
+      var c = i
+      while (c < l && (!lines[c].trim() || lines[c].match(/^\s/))) {
+        this._indent.push(lines[c++])
+      }
+      //console.error(c-i, "indented", this._indent, this._current)
+      i = c - 1
+      continue
+    }
+    // some kind of line.  summary, ok, notok, comment, or garbage.
+    // this also finishes parsing any of the indented lines from before
+    this._parseLine(line)
+  }
+  return true
+}
+
+TapConsumer.prototype.end = function () {
+  // finish up any hanging indented sections or final buffer
+  if (this._buffer.match(/^\s/)) this._indent.push(this.buffer)
+  else this._parseLine(this._buffer)
+
+  if (!this.bailedOut &&
+      this._plan !== null &&
+      this.results.testsTotal !== this._plan) {
+    while (this._actualCount < this._plan) {
+      this.emit("data", {ok: false, name:"MISSING TEST",
+                         id:this._actualCount ++ })
+    }
+  }
+
+  this._parseLine("")
+  this._buffer = ""
+  this.writable = false
+  this.emit("end", null, this._actualCount, this._passed)
+}
+
+TapConsumer.prototype._parseLine = function (line) {
+  if (this.bailedOut) return
+  //console.error("_parseLine", [line])
+  // if there are any indented lines, and there is a
+  // current object already, then they belong to it.
+  // if there is not a current object, then they're garbage.
+  if (this._current && this._indent.length) {
+    this._parseIndented()
+  }
+  this._indent.length = 0
+  if (this._current) {
+    if (this._current.ok) this._passed.push(this._current.id)
+    else this._failed.push(this._current.id)
+    this.emit("data", this._current)
+  }
+  this._current = null
+  line = line.trim()
+  if (!line) return
+  // try to see what kind of line this is.
+
+  var bo
+  if (bo = line.match(/^bail out!\s*(.*)$/i)) {
+    this.bailedOut = true
+    // this.emit("error", new Error(line))
+    this.emit("bailout", bo[1])
+    return
+  }
+
+  if (line.match(/^#/)) { // just a comment
+    line = line.replace(/^#+/, "").trim()
+    // console.error("outputting comment", [line])
+    if (line) this.emit("data", line)
+    return
+  }
+
+  var plan = line.match(/^([0-9]+)\.\.([0-9]+)(?:\s+#(.*))?$/)
+  if (plan) {
+    var start = +(plan[1])
+      , end = +(plan[2])
+      , comment = plan[3]
+
+    // TODO: maybe do something else with this?
+    // it might be something like: "1..0 #Skip because of reasons"
+    this._plan = end
+    this.emit("plan", end, comment)
+    // plan must come before or after all tests.
+    if (this._actualCount !== 0) {
+      this._sawPlan = true
+    }
+    return
+  }
+
+  if (line.match(/^(not )?ok(?:\s+([0-9]+))?/)) {
+    this._parseResultLine(line)
+    return
+  }
+
+  // garbage.  emit as a comment.
+  //console.error("emitting", [line.trim()])
+  if (line.trim()) this.emit("data", line.trim())
+}
+
+TapConsumer.prototype._parseDirective = function (line) {
+  line = line.trim()
+  if (line.match(/^TODO\b/i)) {
+    return { todo:true, explanation: line.replace(/^TODO\s*/i, "") }
+  } else if (line.match(/^SKIP\b/i)) {
+    return { skip:true, explanation: line.replace(/^SKIP\s*/i, "") }
+  }
+}
+
+TapConsumer.prototype._parseResultLine = function (line) {
+  this._actualCount ++
+  if (this._sawPlan) {
+    this.emit("data", {ok: false, name:"plan in the middle of tests"
+                      ,id:this._actualCount ++})
+  }
+  var parsed = line.match(/^(not )?ok(?: ([0-9]+))?(?:(?: - )?(.*))?$/)
+    , ok = !parsed[1]
+    , id = +(parsed[2] || this._actualCount)
+    , rest = parsed[3] || ""
+    , name
+    , res = { id:id, ok:ok }
+
+  // split on un-escaped # characters
+
+  //console.log("# "+JSON.stringify([name, rest]))
+  rest = rest.replace(/([^\\])((?:\\\\)*)#/g, "$1\n$2").split("\n")
+  name = rest.shift()
+  rest = rest.filter(function (r) { return r.trim() }).join("#")
+  //console.log("# "+JSON.stringify([name, rest]))
+
+  // now, let's see if there's a directive in there.
+  var dir = this._parseDirective(rest.trim())
+  if (!dir) name += rest ? "#" + rest : ""
+  else {
+    res.ok = true
+    if (dir.skip) res.skip = true
+    else if (dir.todo) res.todo = true
+    if (dir.explanation) res.explanation = dir.explanation
+  }
+  res.name = name
+
+  //console.error(line, [ok, id, name])
+  this._current = res
+}
+
+TapConsumer.prototype._parseIndented = function () {
+  // pull yamlish block out
+  var ind = this._indent
+    , ys
+    , ye
+    , yind
+    , diag
+  //console.error(ind, this._indent)
+  for (var i = 0, l = ind.length; i < l; i ++) {
+    var line = ind[i]
+    if (line === undefined) continue
+    var lt = line.trim()
+
+    if (!ys) {
+      ys = line.match(/^(\s*)---(.*)$/)
+      if (ys) {
+        yind = ys[1]
+        diag = [ys[2]]
+        //console.error([line,ys, diag])
+        continue
+      } else if (lt) this.emit("data", lt)
+    } else if (ys && !ye) {
+      if (line === yind + "...") ye = true
+      else {
+        diag.push(line.substr(yind.length))
+      }
+    } else if (ys && ye && lt) this.emit("data", lt)
+  }
+  if (diag) {
+    //console.error('about to parse', diag)
+    diag = yamlish.decode(diag.join("\n"))
+    //console.error('parsed', diag)
+    Object.keys(diag).forEach(function (k) {
+      //console.error(this._current, k)
+      if (!this._current.hasOwnProperty(k)) this._current[k] = diag[k]
+    }, this)
+  }
+}
diff --git a/lib/tap-cov-html.js b/lib/tap-cov-html.js
new file mode 100644
index 0000000..3c1c192
--- /dev/null
+++ b/lib/tap-cov-html.js
@@ -0,0 +1,78 @@
+var fs = require('fs'),
+    path = require('path'),
+    asyncMap = require("slide").asyncMap,
+    util = require('util');
+
+var CovHtml = module.exports = function(cov_stats, cov_dir, cb) {
+  var index = [];
+
+  asyncMap(
+    Object.keys(cov_stats),
+    function(f, cb) {
+      var st = cov_stats[f],
+          missing_lines = st.missing.map(function(l) {
+            return l.number;
+          }),
+          out = '<!doctype html>\n<html lang="en">\n<head>\n  ' +
+                '<meta charset="utf-8">\n  <title>' +
+
+      f + ' (' + st.loc + ')</title>\n' +
+      '<style type="text/css">\n' + 
+      'li {\n' +
+      '  font-family: monospace;\n' +
+      '  white-space: pre;\n' +
+      '}\n' +
+      '</style>\n' +
+      '</head>\n<body>\n' +
+      '<h1>' + f + ' (' + st.loc + ')' + '</h1>' +
+      '<h2>Run: ' + (st.missing.length ? st.loc - st.missing.length : st.loc) + ', Missing: ' +
+      st.missing.length + ', Percentage: ' + st.percentage + '</h2>' +
+      '<h2>Source:</h2>\n' +
+      '<ol>\n' + 
+      st.lines.map(function(line) {
+        var number = line.number,
+            color = (missing_lines.indexOf(number) !== -1) ? '#fcc' : '#cfc';
+        return '<li id="L' + line.number + '" style="background-color: ' + color +
+               ';">' + line.source.replace(/</g, "<") + '</li>';
+      }).join('\n') + 
+      '</ol>\n' +
+      '<h2>Data</h2>\n'+
+      '<pre>' + util.inspect(st, true, Infinity, false).replace(/</g, "<") + '</pre></body>\n</html>';
+
+      fs.writeFile(
+        cov_dir + '/' + 
+        f.replace(process.cwd() + '/', '').replace(/\//g, '+') + '.html',
+        out,
+        'utf8',
+        function(err) {
+          if (err) {
+            throw err;
+          }
+          index.push(f);
+          cb();
+        });
+    },
+    function(err) {
+      if (err) {
+        throw err;
+      }
+      var out = '<!doctype html>\n<html lang="en">\n<head>\n  ' +
+          '<meta charset="utf-8">\n  <title>Coverage Index</title>\n</head>\n' +
+          '<body>\n<h1>Code Coverage Information</h1>\n<ul>' +
+          index.map(function(fname) {
+            return '<li><a href="' +
+            fname.replace(process.cwd() + '/', '').replace(/\//g, '+') + '.html' +
+            '">' + fname + '</a></li>';
+          }).join('\n') + '</ul>\n</body>\n</html>';
+
+      fs.writeFile(cov_dir + '/index.html', out, 'utf8', function(err) {
+        if (err) {
+          throw err;
+        }
+        cb();
+      });   
+    }
+  );
+};
+
+
diff --git a/lib/tap-global-harness.js b/lib/tap-global-harness.js
new file mode 100644
index 0000000..2fb1933
--- /dev/null
+++ b/lib/tap-global-harness.js
@@ -0,0 +1,68 @@
+// this is just a harness that pipes to stdout.
+// It's the default one.
+module.exports = GlobalHarness
+
+var globalHarness = global.TAP_Global_Harness
+  , inherits = require("inherits")
+  , Results = require("./tap-results")
+  , Harness = require("./tap-harness")
+  , Test = require("./tap-test")
+
+inherits(GlobalHarness, Harness)
+function GlobalHarness () {
+  //console.error("calling GlobalHarness")
+  if (globalHarness) return globalHarness
+  if (!(this instanceof GlobalHarness)) {
+    return globalHarness = new GlobalHarness
+  }
+
+  globalHarness = global.TAP_Global_Harness = this
+  Harness.call(this, Test)
+
+  this.output.pipe(process.stdout)
+  //this.output.on("data", function () {
+  //  process.nextTick(process.stdout.flush.bind(process.stdout))
+  //})
+
+  this.test = this.test.bind(this)
+
+  this.plan = this.plan.bind(this)
+
+  var output = this.output
+  this.on("childEnd", function (child) {
+    //console.error("childEnd in global harness")
+    //console.error(child.results)
+    // write out the stuff for this child.
+    //console.error("child.conf", child.conf)
+
+    // maybe write some other stuff about the number of tests in this
+    // thing, etc.  I dunno.
+    //console.error("child results", child.results)
+    this.results.list.forEach(function (res) {
+      //delete res.error
+      //console.error("child resuilt", res)
+      output.write(res)
+    })
+    //console.error("wrote child results")
+    this.results.list.length = 0
+  })
+
+  var streamEnded = false
+  this.on("end", function () {
+    //console.error("global ending the stream")
+    if (!streamEnded) {
+      this.results.list.forEach(function (res) {
+        output.write(res)
+      })
+      this.results.list.length = 0
+      output.end()
+      streamEnded = true
+    }
+  })
+
+  //this.on("end", this.output.end.bind(this.output))
+
+  process.on("unhandledException", function (e) {
+    this.bailout("unhandled exception: " + e.message)
+  })
+}
diff --git a/lib/tap-harness.js b/lib/tap-harness.js
new file mode 100644
index 0000000..f06f63c
--- /dev/null
+++ b/lib/tap-harness.js
@@ -0,0 +1,224 @@
+// a thing that runs tests.
+// Every "test" is also a harness.  If they do not have a harness,
+// then they are attached to the defaut "global harness",
+// which writes its results to stdout.
+
+
+// TODO:
+// - Bailout should stop running any tests.
+// - "skip" in the test config obj should skip it.
+
+module.exports = Harness
+var EE = require("events").EventEmitter
+require("inherits")(Harness, EE)
+
+var Results = require("./tap-results")
+  , TapProducer = require("./tap-producer")
+  , assert = require("./tap-assert")
+
+function Harness (Test) {
+  if (!(this instanceof Harness)) return new Harness(Test)
+
+  //console.error("Test in "+this.constructor.name, Test)
+
+  this._Test = Test
+  this._plan = null
+  this._children = []
+  this._started = false
+
+  this._testCount = 0
+  this._planSum = 0
+
+  this.results = new Results()
+  // emit result events on the harness.
+  //this.results.on("result", function (res) {
+  //  console.error("proxying result ev from res to harness")
+  //  this.emit("result", res)
+  //}.bind(this))
+  var me = this
+  this.results.on("result", this.emit.bind(this, "result"))
+
+  var p = this.process.bind(this)
+  this.process = function () {
+    this._started = true
+    process.nextTick(p)
+  }
+
+  this.output = new TapProducer()
+  EE.call(this)
+}
+
+// this function actually only gets called bound to
+// the Harness object, and on process.nextTick.  Even if
+// passed as an event handler, everything *else* will
+// happen before it gets called.
+Harness.prototype.process = function () {
+  //console.error("harness process")
+  // "end" can emit multiple times, so only actually move on
+  // to the next test if the current one is actually over.
+  // TODO: multiple in-process tests, if all are marked "async"
+  if (this._current) {
+    if (!this._current._ended) return
+    // handle the current one before moving onto the next.
+    this.childEnd(this._current)
+  }
+  var skip = true
+  while (skip) {
+    //console.error("checking for skips")
+    var current = this._current = this._children.shift()
+    if (current) {
+      skip = current.conf.skip
+      if (skip) {
+        //console.error("add a failure for the skipping")
+        this.results.add(assert.fail(current.conf.name
+                                    ,{skip:true, diag:false}))
+      }
+    } else skip = false
+  }
+
+  // keep processing through skipped tests, instead of running them.
+  if (current && this._bailedOut) {
+    return this.process()
+  }
+
+  //console.error("got current?", !!current)
+  if (current) {
+    current.on("end", this.process)
+    current.emit("ready")
+    //console.error("emitted ready")
+    //console.error("_plan", this._plan, this.constructor.name)
+  } else {
+    //console.error("Harness process: no more left.  ending")
+    if (this._endNice) {
+      this._endNice()
+    } else {
+      this.end()
+    }
+  }
+}
+
+Harness.prototype.end = function () {
+  if (this._children.length) {
+    return this.process()
+  }
+  //console.error("harness end", this.constructor.name)
+  if (this._bailedOut) return
+
+  // can't call .end() more than once.
+  if (this._ended) {
+    //console.error("adding failure for end calling")
+    this.results.add(assert.fail("end called more than once"))
+  }
+
+  // see if the plan is completed properly, if there was one.
+  if (this._plan !== null) {
+    var total = this._testCount
+    if (total !== this._plan) {
+      this.results.add(assert.equal(total, this._plan, "test count != plan"))
+    }
+    this._plan = total
+  }
+
+  //console.error("setting ended true", this.constructor.name)
+  this._ended = true
+  this.emit("end")
+}
+
+Harness.prototype.plan = function (p) {
+  //console.error("setting plan", new Error().stack)
+  if (this._plan !== null) {
+    //console.error("about to add failure for calling plan")
+    return this.results.add(assert.fail("plan set multiple times"))
+  }
+  this._plan = p
+  if (p === 0 || this.results.testsTotal) {
+    this.end()
+  }
+}
+
+Harness.prototype.childEnd = function (child) {
+  //console.error("childEnd")
+  this._testCount ++
+  this._planSum += child._plan
+  //console.error("adding set of child.results")
+
+  this.results.add(child.conf.name || "(unnamed test)")
+  this.results.addSet(child.results)
+  this.emit("childEnd", child)
+  // was this planned?
+  if (this._plan === this._testCount) {
+    //console.error("plan", [this._plan, this._testCount])
+    return this.end()
+  }
+}
+
+function copyObj(o) {
+  var copied = {}
+  Object.keys(o).forEach(function (k) { copied[k] = o[k] })
+  return copied
+}
+
+Harness.prototype.test = function test (name, conf, cb) {
+  if (this._bailedOut) return
+
+  if (typeof conf === "function") cb = conf, conf = null
+  if (typeof name === "object") conf = name, name = null
+  if (typeof name === "function") cb = name, name = null
+
+  conf = (conf ? copyObj(conf) : {})
+  name = name || ""
+
+  //console.error("making test", [name, conf, cb])
+
+  // timeout: value in milliseconds. Defaults to 30s
+  // Set to Infinity to have no timeout.
+  if (isNaN(conf.timeout)) conf.timeout = 30000
+  var t = new this._Test(this, name, conf)
+  var self = this
+  if (cb) {
+    //console.error("attaching cb to ready event")
+    t.on("ready", function () {
+      if (!isNaN(conf.timeout) && isFinite(conf.timeout)) {
+        var timer = setTimeout(this.timeout.bind(this), conf.timeout)
+        var clear = function () {
+          clearTimeout(timer)
+        }
+        t.on("end", clear)
+        t.on("bailout", function (message) {
+          self.bailout(message)
+          clear()
+        })
+      }
+    })
+    t.on("ready", cb.bind(t, t))
+    // proxy the child results to this object.
+    //t.on("result", function (res) {
+    //  console.error("in harness, proxying result up")
+    //  t.results.add(res)
+    //})
+  }
+  return t
+}
+
+Harness.prototype.bailout = function (message) {
+  // console.error("Harness bailout", this.constructor.name)
+  message = message || ""
+  //console.error("adding bailout message result")
+  this.results.add({bailout: message})
+  // console.error(">>> results after bailout" , this.results)
+  this._bailedOut = true
+  this.emit("bailout", message)
+  this.output.end({bailout: message})
+}
+
+Harness.prototype.add = function (child) {
+  //console.error("adding child")
+  this._children.push(child)
+  if (!this._started) this.process()
+}
+
+// the tearDown function is *always* guaranteed to happen.
+// Even if there's a bailout.
+Harness.prototype.tearDown = function (fn) {
+  this.on("end", fn)
+}
diff --git a/lib/tap-producer.js b/lib/tap-producer.js
new file mode 100644
index 0000000..99dbb87
--- /dev/null
+++ b/lib/tap-producer.js
@@ -0,0 +1,131 @@
+module.exports = TapProducer
+
+var Results = require("./tap-results")
+  , inherits = require("inherits")
+  , yamlish = require("yamlish")
+
+TapProducer.encode = function (result, diag) {
+  var tp = new TapProducer(diag)
+    , out = ""
+  tp.on("data", function (c) { out += c })
+  if (Array.isArray(result)) {
+    result.forEach(tp.write, tp)
+  } else tp.write(result)
+  tp.end()
+  return out
+}
+
+var Stream = require("stream").Stream
+inherits(TapProducer, Stream)
+function TapProducer (diag) {
+  Stream.call(this)
+  this.diag = diag
+  this.count = 0
+  this.readable = this.writable = true
+  this.results = new Results
+}
+
+TapProducer.prototype.trailer = true
+
+TapProducer.prototype.write = function (res) {
+  // console.error("TapProducer.write", res)
+  if (typeof res === "function") throw new Error("wtf?")
+  if (!this.writable) this.emit("error", new Error("not writable"))
+
+  if (!this._didHead) {
+    this.emit("data", "TAP version 13\n")
+    this._didHead = true
+  }
+
+  var diag = res.diag
+  if (diag === undefined) diag = this.diag
+
+  this.emit("data", encodeResult(res, this.count + 1, diag))
+
+  if (typeof res === "string") return true
+
+  if (res.bailout) {
+    var bo = "bail out!"
+    if (typeof res.bailout === "string") bo += " " + res.bailout
+    this.emit("data", bo)
+    return
+  }
+  this.results.add(res, false)
+
+  this.count ++
+}
+
+TapProducer.prototype.end = function (res) {
+  if (res) this.write(res)
+  // console.error("TapProducer end", res, this.results)
+  this.emit("data", "\n1.."+this.results.testsTotal+"\n")
+  if (this.trailer && typeof this.trailer !== "string") {
+    // summary trailer.
+    var trailer = "tests "+this.results.testsTotal + "\n"
+    if (this.results.pass) {
+      trailer += "pass  " + this.results.pass + "\n"
+    }
+    if (this.results.fail) {
+      trailer += "fail  " + this.results.fail + "\n"
+    }
+    if (this.results.skip) {
+      trailer += "skip  "+this.results.skip + "\n"
+    }
+    if (this.results.todo) {
+      trailer += "todo  "+this.results.todo + "\n"
+    }
+    if (this.results.bailedOut) {
+      trailer += "bailed out" + "\n"
+    }
+
+    if (this.results.testsTotal === this.results.pass) {
+      trailer += "\nok\n"
+    }
+    this.trailer = trailer
+  }
+  if (this.trailer) this.write(this.trailer)
+  this.writable = false
+  this.emit("end", null, this.count, this.ok)
+}
+
+function encodeResult (res, count, diag) {
+  // console.error(res, count, diag)
+  if (typeof res === "string") {
+    res = res.split(/\r?\n/).map(function (l) {
+      if (!l.trim()) return l.trim()
+      return "# " + l
+    }).join("\n")
+    if (res.substr(-1) !== "\n") res += "\n"
+    return res
+  }
+
+  if (res.bailout) return ""
+
+
+  if (!!process.env.TAP_NODIAG) diag = false
+  else if (!!process.env.TAP_DIAG) diag = true
+  else if (diag === undefined) diag = !res.ok
+
+  var output = ""
+  res.name = res.name && ("" + res.name).trim()
+  output += ( !res.ok ? "not " : "") + "ok " + count
+            + ( !res.name ? ""
+              : " " + res.name.replace(/[\r\n]/g, " ") )
+            + ( res.skip ? " # SKIP"
+              : res.todo ? " # TODO"
+              : "" )
+            + "\n"
+
+  if (!diag) return output
+  var d = {}
+    , dc = 0
+  Object.keys(res).filter(function (k) {
+    return k !== "ok" && k !== "name" && k !== "id"
+  }).forEach(function (k) {
+    dc ++
+    d[k] = res[k]
+  })
+  //console.error(d, "about to encode")
+  if (dc > 0) output += "  ---"+yamlish.encode(d)+"\n  ...\n"
+  return output
+}
diff --git a/lib/tap-results.js b/lib/tap-results.js
new file mode 100644
index 0000000..6fe90e8
--- /dev/null
+++ b/lib/tap-results.js
@@ -0,0 +1,71 @@
+// A class for counting up results in a test harness.
+
+module.exports = Results
+
+var inherits = require("inherits")
+  , EventEmitter = require("events").EventEmitter
+
+inherits(Results, EventEmitter)
+
+function Results (r) {
+  //console.error("result constructor", r)
+  this.ok = true
+  this.addSet(r)
+}
+
+Results.prototype.addSet = function (r) {
+  //console.error("add set of results", r)
+  r = r || {ok: true}
+  ; [ "todo"
+    , "todoPass"
+    , "todoFail"
+    , "skip"
+    , "skipPass"
+    , "skipFail"
+    , "pass"
+    , "passTotal"
+    , "fail"
+    , "failTotal"
+    , "tests"
+    , "testsTotal" ].forEach(function (k) {
+      this[k] = (this[k] || 0) + (r[k] || 0)
+      //console.error([k, this[k]])
+    }, this)
+
+  this.ok = this.ok && r.ok && true
+  this.bailedOut = this.bailedOut || r.bailedOut || false
+  this.list = (this.list || []).concat(r.list || [])
+  this.emit("set", this.list)
+  //console.error("after addSet", this)
+}
+
+Results.prototype.add = function (r, addToList) {
+  //console.error("add result", r)
+  var pf = r.ok ? "pass" : "fail"
+    , PF = r.ok ? "Pass" : "Fail"
+
+  this.testsTotal ++
+  this[pf + "Total"] ++
+
+  if (r.skip) {
+    this["skip" + PF] ++
+    this.skip ++
+  } else if (r.todo) {
+    this["todo" + PF] ++
+    this.todo ++
+  } else {
+    this.tests ++
+    this[pf] ++
+  }
+
+  if (r.bailout || typeof r.bailout === "string") {
+    // console.error("Bailing out in result")
+    this.bailedOut = true
+  }
+  this.ok = !!(this.ok && r.ok)
+
+  if (addToList === false) return
+  this.list = this.list || []
+  this.list.push(r)
+  this.emit("result", r)
+}
diff --git a/lib/tap-runner.js b/lib/tap-runner.js
new file mode 100644
index 0000000..c60e8d1
--- /dev/null
+++ b/lib/tap-runner.js
@@ -0,0 +1,501 @@
+var fs = require("fs")
+  , child_process = require("child_process")
+  , path = require("path")
+  , chain = require("slide").chain
+  , asyncMap = require("slide").asyncMap
+  , TapProducer = require("./tap-producer.js")
+  , TapConsumer = require("./tap-consumer.js")
+  , assert = require("./tap-assert.js")
+  , inherits = require("inherits")
+  , util = require("util")
+  , CovHtml = require("./tap-cov-html.js")
+  , glob = require("glob")
+
+  // XXX Clean up the coverage options
+  , doCoverage = process.env.TAP_COV
+               || process.env.npm_package_config_coverage
+               || process.env.npm_config_coverage
+
+module.exports = Runner
+
+inherits(Runner, TapProducer)
+
+function Runner (options, cb) {
+  this.options = options
+
+  var diag = this.options.diag
+  var dir = this.options.argv.remain
+  TapProducer.call(this, diag)
+
+  this.doCoverage = doCoverage
+  // An array of full paths to files to obtain coverage
+  this.coverageFiles = []
+  // The source of these files
+  this.coverageFilesSource = {}
+  // Where to write coverage information
+  this.coverageOutDir = this.options["coverage-dir"]
+  // Temporary test files bunkerified we'll remove later
+  this.f2delete = []
+  // Raw coverage stats, as read from JSON files
+  this.rawCovStats = []
+  // Processed coverage information, per file to cover:
+  this.covStats = {}
+
+  if (dir) {
+    var filesToCover = this.options.cover
+
+    if (doCoverage) {
+      var mkdirp = require("mkdirp")
+      this.coverageOutDir = path.resolve(this.coverageOutDir)
+      this.getFilesToCover(filesToCover)
+      var self = this
+      return mkdirp(this.coverageOutDir, 0755, function (er) {
+        if (er) return self.emit("error", er)
+        self.run(dir, cb)
+      })
+    }
+
+    this.run(dir, cb)
+  }
+}
+
+
+Runner.prototype.run = function() {
+  var self = this
+    , args = Array.prototype.slice.call(arguments)
+    , cb = args.pop() || finish
+
+  function finish (er) {
+    if (er) {
+      self.emit("error", er)
+    }
+
+    if (!doCoverage) return self.end()
+
+    // Cleanup temporary test files with coverage:
+    self.f2delete.forEach(function(f) {
+      fs.unlinkSync(f)
+    })
+    self.getFilesToCoverSource(function(err, data) {
+      if (err) {
+        self.emit("error", err)
+      }
+      self.getPerFileCovInfo(function(err, data) {
+        if (err) {
+          self.emit("error", err)
+        }
+        self.mergeCovStats(function(err, data) {
+          if (err) {
+            self.emit("error", err)
+          }
+          CovHtml(self.covStats, self.coverageOutDir, function() {
+            self.end()
+          })
+        })
+      })
+    })
+  }
+
+  if (Array.isArray(args[0])) {
+    args = args[0]
+  }
+  self.runFiles(args, "", cb)
+}
+
+Runner.prototype.runDir = function (dir, cb) {
+  var self = this
+  fs.readdir(dir, function (er, files) {
+    if (er) {
+      self.write(assert.fail("failed to readdir " + dir, { error: er }))
+      self.end()
+      return
+    }
+    files = files.sort(function(a, b) {
+      return a > b ? 1 : -1
+    })
+    files = files.filter(function(f) {
+      return !f.match(/^\./)
+    })
+    files = files.map(function(file) {
+      return path.resolve(dir, file)
+    })
+
+    self.runFiles(files, path.resolve(dir), cb)
+  })
+}
+
+
+// glob the filenames so that test/*.js works on windows
+Runner.prototype.runFiles = function (files, dir, cb) {
+  var self = this
+  var globRes = []
+  chain(files.map(function (f) {
+    return function (cb) {
+      glob(f, function (er, files) {
+        if (er)
+          return cb(er)
+        globRes.push.apply(globRes, files)
+        cb()
+      })
+    }
+  }), function (er) {
+    if (er)
+      return cb(er)
+    runFiles(self, globRes, dir, cb)
+  })
+}
+
+// set some default options for node debugging tests
+function setOptionsForDebug(self) {
+  // Note: we automatically increase the default timeout here. Yes
+  // the user can specify --timeout to increase, but by default,
+  // 30 seconds is not a long time to debug your test.
+  self.options.timeout = 1000000;
+
+  // Note: we automatically turn on stderr so user can see the 'debugger listening on port' message.
+  // Without this it looks like tap has hung..
+  self.options.stderr = true;
+}
+
+function runFiles(self, files, dir, cb) {
+  chain(files.map(function(f) {
+    return function (cb) {
+      if (self._bailedOut) return
+      var relDir = dir || path.dirname(f)
+        , fileName = relDir === "." ? f : f.substr(relDir.length + 1)
+
+      self.write(fileName)
+      fs.lstat(f, function(er, st) {
+        if (er) {
+          self.write(assert.fail("failed to stat " + f, {error: er}))
+          return cb()
+        }
+
+        var cmd = f, args = [], env = {}
+
+        if (path.extname(f) === ".js") {
+          cmd = process.execPath
+          if (self.options.gc) {
+            args.push("--expose-gc")
+          }
+          if (self.options.debug) {
+            args.push("--debug")
+            setOptionsForDebug(self)
+          }
+          if (self.options["debug-brk"]) {
+            args.push("--debug-brk")
+            setOptionsForDebug(self)
+          }
+          if (self.options.strict) {
+            args.push("--use-strict")
+          }
+          if (self.options.harmony) {
+            args.push("--harmony")
+          }
+          args.push(fileName)
+        } else if (path.extname(f) === ".coffee") {
+          cmd = "coffee"
+          args.push(fileName)
+        } else {
+          // Check if file is executable
+          if ((st.mode & 0100) && process.getuid) {
+            if (process.getuid() != st.uid) {
+              return cb()
+            }
+          } else if ((st.mode & 0010) && process.getgid) {
+            if (process.getgid() != st.gid) {
+              return cb()
+            }
+          } else if ((st.mode & 0001) == 0) {
+            return cb()
+          }
+        }
+
+        if (st.isDirectory()) {
+          return self.runDir(f, cb)
+        }
+
+        if (doCoverage && path.extname(f) === ".js") {
+          var foriginal = fs.readFileSync(f, "utf8")
+            , fcontents = self.coverHeader() + foriginal + self.coverFooter()
+            , tmpBaseName = path.basename(f, path.extname(f))
+                          + ".with-coverage." + process.pid + path.extname(f)
+            , tmpFname = path.resolve(path.dirname(f), tmpBaseName)
+
+          fs.writeFileSync(tmpFname, fcontents, "utf8")
+          args.splice(-1, 1, tmpFname)
+        }
+
+        for (var i in process.env) {
+          env[i] = process.env[i]
+        }
+        env.TAP = 1
+
+        var cp = child_process.spawn(cmd, args, { env: env, cwd: relDir })
+          , out = ""
+          , err = ""
+          , tc = new TapConsumer()
+          , childTests = [f]
+
+        var timeout = setTimeout(function () {
+          if (!cp._ended) {
+            cp._timedOut = true
+            cp.kill()
+          }
+        }, self.options.timeout * 1000)
+
+        tc.on("data", function(c) {
+          self.emit("result", c)
+          self.write(c)
+        })
+
+        tc.on("bailout", function (message) {
+          clearTimeout(timeout)
+          console.log("# " + f.substr(process.cwd().length + 1))
+          process.stderr.write(err)
+          process.stdout.write(out + "\n")
+          self._bailedOut = true
+          cp._ended = true
+          cp.kill()
+        })
+
+        cp.stdout.pipe(tc)
+        cp.stdout.on("data", function (c) { out += c })
+        cp.stderr.on("data", function (c) {
+          if (self.options.stderr) process.stderr.write(c)
+          err += c
+        })
+
+        cp.on("close", function (code, signal) {
+          if (cp._ended) return
+          cp._ended = true
+          var ok = !cp._timedOut && code === 0
+          clearTimeout(timeout)
+          //childTests.forEach(function (c) { self.write(c) })
+          var res = { name: path.dirname(f).replace(process.cwd() + "/", "")
+                          + "/" + fileName
+                    , ok: ok
+                    , exit: code }
+
+          if (cp._timedOut)
+            res.timedOut = cp._timedOut
+          if (signal)
+            res.signal = signal
+
+          if (err) {
+            res.stderr = err
+            if (tc.results.ok &&
+                tc.results.tests === 0 &&
+                !self.options.stderr) {
+              // perhaps a compilation error or something else failed.
+              // no need if stderr is set, since it will have been
+              // output already anyway.
+              console.error(err)
+            }
+          }
+
+          // tc.results.ok = tc.results.ok && ok
+          tc.results.add(res)
+          res.command = '"'+[cmd].concat(args).join(" ")+'"'
+          self.emit("result", res)
+          self.emit("file", f, res, tc.results)
+          self.write(res)
+          self.write("\n")
+          if (doCoverage) {
+            self.f2delete.push(tmpFname)
+          }
+          cb()
+        })
+      })
+    }
+  }), cb)
+
+  return self
+}
+
+
+// Get an array of full paths to files we are interested into obtain
+// code coverage.
+Runner.prototype.getFilesToCover = function(filesToCover) {
+  var self = this
+  filesToCover = filesToCover.split(",").map(function(f) {
+    return path.resolve(f)
+  }).filter(function(f) {
+    var existsSync = fs.existsSync || path.existsSync;
+    return existsSync(f)
+  })
+
+  function recursive(f) {
+    if (path.extname(f) === "") {
+      // Is a directory:
+      fs.readdirSync(f).forEach(function(p) {
+        recursive(f + "/" + p)
+      })
+    } else {
+      self.coverageFiles.push(f)
+    }
+  }
+  filesToCover.forEach(function(f) {
+    recursive(f)
+  })
+}
+
+// Prepend to every test file to run. Note tap.test at the very top due it
+// "plays" with include paths.
+Runner.prototype.coverHeader = function() {
+  // semi here since we're injecting it before the first line,
+  // and don't want to mess up line numbers in the test files.
+  return "var ___TAP_COVERAGE = require("
+       + JSON.stringify(require.resolve("runforcover"))
+       + ").cover(/.*/g);"
+}
+
+// Append at the end of every test file to run. Actually, the stuff which gets
+// the coverage information.
+// Maybe it would be better to move into a separate file template so editing
+// could be easier.
+Runner.prototype.coverFooter = function() {
+  var self = this
+  // This needs to be a string with proper interpolations:
+  return [ ""
+  , "var ___TAP = require(" + JSON.stringify(require.resolve("./main.js")) + ")"
+  , "if (typeof ___TAP._plan === 'number') ___TAP._plan ++"
+  , "___TAP.test(" + JSON.stringify("___coverage") + ", function(t) {"
+  , "  var covFiles = " + JSON.stringify(self.coverageFiles)
+  , "    , covDir = " + JSON.stringify(self.coverageOutDir)
+  , "    , path = require('path')"
+  , "    , fs = require('fs')"
+  , "    , testFnBase = path.basename(__filename, '.js') + '.json'"
+  , "    , testFn = path.resolve(covDir, testFnBase)"
+  , ""
+  , "  function asyncForEach(arr, fn, callback) {"
+  , "    if (!arr.length) {"
+  , "      return callback()"
+  , "    }"
+  , "    var completed = 0"
+  , "    arr.forEach(function(i) {"
+  , "      fn(i, function (err) {"
+  , "        if (err) {"
+  , "          callback(err)"
+  , "          callback = function () {}"
+  , "        } else {"
+  , "          completed += 1"
+  , "          if (completed === arr.length) {"
+  , "            callback()"
+  , "          }"
+  , "        }"
+  , "      })"
+  , "    })"
+  , "  }"
+  , ""
+  , "  ___TAP_COVERAGE(function(coverageData) {"
+  , "    var outObj = {}"
+  , "    asyncForEach(covFiles, function(f, cb) {"
+  , "      if (coverageData[f]) {"
+  , "        var stats = coverageData[f].stats()"
+  , "          , stObj = stats"
+  , "        stObj.lines = stats.lines.map(function (l) {"
+  , "          return { number: l.lineno, source: l.source() }"
+  , "        })"
+  , "        outObj[f] = stObj"
+  , "      }"
+  , "      cb()"
+  , "    }, function(err) {"
+  , "      ___TAP_COVERAGE.release()"
+  , "      fs.writeFileSync(testFn, JSON.stringify(outObj))"
+  , "      t.end()"
+  , "    })"
+  , "  })"
+  , "})" ].join("\n")
+}
+
+
+Runner.prototype.getFilesToCoverSource = function(cb) {
+  var self = this
+  asyncMap(self.coverageFiles, function(f, cb) {
+    fs.readFile(f, "utf8", function(err, data) {
+      var lc = 0
+      if (err) {
+        cb(err)
+      }
+      self.coverageFilesSource[f] = data.split("\n").map(function(l) {
+        lc += 1
+        return { number: lc, source: l }
+      })
+      cb()
+    })
+  }, cb)
+}
+
+Runner.prototype.getPerFileCovInfo = function(cb) {
+  var self = this
+    , covPath = path.resolve(self.coverageOutDir)
+
+  fs.readdir(covPath, function(err, files) {
+    if (err) {
+      self.emit("error", err)
+    }
+    var covFiles = files.filter(function(f) {
+      return path.extname(f) === ".json"
+    })
+    asyncMap(covFiles, function(f, cb) {
+      fs.readFile(path.resolve(covPath, f), "utf8", function(err, data) {
+        if (err) {
+          cb(err)
+        }
+        self.rawCovStats.push(JSON.parse(data))
+        cb()
+      })
+    }, function(f, cb) {
+      fs.unlink(path.resolve(covPath, f), cb)
+    }, cb)
+  })
+}
+
+Runner.prototype.mergeCovStats = function(cb) {
+  var self = this
+  self.rawCovStats.forEach(function(st) {
+    Object.keys(st).forEach(function(i) {
+      // If this is the first time we reach this file, just add the info:
+      if (!self.covStats[i]) {
+        self.covStats[i] = {
+          missing: st[i].lines
+        }
+      } else {
+        // If we already added info for this file before, we need to remove
+        // from self.covStats any line not duplicated again (since it has
+        // run on such case)
+        self.covStats[i].missing = self.covStats[i].missing.filter(
+          function(l) {
+            return (st[i].lines.indexOf(l))
+          })
+      }
+    })
+  })
+
+  // This is due to a bug into
+  // chrisdickinson/node-bunker/blob/feature/add-coverage-interface
+  // which is using array indexes for line numbers instead of the right number
+  Object.keys(self.covStats).forEach(function(f) {
+    self.covStats[f].missing = self.covStats[f].missing.map(function(line) {
+      return { number: line.number, source: line.source }
+    })
+  })
+
+  Object.keys(self.coverageFilesSource).forEach(function(f) {
+    if (!self.covStats[f]) {
+      self.covStats[f] = { missing: self.coverageFilesSource[f]
+                          , percentage: 0
+      }
+    }
+    self.covStats[f].lines = self.coverageFilesSource[f]
+    self.covStats[f].loc = self.coverageFilesSource[f].length
+
+    if (!self.covStats[f].percentage) {
+      self.covStats[f].percentage =
+        1 - (self.covStats[f].missing.length / self.covStats[f].loc)
+    }
+
+  })
+  cb()
+}
diff --git a/lib/tap-test.js b/lib/tap-test.js
new file mode 100644
index 0000000..ec73321
--- /dev/null
+++ b/lib/tap-test.js
@@ -0,0 +1,110 @@
+// This is a very simple test framework that leverages the tap framework
+// to run tests and output tap-parseable results.
+
+module.exports = Test
+
+var assert = require("./tap-assert")
+  , inherits = require("inherits")
+  , Results = require("./tap-results")
+  , Harness = require("./tap-harness")
+
+// tests are also test harnesses
+inherits(Test, Harness)
+
+function Test (harness, name, conf) {
+  //console.error("test ctor")
+  if (!(this instanceof Test)) return new Test(harness, name, conf)
+
+  Harness.call(this, Test)
+
+  conf.name = name || conf.name || "(anonymous)"
+  this.conf = conf
+
+  this.harness = harness
+  this.harness.add(this)
+}
+
+// it's taking too long!
+Test.prototype.timeout = function () {
+  // detect false alarms
+  if (this._ended) return
+  this.fail("Timeout!")
+  this.end()
+}
+
+Test.prototype.clear = function () {
+  this._started = false
+  this._ended = false
+  this._plan = null
+  this._bailedOut = false
+  this._testCount = 0
+  this.results = new Results()
+}
+
+// this gets called if a test throws ever
+Test.prototype.threw = function (ex) {
+  //console.error("threw!", ex.stack)
+  this.fail(ex.name + ": " + ex.message, { error: ex, thrown: true })
+  // may emit further failing tests if the plan is not completed
+  //console.error("end, because it threw")
+  if (!this._ended) this.end()
+}
+
+Test.prototype.comment = function (m) {
+  if (typeof m !== "string") {
+    return this.fail("Test.comment argument must be a string")
+  }
+  this.result("\n" + m.trim())
+}
+
+Test.prototype.result = function (res) {
+  this.results.add(res)
+  this._testCount ++
+  this.emit("result", res)
+  if (this._plan === this._testCount) {
+    process.nextTick(this._endNice.bind(this))
+  }
+}
+
+Test.prototype._endNice = function () {
+  if (!this._ended) this.end()
+}
+
+// parasitic
+// Who says you can't do multiple inheritance in js?
+Object.getOwnPropertyNames(assert).forEach(function (k) {
+  if (k === "prototype" || k === "name") return
+  var d = Object.getOwnPropertyDescriptor(assert, k)
+    , v = d.value
+  if (!v) return
+  d.value = assertParasite(v)
+  Object.defineProperty(Test.prototype, k, d)
+})
+
+function assertParasite (fn) { return function _testAssert () {
+  //console.error("_testAssert", fn.name, arguments)
+  if (this._bailedOut) return
+  var res = fn.apply(assert, arguments)
+  this.result(res)
+  return res
+}}
+
+// a few tweaks on the EE emit function, because
+// we want to catch all thrown errors and bubble up "bailout"
+Test.prototype.emit = (function (em) { return function (t) {
+  // bailouts bubble until handled
+  if (t === "bailout" &&
+      this.listeners(t).length === 0 &&
+      this.harness) {
+    return this.harness.bailout(arguments[1])
+  }
+
+  if (t === "error") return em.apply(this, arguments)
+  try {
+    em.apply(this, arguments)
+  } catch (ex) {
+    // any exceptions in a test are a failure
+    //console.error("caught!", ex.stack)
+    this.threw(ex)
+  }
+}})(Harness.prototype.emit)
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..4dcbdf3
--- /dev/null
+++ b/package.json
@@ -0,0 +1,41 @@
+{
+  "name": "tap",
+  "version": "0.4.13",
+  "author": "Isaac Z. Schlueter <i at izs.me> (http://blog.izs.me)",
+  "description": "A Test-Anything-Protocol library",
+  "bin": "bin/tap.js",
+  "main": "lib/main.js",
+  "engines": {
+    "node": ">=0.8"
+  },
+  "dependencies": {
+    "buffer-equal": "~0.0.0",
+    "deep-equal": "~0.0.0",
+    "difflet": "~0.2.0",
+    "glob": "~3.2.1",
+    "inherits": "*",
+    "mkdirp": "~0.3 || 0.4 || 0.5",
+    "nopt": "~2",
+    "runforcover": "~0.0.2",
+    "slide": "*",
+    "yamlish": "*"
+  },
+  "keywords": [
+    "assert",
+    "test",
+    "tap"
+  ],
+  "contributors": [
+    "Isaac Z. Schlueter <i at izs.me> (http://blog.izs.me)",
+    "baudehlo <helpme+github at gmail.com>"
+  ],
+  "license": {
+    "type": "MIT",
+    "url": "https://github.com/isaacs/node-tap/raw/master/LICENSE"
+  },
+  "repository": "git://github.com/isaacs/node-tap.git",
+  "scripts": {
+    "test": "bin/tap.js test/*.js"
+  },
+  "devDependencies": {}
+}
diff --git a/test-disabled/bailout.js b/test-disabled/bailout.js
new file mode 100644
index 0000000..498035c
--- /dev/null
+++ b/test-disabled/bailout.js
@@ -0,0 +1,36 @@
+var tap = require("tap")
+  , test = tap.test
+
+test("bailout test", { skip: false }, function (t) {
+
+  // t.once("bailout", function () {
+  //   console.error("bailout event")//, t)
+  //   t.clear()
+  // })
+
+  // t.once("end", function () {
+  //   console.error("end event")
+  // })
+
+  // simulate three tests where the second bails out.
+  t.test("first", function (t) {
+    t.pass("this is ok")
+    t.end()
+  })
+
+  t.test("bailout", function (t) {
+    console.error("bailout test")
+    t.pass("pass")
+    t.bailout("bail out message")
+    t.fail("fail")
+    t.end()
+  })
+
+  t.test("second (should not happen)", function (t) {
+    t.fail("this should not happen")
+    t.end()
+  })
+
+  t.end()
+
+})
diff --git a/test-disabled/foo.js b/test-disabled/foo.js
new file mode 100644
index 0000000..6360156
--- /dev/null
+++ b/test-disabled/foo.js
@@ -0,0 +1 @@
+process.stdin
diff --git a/test-disabled/t.js b/test-disabled/t.js
new file mode 100644
index 0000000..581d24b
--- /dev/null
+++ b/test-disabled/t.js
@@ -0,0 +1,16 @@
+var test = require('tap').test;
+
+function foo() {
+  throw new Error('one');
+}
+
+test('demonstrate bug in t.throws', function (t) {
+  t.throws(
+    function () {
+      foo();
+    },
+    new Error('two')),
+    // "this should throw",
+    // {}); // not 'one'!
+  t.end();
+});
diff --git a/test/buffer_compare.js b/test/buffer_compare.js
new file mode 100644
index 0000000..b1e1505
--- /dev/null
+++ b/test/buffer_compare.js
@@ -0,0 +1,11 @@
+var test = require("../").test
+
+test("same buffers", function (t) {
+  t.same(new Buffer([3,4,243]), new Buffer([3,4,243]))
+  t.end()
+})
+
+test("not same buffers", function (t) {
+  t.notSame(new Buffer([3,5,243]), new Buffer([3,4,243]))
+  t.end()
+})
diff --git a/test/common.js b/test/common.js
new file mode 100644
index 0000000..7cc43c1
--- /dev/null
+++ b/test/common.js
@@ -0,0 +1,32 @@
+exports.taps = ["Tests for the foo module"
+               ,{ok:true, name:"test that the foo is fooish"
+                ,file:"foo.js", line:8, name:"fooish test"
+                ,stack:new Error("fooish").stack}
+               ,{ok:false, name:"a test that the bar is barish"
+                ,file:"bar.js", line:25
+                ,expected:"bar\nbar\nbaz", actual:"rab\nrib\nzib"
+                ,hash:{more:"\nstuff\nhere\n",regexp:/asdf/}}
+               ,"Quux module tests"
+               ,"This is a longer comment"
+               ,{ok:true, name:"an easy one."}
+               ,{ok:false, name:"bloooooo"
+                ,expected:"blerggeyyy"
+                ,actual:"blorggeyy"}
+               ,{ok:false, name:"array test"
+                ,expected:[{ok:true},{ok:true},{stack:new Error().stack}]
+                ,actual:[1234567890,123456789,{error:new Error("yikes")}]}
+               ,{ok:true, name:"nulltest"
+                ,expected:undefined, actual:null}
+               ,{ok:true, name:"weird key test"
+                ,expected:"weird key"
+                ,actual:"weird key"
+                ,"this object":{"has a ":"weird key"
+                               ,"and a looooooooonnnnnnnnnggg":"jacket"}}
+               ,{ok:true, name:"regexp test"
+                ,regexp:/asdf/,function:function (a,b) { return a + b }}
+               ]
+
+if (require.main === module) {
+  console.log("1..1")
+  console.log("ok 1 - just setup, nothing relevant")
+}
diff --git a/test/debug-test.js b/test/debug-test.js
new file mode 100644
index 0000000..a99ad40
--- /dev/null
+++ b/test/debug-test.js
@@ -0,0 +1,16 @@
+var tap = require("../")
+  , fs = require("fs")
+  , cp = require("child_process")
+  , util = require("util")
+
+tap.test("debug test", function (t) {
+  console.error("debug test")
+  t.plan(1)
+  console.error("t.plan="+t._plan)
+
+  cp.exec("../bin/tap.js --debug meta-test.js", function (err, stdo, stde) {
+    console.error(util.inspect(stde)) 
+    t.notEqual(stde.indexOf("debugger listening on port"), -1, "Should output debugger message")
+    t.end();
+  })
+})
diff --git a/test/deep.js b/test/deep.js
new file mode 100644
index 0000000..52b6110
--- /dev/null
+++ b/test/deep.js
@@ -0,0 +1,43 @@
+var tap = require("../")
+  , test = tap.test
+
+test("deepEquals shouldn't care about key order", function (t) {
+  t.deepEqual({ a : 1, b : 2 }, { b : 2, a : 1 })
+  t.end()
+})
+
+test("deepEquals shouldn't care about key order recursively", function (t) {
+  t.deepEqual(
+    { x : { a : 1, b : 2 }, y : { c : 3, d : 4 } },
+    { y : { d : 4, c : 3 }, x : { b : 2, a : 1 } }
+  )
+  t.end()
+})
+
+test("deepEquals shoudn't care about key order but still might", function (t) {
+  t.deepEqual(
+    [ { foo:
+        { z: 100
+        , y: 200
+        , x: 300 } }
+    , "bar"
+    , 11
+    , { baz: 
+        { d : 4
+        , a: 1
+        , b: 2
+        , c: 3 } } ]
+  , [ { foo : 
+        { z: 100
+        , y: 200
+        , x: 300 } }
+    , "bar"
+    , 11
+    , { baz: 
+        { a: 1
+        , b: 2
+        , c: 3
+        , d: 4 } } ]
+  )
+  t.end()
+});
diff --git a/test/end-exception/t.js b/test/end-exception/t.js
new file mode 100644
index 0000000..eaa5b46
--- /dev/null
+++ b/test/end-exception/t.js
@@ -0,0 +1,12 @@
+var test = require("../../").test
+
+test(function (t) {
+  t.plan(1)
+
+  t.on('end', function () {
+    console.log('end()')
+    throw new Error('beep')
+  })
+
+  t.equal(3, 3)
+})
diff --git a/test/executed.sh b/test/executed.sh
new file mode 100755
index 0000000..7300937
--- /dev/null
+++ b/test/executed.sh
@@ -0,0 +1,4 @@
+#!/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
new file mode 100644
index 0000000..87377c1
--- /dev/null
+++ b/test/expose-gc-test.js
@@ -0,0 +1,46 @@
+var tap = require("../")
+  , fs = require("fs")
+  , cp = require("child_process")
+
+fs.writeFileSync("gc-script.js", "console.log(!!global.gc)", "utf8")
+
+tap.test("gc test when the gc isn't there", function (t) {
+  console.error("gc test")
+  t.plan(1)
+  console.error("t.plan="+t._plan)
+
+  cp.exec("../bin/tap.js ./gc-script", function (err, stdo, stde) {
+    console.error("assert gc does not exist")
+    t.ok("false", stdo)
+  })
+})
+
+tap.test("gc test when the gc should be there", function (t) {
+  console.error("gc test")
+  t.plan(2)
+  console.error("t.plan="+t._plan)
+
+  t.test("test for gc using --gc", function (t) {
+    console.error("gc test using --gc")
+    t.plan(1)
+    console.error("t.plan="+t._plan)
+
+    cp.exec("../bin/tap.js --gc ./gc-script", function (err, stdo, stde) {
+      console.error("assert gc exists")
+      t.ok("true", stdo)
+    })
+  })
+
+  t.test("test for gc using --expose-gc", function (t) {
+    console.error("gc test using --expose-gc")
+    t.plan(1)
+    console.error("t.plan="+t._plan)
+
+    cp.exec("../bin/tap.js --expose-gc ./gc-script", function (err, stdo) {
+      console.error("assert gc exists")
+      t.ok("true", stdo)
+    })
+  })
+})
+
+fs.unlinkSync("gc-script.js");
diff --git a/test/independent-timeouts.js b/test/independent-timeouts.js
new file mode 100644
index 0000000..5a35e61
--- /dev/null
+++ b/test/independent-timeouts.js
@@ -0,0 +1,16 @@
+// https://github.com/isaacs/node-tap/issues/23
+
+var tap = require("../")
+  , test = tap.test
+
+test("finishes in time", {timeout: 500}, function(t) {
+  setTimeout(function () {
+    t.end();
+  }, 300);
+})
+test("finishes in time too", {timeout: 500}, function(t) {
+  setTimeout(function () {
+    t.end();
+  }, 300);
+})
+
diff --git a/test/isolated-conf-test.js b/test/isolated-conf-test.js
new file mode 100644
index 0000000..d8bfae6
--- /dev/null
+++ b/test/isolated-conf-test.js
@@ -0,0 +1,16 @@
+// https://github.com/isaacs/node-tap/issues/24
+
+var tap = require("../")
+  , test = tap.test
+
+var config = {foo: "bar"}
+test("one", config, function(t) {
+  t.equal(t.conf.foo, "bar")
+  t.equal(t.conf.name, "one")  // before fix this would be "two"
+  t.end()
+})
+test("two", config, function(t) {
+  t.equal(t.conf.foo, "bar")
+  t.equal(t.conf.name, "two")
+  t.end()
+})
diff --git a/test/meta-test.js b/test/meta-test.js
new file mode 100644
index 0000000..8f56f26
--- /dev/null
+++ b/test/meta-test.js
@@ -0,0 +1,73 @@
+var tap = require("../")
+  , test = tap.test
+
+test("meta test", { skip: false }, function (t) {
+
+  function thr0w() { throw new Error('raburt') }
+  function noop () {}
+
+  // this also tests the ok/notOk functions
+  t.once("end", section2)
+  t.ok(true, "true is ok")
+  t.ok(noop, "function is ok")
+  t.ok({}, "object is ok")
+  t.ok(t, "t is ok")
+  t.ok(100, "number is ok")
+  t.ok("asdf", "string is ok")
+  t.notOk(false, "false is notOk")
+  t.notOk(0, "0 is notOk")
+  t.notOk(null, "null is notOk")
+  t.notOk(undefined, "undefined is notOk")
+  t.notOk(NaN, "NaN is notOk")
+  t.notOk("", "empty string is notOk")
+  t.throws(thr0w, "Thrower throws");
+  t.doesNotThrow(noop, "noop does not throw");
+  t.similar({foo:"bar", bar:"foo"}, {foo:"bar"}, "similar objects are ok");
+  t.dissimilar({}, {mandatory:"value"}, "dissimilar objects are ok");
+  t.dissimilar(null, {}, "null is dissimilar from an object, even with no keys");
+
+  // a few failures.
+  t.ifError(new Error("this is an error"))
+  t.ifError({ message: "this is a custom error" })
+  t.ok(false, "false is not ok")
+  t.notOk(true, "true is not not ok")
+  t.similar(null, {}, "Null is not similar to an object, even with no keys");
+  t.throws(noop, "noop does not throw");
+  t.throws(noop, new Error("Whoops!"), "noop does not throw an Error");
+  t.throws(noop, {name:"MyError", message:"Whoops!"}, "noop does not throw a MyError");
+  t.doesNotThrow(thr0w, "thrower does throw");
+
+  // things that are like other things
+  t.like("asdf", "asdf")
+  t.like("asdf", /^a.*f$/)
+  t.like(100, 100)
+  t.like(100, '100')
+  t.like(100, 100.0)
+  t.unlike("asdf", "fdsa")
+  t.unlike("asdf", /^you jelly, bro?/)
+  t.unlike(100, 100.1)
+  t.like(true, 1)
+  t.like(null, undefined)
+  t.like(true, [1])
+  t.like(false, [])
+  t.like('', [])
+  t.end()
+
+  function section2 () {
+    var results = t.results
+    t.clear()
+    t.ok(true, "sanity check")
+    t.notOk(results.ok, "not ok")
+    t.equal(results.tests, 39, "total test count")
+    t.equal(results.passTotal, 30, "tests passed")
+    t.equal(results.fail, 9, "tests failed")
+    t.type(results.ok, "boolean", "ok is boolean")
+    t.type(results.skip, "number", "skip is number")
+    t.type(results, "Results", "results isa Results")
+    t.type(t, "Test", "test isa Test")
+    t.type(t, "Harness", "test isa Harness")
+    t.end()
+  }
+})
+
+
diff --git a/test/nested-test.js b/test/nested-test.js
new file mode 100644
index 0000000..493f13a
--- /dev/null
+++ b/test/nested-test.js
@@ -0,0 +1,23 @@
+var tap = require("../"),
+    test = tap.test,
+    util = require('util');
+
+test("parent", function (t) {
+  // TODO: Make grandchildren tests count?
+  t.plan(3);
+  t.ok(true, 'p test');
+  t.test("subtest", function (t) {
+    t.ok(true, 'ch test');
+    t.test('nested subtest', function(t) {
+      t.ok(true, 'grch test');
+      t.end();
+    });
+    t.end();
+  });
+  t.test('another subtest', function(t) {
+    t.ok(true, 'ch test 2');
+    t.end();
+  });
+  t.end();
+})
+
diff --git a/test/non-tap-output.js b/test/non-tap-output.js
new file mode 100644
index 0000000..929e9aa
--- /dev/null
+++ b/test/non-tap-output.js
@@ -0,0 +1,12 @@
+console.log("everything is fine\n"
+           +"there are no errors\n"
+           +"this output is not haiku.\n\n"
+           +"is 8 ok?\n"
+           +"ok, 8 can stay.\n"
+           +"ok 100 might be confusing\n"
+           +"  but: nevertheless, here we are\n"
+           +"  this: is indented\n"
+           +"  and: it\n"
+           +"  might: ~\n"
+           +"  be: yaml?\n"
+           +"ok done now, exiting")
diff --git a/test/not-executed.sh b/test/not-executed.sh
new file mode 100644
index 0000000..de46caa
--- /dev/null
+++ b/test/not-executed.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+echo "1..1"
+echo "not ok 1 File without executable bit should not be run"
diff --git a/test/output-childtest-description.js b/test/output-childtest-description.js
new file mode 100644
index 0000000..e025ba8
--- /dev/null
+++ b/test/output-childtest-description.js
@@ -0,0 +1,50 @@
+var tap  =  require("../")
+  , fs   =  require("fs")
+  , path =  require('path')
+  , cp   =  require("child_process")
+  , nestedTests = 
+      [ "var test = require('..').test"
+      , "test('parent test description', function (t) {"
+      , " t.plan(2)"
+      , " t.ok(true, 'test in parent')"
+      , " t.test('child test description', function (t) {"
+      , "    t.plan(1)"
+      , "    t.ok(true, 'test in child')  "
+      , " })"
+      , "})"
+      ].join("\n")
+  , nestedTestsFile = path.join(__dirname, "nested-tests-fixture.js")
+
+fs.writeFileSync(nestedTestsFile, nestedTests, "utf8")
+console.log(nestedTestsFile);
+
+tap.test("nested tests, parent and child pass", function (t) {
+  /*
+   * Ensure the output includes the following lines in the right order:
+   * '# parent test description'
+   *   'ok 1 test in parent'
+   * '# child test description'
+   *   'ok 2 test in child'
+   */
+
+  t.plan(5)
+
+  cp.exec("node " + nestedTestsFile, function (err, stdo, stde) {
+    var lines = stdo.split("\n")
+      , parentDes =  lines.indexOf("# parent test description")
+      , parentRes =  lines.indexOf("ok 1 test in parent")
+      , childDes  =  lines.indexOf("# child test description")
+      , childRes  =  lines.indexOf("ok 2 test in child")
+
+    t.notEqual(parentDes, -1, "outputs parent description")
+    t.notEqual(childDes, -1, "outputs child description")
+
+    t.ok(parentDes < parentRes ,  "outputs parent description before parent result")
+    t.ok(parentRes < childDes  ,  "outputs parent result before child description")
+    t.ok(childDes  < childRes  ,  "outputs child description before child result")
+
+    fs.unlinkSync(nestedTestsFile);
+    t.end()
+  })
+})
+
diff --git a/test/result-trap.js b/test/result-trap.js
new file mode 100644
index 0000000..1ac600f
--- /dev/null
+++ b/test/result-trap.js
@@ -0,0 +1,25 @@
+var tap = require("../")
+
+tap.test("trap result #TODO", function (t0) {
+
+  console.log("not ok 1 result event trapping #TODO")
+  return t0.end()
+
+  t0.plan(3)
+
+  var t1 = new(tap.Harness)(tap.Test).test()
+
+  t1.plan(1)
+
+  t1.on("result", function (res) {
+    if (res.wanted === 4) {
+      t0.equal(res.found, 3)
+      t0.equal(res.wanted, 4)
+
+      t0.end()
+      t1.end()
+    }
+  })
+
+  t1.equal(1 + 2, 4)
+})
diff --git a/test/segv.js b/test/segv.js
new file mode 100644
index 0000000..4a93a04
--- /dev/null
+++ b/test/segv.js
@@ -0,0 +1,69 @@
+var test = require('../').test
+var Runner = require('../lib/tap-runner.js')
+var TC = require('../lib/tap-consumer.js')
+
+var fs = require('fs')
+var spawn = require('child_process').spawn
+var segv =
+    'int main (void) {\n' +
+    '   char *s = "hello world";\n' +
+    '   *s = \'H\';\n' +
+    '}\n'
+var compiled = false
+
+test('setup', function (t) {
+  fs.writeFile('segv.c', segv, 'utf8', function (er) {
+    if (er)
+      throw er
+    var cp = spawn('gcc', ['segv.c', '-o', 'segv'])
+    cp.on('exit', function (code, sig) {
+      if (code !== 0) {
+        t.bailout('failed to compile segv program')
+        return
+      }
+      t.pass('compiled seg faulter')
+      t.end()
+    })
+  })
+})
+
+test('segv', function (t) {
+  var r = new Runner({argv:{remain:['./segv']}})
+  var tc = new TC()
+  var expect =
+      [ 'TAP version 13'
+      , './segv'
+      , { 'id': 1,
+          'ok': false,
+          'name': ' ././segv',
+          'exit': null,
+          'timedOut': true,
+          'signal': process.platform === 'linux' ? 'SIGSEGV' : 'SIGTERM',
+          'command': '"./segv"' }
+      , 'tests 1'
+      , 'fail  1' ]
+  r.pipe(tc)
+  tc.on('data', function (d) {
+    var e = expect.shift()
+
+    // specific signal can be either term or bus
+    if (d.signal && e.signal)
+      e.signal = d.signal === "SIGTERM" || d.signal === "SIGBUS" ?
+        d.signal : e.signal
+
+    t.same(d, e)
+  })
+  tc.on('end', function () {
+    t.equal(expect.length, 0)
+    t.end()
+  })
+})
+
+test('cleanup', function (t) {
+  fs.unlink('segv.c', function () {
+    fs.unlink('segv', function () {
+      t.pass('cleaned up')
+      t.end()
+    })
+  })
+})
diff --git a/test/simple-harness-test-with-plan.js b/test/simple-harness-test-with-plan.js
new file mode 100644
index 0000000..813c4cf
--- /dev/null
+++ b/test/simple-harness-test-with-plan.js
@@ -0,0 +1,16 @@
+var tap = require("../")
+  , test = tap.test
+  , 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
new file mode 100644
index 0000000..64451ae
--- /dev/null
+++ b/test/simple-harness-test.js
@@ -0,0 +1,13 @@
+var tap = require("../")
+  , 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/test-test.js b/test/test-test.js
new file mode 100644
index 0000000..f383941
--- /dev/null
+++ b/test/test-test.js
@@ -0,0 +1,91 @@
+var tap = require("../")
+  , test = tap.test
+  , Test = require("../lib/tap-test")
+  , Harness = require("../lib/tap-harness")
+
+test("testing the test object", function (t) {
+
+  t.isa(t, Test, "test object should be instanceof Test")
+  t.isa(t, Harness, "test object should be instanceof Harness")
+  t.is(t._Test, Test, "test._Test should be the Test class")
+
+  // now test all the methods.
+  ; [ "isNotDeepEqual"
+    , "equals"
+    , "inequivalent"
+    , "threw"
+    , "strictEqual"
+    , "emit"
+    , "fail"
+    , "strictEquals"
+    , "notLike"
+    , "dissimilar"
+    , "true"
+    , "assert"
+    , "is"
+    , "ok"
+    , "isEqual"
+    , "isDeeply"
+    , "deepEqual"
+    , "deepEquals"
+    , "pass"
+    , "length"
+    , "skip"
+    , "isNotEqual"
+    , "looseEquals"
+    , "false"
+    , "notDeeply"
+    , "ifErr"
+    , "hasFields"
+    , "isNotDeeply"
+    , "like"
+    , "similar"
+    , "notOk"
+    , "isDissimilar"
+    , "isEquivalent"
+    , "doesNotEqual"
+    , "isSimilar"
+    , "notDeepEqual"
+    , "type"
+    , "notok"
+    , "isInequivalent"
+    , "isNot"
+    , "same"
+    , "isInequal"
+    , "_endNice"
+    , "ifError"
+    , "iferror"
+    , "clear"
+    , "has"
+    , "not"
+    , "timeout"
+    , "notSimilar"
+    , "isUnlike"
+    , "notEquals"
+    , "unsimilar"
+    , "result"
+    , "doesNotThrow"
+    , "error"
+    , "constructor"
+    , "notEqual"
+    , "throws"
+    , "isLike"
+    , "isNotSimilar"
+    , "isNotEquivalent"
+    , "inequal"
+    , "notEquivalent"
+    , "isNotLike"
+    , "equivalent"
+    , "looseEqual"
+    , "equal"
+    , "unlike"
+    , "doesNotHave"
+    , "comment"
+    , "isa"
+    ].forEach(function (method) {
+      t.ok(t[method], "should have "+method+" method")
+      t.isa(t[method], "function", method+" method should be a function")
+    })
+    t.end()
+})
+
diff --git a/test/timeout.js b/test/timeout.js
new file mode 100644
index 0000000..4ee409c
--- /dev/null
+++ b/test/timeout.js
@@ -0,0 +1,33 @@
+var tap = require("../")
+
+tap.test("timeout test with plan only", function (t) {
+  console.error("timeout test")
+  t.plan(2)
+  console.error("t.plan="+t._plan)
+  setTimeout(function () {
+    console.error("a assert")
+    t.ok(true, "a")
+  }, 1000)
+  setTimeout(function () {
+    console.error("b assert")
+    t.ok(true, "b")
+  }, 1000)
+})
+
+tap.test("timeout test with plan and end", function (t) {
+  console.error("timeout test")
+  t.plan(2)
+
+  var tc = 2
+  console.error("t.plan="+t._plan)
+  setTimeout(function () {
+    console.error("a assert")
+    t.ok(true, "a")
+    if (-- tc === 0) t.end()
+  }, 1000)
+  setTimeout(function () {
+    console.error("b assert")
+    t.ok(true, "b")
+    if (-- tc === 0) t.end()
+  }, 1000)
+})
diff --git a/test/trivial-success.js b/test/trivial-success.js
new file mode 100644
index 0000000..e69de29
diff --git a/test/undefined_indented.js b/test/undefined_indented.js
new file mode 100644
index 0000000..98abe02
--- /dev/null
+++ b/test/undefined_indented.js
@@ -0,0 +1,27 @@
+var tap = require("../")
+
+tap.test("consume yaml", function (t) {
+  t.plan(1)
+
+  var s =
+    [ "not ok 1 beep boop"
+    , "  ---"
+    , "    stack:"
+    , "      - rawr"
+    , "      - dinosaurs"
+    , "  ..."
+    ].join("\n")
+  , c = tap.createConsumer()
+
+  c.on("data", function (res) {
+    t.same(res, {
+      id: 1
+      , ok: false
+      , name: " beep boop" // <-- should perhaps .trim() this?
+      , stack: [ "rawr", "dinosaurs" ]
+    })
+    t.end()
+  })
+  c.write(s)
+  c.end()
+})
diff --git a/test/valid-command.js b/test/valid-command.js
new file mode 100644
index 0000000..a54c3d6
--- /dev/null
+++ b/test/valid-command.js
@@ -0,0 +1,37 @@
+var test = require('../').test
+var Runner = require('../lib/tap-runner.js')
+var TC = require('../lib/tap-consumer.js')
+
+test('valid command', function (t) {
+  var r = new Runner({argv:{remain:['./end-exception/t.js']}})
+  var tc = new TC()
+  var node = process.execPath
+  var expectObj ={
+    'id': 1,
+    'ok': false,
+    'name': ' ./end-exception/t.js',
+    'timedOut': true,
+    'command': '"' + node + ' t.js"',
+    exit: null
+  }
+  if (process.platform === 'linux') {
+    expectObj.exit = 143
+  } else {
+    expectObj.signal = 'SIGTERM'
+  }
+  var expect =
+      [ 'TAP version 13'
+      , 't.js'
+      , expectObj
+      , 'tests 1'
+      , 'fail  1' ]
+  r.pipe(tc)
+  tc.on('data', function (d) {
+    var e = expect.shift()
+    t.same(d, e)
+  })
+  tc.on('end', function () {
+    t.equal(expect.length, 0)
+    t.end()
+  })
+})

-- 
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