Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 2 additions & 66 deletions .github/workflows/terraform-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,71 +60,7 @@ jobs:

- name: Convert test results to JUnit XML
if: always() && steps.terraform-tests.outcome != 'skipped'
run: |
$jsonPath = 'logs/terraform-test-results.json'
$xmlPath = 'logs/terraform-test-results.xml'

if (-not (Test-Path $jsonPath)) {
Write-Warning "Test results not found at $jsonPath"
return
}

$results = Get-Content $jsonPath -Raw | ConvertFrom-Json
$totalTests = $results.summary.total_passed + $results.summary.total_failed + $results.summary.total_errors

$xml = [System.Xml.XmlDocument]::new()
$declaration = $xml.CreateXmlDeclaration('1.0', 'UTF-8', $null)
$xml.AppendChild($declaration) | Out-Null

$testsuites = $xml.CreateElement('testsuites')
$testsuites.SetAttribute('name', 'Terraform Tests')
$testsuites.SetAttribute('tests', $totalTests)
$testsuites.SetAttribute('failures', $results.summary.total_failed)
$testsuites.SetAttribute('errors', $results.summary.total_errors)
$xml.AppendChild($testsuites) | Out-Null

foreach ($module in $results.modules) {
$moduleTests = $module.passed + $module.failed + $module.errors
$testsuite = $xml.CreateElement('testsuite')
$testsuite.SetAttribute('name', $module.path)
$testsuite.SetAttribute('tests', $moduleTests)
$testsuite.SetAttribute('failures', $module.failed)
$testsuite.SetAttribute('errors', $module.errors)
$testsuites.AppendChild($testsuite) | Out-Null

# Emit one testcase per passed test
for ($i = 1; $i -le $module.passed; $i++) {
$testcase = $xml.CreateElement('testcase')
$testcase.SetAttribute('classname', $module.path)
$testcase.SetAttribute('name', "test_$i")
$testsuite.AppendChild($testcase) | Out-Null
}

# Emit one testcase per failed test
for ($i = 1; $i -le $module.failed; $i++) {
$testcase = $xml.CreateElement('testcase')
$testcase.SetAttribute('classname', $module.path)
$testcase.SetAttribute('name', "failed_$i")
$failure = $xml.CreateElement('failure')
$failure.SetAttribute('message', "Test failed in $($module.path)")
$testcase.AppendChild($failure) | Out-Null
$testsuite.AppendChild($testcase) | Out-Null
}

# Emit one testcase per errored test
for ($i = 1; $i -le $module.errors; $i++) {
$testcase = $xml.CreateElement('testcase')
$testcase.SetAttribute('classname', $module.path)
$testcase.SetAttribute('name', "error_$i")
$errorEl = $xml.CreateElement('error')
$errorEl.SetAttribute('message', "Test errored in $($module.path)")
$testcase.AppendChild($errorEl) | Out-Null
$testsuite.AppendChild($testcase) | Out-Null
}
}

$xml.Save($xmlPath)
Write-Host "JUnit XML written to $xmlPath"
run: shared/ci/linting/ConvertTo-JUnitXml.ps1

- name: Upload Terraform test results
if: always() && steps.terraform-tests.outcome != 'skipped'
Expand All @@ -141,7 +77,7 @@ jobs:
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
with:
use_oidc: true
report-type: test_results
report_type: test_results
files: logs/terraform-test-results.xml
flags: terraform
name: terraform-test-results
Expand Down
5 changes: 5 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# Flags:
# pester — PowerShell tests covering scripts/
# pytest — Python tests covering src/
# terraform — Terraform tests covering infrastructure/terraform/

codecov:
notify:
Expand Down Expand Up @@ -50,6 +51,10 @@ flags:
paths:
- data-management/viewer/backend/src/
carryforward: true
terraform:
paths:
- infrastructure/terraform/
carryforward: true

parsers:
jacoco:
Expand Down
1 change: 1 addition & 0 deletions infrastructure/terraform/tests/outputs.tftest.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,4 @@ run "optional_outputs_null_when_disabled" {
error_message = "aml_compute_cluster output should be null when AML compute is disabled"
}
}

156 changes: 156 additions & 0 deletions shared/ci/linting/ConvertTo-JUnitXml.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
#!/usr/bin/env pwsh
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: MIT

#Requires -Version 7.0

