├── .editorconfig
├── .gitattributes
├── .gitignore
├── .jshintrc
├── .travis.yml
├── CHANGELOG.md
├── index.js
├── license
├── package-lock.json
├── package.json
├── readme.md
└── test
├── cases
├── async-err.js
├── async-exit-timeout.js
├── async.js
├── stub.js
├── sync.js
└── unhandled-promise.js
└── tests.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = tab
5 | end_of_line = lf
6 | charset = utf-8
7 | trim_trailing_whitespace = true
8 | insert_final_newline = true
9 |
10 | [package.json]
11 | indent_style = space
12 | indent_size = 2
13 |
14 | [*.md]
15 | trim_trailing_whitespace = false
16 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
3 | /.idea
4 |
5 | coverage
6 | .nyc_output
7 |
8 | npm-debug.log
9 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "node": true,
3 | "esnext": true,
4 | "bitwise": true,
5 | "camelcase": true,
6 | "curly": true,
7 | "immed": true,
8 | "newcap": true,
9 | "noarg": true,
10 | "undef": true,
11 | "unused": "vars",
12 | "strict": true
13 | }
14 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: node_js
3 | node_js:
4 | - '8'
5 | - '6'
6 | - '4'
7 | before_install:
8 | - 'npm install -g npm@latest'
9 | after_success:
10 | - 'npm test && ./node_modules/.bin/nyc report --reporter=text-lcov | ./node_modules/.bin/coveralls'
11 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | 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.
4 |
5 |
6 | ## [2.0.1](https://github.com/tapppi/async-exit-hook/compare/v2.0.0...v2.0.1) (2017-08-03)
7 |
8 |
9 |
10 |
11 | # [2.0.0](https://github.com/tapppi/async-exit-hook/compare/v1.1.2...v2.0.0) (2017-08-03)
12 |
13 |
14 | ### Features
15 |
16 | * add unhandledRejectionHandler ([#3](https://github.com/tapppi/async-exit-hook/issues/3)) ([96a194f](https://github.com/tapppi/async-exit-hook/commit/96a194f))
17 |
18 |
19 | ### BREAKING CHANGES
20 |
21 | * unhandledExceptionHandler no longer
22 | catches rejections.
23 |
24 |
25 |
26 |
27 | ## [1.1.2](https://github.com/tapppi/async-exit-hook/compare/v1.1.1...v1.1.2) (2017-03-29)
28 |
29 |
30 | ### Bug Fixes
31 |
32 | * filters are used individually for events [#1](https://github.com/tapppi/async-exit-hook/issues/1) ([03235c8](https://github.com/tapppi/async-exit-hook/commit/03235c8))
33 |
34 |
35 |
36 |
37 | ## [1.1.1](https://github.com/tapppi/async-exit-hook/compare/v1.1.0...v1.1.1) (2016-11-04)
38 |
39 |
40 | ### Bug Fixes
41 |
42 | * unhandled rejections now handled ([4302b9e](https://github.com/tapppi/async-exit-hook/commit/4302b9e))
43 |
44 |
45 | ### Chores
46 |
47 | * drop support for node 0.12 ([2830391](https://github.com/tapppi/async-exit-hook/commit/2830391))
48 |
49 |
50 | ### BREAKING CHANGES
51 |
52 | * node 0.12 not tested anymore
53 |
54 |
55 |
56 |
57 | # [1.1.0](https://github.com/tapppi/async-exit-hook/compare/v1.0.0...v1.1.0) (2016-10-13)
58 |
59 |
60 | ### Features
61 |
62 | * support uncaughtRejectionHandler ([9098e3c](https://github.com/tapppi/async-exit-hook/commit/9098e3c))
63 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const hooks = [];
4 | const errHooks = [];
5 | let called = false;
6 | let waitingFor = 0;
7 | let asyncTimeoutMs = 10000;
8 |
9 | const events = {};
10 | const filters = {};
11 |
12 | function exit(exit, code, err) {
13 | // Helper functions
14 | let doExitDone = false;
15 |
16 | function doExit() {
17 | if (doExitDone) {
18 | return;
19 | }
20 | doExitDone = true;
21 |
22 | if (exit === true) {
23 | // All handlers should be called even if the exit-hook handler was registered first
24 | process.nextTick(process.exit.bind(null, code));
25 | }
26 | }
27 |
28 | // Async hook callback, decrements waiting counter
29 | function stepTowardExit() {
30 | process.nextTick(() => {
31 | if (--waitingFor === 0) {
32 | doExit();
33 | }
34 | });
35 | }
36 |
37 | // Runs a single hook
38 | function runHook(syncArgCount, err, hook) {
39 | // Cannot perform async hooks in `exit` event
40 | if (exit && hook.length > syncArgCount) {
41 | // Hook is async, expects a finish callback
42 | waitingFor++;
43 |
44 | if (err) {
45 | // Pass error, calling uncaught exception handlers
46 | return hook(err, stepTowardExit);
47 | }
48 | return hook(stepTowardExit);
49 | }
50 |
51 | // Hook is synchronous
52 | if (err) {
53 | // Pass error, calling uncaught exception handlers
54 | return hook(err);
55 | }
56 | return hook();
57 | }
58 |
59 | // Only execute hooks once
60 | if (called) {
61 | return;
62 | }
63 |
64 | called = true;
65 |
66 | // Run hooks
67 | if (err) {
68 | // Uncaught exception, run error hooks
69 | errHooks.map(runHook.bind(null, 1, err));
70 | }
71 | hooks.map(runHook.bind(null, 0, null));
72 |
73 | if (waitingFor) {
74 | // Force exit after x ms (10000 by default), even if async hooks in progress
75 | setTimeout(() => {
76 | doExit();
77 | }, asyncTimeoutMs);
78 | } else {
79 | // No asynchronous hooks, exit immediately
80 | doExit();
81 | }
82 | }
83 |
84 | // Add a hook
85 | function add(hook) {
86 | hooks.push(hook);
87 |
88 | if (hooks.length === 1) {
89 | add.hookEvent('exit');
90 | add.hookEvent('beforeExit', 0);
91 | add.hookEvent('SIGHUP', 128 + 1);
92 | add.hookEvent('SIGINT', 128 + 2);
93 | add.hookEvent('SIGTERM', 128 + 15);
94 | add.hookEvent('SIGBREAK', 128 + 21);
95 |
96 | // PM2 Cluster shutdown message. Caught to support async handlers with pm2, needed because
97 | // explicitly calling process.exit() doesn't trigger the beforeExit event, and the exit
98 | // event cannot support async handlers, since the event loop is never called after it.
99 | add.hookEvent('message', 0, function (msg) { // eslint-disable-line prefer-arrow-callback
100 | if (msg !== 'shutdown') {
101 | return true;
102 | }
103 | });
104 | }
105 | }
106 |
107 | // New signal / event to hook
108 | add.hookEvent = function (event, code, filter) {
109 | events[event] = function () {
110 | const eventFilters = filters[event];
111 | for (let i = 0; i < eventFilters.length; i++) {
112 | if (eventFilters[i].apply(this, arguments)) {
113 | return;
114 | }
115 | }
116 | exit(code !== undefined && code !== null, code);
117 | };
118 |
119 | if (!filters[event]) {
120 | filters[event] = [];
121 | }
122 |
123 | if (filter) {
124 | filters[event].push(filter);
125 | }
126 | process.on(event, events[event]);
127 | };
128 |
129 | // Unhook signal / event
130 | add.unhookEvent = function (event) {
131 | process.removeListener(event, events[event]);
132 | delete events[event];
133 | delete filters[event];
134 | };
135 |
136 | // List hooked events
137 | add.hookedEvents = function () {
138 | const ret = [];
139 | for (const name in events) {
140 | if ({}.hasOwnProperty.call(events, name)) {
141 | ret.push(name);
142 | }
143 | }
144 | return ret;
145 | };
146 |
147 | // Add an uncaught exception handler
148 | add.uncaughtExceptionHandler = function (hook) {
149 | errHooks.push(hook);
150 |
151 | if (errHooks.length === 1) {
152 | process.once('uncaughtException', exit.bind(null, true, 1));
153 | }
154 | };
155 |
156 | // Add an unhandled rejection handler
157 | add.unhandledRejectionHandler = function (hook) {
158 | errHooks.push(hook);
159 |
160 | if (errHooks.length === 1) {
161 | process.once('unhandledRejection', exit.bind(null, true, 1));
162 | }
163 | };
164 |
165 | // Configure async force exit timeout
166 | add.forceExitTimeout = function (ms) {
167 | asyncTimeoutMs = ms;
168 | };
169 |
170 | // Export
171 | module.exports = add;
172 |
--------------------------------------------------------------------------------
/license:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) Sindre Sorhus (sindresorhus.com)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "async-exit-hook",
3 | "version": "2.0.1",
4 | "description": "Run some code when the process exits (supports async hooks and pm2 clustering)",
5 | "license": "MIT",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/tapppi/async-exit-hook.git"
9 | },
10 | "author": {
11 | "name": "Tapani Moilanen",
12 | "email": "moilanen.tapani@gmail.com",
13 | "url": "https://github.com/tapppi"
14 | },
15 | "contributors": [
16 | {
17 | "name": "Sindre Sorhus",
18 | "email": "sindresorhus@gmail.com",
19 | "url": "http://sindresorhus.com"
20 | }
21 | ],
22 | "engines": {
23 | "node": ">=0.12.0"
24 | },
25 | "scripts": {
26 | "test": "xo && nyc ava",
27 | "release": "standard-version"
28 | },
29 | "files": [
30 | "index.js"
31 | ],
32 | "keywords": [
33 | "exit",
34 | "quit",
35 | "process",
36 | "hook",
37 | "graceful",
38 | "handler",
39 | "shutdown",
40 | "sigterm",
41 | "sigint",
42 | "sighup",
43 | "pm2",
44 | "cluster",
45 | "child",
46 | "reload",
47 | "async",
48 | "terminate",
49 | "kill",
50 | "stop",
51 | "event"
52 | ],
53 | "devDependencies": {
54 | "ava": "^0.21.0",
55 | "coveralls": "^2.11.14",
56 | "nyc": "^10.3.2",
57 | "standard-version": "^4.2.0",
58 | "xo": "^0.18.2"
59 | },
60 | "ava": {
61 | "files": [
62 | "test/*.js",
63 | "!tests/cases/*"
64 | ]
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # async-exit-hook
2 | [](https://travis-ci.org/Tapppi/async-exit-hook)
3 | [](https://coveralls.io/github/Tapppi/async-exit-hook?branch=master)
4 |
5 | > Run some code when the process exits
6 |
7 | The `process.on('exit')` event doesn't catch all the ways a process can exit. This module catches:
8 |
9 | * process SIGINT, SIGTERM and SIGHUP, SIGBREAK signals
10 | * process beforeExit and exit events
11 | * PM2 clustering process shutdown message ([PM2 graceful reload](http://pm2.keymetrics.io/docs/usage/cluster-mode/#graceful-reload))
12 |
13 | Useful for cleaning up. You can also include async handlers, and add custom events to hook and exit on.
14 |
15 | Forked and pretty much rewritten from [exit-hook](https://npmjs.com/package/exit-hook).
16 |
17 |
18 | ## Install
19 |
20 | ```
21 | $ npm install --save async-exit-hook
22 | ```
23 |
24 | ## Usage
25 |
26 | ### Considerations and warning
27 | #### On `process.exit()` and asynchronous code
28 | **If you use asynchronous exit hooks, DO NOT use `process.exit()` to exit.
29 | The `exit` event DOES NOT support asynchronous code.**
30 | >['beforeExit' is not emitted for conditions causing explicit termination, such as process.exit()]
31 | (https://nodejs.org/api/process.html#process_event_beforeexit)
32 |
33 | #### Windows and `process.kill(signal)`
34 | On windows `process.kill(signal)` immediately kills the process, and does not fire signal events,
35 | and as such, cannot be used to gracefully exit. See *Clustering and child processes* for a
36 | workaround when killing child processes. I'm planning to support gracefully exiting
37 | with async support on windows soon.
38 |
39 | ### Clustering and child processes
40 | If you use custom clustering / child processes, you can gracefully shutdown your child process
41 | by sending a shutdown message (`childProc.send('shutdown')`).
42 |
43 | ### Example
44 | ```js
45 | const exitHook = require('async-exit-hook');
46 |
47 | exitHook(() => {
48 | console.log('exiting');
49 | });
50 |
51 | // you can add multiple hooks, even across files
52 | exitHook(() => {
53 | console.log('exiting 2');
54 | });
55 |
56 | // you can add async hooks by accepting a callback
57 | exitHook(callback => {
58 | setTimeout(() => {
59 | console.log('exiting 3');
60 | callback();
61 | }, 1000);
62 | });
63 |
64 | // You can hook uncaught errors with uncaughtExceptionHandler(), consequently adding
65 | // async support to uncaught errors (normally uncaught errors result in a synchronous exit).
66 | exitHook.uncaughtExceptionHandler(err => {
67 | console.error(err);
68 | });
69 |
70 | // You can hook unhandled rejections with unhandledRejectionHandler()
71 | exitHook.unhandledRejectionHandler(err => {
72 | console.error(err);
73 | });
74 |
75 | // You can add multiple uncaught error handlers
76 | // Add the second parameter (callback) to indicate async hooks
77 | exitHook.uncaughtExceptionHandler((err, callback) => {
78 | sendErrorToCloudOrWhatever(err) // Returns promise
79 | .then(() => {
80 | console.log('Sent err to cloud');
81 | })
82 | .catch(sendError => {
83 | console.error('Error sending to cloud: ', err.stack);
84 | })
85 | .then(() => callback);
86 | });
87 |
88 | // Add exit hooks for a signal or custom message:
89 |
90 | // Custom signal
91 | // Arguments are `signal, exitCode` (SIGBREAK is already handled, this is an example)
92 | exitHook.hookEvent('SIGBREAK', 21);
93 |
94 | // process event: `message` with a filter
95 | // filter gets all arguments passed to *handler*: `process.on(message, *handler*)`
96 | // Exits on process event `message` with msg `customShutdownMessage` only
97 | exitHook.hookEvent('message', 0, msg => msg !== 'customShutdownMessage');
98 |
99 | // All async hooks will work with uncaught errors when you have specified an uncaughtExceptionHandler
100 | throw new Error('awesome');
101 |
102 | //=> // Sync uncaughtExcpetion hooks called and retun
103 | //=> '[Error: awesome]'
104 | //=> // Sync hooks called and retun
105 | //=> 'exiting'
106 | //=> 'exiting 2'
107 | //=> // Async uncaughtException hooks return
108 | //=> 'Sent error to cloud'
109 | //=> // Sync uncaughtException hooks return
110 | //=> 'exiting 3'
111 | ```
112 |
113 |
114 | ## License
115 |
116 | MIT © Tapani Moilanen
117 | MIT © [Sindre Sorhus](http://sindresorhus.com)
118 |
--------------------------------------------------------------------------------
/test/cases/async-err.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const exitHook = require('./../../index');
3 | const stub = require('./stub');
4 |
5 | exitHook(cb => {
6 | setTimeout(() => {
7 | stub.called();
8 | cb();
9 | }, 50);
10 | stub.called();
11 | });
12 |
13 | exitHook(() => {
14 | stub.called();
15 | });
16 |
17 | exitHook.uncaughtExceptionHandler((err, cb) => {
18 | setTimeout(() => {
19 | stub.called();
20 | cb();
21 | }, 50);
22 | if (!err || err.message !== 'test') {
23 | stub.reject('No error passed to uncaughtExceptionHandler, or message not test - ');
24 | }
25 | stub.called();
26 | });
27 |
28 | process.on('uncaughtException', () => {
29 | // All uncaught exception handlers should be called even though the exit hook handler was registered
30 | stub.called();
31 | });
32 |
33 | stub.addCheck(6);
34 |
35 | throw new Error('test');
36 |
--------------------------------------------------------------------------------
/test/cases/async-exit-timeout.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const exitHook = require('./../../index');
3 | const stub = require('./stub');
4 |
5 | exitHook(cb => {
6 | setTimeout(() => {
7 | stub.called();
8 | cb();
9 | }, 2000);
10 | stub.called();
11 | });
12 |
13 | exitHook(() => {
14 | stub.called();
15 | });
16 |
17 | // eslint-disable-next-line handle-callback-err
18 | exitHook.uncaughtExceptionHandler((err, cb) => {
19 | setTimeout(() => {
20 | stub.called();
21 | cb();
22 | }, 2000);
23 | stub.called();
24 | });
25 |
26 | exitHook.forceExitTimeout(500);
27 | stub.addCheck(3);
28 |
29 | throw new Error('test');
30 |
--------------------------------------------------------------------------------
/test/cases/async.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const exitHook = require('./../../index');
3 | const stub = require('./stub');
4 |
5 | exitHook(cb => {
6 | setTimeout(() => {
7 | stub.called();
8 | cb();
9 | }, 50);
10 | stub.called();
11 | });
12 |
13 | exitHook(() => {
14 | stub.called();
15 | });
16 |
17 | stub.addCheck(3);
18 |
--------------------------------------------------------------------------------
/test/cases/stub.js:
--------------------------------------------------------------------------------
1 | // Stub to make sure that the required callbacks are called by exit-hook
2 | 'use strict';
3 | let c = 0;
4 | let noCallback = true;
5 |
6 | // Increment the called count
7 | exports.called = () => {
8 | c++;
9 | };
10 |
11 | // Exit with error
12 | exports.reject = (s, code) => {
13 | process.stdout.write('FAILURE: ' + s);
14 | // eslint-disable-next-line unicorn/no-process-exit
15 | process.exit(code === null || code === undefined ? 1 : code);
16 | };
17 |
18 | // Exit with success
19 | exports.done = () => {
20 | process.stdout.write('SUCCESS');
21 | // eslint-disable-next-line unicorn/no-process-exit
22 | process.exit(0);
23 | };
24 |
25 | // Add the exit check with a specific expected called count
26 | exports.addCheck = num => {
27 | noCallback = false;
28 |
29 | // Only call exit once, and save uncaught errors
30 | let called = false;
31 | let ucErrStr;
32 |
33 | // Save errors that do not start with 'test'
34 | process.on('uncaughtException', err => {
35 | if (err.message.indexOf('test') !== 0) {
36 | ucErrStr = err.stack;
37 | }
38 | });
39 | // Save rejections that do not start with 'test'
40 | process.on('unhandledRejection', reason => {
41 | if ((reason.message || reason).indexOf('test') !== 0) {
42 | ucErrStr = reason.message || reason;
43 | }
44 | });
45 |
46 | // Check that there were no unexpected errors and all callbacks were called
47 | function onExitCheck(timeout) {
48 | if (called) {
49 | return;
50 | }
51 | called = true;
52 |
53 | if (timeout) {
54 | exports.reject('Test timed out');
55 | } else if (ucErrStr) {
56 | exports.reject(ucErrStr);
57 | } else if (c === num) {
58 | exports.done();
59 | } else {
60 | exports.reject('Expected ' + num + ' callback calls, but ' + c + ' received');
61 | }
62 | }
63 |
64 | process.once('exit', onExitCheck.bind(null, null));
65 | setTimeout(onExitCheck.bind(null, true), 10000);
66 | };
67 |
68 | // If the check isn't added, throw on exit
69 | process.once('exit', () => {
70 | if (noCallback) {
71 | exports.reject('FAILURE, CHECK NOT ADDED');
72 | }
73 | });
74 |
--------------------------------------------------------------------------------
/test/cases/sync.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const stub = require('./stub');
3 | const exitHook = require('./../../index');
4 |
5 | exitHook(() => {
6 | stub.called();
7 | });
8 |
9 | exitHook(() => {
10 | stub.called();
11 | });
12 |
13 | process.on('exit', () => {
14 | stub.called();
15 | });
16 |
17 | stub.addCheck(3);
18 |
--------------------------------------------------------------------------------
/test/cases/unhandled-promise.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const exitHook = require('./../../index');
3 | const stub = require('./stub');
4 |
5 | exitHook(cb => {
6 | setTimeout(() => {
7 | stub.called();
8 | cb();
9 | }, 50);
10 | stub.called();
11 | });
12 |
13 | exitHook(() => {
14 | stub.called();
15 | });
16 |
17 | exitHook.unhandledRejectionHandler((err, cb) => {
18 | setTimeout(() => {
19 | stub.called();
20 | cb();
21 | }, 50);
22 | if (!err || err.message !== 'test-promise') {
23 | stub.reject(`No error passed to unhandledRejectionHandler, or message not test-promise - ${err.message}`);
24 | }
25 | stub.called();
26 | });
27 |
28 | process.on('unhandledRejection', () => {
29 | // All uncaught rejection handlers should be called even though the exit hook handler was registered
30 | stub.called();
31 | });
32 |
33 | stub.addCheck(6);
34 |
35 | (() => {
36 | return Promise.reject(new Error('test-promise'));
37 | })();
38 |
--------------------------------------------------------------------------------
/test/tests.js:
--------------------------------------------------------------------------------
1 | // Tests have to happen in a subprocess to test the exit functionality
2 | 'use strict';
3 |
4 | const fork = require('child_process').fork;
5 | const path = require('path');
6 |
7 | const test = require('ava');
8 |
9 | /**
10 | * Starts a test file in a subprocess, returns a promise that resolves with the subprocess
11 | * exit code and output in an array ([code, output])
12 | *
13 | * @async
14 | * @param {String} test Filename without path or extension
15 | * @param {String} signal Signal (or 'shutdown' for message) to send to the process
16 | * @return {Promise.<[Number, String]>} Array with the exit code and output of the subprocess
17 | */
18 | function testInSub(test, signal) {
19 | return new Promise(resolve => {
20 | const proc = fork(
21 | path.resolve(__dirname, './cases/' + test + '.js'),
22 | {
23 | env: process.env,
24 | silent: true
25 | }
26 | );
27 |
28 | let output = '';
29 |
30 | proc.stdout.on('data', data => {
31 | output += data.toString();
32 | });
33 |
34 | proc.stderr.on('data', data => {
35 | output += data.toString();
36 | });
37 |
38 | proc.on('exit', code => {
39 | resolve([code, output]);
40 | });
41 |
42 | if (signal === 'shutdown') {
43 | proc.send(signal);
44 | } else if (signal) {
45 | proc.kill(signal);
46 | }
47 | });
48 | }
49 |
50 | test('API: test adding and removing and listing hooks', t => {
51 | const exitHook = require('./../');
52 |
53 | t.plan(3);
54 |
55 | // Enable hooks
56 | exitHook(() => {});
57 |
58 | // Ensure SIGBREAK hook
59 | exitHook.hookEvent('SIGBREAK', 128 + 21);
60 | t.not(exitHook.hookedEvents().indexOf('SIGBREAK'), -1);
61 |
62 | // Unhook SIGBREAK
63 | exitHook.unhookEvent('SIGBREAK');
64 | t.is(exitHook.hookedEvents().indexOf('SIGBREAK'), -1);
65 |
66 | // Rehook SIGBREAK
67 | exitHook.hookEvent('SIGBREAK', 128 + 21);
68 | t.not(exitHook.hookedEvents().indexOf('SIGBREAK'), -1);
69 | });
70 |
71 | test('sync handlers', async t => {
72 | t.plan(2);
73 | const [code, output] = await testInSub('sync', 'shutdown');
74 |
75 | t.is(output, 'SUCCESS');
76 | t.is(code, 0);
77 | });
78 |
79 | test('async handlers', async t => {
80 | t.plan(2);
81 | const [code, output] = await testInSub('async', 'shutdown');
82 |
83 | t.is(output, 'SUCCESS');
84 | t.is(code, 0);
85 | });
86 |
87 | test('async uncaught exception handler', async t => {
88 | t.plan(2);
89 | const [code, output] = await testInSub('async-err');
90 |
91 | t.is(output, 'SUCCESS');
92 | t.is(code, 0);
93 | });
94 |
95 | test('async exit timeout', async t => {
96 | t.plan(2);
97 | const [code, output] = await testInSub('async-exit-timeout');
98 |
99 | t.is(output, 'SUCCESS');
100 | t.is(code, 0);
101 | });
102 |
103 | test('unhandled promise rejection', async t => {
104 | t.plan(2);
105 | const [code, output] = await testInSub('unhandled-promise');
106 |
107 | t.is(output, 'SUCCESS');
108 | t.is(code, 0);
109 | });
110 |
--------------------------------------------------------------------------------