Skip to content
File

Blob: src/workerd/api/node/tests/fs-link-test.js

javascript469 lines
1// Copyright (c) 2017-2022 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4import { deepStrictEqual, ok, rejects, strictEqual, throws } from 'node:assert';
5 
6import {
7 existsSync,
8 statSync,
9 linkSync,
10 lstatSync,
11 symlinkSync,
12 readlinkSync,
13 realpathSync,
14 unlinkSync,
15 link,
16 symlink,
17 readlink,
18 realpath,
19 unlink,
20 promises,
21} from 'node:fs';
22 
23strictEqual(typeof existsSync, 'function');
24strictEqual(typeof statSync, 'function');
25strictEqual(typeof linkSync, 'function');
26strictEqual(typeof lstatSync, 'function');
27strictEqual(typeof symlinkSync, 'function');
28strictEqual(typeof readlinkSync, 'function');
29strictEqual(typeof realpathSync, 'function');
30strictEqual(typeof unlinkSync, 'function');
31strictEqual(typeof link, 'function');
32strictEqual(typeof symlink, 'function');
33strictEqual(typeof readlink, 'function');
34strictEqual(typeof realpath, 'function');
35strictEqual(typeof unlink, 'function');
36strictEqual(typeof promises.link, 'function');
37strictEqual(typeof promises.symlink, 'function');
38strictEqual(typeof promises.readlink, 'function');
39strictEqual(typeof promises.realpath, 'function');
40strictEqual(typeof promises.unlink, 'function');
41 
42const src = '/dev/null';
43const dst = '/tmp/test-link';
44 
45const kInvalidArgTypeError = { code: 'ERR_INVALID_ARG_TYPE' };
46const kEInvalError = { code: 'EINVAL' };
47const kENoentError = { code: 'ENOENT' };
48const kEExistError = { code: 'EEXIST' };
49const kEPermError = { code: 'EPERM' };
50const kEIsDir = { code: 'EISDIR' };
51 
52export const linkAndUnlinkSyncTest = {
53 test() {
54 throws(() => linkSync(), kInvalidArgTypeError);
55 throws(() => linkSync(''), kInvalidArgTypeError);
56 throws(() => realpathSync(), kInvalidArgTypeError);
57 throws(() => readlinkSync(), kInvalidArgTypeError);
58 throws(() => unlinkSync(), kInvalidArgTypeError);
59 
60 ok(existsSync(src));
61 ok(!existsSync(dst));
62 linkSync(src, dst);
63 ok(existsSync(dst));
64 
65 // These are the same file.
66 deepStrictEqual(statSync(dst), statSync(src));
67 deepStrictEqual(lstatSync(dst), statSync(src));
68 
69 // Because this is a hard link, the realpath is /tmp/a
70 strictEqual(realpathSync(dst), dst);
71 
72 // And readlinkSync throws
73 throws(() => readlinkSync(dst), kEInvalError);
74 
75 // Linking from a non-existent file throws.
76 throws(() => linkSync('/does/not/exist', '/tmp/abc'), kENoentError);
77 
78 // Make sure the destination does not exist after the failed link.
79 ok(!existsSync('/tmp/abc'));
80 throws(() => linkSync('/dev/foo', '/tmp/abc'), kENoentError);
81 ok(!existsSync('/tmp/abc'));
82 
83 // Creating a link in a non-existent directory throws.
84 throws(() => linkSync('/dev/null', '/tmp/abc/xyz'), kENoentError);
85 
86 // Linking when the path already exists throws.
87 throws(() => linkSync(src, dst), kEExistError);
88 
89 // Linking with an empty file name throws.
90 throws(() => linkSync(src, '/foo/'), kEInvalError);
91 
92 // Invalid paths throw
93 throws(() => linkSync(src, '/dev/null/test'), kEInvalError);
94 throws(() => linkSync('/dev/null/test', '/tmp/null/test'), kENoentError);
95 
96 // Attempting to link directories throws
97 throws(() => linkSync('/tmp', '/tmp/abc'), kEPermError);
98 
99 // The destination can be unlinked
100 unlinkSync(dst);
101 ok(!existsSync(dst));
102 
103 // But unlinking does not remove the source.
104 ok(existsSync(src));
105 
106 // Cannot unlink read-only files
107 throws(() => unlinkSync('/bundle/worker'), kEPermError);
108 throws(() => unlinkSync('/bundle'), kEIsDir);
109 
110 // Make sure that the failed unlink had no effect.
111 ok(existsSync('/bundle/worker'));
112 ok(existsSync('/bundle'));
113 },
114};
115 
116export const linkAndUnlinkAsyncCallbackTest = {
117 async test() {
118 const doLinkTest = async (src, dst) => {
119 const { promise, resolve, reject } = Promise.withResolvers();
120 link(src, dst, (err) => {
121 try {
122 if (err) return reject(err);
123 ok(existsSync(dst));
124 deepStrictEqual(statSync(dst), statSync(src));
125 deepStrictEqual(lstatSync(dst), statSync(src));
126 strictEqual(realpathSync(dst), dst);
127 throws(() => readlinkSync(dst), kEInvalError);
128 } catch (err) {
129 reject(err);
130 }
131 resolve();
132 });
133 await promise;
134 };
135 
136 const doFailedLinkTest = async (src, dst, expectedError) => {
137 const { promise, resolve, reject } = Promise.withResolvers();
138 link(src, dst, (err) => {
139 try {
140 strictEqual(err.code, expectedError.code);
141 ok(!existsSync(dst));
142 } catch (err) {
143 reject(err);
144 }
145 resolve();
146 });
147 await promise;
148 };
149 
150 const doUnlinkTest = async (dst) => {
151 const { promise, resolve, reject } = Promise.withResolvers();
152 unlink(dst, (err) => {
153 try {
154 if (err) return reject(err);
155 ok(!existsSync(dst));
156 ok(existsSync(src));
157 } catch (err) {
158 reject(err);
159 }
160 resolve();
161 });
162 await promise;
163 };
164 
165 const doFailedUnlinkTest = async (dst, expectedError) => {
166 const { promise, resolve, reject } = Promise.withResolvers();
167 unlink(dst, (err) => {
168 try {
169 strictEqual(err.code, expectedError.code);
170 ok(existsSync(dst));
171 } catch (err) {
172 reject(err);
173 }
174 resolve();
175 });
176 await promise;
177 };
178 
179 throws(() => link(), kInvalidArgTypeError);
180 throws(() => link(''), kInvalidArgTypeError);
181 throws(() => link('', ''), kInvalidArgTypeError);
182 throws(() => realpath(), kInvalidArgTypeError);
183 throws(() => realpath(''), kInvalidArgTypeError);
184 throws(() => readlink(), kInvalidArgTypeError);
185 throws(() => readlink(''), kInvalidArgTypeError);
186 throws(() => unlink(), kInvalidArgTypeError);
187 throws(() => unlink(''), kInvalidArgTypeError);
188 
189 ok(existsSync(src));
190 ok(!existsSync(dst));
191 await doLinkTest(src, dst);
192 
193 await doFailedLinkTest('/does/not/exist', '/tmp/abc', kENoentError);
194 ok(!existsSync('/tmp/abc'));
195 
196 await doFailedLinkTest('/dev/foo', '/tmp/abc', kENoentError);
197 ok(!existsSync('/tmp/abc'));
198 
199 // Creating a link in a non-existent directory throws.
200 await doFailedLinkTest('/dev/null', '/tmp/abc/xyz', kENoentError);
201 
202 // Invalid paths throw
203 await doFailedLinkTest(src, '/dev/null/test', kEInvalError);
204 await doFailedLinkTest('/dev/null/test', '/tmp/null/test', kENoentError);
205 
206 // Attempting to link directories throws
207 await doFailedLinkTest('/tmp', '/tmp/abc', kEPermError);
208 
209 await doUnlinkTest(dst);
210 
211 await doFailedUnlinkTest('/bundle/worker', kEPermError);
212 await doFailedUnlinkTest('/bundle', kEIsDir);
213 },
214};
215 
216export const linkAndLinkPromisesTest = {
217 async test() {
218 await rejects(promises.link(), kInvalidArgTypeError);
219 await rejects(promises.link(''), kInvalidArgTypeError);
220 await rejects(promises.realpath(), kInvalidArgTypeError);
221 await rejects(promises.readlink(), kInvalidArgTypeError);
222 await rejects(promises.unlink(), kInvalidArgTypeError);
223 
224 ok(existsSync(src));
225 ok(!existsSync(dst));
226 await promises.link(src, dst);
227 
228 // These are the same file.
229 deepStrictEqual(statSync(dst), statSync(src));
230 deepStrictEqual(lstatSync(dst), statSync(src));
231 
232 // Because this is a hard link, the realpath is /tmp/a
233 strictEqual(await promises.realpath(dst), dst);
234 
235 // And readlinkSync throws
236 await rejects(promises.readlink(dst), kEInvalError);
237 
238 await rejects(promises.link('/does/not/exist', '/tmp/abc'), kENoentError);
239 ok(!existsSync('/tmp/abc'));
240 
241 await rejects(promises.link('/dev/null', '/tmp/abc/xyz'), kENoentError);
242 
243 await rejects(promises.link(src, dst), kEExistError);
244 
245 await rejects(promises.link(src, '/foo/'), kEInvalError);
246 
247 await rejects(promises.link(src, '/dev/null/test'), kEInvalError);
248 
249 await rejects(
250 promises.link('/dev/null/test', '/tmp/null/test'),
251 kENoentError
252 );
253 
254 await rejects(promises.link('/tmp', '/tmp/abc'), kEPermError);
255 
256 await promises.unlink(dst);
257 ok(!existsSync(dst));
258 // But unlinking does not remove the source.
259 ok(existsSync(src));
260 
261 await rejects(promises.unlink('/bundle/worker'), kEPermError);
262 await rejects(promises.unlink('/bundle'), kEIsDir);
263 },
264};
265 
266export const symlinkAndUnlinkSyncTest = {
267 test() {
268 throws(() => symlinkSync(), kInvalidArgTypeError);
269 throws(() => symlinkSync(''), kInvalidArgTypeError);
270 
271 ok(existsSync(src));
272 ok(!existsSync(dst));
273 symlinkSync(src, dst);
274 ok(existsSync(dst));
275 
276 deepStrictEqual(statSync(dst), statSync(src));
277 
278 // The symlink points to the source.
279 strictEqual(readlinkSync(dst), src);
280 
281 // The realpath is the destination
282 strictEqual(realpathSync(dst), src);
283 
284 // And readlinkSync does not throw
285 strictEqual(readlinkSync(dst), src);
286 
287 // Linking from a non-existent file works
288 symlinkSync('/does/not/exist', '/tmp/abc');
289 
290 // Exists sync follows the symlink, and show return false.
291 ok(!existsSync('/tmp/abc'));
292 
293 // But the symlink does exist.
294 const linkStat = lstatSync('/tmp/abc');
295 ok(linkStat.isSymbolicLink());
296 
297 // Creating a link in a non-existent directory throws.
298 throws(() => symlinkSync('/dev/null', '/tmp/abc/xyz'), kENoentError);
299 
300 // Linking when the path already exists throws.
301 throws(() => symlinkSync(src, dst), kEExistError);
302 
303 // Linking with an empty file name throws.
304 throws(() => symlinkSync(src, '/foo/'), kEInvalError);
305 
306 // Invalid paths throw
307 throws(() => symlinkSync(src, '/dev/null/test'), kEInvalError);
308 throws(() => symlinkSync('/dev/null/test', '/tmp/null/test'), kENoentError);
309 
310 // Symlinking directories works.
311 symlinkSync('/tmp', '/tmp/linked-dir');
312 ok(existsSync('/tmp/linked-dir'));
313 
314 // The destination can be unlinked
315 unlinkSync(dst);
316 ok(!existsSync(dst));
317 
318 // But unlinking does not remove the source.
319 ok(existsSync(src));
320 },
321};
322 
323export const symlinkAndUnlinkAsyncCallbackTest = {
324 async test() {
325 throws(() => symlink(), kInvalidArgTypeError);
326 throws(() => symlink(''), kInvalidArgTypeError);
327 throws(() => symlink('', ''), kInvalidArgTypeError);
328 throws(() => realpath(), kInvalidArgTypeError);
329 throws(() => realpath(''), kInvalidArgTypeError);
330 throws(() => readlink(), kInvalidArgTypeError);
331 throws(() => readlink(''), kInvalidArgTypeError);
332 throws(() => unlink(), kInvalidArgTypeError);
333 throws(() => unlink(''), kInvalidArgTypeError);
334 
335 const doSymlinkTest = async (src, dst) => {
336 const { promise, resolve, reject } = Promise.withResolvers();
337 symlink(src, dst, (err) => {
338 if (err) return reject(err);
339 try {
340 const srcStat = statSync(src, { throwIfNoEntry: false });
341 if (srcStat != null) {
342 const dstStat = statSync(dst, { throwIfNoEntry: false });
343 deepStrictEqual(dstStat, srcStat);
344 } else {
345 // The source does not exist but the symlink should still.
346 const dstStat = lstatSync(dst);
347 ok(dstStat.isSymbolicLink());
348 }
349 strictEqual(readlinkSync(dst), src);
350 strictEqual(realpathSync(dst), src);
351 } catch (err) {
352 reject(err);
353 }
354 resolve();
355 });
356 await promise;
357 };
358 
359 const doFailedSymlinkTest = async (src, dst, expectedError) => {
360 const { promise, resolve, reject } = Promise.withResolvers();
361 symlink(src, dst, (err) => {
362 try {
363 strictEqual(err.code, expectedError.code);
364 } catch (err) {
365 reject(err);
366 }
367 resolve();
368 });
369 await promise;
370 };
371 
372 const doUnlinkTest = async (dst) => {
373 const { promise, resolve, reject } = Promise.withResolvers();
374 unlink(dst, (err) => {
375 try {
376 if (err) return reject(err);
377 ok(!existsSync(dst));
378 ok(existsSync(src));
379 } catch (err) {
380 reject(err);
381 }
382 resolve();
383 });
384 await promise;
385 };
386 
387 ok(existsSync(src));
388 ok(!existsSync(dst));
389 await doSymlinkTest(src, dst);
390 await doSymlinkTest('/does/not/exist', '/tmp/abc');
391 
392 // Exists sync follows the symlink, and show return false.
393 ok(!existsSync('/tmp/abc'));
394 
395 await doFailedSymlinkTest(src, dst, kEExistError);
396 await doFailedSymlinkTest(src, '/foo/', kEInvalError);
397 await doFailedSymlinkTest(src, '/dev/null/test', kEInvalError);
398 await doFailedSymlinkTest('/dev/null/test', '/tmp/null/test', kENoentError);
399 
400 // Symlinking directories works.
401 await doSymlinkTest('/tmp', '/tmp/linked-dir');
402 
403 // The destination can be unlinked
404 await doUnlinkTest(dst);
405 },
406};
407 
408export const symlinkAndUnlinkPromisesTest = {
409 async test() {
410 await rejects(promises.symlink(), kInvalidArgTypeError);
411 await rejects(promises.symlink(''), kInvalidArgTypeError);
412 await rejects(promises.realpath(), kInvalidArgTypeError);
413 await rejects(promises.readlink(), kInvalidArgTypeError);
414 await rejects(promises.unlink(), kInvalidArgTypeError);
415 
416 ok(existsSync(src));
417 ok(!existsSync(dst));
418 await promises.symlink(src, dst);
419 
420 deepStrictEqual(statSync(dst), statSync(src));
421 
422 // The symlink points to the source.
423 strictEqual(await promises.readlink(dst), src);
424 
425 // The realpath is the destination
426 strictEqual(await promises.realpath(dst), src);
427 
428 // And readlink does not throw
429 strictEqual(await promises.readlink(dst), src);
430 
431 // Linking from a non-existent file works
432 await promises.symlink('/does/not/exist', '/tmp/abc');
433 
434 // Exists sync follows the symlink, and show return false.
435 ok(!existsSync('/tmp/abc'));
436 
437 // But the symlink does exist.
438 const linkStat = lstatSync('/tmp/abc');
439 ok(linkStat.isSymbolicLink());
440 
441 // Creating a link in a non-existent directory throws.
442 await rejects(promises.symlink('/dev/null', '/tmp/abc/xyz'), kENoentError);
443 
444 // Linking when the path already exists throws.
445 await rejects(promises.symlink(src, dst), kEExistError);
446 
447 // Linking with an empty file name throws.
448 await rejects(promises.symlink(src, '/foo/'), kEInvalError);
449 
450 // Invalid paths throw
451 await rejects(promises.symlink(src, '/dev/null/test'), kEInvalError);
452 await rejects(
453 promises.symlink('/dev/null/test', '/tmp/null/test'),
454 kENoentError
455 );
456 
457 // Symlinking directories works.
458 await promises.symlink('/tmp', '/tmp/linked-dir');
459 ok(existsSync('/tmp/linked-dir'));
460 
461 // The destination can be unlinked
462 await promises.unlink(dst);
463 ok(!existsSync(dst));
464 
465 // But unlinking does not remove the source.
466 ok(existsSync(src));
467 },
468};