diff --git a/Common/Advanced/EarlyAccess/IConnectionSendViewer.cs b/Common/Advanced/EarlyAccess/IConnectionSendViewer.cs index eeb1a641..651ae1e9 100644 --- a/Common/Advanced/EarlyAccess/IConnectionSendViewer.cs +++ b/Common/Advanced/EarlyAccess/IConnectionSendViewer.cs @@ -3,8 +3,7 @@ using System.Threading.Tasks; namespace MirrorSharp.Advanced.EarlyAccess { - internal interface IConnectionSendViewer - { + internal interface IConnectionSendViewer { Task ViewDuringSendAsync(string messageTypeName, ReadOnlyMemory message, IWorkSession session, CancellationToken cancellationToken); } -} +} \ No newline at end of file diff --git a/Common/Internal/Connection.cs b/Common/Internal/Connection.cs index 0ad0643c..377a7b7e 100644 --- a/Common/Internal/Connection.cs +++ b/Common/Internal/Connection.cs @@ -11,27 +11,26 @@ using MirrorSharp.Internal.Results; namespace MirrorSharp.Internal { - internal class Connection : ICommandResultSender, IDisposable { + internal class Connection : IConnection, ICommandResultSender { public static int InputBufferSize => 4096; - private readonly ArrayPool _bufferPool; + private readonly ArrayPool _inputBufferPool; private readonly IConnectionSendViewer? _sendViewer; private readonly WebSocket _socket; private readonly WorkSession _session; private readonly ImmutableArray _handlers; private readonly byte[] _inputBuffer; - private readonly FastUtf8JsonWriter _messageWriter; + private readonly ConnectionMessageWriter _messageWriter; private readonly IConnectionOptions? _options; private readonly IExceptionLogger? _exceptionLogger; - private string? _currentMessageTypeName; - public Connection( WebSocket socket, WorkSession session, + ArrayPool inputBufferPool, ImmutableArray handlers, - ArrayPool bufferPool, + ConnectionMessageWriter messageWriter, IConnectionSendViewer? sendViewer, IExceptionLogger? exceptionLogger, IConnectionOptions? options @@ -39,12 +38,12 @@ public Connection( _socket = socket; _session = session; _handlers = handlers; - _messageWriter = new FastUtf8JsonWriter(bufferPool); + _messageWriter = messageWriter; _options = options; _sendViewer = sendViewer; _exceptionLogger = exceptionLogger; - _bufferPool = bufferPool; - _inputBuffer = bufferPool.Rent(InputBufferSize); + _inputBufferPool = inputBufferPool; + _inputBuffer = inputBufferPool.Rent(InputBufferSize); } public bool IsConnected => _socket.State == WebSocketState.Open; @@ -138,23 +137,19 @@ private ICommandHandler ResolveHandler(byte commandId) { } private Task SendErrorAsync(string message, CancellationToken cancellationToken) { - var writer = StartJsonMessage("error"); - writer.WriteProperty("message", message); + _messageWriter.WriteErrorStart(message); return SendJsonMessageAsync(cancellationToken); } - private FastUtf8JsonWriter StartJsonMessage(string messageTypeName) { - _messageWriter.Reset(); - _messageWriter.WriteStartObject(); - _messageWriter.WriteProperty("type", messageTypeName); - _currentMessageTypeName = messageTypeName; - return _messageWriter; - } - private Task SendJsonMessageAsync(CancellationToken cancellationToken) { - _messageWriter.WriteEndObject(); + _messageWriter.WriteMessageEnd(); - var viewTask = _sendViewer?.ViewDuringSendAsync(_currentMessageTypeName!, _messageWriter.WrittenSegment, _session, cancellationToken); + var viewTask = _sendViewer?.ViewDuringSendAsync( + _messageWriter.CurrentMessageTypeName!, + _messageWriter.WrittenSegment, + _session, + cancellationToken + ); var sendTask = _socket.SendAsync( _messageWriter.WrittenSegment, WebSocketMessageType.Text, true, cancellationToken @@ -172,12 +167,24 @@ private async Task WhenAll(Task first, Task second) { } public void Dispose() { - _bufferPool.Return(_inputBuffer); - _messageWriter.Dispose(); - _session.Dispose(); + try { + try { + _inputBufferPool.Return(_inputBuffer); + } + finally { + _messageWriter.Dispose(); + } + } + finally { + _session.Dispose(); + } + } + + IFastJsonWriter ICommandResultSender.StartJsonMessage(string messageTypeName) { + _messageWriter.WriteMessageStart(messageTypeName); + return _messageWriter.JsonWriter; } - IFastJsonWriter ICommandResultSender.StartJsonMessage(string messageTypeName) => StartJsonMessage(messageTypeName); Task ICommandResultSender.SendJsonMessageAsync(CancellationToken cancellationToken) => SendJsonMessageAsync(cancellationToken); } } diff --git a/Common/Internal/ConnectionMessageWriter.cs b/Common/Internal/ConnectionMessageWriter.cs new file mode 100644 index 00000000..69a2d213 --- /dev/null +++ b/Common/Internal/ConnectionMessageWriter.cs @@ -0,0 +1,37 @@ +using System; +using MirrorSharp.Advanced; + +namespace MirrorSharp.Internal { + internal class ConnectionMessageWriter : IDisposable { + private readonly FastUtf8JsonWriter _jsonWriter; + private string? _currentMessageTypeName; + + public ConnectionMessageWriter(FastUtf8JsonWriter jsonWriter) { + _jsonWriter = jsonWriter; + } + + public void WriteMessageStart(string messageTypeName) { + _jsonWriter.Reset(); + _jsonWriter.WriteStartObject(); + _jsonWriter.WriteProperty("type", messageTypeName); + _currentMessageTypeName = messageTypeName; + } + + public void WriteErrorStart(string message) { + WriteMessageStart("error"); + _jsonWriter.WriteProperty("message", message); + } + + public void WriteMessageEnd() { + _jsonWriter.WriteEndObject(); + } + + public ArraySegment WrittenSegment => _jsonWriter.WrittenSegment; + public FastUtf8JsonWriter JsonWriter => _jsonWriter; + public string? CurrentMessageTypeName => _currentMessageTypeName; + + public void Dispose() { + _jsonWriter.Dispose(); + } + } +} diff --git a/Common/Internal/FastUtf8JsonWriter.cs b/Common/Internal/FastUtf8JsonWriter.cs index c6e39508..28e815c7 100644 --- a/Common/Internal/FastUtf8JsonWriter.cs +++ b/Common/Internal/FastUtf8JsonWriter.cs @@ -36,7 +36,7 @@ public FastUtf8JsonWriter(ArrayPool bufferPool) { _encoder = Encoding.UTF8.GetEncoder(); } - public ArraySegment WrittenSegment => new ArraySegment(_buffer, 0, _position); + public ArraySegment WrittenSegment => new (_buffer, 0, _position); public void WriteStartObject() { WriteStartValue(); diff --git a/Common/Internal/IConnection.cs b/Common/Internal/IConnection.cs new file mode 100644 index 00000000..6998d106 --- /dev/null +++ b/Common/Internal/IConnection.cs @@ -0,0 +1,11 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace MirrorSharp.Internal { + internal interface IConnection : IDisposable { + bool IsConnected { get; } + + Task ReceiveAndProcessAsync(CancellationToken cancellationToken); + } +} diff --git a/Common/Internal/IWorkSessionTracker.cs b/Common/Internal/IWorkSessionTracker.cs new file mode 100644 index 00000000..71839101 --- /dev/null +++ b/Common/Internal/IWorkSessionTracker.cs @@ -0,0 +1,5 @@ +namespace MirrorSharp.Internal { + internal interface IWorkSessionTracker { + void TrackNewWorkSession(WorkSession session); + } +} diff --git a/Common/Internal/ImmutableExtensionServices.cs b/Common/Internal/ImmutableExtensionServices.cs index 5b65b77c..eaf00057 100644 --- a/Common/Internal/ImmutableExtensionServices.cs +++ b/Common/Internal/ImmutableExtensionServices.cs @@ -9,7 +9,8 @@ public ImmutableExtensionServices( IRoslynSourceTextGuard? roslynSourceTextGuard, IRoslynCompilationGuard? roslynCompilationGuard, IConnectionSendViewer? connectionSendViewer, - IExceptionLogger? exceptionLogger + IExceptionLogger? exceptionLogger, + IWorkSessionTracker? sessionTracker ) { SetOptionsFromClient = setOptionsFromClient; SlowUpdate = slowUpdate; @@ -17,6 +18,7 @@ public ImmutableExtensionServices( RoslynCompilationGuard = roslynCompilationGuard; ConnectionSendViewer = connectionSendViewer; ExceptionLogger = exceptionLogger; + SessionTracker = sessionTracker; } public ISetOptionsFromClientExtension? SetOptionsFromClient { get; } @@ -25,5 +27,6 @@ public ImmutableExtensionServices( public IRoslynCompilationGuard? RoslynCompilationGuard { get; } public IConnectionSendViewer? ConnectionSendViewer { get; } public IExceptionLogger? ExceptionLogger { get; } + public IWorkSessionTracker? SessionTracker { get; } } } diff --git a/Common/Internal/MiddlewareBase.cs b/Common/Internal/MiddlewareBase.cs index 7486596a..0b413e18 100644 --- a/Common/Internal/MiddlewareBase.cs +++ b/Common/Internal/MiddlewareBase.cs @@ -92,10 +92,19 @@ Task ExecuteAsync(char commandId, string command) { protected async Task WebSocketLoopAsync(WebSocket socket, CancellationToken cancellationToken) { WorkSession? session = null; - Connection? connection = null; + FastUtf8JsonWriter? messageJsonWriter = null; + ConnectionMessageWriter? messageWriter = null; + IConnection? connection = null; try { - session = StartWorkSession(); - connection = new Connection(socket, session, _handlers, ArrayPool.Shared, _extensions.ConnectionSendViewer, _extensions.ExceptionLogger, _options); + messageJsonWriter = new FastUtf8JsonWriter(ArrayPool.Shared); + messageWriter = new ConnectionMessageWriter(messageJsonWriter); + try { + session = StartWorkSession(); + connection = new Connection(socket, session, ArrayPool.Shared, _handlers, messageWriter, _extensions.ConnectionSendViewer, _extensions.ExceptionLogger, _options); + } + catch (Exception ex) { + connection = new StartupFailedConnection(socket, ex, ArrayPool.Shared, messageWriter, _options); + } while (connection.IsConnected) { try { @@ -111,7 +120,17 @@ protected async Task WebSocketLoopAsync(WebSocket socket, CancellationToken canc connection.Dispose(); } else { - session?.Dispose(); + try { + if (messageWriter != null) { + messageWriter.Dispose(); + } + else { + messageJsonWriter?.Dispose(); + } + } + finally { + session?.Dispose(); + } } } } diff --git a/Common/Internal/Roslyn/RoslynLanguageBase.cs b/Common/Internal/Roslyn/RoslynLanguageBase.cs index 440fb44d..32978f6a 100644 --- a/Common/Internal/Roslyn/RoslynLanguageBase.cs +++ b/Common/Internal/Roslyn/RoslynLanguageBase.cs @@ -46,16 +46,27 @@ IRoslynLanguageOptions options } private CompositionHost CreateCompositionHost(string featuresAssemblyName, string workspacesAssemblyName) { + var roslynInternalsAssembly = RoslynInternals.GetInternalsAssemblySlow(); var types = new[] { RoslynAssemblies.MicrosoftCodeAnalysisWorkspaces, RoslynAssemblies.MicrosoftCodeAnalysisFeatures, Assembly.Load(new AssemblyName(featuresAssemblyName)), Assembly.Load(new AssemblyName(workspacesAssemblyName)), - RoslynInternals.GetInternalsAssemblySlow() + roslynInternalsAssembly }.SelectMany(a => a.DefinedTypes).Where(ShouldConsiderForHostServices); var configuration = new ContainerConfiguration().WithParts(types); - return configuration.CreateContainer(); + try { + return configuration.CreateContainer(); + } + catch (MissingMethodException ex) { + throw new Exception( + $"Failed to initialize MirrorSharp {GetType().Name}.{Environment.NewLine}" + + $" Microsoft.CodeAnalysis {RoslynAssemblies.MicrosoftCodeAnalysis.GetName().Version}.{Environment.NewLine}" + + $" {roslynInternalsAssembly.GetName().Name}", + ex + ); + } } protected virtual bool ShouldConsiderForHostServices(Type type) diff --git a/Common/Internal/StartupFailedConnection.cs b/Common/Internal/StartupFailedConnection.cs new file mode 100644 index 00000000..ebbd4274 --- /dev/null +++ b/Common/Internal/StartupFailedConnection.cs @@ -0,0 +1,69 @@ + +using System; +using System.Buffers; +using System.Net.WebSockets; +using System.Threading; +using System.Threading.Tasks; + +namespace MirrorSharp.Internal; + +internal class StartupFailedConnection : IConnection { + public static int InputBufferSize => 4096; + + private readonly ArrayPool _bufferPool; + private readonly WebSocket _socket; + private readonly Exception _startupException; + private readonly byte[] _inputBuffer; + + private readonly ConnectionMessageWriter _messageWriter; + private readonly IConnectionOptions? _options; + + public StartupFailedConnection( + WebSocket socket, + Exception startupException, + ArrayPool bufferPool, + ConnectionMessageWriter messageWriter, + IConnectionOptions? options + ) { + _socket = socket; + _startupException = startupException; + _messageWriter = messageWriter; + _options = options; + _bufferPool = bufferPool; + _inputBuffer = bufferPool.Rent(InputBufferSize); + } + + public bool IsConnected => _socket.State == WebSocketState.Open; + + public async Task ReceiveAndProcessAsync(CancellationToken cancellationToken) { + var first = await _socket.ReceiveAsync(new ArraySegment(_inputBuffer), cancellationToken).ConfigureAwait(false); + if (first.MessageType == WebSocketMessageType.Close) { + await _socket.CloseAsync(first.CloseStatus ?? WebSocketCloseStatus.Empty, first.CloseStatusDescription, cancellationToken).ConfigureAwait(false); + return; + } + + if (!first.EndOfMessage) + await ReceiveToEndAsync(cancellationToken).ConfigureAwait(false); + + var error = (_options?.IncludeExceptionDetails ?? false) + ? _startupException.ToString() + : "A server error has occurred during startup."; + + _messageWriter.WriteErrorStart(error); + _messageWriter.WriteMessageEnd(); + await _socket.SendAsync( + _messageWriter.WrittenSegment, + WebSocketMessageType.Text, true, cancellationToken + ); + } + + private async Task ReceiveToEndAsync(CancellationToken cancellationToken) { + while (!(await _socket.ReceiveAsync(new ArraySegment(_inputBuffer), cancellationToken).ConfigureAwait(false)).EndOfMessage) { + } + } + + public void Dispose() { + _bufferPool.Return(_inputBuffer); + _messageWriter.Dispose(); + } +} \ No newline at end of file diff --git a/Internal.Roslyn411/MirrorSharpDiagnosticAnalyzerService.cs b/Internal.Roslyn411/MirrorSharpDiagnosticAnalyzerService.cs index 2c8f6818..8dc4748d 100644 --- a/Internal.Roslyn411/MirrorSharpDiagnosticAnalyzerService.cs +++ b/Internal.Roslyn411/MirrorSharpDiagnosticAnalyzerService.cs @@ -21,7 +21,7 @@ internal class MirrorSharpDiagnosticAnalyzerService : IDiagnosticAnalyzerService Task> IDiagnosticAnalyzerService.GetDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, Func>? getDocumentIds, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) => throw new NotSupportedException(); Task> IDiagnosticAnalyzerService.GetDiagnosticsForSpanAsync(TextDocument document, TextSpan? range, Func? shouldIncludeDiagnostic, bool includeCompilerDiagnostics, bool includeSuppressedDiagnostics, ICodeActionRequestPriorityProvider priorityProvider, DiagnosticKind diagnosticKind, bool isExplicit, CancellationToken cancellationToken) => throw new NotSupportedException(); Task> IDiagnosticAnalyzerService.GetProjectDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) => throw new NotSupportedException(); - void IDiagnosticAnalyzerService.RequestDiagnosticRefresh() => throw new NotImplementedException(); + void IDiagnosticAnalyzerService.RequestDiagnosticRefresh() => throw new NotSupportedException(); Task<(ImmutableArray diagnostics, bool upToDate)> IDiagnosticAnalyzerService.TryGetDiagnosticsForSpanAsync(TextDocument document, TextSpan range, Func? shouldIncludeDiagnostic, bool includeSuppressedDiagnostics, ICodeActionRequestPriorityProvider priorityProvider, DiagnosticKind diagnosticKind, bool isExplicit, CancellationToken cancellationToken) => throw new NotSupportedException(); } } diff --git a/Testing/Internal/HandlerTestArgument.cs b/Testing/Internal/HandlerTestArgument.cs index ee67d7aa..24d6c14e 100644 --- a/Testing/Internal/HandlerTestArgument.cs +++ b/Testing/Internal/HandlerTestArgument.cs @@ -39,6 +39,10 @@ public static implicit operator HandlerTestArgument(byte[][] data) { return new HandlerTestArgument(data); } + public byte[] ToBytes(char commandId) { + + } + public AsyncData ToAsyncData(char commandId) { var nextIndex = 1; diff --git a/Testing/Internal/TestMiddleware.cs b/Testing/Internal/TestMiddleware.cs new file mode 100644 index 00000000..ef4aac61 --- /dev/null +++ b/Testing/Internal/TestMiddleware.cs @@ -0,0 +1,16 @@ +using System.Net.WebSockets; +using System.Threading.Tasks; +using System.Threading; +using MirrorSharp.Internal; + +namespace MirrorSharp.Testing.Internal { + internal class TestMiddleware : MiddlewareBase { + public TestMiddleware(LanguageManager languageManager, IMiddlewareOptions options, ImmutableExtensionServices extensions) + : base(languageManager, options, extensions) { + } + + public Task WebSocketLoopAsync(TestWebSocket socket, CancellationToken cancellationToken) { + return base.WebSocketLoopAsync(socket, cancellationToken); + } + } +} diff --git a/Testing/Internal/TestSocket.cs b/Testing/Internal/TestSocket.cs new file mode 100644 index 00000000..55308107 --- /dev/null +++ b/Testing/Internal/TestSocket.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.WebSockets; +using System.Threading; +using System.Threading.Tasks; + +namespace MirrorSharp.Testing.Internal { + internal class TestSocket : WebSocket { + private readonly Queue> _dataToReceive = new(); + + public override WebSocketCloseStatus? CloseStatus => throw new NotSupportedException(); + + public override string CloseStatusDescription => throw new NotSupportedException(); + + public override WebSocketState State => throw new NotSupportedException(); + + public override string SubProtocol => throw new NotSupportedException(); + + public override void Abort() { + throw new NotSupportedException(); + } + + public override Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken) { + throw new NotSupportedException(); + } + + public override Task CloseOutputAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken) { + throw new NotSupportedException(); + } + + public override void Dispose() { + throw new NotSupportedException(); + } + + public override async Task ReceiveAsync(ArraySegment buffer, CancellationToken cancellationToken) { + var dataStream = _dataToReceive.Peek(); + if (dataStream == null) + throw new Exception("No data was set up to be received"); + + var count = await dataStream.ReadAsync(buffer.Array, buffer.Offset, buffer.Count); + if (dataStream.Position == dataStream.Length) + _dataToReceive.Dequeue(); + return new WebSocketReceiveResult(count, WebSocketMessageType.Text, dataStream.Position == dataStream.Length); + } + + public override Task SendAsync(ArraySegment buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken) { + throw new NotSupportedException(); + } + + public void SetupToReceive(byte[] data) { + _dataToReceive.Enqueue(new MemoryStream(data)); + } + } +} diff --git a/Testing/MirrorSharpServices.cs b/Testing/MirrorSharpServices.cs index e6067a70..c9d986cc 100644 --- a/Testing/MirrorSharpServices.cs +++ b/Testing/MirrorSharpServices.cs @@ -18,12 +18,15 @@ public class MirrorSharpServices { /// Defines a called for any unhandled exception. public IExceptionLogger? ExceptionLogger { get; set; } + internal IWorkSessionTracker? SessionTracker { get; set; } + internal ImmutableExtensionServices ToImmutable() { return new ImmutableExtensionServices( SetOptionsFromClient, SlowUpdate, RoslynSourceTextGuard, RoslynCompilationGuard, ConnectionSendViewer, - ExceptionLogger + ExceptionLogger, + SessionTracker ); } } diff --git a/Testing/MirrorSharpTestDriver.cs b/Testing/MirrorSharpTestDriver.cs index 9f7f3764..928fc8ea 100644 --- a/Testing/MirrorSharpTestDriver.cs +++ b/Testing/MirrorSharpTestDriver.cs @@ -16,25 +16,30 @@ // ReSharper disable HeapView.ObjectAllocation namespace MirrorSharp.Testing { - public class MirrorSharpTestDriver { + public class MirrorSharpTestDriver: IWorkSessionTracker { private static readonly MirrorSharpOptions DefaultOptions = new(); - private static readonly MirrorSharpServices DefaultServices = new(); private static readonly ConcurrentDictionary LanguageManagerCache = new(); private readonly TestMiddleware _middleware; private readonly MirrorSharpServices _services; + private WorkSession? _session; + private MirrorSharpTestDriver(MirrorSharpOptions? options = null, MirrorSharpServices? services = null, string languageName = LanguageNames.CSharp) { + if (services?.SessionTracker != null) + throw new ArgumentException("Custom session trackers are not supported.", nameof(services)); + options ??= DefaultOptions; - services ??= DefaultServices; + services ??= new(); + + services.SessionTracker = this; _services = services; - var language = GetLanguageManager(options).GetLanguage(languageName); - _middleware = new TestMiddleware(options, services); - Session = new WorkSession(language, options, services.ToImmutable()); + _middleware = new TestMiddleware(GetLanguageManager(options), options, services.ToImmutable()); } - - internal WorkSession Session { get; } + + internal WorkSession Session => _session + ?? throw new InvalidOperationException("Work session has not been captured yet."); // Obsolete: will be removed in the next major version. However no changes are required on caller side. public static MirrorSharpTestDriver New() { @@ -119,7 +124,9 @@ internal async Task SendWithRequiredResultAsync(char commandId internal async Task SendWithOptionalResultAsync(char commandId, HandlerTestArgument? argument = null) where TResult : class { - var sender = new StubCommandResultSender(Session, _services.ConnectionSendViewer); + var socket = new TestSocket(); + socket.SetupToReceive(argument?.ToAsyncData(commandId)); + await _middleware.GetHandler(commandId).ExecuteAsync(argument?.ToAsyncData(commandId) ?? AsyncData.Empty, Session, sender, CancellationToken.None); return sender.LastMessageJson != null ? JsonConvert.DeserializeObject(sender.LastMessageJson) @@ -139,9 +146,11 @@ private static LanguageManager GetLanguageManager(MirrorSharpOptions options) { return LanguageManagerCache.GetOrAdd(options, _ => new LanguageManager(options)); } - private class TestMiddleware : MiddlewareBase { - public TestMiddleware(MirrorSharpOptions options, MirrorSharpServices services) : base(GetLanguageManager(options), options, services.ToImmutable()) { - } + void IWorkSessionTracker.TrackNewWorkSession(WorkSession session) { + if (_session != null) + throw new InvalidOperationException("Attempted to capture work session twice for the same driver."); + + _session = session; } } } diff --git a/Testing/Properties/AssemblyInfo.cs b/Testing/Properties/AssemblyInfo.cs index be7267bb..fd97f453 100644 --- a/Testing/Properties/AssemblyInfo.cs +++ b/Testing/Properties/AssemblyInfo.cs @@ -1,4 +1,6 @@ +using System.Net.WebSockets; using System.Runtime.InteropServices; +using SourceMock; // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from @@ -6,4 +8,6 @@ [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("0814c69a-e4a5-4fc9-bc57-415f8295b6b6")] \ No newline at end of file +[assembly: Guid("0814c69a-e4a5-4fc9-bc57-415f8295b6b6")] + +[assembly: GenerateMocksForTypes(typeof(WebSocket))] \ No newline at end of file diff --git a/Testing/Results/ErrorResult.cs b/Testing/Results/ErrorResult.cs new file mode 100644 index 00000000..cc250ee3 --- /dev/null +++ b/Testing/Results/ErrorResult.cs @@ -0,0 +1,9 @@ +namespace MirrorSharp.Testing.Results { + internal class ErrorResult { + public ErrorResult(string message) { + Message = message; + } + + public string Message { get; } + } +} diff --git a/Testing/Testing.csproj b/Testing/Testing.csproj index 3533a5ab..dd83467b 100644 --- a/Testing/Testing.csproj +++ b/Testing/Testing.csproj @@ -14,6 +14,9 @@ + + all + diff --git a/Testing/packages.lock.json b/Testing/packages.lock.json index f23b080b..2b8f8728 100644 --- a/Testing/packages.lock.json +++ b/Testing/packages.lock.json @@ -38,6 +38,15 @@ "resolved": "13.0.3", "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" }, + "SourceMock": { + "type": "Direct", + "requested": "[0.10.0, )", + "resolved": "0.10.0", + "contentHash": "Dd/YMTTRZSDR34HgVEgfMoZfOTId3Ns0vehFcdLVwMuKLgMcg7/4Oj80qX112np9siR2C4vL3qfT1I9ahzxu9Q==", + "dependencies": { + "System.Collections.Immutable": "5.0.0" + } + }, "System.Buffers": { "type": "Direct", "requested": "[4.5.1, )", @@ -135,8 +144,11 @@ }, "System.Collections.Immutable": { "type": "Transitive", - "resolved": "1.5.0", - "contentHash": "EXKiDFsChZW0RjrZ4FYHu9aW6+P4MCgEDCklsVseRfhoO0F+dXeMSsMRAlVXIo06kGJ/zv+2w1a2uc2+kxxSaQ==" + "resolved": "5.0.0", + "contentHash": "FXkLXiK0sVVewcso0imKQoOxjoPAj42R8HtjjbSjVPAzwDfzoyoznWxgA3c38LDbN9SJux1xXoXYAhz98j7r2g==", + "dependencies": { + "System.Memory": "4.5.4" + } }, "System.Composition": { "type": "Transitive", diff --git a/Tests.RoslynLatest/Tests.RoslynLatest.csproj b/Tests.RoslynLatest/Tests.RoslynLatest.csproj index 0fc19bf9..8a685763 100644 --- a/Tests.RoslynLatest/Tests.RoslynLatest.csproj +++ b/Tests.RoslynLatest/Tests.RoslynLatest.csproj @@ -18,9 +18,9 @@ - - - + + + diff --git a/Tests.RoslynLatest/packages.lock.json b/Tests.RoslynLatest/packages.lock.json index 5d11f89e..c61d6f60 100644 --- a/Tests.RoslynLatest/packages.lock.json +++ b/Tests.RoslynLatest/packages.lock.json @@ -10,15 +10,15 @@ }, "Microsoft.CodeAnalysis": { "type": "Direct", - "requested": "[4.11.0-3.24281.8, )", - "resolved": "4.11.0-3.24281.8", - "contentHash": "nTeWHnaE29rJFCxA9xWGANUrE7vFrETF9PUwIl+idk0tU5UokEJE+HLRk8leO9XYzv6+JzQKZy38S9Q61KNEqg==", + "requested": "[4.12.0-1.24355.3, )", + "resolved": "4.12.0-1.24355.3", + "contentHash": "QfcgEM/C4eLmATp+8X1n37yHe1Fve/T9XZsrbT6OwuCEisE5UNyNPMCVcR7UxDj7VSA59EWyleMWUGUuZMGjjA==", "dependencies": { "Humanizer.Core": "2.14.1", "Microsoft.Bcl.AsyncInterfaces": "8.0.0", "Microsoft.CodeAnalysis.Analyzers": "3.3.4", - "Microsoft.CodeAnalysis.CSharp.Workspaces": "[4.11.0-3.24281.8]", - "Microsoft.CodeAnalysis.VisualBasic.Workspaces": "[4.11.0-3.24281.8]", + "Microsoft.CodeAnalysis.CSharp.Workspaces": "[4.12.0-1.24355.3]", + "Microsoft.CodeAnalysis.VisualBasic.Workspaces": "[4.12.0-1.24355.3]", "System.Buffers": "4.5.1", "System.Collections.Immutable": "8.0.0", "System.Composition": "8.0.0", @@ -34,19 +34,19 @@ }, "Microsoft.CodeAnalysis.CSharp.Features": { "type": "Direct", - "requested": "[4.11.0-3.24281.8, )", - "resolved": "4.11.0-3.24281.8", - "contentHash": "yeh+lAIIUKK8sCEUnAV12ZazPBQv8TOCSniEfA4LbiP1r/JOhHso4d/IRZrV+j2W4WpSZ+VhMDSTTVBFpcfdpQ==", + "requested": "[4.12.0-1.24355.3, )", + "resolved": "4.12.0-1.24355.3", + "contentHash": "OxRUoYYZT8FU/W/qBDpI/Zk34BShtUKcdLe5v0RgTE2PUd8bqQ8Xo3++bJLjga/mAwcqokZXSngq772CYvS/og==", "dependencies": { "Humanizer.Core": "2.14.1", "Microsoft.CodeAnalysis.AnalyzerUtilities": "3.3.0", "Microsoft.CodeAnalysis.Analyzers": "3.3.4", - "Microsoft.CodeAnalysis.CSharp": "[4.11.0-3.24281.8]", - "Microsoft.CodeAnalysis.CSharp.Workspaces": "[4.11.0-3.24281.8]", - "Microsoft.CodeAnalysis.Common": "[4.11.0-3.24281.8]", + "Microsoft.CodeAnalysis.CSharp": "[4.12.0-1.24355.3]", + "Microsoft.CodeAnalysis.CSharp.Workspaces": "[4.12.0-1.24355.3]", + "Microsoft.CodeAnalysis.Common": "[4.12.0-1.24355.3]", "Microsoft.CodeAnalysis.Elfie": "1.0.0", - "Microsoft.CodeAnalysis.Features": "[4.11.0-3.24281.8]", - "Microsoft.CodeAnalysis.Workspaces.Common": "[4.11.0-3.24281.8]", + "Microsoft.CodeAnalysis.Features": "[4.12.0-1.24355.3]", + "Microsoft.CodeAnalysis.Workspaces.Common": "[4.12.0-1.24355.3]", "Microsoft.DiaSymReader": "2.0.0", "System.Collections.Immutable": "8.0.0", "System.Composition": "8.0.0", @@ -62,16 +62,16 @@ }, "Microsoft.CodeAnalysis.VisualBasic.Features": { "type": "Direct", - "requested": "[4.11.0-3.24281.8, )", - "resolved": "4.11.0-3.24281.8", - "contentHash": "rUp8WdTHv7ZToAMAqI5LQgKgULUtFJOe/oX4o6OUFYPYL5lQ/U+pyv5rA4rHKEm0XQa5ut5bptGmAq85ywUJvQ==", + "requested": "[4.12.0-1.24355.3, )", + "resolved": "4.12.0-1.24355.3", + "contentHash": "A3XaNteS2UByU7jz95jwJyEstJ1oLuK6QkuQYzwPFo/bKsh6Uk5Y3SWu4dCE++G2mX6z0xyR6idTdVwKzZEq5w==", "dependencies": { "Humanizer.Core": "2.14.1", "Microsoft.CodeAnalysis.AnalyzerUtilities": "3.3.0", "Microsoft.CodeAnalysis.Analyzers": "3.3.4", "Microsoft.CodeAnalysis.Elfie": "1.0.0", - "Microsoft.CodeAnalysis.Features": "[4.11.0-3.24281.8]", - "Microsoft.CodeAnalysis.VisualBasic.Workspaces": "[4.11.0-3.24281.8]", + "Microsoft.CodeAnalysis.Features": "[4.12.0-1.24355.3]", + "Microsoft.CodeAnalysis.VisualBasic.Workspaces": "[4.12.0-1.24355.3]", "Microsoft.DiaSymReader": "2.0.0", "System.Collections.Immutable": "8.0.0", "System.Composition": "8.0.0", @@ -188,8 +188,8 @@ }, "Microsoft.CodeAnalysis.Common": { "type": "Transitive", - "resolved": "4.11.0-3.24281.8", - "contentHash": "zayItp/PxshJ0+/USsFTdfUsuRfFT+D2N/MpTITeYoc0g1pRzpoZtJr4/IMsEMMBg2o5CV5c5Bt5dm+EYF+4cw==", + "resolved": "4.12.0-1.24355.3", + "contentHash": "/C6TU2CpbhhRQ7af8j7J4S1wWc4seoOhX2wUZ+gN3hQb0cOcJUo4azEDqVfmXKMZqrox/MSka02SwmbGyQeSVA==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "3.3.4", "System.Collections.Immutable": "8.0.0", @@ -198,25 +198,25 @@ }, "Microsoft.CodeAnalysis.CSharp": { "type": "Transitive", - "resolved": "4.11.0-3.24281.8", - "contentHash": "wbKTu8hTWK7Ap1R5dA1wtGI8ZU8t4Umw/Fdqo+uFn2hJ29hhtTh9N4zSl2PKfl8pHvR1WLZrBH9YQ91YDw129Q==", + "resolved": "4.12.0-1.24355.3", + "contentHash": "zPKBn85tYyUG989qYvwlOx7ThT4IOR8ea0buSy30ulTeLBIyO1a0ytUBySeDz6p92i1f65PtRgaqkwSUoljVqg==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "3.3.4", - "Microsoft.CodeAnalysis.Common": "[4.11.0-3.24281.8]", + "Microsoft.CodeAnalysis.Common": "[4.12.0-1.24355.3]", "System.Collections.Immutable": "8.0.0", "System.Reflection.Metadata": "8.0.0" } }, "Microsoft.CodeAnalysis.CSharp.Workspaces": { "type": "Transitive", - "resolved": "4.11.0-3.24281.8", - "contentHash": "SR/5hb6yXlPD5QMGclYFBBle997IX+qbhV03xdTSn2R5/XinZQA9D/CYhbdP2Nt86/hkiiRjj/8B8ffz2DtDzw==", + "resolved": "4.12.0-1.24355.3", + "contentHash": "9hK3T/IaRVzkQaw/ToZatufIZSiR0CxKBlV5KX4z9rbumUvaJ8i9/zpoyy1qAJ8Vr4L2hV4KmNLT44nqXS/BSg==", "dependencies": { "Humanizer.Core": "2.14.1", "Microsoft.CodeAnalysis.Analyzers": "3.3.4", - "Microsoft.CodeAnalysis.CSharp": "[4.11.0-3.24281.8]", - "Microsoft.CodeAnalysis.Common": "[4.11.0-3.24281.8]", - "Microsoft.CodeAnalysis.Workspaces.Common": "[4.11.0-3.24281.8]", + "Microsoft.CodeAnalysis.CSharp": "[4.12.0-1.24355.3]", + "Microsoft.CodeAnalysis.Common": "[4.12.0-1.24355.3]", + "Microsoft.CodeAnalysis.Workspaces.Common": "[4.12.0-1.24355.3]", "System.Collections.Immutable": "8.0.0", "System.Composition": "8.0.0", "System.IO.Pipelines": "8.0.0", @@ -235,16 +235,16 @@ }, "Microsoft.CodeAnalysis.Features": { "type": "Transitive", - "resolved": "4.11.0-3.24281.8", - "contentHash": "WwcD0nUwOGF44pdVvk2/KWeCEB0mjE81OnYIFoNClLukiTV9qKcU+HlAEEqyGPUtFf+pI0hyYMEYEU34AkITww==", + "resolved": "4.12.0-1.24355.3", + "contentHash": "RudbdLBdA4J/7ry+iB5ia/G4ruJLAeetJfVNnyhqx7dle3mcaGMGQnlOpNJ0P7+KXb3yWk7sH4TaKlg5ec0R9w==", "dependencies": { "Humanizer.Core": "2.14.1", "Microsoft.CodeAnalysis.AnalyzerUtilities": "3.3.0", "Microsoft.CodeAnalysis.Analyzers": "3.3.4", - "Microsoft.CodeAnalysis.Common": "[4.11.0-3.24281.8]", + "Microsoft.CodeAnalysis.Common": "[4.12.0-1.24355.3]", "Microsoft.CodeAnalysis.Elfie": "1.0.0", - "Microsoft.CodeAnalysis.Scripting.Common": "[4.11.0-3.24281.8]", - "Microsoft.CodeAnalysis.Workspaces.Common": "[4.11.0-3.24281.8]", + "Microsoft.CodeAnalysis.Scripting.Common": "[4.12.0-1.24355.3]", + "Microsoft.CodeAnalysis.Workspaces.Common": "[4.12.0-1.24355.3]", "Microsoft.DiaSymReader": "2.0.0", "System.Collections.Immutable": "8.0.0", "System.Composition": "8.0.0", @@ -260,36 +260,36 @@ }, "Microsoft.CodeAnalysis.Scripting.Common": { "type": "Transitive", - "resolved": "4.11.0-3.24281.8", - "contentHash": "0D9KsQrQEPY7p0DKuy8P3T+1dXqTVALV4SUTmrz9VNjZcWGKFjmWH88jUCXA6EiA0lyxYn8hAsqcued+2wDMSw==", + "resolved": "4.12.0-1.24355.3", + "contentHash": "H8WZdNdJNhwtWPiFm9RNsBJ5wEhbkYR9DI951LHyByNjd90+Pvlj6EaD0qkM9WfLrNsXLbfLe+tUeJ8DtzjoGw==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "3.3.4", - "Microsoft.CodeAnalysis.Common": "[4.11.0-3.24281.8]", + "Microsoft.CodeAnalysis.Common": "[4.12.0-1.24355.3]", "System.Collections.Immutable": "8.0.0", "System.Reflection.Metadata": "8.0.0" } }, "Microsoft.CodeAnalysis.VisualBasic": { "type": "Transitive", - "resolved": "4.11.0-3.24281.8", - "contentHash": "kpezU7WMF9fh6YDYt9rlO/q5qjl1UeubHD8ChtL49E8uwwfZxNAWoSuf0cChDMCJb5PS/vvEI4i6QoIsPdZuzQ==", + "resolved": "4.12.0-1.24355.3", + "contentHash": "oB8ggPuG23Wdp9zhvLxgVrPOIGbJIEF3G8SEoX73oj3GJXPpEQ0fB7nnlJv2AEql5MnTXT8kTcWgHvZaWyviIw==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "3.3.4", - "Microsoft.CodeAnalysis.Common": "[4.11.0-3.24281.8]", + "Microsoft.CodeAnalysis.Common": "[4.12.0-1.24355.3]", "System.Collections.Immutable": "8.0.0", "System.Reflection.Metadata": "8.0.0" } }, "Microsoft.CodeAnalysis.VisualBasic.Workspaces": { "type": "Transitive", - "resolved": "4.11.0-3.24281.8", - "contentHash": "O9GipefmUP+x4nYQAZWjdXVXwBcD3Noo1QxZteagIZGVMwwCp496ig/i7qbxBxVINP8uVzyMM9h4/ULqntslig==", + "resolved": "4.12.0-1.24355.3", + "contentHash": "k9x62Z/nJm1S4fJzg5ub/bBFU5SSws/B4AvBqcxxp9FB7SIIJbAbcB+7qlDyxbNj4m9DEqyb3jLs63XPCl6dJg==", "dependencies": { "Humanizer.Core": "2.14.1", "Microsoft.CodeAnalysis.Analyzers": "3.3.4", - "Microsoft.CodeAnalysis.Common": "[4.11.0-3.24281.8]", - "Microsoft.CodeAnalysis.VisualBasic": "[4.11.0-3.24281.8]", - "Microsoft.CodeAnalysis.Workspaces.Common": "[4.11.0-3.24281.8]", + "Microsoft.CodeAnalysis.Common": "[4.12.0-1.24355.3]", + "Microsoft.CodeAnalysis.VisualBasic": "[4.12.0-1.24355.3]", + "Microsoft.CodeAnalysis.Workspaces.Common": "[4.12.0-1.24355.3]", "System.Collections.Immutable": "8.0.0", "System.Composition": "8.0.0", "System.IO.Pipelines": "8.0.0", @@ -299,12 +299,12 @@ }, "Microsoft.CodeAnalysis.Workspaces.Common": { "type": "Transitive", - "resolved": "4.11.0-3.24281.8", - "contentHash": "oervXcOho9XEZUrfhMGcII1hijbJ/81oy2YLAy5fRmbhEmKohDYeYSy4FlDZpeVkMQCzNwrKB9yetkoB7L2Pww==", + "resolved": "4.12.0-1.24355.3", + "contentHash": "2NxKHyzOi3x7ygIbRoTvY8TXgBIdmaxb696xNGzNfhJS2+iP8RL+jhkFnmyggIBhc79Lzg66Ju4Ba64KuW0D0Q==", "dependencies": { "Humanizer.Core": "2.14.1", "Microsoft.CodeAnalysis.Analyzers": "3.3.4", - "Microsoft.CodeAnalysis.Common": "[4.11.0-3.24281.8]", + "Microsoft.CodeAnalysis.Common": "[4.12.0-1.24355.3]", "System.Collections.Immutable": "8.0.0", "System.Composition": "8.0.0", "System.IO.Pipelines": "8.0.0", diff --git a/Tests/ConnectionTests.cs b/Tests/ConnectionTests.cs index 4d8dd127..8d09433c 100644 --- a/Tests/ConnectionTests.cs +++ b/Tests/ConnectionTests.cs @@ -61,8 +61,9 @@ public async Task ReceiveAndProcessAsync_HandlesLongMessage() { private Connection CreateConnection(WebSocket socketMock, WorkSession session, CommandHandlerMock handler) { return new Connection( - socketMock, session, CreateCommandHandlers(handler), - ArrayPool.Shared, + socketMock, session, ArrayPool.Shared, + CreateCommandHandlers(handler), + new ConnectionMessageWriter(new FastUtf8JsonWriter(ArrayPool.Shared)), sendViewer: null, exceptionLogger: null, options: null ); } diff --git a/Tests/Features/StartupFailedTests.cs b/Tests/Features/StartupFailedTests.cs new file mode 100644 index 00000000..0e420319 --- /dev/null +++ b/Tests/Features/StartupFailedTests.cs @@ -0,0 +1,21 @@ +using MirrorSharp.Testing; +using Xunit; +using System.Threading.Tasks; +using MirrorSharp.Internal; +using MirrorSharp.Testing.Results; + +namespace MirrorSharp.Tests.Features { + public class StartupFailedTests { + [Fact] + public async Task AnyCommand_IfStartupFailed_ReturnsStartupError() { + // Arrange + var driver = MirrorSharpTestDriver.New(new MirrorSharpOptions().DisableCSharp()); + + // Act + var result = await driver.SendWithRequiredResultAsync(CommandIds.SetOptions, ""); + + // Assert + Assert.Contains("???", result.Message); + } + } +}