Skip to content

Commit 55c8115

Browse files
authored
Fix cross-platform unit test path compatibility issues
Merge pull request awslabs#47 from austoonz/chore/fix-xplat-unit-testing - Add Assert-PathEquals helper function to normalize path separators for cross-platform compatibility in unit tests - Replace direct string path comparisons with Assert-PathEquals in Get-Handler.Tests.ps1 and Set-PSModulePath.Tests.ps1 - Remove Windows-specific read-only directory test that was causing cross-platform issues (not relevant for the Lambda runtime which is Linux only) - Ensure all unit tests pass on Windows, Linux, and macOS by handling path separator differences transparently The new Assert-PathEquals function normalizes both actual and expected paths to use forward slashes before comparison, allowing tests to pass regardless of the platform's native path separator behavior.
2 parents 2eac8bb + 078eb9c commit 55c8115

5 files changed

Lines changed: 242 additions & 60 deletions

File tree

powershell-runtime/tests/helpers/AssertionHelpers.Tests.ps1

Lines changed: 151 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -446,4 +446,154 @@ Describe "Assert-FileExists" {
446446
{ Assert-FileExists -Path $unreadableFile -ShouldBeFile } | Should -Not -Throw
447447
}
448448
}
449-
}
449+
}
450+
451+
Describe "Assert-PathEquals" {
452+
Context "When paths are identical" {
453+
It "Should pass for identical forward slash paths" {
454+
{ Assert-PathEquals -Actual "/var/task/handler.ps1" -Expected "/var/task/handler.ps1" } | Should -Not -Throw
455+
}
456+
457+
It "Should pass for identical backslash paths" {
458+
{ Assert-PathEquals -Actual "C:\var\task\handler.ps1" -Expected "C:\var\task\handler.ps1" } | Should -Not -Throw
459+
}
460+
461+
It "Should pass for identical relative paths" {
462+
{ Assert-PathEquals -Actual "lib/utilities.ps1" -Expected "lib/utilities.ps1" } | Should -Not -Throw
463+
}
464+
465+
It "Should pass for identical single file names" {
466+
{ Assert-PathEquals -Actual "handler.ps1" -Expected "handler.ps1" } | Should -Not -Throw
467+
}
468+
}
469+
470+
Context "When paths differ only by separators" {
471+
It "Should pass when actual uses backslashes and expected uses forward slashes" {
472+
{ Assert-PathEquals -Actual "C:\var\task\handler.ps1" -Expected "C:/var/task/handler.ps1" } | Should -Not -Throw
473+
}
474+
475+
It "Should pass when actual uses forward slashes and expected uses backslashes" {
476+
{ Assert-PathEquals -Actual "/var/task/handler.ps1" -Expected "\var\task\handler.ps1" } | Should -Not -Throw
477+
}
478+
479+
It "Should pass for mixed separators in both paths" {
480+
{ Assert-PathEquals -Actual "C:\var/task\handler.ps1" -Expected "C:/var\task/handler.ps1" } | Should -Not -Throw
481+
}
482+
483+
It "Should pass for nested directory paths with different separators" {
484+
{ Assert-PathEquals -Actual "lib\modules\aws\tools\handler.ps1" -Expected "lib/modules/aws/tools/handler.ps1" } | Should -Not -Throw
485+
}
486+
}
487+
488+
Context "When paths are genuinely different" {
489+
It "Should throw for different file names" {
490+
{ Assert-PathEquals -Actual "/var/task/handler.ps1" -Expected "/var/task/different.ps1" } | Should -Throw -ExpectedMessage "*Expected path '/var/task/different.ps1' but got '/var/task/handler.ps1'*"
491+
}
492+
493+
It "Should throw for different directory paths" {
494+
{ Assert-PathEquals -Actual "/var/task/handler.ps1" -Expected "/var/runtime/handler.ps1" } | Should -Throw -ExpectedMessage "*Expected path '/var/runtime/handler.ps1' but got '/var/task/handler.ps1'*"
495+
}
496+
497+
It "Should throw for different number of path segments" {
498+
{ Assert-PathEquals -Actual "/var/task/lib/handler.ps1" -Expected "/var/task/handler.ps1" } | Should -Throw -ExpectedMessage "*Expected path '/var/task/handler.ps1' but got '/var/task/lib/handler.ps1'*"
499+
}
500+
501+
It "Should throw for completely different paths" {
502+
{ Assert-PathEquals -Actual "C:\Windows\System32\file.txt" -Expected "/usr/local/bin/script.sh" } | Should -Throw -ExpectedMessage "*Expected path '/usr/local/bin/script.sh' but got 'C:\Windows\System32\file.txt'*"
503+
}
504+
}
505+
506+
Context "When using custom Because parameter" {
507+
It "Should include custom reason in error message" {
508+
{ Assert-PathEquals -Actual "/var/task/wrong.ps1" -Expected "/var/task/handler.ps1" -Because "the script file path should match the handler configuration" } | Should -Throw -ExpectedMessage "*because the script file path should match the handler configuration*"
509+
}
510+
511+
It "Should include custom reason with normalized paths" {
512+
{ Assert-PathEquals -Actual "C:\var\task\wrong.ps1" -Expected "/var/task/handler.ps1" -Because "cross-platform paths should be normalized" } | Should -Throw -ExpectedMessage "*because cross-platform paths should be normalized*"
513+
}
514+
}
515+
516+
Context "When handling edge cases" {
517+
It "Should handle PowerShell parameter validation for empty strings" {
518+
# PowerShell parameter validation prevents empty strings for mandatory parameters
519+
{ Assert-PathEquals -Actual "" -Expected "" } | Should -Throw -ExpectedMessage "*Cannot bind argument to parameter*"
520+
}
521+
522+
It "Should handle PowerShell parameter validation for empty actual path" {
523+
{ Assert-PathEquals -Actual "" -Expected "/var/task/handler.ps1" } | Should -Throw -ExpectedMessage "*Cannot bind argument to parameter*"
524+
}
525+
526+
It "Should handle paths with trailing separators" {
527+
{ Assert-PathEquals -Actual "/var/task/" -Expected "/var/task" } | Should -Throw -ExpectedMessage "*Expected path '/var/task' but got '/var/task/'*"
528+
}
529+
530+
It "Should handle paths with multiple consecutive separators" {
531+
{ Assert-PathEquals -Actual "/var//task/handler.ps1" -Expected "/var/task/handler.ps1" } | Should -Throw -ExpectedMessage "*Expected path '/var/task/handler.ps1' but got '/var//task/handler.ps1'*"
532+
}
533+
534+
It "Should handle paths with dots" {
535+
{ Assert-PathEquals -Actual "/var/task/./handler.ps1" -Expected "/var/task/handler.ps1" } | Should -Throw -ExpectedMessage "*Expected path '/var/task/handler.ps1' but got '/var/task/./handler.ps1'*"
536+
}
537+
538+
It "Should handle paths with parent directory references" {
539+
{ Assert-PathEquals -Actual "/var/task/../task/handler.ps1" -Expected "/var/task/handler.ps1" } | Should -Throw -ExpectedMessage "*Expected path '/var/task/handler.ps1' but got '/var/task/../task/handler.ps1'*"
540+
}
541+
}
542+
543+
Context "When testing real-world Lambda scenarios" {
544+
It "Should pass for typical Lambda task paths with different separators" {
545+
{ Assert-PathEquals -Actual "/var\task\handler.ps1" -Expected "/var/task/handler.ps1" } | Should -Not -Throw
546+
}
547+
548+
It "Should pass for module paths in subdirectories" {
549+
{ Assert-PathEquals -Actual "/var\task\lib\utilities.ps1" -Expected "/var/task/lib/utilities.ps1" } | Should -Not -Throw
550+
}
551+
552+
It "Should pass for PowerShell module manifest paths" {
553+
{ Assert-PathEquals -Actual "/opt\powershell\modules\MyModule\MyModule.psd1" -Expected "/opt/powershell/modules/MyModule/MyModule.psd1" } | Should -Not -Throw
554+
}
555+
556+
It "Should pass for Windows-style Lambda paths normalized to Linux" {
557+
{ Assert-PathEquals -Actual "C:\lambda\task\function.ps1" -Expected "C:/lambda/task/function.ps1" } | Should -Not -Throw
558+
}
559+
560+
It "Should handle layer paths correctly" {
561+
{ Assert-PathEquals -Actual "/opt\powershell\modules\AWS.Tools.Common\AWS.Tools.Common.psd1" -Expected "/opt/powershell/modules/AWS.Tools.Common/AWS.Tools.Common.psd1" } | Should -Not -Throw
562+
}
563+
}
564+
565+
Context "When error messages show normalization details" {
566+
It "Should show normalized paths in error message when normalization occurred" {
567+
{ Assert-PathEquals -Actual "C:\var\task\wrong.ps1" -Expected "/var/task/handler.ps1" } | Should -Throw -ExpectedMessage "*normalized: '/var/task/handler.ps1' vs 'C:/var/task/wrong.ps1'*"
568+
}
569+
570+
It "Should not show normalization details when no normalization was needed" {
571+
{ Assert-PathEquals -Actual "/var/task/wrong.ps1" -Expected "/var/task/handler.ps1" } | Should -Throw -ExpectedMessage "*Expected path '/var/task/handler.ps1' but got '/var/task/wrong.ps1'*"
572+
}
573+
574+
It "Should show normalization details only when actual path was normalized" {
575+
{ Assert-PathEquals -Actual "C:\var\task\handler.ps1" -Expected "C:/var/task/different.ps1" } | Should -Throw -ExpectedMessage "*normalized: 'C:/var/task/different.ps1' vs 'C:/var/task/handler.ps1'*"
576+
}
577+
}
578+
579+
Context "When testing verbose output" {
580+
It "Should write verbose message on successful assertion" {
581+
Mock Write-Verbose { }
582+
583+
Assert-PathEquals -Actual "/var/task/handler.ps1" -Expected "/var/task/handler.ps1" -Verbose
584+
585+
Should -Invoke Write-Verbose -Exactly 1 -ParameterFilter { $Message -like "*Path assertion passed*" }
586+
}
587+
588+
It "Should write verbose message with both paths" {
589+
Mock Write-Verbose { }
590+
591+
# This should pass since the paths are equivalent after normalization
592+
Assert-PathEquals -Actual "C:\var\task\handler.ps1" -Expected "C:/var/task/handler.ps1" -Verbose
593+
594+
Should -Invoke Write-Verbose -Exactly 1 -ParameterFilter {
595+
$Message -like "*'C:/var/task/handler.ps1' matches 'C:\var\task\handler.ps1'*"
596+
}
597+
}
598+
}
599+
}

