test: deflake ThreadEventsListener real-event-source test (wiring assertion + retry) #7711
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 | |
| with: | |
| disable-sudo: true | |
| egress-policy: audit | |
| - name: Checkout | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - 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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| fetch-depth: 0 | |
| filter: blob:none | |
| - name: Cache NuGet packages | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| 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@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.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@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 | |
| 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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| fetch-depth: 0 | |
| filter: blob:none | |
| - name: Cache NuGet packages | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| 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@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| fetch-depth: 0 | |
| filter: blob:none | |
| - name: Cache NuGet packages | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| 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@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| 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 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@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0 | |
| with: | |
| dotnet-version: '10.0.x' | |
| dotnet-quality: 'ga' | |
| - name: Run Integration Tests | |
| env: | |
| NR_TEST_SECRETS: ${{ secrets.TEST_SECRETS }} | |
| CI_BRANCH: ${{ github.ref_name }} | |
| CI_COMMIT: ${{ github.sha }} | |
| CI_RUN_ID: ${{ github.run_id }} | |
| CI_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| CI_TRIGGER: ${{ github.event_name }} | |
| 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" | |
| # Retry once on failure to absorb transient infrastructure blips (e.g. port | |
| # contention, SSL cert / tooling startup races, or a flaky test-app launch) that | |
| # can surface as a spurious failure while the code under test is healthy. A | |
| # retried-but-passing job still emits a ::warning:: so genuine regressions / | |
| # flake churn stay visible in the UI. | |
| $maxAttempts = 2 | |
| $attempt = 1 | |
| while ($true) { | |
| & "${{ env.integration_tests_path }}/NewRelic.Agent.IntegrationTests.exe" -namespace NewRelic.Agent.IntegrationTests.${{ matrix.namespace }} -trx "C:\IntegrationTestWorkingDirectory\TestResults\${{ matrix.namespace }}_testResults.trx" | |
| $exitCode = $LASTEXITCODE | |
| if ($exitCode -eq 0) { | |
| if ($attempt -gt 1) { | |
| Write-Host "::warning::Integration tests for ${{ matrix.namespace }} passed on attempt $attempt after a transient failure. Investigate if this recurs." | |
| # Best-effort flaky-retry telemetry (never fails the job). | |
| $env:RETRY_TEST_TYPE = "integrationTests" | |
| $env:RETRY_TEST_GROUP = "${{ matrix.namespace }}" | |
| & bash build/Scripts/report-flaky-retry.sh | |
| } | |
| break | |
| } | |
| if ($attempt -ge $maxAttempts) { | |
| Write-Host "Integration tests for ${{ matrix.namespace }} failed after $attempt attempt(s) (exit code $exitCode)." | |
| exit $exitCode | |
| } | |
| Write-Host "Integration tests for ${{ matrix.namespace }} failed on attempt $attempt (exit code $exitCode); retrying once to absorb a possible transient blip..." | |
| $attempt++ | |
| Start-Sleep -Seconds 10 | |
| } | |
| 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" | |
| $countLog = "C:\IntegrationTestWorkingDirectory\TestResults\${{ matrix.namespace }}_test_count.json" | |
| Write-Host "Checking for TRX file at: $trxFile" | |
| $executedTests = 0 | |
| 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) { | |
| foreach ($line in ($message.InnerText -split "\r?\n")) { | |
| if ($line -match '^\s*(.+?):\s+([a-z0-9_]+):\s+(\d+)\s+bytes\s*$') { | |
| $className = $Matches[1].Trim() | |
| $category = $Matches[2] | |
| $byteCount = [int]$Matches[3] | |
| if (-not $payloadData.ContainsKey($className)) { $payloadData[$className] = @{} } | |
| if ($payloadData[$className].ContainsKey($category)) { | |
| $payloadData[$className][$category] += $byteCount | |
| } else { | |
| $payloadData[$className][$category] = $byteCount | |
| } | |
| } | |
| } | |
| } | |
| Write-Host "Found $($payloadData.Count) payload log entries" | |
| if ($payloadData.Count -gt 0) { | |
| $payloadData | ConvertTo-Json -Depth 5 | 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 | |
| } | |
| # Extract the executed-test count from the TRX ResultSummary/Counters (VSTest | |
| # schema, emitted by the xUnit v3 runner). 'executed' counts tests that ran | |
| # (any outcome), excluding skipped / not-executed. | |
| $counters = $trxContent.SelectSingleNode("//*[local-name()='ResultSummary']/*[local-name()='Counters']") | |
| if ($counters -ne $null -and $counters.executed) { $executedTests = [int]$counters.executed } | |
| } else { | |
| Write-Host "TRX file not found" | |
| @{} | ConvertTo-Json | Out-File -FilePath $payloadLog -Encoding UTF8 | |
| } | |
| Write-Host "Executed test count: $executedTests" | |
| @{ executedTests = $executedTests } | ConvertTo-Json | Out-File -FilePath $countLog -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 | |
| C:\IntegrationTestWorkingDirectory\TestResults\*_test_count.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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| 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@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0 | |
| with: | |
| dotnet-version: '10.0.x' | |
| dotnet-quality: 'ga' | |
| - name: Run Unbounded Integration Tests | |
| env: | |
| NR_TEST_SECRETS: ${{ secrets.TEST_SECRETS }} | |
| CI_BRANCH: ${{ github.ref_name }} | |
| CI_COMMIT: ${{ github.sha }} | |
| CI_RUN_ID: ${{ github.run_id }} | |
| CI_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| CI_TRIGGER: ${{ github.event_name }} | |
| 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" | |
| # Retry once on failure to absorb transient WAN/infra blips between the GitHub-hosted | |
| # runner and the Azure-hosted unbounded services (e.g. brief packet loss surfacing as a | |
| # client command timeout while the service itself is healthy). A retried-but-passing job | |
| # still emits a ::warning:: so genuine regressions / flake churn stay visible in the UI. | |
| $maxAttempts = 2 | |
| $attempt = 1 | |
| while ($true) { | |
| & "${{ env.unbounded_tests_path }}/NewRelic.Agent.UnboundedIntegrationTests.exe" -namespace NewRelic.Agent.UnboundedIntegrationTests.${{ matrix.namespace }} -trx "C:\IntegrationTestWorkingDirectory\TestResults\${{ matrix.namespace }}_testResults.trx" | |
| $exitCode = $LASTEXITCODE | |
| if ($exitCode -eq 0) { | |
| if ($attempt -gt 1) { | |
| Write-Host "::warning::Unbounded tests for ${{ matrix.namespace }} passed on attempt $attempt after a transient failure. Investigate if this recurs." | |
| # Best-effort flaky-retry telemetry (never fails the job). | |
| $env:RETRY_TEST_TYPE = "unboundedTests" | |
| $env:RETRY_TEST_GROUP = "${{ matrix.namespace }}" | |
| & bash build/Scripts/report-flaky-retry.sh | |
| } | |
| break | |
| } | |
| if ($attempt -ge $maxAttempts) { | |
| Write-Host "Unbounded tests for ${{ matrix.namespace }} failed after $attempt attempt(s) (exit code $exitCode)." | |
| exit $exitCode | |
| } | |
| Write-Host "Unbounded tests for ${{ matrix.namespace }} failed on attempt $attempt (exit code $exitCode); retrying once to absorb a possible transient blip..." | |
| $attempt++ | |
| Start-Sleep -Seconds 10 | |
| } | |
| 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" | |
| $countLog = "C:\IntegrationTestWorkingDirectory\TestResults\${{ matrix.namespace }}_test_count.json" | |
| Write-Host "Checking for TRX file at: $trxFile" | |
| $executedTests = 0 | |
| 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) { | |
| foreach ($line in ($message.InnerText -split "\r?\n")) { | |
| if ($line -match '^\s*(.+?):\s+([a-z0-9_]+):\s+(\d+)\s+bytes\s*$') { | |
| $className = $Matches[1].Trim() | |
| $category = $Matches[2] | |
| $byteCount = [int]$Matches[3] | |
| if (-not $payloadData.ContainsKey($className)) { $payloadData[$className] = @{} } | |
| if ($payloadData[$className].ContainsKey($category)) { | |
| $payloadData[$className][$category] += $byteCount | |
| } else { | |
| $payloadData[$className][$category] = $byteCount | |
| } | |
| } | |
| } | |
| } | |
| Write-Host "Found $($payloadData.Count) payload log entries" | |
| if ($payloadData.Count -gt 0) { | |
| $payloadData | ConvertTo-Json -Depth 5 | 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 | |
| } | |
| # Extract the executed-test count from the TRX ResultSummary/Counters (VSTest | |
| # schema, emitted by the xUnit v3 runner). 'executed' counts tests that ran | |
| # (any outcome), excluding skipped / not-executed. | |
| $counters = $trxContent.SelectSingleNode("//*[local-name()='ResultSummary']/*[local-name()='Counters']") | |
| if ($counters -ne $null -and $counters.executed) { $executedTests = [int]$counters.executed } | |
| } else { | |
| Write-Host "TRX file not found" | |
| @{} | ConvertTo-Json | Out-File -FilePath $payloadLog -Encoding UTF8 | |
| } | |
| Write-Host "Executed test count: $executedTests" | |
| @{ executedTests = $executedTests } | ConvertTo-Json | Out-File -FilePath $countLog -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 | |
| C:\IntegrationTestWorkingDirectory\TestResults\*_test_count.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@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 | |
| with: | |
| disable-sudo: true | |
| egress-policy: audit | |
| - name: Checkout | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - 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@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 | |
| with: | |
| disable-sudo: true | |
| egress-policy: audit | |
| - name: Checkout | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - 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@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0 | |
| with: | |
| dotnet-version: '10.0.x' | |
| dotnet-quality: 'ga' | |
| - name: Checkout | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| 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@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 | |
| 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@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 | |
| with: | |
| disable-sudo: true | |
| egress-policy: audit | |
| - name: Checkout | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | |
| - 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: bash build/Scripts/combine-payload-logs.sh payload-logs/integration "Integration Tests" combined-logs/integration_payload_bytes_combined.json | |
| 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@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 | |
| with: | |
| disable-sudo: true | |
| egress-policy: audit | |
| - name: Checkout | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | |
| - 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: bash build/Scripts/combine-payload-logs.sh payload-logs/unbounded "Unbounded Integration Tests" combined-logs/unbounded_payload_bytes_combined.json | |
| 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@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 | |
| with: | |
| disable-sudo: true | |
| egress-policy: audit | |
| - name: Checkout | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | |
| - 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 ls payload-summaries/*_combined.json >/dev/null 2>&1; then | |
| final="$(jq -n '{bytes:0, byType:{}, executedTests:0}')" | |
| for f in payload-summaries/*_combined.json; do | |
| base="$(basename "$f" _payload_bytes_combined.json)" | |
| case "$base" in | |
| integration) key="integrationTests";; | |
| unbounded) key="unboundedTests";; | |
| linux_container) key="containerTests";; | |
| *) key="$base";; | |
| esac | |
| final="$(jq --arg key "$key" --slurpfile c "$f" ' | |
| .[$key] = {generatedAt:$c[0].generatedAt, bytes:$c[0].grandTotalBytes, executedTests:($c[0].executedTests // 0), byType:$c[0].byType, details:$c[0].details} | |
| | .bytes += $c[0].grandTotalBytes | |
| | .executedTests += ($c[0].executedTests // 0) | |
| | .byType = (reduce ($c[0].byType|to_entries[]) as $cat (.byType; .[$cat.key]=((.[$cat.key]//0)+$cat.value))) | |
| ' <<<"$final")" | |
| done | |
| echo "$final" > final_payload_summary.json | |
| else | |
| echo '{"bytes": 0, "byType": {}, "executedTests": 0}' > final_payload_summary.json | |
| fi | |
| echo "FINAL CONSOLIDATED PAYLOAD SUMMARY"; cat final_payload_summary.json | |
| 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: Guard against empty payload data | |
| if: inputs.aggregate_payload_data == true || github.event_name == 'schedule' | |
| run: bash build/Scripts/check-payload-not-empty.sh final_payload_summary.json | |
| shell: bash | |
| - name: Report payload metrics to New Relic (staging) | |
| if: inputs.aggregate_payload_data == true || github.event_name == 'schedule' | |
| continue-on-error: true | |
| env: | |
| PAYLOAD_SUMMARY_FILE: final_payload_summary.json | |
| NR_METRIC_API_URL: https://staging-metric-api.newrelic.com/metric/v1 | |
| NR_TEST_SECRETS: ${{ secrets.TEST_SECRETS }} | |
| CI_BRANCH: ${{ github.ref_name }} | |
| CI_COMMIT: ${{ github.sha }} | |
| CI_RUN_ID: ${{ github.run_id }} | |
| CI_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| run: bash build/Scripts/report-payload-metrics.sh | |
| shell: bash | |
| - 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 |