@zenfs/core 2.5.7 → 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.
- package/dist/backends/cow.d.ts +13 -0
- package/dist/backends/cow.js +48 -11
- package/dist/backends/single_buffer.d.ts +2 -2
- package/dist/backends/store/fs.d.ts +6 -6
- package/dist/backends/store/fs.js +14 -10
- package/dist/internal/devices.d.ts +6 -6
- package/dist/internal/filesystem.d.ts +4 -4
- package/dist/internal/index_fs.d.ts +4 -4
- package/dist/internal/index_fs.js +6 -8
- package/dist/internal/inode.js +3 -0
- package/dist/node/async.js +1 -1
- package/dist/node/dir.d.ts +3 -1
- package/dist/node/dir.js +23 -5
- package/dist/node/promises.d.ts +4 -2
- package/dist/node/promises.js +130 -89
- package/dist/node/streams.d.ts +4 -0
- package/dist/node/streams.js +9 -10
- package/dist/node/sync.js +71 -40
- package/dist/utils.d.ts +8 -1
- package/dist/utils.js +26 -7
- package/dist/vfs/async.js +224 -85
- package/dist/vfs/file.d.ts +13 -7
- package/dist/vfs/file.js +661 -357
- package/dist/vfs/shared.d.ts +1 -0
- package/dist/vfs/shared.js +19 -3
- package/dist/vfs/sync.js +214 -85
- package/dist/vfs/vcache.d.ts +66 -0
- package/dist/vfs/vcache.js +249 -0
- package/dist/vfs/vnode.d.ts +66 -0
- package/dist/vfs/vnode.js +200 -0
- package/dist/vfs/watchers.js +4 -2
- package/dist/vfs/xattr.js +2 -2
- package/eslint.shared.js +1 -1
- package/package.json +6 -3
- package/scripts/make-index.js +7 -1
- package/scripts/test.js +28 -21
- package/tests/backend/port.test.ts +3 -3
- package/tests/backend/store.test.ts +49 -0
- package/tests/common/inode.test.ts +8 -0
- package/tests/common.ts +62 -0
- package/tests/fetch/fetch.ts +6 -0
- package/tests/fetch/run.sh +2 -2
- package/tests/fetch/server.js +3 -3
- package/tests/fs/append.test.ts +5 -5
- package/tests/fs/concurrency.test.ts +52 -0
- package/tests/fs/dir.test.ts +13 -15
- package/tests/fs/directory.test.ts +32 -31
- package/tests/fs/errors.test.ts +151 -31
- package/tests/fs/exists.test.ts +4 -4
- package/tests/fs/links.test.ts +4 -11
- package/tests/fs/open.test.ts +4 -4
- package/tests/fs/permissions.test.ts +15 -15
- package/tests/fs/read.test.ts +9 -9
- package/tests/fs/readFile.test.ts +9 -9
- package/tests/fs/rename.test.ts +8 -8
- package/tests/fs/scaling.test.ts +4 -4
- package/tests/fs/stat.test.ts +20 -20
- package/tests/fs/streams.test.ts +9 -6
- package/tests/fs/tempdir.test.ts +33 -10
- package/tests/fs/times.test.ts +11 -11
- package/tests/fs/truncate.test.ts +6 -6
- package/tests/fs/watch.test.ts +107 -53
- package/tests/fs/write.test.ts +10 -10
- package/tests/fs/xattr.test.ts +3 -3
- package/tests/native/run.sh +3 -0
- package/tests/native/setup.ts +230 -0
- 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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
'
|
|
159
|
-
options.inspect
|
|
160
|
-
'--test
|
|
161
|
-
|
|
162
|
-
options.
|
|
163
|
-
|
|
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
|
-
.
|
|
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
|
-
|
|
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: [
|
|
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: [
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
+
});
|
|
@@ -17,4 +17,12 @@ suite('Inode manipulation', () => {
|
|
|
17
17
|
const newInode = Object.assign(new Inode(), inode);
|
|
18
18
|
assert.equal(newInode.mode, inode.mode);
|
|
19
19
|
});
|
|
20
|
+
|
|
21
|
+
test('Copy to new Inode using the constructor does not alias', () => {
|
|
22
|
+
const newInode = new Inode(inode);
|
|
23
|
+
assert.equal(newInode.mode, inode.mode);
|
|
24
|
+
|
|
25
|
+
newInode.mode = 0o40700;
|
|
26
|
+
assert.equal(inode.mode, 0o40755);
|
|
27
|
+
});
|
|
20
28
|
});
|
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
|
+
}
|
package/tests/fetch/fetch.ts
CHANGED
|
@@ -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
|
+
};
|
package/tests/fetch/run.sh
CHANGED
|
@@ -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 -
|
|
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
|
package/tests/fetch/server.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import {
|
|
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
|
|
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
|
-
|
|
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');
|
package/tests/fs/append.test.ts
CHANGED
|
@@ -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
|
+
});
|
package/tests/fs/dir.test.ts
CHANGED
|
@@ -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()
|
|
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()
|
|
103
|
-
assert(dir['closed']);
|
|
101
|
+
assert.throws(() => dir.closeSync(), { code: 'ERR_DIR_CLOSED' });
|
|
104
102
|
});
|
|
105
103
|
});
|