@zenfs/core 1.2.10 → 1.3.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 (53) hide show
  1. package/dist/backends/fetch.js +1 -1
  2. package/dist/backends/memory.d.ts +1 -2
  3. package/dist/backends/overlay.js +2 -3
  4. package/dist/backends/port/fs.d.ts +2 -15
  5. package/dist/backends/store/fs.d.ts +17 -28
  6. package/dist/backends/store/fs.js +169 -191
  7. package/dist/backends/store/simple.d.ts +20 -21
  8. package/dist/backends/store/simple.js +24 -24
  9. package/dist/backends/store/store.d.ts +22 -23
  10. package/dist/backends/store/store.js +6 -6
  11. package/dist/config.d.ts +8 -0
  12. package/dist/config.js +2 -1
  13. package/dist/devices.d.ts +2 -3
  14. package/dist/emulation/cache.d.ts +40 -31
  15. package/dist/emulation/cache.js +62 -53
  16. package/dist/emulation/index.d.ts +1 -1
  17. package/dist/emulation/index.js +1 -1
  18. package/dist/emulation/promises.js +18 -17
  19. package/dist/emulation/shared.d.ts +6 -0
  20. package/dist/emulation/shared.js +13 -1
  21. package/dist/emulation/sync.js +14 -13
  22. package/dist/file.d.ts +3 -15
  23. package/dist/file.js +6 -18
  24. package/dist/inode.d.ts +4 -13
  25. package/dist/inode.js +20 -27
  26. package/dist/mixins/async.d.ts +15 -10
  27. package/dist/mixins/async.js +3 -1
  28. package/dist/utils.d.ts +5 -7
  29. package/dist/utils.js +11 -20
  30. package/package.json +1 -1
  31. package/src/backends/fetch.ts +1 -1
  32. package/src/backends/memory.ts +1 -2
  33. package/src/backends/overlay.ts +2 -2
  34. package/src/backends/store/fs.ts +187 -220
  35. package/src/backends/store/simple.ts +36 -37
  36. package/src/backends/store/store.ts +25 -26
  37. package/src/config.ts +11 -1
  38. package/src/devices.ts +2 -3
  39. package/src/emulation/cache.ts +68 -60
  40. package/src/emulation/index.ts +1 -1
  41. package/src/emulation/promises.ts +20 -19
  42. package/src/emulation/shared.ts +13 -1
  43. package/src/emulation/sync.ts +14 -13
  44. package/src/file.ts +8 -20
  45. package/src/inode.ts +8 -29
  46. package/src/mixins/async.ts +27 -24
  47. package/src/utils.ts +11 -23
  48. package/tests/fs/dir.test.ts +21 -31
  49. package/tests/fs/directory.test.ts +6 -4
  50. package/tests/fs/links.test.ts +9 -2
  51. package/tests/fs/permissions.test.ts +2 -2
  52. package/tests/fs/times.test.ts +22 -11
  53. package/tests/setup/cow+fetch.ts +4 -2
package/src/file.ts CHANGED
@@ -145,7 +145,7 @@ export function isExclusive(flag: string): boolean {
145
145
  return flag.indexOf('x') !== -1;
146
146
  }
147
147
 
