diff --git a/extensions/vscode-notebook-tests/src/notebook.test.ts b/extensions/vscode-notebook-tests/src/notebook.test.ts index 9061f6e472a..ad9e518c74f 100644 --- a/extensions/vscode-notebook-tests/src/notebook.test.ts +++ b/extensions/vscode-notebook-tests/src/notebook.test.ts @@ -6,7 +6,7 @@ import 'mocha'; import * as assert from 'assert'; import * as vscode from 'vscode'; -import { join } from 'path'; +import { createRandomFile } from './utils'; export function timeoutAsync(n: number): Promise { return new Promise(resolve => { @@ -85,19 +85,11 @@ async function saveAllFilesAndCloseAll(resource: vscode.Uri) { } function assertInitalState() { - if (vscode.notebook.activeNotebookEditor !== undefined) { - return false; - } + // no-op unless we figure out why some documents are opened after the editor is closed - if (vscode.notebook.notebookDocuments.length !== 0) { - return false; - } - - if (vscode.notebook.visibleNotebookEditors.length !== 0) { - return false; - } - - return true; + // assert.equal(vscode.notebook.activeNotebookEditor, undefined); + // assert(vscode.notebook.notebookDocuments.length, 0); + // assert.equal(vscode.notebook.visibleNotebookEditors.length, 0); } suite('Notebook API tests', () => { @@ -127,10 +119,9 @@ suite('Notebook API tests', () => { // }); test('document open/close event', async function () { - if (!assertInitalState()) { - return; - } - const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + assertInitalState(); + + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); const firstDocumentOpen = getEventOncePromise(vscode.notebook.onDidOpenNotebookDocument); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await firstDocumentOpen; @@ -141,10 +132,9 @@ suite('Notebook API tests', () => { }); test('shared document in notebook editors', async function () { - if (!assertInitalState()) { - return; - } - const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + assertInitalState(); + + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); let counter = 0; const disposables: vscode.Disposable[] = []; disposables.push(vscode.notebook.onDidOpenNotebookDocument(() => { @@ -165,10 +155,9 @@ suite('Notebook API tests', () => { }); test('editor open/close event', async function () { - if (!assertInitalState()) { - return; - } - const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + assertInitalState(); + + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); const firstEditorOpen = getEventOncePromise(vscode.notebook.onDidChangeVisibleNotebookEditors); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await firstEditorOpen; @@ -179,10 +168,9 @@ suite('Notebook API tests', () => { }); test('editor open/close event 2', async function () { - if (!assertInitalState()) { - return; - } - const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + assertInitalState(); + + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); let count = 0; const disposables: vscode.Disposable[] = []; disposables.push(vscode.notebook.onDidChangeVisibleNotebookEditors(() => { @@ -200,10 +188,9 @@ suite('Notebook API tests', () => { }); test('editor editing event 2', async function () { - if (!assertInitalState()) { - return; - } - const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + assertInitalState(); + + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); const cellsChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeNotebookCells); @@ -275,10 +262,8 @@ suite('Notebook API tests', () => { }); test('editor move cell event', async function () { - if (!assertInitalState()) { - return; - } - const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); @@ -319,10 +304,8 @@ suite('Notebook API tests', () => { }); test('notebook editor active/visible', async function () { - if (!assertInitalState()) { - return; - } - const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); const firstEditor = vscode.notebook.activeNotebookEditor; assert.equal(firstEditor?.active, true); @@ -357,10 +340,8 @@ suite('Notebook API tests', () => { }); test('notebook active editor change', async function () { - if (!assertInitalState()) { - return; - } - const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); const firstEditorOpen = getEventOncePromise(vscode.notebook.onDidChangeActiveNotebookEditor); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await firstEditorOpen; @@ -373,10 +354,8 @@ suite('Notebook API tests', () => { }); test('edit API', async function () { - if (!assertInitalState()) { - return; - } - const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); const cellsChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeNotebookCells); @@ -395,10 +374,8 @@ suite('Notebook API tests', () => { }); test('initialzation should not emit cell change events.', async function () { - if (!assertInitalState()) { - return; - } - const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); let count = 0; const disposables: vscode.Disposable[] = []; @@ -417,10 +394,8 @@ suite('Notebook API tests', () => { suite('notebook workflow', () => { test('notebook open', async function () { - if (!assertInitalState()) { - return; - } - const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first'); assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), 'test'); @@ -441,10 +416,8 @@ suite('notebook workflow', () => { }); test('notebook cell actions', async function () { - if (!assertInitalState()) { - return; - } - const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first'); assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), 'test'); @@ -517,10 +490,8 @@ suite('notebook workflow', () => { }); test('notebook join cells', async function () { - if (!assertInitalState()) { - return; - } - const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first'); assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), 'test'); @@ -543,10 +514,8 @@ suite('notebook workflow', () => { }); test('move cells will not recreate cells in ExtHost', async function () { - if (!assertInitalState()) { - return; - } - const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); @@ -566,7 +535,7 @@ suite('notebook workflow', () => { }); // test.only('document metadata is respected', async function () { - // const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + // const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); // assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first'); @@ -590,10 +559,8 @@ suite('notebook workflow', () => { // }); test('cell runnable metadata is respected', async () => { - if (!assertInitalState()) { - return; - } - const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first'); const editor = vscode.notebook.activeNotebookEditor!; @@ -614,10 +581,8 @@ suite('notebook workflow', () => { }); test('document runnable metadata is respected', async () => { - if (!assertInitalState()) { - return; - } - const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first'); const editor = vscode.notebook.activeNotebookEditor!; @@ -639,10 +604,8 @@ suite('notebook workflow', () => { suite('notebook dirty state', () => { test('notebook open', async function () { - if (!assertInitalState()) { - return; - } - const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first'); assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), 'test'); @@ -672,10 +635,8 @@ suite('notebook dirty state', () => { suite('notebook undo redo', () => { test('notebook open', async function () { - if (!assertInitalState()) { - return; - } - const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first'); assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), 'test'); @@ -717,10 +678,8 @@ suite('notebook undo redo', () => { }); test.skip('execute and then undo redo', async function () { - if (!assertInitalState()) { - return; - } - const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); const cellsChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeNotebookCells); @@ -785,7 +744,7 @@ suite('notebook undo redo', () => { suite('notebook working copy', () => { // test('notebook revert on close', async function () { - // const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + // const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); // await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); // assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), ''); @@ -806,7 +765,7 @@ suite('notebook working copy', () => { // }); // test('notebook revert', async function () { - // const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + // const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); // await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); // assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), ''); @@ -826,10 +785,8 @@ suite('notebook working copy', () => { // }); test('multiple tabs: dirty + clean', async function () { - if (!assertInitalState()) { - return; - } - const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), ''); @@ -839,7 +796,7 @@ suite('notebook working copy', () => { edit.insert(vscode.notebook.activeNotebookEditor!.selection!.uri, new vscode.Position(0, 0), 'var abc = 0;'); await vscode.workspace.applyEdit(edit); - const secondResource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './second.vsctestnb')); + const secondResource = await createRandomFile('', undefined, 'second', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', secondResource, 'notebookCoreTest'); await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); @@ -854,10 +811,8 @@ suite('notebook working copy', () => { }); test('multiple tabs: two dirty tabs and switching', async function () { - if (!assertInitalState()) { - return; - } - const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), ''); @@ -867,7 +822,7 @@ suite('notebook working copy', () => { edit.insert(vscode.notebook.activeNotebookEditor!.selection!.uri, new vscode.Position(0, 0), 'var abc = 0;'); await vscode.workspace.applyEdit(edit); - const secondResource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './second.vsctestnb')); + const secondResource = await createRandomFile('', undefined, 'second', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', secondResource, 'notebookCoreTest'); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), ''); @@ -894,11 +849,9 @@ suite('notebook working copy', () => { }); test('multiple tabs: different editors with same document', async function () { - if (!assertInitalState()) { - return; - } + assertInitalState(); - const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); const firstNotebookEditor = vscode.notebook.activeNotebookEditor; assert.equal(firstNotebookEditor !== undefined, true, 'notebook first'); @@ -924,10 +877,8 @@ suite('notebook working copy', () => { suite('metadata', () => { test('custom metadata should be supported', async function () { - if (!assertInitalState()) { - return; - } - const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first'); assert.equal(vscode.notebook.activeNotebookEditor!.document.metadata.custom!['testMetadata'] as boolean, false); @@ -940,10 +891,8 @@ suite('metadata', () => { // TODO@rebornix skip as it crashes the process all the time test.skip('custom metadata should be supported 2', async function () { - if (!assertInitalState()) { - return; - } - const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first'); assert.equal(vscode.notebook.activeNotebookEditor!.document.metadata.custom!['testMetadata'] as boolean, false); @@ -962,10 +911,8 @@ suite('metadata', () => { suite('regression', () => { test('microsoft/vscode-github-issue-notebooks#26. Insert template cell in the new empty document', async function () { - if (!assertInitalState()) { - return; - } - const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './empty.vsctestnb')); + assertInitalState(); + const resource = await createRandomFile('', undefined, 'empty', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first'); assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), ''); @@ -974,10 +921,8 @@ suite('regression', () => { }); test('#97830, #97764. Support switch to other editor types', async function () { - if (!assertInitalState()) { - return; - } - const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './empty.vsctestnb')); + assertInitalState(); + const resource = await createRandomFile('', undefined, 'empty', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); const edit = new vscode.WorkspaceEdit(); @@ -996,10 +941,8 @@ suite('regression', () => { // open text editor, pin, and then open a notebook test('#96105 - dirty editors', async function () { - if (!assertInitalState()) { - return; - } - const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './empty.vsctestnb')); + assertInitalState(); + const resource = await createRandomFile('', undefined, 'empty', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'default'); const edit = new vscode.WorkspaceEdit(); edit.insert(resource, new vscode.Position(0, 0), 'var abc = 0;'); @@ -1014,9 +957,7 @@ suite('regression', () => { }); test('#102411 - untitled notebook creation failed', async function () { - if (!assertInitalState()) { - return; - } + assertInitalState(); await vscode.commands.executeCommand('workbench.action.files.newUntitledFile', { viewType: 'notebookCoreTest' }); assert.notEqual(vscode.notebook.activeNotebookEditor, undefined, 'untitled notebook editor is not undefined'); @@ -1024,10 +965,8 @@ suite('regression', () => { }); test('#102423 - copy/paste shares the same text buffer', async function () { - if (!assertInitalState()) { - return; - } - const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); let activeCell = vscode.notebook.activeNotebookEditor!.selection; @@ -1057,7 +996,7 @@ suite('webview', () => { // return; // } - // const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + // const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); // assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first'); // const uri = vscode.notebook.activeNotebookEditor!.asWebviewUri(vscode.Uri.file('./hello.png')); diff --git a/extensions/vscode-notebook-tests/src/notebookTestMain.ts b/extensions/vscode-notebook-tests/src/notebookTestMain.ts index 3a80303a502..d77e0725f5a 100644 --- a/extensions/vscode-notebook-tests/src/notebookTestMain.ts +++ b/extensions/vscode-notebook-tests/src/notebookTestMain.ts @@ -15,7 +15,7 @@ export function activate(context: vscode.ExtensionContext): any { context.subscriptions.push(vscode.notebook.registerNotebookContentProvider('notebookCoreTest', { onDidChangeNotebook: _onDidChangeNotebook.event, openNotebook: async (_resource: vscode.Uri) => { - if (_resource.path.endsWith('empty.vsctestnb')) { + if (/.*empty\-.*\.vsctestnb$/.test(_resource.path)) { return { languages: ['typescript'], metadata: {}, diff --git a/extensions/vscode-notebook-tests/src/utils.ts b/extensions/vscode-notebook-tests/src/utils.ts new file mode 100644 index 00000000000..95dae41d5dc --- /dev/null +++ b/extensions/vscode-notebook-tests/src/utils.ts @@ -0,0 +1,261 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + + +import * as path from 'path'; +import * as vscode from 'vscode'; + +class File implements vscode.FileStat { + + type: vscode.FileType; + ctime: number; + mtime: number; + size: number; + + name: string; + data?: Uint8Array; + + constructor(name: string) { + this.type = vscode.FileType.File; + this.ctime = Date.now(); + this.mtime = Date.now(); + this.size = 0; + this.name = name; + } +} + +class Directory implements vscode.FileStat { + + type: vscode.FileType; + ctime: number; + mtime: number; + size: number; + + name: string; + entries: Map; + + constructor(name: string) { + this.type = vscode.FileType.Directory; + this.ctime = Date.now(); + this.mtime = Date.now(); + this.size = 0; + this.name = name; + this.entries = new Map(); + } +} + +export type Entry = File | Directory; + +export class TestFS implements vscode.FileSystemProvider { + + constructor( + readonly scheme: string, + readonly isCaseSensitive: boolean + ) { } + + readonly root = new Directory(''); + + // --- manage file metadata + + stat(uri: vscode.Uri): vscode.FileStat { + return this._lookup(uri, false); + } + + readDirectory(uri: vscode.Uri): [string, vscode.FileType][] { + const entry = this._lookupAsDirectory(uri, false); + let result: [string, vscode.FileType][] = []; + for (const [name, child] of entry.entries) { + result.push([name, child.type]); + } + return result; + } + + // --- manage file contents + + readFile(uri: vscode.Uri): Uint8Array { + const data = this._lookupAsFile(uri, false).data; + if (data) { + return data; + } + throw vscode.FileSystemError.FileNotFound(); + } + + writeFile(uri: vscode.Uri, content: Uint8Array, options: { create: boolean, overwrite: boolean }): void { + let basename = path.posix.basename(uri.path); + let parent = this._lookupParentDirectory(uri); + let entry = parent.entries.get(basename); + if (entry instanceof Directory) { + throw vscode.FileSystemError.FileIsADirectory(uri); + } + if (!entry && !options.create) { + throw vscode.FileSystemError.FileNotFound(uri); + } + if (entry && options.create && !options.overwrite) { + throw vscode.FileSystemError.FileExists(uri); + } + if (!entry) { + entry = new File(basename); + parent.entries.set(basename, entry); + this._fireSoon({ type: vscode.FileChangeType.Created, uri }); + } + entry.mtime = Date.now(); + entry.size = content.byteLength; + entry.data = content; + + this._fireSoon({ type: vscode.FileChangeType.Changed, uri }); + } + + // --- manage files/folders + + rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: { overwrite: boolean }): void { + + if (!options.overwrite && this._lookup(newUri, true)) { + throw vscode.FileSystemError.FileExists(newUri); + } + + let entry = this._lookup(oldUri, false); + let oldParent = this._lookupParentDirectory(oldUri); + + let newParent = this._lookupParentDirectory(newUri); + let newName = path.posix.basename(newUri.path); + + oldParent.entries.delete(entry.name); + entry.name = newName; + newParent.entries.set(newName, entry); + + this._fireSoon( + { type: vscode.FileChangeType.Deleted, uri: oldUri }, + { type: vscode.FileChangeType.Created, uri: newUri } + ); + } + + delete(uri: vscode.Uri): void { + let dirname = uri.with({ path: path.posix.dirname(uri.path) }); + let basename = path.posix.basename(uri.path); + let parent = this._lookupAsDirectory(dirname, false); + if (!parent.entries.has(basename)) { + throw vscode.FileSystemError.FileNotFound(uri); + } + parent.entries.delete(basename); + parent.mtime = Date.now(); + parent.size -= 1; + this._fireSoon({ type: vscode.FileChangeType.Changed, uri: dirname }, { uri, type: vscode.FileChangeType.Deleted }); + } + + createDirectory(uri: vscode.Uri): void { + let basename = path.posix.basename(uri.path); + let dirname = uri.with({ path: path.posix.dirname(uri.path) }); + let parent = this._lookupAsDirectory(dirname, false); + + let entry = new Directory(basename); + parent.entries.set(entry.name, entry); + parent.mtime = Date.now(); + parent.size += 1; + this._fireSoon({ type: vscode.FileChangeType.Changed, uri: dirname }, { type: vscode.FileChangeType.Created, uri }); + } + + // --- lookup + + private _lookup(uri: vscode.Uri, silent: false): Entry; + private _lookup(uri: vscode.Uri, silent: boolean): Entry | undefined; + private _lookup(uri: vscode.Uri, silent: boolean): Entry | undefined { + let parts = uri.path.split('/'); + let entry: Entry = this.root; + for (const part of parts) { + const partLow = part.toLowerCase(); + if (!part) { + continue; + } + let child: Entry | undefined; + if (entry instanceof Directory) { + if (this.isCaseSensitive) { + child = entry.entries.get(part); + } else { + for (let [key, value] of entry.entries) { + if (key.toLowerCase() === partLow) { + child = value; + break; + } + } + } + } + if (!child) { + if (!silent) { + throw vscode.FileSystemError.FileNotFound(uri); + } else { + return undefined; + } + } + entry = child; + } + return entry; + } + + private _lookupAsDirectory(uri: vscode.Uri, silent: boolean): Directory { + let entry = this._lookup(uri, silent); + if (entry instanceof Directory) { + return entry; + } + throw vscode.FileSystemError.FileNotADirectory(uri); + } + + private _lookupAsFile(uri: vscode.Uri, silent: boolean): File { + let entry = this._lookup(uri, silent); + if (entry instanceof File) { + return entry; + } + throw vscode.FileSystemError.FileIsADirectory(uri); + } + + private _lookupParentDirectory(uri: vscode.Uri): Directory { + const dirname = uri.with({ path: path.posix.dirname(uri.path) }); + return this._lookupAsDirectory(dirname, false); + } + + // --- manage file events + + private _emitter = new vscode.EventEmitter(); + private _bufferedEvents: vscode.FileChangeEvent[] = []; + private _fireSoonHandle?: NodeJS.Timer; + + readonly onDidChangeFile: vscode.Event = this._emitter.event; + + watch(_resource: vscode.Uri): vscode.Disposable { + // ignore, fires for all changes... + return new vscode.Disposable(() => { }); + } + + private _fireSoon(...events: vscode.FileChangeEvent[]): void { + this._bufferedEvents.push(...events); + + if (this._fireSoonHandle) { + clearTimeout(this._fireSoonHandle); + } + + this._fireSoonHandle = setTimeout(() => { + this._emitter.fire(this._bufferedEvents); + this._bufferedEvents.length = 0; + }, 5); + } +} + +export function rndName() { + return Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 10); +} + +export const testFs = new TestFS('fake-fs', true); +vscode.workspace.registerFileSystemProvider(testFs.scheme, testFs, { isCaseSensitive: testFs.isCaseSensitive }); + +export async function createRandomFile(contents = '', dir: vscode.Uri | undefined = undefined, prefix = '', ext = ''): Promise { + let fakeFile: vscode.Uri; + if (dir) { + fakeFile = dir.with({ path: dir.path + '/' + rndName() + ext }); + } else { + fakeFile = vscode.Uri.parse(`${testFs.scheme}:/${prefix}-${rndName() + ext}`); + } + + await testFs.writeFile(fakeFile, Buffer.from(contents), { create: true, overwrite: true }); + return fakeFile; +} diff --git a/scripts/test-integration.sh b/scripts/test-integration.sh index 3288a8c0516..2dc5ff578d0 100755 --- a/scripts/test-integration.sh +++ b/scripts/test-integration.sh @@ -49,6 +49,7 @@ fi ./scripts/test.sh --runGlob **/*.integrationTest.js "$@" # Tests in the extension host +"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-notebook-tests/test --enable-proposed-api=vscode.vscode-notebook-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-notebook-tests --extensionTestsPath=$ROOT/extensions/vscode-notebook-tests/out/ --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-api-tests/testWorkspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/singlefolder-tests --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-api-tests/testworkspace.code-workspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/workspace-tests --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-colorize-tests/test --extensionDevelopmentPath=$ROOT/extensions/vscode-colorize-tests --extensionTestsPath=$ROOT/extensions/vscode-colorize-tests/out --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR @@ -56,7 +57,6 @@ fi #"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/typescript-language-features/test-workspace --extensionDevelopmentPath=$ROOT/extensions/typescript-language-features --extensionTestsPath=$ROOT/extensions/typescript-language-features/out/test --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/emmet/out/test/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/emmet --extensionTestsPath=$ROOT/extensions/emmet/out/test --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $(mktemp -d 2>/dev/null) --enable-proposed-api=vscode.git --extensionDevelopmentPath=$ROOT/extensions/git --extensionTestsPath=$ROOT/extensions/git/out/test --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR -"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-notebook-tests/test --enable-proposed-api=vscode.vscode-notebook-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-notebook-tests --extensionTestsPath=$ROOT/extensions/vscode-notebook-tests/out/ --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR # Tests in commonJS cd $ROOT/extensions/css-language-features/server && $ROOT/scripts/node-electron.sh test/index.js