Skip to content

Commit

Permalink
test: add and use tmpdir.hasTooLittleSpace()
Browse files Browse the repository at this point in the history
In general, we assume that the tmpdir will provide sufficient space for
most tests. Some tests, however, require hundreds of megabytes or even
gigabytes of space, which often causes them to fail, especially on our
macOS infrastructure. The most recent reliability report contains more
than 20 related CI failures.

This change adds a new function hasTooLittleSpace() to the tmpdir module
that uses statfsSync() to guess whether allocating a certain amount of
space within the temporary directory will fail.

This change also updates the most frequently failing tests to use the
new function, in which case the relevant parts of the tests are skipped.

Refs: nodejs/reliability#549
  • Loading branch information
tniessen committed Apr 28, 2023
1 parent a75959b commit f5a4908
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 22 deletions.
9 changes: 9 additions & 0 deletions test/common/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1030,6 +1030,15 @@ Avoid calling it more than once in an asynchronous context as one call
might refresh the temporary directory of a different context, causing
the test to fail somewhat mysteriously.

### `hasTooLittleSpace(size)`

* `x` [\<number>][<number>] Required size, in bytes.

Returns `true` if the available blocks of the file system underlying `path`
might be insufficient to hold a single file of `size` bytes. This is useful for
skipping tests that require hundreds of megabytes or even gigabytes of temporary
files, but it is susceptible to race conditions.

## UDP pair helper

The `common/udppair` module exports a function `makeUDPPair` and a class
Expand Down
6 changes: 6 additions & 0 deletions test/common/tmpdir.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,13 @@ function onexit(useSpawn) {
}
}

function hasTooLittleSpace(size) {
const { bavail, bsize } = fs.statfsSync(tmpPath);
return bavail < Math.ceil(size / bsize);
}

module.exports = {
path: tmpPath,
refresh,
hasTooLittleSpace,
};
27 changes: 16 additions & 11 deletions test/parallel/test-fs-promises-file-handle-readFile.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,17 +106,22 @@ async function doReadAndCancel() {
// Variable taken from https:/nodejs/node/blob/1377163f3351/lib/internal/fs/promises.js#L5
const kIoMaxLength = 2 ** 31 - 1;

const newFile = path.resolve(tmpDir, 'dogs-running3.txt');
await writeFile(newFile, Buffer.from('0'));
await truncate(newFile, kIoMaxLength + 1);

const fileHandle = await open(newFile, 'r');

await assert.rejects(fileHandle.readFile(), {
name: 'RangeError',
code: 'ERR_FS_FILE_TOO_LARGE'
});
await fileHandle.close();
if (tmpdir.hasTooLittleSpace(kIoMaxLength)) {
// truncate() will fail with ENOSPC if there is not enough space.
common.printSkipMessage(`Not enough space in ${tmpDir}`);
} else {
const newFile = path.resolve(tmpDir, 'dogs-running3.txt');
await writeFile(newFile, Buffer.from('0'));
await truncate(newFile, kIoMaxLength + 1);

const fileHandle = await open(newFile, 'r');

await assert.rejects(fileHandle.readFile(), {
name: 'RangeError',
code: 'ERR_FS_FILE_TOO_LARGE'
});
await fileHandle.close();
}
}
}

Expand Down
28 changes: 17 additions & 11 deletions test/parallel/test-fs-readfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,21 +52,27 @@ for (const e of fileInfo) {
assert.deepStrictEqual(buf, e.contents);
}));
}
// Test readFile size too large

// readFile() and readFileSync() should fail if the file is too big.
{
const kIoMaxLength = 2 ** 31 - 1;

const file = path.join(tmpdir.path, `${prefix}-too-large.txt`);
fs.writeFileSync(file, Buffer.from('0'));
fs.truncateSync(file, kIoMaxLength + 1);
if (tmpdir.hasTooLittleSpace(kIoMaxLength)) {
// truncateSync() will fail with ENOSPC if there is not enough space.
common.printSkipMessage(`Not enough space in ${tmpdir.path}`);
} else {
const file = path.join(tmpdir.path, `${prefix}-too-large.txt`);
fs.writeFileSync(file, Buffer.from('0'));
fs.truncateSync(file, kIoMaxLength + 1);

fs.readFile(file, common.expectsError({
code: 'ERR_FS_FILE_TOO_LARGE',
name: 'RangeError',
}));
assert.throws(() => {
fs.readFileSync(file);
}, { code: 'ERR_FS_FILE_TOO_LARGE', name: 'RangeError' });
fs.readFile(file, common.expectsError({
code: 'ERR_FS_FILE_TOO_LARGE',
name: 'RangeError',
}));
assert.throws(() => {
fs.readFileSync(file);
}, { code: 'ERR_FS_FILE_TOO_LARGE', name: 'RangeError' });
}
}

{
Expand Down
4 changes: 4 additions & 0 deletions test/pummel/test-fs-readfile-tostring-fail.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ if (common.isAIX && (Number(cp.execSync('ulimit -f')) * 512) < kStringMaxLength)
const tmpdir = require('../common/tmpdir');
tmpdir.refresh();

if (tmpdir.hasTooLittleSpace(kStringMaxLength)) {
common.skip(`Not enough space in ${tmpdir.path}`);
}

const file = path.join(tmpdir.path, 'toobig.txt');
const stream = fs.createWriteStream(file, {
flags: 'a',
Expand Down

0 comments on commit f5a4908

Please sign in to comment.