Skip to content

feat: Add support for instrumenting Hangfire background jobs. #7283

feat: Add support for instrumenting Hangfire background jobs.

feat: Add support for instrumenting Hangfire background jobs. #7283

Workflow file for this run

name: All Solutions Build
on:
pull_request:
branches:
- main
- "feature/**"
release:
types: [published]
workflow_dispatch:
inputs:
aggregate_payload_data:
description: 'Collect and aggregate payload data'
required: false
type: boolean
default: false
parallelize_integration_tests:
description: 'Enable parallel integration test execution (requires pre-built test apps)'
required: false
type: boolean
default: true
schedule:
- cron: "0 9 * * 1-5"
# only allow one instance of this workflow to be running per PR or branch, cancels any that are already running
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
env:
scripts_path: ${{ github.workspace }}\build\scripts
tools_path: ${{ github.workspace }}\build\Tools
DOTNET_NOLOGO: true
jobs:
check-modified-files:
name: Check if source files were modified, skip remaining jobs if not
# skip if the PR source branch starts with "release-please"
if: github.event_name != 'pull_request' || !startsWith(github.head_ref, 'release-please')
uses: ./.github/workflows/check_modified_files.yml
secrets: inherit
permissions:
contents: read
pull-requests: read
shellcheck:
name: Validate shell scripts
needs: check-modified-files
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0
with:
disable-sudo: true
egress-policy: audit
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Run Shellcheck
run: |
find ${{ github.workspace }} -name "*.sh" -exec shellcheck --severity=error {} +
# This builds both FullAgent and MSIInstaller since MSIInstaller requires FullAgent artifacts.
build-fullagent-msi:
name: Build FullAgent and MSIInstaller
runs-on: windows-2022
needs:
- check-modified-files
- shellcheck
# don't run this job if triggered by Dependabot, will cause all other jobs to be skipped as well
# run this job if source files were modified, or if triggered by a release, a manual execution or schedule
if: github.actor != 'dependabot[bot]' && (needs.check-modified-files.outputs.source-files-changed == 'true' || github.event.release || github.event_name == 'workflow_dispatch' || github.event_name == 'schedule')
env:
fullagent_solution_path: ${{ github.workspace }}\FullAgent.sln
msi_solution_path: ${{ github.workspace }}\src\Agent\MsiInstaller\MsiInstaller.sln
outputs:
agentVersion: ${{ steps.agentVersion.outputs.version }}
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
filter: blob:none
- name: Cache NuGet packages
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/Directory.Packages.props') }}
restore-keys: ${{ runner.os }}-nuget-
- name: Install latest .NET 10 SDK
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
with:
dotnet-version: '10.0.x'
dotnet-quality: 'ga'
- name: Build FullAgent.sln
run: |
Write-Host "dotnet build --force --configuration Release -p:AllowUnsafeBlocks=true ${{ env.fullagent_solution_path }}"
dotnet build --force --configuration Release -p:AllowUnsafeBlocks=true ${{ env.fullagent_solution_path }}
shell: powershell
- name: Create agentVersion
id: agentVersion
run: |
$agentVersion = (Get-Item "${{ github.workspace }}\src\_build\AnyCPU-Release\NewRelic.Agent.Core\net462\NewRelic.Agent.Core.dll").VersionInfo.FileVersion
echo "version=$agentVersion" >> $env:GITHUB_OUTPUT
shell: powershell
- name: Archive FullAgent Home folders
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: homefolders
path: |
${{ github.workspace }}\src\Agent\newrelichome_x64
${{ github.workspace }}\src\Agent\newrelichome_x64_coreclr
${{ github.workspace }}\src\Agent\newrelichome_x64_coreclr_linux
${{ github.workspace }}\src\Agent\newrelichome_arm64_coreclr_linux
${{ github.workspace }}\src\Agent\newrelichome_x86
${{ github.workspace }}\src\Agent\newrelichome_x86_coreclr
if-no-files-found: error
- name: Convert Code Signing Certificate Into File
id: write_cert
run: |
$filePath = '${{ github.workspace }}\newrelic_code_sign_cert.pfx'
$bytes = [Convert]::FromBase64String('${{ secrets.SIGNING_CERT }}')
[IO.File]::WriteAllBytes($filePath, $bytes)
echo "filePath=$filePath" >> $env:GITHUB_OUTPUT
shell: powershell
- name: Install Code Signing Certificate
run: |
Write-Host "certutil.exe -f -user -p <passphrase> -importPFX ${{ steps.write_cert.outputs.filePath }} NoRoot"
certutil.exe -f -user -p ${{ secrets.CERT_PASSPHRASE }} -importPFX ${{ steps.write_cert.outputs.filePath }} NoRoot
shell: powershell
- name: Add msbuild to PATH (required for MsiInstaller build)
uses: microsoft/setup-msbuild@30375c66a4eea26614e0d39710365f22f8b0af57 # v3.0.0
- name: Build MsiInstaller.sln x86
run: |
Write-Host "MSBuild.exe -restore -m -p:Configuration=Release -p:AllowUnsafeBlocks=true -p:Platform=x86 ${{ env.msi_solution_path }}"
MSBuild.exe -restore -m -p:Configuration=Release -p:AllowUnsafeBlocks=true -p:Platform=x86 ${{ env.msi_solution_path }}
shell: powershell
- name: Build MsiInstaller.sln x64
run: |
Write-Host "MSBuild.exe -restore -m -p:Configuration=Release -p:AllowUnsafeBlocks=true -p:Platform=x64 ${{ env.msi_solution_path }}"
MSBuild.exe -restore -m -p:Configuration=Release -p:AllowUnsafeBlocks=true -p:Platform=x64 ${{ env.msi_solution_path }}
shell: powershell
- name: Archive msi _build Artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: msi-build-folder-artifacts
path: ${{ github.workspace }}\src\_build
if-no-files-found: error
run-linux-container-tests:
name: Run Linux Container Tests
needs:
- build-fullagent-msi
uses: ./.github/workflows/linux_container_tests.yml
with:
aggregate_payload_data: ${{ inputs.aggregate_payload_data == true || github.event_name == 'schedule' }}
secrets: inherit
permissions:
actions: write
contents: read
# ---------------------------------------------------------------------------
# Compute the set of test namespaces to run, after applying exclusions from
# GitHub repo variables. To skip a namespace, add it to the JSON-array
# variable in Settings > Secrets and variables > Actions > Variables:
# INTEGRATION_EXCLUDE_NAMESPACES (e.g. ["LLM","OpenTelemetry"])
# UNBOUNDED_EXCLUDE_NAMESPACES (e.g. ["Couchbase"])
# ---------------------------------------------------------------------------
get-test-namespaces:
name: Get Test Namespaces
needs: check-modified-files
if: github.actor != 'dependabot[bot]' && (needs.check-modified-files.outputs.source-files-changed == 'true' || github.event.release || github.event_name == 'workflow_dispatch' || github.event_name == 'schedule')
runs-on: ubuntu-latest
outputs:
integration-namespaces: ${{ steps.compute.outputs.integration }}
unbounded-namespaces: ${{ steps.compute.outputs.unbounded }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0
with:
disable-sudo: true
egress-policy: audit
- name: Compute namespace lists
id: compute
env:
INTEGRATION_EXCLUDE: ${{ vars.INTEGRATION_EXCLUDE_NAMESPACES }}
UNBOUNDED_EXCLUDE: ${{ vars.UNBOUNDED_EXCLUDE_NAMESPACES }}
run: |
# ── Canonical namespace lists ──────────────────────────────────
# maintain alphabetical order, please!
integration_all='[
"AgentFeatures",
"AgentLogs",
"AgentMetrics",
"Api",
"AppDomainCaching",
"AspNetCore",
"AwsLambda.AutoInstrumentation",
"AwsLambda.CloudWatch",
"AwsLambda.Custom",
"AwsLambda.DynamoDb",
"AwsLambda.General",
"AwsLambda.Kinesis",
"AwsLambda.S3",
"AwsLambda.Ses",
"AwsLambda.Sns",
"AwsLambda.Sqs",
"AwsLambda.WebRequest",
"AwsSdk",
"AzureFunction",
"BasicInstrumentation",
"Blazor",
"CatInbound",
"CatOutbound",
"CodeLevelMetrics",
"Configuration",
"CSP",
"CustomAttributes",
"CustomInstrumentation",
"DataTransmission",
"DistributedTracing",
"Errors",
"Grpc",
"Hangfire",
"HttpClientInstrumentation",
"HybridHttpContextStorage",
"InfiniteTracing",
"LLM",
"Logging.AuditLog",
"Logging.ContextData",
"Logging.HsmAndCsp",
"Logging.Labels",
"Logging.LocalDecoration",
"Logging.LogLevelDetection",
"Logging.MaxSamplesStored",
"Logging.MetricsAndForwarding",
"Logging.StructuredLogArgContextData",
"Logging.ZeroMaxSamplesStored",
"MassTransit",
"OpenTelemetry",
"Owin",
"ReJit.NetCore",
"ReJit.NetFramework",
"RequestHandling",
"RequestHeadersCapture.AspNet",
"RequestHeadersCapture.AspNetCore",
"RequestHeadersCapture.EnvironmentVariables",
"RequestHeadersCapture.Owin",
"RequestHeadersCapture.WCF",
"RestSharp",
"WCF.Client.IIS.ASPDisabled",
"WCF.Client.IIS.ASPEnabled",
"WCF.Client.Self",
"WCF.Service.IIS.ASPDisabled",
"WCF.Service.IIS.ASPEnabled",
"WCF.Service.Self"
]'
unbounded_all='[
"AzureServiceBus",
"CosmosDB",
"Couchbase",
"Elasticsearch",
"MongoDB",
"Msmq",
"MsSql",
"MySql",
"NServiceBus",
"NServiceBus5",
"OpenSearch",
"Oracle",
"Postgres",
"RabbitMq",
"Redis"
]'
# ── Read exclusion variables (default to empty array) ─────────
integration_exclude="${INTEGRATION_EXCLUDE:-[]}"
unbounded_exclude="${UNBOUNDED_EXCLUDE:-[]}"
# ── Apply exclusions ──────────────────────────────────────────
integration=$(jq -c '. - $ex' --argjson ex "$integration_exclude" <<< "$integration_all")
unbounded=$(jq -c '. - $ex' --argjson ex "$unbounded_exclude" <<< "$unbounded_all")
# ── Log what was excluded ─────────────────────────────────────
int_removed=$(jq -c '. as $all | $all - ($all - $ex)' --argjson ex "$integration_exclude" <<< "$integration_all")
unb_removed=$(jq -c '. as $all | $all - ($all - $ex)' --argjson ex "$unbounded_exclude" <<< "$unbounded_all")
echo "::group::Integration test namespaces"
echo "Excluded: $int_removed"
echo "Running: $integration"
echo "::endgroup::"
echo "::group::Unbounded test namespaces"
echo "Excluded: $unb_removed"
echo "Running: $unbounded"
echo "::endgroup::"
echo "integration=$integration" >> "$GITHUB_OUTPUT"
echo "unbounded=$unbounded" >> "$GITHUB_OUTPUT"
shell: bash
build-integration-tests:
needs: build-fullagent-msi
name: Build IntegrationTests
runs-on: windows-2025-vs2026
env:
integration_solution_path: ${{ github.workspace }}\tests\Agent\IntegrationTests\IntegrationTests.sln
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
filter: blob:none
- name: Cache NuGet packages
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/Directory.Packages.props') }}
restore-keys: ${{ runner.os }}-nuget-
- name: Install latest .NET 10 SDK
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
with:
dotnet-version: '10.0.x'
dotnet-quality: 'ga'
- name: Add msbuild to PATH
uses: microsoft/setup-msbuild@30375c66a4eea26614e0d39710365f22f8b0af57 # v3.0.0
# if needed for future .NET preview builds:
#with:
# vs-prerelease: true
- name: List SDKS
run: dotnet --list-sdks
shell: powershell
- name: Build IntegrationTests.sln
run: |
Write-Host "List NuGet Sources"
dotnet nuget list source # For unknown reasons, this step is necessary to avoid subsequent problems with NuGet package restore
Write-Host "MSBuild.exe -restore -m -p:Configuration=Release -p:DeployOnBuild=true -p:PublishProfile=LocalDeploy ${{ env.integration_solution_path }}"
MSBuild.exe -restore -m -p:Configuration=Release -p:DeployOnBuild=true -p:PublishProfile=LocalDeploy ${{ env.integration_solution_path }}
shell: powershell
- name: Pre-publish .NET Core test apps
run: |
# Pre-publish .NET Core test apps so test fixtures can copy pre-built output
# instead of invoking dotnet publish at runtime. This eliminates file-lock
# collisions on shared dependency obj/ directories during parallel test execution.
# We skip class libraries and Azure Function apps (func.exe requires the original
# build output structure, not RID-specific publish output).
$runtime = "win-x64"
$searchPaths = @("Applications", "SharedApplications")
$basePath = "${{ github.workspace }}\tests\Agent\IntegrationTests"
# Azure Function apps are incompatible with pre-publish: func.exe start --no-build
# expects framework-dependent build output, not RID-specific publish output
$skipProjects = @("AzureFunctionApplication", "AzureFunctionInProcApplication")
$published = 0; $skipped = 0; $failed = 0
foreach ($searchPath in $searchPaths) {
$fullPath = Join-Path $basePath $searchPath
if (-not (Test-Path $fullPath)) { continue }
$projects = Get-ChildItem -Path $fullPath -Filter "*.csproj" -Recurse
foreach ($project in $projects) {
$content = Get-Content $project.FullName -Raw
# Skip explicit class libraries and excluded projects
if ($content -match '<OutputType>\s*Library\s*</OutputType>') { $skipped++; continue }
if ($skipProjects -contains $project.BaseName) { $skipped++; continue }
if ($content -match '<TargetFrameworks?>(.*?)</TargetFrameworks?>') {
$tfms = $Matches[1] -split ';'
foreach ($tfm in $tfms) {
if ($tfm -match '^net\d+\.\d+$') {
Write-Host "Pre-publishing $($project.Name) for $tfm/$runtime"
$output = dotnet publish --configuration Release --runtime $runtime --framework $tfm $project.FullName 2>&1
if ($LASTEXITCODE -ne 0) {
Write-Warning "Failed to pre-publish $($project.Name) for $tfm/$runtime"
$output | Where-Object { $_ -match "error" } | ForEach-Object { Write-Host " $_" }
$failed++
} else {
$published++
}
}
}
}
}
}
Write-Host "Pre-publish complete: $published succeeded, $skipped skipped (libraries), $failed failed"
# Clean up intermediate RID-specific build artifacts to reduce artifact size.
# dotnet publish --runtime creates large intermediate files alongside the publish/
# subdirectory (including self-contained runtime copies). Only publish/ is needed.
$cleaned = 0
foreach ($searchPath in $searchPaths) {
$fullPath = Join-Path $basePath $searchPath
if (-not (Test-Path $fullPath)) { continue }
Get-ChildItem -Path $fullPath -Directory -Recurse -Filter $runtime | ForEach-Object {
Get-ChildItem -Path $_.FullName -Exclude "publish" | Remove-Item -Recurse -Force
$cleaned++
}
}
Write-Host "Cleaned intermediate build artifacts from $cleaned RID directories"
shell: powershell
- name: Archive Artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: integrationtests
path: |
${{ github.workspace }}\test.runsettings # Force the artifacts to use repo root as root of package.
${{ github.workspace }}\tests\Agent\IntegrationTests\**\bin\**\*
${{ github.workspace }}\tests\Agent\IntegrationTests\**\Deploy\**\*
!${{ github.workspace }}\tests\Agent\IntegrationTests\**\obj\**\*
if-no-files-found: error
build-unbounded-tests:
needs: build-fullagent-msi
name: Build UnboundedIntegrationTests
runs-on: windows-2025-vs2026
env:
unbounded_solution_path: ${{ github.workspace }}\tests\Agent\IntegrationTests\UnboundedIntegrationTests.sln
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
filter: blob:none
- name: Cache NuGet packages
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/Directory.Packages.props') }}
restore-keys: ${{ runner.os }}-nuget-
- name: Install latest .NET 10 SDK
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
with:
dotnet-version: '10.0.x'
dotnet-quality: 'ga'
- name: Add msbuild to PATH
uses: microsoft/setup-msbuild@30375c66a4eea26614e0d39710365f22f8b0af57 # v3.0.0
# if needed for future .NET preview builds:
#with:
# vs-prerelease: true
- name: Build UnboundedIntegrationTests.sln
run: |
Write-Host "List NuGet Sources"
dotnet nuget list source # For unknown reasons, this step is necessary to avoid subsequent problems with NuGet package restore
Write-Host "MSBuild.exe -restore -m -p:Configuration=Release -p:DeployOnBuild=true -p:PublishProfile=LocalDeploy ${{ env.unbounded_solution_path }}"
MSBuild.exe -restore -m -p:Configuration=Release -p:DeployOnBuild=true -p:PublishProfile=LocalDeploy ${{ env.unbounded_solution_path }}
shell: powershell
- name: Pre-publish .NET Core test apps
run: |
# Pre-publish .NET Core test apps so test fixtures can copy pre-built output
# instead of invoking dotnet publish at runtime. This eliminates file-lock
# collisions on shared dependency obj/ directories during parallel test execution.
# We skip class libraries (OutputType=Library or netstandard-only targets).
$runtime = "win-x64"
$searchPaths = @("UnboundedApplications", "SharedApplications")
$basePath = "${{ github.workspace }}\tests\Agent\IntegrationTests"
$published = 0; $skipped = 0; $failed = 0
foreach ($searchPath in $searchPaths) {
$fullPath = Join-Path $basePath $searchPath
if (-not (Test-Path $fullPath)) { continue }
$projects = Get-ChildItem -Path $fullPath -Filter "*.csproj" -Recurse
foreach ($project in $projects) {
$content = Get-Content $project.FullName -Raw
# Skip explicit class libraries
if ($content -match '<OutputType>\s*Library\s*</OutputType>') { $skipped++; continue }
if ($content -match '<TargetFrameworks?>(.*?)</TargetFrameworks?>') {
$tfms = $Matches[1] -split ';'
foreach ($tfm in $tfms) {
if ($tfm -match '^net\d+\.\d+$') {
Write-Host "Pre-publishing $($project.Name) for $tfm/$runtime"
$output = dotnet publish --configuration Release --runtime $runtime --framework $tfm $project.FullName 2>&1
if ($LASTEXITCODE -ne 0) {
Write-Warning "Failed to pre-publish $($project.Name) for $tfm/$runtime"
$output | Where-Object { $_ -match "error" } | ForEach-Object { Write-Host " $_" }
$failed++
} else {
$published++
}
}
}
}
}
}
Write-Host "Pre-publish complete: $published succeeded, $skipped skipped (libraries), $failed failed"
# Clean up intermediate RID-specific build artifacts to reduce artifact size.
$cleaned = 0
foreach ($searchPath in $searchPaths) {
$fullPath = Join-Path $basePath $searchPath
if (-not (Test-Path $fullPath)) { continue }
Get-ChildItem -Path $fullPath -Directory -Recurse -Filter $runtime | ForEach-Object {
Get-ChildItem -Path $_.FullName -Exclude "publish" | Remove-Item -Recurse -Force
$cleaned++
}
}
Write-Host "Cleaned intermediate build artifacts from $cleaned RID directories"
shell: powershell
- name: Archive Artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: unboundedintegrationtests
path: |
${{ github.workspace }}\test.runsettings # Force the artifacts to use repo root as root of package.
${{ github.workspace }}\tests\Agent\IntegrationTests\**\bin\**\*
${{ github.workspace }}\tests\Agent\IntegrationTests\**\Deploy\**\*
!${{ github.workspace }}\tests\Agent\IntegrationTests\**\obj\**\*
if-no-files-found: error
run-integration-tests:
needs: [build-integration-tests, get-test-namespaces]
name: Run IntegrationTests
runs-on: windows-2025-vs2026
strategy:
matrix:
namespace: ${{ fromJson(needs.get-test-namespaces.outputs.integration-namespaces) }}
fail-fast: false # we don't want one test failure in one namespace to kill the other runs
env:
integration_tests_shared_project: ${{ github.workspace }}/tests/Agent/IntegrationTests/Shared
integration_tests_path: ${{ github.workspace }}/tests/Agent/IntegrationTests/IntegrationTests/bin/Release/net10.0
# Make this variable true to enable extra data-gathering and logging to help troubleshoot test failures, at the cost of additional time and resources
enhanced_logging: false
NR_DOTNET_TEST_SAVE_WORKING_DIRECTORY: 1
NR_DOTNET_TEST_PREBUILT_APPS: 1 # use pre-published test app output instead of dotnet publish at runtime
azure_func_exe_path: C:\ProgramData\chocolatey\lib\azure-functions-core-tools\tools\func.exe
NEW_RELIC_AZURE_FUNCTION_LOG_LEVEL_OVERRIDE: 1 # enables profiler debug logs when testing an azure function
# Set an environment variable that the tests will use to set the application name.
CI_NEW_RELIC_APP_NAME: ${{ github.event_name == 'schedule' && 'DotNetIngestTracking' || '' }}
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
filter: blob:none
- name: Disable TLS 1.3
run: |
$registryPath = "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.3\Client"
if(!(Test-Path $registryPath)) {
New-Item -Path $registryPath -Force
}
New-ItemProperty -Path $registryPath -Name "DisabledByDefault" -Value "1" -PropertyType DWORD -Force
New-ItemProperty -Path $registryPath -Name "Enabled" -Value "0" -PropertyType DWORD -Force
shell: powershell
- name: Create and trust .NET development SSL certificate
run: |
dotnet dev-certs https --clean
dotnet dev-certs https --export-path ./devcert.pfx --password "password1"
$pwd = ConvertTo-SecureString -String "password1" -Force -AsPlainText
Import-PfxCertificate -FilePath ./devcert.pfx -CertStoreLocation Cert:\LocalMachine\Root -Password $pwd
dotnet dev-certs https --check --trust
shell: powershell
- name: Set up secrets
env:
INTEGRATION_TEST_SECRETS: ${{ secrets.TEST_SECRETS }}
run: |
"$Env:INTEGRATION_TEST_SECRETS" | dotnet user-secrets set --project ${{ env.integration_tests_shared_project }}
shell: pwsh #this doesn't work with normal powershell due to UTF-8 BOM handling
- name: Download Agent Home Folders
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: homefolders
path: src/Agent
- name: Download Integration Test Artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: integrationtests
# Should not need a path because the integration test artifacts are archived with the full directory structure
- name: Install HostableWebCore Feature
if: | # only install for the required namespaces
matrix.namespace == 'AgentFeatures' || matrix.namespace == 'AgentLogs' || matrix.namespace == 'AgentMetrics' || matrix.namespace == 'BasicInstrumentation' ||
matrix.namespace == 'CatInbound' || matrix.namespace == 'CatOutbound' || matrix.namespace == 'CodeLevelMetrics' || matrix.namespace == 'CSP' ||
matrix.namespace == 'CustomAttributes' || matrix.namespace == 'CustomInstrumentation' || matrix.namespace == 'DataTransmission' ||
matrix.namespace == 'DistributedTracing' || matrix.namespace == 'Errors' || matrix.namespace == 'HttpClientInstrumentation' || matrix.namespace == 'HybridHttpContextStorage' ||
matrix.namespace == 'Rejit.NetFramework' || matrix.namespace == 'RequestHandling' || matrix.namespace == 'RequestHeadersCapture.AspNet' ||
matrix.namespace == 'RequestHeadersCapture.AspNetCore' || matrix.namespace == 'RequestHeadersCapture.EnvironmentVariables' ||
matrix.namespace == 'RequestHeadersCapture.WCF' || matrix.namespace == 'WCF.Client.IIS.ASPDisabled' ||
matrix.namespace == 'WCF.Client.IIS.ASPEnabled' || matrix.namespace == 'WCF.Service.IIS.ASPDisabled' ||
matrix.namespace == 'WCF.Service.IIS.ASPEnabled'
run: |
Enable-WindowsOptionalFeature -Online -FeatureName IIS-HostableWebCore
shell: powershell
- name: Install aiohttp
if: matrix.namespace == 'DistributedTracing'
run: |
pip install aiohttp
shell: powershell
- name: Install Azure Functions Core Tools
if: matrix.namespace == 'AzureFunction'
run: |
choco install azure-functions-core-tools -y --params "'/x64'"
shell: powershell
- name: Install latest .NET 10 SDK
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
with:
dotnet-version: '10.0.x'
dotnet-quality: 'ga'
- name: Run Integration Tests
run: |
if ($Env:enhanced_logging -eq $True) {
Write-Host "List ports in use"
netstat -no
}
Write-Host "Run tests"
# Most integration tests can run in parallel with pre-built test apps.
# Some namespaces must remain serial because they use shared external tools
# that don't support concurrent instances (e.g., Azure Functions Core Tools
# func.exe crashes when multiple instances launch simultaneously due to shared
# state in storage emulator/lock files). To parallelize these, the shared tool
# contention would need to be resolved.
$noParallelNamespaces = @("AzureFunction")
$parallelize = "${{ inputs.parallelize_integration_tests }}"
if ($parallelize -eq "" -or $parallelize -eq "true") { $parallelize = $true } else { $parallelize = $false }
if ($noParallelNamespaces -contains "${{ matrix.namespace }}") { $parallelize = $false }
$json = Get-Content "${{ env.integration_tests_path }}/xunit.runner.json" | ConvertFrom-Json
$json | Add-Member -Name "parallelizeAssembly" -Value $parallelize -MemberType NoteProperty
$json | Add-Member -Name "parallelizeTestCollections" -Value $parallelize -MemberType NoteProperty
$json | ConvertTo-Json | Out-File "${{ env.integration_tests_path }}/xunit.runner.json"
${{ env.integration_tests_path }}/NewRelic.Agent.IntegrationTests.exe -namespace NewRelic.Agent.IntegrationTests.${{ matrix.namespace }} -trx "C:\IntegrationTestWorkingDirectory\TestResults\${{ matrix.namespace }}_testResults.trx"
if ($Env:enhanced_logging -eq $True) {
Write-Host "Get HostableWebCore errors (if any)"
Get-EventLog -LogName Application -Source HostableWebCore -ErrorAction:Ignore
Write-Host "Get .NET Runtime errors (if any)"
Get-EventLog -LogName Application -Source ".NET Runtime" -EntryType "Error","Warning" -ErrorAction:Ignore
}
shell: powershell
- name: Extract payload bytes log from TRX
if: inputs.aggregate_payload_data == true || github.event_name == 'schedule'
run: |
$trxFile = "C:\IntegrationTestWorkingDirectory\TestResults\${{ matrix.namespace }}_testResults.trx"
$payloadLog = "C:\IntegrationTestWorkingDirectory\TestResults\${{ matrix.namespace }}_payload_bytes.json"
Write-Host "Checking for TRX file at: $trxFile"
if (Test-Path $trxFile) {
$fileSize = (Get-Item $trxFile).Length
Write-Host "TRX file found. Size: $fileSize bytes"
# Read TRX file and extract lines containing payload information from Message elements
[xml]$trxContent = Get-Content $trxFile
$messageElements = $trxContent.SelectNodes("//*[local-name()='Output']/*[local-name()='TextMessages']/*[local-name()='Message']")
$payloadData = @{}
foreach ($message in $messageElements) {
$text = $message.InnerText
if ($text -match "^(.+?):\s*Total payload bytes sent:\s*(\d+)") {
$className = $Matches[1].Trim()
$byteCount = [int]$Matches[2]
$payloadData[$className] = $byteCount
Write-Host "Found: $className = $byteCount bytes"
}
}
Write-Host "Found $($payloadData.Count) payload log entries"
if ($payloadData.Count -gt 0) {
$payloadData | ConvertTo-Json | Out-File -FilePath $payloadLog -Encoding UTF8
Write-Host "Extracted payload logs to $payloadLog"
Write-Host "Content:"
Get-Content $payloadLog | ForEach-Object { Write-Host $_ }
} else {
Write-Host "No payload byte logs found in TRX file"
@{} | ConvertTo-Json | Out-File -FilePath $payloadLog -Encoding UTF8
}
} else {
Write-Host "TRX file not found"
@{} | ConvertTo-Json | Out-File -FilePath $payloadLog -Encoding UTF8
}
shell: powershell
- name: Upload payload bytes log
if: inputs.aggregate_payload_data == true || github.event_name == 'schedule'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: integration-payload-bytes-${{ matrix.namespace }}
path: C:\IntegrationTestWorkingDirectory\TestResults\*_payload_bytes.json
if-no-files-found: warn
- name: Archive integration test results on failure
if: ${{ failure() }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: integration-test-results-${{ matrix.namespace }}
path: |
C:\IntegrationTestWorkingDirectory\**\*.log
C:\IntegrationTestWorkingDirectory\**\*.config
C:\IntegrationTestWorkingDirectory\TestResults\**\*TestResults.trx
if-no-files-found: error
run-unbounded-tests:
needs: [build-unbounded-tests, get-test-namespaces]
name: Run Unbounded Tests
runs-on: windows-2025-vs2026
strategy:
matrix:
namespace: ${{ fromJson(needs.get-test-namespaces.outputs.unbounded-namespaces) }}
fail-fast: false # we don't want one test failure in one namespace to kill the other runs
env:
integration_tests_shared_project: ${{ github.workspace }}/tests/Agent/IntegrationTests/Shared
unbounded_tests_path: ${{ github.workspace }}/tests/Agent/IntegrationTests/UnboundedIntegrationTests/bin/Release/net10.0
NR_DOTNET_TEST_SAVE_WORKING_DIRECTORY: 1
NR_DOTNET_TEST_PREBUILT_APPS: 1 # use pre-published test app output instead of dotnet publish at runtime
# Make this variable true to enable extra data-gathering and logging to help troubleshoot test failures, at the cost of additional time and resources
enhanced_logging: false
# Set an environment variable that the tests will use to set the application name.
CI_NEW_RELIC_APP_NAME: ${{ github.event_name == 'schedule' && 'DotNetIngestTracking' || '' }}
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
filter: blob:none
- name: Download Agent Home Folders
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: homefolders
path: src/Agent
- name: Download Unbounded Integration Test Artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: unboundedintegrationtests
# Should not need a path because the integration test artifacts are archived with the full directory structure
- name: Disable TLS 1.3
run: |
$registryPath = "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.3\Client"
if(!(Test-Path $registryPath)) {
New-Item -Path $registryPath -Force
}
New-ItemProperty -Path $registryPath -Name "DisabledByDefault" -Value "1" -PropertyType DWORD -Force
New-ItemProperty -Path $registryPath -Name "Enabled" -Value "0" -PropertyType DWORD -Force
shell: powershell
- name: Install HostableWebCore Feature
if: | # only install for the required namespaces
matrix.namespace == 'MongoDB' || matrix.namespace == 'MsSql' || matrix.namespace == 'Oracle'
run: |
Enable-WindowsOptionalFeature -Online -FeatureName IIS-HostableWebCore
shell: powershell
- name: Install MSMQ dependencies
if: matrix.namespace == 'Msmq'
run: |
Write-Host "Installing Msmq Features"
Enable-WindowsOptionalFeature -Online -FeatureName MSMQ-Server -All
Enable-WindowsOptionalFeature -Online -FeatureName MSMQ-HTTP -All
Enable-WindowsOptionalFeature -Online -FeatureName MSMQ-Triggers -All
shell: powershell
- name: Install MsSql dependencies
if: matrix.namespace == 'MsSql'
run: |
Write-Host "Installing MSSQL CLI"
msiexec /i "${{ github.workspace }}\build\Tools\sqlncli.msi" IACCEPTSQLNCLILICENSETERMS=YES /quiet /qn /norestart
Start-Sleep 20 # Need to wait for install to finish -- takes only a few seconds, but we need to be sure.
shell: powershell
- name: Set up secrets
env:
INTEGRATION_TEST_SECRETS: ${{ secrets.TEST_SECRETS }}
run: |
"$Env:INTEGRATION_TEST_SECRETS" | dotnet user-secrets set --project ${{ env.integration_tests_shared_project }}
shell: pwsh #this doesn't work with normal powershell due to UTF-8 BOM handling
# save in case we move back to using the emulator
# - name: Start Local CosmosDB Emulator for CosmosDB Tests
# if: matrix.namespace == 'CosmosDB'
# run: |
# Write-Host "Launching Cosmos DB Emulator"
# Import-Module "$env:ProgramFiles\Azure Cosmos DB Emulator\PSModules\Microsoft.Azure.CosmosDB.Emulator"
# Start-CosmosDbEmulator
# shell: pwsh
- name: Install latest .NET 10 SDK
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
with:
dotnet-version: '10.0.x'
dotnet-quality: 'ga'
- name: Run Unbounded Integration Tests
run: |
if ($Env:enhanced_logging -eq $True) {
Write-Host "List ports in use"
netstat -no
}
# Most unbounded tests can run in parallel with pre-built test apps.
# Some namespaces must remain serial because they use shared external infrastructure
# with hard-coded object names (e.g., MsSql tests all query NewRelic.dbo.TeamMembers
# with constant values, NServiceBus5 tests share MSMQ queues). To parallelize these,
# each test fixture would need isolated resources (per-test databases/tables/queues).
$noParallelNamespaces = @("MsSql", "NServiceBus", "NServiceBus5")
$parallelize = $noParallelNamespaces -notcontains "${{ matrix.namespace }}"
$json = Get-Content "${{ env.unbounded_tests_path }}/xunit.runner.json" | ConvertFrom-Json
$json | Add-Member -Name "parallelizeAssembly" -Value $parallelize -MemberType NoteProperty
$json | Add-Member -Name "parallelizeTestCollections" -Value $parallelize -MemberType NoteProperty
$json | ConvertTo-Json | Out-File "${{ env.unbounded_tests_path }}/xunit.runner.json"
${{ env.unbounded_tests_path }}/NewRelic.Agent.UnboundedIntegrationTests.exe -namespace NewRelic.Agent.UnboundedIntegrationTests.${{ matrix.namespace }} -trx "C:\IntegrationTestWorkingDirectory\TestResults\${{ matrix.namespace }}_testResults.trx"
if ($Env:enhanced_logging -eq $True) {
Write-Host "Get HostableWebCore errors (if any)"
Get-EventLog -LogName Application -Source HostableWebCore -ErrorAction:Ignore
Write-Host "Get .NET Runtime errors (if any)"
Get-EventLog -LogName Application -Source ".NET Runtime" -EntryType "Error","Warning" -ErrorAction:Ignore
}
shell: powershell
- name: Extract payload bytes log from TRX
if: inputs.aggregate_payload_data == true || github.event_name == 'schedule'
run: |
$trxFile = "C:\IntegrationTestWorkingDirectory\TestResults\${{ matrix.namespace }}_testResults.trx"
$payloadLog = "C:\IntegrationTestWorkingDirectory\TestResults\${{ matrix.namespace }}_payload_bytes.json"
Write-Host "Checking for TRX file at: $trxFile"
if (Test-Path $trxFile) {
$fileSize = (Get-Item $trxFile).Length
Write-Host "TRX file found. Size: $fileSize bytes"
# Read TRX file and extract lines containing payload information from Message elements
[xml]$trxContent = Get-Content $trxFile
$messageElements = $trxContent.SelectNodes("//*[local-name()='Output']/*[local-name()='TextMessages']/*[local-name()='Message']")
$payloadData = @{}
foreach ($message in $messageElements) {
$text = $message.InnerText
if ($text -match "^(.+?):\s*Total payload bytes sent:\s*(\d+)") {
$className = $Matches[1].Trim()
$byteCount = [int]$Matches[2]
$payloadData[$className] = $byteCount
Write-Host "Found: $className = $byteCount bytes"
}
}
Write-Host "Found $($payloadData.Count) payload log entries"
if ($payloadData.Count -gt 0) {
$payloadData | ConvertTo-Json | Out-File -FilePath $payloadLog -Encoding UTF8
Write-Host "Extracted payload logs to $payloadLog"
Write-Host "Content:"
Get-Content $payloadLog | ForEach-Object { Write-Host $_ }
} else {
Write-Host "No payload byte logs found in TRX file"
@{} | ConvertTo-Json | Out-File -FilePath $payloadLog -Encoding UTF8
}
} else {
Write-Host "TRX file not found"
@{} | ConvertTo-Json | Out-File -FilePath $payloadLog -Encoding UTF8
}
shell: powershell
- name: Upload payload bytes log
if: inputs.aggregate_payload_data == true || github.event_name == 'schedule'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: unbounded-payload-bytes-${{ matrix.namespace }}
path: C:\IntegrationTestWorkingDirectory\TestResults\*_payload_bytes.json
if-no-files-found: warn
- name: Archive unbounded test results on failure
if: ${{ failure() }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: unbounded-test-working-directory-${{ matrix.namespace }}
path: |
C:\IntegrationTestWorkingDirectory\**\*.log
C:\IntegrationTestWorkingDirectory\**\*.config
C:\IntegrationTestWorkingDirectory\TestResults\**\*TestResults.trx
if-no-files-found: error
create-package-rpm:
needs: build-fullagent-msi
name: Create RPM Package
runs-on: ubuntu-22.04
steps:
- name: Harden Runner
uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0
with:
disable-sudo: true
egress-policy: audit
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Download msi _build Artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: msi-build-folder-artifacts
path: src/_build
- name: Download Agent Home Folders
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: homefolders
path: src/Agent
- name: Convert GPG Private Key Into File
id: write_gpgkey
run: |
filePath="/tmp/private_gpg_key.gpg"
echo "${{ secrets.NEW_GPG_KEY }}" | base64 -d > $filePath
echo "filePath=$filePath" >> $GITHUB_OUTPUT
shell: bash
- name: Copy GPG Key to keys
run: |
mkdir ${{ github.workspace }}/build/Linux/keys
cp -f ${{ steps.write_gpgkey.outputs.filePath }} ${{ github.workspace }}/build/Linux/keys/private_gpg_key.gpg
shell: bash
- name: Build RPM
run: |
agentVersion=${{ needs.build-fullagent-msi.outputs.agentVersion }}
if [[ "$agentVersion" =~ [0-9]+\.[0-9]+\.[0-9]+\.[0-9]+ ]]; then
major=$(echo $agentVersion | cut -d'.' -f1)
minor=$(echo $agentVersion | cut -d'.' -f2)
patch=$(echo $agentVersion | cut -d'.' -f3)
agentVersion="${major}.${minor}.${patch}"
echo "agentVersion is simplified to $agentVersion"
fi
cd ${{ github.workspace }}/build/Linux
docker compose build build_rpm
docker compose run -e AGENT_VERSION=$agentVersion -e GPG_KEY=/keys/private_gpg_key.gpg -e GPG_KEY_PASSPHRASE=${{ secrets.NEW_GPG_KEY_PASSPHRASE }} build_rpm
shell: bash
- name: Archive RPM Package Artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: rpm-build-artifacts
path: ${{ github.workspace }}/src/_build/CoreArtifacts
if-no-files-found: error
create-package-deb:
needs: build-fullagent-msi
name: Create Debian package
runs-on: ubuntu-22.04
steps:
- name: Harden Runner
uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0
with:
disable-sudo: true
egress-policy: audit
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Download Agent Home Folders
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: homefolders
path: src/Agent
- name: Download msi _build Artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: msi-build-folder-artifacts
path: src/_build
- name: Build Debian Package
run: |
agentVersion=${{ needs.build-fullagent-msi.outputs.agentVersion }}
if [[ "$agentVersion" =~ [0-9]+\.[0-9]+\.[0-9]+\.[0-9]+ ]]; then
major=$(echo $agentVersion | cut -d'.' -f1)
minor=$(echo $agentVersion | cut -d'.' -f2)
patch=$(echo $agentVersion | cut -d'.' -f3)
agentVersion="${major}.${minor}.${patch}"
echo "agentVersion is simplified to $agentVersion"
fi
cd ${{ github.workspace }}/build/Linux
docker compose build build_deb
docker compose run -e AGENT_VERSION=$agentVersion build_deb
shell: bash
- name: Archive Debian Package Artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: debian-build-artifacts
path: ${{ github.workspace }}/src/_build/CoreArtifacts
if-no-files-found: error
run-artifactbuilder:
needs: [create-package-rpm, create-package-deb]
name: Run ArtifactBuilder
runs-on: windows-2022
steps:
- name: Install latest .NET 10 SDK
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
with:
dotnet-version: '10.0.x'
dotnet-quality: 'ga'
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
filter: blob:none
- name: Download Agent Home Folders
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: homefolders
path: src/Agent
- name: Download msi _build Artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: msi-build-folder-artifacts
path: src/_build
- name: Download Debian _build Artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: debian-build-artifacts
path: src/_build/CoreArtifacts
- name: Download RPM _build Artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: rpm-build-artifacts
path: src/_build/CoreArtifacts
- name: Add msbuild to PATH
uses: microsoft/setup-msbuild@30375c66a4eea26614e0d39710365f22f8b0af57 # v3.0.0
- name: Build NewRelic.NuGetHelper
run: |
MSBuild.exe -restore -m -p:Configuration=Release ${{ github.workspace }}\build\NewRelic.NuGetHelper\NewRelic.NuGetHelper.csproj
shell: powershell
- name: Run ArtifactBuilder
run: |
${{ github.workspace }}\build\package.ps1 -configuration Release -IncludeDownloadSite
shell: powershell
- name: Archive Deploy Artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: deploy-artifacts
path: |
${{ github.workspace }}\build\BuildArtifacts
if-no-files-found: error
# This job is necessary in order for us to have a branch protection rule for tests with a matrix
# if any of the matrix tests fail, this job fails and the branch protection rule keeps the PR from merging
integration-test-status:
name: Check Test Matrix Status
runs-on: ubuntu-latest
needs: [run-linux-container-tests, run-integration-tests, run-unbounded-tests]
if: always()
steps:
- name: Harden Runner
uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0
with:
disable-sudo: true
egress-policy: audit
- name: Successful test run
if: ${{ !(contains(needs.*.result, 'failure')) }}
run: exit 0
- name: Failing test run
if: ${{ contains(needs.*.result, 'failure') }}
run: exit 1
aggregate-integration-payload-logs:
name: Aggregate Integration Test Payload Logs
needs: [integration-test-status]
runs-on: ubuntu-latest
if: inputs.aggregate_payload_data == true || github.event_name == 'schedule'
permissions:
actions: write
contents: read
steps:
- name: Harden Runner
uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0
with:
disable-sudo: true
egress-policy: audit
- name: Download all integration payload artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: integration-payload-bytes-*
path: payload-logs/integration
merge-multiple: true
- name: Combine payload logs
run: |
mkdir -p combined-logs
if [ -d "payload-logs/integration" ]; then
echo "[" > combined-logs/integration_payload_bytes_combined.json
first=true
grand_total=0
# Process JSON files and merge into array
while IFS= read -r file; do
echo "Processing: $(basename "$file")"
# Read JSON and extract key-value pairs
while IFS= read -r line; do
# Skip lines that are just braces or empty
if [[ "$line" =~ ^[[:space:]]*\{[[:space:]]*$ ]] || [[ "$line" =~ ^[[:space:]]*\}[[:space:]]*$ ]] || [[ -z "$line" ]]; then
continue
fi
# Extract class name and byte count from JSON property
if [[ "$line" =~ \"([^\"]+)\":[[:space:]]*([0-9]+) ]]; then
className="${BASH_REMATCH[1]}"
byteCount="${BASH_REMATCH[2]}"
grand_total=$((grand_total + byteCount))
if [ "$first" = true ]; then
first=false
else
echo "," >> combined-logs/integration_payload_bytes_combined.json
fi
echo " {\"className\": \"$className\", \"payloadBytes\": $byteCount}" >> combined-logs/integration_payload_bytes_combined.json
fi
done < "$file"
done < <(find payload-logs/integration -name "*_payload_bytes.json" -type f | sort)
echo "" >> combined-logs/integration_payload_bytes_combined.json
echo "]" >> combined-logs/integration_payload_bytes_combined.json
# Create summary metadata file
echo "{" > combined-logs/integration_payload_bytes_summary.json
echo " \"testType\": \"Integration Tests\"," >> combined-logs/integration_payload_bytes_summary.json
echo " \"generatedAt\": \"$(date -u +"%Y-%m-%dT%H:%M:%SZ")\"," >> combined-logs/integration_payload_bytes_summary.json
echo " \"grandTotalBytes\": $grand_total" >> combined-logs/integration_payload_bytes_summary.json
echo "}" >> combined-logs/integration_payload_bytes_summary.json
echo "Combined payload JSON created with grand total: $grand_total bytes"
echo "Payload data:"
cat combined-logs/integration_payload_bytes_combined.json
echo ""
echo "Summary:"
cat combined-logs/integration_payload_bytes_summary.json
else
echo "No integration payload logs found"
echo "[]" > combined-logs/integration_payload_bytes_combined.json
echo "{\"testType\": \"Integration Tests\", \"grandTotalBytes\": 0}" > combined-logs/integration_payload_bytes_summary.json
fi
shell: bash
- name: Upload combined integration payload log
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: combined-integration-payload-bytes
path: combined-logs/integration_payload_bytes_*.json
if-no-files-found: warn
- name: Delete individual integration payload artifacts
run: |
# Get all artifacts from this run matching the pattern and delete them
gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --paginate \
--jq '.artifacts[] | select(.name | startswith("integration-payload-bytes-")) | "\(.id) \(.name)"' \
| while read -r artifact_id artifact_name; do
echo "Deleting artifact: $artifact_name (ID: $artifact_id)"
gh api --method DELETE repos/${{ github.repository }}/actions/artifacts/$artifact_id \
&& echo "Successfully deleted $artifact_name" \
|| echo "Failed to delete $artifact_name"
done
env:
GH_TOKEN: ${{ github.token }}
shell: bash
continue-on-error: true
aggregate-unbounded-payload-logs:
name: Aggregate Unbounded Test Payload Logs
needs: [integration-test-status]
runs-on: ubuntu-latest
if: inputs.aggregate_payload_data == true || github.event_name == 'schedule'
permissions:
actions: write
contents: read
steps:
- name: Harden Runner
uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0
with:
disable-sudo: true
egress-policy: audit
- name: Download all unbounded payload artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: unbounded-payload-bytes-*
path: payload-logs/unbounded
merge-multiple: true
- name: Combine payload logs
run: |
mkdir -p combined-logs
if [ -d "payload-logs/unbounded" ]; then
echo "[" > combined-logs/unbounded_payload_bytes_combined.json
first=true
grand_total=0
# Process JSON files and merge into array
while IFS= read -r file; do
echo "Processing: $(basename "$file")"
# Read JSON and extract key-value pairs
while IFS= read -r line; do
# Skip lines that are just braces or empty
if [[ "$line" =~ ^[[:space:]]*\{[[:space:]]*$ ]] || [[ "$line" =~ ^[[:space:]]*\}[[:space:]]*$ ]] || [[ -z "$line" ]]; then
continue
fi
# Extract class name and byte count from JSON property
if [[ "$line" =~ \"([^\"]+)\":[[:space:]]*([0-9]+) ]]; then
className="${BASH_REMATCH[1]}"
byteCount="${BASH_REMATCH[2]}"
grand_total=$((grand_total + byteCount))
if [ "$first" = true ]; then
first=false
else
echo "," >> combined-logs/unbounded_payload_bytes_combined.json
fi
echo " {\"className\": \"$className\", \"payloadBytes\": $byteCount}" >> combined-logs/unbounded_payload_bytes_combined.json
fi
done < "$file"
done < <(find payload-logs/unbounded -name "*_payload_bytes.json" -type f | sort)
echo "" >> combined-logs/unbounded_payload_bytes_combined.json
echo "]" >> combined-logs/unbounded_payload_bytes_combined.json
# Create summary metadata file
echo "{" > combined-logs/unbounded_payload_bytes_summary.json
echo " \"testType\": \"Unbounded Integration Tests\"," >> combined-logs/unbounded_payload_bytes_summary.json
echo " \"generatedAt\": \"$(date -u +"%Y-%m-%dT%H:%M:%SZ")\"," >> combined-logs/unbounded_payload_bytes_summary.json
echo " \"grandTotalBytes\": $grand_total" >> combined-logs/unbounded_payload_bytes_summary.json
echo "}" >> combined-logs/unbounded_payload_bytes_summary.json
echo "Combined payload JSON created with grand total: $grand_total bytes"
echo "Payload data:"
cat combined-logs/unbounded_payload_bytes_combined.json
echo ""
echo "Summary:"
cat combined-logs/unbounded_payload_bytes_summary.json
else
echo "No unbounded payload logs found"
echo "[]" > combined-logs/unbounded_payload_bytes_combined.json
echo "{\"testType\": \"Unbounded Integration Tests\", \"grandTotalBytes\": 0}" > combined-logs/unbounded_payload_bytes_summary.json
fi
shell: bash
- name: Upload combined unbounded payload log
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: combined-unbounded-payload-bytes
path: combined-logs/unbounded_payload_bytes_*.json
if-no-files-found: warn
- name: Delete individual unbounded payload artifacts
run: |
# Get all artifacts from this run matching the pattern and delete them
gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --paginate \
--jq '.artifacts[] | select(.name | startswith("unbounded-payload-bytes-")) | "\(.id) \(.name)"' \
| while read -r artifact_id artifact_name; do
echo "Deleting artifact: $artifact_name (ID: $artifact_id)"
gh api --method DELETE repos/${{ github.repository }}/actions/artifacts/$artifact_id \
&& echo "Successfully deleted $artifact_name" \
|| echo "Failed to delete $artifact_name"
done
env:
GH_TOKEN: ${{ github.token }}
shell: bash
continue-on-error: true
display-all-payload-summaries:
name: Display All Payload Summaries
needs: [aggregate-integration-payload-logs, aggregate-unbounded-payload-logs, run-linux-container-tests]
runs-on: ubuntu-latest
if: inputs.aggregate_payload_data == true || github.event_name == 'schedule'
permissions:
actions: write
contents: read
steps:
- name: Harden Runner
uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0
with:
disable-sudo: true
egress-policy: audit
- name: Download combined integration payload log
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: combined-integration-payload-bytes
path: payload-summaries
continue-on-error: true
- name: Download combined unbounded payload log
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: combined-unbounded-payload-bytes
path: payload-summaries
continue-on-error: true
- name: Download combined Linux container payload log
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: combined-linux-container-payload-bytes
path: payload-summaries
continue-on-error: true
- name: Create consolidated JSON output
run: |
if [ -d "payload-summaries" ]; then
echo "{" > final_payload_summary.json
first_type=true
overall_total=0
# Process each test type's combined data
for data_file in payload-summaries/*_combined.json; do
if [ -f "$data_file" ]; then
filename=$(basename "$data_file" _payload_bytes_combined.json)
# Determine test type name
case "$filename" in
integration)
test_type="integrationTests"
;;
unbounded)
test_type="unboundedTests"
;;
linux_container)
test_type="containerTests"
;;
*)
test_type="$filename"
;;
esac
# Get corresponding summary file for grand total and timestamp
summary_file="payload-summaries/${filename}_payload_bytes_summary.json"
grand_total=0
generated_at=""
if [ -f "$summary_file" ]; then
if grep -q "grandTotalBytes" "$summary_file"; then
grand_total=$(grep -oP '"grandTotalBytes":\s*\K\d+' "$summary_file")
overall_total=$((overall_total + grand_total))
fi
if grep -q "generatedAt" "$summary_file"; then
generated_at=$(grep -oP '"generatedAt":\s*"\K[^"]+' "$summary_file")
fi
fi
# Add comma separator if not first entry
if [ "$first_type" = false ]; then
echo "," >> final_payload_summary.json
fi
first_type=false
# Add test type section
echo " \"$test_type\": {" >> final_payload_summary.json
echo " \"generatedAt\": \"$generated_at\"," >> final_payload_summary.json
echo " \"bytes\": $grand_total," >> final_payload_summary.json
echo " \"details\": [" >> final_payload_summary.json
# Read and merge test class data from combined file, converting payloadBytes to bytes
first_test=true
while IFS= read -r line; do
# Skip array brackets and empty lines
if [[ "$line" =~ ^[[:space:]]*\[[[:space:]]*$ ]] || [[ "$line" =~ ^[[:space:]]*\][[:space:]]*$ ]] || [[ -z "$line" ]]; then
continue
fi
# Extract className and payloadBytes, convert to new format
if [[ "$line" =~ \"className\":[[:space:]]*\"([^\"]+)\".*\"payloadBytes\":[[:space:]]*([0-9]+) ]]; then
className="${BASH_REMATCH[1]}"
byteCount="${BASH_REMATCH[2]}"
# Add comma separator if not first test entry
if [ "$first_test" = false ]; then
echo "," >> final_payload_summary.json
fi
first_test=false
echo " {\"className\": \"$className\", \"bytes\": $byteCount}" >> final_payload_summary.json
fi
done < "$data_file"
echo "" >> final_payload_summary.json
echo " ]" >> final_payload_summary.json
echo -n " }" >> final_payload_summary.json
fi
done
echo "," >> final_payload_summary.json
echo " \"bytes\": $overall_total" >> final_payload_summary.json
echo "}" >> final_payload_summary.json
echo "=========================================="
echo "FINAL CONSOLIDATED PAYLOAD SUMMARY"
echo "=========================================="
cat final_payload_summary.json
echo ""
echo "=========================================="
echo "OVERALL TOTAL: $overall_total bytes"
echo "=========================================="
else
echo "No payload summary files found"
echo '{"bytes": 0}' > final_payload_summary.json
fi
shell: bash
- name: Upload final consolidated summary
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: final-payload-summary
path: final_payload_summary.json
if-no-files-found: warn
- name: Delete combined payload artifacts
run: |
# Get combined artifacts from this run and delete them
gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --paginate \
--jq '.artifacts[] | select(.name | startswith("combined-")) | select(.name | endswith("-payload-bytes")) | "\(.id) \(.name)"' \
| while read -r artifact_id artifact_name; do
echo "Deleting artifact: $artifact_name (ID: $artifact_id)"
gh api --method DELETE repos/${{ github.repository }}/actions/artifacts/$artifact_id \
&& echo "Successfully deleted $artifact_name" \
|| echo "Failed to delete $artifact_name"
done
env:
GH_TOKEN: ${{ github.token }}
shell: bash
continue-on-error: true