Skip to content

Refactor CI workflow to modernize database testing setup and streamli… #23

Refactor CI workflow to modernize database testing setup and streamli…

Refactor CI workflow to modernize database testing setup and streamli… #23

Workflow file for this run

name: CI
on:
push:
branches: ["master", "develop", "feature/**", "release/**", "hotfix/**"]
tags: ["*"]
pull_request:
branches: ["master", "develop"]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
# Keep the Windows build separate so we still validate the .NET Framework target
# and the packaging prerequisites that only exist on Windows runners.
build:
name: Build (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
fetch-tags: true
submodules: recursive
- name: Setup .NET SDKs
uses: actions/setup-dotnet@v5
with:
dotnet-version: |
8.0.x
9.0.x
10.0.x
- name: Cache NuGet packages
uses: actions/cache@v5
with:
path: ~/.nuget/packages
key: nuget-${{ runner.os }}-${{ hashFiles('**/*.csproj', '**/*.props', '**/*.targets', '.config/dotnet-tools.json') }}
restore-keys: |
nuget-${{ runner.os }}-
- name: Restore .NET tools
run: dotnet tool restore
- name: Restore solution
run: dotnet restore ./src/NEventStore.Persistence.Sql.Core.sln --verbosity m
- name: Run GitVersion and patch assembly info
id: gitversion
shell: pwsh
working-directory: ${{ github.workspace }}
run: |
$gitVersion = dotnet tool run dotnet-gitversion /targetpath "${{ github.workspace }}" /output json /updateAssemblyInfo | ConvertFrom-Json
dotnet tool run dotnet-gitversion /targetpath "${{ github.workspace }}/dependencies/NEventStore" /updateAssemblyInfo | Out-Null
"semver=$($gitVersion.SemVer)" >> $env:GITHUB_OUTPUT
- name: Build solution
run: dotnet build ./src/NEventStore.Persistence.Sql.Core.sln -c Release --no-restore /p:ContinuousIntegrationBuild=True
# Tests run on Linux with database services (PostgreSQL, MySQL, SQLite).
# Each database provider runs independently to isolate failures and validate
# database-specific persistence logic.
test-sql-databases:
needs: build
name: Test (Linux, ${{ matrix.db }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- db: mssql
image: mcr.microsoft.com/mssql/server:2022-RTM-GDR1-ubuntu-20.04
options: >-
--health-cmd "/opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P 'Password12!' -Q 'SELECT 1'"
--health-interval 20s
--health-timeout 10s
--health-retries 10
--health-start-period 30s
env:
SA_PASSWORD: Password12!
ACCEPT_EULA: Y
MSSQL_PID: Express
ports:
- 1433:1433
- db: postgresql
image: postgres:15-alpine
options: >-
--health-cmd pg_isready
--health-interval 20s
--health-timeout 10s
--health-retries 10
--health-start-period 30s
env:
POSTGRES_DB: NEventStore
POSTGRES_USER: postgres
POSTGRES_PASSWORD: Password12!
ports:
- 5432:5432
- db: mysql
image: mysql:8
options: >-
--health-cmd "mysqladmin ping -h localhost"
--health-interval 20s
--health-timeout 10s
--health-retries 10
--health-start-period 30s
env:
MYSQL_DATABASE: NEventStore
MYSQL_ROOT_PASSWORD: Password12!
ports:
- 3306:3306
- db: oracle
image: gvenzl/oracle-xe
options: >-
--health-cmd "sqlplus -L -S / as sysdba @/dev/null"
--health-interval 25s
--health-timeout 10s
--health-retries 10
--health-start-period 30s
env:
ORACLE_ALLOW_REMOTE: true
ORACLE_PASSWORD: Password12!
ports:
- 1521:1521
- db: sqlite
image: alpine:3.18
options: >-
--health-cmd "exit 0"
env: {}
ports: []
services:
database:
image: ${{ matrix.image }}
env: ${{ matrix.env }}
options: ${{ matrix.options }}
ports: ${{ matrix.ports }}
env:
TFMS: "net8.0 net9.0 net10.0"
NEventStore.MsSql: Server=localhost;Database=NEventStore;User Id=SA;Password=Password12!;TrustServerCertificate=True;
NEventStore.PostgreSql: Server=localhost;Database=NEventStore;Uid=postgres;Pwd=Password12!;Enlist=false;
NEventStore.MySql: Server=localhost;Database=NEventStore;Uid=root;Pwd=Password12!;AutoEnlist=false;
NEventStore.Sqlite: Data Source=:memory:;Cache=Shared;
NEventStore.Oracle: Data Source=localhost:1521/XE;User Id=system;Password=Password12!;Persist Security Info=True;
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
fetch-tags: true
submodules: recursive
- name: Setup .NET SDKs
uses: actions/setup-dotnet@v5
with:
dotnet-version: |
8.0.x
9.0.x
10.0.x
- name: Cache NuGet packages
uses: actions/cache@v5
with:
path: ~/.nuget/packages
key: nuget-${{ runner.os }}-${{ hashFiles('**/*.csproj', '**/*.props', '**/*.targets', '.config/dotnet-tools.json') }}
restore-keys: |
nuget-${{ runner.os }}-
- name: Create NEventStore database once SQL Server is healthy
if: ${{ matrix.db == 'mssql' }}
shell: bash
run: |
set -euo pipefail
# Find the SQL Server service container by ancestor image
container_id="$(/usr/bin/docker ps --filter "ancestor=${{ matrix.image }}" --format "{{.ID}}" | head -n 1)" || true
# Fallback to searching by name "database"
if [ -z "$container_id" ]; then
container_id="$(/usr/bin/docker ps --filter "name=database" --format "{{.ID}}" | head -n 1)" || true
fi
if [ -z "$container_id" ]; then
echo "SQL Server service container not found."
/usr/bin/docker ps --format "{{.ID}} {{.Image}} {{.Names}}" || true
exit 1
fi
# Wait until container reports healthy
for i in $(seq 1 60); do
health_status="$(/usr/bin/docker inspect --format='{{.State.Health.Status}}' "$container_id" 2>/dev/null || echo starting)"
if [ "$health_status" = "healthy" ]; then
echo "SQL Server container healthy"
break
fi
echo "Waiting for SQL Server to be healthy: $health_status ($i/60)"
sleep 2
done
if [ "$health_status" != "healthy" ]; then
echo "SQL Server container is not healthy. Logs follow (last 200 lines):"
/usr/bin/docker logs "$container_id" --tail 200 || true
exit 1
fi
# Create database using sqlcmd inside the container
/usr/bin/docker exec "$container_id" /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P "Password12!" -Q "IF DB_ID(N'NEventStore') IS NULL CREATE DATABASE [NEventStore];"
- name: Run tests for all TFMs
shell: bash
run: |
set -euo pipefail
for tfm in $TFMS; do
echo "Running tests for $tfm"
dotnet test ./src/NEventStore.Persistence.Sql.Core.sln -c Release -f $tfm --logger "trx;LogFileName=test-results-${tfm}.trx" || exit 1
done
- name: Upload test results
uses: actions/upload-artifact@v7
with:
name: test-results-${{ matrix.db }}
path: "**/test-results-*.trx"
if-no-files-found: ignore
retention-days: 14