<#
.SYNOPSIS
Converts Terraform test JSON results to JUnit XML format.
.DESCRIPTION
Reads JSON output from Invoke-TerraformTest.ps1 and produces a JUnit XML
file compatible with Codecov Test Analytics. Includes required attributes
(time, skipped, timestamp) on all elements and uses real test names from
the test_runs array when available.
.PARAMETER InputPath
Path to the JSON results file. Defaults to logs/terraform-test-results.json.
.PARAMETER OutputPath
Path for the JUnit XML output. Defaults to logs/terraform-test-results.xml.
#>

[CmdletBinding()]
param(
[string]$InputPath,
[string]$OutputPath
)

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

Import-Module (Join-Path $PSScriptRoot "../../../scripts/lib/Modules/CIHelpers.psm1") -Force

function ConvertTo-JUnitXmlCore {
[CmdletBinding()]
param(
[string]$InputPath,
[string]$OutputPath
)

$repoRoot = & git rev-parse --show-toplevel 2>$null
if (-not $repoRoot) {
$repoRoot = (Get-Item $PSScriptRoot).Parent.Parent.Parent.FullName
}

if (-not $InputPath) { $InputPath = Join-Path $repoRoot 'logs/terraform-test-results.json' }
if (-not $OutputPath) { $OutputPath = Join-Path $repoRoot 'logs/terraform-test-results.xml' }

if (-not (Test-Path $InputPath)) {
Write-Warning "Test results not found at $InputPath"
Write-CIAnnotation -Level Warning -Message "Test results not found at $InputPath"
return 1
}

$results = Get-Content $InputPath -Raw | ConvertFrom-Json
$totalTests = $results.summary.total_passed + $results.summary.total_failed + $results.summary.total_errors
$timestamp = ([datetime]::Parse($results.timestamp)).ToString('yyyy-MM-ddTHH:mm:ss')

$xml = [System.Xml.XmlDocument]::new()
$declaration = $xml.CreateXmlDeclaration('1.0', 'UTF-8', $null)
$xml.AppendChild($declaration) | Out-Null

$testsuites = $xml.CreateElement('testsuites')
$testsuites.SetAttribute('name', 'Terraform Tests')
$testsuites.SetAttribute('tests', $totalTests)
$testsuites.SetAttribute('failures', $results.summary.total_failed)
$testsuites.SetAttribute('errors', $results.summary.total_errors)
$testsuites.SetAttribute('time', '0')
$xml.AppendChild($testsuites) | Out-Null

foreach ($module in $results.modules) {
$moduleTests = $module.passed + $module.failed + $module.errors
$testsuite = $xml.CreateElement('testsuite')
$testsuite.SetAttribute('name', $module.path)
$testsuite.SetAttribute('tests', $moduleTests)
$testsuite.SetAttribute('failures', $module.failed)
$testsuite.SetAttribute('errors', $module.errors)
$testsuite.SetAttribute('skipped', '0')
$testsuite.SetAttribute('timestamp', $timestamp)
$testsuite.SetAttribute('time', '0')
$testsuites.AppendChild($testsuite) | Out-Null

$hasTestRuns = ($null -ne $module.PSObject.Properties['test_runs']) -and ($module.test_runs.Count -gt 0)
if ($hasTestRuns) {
foreach ($run in $module.test_runs) {
$testcase = $xml.CreateElement('testcase')
$testcase.SetAttribute('classname', $module.path)
$testcase.SetAttribute('name', $run.name)
$testcase.SetAttribute('time', '0')

if ($run.status -eq 'fail') {
$failure = $xml.CreateElement('failure')
$failure.SetAttribute('message', "Test failed: $($run.name)")
$testcase.AppendChild($failure) | Out-Null
}
elseif ($run.status -eq 'error') {
$errorEl = $xml.CreateElement('error')
$errorEl.SetAttribute('message', "Test error: $($run.name)")
$testcase.AppendChild($errorEl) | Out-Null
}

$testsuite.AppendChild($testcase) | Out-Null
}
}
else {
for ($i = 1; $i -le $module.passed; $i++) {
$testcase = $xml.CreateElement('testcase')
$testcase.SetAttribute('classname', $module.path)
$testcase.SetAttribute('name', "test_$i")
$testcase.SetAttribute('time', '0')
$testsuite.AppendChild($testcase) | Out-Null
}
for ($i = 1; $i -le $module.failed; $i++) {
$testcase = $xml.CreateElement('testcase')
$testcase.SetAttribute('classname', $module.path)
$testcase.SetAttribute('name', "failed_$i")
$testcase.SetAttribute('time', '0')
$failure = $xml.CreateElement('failure')
$failure.SetAttribute('message', "Test failed in $($module.path)")
$testcase.AppendChild($failure) | Out-Null
$testsuite.AppendChild($testcase) | Out-Null
}
for ($i = 1; $i -le $module.errors; $i++) {
$testcase = $xml.CreateElement('testcase')
$testcase.SetAttribute('classname', $module.path)
$testcase.SetAttribute('name', "error_$i")
$testcase.SetAttribute('time', '0')
$errorEl = $xml.CreateElement('error')
$errorEl.SetAttribute('message', "Test errored in $($module.path)")
$testcase.AppendChild($errorEl) | Out-Null
$testsuite.AppendChild($testcase) | Out-Null
}
}
}

$outputDir = Split-Path $OutputPath -Parent
if ($outputDir -and -not (Test-Path $outputDir)) {
New-Item -ItemType Directory -Force -Path $outputDir | Out-Null
}

$xml.Save($OutputPath)
Write-Host "JUnit XML written to $OutputPath"
return 0
}

