@zenfs/core 2.5.8 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/backends/cow.d.ts +13 -0
  2. package/dist/backends/cow.js +48 -11
  3. package/dist/backends/single_buffer.d.ts +2 -2
  4. package/dist/backends/store/fs.d.ts +6 -6
  5. package/dist/backends/store/fs.js +10 -2
  6. package/dist/internal/devices.d.ts +6 -6
  7. package/dist/internal/index_fs.d.ts +4 -4
  8. package/dist/node/async.js +1 -1
  9. package/dist/node/dir.d.ts +3 -1
  10. package/dist/node/dir.js +23 -5
  11. package/dist/node/promises.d.ts +3 -1
  12. package/dist/node/promises.js +128 -89
  13. package/dist/node/streams.d.ts +4 -0
  14. package/dist/node/streams.js +9 -10
  15. package/dist/node/sync.js +71 -40
  16. package/dist/utils.d.ts +8 -1
  17. package/dist/utils.js +26 -7
  18. package/dist/vfs/async.js +224 -85
  19. package/dist/vfs/file.d.ts +13 -7
  20. package/dist/vfs/file.js +661 -357
  21. package/dist/vfs/shared.d.ts +1 -0
  22. package/dist/vfs/shared.js +19 -3
  23. package/dist/vfs/sync.js +214 -85
  24. package/dist/vfs/vcache.d.ts +66 -0
  25. package/dist/vfs/vcache.js +249 -0
  26. package/dist/vfs/vnode.d.ts +66 -0
  27. package/dist/vfs/vnode.js +200 -0
  28. package/dist/vfs/watchers.js +4 -2
  29. package/dist/vfs/xattr.js +2 -2
  30. package/eslint.shared.js +1 -1
  31. package/package.json +3 -3
  32. package/scripts/make-index.js +7 -1
  33. package/scripts/test.js +28 -21
  34. package/tests/backend/port.test.ts +3 -3
  35. package/tests/backend/store.test.ts +49 -0
  36. package/tests/common.ts +62 -0
  37. package/tests/fetch/fetch.ts +6 -0
  38. package/tests/fetch/run.sh +2 -2
  39. package/tests/fetch/server.js +3 -3
  40. package/tests/fs/append.test.ts +5 -5
  41. package/tests/fs/concurrency.test.ts +52 -0
  42. package/tests/fs/dir.test.ts +13 -15
  43. package/tests/fs/directory.test.ts +32 -31
  44. package/tests/fs/errors.test.ts +151 -31
  45. package/tests/fs/exists.test.ts +4 -4
  46. package/tests/fs/links.test.ts +4 -11
  47. package/tests/fs/open.test.ts +4 -4
  48. package/tests/fs/permissions.test.ts +15 -15
  49. package/tests/fs/read.test.ts +9 -9
  50. package/tests/fs/readFile.test.ts +9 -9
  51. package/tests/fs/rename.test.ts +8 -8
  52. package/tests/fs/scaling.test.ts +4 -4
  53. package/tests/fs/stat.test.ts +20 -20
  54. package/tests/fs/streams.test.ts +9 -6
  55. package/tests/fs/tempdir.test.ts +33 -10
  56. package/tests/fs/times.test.ts +11 -11
  57. package/tests/fs/truncate.test.ts +6 -6
  58. package/tests/fs/watch.test.ts +107 -53
  59. package/tests/fs/write.test.ts +10 -10
  60. package/tests/fs/xattr.test.ts +3 -3
  61. package/tests/native/run.sh +3 -0
  62. package/tests/native/setup.ts +230 -0
  63. package/types/readable-stream.d.ts +2 -2
@@ -2,38 +2,38 @@
2
2
  import assert from 'node:assert/strict';
3
3
  import { join } from 'node:path/posix';
4
4
  import { suite, test } from 'node:test';
5
- import { fs } from '../common.js';
5
+ import { config, fs } from '../common.js';
6
6
 
7
7
  const testDir = 'test-dir';
8
8
  const testFiles = ['file1.txt', 'file2.txt', 'file3.txt'];
9
9
  const testDirectories = ['subdir1', 'subdir2'];
10
10
 
11
- fs.mkdirSync(testDir);
11
+ await fs.promises.mkdir(testDir);
12
12
  for (const file of testFiles) {
13
- fs.writeFileSync(`${testDir}/${file}`, 'Sample content');
13
+ await fs.promises.writeFile(`${testDir}/${file}`, 'Sample content');
14
14
  }
