221 lines
7.4 KiB
C#
221 lines
7.4 KiB
C#
|
|
using VisionHost.Contracts;
|
|
using VisionHost.Runtime;
|
|
using Xunit;
|
|
|
|
namespace VisionHost.Runtime.Tests;
|
|
|
|
public sealed class RunServiceAcceptanceTests
|
|
{
|
|
private static (RunService run, StubFrameSource cam, ScriptedConfirm confirm) New(
|
|
IEnumerable<bool>? start = null,
|
|
IEnumerable<bool>? stop = null)
|
|
{
|
|
var cam = new StubFrameSource();
|
|
var confirm = new ScriptedConfirm(start, stop);
|
|
var run = new RunService(cam, cam, new StubInspectService(), new StubPlcIo(), confirm);
|
|
return (run, cam, confirm);
|
|
}
|
|
|
|
[Fact]
|
|
public void Disconnected_StartDisabled_NoConfirm()
|
|
{
|
|
var (run, cam, confirm) = New();
|
|
cam.SetConnection(ConnectionState.Disconnected);
|
|
var s = run.Snapshot();
|
|
Assert.Equal(RunState.Disconnected, s.RunState);
|
|
Assert.Equal("断连", s.BadgeText);
|
|
Assert.False(s.CanStart);
|
|
Assert.Equal("未连接", s.ConnectionText);
|
|
Assert.False(run.RequestStart());
|
|
Assert.Equal(0, confirm.StartPrompts);
|
|
run.Dispose();
|
|
}
|
|
|
|
[Fact]
|
|
public void Reconnecting_StartDisabled_NoConfirm()
|
|
{
|
|
var (run, cam, confirm) = New();
|
|
cam.SetConnection(ConnectionState.Reconnecting);
|
|
Assert.False(run.Snapshot().CanStart);
|
|
Assert.False(run.RequestStart());
|
|
Assert.Equal(0, confirm.StartPrompts);
|
|
run.Dispose();
|
|
}
|
|
|
|
[Fact]
|
|
public void Start_CancelKeepsIdle()
|
|
{
|
|
var (run, _, confirm) = New(start: new[] { false });
|
|
Assert.True(run.Snapshot().CanStart);
|
|
Assert.False(run.RequestStart());
|
|
Assert.Equal(1, confirm.StartPrompts);
|
|
Assert.False(run.Snapshot().IsRunning);
|
|
Assert.Equal(RunState.Idle, run.Snapshot().RunState);
|
|
run.Dispose();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Start_Confirm_ClearsCounts_AndRuns()
|
|
{
|
|
var (run, _, _) = New(start: new[] { true }, stop: new[] { true });
|
|
Assert.True(run.RequestStart());
|
|
await WaitAsync(() => run.Snapshot().TotalCount >= 1);
|
|
var mid = run.Snapshot();
|
|
Assert.True(mid.IsRunning);
|
|
Assert.True(mid.CanStop);
|
|
Assert.False(mid.CanStart);
|
|
Assert.True(run.RequestStop());
|
|
Assert.True(mid.TotalCount >= 1);
|
|
|
|
var confirm2 = new ScriptedConfirm(new[] { true }, new[] { true });
|
|
run.Dispose();
|
|
var cam = new StubFrameSource();
|
|
var run2 = new RunService(cam, cam, new StubInspectService(), new StubPlcIo(), confirm2);
|
|
Assert.True(run2.RequestStart());
|
|
Assert.Equal(0, run2.Snapshot().OkCount);
|
|
Assert.Equal(0, run2.Snapshot().NgCount);
|
|
run2.Dispose();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Stop_CancelKeepsRunning_ConfirmKeepsCounts()
|
|
{
|
|
var confirm = new ScriptedConfirm(start: new[] { true }, stop: new[] { false, true });
|
|
var cam = new StubFrameSource();
|
|
using var run = new RunService(cam, cam, new StubInspectService(), new StubPlcIo(), confirm);
|
|
Assert.True(run.RequestStart());
|
|
await WaitAsync(() => run.Snapshot().TotalCount >= 2);
|
|
var before = run.Snapshot().TotalCount;
|
|
Assert.False(run.RequestStop());
|
|
Assert.True(run.Snapshot().IsRunning);
|
|
Assert.Equal(1, confirm.StopPrompts);
|
|
Assert.True(run.RequestStop());
|
|
var after = run.Snapshot();
|
|
Assert.False(after.IsRunning);
|
|
Assert.True(after.TotalCount >= before);
|
|
Assert.True(after.CanStart);
|
|
Assert.False(after.CanStop);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task FifthShot_IsNgStub()
|
|
{
|
|
var (run, _, _) = New(start: new[] { true }, stop: new[] { true });
|
|
run.RequestStart();
|
|
await WaitAsync(() => run.Snapshot().TotalCount >= 5, 5000);
|
|
var s = run.Snapshot();
|
|
Assert.True(s.NgCount >= 1);
|
|
Assert.Contains(s.LastResultText, new[] { "NG STUB", "OK" });
|
|
await WaitAsync(() => run.Snapshot().LastResultText == "NG STUB" || run.Snapshot().NgCount >= 1);
|
|
run.RequestStop();
|
|
run.Dispose();
|
|
}
|
|
|
|
[Fact]
|
|
public void LastResult_Dash_WhenNoShot()
|
|
{
|
|
var (run, _, _) = New();
|
|
Assert.Equal("—", run.Snapshot().LastResultText);
|
|
run.Dispose();
|
|
}
|
|
|
|
[Fact]
|
|
public void LastResult_NgWithoutCode_IsNgOnly()
|
|
{
|
|
var cam = new StubFrameSource();
|
|
var inspect = new FixedInspect(new InspectResult(false, null, 1, null));
|
|
using var run = new RunService(cam, cam, inspect, new StubPlcIo(), new AlwaysConfirm());
|
|
run.RequestStart();
|
|
cam.GetType(); // keep
|
|
// inject via queue by starting grab
|
|
WaitAsync(() => run.Snapshot().TotalCount >= 1).GetAwaiter().GetResult();
|
|
Assert.Equal("NG", run.Snapshot().LastResultText);
|
|
run.RequestStop();
|
|
}
|
|
|
|
[Fact]
|
|
public void Alarm_WhileConnected_CanStopIfRunning()
|
|
{
|
|
var (run, _, _) = New(start: new[] { true });
|
|
run.RequestStart();
|
|
run.RaiseAlarm("光源异常");
|
|
var s = run.Snapshot();
|
|
Assert.Equal(RunState.Alarm, s.RunState);
|
|
Assert.Equal("报警", s.BadgeText);
|
|
Assert.Equal("光源异常", s.AlarmText);
|
|
Assert.True(s.CanStop);
|
|
run.Dispose();
|
|
}
|
|
|
|
[Fact]
|
|
public void Disconnect_OverridesAlarm_StartDisabled()
|
|
{
|
|
var (run, cam, confirm) = New();
|
|
run.RaiseAlarm("光源异常");
|
|
cam.SetConnection(ConnectionState.Disconnected);
|
|
var s = run.Snapshot();
|
|
Assert.Equal(RunState.Disconnected, s.RunState);
|
|
Assert.False(s.CanStart);
|
|
Assert.False(run.RequestStart());
|
|
Assert.Equal(0, confirm.StartPrompts);
|
|
run.Dispose();
|
|
}
|
|
|
|
[Fact]
|
|
public void Counts_PersistAcrossStop_ResetOnNextStart()
|
|
{
|
|
var confirm = new ScriptedConfirm(start: new[] { true, true }, stop: new[] { true });
|
|
var cam = new StubFrameSource();
|
|
using var run = new RunService(cam, cam, new StubInspectService(), new StubPlcIo(), confirm);
|
|
run.RequestStart();
|
|
WaitAsync(() => run.Snapshot().TotalCount >= 3).GetAwaiter().GetResult();
|
|
run.RequestStop();
|
|
var kept = run.Snapshot().TotalCount;
|
|
Assert.True(kept >= 3);
|
|
run.RequestStart();
|
|
Assert.Equal(0, run.Snapshot().TotalCount);
|
|
}
|
|
|
|
private static async Task WaitAsync(Func<bool> pred, int ms = 3000)
|
|
{
|
|
var until = DateTime.UtcNow.AddMilliseconds(ms);
|
|
while (DateTime.UtcNow < until)
|
|
{
|
|
if (pred()) return;
|
|
await Task.Delay(20);
|
|
}
|
|
throw new TimeoutException("condition not met");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Changed_FromWorker_PostsToUiDispatch()
|
|
{
|
|
var ui = new RecordingDispatch();
|
|
using var vm = RunViewModel.CreateStub(new AlwaysConfirm(), ui);
|
|
Assert.True(vm.Service.RequestStart());
|
|
await WaitAsync(() => vm.TotalCount >= 1);
|
|
Assert.True(ui.PostCount >= 1);
|
|
vm.Service.RequestStop();
|
|
}
|
|
|
|
private sealed class RecordingDispatch : IUiDispatch
|
|
{
|
|
public int PostCount;
|
|
|
|
public void Post(Action action)
|
|
{
|
|
PostCount++;
|
|
action();
|
|
}
|
|
}
|
|
|
|
private sealed class FixedInspect : IInspectService
|
|
{
|
|
private readonly InspectResult _r;
|
|
public FixedInspect(InspectResult r) => _r = r;
|
|
public Task<InspectResult> Inspect(object frameHandle, string recipeId, CancellationToken cancellationToken)
|
|
=> Task.FromResult(_r);
|
|
}
|
|
}
|