Skip to content

Commit 2d9cec5

Browse files
author
Andrew Pearce
committed
feat: Add integration testing framework for PowerShell Lambda runtime
Add integration testing infrastructure to validate PowerShell Lambda runtime functionality across handler types. - Integration test suite for Script, Function, and Module handler types - Helper module with validation functions and schema-based context validation - AWS infrastructure template for test environment deployment - Test handler implementations for each PowerShell handler pattern This establishes testing infrastructure to validate runtime functionality and support regression testing.
1 parent 4266ef6 commit 2d9cec5

19 files changed

+1716
-136
lines changed

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,12 @@ This runtime uses Lambda's [custom runtime](https://docs.aws.amazon.com/lambda/l
2828

2929
### Testing
3030

31-
The runtime includes comprehensive unit tests. See [powershell-runtime/tests/README.md](powershell-runtime/tests/README.md) for testing documentation and commands.
31+
The runtime includes unit tests and integration tests:
32+
33+
* **Unit Tests**: Automated tests covering runtime functions and build processes
34+
* **Integration Tests**: Manual tests with real AWS Lambda functions for end-to-end validation
35+
36+
See [powershell-runtime/tests/README.md](powershell-runtime/tests/README.md) for testing documentation and commands.
3237

3338
## Building and Deploying
3439

powershell-runtime/readme.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
# PowerShell-runtime
1+
# PowerShell Runtime
22

33
Contains the PowerShell custom runtime based on `provided.al2023` with deployment methods.
44

55
Deploy the example [demo-runtime-layer-function](../examples/demo-runtime-layer-function/) to explore how the runtime and PowerShell function work.
66

7-
## Deploying the PowerShell custom runtime
7+
## Deploying the PowerShell Custom Runtime
88

9-
The recommended deployment method is AWS SAM, though other infrastructure-as-code tools are also supported.
9+
AWS SAM is the deployment method, though other infrastructure-as-code tools are also supported.
1010

1111
## AWS SAM
1212

@@ -58,7 +58,7 @@ Enter a **Stack Name** such as `powershell-runtime` and accept the remaining ini
5858

5959
## Development and Testing
6060

61-
See [tests/README.md](tests/README.md) for comprehensive testing documentation and commands.
61+
See [tests/README.md](tests/README.md) for testing documentation and commands.
6262

6363
## Powershell runtime information
6464

@@ -121,7 +121,7 @@ The PowerShell runtime imports the specified `<module_name>`. This allows you to
121121

122122
### PowerShell module support
123123

124-
You can include additional PowerShell modules either via a Lambda Layer, or within your function code package, or container image. Using Lambda layers provides a convenient way to package and share modules that you can use with your Lambda functions. Layers reduce the size of uploaded deployment archives and make it faster to deploy your code.
124+
You can include PowerShell modules via a Lambda Layer, within your function code package, or container image. Using Lambda layers provides a way to package and share modules that you can use with your Lambda functions. Layers reduce the size of uploaded deployment archives and make it faster to deploy your code.
125125

126126
The `PSModulePath` environment variable contains a list of folder locations that are searched to find user-supplied modules. This is configured during the runtime initialization. Folders are specified in the following order:
127127

