1// Copyright Joyent, Inc. and other Node contributors. 2// 3// Permission is hereby granted, free of charge, to any person obtaining a 4// copy of this software and associated documentation files (the 5// "Software"), to deal in the Software without restriction, including 6// without limitation the rights to use, copy, modify, merge, publish, 7// distribute, sublicense, and/or sell copies of the Software, and to permit 8// persons to whom the Software is furnished to do so, subject to the 9// following conditions: 10// 11// The above copyright notice and this permission notice shall be included 12// in all copies or substantial portions of the Software. 13// 14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20// USE OR OTHER DEALINGS IN THE SOFTWARE. 21 22'use strict'; 23const common = require('../common'); 24const assert = require('assert'); 25const fs = require('fs'); 26const path = require('path'); 27 28const tmpdir = require('../common/tmpdir'); 29tmpdir.refresh(); 30 31let dirc = 0; 32function nextdir() { 33 return `test${++dirc}`; 34} 35 36// fs.mkdir creates directory using assigned path 37{ 38 const pathname = path.join(tmpdir.path, nextdir()); 39 40 fs.mkdir(pathname, common.mustCall(function(err) { 41 assert.strictEqual(err, null); 42 assert.strictEqual(fs.existsSync(pathname), true); 43 })); 44} 45 46// fs.mkdir creates directory with assigned mode value 47{ 48 const pathname = path.join(tmpdir.path, nextdir()); 49 50 fs.mkdir(pathname, 0o777, common.mustCall(function(err) { 51 assert.strictEqual(err, null); 52 assert.strictEqual(fs.existsSync(pathname), true); 53 })); 54} 55 56// mkdirSync successfully creates directory from given path 57{ 58 const pathname = path.join(tmpdir.path, nextdir()); 59 60 fs.mkdirSync(pathname); 61 62 const exists = fs.existsSync(pathname); 63 assert.strictEqual(exists, true); 64} 65 66// mkdirSync and mkdir require path to be a string, buffer or url. 67// Anything else generates an error. 68[false, 1, {}, [], null, undefined].forEach((i) => { 69 assert.throws( 70 () => fs.mkdir(i, common.mustNotCall()), 71 { 72 code: 'ERR_INVALID_ARG_TYPE', 73 name: 'TypeError' 74 } 75 ); 76 assert.throws( 77 () => fs.mkdirSync(i), 78 { 79 code: 'ERR_INVALID_ARG_TYPE', 80 name: 'TypeError' 81 } 82 ); 83}); 84 85// mkdirpSync when both top-level, and sub-folders do not exist. 86{ 87 const pathname = path.join(tmpdir.path, nextdir(), nextdir()); 88 89 fs.mkdirSync(pathname, { recursive: true }); 90 91 const exists = fs.existsSync(pathname); 92 assert.strictEqual(exists, true); 93 assert.strictEqual(fs.statSync(pathname).isDirectory(), true); 94} 95 96// mkdirpSync when folder already exists. 97{ 98 const pathname = path.join(tmpdir.path, nextdir(), nextdir()); 99 100 fs.mkdirSync(pathname, { recursive: true }); 101 // Should not cause an error. 102 fs.mkdirSync(pathname, { recursive: true }); 103 104 const exists = fs.existsSync(pathname); 105 assert.strictEqual(exists, true); 106 assert.strictEqual(fs.statSync(pathname).isDirectory(), true); 107} 108 109// mkdirpSync ../ 110{ 111 const pathname = `${tmpdir.path}/${nextdir()}/../${nextdir()}/${nextdir()}`; 112 fs.mkdirSync(pathname, { recursive: true }); 113 const exists = fs.existsSync(pathname); 114 assert.strictEqual(exists, true); 115 assert.strictEqual(fs.statSync(pathname).isDirectory(), true); 116} 117 118// mkdirpSync when path is a file. 119{ 120 const pathname = path.join(tmpdir.path, nextdir(), nextdir()); 121 122 fs.mkdirSync(path.dirname(pathname)); 123 fs.writeFileSync(pathname, '', 'utf8'); 124 125 assert.throws( 126 () => { fs.mkdirSync(pathname, { recursive: true }); }, 127 { 128 code: 'EEXIST', 129 message: /EEXIST: .*mkdir/, 130 name: 'Error', 131 syscall: 'mkdir', 132 } 133 ); 134} 135 136// mkdirpSync when part of the path is a file. 137{ 138 const filename = path.join(tmpdir.path, nextdir(), nextdir()); 139 const pathname = path.join(filename, nextdir(), nextdir()); 140 141 fs.mkdirSync(path.dirname(filename)); 142 fs.writeFileSync(filename, '', 'utf8'); 143 144 assert.throws( 145 () => { fs.mkdirSync(pathname, { recursive: true }); }, 146 { 147 code: 'ENOTDIR', 148 message: /ENOTDIR: .*mkdir/, 149 name: 'Error', 150 syscall: 'mkdir', 151 path: pathname // See: https://github.com/nodejs/node/issues/28015 152 } 153 ); 154} 155 156// `mkdirp` when folder does not yet exist. 157{ 158 const pathname = path.join(tmpdir.path, nextdir(), nextdir()); 159 160 fs.mkdir(pathname, { recursive: true }, common.mustCall(function(err) { 161 assert.strictEqual(err, null); 162 assert.strictEqual(fs.existsSync(pathname), true); 163 assert.strictEqual(fs.statSync(pathname).isDirectory(), true); 164 })); 165} 166 167// `mkdirp` when path is a file. 168{ 169 const pathname = path.join(tmpdir.path, nextdir(), nextdir()); 170 171 fs.mkdirSync(path.dirname(pathname)); 172 fs.writeFileSync(pathname, '', 'utf8'); 173 fs.mkdir(pathname, { recursive: true }, common.mustCall((err) => { 174 assert.strictEqual(err.code, 'EEXIST'); 175 assert.strictEqual(err.syscall, 'mkdir'); 176 assert.strictEqual(fs.statSync(pathname).isDirectory(), false); 177 })); 178} 179 180// `mkdirp` when part of the path is a file. 181{ 182 const filename = path.join(tmpdir.path, nextdir(), nextdir()); 183 const pathname = path.join(filename, nextdir(), nextdir()); 184 185 fs.mkdirSync(path.dirname(filename)); 186 fs.writeFileSync(filename, '', 'utf8'); 187 fs.mkdir(pathname, { recursive: true }, common.mustCall((err) => { 188 assert.strictEqual(err.code, 'ENOTDIR'); 189 assert.strictEqual(err.syscall, 'mkdir'); 190 assert.strictEqual(fs.existsSync(pathname), false); 191 // See: https://github.com/nodejs/node/issues/28015 192 // The path field varies slightly in Windows errors, vs., other platforms 193 // see: https://github.com/libuv/libuv/issues/2661, for this reason we 194 // use startsWith() rather than comparing to the full "pathname". 195 assert(err.path.startsWith(filename)); 196 })); 197} 198 199// mkdirpSync dirname loop 200// XXX: windows and smartos have issues removing a directory that you're in. 201if (common.isMainThread && (common.isLinux || common.isOSX)) { 202 const pathname = path.join(tmpdir.path, nextdir()); 203 fs.mkdirSync(pathname); 204 process.chdir(pathname); 205 fs.rmdirSync(pathname); 206 assert.throws( 207 () => { fs.mkdirSync('X', { recursive: true }); }, 208 { 209 code: 'ENOENT', 210 message: /ENOENT: .*mkdir/, 211 name: 'Error', 212 syscall: 'mkdir', 213 } 214 ); 215 fs.mkdir('X', { recursive: true }, (err) => { 216 assert.strictEqual(err.code, 'ENOENT'); 217 assert.strictEqual(err.syscall, 'mkdir'); 218 }); 219} 220 221// mkdirSync and mkdir require options.recursive to be a boolean. 222// Anything else generates an error. 223{ 224 const pathname = path.join(tmpdir.path, nextdir()); 225 ['', 1, {}, [], null, Symbol('test'), () => {}].forEach((recursive) => { 226 const received = common.invalidArgTypeHelper(recursive); 227 assert.throws( 228 () => fs.mkdir(pathname, { recursive }, common.mustNotCall()), 229 { 230 code: 'ERR_INVALID_ARG_TYPE', 231 name: 'TypeError', 232 message: 'The "options.recursive" property must be of type boolean.' + 233 received 234 } 235 ); 236 assert.throws( 237 () => fs.mkdirSync(pathname, { recursive }), 238 { 239 code: 'ERR_INVALID_ARG_TYPE', 240 name: 'TypeError', 241 message: 'The "options.recursive" property must be of type boolean.' + 242 received 243 } 244 ); 245 }); 246} 247 248// `mkdirp` returns first folder created, when all folders are new. 249{ 250 const dir1 = nextdir(); 251 const dir2 = nextdir(); 252 const firstPathCreated = path.join(tmpdir.path, dir1); 253 const pathname = path.join(tmpdir.path, dir1, dir2); 254 255 fs.mkdir(pathname, { recursive: true }, common.mustCall(function(err, path) { 256 assert.strictEqual(err, null); 257 assert.strictEqual(fs.existsSync(pathname), true); 258 assert.strictEqual(fs.statSync(pathname).isDirectory(), true); 259 assert.strictEqual(path, firstPathCreated); 260 })); 261} 262 263// `mkdirp` returns first folder created, when last folder is new. 264{ 265 const dir1 = nextdir(); 266 const dir2 = nextdir(); 267 const pathname = path.join(tmpdir.path, dir1, dir2); 268 fs.mkdirSync(path.join(tmpdir.path, dir1)); 269 fs.mkdir(pathname, { recursive: true }, common.mustCall(function(err, path) { 270 assert.strictEqual(err, null); 271 assert.strictEqual(fs.existsSync(pathname), true); 272 assert.strictEqual(fs.statSync(pathname).isDirectory(), true); 273 assert.strictEqual(path, pathname); 274 })); 275} 276 277// `mkdirp` returns undefined, when no new folders are created. 278{ 279 const dir1 = nextdir(); 280 const dir2 = nextdir(); 281 const pathname = path.join(tmpdir.path, dir1, dir2); 282 fs.mkdirSync(path.join(tmpdir.path, dir1, dir2), { recursive: true }); 283 fs.mkdir(pathname, { recursive: true }, common.mustCall(function(err, path) { 284 assert.strictEqual(err, null); 285 assert.strictEqual(fs.existsSync(pathname), true); 286 assert.strictEqual(fs.statSync(pathname).isDirectory(), true); 287 assert.strictEqual(path, undefined); 288 })); 289} 290 291// `mkdirp.sync` returns first folder created, when all folders are new. 292{ 293 const dir1 = nextdir(); 294 const dir2 = nextdir(); 295 const firstPathCreated = path.join(tmpdir.path, dir1); 296 const pathname = path.join(tmpdir.path, dir1, dir2); 297 const p = fs.mkdirSync(pathname, { recursive: true }); 298 assert.strictEqual(fs.existsSync(pathname), true); 299 assert.strictEqual(fs.statSync(pathname).isDirectory(), true); 300 assert.strictEqual(p, firstPathCreated); 301} 302 303// `mkdirp.sync` returns first folder created, when last folder is new. 304{ 305 const dir1 = nextdir(); 306 const dir2 = nextdir(); 307 const pathname = path.join(tmpdir.path, dir1, dir2); 308 fs.mkdirSync(path.join(tmpdir.path, dir1), { recursive: true }); 309 const p = fs.mkdirSync(pathname, { recursive: true }); 310 assert.strictEqual(fs.existsSync(pathname), true); 311 assert.strictEqual(fs.statSync(pathname).isDirectory(), true); 312 assert.strictEqual(p, pathname); 313} 314 315// `mkdirp.sync` returns undefined, when no new folders are created. 316{ 317 const dir1 = nextdir(); 318 const dir2 = nextdir(); 319 const pathname = path.join(tmpdir.path, dir1, dir2); 320 fs.mkdirSync(path.join(tmpdir.path, dir1, dir2), { recursive: true }); 321 const p = fs.mkdirSync(pathname, { recursive: true }); 322 assert.strictEqual(fs.existsSync(pathname), true); 323 assert.strictEqual(fs.statSync(pathname).isDirectory(), true); 324 assert.strictEqual(p, undefined); 325} 326 327// `mkdirp.promises` returns first folder created, when all folders are new. 328{ 329 const dir1 = nextdir(); 330 const dir2 = nextdir(); 331 const firstPathCreated = path.join(tmpdir.path, dir1); 332 const pathname = path.join(tmpdir.path, dir1, dir2); 333 async function testCase() { 334 const p = await fs.promises.mkdir(pathname, { recursive: true }); 335 assert.strictEqual(fs.existsSync(pathname), true); 336 assert.strictEqual(fs.statSync(pathname).isDirectory(), true); 337 assert.strictEqual(p, firstPathCreated); 338 } 339 testCase(); 340} 341 342// Keep the event loop alive so the async mkdir() requests 343// have a chance to run (since they don't ref the event loop). 344process.nextTick(() => {}); 345