From 6035d8015a71c9399aaccf06376c917dc7845f19 Mon Sep 17 00:00:00 2001 From: Peter Waldschmidt Date: Fri, 12 Jun 2026 14:17:10 -0400 Subject: [PATCH 1/2] Optional source based test discovery support in C# Dev Kit. --- .../server/roslynLanguageServer.ts | 54 ++++++++++++++++++- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/src/lsptoolshost/server/roslynLanguageServer.ts b/src/lsptoolshost/server/roslynLanguageServer.ts index e9fa5333a..39c0a5ec3 100644 --- a/src/lsptoolshost/server/roslynLanguageServer.ts +++ b/src/lsptoolshost/server/roslynLanguageServer.ts @@ -5,6 +5,7 @@ import * as vscode from 'vscode'; import * as path from 'path'; +import * as fs from 'fs'; import { CancellationToken, LanguageClientOptions, @@ -64,6 +65,21 @@ export class RoslynLanguageServer { */ private static _stopTimeout = 10000; + /** + * The npm component name under which C# Dev Kit exposes the source-based test discovery + * Roslyn-host assembly in its `components` export map. Must match the package name in + * vs-green's server/src/CSDevKit.SourceTestDiscovery/package.json. + */ + private static readonly sourceTestDiscoveryComponentName = '@microsoft/visualstudio-source-test-discovery'; + + /** + * The assembly that implements the source-based test discovery brokered service, loaded into the + * Roslyn language server via `--extension`. Must match the AssemblyName produced by vs-green's + * CSDevKit.SourceTestDiscovery project. + */ + private static readonly sourceTestDiscoveryComponentDll = + 'Microsoft.VisualStudio.CSharpDevKit.SourceTestDiscovery.dll'; + /** * The process Id of the currently running language server process. */ @@ -681,7 +697,11 @@ export class RoslynLanguageServer { // Set command enablement as soon as we know devkit is available. await vscode.commands.executeCommand('setContext', 'dotnet.server.activationContext', 'RoslynDevKit'); - const csharpDevKitArgs = this.getCSharpDevKitExportArgs(additionalExtensionPaths, channel); + const csharpDevKitArgs = this.getCSharpDevKitExportArgs( + additionalExtensionPaths, + channel, + csharpDevKitExtensionExports + ); args = args.concat(csharpDevKitArgs); await this.setupDevKitEnvironment(dotnetInfo.env, csharpDevKitExtensionExports); @@ -908,7 +928,8 @@ export class RoslynLanguageServer { private static getCSharpDevKitExportArgs( additionalExtensionPaths: string[], - channel: vscode.LogOutputChannel + channel: vscode.LogOutputChannel, + csharpDevKitExtensionExports: CSharpDevKitExports ): string[] { const args: string[] = []; @@ -928,6 +949,35 @@ export class RoslynLanguageServer { ); } + // Include the C# Dev Kit source-based test discovery component, if present. Unlike the other + // components above (which ship inside this extension), this one is built and shipped by C# Dev + // Kit itself and surfaced to us via its `components` export map. We load the assembly directly + // from the C# Dev Kit deployment folder, so there is no separate package to acquire here. + // + // It is optional: C# Dev Kit builds that predate this component won't have the map entry, in + // which case we skip it. The consumer of the discovery service is the C# Dev Kit server itself, + // so a marketplace version mismatch is benign — we only forward a path string to Roslyn. + const testDiscoveryComponentPath = + csharpDevKitExtensionExports.components?.[RoslynLanguageServer.sourceTestDiscoveryComponentName]; + if (testDiscoveryComponentPath) { + const testDiscoveryDllPath = path.join( + testDiscoveryComponentPath, + RoslynLanguageServer.sourceTestDiscoveryComponentDll + ); + if (fs.existsSync(testDiscoveryDllPath)) { + channel.info(`Including source-based test discovery component: ${testDiscoveryDllPath}`); + additionalExtensionPaths.push(testDiscoveryDllPath); + } else { + channel.warn( + `C# Dev Kit reported a source-based test discovery component at '${testDiscoveryComponentPath}', but '${testDiscoveryDllPath}' was not found.` + ); + } + } else { + channel.info( + 'Source-based test discovery component was not provided by C# Dev Kit; skipping (this is expected on older C# Dev Kit versions).' + ); + } + return args; } From 590d11c9252feaac53b389022edb40091a2fa9a2 Mon Sep 17 00:00:00 2001 From: Peter Waldschmidt Date: Tue, 16 Jun 2026 15:53:04 -0400 Subject: [PATCH 2/2] Go back to an insertion model. --- .gitignore | 1 + package.json | 7 ++- package.nls.json | 1 + .../extensions/builtInComponents.ts | 6 ++ .../server/roslynLanguageServer.ts | 62 ++++--------------- tasks/packaging/offlinePackagingTasks.ts | 7 +++ tasks/projectPaths.ts | 1 + 7 files changed, 33 insertions(+), 52 deletions(-) diff --git a/.gitignore b/.gitignore index 51f48c4cf..fab283481 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ out .razorExtension/ .vscode-test/ .roslynCopilot/ +.testDiscovery/ msbuild/signing/signJs/*.log msbuild/signing/signVsix/*.log dist/ diff --git a/package.json b/package.json index 3752f98b0..d579c6e04 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,8 @@ "roslyn": "5.9.0-1.26303.15", "omniSharp": "1.39.14", "razorOmnisharp": "7.0.0-preview.23363.1", - "xamlTools": "18.9.11909.33" + "xamlTools": "18.9.11909.33", + "testDiscovery": "0.1.0-dev" }, "main": "./dist/extension", "l10n": "./l10n", @@ -1479,6 +1480,10 @@ "roslynCopilot": { "description": "%configuration.dotnet.server.componentPaths.roslynCopilot%", "type": "string" + }, + "testDiscovery": { + "description": "%configuration.dotnet.server.componentPaths.testDiscovery%", + "type": "string" } }, "default": {} diff --git a/package.nls.json b/package.nls.json index b11039058..224e27a3d 100644 --- a/package.nls.json +++ b/package.nls.json @@ -35,6 +35,7 @@ "configuration.dotnet.server.componentPaths.roslynDevKit": "Overrides the folder path for the .roslynDevKit component of the language server", "configuration.dotnet.server.componentPaths.xamlTools": "Overrides the folder path for the .xamlTools component of the language server", "configuration.dotnet.server.componentPaths.roslynCopilot": "Overrides the folder path for the .roslynCopilot component of the language server", + "configuration.dotnet.server.componentPaths.testDiscovery": "Overrides the folder path for the .testDiscovery component of the language server", "configuration.dotnet.server.componentPaths.razorExtension": "Overrides the folder path for the Razor extension component of the language server", "configuration.dotnet.server.startTimeout": "Specifies a timeout (in ms) for the client to successfully start and connect to the language server.", "configuration.dotnet.server.waitForDebugger": "Passes the --debug flag when launching the server to allow a debugger to be attached. (Requires extension restart)", diff --git a/src/lsptoolshost/extensions/builtInComponents.ts b/src/lsptoolshost/extensions/builtInComponents.ts index 29ec6508c..26d170326 100644 --- a/src/lsptoolshost/extensions/builtInComponents.ts +++ b/src/lsptoolshost/extensions/builtInComponents.ts @@ -35,6 +35,12 @@ export const componentInfo: { [key: string]: ComponentInfo } = { componentDllPaths: ['Microsoft.VisualStudio.Copilot.Roslyn.LanguageServer.dll'], isOptional: true, }, + testDiscovery: { + defaultFolderName: '.testDiscovery', + optionName: 'testDiscovery', + componentDllPaths: ['Microsoft.VisualStudio.CSharpDevKit.SourceTestDiscovery.dll'], + isOptional: true, + }, }; export function getComponentPaths( diff --git a/src/lsptoolshost/server/roslynLanguageServer.ts b/src/lsptoolshost/server/roslynLanguageServer.ts index 39c0a5ec3..b2c896e6f 100644 --- a/src/lsptoolshost/server/roslynLanguageServer.ts +++ b/src/lsptoolshost/server/roslynLanguageServer.ts @@ -5,7 +5,6 @@ import * as vscode from 'vscode'; import * as path from 'path'; -import * as fs from 'fs'; import { CancellationToken, LanguageClientOptions, @@ -65,21 +64,6 @@ export class RoslynLanguageServer { */ private static _stopTimeout = 10000; - /** - * The npm component name under which C# Dev Kit exposes the source-based test discovery - * Roslyn-host assembly in its `components` export map. Must match the package name in - * vs-green's server/src/CSDevKit.SourceTestDiscovery/package.json. - */ - private static readonly sourceTestDiscoveryComponentName = '@microsoft/visualstudio-source-test-discovery'; - - /** - * The assembly that implements the source-based test discovery brokered service, loaded into the - * Roslyn language server via `--extension`. Must match the AssemblyName produced by vs-green's - * CSDevKit.SourceTestDiscovery project. - */ - private static readonly sourceTestDiscoveryComponentDll = - 'Microsoft.VisualStudio.CSharpDevKit.SourceTestDiscovery.dll'; - /** * The process Id of the currently running language server process. */ @@ -697,11 +681,7 @@ export class RoslynLanguageServer { // Set command enablement as soon as we know devkit is available. await vscode.commands.executeCommand('setContext', 'dotnet.server.activationContext', 'RoslynDevKit'); - const csharpDevKitArgs = this.getCSharpDevKitExportArgs( - additionalExtensionPaths, - channel, - csharpDevKitExtensionExports - ); + const csharpDevKitArgs = this.getCSharpDevKitExportArgs(additionalExtensionPaths, channel); args = args.concat(csharpDevKitArgs); await this.setupDevKitEnvironment(dotnetInfo.env, csharpDevKitExtensionExports); @@ -928,8 +908,7 @@ export class RoslynLanguageServer { private static getCSharpDevKitExportArgs( additionalExtensionPaths: string[], - channel: vscode.LogOutputChannel, - csharpDevKitExtensionExports: CSharpDevKitExports + channel: vscode.LogOutputChannel ): string[] { const args: string[] = []; @@ -949,34 +928,15 @@ export class RoslynLanguageServer { ); } - // Include the C# Dev Kit source-based test discovery component, if present. Unlike the other - // components above (which ship inside this extension), this one is built and shipped by C# Dev - // Kit itself and surfaced to us via its `components` export map. We load the assembly directly - // from the C# Dev Kit deployment folder, so there is no separate package to acquire here. - // - // It is optional: C# Dev Kit builds that predate this component won't have the map entry, in - // which case we skip it. The consumer of the discovery service is the C# Dev Kit server itself, - // so a marketplace version mismatch is benign — we only forward a path string to Roslyn. - const testDiscoveryComponentPath = - csharpDevKitExtensionExports.components?.[RoslynLanguageServer.sourceTestDiscoveryComponentName]; - if (testDiscoveryComponentPath) { - const testDiscoveryDllPath = path.join( - testDiscoveryComponentPath, - RoslynLanguageServer.sourceTestDiscoveryComponentDll - ); - if (fs.existsSync(testDiscoveryDllPath)) { - channel.info(`Including source-based test discovery component: ${testDiscoveryDllPath}`); - additionalExtensionPaths.push(testDiscoveryDllPath); - } else { - channel.warn( - `C# Dev Kit reported a source-based test discovery component at '${testDiscoveryComponentPath}', but '${testDiscoveryDllPath}' was not found.` - ); - } - } else { - channel.info( - 'Source-based test discovery component was not provided by C# Dev Kit; skipping (this is expected on older C# Dev Kit versions).' - ); - } + // Also include the C# Dev Kit source-based test discovery extension, if present. The component + // is built by C# Dev Kit (vs-green), published as a NuGet package, and restored into this + // extension at build time (see allNugetPackages in offlinePackagingTasks.ts), using the same + // mechanism as the Xaml tools and roslynDevKit components above. It is optional: builds + // that don't restore the package won't have the folder, in which case getComponentPaths returns + // an empty array and we skip it. The consumer of the discovery service is the C# Dev Kit server. + getComponentPaths('testDiscovery', languageServerOptions, channel).forEach((path) => + additionalExtensionPaths.push(path) + ); return args; } diff --git a/tasks/packaging/offlinePackagingTasks.ts b/tasks/packaging/offlinePackagingTasks.ts index b74b66964..a31034395 100644 --- a/tasks/packaging/offlinePackagingTasks.ts +++ b/tasks/packaging/offlinePackagingTasks.ts @@ -23,6 +23,7 @@ import { rootPath, devKitDependenciesDirectory, xamlToolsDirectory, + testDiscoveryDirectory, } from '../projectPaths'; import { getPackageJSON } from '../packageJson'; import { createPackageAsync, generateVsixManifest } from './vsceTasks'; @@ -91,6 +92,12 @@ export const allNugetPackages: { [key: string]: NugetPackageInfo } = { getPackageContentPath: (_platformInfo) => 'content', vsixOutputPath: xamlToolsDirectory, }, + testDiscovery: { + getPackageName: (_platformInfo) => 'Microsoft.VisualStudio.CSharpDevKit.SourceTestDiscovery', + packageJsonName: 'testDiscovery', + getPackageContentPath: (_platformInfo) => 'content', + vsixOutputPath: testDiscoveryDirectory, + }, }; interface PlatformEntry { diff --git a/tasks/projectPaths.ts b/tasks/projectPaths.ts index a2362f9b0..0972ed0bf 100644 --- a/tasks/projectPaths.ts +++ b/tasks/projectPaths.ts @@ -18,6 +18,7 @@ export const nugetTempPath = path.join(rootPath, 'out', '.nuget'); export const languageServerDirectory = path.join(rootPath, '.roslyn'); export const devKitDependenciesDirectory = path.join(rootPath, componentInfo.roslynDevKit.defaultFolderName); export const xamlToolsDirectory = path.join(rootPath, componentInfo.xamlTools.defaultFolderName); +export const testDiscoveryDirectory = path.join(rootPath, componentInfo.testDiscovery.defaultFolderName); export const codeExtensionPath = commandLineOptions.codeExtensionPath || rootPath;