mirror of
https://github.com/shelljs/shelljs.git
synced 2025-12-08 20:35:51 +00:00
No change to logic. This swaps over tests to use require() since everything is currently designed for the commonjs module system.
925 lines
35 KiB
JavaScript
925 lines
35 KiB
JavaScript
const fs = require('fs');
|
|
|
|
const test = require('ava');
|
|
|
|
const shell = require('..');
|
|
const common = require('../src/common');
|
|
const utils = require('./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 <source> and/or <dest>');
|
|
});
|
|
|
|
test('no destination', t => {
|
|
const result = shell.cp('file1');
|
|
t.truthy(shell.error());
|
|
t.is(result.code, 1);
|
|
t.is(result.stderr, 'cp: missing <source> and/or <dest>');
|
|
});
|
|
|
|
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 <source> and/or <dest>');
|
|
});
|
|
|
|
test('invalid option', t => {
|
|
const result = shell.cp('-@', 'test/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 #2', 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('multiple sources do 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', 'test/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('test/resources/file1', 'test/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)');
|
|
});
|
|
|
|
test('empty string source', t => {
|
|
const result = shell.cp('', 'dest');
|
|
t.truthy(shell.error());
|
|
t.is(result.code, 1);
|
|
t.is(result.stderr, "cp: no such file or directory: ''");
|
|
});
|
|
|
|
//
|
|
// Valids
|
|
//
|
|
|
|
test('dest already exists', t => {
|
|
const oldContents = shell.cat('test/resources/file2').toString();
|
|
const result = shell.cp('-n', 'test/resources/file1', 'test/resources/file2');
|
|
t.falsy(shell.error());
|
|
t.is(result.code, 0);
|
|
t.is(result.stderr, '');
|
|
t.is(shell.cat('test/resources/file2').toString(), oldContents);
|
|
});
|
|
|
|
test('-nR does not overwrite an existing file at the destination', t => {
|
|
// Create tmp/new/cp/a
|
|
const dest = `${t.context.tmp}/new/cp`;
|
|
shell.mkdir('-p', dest);
|
|
const oldContents = 'original content';
|
|
shell.ShellString(oldContents).to(`${dest}/a`);
|
|
|
|
// Attempt to overwrite /tmp/new/cp/ with test/resources/cp/
|
|
const result = shell.cp('-nR', 'test/resources/cp/', `${t.context.tmp}/new/`);
|
|
t.falsy(shell.error());
|
|
t.is(result.code, 0);
|
|
t.falsy(result.stderr);
|
|
t.is(shell.cat(`${dest}/a`).toString(), oldContents);
|
|
});
|
|
|
|
test('-n does not overwrite an existing file if the destination is a directory', t => {
|
|
const oldContents = 'original content';
|
|
shell.cp('test/resources/file1', `${t.context.tmp}`);
|
|
new shell.ShellString(oldContents).to(`${t.context.tmp}/file1`);
|
|
const result = shell.cp('-n', 'test/resources/file1', `${t.context.tmp}`);
|
|
t.falsy(shell.error());
|
|
t.is(result.code, 0);
|
|
t.falsy(result.stderr);
|
|
t.is(shell.cat(`${t.context.tmp}/file1`).toString(), oldContents);
|
|
});
|
|
|
|
test('-f by default', t => {
|
|
shell.cp('test/resources/file2', 'test/resources/copyfile2');
|
|
const result = shell.cp('test/resources/file1', 'test/resources/file2'); // dest already exists
|
|
t.falsy(shell.error());
|
|
t.is(result.code, 0);
|
|
t.falsy(result.stderr);
|
|
t.is(shell.cat('test/resources/file1').toString(), shell.cat('test/resources/file2').toString()); // after cp
|
|
shell.mv('test/resources/copyfile2', 'test/resources/file2'); // restore
|
|
t.falsy(shell.error());
|
|
});
|
|
|
|
test('-f (explicitly)', t => {
|
|
shell.cp('test/resources/file2', 'test/resources/copyfile2');
|
|
const result = shell.cp('-f', 'test/resources/file1', 'test/resources/file2'); // dest already exists
|
|
t.falsy(shell.error());
|
|
t.falsy(result.stderr);
|
|
t.is(result.code, 0);
|
|
t.is(shell.cat('test/resources/file1').toString(), shell.cat('test/resources/file2').toString()); // after cp
|
|
shell.mv('test/resources/copyfile2', 'test/resources/file2'); // restore
|
|
t.falsy(shell.error());
|
|
t.is(result.code, 0);
|
|
});
|
|
|
|
test('simple - to dir', t => {
|
|
const result = shell.cp('test/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('test/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('test/resources/file1', 'test/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(['test/resources/file1', 'test/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('test/resources/file2', `${t.context.tmp}/file3`);
|
|
t.truthy(fs.existsSync(`${t.context.tmp}/file3`));
|
|
const result = shell.cp('-f', 'test/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('test/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('test/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', 'test/resources/file1', 'test/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('omit directory if missing recursive flag', t => {
|
|
const result = shell.cp('test/resources/cp', t.context.tmp);
|
|
t.is(shell.error(), "cp: omitting directory 'test/resources/cp'");
|
|
t.is(result.stderr, "cp: omitting directory 'test/resources/cp'");
|
|
t.is(result.code, 1);
|
|
t.falsy(fs.existsSync(`${t.context.tmp}/file1`));
|
|
t.falsy(fs.existsSync(`${t.context.tmp}/file2`));
|
|
});
|
|
|
|
test('recursive, nothing exists', t => {
|
|
const result = shell.cp('-R', 'test/resources/cp', t.context.tmp);
|
|
t.falsy(shell.error());
|
|
t.falsy(result.stderr);
|
|
t.is(result.code, 0);
|
|
t.is(shell.ls('-R', 'test/resources/cp').toString(), shell.ls('-R', `${t.context.tmp}/cp`).toString());
|
|
});
|
|
|
|
test('recursive, nothing exists, source ends in "/"', t => {
|
|
// Github issue #15
|
|
const result = shell.cp('-R', 'test/resources/cp/', `${t.context.tmp}/`);
|
|
t.falsy(shell.error());
|
|
t.falsy(result.stderr);
|
|
t.is(result.code, 0);
|
|
t.is(shell.ls('-R', 'test/resources/cp').toString(), shell.ls('-R', `${t.context.tmp}/cp`).toString());
|
|
});
|
|
|
|
test('recursive, globbing regular files with extension', t => {
|
|
// Github issue #376
|
|
const result = shell.cp('-R', 'test/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', t => {
|
|
// Github issue #376
|
|
const result = shell.cp('-R', 'test/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(common.statFollowLinks(`${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', 'test/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 => {
|
|
utils.skipOnWin(t, () => {
|
|
shell.cp('-R', 'test/resources/cp/*', t.context.tmp);
|
|
t.truthy(common.statNoFollowLinks(`${t.context.tmp}/links/sym.lnk`).isSymbolicLink()); // this one is a link
|
|
t.falsy((common.statNoFollowLinks(`${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(common.statNoFollowLinks(`${t.context.tmp}/links/sym.lnk`).isSymbolicLink()); // this one is a link
|
|
t.truthy(common.statNoFollowLinks(`${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 => {
|
|
utils.skipOnWin(t, () => {
|
|
// Recursive, everything exists, overwrite a real file *by following a link*
|
|
// Because missing the -R implies -L.
|
|
shell.cp('-R', 'test/resources/cp/*', t.context.tmp);
|
|
t.truthy(common.statNoFollowLinks(`${t.context.tmp}/links/sym.lnk`).isSymbolicLink()); // this one is a link
|
|
t.falsy((common.statNoFollowLinks(`${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(common.statNoFollowLinks(`${t.context.tmp}/links/sym.lnk`).isSymbolicLink()); // this one is a link
|
|
t.falsy(common.statNoFollowLinks(`${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', 'test/resources/cp', t.context.tmp);
|
|
shell.ShellString('changing things around').to(`${t.context.tmp}/cp/dir_a/z`);
|
|
t.not(shell.cat('test/resources/cp/dir_a/z').toString(), shell.cat(`${t.context.tmp}/cp/dir_a/z`).toString()); // before cp
|
|
result = shell.cp('-Rf', 'test/resources/cp', t.context.tmp);
|
|
t.falsy(shell.error());
|
|
t.falsy(result.stderr);
|
|
t.is(result.code, 0);
|
|
t.is(shell.cat('test/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", t => {
|
|
// Github issue #44
|
|
const result = shell.cp('-r', 'test/resources/issue44', `${t.context.tmp}/dir2`);
|
|
t.falsy(shell.error());
|
|
t.falsy(result.stderr);
|
|
t.is(result.code, 0);
|
|
t.is(shell.ls('-R', 'test/resources/issue44').toString(), shell.ls('-R', `${t.context.tmp}/dir2`).toString());
|
|
t.is(
|
|
shell.cat('test/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", t => {
|
|
// Github issue #44
|
|
const result = shell.cp('-r', 'test/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', 'test/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', 'test/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('preserve mode bits by default for file', t => {
|
|
utils.skipOnWin(t, () => {
|
|
const execBit = parseInt('001', 8);
|
|
t.is(common.statFollowLinks('test/resources/cp-mode-bits/executable').mode & execBit, execBit);
|
|
shell.cp('test/resources/cp-mode-bits/executable', `${t.context.tmp}/executable`);
|
|
t.is(
|
|
common.statFollowLinks('test/resources/cp-mode-bits/executable').mode,
|
|
common.statFollowLinks(`${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', 'test/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(
|
|
'test/resources/file1.txt', 'test/resources/file2.txt', 'test/resources/cp',
|
|
'test/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 => {
|
|
utils.skipOnWin(t, () => {
|
|
shell.cp('-R', 'test/resources/cp/links/sym.lnk', t.context.tmp);
|
|
t.truthy(common.statNoFollowLinks(`${t.context.tmp}/sym.lnk`).isSymbolicLink());
|
|
});
|
|
});
|
|
|
|
test("-Ru respects the -u flag recursively (don't update newer file)", t => {
|
|
// Setup code
|
|
const dir = `${t.context.tmp}/cp-Ru`;
|
|
const sourceDir = `${dir}/original`;
|
|
const sourceFile = `${sourceDir}/file`;
|
|
const destDir = `${dir}/new`;
|
|
const nestedDestDir = `${dir}/new/original`;
|
|
const destFile = `${nestedDestDir}/file`;
|
|
[sourceDir, destDir, nestedDestDir].forEach(d => shell.mkdir('-p', d));
|
|
shell.ShellString('Source File Contents\n').to(sourceFile);
|
|
shell.ShellString('Destination File Contents\n').to(destFile);
|
|
const oldModifyTimeMs = 12345000;
|
|
const newModifyTimeMs = 67890000;
|
|
// End setup
|
|
|
|
// Set the source file to be OLDER than the destination file
|
|
shell.touch({ '-m': true, '-d': new Date(oldModifyTimeMs) }, sourceFile);
|
|
shell.touch({ '-m': true, '-d': new Date(newModifyTimeMs) }, destFile);
|
|
shell.cp('-Ru', sourceDir, destDir);
|
|
// Check that dest has not been updated
|
|
t.is(shell.cat(destFile).stdout, 'Destination File Contents\n');
|
|
});
|
|
|
|
test('-Ru respects the -u flag recursively (update older file)', t => {
|
|
// Setup code
|
|
const dir = `${t.context.tmp}/cp-Ru`;
|
|
const sourceDir = `${dir}/original`;
|
|
const sourceFile = `${sourceDir}/file`;
|
|
const destDir = `${dir}/new`;
|
|
const nestedDestDir = `${dir}/new/original`;
|
|
const destFile = `${nestedDestDir}/file`;
|
|
[sourceDir, destDir, nestedDestDir].forEach(d => shell.mkdir('-p', d));
|
|
shell.ShellString('Source File Contents\n').to(sourceFile);
|
|
shell.ShellString('Destination File Contents\n').to(destFile);
|
|
const oldModifyTimeMs = 12345000;
|
|
const newModifyTimeMs = 67890000;
|
|
// End setup
|
|
|
|
// Set the source file to be NEWER than the destination file
|
|
shell.touch({ '-m': true, '-d': new Date(newModifyTimeMs) }, sourceFile);
|
|
shell.touch({ '-m': true, '-d': new Date(oldModifyTimeMs) }, destFile);
|
|
shell.cp('-Ru', sourceDir, destDir);
|
|
// Check that dest has been overwritten
|
|
t.is(shell.cat(destFile).stdout, 'Source File Contents\n');
|
|
});
|
|
|
|
test('using -P explicitly works', t => {
|
|
utils.skipOnWin(t, () => {
|
|
shell.cp('-P', 'test/resources/cp/links/sym.lnk', t.context.tmp);
|
|
t.truthy(common.statNoFollowLinks(`${t.context.tmp}/sym.lnk`).isSymbolicLink());
|
|
});
|
|
});
|
|
|
|
test('using -PR on a link to a folder does not follow the link', t => {
|
|
utils.skipOnWin(t, () => {
|
|
shell.cp('-PR', 'test/resources/cp/symFolder', t.context.tmp);
|
|
t.truthy(common.statNoFollowLinks(`${t.context.tmp}/symFolder`).isSymbolicLink());
|
|
});
|
|
});
|
|
|
|
test('-L overrides -P for copying directory', t => {
|
|
utils.skipOnWin(t, () => {
|
|
shell.cp('-LPR', 'test/resources/cp/symFolder', t.context.tmp);
|
|
t.falsy(common.statNoFollowLinks(`${t.context.tmp}/symFolder`).isSymbolicLink());
|
|
t.falsy(common.statNoFollowLinks(`${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 => {
|
|
utils.skipOnWin(t, () => {
|
|
const result = shell.cp('-rL', 'test/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', 'test/resources/file1.js', t.context.tmp);
|
|
t.falsy(shell.error());
|
|
t.not(shell.cat('test/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', 'test/resources/file1.js', t.context.tmp);
|
|
t.falsy(shell.error());
|
|
t.is(shell.cat('test/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', 'test/resources/file1.js', t.context.tmp);
|
|
t.falsy(shell.error());
|
|
t.is(shell.cat('test/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', 'test/resources/cp/symFolder', t.context.tmp);
|
|
t.falsy(common.statNoFollowLinks(`${t.context.tmp}/symFolder`).isSymbolicLink());
|
|
});
|
|
|
|
test('Without -R, -L is implied', t => {
|
|
shell.cp('test/resources/cp/links/sym.lnk', t.context.tmp);
|
|
t.falsy(common.statNoFollowLinks(`${t.context.tmp}/sym.lnk`).isSymbolicLink());
|
|
});
|
|
|
|
test('-L explicitly works', t => {
|
|
shell.cp('-L', 'test/resources/cp/links/sym.lnk', t.context.tmp);
|
|
t.falsy(common.statNoFollowLinks(`${t.context.tmp}/sym.lnk`).isSymbolicLink());
|
|
});
|
|
|
|
test('using -LR does not imply -P', t => {
|
|
shell.cp('-LR', 'test/resources/cp/links/sym.lnk', t.context.tmp);
|
|
t.falsy(common.statNoFollowLinks(`${t.context.tmp}/sym.lnk`).isSymbolicLink());
|
|
});
|
|
|
|
test('using -LR also works recursively on directories containing links', t => {
|
|
shell.cp('-LR', 'test/resources/cp/links', t.context.tmp);
|
|
t.falsy(common.statNoFollowLinks(`${t.context.tmp}/links/sym.lnk`).isSymbolicLink());
|
|
});
|
|
|
|
test('-L always overrides a -P', t => {
|
|
shell.cp('-LP', 'test/resources/cp/links/sym.lnk', t.context.tmp);
|
|
t.falsy(common.statNoFollowLinks(`${t.context.tmp}/sym.lnk`).isSymbolicLink());
|
|
shell.cp('-LPR', 'test/resources/cp/links/sym.lnk', t.context.tmp);
|
|
t.falsy(common.statNoFollowLinks(`${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', 'test/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', '../../test/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', 'test/resources/file1.txt', `${t.context.tmp}/sub/file.txt`);
|
|
shell.cp('-f', 'test/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', '../../test/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', '../../../test/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', 'test/resources/../resources/./cp', t.context.tmp);
|
|
t.falsy(shell.error()); // crash test only
|
|
t.falsy(result.stderr);
|
|
t.is(result.code, 0);
|
|
});
|
|
|
|
test('copy file to same path', t => {
|
|
const result = shell.cp('test/resources/file1', 'test/resources/file1');
|
|
t.truthy(shell.error());
|
|
t.is(result.code, 1);
|
|
t.is(result.stderr, "cp: 'test/resources/file1' and 'test/resources/file1' are the same file");
|
|
});
|
|
|
|
test('copy file to same directory', t => {
|
|
const result = shell.cp('test/resources/file1', 'test/resources');
|
|
t.truthy(shell.error());
|
|
t.is(result.code, 1);
|
|
t.is(result.stderr, "cp: 'test/resources/file1' and 'test/resources/file1' are the same file");
|
|
});
|
|
|
|
test('copy multiple files to same location', t => {
|
|
const result = shell.cp('test/resources/file1', 'test/resources/file2', 'test/resources');
|
|
t.truthy(shell.error());
|
|
t.is(result.code, 1);
|
|
t.is(
|
|
result.stderr,
|
|
"cp: 'test/resources/file1' and 'test/resources/file1' are the same file\n" +
|
|
"cp: 'test/resources/file2' and 'test/resources/file2' are the same file"
|
|
);
|
|
});
|
|
|
|
test('should not overwrite recently created files', t => {
|
|
const result = shell.cp('test/resources/file1', 'test/resources/cp/file1', t.context.tmp);
|
|
t.truthy(shell.error());
|
|
t.is(result.code, 1);
|
|
|
|
// Ensure First file is copied
|
|
t.is(shell.cat(`${t.context.tmp}/file1`).toString(), 'test1');
|
|
t.is(
|
|
result.stderr,
|
|
`cp: will not overwrite just-created '${t.context.tmp}/file1' with 'test/resources/cp/file1'`
|
|
);
|
|
});
|
|
|
|
|
|
test('should not overwrite recently created files (in recursive Mode)', t => {
|
|
const result = shell.cp('-R', 'test/resources/file1', 'test/resources/cp/file1', t.context.tmp);
|
|
t.truthy(shell.error());
|
|
t.is(result.code, 1);
|
|
|
|
// Ensure First file is copied
|
|
t.is(shell.cat(`${t.context.tmp}/file1`).toString(), 'test1');
|
|
t.is(
|
|
result.stderr,
|
|
`cp: will not overwrite just-created '${t.context.tmp}/file1' with 'test/resources/cp/file1'`
|
|
);
|
|
});
|
|
|
|
test('should not overwrite recently created files (not give error no-force mode)', t => {
|
|
const result = shell.cp('-n', 'test/resources/file1', 'test/resources/cp/file1', t.context.tmp);
|
|
t.falsy(shell.error());
|
|
t.is(result.code, 0);
|
|
|
|
// Ensure First file is copied
|
|
t.is(shell.cat(`${t.context.tmp}/file1`).toString(), 'test1');
|
|
});
|
|
|
|
// cp -R should be able to copy a readonly src (issue #98).
|
|
// On Windows, chmod acts VERY differently so skip these tests for now
|
|
test('cp -R should be able to copy a readonly src. issue #98; (Non window platforms only)', t => {
|
|
utils.skipOnWin(t, () => {
|
|
shell.cp('-r', 'test/resources/cp', t.context.tmp);
|
|
shell.chmod('555', `${t.context.tmp}/cp/`);
|
|
shell.chmod('555', `${t.context.tmp}/cp/dir_a`);
|
|
shell.chmod('555', `${t.context.tmp}/cp/dir_b`);
|
|
shell.chmod('555', `${t.context.tmp}/cp/a`);
|
|
|
|
const result = shell.cp('-r', `${t.context.tmp}/cp`, `${t.context.tmp}/cp_cp`);
|
|
t.falsy(shell.error());
|
|
t.falsy(result.stderr);
|
|
t.is(result.code, 0);
|
|
|
|
t.is(shell.ls('-R', `${t.context.tmp}/cp`) + '', shell.ls('-R', `${t.context.tmp}/cp_cp`) + '');
|
|
t.is(fs.statSync(`${t.context.tmp}/cp_cp`).mode & parseInt('777', 8), parseInt('555', 8));
|
|
t.is(fs.statSync(`${t.context.tmp}/cp_cp/dir_a`).mode & parseInt('777', 8), parseInt('555', 8));
|
|
t.is(fs.statSync(`${t.context.tmp}/cp_cp/a`).mode & parseInt('777', 8), parseInt('555', 8));
|
|
|
|
shell.chmod('-R', '755', t.context.tmp);
|
|
});
|
|
});
|
|
|
|
test('cp -p should preserve mode, ownership, and timestamp (regular file)', t => {
|
|
// Setup: copy to srcFile and modify mode and timestamp
|
|
const srcFile = `${t.context.tmp}/srcFile`;
|
|
shell.cp('test/resources/cp/file1', srcFile);
|
|
// Make this a round number of seconds, since the underlying system may not
|
|
// have millisecond precision.
|
|
const newModifyTimeMs = 12345000;
|
|
const newAccessTimeMs = 67890000;
|
|
shell.touch({ '-d': new Date(newModifyTimeMs), '-m': true }, srcFile);
|
|
shell.touch({ '-d': new Date(newAccessTimeMs), '-a': true }, srcFile);
|
|
const mode = '444';
|
|
shell.chmod(mode, srcFile);
|
|
|
|
// Now re-copy with '-p' and verify metadata.
|
|
const result = shell.cp('-p', srcFile, `${t.context.tmp}/preservedFile1`);
|
|
const stat = common.statFollowLinks(srcFile);
|
|
const statOfResult = common.statFollowLinks(`${t.context.tmp}/preservedFile1`);
|
|
|
|
t.is(result.code, 0);
|
|
|
|
// Original file should be unchanged:
|
|
t.is(stat.mtime.getTime(), newModifyTimeMs);
|
|
// cp appears to update the atime, but only of the srcFile
|
|
t.is(stat.mode.toString(8), '100' + mode);
|
|
|
|
// New file should keep same attributes
|
|
t.is(statOfResult.mtime.getTime(), newModifyTimeMs);
|
|
t.is(statOfResult.atime.getTime(), newAccessTimeMs);
|
|
t.is(statOfResult.mode.toString(8), '100' + mode);
|
|
|
|
t.is(stat.uid, statOfResult.uid);
|
|
t.is(stat.gid, statOfResult.gid);
|
|
});
|
|
|
|
test('cp -p should preserve mode, ownership, and timestamp (directory)', t => {
|
|
// Setup: copy to srcFile and modify mode and timestamp
|
|
const srcDir = `${t.context.tmp}/srcDir`;
|
|
const srcFile = `${srcDir}/srcFile`;
|
|
shell.mkdir(srcDir);
|
|
shell.cp('test/resources/cp/file1', srcFile);
|
|
// Make this a round number of seconds, since the underlying system may not
|
|
// have millisecond precision.
|
|
const newModifyTimeMs = 12345000;
|
|
const newAccessTimeMs = 67890000;
|
|
shell.touch({ '-d': new Date(newModifyTimeMs), '-m': true }, srcFile);
|
|
shell.touch({ '-d': new Date(newAccessTimeMs), '-a': true }, srcFile);
|
|
fs.utimesSync(srcDir, new Date(newAccessTimeMs), new Date(newModifyTimeMs));
|
|
const mode = '444';
|
|
shell.chmod(mode, srcFile);
|
|
|
|
// Now re-copy (the whole dir) with '-p' and verify metadata of file contents.
|
|
const result = shell.cp('-pr', srcDir, `${t.context.tmp}/preservedDir`);
|
|
const stat = common.statFollowLinks(srcFile);
|
|
const statDir = common.statFollowLinks(srcDir);
|
|
const statOfResult = common.statFollowLinks(`${t.context.tmp}/preservedDir/srcFile`);
|
|
const statOfResultDir = common.statFollowLinks(`${t.context.tmp}/preservedDir`);
|
|
|
|
t.is(result.code, 0);
|
|
|
|
// Both original file and original dir should be unchanged:
|
|
t.is(statDir.mtime.getTime(), newModifyTimeMs);
|
|
t.is(stat.mtime.getTime(), newModifyTimeMs);
|
|
// cp appears to update the atime, but only of the srcFile & srcDir
|
|
t.is(stat.mode.toString(8), '100' + mode);
|
|
|
|
// Both new file and new dir should keep same attributes
|
|
t.is(statOfResultDir.mtime.getTime(), newModifyTimeMs);
|
|
utils.skipOnWin(t, () => {
|
|
// The resultDir atime may be updated on Windows as a side-effect, e.g. chmod().
|
|
t.is(statOfResultDir.atime.getTime(), newAccessTimeMs);
|
|
});
|
|
t.is(statOfResult.mtime.getTime(), newModifyTimeMs);
|
|
t.is(statOfResult.atime.getTime(), newAccessTimeMs);
|
|
t.is(statOfResult.mode.toString(8), '100' + mode);
|
|
|
|
t.is(stat.uid, statOfResult.uid);
|
|
t.is(stat.gid, statOfResult.gid);
|
|
});
|
|
|
|
test('cp -p should preserve mode, ownership, and timestamp (symlink)', t => {
|
|
// Skip in Windows because symlinks require elevated permissions.
|
|
utils.skipOnWin(t, () => {
|
|
// Setup: copy to srcFile, create srcLink, and modify mode and timestamp
|
|
shell.cp('test/resources/cp/file1', `${t.context.tmp}/srcFile`);
|
|
const srcLink = `${t.context.tmp}/srcLink`;
|
|
shell.ln('-s', 'srcFile', `${t.context.tmp}/srcLink`);
|
|
// Make this a round number of seconds, since the underlying system may not
|
|
// have millisecond precision.
|
|
const newModifyTimeMs = 12345000;
|
|
const newAccessTimeMs = 67890000;
|
|
shell.touch({ '-d': new Date(newModifyTimeMs), '-m': true }, srcLink);
|
|
shell.touch({ '-d': new Date(newAccessTimeMs), '-a': true }, srcLink);
|
|
const mode = '444';
|
|
shell.chmod(mode, srcLink);
|
|
|
|
// Now re-copy with '-p' and verify metadata.
|
|
const result = shell.cp('-p', srcLink, `${t.context.tmp}/preservedLink`);
|
|
const stat = common.statFollowLinks(srcLink);
|
|
const statOfResult = common.statFollowLinks(`${t.context.tmp}/preservedLink`);
|
|
|
|
t.is(result.code, 0);
|
|
|
|
// Original file should be unchanged:
|
|
t.is(stat.mtime.getTime(), newModifyTimeMs);
|
|
// cp appears to update the atime, but only of the srcFile
|
|
t.is(stat.mode.toString(8), '100' + mode);
|
|
|
|
// New file should keep same attributes
|
|
t.is(statOfResult.mtime.getTime(), newModifyTimeMs);
|
|
t.is(statOfResult.atime.getTime(), newAccessTimeMs);
|
|
t.is(statOfResult.mode.toString(8), '100' + mode);
|
|
|
|
t.is(stat.uid, statOfResult.uid);
|
|
t.is(stat.gid, statOfResult.gid);
|
|
});
|
|
});
|