CNC执行逻辑的开发,点击运行,停止
This commit is contained in:
@@ -0,0 +1,232 @@
|
||||
// Feature: cnc-run-execution
|
||||
// Properties 1, 2, 12: CncEditorViewModel execution control
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FsCheck;
|
||||
using FsCheck.Fluent;
|
||||
using FsCheck.Xunit;
|
||||
using Moq;
|
||||
using Prism.Events;
|
||||
using XP.Common.Logging.Interfaces;
|
||||
using XplorePlane.Models;
|
||||
using XplorePlane.Services.AppState;
|
||||
using XplorePlane.Services.Cnc;
|
||||
using XplorePlane.ViewModels.Cnc;
|
||||
using Xunit;
|
||||
|
||||
namespace XplorePlane.Tests.ViewModels
|
||||
{
|
||||
public class CncEditorViewModelTests
|
||||
{
|
||||
// ── Helpers ──────────────────────────────────────────────────────────
|
||||
|
||||
private static CncEditorViewModel CreateVm(
|
||||
Mock<ICncExecutionService> mockExecSvc = null,
|
||||
CncProgram initialProgram = null)
|
||||
{
|
||||
var mockCncProgramSvc = new Mock<ICncProgramService>();
|
||||
var mockAppState = new Mock<IAppStateService>();
|
||||
var mockLogger = new Mock<ILoggerService>();
|
||||
mockLogger.Setup(l => l.ForModule<CncEditorViewModel>()).Returns(mockLogger.Object);
|
||||
|
||||
mockExecSvc ??= new Mock<ICncExecutionService>();
|
||||
|
||||
// Setup CreateProgram so ExecuteNewProgram works
|
||||
mockCncProgramSvc
|
||||
.Setup(s => s.CreateProgram(It.IsAny<string>()))
|
||||
.Returns((string name) => new CncProgram(
|
||||
Guid.NewGuid(), name,
|
||||
DateTime.UtcNow, DateTime.UtcNow,
|
||||
new List<CncNode>
|
||||
{
|
||||
new ReferencePointNode(Guid.NewGuid(), 0, "参考点_0", 0, 0, 0, 0, 0, 0, false, 0, 0)
|
||||
}.AsReadOnly()));
|
||||
|
||||
var vm = new CncEditorViewModel(
|
||||
mockCncProgramSvc.Object,
|
||||
mockAppState.Object,
|
||||
new EventAggregator(),
|
||||
mockLogger.Object,
|
||||
mockExecSvc.Object);
|
||||
|
||||
if (initialProgram != null)
|
||||
{
|
||||
// Use reflection to set _currentProgram and refresh nodes
|
||||
var field = typeof(CncEditorViewModel)
|
||||
.GetField("_currentProgram", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
field?.SetValue(vm, initialProgram);
|
||||
|
||||
var refresh = typeof(CncEditorViewModel)
|
||||
.GetMethod("RefreshNodes", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
refresh?.Invoke(vm, null);
|
||||
}
|
||||
|
||||
return vm;
|
||||
}
|
||||
|
||||
private static CncProgram MakeProgram(int nodeCount = 2)
|
||||
{
|
||||
var nodes = Enumerable.Range(0, nodeCount)
|
||||
.Select(i => (CncNode)new ReferencePointNode(Guid.NewGuid(), i, $"Node_{i}", 0, 0, 0, 0, 0, 0, false, 0, 0))
|
||||
.ToList()
|
||||
.AsReadOnly();
|
||||
return new CncProgram(Guid.NewGuid(), "TestProgram", DateTime.UtcNow, DateTime.UtcNow, nodes);
|
||||
}
|
||||
|
||||
// ── Property 1: 运行/停止按钮状态互斥 ────────────────────────────────
|
||||
|
||||
// Feature: cnc-run-execution, Property 1: 运行/停止按钮状态互斥
|
||||
// Validates: Requirements 1.1, 1.3, 1.4
|
||||
[Property(MaxTest = 100)]
|
||||
public Property RunStop_Commands_AreMutuallyExclusive()
|
||||
{
|
||||
var gen =
|
||||
from nodeCount in Gen.Choose(1, 8)
|
||||
select MakeProgram(nodeCount);
|
||||
|
||||
return Prop.ForAll(gen.ToArbitrary(), program =>
|
||||
{
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
var mockExecSvc = new Mock<ICncExecutionService>();
|
||||
mockExecSvc
|
||||
.Setup(s => s.ExecuteAsync(
|
||||
It.IsAny<CncProgram>(),
|
||||
It.IsAny<IProgress<CncNodeExecutionProgress>>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.Returns(tcs.Task);
|
||||
|
||||
var vm = CreateVm(mockExecSvc, program);
|
||||
|
||||
// Before running: Run enabled, Stop disabled
|
||||
bool beforeRunOk = vm.RunCncCommand.CanExecute()
|
||||
&& !vm.StopCncCommand.CanExecute();
|
||||
|
||||
// Start execution (fire-and-forget, task is blocked by tcs)
|
||||
_ = Task.Run(() => vm.RunCncCommand.Execute());
|
||||
// Give the async method a moment to set IsRunning = true
|
||||
SpinWait.SpinUntil(() => vm.IsRunning, TimeSpan.FromMilliseconds(500));
|
||||
|
||||
bool duringRunOk = !vm.RunCncCommand.CanExecute()
|
||||
&& vm.StopCncCommand.CanExecute();
|
||||
|
||||
// Complete execution
|
||||
tcs.SetResult(true);
|
||||
SpinWait.SpinUntil(() => !vm.IsRunning, TimeSpan.FromMilliseconds(500));
|
||||
|
||||
bool afterRunOk = vm.RunCncCommand.CanExecute()
|
||||
&& !vm.StopCncCommand.CanExecute();
|
||||
|
||||
return beforeRunOk && duringRunOk && afterRunOk;
|
||||
});
|
||||
}
|
||||
|
||||
// ── Property 2: 执行完成后状态重置 ───────────────────────────────────
|
||||
|
||||
// Feature: cnc-run-execution, Property 2: 执行完成后状态重置
|
||||
// Validates: Requirements 1.7, 6.5
|
||||
[Property(MaxTest = 100)]
|
||||
public Property AfterExecution_IsRunningFalse_AllNodesIdle()
|
||||
{
|
||||
var gen =
|
||||
from nodeCount in Gen.Choose(1, 6)
|
||||
from cancelled in ArbMap.Default.GeneratorFor<bool>()
|
||||
select (nodeCount, cancelled);
|
||||
|
||||
return Prop.ForAll(gen.ToArbitrary(), tuple =>
|
||||
{
|
||||
var (nodeCount, cancelled) = tuple;
|
||||
var program = MakeProgram(nodeCount);
|
||||
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
var mockExecSvc = new Mock<ICncExecutionService>();
|
||||
mockExecSvc
|
||||
.Setup(s => s.ExecuteAsync(
|
||||
It.IsAny<CncProgram>(),
|
||||
It.IsAny<IProgress<CncNodeExecutionProgress>>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.Returns(tcs.Task);
|
||||
|
||||
var vm = CreateVm(mockExecSvc, program);
|
||||
|
||||
// Start execution
|
||||
_ = Task.Run(() => vm.RunCncCommand.Execute());
|
||||
SpinWait.SpinUntil(() => vm.IsRunning, TimeSpan.FromMilliseconds(500));
|
||||
|
||||
// Simulate some nodes going Running
|
||||
foreach (var node in vm.Nodes)
|
||||
node.ExecutionState = NodeExecutionState.Running;
|
||||
|
||||
// Complete or cancel
|
||||
if (cancelled)
|
||||
tcs.SetCanceled();
|
||||
else
|
||||
tcs.SetResult(true);
|
||||
|
||||
SpinWait.SpinUntil(() => !vm.IsRunning, TimeSpan.FromMilliseconds(500));
|
||||
|
||||
bool isRunningFalse = !vm.IsRunning;
|
||||
bool allIdle = vm.Nodes.All(n => n.ExecutionState == NodeExecutionState.Idle);
|
||||
|
||||
return isRunningFalse && allIdle;
|
||||
});
|
||||
}
|
||||
|
||||
// ── Property 12: 执行中编辑命令全部禁用 ──────────────────────────────
|
||||
|
||||
// Feature: cnc-run-execution, Property 12: 执行中编辑命令全部禁用
|
||||
// Validates: Requirements 6.7
|
||||
[Property(MaxTest = 100)]
|
||||
public Property WhileRunning_AllEditCommands_AreDisabled()
|
||||
{
|
||||
var gen =
|
||||
from nodeCount in Gen.Choose(2, 8)
|
||||
select MakeProgram(nodeCount);
|
||||
|
||||
return Prop.ForAll(gen.ToArbitrary(), program =>
|
||||
{
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
var mockExecSvc = new Mock<ICncExecutionService>();
|
||||
mockExecSvc
|
||||
.Setup(s => s.ExecuteAsync(
|
||||
It.IsAny<CncProgram>(),
|
||||
It.IsAny<IProgress<CncNodeExecutionProgress>>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.Returns(tcs.Task);
|
||||
|
||||
var vm = CreateVm(mockExecSvc, program);
|
||||
|
||||
// Start execution
|
||||
_ = Task.Run(() => vm.RunCncCommand.Execute());
|
||||
SpinWait.SpinUntil(() => vm.IsRunning, TimeSpan.FromMilliseconds(500));
|
||||
|
||||
// All insert/delete/move commands must be disabled
|
||||
var editCommands = new[]
|
||||
{
|
||||
vm.InsertReferencePointCommand.CanExecute(),
|
||||
vm.InsertSaveNodeWithImageCommand.CanExecute(),
|
||||
vm.InsertSaveNodeCommand.CanExecute(),
|
||||
vm.InsertSavePositionCommand.CanExecute(),
|
||||
vm.InsertInspectionModuleCommand.CanExecute(),
|
||||
vm.InsertInspectionMarkerCommand.CanExecute(),
|
||||
vm.InsertPauseDialogCommand.CanExecute(),
|
||||
vm.InsertWaitDelayCommand.CanExecute(),
|
||||
vm.InsertCompleteProgramCommand.CanExecute(),
|
||||
vm.DeleteNodeCommand.CanExecute(),
|
||||
};
|
||||
|
||||
bool allDisabled = editCommands.All(canExec => !canExec);
|
||||
|
||||
// Cleanup
|
||||
tcs.SetResult(true);
|
||||
SpinWait.SpinUntil(() => !vm.IsRunning, TimeSpan.FromMilliseconds(500));
|
||||
|
||||
return allDisabled;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user