import fs from 'fs'; import test from 'ava'; import shell from '..'; import common from '../src/common'; import utils from './utils/utils'; const oldMaxDepth = shell.config.maxdepth; const CWD = process.cwd(); test.beforeEach(t => { t.context.tmp = utils.getTempDir(); shell.config.resetForTesting(); shell.mkdir(t.context.tmp); }); test.afterEach.always(t => { process.chdir(CWD); shell.rm('-rf', t.context.tmp); shell.config.maxdepth = oldMaxDepth; }); // // Invalids // test('no args', t => { const result = shell.cp(); t.truthy(shell.error()); t.is(result.code, 1); t.is(result.stderr, 'cp: missing and/or '); }); test('no destination', t => { const result = shell.cp('file1'); t.truthy(shell.error()); t.is(result.code, 1); t.is(result.stderr, 'cp: missing and/or '); }); test('only an option', t => { const result = shell.cp('-f'); t.truthy(shell.error()); t.is(result.code, 1); t.is(result.stderr, 'cp: missing and/or '); }); test('invalid option', t => { const result = shell.cp('-@', 'resources/file1', `${t.context.tmp}/file1`); t.truthy(shell.error()); t.is(result.code, 1); t.falsy(fs.existsSync(`${t.context.tmp}/file1`)); t.is(result.stderr, 'cp: option not recognized: @'); }); test('invalid option', t => { const result = shell.cp('-Z', 'asdfasdf', `${t.context.tmp}/file2`); t.truthy(shell.error()); t.is(result.code, 1); t.falsy(fs.existsSync(`${t.context.tmp}/file2`)); t.is(result.stderr, 'cp: option not recognized: Z'); }); test('source does not exist', t => { const result = shell.cp('asdfasdf', t.context.tmp); t.truthy(shell.error()); t.is(result.code, 1); t.is(utils.numLines(result.stderr), 1); t.falsy(fs.existsSync(`${t.context.tmp}/asdfasdf`)); t.is(result.stderr, 'cp: no such file or directory: asdfasdf'); }); test('sources does not exist', t => { const result = shell.cp('asdfasdf1', 'asdfasdf2', t.context.tmp); t.truthy(shell.error()); t.is(result.code, 1); t.is(utils.numLines(result.stderr), 2); t.falsy(fs.existsSync(`${t.context.tmp}/asdfasdf1`)); t.falsy(fs.existsSync(`${t.context.tmp}/asdfasdf2`)); t.is( result.stderr, 'cp: no such file or directory: asdfasdf1\ncp: no such file or directory: asdfasdf2' ); }); test('too many sources', t => { const result = shell.cp('asdfasdf1', 'asdfasdf2', 'resources/file1'); t.truthy(shell.error()); t.is(result.code, 1); t.is(result.stderr, 'cp: dest is not a directory (too many sources)'); }); test('too many sources #2', t => { const result = shell.cp('resources/file1', 'resources/file2', `${t.context.tmp}/a_file`); t.truthy(shell.error()); t.is(result.code, 1); t.falsy(fs.existsSync(`${t.context.tmp}/a_file`)); t.is(result.stderr, 'cp: dest is not a directory (too many sources)'); }); // // Valids // test('dest already exists', t => { const oldContents = shell.cat('resources/file2').toString(); const result = shell.cp('-n', 'resources/file1', 'resources/file2'); t.falsy(shell.error()); t.is(result.code, 0); t.is(result.stderr, ''); t.is(shell.cat('resources/file2').toString(), oldContents); }); test('-f by default', t => { shell.cp('resources/file2', 'resources/copyfile2'); const result = shell.cp('resources/file1', 'resources/file2'); // dest already exists t.falsy(shell.error()); t.is(result.code, 0); t.falsy(result.stderr); t.is(shell.cat('resources/file1').toString(), shell.cat('resources/file2').toString()); // after cp shell.mv('resources/copyfile2', 'resources/file2'); // restore t.falsy(shell.error()); }); test('-f (explicitly)', t => { shell.cp('resources/file2', 'resources/copyfile2'); const result = shell.cp('-f', 'resources/file1', 'resources/file2'); // dest already exists t.falsy(shell.error()); t.falsy(result.stderr); t.is(result.code, 0); t.is(shell.cat('resources/file1').toString(), shell.cat('resources/file2').toString()); // after cp shell.mv('resources/copyfile2', 'resources/file2'); // restore t.falsy(shell.error()); t.is(result.code, 0); }); test('simple - to dir', t => { const result = shell.cp('resources/file1', t.context.tmp); t.falsy(shell.error()); t.falsy(result.stderr); t.is(result.code, 0); t.truthy(fs.existsSync(`${t.context.tmp}/file1`)); }); test('simple - to file', t => { const result = shell.cp('resources/file2', `${t.context.tmp}/file2`); t.falsy(shell.error()); t.falsy(result.stderr); t.is(result.code, 0); t.truthy(fs.existsSync(`${t.context.tmp}/file2`)); }); test('simple - file list', t => { const result = shell.cp('resources/file1', 'resources/file2', t.context.tmp); t.falsy(shell.error()); t.falsy(result.stderr); t.is(result.code, 0); t.truthy(fs.existsSync(`${t.context.tmp}/file1`)); t.truthy(fs.existsSync(`${t.context.tmp}/file2`)); }); test('simple - file list, array syntax', t => { const result = shell.cp(['resources/file1', 'resources/file2'], t.context.tmp); t.falsy(shell.error()); t.falsy(result.stderr); t.is(result.code, 0); t.truthy(fs.existsSync(`${t.context.tmp}/file1`)); t.truthy(fs.existsSync(`${t.context.tmp}/file2`)); }); test('-f option', t => { shell.cp('resources/file2', `${t.context.tmp}/file3`); t.truthy(fs.existsSync(`${t.context.tmp}/file3`)); const result = shell.cp('-f', 'resources/file2', `${t.context.tmp}/file3`); // file exists, but -f specified t.falsy(shell.error()); t.falsy(result.stderr); t.is(result.code, 0); t.truthy(fs.existsSync(`${t.context.tmp}/file3`)); }); test('glob', t => { const result = shell.cp('resources/file?', t.context.tmp); t.falsy(shell.error()); t.falsy(result.stderr); t.is(result.code, 0); t.truthy(fs.existsSync(`${t.context.tmp}/file1`)); t.truthy(fs.existsSync(`${t.context.tmp}/file2`)); t.falsy(fs.existsSync(`${t.context.tmp}/file1.js`)); t.falsy(fs.existsSync(`${t.context.tmp}/file2.js`)); t.falsy(fs.existsSync(`${t.context.tmp}/file1.txt`)); t.falsy(fs.existsSync(`${t.context.tmp}/file2.txt`)); }); test('wildcard', t => { shell.rm(`${t.context.tmp}/file1`, `${t.context.tmp}/file2`); const result = shell.cp('resources/file*', t.context.tmp); t.falsy(shell.error()); t.falsy(result.stderr); t.is(result.code, 0); t.truthy(fs.existsSync(`${t.context.tmp}/file1`)); t.truthy(fs.existsSync(`${t.context.tmp}/file2`)); t.truthy(fs.existsSync(`${t.context.tmp}/file1.js`)); t.truthy(fs.existsSync(`${t.context.tmp}/file2.js`)); t.truthy(fs.existsSync(`${t.context.tmp}/file1.txt`)); t.truthy(fs.existsSync(`${t.context.tmp}/file2.txt`)); }); test('recursive, with regular files', t => { const result = shell.cp('-R', 'resources/file1', 'resources/file2', t.context.tmp); t.falsy(shell.error()); t.falsy(result.stderr); t.is(result.code, 0); t.truthy(fs.existsSync(`${t.context.tmp}/file1`)); t.truthy(fs.existsSync(`${t.context.tmp}/file2`)); }); test('recursive, nothing exists', t => { const result = shell.cp('-R', 'resources/cp', t.context.tmp); t.falsy(shell.error()); t.falsy(result.stderr); t.is(result.code, 0); t.is(shell.ls('-R', 'resources/cp').toString(), shell.ls('-R', `${t.context.tmp}/cp`).toString()); }); test( 'recursive, nothing exists, source ends in \'/\' (see Github issue #15)', t => { const result = shell.cp('-R', 'resources/cp/', `${t.context.tmp}/`); t.falsy(shell.error()); t.falsy(result.stderr); t.is(result.code, 0); t.is(shell.ls('-R', 'resources/cp').toString(), shell.ls('-R', `${t.context.tmp}/cp`).toString()); } ); test( 'recursive, globbing regular files with extension (see Github issue #376)', t => { const result = shell.cp('-R', 'resources/file*.txt', t.context.tmp); t.falsy(shell.error()); t.falsy(result.stderr); t.is(result.code, 0); t.truthy(fs.existsSync(`${t.context.tmp}/file1.txt`)); t.truthy(fs.existsSync(`${t.context.tmp}/file2.txt`)); } ); test( 'recursive, copying one regular file (also related to Github issue #376)', t => { const result = shell.cp('-R', 'resources/file1.txt', t.context.tmp); t.falsy(shell.error()); t.falsy(result.stderr); t.is(result.code, 0); t.truthy(fs.existsSync(`${t.context.tmp}/file1.txt`)); t.falsy(fs.statSync(`${t.context.tmp}/file1.txt`).isDirectory()); // don't let it be a dir } ); test('recursive, everything exists, no force flag', t => { const result = shell.cp('-R', 'resources/cp', t.context.tmp); t.falsy(shell.error()); // crash test only t.falsy(result.stderr); t.is(result.code, 0); }); test('-R implies to not follow links', t => { if (process.platform !== 'win32') { shell.cp('-R', 'resources/cp/*', t.context.tmp); t.truthy(fs.lstatSync(`${t.context.tmp}/links/sym.lnk`).isSymbolicLink()); // this one is a link t.falsy((fs.lstatSync(`${t.context.tmp}/fakeLinks/sym.lnk`).isSymbolicLink())); // this one isn't t.not( shell.cat(`${t.context.tmp}/links/sym.lnk`).toString(), shell.cat(`${t.context.tmp}/fakeLinks/sym.lnk`).toString() ); const result = shell.cp('-R', `${t.context.tmp}/links/*`, `${t.context.tmp}/fakeLinks`); t.falsy(shell.error()); t.falsy(result.stderr); t.is(result.code, 0); t.truthy(fs.lstatSync(`${t.context.tmp}/links/sym.lnk`).isSymbolicLink()); // this one is a link t.truthy(fs.lstatSync(`${t.context.tmp}/fakeLinks/sym.lnk`).isSymbolicLink()); // this one is now a link t.is( shell.cat(`${t.context.tmp}/links/sym.lnk`).toString(), shell.cat(`${t.context.tmp}/fakeLinks/sym.lnk`).toString() ); } }); test('Missing -R implies -L', t => { if (process.platform !== 'win32') { // Recursive, everything exists, overwrite a real file *by following a link* // Because missing the -R implies -L. shell.cp('-R', 'resources/cp/*', t.context.tmp); t.truthy(fs.lstatSync(`${t.context.tmp}/links/sym.lnk`).isSymbolicLink()); // this one is a link t.falsy((fs.lstatSync(`${t.context.tmp}/fakeLinks/sym.lnk`).isSymbolicLink())); // this one isn't t.not( shell.cat(`${t.context.tmp}/links/sym.lnk`).toString(), shell.cat(`${t.context.tmp}/fakeLinks/sym.lnk`).toString() ); const result = shell.cp(`${t.context.tmp}/links/*`, `${t.context.tmp}/fakeLinks`); // don't use -R t.falsy(shell.error()); t.falsy(result.stderr); t.is(result.code, 0); t.truthy(fs.lstatSync(`${t.context.tmp}/links/sym.lnk`).isSymbolicLink()); // this one is a link t.falsy(fs.lstatSync(`${t.context.tmp}/fakeLinks/sym.lnk`).isSymbolicLink()); // this one is still not a link // But it still follows the link t.is( shell.cat(`${t.context.tmp}/links/sym.lnk`).toString(), shell.cat(`${t.context.tmp}/fakeLinks/sym.lnk`).toString() ); } }); test('recursive, everything exists, with force flag', t => { let result = shell.cp('-R', 'resources/cp', t.context.tmp); shell.ShellString('changing things around').to(`${t.context.tmp}/cp/dir_a/z`); t.not(shell.cat('resources/cp/dir_a/z').toString(), shell.cat(`${t.context.tmp}/cp/dir_a/z`).toString()); // before cp result = shell.cp('-Rf', 'resources/cp', t.context.tmp); t.falsy(shell.error()); t.falsy(result.stderr); t.is(result.code, 0); t.is(shell.cat('resources/cp/dir_a/z').toString(), shell.cat(`${t.context.tmp}/cp/dir_a/z`).toString()); // after cp }); test( 'recursive, creates dest dir since it\'s only one level deep (see Github issue #44)', t => { const result = shell.cp('-r', 'resources/issue44', `${t.context.tmp}/dir2`); t.falsy(shell.error()); t.falsy(result.stderr); t.is(result.code, 0); t.is(shell.ls('-R', 'resources/issue44').toString(), shell.ls('-R', `${t.context.tmp}/dir2`).toString()); t.is( shell.cat('resources/issue44/main.js').toString(), shell.cat(`${t.context.tmp}/dir2/main.js`).toString() ); } ); test( 'recursive, does *not* create dest dir since it\'s too deep (see Github issue #44)', t => { const result = shell.cp('-r', 'resources/issue44', `${t.context.tmp}/dir2/dir3`); t.truthy(shell.error()); t.is( result.stderr, `cp: cannot create directory '${t.context.tmp}/dir2/dir3': No such file or directory` ); t.is(result.code, 1); t.falsy(fs.existsSync(`${t.context.tmp}/dir2`)); } ); test('recursive, copies entire directory', t => { const result = shell.cp('-r', 'resources/cp/dir_a', `${t.context.tmp}/dest`); t.falsy(shell.error()); t.falsy(result.stderr); t.is(result.code, 0); t.truthy(fs.existsSync(`${t.context.tmp}/dest/z`)); }); test('recursive, with trailing slash, does the exact same', t => { const result = shell.cp('-r', 'resources/cp/dir_a/', `${t.context.tmp}/dest`); t.is(result.code, 0); t.falsy(shell.error()); t.truthy(fs.existsSync(`${t.context.tmp}/dest/z`)); }); test( 'On Windows, permission bits are quite different so skip those tests for now', t => { if (common.platform !== 'win') { // preserve mode bits const execBit = parseInt('001', 8); t.is(fs.statSync('resources/cp-mode-bits/executable').mode & execBit, execBit); shell.cp('resources/cp-mode-bits/executable', `${t.context.tmp}/executable`); t.is( fs.statSync('resources/cp-mode-bits/executable').mode, fs.statSync(`${t.context.tmp}/executable`).mode ); } } ); test('Make sure hidden files are copied recursively', t => { shell.rm('-rf', t.context.tmp); const result = shell.cp('-r', 'resources/ls/', t.context.tmp); t.falsy(shell.error()); t.falsy(result.stderr); t.is(result.code, 0); t.truthy(fs.existsSync(`${t.context.tmp}/.hidden_file`)); }); test('no-recursive will copy regular files only', t => { const result = shell.cp('resources/file1.txt', 'resources/ls/', t.context.tmp); t.is(result.code, 1); t.truthy(shell.error()); t.falsy(fs.existsSync(`${t.context.tmp}/.hidden_file`)); // doesn't copy dir contents t.falsy(fs.existsSync(`${t.context.tmp}/ls`)); // doesn't copy dir itself t.truthy(fs.existsSync(`${t.context.tmp}/file1.txt`)); }); test('no-recursive will copy regular files only', t => { const result = shell.cp('resources/file1.txt', 'resources/file2.txt', 'resources/cp', 'resources/ls/', t.context.tmp); t.is(result.code, 1); t.truthy(shell.error()); t.falsy(fs.existsSync(`${t.context.tmp}/.hidden_file`)); // doesn't copy dir contents t.falsy(fs.existsSync(`${t.context.tmp}/ls`)); // doesn't copy dir itself t.falsy(fs.existsSync(`${t.context.tmp}/a`)); // doesn't copy dir contents t.falsy(fs.existsSync(`${t.context.tmp}/cp`)); // doesn't copy dir itself t.truthy(fs.existsSync(`${t.context.tmp}/file1.txt`)); t.truthy(fs.existsSync(`${t.context.tmp}/file2.txt`)); }); test('-R implies -P', t => { if (process.platform !== 'win32') { shell.cp('-R', 'resources/cp/links/sym.lnk', t.context.tmp); t.truthy(fs.lstatSync(`${t.context.tmp}/sym.lnk`).isSymbolicLink()); } }); test('using -P explicitly works', t => { if (process.platform !== 'win32') { shell.cp('-P', 'resources/cp/links/sym.lnk', t.context.tmp); t.truthy(fs.lstatSync(`${t.context.tmp}/sym.lnk`).isSymbolicLink()); } }); test('using -PR on a link to a folder does not follow the link', t => { if (process.platform !== 'win32') { shell.cp('-PR', 'resources/cp/symFolder', t.context.tmp); t.truthy(fs.lstatSync(`${t.context.tmp}/symFolder`).isSymbolicLink()); } }); test('-L overrides -P for copying directory', t => { if (process.platform !== 'win32') { shell.cp('-LPR', 'resources/cp/symFolder', t.context.tmp); t.falsy(fs.lstatSync(`${t.context.tmp}/symFolder`).isSymbolicLink()); t.falsy(fs.lstatSync(`${t.context.tmp}/symFolder/sym.lnk`).isSymbolicLink()); } }); test('Recursive, copies entire directory with no symlinks and -L option does not cause change in behavior', t => { if (process.platform !== 'win32') { const result = shell.cp('-rL', 'resources/cp/dir_a', `${t.context.tmp}/dest`); t.falsy(shell.error()); t.falsy(result.stderr); t.is(result.code, 0); t.truthy(fs.existsSync(`${t.context.tmp}/dest/z`)); } }); test('-u flag won\'t overwrite newer files', t => { shell.touch(`${t.context.tmp}/file1.js`); shell.cp('-u', 'resources/file1.js', t.context.tmp); t.falsy(shell.error()); t.not(shell.cat('resources/file1.js').toString(), shell.cat(`${t.context.tmp}/file1.js`).toString()); }); test('-u flag does overwrite older files', t => { shell.touch({ '-d': new Date(10) }, `${t.context.tmp}/file1.js`); // really old file shell.cp('-u', 'resources/file1.js', t.context.tmp); t.falsy(shell.error()); t.is(shell.cat('resources/file1.js').toString(), shell.cat(`${t.context.tmp}/file1.js`).toString()); }); test('-u flag works even if it\'s not overwriting a file', t => { t.falsy(fs.existsSync(`${t.context.tmp}/file1.js`)); shell.cp('-u', 'resources/file1.js', t.context.tmp); t.falsy(shell.error()); t.is(shell.cat('resources/file1.js').toString(), shell.cat(`${t.context.tmp}/file1.js`).toString()); }); test('-u flag works correctly recursively', t => { shell.mkdir(`${t.context.tmp}/foo`); [1, 2, 3].forEach(num => { new shell.ShellString('old\n').to(`${t.context.tmp}/foo/file${num}`); shell.touch({ '-d': new Date(10) }, `${t.context.tmp}/foo/file${num}`); }); shell.mkdir(`${t.context.tmp}/bar`); [1, 2, 3].forEach(num => { new shell.ShellString('new\n').to(`${t.context.tmp}/bar/file${num}`); shell.touch({ '-d': new Date(1000) }, `${t.context.tmp}/bar/file${num}`); }); // put one new one in the foo directory new shell.ShellString('newest\n').to(`${t.context.tmp}/foo/file3`); shell.touch({ '-d': new Date(10000) }, `${t.context.tmp}/foo/file3`); shell.cp('-u', `${t.context.tmp}/foo/*`, `${t.context.tmp}/bar`); t.falsy(shell.error()); t.is(shell.cat(`${t.context.tmp}/bar/*`).toString(), 'new\nnew\nnewest\n'); }); test('using -R on a link to a folder *does* follow the link', t => { shell.cp('-R', 'resources/cp/symFolder', t.context.tmp); t.falsy(fs.lstatSync(`${t.context.tmp}/symFolder`).isSymbolicLink()); }); test('Without -R, -L is implied', t => { shell.cp('resources/cp/links/sym.lnk', t.context.tmp); t.falsy(fs.lstatSync(`${t.context.tmp}/sym.lnk`).isSymbolicLink()); }); test('-L explicitly works', t => { shell.cp('-L', 'resources/cp/links/sym.lnk', t.context.tmp); t.falsy(fs.lstatSync(`${t.context.tmp}/sym.lnk`).isSymbolicLink()); }); test('using -LR does not imply -P', t => { shell.cp('-LR', 'resources/cp/links/sym.lnk', t.context.tmp); t.falsy(fs.lstatSync(`${t.context.tmp}/sym.lnk`).isSymbolicLink()); }); test('using -LR also works recursively on directories containing links', t => { shell.cp('-LR', 'resources/cp/links', t.context.tmp); t.falsy(fs.lstatSync(`${t.context.tmp}/links/sym.lnk`).isSymbolicLink()); }); test('-L always overrides a -P', t => { shell.cp('-LP', 'resources/cp/links/sym.lnk', t.context.tmp); t.falsy(fs.lstatSync(`${t.context.tmp}/sym.lnk`).isSymbolicLink()); shell.cp('-LPR', 'resources/cp/links/sym.lnk', t.context.tmp); t.falsy(fs.lstatSync(`${t.context.tmp}/sym.lnk`).isSymbolicLink()); }); test('Make sure max depth does not limit shallow directory structures', t => { shell.config.maxdepth = 3; const TMP = t.context.tmp; shell.mkdir(`${TMP}/foo`); for (let k = 0; k < 5; k++) { shell.mkdir(`${TMP}/foo/dir${k}`); } shell.cp('-r', `${TMP}/foo`, `${TMP}/bar`); t.is(shell.ls(`${TMP}/foo`).stdout, shell.ls(`${TMP}/bar`).stdout); }); test('Test max depth.', t => { shell.config.maxdepth = 32; let directory = ''; for (let i = 1; i < 40; i++) { directory += '/' + i; } let directory32deep = ''; for (let i = 1; i < 32; i++) { directory32deep += '/' + i; } shell.mkdir('-p', `${t.context.tmp}/0${directory}`); shell.cp('-r', `${t.context.tmp}/0`, `${t.context.tmp}/copytestdepth`); // Check full directory exists. t.truthy(shell.test('-d', `${t.context.tmp}/0/${directory}`)); // Check full copy of directory does not exist. t.falsy(shell.test('-d', `${t.context.tmp}/copytestdepth${directory}`)); // Check last directory to exist is below maxdepth. t.truthy(shell.test('-d', `${t.context.tmp}/copytestdepth${directory32deep}`)); t.falsy(shell.test('-d', `${t.context.tmp}/copytestdepth${directory32deep}/32`)); utils.skipOnWinForEPERM(shell.ln.bind(shell, '-s', `${t.context.tmp}/0`, `${t.context.tmp}/symlinktest`), () => { if (!shell.test('-L', `${t.context.tmp}/symlinktest`)) { t.fail(); } // Create symlinks to check for cycle. shell.cd(`${t.context.tmp}/0/1/2/3/4`); t.falsy(shell.error()); shell.ln('-s', '../../../2', 'link'); t.falsy(shell.error()); shell.ln('-s', './5/6/7', 'link1'); t.falsy(shell.error()); shell.cd('../../../../../..'); t.falsy(shell.error()); t.truthy(shell.test('-d', t.context.tmp)); shell.cp('-r', `${t.context.tmp}/0/1`, `${t.context.tmp}/copytestdepth`); t.falsy(shell.error()); t.truthy(shell.test('-d', `${t.context.tmp}/copytestdepth/1/2/3/4/link/3/4/link/3/4`)); }); }); test('cp -L follows symlinks', t => { utils.skipOnWinForEPERM(shell.ln.bind(shell, '-s', `${t.context.tmp}/0`, `${t.context.tmp}/symlinktest`), () => { shell.mkdir('-p', `${t.context.tmp}/sub`); shell.mkdir('-p', `${t.context.tmp}/new`); shell.cp('-f', 'resources/file1.txt', `${t.context.tmp}/sub/file.txt`); shell.cd(`${t.context.tmp}/sub`); shell.ln('-s', 'file.txt', 'foo.lnk'); shell.ln('-s', 'file.txt', 'sym.lnk'); shell.cd('..'); shell.cp('-L', 'sub/*', 'new/'); shell.cd('new'); shell.cp('-f', '../../resources/file2.txt', 'file.txt'); t.is(shell.cat('file.txt').toString(), 'test2\n'); // Ensure other files have not changed. t.is(shell.cat('foo.lnk').toString(), 'test1\n'); t.is(shell.cat('sym.lnk').toString(), 'test1\n'); t.falsy(shell.test('-L', 'foo.lnk')); t.falsy(shell.test('-L', 'sym.lnk')); shell.cd('../..'); }); }); test('Test with recursive option and symlinks.', t => { utils.skipOnWinForEPERM(shell.ln.bind(shell, '-s', `${t.context.tmp}/0`, `${t.context.tmp}/symlinktest`), () => { shell.mkdir('-p', `${t.context.tmp}/sub/sub1`); shell.cp('-f', 'resources/file1.txt', `${t.context.tmp}/sub/file.txt`); shell.cp('-f', 'resources/file1.txt', `${t.context.tmp}/sub/sub1/file.txt`); shell.cd(`${t.context.tmp}/sub`); shell.ln('-s', 'file.txt', 'foo.lnk'); shell.ln('-s', 'file.txt', 'sym.lnk'); shell.cd('sub1'); shell.ln('-s', '../file.txt', 'foo.lnk'); shell.ln('-s', '../file.txt', 'sym.lnk'); // Ensure file reads from proper source t.is(shell.cat('file.txt').toString(), 'test1\n'); t.is(shell.cat('foo.lnk').toString(), 'test1\n'); t.is(shell.cat('sym.lnk').toString(), 'test1\n'); t.truthy(shell.test('-L', 'foo.lnk')); t.truthy(shell.test('-L', 'sym.lnk')); shell.cd('../..'); shell.cp('-rL', 'sub/', 'new/'); shell.cd('new'); // Ensure copies of files are symlinks by updating file contents. shell.cp('-f', '../../resources/file2.txt', 'file.txt'); t.is(shell.cat('file.txt').toString(), 'test2\n'); // Ensure other files have not changed. t.is(shell.cat('foo.lnk').toString(), 'test1\n'); t.is(shell.cat('sym.lnk').toString(), 'test1\n'); // Ensure the links are converted to files. t.falsy(shell.test('-L', 'foo.lnk')); t.falsy(shell.test('-L', 'sym.lnk')); // Ensure other files have not changed. shell.cd('sub1'); shell.cp('-f', '../../../resources/file2.txt', 'file.txt'); t.is(shell.cat('file.txt').toString(), 'test2\n'); t.is(shell.cat('foo.lnk').toString(), 'test1\n'); t.is(shell.cat('sym.lnk').toString(), 'test1\n'); // Ensure the links are converted to files t.falsy(shell.test('-L', 'foo.lnk')); t.falsy(shell.test('-L', 'sym.lnk')); }); }); test('recursive, with a non-normalized path', t => { const result = shell.cp('-R', 'resources/../resources/./cp', t.context.tmp); t.falsy(shell.error()); // crash test only t.falsy(result.stderr); t.is(result.code, 0); });