diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts index 47e9cfcdc80..680e67aa799 100644 --- a/src/harness/virtualFileSystemWithWatch.ts +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -113,6 +113,12 @@ interface Array { length: number; [n: number]: T; }` delete this.map[key]; } } + + serialize(baseline: string[]) { + for (const id in this.map) { + baseline.push(`${id} at time ${this.map[id].time} in ${this.map[id].ms} ms: ${this.map[id].args[1]}`) + } + } } type TimeOutCallback = (...args: any[]) => void; @@ -243,7 +249,7 @@ interface Array { length: number; [n: number]: T; }` if (this.inodes) this.inodes.set(path, this.nextInode++); } override getInode(path: Path): number | undefined { - if (this.inodes) return this.inodes.get(path) + if (this.inodes) return this.inodes.get(path); } protected override deleteInode(path: Path) { if (this.inodes) this.inodes.delete(path); @@ -414,6 +420,10 @@ interface Array { length: number; [n: number]: T; }` this.timeoutCallbacks.unregister(timeoutId); } + serializeTimeout(baseline: string[]) { + this.timeoutCallbacks.serialize(baseline) + } + clearScreen(): void { this.screenClears.push(this.output.length); } @@ -490,14 +500,11 @@ interface Array { length: number; [n: number]: T; }` this.clearOutput(); } - serializeWatches(baseline: string[] = []) { - serializeMultiMap(baseline, "WatchedFiles", this.watchedFiles, ({ fileName, pollingInterval }) => ({ fileName, pollingInterval })); - baseline.push(""); - serializeMultiMap(baseline, "FsWatches", this.fsWatches, serializeTestFsWatcher); - baseline.push(""); - serializeMultiMap(baseline, "FsWatchesRecursive", this.fsWatchesRecursive, serializeTestFsWatcher); - baseline.push(""); - return baseline; + readonly exitMessage = "System Exit"; + exitCode: number | undefined; + exit(exitCode?: number) { + this.exitCode = exitCode; + throw new Error(this.exitMessage); } override getEnvironmentVariable(name: string) { @@ -505,7 +512,7 @@ interface Array { length: number; [n: number]: T; }` } } - export function snap(fshost: VirtualFS.VirtualServerHost): ESMap { + export function snap(fshost: VirtualServerHost): ESMap { const result = new Map(); fshost.fs.forEach((value, key) => { const cloneValue = clone(value); @@ -518,7 +525,7 @@ interface Array { length: number; [n: number]: T; }` return result; } - export function diff(fshost: VirtualFS.VirtualServerHost, baseline: string[], base: ESMap = new Map()) { + export function diff(fshost: VirtualServerHost, baseline: string[], base: ESMap = new Map()) { fshost.fs.forEach((newFsEntry, path) => { diffFsEntry(baseline, base.get(path), newFsEntry, fshost.getInode(path), fshost.writtenFiles); }); @@ -598,23 +605,6 @@ interface Array { length: number; [n: number]: T; }` } } - function serializeTestFsWatcher({ directoryName, inode }: VirtualFsWatcher) { - return { - directoryName, - inode, - }; - } - - function serializeMultiMap(baseline: string[], caption: string, multiMap: MultiMap, valueMapper: (value: T) => U) { - baseline.push(`${caption}::`); - multiMap.forEach((values, key) => { - baseline.push(`${key}:`); - for (const value of values) { - baseline.push(` ${JSON.stringify(valueMapper(value))}`); - } - }); - } - function baselineOutputs(baseline: string[], output: readonly string[], start: number, end = output.length) { let baselinedOutput: string[] | undefined; for (let i = start; i < end; i++) { diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 5fbfe4aa318..c5f4d11c940 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -416,7 +416,7 @@ namespace ts.server { storeFilesChangingSignatureDuringEmit: host.storeFilesChangingSignatureDuringEmit, // VirtualFS ensureFileOrFolder: fshost.ensureFileOrFolder.bind(fshost), - } as any; + } as ServerHost; } const noopConfigFileWatcher: FileWatcher = { close: noop }; @@ -3757,13 +3757,13 @@ namespace ts.server { updateFileSystem(updatedFiles: protocol.FileSystemRequestArgs[] | undefined, deletedFiles?: string[]) { const fshost = this.host as unknown as FileServerHost; if (updatedFiles) { - Debug.assert(fshost.ensureFileOrFolder) + Debug.assert(fshost.ensureFileOrFolder); for (const { file, fileContent } of updatedFiles) { fshost.ensureFileOrFolder({ path: file, content: fileContent }); } } if (deletedFiles) { - Debug.assert(fshost.deleteFile) + Debug.assert(fshost.deleteFile); for (const file of deletedFiles) { fshost.deleteFile(file, /*deleteEmptyParentFolders*/ true); } diff --git a/src/testRunner/unittests/tsbuild/helpers.ts b/src/testRunner/unittests/tsbuild/helpers.ts index 9366331b305..31f8f250d70 100644 --- a/src/testRunner/unittests/tsbuild/helpers.ts +++ b/src/testRunner/unittests/tsbuild/helpers.ts @@ -327,7 +327,7 @@ interface Symbol { originalReadCall?: System["readFile"], ) { const buildInfoPath = getTsBuildInfoEmitOutputFilePath(options); - if (!buildInfoPath || !sys.writtenFiles!.has(toPathWithSystem(sys, buildInfoPath))) return; + if (!buildInfoPath || !sys.writtenFiles.has(toPathWithSystem(sys, buildInfoPath))) return; if (!sys.fileExists(buildInfoPath)) return; const buildInfo = getBuildInfo((originalReadCall || sys.readFile).call(sys, buildInfoPath, "utf8")!); diff --git a/src/testRunner/unittests/tsserver/updateFileSystem.ts b/src/testRunner/unittests/tsserver/updateFileSystem.ts index e018949dfeb..db9c4b0f485 100644 --- a/src/testRunner/unittests/tsserver/updateFileSystem.ts +++ b/src/testRunner/unittests/tsserver/updateFileSystem.ts @@ -22,42 +22,46 @@ namespace ts.projectSystem { }; const lib: protocol.FileSystemRequestArgs = { file: "/fshost/lib/lib.d.ts", - fileContent: `/// -interface Boolean {} -interface Function {} -interface CallableFunction {} -interface NewableFunction {} -interface IArguments {} -interface Number { toExponential: any; } -interface Object {} -interface RegExp {} -interface String { charAt: any; } -interface Array { length: number; [n: number]: T; }` - }; + fileContent: libFile.content + }; function fileContentWithComment(file: protocol.FileSystemRequestArgs) { return `// some copy right notice ${file.fileContent}`; } - function baselineFileSystem(scenario: string, subScenario: string, requests: [string, Partial][], host: VirtualFS.VirtualServerHost, session: TestSession) { + function baselineFileSystem(scenario: string, subScenario: string, requests: [string, (() => void) | Partial][], host: TestServerHost, fshost: VirtualFS.VirtualServerHost, session: TestSession) { const history: string[] = []; - let prev = VirtualFS.snap(host); + + let prev = VirtualFS.snap(fshost); + let prev2 = VirtualFS.snap(host); for (const [name, request] of requests) { - session.executeCommandSeq(request); + if (typeof request === "function") { + request(); + } + else { + session.executeCommandSeq(request); + } history.push(""); history.push("#### " + name); - VirtualFS.diff(host, history, prev); - prev = VirtualFS.snap(host); + VirtualFS.diff(fshost, history, prev); + prev = VirtualFS.snap(fshost); + VirtualFS.diff(host, history, prev2); + prev2 = VirtualFS.snap(host); + host.serializeTimeout(history) } + history.push("### fshost watches") + fshost.serializeWatches(history) + history.push("### host watches") + host.serializeWatches(history) Harness.Baseline.runBaseline(`tsserver/${scenario}/${subScenario.split(" ").join("-")}.txt`, history.join("\r\n")); baselineTsserverLogs(scenario, subScenario, session); } it("with updateFileSystem request", () => { - const host = VirtualFS.createVirtualServerHost({ executingFilePath: "/host/tsc.js" }); + const host = VirtualFS.createServerHost({ executingFilePath: "/host/tsc.js" }); const fshost = VirtualFS.createVirtualServerHost({ executingFilePath: "/fshost/tsc.js" }); const session = createSession(host, { fshost, logger: createLoggerWithInMemoryLogs(), canUseEvents: true }); - const requests: [string, Partial][] = [ + const requests: [string, (() => void) | Partial][] = [ ["Initial updateFileSystem", { command: protocol.CommandTypes.UpdateFileSystem, arguments:{ @@ -96,6 +100,22 @@ ${file.fileContent}`; deleted: [], }, }], + ["ensure /host/app.ts exists", () => host.ensureFileOrFolder({ + path: "/host/b/app.ts", + content: 'console.log("hello")' + })], + ["modify /host/app.ts", () => host.modifyFile( + "/host/b/app.ts", + "console.log('goodbye')" + )], + ["ensure /fshost/file4.ts exists", () => fshost.ensureFileOrFolder({ + path:"/fshost/b/file4.ts", + content: "console.log('file4 exists')", + })], + ["modify /fshost/file4", () => fshost.modifyFile( + "/fshost/b/file4.ts", + "console.log('file4 modified')" + )], ["delete", { command: protocol.CommandTypes.UpdateFileSystem, arguments:{ @@ -119,17 +139,7 @@ ${file.fileContent}`; const scenario = "updateFileSystem"; const subScenario = "open and close files"; - baselineFileSystem(scenario, subScenario, requests, fshost, session); - - host.ensureFileOrFolder({ - path: "/host/b/app.ts", - content: 'console.log("hello")' - }); - host.modifyFile("/host/b/app.ts", "console.log('goodbye')"); - assert.isFalse(host.fileExists("/fshost/b/app.ts"), "host fileExists /fshost/b/app.ts"); - assert.isTrue(fshost.fileExists("/fshost/b/app.ts"), "fshost fileExists /fshost/b/app.ts"); - assert.isTrue(host.fileExists("/host/b/app.ts"), "host fileExists /host/b/app.ts"); - assert.isFalse(fshost.fileExists("/host/b/app.ts"), "fshost fileExists /host/b/app.ts"); + baselineFileSystem(scenario, subScenario, requests, host, fshost, session); }); }); } diff --git a/src/testRunner/unittests/tsserver/webServer.ts b/src/testRunner/unittests/tsserver/webServer.ts index 9f4a08b6b6e..b2acf51154a 100644 --- a/src/testRunner/unittests/tsserver/webServer.ts +++ b/src/testRunner/unittests/tsserver/webServer.ts @@ -42,7 +42,7 @@ namespace ts.projectSystem { if (isVfs) { serverMode = LanguageServiceMode.Semantic; fshost = VirtualFS.createVirtualServerHost({ executingFilePath: "/a/lib/tsc.js" }); - (fshost as VirtualFS.VirtualServerHost).ensureFileOrFolder(libFile); + (fshost).ensureFileOrFolder(libFile); } const logger = logLevel !== undefined ? new server.MainProcessLogger(logLevel, webHost) : nullLogger(); const session = new TestWorkerSession(webSys, fshost, webHost, { serverMode }, logger); diff --git a/src/vfs/virtualFileSystemWithWatch.ts b/src/vfs/virtualFileSystemWithWatch.ts index f186da37f91..4f71f3cb466 100644 --- a/src/vfs/virtualFileSystemWithWatch.ts +++ b/src/vfs/virtualFileSystemWithWatch.ts @@ -636,18 +636,39 @@ namespace ts.VirtualFS { return fsEntry?.fullPath || realFullPath; } - readonly exitMessage = "System Exit"; - exitCode: number | undefined; readonly resolvePath = (s: string) => s; readonly getExecutingFilePath = () => this.executingFilePath; readonly getCurrentDirectory = () => this.currentDirectory; - exit(exitCode?: number) { - this.exitCode = exitCode; - throw new Error(this.exitMessage); + + serializeWatches(baseline: string[] = []) { + serializeMultiMap(baseline, "WatchedFiles", this.watchedFiles, ({ fileName, pollingInterval }) => ({ fileName, pollingInterval })); + baseline.push(""); + serializeMultiMap(baseline, "FsWatches", this.fsWatches, serializeTestFsWatcher); + baseline.push(""); + serializeMultiMap(baseline, "FsWatchesRecursive", this.fsWatchesRecursive, serializeTestFsWatcher); + baseline.push(""); + return baseline; } getEnvironmentVariable(_name: string) { return ""; } } + + function serializeTestFsWatcher({ directoryName, inode }: VirtualFsWatcher) { + return { + directoryName, + inode, + }; + } + + function serializeMultiMap(baseline: string[], caption: string, multiMap: MultiMap, valueMapper: (value: T) => U) { + baseline.push(`${caption}::`); + multiMap.forEach((values, key) => { + baseline.push(`${key}:`); + for (const value of values) { + baseline.push(` ${JSON.stringify(valueMapper(value))}`); + } + }); + } } diff --git a/tests/baselines/reference/tsserver/updateFileSystem/open-and-close-files.js b/tests/baselines/reference/tsserver/updateFileSystem/open-and-close-files.js index d80453f205b..c0530988564 100644 --- a/tests/baselines/reference/tsserver/updateFileSystem/open-and-close-files.js +++ b/tests/baselines/reference/tsserver/updateFileSystem/open-and-close-files.js @@ -51,6 +51,8 @@ Project '/fshost/b/tsconfig.json' (Configured) ----------------------------------------------- event: {"seq":0,"type":"event","event":"projectLoadingFinish","body":{"projectName":"/fshost/b/tsconfig.json"}} +event: + {"seq":0,"type":"event","event":"telemetry","body":{"telemetryEventName":"projectInfo","payload":{"projectId":"bea14cc795bf8fe7209e389c09ff1c7f1d8288597401b8616cd5edcd3f073d77","fileStats":{"js":0,"jsSize":0,"jsx":0,"jsxSize":0,"ts":4,"tsSize":79,"tsx":0,"tsxSize":0,"dts":0,"dtsSize":0,"deferred":0,"deferredSize":0},"compilerOptions":{},"typeAcquisition":{"enable":false,"include":false,"exclude":false},"extends":false,"files":false,"include":false,"exclude":false,"compileOnSave":false,"configFileName":"tsconfig.json","projectType":"configured","languageServiceEnabled":true,"version":"FakeVersion"}}} event: {"seq":0,"type":"event","event":"configFileDiag","body":{"triggerFile":"/fshost/b/app.ts","configFile":"/fshost/b/tsconfig.json","diagnostics":[{"text":"File '/fshost/lib.d.ts' not found.\n The file is in the program because:\n Default library for target 'es3'","code":6053,"category":"error"},{"text":"Cannot find global type 'Array'.","code":2318,"category":"error"},{"text":"Cannot find global type 'Boolean'.","code":2318,"category":"error"},{"text":"Cannot find global type 'Function'.","code":2318,"category":"error"},{"text":"Cannot find global type 'IArguments'.","code":2318,"category":"error"},{"text":"Cannot find global type 'Number'.","code":2318,"category":"error"},{"text":"Cannot find global type 'Object'.","code":2318,"category":"error"},{"text":"Cannot find global type 'RegExp'.","code":2318,"category":"error"},{"text":"Cannot find global type 'String'.","code":2318,"category":"error"}]}} Project '/fshost/b/tsconfig.json' (Configured) @@ -82,15 +84,19 @@ request:{"command":"updateFileSystem","arguments":{"fileSystem":"memfs","files": response:{"response":true,"responseRequired":true} request:{"command":"updateFileSystem","arguments":{"fileSystem":"memfs","files":[{"file":"/fshost/b/app.ts","fileContent":"import { xyz } from './file3'; let y = xyz"}],"deleted":[]},"seq":5,"type":"request"} response:{"response":true,"responseRequired":true} +DirectoryWatcher:: Triggered with /fshost/b/file4.ts :: WatchInfo: /fshost/b 1 undefined Config: /fshost/b/tsconfig.json WatchType: Wild card directory +Scheduled: /fshost/b/tsconfig.json +Scheduled: *ensureProjectForOpenFiles* +Elapsed:: *ms DirectoryWatcher:: Triggered with /fshost/b/file4.ts :: WatchInfo: /fshost/b 1 undefined Config: /fshost/b/tsconfig.json WatchType: Wild card directory request:{"command":"updateFileSystem","arguments":{"fileSystem":"memfs","files":[],"deleted":["/fshost/b/commonFile1.ts"]},"seq":6,"type":"request"} FileWatcher:: Triggered with /fshost/b/commonFile1.ts 2:: WatchInfo: /fshost/b/commonFile1.ts 500 undefined WatchType: Closed Script info FileWatcher:: Close:: WatchInfo: /fshost/b/commonFile1.ts 500 undefined WatchType: Closed Script info -Scheduled: /fshost/b/tsconfig.json -Scheduled: *ensureProjectForOpenFiles* +Scheduled: /fshost/b/tsconfig.json, Cancelled earlier one +Scheduled: *ensureProjectForOpenFiles*, Cancelled earlier one Elapsed:: *ms FileWatcher:: Triggered with /fshost/b/commonFile1.ts 2:: WatchInfo: /fshost/b/commonFile1.ts 500 undefined WatchType: Closed Script info DirectoryWatcher:: Triggered with /fshost/b/commonFile1.ts :: WatchInfo: /fshost/b 1 undefined Config: /fshost/b/tsconfig.json WatchType: Wild card directory -Scheduled: /fshost/b/tsconfig.json -Scheduled: *ensureProjectForOpenFiles* +Scheduled: /fshost/b/tsconfig.json, Cancelled earlier one +Scheduled: *ensureProjectForOpenFiles*, Cancelled earlier one Elapsed:: *ms DirectoryWatcher:: Triggered with /fshost/b/commonFile1.ts :: WatchInfo: /fshost/b 1 undefined Config: /fshost/b/tsconfig.json WatchType: Wild card directory response:{"response":true,"responseRequired":true} request:{"command":"close","arguments":{"file":"/fshost/b/app.ts"},"seq":7,"type":"request"} diff --git a/tests/baselines/reference/tsserver/updateFileSystem/open-and-close-files.txt b/tests/baselines/reference/tsserver/updateFileSystem/open-and-close-files.txt index f6a29462bd2..92487b80d08 100644 --- a/tests/baselines/reference/tsserver/updateFileSystem/open-and-close-files.txt +++ b/tests/baselines/reference/tsserver/updateFileSystem/open-and-close-files.txt @@ -30,23 +30,96 @@ interface Array { length: number; [n: number]: T; } + #### Opening app.ts + #### Opening file3.ts + #### non-delete + #### change app.ts content //// [/fshost/b/app.ts] import { xyz } from './file3'; let y = xyz + +#### ensure /host/app.ts exists + +//// [/host/b/app.ts] +console.log("hello") + + + +#### modify /host/app.ts + +//// [/host/b/app.ts] +console.log('goodbye') + + + +#### ensure /fshost/file4.ts exists +//// [/fshost/b/file4.ts] +console.log('file4 exists') + + + +1 at time 12000 in 250 ms: /fshost/b/tsconfig.json +2 at time 12000 in 2500 ms: *ensureProjectForOpenFiles* + +#### modify /fshost/file4 +//// [/fshost/b/file4.ts] +console.log('file4 modified') + + + +1 at time 12000 in 250 ms: /fshost/b/tsconfig.json +2 at time 12000 in 2500 ms: *ensureProjectForOpenFiles* + #### delete //// [/fshost/b/commonFile1.ts] deleted +5 at time 12000 in 250 ms: /fshost/b/tsconfig.json +6 at time 12000 in 2500 ms: *ensureProjectForOpenFiles* + #### close + + +5 at time 12000 in 250 ms: /fshost/b/tsconfig.json +6 at time 12000 in 2500 ms: *ensureProjectForOpenFiles* +### fshost watches +WatchedFiles:: +/fshost/b/tsconfig.json: + {"fileName":"/fshost/b/tsconfig.json","pollingInterval":250} +/fshost/b: + {"fileName":"/fshost/b","pollingInterval":500} +/fshost/b/commonfile2.ts: + {"fileName":"/fshost/b/commonFile2.ts","pollingInterval":250} +/fshost/lib.d.ts: + {"fileName":"/fshost/lib.d.ts","pollingInterval":250} +/fshost/b/node_modules/@types: + {"fileName":"/fshost/b/node_modules/@types","pollingInterval":500} +/fshost/b/app.ts: + {"fileName":"/fshost/b/app.ts","pollingInterval":250} + +FsWatches:: +/host/b/app.ts: + {"directoryName":"/host/b"} + +FsWatchesRecursive:: +/fshost/b: + {"directoryName":"/fshost/b"} + +### host watches +WatchedFiles:: + +FsWatches:: + +FsWatchesRecursive::