217 lines
10 KiB
C#
217 lines
10 KiB
C#
using Moq;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using XP.Common.Logging.Interfaces;
|
|
using XP.Hardware.Detector.Abstractions;
|
|
using XP.Hardware.Detector.Abstractions.Enums;
|
|
using XP.Hardware.Detector.Services;
|
|
using XP.Hardware.MotionControl.Abstractions;
|
|
using XP.Hardware.MotionControl.Abstractions.Enums;
|
|
using XP.Hardware.MotionControl.Services;
|
|
using XP.Hardware.RaySource.Abstractions;
|
|
using XP.Hardware.RaySource.Services;
|
|
using XplorePlane.Models.Inspection;
|
|
using XplorePlane.Services.AppState;
|
|
using XplorePlane.Services.Inspection;
|
|
using Xunit;
|
|
|
|
namespace XplorePlane.Tests.Inspection
|
|
{
|
|
public sealed class RealInspectionAcquisitionServiceTests
|
|
{
|
|
[Fact]
|
|
public async Task AcquireAsync_AppliesHardwareAndReturnsNewDetectorFrame()
|
|
{
|
|
var harness = CreateHarness();
|
|
harness.PublishFrameOnAcquire();
|
|
|
|
var result = await harness.Service.AcquireAsync(
|
|
Machine(), Acquisition(), CancellationToken.None);
|
|
|
|
Assert.Equal("Detector", result.Source);
|
|
Assert.Equal(2, result.Image.PixelWidth);
|
|
harness.Motion.Verify(x => x.MoveAllToTarget(It.IsAny<Dictionary<AxisId, double>>(), false), Times.Once);
|
|
harness.Detector.Verify(x => x.ApplyParametersAsync(0, 1, 10m, 2, It.IsAny<CancellationToken>()), Times.Once);
|
|
harness.Detector.Verify(x => x.AcquireSingleFrameAsync(It.IsAny<CancellationToken>()), Times.Once);
|
|
harness.Ray.Verify(x => x.SetVoltage(120), Times.Once);
|
|
harness.Ray.Verify(x => x.SetCurrent(80), Times.Once);
|
|
harness.Ray.Verify(x => x.TurnOn(), Times.Once);
|
|
harness.Ray.Verify(x => x.TurnOff(), Times.Once);
|
|
harness.Motion.Verify(x => x.StopAll(), Times.Never);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AcquireAsync_CancellationStopsMotionAndTurnsOffRay()
|
|
{
|
|
var harness = CreateHarness(new InspectionAcquisitionOptions
|
|
{
|
|
RayStabilizationDelay = TimeSpan.FromSeconds(5),
|
|
MotionTimeout = TimeSpan.FromSeconds(1),
|
|
AcquisitionTimeout = TimeSpan.FromSeconds(1),
|
|
CleanupTimeout = TimeSpan.FromMilliseconds(100)
|
|
});
|
|
using var cancellation = new CancellationTokenSource(TimeSpan.FromMilliseconds(30));
|
|
|
|
await Assert.ThrowsAnyAsync<OperationCanceledException>(() =>
|
|
harness.Service.AcquireAsync(Machine(), Acquisition(), cancellation.Token));
|
|
|
|
harness.Motion.Verify(x => x.StopAll(), Times.Once);
|
|
harness.Ray.Verify(x => x.TurnOff(), Times.Once);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AcquireAsync_OpenSafetyDoorRejectsBeforeMotionAndRay()
|
|
{
|
|
var harness = CreateHarness(doorStatus: DoorStatus.Open);
|
|
|
|
var error = await Assert.ThrowsAsync<InspectionAcquisitionException>(() =>
|
|
harness.Service.AcquireAsync(Machine(), Acquisition(), CancellationToken.None));
|
|
|
|
Assert.Contains("安全门", error.Message);
|
|
harness.Motion.Verify(x => x.MoveAllToTarget(It.IsAny<Dictionary<AxisId, double>>(), false), Times.Never);
|
|
harness.Ray.Verify(x => x.TurnOn(), Times.Never);
|
|
harness.Motion.Verify(x => x.StopAll(), Times.Once);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AcquireAsync_DisconnectedDetectorFailsBeforeHardwareCommands()
|
|
{
|
|
var harness = CreateHarness(detectorConnected: false);
|
|
|
|
var error = await Assert.ThrowsAsync<InspectionAcquisitionException>(() =>
|
|
harness.Service.AcquireAsync(Machine(), Acquisition(), CancellationToken.None));
|
|
|
|
Assert.Contains("探测器未连接", error.Message);
|
|
harness.Motion.Verify(x => x.MoveAllToTarget(It.IsAny<Dictionary<AxisId, double>>(), false), Times.Never);
|
|
harness.Ray.Verify(x => x.TurnOn(), Times.Never);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AcquireAsync_MissingNewFrameTimesOutAndCleansUp()
|
|
{
|
|
var harness = CreateHarness(new InspectionAcquisitionOptions
|
|
{
|
|
RayStabilizationDelay = TimeSpan.Zero,
|
|
MotionTimeout = TimeSpan.FromSeconds(1),
|
|
AcquisitionTimeout = TimeSpan.FromMilliseconds(40),
|
|
CleanupTimeout = TimeSpan.FromMilliseconds(100)
|
|
});
|
|
|
|
var error = await Assert.ThrowsAsync<InspectionAcquisitionException>(() =>
|
|
harness.Service.AcquireAsync(Machine(), Acquisition(), CancellationToken.None));
|
|
|
|
Assert.Contains("超时", error.Message);
|
|
harness.Motion.Verify(x => x.StopAll(), Times.Once);
|
|
harness.Ray.Verify(x => x.TurnOff(), Times.Once);
|
|
}
|
|
|
|
private static Harness CreateHarness(
|
|
InspectionAcquisitionOptions options = null,
|
|
DoorStatus doorStatus = DoorStatus.Closed,
|
|
bool detectorConnected = true)
|
|
{
|
|
var motion = new Mock<IMotionControlService>();
|
|
motion.Setup(x => x.MoveAllToTarget(It.IsAny<Dictionary<AxisId, double>>(), false))
|
|
.Returns(MotionResult.Ok());
|
|
motion.Setup(x => x.MoveRotaryToTarget(It.IsAny<RotaryAxisId>(), It.IsAny<double>(), It.IsAny<double?>()))
|
|
.Returns(MotionResult.Ok());
|
|
motion.Setup(x => x.StopAll()).Returns(MotionResult.Ok());
|
|
|
|
var safetyDoor = new Mock<ISafetyDoor>();
|
|
safetyDoor.SetupGet(x => x.Status).Returns(doorStatus);
|
|
var linearAxes = new Dictionary<AxisId, ILinearAxis>();
|
|
foreach (var id in Enum.GetValues<AxisId>())
|
|
{
|
|
var axis = new Mock<ILinearAxis>();
|
|
axis.SetupGet(x => x.Status).Returns(AxisStatus.Idle);
|
|
linearAxes[id] = axis.Object;
|
|
}
|
|
var rotaryAxes = new Dictionary<RotaryAxisId, IRotaryAxis>();
|
|
foreach (var id in Enum.GetValues<RotaryAxisId>())
|
|
{
|
|
var axis = new Mock<IRotaryAxis>();
|
|
axis.SetupGet(x => x.Enabled).Returns(true);
|
|
axis.SetupGet(x => x.Status).Returns(AxisStatus.Idle);
|
|
rotaryAxes[id] = axis.Object;
|
|
}
|
|
var motionSystem = new Mock<IMotionSystem>();
|
|
motionSystem.SetupGet(x => x.SafetyDoor).Returns(safetyDoor.Object);
|
|
motionSystem.SetupGet(x => x.LinearAxes).Returns(linearAxes);
|
|
motionSystem.SetupGet(x => x.RotaryAxes).Returns(rotaryAxes);
|
|
|
|
var ray = new Mock<IRaySourceService>();
|
|
ray.SetupGet(x => x.IsInitialized).Returns(true);
|
|
ray.SetupGet(x => x.IsConnected).Returns(true);
|
|
ray.SetupGet(x => x.IsXRayOn).Returns(false);
|
|
ray.Setup(x => x.SetVoltage(It.IsAny<float>())).Returns(XRayResult.Ok());
|
|
ray.Setup(x => x.SetCurrent(It.IsAny<float>())).Returns(XRayResult.Ok());
|
|
ray.Setup(x => x.TurnOn()).Returns(XRayResult.Ok());
|
|
ray.Setup(x => x.TurnOff()).Returns(XRayResult.Ok());
|
|
ray.Setup(x => x.EmergencyShutdown()).Returns(XRayResult.Ok());
|
|
|
|
var detector = new Mock<IDetectorService>();
|
|
detector.SetupGet(x => x.IsConnected).Returns(detectorConnected);
|
|
detector.SetupGet(x => x.Status).Returns(DetectorStatus.Ready);
|
|
detector.Setup(x => x.ApplyParametersAsync(
|
|
It.IsAny<int>(), It.IsAny<int>(), It.IsAny<decimal>(), It.IsAny<int>(), It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(DetectorResult.Success());
|
|
detector.Setup(x => x.AcquireSingleFrameAsync(It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(DetectorResult.Success());
|
|
|
|
ImageCapturedEventArgs latestFrame = null;
|
|
var appState = new Mock<IAppStateService>();
|
|
appState.SetupGet(x => x.LatestDetectorFrame).Returns(() => latestFrame);
|
|
|
|
var logger = new Mock<ILoggerService>();
|
|
logger.Setup(x => x.ForModule<RealInspectionAcquisitionService>()).Returns(logger.Object);
|
|
options ??= new InspectionAcquisitionOptions
|
|
{
|
|
RayStabilizationDelay = TimeSpan.Zero,
|
|
MotionTimeout = TimeSpan.FromSeconds(1),
|
|
AcquisitionTimeout = TimeSpan.FromSeconds(1),
|
|
CleanupTimeout = TimeSpan.FromMilliseconds(100)
|
|
};
|
|
var service = new RealInspectionAcquisitionService(
|
|
motion.Object,
|
|
motionSystem.Object,
|
|
ray.Object,
|
|
detector.Object,
|
|
appState.Object,
|
|
options,
|
|
logger.Object);
|
|
|
|
return new Harness(service, motion, ray, detector, frame => latestFrame = frame);
|
|
}
|
|
|
|
private static MachineParameters Machine() =>
|
|
new(1, 2, 3, 4, 5, 6, 7, 100, 200, 2);
|
|
|
|
private static AcquisitionParameters Acquisition() =>
|
|
new(120, 80, 100, 2, "Default", false, false, 0, 1, 10);
|
|
|
|
private sealed record Harness(
|
|
RealInspectionAcquisitionService Service,
|
|
Mock<IMotionControlService> Motion,
|
|
Mock<IRaySourceService> Ray,
|
|
Mock<IDetectorService> Detector,
|
|
Action<ImageCapturedEventArgs> SetFrame)
|
|
{
|
|
public void PublishFrameOnAcquire()
|
|
{
|
|
Detector.Setup(x => x.AcquireSingleFrameAsync(It.IsAny<CancellationToken>()))
|
|
.Callback(() => SetFrame(new ImageCapturedEventArgs
|
|
{
|
|
ImageData = new ushort[] { 0, 1000, 2000, ushort.MaxValue },
|
|
Width = 2,
|
|
Height = 2,
|
|
FrameNumber = 1,
|
|
CaptureTime = DateTime.UtcNow
|
|
}))
|
|
.ReturnsAsync(DetectorResult.Success());
|
|
}
|
|
}
|
|
}
|
|
}
|