Skip to content
Permalink
Browse files

test: clean tmpdir on process exit

PR-URL: #28858
Reviewed-By: Rich Trott <rtrott@gmail.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
  • Loading branch information...
joaocgreis authored and Trott committed Jul 30, 2019
1 parent 0376b5b commit 8ef68e66d0465441a79a5dae22e480bf0d83ed25
Showing with 212 additions and 66 deletions.
  1. +23 −6 test/addons/load-long-path/test.js
  2. +7 −0 test/common/README.md
  3. +39 −0 test/common/tmpdir.js
  4. +19 −2 test/parallel/test-child-process-server-close.js
  5. +0 −10 test/parallel/test-fs-append-file-sync.js
  6. +1 −0 test/parallel/test-fs-buffertype-writesync.js
  7. +1 −0 test/parallel/test-fs-fsync.js
  8. +0 −4 test/parallel/test-fs-long-path.js
  9. +4 −1 test/parallel/test-fs-open-flags.js
  10. +2 −2 test/parallel/test-fs-options-immutable.js
  11. +4 −0 test/parallel/test-fs-promises-file-handle-append-file.js
  12. +2 −0 test/parallel/test-fs-promises-file-handle-chmod.js
  13. +4 −0 test/parallel/test-fs-promises-file-handle-read.js
  14. +2 −0 test/parallel/test-fs-promises-file-handle-readFile.js
  15. +1 −0 test/parallel/test-fs-promises-file-handle-stat.js
  16. +1 −0 test/parallel/test-fs-promises-file-handle-sync.js
  17. +2 −0 test/parallel/test-fs-promises-file-handle-truncate.js
  18. +8 −0 test/parallel/test-fs-promises-file-handle-write.js
  19. +2 −0 test/parallel/test-fs-promises-file-handle-writeFile.js
  20. +2 −0 test/parallel/test-fs-promises-readfile-with-fd.js
  21. +2 −0 test/parallel/test-fs-promises-writefile-with-fd.js
  22. +7 −0 test/parallel/test-fs-promises.js
  23. +1 −0 test/parallel/test-fs-readdir-types.js
  24. +4 −0 test/parallel/test-fs-readfile-fd.js
  25. +0 −4 test/parallel/test-fs-readfile-pipe-large.js
  26. +0 −4 test/parallel/test-fs-readfilesync-pipe-large.js
  27. +1 −0 test/parallel/test-fs-ready-event-stream.js
  28. +1 −1 test/parallel/test-fs-truncate-fd.js
  29. +4 −4 test/parallel/test-fs-truncate.js
  30. +0 −7 test/parallel/test-fs-write-file.js
  31. +1 −0 test/parallel/test-fs-write-stream-change-open.js
  32. +1 −0 test/parallel/test-fs-write-stream-err.js
  33. +4 −4 test/parallel/test-fs-write-stream-throw-type-error.js
  34. +1 −0 test/parallel/test-fs-write-stream.js
  35. +6 −0 test/parallel/test-fs-writefile-with-fd.js
  36. +0 −2 test/parallel/test-http2-respond-file-error-pipe-offset.js
  37. +0 −2 test/parallel/test-http2-respond-file-with-pipe.js
  38. +2 −0 test/parallel/test-internal-fs-syncwritestream.js
  39. +18 −1 test/parallel/test-pipe-unref.js
  40. +6 −0 test/parallel/test-readline-async-iterators-destroy.js
  41. +4 −1 test/parallel/test-repl-history-perm.js
  42. +18 −1 test/parallel/test-tls-wrap-econnreset-pipe.js
  43. +0 −6 test/pummel/test-fs-largefile.js
  44. +1 −0 test/pummel/test-fs-watch-file-slow.js
  45. +1 −4 test/pummel/test-fs-watch-file.js
  46. +2 −0 test/pummel/test-regress-GH-814.js
  47. +2 −0 test/pummel/test-regress-GH-814_2.js
  48. +1 −0 test/sequential/test-http2-timeout-large-write-file.js
