test: freeze Local G2 diagnostic evidence
This commit is contained in:
+30
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"SourceMeasurementBatchSha256": "ac8166828813d85bf6f8b58f985839e5b2a049e75cb94186ed04f59d540e4eed",
|
||||||
|
"FixtureSha256": "3d05daee5a211b3e7aa0b77193423b5fa07d3135e241a4413be3518fc7efe563",
|
||||||
|
"ScenarioId": "single-turn",
|
||||||
|
"CandidateStableKey": "single-turn/s0/r0/w5/seed2",
|
||||||
|
"CandidateSha256": "7cefb76c77e48a49bf3212e7a2472e23036c3899daa94b5c6b68fa5db29a3392",
|
||||||
|
"CandidateIndex": 5,
|
||||||
|
"SegmentIndex": 0,
|
||||||
|
"WindowStartArcLengthMeters": 1.9199999999999982,
|
||||||
|
"WindowEndArcLengthMeters": 2.1199999999999983,
|
||||||
|
"StartGeometricCurvaturePerMeter": 0.41666666666666663,
|
||||||
|
"EndGeometricCurvaturePerMeter": 0.0,
|
||||||
|
"StartVehicleCurvaturePerMeter": 0.41666666666666663,
|
||||||
|
"EndVehicleCurvaturePerMeter": 0.0,
|
||||||
|
"EvaluatorResult": "InsufficientClearance",
|
||||||
|
"StopGate": "Clearance",
|
||||||
|
"PublishedStatus": "Unchanged",
|
||||||
|
"CandidatePoints": [
|
||||||
|
{ "X": 2.7216397035494579, "Y": 1.7279184433567971, "ReferenceArcLengthMeters": 1.9199999999999982, "HeadingRadians": 0.799999999999999, "UnwrappedHeadingRadians": 0.799999999999999, "Source": 4 },
|
||||||
|
{ "X": 2.7355188205174521, "Y": 1.7423187041196302, "ReferenceArcLengthMeters": 1.9399999999999982, "HeadingRadians": 0.80751185024996908, "UnwrappedHeadingRadians": 0.80751185024996908, "Source": 4 },
|
||||||
|
{ "X": 2.7492871654421882, "Y": 1.7568248978050907, "ReferenceArcLengthMeters": 1.9599999999999982, "HeadingRadians": 0.8157618125921976, "UnwrappedHeadingRadians": 0.8157618125921976, "Source": 4 },
|
||||||
|
{ "X": 2.7629234692741469, "Y": 1.7714552535786874, "ReferenceArcLengthMeters": 1.9799999999999982, "HeadingRadians": 0.82542852165703573, "UnwrappedHeadingRadians": 0.82542852165703573, "Source": 4 },
|
||||||
|
{ "X": 2.7764244476704873, "Y": 1.786210614200662, "ReferenceArcLengthMeters": 1.9999999999999982, "HeadingRadians": 0.83333333333333237, "UnwrappedHeadingRadians": 0.83333333333333237, "Source": 4 },
|
||||||
|
{ "X": 2.7925350453990334, "Y": 1.803999635249544, "ReferenceArcLengthMeters": 2.0239999999999982, "HeadingRadians": 0.835253334801853, "UnwrappedHeadingRadians": 0.835253334801853, "Source": 4 },
|
||||||
|
{ "X": 2.80865418003672, "Y": 1.8217809211927092, "ReferenceArcLengthMeters": 2.0479999999999983, "HeadingRadians": 0.83333333355178374, "UnwrappedHeadingRadians": 0.83333333355178374, "Source": 4 },
|
||||||
|
{ "X": 2.8248074262445737, "Y": 1.8395312269498194, "ReferenceArcLengthMeters": 2.0719999999999983, "HeadingRadians": 0.8318933325232104, "UnwrappedHeadingRadians": 0.8318933325232104, "Source": 4 },
|
||||||
|
{ "X": 2.8409691920174533, "Y": 1.8572737784943611, "ReferenceArcLengthMeters": 2.0959999999999983, "HeadingRadians": 0.83237333274470626, "UnwrappedHeadingRadians": 0.83237333274470626, "Source": 4 },
|
||||||
|
{ "X": 2.8571139169604551, "Y": 1.8750318365841865, "ReferenceArcLengthMeters": 2.1199999999999983, "HeadingRadians": 0.83333333333333237, "UnwrappedHeadingRadians": 0.83333333333333237, "Source": 4 }
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using MultiWheelC.TrajectoryPlanning.PathSmoothing;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace MultiWheelC.TrajectoryPlanning.PathSmoothing.Test;
|
||||||
|
|
||||||
|
public sealed class LocalG2DiagnosticEvidenceLoader
|
||||||
|
{
|
||||||
|
private const string SourceMeasurementBatchSha256 = "ac8166828813d85bf6f8b58f985839e5b2a049e75cb94186ed04f59d540e4eed";
|
||||||
|
private const string FixtureSha256 = "3d05daee5a211b3e7aa0b77193423b5fa07d3135e241a4413be3518fc7efe563";
|
||||||
|
private const string CandidateStableKey = "single-turn/s0/r0/w5/seed2";
|
||||||
|
private const string CandidateSha256 = "7cefb76c77e48a49bf3212e7a2472e23036c3899daa94b5c6b68fa5db29a3392";
|
||||||
|
|
||||||
|
public LocalG2DiagnosticEvidence LoadAndVerify(string evidencePath)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(evidencePath))
|
||||||
|
throw new InvalidDataException("Diagnostic evidence path is required.");
|
||||||
|
|
||||||
|
LocalG2DiagnosticEvidence evidence = JsonConvert.DeserializeObject<LocalG2DiagnosticEvidence>(File.ReadAllText(evidencePath));
|
||||||
|
Validate(evidence);
|
||||||
|
return evidence;
|
||||||
|
}
|
||||||
|
catch (InvalidDataException)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
throw new InvalidDataException("Local G2 diagnostic evidence is invalid.", exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Validate(LocalG2DiagnosticEvidence evidence)
|
||||||
|
{
|
||||||
|
if (evidence == null ||
|
||||||
|
!string.Equals(evidence.SourceMeasurementBatchSha256, SourceMeasurementBatchSha256, StringComparison.Ordinal) ||
|
||||||
|
!string.Equals(evidence.FixtureSha256, FixtureSha256, StringComparison.Ordinal) ||
|
||||||
|
!string.Equals(evidence.ScenarioId, "single-turn", StringComparison.Ordinal) ||
|
||||||
|
!string.Equals(evidence.CandidateStableKey, CandidateStableKey, StringComparison.Ordinal) ||
|
||||||
|
!string.Equals(evidence.CandidateSha256, CandidateSha256, StringComparison.Ordinal) ||
|
||||||
|
evidence.CandidateIndex != 5 || evidence.SegmentIndex != 0 ||
|
||||||
|
!string.Equals(evidence.EvaluatorResult, "InsufficientClearance", StringComparison.Ordinal) ||
|
||||||
|
!string.Equals(evidence.StopGate, "Clearance", StringComparison.Ordinal) ||
|
||||||
|
!string.Equals(evidence.PublishedStatus, "Unchanged", StringComparison.Ordinal) ||
|
||||||
|
!IsFinite(evidence.WindowStartArcLengthMeters) || !IsFinite(evidence.WindowEndArcLengthMeters) ||
|
||||||
|
evidence.WindowEndArcLengthMeters.Value <= evidence.WindowStartArcLengthMeters.Value ||
|
||||||
|
!IsFinite(evidence.StartGeometricCurvaturePerMeter) || !IsFinite(evidence.EndGeometricCurvaturePerMeter) ||
|
||||||
|
!IsFinite(evidence.StartVehicleCurvaturePerMeter) || !IsFinite(evidence.EndVehicleCurvaturePerMeter) ||
|
||||||
|
evidence.CandidatePoints == null || evidence.CandidatePoints.Count != 10)
|
||||||
|
{
|
||||||
|
throw new InvalidDataException("Local G2 diagnostic evidence contract is invalid.");
|
||||||
|
}
|
||||||
|
|
||||||
|
double previousArc = 0d;
|
||||||
|
for (int index = 0; index < evidence.CandidatePoints.Count; index++)
|
||||||
|
{
|
||||||
|
LocalG2DiagnosticEvidencePoint point = evidence.CandidatePoints[index];
|
||||||
|
if (point == null || !IsFinite(point.X) || !IsFinite(point.Y) || !IsFinite(point.ReferenceArcLengthMeters) ||
|
||||||
|
!IsFinite(point.HeadingRadians) || !IsFinite(point.UnwrappedHeadingRadians) ||
|
||||||
|
point.Source != (int)SmoothedPathPointSource.LocalG2Transition ||
|
||||||
|
(index > 0 && point.ReferenceArcLengthMeters.Value <= previousArc))
|
||||||
|
{
|
||||||
|
throw new InvalidDataException("Local G2 diagnostic evidence point contract is invalid.");
|
||||||
|
}
|
||||||
|
|
||||||
|
previousArc = point.ReferenceArcLengthMeters.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Math.Abs(evidence.CandidatePoints[0].ReferenceArcLengthMeters.Value - evidence.WindowStartArcLengthMeters.Value) > 1e-8d ||
|
||||||
|
Math.Abs(evidence.CandidatePoints[evidence.CandidatePoints.Count - 1].ReferenceArcLengthMeters.Value - evidence.WindowEndArcLengthMeters.Value) > 1e-8d)
|
||||||
|
{
|
||||||
|
throw new InvalidDataException("Local G2 diagnostic evidence window contract is invalid.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsFinite(double? value)
|
||||||
|
{
|
||||||
|
return value.HasValue && IsFinite(value.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsFinite(double value) => !double.IsNaN(value) && !double.IsInfinity(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class LocalG2DiagnosticEvidence
|
||||||
|
{
|
||||||
|
public string SourceMeasurementBatchSha256 { get; set; }
|
||||||
|
public string FixtureSha256 { get; set; }
|
||||||
|
public string ScenarioId { get; set; }
|
||||||
|
public string CandidateStableKey { get; set; }
|
||||||
|
public string CandidateSha256 { get; set; }
|
||||||
|
public int CandidateIndex { get; set; }
|
||||||
|
public int SegmentIndex { get; set; }
|
||||||
|
public double? WindowStartArcLengthMeters { get; set; }
|
||||||
|
public double? WindowEndArcLengthMeters { get; set; }
|
||||||
|
public double? StartGeometricCurvaturePerMeter { get; set; }
|
||||||
|
public double? EndGeometricCurvaturePerMeter { get; set; }
|
||||||
|
public double? StartVehicleCurvaturePerMeter { get; set; }
|
||||||
|
public double? EndVehicleCurvaturePerMeter { get; set; }
|
||||||
|
public List<LocalG2DiagnosticEvidencePoint> CandidatePoints { get; set; }
|
||||||
|
public string EvaluatorResult { get; set; }
|
||||||
|
public string StopGate { get; set; }
|
||||||
|
public string PublishedStatus { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class LocalG2DiagnosticEvidencePoint
|
||||||
|
{
|
||||||
|
public double? X { get; set; }
|
||||||
|
public double? Y { get; set; }
|
||||||
|
public double? ReferenceArcLengthMeters { get; set; }
|
||||||
|
public double? HeadingRadians { get; set; }
|
||||||
|
public double? UnwrappedHeadingRadians { get; set; }
|
||||||
|
public int Source { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
function Assert-Equal($expected, $actual, [string]$message) {
|
||||||
|
if ($expected -ne $actual) {
|
||||||
|
throw "$message Expected: '$expected'. Actual: '$actual'."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Assert-True([bool]$condition, [string]$message) {
|
||||||
|
if (-not $condition) {
|
||||||
|
throw $message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$repositoryRoot = Split-Path -Parent $PSScriptRoot
|
||||||
|
$assemblyPath = Join-Path $repositoryRoot 'bin\Debug\netstandard2.0\ClumsyPilot.dll'
|
||||||
|
$fixturePath = Join-Path $repositoryRoot 'ParkrobTrajplanner\PathSmoothing\Test\Fixtures\local-g2-diagnostic-single-turn.json'
|
||||||
|
|
||||||
|
if (-not (Test-Path -LiteralPath $assemblyPath)) {
|
||||||
|
throw "ClumsyPilot assembly was not found: $assemblyPath"
|
||||||
|
}
|
||||||
|
|
||||||
|
$assembly = [System.Reflection.Assembly]::LoadFrom($assemblyPath)
|
||||||
|
$loaderType = $assembly.GetType('MultiWheelC.TrajectoryPlanning.PathSmoothing.Test.LocalG2DiagnosticEvidenceLoader', $true)
|
||||||
|
$loader = [System.Activator]::CreateInstance($loaderType)
|
||||||
|
$loadAndVerify = $loaderType.GetMethod('LoadAndVerify')
|
||||||
|
$evidence = $loadAndVerify.Invoke($loader, @($fixturePath))
|
||||||
|
|
||||||
|
Assert-Equal 'single-turn' $evidence.ScenarioId 'Diagnostic evidence scenario must be stable.'
|
||||||
|
Assert-Equal 'single-turn/s0/r0/w5/seed2' $evidence.CandidateStableKey 'Diagnostic evidence key must be stable.'
|
||||||
|
Assert-Equal 5 $evidence.CandidateIndex 'Diagnostic evidence candidate index must be stable.'
|
||||||
|
Assert-Equal 10 $evidence.CandidatePoints.Count 'Diagnostic evidence must retain all ten recorded samples.'
|
||||||
|
Assert-Equal 'InsufficientClearance' $evidence.EvaluatorResult 'Diagnostic evidence must retain the observed evaluator result.'
|
||||||
|
Assert-Equal 'Clearance' $evidence.StopGate 'Diagnostic evidence must retain the observed stop gate.'
|
||||||
|
Assert-Equal 'Unchanged' $evidence.PublishedStatus 'Diagnostic evidence must retain the strict final status.'
|
||||||
|
|
||||||
|
$temporaryEvidencePath = Join-Path ([System.IO.Path]::GetTempPath()) ("local-g2-diagnostic-evidence-{0}.json" -f [System.Guid]::NewGuid())
|
||||||
|
try {
|
||||||
|
$tamperedContent = (Get-Content -LiteralPath $fixturePath -Raw).Replace('"StopGate": "Clearance"', '"StopGate": "Collision"')
|
||||||
|
[System.IO.File]::WriteAllText($temporaryEvidencePath, $tamperedContent, [System.Text.Encoding]::UTF8)
|
||||||
|
|
||||||
|
$rejected = $false
|
||||||
|
try {
|
||||||
|
$null = $loadAndVerify.Invoke($loader, @($temporaryEvidencePath))
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
$rejected = $true
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert-True $rejected 'Tampered diagnostic evidence must be rejected.'
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if (Test-Path -LiteralPath $temporaryEvidencePath) {
|
||||||
|
Remove-Item -LiteralPath $temporaryEvidencePath -Force
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user