powershell-runtime/tests/helpers/AssertionHelpers.ps1

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -460,5 +460,61 @@ function Assert-FileExists {
460460
Write-Verbose "File existence assertion passed: '$Path'"
461461
}
462462

463+
<#
464+
.SYNOPSIS
465+
Verify path equality with cross-platform path separator handling.
466+
467+
.DESCRIPTION
468+
Asserts that two paths are equal by normalizing path separators to forward slashes,
469+
allowing tests to pass on both Windows and Linux regardless of the path separator
470+
used by System.IO.Path.Combine() in the runtime.
471+
472+
.PARAMETER Actual
473+
The actual path value from the test result.
474+
475+
.PARAMETER Expected
476+
The expected path value (should use forward slashes for consistency).
477+
478+
.PARAMETER Because
479+
Optional reason for the assertion failure.
480+
481+
.EXAMPLE
482+
Assert-PathEquals -Actual $result.scriptFilePath -Expected "/var/task/handler.ps1"
483+
Verifies that the script file path matches, regardless of platform path separators.
484+
485+
.EXAMPLE
486+
Assert-PathEquals -Actual $result.scriptFilePath -Expected "/var/task/lib/utilities.ps1" -Because "subdirectory paths should be handled correctly"
487+
Verifies path equality with a custom failure message.
488+
#>
489+
function Assert-PathEquals {
490+
[CmdletBinding()]
491+
param(
492+
[Parameter(Mandatory)]
493+
[string]$Actual,
494+
495+
[Parameter(Mandatory)]
496+
[string]$Expected,
497+
498+
[string]$Because
499+
)
500+
501+
# Normalize both paths to use forward slashes for comparison
502+
$normalizedActual = $Actual -replace '\\', '/'
503+
$normalizedExpected = $Expected -replace '\\', '/'
504+
505+
if ($normalizedActual -ne $normalizedExpected) {
506+
$message = "Expected path '$Expected' but got '$Actual'"
507+
if ($normalizedActual -ne $Actual) {
508+
$message += " (normalized: '$normalizedExpected' vs '$normalizedActual')"
509+
}
510+
if ($Because) {
511+
$message += " because $Because"
512+
}
513+
throw $message
514+
}
515+
516+
Write-Verbose "Path assertion passed: '$Expected' matches '$Actual'"
517+
}
518+
463519
# Functions are available when dot-sourced
464-
# To use: . ./AssertionHelpers.ps1
520+
# To use: . ./AssertionHelpers.ps1

