1var fs = require('graceful-fs') 2var path = require('path') 3var spawn = require('child_process').spawn 4 5var mkdirp = require('mkdirp') 6var osenv = require('osenv') 7var rimraf = require('rimraf') 8var test = require('tap').test 9 10var node = process.execPath 11var npm = require.resolve('../../bin/npm-cli.js') 12 13var pkg = path.resolve(__dirname, 'lifecycle-signal') 14 15var asyncScript = 'console.error(process.pid);process.on(\'SIGINT\',function (){' 16asyncScript += 'setTimeout(function(){console.error(process.pid);process.exit()},10)' 17asyncScript += '});setInterval(function(){},10);' 18 19var zombieScript = 'console.error(process.pid);process.on(\'SIGINT\',function (){' 20zombieScript += '});setInterval(function(){console.error(process.pid)},10);' 21 22var SIGSEGV = require('constants').SIGSEGV // eslint-disable-line node/no-deprecated-api 23 24var json = { 25 name: 'lifecycle-signal', 26 version: '1.2.5', 27 scripts: { 28 preinstall: 'node -e "process.kill(process.pid,\'SIGSEGV\')"', 29 forever: 'node -e "console.error(process.pid);setInterval(function(){},1000)"', 30 async: 'node -e "' + asyncScript + '"', 31 zombie: 'node -e "' + zombieScript + '"' 32 } 33} 34 35test('setup', function (t) { 36 cleanup() 37 mkdirp.sync(pkg) 38 fs.writeFileSync( 39 path.join(pkg, 'package.json'), 40 JSON.stringify(json, null, 2) 41 ) 42 43 process.chdir(pkg) 44 t.end() 45}) 46 47test('lifecycle signal abort', { 48 skip: process.platform === 'win32' && 'windows does not use lifecycle signals' 49}, function (t) { 50 var child = spawn(node, [npm, 'install'], { 51 cwd: pkg 52 }) 53 child.on('close', function (code, signal) { 54 // The error may be forwarded by the shell as an exit code rather than 55 // the signal itself. 56 t.ok((code === 128 + SIGSEGV) || signal === 'SIGSEGV') 57 t.end() 58 }) 59}) 60 61test('lifecycle propagate signal term to child', { 62 /* This feature is broken. npm runs its lifecycle processes in a shell, and at 63 * least `bash` doesn’t forward SIGTERM to its children. */ 64 skip: process.platform !== 'darwin' && 'broken' 65}, function (t) { 66 var innerChildPid 67 var child = spawn(npm, ['run', 'forever'], { 68 cwd: pkg 69 }) 70 child.stderr.on('data', function (data) { 71 innerChildPid = parseInt(data.toString(), 10) 72 t.doesNotThrow(function () { 73 process.kill(innerChildPid, 0) // inner child should be running 74 }) 75 child.kill() // send SIGTERM to npm 76 }) 77 child.on('exit', function (code, signal) { 78 t.equal(code, null) 79 t.equal(signal, 'SIGTERM') 80 t.throws(function () { 81 process.kill(innerChildPid, 0) // SIGTERM should have reached inner child 82 }) 83 t.end() 84 }) 85}) 86 87test('lifecycle wait for async child process exit', { 88 skip: process.platform !== 'darwin' && 'broken' 89}, function (t) { 90 var innerChildPid 91 var interrupted 92 var child = spawn(npm, ['run', 'async'], { 93 cwd: pkg 94 }) 95 child.stderr.on('data', function (data) { 96 if (!interrupted) { 97 interrupted = true 98 child.kill('SIGINT') 99 } else { 100 innerChildPid = parseInt(data.toString(), 10) 101 } 102 }) 103 child.on('exit', function (code, signal) { 104 t.ok(innerChildPid) 105 t.end() 106 }) 107}) 108 109test('lifecycle force kill using multiple SIGINT signals', { 110 skip: process.platform !== 'darwin' && 'broken' 111}, function (t) { 112 var innerChildPid 113 var interrupted 114 var child = spawn(npm, ['run', 'zombie'], { 115 cwd: pkg 116 }) 117 child.stderr.on('data', function (data) { 118 if (!interrupted) { 119 interrupted = true 120 child.kill('SIGINT') 121 } else { 122 innerChildPid = parseInt(data.toString(), 10) 123 child.kill('SIGINT') 124 } 125 }) 126 child.on('exit', function (code, signal) { 127 t.ok(innerChildPid) 128 t.end() 129 }) 130}) 131 132test('cleanup', function (t) { 133 cleanup() 134 t.end() 135}) 136 137function cleanup () { 138 process.chdir(osenv.tmpdir()) 139 rimraf.sync(pkg) 140} 141