180 lines
12 KiB
PowerShell
180 lines
12 KiB
PowerShell
param([string]$AssemblyPath = (Join-Path $PSScriptRoot '..\bin\Debug\netstandard2.0\ClumsyPilot.dll'))
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
$assembly = [Reflection.Assembly]::LoadFrom((Resolve-Path $AssemblyPath))
|
|
$root = 'MultiWheelC.TrajectoryPlanning.PathSmoothing.'
|
|
$coarsePathRoot = 'MultiWheelC.TrajectoryPlanning.CoarsePath.'
|
|
$mappingRoot = 'MultiWheelC.TrajectoryPlanning.Mapping.'
|
|
|
|
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-Near([double]$Expected, [double]$Actual, [string]$Message) {
|
|
if ([Math]::Abs($Expected - $Actual) -gt 0.000001d) {
|
|
throw "$Message Expected=$Expected Actual=$Actual"
|
|
}
|
|
}
|
|
|
|
function Assert-Throws([scriptblock]$Action, [string]$Message) {
|
|
$threw = $false
|
|
try { & $Action }
|
|
catch { $threw = $true }
|
|
if (-not $threw) { throw $Message }
|
|
}
|
|
|
|
function Assert-ReadOnlyCollection($Collection, [string]$Message) {
|
|
$list = [System.Collections.IList]$Collection
|
|
Assert-True ($null -ne $list) "$Message The collection must implement IList."
|
|
Assert-True $list.IsReadOnly "$Message The collection must report IsReadOnly."
|
|
Assert-Throws { $list.Add($null) } "$Message The collection must reject Add."
|
|
}
|
|
|
|
function Get-RequiredType([string]$Name) {
|
|
return $assembly.GetType($Name, $true)
|
|
}
|
|
|
|
$configurationType = Get-RequiredType ($root + 'PathSmoothingConfiguration')
|
|
$methodType = Get-RequiredType ($root + 'SmoothingMethod')
|
|
$statusType = Get-RequiredType ($root + 'PathSmoothingStatus')
|
|
$sourceType = Get-RequiredType ($root + 'SmoothedPathPointSource')
|
|
$pointType = Get-RequiredType ($root + 'SmoothedPathPoint')
|
|
$segmentType = Get-RequiredType ($root + 'SmoothedPathSegment')
|
|
$bsplineOptionsType = Get-RequiredType ($root + 'CubicBSplineOptions')
|
|
$bezierOptionsType = Get-RequiredType ($root + 'LocalCubicBezierOptions')
|
|
$quinticOptionsType = Get-RequiredType ($root + 'PiecewiseQuinticOptions')
|
|
$requestType = Get-RequiredType ($root + 'PathSmoothingRequest')
|
|
$metricsType = Get-RequiredType ($root + 'PathQualityMetrics')
|
|
$diagnosticsType = Get-RequiredType ($root + 'PathSmoothingDiagnostics')
|
|
$resultType = Get-RequiredType ($root + 'PathSmoothingResult')
|
|
$directionType = Get-RequiredType ($coarsePathRoot + 'TravelDirection')
|
|
$coarsePointType = Get-RequiredType ($coarsePathRoot + 'CoarsePathPoint')
|
|
$coarseSegmentType = Get-RequiredType ($coarsePathRoot + 'PathSegment')
|
|
$mapType = Get-RequiredType ($mappingRoot + 'PlanningGridMap')
|
|
$vehicleType = Get-RequiredType ($coarsePathRoot + 'VehicleParameters')
|
|
|
|
Assert-Equal $true $methodType.IsEnum 'SmoothingMethod must be a public enum.'
|
|
Assert-Equal $true $statusType.IsEnum 'PathSmoothingStatus must be a public enum.'
|
|
Assert-Equal $true $sourceType.IsEnum 'SmoothedPathPointSource must be a public enum.'
|
|
Assert-Equal 'CubicBSpline' ([Enum]::GetNames($methodType)[0]) 'Smoothing method order must remain stable.'
|
|
Assert-Equal 'LocalCubicBezier' ([Enum]::GetNames($methodType)[1]) 'Smoothing method order must remain stable.'
|
|
Assert-Equal 'PiecewiseQuintic' ([Enum]::GetNames($methodType)[2]) 'Smoothing method order must remain stable.'
|
|
Assert-Equal 'Success' ([Enum]::GetNames($statusType)[0]) 'Smoothing status order must remain stable.'
|
|
Assert-Equal 'FallbackToCoarsePath' ([Enum]::GetNames($statusType)[1]) 'Fallback status must be explicit.'
|
|
|
|
$configuration = [Activator]::CreateInstance($configurationType)
|
|
Assert-Near 0.05 $configuration.OutputSpacingMeters 'Default output spacing must be 0.05 m.'
|
|
Assert-Near 0.025 $configuration.MaximumCollisionCheckStepMeters 'Default collision step must be 0.025 m.'
|
|
Assert-Near 0.02 $configuration.MinimumClearanceReserveMeters 'Default clearance reserve must be 0.02 m.'
|
|
Assert-Near 1.0 $configuration.SmoothingStrength 'Default smoothing strength must be 1.0.'
|
|
Assert-Equal $true $configuration.AllowFallbackToCoarsePath 'Fallback must be enabled by default.'
|
|
Assert-Equal 4 $configuration.RetryStrengthScales.Count 'Retry schedule must contain four entries.'
|
|
Assert-Near 1.0 $configuration.RetryStrengthScales[0] 'First retry scale must be 1.0.'
|
|
Assert-Near 0.25 $configuration.RetryStrengthScales[3] 'Last retry scale must be 0.25.'
|
|
Assert-ReadOnlyCollection $configuration.RetryStrengthScales 'Retry schedule must be immutable.'
|
|
Assert-Near (1.0 / 3.0) ([Activator]::CreateInstance($bsplineOptionsType)).EndpointTangentScale 'B-spline endpoint tangent default must be one third.'
|
|
$bezier = [Activator]::CreateInstance($bezierOptionsType)
|
|
Assert-Near ([Math]::PI / 18.0) $bezier.CornerHeadingThresholdRadians 'Bezier corner threshold must be 10 degrees.'
|
|
Assert-Near 0.60 $bezier.MaximumWindowLengthMeters 'Bezier window default must be 0.60 m.'
|
|
Assert-Near (1.0 / 3.0) $bezier.HandleLengthRatio 'Bezier handle default must be one third.'
|
|
$quintic = [Activator]::CreateInstance($quinticOptionsType)
|
|
Assert-Near 0.50 $quintic.KnotSpacingMeters 'Quintic knot spacing must be 0.50 m.'
|
|
Assert-Near 0.10 $quintic.MinimumKnotSpacingMeters 'Quintic minimum knot spacing must be 0.10 m.'
|
|
|
|
$forward = [Enum]::Parse($directionType, 'Forward')
|
|
$anchor = [Enum]::Parse($sourceType, 'Anchor')
|
|
$point = [Activator]::CreateInstance($pointType, @(
|
|
[double]1.25, [double]-2.50, [double]0.30, [double]6.58, [double]4.75,
|
|
$forward, [double]0.12, [double]0.12, [double]0.44, $false, $anchor))
|
|
Assert-Near 1.25 $point.X 'Smoothed point X must be stored in m.'
|
|
Assert-Near -2.50 $point.Y 'Smoothed point Y must be stored in m.'
|
|
Assert-Near 0.30 $point.Heading 'Smoothed point heading must be stored in rad.'
|
|
Assert-Near 6.58 $point.UnwrappedHeading 'Smoothed point unwrapped heading must be stored in rad.'
|
|
Assert-Near 4.75 $point.ArcLength 'Smoothed point arc length must be stored in m.'
|
|
Assert-Equal 'Forward' $point.Direction.ToString() 'Smoothed point direction must be preserved.'
|
|
Assert-Near 0.12 $point.GeometricCurvature 'Smoothed point geometric curvature must be stored in 1/m.'
|
|
Assert-Near 0.12 $point.VehicleCurvature 'Smoothed point vehicle curvature must be stored in 1/m.'
|
|
Assert-Near 0.44 $point.BodyClearance 'Smoothed point clearance must be stored in m.'
|
|
Assert-Equal $false $point.IsGearSwitchPoint 'Smoothed point gear-switch marker must be preserved.'
|
|
Assert-Equal 'Anchor' $point.Source.ToString() 'Smoothed point source must be preserved.'
|
|
|
|
$segmentA = [Activator]::CreateInstance($segmentType, @(0, $forward, 0, 2, $false, $true))
|
|
$reverse = [Enum]::Parse($directionType, 'Reverse')
|
|
$segmentB = [Activator]::CreateInstance($segmentType, @(1, $reverse, 3, 5, $true, $false))
|
|
Assert-Equal 0 $segmentA.SegmentIndex 'First smoothing segment index must be retained.'
|
|
Assert-Equal 'Forward' $segmentA.Direction.ToString() 'First smoothing segment direction must be retained.'
|
|
Assert-Equal 2 $segmentA.EndIndex 'First smoothing segment end index must be retained.'
|
|
Assert-Equal $true $segmentA.EndsAtGearSwitch 'First smoothing segment switch flag must be retained.'
|
|
Assert-Equal 1 $segmentB.SegmentIndex 'Second smoothing segment index must be retained.'
|
|
Assert-Equal 'Reverse' $segmentB.Direction.ToString() 'Second smoothing segment direction must be retained.'
|
|
Assert-Equal $true $segmentB.StartsAtGearSwitch 'Second smoothing segment switch flag must be retained.'
|
|
|
|
$metrics = [Activator]::CreateInstance($metricsType)
|
|
Assert-Equal $false $metrics.IsFeasible 'Default metrics must be infeasible until analysis accepts a candidate.'
|
|
Assert-Near 0.0 $metrics.PathLengthMeters 'Default metrics must be zero-valued.'
|
|
Assert-Near 0.0 $metrics.MinimumBodyClearanceMeters 'Default metrics must be zero-valued.'
|
|
$diagnostics = [Activator]::CreateInstance($diagnosticsType)
|
|
Assert-True ($diagnostics.Metrics -ne $null) 'Default diagnostics must provide quality metrics.'
|
|
Assert-Equal 0 $diagnostics.RetryCount 'Default diagnostics must have no retries.'
|
|
Assert-Near 0.0 $diagnostics.AcceptedStrength 'Default diagnostics must have zero accepted strength.'
|
|
|
|
$pointArray = [Array]::CreateInstance($pointType, 1)
|
|
$pointArray.SetValue($point, 0)
|
|
$segmentArray = [Array]::CreateInstance($segmentType, 2)
|
|
$segmentArray.SetValue($segmentA, 0)
|
|
$segmentArray.SetValue($segmentB, 1)
|
|
$method = [Enum]::Parse($methodType, 'CubicBSpline')
|
|
$successMethod = $resultType.GetMethod('Success')
|
|
Assert-True ($null -ne $successMethod) 'PathSmoothingResult must expose Success.'
|
|
$success = $successMethod.Invoke($null, @($method, $pointArray, $segmentArray, $diagnostics))
|
|
Assert-Equal 'Success' $success.Status.ToString() 'Success factory must publish Success status.'
|
|
Assert-Equal 'CubicBSpline' $success.Method.ToString() 'Success factory must retain the selected method.'
|
|
Assert-Equal 1 $success.Path.Count 'Success factory must publish the provided path.'
|
|
Assert-Equal 2 $success.Segments.Count 'Success factory must publish the provided segments.'
|
|
Assert-ReadOnlyCollection $success.Path 'Success path must be immutable.'
|
|
Assert-ReadOnlyCollection $success.Segments 'Success segments must be immutable.'
|
|
$pointArray.SetValue($null, 0)
|
|
$segmentArray.SetValue($null, 0)
|
|
Assert-True ($null -ne $success.Path[0]) 'Success factory must copy path collections.'
|
|
Assert-True ($null -ne $success.Segments[0]) 'Success factory must copy segment collections.'
|
|
|
|
$fallbackMethod = $resultType.GetMethod('Fallback')
|
|
Assert-True ($null -ne $fallbackMethod) 'PathSmoothingResult must expose Fallback.'
|
|
$fallbackPath = [Array]::CreateInstance($pointType, 1)
|
|
$fallbackPath.SetValue($point, 0)
|
|
$fallbackSegments = [Array]::CreateInstance($segmentType, 1)
|
|
$fallbackSegments.SetValue($segmentA, 0)
|
|
$fallback = $fallbackMethod.Invoke($null, @($method, $fallbackPath, $fallbackSegments, $diagnostics))
|
|
Assert-Equal 'FallbackToCoarsePath' $fallback.Status.ToString() 'Fallback factory must publish an explicit fallback status.'
|
|
Assert-Equal 1 $fallback.Path.Count 'Fallback factory must publish a validated fallback path.'
|
|
|
|
$failureMethod = $resultType.GetMethod('Failure')
|
|
Assert-True ($null -ne $failureMethod) 'PathSmoothingResult must expose Failure.'
|
|
$failed = $failureMethod.Invoke(
|
|
$null,
|
|
@([Enum]::Parse($statusType, 'InvalidInput'),
|
|
[Activator]::CreateInstance($diagnosticsType)))
|
|
Assert-Equal 'InvalidInput' $failed.Status.ToString() 'Failure factory must retain failure status.'
|
|
Assert-Equal 0 $failed.Path.Count 'Failure must publish no path.'
|
|
Assert-Equal 0 $failed.Segments.Count 'Failure must publish no segments.'
|
|
Assert-ReadOnlyCollection $failed.Path 'Failure path must be immutable.'
|
|
Assert-ReadOnlyCollection $failed.Segments 'Failure segments must be immutable.'
|
|
Assert-Throws { $failureMethod.Invoke($null, @([Enum]::Parse($statusType, 'Success'), $diagnostics)) } 'Failure factory must reject Success.'
|
|
Assert-Throws { $failureMethod.Invoke($null, @([Enum]::Parse($statusType, 'FallbackToCoarsePath'), $diagnostics)) } 'Failure factory must reject fallback status.'
|
|
Assert-Throws { $successMethod.Invoke($null, @($method, [Array]::CreateInstance($pointType, 0), $fallbackSegments, $diagnostics)) } 'Success factory must reject an empty path.'
|
|
Assert-Throws { $successMethod.Invoke($null, @($method, $fallbackPath, [Array]::CreateInstance($segmentType, 0), $diagnostics)) } 'Success factory must reject empty segments.'
|
|
|
|
$requestConstructor = $requestType.GetConstructor(@(
|
|
[System.Collections.Generic.IReadOnlyList``1].MakeGenericType($coarsePointType),
|
|
[System.Collections.Generic.IReadOnlyList``1].MakeGenericType($coarseSegmentType),
|
|
$mapType,
|
|
$vehicleType,
|
|
$configurationType))
|
|
Assert-True ($null -ne $requestConstructor) 'PathSmoothingRequest must expose the public five-argument constructor.'
|
|
|
|
Write-Output 'Path smoothing contract checks passed.'
|