Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Di friendly #139

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions LittleForker.sln
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LittleForker.Tests", "src\L
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NonTerminatingProcess", "src\NonTerminatingProcess\NonTerminatingProcess.csproj", "{D68ABF78-2FC6-4EBD-AAAB-742637896206}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SelfTerminatingProcess", "src\SelfTerminatingProcess\SelfTerminatingProcess.csproj", "{F85DBC32-C585-4941-A210-5DE05A231BDF}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -28,10 +26,6 @@ Global
{D68ABF78-2FC6-4EBD-AAAB-742637896206}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D68ABF78-2FC6-4EBD-AAAB-742637896206}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D68ABF78-2FC6-4EBD-AAAB-742637896206}.Release|Any CPU.Build.0 = Release|Any CPU
{F85DBC32-C585-4941-A210-5DE05A231BDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F85DBC32-C585-4941-A210-5DE05A231BDF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F85DBC32-C585-4941-A210-5DE05A231BDF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F85DBC32-C585-4941-A210-5DE05A231BDF}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
61 changes: 42 additions & 19 deletions src/LittleForker.Tests/CooperativeShutdownTests.cs
Original file line number Diff line number Diff line change
@@ -1,35 +1,58 @@
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Shouldly;
using Microsoft.Extensions.Options;
using Xunit;
using Xunit.Abstractions;

namespace LittleForker
namespace LittleForker;

public class CooperativeShutdownTests
{
public class CooperativeShutdownTests
private readonly ILoggerFactory _loggerFactory;

public CooperativeShutdownTests(ITestOutputHelper outputHelper)
{
private readonly ILoggerFactory _loggerFactory;
_loggerFactory = new XunitLoggerFactory(outputHelper).LoggerFactory;
}

public CooperativeShutdownTests(ITestOutputHelper outputHelper)
[Fact]
public async Task When_server_signals_exit_then_should_notify_client_to_exit()
{
var applicationLifetime = new FakeHostApplicationLifetime();
var options = new CooperativeShutdownHostedServiceOptions()
{
_loggerFactory = new XunitLoggerFactory(outputHelper).LoggerFactory;
}
PipeName = Guid.NewGuid().ToString()
};
var cooperativeShutdownHostedService = new CooperativeShutdownHostedService(
applicationLifetime,
Options.Create(options),
_loggerFactory.CreateLogger<CooperativeShutdownHostedService>());

[Fact]
public async Task When_server_signals_exit_then_should_notify_client_to_exit()
{
var exitCalled = new TaskCompletionSource<bool>();
var listener = await CooperativeShutdown.Listen(
() => exitCalled.SetResult(true),
_loggerFactory);
await cooperativeShutdownHostedService.StartAsync(CancellationToken.None);

await CooperativeShutdown.SignalExit(Process.GetCurrentProcess().Id, _loggerFactory);
await CooperativeShutdown.SignalExit(options.PipeName, _loggerFactory);

(await exitCalled.Task).ShouldBeTrue();
await applicationLifetime.StopApplicationCalled.TimeoutAfter(TimeSpan.FromSeconds(5));

listener.Dispose();
await cooperativeShutdownHostedService.StopAsync(CancellationToken.None);
}


private class FakeHostApplicationLifetime : IHostApplicationLifetime
{
private readonly TaskCompletionSource _stopApplicationCalled = new();
public CancellationToken ApplicationStarted => throw new NotImplementedException();
public CancellationToken ApplicationStopping => throw new NotImplementedException();
public CancellationToken ApplicationStopped => throw new NotImplementedException();

public void StopApplication()
{
_stopApplicationCalled.SetResult();
}

public Task StopApplicationCalled => _stopApplicationCalled.Task;
}
}
}
13 changes: 6 additions & 7 deletions src/LittleForker.Tests/Infra/TestOutputHelperExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
using Xunit.Abstractions;

namespace LittleForker
namespace LittleForker;

public static class TestOutputHelperExtensions
{
public static class TestOutputHelperExtensions
public static void WriteLine2(this ITestOutputHelper outputHelper, object o)
{
public static void WriteLine2(this ITestOutputHelper outputHelper, object o)
if (o != null)
{
if (o != null)
{
outputHelper.WriteLine(o.ToString());
}
outputHelper.WriteLine(o.ToString());
}
}
}
3 changes: 0 additions & 3 deletions src/LittleForker.Tests/LittleForker.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,6 @@
<None Update="NonTerminatingProcess\**\*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="SelfTerminatingProcess\**\*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
137 changes: 68 additions & 69 deletions src/LittleForker.Tests/ProcessExitedHelperTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,89 +5,88 @@
using Xunit;
using Xunit.Abstractions;

namespace LittleForker
namespace LittleForker;

