test: add and use tmpdir.hasEnoughSpace()

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 hasEnoughSpace() to the tmpdir module
that uses statfsSync() to guess whether allocating a certain amount of
space within the temporary directory will succeed.

This change also updates the most frequently failing tests to use the
new function such that the relevant parts of the tests are skipped if
tmpdir has insufficient space.

Refs: https://github.com/nodejs/reliability/issues/549
PR-URL: https://github.com/nodejs/node/pull/47767
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
Reviewed-By: Richard Lau <rlau@redhat.com>
This commit is contained in:
Tobias Nießen
2023-05-01 17:23:49 +02:00
committed by GitHub
parent d225d95758
commit 609d2b0ff2
5 changed files with 50 additions and 20 deletions

View File

@@ -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.
### `hasEnoughSpace(size)`
* `size` [\<number>][<number>] Required size, in bytes.
Returns `true` if the available blocks of the file system underlying `path`
are likely sufficient 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 inaccurate and susceptible to race conditions.
## UDP pair helper
The `common/udppair` module exports a function `makeUDPPair` and a class

View File

@@ -69,7 +69,13 @@ function onexit(useSpawn) {
}
}
function hasEnoughSpace(size) {
const { bavail, bsize } = fs.statfsSync(tmpPath);
return bavail >= Math.ceil(size / bsize);
}
module.exports = {
path: tmpPath,
refresh,
hasEnoughSpace,
};

View File

@@ -106,17 +106,22 @@ async function doReadAndCancel() {
// Variable taken from https://github.com/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);
if (!tmpdir.hasEnoughSpace(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');
const fileHandle = await open(newFile, 'r');
await assert.rejects(fileHandle.readFile(), {
name: 'RangeError',
code: 'ERR_FS_FILE_TOO_LARGE'
});
await fileHandle.close();
await assert.rejects(fileHandle.readFile(), {
name: 'RangeError',
code: 'ERR_FS_FILE_TOO_LARGE'
});
await fileHandle.close();
}
}
}

View File

@@ -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.hasEnoughSpace(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' });
}
}
{

View File

@@ -16,6 +16,10 @@ if (common.isAIX && (Number(cp.execSync('ulimit -f')) * 512) < kStringMaxLength)
const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
if (!tmpdir.hasEnoughSpace(kStringMaxLength)) {
common.skip(`Not enough space in ${tmpdir.path}`);
}
const file = path.join(tmpdir.path, 'toobig.txt');
const stream = fs.createWriteStream(file, {
flags: 'a',