148
- export abstract class File {
148
+ export abstract class File<FS extends FileSystem = FileSystem> {
149
149
  public constructor(
150
150
  /**
151
151
  * @internal
@@ -166,12 +166,12 @@ export abstract class File {
166
166
  public abstract close(): Promise<void>;
167
167
  public abstract closeSync(): void;
168
168
 
169
- public [Symbol.asyncDispose](): Promise<void> {
170
- return this.close();
169
+ public async [Symbol.asyncDispose](): Promise<void> {
170
+ await this.close();
171
171
  }
172
172
 
173
173
  public [Symbol.dispose](): void {
174
- return this.closeSync();
174
+ this.closeSync();
175
175
  }
176
176
 
177
177
  public abstract truncate(len: number): Promise<void>;
@@ -269,7 +269,7 @@ export abstract class File {
269
269
  * An implementation of `File` that operates completely in-memory.
270
270
  * `PreloadFile`s are backed by a `Uint8Array`.
271
271
  */
272
- export class PreloadFile<FS extends FileSystem> extends File {
272
+ export class PreloadFile<FS extends FileSystem> extends File<FS> {
273
273
  /**
274
274
  * Current position
275
275
  */
@@ -290,11 +290,7 @@ export class PreloadFile<FS extends FileSystem> extends File {
290
290
  * Note that, if contents is specified, it will be mutated by the file.
291
291
  */
292
292
  public constructor(
293
- /**
294
- * The file system that created the file.
295
- * @internal
296
- */
297
- public fs: FS,
293
+ fs: FS,
298
294
  path: string,
299
295
  public readonly flag: string,
300
296
  public readonly stats: Stats,
@@ -431,12 +427,12 @@ export class PreloadFile<FS extends FileSystem> extends File {
431
427
  if (length > this._buffer.length) {
432
428
  const data = new Uint8Array(length - this._buffer.length);
433
429
  // Write will set stats.size and handle syncing.
434
- this.writeSync(data, 0, data.length, this._buffer.length);
430
+ this._write(data, 0, data.length, this._buffer.length);
435
431
  return;
436
432
  }
437
433
  this.stats.size = length;
438
434
  // Truncate.
439
- this._buffer = this._buffer.slice(0, length);
435
+ this._buffer = length ? this._buffer.slice(0, length) : new Uint8Array();
440
436
  }
441
437
 
442
438
  public async truncate(length: number): Promise<void> {
@@ -645,14 +641,6 @@ export class PreloadFile<FS extends FileSystem> extends File {
645
641
  this.stats.mode = (this.stats.mode & ~S_IFMT) | type;
646
642
  this.syncSync();
647
643
  }
648
-
649
- public async [Symbol.asyncDispose]() {
650
- await this.close();
651
- }
652
-
653
- public [Symbol.dispose]() {
654
- this.closeSync();
655
- }
656
644
  }
657
645
 
658
646
  /**
package/src/inode.ts CHANGED
@@ -1,11 +1,6 @@
1
- import { deserialize, serialize, sizeof, struct, types as t } from 'utilium';
1
+ import { deserialize, sizeof, struct, types as t } from 'utilium';
2
2
  import { Stats, type StatsLike } from './stats.js';
3
-
4
- /**
5
- * Alias for an ino.
6
- * This will be helpful if in the future inode numbers/IDs are changed to strings or numbers.
7
- */
8
- export type Ino = bigint;
3
+ import { randomBigInt } from './utils.js';
9
4
 
10
5
  /**
11
6
  * Room inode
@@ -13,31 +8,13 @@ export type Ino = bigint;
13
8
  */
14
9
  export const rootIno = 0n;
15
10
 
16
- /**
17
- * Generates a random 32 bit integer, then converts to a hex string
18
- */
19
- function _random() {
20
- return Math.round(Math.random() * 2 ** 32).toString(16);
21
- }
22
-
23
- /**
24
- * Generate a random ino
25
- * @internal
26
- */
27
- export function randomIno(): Ino {
28
- return BigInt('0x' + _random() + _random());
29
- }
30
-
31
11
  /**
32
12
  * Generic inode definition that can easily be serialized.
13
+ * @internal
33
14
  */
34
15
  @struct()
35
16
  export class Inode implements StatsLike {
36
- public get data(): Uint8Array {
37
- return serialize(this);
38
- }
39
-
40
- public constructor(buffer?: ArrayBufferLike) {
17
+ public constructor(buffer?: ArrayBufferLike | ArrayBufferView) {
41
18
  if (buffer) {
42
19
  if (buffer.byteLength < sizeof(Inode)) {
43
20
  throw new RangeError(`Can not create an inode from a buffer less than ${sizeof(Inode)} bytes`);
@@ -48,7 +25,8 @@ export class Inode implements StatsLike {
48
25
  }
49
26
 
50
27
  // set defaults on a fresh inode
51
- this.ino = randomIno();
28
+ this.ino = randomBigInt();
29
+ this.data = randomBigInt();
52
30
  this.nlink = 1;
53
31
  this.size = 4096;
54
32
  const now = Date.now();
@@ -58,7 +36,7 @@ export class Inode implements StatsLike {
58
36
  this.birthtimeMs = now;
59
37
  }
60
38
 
61
- @t.uint64 public ino!: Ino;
39
+ @t.uint64 public data!: bigint;
62
40
  @t.uint32 public size!: number;
63
41
  @t.uint16 public mode!: number;
64
42
  @t.uint32 public nlink!: number;
@@ -68,6 +46,7 @@ export class Inode implements StatsLike {
68
46
  @t.float64 public birthtimeMs!: number;
69
47
  @t.float64 public mtimeMs!: number;
70
48
  @t.float64 public ctimeMs!: number;
49
+ @t.uint64 public ino!: bigint;
71
50
 
72
51
  /**
73
52
  * Handy function that converts the Inode to a Node Stats object.
@@ -12,6 +12,28 @@ export type AsyncOperation = {
12
12
  [K in keyof AsyncFSMethods]: [K, ...Parameters<FileSystem[K]>];
13
13
  }[keyof AsyncFSMethods];
14
14
 
15
+ /**
16
+ * @internal
17
+ */
18
+ export interface Async {
19
+ /**
20
+ * @internal @protected
21
+ */
22
+ _sync?: FileSystem;
23
+ queueDone(): Promise<void>;
24
+ ready(): Promise<void>;
25
+ renameSync(oldPath: string, newPath: string): void;
26
+ statSync(path: string): Stats;
27
+ createFileSync(path: string, flag: string, mode: number): File;
28
+ openFileSync(path: string, flag: string): File;
29
+ unlinkSync(path: string): void;
30
+ rmdirSync(path: string): void;
31
+ mkdirSync(path: string, mode: number): void;
32
+ readdirSync(path: string): string[];
33
+ linkSync(srcpath: string, dstpath: string): void;
34
+ syncSync(path: string, data: Uint8Array, stats: Readonly<Stats>): void;
35
+ }
36
+
15
37
  /**
16
38
  * Async() implements synchronous methods on an asynchronous file system
17
39
  *
@@ -22,29 +44,7 @@ export type AsyncOperation = {
22
44
  * During loading, the contents of the async file system are preloaded into the synchronous store.
23
45
  *
24
46
  */
25
- export function Async<T extends typeof FileSystem>(
26
- FS: T
27
- ): Mixin<
28
- T,
29
- {
30
- /**
31
- * @internal @protected
32
- */
33
- _sync?: FileSystem;
34
- queueDone(): Promise<void>;
35
- ready(): Promise<void>;
36
- renameSync(oldPath: string, newPath: string): void;
37
- statSync(path: string): Stats;
38
- createFileSync(path: string, flag: string, mode: number): File;
39
- openFileSync(path: string, flag: string): File;
40
- unlinkSync(path: string): void;
41
- rmdirSync(path: string): void;
42
- mkdirSync(path: string, mode: number): void;
43
- readdirSync(path: string): string[];
44
- linkSync(srcpath: string, dstpath: string): void;
45
- syncSync(path: string, data: Uint8Array, stats: Readonly<Stats>): void;
46
- }
47
- > {
47
+ export function Async<const T extends typeof FileSystem>(FS: T): Mixin<T, Async> {
48
48
  abstract class AsyncFS extends FS {
49
49
  /**
50
50
  * Queue of pending asynchronous operations.
@@ -67,6 +67,7 @@ export function Async<T extends typeof FileSystem>(
67
67
 
68
68
  public async ready(): Promise<void> {
69
69
  await super.ready();
70
+ await this.queueDone();
70
71
  if (this._isInitialized || this._disableSync) {
71
72
  return;
72
73
  }
@@ -128,7 +129,7 @@ export function Async<T extends typeof FileSystem>(
128
129
 
129
130
  public openFileSync(path: string, flag: string): PreloadFile<this> {
130
131
  this.checkSync(path, 'openFile');
131
- const file = this._sync.openFileSync(path, flag);
132
+ const file = this._sync.openFileSync(path, flag + '+');
132
133
  const stats = file.statSync();
133
134
  const buffer = new Uint8Array(stats.size);
134
135
  file.readSync(buffer);
@@ -225,3 +226,5 @@ export function Async<T extends typeof FileSystem>(
225
226
 
226
227
  return AsyncFS;
227
228
  }
229
+
230
+ export function asyncPatch<T extends typeof FileSystem>(fs: Mixin<T, Async>) {}
package/src/utils.ts CHANGED
@@ -182,22 +182,6 @@ export function encodeDirListing(data: Record<string, bigint>): Uint8Array {
182
182
 
183
183
  export type Callback<Args extends unknown[] = []> = (e?: ErrnoError, ...args: OptionalTuple<Args>) => unknown;
184
184
 
185
- /**
186
- * converts Date or number to a integer UNIX timestamp
187
- * Grabbed from NodeJS sources (lib/fs.js)
188
- *
189
- * @internal
190
- */
191
- export function _toUnixTimestamp(time: Date | number): number {
192
- if (typeof time === 'number') {
193
- return Math.floor(time);
194
- }
195
- if (time instanceof Date) {
196
- return Math.floor(time.getTime() / 1000);
197
- }
198
- throw new Error('Cannot parse time');
199
- }
200
-
201
185
  /**
202
186
  * Normalizes a mode
203
187
  * @internal
@@ -230,15 +214,11 @@ export function normalizeTime(time: string | number | Date): Date {
230
214
  return time;
231
215
  }
232
216
 
233
- if (typeof time == 'number') {
234
- return new Date(time * 1000);
235
- }
236
-
237
- if (typeof time == 'string') {
217
+ try {
238
218
  return new Date(time);
219
+ } catch {
220
+ throw new ErrnoError(Errno.EINVAL, 'Invalid time.');
239
221
  }
240
-
241
- throw new ErrnoError(Errno.EINVAL, 'Invalid time.');
242
222
  }
243
223
 
244
224
  /**
@@ -286,3 +266,11 @@ export function normalizeOptions(
286
266
  }
287
267
 
288
268
  export type Concrete<T extends ClassLike> = Pick<T, keyof T> & (new (...args: any[]) => InstanceType<T>);
269
+
270
+ /**
271
+ * Generate a random ino
272
+ * @internal
273
+ */
274
+ export function randomBigInt(): bigint {
275
+ return crypto.getRandomValues(new BigUint64Array(1))[0];
276
+ }
@@ -3,18 +3,20 @@ import { suite, test } from 'node:test';
3
3
  import { fs } from '../common.js';
4
4
 
5
5
  const testFile = 'test-file.txt';
6
- await fs.promises.writeFile(testFile, 'Sample content');
7
- await fs.promises.mkdir('test-directory');
8
- await fs.promises.symlink(testFile, 'test-symlink');
6
+ fs.writeFileSync(testFile, 'Sample content');
7
+ fs.mkdirSync('test-directory');
8
+ fs.symlinkSync(testFile, 'test-symlink');
9
9
  const testDirPath = 'test-dir';
10
10
  const testFiles = ['file1.txt', 'file2.txt'];
11
- await fs.promises.mkdir(testDirPath);
11
+ fs.mkdirSync(testDirPath);
12
12
  for (const file of testFiles) {
13
- await fs.promises.writeFile(`${testDirPath}/${file}`, 'Sample content');
13
+ fs.writeFileSync(`${testDirPath}/${file}`, 'Sample content');
14
14
  }
15
15
 
16
+ await fs._synced();
17
+
16
18
  suite('Dirent', () => {
17
- test('Dirent name and parentPath getters', async () => {
19
+ test('name and parentPath getters', async () => {
18
20
  const stats = await fs.promises.lstat(testFile);
19
21
  const dirent = new fs.Dirent(testFile, stats);
20
22
 
@@ -22,7 +24,7 @@ suite('Dirent', () => {
22
24
  assert.equal(dirent.parentPath, testFile);
23
25
  });
24
26
 
25
- test('Dirent.isFile', async () => {
27
+ test('isFile', async () => {
26
28
  const fileStats = await fs.promises.lstat(testFile);
27
29
  const fileDirent = new fs.Dirent(testFile, fileStats);
28
30
 
@@ -30,7 +32,7 @@ suite('Dirent', () => {
30
32
  assert(!fileDirent.isDirectory());
31
33
  });
32
34
 
33
- test('Dirent.isDirectory', async () => {
35
+ test('isDirectory', async () => {
34
36
  const dirStats = await fs.promises.lstat('test-directory');
35
37
  const dirDirent = new fs.Dirent('test-directory', dirStats);
36
38
 
@@ -38,14 +40,14 @@ suite('Dirent', () => {
38
40
  assert(dirDirent.isDirectory());
39
41
  });
40
42
 
41
- test('Dirent.isSymbolicLink', async () => {
43
+ test('isSymbolicLink', async () => {
42
44
  const symlinkStats = await fs.promises.lstat('test-symlink');
43
45
  const symlinkDirent = new fs.Dirent('test-symlink', symlinkStats);
44
46
 
45
47
  assert(symlinkDirent.isSymbolicLink());
46
48
  });
47
49
 
48
- test('Dirent other methods return false', async () => {
50
+ test('other methods return false', async () => {
49
51
  const fileStats = await fs.promises.lstat(testFile);
50
52
  const fileDirent = new fs.Dirent(testFile, fileStats);
51
53
 
@@ -56,7 +58,7 @@ suite('Dirent', () => {
56
58
  });
57
59
 
58
60
  suite('Dir', () => {
59
- test('Dir read() method (Promise varient)', async () => {
61
+ test('read()', async () => {
60
62
  const dir = new fs.Dir(testDirPath);
61
63
 
62
64
  const dirent1 = await dir.read();
@@ -73,19 +75,7 @@ suite('Dir', () => {
73
75
  await dir.close();
74
76
  });
75
77
 
76
- test('Dir read() method (Callback varient)', (_, done) => {
77
- const dir = new fs.Dir(testDirPath);
78
- dir.read((err, dirent) => {
79
- assert.strictEqual(err, undefined);
80
- assert.notEqual(dirent, undefined);
81
- assert(dirent instanceof fs.Dirent);
82
- assert(testFiles.includes(dirent?.name));
83
- dir.closeSync();
84
- done();
85
- });
86
- });
87
-
88
- test('Dir readSync() method', () => {
78
+ test('readSync()', () => {
89
79
  const dir = new fs.Dir(testDirPath);
90
80
 
91
81
  const dirent1 = dir.readSync();
@@ -102,19 +92,19 @@ suite('Dir', () => {
102
92
  dir.closeSync();
103
93
  });
104
94
 
105
- test('Dir close() method (Promise version)', async () => {
95
+ test('close()', async () => {
106
96
  const dir = new fs.Dir(testDirPath);
107
97
  await dir.close();
108
98
  rejects(dir.read(), 'Can not use closed Dir');
109
99
  });
110
100
 
111
- test('Dir closeSync() method', () => {
101
+ test('closeSync()', () => {
112
102
  const dir = new fs.Dir(testDirPath);
113
103
  dir.closeSync();
114
104
  assert.throws(() => dir.readSync(), 'Can not use closed Dir');
115
105
  });
116
106
 
117
- test('Dir asynchronous iteration', async () => {
107
+ test('asynchronous iteration', async () => {
118
108
  const dir = new fs.Dir(testDirPath);
119
109
  const dirents: fs.Dirent[] = [];
120
110
 
@@ -128,26 +118,26 @@ suite('Dir', () => {
128
118
  assert(testFiles.includes(dirents[1].name));
129
119
  });
130
120
 
131
- test('Dir read after directory is closed', async () => {
121
+ test('read after directory is closed', async () => {
132
122
  const dir = new fs.Dir(testDirPath);
133
123
  await dir.close();
134
124
  await assert.rejects(dir.read(), 'Can not use closed Dir');
135
125
  });
136
126
 
137
- test('Dir readSync after directory is closed', () => {
127
+ test('readSync after directory is closed', () => {
138
128
  const dir = new fs.Dir(testDirPath);
139
129
  dir.closeSync();
140
130
  assert.throws(() => dir.readSync(), 'Can not use closed Dir');
141
131
  });
142
132
 
143
- test('Dir close multiple times', async () => {
133
+ test('close multiple times', async () => {
144
134
  const dir = new fs.Dir(testDirPath);
145
135
  await dir.close();
146
136
  await dir.close(); // Should not throw an error
147
137
  assert(dir['closed']);
148
138
  });
149
139
 
150
- test('Dir closeSync multiple times', () => {
140
+ test('closeSync multiple times', () => {
151
141
  const dir = new fs.Dir(testDirPath);
152
142
  dir.closeSync();
153
143
  dir.closeSync(); // Should not throw an error
@@ -7,17 +7,19 @@ const testDir = 'test-dir';
7
7
  const testFiles = ['file1.txt', 'file2.txt', 'file3.txt'];
8
8
  const testDirectories = ['subdir1', 'subdir2'];
9
9
 
10
- await fs.promises.mkdir(testDir);
10
+ fs.mkdirSync(testDir);
11
11
  for (const file of testFiles) {
12
- await fs.promises.writeFile(`${testDir}/${file}`, 'Sample content');
12
+ fs.writeFileSync(`${testDir}/${file}`, 'Sample content');
13
13
  }
14
14
  for (const dir of testDirectories) {
15
- await fs.promises.mkdir(`${testDir}/${dir}`);
15
+ fs.mkdirSync(`${testDir}/${dir}`);
16
16
  for (const file of ['file4.txt', 'file5.txt']) {
17
- await fs.promises.writeFile(`${testDir}/${dir}/${file}`, 'Sample content');
17
+ fs.writeFileSync(`${testDir}/${dir}/${file}`, 'Sample content');
18
18
  }
19
19
  }
20
20
 
21
+ await fs._synced();
22
+
21
23
  suite('Directories', () => {
22
24
  test('mkdir', async () => {
23
25
  await fs.promises.mkdir('/one', 0o755);
@@ -2,6 +2,7 @@ import assert from 'node:assert';
2
2
  import { suite, test } from 'node:test';
3
3
  import { join } from '../../dist/emulation/path.js';
4
4
  import { fs } from '../common.js';
5
+ import type { ErrnoError } from '../../dist/error.js';
5
6
 
6
7
  suite('Links', () => {
7
8
  const target = '/a1.js',
@@ -28,8 +29,14 @@ suite('Links', () => {
28
29
  assert(await fs.promises.exists(target));
29
30
  });
30
31
 
31
- test('link', async () => {
32
- await fs.promises.link(target, hardlink);
32
+ test('link', async t => {
33
+ const _ = await fs.promises.link(target, hardlink).catch((e: ErrnoError) => {
34
+ if (e.code == 'ENOSYS') return e;
35
+ throw e;
36
+ });
37
+ if (_) {
38
+ return t.skip('Backend does not support hard links');
39
+ }
33
40
  const targetContent = await fs.promises.readFile(target, 'utf8');
34
41
  const linkContent = await fs.promises.readFile(hardlink, 'utf8');
35
42
  assert.strictEqual(targetContent, linkContent);
@@ -43,8 +43,8 @@ suite('Permissions', () => {
43
43
  const stats = await fs.promises.lstat(link);
44
44
  assert.equal(stats.mode & 0o777, asyncMode);
45
45
 
46
- fs.lchmodSync(link, syncMode);
47
- assert.equal(fs.lstatSync(link).mode & 0o777, syncMode);
46
+ await fs.promises.lchmod(link, syncMode);
47
+ assert.equal((await fs.promises.lstat(link)).mode & 0o777, syncMode);
48
48
  });
49
49
 
50
50
  async function test_item(path: string): Promise<void> {
@@ -2,22 +2,33 @@ import assert from 'node:assert';
2
2
  import { suite, test } from 'node:test';
3
3
  import { wait } from 'utilium';
4
4
  import { ErrnoError } from '../../dist/error.js';
5
- import { _toUnixTimestamp } from '../../dist/utils.js';
5
+ import type { StatsLike } from '../../dist/stats.js';
6
6
  import { fs } from '../common.js';
7
7
 
8
8
  const path = 'x.txt';
9
9
 
10
- suite('times', () => {
11
- function expect_assert(resource: string | number, atime: Date | number, mtime: Date | number) {
12
- const stats = typeof resource == 'string' ? fs.statSync(resource) : fs.fstatSync(resource);
13
- // check up to single-second precision since sub-second precision is OS and fs dependent
14
- assert.equal(_toUnixTimestamp(atime), _toUnixTimestamp(stats.atime));
15
- assert.equal(_toUnixTimestamp(mtime), _toUnixTimestamp(stats.mtime));
16
- }
10
+ /**
11
+ * Gets unix timestamps from stats
12
+ *
13
+ * @internal
14
+ */
15
+ export function unixTimestamps(stats: StatsLike<number>): Record<'atime' | 'mtime', number> {
16
+ return {
17
+ atime: Math.floor(stats.atimeMs),
18
+ mtime: Math.floor(stats.mtimeMs),
19
+ };
20
+ }
17
21
 
22
+ suite('times', () => {
18
23
  async function runTest(atime: Date | number, mtime: Date | number): Promise<void> {
24
+ const times = {
25
+ atime: typeof atime == 'number' ? Math.floor(atime) : atime.getTime(),
26
+ mtime: typeof mtime == 'number' ? Math.floor(mtime) : mtime.getTime(),
27
+ };
28
+
19
29
  await fs.promises.utimes(path, atime, mtime);
20
- expect_assert(path, atime, mtime);
30
+
31
+ assert.deepStrictEqual(unixTimestamps(await fs.promises.stat(path)), times);
21
32
 
22
33
  await fs.promises.utimes('foobarbaz', atime, mtime).catch((error: ErrnoError) => {
23
34
  assert(error instanceof ErrnoError);
@@ -27,10 +38,10 @@ suite('times', () => {
27
38
  await using handle = await fs.promises.open(path, 'r');
28
39
 
29
40
  await handle.utimes(atime, mtime);
30
- expect_assert(handle.fd, atime, mtime);
41
+ assert.deepStrictEqual(unixTimestamps(await handle.stat()), times);
31
42
 
32
43
  fs.utimesSync(path, atime, mtime);
33
- expect_assert(path, atime, mtime);
44
+ assert.deepStrictEqual(unixTimestamps(fs.statSync(path)), times);
34
45
 
35
46
  try {
36
47
  fs.utimesSync('foobarbaz', atime, mtime);
@@ -33,11 +33,13 @@ server
33
33
  .listen(port)
34
34
  .unref();
35
35
 
36
+ const baseUrl = 'http://localhost:' + port;
37
+
36
38
  await configureSingle({
37
39
  backend: Overlay,
38
40
  readable: Fetch.create({
39
- baseUrl: `http://localhost:${port}/`,
40
- index: '.index.json',
41
+ baseUrl,
42
+ index: baseUrl + '/.index.json',
41
43
  }),
42
44
  writable: InMemory.create({ name: 'cow' }),
43
45
  });