2026-07-30 08:43:35 +08:00
param (
[ string ] $AssemblyPath = ( Join-Path $PSScriptRoot '..\bin\Debug\netstandard2.0\ClumsyPilot.dll' ),
[ string ] $FixturePath = ( Join-Path $PSScriptRoot '..\ParkrobTrajplanner\PathSmoothing\Test\Fixtures\path-smoothing-fixtures.json' ))
$ErrorActionPreference = 'Stop'
$newtonsoftPath = Join-Path $env:USERPROFILE '.nuget\packages\newtonsoft.json\13.0.4\lib\netstandard2.0\Newtonsoft.Json.dll'
if ( Test-Path -LiteralPath $newtonsoftPath ) { [ Reflection.Assembly ]:: LoadFrom ( $newtonsoftPath ) | Out-Null }
$assembly = [ Reflection.Assembly ]:: LoadFrom (( Resolve-Path $AssemblyPath ))
function Assert-True($Actual , [ string ] $Message ) { if ( -not $Actual ) { throw $Message } }
function Assert-Equal($Expected , $Actual , [ string ] $Message ) {
if ( $Expected -ne $Actual ) { throw " $Message Expected= $Expected Actual= $Actual " }
}
function Assert-ThrowsMatching([scriptblock]$Action , [ string ] $ExpectedPattern , [ string ] $Message ) {
try {
& $Action
}
catch {
$exception = $_ . Exception
while ( $null -ne $exception . InnerException ) { $exception = $exception . InnerException }
if ( $exception . Message -match $ExpectedPattern ) { return }
throw " $Message ExpectedPattern= $ExpectedPattern Actual= $( $exception . Message ) "
}
throw " $Message Expected an exception."
}
function Get-RequiredType([string]$Name ) { return $assembly . GetType ( $Name , $true ) }
$root = 'MultiWheelC.TrajectoryPlanning.PathSmoothing.Test.'
2026-08-09 22:13:18 +08:00
$comparison = 'MultiWheelC.TrajectoryPlanning.PathSmoothing.Output.Comparison.'
2026-07-30 08:43:35 +08:00
$loaderType = Get-RequiredType ( $root + 'SmoothingScenarioFixtureLoader' )
$factoryType = Get-RequiredType ( $root + 'SmoothingScenarioFactory' )
$fixtureType = Get-RequiredType ( $root + 'SmoothingScenarioFixture' )
$comparisonRequestType = Get-RequiredType ( $comparison + 'PathSmoothingComparisonRequest' )
$preprocessorType = Get-RequiredType 'MultiWheelC.TrajectoryPlanning.PathSmoothing.Processing.PathSmoothingPreprocessor'
$loadMethod = $loaderType . GetMethod ( 'LoadAndVerify' , [ Type[] ] @ ([ string ]))
Assert-True ( $null -ne $loadMethod ) 'Fixture loader must expose LoadAndVerify(string).'
$fixtureRequestsMethod = $factoryType . GetMethod ( 'CreateFixtureRequests' , [ Type[] ] @ ([ string ]))
Assert-True ( $null -ne $fixtureRequestsMethod ) 'Scenario factory must expose CreateFixtureRequests(string).'
$generateMethod = $assembly . GetType ( $root + 'SmoothingFixtureGenerator' , $true ). GetMethod ( 'Generate' , [ Type[] ] @ ([ string], [bool ]))
Assert-True ( $null -ne $generateMethod ) 'Fixture generator must expose Generate(string, bool).'
$tryPrepareMethod = $preprocessorType . GetMethod ( 'TryPrepare' )
Assert-True ( $null -ne $tryPrepareMethod ) 'Fixture paths must be checked through PathSmoothingPreprocessor.TryPrepare.'
$fixtures = $loadMethod . Invoke ( $null , @ (( Resolve-Path $FixturePath ). Path ))
$fixtureDocument = Get-Content -Raw -Encoding UTF8 $FixturePath | ConvertFrom-Json
$expected = @ (
'straight' , 'single-turn' , 's-bend' , 'large-heading-change' ,
'rectangle-detour' , 'multi-obstacle-detour' ,
'narrow-corridor' , 'forward-reverse-switch' )
Assert-Equal $expected . Count $fixtures . Count 'Fixture loader must return exactly eight fast fixtures.'
$actualIds = @ ( $fixtures | ForEach-Object { $_ . Id })
Assert-Equal ( $expected -join ',' ) ( $actualIds -join ',' ) 'Fixture IDs must be stable and in documented order.'
Assert-Equal $actualIds . Count ( @ ( $actualIds | Select-Object -Unique ). Count ) 'Fixture IDs must be unique.'
foreach ( $fixture in $fixtures ) {
Assert-True ( $fixture -is $fixtureType ) 'Loader must return immutable smoothing fixture values.'
Assert-True ( $fixture . FixtureVersion -gt 0 ) "Fixture $( $fixture . Id ) must carry a positive version."
Assert-True $fixture . IsConfigurationFingerprintCurrent "Fixture $( $fixture . Id ) must match its stored configuration fingerprint."
Assert-True ( $fixture . ConfigurationFingerprint -match '^sha256:[0-9a-f]{64}$' ) "Fixture $( $fixture . Id ) must expose a lowercase SHA-256 fingerprint."
Assert-True ( $fixture . Path . Count -gt 1 ) "Fixture $( $fixture . Id ) must include a coarse path."
Assert-True ( $fixture . Segments . Count -gt 0 ) "Fixture $( $fixture . Id ) must include direction segments."
foreach ( $segment in $fixture . Segments ) {
Assert-True ( $segment . StartIndex -ge 0 -and $segment . EndIndex -lt $fixture . Path . Count -and $segment . EndIndex -ge $segment . StartIndex ) "Fixture $( $fixture . Id ) must retain valid segment coverage."
}
}
foreach ( $record in $fixtureDocument . scenarios ) {
Assert-True ( $null -ne $record . planningConfiguration ) "Fixture $( $record . id ) must retain the planning configuration that produced its coarse path."
}
$requests = $fixtureRequestsMethod . Invoke ( $null , @ (( Resolve-Path $FixturePath ). Path ))
Assert-Equal 8 $requests . Count 'Fast fixtures must create exactly eight comparison requests without Hybrid A*.'
foreach ( $request in $requests ) {
Assert-True ( $request -is $comparisonRequestType ) 'Fast fixture factory must create comparison requests.'
$prepareArguments = [ object[] ] @ ( $request . SmoothingRequest , $null , $null )
$prepared = $tryPrepareMethod . Invoke ([ Activator ]:: CreateInstance ( $preprocessorType ), $prepareArguments )
Assert-True $prepared "Fixture path must satisfy the PathSmoothingPreprocessor input contract. Reason= $( $prepareArguments [ 2 ]) "
}
$loaderSource = Get-Content -Raw -Encoding UTF8 ( Join-Path $PSScriptRoot '..\ParkrobTrajplanner\PathSmoothing\Test\SmoothingScenarioFixtureLoader.cs' )
Assert-True ( -not $loaderSource . Contains ( 'HybridAStarPlanner' )) 'Fixture-only loading must not reference HybridAStarPlanner.'
$generatorSource = Get-Content -Raw -Encoding UTF8 ( Join-Path $PSScriptRoot '..\ParkrobTrajplanner\PathSmoothing\Test\SmoothingFixtureGenerator.cs' )
Assert-True $generatorSource . Contains ( 'CoarsePathPlanningService' ) 'Fixture generation must snapshot actual successful coarse-path planning outputs.'
Assert-True $generatorSource . Contains ( 'CoarsePathScenarioFactory' ) 'Fixture generation must use the established coarse-path scenarios where available.'
function Write-CorruptedFixture([scriptblock]$Mutate ) {
$temporaryPath = [ IO.Path ]:: GetTempFileName ()
$document = Get-Content -Raw -Encoding UTF8 $FixturePath | ConvertFrom-Json
& $Mutate $document
[ IO.File ]:: WriteAllText ( $temporaryPath , ( $document | ConvertTo-Json -Depth 16 ), [ Text.UTF8Encoding ]:: new ( $false ))
return $temporaryPath
}
$coverageFixture = Write-CorruptedFixture { param ( $document ) $document . scenarios [ 0 ]. segments [ 0 ]. endIndex = 1 }
$directionFixture = Write-CorruptedFixture { param ( $document ) $document . scenarios [ 0 ]. segments [ 0 ]. direction = 1 }
$switchFixture = Write-CorruptedFixture { param ( $document ) $document . scenarios [ 0 ]. segments [ 0 ]. endsAtGearSwitch = $true }
try {
Assert-ThrowsMatching { $loadMethod . Invoke ( $null , @ ( $coverageFixture )) } 'Fixture path contract' 'Loader must reject segment gaps before accepting the fingerprint.'
Assert-ThrowsMatching { $loadMethod . Invoke ( $null , @ ( $directionFixture )) } 'Fixture path contract' 'Loader must reject segment direction mismatches before accepting the fingerprint.'
Assert-ThrowsMatching { $loadMethod . Invoke ( $null , @ ( $switchFixture )) } 'Fixture path contract' 'Loader must reject illegal gear-switch topology before accepting the fingerprint.'
}
finally {
foreach ( $temporaryPath in @ ( $coverageFixture , $directionFixture , $switchFixture )) {
if ( $temporaryPath -and [ IO.File ]:: Exists ( $temporaryPath )) { [ IO.File ]:: Delete ( $temporaryPath ) }
}
}
$generationPath = [ IO.Path ]:: GetTempFileName ()
try {
Assert-ThrowsMatching { $generateMethod . Invoke ( $null , @ ( $generationPath , $false )) } '.+' 'Fixture generation must refuse to overwrite an existing target.'
$generateMethod . Invoke ( $null , @ ( $generationPath , $true ))
$generatedFixtures = $loadMethod . Invoke ( $null , @ ( $generationPath ))
Assert-Equal 8 $generatedFixtures . Count 'Generated fixture data must remain loadable and retain all scenarios.'
}
finally {
if ([ IO.File ]:: Exists ( $generationPath )) { [ IO.File ]:: Delete ( $generationPath ) }
}
Write-Output 'Path smoothing fixture checks passed.'