@@ -19,41 +19,55 @@
// 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);

// Symlinks MUST use \ paths, never / paths.
fs._symlinkSync = fs.symlinkSync;
fs.symlinkSync = function(a, b, type) {
a = a.split('/').join('\\');
b = b.split('/').join('\\');
return fs._symlinkSync(a, b, type);
};

// 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;
var tmpAbsDir;
function getAbsPaths(cb) {
var failed = false;
var did = 0;
var expect = 2;
bashRealpath(common.fixturesDir, function(er, path) {
if (failed) return;
if (er) return cb(failed = er);
fixturesAbsDir = path;
did++;
if (did === expect) cb();
});
bashRealpath(common.tmpDir, function(er, path) {
if (failed) return;
if (er) return cb(failed = er);
tmpAbsDir = path;
did++;
if (did === expect) cb();
});
}
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++;
@@ -72,12 +86,6 @@ function asynctest(testBlock, args, callback, assertBlock) {
}));
}

function bashRealpath(path, callback) {
exec("cd '" + path.replace("'", "\\'") + "' && pwd -P", function(err, o) {
callback(err, o.trim());
});
}

// sub-tests:
function test_simple_error_callback(cb) {
var ncalls = 0;
@@ -96,21 +104,26 @@ function test_simple_error_callback(cb) {

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) {}
fs.symlinkSync(t[1], t[0]);
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, expected,
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, expected,
assert.equal(result, path.resolve(expected),
'got ' +
common.inspect(result) +
' expected ' +
@@ -120,23 +133,31 @@ function test_simple_relative_symlink(callback) {

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/index.js';
expected = fixturesAbsDir + '/nested-index/one';
[
[entry, expected]
].forEach(function(t) {
try {fs.unlinkSync(t[0]);} catch (e) {}
fs.symlinkSync(t[1], t[0]);
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, expected,
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, expected,
assert.equal(result, path.resolve(expected),
'got ' +
common.inspect(result) +
' expected ' +
@@ -146,69 +167,82 @@ function test_simple_absolute_symlink(callback) {

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);
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);
fs.symlinkSync(linkData2, entry, 'file');
unlink.push(linkPath1);
unlink.push(entry);

assert.equal(fs.realpathSync(entry), expected);
assert.equal(fs.realpathSync(entry), path.resolve(expected));
asynctest(fs.realpath, [entry], callback, function(err, result) {
assert.equal(result, expected,
assert.equal(result, path.resolve(expected),
'got ' +
common.inspect(result) +
' expected ' +
common.inspect(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);
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);
fs.symlinkSync(linkData2b, entry, 'dir');
unlink.push(linkPath1b);
unlink.push(entry);

assert.equal(fs.realpathSync(entry), expected);
assert.equal(fs.realpathSync(entry), path.resolve(expected));

asynctest(fs.realpath, [entry], callback, function(err, result) {
assert.equal(result, expected,
assert.equal(result, path.resolve(expected),
'got ' +
common.inspect(result) +
' expected ' +
common.inspect(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]);
fs.symlinkSync(t[1], t[0], 'dir');
unlink.push(t[0]);
});
assert.throws(function() { fs.realpathSync(entry); });
@@ -220,57 +254,74 @@ function test_cyclic_link_protection(callback) {

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);
fs.symlinkSync(cycles, link, 'dir');
unlink.push(link);
assert.equal(fs.realpathSync(testPath), expected);
assert.equal(fs.realpathSync(testPath), path.resolve(expected));
asynctest(fs.realpath, [testPath], callback, function(er, res) {
assert.equal(res, expected);
assert.equal(res, path.resolve(expected));
});
}

function test_relative_input_cwd(callback) {
console.log('test_relative_input_cwd');
var p = common.tmpDir.lastIndexOf('/');
var entrydir = common.tmpDir.substr(0, p);
var entry = common.tmpDir.substr(p + 1) + '/cycles/realpath-3a';

// 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];
if (fn.charAt(0) !== '/') fn = entrydir + '/' + fn;
console.error('fn=%j', fn);
try {fs.unlinkSync(fn);} catch (e) {}
fs.symlinkSync(t[1], fn);
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), expected);
assert.equal(fs.realpathSync(entry), path.resolve(expected));
asynctest(fs.realpath, [entry], callback, function(err, result) {
process.chdir(origcwd);
assert.equal(result, expected,
assert.equal(result, path.resolve(expected),
'got ' +
common.inspect(result) +
' expected ' +
common.inspect(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.
// obtain our current realpath using bash (so we can test ourselves)
/*
/tmp/node-test-realpath-f1 -> ../tmp/node-test-realpath-d1/foo
/tmp/node-test-realpath-d1 -> ../node-test-realpath-d2
@@ -307,33 +358,32 @@ function test_deep_symlink_mix(callback) {
unlink.push(tmp('node-test-realpath-d2'));
}
var expected = tmpAbsDir + '/cycles/root.js';
assert.equal(fs.realpathSync(entry), expected);
assert.equal(fs.realpathSync(entry), path.resolve(expected));
asynctest(fs.realpath, [entry], callback, function(err, result) {
assert.equal(result, expected,
assert.equal(result, path.resolve(expected),
'got ' +
common.inspect(result) +
' expected ' +
common.inspect(expected));
common.inspect(path.resolve(expected)));
return true;
});
}

function test_non_symlinks(callback) {
console.log('test_non_symlinks');
var p = tmpAbsDir.lastIndexOf('/');
var entrydir = tmpAbsDir.substr(0, p);
var entry = tmpAbsDir.substr(p + 1) + '/cycles/root.js';
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), expected);
assert.equal(fs.realpathSync(entry), path.resolve(expected));
asynctest(fs.realpath, [entry], callback, function(err, result) {
process.chdir(origcwd);
assert.equal(result, expected,
assert.equal(result, path.resolve(expected),
'got ' +
common.inspect(result) +
' expected ' +
common.inspect(expected));
common.inspect(path.resolve(expected)));
return true;
});
}
@@ -343,12 +393,12 @@ function test_escape_cwd(cb) {
console.log('test_escape_cwd');
asynctest(fs.realpath, ['..'], cb, function(er, uponeActual) {
assert.equal(upone, uponeActual,
'realpath("..") expected: ' + upone + ' actual:' + uponeActual);
'realpath("..") expected: ' + path.resolve(upone) + ' actual:' + uponeActual);
});
}
var uponeActual = fs.realpathSync('..');
assert.equal(upone, uponeActual,
'realpathSync("..") expected: ' + upone + ' actual:' + uponeActual);
'realpathSync("..") expected: ' + path.resolve(upone) + ' actual:' + uponeActual);


// going up with .. multiple times
@@ -360,6 +410,10 @@ assert.equal(upone, uponeActual,
// 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');
@@ -397,6 +451,13 @@ function test_up_multiple(cb) {
// 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',
@@ -423,17 +484,17 @@ function test_abs_with_kids(cb) {
fs.mkdirSync(root + folder, 0700);
});
fs.writeFileSync(root + '/a/b/c/x.txt', 'foo');
fs.symlinkSync(root + '/a/b', root + '/a/link');
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, expectPath);
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, expectPath);
assert.equal(actual, path.resolve(expectPath));
cleanup();
});
}
@@ -450,15 +511,25 @@ function test_lying_cache_liar(cb) {
'/a/b' : '/a/b',
'/a/b/c' : '/a/b',
'/a/b/d' : '/a/b/d' };
var rps = fs.realpathSync('/foo/bar/baz/bluff', cache);
assert.equal(cache['/foo/bar/baz/bluff'], rps);
fs.realpath('/1/2/3/4/5/6/7', cache, function(er, rp) {
assert.equal(cache['/1/2/3/4/5/6/7'], rp);
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');
fs.realpath(nums, cache, function(er, rp) {
assert.equal(cache[nums], rp);
if (--n === 0) cb();
});

var test = '/a/b/c/d',
expect = '/a/b/d';
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) {
@@ -498,8 +569,15 @@ function runNextTest(err) {
test(runNextTest);
}

getAbsPaths(function(er) {
if (er) throw er;

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);
@@ -511,15 +589,7 @@ getAbsPaths(function(er) {
fs.writeFileSync(tmp('cycles/root.js'), "console.error('roooot!');");
console.error('start tests');
runNextTest();
});


assert.equal('/', fs.realpathSync('/'));
fs.realpath('/', function(err, result) {
assert.equal(null, err);
assert.equal('/', result);
});

}


process.on('exit', function() {