$ErrorActionPreference = 'Stop' function Assert-True([bool]$condition, [string]$message) { if (-not $condition) { throw $message } } function Assert-Match([string]$content, [string]$pattern, [string]$message) { Assert-True ($content -match $pattern) $message } function Assert-NotMatch([string]$content, [string]$pattern, [string]$message) { Assert-True ($content -notmatch $pattern) $message } $sourcePath = Join-Path $PSScriptRoot '..\ParkrobTrajplanner\CoarsePath\Test\MovementTest.CoarsePathTest.cs' Assert-True (Test-Path -LiteralPath $sourcePath) 'P1 coarse-path UI movement-test source must exist.' $source = Get-Content -Raw -Encoding UTF8 $sourcePath Assert-Match $source 'class\s+CoarsePathPlanningTest\s*:\s*MovementTest' 'The UI entry must inherit MovementTest.' Assert-Match $source 'namespace\s+MultiWheelC\s*;' 'MovementTest entries must use the project discovery namespace.' Assert-Match $source 'CoarsePathPlanningService' 'The UI must keep one shared planning service for map-cache reuse.' Assert-Match $source 'Task\.Run\s*\(' 'Planning must run in a background Task.' Assert-Match $source 'CancellationTokenSource' 'The UI must retain a cancellation source for its active task.' Assert-Match $source 'override\s+void\s+TestStop\s*\(' 'The UI must implement TestStop cancellation.' Assert-Match $source 'CoarsePathScenarioFactory\.Create\s*\(' 'The UI must expose fixed scenario entries through the factory.' Assert-Match $source 'ManualCoarsePathObstacle' 'The manual UI must construct typed manual obstacles.' Assert-Match $source 'CreateManualObstacleDemo\s*\(' 'The manual UI must submit obstacles through the factory.' Assert-Match $source 'MaximumManualObstacleCount\s*=\s*20' 'The manual UI must bound obstacle input to 20.' Assert-Match $source 'ReadManualObstacles\s*\(' 'The manual UI must read the requested obstacle sequence.' Assert-Match $source 'Interlocked\.Increment\s*\(' 'The manual UI must issue a fresh obstacle snapshot version.' Assert-Match $source 'Circle\s*\(' 'The manual UI must support circle input.' Assert-Match $source 'AxisAlignedRectangle\s*\(' 'The manual UI must support axis-aligned rectangle input.' Assert-Match $source 'UI\.GetInput\s*\(' 'The manual demo must accept user input.' Assert-Match $source 'ReadPositiveTimeoutInput\s*\(' 'The manual UI must read a finite positive timeout.' Assert-Match $source 'Configuration\.SearchTimeout\s*=\s*searchTimeout' 'The manual UI must apply the timeout to the current job.' Assert-Match $source 'TimeSpan\.FromSeconds\s*\(' 'The manual timeout must convert seconds to TimeSpan.' Assert-Match $source 'timeoutSeconds\s*<=\s*0' 'The manual timeout must reject zero and negative values.' Assert-Match $source 'getCartLocation\s*\(' 'The manual demo must read the AMR body-center pose.' Assert-Match $source 'DrawLegend\s*\(' 'The painter output must include a visual legend.' Assert-Match $source 'PlanningGridMap' 'The painter output must consume the planning grid result.' Assert-Match $source '\.IsOccupied\s*\(' 'The painter output must draw occupied grid cells.' Assert-Match $source 'ResolutionMm' 'The painter output must draw the grid at its true resolution.' Assert-Match $source 'SnapshotId' 'The painter output must show the map snapshot identity.' Assert-Match $source 'IsGearSwitchPoint' 'The painter output must highlight gear-switch points.' Assert-Match $source 'GoalPositionToleranceMeters' 'The painter output must draw goal tolerance.' Assert-True (([regex]::Matches($source, 'PathSearchElapsed')).Count -ge 2) 'The status layer and Toast must both show path-search elapsed time.' Assert-Match $source 'BuildToastMessage[\s\S]*TerminationReason' 'The failure Toast must include the termination reason.' Assert-Match $source 'ExpandedNodeCount' 'The status layer must show expanded-node statistics.' Assert-Match $source 'VehicleKinematics\.TryGetMaximumCurvaturePerMeter' 'The status layer must show the effective turning radius.' Assert-NotMatch $source 'PlanningMapFactory' 'Movement tests must not build maps directly; use CoarsePathPlanningService.' Assert-NotMatch $source '\.Result\b' 'The UI must not block on Task.Result.' Assert-NotMatch $source '\.Wait\s*\(' 'The UI must not block the movement-test thread.' $runnerStart = $source.IndexOf('internal static class CoarsePathPlanningTestRunner') Assert-True ($runnerStart -ge 0) 'Shared runner source must exist.' $runnerSource = $source.Substring($runnerStart) Assert-Match $runnerSource 'RunScenario[\s\S]*getCartLocation\s*\(' 'Fixed scenarios must read the current AMR pose.' Assert-Match $runnerSource 'CoarsePathScenarioFactory\.Create\s*\(\s*scenario\s*,' 'Fixed scenarios must use the AMR-aware factory overload.' Assert-Match $runnerSource 'AMR' 'The runner must expose AMR pose diagnostics.' Assert-Match $runnerSource 'ArgumentException\("AMR' 'Invalid AMR input must be reported without starting planning.' Assert-Match $runnerSource 'double\.IsNaN|double\.IsInfinity' 'The runner must reject non-finite AMR coordinates.' Assert-Match $runnerSource 'DrawStatus\s*\([\s\S]*AmrPoseSnapshot' 'Result status must receive the frozen AMR snapshot.' $inputFailureStart = $runnerSource.IndexOf('internal static void ShowInputFailure') $inputFailureEnd = $runnerSource.IndexOf('private static void Finish', $inputFailureStart) Assert-True ($inputFailureStart -ge 0 -and $inputFailureEnd -gt $inputFailureStart) 'Input failure renderer must be isolated before completion handling.' $inputFailureSource = $runnerSource.Substring($inputFailureStart, $inputFailureEnd - $inputFailureStart) Assert-Match $inputFailureSource 'Painter\.DrawText\s*\(' 'Invalid AMR input must be shown on the Painter status layer.' $readmePath = Join-Path $PSScriptRoot '..\ParkrobTrajplanner\CoarsePath\README.md' Assert-True (Test-Path -LiteralPath $readmePath) 'CoarsePath README must exist beside its module.' $readme = Get-Content -Raw -Encoding UTF8 $readmePath foreach ($requiredText in @( 'CoarsePathPlanningV1', 'CoarsePathPlanningTest', 'CreateManualGoalDemo', 'getCartLocation', 'mm', 'deg', 'rad', 'CancellationTokenSource', 'TestStop', 'NoFeasiblePath', 'IsGearSwitchPoint')) { Assert-True ($readme.Contains($requiredText)) "P1 README must document $requiredText." } $readmeStructure = @( 'File Structure', 'Planning Data Flow', 'Build Status and Stop', 'Coordinates and Units', 'Minimal Call Example', 'Cache and SourceVersion', 'Detailed Usage Guide', 'P1 Manual Tests and Visualization', 'Common Errors', 'First-Version Limits', 'CoarsePathPlanningService.Plan(job, cancellationToken)', 'CoarsePathPlanningJob', 'PlanningGridMap', 'SourceVersion', 'CoarsePathPlanningV1', 'CancellationTokenSource', 'NoFeasiblePath', 'IsGearSwitchPoint', 'CreateManualObstacleDemo', 'ManualCoarsePathObstacle', 'manual-user-input', '0-20', 'PathSearchElapsed', '1.20 m', 'Reeds-Shepp', 'AxisAlignedRectangle', 'Create(CoarsePathTestScenario scenario, double amrXMillimeters', 'DetectionHeadingRadians', '../Map/README.md' ) foreach ($requiredText in $readmeStructure) { Assert-True ($readme.Contains($requiredText)) "Restructured CoarsePath README must document $requiredText." } Write-Output 'Coarse path P1 UI source checks passed.'