forked from enviPath/enviPy
217 lines
5.2 KiB
JavaScript
217 lines
5.2 KiB
JavaScript
var Test = require('./test.js')
|
|
var Stdin = require('./stdin.js')
|
|
var Spawn = require('./spawn.js')
|
|
var util = require('util')
|
|
var objToYaml = require('./obj-to-yaml.js')
|
|
var yaml = require('js-yaml')
|
|
|
|
util.inherits(TAP, Test)
|
|
function TAP (options) {
|
|
Test.call(this, options)
|
|
this.runOnly = process.env.TAP_ONLY === '1'
|
|
this.start = Date.now()
|
|
}
|
|
|
|
var didPipe = false
|
|
TAP.prototype.pipe = function () {
|
|
didPipe = true
|
|
this.setTimeout(this.options.timeout)
|
|
this.pipe = Test.prototype.pipe
|
|
this.push = Test.prototype.push
|
|
var ret = this.pipe.apply(this, arguments)
|
|
this.process()
|
|
return ret
|
|
}
|
|
|
|
function monkeypatchEpipe () {
|
|
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)
|
|
}
|
|
|
|
function monkeypatchExit () {
|
|
// ensure that we always get run, even if a user does
|
|
// process.on('exit', process.exit)
|
|
process.reallyExit = function (original) {
|
|
return function reallyExit (code) {
|
|
code = onExitEvent(code)
|
|
return original.call(this, code)
|
|
}
|
|
}(process.reallyExit)
|
|
|
|
process.exit = function (original) {
|
|
return function exit (code) {
|
|
code = onExitEvent(code)
|
|
return original.call(this, code)
|
|
}
|
|
}(process.exit)
|
|
|
|
process.on('exit', onExitEvent)
|
|
}
|
|
|
|
var didOnExitEvent = false
|
|
function onExitEvent (code) {
|
|
if (didOnExitEvent)
|
|
return process.exitCode || code
|
|
|
|
didOnExitEvent = true
|
|
|
|
if (!tap.results)
|
|
tap.endAll()
|
|
|
|
if (tap.results && !tap.results.ok && code === 0) {
|
|
process.exitCode = 1
|
|
if (process.version.match(/^v0\.(10|[0-9])\./))
|
|
process.exit(code)
|
|
}
|
|
|
|
return process.exitCode || code || 0
|
|
}
|
|
|
|
TAP.prototype.push = function push () {
|
|
// this resets push and pipe to standard values
|
|
this.pipe(process.stdout)
|
|
this.patchProcess()
|
|
return this.push.apply(this, arguments)
|
|
}
|
|
|
|
TAP.prototype.patchProcess = function () {
|
|
monkeypatchEpipe()
|
|
monkeypatchExit()
|
|
process.on('uncaughtException', this.threw)
|
|
process.on('unhandledRejection', function (er) {
|
|
this.threw(er)
|
|
}.bind(this))
|
|
}
|
|
|
|
TAP.prototype.onbail = function () {
|
|
Test.prototype.onbail.apply(this, arguments)
|
|
this.endAll()
|
|
process.exit(1)
|
|
}
|
|
|
|
TAP.prototype.onbeforeend = function () {
|
|
if (didPipe && this.time && !this.bailedOut)
|
|
this.emit('data', '# time=' + this.time + 'ms\n')
|
|
}
|
|
|
|
TAP.prototype.ondone = function () {
|
|
try {
|
|
this.emit('teardown')
|
|
} catch (er) {
|
|
this.threw(er)
|
|
}
|
|
}
|
|
|
|
// Root test runner doesn't have the 'teardown' event, because it
|
|
// isn't hooked into any parent Test as a harness.
|
|
TAP.prototype.teardown = TAP.prototype.tearDown = function (fn) {
|
|
this.autoend()
|
|
return Test.prototype.teardown.apply(this, arguments)
|
|
}
|
|
|
|
var opt = { name: 'TAP' }
|
|
if (process.env.TAP_DEBUG === '1' ||
|
|
/\btap\b/.test(process.env.NODE_DEBUG || ''))
|
|
opt.debug = true
|
|
|
|
if (process.env.TAP_GREP) {
|
|
opt.grep = process.env.TAP_GREP.split('\n').map(function (g) {
|
|
var p = g.match(/^\/(.*)\/([a-z]*)$/)
|
|
g = p ? p[1] : g
|
|
var flags = p ? p[2] : ''
|
|
return new RegExp(g, flags)
|
|
})
|
|
}
|
|
|
|
if (process.env.TAP_GREP_INVERT === '1') {
|
|
opt.grepInvert = true
|
|
}
|
|
|
|
if (process.env.TAP_ONLY === '1') {
|
|
opt.only = true
|
|
}
|
|
|
|
var tap = new TAP(opt)
|
|
module.exports = tap
|
|
tap.mocha = require('./mocha.js')
|
|
tap.mochaGlobals = tap.mocha.global
|
|
|
|
tap.Test = Test
|
|
tap.Spawn = Spawn
|
|
tap.Stdin = Stdin
|
|
tap.synonyms = require('./synonyms.js')
|
|
|
|
// SIGTERM means being forcibly killed, almost always by timeout
|
|
var onExit = require('signal-exit')
|
|
var didTimeoutKill = false
|
|
onExit(function (code, signal) {
|
|
if (signal !== 'SIGTERM' || !didPipe || didTimeoutKill)
|
|
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
|
|
})
|
|
}
|
|
|
|
// this is impossible to cover, because it happens after nyc has
|
|
// already done its stuff.
|
|
/* istanbul ignore else */
|
|
if (!tap.results && tap.timeout)
|
|
tap.timeout(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(objToYaml(extra))
|
|
}
|
|
didTimeoutKill = true
|
|
process.kill(process.pid, 'SIGTERM')
|
|
}
|
|
})
|