powershell-runtime/source/PowerShellLambdaContext.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ double DeadlineMS
106106
{
107107
this.FunctionName = FunctionName;
108108
this.FunctionVersion = FunctionVersion;
109-
this.InvokedFunctionArn = FunctionName;
109+
this.InvokedFunctionArn = InvokedFunctionArn;
110110
this.MemoryLimitInMB = MemoryLimitInMB;
111111
this.AwsRequestId = AwsRequestId;
112112
this.LogGroupName = LogGroupName;

powershell-runtime/source/modules/Private/Set-LambdaContext.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ function Private:Set-LambdaContext {
1818
$private:LambdaContext = [Amazon.Lambda.PowerShell.Internal.LambdaContext]::new(
1919
$env:AWS_LAMBDA_FUNCTION_NAME,
2020
$env:AWS_LAMBDA_FUNCTION_VERSION,
21-
$env:AWS_LAMBDA_RUNTIME_INVOKE_FUNCTION_ARN,
21+
$env:AWS_LAMBDA_RUNTIME_INVOKED_FUNCTION_ARN,
2222
[int]$env:AWS_LAMBDA_FUNCTION_MEMORY_SIZE,
2323
$env:AWS_LAMBDA_RUNTIME_AWS_REQUEST_ID,
2424
$env:AWS_LAMBDA_LOG_GROUP_NAME,

powershell-runtime/tests/Invoke-Tests.ps1

Lines changed: 211 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@
1111
- BuiltModule: Tests the built/merged module (validation)
1212

1313
.PARAMETER TestType
14-
Specifies which type of tests to run. Valid values: 'All', 'Unit', 'Build'
14+
Specifies which type of tests to run. Valid values: 'LocalUnit', 'Unit', 'Build', 'Private', 'Integration'
15+
- LocalUnit: Runs all local unit tests (Build, Module, and Private) - Default
16+
- Unit: Runs Module and Private unit tests
17+
- Build: Runs only Build tests
18+
- Private: Runs only Private unit tests
19+
- Integration: Runs only integration tests
1520

1621
.PARAMETER Path
1722
Specifies specific test files or directories to run
@@ -31,27 +36,55 @@
3136
.PARAMETER DetailedOutput
3237
Enables detailed Pester output for debugging
3338

39+
.PARAMETER StackName
40+
Name of the CloudFormation stack for integration tests (default: "powershell-runtime-integration-test-infrastructure")
41+
Only used when TestType is 'Integration'
42+
43+
.PARAMETER Region
44+
AWS region for infrastructure deployment (default: "us-east-1")
45+
Only used when TestType is 'Integration'
46+
47+
.PARAMETER ProfileName
48+
AWS profile name for authentication
49+
Only used when TestType is 'Integration'
50+
3451
.EXAMPLE
3552
./Invoke-Tests.ps1
36-
Runs all tests in Source mode (fastest for development)
53+
Runs all local unit tests (Build, Module, Private) in Source mode (fastest for development)
3754

3855
.EXAMPLE
3956
./Invoke-Tests.ps1 -TestBuiltModule -Coverage
40-
Runs all tests against built module with coverage analysis
57+
Runs all local unit tests against built module with coverage analysis
58+
59+
.EXAMPLE
60+
./Invoke-Tests.ps1 -TestType Private
61+
Runs only Private unit tests
62+
63+
.EXAMPLE
64+
./Invoke-Tests.ps1 -TestType Build
65+
Runs only Build tests
4166

4267
.EXAMPLE
4368
./Invoke-Tests.ps1 -Path './tests/unit/Private/Get-Handler.Tests.ps1'
4469
Runs a specific test file
4570

71+
.EXAMPLE
72+
./Invoke-Tests.ps1 -TestType Integration
73+
Runs integration tests with default stack name and region, automatically sets up and cleans up environment
74+
75+
.EXAMPLE
76+
./Invoke-Tests.ps1 -TestType Integration -StackName "my-test-stack" -Region "us-west-2" -ProfileName "dev"
77+
Runs integration tests with custom stack, region, and AWS profile
78+
4679
.EXAMPLE
4780
./Invoke-Tests.ps1 -CI -OutputFormat NUnitXml
4881
Runs all tests in CI mode with NUnit XML output
4982
#>
5083

5184
[CmdletBinding()]
5285
param(
53-
[ValidateSet('All', 'Unit', 'Build')]
54-
[string]$TestType = 'All',
86+
[ValidateSet('LocalUnit', 'Unit', 'Build', 'Private', 'Integration')]
87+
[string]$TestType = 'LocalUnit',
5588

5689
[string[]]$Path,
5790
[switch]$TestBuiltModule,
@@ -61,7 +94,12 @@ param(
6194
[ValidateSet('NUnitXml', 'JUnitXml', 'Console')]
6295
[string]$OutputFormat = 'Console',
6396

64-
[switch]$DetailedOutput
97+
[switch]$DetailedOutput,
98+
99+
# Integration test parameters
100+
[string]$StackName = "powershell-runtime-integration-test-infrastructure",
101+
[string]$Region = "us-east-1",
102+
[string]$ProfileName
65103
)
66104

67105
# Set error action preference for consistent behavior
@@ -140,7 +178,7 @@ function Initialize-TestFramework {
140178
function Get-CoveragePaths {
141179
param($TestPaths, $ProjectRoot, $TestBuiltModule, $TestType)
142180

143-
# For Build tests, always include the build script
181+
# Build tests - only cover build script
144182
if ($TestType -eq 'Build') {
145183
$buildScript = Join-Path $ProjectRoot "build-PwshRuntimeLayer.ps1"
146184
if (Test-Path $buildScript) {
@@ -152,20 +190,42 @@ function Get-CoveragePaths {
152190
}
153191
}
154192

193+
# Private tests - only cover private source functions
194+
if ($TestType -eq 'Private') {
195+
$privatePath = Join-Path $ProjectRoot "source/modules/Private"
196+
if (Test-Path $privatePath) {
197+
return Get-ChildItem -Path $privatePath -Filter "*.ps1" -Recurse | ForEach-Object { $_.FullName }
198+
}
199+
else {
200+
Write-Warning "Private source path not found at: $privatePath"
201+
return @()
202+
}
203+
}
204+
205+
# For Unit/LocalUnit tests:
206+
# - If TestBuiltModule: only cover built module
207+
# - If source testing: cover all source files
155208
if ($TestBuiltModule) {
156209
$builtModule = Join-Path $ProjectRoot "layers/runtimeLayer/modules/pwsh-runtime.psm1"
157210
if (Test-Path $builtModule) {
158211
return @($builtModule)
159212
}
213+
else {
214+
Write-Warning "Built module not found at: $builtModule"
215+
return @()
216+
}
160217
}
161-
162-
# For source testing, include all source files
163-
$sourcePath = Join-Path $ProjectRoot "source/modules"
164-
if (Test-Path $sourcePath) {
165-
return Get-ChildItem -Path $sourcePath -Filter "*.ps1" -Recurse | ForEach-Object { $_.FullName }
218+
else {
219+
# Source testing - cover all source files
220+
$sourcePath = Join-Path $ProjectRoot "source/modules"
221+
if (Test-Path $sourcePath) {
222+
return Get-ChildItem -Path $sourcePath -Filter "*.ps1" -Recurse | ForEach-Object { $_.FullName }
223+
}
224+
else {
225+
Write-Warning "Source path not found at: $sourcePath"
226+
return @()
227+
}
166228
}
167-
168-
return @()
169229
}
170230

171231
function Write-CIDiagnostics {
@@ -199,6 +259,98 @@ function Invoke-CICleanup {
199259
Write-Host "CI Cleanup: Completed" -ForegroundColor Green
200260
}
201261

262+
function Initialize-IntegrationTestEnvironment {
263+
[CmdletBinding()]
264+
param(
265+
[string]$StackName,
266+
[string]$Region,
267+
[string]$ProfileName
268+
)
269+
270+
Write-Host "Setting up integration test environment..." -ForegroundColor Yellow
271+
272+
# Import required AWS modules
273+
Write-Host "Importing required AWS modules..." -ForegroundColor Yellow
274+
if (-not (Get-Module -Name AWS.Tools.CloudFormation -ListAvailable)) {
275+
throw "AWS.Tools.CloudFormation module is not installed. Please install it with: Install-Module -Name AWS.Tools.CloudFormation -Force"
276+
}
277+
Import-Module AWS.Tools.CloudFormation -ErrorAction Stop
278+
Write-Host "AWS modules imported successfully" -ForegroundColor Green
279+
280+
# Set up authentication parameters
281+
$awsParams = @{
282+
Region = $Region
283+
}
284+
285+
# Add profile if specified
286+
if ($ProfileName) {
287+
$awsParams.ProfileName = $ProfileName
288+
}
289+
290+
Write-Host "Retrieving stack outputs for $StackName..." -ForegroundColor Yellow
291+
292+
try {
293+
$stack = Get-CFNStack -StackName $StackName @awsParams
294+
}
295+
catch {
296+
Write-Error "Failed to retrieve stack information: $_"
297+
throw "Could not find stack '$StackName'. Make sure the stack exists and you have permission to access it."
298+
}
299+
300+
# Convert outputs to environment variables
301+
$outputCount = 0
302+
foreach ($output in ($stack.Outputs | Sort-Object -Property OutputKey)) {
303+
$envVarName = "PWSH_TEST_$($output.OutputKey.ToUpper())"
304+
Write-Host "Setting $envVarName = $($output.OutputValue)" -ForegroundColor Cyan
305+
[Environment]::SetEnvironmentVariable($envVarName, $output.OutputValue, "Process")
306+
$outputCount++
307+
}
308+
309+
# Set a flag indicating infrastructure is available
310+
[Environment]::SetEnvironmentVariable("PWSH_TEST_INFRASTRUCTURE_DEPLOYED", "TRUE", "Process")
311+
312+
# Set configuration environment variables for use by other scripts/tests
313+
Write-Host "Setting configuration environment variables..." -ForegroundColor Yellow
314+
[Environment]::SetEnvironmentVariable("PWSH_TEST_STACK_NAME", $StackName, "Process")
315+
Write-Host "Setting PWSH_TEST_STACK_NAME = $StackName" -ForegroundColor Cyan
316+
317+
[Environment]::SetEnvironmentVariable("PWSH_TEST_AWS_REGION", $Region, "Process")
318+
Write-Host "Setting PWSH_TEST_AWS_REGION = $Region" -ForegroundColor Cyan
319+
320+
if ($ProfileName) {
321+
[Environment]::SetEnvironmentVariable("PWSH_TEST_PROFILE_NAME", $ProfileName, "Process")
322+
Write-Host "Setting PWSH_TEST_PROFILE_NAME = $ProfileName" -ForegroundColor Cyan
323+
}
324+
325+
Write-Host "Successfully set $outputCount stack output environment variables for integration tests" -ForegroundColor Green
326+
Write-Host "Environment variable PWSH_TEST_INFRASTRUCTURE_DEPLOYED has been set to TRUE" -ForegroundColor Green
327+
Write-Host "Configuration environment variables (PWSH_TEST_STACK_NAME, PWSH_TEST_AWS_REGION$(if ($ProfileName) { ', PWSH_TEST_PROFILE_NAME' })) have been set" -ForegroundColor Green
328+
}
329+
330+
function Clear-IntegrationTestEnvironment {
331+
[CmdletBinding()]
332+
param()
333+
334+
Write-Host "Cleaning up integration test environment variables..." -ForegroundColor Yellow
335+
336+
# Get all environment variables that start with PWSH_TEST_
337+
$testEnvVars = [Environment]::GetEnvironmentVariables("Process").Keys | Where-Object { $_ -like "PWSH_TEST_*" }
338+
339+
$cleanedCount = 0
340+
foreach ($envVar in $testEnvVars) {
341+
Write-Host "Removing $envVar" -ForegroundColor Cyan
342+
[Environment]::SetEnvironmentVariable($envVar, $null, "Process")
343+
$cleanedCount++
344+
}
345+
346+
if ($cleanedCount -gt 0) {
347+
Write-Host "Successfully cleaned up $cleanedCount integration test environment variables" -ForegroundColor Green
348+
}
349+
else {
350+
Write-Host "No integration test environment variables found to clean up" -ForegroundColor Yellow
351+
}
352+
}
353+
202354

203355

204356
function Write-GitHubStepSummary {
@@ -358,15 +510,39 @@ try {
358510

359511
Initialize-TestFramework
360512

513+
# Set up integration test environment if needed
514+
if ($TestType -eq 'Integration') {
515+
try {
516+
Initialize-IntegrationTestEnvironment -StackName $StackName -Region $Region -ProfileName $ProfileName
517+
}
518+
catch {
519+
Write-Error "Failed to initialize integration test environment: $_"
520+
throw "Integration test environment setup failed. Please ensure the CloudFormation stack '$StackName' exists in region '$Region' and you have appropriate permissions."
521+
}
522+
}
523+
361524
# Configure paths
362525
$TestPaths = if ($Path) {
363526
$Path | Where-Object { Test-Path $_ }
364527
}
365528
else {
366529
switch ($TestType) {
367-
'Unit' { Join-Path $script:ProjectRoot 'tests/unit' }
530+
'Unit' {
531+
@(
532+
(Join-Path $script:ProjectRoot 'tests/unit/Module'),
533+
(Join-Path $script:ProjectRoot 'tests/unit/Private')
534+
)
535+
}
368536
'Build' { Join-Path $script:ProjectRoot 'tests/unit/Build' }
369-
default { Join-Path $script:ProjectRoot 'tests/unit' }
537+
'Private' { Join-Path $script:ProjectRoot 'tests/unit/Private' }
538+
'Integration' { Join-Path $script:ProjectRoot 'tests/integration' }
539+
'LocalUnit' {
540+
@(
541+
(Join-Path $script:ProjectRoot 'tests/unit/Build'),
542+
(Join-Path $script:ProjectRoot 'tests/unit/Module'),
543+
(Join-Path $script:ProjectRoot 'tests/unit/Private')
544+
)
545+
}
370546
}
371547
}
372548

@@ -391,12 +567,17 @@ try {
391567
$Config.CodeCoverage.Enabled = $true
392568
$Config.CodeCoverage.OutputFormat = 'JaCoCo'
393569
$Config.CodeCoverage.OutputPath = Join-Path $script:ProjectRoot "CodeCoverage.xml"
394-
$Config.CodeCoverage.Path = Get-CoveragePaths -TestPaths $TestPaths -ProjectRoot $script:ProjectRoot -TestBuiltModule $TestBuiltModule -TestType $TestType
570+
$coveragePaths = Get-CoveragePaths -TestPaths $TestPaths -ProjectRoot $script:ProjectRoot -TestBuiltModule $TestBuiltModule -TestType $TestType
571+
$Config.CodeCoverage.Path = $coveragePaths
395572

396573
$coverageThreshold = $script:TestConfig.PrivateData.TestSettings.CodeCoverage.Threshold
397574
$Config.CodeCoverage.CoveragePercentTarget = $coverageThreshold
398575

399-
Write-Host "Coverage enabled: $($Config.CodeCoverage.Path.Count) files" -ForegroundColor Yellow
576+
Write-Host "Coverage enabled for $($coveragePaths.Count) files:" -ForegroundColor Yellow
577+
foreach ($path in $coveragePaths) {
578+
$relativePath = $path -replace [regex]::Escape($script:ProjectRoot), '.'
579+
Write-Host " - $relativePath" -ForegroundColor Cyan
580+
}
400581
}
401582

402583
# Configure output
@@ -461,4 +642,15 @@ catch {
461642
Invoke-CICleanup
462643
}
463644
Exit-TestScript -ExitCode 1 -ExitMessage "Test execution failed: $_"
464-
}
645+
}
646+
finally {
647+
# Always clean up integration test environment variables
648+
if ($TestType -eq 'Integration') {
649+
try {
650+
Clear-IntegrationTestEnvironment
651+
}
652+
catch {
653+
Write-Warning "Failed to clean up integration test environment: $_"
654+
}
655+
}
656+
}

0 commit comments

Comments
 (0)