* 新增 Rust 原生 ICSP 规划库 (Cargo 项目, CubicSpline 算法, FFI 接口) * 新增 Flyshot.Native.Planning C# 互操作项目 (P/Invoke 封装) * 更新 ControllerClientCompat 适配新的规划接口 * 添加构建脚本 (Copy-NativePlanningArtifacts.ps1) * 新增互操作测试及一致性对比测试 * 更新解决方案文件和设计文档
136 lines
4.9 KiB
C#
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);
|
|
}
|
|
}
|