Skip to content

Commit 1b95ca5

Browse files
committed
test(cp): 100% coverage
1 parent 56889ca commit 1b95ca5

File tree

6 files changed

+152
-7
lines changed

6 files changed

+152
-7
lines changed

lib/cp/index.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const polyfill = require('./polyfill.js')
66
// node 16.7.0 added fs.cp
77
const useNative = node.satisfies('>=16.7.0')
88

9-
const rm = async (path, opts) => {
9+
const cp = async (src, dest, opts) => {
1010
const options = getOptions(opts, {
1111
copy: ['dereference', 'errorOnExist', 'filter', 'force', 'preserveTimestamps', 'recursive'],
1212
})
@@ -15,8 +15,8 @@ const rm = async (path, opts) => {
1515
// process.version to try to trigger it just for coverage
1616
// istanbul ignore next
1717
return useNative
18-
? fs.rm(path, options)
19-
: polyfill(path, options)
18+
? fs.cp(src, dest, options)
19+
: polyfill(src, dest, options)
2020
}
2121

22-
module.exports = rm
22+
module.exports = cp

lib/cp/polyfill.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ function getValidatedPath(fileURLOrPath) {
8585

8686
async function cpFn(src, dest, opts) {
8787
// Warn about using preserveTimestamps on 32-bit node
88+
// istanbul ignore next
8889
if (opts.preserveTimestamps && process.arch === 'ia32') {
8990
const warning = 'Using the preserveTimestamps option in 32-bit ' +
9091
'node is not recommended';
@@ -170,6 +171,7 @@ async function checkParentDir(destStat, src, dest, opts) {
170171
function pathExists(dest) {
171172
return stat(dest).then(
172173
() => true,
174+
// istanbul ignore next: not sure when this would occur
173175
(err) => (err.code === 'ENOENT' ? false : Promise.reject(err)));
174176
}
175177

@@ -187,7 +189,9 @@ async function checkParentPaths(src, srcStat, dest) {
187189
try {
188190
destStat = await stat(destParent, { bigint: true });
189191
} catch (err) {
192+
// istanbul ignore else: not sure when this would occur
190193
if (err.code === 'ENOENT') return;
194+
// istanbul ignore next: not sure when this would occur
191195
throw err;
192196
}
193197
if (areIdentical(srcStat, destStat)) {
@@ -227,6 +231,7 @@ function startCopy(destStat, src, dest, opts) {
227231
async function getStatsForCopy(destStat, src, dest, opts) {
228232
const statFn = opts.dereference ? stat : lstat;
229233
const srcStat = await statFn(src);
234+
// istanbul ignore else: can't portably test FIFO
230235
if (srcStat.isDirectory() && opts.recursive) {
231236
return onDir(srcStat, destStat, src, dest, opts);
232237
} else if (srcStat.isDirectory()) {
@@ -249,14 +254,15 @@ async function getStatsForCopy(destStat, src, dest, opts) {
249254
syscall: 'cp',
250255
errno: EINVAL,
251256
});
252-
} else if (srcStat.isFIFO()) {
257+
} else if (srcStat.isFIFO()) {
253258
throw new ERR_FS_CP_FIFO_PIPE({
254259
message: `cannot copy a FIFO pipe: ${dest}`,
255260
path: dest,
256261
syscall: 'cp',
257262
errno: EINVAL,
258263
});
259264
}
265+
// istanbul ignore next: should be unreachable
260266
throw new ERR_FS_CP_UNKNOWN({
261267
message: `cannot copy an unknown file type: ${dest}`,
262268
path: dest,
@@ -365,9 +371,11 @@ async function onLink(destStat, src, dest) {
365371
// Dest exists and is a regular file or directory,
366372
// Windows may throw UNKNOWN error. If dest already exists,
367373
// fs throws error anyway, so no need to guard against it here.
374+
// istanbul ignore next: can only test on windows
368375
if (err.code === 'EINVAL' || err.code === 'UNKNOWN') {
369376
return symlink(resolvedSrc, dest);
370377
}
378+
// istanbul ignore next: should not be possible
371379
throw err;
372380
}
373381
if (!isAbsolute(resolvedDest)) {

lib/errors.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
'use strict'
2+
const { inspect } = require('util')
23

34
// adapted from node's internal/errors
45
// https://github.com/nodejs/node/blob/c8a04049be96d2a6d625d4417df095fc0f3eaa7b/lib/internal/errors.js
@@ -80,7 +81,7 @@ class SystemError {
8081
}
8182

8283
[Symbol.for('nodejs.util.inspect.custom')](_recurseTimes, ctx) {
83-
return lazyInternalUtilInspect().inspect(this, {
84+
return inspect(this, {
8485
...ctx,
8586
getters: true,
8687
customInspect: false

test/cp/index.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
const { join } = require('path')
2+
const t = require('tap')
3+
4+
const fs = require('../../')
5+
6+
t.test('can copy a file', async (t) => {
7+
const dir = t.testdir({
8+
file: 'some random file',
9+
})
10+
const src = join(dir, 'file')
11+
const dest = join(dir, 'dest')
12+
13+
await fs.cp(src, dest)
14+
15+
t.equal(await fs.exists(dest), true, 'dest exits')
16+
})
17+
18+
t.test('can copy a directory', async (t) => {
19+
const dir = t.testdir({
20+
directory: {},
21+
})
22+
const src = join(dir, 'directory')
23+
const dest = join(dir, 'dest')
24+
25+
await fs.cp(src, dest, { recursive: true })
26+
27+
t.equal(await fs.exists(dest), true, 'dest exists')
28+
})
29+

test/cp/polyfill.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,23 @@ t.test('It copies file itself, rather than symlink, when dereference is true.',
111111
t.ok(stat.isFile());
112112
})
113113

114+
t.test('It copies relative symlinks', async t => {
115+
const src = nextdir();
116+
mkdirSync(src, { recursive: true });
117+
writeFileSync(join(src, 'foo.js'), 'foo', 'utf8');
118+
symlinkSync('./foo.js', join(src, 'bar.js'));
119+
120+
const dest = nextdir();
121+
const destFile = join(dest, 'bar.js');
122+
mkdirSync(dest, { recursive: true });
123+
writeFileSync(join(dest, 'foo.js'), 'foo', 'utf8');
124+
symlinkSync('./foo.js', destFile);
125+
126+
await cp(src, dest, {recursive: true})
127+
const stat = lstatSync(destFile);
128+
t.ok(stat.isSymbolicLink());
129+
})
130+
114131
t.test('It returns error when src and dest are identical.', async t => {
115132
t.rejects(
116133
cp(kitchenSink, kitchenSink),
@@ -327,10 +344,19 @@ t.test('It accepts file URL as src and dest.', async t => {
327344

328345
t.test('It throws if options is not object.', async t => {
329346
t.rejects(
330-
() => cp('a', 'b', 'hello', () => {}),
347+
() => cp('a', 'b', 'hello'),
331348
{ code: 'ERR_INVALID_ARG_TYPE' });
332349
})
333350

351+
t.test('It throws ENAMETOOLONG when name is too long', async t => {
352+
const src = nextdir();
353+
mkdirSync(src, { recursive: true });
354+
const dest = join(tmpdir, 'a'.repeat(10_000));
355+
t.rejects(
356+
cp(src, dest),
357+
{ code: 'ENAMETOOLONG' });
358+
})
359+
334360
function assertDirEquivalent(t, dir1, dir2) {
335361
const dir1Entries = [];
336362
collectEntries(dir1, dir1Entries);

test/errors.js

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
const t = require('tap')
2+
const { ERR_FS_EISDIR } = require('../lib/errors')
3+
const { constants: { errno: { EISDIR, EIO } } } = require('os')
4+
const { inspect } = require('util')
5+
6+
t.test('message with path and dest', async t => {
7+
const err = new ERR_FS_EISDIR({
8+
path: 'path',
9+
dest: 'dest',
10+
syscall: 'cp',
11+
code: EISDIR,
12+
message: 'failed',
13+
})
14+
15+
t.equal(err.message, `Path is a directory: cp returned ${EISDIR} (failed) path => dest`)
16+
})
17+
18+
t.test('message without path or dest', async t => {
19+
const err = new ERR_FS_EISDIR({
20+
syscall: 'cp',
21+
code: EISDIR,
22+
message: 'failed',
23+
})
24+
25+
t.equal(err.message, `Path is a directory: cp returned ${EISDIR} (failed)`)
26+
})
27+
28+
t.test('errno is alias for info.errno', async t => {
29+
const err = new ERR_FS_EISDIR({ errno: EISDIR })
30+
t.equal(err.errno, EISDIR)
31+
t.equal(err.info.errno, EISDIR)
32+
err.errno = EIO
33+
t.equal(err.errno, EIO)
34+
t.equal(err.info.errno, EIO)
35+
})
36+
37+
t.test('syscall is alias for info.syscall', async t => {
38+
const err = new ERR_FS_EISDIR({ syscall: 'cp' })
39+
t.equal(err.syscall, 'cp')
40+
t.equal(err.info.syscall, 'cp')
41+
err.syscall = 'readlink'
42+
t.equal(err.syscall, 'readlink')
43+
t.equal(err.info.syscall, 'readlink')
44+
})
45+
46+
t.test('path is alias for info.path', async t => {
47+
const err = new ERR_FS_EISDIR({ path: 'first' })
48+
t.equal(err.path, 'first')
49+
t.equal(err.info.path, 'first')
50+
err.path = 'second'
51+
t.equal(err.path, 'second')
52+
t.equal(err.info.path, 'second')
53+
})
54+
55+
t.test('dest is alias for info.dest', async t => {
56+
const err = new ERR_FS_EISDIR({ dest: 'first' })
57+
t.equal(err.dest, 'first')
58+
t.equal(err.info.dest, 'first')
59+
err.dest = 'second'
60+
t.equal(err.dest, 'second')
61+
t.equal(err.info.dest, 'second')
62+
})
63+
64+
t.test('toString', async t => {
65+
const err = new ERR_FS_EISDIR({
66+
syscall: 'cp',
67+
code: EISDIR,
68+
message: 'failed',
69+
})
70+
t.equal(err.toString(),
71+
`SystemError [ERR_FS_EISDIR]: Path is a directory: cp returned ${EISDIR} (failed)`)
72+
})
73+
74+
t.test('inspect', async t => {
75+
const err = new ERR_FS_EISDIR({
76+
syscall: 'cp',
77+
errno: EISDIR,
78+
message: 'failed',
79+
})
80+
t.ok(inspect(err))
81+
})

0 commit comments

Comments
 (0)