powershell-runtime/tests/unit/Build/build-PwshRuntimeLayer.Tests.ps1

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -310,33 +310,6 @@ Describe "build-PwshRuntimeLayer.ps1" {
310310
$_.Exception.Message | Should -Not -BeNullOrEmpty
311311
}
312312
}
313-
314-
It "Should handle read-only layer path appropriately" {
315-
# Create a directory and make it read-only (if supported on platform)
316-
$readOnlyPath = Join-Path $TestDrive "readonly-layer"
317-
New-Item -Path $readOnlyPath -ItemType Directory -Force
318-
319-
try {
320-
# Try to make directory read-only (Windows only test)
321-
if ($IsWindows) {
322-
Set-ItemProperty -Path $readOnlyPath -Name IsReadOnly -Value $true
323-
# Build script should handle this appropriately on Windows
324-
{ & $script:BuildScript -LayerPath $readOnlyPath -SkipRuntimeSetup 6>$null } | Should -Throw
325-
}
326-
else {
327-
# On non-Windows platforms, test that build succeeds even with permission issues
328-
# The build script should handle permission issues gracefully
329-
{ & $script:BuildScript -LayerPath $readOnlyPath -SkipRuntimeSetup 6>$null } | Should -Not -Throw
330-
}
331-
}
332-
finally {
333-
# Clean up read-only attribute
334-
if ($IsWindows -and (Test-Path $readOnlyPath)) {
335-
Set-ItemProperty -Path $readOnlyPath -Name IsReadOnly -Value $false
336-
Remove-Item $readOnlyPath -Recurse -Force -ErrorAction SilentlyContinue
337-
}
338-
}
339-
}
340313
}
341314

