From 4e29f57a36ae7e6084d1d09516c6e16e610d0542 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Fri, 12 Jul 2024 08:58:04 +1000 Subject: [PATCH 1/8] Logging --- .../Cohost/ShortCircuitingRemoteServiceProvider.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/ShortCircuitingRemoteServiceProvider.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/ShortCircuitingRemoteServiceProvider.cs index e49bb65bbed..bfb2acb58ea 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/ShortCircuitingRemoteServiceProvider.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/ShortCircuitingRemoteServiceProvider.cs @@ -73,6 +73,7 @@ private static Dictionary BuildFactoryMap() var solutionInfo = new RazorPinnedSolutionInfoWrapper(); testOutputHelper.WriteLine($"Pretend OOP call for {typeof(TService).Name}, invocation: {Path.GetFileNameWithoutExtension(callerFilePath)}.{callerMemberName}"); + testOutputHelper.WriteLine($"Project assembly path: `{solution.Projects.First().CompilationOutputInfo.AssemblyPath ?? "null"}`"); return await invocation(service, solutionInfo, cancellationToken); } } From cbc5e4aa85167707f2972bc75345792dc57fbe45 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Fri, 12 Jul 2024 10:27:42 +1000 Subject: [PATCH 2/8] Stop loggign from OOP in tests, because logging in OOP is dodgy --- .../ShortCircuitingRemoteServiceProvider.cs | 4 +- .../Cohost/TestTraceSourceProvider.cs | 52 ------------------- 2 files changed, 3 insertions(+), 53 deletions(-) delete mode 100644 src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/TestTraceSourceProvider.cs diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/ShortCircuitingRemoteServiceProvider.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/ShortCircuitingRemoteServiceProvider.cs index bfb2acb58ea..128d2d6815c 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/ShortCircuitingRemoteServiceProvider.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/ShortCircuitingRemoteServiceProvider.cs @@ -3,11 +3,13 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Razor.Test.Common.VisualStudio; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.ExternalAccess.Razor; using Microsoft.CodeAnalysis.Razor.Remote; @@ -26,7 +28,7 @@ internal class ShortCircuitingRemoteServiceProvider(ITestOutputHelper testOutput { private static readonly Dictionary s_factoryMap = BuildFactoryMap(); - private readonly IServiceProvider _serviceProvider = new TestTraceSourceProvider(testOutputHelper); + private readonly IServiceProvider _serviceProvider = VsMocks.CreateServiceProvider(b => b.AddService(serviceInstance: null)); private static Dictionary BuildFactoryMap() { diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/TestTraceSourceProvider.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/TestTraceSourceProvider.cs deleted file mode 100644 index b61b1a61d3c..00000000000 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/TestTraceSourceProvider.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root for license information. - -using System; -using System.Diagnostics; -using Xunit.Abstractions; - -namespace Microsoft.VisualStudio.LanguageServices.Razor.Test.Cohost; - -/// -/// An implementation of IServiceProvider that only provides a TraceSource, that writes to test output -/// -internal class TestTraceSourceProvider(ITestOutputHelper testOutputHelper) : IServiceProvider -{ - public object GetService(Type serviceType) - { - if (serviceType == typeof(TraceSource)) - { - return new TestOutputTraceSource(testOutputHelper); - } - - throw new NotImplementedException(); - } - - private class TestOutputTraceSource : TraceSource - { - public TestOutputTraceSource(ITestOutputHelper testOutputHelper) - : base("OOP", SourceLevels.All) - { - Listeners.Add(new TestOutputTraceListener(testOutputHelper)); - } - - private class TestOutputTraceListener(ITestOutputHelper testOutputHelper) : TraceListener - { - public override void Write(string message) - { - // ITestOutputHelper doesn't have a Write method, but all we lose is some extra ServiceHub details like log level - } - - public override void WriteLine(string message) - { - // Ignore some specific ServiceHub noise, since we're not using ServiceHub anyway - if (message.StartsWith("Added local RPC method") || message == "Listening started.") - { - return; - } - - testOutputHelper.WriteLine(message); - } - } - } -} From d408918897e214472a48e4ba81c942242ddc23f6 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Fri, 12 Jul 2024 10:27:55 +1000 Subject: [PATCH 3/8] =?UTF-8?q?Dispose=20of=20disposables=20=F0=9F=A4=A6?= =?UTF-8?q?=E2=80=8D=E2=99=82=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Cohost/ShortCircuitingRemoteServiceProvider.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/ShortCircuitingRemoteServiceProvider.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/ShortCircuitingRemoteServiceProvider.cs index 128d2d6815c..fefdd323c1e 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/ShortCircuitingRemoteServiceProvider.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/ShortCircuitingRemoteServiceProvider.cs @@ -69,13 +69,13 @@ private static Dictionary BuildFactoryMap() // We don't ever use this stream, because we never really use ServiceHub, but going through its factory method means the // remote services under test are using their full MEF composition etc. so we get excellent coverage. var (stream, _) = FullDuplexStream.CreatePair(); - var service = (TService)await factory.CreateAsync(stream, _serviceProvider, serviceActivationOptions: default, testServiceBroker, authorizationServiceClient: default!); + using var service = (IDisposable)await factory.CreateAsync(stream, _serviceProvider, serviceActivationOptions: default, testServiceBroker, authorizationServiceClient: default!); // This is never used, we short-circuited things by passing the solution direct to the InterceptingServiceBroker var solutionInfo = new RazorPinnedSolutionInfoWrapper(); testOutputHelper.WriteLine($"Pretend OOP call for {typeof(TService).Name}, invocation: {Path.GetFileNameWithoutExtension(callerFilePath)}.{callerMemberName}"); testOutputHelper.WriteLine($"Project assembly path: `{solution.Projects.First().CompilationOutputInfo.AssemblyPath ?? "null"}`"); - return await invocation(service, solutionInfo, cancellationToken); + return await invocation((TService)service, solutionInfo, cancellationToken); } } From 5090e74580641ebdc445b67beeceaa553f8a7630 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Fri, 12 Jul 2024 10:27:59 +1000 Subject: [PATCH 4/8] Whitespace --- .../RazorServiceFactoryBase.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RazorServiceFactoryBase.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RazorServiceFactoryBase.cs index 23f1e83b20f..c3c7e248391 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RazorServiceFactoryBase.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RazorServiceFactoryBase.cs @@ -35,11 +35,11 @@ public RazorServiceFactoryBase(RazorServiceDescriptorsWrapper razorServiceDescri } public async Task CreateAsync( - Stream stream, - IServiceProvider hostProvidedServices, - ServiceActivationOptions serviceActivationOptions, - IServiceBroker serviceBroker, - AuthorizationServiceClient? authorizationServiceClient) + Stream stream, + IServiceProvider hostProvidedServices, + ServiceActivationOptions serviceActivationOptions, + IServiceBroker serviceBroker, + AuthorizationServiceClient? authorizationServiceClient) { // Dispose the AuthorizationServiceClient since we won't be using it authorizationServiceClient?.Dispose(); From c78deb4c25efec5cdb9ed203954cdd283d15537b Mon Sep 17 00:00:00 2001 From: David Wengier Date: Fri, 12 Jul 2024 11:48:16 +1000 Subject: [PATCH 5/8] Tell the compiler that services are disposable --- .../Remote/IRemoteClientInitializationService.cs | 3 ++- .../Remote/IRemoteFoldingRangeService.cs | 3 ++- .../Remote/IRemoteHtmlDocumentService.cs | 3 ++- .../Remote/IRemoteLinkedEditingRangeService.cs | 3 ++- .../Remote/IRemoteSemanticTokensService.cs | 2 +- .../Remote/IRemoteServiceProvider.cs | 2 +- .../Remote/IRemoteTagHelperProviderService.cs | 3 ++- .../Remote/IRemoteUriPresentationService.cs | 2 +- .../Remote/RemoteServiceProvider.cs | 2 +- 9 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteClientInitializationService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteClientInitializationService.cs index 8089bc68099..32c9d4bd1ac 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteClientInitializationService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteClientInitializationService.cs @@ -1,12 +1,13 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. +using System; using System.Threading; using System.Threading.Tasks; namespace Microsoft.CodeAnalysis.Razor.Remote; -internal interface IRemoteClientInitializationService +internal interface IRemoteClientInitializationService : IDisposable { ValueTask InitializeAsync(RemoteClientInitializationOptions initializationOptions, CancellationToken cancellationToken); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteFoldingRangeService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteFoldingRangeService.cs index f709ed98631..c3368da527e 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteFoldingRangeService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteFoldingRangeService.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. +using System; using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; @@ -9,7 +10,7 @@ namespace Microsoft.CodeAnalysis.Razor.Remote; -internal interface IRemoteFoldingRangeService +internal interface IRemoteFoldingRangeService : IDisposable { ValueTask> GetFoldingRangesAsync(RazorPinnedSolutionInfoWrapper solutionInfo, DocumentId documentId, ImmutableArray htmlRanges, CancellationToken cancellationToken); } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteHtmlDocumentService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteHtmlDocumentService.cs index 832a851949a..de87623fe74 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteHtmlDocumentService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteHtmlDocumentService.cs @@ -1,13 +1,14 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. +using System; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.ExternalAccess.Razor; namespace Microsoft.CodeAnalysis.Razor.Remote; -internal interface IRemoteHtmlDocumentService +internal interface IRemoteHtmlDocumentService : IDisposable { ValueTask GetHtmlDocumentTextAsync(RazorPinnedSolutionInfoWrapper solutionInfo, DocumentId razorDocumentId, CancellationToken cancellationToken); } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteLinkedEditingRangeService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteLinkedEditingRangeService.cs index 0520974b6fd..87f94ac9f10 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteLinkedEditingRangeService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteLinkedEditingRangeService.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. +using System; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.ExternalAccess.Razor; @@ -8,7 +9,7 @@ namespace Microsoft.CodeAnalysis.Razor.Remote; -internal interface IRemoteLinkedEditingRangeService +internal interface IRemoteLinkedEditingRangeService : IDisposable { ValueTask GetRangesAsync(RazorPinnedSolutionInfoWrapper solutionInfo, DocumentId razorDocumentId, LinePosition linePosition, CancellationToken cancellationToken); } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteSemanticTokensService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteSemanticTokensService.cs index 19778bfcfea..1d2f1b0a8ea 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteSemanticTokensService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteSemanticTokensService.cs @@ -9,7 +9,7 @@ namespace Microsoft.CodeAnalysis.Razor.Remote; -internal interface IRemoteSemanticTokensService +internal interface IRemoteSemanticTokensService : IDisposable { ValueTask GetSemanticTokensDataAsync( RazorPinnedSolutionInfoWrapper solutionInfo, diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteServiceProvider.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteServiceProvider.cs index a2cf6917beb..e8241e0aa2c 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteServiceProvider.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteServiceProvider.cs @@ -17,5 +17,5 @@ internal interface IRemoteServiceProvider CancellationToken cancellationToken, [CallerFilePath] string? callerFilePath = null, [CallerMemberName] string? callerMemberName = null) - where TService : class; + where TService : class, IDisposable; } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteTagHelperProviderService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteTagHelperProviderService.cs index eb17de0887c..b2ddfd2af45 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteTagHelperProviderService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteTagHelperProviderService.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. +using System; using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; @@ -10,7 +11,7 @@ namespace Microsoft.CodeAnalysis.Razor.Remote; -internal interface IRemoteTagHelperProviderService +internal interface IRemoteTagHelperProviderService : IDisposable { ValueTask GetTagHelpersDeltaAsync( RazorPinnedSolutionInfoWrapper solutionInfo, diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteUriPresentationService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteUriPresentationService.cs index 4c9e3707bef..1d13c9c5743 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteUriPresentationService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteUriPresentationService.cs @@ -9,7 +9,7 @@ namespace Microsoft.CodeAnalysis.Razor.Remote; -internal interface IRemoteUriPresentationService +internal interface IRemoteUriPresentationService : IDisposable { ValueTask GetPresentationAsync(RazorPinnedSolutionInfoWrapper solutionInfo, DocumentId razorDocumentId, LinePositionSpan span, Uri[]? uris, CancellationToken cancellationToken); } diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Remote/RemoteServiceProvider.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Remote/RemoteServiceProvider.cs index 77361646138..bbbb114f011 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Remote/RemoteServiceProvider.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Remote/RemoteServiceProvider.cs @@ -47,7 +47,7 @@ internal sealed class RemoteServiceProvider( CancellationToken cancellationToken, [CallerFilePath] string? callerFilePath = null, [CallerMemberName] string? callerMemberName = null) - where TService : class + where TService : class, IDisposable { var client = await TryGetClientAsync(cancellationToken).ConfigureAwait(false); if (client is null) From b1f89e0e025e5a0ef25f5c2abffa62228df52dbd Mon Sep 17 00:00:00 2001 From: David Wengier Date: Fri, 12 Jul 2024 11:49:59 +1000 Subject: [PATCH 6/8] Abstract IServiceBroker away so that we can test services without needing anything from service broker --- .../RemoteFoldingRangeService.cs | 3 +- .../RemoteFoldingRangeServiceFactory.cs | 3 +- .../RemoteHtmlDocumentService.cs | 3 +- .../RemoteHtmlDocumentServiceFactory.cs | 3 +- ...eInterceptor.cs => IRazorServiceBroker.cs} | 2 +- .../RemoteClientInitializationService.cs | 3 +- ...emoteClientInitializationServiceFactory.cs | 3 +- .../RemoteLinkedEditingRangeService.cs | 3 +- .../RemoteLinkedEditingRangeServiceFactory.cs | 3 +- .../RazorDocumentServiceBase.cs | 3 +- .../RazorServiceBase.cs | 16 ++++------ .../RazorServiceBroker.cs | 32 +++++++++++++++++++ .../RazorServiceFactoryBase.cs | 17 ++++++++-- .../RemoteSemanticTokensService.cs | 3 +- .../RemoteSemanticTokensServiceFactory.cs | 3 +- .../RemoteTagHelperProviderService.cs | 3 +- .../RemoteTagHelperProviderServiceFactory.cs | 3 +- .../RemoteUriPresentationService.cs | 3 +- .../RemoteUriPresentationServiceFactory.cs | 3 +- 19 files changed, 69 insertions(+), 43 deletions(-) rename src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/{IBrokeredServiceInterceptor.cs => IRazorServiceBroker.cs} (93%) create mode 100644 src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RazorServiceBroker.cs diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/FoldingRanges/RemoteFoldingRangeService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/FoldingRanges/RemoteFoldingRangeService.cs index 5d8f4264552..0d171ddc3d1 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/FoldingRanges/RemoteFoldingRangeService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/FoldingRanges/RemoteFoldingRangeService.cs @@ -11,13 +11,12 @@ using Microsoft.CodeAnalysis.Razor.Remote; using Microsoft.CodeAnalysis.Razor.Workspaces; using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; -using Microsoft.ServiceHub.Framework; using Microsoft.VisualStudio.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.Remote.Razor; internal sealed class RemoteFoldingRangeService( - IServiceBroker serviceBroker, + IRazorServiceBroker serviceBroker, IFoldingRangeService foldingRangeService, DocumentSnapshotFactory documentSnapshotFactory, IFilePathService filePathService) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/FoldingRanges/RemoteFoldingRangeServiceFactory.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/FoldingRanges/RemoteFoldingRangeServiceFactory.cs index e0fbb4a6d33..6afdd17fa6c 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/FoldingRanges/RemoteFoldingRangeServiceFactory.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/FoldingRanges/RemoteFoldingRangeServiceFactory.cs @@ -5,7 +5,6 @@ using Microsoft.CodeAnalysis.Razor.Remote; using Microsoft.CodeAnalysis.Razor.Workspaces; using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; -using Microsoft.ServiceHub.Framework; using Microsoft.VisualStudio.Composition; namespace Microsoft.CodeAnalysis.Remote.Razor; @@ -18,7 +17,7 @@ public RemoteFoldingRangeServiceFactory() { } - protected override IRemoteFoldingRangeService CreateService(IServiceBroker serviceBroker, ExportProvider exportProvider) + protected override IRemoteFoldingRangeService CreateService(IRazorServiceBroker serviceBroker, ExportProvider exportProvider) { var infoService = exportProvider.GetExportedValue(); var documentSnapshotFactory = exportProvider.GetExportedValue(); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/HtmlDocuments/RemoteHtmlDocumentService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/HtmlDocuments/RemoteHtmlDocumentService.cs index 783bd565c7b..fd7d305251c 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/HtmlDocuments/RemoteHtmlDocumentService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/HtmlDocuments/RemoteHtmlDocumentService.cs @@ -7,12 +7,11 @@ using Microsoft.CodeAnalysis.Razor.Remote; using Microsoft.CodeAnalysis.Razor.Workspaces; using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; -using Microsoft.ServiceHub.Framework; namespace Microsoft.CodeAnalysis.Remote.Razor; internal sealed class RemoteHtmlDocumentService( - IServiceBroker serviceBroker, + IRazorServiceBroker serviceBroker, DocumentSnapshotFactory documentSnapshotFactory) : RazorDocumentServiceBase(serviceBroker, documentSnapshotFactory), IRemoteHtmlDocumentService { diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/HtmlDocuments/RemoteHtmlDocumentServiceFactory.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/HtmlDocuments/RemoteHtmlDocumentServiceFactory.cs index f6d6cf62983..f55a1a0a5e9 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/HtmlDocuments/RemoteHtmlDocumentServiceFactory.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/HtmlDocuments/RemoteHtmlDocumentServiceFactory.cs @@ -3,7 +3,6 @@ using Microsoft.CodeAnalysis.Razor.Remote; using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; -using Microsoft.ServiceHub.Framework; using Microsoft.VisualStudio.Composition; namespace Microsoft.CodeAnalysis.Remote.Razor; @@ -16,7 +15,7 @@ public RemoteHtmlDocumentServiceFactory() { } - protected override IRemoteHtmlDocumentService CreateService(IServiceBroker serviceBroker, ExportProvider exportProvider) + protected override IRemoteHtmlDocumentService CreateService(IRazorServiceBroker serviceBroker, ExportProvider exportProvider) { var documentSnapshotFactory = exportProvider.GetExportedValue(); return new RemoteHtmlDocumentService(serviceBroker, documentSnapshotFactory); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/IBrokeredServiceInterceptor.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/IRazorServiceBroker.cs similarity index 93% rename from src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/IBrokeredServiceInterceptor.cs rename to src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/IRazorServiceBroker.cs index a2a3c23a48b..7f2d59ae0d6 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/IBrokeredServiceInterceptor.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/IRazorServiceBroker.cs @@ -12,7 +12,7 @@ namespace Microsoft.CodeAnalysis.Remote.Razor; /// /// An abstraction to avoid calling the static helper defined in Roslyn /// -internal interface IBrokeredServiceInterceptor +internal interface IRazorServiceBroker : IDisposable { ValueTask RunServiceAsync(Func implementation, CancellationToken cancellationToken); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Initialization/RemoteClientInitializationService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Initialization/RemoteClientInitializationService.cs index bb9d11ee466..d5d316c971e 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Initialization/RemoteClientInitializationService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Initialization/RemoteClientInitializationService.cs @@ -5,12 +5,11 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Razor.Remote; using Microsoft.CodeAnalysis.Remote.Razor.SemanticTokens; -using Microsoft.ServiceHub.Framework; namespace Microsoft.CodeAnalysis.Remote.Razor; internal sealed class RemoteClientInitializationService( - IServiceBroker serviceBroker) + IRazorServiceBroker serviceBroker) : RazorServiceBase(serviceBroker), IRemoteClientInitializationService { public ValueTask InitializeAsync(RemoteClientInitializationOptions options, CancellationToken cancellationToken) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Initialization/RemoteClientInitializationServiceFactory.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Initialization/RemoteClientInitializationServiceFactory.cs index 9a17b756487..c1cb47432a3 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Initialization/RemoteClientInitializationServiceFactory.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Initialization/RemoteClientInitializationServiceFactory.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using Microsoft.CodeAnalysis.Razor.Remote; -using Microsoft.ServiceHub.Framework; using Microsoft.VisualStudio.Composition; namespace Microsoft.CodeAnalysis.Remote.Razor; @@ -15,7 +14,7 @@ public RemoteClientInitializationServiceFactory() { } - protected override IRemoteClientInitializationService CreateService(IServiceBroker serviceBroker, ExportProvider exportProvider) + protected override IRemoteClientInitializationService CreateService(IRazorServiceBroker serviceBroker, ExportProvider exportProvider) { return new RemoteClientInitializationService(serviceBroker); } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/LinkedEditingRange/RemoteLinkedEditingRangeService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/LinkedEditingRange/RemoteLinkedEditingRangeService.cs index 8222947648a..d03ed067087 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/LinkedEditingRange/RemoteLinkedEditingRangeService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/LinkedEditingRange/RemoteLinkedEditingRangeService.cs @@ -9,12 +9,11 @@ using Microsoft.CodeAnalysis.Razor.Remote; using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Text; -using Microsoft.ServiceHub.Framework; namespace Microsoft.CodeAnalysis.Remote.Razor; internal sealed class RemoteLinkedEditingRangeService( - IServiceBroker serviceBroker, + IRazorServiceBroker serviceBroker, DocumentSnapshotFactory documentSnapshotFactory, ILoggerFactory loggerFactory) : RazorDocumentServiceBase(serviceBroker, documentSnapshotFactory), IRemoteLinkedEditingRangeService diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/LinkedEditingRange/RemoteLinkedEditingRangeServiceFactory.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/LinkedEditingRange/RemoteLinkedEditingRangeServiceFactory.cs index fb94aad1bca..29c5d7699b3 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/LinkedEditingRange/RemoteLinkedEditingRangeServiceFactory.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/LinkedEditingRange/RemoteLinkedEditingRangeServiceFactory.cs @@ -4,7 +4,6 @@ using Microsoft.CodeAnalysis.Razor.Logging; using Microsoft.CodeAnalysis.Razor.Remote; using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; -using Microsoft.ServiceHub.Framework; using Microsoft.VisualStudio.Composition; namespace Microsoft.CodeAnalysis.Remote.Razor; @@ -17,7 +16,7 @@ public RemoteLinkedEditingRangeServiceFactory() { } - protected override IRemoteLinkedEditingRangeService CreateService(IServiceBroker serviceBroker, ExportProvider exportProvider) + protected override IRemoteLinkedEditingRangeService CreateService(IRazorServiceBroker serviceBroker, ExportProvider exportProvider) { var documentSnapshotFactory = exportProvider.GetExportedValue(); var loggerFactory = exportProvider.GetExportedValue(); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RazorDocumentServiceBase.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RazorDocumentServiceBase.cs index e05a3fa00e8..bf2f8c0ef61 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RazorDocumentServiceBase.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RazorDocumentServiceBase.cs @@ -6,12 +6,11 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.ExternalAccess.Razor; using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; -using Microsoft.ServiceHub.Framework; namespace Microsoft.CodeAnalysis.Remote.Razor; internal abstract class RazorDocumentServiceBase( - IServiceBroker serviceBroker, + IRazorServiceBroker serviceBroker, DocumentSnapshotFactory documentSnapshotFactory) : RazorServiceBase(serviceBroker) { diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RazorServiceBase.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RazorServiceBase.cs index 35855fe0acb..c9ea08672ec 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RazorServiceBase.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RazorServiceBase.cs @@ -5,30 +5,26 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.ExternalAccess.Razor; -using Microsoft.CodeAnalysis.ExternalAccess.Razor.Api; -using Microsoft.ServiceHub.Framework; namespace Microsoft.CodeAnalysis.Remote.Razor; internal abstract class RazorServiceBase : IDisposable { - private readonly ServiceBrokerClient _serviceBrokerClient; - private readonly IBrokeredServiceInterceptor? _brokeredServiceInterceptor; + private readonly IRazorServiceBroker _razorServiceBroker; - public RazorServiceBase(IServiceBroker serviceBroker) + public RazorServiceBase(IRazorServiceBroker razorServiceBroker) { - _brokeredServiceInterceptor = serviceBroker as IBrokeredServiceInterceptor; - _serviceBrokerClient = new ServiceBrokerClient(serviceBroker, joinableTaskFactory: null); + _razorServiceBroker = razorServiceBroker; } protected ValueTask RunServiceAsync(Func implementation, CancellationToken cancellationToken) - => _brokeredServiceInterceptor?.RunServiceAsync(implementation, cancellationToken) ?? RazorBrokeredServiceImplementation.RunServiceAsync(implementation, cancellationToken); + => _razorServiceBroker.RunServiceAsync(implementation, cancellationToken); protected ValueTask RunServiceAsync(RazorPinnedSolutionInfoWrapper solutionInfo, Func> implementation, CancellationToken cancellationToken) - => _brokeredServiceInterceptor?.RunServiceAsync(solutionInfo, implementation, cancellationToken) ?? RazorBrokeredServiceImplementation.RunServiceAsync(solutionInfo, _serviceBrokerClient, implementation, cancellationToken); + => _razorServiceBroker.RunServiceAsync(solutionInfo, implementation, cancellationToken); public void Dispose() { - _serviceBrokerClient.Dispose(); + _razorServiceBroker.Dispose(); } } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RazorServiceBroker.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RazorServiceBroker.cs new file mode 100644 index 00000000000..928ffed1f99 --- /dev/null +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RazorServiceBroker.cs @@ -0,0 +1,32 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root for license information. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.ExternalAccess.Razor; +using Microsoft.CodeAnalysis.ExternalAccess.Razor.Api; +using Microsoft.ServiceHub.Framework; + +namespace Microsoft.CodeAnalysis.Remote.Razor; + +internal class RazorServiceBroker : IRazorServiceBroker +{ + private readonly ServiceBrokerClient _serviceBrokerClient; + + public RazorServiceBroker(IServiceBroker serviceBroker) + { + _serviceBrokerClient = new ServiceBrokerClient(serviceBroker, joinableTaskFactory: null); + } + + public ValueTask RunServiceAsync(Func implementation, CancellationToken cancellationToken) + => RazorBrokeredServiceImplementation.RunServiceAsync(implementation, cancellationToken); + + public ValueTask RunServiceAsync(RazorPinnedSolutionInfoWrapper solutionInfo, Func> implementation, CancellationToken cancellationToken) + => RazorBrokeredServiceImplementation.RunServiceAsync(solutionInfo, _serviceBrokerClient, implementation, cancellationToken); + + public void Dispose() + { + _serviceBrokerClient.Dispose(); + } +} diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RazorServiceFactoryBase.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RazorServiceFactoryBase.cs index c3c7e248391..32d4f4e6084 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RazorServiceFactoryBase.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RazorServiceFactoryBase.cs @@ -53,7 +53,9 @@ public async Task CreateAsync( var exportProvider = await RemoteMefComposition.GetExportProviderAsync().ConfigureAwait(false); - var service = CreateService(serviceBroker, exportProvider); + var razorServiceBroker = new RazorServiceBroker(serviceBroker); + + var service = CreateService(razorServiceBroker, exportProvider); serverConnection.AddLocalRpcTarget(service); serverConnection.StartListening(); @@ -61,5 +63,16 @@ public async Task CreateAsync( return service; } - protected abstract TService CreateService(IServiceBroker serviceBroker, ExportProvider exportProvider); + protected abstract TService CreateService(IRazorServiceBroker serviceBroker, ExportProvider exportProvider); + + internal TestAccessor GetTestAccessor() + { + return new TestAccessor(this); + } + + internal readonly struct TestAccessor(RazorServiceFactoryBase instance) + { + public TService CreateService(IRazorServiceBroker serviceBroker, ExportProvider exportProvider) + => instance.CreateService(serviceBroker, exportProvider); + } } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/SemanticTokens/RemoteSemanticTokensService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/SemanticTokens/RemoteSemanticTokensService.cs index 613236838fb..ed8ba161e1d 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/SemanticTokens/RemoteSemanticTokensService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/SemanticTokens/RemoteSemanticTokensService.cs @@ -9,12 +9,11 @@ using Microsoft.CodeAnalysis.Razor.SemanticTokens; using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Text; -using Microsoft.ServiceHub.Framework; namespace Microsoft.CodeAnalysis.Remote.Razor; internal sealed class RemoteSemanticTokensService( - IServiceBroker serviceBroker, + IRazorServiceBroker serviceBroker, IRazorSemanticTokensInfoService razorSemanticTokensInfoService, DocumentSnapshotFactory documentSnapshotFactory) : RazorDocumentServiceBase(serviceBroker, documentSnapshotFactory), IRemoteSemanticTokensService diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/SemanticTokens/RemoteSemanticTokensServiceFactory.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/SemanticTokens/RemoteSemanticTokensServiceFactory.cs index 87e5dd42bef..71134768ec1 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/SemanticTokens/RemoteSemanticTokensServiceFactory.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/SemanticTokens/RemoteSemanticTokensServiceFactory.cs @@ -4,7 +4,6 @@ using Microsoft.CodeAnalysis.Razor.Remote; using Microsoft.CodeAnalysis.Razor.SemanticTokens; using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; -using Microsoft.ServiceHub.Framework; using Microsoft.VisualStudio.Composition; namespace Microsoft.CodeAnalysis.Remote.Razor; @@ -17,7 +16,7 @@ public RemoteSemanticTokensServiceFactory() { } - protected override IRemoteSemanticTokensService CreateService(IServiceBroker serviceBroker, ExportProvider exportProvider) + protected override IRemoteSemanticTokensService CreateService(IRazorServiceBroker serviceBroker, ExportProvider exportProvider) { var infoService = exportProvider.GetExportedValue(); var documentSnapshotFactory = exportProvider.GetExportedValue(); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/TagHelpers/RemoteTagHelperProviderService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/TagHelpers/RemoteTagHelperProviderService.cs index d095a0c8fc5..7d8d80659a1 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/TagHelpers/RemoteTagHelperProviderService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/TagHelpers/RemoteTagHelperProviderService.cs @@ -10,12 +10,11 @@ using Microsoft.AspNetCore.Razor.Utilities; using Microsoft.CodeAnalysis.ExternalAccess.Razor; using Microsoft.CodeAnalysis.Razor.Remote; -using Microsoft.ServiceHub.Framework; namespace Microsoft.CodeAnalysis.Remote.Razor; internal sealed class RemoteTagHelperProviderService( - IServiceBroker serviceBroker, + IRazorServiceBroker serviceBroker, RemoteTagHelperResolver tagHelperResolver, RemoteTagHelperDeltaProvider tagHelperDeltaProvider) : RazorServiceBase(serviceBroker), IRemoteTagHelperProviderService diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/TagHelpers/RemoteTagHelperProviderServiceFactory.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/TagHelpers/RemoteTagHelperProviderServiceFactory.cs index 1cad94d5c67..c1a3b34a2c9 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/TagHelpers/RemoteTagHelperProviderServiceFactory.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/TagHelpers/RemoteTagHelperProviderServiceFactory.cs @@ -3,7 +3,6 @@ using Microsoft.AspNetCore.Razor; using Microsoft.CodeAnalysis.Razor.Remote; -using Microsoft.ServiceHub.Framework; using Microsoft.VisualStudio.Composition; namespace Microsoft.CodeAnalysis.Remote.Razor; @@ -16,7 +15,7 @@ public RemoteTagHelperProviderServiceFactory() { } - protected override IRemoteTagHelperProviderService CreateService(IServiceBroker serviceBroker, ExportProvider exportProvider) + protected override IRemoteTagHelperProviderService CreateService(IRazorServiceBroker serviceBroker, ExportProvider exportProvider) { var tagHelperResolver = exportProvider.GetExportedValue().AssumeNotNull(); var tagHelperDeltaProvider = exportProvider.GetExportedValue().AssumeNotNull(); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/UriPresentation/RemoteUriPresentationService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/UriPresentation/RemoteUriPresentationService.cs index 4f655b1d2e5..507f90011c2 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/UriPresentation/RemoteUriPresentationService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/UriPresentation/RemoteUriPresentationService.cs @@ -13,12 +13,11 @@ using Microsoft.CodeAnalysis.Razor.Workspaces; using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Text; -using Microsoft.ServiceHub.Framework; namespace Microsoft.CodeAnalysis.Remote.Razor; internal sealed class RemoteUriPresentationService( - IServiceBroker serviceBroker, + IRazorServiceBroker serviceBroker, IRazorDocumentMappingService documentMappingService, DocumentSnapshotFactory documentSnapshotFactory, ILoggerFactory loggerFactory) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/UriPresentation/RemoteUriPresentationServiceFactory.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/UriPresentation/RemoteUriPresentationServiceFactory.cs index 20499a8bb35..ecb75c9d11b 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/UriPresentation/RemoteUriPresentationServiceFactory.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/UriPresentation/RemoteUriPresentationServiceFactory.cs @@ -5,7 +5,6 @@ using Microsoft.CodeAnalysis.Razor.Logging; using Microsoft.CodeAnalysis.Razor.Remote; using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; -using Microsoft.ServiceHub.Framework; using Microsoft.VisualStudio.Composition; namespace Microsoft.CodeAnalysis.Remote.Razor; @@ -18,7 +17,7 @@ public RemoteUriPresentationServiceFactory() { } - protected override IRemoteUriPresentationService CreateService(IServiceBroker serviceBroker, ExportProvider exportProvider) + protected override IRemoteUriPresentationService CreateService(IRazorServiceBroker serviceBroker, ExportProvider exportProvider) { var documentMappingService = exportProvider.GetExportedValue(); var documentSnapshotFactory = exportProvider.GetExportedValue(); From 45a08219b9d8018aaeb97c78f147a9d404ee5dee Mon Sep 17 00:00:00 2001 From: David Wengier Date: Fri, 12 Jul 2024 11:51:05 +1000 Subject: [PATCH 7/8] Create MEF composition and services directly in testing --- .../RemoteMefComposition.cs | 3 ++- .../Cohost/CohostTestBase.cs | 18 ++++++++++++--- ...ovider.cs => TestRemoteServiceProvider.cs} | 23 +++++++------------ ...gServiceBroker.cs => TestServiceBroker.cs} | 21 ++++------------- 4 files changed, 30 insertions(+), 35 deletions(-) rename src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/{ShortCircuitingRemoteServiceProvider.cs => TestRemoteServiceProvider.cs} (68%) rename src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/{InterceptingServiceBroker.cs => TestServiceBroker.cs} (53%) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RemoteMefComposition.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RemoteMefComposition.cs index 0a8b90be376..a86c3fcf0c6 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RemoteMefComposition.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RemoteMefComposition.cs @@ -20,8 +20,9 @@ internal static class RemoteMefComposition public static Task GetExportProviderAsync() => s_exportProviderLazy.GetValueAsync(); + // Internal for testing // Inspired by https://github.com/dotnet/roslyn/blob/25aa74d725e801b8232dbb3e5abcda0fa72da8c5/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspaceManager.cs#L77 - private static async Task CreateExportProviderAsync() + internal static async Task CreateExportProviderAsync() { var resolver = new Resolver(SimpleAssemblyLoader.Instance); var discovery = new AttributedPartDiscovery(resolver, isNonPublicSupported: true); // MEFv2 only diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostTestBase.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostTestBase.cs index cc0800edf62..9a57b3b42d8 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostTestBase.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostTestBase.cs @@ -8,22 +8,34 @@ using Microsoft.AspNetCore.Razor.Test.Common.Workspaces; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Razor.Remote; +using Microsoft.CodeAnalysis.Remote.Razor; using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.Composition; using Xunit.Abstractions; namespace Microsoft.VisualStudio.LanguageServices.Razor.Test.Cohost; public abstract class CohostTestBase(ITestOutputHelper testOutputHelper) : WorkspaceTestBase(testOutputHelper) { + private ExportProvider? _exportProvider; private IRemoteServiceProvider? _remoteServiceProvider; private protected IRemoteServiceProvider RemoteServiceProvider => _remoteServiceProvider.AssumeNotNull(); - protected override Task InitializeAsync() + protected override async Task InitializeAsync() { - _remoteServiceProvider = new ShortCircuitingRemoteServiceProvider(TestOutputHelper); + await base.InitializeAsync(); - return base.InitializeAsync(); + _exportProvider = await RemoteMefComposition.CreateExportProviderAsync(); + + _remoteServiceProvider = new TestRemoteServiceProvider(_exportProvider); + } + + protected override async Task DisposeAsync() + { + _exportProvider?.Dispose(); + + await base.DisposeAsync(); } protected TextDocument CreateRazorDocument(string contents) diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/ShortCircuitingRemoteServiceProvider.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/TestRemoteServiceProvider.cs similarity index 68% rename from src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/ShortCircuitingRemoteServiceProvider.cs rename to src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/TestRemoteServiceProvider.cs index fefdd323c1e..88b0b7f5d8a 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/ShortCircuitingRemoteServiceProvider.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/TestRemoteServiceProvider.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.IO; using System.Linq; using System.Runtime.CompilerServices; using System.Threading; @@ -15,16 +14,15 @@ using Microsoft.CodeAnalysis.Razor.Remote; using Microsoft.CodeAnalysis.Remote.Razor; using Microsoft.ServiceHub.Framework; -using Nerdbank.Streams; +using Microsoft.VisualStudio.Composition; using Xunit; -using Xunit.Abstractions; namespace Microsoft.VisualStudio.LanguageServices.Razor.Test.Cohost; /// /// An implementation of IRemoteServiceProvider that doesn't actually do anything remote, but rather directly calls service methods /// -internal class ShortCircuitingRemoteServiceProvider(ITestOutputHelper testOutputHelper) : IRemoteServiceProvider +internal class TestRemoteServiceProvider(ExportProvider exportProvider) : IRemoteServiceProvider { private static readonly Dictionary s_factoryMap = BuildFactoryMap(); @@ -60,22 +58,17 @@ private static Dictionary BuildFactoryMap() CancellationToken cancellationToken, [CallerFilePath] string? callerFilePath = null, [CallerMemberName] string? callerMemberName = null) - where TService : class + where TService : class, IDisposable { Assert.True(s_factoryMap.TryGetValue(typeof(TService), out var factory)); - var testServiceBroker = new InterceptingServiceBroker(solution); + var testServiceBroker = new TestServiceBroker(solution); - // We don't ever use this stream, because we never really use ServiceHub, but going through its factory method means the - // remote services under test are using their full MEF composition etc. so we get excellent coverage. - var (stream, _) = FullDuplexStream.CreatePair(); - using var service = (IDisposable)await factory.CreateAsync(stream, _serviceProvider, serviceActivationOptions: default, testServiceBroker, authorizationServiceClient: default!); + var serviceFactory = (RazorServiceFactoryBase)factory; + using var service = serviceFactory.GetTestAccessor().CreateService(testServiceBroker, exportProvider); - // This is never used, we short-circuited things by passing the solution direct to the InterceptingServiceBroker + // This is never used, we short-circuited things by passing the solution direct to the TestServiceBroker var solutionInfo = new RazorPinnedSolutionInfoWrapper(); - - testOutputHelper.WriteLine($"Pretend OOP call for {typeof(TService).Name}, invocation: {Path.GetFileNameWithoutExtension(callerFilePath)}.{callerMemberName}"); - testOutputHelper.WriteLine($"Project assembly path: `{solution.Projects.First().CompilationOutputInfo.AssemblyPath ?? "null"}`"); - return await invocation((TService)service, solutionInfo, cancellationToken); + return await invocation(service, solutionInfo, cancellationToken); } } diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/InterceptingServiceBroker.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/TestServiceBroker.cs similarity index 53% rename from src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/InterceptingServiceBroker.cs rename to src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/TestServiceBroker.cs index 364f0effc00..8dfd9f38217 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/InterceptingServiceBroker.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/TestServiceBroker.cs @@ -2,31 +2,16 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using System; -using System.IO.Pipelines; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.ExternalAccess.Razor; using Microsoft.CodeAnalysis.Remote.Razor; -using Microsoft.ServiceHub.Framework; namespace Microsoft.VisualStudio.LanguageServices.Razor.Test.Cohost; -internal class InterceptingServiceBroker(Solution solution) : IServiceBroker, IBrokeredServiceInterceptor +internal class TestServiceBroker(Solution solution) : IRazorServiceBroker { - public event EventHandler? AvailabilityChanged { add { } remove { } } - - public ValueTask GetPipeAsync(ServiceMoniker serviceMoniker, ServiceActivationOptions options = default, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public ValueTask GetProxyAsync(ServiceRpcDescriptor serviceDescriptor, ServiceActivationOptions options = default, CancellationToken cancellationToken = default) - where T : class - { - throw new NotImplementedException(); - } - public ValueTask RunServiceAsync(Func implementation, CancellationToken cancellationToken) { return implementation(cancellationToken); @@ -36,4 +21,8 @@ public ValueTask RunServiceAsync(RazorPinnedSolutionInfoWrapper solutionIn { return implementation(solution); } + + public void Dispose() + { + } } From 66e42475cc074e18a12984b786b7b8143573fc9f Mon Sep 17 00:00:00 2001 From: David Wengier Date: Fri, 12 Jul 2024 12:39:22 +1000 Subject: [PATCH 8/8] Clean up, and allow multple services to be used by a single endpoint --- .../ToolingTestBase.cs | 10 ++++ .../Cohost/CohostTestBase.cs | 14 +---- .../Cohost/ServiceFactoryMap.cs | 48 +++++++++++++++ .../Cohost/TestRemoteServiceProvider.cs | 58 ++++++++----------- .../Cohost/TestServiceBroker.cs | 12 +++- 5 files changed, 93 insertions(+), 49 deletions(-) create mode 100644 src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/ServiceFactoryMap.cs diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/ToolingTestBase.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/ToolingTestBase.cs index 8dad3a87185..15885cad28e 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/ToolingTestBase.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/ToolingTestBase.cs @@ -166,6 +166,16 @@ protected void AddDisposable(IDisposable disposable) _disposables.Add(disposable); } + /// + /// Register an instance to be disposed when the test completes. + /// + protected T AddDisposable(T disposable) + where T : IDisposable + { + AddDisposable((IDisposable)disposable); + return disposable; + } + /// /// Register a set of instances to be disposed when the test completes. /// diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostTestBase.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostTestBase.cs index 9a57b3b42d8..775a3f08ebf 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostTestBase.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostTestBase.cs @@ -10,14 +10,12 @@ using Microsoft.CodeAnalysis.Razor.Remote; using Microsoft.CodeAnalysis.Remote.Razor; using Microsoft.CodeAnalysis.Text; -using Microsoft.VisualStudio.Composition; using Xunit.Abstractions; namespace Microsoft.VisualStudio.LanguageServices.Razor.Test.Cohost; public abstract class CohostTestBase(ITestOutputHelper testOutputHelper) : WorkspaceTestBase(testOutputHelper) { - private ExportProvider? _exportProvider; private IRemoteServiceProvider? _remoteServiceProvider; private protected IRemoteServiceProvider RemoteServiceProvider => _remoteServiceProvider.AssumeNotNull(); @@ -26,16 +24,8 @@ protected override async Task InitializeAsync() { await base.InitializeAsync(); - _exportProvider = await RemoteMefComposition.CreateExportProviderAsync(); - - _remoteServiceProvider = new TestRemoteServiceProvider(_exportProvider); - } - - protected override async Task DisposeAsync() - { - _exportProvider?.Dispose(); - - await base.DisposeAsync(); + var exportProvider = AddDisposable(await RemoteMefComposition.CreateExportProviderAsync()); + _remoteServiceProvider = AddDisposable(new TestRemoteServiceProvider(exportProvider)); } protected TextDocument CreateRazorDocument(string contents) diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/ServiceFactoryMap.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/ServiceFactoryMap.cs new file mode 100644 index 00000000000..ec32e4503a2 --- /dev/null +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/ServiceFactoryMap.cs @@ -0,0 +1,48 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis.Remote.Razor; +using Microsoft.ServiceHub.Framework; +using System.Collections.Generic; +using System.Linq; +using System; +using Xunit; + +namespace Microsoft.VisualStudio.LanguageServices.Razor.Test.Cohost; + +internal static class ServiceFactoryMap +{ + private static readonly Dictionary s_factoryMap = BuildFactoryMap(); + + private static Dictionary BuildFactoryMap() + { + var result = new Dictionary(); + + foreach (var type in typeof(RazorServiceFactoryBase<>).Assembly.GetTypes()) + { + if (!type.IsAbstract && + typeof(IServiceHubServiceFactory).IsAssignableFrom(type)) + { + Assert.Equal(typeof(RazorServiceFactoryBase<>), type.BaseType.GetGenericTypeDefinition()); + + var genericType = type.BaseType.GetGenericArguments().FirstOrDefault(); + if (genericType != null) + { + // ServiceHub requires a parameterless constructor, so we can safely rely on it existing too + var factory = (IServiceHubServiceFactory)Activator.CreateInstance(type); + result.Add(genericType, factory); + } + } + } + + return result; + } + + public static RazorServiceFactoryBase GetServiceFactory() + where TService : class + { + Assert.True(s_factoryMap.TryGetValue(typeof(TService), out var factory)); + + return (RazorServiceFactoryBase)factory; + } +} diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/TestRemoteServiceProvider.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/TestRemoteServiceProvider.cs index 88b0b7f5d8a..72745c0ed25 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/TestRemoteServiceProvider.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/TestRemoteServiceProvider.cs @@ -3,53 +3,35 @@ using System; using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; -using Microsoft.AspNetCore.Razor.Test.Common.VisualStudio; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.ExternalAccess.Razor; using Microsoft.CodeAnalysis.Razor.Remote; -using Microsoft.CodeAnalysis.Remote.Razor; -using Microsoft.ServiceHub.Framework; using Microsoft.VisualStudio.Composition; -using Xunit; namespace Microsoft.VisualStudio.LanguageServices.Razor.Test.Cohost; /// /// An implementation of IRemoteServiceProvider that doesn't actually do anything remote, but rather directly calls service methods /// -internal class TestRemoteServiceProvider(ExportProvider exportProvider) : IRemoteServiceProvider +internal class TestRemoteServiceProvider(ExportProvider exportProvider) : IRemoteServiceProvider, IDisposable { - private static readonly Dictionary s_factoryMap = BuildFactoryMap(); + private readonly TestServiceBroker _testServiceBroker = new TestServiceBroker(); + private readonly Dictionary _services = new Dictionary(); - private readonly IServiceProvider _serviceProvider = VsMocks.CreateServiceProvider(b => b.AddService(serviceInstance: null)); - - private static Dictionary BuildFactoryMap() + private TService GetOrCreateService() + where TService : class, IDisposable { - var result = new Dictionary(); - - foreach (var type in typeof(RazorServiceFactoryBase<>).Assembly.GetTypes()) + if (!_services.TryGetValue(typeof(TService), out var service)) { - if (!type.IsAbstract && - typeof(IServiceHubServiceFactory).IsAssignableFrom(type)) - { - Assert.Equal(typeof(RazorServiceFactoryBase<>), type.BaseType.GetGenericTypeDefinition()); - - var genericType = type.BaseType.GetGenericArguments().FirstOrDefault(); - if (genericType != null) - { - // ServiceHub requires a parameterless constructor, so we can safely rely on it existing too - var factory = (IServiceHubServiceFactory)Activator.CreateInstance(type); - result.Add(genericType, factory); - } - } + var factory = ServiceFactoryMap.GetServiceFactory(); + service = factory.GetTestAccessor().CreateService(_testServiceBroker, exportProvider); + _services.Add(typeof(TService), service); } - return result; + return (TService)service; } public async ValueTask TryInvokeAsync( @@ -60,15 +42,21 @@ private static Dictionary BuildFactoryMap() [CallerMemberName] string? callerMemberName = null) where TService : class, IDisposable { - Assert.True(s_factoryMap.TryGetValue(typeof(TService), out var factory)); - - var testServiceBroker = new TestServiceBroker(solution); - - var serviceFactory = (RazorServiceFactoryBase)factory; - using var service = serviceFactory.GetTestAccessor().CreateService(testServiceBroker, exportProvider); + var service = GetOrCreateService(); - // This is never used, we short-circuited things by passing the solution direct to the TestServiceBroker + // In an ideal world we'd be able to maintain a dictionary of solution checksums in TestServiceBroker, and use + // the RazorPinnedSolutionInfoWrapper properly, but we need Roslyn changes for that. For now, this works fine + // as we don't have any code that makes multiple parallel calls to TryInvokeAsync in the same test. var solutionInfo = new RazorPinnedSolutionInfoWrapper(); + _testServiceBroker.UpdateSolution(solution); return await invocation(service, solutionInfo, cancellationToken); } + + public void Dispose() + { + foreach (var service in _services.Values) + { + service.Dispose(); + } + } } diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/TestServiceBroker.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/TestServiceBroker.cs index 8dfd9f38217..57e026d1d1c 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/TestServiceBroker.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/TestServiceBroker.cs @@ -4,14 +4,22 @@ using System; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Razor; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.ExternalAccess.Razor; using Microsoft.CodeAnalysis.Remote.Razor; namespace Microsoft.VisualStudio.LanguageServices.Razor.Test.Cohost; -internal class TestServiceBroker(Solution solution) : IRazorServiceBroker +internal class TestServiceBroker : IRazorServiceBroker { + private Solution? _solution; + + public void UpdateSolution(Solution solution) + { + _solution = solution; + } + public ValueTask RunServiceAsync(Func implementation, CancellationToken cancellationToken) { return implementation(cancellationToken); @@ -19,7 +27,7 @@ public ValueTask RunServiceAsync(Func implementati public ValueTask RunServiceAsync(RazorPinnedSolutionInfoWrapper solutionInfo, Func> implementation, CancellationToken cancellationToken) { - return implementation(solution); + return implementation(_solution.AssumeNotNull()); } public void Dispose()