Files
FlyShotHost/tests/Flyshot.Core.Tests/RustIcspPlannerInteropTests.cs
yunxiao.zhu 9c56ee825e ✨ feat(native-planning): 添加 Rust ICSP 原生规划核心及 C# 互操作集成
* 新增 Rust 原生 ICSP 规划库 (Cargo 项目, CubicSpline 算法, FFI 接口)
* 新增 Flyshot.Native.Planning C# 互操作项目 (P/Invoke 封装)
* 更新 ControllerClientCompat 适配新的规划接口
* 添加构建脚本 (Copy-NativePlanningArtifacts.ps1)
* 新增互操作测试及一致性对比测试
* 更新解决方案文件和设计文档
2026-06-26 15:41:01 +08:00

136 lines
4.9 KiB
C#

using Flyshot.Core.Domain;
using Flyshot.Core.Planning;
using Flyshot.Native.Planning;
namespace Flyshot.Core.Tests;
/// <summary>
/// 验证 Rust ICSP 托管互操作边界,包括原生库加载失败时的诊断信息。
/// </summary>
public sealed class RustIcspPlannerInteropTests
{
/// <summary>
/// 验证显式指定不存在的原生库时,规划器会抛出包含库路径的明确异常。
/// </summary>
[Fact]
public void RustIcspPlanner_ThrowsClearError_WhenNativeLibraryIsMissing()
{
var planner = new RustIcspPlanner(
options: NativeIcspPlannerOptions.Default,
libraryResolver: new NativePlanningLibraryResolver(_ => "missing-library"));
var exception = Assert.Throws<NativePlanningException>(() => planner.Plan(CreateSimpleRequest()));
Assert.Contains("missing-library", exception.Message);
}
/// <summary>
/// 验证实际原生规划结果会被复制为现有 <see cref="Flyshot.Core.Planning.PlannedTrajectory"/>。
/// </summary>
[Fact]
public void RustIcspPlanner_ReturnsManagedPlannedTrajectory_FromNativeResult()
{
var planner = new RustIcspPlanner(
NativeIcspPlannerOptions.Default with { EnforceFinalScale = false });
var result = planner.Plan(CreateSimpleRequest());
Assert.Equal(PlanningMethod.IcspRust, result.Method);
Assert.Equal(result.PlannedWaypointCount, result.WaypointTimes.Count);
Assert.Equal(result.PlannedWaypointCount - 1, result.SegmentDurations.Count);
Assert.Equal(result.SegmentDurations.Count, result.SegmentScales.Count);
}
/// <summary>
/// 验证互操作测试夹具在两套实现中都由相同的最终 scale 规则拒绝。
/// </summary>
[Fact]
public void ManagedIcspPlanner_RejectsInteropFixtureWithExpectedFinalScale()
{
var planner = new ICspPlanner();
var exception = Assert.Throws<InvalidOperationException>(
() => planner.Plan(CreateSimpleRequest()));
Assert.Contains("global_scale=1.058684", exception.Message);
}
/// <summary>
/// 验证进程已加载默认 DLL 后,显式指定其他缺失库仍会暴露部署错误。
/// </summary>
[Fact]
public void RustIcspPlanner_DoesNotReuseLoadedLibrary_ForDifferentExplicitPath()
{
var defaultPlanner = new RustIcspPlanner(
NativeIcspPlannerOptions.Default with { EnforceFinalScale = false });
_ = defaultPlanner.Plan(CreateSimpleRequest());
var missingPlanner = new RustIcspPlanner(
options: NativeIcspPlannerOptions.Default,
libraryResolver: new NativePlanningLibraryResolver(_ => "different-missing-library"));
var exception = Assert.Throws<NativePlanningException>(
() => missingPlanner.Plan(CreateSimpleRequest()));
Assert.Contains("different-missing-library", exception.Message);
}
/// <summary>
/// 验证 Rust 未收敛错误码和线程局部错误文本会转换为可诊断的托管异常。
/// </summary>
[Fact]
public void RustIcspPlanner_ConvertsNativeNotConvergedError()
{
var planner = new RustIcspPlanner(
NativeIcspPlannerOptions.Default with { MaxIterations = 0 });
var exception = Assert.Throws<NativePlanningException>(
() => planner.Plan(CreateSimpleRequest()));
Assert.Equal(3, exception.ErrorCodeValue);
Assert.Contains("global_scale", exception.Message);
}
/// <summary>
/// 创建用于互操作边界测试的最小四路点单自由度规划请求。
/// </summary>
/// <returns>满足 ICSP 最小路点数量要求的轨迹请求。</returns>
private static TrajectoryRequest CreateSimpleRequest()
{
var robot = new RobotProfile(
name: "rust-interop-test",
modelPath: "test.robot",
degreesOfFreedom: 1,
jointLimits:
[
new JointLimit(
jointName: "J1",
velocityLimit: 2.0,
accelerationLimit: 4.0,
jerkLimit: 8.0)
],
jointCouplings: [],
servoPeriod: TimeSpan.FromMilliseconds(8),
triggerPeriod: TimeSpan.FromMilliseconds(8));
var program = new FlyshotProgram(
name: "rust-interop-test",
waypoints:
[
new JointWaypoint([0.0]),
new JointWaypoint([0.5]),
new JointWaypoint([1.0]),
new JointWaypoint([1.5])
],
shotFlags: [false, false, false, false],
offsetValues: [0, 0, 0, 0],
addressGroups:
[
new IoAddressGroup([]),
new IoAddressGroup([]),
new IoAddressGroup([]),
new IoAddressGroup([])
]);
return new TrajectoryRequest(robot, program, PlanningMethod.Icsp);
}
}