Skip to content

Commit 150ab4e

Browse files
authored
Merge pull request #159 from raszi/gh-121
fix #121
2 parents 0343c9e + bd853cb commit 150ab4e

17 files changed

+253
-56
lines changed

.eslintrc.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ module.exports = {
2323
"semi": [
2424
"error",
2525
"always"
26-
]
26+
],
27+
"no-extra-boolean-cast": 0
2728
}
2829
};

.travis.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ node_js:
44
- "10"
55
- "9"
66
- "8"
7-
- "6"
87
sudo: false
98
cache:
109
directories:

appveyor.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22

33
environment:
44
matrix:
5-
- nodejs_version: "6"
65
- nodejs_version: "7"
76
- nodejs_version: "8"
87
- nodejs_version: "9"
98
- nodejs_version: "10"
9+
- nodejs_version: "11"
1010

1111
install:
1212
- ps: Install-Product node $env:nodejs_version

lib/tmp.js

Lines changed: 69 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ const
4343
DIR_MODE = 448 /* 0o700 */,
4444
FILE_MODE = 384 /* 0o600 */,
4545

46-
EVENT = 'exit',
46+
EXIT = 'exit',
47+
48+
SIGINT = 'SIGINT',
4749

4850
// this will hold the objects need to be removed on exit
4951
_removeObjects = [];
@@ -101,7 +103,7 @@ function _isUndefined(obj) {
101103
*/
102104
function _parseArguments(options, callback) {
103105
/* istanbul ignore else */
104-
if (typeof options == 'function') {
106+
if (typeof options === 'function') {
105107
return [{}, options];
106108
}
107109

@@ -132,7 +134,7 @@ function _generateTmpName(opts) {
132134
var template = opts.template;
133135
// make sure that we prepend the tmp path if none was given
134136
/* istanbul ignore else */
135-
if (path.basename(template) == template)
137+
if (path.basename(template) === template)
136138
template = path.join(opts.dir || tmpDir, template);
137139
return template.replace(TEMPLATE_PATTERN, _randomChars(6));
138140
}
@@ -479,7 +481,7 @@ function _prepareRemoveCallback(removeFunction, arg, cleanupCallbackSync) {
479481

480482
called = true;
481483
// sync?
482-
if (removeFunction.length == 1) {
484+
if (removeFunction.length === 1) {
483485
try {
484486
removeFunction(arg);
485487
return next(null);
@@ -550,7 +552,7 @@ function isENOENT(error) {
550552
* error.errno n/a
551553
*/
552554
function isExpectedError(error, code, errno) {
553-
return error.code == code || error.code == errno;
555+
return error.code === code || error.code === errno;
554556
}
555557

556558
/**
@@ -569,43 +571,87 @@ function setGracefulCleanup() {
569571
* @returns {Boolean} true whether listener is a legacy listener
570572
*/
571573
function _is_legacy_listener(listener) {
572-
return (listener.name == '_exit' || listener.name == '_uncaughtExceptionThrown')
574+
return (listener.name === '_exit' || listener.name === '_uncaughtExceptionThrown')
573575
&& listener.toString().indexOf('_garbageCollector();') > -1;
574576
}
575577

576578
/**
577-
* Safely install process exit listeners.
578-
*
579+
* Safely install SIGINT listener.
580+
*
581+
* NOTE: this will only work on OSX and Linux.
582+
*
579583
* @private
580584
*/
581-
function _safely_install_listener() {
582-
var listeners = process.listeners(EVENT);
585+
function _safely_install_sigint_listener() {
583586

584-
// collect any existing listeners
585-
var existingListeners = [];
586-
for (var i = 0, length = listeners.length; i < length; i++) {
587-
var lstnr = listeners[i];
587+
const listeners = process.listeners(SIGINT);
588+
const existingListeners = [];
589+
for (let i = 0, length = listeners.length; i < length; i++) {
590+
const lstnr = listeners[i];
588591
/* istanbul ignore else */
589-
if (lstnr.name == '_tmp$safe_listener' || _is_legacy_listener(lstnr)) {
590-
// we must forget about the uncaughtException listener
591-
if (lstnr.name != '_uncaughtExceptionThrown') existingListeners.push(lstnr);
592-
process.removeListener(EVENT, lstnr);
592+
if (lstnr.name === '_tmp$sigint_listener') {
593+
existingListeners.push(lstnr);
594+
process.removeListener(SIGINT, lstnr);
593595
}
594596
}
597+
process.on(SIGINT, function _tmp$sigint_listener(doExit) {
598+
for (let i = 0, length = existingListeners.length; i < length; i++) {
599+
// let the existing listener do the garbage collection (e.g. jest sandbox)
600+
try {
601+
existingListeners[i](false);
602+
} catch (err) {
603+
// ignore
604+
}
605+
}
606+
try {
607+
// force the garbage collector even it is called again in the exit listener
608+
_garbageCollector();
609+
} finally {
610+
if (!!doExit) {
611+
process.exit(0);
612+
}
613+
}
614+
});
615+
}
595616

596-
process.addListener(EVENT, function _tmp$safe_listener(data) {
617+
/**
618+
* Safely install process exit listener.
619+
*
620+
* @private
621+
*/
622+
function _safely_install_exit_listener() {
623+
const listeners = process.listeners(EXIT);
624+
625+
// collect any existing listeners
626+
const existingListeners = [];
627+
for (let i = 0, length = listeners.length; i < length; i++) {
628+
const lstnr = listeners[i];
597629
/* istanbul ignore else */
598-
if (existingListeners.length) {
599-
for (var i = 0, length = existingListeners.length; i < length; i++) {
630+
// TODO: remove support for legacy listeners once release 1.0.0 is out
631+
if (lstnr.name === '_tmp$safe_listener' || _is_legacy_listener(lstnr)) {
632+
// we must forget about the uncaughtException listener, hopefully it is ours
633+
if (lstnr.name !== '_uncaughtExceptionThrown') {
634+
existingListeners.push(lstnr);
635+
}
636+
process.removeListener(EXIT, lstnr);
637+
}
638+
}
639+
// TODO: what was the data parameter good for?
640+
process.addListener(EXIT, function _tmp$safe_listener(data) {
641+
for (let i = 0, length = existingListeners.length; i < length; i++) {
642+
// let the existing listener do the garbage collection (e.g. jest sandbox)
643+
try {
600644
existingListeners[i](data);
645+
} catch (err) {
646+
// ignore
601647
}
602648
}
603649
_garbageCollector();
604650
});
605651
}
606652

607-
_safely_install_listener();
608-
653+
_safely_install_exit_listener();
654+
_safely_install_sigint_listener();
609655

610656
/**
611657
* Configuration options.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
"scripts": {
3939
"lint": "eslint lib --env mocha test",
4040
"clean": "rm -Rf ./coverage",
41-
"test": "istanbul cover ./node_modules/mocha/bin/_mocha --report none --print none --dir ./coverage/json -- -u exports test/*-test.js && istanbul report --root ./coverage/json html && istanbul report text-summary",
41+
"test": "npm run clean && istanbul cover ./node_modules/mocha/bin/_mocha --report none --print none --dir ./coverage/json -u exports -R test/*-test.js && istanbul report --root ./coverage/json html && istanbul report text-summary",
4242
"doc": "jsdoc -c .jsdoc.json"
4343
}
4444
}

test/child-process.js

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,25 @@ module.exports.genericChildProcess = _spawnProcess('spawn-generic.js');
1212
module.exports.childProcess = _spawnProcess('spawn-custom.js');
1313

1414
function _spawnProcess(spawnFile) {
15-
return function (testCase, configFile, cb) {
16-
testCase.timeout(5000);
17-
18-
var
15+
return function (testCase, configFile, cb, signal) {
16+
const
1917
configFilePath = path.join(__dirname, 'outband', configFile),
2018
commandArgs = [path.join(__dirname, spawnFile), configFilePath];
2119

2220
exists(configFilePath, function (configExists) {
23-
if (configExists) return _doSpawn(commandArgs, cb);
21+
if (configExists) return _doSpawn(commandArgs, cb, signal);
2422

2523
cb(new Error('ENOENT: configFile ' + configFilePath + ' does not exist'));
2624
});
2725
};
2826
}
2927

30-
function _doSpawn(commandArgs, cb) {
31-
var
28+
function _doSpawn(commandArgs, cb, signal) {
29+
const
3230
node_path = process.argv[0],
3331
stdoutBufs = [],
34-
stderrBufs = [],
32+
stderrBufs = [];
33+
let
3534
child,
3635
done = false,
3736
stderrDone = false,
@@ -50,14 +49,11 @@ function _doSpawn(commandArgs, cb) {
5049
child = spawn(node_path, commandArgs);
5150
child.stdin.end();
5251

53-
// TODO we no longer support node 0.6
54-
// Cannot use 'close' event because not on node-0.6.
5552
function _close() {
56-
var
57-
stderr = _bufferConcat(stderrBufs).toString(),
58-
stdout = _bufferConcat(stdoutBufs).toString();
59-
6053
if (stderrDone && stdoutDone && !done) {
54+
const
55+
stderr = _bufferConcat(stderrBufs).toString(),
56+
stdout = _bufferConcat(stdoutBufs).toString();
6157
done = true;
6258
cb(null, stderr, stdout);
6359
}
@@ -83,6 +79,13 @@ function _doSpawn(commandArgs, cb) {
8379
stderrDone = true;
8480
_close();
8581
});
82+
83+
if (signal) {
84+
setTimeout(function () {
85+
// does not work on node 6
86+
child.kill(signal);
87+
}, 2000);
88+
}
8689
}
8790

8891
function _bufferConcat(buffers) {
@@ -91,10 +94,9 @@ function _bufferConcat(buffers) {
9194
}
9295

9396
return new Buffer(buffers.reduce(function (acc, buf) {
94-
for (var i = 0; i < buf.length; i++) {
97+
for (let i = 0; i < buf.length; i++) {
9598
acc.push(buf[i]);
9699
}
97100
return acc;
98101
}, []));
99102
}
100-

test/dir-sync-test.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,10 +125,11 @@ describe('tmp', function () {
125125
try {
126126
assertions.assertExists(stdout);
127127
assertions.assertExists(path.join(stdout, 'should-be-removed.file'), true);
128-
if (process.platform == 'win32')
128+
if (process.platform == 'win32') {
129129
assertions.assertExists(path.join(stdout, 'symlinkme-target'), true);
130-
else
130+
} else {
131131
assertions.assertExists(path.join(stdout, 'symlinkme-target'));
132+
}
132133
} catch (err) {
133134
rimraf.sync(stdout);
134135
return done(err);

test/issue121-test.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/* eslint-disable no-octal */
2+
// vim: expandtab:ts=2:sw=2
3+
4+
const
5+
assertions = require('./assertions'),
6+
childProcess = require('./child-process').childProcess,
7+
os = require('os'),
8+
rimraf = require('rimraf'),
9+
testCases = [
10+
{ signal: 'SIGINT', expectExists: false },
11+
{ signal: 'SIGTERM', expectExists: true }
12+
];
13+
14+
// skip tests on win32
15+
const isWindows = os.platform() === 'win32';
16+
const tfunc = isWindows ? xit : it;
17+
18+
describe('tmp', function () {
19+
describe('issue121 - clean up on terminating signals', function () {
20+
for (let tc of testCases) {
21+
tfunc('for signal ' + tc.signal, function (done) {
22+
// increase timeout so that the child process may fail
23+
this.timeout(20000);
24+
issue121Tests(tc.signal, tc.expectExists)(done);
25+
});
26+
}
27+
});
28+
});
29+
30+
function issue121Tests(signal, expectExists) {
31+
return function (done) {
32+
childProcess(this, 'issue121.json', function (err, stderr, stdout) {
33+
if (err) return done(err);
34+
else if (stderr) return done(new Error(stderr));
35+
36+
try {
37+
if (expectExists) {
38+
assertions.assertExists(stdout);
39+
}
40+
else {
41+
assertions.assertDoesNotExist(stdout);
42+
}
43+
done();
44+
} catch (err) {
45+
done(err);
46+
} finally {
47+
// cleanup
48+
if (expectExists) {
49+
rimraf.sync(stdout);
50+
}
51+
}
52+
}, signal);
53+
};
54+
}

test/issue129-sigint-test.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/* eslint-disable no-octal */
2+
// vim: expandtab:ts=2:sw=2
3+
4+
var
5+
assert = require('assert'),
6+
assertions = require('./assertions'),
7+
childProcess = require('./child-process').childProcess;
8+
9+
describe('tmp', function () {
10+
describe('issue129: safely install sigint listeners', function () {
11+
it('when simulating sandboxed behavior', function (done) {
12+
childProcess(this, 'issue129-sigint.json', function (err, stderr, stdout) {
13+
if (err) return done(err);
14+
if (!stdout && !stderr) return done(new Error('stderr or stdout expected'));
15+
if (stderr) {
16+
try {
17+
assertions.assertDoesNotStartWith(stderr, 'EEXISTS:MULTIPLE');
18+
assertions.assertDoesNotStartWith(stderr, 'ENOAVAIL:EXISTING');
19+
} catch (err) {
20+
return done(err);
21+
}
22+
}
23+
if (stdout) {
24+
try {
25+
assert.equal(stdout, 'EOK', 'existing listeners should have been removed and called');
26+
} catch (err) {
27+
return done(err);
28+
}
29+
}
30+
done();
31+
});
32+
});
33+
});
34+
});

test/outband/issue121.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/* eslint-disable no-octal */
2+
// vim: expandtab:ts=2:sw=2
3+
4+
const
5+
tmp = require('../../lib/tmp');
6+
7+
// https://github.com/raszi/node-tmp/issues/121
8+
module.exports = function () {
9+
10+
tmp.setGracefulCleanup();
11+
12+
const result = tmp.dirSync({ unsafeCleanup: true });
13+
14+
this.out(result.name, function () { });
15+
16+
setTimeout(function () {
17+
throw new Error('ran into timeout');
18+
}, 10000);
19+
};

test/outband/issue121.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"graceful": true,
3+
"tc": "issue121"
4+
}

0 commit comments

Comments
 (0)