15
15
  for (const dir of testDirectories) {
16
- fs.mkdirSync(`${testDir}/${dir}`);
16
+ await fs.promises.mkdir(`${testDir}/${dir}`);
17
17
  for (const file of ['file4.txt', 'file5.txt']) {
18
- fs.writeFileSync(`${testDir}/${dir}/${file}`, 'Sample content');
18
+ await fs.promises.writeFile(`${testDir}/${dir}/${file}`, 'Sample content');
19
19
  }
20
20
  }
21
21
 
22
- suite('Directories', () => {
23
- test('mkdir', async () => {
22
+ suite('Directories', config('directories'), () => {
23
+ test('mkdir', config('async'), async () => {
24
24
  await fs.promises.mkdir('/one', 0o755);
25
25
  assert(await fs.promises.exists('/one'));
26
26
  await assert.rejects(fs.promises.mkdir('/one', 0o755), { code: 'EEXIST' });
27
27
  });
28
28
 
29
- test('mkdirSync', async () => await fs.promises.mkdir('/two', 0o000));
29
+ test('mkdirSync', config('sync'), () => fs.mkdirSync('/two', 0o000));
30
30
 
31
- test('mkdir, nested', async () => {
31
+ test('mkdir, nested', config('async'), async () => {
32
32
  await assert.rejects(fs.promises.mkdir('/nested/dir'), { code: 'ENOENT', path: '/nested/dir' });
33
33
  assert(!(await fs.promises.exists('/nested/dir')));
34
34
  });
35
35
 
36
- test('mkdir, recursive', async () => {
36
+ test('mkdir, recursive', config('async'), async () => {
37
37
  assert.equal(await fs.promises.mkdir('/recursiveP/A/B', { recursive: true, mode: 0o755 }), '/recursiveP');
38
38
  assert.equal(await fs.promises.mkdir('/recursiveP/A/B/C/D', { recursive: true, mode: 0o777 }), '/recursiveP/A/B/C');
39
39
  assert.equal(await fs.promises.mkdir('/recursiveP/A/B/C/D', { recursive: true, mode: 0o700 }), undefined);
@@ -45,7 +45,7 @@ suite('Directories', () => {
45
45
  assert.equal((await fs.promises.stat('/recursiveP/A/B/C/D')).mode, fs.constants.S_IFDIR | 0o777);
46
46
  });
47
47
 
48
- test('mkdirSync, recursive', () => {
48
+ test('mkdirSync, recursive', config('sync'), () => {
49
49
  assert.equal(fs.mkdirSync('/recursiveS/A/B', { recursive: true, mode: 0o755 }), '/recursiveS');
50
50
  assert.equal(fs.mkdirSync('/recursiveS/A/B/C/D', { recursive: true, mode: 0o777 }), '/recursiveS/A/B/C');
51
51
  assert.equal(fs.mkdirSync('/recursiveS/A/B/C/D', { recursive: true, mode: 0o700 }), undefined);
@@ -57,30 +57,30 @@ suite('Directories', () => {
57
57
  assert.equal(fs.statSync('/recursiveS/A/B/C/D').mode, fs.constants.S_IFDIR | 0o777);
58
58
  });
59
59
 
60
- test('rmdir (non-empty)', async () => {
60
+ test('rmdir (non-empty)', config('async'), async () => {
61
61
  await fs.promises.mkdir('/rmdirTest');
62
62
  await fs.promises.mkdir('/rmdirTest/rmdirTest2');
63
63
 
64
64
  await assert.rejects(fs.promises.rmdir('/rmdirTest'), { code: 'ENOTEMPTY' });
65
65
  });
66
66
 
67
- test('readdirSync on file', () => {
67
+ test('readdirSync on file', config('sync'), () => {
68
68
  assert.throws(() => fs.readdirSync('a.js'), { code: 'ENOTDIR' });
69
69
  });
70
70
 
71
- test('readdir on file', async () => {
71
+ test('readdir on file', config('async'), async () => {
72
72
  await assert.rejects(fs.promises.readdir('a.js'), { code: 'ENOTDIR' });
73
73
  });
74
74
 
75
- test('readdirSync on non-existent directory', () => {
75
+ test('readdirSync on non-existent directory', config('sync'), () => {
76
76
  assert.throws(() => fs.readdirSync('/does/not/exist'), { code: 'ENOENT' });
77
77
  });
78
78
 
79
- test('readdir on non-existent directory', async () => {
79
+ test('readdir on non-existent directory', config('async'), async () => {
80
80
  await assert.rejects(fs.promises.readdir('/does/not/exist'), { code: 'ENOENT' });
81
81
  });
82
82
 
83
- test('rm recursively', async () => {
83
+ test('rm recursively', config('async'), async () => {
84
84
  await fs.promises.mkdir('/rmDirRecursively');
85
85
  await fs.promises.mkdir('/rmDirRecursively/rmDirNested');
86
86
  await fs.promises.writeFile('/rmDirRecursively/rmDirNested/test.txt', 'hello world!');
@@ -88,7 +88,7 @@ suite('Directories', () => {
88
88
  await fs.promises.rm('/rmDirRecursively', { recursive: true });
89
89
  });
90
90
 
91
- test('rmSync recursively', () => {
91
+ test('rmSync recursively', config('sync'), () => {
92
92
  fs.mkdirSync('/rmDirRecursively');
93
93
  fs.mkdirSync('/rmDirRecursively/rmDirNested');
94
94
  fs.writeFileSync('/rmDirRecursively/rmDirNested/test.txt', 'hello world!');
@@ -96,7 +96,7 @@ suite('Directories', () => {
96
96
  fs.rmSync('/rmDirRecursively', { recursive: true });
97
97
  });
98
98
 
99
- test('readdir returns files and directories', async () => {
99
+ test('readdir returns files and directories', config('async'), async () => {
100
100
  const dirents = await fs.promises.readdir(testDir, { withFileTypes: true });
101
101
  const files = dirents.filter(dirent => dirent.isFile()).map(dirent => dirent.name);
102
102
  const dirs = dirents.filter(dirent => dirent.isDirectory()).map(dirent => dirent.name);
@@ -105,7 +105,7 @@ suite('Directories', () => {
105
105
  assert(testDirectories.every(dir => dirs.includes(dir)));
106
106
  });
107
107
 
108
- test('readdirSync returns files and directories', () => {
108
+ test('readdirSync returns files and directories', config('sync'), () => {
109
109
  const dirents = fs.readdirSync(testDir, { withFileTypes: true });
110
110
  const files = dirents.filter(dirent => dirent.isFile()).map(dirent => dirent.name);
111
111
  const dirs = dirents.filter(dirent => dirent.isDirectory()).map(dirent => dirent.name);
@@ -114,53 +114,54 @@ suite('Directories', () => {
114
114
  assert(testDirectories.every(dir => dirs.includes(dir)));
115
115
  });
116
116
 
117
- test('readdir returns Dirent objects', async () => {
117
+ test('readdir returns Dirent objects', config('async'), async () => {
118
118
  const dirents = await fs.promises.readdir(testDir, { withFileTypes: true });
119
119
  assert(dirents[0] instanceof fs.Dirent);
120
120
  });
121
121
 
122
- test('readdirSync returns Dirent objects', () => {
122
+ test('readdirSync returns Dirent objects', config('sync'), () => {
123
123
  const dirents = fs.readdirSync(testDir, { withFileTypes: true });
124
124
  assert(dirents[0] instanceof fs.Dirent);
125
125
  });
126
126
 
127
- test('readdir works without withFileTypes option', async () => {
127
+ test('readdir works without withFileTypes option', config('async'), async () => {
128
128
  const files = await fs.promises.readdir(testDir);
129
129
  assert(testFiles.every(entry => files.includes(entry)));
130
130
  assert(testDirectories.every(entry => files.includes(entry)));
131
131
  });
132
132
 
133
- test('readdirSync works without withFileTypes option', () => {
133
+ test('readdirSync works without withFileTypes option', config('sync'), () => {
134
134
  const files = fs.readdirSync(testDir);
135
135
  assert(testFiles.every(entry => files.includes(entry)));
136
136
  assert(testDirectories.every(entry => files.includes(entry)));
137
137
  });
138
138
 
139
- test('readdir returns files recursively', async () => {
139
+ test('readdir returns files recursively', config('async'), async () => {
140
140
  const entries = await fs.promises.readdir(testDir, { recursive: true });
141
141
  assert(entries.includes('file1.txt'));
142
142
  assert(entries.includes('subdir1/file4.txt'));
143
143
  assert(entries.includes('subdir2/file5.txt'));
144
144
  });
145
145
 
146
- test('readdir returns Dirent recursively', async () => {
146
+ test('readdir returns Dirent recursively', config('async'), async () => {
147
147
  const entries = await fs.promises.readdir(testDir, { recursive: true, withFileTypes: true });
148
148
  entries.sort((a, b) => join(a.parentPath, a.name).localeCompare(join(b.parentPath, b.name)));
149
149
  const values = entries.map(entry => [entry.parentPath, entry.name]);
150
150
 
151
- assert.deepEqual(values[0], ['.', 'file1.txt']);
152
- assert.deepEqual(values[4], ['subdir1', 'file4.txt']);
153
- assert.deepEqual(values[8], ['subdir2', 'file5.txt']);
151
+ // `parentPath` is resolved against the directory that was read
152
+ assert.deepEqual(values[0], ['/' + testDir, 'file1.txt']);
153
+ assert.deepEqual(values[4], ['/' + testDir + '/subdir1', 'file4.txt']);
154
+ assert.deepEqual(values[8], ['/' + testDir + '/subdir2', 'file5.txt']);
154
155
  });
155
156
 
156
- test('readdirSync returns files recursively', () => {
157
+ test('readdirSync returns files recursively', config('sync'), () => {
157
158
  const entries = fs.readdirSync(testDir, { recursive: true }).sort();
158
159
  assert.equal(entries[0], 'file1.txt');
159
160
  assert.equal(entries[4], 'subdir1/file4.txt');
160
161
  assert.equal(entries[8], 'subdir2/file5.txt');
161
162
  });
162
163
 
163
- test('Cyrillic file names', () => {
164
+ test('Cyrillic file names', config('sync'), () => {
164
165
  fs.writeFileSync('/мой-файл.txt', 'HELLO!', 'utf-8');
165
166
  assert(fs.readdirSync('/').includes('мой-файл.txt'));
166
167
  });
@@ -1,39 +1,159 @@
1
1
  // SPDX-License-Identifier: LGPL-3.0-or-later
2
2
  import assert from 'node:assert/strict';
3
3
  import test, { suite } from 'node:test';
4
- import { fs } from '../common.js';
4
+ import { config, fs } from '../common.js';
5
5
 
6
6
  const existingFile = '/exit.js';
7
+ const path = '/non-existent';
8
+
9
+ interface Case {
10
+ /** The name of the test */
11
+ name: string;
12
+ code: string;
13
+ /** The syscall the error should be attributed to, which is not always the name of the function */
14
+ syscall: string;
15
+ path: string;
16
+ dest?: string;
17
+ sync: (this: void) => unknown;
18
+ async: (this: void) => Promise<unknown>;
19
+ }
20
+
21
+ const cases: Case[] = [
22
+ {
23
+ name: 'stat',
24
+ code: 'ENOENT',
25
+ syscall: 'stat',
26
+ path,
27
+ sync: () => fs.statSync(path),
28
+ async: () => fs.promises.stat(path),
29
+ },
30
+ {
31
+ name: 'lstat',
32
+ code: 'ENOENT',
33
+ syscall: 'lstat',
34
+ path,
35
+ sync: () => fs.lstatSync(path),
36
+ async: () => fs.promises.lstat(path),
37
+ },
38
+ {
39
+ name: 'mkdir',
40
+ code: 'EEXIST',
41
+ syscall: 'mkdir',
42
+ path: existingFile,
43
+ sync: () => fs.mkdirSync(existingFile, 0o666),
44
+ async: () => fs.promises.mkdir(existingFile, 0o666),
45
+ },
46
+ {
47
+ name: 'rmdir (missing)',
48
+ code: 'ENOENT',
49
+ syscall: 'rmdir',
50
+ path,
51
+ sync: () => fs.rmdirSync(path),
52
+ async: () => fs.promises.rmdir(path),
53
+ },
54
+ {
55
+ name: 'rmdir (not a directory)',
56
+ code: 'ENOTDIR',
57
+ syscall: 'rmdir',
58
+ path: existingFile,
59
+ sync: () => fs.rmdirSync(existingFile),
60
+ async: () => fs.promises.rmdir(existingFile),
61
+ },
62
+ {
63
+ name: 'rename',
64
+ code: 'ENOENT',
65
+ syscall: 'rename',
66
+ path,
67
+ dest: '/foo',
68
+ sync: () => fs.renameSync(path, 'foo'),
69
+ async: () => fs.promises.rename(path, 'foo'),
70
+ },
71
+ {
72
+ name: 'open',
73
+ code: 'ENOENT',
74
+ syscall: 'open',
75
+ path,
76
+ sync: () => fs.openSync(path, 'r'),
77
+ async: () => fs.promises.open(path, 'r'),
78
+ },
79
+ {
80
+ // Note Node attributes `readdir` failures to `scandir`
81
+ name: 'readdir',
82
+ code: 'ENOENT',
83
+ syscall: 'scandir',
84
+ path,
85
+ sync: () => fs.readdirSync(path),
86
+ async: () => fs.promises.readdir(path),
87
+ },
88
+ {
89
+ name: 'unlink',
90
+ code: 'ENOENT',
91
+ syscall: 'unlink',
92
+ path,
93
+ sync: () => fs.unlinkSync(path),
94
+ async: () => fs.promises.unlink(path),
95
+ },
96
+ {
97
+ // Note `path` is the existing file and `dest` is the new link
98
+ name: 'link',
99
+ code: 'ENOENT',
100
+ syscall: 'link',
101
+ path,
102
+ dest: '/foo',
103
+ sync: () => fs.linkSync(path, 'foo'),
104
+ async: () => fs.promises.link(path, 'foo'),
105
+ },
106
+ {
107
+ name: 'chmod',
108
+ code: 'ENOENT',
109
+ syscall: 'chmod',
110
+ path,
111
+ sync: () => fs.chmodSync(path, 0o666),
112
+ async: () => fs.promises.chmod(path, 0o666),
113
+ },
114
+ {
115
+ name: 'readlink',
116
+ code: 'ENOENT',
117
+ syscall: 'readlink',
118
+ path,
119
+ sync: () => fs.readlinkSync(path),
120
+ async: () => fs.promises.readlink(path),
121
+ },
122
+ ];
123
+
124
+ /**
125
+ * Checks an error against the expected code, syscall, path, and message.
126
+ *
127
+ * The message is only matched at the start, since the description text for an errno
128
+ * is not standardized and implementations may add more information at the end.
129
+ */
130
+ function check(error: unknown, c: Case): void {
131
+ const e = error as NodeJS.ErrnoException & { dest?: string };
132
+
133
+ assert.equal(e.code, c.code);
134
+ assert.equal(e.syscall, c.syscall);
135
+ assert.equal(e.path, c.path);
136
+ if (c.dest) assert.equal(e.dest, c.dest);
137
+
138
+ // e.g. `ENOENT: no such file or directory, stat '/non-existent'`
139
+ const expected = new RegExp(`^${c.code}: .+, ${c.syscall} '${c.path}'` + (c.dest ? ` -> '${c.dest}'` : ''));
140
+ assert.match(e.message, expected);
141
+ }
7
142
 
8
143
  suite('Error messages', () => {
9
- const path = '/non-existent';
10
-
11
- const missing = { path, code: 'ENOENT' };
12
- const existing = { path: existingFile, code: 'EEXIST' };
13
- const notDir = { path: existingFile, code: 'ENOTDIR' };
14
-
15
- test('stat', async () => await assert.rejects(() => fs.promises.stat(path), missing));
16
- test('mkdir', async () => await assert.rejects(() => fs.promises.mkdir(existingFile, 0o666), existing));
17
- test('rmdir (missing)', async () => await assert.rejects(() => fs.promises.rmdir(path), missing));
18
- test('rmdir (existing)', async () => await assert.rejects(() => fs.promises.rmdir(existingFile), notDir));
19
- test('rename', async () => await assert.rejects(() => fs.promises.rename(path, 'foo'), missing));
20
- test('open', async () => await assert.rejects(() => fs.promises.open(path, 'r'), missing));
21
- test('readdir', async () => await assert.rejects(() => fs.promises.readdir(path), missing));
22
- test('unlink', async () => await assert.rejects(() => fs.promises.unlink(path), missing));
23
- test('link', async () => await assert.rejects(() => fs.promises.link(path, 'foo'), { code: 'ENOENT', path: '/foo' }));
24
- test('chmod', async () => await assert.rejects(() => fs.promises.chmod(path, 0o666), missing));
25
- test('lstat', async () => await assert.rejects(() => fs.promises.lstat(path), missing));
26
- test('readlink', async () => await assert.rejects(() => fs.promises.readlink(path), missing));
27
- test('statSync', () => assert.throws(() => fs.statSync(path), missing));
28
- test('mkdirSync', () => assert.throws(() => fs.mkdirSync(existingFile, 0o666), existing));
29
- test('rmdirSync', () => assert.throws(() => fs.rmdirSync(path), missing));
30
- test('rmdirSync', () => assert.throws(() => fs.rmdirSync(existingFile), notDir));
31
- test('renameSync', () => assert.throws(() => fs.renameSync(path, 'foo'), missing));
32
- test('openSync', () => assert.throws(() => fs.openSync(path, 'r'), missing));
33
- test('readdirSync', () => assert.throws(() => fs.readdirSync(path), missing));
34
- test('unlinkSync', () => assert.throws(() => fs.unlinkSync(path), missing));
35
- test('linkSync', () => assert.throws(() => fs.linkSync(path, 'foo'), { code: 'ENOENT', path: '/foo' }));
36
- test('chmodSync', () => assert.throws(() => fs.chmodSync(path, 0o666), missing));
37
- test('lstatSync', () => assert.throws(() => fs.lstatSync(path), missing));
38
- test('readlinkSync', () => assert.throws(() => fs.readlinkSync(path), missing));
144
+ for (const c of cases) {
145
+ test(c.name, config('async'), async () => {
146
+ await assert.rejects(c.async, (error: unknown) => {
147
+ check(error, c);
148
+ return true;
149
+ });
150
+ });
151
+
152
+ test(c.name + 'Sync', config('sync'), () => {
153
+ assert.throws(c.sync, (error: unknown) => {
154
+ check(error, c);
155
+ return true;
156
+ });
157
+ });
158
+ }
39
159
  });
@@ -1,22 +1,22 @@
1
1
  // SPDX-License-Identifier: LGPL-3.0-or-later
2
2
  import assert from 'node:assert/strict';
3
3
  import { suite, test } from 'node:test';
4
- import { fs } from '../common.js';
4
+ import { config, fs } from '../common.js';
5
5
 
6
6
  suite('exists', () => {
7
7
  const f = 'x.txt';
8
8
 
9
- test('return true for an existing file', async () => {
9
+ test('return true for an existing file', config('async'), async () => {
10
10
  const exists = await fs.promises.exists(f);
11
11
  assert(exists);
12
12
  });
13
13
 
14
- test('return false for a non-existent file', async () => {
14
+ test('return false for a non-existent file', config('async'), async () => {
15
15
  const exists = await fs.promises.exists(f + '-NO');
16
16
  assert(!exists);
17
17
  });
18
18
 
19
- test('have sync methods that behave the same', () => {
19
+ test('have sync methods that behave the same', config('sync'), () => {
20
20
  assert(fs.existsSync(f));
21
21
  assert(!fs.existsSync(f + '-NO'));
22
22
  });
@@ -1,11 +1,10 @@
1
1
  // SPDX-License-Identifier: LGPL-3.0-or-later
2
2
  import { join } from '@zenfs/core/path';
3
- import type { Exception } from 'kerium';
4
3
  import assert from 'node:assert/strict';
5
4
  import { suite, test } from 'node:test';
6
- import { fs } from '../common.js';
5
+ import { config, fs } from '../common.js';
7
6
 
8
- suite('Links', () => {
7
+ suite('Links', config('symlinks'), () => {
9
8
  const target = '/a1.js',
10
9
  symlink = 'symlink1.js',
11
10
  hardlink = 'link1.js';
@@ -69,14 +68,8 @@ suite('Links', () => {
69
68
  assert(await fs.promises.exists(target));
70
69
  });
71
70
 
72
- test('link', async t => {
73
- const _ = await fs.promises.link(target, hardlink).catch((e: Exception) => {
74
- if (e.code == 'ENOSYS') return e;
75
- throw e;
76
- });
77
- if (_) {
78
- return t.skip('Backend does not support hard links');
79
- }
71
+ test('link', config('links'), async () => {
72
+ await fs.promises.link(target, hardlink);
80
73
  const targetContent = await fs.promises.readFile(target, 'utf8');
81
74
  const linkContent = await fs.promises.readFile(hardlink, 'utf8');
82
75
  assert.equal(targetContent, linkContent);
@@ -1,22 +1,22 @@
1
1
  // SPDX-License-Identifier: LGPL-3.0-or-later
2
2
  import assert from 'node:assert/strict';
3
3
  import { suite, test } from 'node:test';
4
- import { fs } from '../common.js';
4
+ import { config, fs } from '../common.js';
5
5
 
6
6
  suite('Opening files', () => {
7
7
  const filename = 'a.js';
8
8
 
9
- test('throw ENOENT when opening non-existent file', async () => {
9
+ test('throw ENOENT when opening non-existent file', config('sync', 'async'), async () => {
10
10
  assert.throws(() => fs.openSync('/path/to/file/that/does/not/exist', 'r'), { code: 'ENOENT' });
11
11
  await assert.rejects(fs.promises.open('/path/to/file/that/does/not/exist', 'r'), { code: 'ENOENT' });
12
12
  });
13
13
 
14
- test('open file with mode "r"', async () => {
14
+ test('open file with mode "r"', config('async'), async () => {
15
15
  const { fd } = await fs.promises.open(filename, 'r');
16
16
  assert(fd >= -Infinity);
17
17
  });
18
18
 
19
- test('open file with mode "rs"', async () => {
19
+ test('open file with mode "rs"', config('async'), async () => {
20
20
  const { fd } = await fs.promises.open(filename, 'rs');
21
21
  assert(fd >= -Infinity);
22
22
  });
@@ -1,19 +1,20 @@
1
1
  // SPDX-License-Identifier: LGPL-3.0-or-later
2
- import { Exception } from 'kerium';
2
+ import { R_OK, W_OK, X_OK } from '@zenfs/core/constants';
3
+ import { defaultContext } from '@zenfs/core/internal/contexts.js';
4
+ import { hasAccess } from '@zenfs/core/internal/inode.js';
5
+ import { join } from '@zenfs/core/path';
6
+ import type { Exception } from 'kerium';
3
7
  import assert from 'node:assert/strict';
4
8
  import { suite, test } from 'node:test';
5
9
  import { encodeUTF8 } from 'utilium';
6
- import { defaultContext } from '@zenfs/core/internal/contexts.js';
7
- import { join } from '@zenfs/core/path';
8
- import { R_OK, W_OK, X_OK } from '@zenfs/core/constants';
9
- import { fs } from '../common.js';
10
+ import { config, fs } from '../common.js';
10
11
 
11
12
  const asyncMode = 0o777;
12
13
  const syncMode = 0o644;
13
14
  const file = 'a.js';
14
15
 
15
- suite('Permissions', () => {
16
- test('chmod', async () => {
16
+ suite('Permissions', config('permissions'), () => {
17
+ test('chmod', config('sync', 'async'), async () => {
17
18
  await fs.promises.chmod(file, asyncMode.toString(8));
18
19
 
19
20
  const stats = await fs.promises.stat(file);
@@ -23,7 +24,7 @@ suite('Permissions', () => {
23
24
  assert.equal(fs.statSync(file).mode & 0o777, syncMode);
24
25
  });
25
26
 
26
- test('fchmod', async () => {
27
+ test('fchmod', config('sync', 'async'), async () => {
27
28
  const handle = await fs.promises.open(file, 'a', 0o644);
28
29
 
29
30
  await handle.chmod(asyncMode);
@@ -35,7 +36,7 @@ suite('Permissions', () => {
35
36
  assert.equal(fs.statSync(file).mode & 0o777, syncMode);
36
37
  });
37
38
 
38
- test('lchmod', async () => {
39
+ test('lchmod', config('lchmod', 'async'), async () => {
39
40
  const link = 'symbolic-link';
40
41
 
41
42
  await fs.promises.symlink(file, link);
@@ -50,16 +51,15 @@ suite('Permissions', () => {
50
51
 
51
52
  async function test_item(path: string): Promise<void> {
52
53
  const stats = await fs.promises.stat(path).catch((error: Exception) => {
53
- assert(error instanceof Exception);
54
54
  assert.equal(error.code, 'EACCES');
55
55
  });
56
56
  if (!stats) return;
57
- assert(stats.hasAccess(X_OK));
57
+ assert(hasAccess(defaultContext, stats, X_OK));
58
58
 
59
59
  function checkError(access: number) {
60
60
  return function (error: Exception) {
61
- assert(error instanceof Exception);
62
- assert(!stats!.hasAccess(access));
61
+ assert(error instanceof Error);
62
+ assert(!hasAccess(defaultContext, stats!, access));
63
63
  };
64
64
  }
65
65
 
@@ -70,7 +70,7 @@ suite('Permissions', () => {
70
70
  } else {
71
71
  await fs.promises.readFile(path).catch(checkError(R_OK));
72
72
  }
73
- assert(stats.hasAccess(R_OK));
73
+ assert(hasAccess(defaultContext, stats, R_OK));
74
74
 
75
75
  if (stats.isDirectory()) {
76
76
  const testFile = join(path, '__test_file_plz_ignore.txt');
@@ -81,7 +81,7 @@ suite('Permissions', () => {
81
81
  if (!handle) return;
82
82
  await handle.close();
83
83
  }
84
- assert(stats.hasAccess(W_OK));
84
+ assert(hasAccess(defaultContext, stats, W_OK));
85
85
  }
86
86
 
87
87
  const copy = { ...defaultContext.credentials };
@@ -5,21 +5,21 @@ import assert from 'node:assert/strict';
5
5
  import type { OpenMode, PathLike } from 'node:fs';
6
6
  import { suite, test } from 'node:test';
7
7
  import { promisify } from 'node:util';
8
- import { fs } from '../common.js';
8
+ import { config, fs } from '../common.js';
9
9
 
10
10
  const filepath = 'x.txt';
11
11
  const expected = 'xyz\n';
12
12
  const ellipses = '…'.repeat(10_000);
13
13
 
14
14
  suite('read', () => {
15
- test('read file asynchronously', async () => {
15
+ test('read file asynchronously', config('async'), async () => {
16
16
  const handle = await fs.promises.open(filepath, 'r');
17
17
  const { bytesRead, buffer } = await handle.read(Buffer.alloc(expected.length), 0, expected.length, 0);
18
18
  assert.equal(bytesRead, expected.length);
19
19
  assert.equal(buffer.toString(), expected);
20
20
  });
21
21
 
22
- test('read file synchronously', () => {
22
+ test('read file synchronously', config('sync'), () => {
23
23
  const fd = fs.openSync(filepath, 'r');
24
24
  const buffer = Buffer.alloc(expected.length);
25
25
  const bytesRead = fs.readSync(fd, buffer, 0, expected.length, 0);
@@ -28,14 +28,14 @@ suite('read', () => {
28
28
  assert.equal(buffer.toString(), expected);
29
29
  });
30
30
 
31
- test('Read a file and check its binary bytes asynchronously', async () => {
31
+ test('Read a file and check its binary bytes asynchronously', config('async'), async () => {
32
32
  const buff = await fs.promises.readFile('elipses.txt');
33
33
  assert.equal(buff.length, 30_000);
34
34
  assert.equal(buff.toString(), ellipses);
35
35
  assert.equal((buff[1] << 8) | buff[0], 32994);
36
36
  });
37
37
 
38
- test('Read a file and check its binary bytes synchronously', () => {
38
+ test('Read a file and check its binary bytes synchronously', config('sync'), () => {
39
39
  const buff = fs.readFileSync('elipses.txt');
40
40
  assert.equal(buff.length, 30_000);
41
41
  assert.equal(buff.toString(), ellipses);
@@ -45,7 +45,7 @@ suite('read', () => {
45
45
  const bufferAsync = Buffer.alloc(expected.length);
46
46
  const bufferSync = Buffer.alloc(expected.length);
47
47
 
48
- test('read file from handle asynchronously', async () => {
48
+ test('read file from handle asynchronously', config('async'), async () => {
49
49
  const handle = await fs.promises.open(filepath, 'r');
50
50
  const { bytesRead } = await handle.read(bufferAsync, 0, expected.length, 0);
51
51
 
@@ -53,7 +53,7 @@ suite('read', () => {
53
53
  assert.equal(bufferAsync.toString(), expected);
54
54
  });
55
55
 
56
- test('read file from handle synchronously', () => {
56
+ test('read file from handle synchronously', config('sync'), () => {
57
57
  const fd = fs.openSync(filepath, 'r');
58
58
  const bytesRead = fs.readSync(fd, bufferSync, 0, expected.length, 0);
59
59
 
@@ -61,7 +61,7 @@ suite('read', () => {
61
61
  assert.equal(bytesRead, expected.length);
62
62
  });
63
63
 
64
- test('read file synchronously to non-zero offset', () => {
64
+ test('read file synchronously to non-zero offset', config('sync'), () => {
65
65
  const fd = fs.openSync(filepath, 'r');
66
66
  const buffer = Buffer.alloc(expected.length + 10);
67
67
  const bytesRead = fs.readSync(fd, buffer, 10, expected.length, 0);
@@ -70,7 +70,7 @@ suite('read', () => {
70
70
  assert.equal(bytesRead, expected.length);
71
71
  });
72
72
 
73
- test('read using callback API #239', async () => {
73
+ test('read using callback API #239', config('sync', 'write'), async () => {
74
74
  const path = '/text.txt';
75
75
 
76
76
  fs.writeFileSync(path, 'hello world');