feat: add planning visualization contracts
This commit is contained in:
@@ -12,11 +12,17 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="TrajectoryPlanningVisualization\**\*.cs" />
|
||||
<Compile Remove="tests\TrajectoryPlanningVisualizationVerificationHost\**\*.cs" />
|
||||
<Compile Remove="tests\EMPlannerVerificationHost\**\*.cs" />
|
||||
<Compile Remove="ParkrobTrajplanner\auto_avoidance\**\*.cs"
|
||||
Condition="'$(ExcludeLegacyAutoAvoidance)' == 'true'" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="TrajectoryPlanningVisualization\TrajectoryPlanningVisualization.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="CommonUsage">
|
||||
<HintPath>ref\CommonUsage.dll</HintPath>
|
||||
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace TrajectoryPlanningVisualization;
|
||||
|
||||
public sealed class PlanningVisualizationStaticSnapshot
|
||||
{
|
||||
public PlanningVisualizationStaticSnapshot(string sessionNameChinese, VisualizationBounds worldBounds,
|
||||
VisualizationOccupancyGrid occupancyGrid, IReadOnlyList<VisualizationPolyline> staticPolylines,
|
||||
IReadOnlyList<VisualizationMarker> staticMarkers,
|
||||
IReadOnlyList<VisualizationDirectionSegment> directionSegments,
|
||||
IReadOnlyList<VisualizationConfigurationGroup> configurationGroups)
|
||||
{
|
||||
SessionNameChinese = VisualizationContract.RequireText(sessionNameChinese, nameof(sessionNameChinese));
|
||||
WorldBounds = worldBounds ?? throw new ArgumentNullException(nameof(worldBounds));
|
||||
OccupancyGrid = occupancyGrid;
|
||||
StaticPolylines = VisualizationContract.Copy(staticPolylines, nameof(staticPolylines));
|
||||
StaticMarkers = VisualizationContract.Copy(staticMarkers, nameof(staticMarkers));
|
||||
DirectionSegments = VisualizationContract.Copy(directionSegments, nameof(directionSegments));
|
||||
ConfigurationGroups = VisualizationContract.Copy(configurationGroups, nameof(configurationGroups));
|
||||
}
|
||||
|
||||
public string SessionNameChinese { get; }
|
||||
public VisualizationBounds WorldBounds { get; }
|
||||
public VisualizationOccupancyGrid OccupancyGrid { get; }
|
||||
public IReadOnlyList<VisualizationPolyline> StaticPolylines { get; }
|
||||
public IReadOnlyList<VisualizationMarker> StaticMarkers { get; }
|
||||
public IReadOnlyList<VisualizationDirectionSegment> DirectionSegments { get; }
|
||||
public IReadOnlyList<VisualizationConfigurationGroup> ConfigurationGroups { get; }
|
||||
}
|
||||
|
||||
public sealed class PlanningVisualizationDynamicSnapshot
|
||||
{
|
||||
public PlanningVisualizationDynamicSnapshot(long sequence, DateTimeOffset observedAtUtc,
|
||||
string sessionStateChinese, int activeSegmentIndex, string activeDirection,
|
||||
VisualizationPose vehiclePose, IReadOnlyList<VisualizationPolyline> dynamicPolylines,
|
||||
IReadOnlyList<VisualizationMarker> dynamicMarkers, IReadOnlyList<VisualizationChart> charts,
|
||||
IReadOnlyList<VisualizationValue> statusValues, VisualizationCycleSummary cycleSummary)
|
||||
{
|
||||
Sequence = sequence;
|
||||
ObservedAtUtc = observedAtUtc;
|
||||
SessionStateChinese = VisualizationContract.RequireText(sessionStateChinese, nameof(sessionStateChinese));
|
||||
ActiveSegmentIndex = activeSegmentIndex;
|
||||
ActiveDirection = VisualizationContract.RequireText(activeDirection, nameof(activeDirection));
|
||||
VehiclePose = vehiclePose ?? throw new ArgumentNullException(nameof(vehiclePose));
|
||||
DynamicPolylines = VisualizationContract.Copy(dynamicPolylines, nameof(dynamicPolylines));
|
||||
DynamicMarkers = VisualizationContract.Copy(dynamicMarkers, nameof(dynamicMarkers));
|
||||
Charts = VisualizationContract.Copy(charts, nameof(charts));
|
||||
StatusValues = VisualizationContract.Copy(statusValues, nameof(statusValues));
|
||||
CycleSummary = cycleSummary ?? throw new ArgumentNullException(nameof(cycleSummary));
|
||||
}
|
||||
|
||||
public long Sequence { get; }
|
||||
public DateTimeOffset ObservedAtUtc { get; }
|
||||
public string SessionStateChinese { get; }
|
||||
public int ActiveSegmentIndex { get; }
|
||||
public string ActiveDirection { get; }
|
||||
public VisualizationPose VehiclePose { get; }
|
||||
public IReadOnlyList<VisualizationPolyline> DynamicPolylines { get; }
|
||||
public IReadOnlyList<VisualizationMarker> DynamicMarkers { get; }
|
||||
public IReadOnlyList<VisualizationChart> Charts { get; }
|
||||
public IReadOnlyList<VisualizationValue> StatusValues { get; }
|
||||
public VisualizationCycleSummary CycleSummary { get; }
|
||||
}
|
||||
|
||||
public sealed class VisualizationCycleSummary
|
||||
{
|
||||
public VisualizationCycleSummary(long cycleVersion, DateTimeOffset occurredAtUtc, string status, bool published,
|
||||
double planningElapsedMilliseconds, int segmentIndex, string direction, string longitudinalMode,
|
||||
string terminalType, double? terminalVelocity, double? terminalAcceleration, string failureReason)
|
||||
{
|
||||
VisualizationContract.RequireFinite(planningElapsedMilliseconds, nameof(planningElapsedMilliseconds));
|
||||
VisualizationContract.RequireFinite(terminalVelocity, nameof(terminalVelocity));
|
||||
VisualizationContract.RequireFinite(terminalAcceleration, nameof(terminalAcceleration));
|
||||
CycleVersion = cycleVersion;
|
||||
OccurredAtUtc = occurredAtUtc;
|
||||
Status = VisualizationContract.RequireText(status, nameof(status));
|
||||
Published = published;
|
||||
PlanningElapsedMilliseconds = planningElapsedMilliseconds;
|
||||
SegmentIndex = segmentIndex;
|
||||
Direction = VisualizationContract.RequireText(direction, nameof(direction));
|
||||
LongitudinalMode = VisualizationContract.RequireText(longitudinalMode, nameof(longitudinalMode));
|
||||
TerminalType = VisualizationContract.RequireText(terminalType, nameof(terminalType));
|
||||
TerminalVelocity = terminalVelocity;
|
||||
TerminalAcceleration = terminalAcceleration;
|
||||
FailureReason = VisualizationContract.RequireText(failureReason, nameof(failureReason));
|
||||
}
|
||||
|
||||
public long CycleVersion { get; }
|
||||
public DateTimeOffset OccurredAtUtc { get; }
|
||||
public string Status { get; }
|
||||
public bool Published { get; }
|
||||
public double PlanningElapsedMilliseconds { get; }
|
||||
public int SegmentIndex { get; }
|
||||
public string Direction { get; }
|
||||
public string LongitudinalMode { get; }
|
||||
public string TerminalType { get; }
|
||||
public double? TerminalVelocity { get; }
|
||||
public double? TerminalAcceleration { get; }
|
||||
public string FailureReason { get; }
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace TrajectoryPlanningVisualization;
|
||||
|
||||
public enum VisualizationLineStyle
|
||||
{
|
||||
Solid,
|
||||
Dashed,
|
||||
Limit
|
||||
}
|
||||
|
||||
public sealed class VisualizationSeries
|
||||
{
|
||||
public VisualizationSeries(string id, string legend, VisualizationLineStyle lineStyle,
|
||||
IReadOnlyList<VisualizationPoint> points)
|
||||
{
|
||||
Id = VisualizationContract.RequireText(id, nameof(id));
|
||||
Legend = VisualizationContract.RequireText(legend, nameof(legend));
|
||||
LineStyle = lineStyle;
|
||||
Points = VisualizationContract.Copy(points, nameof(points));
|
||||
}
|
||||
|
||||
public string Id { get; }
|
||||
public string Legend { get; }
|
||||
public VisualizationLineStyle LineStyle { get; }
|
||||
public IReadOnlyList<VisualizationPoint> Points { get; }
|
||||
}
|
||||
|
||||
public sealed class VisualizationChart
|
||||
{
|
||||
public VisualizationChart(string id, string chineseTitle, string xAxisLabel, string yAxisLabel,
|
||||
IReadOnlyList<VisualizationSeries> series, string noteChinese = "")
|
||||
{
|
||||
Id = VisualizationContract.RequireText(id, nameof(id));
|
||||
ChineseTitle = VisualizationContract.RequireText(chineseTitle, nameof(chineseTitle));
|
||||
XAxisLabel = VisualizationContract.RequireText(xAxisLabel, nameof(xAxisLabel));
|
||||
YAxisLabel = VisualizationContract.RequireText(yAxisLabel, nameof(yAxisLabel));
|
||||
Series = VisualizationContract.Copy(series, nameof(series));
|
||||
NoteChinese = VisualizationContract.RequireText(noteChinese, nameof(noteChinese));
|
||||
}
|
||||
|
||||
public string Id { get; }
|
||||
public string ChineseTitle { get; }
|
||||
public string XAxisLabel { get; }
|
||||
public string YAxisLabel { get; }
|
||||
public IReadOnlyList<VisualizationSeries> Series { get; }
|
||||
public string NoteChinese { get; }
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace TrajectoryPlanningVisualization;
|
||||
|
||||
public sealed class VisualizationPolyline
|
||||
{
|
||||
public VisualizationPolyline(string id, string legendChinese, string kind, VisualizationLineStyle lineStyle,
|
||||
IReadOnlyList<VisualizationPoint> points)
|
||||
{
|
||||
Id = VisualizationContract.RequireText(id, nameof(id));
|
||||
LegendChinese = VisualizationContract.RequireText(legendChinese, nameof(legendChinese));
|
||||
Kind = VisualizationContract.RequireText(kind, nameof(kind));
|
||||
LineStyle = lineStyle;
|
||||
Points = VisualizationContract.Copy(points, nameof(points));
|
||||
}
|
||||
|
||||
public string Id { get; }
|
||||
public string LegendChinese { get; }
|
||||
public string Kind { get; }
|
||||
public VisualizationLineStyle LineStyle { get; }
|
||||
public IReadOnlyList<VisualizationPoint> Points { get; }
|
||||
}
|
||||
|
||||
public sealed class VisualizationMarker
|
||||
{
|
||||
public VisualizationMarker(string id, string kind, string labelChinese, VisualizationPoint position)
|
||||
{
|
||||
Id = VisualizationContract.RequireText(id, nameof(id));
|
||||
Kind = VisualizationContract.RequireText(kind, nameof(kind));
|
||||
LabelChinese = VisualizationContract.RequireText(labelChinese, nameof(labelChinese));
|
||||
Position = position ?? throw new ArgumentNullException(nameof(position));
|
||||
}
|
||||
|
||||
public string Id { get; }
|
||||
public string Kind { get; }
|
||||
public string LabelChinese { get; }
|
||||
public VisualizationPoint Position { get; }
|
||||
}
|
||||
|
||||
public sealed class VisualizationDirectionSegment
|
||||
{
|
||||
public VisualizationDirectionSegment(int segmentIndex, string direction, bool startsAtGearSwitch,
|
||||
bool endsAtGearSwitch, IReadOnlyList<VisualizationPoint> points)
|
||||
{
|
||||
SegmentIndex = segmentIndex;
|
||||
Direction = VisualizationContract.RequireText(direction, nameof(direction));
|
||||
StartsAtGearSwitch = startsAtGearSwitch;
|
||||
EndsAtGearSwitch = endsAtGearSwitch;
|
||||
Points = VisualizationContract.Copy(points, nameof(points));
|
||||
}
|
||||
|
||||
public int SegmentIndex { get; }
|
||||
public string Direction { get; }
|
||||
public bool StartsAtGearSwitch { get; }
|
||||
public bool EndsAtGearSwitch { get; }
|
||||
public IReadOnlyList<VisualizationPoint> Points { get; }
|
||||
}
|
||||
|
||||
public sealed class VisualizationOccupancyGrid
|
||||
{
|
||||
public VisualizationOccupancyGrid(VisualizationBounds bounds, double resolutionMeters, int rows, int columns,
|
||||
byte[] occupancyBits)
|
||||
{
|
||||
Bounds = bounds ?? throw new ArgumentNullException(nameof(bounds));
|
||||
VisualizationContract.RequireFinite(resolutionMeters, nameof(resolutionMeters));
|
||||
if (resolutionMeters <= 0d)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(resolutionMeters));
|
||||
}
|
||||
|
||||
if (rows <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(rows));
|
||||
}
|
||||
|
||||
if (columns <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(columns));
|
||||
}
|
||||
|
||||
if (occupancyBits == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(occupancyBits));
|
||||
}
|
||||
|
||||
long cellCount = (long)rows * columns;
|
||||
long requiredByteCount = (cellCount + 7L) / 8L;
|
||||
if (occupancyBits.Length != requiredByteCount)
|
||||
{
|
||||
throw new ArgumentException("Occupancy bit count does not match the grid dimensions.", nameof(occupancyBits));
|
||||
}
|
||||
|
||||
var bitsCopy = new byte[occupancyBits.Length];
|
||||
Buffer.BlockCopy(occupancyBits, 0, bitsCopy, 0, bitsCopy.Length);
|
||||
ResolutionMeters = resolutionMeters;
|
||||
Rows = rows;
|
||||
Columns = columns;
|
||||
OccupancyBitsBase64 = Convert.ToBase64String(bitsCopy);
|
||||
}
|
||||
|
||||
public VisualizationBounds Bounds { get; }
|
||||
public double ResolutionMeters { get; }
|
||||
public int Rows { get; }
|
||||
public int Columns { get; }
|
||||
public string OccupancyBitsBase64 { get; }
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace TrajectoryPlanningVisualization;
|
||||
|
||||
public sealed class VisualizationPoint
|
||||
{
|
||||
public VisualizationPoint(double x, double y)
|
||||
{
|
||||
VisualizationContract.RequireFinite(x, nameof(x));
|
||||
VisualizationContract.RequireFinite(y, nameof(y));
|
||||
X = x;
|
||||
Y = y;
|
||||
}
|
||||
|
||||
public double X { get; }
|
||||
public double Y { get; }
|
||||
}
|
||||
|
||||
public sealed class VisualizationPose
|
||||
{
|
||||
public VisualizationPose(double x, double y, double headingRadians)
|
||||
{
|
||||
VisualizationContract.RequireFinite(x, nameof(x));
|
||||
VisualizationContract.RequireFinite(y, nameof(y));
|
||||
VisualizationContract.RequireFinite(headingRadians, nameof(headingRadians));
|
||||
X = x;
|
||||
Y = y;
|
||||
HeadingRadians = headingRadians;
|
||||
}
|
||||
|
||||
public double X { get; }
|
||||
public double Y { get; }
|
||||
public double HeadingRadians { get; }
|
||||
}
|
||||
|
||||
public sealed class VisualizationBounds
|
||||
{
|
||||
public VisualizationBounds(double xMin, double xMax, double yMin, double yMax)
|
||||
{
|
||||
VisualizationContract.RequireFinite(xMin, nameof(xMin));
|
||||
VisualizationContract.RequireFinite(xMax, nameof(xMax));
|
||||
VisualizationContract.RequireFinite(yMin, nameof(yMin));
|
||||
VisualizationContract.RequireFinite(yMax, nameof(yMax));
|
||||
if (xMax < xMin || yMax < yMin)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(xMax), "Bounds maxima must not precede minima.");
|
||||
}
|
||||
|
||||
XMin = xMin;
|
||||
XMax = xMax;
|
||||
YMin = yMin;
|
||||
YMax = yMax;
|
||||
}
|
||||
|
||||
public double XMin { get; }
|
||||
public double XMax { get; }
|
||||
public double YMin { get; }
|
||||
public double YMax { get; }
|
||||
}
|
||||
|
||||
public sealed class VisualizationValue
|
||||
{
|
||||
public VisualizationValue(string chineseName, string rawName, string value, string unit, string severity)
|
||||
{
|
||||
ChineseName = VisualizationContract.RequireText(chineseName, nameof(chineseName));
|
||||
RawName = VisualizationContract.RequireText(rawName, nameof(rawName));
|
||||
Value = VisualizationContract.RequireText(value, nameof(value));
|
||||
Unit = VisualizationContract.RequireText(unit, nameof(unit));
|
||||
if (severity != "normal" && severity != "notice" && severity != "failure")
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(severity), "Severity must be normal, notice, or failure.");
|
||||
}
|
||||
|
||||
Severity = severity;
|
||||
}
|
||||
|
||||
public string ChineseName { get; }
|
||||
public string RawName { get; }
|
||||
public string Value { get; }
|
||||
public string Unit { get; }
|
||||
public string Severity { get; }
|
||||
}
|
||||
|
||||
public sealed class VisualizationConfigurationGroup
|
||||
{
|
||||
public VisualizationConfigurationGroup(string chineseTitle, IReadOnlyList<VisualizationValue> entries)
|
||||
{
|
||||
ChineseTitle = VisualizationContract.RequireText(chineseTitle, nameof(chineseTitle));
|
||||
Entries = VisualizationContract.Copy(entries, nameof(entries));
|
||||
}
|
||||
|
||||
public string ChineseTitle { get; }
|
||||
public IReadOnlyList<VisualizationValue> Entries { get; }
|
||||
}
|
||||
|
||||
internal static class VisualizationContract
|
||||
{
|
||||
public static string RequireText(string value, string parameterName)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(parameterName);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public static void RequireFinite(double value, string parameterName)
|
||||
{
|
||||
if (double.IsNaN(value) || double.IsInfinity(value))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(parameterName, "Value must be finite.");
|
||||
}
|
||||
}
|
||||
|
||||
public static void RequireFinite(double? value, string parameterName)
|
||||
{
|
||||
if (value.HasValue)
|
||||
{
|
||||
RequireFinite(value.Value, parameterName);
|
||||
}
|
||||
}
|
||||
|
||||
public static IReadOnlyList<T> Copy<T>(IReadOnlyList<T> values, string parameterName)
|
||||
{
|
||||
if (values == null)
|
||||
{
|
||||
throw new ArgumentNullException(parameterName);
|
||||
}
|
||||
|
||||
var copy = new List<T>(values.Count);
|
||||
for (var index = 0; index < values.Count; index++)
|
||||
{
|
||||
T value = values[index];
|
||||
if (value is null)
|
||||
{
|
||||
throw new ArgumentException("Collections cannot contain null elements.", parameterName);
|
||||
}
|
||||
|
||||
copy.Add(value);
|
||||
}
|
||||
|
||||
return new ReadOnlyCollection<T>(copy);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<LangVersion>10</LangVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using TrajectoryPlanningVisualization;
|
||||
|
||||
namespace TrajectoryPlanningVisualizationVerificationHost;
|
||||
|
||||
internal static class ContractChecks
|
||||
{
|
||||
public static void Run()
|
||||
{
|
||||
CopiesCollectionsAndRejectsNonFiniteNumbers();
|
||||
StoresOccupancyAsDefensiveCompactBits();
|
||||
ExposesImmutableSnapshotContracts();
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
|
||||
namespace TrajectoryPlanningVisualizationVerificationHost;
|
||||
|
||||
internal static class Program
|
||||
{
|
||||
private static int Main()
|
||||
{
|
||||
try
|
||||
{
|
||||
ContractChecks.Run();
|
||||
Console.WriteLine("PASS trajectory-planning-visualization");
|
||||
return 0;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Console.Error.WriteLine(exception);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0-windows</TargetFramework>
|
||||
<LangVersion>10</LangVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\TrajectoryPlanningVisualization\TrajectoryPlanningVisualization.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
|
||||
namespace TrajectoryPlanningVisualizationVerificationHost;
|
||||
|
||||
internal static class Verification
|
||||
{
|
||||
public static void Equal<T>(T expected, T actual, string message)
|
||||
{
|
||||
if (!Equals(expected, actual))
|
||||
{
|
||||
throw new InvalidOperationException(message + ": expected " + expected + ", actual " + actual);
|
||||
}
|
||||
}
|
||||
|
||||
public static void NearlyEqual(double expected, double actual, string message)
|
||||
{
|
||||
if (Math.Abs(expected - actual) > 0.0000001d)
|
||||
{
|
||||
throw new InvalidOperationException(message + ": expected " + expected + ", actual " + actual);
|
||||
}
|
||||
}
|
||||
|
||||
public static void True(bool value, string message)
|
||||
{
|
||||
if (!value)
|
||||
{
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Throws<TException>(Action action, string message)
|
||||
where TException : Exception
|
||||
{
|
||||
try
|
||||
{
|
||||
action();
|
||||
}
|
||||
catch (TException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user