@@ -6,9 +6,9 @@ if (common.isWindows && (process.env.PROCESSOR_ARCHITEW6432 !== undefined))
const fs = require('fs');
const path = require('path');
const assert = require('assert');
const { fork } = require('child_process');

const tmpdir = require('../../common/tmpdir');
tmpdir.refresh();

// Make a path that is more than 260 chars long.
// Any given folder cannot have a name longer than 260 characters,
@@ -17,7 +17,6 @@ let addonDestinationDir = path.resolve(tmpdir.path);

for (let i = 0; i < 10; i++) {
addonDestinationDir = path.join(addonDestinationDir, 'x'.repeat(30));
fs.mkdirSync(addonDestinationDir);
}

const addonPath = path.join(__dirname,
@@ -26,11 +25,29 @@ const addonPath = path.join(__dirname,
'binding.node');
const addonDestinationPath = path.join(addonDestinationDir, 'binding.node');

// Loading an addon keeps the file open until the process terminates. Load
// the addon in a child process so that when the parent terminates the file
// is already closed and the tmpdir can be cleaned up.

// Child
if (process.argv[2] === 'child') {
// Attempt to load at long path destination
const addon = require(addonDestinationPath);
assert.notStrictEqual(addon, null);
assert.strictEqual(addon.hello(), 'world');
return;
}

// Parent
tmpdir.refresh();

// Copy binary to long path destination
fs.mkdirSync(addonDestinationDir, { recursive: true });
const contents = fs.readFileSync(addonPath);
fs.writeFileSync(addonDestinationPath, contents);

// Attempt to load at long path destination
const addon = require(addonDestinationPath);
assert.notStrictEqual(addon, null);
assert.strictEqual(addon.hello(), 'world');
// Run test
const child = fork(__filename, ['child'], { stdio: 'inherit' });
child.on('exit', common.mustCall(function(code) {
assert.strictEqual(code, 0);
}));
@@ -906,6 +906,13 @@ The realpath of the testing temporary directory.

Deletes and recreates the testing temporary directory.

The first time `refresh()` runs, it adds a listener to process `'exit'` that
cleans the temporary directory. Thus, every file under `tmpdir.path` needs to
be closed before the test completes. A good way to do this is to add a
listener to process `'beforeExit'`. If a file needs to be left open until
Node.js completes, use a child process and call `refresh()` only in the
parent.

## WPT Module

### harness
@@ -5,6 +5,7 @@ const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
const { debuglog } = require('util');
const { isMainThread } = require('worker_threads');

const debug = debuglog('test/tmpdir');

@@ -61,6 +62,9 @@ function rimrafSync(pathname, { spawn = true } = {}) {
}
rmdirSync(pathname, e);
}

if (fs.existsSync(pathname))
throw new Error(`Unable to rimraf ${pathname}`);
}

function rmdirSync(p, originalEr) {
@@ -80,7 +84,9 @@ function rmdirSync(p, originalEr) {
}
});
fs.rmdirSync(p);
return;
}
throw e;
}
}

@@ -93,9 +99,42 @@ const tmpdirName = '.tmp.' +
(process.env.TEST_SERIAL_ID || process.env.TEST_THREAD_ID || '0');
const tmpPath = path.join(testRoot, tmpdirName);

let firstRefresh = true;
function refresh(opts = {}) {
rimrafSync(this.path, opts);
fs.mkdirSync(this.path);

if (firstRefresh) {
firstRefresh = false;
// Clean only when a test uses refresh. This allows for child processes to
// use the tmpdir and only the parent will clean on exit.
process.on('exit', onexit);
}
}

function onexit() {
// Change directory to avoid possible EBUSY
if (isMainThread)
process.chdir(testRoot);

try {
rimrafSync(tmpPath, { spawn: false });
} catch (e) {
console.error('Can\'t clean tmpdir:', tmpPath);

const files = fs.readdirSync(tmpPath);
console.error('Files blocking:', files);

if (files.some((f) => f.startsWith('.nfs'))) {
// Warn about NFS "silly rename"
console.error('Note: ".nfs*" might be files that were open and ' +
'unlinked but not closed.');
console.error('See http://nfs.sourceforge.net/#faq_d2 for details.');
}

console.error();
throw e;
}
}

