218 lines
9.8 KiB
C#
218 lines
9.8 KiB
C#
#pragma warning disable xUnit1031 // FsCheck property tests require synchronous execution via GetAwaiter().GetResult()
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Windows.Media;
|
|
using System.Windows.Media.Imaging;
|
|
using FsCheck;
|
|
using FsCheck.Fluent;
|
|
using FsCheck.Xunit;
|
|
using Moq;
|
|
using Prism.Events;
|
|
using XP.Common.Logging.Interfaces;
|
|
using XP.Hardware.Detector.Abstractions;
|
|
using XplorePlane.Events;
|
|
using XplorePlane.Models;
|
|
using XplorePlane.Services;
|
|
using XplorePlane.Services.AppState;
|
|
using XplorePlane.Services.Cnc;
|
|
using XplorePlane.Services.InspectionResults;
|
|
using XplorePlane.Services.MainViewport;
|
|
using Xunit;
|
|
|
|
namespace XplorePlane.Tests.Services
|
|
{
|
|
/// <summary>
|
|
/// Property 9: 运行摘要字段完整性与一致性
|
|
/// **Validates: Requirements 4.1, 4.2**
|
|
/// </summary>
|
|
public class SummaryCorrectnessPropertyTests
|
|
{
|
|
private static BitmapSource CreateFrozenBitmap()
|
|
{
|
|
var bitmap = BitmapSource.Create(1, 1, 96, 96, PixelFormats.Bgra32, null, new byte[4], 4);
|
|
bitmap.Freeze();
|
|
return bitmap;
|
|
}
|
|
|
|
private static (CncExecutionService Service, Mock<IImagePersistenceService> ImagePersistence)
|
|
CreateServiceWithFailurePattern(List<bool> imageAcquisitionFailures)
|
|
{
|
|
var mockStore = new Mock<IInspectionResultStore>();
|
|
var mockLogger = new Mock<ILoggerService>();
|
|
var mockMainViewportService = new Mock<IMainViewportService>();
|
|
var mockAppStateService = new Mock<IAppStateService>();
|
|
var mockPipelineExecutionService = new Mock<IPipelineExecutionService>();
|
|
var mockImageProcessingService = new Mock<IImageProcessingService>();
|
|
var mockEventAggregator = new Mock<IEventAggregator>();
|
|
var mockImagePersistenceService = new Mock<IImagePersistenceService>();
|
|
|
|
mockLogger.Setup(l => l.ForModule<CncExecutionService>()).Returns(mockLogger.Object);
|
|
mockEventAggregator
|
|
.Setup(ea => ea.GetEvent<DetectorDisconnectedEvent>())
|
|
.Returns(new DetectorDisconnectedEvent());
|
|
mockStore.Setup(s => s.BeginRunAsync(
|
|
It.IsAny<InspectionRunRecord>(),
|
|
It.IsAny<InspectionAssetWriteRequest>()))
|
|
.Returns(Task.CompletedTask);
|
|
mockStore.Setup(s => s.AppendNodeResultAsync(
|
|
It.IsAny<InspectionNodeResult>(),
|
|
It.IsAny<IEnumerable<InspectionMetricResult>>(),
|
|
It.IsAny<PipelineExecutionSnapshot>(),
|
|
It.IsAny<IEnumerable<InspectionAssetWriteRequest>>()))
|
|
.Returns(Task.CompletedTask);
|
|
mockStore.Setup(s => s.CompleteRunAsync(
|
|
It.IsAny<Guid>(),
|
|
It.IsAny<bool?>(),
|
|
It.IsAny<DateTime?>()))
|
|
.Returns(Task.CompletedTask);
|
|
|
|
var frozenBitmap = CreateFrozenBitmap();
|
|
int imageCallCount = 0;
|
|
mockMainViewportService
|
|
.SetupGet(m => m.LatestManualImage)
|
|
.Returns(() =>
|
|
{
|
|
int currentPos = imageCallCount++;
|
|
if (currentPos < imageAcquisitionFailures.Count && imageAcquisitionFailures[currentPos])
|
|
return null;
|
|
return frozenBitmap;
|
|
});
|
|
mockMainViewportService
|
|
.SetupGet(m => m.CurrentDisplayImage)
|
|
.Returns((ImageSource)null);
|
|
mockAppStateService
|
|
.SetupGet(a => a.LatestDetectorFrame)
|
|
.Returns((ImageCapturedEventArgs)null);
|
|
|
|
mockImagePersistenceService
|
|
.Setup(s => s.SaveImageAsync(
|
|
It.IsAny<byte[]>(), It.IsAny<string>(),
|
|
It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(new ImageSaveResult(true, @"C:\test\image.bmp", 1024));
|
|
mockImagePersistenceService
|
|
.Setup(s => s.WriteSummaryAsync(
|
|
It.IsAny<BatchCaptureResult>(), It.IsAny<string>(),
|
|
It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(true);
|
|
|
|
var service = new CncExecutionService(
|
|
mockStore.Object, mockLogger.Object,
|
|
mockMainViewportService.Object, mockAppStateService.Object,
|
|
mockPipelineExecutionService.Object, mockImageProcessingService.Object,
|
|
mockEventAggregator.Object, mockImagePersistenceService.Object);
|
|
|
|
return (service, mockImagePersistenceService);
|
|
}
|
|
|
|
private static Gen<List<SavePositionNode>> SavePositionNodesGen =>
|
|
from count in Gen.Choose(1, 8)
|
|
select Enumerable.Range(0, count)
|
|
.Select(i => new SavePositionNode(
|
|
Guid.NewGuid(), i, $"Position_{i}",
|
|
MotionState.Default, SaveImage: true))
|
|
.ToList();
|
|
|
|
// Feature: cnc-multi-position-image-capture, Property 9: 运行摘要字段完整性与一致性
|
|
// **Validates: Requirements 4.1, 4.2**
|
|
[Property(MaxTest = 100)]
|
|
public Property Summary_SucceededPlusFailedEqualsTotalPositions()
|
|
{
|
|
return Prop.ForAll(
|
|
SavePositionNodesGen.ToArbitrary(),
|
|
nodes =>
|
|
{
|
|
var random = new Random(nodes.GetHashCode());
|
|
var failures = nodes.Select(_ => random.Next(2) == 0).ToList();
|
|
int totalPositions = nodes.Count;
|
|
|
|
var program = new CncProgram(
|
|
Guid.NewGuid(), "TestProgram",
|
|
DateTime.UtcNow, DateTime.UtcNow,
|
|
nodes.Cast<CncNode>().ToList().AsReadOnly());
|
|
|
|
var (service, mockImagePersistence) = CreateServiceWithFailurePattern(failures);
|
|
|
|
BatchCaptureResult capturedResult = null;
|
|
mockImagePersistence
|
|
.Setup(s => s.WriteSummaryAsync(
|
|
It.IsAny<BatchCaptureResult>(), It.IsAny<string>(),
|
|
It.IsAny<CancellationToken>()))
|
|
.Callback<BatchCaptureResult, string, CancellationToken>((r, _, _) => capturedResult = r)
|
|
.ReturnsAsync(true);
|
|
|
|
service.ExecuteAsync(program, null, CancellationToken.None)
|
|
.GetAwaiter().GetResult();
|
|
|
|
if (capturedResult == null) return false;
|
|
|
|
bool sumCorrect = capturedResult.SucceededPositions + capturedResult.FailedPositions
|
|
== capturedResult.TotalPositions;
|
|
bool totalCorrect = capturedResult.TotalPositions == totalPositions;
|
|
bool statusCorrect = capturedResult.Status == "Completed";
|
|
bool positionsCountCorrect = capturedResult.Positions.Count == totalPositions;
|
|
bool aggregatesMatch = false;
|
|
if (positionsCountCorrect)
|
|
{
|
|
int succeeded = capturedResult.Positions.Count(p => p.Status == "Success");
|
|
int failed = capturedResult.Positions.Count(p => p.Status == "Failed");
|
|
aggregatesMatch = succeeded == capturedResult.SucceededPositions
|
|
&& failed == capturedResult.FailedPositions;
|
|
}
|
|
|
|
return sumCorrect && totalCorrect && statusCorrect
|
|
&& positionsCountCorrect && aggregatesMatch;
|
|
});
|
|
}
|
|
|
|
// Feature: cnc-multi-position-image-capture, Property 9: 运行摘要字段完整性与一致性
|
|
// **Validates: Requirements 4.1, 4.2**
|
|
[Property(MaxTest = 100)]
|
|
public Property Summary_PositionStatusReflectsImageAcquisitionOutcome()
|
|
{
|
|
return Prop.ForAll(
|
|
SavePositionNodesGen.ToArbitrary(),
|
|
nodes =>
|
|
{
|
|
var random = new Random(nodes.Sum(n => n.Index) * 17 + nodes.Count);
|
|
var failures = nodes.Select(_ => random.Next(2) == 0).ToList();
|
|
int totalPositions = nodes.Count;
|
|
|
|
var program = new CncProgram(
|
|
Guid.NewGuid(), "TestProgram",
|
|
DateTime.UtcNow, DateTime.UtcNow,
|
|
nodes.Cast<CncNode>().ToList().AsReadOnly());
|
|
|
|
var (service, mockImagePersistence) = CreateServiceWithFailurePattern(failures);
|
|
|
|
BatchCaptureResult capturedResult = null;
|
|
mockImagePersistence
|
|
.Setup(s => s.WriteSummaryAsync(
|
|
It.IsAny<BatchCaptureResult>(), It.IsAny<string>(),
|
|
It.IsAny<CancellationToken>()))
|
|
.Callback<BatchCaptureResult, string, CancellationToken>((r, _, _) => capturedResult = r)
|
|
.ReturnsAsync(true);
|
|
|
|
service.ExecuteAsync(program, null, CancellationToken.None)
|
|
.GetAwaiter().GetResult();
|
|
|
|
if (capturedResult == null || capturedResult.Positions.Count != totalPositions)
|
|
return false;
|
|
|
|
for (int i = 0; i < totalPositions; i++)
|
|
{
|
|
var posResult = capturedResult.Positions[i];
|
|
bool imageAcquired = !failures[i];
|
|
string expectedStatus = imageAcquired ? "Success" : "Failed";
|
|
if (posResult.Status != expectedStatus) return false;
|
|
}
|
|
|
|
return true;
|
|
});
|
|
}
|
|
}
|
|
}
|