[Pkg-javascript-commits] [node-signal-exit] 01/07: Import Upstream version 3.0.1

Sruthi Chandran srud-guest at moszumanska.debian.org
Tue Oct 18 16:20:49 UTC 2016


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

srud-guest pushed a commit to branch master
in repository node-signal-exit.

commit b4a65be8b26bb5fee973cd3b0bed3520f28d09a9
Author: Sruthi <srud at disroot.org>
Date:   Tue Oct 18 20:03:56 2016 +0530

    Import Upstream version 3.0.1
---
 .gitignore                            |   5 +
 .travis.yml                           |   6 +
 CHANGELOG.md                          |  27 ++
 LICENSE.txt                           |  14 +
 README.md                             |  40 ++
 appveyor.yml                          |  19 +
 index.js                              | 148 +++++++
 package.json                          |  38 ++
 signals.js                            |  53 +++
 test/all-integration-test.js          |  97 +++++
 test/fixtures/awaiter.js              |  35 ++
 test/fixtures/change-code-expect.json | 800 ++++++++++++++++++++++++++++++++++
 test/fixtures/change-code.js          |  99 +++++
 test/fixtures/end-of-execution.js     |   5 +
 test/fixtures/exit-last.js            |  14 +
 test/fixtures/exit.js                 |   7 +
 test/fixtures/exiter.js               |  44 ++
 test/fixtures/load-unload.js          |   7 +
 test/fixtures/multiple-load.js        |  52 +++
 test/fixtures/parent.js               |  51 +++
 test/fixtures/sigint.js               |  11 +
 test/fixtures/sigkill.js              |  19 +
 test/fixtures/signal-default.js       |  99 +++++
 test/fixtures/signal-last.js          |  17 +
 test/fixtures/signal-listener.js      |  23 +
 test/fixtures/sigpipe.js              |   8 +
 test/fixtures/sigterm.js              |   9 +
 test/fixtures/unwrap.js               |  37 ++
 test/multi-exit.js                    |  61 +++
 test/signal-exit-test.js              | 116 +++++
 30 files changed, 1961 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..fad8f86
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+node_modules
+.DS_Store
+nyc_output
+coverage
+.nyc_output
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..089861a
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,6 @@
+sudo: false
+language: node_js
+node_js:
+  - '0.12'
+  - '4'
+  - '5'
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..e2f70d2
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,27 @@
+# Change Log
+
+All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
+
+<a name="3.0.1"></a>
+## [3.0.1](https://github.com/tapjs/signal-exit/compare/v3.0.0...v3.0.1) (2016-09-08)
+
+
+### Bug Fixes
+
+* do not listen on SIGBUS, SIGFPE, SIGSEGV and SIGILL ([#40](https://github.com/tapjs/signal-exit/issues/40)) ([5b105fb](https://github.com/tapjs/signal-exit/commit/5b105fb))
+
+
+
+<a name="3.0.0"></a>
+# [3.0.0](https://github.com/tapjs/signal-exit/compare/v2.1.2...v3.0.0) (2016-06-13)
+
+
+### Bug Fixes
+
+* get our test suite running on Windows ([#23](https://github.com/tapjs/signal-exit/issues/23)) ([6f3eda8](https://github.com/tapjs/signal-exit/commit/6f3eda8))
+* hooking SIGPROF was interfering with profilers see [#21](https://github.com/tapjs/signal-exit/issues/21) ([#24](https://github.com/tapjs/signal-exit/issues/24)) ([1248a4c](https://github.com/tapjs/signal-exit/commit/1248a4c))
+
+
+### BREAKING CHANGES
+
+* signal-exit no longer wires into SIGPROF
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..c7e2747
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,14 @@
+Copyright (c) 2015, Contributors
+
+Permission to use, copy, modify, and/or distribute this software
+for any purpose with or without fee is hereby granted, provided
+that the above copyright notice and this permission notice
+appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE
+LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
+OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8ebccab
--- /dev/null
+++ b/README.md
@@ -0,0 +1,40 @@
+# signal-exit
+
+[![Build Status](https://travis-ci.org/tapjs/signal-exit.png)](https://travis-ci.org/tapjs/signal-exit)
+[![Coverage](https://coveralls.io/repos/tapjs/signal-exit/badge.svg?branch=master)](https://coveralls.io/r/tapjs/signal-exit?branch=master)
+[![NPM version](https://img.shields.io/npm/v/signal-exit.svg)](https://www.npmjs.com/package/signal-exit)
+[![Windows Tests](https://img.shields.io/appveyor/ci/bcoe/signal-exit/master.svg?label=Windows%20Tests)](https://ci.appveyor.com/project/bcoe/signal-exit)
+[![Standard Version](https://img.shields.io/badge/release-standard%20version-brightgreen.svg)](https://github.com/conventional-changelog/standard-version)
+
+When you want to fire an event no matter how a process exits:
+
+* reaching the end of execution.
+* explicitly having `process.exit(code)` called.
+* having `process.kill(pid, sig)` called.
+* receiving a fatal signal from outside the process
+
+Use `signal-exit`.
+
+```js
+var onExit = require('signal-exit')
+
+onExit(function (code, signal) {
+  console.log('process exited!')
+})
+```
+
+## API
+
+`var remove = onExit(function (code, signal) {}, options)`
+
+The return value of the function is a function that will remove the
+handler.
+
+Note that the function *only* fires for signals if the signal would
+cause the proces to exit.  That is, there are no other listeners, and
+it is a fatal signal.
+
+## Options
+
+* `alwaysLast`: Run this handler after any other signal or exit
+  handlers.  This causes `process.emit` to be monkeypatched.
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 0000000..b29e6c6
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,19 @@
+environment:
+  matrix:
+    - nodejs_version: '5'
+    - nodejs_version: '4'
+    - nodejs_version: '0.12'
+install:
+  - ps: Install-Product node $env:nodejs_version
+  - set CI=true
+  - npm -g install npm at latest
+  - set PATH=%APPDATA%\npm;%PATH%
+  - npm install
+matrix:
+  fast_finish: true
+build: off
+version: '{build}'
+shallow_clone: true
+clone_depth: 1
+test_script:
+  - npm test
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..7dd8d91
--- /dev/null
+++ b/index.js
@@ -0,0 +1,148 @@
+// Note: since nyc uses this module to output coverage, any lines
+// that are in the direct sync flow of nyc's outputCoverage are
+// ignored, since we can never get coverage for them.
+var assert = require('assert')
+var signals = require('./signals.js')
+
+var EE = require('events')
+/* istanbul ignore if */
+if (typeof EE !== 'function') {
+  EE = EE.EventEmitter
+}
+
+var emitter
+if (process.__signal_exit_emitter__) {
+  emitter = process.__signal_exit_emitter__
+} else {
+  emitter = process.__signal_exit_emitter__ = new EE()
+  emitter.count = 0
+  emitter.emitted = {}
+}
+
+module.exports = function (cb, opts) {
+  assert.equal(typeof cb, 'function', 'a callback must be provided for exit handler')
+
+  if (loaded === false) {
+    load()
+  }
+
+  var ev = 'exit'
+  if (opts && opts.alwaysLast) {
+    ev = 'afterexit'
+  }
+
+  var remove = function () {
+    emitter.removeListener(ev, cb)
+    if (emitter.listeners('exit').length === 0 &&
+        emitter.listeners('afterexit').length === 0) {
+      unload()
+    }
+  }
+  emitter.on(ev, cb)
+
+  return remove
+}
+
+module.exports.unload = unload
+function unload () {
+  if (!loaded) {
+    return
+  }
+  loaded = false
+
+  signals.forEach(function (sig) {
+    try {
+      process.removeListener(sig, sigListeners[sig])
+    } catch (er) {}
+  })
+  process.emit = originalProcessEmit
+  process.reallyExit = originalProcessReallyExit
+  emitter.count -= 1
+}
+
+function emit (event, code, signal) {
+  if (emitter.emitted[event]) {
+    return
+  }
+  emitter.emitted[event] = true
+  emitter.emit(event, code, signal)
+}
+
+// { <signal>: <listener fn>, ... }
+var sigListeners = {}
+signals.forEach(function (sig) {
+  sigListeners[sig] = function listener () {
+    // If there are no other listeners, an exit is coming!
+    // Simplest way: remove us and then re-send the signal.
+    // We know that this will kill the process, so we can
+    // safely emit now.
+    var listeners = process.listeners(sig)
+    if (listeners.length === emitter.count) {
+      unload()
+      emit('exit', null, sig)
+      /* istanbul ignore next */
+      emit('afterexit', null, sig)
+      /* istanbul ignore next */
+      process.kill(process.pid, sig)
+    }
+  }
+})
+
+module.exports.signals = function () {
+  return signals
+}
+
+module.exports.load = load
+
+var loaded = false
+
+function load () {
+  if (loaded) {
+    return
+  }
+  loaded = true
+
+  // This is the number of onSignalExit's that are in play.
+  // It's important so that we can count the correct number of
+  // listeners on signals, and don't wait for the other one to
+  // handle it instead of us.
+  emitter.count += 1
+
+  signals = signals.filter(function (sig) {
+    try {
+      process.on(sig, sigListeners[sig])
+      return true
+    } catch (er) {
+      return false
+    }
+  })
+
+  process.emit = processEmit
+  process.reallyExit = processReallyExit
+}
+
+var originalProcessReallyExit = process.reallyExit
+function processReallyExit (code) {
+  process.exitCode = code || 0
+  emit('exit', process.exitCode, null)
+  /* istanbul ignore next */
+  emit('afterexit', process.exitCode, null)
+  /* istanbul ignore next */
+  originalProcessReallyExit.call(process, process.exitCode)
+}
+
+var originalProcessEmit = process.emit
+function processEmit (ev, arg) {
+  if (ev === 'exit') {
+    if (arg !== undefined) {
+      process.exitCode = arg
+    }
+    var ret = originalProcessEmit.apply(this, arguments)
+    emit('exit', process.exitCode, null)
+    /* istanbul ignore next */
+    emit('afterexit', process.exitCode, null)
+    return ret
+  } else {
+    return originalProcessEmit.apply(this, arguments)
+  }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..5cd4455
--- /dev/null
+++ b/package.json
@@ -0,0 +1,38 @@
+{
+  "name": "signal-exit",
+  "version": "3.0.1",
+  "description": "when you want to fire an event no matter how a process exits.",
+  "main": "index.js",
+  "scripts": {
+    "pretest": "standard",
+    "test": "tap --timeout=240 ./test/*.js --cov",
+    "coverage": "nyc report --reporter=text-lcov | coveralls",
+    "release": "standard-version"
+  },
+  "files": [
+    "index.js",
+    "signals.js"
+  ],
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/tapjs/signal-exit.git"
+  },
+  "keywords": [
+    "signal",
+    "exit"
+  ],
+  "author": "Ben Coe <ben at npmjs.com>",
+  "license": "ISC",
+  "bugs": {
+    "url": "https://github.com/tapjs/signal-exit/issues"
+  },
+  "homepage": "https://github.com/tapjs/signal-exit",
+  "devDependencies": {
+    "chai": "^3.5.0",
+    "coveralls": "^2.11.10",
+    "nyc": "^7.0.0",
+    "standard": "^7.1.2",
+    "standard-version": "^2.3.0",
+    "tap": "^6.2.0"
+  }
+}
diff --git a/signals.js b/signals.js
new file mode 100644
index 0000000..3bd67a8
--- /dev/null
+++ b/signals.js
@@ -0,0 +1,53 @@
+// This is not the set of all possible signals.
+//
+// It IS, however, the set of all signals that trigger
+// an exit on either Linux or BSD systems.  Linux is a
+// superset of the signal names supported on BSD, and
+// the unknown signals just fail to register, so we can
+// catch that easily enough.
+//
+// Don't bother with SIGKILL.  It's uncatchable, which
+// means that we can't fire any callbacks anyway.
+//
+// If a user does happen to register a handler on a non-
+// fatal signal like SIGWINCH or something, and then
+// exit, it'll end up firing `process.emit('exit')`, so
+// the handler will be fired anyway.
+//
+// SIGBUS, SIGFPE, SIGSEGV and SIGILL, when not raised
+// artificially, inherently leave the process in a
+// state from which it is not safe to try and enter JS
+// listeners.
+module.exports = [
+  'SIGABRT',
+  'SIGALRM',
+  'SIGHUP',
+  'SIGINT',
+  'SIGTERM'
+]
+
+if (process.platform !== 'win32') {
+  module.exports.push(
+    'SIGVTALRM',
+    'SIGXCPU',
+    'SIGXFSZ',
+    'SIGUSR2',
+    'SIGTRAP',
+    'SIGSYS',
+    'SIGQUIT',
+    'SIGIOT'
+    // should detect profiler and enable/disable accordingly.
+    // see #21
+    // 'SIGPROF'
+  )
+}
+
+if (process.platform === 'linux') {
+  module.exports.push(
+    'SIGIO',
+    'SIGPOLL',
+    'SIGPWR',
+    'SIGSTKFLT',
+    'SIGUNUSED'
+  )
+}
diff --git a/test/all-integration-test.js b/test/all-integration-test.js
new file mode 100644
index 0000000..704a4d1
--- /dev/null
+++ b/test/all-integration-test.js
@@ -0,0 +1,97 @@
+/* global describe, it */
+
+var exec = require('child_process').exec
+var assert = require('assert')
+var isWindows = process.platform === 'win32'
+var shell = isWindows ? null : { shell: '/bin/bash' }
+var node = isWindows ? '"' + process.execPath + '"' : process.execPath
+
+require('chai').should()
+require('tap').mochaGlobals()
+
+var onSignalExit = require('../')
+
+describe('all-signals-integration-test', function () {
+  // These are signals that are aliases for other signals, so
+  // the result will sometimes be one of the others.  For these,
+  // we just verify that we GOT a signal, not what it is.
+  function weirdSignal (sig) {
+    return sig === 'SIGIOT' ||
+      sig === 'SIGIO' ||
+      sig === 'SIGSYS' ||
+      sig === 'SIGIOT' ||
+      sig === 'SIGABRT' ||
+      sig === 'SIGPOLL' ||
+      sig === 'SIGUNUSED'
+  }
+
+  // Exhaustively test every signal, and a few numbers.
+  // signal-exit does not currently support process.kill()
+  // on win32.
+  var signals = isWindows ? [] : onSignalExit.signals()
+  signals.concat('', 0, 1, 2, 3, 54).forEach(function (sig) {
+    var js = require.resolve('./fixtures/exiter.js')
+    it('exits properly: ' + sig, function (done) {
+      // issues with SIGUSR1 on Node 0.10.x
+      if (process.version.match(/^v0\.10\./) && sig === 'SIGUSR1') return done()
+
+      var cmd = node + ' ' + js + ' ' + sig
+      exec(cmd, shell, function (err, stdout, stderr) {
+        if (sig) {
+          if (!isWindows) assert(err)
+          if (!isNaN(sig)) {
+            if (!isWindows) assert.equal(err.code, sig)
+          } else if (!weirdSignal(sig)) {
+            if (!isWindows) err.signal.should.equal(sig)
+          } else if (sig) {
+            if (!isWindows) assert(err.signal)
+          }
+        } else {
+          assert.ifError(err)
+        }
+
+        try {
+          var data = JSON.parse(stdout)
+        } catch (er) {
+          console.error('invalid json: %j', stdout, stderr)
+          throw er
+        }
+
+        if (weirdSignal(sig)) {
+          data.wanted[1] = true
+          data.found[1] = !!data.found[1]
+        }
+        assert.deepEqual(data.found, data.wanted)
+        done()
+      })
+    })
+  })
+
+  signals.forEach(function (sig) {
+    var js = require.resolve('./fixtures/parent.js')
+    it('exits properly: (external sig) ' + sig, function (done) {
+      // issues with SIGUSR1 on Node 0.10.x
+      if (process.version.match(/^v0\.10\./) && sig === 'SIGUSR1') return done()
+
+      var cmd = node + ' ' + js + ' ' + sig
+      exec(cmd, shell, function (err, stdout, stderr) {
+        assert.ifError(err)
+        try {
+          var data = JSON.parse(stdout)
+        } catch (er) {
+          console.error('invalid json: %j', stdout, stderr)
+          throw er
+        }
+
+        if (weirdSignal(sig)) {
+          data.wanted[1] = true
+          data.found[1] = !!data.found[1]
+          data.external[1] = !!data.external[1]
+        }
+        assert.deepEqual(data.found, data.wanted)
+        assert.deepEqual(data.external, data.wanted)
+        done()
+      })
+    })
+  })
+})
diff --git a/test/fixtures/awaiter.js b/test/fixtures/awaiter.js
new file mode 100644
index 0000000..5bc3f68
--- /dev/null
+++ b/test/fixtures/awaiter.js
@@ -0,0 +1,35 @@
+var expectSignal = process.argv[2]
+
+if (!expectSignal || !isNaN(expectSignal)) {
+  throw new Error('signal not provided')
+}
+
+var onSignalExit = require('../../')
+
+onSignalExit(function (code, signal) {
+  // some signals don't always get recognized properly, because
+  // they have the same numeric code.
+  if (wanted[1] === true) {
+    signal = !!signal
+  }
+  console.log('%j', {
+    found: [ code, signal ],
+    wanted: wanted
+  })
+})
+
+var wanted
+switch (expectSignal) {
+  case 'SIGIOT':
+  case 'SIGUNUSED':
+  case 'SIGPOLL':
+    wanted = [ null, true ]
+    break
+  default:
+    wanted = [ null, expectSignal ]
+    break
+}
+
+console.error('want', wanted)
+
+setTimeout(function () {}, 1000)
diff --git a/test/fixtures/change-code-expect.json b/test/fixtures/change-code-expect.json
new file mode 100644
index 0000000..7eeeb4c
--- /dev/null
+++ b/test/fixtures/change-code-expect.json
@@ -0,0 +1,800 @@
+{
+  "explicit 0 nochange sigexit": {
+    "code": 0,
+    "signal": null,
+    "exitCode": 0,
+    "actualCode": 0,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "second code=0"
+    ]
+  },
+  "explicit 0 nochange nosigexit": {
+    "code": 0,
+    "signal": null,
+    "exitCode": 0,
+    "actualCode": 0,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "second code=0"
+    ]
+  },
+  "explicit 0 change sigexit": {
+    "code": 5,
+    "signal": null,
+    "exitCode": 5,
+    "actualCode": 5,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "set code from 0 to 5"
+    ]
+  },
+  "explicit 0 change nosigexit": {
+    "code": 0,
+    "signal": null,
+    "exitCode": 0,
+    "actualCode": 5,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "set code from 0 to 5"
+    ]
+  },
+  "explicit 0 code sigexit": {
+    "code": 5,
+    "signal": null,
+    "exitCode": 5,
+    "actualCode": 5,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "set code from 0 to 5",
+      "second code=0"
+    ]
+  },
+  "explicit 0 code nosigexit": {
+    "code": 0,
+    "signal": null,
+    "exitCode": 0,
+    "actualCode": 5,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "set code from 0 to 5",
+      "second code=0"
+    ]
+  },
+  "explicit 0 twice sigexit": {
+    "code": 5,
+    "signal": null,
+    "exitCode": 5,
+    "actualCode": 5,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "set code from 0 to 5"
+    ]
+  },
+  "explicit 0 twice nosigexit": {
+    "code": 0,
+    "signal": null,
+    "exitCode": 0,
+    "actualCode": 5,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "set code from 0 to 5"
+    ]
+  },
+  "explicit 0 twicecode sigexit": {
+    "code": 6,
+    "signal": null,
+    "exitCode": 6,
+    "actualCode": 6,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "set code from 0 to 5",
+      "set code from 5 to 6"
+    ]
+  },
+  "explicit 0 twicecode nosigexit": {
+    "code": 0,
+    "signal": null,
+    "exitCode": 0,
+    "actualCode": 6,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "set code from 0 to 5",
+      "set code from 5 to 6"
+    ]
+  },
+  "explicit 2 nochange sigexit": {
+    "code": 2,
+    "signal": null,
+    "exitCode": 2,
+    "actualCode": 2,
+    "actualSignal": null,
+    "stderr": [
+      "first code=2",
+      "second code=2"
+    ]
+  },
+  "explicit 2 nochange nosigexit": {
+    "code": 2,
+    "signal": null,
+    "exitCode": 2,
+    "actualCode": 2,
+    "actualSignal": null,
+    "stderr": [
+      "first code=2",
+      "second code=2"
+    ]
+  },
+  "explicit 2 change sigexit": {
+    "code": 5,
+    "signal": null,
+    "exitCode": 5,
+    "actualCode": 5,
+    "actualSignal": null,
+    "stderr": [
+      "first code=2",
+      "set code from 2 to 5"
+    ]
+  },
+  "explicit 2 change nosigexit": {
+    "code": 2,
+    "signal": null,
+    "exitCode": 2,
+    "actualCode": 5,
+    "actualSignal": null,
+    "stderr": [
+      "first code=2",
+      "set code from 2 to 5"
+    ]
+  },
+  "explicit 2 code sigexit": {
+    "code": 5,
+    "signal": null,
+    "exitCode": 5,
+    "actualCode": 5,
+    "actualSignal": null,
+    "stderr": [
+      "first code=2",
+      "set code from 2 to 5",
+      "second code=2"
+    ]
+  },
+  "explicit 2 code nosigexit": {
+    "code": 2,
+    "signal": null,
+    "exitCode": 2,
+    "actualCode": 5,
+    "actualSignal": null,
+    "stderr": [
+      "first code=2",
+      "set code from 2 to 5",
+      "second code=2"
+    ]
+  },
+  "explicit 2 twice sigexit": {
+    "code": 5,
+    "signal": null,
+    "exitCode": 5,
+    "actualCode": 5,
+    "actualSignal": null,
+    "stderr": [
+      "first code=2",
+      "set code from 2 to 5"
+    ]
+  },
+  "explicit 2 twice nosigexit": {
+    "code": 2,
+    "signal": null,
+    "exitCode": 2,
+    "actualCode": 5,
+    "actualSignal": null,
+    "stderr": [
+      "first code=2",
+      "set code from 2 to 5"
+    ]
+  },
+  "explicit 2 twicecode sigexit": {
+    "code": 6,
+    "signal": null,
+    "exitCode": 6,
+    "actualCode": 6,
+    "actualSignal": null,
+    "stderr": [
+      "first code=2",
+      "set code from 2 to 5",
+      "set code from 5 to 6"
+    ]
+  },
+  "explicit 2 twicecode nosigexit": {
+    "code": 2,
+    "signal": null,
+    "exitCode": 2,
+    "actualCode": 6,
+    "actualSignal": null,
+    "stderr": [
+      "first code=2",
+      "set code from 2 to 5",
+      "set code from 5 to 6"
+    ]
+  },
+  "explicit null nochange sigexit": {
+    "code": 0,
+    "signal": null,
+    "exitCode": 0,
+    "actualCode": 0,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "second code=0"
+    ]
+  },
+  "explicit null nochange nosigexit": {
+    "code": 0,
+    "signal": null,
+    "exitCode": 0,
+    "actualCode": 0,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "second code=0"
+    ]
+  },
+  "explicit null change sigexit": {
+    "code": 5,
+    "signal": null,
+    "exitCode": 5,
+    "actualCode": 5,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "set code from 0 to 5"
+    ]
+  },
+  "explicit null change nosigexit": {
+    "code": 0,
+    "signal": null,
+    "exitCode": 0,
+    "actualCode": 5,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "set code from 0 to 5"
+    ]
+  },
+  "explicit null code sigexit": {
+    "code": 5,
+    "signal": null,
+    "exitCode": 5,
+    "actualCode": 5,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "set code from 0 to 5",
+      "second code=0"
+    ]
+  },
+  "explicit null code nosigexit": {
+    "code": 0,
+    "signal": null,
+    "exitCode": 0,
+    "actualCode": 5,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "set code from 0 to 5",
+      "second code=0"
+    ]
+  },
+  "explicit null twice sigexit": {
+    "code": 5,
+    "signal": null,
+    "exitCode": 5,
+    "actualCode": 5,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "set code from 0 to 5"
+    ]
+  },
+  "explicit null twice nosigexit": {
+    "code": 0,
+    "signal": null,
+    "exitCode": 0,
+    "actualCode": 5,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "set code from 0 to 5"
+    ]
+  },
+  "explicit null twicecode sigexit": {
+    "code": 6,
+    "signal": null,
+    "exitCode": 6,
+    "actualCode": 6,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "set code from 0 to 5",
+      "set code from 5 to 6"
+    ]
+  },
+  "explicit null twicecode nosigexit": {
+    "code": 0,
+    "signal": null,
+    "exitCode": 0,
+    "actualCode": 6,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "set code from 0 to 5",
+      "set code from 5 to 6"
+    ]
+  },
+  "code 0 nochange sigexit": {
+    "code": 0,
+    "signal": null,
+    "exitCode": 0,
+    "actualCode": 0,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "second code=0"
+    ]
+  },
+  "code 0 nochange nosigexit": {
+    "code": 0,
+    "signal": null,
+    "exitCode": 0,
+    "actualCode": 0,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "second code=0"
+    ]
+  },
+  "code 0 change sigexit": {
+    "code": 5,
+    "signal": null,
+    "exitCode": 5,
+    "actualCode": 5,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "set code from 0 to 5"
+    ]
+  },
+  "code 0 change nosigexit": {
+    "code": 0,
+    "signal": null,
+    "exitCode": 0,
+    "actualCode": 5,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "set code from 0 to 5"
+    ]
+  },
+  "code 0 code sigexit": {
+    "code": 5,
+    "signal": null,
+    "exitCode": 5,
+    "actualCode": 5,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "set code from 0 to 5",
+      "second code=0"
+    ]
+  },
+  "code 0 code nosigexit": {
+    "code": 0,
+    "signal": null,
+    "exitCode": 0,
+    "actualCode": 5,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "set code from 0 to 5",
+      "second code=0"
+    ]
+  },
+  "code 0 twice sigexit": {
+    "code": 5,
+    "signal": null,
+    "exitCode": 5,
+    "actualCode": 5,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "set code from 0 to 5"
+    ]
+  },
+  "code 0 twice nosigexit": {
+    "code": 0,
+    "signal": null,
+    "exitCode": 0,
+    "actualCode": 5,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "set code from 0 to 5"
+    ]
+  },
+  "code 0 twicecode sigexit": {
+    "code": 6,
+    "signal": null,
+    "exitCode": 6,
+    "actualCode": 6,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "set code from 0 to 5",
+      "set code from 5 to 6"
+    ]
+  },
+  "code 0 twicecode nosigexit": {
+    "code": 0,
+    "signal": null,
+    "exitCode": 0,
+    "actualCode": 6,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "set code from 0 to 5",
+      "set code from 5 to 6"
+    ]
+  },
+  "code 2 nochange sigexit": {
+    "code": 2,
+    "signal": null,
+    "exitCode": 2,
+    "actualCode": 2,
+    "actualSignal": null,
+    "stderr": [
+      "first code=2",
+      "second code=2"
+    ]
+  },
+  "code 2 nochange nosigexit": {
+    "code": 2,
+    "signal": null,
+    "exitCode": 2,
+    "actualCode": 2,
+    "actualSignal": null,
+    "stderr": [
+      "first code=2",
+      "second code=2"
+    ]
+  },
+  "code 2 change sigexit": {
+    "code": 5,
+    "signal": null,
+    "exitCode": 5,
+    "actualCode": 5,
+    "actualSignal": null,
+    "stderr": [
+      "first code=2",
+      "set code from 2 to 5"
+    ]
+  },
+  "code 2 change nosigexit": {
+    "code": 2,
+    "signal": null,
+    "exitCode": 2,
+    "actualCode": 5,
+    "actualSignal": null,
+    "stderr": [
+      "first code=2",
+      "set code from 2 to 5"
+    ]
+  },
+  "code 2 code sigexit": {
+    "code": 5,
+    "signal": null,
+    "exitCode": 5,
+    "actualCode": 5,
+    "actualSignal": null,
+    "stderr": [
+      "first code=2",
+      "set code from 2 to 5",
+      "second code=2"
+    ]
+  },
+  "code 2 code nosigexit": {
+    "code": 2,
+    "signal": null,
+    "exitCode": 2,
+    "actualCode": 5,
+    "actualSignal": null,
+    "stderr": [
+      "first code=2",
+      "set code from 2 to 5",
+      "second code=2"
+    ]
+  },
+  "code 2 twice sigexit": {
+    "code": 5,
+    "signal": null,
+    "exitCode": 5,
+    "actualCode": 5,
+    "actualSignal": null,
+    "stderr": [
+      "first code=2",
+      "set code from 2 to 5"
+    ]
+  },
+  "code 2 twice nosigexit": {
+    "code": 2,
+    "signal": null,
+    "exitCode": 2,
+    "actualCode": 5,
+    "actualSignal": null,
+    "stderr": [
+      "first code=2",
+      "set code from 2 to 5"
+    ]
+  },
+  "code 2 twicecode sigexit": {
+    "code": 6,
+    "signal": null,
+    "exitCode": 6,
+    "actualCode": 6,
+    "actualSignal": null,
+    "stderr": [
+      "first code=2",
+      "set code from 2 to 5",
+      "set code from 5 to 6"
+    ]
+  },
+  "code 2 twicecode nosigexit": {
+    "code": 2,
+    "signal": null,
+    "exitCode": 2,
+    "actualCode": 6,
+    "actualSignal": null,
+    "stderr": [
+      "first code=2",
+      "set code from 2 to 5",
+      "set code from 5 to 6"
+    ]
+  },
+  "code null nochange sigexit": {
+    "code": 0,
+    "signal": null,
+    "exitCode": 0,
+    "actualCode": 0,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "second code=0"
+    ]
+  },
+  "code null nochange nosigexit": {
+    "code": 0,
+    "signal": null,
+    "exitCode": 0,
+    "actualCode": 0,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "second code=0"
+    ]
+  },
+  "code null change sigexit": {
+    "code": 5,
+    "signal": null,
+    "exitCode": 5,
+    "actualCode": 5,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "set code from 0 to 5"
+    ]
+  },
+  "code null change nosigexit": {
+    "code": 0,
+    "signal": null,
+    "exitCode": 0,
+    "actualCode": 5,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "set code from 0 to 5"
+    ]
+  },
+  "code null code sigexit": {
+    "code": 5,
+    "signal": null,
+    "exitCode": 5,
+    "actualCode": 5,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "set code from 0 to 5",
+      "second code=0"
+    ]
+  },
+  "code null code nosigexit": {
+    "code": 0,
+    "signal": null,
+    "exitCode": 0,
+    "actualCode": 5,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "set code from 0 to 5",
+      "second code=0"
+    ]
+  },
+  "code null twice sigexit": {
+    "code": 5,
+    "signal": null,
+    "exitCode": 5,
+    "actualCode": 5,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "set code from 0 to 5"
+    ]
+  },
+  "code null twice nosigexit": {
+    "code": 0,
+    "signal": null,
+    "exitCode": 0,
+    "actualCode": 5,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "set code from 0 to 5"
+    ]
+  },
+  "code null twicecode sigexit": {
+    "code": 6,
+    "signal": null,
+    "exitCode": 6,
+    "actualCode": 6,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "set code from 0 to 5",
+      "set code from 5 to 6"
+    ]
+  },
+  "code null twicecode nosigexit": {
+    "code": 0,
+    "signal": null,
+    "exitCode": 0,
+    "actualCode": 6,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "set code from 0 to 5",
+      "set code from 5 to 6"
+    ]
+  },
+  "normal 0 nochange sigexit": {
+    "code": 0,
+    "signal": null,
+    "exitCode": 0,
+    "actualCode": 0,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "second code=0"
+    ]
+  },
+  "normal 0 nochange nosigexit": {
+    "code": 0,
+    "signal": null,
+    "exitCode": 0,
+    "actualCode": 0,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "second code=0"
+    ]
+  },
+  "normal 0 change sigexit": {
+    "code": 5,
+    "signal": null,
+    "exitCode": 5,
+    "actualCode": 5,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "set code from 0 to 5"
+    ]
+  },
+  "normal 0 change nosigexit": {
+    "code": 0,
+    "signal": null,
+    "exitCode": 0,
+    "actualCode": 5,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "set code from 0 to 5"
+    ]
+  },
+  "normal 0 code sigexit": {
+    "code": 5,
+    "signal": null,
+    "exitCode": 5,
+    "actualCode": 5,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "set code from 0 to 5",
+      "second code=0"
+    ]
+  },
+  "normal 0 code nosigexit": {
+    "code": 0,
+    "signal": null,
+    "exitCode": 0,
+    "actualCode": 5,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "set code from 0 to 5",
+      "second code=0"
+    ]
+  },
+  "normal 0 twice sigexit": {
+    "code": 5,
+    "signal": null,
+    "exitCode": 5,
+    "actualCode": 5,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "set code from 0 to 5"
+    ]
+  },
+  "normal 0 twice nosigexit": {
+    "code": 0,
+    "signal": null,
+    "exitCode": 0,
+    "actualCode": 5,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "set code from 0 to 5"
+    ]
+  },
+  "normal 0 twicecode sigexit": {
+    "code": 6,
+    "signal": null,
+    "exitCode": 6,
+    "actualCode": 6,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "set code from 0 to 5",
+      "set code from 5 to 6"
+    ]
+  },
+  "normal 0 twicecode nosigexit": {
+    "code": 0,
+    "signal": null,
+    "exitCode": 0,
+    "actualCode": 6,
+    "actualSignal": null,
+    "stderr": [
+      "first code=0",
+      "set code from 0 to 5",
+      "set code from 5 to 6"
+    ]
+  }
+}
diff --git a/test/fixtures/change-code.js b/test/fixtures/change-code.js
new file mode 100644
index 0000000..0a4388e
--- /dev/null
+++ b/test/fixtures/change-code.js
@@ -0,0 +1,99 @@
+var join = require('path').join
+
+if (process.argv.length === 2) {
+  var types = [ 'explicit', 'code', 'normal' ]
+  var codes = [ 0, 2, 'null' ]
+  var changes = [ 'nochange', 'change', 'code', 'twice', 'twicecode' ]
+  var handlers = [ 'sigexit', 'nosigexit' ]
+  var opts = []
+  types.forEach(function (type) {
+    var testCodes = type === 'normal' ? [ 0 ] : codes
+    testCodes.forEach(function (code) {
+      changes.forEach(function (change) {
+        handlers.forEach(function (handler) {
+          opts.push([type, code, change, handler].join(' '))
+        })
+      })
+    })
+  })
+
+  var results = {}
+
+  var exec = require('child_process').exec
+  run(opts.shift())
+} else {
+  var type = process.argv[2]
+  var code = +process.argv[3]
+  var change = process.argv[4]
+  var sigexit = process.argv[5] !== 'nosigexit'
+
+  if (sigexit) {
+    var onSignalExit = require('../../')
+    onSignalExit(listener)
+  } else {
+    process.on('exit', listener)
+  }
+
+  process.on('exit', function (code) {
+    console.error('first code=%j', code)
+  })
+
+  if (change !== 'nochange') {
+    process.once('exit', function (code) {
+      console.error('set code from %j to %j', code, 5)
+      if (change === 'code' || change === 'twicecode') {
+        process.exitCode = 5
+      } else {
+        process.exit(5)
+      }
+    })
+    if (change === 'twicecode' || change === 'twice') {
+      process.once('exit', function (code) {
+        code = process.exitCode || code
+        console.error('set code from %j to %j', code, code + 1)
+        process.exit(code + 1)
+      })
+    }
+  }
+
+  process.on('exit', function (code) {
+    console.error('second code=%j', code)
+  })
+
+  if (type === 'explicit') {
+    if (code || code === 0) {
+      process.exit(code)
+    } else {
+      process.exit()
+    }
+  } else if (type === 'code') {
+    process.exitCode = +code || 0
+  }
+}
+
+function listener (code, signal) {
+  signal = signal || null
+  console.log('%j', { code: code, signal: signal, exitCode: process.exitCode || 0 })
+}
+
+function run (opt) {
+  console.error(opt)
+  var shell = process.platform === 'win32' ? null : { shell: '/bin/bash' }
+  exec(join(process.execPath, ' ', __filename, ' ' + opt), shell, function (err, stdout, stderr) {
+    var res = JSON.parse(stdout)
+    if (err) {
+      res.actualCode = err.code
+      res.actualSignal = err.signal
+    } else {
+      res.actualCode = 0
+      res.actualSignal = null
+    }
+    res.stderr = stderr.trim().split('\n')
+    results[opt] = res
+    if (opts.length) {
+      run(opts.shift())
+    } else {
+      console.log(JSON.stringify(results, null, 2))
+    }
+  })
+}
diff --git a/test/fixtures/end-of-execution.js b/test/fixtures/end-of-execution.js
new file mode 100644
index 0000000..8b8f245
--- /dev/null
+++ b/test/fixtures/end-of-execution.js
@@ -0,0 +1,5 @@
+var onSignalExit = require('../../')
+
+onSignalExit(function (code, signal) {
+  console.log('reached end of execution, ' + code + ', ' + signal)
+})
diff --git a/test/fixtures/exit-last.js b/test/fixtures/exit-last.js
new file mode 100644
index 0000000..899f475
--- /dev/null
+++ b/test/fixtures/exit-last.js
@@ -0,0 +1,14 @@
+var onSignalExit = require('../../')
+var counter = 0
+
+onSignalExit(function (code, signal) {
+  counter++
+  console.log('last counter=%j, code=%j, signal=%j',
+              counter, code, signal)
+}, {alwaysLast: true})
+
+onSignalExit(function (code, signal) {
+  counter++
+  console.log('first counter=%j, code=%j, signal=%j',
+              counter, code, signal)
+})
diff --git a/test/fixtures/exit.js b/test/fixtures/exit.js
new file mode 100644
index 0000000..c1aab3e
--- /dev/null
+++ b/test/fixtures/exit.js
@@ -0,0 +1,7 @@
+var onSignalExit = require('../../')
+
+onSignalExit(function (code, signal) {
+  console.log('exited with process.exit(), ' + code + ', ' + signal)
+})
+
+process.exit(32)
diff --git a/test/fixtures/exiter.js b/test/fixtures/exiter.js
new file mode 100644
index 0000000..61dc120
--- /dev/null
+++ b/test/fixtures/exiter.js
@@ -0,0 +1,44 @@
+var exit = process.argv[2] || 0
+
+var onSignalExit = require('../../')
+
+onSignalExit(function (code, signal) {
+  // some signals don't always get recognized properly, because
+  // they have the same numeric code.
+  if (wanted[1] === true) {
+    signal = !!signal
+  }
+  console.log('%j', {
+    found: [ code, signal ],
+    wanted: wanted
+  })
+})
+
+var wanted
+if (isNaN(exit)) {
+  switch (exit) {
+    case 'SIGIOT':
+    case 'SIGUNUSED':
+    case 'SIGPOLL':
+      wanted = [ null, true ]
+      break
+    default:
+      wanted = [ null, exit ]
+      break
+  }
+
+  try {
+    process.kill(process.pid, exit)
+    setTimeout(function () {}, 1000)
+  } catch (er) {
+    wanted = [ 0, null ]
+  }
+} else {
+  exit = +exit
+  wanted = [ exit, null ]
+  // If it's explicitly requested 0, then explicitly call it.
+  // "no arg" = "exit naturally"
+  if (exit || process.argv[2]) {
+    process.exit(exit)
+  }
+}
diff --git a/test/fixtures/load-unload.js b/test/fixtures/load-unload.js
new file mode 100644
index 0000000..5509e2e
--- /dev/null
+++ b/test/fixtures/load-unload.js
@@ -0,0 +1,7 @@
+// just be silly with calling these functions a bunch
+// mostly just to get coverage of the guard branches
+var onSignalExit = require('../../')
+onSignalExit.load()
+onSignalExit.load()
+onSignalExit.unload()
+onSignalExit.unload()
diff --git a/test/fixtures/multiple-load.js b/test/fixtures/multiple-load.js
new file mode 100644
index 0000000..02fcd7f
--- /dev/null
+++ b/test/fixtures/multiple-load.js
@@ -0,0 +1,52 @@
+// simulate cases where the module could be loaded from multiple places
+var onSignalExit = require('../../')
+var counter = 0
+
+onSignalExit(function (code, signal) {
+  counter++
+  console.log('last counter=%j, code=%j, signal=%j',
+              counter, code, signal)
+}, {alwaysLast: true})
+
+onSignalExit(function (code, signal) {
+  counter++
+  console.log('first counter=%j, code=%j, signal=%j',
+              counter, code, signal)
+})
+
+delete require('module')._cache[require.resolve('../../')]
+onSignalExit = require('../../')
+
+onSignalExit(function (code, signal) {
+  counter++
+  console.log('last counter=%j, code=%j, signal=%j',
+              counter, code, signal)
+}, {alwaysLast: true})
+
+onSignalExit(function (code, signal) {
+  counter++
+  console.log('first counter=%j, code=%j, signal=%j',
+              counter, code, signal)
+})
+
+// Lastly, some that should NOT be shown
+delete require('module')._cache[require.resolve('../../')]
+onSignalExit = require('../../')
+
+var unwrap = onSignalExit(function (code, signal) {
+  counter++
+  console.log('last counter=%j, code=%j, signal=%j',
+              counter, code, signal)
+}, {alwaysLast: true})
+unwrap()
+
+unwrap = onSignalExit(function (code, signal) {
+  counter++
+  console.log('first counter=%j, code=%j, signal=%j',
+              counter, code, signal)
+})
+
+unwrap()
+
+process.kill(process.pid, 'SIGHUP')
+setTimeout(function () {}, 1000)
diff --git a/test/fixtures/parent.js b/test/fixtures/parent.js
new file mode 100644
index 0000000..5dcc382
--- /dev/null
+++ b/test/fixtures/parent.js
@@ -0,0 +1,51 @@
+var signal = process.argv[2]
+var gens = +process.argv[3] || 0
+
+if (!signal || !isNaN(signal)) {
+  throw new Error('signal not provided')
+}
+
+var spawn = require('child_process').spawn
+var file = require.resolve('./awaiter.js')
+console.error(process.pid, signal, gens)
+
+if (gens > 0) {
+  file = __filename
+}
+
+var child = spawn(process.execPath, [file, signal, gens - 1], {
+  stdio: [ 0, 'pipe', 'pipe' ]
+})
+
+if (!gens) {
+  child.stderr.on('data', function () {
+    child.kill(signal)
+  })
+}
+
+var result = ''
+child.stdout.on('data', function (c) {
+  result += c
+})
+
+child.on('close', function (code, sig) {
+  try {
+    result = JSON.parse(result)
+  } catch (er) {
+    console.log('%j', {
+      error: 'failed to parse json\n' + er.message,
+      result: result,
+      pid: process.pid,
+      child: child.pid,
+      gens: gens,
+      expect: [ null, signal ],
+      actual: [ code, sig ]
+    })
+    return
+  }
+  if (result.wanted[1] === true) {
+    sig = !!sig
+  }
+  result.external = result.external || [ code, sig ]
+  console.log('%j', result)
+})
diff --git a/test/fixtures/sigint.js b/test/fixtures/sigint.js
new file mode 100644
index 0000000..769a076
--- /dev/null
+++ b/test/fixtures/sigint.js
@@ -0,0 +1,11 @@
+var onSignalExit = require('../../')
+
+onSignalExit(function (code, signal) {
+  console.log('exited with sigint, ' + code + ', ' + signal)
+})
+
+// For some reason, signals appear to not always be fast enough
+// to come in before the process exits.  Just a few ticks needed.
+setTimeout(function () {}, 1000)
+
+process.kill(process.pid, 'SIGINT')
diff --git a/test/fixtures/sigkill.js b/test/fixtures/sigkill.js
new file mode 100644
index 0000000..88492d2
--- /dev/null
+++ b/test/fixtures/sigkill.js
@@ -0,0 +1,19 @@
+// SIGKILL can't be caught, and in fact, even trying to add the
+// listener will throw an error.
+// We handle that nicely.
+//
+// This is just here to get another few more lines of test
+// coverage.  That's also why it lies about being on a linux
+// platform so that we pull in those other event types.
+
+Object.defineProperty(process, 'platform', {
+  value: 'linux',
+  writable: false,
+  enumerable: true,
+  configurable: true
+})
+
+var signals = require('../../signals.js')
+signals.push('SIGKILL')
+var onSignalExit = require('../../')
+onSignalExit.load()
diff --git a/test/fixtures/signal-default.js b/test/fixtures/signal-default.js
new file mode 100644
index 0000000..7016007
--- /dev/null
+++ b/test/fixtures/signal-default.js
@@ -0,0 +1,99 @@
+// This fixture is not used in any tests.  It is here merely as a way to
+// do research into the various signal behaviors on Linux and Darwin.
+// Run with no args to cycle through every signal type.  Run with a signal
+// arg to learn about how that signal behaves.
+
+if (process.argv[2]) {
+  child(process.argv[2])
+} else {
+  var signals = [
+    'SIGABRT',
+    'SIGALRM',
+    'SIGBUS',
+    'SIGCHLD',
+    'SIGCLD',
+    'SIGCONT',
+    'SIGEMT',
+    'SIGFPE',
+    'SIGHUP',
+    'SIGILL',
+    'SIGINFO',
+    'SIGINT',
+    'SIGIO',
+    'SIGIOT',
+    'SIGKILL',
+    'SIGLOST',
+    'SIGPIPE',
+    'SIGPOLL',
+    // 'SIGPROF', see #21
+    'SIGPWR',
+    'SIGQUIT',
+    'SIGSEGV',
+    'SIGSTKFLT',
+    'SIGSTOP',
+    'SIGSYS',
+    'SIGTERM',
+    'SIGTRAP',
+    'SIGTSTP',
+    'SIGTTIN',
+    'SIGTTOU',
+    'SIGUNUSED',
+    'SIGURG',
+    'SIGUSR1',
+    'SIGUSR2',
+    'SIGVTALRM',
+    'SIGWINCH',
+    'SIGXCPU',
+    'SIGXFSZ'
+  ]
+
+  var spawn = require('child_process').spawn
+  ;(function test (signal) {
+    if (!signal) {
+      return
+    }
+    var child = spawn(process.execPath, [__filename, signal], { stdio: 'inherit' })
+    var timer = setTimeout(function () {
+      console.log('requires SIGCONT')
+      process.kill(child.pid, 'SIGCONT')
+    }, 750)
+
+    child.on('close', function (code, signal) {
+      console.log('code=%j signal=%j\n', code, signal)
+      clearTimeout(timer)
+      test(signals.pop())
+    })
+  })(signals.pop())
+}
+
+function child (signal) {
+  console.log('signal=%s', signal)
+
+  // set a timeout so we know whether or not the process terminated.
+  setTimeout(function () {
+    console.log('not terminated')
+  }, 200)
+
+  process.on('exit', function (code) {
+    console.log('emit exit code=%j', code)
+  })
+
+  try {
+    process.on(signal, function fn () {
+      console.log('signal is catchable', signal)
+      process.removeListener(signal, fn)
+      setTimeout(function () {
+        console.error('signal again')
+        process.kill(process.pid, signal)
+      })
+    })
+  } catch (er) {
+    console.log('not listenable')
+  }
+
+  try {
+    process.kill(process.pid, signal)
+  } catch (er) {
+    console.log('not issuable')
+  }
+}
diff --git a/test/fixtures/signal-last.js b/test/fixtures/signal-last.js
new file mode 100644
index 0000000..9e7dec8
--- /dev/null
+++ b/test/fixtures/signal-last.js
@@ -0,0 +1,17 @@
+var onSignalExit = require('../../')
+var counter = 0
+
+onSignalExit(function (code, signal) {
+  counter++
+  console.log('last counter=%j, code=%j, signal=%j',
+              counter, code, signal)
+}, {alwaysLast: true})
+
+onSignalExit(function (code, signal) {
+  counter++
+  console.log('first counter=%j, code=%j, signal=%j',
+              counter, code, signal)
+})
+
+process.kill(process.pid, 'SIGHUP')
+setTimeout(function () {}, 1000)
diff --git a/test/fixtures/signal-listener.js b/test/fixtures/signal-listener.js
new file mode 100644
index 0000000..5a84d12
--- /dev/null
+++ b/test/fixtures/signal-listener.js
@@ -0,0 +1,23 @@
+var onSignalExit = require('../../')
+
+setTimeout(function () {})
+
+var calledListener = 0
+onSignalExit(function (code, signal) {
+  console.log('exited calledListener=%j, code=%j, signal=%j',
+              calledListener, code, signal)
+})
+
+process.on('SIGHUP', listener)
+process.kill(process.pid, 'SIGHUP')
+
+function listener () {
+  calledListener++
+  if (calledListener > 3) {
+    process.removeListener('SIGHUP', listener)
+  }
+
+  setTimeout(function () {
+    process.kill(process.pid, 'SIGHUP')
+  })
+}
diff --git a/test/fixtures/sigpipe.js b/test/fixtures/sigpipe.js
new file mode 100644
index 0000000..169faed
--- /dev/null
+++ b/test/fixtures/sigpipe.js
@@ -0,0 +1,8 @@
+var onSignalExit = require('../..')
+onSignalExit(function (code, signal) {
+  console.error('onSignalExit(%j,%j)', code, signal)
+})
+setTimeout(function () {
+  console.log('hello')
+})
+process.kill(process.pid, 'SIGPIPE')
diff --git a/test/fixtures/sigterm.js b/test/fixtures/sigterm.js
new file mode 100644
index 0000000..85b598a
--- /dev/null
+++ b/test/fixtures/sigterm.js
@@ -0,0 +1,9 @@
+var onSignalExit = require('../../')
+
+onSignalExit(function (code, signal) {
+  console.log('exited with sigterm, ' + code + ', ' + signal)
+})
+
+setTimeout(function () {}, 1000)
+
+process.kill(process.pid, 'SIGTERM')
diff --git a/test/fixtures/unwrap.js b/test/fixtures/unwrap.js
new file mode 100644
index 0000000..8d8b1ad
--- /dev/null
+++ b/test/fixtures/unwrap.js
@@ -0,0 +1,37 @@
+// simulate cases where the module could be loaded from multiple places
+
+// Need to lie about this a little bit, since nyc uses this module
+// for its coverage wrap-up handling
+if (process.env.NYC_CWD) {
+  var emitter = process.__signal_exit_emitter__
+  var listeners = emitter.listeners('afterexit')
+  process.removeAllListeners('SIGHUP')
+  delete process.__signal_exit_emitter__
+  delete require('module')._cache[require.resolve('../../')]
+}
+
+var onSignalExit = require('../../')
+var counter = 0
+
+var unwrap = onSignalExit(function (code, signal) {
+  counter++
+  console.log('last counter=%j, code=%j, signal=%j',
+              counter, code, signal)
+}, {alwaysLast: true})
+unwrap()
+
+unwrap = onSignalExit(function (code, signal) {
+  counter++
+  console.log('first counter=%j, code=%j, signal=%j',
+              counter, code, signal)
+})
+unwrap()
+
+if (global.__coverage__ && listeners && listeners.length) {
+  listeners.forEach(function (fn) {
+    onSignalExit(fn, { alwaysLast: true })
+  })
+}
+
+process.kill(process.pid, 'SIGHUP')
+setTimeout(function () {}, 1000)
diff --git a/test/multi-exit.js b/test/multi-exit.js
new file mode 100644
index 0000000..0bcbb30
--- /dev/null
+++ b/test/multi-exit.js
@@ -0,0 +1,61 @@
+var exec = require('child_process').exec
+var t = require('tap')
+var isWindows = process.platform === 'win32'
+var shell = isWindows ? null : { shell: '/bin/bash' }
+var node = isWindows ? '"' + process.execPath + '"' : process.execPath
+
+var fixture = require.resolve('./fixtures/change-code.js')
+var expect = require('./fixtures/change-code-expect.json')
+
+// process.exitCode has problems prior to:
+// https://github.com/joyent/node/commit/c0d81f90996667a658aa4403123e02161262506a
+function isZero10 () {
+  return /^v0\.10\..+$/.test(process.version)
+}
+
+// process.exit(code), process.exitCode = code, normal exit
+var types = [ 'explicit', 'normal' ]
+if (!isZero10()) types.push('code')
+
+// initial code that is set.  Note, for 'normal' exit, there's no
+// point doing these, because we just exit without modifying code
+var codes = [ 0, 2, 'null' ]
+
+// do not change, change to 5 with exit(), change to 5 with exitCode,
+// change to 5 and then to 2 with exit(), change twice with exitcode
+var changes = [ 'nochange', 'change', 'twice' ]
+if (!isZero10()) changes.push('code', 'twicecode')
+
+// use signal-exit, use process.on('exit')
+var handlers = [ 'sigexit', 'nosigexit' ]
+
+var opts = []
+types.forEach(function (type) {
+  var testCodes = type === 'normal' ? [0] : codes
+  testCodes.forEach(function (code) {
+    changes.forEach(function (change) {
+      handlers.forEach(function (handler) {
+        opts.push([type, code, change, handler].join(' '))
+      })
+    })
+  })
+})
+
+opts.forEach(function (opt) {
+  t.test(opt, function (t) {
+    var cmd = node + ' ' + fixture + ' ' + opt
+    exec(cmd, shell, function (err, stdout, stderr) {
+      var res = JSON.parse(stdout)
+      if (err) {
+        res.actualCode = err.code
+        res.actualSignal = err.signal
+      } else {
+        res.actualCode = 0
+        res.actualSignal = null
+      }
+      res.stderr = stderr.trim().split('\n')
+      t.same(res, expect[opt])
+      t.end()
+    })
+  })
+})
diff --git a/test/signal-exit-test.js b/test/signal-exit-test.js
new file mode 100644
index 0000000..b816273
--- /dev/null
+++ b/test/signal-exit-test.js
@@ -0,0 +1,116 @@
+/* global describe, it */
+
+var exec = require('child_process').exec
+var expect = require('chai').expect
+var assert = require('assert')
+var isWindows = process.platform === 'win32'
+var shell = isWindows ? null : { shell: '/bin/bash' }
+var node = isWindows ? '"' + process.execPath + '"' : process.execPath
+
+require('chai').should()
+require('tap').mochaGlobals()
+
+describe('signal-exit', function () {
+  it('receives an exit event when a process exits normally', function (done) {
+    exec(node + ' ./test/fixtures/end-of-execution.js', shell, function (err, stdout, stderr) {
+      expect(err).to.equal(null)
+      stdout.should.match(/reached end of execution, 0, null/)
+      done()
+    })
+  })
+
+  it('receives an exit event when process.exit() is called', function (done) {
+    exec(node + ' ./test/fixtures/exit.js', shell, function (err, stdout, stderr) {
+      if (!isWindows) err.code.should.equal(32)
+      stdout.should.match(/exited with process\.exit\(\), 32, null/)
+      done()
+    })
+  })
+
+  it('ensures that if alwaysLast=true, the handler is run last (signal)', function (done) {
+    exec(node + ' ./test/fixtures/signal-last.js', shell, function (err, stdout, stderr) {
+      assert(err)
+      stdout.should.match(/first counter=1/)
+      stdout.should.match(/last counter=2/)
+      done()
+    })
+  })
+
+  it('ensures that if alwaysLast=true, the handler is run last (normal exit)', function (done) {
+    exec(node + ' ./test/fixtures/exit-last.js', shell, function (err, stdout, stderr) {
+      assert.ifError(err)
+      stdout.should.match(/first counter=1/)
+      stdout.should.match(/last counter=2/)
+      done()
+    })
+  })
+
+  it('works when loaded multiple times', function (done) {
+    exec(node + ' ./test/fixtures/multiple-load.js', shell, function (err, stdout, stderr) {
+      assert(err)
+      stdout.should.match(/first counter=1/)
+      stdout.should.match(/first counter=2/)
+      stdout.should.match(/last counter=3/)
+      stdout.should.match(/last counter=4/)
+      done()
+    })
+  })
+
+  it('removes handlers when fully unwrapped', function (done) {
+    exec(node + ' ./test/fixtures/unwrap.js', shell, function (err, stdout, stderr) {
+      assert(err)
+      if (!isWindows) err.signal.should.equal('SIGHUP')
+      if (!isWindows) expect(err.code).to.equal(null)
+      done()
+    })
+  })
+
+  it('does not load() or unload() more than once', function (done) {
+    exec(node + ' ./test/fixtures/load-unload.js', shell, function (err, stdout, stderr) {
+      assert.ifError(err)
+      done()
+    })
+  })
+
+  if (!isWindows) {
+    it('receives an exit event when a process is terminated with sigint', function (done) {
+      exec(node + ' ./test/fixtures/sigint.js', shell, function (err, stdout, stderr) {
+        assert(err)
+        stdout.should.match(/exited with sigint, null, SIGINT/)
+        done()
+      })
+    })
+
+    it('receives an exit event when a process is terminated with sigterm', function (done) {
+      exec(node + ' ./test/fixtures/sigterm.js', shell, function (err, stdout, stderr) {
+        assert(err)
+        stdout.should.match(/exited with sigterm, null, SIGTERM/)
+        done()
+      })
+    })
+
+    it('does not exit on sigpipe', function (done) {
+      exec(node + ' ./test/fixtures/sigpipe.js', shell, function (err, stdout, stderr) {
+        assert.ifError(err)
+        stdout.should.match(/hello/)
+        stderr.should.match(/onSignalExit\(0,null\)/)
+        done()
+      })
+    })
+
+    it('handles uncatchable signals with grace and poise', function (done) {
+      exec(node + ' ./test/fixtures/sigkill.js', shell, function (err, stdout, stderr) {
+        assert.ifError(err)
+        done()
+      })
+    })
+
+    it('does not exit if user handles signal', function (done) {
+      exec(node + ' ./test/fixtures/signal-listener.js', shell, function (err, stdout, stderr) {
+        assert(err)
+        assert.equal(stdout, 'exited calledListener=4, code=null, signal="SIGHUP"\n')
+        done()
+      })
+    })
+  }
+})

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



More information about the Pkg-javascript-commits mailing list