#region Main Execution
if ($MyInvocation.InvocationName -ne '.') {
try {
$exitCode = ConvertTo-JUnitXmlCore @PSBoundParameters
exit $exitCode
}
catch {
Write-Error -ErrorAction Continue "ConvertTo-JUnitXml failed: $($_.Exception.Message)"
Write-CIAnnotation -Level Error -Message $_.Exception.Message
exit 1
}
}
#endregion Main Execution
39 changes: 22 additions & 17 deletions shared/ci/linting/Invoke-TerraformTest.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,12 @@ function Invoke-TerraformTestCore {
if ($LASTEXITCODE -ne 0) {
$totalErrors++
$moduleResults += @{
path = $displayPath
passed = 0
failed = 0
errors = 1
skipped = $false
path = $displayPath
passed = 0
failed = 0
errors = 1
skipped = $false
test_runs = @()
}
Write-CIAnnotation -Level Error -Message "terraform init failed in $displayPath"
continue
Expand All @@ -171,6 +172,7 @@ function Invoke-TerraformTestCore {
$modulePassed = 0
$moduleFailed = 0
$moduleErrors = 0
$moduleTestRuns = @()

foreach ($line in $testOutput) {
$lineStr = $line.ToString().Trim()
Expand All @@ -185,17 +187,18 @@ function Invoke-TerraformTestCore {

if ($jsonObj.type -eq 'test_run' -and $jsonObj.test_run.progress -eq 'complete') {
$status = $jsonObj.test_run.status
$testName = "$($jsonObj.test_run.run)"
$moduleTestRuns += @{ name = $testName; status = $status }

if ($status -eq 'pass') {
$modulePassed++
}
elseif ($status -eq 'fail') {
$moduleFailed++
$testName = "$($jsonObj.test_run.run)"
Write-CIAnnotation -Level Error -Message "Test failed in ${displayPath}: $testName"
}
elseif ($status -eq 'error') {
$moduleErrors++
$testName = "$($jsonObj.test_run.run)"
Write-CIAnnotation -Level Error -Message "Test error in ${displayPath}: $testName"
}
}
Expand All @@ -206,11 +209,12 @@ function Invoke-TerraformTestCore {
$totalErrors += $moduleErrors

$moduleResults += @{
path = $displayPath
passed = $modulePassed
failed = $moduleFailed
errors = $moduleErrors
skipped = $false
path = $displayPath
passed = $modulePassed
failed = $moduleFailed
errors = $moduleErrors
skipped = $false
test_runs = @($moduleTestRuns)
}
}
finally {
Expand All @@ -227,11 +231,12 @@ function Invoke-TerraformTestCore {
terraform_version = $versionString
modules = @($moduleResults | ForEach-Object {
@{
path = $_.path
passed = $_.passed
failed = $_.failed
errors = $_.errors
skipped = if ($_.skipped) { $true } else { $false }
path = $_.path
passed = $_.passed
failed = $_.failed
errors = $_.errors
skipped = if ($_.skipped) { $true } else { $false }
test_runs = @($_.test_runs)
}
})
summary = @{
Expand Down
Loading
Loading