513fa1aa16
Comment/XML-doc changes only: - add summaries to ~10k test methods, fixtures, fakes and helpers (+ <param> on theories, <returns> on async Task methods, <inheritdoc/> on interface implementations the checker can resolve) - strip ~2.6k project-tracking references (WP*, P*-*, row-NN, Task N, #NN, Component-0NN) from comments, keeping the explanatory text; reword false positives in comments only - real docs on fakes implementing BCL/library interfaces (IDisposable, ILogger, IOptionsMonitor, IAsyncLifetime) - reword the last 5 src/ false positives (CWE-1236, failures-1, year-0001, M365) Only non-comment edits: four one-line nested classes split across lines so members can carry docs. Build 0 errors / 0 warnings.
111 lines
4.6 KiB
C#
111 lines
4.6 KiB
C#
using System.Threading.Tasks;
|
|
using ZB.MOM.WW.ScadaBridge.CLI.Commands;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.CLI.Tests;
|
|
|
|
/// <summary>
|
|
/// Regression tests for the testable pieces of <c>DebugCommands.StreamDebugAsync</c>:
|
|
/// Ctrl+C during connect being misreported as a connection failure, and
|
|
/// non-deterministic exit codes after stream termination.
|
|
/// </summary>
|
|
public class DebugStreamTests
|
|
{
|
|
// --- connect-failure classification --------------------------------------
|
|
|
|
/// <summary>Verifies that an <see cref="OperationCanceledException"/> raised after cancellation was requested is treated as a graceful cancellation.</summary>
|
|
[Fact]
|
|
public void ClassifyConnectFailure_OperationCanceled_IsTreatedAsCancellation()
|
|
{
|
|
// Ctrl+C while StartAsync is still establishing the connection throws
|
|
// OperationCanceledException — this is a graceful cancellation, not a failure.
|
|
var result = DebugStreamHelpers.ClassifyConnectFailure(
|
|
new OperationCanceledException(), cancellationRequested: true);
|
|
|
|
Assert.True(result.IsCancellation);
|
|
Assert.Equal(0, result.ExitCode);
|
|
}
|
|
|
|
/// <summary>Verifies that a <see cref="TaskCanceledException"/> raised after cancellation was requested is treated as a cancellation.</summary>
|
|
[Fact]
|
|
public void ClassifyConnectFailure_TaskCanceled_WhenCancelRequested_IsCancellation()
|
|
{
|
|
var result = DebugStreamHelpers.ClassifyConnectFailure(
|
|
new TaskCanceledException(), cancellationRequested: true);
|
|
|
|
Assert.True(result.IsCancellation);
|
|
Assert.Equal(0, result.ExitCode);
|
|
}
|
|
|
|
/// <summary>Verifies that a genuine connection exception is classified as a connection failure with exit code 1.</summary>
|
|
[Fact]
|
|
public void ClassifyConnectFailure_RealException_IsConnectionFailure()
|
|
{
|
|
var result = DebugStreamHelpers.ClassifyConnectFailure(
|
|
new HttpRequestException("connection refused"), cancellationRequested: false);
|
|
|
|
Assert.False(result.IsCancellation);
|
|
Assert.Equal(1, result.ExitCode);
|
|
}
|
|
|
|
/// <summary>Verifies that a cancellation exception without a user cancel request is classified as a connection failure.</summary>
|
|
[Fact]
|
|
public void ClassifyConnectFailure_CanceledExceptionButNoCancelRequested_IsConnectionFailure()
|
|
{
|
|
// A cancellation that did not originate from the user (e.g. a server-side abort)
|
|
// is still a real connection failure.
|
|
var result = DebugStreamHelpers.ClassifyConnectFailure(
|
|
new OperationCanceledException(), cancellationRequested: false);
|
|
|
|
Assert.False(result.IsCancellation);
|
|
Assert.Equal(1, result.ExitCode);
|
|
}
|
|
|
|
// --- deterministic exit-code resolution ----------------------------------
|
|
|
|
/// <summary>Verifies that a result already set by stream termination is preferred when resolving the exit code.</summary>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[Fact]
|
|
public async Task ResolveStreamExitCodeAsync_TerminationResultSet_PrefersThatResult()
|
|
{
|
|
// OnStreamTerminated set exitTcs to 1 — that must win even on the Ctrl+C path.
|
|
var tcs = new TaskCompletionSource<int>();
|
|
tcs.SetResult(1);
|
|
|
|
var code = await DebugStreamHelpers.ResolveStreamExitCodeAsync(tcs.Task);
|
|
|
|
Assert.Equal(1, code);
|
|
}
|
|
|
|
/// <summary>Verifies that the exit code resolves to zero when no termination result was ever set.</summary>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[Fact]
|
|
public async Task ResolveStreamExitCodeAsync_NoResult_ReturnsZero()
|
|
{
|
|
// Pure Ctrl+C: exitTcs never completed — graceful shutdown, exit 0.
|
|
var tcs = new TaskCompletionSource<int>();
|
|
|
|
var code = await DebugStreamHelpers.ResolveStreamExitCodeAsync(tcs.Task);
|
|
|
|
Assert.Equal(0, code);
|
|
}
|
|
|
|
/// <summary>Verifies that a result arriving during the grace period is observed when resolving the exit code.</summary>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[Fact]
|
|
public async Task ResolveStreamExitCodeAsync_ResultArrivesDuringGrace_IsObserved()
|
|
{
|
|
// A stream termination racing with Ctrl+C: the result lands shortly after the
|
|
// wait was cancelled. The grace period must let it be observed deterministically.
|
|
var tcs = new TaskCompletionSource<int>();
|
|
_ = Task.Run(async () =>
|
|
{
|
|
await Task.Delay(20);
|
|
tcs.TrySetResult(1);
|
|
});
|
|
|
|
var code = await DebugStreamHelpers.ResolveStreamExitCodeAsync(tcs.Task);
|
|
|
|
Assert.Equal(1, code);
|
|
}
|
|
}
|