using Flyshot.Core.Domain; using Flyshot.CSharp.Planning; using Flyshot.Core.Planning; using Flyshot.Native.Planning; namespace Flyshot.Core.Tests; /// /// 验证 Rust ICSP 托管互操作边界,包括原生库加载失败时的诊断信息。 /// public sealed class RustIcspPlannerInteropTests { /// /// 验证显式指定不存在的原生库时,规划器会抛出包含库路径的明确异常。 /// [Fact] public void RustIcspPlanner_ThrowsClearError_WhenNativeLibraryIsMissing() { var planner = new RustIcspPlanner( options: NativeIcspPlannerOptions.Default, libraryResolver: new NativePlanningLibraryResolver(_ => "missing-library")); var exception = Assert.Throws(() => planner.Plan(CreateSimpleRequest())); Assert.Contains("missing-library", exception.Message); } /// /// 验证实际原生规划结果会被复制为现有 。 /// [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); } /// /// 验证互操作测试夹具在两套实现中都由相同的最终 scale 规则拒绝。 /// [Fact] public void ManagedIcspPlanner_RejectsInteropFixtureWithExpectedFinalScale() { var planner = new ICspPlanner(); var exception = Assert.Throws( () => planner.Plan(CreateSimpleRequest())); Assert.Contains("global_scale=1.058684", exception.Message); } /// /// 验证进程已加载默认 DLL 后,显式指定其他缺失库仍会暴露部署错误。 /// [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( () => missingPlanner.Plan(CreateSimpleRequest())); Assert.Contains("different-missing-library", exception.Message); } /// /// 验证 Rust 未收敛错误码和线程局部错误文本会转换为可诊断的托管异常。 /// [Fact] public void RustIcspPlanner_ConvertsNativeNotConvergedError() { var planner = new RustIcspPlanner( NativeIcspPlannerOptions.Default with { MaxIterations = 0 }); var exception = Assert.Throws( () => planner.Plan(CreateSimpleRequest())); Assert.Equal(3, exception.ErrorCodeValue); Assert.Contains("global_scale", exception.Message); } /// /// 创建用于互操作边界测试的最小四路点单自由度规划请求。 /// /// 满足 ICSP 最小路点数量要求的轨迹请求。 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); } }