node/test/parallel/test-fs-realpath.js
Antoine du Hamel 4a54a80aa3
test: replace .substr with .slice
`String.prototype.substr` is deprecated, and using it will raise an
error when using ESLint 9+.

Co-authored-by: =?UTF-8?q?Micha=C3=ABl=20Zasso?= <targos@protonmail.com>
PR-URL: https://github.com/nodejs/node/pull/53070
Reviewed-By: Michaël Zasso <targos@protonmail.com>
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Yagiz Nizipli <yagiz.nizipli@sentry.io>
Reviewed-By: Juan José Arboleda <soyjuanarbol@gmail.com>
Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
Reviewed-By: Tierney Cyren <hello@bnb.im>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
2024-05-22 05:08:59 +00:00

619 lines
19 KiB
JavaScript

// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const common = require('../common');
const fixtures = require('../common/fixtures');
const tmpdir = require('../common/tmpdir');
if (!common.isMainThread)
common.skip('process.chdir is not available in Workers');
const assert = require('assert');
const fs = require('fs');
const path = require('path');
let async_completed = 0;
let async_expected = 0;
const unlink = [];
const skipSymlinks = !common.canCreateSymLink();
const tmpDir = tmpdir.path;
tmpdir.refresh();
let root = '/';
let assertEqualPath = assert.strictEqual;
if (common.isWindows) {
// Something like "C:\\"
root = process.cwd().slice(0, 3);
assertEqualPath = function(path_left, path_right, message) {
assert
.strictEqual(path_left.toLowerCase(), path_right.toLowerCase(), message);
};
}
process.nextTick(runTest);
function tmp(p) {
return path.join(tmpDir, p);
}
const targetsAbsDir = path.join(tmpDir, 'targets');
const tmpAbsDir = tmpDir;
// Set up targetsAbsDir and expected subdirectories
fs.mkdirSync(targetsAbsDir);
fs.mkdirSync(path.join(targetsAbsDir, 'nested-index'));
fs.mkdirSync(path.join(targetsAbsDir, 'nested-index', 'one'));
fs.mkdirSync(path.join(targetsAbsDir, 'nested-index', 'two'));
function asynctest(testBlock, args, callback, assertBlock) {
async_expected++;
testBlock.apply(testBlock, args.concat(function(err) {
let ignoreError = false;
if (assertBlock) {
try {
ignoreError = assertBlock.apply(assertBlock, arguments);
} catch (e) {
err = e;
}
}
async_completed++;
callback(ignoreError ? null : err);
}));
}
// sub-tests:
function test_simple_error_callback(realpath, realpathSync, cb) {
realpath('/this/path/does/not/exist', common.mustCall(function(err, s) {
assert(err);
assert(!s);
cb();
}));
}
function test_simple_error_cb_with_null_options(realpath, realpathSync, cb) {
realpath('/this/path/does/not/exist', null, common.mustCall(function(err, s) {
assert(err);
assert(!s);
cb();
}));
}
function test_simple_relative_symlink(realpath, realpathSync, callback) {
console.log('test_simple_relative_symlink');
if (skipSymlinks) {
common.printSkipMessage('symlink test (no privs)');
return callback();
}
const entry = `${tmpDir}/symlink`;
const expected = `${tmpDir}/cycles/root.js`;
[
[entry, `../${path.basename(tmpDir)}/cycles/root.js`],
].forEach(function(t) {
try { fs.unlinkSync(t[0]); } catch {
// Continue regardless of error.
}
console.log('fs.symlinkSync(%j, %j, %j)', t[1], t[0], 'file');
fs.symlinkSync(t[1], t[0], 'file');
unlink.push(t[0]);
});
const result = realpathSync(entry);
assertEqualPath(result, path.resolve(expected));
asynctest(realpath, [entry], callback, function(err, result) {
assertEqualPath(result, path.resolve(expected));
});
}
function test_simple_absolute_symlink(realpath, realpathSync, callback) {
console.log('test_simple_absolute_symlink');
// This one should still run, even if skipSymlinks is set,
// because it uses a junction.
const type = skipSymlinks ? 'junction' : 'dir';
console.log('using type=%s', type);
const entry = `${tmpAbsDir}/symlink`;
const expected = fixtures.path('nested-index', 'one');
[
[entry, expected],
].forEach(function(t) {
try { fs.unlinkSync(t[0]); } catch {
// Continue regardless of error.
}
console.error('fs.symlinkSync(%j, %j, %j)', t[1], t[0], type);
fs.symlinkSync(t[1], t[0], type);
unlink.push(t[0]);
});
const result = realpathSync(entry);
assertEqualPath(result, path.resolve(expected));
asynctest(realpath, [entry], callback, function(err, result) {
assertEqualPath(result, path.resolve(expected));
});
}
function test_deep_relative_file_symlink(realpath, realpathSync, callback) {
console.log('test_deep_relative_file_symlink');
if (skipSymlinks) {
common.printSkipMessage('symlink test (no privs)');
return callback();
}
const expected = fixtures.path('cycles', 'root.js');
const linkData1 = path
.relative(path.join(targetsAbsDir, 'nested-index', 'one'),
expected);
const linkPath1 = path.join(targetsAbsDir,
'nested-index', 'one', 'symlink1.js');
try { fs.unlinkSync(linkPath1); } catch {
// Continue regardless of error.
}
fs.symlinkSync(linkData1, linkPath1, 'file');
const linkData2 = '../one/symlink1.js';
const entry = path.join(targetsAbsDir,
'nested-index', 'two', 'symlink1-b.js');
try { fs.unlinkSync(entry); } catch {
// Continue regardless of error.
}
fs.symlinkSync(linkData2, entry, 'file');
unlink.push(linkPath1);
unlink.push(entry);
assertEqualPath(realpathSync(entry), path.resolve(expected));
asynctest(realpath, [entry], callback, function(err, result) {
assertEqualPath(result, path.resolve(expected));
});
}
function test_deep_relative_dir_symlink(realpath, realpathSync, callback) {
console.log('test_deep_relative_dir_symlink');
if (skipSymlinks) {
common.printSkipMessage('symlink test (no privs)');
return callback();
}
const expected = fixtures.path('cycles', 'folder');
const path1b = path.join(targetsAbsDir, 'nested-index', 'one');
const linkPath1b = path.join(path1b, 'symlink1-dir');
const linkData1b = path.relative(path1b, expected);
try { fs.unlinkSync(linkPath1b); } catch {
// Continue regardless of error.
}
fs.symlinkSync(linkData1b, linkPath1b, 'dir');
const linkData2b = '../one/symlink1-dir';
const entry = path.join(targetsAbsDir,
'nested-index', 'two', 'symlink12-dir');
try { fs.unlinkSync(entry); } catch {
// Continue regardless of error.
}
fs.symlinkSync(linkData2b, entry, 'dir');
unlink.push(linkPath1b);
unlink.push(entry);
assertEqualPath(realpathSync(entry), path.resolve(expected));
asynctest(realpath, [entry], callback, function(err, result) {
assertEqualPath(result, path.resolve(expected));
});
}
function test_cyclic_link_protection(realpath, realpathSync, callback) {
console.log('test_cyclic_link_protection');
if (skipSymlinks) {
common.printSkipMessage('symlink test (no privs)');
return callback();
}
const entry = path.join(tmpDir, '/cycles/realpath-3a');
[
[entry, '../cycles/realpath-3b'],
[path.join(tmpDir, '/cycles/realpath-3b'), '../cycles/realpath-3c'],
[path.join(tmpDir, '/cycles/realpath-3c'), '../cycles/realpath-3a'],
].forEach(function(t) {
try { fs.unlinkSync(t[0]); } catch {
// Continue regardless of error.
}
fs.symlinkSync(t[1], t[0], 'dir');
unlink.push(t[0]);
});
assert.throws(() => {
realpathSync(entry);
}, { code: 'ELOOP', name: 'Error' });
asynctest(
realpath, [entry], callback, common.mustCall(function(err, result) {
assert.strictEqual(err.path, entry);
assert.strictEqual(result, undefined);
return true;
}));
}
function test_cyclic_link_overprotection(realpath, realpathSync, callback) {
console.log('test_cyclic_link_overprotection');
if (skipSymlinks) {
common.printSkipMessage('symlink test (no privs)');
return callback();
}
const cycles = `${tmpDir}/cycles`;
const expected = realpathSync(cycles);
const folder = `${cycles}/folder`;
const link = `${folder}/cycles`;
let testPath = cycles;
testPath += '/folder/cycles'.repeat(10);
try { fs.unlinkSync(link); } catch {
// Continue regardless of error.
}
fs.symlinkSync(cycles, link, 'dir');
unlink.push(link);
assertEqualPath(realpathSync(testPath), path.resolve(expected));
asynctest(realpath, [testPath], callback, function(er, res) {
assertEqualPath(res, path.resolve(expected));
});
}
function test_relative_input_cwd(realpath, realpathSync, callback) {
console.log('test_relative_input_cwd');
if (skipSymlinks) {
common.printSkipMessage('symlink test (no privs)');
return callback();
}
// We need to calculate the relative path to the tmp dir from cwd
const entrydir = process.cwd();
const entry = path.relative(entrydir,
path.join(`${tmpDir}/cycles/realpath-3a`));
const expected = `${tmpDir}/cycles/root.js`;
[
[entry, '../cycles/realpath-3b'],
[`${tmpDir}/cycles/realpath-3b`, '../cycles/realpath-3c'],
[`${tmpDir}/cycles/realpath-3c`, 'root.js'],
].forEach(function(t) {
const fn = t[0];
console.error('fn=%j', fn);
try { fs.unlinkSync(fn); } catch {
// Continue regardless of error.
}
const b = path.basename(t[1]);
const type = (b === 'root.js' ? 'file' : 'dir');
console.log('fs.symlinkSync(%j, %j, %j)', t[1], fn, type);
fs.symlinkSync(t[1], fn, 'file');
unlink.push(fn);
});
const origcwd = process.cwd();
process.chdir(entrydir);
assertEqualPath(realpathSync(entry), path.resolve(expected));
asynctest(realpath, [entry], callback, function(err, result) {
process.chdir(origcwd);
assertEqualPath(result, path.resolve(expected));
return true;
});
}
function test_deep_symlink_mix(realpath, realpathSync, callback) {
console.log('test_deep_symlink_mix');
if (common.isWindows) {
// This one is a mix of files and directories, and it's quite tricky
// to get the file/dir links sorted out correctly.
common.printSkipMessage('symlink test (no privs)');
return callback();
}
// /tmp/node-test-realpath-f1 -> $tmpDir/node-test-realpath-d1/foo
// /tmp/node-test-realpath-d1 -> $tmpDir/node-test-realpath-d2
// /tmp/node-test-realpath-d2/foo -> $tmpDir/node-test-realpath-f2
// /tmp/node-test-realpath-f2
// -> $tmpDir/targets/nested-index/one/realpath-c
// $tmpDir/targets/nested-index/one/realpath-c
// -> $tmpDir/targets/nested-index/two/realpath-c
// $tmpDir/targets/nested-index/two/realpath-c -> $tmpDir/cycles/root.js
// $tmpDir/targets/cycles/root.js (hard)
const entry = tmp('node-test-realpath-f1');
try { fs.unlinkSync(tmp('node-test-realpath-d2/foo')); } catch {
// Continue regardless of error.
}
try { fs.rmdirSync(tmp('node-test-realpath-d2')); } catch {
// Continue regardless of error.
}
fs.mkdirSync(tmp('node-test-realpath-d2'), 0o700);
try {
[
[entry, `${tmpDir}/node-test-realpath-d1/foo`],
[tmp('node-test-realpath-d1'),
`${tmpDir}/node-test-realpath-d2`],
[tmp('node-test-realpath-d2/foo'), '../node-test-realpath-f2'],
[tmp('node-test-realpath-f2'),
`${targetsAbsDir}/nested-index/one/realpath-c`],
[`${targetsAbsDir}/nested-index/one/realpath-c`,
`${targetsAbsDir}/nested-index/two/realpath-c`],
[`${targetsAbsDir}/nested-index/two/realpath-c`,
`${tmpDir}/cycles/root.js`],
].forEach(function(t) {
try { fs.unlinkSync(t[0]); } catch {
// Continue regardless of error.
}
fs.symlinkSync(t[1], t[0]);
unlink.push(t[0]);
});
} finally {
unlink.push(tmp('node-test-realpath-d2'));
}
const expected = `${tmpAbsDir}/cycles/root.js`;
assertEqualPath(realpathSync(entry), path.resolve(expected));
asynctest(realpath, [entry], callback, function(err, result) {
assertEqualPath(result, path.resolve(expected));
return true;
});
}
function test_non_symlinks(realpath, realpathSync, callback) {
console.log('test_non_symlinks');
const entrydir = path.dirname(tmpAbsDir);
const entry = `${tmpAbsDir.slice(entrydir.length + 1)}/cycles/root.js`;
const expected = `${tmpAbsDir}/cycles/root.js`;
const origcwd = process.cwd();
process.chdir(entrydir);
assertEqualPath(realpathSync(entry), path.resolve(expected));
asynctest(realpath, [entry], callback, function(err, result) {
process.chdir(origcwd);
assertEqualPath(result, path.resolve(expected));
return true;
});
}
const upone = path.join(process.cwd(), '..');
function test_escape_cwd(realpath, realpathSync, cb) {
console.log('test_escape_cwd');
asynctest(realpath, ['..'], cb, function(er, uponeActual) {
assertEqualPath(
upone, uponeActual,
`realpath("..") expected: ${path.resolve(upone)} actual:${uponeActual}`);
});
}
function test_upone_actual(realpath, realpathSync, cb) {
console.log('test_upone_actual');
const uponeActual = realpathSync('..');
assertEqualPath(upone, uponeActual);
cb();
}
// Going up with .. multiple times
// .
// `-- a/
// |-- b/
// | `-- e -> ..
// `-- d -> ..
// realpath(a/b/e/d/a/b/e/d/a) ==> a
function test_up_multiple(realpath, realpathSync, cb) {
console.error('test_up_multiple');
if (skipSymlinks) {
common.printSkipMessage('symlink test (no privs)');
return cb();
}
const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
fs.mkdirSync(tmp('a'), 0o755);
fs.mkdirSync(tmp('a/b'), 0o755);
fs.symlinkSync('..', tmp('a/d'), 'dir');
unlink.push(tmp('a/d'));
fs.symlinkSync('..', tmp('a/b/e'), 'dir');
unlink.push(tmp('a/b/e'));
const abedabed = tmp('abedabed'.split('').join('/'));
const abedabed_real = tmp('');
const abedabeda = tmp('abedabeda'.split('').join('/'));
const abedabeda_real = tmp('a');
assertEqualPath(realpathSync(abedabeda), abedabeda_real);
assertEqualPath(realpathSync(abedabed), abedabed_real);
realpath(abedabeda, function(er, real) {
assert.ifError(er);
assertEqualPath(abedabeda_real, real);
realpath(abedabed, function(er, real) {
assert.ifError(er);
assertEqualPath(abedabed_real, real);
cb();
});
});
}
// Going up with .. multiple times with options = null
// .
// `-- a/
// |-- b/
// | `-- e -> ..
// `-- d -> ..
// realpath(a/b/e/d/a/b/e/d/a) ==> a
function test_up_multiple_with_null_options(realpath, realpathSync, cb) {
console.error('test_up_multiple');
if (skipSymlinks) {
common.printSkipMessage('symlink test (no privs)');
return cb();
}
const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
fs.mkdirSync(tmp('a'), 0o755);
fs.mkdirSync(tmp('a/b'), 0o755);
fs.symlinkSync('..', tmp('a/d'), 'dir');
unlink.push(tmp('a/d'));
fs.symlinkSync('..', tmp('a/b/e'), 'dir');
unlink.push(tmp('a/b/e'));
const abedabed = tmp('abedabed'.split('').join('/'));
const abedabed_real = tmp('');
const abedabeda = tmp('abedabeda'.split('').join('/'));
const abedabeda_real = tmp('a');
assertEqualPath(realpathSync(abedabeda), abedabeda_real);
assertEqualPath(realpathSync(abedabed), abedabed_real);
realpath(abedabeda, null, function(er, real) {
assert.ifError(er);
assertEqualPath(abedabeda_real, real);
realpath(abedabed, null, function(er, real) {
assert.ifError(er);
assertEqualPath(abedabed_real, real);
cb();
});
});
}
// Absolute symlinks with children.
// .
// `-- a/
// |-- b/
// | `-- c/
// | `-- x.txt
// `-- link -> /tmp/node-test-realpath-abs-kids/a/b/
// realpath(root+'/a/link/c/x.txt') ==> root+'/a/b/c/x.txt'
function test_abs_with_kids(realpath, realpathSync, cb) {
console.log('test_abs_with_kids');
// This one should still run, even if skipSymlinks is set,
// because it uses a junction.
const type = skipSymlinks ? 'junction' : 'dir';
console.log('using type=%s', type);
const root = `${tmpAbsDir}/node-test-realpath-abs-kids`;
function cleanup() {
['/a/b/c/x.txt',
'/a/link',
].forEach(function(file) {
try { fs.unlinkSync(root + file); } catch {
// Continue regardless of error.
}
});
['/a/b/c',
'/a/b',
'/a',
'',
].forEach(function(folder) {
try { fs.rmdirSync(root + folder); } catch {
// Continue regardless of error.
}
});
}
function setup() {
cleanup();
['',
'/a',
'/a/b',
'/a/b/c',
].forEach(function(folder) {
console.log(`mkdir ${root}${folder}`);
fs.mkdirSync(root + folder, 0o700);
});
fs.writeFileSync(`${root}/a/b/c/x.txt`, 'foo');
fs.symlinkSync(`${root}/a/b`, `${root}/a/link`, type);
}
setup();
const linkPath = `${root}/a/link/c/x.txt`;
const expectPath = `${root}/a/b/c/x.txt`;
const actual = realpathSync(linkPath);
// console.log({link:linkPath,expect:expectPath,actual:actual},'sync');
assertEqualPath(actual, path.resolve(expectPath));
asynctest(realpath, [linkPath], cb, function(er, actual) {
// console.log({link:linkPath,expect:expectPath,actual:actual},'async');
assertEqualPath(actual, path.resolve(expectPath));
cleanup();
});
}
function test_root(realpath, realpathSync, cb) {
assertEqualPath(root, realpathSync('/'));
realpath('/', function(err, result) {
assert.ifError(err);
assertEqualPath(root, result);
cb();
});
}
function test_root_with_null_options(realpath, realpathSync, cb) {
realpath('/', null, function(err, result) {
assert.ifError(err);
assertEqualPath(root, result);
cb();
});
}
// ----------------------------------------------------------------------------
const tests = [
test_simple_error_callback,
test_simple_error_cb_with_null_options,
test_simple_relative_symlink,
test_simple_absolute_symlink,
test_deep_relative_file_symlink,
test_deep_relative_dir_symlink,
test_cyclic_link_protection,
test_cyclic_link_overprotection,
test_relative_input_cwd,
test_deep_symlink_mix,
test_non_symlinks,
test_escape_cwd,
test_upone_actual,
test_abs_with_kids,
test_up_multiple,
test_up_multiple_with_null_options,
test_root,
test_root_with_null_options,
];
const numtests = tests.length;
let testsRun = 0;
function runNextTest(err) {
assert.ifError(err);
const test = tests.shift();
if (!test) {
return console.log(`${numtests} subtests completed OK for fs.realpath`);
}
testsRun++;
test(fs.realpath, fs.realpathSync, common.mustSucceed(() => {
testsRun++;
test(fs.realpath.native,
fs.realpathSync.native,
common.mustCall(runNextTest));
}));
}
function runTest() {
const tmpDirs = ['cycles', 'cycles/folder'];
tmpDirs.forEach(function(t) {
t = tmp(t);
fs.mkdirSync(t, 0o700);
});
fs.writeFileSync(tmp('cycles/root.js'), "console.error('roooot!');");
console.error('start tests');
runNextTest();
}
process.on('exit', function() {
assert.strictEqual(2 * numtests, testsRun);
assert.strictEqual(async_completed, async_expected);
});