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 { 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( () => new VisualizationPoint(double.NaN, 0d), "point rejects NaN"); Verification.Throws( () => new VisualizationPose(0d, 0d, double.PositiveInfinity), "pose rejects infinity"); Verification.Throws( () => new VisualizationBounds(0d, 1d, 0d, double.NaN), "bounds rejects NaN"); Verification.Throws( () => 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( () => 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(), Array.Empty(), Array.Empty(), Array.Empty()); var dynamicSnapshot = new PlanningVisualizationDynamicSnapshot( 1L, DateTimeOffset.UtcNow, "运行中", 0, "forward", new VisualizationPose(0d, 0d, 0d), Array.Empty(), Array.Empty(), Array.Empty(), Array.Empty(), 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"); } }