diff --git a/.gitignore b/.gitignore index 04d1209d3..97caab569 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ nuget.exe debugSettings.json buildlog /.vs +osharp_pack/ # NuGet *.nuget.targets diff --git a/Directory.Build.targets b/Directory.Build.targets index 46eeba140..b8e0bffbf 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -1,52 +1,53 @@ - - - - $(BaseIntermediateOutputPath)\GeneratedFiles - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + $(BaseIntermediateOutputPath)\GeneratedFiles + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/LSP.sln b/LSP.sln index 1a5d0701e..7ca6d6531 100644 --- a/LSP.sln +++ b/LSP.sln @@ -77,6 +77,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dap.Protocol.Proposals", "s EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Protocol.Proposals", "src\Protocol.Proposals\Protocol.Proposals.csproj", "{201B1CA7-AB12-41AD-9246-BC30F2EBE2DF}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lsp.Integration.Tests", "test\Lsp.Integration.Tests\Lsp.Integration.Tests.csproj", "{72A74595-A278-46F0-9C8B-3151C9681B91}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -357,6 +359,18 @@ Global {201B1CA7-AB12-41AD-9246-BC30F2EBE2DF}.Release|x64.Build.0 = Release|Any CPU {201B1CA7-AB12-41AD-9246-BC30F2EBE2DF}.Release|x86.ActiveCfg = Release|Any CPU {201B1CA7-AB12-41AD-9246-BC30F2EBE2DF}.Release|x86.Build.0 = Release|Any CPU + {72A74595-A278-46F0-9C8B-3151C9681B91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {72A74595-A278-46F0-9C8B-3151C9681B91}.Debug|Any CPU.Build.0 = Debug|Any CPU + {72A74595-A278-46F0-9C8B-3151C9681B91}.Debug|x64.ActiveCfg = Debug|Any CPU + {72A74595-A278-46F0-9C8B-3151C9681B91}.Debug|x64.Build.0 = Debug|Any CPU + {72A74595-A278-46F0-9C8B-3151C9681B91}.Debug|x86.ActiveCfg = Debug|Any CPU + {72A74595-A278-46F0-9C8B-3151C9681B91}.Debug|x86.Build.0 = Debug|Any CPU + {72A74595-A278-46F0-9C8B-3151C9681B91}.Release|Any CPU.ActiveCfg = Release|Any CPU + {72A74595-A278-46F0-9C8B-3151C9681B91}.Release|Any CPU.Build.0 = Release|Any CPU + {72A74595-A278-46F0-9C8B-3151C9681B91}.Release|x64.ActiveCfg = Release|Any CPU + {72A74595-A278-46F0-9C8B-3151C9681B91}.Release|x64.Build.0 = Release|Any CPU + {72A74595-A278-46F0-9C8B-3151C9681B91}.Release|x86.ActiveCfg = Release|Any CPU + {72A74595-A278-46F0-9C8B-3151C9681B91}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -385,6 +399,7 @@ Global {58E83291-1ED9-4921-A12F-F2450AB17F47} = {2F323ED5-EBF8-45E1-B9D3-C014561B3DDA} {D43637CC-94E6-4ED4-BAA3-E5D1AD3285F5} = {D764E024-3D3F-4112-B932-2DB722A1BACC} {201B1CA7-AB12-41AD-9246-BC30F2EBE2DF} = {D764E024-3D3F-4112-B932-2DB722A1BACC} + {72A74595-A278-46F0-9C8B-3151C9681B91} = {2F323ED5-EBF8-45E1-B9D3-C014561B3DDA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D38DD0EC-D095-4BCD-B8AF-2D788AF3B9AE} diff --git a/build.sh b/build.sh old mode 100644 new mode 100755 diff --git a/package-lock.json b/package-lock.json index c55d02cb2..bc8cb820c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -344,9 +344,9 @@ "dev": true }, "husky": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/husky/-/husky-5.1.1.tgz", - "integrity": "sha512-80LZ736V0Nr4/st0c2COYaMbEQhHNmAbLMN8J/kLk7/mo0QdUkUGNDjv/7jVkhug377Wh8wfbWyaVXEJ/h2B/Q==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/husky/-/husky-6.0.0.tgz", + "integrity": "sha512-SQS2gDTB7tBN486QSoKPKQItZw97BMOd+Kdb6ghfpBc0yXyzrddI0oDV5MkDAbuB4X2mO3/nj60TRMcYxwzZeQ==", "dev": true }, "import-fresh": { diff --git a/package.json b/package.json index 6d664e7cf..31a2396f6 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "private": true, "devDependencies": { - "husky": "^5.1.1", + "husky": "^6.0.0", "lint-staged": "^10.5.4", "prettier": "^2.2.1" } diff --git a/src/Client/LanguageClient.cs b/src/Client/LanguageClient.cs index e2304fb25..0d5ba8b19 100644 --- a/src/Client/LanguageClient.cs +++ b/src/Client/LanguageClient.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Reactive.Concurrency; using System.Reactive.Disposables; using System.Reactive.Linq; using System.Reactive.Subjects; @@ -48,6 +49,7 @@ public class LanguageClient : JsonRpcServerBase, ILanguageClient private readonly IEnumerable _initializedHandlers; private readonly LspSerializer _serializer; private readonly InstanceHasStarted _instanceHasStarted; + private readonly IScheduler _scheduler; private readonly IResponseRouter _responseRouter; private readonly ISubject _initializeComplete = new AsyncSubject(); private readonly CompositeDisposable _disposable = new CompositeDisposable(); @@ -150,7 +152,8 @@ internal LanguageClient( IEnumerable initializedDelegates, IEnumerable initializedHandlers, LspSerializer serializer, - InstanceHasStarted instanceHasStarted + InstanceHasStarted instanceHasStarted, + IScheduler scheduler ) : base(handlerCollection, responseRouter) { _connection = connection; @@ -179,6 +182,7 @@ InstanceHasStarted instanceHasStarted _initializedHandlers = initializedHandlers; _serializer = serializer; _instanceHasStarted = instanceHasStarted; + _scheduler = scheduler; _concurrency = options.Value.Concurrency; // We need to at least create Window here in case any handler does loggin in their constructor @@ -262,6 +266,7 @@ await LanguageProtocolEventingHelper.Run( _initializeHandlers.Union(_collection.Select(z => z.Handler).OfType()), (handler, ct) => handler.OnInitialize(this, @params, ct), _concurrency, + _scheduler, token ).ConfigureAwait(false); @@ -281,6 +286,7 @@ await LanguageProtocolEventingHelper.Run( _initializedHandlers.Union(_collection.Select(z => z.Handler).OfType()), (handler, ct) => handler.OnInitialized(this, @params, serverParams, ct), _concurrency, + _scheduler, token ).ConfigureAwait(false); @@ -299,6 +305,7 @@ await LanguageProtocolEventingHelper.Run( _startedHandlers.Union(_collection.Select(z => z.Handler).OfType()), (handler, ct) => handler.OnStarted(this, ct), _concurrency, + _scheduler, token ).ConfigureAwait(false); @@ -395,7 +402,7 @@ private Supports UseOrTryAndFindCapability(Supports supports) where T : bool IResponseRouter.TryGetRequest(long id, [NotNullWhen(true)] out string method, [NotNullWhen(true)] out TaskCompletionSource pendingTask) => _responseRouter.TryGetRequest(id, out method, out pendingTask); - public Task WasStarted => _initializeComplete.ToTask(); + public Task WasStarted => _initializeComplete.ToTask(_scheduler); public void Dispose() { diff --git a/src/Client/LanguageClientOptionsExtensions.cs b/src/Client/LanguageClientOptionsExtensions.cs index 2c10ae955..32fe472e1 100644 --- a/src/Client/LanguageClientOptionsExtensions.cs +++ b/src/Client/LanguageClientOptionsExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Reactive.Concurrency; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -87,6 +88,54 @@ public static LanguageClientOptions WithClientCapabilities(this LanguageClientOp return options; } + /// + /// Sets both input and output schedulers to the same scheduler + /// + /// + /// + /// + public static LanguageClientOptions WithScheduler(this LanguageClientOptions options, IScheduler inputScheduler) + { + options.InputScheduler = options.OutputScheduler = options.DefaultScheduler = inputScheduler; + return options; + } + + /// + /// Sets the scheduler used during reading input + /// + /// + /// + /// + public static LanguageClientOptions WithInputScheduler(this LanguageClientOptions options, IScheduler inputScheduler) + { + options.InputScheduler = inputScheduler; + return options; + } + + /// + /// Sets the default scheduler to be used when scheduling other tasks + /// + /// + /// + /// + public static LanguageClientOptions WithDefaultScheduler(this LanguageClientOptions options, IScheduler defaultScheduler) + { + options.DefaultScheduler = defaultScheduler; + return options; + } + + /// + /// Sets the scheduler use during writing output + /// + /// + /// + /// + public static LanguageClientOptions WithOutputScheduler(this LanguageClientOptions options, IScheduler outputScheduler) + { + options.OutputScheduler = outputScheduler; + return options; + } + public static LanguageClientOptions OnInitialize(this LanguageClientOptions options, OnLanguageClientInitializeDelegate @delegate) { options.Services.AddSingleton(@delegate); diff --git a/src/Client/LanguageClientRegistrationManager.cs b/src/Client/LanguageClientRegistrationManager.cs index 828a7b596..b6703e4c3 100644 --- a/src/Client/LanguageClientRegistrationManager.cs +++ b/src/Client/LanguageClientRegistrationManager.cs @@ -2,6 +2,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Reactive.Concurrency; using System.Reactive.Linq; using System.Reactive.Subjects; using System.Threading; @@ -26,7 +27,7 @@ internal class LanguageClientRegistrationManager : IRegisterCapabilityHandler, I private readonly ILspHandlerTypeDescriptorProvider _handlerTypeDescriptorProvider; private readonly ILogger _logger; private readonly ConcurrentDictionary _registrations; - private readonly ReplaySubject> _registrationSubject = new ReplaySubject>(1); + private readonly ReplaySubject> _registrationSubject = new ReplaySubject>(1, Scheduler.Immediate); public LanguageClientRegistrationManager( ISerializer serializer, diff --git a/src/Client/LanguageClientWorkspaceFoldersManager.cs b/src/Client/LanguageClientWorkspaceFoldersManager.cs index c768335bc..1397dad26 100644 --- a/src/Client/LanguageClientWorkspaceFoldersManager.cs +++ b/src/Client/LanguageClientWorkspaceFoldersManager.cs @@ -2,6 +2,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Reactive.Concurrency; using System.Reactive.Linq; using System.Reactive.Subjects; using System.Threading; @@ -25,7 +26,7 @@ public LanguageClientWorkspaceFoldersManager(IWorkspaceLanguageClient client, IE { _client = client; _workspaceFolders = new ConcurrentDictionary(DocumentUri.Comparer); - _workspaceFoldersSubject = new ReplaySubject>(1); + _workspaceFoldersSubject = new ReplaySubject>(1, Scheduler.Immediate); foreach (var folder in workspaceFolders) { diff --git a/src/Dap.Client/DebugAdapterClient.cs b/src/Dap.Client/DebugAdapterClient.cs index 5d8854b95..f2ec06604 100644 --- a/src/Dap.Client/DebugAdapterClient.cs +++ b/src/Dap.Client/DebugAdapterClient.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reactive.Concurrency; using System.Reactive.Disposables; using System.Reactive.Subjects; using System.Reactive.Threading.Tasks; @@ -30,6 +31,7 @@ public class DebugAdapterClient : JsonRpcServerBase, IDebugAdapterClient, IDebug private readonly IEnumerable _startedDelegates; private readonly IEnumerable _startedHandlers; private readonly InstanceHasStarted _instanceHasStarted; + private readonly IScheduler _scheduler; private readonly CompositeDisposable _disposable = new CompositeDisposable(); private readonly Connection _connection; private readonly DapReceiver _receiver; @@ -97,7 +99,8 @@ internal DebugAdapterClient( IEnumerable initializedDelegates, IEnumerable initializedHandlers, IEnumerable startedHandlers, - InstanceHasStarted instanceHasStarted + InstanceHasStarted instanceHasStarted, + IScheduler scheduler ) : base(collection, responseRouter) { _settingsBag = settingsBag; @@ -114,6 +117,7 @@ InstanceHasStarted instanceHasStarted _initializedHandlers = initializedHandlers; _startedHandlers = startedHandlers; _instanceHasStarted = instanceHasStarted; + _scheduler = scheduler; _concurrency = options.Value.Concurrency; _disposable.Add(collection.Add(this)); @@ -127,6 +131,7 @@ await DebugAdapterEventingHelper.Run( _initializeHandlers.Union(_collection.Select(z => z.Handler).OfType()), (handler, ct) => handler.OnInitialize(this, ClientSettings, ct), _concurrency, + _scheduler, token ).ConfigureAwait(false); @@ -145,10 +150,11 @@ await DebugAdapterEventingHelper.Run( _initializedHandlers.Union(_collection.Select(z => z.Handler).OfType()), (handler, ct) => handler.OnInitialized(this, ClientSettings, ServerSettings, ct), _concurrency, + _scheduler, token ).ConfigureAwait(false); - await _initializedComplete.ToTask(token); + await _initializedComplete.ToTask(token, _scheduler); await DebugAdapterEventingHelper.Run( _startedDelegates, @@ -156,6 +162,7 @@ await DebugAdapterEventingHelper.Run( _startedHandlers.Union(_collection.Select(z => z.Handler).OfType()), (handler, ct) => handler.OnStarted(this, ct), _concurrency, + _scheduler, token ).ConfigureAwait(false); diff --git a/src/Dap.Client/DebugAdapterClientOptionsExtensions.cs b/src/Dap.Client/DebugAdapterClientOptionsExtensions.cs index 4b3f8236c..6c10f6646 100644 --- a/src/Dap.Client/DebugAdapterClientOptionsExtensions.cs +++ b/src/Dap.Client/DebugAdapterClientOptionsExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Reactive.Concurrency; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -21,6 +22,54 @@ public static DebugAdapterClientOptions WithRequestProcessIdentifier(this DebugA return options; } + /// + /// Sets both input and output schedulers to the same scheduler + /// + /// + /// + /// + public static DebugAdapterClientOptions WithScheduler(this DebugAdapterClientOptions options, IScheduler inputScheduler) + { + options.InputScheduler = options.OutputScheduler = options.DefaultScheduler = inputScheduler; + return options; + } + + /// + /// Sets the scheduler used during reading input + /// + /// + /// + /// + public static DebugAdapterClientOptions WithInputScheduler(this DebugAdapterClientOptions options, IScheduler inputScheduler) + { + options.InputScheduler = inputScheduler; + return options; + } + + /// + /// Sets the default scheduler to be used when scheduling other tasks + /// + /// + /// + /// + public static DebugAdapterClientOptions WithDefaultScheduler(this DebugAdapterClientOptions options, IScheduler defaultScheduler) + { + options.DefaultScheduler = defaultScheduler; + return options; + } + + /// + /// Sets the scheduler use during writing output + /// + /// + /// + /// + public static DebugAdapterClientOptions WithOutputScheduler(this DebugAdapterClientOptions options, IScheduler outputScheduler) + { + options.OutputScheduler = outputScheduler; + return options; + } + public static DebugAdapterClientOptions OnInitialize(this DebugAdapterClientOptions options, OnDebugAdapterClientInitializeDelegate @delegate) { options.Services.AddSingleton(@delegate); diff --git a/src/Dap.Client/ProgressObservable.cs b/src/Dap.Client/ProgressObservable.cs index c961689b3..8860ba5c9 100644 --- a/src/Dap.Client/ProgressObservable.cs +++ b/src/Dap.Client/ProgressObservable.cs @@ -1,4 +1,5 @@ using System; +using System.Reactive.Concurrency; using System.Reactive.Disposables; using System.Reactive.Subjects; using OmniSharp.Extensions.DebugAdapter.Protocol; @@ -14,7 +15,7 @@ internal class ProgressObservable : IProgressObservable, IObserver(1); + _dataSubject = new ReplaySubject(1, Scheduler.Immediate); _disposable = new CompositeDisposable { Disposable.Create(_dataSubject.OnCompleted) }; ProgressToken = token; diff --git a/src/Dap.Protocol/DapReceiver.cs b/src/Dap.Protocol/DapReceiver.cs index 709070725..c7555e100 100644 --- a/src/Dap.Protocol/DapReceiver.cs +++ b/src/Dap.Protocol/DapReceiver.cs @@ -52,6 +52,8 @@ protected virtual IEnumerable GetRenor(JToken @object) var sequence = id.Value(); var messageType = type.Value(); + var properties = request.Properties().ToLookup(z => z.Name, StringComparer.OrdinalIgnoreCase); + if (messageType == "event") { if (!request.TryGetValue("event", out var @event)) @@ -60,7 +62,10 @@ protected virtual IEnumerable GetRenor(JToken @object) yield break; } - yield return new Notification(@event.Value(), request.TryGetValue("body", out var body) ? body : null); + yield return new Notification(@event.Value(), request.TryGetValue("body", out var body) ? body : null) { + TraceState = properties["tracestate"].FirstOrDefault()?.Value(), + TraceParent = properties["traceparent"].FirstOrDefault()?.Value() + }; yield break; } @@ -81,16 +86,29 @@ protected virtual IEnumerable GetRenor(JToken @object) // This makes it so that the cancel handler implementer must still return a positive response even if the request didn't make it through. if (ro.TryGetValue("requestId", out var requestId)) { - yield return new Notification(JsonRpcNames.CancelRequest, JObject.FromObject(new { id = requestId })); + yield return new Notification(JsonRpcNames.CancelRequest, JObject.FromObject(new { id = requestId })) { + TraceState = properties["tracestate"].FirstOrDefault()?.Value(), + TraceParent = properties["traceparent"].FirstOrDefault()?.Value() + }; ro.Remove("requestId"); } + else + { + yield return new Request(sequence, RequestNames.Cancel, ro) { + TraceState = properties["tracestate"].FirstOrDefault()?.Value(), + TraceParent = properties["traceparent"].FirstOrDefault()?.Value() + }; + yield break; + } + } - yield return new Request(sequence, RequestNames.Cancel, ro); + { + yield return new Request(sequence, requestName, requestObject) { + TraceState = properties["tracestate"].FirstOrDefault()?.Value(), + TraceParent = properties["traceparent"].FirstOrDefault()?.Value() + }; yield break; } - - yield return new Request(sequence, requestName, requestObject); - yield break; } if (messageType == "response") diff --git a/src/Dap.Protocol/DebugAdapterConverters/DapClientNotificationConverter.cs b/src/Dap.Protocol/DebugAdapterConverters/DapClientNotificationConverter.cs index c4bd824c9..17daf699a 100644 --- a/src/Dap.Protocol/DebugAdapterConverters/DapClientNotificationConverter.cs +++ b/src/Dap.Protocol/DebugAdapterConverters/DapClientNotificationConverter.cs @@ -33,6 +33,16 @@ public override void WriteJson(JsonWriter writer, OutgoingNotification value, Js writer.WritePropertyName("body"); serializer.Serialize(writer, value.Params); } + if (value.TraceParent != null) + { + writer.WritePropertyName("traceparent"); + writer.WriteValue(value.TraceParent); + if (!string.IsNullOrWhiteSpace(value.TraceState)) + { + writer.WritePropertyName("tracestate"); + writer.WriteValue(value.TraceState); + } + } writer.WriteEndObject(); } diff --git a/src/Dap.Protocol/DebugAdapterConverters/DapClientRequestConverter.cs b/src/Dap.Protocol/DebugAdapterConverters/DapClientRequestConverter.cs index 1b92d21c1..d69f82722 100644 --- a/src/Dap.Protocol/DebugAdapterConverters/DapClientRequestConverter.cs +++ b/src/Dap.Protocol/DebugAdapterConverters/DapClientRequestConverter.cs @@ -28,6 +28,16 @@ public override void WriteJson(JsonWriter writer, OutgoingRequest value, JsonSer writer.WritePropertyName("arguments"); serializer.Serialize(writer, value.Params); } + if (value.TraceParent != null) + { + writer.WritePropertyName("traceparent"); + writer.WriteValue(value.TraceParent); + if (!string.IsNullOrWhiteSpace(value.TraceState)) + { + writer.WritePropertyName("tracestate"); + writer.WriteValue(value.TraceState); + } + } writer.WriteEndObject(); } diff --git a/src/Dap.Protocol/Feature/Events/ProgressFeature.cs b/src/Dap.Protocol/Feature/Events/ProgressFeature.cs index f0df93eac..16ebd60a5 100644 --- a/src/Dap.Protocol/Feature/Events/ProgressFeature.cs +++ b/src/Dap.Protocol/Feature/Events/ProgressFeature.cs @@ -23,7 +23,7 @@ public abstract record ProgressEvent public string? Message { get; init; } } - [Parallel] + [Serial] [Method(EventNames.ProgressStart, Direction.ServerToClient)] [ GenerateHandler, @@ -60,7 +60,7 @@ public record ProgressStartEvent : ProgressEvent, IRequest public int? Percentage { get; init; } } - [Parallel] + [Serial] [Method(EventNames.ProgressUpdate, Direction.ServerToClient)] [ GenerateHandler, @@ -76,7 +76,7 @@ public record ProgressUpdateEvent : ProgressEvent, IRequest public double? Percentage { get; init; } } - [Parallel] + [Serial] [Method(EventNames.ProgressEnd, Direction.ServerToClient)] [ GenerateHandler, diff --git a/src/Dap.Protocol/Feature/Requests/ThreadsFeature.cs b/src/Dap.Protocol/Feature/Requests/ThreadsFeature.cs index 165077bea..1975a2b2e 100644 --- a/src/Dap.Protocol/Feature/Requests/ThreadsFeature.cs +++ b/src/Dap.Protocol/Feature/Requests/ThreadsFeature.cs @@ -2,7 +2,6 @@ using OmniSharp.Extensions.DebugAdapter.Protocol.Models; using OmniSharp.Extensions.JsonRpc; using OmniSharp.Extensions.JsonRpc.Generation; -using Thread = System.Threading.Thread; // ReSharper disable once CheckNamespace namespace OmniSharp.Extensions.DebugAdapter.Protocol diff --git a/src/Dap.Server/DebugAdapterServer.cs b/src/Dap.Server/DebugAdapterServer.cs index 880b6e7db..7e6cde49a 100644 --- a/src/Dap.Server/DebugAdapterServer.cs +++ b/src/Dap.Server/DebugAdapterServer.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reactive.Concurrency; using System.Reactive.Disposables; using System.Reactive.Subjects; using System.Reactive.Threading.Tasks; @@ -30,6 +31,7 @@ public class DebugAdapterServer : JsonRpcServerBase, IDebugAdapterServer, IDebug private readonly IEnumerable _startedDelegates; private readonly IEnumerable _startedHandlers; private readonly InstanceHasStarted _instanceHasStarted; + private readonly IScheduler _scheduler; private readonly IServiceProvider _serviceProvider; private readonly CompositeDisposable _disposable = new CompositeDisposable(); private readonly Connection _connection; @@ -100,7 +102,8 @@ internal DebugAdapterServer( IEnumerable initializeHandlers, IEnumerable initializedHandlers, IEnumerable startedHandlers, - InstanceHasStarted instanceHasStarted + InstanceHasStarted instanceHasStarted, + IScheduler scheduler ) : base(collection, responseRouter) { _capabilities = capabilities; @@ -117,6 +120,7 @@ InstanceHasStarted instanceHasStarted _initializedHandlers = initializedHandlers; _startedHandlers = startedHandlers; _instanceHasStarted = instanceHasStarted; + _scheduler = scheduler; _concurrency = options.Value.Concurrency; _disposable.Add(collection.Add(this)); @@ -142,7 +146,7 @@ public async Task Initialize(CancellationToken token) _connection.Open(); try { - _initializingTask = _initializeComplete.ToTask(token); + _initializingTask = _initializeComplete.ToTask(token, _scheduler); await _initializingTask.ConfigureAwait(false); await DebugAdapterEventingHelper.Run( _startedDelegates, @@ -150,6 +154,7 @@ await DebugAdapterEventingHelper.Run( _startedHandlers.Union(_collection.Select(z => z.Handler).OfType()), (handler, ct) => handler.OnStarted(this, ct), _concurrency, + _scheduler, token ).ConfigureAwait(false); _instanceHasStarted.Started = true; @@ -181,6 +186,7 @@ await DebugAdapterEventingHelper.Run( _initializeHandlers.Union(_collection.Select(z => z.Handler).OfType()), (handler, ct) => handler.OnInitialize(this, request, ct), _concurrency, + _scheduler, cancellationToken ).ConfigureAwait(false); @@ -231,6 +237,7 @@ await DebugAdapterEventingHelper.Run( _initializedHandlers.Union(_collection.Select(z => z.Handler).OfType()), (handler, ct) => handler.OnInitialized(this, request, response, ct), _concurrency, + _scheduler, cancellationToken ).ConfigureAwait(false); diff --git a/src/Dap.Server/DebugAdapterServerOptionsExtensions.cs b/src/Dap.Server/DebugAdapterServerOptionsExtensions.cs index 89aa784bb..f25ed04e0 100644 --- a/src/Dap.Server/DebugAdapterServerOptionsExtensions.cs +++ b/src/Dap.Server/DebugAdapterServerOptionsExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Reactive.Concurrency; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -21,6 +22,54 @@ public static DebugAdapterServerOptions WithRequestProcessIdentifier(this DebugA return options; } + /// + /// Sets both input and output schedulers to the same scheduler + /// + /// + /// + /// + public static DebugAdapterServerOptions WithScheduler(this DebugAdapterServerOptions options, IScheduler inputScheduler) + { + options.InputScheduler = options.OutputScheduler = options.DefaultScheduler = inputScheduler; + return options; + } + + /// + /// Sets the scheduler used during reading input + /// + /// + /// + /// + public static DebugAdapterServerOptions WithInputScheduler(this DebugAdapterServerOptions options, IScheduler inputScheduler) + { + options.InputScheduler = inputScheduler; + return options; + } + + /// + /// Sets the default scheduler to be used when scheduling other tasks + /// + /// + /// + /// + public static DebugAdapterServerOptions WithDefaultScheduler(this DebugAdapterServerOptions options, IScheduler defaultScheduler) + { + options.DefaultScheduler = defaultScheduler; + return options; + } + + /// + /// Sets the scheduler use during writing output + /// + /// + /// + /// + public static DebugAdapterServerOptions WithOutputScheduler(this DebugAdapterServerOptions options, IScheduler outputScheduler) + { + options.OutputScheduler = outputScheduler; + return options; + } + public static DebugAdapterServerOptions OnInitialize(this DebugAdapterServerOptions options, OnDebugAdapterServerInitializeDelegate @delegate) { options.Services.AddSingleton(@delegate); diff --git a/src/Dap.Shared/DebugAdapterEventingHelper.cs b/src/Dap.Shared/DebugAdapterEventingHelper.cs index 60176311f..4db62e462 100644 --- a/src/Dap.Shared/DebugAdapterEventingHelper.cs +++ b/src/Dap.Shared/DebugAdapterEventingHelper.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reactive.Concurrency; using System.Reactive.Linq; using System.Reactive.Threading.Tasks; using System.Threading; @@ -16,6 +17,7 @@ public static Task Run( IEnumerable handlers, Func executeHandler, int? concurrency, + IScheduler scheduler, CancellationToken cancellationToken ) { @@ -28,13 +30,13 @@ CancellationToken cancellationToken { return events.Merge(concurrency.Value) .LastOrDefaultAsync() - .ToTask(cancellationToken); + .ToTask(cancellationToken, scheduler); } return events .Merge() .LastOrDefaultAsync() - .ToTask(cancellationToken); + .ToTask(cancellationToken, scheduler); } } } diff --git a/src/Dap.Shared/DebugAdapterRequestRouter.cs b/src/Dap.Shared/DebugAdapterRequestRouter.cs index 8b9e939bc..0cc429eda 100644 --- a/src/Dap.Shared/DebugAdapterRequestRouter.cs +++ b/src/Dap.Shared/DebugAdapterRequestRouter.cs @@ -12,9 +12,10 @@ internal class DebugAdapterRequestRouter : RequestRouterBase private readonly DebugAdapterHandlerCollection _collection; public DebugAdapterRequestRouter( - DebugAdapterHandlerCollection collection, ISerializer serializer, IServiceScopeFactory serviceScopeFactory, ILogger logger + DebugAdapterHandlerCollection collection, ISerializer serializer, IServiceScopeFactory serviceScopeFactory, ILogger logger, + IActivityTracingStrategy? activityTracingStrategy = null ) - : base(serializer, serviceScopeFactory, logger) => + : base(serializer, serviceScopeFactory, logger, activityTracingStrategy) => _collection = collection; public IDisposable Add(IJsonRpcHandler handler) => _collection.Add(handler); diff --git a/src/Dap.Testing/DebugAdapterProtocolTestBase.cs b/src/Dap.Testing/DebugAdapterProtocolTestBase.cs index 92eabb3bf..2cbe87d4e 100644 --- a/src/Dap.Testing/DebugAdapterProtocolTestBase.cs +++ b/src/Dap.Testing/DebugAdapterProtocolTestBase.cs @@ -45,10 +45,11 @@ Action serverOptionsAction options .WithLoggerFactory(TestOptions.ClientLoggerFactory) .ConfigureLogging( - x => { - x.SetMinimumLevel(LogLevel.Trace); - } + x => { x.SetMinimumLevel(LogLevel.Trace); } ) + .WithInputScheduler(options.InputScheduler) + .WithOutputScheduler(options.OutputScheduler) + .WithDefaultScheduler(options.DefaultScheduler) .Services .AddTransient(typeof(IPipelineBehavior<,>), typeof(SettlePipeline<,>)) .AddSingleton(ClientEvents as IRequestSettler); @@ -61,11 +62,10 @@ Action serverOptionsAction options => { options .WithLoggerFactory(TestOptions.ServerLoggerFactory) - .ConfigureLogging( - x => { - x.SetMinimumLevel(LogLevel.Trace); - } - ) + .ConfigureLogging(x => { x.SetMinimumLevel(LogLevel.Trace); }) + .WithInputScheduler(options.InputScheduler) + .WithOutputScheduler(options.OutputScheduler) + .WithDefaultScheduler(options.DefaultScheduler) .Services .AddTransient(typeof(IPipelineBehavior<,>), typeof(SettlePipeline<,>)) .AddSingleton(ServerEvents as IRequestSettler); @@ -77,10 +77,9 @@ Action serverOptionsAction Disposable.Add(_client); Disposable.Add(_server); - return await Observable.FromAsync(_client.Initialize).ForkJoin( - Observable.FromAsync(_server.Initialize), - (_, _) => ( _client, _server ) - ).ToTask(CancellationToken).ConfigureAwait(false); + return await Observable.FromAsync(_client.Initialize) + .ForkJoin(Observable.FromAsync(_server.Initialize), (_, _) => ( _client, _server )) + .ToTask(CancellationToken); } } } diff --git a/src/Dap.Testing/DebugAdapterServerTestBase.cs b/src/Dap.Testing/DebugAdapterServerTestBase.cs index 7654f2cf1..1c53d4354 100644 --- a/src/Dap.Testing/DebugAdapterServerTestBase.cs +++ b/src/Dap.Testing/DebugAdapterServerTestBase.cs @@ -37,6 +37,9 @@ protected virtual async Task InitializeClient(Action), typeof(SettlePipeline<,>)) .AddSingleton(Events as IRequestSettler); diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 48b9b1adf..727ebafee 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,33 +1,36 @@ - - - - $(GitVersion_NuGetVersion) - 0.0.9.9 - $(GitVersion_Major).$(GitVersion_Minor).0.0 - $(GitVersion_AssemblySemVer) - $(GitVersion_InformationalVersion) - true - enable - - - - - <_Parameter1>Client.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f - - - <_Parameter1>JsonRpc.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f - - - <_Parameter1>Dap.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f - - - <_Parameter1>Lsp.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f - - - <_Parameter1>TestingUtils, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f - - - <_Parameter1>DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7 - - - + + + + $(GitVersion_NuGetVersion) + 0.0.9.9 + $(GitVersion_Major).$(GitVersion_Minor).0.0 + $(GitVersion_AssemblySemVer) + $(GitVersion_InformationalVersion) + true + enable + + + + + <_Parameter1>Client.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f + + + <_Parameter1>JsonRpc.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f + + + <_Parameter1>Dap.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f + + + <_Parameter1>Lsp.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f + + + <_Parameter1>Lsp.Integration.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f + + + <_Parameter1>TestingUtils, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f + + + <_Parameter1>DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7 + + + diff --git a/src/JsonRpc.Generators/Helpers.cs b/src/JsonRpc.Generators/Helpers.cs index ebed8a799..f7559140d 100644 --- a/src/JsonRpc.Generators/Helpers.cs +++ b/src/JsonRpc.Generators/Helpers.cs @@ -802,8 +802,40 @@ public static ArrowExpressionClauseSyntax GetRequestInvokeExpression() => ) ); - public static ArrowExpressionClauseSyntax GetPartialInvokeExpression(TypeSyntax responseType) => - ArrowExpressionClause( + public static ArrowExpressionClauseSyntax GetPartialInvokeExpression(TypeSyntax responseType, TypeSyntax? partialItemType) + { + var realResponseType = responseType is NullableTypeSyntax nts ? nts.ElementType : responseType; + var factoryArgument = Argument( + SimpleLambdaExpression( + Parameter(Identifier("value")), + ObjectCreationExpression(realResponseType) + .WithArgumentList(ArgumentList(SingletonSeparatedList(Argument(IdentifierName("value"))))) + ) + ); + var arguments = new[] { + Argument( + IdentifierName(@"request") + ), + factoryArgument, + Argument(IdentifierName("cancellationToken")) + }; + if (partialItemType is {}) + { + var realPartialItemType = partialItemType is NullableTypeSyntax nts2 ? nts2.ElementType : partialItemType; + arguments = new[] { + arguments[0], + arguments[1], + Argument( + SimpleLambdaExpression( + Parameter(Identifier("value")), + ObjectCreationExpression(realPartialItemType) + .WithArgumentList(ArgumentList(SingletonSeparatedList(Argument(IdentifierName("value"))))) + ) + ), + arguments[2] + }; + } + return ArrowExpressionClause( InvocationExpression( MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, @@ -815,26 +847,9 @@ public static ArrowExpressionClauseSyntax GetPartialInvokeExpression(TypeSyntax IdentifierName("MonitorUntil") ) ) - .WithArgumentList( - ArgumentList( - SeparatedList( - new[] { - Argument( - IdentifierName(@"request") - ), - Argument( - SimpleLambdaExpression( - Parameter(Identifier("value")), - ObjectCreationExpression(responseType is NullableTypeSyntax nts ? nts.ElementType : responseType) - .WithArgumentList(ArgumentList(SingletonSeparatedList(Argument(IdentifierName("value"))))) - ) - ), - Argument(IdentifierName("cancellationToken")) - } - ) - ) - ) + .WithArgumentList(ArgumentList(SeparatedList(arguments))) ); + } public static string GetExtensionClassName(INamedTypeSymbol symbol) => SpecialCasedHandlerFullName(symbol).Split('.').Last() + "Extensions"; diff --git a/src/JsonRpc.Generators/Strategies/SendMethodRequestStrategy.cs b/src/JsonRpc.Generators/Strategies/SendMethodRequestStrategy.cs index 47eb046d8..fdcf99139 100644 --- a/src/JsonRpc.Generators/Strategies/SendMethodRequestStrategy.cs +++ b/src/JsonRpc.Generators/Strategies/SendMethodRequestStrategy.cs @@ -59,7 +59,7 @@ public IEnumerable Apply(ExtensionMethodContext extensi ) ) .WithParameterList(parameterList) - .WithExpressionBody(Helpers.GetPartialInvokeExpression(request.Response.Syntax)) + .WithExpressionBody(Helpers.GetPartialInvokeExpression(request.Response.Syntax, request.PartialItem.Syntax)) .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)); yield break; } @@ -91,7 +91,7 @@ public IEnumerable Apply(ExtensionMethodContext extensi ) ) .WithParameterList(parameterList) - .WithExpressionBody(Helpers.GetPartialInvokeExpression(request.Response.Syntax)) + .WithExpressionBody(Helpers.GetPartialInvokeExpression(request.Response.Syntax, default)) .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)); yield break; } diff --git a/src/JsonRpc.Testing/JsonRpcTestBase.cs b/src/JsonRpc.Testing/JsonRpcTestBase.cs index 4b0accc07..991ca6165 100644 --- a/src/JsonRpc.Testing/JsonRpcTestBase.cs +++ b/src/JsonRpc.Testing/JsonRpcTestBase.cs @@ -1,7 +1,9 @@ using System; using System.Diagnostics; using System.Reactive; +using System.Reactive.Concurrency; using System.Reactive.Disposables; +using System.Reactive.Threading.Tasks; using System.Threading; using System.Threading.Tasks; diff --git a/src/JsonRpc.Testing/JsonRpcTestOptions.cs b/src/JsonRpc.Testing/JsonRpcTestOptions.cs index 13df29487..1b998acab 100644 --- a/src/JsonRpc.Testing/JsonRpcTestOptions.cs +++ b/src/JsonRpc.Testing/JsonRpcTestOptions.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO.Pipelines; using System.Linq; +using System.Reactive.Concurrency; using System.Reflection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; @@ -24,6 +25,8 @@ public JsonRpcTestOptions(ILoggerFactory clientLoggerFactory, ILoggerFactory ser public ILoggerFactory ClientLoggerFactory { get; internal set; } = NullLoggerFactory.Instance; public ILoggerFactory ServerLoggerFactory { get; internal set; } = NullLoggerFactory.Instance; + public IScheduler ClientScheduler { get; internal set; } = TaskPoolScheduler.Default; + public IScheduler ServerScheduler { get; internal set; } = TaskPoolScheduler.Default; public TimeSpan WaitTime { get; internal set; } = TimeSpan.FromMilliseconds(50); public TimeSpan Timeout { get; internal set; } = TimeSpan.FromMilliseconds(500); public TimeSpan CancellationTimeout { get; internal set; } = TimeSpan.FromSeconds(50); diff --git a/src/JsonRpc.Testing/JsonRpcTestOptionsExtensions.cs b/src/JsonRpc.Testing/JsonRpcTestOptionsExtensions.cs index 1fa284b70..b367ff631 100644 --- a/src/JsonRpc.Testing/JsonRpcTestOptionsExtensions.cs +++ b/src/JsonRpc.Testing/JsonRpcTestOptionsExtensions.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO.Pipelines; using System.Linq; +using System.Reactive.Concurrency; using System.Reflection; using Microsoft.Extensions.Logging; @@ -21,6 +22,30 @@ public static JsonRpcTestOptions WithClientLoggerFactory(this JsonRpcTestOptions return options; } + public static JsonRpcTestOptions WithLoggerFactory(this JsonRpcTestOptions options, ILoggerFactory loggerFactory) + { + options.ClientLoggerFactory = options.ServerLoggerFactory = loggerFactory; + return options; + } + + public static JsonRpcTestOptions WithServerScheduler(this JsonRpcTestOptions options, IScheduler scheduler) + { + options.ServerScheduler = scheduler; + return options; + } + + public static JsonRpcTestOptions WithClientScheduler(this JsonRpcTestOptions options, IScheduler scheduler) + { + options.ClientScheduler = scheduler; + return options; + } + + public static JsonRpcTestOptions WithScheduler(this JsonRpcTestOptions options, IScheduler scheduler) + { + options.ClientScheduler = options.ServerScheduler = scheduler; + return options; + } + public static JsonRpcTestOptions WithWaitTime(this JsonRpcTestOptions options, TimeSpan waitTime) { options.WaitTime = waitTime; diff --git a/src/JsonRpc.Testing/Settler.cs b/src/JsonRpc.Testing/Settler.cs index 2fc758d70..f25ee60e3 100644 --- a/src/JsonRpc.Testing/Settler.cs +++ b/src/JsonRpc.Testing/Settler.cs @@ -60,7 +60,7 @@ public Settler(JsonRpcTestOptions options, CancellationToken cancellationToken, _requester = subject.AsObserver(); } - public Task SettleNext() => SettleNextInternal().ToTask(_cancellationToken); + public Task SettleNext() => SettleNextInternal().ToTask(_cancellationToken, _scheduler); public IObservable SettleNextInternal() => _settle .Catch(_ => _timeoutValue) diff --git a/src/JsonRpc/Client/OutgoingNotification.cs b/src/JsonRpc/Client/OutgoingNotification.cs index 750249a02..9f5696be9 100644 --- a/src/JsonRpc/Client/OutgoingNotification.cs +++ b/src/JsonRpc/Client/OutgoingNotification.cs @@ -1,9 +1,23 @@ +using Newtonsoft.Json; + namespace OmniSharp.Extensions.JsonRpc.Client { - public class OutgoingNotification + public record OutgoingNotification : ITraceData { public string Method { get; set; } = null!; public object? Params { get; set; } + + /// + /// Gets or sets the data for the W3C Trace Context traceparent value. + /// + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public string? TraceParent { get; set; } + + /// + /// Gets or sets the data for the W3C Trace Context tracestate value. + /// + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public string? TraceState { get; set; } } } diff --git a/src/JsonRpc/Client/OutgoingRequest.cs b/src/JsonRpc/Client/OutgoingRequest.cs index 6252de1aa..e639e8435 100644 --- a/src/JsonRpc/Client/OutgoingRequest.cs +++ b/src/JsonRpc/Client/OutgoingRequest.cs @@ -2,7 +2,7 @@ namespace OmniSharp.Extensions.JsonRpc.Client { - public class OutgoingRequest + public record OutgoingRequest : ITraceData { public object? Id { get; set; } @@ -10,5 +10,17 @@ public class OutgoingRequest [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public object? Params { get; set; } + + /// + /// Gets or sets the data for the W3C Trace Context traceparent value. + /// + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public string? TraceParent { get; set; } + + /// + /// Gets or sets the data for the W3C Trace Context tracestate value. + /// + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public string? TraceState { get; set; } } } diff --git a/src/JsonRpc/Client/OutgoingResponse.cs b/src/JsonRpc/Client/OutgoingResponse.cs index 0b4dfa3fe..582e53c58 100644 --- a/src/JsonRpc/Client/OutgoingResponse.cs +++ b/src/JsonRpc/Client/OutgoingResponse.cs @@ -2,7 +2,7 @@ namespace OmniSharp.Extensions.JsonRpc.Client { - public class OutgoingResponse + public record OutgoingResponse { public OutgoingResponse(object id, ServerRequest request) { diff --git a/src/JsonRpc/IActivityTracingStrategy.cs b/src/JsonRpc/IActivityTracingStrategy.cs new file mode 100644 index 000000000..6a8837a81 --- /dev/null +++ b/src/JsonRpc/IActivityTracingStrategy.cs @@ -0,0 +1,353 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading; +using Microsoft; +using OmniSharp.Extensions.JsonRpc.Server; + +namespace OmniSharp.Extensions.JsonRpc +{ + /// + /// Based on https://github.com/microsoft/vs-streamjsonrpc IActivityTracingStrategy + /// + public interface IActivityTracingStrategy + { + void ApplyOutgoing(ITraceData data); + IDisposable? ApplyInbound(ITraceData data); + } + internal static class Hex + { + private static readonly byte[] HexBytes = new byte[] { (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f' }; + private static readonly byte[] ReverseHexDigits = BuildReverseHexDigits(); + + internal static void Encode(ReadOnlySpan src, ref Span dest) + { + Span bytes = MemoryMarshal.Cast(dest); + + // Inspired by http://stackoverflow.com/questions/623104/c-byte-to-hex-string/3974535#3974535 + int lengthInNibbles = src.Length * 2; + + for (int i = 0; i < (lengthInNibbles & -2); i++) + { + int index0 = +i >> 1; + var b = (byte)(src[index0] >> 4); + bytes[(2 * i) + 1] = 0; + bytes[2 * i++] = HexBytes[b]; + + b = (byte)(src[index0] & 0x0F); + bytes[(2 * i) + 1] = 0; + bytes[2 * i] = HexBytes[b]; + } + + dest = dest.Slice(lengthInNibbles); + } + + internal static void Decode(ReadOnlySpan value, Span bytes) + { + for (int i = 0; i < value.Length; i++) + { + int c1 = ReverseHexDigits[value[i++] - '0'] << 4; + int c2 = ReverseHexDigits[value[i] - '0']; + + bytes[i >> 1] = (byte)(c1 + c2); + } + } + + private static byte[] BuildReverseHexDigits() + { + var bytes = new byte['f' - '0' + 1]; + + for (int i = 0; i < 10; i++) + { + bytes[i] = (byte)i; + } + + for (int i = 10; i < 16; i++) + { + bytes[i + 'a' - '0' - 0x0a] = (byte)i; + bytes[i + 'A' - '0' - 0x0a] = (byte)i; + } + + return bytes; + } + } + + internal unsafe struct TraceParent + { + internal const int VersionByteCount = 1; + internal const int ParentIdByteCount = 8; + internal const int TraceIdByteCount = 16; + internal const int FlagsByteCount = 1; + + internal byte Version; + + internal fixed byte TraceId[TraceIdByteCount]; + + internal fixed byte ParentId[ParentIdByteCount]; + + internal TraceFlags Flags; + + internal TraceParent(string? traceparent) + { + if (traceparent is null) + { + this.Version = 0; + this.Flags = TraceFlags.None; + return; + } + + ReadOnlySpan traceparentChars = traceparent.AsSpan(); + + // Decode version + ReadOnlySpan slice = Consume(ref traceparentChars, VersionByteCount * 2); + fixed (byte* pVersion = &this.Version) + { + Hex.Decode(slice, new Span(pVersion, 1)); + } + + ConsumeHyphen(ref traceparentChars); + + // Decode traceid + slice = Consume(ref traceparentChars, TraceIdByteCount * 2); + fixed (byte* pTraceId = this.TraceId) + { + Hex.Decode(slice, new Span(pTraceId, TraceIdByteCount)); + } + + ConsumeHyphen(ref traceparentChars); + + // Decode parentid + slice = Consume(ref traceparentChars, ParentIdByteCount * 2); + fixed (byte* pParentId = this.ParentId) + { + Hex.Decode(slice, new Span(pParentId, ParentIdByteCount)); + } + + ConsumeHyphen(ref traceparentChars); + + // Decode flags + slice = Consume(ref traceparentChars, FlagsByteCount * 2); + fixed (TraceFlags* pFlags = &this.Flags) + { + Hex.Decode(slice, new Span(pFlags, 1)); + } + + static void ConsumeHyphen(ref ReadOnlySpan value) + { + if (value[0] != '-') + { + Requires.Fail("Invalid format."); + } + + value = value.Slice(1); + } + + ReadOnlySpan Consume(ref ReadOnlySpan buffer, int length) + { + ReadOnlySpan result = buffer.Slice(0, length); + buffer = buffer.Slice(length); + return result; + } + } + + [Flags] + internal enum TraceFlags : byte + { + /// + /// No flags. + /// + None = 0x0, + + /// + /// The parent is tracing their action. + /// + Sampled = 0x1, + } + + internal Guid TraceIdGuid + { + get + { + fixed (byte* pTraceId = this.TraceId) + { + return CopyBufferToGuid(new ReadOnlySpan(pTraceId, TraceIdByteCount)); + } + } + } + + public override string ToString() + { + // When calculating the number of characters required, double each 'byte' we have to encode since we're using hex. + Span traceparent = stackalloc char[(VersionByteCount * 2) + 1 + (TraceIdByteCount * 2) + 1 + (ParentIdByteCount * 2) + 1 + (FlagsByteCount * 2)]; + Span traceParentRemaining = traceparent; + + fixed (byte* pVersion = &this.Version) + { + Hex.Encode(new ReadOnlySpan(pVersion, 1), ref traceParentRemaining); + } + + AddHyphen(ref traceParentRemaining); + + fixed (byte* pTraceId = this.TraceId) + { + Hex.Encode(new ReadOnlySpan(pTraceId, TraceIdByteCount), ref traceParentRemaining); + } + + AddHyphen(ref traceParentRemaining); + + fixed (byte* pParentId = this.ParentId) + { + Hex.Encode(new ReadOnlySpan(pParentId, ParentIdByteCount), ref traceParentRemaining); + } + + AddHyphen(ref traceParentRemaining); + + fixed (TraceFlags* pFlags = &this.Flags) + { + Hex.Encode(new ReadOnlySpan(pFlags, 1), ref traceParentRemaining); + } + + Debug.Assert(traceParentRemaining.Length == 0, "Characters were not initialized."); + + fixed (char* pValue = traceparent) + { + return new string(pValue, 0, traceparent.Length); + } + + static void AddHyphen(ref Span value) + { + value[0] = '-'; + value = value.Slice(1); + } + } + + private static unsafe Guid CopyBufferToGuid(ReadOnlySpan buffer) + { + Debug.Assert(buffer.Length == 16, "Guid buffer length mismatch."); + fixed (byte* pBuffer = buffer) + { + return *(Guid*)pBuffer; + } + } + } + + public sealed class CorrelationManagerTracingStrategy : IActivityTracingStrategy + { + private static readonly AsyncLocal TraceStateAsyncLocal = new AsyncLocal(); + + /// + /// Gets or sets the contextual tracestate value. + /// + public static string? TraceState + { + get => TraceStateAsyncLocal.Value; + set => TraceStateAsyncLocal.Value = value; + } + + /// + /// Gets or sets the that will receive the activity transfer, start and stop events . + /// + public TraceSource? TraceSource { get; set; } + + public unsafe void ApplyOutgoing(ITraceData data) + { + if (Trace.CorrelationManager.ActivityId != Guid.Empty) + { + var traceparent = default(TraceParent); + + FillRandomBytes(new Span(traceparent.ParentId, TraceParent.ParentIdByteCount)); + CopyGuidToBuffer(Trace.CorrelationManager.ActivityId, new Span(traceparent.TraceId, TraceParent.TraceIdByteCount)); + + if (this.TraceSource is object && (this.TraceSource.Switch.Level & SourceLevels.ActivityTracing) == SourceLevels.ActivityTracing && this.TraceSource.Listeners.Count > 0) + { + traceparent.Flags |= TraceParent.TraceFlags.Sampled; + } + + data.TraceParent = traceparent.ToString(); + data.TraceState = TraceState; + } + } + + /// + public unsafe IDisposable? ApplyInbound(ITraceData request) + { + var traceparent = new TraceParent(request.TraceParent); + Guid childActivityId = Guid.NewGuid(); + string? activityName = request is IMethodWithParams p ? p.Method : null; + + return new ActivityState(request, this.TraceSource, activityName, traceparent.TraceIdGuid, childActivityId); + } + + private static void FillRandomBytes(Span buffer) => CopyGuidToBuffer(Guid.NewGuid(), buffer); + + private unsafe static void CopyGuidToBuffer(Guid guid, Span buffer) + { + ReadOnlySpan guidBytes = new ReadOnlySpan(&guid, sizeof(Guid)); + guidBytes.Slice(0, buffer.Length).CopyTo(buffer); + } + + private class ActivityState : IDisposable + { + private readonly TraceSource? traceSource; + private readonly Guid originalActivityId; + private readonly string? originalTraceState; + private readonly string? activityName; + private readonly Guid parentTraceId; + + internal ActivityState(ITraceData request, TraceSource? traceSource, string? activityName, Guid parentTraceId, Guid childTraceId) + { + this.originalActivityId = Trace.CorrelationManager.ActivityId; + this.originalTraceState = TraceState; + this.activityName = activityName; + this.parentTraceId = parentTraceId; + + if (traceSource is object && parentTraceId != Guid.Empty) + { + // We set ActivityId to a short-lived value here for the sake of the TraceTransfer call that comes next. + // TraceTransfer goes from the current activity to the one passed as an argument. + // Without a traceSource object, there's no transfer and thus no need to set this temporary ActivityId. + Trace.CorrelationManager.ActivityId = parentTraceId; + traceSource.TraceTransfer(0, nameof(TraceEventType.Transfer), childTraceId); + } + + Trace.CorrelationManager.ActivityId = childTraceId; + TraceState = request.TraceState; + + traceSource?.TraceEvent(TraceEventType.Start, 0, this.activityName); + + this.traceSource = traceSource; + } + + public void Dispose() + { + this.traceSource?.TraceEvent(TraceEventType.Stop, 0, this.activityName); + + if (this.parentTraceId != Guid.Empty) + { + this.traceSource?.TraceTransfer(0, nameof(TraceEventType.Transfer), this.parentTraceId); + } + + Trace.CorrelationManager.ActivityId = this.originalActivityId; + TraceState = this.originalTraceState; + } + } + } + + public interface ITraceData + { + + + /// + /// Gets or sets the data for the W3C Trace Context traceparent value. + /// + string? TraceParent { get; set; } + + /// + /// Gets or sets the data for the W3C Trace Context tracestate value. + /// + string? TraceState { get; set; } + } +} diff --git a/src/JsonRpc/JsonRpc.csproj b/src/JsonRpc/JsonRpc.csproj index 81d2c2328..f62bc92b6 100644 --- a/src/JsonRpc/JsonRpc.csproj +++ b/src/JsonRpc/JsonRpc.csproj @@ -5,18 +5,20 @@ OmniSharp.Extensions.JsonRpc OmniSharp.Extensions.JsonRpc Primitives for working with JsonRpc. This library is used as the base for communication with language servers + true - - - - - - - - - - + + + + + + + + + + + diff --git a/src/JsonRpc/JsonRpcServerOptionsBase.cs b/src/JsonRpc/JsonRpcServerOptionsBase.cs index 6a513ba3e..22847aec2 100644 --- a/src/JsonRpc/JsonRpcServerOptionsBase.cs +++ b/src/JsonRpc/JsonRpcServerOptionsBase.cs @@ -37,7 +37,8 @@ public ILoggerFactory LoggerFactory public IRequestProcessIdentifier? RequestProcessIdentifier { get; set; } public int? Concurrency { get; set; } public IScheduler InputScheduler { get; set; } = TaskPoolScheduler.Default; - public IScheduler OutputScheduler { get; set; } = TaskPoolScheduler.Default; + public IScheduler OutputScheduler { get; set; } = Scheduler.Immediate; + public IScheduler DefaultScheduler { get; set; } = TaskPoolScheduler.Default; public CreateResponseExceptionHandler? CreateResponseException { get; set; } public OnUnhandledExceptionHandler? OnUnhandledException { get; set; } public bool SupportsContentModified { get; set; } = true; @@ -143,5 +144,12 @@ public T WithLink(string fromMethod, string toMethod) Handlers.Add(JsonRpcHandlerDescription.Link(fromMethod, toMethod)); return (T) (object) this; } + + public T WithActivityTracingStrategy(IActivityTracingStrategy activityTracingStrategy) + { + Services.RemoveAll(typeof(IActivityTracingStrategy)); + Services.AddSingleton(activityTracingStrategy); + return (T) (object) this; + } } } diff --git a/src/JsonRpc/JsonRpcServerOptionsExtensions.cs b/src/JsonRpc/JsonRpcServerOptionsExtensions.cs index b2a821b75..6c341cc7a 100644 --- a/src/JsonRpc/JsonRpcServerOptionsExtensions.cs +++ b/src/JsonRpc/JsonRpcServerOptionsExtensions.cs @@ -24,7 +24,7 @@ public static JsonRpcServerOptions WithAssemblyAttributeScanning(this JsonRpcSer /// public static JsonRpcServerOptions WithScheduler(this JsonRpcServerOptions options, IScheduler inputScheduler) { - options.InputScheduler = options.OutputScheduler = inputScheduler; + options.InputScheduler = options.OutputScheduler = options.DefaultScheduler = inputScheduler; return options; } @@ -40,6 +40,18 @@ public static JsonRpcServerOptions WithInputScheduler(this JsonRpcServerOptions return options; } + /// + /// Sets the default scheduler to be used when scheduling other tasks + /// + /// + /// + /// + public static JsonRpcServerOptions WithDefaultScheduler(this JsonRpcServerOptions options, IScheduler defaultScheduler) + { + options.DefaultScheduler = defaultScheduler; + return options; + } + /// /// Sets the scheduler use during writing output /// diff --git a/src/JsonRpc/JsonRpcServerServiceCollectionExtensions.cs b/src/JsonRpc/JsonRpcServerServiceCollectionExtensions.cs index 35538dfab..1859fd078 100644 --- a/src/JsonRpc/JsonRpcServerServiceCollectionExtensions.cs +++ b/src/JsonRpc/JsonRpcServerServiceCollectionExtensions.cs @@ -58,6 +58,8 @@ internal static IContainer AddJsonRpcServerCore(this IContainer container, Js reuse: Reuse.Singleton ); + container.RegisterInstance(options.DefaultScheduler); + container.RegisterMany( serviceTypeCondition: type => type.IsInterface, reuse: Reuse.Singleton diff --git a/src/JsonRpc/OutputHandler.cs b/src/JsonRpc/OutputHandler.cs index 2b167bdc9..f20d06d3b 100644 --- a/src/JsonRpc/OutputHandler.cs +++ b/src/JsonRpc/OutputHandler.cs @@ -3,12 +3,14 @@ using System.ComponentModel; using System.IO.Pipelines; using System.Linq; +using System.Reactive; using System.Reactive.Concurrency; using System.Reactive.Disposables; using System.Reactive.Linq; using System.Reactive.Subjects; using System.Text; using System.Threading; +using System.Threading.Channels; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -20,40 +22,52 @@ public class OutputHandler : IOutputHandler private readonly ISerializer _serializer; private readonly IEnumerable _outputFilters; private readonly ILogger _logger; - private readonly Subject _queue; - private readonly ReplaySubject _delayedQueue; + private readonly IActivityTracingStrategy? _activityTracingStrategy; + + + private readonly ChannelReader _queue; + private readonly Queue _delayedQueue; private readonly TaskCompletionSource _outputIsFinished; private readonly CompositeDisposable _disposable; private bool _delayComplete; + private readonly CancellationTokenSource _stopProcessing; + private readonly Channel _channel; + private readonly ChannelWriter _writer; public OutputHandler( PipeWriter pipeWriter, ISerializer serializer, IEnumerable outputFilters, IScheduler scheduler, - ILogger logger + ILogger logger, + IActivityTracingStrategy? activityTracingStrategy = null ) { _pipeWriter = pipeWriter; _serializer = serializer; _outputFilters = outputFilters.ToArray(); _logger = logger; - _queue = new Subject(); - _delayedQueue = new ReplaySubject(); + _activityTracingStrategy = activityTracingStrategy; + _delayedQueue = new Queue(); _outputIsFinished = new TaskCompletionSource(); + _channel = Channel.CreateUnbounded( + new UnboundedChannelOptions() { + AllowSynchronousContinuations = true, + SingleReader = true, + SingleWriter = false + } + ); + _queue = _channel.Reader; + _writer = _channel.Writer; + + _stopProcessing = new CancellationTokenSource(); _disposable = new CompositeDisposable { - _queue - .ObserveOn(scheduler) - .Select(value => Observable.FromAsync(ct => ProcessOutputStream(value, ct))) - .Concat() - .Subscribe(), - _delayedQueue - .ToArray() - .SelectMany(z => z) - .Subscribe(_queue.OnNext), - _queue, - _delayedQueue + Disposable.Create(() => _stopProcessing.Cancel()), + _stopProcessing, + Observable.FromAsync(() => ProcessOutputStream(_stopProcessing.Token)) + .Do(_ => { }, e => _logger.LogCritical(e, "unhandled exception")) + .Subscribe() }; } @@ -66,30 +80,37 @@ public void Send(object? value) { try { - if (_queue.IsDisposed || _disposable.IsDisposed || value == null) return; - if (!ShouldSend(value)) + if (_disposable.IsDisposed || value == null) return; + if (!ShouldSend(value) && !_delayComplete) { - if (_delayComplete || _delayedQueue.IsDisposed || !_delayedQueue.HasObservers) return; - _delayedQueue.OnNext(value); + _delayedQueue.Enqueue(value); } else { - _queue.OnNext(value); + _writer.TryWrite(value); } } - catch (ObjectDisposedException) { } + catch (ObjectDisposedException) + { + } } public void Initialized() { - if (_delayComplete || _delayedQueue.IsDisposed || !_delayedQueue.HasObservers) return; - _delayedQueue.OnCompleted(); + if (_delayComplete) return; + while (_delayedQueue.Count > 0) + { + var item = _delayedQueue.Dequeue(); + _writer.TryWrite(item); + } + _delayComplete = true; - _delayedQueue.Dispose(); + _delayedQueue.Clear(); } public async Task StopAsync() { + _channel.Writer.TryComplete(); await _pipeWriter.CompleteAsync().ConfigureAwait(false); _disposable.Dispose(); } @@ -105,23 +126,36 @@ internal async Task WriteAndFlush() await _pipeWriter.CompleteAsync().ConfigureAwait(false); } - private async Task ProcessOutputStream(object value, CancellationToken cancellationToken) + private async Task ProcessOutputStream(CancellationToken cancellationToken) { try { -// _logger.LogTrace("Writing out {@Value}", value); - // TODO: this will be part of the serialization refactor to make streaming first class - var content = _serializer.SerializeObject(value); - var contentBytes = Encoding.UTF8.GetBytes(content).AsMemory(); - await _pipeWriter.WriteAsync(Encoding.UTF8.GetBytes($"Content-Length: {contentBytes.Length}\r\n\r\n"), cancellationToken).ConfigureAwait(false); - await _pipeWriter.WriteAsync(contentBytes, cancellationToken).ConfigureAwait(false); - await _pipeWriter.FlushAsync(cancellationToken).ConfigureAwait(false); + do + { + var value = await _queue.ReadAsync(cancellationToken); + if (value is ITraceData traceData) + { + _activityTracingStrategy?.ApplyOutgoing(traceData); + } + + // TODO: this will be part of the serialization refactor to make streaming first class + var content = _serializer.SerializeObject(value); + var contentBytes = Encoding.UTF8.GetBytes(content).AsMemory(); + await _pipeWriter.WriteAsync(Encoding.UTF8.GetBytes($"Content-Length: {contentBytes.Length}\r\n\r\n"), cancellationToken).ConfigureAwait(false); + await _pipeWriter.WriteAsync(contentBytes, cancellationToken).ConfigureAwait(false); + await _pipeWriter.FlushAsync(cancellationToken).ConfigureAwait(false); + } while (true); } catch (OperationCanceledException ex) when (ex.CancellationToken != cancellationToken) { _logger.LogTrace(ex, "Cancellation happened"); Error(ex); } + catch (OperationCanceledException ex) when (ex.CancellationToken == cancellationToken) + { + _logger.LogTrace(ex, "Cancellation happened"); + Cancel(); + } catch (Exception e) { _logger.LogTrace(e, "Could not write to output handler, perhaps serialization failed?"); @@ -134,12 +168,21 @@ private async Task ProcessOutputStream(object value, CancellationToken cancellat private void Error(Exception ex) { _outputIsFinished.TrySetResult(ex); + _writer.TryComplete(); + _disposable.Dispose(); + } + + private void Cancel() + { + _outputIsFinished.TrySetCanceled(); + _writer.TryComplete(); _disposable.Dispose(); } public void Dispose() { _outputIsFinished.TrySetResult(null); + _writer.TryComplete(); _disposable.Dispose(); } } diff --git a/src/JsonRpc/ProcessScheduler.cs b/src/JsonRpc/ProcessScheduler.cs index 4540ea95f..ae0da9171 100644 --- a/src/JsonRpc/ProcessScheduler.cs +++ b/src/JsonRpc/ProcessScheduler.cs @@ -34,7 +34,7 @@ IScheduler scheduler var observableQueue = new BehaviorSubject<(RequestProcessType type, ReplaySubject> observer, Subject? contentModifiedSource)>( - ( RequestProcessType.Serial, new ReplaySubject>(int.MaxValue), supportContentModified ? new Subject() : null ) + ( RequestProcessType.Serial, new ReplaySubject>(int.MaxValue, Scheduler.Immediate), supportContentModified ? new Subject() : null ) ); cd.Add( @@ -52,7 +52,7 @@ IScheduler scheduler logger.LogDebug("Completing existing request process type {Type}", observableQueue.Value.type); observableQueue.Value.observer.OnCompleted(); - observableQueue.OnNext(( item.type, new ReplaySubject>(int.MaxValue), supportContentModified ? new Subject() : null )); + observableQueue.OnNext(( item.type, new ReplaySubject>(int.MaxValue, Scheduler.Immediate), supportContentModified ? new Subject() : null )); } logger.LogDebug("Queueing {Type}:{Name} request for processing", item.type, item.name); diff --git a/src/JsonRpc/Receiver.cs b/src/JsonRpc/Receiver.cs index af6700d8e..9b7c3dcee 100644 --- a/src/JsonRpc/Receiver.cs +++ b/src/JsonRpc/Receiver.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json.Linq; @@ -97,14 +98,24 @@ protected virtual Renor GetRenor(JToken @object) @params = new JObject(); } + var properties = request.Properties().ToLookup(z => z.Name, StringComparer.OrdinalIgnoreCase); + // id == request // !id == notification if (!hasRequestId) { - return new Notification(method!, @params); + return new Notification(method!, @params) { + TraceState = properties["tracestate"].FirstOrDefault()?.Value(), + TraceParent = properties["traceparent"].FirstOrDefault()?.Value() + }; + } + else + { + return new Request(requestId!, method!, @params) { + TraceState = properties["tracestate"].FirstOrDefault()?.Value(), + TraceParent = properties["traceparent"].FirstOrDefault()?.Value() + }; } - - return new Request(requestId!, method!, @params); } public bool ShouldOutput(object value) => _initialized; diff --git a/src/JsonRpc/RequestRouter.cs b/src/JsonRpc/RequestRouter.cs index 12a0ddb23..cf52940e3 100644 --- a/src/JsonRpc/RequestRouter.cs +++ b/src/JsonRpc/RequestRouter.cs @@ -13,9 +13,10 @@ public RequestRouter( IHandlersManager collection, ISerializer serializer, IServiceScopeFactory serviceScopeFactory, - ILogger logger + ILogger logger, + IActivityTracingStrategy? activityTracingStrategy = null ) - : base(serializer, serviceScopeFactory, logger) => + : base(serializer, serviceScopeFactory, logger, activityTracingStrategy) => _collection = collection; private IHandlerDescriptor FindDescriptor(IMethodWithParams instance) => _collection.Descriptors.FirstOrDefault(x => x.Method == instance.Method); diff --git a/src/JsonRpc/RequestRouterBase.cs b/src/JsonRpc/RequestRouterBase.cs index 0470fc30b..dcfafde60 100644 --- a/src/JsonRpc/RequestRouterBase.cs +++ b/src/JsonRpc/RequestRouterBase.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Reactive.Disposables; using System.Reflection; using System.Threading; using System.Threading.Tasks; @@ -22,41 +23,52 @@ public abstract class RequestRouterBase : IRequestRouter descriptors, Notification notification, CancellationToken token) { - using var debug = _logger.TimeDebug("Routing Notification {Method}", notification.Method); - using var _ = _logger.BeginScope( - new[] { - new KeyValuePair("Method", notification.Method), - new KeyValuePair("Params", notification.Params?.ToString()) - } - ); - - object? @params = null; - if (!( descriptors.Default?.Params is null )) + using (_activityTracingStrategy?.ApplyInbound(notification)?? Disposable.Empty) { - if (descriptors.Default.IsDelegatingHandler) - { - _logger.LogTrace("Converting params for Notification {Method} to {Type}", notification.Method, descriptors.Default.Params.GetGenericArguments()[0].FullName); - var o = notification.Params?.ToObject(descriptors.Default.Params.GetGenericArguments()[0], _serializer.JsonSerializer); - @params = Activator.CreateInstance(descriptors.Default.Params, o); - } - else + using var debug = _logger.TimeDebug("Routing Notification {Method}", notification.Method); + using var _ = _logger.BeginScope( + new[] { + new KeyValuePair("Method", notification.Method), + new KeyValuePair("Params", notification.Params?.ToString()) + } + ); + + object? @params = null; + if (!( descriptors.Default?.Params is null )) { - _logger.LogTrace("Converting params for Notification {Method} to {Type}", notification.Method, descriptors.Default.Params.FullName); - @params = notification.Params?.ToObject(descriptors.Default.Params, _serializer.JsonSerializer); + if (descriptors.Default.IsDelegatingHandler) + { + _logger.LogTrace( + "Converting params for Notification {Method} to {Type}", notification.Method, descriptors.Default.Params.GetGenericArguments()[0].FullName + ); + var o = notification.Params?.ToObject(descriptors.Default.Params.GetGenericArguments()[0], _serializer.JsonSerializer); + @params = Activator.CreateInstance(descriptors.Default.Params, o); + } + else + { + _logger.LogTrace("Converting params for Notification {Method} to {Type}", notification.Method, descriptors.Default.Params.FullName); + @params = notification.Params?.ToObject(descriptors.Default.Params, _serializer.JsonSerializer); + } } - } - await Task.WhenAll(descriptors.Select(descriptor => InnerRoute(_serviceScopeFactory, descriptor, @params, token))).ConfigureAwait(false); + await Task.WhenAll(descriptors.Select(descriptor => InnerRoute(_serviceScopeFactory, descriptor, @params, token))).ConfigureAwait(false); + } static async Task InnerRoute(IServiceScopeFactory serviceScopeFactory, TDescriptor descriptor, object? @params, CancellationToken token) { @@ -74,60 +86,65 @@ public virtual async Task RouteRequest(IRequestDescriptor("Id", request.Id.ToString()), - new KeyValuePair("Method", request.Method), - new KeyValuePair("Params", request.Params?.ToString()) - } - ); - - object? @params; - try + using (_activityTracingStrategy?.ApplyInbound(request) ?? Disposable.Empty) { - if (descriptors.Default!.IsDelegatingHandler) + using var debug = _logger.TimeDebug("Routing Request ({Id}) {Method}", request.Id, request.Method); + using var _ = _logger.BeginScope( + new[] { + new KeyValuePair("Id", request.Id.ToString()), + new KeyValuePair("Method", request.Method), + new KeyValuePair("Params", request.Params?.ToString()) + } + ); + + object? @params; + try { - _logger.LogTrace( - "Converting params for Request ({Id}) {Method} to {Type}", request.Id, request.Method, - descriptors.Default!.Params!.GetGenericArguments()[0].FullName - ); - var o = request.Params?.ToObject(descriptors.Default!.Params!.GetGenericArguments()[0], _serializer.JsonSerializer); - @params = Activator.CreateInstance(descriptors.Default!.Params, o); + if (descriptors.Default!.IsDelegatingHandler) + { + _logger.LogTrace( + "Converting params for Request ({Id}) {Method} to {Type}", request.Id, request.Method, + descriptors.Default!.Params!.GetGenericArguments()[0].FullName + ); + var o = request.Params?.ToObject(descriptors.Default!.Params!.GetGenericArguments()[0], _serializer.JsonSerializer); + @params = Activator.CreateInstance(descriptors.Default!.Params, o); + } + else + { + _logger.LogTrace("Converting params for Request ({Id}) {Method} to {Type}", request.Id, request.Method, descriptors.Default!.Params!.FullName); + _logger.LogTrace("Converting params for Notification {Method} to {Type}", request.Method, descriptors.Default!.Params.FullName); + @params = request.Params?.ToObject(descriptors.Default!.Params, _serializer.JsonSerializer); + } } - else + catch (Exception cannotDeserializeRequestParams) { - _logger.LogTrace("Converting params for Request ({Id}) {Method} to {Type}", request.Id, request.Method, descriptors.Default!.Params!.FullName); - _logger.LogTrace("Converting params for Notification {Method} to {Type}", request.Method, descriptors.Default!.Params.FullName); - @params = request.Params?.ToObject(descriptors.Default!.Params, _serializer.JsonSerializer); + _logger.LogError(new EventId(-32602), cannotDeserializeRequestParams, "Failed to deserialize request parameters."); + return new InvalidParams(request.Id, request.Method); } - } - catch (Exception cannotDeserializeRequestParams) - { - _logger.LogError(new EventId(-32602), cannotDeserializeRequestParams, "Failed to deserialize request parameters."); - return new InvalidParams(request.Id, request.Method); - } - using var scope = _serviceScopeFactory.CreateScope(); - // TODO: Do we want to support more handlers as "aggregate"? - if (typeof(IEnumerable).IsAssignableFrom(descriptors.Default!.Response) && typeof(string) != descriptors.Default!.Response && !typeof(JToken).IsAssignableFrom(descriptors.Default!.Response)) - { - var responses = await Task.WhenAll(descriptors.Select(descriptor => InnerRoute(_serviceScopeFactory, request, descriptor, @params, token, _logger))).ConfigureAwait(false); - var errorResponse = responses.FirstOrDefault(x => x.IsError); - if (errorResponse.IsError) return errorResponse; - if (responses.Length == 1) + using var scope = _serviceScopeFactory.CreateScope(); + // TODO: Do we want to support more handlers as "aggregate"? + if (typeof(IEnumerable).IsAssignableFrom(descriptors.Default!.Response) && typeof(string) != descriptors.Default!.Response + && !typeof(JToken).IsAssignableFrom(descriptors.Default!.Response)) { - return responses[0]; + var responses = await Task.WhenAll(descriptors.Select(descriptor => InnerRoute(_serviceScopeFactory, request, descriptor, @params, token, _logger))) + .ConfigureAwait(false); + var errorResponse = responses.FirstOrDefault(x => x.IsError); + if (errorResponse.IsError) return errorResponse; + if (responses.Length == 1) + { + return responses[0]; + } + + var response = Activator.CreateInstance( + typeof(AggregateResponse<>).MakeGenericType(descriptors.Default!.Response!), responses.Select(z => z.Response!.Result) + ); + return new OutgoingResponse(request.Id, response, request); } - var response = Activator.CreateInstance( - typeof(AggregateResponse<>).MakeGenericType(descriptors.Default!.Response!), responses.Select(z => z.Response!.Result) - ); - return new OutgoingResponse(request.Id, response, request); + return await InnerRoute(_serviceScopeFactory, request, descriptors.Default!, @params, token, _logger).ConfigureAwait(false); } - return await InnerRoute(_serviceScopeFactory, request, descriptors.Default!, @params, token, _logger).ConfigureAwait(false); - static async Task InnerRoute( IServiceScopeFactory serviceScopeFactory, Request request, TDescriptor descriptor, object? @params, CancellationToken token, ILogger logger diff --git a/src/JsonRpc/Serialization/Converters/ClientNotificationConverter.cs b/src/JsonRpc/Serialization/Converters/ClientNotificationConverter.cs index 14561083c..3e2c72ade 100644 --- a/src/JsonRpc/Serialization/Converters/ClientNotificationConverter.cs +++ b/src/JsonRpc/Serialization/Converters/ClientNotificationConverter.cs @@ -26,6 +26,16 @@ public override void WriteJson(JsonWriter writer, OutgoingNotification value, Js writer.WritePropertyName("params"); serializer.Serialize(writer, value.Params); } + if (value.TraceParent != null) + { + writer.WritePropertyName("traceparent"); + writer.WriteValue(value.TraceParent); + if (!string.IsNullOrWhiteSpace(value.TraceState)) + { + writer.WritePropertyName("tracestate"); + writer.WriteValue(value.TraceState); + } + } writer.WriteEndObject(); } diff --git a/src/JsonRpc/Serialization/Converters/ClientRequestConverter.cs b/src/JsonRpc/Serialization/Converters/ClientRequestConverter.cs index edf7863f0..cd62617cf 100644 --- a/src/JsonRpc/Serialization/Converters/ClientRequestConverter.cs +++ b/src/JsonRpc/Serialization/Converters/ClientRequestConverter.cs @@ -28,6 +28,16 @@ public override void WriteJson(JsonWriter writer, OutgoingRequest value, JsonSer writer.WritePropertyName("params"); serializer.Serialize(writer, value.Params); } + if (value.TraceParent != null) + { + writer.WritePropertyName("traceparent"); + writer.WriteValue(value.TraceParent); + if (!string.IsNullOrWhiteSpace(value.TraceState)) + { + writer.WritePropertyName("tracestate"); + writer.WriteValue(value.TraceState); + } + } writer.WriteEndObject(); } diff --git a/src/JsonRpc/Server/Notification.cs b/src/JsonRpc/Server/Notification.cs index b86d3b446..072489a02 100644 --- a/src/JsonRpc/Server/Notification.cs +++ b/src/JsonRpc/Server/Notification.cs @@ -2,7 +2,7 @@ namespace OmniSharp.Extensions.JsonRpc.Server { - public class Notification : IMethodWithParams + public class Notification : IMethodWithParams, ITraceData { public Notification( string method, @@ -16,5 +16,15 @@ public Notification( public string Method { get; } public JToken? Params { get; } + + /// + /// Gets or sets the data for the W3C Trace Context traceparent value. + /// + public string? TraceParent { get; set; } + + /// + /// Gets or sets the data for the W3C Trace Context tracestate value. + /// + public string? TraceState { get; set; } } } diff --git a/src/JsonRpc/Server/Request.cs b/src/JsonRpc/Server/Request.cs index d5c680890..9cd23e59e 100644 --- a/src/JsonRpc/Server/Request.cs +++ b/src/JsonRpc/Server/Request.cs @@ -2,7 +2,7 @@ namespace OmniSharp.Extensions.JsonRpc.Server { - public class Request : IMethodWithParams + public class Request : IMethodWithParams, ITraceData { public Request( object id, @@ -20,5 +20,15 @@ public Request( public string Method { get; } public JToken? Params { get; } + + /// + /// Gets or sets the data for the W3C Trace Context traceparent value. + /// + public string? TraceParent { get; set; } + + /// + /// Gets or sets the data for the W3C Trace Context tracestate value. + /// + public string? TraceState { get; set; } } } diff --git a/src/Protocol/AbstractHandlers.cs b/src/Protocol/AbstractHandlers.cs index 263864bc5..4fa7cc9c3 100644 --- a/src/Protocol/AbstractHandlers.cs +++ b/src/Protocol/AbstractHandlers.cs @@ -141,10 +141,14 @@ CancellationToken cancellationToken } var subject = new AsyncSubject(); + var task = subject + .Select(_factory) + .ToTask(cancellationToken, _progressManager.Scheduler) + .ConfigureAwait(false); // in the event nothing is emitted... subject.OnNext(default!); Handle(request, subject, cancellationToken); - return _factory(await subject); + return await task; } protected abstract void Handle(TParams request, IObserver results, CancellationToken cancellationToken); @@ -181,10 +185,14 @@ CancellationToken cancellationToken } var subject = new AsyncSubject(); + var task = subject + .Select(_factory) + .ToTask(cancellationToken, _progressManager.Scheduler) + .ConfigureAwait(false); // in the event nothing is emitted... subject.OnNext(default!); Handle(request, subject, cancellationToken); - return _factory(await subject); + return await task; } protected abstract void Handle(TParams request, IObserver results, CancellationToken cancellationToken); @@ -221,10 +229,14 @@ CancellationToken cancellationToken } var subject = new AsyncSubject(); + var task = subject + .Select(_factory) + .ToTask(cancellationToken, _progressManager.Scheduler) + .ConfigureAwait(false); // in the event nothing is emitted... subject.OnNext(default!); Handle(request, subject, cancellationToken); - return _factory(await subject); + return await task; } protected abstract void Handle(TParams request, IObserver results, CancellationToken cancellationToken); @@ -258,15 +270,18 @@ protected PartialResults(IProgressManager progressManager, Func>(); - var task = subject.Aggregate( - new List(), (acc, items) => { - acc.AddRange(items); - return acc; - } - ) - .ToTask(cancellationToken); + var task = subject + .Aggregate( + new List(), (acc, items) => { + acc.AddRange(items); + return acc; + } + ) + .Select(_factory) + .ToTask(cancellationToken, _progressManager.Scheduler) + .ConfigureAwait(false); Handle(request, subject, cancellationToken); - return _factory(await task.ConfigureAwait(false)); + return await task; } protected abstract void Handle(TParams request, IObserver> results, CancellationToken cancellationToken); @@ -299,19 +314,23 @@ protected PartialResults(IProgressManager progressManager, Func>(); - var task = subject.Aggregate( - new List(), (acc, items) => { - acc.AddRange(items); - return acc; - } - ) - .ToTask(cancellationToken); + var task = subject + .Aggregate( + new List(), (acc, items) => { + acc.AddRange(items); + return acc; + } + ) + .Select(_factory) + .ToTask(cancellationToken, _progressManager.Scheduler) + .ConfigureAwait(false); Handle(request, subject, cancellationToken); - return _factory(await task.ConfigureAwait(false)); + return await task; } protected abstract void Handle(TParams request, IObserver> results, CancellationToken cancellationToken); } + public abstract class PartialResultsCapability : BaseCapability, IJsonRpcRequestHandler @@ -339,15 +358,18 @@ protected PartialResultsCapability(IProgressManager progressManager, Func>(); - var task = subject.Aggregate( - new List(), (acc, items) => { - acc.AddRange(items); - return acc; - } - ) - .ToTask(cancellationToken); + var task = subject + .Aggregate( + new List(), (acc, items) => { + acc.AddRange(items); + return acc; + } + ) + .Select(_factory) + .ToTask(cancellationToken, _progressManager.Scheduler) + .ConfigureAwait(false); Handle(request, subject, cancellationToken); - return _factory(await task.ConfigureAwait(false)); + return await task; } protected abstract void Handle(TParams request, IObserver> results, CancellationToken cancellationToken); diff --git a/src/Protocol/Features/Document/SemanticTokensFeature.cs b/src/Protocol/Features/Document/SemanticTokensFeature.cs index 346aab518..99f1236cc 100644 --- a/src/Protocol/Features/Document/SemanticTokensFeature.cs +++ b/src/Protocol/Features/Document/SemanticTokensFeature.cs @@ -37,7 +37,7 @@ namespace Models ] [RegistrationOptions(typeof(SemanticTokensRegistrationOptions)), Capability(typeof(SemanticTokensCapability))] public partial record SemanticTokensParams : IWorkDoneProgressParams, ITextDocumentIdentifierParams, - IPartialItemRequest + IPartialItemRequest { /// /// The text document. @@ -57,7 +57,7 @@ public partial record SemanticTokensParams : IWorkDoneProgressParams, ITextDocum ] [RegistrationOptions(typeof(SemanticTokensRegistrationOptions)), Capability(typeof(SemanticTokensCapability))] public partial record SemanticTokensDeltaParams : IWorkDoneProgressParams, ITextDocumentIdentifierParams, - IPartialItemRequest, IDoesNotParticipateInRegistration + IPartialItemRequest, IDoesNotParticipateInRegistration { /// /// The text document. @@ -82,7 +82,7 @@ public partial record SemanticTokensDeltaParams : IWorkDoneProgressParams, IText ] [RegistrationOptions(typeof(SemanticTokensRegistrationOptions)), Capability(typeof(SemanticTokensCapability))] public partial record SemanticTokensRangeParams : IWorkDoneProgressParams, ITextDocumentIdentifierParams, - IPartialItemRequest, IDoesNotParticipateInRegistration + IPartialItemRequest, IDoesNotParticipateInRegistration { /// /// The text document. @@ -94,6 +94,7 @@ public partial record SemanticTokensRangeParams : IWorkDoneProgressParams, IText /// public Range Range { get; init; } } + [Parallel] [Method(WorkspaceNames.SemanticTokensRefresh, Direction.ServerToClient)] [GenerateHandler("OmniSharp.Extensions.LanguageServer.Protocol.Workspace"), GenerateHandlerMethods, @@ -102,6 +103,7 @@ public partial record SemanticTokensRangeParams : IWorkDoneProgressParams, IText public partial record SemanticTokensRefreshParams : IRequest { } + public interface ISemanticTokenResult { /// @@ -165,6 +167,10 @@ public partial record SemanticTokensPartialResult /// https://github.com/microsoft/vscode-extension-samples/blob/5ae1f7787122812dcc84e37427ca90af5ee09f14/semantic-tokens-sample/vscode.proposed.d.ts#L71 /// public ImmutableArray Data { get; init; } + + public SemanticTokensPartialResult() {} + + internal SemanticTokensPartialResult(SemanticTokens? result) => Data = result?.Data ?? ImmutableArray.Empty; } @@ -209,6 +215,10 @@ public record SemanticTokensDeltaPartialResult /// https://github.com/microsoft/vscode-extension-samples/blob/5ae1f7787122812dcc84e37427ca90af5ee09f14/semantic-tokens-sample/vscode.proposed.d.ts#L71 /// public Container Edits { get; init; } + public SemanticTokensDeltaPartialResult() {} + + internal SemanticTokensDeltaPartialResult(SemanticTokensDelta? result) => Edits = result?.Edits ?? new Container(); + } /// @@ -243,6 +253,7 @@ public record SemanticTokensEdit [Optional] public ImmutableArray? Data { get; init; } = ImmutableArray.Empty; } + [JsonConverter(typeof(SemanticTokensFullOrDeltaConverter))] public record SemanticTokensFullOrDelta { @@ -280,7 +291,7 @@ public SemanticTokensFullOrDelta(SemanticTokensFullOrDeltaPartialResult partialR public SemanticTokens? Full { get; init; } public bool IsDelta => Delta != null; - public SemanticTokensDelta? Delta { get;init; } + public SemanticTokensDelta? Delta { get; init; } [return: NotNullIfNotNull("semanticTokensDelta")] public static SemanticTokensFullOrDelta? From(SemanticTokensDelta? semanticTokensDelta) => semanticTokensDelta switch { @@ -319,6 +330,7 @@ public SemanticTokensFullOrDelta(SemanticTokensFullOrDeltaPartialResult partialR _ => null }; } + [JsonConverter(typeof(SemanticTokensFullOrDeltaPartialResultConverter))] public record SemanticTokensFullOrDeltaPartialResult { @@ -338,6 +350,19 @@ SemanticTokensDeltaPartialResult delta Delta = delta; } + public SemanticTokensFullOrDeltaPartialResult(SemanticTokensFullOrDelta delta) + { + if (delta.IsFull) + { + Full = new SemanticTokensPartialResult(delta.Full); + } + + if (delta.IsDelta) + { + Delta = new SemanticTokensDeltaPartialResult(delta.Delta); + } + } + public bool IsDelta => Delta != null; public SemanticTokensDeltaPartialResult? Delta { get; } @@ -535,6 +560,7 @@ public readonly partial struct SemanticTokenType public static SemanticTokenType Event { get; } = new SemanticTokenType("event"); public static SemanticTokenType EnumMember { get; } = new SemanticTokenType("enumMember"); } + [RegistrationName(TextDocumentNames.SemanticTokensRegistration)] [GenerateRegistrationOptions(nameof(ServerCapabilities.SemanticTokensProvider))] [RegistrationOptionsConverter(typeof(SemanticTokensRegistrationOptionsConverter))] @@ -731,6 +757,7 @@ public abstract class SemanticTokensHandlerBase : protected abstract Task Tokenize(SemanticTokensBuilder builder, ITextDocumentIdentifierParams identifier, CancellationToken cancellationToken); protected abstract Task GetSemanticTokensDocument(ITextDocumentIdentifierParams @params, CancellationToken cancellationToken); } + public static partial class SemanticTokensExtensions { private static SemanticTokensRegistrationOptions RegistrationOptionsFactory(SemanticTokensCapability capability, ClientCapabilities clientCapabilities) @@ -739,7 +766,7 @@ private static SemanticTokensRegistrationOptions RegistrationOptionsFactory(Sema Full = new SemanticTokensCapabilityRequestFull() }; registrationOptions.Range ??= new SemanticTokensCapabilityRequestRange(); - if (registrationOptions is { Full: { IsValue: true, Value: {} } }) + if (registrationOptions is { Full: { IsValue: true, Value: { } } }) { registrationOptions.Full.Value.Delta = true; } @@ -827,7 +854,9 @@ protected override Task Tokenize(SemanticTokensBuilder builder, ITextDocumentIde protected override Task GetSemanticTokensDocument(ITextDocumentIdentifierParams @params, CancellationToken cancellationToken) => _getSemanticTokensDocument(@params, Capability, cancellationToken); - protected internal override SemanticTokensRegistrationOptions CreateRegistrationOptions(SemanticTokensCapability capability, ClientCapabilities clientCapabilities) => + protected internal override SemanticTokensRegistrationOptions CreateRegistrationOptions( + SemanticTokensCapability capability, ClientCapabilities clientCapabilities + ) => _registrationOptionsFactory(capability, clientCapabilities); } @@ -836,10 +865,13 @@ protected internal override SemanticTokensRegistrationOptions CreateRegistration SemanticTokensParams @params, CancellationToken cancellationToken = default ) => mediator.ProgressManager.MonitorUntil( - @params, (partial, result) => new SemanticTokens { + @params, + (partial, result) => new SemanticTokens { Data = partial.Data, ResultId = result?.ResultId - }, cancellationToken + }, + tokens => new SemanticTokensPartialResult(tokens)!, + cancellationToken ); public static IRequestProgressObservable RequestSemanticTokensDelta( @@ -876,10 +908,13 @@ protected internal override SemanticTokensRegistrationOptions CreateRegistration SemanticTokensRangeParams @params, CancellationToken cancellationToken = default ) => mediator.ProgressManager.MonitorUntil( - @params, (partial, result) => new SemanticTokens { + @params, + (partial, result) => new SemanticTokens { Data = partial.Data, ResultId = result?.ResultId - }, cancellationToken + }, + tokens => new SemanticTokensPartialResult(tokens)!, + cancellationToken ); } } diff --git a/src/Protocol/Features/ProgressFeature.cs b/src/Protocol/Features/ProgressFeature.cs index 9807c1d15..37e3038c0 100644 --- a/src/Protocol/Features/ProgressFeature.cs +++ b/src/Protocol/Features/ProgressFeature.cs @@ -19,7 +19,7 @@ namespace OmniSharp.Extensions.LanguageServer.Protocol { namespace Models { - [Parallel] + [Serial] [Method(GeneralNames.Progress, Direction.Bidirectional)] [GenerateHandler("OmniSharp.Extensions.LanguageServer.Protocol"), GenerateHandlerMethods, GenerateRequestMethods(typeof(IGeneralLanguageClient), typeof(ILanguageClient), typeof(IGeneralLanguageServer), typeof(ILanguageServer))] @@ -103,7 +103,10 @@ public string? String public static partial class ProgressExtensions { public static IRequestProgressObservable RequestProgress( - this ILanguageProtocolProxy requestRouter, IPartialItemRequest @params, Func factory, CancellationToken cancellationToken = default + this ILanguageProtocolProxy requestRouter, + IPartialItemRequest @params, + Func factory, + CancellationToken cancellationToken = default ) { @params.SetPartialResultToken(new ProgressToken(Guid.NewGuid().ToString())); diff --git a/src/Protocol/Features/Window/WorkDoneProgressFeature.cs b/src/Protocol/Features/Window/WorkDoneProgressFeature.cs index 9e51d0ce4..d53fc071c 100644 --- a/src/Protocol/Features/Window/WorkDoneProgressFeature.cs +++ b/src/Protocol/Features/Window/WorkDoneProgressFeature.cs @@ -11,7 +11,7 @@ namespace OmniSharp.Extensions.LanguageServer.Protocol { namespace Models { - [Parallel] + [Serial] [Method(WindowNames.WorkDoneProgressCreate, Direction.ServerToClient)] [GenerateHandler("OmniSharp.Extensions.LanguageServer.Protocol.Window"), GenerateHandlerMethods, GenerateRequestMethods(typeof(IWindowLanguageServer), typeof(ILanguageServer))] @@ -23,7 +23,7 @@ public partial record WorkDoneProgressCreateParams : IRequest public ProgressToken? Token { get; init; } } - [Parallel] + [Serial] [Method(WindowNames.WorkDoneProgressCancel, Direction.ClientToServer)] [GenerateHandler("OmniSharp.Extensions.LanguageServer.Protocol.Window"), GenerateHandlerMethods, GenerateRequestMethods(typeof(IWindowLanguageClient), typeof(ILanguageClient))] diff --git a/src/Protocol/LanguageProtocolDelegatingHandlers.cs b/src/Protocol/LanguageProtocolDelegatingHandlers.cs index 13251bc88..1b6386b75 100644 --- a/src/Protocol/LanguageProtocolDelegatingHandlers.cs +++ b/src/Protocol/LanguageProtocolDelegatingHandlers.cs @@ -29,14 +29,19 @@ public sealed class Request private readonly Guid _id; Guid ICanBeIdentifiedHandler.Id => _id; - public Request(Guid id, Func> handler, RegistrationOptionsDelegate registrationOptionsFactory) + public Request( + Guid id, Func> handler, + RegistrationOptionsDelegate registrationOptionsFactory + ) { _id = id; _handler = handler; _registrationOptionsFactory = registrationOptionsFactory; } - public Request(Func> handler, RegistrationOptionsDelegate registrationOptionsFactory) : + public Request( + Func> handler, RegistrationOptionsDelegate registrationOptionsFactory + ) : this(Guid.Empty, handler, registrationOptionsFactory) { } @@ -44,7 +49,8 @@ public Request(Func> hand Task IRequestHandler.Handle(TParams request, CancellationToken cancellationToken) => _handler(request, Capability, cancellationToken); - protected internal override TRegistrationOptions CreateRegistrationOptions(TCapability capability, ClientCapabilities clientCapabilities) => _registrationOptionsFactory(capability, clientCapabilities); + protected internal override TRegistrationOptions CreateRegistrationOptions(TCapability capability, ClientCapabilities clientCapabilities) => + _registrationOptionsFactory(capability, clientCapabilities); } public sealed class CanBeResolved : @@ -56,11 +62,14 @@ public sealed class CanBeResolved : where TCapability : ICapability { private readonly Func> _resolveHandler; - private readonly RegistrationOptionsDelegate _registrationOptionsFactory; + private readonly RegistrationOptionsDelegate _registrationOptionsFactory; private readonly Guid _id; Guid ICanBeIdentifiedHandler.Id => _id; - public CanBeResolved(Guid id, Func> resolveHandler, RegistrationOptionsDelegate registrationOptionsFactory) + public CanBeResolved( + Guid id, Func> resolveHandler, + RegistrationOptionsDelegate registrationOptionsFactory + ) { _resolveHandler = resolveHandler; _registrationOptionsFactory = registrationOptionsFactory; @@ -69,7 +78,8 @@ public CanBeResolved(Guid id, Func IRequestHandler.Handle(TItem request, CancellationToken cancellationToken) => _resolveHandler(request, Capability, cancellationToken); - protected internal override TRegistrationOptions CreateRegistrationOptions(TCapability capability, ClientCapabilities clientCapabilities) => _registrationOptionsFactory(capability, clientCapabilities); + protected internal override TRegistrationOptions CreateRegistrationOptions(TCapability capability, ClientCapabilities clientCapabilities) => + _registrationOptionsFactory(capability, clientCapabilities); } public sealed class CanBeResolved : @@ -109,15 +119,17 @@ public sealed class Request : private readonly Guid _id; Guid ICanBeIdentifiedHandler.Id => _id; - public Request(Guid id, Func handler, RegistrationOptionsDelegate registrationOptionsFactory) + public Request( + Guid id, Func handler, RegistrationOptionsDelegate registrationOptionsFactory + ) { _id = id; _handler = handler; _registrationOptionsFactory = registrationOptionsFactory; } - public Request(Func handler, RegistrationOptionsDelegate registrationOptionsFactory): - this(Guid.Empty, handler, registrationOptionsFactory) + public Request(Func handler, RegistrationOptionsDelegate registrationOptionsFactory) : + this(Guid.Empty, handler, registrationOptionsFactory) { } @@ -128,7 +140,8 @@ async Task IRequestHandler.Handle(TParams request, Cancella return Unit.Value; } - protected internal override TRegistrationOptions CreateRegistrationOptions(TCapability capability, ClientCapabilities clientCapabilities) => _registrationOptionsFactory(capability, clientCapabilities); + protected internal override TRegistrationOptions CreateRegistrationOptions(TCapability capability, ClientCapabilities clientCapabilities) => + _registrationOptionsFactory(capability, clientCapabilities); } public sealed class RequestRegistration : @@ -148,7 +161,9 @@ public RequestRegistration(Func> handl { } - public RequestRegistration(Guid id, Func> handler, RegistrationOptionsDelegate registrationOptionsFactory) + public RequestRegistration( + Guid id, Func> handler, RegistrationOptionsDelegate registrationOptionsFactory + ) { _id = id; _handler = handler; @@ -180,7 +195,7 @@ public RequestRegistration(Guid id, Func handl _registrationOptionsFactory = registrationOptionsFactory; } - public RequestRegistration(Func handler, RegistrationOptionsDelegate registrationOptionsFactory): + public RequestRegistration(Func handler, RegistrationOptionsDelegate registrationOptionsFactory) : this(Guid.Empty, handler, registrationOptionsFactory) { } @@ -270,7 +285,8 @@ public sealed class PartialResult _id; public PartialResult( - Guid id, Action, TCapability, CancellationToken> handler, RegistrationOptionsDelegate registrationOptionsFactory, IProgressManager progressManager, + Guid id, Action, TCapability, CancellationToken> handler, + RegistrationOptionsDelegate registrationOptionsFactory, IProgressManager progressManager, Func factory ) { @@ -282,9 +298,10 @@ public PartialResult( } public PartialResult( - Action, TCapability, CancellationToken> handler, RegistrationOptionsDelegate registrationOptionsFactory, IProgressManager progressManager, + Action, TCapability, CancellationToken> handler, + RegistrationOptionsDelegate registrationOptionsFactory, IProgressManager progressManager, Func factory - ): + ) : this(Guid.Empty, handler, registrationOptionsFactory, progressManager, factory) { } @@ -303,13 +320,18 @@ CancellationToken cancellationToken } var subject = new AsyncSubject(); + var task = subject + .Select(_factory) + .ToTask(cancellationToken, _progressManager.Scheduler) + .ConfigureAwait(false); // in the event nothing is emitted... subject.OnNext(default!); _handler(request, subject, Capability, cancellationToken); - return await subject.Select(_factory).ToTask(cancellationToken).ConfigureAwait(false); + return await task; } - protected internal override TRegistrationOptions CreateRegistrationOptions(TCapability capability, ClientCapabilities clientCapabilities) => _registrationOptionsFactory(capability, clientCapabilities); + protected internal override TRegistrationOptions CreateRegistrationOptions(TCapability capability, ClientCapabilities clientCapabilities) => + _registrationOptionsFactory(capability, clientCapabilities); } public sealed class PartialResult : @@ -329,14 +351,17 @@ public sealed class PartialResult _id; public PartialResult( - Action, CancellationToken> handler, RegistrationOptionsDelegate registrationOptionsFactory, IProgressManager progressManager, + Action, CancellationToken> handler, RegistrationOptionsDelegate registrationOptionsFactory, + IProgressManager progressManager, Func factory ) : this(Guid.Empty, handler, registrationOptionsFactory, progressManager, factory) { } + public PartialResult( - Guid id, Action, CancellationToken> handler, RegistrationOptionsDelegate registrationOptionsFactory, IProgressManager progressManager, + Guid id, Action, CancellationToken> handler, RegistrationOptionsDelegate registrationOptionsFactory, + IProgressManager progressManager, Func factory ) { @@ -358,10 +383,14 @@ public PartialResult( } var subject = new AsyncSubject(); + var task = subject + .Select(_factory) + .ToTask(cancellationToken, _progressManager.Scheduler) + .ConfigureAwait(false); // in the event nothing is emitted... subject.OnNext(default!); _handler(request, subject, cancellationToken); - return await subject.Select(_factory).ToTask(cancellationToken).ConfigureAwait(false); + return await task; } protected override TRegistrationOptions CreateRegistrationOptions(ClientCapabilities clientCapabilities) => _registrationOptionsFactory(clientCapabilities); @@ -410,10 +439,14 @@ public PartialResultCapability( } var subject = new AsyncSubject(); + var task = subject + .Select(_factory) + .ToTask(cancellationToken, _progressManager.Scheduler) + .ConfigureAwait(false); // in the event nothing is emitted... subject.OnNext(default!); _handler(request, Capability, subject, cancellationToken); - return await subject.Select(_factory).ToTask(cancellationToken).ConfigureAwait(false); + return await task; } } @@ -454,10 +487,14 @@ public PartialResult(Guid id, Action, CancellationToke } var subject = new AsyncSubject(); + var task = subject + .Select(_factory) + .ToTask(cancellationToken, _progressManager.Scheduler) + .ConfigureAwait(false); // in the event nothing is emitted... subject.OnNext(default!); _handler(request, subject, cancellationToken); - return await subject.Select(_factory).ToTask(cancellationToken).ConfigureAwait(false); + return await task; } } @@ -479,7 +516,8 @@ public sealed class PartialResults _id; public PartialResults( - Action>, TCapability, CancellationToken> handler, RegistrationOptionsDelegate registrationOptionsFactory, IProgressManager progressManager, + Action>, TCapability, CancellationToken> handler, + RegistrationOptionsDelegate registrationOptionsFactory, IProgressManager progressManager, Func, TResponse?> factory ) : this(Guid.Empty, handler, registrationOptionsFactory, progressManager, factory) @@ -487,7 +525,8 @@ public PartialResults( } public PartialResults( - Guid id, Action>, TCapability, CancellationToken> handler, RegistrationOptionsDelegate registrationOptionsFactory, + Guid id, Action>, TCapability, CancellationToken> handler, + RegistrationOptionsDelegate registrationOptionsFactory, IProgressManager progressManager, Func, TResponse?> factory ) { @@ -509,19 +548,22 @@ public PartialResults( } var subject = new Subject>(); - var task = subject.Aggregate( - new List(), (acc, items) => { - acc.AddRange(items); - return acc; - } - ) - .ToTask(cancellationToken); + var task = subject + .Aggregate( + new List(), (acc, items) => { + acc.AddRange(items); + return acc; + } + ) + .Select(_factory) + .ToTask(cancellationToken, _progressManager.Scheduler) + .ConfigureAwait(false); _handler(request, subject, Capability, cancellationToken); - var result = _factory(await task.ConfigureAwait(false)); - return result; + return await task; } - protected internal override TRegistrationOptions CreateRegistrationOptions(TCapability capability, ClientCapabilities clientCapabilities) => _registrationOptionsFactory(capability, clientCapabilities); + protected internal override TRegistrationOptions CreateRegistrationOptions(TCapability capability, ClientCapabilities clientCapabilities) => + _registrationOptionsFactory(capability, clientCapabilities); } public sealed class PartialResults : @@ -541,7 +583,8 @@ public sealed class PartialResults _id; public PartialResults( - Action>, CancellationToken> handler, RegistrationOptionsDelegate registrationOptionsFactory, IProgressManager progressManager, + Action>, CancellationToken> handler, RegistrationOptionsDelegate registrationOptionsFactory, + IProgressManager progressManager, Func, TResponse?> factory ) : this(Guid.Empty, handler, registrationOptionsFactory, progressManager, factory) @@ -549,7 +592,8 @@ public PartialResults( } public PartialResults( - Guid id, Action>, CancellationToken> handler, RegistrationOptionsDelegate registrationOptionsFactory, IProgressManager progressManager, + Guid id, Action>, CancellationToken> handler, RegistrationOptionsDelegate registrationOptionsFactory, + IProgressManager progressManager, Func, TResponse?> factory ) { @@ -571,16 +615,18 @@ public PartialResults( } var subject = new Subject>(); - var task = subject.Aggregate( - new List(), (acc, items) => { - acc.AddRange(items); - return acc; - } - ) - .ToTask(cancellationToken); + var task = subject + .Aggregate( + new List(), (acc, items) => { + acc.AddRange(items); + return acc; + } + ) + .Select(_factory) + .ToTask(cancellationToken, _progressManager.Scheduler) + .ConfigureAwait(false); _handler(request, subject, cancellationToken); - var result = _factory(await task.ConfigureAwait(false)); - return result; + return await task; } protected override TRegistrationOptions CreateRegistrationOptions(ClientCapabilities clientCapabilities) => _registrationOptionsFactory(clientCapabilities); @@ -631,16 +677,18 @@ public PartialResultsCapability( } var subject = new Subject>(); - var task = subject.Aggregate( - new List(), (acc, items) => { - acc.AddRange(items); - return acc; - } - ) - .ToTask(cancellationToken); + var task = subject + .Aggregate( + new List(), (acc, items) => { + acc.AddRange(items); + return acc; + } + ) + .Select(_factory) + .ToTask(cancellationToken, _progressManager.Scheduler) + .ConfigureAwait(false); _handler(request, subject, Capability, cancellationToken); - var result = _factory(await task.ConfigureAwait(false)); - return result; + return await task; } } @@ -688,16 +736,18 @@ public PartialResults( } var subject = new Subject>(); - var task = subject.Aggregate( - new List(), (acc, items) => { - acc.AddRange(items); - return acc; - } - ) - .ToTask(cancellationToken); + var task = subject + .Aggregate( + new List(), (acc, items) => { + acc.AddRange(items); + return acc; + } + ) + .Select(_factory) + .ToTask(cancellationToken, _progressManager.Scheduler) + .ConfigureAwait(false); _handler(request, subject, cancellationToken); - var result = _factory(await task.ConfigureAwait(false)); - return result; + return await task; } } @@ -714,14 +764,18 @@ public sealed class Notification : private readonly Guid _id; Guid ICanBeIdentifiedHandler.Id => _id; - public Notification(Guid id, Func handler, RegistrationOptionsDelegate registrationOptionsFactory) + public Notification( + Guid id, Func handler, RegistrationOptionsDelegate registrationOptionsFactory + ) { _id = id; _handler = handler; _registrationOptionsFactory = registrationOptionsFactory; } - public Notification(Func handler, RegistrationOptionsDelegate registrationOptionsFactory) : + public Notification( + Func handler, RegistrationOptionsDelegate registrationOptionsFactory + ) : this(Guid.Empty, handler, registrationOptionsFactory) { } @@ -732,7 +786,8 @@ async Task IRequestHandler.Handle(TParams request, Cancella return Unit.Value; } - protected internal override TRegistrationOptions CreateRegistrationOptions(TCapability capability, ClientCapabilities clientCapabilities) => _registrationOptionsFactory(capability, clientCapabilities); + protected internal override TRegistrationOptions CreateRegistrationOptions(TCapability capability, ClientCapabilities clientCapabilities) => + _registrationOptionsFactory(capability, clientCapabilities); } public sealed class Notification : @@ -807,6 +862,7 @@ public TypedPartialObserver(IObserver> results, Func fact _results = results; _factory = factory; } + public void OnCompleted() => _results.OnCompleted(); public void OnError(Exception error) => _results.OnError(error); diff --git a/src/Protocol/LanguageProtocolEventingHelper.cs b/src/Protocol/LanguageProtocolEventingHelper.cs index 0a3898eb3..812984951 100644 --- a/src/Protocol/LanguageProtocolEventingHelper.cs +++ b/src/Protocol/LanguageProtocolEventingHelper.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reactive.Concurrency; using System.Reactive.Linq; using System.Reactive.Threading.Tasks; using System.Threading; @@ -16,6 +17,7 @@ public static Task Run( IEnumerable handlers, Func executeHandler, int? concurrency, + IScheduler scheduler, CancellationToken cancellationToken ) { @@ -28,13 +30,13 @@ CancellationToken cancellationToken { return events.Merge(concurrency.Value) .LastOrDefaultAsync() - .ToTask(cancellationToken); + .ToTask(cancellationToken, scheduler); } return events .Merge() .LastOrDefaultAsync() - .ToTask(cancellationToken); + .ToTask(cancellationToken, scheduler); } } } diff --git a/src/Protocol/Progress/IProgressManager.cs b/src/Protocol/Progress/IProgressManager.cs index df6a0df52..f46219cc3 100644 --- a/src/Protocol/Progress/IProgressManager.cs +++ b/src/Protocol/Progress/IProgressManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Reactive.Concurrency; using System.Threading; using Newtonsoft.Json.Linq; using OmniSharp.Extensions.LanguageServer.Protocol.Models; @@ -14,9 +15,25 @@ public interface IProgressManager : IProgressHandler IRequestProgressObservable MonitorUntil( IPartialItemRequest request, Func factory, + Func reverseFactory, CancellationToken cancellationToken ); + IRequestProgressObservable MonitorUntil( + IPartialItemRequest request, + Func factory, + Func reverseFactory, + CancellationToken cancellationToken + ); + + [Obsolete("Method may not work correctly when subscribing to the result, use overload with the reverse factory parameter")] + IRequestProgressObservable MonitorUntil( + IPartialItemRequest request, + Func factory, + CancellationToken cancellationToken + ); + + [Obsolete("Method may not work correctly when subscribing to the result, use overload with the reverse factory parameter")] IRequestProgressObservable MonitorUntil( IPartialItemRequest request, Func factory, @@ -39,5 +56,6 @@ CancellationToken cancellationToken IProgressObserver> For(IPartialItemsRequest request, CancellationToken cancellationToken) where TResponse : IEnumerable?; + IScheduler Scheduler { get; } } } diff --git a/src/Protocol/Progress/RequestProgressObservable.cs b/src/Protocol/Progress/PartialItemRequestProgressObservable.cs similarity index 53% rename from src/Protocol/Progress/RequestProgressObservable.cs rename to src/Protocol/Progress/PartialItemRequestProgressObservable.cs index 2295753d0..439e672b3 100644 --- a/src/Protocol/Progress/RequestProgressObservable.cs +++ b/src/Protocol/Progress/PartialItemRequestProgressObservable.cs @@ -1,4 +1,6 @@ using System; +using System.Reactive; +using System.Reactive.Concurrency; using System.Reactive.Disposables; using System.Reactive.Linq; using System.Reactive.Subjects; @@ -6,52 +8,56 @@ using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; using OmniSharp.Extensions.JsonRpc; using OmniSharp.Extensions.LanguageServer.Protocol.Models; namespace OmniSharp.Extensions.LanguageServer.Protocol.Progress { - internal class RequestProgressObservable : IRequestProgressObservable, IObserver + internal class PartialItemRequestProgressObservable : IRequestProgressObservable, IObserver { private readonly ISerializer _serializer; private readonly ReplaySubject _dataSubject; private readonly CompositeDisposable _disposable; private readonly Task _task; + private bool _receivedPartialData; - public RequestProgressObservable( + public PartialItemRequestProgressObservable( ISerializer serializer, ProgressToken token, IObservable requestResult, Func factory, + Func reverseFactory, CancellationToken cancellationToken, - Action disposal + Action onCompleteAction ) { _serializer = serializer; - _dataSubject = new ReplaySubject(1); - var request = requestResult - .Do(_ => { }, OnError, OnCompleted) - .Replay(1); - _disposable = new CompositeDisposable { - request.Connect(), - Disposable.Create(disposal) - }; - - _task = _dataSubject - .ForkJoin(requestResult, factory) - .ToTask(cancellationToken); -#pragma warning disable VSTHRD105 -#pragma warning disable VSTHRD110 - _task.ContinueWith(_ => Dispose()); -#pragma warning restore VSTHRD110 -#pragma warning restore VSTHRD105 + _dataSubject = new ReplaySubject(1, Scheduler.Immediate); + _disposable = new CompositeDisposable() { _dataSubject }; + _task = Observable.Create( + observer => new CompositeDisposable() { + _dataSubject + .ForkJoin( + requestResult + .Do( + _ => { + if (_receivedPartialData) return; + _dataSubject.OnNext(reverseFactory(_)); + }, + _dataSubject.OnError, + _dataSubject.OnCompleted + ), + factory + ) + .Subscribe(observer), + Disposable.Create(onCompleteAction) + } + ) + .ToTask(cancellationToken); ProgressToken = token; - if (_dataSubject is IDisposable disposable) - { - _disposable.Add(disposable); - } } public ProgressToken ProgressToken { get; } @@ -75,6 +81,7 @@ private void OnError(Exception error) public void OnNext(JToken value) { if (_dataSubject.IsDisposed) return; + _receivedPartialData = true; _dataSubject.OnNext(value.ToObject(_serializer.JsonSerializer)); } @@ -84,7 +91,7 @@ public void Dispose() _disposable.Dispose(); } - public IDisposable Subscribe(IObserver observer) => _disposable.IsDisposed ? Disposable.Empty : _dataSubject.Subscribe(observer); + public IDisposable Subscribe(IObserver observer) => _dataSubject.Subscribe(observer); #pragma warning disable VSTHRD003 public Task AsTask() => _task; diff --git a/src/Protocol/Progress/PartialItemsRequestProgressObservable.cs b/src/Protocol/Progress/PartialItemsRequestProgressObservable.cs index be654e680..79a4fa040 100644 --- a/src/Protocol/Progress/PartialItemsRequestProgressObservable.cs +++ b/src/Protocol/Progress/PartialItemsRequestProgressObservable.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reactive; +using System.Reactive.Concurrency; using System.Reactive.Disposables; using System.Reactive.Linq; using System.Reactive.Subjects; @@ -8,6 +10,7 @@ using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; using OmniSharp.Extensions.JsonRpc; using OmniSharp.Extensions.LanguageServer.Protocol.Models; @@ -21,6 +24,7 @@ internal class PartialItemsRequestProgressObservable : IRequestP private readonly ReplaySubject> _dataSubject; private readonly CompositeDisposable _disposable; private readonly Task _task; + private bool _receivedPartialData; public PartialItemsRequestProgressObservable( ISerializer serializer, @@ -28,48 +32,39 @@ public PartialItemsRequestProgressObservable( IObservable requestResult, Func, TResult> factory, CancellationToken cancellationToken, - Action disposal + Action onCompleteAction ) { _serializer = serializer; - _dataSubject = new ReplaySubject>(int.MaxValue); - var request = requestResult - .Do( - // this should be fine as long as the other side is spec compliant (requests cannot return new results) so this should be null or empty - result => _dataSubject.OnNext(result ?? Enumerable.Empty()), - OnError, - OnCompleted - ) - .Replay(1); - _disposable = new CompositeDisposable { - request.Connect(), - Disposable.Create(disposal) - }; - - _task = _dataSubject - .Scan( - new List(), - (acc, data) => { - acc.AddRange(data); - return acc; - } - ) - .StartWith(new List()) - .Select(factory) - .ForkJoin(request, (items, result) => items?.Count() > result?.Count() ? items : result) - .ToTask(cancellationToken); - -#pragma warning disable VSTHRD105 -#pragma warning disable VSTHRD110 - _task.ContinueWith(_ => Dispose()); -#pragma warning restore VSTHRD110 -#pragma warning restore VSTHRD105 + _dataSubject = new ReplaySubject>(int.MaxValue, Scheduler.Immediate); + _disposable = new CompositeDisposable() { _dataSubject }; + _task = Observable.Create( + observer => new CompositeDisposable() { + _dataSubject + .Aggregate( + new List(), + (acc, data) => { + acc.AddRange(data); + return acc; + } + ) + .Select(factory) + .ForkJoin( + requestResult + .Do( + result => _dataSubject.OnNext(result ?? Enumerable.Empty()), + _dataSubject.OnError, + _dataSubject.OnCompleted + ), + (items, result) => items?.Count() > result?.Count() ? items : result + ) + .Subscribe(observer), + Disposable.Create(onCompleteAction) + } + ) + .ToTask(cancellationToken); ProgressToken = token; - if (_dataSubject is IDisposable disposable) - { - _disposable.Add(disposable); - } } public ProgressToken ProgressToken { get; } @@ -93,6 +88,7 @@ private void OnError(Exception error) public void OnNext(JToken value) { if (_dataSubject.IsDisposed) return; + _receivedPartialData = true; _dataSubject.OnNext(value.ToObject(_serializer.JsonSerializer)); } @@ -102,7 +98,8 @@ public void Dispose() _disposable.Dispose(); } - public IDisposable Subscribe(IObserver> observer) => _disposable.IsDisposed ? Disposable.Empty : _dataSubject.Subscribe(observer); +// public IDisposable Subscribe(IObserver> observer) => _disposable.IsDisposed ? Disposable.Empty : _dataSubject.Subscribe(observer); + public IDisposable Subscribe(IObserver> observer) => _dataSubject.Subscribe(observer); #pragma warning disable VSTHRD003 public Task AsTask() => _task; @@ -118,14 +115,15 @@ public PartialItemsRequestProgressObservable( IObservable?> requestResult, Func, Container?> factory, CancellationToken cancellationToken, - Action disposal + Action onCompleteAction, + ILogger logger ) : base( serializer, token, requestResult, factory, cancellationToken, - disposal + onCompleteAction ) { } diff --git a/src/Protocol/Progress/ProgressManager.cs b/src/Protocol/Progress/ProgressManager.cs index c4885c4e2..c57fb182a 100644 --- a/src/Protocol/Progress/ProgressManager.cs +++ b/src/Protocol/Progress/ProgressManager.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Reactive.Concurrency; using System.Reactive.Linq; using System.Threading; using System.Threading.Tasks; using MediatR; +using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; using OmniSharp.Extensions.JsonRpc; using OmniSharp.Extensions.LanguageServer.Protocol.Models; @@ -16,6 +18,8 @@ internal class ProgressManager : IProgressManager { private readonly IResponseRouter _router; private readonly ISerializer _serializer; + private readonly IScheduler _scheduler; + private readonly Lazy> _logger; private readonly ConcurrentDictionary _activeObservers = new ConcurrentDictionary(EqualityComparer.Default); @@ -23,12 +27,16 @@ internal class ProgressManager : IProgressManager private readonly ConcurrentDictionary _activeObservables = new ConcurrentDictionary(EqualityComparer.Default); - public ProgressManager(IResponseRouter router, ISerializer serializer) + public ProgressManager(IResponseRouter router, ISerializer serializer, IScheduler scheduler, Lazy> logger) { _router = router; _serializer = serializer; + _scheduler = scheduler; + _logger = logger; } + IScheduler IProgressManager.Scheduler => _scheduler; + Task IRequestHandler.Handle(ProgressParams request, CancellationToken cancellationToken) { if (_activeObservables.TryGetValue(request.Token, out var observable) && observable is IObserver observer) @@ -55,7 +63,8 @@ public IProgressObservable Monitor(ProgressToken token, Func fa } public IRequestProgressObservable MonitorUntil( - IPartialItemRequest request, Func factory, + IPartialItemRequest request, + Func factory, CancellationToken cancellationToken ) { @@ -66,19 +75,22 @@ CancellationToken cancellationToken return observable; } - observable = new RequestProgressObservable( + observable = new PartialItemRequestProgressObservable( _serializer, request.PartialResultToken!, MakeRequest(request), (x, _) => factory(x), - cancellationToken, () => _activeObservables.TryRemove(request.PartialResultToken!, out _) + _ => default!, + cancellationToken, + () => _activeObservables.TryRemove(request.PartialResultToken!, out _) ); _activeObservables.TryAdd(request.PartialResultToken!, observable); return observable; } public IRequestProgressObservable MonitorUntil( - IPartialItemRequest request, Func factory, + IPartialItemRequest request, + Func factory, CancellationToken cancellationToken ) { @@ -88,9 +100,67 @@ CancellationToken cancellationToken return observable; } - observable = new RequestProgressObservable( - _serializer, request.PartialResultToken, MakeRequest(request), factory, - cancellationToken, () => _activeObservables.TryRemove(request.PartialResultToken, out _) + observable = new PartialItemRequestProgressObservable( + _serializer, + request.PartialResultToken, + MakeRequest(request), + factory, + _ => default!, + cancellationToken, + () => _activeObservables.TryRemove(request.PartialResultToken, out _) + ); + _activeObservables.TryAdd(request.PartialResultToken, observable); + return observable; + } + + public IRequestProgressObservable MonitorUntil( + IPartialItemRequest request, + Func factory, + Func reverseFactory, + CancellationToken cancellationToken + ) + { + request.SetPartialResultToken(); + if (_activeObservables.TryGetValue(request.PartialResultToken!, out var o) + && o is IRequestProgressObservable observable) + { + return observable; + } + + observable = new PartialItemRequestProgressObservable( + _serializer, + request.PartialResultToken!, + MakeRequest(request), + (x, _) => factory(x), + reverseFactory, + cancellationToken, + () => _activeObservables.TryRemove(request.PartialResultToken!, out _) + ); + _activeObservables.TryAdd(request.PartialResultToken!, observable); + return observable; + } + + public IRequestProgressObservable MonitorUntil( + IPartialItemRequest request, + Func factory, + Func reverseFactory, + CancellationToken cancellationToken + ) + { + request.SetPartialResultToken(); + if (_activeObservables.TryGetValue(request.PartialResultToken!, out var o) && o is IRequestProgressObservable observable) + { + return observable; + } + + observable = new PartialItemRequestProgressObservable( + _serializer, + request.PartialResultToken, + MakeRequest(request), + factory, + reverseFactory, + cancellationToken, + () => _activeObservables.TryRemove(request.PartialResultToken, out _) ); _activeObservables.TryAdd(request.PartialResultToken, observable); return observable; @@ -151,8 +221,13 @@ public IRequestProgressObservable MonitorUntil(IPartialItemsReques } observable = new PartialItemsRequestProgressObservable( - _serializer, request.PartialResultToken, MakeRequest(request), x => new Container(x), cancellationToken, - () => _activeObservables.TryRemove(request.PartialResultToken, out _) + _serializer, + request.PartialResultToken, + MakeRequest(request), + x => new Container(x), + cancellationToken, + () => _activeObservables.TryRemove(request.PartialResultToken, out _), + _logger.Value ); _activeObservables.TryAdd(request.PartialResultToken, observable); return observable; @@ -209,6 +284,7 @@ private IObservable MakeRequest(IRequest reques try { observer.OnNext(await _router.SendRequest(request, ct).ConfigureAwait(false)); + await Task.Yield(); observer.OnCompleted(); } catch (OperationCanceledException e) diff --git a/src/Protocol/Progress/ProgressObservable.cs b/src/Protocol/Progress/ProgressObservable.cs index 7cf81d7b3..645df8a34 100644 --- a/src/Protocol/Progress/ProgressObservable.cs +++ b/src/Protocol/Progress/ProgressObservable.cs @@ -1,4 +1,5 @@ using System; +using System.Reactive.Concurrency; using System.Reactive.Disposables; using System.Reactive.Linq; using System.Reactive.Subjects; @@ -16,7 +17,7 @@ internal class ProgressObservable : IProgressObservable, IObserver public ProgressObservable(ProgressToken token, Func factory, Action disposal) { _factory = factory; - _dataSubject = new ReplaySubject(1); + _dataSubject = new ReplaySubject(1, Scheduler.Immediate); _disposable = new CompositeDisposable { Disposable.Create(_dataSubject.OnCompleted), Disposable.Create(disposal) }; ProgressToken = token; diff --git a/src/Protocol/Protocol.csproj b/src/Protocol/Protocol.csproj index 90fd9171f..bedebe4d9 100644 --- a/src/Protocol/Protocol.csproj +++ b/src/Protocol/Protocol.csproj @@ -8,14 +8,14 @@ - - - + + + - - + + diff --git a/src/Server/Configuration/DidChangeConfigurationProvider.cs b/src/Server/Configuration/DidChangeConfigurationProvider.cs index 988f00d0f..143954809 100644 --- a/src/Server/Configuration/DidChangeConfigurationProvider.cs +++ b/src/Server/Configuration/DidChangeConfigurationProvider.cs @@ -2,6 +2,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Reactive.Concurrency; using System.Reactive.Disposables; using System.Reactive.Linq; using System.Reactive.Subjects; @@ -28,6 +29,7 @@ internal class DidChangeConfigurationProvider : ConfigurationProvider, IDidChang private readonly ILogger _logger; private readonly IWorkspaceLanguageServer _workspaceLanguageServer; private readonly ConfigurationConverter _configurationConverter; + private readonly IScheduler _scheduler; private DidChangeConfigurationCapability? _capability; private readonly ConfigurationRoot _configuration; private readonly CompositeDisposable _compositeDisposable = new CompositeDisposable(); @@ -40,12 +42,14 @@ public DidChangeConfigurationProvider( Action configurationBuilderAction, ILogger logger, IWorkspaceLanguageServer workspaceLanguageServer, - ConfigurationConverter configurationConverter + ConfigurationConverter configurationConverter, + IScheduler scheduler ) { _logger = logger; _workspaceLanguageServer = workspaceLanguageServer; _configurationConverter = configurationConverter; + _scheduler = scheduler; var builder = new ConfigurationBuilder() .Add(new DidChangeConfigurationSource(this)); configurationBuilderAction(builder); @@ -81,7 +85,7 @@ public Task Handle(DidChangeConfigurationParams request, CancellationToken Task IOnLanguageServerStarted.OnStarted(ILanguageServer server, CancellationToken cancellationToken) => GetWorkspaceConfigurationAsync(cancellationToken); - private Task GetWorkspaceConfigurationAsync(CancellationToken cancellationToken) => GetWorkspaceConfiguration().LastOrDefaultAsync().ToTask(cancellationToken); + private Task GetWorkspaceConfigurationAsync(CancellationToken cancellationToken) => GetWorkspaceConfiguration().LastOrDefaultAsync().ToTask(cancellationToken, _scheduler); private IObservable GetWorkspaceConfiguration() { @@ -225,7 +229,7 @@ public async Task GetScopedConfiguration(DocumentUri scope var data = await GetConfigurationFromClient(scopes.Select(z => new ConfigurationItem { Section = z.Section, ScopeUri = scopeUri })) .Select(z => ( z.scope.Section ?? string.Empty, z.settings )) .ToArray() - .ToTask(cancellationToken) + .ToTask(cancellationToken, _scheduler) .ConfigureAwait(false); var config = new ScopedConfiguration( diff --git a/src/Server/LanguageServer.Shutdown.cs b/src/Server/LanguageServer.Shutdown.cs index 2c8aab3fd..9f260c60d 100644 --- a/src/Server/LanguageServer.Shutdown.cs +++ b/src/Server/LanguageServer.Shutdown.cs @@ -18,8 +18,8 @@ public partial class LanguageServer : IExitHandler, IShutdownHandler public IObservable Shutdown => _shutdownSubject.AsObservable(); public IObservable Exit => _exitSubject.AsObservable(); - public Task WasShutDown => _shutdownSubject.ToTask(); - public Task WaitForExit => _exitSubject.ToTask(); + public Task WasShutDown => _shutdownSubject.ToTask(_scheduler); + public Task WaitForExit => _exitSubject.ToTask(_scheduler); #pragma warning disable VSTHRD100 public async void ForcefulShutdown() diff --git a/src/Server/LanguageServer.cs b/src/Server/LanguageServer.cs index 123958bdf..6838a50c0 100644 --- a/src/Server/LanguageServer.cs +++ b/src/Server/LanguageServer.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reactive.Concurrency; using System.Reactive.Disposables; using System.Reactive.Linq; using System.Reactive.Subjects; @@ -50,6 +51,7 @@ public partial class LanguageServer : JsonRpcServerBase, ILanguageServer, ILangu private readonly IEnumerable _registrationOptionsConverters; private readonly InstanceHasStarted _instanceHasStarted; private readonly LanguageServerLoggingManager _languageServerLoggingManager; + private readonly IScheduler _scheduler; private readonly IEnumerable _startedDelegates; private readonly IEnumerable _startedHandlers; private readonly ISubject _initializeComplete = new AsyncSubject(); @@ -149,7 +151,8 @@ internal LanguageServer( IEnumerable initializedHandlers, IEnumerable registrationOptionsConverters, InstanceHasStarted instanceHasStarted, - LanguageServerLoggingManager languageServerLoggingManager + LanguageServerLoggingManager languageServerLoggingManager, + IScheduler scheduler ) : base(handlerCollection, responseRouter) { Configuration = configuration; @@ -182,6 +185,7 @@ LanguageServerLoggingManager languageServerLoggingManager _registrationOptionsConverters = registrationOptionsConverters; _instanceHasStarted = instanceHasStarted; _languageServerLoggingManager = languageServerLoggingManager; + _scheduler = scheduler; _concurrency = options.Value.Concurrency; _capabilityTypes = options.Value.UseAssemblyAttributeScanning @@ -242,7 +246,7 @@ public async Task Initialize(CancellationToken token) _connection.Open(); try { - _initializingTask = _initializeComplete.ToTask(token); + _initializingTask = _initializeComplete.ToTask(token, _scheduler); await _initializingTask.ConfigureAwait(false); await LanguageProtocolEventingHelper.Run( _startedDelegates, @@ -250,6 +254,7 @@ await LanguageProtocolEventingHelper.Run( _startedHandlers.Union(_collection.Select(z => z.Handler).OfType()), (handler, ct) => handler.OnStarted(this, ct), _concurrency, + _scheduler, token ).ConfigureAwait(false); @@ -281,6 +286,7 @@ await LanguageProtocolEventingHelper.Run( _initializeHandlers.Union(_collection.Select(z => z.Handler).OfType()), (handler, ct) => handler.OnInitialize(this, ClientSettings, ct), _concurrency, + _scheduler, token ).ConfigureAwait(false); @@ -302,14 +308,20 @@ await LanguageProtocolEventingHelper.Run( _initializedHandlers.Union(_collection.Select(z => z.Handler).OfType()), (handler, ct) => handler.OnInitialized(this, ClientSettings, result, ct), _concurrency, + _scheduler, token ).ConfigureAwait(false); + // Allow the server receiver to start processing incoming notifications and requests. It + // is necessary to do this now, and not in the Initialized handler, because otherwise + // clients can enter a race with the receiver and have their notifications and requests + // erroneously dropped. + _serverReceiver.Initialized(); + // TODO: if (_clientVersion == ClientVersion.Lsp2) { - _serverReceiver.Initialized(); _initializeComplete.OnNext(result); _initializeComplete.OnCompleted(); } @@ -321,7 +333,6 @@ public Task Handle(InitializedParams @params, CancellationToken token) { if (_clientVersion == ClientVersion.Lsp3) { - _serverReceiver.Initialized(); _initializeComplete.OnNext(ServerSettings); _initializeComplete.OnCompleted(); } @@ -493,7 +504,7 @@ private InitializeResult ReadServerCapabilities( public IObservable Start => _initializeComplete.AsObservable(); - public Task WasStarted => _initializeComplete.ToTask(); + public Task WasStarted => _initializeComplete.ToTask(_scheduler); public void Dispose() { diff --git a/src/Server/LanguageServerHelpers.cs b/src/Server/LanguageServerHelpers.cs index 77af39937..88797f1ae 100644 --- a/src/Server/LanguageServerHelpers.cs +++ b/src/Server/LanguageServerHelpers.cs @@ -127,30 +127,27 @@ IReadOnlyList descriptors ); } - return registrations.Distinct(new Registration.TextDocumentComparer()).ToArray(); - } - ) - .Where(z => z.Any()) - .SelectMany( - registrations => Observable.FromAsync(ct => client.RegisterCapability(new RegistrationParams { Registrations = registrations.ToArray() }, ct)), - (a, _) => a - ) - .Aggregate(Array.Empty(), (_, z) => z) - .Subscribe( - registrations => { + var distinctRegistrations = registrations.Distinct(new Registration.TextDocumentComparer()).ToArray(); disposable.Add( Disposable.Create( () => { client.UnregisterCapability( new UnregistrationParams { - Unregisterations = registrations.ToArray() + Unregisterations = distinctRegistrations } ).ToObservable().Subscribe(); } ) ); + return distinctRegistrations; } - ); + ) + .Where(z => z.Any()) + .SelectMany( + registrations => Observable.FromAsync(ct => client.RegisterCapability(new RegistrationParams { Registrations = registrations.ToArray() }, ct)), + (a, _) => a + ) + .Subscribe(); disposable.Add(result); return disposable; } diff --git a/src/Server/LanguageServerOptionsExtensions.cs b/src/Server/LanguageServerOptionsExtensions.cs index 6715949e6..91c0097fa 100644 --- a/src/Server/LanguageServerOptionsExtensions.cs +++ b/src/Server/LanguageServerOptionsExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Reactive.Concurrency; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -35,6 +36,54 @@ public static LanguageServerOptions WithServerInfo(this LanguageServerOptions op return options; } + /// + /// Sets both input and output schedulers to the same scheduler + /// + /// + /// + /// + public static LanguageServerOptions WithScheduler(this LanguageServerOptions options, IScheduler inputScheduler) + { + options.InputScheduler = options.OutputScheduler = options.DefaultScheduler = inputScheduler; + return options; + } + + /// + /// Sets the scheduler used during reading input + /// + /// + /// + /// + public static LanguageServerOptions WithInputScheduler(this LanguageServerOptions options, IScheduler inputScheduler) + { + options.InputScheduler = inputScheduler; + return options; + } + + /// + /// Sets the default scheduler to be used when scheduling other tasks + /// + /// + /// + /// + public static LanguageServerOptions WithDefaultScheduler(this LanguageServerOptions options, IScheduler defaultScheduler) + { + options.DefaultScheduler = defaultScheduler; + return options; + } + + /// + /// Sets the scheduler use during writing output + /// + /// + /// + /// + public static LanguageServerOptions WithOutputScheduler(this LanguageServerOptions options, IScheduler outputScheduler) + { + options.OutputScheduler = outputScheduler; + return options; + } + public static LanguageServerOptions OnInitialize(this LanguageServerOptions options, OnLanguageServerInitializeDelegate @delegate) { options.Services.AddSingleton(@delegate); diff --git a/src/Server/LanguageServerWorkspaceFolderManager.cs b/src/Server/LanguageServerWorkspaceFolderManager.cs index 951e4fde7..f56dce122 100644 --- a/src/Server/LanguageServerWorkspaceFolderManager.cs +++ b/src/Server/LanguageServerWorkspaceFolderManager.cs @@ -2,6 +2,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Reactive.Concurrency; using System.Reactive.Linq; using System.Reactive.Subjects; using System.Threading; @@ -27,7 +28,7 @@ public LanguageServerWorkspaceFolderManager(IWorkspaceLanguageServer server) { _server = server; _workspaceFolders = new ConcurrentDictionary(DocumentUri.Comparer); - _workspaceFoldersSubject = new ReplaySubject>(1); + _workspaceFoldersSubject = new ReplaySubject>(1, Scheduler.Immediate); _workspaceFoldersChangedSubject = new Subject(); } diff --git a/src/Shared/LspRequestRouter.cs b/src/Shared/LspRequestRouter.cs index ad5d1dbe6..2d979c00a 100644 --- a/src/Shared/LspRequestRouter.cs +++ b/src/Shared/LspRequestRouter.cs @@ -23,9 +23,10 @@ public LspRequestRouter( ILogger logger, IEnumerable handlerMatchers, ISerializer serializer, - IServiceScopeFactory serviceScopeFactory + IServiceScopeFactory serviceScopeFactory, + IActivityTracingStrategy? activityTracingStrategy = null ) : - base(serializer, serviceScopeFactory, logger) + base(serializer, serviceScopeFactory, logger, activityTracingStrategy) { _collection = collection; _handlerMatchers = new HashSet(handlerMatchers); diff --git a/src/Testing/LanguageProtocolTestBase.cs b/src/Testing/LanguageProtocolTestBase.cs index d44f6761c..f8d844c60 100644 --- a/src/Testing/LanguageProtocolTestBase.cs +++ b/src/Testing/LanguageProtocolTestBase.cs @@ -49,6 +49,9 @@ Action serverOptionsAction .WithAssemblies(TestOptions.Assemblies) .ConfigureLogging(x => x.SetMinimumLevel(LogLevel.Trace)) .WithAssemblies(typeof(LanguageProtocolTestBase).Assembly, GetType().Assembly) + .WithInputScheduler(options.InputScheduler) + .WithOutputScheduler(options.OutputScheduler) + .WithDefaultScheduler(options.DefaultScheduler) .Services .AddTransient(typeof(IPipelineBehavior<,>), typeof(SettlePipeline<,>)) .AddSingleton(ClientEvents as IRequestSettler); @@ -64,6 +67,9 @@ Action serverOptionsAction .WithAssemblies(TestOptions.Assemblies) .ConfigureLogging(x => x.SetMinimumLevel(LogLevel.Trace)) .WithAssemblies(typeof(LanguageProtocolTestBase).Assembly, GetType().Assembly) + .WithInputScheduler(options.InputScheduler) + .WithOutputScheduler(options.OutputScheduler) + .WithDefaultScheduler(options.DefaultScheduler) .Services .AddTransient(typeof(IPipelineBehavior<,>), typeof(SettlePipeline<,>)) .AddSingleton(ServerEvents as IRequestSettler); diff --git a/src/Testing/LanguageServerTestBase.cs b/src/Testing/LanguageServerTestBase.cs index 61868007c..0343191e8 100644 --- a/src/Testing/LanguageServerTestBase.cs +++ b/src/Testing/LanguageServerTestBase.cs @@ -36,6 +36,9 @@ protected virtual ILanguageClient CreateClient(Action? cl .WithAssemblies(TestOptions.Assemblies) .WithAssemblies(typeof(LanguageProtocolTestBase).Assembly, GetType().Assembly) .ConfigureLogging(x => x.SetMinimumLevel(LogLevel.Trace)) + .WithInputScheduler(options.InputScheduler) + .WithOutputScheduler(options.OutputScheduler) + .WithDefaultScheduler(options.DefaultScheduler) .Services .AddTransient(typeof(IPipelineBehavior<,>), typeof(SettlePipeline<,>)) .AddSingleton(Events as IRequestSettler); diff --git a/test/Client.Tests/ClientTests.cs b/test/Client.Tests/ClientTests.cs index c4d583028..e435f3bb9 100644 --- a/test/Client.Tests/ClientTests.cs +++ b/test/Client.Tests/ClientTests.cs @@ -38,7 +38,7 @@ public ClientTests(ITestOutputHelper testOutput) /// /// Ensure that the language client can successfully request Hover information. /// - [FactWithSkipOn(SkipOnPlatform.Windows, DisplayName = "Language client can successfully request hover info")] + [Fact(DisplayName = "Language client can successfully request hover info")] public async Task Hover_Success() { const int line = 5; @@ -111,7 +111,7 @@ public async Task Hover_Success() /// /// Ensure that the language client can successfully request Completions. /// - [FactWithSkipOn(SkipOnPlatform.Windows, DisplayName = "Language client can successfully request completions")] + [Fact(DisplayName = "Language client can successfully request completions")] public async Task Completions_Success() { const int line = 5; @@ -215,7 +215,7 @@ public async Task Completions_Success() /// /// Ensure that the language client can successfully request SignatureHelp. /// - [FactWithSkipOn(SkipOnPlatform.Windows, DisplayName = "Language client can successfully request signature help")] + [Fact(DisplayName = "Language client can successfully request signature help")] public async Task SignatureHelp_Success() { const int line = 5; @@ -308,7 +308,7 @@ public async Task SignatureHelp_Success() /// /// Ensure that the language client can successfully request Definition. /// - [FactWithSkipOn(SkipOnPlatform.Windows, DisplayName = "Language client can successfully request definition")] + [Fact(DisplayName = "Language client can successfully request definition")] public async Task Definition_Success() { const int line = 5; @@ -384,7 +384,7 @@ public async Task Definition_Success() /// /// Ensure that the language client can successfully request DocumentHighlight. /// - [FactWithSkipOn(SkipOnPlatform.Windows, DisplayName = "Language client can successfully request document highlights")] + [Fact(DisplayName = "Language client can successfully request document highlights")] public async Task DocumentHighlights_Success() { const int line = 5; @@ -449,7 +449,7 @@ public async Task DocumentHighlights_Success() /// /// Ensure that the language client can successfully request DocumentHighlight. /// - [FactWithSkipOn(SkipOnPlatform.Windows, DisplayName = "Language client can successfully request document symbols")] + [Fact(DisplayName = "Language client can successfully request document symbols")] public async Task DocumentSymbols_DocumentSymbol_Success() { const int line = 5; @@ -534,7 +534,7 @@ public async Task DocumentSymbols_DocumentSymbol_Success() /// /// Ensure that the language client can successfully request FoldingRanges. /// - [FactWithSkipOn(SkipOnPlatform.Windows, DisplayName = "Language client can successfully request document folding ranges")] + [Fact(DisplayName = "Language client can successfully request document folding ranges")] public async Task FoldingRanges_Success() { var expectedDocumentPath = AbsoluteDocumentPath; @@ -594,7 +594,7 @@ public async Task FoldingRanges_Success() /// /// Ensure that the language client can successfully receive Diagnostics from the server. /// - [FactWithSkipOn(SkipOnPlatform.Windows, DisplayName = "Language client can successfully receive diagnostics")] + [Fact(DisplayName = "Language client can successfully receive diagnostics")] public async Task Diagnostics_Success() { var documentPath = AbsoluteDocumentPath; diff --git a/test/Dap.Tests/Integration/ConnectionAndDisconnectionTests.cs b/test/Dap.Tests/Integration/ConnectionAndDisconnectionTests.cs index e616f653c..6e58eba5f 100644 --- a/test/Dap.Tests/Integration/ConnectionAndDisconnectionTests.cs +++ b/test/Dap.Tests/Integration/ConnectionAndDisconnectionTests.cs @@ -22,7 +22,7 @@ public ConnectionAndDisconnectionTests(ITestOutputHelper outputHelper) : base( { } - [RetryFact] + [Fact] public async Task Server_Should_Stay_Alive_When_Requests_Throw_An_Exception() { var (client, _) = await Initialize(ConfigureClient, ConfigureServer); @@ -37,7 +37,7 @@ public async Task Server_Should_Stay_Alive_When_Requests_Throw_An_Exception() result.Should().BeTrue(); } - [RetryFact] + [Fact] public async Task Client_Should_Stay_Alive_When_Requests_Throw_An_Exception() { var (_, server) = await Initialize(ConfigureClient, ConfigureServer); @@ -52,7 +52,7 @@ public async Task Client_Should_Stay_Alive_When_Requests_Throw_An_Exception() result.Should().BeTrue(); } - [RetryFact] + [Fact] public async Task Server_Should_Support_Links() { var (client, _) = await Initialize(ConfigureClient, ConfigureServer); @@ -67,7 +67,7 @@ public async Task Server_Should_Support_Links() result.Should().BeTrue(); } - [RetryFact] + [Fact] public async Task Client_Should_Support_Links() { var (_, server) = await Initialize(ConfigureClient, ConfigureServer); diff --git a/test/Dap.Tests/Integration/CustomRequestsTests.cs b/test/Dap.Tests/Integration/CustomRequestsTests.cs index 5415e2b65..0d254ee7e 100644 --- a/test/Dap.Tests/Integration/CustomRequestsTests.cs +++ b/test/Dap.Tests/Integration/CustomRequestsTests.cs @@ -20,7 +20,7 @@ public CustomRequestsTests(ITestOutputHelper outputHelper) : base(new JsonRpcTes { } - [RetryFact] + [Fact] public async Task Should_Support_Custom_Attach_Request_Using_Base_Class() { var fake = Substitute.For>(); @@ -42,7 +42,7 @@ await client.Attach( request.ProcessId.Should().Be("4321"); } - [RetryFact] + [Fact] public async Task Should_Support_Custom_Attach_Request_Receiving_Regular_Request_Using_Base_Class() { var fake = Substitute.For(); @@ -64,7 +64,7 @@ await client.Attach( request.ExtensionData.Should().ContainKey("processId").And.Subject["processId"].Should().Be("4321"); } - [RetryFact] + [Fact] public async Task Should_Support_Custom_Attach_Request_Using_Extension_Data_Using_Base_Class() { var fake = Substitute.For>(); @@ -88,7 +88,7 @@ await client.Attach( request.ProcessId.Should().Be("4321"); } - [RetryFact] + [Fact] public async Task Should_Support_Custom_Launch_Request_Using_Base_Class() { var fake = Substitute.For>(); @@ -106,7 +106,7 @@ await client.Launch( request.Script.Should().Be("build.ps1"); } - [RetryFact] + [Fact] public async Task Should_Support_Custom_Launch_Request_Receiving_Regular_Request_Using_Base_Class() { var fake = Substitute.For(); @@ -124,7 +124,7 @@ await client.Launch( request.ExtensionData.Should().ContainKey("script").And.Subject["script"].Should().Be("build.ps1"); } - [RetryFact] + [Fact] public async Task Should_Support_Custom_Launch_Request_Using_Extension_Data_Base_Class() { var fake = Substitute.For>(); @@ -144,7 +144,7 @@ await client.Launch( request.Script.Should().Be("build.ps1"); } - [RetryFact] + [Fact] public async Task Should_Support_Custom_Attach_Request_Using_Delegate() { var fake = Substitute.For>>(); @@ -166,7 +166,7 @@ await client.Attach( request.ProcessId.Should().Be("4321"); } - [RetryFact] + [Fact] public async Task Should_Support_Custom_Attach_Request_Receiving_Regular_Request_Using_Delegate() { var fake = Substitute.For>>(); @@ -188,7 +188,7 @@ await client.Attach( request.ExtensionData.Should().ContainKey("processId").And.Subject["processId"].Should().Be("4321"); } - [RetryFact] + [Fact] public async Task Should_Support_Custom_Attach_Request_Using_Extension_Data_Using_Delegate() { var fake = Substitute.For>>(); @@ -212,7 +212,7 @@ await client.Attach( request.ProcessId.Should().Be("4321"); } - [RetryFact] + [Fact] public async Task Should_Support_Custom_Launch_Request_Using_Delegate() { var fake = Substitute.For>>(); @@ -230,7 +230,7 @@ await client.Launch( request.Script.Should().Be("build.ps1"); } - [RetryFact] + [Fact] public async Task Should_Support_Custom_Launch_Request_Receiving_Regular_Request_Using_Delegate() { var fake = Substitute.For>>(); @@ -248,7 +248,7 @@ await client.Launch( request.ExtensionData.Should().ContainKey("script").And.Subject["script"].Should().Be("build.ps1"); } - [RetryFact] + [Fact] public async Task Should_Support_Custom_Launch_Request_Using_Extension_Data_Using_Delegate() { var fake = Substitute.For>>(); diff --git a/test/Dap.Tests/Integration/GenericDapServerTests.cs b/test/Dap.Tests/Integration/GenericDapServerTests.cs index 53f8d3471..da531ceff 100644 --- a/test/Dap.Tests/Integration/GenericDapServerTests.cs +++ b/test/Dap.Tests/Integration/GenericDapServerTests.cs @@ -17,7 +17,7 @@ public GenericDapServerTests(ITestOutputHelper outputHelper) : base(new JsonRpcT { } - [RetryFact] + [Fact] public async Task Supports_Multiple_Handlers_On_A_Single_Class() { var handler = new Handler(); diff --git a/test/Dap.Tests/Integration/HandlersManagerIntegrationTests.cs b/test/Dap.Tests/Integration/HandlersManagerIntegrationTests.cs index 017ea2943..035394490 100644 --- a/test/Dap.Tests/Integration/HandlersManagerIntegrationTests.cs +++ b/test/Dap.Tests/Integration/HandlersManagerIntegrationTests.cs @@ -19,7 +19,7 @@ public HandlersManagerIntegrationTests(ITestOutputHelper testOutputHelper) : bas { } - [RetryFact] + [Fact] public async Task Should_Return_Default_Handlers() { var (_, server) = await Initialize(options => {}, options => {}); @@ -29,7 +29,7 @@ public async Task Should_Return_Default_Handlers() handlersManager.GetHandlers().Should().HaveCount(2); } - [RetryFact] + [Fact] public async Task Link_Should_Fail_If_No_Handler_Is_Defined() { var (_, server) = await Initialize(options => {}, options => {}); @@ -40,7 +40,7 @@ public async Task Link_Should_Fail_If_No_Handler_Is_Defined() a.Should().Throw().Which.Message.Should().Contain("Descriptors must be registered before links can be created"); } - [RetryFact] + [Fact] public async Task Link_Should_Fail_If_Link_Is_On_The_Wrong_Side() { var (_, server) = await Initialize(options => {}, options => {}); diff --git a/test/Dap.Tests/Integration/ProgressTests.cs b/test/Dap.Tests/Integration/ProgressTests.cs index 0267bb1f1..1a7b5d885 100644 --- a/test/Dap.Tests/Integration/ProgressTests.cs +++ b/test/Dap.Tests/Integration/ProgressTests.cs @@ -24,7 +24,7 @@ public ProgressTests(ITestOutputHelper outputHelper) : base( { } - [FactWithSkipOn(SkipOnPlatform.All)] + [Fact] public async Task Should_Support_Progress_From_Sever_To_Client() { var (client, server) = await Initialize(ConfigureClient, ConfigureServer); @@ -87,7 +87,7 @@ public async Task Should_Support_Progress_From_Sever_To_Client() results.Should().ContainInOrder("Begin", "Report 1", "Report 2", "Report 3", "Report 4", "End"); } - [FactWithSkipOn(SkipOnPlatform.All)] + [Fact] public async Task Should_Support_Cancelling_Progress_From_Server_To_Client_Request() { var (client, server) = await Initialize(ConfigureClient, ConfigureServer); diff --git a/test/Dap.Tests/Integration/RequestCancellationTests.cs b/test/Dap.Tests/Integration/RequestCancellationTests.cs index 1060a4f35..715f763b7 100644 --- a/test/Dap.Tests/Integration/RequestCancellationTests.cs +++ b/test/Dap.Tests/Integration/RequestCancellationTests.cs @@ -21,7 +21,7 @@ public RequestCancellationTests(ITestOutputHelper outputHelper) : base(new JsonR { } - [RetryFact] + [Fact] public async Task Should_Cancel_Pending_Requests() { var (client, _) = await Initialize(ConfigureClient, ConfigureServer); diff --git a/test/Directory.Build.targets b/test/Directory.Build.targets index f84bb6488..2e1c2229c 100644 --- a/test/Directory.Build.targets +++ b/test/Directory.Build.targets @@ -1,33 +1,36 @@ - - - - - - - - - - - - - - - - - - - - <_Parameter1>Client.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f - - - <_Parameter1>JsonRpc.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f - - - <_Parameter1>Dap.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f - - - <_Parameter1>Lsp.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f - - - - + + + + + + + + + + + + + + + + + + + + <_Parameter1>Client.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f + + + <_Parameter1>JsonRpc.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f + + + <_Parameter1>Dap.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f + + + <_Parameter1>Lsp.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f + + + <_Parameter1>Lsp.Integration.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f + + + + diff --git a/test/Generation.Tests/AutoImplementParamsGeneratorTests.cs b/test/Generation.Tests/AutoImplementParamsGeneratorTests.cs index a512d39a5..67abc23a2 100644 --- a/test/Generation.Tests/AutoImplementParamsGeneratorTests.cs +++ b/test/Generation.Tests/AutoImplementParamsGeneratorTests.cs @@ -2,12 +2,13 @@ using OmniSharp.Extensions.JsonRpc.Generators; using OmniSharp.Extensions.JsonRpc.Generators.Cache; using TestingUtils; +using Xunit; namespace Generation.Tests { public class AutoImplementParamsGeneratorTests { - [FactWithSkipOn(SkipOnPlatform.Windows)] + [Fact] public async Task Auto_Magically_Implements_Properties() { var source = @" diff --git a/test/Generation.Tests/EnumLikeStringGeneratorTests.cs b/test/Generation.Tests/EnumLikeStringGeneratorTests.cs index a55a74410..e151a270b 100644 --- a/test/Generation.Tests/EnumLikeStringGeneratorTests.cs +++ b/test/Generation.Tests/EnumLikeStringGeneratorTests.cs @@ -2,12 +2,13 @@ using OmniSharp.Extensions.JsonRpc.Generators; using OmniSharp.Extensions.JsonRpc.Generators.Cache; using TestingUtils; +using Xunit; namespace Generation.Tests { public class EnumLikeStringGeneratorTests { - [FactWithSkipOn(SkipOnPlatform.Windows)] + [Fact] public async Task Auto_Magically_Implements_IEnumLikeString() { var source = @" diff --git a/test/Generation.Tests/GeneratedRegistrationOptionsTests.cs b/test/Generation.Tests/GeneratedRegistrationOptionsTests.cs index 2c1b00ede..1ff3de714 100644 --- a/test/Generation.Tests/GeneratedRegistrationOptionsTests.cs +++ b/test/Generation.Tests/GeneratedRegistrationOptionsTests.cs @@ -1,12 +1,13 @@ using System.Threading.Tasks; using OmniSharp.Extensions.JsonRpc.Generators; using TestingUtils; +using Xunit; namespace Generation.Tests { public class GeneratedRegistrationOptionsTests { - [FactWithSkipOn(SkipOnPlatform.Windows)] + [Fact] public async Task Supports_Generating_Strongly_Typed_WorkDone_Registration_Options() { var source = @" @@ -67,7 +68,7 @@ public bool WorkDoneProgress #nullable restore"; await GenerationHelpers.AssertGeneratedAsExpected(source, expected); } - [FactWithSkipOn(SkipOnPlatform.Windows)] + [Fact] public async Task Supports_Generating_Strongly_Typed_WorkDone_Registration_Options_Interface() { var source = @" @@ -132,7 +133,7 @@ public bool WorkDoneProgress await GenerationHelpers.AssertGeneratedAsExpected(source, expected); } - [FactWithSkipOn(SkipOnPlatform.Windows)] + [Fact] public async Task Supports_Generating_Strongly_Typed_Registration_Options() { var source = @" @@ -275,7 +276,7 @@ public bool WorkDoneProgress await GenerationHelpers.AssertGeneratedAsExpected(source, expected); } - [FactWithSkipOn(SkipOnPlatform.Windows)] + [Fact] public async Task Supports_Generating_Strongly_Typed_Registration_Options_With_Converters() { var source = @" diff --git a/test/Generation.Tests/JsonRpcGenerationTests.cs b/test/Generation.Tests/JsonRpcGenerationTests.cs index 42ffab279..87b7e43de 100644 --- a/test/Generation.Tests/JsonRpcGenerationTests.cs +++ b/test/Generation.Tests/JsonRpcGenerationTests.cs @@ -12,7 +12,7 @@ namespace Generation.Tests { public class JsonRpcGenerationTests { - [FactWithSkipOn(SkipOnPlatform.Windows)] + [Fact] public async Task Supports_Generating_Notifications_And_Infers_Direction_ExitHandler() { var source = @" @@ -74,7 +74,7 @@ public static partial class ExitExtensions await AssertGeneratedAsExpected(source, expected); } - [FactWithSkipOn(SkipOnPlatform.Windows)] + [Fact] public async Task Supports_Generating_Generic_Response_Types() { var source = @" @@ -182,7 +182,7 @@ public static ILanguageServerRegistry OnExecuteCommand(this ILanguageServerRe await AssertGeneratedAsExpected(source, expected); } - [FactWithSkipOn(SkipOnPlatform.Windows)] + [Fact] public async Task Should_Report_Diagnostic_If_Missing_Information() { var source = @" @@ -212,7 +212,7 @@ public interface IExitHandler : IJsonRpcNotificationHandler a.Should().Throw("cache").WithMessage("*Could not infer the request router(s)*"); } - [FactWithSkipOn(SkipOnPlatform.Windows)] + [Fact] public async Task Supports_Generating_Notifications_And_Infers_Direction_CapabilitiesHandler() { var source = @" @@ -266,7 +266,7 @@ public static partial class CapabilitiesExtensions } - [FactWithSkipOn(SkipOnPlatform.Windows)] + [Fact] public async Task Supports_Generating_Notifications_ExitHandler() { var source = @" @@ -327,7 +327,7 @@ public static partial class ExitExtensions await AssertGeneratedAsExpected(source, expected); } - [FactWithSkipOn(SkipOnPlatform.Windows)] + [Fact] public async Task Supports_Generating_Notifications_And_Infers_Direction_DidChangeTextHandler() { var source = @" @@ -417,7 +417,7 @@ public static ILanguageServerRegistry OnDidChangeTextDocument(this ILanguageServ await AssertGeneratedAsExpected(source, expected); } - [FactWithSkipOn(SkipOnPlatform.Windows)] + [Fact] public async Task Supports_Generating_Notifications_And_Infers_Direction_FoldingRangeHandler() { var source = @" @@ -511,7 +511,7 @@ public static ILanguageServerRegistry ObserveFoldingRange(this ILanguageServerRe await AssertGeneratedAsExpected(source, expected); } - [FactWithSkipOn(SkipOnPlatform.Windows)] + [Fact] public async Task Supports_Generating_Requests_And_Infers_Direction() { var source = @" @@ -600,7 +600,7 @@ public static ILanguageServerRegistry ObserveDefinition(this ILanguageServerRegi } - [FactWithSkipOn(SkipOnPlatform.Windows)] + [Fact] public async Task Supports_Generating_Requests() { var source = @" @@ -688,7 +688,7 @@ public static ILanguageServerRegistry ObserveDefinition(this ILanguageServerRegi await AssertGeneratedAsExpected(source, expected); } - [FactWithSkipOn(SkipOnPlatform.Windows)] + [Fact] public async Task Supports_Custom_Method_Names() { var source = @" @@ -751,7 +751,7 @@ public static partial class LanguageProtocolInitializeExtensions await AssertGeneratedAsExpected(source, expected); } - [FactWithSkipOn(SkipOnPlatform.Windows)] + [Fact] public async Task Supports_Allow_Derived_Requests() { var source = @" @@ -833,7 +833,7 @@ public static IDebugAdapterServerRegistry OnAttachRequest(this IDebugAdapterS await AssertGeneratedAsExpected(source, expected); } - [FactWithSkipOn(SkipOnPlatform.Windows)] + [Fact] public async Task Supports_Allow_Derived_Requests_Nullable() { var source = @" @@ -997,7 +997,7 @@ public static IDebugAdapterServerRegistry OnAttachRequest(this IDebugAdapterS await AssertGeneratedAsExpected(source, expected); } - [FactWithSkipOn(SkipOnPlatform.Windows)] + [Fact] public async Task Supports_Allow_Generic_Types() { var source = @" @@ -1079,7 +1079,7 @@ public static IDebugAdapterServerRegistry OnAttachRequest(this IDebugAdapterS await AssertGeneratedAsExpected(source, expected); } - [FactWithSkipOn(SkipOnPlatform.Windows)] + [Fact] public async Task Supports_Params_Type_As_Source() { var source = @" diff --git a/test/Generation.Tests/LspFeatureTests.cs b/test/Generation.Tests/LspFeatureTests.cs index c12caa23a..ed42f75f3 100644 --- a/test/Generation.Tests/LspFeatureTests.cs +++ b/test/Generation.Tests/LspFeatureTests.cs @@ -7,7 +7,7 @@ namespace Generation.Tests { public class LspFeatureTests { -// [FactWithSkipOn(SkipOnPlatform.Windows, Skip = "for testing")] +// [Fact(Skip = "for testing"] [Fact] public async Task Supports_Params_Type_As_Source() { @@ -105,7 +105,7 @@ public static ILanguageServerRegistry ObserveWorkspaceSymbols(this ILanguageServ await GenerationHelpers.AssertGeneratedAsExpected(source, expected); } - [FactWithSkipOn(SkipOnPlatform.Windows)] + [Fact] public async Task Supports_Generating_Custom_Language_Extensions() { var source = @" @@ -373,7 +373,7 @@ public static ILanguageServerRegistry ObserveDiscoverUnitTests(this ILanguageSer await GenerationHelpers.AssertGeneratedAsExpected(source, expectedHandlers); } - [FactWithSkipOn(SkipOnPlatform.Windows)] + [Fact] public async Task Supports_Generating_Void_Task_Return() { var source = @" diff --git a/test/Generation.Tests/TypedCanBeResolvedTests.cs b/test/Generation.Tests/TypedCanBeResolvedTests.cs index 4e411ea0a..2d7b0b28e 100644 --- a/test/Generation.Tests/TypedCanBeResolvedTests.cs +++ b/test/Generation.Tests/TypedCanBeResolvedTests.cs @@ -1,12 +1,13 @@ using System.Threading.Tasks; using OmniSharp.Extensions.JsonRpc.Generators; using TestingUtils; +using Xunit; namespace Generation.Tests { public class TypedCanBeResolvedTests { - [FactWithSkipOn(SkipOnPlatform.Windows)] + [Fact] public async Task Supports_Generating_Strongly_Typed_ICanBeResolved_Data() { var source = @" @@ -282,7 +283,7 @@ public CodeLensContainer(params CodeLens[] items): base(items) await GenerationHelpers.AssertGeneratedAsExpected(source, expected); } - [FactWithSkipOn(SkipOnPlatform.Windows)] + [Fact] public async Task Supports_Generating_Strongly_Typed_Container() { var source = @" diff --git a/test/JsonRpc.Tests/IntegrationTests.cs b/test/JsonRpc.Tests/IntegrationTests.cs index 748892b56..755efba60 100644 --- a/test/JsonRpc.Tests/IntegrationTests.cs +++ b/test/JsonRpc.Tests/IntegrationTests.cs @@ -30,7 +30,7 @@ private class Data public string Value { get; set; } = null!; } - [RetryFact] + [Fact] public async Task Should_Send_and_receive_requests() { var (client, server) = await Initialize( @@ -45,7 +45,7 @@ public async Task Should_Send_and_receive_requests() clientResponse.Value.Should().Be("myresponse"); } - [RetryFact] + [Fact] public async Task Should_throw_when_sending_requests() { var (client, server) = await Initialize( @@ -60,7 +60,7 @@ public async Task Should_throw_when_sending_requests() serverRequest.Should().Throw(); } - [RetryFact] + [Fact] public async Task Should_throw_when_receiving_requests() { var (client, server) = await Initialize( @@ -75,7 +75,7 @@ public async Task Should_throw_when_receiving_requests() serverRequest.Should().Throw(); } - [RetryFact] + [Fact] public async Task Should_Send_and_receive_notifications() { var clientNotification = new AsyncSubject(); @@ -108,7 +108,7 @@ public async Task Should_Send_and_receive_notifications() clientResponse.Value.Should().Be("esnopserym"); } - [RetryFact] + [Fact] public async Task Should_Send_and_cancel_requests_immediate() { var (client, server) = await Initialize( @@ -144,7 +144,7 @@ public async Task Should_Send_and_cancel_requests_immediate() } } - [RetryFact] + [Fact] public async Task Should_Send_and_cancel_requests_from_otherside() { var (client, server) = await Initialize( @@ -181,7 +181,7 @@ public async Task Should_Send_and_cancel_requests_from_otherside() } } - [RetryFact] + [Fact] public async Task Should_Cancel_Parallel_Requests_When_Options_Are_Given() { var (client, server) = await Initialize( @@ -232,7 +232,7 @@ public async Task Should_Cancel_Parallel_Requests_When_Options_Are_Given() } } - [RetryFact] + [Fact] public async Task Should_Link_Request_A_to_Request_B() { var (client, server) = await Initialize( diff --git a/test/JsonRpc.Tests/RecursiveResolutionTests.cs b/test/JsonRpc.Tests/RecursiveResolutionTests.cs index f925c0003..16fa140f1 100644 --- a/test/JsonRpc.Tests/RecursiveResolutionTests.cs +++ b/test/JsonRpc.Tests/RecursiveResolutionTests.cs @@ -9,6 +9,7 @@ using OmniSharp.Extensions.JsonRpc; using OmniSharp.Extensions.JsonRpc.Testing; using TestingUtils; +using Xunit; using Xunit.Abstractions; namespace JsonRpc.Tests @@ -19,7 +20,7 @@ public RecursiveResolutionTests(ITestOutputHelper testOutputHelper) : base(new J { } - [FactWithSkipOn(SkipOnPlatform.Windows, Skip = "appears to cause a deadlock")] + [Fact(Skip = "appears to cause a deadlock")] public void Server_Can_Be_Injected_Into_Handler_After_Creation_Using_Registration() { Func a = async () => { @@ -36,7 +37,7 @@ public void Server_Can_Be_Injected_Into_Handler_After_Creation_Using_Registratio a.Should().NotThrow(); } - [FactWithSkipOn(SkipOnPlatform.Windows, Skip = "appears to cause a deadlock")] + [Fact(Skip = "appears to cause a deadlock")] public void Server_Cannot_Be_Injected_Into_Handler_During_Creation_Using_Registration() { Func a = () => Initialize( @@ -49,7 +50,7 @@ public void Server_Cannot_Be_Injected_Into_Handler_During_Creation_Using_Registr result.And.ErrorName.Should().Be("UnableToResolveFromRegisteredServices"); } - [FactWithSkipOn(SkipOnPlatform.Windows, Skip = "appears to cause a deadlock")] + [Fact(Skip = "appears to cause a deadlock")] public void Server_Cannot_Be_Injected_Into_Handler_During_Creation_Using_Description() { Func a = () => Initialize( @@ -62,7 +63,7 @@ public void Server_Cannot_Be_Injected_Into_Handler_During_Creation_Using_Descrip result.And.ErrorName.Should().Be("UnableToResolveFromRegisteredServices"); } - [FactWithSkipOn(SkipOnPlatform.Windows, Skip = "appears to cause a deadlock")] + [Fact(Skip = "appears to cause a deadlock")] public void Server_Cannot_Be_Injected_Into_Handler_During_Creation_Using_Injection() { Func a = () => Initialize( @@ -75,7 +76,7 @@ public void Server_Cannot_Be_Injected_Into_Handler_During_Creation_Using_Injecti result.And.ErrorName.Should().Be("UnableToResolveFromRegisteredServices"); } - [FactWithSkipOn(SkipOnPlatform.Windows, Skip = "appears to cause a deadlock")] + [Fact(Skip = "appears to cause a deadlock")] public async Task Server_Facade_Can_Be_Injected_Into_Handler_During_Creation_Using_Registration() { Func a = () => Initialize( @@ -86,7 +87,7 @@ public async Task Server_Facade_Can_Be_Injected_Into_Handler_During_Creation_Usi await a.Should().NotThrowAsync(); } - [FactWithSkipOn(SkipOnPlatform.Windows, Skip = "appears to cause a deadlock")] + [Fact(Skip = "appears to cause a deadlock")] public async Task Server_Facade_Can_Be_Injected_Into_Handler_During_Creation_Using_Description() { Func a = () => Initialize( @@ -97,7 +98,7 @@ public async Task Server_Facade_Can_Be_Injected_Into_Handler_During_Creation_Usi await a.Should().NotThrowAsync(); } - [FactWithSkipOn(SkipOnPlatform.Windows, Skip = "appears to cause a deadlock")] + [Fact(Skip = "appears to cause a deadlock")] public async Task Server_Facade_Can_Injected_Into_Handler_During_Creation_Using_Injection() { Func a = () => Initialize( diff --git a/test/Lsp.Integration.Tests/ActivityTracingTests.cs b/test/Lsp.Integration.Tests/ActivityTracingTests.cs new file mode 100644 index 000000000..6e9547cc9 --- /dev/null +++ b/test/Lsp.Integration.Tests/ActivityTracingTests.cs @@ -0,0 +1,64 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using NSubstitute; +using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.JsonRpc.Testing; +using OmniSharp.Extensions.LanguageProtocol.Testing; +using Serilog.Events; +using Xunit; +using Xunit.Abstractions; + +namespace Lsp.Tests.Integration +{ + public class ActivityTracingTests : LanguageProtocolTestBase + { + public ActivityTracingTests(ITestOutputHelper outputHelper) : base(new JsonRpcTestOptions().ConfigureForXUnit(outputHelper, LogEventLevel.Verbose)) + { + } + + [Fact] + public async Task Should_Have_Activity_Information() + { + var clientStub = Substitute.For(); + clientStub.ApplyInbound(Arg.Any()).Returns(System.Reactive.Disposables.Disposable.Empty); + var serverStub = Substitute.For(); + serverStub.ApplyInbound(Arg.Any()).Returns(System.Reactive.Disposables.Disposable.Empty); + + var (client, server) = await Initialize( + options => options.WithActivityTracingStrategy(clientStub), + options => options.WithActivityTracingStrategy(serverStub) + ); + + serverStub.Received().ApplyInbound(Arg.Any()); + clientStub.Received().ApplyOutgoing(Arg.Any()); + } + + [Fact] + public async Task Should_Have_Activity_Information_Exchanging_Data() + { + var clientStub = Substitute.For(); + clientStub.ApplyInbound(Arg.Any()).Returns(System.Reactive.Disposables.Disposable.Empty); + var serverStub = Substitute.For(); + serverStub.ApplyInbound(Arg.Any()).Returns(System.Reactive.Disposables.Disposable.Empty); + + var (client, server) = await Initialize( + options => options + .WithActivityTracingStrategy(clientStub) + .OnRequest("test", (Func) ( ct => Task.CompletedTask )), + options => options + .WithActivityTracingStrategy(serverStub) + .OnRequest("test", (Func) ( ct => Task.CompletedTask )) + ); + + await client.SendRequest("test").ReturningVoid(CancellationToken); + await server.SendRequest("test").ReturningVoid(CancellationToken); + + serverStub.Received().ApplyInbound(Arg.Any()); + clientStub.Received().ApplyOutgoing(Arg.Any()); + serverStub.Received().ApplyOutgoing(Arg.Any()); + clientStub.Received().ApplyInbound(Arg.Any()); + } + } +} diff --git a/test/Lsp.Tests/Integration/ConnectionAndDisconnectionTests.cs b/test/Lsp.Integration.Tests/ConnectionAndDisconnectionTests.cs similarity index 98% rename from test/Lsp.Tests/Integration/ConnectionAndDisconnectionTests.cs rename to test/Lsp.Integration.Tests/ConnectionAndDisconnectionTests.cs index f67baa0f3..7ccfd30b7 100644 --- a/test/Lsp.Tests/Integration/ConnectionAndDisconnectionTests.cs +++ b/test/Lsp.Integration.Tests/ConnectionAndDisconnectionTests.cs @@ -22,7 +22,7 @@ public ConnectionAndDisconnectionTests(ITestOutputHelper outputHelper) : base( { } - [RetryFact] + [Fact] public async Task Server_Should_Stay_Alive_When_Requests_Throw_An_Exception() { var (client, _) = await Initialize(ConfigureClient, ConfigureServer); @@ -37,7 +37,7 @@ public async Task Server_Should_Stay_Alive_When_Requests_Throw_An_Exception() result.Should().BeTrue(); } - [RetryFact] + [Fact] public async Task Client_Should_Stay_Alive_When_Requests_Throw_An_Exception() { var (_, server) = await Initialize(ConfigureClient, ConfigureServer); @@ -52,7 +52,7 @@ public async Task Client_Should_Stay_Alive_When_Requests_Throw_An_Exception() result.Should().BeTrue(); } - [RetryFact] + [Fact] public async Task Server_Should_Support_Links() { var (client, _) = await Initialize(ConfigureClient, ConfigureServer); @@ -67,7 +67,7 @@ public async Task Server_Should_Support_Links() result.Should().BeTrue(); } - [RetryFact] + [Fact] public async Task Client_Should_Support_Links() { var (_, server) = await Initialize(ConfigureClient, ConfigureServer); diff --git a/test/Lsp.Tests/Integration/CustomRequestsTests.cs b/test/Lsp.Integration.Tests/CustomRequestsTests.cs similarity index 89% rename from test/Lsp.Tests/Integration/CustomRequestsTests.cs rename to test/Lsp.Integration.Tests/CustomRequestsTests.cs index a49ae20f4..042480be5 100644 --- a/test/Lsp.Tests/Integration/CustomRequestsTests.cs +++ b/test/Lsp.Integration.Tests/CustomRequestsTests.cs @@ -10,6 +10,7 @@ using OmniSharp.Extensions.LanguageServer.Protocol.Models; using OmniSharp.Extensions.LanguageServer.Protocol.Window; using TestingUtils; +using Xunit; using Xunit.Abstractions; namespace Lsp.Tests.Integration @@ -20,7 +21,7 @@ public CustomRequestsTests(ITestOutputHelper outputHelper) : base(new JsonRpcTes { } - [RetryFact] + [Fact] public async Task Should_Support_Custom_Telemetry_Using_Base_Class() { var fake = Substitute.For>(); @@ -34,17 +35,17 @@ public async Task Should_Support_Custom_Telemetry_Using_Base_Class() PromptToUpdatePackageManagement = false }; server.SendTelemetryEvent(@event); - await SettleNext(); + await TestHelper.DelayUntil(() => fake.ReceivedCalls().Any(), CancellationToken); var call = fake.ReceivedCalls().Single(); var args = call.GetArguments(); args[0].Should().BeOfType() .And.Subject - .Should().BeEquivalentTo(@event, z=> z.UsingStructuralRecordEquality().Excluding(x => x.ExtensionData)); + .Should().BeEquivalentTo(@event, z => z.UsingStructuralRecordEquality().Excluding(x => x.ExtensionData)); } - [RetryFact] + [Fact] public async Task Should_Support_Custom_Telemetry_Receiving_Regular_Telemetry_Using_Base_Class() { var fake = Substitute.For(); @@ -58,7 +59,7 @@ public async Task Should_Support_Custom_Telemetry_Receiving_Regular_Telemetry_Us PromptToUpdatePackageManagement = false }; server.SendTelemetryEvent(@event); - await SettleNext(); + await TestHelper.DelayUntil(() => fake.ReceivedCalls().Any(), CancellationToken); var call = fake.ReceivedCalls().Single(); var args = call.GetArguments(); @@ -70,11 +71,11 @@ public async Task Should_Support_Custom_Telemetry_Receiving_Regular_Telemetry_Us request.ExtensionData.Should().ContainKey("promptToUpdatePackageManagement").And.Subject["promptToUpdatePackageManagement"].Should().Be(false); } - [RetryFact] + [Fact] public async Task Should_Support_Custom_Telemetry_Using_Extension_Data_Using_Base_Class() { var fake = Substitute.For>(); - var (_, server) = await Initialize(options => { options.AddHandler(fake);}, options => { }); + var (_, server) = await Initialize(options => { options.AddHandler(fake); }, options => { }); server.SendTelemetryEvent( new TelemetryEventParams { @@ -87,7 +88,7 @@ public async Task Should_Support_Custom_Telemetry_Using_Extension_Data_Using_Bas } } ); - await SettleNext(); + await TestHelper.DelayUntil(() => fake.ReceivedCalls().Any(), CancellationToken); var call = fake.ReceivedCalls().Single(); var args = call.GetArguments(); @@ -99,7 +100,7 @@ public async Task Should_Support_Custom_Telemetry_Using_Extension_Data_Using_Bas request.PromptToUpdatePackageManagement.Should().Be(false); } - [RetryFact] + [Fact] public async Task Should_Support_Custom_Telemetry_Using_Delegate() { var fake = Substitute.For>(); @@ -113,17 +114,17 @@ public async Task Should_Support_Custom_Telemetry_Using_Delegate() PromptToUpdatePackageManagement = false }; server.SendTelemetryEvent(@event); - await SettleNext(); + await TestHelper.DelayUntil(() => fake.ReceivedCalls().Any(), CancellationToken); var call = fake.ReceivedCalls().Single(); var args = call.GetArguments(); args[0] .Should().BeOfType() .And.Subject - .Should().BeEquivalentTo(@event, z=> z.UsingStructuralRecordEquality().Excluding(x => x.ExtensionData)); + .Should().BeEquivalentTo(@event, z => z.UsingStructuralRecordEquality().Excluding(x => x.ExtensionData)); } - [RetryFact] + [Fact] public async Task Should_Support_Custom_Telemetry_Receiving_Regular_Telemetry_Using_Delegate() { var fake = Substitute.For>(); @@ -137,7 +138,7 @@ public async Task Should_Support_Custom_Telemetry_Receiving_Regular_Telemetry_Us PromptToUpdatePackageManagement = false }; server.SendTelemetryEvent(@event); - await SettleNext(); + await TestHelper.DelayUntil(() => fake.ReceivedCalls().Any(), CancellationToken); var call = fake.ReceivedCalls().Single(); var args = call.GetArguments(); @@ -149,7 +150,7 @@ public async Task Should_Support_Custom_Telemetry_Receiving_Regular_Telemetry_Us request.ExtensionData.Should().ContainKey("promptToUpdatePackageManagement").And.Subject["promptToUpdatePackageManagement"].Should().Be(false); } - [RetryFact] + [Fact] public async Task Should_Support_Custom_Telemetry_Using_Extension_Data_Using_Delegate() { var fake = Substitute.For>(); @@ -166,7 +167,7 @@ public async Task Should_Support_Custom_Telemetry_Using_Extension_Data_Using_Del } } ); - await SettleNext(); + await TestHelper.DelayUntil(() => fake.ReceivedCalls().Any(), CancellationToken); var call = fake.ReceivedCalls().Single(); var args = call.GetArguments(); diff --git a/test/Lsp.Tests/Integration/DisableDefaultsTests.cs b/test/Lsp.Integration.Tests/DisableDefaultsTests.cs similarity index 98% rename from test/Lsp.Tests/Integration/DisableDefaultsTests.cs rename to test/Lsp.Integration.Tests/DisableDefaultsTests.cs index 3ed3fdd08..87a0998f1 100644 --- a/test/Lsp.Tests/Integration/DisableDefaultsTests.cs +++ b/test/Lsp.Integration.Tests/DisableDefaultsTests.cs @@ -29,7 +29,7 @@ public DisableDefaultsTests(ITestOutputHelper outputHelper) : base(new JsonRpcTe { } - [RetryFact] + [Fact] public async Task Should_Disable_Registration_Manager() { var registrationAction = Substitute.For>(); @@ -48,7 +48,7 @@ public async Task Should_Disable_Registration_Manager() clientManager.Descriptors.Should().ContainSingle(f => f.Method == ClientNames.UnregisterCapability); } - [RetryFact] + [Fact] public async Task Should_Disable_Workspace_Folder_Manager() { var clientAction = Substitute.For?>>>(); @@ -67,7 +67,7 @@ public async Task Should_Disable_Workspace_Folder_Manager() serverManager.Descriptors.Should().ContainSingle(f => f.Method == WorkspaceNames.DidChangeWorkspaceFolders); } - [RetryFact] + [Fact] public async Task Should_Allow_Custom_Workspace_Folder_Manager_Delegate() { var action = Substitute.For>(); @@ -97,7 +97,7 @@ await TestHelper.DelayUntil( ); } - [RetryFact] + [Fact] public async Task Should_Disable_Configuration() { var action = Substitute.For>(); @@ -111,7 +111,7 @@ public async Task Should_Disable_Configuration() serverManager.Descriptors.Should().ContainSingle(f => f.Method == WorkspaceNames.DidChangeConfiguration); } - [RetryFact] + [Fact] public async Task Should_Allow_Custom_Configuration_Delegate() { var action = Substitute.For>(); diff --git a/test/Lsp.Tests/Integration/DynamicRegistrationTests.cs b/test/Lsp.Integration.Tests/DynamicRegistrationTests.cs similarity index 98% rename from test/Lsp.Tests/Integration/DynamicRegistrationTests.cs rename to test/Lsp.Integration.Tests/DynamicRegistrationTests.cs index 60df90996..d5059a3cb 100644 --- a/test/Lsp.Tests/Integration/DynamicRegistrationTests.cs +++ b/test/Lsp.Integration.Tests/DynamicRegistrationTests.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Reactive.Disposables; using System.Reactive.Linq; using System.Threading.Tasks; using FluentAssertions; @@ -27,7 +28,7 @@ public static class DynamicRegistration { public class DynamicRegistrationTests : LanguageProtocolTestBase { - [RetryFact] + [Fact] public async Task Should_Register_Dynamically_After_Initialization() { var (client, _) = await Initialize(new ConfigureClient().Configure, new ConfigureServer().Configure); @@ -49,7 +50,7 @@ await TestHelper.DelayUntil( ); } - [RetryFact] + [Fact] public async Task Should_Register_Dynamically_While_Server_Is_Running() { var (client, server) = await Initialize(new ConfigureClient().Configure, new ConfigureServer().Configure); @@ -98,7 +99,7 @@ await TestHelper.DelayUntil( client.RegistrationManager.CurrentRegistrations.Should().Contain(x => x.Method == "@/" + TextDocumentNames.Completion); } - [RetryFact] + [Fact] public async Task Should_Unregister_Dynamically_While_Server_Is_Running() { var (client, server) = await Initialize(new ConfigureClient().Configure, new ConfigureServer().Configure); @@ -114,19 +115,24 @@ public async Task Should_Unregister_Dynamically_While_Server_Is_Running() } ) ); + + // Sometimes we come through and this fails + disposable.Should().BeOfType().Subject.Count.Should().Be(2); + await TestHelper.DelayUntil( () => client.RegistrationManager.CurrentRegistrations, registrations => registrations.Any(registration => SelectorMatches(registration, x => x.HasLanguage && x.Language == "vb")), CancellationToken ); - disposable.Dispose(); + disposable.Dispose(); await TestHelper.DelayUntil( () => client.RegistrationManager.CurrentRegistrations, registrations => !registrations.Any(registration => SelectorMatches(registration, x => x.HasLanguage && x.Language == "vb")), CancellationToken ); + await Task.Delay(200); client.RegistrationManager.CurrentRegistrations.Should().NotContain( x => @@ -134,7 +140,7 @@ await TestHelper.DelayUntil( ); } - [RetryFact] + [Fact] public async Task Should_Only_Register_Semantic_Tokens_Registration_Once() { var tokens = Substitute.For(); @@ -182,7 +188,7 @@ public StaticDynamicRegistrationTests(ITestOutputHelper testOutputHelper) : base { } - [RetryFact] + [Fact] public async Task Should_Gather_Static_Registrations() { var (client, _) = await Initialize( @@ -217,7 +223,7 @@ await TestHelper.DelayUntil( client.RegistrationManager.CurrentRegistrations.Should().Contain(x => x.Method == TextDocumentNames.SemanticTokensRegistration); } - [RetryFact] + [Fact] public async Task Should_Register_Static_When_Dynamic_Is_Disabled() { var (client, server) = await Initialize( diff --git a/test/Lsp.Tests/Integration/ErroringHandlingTests.cs b/test/Lsp.Integration.Tests/ErroringHandlingTests.cs similarity index 98% rename from test/Lsp.Tests/Integration/ErroringHandlingTests.cs rename to test/Lsp.Integration.Tests/ErroringHandlingTests.cs index 6d7f7ff70..581d12a5b 100644 --- a/test/Lsp.Tests/Integration/ErroringHandlingTests.cs +++ b/test/Lsp.Integration.Tests/ErroringHandlingTests.cs @@ -23,7 +23,7 @@ public ErroringHandlingTests(ITestOutputHelper outputHelper) : base(new JsonRpcT { } - [RetryFact] + [Fact] public async Task Should_Handle_Malformed_Request() { var (client, server) = await Initialize(ConfigureClient, ConfigureServer); @@ -46,7 +46,7 @@ public async Task Should_Handle_Malformed_Request() a.Should().Throw(); } - [RetryFact] + [Fact] public async Task Should_Handle_Malformed_Notification() { var (client, server) = await Initialize(ConfigureClient, ConfigureServer); diff --git a/test/Lsp.Tests/Integration/EventingTests.cs b/test/Lsp.Integration.Tests/EventingTests.cs similarity index 98% rename from test/Lsp.Tests/Integration/EventingTests.cs rename to test/Lsp.Integration.Tests/EventingTests.cs index 472ee7e37..1a9cc5164 100644 --- a/test/Lsp.Tests/Integration/EventingTests.cs +++ b/test/Lsp.Integration.Tests/EventingTests.cs @@ -27,7 +27,7 @@ public EventingTests(ITestOutputHelper outputHelper) : base(new JsonRpcTestOptio { } - [RetryFact] + [Fact] public async Task Initialize_Interface_Is_Supported() { var onLanguageClientInitialize = Substitute.For(); @@ -41,7 +41,7 @@ public async Task Initialize_Interface_Is_Supported() await onLanguageServerInitialize.Received(1).OnInitialize(server, server.ClientSettings, Arg.Any()); } - [RetryFact] + [Fact] public async Task Initialize_Delegate_Is_Supported() { var onLanguageClientInitialize = Substitute.For(); @@ -55,7 +55,7 @@ public async Task Initialize_Delegate_Is_Supported() await onLanguageServerInitialize.Received(1).Invoke(server, server.ClientSettings, Arg.Any()); } - [RetryFact] + [Fact] public async Task Initialize_Interface_Is_Supported_On_Handlers() { var onLanguageClientInitialize = @@ -71,7 +71,7 @@ public async Task Initialize_Interface_Is_Supported_On_Handlers() await onLanguageServerInitialize.Received(1).OnInitialize(server, server.ClientSettings, Arg.Any()); } - [RetryFact] + [Fact] public async Task Initialize_Interface_Is_Supported_On_Handlers_After_Startup() { var onLanguageClientInitialize = @@ -90,7 +90,7 @@ public async Task Initialize_Interface_Is_Supported_On_Handlers_After_Startup() await onLanguageServerInitialize.Received(1).OnInitialize(server, server.ClientSettings, Arg.Any()); } - [RetryFact] + [Fact] public async Task Initialized_Interface_Is_Supported() { var onLanguageClientInitialized = Substitute.For(); @@ -104,7 +104,7 @@ public async Task Initialized_Interface_Is_Supported() await onLanguageServerInitialized.Received(1).OnInitialized(server, server.ClientSettings, server.ServerSettings, Arg.Any()); } - [RetryFact] + [Fact] public async Task Initialized_Delegate_Is_Supported() { var onLanguageClientInitialized = Substitute.For(); @@ -118,7 +118,7 @@ public async Task Initialized_Delegate_Is_Supported() await onLanguageServerInitialized.Received(1).Invoke(server, server.ClientSettings, server.ServerSettings, Arg.Any()); } - [RetryFact] + [Fact] public async Task Initialized_Interface_Is_Supported_On_Handlers() { var onLanguageClientInitialized = @@ -134,7 +134,7 @@ public async Task Initialized_Interface_Is_Supported_On_Handlers() await onLanguageServerInitialized.Received(1).OnInitialized(server, server.ClientSettings, server.ServerSettings, Arg.Any()); } - [RetryFact] + [Fact] public async Task Initialized_Interface_Is_Supported_On_Handlers_After_Startup() { var onLanguageClientInitialized = @@ -153,7 +153,7 @@ public async Task Initialized_Interface_Is_Supported_On_Handlers_After_Startup() await onLanguageServerInitialized.Received(1).OnInitialized(server, server.ClientSettings, server.ServerSettings, Arg.Any()); } - [RetryFact] + [Fact] public async Task Started_Interface_Is_Supported() { var onLanguageClientStarted = Substitute.For(); @@ -167,7 +167,7 @@ public async Task Started_Interface_Is_Supported() await onLanguageServerStarted.Received(1).OnStarted(server, Arg.Any()); } - [RetryFact] + [Fact] public async Task Started_Delegate_Is_Supported() { var onLanguageClientStarted = Substitute.For(); @@ -181,7 +181,7 @@ public async Task Started_Delegate_Is_Supported() await onLanguageServerStarted.Received(1).Invoke(server, Arg.Any()); } - [RetryFact] + [Fact] public async Task Started_Interface_Is_Supported_On_Handlers() { var onLanguageClientStarted = @@ -197,7 +197,7 @@ public async Task Started_Interface_Is_Supported_On_Handlers() await onLanguageServerStarted.Received(1).OnStarted(server, Arg.Any()); } - [RetryFact] + [Fact] public async Task Started_Interface_Is_Supported_On_Handlers_After_Startup() { var onLanguageClientStarted = diff --git a/test/Lsp.Tests/Integration/ExecuteCommandTests.cs b/test/Lsp.Integration.Tests/ExecuteCommandTests.cs similarity index 98% rename from test/Lsp.Tests/Integration/ExecuteCommandTests.cs rename to test/Lsp.Integration.Tests/ExecuteCommandTests.cs index fa8554321..d059aa2b9 100644 --- a/test/Lsp.Tests/Integration/ExecuteCommandTests.cs +++ b/test/Lsp.Integration.Tests/ExecuteCommandTests.cs @@ -28,7 +28,7 @@ public ExecuteCommandTests(ITestOutputHelper outputHelper) : base(new JsonRpcTes { } - [RetryFact] + [Fact] public async Task Should_Execute_A_Command() { var command = Substitute.For>(); @@ -65,7 +65,7 @@ public async Task Should_Execute_A_Command() await command.Received(1).Invoke(Arg.Any()); } - [RetryFact] + [Fact] public async Task Should_Execute_The_Correct_Command() { var commanda = Substitute.For>(); @@ -118,7 +118,7 @@ public async Task Should_Execute_The_Correct_Command() arg.Should().BeOfType(); } - [RetryFact] + [Fact] public async Task Should_Fail_To_Execute_A_Command_When_No_Command_Is_Defined() { var (client, _) = await Initialize( @@ -147,7 +147,7 @@ public async Task Should_Fail_To_Execute_A_Command_When_No_Command_Is_Defined() await action.Should().ThrowAsync(); } - [RetryFact] + [Fact] public async Task Should_Fail_To_Execute_A_Command_When_No_Command_Name_Is_Given() { var command = Substitute.For>(); @@ -187,7 +187,7 @@ public async Task Should_Fail_To_Execute_A_Command_When_No_Command_Name_Is_Given await command.Received(0).Invoke(Arg.Any()); } - [RetryFact] + [Fact] public async Task Should_Fail_To_Execute_A_Command() { var commandc = Substitute.For>(); @@ -233,7 +233,7 @@ public async Task Should_Fail_To_Execute_A_Command() await commandb.Received(0).Invoke(Arg.Any()); } - [RetryFact] + [Fact] public async Task Should_Execute_1_Args() { var (client, _) = await Initialize( @@ -270,7 +270,7 @@ public async Task Should_Execute_1_Args() await action.Should().NotThrowAsync(); } - [RetryFact] + [Fact] public async Task Should_Execute_2_Args() { var (client, _) = await Initialize( @@ -308,7 +308,7 @@ public async Task Should_Execute_2_Args() await action.Should().NotThrowAsync(); } - [RetryFact] + [Fact] public async Task Should_Execute_3_Args() { var (client, _) = await Initialize( @@ -347,7 +347,7 @@ public async Task Should_Execute_3_Args() await action.Should().NotThrowAsync(); } - [RetryFact] + [Fact] public async Task Should_Execute_4_Args() { var (client, _) = await Initialize( @@ -387,7 +387,7 @@ public async Task Should_Execute_4_Args() await action.Should().NotThrowAsync(); } - [RetryFact] + [Fact] public async Task Should_Execute_5_Args() { var (client, _) = await Initialize( @@ -430,7 +430,7 @@ public async Task Should_Execute_5_Args() await action.Should().NotThrowAsync(); } - [RetryFact] + [Fact] public async Task Should_Execute_6_Args() { var (client, _) = await Initialize( @@ -475,7 +475,7 @@ public async Task Should_Execute_6_Args() await action.Should().NotThrowAsync(); } - [RetryFact] + [Fact] public async Task Should_Execute_1_With_Missing_Args() { var (client, _) = await Initialize( @@ -512,7 +512,7 @@ public async Task Should_Execute_1_With_Missing_Args() await action.Should().NotThrowAsync(); } - [RetryFact] + [Fact] public async Task Should_Execute_2_With_Missing_Args() { var (client, _) = await Initialize( @@ -550,7 +550,7 @@ public async Task Should_Execute_2_With_Missing_Args() await action.Should().NotThrowAsync(); } - [RetryFact] + [Fact] public async Task Should_Execute_3_With_Missing_Args() { var (client, _) = await Initialize( @@ -589,7 +589,7 @@ public async Task Should_Execute_3_With_Missing_Args() await action.Should().NotThrowAsync(); } - [RetryFact] + [Fact] public async Task Should_Execute_4_With_Missing_Args() { var (client, _) = await Initialize( @@ -629,7 +629,7 @@ public async Task Should_Execute_4_With_Missing_Args() await action.Should().NotThrowAsync(); } - [RetryFact] + [Fact] public async Task Should_Execute_5_With_Missing_Args() { var (client, _) = await Initialize( @@ -670,7 +670,7 @@ public async Task Should_Execute_5_With_Missing_Args() await action.Should().NotThrowAsync(); } - [RetryFact] + [Fact] public async Task Should_Execute_6_With_Missing_Args() { var (client, _) = await Initialize( @@ -712,7 +712,7 @@ public async Task Should_Execute_6_With_Missing_Args() await action.Should().NotThrowAsync(); } - [RetryFact] + [Fact] public async Task Should_Execute_1_Null_Args() { var (client, _) = await Initialize( @@ -749,7 +749,7 @@ public async Task Should_Execute_1_Null_Args() await action.Should().NotThrowAsync(); } - [RetryFact] + [Fact] public async Task Should_Execute_2_Null_Args() { var (client, _) = await Initialize( @@ -787,7 +787,7 @@ public async Task Should_Execute_2_Null_Args() await action.Should().NotThrowAsync(); } - [RetryFact] + [Fact] public async Task Should_Execute_3_Null_Args() { var (client, _) = await Initialize( @@ -826,7 +826,7 @@ public async Task Should_Execute_3_Null_Args() await action.Should().NotThrowAsync(); } - [RetryFact] + [Fact] public async Task Should_Execute_4_Null_Args() { var (client, _) = await Initialize( @@ -866,7 +866,7 @@ public async Task Should_Execute_4_Null_Args() await action.Should().NotThrowAsync(); } - [RetryFact] + [Fact] public async Task Should_Execute_5_Null_Args() { var (client, _) = await Initialize( @@ -907,7 +907,7 @@ public async Task Should_Execute_5_Null_Args() await action.Should().NotThrowAsync(); } - [RetryFact] + [Fact] public async Task Should_Execute_6_Null_Args() { var (client, _) = await Initialize( diff --git a/test/Lsp.Tests/Integration/ExecuteTypedCommandTests.cs b/test/Lsp.Integration.Tests/ExecuteTypedCommandTests.cs similarity index 98% rename from test/Lsp.Tests/Integration/ExecuteTypedCommandTests.cs rename to test/Lsp.Integration.Tests/ExecuteTypedCommandTests.cs index 0937a389d..4caf445ae 100644 --- a/test/Lsp.Tests/Integration/ExecuteTypedCommandTests.cs +++ b/test/Lsp.Integration.Tests/ExecuteTypedCommandTests.cs @@ -28,7 +28,7 @@ public ExecuteTypedCommandTests(ITestOutputHelper outputHelper) : base(new JsonR { } - [RetryFact] + [Fact] public async Task Should_Execute_A_Command() { var command = Substitute.For, Task>>(); @@ -66,7 +66,7 @@ public async Task Should_Execute_A_Command() await command.Received(1).Invoke(Arg.Any>()); } - [RetryFact] + [Fact] public async Task Should_Execute_The_Correct_Command() { var commanda = Substitute.For, Task>>(); @@ -122,7 +122,7 @@ public async Task Should_Execute_The_Correct_Command() arg.Should().BeOfType(); } - [RetryFact] + [Fact] public async Task Should_Fail_To_Execute_A_Command_When_No_Command_Name_Is_Given() { var command = Substitute.For,Task>>(); @@ -163,7 +163,7 @@ public async Task Should_Fail_To_Execute_A_Command_When_No_Command_Name_Is_Given await command.Received(0).Invoke(Arg.Any>()); } - [RetryFact] + [Fact] public async Task Should_Fail_To_Execute_A_Command() { var commandc = Substitute.For,Task>>(); @@ -211,7 +211,7 @@ public async Task Should_Fail_To_Execute_A_Command() await commandb.Received(0).Invoke(Arg.Any>()); } - [RetryFact] + [Fact] public async Task Should_Execute_1_Args() { var (client, _) = await Initialize( @@ -248,7 +248,7 @@ public async Task Should_Execute_1_Args() await action.Should().NotThrowAsync(); } - [RetryFact] + [Fact] public async Task Should_Execute_2_Args() { var (client, _) = await Initialize( @@ -286,7 +286,7 @@ public async Task Should_Execute_2_Args() response.Value.Should().Be("1234"); } - [RetryFact] + [Fact] public async Task Should_Execute_3_Args() { var (client, _) = await Initialize( @@ -325,7 +325,7 @@ public async Task Should_Execute_3_Args() await action.Should().NotThrowAsync(); } - [RetryFact] + [Fact] public async Task Should_Execute_4_Args() { var (client, _) = await Initialize( @@ -365,7 +365,7 @@ public async Task Should_Execute_4_Args() await action.Should().NotThrowAsync(); } - [RetryFact] + [Fact] public async Task Should_Execute_5_Args() { var (client, _) = await Initialize( @@ -408,7 +408,7 @@ public async Task Should_Execute_5_Args() await action.Should().NotThrowAsync(); } - [RetryFact] + [Fact] public async Task Should_Execute_6_Args() { var (client, _) = await Initialize( @@ -453,7 +453,7 @@ public async Task Should_Execute_6_Args() await action.Should().NotThrowAsync(); } - [RetryFact] + [Fact] public async Task Should_Execute_1_With_Missing_Args() { var (client, _) = await Initialize( @@ -490,7 +490,7 @@ public async Task Should_Execute_1_With_Missing_Args() await action.Should().NotThrowAsync(); } - [RetryFact] + [Fact] public async Task Should_Execute_2_With_Missing_Args() { var (client, _) = await Initialize( @@ -528,7 +528,7 @@ public async Task Should_Execute_2_With_Missing_Args() await action.Should().NotThrowAsync(); } - [RetryFact] + [Fact] public async Task Should_Execute_3_With_Missing_Args() { var (client, _) = await Initialize( @@ -567,7 +567,7 @@ public async Task Should_Execute_3_With_Missing_Args() await action.Should().NotThrowAsync(); } - [RetryFact] + [Fact] public async Task Should_Execute_4_With_Missing_Args() { var (client, _) = await Initialize( @@ -607,7 +607,7 @@ public async Task Should_Execute_4_With_Missing_Args() await action.Should().NotThrowAsync(); } - [RetryFact] + [Fact] public async Task Should_Execute_5_With_Missing_Args() { var (client, _) = await Initialize( @@ -648,7 +648,7 @@ public async Task Should_Execute_5_With_Missing_Args() await action.Should().NotThrowAsync(); } - [RetryFact] + [Fact] public async Task Should_Execute_6_With_Missing_Args() { var (client, _) = await Initialize( @@ -690,7 +690,7 @@ public async Task Should_Execute_6_With_Missing_Args() await action.Should().NotThrowAsync(); } - [RetryFact] + [Fact] public async Task Should_Execute_1_Null_Args() { var (client, _) = await Initialize( @@ -727,7 +727,7 @@ public async Task Should_Execute_1_Null_Args() await action.Should().NotThrowAsync(); } - [RetryFact] + [Fact] public async Task Should_Execute_2_Null_Args() { var (client, _) = await Initialize( @@ -765,7 +765,7 @@ public async Task Should_Execute_2_Null_Args() await action.Should().NotThrowAsync(); } - [RetryFact] + [Fact] public async Task Should_Execute_3_Null_Args() { var (client, _) = await Initialize( @@ -804,7 +804,7 @@ public async Task Should_Execute_3_Null_Args() await action.Should().NotThrowAsync(); } - [RetryFact] + [Fact] public async Task Should_Execute_4_Null_Args() { var (client, _) = await Initialize( @@ -844,7 +844,7 @@ public async Task Should_Execute_4_Null_Args() await action.Should().NotThrowAsync(); } - [RetryFact] + [Fact] public async Task Should_Execute_5_Null_Args() { var (client, _) = await Initialize( @@ -885,7 +885,7 @@ public async Task Should_Execute_5_Null_Args() await action.Should().NotThrowAsync(); } - [RetryFact] + [Fact] public async Task Should_Execute_6_Null_Args() { var (client, _) = await Initialize( diff --git a/test/Lsp.Tests/Integration/ExtensionTests.cs b/test/Lsp.Integration.Tests/ExtensionTests.cs similarity index 96% rename from test/Lsp.Tests/Integration/ExtensionTests.cs rename to test/Lsp.Integration.Tests/ExtensionTests.cs index 72a77c23b..1405c7c81 100644 --- a/test/Lsp.Tests/Integration/ExtensionTests.cs +++ b/test/Lsp.Integration.Tests/ExtensionTests.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Reactive.Linq; using System.Reactive.Threading.Tasks; using System.Threading; @@ -28,7 +29,7 @@ public ExtensionTests(ITestOutputHelper outputHelper) : base(new JsonRpcTestOpti { } - [RetryFact] + [Fact] public async Task Should_Support_Custom_Capabilities() { var onDiscoverHandler = Substitute.For>>>(); @@ -80,7 +81,7 @@ public async Task Should_Support_Custom_Capabilities() } { - await client.RegistrationManager.Registrations.Throttle(TimeSpan.FromMilliseconds(300)).Take(1).ToTask(CancellationToken); + await TestHelper.DelayUntil(() => client.RegistrationManager.CurrentRegistrations.Any(z => z.Method == "tests"), CancellationToken); client.RegistrationManager.CurrentRegistrations.Should().Contain(z => z.Method == "tests").And.HaveCount(1); } @@ -96,7 +97,7 @@ await client.RequestDiscoverUnitTests( onRunUnitHandler.Received(1).Invoke(Arg.Any(), Arg.Is(x => x.Property == "Abcd"), Arg.Any()); } - [RetryFact] + [Fact] public async Task Should_Support_Custom_Capabilities_Using_Json() { var onDiscoverHandler = Substitute.For>>>(); @@ -130,7 +131,7 @@ public async Task Should_Support_Custom_Capabilities_Using_Json() } { - await client.RegistrationManager.Registrations.Throttle(TimeSpan.FromMilliseconds(300)).Take(1).ToTask(CancellationToken); + await TestHelper.DelayUntil(() => client.RegistrationManager.CurrentRegistrations.Any(z => z.Method == "tests"), CancellationToken); client.RegistrationManager.CurrentRegistrations.Should().Contain(z => z.Method == "tests").And.HaveCount(1); } @@ -141,7 +142,7 @@ public async Task Should_Support_Custom_Capabilities_Using_Json() onRunUnitHandler.Received(1).Invoke(Arg.Any(), Arg.Is(x => x.Property == "Abcd"), Arg.Any()); } - [RetryFact] + [Fact] public async Task Should_Support_Custom_Static_Options() { var onDiscoverHandler = Substitute.For>>>(); @@ -179,7 +180,7 @@ public async Task Should_Support_Custom_Static_Options() } } - [RetryFact] + [Fact] public async Task Should_Convert_Registration_Options_Into_Static_Options_As_Required() { var (client, _) = await Initialize( diff --git a/test/Lsp.Tests/Integration/FileOperationTests.cs b/test/Lsp.Integration.Tests/FileOperationTests.cs similarity index 96% rename from test/Lsp.Tests/Integration/FileOperationTests.cs rename to test/Lsp.Integration.Tests/FileOperationTests.cs index eb36d280b..6fc4fb275 100644 --- a/test/Lsp.Tests/Integration/FileOperationTests.cs +++ b/test/Lsp.Integration.Tests/FileOperationTests.cs @@ -12,6 +12,7 @@ using OmniSharp.Extensions.LanguageServer.Server; using Serilog.Events; using TestingUtils; +using Xunit; using Xunit.Abstractions; namespace Lsp.Tests.Integration @@ -29,7 +30,7 @@ public FileOperationTests(ITestOutputHelper outputHelper) : base(new JsonRpcTest { } - [RetryFact] + [Fact] public async Task Should_Handle_FileCreate() { var (client, server) = await Initialize(Configure, Configure); @@ -45,7 +46,7 @@ await client.RequestWillCreateFile( } ); - await SettleNext(); + await TestHelper.DelayUntil(() => _didCreateFileHandler.ReceivedCalls().Any(), CancellationToken); _didCreateFileHandler.ReceivedCalls().Should().HaveCount(1); _willCreateFileHandler.ReceivedCalls().Should().HaveCount(1); @@ -75,7 +76,7 @@ static void VerifyClientSettings(InitializeParams result) } } - [RetryFact] + [Fact] public async Task Should_Handle_FileRename() { var (client, server) = await Initialize(Configure, Configure); @@ -91,7 +92,7 @@ await client.RequestWillRenameFile( } ); - await SettleNext(); + await TestHelper.DelayUntil(() => _didRenameFileHandler.ReceivedCalls().Any(), CancellationToken); _didRenameFileHandler.ReceivedCalls().Should().HaveCount(1); _willRenameFileHandler.ReceivedCalls().Should().HaveCount(1); @@ -121,7 +122,7 @@ static void VerifyClientSettings(InitializeParams result) } } - [RetryFact] + [Fact] public async Task Should_Handle_FileDelete() { var (client, server) = await Initialize(Configure, Configure); @@ -137,7 +138,7 @@ await client.RequestWillDeleteFile( } ); - await SettleNext(); + await TestHelper.DelayUntil(() => _didDeleteFileHandler.ReceivedCalls().Any(), CancellationToken); _didDeleteFileHandler.ReceivedCalls().Should().HaveCount(1); _willDeleteFileHandler.ReceivedCalls().Should().HaveCount(1); diff --git a/test/Lsp.Tests/Integration/Fixtures/DefaultClient.cs b/test/Lsp.Integration.Tests/Fixtures/DefaultClient.cs similarity index 100% rename from test/Lsp.Tests/Integration/Fixtures/DefaultClient.cs rename to test/Lsp.Integration.Tests/Fixtures/DefaultClient.cs diff --git a/test/Lsp.Tests/Integration/Fixtures/DefaultOptions.cs b/test/Lsp.Integration.Tests/Fixtures/DefaultOptions.cs similarity index 100% rename from test/Lsp.Tests/Integration/Fixtures/DefaultOptions.cs rename to test/Lsp.Integration.Tests/Fixtures/DefaultOptions.cs diff --git a/test/Lsp.Tests/Integration/Fixtures/DefaultServer.cs b/test/Lsp.Integration.Tests/Fixtures/DefaultServer.cs similarity index 100% rename from test/Lsp.Tests/Integration/Fixtures/DefaultServer.cs rename to test/Lsp.Integration.Tests/Fixtures/DefaultServer.cs diff --git a/test/Lsp.Tests/Integration/Fixtures/ExampleExtensions.cs b/test/Lsp.Integration.Tests/Fixtures/ExampleExtensions.cs similarity index 100% rename from test/Lsp.Tests/Integration/Fixtures/ExampleExtensions.cs rename to test/Lsp.Integration.Tests/Fixtures/ExampleExtensions.cs diff --git a/test/Lsp.Tests/Integration/Fixtures/IConfigureLanguageClientOptions.cs b/test/Lsp.Integration.Tests/Fixtures/IConfigureLanguageClientOptions.cs similarity index 100% rename from test/Lsp.Tests/Integration/Fixtures/IConfigureLanguageClientOptions.cs rename to test/Lsp.Integration.Tests/Fixtures/IConfigureLanguageClientOptions.cs diff --git a/test/Lsp.Tests/Integration/Fixtures/IConfigureLanguageProtocolFixture.cs b/test/Lsp.Integration.Tests/Fixtures/IConfigureLanguageProtocolFixture.cs similarity index 100% rename from test/Lsp.Tests/Integration/Fixtures/IConfigureLanguageProtocolFixture.cs rename to test/Lsp.Integration.Tests/Fixtures/IConfigureLanguageProtocolFixture.cs diff --git a/test/Lsp.Tests/Integration/Fixtures/IConfigureLanguageServerOptions.cs b/test/Lsp.Integration.Tests/Fixtures/IConfigureLanguageServerOptions.cs similarity index 100% rename from test/Lsp.Tests/Integration/Fixtures/IConfigureLanguageServerOptions.cs rename to test/Lsp.Integration.Tests/Fixtures/IConfigureLanguageServerOptions.cs diff --git a/test/Lsp.Tests/Integration/Fixtures/LanguageProtocolFixture.cs b/test/Lsp.Integration.Tests/Fixtures/LanguageProtocolFixture.cs similarity index 56% rename from test/Lsp.Tests/Integration/Fixtures/LanguageProtocolFixture.cs rename to test/Lsp.Integration.Tests/Fixtures/LanguageProtocolFixture.cs index 4b7c7a84b..fd9543ab0 100644 --- a/test/Lsp.Tests/Integration/Fixtures/LanguageProtocolFixture.cs +++ b/test/Lsp.Integration.Tests/Fixtures/LanguageProtocolFixture.cs @@ -14,20 +14,30 @@ public class LanguageProtocolFixture ConfigureForSupports(this EquivalencyAssertionOptions options, ILogger? logger = null) => + options + .WithTracing(new TraceWriter(logger ?? NullLogger.Instance)) + .ComparingByMembers>() + .ComparingByMembers>() + .ComparingByMembers>() + .ComparingByMembers>() + .ComparingByMembers>() + .ComparingByMembers>() + .ComparingByMembers>() + .ComparingByMembers>() + .ComparingByMembers>() + .ComparingByMembers>() + .ComparingByMembers>() + .ComparingByMembers>() + .ComparingByMembers>() + .ComparingByMembers>() + .ComparingByMembers>() + .ComparingByMembers>() + .ComparingByMembers>() + .ComparingByMembers>() + .ComparingByMembers>() + .ComparingByMembers>() + .ComparingByMembers>() + .ComparingByMembers>() + .ComparingByMembers>() + .ComparingByMembers>() + .ComparingByMembers>() + .ComparingByMembers>() + .ComparingByMembers>() + .ComparingByMembers>() + .ComparingByMembers>() + .ComparingByMembers>(); + } +} diff --git a/test/Lsp.Tests/Integration/HandlersManagerIntegrationTests.cs b/test/Lsp.Integration.Tests/HandlersManagerIntegrationTests.cs similarity index 97% rename from test/Lsp.Tests/Integration/HandlersManagerIntegrationTests.cs rename to test/Lsp.Integration.Tests/HandlersManagerIntegrationTests.cs index abfb2a74e..f5050a773 100644 --- a/test/Lsp.Tests/Integration/HandlersManagerIntegrationTests.cs +++ b/test/Lsp.Integration.Tests/HandlersManagerIntegrationTests.cs @@ -20,7 +20,7 @@ public HandlersManagerIntegrationTests(ITestOutputHelper testOutputHelper) : bas { } - [RetryFact] + [Fact] public async Task Should_Return_Default_Handlers() { var (_, server) = await Initialize(options => {}, options => {}); @@ -30,7 +30,7 @@ public async Task Should_Return_Default_Handlers() handlersManager.GetHandlers().Should().HaveCount(6); } - [RetryFact] + [Fact] public async Task Should_Return_Additional_Handlers() { var (_, server) = await Initialize(options => {}, options => {}); @@ -41,7 +41,7 @@ public async Task Should_Return_Additional_Handlers() handlersManager.GetHandlers().Should().HaveCount(7); } - [RetryFact] + [Fact] public async Task Link_Should_Fail_If_No_Handler_Is_Defined() { var (_, server) = await Initialize(options => {}, options => {}); @@ -52,7 +52,7 @@ public async Task Link_Should_Fail_If_No_Handler_Is_Defined() a.Should().Throw().Which.Message.Should().Contain("Descriptors must be registered before links can be created"); } - [RetryFact] + [Fact] public async Task Link_Should_Fail_If_Link_Is_On_The_Wrong_Side() { var (_, server) = await Initialize(options => {}, options => {}); diff --git a/test/Lsp.Tests/Integration/InitializationTests.cs b/test/Lsp.Integration.Tests/InitializationTests.cs similarity index 98% rename from test/Lsp.Tests/Integration/InitializationTests.cs rename to test/Lsp.Integration.Tests/InitializationTests.cs index ade4633b8..6c783cf6c 100644 --- a/test/Lsp.Tests/Integration/InitializationTests.cs +++ b/test/Lsp.Integration.Tests/InitializationTests.cs @@ -33,7 +33,7 @@ public InitializationTests(ITestOutputHelper outputHelper) : base(new JsonRpcTes { } - [RetryFact] + [Fact] public async Task Logs_should_be_allowed_during_startup() { await Initialize(ConfigureClient, ConfigureServer); @@ -42,7 +42,7 @@ public async Task Logs_should_be_allowed_during_startup() _logs.Should().ContainInOrder("OnInitialize", "OnInitialized"); } - [RetryFact] + [Fact] public async Task Facades_should_be_resolvable() { var (client, server) = await Initialize(ConfigureClient, ConfigureServer); @@ -54,7 +54,7 @@ public async Task Facades_should_be_resolvable() response.Should().NotBeNull(); } - [RetryFact] + [Fact] public async Task Should_Not_Be_Able_To_Send_Messages_Unit_Initialization() { if (!(TestOptions.ClientLoggerFactory is TestLoggerFactory loggerFactory)) throw new Exception("wtf"); @@ -99,7 +99,7 @@ public async Task Should_Not_Be_Able_To_Send_Messages_Unit_Initialization() onInitializedNotify.Received(1).Invoke(); } - [RetryFact] + [Fact] public async Task Should_Be_Able_To_Register_Before_Initialize() { var (client, server) = Create(options => options.EnableDynamicRegistration().EnableAllCapabilities(), options => { }); diff --git a/test/Lsp.Tests/Integration/IntegrationTestingExtensions.cs b/test/Lsp.Integration.Tests/IntegrationTestingExtensions.cs similarity index 100% rename from test/Lsp.Tests/Integration/IntegrationTestingExtensions.cs rename to test/Lsp.Integration.Tests/IntegrationTestingExtensions.cs diff --git a/test/Lsp.Tests/Integration/LanguageServerConfigurationTests.cs b/test/Lsp.Integration.Tests/LanguageServerConfigurationTests.cs similarity index 97% rename from test/Lsp.Tests/Integration/LanguageServerConfigurationTests.cs rename to test/Lsp.Integration.Tests/LanguageServerConfigurationTests.cs index 0e85ed2ad..684b95d3d 100644 --- a/test/Lsp.Tests/Integration/LanguageServerConfigurationTests.cs +++ b/test/Lsp.Integration.Tests/LanguageServerConfigurationTests.cs @@ -9,6 +9,7 @@ using Newtonsoft.Json.Linq; using NSubstitute; using NSubstitute.Extensions; +using NSubstitute.ReceivedExtensions; using OmniSharp.Extensions.JsonRpc.Testing; using OmniSharp.Extensions.LanguageProtocol.Testing; using OmniSharp.Extensions.LanguageServer.Client; @@ -31,7 +32,7 @@ public LanguageServerConfigurationTests(ITestOutputHelper outputHelper) : base(n { } - [RetryFact] + [Fact] public async Task Should_Not_Support_Configuration_It_Not_Configured() { var (_, server, configuration) = await InitializeWithConfiguration(ConfigureClient, o => { }); @@ -45,7 +46,7 @@ public async Task Should_Not_Support_Configuration_It_Not_Configured() server.Configuration.AsEnumerable().Should().BeEmpty(); } - [RetryFact] + [Fact] public async Task Should_Allow_Null_Response() { var (client, server) = await Initialize( @@ -59,7 +60,7 @@ public async Task Should_Allow_Null_Response() a.Should().NotThrow(); } - [RetryFact] + [Fact] public async Task Should_Update_Configuration_On_Server() { var (_, server, configuration) = await InitializeWithConfiguration(ConfigureClient, ConfigureServer); @@ -74,7 +75,7 @@ public async Task Should_Update_Configuration_On_Server() server.Configuration["othersection:value"].Should().Be("key"); } - [RetryFact] + [Fact] public async Task Should_Update_Configuration_On_Server_After_Starting() { var (_, server, configuration) = await InitializeWithConfiguration(ConfigureClient, options => {}); @@ -90,7 +91,7 @@ public async Task Should_Update_Configuration_On_Server_After_Starting() server.Configuration["othersection:value"].Should().Be("key"); } - [RetryFact] + [Fact] public async Task Should_Update_Configuration_Should_Stop_Watching_Sections() { var (_, server, configuration) = await InitializeWithConfiguration(ConfigureClient, ConfigureServer); @@ -112,7 +113,7 @@ public async Task Should_Update_Configuration_Should_Stop_Watching_Sections() server.Configuration["othersection:value"].Should().BeNull(); } - [RetryFact] + [Fact] public async Task Should_Update_Scoped_Configuration() { var (_, server, configuration) = await InitializeWithConfiguration(ConfigureClient, ConfigureServer); @@ -134,7 +135,7 @@ public async Task Should_Update_Scoped_Configuration() scopedConfiguration["othersection:value"].Should().Be("scopedkey"); } - [RetryFact] + [Fact] public async Task Should_Fallback_To_Original_Configuration() { var (_, server, configuration) = await InitializeWithConfiguration(ConfigureClient, ConfigureServer); @@ -166,7 +167,7 @@ public async Task Should_Fallback_To_Original_Configuration() scopedConfiguration["othersection:value"].Should().Be("key"); } - [RetryFact] + [Fact] public async Task Should_Only_Update_Configuration_Items_That_Are_Defined() { var (_, server, configuration) = await InitializeWithConfiguration(ConfigureClient, ConfigureServer); @@ -184,7 +185,7 @@ public async Task Should_Only_Update_Configuration_Items_That_Are_Defined() server.Configuration["notmysection:key"].Should().BeNull(); } - [RetryFact] + [Fact] public async Task Should_Support_Configuration_Binding() { var (_, server, configuration) = await InitializeWithConfiguration(ConfigureClient, ConfigureServer); @@ -208,7 +209,7 @@ public async Task Should_Support_Configuration_Binding() data.Port.Should().Be(80); } - [RetryFact] + [Fact] public async Task Should_Support_Options() { var (_, server, configuration) = await InitializeWithConfiguration(ConfigureClient, options => { @@ -233,7 +234,7 @@ public async Task Should_Support_Options() options.Value.Port.Should().Be(443); } - [RetryFact] + [Fact] public async Task Should_Support_Options_Monitor() { var (_, server, configuration) = await InitializeWithConfiguration(ConfigureClient, options => { @@ -253,7 +254,7 @@ public async Task Should_Support_Options_Monitor() // IOptionsMonitor<> is registered as a singleton, so this will update options.CurrentValue.Host.Should().Be("localhost"); options.CurrentValue.Port.Should().Be(443); - sub.Received(1).Invoke(Arg.Any()); + sub.Received(Quantity.AtLeastOne()).Invoke(Arg.Any()); configuration.Update("mysection", new Dictionary { ["host"] = "127.0.0.1", ["port"] = "80" }); await options.WaitForChange(CancellationToken); @@ -261,7 +262,7 @@ public async Task Should_Support_Options_Monitor() options.CurrentValue.Host.Should().Be("127.0.0.1"); options.CurrentValue.Port.Should().Be(80); - sub.Received(2).Invoke(Arg.Any()); + sub.Received(Quantity.Within(2, int.MaxValue)).Invoke(Arg.Any()); } class BinderSourceUrl diff --git a/test/Lsp.Tests/Integration/LanguageServerLoggingTests.cs b/test/Lsp.Integration.Tests/LanguageServerLoggingTests.cs similarity index 97% rename from test/Lsp.Tests/Integration/LanguageServerLoggingTests.cs rename to test/Lsp.Integration.Tests/LanguageServerLoggingTests.cs index 309d02523..273dcffef 100644 --- a/test/Lsp.Tests/Integration/LanguageServerLoggingTests.cs +++ b/test/Lsp.Integration.Tests/LanguageServerLoggingTests.cs @@ -25,7 +25,7 @@ public LanguageServerLoggingTests(ITestOutputHelper outputHelper) : base(new Jso { } - [RetryFact] + [Fact] public async Task Logs_Are_Sent_To_Client_From_Server() { var logs = new ConcurrentBag(); @@ -54,6 +54,7 @@ public async Task Logs_Are_Sent_To_Client_From_Server() logger.LogDebug("Just gotta let you debug!"); await logs.DelayUntilCount(6, CancellationToken); + await SettleNext(); var items = logs.Take(6).ToList(); items.Should().HaveCount(6); @@ -63,7 +64,7 @@ public async Task Logs_Are_Sent_To_Client_From_Server() items.Where(z => z.Type == MessageType.Log).Should().HaveCount(2); } - [RetryFact] + [Fact] public async Task Logs_Are_Sent_To_Client_From_Server_Respecting_SetMinimumLevel() { var logs = new ConcurrentBag(); @@ -92,6 +93,7 @@ public async Task Logs_Are_Sent_To_Client_From_Server_Respecting_SetMinimumLevel logger.LogDebug("Just gotta let you debug!"); await logs.DelayUntilCount(3, CancellationToken); + await SettleNext(); var items = logs.Take(3).ToList(); items.Should().HaveCount(3); @@ -101,7 +103,7 @@ public async Task Logs_Are_Sent_To_Client_From_Server_Respecting_SetMinimumLevel items.Where(z => z.Type == MessageType.Log).Should().HaveCount(0); } - [RetryFact] + [Fact] public async Task Logs_Are_Sent_To_Client_From_Server_Respecting_TraceLevel() { var logs = new ConcurrentBag(); @@ -130,6 +132,7 @@ public async Task Logs_Are_Sent_To_Client_From_Server_Respecting_TraceLevel() logger.LogDebug("Just gotta let you debug!"); await logs.DelayUntilCount(4, CancellationToken); + await SettleNext(); var items = logs.Take(4).ToList(); items.Should().HaveCount(4); @@ -139,7 +142,7 @@ public async Task Logs_Are_Sent_To_Client_From_Server_Respecting_TraceLevel() items.Where(z => z.Type == MessageType.Log).Should().HaveCount(0); } - [RetryFact] + [Fact] public async Task Client_Can_Dynamically_Change_Server_Trace_Level_Off_To_Verbose() { var logs = new ConcurrentBag(); @@ -170,6 +173,7 @@ public async Task Client_Can_Dynamically_Change_Server_Trace_Level_Off_To_Verbos await logs.DelayUntilCount(3, CancellationToken); { var items = logs.Take(3).ToList(); + await SettleNext(); ; items.Should().HaveCount(3); @@ -193,6 +197,7 @@ public async Task Client_Can_Dynamically_Change_Server_Trace_Level_Off_To_Verbos await logs.DelayUntilCount(6, CancellationToken); { + await SettleNext(); var items = logs.Take(6).ToList(); ; @@ -204,7 +209,7 @@ public async Task Client_Can_Dynamically_Change_Server_Trace_Level_Off_To_Verbos } } - [RetryFact] + [Fact] public async Task Client_Can_Dynamically_Change_Server_Trace_Level_Verbose_To_Off() { var logs = new ConcurrentBag(); @@ -236,6 +241,7 @@ public async Task Client_Can_Dynamically_Change_Server_Trace_Level_Verbose_To_Of await logs.DelayUntilCount(6, CancellationToken); { + await SettleNext(); var items = logs.Take(6).ToList(); items.Should().HaveCount(6); @@ -259,6 +265,7 @@ public async Task Client_Can_Dynamically_Change_Server_Trace_Level_Verbose_To_Of await logs.DelayUntilCount(3, CancellationToken); { + await SettleNext(); var items = logs.Take(3).ToList(); items.Should().HaveCount(3); diff --git a/test/Lsp.Tests/Integration/LogMessageTests.cs b/test/Lsp.Integration.Tests/LogMessageTests.cs similarity index 98% rename from test/Lsp.Tests/Integration/LogMessageTests.cs rename to test/Lsp.Integration.Tests/LogMessageTests.cs index b527a0ece..6a4869a85 100644 --- a/test/Lsp.Tests/Integration/LogMessageTests.cs +++ b/test/Lsp.Integration.Tests/LogMessageTests.cs @@ -23,7 +23,7 @@ public LogMessageTests(ITestOutputHelper outputHelper) : base(new JsonRpcTestOpt private readonly List _receivedMessages = new List(); - [RetryFact] + [Fact] public async Task Should_Log_Messages_Through_Window_Extension_Methods() { var (_, server) = await Initialize(ConfigureClient, ConfigureServer); @@ -52,7 +52,7 @@ public async Task Should_Log_Messages_Through_Window_Extension_Methods() _receivedMessages.Should().Contain(z => z.Type == MessageType.Log).And.Subject.Count(z => z.Type == MessageType.Log).Should().Be(3); } - [RetryFact] + [Fact] public async Task Should_Log_Messages_Through_Server_Extension_Methods() { var (_, server) = await Initialize(ConfigureClient, ConfigureServer); diff --git a/test/Lsp.Integration.Tests/Lsp.Integration.Tests.csproj b/test/Lsp.Integration.Tests/Lsp.Integration.Tests.csproj new file mode 100644 index 000000000..46970d637 --- /dev/null +++ b/test/Lsp.Integration.Tests/Lsp.Integration.Tests.csproj @@ -0,0 +1,24 @@ + + + net5.0;netcoreapp3.1;netcoreapp2.1 + true + AnyCPU + + + + + + + + + + + + + + + + + + + diff --git a/test/Lsp.Tests/Integration/MonikerTests.cs b/test/Lsp.Integration.Tests/MonikerTests.cs similarity index 99% rename from test/Lsp.Tests/Integration/MonikerTests.cs rename to test/Lsp.Integration.Tests/MonikerTests.cs index fa34b3444..6b5c37aaf 100644 --- a/test/Lsp.Tests/Integration/MonikerTests.cs +++ b/test/Lsp.Integration.Tests/MonikerTests.cs @@ -26,7 +26,7 @@ public MonikerTests(ITestOutputHelper outputHelper) : base(new JsonRpcTestOption _request = Substitute.For?>>>(); } - [RetryFact] + [Fact] public async Task Should_Get_Monikers() { _request.Invoke(Arg.Any(), Arg.Any()) diff --git a/test/Lsp.Tests/Integration/OverrideHandlerTests.cs b/test/Lsp.Integration.Tests/OverrideHandlerTests.cs similarity index 99% rename from test/Lsp.Tests/Integration/OverrideHandlerTests.cs rename to test/Lsp.Integration.Tests/OverrideHandlerTests.cs index 219cae125..e0e961e2a 100644 --- a/test/Lsp.Tests/Integration/OverrideHandlerTests.cs +++ b/test/Lsp.Integration.Tests/OverrideHandlerTests.cs @@ -24,7 +24,7 @@ public OverrideHandlerTests(ITestOutputHelper testOutputHelper) : base(new JsonR { } - [RetryFact] + [Fact] public async Task Should_Support_Custom_Execute_Command_Handlers() { var (client, _) = await Initialize( @@ -44,7 +44,7 @@ public async Task Should_Support_Custom_Execute_Command_Handlers() response.Should().BeEquivalentTo(JToken.FromObject(new { someValue = "custom" })); } - [RetryFact] + [Fact] public async Task Should_Support_Mixed_Execute_Command_Handlers() { var (client, _) = await Initialize( diff --git a/test/Lsp.Tests/Integration/PartialItemTests.cs b/test/Lsp.Integration.Tests/PartialItemTests.cs similarity index 97% rename from test/Lsp.Tests/Integration/PartialItemTests.cs rename to test/Lsp.Integration.Tests/PartialItemTests.cs index e7d8ea958..bbf7c21d8 100644 --- a/test/Lsp.Tests/Integration/PartialItemTests.cs +++ b/test/Lsp.Integration.Tests/PartialItemTests.cs @@ -24,7 +24,7 @@ public Delegates(ITestOutputHelper testOutputHelper, LanguageProtocolFixture z.Data.Length).Should().ContainInOrder(1, 2, 3); } - [RetryFact(10)] + [Fact] public async Task Should_Behave_Like_An_Observable_Without_Progress_Support() { var response = await Client.SendRequest(new SemanticTokensParams { TextDocument = new TextDocumentIdentifier(@"c:\test.cs") }, CancellationToken); diff --git a/test/Lsp.Tests/Integration/PartialItemsTests.cs b/test/Lsp.Integration.Tests/PartialItemsTests.cs similarity index 97% rename from test/Lsp.Tests/Integration/PartialItemsTests.cs rename to test/Lsp.Integration.Tests/PartialItemsTests.cs index 78f65a906..d3243a697 100644 --- a/test/Lsp.Tests/Integration/PartialItemsTests.cs +++ b/test/Lsp.Integration.Tests/PartialItemsTests.cs @@ -29,7 +29,7 @@ public Delegates(ITestOutputHelper testOutputHelper, LanguageProtocolFixture z.Command!.Name).Should().ContainInOrder("CodeLens 1", "CodeLens 2", "CodeLens 3"); } - [RetryFact(10, skipOn: SkipOnPlatform.All)] + [Fact] public async Task Should_Behave_Like_An_Observable() { var items = await Client.TextDocument @@ -63,7 +63,7 @@ public async Task Should_Behave_Like_An_Observable() items.Select(z => z.Command!.Name).Should().ContainInOrder("CodeLens 1", "CodeLens 2", "CodeLens 3"); } - [RetryFact(10, skipOn: SkipOnPlatform.All)] + [Fact] public async Task Should_Behave_Like_An_Observable_Without_Progress_Support() { var response = await Client.SendRequest( @@ -120,7 +120,7 @@ public Handlers(ITestOutputHelper testOutputHelper, LanguageProtocolFixture(); diff --git a/test/Lsp.Tests/Integration/ProgressTests.cs b/test/Lsp.Integration.Tests/ProgressTests.cs similarity index 90% rename from test/Lsp.Tests/Integration/ProgressTests.cs rename to test/Lsp.Integration.Tests/ProgressTests.cs index bc3adece5..e61bb7e56 100644 --- a/test/Lsp.Tests/Integration/ProgressTests.cs +++ b/test/Lsp.Integration.Tests/ProgressTests.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Reactive; using System.Reactive.Disposables; using System.Reactive.Linq; @@ -26,7 +28,7 @@ private class Data public string Value { get; set; } = "Value"; } - [FactWithSkipOn(SkipOnPlatform.All)] + [Fact] public async Task Should_Send_Progress_From_Server_To_Client() { var token = new ProgressToken(Guid.NewGuid().ToString()); @@ -76,7 +78,7 @@ await Observable.Create( data.Should().ContainInOrder(new[] { "1", "3", "2", "4", "5" }); } - [FactWithSkipOn(SkipOnPlatform.All)] + [Fact] public async Task Should_Send_Progress_From_Client_To_Server() { var token = new ProgressToken(Guid.NewGuid().ToString()); @@ -116,7 +118,10 @@ public async Task Should_Send_Progress_From_Client_To_Server() await Observable.Create( innerObserver => new CompositeDisposable() { - observable.Take(5).Select(z => z.Value).Subscribe(v => innerObserver.OnNext(Unit.Default), innerObserver.OnCompleted), + observable + .Take(5) + .Select(z => z.Value) + .Subscribe(v => innerObserver.OnNext(Unit.Default), innerObserver.OnCompleted), workDoneObservable } ).ToTask(CancellationToken); @@ -126,14 +131,14 @@ await Observable.Create( data.Should().ContainInOrder(new[] { "1", "3", "2", "4", "5" }); } - [FactWithSkipOn(SkipOnPlatform.All)] + [Fact] public void WorkDone_Should_Be_Supported() { Server.WorkDoneManager.IsSupported.Should().BeTrue(); Client.WorkDoneManager.IsSupported.Should().BeTrue(); } - [FactWithSkipOn(SkipOnPlatform.All)] + [Fact] public async Task Should_Support_Creating_Work_Done_From_Sever_To_Client() { var token = new ProgressToken(Guid.NewGuid().ToString()); @@ -195,7 +200,7 @@ public async Task Should_Support_Creating_Work_Done_From_Sever_To_Client() results.Should().ContainInOrder("Begin", "Report 1", "Report 2", "Report 3", "Report 4", "End"); } - [FactWithSkipOn(SkipOnPlatform.All)] + [Fact] public async Task Should_Support_Observing_Work_Done_From_Client_To_Server_Request() { var token = new ProgressToken(Guid.NewGuid().ToString()); @@ -257,14 +262,14 @@ public async Task Should_Support_Observing_Work_Done_From_Client_To_Server_Reque results.Should().ContainInOrder("Begin", "Report 1", "Report 2", "Report 3", "Report 4", "End"); } - [FactWithSkipOn(SkipOnPlatform.All)] + [Fact] public async Task Should_Support_Cancelling_Work_Done_From_Client_To_Server_Request() { var token = new ProgressToken(Guid.NewGuid().ToString()); var workDoneObservable = Client.WorkDoneManager.Monitor(token); - var observable = workDoneObservable.Replay(); - using var _ = observable.Connect(); + var data = new List(); + using var _ = workDoneObservable.Subscribe(x => data.Add(x)); using var workDoneObserver = await Server.WorkDoneManager.Create( token, new WorkDoneProgressBegin { @@ -291,7 +296,7 @@ public async Task Should_Support_Cancelling_Work_Done_From_Client_To_Server_Requ } ); - await observable.Take(3).ToTask(CancellationToken); + await TestHelper.DelayUntil(() => data.Count >= 3, CancellationToken); workDoneObservable.Dispose(); @@ -311,14 +316,16 @@ public async Task Should_Support_Cancelling_Work_Done_From_Client_To_Server_Requ workDoneObserver.OnCompleted(); - var results = await observable.Select( - z => z switch { - WorkDoneProgressBegin begin => begin.Message, - WorkDoneProgressReport begin => begin.Message, - WorkDoneProgressEnd begin => begin.Message, - _ => throw new NotSupportedException() - } - ).ToArray().ToTask(CancellationToken); + var results = data + .Select( + z => z switch { + WorkDoneProgressBegin begin => begin.Message, + WorkDoneProgressReport begin => begin.Message, + WorkDoneProgressEnd begin => begin.Message, + _ => throw new NotSupportedException() + } + ) + .ToArray(); results.Should().ContainInOrder("Begin", "Report 1", "Report 2"); } diff --git a/test/Lsp.Integration.Tests/Properties.cs b/test/Lsp.Integration.Tests/Properties.cs new file mode 100644 index 000000000..80a070195 --- /dev/null +++ b/test/Lsp.Integration.Tests/Properties.cs @@ -0,0 +1,3 @@ +using System; + +[assembly: Xunit.CollectionBehavior(MaxParallelThreads = 10)] diff --git a/test/Lsp.Tests/Integration/ProposalTests.cs b/test/Lsp.Integration.Tests/ProposalTests.cs similarity index 98% rename from test/Lsp.Tests/Integration/ProposalTests.cs rename to test/Lsp.Integration.Tests/ProposalTests.cs index c0f593d7c..002834c5e 100644 --- a/test/Lsp.Tests/Integration/ProposalTests.cs +++ b/test/Lsp.Integration.Tests/ProposalTests.cs @@ -27,7 +27,7 @@ public ProposalTests(ITestOutputHelper testOutputHelper) : base(new JsonRpcTestO { } - [RetryFact] + [Fact] public async Task Server_Should_Deserialize_Capabilities_As_Proposal_Types() { var (_, server) = await Initialize( @@ -47,7 +47,7 @@ public async Task Server_Should_Deserialize_Capabilities_As_Proposal_Types() server.ClientSettings.Capabilities!.Workspace.Should().BeOfType(); } - [RetryFact] + [Fact] public async Task Client_Should_Deserialize_Capabilities_As_Proposal_Types() { var (client, _) = await Initialize( diff --git a/test/Lsp.Integration.Tests/Records.cs b/test/Lsp.Integration.Tests/Records.cs new file mode 100644 index 000000000..207d2246e --- /dev/null +++ b/test/Lsp.Integration.Tests/Records.cs @@ -0,0 +1,23 @@ +#pragma warning disable MA0048 // File name must match type name +#define INTERNAL_RECORD_ATTRIBUTES +#if NETSTANDARD || NETCOREAPP +using System.ComponentModel; + +// ReSharper disable once CheckNamespace +namespace System.Runtime.CompilerServices +{ + /// + /// Reserved to be used by the compiler for tracking metadata. + /// This class should not be used by developers in source code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] +#if INTERNAL_RECORD_ATTRIBUTES + internal +#else + public +#endif + static class IsExternalInit + { + } +} +#endif diff --git a/test/Lsp.Tests/Integration/RecursiveResolutionTests.cs b/test/Lsp.Integration.Tests/RecursiveResolutionTests.cs similarity index 100% rename from test/Lsp.Tests/Integration/RecursiveResolutionTests.cs rename to test/Lsp.Integration.Tests/RecursiveResolutionTests.cs diff --git a/test/Lsp.Tests/Integration/RenameTests.cs b/test/Lsp.Integration.Tests/RenameTests.cs similarity index 98% rename from test/Lsp.Tests/Integration/RenameTests.cs rename to test/Lsp.Integration.Tests/RenameTests.cs index 182df71f3..1ea72c999 100644 --- a/test/Lsp.Tests/Integration/RenameTests.cs +++ b/test/Lsp.Integration.Tests/RenameTests.cs @@ -32,7 +32,7 @@ public RenameTests(ITestOutputHelper outputHelper) : base(new JsonRpcTestOptions _rename = Substitute.For>>(); } - [RetryFact] + [Fact] public async Task Should_Handle_Rename_With_No_Value() { _prepareRename.Invoke(Arg.Any(), Arg.Any(), Arg.Any()) @@ -88,7 +88,7 @@ public async Task Should_Handle_Rename_With_No_Value() capability1.Should().BeSameAs(capability2); } - [RetryFact] + [Fact] public async Task Should_Handle_Prepare_Rename_With_No_Value() { _prepareRename.Invoke(Arg.Any(), Arg.Any(), Arg.Any()) @@ -106,7 +106,7 @@ public async Task Should_Handle_Prepare_Rename_With_No_Value() result.Should().BeNull(); } - [RetryFact] + [Fact] public async Task Should_Handle_Prepare_Rename_With_Range() { _prepareRename.Invoke(Arg.Any(), Arg.Any(), Arg.Any()) @@ -135,7 +135,7 @@ public async Task Should_Handle_Prepare_Rename_With_Range() result!.IsRange.Should().BeTrue(); } - [RetryFact] + [Fact] public async Task Should_Handle_Prepare_Rename_With_PlaceholderRange() { _prepareRename.Invoke(Arg.Any(), Arg.Any(), Arg.Any()) @@ -168,7 +168,7 @@ public async Task Should_Handle_Prepare_Rename_With_PlaceholderRange() result!.IsPlaceholderRange.Should().BeTrue(); } - [RetryFact] + [Fact] public async Task Should_Handle_Prepare_Rename_With_DefaultBehavior() { _prepareRename.Invoke(Arg.Any(), Arg.Any(), Arg.Any()) @@ -193,7 +193,7 @@ public async Task Should_Handle_Prepare_Rename_With_DefaultBehavior() result!.IsDefaultBehavior.Should().BeTrue(); } - [RetryFact] + [Fact] public async Task Should_Not_Register_Prepare_Rename() { var (client, _) = await Initialize(ClientOptionsAction, ServerOptionsAction); diff --git a/test/Lsp.Tests/Integration/RequestCancellationTests.cs b/test/Lsp.Integration.Tests/RequestCancellationTests.cs similarity index 98% rename from test/Lsp.Tests/Integration/RequestCancellationTests.cs rename to test/Lsp.Integration.Tests/RequestCancellationTests.cs index 59a666983..4e964bbcc 100644 --- a/test/Lsp.Tests/Integration/RequestCancellationTests.cs +++ b/test/Lsp.Integration.Tests/RequestCancellationTests.cs @@ -26,7 +26,7 @@ public RequestCancellationTests(ITestOutputHelper outputHelper) : base(new JsonR { } - [RetryFact] + [Fact] public async Task Should_Cancel_Pending_Requests() { var (client, _) = await Initialize(ConfigureClient, ConfigureServer); @@ -43,7 +43,7 @@ public async Task Should_Cancel_Pending_Requests() action.Should().Throw(); } - [RetryFact] + [Fact] public async Task Should_Abandon_Pending_Requests_For_Text_Changes() { var (client, _) = await Initialize(ConfigureClient, ConfigureServer); @@ -68,7 +68,7 @@ public async Task Should_Abandon_Pending_Requests_For_Text_Changes() action.Should().Throw(); } - [RetryFact] + [Fact] public async Task Should_Cancel_Requests_After_Timeout() { Func action = async () => { @@ -88,7 +88,7 @@ await client.TextDocument.RequestCompletion( action.Should().Throw(); } - [RetryFact] + [Fact] public void Should_Cancel_Requests_After_Timeout_without_Content_Modified() { Func action = async () => { @@ -108,7 +108,7 @@ await client.TextDocument.RequestCompletion( action.Should().Throw(); } - [RetryFact] + [Fact] public async Task Can_Publish_Diagnostics_Delayed() { var (_, server) = await Initialize( diff --git a/test/Lsp.Tests/Integration/ShowMessageTests.cs b/test/Lsp.Integration.Tests/ShowMessageTests.cs similarity index 98% rename from test/Lsp.Tests/Integration/ShowMessageTests.cs rename to test/Lsp.Integration.Tests/ShowMessageTests.cs index 09ba45fbb..92cd016af 100644 --- a/test/Lsp.Tests/Integration/ShowMessageTests.cs +++ b/test/Lsp.Integration.Tests/ShowMessageTests.cs @@ -24,7 +24,7 @@ public ShowMessageTests(ITestOutputHelper outputHelper) : base(new JsonRpcTestOp private readonly List _receivedMessages = new List(); - [RetryFact] + [Fact] public async Task Should_Show_Messages_Through_Window_Extension_Methods() { var (_, server) = await Initialize(ConfigureClient, ConfigureServer); @@ -53,7 +53,7 @@ public async Task Should_Show_Messages_Through_Window_Extension_Methods() _receivedMessages.Should().Contain(z => z.Type == MessageType.Log).And.Subject.Count(z => z.Type == MessageType.Log).Should().Be(3); } - [RetryFact] + [Fact] public async Task Should_Show_Messages_Through_Server_Extension_Methods() { var (_, server) = await Initialize(ConfigureClient, ConfigureServer); diff --git a/test/Lsp.Tests/Integration/TypedCallHierarchyTests.cs b/test/Lsp.Integration.Tests/TypedCallHierarchyTests.cs similarity index 99% rename from test/Lsp.Tests/Integration/TypedCallHierarchyTests.cs rename to test/Lsp.Integration.Tests/TypedCallHierarchyTests.cs index d98e35252..252524f53 100644 --- a/test/Lsp.Tests/Integration/TypedCallHierarchyTests.cs +++ b/test/Lsp.Integration.Tests/TypedCallHierarchyTests.cs @@ -30,7 +30,7 @@ public TypedCallHierarchyTests(ITestOutputHelper outputHelper) : base(new JsonRp { } - [RetryFact] + [Fact] public async Task Should_Aggregate_With_All_Related_Handlers() { var incomingHandlerA = Substitute.For, Task?>>>(); @@ -106,7 +106,7 @@ public async Task Should_Aggregate_With_All_Related_Handlers() outgoingHandlerB.Received(1).Invoke(Arg.Any>()); } - [RetryFact] + [Fact] public async Task Should_Resolve_With_Data_Capability() { var incomingHandler = Substitute.For, Task?>>>(); @@ -152,7 +152,7 @@ public async Task Should_Resolve_With_Data_Capability() outgoingHandler.Received(1).Invoke(Arg.Any>()); } - [RetryFact] + [Fact] public async Task Should_Resolve_With_Partial_Data_Capability() { var incomingHandler = Substitute.For, IObserver>>>(); diff --git a/test/Lsp.Tests/Integration/TypedCodeActionTests.cs b/test/Lsp.Integration.Tests/TypedCodeActionTests.cs similarity index 99% rename from test/Lsp.Tests/Integration/TypedCodeActionTests.cs rename to test/Lsp.Integration.Tests/TypedCodeActionTests.cs index a0245b1f4..02373175d 100644 --- a/test/Lsp.Tests/Integration/TypedCodeActionTests.cs +++ b/test/Lsp.Integration.Tests/TypedCodeActionTests.cs @@ -28,7 +28,7 @@ public TypedCodeActionTests(ITestOutputHelper outputHelper) : base(new JsonRpcTe { } - [RetryFact] + [Fact] public async Task Should_Aggregate_With_All_Related_Handlers() { var (client, _) = await Initialize( @@ -157,7 +157,7 @@ public async Task Should_Aggregate_With_All_Related_Handlers() actions.Length.Should().Be(3); } - [RetryFact] + [Fact] public async Task Should_Resolve_With_Data_Capability() { var (client, _) = await Initialize( @@ -203,7 +203,7 @@ public async Task Should_Resolve_With_Data_Capability() item.CodeAction!.Command!.Name.Should().Be("resolved"); } - [RetryFact] + [Fact] public async Task Should_Resolve_With_Partial_Data_Capability() { var (client, _) = await Initialize( @@ -248,7 +248,7 @@ public async Task Should_Resolve_With_Partial_Data_Capability() item.CodeAction!.Command!.Name.Should().Be("resolved"); } - [RetryFact] + [Fact] public async Task Should_Resolve_With_Data_CancellationToken() { var (client, _) = await Initialize( @@ -294,7 +294,7 @@ public async Task Should_Resolve_With_Data_CancellationToken() item.CodeAction!.Command!.Name.Should().Be("resolved"); } - [RetryFact] + [Fact] public async Task Should_Resolve_With_Partial_Data_CancellationToken() { var (client, _) = await Initialize( @@ -339,7 +339,7 @@ public async Task Should_Resolve_With_Partial_Data_CancellationToken() item.CodeAction!.Command!.Name.Should().Be("resolved"); } - [RetryFact] + [Fact] public async Task Should_Resolve_With_Data() { var (client, _) = await Initialize( @@ -385,7 +385,7 @@ public async Task Should_Resolve_With_Data() item.CodeAction!.Command!.Name.Should().Be("resolved"); } - [RetryFact] + [Fact] public async Task Should_Resolve_With_Partial_Data() { var (client, _) = await Initialize( @@ -431,7 +431,7 @@ public async Task Should_Resolve_With_Partial_Data() } - [RetryFact] + [Fact] public async Task Should_Resolve_Capability() { var (client, _) = await Initialize( @@ -467,7 +467,7 @@ public async Task Should_Resolve_Capability() item.CodeAction!.Command!.Name.Should().Be("resolved"); } - [RetryFact] + [Fact] public async Task Should_Resolve_Partial_Capability() { var (client, _) = await Initialize( @@ -502,7 +502,7 @@ public async Task Should_Resolve_Partial_Capability() item.CodeAction!.Command!.Name.Should().Be("resolved"); } - [RetryFact] + [Fact] public async Task Should_Resolve_CancellationToken() { var (client, _) = await Initialize( @@ -538,7 +538,7 @@ public async Task Should_Resolve_CancellationToken() item.CodeAction!.Command!.Name.Should().Be("resolved"); } - [RetryFact] + [Fact] public async Task Should_Resolve_Partial_CancellationToken() { var (client, _) = await Initialize( @@ -573,7 +573,7 @@ public async Task Should_Resolve_Partial_CancellationToken() item.CodeAction!.Command!.Name.Should().Be("resolved"); } - [RetryFact] + [Fact] public async Task Should_Resolve() { var (client, _) = await Initialize( @@ -609,7 +609,7 @@ public async Task Should_Resolve() item.CodeAction!.Command!.Name.Should().Be("resolved"); } - [RetryFact] + [Fact] public async Task Should_Resolve_Partial() { var (client, _) = await Initialize( diff --git a/test/Lsp.Tests/Integration/TypedCodeLensTests.cs b/test/Lsp.Integration.Tests/TypedCodeLensTests.cs similarity index 99% rename from test/Lsp.Tests/Integration/TypedCodeLensTests.cs rename to test/Lsp.Integration.Tests/TypedCodeLensTests.cs index 39e84ff7f..7e70e8bf0 100644 --- a/test/Lsp.Tests/Integration/TypedCodeLensTests.cs +++ b/test/Lsp.Integration.Tests/TypedCodeLensTests.cs @@ -29,7 +29,7 @@ public TypedCodeLensTests(ITestOutputHelper outputHelper) : base(new JsonRpcTest { } - [RetryFact] + [Fact] public async Task Should_Aggregate_With_All_Related_Handlers() { var (client, _) = await Initialize( @@ -142,7 +142,7 @@ public async Task Should_Aggregate_With_All_Related_Handlers() lens.Length.Should().Be(3); } - [RetryFact] + [Fact] public async Task Should_Resolve_With_Data_Capability() { var (client, _) = await Initialize( @@ -187,7 +187,7 @@ public async Task Should_Resolve_With_Data_Capability() item.Command!.Name.Should().Be("resolved"); } - [RetryFact] + [Fact] public async Task Should_Resolve_With_Partial_Data_Capability() { var (client, _) = await Initialize( @@ -231,7 +231,7 @@ public async Task Should_Resolve_With_Partial_Data_Capability() item.Command!.Name.Should().Be("resolved"); } - [RetryFact] + [Fact] public async Task Should_Resolve_With_Data_CancellationToken() { var (client, _) = await Initialize( @@ -276,7 +276,7 @@ public async Task Should_Resolve_With_Data_CancellationToken() item.Command!.Name.Should().Be("resolved"); } - [RetryFact] + [Fact] public async Task Should_Resolve_With_Partial_Data_CancellationToken() { var (client, _) = await Initialize( @@ -320,7 +320,7 @@ public async Task Should_Resolve_With_Partial_Data_CancellationToken() item.Command!.Name.Should().Be("resolved"); } - [RetryFact] + [Fact] public async Task Should_Resolve_With_Data() { var (client, _) = await Initialize( @@ -365,7 +365,7 @@ public async Task Should_Resolve_With_Data() item.Command!.Name.Should().Be("resolved"); } - [RetryFact] + [Fact] public async Task Should_Resolve_With_Partial_Data() { var (client, _) = await Initialize( @@ -410,7 +410,7 @@ public async Task Should_Resolve_With_Partial_Data() } - [RetryFact] + [Fact] public async Task Should_Resolve_Capability() { var (client, _) = await Initialize( @@ -445,7 +445,7 @@ public async Task Should_Resolve_Capability() item.Command!.Name.Should().Be("resolved"); } - [RetryFact] + [Fact] public async Task Should_Resolve_Partial_Capability() { var (client, _) = await Initialize( @@ -479,7 +479,7 @@ public async Task Should_Resolve_Partial_Capability() item.Command!.Name.Should().Be("resolved"); } - [RetryFact] + [Fact] public async Task Should_Resolve_CancellationToken() { var (client, _) = await Initialize( @@ -514,7 +514,7 @@ public async Task Should_Resolve_CancellationToken() item.Command!.Name.Should().Be("resolved"); } - [RetryFact] + [Fact] public async Task Should_Resolve_Partial_CancellationToken() { var (client, _) = await Initialize( @@ -548,7 +548,7 @@ public async Task Should_Resolve_Partial_CancellationToken() item.Command!.Name.Should().Be("resolved"); } - [RetryFact] + [Fact] public async Task Should_Resolve() { var (client, _) = await Initialize( @@ -583,7 +583,7 @@ public async Task Should_Resolve() item.Command!.Name.Should().Be("resolved"); } - [RetryFact] + [Fact] public async Task Should_Resolve_Partial() { var (client, _) = await Initialize( diff --git a/test/Lsp.Tests/Integration/TypedCompletionTests.cs b/test/Lsp.Integration.Tests/TypedCompletionTests.cs similarity index 99% rename from test/Lsp.Tests/Integration/TypedCompletionTests.cs rename to test/Lsp.Integration.Tests/TypedCompletionTests.cs index 957174ddc..e39147815 100644 --- a/test/Lsp.Tests/Integration/TypedCompletionTests.cs +++ b/test/Lsp.Integration.Tests/TypedCompletionTests.cs @@ -29,7 +29,7 @@ public TypedCompletionTests(ITestOutputHelper outputHelper) : base(new JsonRpcTe { } - [RetryFact] + [Fact] public async Task Should_Aggregate_With_All_Related_Handlers() { var (client, _) = await Initialize( @@ -142,7 +142,7 @@ public async Task Should_Aggregate_With_All_Related_Handlers() lens.Length.Should().Be(3); } - [RetryFact] + [Fact] public async Task Should_Resolve_With_Data_Capability() { var (client, _) = await Initialize( @@ -191,7 +191,7 @@ completionItem with { item.Detail.Should().Be("resolved"); } - [RetryFact] + [Fact] public async Task Should_Resolve_With_Partial_Data_Capability() { var (client, _) = await Initialize( @@ -234,7 +234,7 @@ public async Task Should_Resolve_With_Partial_Data_Capability() item.Detail.Should().Be("resolved"); } - [RetryFact] + [Fact] public async Task Should_Resolve_With_Data_CancellationToken() { var (client, _) = await Initialize( @@ -278,7 +278,7 @@ public async Task Should_Resolve_With_Data_CancellationToken() item.Detail.Should().Be("resolved"); } - [RetryFact] + [Fact] public async Task Should_Resolve_With_Partial_Data_CancellationToken() { var (client, _) = await Initialize( @@ -321,7 +321,7 @@ public async Task Should_Resolve_With_Partial_Data_CancellationToken() item.Detail.Should().Be("resolved"); } - [RetryFact] + [Fact] public async Task Should_Resolve_With_Data() { var (client, _) = await Initialize( @@ -365,7 +365,7 @@ public async Task Should_Resolve_With_Data() item.Detail.Should().Be("resolved"); } - [RetryFact] + [Fact] public async Task Should_Resolve_With_Partial_Data() { var (client, _) = await Initialize( @@ -409,7 +409,7 @@ public async Task Should_Resolve_With_Partial_Data() } - [RetryFact] + [Fact] public async Task Should_Resolve_Capability() { var (client, _) = await Initialize( @@ -441,7 +441,7 @@ public async Task Should_Resolve_Capability() item.Detail.Should().Be("resolved"); } - [RetryFact] + [Fact] public async Task Should_Resolve_Partial_Capability() { var (client, _) = await Initialize( @@ -472,7 +472,7 @@ public async Task Should_Resolve_Partial_Capability() item.Detail.Should().Be("resolved"); } - [RetryFact] + [Fact] public async Task Should_Resolve_CancellationToken() { var (client, _) = await Initialize( @@ -504,7 +504,7 @@ public async Task Should_Resolve_CancellationToken() item.Detail.Should().Be("resolved"); } - [RetryFact] + [Fact] public async Task Should_Resolve_Partial_CancellationToken() { var (client, _) = await Initialize( @@ -535,7 +535,7 @@ public async Task Should_Resolve_Partial_CancellationToken() item.Detail.Should().Be("resolved"); } - [RetryFact] + [Fact] public async Task Should_Resolve() { var (client, _) = await Initialize( @@ -567,7 +567,7 @@ public async Task Should_Resolve() item.Detail.Should().Be("resolved"); } - [RetryFact] + [Fact] public async Task Should_Resolve_Partial() { var (client, _) = await Initialize( diff --git a/test/Lsp.Tests/Integration/TypedDocumentLinkTests.cs b/test/Lsp.Integration.Tests/TypedDocumentLinkTests.cs similarity index 98% rename from test/Lsp.Tests/Integration/TypedDocumentLinkTests.cs rename to test/Lsp.Integration.Tests/TypedDocumentLinkTests.cs index de3da02c4..0cd4ebd54 100644 --- a/test/Lsp.Tests/Integration/TypedDocumentLinkTests.cs +++ b/test/Lsp.Integration.Tests/TypedDocumentLinkTests.cs @@ -27,7 +27,7 @@ public TypedDocumentLinkTests(ITestOutputHelper outputHelper) : base(new JsonRpc { } - [RetryFact] + [Fact] public async Task Should_Aggregate_With_All_Related_Handlers() { var (client, _) = await Initialize( @@ -136,7 +136,7 @@ public async Task Should_Aggregate_With_All_Related_Handlers() lens.Length.Should().Be(3); } - [RetryFact] + [Fact] public async Task Should_Resolve_With_Data_Capability() { var (client, _) = await Initialize( @@ -177,7 +177,7 @@ public async Task Should_Resolve_With_Data_Capability() item.Tooltip.Should().Be("resolved"); } - [RetryFact] + [Fact] public async Task Should_Resolve_With_Partial_Data_Capability() { var (client, _) = await Initialize( @@ -217,7 +217,7 @@ public async Task Should_Resolve_With_Partial_Data_Capability() item.Tooltip.Should().Be("resolved"); } - [RetryFact] + [Fact] public async Task Should_Resolve_With_Data_CancellationToken() { var (client, _) = await Initialize( @@ -258,7 +258,7 @@ public async Task Should_Resolve_With_Data_CancellationToken() item.Tooltip.Should().Be("resolved"); } - [RetryFact] + [Fact] public async Task Should_Resolve_With_Partial_Data_CancellationToken() { var (client, _) = await Initialize( @@ -298,7 +298,7 @@ public async Task Should_Resolve_With_Partial_Data_CancellationToken() item.Tooltip.Should().Be("resolved"); } - [RetryFact] + [Fact] public async Task Should_Resolve_With_Data() { var (client, _) = await Initialize( @@ -339,7 +339,7 @@ public async Task Should_Resolve_With_Data() item.Tooltip.Should().Be("resolved"); } - [RetryFact] + [Fact] public async Task Should_Resolve_With_Partial_Data() { var (client, _) = await Initialize( @@ -380,7 +380,7 @@ public async Task Should_Resolve_With_Partial_Data() } - [RetryFact] + [Fact] public async Task Should_Resolve_Capability() { var (client, _) = await Initialize( @@ -411,7 +411,7 @@ public async Task Should_Resolve_Capability() item.Tooltip.Should().Be("resolved"); } - [RetryFact] + [Fact] public async Task Should_Resolve_Partial_Capability() { var (client, _) = await Initialize( @@ -441,7 +441,7 @@ public async Task Should_Resolve_Partial_Capability() item.Tooltip.Should().Be("resolved"); } - [RetryFact] + [Fact] public async Task Should_Resolve_CancellationToken() { var (client, _) = await Initialize( @@ -472,7 +472,7 @@ public async Task Should_Resolve_CancellationToken() item.Tooltip.Should().Be("resolved"); } - [RetryFact] + [Fact] public async Task Should_Resolve_Partial_CancellationToken() { var (client, _) = await Initialize( @@ -502,7 +502,7 @@ public async Task Should_Resolve_Partial_CancellationToken() item.Tooltip.Should().Be("resolved"); } - [RetryFact] + [Fact] public async Task Should_Resolve() { var (client, _) = await Initialize( @@ -533,7 +533,7 @@ public async Task Should_Resolve() item.Tooltip.Should().Be("resolved"); } - [RetryFact] + [Fact] public async Task Should_Resolve_Partial() { var (client, _) = await Initialize( diff --git a/test/Lsp.Tests/Integration/WorkspaceFolderTests.cs b/test/Lsp.Integration.Tests/WorkspaceFolderTests.cs similarity index 97% rename from test/Lsp.Tests/Integration/WorkspaceFolderTests.cs rename to test/Lsp.Integration.Tests/WorkspaceFolderTests.cs index 38361ea77..2c716f85c 100644 --- a/test/Lsp.Tests/Integration/WorkspaceFolderTests.cs +++ b/test/Lsp.Integration.Tests/WorkspaceFolderTests.cs @@ -33,7 +33,7 @@ public WorkspaceFolderTests(ITestOutputHelper outputHelper) : base( { } - [RetryFact] + [Fact] public async Task Should_Disable_If_Not_Supported() { var (_, server) = await Initialize( @@ -47,14 +47,14 @@ public async Task Should_Disable_If_Not_Supported() folders.Should().BeEmpty(); } - [RetryFact] + [Fact] public async Task Should_Enable_If_Supported() { var (_, server) = await Initialize(ConfigureClient, ConfigureServer); server.WorkspaceFolderManager.IsSupported.Should().Be(true); } - [RetryFact] + [Fact] public async Task Should_Add_A_Workspace_Folder() { var (client, server) = await Initialize(ConfigureClient, ConfigureServer); @@ -71,7 +71,7 @@ public async Task Should_Add_A_Workspace_Folder() folders[0].Folder.Name.Should().Be(nameof(Should_Add_A_Workspace_Folder)); } - [RetryFact] + [Fact] public async Task Should_Allow_Null_Response() { var (client, server) = await Initialize( @@ -84,7 +84,7 @@ public async Task Should_Allow_Null_Response() a.Should().NotThrow(); } - [RetryFact] + [Fact] public async Task Should_Have_Workspace_Folder_At_Startup() { var (_, server) = await Initialize(options => { options.WithWorkspaceFolder("/abcd/", nameof(Should_Have_Workspace_Folder_At_Startup)); }, ConfigureServer); @@ -93,7 +93,7 @@ public async Task Should_Have_Workspace_Folder_At_Startup() folder.Name.Should().Be(nameof(Should_Have_Workspace_Folder_At_Startup)); } - [RetryFact] + [Fact] public async Task Should_Remove_Workspace_Folder_by_name() { var (client, server) = await Initialize(options => { options.WithWorkspaceFolder("/abcd/", nameof(Should_Remove_Workspace_Folder_by_name)); }, ConfigureServer); @@ -113,7 +113,7 @@ public async Task Should_Remove_Workspace_Folder_by_name() folders[0].Folder.Name.Should().Be(nameof(Should_Remove_Workspace_Folder_by_name)); } - [RetryFact] + [Fact] public async Task Should_Remove_Workspace_Folder_by_uri() { var (client, server) = await Initialize(options => { options.WithWorkspaceFolder("/abcd/", nameof(Should_Remove_Workspace_Folder_by_uri)); }, ConfigureServer); @@ -133,7 +133,7 @@ public async Task Should_Remove_Workspace_Folder_by_uri() folders[0].Folder.Name.Should().Be(nameof(Should_Remove_Workspace_Folder_by_uri)); } - [RetryFact] + [Fact] public async Task Should_Handle_Null_Workspace_Folders() { var workspaceLanguageServer = Substitute.For(); @@ -154,7 +154,7 @@ public async Task Should_Handle_Null_Workspace_Folders() await started.OnStarted(languageServer, CancellationToken); } - [RetryFact] + [Fact] public async Task Should_Handle_Null_Workspace_Folders_On_Refresh() { var workspaceLanguageServer = Substitute.For(); diff --git a/test/Lsp.Tests/CompletionItemKindTests.cs b/test/Lsp.Tests/CompletionItemKindTests.cs index 27581d245..08bd91c48 100644 --- a/test/Lsp.Tests/CompletionItemKindTests.cs +++ b/test/Lsp.Tests/CompletionItemKindTests.cs @@ -1,5 +1,4 @@ using FluentAssertions; -using Lsp.Tests.Integration.Fixtures; using OmniSharp.Extensions.LanguageServer.Protocol; using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; using OmniSharp.Extensions.LanguageServer.Protocol.Models; diff --git a/test/TestingUtils/RetryFactDiscoverer.cs b/test/TestingUtils/RetryFactDiscoverer.cs index 7db21d8ff..280837c86 100644 --- a/test/TestingUtils/RetryFactDiscoverer.cs +++ b/test/TestingUtils/RetryFactDiscoverer.cs @@ -23,13 +23,13 @@ public IEnumerable Discover(ITestFrameworkDiscoveryOptions disco { testCase = new ExecutionErrorTestCase(_messageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod, - "[RetryFact] methods are not allowed to have parameters. Did you mean to use [RetryTheory]?"); + "[Fact]//[RetryFact] methods are not allowed to have parameters. Did you mean to use [RetryTheory]?"); } else if (testMethod.Method.IsGenericMethodDefinition) { testCase = new ExecutionErrorTestCase(_messageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod, - "[RetryFact] methods are not allowed to be generic."); + "[Fact]//[RetryFact] methods are not allowed to be generic."); } else {