Files
FlyShotHost/tests/Flyshot.Core.Tests/RustIcspPlannerInteropTests.cs
yunxiao.zhu 537c6b5320 💥 feat(planning)!: 拆分 C# ICSP 并默认 Rust
* 新增可选 Flyshot.CSharp.Planning 项目

* 将 icsp 映射到 Rust 后端并拒绝旧 icsp_rust

* 补充后端注册、文档、调试页和回归测试

BREAKING CHANGE: method=icsp 现在默认调用 Rust 后端;C# ICSP 改为 method=icsp-csharp,旧 method=icsp_rust 不再作为正式入口。
2026-06-27 15:03:05 +08:00

137 lines
5.0 KiB
C#

using Flyshot.Core.Domain;
using Flyshot.CSharp.Planning;
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.Icsp, 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);
}
}