Created
May 5, 2012 04:51
-
-
Save coltrane/2599899 to your computer and use it in GitHub Desktop.
Tests the response of node.js child processes to various posix signals.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var child_process = require('child_process'), | |
assert = require('assert') | |
var isChild = !!(process.send), | |
isMaster = ((!isChild) && (process.argv.length > 2)), | |
isTopLevel = (!isMaster && !isChild) | |
if( isTopLevel ) { | |
//** | |
//* TopLevel - The program was called with no arguments. We'll run the test | |
//* once for each stop-signal ('T' or 'A') in the list. (except, we'll skip | |
//* SIGUSR1, because node's debugger owns that one. | |
//* | |
(function top_level(){ | |
var all_signals = [ | |
// A list of all posix-defined signals. "The following signals shall be supported on | |
// all implementations". | |
// See: <http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/signal.h.html> | |
// | |
// the `action` field shows 'default action', which the OS will (should) take unless | |
// the signal is trapped and handled differently. | |
// T = Abnormal termination of the process. | |
// A = Abnormal termination of the process (with additional actions). | |
// I = Ignore the signal | |
// S = Stop the process | |
// C = Continue the process, if it is stopped; otherwise, ignore the signal. | |
// | |
{ name: 'SIGABRT', action: 'A', desc: 'Process abort signal.' }, | |
{ name: 'SIGALRM', action: 'T', desc: 'Alarm clock.' }, | |
{ name: 'SIGBUS', action: 'A', desc: 'Access to an undefined portion of a memory object.' }, | |
{ name: 'SIGCHLD', action: 'I', desc: 'Child process terminated, stopped, or continued. ' }, | |
{ name: 'SIGCONT', action: 'C', desc: 'Continue executing, if stopped.' }, | |
{ name: 'SIGFPE', action: 'A', desc: 'Erroneous arithmetic operation.' }, | |
{ name: 'SIGHUP', action: 'T', desc: 'Hangup.' }, | |
{ name: 'SIGILL', action: 'A', desc: 'Illegal instruction.' }, | |
{ name: 'SIGINT', action: 'T', desc: 'Terminal interrupt signal.' }, | |
{ name: 'SIGKILL', action: 'T', desc: 'Kill (cannot be caught or ignored).' }, | |
{ name: 'SIGPIPE', action: 'T', desc: 'Write on a pipe with no one to read it.' }, | |
{ name: 'SIGQUIT', action: 'A', desc: 'Terminal quit signal.' }, | |
{ name: 'SIGSEGV', action: 'A', desc: 'Invalid memory reference.' }, | |
{ name: 'SIGSTOP', action: 'S', desc: 'Stop executing (cannot be caught or ignored).' }, | |
{ name: 'SIGTERM', action: 'T', desc: 'Termination signal.' }, | |
{ name: 'SIGTSTP', action: 'S', desc: 'Terminal stop signal.' }, | |
{ name: 'SIGTTIN', action: 'S', desc: 'Background process attempting read.' }, | |
{ name: 'SIGTTOU', action: 'S', desc: 'Background process attempting write.' }, | |
{ name: 'SIGUSR1', action: 'T', desc: 'User-defined signal 1.' }, | |
{ name: 'SIGUSR2', action: 'T', desc: 'User-defined signal 2.' }, | |
{ name: 'SIGPOLL', action: 'T', desc: 'Pollable event. ' }, | |
{ name: 'SIGPROF', action: 'T', desc: 'Profiling timer expired. ' }, | |
{ name: 'SIGSYS', action: 'A', desc: 'Bad system call.' }, | |
{ name: 'SIGTRAP', action: 'A', desc: 'Trace/breakpoint trap. ' }, | |
{ name: 'SIGURG', action: 'I', desc: 'High bandwidth data is available at a socket.' }, | |
{ name: 'SIGVTALRM', action: 'T', desc: 'Virtual timer expired.' }, | |
{ name: 'SIGXCPU', action: 'A', desc: 'CPU time limit exceeded.' }, | |
{ name: 'SIGXFSZ', action: 'A', desc: 'File size limit exceeded. ' } | |
]; | |
var selectedActions = {T:true, A:true}, | |
globalResult = 0; | |
function finish() { | |
process.exit(globalResult); | |
} | |
(function testSignal(idx) { | |
idx = idx||0; | |
var sig = all_signals[idx]; | |
if( !sig ) { finish(); return; } | |
function nextTest() { | |
process.nextTick(function() { testSignal(idx+1); }); | |
}; | |
// ignore signals that do not match the "selected actions" | |
if( ! (sig.action in selectedActions) ) { | |
nextTest(); | |
return; | |
} | |
// run the test in its own process. | |
// the argument `sig.name` tells the script to be a "master" | |
// and test the given signal. (see "Master" below). | |
child_process.execFile( | |
process.execPath, [process.argv[1], sig.name], null, | |
function(code, stdout, stderr) { | |
process.stdout.write(stdout); | |
process.stderr.write(stderr); | |
globalResult = globalResult || code; | |
nextTest() | |
} | |
) | |
}()); | |
}()); //end: toplevel | |
} else if( isMaster ) { | |
//** | |
//* Master (Test Stop Signal) - The first cmdline argument will be the | |
//* name of the signal to be tested. Test it, and exit. | |
//* | |
(function do_master() { | |
var excludedSignals = { 'SIGUSR1': 'used by v8/node debugger' }, | |
testSig = process.argv[2], | |
tmr0 = null, | |
timeout = 5000, | |
reportText; | |
function clean_exit(code) { | |
process.removeAllListeners(); | |
clearTimeout(tmr0); | |
process.exit(code); | |
} | |
process.on('uncaughtException', function (err) { | |
var msg = ''; | |
if( err.name === 'AssertionError' ) { | |
msg = err.message; | |
} else { | |
//throw err; | |
msg = err; | |
} | |
if( reportText ) { msg = reportText + msg; } | |
print('FAIL: '+ msg + '\n'); | |
clean_exit(1); | |
}); | |
if( testSig ) { | |
print(testSig + ' ...'); | |
} else { | |
// disable the timeout if no signal was specified... | |
timeout = 0; | |
} | |
if( testSig && (testSig in excludedSignals) ) { | |
// skip "excluded" signals (treat them as ok) | |
print( 'OK (skipped) ' + excludedSignals[testSig] + '\n'); | |
clean_exit(0); | |
} else { | |
// -- run the test -- // | |
var didExit = false | |
, didDisconnect = false | |
if( timeout ) { | |
tmr0 = setTimeout( function() { | |
assert.ok( false, 'timeout ('+(timeout/1000)+'s): child did not respond to signal.'); | |
}, timeout); | |
} | |
child = child_process.fork(process.argv[1]); | |
// child messages us, once it's up and running | |
child.on('message', function(msg) { | |
process.nextTick( start ); | |
}); | |
// performs the action that was requested of this test run. | |
// (ie. send a signal, tell child exit, or just wait) | |
function start() { | |
if( testSig ) { | |
child.kill(testSig); | |
} else { | |
print('master ['+process.pid+']\n'); | |
print('child ['+child.pid+']\n'); | |
print('waiting for child to exit... '); | |
} | |
} | |
child.on('disconnect', function() { | |
didDisconnect = true; | |
}); | |
child.on('exit', function(exitCode, sigCode) { | |
clearTimeout(tmr0); | |
didExit = true; | |
reportText = '[exitCode='+exitCode+', signal='+sigCode+'] '; | |
//print(reportText); | |
if( testSig ) { | |
// verify that we got the signal we were expecting... | |
assert.equal( sigCode, testSig, 'exit: reported incorrect signal'); | |
assert.equal( exitCode , null, 'exit: reported incorrect exitCode.') | |
} else { | |
assert( (exitCode != null && sigCode==null) || (exitCode == null && sigCode != null)); | |
} | |
assert.ok( didDisconnect, '(exit) missing disconnect event, or events emitted out-of-order.'); | |
}); | |
process.on('exit', function() { | |
assert.ok( didExit, 'no \'exit\' event was fired for child process' ); | |
assert.ok( didDisconnect , 'no \'disconnect\' event was fired for child process' ); | |
print('OK\n'); | |
}); | |
process.on('SIGINT', function() { | |
print('interrupted!\n'); | |
clean_exit(-1); | |
}); | |
} | |
}()); //end: master | |
} else if( isChild ) { | |
//** | |
//* Child - created by the master, to be the recipient of the signal | |
//* | |
(function do_child() { | |
process.send('helo'); | |
// child stays alive, because of ipc channel. | |
}()); //end: child | |
} else { | |
// should not happen. | |
assert.ok( false, "the test script is unable to identify whether it is "+ | |
"master, child, or toplevel. This should never happen"); | |
} | |
// a few shared utilities | |
function print(str) { | |
process.stderr.write(str); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The following results were obtained using node v0.7.9-pre (6f82b9f), on OS-X lion.
Analysis:
-D_POSIX_C_SOURCE
might fix this, but that seems to break lots of other stuff)._exit(1)
. This causes the child_process to exit "normally" (with a non-zero exit code), rather than exiting "abnormally" (with a signal code).