-
Notifications
You must be signed in to change notification settings - Fork 12.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add back tests for async plugin that got deleted as part of #51699
- Loading branch information
1 parent
6c3239c
commit 4e878d1
Showing
8 changed files
with
578 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
import * as ts from "../../_namespaces/ts"; | ||
import { defer } from "../../_namespaces/Utils"; | ||
import { | ||
baselineTsserverLogs, | ||
closeFilesForSession, | ||
createLoggerWithInMemoryLogs, | ||
createSession, | ||
openFilesForSession, | ||
} from "../helpers/tsserver"; | ||
import { createServerHost, libFile } from "../helpers/virtualFileSystemWithWatch"; | ||
|
||
describe("unittests:: tsserver:: pluginsAsync:: async loaded plugins", () => { | ||
function setup(globalPlugins: string[]) { | ||
const host = createServerHost([libFile]); | ||
const session = createSession(host, { canUseEvents: true, globalPlugins, logger: createLoggerWithInMemoryLogs(host) }); | ||
return { host, session }; | ||
} | ||
|
||
it("plugins are not loaded immediately", async () => { | ||
const { host, session } = setup(["plugin-a"]); | ||
let pluginModuleInstantiated = false; | ||
let pluginInvoked = false; | ||
host.importPlugin = async (_root: string, _moduleName: string): Promise<ts.server.ModuleImportResult> => { | ||
await Promise.resolve(); // simulate at least a single turn delay | ||
pluginModuleInstantiated = true; | ||
return { | ||
module: (() => { | ||
pluginInvoked = true; | ||
return { create: info => info.languageService }; | ||
}) as ts.server.PluginModuleFactory, | ||
error: undefined | ||
}; | ||
}; | ||
|
||
openFilesForSession([{ file: "^memfs:/foo.ts", content: "" }], session); | ||
const projectService = session.getProjectService(); | ||
|
||
session.logger.log(`This should be false because 'executeCommand' should have already triggered plugin enablement asynchronously and there are no plugin enablements currently being processed`); | ||
session.logger.log(`hasNewPluginEnablementRequests:: ${projectService.hasNewPluginEnablementRequests()}`); | ||
|
||
session.logger.log(`Should be true because async imports have already been triggered in the background`); | ||
session.logger.log(`hasPendingPluginEnablements:: ${projectService.hasPendingPluginEnablements()}`); | ||
|
||
session.logger.log(`Should be false because resolution of async imports happens in a later turn`); | ||
session.logger.log(`pluginModuleInstantiated:: ${pluginModuleInstantiated}`); | ||
|
||
await projectService.waitForPendingPlugins(); | ||
|
||
session.logger.log(`at this point all plugin modules should have been instantiated and all plugins should have been invoked`); | ||
session.logger.log(`pluginModuleInstantiated:: ${pluginModuleInstantiated}`); | ||
session.logger.log(`pluginInvoked:: ${pluginInvoked}`); | ||
|
||
baselineTsserverLogs("pluginsAsync", "plugins are not loaded immediately", session); | ||
}); | ||
|
||
it("plugins evaluation in correct order even if imports resolve out of order", async () => { | ||
const { host, session } = setup(["plugin-a", "plugin-b"]); | ||
const pluginADeferred = defer(); | ||
const pluginBDeferred = defer(); | ||
host.importPlugin = async (_root: string, moduleName: string): Promise<ts.server.ModuleImportResult> => { | ||
session.logger.log(`request import ${moduleName}`); | ||
const promise = moduleName === "plugin-a" ? pluginADeferred.promise : pluginBDeferred.promise; | ||
await promise; | ||
session.logger.log(`fulfill import ${moduleName}`); | ||
return { | ||
module: (() => { | ||
session.logger.log(`invoke plugin ${moduleName}`); | ||
return { create: info => info.languageService }; | ||
}) as ts.server.PluginModuleFactory, | ||
error: undefined | ||
}; | ||
}; | ||
|
||
openFilesForSession([{ file: "^memfs:/foo.ts", content: "" }], session); | ||
const projectService = session.getProjectService(); | ||
|
||
// wait a turn | ||
await Promise.resolve(); | ||
|
||
// resolve imports out of order | ||
pluginBDeferred.resolve(); | ||
pluginADeferred.resolve(); | ||
|
||
// wait for load to complete | ||
await projectService.waitForPendingPlugins(); | ||
|
||
baselineTsserverLogs("pluginsAsync", "plugins evaluation in correct order even if imports resolve out of order", session); | ||
}); | ||
|
||
it("sends projectsUpdatedInBackground event", async () => { | ||
const { host, session } = setup(["plugin-a"]); | ||
host.importPlugin = async (_root: string, _moduleName: string): Promise<ts.server.ModuleImportResult> => { | ||
await Promise.resolve(); // simulate at least a single turn delay | ||
return { | ||
module: (() => ({ create: info => info.languageService })) as ts.server.PluginModuleFactory, | ||
error: undefined | ||
}; | ||
}; | ||
|
||
openFilesForSession([{ file: "^memfs:/foo.ts", content: "" }], session); | ||
const projectService = session.getProjectService(); | ||
|
||
|
||
await projectService.waitForPendingPlugins(); | ||
|
||
baselineTsserverLogs("pluginsAsync", "sends projectsUpdatedInBackground event", session); | ||
}); | ||
|
||
it("adds external files", async () => { | ||
const { host, session } = setup(["plugin-a"]); | ||
const pluginAShouldLoad = defer(); | ||
host.importPlugin = async (_root: string, _moduleName: string): Promise<ts.server.ModuleImportResult> => { | ||
// wait until the initial external files are requested from the project service. | ||
await pluginAShouldLoad.promise; | ||
|
||
return { | ||
module: (() => ({ | ||
create: info => info.languageService, | ||
getExternalFiles: () => ["external.txt"], | ||
})) as ts.server.PluginModuleFactory, | ||
error: undefined | ||
}; | ||
}; | ||
|
||
openFilesForSession([{ file: "^memfs:/foo.ts", content: "" }], session); | ||
const projectService = session.getProjectService(); | ||
|
||
const project = projectService.inferredProjects[0]; | ||
|
||
session.logger.log(`External files before plugin is loaded: ${project.getExternalFiles().join(",")}`); | ||
// we've ready the initial set of external files, allow the plugin to continue loading. | ||
pluginAShouldLoad.resolve(); | ||
|
||
// wait for plugins | ||
await projectService.waitForPendingPlugins(); | ||
|
||
host.runQueuedTimeoutCallbacks(); | ||
|
||
session.logger.log(`External files before plugin after plugin is loaded: ${project.getExternalFiles().join(",")}`); | ||
baselineTsserverLogs("pluginsAsync", "adds external files", session); | ||
}); | ||
|
||
it("project is closed before plugins are loaded", async () => { | ||
const { host, session } = setup(["plugin-a"]); | ||
const pluginALoaded = defer(); | ||
const projectClosed = defer(); | ||
host.importPlugin = async (_root: string, _moduleName: string): Promise<ts.server.ModuleImportResult> => { | ||
// mark that the plugin has started loading | ||
pluginALoaded.resolve(); | ||
|
||
// wait until after a project close has been requested to continue | ||
await projectClosed.promise; | ||
return { | ||
module: (() => ({ create: info => info.languageService })) as ts.server.PluginModuleFactory, | ||
error: undefined | ||
}; | ||
}; | ||
|
||
openFilesForSession([{ file: "^memfs:/foo.ts", content: "" }], session); | ||
const projectService = session.getProjectService(); | ||
|
||
|
||
// wait for the plugin to start loading | ||
await pluginALoaded.promise; | ||
|
||
// close the project | ||
closeFilesForSession(["^memfs:/foo.ts"], session); | ||
|
||
// continue loading the plugin | ||
projectClosed.resolve(); | ||
|
||
await projectService.waitForPendingPlugins(); | ||
|
||
// the project was closed before plugins were ready. no project update should have been requested | ||
baselineTsserverLogs("pluginsAsync", "project is closed before plugins are loaded", session); | ||
}); | ||
}); |
83 changes: 83 additions & 0 deletions
83
tests/baselines/reference/tsserver/pluginsAsync/adds-external-files.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
currentDirectory:: / useCaseSensitiveFileNames: false | ||
Info seq [hh:mm:ss:mss] Provided types map file "/a/lib/typesMap.json" doesn't exist | ||
Before request | ||
//// [/a/lib/lib.d.ts] | ||
/// <reference no-default-lib="true"/> | ||
interface Boolean {} | ||
interface Function {} | ||
interface CallableFunction {} | ||
interface NewableFunction {} | ||
interface IArguments {} | ||
interface Number { toExponential: any; } | ||
interface Object {} | ||
interface RegExp {} | ||
interface String { charAt: any; } | ||
interface Array<T> { length: number; [n: number]: T; } | ||
Info seq [hh:mm:ss:mss] request: | ||
{ | ||
"command": "open", | ||
"arguments": { | ||
"file": "^memfs:/foo.ts", | ||
"fileContent": "" | ||
}, | ||
"seq": 1, | ||
"type": "request" | ||
} | ||
Info seq [hh:mm:ss:mss] Search path: ^memfs: | ||
Info seq [hh:mm:ss:mss] For info: ^memfs:/foo.ts :: No config files found. | ||
Info seq [hh:mm:ss:mss] Loading global plugin plugin-a | ||
Info seq [hh:mm:ss:mss] Enabling plugin plugin-a from candidate paths: /a/lib/tsc.js/../../.. | ||
Info seq [hh:mm:ss:mss] Dynamically importing plugin-a from /a/lib/tsc.js/../../.. (resolved to /a/lib/tsc.js/../../../node_modules) | ||
Info seq [hh:mm:ss:mss] Starting updateGraphWorker: Project: /dev/null/inferredProject1* | ||
Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /a/lib/lib.d.ts 500 undefined WatchType: Closed Script info | ||
Info seq [hh:mm:ss:mss] Finishing updateGraphWorker: Project: /dev/null/inferredProject1* Version: 1 structureChanged: true structureIsReused:: Not Elapsed:: *ms | ||
Info seq [hh:mm:ss:mss] Project '/dev/null/inferredProject1*' (Inferred) | ||
Info seq [hh:mm:ss:mss] Files (2) | ||
/a/lib/lib.d.ts Text-1 "/// <reference no-default-lib=\"true\"/>\ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array<T> { length: number; [n: number]: T; }" | ||
^memfs:/foo.ts SVC-1-0 "" | ||
a/lib/lib.d.ts | ||
Default library for target 'es5' | ||
^memfs:/foo.ts | ||
Root file specified for compilation | ||
Info seq [hh:mm:ss:mss] ----------------------------------------------- | ||
Info seq [hh:mm:ss:mss] Project '/dev/null/inferredProject1*' (Inferred) | ||
Info seq [hh:mm:ss:mss] Files (2) | ||
|
||
Info seq [hh:mm:ss:mss] ----------------------------------------------- | ||
Info seq [hh:mm:ss:mss] Open files: | ||
Info seq [hh:mm:ss:mss] FileName: ^memfs:/foo.ts ProjectRootPath: undefined | ||
Info seq [hh:mm:ss:mss] Projects: /dev/null/inferredProject1* | ||
Info seq [hh:mm:ss:mss] response: | ||
{ | ||
"responseRequired": false | ||
} | ||
After request | ||
|
||
FsWatches:: | ||
/a/lib/lib.d.ts: *new* | ||
{} | ||
|
||
External files before plugin is loaded: | ||
Info seq [hh:mm:ss:mss] Plugin validation succeeded | ||
Info seq [hh:mm:ss:mss] Scheduled: /dev/null/inferredProject1* | ||
Info seq [hh:mm:ss:mss] got projects updated in background, updating diagnostics for ^memfs:/foo.ts | ||
Info seq [hh:mm:ss:mss] event: | ||
{"seq":0,"type":"event","event":"projectsUpdatedInBackground","body":{"openFiles":["^memfs:/foo.ts"]}} | ||
Before running Timeout callback:: count: 2 | ||
1: /dev/null/inferredProject1* | ||
2: checkOne | ||
|
||
Info seq [hh:mm:ss:mss] Running: /dev/null/inferredProject1* | ||
Info seq [hh:mm:ss:mss] Starting updateGraphWorker: Project: /dev/null/inferredProject1* | ||
Info seq [hh:mm:ss:mss] Finishing updateGraphWorker: Project: /dev/null/inferredProject1* Version: 2 structureChanged: false structureIsReused:: Not Elapsed:: *ms | ||
Info seq [hh:mm:ss:mss] Same program as before | ||
Info seq [hh:mm:ss:mss] event: | ||
{"seq":0,"type":"event","event":"syntaxDiag","body":{"file":"^memfs:/foo.ts","diagnostics":[]}} | ||
After running Timeout callback:: count: 0 | ||
|
||
External files before plugin after plugin is loaded: external.txt |
78 changes: 78 additions & 0 deletions
78
tests/baselines/reference/tsserver/pluginsAsync/plugins-are-not-loaded-immediately.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
currentDirectory:: / useCaseSensitiveFileNames: false | ||
Info seq [hh:mm:ss:mss] Provided types map file "/a/lib/typesMap.json" doesn't exist | ||
Before request | ||
//// [/a/lib/lib.d.ts] | ||
/// <reference no-default-lib="true"/> | ||
interface Boolean {} | ||
interface Function {} | ||
interface CallableFunction {} | ||
interface NewableFunction {} | ||
interface IArguments {} | ||
interface Number { toExponential: any; } | ||
interface Object {} | ||
interface RegExp {} | ||
interface String { charAt: any; } | ||
interface Array<T> { length: number; [n: number]: T; } | ||
Info seq [hh:mm:ss:mss] request: | ||
{ | ||
"command": "open", | ||
"arguments": { | ||
"file": "^memfs:/foo.ts", | ||
"fileContent": "" | ||
}, | ||
"seq": 1, | ||
"type": "request" | ||
} | ||
Info seq [hh:mm:ss:mss] Search path: ^memfs: | ||
Info seq [hh:mm:ss:mss] For info: ^memfs:/foo.ts :: No config files found. | ||
Info seq [hh:mm:ss:mss] Loading global plugin plugin-a | ||
Info seq [hh:mm:ss:mss] Enabling plugin plugin-a from candidate paths: /a/lib/tsc.js/../../.. | ||
Info seq [hh:mm:ss:mss] Dynamically importing plugin-a from /a/lib/tsc.js/../../.. (resolved to /a/lib/tsc.js/../../../node_modules) | ||
Info seq [hh:mm:ss:mss] Starting updateGraphWorker: Project: /dev/null/inferredProject1* | ||
Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /a/lib/lib.d.ts 500 undefined WatchType: Closed Script info | ||
Info seq [hh:mm:ss:mss] Finishing updateGraphWorker: Project: /dev/null/inferredProject1* Version: 1 structureChanged: true structureIsReused:: Not Elapsed:: *ms | ||
Info seq [hh:mm:ss:mss] Project '/dev/null/inferredProject1*' (Inferred) | ||
Info seq [hh:mm:ss:mss] Files (2) | ||
/a/lib/lib.d.ts Text-1 "/// <reference no-default-lib=\"true\"/>\ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array<T> { length: number; [n: number]: T; }" | ||
^memfs:/foo.ts SVC-1-0 "" | ||
a/lib/lib.d.ts | ||
Default library for target 'es5' | ||
^memfs:/foo.ts | ||
Root file specified for compilation | ||
Info seq [hh:mm:ss:mss] ----------------------------------------------- | ||
Info seq [hh:mm:ss:mss] Project '/dev/null/inferredProject1*' (Inferred) | ||
Info seq [hh:mm:ss:mss] Files (2) | ||
Info seq [hh:mm:ss:mss] ----------------------------------------------- | ||
Info seq [hh:mm:ss:mss] Open files: | ||
Info seq [hh:mm:ss:mss] FileName: ^memfs:/foo.ts ProjectRootPath: undefined | ||
Info seq [hh:mm:ss:mss] Projects: /dev/null/inferredProject1* | ||
Info seq [hh:mm:ss:mss] response: | ||
{ | ||
"responseRequired": false | ||
} | ||
After request | ||
FsWatches:: | ||
/a/lib/lib.d.ts: *new* | ||
{} | ||
This should be false because 'executeCommand' should have already triggered plugin enablement asynchronously and there are no plugin enablements currently being processed | ||
hasNewPluginEnablementRequests:: false | ||
Should be true because async imports have already been triggered in the background | ||
hasPendingPluginEnablements:: true | ||
Should be false because resolution of async imports happens in a later turn | ||
pluginModuleInstantiated:: false | ||
Info seq [hh:mm:ss:mss] Plugin validation succeeded | ||
Info seq [hh:mm:ss:mss] Scheduled: /dev/null/inferredProject1* | ||
Info seq [hh:mm:ss:mss] got projects updated in background, updating diagnostics for ^memfs:/foo.ts | ||
Info seq [hh:mm:ss:mss] event: | ||
{"seq":0,"type":"event","event":"projectsUpdatedInBackground","body":{"openFiles":["^memfs:/foo.ts"]}} | ||
at this point all plugin modules should have been instantiated and all plugins should have been invoked | ||
pluginModuleInstantiated:: true | ||
pluginInvoked:: true |
Oops, something went wrong.