save progress
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
// <copyright file="ReplayIntegrationTestBase.cs" company="StellaOps">
|
||||
// Copyright (c) StellaOps. Licensed under AGPL-3.0-or-later.
|
||||
// </copyright>
|
||||
// Sprint: SPRINT_20260105_002_002_TEST_trace_replay_evidence
|
||||
// Task: TREP-007, TREP-008
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using StellaOps.Replay.Anonymization;
|
||||
using StellaOps.Testing.Temporal;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Testing.Replay;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for integration tests that replay production traces.
|
||||
/// </summary>
|
||||
public abstract class ReplayIntegrationTestBase : IAsyncLifetime
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the trace corpus manager.
|
||||
/// </summary>
|
||||
protected ITraceCorpusManager CorpusManager { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the replay orchestrator.
|
||||
/// </summary>
|
||||
protected IReplayOrchestrator ReplayOrchestrator { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the simulated time provider.
|
||||
/// </summary>
|
||||
protected SimulatedTimeProvider TimeProvider { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the service provider.
|
||||
/// </summary>
|
||||
protected IServiceProvider Services { get; private set; } = null!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async ValueTask InitializeAsync()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
ConfigureServices(services);
|
||||
|
||||
Services = services.BuildServiceProvider();
|
||||
CorpusManager = Services.GetRequiredService<ITraceCorpusManager>();
|
||||
ReplayOrchestrator = Services.GetRequiredService<IReplayOrchestrator>();
|
||||
TimeProvider = Services.GetRequiredService<SimulatedTimeProvider>();
|
||||
|
||||
await OnInitializedAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configure services for the test.
|
||||
/// </summary>
|
||||
/// <param name="services">The service collection.</param>
|
||||
protected virtual void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddReplayTesting();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called after initialization is complete.
|
||||
/// </summary>
|
||||
protected virtual Task OnInitializedAsync() => Task.CompletedTask;
|
||||
|
||||
/// <summary>
|
||||
/// Replay a trace and verify behavior matches expected outcome.
|
||||
/// </summary>
|
||||
/// <param name="trace">The trace to replay.</param>
|
||||
/// <param name="expectation">Expected outcome.</param>
|
||||
/// <returns>The replay result.</returns>
|
||||
protected async Task<ReplayResult> ReplayAndVerifyAsync(
|
||||
TraceCorpusEntry trace,
|
||||
ReplayExpectation expectation)
|
||||
{
|
||||
var result = await ReplayOrchestrator.ReplayAsync(
|
||||
trace.Trace,
|
||||
TimeProvider);
|
||||
|
||||
VerifyExpectation(result, expectation);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replay all traces matching query and collect results.
|
||||
/// </summary>
|
||||
/// <param name="query">Query for traces to replay.</param>
|
||||
/// <param name="expectationFactory">Factory to create expectations per trace.</param>
|
||||
/// <returns>Batch replay results.</returns>
|
||||
protected async Task<ReplayBatchResult> ReplayBatchAsync(
|
||||
TraceQuery query,
|
||||
Func<TraceCorpusEntry, ReplayExpectation> expectationFactory)
|
||||
{
|
||||
var results = new List<(TraceCorpusEntry Trace, ReplayResult Result, bool Passed)>();
|
||||
|
||||
await foreach (var trace in CorpusManager.QueryAsync(query))
|
||||
{
|
||||
var expectation = expectationFactory(trace);
|
||||
var result = await ReplayOrchestrator.ReplayAsync(trace.Trace, TimeProvider);
|
||||
|
||||
var passed = VerifyExpectationSafe(result, expectation);
|
||||
results.Add((trace, result, passed));
|
||||
}
|
||||
|
||||
return new ReplayBatchResult([.. results]);
|
||||
}
|
||||
|
||||
private static void VerifyExpectation(ReplayResult result, ReplayExpectation expectation)
|
||||
{
|
||||
if (expectation.ShouldSucceed)
|
||||
{
|
||||
result.Success.Should().BeTrue(
|
||||
$"Replay should succeed: {result.FailureReason}");
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Success.Should().BeFalse(
|
||||
$"Replay should fail with: {expectation.ExpectedFailure}");
|
||||
}
|
||||
|
||||
if (expectation.ExpectedOutputHash is not null)
|
||||
{
|
||||
result.OutputHash.Should().Be(expectation.ExpectedOutputHash,
|
||||
"Output hash should match expected");
|
||||
}
|
||||
}
|
||||
|
||||
private static bool VerifyExpectationSafe(ReplayResult result, ReplayExpectation expectation)
|
||||
{
|
||||
try
|
||||
{
|
||||
VerifyExpectation(result, expectation);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual ValueTask DisposeAsync() => ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Expected outcome of a trace replay.
|
||||
/// </summary>
|
||||
/// <param name="ShouldSucceed">Whether replay should succeed.</param>
|
||||
/// <param name="ExpectedFailure">Expected failure reason, if should fail.</param>
|
||||
/// <param name="ExpectedOutputHash">Expected output hash for determinism check.</param>
|
||||
/// <param name="ExpectedWarnings">Expected warnings.</param>
|
||||
public sealed record ReplayExpectation(
|
||||
bool ShouldSucceed,
|
||||
string? ExpectedFailure = null,
|
||||
string? ExpectedOutputHash = null,
|
||||
ImmutableArray<string> ExpectedWarnings = default);
|
||||
|
||||
/// <summary>
|
||||
/// Result of a batch replay operation.
|
||||
/// </summary>
|
||||
/// <param name="Results">Individual trace results.</param>
|
||||
public sealed record ReplayBatchResult(
|
||||
ImmutableArray<(TraceCorpusEntry Trace, ReplayResult Result, bool Passed)> Results)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the total number of traces replayed.
|
||||
/// </summary>
|
||||
public int TotalCount => Results.Length;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of traces that passed.
|
||||
/// </summary>
|
||||
public int PassedCount => Results.Count(r => r.Passed);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of traces that failed.
|
||||
/// </summary>
|
||||
public int FailedCount => Results.Count(r => !r.Passed);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pass rate as a decimal (0-1).
|
||||
/// </summary>
|
||||
public decimal PassRate => TotalCount > 0 ? (decimal)PassedCount / TotalCount : 0;
|
||||
}
|
||||
Reference in New Issue
Block a user