[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