add websocket server to this dir. fix stuff for client

This commit is contained in:
2024-10-30 15:12:52 -05:00
parent f8152c6db8
commit 4886bc5b1f
3058 changed files with 1201180 additions and 2 deletions

30
websocket_server/node_modules/node-cleanup/.npmignore generated vendored Normal file
View File

@ -0,0 +1,30 @@
# OS X files
.DS_Store
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules

21
websocket_server/node_modules/node-cleanup/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016 Joseph T. Lapp
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

203
websocket_server/node_modules/node-cleanup/README.md generated vendored Normal file
View File

@ -0,0 +1,203 @@
# node-cleanup
installs custom cleanup handlers that run on exiting node
## Installation
```
npm install node-cleanup --save
```
## Overview
`nodeCleanup()` installs functions that perform cleanup activities just before the node process exits. Let's call these functions "cleanup handlers." The cleanup handlers run under the following conditions:
- When the process exits normally (exit code 0).
- When the process exits due to an error, such as an uncaught exception (exit code 1).
- When the process receives one of the following POSIX signals: SIGINT (e.g. *Ctrl-C*), SIGHUP, SIGQUIT, or SIGTERM.
This solution has the following features:
- Allows cleanup handlers to behave as a function of exit code and signal.
- Allows multiple independent subsystems to install cleanup handlers.
- Allows for asynchronous cleanup on receiving a signal by postponing process termination.
- Allows for deferring to child processes the decision about whether a signal terminates the present process. For example, Emacs intercepts *Ctrl-C*, which should prevent its parent process from terminating.
- Allows for writing custom messages to `stderr` on SIGINT (e.g. *Ctrl-C*) and uncaught exceptions, regardless of the number of cleanup handlers installed.
- Allows for uninstalling all cleanup handlers, such as to change termination behavior after having intercepted and cleaned up for a signal.
The module also has an extensive test suite to help ensure reliability.
## Usage
Here is the typical way to use `nodeCleanup()`:
```js
var nodeCleanup = require('node-cleanup');
nodeCleanup(function (exitCode, signal) {
// release resources here before node exits
});
```
If you only want to install your own messages for *Ctrl-C* and uncaught exception (either or both), you can do this:
```js
nodeCleanup({
ctrl_C: "{^C}",
uncaughtException: "Uh oh. Look what happened:"
});
```
To get the default `stderr` messages, without installing a cleanup handler:
```js
nodeCleanup();
```
You may also combine these to install a cleanup handler and `stderr` messages:
```js
nodeCleanup(function (exitCode, signal) {
// release resources here before node exits
}, {
ctrl_C: "{^C}",
uncaughtException: "Uh oh. Look what happened:"
});
```
You may perform asynchronous cleanup upon receiving a signal, as follows:
```js
nodeCleanup(function (exitCode, signal) {
if (signal) {
unsavedData.save(function done() {
// calling process.exit() won't inform parent process of signal
process.kill(process.pid, signal);
});
nodeCleanup.uninstall(); // don't call cleanup handler again
return false;
}
});
```
When you hit *Ctrl-C*, you send a SIGINT signal to each process in the current process group. A process group is set of processes that are all supposed to end together as a group instead of persisting independently. However, some programs, such as Emacs, intercept and repurpose SIGINT so that it does not end the process. In such cases, SIGINT should not end any processes of the group. Here is how you can delegate the decision to terminate to a child process:
```js
var nodeCleanup = require('node-cleanup');
var fork = require('child_process').fork;
var child = fork('path-to-child-script.js');
child.on('exit', function (exitCode, signal) {
child = null; // enable the cleanup handler
if (signal === 'SIGINT')
process.kill(process.pid, 'SIGINT');
});
nodeCleanup(function (exitCode, signal) {
if (child !== null && signal === 'SIGINT')
return false; // don't exit yet
// release resources here before node exits
});
```
## Reference
### `nodeCleanup()`
`nodeCleanup()` has the following available ([FlowType](https://flowtype.org/docs/getting-started.html#_)) signatures:
```
function nodeCleanup(cleanupHandler: Function): void
function nodeCleanup(cleanupHandler: Function, stderrMessages: object): void
function nodeCleanup(stderrMessages: object): void
function nodeCleanup(): void
```
The 1st form installs a cleanup handler. The 2nd form also assigns messages to write to `stderr` on SIGINT or an uncaught exception. The 3rd and 4th forms only assign messages to write to `stderr`, without installing a cleanup handler. The 4th form assigns default `stderr` messages.
`cleanupHandler` is a cleanup handler callback and is described in its own section below. When no cleanup handlers are installed, termination events all result in the process terminating, including signal events.
`stderrMessages` is an object mapping any of the keys `ctrl_C` and `uncaughtException` to message strings that output to `stderr`. Set a message to the empty string `''` inhibit a previously-assigned message.
`nodeCleanup()` may be called multiple times to install multiple cleanup handlers or override previous messages. Each handler gets called on each signal or termination condition. The most recently assigned messages apply.
### `nodeCleanup.uninstall()`
`nodeCleanup.uninstall()` uninstalls all installed cleanup handlers and voids the `stderr` message assignments. It may be called multiple times without harm.
This function is primarily useful when a signal occurs and the cleanup handler performs cleanup but disables immediate process termination. In this case, when it is finally time to terminate the process, the cleanup handlers shouldn't run again, so the process uninstalls the handlers before terminating itself.
### Cleanup Handlers
Each cleanup handler has the following ([FlowType](https://flowtype.org/docs/getting-started.html#_)) signature:
```
function cleanupHandler(exitCode: number|null, signal: string|null): boolean?
```
If the process is terminating for a reason other than a POSIX signal, `exitCode` is the exit code, and `signal` is null. Otherwise, if the process received a signal, `signal` is the signal's string name, and `exitCode` is null. These are the arguments passed to a [child process `exit` event](https://nodejs.org/api/child_process.html#child_process_event_exit) handler, mirrored here in `node-cleanup` for consistency.
Node.js defines [these standard exit codes](https://nodejs.org/api/process.html#process_exit_codes), but it does not appear to use code values >128 for signals. According to the node.js docs, [these are the possible signals](http://man7.org/linux/man-pages/man7/signal.7.html), but the cleanup handlers only run on SIGINT (e.g. *Ctrl-C*), SIGHUP, SIGQUIT, or SIGTERM. (It is not possible to intercept SIGKILL.)
The return value of a cleanup handler is only significant for signals. If any cleanup handler returns a boolean `false`, the process does not exit. If they all return `true` (or for backwards compatibility, no return value), the process exits, reporting the signal to the parent process as the reason for the exit. The process always exits after calling the cleanup handlers for non-signals.
When a cleanup handler returns `false` to prevent the process from exiting, the cleanup handler normally takes steps to ensure proper termination later. For example, the process may wait for asynchronous cleanup to complete, or it may wait for a child process to signal termination. Normally in these cases the process would use `nodeCleanup.uninstall()` to uninstall the cleanup handlers prior to the second termination to prevent them from running again.
A cleanup handler should never call `process.exit()`. If a handler prevents a signal from terminating the process but later wishes to terminate the process for reason of this signal, the process should call `process.kill(process.pid, signal)`. In particular, the process should **not** call `process.exit(128 + signalNumber)`, because while this does communicate the exit code to the parent process, it does not communicate the exit signal by the means that the [node.js `child_process` expects](https://nodejs.org/api/child_process.html#child_process_event_exit).
## Testing
This module includes an extensive test suite. You can run it from the module directory with either the [`tap`](http://www.node-tap.org/basics/) or [`subtap`](https://github.com/jtlapp/subtap) test runner, as follows:
```
npm install -g tap
npm install
tap tests/*.js
```
or
```
npm install -g subtap
npm install
subtap
```
(As of this writing, the test suite has only been run on a Mac. Behavior may vary from OS to OS, so I'm looking for feedback from other operating systems.)
## Incompatibilities with v1.0.x
`node-cleanup` v2+ is not fully compatible with v1.x. You may need to change your usage to upgrade. These are the potential incompatibilities:
- The cleanup handlers now also run on SIGHUP, SIGQUIT, and SIGTERM, which were not getting cleanup processing before.
- `stderr` messages are handled quite differently. Previously, there were defaults that you had to override, and only your first message assignments applied. Now, the defaults **only** install with the parameterless call `nodeCleanup()`. Otherwise there are no messages unless you provide them. Moreover, the most recent message assignments are the ones that get used.
## Acknowledgements
This module began by borrowing and modifying code from CanyonCasa's [answer to a stackoverflow question](http://stackoverflow.com/a/21947851/650894). I had found the code necessary for all my node projects. @Banjocat piped in with a [comment](http://stackoverflow.com/questions/14031763/doing-a-cleanup-action-just-before-node-js-exits/21947851#comment68567869_21947851) about how the solution didn't properly handle SIGINT. (See [this detailed explanation](https://www.cons.org/cracauer/sigint.html) of the SIGINT problem). I have completely rewritten the module to properly deal with SIGINT and other signals (I hope!). The rewrite also provides some additional flexibility that @zixia and I found ourselves needing for our respective projects.
## License
*This license applies to v2 and later. v1 derived from [this stackoverflow answer](http://stackoverflow.com/a/21947851/650894).*
MIT License
Copyright (c) 2016 Joseph T. Lapp
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,148 @@
/******************************************************************************
nodeCleanup() installs functions -- cleanup handlers -- that perform cleanup activities just before the node process exits, except on SIGKILL, which can't be intercepted. nodeCleanup() can also install messages to be written to stderr on either SIGINT or an uncaught exception.
Each cleanup handler has the following (FlowType) signature:
cleanupHandler(exitCode: number|null, signal: string|null): boolean?
If the process is terminating for a reason other than a signal, exitCode is an integer that provides the reason for termination, and signal is null. If the process received a POSIX signal, signal is the signal's string name, and exitCode is null. These are also the arguments passed to a process' `exit` event handler, mirrored in node-cleanup for consistency.
The process terminates after cleanup, except possibly on signals. If any cleanup handler returns a boolean false for a signal, the process will not exit; otherwise the process exits. SIGKILL cannot be intercepted.
Install a cleanup handler as follows:
var nodeCleanup = require('node-cleanup');
nodeCleanup(cleanupHandler, stderrMessages);
Or to only install stderr messages:
nodeCleanup(stderrMessages);
Or to install the default stderr messages:
nodeCleanup();
nodeCleanup() may be called multiple times to install multiple cleanup handlers. However, only the most recently installed stderr messages get used. The messages available are ctrl_C and uncaughtException.
The following uninstalls all cleanup handlers and may be called multiple times in succession:
nodeCleanup.uninstall();
This module has its origin in code by @CanyonCasa at http://stackoverflow.com/a/21947851/650894, but the module was significantly rewritten to resolve issues raised by @Banjocat at http://stackoverflow.com/questions/14031763/doing-a-cleanup-action-just-before-node-js-exits#comment68567869_21947851. It has also been extended for greater configurability.
******************************************************************************/
//// CONSTANTS ////////////////////////////////////////////////////////////////
var DEFAULT_MESSAGES = {
ctrl_C: '[ctrl-C]',
uncaughtException: 'Uncaught exception...'
};
//// CONFIGURATION ////////////////////////////////////////////////////////////
var cleanupHandlers = null; // array of cleanup handlers to call
var messages = null; // messages to write to stderr
var sigintHandler; // POSIX signal handlers
var sighupHandler;
var sigquitHandler;
var sigtermHandler;
//// HANDLERS /////////////////////////////////////////////////////////////////
function signalHandler(signal)
{
var exit = true;
cleanupHandlers.forEach(function (cleanup) {
if (cleanup(null, signal) === false)
exit = false;
});
if (exit) {
if (signal === 'SIGINT' && messages && messages.ctrl_C !== '')
process.stderr.write(messages.ctrl_C + "\n");
uninstall(); // don't cleanup again
// necessary to communicate the signal to the parent process
process.kill(process.pid, signal);
}
}
function exceptionHandler(e)
{
if (messages && messages.uncaughtException !== '')
process.stderr.write(messages.uncaughtException + "\n");
process.stderr.write(e.stack + "\n");
process.exit(1); // will call exitHandler() for cleanup
}
function exitHandler(exitCode, signal)
{
cleanupHandlers.forEach(function (cleanup) {
cleanup(exitCode, signal);
});
}
//// MAIN /////////////////////////////////////////////////////////////////////
function install(cleanupHandler, stderrMessages)
{
if (cleanupHandler) {
if (typeof cleanupHandler === 'object') {
stderrMessages = cleanupHandler;
cleanupHandler = null;
}
}
else if (!stderrMessages)
stderrMessages = DEFAULT_MESSAGES;
if (stderrMessages) {
if (messages === null)
messages = { ctrl_C: '', uncaughtException: '' };
if (typeof stderrMessages.ctrl_C === 'string')
messages.ctrl_C = stderrMessages.ctrl_C;
if (typeof stderrMessages.uncaughtException === 'string')
messages.uncaughtException = stderrMessages.uncaughtException;
}
if (cleanupHandlers === null) {
cleanupHandlers = []; // establish before installing handlers
sigintHandler = signalHandler.bind(this, 'SIGINT');
sighupHandler = signalHandler.bind(this, 'SIGHUP');
sigquitHandler = signalHandler.bind(this, 'SIGQUIT');
sigtermHandler = signalHandler.bind(this, 'SIGTERM');
process.on('SIGINT', sigintHandler);
process.on('SIGHUP', sighupHandler);
process.on('SIGQUIT', sigquitHandler);
process.on('SIGTERM', sigtermHandler);
process.on('uncaughtException', exceptionHandler);
process.on('exit', exitHandler);
cleanupHandlers.push(cleanupHandler || noCleanup);
}
else if (cleanupHandler)
cleanupHandlers.push(cleanupHandler);
}
function uninstall()
{
if (cleanupHandlers !== null) {
process.removeListener('SIGINT', sigintHandler);
process.removeListener('SIGHUP', sighupHandler);
process.removeListener('SIGQUIT', sigquitHandler);
process.removeListener('SIGTERM', sigtermHandler);
process.removeListener('uncaughtException', exceptionHandler);
process.removeListener('exit', exitHandler);
cleanupHandlers = null; // null only after uninstalling
}
}
function noCleanup()
{
return true; // signals will always terminate process
}
//// EXPORTS //////////////////////////////////////////////////////////////////
module.exports = install;
install.uninstall = uninstall;

View File

@ -0,0 +1,41 @@
{
"name": "node-cleanup",
"version": "2.1.2",
"description": "installs custom cleanup handlers that run on exiting node",
"main": "node-cleanup.js",
"scripts": {
"test": "tap tests/*.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/jtlapp/node-cleanup.git"
},
"keywords": [
"node",
"exit",
"cleanup",
"sigint",
"ctrl-c"
],
"author": "",
"contributors": [
{
"name": "CanyonCasa",
"url": "http://stackoverflow.com/users/3319552/canyoncasa"
},
{
"name": "Joseph T. Lapp",
"email": "arachnojoe@gmail.com",
"url": "http://josephtlapp.com"
}
],
"bugs": {
"url": "https://github.com/jtlapp/node-cleanup/issues"
},
"homepage": "https://github.com/jtlapp/node-cleanup#readme",
"license": "MIT",
"devDependencies": {
"memory-streams": "^0.1.0",
"tap": "^8.0.1"
}
}

View File

@ -0,0 +1,23 @@
#!/usr/bin/env node
/******************************************************************************
Grandchild process used to test process group signals. It conditionally ignores SIGINTs in order to emulate programs that catch and discard them.
******************************************************************************/
var heedSignal = (process.argv[2] === 'true');
var waitMillis = parseInt(process.argv[3]);
if (!heedSignal) {
process.on('SIGINT', function () {
// ignore it
});
}
setTimeout(function () {
// disconnect IPC so can exit when stdout, stderr,
// child processes, and other resources complete.
process.disconnect();
process.exit(0);
}, waitMillis);
process.send('ready');

View File

@ -0,0 +1,92 @@
#!/usr/bin/env node
/******************************************************************************
Child process that installs node-cleanup for testing in situations that may entail forking a grandchild process in the same process group. It accepts a single argument of serialized JSON having the following (FlowType) structure:
{
grandchild: boolean; // whether to fork a grandchild process
grandchildHeedsSIGINT: boolean; // whether grandchild heeds SIGINT
messages: object|null; // messages argument for nodeCleanup()
exception: boolean; // whether to throw an uncaught exception
skipTermination: boolean; // true => cleanup handler returns false
exitReturn: 'true'|'undefined'; // value that cleanup handler should
// return to indicate whether the process should exit
maxDuration: number; // max duration of process in millisecs
}
exitReturn indicates the type of value that the cleanup handler returns to indicate whether the process should exit on SIGINT. Ideally, the handler would always return a boolean, but for backwards compatibility, for compatibility with the Stackoverflow solution, and to allow programmers some room for laziness when there are no child processes, an undefined return value also indicates 'true'.
This process implements the WCE solution described for SIGINT at https://www.cons.org/cracauer/sigint.html. That is, the process ignores a SIGINT received while a nested process is running, but sends a SIGINT to itself after a nested process terminates with SIGINT. SIGQUIT is not handled this way because it is not expected that child processes would override and ignore it.
The process writes behavioral results to stdout for comparison with expectations. The output includes space-delimited strings from the following list, ordered in the string by their order of occurrence:
cleanup - child's cleanup handler was called and performed cleanup
skipped_cleanup - child's cleanup handler was called for SIGINT but
did not perform cleanup because a child was running.
grandchild=<reason> - grandchild exited for the given reason, which is
either an integer exit code or the string name of a signal
The process also writes to stderr for comparison with expectations.
******************************************************************************/
//// MODULES //////////////////////////////////////////////////////////////////
var path = require('path');
var fork = require('child_process').fork;
var nodeCleanup = require('../../');
//// CONFIGURATION ////////////////////////////////////////////////////////////
var config = JSON.parse(process.argv[2]);
var grandchildFile = path.resolve(__dirname, "./grandchild.js");
var grandchildMaxDuration = Math.round(config.maxDuration*0.5);
//// STATE ////////////////////////////////////////////////////////////////////
var grandchild = null;
//// MAIN /////////////////////////////////////////////////////////////////////
nodeCleanup(function (exitCode, signal) {
var reason = (exitCode !== null ? exitCode : signal);
if (grandchild !== null && reason === 'SIGINT') {
process.stdout.write('skipped_cleanup ');
return false;
}
process.stdout.write('cleanup ');
if (config.skipTermination) {
nodeCleanup.uninstall(); // don't cleanup again
return false;
}
if (config.exitReturn === 'true')
return true;
}, config.messages);
setTimeout(function () {
// disconnect IPC so can exit when stdout, stderr,
// child processes, and other resources complete.
process.disconnect();
if (config.exception)
throw new Error("unexpected exception");
process.exit(0);
}, config.maxDuration);
if (config.grandchild) {
grandchild = fork(grandchildFile, [
config.grandchildHeedsSIGINT,
grandchildMaxDuration
]);
grandchild.on('message', function (msg) {
if (msg === 'ready')
process.send('ready');
});
grandchild.on('exit', function (exitCode, signal) {
grandchild = null; // allow process to heed forthcoming SIGINT
process.stdout.write('grandchild='+
(exitCode !== null ? exitCode : signal) +' ');
if (signal === 'SIGINT')
process.kill(process.pid, signal);
});
}
else
process.send('ready');

View File

@ -0,0 +1,75 @@
#!/usr/bin/env node
/******************************************************************************
Child process that installs node-cleanup for testing zero, one, or multiple (two) concurrent cleanup handlers. It accepts a single argument of serialized JSON having the following (FlowType) structure:
{
handlers; number; // 0, 1, or 2 concurrent cleanup handlers
messages0: object|null; // messages argument for no-cleanup call, if any
messages1: object|null; // messages argument for 1st nodeCleanup() call
messages2: object|null; // messages argument for 2nd nodeCleanup() call
return1: boolean; // return value of 1st cleanup handler
return2: boolean; // return value of 2nd cleanup handler
uninstall: boolean; // whether to uninstall handlers before test
maxDuration: number; // max duration of process in millisecs
}
The process writes behavioral results to stdout for comparison with expectations. The output includes space-delimited strings from the following list, ordered in the string by their order of occurrence:
cleanup1 - the first cleanup handler ran
cleanup2 - the second cleanup handler ran
The process also writes to stderr for comparison with expectations.
******************************************************************************/
//// MODULES //////////////////////////////////////////////////////////////////
var nodeCleanup = require('../../');
//// CONFIGURATION ////////////////////////////////////////////////////////////
var config = JSON.parse(process.argv[2]);
//// CLEANUP HANDLERS /////////////////////////////////////////////////////////
function cleanup1(exitCode, signal) {
process.stdout.write('cleanup1 ');
if (!config.return1)
nodeCleanup.uninstall(); // don't cleanup again
return config.return1;
}
function cleanup2(exitCode, signal) {
process.stdout.write('cleanup2 ');
if (!config.return2)
nodeCleanup.uninstall(); // don't cleanup again
return config.return2;
}
//// MAIN /////////////////////////////////////////////////////////////////////
if (config.handlers === 0) {
if (config.messages0)
nodeCleanup(config.messages0);
else
nodeCleanup();
}
else {
nodeCleanup(cleanup1, config.messages1);
if (config.handlers > 1)
nodeCleanup(cleanup2, config.messages2);
}
if (config.uninstall)
nodeCleanup.uninstall();
setTimeout(function () {
// disconnect IPC so can exit when stdout, stderr,
// child processes, and other resources complete.
process.disconnect();
if (config.exception)
throw new Error("unexpected exception");
process.exit(0);
}, config.maxDuration);
process.send('ready');

View File

@ -0,0 +1,88 @@
/******************************************************************************
Library of functions for running child processes.
******************************************************************************/
//// MODULES //////////////////////////////////////////////////////////////////
var MemoryStream = require('memory-streams').WritableStream;
var path = require('path');
var spawn = require('child_process').spawn;
//// PUBLIC CONSTANTS /////////////////////////////////////////////////////////
exports.DEFAULT_SIGINT_OUT = "[ctrl-C]\n";
exports.DEFAULT_EXCEPTION_OUT = /^Uncaught exception\.\.\./;
//// PRIVATE CONSTANTS ////////////////////////////////////////////////////////
var MAX_DURATION = 250; // max duration of child process in millisecs
var childEnv = {};
Object.keys(process.env).forEach(function (key) {
childEnv[key] = process.env[key];
});
var childOptions = {
env: childEnv,
stdio: ['inherit', 'pipe', 'pipe', 'ipc'],
detached: true
};
//// FUNCTIONS ////////////////////////////////////////////////////////////////
/**
* Launch a child process that uses nodeCleanup(), performing an action once the child is running, and provide the results of the run when the child exits. The child runs in a new process group, separate from the test process, so that the caller may signal the group as a whole.
*
* @param config Child process configuration. See the description of this structure in the comments of tests/bin/child.js.
* @param action A callback function(childPID) that may send signals to the child or the child's process group (-childPID). Must be synchronous.
* @param done A callback function(reason, stdoutString, stderrString) providing the output of the child. This callback should test this output against expectations. reason is the exit code, unless the process was terminated by a signal, in which case it is the string name of the signal.
*/
exports.launch = function (config, action, done)
{
config.maxDuration = MAX_DURATION;
var childPath = path.resolve(__dirname, "../bin/"+ config.child +".js");
var childArgs = [ childPath, JSON.stringify(config) ];
var child = spawn(process.execPath, childArgs, childOptions);
var stdoutStream = new MemoryStream();
var stderrStream = new MemoryStream();
child.stdout.pipe(stdoutStream);
child.stderr.pipe(stderrStream);
child.on('message', function (msg) {
if (msg === 'ready')
action(child.pid);
});
child.on('exit', function (exitCode, signal) {
done((exitCode !== null ? exitCode : signal),
stdoutStream.toString(), stderrStream.toString());
});
};
/**
* Shorthand function for launching a process, optionally sending a signal to it, and testing the resulting output. It calls t.equal() or t.match() on each of the expected results, depending on whether it is a string or a regular expression, and then t.end() to complete the test.
*
* @param t tap test instance
* @param config See launch() config.
* @param action See launch() action.
* @param expectedResults A structure of the form {exitCode, stdout, stderr} containing the expected output of the test. exitCode is an integer. stdout and stderr are strings or regular expressions. stdout is compared against the result trimmed of preceding and trailing whitespace. When stdout or stderr is a string, it is tested for being identical with the actual result.
*/
exports.test = function (t, config, action, expectedResults)
{
exports.launch(config, action, function (reason, stdout, stderr) {
t.equal(reason, expectedResults.exitReason, "exit reason");
stdout = stdout.trim();
if (typeof expectedResults.stdout === 'string')
t.equal(stdout, expectedResults.stdout, "stdout");
else
t.match(stdout, expectedResults.stdout, "stdout");
if (typeof expectedResults.stderr === 'string')
t.equal(stderr, expectedResults.stderr, "stderr");
else
t.match(stderr, expectedResults.stderr, "stderr");
t.end();
});
};

View File

@ -0,0 +1,358 @@
// tests in which spawned child installs multiple cleanup handlers
var t = require('tap');
var lib = require('./lib/library');
t.test("multiple handlers: normal exit", function (t) {
lib.test(t, {
child: 'stackable',
handlers: 2,
messages1: null,
messages2: null,
return1: true,
return2: true,
exception: false,
uninstall: false
}, function (childPID) {
// no signal
}, {
exitReason: 0,
stdout: "cleanup1 cleanup2",
stderr: ""
});
});
t.test("multiple handlers: uncaught exception - custom messages", function (t) {
lib.test(t, {
child: 'stackable',
handlers: 2,
messages1: {
uncaughtException: "Not the surprise you're looking for."
},
messages2: {
uncaughtException: "Look! A surprise!"
},
return1: true,
return2: true,
exception: true,
uninstall: false
}, function (childPID) {
// no signal
}, {
exitReason: 1,
stdout: "cleanup1 cleanup2",
stderr: /^Look! A surprise!/
});
});
t.test("multiple handlers: uncaught exception - removed message",
function (t) {
lib.test(t, {
child: 'stackable',
handlers: 2,
messages1: {
uncaughtException: "Not the surprise you're looking for."
},
messages2: {
uncaughtException: ""
},
return1: true,
return2: true,
exception: true,
uninstall: false
}, function (childPID) {
// no signal
}, {
exitReason: 1,
stdout: "cleanup1 cleanup2",
stderr: /tests[\/\\]bin[\/\\]stackable.js/
});
}
);
t.test("multiple handlers: uncaught exception - added message",
function (t) {
lib.test(t, {
child: 'stackable',
handlers: 2,
messages1: {
ctrl_C: "{^C}}"
},
messages2: {
uncaughtException: "Oops!"
},
return1: true,
return2: true,
exception: true,
uninstall: false
}, function (childPID) {
// no signal
}, {
exitReason: 1,
stdout: "cleanup1 cleanup2",
stderr: /^Oops!/
});
}
);
t.test("multiple handlers: uncaught exception - no message", function (t) {
lib.test(t, {
child: 'stackable',
handlers: 2,
messages1: null,
messages2: null,
return1: true,
return2: true,
exception: true,
uninstall: false
}, function (childPID) {
// no signal
}, {
exitReason: 1,
stdout: "cleanup1 cleanup2",
stderr: /tests[\/\\]bin[\/\\]stackable.js/
});
});
t.test("multiple handlers: child SIGINT - both heeded, custom messages",
function (t) {
lib.test(t, {
child: 'stackable',
handlers: 2,
messages1: {
ctrl_C: "{^C1}"
},
messages2: {
ctrl_C: "{^C2}"
},
return1: true,
return2: true,
exception: false,
uninstall: false
}, function (childPID) {
process.kill(childPID, 'SIGINT');
}, {
exitReason: 'SIGINT',
stdout: "cleanup1 cleanup2",
stderr: "{^C2}\n"
});
}
);
t.test("multiple handlers: child SIGINT - first heeded, custom messages",
function (t) {
lib.test(t, {
child: 'stackable',
handlers: 2,
messages1: {
ctrl_C: "{^C1}"
},
messages2: {
ctrl_C: "{^C2}"
},
return1: true,
return2: false,
exception: false,
uninstall: false
}, function (childPID) {
process.kill(childPID, 'SIGINT');
}, {
exitReason: 0,
stdout: "cleanup1 cleanup2",
stderr: ""
});
}
);
t.test("multiple handlers: child SIGINT - second heeded, custom messages",
function (t) {
lib.test(t, {
child: 'stackable',
handlers: 2,
messages1: {
ctrl_C: "{^C1}"
},
messages2: {
ctrl_C: "{^C2}"
},
return1: false,
return2: true,
exception: false,
uninstall: false
}, function (childPID) {
process.kill(childPID, 'SIGINT');
}, {
exitReason: 0,
stdout: "cleanup1 cleanup2",
stderr: ""
});
}
);
t.test("multiple handlers: child SIGINT - removed message", function (t) {
lib.test(t, {
child: 'stackable',
handlers: 2,
messages1: {
ctrl_C: "{^C1}"
},
messages2: {
ctrl_C: ""
},
return1: true,
return2: true,
exception: false,
uninstall: false
}, function (childPID) {
process.kill(childPID, 'SIGINT');
}, {
exitReason: 'SIGINT',
stdout: "cleanup1 cleanup2",
stderr: ""
});
});
t.test("multiple handlers: child SIGINT - added message", function (t) {
lib.test(t, {
child: 'stackable',
handlers: 2,
messages1: {
uncaughtException: "Oops!"
},
messages2: {
ctrl_C: "{^C1}"
},
return1: true,
return2: true,
exception: false,
uninstall: false
}, function (childPID) {
process.kill(childPID, 'SIGINT');
}, {
exitReason: 'SIGINT',
stdout: "cleanup1 cleanup2",
stderr: "{^C1}\n"
});
});
t.test("multiple handlers: child SIGQUIT", function (t) {
lib.test(t, {
child: 'stackable',
handlers: 2,
messages1: null,
messages2: null,
return1: true,
return2: true,
exception: false,
uninstall: false
}, function (childPID) {
process.kill(childPID, 'SIGQUIT');
}, {
exitReason: 'SIGQUIT',
stdout: "cleanup1 cleanup2",
stderr: ""
});
});
t.test("multiple handlers: child SIGTERM", function (t) {
lib.test(t, {
child: 'stackable',
handlers: 2,
messages1: null,
messages2: null,
return1: true,
return2: true,
exception: false,
uninstall: false
}, function (childPID) {
process.kill(childPID, 'SIGTERM');
}, {
exitReason: 'SIGTERM',
stdout: "cleanup1 cleanup2",
stderr: ""
});
});
t.test("multiple handlers: child SIGKILL", function (t) {
lib.test(t, {
child: 'stackable',
handlers: 2,
messages1: null,
messages2: null,
return1: true,
return2: true,
exception: false,
uninstall: false
}, function (childPID) {
process.kill(childPID, 'SIGKILL');
}, {
exitReason: 'SIGKILL',
stdout: "",
stderr: ""
});
});
t.test("multiple handlers/uninstall: normal child exit", function (t) {
lib.test(t, {
child: 'stackable',
handlers: 2,
messages1: null,
messages2: null,
return1: true,
return2: true,
exception: false,
uninstall: true
}, function (childPID) {
// no signal
}, {
exitReason: 0,
stdout: "",
stderr: ""
});
});
t.test("multiple handlers/uninstall: uncaught exception", function (t) {
lib.test(t, {
child: 'stackable',
handlers: 2,
messages1: {
uncaughtException: "Shouldn't show."
},
messages2: {
uncaughtException: "Also shouldn't show."
},
return1: true,
return2: true,
exception: true,
uninstall: true
}, function (childPID) {
// no signal
}, {
exitReason: 1,
stdout: "",
stderr: /tests[\/\\]bin[\/\\]stackable.js/
});
});
t.test("multiple handlers/uninstall: child SIGINT", function (t) {
lib.test(t, {
child: 'stackable',
handlers: 2,
messages1: {
ctrl_C: "{^C1}"
},
messages2: {
ctrl_C: "{^C2}"
},
return1: true,
return2: true,
exception: false,
uninstall: true
}, function (childPID) {
process.kill(childPID, 'SIGINT');
}, {
exitReason: 'SIGINT',
stdout: "",
stderr: ""
});
});

View File

@ -0,0 +1,175 @@
// tests in which spawned child uses the default cleanup handler
var t = require('tap');
var lib = require('./lib/library');
t.test("nocleanup: normal child exit", function (t) {
lib.test(t, {
child: 'stackable',
handlers: 0,
exception: false,
uninstall: false
}, function (childPID) {
// no signal
}, {
exitReason: 0,
stdout: "",
stderr: ""
});
});
t.test("nocleanup: uncaught exception - default message", function (t) {
lib.test(t, {
child: 'stackable',
handlers: 0,
exception: true,
uninstall: false
}, function (childPID) {
// no signal
}, {
exitReason: 1,
stdout: "",
stderr: lib.DEFAULT_EXCEPTION_OUT
});
});
t.test("nocleanup: uncaught exception - custom message", function (t) {
lib.test(t, {
child: 'stackable',
handlers: 0,
messages0: {
uncaughtException: "Yikes!"
},
exception: true,
uninstall: false
}, function (childPID) {
// no signal
}, {
exitReason: 1,
stdout: "",
stderr: /^Yikes!/
});
});
t.test("nocleanup: child SIGINT - default message", function (t) {
lib.test(t, {
child: 'stackable',
handlers: 0,
exception: false,
uninstall: false
}, function (childPID) {
process.kill(childPID, 'SIGINT');
}, {
exitReason: 'SIGINT',
stdout: "",
stderr: lib.DEFAULT_SIGINT_OUT
});
});
t.test("nocleanup: child SIGINT - custom message", function (t) {
lib.test(t, {
child: 'stackable',
handlers: 0,
messages0: {
ctrl_C: "{^C}"
},
exception: false,
uninstall: false
}, function (childPID) {
process.kill(childPID, 'SIGINT');
}, {
exitReason: 'SIGINT',
stdout: "",
stderr: "{^C}\n"
});
});
t.test("nocleanup: child SIGQUIT", function (t) {
lib.test(t, {
child: 'stackable',
handlers: 0,
exception: false,
uninstall: false
}, function (childPID) {
process.kill(childPID, 'SIGQUIT');
}, {
exitReason: 'SIGQUIT',
stdout: "",
stderr: ""
});
});
t.test("nocleanup: child SIGTERM", function (t) {
lib.test(t, {
child: 'stackable',
handlers: 0,
exception: false,
uninstall: false
}, function (childPID) {
process.kill(childPID, 'SIGTERM');
}, {
exitReason: 'SIGTERM',
stdout: "",
stderr: ""
});
});
t.test("nocleanup: child SIGKILL", function (t) {
lib.test(t, {
child: 'stackable',
handlers: 0,
exception: false,
uninstall: false
}, function (childPID) {
process.kill(childPID, 'SIGKILL');
}, {
exitReason: 'SIGKILL',
stdout: "",
stderr: ""
});
});
t.test("nocleanup/uninstall: normal child exit", function (t) {
lib.test(t, {
child: 'stackable',
handlers: 0,
exception: false,
uninstall: true
}, function (childPID) {
// no signal
}, {
exitReason: 0,
stdout: "",
stderr: ""
});
});
t.test("nocleanup/uninstall: uncaught exception", function (t) {
lib.test(t, {
child: 'stackable',
handlers: 0,
exception: true,
uninstall: true
}, function (childPID) {
// no signal
}, {
exitReason: 1,
stdout: "",
stderr: /tests[\/\\]bin[\/\\]stackable.js/
});
});
t.test("nocleanup/uninstall: child SIGINT", function (t) {
lib.test(t, {
child: 'stackable',
handlers: 0,
exception: false,
uninstall: true
}, function (childPID) {
process.kill(childPID, 'SIGINT');
}, {
exitReason: 'SIGINT',
stdout: "",
stderr: ""
});
});

View File

@ -0,0 +1,399 @@
// tests in which spawned child installs a custom cleanup handler
var t = require('tap');
var lib = require('./lib/library');
t.test("single: normal child exit - true return", function (t) {
lib.test(t, {
child: 'groupable',
grandchild: false,
grandchildHeedsSIGINT: false,
messages: null,
exception: false,
skipTermination: false,
exitReturn: 'true'
}, function (childPID) {
// no signal
}, {
exitReason: 0,
stdout: "cleanup",
stderr: ""
});
});
t.test("single: normal child exit - undefined return", function (t) {
lib.test(t, {
child: 'groupable',
grandchild: false,
grandchildHeedsSIGINT: false,
messages: null,
exception: false,
skipTermination: false,
exitReturn: 'undefined'
}, function (childPID) {
// no signal
}, {
exitReason: 0,
stdout: "cleanup",
stderr: ""
});
});
t.test("single: normal grandchild exit", function (t) {
lib.test(t, {
child: 'groupable',
grandchild: true,
grandchildHeedsSIGINT: false,
messages: null,
exception: false,
skipTermination: false,
exitReturn: 'true'
}, function (childPID) {
// no signal
}, {
exitReason: 0,
stdout: "grandchild=0 cleanup",
stderr: ""
});
});
t.test("single: uncaught exception - custom message", function (t) {
lib.test(t, {
child: 'groupable',
grandchild: false,
grandchildHeedsSIGINT: false,
messages: {
uncaughtException: "Oh gosh look what happened:"
},
exception: true,
skipTermination: false,
exitReturn: 'true'
}, function (childPID) {
// no signal
}, {
exitReason: 1,
stdout: "cleanup",
stderr: /^Oh gosh look what happened:/
});
});
t.test("single: uncaught exception - no message", function (t) {
lib.test(t, {
child: 'groupable',
grandchild: false,
grandchildHeedsSIGINT: false,
messages: {
uncaughtException: ""
},
exception: true,
skipTermination: false,
exitReturn: 'true'
}, function (childPID) {
// no signal
}, {
exitReason: 1,
stdout: "cleanup",
stderr: /^Error: unexpected exception/
});
});
t.test("single: child SIGINT - true return, custom message", function (t) {
lib.test(t, {
child: 'groupable',
grandchild: false,
grandchildHeedsSIGINT: false,
messages: {
ctrl_C: "{^C}"
},
exception: false,
skipTermination: false,
exitReturn: 'true'
}, function (childPID) {
process.kill(childPID, 'SIGINT');
}, {
exitReason: 'SIGINT',
stdout: "cleanup",
stderr: "{^C}\n"
});
});
t.test("single: child SIGINT - undefined return, custom message",
function (t) {
lib.test(t, {
child: 'groupable',
grandchild: false,
grandchildHeedsSIGINT: false,
messages: {
ctrl_C: "{^C}"
},
exception: false,
skipTermination: false,
exitReturn: 'undefined'
}, function (childPID) {
process.kill(childPID, 'SIGINT');
}, {
exitReason: 'SIGINT',
stdout: "cleanup",
stderr: "{^C}\n"
});
}
);
t.test("single: child SIGINT - no message", function (t) {
lib.test(t, {
child: 'groupable',
grandchild: false,
grandchildHeedsSIGINT: false,
messages: null,
exception: false,
skipTermination: false,
exitReturn: 'true'
}, function (childPID) {
process.kill(childPID, 'SIGINT');
}, {
exitReason: 'SIGINT',
stdout: "cleanup",
stderr: ""
});
});
t.test("single: group SIGINT - grandchild ignores", function (t) {
lib.test(t, {
child: 'groupable',
grandchild: true,
grandchildHeedsSIGINT: false,
messages: {
ctrl_C: "{^C}"
},
exception: false,
skipTermination: false,
exitReturn: 'true'
}, function (childPID) {
process.kill(-childPID, 'SIGINT');
}, {
exitReason: 0,
stdout: "skipped_cleanup grandchild=0 cleanup",
stderr: ""
});
});
t.test("single: group SIGINT - grandchild heeds", function (t) {
lib.test(t, {
child: 'groupable',
grandchild: true,
grandchildHeedsSIGINT: true,
messages: {
ctrl_C: "{^C}"
},
exception: false,
skipTermination: false,
exitReturn: 'true'
}, function (childPID) {
process.kill(-childPID, 'SIGINT');
}, {
exitReason: 'SIGINT',
stdout: "skipped_cleanup grandchild=SIGINT cleanup",
stderr: "{^C}\n"
});
});
t.test("single: child SIGHUP - exiting", function (t) {
lib.test(t, {
child: 'groupable',
grandchild: false,
grandchildHeedsSIGINT: false,
messages: null,
exception: false,
skipTermination: false,
exitReturn: 'true'
}, function (childPID) {
process.kill(childPID, 'SIGHUP');
}, {
exitReason: 'SIGHUP',
stdout: "cleanup",
stderr: ""
});
});
t.test("single: child SIGHUP - non-exiting", function (t) {
lib.test(t, {
child: 'groupable',
grandchild: false,
grandchildHeedsSIGINT: false,
messages: null,
exception: false,
skipTermination: true,
exitReturn: 'true'
}, function (childPID) {
process.kill(childPID, 'SIGHUP');
}, {
exitReason: 0,
stdout: "cleanup",
stderr: ""
});
});
t.test("single: group SIGHUP", function (t) {
lib.test(t, {
child: 'groupable',
grandchild: true,
grandchildHeedsSIGINT: false,
messages: null,
exception: false,
skipTermination: false,
exitReturn: 'true'
}, function (childPID) {
process.kill(-childPID, 'SIGHUP');
}, {
exitReason: 'SIGHUP',
// grandchild may exit before,during, or after child exits
stdout: /^(cleanup( grandchild=SIGHUP)?|grandchild=SIGHUP cleanup)$/,
stderr: ""
});
});
t.test("single: child SIGQUIT - exiting", function (t) {
lib.test(t, {
child: 'groupable',
grandchild: false,
grandchildHeedsSIGINT: false,
messages: null,
exception: false,
skipTermination: false,
exitReturn: 'true'
}, function (childPID) {
process.kill(childPID, 'SIGQUIT');
}, {
exitReason: 'SIGQUIT',
stdout: "cleanup",
stderr: ""
});
});
t.test("single: child SIGQUIT - non-exiting", function (t) {
lib.test(t, {
child: 'groupable',
grandchild: false,
grandchildHeedsSIGINT: false,
messages: null,
exception: false,
skipTermination: true,
exitReturn: 'true'
}, function (childPID) {
process.kill(childPID, 'SIGQUIT');
}, {
exitReason: 0,
stdout: "cleanup",
stderr: ""
});
});
t.test("single: group SIGQUIT", function (t) {
lib.test(t, {
child: 'groupable',
grandchild: true,
grandchildHeedsSIGINT: false,
messages: null,
exception: false,
skipTermination: false,
exitReturn: 'true'
}, function (childPID) {
process.kill(-childPID, 'SIGQUIT');
}, {
exitReason: 'SIGQUIT',
// grandchild may exit before,during, or after child exits
stdout: /^(cleanup( grandchild=SIGQUIT)?|grandchild=SIGQUIT cleanup)$/,
stderr: ""
});
});
t.test("single: child SIGTERM - exiting", function (t) {
lib.test(t, {
child: 'groupable',
grandchild: false,
grandchildHeedsSIGINT: false,
messages: null,
exception: false,
skipTermination: false,
exitReturn: 'true'
}, function (childPID) {
process.kill(childPID, 'SIGTERM');
}, {
exitReason: 'SIGTERM',
stdout: "cleanup",
stderr: ""
});
});
t.test("single: child SIGTERM - non-exiting", function (t) {
lib.test(t, {
child: 'groupable',
grandchild: false,
grandchildHeedsSIGINT: false,
messages: null,
exception: false,
skipTermination: true,
exitReturn: 'true'
}, function (childPID) {
process.kill(childPID, 'SIGTERM');
}, {
exitReason: 0,
stdout: "cleanup",
stderr: ""
});
});
t.test("single: group SIGTERM", function (t) {
lib.test(t, {
child: 'groupable',
grandchild: true,
grandchildHeedsSIGINT: false,
messages: null,
exception: false,
skipTermination: false,
exitReturn: 'true'
}, function (childPID) {
process.kill(-childPID, 'SIGTERM');
}, {
exitReason: 'SIGTERM',
// grandchild may exit before,during, or after child exits
stdout: /^(cleanup( grandchild=SIGTERM)?|grandchild=SIGTERM cleanup)$/,
stderr: ""
});
});
t.test("single: child SIGKILL", function (t) {
lib.test(t, {
child: 'groupable',
grandchild: false,
grandchildHeedsSIGINT: false,
messages: null,
exception: false,
skipTermination: false,
exitReturn: 'true'
}, function (childPID) {
process.kill(childPID, 'SIGKILL');
}, {
exitReason: 'SIGKILL',
stdout: "",
stderr: ""
});
});
t.test("single: group SIGKILL", function (t) {
lib.test(t, {
child: 'groupable',
grandchild: true,
grandchildHeedsSIGINT: false,
messages: null,
exception: false,
skipTermination: false,
exitReturn: 'true'
}, function (childPID) {
process.kill(-childPID, 'SIGKILL');
}, {
exitReason: 'SIGKILL',
stdout: "",
stderr: ""
});
});