Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/platform/files/test/browser/fileService.test.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import assert from 'assert';
7
import { DeferredPromise, timeout } from '../../../../base/common/async.js';
8
import { bufferToReadable, bufferToStream, VSBuffer } from '../../../../base/common/buffer.js';
9
import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js';
10
import { Emitter, Event } from '../../../../base/common/event.js';
11
import { DisposableStore, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
12
import { isEqual } from '../../../../base/common/resources.js';
13
import { consumeStream, newWriteableStream, ReadableStreamEvents } from '../../../../base/common/stream.js';
14
import { URI } from '../../../../base/common/uri.js';
15
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
16
import { IFileOpenOptions, IFileReadStreamOptions, FileSystemProviderCapabilities, FileType, IFileSystemProviderCapabilitiesChangeEvent, IFileSystemProviderRegistrationEvent, IStat, IFileAtomicReadOptions, IFileAtomicWriteOptions, IFileAtomicDeleteOptions, IFileSystemProviderWithFileAtomicReadCapability, IFileSystemProviderWithFileAtomicDeleteCapability, IFileSystemProviderWithFileAtomicWriteCapability, IFileAtomicOptions, IFileChange, isFileSystemWatcher, FileChangesEvent, FileChangeType } from '../../common/files.js';
17
import { FileService } from '../../common/fileService.js';
18
import { NullFileSystemProvider } from '../common/nullFileSystemProvider.js';
19
import { NullLogService } from '../../../log/common/log.js';
20
21
suite('File Service', () => {
22
23
const disposables = new DisposableStore();
24
25
teardown(() => {
26
disposables.clear();
27
});
28
29
test('provider registration', async () => {
30
const service = disposables.add(new FileService(new NullLogService()));
31
const resource = URI.parse('test://foo/bar');
32
const provider = new NullFileSystemProvider();
33
34
assert.strictEqual(await service.canHandleResource(resource), false);
35
assert.strictEqual(service.hasProvider(resource), false);
36
assert.strictEqual(service.getProvider(resource.scheme), undefined);
37
38
const registrations: IFileSystemProviderRegistrationEvent[] = [];
39
disposables.add(service.onDidChangeFileSystemProviderRegistrations(e => {
40
registrations.push(e);
41
}));
42
43
const capabilityChanges: IFileSystemProviderCapabilitiesChangeEvent[] = [];
44
disposables.add(service.onDidChangeFileSystemProviderCapabilities(e => {
45
capabilityChanges.push(e);
46
}));
47
48
let registrationDisposable: IDisposable | undefined;
49
let callCount = 0;
50
disposables.add(service.onWillActivateFileSystemProvider(e => {
51
callCount++;
52
53
if (e.scheme === 'test' && callCount === 1) {
54
e.join(new Promise(resolve => {
55
registrationDisposable = service.registerProvider('test', provider);
56
57
resolve();
58
}));
59
}
60
}));
61
62
assert.strictEqual(await service.canHandleResource(resource), true);
63
assert.strictEqual(service.hasProvider(resource), true);
64
assert.strictEqual(service.getProvider(resource.scheme), provider);
65
66
assert.strictEqual(registrations.length, 1);
67
assert.strictEqual(registrations[0].scheme, 'test');
68
assert.strictEqual(registrations[0].added, true);
69
assert.ok(registrationDisposable);
70
71
assert.strictEqual(capabilityChanges.length, 0);
72
73
provider.setCapabilities(FileSystemProviderCapabilities.FileFolderCopy);
74
assert.strictEqual(capabilityChanges.length, 1);
75
provider.setCapabilities(FileSystemProviderCapabilities.Readonly);
76
assert.strictEqual(capabilityChanges.length, 2);
77
78
await service.activateProvider('test');
79
assert.strictEqual(callCount, 2); // activation is called again
80
81
assert.strictEqual(service.hasCapability(resource, FileSystemProviderCapabilities.Readonly), true);
82
assert.strictEqual(service.hasCapability(resource, FileSystemProviderCapabilities.FileOpenReadWriteClose), false);
83
84
registrationDisposable.dispose();
85
86
assert.strictEqual(await service.canHandleResource(resource), false);
87
assert.strictEqual(service.hasProvider(resource), false);
88
89
assert.strictEqual(registrations.length, 2);
90
assert.strictEqual(registrations[1].scheme, 'test');
91
assert.strictEqual(registrations[1].added, false);
92
});
93
94
test('watch', async () => {
95
const service = disposables.add(new FileService(new NullLogService()));
96
97
let disposeCounter = 0;
98
disposables.add(service.registerProvider('test', new NullFileSystemProvider(() => {
99
return toDisposable(() => {
100
disposeCounter++;
101
});
102
})));
103
await service.activateProvider('test');
104
105
const resource1 = URI.parse('test://foo/bar1');
106
const watcher1Disposable = service.watch(resource1);
107
108
await timeout(0); // service.watch() is async
109
assert.strictEqual(disposeCounter, 0);
110
watcher1Disposable.dispose();
111
assert.strictEqual(disposeCounter, 1);
112
113
disposeCounter = 0;
114
const resource2 = URI.parse('test://foo/bar2');
115
const watcher2Disposable1 = service.watch(resource2);
116
const watcher2Disposable2 = service.watch(resource2);
117
const watcher2Disposable3 = service.watch(resource2);
118
119
await timeout(0); // service.watch() is async
120
assert.strictEqual(disposeCounter, 0);
121
watcher2Disposable1.dispose();
122
assert.strictEqual(disposeCounter, 0);
123
watcher2Disposable2.dispose();
124
assert.strictEqual(disposeCounter, 0);
125
watcher2Disposable3.dispose();
126
assert.strictEqual(disposeCounter, 1);
127
128
disposeCounter = 0;
129
const resource3 = URI.parse('test://foo/bar3');
130
const watcher3Disposable1 = service.watch(resource3);
131
const watcher3Disposable2 = service.watch(resource3, { recursive: true, excludes: [] });
132
const watcher3Disposable3 = service.watch(resource3, { recursive: false, excludes: [], includes: [] });
133
134
await timeout(0); // service.watch() is async
135
assert.strictEqual(disposeCounter, 0);
136
watcher3Disposable1.dispose();
137
assert.strictEqual(disposeCounter, 1);
138
watcher3Disposable2.dispose();
139
assert.strictEqual(disposeCounter, 2);
140
watcher3Disposable3.dispose();
141
assert.strictEqual(disposeCounter, 3);
142
143
service.dispose();
144
});
145
146
test('watch - with corelation', async () => {
147
const service = disposables.add(new FileService(new NullLogService()));
148
149
const provider = new class extends NullFileSystemProvider {
150
private readonly _testOnDidChangeFile = new Emitter<readonly IFileChange[]>();
151
override readonly onDidChangeFile: Event<readonly IFileChange[]> = this._testOnDidChangeFile.event;
152
153
fireFileChange(changes: readonly IFileChange[]) {
154
this._testOnDidChangeFile.fire(changes);
155
}
156
};
157
158
disposables.add(service.registerProvider('test', provider));
159
await service.activateProvider('test');
160
161
const globalEvents: FileChangesEvent[] = [];
162
disposables.add(service.onDidFilesChange(e => {
163
globalEvents.push(e);
164
}));
165
166
const watcher0 = disposables.add(service.watch(URI.parse('test://watch/folder1'), { recursive: true, excludes: [], includes: [] }));
167
assert.strictEqual(isFileSystemWatcher(watcher0), false);
168
const watcher1 = disposables.add(service.watch(URI.parse('test://watch/folder2'), { recursive: true, excludes: [], includes: [], correlationId: 100 }));
169
assert.strictEqual(isFileSystemWatcher(watcher1), true);
170
const watcher2 = disposables.add(service.watch(URI.parse('test://watch/folder3'), { recursive: true, excludes: [], includes: [], correlationId: 200 }));
171
assert.strictEqual(isFileSystemWatcher(watcher2), true);
172
173
const watcher1Events: FileChangesEvent[] = [];
174
disposables.add(watcher1.onDidChange(e => {
175
watcher1Events.push(e);
176
}));
177
178
const watcher2Events: FileChangesEvent[] = [];
179
disposables.add(watcher2.onDidChange(e => {
180
watcher2Events.push(e);
181
}));
182
183
provider.fireFileChange([{ resource: URI.parse('test://watch/folder1'), type: FileChangeType.ADDED }]);
184
provider.fireFileChange([{ resource: URI.parse('test://watch/folder2'), type: FileChangeType.ADDED, cId: 100 }]);
185
provider.fireFileChange([{ resource: URI.parse('test://watch/folder2'), type: FileChangeType.ADDED, cId: 100 }]);
186
provider.fireFileChange([{ resource: URI.parse('test://watch/folder3/file'), type: FileChangeType.UPDATED, cId: 200 }]);
187
provider.fireFileChange([{ resource: URI.parse('test://watch/folder3'), type: FileChangeType.UPDATED, cId: 200 }]);
188
189
provider.fireFileChange([{ resource: URI.parse('test://watch/folder4'), type: FileChangeType.ADDED, cId: 50 }]);
190
provider.fireFileChange([{ resource: URI.parse('test://watch/folder4'), type: FileChangeType.ADDED, cId: 60 }]);
191
provider.fireFileChange([{ resource: URI.parse('test://watch/folder4'), type: FileChangeType.ADDED, cId: 70 }]);
192
193
assert.strictEqual(globalEvents.length, 1);
194
assert.strictEqual(watcher1Events.length, 2);
195
assert.strictEqual(watcher2Events.length, 2);
196
});
197
198
test('error from readFile bubbles through (https://github.com/microsoft/vscode/issues/118060) - async', async () => {
199
testReadErrorBubbles(true);
200
});
201
202
test('error from readFile bubbles through (https://github.com/microsoft/vscode/issues/118060)', async () => {
203
testReadErrorBubbles(false);
204
});
205
206
async function testReadErrorBubbles(async: boolean) {
207
const service = disposables.add(new FileService(new NullLogService()));
208
209
const provider = new class extends NullFileSystemProvider {
210
override async stat(resource: URI): Promise<IStat> {
211
return {
212
mtime: Date.now(),
213
ctime: Date.now(),
214
size: 100,
215
type: FileType.File
216
};
217
}
218
219
override readFile(resource: URI): Promise<Uint8Array> {
220
if (async) {
221
return timeout(5, CancellationToken.None).then(() => { throw new Error('failed'); });
222
}
223
224
throw new Error('failed');
225
}
226
227
override open(resource: URI, opts: IFileOpenOptions): Promise<number> {
228
if (async) {
229
return timeout(5, CancellationToken.None).then(() => { throw new Error('failed'); });
230
}
231
232
throw new Error('failed');
233
}
234
235
override readFileStream(resource: URI, opts: IFileReadStreamOptions, token: CancellationToken): ReadableStreamEvents<Uint8Array> {
236
if (async) {
237
const stream = newWriteableStream<Uint8Array>(chunk => chunk[0]);
238
timeout(5, CancellationToken.None).then(() => stream.error(new Error('failed')));
239
240
return stream;
241
242
}
243
244
throw new Error('failed');
245
}
246
};
247
248
disposables.add(service.registerProvider('test', provider));
249
250
for (const capabilities of [FileSystemProviderCapabilities.FileReadWrite, FileSystemProviderCapabilities.FileReadStream, FileSystemProviderCapabilities.FileOpenReadWriteClose]) {
251
provider.setCapabilities(capabilities);
252
253
let e1;
254
try {
255
await service.readFile(URI.parse('test://foo/bar'));
256
} catch (error) {
257
e1 = error;
258
}
259
260
assert.ok(e1);
261
262
let e2;
263
try {
264
const stream = await service.readFileStream(URI.parse('test://foo/bar'));
265
await consumeStream(stream.value, chunk => chunk[0]);
266
} catch (error) {
267
e2 = error;
268
}
269
270
assert.ok(e2);
271
}
272
}
273
274
test('readFile/readFileStream supports cancellation (https://github.com/microsoft/vscode/issues/138805)', async () => {
275
const service = disposables.add(new FileService(new NullLogService()));
276
277
let readFileStreamReady: DeferredPromise<void> | undefined = undefined;
278
279
const provider = new class extends NullFileSystemProvider {
280
281
override async stat(resource: URI): Promise<IStat> {
282
return {
283
mtime: Date.now(),
284
ctime: Date.now(),
285
size: 100,
286
type: FileType.File
287
};
288
}
289
290
override readFileStream(resource: URI, opts: IFileReadStreamOptions, token: CancellationToken): ReadableStreamEvents<Uint8Array> {
291
const stream = newWriteableStream<Uint8Array>(chunk => chunk[0]);
292
disposables.add(token.onCancellationRequested(() => {
293
stream.error(new Error('Expected cancellation'));
294
stream.end();
295
}));
296
297
readFileStreamReady!.complete();
298
299
return stream;
300
}
301
};
302
303
disposables.add(service.registerProvider('test', provider));
304
305
provider.setCapabilities(FileSystemProviderCapabilities.FileReadStream);
306
307
let e1;
308
try {
309
const cts = new CancellationTokenSource();
310
readFileStreamReady = new DeferredPromise();
311
const promise = service.readFile(URI.parse('test://foo/bar'), undefined, cts.token);
312
await Promise.all([readFileStreamReady.p.then(() => cts.cancel()), promise]);
313
} catch (error) {
314
e1 = error;
315
}
316
317
assert.ok(e1);
318
319
let e2;
320
try {
321
const cts = new CancellationTokenSource();
322
readFileStreamReady = new DeferredPromise();
323
const stream = await service.readFileStream(URI.parse('test://foo/bar'), undefined, cts.token);
324
await Promise.all([readFileStreamReady.p.then(() => cts.cancel()), consumeStream(stream.value, chunk => chunk[0])]);
325
} catch (error) {
326
e2 = error;
327
}
328
329
assert.ok(e2);
330
});
331
332
test('enforced atomic read/write/delete', async () => {
333
const service = disposables.add(new FileService(new NullLogService()));
334
335
const atomicResource = URI.parse('test://foo/bar/atomic');
336
const nonAtomicResource = URI.parse('test://foo/nonatomic');
337
338
let atomicReadCounter = 0;
339
let atomicWriteCounter = 0;
340
let atomicDeleteCounter = 0;
341
342
const provider = new class extends NullFileSystemProvider implements IFileSystemProviderWithFileAtomicReadCapability, IFileSystemProviderWithFileAtomicWriteCapability, IFileSystemProviderWithFileAtomicDeleteCapability {
343
344
override async stat(resource: URI): Promise<IStat> {
345
return {
346
type: FileType.File,
347
ctime: Date.now(),
348
mtime: Date.now(),
349
size: 0
350
};
351
}
352
353
override async readFile(resource: URI, opts?: IFileAtomicReadOptions): Promise<Uint8Array> {
354
if (opts?.atomic) {
355
atomicReadCounter++;
356
}
357
return new Uint8Array();
358
}
359
360
override readFileStream(resource: URI, opts: IFileReadStreamOptions, token: CancellationToken): ReadableStreamEvents<Uint8Array> {
361
return newWriteableStream<Uint8Array>(chunk => chunk[0]);
362
}
363
364
enforceAtomicReadFile(resource: URI): boolean {
365
return isEqual(resource, atomicResource);
366
}
367
368
override async writeFile(resource: URI, content: Uint8Array, opts: IFileAtomicWriteOptions): Promise<void> {
369
if (opts.atomic) {
370
atomicWriteCounter++;
371
}
372
}
373
374
enforceAtomicWriteFile(resource: URI): IFileAtomicOptions | false {
375
return isEqual(resource, atomicResource) ? { postfix: '.tmp' } : false;
376
}
377
378
override async delete(resource: URI, opts: IFileAtomicDeleteOptions): Promise<void> {
379
if (opts.atomic) {
380
atomicDeleteCounter++;
381
}
382
}
383
384
enforceAtomicDelete(resource: URI): IFileAtomicOptions | false {
385
return isEqual(resource, atomicResource) ? { postfix: '.tmp' } : false;
386
}
387
};
388
389
provider.setCapabilities(
390
FileSystemProviderCapabilities.FileReadWrite |
391
FileSystemProviderCapabilities.FileOpenReadWriteClose |
392
FileSystemProviderCapabilities.FileReadStream |
393
FileSystemProviderCapabilities.FileAtomicRead |
394
FileSystemProviderCapabilities.FileAtomicWrite |
395
FileSystemProviderCapabilities.FileAtomicDelete
396
);
397
398
disposables.add(service.registerProvider('test', provider));
399
400
await service.readFile(atomicResource);
401
await service.readFile(nonAtomicResource);
402
await service.readFileStream(atomicResource);
403
await service.readFileStream(nonAtomicResource);
404
405
await service.writeFile(atomicResource, VSBuffer.fromString(''));
406
await service.writeFile(nonAtomicResource, VSBuffer.fromString(''));
407
408
await service.writeFile(atomicResource, bufferToStream(VSBuffer.fromString('')));
409
await service.writeFile(nonAtomicResource, bufferToStream(VSBuffer.fromString('')));
410
411
await service.writeFile(atomicResource, bufferToReadable(VSBuffer.fromString('')));
412
await service.writeFile(nonAtomicResource, bufferToReadable(VSBuffer.fromString('')));
413
414
await service.del(atomicResource);
415
await service.del(nonAtomicResource);
416
417
assert.strictEqual(atomicReadCounter, 2);
418
assert.strictEqual(atomicWriteCounter, 3);
419
assert.strictEqual(atomicDeleteCounter, 1);
420
});
421
422
ensureNoDisposablesAreLeakedInTestSuite();
423
});
424
425