| // 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, '../tmp/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'); |
| |
| // 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, '../tmp/node-test-realpath-d1/foo'], |
| [tmp('node-test-realpath-d1'), '../tmp/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', |
| '../../../tmp/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(); |
| } |
| 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(); |
| }); |
| }); |
| } |
| |
| |
| // 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); |
| }); |