Skip to content

Commit c072129

Browse files
Work around upstream bugs in the new "fs" API.
1 parent a247a23 commit c072129

File tree

3 files changed

+64
-30
lines changed

3 files changed

+64
-30
lines changed

src/client/common/platform/fileSystem.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,8 @@ export class RawFileSystem implements IRawFileSystem {
174174

175175
public async rmtree(dirname: string): Promise<void> {
176176
const uri = vscode.Uri.file(dirname);
177+
// XXX The docs say "throws - FileNotFound when uri doesn't exist".
178+
await this.newapi.stat(uri); // Force a failure if missing.
177179
return this.newapi.delete(uri, {
178180
recursive: true,
179181
useTrash: false
@@ -232,6 +234,9 @@ export class RawFileSystem implements IRawFileSystem {
232234
public async copyFile(src: string, dest: string): Promise<void> {
233235
const srcURI = vscode.Uri.file(src);
234236
const destURI = vscode.Uri.file(dest);
237+
// XXX The docs say "throws - FileNotFound when parent of
238+
// destination doesn't exist".
239+
await this.newapi.stat(vscode.Uri.file(this.path.dirname(dest))); // Force a failure if missing.
235240
await this.newapi.copy(srcURI, destURI, {
236241
overwrite: true
237242
});

src/client/extension.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,12 @@ export async function activate(context: ExtensionContext): Promise<IExtensionApi
124124

125125
// tslint:disable-next-line:max-func-body-length
126126
async function activateUnsafe(context: ExtensionContext): Promise<IExtensionApi> {
127+
const vscode = require('vscode');
128+
await vscode.workspace.fs.copy(
129+
vscode.Uri.file('/tmp/spam.py'),
130+
vscode.Uri.file('/tmp/__does_not_exist__/spam.py')
131+
);
132+
127133
displayProgress(activationDeferred.promise);
128134
durations.startActivateTime = stopWatch.elapsedTime;
129135
const cont = new Container();

src/test/common/platform/filesystem.unit.test.ts

Lines changed: 53 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,28 @@ import {
1717

1818
// tslint:disable:max-func-body-length chai-vague-errors
1919

20+
function createMockStat(): TypeMoq.IMock<FileStat> {
21+
const stat = TypeMoq.Mock.ofType<FileStat>(undefined, TypeMoq.MockBehavior.Strict);
22+
// This is necessary because passing "mock.object" to
23+
// Promise.resolve() triggers the lookup.
24+
//tslint:disable-next-line:no-any
25+
stat.setup((s: any) => s.then)
26+
.returns(() => undefined)
27+
.verifiable(TypeMoq.Times.atLeast(0));
28+
return stat;
29+
}
30+
31+
function createMockLegacyStat(): TypeMoq.IMock<fsextra.Stats> {
32+
const stat = TypeMoq.Mock.ofType<fsextra.Stats>(undefined, TypeMoq.MockBehavior.Strict);
33+
// This is necessary because passing "mock.object" to
34+
// Promise.resolve() triggers the lookup.
35+
//tslint:disable-next-line:no-any
36+
stat.setup((s: any) => s.then)
37+
.returns(() => undefined)
38+
.verifiable(TypeMoq.Times.atLeast(0));
39+
return stat;
40+
}
41+
2042
//tslint:disable-next-line:no-any
2143
type TempCallback = (err: any, path: string, fd: number, cleanupCallback: () => void) => void;
2244
interface IRawFS {
@@ -251,48 +273,30 @@ suite('FileSystem paths', () => {
251273

252274
suite('Raw FileSystem', () => {
253275
let raw: TypeMoq.IMock<IRawFS>;
254-
//let stat: TypeMoq.IMock<FileStat>;
255276
let oldStat: TypeMoq.IMock<fsextra.Stats>;
256277
let filesystem: IRawFileSystem;
257278
setup(() => {
258279
raw = TypeMoq.Mock.ofType<IRawFS>(undefined, TypeMoq.MockBehavior.Strict);
259-
//stat = TypeMoq.Mock.ofType<FileStat>(undefined, TypeMoq.MockBehavior.Strict);
260-
oldStat = TypeMoq.Mock.ofType<fsextra.Stats>(undefined, TypeMoq.MockBehavior.Strict);
280+
oldStat = createMockLegacyStat();
261281
filesystem = new RawFileSystem(
262-
//raw.object,
263282
raw.object,
264283
raw.object,
265284
raw.object,
266285
raw.object
267286
);
268-
269-
// This is necessary because passing "stat.object" to
270-
// Promise.resolve() triggers the lookup.
271-
//tslint:disable-next-line:no-any
272-
oldStat.setup((s: any) => s.then)
273-
.returns(() => undefined)
274-
.verifiable(TypeMoq.Times.atLeast(0));
275287
});
276288
function verifyAll() {
277289
raw.verifyAll();
278-
//stat.verifyAll();
279290
oldStat.verifyAll();
280291
}
281292

282293
function setupOldStat(stat: FileStat, old?: TypeMoq.IMock<fsextra.Stats> | null) {
283294
if (old === undefined) {
284295
old = oldStat;
285296
} else if (old === null) {
286-
old = TypeMoq.Mock.ofType<fsextra.Stats>(undefined, TypeMoq.MockBehavior.Strict);
297+
old = createMockLegacyStat();
287298
}
288299

289-
// This is necessary because passing "stat.object" to
290-
// Promise.resolve() triggers the lookup.
291-
//tslint:disable-next-line:no-any
292-
old!.setup((s: any) => s.then)
293-
.returns(() => undefined)
294-
.verifiable(TypeMoq.Times.atLeast(0));
295-
296300
if (stat.type === FileType.File) {
297301
old!.setup(s => s.isFile())
298302
.returns(() => true);
@@ -419,22 +423,28 @@ suite('Raw FileSystem', () => {
419423
suite('rmtree', () => {
420424
test('wraps the low-level function', async () => {
421425
const dirname = 'x/y/z/spam';
422-
raw.setup(r => r.delete(Uri.file(dirname), { recursive: true, useTrash: false }))
426+
const uri = Uri.file(dirname);
427+
const stat = createMockStat();
428+
raw.setup(r => r.stat(uri))
429+
.returns(() => Promise.resolve(stat.object));
430+
raw.setup(r => r.delete(uri, { recursive: true, useTrash: false }))
423431
.returns(() => Promise.resolve());
424432

425433
await filesystem.rmtree(dirname);
426434

427435
verifyAll();
436+
stat.verifyAll();
428437
});
429438

430439
test('fails if the directory does not exist', async () => {
431440
const dirname = 'x/y/z/spam';
432-
raw.setup(r => r.delete(Uri.file(dirname), { recursive: true, useTrash: false }))
441+
raw.setup(r => r.stat(Uri.file(dirname)))
433442
.throws(new Error('file not found'));
434443

435444
const promise = filesystem.rmtree(dirname);
436445

437446
await expect(promise).to.eventually.be.rejected;
447+
verifyAll();
438448
});
439449
});
440450

@@ -545,12 +555,32 @@ suite('Raw FileSystem', () => {
545555
test('read/write streams are used properly', async () => {
546556
const src = 'x/y/z/spam.py';
547557
const dest = 'x/y/z/spam.py.bak';
558+
raw.setup(r => r.dirname(dest))
559+
.returns(() => 'x/y/z');
560+
const stat = createMockStat();
561+
raw.setup(r => r.stat(Uri.file('x/y/z')))
562+
.returns(() => Promise.resolve(stat.object));
548563
raw.setup(r => r.copy(Uri.file(src), Uri.file(dest), { overwrite: true }))
549564
.returns(() => Promise.resolve());
550565

551566
await filesystem.copyFile(src, dest);
552567

553568
verifyAll();
569+
stat.verifyAll();
570+
});
571+
572+
test('fails if the parent directory does not exist', async () => {
573+
const src = '/tmp/spam.py';
574+
const dest = '/tmp/__does_not_exist__/spam.py';
575+
raw.setup(r => r.dirname(dest))
576+
.returns(() => '/tmp/__does_not_exist__');
577+
raw.setup(r => r.stat(Uri.file('/tmp/__does_not_exist__')))
578+
.throws(new Error('file not found'));
579+
580+
const promise = filesystem.copyFile(src, dest);
581+
582+
await expect(promise).to.eventually.be.rejected;
583+
verifyAll();
554584
});
555585
});
556586

@@ -630,7 +660,7 @@ suite('FileSystem Utils', () => {
630660
let deps: TypeMoq.IMock<IDeps>;
631661
let utils: IFileSystemUtils;
632662
setup(() => {
633-
stat = TypeMoq.Mock.ofType<FileStat>(undefined, TypeMoq.MockBehavior.Strict);
663+
stat = createMockStat();
634664
filesystem = TypeMoq.Mock.ofType<IRawFileSystem>(undefined, TypeMoq.MockBehavior.Strict);
635665
path = TypeMoq.Mock.ofType<IFileSystemPath>(undefined, TypeMoq.MockBehavior.Strict);
636666
tmp = TypeMoq.Mock.ofType<ITempFileSystem>(undefined, TypeMoq.MockBehavior.Strict);
@@ -642,13 +672,6 @@ suite('FileSystem Utils', () => {
642672
((data: string) => deps.object.getHashString(data)),
643673
((p, cb) => deps.object.glob(p, cb))
644674
);
645-
646-
// This is necessary because passing "stat.object" to
647-
// Promise.resolve() triggers the lookup.
648-
//tslint:disable-next-line:no-any
649-
stat.setup((s: any) => s.then)
650-
.returns(() => undefined)
651-
.verifiable(TypeMoq.Times.atLeast(0));
652675
});
653676
function verifyAll() {
654677
filesystem.verifyAll();

0 commit comments

Comments
 (0)