microsoft/hve-core

Public

mirrored fromhttps://github.com/microsoft/hve-coreAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
8b197250063fc1629244f661f78baf9022cebbb0

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

scripts/tests/Invoke-PesterTests.ps1

168lines · modecode

1#!/usr/bin/env pwsh
2# Copyright (c) Microsoft Corporation.
3# SPDX-License-Identifier: MIT
4#
5# Invoke-PesterTests.ps1
6#
7# Purpose: Pester test runner that writes summary and failure details to logs/
8# Author: HVE Core Team
9
10#Requires -Version 7.0
11
12<#
13.SYNOPSIS
14 Runs Pester tests and writes structured output to the logs/ directory.
15
16.DESCRIPTION
17 Wraps Invoke-Pester with the repository's Pester configuration and writes
18 logs/pester-summary.json (pass/fail counts, duration) and
19 logs/pester-failures.json (failure details with error messages and stack traces).
20
21.PARAMETER TestPath
22 One or more paths to test files or directories. Defaults to the scripts/tests/ directory.
23
24.PARAMETER CI
25 Enables CI mode: NUnit XML output, exit-on-failure, and GitHub Actions log format.
26
27.PARAMETER CodeCoverage
28 Enables JaCoCo code coverage reporting to logs/coverage.xml.
29
30.EXAMPLE
31 ./scripts/tests/Invoke-PesterTests.ps1
32
33.EXAMPLE
34 ./scripts/tests/Invoke-PesterTests.ps1 -TestPath "scripts/tests/linting/"
35
36.EXAMPLE
37 ./scripts/tests/Invoke-PesterTests.ps1 -CI -CodeCoverage
38#>
39[CmdletBinding()]
40param(
41 [Parameter(Mandatory = $false)]
42 [string[]]$TestPath,
43
44 [Parameter(Mandatory = $false)]
45 [switch]$CI,
46
47 [Parameter(Mandatory = $false)]
48 [switch]$CodeCoverage
49)
50
51$ErrorActionPreference = 'Stop'
52
53$repoRoot = git rev-parse --show-toplevel 2>$null
54if (-not $repoRoot) {
55 $repoRoot = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
56}
57$logsDir = Join-Path $repoRoot 'logs'
58$configScript = Join-Path $PSScriptRoot 'pester.config.ps1'
59$summaryPath = Join-Path $logsDir 'pester-summary.json'
60$failuresPath = Join-Path $logsDir 'pester-failures.json'
61
62if (-not (Test-Path $logsDir)) {
63 New-Item -ItemType Directory -Force -Path $logsDir | Out-Null
64}
65
66# Build config arguments
67$configArgs = @{}
68if ($CI) {
69 $configArgs['CI'] = $true
70}
71if ($CodeCoverage) {
72 $configArgs['CodeCoverage'] = $true
73}
74if ($TestPath) {
75 $resolvedPaths = @($TestPath | ForEach-Object {
76 $p = if ([System.IO.Path]::IsPathRooted($_)) { $_ } else { Join-Path $repoRoot $_ }
77 if (-not (Test-Path $p)) {
78 Write-Warning "Test path not found: $_"
79 }
80 $p
81 })
82 if ($resolvedPaths.Count -gt 0) {
83 $configArgs['TestPath'] = $resolvedPaths
84 }
85}
86
87$configuration = & $configScript @configArgs
88
89# Ensure PassThru and file output are enabled regardless of CI flag
90$configuration.Run.PassThru = $true
91
92Write-Host "🧪 Running Pester tests..." -ForegroundColor Cyan
93if ($TestPath) {
94 Write-Host " Test paths: $($TestPath -join ', ')" -ForegroundColor Cyan
95}
96
97$result = Invoke-Pester -Configuration $configuration
98
99# Build summary
100$summary = [ordered]@{
101 Timestamp = (Get-Date -Format 'o')
102 Result = $result.Result
103 TotalCount = $result.TotalCount
104 PassedCount = $result.PassedCount
105 FailedCount = $result.FailedCount
106 SkippedCount = $result.SkippedCount
107 Duration = $result.Duration.ToString()
108}
109
110if ($CodeCoverage -and $result.CodeCoverage) {
111 $summary['CoveragePercent'] = [math]::Round($result.CodeCoverage.CoveragePercent, 2)
112}
113
114$summary | ConvertTo-Json -Depth 3 | Out-File -FilePath $summaryPath -Encoding utf8
115
116# Build failures list
117$failures = @()
118foreach ($test in $result.Tests) {
119 if ($test.Result -eq 'Failed') {
120 $failures += [ordered]@{
121 Name = $test.ExpandedName
122 Path = $test.ScriptBlock.File
123 ErrorMessage = ($test.ErrorRecord | ForEach-Object { $_.Exception.Message }) -join "`n"
124 StackTrace = ($test.ErrorRecord | ForEach-Object { $_.ScriptStackTrace }) -join "`n"
125 }
126 }
127}
128
129# Recursively collect failures from containers when Tests are nested
130function Get-FailedTests {
131 param([object[]]$Blocks)
132 $collected = @()
133 foreach ($block in $Blocks) {
134 if ($block.Tests) {
135 foreach ($test in $block.Tests) {
136 if ($test.Result -eq 'Failed') {
137 $collected += [ordered]@{
138 Name = $test.ExpandedName
139 Path = if ($test.ScriptBlock.File) { $test.ScriptBlock.File } else { '' }
140 ErrorMessage = ($test.ErrorRecord | ForEach-Object { $_.Exception.Message }) -join "`n"
141 StackTrace = ($test.ErrorRecord | ForEach-Object { $_.ScriptStackTrace }) -join "`n"
142 }
143 }
144 }
145 }
146 if ($block.Blocks) {
147 $collected += Get-FailedTests -Blocks $block.Blocks
148 }
149 }
150 return $collected
151}
152
153# If top-level Tests didn't capture failures, walk the container tree
154if ($failures.Count -eq 0 -and $result.FailedCount -gt 0) {
155 $failures = @(Get-FailedTests -Blocks $result.Containers)
156}
157
158$failures | ConvertTo-Json -Depth 5 | Out-File -FilePath $failuresPath -Encoding utf8
159
160# Report
161if ($result.FailedCount -gt 0) {
162 Write-Host "`n❌ $($result.FailedCount) test(s) failed. See logs/pester-summary.json and logs/pester-failures.json" -ForegroundColor Red
163 exit 1
164}
165else {
166 Write-Host "`n✅ All $($result.PassedCount) tests passed." -ForegroundColor Green
167 exit 0
168}
169