Files
Christoph Schwörer f045b99a25 Codebasis als Dateien ins Arbeitsrepo statt als Gitlink
QuellCode/CentronERP war nur als Gitlink (Submodul-Referenz auf 79c1142)
getrackt, ohne .gitmodules und ohne erreichbares Remote. Der
Untersuchungsgegenstand der Versuchsreihe war damit nicht reproduzierbar
gesichert: Ein Klon haette ein leeres Verzeichnis erhalten, und die Belege
der 3.287 Anforderungen waeren nicht ueberpruefbar gewesen.

Umstellung:
- Historie nach c:\DEV\CentronERP_git_snapshot_79c1142 ausgelagert
  (vollstaendig lesbar, enthaelt 79c1142 und Vorgaenger 89ccfd6)
- Gitlink aus dem Index entfernt
- Dateiinhalt aufgenommen: 24.557 Dateien, rund 333 MB

Die verschachtelte .gitignore der Codebasis gilt weiter, Build-Artefakte
bleiben ausgeschlossen. Details in Versuche/Versuch_01/_Codebasis-Nachweis.md
2026-08-26 07:43:51 +02:00

270 lines
9.7 KiB
YAML

name: Regression tests
on:
workflow_dispatch:
pull_request:
branches:
- main
- 'release/**'
push:
branches:
- main
- 'release/**'
permissions:
contents: read
# Supersede in-flight runs of this workflow for the same pull request. Pushes to main and
# release branches are excluded so every commit there still gets a full, recorded result.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
regression-tests:
name: End-to-end regression tests
runs-on: ubuntu-24.04
timeout-minutes: 180
env:
DOTNET_NOLOGO: true
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
DevExpress_License: ${{ secrets.DEVEXPRESS_LICENSE }}
ACR_USERNAME: ${{ secrets.ACR_USERNAME }}
ACR_PASSWORD: ${{ secrets.ACR_PASSWORD }}
DB_USERNAME: sa
DB_PASSWORD: SA!password
DB_PORT: 1433
steps:
- name: Check out repository
uses: actions/checkout@v7
with:
fetch-depth: 0
# Verifier.OverrideExpectedFiles = true rewrites the expected files instead of comparing
# against them, so a test that keeps the flag silently verifies nothing. It is only meant
# to be set temporarily while regenerating snapshots and must never be committed.
- name: Check for committed OverrideExpectedFiles
shell: pwsh
run: |
$violations = foreach ($file in Get-ChildItem -Path ./tests -Recurse -Filter *.cs) {
$segments = $file.FullName -split '[\\/]'
if ($segments -contains 'bin' -or $segments -contains 'obj') { continue }
$text = Get-Content -LiteralPath $file.FullName -Raw
if ([string]::IsNullOrEmpty($text)) { continue }
# Blank out block comments, keeping newlines so reported line numbers stay correct.
$text = [regex]::Replace($text, '(?s)/\*.*?\*/', { param($m) $m.Value -replace '[^\r\n]', '' })
$lineNumber = 0
foreach ($line in $text -split '\r?\n') {
$lineNumber++
# The assignment must start the line, optionally behind a dotted receiver such as
# this.Verifier. - anything else in front (other code, // or /// ) means it is not a
# statement. Deliberately not stripping // comments: that would also cut a real
# assignment that follows a string containing '//', turning a false positive into a
# false negative.
if ($line -match '^\s*(?:[A-Za-z_][A-Za-z0-9_.]*\.)?OverrideExpectedFiles\s*=\s*true') {
[pscustomobject]@{ Path = $file.FullName; Line = $lineNumber }
}
}
}
if ($violations) {
foreach ($violation in $violations) {
Write-Host "::error file=$($violation.Path),line=$($violation.Line)::Remove 'OverrideExpectedFiles = true' before committing - the test compares nothing while it is set."
}
throw "Found $(@($violations).Count) committed 'OverrideExpectedFiles = true' assignment(s). Regenerate the expected files locally, then remove the flag."
}
Write-Host 'No committed OverrideExpectedFiles assignments found.'
- name: Set up .NET SDK
uses: actions/setup-dotnet@v5
with:
dotnet-version: 10.0.x
- name: Verify test environment
shell: pwsh
run: |
$sdkVersion = dotnet --version
if ($LASTEXITCODE -ne 0 -or $sdkVersion -notmatch '^10\.0\.') {
throw "Expected a .NET 10 SDK, but resolved '$sdkVersion'."
}
docker version
if ($LASTEXITCODE -ne 0) {
throw 'Docker is unavailable on the self-hosted runner.'
}
- name: Sign in to Azure Container Registry
shell: pwsh
run: |
if ([string]::IsNullOrWhiteSpace($env:ACR_USERNAME) -or
[string]::IsNullOrWhiteSpace($env:ACR_PASSWORD)) {
throw 'ACR_USERNAME and ACR_PASSWORD secrets are required on GitHub-hosted runners.'
}
$env:ACR_PASSWORD | docker login centron.azurecr.io --username $env:ACR_USERNAME --password-stdin
if ($LASTEXITCODE -ne 0) {
throw 'Azure Container Registry login failed.'
}
# Superseded runs are now cancelled mid-test. "Stop regression database" below uses
# if: always() and therefore still runs on cancellation, but a hard runner failure can
# leave a container behind. Drop anything older than the job timeout so leftovers cannot
# pile up on the self-hosted runner. Younger containers may belong to a concurrent run of
# another pull request and are left alone.
- name: Remove stale regression containers
continue-on-error: true
shell: pwsh
run: |
$cutoff = (Get-Date).ToUniversalTime().AddHours(-4)
# The docker name filter is a regex over a substring, so an unanchored pattern would
# also match something like backup-centron-regression-db. '^/?' anchors it and works
# whether the daemon matches the bare name or the internal '/name'.
foreach ($id in @(docker ps --all --quiet --filter 'name=^/?centron-regression-')) {
if ([string]::IsNullOrWhiteSpace($id)) { continue }
$parts = (docker inspect --format '{{.Name}}|{{.Created}}' $id) -split '\|', 2
if ($parts.Count -ne 2) { continue }
$name = $parts[0].TrimStart('/')
$created = $parts[1]
# Second guard: this force-removes containers, so never act on a name that does not
# actually carry the prefix, whatever the daemon's filter semantics happen to be.
if (-not $name.StartsWith('centron-regression-')) { continue }
$parsed = [datetime]::MinValue
$isParsed = [datetime]::TryParse(
$created,
[cultureinfo]::InvariantCulture,
[System.Globalization.DateTimeStyles]::AdjustToUniversal,
[ref] $parsed)
if ($isParsed -and $parsed -lt $cutoff) {
Write-Host "Removing stale regression container $name created at $created."
docker rm --force $id | Out-Null
}
}
$global:LASTEXITCODE = 0
- name: Start regression database
shell: pwsh
run: |
$containerName = "centron-regression-$env:GITHUB_RUN_ID-$env:GITHUB_RUN_ATTEMPT".ToLowerInvariant()
"DB_CONTAINER_NAME=$containerName" >> $env:GITHUB_ENV
docker pull centron.azurecr.io/centron_db/regression_tests:latest
if ($LASTEXITCODE -ne 0) {
throw 'Could not pull the regression database image.'
}
docker run --detach `
--name $containerName `
--env "MSSQL_SA_PASSWORD=$env:DB_PASSWORD" `
--env ACCEPT_EULA=Y `
--env MSSQL_PID=Standard `
--publish "${env:DB_PORT}:1433" `
centron.azurecr.io/centron_db/regression_tests:latest | Out-Null
if ($LASTEXITCODE -ne 0) {
throw 'Could not start the regression database container.'
}
- name: Wait for regression database
shell: pwsh
run: |
for ($attempt = 1; $attempt -le 120; $attempt++) {
$client = [Net.Sockets.TcpClient]::new()
try {
$connected = $client.ConnectAsync('localhost', [int]$env:DB_PORT).Wait(1000)
if ($connected -and $client.Connected) {
Write-Host 'Regression database is reachable.'
exit 0
}
}
catch {
# Database is still starting.
}
finally {
$client.Dispose()
}
Start-Sleep -Seconds 2
}
throw 'Regression database did not become reachable within four minutes.'
- name: Build regression tests
shell: pwsh
run: |
dotnet build `
"./tests/Centron.Tests.EndToEnd/Centron.Tests.EndToEnd.csproj" `
--configuration Release `
--framework net10.0 `
-nodeReuse:false
- name: Run regression tests
shell: pwsh
env:
CENTRON_TESTS_DATABASE_SERVER: localhost,1433
CENTRON_TESTS_DATABASE_USERNAME: sa
CENTRON_TESTS_DATABASE_PASSWORD: SA!password
DATABASE_BACKUP_PATH: /var/opt/mssql/backup/DatabaseBackup.bak
run: |
dotnet test `
"./tests/Centron.Tests.EndToEnd/Centron.Tests.EndToEnd.csproj" `
--configuration Release `
--framework net10.0 `
--no-build `
--logger "trx;LogFileName=TestResults.trx" `
--results-directory "./artifacts/EndToEndTests" `
-nodeReuse:false
- name: Capture database logs
if: always()
continue-on-error: true
shell: pwsh
run: |
New-Item -ItemType Directory -Force -Path './artifacts/EndToEndTests' | Out-Null
docker logs $env:DB_CONTAINER_NAME *>&1 |
Set-Content -LiteralPath './artifacts/EndToEndTests/database.log'
- name: Stop regression database
if: always()
continue-on-error: true
shell: pwsh
run: |
if (-not [string]::IsNullOrWhiteSpace($env:DB_CONTAINER_NAME)) {
docker rm --force $env:DB_CONTAINER_NAME 2>$null
}
$global:LASTEXITCODE = 0
- name: Upload regression results
if: always()
uses: actions/upload-artifact@v7
with:
name: regression-test-results
path: artifacts/EndToEndTests/
if-no-files-found: warn
retention-days: 14