From 3d3977f2b78311b36cb7e69290a3ec76afaa54e7 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Sun, 12 Nov 2017 22:55:29 -0800 Subject: [PATCH] Migrate compileOnSave tests to vfs --- src/harness/mocks.ts | 5 +- src/harness/unittests/compileOnSave.ts | 143 ++++++++++++++++++++----- src/harness/vfs.ts | 68 ++++++------ 3 files changed, 155 insertions(+), 61 deletions(-) diff --git a/src/harness/mocks.ts b/src/harness/mocks.ts index 051fdc094a4..315677b7117 100644 --- a/src/harness/mocks.ts +++ b/src/harness/mocks.ts @@ -1,5 +1,7 @@ /// -/// +/// import { debug } from "util"; + + // NOTE: The contents of this file are all exported from the namespace 'mocks'. This is to // support the eventual conversion of harness into a modular system. @@ -298,7 +300,6 @@ namespace mocks { return undefined; } - // TOOD: record and invoke callbacks to simulate timer events public setTimeout(callback: (...args: any[]) => void, timeout: number, ...args: any[]) { return this.timers.setTimeout(callback, timeout, ...args); } diff --git a/src/harness/unittests/compileOnSave.ts b/src/harness/unittests/compileOnSave.ts index 7ef3e2aa1af..36f9d1c366b 100644 --- a/src/harness/unittests/compileOnSave.ts +++ b/src/harness/unittests/compileOnSave.ts @@ -1,6 +1,7 @@ /// /// /// +/// namespace ts.projectSystem { import CommandNames = server.CommandNames; @@ -58,6 +59,24 @@ namespace ts.projectSystem { // A compile on save affected file request using file1 let moduleFile1FileListRequest: server.protocol.Request; + let sharedFs: vfs.VirtualFileSystem; + before(() => { + const fs = new vfs.VirtualFileSystem("/", /*useCaseSensitiveFileNames*/ true); + fs.addFile("/a/b/moduleFile1.ts", `export function Foo() { };`); + fs.addFile("/a/b/file1Consumer1.ts", `import {Foo} from "./moduleFile1"; export var y = 10;`); + fs.addFile("/a/b/file1Consumer2.ts", `import {Foo} from "./moduleFile1"; let z = 10;`); + fs.addFile("/a/b/globalFile3.ts", `interface GlobalFoo { age: number }`); + fs.addFile("/a/b/moduleFile2.ts", `export var Foo4 = 10;`); + fs.addFile("/a/b/tsconfig.json", `{ compileOnSave": true }`); + fs.addFile(libFile.path, libFile.content); + fs.makeReadOnly(); + sharedFs = fs; + }); + + after(() => { + sharedFs = undefined; + }); + beforeEach(() => { moduleFile1 = { path: "/a/b/moduleFile1.ts", @@ -115,7 +134,7 @@ namespace ts.projectSystem { }); it("should contains only itself if a module file's shape didn't change, and all files referencing it if its shape changed", () => { - const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); + const host = new mocks.MockServerHost(sharedFs.shadow()); const typingsInstaller = createTestTypingsInstaller(host); const session = createSession(host, typingsInstaller); @@ -140,7 +159,7 @@ namespace ts.projectSystem { }); it("should be up-to-date with the reference map changes", () => { - const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); + const host = new mocks.MockServerHost(sharedFs.shadow()); const typingsInstaller = createTestTypingsInstaller(host); const session = createSession(host, typingsInstaller); @@ -187,7 +206,7 @@ namespace ts.projectSystem { }); it("should be up-to-date with changes made in non-open files", () => { - const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); + const host = new mocks.MockServerHost(sharedFs.shadow()); const typingsInstaller = createTestTypingsInstaller(host); const session = createSession(host, typingsInstaller); @@ -196,15 +215,13 @@ namespace ts.projectSystem { // Send an initial compileOnSave request sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); - file1Consumer1.content = `let y = 10;`; - host.reloadFS([moduleFile1, file1Consumer1, file1Consumer2, configFile, libFile]); - + host.vfs.writeFile(file1Consumer1.path, `let y = 10;`); session.executeCommand(changeModuleFile1ShapeRequest1); sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer2] }]); }); it("should be up-to-date with deleted files", () => { - const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); + const host = new mocks.MockServerHost(sharedFs.shadow()); const typingsInstaller = createTestTypingsInstaller(host); const session = createSession(host, typingsInstaller); @@ -212,13 +229,14 @@ namespace ts.projectSystem { sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); session.executeCommand(changeModuleFile1ShapeRequest1); - // Delete file1Consumer2 - host.reloadFS([moduleFile1, file1Consumer1, configFile, libFile]); + + host.vfs.removeFile(file1Consumer2.path); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1] }]); }); it("should be up-to-date with newly created files", () => { - const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); + const host = new mocks.MockServerHost(sharedFs.shadow()); const typingsInstaller = createTestTypingsInstaller(host); const session = createSession(host, typingsInstaller); @@ -229,8 +247,7 @@ namespace ts.projectSystem { path: "/a/b/file1Consumer3.ts", content: `import {Foo} from "./moduleFile1"; let y = Foo();` }; - host.reloadFS([moduleFile1, file1Consumer1, file1Consumer2, file1Consumer3, globalFile3, configFile, libFile]); - host.runQueuedTimeoutCallbacks(); + host.vfs.writeFile(file1Consumer3.path, file1Consumer3.content); session.executeCommand(changeModuleFile1ShapeRequest1); sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2, file1Consumer3] }]); }); @@ -254,7 +271,12 @@ namespace ts.projectSystem { }` }; - const host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]); + const host = new mocks.MockServerHost(new vfs.VirtualFileSystem("/", /*useCaseSensitiveFileNames*/ true)); + host.vfs.addFile(moduleFile1.path, moduleFile1.content); + host.vfs.addFile(file1Consumer1.path, file1Consumer1.content); + host.vfs.addFile(configFile.path, configFile.content); + host.vfs.addFile(libFile.path, libFile.content); + const typingsInstaller = createTestTypingsInstaller(host); const session = createSession(host, typingsInstaller); @@ -271,7 +293,7 @@ namespace ts.projectSystem { }); it("should return all files if a global file changed shape", () => { - const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); + const host = new mocks.MockServerHost(sharedFs.shadow()); const typingsInstaller = createTestTypingsInstaller(host); const session = createSession(host, typingsInstaller); @@ -297,7 +319,8 @@ namespace ts.projectSystem { content: `{}` }; - const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, configFile, libFile]); + const host = new mocks.MockServerHost(sharedFs.shadow()); + host.vfs.writeFile(configFile.path, configFile.content); const typingsInstaller = createTestTypingsInstaller(host); const session = createSession(host, typingsInstaller); openFilesForSession([moduleFile1], session); @@ -319,7 +342,10 @@ namespace ts.projectSystem { }` }; - const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, configFile2, configFile, libFile]); + const host = new mocks.MockServerHost(sharedFs.shadow()); + host.vfs.writeFile(configFile.path, configFile.content); + host.vfs.addFile(configFile2.path, configFile2.content); + const typingsInstaller = createTestTypingsInstaller(host); const session = createSession(host, typingsInstaller); @@ -338,7 +364,12 @@ namespace ts.projectSystem { }` }; - const host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]); + const host = new mocks.MockServerHost(new vfs.VirtualFileSystem("/", /*useCaseSensitiveFileNames*/ true)); + host.vfs.addFile(moduleFile1.path, moduleFile1.content); + host.vfs.addFile(file1Consumer1.path, file1Consumer1.content); + host.vfs.addFile(configFile.path, configFile.content); + host.vfs.addFile(libFile.path, libFile.content); + const typingsInstaller = createTestTypingsInstaller(host); const session = createSession(host, typingsInstaller); openFilesForSession([moduleFile1], session); @@ -367,7 +398,12 @@ namespace ts.projectSystem { }` }; - const host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]); + const host = new mocks.MockServerHost(new vfs.VirtualFileSystem("/", /*useCaseSensitiveFileNames*/ true)); + host.vfs.addFile(moduleFile1.path, moduleFile1.content); + host.vfs.addFile(file1Consumer1.path, file1Consumer1.content); + host.vfs.addFile(configFile.path, configFile.content); + host.vfs.addFile(libFile.path, libFile.content); + const typingsInstaller = createTestTypingsInstaller(host); const session = createSession(host, typingsInstaller); openFilesForSession([moduleFile1], session); @@ -389,7 +425,16 @@ namespace ts.projectSystem { path: "/a/b/file1Consumer1Consumer1.ts", content: `import {y} from "./file1Consumer1";` }; - const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer1Consumer1, globalFile3, configFile, libFile]); + + const host = new mocks.MockServerHost(new vfs.VirtualFileSystem("/", /*useCaseSensitiveFileNames*/ true)); + host.vfs.addFile(moduleFile1.path, moduleFile1.content); + host.vfs.addFile(file1Consumer1.path, file1Consumer1.content); + host.vfs.addFile(file1Consumer1Consumer1.path, file1Consumer1Consumer1.content); + host.vfs.addFile(globalFile3.path, globalFile3.content); + host.vfs.addFile(configFile.path, configFile.content); + host.vfs.addFile(libFile.path, libFile.content); + host.vfs.addFile(file1Consumer1Consumer1.path, file1Consumer1Consumer1.content); + const typingsInstaller = createTestTypingsInstaller(host); const session = createSession(host, typingsInstaller); @@ -422,7 +467,12 @@ namespace ts.projectSystem { /// export var t2 = 10;` }; - const host = createServerHost([file1, file2, configFile]); + + const host = new mocks.MockServerHost(new vfs.VirtualFileSystem("/", /*useCaseSensitiveFileNames*/ true)); + host.vfs.addFile(file1.path, file1.content); + host.vfs.addFile(file2.path, file2.content); + host.vfs.addFile(configFile.path, configFile.content); + const typingsInstaller = createTestTypingsInstaller(host); const session = createSession(host, typingsInstaller); @@ -438,7 +488,13 @@ namespace ts.projectSystem { const configFile1: FileOrFolder = { path: "/a/b/tsconfig.json", content: `{ "compileOnSave": true }` }; const configFile2: FileOrFolder = { path: "/a/c/tsconfig.json", content: `{ "compileOnSave": true }` }; - const host = createServerHost([file1, file2, file3, configFile1, configFile2]); + const host = new mocks.MockServerHost(new vfs.VirtualFileSystem("/", /*useCaseSensitiveFileNames*/ true)); + host.vfs.addFile(file1.path, file1.content); + host.vfs.addFile(file2.path, file2.content); + host.vfs.addFile(file3.path, file3.content); + host.vfs.addFile(configFile1.path, configFile1.content); + host.vfs.addFile(configFile2.path, configFile2.content); + const session = createSession(host); openFilesForSession([file1, file2, file3], session); @@ -457,16 +513,23 @@ namespace ts.projectSystem { /// export var x = Foo();` }; - const host = createServerHost([moduleFile1, referenceFile1, configFile]); + + const host = new mocks.MockServerHost(new vfs.VirtualFileSystem("/", /*useCaseSensitiveFileNames*/ true)); + host.vfs.addFile(moduleFile1.path, moduleFile1.content); + host.vfs.addFile(referenceFile1.path, referenceFile1.content); + host.vfs.addFile(configFile.path, configFile.content); + const session = createSession(host); openFilesForSession([referenceFile1], session); - host.reloadFS([referenceFile1, configFile]); + + host.vfs.removeFile(moduleFile1.path); const request = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: referenceFile1.path }); sendAffectedFileRequestAndCheckResult(session, request, [ { projectFileName: configFile.path, files: [referenceFile1] } ]); + const requestForMissingFile = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: moduleFile1.path }); sendAffectedFileRequestAndCheckResult(session, requestForMissingFile, []); }); @@ -478,10 +541,15 @@ namespace ts.projectSystem { /// export var x = Foo();` }; - const host = createServerHost([referenceFile1, configFile]); + + const host = new mocks.MockServerHost(new vfs.VirtualFileSystem("/", /*useCaseSensitiveFileNames*/ true)); + host.vfs.addFile(referenceFile1.path, referenceFile1.content); + host.vfs.addFile(configFile.path, configFile.content); + const session = createSession(host); openFilesForSession([referenceFile1], session); + const request = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: referenceFile1.path }); sendAffectedFileRequestAndCheckResult(session, request, [ { projectFileName: configFile.path, files: [referenceFile1] } @@ -502,7 +570,10 @@ namespace ts.projectSystem { path: path + ts.Extension.Ts, content: lines.join(newLine) }; - const host = createServerHost([f], { newLine }); + + const host = new mocks.MockServerHost(new vfs.VirtualFileSystem("/", /*useCaseSensitiveFileNames*/ true), /*executingFilePath*/ undefined, newLine); + host.vfs.addFile(f.path, f.content); + const session = createSession(host); const openRequest: server.protocol.OpenRequest = { seq: 1, @@ -536,7 +607,13 @@ namespace ts.projectSystem { path: "/a/b/tsconfig.json", content: `{}` }; - const host = createServerHost([file1, file2, configFile, libFile], { newLine: "\r\n" }); + + const host = new mocks.MockServerHost(new vfs.VirtualFileSystem("/", /*useCaseSensitiveFileNames*/ true), /*executingFilePath*/ undefined, "\r\n"); + host.vfs.addFile(file1.path, file1.content); + host.vfs.addFile(file2.path, file2.content); + host.vfs.addFile(configFile.path, configFile.content); + host.vfs.addFile(libFile.path, libFile.content); + const typingsInstaller = createTestTypingsInstaller(host); const session = createSession(host, { typingsInstaller }); @@ -564,7 +641,13 @@ namespace ts.projectSystem { content: "console.log('file3');" }; const externalProjectName = "/a/b/externalproject"; - const host = createServerHost([file1, file2, file3, libFile]); + + const host = new mocks.MockServerHost(new vfs.VirtualFileSystem("/", /*useCaseSensitiveFileNames*/ true)); + host.vfs.addFile(file1.path, file1.content); + host.vfs.addFile(file2.path, file2.content); + host.vfs.addFile(file3.path, file3.content); + host.vfs.addFile(libFile.path, libFile.content); + const session = createSession(host); const projectService = session.getProjectService(); @@ -596,7 +679,11 @@ namespace ts.projectSystem { content: "consonle.log('file1');" }; const externalProjectName = "/root/TypeScriptProject3/TypeScriptProject3/TypeScriptProject3.csproj"; - const host = createServerHost([file1, libFile]); + + const host = new mocks.MockServerHost(new vfs.VirtualFileSystem("/", /*useCaseSensitiveFileNames*/ true)); + host.vfs.addFile(file1.path, file1.content); + host.vfs.addFile(libFile.path, libFile.content); + const session = createSession(host); const projectService = session.getProjectService(); diff --git a/src/harness/vfs.ts b/src/harness/vfs.ts index 0fcaef05443..92f34f6b832 100644 --- a/src/harness/vfs.ts +++ b/src/harness/vfs.ts @@ -869,7 +869,7 @@ namespace vfs { * Gets the root directory for this entry. */ public get root(): VirtualDirectory | undefined { - return this.parent instanceof VirtualRoot ? this : undefined; + return this.parent instanceof VirtualRoot ? this : this.parent.root; } /** @@ -1129,11 +1129,13 @@ namespace vfs { const { files, directories } = resolver.getEntries(this); for (const dir of directories) { const vdir = new VirtualDirectory(this, dir, resolver); + vdir.addListener("fileSystemChange", this._onChildFileSystemChange); if (this.isReadOnly) vdir.makeReadOnly(); entries.set(vdir.name, vdir); } for (const file of files) { const vfile = new VirtualFile(this, file, resolver); + vfile.addListener("fileSystemChange", this._onChildFileSystemChange); if (this.isReadOnly) vfile.makeReadOnly(); entries.set(vfile.name, vfile); } @@ -1141,6 +1143,7 @@ namespace vfs { else if (shadowRoot) { shadowRoot.getOwnEntries().forEach(entry => { const clone = entry.shadow(this); + clone.addListener("fileSystemChange", this._onChildFileSystemChange); if (this.isReadOnly) clone.makeReadOnly(); entries.set(clone.name, clone); }); @@ -1292,7 +1295,7 @@ namespace vfs { this.updateAccessTime(); this.updateModificationTime(); this.emit("childAdded", entry); - entry.emit("fileSystemChange", entry.path, "added"); + this.emit("fileSystemChange", entry.path, "added"); entry.addListener("fileSystemChange", this._onChildFileSystemChange); } @@ -1618,34 +1621,14 @@ namespace vfs { * Gets the text content of this file. */ public get content(): string | undefined { - if (!this._contentWasSet) { - const resolver = this._resolver; - const shadowRoot = this._shadowRoot; - if (resolver) { - this._resolver = undefined; - this._content = typeof resolver === "function" ? resolver(this) : resolver.getContent(this); - this._contentWasSet = true; - } - else if (shadowRoot) { - this._content = shadowRoot.content; - this._contentWasSet = true; - } - } - return this._content; + return this.readContent(/*updateAccessTime*/ false); } /** * Sets the text content of this file. */ public set content(value: string | undefined) { - if (this.content !== value) { - this.writePreamble(); - this._resolver = undefined; - this._content = value; - this._contentWasSet = true; - this.emit("contentChanged", this); - this.emit("fileSystemChange", this.path, "modified"); - } + this.writeContent(value, /*updateModificationType*/ false); } /** @@ -1663,18 +1646,41 @@ namespace vfs { /** * Reads the content and updates the file's access time. */ - public readContent() { - const content = this.content; - if (content) this.updateAccessTime(); - return content; + public readContent(updateAccessTime = true) { + if (!this._contentWasSet) { + const resolver = this._resolver; + const shadowRoot = this._shadowRoot; + if (resolver) { + this._resolver = undefined; + this._content = typeof resolver === "function" ? resolver(this) : resolver.getContent(this); + this._contentWasSet = true; + } + else if (shadowRoot) { + this._content = shadowRoot.content; + this._contentWasSet = true; + } + } + if (this._content && updateAccessTime) { + this.updateAccessTime(); + } + return this._content; } /** * Writes the provided content and updates the file's modification time. */ - public writeContent(content: string) { - this.content = content; - if (content) this.updateModificationTime(); + public writeContent(content: string | undefined, updateModificationTime = true) { + if (this.content !== content) { + this.writePreamble(); + this._resolver = undefined; + this._content = content; + this._contentWasSet = true; + if (content && updateModificationTime) { + this.updateModificationTime(); + } + this.emit("contentChanged", this); + this.emit("fileSystemChange", this.path, "modified"); + } } public getStats() {