module.exports = {
@@ -1,12 +1,29 @@
'use strict';

const common = require('../common');
const { spawn } = require('child_process');
const assert = require('assert');
const { fork, spawn } = require('child_process');
const net = require('net');

const tmpdir = require('../common/tmpdir');
tmpdir.refresh();

// Run in a child process because the PIPE file descriptor stays open until
// Node.js completes, blocking the tmpdir and preventing cleanup.

if (process.argv[2] !== 'child') {
// Parent
tmpdir.refresh();

// Run test
const child = fork(__filename, ['child'], { stdio: 'inherit' });
child.on('exit', common.mustCall(function(code) {
assert.strictEqual(code, 0);
}));

return;
}

// Child
const server = net.createServer((conn) => {
spawn(process.execPath, ['-v'], {
stdio: ['ignore', conn, 'ignore']
@@ -99,13 +99,3 @@ const fileData5 = fs.readFileSync(filename5);

assert.strictEqual(Buffer.byteLength(data) + currentFileData.length,
fileData5.length);

// Exit logic for cleanup.

process.on('exit', function() {
fs.unlinkSync(filename);
fs.unlinkSync(filename2);
fs.unlinkSync(filename3);
fs.unlinkSync(filename4);
fs.unlinkSync(filename5);
});
@@ -19,4 +19,5 @@ v.forEach((value) => {
const fd = fs.openSync(filePath, 'w');
fs.writeSync(fd, value);
assert.strictEqual(fs.readFileSync(filePath).toString(), String(value));
fs.closeSync(fd);
});
@@ -46,6 +46,7 @@ fs.open(fileTemp, 'a', 0o777, common.mustCall(function(err, fd) {
assert.ifError(err);
fs.fsync(fd, common.mustCall(function(err) {
assert.ifError(err);
fs.closeSync(fd);
}));
}));
}));
@@ -49,7 +49,3 @@ fs.writeFile(fullPath, 'ok', common.mustCall(function(err) {
assert.ifError(err);
}));
}));

process.on('exit', function() {
fs.unlinkSync(fullPath);
});
@@ -94,5 +94,8 @@ if (common.isLinux || common.isOSX) {
tmpdir.refresh();
const file = path.join(tmpdir.path, 'a.js');
fs.copyFileSync(fixtures.path('a.js'), file);
fs.open(file, O_DSYNC, common.mustCall(assert.ifError));
fs.open(file, O_DSYNC, common.mustCall((err, fd) => {
assert.ifError(err);
fs.closeSync(fd);
}));
}
@@ -70,6 +70,6 @@ if (common.canCreateSymLink()) {
{
const fileName = path.resolve(tmpdir.path, 'streams');
fs.WriteStream(fileName, options).once('open', common.mustCall(() => {
fs.ReadStream(fileName, options);
}));
fs.ReadStream(fileName, options).destroy();
})).end();
}
@@ -22,6 +22,8 @@ async function validateAppendBuffer() {
await fileHandle.appendFile(buffer);
const appendedFileData = fs.readFileSync(filePath);
assert.deepStrictEqual(appendedFileData, buffer);

await fileHandle.close();
}

