Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support fs.mkdir recursive option for Node.js >=10.12.0 #268

Merged
merged 2 commits into from
May 17, 2019
Merged
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
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ node_js:
- "6"
- "8"
- "10"
- "11"
- "12"
33 changes: 19 additions & 14 deletions lib/binding.js
Original file line number Diff line number Diff line change
Expand Up @@ -931,27 +931,32 @@ Binding.prototype.mkdir = function(pathname, mode, recursive, callback, ctx) {
recursive = false;
}

if (recursive) {
notImplemented();
}

markSyscall(ctx, 'mkdir');

maybeCallback(normalizeCallback(callback), ctx, this, function() {
const item = _system.getItem(pathname);
if (item) {
throw new FSError('EEXIST', pathname);
}
const parent = _system.getItem(path.dirname(pathname));
if (!parent) {
throw new FSError('ENOENT', pathname);
}
this.access(path.dirname(pathname), parseInt('0002', 8));
const dir = new Directory();
if (mode) {
dir.setMode(mode);
}
parent.addItem(path.basename(pathname), dir);

const _mkdir = function(_pathname) {
const parentDir = path.dirname(_pathname);
let parent = _system.getItem(parentDir);
if (!parent) {
if (!recursive) {
throw new FSError('ENOENT', _pathname);
}
parent = _mkdir(parentDir, true);
}
this.access(parentDir, parseInt('0002', 8));
const dir = new Directory();
if (mode) {
dir.setMode(mode);
}
return parent.addItem(path.basename(_pathname), dir);
}.bind(this);

_mkdir(pathname);
});
};

Expand Down
57 changes: 57 additions & 0 deletions test/lib/binding.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1179,6 +1179,63 @@ describe('Binding', function() {
});
});

describe('#mkdir() recursive', function() {
it('creates a new directory', function() {
const binding = new Binding(system);
const dirPath = path.join('mock-dir', 'foo');
binding.mkdir(dirPath, parseInt('0755', 8), true);
const dir = system.getItem(dirPath);
assert.instanceOf(dir, Directory);
assert.equal(dir.getMode(), parseInt('0755', 8));
});

it('creates a new deep directory', function() {
const binding = new Binding(system);
const dirPath1 = path.join('mock-dir', 'foo');
const dirPath2 = path.join(dirPath1, 'bar');
const dirPath3 = path.join(dirPath2, 'loo');
binding.mkdir(dirPath3, parseInt('0755', 8), true);

let dir = system.getItem(dirPath3);
assert.instanceOf(dir, Directory);
assert.equal(dir.getMode(), parseInt('0755', 8));

dir = system.getItem(dirPath2);
assert.instanceOf(dir, Directory);
assert.equal(dir.getMode(), parseInt('0755', 8));

dir = system.getItem(dirPath1);
assert.instanceOf(dir, Directory);
assert.equal(dir.getMode(), parseInt('0755', 8));
});

it('fails if permission does not allow recursive creation', function() {
const binding = new Binding(system);
const dirPath1 = path.join('mock-dir', 'foo');
const dirPath2 = path.join(dirPath1, 'bar');
const dirPath3 = path.join(dirPath2, 'loo');
assert.throws(function() {
binding.mkdir(dirPath3, parseInt('0400', 8), true);
});
});

it('fails if one parent is not a folder', function() {
const binding = new Binding(system);
const dirPath = path.join('mock-dir', 'one.txt', 'foo', 'bar');
assert.throws(function() {
binding.mkdir(dirPath, parseInt('0755', 8), true);
});
});

it('fails if file exists', function() {
const binding = new Binding(system);
const dirPath = path.join('mock-dir', 'non-empty', 'a.txt');
assert.throws(function() {
binding.mkdir(dirPath, parseInt('0755', 8), true);
});
});
});

