Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions lib/internal/vfs/providers/real.js
Original file line number Diff line number Diff line change
Expand Up @@ -458,12 +458,16 @@ class RealFSProvider extends VirtualProvider {

mkdirSync(vfsPath, options) {
const realPath = this.#resolvePath(vfsPath);
return fs.mkdirSync(realPath, options);
const created = fs.mkdirSync(realPath, options);
if (created === undefined) return undefined;
return this.#resolvedToVfsPath(created, vfsPath, 'mkdir');
}

async mkdir(vfsPath, options) {
const realPath = this.#resolvePath(vfsPath);
return fs.promises.mkdir(realPath, options);
const created = await fs.promises.mkdir(realPath, options);
if (created === undefined) return undefined;
return this.#resolvedToVfsPath(created, vfsPath, 'mkdir');
}

rmdirSync(vfsPath) {
Expand Down Expand Up @@ -566,7 +570,13 @@ class RealFSProvider extends VirtualProvider {
// because fs.realpathSync (a JS impl) preserves case but fs.promises.realpath
// (native) canonicalizes the drive letter and other components.
#resolvedToVfsPath(resolved, vfsPath, syscall) {
const rel = path.relative(this.#rootPath, resolved);
// Both sides are put in the Windows long-path form first, because the
// native bindings answer in it (\\?\C:\dir) and path.relative() reads
// that as a different root than a plain rootPath, which would make every
// comparison look like an escape. A no-op elsewhere, and idempotent, so
// a rootPath already in that form is covered too.
const rel = path.relative(path.toNamespacedPath(this.#rootPath),
path.toNamespacedPath(resolved));
if (rel === '') return '/';
if (rel === '..' ||
StringPrototypeStartsWith(rel, '..' + path.sep) ||
Expand Down
13 changes: 13 additions & 0 deletions test/parallel/test-vfs-real-provider-promises.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,19 @@ const myVfs = vfs.create(new vfs.RealFSProvider(root));
assert.deepStrictEqual(entries.sort(), ['sub']);
await myVfs.promises.rmdir('/d/sub');

// Recursive mkdir returns the first directory created, as a VFS-relative
// path (never the backing-directory path), matching MemoryProvider.
const createdSub = await myVfs.promises.mkdir('/d/sub', { recursive: true });
assert.strictEqual(createdSub, '/d/sub');
assert.strictEqual(await myVfs.promises.mkdir('/d/sub', { recursive: true }),
undefined);
const created = await myVfs.promises.mkdir('/d/deep/nested', { recursive: true });
assert.strictEqual(created, '/d/deep');
assert.strictEqual(myVfs.existsSync(created), true);
await myVfs.promises.rmdir('/d/sub');
await myVfs.promises.rmdir('/d/deep/nested');
await myVfs.promises.rmdir('/d/deep');

// rename
await myVfs.promises.writeFile('/old.txt', 'x');
await myVfs.promises.rename('/old.txt', '/new.txt');
Expand Down
45 changes: 44 additions & 1 deletion test/parallel/test-vfs-real-provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
// test-vfs-real-provider-handle.js, and symlinks/path-escape live in
// test-vfs-real-provider-symlinks.js.

require('../common');
const common = require('../common');
const tmpdir = require('../common/tmpdir');
const assert = require('assert');
const fs = require('fs');
Expand Down Expand Up @@ -106,6 +106,49 @@ fs.mkdirSync(testDir, { recursive: true });
fs.rmdirSync(path.join(testDir, 'deep/nested/dir'));
fs.rmdirSync(path.join(testDir, 'deep/nested'));
fs.rmdirSync(path.join(testDir, 'deep'));

// Recursive mkdir returns the first directory created, as a VFS-relative
// path (never the backing-directory path), matching MemoryProvider.
const created = realVfs.mkdirSync('/returned/nested/dir', { recursive: true });
assert.strictEqual(created, '/returned');
assert.strictEqual(realVfs.existsSync(created), true);
assert.strictEqual(
realVfs.mkdirSync('/returned/nested/dir', { recursive: true }),
undefined,
);
assert.strictEqual(realVfs.mkdirSync('/returned/new-dir'),
undefined);
fs.rmdirSync(path.join(testDir, 'returned/nested/dir'));
fs.rmdirSync(path.join(testDir, 'returned/nested'));
fs.rmdirSync(path.join(testDir, 'returned/new-dir'));
fs.rmdirSync(path.join(testDir, 'returned'));
}

// Mounted recursive mkdir returns a path that exists on the real filesystem.
{
const realVfs = vfs.create(new vfs.RealFSProvider(testDir));
const mountPoint = realVfs.mount();
try {
const created = realVfs.mkdirSync(path.join(mountPoint, 'm/nested/dir'),
{ recursive: true });
assert.strictEqual(created, path.join(mountPoint, 'm'));
assert.strictEqual(fs.existsSync(created), true);
} finally {
realVfs.unmount();
fs.rmSync(path.join(testDir, 'm'), { recursive: true, force: true });
}
}

// Recursive mkdir works when the root is given in the Windows long-path
// form, which is what the native bindings hand back: the returned path must
// still be translated relative to the root, not rejected as an escape.
if (common.isWindows) {
const longVfs = vfs.create(new vfs.RealFSProvider(
path.toNamespacedPath(testDir)));
const created = longVfs.mkdirSync('/long/deep/dir', { recursive: true });
assert.strictEqual(created, '/long');
assert.strictEqual(longVfs.existsSync('/long/deep/dir'), true);
fs.rmSync(path.join(testDir, 'long'), { recursive: true, force: true });
}

// unlink
Expand Down
Loading