public class ProcessExitedHelperTests
{
public class ProcessExitedHelperTests
private readonly ITestOutputHelper _outputHelper;
private readonly ILoggerFactory _loggerFactory;

public ProcessExitedHelperTests(ITestOutputHelper outputHelper)
{
private readonly ITestOutputHelper _outputHelper;
private readonly ILoggerFactory _loggerFactory;
_outputHelper = outputHelper;
_loggerFactory = new XunitLoggerFactory(outputHelper).LoggerFactory;
}

public ProcessExitedHelperTests(ITestOutputHelper outputHelper)
[Fact]
public async Task When_parent_process_does_not_exist_then_should_call_parent_exited_callback()
{
var parentExited = new TaskCompletionSource<int?>();
using (new ProcessExitedHelper(-1, watcher => parentExited.SetResult(watcher.ProcessId), _loggerFactory))
{
_outputHelper = outputHelper;
_loggerFactory = new XunitLoggerFactory(outputHelper).LoggerFactory;
var processId = await parentExited.Task.TimeoutAfter(TimeSpan.FromSeconds(2));
processId.ShouldBe(-1);
}
}

[Fact]
public async Task When_parent_process_does_not_exist_then_should_call_parent_exited_callback()
{
var parentExited = new TaskCompletionSource<int?>();
using (new ProcessExitedHelper(-1, watcher => parentExited.SetResult(watcher.ProcessId), _loggerFactory))
[Fact]
public async Task When_parent_process_exits_than_should_call_parent_exited_callback()
{
// Start parent
var settings =
new ProcessSupervisorSettings(Environment.CurrentDirectory, "dotnet")
{
var processId = await parentExited.Task.TimeoutAfter(TimeSpan.FromSeconds(2));
processId.ShouldBe(-1);
}
}
Arguments = "./NonTerminatingProcess/NonTerminatingProcess.dll"
};
var supervisor = new ProcessSupervisor(settings, _loggerFactory);
var parentIsRunning = supervisor.WhenStateIs(ProcessSupervisor.State.Running);
supervisor.OutputDataReceived += data => _outputHelper.WriteLine($"Parent Process: {data}");
await supervisor.Start();
await parentIsRunning;

[Fact]
public async Task When_parent_process_exits_than_should_call_parent_exited_callback()
// Monitor parent
var parentExited = new TaskCompletionSource<int?>();
using (new ProcessExitedHelper(supervisor.ProcessInfo.Id, watcher => parentExited.SetResult(watcher.ProcessId), _loggerFactory))
{
// Start parent
var supervisor = new ProcessSupervisor(
_loggerFactory,
ProcessRunType.NonTerminating,
Environment.CurrentDirectory,
"dotnet",
"./NonTerminatingProcess/NonTerminatingProcess.dll");
var parentIsRunning = supervisor.WhenStateIs(ProcessSupervisor.State.Running);
supervisor.OutputDataReceived += data => _outputHelper.WriteLine($"Parent Process: {data}");
await supervisor.Start();
await parentIsRunning;
// Stop parent
await supervisor.Stop(TimeSpan.FromSeconds(2));
var processId = await parentExited.Task.TimeoutAfter(TimeSpan.FromSeconds(2));
processId.Value.ShouldBeGreaterThan(0);
}
}

// Monitor parent
var parentExited = new TaskCompletionSource<int?>();
using (new ProcessExitedHelper(supervisor.ProcessInfo.Id, watcher => parentExited.SetResult(watcher.ProcessId), _loggerFactory))
[Fact]
public async Task When_parent_process_exits_then_child_process_should_also_do_so()
{
// Start parent
var parentSettings =
new ProcessSupervisorSettings(Environment.CurrentDirectory, "dotnet")
{
// Stop parent
await supervisor.Stop(TimeSpan.FromSeconds(2));
var processId = await parentExited.Task.TimeoutAfter(TimeSpan.FromSeconds(2));
processId.Value.ShouldBeGreaterThan(0);
}
}
Arguments = "./NonTerminatingProcess/NonTerminatingProcess.dll"
};

[Fact]
public async Task When_parent_process_exits_then_child_process_should_also_do_so()
{
// Start parent
var parentSupervisor = new ProcessSupervisor(
_loggerFactory,
ProcessRunType.NonTerminating,
Environment.CurrentDirectory,
"dotnet",
"./NonTerminatingProcess/NonTerminatingProcess.dll");
parentSupervisor.OutputDataReceived += data => _outputHelper.WriteLine($"Parent: {data}");
var parentIsRunning = parentSupervisor.WhenStateIs(ProcessSupervisor.State.Running);
await parentSupervisor.Start();
await parentIsRunning;
var parentSupervisor = new ProcessSupervisor(parentSettings, _loggerFactory);
parentSupervisor.OutputDataReceived += data => _outputHelper.WriteLine($"Parent: {data}");
var parentIsRunning = parentSupervisor.WhenStateIs(ProcessSupervisor.State.Running);
await parentSupervisor.Start();
await parentIsRunning;

// Start child
var childSupervisor = new ProcessSupervisor(
_loggerFactory,
ProcessRunType.SelfTerminating,
Environment.CurrentDirectory,
"dotnet",
$"./NonTerminatingProcess/NonTerminatingProcess.dll --ParentProcessId={parentSupervisor.ProcessInfo.Id}");
childSupervisor.OutputDataReceived += data => _outputHelper.WriteLine($"Child: {data}");
var childIsRunning = childSupervisor.WhenStateIs(ProcessSupervisor.State.Running);
var childHasStopped = childSupervisor.WhenStateIs(ProcessSupervisor.State.ExitedSuccessfully);
await childSupervisor.Start();
await childIsRunning;
// Start child
var childSettings = new ProcessSupervisorSettings(Environment.CurrentDirectory, "dotnet")
{
Arguments = $"./NonTerminatingProcess/NonTerminatingProcess.dll --ParentProcessId={parentSupervisor.ProcessInfo.Id}"
};
var childSupervisor = new ProcessSupervisor(childSettings, _loggerFactory);
childSupervisor.OutputDataReceived += data => _outputHelper.WriteLine($"Child: {data}");
var childIsRunning = childSupervisor.WhenStateIs(ProcessSupervisor.State.Running);
var childHasStopped = childSupervisor.WhenStateIs(ProcessSupervisor.State.ExitedSuccessfully);
await childSupervisor.Start();
await childIsRunning;

// Stop parent
await parentSupervisor.Stop();
// Stop parent
await parentSupervisor.Stop();

// Wait for child to stop
await childHasStopped.TimeoutAfter(TimeSpan.FromSeconds(2));
}
// Wait for child to stop
await childHasStopped.TimeoutAfter(TimeSpan.FromSeconds(2));
}
}
Loading