@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
package/scripts/test.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  // NOTE: Not compiled, use erasable TS only
3
- import { execSync } from 'node:child_process';
3
+ import { execFileSync } from 'node:child_process';
4
4
  import { existsSync, globSync, mkdirSync, rmSync } from 'node:fs';
5
5
  import { join, parse, basename } from 'node:path';
6
6
  import { parseArgs, styleText } from 'node:util';
@@ -25,7 +25,7 @@ const { values: options, positionals } = parseArgs({
25
25
  inspect: { short: 'I', type: 'boolean', default: false },
26
26
  skip: { short: 's', type: 'string', multiple: true, default: [] },
27
27
  'exit-on-fail': { short: 'e', type: 'boolean' },
28
- runs: { short: 'r', type: 'string' },
28
+ runs: { short: 'r', type: 'string', default: '1' },
29
29
 
30
30
  // Coverage and performance
31
31
  coverage: { type: 'string', default: 'tests/.coverage' },
@@ -34,9 +34,13 @@ const { values: options, positionals } = parseArgs({
34
34
  clean: { type: 'boolean', default: false },
35
35
  profile: { type: 'boolean', default: false },
36
36
  },
37
+ strict: true,
37
38
  allowPositionals: true,
38
39
  });
39
40
 
41
+ /**
42
+ * @param {...any} args
43
+ */
40
44
  function debug(...args) {
41
45
  if (options.debug) console.debug(styleText('dim', '[debug]'), ...args.map(a => (typeof a === 'string' ? styleText('dim', a) : a)));
42
46
  }
@@ -92,7 +96,7 @@ if (options.clean) {
92
96
 
93
97
  function report() {
94
98
  try {
95
- execSync('npx c8 report --reporter=text', { stdio: 'inherit' });
99
+ execFileSync('npx', ['--silent', 'c8', 'report', '--reporter=text'], { stdio: 'inherit' });
96
100
  } catch (e) {
97
101
  console.error('Failed to generate coverage report!');
98
102
  console.error(e);
@@ -106,9 +110,12 @@ if (options.report) {
106
110
  process.exit();
107
111
  }
108
112
 
113
+ const nRuns = Number.isSafeInteger(parseInt(options.runs)) ? parseInt(options.runs) : 1;
114
+
115
+ /** @type {typeof import('./ci.js')} */
109
116
  let ci;
110
117
  if (options.ci) {
111
- if (options.runs) {
118
+ if (options.runs && nRuns !== 1) {
112
119
  console.error('Cannot use --ci with --runs');
113
120
  process.exit(1);
114
121
  }
@@ -120,7 +127,7 @@ options.verbose && options.force && console.debug('Forcing tests to exit (--test
120
127
  if (options.build) {
121
128
  !options.quiet && process.stdout.write('Building... ');
122
129
  try {
123
- execSync('npm run build');
130
+ execFileSync('npm', ['run', 'build', '--silent'], { stdio: 'inherit' });
124
131
  console.log('done.');
125
132
  } catch {
126
133
  console.warn('failed, continuing without it.');
@@ -151,22 +158,24 @@ mkdirSync(options.coverage, { recursive: true });
151
158
  * Generate the command used to run the tests
152
159
  * @param {string} profileName
153
160
  * @param {...string} rest
154
- * @returns {string}
161
+ * @returns {string[]}
155
162
  */
156
163
  function makeCommand(profileName, ...rest) {
157
164
  const command = [
158
- 'tsx --trace-deprecation',
159
- options.inspect ? '--inspect' : '',
160
- '--test --experimental-test-coverage',
161
- options.force ? '--test-force-exit' : '',
162
- options.skip.length ? `--test-skip-pattern='${options.skip.join('|').replaceAll("'", "\\'")}'` : '',
163
- !options.profile ? '' : `--cpu-prof --cpu-prof-dir=.profiles --cpu-prof-name=${profileName}.cpuprofile --cpu-prof-interval=500`,
165
+ '--trace-deprecation',
166
+ options.inspect && '--inspect',
167
+ '--test',
168
+ '--experimental-test-coverage',
169
+ options.force && '--test-force-exit',
170
+ options.skip.length && `--test-skip-pattern=${options.skip.join('|')}`,
171
+ ...(!options.profile
172
+ ? []
173
+ : ['--cpu-prof', '--cpu-prof-dir=.profiles', `--cpu-prof-name=${profileName}.cpuprofile`, '--cpu-prof-interval=500']),
164
174
  ...rest,
165
175
  ]
166
176
  .filter(v => v)
167
- .join(' ');
168
-
169
- if (!options.quiet) debug('command:', command);
177
+ .filter(v => typeof v == 'string');
178
+ if (!options.quiet) debug('command: tsx', command.join(' '));
170
179
 
171
180
  return command;
172
181
  }
@@ -186,8 +195,6 @@ function duration(ms) {
186
195
  return ms + ' ' + unit;
187
196
  }
188
197
 
189
- const nRuns = Number.isSafeInteger(parseInt(options.runs)) ? parseInt(options.runs) : 1;
190
-
191
198
  /**
192
199
  * @typedef {object} RunTestOptions
193
200
  * @property {string} name
@@ -228,7 +235,7 @@ async function runTests(config) {
228
235
  }
229
236
 
230
237
  try {
231
- execSync(command, {
238
+ execFileSync('tsx', command, {
232
239
  stdio: ['ignore', options.verbose ? 'inherit' : 'ignore', 'inherit'],
233
240
  });
234
241
  if (!options.quiet) console.log(`${styleText('green', 'passed')}${identText} ${time()}`);
@@ -250,7 +257,7 @@ async function runTests(config) {
250
257
  if (options.common) {
251
258
  await runTests({
252
259
  name: 'common',
253
- args: [`'tests/*.test.ts'`, `'tests/**/!(fs)/*.test.ts'`],
260
+ args: ['tests/*.test.ts', `tests/**/!(fs)/*.test.ts`],
254
261
  statusName: 'Common tests',
255
262
  });
256
263
  }
@@ -269,9 +276,9 @@ for (const setupFile of positionals) {
269
276
 
270
277
  await runTests({
271
278
  name,
272
- args: [`'${testsGlob.replaceAll("'", "\\'")}'`, process.env.CMD],
279
+ args: [`${testsGlob.replaceAll("'", "\\'")}`, process.env.CMD || ''],
273
280
  shouldSkip() {
274
- return basename(setupFile).startsWith('_');
281
+ return basename(setupFile).startsWith('_') && options.auto;
275
282
  },
276
283
  });
277
284
  }
@@ -44,7 +44,7 @@ suite('Remote FS with resolveRemoteMount', () => {
44
44
  const content = 'FS is in a port';
45
45
 
46
46
  test('Configuration', async () => {
47
- await configureSingle({ backend: Port, port: configPort, timeout: 500 });
47
+ await configureSingle({ backend: Port, port: configPort, timeout: 1000 });
48
48
  });
49
49
 
50
50
  test('Write', async () => {
@@ -71,7 +71,7 @@ await suite('FS with MessageChannel', () => {
71
71
  test('configuration', async () => {
72
72
  tmpfs = await resolveMountConfig({ backend: InMemory, label: 'tmp' });
73
73
  attachFS(channel.port2, tmpfs);
74
- await configureSingle({ backend: Port, port: channel.port1, disableAsyncCache: true, timeout: 500 });
74
+ await configureSingle({ backend: Port, port: channel.port1, disableAsyncCache: true, timeout: 1000 });
75
75
  });
76
76
 
77
77
  test('write', async () => {
@@ -108,7 +108,7 @@ await suite('Remote FS', () => {
108
108
  const content = 'FS is in a port';
109
109
 
110
110
  test('Configuration', async () => {
111
- await configureSingle({ backend: Port, port: remotePort, timeout: 500 });
111
+ await configureSingle({ backend: Port, port: remotePort, timeout: 1000 });
112
112
  });
113
113
 
114
114
  test('Write', async () => {
@@ -0,0 +1,49 @@
1
+ // SPDX-License-Identifier: LGPL-3.0-or-later
2
+ import { InMemoryStore, StoreFS } from '@zenfs/core';
3
+ import assert from 'node:assert/strict';
4
+ import { suite, test } from 'node:test';
5
+ import { setupLogs } from '../logs.js';
6
+ setupLogs();
7
+
8
+ const fs = new StoreFS(new InMemoryStore(0x100000, 'test'));
9
+ await fs.ready();
10
+
11
+ const encoder = new TextEncoder();
12
+
13
+ suite('StoreFS', () => {
14
+ test('write updates inode metadata atomically #286', async () => {
15
+ await fs.createFile('/example.json', { mode: 0o644, uid: 0, gid: 0 });
16
+ const before = await fs.stat('/example.json');
17
+
18
+ // Write without a following touch(), like an interrupted VFS flush
19
+ const data = encoder.encode('{"json":true}');
20
+ await fs.write('/example.json', data, 0);
21
+
22
+ const after = await fs.stat('/example.json');
23
+ assert.equal(after.size, data.byteLength);
24
+ assert(after.mtimeMs >= before.mtimeMs);
25
+ });
26
+
27
+ test('writeSync updates inode metadata atomically #286', () => {
28
+ fs.createFileSync('/example-sync.json', { mode: 0o644, uid: 0, gid: 0 });
29
+
30
+ const data = encoder.encode('{"sync":true}');
31
+ fs.writeSync('/example-sync.json', data, 0);
32
+
33
+ const stats = fs.statSync('/example-sync.json');
34
+ assert.equal(stats.size, data.byteLength);
35
+ });
36
+
37
+ test('write does not resurrect truncated data', async () => {
38
+ await fs.createFile('/truncated.txt', { mode: 0o644, uid: 0, gid: 0 });
39
+
40
+ await fs.write('/truncated.txt', encoder.encode('a'.repeat(100)), 0);
41
+ await fs.touch('/truncated.txt', { size: 5 });
42
+
43
+ // A small write within the truncated size must not restore the old length from the data blob
44
+ await fs.write('/truncated.txt', encoder.encode('bbb'), 0);
45
+
46
+ const stats = await fs.stat('/truncated.txt');
47
+ assert.equal(stats.size, 5);
48
+ });
49
+ });
package/tests/common.ts CHANGED
@@ -20,3 +20,65 @@ const setup = await import(setupPath).catch(error => {
20
20
 
21
21
  // Satisfies is used to make sure that ZenFS is fully type compatible with Node.js
22
22
  export const fs = (setup.fs || defaultFS) as typeof defaultFS satisfies NodeFS;
23
+
24
+ export const Stats = (setup.Stats || defaultFS.Stats) as typeof defaultFS.Stats;
25
+
26
+ /**
27
+ * A feature or function a backend may not support.
28
+ * Tests that need one are skipped when the backend under test does not have it.
29
+ */
30
+ export type TestFlag =
31
+ | 'sync'
32
+ | 'async'
33
+ | 'write'
34
+ | 'appends'
35
+ | 'directories'
36
+ | 'links'
37
+ | 'symlinks'
38
+ | 'rename'
39
+ | 'truncate'
40
+ | 'streams'
41
+ | 'watch'
42
+ | 'permissions'
43
+ | 'xattr'
44
+ | 'times'
45
+ | 'tempdir'
46
+ | 'lchmod';
47
+
48
+ /**
49
+ * Whether a backend supports a flag.
50
+ *
51
+ * `false` is for things a backend can not support, while `'skip'` is for temporarily disabling something.
52
+ * `'todo'` is for things that should work but don't yet.
53
+ */
54
+ export type TestFlagState = boolean | 'skip' | 'todo';
55
+
56
+ /** Flags are supported unless the setup says otherwise */
57
+ const flags = (setup.flags ?? {}) as Partial<Record<TestFlag, TestFlagState>>;
58
+
59
+ /** Options for `suite` and `test`. Note this is a subset of both, since they do not share a type. */
60
+ export interface TestConfig {
61
+ skip?: string;
62
+ todo?: string;
63
+ }
64
+
65
+ /**
66
+ * Configures a `suite` or `test` to run only if the backend supports all of the given flags.
67
+ *
68
+ * @example
69
+ * ```ts
70
+ * test('lchmod', config('lchmod', 'async'), async () => { ... });
71
+ * ```
72
+ */
73
+ export function config(...required: TestFlag[]): TestConfig {
74
+ for (const flag of required) {
75
+ const state = flags[flag] ?? true;
76
+
77
+ if (state === true) continue;
78
+ if (state == 'todo') return { todo: `${flag} is not implemented yet` };
79
+
80
+ return { skip: state == 'skip' ? `${flag} is temporarily skipped` : `${flag} is not supported` };
81
+ }
82
+
83
+ return {};
84
+ }
@@ -1,5 +1,6 @@
1
1
  import { configure, Fetch } from '@zenfs/core';
2
2
  import { log } from 'kerium';
3
+ import type { TestFlag, TestFlagState } from '../common.js';
3
4
  import { baseUrl } from './config.js';
4
5
 
5
6
  await configure({
@@ -18,3 +19,8 @@ await configure({
18
19
  dumpBacklog: true,
19
20
  },
20
21
  });
22
+
23
+ export const flags: Partial<Record<TestFlag, TestFlagState>> = {
24
+ // `IndexFS` does not implement hard links
25
+ links: false,
26
+ };
@@ -6,12 +6,12 @@ SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
6
6
  node $SCRIPT_DIR/server.js &
7
7
  PID=$!
8
8
 
9
- echo -n "Waiting for server to start..."
9
+ echo -en "Waiting for server to start...\r"
10
10
  if [ -n "$VERBOSE" ]; then echo; fi
11
11
  until nc -z localhost 26514; do
12
12
  sleep 0.25
13
13
  done
14
14
 
15
- npx zenfs-test $SCRIPT_DIR/fetch.ts --preserve --force "$@"
15
+ npx --silent zenfs-test $SCRIPT_DIR/fetch.ts --preserve --force "$@"
16
16
 
17
17
  kill $PID
@@ -1,15 +1,15 @@
1
- import { execSync } from 'node:child_process';
1
+ import { execFileSync } from 'node:child_process';
2
2
  import { readFileSync } from 'node:fs';
3
3
  import { createServer } from 'node:http';
4
4
  import { join } from 'node:path';
5
- import { data, port, tmp, indexPath } from './config.js';
5
+ import { data, indexPath, port, tmp } from './config.js';
6
6
 
7
7
  const statusCodes = {
8
8
  ENOENT: 404,
9
9
  };
10
10
 
11
11
  try {
12
- execSync(`npm exec make-index -- ${data} --output ${tmp}/index.json --quiet`, { stdio: 'inherit' });
12
+ execFileSync('npx', ['--silent', 'make-index', data, '--output', `${tmp}/index.json`, '--quiet'], { stdio: 'inherit' });
13
13
  } catch (e) {
14
14
  if (e.signal == 'SIGINT') {
15
15
  console.log('Aborted whilst creating index');
@@ -1,20 +1,20 @@
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 { fs, config } from '../common.js';
5
5
 
6
6
  const content = 'Sample content',
7
7
  original = 'ABCD';
8
8
 
9
- suite('Appends', () => {
10
- test('Create an empty file and add content', async () => {
9
+ suite('Appends', config('appends'), () => {
10
+ test('Create an empty file and add content', config('async'), async () => {
11
11
  const filename = 'append.txt';
12
12
  await fs.promises.appendFile(filename, content);
13
13
  const data = await fs.promises.readFile(filename, 'utf8');
14
14
  assert.equal(data, content);
15
15
  });
16
16
 
17
- test('Append data to a non-empty file', async () => {
17
+ test('Append data to a non-empty file', config('async'), async () => {
18
18
  const filename = 'append2.txt';
19
19
 
20
20
  await fs.promises.writeFile(filename, original);
@@ -23,7 +23,7 @@ suite('Appends', () => {
23
23
  assert.equal(data, original + content);
24
24
  });
25
25
 
26
- test('Append a buffer to the file', async () => {
26
+ test('Append a buffer to the file', config('async'), async () => {
27
27
  const filename = 'append3.txt';
28
28
 
29
29
  await fs.promises.writeFile(filename, original);
@@ -0,0 +1,52 @@
1
+ // SPDX-License-Identifier: LGPL-3.0-or-later
2
+ import assert from 'node:assert/strict';
3
+ import { suite, test } from 'node:test';
4
+ import { config, fs } from '../common.js';
5
+
6
+ suite('Concurrency', config('write', 'async'), () => {
7
+ test('parallel writeFile in the same directory does not lose entries #256', async () => {
8
+ await fs.promises.mkdir('/issue-256');
9
+
10
+ await Promise.all([fs.promises.writeFile('/issue-256/a1.txt', 'a1'), fs.promises.writeFile('/issue-256/a2.txt', 'a2')]);
11
+
12
+ const files = await fs.promises.readdir('/issue-256');
13
+ assert.deepEqual(files.sort(), ['a1.txt', 'a2.txt']);
14
+ });
15
+
16
+ test('concurrent file creation does not lose directory entries #298', async () => {
17
+ await fs.promises.mkdir('/issue-298');
18
+
19
+ const names = Array.from({ length: 32 }, (_, i) => `file-${i}.txt`);
20
+ await Promise.all(names.map(name => fs.promises.writeFile(`/issue-298/${name}`, name)));
21
+
22
+ const files = await fs.promises.readdir('/issue-298');
23
+ assert.deepEqual(files.sort(), names.sort());
24
+ });
25
+
26
+ test('open handles for the same file share metadata #287', async () => {
27
+ await fs.promises.writeFile('/issue-287.txt', 'Hello World');
28
+
29
+ const a = await fs.promises.open('/issue-287.txt', 'r+');
30
+ const b = await fs.promises.open('/issue-287.txt', 'r+');
31
+
32
+ await a.truncate(5); // contents are now "Hello"
33
+ const { size } = await b.stat();
34
+ assert.equal(size, 5);
35
+
36
+ await a.close();
37
+ await b.close();
38
+ });
39
+
40
+ test('writes are visible to other open handles before syncing', async () => {
41
+ const a = await fs.promises.open('/shared-write.txt', 'w+');
42
+ const b = await fs.promises.open('/shared-write.txt', 'r');
43
+
44
+ await a.write('some shared data');
45
+
46
+ const { buffer } = await b.read(new Uint8Array(16), 0, 16, 0);
47
+ assert.equal(new TextDecoder().decode(buffer), 'some shared data');
48
+
49
+ await a.close();
50
+ await b.close();
51
+ });
52
+ });
@@ -2,7 +2,7 @@
2
2
  import { sync, type Dirent } from '@zenfs/core';
3
3
  import assert, { rejects } from 'node:assert/strict';
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 testFile = 'test-file.txt';
8
8
  fs.writeFileSync(testFile, 'Sample content');
@@ -16,8 +16,8 @@ for (const file of testFiles) {
16
16
  }
17
17
  await sync();
18
18
 
19
- suite('Dir', () => {
20
- test('read()', async () => {
19
+ suite('Dir', config('directories'), () => {
20
+ test('read()', config('async'), async () => {
21
21
  const dir = fs.opendirSync(testDirPath);
22
22
 
23
23
  const dirent1 = await dir.read();
@@ -34,7 +34,7 @@ suite('Dir', () => {
34
34
  await dir.close();
35
35
  });
36
36
 
37
- test('readSync()', () => {
37
+ test('readSync()', config('sync'), () => {
38
38
  const dir = fs.opendirSync(testDirPath);
39
39
 
40
40
  const dirent1 = dir.readSync();
@@ -51,19 +51,19 @@ suite('Dir', () => {
51
51
  dir.closeSync();
52
52
  });
53
53
 
54
- test('close()', async () => {
54
+ test('close()', config('async'), async () => {
55
55
  const dir = fs.opendirSync(testDirPath);
56
56
  await dir.close();
57
57
  rejects(dir.read());
58
58
  });
59
59
 
60
- test('closeSync()', () => {
60
+ test('closeSync()', config('sync'), () => {
61
61
  const dir = fs.opendirSync(testDirPath);
62
62
  dir.closeSync();
63
63
  assert.throws(() => dir.readSync());
64
64
  });
65
65
 
66
- test('asynchronous iteration', async () => {
66
+ test('asynchronous iteration', config('async'), async () => {
67
67
  const dir = fs.opendirSync(testDirPath);
68
68
  const dirents: Dirent[] = [];
69
69
 
@@ -77,29 +77,27 @@ suite('Dir', () => {
77
77
  assert(testFiles.includes(dirents[1].name));
78
78
  });
79
79
 
80
- test('read after directory is closed', async () => {
80
+ test('read after directory is closed', config('async'), async () => {
81
81
  const dir = fs.opendirSync(testDirPath);
82
82
  await dir.close();
83
83
  await assert.rejects(dir.read());
84
84
  });
85
85
 
86
- test('readSync after directory is closed', () => {
86
+ test('readSync after directory is closed', config('sync'), () => {
87
87
  const dir = fs.opendirSync(testDirPath);
88
88
  dir.closeSync();
89
89
  assert.throws(() => dir.readSync());
90
90
  });
91
91
 
92
- test('close multiple times', async () => {
92
+ test('close multiple times', config('async'), async () => {
93
93
  const dir = fs.opendirSync(testDirPath);
94
94
  await dir.close();
95
- await dir.close(); // Should not throw an error
96
- assert(dir['closed']);
95
+ await assert.rejects(dir.close(), { code: 'ERR_DIR_CLOSED' });
97
96
  });
98
97
 
99
- test('closeSync multiple times', () => {
98
+ test('closeSync multiple times', config('sync'), () => {
100
99
  const dir = fs.opendirSync(testDirPath);
101
100
  dir.closeSync();
102
- dir.closeSync(); // Should not throw an error
103
- assert(dir['closed']);
101
+ assert.throws(() => dir.closeSync(), { code: 'ERR_DIR_CLOSED' });
104
102
  });
105
103
  });