#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 { /// /// Property 9: 运行摘要字段完整性与一致性 /// **Validates: Requirements 4.1, 4.2** /// 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 ImagePersistence) CreateServiceWithFailurePattern(List imageAcquisitionFailures) { var mockStore = new Mock(); var mockLogger = new Mock(); var mockMainViewportService = new Mock(); var mockAppStateService = new Mock(); var mockPipelineExecutionService = new Mock(); var mockImageProcessingService = new Mock(); var mockEventAggregator = new Mock(); var mockImagePersistenceService = new Mock(); mockLogger.Setup(l => l.ForModule()).Returns(mockLogger.Object); mockEventAggregator .Setup(ea => ea.GetEvent()) .Returns(new DetectorDisconnectedEvent()); mockStore.Setup(s => s.BeginRunAsync( It.IsAny(), It.IsAny())) .Returns(Task.CompletedTask); mockStore.Setup(s => s.AppendNodeResultAsync( It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny>())) .Returns(Task.CompletedTask); mockStore.Setup(s => s.CompleteRunAsync( It.IsAny(), It.IsAny(), It.IsAny())) .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(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(new ImageSaveResult(true, @"C:\test\image.bmp", 1024)); mockImagePersistenceService .Setup(s => s.WriteSummaryAsync( It.IsAny(), It.IsAny(), It.IsAny())) .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> 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().ToList().AsReadOnly()); var (service, mockImagePersistence) = CreateServiceWithFailurePattern(failures); BatchCaptureResult capturedResult = null; mockImagePersistence .Setup(s => s.WriteSummaryAsync( It.IsAny(), It.IsAny(), It.IsAny())) .Callback((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().ToList().AsReadOnly()); var (service, mockImagePersistence) = CreateServiceWithFailurePattern(failures); BatchCaptureResult capturedResult = null; mockImagePersistence .Setup(s => s.WriteSummaryAsync( It.IsAny(), It.IsAny(), It.IsAny())) .Callback((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; }); } } }