120 lines
7.9 KiB
PowerShell
120 lines
7.9 KiB
PowerShell
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.'
|
|
$comparison = 'MultiWheelC.TrajectoryPlanning.PathSmoothing.Output.Comparison.'
|
|
$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.'
|