forked from bittercoder/Migrator.NET
-
-
Notifications
You must be signed in to change notification settings - Fork 12
Expand live database integration coverage #171
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
9bfbe09
Add isolated live database CI jobs and migration contract tests
jogibear9988 19e904d
Complete Db2 and Informix migration operations and correct database t…
jogibear9988 c07fbb7
Configure native Linux drivers and enforce shell startup failures
jogibear9988 a53512f
Fix Firebird column removal and retry transient IBM image downloads
jogibear9988 c913b89
Add public Sybase ASE live coverage and native provider operations
jogibear9988 d12e0a0
Preserve Db2 schema across migration transactions and enforce key nul…
jogibear9988 692710d
Fix native readiness and default changes; audit complete test assignment
jogibear9988 dcdc854
Allocate ASE test storage and use Informix server-only administration
jogibear9988 7f7ad24
Address metadata review findings with live regression tests and fix A…
jogibear9988 90bdad4
Use Firebird database API and enable logged ASE column alterations
jogibear9988 8dd90ac
Preserve inline index flags and ASE constraint-owned index metadata
jogibear9988 2eca98d
Create flagged column indexes using native standalone statements
jogibear9988 848800d
Separate ASE data and log allocations for logged schema changes
jogibear9988 cebced2
Identify and remove Informix constraint-owned indexes correctly
jogibear9988 bfef305
Place ASE test data and logs on dedicated disposable devices
jogibear9988 d38670f
Round-trip Firebird defaults and expose MySQL unique constraint metadata
jogibear9988 3aa4678
Preserve numeric shapes, key membership and typed catalog values
jogibear9988 bd511f5
Register numeric parameters correctly and bind binary data values
jogibear9988 a9cdb7e
Fix Informix length and Firebird native type metadata round trips
jogibear9988 d622aa9
Honor unique column changes and preserve Informix character storage
jogibear9988 f384c79
Correct live regression fixtures for Informix row limits and ASE exce…
jogibear9988 5eb07ee
Wait for Informix catalog initialization before live tests
jogibear9988 1cf71f1
Preserve ASE large-text capacity and decode Informix catalog defaults
jogibear9988 ca2fa58
Normalize Informix boolean literal wrappers before typed parsing
jogibear9988 f7e77fb
Remove Informix catalog literal terminators without trimming user text
jogibear9988 fafe3d4
Match Informix CURRENT defaults to the recreated timestamp precision
jogibear9988 8c0743b
Verify persisted Informix whitespace independently of driver trimming
jogibear9988 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| *.sh text eol=lf |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| #!/usr/bin/env bash | ||
| set -euo pipefail | ||
| database="$1" | ||
| # Registry timeouts are transient; retry downloads, never test failures. | ||
| pull() { | ||
| for attempt in 1 2 3; do | ||
| if docker pull "$1"; then return 0; fi | ||
| sleep 5 | ||
| done | ||
| return 1 | ||
| } | ||
| case "$database" in | ||
| Unit|SQLite) exit 0 ;; | ||
| MySQL) | ||
| docker run -d --name migrator-db -p 3306:3306 -e MYSQL_ROOT_PASSWORD=rootpass -e MYSQL_DATABASE=testdb -e MYSQL_USER=testuser -e MYSQL_PASSWORD=testpass mysql:8.0.44 | ||
| ready() { docker exec migrator-db mysql -uroot -prootpass -e 'SELECT 1' >/dev/null 2>&1; } | ||
| ;; | ||
| MariaDB) | ||
| docker run -d --name migrator-db -p 3306:3306 -e MARIADB_ROOT_PASSWORD=rootpass -e MARIADB_DATABASE=testdb mariadb:11.4.10 | ||
| ready() { docker exec migrator-db mariadb -uroot -prootpass -e 'SELECT 1' >/dev/null 2>&1; } | ||
| ;; | ||
| PostgreSQL) | ||
| docker run -d --name migrator-db -p 5432:5432 -e POSTGRES_USER=testuser -e POSTGRES_PASSWORD=testpass postgres:13.23 | ||
| ready() { docker exec migrator-db pg_isready -U testuser >/dev/null 2>&1; } | ||
| ;; | ||
| SQLServer) | ||
| docker run -d --name migrator-db -p 1433:1433 -e ACCEPT_EULA=Y -e MSSQL_SA_PASSWORD=YourStrong@Passw0rd mcr.microsoft.com/mssql/server:2019-CU32-ubuntu-20.04 | ||
| ready() { docker exec migrator-db /opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa -P 'YourStrong@Passw0rd' -Q 'SELECT 1' >/dev/null 2>&1; } | ||
| ;; | ||
| Oracle) | ||
| docker run -d --name migrator-db -p 1521:1521 -e ORACLE_PASSWORD=adfkweflajdfglkj gvenzl/oracle-free:23.9-slim-faststart | ||
| ready() { docker exec migrator-db healthcheck.sh >/dev/null 2>&1; } | ||
| ;; | ||
| Firebird) | ||
| docker run -d --name migrator-db -p 3050:3050 -e FIREBIRD_ROOT_PASSWORD=masterkey -e FIREBIRD_DATABASE=test.fdb firebirdsql/firebird:5.0.3 | ||
| ready() { echo 'select 1 from rdb$database;' | docker exec -i migrator-db isql -b -u SYSDBA -p masterkey localhost:/var/lib/firebird/data/test.fdb >/dev/null 2>&1; } | ||
| ;; | ||
| Db2) | ||
| pull icr.io/db2_community/db2:11.5.9.0 | ||
| docker run -d --name migrator-db --privileged -p 50000:50000 -e LICENSE=accept -e DB2INST1_PASSWORD=testpass -e DBNAME=testdb -e ARCHIVE_LOGS=false -e AUTOCONFIG=false icr.io/db2_community/db2:11.5.9.0 | ||
| ready() { docker logs migrator-db 2>&1 | grep -q 'Setup has completed'; } | ||
| ;; | ||
| Informix) | ||
| pull icr.io/informix/informix-developer-database:15.0.1.0.3 | ||
| docker run -dt --name migrator-db --hostname ifx --privileged -p 9088:9088 -e LICENSE=accept icr.io/informix/informix-developer-database:15.0.1.0.3 | ||
| ready() { docker exec migrator-db bash -c 'source /usr/local/bin/informix_inf.env; onstat - >/dev/null; test $? -eq 5' >/dev/null 2>&1; } | ||
| ;; | ||
| Sybase) | ||
| pull datagrip/sybase:16.0 | ||
| docker run -dt --name migrator-db -p 5000:5000 datagrip/sybase:16.0 | ||
| ready() { printf 'select 12345\ngo\n' | docker exec -i migrator-db bash -c 'source /opt/sybase/SYBASE.sh; isql -Usa -PmyPassword -Slocalhost:5000' 2>/dev/null | grep -q 12345; } | ||
| ;; | ||
| *) echo "Unknown database: $database" >&2; exit 1 ;; | ||
| esac | ||
| for attempt in $(seq 1 120); do | ||
| if ready; then break; fi | ||
| if [ "$attempt" -eq 120 ]; then docker logs migrator-db; exit 1; fi | ||
| sleep 5 | ||
| done | ||
| case "$database" in | ||
| Sybase) | ||
| printf "disk init name='migrator_data', physname='/opt/sybase/migrator_data.dat', size='128M'\ngo\ndisk init name='migrator_log', physname='/opt/sybase/migrator_log.dat', size='64M'\ngo\n" | docker exec -i migrator-db bash -c 'source /opt/sybase/SYBASE.sh; isql -b -Usa -PmyPassword -Slocalhost:5000' | ||
| ;; | ||
| SQLServer) docker exec migrator-db /opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa -P 'YourStrong@Passw0rd' -b -Q 'CREATE DATABASE [Whatever];' ;; | ||
| Oracle) docker exec -i migrator-db sqlplus -s / as sysdba < .github/workflows/sql/oracle.sql ;; | ||
| Informix) | ||
| # ONLINE can precede completion of the image's catalog initialization. | ||
| for attempt in $(seq 1 24); do | ||
| if echo 'create database testdb with log;' | docker exec -i migrator-db bash -c 'source /usr/local/bin/informix_inf.env; dbaccess sysmaster -'; then break; fi | ||
| if [ "$attempt" -eq 24 ]; then docker logs migrator-db; exit 1; fi | ||
| sleep 5 | ||
| done | ||
| ;; | ||
| esac |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| param( | ||
| [ValidateSet('Unit','SQLite','SQLServer','PostgreSQL','Oracle','MySQL','MariaDB','Firebird','Db2','Informix','Sybase')] | ||
| [string]$Database = 'Unit' | ||
| ) | ||
| $ErrorActionPreference = 'Stop' | ||
| $databases = @('SQLite','SQLServer','PostgreSQL','Oracle','MySQL','MariaDB','Firebird','Db2','Informix','Sybase') | ||
| $filter = if ($Database -eq 'Unit') { ($databases | ForEach-Object { "TestCategory!=$_" }) -join '&' } else { "TestCategory=$Database" } | ||
| $xmlDirectory = Join-Path (Get-Location) "TestResults/$Database" | ||
| dotnet test Migrator.slnx --no-build --filter $filter --logger "trx;LogFileName=$Database.trx" --results-directory TestResults -- NUnit.NumberOfTestWorkers=0 "NUnit.TestOutputXml=$xmlDirectory" | ||
| if ($LASTEXITCODE -ne 0) { throw "Tests failed for $Database" } | ||
| [xml]$results = Get-Content "TestResults/$Database.trx" | ||
| $counters = $results.TestRun.ResultSummary.Counters | ||
| if ([int]$counters.executed -eq 0) { throw "No tests executed for $Database" } | ||
| if ([int]$counters.failed -gt 0) { throw "Failures in $Database results" } | ||
| $skipped = @($results.TestRun.Results.UnitTestResult | Where-Object outcome -eq NotExecuted) | ||
| Write-Host "$Database : $($counters.passed) passed, $($skipped.Count) skipped" | ||
| foreach ($test in $skipped) { Write-Host "Skipped: $($test.testName) $($test.Output.ErrorInfo.Message)" } | ||
| if ($Database -in @('MySQL','MariaDB','Firebird','Db2','Informix','Sybase') -and $skipped.Count -gt 0) { | ||
| throw "New database suites must not skip tests." | ||
| } |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| """Ensure database filters form a complete, disjoint partition of NUnit discovery.""" | ||
| import pathlib | ||
| import sys | ||
| import xml.etree.ElementTree as ET | ||
|
|
||
| expected = {"Unit", "SQLite", "SQLServer", "PostgreSQL", "Oracle", "MySQL", | ||
| "MariaDB", "Firebird", "Db2", "Informix", "Sybase"} | ||
| seen = {} | ||
| executed = 0 | ||
| counts = set() | ||
| found = set() | ||
| for path in pathlib.Path(sys.argv[1]).rglob("Migrator.Tests.xml"): | ||
| database = path.parent.name | ||
| if database not in expected: | ||
| raise SystemExit(f"Unexpected result file: {path}") | ||
| if database in found: | ||
| raise SystemExit(f"Duplicate suite: {database}") | ||
| found.add(database) | ||
| root = ET.parse(path).getroot() | ||
| counts.add(int(root.attrib["testcasecount"])) | ||
| tests = root.findall(".//test-case") | ||
| if not tests or not any(t.get("result") == "Passed" for t in tests): | ||
| raise SystemExit(f"No passing tests: {database}") | ||
| executed += len(tests) | ||
| for test in tests: | ||
| name = test.attrib["fullname"] | ||
| # NUnit can discover inherited or repeated cases with identical full names. | ||
| # They are valid within one job, but must never appear in another job. | ||
| if name in seen and seen[name] != database: | ||
| raise SystemExit(f"Test assigned to both {seen[name]} and {database}: {name}") | ||
| seen[name] = database | ||
| if test.get("result") not in {"Passed", "Skipped"}: | ||
| raise SystemExit(f"Test did not pass: {name}") | ||
| print(f"{database}: {len(tests)} tests") | ||
|
|
||
| if found != expected: | ||
| raise SystemExit(f"Missing suites: {sorted(expected - found)}") | ||
| if len(counts) != 1 or executed != next(iter(counts)): | ||
| raise SystemExit(f"Discovery reports {counts} tests but jobs covered {executed} test cases") | ||
| print(f"All {executed} discovered tests assigned exactly once.") | ||
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,108 +1,84 @@ | ||
| name: .NET Pull Request | ||
|
|
||
| on: | ||
| push: | ||
| branches: [master] | ||
| pull_request: | ||
| branches: [master] | ||
| workflow_dispatch: | ||
| permissions: | ||
| contents: read | ||
| concurrency: | ||
| group: live-databases-${{ github.ref }} | ||
| cancel-in-progress: true | ||
| jobs: | ||
| build: | ||
| test: | ||
| name: Test (${{ matrix.database }}) | ||
| runs-on: ubuntu-22.04 | ||
| services: | ||
| sqlserver: | ||
| image: mcr.microsoft.com/mssql/server:2019-latest | ||
| ports: | ||
| - 1433:1433 | ||
| env: | ||
| SA_PASSWORD: YourStrong@Passw0rd | ||
| ACCEPT_EULA: Y | ||
| options: >- | ||
| --health-cmd "bash -c '</dev/tcp/localhost/1433' && exit 0 || exit 1" | ||
| --health-interval=10s | ||
| --health-timeout=5s | ||
| --health-retries=10 | ||
| postgres: | ||
| image: postgres:13 | ||
| ports: | ||
| - 5432:5432 | ||
| env: | ||
| POSTGRES_USER: testuser | ||
| POSTGRES_PASSWORD: testpass | ||
| # As of v16 we can use: | ||
| # POSTGRES_INITDB_ARGS: "-c max_connections=300" | ||
| options: >- | ||
| --health-cmd="pg_isready -U testuser" | ||
| --health-interval=10s | ||
| --health-timeout=5s | ||
| --health-retries=5 | ||
| oracle: | ||
| image: gvenzl/oracle-free:latest | ||
| ports: | ||
| - 1521:1521 | ||
| env: | ||
| ORACLE_PASSWORD: adfkweflajdfglkj | ||
| options: >- | ||
| --health-cmd healthcheck.sh | ||
| --health-interval 10s | ||
| --health-timeout 5s | ||
| --health-retries 10 | ||
| mysql: | ||
| image: mysql:8.0 | ||
| ports: | ||
| - 3306:3306 | ||
| env: | ||
| MYSQL_ROOT_PASSWORD: rootpass | ||
| MYSQL_DATABASE: testdb | ||
| MYSQL_USER: testuser | ||
| MYSQL_PASSWORD: testpass | ||
| options: >- | ||
| --health-cmd="mysqladmin ping -h localhost -u root -prootpass" | ||
| --health-interval=10s | ||
| --health-timeout=5s | ||
| --health-retries=10 | ||
| timeout-minutes: 35 | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| database: [Unit, SQLite, SQLServer, PostgreSQL, Oracle, MySQL, MariaDB, Firebird, Db2, Informix, Sybase] | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: gvenzl/setup-oracle-sqlcl@v1 | ||
| - name: Setup .NET | ||
| uses: actions/setup-dotnet@v4 | ||
| - uses: actions/setup-dotnet@v4 | ||
| with: | ||
| dotnet-version: | | ||
| 9.0.x | ||
| - name: Install Microsoft GPG apt-key | ||
| dotnet-version: 9.0.x | ||
| - name: Start database | ||
| shell: bash | ||
| run: | | ||
| wget https://packages.microsoft.com/keys/microsoft.asc -O microsoft.asc | ||
| gpg --dearmor microsoft.asc | ||
| chmod 644 microsoft.asc.gpg | ||
| sudo mv microsoft.asc.gpg /etc/apt/trusted.gpg.d/microsoft.gpg | ||
| - name: Add Microsoft SQL Server repo | ||
| run: | | ||
| echo "deb [arch=amd64] https://packages.microsoft.com/config/ubuntu/22.04/prod jammy main" | ||
| sudo apt-get update | ||
| - name: Install SQLCMD tools | ||
| run: | | ||
| sudo ACCEPT_EULA=Y apt-get install -y mssql-tools unixodbc-dev | ||
| echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> ~/.bashrc | ||
| source ~/.bashrc | ||
| - name: Create SQLServer database | ||
| run: | | ||
| /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P 'YourStrong@Passw0rd' -Q "CREATE DATABASE [Whatever];" | ||
| - name: Create Oracle user | ||
| run: | | ||
| sql sys/adfkweflajdfglkj@localhost/FREEPDB1 as sysdba <<EOF | ||
| WHENEVER SQLERROR EXIT SQL.SQLCODE | ||
| SET ECHO ON | ||
| SET FEEDBACK ON | ||
| SET SERVEROUTPUT ON | ||
| @.github/workflows/sql/oracle.sql | ||
| EXIT; | ||
| EOF | ||
|
|
||
| - name: Restore dependencies | ||
| run: | | ||
| dotnet restore Migrator.slnx | ||
| mkdir -p TestResults | ||
| bash .github/scripts/start-database.sh "${{ matrix.database }}" 2>&1 | tee TestResults/startup.log | ||
| timeout-minutes: 15 | ||
| - name: Build | ||
| run: dotnet build Migrator.slnx -p:LiveDatabase=${{ matrix.database }} | ||
| - name: Configure native IBM drivers | ||
| if: matrix.database == 'Db2' || matrix.database == 'Informix' | ||
| shell: bash | ||
| run: | | ||
| dotnet build Migrator.slnx | ||
| sudo apt-get update | ||
| sudo apt-get install -y libaio1 libxml2 unixodbc libncurses5 | ||
| output="$GITHUB_WORKSPACE/src/Migrator.Tests/bin/Debug/net9.0" | ||
| if [ "${{ matrix.database }}" = Db2 ]; then | ||
| echo "DB2_CLI_DRIVER_INSTALL_PATH=$output/clidriver" >> "$GITHUB_ENV" | ||
| echo "LD_LIBRARY_PATH=$output/clidriver/lib" >> "$GITHUB_ENV" | ||
| else | ||
| echo "DELIMIDENT=y" >> "$GITHUB_ENV" | ||
| echo "INFORMIXDIR=$output/native" >> "$GITHUB_ENV" | ||
| echo "LD_LIBRARY_PATH=$output/native/lib:$output/native/lib/cli:$output/native/lib/esql" >> "$GITHUB_ENV" | ||
| fi | ||
| - name: Test | ||
| shell: pwsh | ||
| run: ./.github/scripts/test.ps1 -Database ${{ matrix.database }} | ||
|
jogibear9988 marked this conversation as resolved.
|
||
| - name: Collect database logs | ||
| if: always() | ||
| run: | | ||
| dotnet test Migrator.slnx | ||
| mkdir -p TestResults | ||
| if docker inspect migrator-db >/dev/null 2>&1; then | ||
| docker logs migrator-db > TestResults/database.log 2>&1 | ||
| docker inspect migrator-db > TestResults/container.json | ||
| fi | ||
| - uses: actions/upload-artifact@v4 | ||
| if: always() | ||
| with: | ||
| name: test-results-${{ matrix.database }} | ||
| path: TestResults/ | ||
| if-no-files-found: error | ||
| - name: Remove test container | ||
| if: always() | ||
| run: | | ||
| if docker inspect migrator-db >/dev/null 2>&1; then | ||
| docker rm -fv migrator-db | ||
| fi | ||
| coverage: | ||
| name: Verify complete test coverage | ||
| needs: test | ||
| runs-on: ubuntu-22.04 | ||
| timeout-minutes: 5 | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: actions/download-artifact@v4 | ||
| with: | ||
| pattern: test-results-* | ||
| path: TestResults | ||
| - run: python3 .github/scripts/verify-test-coverage.py TestResults | ||
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
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
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.