123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290 |
- // Licensed to the .NET Foundation under one or more agreements.
- // The .NET Foundation licenses this file to you under the MIT license.
- using System.Diagnostics;
- using Moq;
- namespace Microsoft.NET.Sdk.Razor.Tool.Tests
- {
- public class ServerLifecycleTest
- {
- private static ServerRequest EmptyServerRequest => new(1, Array.Empty<RequestArgument>());
- private static ServerResponse EmptyServerResponse => new CompletedServerResponse(
- returnCode: 0,
- utf8output: false,
- output: string.Empty,
- error: string.Empty);
- [Fact]
- public void ServerStartup_MutexAlreadyAcquired_Fails()
- {
- // Arrange
- var pipeName = Guid.NewGuid().ToString("N");
- var mutexName = MutexName.GetServerMutexName(pipeName);
- var compilerHost = new Mock<CompilerHost>(MockBehavior.Strict);
- var host = new Mock<ConnectionHost>(MockBehavior.Strict);
- // Act & Assert
- using (var mutex = new Mutex(initiallyOwned: true, name: mutexName, createdNew: out var holdsMutex))
- {
- Assert.True(holdsMutex);
- try
- {
- var result = ServerUtilities.RunServer(pipeName, host.Object, compilerHost.Object);
- // Assert failure
- Assert.Equal(1, result);
- }
- finally
- {
- mutex.ReleaseMutex();
- }
- }
- }
- [Fact]
- public void ServerStartup_SuccessfullyAcquiredMutex()
- {
- // Arrange, Act & Assert
- var pipeName = Guid.NewGuid().ToString("N");
- var mutexName = MutexName.GetServerMutexName(pipeName);
- var compilerHost = new Mock<CompilerHost>(MockBehavior.Strict);
- var host = new Mock<ConnectionHost>(MockBehavior.Strict);
- #pragma warning disable xUnit1031
- host
- .Setup(x => x.WaitForConnectionAsync(It.IsAny<CancellationToken>()))
- .Returns(() =>
- {
- // Use a thread instead of Task to guarantee this code runs on a different
- // thread and we can validate the mutex state.
- var source = new TaskCompletionSource<bool>();
- var thread = new Thread(_ =>
- {
- Mutex mutex = null;
- try
- {
- Assert.True(Mutex.TryOpenExisting(mutexName, out mutex));
- Assert.False(mutex.WaitOne(millisecondsTimeout: 0));
- source.SetResult(true);
- }
- catch (Exception ex)
- {
- source.SetException(ex);
- throw;
- }
- finally
- {
- mutex?.Dispose();
- }
- });
- // Synchronously wait here. Don't returned a Task value because we need to
- // ensure the above check completes before the server hits a timeout and
- // releases the mutex.
- thread.Start();
- source.Task.Wait();
- return new TaskCompletionSource<Connection>().Task;
- });
- #pragma warning restore xUnit1031
- var result = ServerUtilities.RunServer(pipeName, host.Object, compilerHost.Object, keepAlive: TimeSpan.FromSeconds(1));
- Assert.Equal(0, result);
- }
- [Fact]
- public async Task ServerRunning_ShutdownRequest_processesSuccessfully()
- {
- // Arrange
- using (var serverData = ServerUtilities.CreateServer())
- {
- // Act
- var serverProcessId = await ServerUtilities.SendShutdown(serverData.PipeName);
- // Assert
- Assert.Equal(Process.GetCurrentProcess().Id, serverProcessId);
- await serverData.Verify(connections: 1, completed: 1);
- }
- }
- /// <summary>
- /// A shutdown request should not abort an existing compilation. It should be allowed to run to
- /// completion.
- /// </summary>
- [Fact]
- public async Task ServerRunning_ShutdownRequest_DoesNotAbortCompilation()
- {
- // Arrange
- var startCompilationSource = new TaskCompletionSource<bool>();
- var finishCompilationSource = new TaskCompletionSource<bool>();
- #pragma warning disable xUnit1031
- var host = CreateCompilerHost(c => c.ExecuteFunc = (req, ct) =>
- {
- // At this point, the connection has been accepted and the compilation has started.
- startCompilationSource.SetResult(true);
- // We want this to keep running even after the shutdown is seen.
- finishCompilationSource.Task.Wait();
- return EmptyServerResponse;
- });
- #pragma warning restore xUnit1031
- using (var serverData = ServerUtilities.CreateServer(compilerHost: host))
- {
- var compileTask = ServerUtilities.Send(serverData.PipeName, EmptyServerRequest);
- // Wait for the request to go through and trigger compilation.
- await startCompilationSource.Task;
- // Act
- // The compilation is now in progress, send the shutdown.
- await ServerUtilities.SendShutdown(serverData.PipeName);
- Assert.False(compileTask.IsCompleted);
- // Now let the task complete.
- finishCompilationSource.SetResult(true);
- // Assert
- var response = await compileTask;
- Assert.Equal(ServerResponse.ResponseType.Completed, response.Type);
- Assert.Equal(0, ((CompletedServerResponse)response).ReturnCode);
- await serverData.Verify(connections: 2, completed: 2);
- }
- }
- /// <summary>
- /// Multiple clients should be able to send shutdown requests to the server.
- /// </summary>
- [Fact]
- public async Task ServerRunning_MultipleShutdownRequests_HandlesSuccessfully()
- {
- // Arrange
- var startCompilationSource = new TaskCompletionSource<bool>();
- var finishCompilationSource = new TaskCompletionSource<bool>();
- #pragma warning disable xUnit1031
- var host = CreateCompilerHost(c => c.ExecuteFunc = (req, ct) =>
- {
- // At this point, the connection has been accepted and the compilation has started.
- startCompilationSource.SetResult(true);
- // We want this to keep running even after the shutdown is seen.
- finishCompilationSource.Task.Wait();
- return EmptyServerResponse;
- });
- #pragma warning restore xUnit1031
- using (var serverData = ServerUtilities.CreateServer(compilerHost: host))
- {
- var compileTask = ServerUtilities.Send(serverData.PipeName, EmptyServerRequest);
- // Wait for the request to go through and trigger compilation.
- await startCompilationSource.Task;
- // Act
- for (var i = 0; i < 10; i++)
- {
- // The compilation is now in progress, send the shutdown.
- var processId = await ServerUtilities.SendShutdown(serverData.PipeName);
- Assert.Equal(Process.GetCurrentProcess().Id, processId);
- Assert.False(compileTask.IsCompleted);
- }
- // Now let the task complete.
- finishCompilationSource.SetResult(true);
- // Assert
- var response = await compileTask;
- Assert.Equal(ServerResponse.ResponseType.Completed, response.Type);
- Assert.Equal(0, ((CompletedServerResponse)response).ReturnCode);
- await serverData.Verify(connections: 11, completed: 11);
- }
- }
- // https://github.com/aspnet/Razor/issues/1991
- [WindowsOnlyFact]
- public async Task ServerRunning_CancelCompilation_CancelsSuccessfully()
- {
- // Arrange
- const int requestCount = 5;
- var count = 0;
- var completionSource = new TaskCompletionSource<bool>();
- var host = CreateCompilerHost(c => c.ExecuteFunc = (req, ct) =>
- {
- if (Interlocked.Increment(ref count) == requestCount)
- {
- completionSource.SetResult(true);
- }
- ct.WaitHandle.WaitOne();
- return new RejectedServerResponse();
- });
- var semaphore = new SemaphoreSlim(1);
- Action<object, EventArgs> onListening = (s, e) =>
- {
- semaphore.Release();
- };
- using (var serverData = ServerUtilities.CreateServer(compilerHost: host, onListening: onListening))
- {
- // Send all the requests.
- var clients = new List<Client>();
- for (var i = 0; i < requestCount; i++)
- {
- // Wait for the server to start listening.
- await semaphore.WaitAsync(TimeSpan.FromMinutes(1));
- var client = await Client.ConnectAsync(serverData.PipeName, timeout: null, cancellationToken: default);
- await EmptyServerRequest.WriteAsync(client.Stream);
- clients.Add(client);
- }
- // Act
- // Wait until all of the connections are being processed by the server.
- await completionSource.Task;
- // Now cancel
- var stats = await serverData.CancelAndCompleteAsync();
- // Assert
- Assert.Equal(requestCount, stats.Connections);
- Assert.Equal(requestCount, count);
- // Read the server response to each client.
- foreach (var client in clients)
- {
- var task = ServerResponse.ReadAsync(client.Stream);
- // We expect this to throw because the stream is already closed.
- await Assert.ThrowsAnyAsync<IOException>(() => task);
- client.Dispose();
- }
- }
- }
- private static TestableCompilerHost CreateCompilerHost(Action<TestableCompilerHost> configureCompilerHost = null)
- {
- var compilerHost = new TestableCompilerHost();
- configureCompilerHost?.Invoke(compilerHost);
- return compilerHost;
- }
- private class TestableCompilerHost : CompilerHost
- {
- internal Func<ServerRequest, CancellationToken, ServerResponse> ExecuteFunc;
- public override ServerResponse Execute(ServerRequest request, CancellationToken cancellationToken)
- {
- if (ExecuteFunc != null)
- {
- return ExecuteFunc(request, cancellationToken);
- }
- return EmptyServerResponse;
- }
- }
- }
- }
|