mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-10-17 01:59:04 +02:00
ci: build Go test binaries while fixtures are provisioned (#2656)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
465fccbe69
commit
d25d2328f2
+42
-44
@@ -1,6 +1,6 @@
|
||||
name: CI
|
||||
|
||||
# Windows tests are gated on Go and workflow changes for pull requests and branch pushes.
|
||||
# Windows tests are gated on Go, workflow and Go test script changes for pull requests and branch pushes.
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
exit 0
|
||||
fi
|
||||
|
||||
changed_files=$(git diff --name-only "$BASE_SHA" "$HEAD_SHA" -- '*.go' '.github/workflows/ci.yml')
|
||||
changed_files=$(git diff --name-only "$BASE_SHA" "$HEAD_SHA" -- '*.go' '.github/workflows/ci.yml' 'tools/build-go-tests.ps1' 'tools/run-go-tests.ps1')
|
||||
if [[ -n "$changed_files" ]]; then
|
||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
@@ -403,6 +403,14 @@ jobs:
|
||||
path: ${{ runner.temp }}/sql-server/setup
|
||||
key: sql-server-2025-express-fa7e1fabc9a2-${{ runner.os }}
|
||||
|
||||
# Its PATH changes apply only after a wait step includes it.
|
||||
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
id: setup-go
|
||||
background: true
|
||||
with:
|
||||
# renovate: golang=go
|
||||
go-version: '1.27.2'
|
||||
|
||||
# Steps that do not register services run in the background while Windows features install.
|
||||
- name: Download SQL Server media
|
||||
id: download-mssql
|
||||
@@ -584,11 +592,15 @@ jobs:
|
||||
- name: Wait for downloads
|
||||
wait: [download-mssql, install-freerdp, download-container-prereqs]
|
||||
|
||||
# Install every Windows feature in one foreground step. Steps that register
|
||||
# services, such as SQL Server setup, New-Cluster, and containerd, start only
|
||||
# afterwards: changing service configuration during the install aborts its
|
||||
# registry transaction, and the rollback then requires a restart.
|
||||
# Install every Windows feature in one step. Steps that register services,
|
||||
# such as SQL Server setup, New-Cluster, and containerd, start only after
|
||||
# "Wait for Windows features": changing service configuration during the
|
||||
# install aborts its registry transaction, and the rollback then requires
|
||||
# a restart.
|
||||
- name: Install Windows features
|
||||
id: install-windows-features
|
||||
# Setting up Go and building the tests register no services, so they overlap the install.
|
||||
background: true
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ErrorActionPreference = "Stop"
|
||||
@@ -627,6 +639,23 @@ jobs:
|
||||
Get-WindowsFeature -Name $features |
|
||||
Format-Table DisplayName, Name, InstallState
|
||||
|
||||
- name: Wait for Go
|
||||
wait: [setup-go]
|
||||
|
||||
# Linking the race-enabled test binaries takes minutes; do it while the
|
||||
# fixtures below are provisioned instead of in the Go tests step.
|
||||
- name: Build Go test binaries
|
||||
id: build-go-tests
|
||||
background: true
|
||||
shell: pwsh
|
||||
run: |
|
||||
./tools/build-go-tests.ps1 -OutputDirectory (Join-Path $env:RUNNER_TEMP "go-tests")
|
||||
go build -trimpath -o windows_exporter.exe ./cmd/windows_exporter
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
|
||||
- name: Wait for Windows features
|
||||
wait: [install-windows-features]
|
||||
|
||||
- name: Setup SQL Server Express
|
||||
id: setup-mssql
|
||||
background: true
|
||||
@@ -1566,14 +1595,9 @@ jobs:
|
||||
}
|
||||
|
||||
|
||||
# The foreground waits for SQL Server here anyway, so Go setup costs nothing.
|
||||
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
with:
|
||||
# renovate: golang=go
|
||||
go-version: '1.27.2'
|
||||
|
||||
- name: Wait for background setup
|
||||
wait:
|
||||
- build-go-tests
|
||||
- pull-hostprocess
|
||||
- pull-nanoserver
|
||||
- setup-bitlocker
|
||||
@@ -1609,43 +1633,20 @@ jobs:
|
||||
if ($LASTEXITCODE -ne 0) { throw "Failed to synchronize WMI performance counters" }
|
||||
|
||||
- name: Go tests
|
||||
id: go-tests
|
||||
shell: pwsh
|
||||
env:
|
||||
# Temporary: log raw ClusAPI property lists for parser fixtures.
|
||||
WINDOWS_EXPORTER_TEST_CLUSAPI_DUMP: "1"
|
||||
run: |
|
||||
go test -json -count=1 -race -timeout=10m ./... 2>&1 | Tee-Object test-results.jsonl |
|
||||
ForEach-Object {
|
||||
$line = $_.ToString()
|
||||
try {
|
||||
$event = $line | ConvertFrom-Json -ErrorAction Stop
|
||||
if ($event.Output) { [Console]::Write($event.Output) }
|
||||
} catch {
|
||||
Write-Host $line
|
||||
}
|
||||
}
|
||||
$testExitCode = $LASTEXITCODE
|
||||
"exit_code=$testExitCode" | Add-Content $env:GITHUB_OUTPUT
|
||||
exit $testExitCode
|
||||
run: ./tools/run-go-tests.ps1 -BinaryDirectory (Join-Path $env:RUNNER_TEMP "go-tests") -Timeout 10m
|
||||
|
||||
- name: Summarize Windows feature tests
|
||||
- name: Smoke test exporter
|
||||
shell: pwsh
|
||||
run: ./tools/end-to-end-test.ps1
|
||||
|
||||
- name: Collect Windows setup logs
|
||||
if: ${{ always() }}
|
||||
shell: pwsh
|
||||
env:
|
||||
TEST_EXIT_CODE: ${{ steps.go-tests.outputs.exit_code || '1' }}
|
||||
run: |
|
||||
Copy-Item "$env:windir/Logs/DISM/dism.log" (Join-Path $env:RUNNER_TEMP "dism.log") -ErrorAction SilentlyContinue
|
||||
# CBS.log names the files that make role installation request a restart.
|
||||
Copy-Item "$env:windir/Logs/CBS/CBS.log" (Join-Path $env:RUNNER_TEMP "cbs.log") -ErrorAction SilentlyContinue
|
||||
./tools/test-summary.ps1 -TestExitCode ([int]$env:TEST_EXIT_CODE)
|
||||
|
||||
- name: Build and smoke test exporter
|
||||
shell: pwsh
|
||||
run: |
|
||||
go build -trimpath -o windows_exporter.exe ./cmd/windows_exporter
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
./tools/end-to-end-test.ps1
|
||||
|
||||
- name: Upload test logs
|
||||
if: ${{ always() }}
|
||||
@@ -1653,9 +1654,6 @@ jobs:
|
||||
with:
|
||||
name: windows-test-results-${{ matrix.os }}
|
||||
path: |
|
||||
test-output.txt
|
||||
test-results.jsonl
|
||||
feature-summary.md
|
||||
${{ runner.temp }}/windows_exporter.*.log
|
||||
${{ runner.temp }}/kubelet.*.log
|
||||
${{ runner.temp }}/sql-server-summary.txt
|
||||
|
||||
+13
-9
@@ -1,9 +1,15 @@
|
||||
# Windows feature tests
|
||||
|
||||
The CI Windows job prepares real Windows roles and workloads before running
|
||||
`go test -json -count=1 -race -timeout=10m ./...`. Collector tests validate the
|
||||
metrics from those workloads. A final exporter smoke check builds and starts the
|
||||
binary, requests `/metrics` once, and checks for a nonempty HTTP 200 response.
|
||||
The CI Windows job prepares real Windows roles and workloads, then runs the Go
|
||||
tests with the race detector. While the fixtures are provisioned, a background
|
||||
step builds every test binary and the exporter with
|
||||
[`tools/build-go-tests.ps1`](../tools/build-go-tests.ps1); linking the race test
|
||||
binaries would otherwise take minutes after provisioning.
|
||||
[`tools/run-go-tests.ps1`](../tools/run-go-tests.ps1) then runs the binaries
|
||||
verbosely in parallel, each in its package directory, and prints each
|
||||
package's output as one block. Collector tests validate the metrics
|
||||
from those workloads. A final exporter smoke check starts the binary, requests
|
||||
`/metrics` once, and checks for a nonempty HTTP 200 response.
|
||||
|
||||
The job runs on Windows Server 2022; Server 2025 runners take much longer to
|
||||
provision the same fixtures. It provisions Containers, Hyper-V, SQL Server
|
||||
@@ -23,8 +29,6 @@ when setup is missing or collection fails instead of skipping. Fixture assertion
|
||||
check known instances, including the active RDP user's session and SQL database.
|
||||
RemoteFX is required when both network and graphics instances are available.
|
||||
|
||||
Each Windows job publishes a feature table in its Actions summary and a
|
||||
`windows-test-results-<OS>` artifact containing `feature-summary.md`, structured
|
||||
Go test events, readable test output, and setup diagnostics. The table distinguishes
|
||||
required fixtures, unavailable collectors, and empty optional instance groups.
|
||||
This records Windows feature availability rather than Go statement coverage.
|
||||
Each Windows job uploads a `windows-test-results-<OS>` artifact containing
|
||||
setup diagnostics. Unavailable optional collectors show up in the Go tests log
|
||||
as skipped `TestCollector` tests with the reason in their output.
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
# Builds the race-enabled test binaries ahead of time, so CI can link them in
|
||||
# the background while Windows fixtures are provisioned. run-go-tests.ps1 runs
|
||||
# the binaries listed in manifest.json.
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string]$OutputDirectory
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Set-StrictMode -Version 3
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $OutputDirectory | Out-Null
|
||||
$OutputDirectory = (Resolve-Path $OutputDirectory).Path
|
||||
|
||||
$listing = go list -f '{{if or .TestGoFiles .XTestGoFiles}}{{.ImportPath}}{{"\t"}}{{.Dir}}{{end}}' ./...
|
||||
if ($LASTEXITCODE -ne 0) { throw "go list failed with exit code $LASTEXITCODE" }
|
||||
|
||||
$packages = @($listing | Where-Object { $_ } | ForEach-Object {
|
||||
$importPath, $dir = $_ -split "`t", 2
|
||||
[pscustomobject]@{
|
||||
ImportPath = $importPath
|
||||
Dir = $dir
|
||||
Base = $importPath.Split('/')[-1]
|
||||
Binary = $null
|
||||
}
|
||||
})
|
||||
|
||||
# `go test -c -o dir/` names binaries after the last import path element, so
|
||||
# packages sharing that name (internal/collector, pkg/collector) build separately.
|
||||
$groups = $packages | Group-Object Base
|
||||
$shared = @($groups | Where-Object Count -eq 1 | ForEach-Object { $_.Group[0] })
|
||||
$clashing = @($groups | Where-Object Count -gt 1 | ForEach-Object { $_.Group })
|
||||
|
||||
foreach ($package in $shared) {
|
||||
$package.Binary = "$($package.Base).test.exe"
|
||||
}
|
||||
|
||||
go test -race -c -o "$OutputDirectory/" @($shared.ImportPath)
|
||||
if ($LASTEXITCODE -ne 0) { throw "go test -c failed with exit code $LASTEXITCODE" }
|
||||
|
||||
foreach ($package in $clashing) {
|
||||
$package.Binary = ($package.ImportPath -replace '[/.]', '_') + '.test.exe'
|
||||
go test -race -c -o (Join-Path $OutputDirectory $package.Binary) $package.ImportPath
|
||||
if ($LASTEXITCODE -ne 0) { throw "go test -c $($package.ImportPath) failed with exit code $LASTEXITCODE" }
|
||||
}
|
||||
|
||||
$packages | Select-Object ImportPath, Dir, Binary |
|
||||
ConvertTo-Json -AsArray |
|
||||
Set-Content -Encoding utf8 (Join-Path $OutputDirectory 'manifest.json')
|
||||
|
||||
Write-Host "Built $($packages.Count) test binaries in $OutputDirectory"
|
||||
@@ -0,0 +1,49 @@
|
||||
# Runs the test binaries built by build-go-tests.ps1 like `go test -v ./...`:
|
||||
# each binary runs in its package directory and packages run in parallel.
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string]$BinaryDirectory,
|
||||
[string]$Timeout = '10m',
|
||||
[int]$ThrottleLimit = [Environment]::ProcessorCount
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Set-StrictMode -Version 3
|
||||
|
||||
$BinaryDirectory = (Resolve-Path $BinaryDirectory).Path
|
||||
$manifest = Get-Content (Join-Path $BinaryDirectory 'manifest.json') -Raw | ConvertFrom-Json
|
||||
|
||||
$runs = $manifest | ForEach-Object -ThrottleLimit $ThrottleLimit -Parallel {
|
||||
$package = $_
|
||||
$timeout = $using:Timeout
|
||||
$binary = Join-Path $using:BinaryDirectory $package.Binary
|
||||
|
||||
# Native commands start in the runspace location, so tests see their
|
||||
# package directory as the working directory, as with `go test`.
|
||||
Set-Location $package.Dir
|
||||
$started = Get-Date
|
||||
$output = & $binary '-test.v' "-test.timeout=$timeout" 2>&1 |
|
||||
ForEach-Object { $_.ToString() }
|
||||
$exitCode = $LASTEXITCODE
|
||||
$elapsed = ((Get-Date) - $started).TotalSeconds
|
||||
|
||||
$status = if ($exitCode -eq 0) { 'ok ' } else { 'FAIL' }
|
||||
[pscustomobject]@{
|
||||
ImportPath = $package.ImportPath
|
||||
ExitCode = $exitCode
|
||||
# Print each package as one block so parallel output does not interleave.
|
||||
Text = (@($output) + ("{0}`t{1}`t{2:N3}s" -f $status, $package.ImportPath, $elapsed)) -join "`n"
|
||||
}
|
||||
} | ForEach-Object {
|
||||
Write-Host $_.Text
|
||||
$_
|
||||
}
|
||||
|
||||
$failed = @($runs | Where-Object ExitCode -ne 0)
|
||||
if ($failed.Count -gt 0) {
|
||||
Write-Host "Failed packages:"
|
||||
$failed | ForEach-Object { Write-Host " $($_.ImportPath) (exit code $($_.ExitCode))" }
|
||||
exit 1
|
||||
}
|
||||
|
||||
exit 0
|
||||
@@ -1,64 +0,0 @@
|
||||
param([int]$TestExitCode)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$prefix = "github.com/prometheus-community/windows_exporter/internal/collector/"
|
||||
$required = @($env:WINDOWS_EXPORTER_TEST_COLLECTORS.Split(','))
|
||||
$results = @{}
|
||||
$text = [System.Text.StringBuilder]::new()
|
||||
|
||||
$lines = if (Test-Path test-results.jsonl) { Get-Content test-results.jsonl } else { @() }
|
||||
foreach ($line in $lines) {
|
||||
try {
|
||||
$event = $line | ConvertFrom-Json -ErrorAction Stop
|
||||
} catch {
|
||||
[void]$text.AppendLine($line)
|
||||
continue
|
||||
}
|
||||
if ($event.Output) { [void]$text.Append($event.Output) }
|
||||
if (-not $event.Package -or -not $event.Package.StartsWith($prefix) -or $event.Test -ne "TestCollector") {
|
||||
continue
|
||||
}
|
||||
$name = $event.Package.Substring($prefix.Length)
|
||||
if (-not $results.ContainsKey($name)) {
|
||||
$results[$name] = @{ State = "not completed"; Notes = @() }
|
||||
}
|
||||
if ($event.Action -in @("pass", "skip", "fail")) {
|
||||
$results[$name].State = $event.Action
|
||||
}
|
||||
if ($event.Output -match 'collector \S+ (?:is not supported|has empty optional instance groups): (.+)') {
|
||||
$results[$name].Notes += $Matches[1].Trim()
|
||||
}
|
||||
}
|
||||
$text.ToString() | Set-Content test-output.txt -NoNewline -Encoding utf8
|
||||
|
||||
# Aborted tests must leave required fixtures visible as not run.
|
||||
foreach ($name in $required) {
|
||||
if (-not $results.ContainsKey($name)) {
|
||||
$results[$name] = @{ State = "not run"; Notes = @() }
|
||||
}
|
||||
}
|
||||
$testState = if (-not $lines) { "not run" } elseif ($TestExitCode -eq 0) { "passed" } else { "failed" }
|
||||
$summary = @(
|
||||
"### Windows feature tests: $env:WINDOWS_TEST_OS"
|
||||
""
|
||||
"Go suite: **$testState**. Required fixtures cannot skip. Empty optional instance groups remain listed below."
|
||||
""
|
||||
"| Collector | Required | Live test | Unavailable features / notes |"
|
||||
"| --- | --- | --- | --- |"
|
||||
)
|
||||
foreach ($name in $results.Keys | Sort-Object) {
|
||||
$fixture = if ($name -in $required) { "yes" } else { "no" }
|
||||
$state = switch ($results[$name].State) {
|
||||
"pass" { "passed" }
|
||||
"skip" { "unavailable" }
|
||||
"fail" { "failed" }
|
||||
default { $results[$name].State }
|
||||
}
|
||||
$notes = ($results[$name].Notes | Sort-Object -Unique) -join '; '
|
||||
$notes = $notes.Replace('|', '\|')
|
||||
$summary += "| $name | $fixture | $state | $notes |"
|
||||
}
|
||||
$summary | Set-Content feature-summary.md -Encoding utf8
|
||||
if ($env:GITHUB_STEP_SUMMARY) {
|
||||
$summary | Add-Content $env:GITHUB_STEP_SUMMARY
|
||||
}
|
||||
Reference in New Issue
Block a user