342315
Context "When testing download logic without runtime setup" {
@@ -754,4 +727,4 @@ function Production-Function {
754727
}
755728

756729

757-
}
730+
}

powershell-runtime/tests/unit/Private/Get-Handler.Tests.ps1

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ Describe "Get-Handler" {
5353
$result | Should -Not -BeNullOrEmpty
5454
$result.handlerType | Should -Be 'Script'
5555
$result.scriptFileName | Should -Be "handler.ps1"
56-
$result.scriptFilePath | Should -Be "/var/task/handler.ps1"
56+
Assert-PathEquals -Actual $result.scriptFilePath -Expected "/var/task/handler.ps1"
5757
$result.PSObject.Properties['functionName'] | Should -BeNullOrEmpty
5858
$result.PSObject.Properties['moduleName'] | Should -BeNullOrEmpty
5959
}
@@ -68,7 +68,7 @@ Describe "Get-Handler" {
6868
# Assert
6969
$result.handlerType | Should -Be 'Script'
7070
$result.scriptFileName | Should -Be "my-complex-handler.ps1"
71-
$result.scriptFilePath | Should -Be "/var/task/my-complex-handler.ps1"
71+
Assert-PathEquals -Actual $result.scriptFilePath -Expected "/var/task/my-complex-handler.ps1"
7272
}
7373

