Files
ParkingRobot/ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/ContractChecks.cs
T

140 lines
6.6 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using TrajectoryPlanningVisualization;
namespace TrajectoryPlanningVisualizationVerificationHost;
internal static class ContractChecks
{
public static void Run()
{
CopiesCollectionsAndRejectsNonFiniteNumbers();
StoresOccupancyAsDefensiveCompactBits();
ExposesImmutableSnapshotContracts();
ExposesChartAnnotationContract();
DocumentsPublicSafetyAndLifecycleBoundary();
}
private static void CopiesCollectionsAndRejectsNonFiniteNumbers()
{
var points = new List<VisualizationPoint> { new VisualizationPoint(1d, 2d) };
var series = new VisualizationSeries("current", "当前轨迹", VisualizationLineStyle.Solid, points);
points[0] = new VisualizationPoint(9d, 9d);
Verification.NearlyEqual(1d, series.Points[0].X, "series copies points");
Verification.Throws<ArgumentOutOfRangeException>(
() => new VisualizationPoint(double.NaN, 0d), "point rejects NaN");
Verification.Throws<ArgumentOutOfRangeException>(
() => new VisualizationPose(0d, 0d, double.PositiveInfinity), "pose rejects infinity");
Verification.Throws<ArgumentOutOfRangeException>(
() => new VisualizationBounds(0d, 1d, 0d, double.NaN), "bounds rejects NaN");
Verification.Throws<ArgumentException>(
() => new VisualizationSeries("invalid", "无效", VisualizationLineStyle.Solid,
new VisualizationPoint[] { null! }), "series rejects null point");
}
private static void StoresOccupancyAsDefensiveCompactBits()
{
byte[] bits = { 0x01 };
var grid = new VisualizationOccupancyGrid(new VisualizationBounds(0d, 1d, 0d, 1d),
0.5d, rows: 2, columns: 2, bits);
bits[0] = 0x00;
Verification.Equal("AQ==", grid.OccupancyBitsBase64, "occupancy grid copies compact bits");
Verification.Throws<ArgumentException>(
() => new VisualizationOccupancyGrid(new VisualizationBounds(0d, 1d, 0d, 1d),
0.5d, rows: 2, columns: 2, new byte[2]), "occupancy grid validates bit length");
}
private static void ExposesImmutableSnapshotContracts()
{
var staticSnapshot = new PlanningVisualizationStaticSnapshot(
"会话",
new VisualizationBounds(0d, 1d, 0d, 1d),
null,
Array.Empty<VisualizationPolyline>(),
Array.Empty<VisualizationMarker>(),
Array.Empty<VisualizationDirectionSegment>(),
Array.Empty<VisualizationConfigurationGroup>());
var dynamicSnapshot = new PlanningVisualizationDynamicSnapshot(
1L,
DateTimeOffset.UtcNow,
"运行中",
0,
"forward",
new VisualizationPose(0d, 0d, 0d),
Array.Empty<VisualizationPolyline>(),
Array.Empty<VisualizationMarker>(),
Array.Empty<VisualizationChart>(),
Array.Empty<VisualizationValue>(),
new VisualizationCycleSummary(1L, DateTimeOffset.UtcNow, "成功", true, 1d, 0,
"forward", "rolling", "none", null, null, ""));
Verification.True(staticSnapshot.StaticPolylines.GetType().Name.Contains("ReadOnlyCollection"),
"static snapshot uses read-only collection");
Verification.True(dynamicSnapshot.Charts.GetType().Name.Contains("ReadOnlyCollection"),
"dynamic snapshot uses read-only collection");
AssertNoSetters(typeof(PlanningVisualizationStaticSnapshot));
AssertNoSetters(typeof(PlanningVisualizationDynamicSnapshot));
}
private static void AssertNoSetters(Type type)
{
foreach (PropertyInfo property in type.GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
Verification.True(property.SetMethod == null, type.Name + " exposes no property setters");
}
}
private static void ExposesChartAnnotationContract()
{
Type chartType = typeof(VisualizationChart);
PropertyInfo annotationsProperty = chartType.GetProperty("Annotations");
Verification.True(annotationsProperty != null, "chart exposes immutable annotations");
Type annotationType = chartType.Assembly.GetType("TrajectoryPlanningVisualization.VisualizationChartAnnotation");
Verification.True(annotationType != null, "chart annotation contract type exists");
var chart = new VisualizationChart("annotation-legacy", "标题", "x (m)", "y (m)",
new[]
{
new VisualizationSeries("s", "轨迹", VisualizationLineStyle.Solid,
new[] { new VisualizationPoint(0d, 0d) })
});
if (annotationsProperty == null)
{
return;
}
object value = annotationsProperty.GetValue(chart);
Verification.True(value is System.Collections.IEnumerable, "chart annotations are enumerable");
int count = 0;
foreach (object annotation in (System.Collections.IEnumerable)value)
{
count++;
}
Verification.Equal(0, count, "legacy chart constructor keeps annotations empty");
AssertNoSetters(chartType);
}
private static void DocumentsPublicSafetyAndLifecycleBoundary()
{
string readmePath = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory,
"..", "..", "..", "..", "..", "TrajectoryPlanningVisualization", "README.md"));
string readme = File.ReadAllText(readmePath);
Verification.True(readme.Contains("OBSERVE_ONLY"), "README documents observe-only boundary");
Verification.True(readme.Contains("127.0.0.1"), "README documents loopback boundary");
Verification.True(readme.Contains("capacity 1"), "README documents latest-frame capacity");
Verification.True(readme.Contains("60"), "README documents default history limit");
Verification.True(readme.Contains("10 Hz"), "README documents default refresh rate");
Verification.True(readme.Contains("j[i]"), "README documents jerk interval semantics");
Verification.True(readme.Contains("末点后无时间区间"), "README documents jerk terminal explanation");
Verification.True(readme.Contains("PlanningVisualizationSessionInfo info = visualization.Start(staticSnapshot);"),
"README documents Start example");
Verification.True(readme.Contains("visualization.Publish(dynamicSnapshot);"), "README documents Publish example");
Verification.True(readme.Contains("visualization.Stop();"), "README documents Stop example");
}
}