From 4e667d30987f32968a22efd5e97fd0f2ae4ecf64 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 29 Jan 2020 15:11:33 -0700 Subject: [PATCH 01/14] Add support for errno ENOTEMPTY. --- src/client/common/platform/errors.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/client/common/platform/errors.ts b/src/client/common/platform/errors.ts index 777d02b7430b..59156152b98f 100644 --- a/src/client/common/platform/errors.ts +++ b/src/client/common/platform/errors.ts @@ -78,6 +78,16 @@ interface ISystemError extends INodeJSError { port?: string; } +// Return a new error for errno ENOTEMPTY. +export function createDirNotEmptyError(dirname: string): ISystemError { + const err = new Error(`directory "${dirname}" not empty`) as ISystemError; + err.name = 'SystemError'; + err.code = 'ENOTEMPTY'; + err.path = dirname; + err.syscall = 'rmdir'; + return err; +} + function isSystemError(err: Error, expectedCode: string): boolean | undefined { const code = (err as ISystemError).code; if (!code) { @@ -130,3 +140,8 @@ export function isNoPermissionsError(err: Error): boolean | undefined { } return isSystemError(err, 'EACCES'); } + +// Return true if the given error is ENOTEMPTY. +export function isDirNotEmptyError(err: Error): boolean | undefined { + return isSystemError(err, 'ENOTEMPTY'); +} From 5bd3fd5bcf971ac2876154bfe8afa32d91fa07e2 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 29 Jan 2020 17:04:24 -0700 Subject: [PATCH 02/14] Use the VS Code FS API in RawFileSystem.*. --- src/client/common/platform/fileSystem.ts | 230 +++-- src/client/common/platform/types.ts | 2 + .../platform/filesystem.functional.test.ts | 814 +----------------- src/test/common/platform/filesystem.test.ts | 814 +++++++++++++++++- .../common/platform/filesystem.unit.test.ts | 421 +++++---- src/test/serviceRegistry.ts | 72 ++ 6 files changed, 1261 insertions(+), 1092 deletions(-) diff --git a/src/client/common/platform/fileSystem.ts b/src/client/common/platform/fileSystem.ts index 10e42aa63521..d9f8185d9a35 100644 --- a/src/client/common/platform/fileSystem.ts +++ b/src/client/common/platform/fileSystem.ts @@ -2,6 +2,8 @@ // Licensed under the MIT License. 'use strict'; +// tslint:disable:no-suspicious-comment + import { createHash } from 'crypto'; import * as fs from 'fs-extra'; import * as glob from 'glob'; @@ -10,8 +12,7 @@ import { promisify } from 'util'; import * as vscode from 'vscode'; import '../../common/extensions'; import { traceError } from '../logger'; -import { createDeferred } from '../utils/async'; -import { isFileNotFoundError, isNoPermissionsError } from './errors'; +import { createDirNotEmptyError, isFileExistsError, isFileNotFoundError, isNoPermissionsError } from './errors'; import { FileSystemPaths, FileSystemPathUtils } from './fs-paths'; import { TemporaryFileSystem } from './fs-temp'; import { @@ -84,23 +85,21 @@ function filterByFileType( // See: https://code.visualstudio.com/api/references/vscode-api#FileSystem // Note that we have used all the API functions *except* "rename()". interface IVSCodeFileSystemAPI { + copy(source: vscode.Uri, target: vscode.Uri, options?: { overwrite: boolean }): Thenable; + createDirectory(uri: vscode.Uri): Thenable; + delete(uri: vscode.Uri, options?: { recursive: boolean; useTrash: boolean }): Thenable; + readDirectory(uri: vscode.Uri): Thenable<[string, FileType][]>; + readFile(uri: vscode.Uri): Thenable; + rename(source: vscode.Uri, target: vscode.Uri, options?: { overwrite: boolean }): Thenable; stat(uri: vscode.Uri): Thenable; + writeFile(uri: vscode.Uri, content: Uint8Array): Thenable; } // This is the parts of the 'fs-extra' module that we use in RawFileSystem. interface IRawFSExtra { - stat(filename: string): Promise; lstat(filename: string): Promise; - readdir(dirname: string): Promise; - readFile(filename: string): Promise; - readFile(filename: string, encoding: string): Promise; - mkdirp(dirname: string): Promise; chmod(filePath: string, mode: string | number): Promise; - rename(src: string, tgt: string): Promise; - writeFile(filename: string, data: {}, options: {}): Promise; appendFile(filename: string, data: {}): Promise; - unlink(filename: string): Promise; - rmdir(dirname: string): Promise; // non-async readFileSync(path: string, encoding: string): string; @@ -109,6 +108,7 @@ interface IRawFSExtra { } interface IRawPath { + dirname(path: string): string; join(...paths: string[]): string; } @@ -148,6 +148,8 @@ export class RawFileSystem implements IRawFileSystem { } public async lstat(filename: string): Promise { + // TODO https://github.com/microsoft/vscode/issues/84175 + // This functionality has been requested for the VS Code API. const stat = await this.fsExtra.lstat(filename); // Note that, unlike stat(), lstat() does not include the type // of the symlink's target. @@ -157,130 +159,178 @@ export class RawFileSystem implements IRawFileSystem { public async chmod(filename: string, mode: string | number): Promise { return this.fsExtra.chmod(filename, mode); - } - - public async move(src: string, tgt: string) { - await this.fsExtra.rename(src, tgt); + // TODO https://github.com/microsoft/vscode/issues/84175 + // This functionality has been requested for the VS Code API. + } + + public async move(src: string, tgt: string): Promise { + const srcUri = vscode.Uri.file(src); + const tgtUri = vscode.Uri.file(tgt); + // TODO (https://github.com/microsoft/vscode/issues/84177) + // The docs say "throws - FileNotFound when parent of + // destination doesn't exist". However, it happily creates + // the parent directory (at least for remote-over-SSH). + // So we have to manually stat, just to be sure. + await this.vscfs.stat(vscode.Uri.file(this.paths.dirname(tgt))); + // We stick with the pre-existing behavior where files are + // overwritten and directories are not. + const options = { overwrite: false }; + try { + await this.vscfs.rename(srcUri, tgtUri, options); + } catch (err) { + if (!isFileExistsError(err)) { + throw err; // re-throw + } + const stat = await this.vscfs.stat(tgtUri); + if (stat.type === FileType.Directory) { + throw err; // re-throw + } + options.overwrite = true; + await this.vscfs.rename(srcUri, tgtUri, options); + } } public async readData(filename: string): Promise { - return this.fsExtra.readFile(filename); + const uri = vscode.Uri.file(filename); + const data = await this.vscfs.readFile(uri); + return Buffer.from(data); } public async readText(filename: string): Promise { - return this.fsExtra.readFile(filename, ENCODING); + const uri = vscode.Uri.file(filename); + const result = await this.vscfs.readFile(uri); + const data = Buffer.from(result); + return data.toString(ENCODING); } public async writeText(filename: string, text: string): Promise { - await this.fsExtra.writeFile(filename, text, { encoding: ENCODING }); + const uri = vscode.Uri.file(filename); + const data = Buffer.from(text); + await this.vscfs.writeFile(uri, data); } public async appendText(filename: string, text: string): Promise { + // TODO: We *could* use the new API for this (read, concat, write)... return this.fsExtra.appendFile(filename, text); } public async copyFile(src: string, dest: string): Promise { - const deferred = createDeferred(); - // prettier-ignore - const rs = this.fsExtra.createReadStream(src) - .on('error', err => { - deferred.reject(err); - }); - // prettier-ignore - const ws = this.fsExtra.createWriteStream(dest) - .on('error', err => { - deferred.reject(err); - }) - .on('close', () => { - deferred.resolve(); - }); - rs.pipe(ws); - return deferred.promise; + const srcURI = vscode.Uri.file(src); + const destURI = vscode.Uri.file(dest); + // TODO (https://github.com/microsoft/vscode/issues/84177) + // The docs say "throws - FileNotFound when parent of + // destination doesn't exist". However, it happily creates + // the parent directory (at least for remote-over-SSH). + // So we have to manually stat, just to be sure. + await this.vscfs.stat(vscode.Uri.file(this.paths.dirname(dest))); + await this.vscfs.copy(srcURI, destURI, { + overwrite: true + }); } public async rmfile(filename: string): Promise { - return this.fsExtra.unlink(filename); + const uri = vscode.Uri.file(filename); + return this.vscfs.delete(uri, { + recursive: false, + useTrash: false + }); + } + + public async rmdir(dirname: string): Promise { + const uri = vscode.Uri.file(dirname); + // TODO (https://github.com/microsoft/vscode/issues/84177) + // The docs say "throws - FileNotFound when uri doesn't exist". + // However, it happily does nothing (at least for remote-over-SSH). + // So we have to (effectively) manually stat, just to be sure. + // TODO (https://github.com/microsoft/vscode/issues/84177) + // The "recursive" option disallows directories, even if they + // are empty. So we have to deal with this ourselves. + const files = await this.vscfs.readDirectory(uri); + if (files && files.length > 0) { + throw createDirNotEmptyError(dirname); + } + return this.vscfs.delete(uri, { + recursive: true, + useTrash: false + }); } public async rmtree(dirname: string): Promise { - return this.fsExtra.rmdir(dirname); + const uri = vscode.Uri.file(dirname); + // TODO (https://github.com/microsoft/vscode/issues/84177) + // The docs say "throws - FileNotFound when uri doesn't exist". + // However, it happily does nothing (at least for remote-over-SSH). + // So we have to manually stat, just to be sure. + await this.vscfs.stat(uri); + return this.vscfs.delete(uri, { + recursive: true, + useTrash: false + }); } public async mkdirp(dirname: string): Promise { - return this.fsExtra.mkdirp(dirname); + // TODO https://github.com/microsoft/vscode/issues/84175 + // Hopefully VS Code provides this method in their API + // so we don't have to roll our own. + const stack = [dirname]; + while (stack.length > 0) { + const current = stack.pop() || ''; + const uri = vscode.Uri.file(current); + try { + await this.vscfs.createDirectory(uri); + } catch (err) { + if (isFileExistsError(err)) { + // already done! + return; + } + // According to the docs, FileNotFound means the parent + // does not exist. + // See: https://code.visualstudio.com/api/references/vscode-api#FileSystemProvider + if (!isFileNotFoundError(err)) { + // Fail for anything else. + throw err; // re-throw + } + // Try creating the parent first. + const parent = this.paths.dirname(current); + if (parent === '' || parent === current) { + // This shouldn't ever happen. + throw err; + } + stack.push(current); + stack.push(parent); + } + } } public async listdir(dirname: string): Promise<[string, FileType][]> { - const files = await this.fsExtra.readdir(dirname); - const promises = files.map(async basename => { + const uri = vscode.Uri.file(dirname); + const files = await this.vscfs.readDirectory(uri); + return files.map(([basename, filetype]) => { const filename = this.paths.join(dirname, basename); - let fileType: FileType; - try { - // Note that getFileType() follows symlinks (while still - // preserving the Symlink flag). - fileType = await this.getFileType(filename); - } catch (err) { - traceError(`failure while getting file type for "${filename}"`, err); - fileType = FileType.Unknown; - } - return [filename, fileType] as [string, FileType]; + return [filename, filetype] as [string, FileType]; }); - return Promise.all(promises); } //**************************** // non-async public readTextSync(filename: string): string { + // TODO https://github.com/microsoft/vscode/issues/84175 + // This functionality has been requested for the VS Code API. return this.fsExtra.readFileSync(filename, ENCODING); } public createReadStream(filename: string): ReadStream { + // TODO https://github.com/microsoft/vscode/issues/84175 + // This functionality has been requested for the VS Code API. return this.fsExtra.createReadStream(filename); } public createWriteStream(filename: string): WriteStream { + // TODO https://github.com/microsoft/vscode/issues/84175 + // This functionality has been requested for the VS Code API. return this.fsExtra.createWriteStream(filename); } - - //**************************** - // internal - - private async getFileType(filename: string): Promise { - let stat: fs.Stats; - try { - // Note that we used to use stat() here instead of lstat(). - // This shouldn't matter because the only consumers were - // internal methods that have been updated appropriately. - stat = await this.fsExtra.lstat(filename); - } catch (err) { - if (isFileNotFoundError(err)) { - return FileType.Unknown; - } - throw err; - } - if (!stat.isSymbolicLink()) { - return convertFileType(stat); - } - - // For symlinks we emulate the behavior of the vscode.workspace.fs API. - // See: https://code.visualstudio.com/api/references/vscode-api#FileType - try { - stat = await this.fsExtra.stat(filename); - } catch (err) { - if (isFileNotFoundError(err)) { - return FileType.SymbolicLink; - } - throw err; - } - if (stat.isFile()) { - return FileType.SymbolicLink | FileType.File; - } else if (stat.isDirectory()) { - return FileType.SymbolicLink | FileType.Directory; - } else { - return FileType.SymbolicLink; - } - } } //========================================== @@ -335,7 +385,7 @@ export class FileSystemUtils implements IFileSystemUtils { } public async deleteDirectory(directoryPath: string): Promise { - return this.raw.rmtree(directoryPath); + return this.raw.rmdir(directoryPath); } public async deleteFile(filename: string): Promise { diff --git a/src/client/common/platform/types.ts b/src/client/common/platform/types.ts index 488d8b1e9a0e..ed2bc5183088 100644 --- a/src/client/common/platform/types.ts +++ b/src/client/common/platform/types.ts @@ -126,6 +126,8 @@ export interface IRawFileSystem { // Create the directory and any missing parent directories. mkdirp(dirname: string): Promise; + // Delete the directory if empty. + rmdir(dirname: string): Promise; // Delete the directory and everything in it. rmtree(dirname: string): Promise; // Return the contents of the directory. diff --git a/src/test/common/platform/filesystem.functional.test.ts b/src/test/common/platform/filesystem.functional.test.ts index fda385686562..0df300f75603 100644 --- a/src/test/common/platform/filesystem.functional.test.ts +++ b/src/test/common/platform/filesystem.functional.test.ts @@ -5,14 +5,13 @@ import { expect, use } from 'chai'; import * as fs from 'fs-extra'; -import * as path from 'path'; import { convertStat, FileSystem, FileSystemUtils, RawFileSystem } from '../../../client/common/platform/fileSystem'; import { FileSystemPaths, FileSystemPathUtils } from '../../../client/common/platform/fs-paths'; import { FileType } from '../../../client/common/platform/types'; import { sleep } from '../../../client/common/utils/async'; // prettier-ignore import { - assertDoesNotExist, assertExists, DOES_NOT_EXIST, + assertDoesNotExist, DOES_NOT_EXIST, fixPath, FSFixture, OSX, SUPPORTS_SOCKETS, SUPPORTS_SYMLINKS, WINDOWS } from './utils'; @@ -135,253 +134,6 @@ suite('FileSystem - raw', () => { }); }); - suite('move', () => { - test('rename file', async () => { - const source = await fix.createFile('spam.py', ''); - const target = await fix.resolve('eggs-txt'); - await assertDoesNotExist(target); - - await fileSystem.move(source, target); - - await assertExists(target); - const text = await fs.readFile(target, 'utf8'); - expect(text).to.equal(''); - await assertDoesNotExist(source); - }); - - test('rename directory', async () => { - const source = await fix.createDirectory('spam'); - await fix.createFile('spam/data.json', ''); - const target = await fix.resolve('eggs'); - const filename = await fix.resolve('eggs/data.json', false); - await assertDoesNotExist(target); - - await fileSystem.move(source, target); - - await assertExists(filename); - const text = await fs.readFile(filename, 'utf8'); - expect(text).to.equal(''); - await assertDoesNotExist(source); - }); - - test('rename symlink', async function() { - if (!SUPPORTS_SYMLINKS) { - // tslint:disable-next-line:no-invalid-this - this.skip(); - } - const filename = await fix.createFile('spam.py'); - const symlink = await fix.createSymlink('spam.lnk', filename); - const target = await fix.resolve('eggs'); - await assertDoesNotExist(target); - - await fileSystem.move(symlink, target); - - await assertExists(target); - const linked = await fs.readlink(target); - expect(linked).to.equal(filename); - await assertDoesNotExist(symlink); - }); - - test('move file', async () => { - const source = await fix.createFile('spam.py', ''); - await fix.createDirectory('eggs'); - const target = await fix.resolve('eggs/spam.py'); - await assertDoesNotExist(target); - - await fileSystem.move(source, target); - - await assertExists(target); - const text = await fs.readFile(target, 'utf8'); - expect(text).to.equal(''); - await assertDoesNotExist(source); - }); - - test('move directory', async () => { - const source = await fix.createDirectory('spam/spam/spam/eggs/spam'); - await fix.createFile('spam/spam/spam/eggs/spam/data.json', ''); - await fix.createDirectory('spam/spam/spam/hash'); - const target = await fix.resolve('spam/spam/spam/hash/spam'); - const filename = await fix.resolve('spam/spam/spam/hash/spam/data.json', false); - await assertDoesNotExist(target); - - await fileSystem.move(source, target); - - await assertExists(filename); - const text = await fs.readFile(filename, 'utf8'); - expect(text).to.equal(''); - await assertDoesNotExist(source); - }); - - test('move symlink', async function() { - if (!SUPPORTS_SYMLINKS) { - // tslint:disable-next-line:no-invalid-this - this.skip(); - } - const filename = await fix.createFile('spam.py'); - const symlink = await fix.createSymlink('w/spam.lnk', filename); - const target = await fix.resolve('x/spam.lnk'); - await assertDoesNotExist(target); - - await fileSystem.move(symlink, target); - - await assertExists(target); - const linked = await fs.readlink(target); - expect(linked).to.equal(filename); - await assertDoesNotExist(symlink); - }); - - test('file target already exists', async () => { - const source = await fix.createFile('spam.py', ''); - const target = await fix.createFile('eggs-txt', ''); - - await fileSystem.move(source, target); - - await assertDoesNotExist(source); - await assertExists(target); - const text2 = await fs.readFile(target, 'utf8'); - expect(text2).to.equal(''); - }); - - test('directory target already exists', async () => { - const source = await fix.createDirectory('spam'); - const file3 = await fix.createFile('spam/data.json', ''); - const target = await fix.createDirectory('eggs'); - const file1 = await fix.createFile('eggs/spam.py', ''); - const file2 = await fix.createFile('eggs/data.json', ''); - - const promise = fileSystem.move(source, target); - - await expect(promise).to.eventually.be.rejected; - // Make sure nothing changed. - const text1 = await fs.readFile(file1, 'utf8'); - expect(text1).to.equal(''); - const text2 = await fs.readFile(file2, 'utf8'); - expect(text2).to.equal(''); - const text3 = await fs.readFile(file3, 'utf8'); - expect(text3).to.equal(''); - }); - - test('fails if the file does not exist', async () => { - const source = await fix.resolve(DOES_NOT_EXIST); - const target = await fix.resolve('spam.py'); - - const promise = fileSystem.move(source, target); - - await expect(promise).to.eventually.be.rejected; - // Make sure nothing changed. - await assertDoesNotExist(target); - }); - - test('fails if the target directory does not exist', async () => { - const source = await fix.createFile('x/spam.py', ''); - const target = await fix.resolve('w/spam.py', false); - await assertDoesNotExist(path.dirname(target)); - - const promise = fileSystem.move(source, target); - - await expect(promise).to.eventually.be.rejected; - // Make sure nothing changed. - await assertExists(source); - await assertDoesNotExist(target); - }); - }); - - suite('readData', () => { - test('returns contents of a file', async () => { - const text = ''; - const expected = Buffer.from(text, 'utf8'); - const filename = await fix.createFile('x/y/z/spam.py', text); - - const content = await fileSystem.readData(filename); - - expect(content).to.deep.equal(expected); - }); - - test('throws an exception if file does not exist', async () => { - const promise = fileSystem.readData(DOES_NOT_EXIST); - - await expect(promise).to.eventually.be.rejected; - }); - }); - - suite('readText', () => { - test('returns contents of a file', async () => { - const expected = ''; - const filename = await fix.createFile('x/y/z/spam.py', expected); - - const content = await fileSystem.readText(filename); - - expect(content).to.be.equal(expected); - }); - - test('always UTF-8', async () => { - const expected = '... 😁 ...'; - const filename = await fix.createFile('x/y/z/spam.py', expected); - - const text = await fileSystem.readText(filename); - - expect(text).to.equal(expected); - }); - - test('returns garbage if encoding is UCS-2', async () => { - const filename = await fix.resolve('spam.py'); - // There are probably cases where this would fail too. - // However, the extension never has to deal with non-UTF8 - // cases, so it doesn't matter too much. - const original = '... 😁 ...'; - await fs.writeFile(filename, original, { encoding: 'ucs2' }); - - const text = await fileSystem.readText(filename); - - expect(text).to.equal('.\u0000.\u0000.\u0000 \u0000=�\u0001� \u0000.\u0000.\u0000.\u0000'); - }); - - test('throws an exception if file does not exist', async () => { - const promise = fileSystem.readText(DOES_NOT_EXIST); - - await expect(promise).to.eventually.be.rejected; - }); - }); - - suite('writeText', () => { - test('creates the file if missing', async () => { - const filename = await fix.resolve('x/y/z/spam.py'); - await assertDoesNotExist(filename); - const data = 'line1\nline2\n'; - - await fileSystem.writeText(filename, data); - - // prettier-ignore - const actual = await fs.readFile(filename) - .then(buffer => buffer.toString()); - expect(actual).to.equal(data); - }); - - test('always UTF-8', async () => { - const filename = await fix.resolve('x/y/z/spam.py'); - const data = '... 😁 ...'; - - await fileSystem.writeText(filename, data); - - // prettier-ignore - const actual = await fs.readFile(filename) - .then(buffer => buffer.toString()); - expect(actual).to.equal(data); - }); - - test('overwrites existing file', async () => { - const filename = await fix.createFile('x/y/z/spam.py', '...'); - const data = 'line1\nline2\n'; - - await fileSystem.writeText(filename, data); - - // prettier-ignore - const actual = await fs.readFile(filename) - .then(buffer => buffer.toString()); - expect(actual).to.equal(data); - }); - }); - suite('appendText', () => { test('existing file', async () => { const orig = 'spamspamspam\n\n'; @@ -422,289 +174,6 @@ suite('FileSystem - raw', () => { }); }); - suite('copyFile', () => { - test('the source file gets copied (same directory)', async () => { - const data = ''; - const src = await fix.createFile('x/y/z/spam.py', data); - const dest = await fix.resolve('x/y/z/spam.py.bak'); - await assertDoesNotExist(dest); - - await fileSystem.copyFile(src, dest); - - // prettier-ignore - const actual = await fs.readFile(dest) - .then(buffer => buffer.toString()); - expect(actual).to.equal(data); - // prettier-ignore - const original = await fs.readFile(src) - .then(buffer => buffer.toString()); - expect(original).to.equal(data); - }); - - test('the source file gets copied (different directory)', async () => { - const data = ''; - const src = await fix.createFile('x/y/z/spam.py', data); - const dest = await fix.resolve('x/y/eggs.py'); - await assertDoesNotExist(dest); - - await fileSystem.copyFile(src, dest); - - // prettier-ignore - const actual = await fs.readFile(dest) - .then(buffer => buffer.toString()); - expect(actual).to.equal(data); - // prettier-ignore - const original = await fs.readFile(src) - .then(buffer => buffer.toString()); - expect(original).to.equal(data); - }); - - test('fails if the source does not exist', async () => { - const dest = await fix.resolve('x/spam.py'); - - const promise = fileSystem.copyFile(DOES_NOT_EXIST, dest); - - await expect(promise).to.eventually.be.rejected; - }); - - test('fails if the target parent directory does not exist', async () => { - const src = await fix.createFile('x/spam.py', '...'); - const dest = await fix.resolve('y/eggs.py', false); - await assertDoesNotExist(path.dirname(dest)); - - const promise = fileSystem.copyFile(src, dest); - - await expect(promise).to.eventually.be.rejected; - }); - }); - - suite('rmfile', () => { - test('deletes the file', async () => { - const filename = await fix.createFile('x/y/z/spam.py', '...'); - await assertExists(filename); - - await fileSystem.rmfile(filename); - - await assertDoesNotExist(filename); - }); - - test('fails if the file does not exist', async () => { - const promise = fileSystem.rmfile(DOES_NOT_EXIST); - - await expect(promise).to.eventually.be.rejected; - }); - }); - - suite('rmtree', () => { - test('deletes the directory if empty', async () => { - const dirname = await fix.createDirectory('x'); - await assertExists(dirname); - - await fileSystem.rmtree(dirname); - - await assertDoesNotExist(dirname); - }); - - test('fails if the directory is not empty', async () => { - const dirname = await fix.createDirectory('x'); - const filename = await fix.createFile('x/y/z/spam.py'); - await assertExists(filename); - - const promise = fileSystem.rmtree(dirname); - - await expect(promise).to.eventually.be.rejected; - }); - - test('fails if the directory does not exist', async () => { - const promise = fileSystem.rmtree(DOES_NOT_EXIST); - - await expect(promise).to.eventually.be.rejected; - }); - }); - - suite('mkdirp', () => { - test('creates the directory and all missing parents', async () => { - await fix.createDirectory('x'); - // x/y, x/y/z, and x/y/z/spam are all missing. - const dirname = await fix.resolve('x/y/z/spam', false); - await assertDoesNotExist(dirname); - - await fileSystem.mkdirp(dirname); - - await assertExists(dirname); - }); - - test('works if the directory already exists', async () => { - const dirname = await fix.createDirectory('spam'); - await assertExists(dirname); - - await fileSystem.mkdirp(dirname); - - await assertExists(dirname); - }); - }); - - suite('listdir', () => { - setup(function() { - if (WINDOWS) { - // tslint:disable-next-line:no-suspicious-comment - // TODO(GH-8995) These tests are failing on Windows, - // so we are // temporarily disabling it. - // tslint:disable-next-line:no-invalid-this - return this.skip(); - } - }); - if (SUPPORTS_SYMLINKS) { - test('mixed', async () => { - // Create the target directory and its contents. - const dirname = await fix.createDirectory('x/y/z'); - const file1 = await fix.createFile('x/y/z/__init__.py', ''); - const script = await fix.createFile('x/y/z/__main__.py', '