7474
It "Should use LAMBDA_TASK_ROOT environment variable for script path" {
@@ -80,7 +80,7 @@ Describe "Get-Handler" {
8080
$result = pwsh-runtime\Get-Handler
8181

8282
# Assert
83-
$result.scriptFilePath | Should -Be "/custom/task/root/test.ps1"
83+
Assert-PathEquals -Actual $result.scriptFilePath -Expected "/custom/task/root/test.ps1"
8484
}
8585

8686
It "Should handle script handler with subdirectory path" {
@@ -93,7 +93,7 @@ Describe "Get-Handler" {
9393
# Assert
9494
$result.handlerType | Should -Be 'Script'
9595
$result.scriptFileName | Should -Be "subfolder/handler.ps1"
96-
$result.scriptFilePath | Should -Be "/var/task/subfolder/handler.ps1"
96+
Assert-PathEquals -Actual $result.scriptFilePath -Expected "/var/task/subfolder/handler.ps1"
9797
}
9898
}
9999

@@ -109,7 +109,7 @@ Describe "Get-Handler" {
109109
$result | Should -Not -BeNullOrEmpty
110110
$result.handlerType | Should -Be 'Function'
111111
$result.scriptFileName | Should -Be "handler.ps1"
112-
$result.scriptFilePath | Should -Be "/var/task/handler.ps1"
112+
Assert-PathEquals -Actual $result.scriptFilePath -Expected "/var/task/handler.ps1"
113113
$result.functionName | Should -Be "MyFunction"
114114
$result.PSObject.Properties['moduleName'] | Should -BeNullOrEmpty
115115
}
@@ -125,7 +125,7 @@ Describe "Get-Handler" {
125125
$result.handlerType | Should -Be 'Function'
126126
$result.scriptFileName | Should -Be "my-script-file.ps1"
127127
$result.functionName | Should -Be "My-Complex-Function-Name"
128-
$result.scriptFilePath | Should -Be "/var/task/my-script-file.ps1"
128+
Assert-PathEquals -Actual $result.scriptFilePath -Expected "/var/task/my-script-file.ps1"
129129
}
130130

131131
It "Should use LAMBDA_TASK_ROOT for function handler script path" {
@@ -137,7 +137,7 @@ Describe "Get-Handler" {
137137
$result = pwsh-runtime\Get-Handler
138138

139139
# Assert
140-
$result.scriptFilePath | Should -Be "/custom/path/handler.ps1"
140+
Assert-PathEquals -Actual $result.scriptFilePath -Expected "/custom/path/handler.ps1"
141141
}
142142

143143
It "Should handle function handler with script in subdirectory" {
@@ -151,7 +151,7 @@ Describe "Get-Handler" {
151151
$result.handlerType | Should -Be 'Function'
152152
$result.scriptFileName | Should -Be "lib/utilities.ps1"
153153
$result.functionName | Should -Be "Get-Data"
154-
$result.scriptFilePath | Should -Be "/var/task/lib/utilities.ps1"
154+
Assert-PathEquals -Actual $result.scriptFilePath -Expected "/var/task/lib/utilities.ps1"
155155
}
156156
}
157157

@@ -229,7 +229,7 @@ Describe "Get-Handler" {
229229

230230
# Assert
231231
$result.scriptFileName | Should -Be "custom-handler.ps1"
232-
$result.scriptFilePath | Should -Be "/var/task/custom-handler.ps1"
232+
Assert-PathEquals -Actual $result.scriptFilePath -Expected "/var/task/custom-handler.ps1"
233233
}
234234

235235
It "Should handle custom function handler parameter" {
@@ -347,4 +347,4 @@ Describe "Get-Handler" {
347347
{ pwsh-runtime\Get-Handler } | Should -Throw
348348
}
349349
}
350-
}
350+
}

0 commit comments

Comments
 (0)