diff options
Diffstat (limited to 'test/parallel/test-fs-realpath.js')
-rw-r--r-- | test/parallel/test-fs-realpath.js | 611 |
1 files changed, 611 insertions, 0 deletions
diff --git a/test/parallel/test-fs-realpath.js b/test/parallel/test-fs-realpath.js new file mode 100644 index 0000000000..9b02ae7173 --- /dev/null +++ b/test/parallel/test-fs-realpath.js @@ -0,0 +1,611 @@ +// 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. + +var common = require('../common'); +var assert = require('assert'); +var fs = require('fs'); +var path = require('path'); +var exec = require('child_process').exec; +var async_completed = 0, async_expected = 0, unlink = []; +var isWindows = process.platform === 'win32'; +var skipSymlinks = false; + +var root = '/'; +if (isWindows) { + // something like "C:\\" + root = process.cwd().substr(0, 3); + + // On Windows, creating symlinks requires admin privileges. + // We'll only try to run symlink test if we have enough privileges. + try { + exec('whoami /priv', function(err, o) { + if (err || o.indexOf('SeCreateSymbolicLinkPrivilege') == -1) { + skipSymlinks = true; + } + runTest(); + }); + } catch (er) { + // better safe than sorry + skipSymlinks = true; + process.nextTick(runTest); + } +} else { + process.nextTick(runTest); +} + + +function tmp(p) { + return path.join(common.tmpDir, p); +} + +var fixturesAbsDir = common.fixturesDir; +var tmpAbsDir = common.tmpDir; + +console.error("absolutes\n%s\n%s", fixturesAbsDir, tmpAbsDir); + +function asynctest(testBlock, args, callback, assertBlock) { + async_expected++; + testBlock.apply(testBlock, args.concat(function(err) { + var 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(cb) { + var ncalls = 0; + + fs.realpath('/this/path/does/not/exist', function(err, s) { + assert(err); + assert(!s); + ncalls++; + cb(); + }); + + process.on('exit', function() { + assert.equal(ncalls, 1); + }); +} + +function test_simple_relative_symlink(callback) { + console.log('test_simple_relative_symlink'); + if (skipSymlinks) { + console.log('skipping symlink test (no privs)'); + return runNextTest(); + } + var entry = common.tmpDir + '/symlink', + expected = common.tmpDir + '/cycles/root.js'; + [ + [entry, '../' + common.tmpDirName + '/cycles/root.js'] + ].forEach(function(t) { + try {fs.unlinkSync(t[0]);}catch (e) {} + console.log('fs.symlinkSync(%j, %j, %j)', t[1], t[0], 'file'); + fs.symlinkSync(t[1], t[0], 'file'); + unlink.push(t[0]); + }); + var result = fs.realpathSync(entry); + assert.equal(result, path.resolve(expected), + 'got ' + common.inspect(result) + ' expected ' + + common.inspect(expected)); + asynctest(fs.realpath, [entry], callback, function(err, result) { + assert.equal(result, path.resolve(expected), + 'got ' + + common.inspect(result) + + ' expected ' + + common.inspect(expected)); + }); +} + +function test_simple_absolute_symlink(callback) { + console.log('test_simple_absolute_symlink'); + + // this one should still run, even if skipSymlinks is set, + // because it uses a junction. + var type = skipSymlinks ? 'junction' : 'dir'; + + console.log('using type=%s', type); + + var entry = tmpAbsDir + '/symlink', + expected = fixturesAbsDir + '/nested-index/one'; + [ + [entry, expected] + ].forEach(function(t) { + try {fs.unlinkSync(t[0]);} catch (e) {} + console.error('fs.symlinkSync(%j, %j, %j)', t[1], t[0], type); + fs.symlinkSync(t[1], t[0], type); + unlink.push(t[0]); + }); + var result = fs.realpathSync(entry); + assert.equal(result, path.resolve(expected), + 'got ' + + common.inspect(result) + + ' expected ' + + common.inspect(expected)); + asynctest(fs.realpath, [entry], callback, function(err, result) { + assert.equal(result, path.resolve(expected), + 'got ' + + common.inspect(result) + + ' expected ' + + common.inspect(expected)); + }); +} + +function test_deep_relative_file_symlink(callback) { + console.log('test_deep_relative_file_symlink'); + if (skipSymlinks) { + console.log('skipping symlink test (no privs)'); + return runNextTest(); + } + + var expected = path.join(common.fixturesDir, 'cycles', 'root.js'); + var linkData1 = '../../cycles/root.js'; + var linkPath1 = path.join(common.fixturesDir, + 'nested-index', 'one', 'symlink1.js'); + try {fs.unlinkSync(linkPath1);} catch (e) {} + fs.symlinkSync(linkData1, linkPath1, 'file'); + + var linkData2 = '../one/symlink1.js'; + var entry = path.join(common.fixturesDir, + 'nested-index', 'two', 'symlink1-b.js'); + try {fs.unlinkSync(entry);} catch (e) {} + fs.symlinkSync(linkData2, entry, 'file'); + unlink.push(linkPath1); + unlink.push(entry); + + assert.equal(fs.realpathSync(entry), path.resolve(expected)); + asynctest(fs.realpath, [entry], callback, function(err, result) { + assert.equal(result, path.resolve(expected), + 'got ' + + common.inspect(result) + + ' expected ' + + common.inspect(path.resolve(expected))); + }); +} + +function test_deep_relative_dir_symlink(callback) { + console.log('test_deep_relative_dir_symlink'); + if (skipSymlinks) { + console.log('skipping symlink test (no privs)'); + return runNextTest(); + } + var expected = path.join(common.fixturesDir, 'cycles', 'folder'); + var linkData1b = '../../cycles/folder'; + var linkPath1b = path.join(common.fixturesDir, + 'nested-index', 'one', 'symlink1-dir'); + try {fs.unlinkSync(linkPath1b);} catch (e) {} + fs.symlinkSync(linkData1b, linkPath1b, 'dir'); + + var linkData2b = '../one/symlink1-dir'; + var entry = path.join(common.fixturesDir, + 'nested-index', 'two', 'symlink12-dir'); + try {fs.unlinkSync(entry);} catch (e) {} + fs.symlinkSync(linkData2b, entry, 'dir'); + unlink.push(linkPath1b); + unlink.push(entry); + + assert.equal(fs.realpathSync(entry), path.resolve(expected)); + + asynctest(fs.realpath, [entry], callback, function(err, result) { + assert.equal(result, path.resolve(expected), + 'got ' + + common.inspect(result) + + ' expected ' + + common.inspect(path.resolve(expected))); + }); +} + +function test_cyclic_link_protection(callback) { + console.log('test_cyclic_link_protection'); + if (skipSymlinks) { + console.log('skipping symlink test (no privs)'); + return runNextTest(); + } + var entry = common.tmpDir + '/cycles/realpath-3a'; + [ + [entry, '../cycles/realpath-3b'], + [common.tmpDir + '/cycles/realpath-3b', '../cycles/realpath-3c'], + [common.tmpDir + '/cycles/realpath-3c', '../cycles/realpath-3a'] + ].forEach(function(t) { + try {fs.unlinkSync(t[0]);} catch (e) {} + fs.symlinkSync(t[1], t[0], 'dir'); + unlink.push(t[0]); + }); + assert.throws(function() { fs.realpathSync(entry); }); + asynctest(fs.realpath, [entry], callback, function(err, result) { + assert.ok(err && true); + return true; + }); +} + +function test_cyclic_link_overprotection(callback) { + console.log('test_cyclic_link_overprotection'); + if (skipSymlinks) { + console.log('skipping symlink test (no privs)'); + return runNextTest(); + } + var cycles = common.tmpDir + '/cycles'; + var expected = fs.realpathSync(cycles); + var folder = cycles + '/folder'; + var link = folder + '/cycles'; + var testPath = cycles; + for (var i = 0; i < 10; i++) testPath += '/folder/cycles'; + try {fs.unlinkSync(link)} catch (ex) {} + fs.symlinkSync(cycles, link, 'dir'); + unlink.push(link); + assert.equal(fs.realpathSync(testPath), path.resolve(expected)); + asynctest(fs.realpath, [testPath], callback, function(er, res) { + assert.equal(res, path.resolve(expected)); + }); +} + +function test_relative_input_cwd(callback) { + console.log('test_relative_input_cwd'); + if (skipSymlinks) { + console.log('skipping symlink test (no privs)'); + return runNextTest(); + } + + // we need to get the relative path to the tmp dir from cwd. + // When the test runner is running it, that will be .../node/test + // but it's more common to run `./node test/.../`, so detect it here. + var entrydir = process.cwd(); + var entry = common.tmpDir.substr(entrydir.length + 1) + '/cycles/realpath-3a'; + var expected = common.tmpDir + '/cycles/root.js'; + [ + [entry, '../cycles/realpath-3b'], + [common.tmpDir + '/cycles/realpath-3b', '../cycles/realpath-3c'], + [common.tmpDir + '/cycles/realpath-3c', 'root.js'] + ].forEach(function(t) { + var fn = t[0]; + console.error('fn=%j', fn); + try {fs.unlinkSync(fn);} catch (e) {} + var b = path.basename(t[1]); + var 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); + }); + + var origcwd = process.cwd(); + process.chdir(entrydir); + assert.equal(fs.realpathSync(entry), path.resolve(expected)); + asynctest(fs.realpath, [entry], callback, function(err, result) { + process.chdir(origcwd); + assert.equal(result, path.resolve(expected), + 'got ' + + common.inspect(result) + + ' expected ' + + common.inspect(path.resolve(expected))); + return true; + }); +} + +function test_deep_symlink_mix(callback) { + console.log('test_deep_symlink_mix'); + if (isWindows) { + // This one is a mix of files and directories, and it's quite tricky + // to get the file/dir links sorted out correctly. + console.log('skipping symlink test (no way to work on windows)'); + return runNextTest(); + } + + // todo: check to see that common.fixturesDir is not rooted in the + // same directory as our test symlink. + /* + /tmp/node-test-realpath-f1 -> ../tmp/node-test-realpath-d1/foo + /tmp/node-test-realpath-d1 -> ../node-test-realpath-d2 + /tmp/node-test-realpath-d2/foo -> ../node-test-realpath-f2 + /tmp/node-test-realpath-f2 + -> /node/test/fixtures/nested-index/one/realpath-c + /node/test/fixtures/nested-index/one/realpath-c + -> /node/test/fixtures/nested-index/two/realpath-c + /node/test/fixtures/nested-index/two/realpath-c -> ../../cycles/root.js + /node/test/fixtures/cycles/root.js (hard) + */ + var entry = tmp('node-test-realpath-f1'); + try { fs.unlinkSync(tmp('node-test-realpath-d2/foo')); } catch (e) {} + try { fs.rmdirSync(tmp('node-test-realpath-d2')); } catch (e) {} + fs.mkdirSync(tmp('node-test-realpath-d2'), 0700); + try { + [ + [entry, '../' + common.tmpDirName + '/node-test-realpath-d1/foo'], + [tmp('node-test-realpath-d1'), + '../' + common.tmpDirName + '/node-test-realpath-d2'], + [tmp('node-test-realpath-d2/foo'), '../node-test-realpath-f2'], + [tmp('node-test-realpath-f2'), fixturesAbsDir + + '/nested-index/one/realpath-c'], + [fixturesAbsDir + '/nested-index/one/realpath-c', fixturesAbsDir + + '/nested-index/two/realpath-c'], + [fixturesAbsDir + '/nested-index/two/realpath-c', + '../../../' + common.tmpDirName + '/cycles/root.js'] + ].forEach(function(t) { + //common.debug('setting up '+t[0]+' -> '+t[1]); + try { fs.unlinkSync(t[0]); } catch (e) {} + fs.symlinkSync(t[1], t[0]); + unlink.push(t[0]); + }); + } finally { + unlink.push(tmp('node-test-realpath-d2')); + } + var expected = tmpAbsDir + '/cycles/root.js'; + assert.equal(fs.realpathSync(entry), path.resolve(expected)); + asynctest(fs.realpath, [entry], callback, function(err, result) { + assert.equal(result, path.resolve(expected), + 'got ' + + common.inspect(result) + + ' expected ' + + common.inspect(path.resolve(expected))); + return true; + }); +} + +function test_non_symlinks(callback) { + console.log('test_non_symlinks'); + var entrydir = path.dirname(tmpAbsDir); + var entry = tmpAbsDir.substr(entrydir.length + 1) + '/cycles/root.js'; + var expected = tmpAbsDir + '/cycles/root.js'; + var origcwd = process.cwd(); + process.chdir(entrydir); + assert.equal(fs.realpathSync(entry), path.resolve(expected)); + asynctest(fs.realpath, [entry], callback, function(err, result) { + process.chdir(origcwd); + assert.equal(result, path.resolve(expected), + 'got ' + + common.inspect(result) + + ' expected ' + + common.inspect(path.resolve(expected))); + return true; + }); +} + +var upone = path.join(process.cwd(), '..'); +function test_escape_cwd(cb) { + console.log('test_escape_cwd'); + asynctest(fs.realpath, ['..'], cb, function(er, uponeActual) { + assert.equal(upone, uponeActual, + 'realpath("..") expected: ' + path.resolve(upone) + ' actual:' + uponeActual); + }); +} +var uponeActual = fs.realpathSync('..'); +assert.equal(upone, uponeActual, + 'realpathSync("..") expected: ' + path.resolve(upone) + ' actual:' + uponeActual); + + +// going up with .. multiple times +// . +// `-- a/ +// |-- b/ +// | `-- e -> .. +// `-- d -> .. +// realpath(a/b/e/d/a/b/e/d/a) ==> a +function test_up_multiple(cb) { + console.error('test_up_multiple'); + if (skipSymlinks) { + console.log('skipping symlink test (no privs)'); + return runNextTest(); + } + function cleanup() { + ['a/b', + 'a' + ].forEach(function(folder) { + try {fs.rmdirSync(tmp(folder))} catch (ex) {} + }); + } + function setup() { + cleanup(); + } + setup(); + fs.mkdirSync(tmp('a'), 0755); + fs.mkdirSync(tmp('a/b'), 0755); + fs.symlinkSync('..', tmp('a/d'), 'dir'); + unlink.push(tmp('a/d')); + fs.symlinkSync('..', tmp('a/b/e'), 'dir'); + unlink.push(tmp('a/b/e')); + + var abedabed = tmp('abedabed'.split('').join('/')); + var abedabed_real = tmp(''); + + var abedabeda = tmp('abedabeda'.split('').join('/')); + var abedabeda_real = tmp('a'); + + assert.equal(fs.realpathSync(abedabeda), abedabeda_real); + assert.equal(fs.realpathSync(abedabed), abedabed_real); + fs.realpath(abedabeda, function (er, real) { + if (er) throw er; + assert.equal(abedabeda_real, real); + fs.realpath(abedabed, function (er, real) { + if (er) throw er; + assert.equal(abedabed_real, real); + cb(); + cleanup(); + }); + }); +} + + +// 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(cb) { + console.log('test_abs_with_kids'); + + // this one should still run, even if skipSymlinks is set, + // because it uses a junction. + var type = skipSymlinks ? 'junction' : 'dir'; + + console.log('using type=%s', type); + + var 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 (ex) {} + }); + ['/a/b/c', + '/a/b', + '/a', + '' + ].forEach(function(folder) { + try {fs.rmdirSync(root + folder)} catch (ex) {} + }); + } + function setup() { + cleanup(); + ['', + '/a', + '/a/b', + '/a/b/c' + ].forEach(function(folder) { + console.log('mkdir ' + root + folder); + fs.mkdirSync(root + folder, 0700); + }); + fs.writeFileSync(root + '/a/b/c/x.txt', 'foo'); + fs.symlinkSync(root + '/a/b', root + '/a/link', type); + } + setup(); + var linkPath = root + '/a/link/c/x.txt'; + var expectPath = root + '/a/b/c/x.txt'; + var actual = fs.realpathSync(linkPath); + // console.log({link:linkPath,expect:expectPath,actual:actual},'sync'); + assert.equal(actual, path.resolve(expectPath)); + asynctest(fs.realpath, [linkPath], cb, function(er, actual) { + // console.log({link:linkPath,expect:expectPath,actual:actual},'async'); + assert.equal(actual, path.resolve(expectPath)); + cleanup(); + }); +} + +function test_lying_cache_liar(cb) { + var n = 2; + + // this should not require *any* stat calls, since everything + // checked by realpath will be found in the cache. + console.log('test_lying_cache_liar'); + var cache = { '/foo/bar/baz/bluff' : '/foo/bar/bluff', + '/1/2/3/4/5/6/7' : '/1', + '/a' : '/a', + '/a/b' : '/a/b', + '/a/b/c' : '/a/b', + '/a/b/d' : '/a/b/d' }; + if (isWindows) { + var wc = {}; + Object.keys(cache).forEach(function(k) { + wc[ path.resolve(k) ] = path.resolve(cache[k]); + }); + cache = wc; + } + + var bluff = path.resolve('/foo/bar/baz/bluff'); + var rps = fs.realpathSync(bluff, cache); + assert.equal(cache[bluff], rps); + var nums = path.resolve('/1/2/3/4/5/6/7'); + var called = false; // no sync cb calling! + fs.realpath(nums, cache, function(er, rp) { + called = true; + assert.equal(cache[nums], rp); + if (--n === 0) cb(); + }); + assert(called === false); + + var test = path.resolve('/a/b/c/d'), + expect = path.resolve('/a/b/d'); + var actual = fs.realpathSync(test, cache); + assert.equal(expect, actual); + fs.realpath(test, cache, function(er, actual) { + assert.equal(expect, actual); + if (--n === 0) cb(); + }); +} + +// ---------------------------------------------------------------------------- + +var tests = [ + test_simple_error_callback, + 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_abs_with_kids, + test_lying_cache_liar, + test_up_multiple +]; +var numtests = tests.length; +var testsRun = 0; +function runNextTest(err) { + if (err) throw err; + var test = tests.shift(); + if (!test) { + return console.log(numtests + + ' subtests completed OK for fs.realpath'); + } + testsRun++; + test(runNextTest); +} + + +assert.equal(root, fs.realpathSync('/')); +fs.realpath('/', function(err, result) { + assert.equal(null, err); + assert.equal(root, result); +}); + + +function runTest() { + var tmpDirs = ['cycles', 'cycles/folder']; + tmpDirs.forEach(function(t) { + t = tmp(t); + var s; + try { s = fs.statSync(t); } catch (ex) {} + if (s) return; + fs.mkdirSync(t, 0700); + }); + fs.writeFileSync(tmp('cycles/root.js'), "console.error('roooot!');"); + console.error('start tests'); + runNextTest(); +} + + +process.on('exit', function() { + assert.equal(numtests, testsRun); + unlink.forEach(function(path) { try {fs.unlinkSync(path);} catch (e) {} }); + assert.equal(async_completed, async_expected); +}); |