public async Task AcceptConnection_WritingResultsFails_ClosesConnection()
        {
            // Arrange
            var memoryStream = new MemoryStream();
            await EmptyServerRequest.WriteAsync(memoryStream, CancellationToken.None).ConfigureAwait(true);

            memoryStream.Position = 0;

            var stream = new Mock <Stream>(MockBehavior.Strict);

            stream
            .Setup(x => x.ReadAsync(It.IsAny <byte[]>(), It.IsAny <int>(), It.IsAny <int>(), It.IsAny <CancellationToken>()))
            .Returns((byte[] array, int start, int length, CancellationToken ct) => memoryStream.ReadAsync(array, start, length, ct));

            var connection   = CreateConnection(stream.Object);
            var compilerHost = CreateCompilerHost(c =>
            {
                c.ExecuteFunc = (req, ct) =>
                {
                    return(EmptyServerResponse);
                };
            });
            var connectionHost = CreateConnectionHost();
            var dispatcher     = new DefaultRequestDispatcher(connectionHost, compilerHost, CancellationToken.None);

            // Act
            // We expect WriteAsync to fail because the mock stream doesn't have a corresponding setup.
            var connectionResult = await dispatcher.AcceptConnection(
                Task.FromResult <Connection>(connection), accept : true, cancellationToken : CancellationToken.None);

            // Assert
            Assert.Equal(ConnectionResult.Reason.ClientDisconnect, connectionResult.CloseReason);
            Assert.Null(connectionResult.KeepAlive);
        }
        public async Task Dispatcher_ClientConnectionThrowsWhenExecutingRequest_ClosesConnection()
        {
            // Arrange
            var called         = false;
            var connectionTask = CreateConnectionWithEmptyServerRequest(c =>
            {
                c.WaitForDisconnectAsyncFunc = (ct) =>
                {
                    called = true;
                    throw new Exception();
                };
            });

            var compilerHost   = CreateCompilerHost();
            var connectionHost = CreateConnectionHost();
            var dispatcher     = new DefaultRequestDispatcher(connectionHost, compilerHost, CancellationToken.None);

            // Act
            var connectionResult = await dispatcher.AcceptConnection(
                connectionTask, accept : true, cancellationToken : CancellationToken.None);

            // Assert
            Assert.True(called);
            Assert.Equal(ConnectionResult.Reason.ClientException, connectionResult.CloseReason);
            Assert.Null(connectionResult.KeepAlive);
        }
        public void Dispatcher_ClientConnectionThrows_BeginsShutdown()
        {
            // Arrange
            var listenCancellationToken = default(CancellationToken);
            var firstConnectionTask     = CreateConnectionWithEmptyServerRequest(c =>
            {
                c.WaitForDisconnectAsyncFunc = (ct) =>
                {
                    listenCancellationToken = ct;
                    return(Task.Delay(Timeout.Infinite, ct).ContinueWith <Connection>(_ => null));
                };
            });
            var secondConnectionTask = CreateConnectionWithEmptyServerRequest(c =>
            {
                c.WaitForDisconnectAsyncFunc = (ct) => throw new Exception();
            });

            var compilerHost   = CreateCompilerHost();
            var connectionHost = CreateConnectionHost(
                firstConnectionTask,
                secondConnectionTask,
                new TaskCompletionSource <Connection>().Task);
            var keepAlive  = TimeSpan.FromSeconds(10);
            var eventBus   = new TestableEventBus();
            var dispatcher = new DefaultRequestDispatcher(connectionHost, compilerHost, CancellationToken.None, eventBus, keepAlive);

            // Act
            dispatcher.Run();

            // Assert
            Assert.True(eventBus.HasDetectedBadConnection);
            Assert.True(listenCancellationToken.IsCancellationRequested);
        }
        public void Dispatcher_ProcessMultipleConnections_HitsKeepAliveTimeout()
        {
            // Arrange
            var count = 5;
            var list  = new List <Task <Connection> >();

            for (var i = 0; i < count; i++)
            {
                var connectionTask = CreateConnectionWithEmptyServerRequest();
                list.Add(connectionTask);
            }

            list.Add(new TaskCompletionSource <Connection>().Task);
            var connectionHost = CreateConnectionHost(list.ToArray());
            var compilerHost   = CreateCompilerHost(c =>
            {
                c.ExecuteFunc = (req, ct) =>
                {
                    return(EmptyServerResponse);
                };
            });

            var keepAlive  = TimeSpan.FromSeconds(1);
            var eventBus   = new TestableEventBus();
            var dispatcher = new DefaultRequestDispatcher(connectionHost, compilerHost, CancellationToken.None, eventBus, keepAlive);

            // Act
            dispatcher.Run();

            // Assert
            Assert.Equal(count, eventBus.CompletedCount);
            Assert.True(eventBus.LastProcessedTime.HasValue);
            Assert.True(eventBus.HitKeepAliveTimeout);
        }
        public async Task Dispatcher_ProcessSimultaneousConnections_HitsKeepAliveTimeout()
        {
            // Arrange
            var totalCount     = 2;
            var readySource    = new TaskCompletionSource <bool>();
            var list           = new List <TaskCompletionSource <bool> >();
            var connectionHost = new Mock <ConnectionHost>();

            connectionHost
            .Setup(x => x.WaitForConnectionAsync(It.IsAny <CancellationToken>()))
            .Returns((CancellationToken ct) =>
            {
                if (list.Count < totalCount)
                {
                    var source         = new TaskCompletionSource <bool>();
                    var connectionTask = CreateConnectionWithEmptyServerRequest(c =>
                    {
                        c.WaitForDisconnectAsyncFunc = _ => source.Task;
                    });
                    list.Add(source);
                    return(connectionTask);
                }

                readySource.SetResult(true);
                return(new TaskCompletionSource <Connection>().Task);
            });

            var compilerHost = CreateCompilerHost(c =>
            {
                c.ExecuteFunc = (req, ct) =>
                {
                    return(EmptyServerResponse);
                };
            });

            var keepAlive      = TimeSpan.FromSeconds(1);
            var eventBus       = new TestableEventBus();
            var dispatcherTask = Task.Run(() =>
            {
                var dispatcher = new DefaultRequestDispatcher(connectionHost.Object, compilerHost, CancellationToken.None, eventBus, keepAlive);
                dispatcher.Run();
            });

            await readySource.Task;

            foreach (var source in list)
            {
                source.SetResult(true);
            }

            // Act
            await dispatcherTask;

            // Assert
            Assert.Equal(totalCount, eventBus.CompletedCount);
            Assert.True(eventBus.LastProcessedTime.HasValue, "LastProcessedTime should have had a value.");
            Assert.True(eventBus.HitKeepAliveTimeout, "HitKeepAliveTimeout should have been hit.");
        }
        public async Task AcceptConnection_ClientDisconnectsWhenExecutingRequest_ClosesConnection()
        {
            // Arrange
            var connectionHost = Mock.Of <ConnectionHost>();

            // Fake a long running task here that we can validate later on.
            var buildTaskSource            = new TaskCompletionSource <bool>();
            var buildTaskCancellationToken = default(CancellationToken);
            var compilerHost = CreateCompilerHost(c =>
            {
                c.ExecuteFunc = (req, ct) =>
                {
                    Task.WaitAll(buildTaskSource.Task);
                    return(EmptyServerResponse);
                };
            });

            var dispatcher           = new DefaultRequestDispatcher(connectionHost, compilerHost, CancellationToken.None);
            var readyTaskSource      = new TaskCompletionSource <bool>();
            var disconnectTaskSource = new TaskCompletionSource <bool>();
            var connectionTask       = CreateConnectionWithEmptyServerRequest(c =>
            {
                c.WaitForDisconnectAsyncFunc = (ct) =>
                {
                    buildTaskCancellationToken = ct;
                    readyTaskSource.SetResult(true);
                    return(disconnectTaskSource.Task);
                };
            });

            var handleTask = dispatcher.AcceptConnection(
                connectionTask, accept: true, cancellationToken: CancellationToken.None);

            // Wait until WaitForDisconnectAsync task is actually created and running.
            await readyTaskSource.Task.ConfigureAwait(false);

            // Act
            // Now simulate a disconnect by the client.
            disconnectTaskSource.SetResult(true);
            var connectionResult = await handleTask;

            buildTaskSource.SetResult(true);

            // Assert
            Assert.Equal(ConnectionResult.Reason.ClientDisconnect, connectionResult.CloseReason);
            Assert.Null(connectionResult.KeepAlive);
            Assert.True(buildTaskCancellationToken.IsCancellationRequested);
        }
        public async Task AcceptConnection_ReadingRequestFails_ClosesConnection()
        {
            // Arrange
            var stream         = Mock.Of <Stream>();
            var compilerHost   = CreateCompilerHost();
            var connectionHost = CreateConnectionHost();
            var dispatcher     = new DefaultRequestDispatcher(connectionHost, compilerHost, CancellationToken.None);
            var connection     = CreateConnection(stream);

            // Act
            var result = await dispatcher.AcceptConnection(
                Task.FromResult <Connection>(connection), accept : true, cancellationToken : CancellationToken.None);

            // Assert
            Assert.Equal(ConnectionResult.Reason.CompilationNotStarted, result.CloseReason);
        }
        public async Task AcceptConnection_ClientConnectionThrowsWhenConnecting_ClosesConnection()
        {
            // Arrange
            var compilerHost   = CreateCompilerHost();
            var connectionHost = CreateConnectionHost();
            var dispatcher     = new DefaultRequestDispatcher(connectionHost, compilerHost, CancellationToken.None);
            var connectionTask = Task.FromException <Connection>(new Exception());

            // Act
            var connectionResult = await dispatcher.AcceptConnection(
                connectionTask, accept : true, cancellationToken : CancellationToken.None);

            // Assert
            Assert.Equal(ConnectionResult.Reason.CompilationNotStarted, connectionResult.CloseReason);
            Assert.Null(connectionResult.KeepAlive);
        }
        public async Task AcceptConnection_ConnectionHostThrowsWhenConnecting_ClosesConnection()
        {
            // Arrange
            var connectionHost = new Mock <ConnectionHost>(MockBehavior.Strict);

            connectionHost.Setup(c => c.WaitForConnectionAsync(It.IsAny <CancellationToken>())).Throws(new Exception());
            var compilerHost = CreateCompilerHost();
            var dispatcher   = new DefaultRequestDispatcher(connectionHost.Object, compilerHost, CancellationToken.None);
            var connection   = CreateConnection(Mock.Of <Stream>());

            // Act
            var connectionResult = await dispatcher.AcceptConnection(
                Task.FromResult <Connection>(connection), accept : true, cancellationToken : CancellationToken.None);

            // Assert
            Assert.Equal(ConnectionResult.Reason.CompilationNotStarted, connectionResult.CloseReason);
            Assert.Null(connectionResult.KeepAlive);
        }
        public void Dispatcher_NoConnections_HitsKeepAliveTimeout()
        {
            // Arrange
            var keepAlive      = TimeSpan.FromSeconds(3);
            var compilerHost   = CreateCompilerHost();
            var connectionHost = new Mock <ConnectionHost>();

            connectionHost
            .Setup(x => x.WaitForConnectionAsync(It.IsAny <CancellationToken>()))
            .Returns(new TaskCompletionSource <Connection>().Task);

            var eventBus   = new TestableEventBus();
            var dispatcher = new DefaultRequestDispatcher(connectionHost.Object, compilerHost, CancellationToken.None, eventBus, keepAlive);
            var startTime  = DateTime.Now;

            // Act
            dispatcher.Run();

            // Assert
            Assert.True(eventBus.HitKeepAliveTimeout);
        }
        public async Task AcceptConnection_ShutdownRequest_ReturnsShutdownResponse()
        {
            // Arrange
            var stream = new TestableStream();
            await ServerRequest.CreateShutdown().WriteAsync(stream.ReadStream, CancellationToken.None);

            stream.ReadStream.Position = 0;

            var connection     = CreateConnection(stream);
            var connectionHost = CreateConnectionHost();
            var compilerHost   = CreateCompilerHost();
            var dispatcher     = new DefaultRequestDispatcher(connectionHost, compilerHost, CancellationToken.None);

            // Act
            var connectionResult = await dispatcher.AcceptConnection(
                Task.FromResult <Connection>(connection), accept : true, cancellationToken : CancellationToken.None);

            // Assert
            Assert.Equal(ConnectionResult.Reason.ClientShutdownRequest, connectionResult.CloseReason);
            stream.WriteStream.Position = 0;
            var response = await ServerResponse.ReadAsync(stream.WriteStream).ConfigureAwait(false);

            Assert.Equal(ServerResponse.ResponseType.Shutdown, response.Type);
        }
        public async Task AcceptConnection_AcceptFalse_RejectsBuildRequest()
        {
            // Arrange
            var stream = new TestableStream();
            await EmptyServerRequest.WriteAsync(stream.ReadStream, CancellationToken.None);

            stream.ReadStream.Position = 0;

            var connection     = CreateConnection(stream);
            var connectionHost = CreateConnectionHost();
            var compilerHost   = CreateCompilerHost();
            var dispatcher     = new DefaultRequestDispatcher(connectionHost, compilerHost, CancellationToken.None);

            // Act
            var connectionResult = await dispatcher.AcceptConnection(
                Task.FromResult <Connection>(connection), accept : false, cancellationToken : CancellationToken.None);

            // Assert
            Assert.Equal(ConnectionResult.Reason.CompilationNotStarted, connectionResult.CloseReason);
            stream.WriteStream.Position = 0;
            var response = await ServerResponse.ReadAsync(stream.WriteStream).ConfigureAwait(false);

            Assert.Equal(ServerResponse.ResponseType.Rejected, response.Type);
        }
        public async Task Dispatcher_ProcessSimultaneousConnections_HitsKeepAliveTimeout()
        {
            // Arrange
            var totalCount     = 2;
            var readySource    = new TaskCompletionSource <bool>();
            var list           = new List <TaskCompletionSource <bool> >();
            var connectionHost = new Mock <ConnectionHost>();

            connectionHost
            .Setup(x => x.WaitForConnectionAsync(It.IsAny <CancellationToken>()))
            .Returns((CancellationToken ct) =>
            {
                if (list.Count < totalCount)
                {
                    var source         = new TaskCompletionSource <bool>();
                    var connectionTask = CreateConnectionWithEmptyServerRequest(c =>
                    {
                        // Keep the connection active until we decide to end it.
                        c.WaitForDisconnectAsyncFunc = _ => source.Task;
                    });
                    list.Add(source);
                    return(connectionTask);
                }

                readySource.SetResult(true);
                return(new TaskCompletionSource <Connection>().Task);
            });

            var compilerHost = CreateCompilerHost(c =>
            {
                c.ExecuteFunc = (req, ct) =>
                {
                    return(EmptyServerResponse);
                };
            });

            var eventBus = new TestableEventBus();
            var completedCompilations   = 0;
            var allCompilationsComplete = new TaskCompletionSource <bool>();

            eventBus.CompilationComplete += (obj, args) =>
            {
                if (++completedCompilations == totalCount)
                {
                    // All compilations have completed.
                    allCompilationsComplete.SetResult(true);
                }
            };
            var keepAlive      = TimeSpan.FromSeconds(1);
            var dispatcherTask = Task.Run(() =>
            {
                var dispatcher = new DefaultRequestDispatcher(connectionHost.Object, compilerHost, CancellationToken.None, eventBus, keepAlive);
                dispatcher.Run();
            });

            // Wait for all connections to be created.
            await readySource.Task;

            // Wait for all compilations to complete.
            await allCompilationsComplete.Task;

            // Now allow all the connections to be disconnected.
            foreach (var source in list)
            {
                source.SetResult(true);
            }

            // Act
            // Now dispatcher should be in an idle state with no active connections.
            await dispatcherTask;

            // Assert
            Assert.False(eventBus.HasDetectedBadConnection);
            Assert.Equal(totalCount, eventBus.CompletedCount);
            Assert.True(eventBus.LastProcessedTime.HasValue, "LastProcessedTime should have had a value.");
            Assert.True(eventBus.HitKeepAliveTimeout, "HitKeepAliveTimeout should have been hit.");
        }