311 lines
9.9 KiB
C#
311 lines
9.9 KiB
C#
using System;
|
|
using MultiWheelC.Fleet;
|
|
using MyParking.Shared;
|
|
|
|
namespace MultiWheelC.Tests
|
|
{
|
|
internal static class FleetPreparationCoordinatorTests
|
|
{
|
|
private const double Tolerance = 1e-9;
|
|
|
|
public static void Run()
|
|
{
|
|
VerifyLeaderYawExample();
|
|
VerifyTailToTailUsesEquivalentAxis();
|
|
VerifyAllMembersMustBeReady();
|
|
VerifyStaleStatusIsIgnored();
|
|
VerifyMemberFaultIsLatched();
|
|
VerifyFaultAfterAuthorizationIsLatched();
|
|
VerifyPrematureActiveIsRejected();
|
|
VerifyCancelClearsPlan();
|
|
|
|
Console.WriteLine(
|
|
"FleetPreparationCoordinator测试通过。共8个场景。");
|
|
}
|
|
|
|
private static void VerifyLeaderYawExample()
|
|
{
|
|
var capture = FleetLayoutCapture.Capture(
|
|
new[]
|
|
{
|
|
new FleetMemberPose(
|
|
1,
|
|
new Pose2D(
|
|
-1.0,
|
|
0.0,
|
|
AngleMath.DegreesToRadians(20.0))),
|
|
new FleetMemberPose(
|
|
2,
|
|
new Pose2D(1.0, 0.0, 0.0))
|
|
},
|
|
leaderVehicleId: 1);
|
|
var coordinator =
|
|
new FleetPreparationCoordinator();
|
|
|
|
coordinator.StartRollingPreparation(
|
|
planId: 1,
|
|
capture.Layout,
|
|
motionDirectionInFleetRadians: 0.0);
|
|
|
|
AssertTargetDegrees(coordinator, 1, 0.0);
|
|
AssertTargetDegrees(coordinator, 2, 20.0);
|
|
}
|
|
|
|
private static void VerifyTailToTailUsesEquivalentAxis()
|
|
{
|
|
var coordinator =
|
|
new FleetPreparationCoordinator();
|
|
coordinator.StartRollingPreparation(
|
|
planId: 2,
|
|
CreateTailToTailLayout(),
|
|
motionDirectionInFleetRadians: 0.0);
|
|
|
|
AssertTargetDegrees(coordinator, 1, 0.0);
|
|
AssertTargetDegrees(coordinator, 2, 0.0);
|
|
}
|
|
|
|
private static void VerifyAllMembersMustBeReady()
|
|
{
|
|
var coordinator = CreateStartedCoordinator(3);
|
|
|
|
AssertState(
|
|
coordinator.ReportMemberStatus(
|
|
CreateStatus(
|
|
3,
|
|
1,
|
|
FleetMemberAgentState.Ready)),
|
|
FleetPreparationCoordinatorState
|
|
.WaitingForMembers,
|
|
"仅一辆车Ready");
|
|
AssertState(
|
|
coordinator.ReportMemberStatus(
|
|
CreateStatus(
|
|
3,
|
|
2,
|
|
FleetMemberAgentState.Ready)),
|
|
FleetPreparationCoordinatorState
|
|
.ReadyToActivate,
|
|
"全部成员Ready");
|
|
AssertState(
|
|
coordinator.ReportMemberStatus(
|
|
CreateStatus(
|
|
3,
|
|
1,
|
|
FleetMemberAgentState.Preparing)),
|
|
FleetPreparationCoordinatorState
|
|
.WaitingForMembers,
|
|
"成员失去Ready");
|
|
AssertState(
|
|
coordinator.ReportMemberStatus(
|
|
CreateStatus(
|
|
3,
|
|
1,
|
|
FleetMemberAgentState.Ready)),
|
|
FleetPreparationCoordinatorState
|
|
.ReadyToActivate,
|
|
"成员重新Ready");
|
|
|
|
if (coordinator.TryAuthorizeActivation(4))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"错误任务编号不应获得激活授权。");
|
|
}
|
|
|
|
if (!coordinator.TryAuthorizeActivation(3))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"全部成员Ready后没有获得激活授权。");
|
|
}
|
|
|
|
AssertState(
|
|
coordinator.State,
|
|
FleetPreparationCoordinatorState
|
|
.ActivationAuthorized,
|
|
"统一激活授权");
|
|
}
|
|
|
|
private static void VerifyStaleStatusIsIgnored()
|
|
{
|
|
var coordinator = CreateStartedCoordinator(5);
|
|
var state = coordinator.ReportMemberStatus(
|
|
CreateStatus(
|
|
4,
|
|
1,
|
|
FleetMemberAgentState.Ready));
|
|
|
|
AssertState(
|
|
state,
|
|
FleetPreparationCoordinatorState
|
|
.WaitingForMembers,
|
|
"旧任务状态报告");
|
|
}
|
|
|
|
private static void VerifyMemberFaultIsLatched()
|
|
{
|
|
var coordinator = CreateStartedCoordinator(6);
|
|
var state = coordinator.ReportMemberStatus(
|
|
CreateStatus(
|
|
6,
|
|
2,
|
|
FleetMemberAgentState.Faulted,
|
|
"舵轮未到位"));
|
|
|
|
AssertState(
|
|
state,
|
|
FleetPreparationCoordinatorState.Faulted,
|
|
"成员准备故障");
|
|
if (string.IsNullOrWhiteSpace(
|
|
coordinator.LastFailureReason))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"成员准备故障没有保存原因。");
|
|
}
|
|
}
|
|
|
|
private static void VerifyFaultAfterAuthorizationIsLatched()
|
|
{
|
|
var coordinator = CreateStartedCoordinator(9);
|
|
coordinator.ReportMemberStatus(
|
|
CreateStatus(
|
|
9,
|
|
1,
|
|
FleetMemberAgentState.Ready));
|
|
coordinator.ReportMemberStatus(
|
|
CreateStatus(
|
|
9,
|
|
2,
|
|
FleetMemberAgentState.Ready));
|
|
coordinator.TryAuthorizeActivation(9);
|
|
|
|
var state = coordinator.ReportMemberStatus(
|
|
CreateStatus(
|
|
9,
|
|
2,
|
|
FleetMemberAgentState.Faulted,
|
|
"激活失败"));
|
|
|
|
AssertState(
|
|
state,
|
|
FleetPreparationCoordinatorState.Faulted,
|
|
"授权后的成员故障");
|
|
}
|
|
|
|
private static void VerifyPrematureActiveIsRejected()
|
|
{
|
|
var coordinator = CreateStartedCoordinator(7);
|
|
var state = coordinator.ReportMemberStatus(
|
|
CreateStatus(
|
|
7,
|
|
1,
|
|
FleetMemberAgentState.Active));
|
|
|
|
AssertState(
|
|
state,
|
|
FleetPreparationCoordinatorState.Faulted,
|
|
"成员提前运动");
|
|
}
|
|
|
|
private static void VerifyCancelClearsPlan()
|
|
{
|
|
var coordinator = CreateStartedCoordinator(8);
|
|
coordinator.Cancel();
|
|
|
|
AssertState(
|
|
coordinator.State,
|
|
FleetPreparationCoordinatorState.Idle,
|
|
"取消准备任务");
|
|
if (coordinator.CurrentPlanId != 0 ||
|
|
coordinator.Targets.Count != 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"取消后没有清除准备任务数据。");
|
|
}
|
|
}
|
|
|
|
private static FleetPreparationCoordinator
|
|
CreateStartedCoordinator(long planId)
|
|
{
|
|
var coordinator =
|
|
new FleetPreparationCoordinator();
|
|
coordinator.StartRollingPreparation(
|
|
planId,
|
|
CreateTailToTailLayout(),
|
|
motionDirectionInFleetRadians: 0.0);
|
|
return coordinator;
|
|
}
|
|
|
|
private static FleetLayout CreateTailToTailLayout()
|
|
{
|
|
return new FleetLayout(
|
|
new[]
|
|
{
|
|
new VehicleLayout(
|
|
1,
|
|
new Pose2D(-1.0, 0.0, 0.0)),
|
|
new VehicleLayout(
|
|
2,
|
|
new Pose2D(1.0, 0.0, Math.PI))
|
|
});
|
|
}
|
|
|
|
private static FleetMemberPreparationStatus CreateStatus(
|
|
long planId,
|
|
int vehicleId,
|
|
FleetMemberAgentState state,
|
|
string failureReason = "")
|
|
{
|
|
return new FleetMemberPreparationStatus(
|
|
planId,
|
|
vehicleId,
|
|
state,
|
|
failureReason);
|
|
}
|
|
|
|
private static void AssertTargetDegrees(
|
|
FleetPreparationCoordinator coordinator,
|
|
int vehicleId,
|
|
double expectedDegrees)
|
|
{
|
|
if (!coordinator.TryGetTarget(
|
|
vehicleId,
|
|
out var target))
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"没有找到车辆{vehicleId}的准备目标。");
|
|
}
|
|
|
|
AssertNear(
|
|
AngleMath.RadiansToDegrees(
|
|
target.MotionDirectionInBodyRadians),
|
|
expectedDegrees,
|
|
$"车辆{vehicleId}本地β");
|
|
}
|
|
|
|
private static void AssertState(
|
|
FleetPreparationCoordinatorState actual,
|
|
FleetPreparationCoordinatorState expected,
|
|
string scenario)
|
|
{
|
|
if (actual != expected)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"{scenario}状态错误:" +
|
|
$"actual={actual}, expected={expected}。");
|
|
}
|
|
}
|
|
|
|
private static void AssertNear(
|
|
double actual,
|
|
double expected,
|
|
string valueName)
|
|
{
|
|
if (Math.Abs(actual - expected) > Tolerance)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"{valueName}错误:" +
|
|
$"actual={actual:F9}, expected={expected:F9}。");
|
|
}
|
|
}
|
|
}
|
|
}
|