refactor(tests): consolidate FakeWorkerProcess onto TestSupport canonical
This commit is contained in:
+1
-58
@@ -7,6 +7,7 @@ using ZB.MOM.WW.MxGateway.Server.Metrics;
|
|||||||
using ZB.MOM.WW.MxGateway.Server.Sessions;
|
using ZB.MOM.WW.MxGateway.Server.Sessions;
|
||||||
using ZB.MOM.WW.MxGateway.Server.Workers;
|
using ZB.MOM.WW.MxGateway.Server.Workers;
|
||||||
using ZB.MOM.WW.MxGateway.Tests.Gateway.Workers.Fakes;
|
using ZB.MOM.WW.MxGateway.Tests.Gateway.Workers.Fakes;
|
||||||
|
using ZB.MOM.WW.MxGateway.Tests.TestSupport;
|
||||||
|
|
||||||
namespace ZB.MOM.WW.MxGateway.Tests.Gateway.Sessions;
|
namespace ZB.MOM.WW.MxGateway.Tests.Gateway.Sessions;
|
||||||
|
|
||||||
@@ -330,62 +331,4 @@ public sealed class SessionWorkerClientFactoryFakeWorkerTests : IAsyncDisposable
|
|||||||
DateTimeOffset.UtcNow);
|
DateTimeOffset.UtcNow);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fake worker process for testing process lifecycle. <see cref="WaitForExitAsync"/>
|
|
||||||
/// awaits a <see cref="TaskCompletionSource"/> completed only by
|
|
||||||
/// <see cref="Kill"/> or <see cref="MarkExited"/>, so a caller observing
|
|
||||||
/// completion can trust that exit actually happened — bringing this fake into
|
|
||||||
/// parity with the smoke-test variant in <c>GatewayEndToEndFakeWorkerSmokeTests</c>
|
|
||||||
/// (Tests-015 / Tests-023). This removes the latent regression vector where a
|
|
||||||
/// future <c>Assert.True(launcher.Process.HasExited)</c> in this file would
|
|
||||||
/// pass spuriously regardless of whether the worker truly exited.
|
|
||||||
/// </summary>
|
|
||||||
private sealed class FakeWorkerProcess(int processId) : IWorkerProcess
|
|
||||||
{
|
|
||||||
private readonly TaskCompletionSource _exited = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
||||||
private bool _disposed;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public int Id { get; } = processId;
|
|
||||||
|
|
||||||
/// <summary>Gets a value indicating whether the process has exited.</summary>
|
|
||||||
public bool HasExited { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>Gets the process exit code, or null if the process has not exited.</summary>
|
|
||||||
public int? ExitCode { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>Gets the number of times the Kill method was called.</summary>
|
|
||||||
public int KillCount { get; private set; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public ValueTask WaitForExitAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
return new ValueTask(_exited.Task.WaitAsync(cancellationToken));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void Kill(bool entireProcessTree)
|
|
||||||
{
|
|
||||||
KillCount++;
|
|
||||||
MarkExited(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_disposed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Gets a value indicating whether this process has been disposed.</summary>
|
|
||||||
public bool IsDisposed => _disposed;
|
|
||||||
|
|
||||||
/// <summary>Marks the process as exited with the specified exit code.</summary>
|
|
||||||
/// <param name="exitCode">The process exit code.</param>
|
|
||||||
public void MarkExited(int exitCode)
|
|
||||||
{
|
|
||||||
HasExited = true;
|
|
||||||
ExitCode = exitCode;
|
|
||||||
_exited.TrySetResult();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ public sealed class WorkerClientTests
|
|||||||
public async Task ReadLoop_WhenClientFaults_KillsOwnedWorkerProcess()
|
public async Task ReadLoop_WhenClientFaults_KillsOwnedWorkerProcess()
|
||||||
{
|
{
|
||||||
await using PipePair pipePair = await PipePair.CreateAsync();
|
await using PipePair pipePair = await PipePair.CreateAsync();
|
||||||
FakeWorkerProcess process = new();
|
FakeWorkerProcess process = new(WorkerProcessId);
|
||||||
await using WorkerClient client = CreateClient(
|
await using WorkerClient client = CreateClient(
|
||||||
pipePair,
|
pipePair,
|
||||||
new WorkerClientOptions
|
new WorkerClientOptions
|
||||||
@@ -309,7 +309,7 @@ public sealed class WorkerClientTests
|
|||||||
public async Task DisposeAsync_WhenOwnedWorkerStillRuns_KillsProcessBeforeDisposing()
|
public async Task DisposeAsync_WhenOwnedWorkerStillRuns_KillsProcessBeforeDisposing()
|
||||||
{
|
{
|
||||||
await using PipePair pipePair = await PipePair.CreateAsync();
|
await using PipePair pipePair = await PipePair.CreateAsync();
|
||||||
FakeWorkerProcess process = new();
|
FakeWorkerProcess process = new(WorkerProcessId);
|
||||||
WorkerClient client = CreateClient(pipePair, processHandle: CreateProcessHandle(process));
|
WorkerClient client = CreateClient(pipePair, processHandle: CreateProcessHandle(process));
|
||||||
|
|
||||||
await client.DisposeAsync().AsTask().WaitAsync(TestTimeout);
|
await client.DisposeAsync().AsTask().WaitAsync(TestTimeout);
|
||||||
@@ -764,49 +764,4 @@ public sealed class WorkerClientTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class FakeWorkerProcess : IWorkerProcess
|
|
||||||
{
|
|
||||||
private readonly TaskCompletionSource _exited = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
||||||
|
|
||||||
/// <summary>Gets the process ID.</summary>
|
|
||||||
public int Id { get; } = WorkerProcessId;
|
|
||||||
|
|
||||||
/// <summary>Gets a value indicating whether the process has exited.</summary>
|
|
||||||
public bool HasExited { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>Gets the process exit code.</summary>
|
|
||||||
public int? ExitCode { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>Gets the number of times kill was called.</summary>
|
|
||||||
public int KillCount { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>Gets the last kill request's entire process tree flag.</summary>
|
|
||||||
public bool KillEntireProcessTree { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>Gets a value indicating whether dispose was called.</summary>
|
|
||||||
public bool Disposed { get; private set; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public ValueTask WaitForExitAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
return new ValueTask(_exited.Task.WaitAsync(cancellationToken));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Records a kill request.</summary>
|
|
||||||
/// <param name="entireProcessTree">Whether to kill the entire process tree.</param>
|
|
||||||
public void Kill(bool entireProcessTree)
|
|
||||||
{
|
|
||||||
KillCount++;
|
|
||||||
KillEntireProcessTree = entireProcessTree;
|
|
||||||
HasExited = true;
|
|
||||||
ExitCode = -1;
|
|
||||||
_exited.TrySetResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
Disposed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using ZB.MOM.WW.MxGateway.Contracts;
|
|||||||
using ZB.MOM.WW.MxGateway.Server.Configuration;
|
using ZB.MOM.WW.MxGateway.Server.Configuration;
|
||||||
using ZB.MOM.WW.MxGateway.Server.Metrics;
|
using ZB.MOM.WW.MxGateway.Server.Metrics;
|
||||||
using ZB.MOM.WW.MxGateway.Server.Workers;
|
using ZB.MOM.WW.MxGateway.Server.Workers;
|
||||||
|
using ZB.MOM.WW.MxGateway.Tests.TestSupport;
|
||||||
|
|
||||||
namespace ZB.MOM.WW.MxGateway.Tests.Gateway.Workers;
|
namespace ZB.MOM.WW.MxGateway.Tests.Gateway.Workers;
|
||||||
|
|
||||||
@@ -240,45 +241,6 @@ public sealed class WorkerProcessLauncherTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Fake worker process for testing process lifecycle and exit behavior.</summary>
|
|
||||||
private sealed class FakeWorkerProcess(int processId) : IWorkerProcess
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public int Id { get; } = processId;
|
|
||||||
|
|
||||||
/// <summary>Gets or sets a value indicating whether the process has exited.</summary>
|
|
||||||
public bool HasExited { get; set; }
|
|
||||||
|
|
||||||
/// <summary>Gets or sets the process exit code.</summary>
|
|
||||||
public int? ExitCode { get; set; }
|
|
||||||
|
|
||||||
/// <summary>Gets a value indicating whether the Dispose method was called.</summary>
|
|
||||||
public bool DisposeCalled { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>Gets a value indicating whether the Kill method was called.</summary>
|
|
||||||
public bool KillCalled { get; private set; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public ValueTask WaitForExitAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
return ValueTask.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void Kill(bool entireProcessTree)
|
|
||||||
{
|
|
||||||
Assert.True(entireProcessTree);
|
|
||||||
KillCalled = true;
|
|
||||||
HasExited = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
DisposeCalled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Fake startup probe that immediately succeeds.</summary>
|
/// <summary>Fake startup probe that immediately succeeds.</summary>
|
||||||
private sealed class SucceedingStartupProbe : IWorkerStartupProbe
|
private sealed class SucceedingStartupProbe : IWorkerStartupProbe
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,10 +4,21 @@ namespace ZB.MOM.WW.MxGateway.Tests.TestSupport;
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Lightweight in-process stand-in for <see cref="IWorkerProcess"/> used by fake worker
|
/// Lightweight in-process stand-in for <see cref="IWorkerProcess"/> used by fake worker
|
||||||
/// launchers in end-to-end tests. Call <see cref="MarkExited"/> from the fake worker
|
/// launchers and lifecycle tests.
|
||||||
/// body once the shutdown-ack handshake is complete so that callers awaiting
|
|
||||||
/// <see cref="WaitForExitAsync"/> observe real exit rather than a timeout.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// <see cref="WaitForExitAsync"/> awaits a <see cref="TaskCompletionSource"/> that is
|
||||||
|
/// completed only by <see cref="Kill"/> or <see cref="MarkExited"/>, so a caller observing
|
||||||
|
/// completion can trust that exit actually happened rather than passing spuriously.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// The disposal and kill bookkeeping is exposed under several aliases
|
||||||
|
/// (<see cref="IsDisposed"/>/<see cref="DisposeCalled"/>/<see cref="Disposed"/>;
|
||||||
|
/// <see cref="KillCount"/>/<see cref="KillCalled"/>) so the various lifecycle tests can
|
||||||
|
/// keep their existing assertion vocabulary while sharing one definition.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
public sealed class FakeWorkerProcess(int processId) : IWorkerProcess
|
public sealed class FakeWorkerProcess(int processId) : IWorkerProcess
|
||||||
{
|
{
|
||||||
private readonly TaskCompletionSource _exited = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
private readonly TaskCompletionSource _exited = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
@@ -15,23 +26,44 @@ public sealed class FakeWorkerProcess(int processId) : IWorkerProcess
|
|||||||
/// <summary>Gets the process identifier.</summary>
|
/// <summary>Gets the process identifier.</summary>
|
||||||
public int Id { get; } = processId;
|
public int Id { get; } = processId;
|
||||||
|
|
||||||
/// <summary>Gets a value indicating whether the process has exited.</summary>
|
/// <summary>Gets or sets a value indicating whether the process has exited.</summary>
|
||||||
public bool HasExited { get; private set; }
|
public bool HasExited { get; set; }
|
||||||
|
|
||||||
/// <summary>Gets the exit code of the process, or <see langword="null"/> if it has not exited.</summary>
|
/// <summary>Gets or sets the exit code of the process, or <see langword="null"/> if it has not exited.</summary>
|
||||||
public int? ExitCode { get; private set; }
|
public int? ExitCode { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Gets the number of times <see cref="Kill"/> was called.</summary>
|
||||||
|
public int KillCount { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>Gets a value indicating whether <see cref="Kill"/> was called at least once.</summary>
|
||||||
|
public bool KillCalled => KillCount > 0;
|
||||||
|
|
||||||
|
/// <summary>Gets the <c>entireProcessTree</c> flag from the most recent <see cref="Kill"/> call.</summary>
|
||||||
|
public bool KillEntireProcessTree { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>Gets a value indicating whether <see cref="Dispose"/> was called.</summary>
|
||||||
|
public bool IsDisposed { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>Gets a value indicating whether <see cref="Dispose"/> was called.</summary>
|
||||||
|
public bool DisposeCalled => IsDisposed;
|
||||||
|
|
||||||
|
/// <summary>Gets a value indicating whether <see cref="Dispose"/> was called.</summary>
|
||||||
|
public bool Disposed => IsDisposed;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ValueTask WaitForExitAsync(CancellationToken cancellationToken) =>
|
public ValueTask WaitForExitAsync(CancellationToken cancellationToken) =>
|
||||||
new(_exited.Task.WaitAsync(cancellationToken));
|
new(_exited.Task.WaitAsync(cancellationToken));
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void Kill(bool entireProcessTree) => MarkExited(-1);
|
public void Kill(bool entireProcessTree)
|
||||||
|
{
|
||||||
|
KillCount++;
|
||||||
|
KillEntireProcessTree = entireProcessTree;
|
||||||
|
MarkExited(-1);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void Dispose()
|
public void Dispose() => IsDisposed = true;
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Marks the process as exited with the specified exit code and unblocks
|
/// Marks the process as exited with the specified exit code and unblocks
|
||||||
|
|||||||
Reference in New Issue
Block a user