refactor: Fix false positive null reference #709
Workflow file for this run
This file contains 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
# Copyright (c) Omar Boukli-Hacene. All rights reserved. | |
# Distributed under an MIT-style license that can be | |
# found in the LICENSE file. | |
# SPDX-License-Identifier: MIT | |
name: Build and test | |
on: | |
pull_request: | |
branches: | |
- main | |
push: | |
branches: | |
- main | |
workflow_dispatch: | |
permissions: {} | |
env: | |
DOTNET_CLI_TELEMETRY_OPTOUT: true | |
DOTNET_NUGET_SIGNATURE_VERIFICATION: true | |
NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages | |
INTEGRATION_TEST_RESULTS_DIR_NAME: integration_test_results | |
UNIT_TEST_RESULTS_DIR_NAME: unit_test_results | |
jobs: | |
build: | |
name: Solution build | |
runs-on: ubuntu-latest | |
steps: | |
- name: Check out repository | |
uses: actions/checkout@v4 | |
with: | |
persist-credentials: false | |
- name: Build release | |
uses: ./.github/workflows/composite/dotnet-build | |
with: | |
configuration: Release | |
integration_test: | |
name: Integration test | |
runs-on: ubuntu-latest | |
env: | |
AKTABOOK_ENVIRONMENT: Test | |
AKTABOOK_INTEGRATION_TEST_ENVIRONMENT: Test | |
BUS_HOST_PID_FILE: src/Aktabook.Bus/bin/Release/net6.0/aktabook-bus.pid | |
RequesterServiceBus__RabbitMQConnectionOptions__HostName: localhost | |
RequesterServiceBus__RabbitMQConnectionOptions__Password: ${{ secrets.RABBITMQ_INTEGRATION_TEST_PASSWORD }} | |
RequesterServiceBus__RabbitMQConnectionOptions__PortNumber: 5672 | |
RequesterServiceBus__RabbitMQConnectionOptions__UserName: ${{ secrets.RABBITMQ_INTEGRATION_TEST_USERNAME }} | |
RequesterServiceBus__RabbitMQConnectionOptions__VirtualHost: aktabook_vhost | |
RequesterServiceDbContext__SqlServerConfig__DataSource: 127.0.0.1 | |
RequesterServiceDbContext__SqlServerConfig__TrustServerCertificate: true | |
RequesterServiceDbContext__SqlServerConfig__InitialCatalog: aktabook_common | |
RequesterServiceDbContext__SqlServerConfig__Password: ${{ secrets.SQLSERVER_INTEGRATION_TEST_SA_PASSWORD }} | |
RequesterServiceDbContext__SqlServerConfig__UserID: sa | |
SQLSERVER_CLIENT_NUGET_VERSION: "5.1.2" | |
outputs: | |
artifact_name: ${{ steps.get_artifact_name.outputs.artifact_name }} | |
services: | |
sql_server: | |
image: mcr.microsoft.com/mssql/server:2022-latest | |
env: | |
ACCEPT_EULA: "Y" | |
MSSQL_PID: Developer | |
MSSQL_SA_PASSWORD: ${{ secrets.SQLSERVER_INTEGRATION_TEST_SA_PASSWORD }} | |
SQLCMDUSER: sa | |
SQLCMDPASSWORD: ${{ secrets.SQLSERVER_INTEGRATION_TEST_SA_PASSWORD }} | |
ports: | |
- 1433:1433 | |
options: >- | |
--health-cmd " | |
/opt/mssql-tools/bin/sqlcmd | |
-h -1 | |
-Q 'SET NOCOUNT ON; SELECT 1' | |
-S 127.0.0.1 | |
-W | |
> /dev/null | |
" | |
--health-interval 7s | |
--health-retries 5 | |
--health-timeout 3s | |
rabbitmq: | |
image: rabbitmq:management | |
env: | |
RABBITMQ_DEFAULT_PASS: ${{ secrets.RABBITMQ_INTEGRATION_TEST_PASSWORD }} | |
RABBITMQ_DEFAULT_USER: ${{ secrets.RABBITMQ_INTEGRATION_TEST_USERNAME }} | |
RABBITMQ_DEFAULT_VHOST: aktabook_vhost | |
ports: | |
- 5672:5672 | |
- 15672:15672 | |
options: >- | |
--health-cmd "rabbitmq-diagnostics ping" | |
--health-interval 7s | |
--health-retries 5 | |
--health-timeout 3s | |
--hostname aktabook-queue | |
steps: | |
- name: Mask escaped values | |
run: >- | |
printf "::add-mask::%q\n" | |
$RequesterServiceDbContext__SqlServerConfig__Password | |
echo "::add-mask::$(jq | |
--raw-input | |
--raw-output | |
@uri <<<"$RequesterServiceBus__RabbitMQConnectionOptions__Password")" | |
- name: Check out repository | |
uses: actions/checkout@v4 | |
with: | |
persist-credentials: false | |
- name: Build release | |
uses: ./.github/workflows/composite/dotnet-build | |
with: | |
configuration: Release | |
- name: Restore .NET tools | |
run: dotnet tool restore | |
- name: Create RabbitMQ virtual host | |
env: | |
BUS_USERNAME: ${{ secrets.RABBITMQ_INTEGRATION_TEST_USERNAME }} | |
BUS_PASSWORD: ${{ secrets.RABBITMQ_INTEGRATION_TEST_PASSWORD }} | |
run: >- | |
curl | |
--data-urlencode | |
--request PUT | |
--silent | |
--netrc-file <(cat <<< "login $BUS_USERNAME password $BUS_PASSWORD") | |
http://localhost:15672/api/vhosts/$RequesterServiceBus__RabbitMQConnectionOptions__VirtualHost | |
- name: Create AMQP connection URL | |
run: >- | |
echo "RABBITMQ_CONNECTION_STRING="$( | |
dotnet script eval -- | |
$RequesterServiceBus__RabbitMQConnectionOptions__HostName | |
$RequesterServiceBus__RabbitMQConnectionOptions__PortNumber | |
$RequesterServiceBus__RabbitMQConnectionOptions__VirtualHost | |
$RequesterServiceBus__RabbitMQConnectionOptions__UserName | |
$RequesterServiceBus__RabbitMQConnectionOptions__Password | |
<<< | |
'Console.WriteLine((new UriBuilder("amqp", | |
Args[0], | |
int.Parse(Args[1]), | |
Uri.EscapeDataString(Args[2])) | |
{ UserName = Uri.EscapeDataString(Args[3]), | |
Password = Uri.EscapeDataString(Args[4]) | |
}).Uri.ToString());' | |
)"" | |
>> $GITHUB_ENV | |
- name: Create NServiceBus delays | |
env: | |
BUS_USERNAME: ${{ secrets.RABBITMQ_INTEGRATION_TEST_USERNAME }} | |
BUS_PASSWORD: ${{ secrets.RABBITMQ_INTEGRATION_TEST_PASSWORD }} | |
run: >- | |
dotnet rabbitmq-transport | |
delays create | |
--connectionStringEnv RABBITMQ_CONNECTION_STRING | |
dotnet rabbitmq-transport | |
delays verify | |
--url http://localhost:15672 | |
--username $BUS_USERNAME | |
--password $BUS_PASSWORD | |
- name: Create NServiceBus BookInfoRequestEndpoint | |
run: >- | |
dotnet rabbitmq-transport | |
endpoint create BookInfoRequestEndpoint | |
--auditQueueName AuditQueue | |
--connectionStringEnv RABBITMQ_CONNECTION_STRING | |
--errorQueueName ErrorQueue | |
--queueType Quorum | |
--routingTopology Conventional | |
--useDurableEntities | |
- name: Start code coverage collection server | |
run: >- | |
dotnet coverage collect | |
--background | |
--session-id coverage_session | |
--server-mode | |
--output ${{ env.INTEGRATION_TEST_RESULTS_DIR_NAME }}/coverage_session/coverage.coverage | |
--output-format coverage | |
- name: Create SQL Server connection string | |
run: >- | |
echo "SQLSERVER_CONNECTION_STRING="$( | |
dotnet script eval -- | |
$RequesterServiceDbContext__SqlServerConfig__DataSource | |
$RequesterServiceDbContext__SqlServerConfig__TrustServerCertificate | |
$RequesterServiceDbContext__SqlServerConfig__UserID | |
$RequesterServiceDbContext__SqlServerConfig__Password | |
$RequesterServiceDbContext__SqlServerConfig__InitialCatalog | |
<<< | |
'#r "nuget: Microsoft.Data.SqlClient, ${{ env.SQLSERVER_CLIENT_NUGET_VERSION }}" | |
using Microsoft.Data.SqlClient; | |
Console.WriteLine((new SqlConnectionStringBuilder | |
{ | |
DataSource = Args[0], | |
TrustServerCertificate = bool.Parse(Args[1]), | |
UserID = Args[2], | |
Password = Args[3], | |
InitialCatalog = Args[4] | |
}).ConnectionString);' | |
)"" | |
>> $GITHUB_ENV | |
- name: Create database objects | |
run: >- | |
dotnet coverage connect | |
coverage_session | |
dotnet ef database update | |
--configuration Release | |
--connection "$SQLSERVER_CONNECTION_STRING" | |
--no-build | |
--project src/Aktabook.Data.Migrations | |
- name: Run bus endpoint host | |
run: >- | |
dotnet coverage connect | |
--background | |
coverage_session | |
./Aktabook.Bus | |
working-directory: src/Aktabook.Bus/bin/Release/net6.0/ | |
- name: Wait for bus readiness check | |
env: | |
PORT: 23514 | |
run: >- | |
x=1; | |
while [[ $x -le 30 && ! -f "$BUS_HOST_PID_FILE" ]]; | |
do sleep $(( x++ )); | |
done | |
nc -4 -d -i 1 -n -v -w 10 -z | |
127.0.0.1 ${{ env.PORT }} | |
- name: Wait for bus liveness first check | |
env: | |
PORT: 23515 | |
run: >- | |
nc -4 -d -i 1 -n -v -w 10 -z | |
127.0.0.1 ${{ env.PORT }} | |
- name: Run integration tests with ephemeral database | |
env: | |
RequesterServiceDbContext__SqlServerConfig__InitialCatalog: aktabook_ephemeral | |
run: >- | |
dotnet test | |
--collect 'Code Coverage' | |
--configuration Release | |
--filter 'FullyQualifiedName~IntegrationTest&Category=Ephemeral' | |
--no-build | |
--no-restore | |
--results-directory ${{ env.INTEGRATION_TEST_RESULTS_DIR_NAME }} | |
- name: Run integration tests with long-lived database | |
run: >- | |
dotnet test | |
--collect 'Code Coverage' | |
--configuration Release | |
--filter 'FullyQualifiedName~IntegrationTest&Category!=Ephemeral' | |
--no-build | |
--no-restore | |
--results-directory ${{ env.INTEGRATION_TEST_RESULTS_DIR_NAME }} | |
- name: Wait for bus liveness last check | |
env: | |
PORT: 23515 | |
run: >- | |
nc -4 -d -i 1 -n -v -w 10 -z | |
127.0.0.1 ${{ env.PORT }} | |
- name: Stop bus endpoint host | |
run: >- | |
pkill | |
--pidfile $BUS_HOST_PID_FILE | |
--signal SIGTERM | |
- name: Stop code coverage collection server | |
run: >- | |
dotnet coverage | |
shutdown | |
coverage_session | |
- name: Create merged test coverage report directory | |
run: mkdir --parents ${{ env.INTEGRATION_TEST_RESULTS_DIR_NAME }}/merged/ | |
- name: Merge test coverage reports | |
run: >- | |
dotnet coverage merge | |
--remove-input-files | |
--recursive | |
--output ${{ env.INTEGRATION_TEST_RESULTS_DIR_NAME }}/merged/merged.xml | |
--output-format xml | |
${{ env.INTEGRATION_TEST_RESULTS_DIR_NAME }}/*/*.* | |
- name: Archive test coverage results | |
run: >- | |
7z a -bb0 -bd -m0=lzma2 -mx=3 | |
${{ env.INTEGRATION_TEST_RESULTS_DIR_NAME }}.7z | |
${{ env.INTEGRATION_TEST_RESULTS_DIR_NAME }}/merged/ | |
- name: Get artifact name | |
id: get_artifact_name | |
run: >- | |
echo "artifact_name="${{ env.INTEGRATION_TEST_RESULTS_DIR_NAME }}-$(git rev-parse --short "$GITHUB_SHA")"" | |
>> $GITHUB_OUTPUT | |
- name: Upload test results archive | |
uses: actions/upload-artifact@v4 | |
with: | |
if-no-files-found: error | |
name: ${{ steps.get_artifact_name.outputs.artifact_name }} | |
path: ${{ env.INTEGRATION_TEST_RESULTS_DIR_NAME }}.7z | |
- name: Upload bus logs | |
if: ${{ always() }} | |
uses: actions/upload-artifact@v4 | |
with: | |
if-no-files-found: error | |
name: bus-logs | |
path: src/Aktabook.Bus/bin/Release/net6.0/Logs | |
- name: Upload public API logs | |
if: ${{ always() }} | |
uses: actions/upload-artifact@v4 | |
with: | |
if-no-files-found: error | |
name: public-api-logs | |
path: src/Aktabook.Bus/bin/Release/net6.0/Logs | |
unit_test: | |
name: Unit test | |
runs-on: ubuntu-latest | |
outputs: | |
artifact_name: ${{ steps.get_artifact_name.outputs.artifact_name }} | |
steps: | |
- name: Check out repository | |
uses: actions/checkout@v4 | |
with: | |
persist-credentials: false | |
- name: Build release | |
uses: ./.github/workflows/composite/dotnet-build | |
with: | |
configuration: Release | |
- name: Restore .NET tools | |
run: dotnet tool restore | |
- name: Run unit tests | |
run: >- | |
dotnet test | |
--collect 'Code Coverage' | |
--configuration Release | |
--filter 'FullyQualifiedName~UnitTest' | |
--no-build | |
--no-restore | |
--results-directory ${{ env.UNIT_TEST_RESULTS_DIR_NAME }} | |
- name: Create merged test coverage report directory | |
run: mkdir --parents ${{ env.UNIT_TEST_RESULTS_DIR_NAME }}/merged/ | |
- name: Merge test coverage reports | |
run: >- | |
dotnet coverage merge | |
--remove-input-files | |
--recursive | |
--output ${{ env.UNIT_TEST_RESULTS_DIR_NAME }}/merged/merged.xml | |
--output-format xml | |
${{ env.UNIT_TEST_RESULTS_DIR_NAME }}/*/*.* | |
- name: Archive test coverage results | |
run: >- | |
7z a -bb0 -bd -m0=lzma2 -mx=3 | |
${{ env.UNIT_TEST_RESULTS_DIR_NAME }}.7z | |
${{ env.UNIT_TEST_RESULTS_DIR_NAME }}/merged/ | |
- name: Get artifact name | |
id: get_artifact_name | |
run: >- | |
echo "artifact_name="${{ env.UNIT_TEST_RESULTS_DIR_NAME }}-$(git rev-parse --short "$GITHUB_SHA")"" | |
>> $GITHUB_OUTPUT | |
- name: Upload test results archive | |
uses: actions/upload-artifact@v4 | |
with: | |
if-no-files-found: error | |
name: ${{ steps.get_artifact_name.outputs.artifact_name }} | |
path: ${{ env.UNIT_TEST_RESULTS_DIR_NAME }}.7z | |
quality_gate: | |
name: Quality gate | |
runs-on: ubuntu-latest | |
needs: | |
- unit_test | |
- integration_test | |
steps: | |
- name: Check out repository | |
uses: actions/checkout@v4 | |
with: | |
fetch-depth: 0 | |
persist-credentials: false | |
- name: Download integration test results | |
uses: actions/download-artifact@v4 | |
with: | |
name: ${{ needs.integration_test.outputs.artifact_name }} | |
- name: Download unit test results | |
uses: actions/download-artifact@v4 | |
with: | |
name: ${{ needs.unit_test.outputs.artifact_name }} | |
- name: Unarchive integration test results | |
run: >- | |
7z x | |
-o"${{ github.workspace }}" | |
${{ env.INTEGRATION_TEST_RESULTS_DIR_NAME }}.7z | |
- name: Unarchive unit test results | |
run: >- | |
7z x | |
-o"${{ github.workspace }}" | |
${{ env.UNIT_TEST_RESULTS_DIR_NAME }}.7z | |
- name: Cache NuGet packages | |
uses: actions/cache@v4 | |
with: | |
path: ~/.nuget/packages | |
key: >- | |
${{ runner.os }}-nuget-${{ | |
hashFiles('**/packages.lock.json', | |
'.config/dotnet-tools.json') | |
}} | |
restore-keys: | | |
${{ runner.os }}-nuget- | |
- name: Set up .NET SDK | |
uses: actions/setup-dotnet@v4 | |
with: | |
cache: true | |
cache-dependency-path: |- | |
src/**/packages.lock.json | |
test/**/packages.lock.json | |
global-json-file: global.json | |
- name: Restore .NET tools | |
run: dotnet tool restore | |
- name: Merge test coverage reports | |
run: >- | |
dotnet coverage merge | |
--remove-input-files | |
--recursive | |
--output merged.xml | |
--output-format xml | |
${{ env.UNIT_TEST_RESULTS_DIR_NAME }}/merged.xml | |
${{ env.INTEGRATION_TEST_RESULTS_DIR_NAME }}/merged.xml | |
- name: Restores dependencies | |
env: | |
DOTNET_NUGET_SIGNATURE_VERIFICATION: true | |
run: dotnet restore --locked-mode | |
- name: Build and analyze | |
env: | |
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} | |
run: >- | |
JAVA_HOME=$JAVA_HOME_17_X64 | |
dotnet sonarscanner begin | |
/k:'oboukli_aktabook' | |
/o:'oboukli' | |
/d:'sonar.host.url=https://sonarcloud.io' | |
/d:'sonar.sourceEncoding=UTF-8' | |
/d:'sonar.login=${{ secrets.SONAR_TOKEN }}' | |
/d:'sonar.cs.vscoveragexml.reportsPaths=merged.xml' | |
dotnet build | |
--no-incremental | |
--no-restore | |
dotnet sonarscanner end | |
/d:'sonar.login=${{ secrets.SONAR_TOKEN }}' |