describe('#mkdtemp()', function() {
it('creates a new directory', function() {
const binding = new Binding(system);
Expand Down
115 changes: 115 additions & 0 deletions test/lib/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2055,6 +2055,7 @@ describe('Mocking the file system', function() {
beforeEach(function() {
mock({
parent: {},
'file.txt': '',
unwriteable: mock.directory({mode: parseInt('0555', 8)})
});
});
Expand All @@ -2071,6 +2072,23 @@ describe('Mocking the file system', function() {
});
});

inVersion('>=10.12').it('creates a new directory recursively', function(
done
) {
fs.mkdir('parent/foo/bar/dir', {recursive: true}, function(err) {
if (err) {
return done(err);
}
let stats = fs.statSync('parent/foo/bar/dir');
assert.isTrue(stats.isDirectory());
stats = fs.statSync('parent/foo/bar');
assert.isTrue(stats.isDirectory());
stats = fs.statSync('parent/foo');
assert.isTrue(stats.isDirectory());
done();
});
});

it('accepts dir mode', function(done) {
fs.mkdir('parent/dir', parseInt('0755', 8), function(err) {
if (err) {
Expand All @@ -2083,13 +2101,61 @@ describe('Mocking the file system', function() {
});
});

inVersion('>=10.12').it('accepts dir mode recursively', function(done) {
fs.mkdir(
'parent/foo/bar/dir',
{recursive: true, mode: parseInt('0755', 8)},
function(err) {
if (err) {
return done(err);
}
let stats = fs.statSync('parent/foo/bar/dir');
assert.isTrue(stats.isDirectory());
assert.equal(stats.mode & parseInt('0777', 8), parseInt('0755', 8));

stats = fs.statSync('parent/foo/bar');
assert.isTrue(stats.isDirectory());
assert.equal(stats.mode & parseInt('0777', 8), parseInt('0755', 8));

stats = fs.statSync('parent/foo');
assert.isTrue(stats.isDirectory());
assert.equal(stats.mode & parseInt('0777', 8), parseInt('0755', 8));
done();
}
);
});

it('fails if parent does not exist', function(done) {
fs.mkdir('parent/bogus/dir', function(err) {
assert.instanceOf(err, Error);
done();
});
});

inVersion('>=10.12').it(
'fails if one parent is not a folder in recursive creation',
function(done) {
fs.mkdir('file.txt/bogus/dir', {recursive: true}, function(err) {
assert.instanceOf(err, Error);
done();
});
}
);

inVersion('>=10.12').it(
'fails if permission does not allow recursive creation',
function(done) {
fs.mkdir(
'parent/foo/bar/dir',
{recursive: true, mode: parseInt('0400', 8)},
function(err) {
assert.instanceOf(err, Error);
done();
}
);
}
);

it('fails if directory already exists', function(done) {
fs.mkdir('parent', function(err) {
assert.instanceOf(err, Error);
Expand Down Expand Up @@ -2137,19 +2203,68 @@ describe('Mocking the file system', function() {
assert.isTrue(stats.isDirectory());
});

inVersion('>=10.12').it('creates a new directory recursively', function() {
fs.mkdirSync('parent/foo/bar/dir', {recursive: true});
let stats = fs.statSync('parent/foo/bar/dir');
assert.isTrue(stats.isDirectory());
stats = fs.statSync('parent/foo/bar');
assert.isTrue(stats.isDirectory());
stats = fs.statSync('parent/foo');
assert.isTrue(stats.isDirectory());
});

it('accepts dir mode', function() {
fs.mkdirSync('parent/dir', parseInt('0755', 8));
const stats = fs.statSync('parent/dir');
assert.isTrue(stats.isDirectory());
assert.equal(stats.mode & parseInt('0777', 8), parseInt('0755', 8));
});

inVersion('>=10.12').it('accepts dir mode recursively', function() {
fs.mkdirSync('parent/foo/bar/dir', {
recursive: true,
mode: parseInt('0755', 8)
});
let stats = fs.statSync('parent/foo/bar/dir');
assert.isTrue(stats.isDirectory());
assert.equal(stats.mode & parseInt('0777', 8), parseInt('0755', 8));

stats = fs.statSync('parent/foo/bar');
assert.isTrue(stats.isDirectory());
assert.equal(stats.mode & parseInt('0777', 8), parseInt('0755', 8));

stats = fs.statSync('parent/foo');
assert.isTrue(stats.isDirectory());
assert.equal(stats.mode & parseInt('0777', 8), parseInt('0755', 8));
});

it('fails if parent does not exist', function() {
assert.throws(function() {
fs.mkdirSync('parent/bogus/dir');
});
});

inVersion('>=10.12').it(
'fails if one parent is not a folder in recursive creation',
function() {
assert.throws(function() {
fs.mkdirSync('file.txt/bogus/dir', {recursive: true});
});
}
);

inVersion('>=10.12').it(
'fails if permission does not allow recursive creation',
function() {
assert.throws(function() {
fs.mkdirSync('parent/foo/bar/dir', {
recursive: true,
mode: parseInt('0400', 8)
});
});
}
);

it('fails if directory already exists', function() {
assert.throws(function() {
fs.mkdirSync('parent');
Expand Down