test: don't assume a certain folder structure
[platform/upstream/nodejs.git] / test / parallel / test-fs-realpath.js
1 'use strict';
2 var common = require('../common');
3 var assert = require('assert');
4 var fs = require('fs');
5 var path = require('path');
6 var exec = require('child_process').exec;
7 var async_completed = 0, async_expected = 0, unlink = [];
8 var skipSymlinks = false;
9
10 common.refreshTmpDir();
11
12 var root = '/';
13 if (common.isWindows) {
14   // something like "C:\\"
15   root = process.cwd().substr(0, 3);
16
17   // On Windows, creating symlinks requires admin privileges.
18   // We'll only try to run symlink test if we have enough privileges.
19   try {
20     exec('whoami /priv', function(err, o) {
21       if (err || o.indexOf('SeCreateSymbolicLinkPrivilege') == -1) {
22         skipSymlinks = true;
23       }
24       runTest();
25     });
26   } catch (er) {
27     // better safe than sorry
28     skipSymlinks = true;
29     process.nextTick(runTest);
30   }
31 } else {
32   process.nextTick(runTest);
33 }
34
35
36 function tmp(p) {
37   return path.join(common.tmpDir, p);
38 }
39
40 var fixturesAbsDir = common.fixturesDir;
41 var tmpAbsDir = common.tmpDir;
42 assert(fixturesAbsDir !== tmpAbsDir);
43
44 console.error('absolutes\n%s\n%s', fixturesAbsDir, tmpAbsDir);
45
46 function asynctest(testBlock, args, callback, assertBlock) {
47   async_expected++;
48   testBlock.apply(testBlock, args.concat(function(err) {
49     var ignoreError = false;
50     if (assertBlock) {
51       try {
52         ignoreError = assertBlock.apply(assertBlock, arguments);
53       }
54       catch (e) {
55         err = e;
56       }
57     }
58     async_completed++;
59     callback(ignoreError ? null : err);
60   }));
61 }
62
63 // sub-tests:
64 function test_simple_error_callback(cb) {
65   var ncalls = 0;
66
67   fs.realpath('/this/path/does/not/exist', function(err, s) {
68     assert(err);
69     assert(!s);
70     ncalls++;
71     cb();
72   });
73
74   process.on('exit', function() {
75     assert.equal(ncalls, 1);
76   });
77 }
78
79 function test_simple_relative_symlink(callback) {
80   console.log('test_simple_relative_symlink');
81   if (skipSymlinks) {
82     console.log('1..0 # Skipped: symlink test (no privs)');
83     return runNextTest();
84   }
85   var entry = common.tmpDir + '/symlink',
86       expected = common.tmpDir + '/cycles/root.js';
87   [
88     [entry, '../' + common.tmpDirName + '/cycles/root.js']
89   ].forEach(function(t) {
90     try {fs.unlinkSync(t[0]);}catch (e) {}
91     console.log('fs.symlinkSync(%j, %j, %j)', t[1], t[0], 'file');
92     fs.symlinkSync(t[1], t[0], 'file');
93     unlink.push(t[0]);
94   });
95   var result = fs.realpathSync(entry);
96   assert.equal(result, path.resolve(expected));
97   asynctest(fs.realpath, [entry], callback, function(err, result) {
98     assert.equal(result, path.resolve(expected));
99   });
100 }
101
102 function test_simple_absolute_symlink(callback) {
103   console.log('test_simple_absolute_symlink');
104
105   // this one should still run, even if skipSymlinks is set,
106   // because it uses a junction.
107   var type = skipSymlinks ? 'junction' : 'dir';
108
109   console.log('using type=%s', type);
110
111   var entry = tmpAbsDir + '/symlink',
112       expected = fixturesAbsDir + '/nested-index/one';
113   [
114     [entry, expected]
115   ].forEach(function(t) {
116     try {fs.unlinkSync(t[0]);} catch (e) {}
117     console.error('fs.symlinkSync(%j, %j, %j)', t[1], t[0], type);
118     fs.symlinkSync(t[1], t[0], type);
119     unlink.push(t[0]);
120   });
121   var result = fs.realpathSync(entry);
122   assert.equal(result, path.resolve(expected));
123   asynctest(fs.realpath, [entry], callback, function(err, result) {
124     assert.equal(result, path.resolve(expected));
125   });
126 }
127
128 function test_deep_relative_file_symlink(callback) {
129   console.log('test_deep_relative_file_symlink');
130   if (skipSymlinks) {
131     console.log('1..0 # Skipped: symlink test (no privs)');
132     return runNextTest();
133   }
134
135   var expected = path.join(common.fixturesDir, 'cycles', 'root.js');
136   var linkData1 = '../../cycles/root.js';
137   var linkPath1 = path.join(common.fixturesDir,
138                             'nested-index', 'one', 'symlink1.js');
139   try {fs.unlinkSync(linkPath1);} catch (e) {}
140   fs.symlinkSync(linkData1, linkPath1, 'file');
141
142   var linkData2 = '../one/symlink1.js';
143   var entry = path.join(common.fixturesDir,
144                         'nested-index', 'two', 'symlink1-b.js');
145   try {fs.unlinkSync(entry);} catch (e) {}
146   fs.symlinkSync(linkData2, entry, 'file');
147   unlink.push(linkPath1);
148   unlink.push(entry);
149
150   assert.equal(fs.realpathSync(entry), path.resolve(expected));
151   asynctest(fs.realpath, [entry], callback, function(err, result) {
152     assert.equal(result, path.resolve(expected));
153   });
154 }
155
156 function test_deep_relative_dir_symlink(callback) {
157   console.log('test_deep_relative_dir_symlink');
158   if (skipSymlinks) {
159     console.log('1..0 # Skipped: symlink test (no privs)');
160     return runNextTest();
161   }
162   var expected = path.join(common.fixturesDir, 'cycles', 'folder');
163   var linkData1b = '../../cycles/folder';
164   var linkPath1b = path.join(common.fixturesDir,
165                              'nested-index', 'one', 'symlink1-dir');
166   try {fs.unlinkSync(linkPath1b);} catch (e) {}
167   fs.symlinkSync(linkData1b, linkPath1b, 'dir');
168
169   var linkData2b = '../one/symlink1-dir';
170   var entry = path.join(common.fixturesDir,
171                         'nested-index', 'two', 'symlink12-dir');
172   try {fs.unlinkSync(entry);} catch (e) {}
173   fs.symlinkSync(linkData2b, entry, 'dir');
174   unlink.push(linkPath1b);
175   unlink.push(entry);
176
177   assert.equal(fs.realpathSync(entry), path.resolve(expected));
178
179   asynctest(fs.realpath, [entry], callback, function(err, result) {
180     assert.equal(result, path.resolve(expected));
181   });
182 }
183
184 function test_cyclic_link_protection(callback) {
185   console.log('test_cyclic_link_protection');
186   if (skipSymlinks) {
187     console.log('1..0 # Skipped: symlink test (no privs)');
188     return runNextTest();
189   }
190   var entry = common.tmpDir + '/cycles/realpath-3a';
191   [
192     [entry, '../cycles/realpath-3b'],
193     [common.tmpDir + '/cycles/realpath-3b', '../cycles/realpath-3c'],
194     [common.tmpDir + '/cycles/realpath-3c', '../cycles/realpath-3a']
195   ].forEach(function(t) {
196     try {fs.unlinkSync(t[0]);} catch (e) {}
197     fs.symlinkSync(t[1], t[0], 'dir');
198     unlink.push(t[0]);
199   });
200   assert.throws(function() { fs.realpathSync(entry); });
201   asynctest(fs.realpath, [entry], callback, function(err, result) {
202     assert.ok(err && true);
203     return true;
204   });
205 }
206
207 function test_cyclic_link_overprotection(callback) {
208   console.log('test_cyclic_link_overprotection');
209   if (skipSymlinks) {
210     console.log('1..0 # Skipped: symlink test (no privs)');
211     return runNextTest();
212   }
213   var cycles = common.tmpDir + '/cycles';
214   var expected = fs.realpathSync(cycles);
215   var folder = cycles + '/folder';
216   var link = folder + '/cycles';
217   var testPath = cycles;
218   for (var i = 0; i < 10; i++) testPath += '/folder/cycles';
219   try {fs.unlinkSync(link);} catch (ex) {}
220   fs.symlinkSync(cycles, link, 'dir');
221   unlink.push(link);
222   assert.equal(fs.realpathSync(testPath), path.resolve(expected));
223   asynctest(fs.realpath, [testPath], callback, function(er, res) {
224     assert.equal(res, path.resolve(expected));
225   });
226 }
227
228 function test_relative_input_cwd(callback) {
229   console.log('test_relative_input_cwd');
230   if (skipSymlinks) {
231     console.log('1..0 # Skipped: symlink test (no privs)');
232     return runNextTest();
233   }
234
235   // we need to get the relative path to the tmp dir from cwd.
236   // When the test runner is running it, that will be .../node/test
237   // but it's more common to run `./node test/.../`, so detect it here.
238   var entrydir = process.cwd();
239   var entry = common.tmpDir.substr(entrydir.length + 1) + '/cycles/realpath-3a';
240   var expected = common.tmpDir + '/cycles/root.js';
241   [
242     [entry, '../cycles/realpath-3b'],
243     [common.tmpDir + '/cycles/realpath-3b', '../cycles/realpath-3c'],
244     [common.tmpDir + '/cycles/realpath-3c', 'root.js']
245   ].forEach(function(t) {
246     var fn = t[0];
247     console.error('fn=%j', fn);
248     try {fs.unlinkSync(fn);} catch (e) {}
249     var b = path.basename(t[1]);
250     var type = (b === 'root.js' ? 'file' : 'dir');
251     console.log('fs.symlinkSync(%j, %j, %j)', t[1], fn, type);
252     fs.symlinkSync(t[1], fn, 'file');
253     unlink.push(fn);
254   });
255
256   var origcwd = process.cwd();
257   process.chdir(entrydir);
258   assert.equal(fs.realpathSync(entry), path.resolve(expected));
259   asynctest(fs.realpath, [entry], callback, function(err, result) {
260     process.chdir(origcwd);
261     assert.equal(result, path.resolve(expected));
262     return true;
263   });
264 }
265
266 function test_deep_symlink_mix(callback) {
267   console.log('test_deep_symlink_mix');
268   if (common.isWindows) {
269     // This one is a mix of files and directories, and it's quite tricky
270     // to get the file/dir links sorted out correctly.
271     console.log('1..0 # Skipped: symlink test (no privs)');
272     return runNextTest();
273   }
274
275   /*
276   /tmp/node-test-realpath-f1 -> $tmpDir/node-test-realpath-d1/foo
277   /tmp/node-test-realpath-d1 -> $tmpDir/node-test-realpath-d2
278   /tmp/node-test-realpath-d2/foo -> $tmpDir/node-test-realpath-f2
279   /tmp/node-test-realpath-f2
280     -> /node/test/fixtures/nested-index/one/realpath-c
281   /node/test/fixtures/nested-index/one/realpath-c
282     -> /node/test/fixtures/nested-index/two/realpath-c
283   /node/test/fixtures/nested-index/two/realpath-c -> $tmpDir/cycles/root.js
284   /node/test/fixtures/cycles/root.js (hard)
285   */
286   var entry = tmp('node-test-realpath-f1');
287   try { fs.unlinkSync(tmp('node-test-realpath-d2/foo')); } catch (e) {}
288   try { fs.rmdirSync(tmp('node-test-realpath-d2')); } catch (e) {}
289   fs.mkdirSync(tmp('node-test-realpath-d2'), 0o700);
290   try {
291     [
292       [entry, common.tmpDir + '/node-test-realpath-d1/foo'],
293       [tmp('node-test-realpath-d1'),
294         common.tmpDir + '/node-test-realpath-d2'],
295       [tmp('node-test-realpath-d2/foo'), '../node-test-realpath-f2'],
296       [tmp('node-test-realpath-f2'), fixturesAbsDir +
297         '/nested-index/one/realpath-c'],
298       [fixturesAbsDir + '/nested-index/one/realpath-c', fixturesAbsDir +
299         '/nested-index/two/realpath-c'],
300       [fixturesAbsDir + '/nested-index/two/realpath-c',
301         common.tmpDir + '/cycles/root.js']
302     ].forEach(function(t) {
303       try { fs.unlinkSync(t[0]); } catch (e) {}
304       fs.symlinkSync(t[1], t[0]);
305       unlink.push(t[0]);
306     });
307   } finally {
308     unlink.push(tmp('node-test-realpath-d2'));
309   }
310   var expected = tmpAbsDir + '/cycles/root.js';
311   assert.equal(fs.realpathSync(entry), path.resolve(expected));
312   asynctest(fs.realpath, [entry], callback, function(err, result) {
313     assert.equal(result, path.resolve(expected));
314     return true;
315   });
316 }
317
318 function test_non_symlinks(callback) {
319   console.log('test_non_symlinks');
320   var entrydir = path.dirname(tmpAbsDir);
321   var entry = tmpAbsDir.substr(entrydir.length + 1) + '/cycles/root.js';
322   var expected = tmpAbsDir + '/cycles/root.js';
323   var origcwd = process.cwd();
324   process.chdir(entrydir);
325   assert.equal(fs.realpathSync(entry), path.resolve(expected));
326   asynctest(fs.realpath, [entry], callback, function(err, result) {
327     process.chdir(origcwd);
328     assert.equal(result, path.resolve(expected));
329     return true;
330   });
331 }
332
333 var upone = path.join(process.cwd(), '..');
334 function test_escape_cwd(cb) {
335   console.log('test_escape_cwd');
336   asynctest(fs.realpath, ['..'], cb, function(er, uponeActual) {
337     assert.equal(upone, uponeActual,
338         'realpath("..") expected: ' + path.resolve(upone) +
339         ' actual:' + uponeActual);
340   });
341 }
342 var uponeActual = fs.realpathSync('..');
343 assert.equal(upone, uponeActual,
344     'realpathSync("..") expected: ' + path.resolve(upone) +
345     ' actual:' + uponeActual);
346
347
348 // going up with .. multiple times
349 // .
350 // `-- a/
351 //     |-- b/
352 //     |   `-- e -> ..
353 //     `-- d -> ..
354 // realpath(a/b/e/d/a/b/e/d/a) ==> a
355 function test_up_multiple(cb) {
356   console.error('test_up_multiple');
357   if (skipSymlinks) {
358     console.log('1..0 # Skipped: symlink test (no privs)');
359     return runNextTest();
360   }
361   function cleanup() {
362     ['a/b',
363       'a'
364     ].forEach(function(folder) {
365       try {fs.rmdirSync(tmp(folder));} catch (ex) {}
366     });
367   }
368   function setup() {
369     cleanup();
370   }
371   setup();
372   fs.mkdirSync(tmp('a'), 0o755);
373   fs.mkdirSync(tmp('a/b'), 0o755);
374   fs.symlinkSync('..', tmp('a/d'), 'dir');
375   unlink.push(tmp('a/d'));
376   fs.symlinkSync('..', tmp('a/b/e'), 'dir');
377   unlink.push(tmp('a/b/e'));
378
379   var abedabed = tmp('abedabed'.split('').join('/'));
380   var abedabed_real = tmp('');
381
382   var abedabeda = tmp('abedabeda'.split('').join('/'));
383   var abedabeda_real = tmp('a');
384
385   assert.equal(fs.realpathSync(abedabeda), abedabeda_real);
386   assert.equal(fs.realpathSync(abedabed), abedabed_real);
387   fs.realpath(abedabeda, function(er, real) {
388     if (er) throw er;
389     assert.equal(abedabeda_real, real);
390     fs.realpath(abedabed, function(er, real) {
391       if (er) throw er;
392       assert.equal(abedabed_real, real);
393       cb();
394       cleanup();
395     });
396   });
397 }
398
399
400 // absolute symlinks with children.
401 // .
402 // `-- a/
403 //     |-- b/
404 //     |   `-- c/
405 //     |       `-- x.txt
406 //     `-- link -> /tmp/node-test-realpath-abs-kids/a/b/
407 // realpath(root+'/a/link/c/x.txt') ==> root+'/a/b/c/x.txt'
408 function test_abs_with_kids(cb) {
409   console.log('test_abs_with_kids');
410
411   // this one should still run, even if skipSymlinks is set,
412   // because it uses a junction.
413   var type = skipSymlinks ? 'junction' : 'dir';
414
415   console.log('using type=%s', type);
416
417   var root = tmpAbsDir + '/node-test-realpath-abs-kids';
418   function cleanup() {
419     ['/a/b/c/x.txt',
420       '/a/link'
421     ].forEach(function(file) {
422       try {fs.unlinkSync(root + file);} catch (ex) {}
423     });
424     ['/a/b/c',
425       '/a/b',
426       '/a',
427       ''
428     ].forEach(function(folder) {
429       try {fs.rmdirSync(root + folder);} catch (ex) {}
430     });
431   }
432   function setup() {
433     cleanup();
434     ['',
435       '/a',
436       '/a/b',
437       '/a/b/c'
438     ].forEach(function(folder) {
439       console.log('mkdir ' + root + folder);
440       fs.mkdirSync(root + folder, 0o700);
441     });
442     fs.writeFileSync(root + '/a/b/c/x.txt', 'foo');
443     fs.symlinkSync(root + '/a/b', root + '/a/link', type);
444   }
445   setup();
446   var linkPath = root + '/a/link/c/x.txt';
447   var expectPath = root + '/a/b/c/x.txt';
448   var actual = fs.realpathSync(linkPath);
449   // console.log({link:linkPath,expect:expectPath,actual:actual},'sync');
450   assert.equal(actual, path.resolve(expectPath));
451   asynctest(fs.realpath, [linkPath], cb, function(er, actual) {
452     // console.log({link:linkPath,expect:expectPath,actual:actual},'async');
453     assert.equal(actual, path.resolve(expectPath));
454     cleanup();
455   });
456 }
457
458 function test_lying_cache_liar(cb) {
459   var n = 2;
460
461   // this should not require *any* stat calls, since everything
462   // checked by realpath will be found in the cache.
463   console.log('test_lying_cache_liar');
464   var cache = { '/foo/bar/baz/bluff' : '/foo/bar/bluff',
465                 '/1/2/3/4/5/6/7' : '/1',
466                 '/a' : '/a',
467                 '/a/b' : '/a/b',
468                 '/a/b/c' : '/a/b',
469                 '/a/b/d' : '/a/b/d' };
470   if (common.isWindows) {
471     var wc = {};
472     Object.keys(cache).forEach(function(k) {
473       wc[ path.resolve(k) ] = path.resolve(cache[k]);
474     });
475     cache = wc;
476   }
477
478   var bluff = path.resolve('/foo/bar/baz/bluff');
479   var rps = fs.realpathSync(bluff, cache);
480   assert.equal(cache[bluff], rps);
481   var nums = path.resolve('/1/2/3/4/5/6/7');
482   var called = false; // no sync cb calling!
483   fs.realpath(nums, cache, function(er, rp) {
484     called = true;
485     assert.equal(cache[nums], rp);
486     if (--n === 0) cb();
487   });
488   assert(called === false);
489
490   var test = path.resolve('/a/b/c/d'),
491       expect = path.resolve('/a/b/d');
492   var actual = fs.realpathSync(test, cache);
493   assert.equal(expect, actual);
494   fs.realpath(test, cache, function(er, actual) {
495     assert.equal(expect, actual);
496     if (--n === 0) cb();
497   });
498 }
499
500 // ----------------------------------------------------------------------------
501
502 var tests = [
503   test_simple_error_callback,
504   test_simple_relative_symlink,
505   test_simple_absolute_symlink,
506   test_deep_relative_file_symlink,
507   test_deep_relative_dir_symlink,
508   test_cyclic_link_protection,
509   test_cyclic_link_overprotection,
510   test_relative_input_cwd,
511   test_deep_symlink_mix,
512   test_non_symlinks,
513   test_escape_cwd,
514   test_abs_with_kids,
515   test_lying_cache_liar,
516   test_up_multiple
517 ];
518 var numtests = tests.length;
519 var testsRun = 0;
520 function runNextTest(err) {
521   if (err) throw err;
522   var test = tests.shift();
523   if (!test) {
524     return console.log(numtests +
525                        ' subtests completed OK for fs.realpath');
526   }
527   testsRun++;
528   test(runNextTest);
529 }
530
531
532 assert.equal(root, fs.realpathSync('/'));
533 fs.realpath('/', function(err, result) {
534   assert.equal(null, err);
535   assert.equal(root, result);
536 });
537
538
539 function runTest() {
540   var tmpDirs = ['cycles', 'cycles/folder'];
541   tmpDirs.forEach(function(t) {
542     t = tmp(t);
543     fs.mkdirSync(t, 0o700);
544   });
545   fs.writeFileSync(tmp('cycles/root.js'), "console.error('roooot!');");
546   console.error('start tests');
547   runNextTest();
548 }
549
550
551 process.on('exit', function() {
552   assert.equal(numtests, testsRun);
553   unlink.forEach(function(path) { try {fs.unlinkSync(path);} catch (e) {} });
554   assert.equal(async_completed, async_expected);
555 });