diff --git a/build/Packages.props b/build/Packages.props index 58f17d70d3..a61c7199b4 100644 --- a/build/Packages.props +++ b/build/Packages.props @@ -62,8 +62,8 @@ - - + + diff --git a/tests/OmniSharp.Lsp.Tests/AbstractLanguageServerTestBase.cs b/tests/OmniSharp.Lsp.Tests/AbstractLanguageServerTestBase.cs index dd96b84198..8a46508ab0 100644 --- a/tests/OmniSharp.Lsp.Tests/AbstractLanguageServerTestBase.cs +++ b/tests/OmniSharp.Lsp.Tests/AbstractLanguageServerTestBase.cs @@ -98,7 +98,7 @@ public async Task Restart(IConfiguration configuration = null, IDictionary()) .Build(); await InitializeAsync(); @@ -106,7 +106,7 @@ public async Task Restart(IConfiguration configuration = null, IDictionary + var(client, configurationProvider) = await InitializeClientWithConfiguration(x => { x.WithCapability(new WorkspaceEditCapability() { @@ -117,107 +117,109 @@ public async Task InitializeAsync() ResourceOperationKind.Create, ResourceOperationKind.Delete, ResourceOperationKind.Rename } }); - }); - Client.Register(c => c.OnApplyWorkspaceEdit(async @params => - { - if (@params.Edit?.Changes != null) + x.OnApplyWorkspaceEdit(async @params => { - foreach (var change in @params.Edit.Changes) + if (@params.Edit?.Changes != null) { - var changes = change.Value - .Select(change => new LinePositionSpanTextChange() - { - NewText = change.NewText, - StartColumn = Convert.ToInt32(change.Range.Start.Character), - StartLine = Convert.ToInt32(change.Range.Start.Line), - EndColumn = Convert.ToInt32(change.Range.End.Character), - EndLine = Convert.ToInt32(change.Range.End.Line), - }) - .ToArray(); - - await OmniSharpTestHost.Workspace.BufferManager.UpdateBufferAsync(new UpdateBufferRequest() + foreach (var change in @params.Edit.Changes) { - FileName = LanguageServerProtocol.Helpers.FromUri(change.Key), - Changes = changes - }); + var changes = change.Value + .Select(change => new LinePositionSpanTextChange() + { + NewText = change.NewText, + StartColumn = Convert.ToInt32(change.Range.Start.Character), + StartLine = Convert.ToInt32(change.Range.Start.Line), + EndColumn = Convert.ToInt32(change.Range.End.Character), + EndLine = Convert.ToInt32(change.Range.End.Line), + }) + .ToArray(); + + await OmniSharpTestHost.Workspace.BufferManager.UpdateBufferAsync(new UpdateBufferRequest() + { + FileName = LanguageServerProtocol.Helpers.FromUri(change.Key), + Changes = changes + }); + } } - } - else if (@params.Edit?.DocumentChanges != null) - { - foreach (var change in @params.Edit.DocumentChanges) + else if (@params.Edit?.DocumentChanges != null) { - if (change.IsTextDocumentEdit) + foreach (var change in @params.Edit.DocumentChanges) { - var contentChanges = change.TextDocumentEdit.Edits.ToArray(); - if (contentChanges.Length == 1 && contentChanges[0].Range == null) + if (change.IsTextDocumentEdit) { - var c = contentChanges[0]; - await OmniSharpTestHost.Workspace.BufferManager.UpdateBufferAsync( - new UpdateBufferRequest() - { - FileName = LanguageServerProtocol.Helpers.FromUri(change.TextDocumentEdit - .TextDocument.Uri), - Buffer = c.NewText - }); - } - else - { - var changes = contentChanges - .Select(change => new LinePositionSpanTextChange() - { - NewText = change.NewText, - StartColumn = Convert.ToInt32(change.Range.Start.Character), - StartLine = Convert.ToInt32(change.Range.Start.Line), - EndColumn = Convert.ToInt32(change.Range.End.Character), - EndLine = Convert.ToInt32(change.Range.End.Line), - }) - .ToArray(); + var contentChanges = change.TextDocumentEdit.Edits.ToArray(); + if (contentChanges.Length == 1 && contentChanges[0].Range == null) + { + var c = contentChanges[0]; + await OmniSharpTestHost.Workspace.BufferManager.UpdateBufferAsync( + new UpdateBufferRequest() + { + FileName = LanguageServerProtocol.Helpers.FromUri(change.TextDocumentEdit + .TextDocument.Uri), + Buffer = c.NewText + }); + } + else + { + var changes = contentChanges + .Select(change => new LinePositionSpanTextChange() + { + NewText = change.NewText, + StartColumn = Convert.ToInt32(change.Range.Start.Character), + StartLine = Convert.ToInt32(change.Range.Start.Line), + EndColumn = Convert.ToInt32(change.Range.End.Character), + EndLine = Convert.ToInt32(change.Range.End.Line), + }) + .ToArray(); - await OmniSharpTestHost.Workspace.BufferManager.UpdateBufferAsync( - new UpdateBufferRequest() - { - FileName = LanguageServerProtocol.Helpers.FromUri(change.TextDocumentEdit - .TextDocument.Uri), - Changes = changes - }); + await OmniSharpTestHost.Workspace.BufferManager.UpdateBufferAsync( + new UpdateBufferRequest() + { + FileName = LanguageServerProtocol.Helpers.FromUri(change.TextDocumentEdit + .TextDocument.Uri), + Changes = changes + }); + } } - } - if (change.IsRenameFile) - { - var documents = - OmniSharpTestHost.Workspace.GetDocuments(change.RenameFile.OldUri.GetFileSystemPath()); - foreach (var oldDocument in documents) + if (change.IsRenameFile) { - var text = await oldDocument.GetTextAsync(); - var newFilePath = change.RenameFile.NewUri.GetFileSystemPath(); - var newFileName = Path.GetFileName(newFilePath); - OmniSharpTestHost.Workspace.TryApplyChanges( - OmniSharpTestHost.Workspace.CurrentSolution - .RemoveDocument(oldDocument.Id) - .AddDocument( - DocumentId.CreateNewId(oldDocument.Project.Id, newFileName), - newFileName, - text, - oldDocument.Folders, - newFilePath - ) - ); + var documents = + OmniSharpTestHost.Workspace.GetDocuments( + change.RenameFile.OldUri.GetFileSystemPath()); + foreach (var oldDocument in documents) + { + var text = await oldDocument.GetTextAsync(); + var newFilePath = change.RenameFile.NewUri.GetFileSystemPath(); + var newFileName = Path.GetFileName(newFilePath); + OmniSharpTestHost.Workspace.TryApplyChanges( + OmniSharpTestHost.Workspace.CurrentSolution + .RemoveDocument(oldDocument.Id) + .AddDocument( + DocumentId.CreateNewId(oldDocument.Project.Id, newFileName), + newFileName, + text, + oldDocument.Folders, + newFilePath + ) + ); + } } } } - } - await ClientEvents.SettleNext(); + await ClientEvents.SettleNext(); + + return new ApplyWorkspaceEditResponse() + { + Applied = true + }; + }); + }); + Client = client; - return new ApplyWorkspaceEditResponse() - { - Applied = true - }; - })); await startUpTask; - Configuration = new ConfigurationProvider(Server, Client, CancellationToken); - Client.Register(x => x.AddHandler(Configuration)); + Configuration = new ConfigurationProvider(Server, Client, configurationProvider, CancellationToken); } public Task DisposeAsync() diff --git a/tests/OmniSharp.Lsp.Tests/ConfigurationProvider.cs b/tests/OmniSharp.Lsp.Tests/ConfigurationProvider.cs index 687c448234..3543062801 100644 --- a/tests/OmniSharp.Lsp.Tests/ConfigurationProvider.cs +++ b/tests/OmniSharp.Lsp.Tests/ConfigurationProvider.cs @@ -7,29 +7,29 @@ using MediatR; using Microsoft.Extensions.Configuration; using Newtonsoft.Json.Linq; +using OmniSharp.Extensions.LanguageProtocol.Testing; using OmniSharp.Extensions.LanguageServer.Protocol; using OmniSharp.Extensions.LanguageServer.Protocol.Client; -using OmniSharp.Extensions.LanguageServer.Protocol.Models; using OmniSharp.Extensions.LanguageServer.Protocol.Server; -using OmniSharp.Extensions.LanguageServer.Protocol.Workspace; -using TestUtility; namespace OmniSharp.Lsp.Tests { - public class ConfigurationProvider: IConfigurationHandler + public class ConfigurationProvider { private readonly ILanguageServer _server; private readonly ILanguageClient _client; + private readonly TestConfigurationProvider _configurationProvider; private readonly CancellationToken _cancellationToken; - private readonly ConcurrentDictionary<(string section, DocumentUri scope), IConfiguration> _scopedConfigurations = - new ConcurrentDictionary<(string section, DocumentUri scope), IConfiguration>(); - - public ConfigurationProvider(ILanguageServer server, ILanguageClient client, + public ConfigurationProvider( + ILanguageServer server, + ILanguageClient client, + TestConfigurationProvider configurationProvider, CancellationToken cancellationToken) { _server = server; _client = client; + _configurationProvider = configurationProvider; _cancellationToken = cancellationToken; } @@ -54,7 +54,7 @@ public Task Update(string section, DocumentUri documentUri, IDictionary configuration); + _configurationProvider.Update(section, documentUri, configuration); return TriggerChange(); } @@ -65,128 +65,13 @@ public Task Reset(string section) public Task Reset(string section, DocumentUri documentUri) { - _scopedConfigurations.TryRemove((section, documentUri), out _); - _client.Workspace.DidChangeConfiguration(new DidChangeConfigurationParams()); + _configurationProvider.Reset(section, documentUri); return TriggerChange(); } - private IConfiguration Get(ConfigurationItem configurationItem) - { - if (_scopedConfigurations.TryGetValue( - (configurationItem.Section, configurationItem.ScopeUri), - out var configuration) - ) - { - return new Microsoft.Extensions.Configuration.ConfigurationBuilder() - .AddConfiguration(configuration, false) - .Build(); - } - - return new Microsoft.Extensions.Configuration.ConfigurationBuilder().Build(); - } - private async Task TriggerChange() { - _client.Workspace.DidChangeConfiguration(new DidChangeConfigurationParams()); await _server.Configuration.WaitForChange(_cancellationToken); } - - Task> IRequestHandler>. Handle(ConfigurationParams request, CancellationToken cancellationToken) - { - var results = new List(); - foreach (var item in request.Items) - { - var config = Get(item); - results.Add(Parse(config.AsEnumerable(true).Where(x => x.Value != null))); - } - - return Task.FromResult>(results); - } - - private JObject Parse(IEnumerable> values) - { - if (values == null) - { - throw new ArgumentNullException(nameof(values)); - } - - var result = new JObject(); - foreach (var item in values) - { - var keys = item.Key.Split(new [] { ":" }, StringSplitOptions.RemoveEmptyEntries); - var prop = keys.Last(); - JToken root = result; - - // This produces a simple look ahead - var zippedKeys = keys - .Zip(keys.Skip(1), (prev, current) => (prev, current)); - - foreach (var (key, next) in zippedKeys) - { - if (int.TryParse(next, out var value)) - { - root = SetValueToToken(root, key, new JArray()); - } - else - { - root = SetValueToToken(root, key, new JObject()); - } - } - - SetValueToToken(root, prop, new JValue(item.Value)); - } - return result; - } - private T SetValueToToken(JToken root, string key, T value) - where T : JToken - { - var currentValue = GetValueFromToken(root, key); - if (currentValue == null || currentValue.Type == JTokenType.Null) - { - if (root is JArray arr) - { - if (int.TryParse(key, out var index)) - { - if (arr.Count <= index) - { - while (arr.Count < index) - arr.Add(null!); - arr.Add(value); - } - else - { - arr[index] = value; - } - - return value; - } - } - else - { - root[key] = value; - return value; - } - } - - if (root is JArray arr2 && int.TryParse(key, out var i)) - { - return (T)arr2[i]; - } - return root[key] as T; - } - - private static JToken GetValueFromToken(JToken root, string key) - { - if (root is JArray arr) - { - if (int.TryParse(key, out var index)) - { - if (arr.Count <= index) return null; - return arr[index]; - } - throw new IndexOutOfRangeException(key); - } - return root[key]; - } } } diff --git a/tests/OmniSharp.Lsp.Tests/LanguageServerFoundationFacts.cs b/tests/OmniSharp.Lsp.Tests/LanguageServerFoundationFacts.cs index 2e115b6a19..e0e4dd83a7 100644 --- a/tests/OmniSharp.Lsp.Tests/LanguageServerFoundationFacts.cs +++ b/tests/OmniSharp.Lsp.Tests/LanguageServerFoundationFacts.cs @@ -1,5 +1,8 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; +using System.Reflection; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -7,6 +10,9 @@ using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; using Newtonsoft.Json.Linq; +using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.JsonRpc.Server; +using OmniSharp.Extensions.LanguageProtocol.Testing; using OmniSharp.Extensions.LanguageServer.Protocol.Models; using OmniSharp.Extensions.LanguageServer.Protocol.Workspace; using OmniSharp.Options; @@ -46,5 +52,63 @@ await Task.WhenAll( Assert.Equal(12, options.CurrentValue.FormattingOptions.IndentationSize); Assert.True(options.CurrentValue.RenameOptions.RenameOverloads); } + + /// + /// This ensures the server has registered all handlers + /// + /// + [Theory] + [ClassData(typeof(RegistersAllKnownOmniSharpHandlersData))] + public void Registers_all_known_OmniSharp_handlers(string method) + { + var descriptor = Server.GetRequiredService().Descriptors + .FirstOrDefault(z => z.Method == $"o#{method}".ToLowerInvariant()); + Assert.NotNull(descriptor); + } + + /// + /// This ensures that the client can call the methods (the server has properly registered it) + /// + /// + /// + [Theory] + [ClassData(typeof(RegistersAllKnownOmniSharpHandlersData))] + public async Task All_known_OmniSharp_handlers_are_callable(string method) + { + try + { + var cts = CancellationTokenSource.CreateLinkedTokenSource(CancellationToken); + cts.CancelAfter(TimeSpan.FromSeconds(1)); + await Client.SendRequest($"o#{method}".ToLowerInvariant(), new object()).ReturningVoid(cts.Token); + } + catch (MethodNotSupportedException) + { + Assert.False(true, "Method should be supported!"); + } + catch (Exception e) + { + Logger.LogWarning(e, "got exception"); + } + } + + class RegistersAllKnownOmniSharpHandlersData : TheoryData + { + public RegistersAllKnownOmniSharpHandlersData() + { + var v1Fields = typeof(OmniSharpEndpoints).GetFields(BindingFlags.Public | BindingFlags.Static) + .Where(z => z.FieldType == typeof(string)); + foreach (var field in v1Fields) + { + Add(field.GetValue(null) as string); + } + + var v2Fields = typeof(OmniSharpEndpoints.V2).GetFields(BindingFlags.Public | BindingFlags.Static) + .Where(z => z.FieldType == typeof(string)); + foreach (var field in v2Fields) + { + Add(field.GetValue(null) as string); + } + } + } } } diff --git a/tests/OmniSharp.Lsp.Tests/OmniSharpCodeActionHandlerFacts.cs b/tests/OmniSharp.Lsp.Tests/OmniSharpCodeActionHandlerFacts.cs index 5d9055e3f7..296335b981 100644 --- a/tests/OmniSharp.Lsp.Tests/OmniSharpCodeActionHandlerFacts.cs +++ b/tests/OmniSharp.Lsp.Tests/OmniSharpCodeActionHandlerFacts.cs @@ -95,7 +95,7 @@ public class c {public c() {Guid.NewGuid();}}"; var response = (await RunRefactoringAsync(code, "Remove Unnecessary Usings", isAnalyzersEnabled: roslynAnalyzersEnabled)).Single(); - var updatedText = await OmniSharpTestHost.Workspace.GetDocument(response.FileName).GetTextAsync(); + var updatedText = await OmniSharpTestHost.Workspace.GetDocument(response.FileName).GetTextAsync(CancellationToken); AssertUtils.AssertIgnoringIndent(expected, updatedText.ToString()); } @@ -199,7 +199,7 @@ private static void NewMethod() var response = (await RunRefactoringAsync(code, "Extract Method", isAnalyzersEnabled: roslynAnalyzersEnabled)) .Single(); - var updatedText = await OmniSharpTestHost.Workspace.GetDocument(response.FileName).GetTextAsync(); + var updatedText = await OmniSharpTestHost.Workspace.GetDocument(response.FileName).GetTextAsync(CancellationToken); AssertUtils.AssertIgnoringIndent(expected, updatedText.ToString()); } @@ -224,7 +224,7 @@ await Client.ExecuteCommand(Command.Create("omnisharp/executeCodeAction") }), CancellationToken); var updatedDocument = OmniSharpTestHost.Workspace.GetDocument(Path.Combine(Path.GetDirectoryName(document.FilePath), "Z.cs")); - var updateDocumentText = await updatedDocument.GetTextAsync(); + var updateDocumentText = await updatedDocument.GetTextAsync(CancellationToken); Assert.Equal(@"namespace ConsoleApplication { @@ -296,7 +296,7 @@ private async Task> FindRefactoringsAsync(string code, Context = new CodeActionContext() { }, Range = LanguageServerProtocol.Helpers.ToRange(range), TextDocument = new TextDocumentIdentifier(bufferPath) - }); + }, CancellationToken); return response.Where(z => z.IsCodeAction).Select(z => z.CodeAction); } @@ -309,7 +309,7 @@ private async Task> RunRefactoringsAsync(string code, Comm OmniSharpTestHost.AddFilesToWorkspace(testFile); - await Client.Workspace.ExecuteCommand(command); + await Client.Workspace.ExecuteCommand(command, CancellationToken); return new[] {testFile}; } diff --git a/tests/TestUtility/ConfigurationHelpers.cs b/tests/TestUtility/ConfigurationHelpers.cs index 52f5afb034..f0ccf86f3c 100644 --- a/tests/TestUtility/ConfigurationHelpers.cs +++ b/tests/TestUtility/ConfigurationHelpers.cs @@ -19,32 +19,5 @@ public static IConfiguration ToConfiguration(this IEnumerable(observer => - { - var reloadToken = configuration.GetReloadToken(); - return reloadToken.RegisterChangeCallback(_ => - { - observer.OnNext(Unit.Default); - observer.OnCompleted(); - }, Unit.Default); - }).ToTask(cancellationToken); - } - - public static Task WaitForChange(this IOptionsMonitor options, CancellationToken cancellationToken) - { - if (options == null) throw new ArgumentNullException(nameof(options)); - return Observable.Create(observer => - { - return options.OnChange(_ => - { - observer.OnNext(Unit.Default); - observer.OnCompleted(); - }); - }).ToTask(cancellationToken); - } } }