async function validateAppendString() {
@@ -33,6 +35,8 @@ async function validateAppendString() {
const stringAsBuffer = Buffer.from(string, 'utf8');
const appendedFileData = fs.readFileSync(filePath);
assert.deepStrictEqual(appendedFileData, stringAsBuffer);

await fileHandle.close();
}

validateAppendBuffer()
@@ -38,6 +38,8 @@ async function validateFilePermission() {
await fileHandle.chmod(newPermissions);
const statsAfterMod = fs.statSync(filePath);
assert.deepStrictEqual(statsAfterMod.mode & expectedAccess, expectedAccess);

await fileHandle.close();
}

validateFilePermission().then(common.mustCall());
@@ -26,6 +26,8 @@ async function validateRead() {
const readAsyncHandle = await fileHandle.read(Buffer.alloc(11), 0, 11, 0);
assert.deepStrictEqual(buffer.length, readAsyncHandle.bytesRead);
assert.deepStrictEqual(buffer, readAsyncHandle.buffer);

await fileHandle.close();
}

async function validateEmptyRead() {
@@ -38,6 +40,8 @@ async function validateEmptyRead() {
fs.closeSync(fd);
const readAsyncHandle = await fileHandle.read(Buffer.alloc(11), 0, 11, 0);
assert.deepStrictEqual(buffer.length, readAsyncHandle.bytesRead);

await fileHandle.close();
}

async function validateLargeRead() {
@@ -25,6 +25,8 @@ async function validateReadFile() {

const readFileData = await fileHandle.readFile();
assert.deepStrictEqual(buffer, readFileData);

await fileHandle.close();
}

async function validateReadFileProc() {
@@ -17,6 +17,7 @@ async function validateStat() {
const fileHandle = await open(filePath, 'w+');
const stats = await fileHandle.stat();
assert.ok(stats.mtime instanceof Date);
await fileHandle.close();
}

validateStat()
@@ -20,6 +20,7 @@ async function validateSync() {
const ret = await handle.read(Buffer.alloc(11), 0, 11, 0);
assert.strictEqual(ret.bytesRead, 11);
assert.deepStrictEqual(ret.buffer, buf);
await handle.close();
}

validateSync();
@@ -20,6 +20,8 @@ async function validateTruncate() {

await fileHandle.truncate(5);
assert.deepStrictEqual((await readFile(filename)).toString(), 'Hello');

await fileHandle.close();
}

validateTruncate().then(common.mustCall());
@@ -22,6 +22,8 @@ async function validateWrite() {
await fileHandle.write(buffer, 0, buffer.length);
const readFileData = fs.readFileSync(filePathForHandle);
assert.deepStrictEqual(buffer, readFileData);

await fileHandle.close();
}

async function validateEmptyWrite() {
@@ -32,6 +34,8 @@ async function validateEmptyWrite() {
await fileHandle.write(buffer, 0, buffer.length);
const readFileData = fs.readFileSync(filePathForHandle);
assert.deepStrictEqual(buffer, readFileData);

await fileHandle.close();
}

async function validateNonUint8ArrayWrite() {
@@ -42,6 +46,8 @@ async function validateNonUint8ArrayWrite() {
await fileHandle.write(buffer, 0, buffer.length);
const readFileData = fs.readFileSync(filePathForHandle);
assert.deepStrictEqual(Buffer.from(buffer, 'utf8'), readFileData);

await fileHandle.close();
}

async function validateNonStringValuesWrite() {
@@ -55,6 +61,8 @@ async function validateNonStringValuesWrite() {
const readFileData = fs.readFileSync(filePathForHandle);
const expected = ['123', '[object Object]', '[object Map]'].join('');
assert.deepStrictEqual(Buffer.from(expected, 'utf8'), readFileData);

await fileHandle.close();
}

Promise.all([
@@ -22,6 +22,8 @@ async function validateWriteFile() {
await fileHandle.writeFile(buffer);
const readFileData = fs.readFileSync(filePathForHandle);
assert.deepStrictEqual(buffer, readFileData);

await fileHandle.close();
}

validateWriteFile()
@@ -28,6 +28,8 @@ async function readFileTest() {

/* readFile() should read from position five, instead of zero. */
assert.deepStrictEqual((await handle.readFile()).toString(), ' World');

await handle.close();
}


@@ -29,6 +29,8 @@ async function writeFileTest() {

/* New content should be written at position five, instead of zero. */
assert.deepStrictEqual(readFileSync(fn).toString(), 'HelloWorld');

await handle.close();
}


0 comments on commit 8ef68e6

Please sign in to comment.
You can’t perform that action at this time.