diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d3bcdfb490..4abddfa987 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,30 +6,31 @@ on: - release-* pull_request: workflow_dispatch: +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true env: DOTNET_NOLOGO: true defaults: run: shell: pwsh jobs: - build: - name: ${{ matrix.os-name }}-${{ matrix.test-category }} - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [windows-latest, ubuntu-latest] - test-category: [ Default, SqlServer, SqlServerPersistence, AzureServiceBus, RabbitMQ, AzureStorageQueues, MSMQ, SQS, PrimaryRavenAcceptance, PrimaryRavenPersistence, PostgreSQL, PostgreSQLPersistence, IBMMQ ] - include: - - os: windows-latest - os-name: Windows - - os: ubuntu-latest - os-name: Linux - exclude: - - os: ubuntu-latest - test-category: MSMQ - - os: windows-latest - test-category: IBMMQ - fail-fast: false + # Build each OS once and publish the test outputs to Azure blob storage. Every + # test job downloads its category's closure instead of rebuilding the solution + # 26 times. Because of the `needs:` gates below, a failed build skips all test + # jobs, so no broker/cloud resource is ever provisioned for an unbuildable commit. + build-windows: + name: Windows Build + runs-on: windows-latest + timeout-minutes: 90 + env: + AZURE_ACI_CREDENTIALS: ${{ secrets.AZURE_ACI_CREDENTIALS }} + AZURE_CI_RG: ServiceControl-CI + AZURE_CI_ACCOUNT: scpartpciartifacts + outputs: + account: ${{ steps.publish.outputs.account }} + container: ${{ steps.publish.outputs.container }} + sas: ${{ steps.publish.outputs.sas }} steps: - name: Check for secrets env: @@ -43,52 +44,334 @@ jobs: uses: actions/setup-dotnet@v6.0.0 with: global-json-file: global.json - - parallel: - - name: Setup WSL - uses: Particular/setup-wsl-action@v1.1.0 - - name: Download RavenDB Server - run: ./tools/download-ravendb-server.ps1 + # Needed by ServiceControlInstaller.Packaging's zip targets during the build. + - name: Download RavenDB Server + run: ./tools/download-ravendb-server.ps1 - name: Build run: dotnet build src --configuration Release -graph - name: Zip PowerShell module run: | New-Item assets\PowerShellModules -ItemType Directory Compress-Archive -Path deploy\PowerShellModules\Particular.ServiceControl.Management\* -DestinationPath assets\PowerShellModules\Particular.ServiceControl.Management.zip + - name: Smoke test PowerShell module import + run: Import-Module ./deploy/PowerShellModules/Particular.ServiceControl.Management - name: Upload assets uses: actions/upload-artifact@v7.0.1 - if: matrix.test-category == 'Default' with: - name: ${{ matrix.os-name }}-assets + name: Windows-assets path: | nugets/ zip/ assets/ retention-days: 1 - - name: Smoke test PowerShell module import - if: matrix.os-name == 'Windows' - run: Import-Module ./deploy/PowerShellModules/Particular.ServiceControl.Management + - name: Azure login + uses: azure/login@v3.0.1 + with: + creds: ${{ secrets.AZURE_ACI_CREDENTIALS }} + - name: Publish test outputs + id: publish + run: | + Write-Output "Ensuring resource group and storage account" + az group create --name $env:AZURE_CI_RG --location westeurope --only-show-errors | Out-Null + for ($attempt = 0; $attempt -lt 3; $attempt++) { + $exists = az storage account show --name $env:AZURE_CI_ACCOUNT --resource-group $env:AZURE_CI_RG --query name --output tsv 2>$null + if ($exists) { break } + az storage account create --name $env:AZURE_CI_ACCOUNT --resource-group $env:AZURE_CI_RG --sku Standard_LRS --kind StorageV2 --only-show-errors | Out-Null + if ($LASTEXITCODE -eq 0) { break } + Start-Sleep -Seconds 15 + } + $key = az storage account keys list --account-name $env:AZURE_CI_ACCOUNT --resource-group $env:AZURE_CI_RG --query "[0].value" --output tsv + if (-not $key) { throw 'Could not obtain storage account key' } + + $container = "run-$($env:GITHUB_RUN_ID)-win" + az storage container create --account-name $env:AZURE_CI_ACCOUNT --account-key $key --name $container --output none + + $expiry = (Get-Date).ToUniversalTime().AddHours(8).ToString('yyyy-MM-ddTHH:mmZ') + $sas = az storage container generate-sas --account-name $env:AZURE_CI_ACCOUNT --account-key $key --name $container --permissions rl --expiry $expiry --output tsv + + $testProjects = Get-ChildItem -Path src -Recurse -Filter 'TestsFilter.cs' -File | ForEach-Object { $_.Directory.Name } | Sort-Object -Unique + $staging = Join-Path $env:GITHUB_WORKSPACE '.staging' + $staged = 0 + foreach ($p in $testProjects) { + $source = Join-Path 'src' $p + if (-not (Test-Path (Join-Path $source 'bin\Release'))) { continue } + $dest = Join-Path $staging "src\$p" + New-Item -ItemType Directory -Force -Path $dest | Out-Null + Copy-Item -Path (Join-Path $source 'bin\Release') -Destination (Join-Path $dest 'bin') -Recurse -Force + Copy-Item -Path (Join-Path $source 'obj') -Destination (Join-Path $dest 'obj') -Recurse -Force + $staged++ + } + Write-Output "Staged $staged test projects" + + az storage blob upload-batch --account-name $env:AZURE_CI_ACCOUNT --account-key $key --destination $container --source $staging --pattern "**" --only-show-errors + if ($LASTEXITCODE -ne 0) { throw 'Blob upload failed' } + + "account=$($env:AZURE_CI_ACCOUNT)" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append + "container=$container" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append + "sas=$sas" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append + + build-linux: + name: Linux Build + runs-on: ubuntu-latest + timeout-minutes: 90 + env: + AZURE_ACI_CREDENTIALS: ${{ secrets.AZURE_ACI_CREDENTIALS }} + AZURE_CI_RG: ServiceControl-CI + AZURE_CI_ACCOUNT: scpartpciartifacts + outputs: + account: ${{ steps.publish.outputs.account }} + container: ${{ steps.publish.outputs.container }} + sas: ${{ steps.publish.outputs.sas }} + steps: + - name: Check for secrets + env: + SECRETS_AVAILABLE: ${{ secrets.SECRETS_AVAILABLE }} + run: exit $(If ($env:SECRETS_AVAILABLE -eq 'true') { 0 } Else { 1 }) + - name: Checkout + uses: actions/checkout@v7.0.1 + with: + fetch-depth: 0 + - name: Setup .NET SDK + uses: actions/setup-dotnet@v6.0.0 + with: + global-json-file: global.json + - name: Download RavenDB Server + run: ./tools/download-ravendb-server.ps1 + - name: Build + run: dotnet build src --configuration Release -graph + - name: Zip PowerShell module + run: | + New-Item assets\PowerShellModules -ItemType Directory + Compress-Archive -Path deploy\PowerShellModules\Particular.ServiceControl.Management\* -DestinationPath assets\PowerShellModules\Particular.ServiceControl.Management.zip + - name: Upload assets + uses: actions/upload-artifact@v7.0.1 + with: + name: Linux-assets + path: | + nugets/ + zip/ + assets/ + retention-days: 1 + - name: Azure login + uses: azure/login@v3.0.1 + with: + creds: ${{ secrets.AZURE_ACI_CREDENTIALS }} + - name: Publish test outputs + id: publish + run: | + Write-Output "Ensuring resource group and storage account" + az group create --name $env:AZURE_CI_RG --location westeurope --only-show-errors | Out-Null + for ($attempt = 0; $attempt -lt 3; $attempt++) { + $exists = az storage account show --name $env:AZURE_CI_ACCOUNT --resource-group $env:AZURE_CI_RG --query name --output tsv 2>$null + if ($exists) { break } + az storage account create --name $env:AZURE_CI_ACCOUNT --resource-group $env:AZURE_CI_RG --sku Standard_LRS --kind StorageV2 --only-show-errors | Out-Null + if ($LASTEXITCODE -eq 0) { break } + Start-Sleep -Seconds 15 + } + $key = az storage account keys list --account-name $env:AZURE_CI_ACCOUNT --resource-group $env:AZURE_CI_RG --query "[0].value" --output tsv + if (-not $key) { throw 'Could not obtain storage account key' } + + $container = "run-$($env:GITHUB_RUN_ID)-linux" + az storage container create --account-name $env:AZURE_CI_ACCOUNT --account-key $key --name $container --output none + + $expiry = (Get-Date).ToUniversalTime().AddHours(8).ToString('yyyy-MM-ddTHH:mmZ') + $sas = az storage container generate-sas --account-name $env:AZURE_CI_ACCOUNT --account-key $key --name $container --permissions rl --expiry $expiry --output tsv + + $testProjects = Get-ChildItem -Path src -Recurse -Filter 'TestsFilter.cs' -File | ForEach-Object { $_.Directory.Name } | Sort-Object -Unique + $staging = Join-Path $env:GITHUB_WORKSPACE '.staging' + $staged = 0 + foreach ($p in $testProjects) { + $source = Join-Path 'src' $p + if (-not (Test-Path (Join-Path $source 'bin/Release'))) { continue } + $dest = Join-Path $staging "src/$p" + New-Item -ItemType Directory -Force -Path $dest | Out-Null + Copy-Item -Path (Join-Path $source 'bin/Release') -Destination (Join-Path $dest 'bin') -Recurse -Force + Copy-Item -Path (Join-Path $source 'obj') -Destination (Join-Path $dest 'obj') -Recurse -Force + $staged++ + } + Write-Output "Staged $staged test projects" + + az storage blob upload-batch --account-name $env:AZURE_CI_ACCOUNT --account-key $key --destination $container --source $staging --pattern "**" --only-show-errors + if ($LASTEXITCODE -ne 0) { throw 'Blob upload failed' } + + "account=$($env:AZURE_CI_ACCOUNT)" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append + "container=$container" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append + "sas=$sas" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append + + test-windows: + name: Windows-${{ matrix.test-category }} + needs: build-windows + runs-on: windows-latest + timeout-minutes: 90 + env: + AZURE_CI_ACCOUNT: ${{ needs.build-windows.outputs.account }} + AZURE_CI_CONTAINER: ${{ needs.build-windows.outputs.container }} + AZURE_CI_SAS: ${{ needs.build-windows.outputs.sas }} + strategy: + matrix: + test-category: [ Default, SqlServer, SqlServerPersistence, AzureServiceBus, RabbitMQ, AzureStorageQueues, MSMQ, SQS, PrimaryRavenAcceptance, PrimaryRavenPersistence, PostgreSQL, PostgreSQLPersistence ] + fail-fast: false + steps: + - name: Check for secrets + env: + SECRETS_AVAILABLE: ${{ secrets.SECRETS_AVAILABLE }} + run: exit $(If ($env:SECRETS_AVAILABLE -eq 'true') { 0 } Else { 1 }) + - name: Checkout + uses: actions/checkout@v7.0.1 + with: + fetch-depth: 0 + - name: Setup .NET SDK + uses: actions/setup-dotnet@v6.0.0 + with: + global-json-file: global.json + - parallel: + - name: Setup WSL + uses: Particular/setup-wsl-action@v1.1.0 + - name: Download RavenDB Server + run: ./tools/download-ravendb-server.ps1 + - name: Download test outputs + run: | + $projects = ./tools/ci-category-projects.ps1 -Category '${{ matrix.test-category }}' + $projects | ForEach-Object { + Write-Output "Downloading $_" + az storage blob download-batch --account-name $env:AZURE_CI_ACCOUNT --sas-token $env:AZURE_CI_SAS --destination $env:GITHUB_WORKSPACE --source $env:AZURE_CI_CONTAINER --pattern "$_/**" --output none + if ($LASTEXITCODE -ne 0) { throw "Download failed for $_" } + } + - name: Setup SQL Server + uses: Particular/install-sql-server-action@v3.0.0 + if: matrix.test-category == 'SqlServer' + with: + connection-string-env-var: ServiceControl_TransportTests_SQL_ConnectionString + catalog: nservicebus + - name: Setup SQL Server persistence + uses: Particular/install-sql-server-action@v3.0.0 + if: matrix.test-category == 'SqlServerPersistence' + with: + connection-string-env-var: ServiceControl_Persistence_SqlServer_ConnectionString + catalog: ServiceControl + enable-full-text-search: true + - name: Setup PostgreSQL + uses: Particular/setup-postgres-action@v3.0.0 + if: matrix.test-category == 'PostgreSQL' + with: + connection-string-name: ServiceControl_TransportTests_PostgreSQL_ConnectionString + registry-username: ${{ secrets.DOCKERHUB_USERNAME }} + registry-password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Setup PostgreSQL persistence + uses: Particular/setup-postgres-action@v3.0.0 + if: matrix.test-category == 'PostgreSQLPersistence' + with: + connection-string-name: ServiceControl_Persistence_PostgreSql_ConnectionString + registry-username: ${{ secrets.DOCKERHUB_USERNAME }} + registry-password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Setup RabbitMQ + uses: Particular/setup-rabbitmq-action@v2.0.0 + if: matrix.test-category == 'RabbitMQ' + with: + connection-string-name: ServiceControl_TransportTests_RabbitMQ_ConnectionString + registry-username: ${{ secrets.DOCKERHUB_USERNAME }} + registry-password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Setup Azure Service Bus + uses: Particular/setup-azureservicebus-action@v2.5.0 + if: matrix.test-category == 'AzureServiceBus' + with: + connection-string-name: ServiceControl_TransportTests_ASBS_ConnectionString + azure-credentials: ${{ secrets.AZURE_ACI_CREDENTIALS }} + tag: ServiceControl + - name: Setup Azure Storage Queues + uses: Particular/setup-azurestorage-action@v2.0.0 + if: matrix.test-category == 'AzureStorageQueues' + with: + connection-string-name: ServiceControl_TransportTests_ASQ_ConnectionString + azure-credentials: ${{ secrets.AZURE_ACI_CREDENTIALS }} + tag: ServiceControl + - name: Setup IBM MQ + if: matrix.test-category == 'IBMMQ' + run: | + docker run --name ibmmq -d -p 1414:1414 -p 9443:9443 ` + --health-cmd "dspmq" --health-interval 10s --health-timeout 5s --health-retries 10 --health-start-period 30s ` + -e LICENSE=accept -e MQ_QMGR_NAME=QM1 -e MQ_ADMIN_PASSWORD=passw0rd ` + icr.io/ibm-messaging/mq:latest + # Wait for container health check to pass + while ((docker inspect --format '{{.State.Health.Status}}' ibmmq) -ne 'healthy') { + Start-Sleep -Seconds 2 + } + echo "ServiceControl_TransportTests_IBMMQ_ConnectionString=mq://admin:passw0rd@localhost:1414/QM1?channel=DEV.ADMIN.SVRCONN&topicprefix=DEV" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append + - name: Setup SQS environment variables + if: matrix.test-category == 'SQS' + run: | + echo "AWS_REGION=${{ secrets.AWS_REGION }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append + echo "AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append + echo "AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append + + # Cleanup of queues starting with `GHA-` handled by https://github.com/Particular/NServiceBus.AmazonSQS/blob/master/.github/workflows/tests-cleanup.yml + $connectString = "AccessKeyId=${{ secrets.AWS_ACCESS_KEY_ID }};SecretAccessKey=${{ secrets.AWS_SECRET_ACCESS_KEY }};Region=${{ secrets.AWS_REGION }};QueueNamePrefix=GHA-${{ github.run_id }}" + echo "ServiceControl_TransportTests_SQS_ConnectionString=$connectString" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append + - name: Run tests + run: | + $projects = ./tools/ci-category-projects.ps1 -Category '${{ matrix.test-category }}' + ./tools/run-tests.ps1 -Projects ($projects -join "`n") + env: + ServiceControl_TESTS_FILTER: ${{ matrix.test-category }} + PARTICULARSOFTWARE_LICENSE: ${{ secrets.LICENSETEXT }} + AZURE_ACI_CREDENTIALS: ${{ secrets.AZURE_ACI_CREDENTIALS }} + + test-linux: + name: Linux-${{ matrix.test-category }} + needs: build-linux + runs-on: ubuntu-latest + timeout-minutes: 90 + env: + AZURE_CI_ACCOUNT: ${{ needs.build-linux.outputs.account }} + AZURE_CI_CONTAINER: ${{ needs.build-linux.outputs.container }} + AZURE_CI_SAS: ${{ needs.build-linux.outputs.sas }} + strategy: + matrix: + test-category: [ Default, SqlServer, SqlServerPersistence, AzureServiceBus, RabbitMQ, AzureStorageQueues, SQS, PrimaryRavenAcceptance, PrimaryRavenPersistence, PostgreSQL, PostgreSQLPersistence, IBMMQ ] + fail-fast: false + steps: + - name: Check for secrets + env: + SECRETS_AVAILABLE: ${{ secrets.SECRETS_AVAILABLE }} + run: exit $(If ($env:SECRETS_AVAILABLE -eq 'true') { 0 } Else { 1 }) + - name: Checkout + uses: actions/checkout@v7.0.1 + with: + fetch-depth: 0 + - name: Setup .NET SDK + uses: actions/setup-dotnet@v6.0.0 + with: + global-json-file: global.json + - parallel: + - name: Setup WSL + uses: Particular/setup-wsl-action@v1.1.0 + - name: Download RavenDB Server + run: ./tools/download-ravendb-server.ps1 + - name: Download test outputs + run: | + $projects = ./tools/ci-category-projects.ps1 -Category '${{ matrix.test-category }}' + $projects | ForEach-Object { + Write-Output "Downloading $_" + az storage blob download-batch --account-name $env:AZURE_CI_ACCOUNT --sas-token $env:AZURE_CI_SAS --destination $env:GITHUB_WORKSPACE --source $env:AZURE_CI_CONTAINER --pattern "$_/**" --output none + if ($LASTEXITCODE -ne 0) { throw "Download failed for $_" } + } # there is an issue with az cli and python 3.14, so for now we need to pin it # once the issue is resolved it should be able to be re-floated # https://github.com/Azure/azure-cli/issues/32980 - name: Set Python 3.13 (Linux) - if: matrix.os-name == 'Linux' && matrix.test-category == 'AzureServiceBus' + if: matrix.test-category == 'AzureServiceBus' uses: actions/setup-python@v5 with: python-version: '3.13' - name: Install pinned Azure CLI on Python 3.13 (Linux) - if: matrix.os-name == 'Linux' && matrix.test-category == 'AzureServiceBus' + if: matrix.test-category == 'AzureServiceBus' shell: bash run: | python -m pip install --upgrade pip python -m pip install --user "azure-cli==2.64.0" echo "$HOME/.local/bin" >> "$GITHUB_PATH" - - name: Azure login - uses: azure/login@v3.0.1 - if: matrix.test-category == 'AzureServiceBus' || matrix.test-category == 'AzureStorageQueues' || matrix.test-category == 'RabbitMQ' || matrix.test-category == 'PostgreSQL' || matrix.test-category == 'PostgreSQLPersistence' - with: - creds: ${{ secrets.AZURE_ACI_CREDENTIALS }} - name: Setup SQL Server uses: Particular/install-sql-server-action@v3.0.0 if: matrix.test-category == 'SqlServer' @@ -148,24 +431,49 @@ jobs: while ((docker inspect --format '{{.State.Health.Status}}' ibmmq) -ne 'healthy') { Start-Sleep -Seconds 2 } - echo "ServiceControl_TransportTests_IBMMQ_ConnectionString=mq://admin:passw0rd@localhost:1414/QM1?channel=DEV.ADMIN.SVRCONN&topicprefix=DEV" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append + echo "ServiceControl_TransportTests_IBMMQ_ConnectionString=mq://admin:passw0rd@localhost:1414/QM1?channel=DEV.ADMIN.SVRCONN&topicprefix=DEV" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append - name: Setup SQS environment variables if: matrix.test-category == 'SQS' run: | - echo "AWS_REGION=${{ secrets.AWS_REGION }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append - echo "AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append - echo "AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append + echo "AWS_REGION=${{ secrets.AWS_REGION }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append + echo "AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append + echo "AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append # Cleanup of queues starting with `GHA-` handled by https://github.com/Particular/NServiceBus.AmazonSQS/blob/master/.github/workflows/tests-cleanup.yml $connectString = "AccessKeyId=${{ secrets.AWS_ACCESS_KEY_ID }};SecretAccessKey=${{ secrets.AWS_SECRET_ACCESS_KEY }};Region=${{ secrets.AWS_REGION }};QueueNamePrefix=GHA-${{ github.run_id }}" - echo "ServiceControl_TransportTests_SQS_ConnectionString=$connectString" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append + echo "ServiceControl_TransportTests_SQS_ConnectionString=$connectString" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append - name: Run tests - uses: Particular/run-tests-action@v1.7.0 + run: | + $projects = ./tools/ci-category-projects.ps1 -Category '${{ matrix.test-category }}' + ./tools/run-tests.ps1 -Projects ($projects -join "`n") env: ServiceControl_TESTS_FILTER: ${{ matrix.test-category }} PARTICULARSOFTWARE_LICENSE: ${{ secrets.LICENSETEXT }} AZURE_ACI_CREDENTIALS: ${{ secrets.AZURE_ACI_CREDENTIALS }} + cleanup-artifacts: + if: always() + needs: [ build-windows, build-linux, test-windows, test-linux ] + runs-on: ubuntu-latest + timeout-minutes: 30 + env: + AZURE_ACI_CREDENTIALS: ${{ secrets.AZURE_ACI_CREDENTIALS }} + AZURE_CI_RG: ServiceControl-CI + AZURE_CI_ACCOUNT: scpartpciartifacts + steps: + - name: Azure login + uses: azure/login@v3.0.1 + with: + creds: ${{ secrets.AZURE_ACI_CREDENTIALS }} + - name: Delete run containers + run: | + $key = az storage account keys list --account-name $env:AZURE_CI_ACCOUNT --resource-group $env:AZURE_CI_RG --query "[0].value" --output tsv + if (-not $key) { throw 'Could not obtain storage account key' } + "run-$($env:GITHUB_RUN_ID)-win", "run-$($env:GITHUB_RUN_ID)-linux" | ForEach-Object { + az storage container delete --account-name $env:AZURE_CI_ACCOUNT --account-key $key --name $_ --yes --only-show-errors + Write-Output "Deleted container $_" + } + windows-installers: uses: ./.github/workflows/build-windows.yml secrets: inherit @@ -179,4 +487,4 @@ jobs: container-test: needs: [ 'containers', 'db-container' ] uses: ./.github/workflows/container-integration-test.yml - secrets: inherit + secrets: inherit \ No newline at end of file diff --git a/tools/ci-category-projects.ps1 b/tools/ci-category-projects.ps1 new file mode 100644 index 0000000000..94b7fcde22 --- /dev/null +++ b/tools/ci-category-projects.ps1 @@ -0,0 +1,62 @@ +# Category -> test project mapping for the build-once CI workflow. +# Mirrors the assembly-level IncludeIn*Tests attributes in src/TestHelper. +# Used by the build job to stage test outputs and by test jobs to download +# their category's closure and run it. + +param( + [Parameter(Mandatory)] + [string]$Category, + + [switch]$ListCategories +) + +$ErrorActionPreference = 'Stop' + +$map = @{ + 'Default' = @( + 'ServiceControl.UnitTests', + 'ServiceControl.Audit.UnitTests', + 'ServiceControl.Config.Tests', + 'ServiceControl.Infrastructure.Tests', + 'Particular.LicensingComponent.UnitTests', + 'ServiceControl.Persistence.Tests.InMemory', + 'ServiceControl.Audit.Persistence.Tests', + 'ServiceControl.Audit.Persistence.Tests.RavenDB', + 'ServiceControlInstaller.Engine.UnitTests', + 'ServiceControlInstaller.Packaging.UnitTests', + 'ServiceControl.Transports.Tests', + 'ServiceControl.Audit.AcceptanceTests', + 'ServiceControl.Audit.AcceptanceTests.RavenDB', + 'ServiceControl.Monitoring.UnitTests', + 'ServiceControl.Monitoring.AcceptanceTests', + 'ServiceControl.MultiInstance.AcceptanceTests' + ) + 'SqlServer' = @('ServiceControl.Transports.SqlServer.Tests') + 'SqlServerPersistence' = @('ServiceControl.Persistence.Tests.SqlServer') + 'AzureServiceBus' = @('ServiceControl.Transports.ASBS.Tests') + 'RabbitMQ' = @( + 'ServiceControl.Transports.RabbitMQClassicConventionalRouting.Tests', + 'ServiceControl.Transports.RabbitMQClassicDirectRouting.Tests', + 'ServiceControl.Transports.RabbitMQQuorumConventionalRouting.Tests', + 'ServiceControl.Transports.RabbitMQQuorumDirectRouting.Tests' + ) + 'AzureStorageQueues' = @('ServiceControl.Transports.ASQ.Tests') + 'MSMQ' = @('ServiceControl.Transports.Msmq.Tests') + 'SQS' = @('ServiceControl.Transports.SQS.Tests') + 'PrimaryRavenAcceptance' = @('ServiceControl.AcceptanceTests.RavenDB') + 'PrimaryRavenPersistence'= @('ServiceControl.Persistence.Tests.RavenDB') + 'PostgreSQL' = @('ServiceControl.Transports.PostgreSql.Tests') + 'PostgreSQLPersistence' = @('ServiceControl.Persistence.Tests.PostgreSql') + 'IBMMQ' = @('ServiceControl.Transports.IBMMQ.Tests') +} + +if ($ListCategories) { + $map.Keys | Sort-Object + exit 0 +} + +if (-not $map.ContainsKey($Category)) { + throw "Unknown category '$Category'. Known: $($map.Keys -join ', ')" +} + +$map[$Category] | ForEach-Object { "src/$_" } \ No newline at end of file diff --git a/tools/run-tests.ps1 b/tools/run-tests.ps1 new file mode 100644 index 0000000000..dcf374032c --- /dev/null +++ b/tools/run-tests.ps1 @@ -0,0 +1,54 @@ +# Runs dotnet test against an explicit list of test projects with --no-build, +# because the build already happened in the build-once CI job and the outputs +# were downloaded. Mirrors what Particular/run-tests-action does. + +param( + [Parameter(Mandatory)] + [string]$Projects, + + [string]$TargetPlatform = 'x64' +) + +$ErrorActionPreference = 'Stop' + +$projectPaths = $Projects -split "`n" | ForEach-Object { $_.Trim() } | Where-Object { $_ } + +if ($projectPaths.Count -eq 0) { + throw 'No test projects were supplied.' +} + +Write-Output "Target Platform = $TargetPlatform" + +$isUnix = $PSVersionTable.Platform -eq 'Unix' +$exitCode = 0 + +foreach ($project in $projectPaths) { + $frameworks = @( + (Select-Xml -Path $project -XPath "/Project/PropertyGroup/TargetFramework").Node.InnerText + (Select-Xml -Path $project -XPath "/Project/PropertyGroup/TargetFrameworks").Node.InnerText -split ';' + ) | Where-Object { $_ } + + if ($frameworks.Count -eq 0) { + throw "Could not determine a target framework for $project." + } + + foreach ($framework in $frameworks) { + if ($isUnix -and ($framework.StartsWith('net4') -or $framework.Contains('-windows'))) { + Write-Output "Skipping $(Split-Path $project -Leaf) ($framework) because it cannot run on this platform." + continue + } + + Write-Output "::group::Running $(Split-Path $project -Leaf) ($framework)" + + dotnet test $project --configuration Release --no-build --no-restore --framework $framework --logger "GitHubActions;report-warnings=false" -- RunConfiguration.TreatNoTestsAsError=true "RunConfiguration.TargetPlatform=$TargetPlatform" + + Write-Output '::endgroup::' + + if ($LASTEXITCODE -ne 0) { + Write-Output "::error::Exit code = $LASTEXITCODE" + $exitCode = 1 + } + } +} + +exit $exitCode \ No newline at end of file