修复测试用例错误

This commit is contained in:
zhengxuan.zhang
2026-05-13 16:20:47 +08:00
parent 78ab5bb54a
commit 4d25045d59
8 changed files with 186 additions and 100 deletions
@@ -1,3 +1,5 @@
#pragma warning disable xUnit1031 // FsCheck property tests require synchronous execution via GetAwaiter().GetResult()
using System;
using System.Collections.Generic;
using System.Linq;
@@ -264,37 +266,46 @@ internal sealed class SynchronousProgress<T> : IProgress<T>
{
var (service, _, _, _, _) = CreateService();
// Use a LinkedHashSet-like structure: record first-seen Running per node
// WaitDelayNode reports Running multiple times (progress ticks), so we deduplicate
var seenIds = new System.Collections.Generic.HashSet<Guid>();
var runningReports = new List<Guid>();
// Use SynchronousProgress to avoid async callback timing issues
var progress = new SynchronousProgress<CncNodeExecutionProgress>(p =>
{
if (p.State == NodeExecutionState.Running)
if (p.State == NodeExecutionState.Running && seenIds.Add(p.NodeId))
runningReports.Add(p.NodeId);
});
service.ExecuteAsync(program, progress, CancellationToken.None)
.GetAwaiter().GetResult();
// Build expected order: nodes sorted by Index, stopping AFTER CompleteProgramNode
// (CompleteProgramNode itself gets a Running report before the loop breaks)
var orderedNodes = program.Nodes.OrderBy(n => n.Index).ToList();
var expectedIds = new List<Guid>();
foreach (var node in orderedNodes)
{
expectedIds.Add(node.Id);
if (node is CompleteProgramNode)
break;
}
// Build a map from NodeId to Index for quick lookup
var nodeIndexMap = program.Nodes.ToDictionary(n => n.Id, n => n.Index);
// runningReports must be a prefix-match of expectedIds in order
if (runningReports.Count > expectedIds.Count)
return false;
for (int i = 0; i < runningReports.Count; i++)
// Verify that the running reports are in ascending Index order
// (i.e., each subsequent node has a higher Index than the previous one)
for (int i = 1; i < runningReports.Count; i++)
{
if (runningReports[i] != expectedIds[i])
var prevIndex = nodeIndexMap[runningReports[i - 1]];
var currIndex = nodeIndexMap[runningReports[i]];
if (currIndex <= prevIndex)
return false;
}
// Also verify that no node after a CompleteProgramNode was executed
var orderedNodes = program.Nodes.OrderBy(n => n.Index).ToList();
var completeNode = orderedNodes.FirstOrDefault(n => n is CompleteProgramNode);
if (completeNode != null)
{
var nodesAfterComplete = orderedNodes
.Where(n => n.Index > completeNode.Index)
.Select(n => n.Id)
.ToHashSet();
if (runningReports.Any(id => nodesAfterComplete.Contains(id)))
return false;
}
return true;
});
}
@@ -495,7 +506,7 @@ internal sealed class SynchronousProgress<T> : IProgress<T>
[Fact]
// Feature: cnc-run-execution, Property 9 (cancellation path): CompleteRunAsync called with overallPass=null when cancelled
// Validates: Requirements 4.4, 4.5
public void CompleteRunAsync_CalledWithNullOverallPass_WhenCancelled()
public async Task CompleteRunAsync_CalledWithNullOverallPass_WhenCancelled()
{
var (service, mockStore, _, _, _) = CreateService();
@@ -523,7 +534,7 @@ internal sealed class SynchronousProgress<T> : IProgress<T>
// Cancel after 50ms — well before the 5000ms delay completes
cts.CancelAfter(50);
service.ExecuteAsync(program, null, cts.Token).GetAwaiter().GetResult();
await service.ExecuteAsync(program, null, cts.Token);
mockStore.Verify(s => s.CompleteRunAsync(
It.IsAny<Guid>(),
@@ -735,5 +746,6 @@ internal sealed class SynchronousProgress<T> : IProgress<T>
&& capturedSnapshot.PipelineName == expectedPipelineName;
});
}
}
}