Commit 9a4ab017 authored by isaacs's avatar isaacs

Getting closer

- Implemented a root test handler (uncaught exceptions still not
  handled)
- Promises can be returned by the cb
- subtest methods return a promise
- Fix an issue where an empty buffered test would not be handled
parent ea7ee511
#!/usr/bin/env node
var tap = require('../lib/root.js')
var tap = require('../lib/tap.js')
var args = process.argv.slice(2)
if (args.length === 1) {
......
......@@ -719,7 +719,7 @@ function runTests (options) {
// At this point, we know we need to use the tap root,
// because there are 1 or more files to spawn.
var tap = require('../lib/root.js')
var tap = require('../lib/tap.js')
// if not -Rtap, then output what the user wants.
if (options.reporter !== 'tap') {
......
var t = require('../lib/root.js')
var t = require('../lib/tap.js')
t.plan(2)
t.test('gun show', function (t) {
......
......@@ -21,7 +21,7 @@ function Base (options) {
this.bail = ownOrEnv(options, 'bail', 'TAP_BAIL', true)
this.name = ownOr(options, 'name', '')
this.indent = ownOr(options, 'indent', '')
this.buffered = ownOrEnv(options, 'buffered', 'TAP_BUFFER', true)
this.buffered = !!options.buffered
this.finished = false
this.strict = ownOrEnv(options, 'strict', 'TAP_STRICT', true)
this.omitVersion = !!options.omitVersion
......@@ -33,14 +33,14 @@ function Base (options) {
this.finished = false
this.output = ''
this.results = null
this.bailedOut = false
if (this.skip || this.todo)
this.main = Base.prototype.main
if (typeof options.debug === 'boolean')
this.debug = options.debug ? debug : nodebug
Readable.apply(this, options)
this.ended = false
this.on('end', function () {
this.ended = true
})
}
Base.prototype.main = function (cb) {
......@@ -48,7 +48,7 @@ Base.prototype.main = function (cb) {
}
Base.prototype.online = function (line) {
console.error('LINE %s %j', this.name, line)
this.debug('LINE %j', line)
return this.push(this.indent + line)
}
......@@ -69,16 +69,17 @@ Base.prototype.push = function (c, e) {
this._readableState.sync = false
}
// console.error(this._readableState)
// this.debug(this._readableState)
return Readable.prototype.push.call(this, c, e)
}
Base.prototype.onbail = function (reason) {
this.bailedOut = reason || true
this.emit('bailout', reason)
}
Base.prototype.oncomplete = function (results) {
console.error('ONCOMPLETE %j %j', this.name, results)
this.debug('ONCOMPLETE %j %j', this.name, results)
this.results = results
this.emit('end')
}
......@@ -98,7 +99,7 @@ Base.prototype.setupParser = function (options) {
Base.prototype._read = function (n) {
// this.emit('readable')
// console.error('_read %j', this.name, arguments)
// this.debug('_read %j', this.name, arguments)
}
Base.prototype.inspect = function () {
......@@ -112,6 +113,18 @@ Base.prototype.inspect = function () {
output: this.output,
skip: this.skip,
todo: this.todo,
ended: this.ended
results: this.results
})
}
Base.prototype.debug = (/\btap\b/i.test(process.env.NODE_DEBUG || ''))
? debug : nodebug
function nodebug () {}
function debug () {
var prefix = 'TAP ' + process.pid + ' ' + this.name + ': '
var msg = util.format.apply(util, arguments).trim()
msg = prefix + msg.split('\n').join('\n' + prefix)
console.error(msg)
}
module.exports = Deferred
var Promise = require('bluebird')
function Deferred () {
this.resolve = null
this.reject = null
this.promise = new Promise(function (resolve, reject) {
this.reject = reject
this.resolve = resolve
}.bind(this))
}
......@@ -27,7 +27,7 @@ module.exports = function (name, extra, cb) {
name = name || '(unnamed test)'
extra.name = name
extra.cb = cb || function () {
console.error('wtf?')
throw new Error('callback called for TODO test')
}
return extra
}
module.exports = testPoint
module.exports = TestPoint
var path = require('path')
var binpath = path.resolve(__dirname, '../bin')
......@@ -6,41 +6,42 @@ var util = require('util')
var yaml = require('js-yaml')
var cleanYamlObject = require('clean-yaml-object')
function testPoint (ok, n, message, extra) {
function TestPoint (ok, message, extra) {
if (typeof ok !== 'boolean')
throw new TypeError('ok must be boolean')
if (typeof n !== 'number' || isNaN(n) || !isFinite(n) || n < 1)
throw new TypeError('n must be number >= 1')
this.ok = ok ? 'ok ' : 'not ok '
this.message = tpMessage(message, extra)
}
function tpMessage (message, extra) {
message = message + ''
if (message)
message = ' - ' + message
message = message.replace(/[\n\r]/g, ' ').replace(/\t/g, ' ')
extra = extra || {}
var ret = util.format('%s %d%s', ok ? 'ok' : 'not ok', n, message)
if (extra.skip) {
ret += ' # SKIP'
message += ' # SKIP'
if (typeof extra.skip === 'string')
ret += ' ' + extra.skip
message += ' ' + extra.skip
} else if (extra.todo) {
ret += ' # TODO'
message += ' # TODO'
if (typeof extra.todo === 'string')
ret += ' ' + extra.todo
message += ' ' + extra.todo
}
var diagYaml = extra.diagnostic ? diags(extra) : ''
ret += diagYaml
message += diagYaml
if (extra.tapChildBuffer) {
if (extra.tapChildBuffer || extra.tapChildBuffer === '') {
if (!diagYaml)
ret += ' '
ret += '{\n' + extra.tapChildBuffer + '}\n'
message += ' '
message += '{\n' + extra.tapChildBuffer.trimRight() + '\n}\n'
}
ret += '\n'
return ret
message += '\n'
return message
}
function diags (extra) {
......@@ -78,7 +79,8 @@ function yamlFilter (propertyName, isRoot, source, target) {
return true
if (propertyName === 'stack') {
target.stack = source.stack
if (source.stack)
target.stack = source.stack
return false
}
......
......@@ -4,6 +4,7 @@ var assert = require('assert')
var util = require('util')
util.inherits(Spawn, Base)
var ownOr = require('./own-or.js')
var path = require('path')
module.exports = Spawn
......@@ -16,44 +17,47 @@ function Spawn (options) {
return new Spawn(options)
Base.call(this, options)
this.command = options.command
if (!this.command) {
throw new TypeError('no command provided')
}
this.args = options.args
var spawnOpt = this.spawnOpt = options.spawnOpt || {}
// stdout must be a pipe
if (spawnOpt.stdio) {
if (typeof spawnOpt.stdio === 'string') {
spawnOpt.stdio = [ spawnOpt.stdio, 'pipe', spawnOpt.stdio ]
if (options.stdio) {
if (typeof options.stdio === 'string') {
this.stdio = [ options.stdio, 'pipe', options.stdio ]
} else {
spawnOpt.stdio[1] = 'pipe'
this.stdio[1] = 'pipe'
}
}
} else
this.stdio = [ 0, 'pipe', 2 ]
if (this.bail) {
if (!spawnOpt.env) {
spawnOpt.env = Object.keys(process.env).reduce(function (env, k) {
env[k] = process.env[k]
return env
}, {})
}
spawnOpt.env.TAP_BAIL = '1'
}
if (!options.env)
this.env = Object.keys(process.env).reduce(function (env, k) {
env[k] = process.env[k]
return env
}, {})
else
this.env = options.env
if (this.bail)
this.env.TAP_BAIL = '1'
this.cwd = ownOr(spawnOpt, 'cwd', process.cwd())
spawnOpt.cwd = this.cwd
this.cwd = ownOr(options, 'cwd', process.cwd())
options.cwd = this.cwd
if (!this.name) {
if (this.name === process.execPath) {
this.name = this.args.map(function (a) {
if (a.indexOf(this.cwd) === 0) {
return './' + a.substr(this.cwd.length + 1).replace(/\\/g, '/')
} else {
return a
}
}).join(' ')
if (this.command === process.execPath) {
this.name = path.basename(process.execPath) + ' ' +
this.args.map(function (a) {
if (a.indexOf(this.cwd) === 0) {
return './' +
a.substr(this.cwd.length + 1).replace(/\\/g, '/')
} else {
return a
}
}, this).join(' ')
} else {
this.name = this.command + ' ' + this.args.join(' ')
}
......@@ -63,26 +67,38 @@ function Spawn (options) {
}
Spawn.prototype.main = function (cb) {
var proc = this.proc = spawn(this.command, this.args, this.spawnOpt)
var options = Object.keys(this.options).reduce(function (o, k) {
o[k] = this.options[k]
return o
}.bind(this), {
cwd: this.cwd,
env: this.env,
stdio: this.stdio
})
var proc = this.proc = spawn(this.command, this.args, options)
proc.stdout.pipe(this.parser)
proc.on('close', this.onprocclose.bind(this, cb))
proc.on('error', function (er) {
// unhook entirely
proc.stdout.removeAllListeners('data')
proc.stdout.removeAllListeners('end')
proc.removeAllListeners('close')
proc.kill('SIGKILL')
cb(er)
})
proc.on('error', this.onprocerror.bind(this, cb))
}
Spawn.prototype.onprocerror = function (cb, er) {
// unhook entirely
this.proc.stdout.removeAllListeners('data')
this.proc.stdout.removeAllListeners('end')
this.proc.removeAllListeners('close')
this.proc.kill('SIGKILL')
cb(er)
}
Spawn.prototype.onprocclose = function (cb, code, signal) {
if (!code && !signal)
return cb()
this.debug('SPAWN close %j %s', code, signal)
this.options.exitCode = code
this.options.signal = signal
var er = new Error('command failed: ' + this.name)
er.code = code
er.signal = signal
return cb(er)
if (signal)
this.options.signal = signal
this.results = this.results || {}
if (code || signal) {
this.results.ok = false
this.parser.ok = false
}
return cb()
}
var Test = require('./test.js')
var util = require('util')
util.inherits(TAP, Test)
function TAP (options) {
Test.call(this, options)
// TODO unref the timer, once there is one.
}
TAP.prototype.oncomplete = function (results) {
// maybe some diagnostic comments? 10 tests, 2 failed, etc.
// prove-style?
process.removeListener('uncaughtException', onUncaught)
return Test.prototype.oncomplete.call(this, results)
}
var tap = new TAP({ name: 'TAP' })
module.exports = tap
process.on('exit', function (code) {
if (!tap.results)
tap.end()
// console.error(tap)
if (tap.results && !tap.results.ok && code === 0)
process.exit(1)
})
var didPipe = false
TAP.prototype.pipe = function () {
didPipe = true
delete TAP.prototype.pipe
delete TAP.prototype.push
process.on('uncaughtException', onUncaught)
return Test.prototype.pipe.apply(this, arguments)
}
TAP.prototype.push = function push () {
this.pipe(process.stdout)
process.stdout.emit = function (emit) { return function (ev, er) {
if (ev === 'error' && er.code === 'EPIPE')
return this.emit = emit
return emit.apply(this, arguments)
}}(process.stdout.emit)
return this.push.apply(tap, arguments)
}
tap.Test = Test
tap.synonyms = require('./synonyms.js')
function onUncaught (er) {
var child = tap
while (child._currentChild && child._currentChild instanceof Test) {
child = child._currentChild
}
child.threw(er)
}
// SIGTERM means being forcibly killed, almost always by timeout
var onExit = require('signal-exit')
onExit(function (code, signal) {
if (signal !== 'SIGTERM' || !didPipe)
return
var handles = process._getActiveHandles().filter(function (h) {
return h !== process.stdout &&
h !== process.stdin &&
h !== process.stderr
})
var requests = process._getActiveRequests()
// Ignore this because it's really hard to test cover in a way
// that isn't inconsistent and unpredictable.
/* istanbul ignore next */
var extra = {
at: null,
signal: signal
}
if (requests.length) {
extra.requests = requests.map(function (r) {
var ret = { type: r.constructor.name }
if (r.context) {
ret.context = r.context
}
return ret
})
}
if (handles.length) {
extra.handles = handles.map(function (h) {
var ret = { type: h.constructor.name }
if (h.msecs) {
ret.msecs = h.msecs
}
if (h._events) {
ret.events = Object.keys(h._events)
}
if (h._sockname) {
ret.sockname = h._sockname
}
if (h._connectionKey) {
ret.connectionKey = h._connectionKey
}
return ret
})
}
if (!tap.results && tap._onTimeout)
tap._onTimeout(extra)
else {
console.error('possible timeout: SIGTERM received after tap end')
if (extra.handles || extra.requests) {
delete extra.signal
if (!extra.at) {
delete extra.at
}
var yaml = require('js-yaml')
console.error(' ---\n ' +
yaml.safeDump(extra).split('\n').join('\n ').trim() +
'\n ...\n')
}
process.exit(1)
}
})
This diff is collapsed.
......@@ -7,7 +7,7 @@
"bin": {
"tap": "bin/run.js"
},
"main": "lib/root.js",
"main": "lib/tap.js",
"engines": {
"node": ">=0.8"
},
......@@ -29,7 +29,7 @@
"signal-exit": "^3.0.0",
"stack-utils": "^0.4.0",
"tap-mocha-reporter": "^3.0.1",
"tap-parser": "^4.2.2",
"tap-parser": "5",
"tmatch": "^3.0.0"
},
"keywords": [
......@@ -50,19 +50,11 @@
"mkdirp": "^0.5.1",
"rimraf": "^2.5.4",
"standard": "^7.1.0",
"tap-parser": "^4.2.3",
"which": "^1.1.1"
},
"files": [
"bin/mochatap.js",
"bin/run.js",
"bin/usage.txt",
"lib/assert.js",
"lib/mocha.js",
"lib/root.js",
"lib/stack.js",
"lib/synonyms.js",
"lib/test.js"
"bin/*",
"lib/*"
],
"config": {
"nyc": {
......
var test = require('../lib/root.js').test
var test = require('../').test
var Test = require('../lib/test.js')
var util = require('util')
var truthies = [ true, 1, 'ok', Infinity, function () {}, {}, [], /./ ]
......
var glob = require('glob')
var t = require('../lib/root.js')
var t = require('../')
var spawn = require('child_process').spawn
var node = process.execPath
var fs = require('fs')
......
var test = require('../../lib/root.js').test
var test = require('../..').test
test('not much', function (t) {
t.ok(true, 'always passes', {skip: 'skip it good'})
......
var t = require('../../lib/root.js')
var t = require('../..')
console.log('>>>> before any tests')
......
if (typeof describe !== 'function') {
var t = require('../../lib/root.js')
var t = require('../..')
t.mochaGlobals()
}
......
if (typeof describe !== 'function') {
var t = require('../../lib/root.js')
var t = require('../..')
t.mochaGlobals()
}
......
var t = require('../../lib/root.js')
var t = require('../..')
t.test('children plan too big', function (t) {
t.plan(9)
......
var t = require('../../lib/test.js')()
// var t = require('../..')
var T = require('../../lib/test.js')
var t = new T()
t.pipe(process.stdout)
process.on('exit', function () {
t.end()
})
if (process.argv[2] !== 'child') {
t.spawn(process.execPath, [__filename, 'child'])
......@@ -46,3 +45,5 @@ t.test('async kid', function (t) {
})
t.pass('pass after async kid')
t.end()
var t = require('../../lib/root.js')
var t = require('../..')
t.test('parent of timeout test', function (t) {
t.test('timeout test', { timeout: 50 }, function (t) {
......
var t = require('../../lib/root.js')
var t = require('../..')
t.test('a set of tests to be done later', function (t) {
t.test('should have a thingie')
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment