diff --git a/.github/workflows/build_and_test_on_spawn.yml b/.github/workflows/build_and_test_on_spawn.yml deleted file mode 100644 index c87d6fa45..000000000 --- a/.github/workflows/build_and_test_on_spawn.yml +++ /dev/null @@ -1,392 +0,0 @@ -# Builds and tests a database project (in this case tSQLt) -# -# Originally from: https://github.com/lizbaron/db-cicd-with-github-actions/blob/9a514ed0c41029daa231db22e30de7806636160c/.github/workflows/build_and_test_on_linux.yml - -name: Build and Test tSQLt on Spawn - -on: - push: - branches: - - main - workflow_dispatch: - inputs: - spawnDataImages: - description: 'names of the data-images on spawn (e.g. all is currently equivalent to [["SQL2017","mssql-empty:v2017"],["SQL2019","mssql-empty:v2019"]] 2021-09-26)' - required: true - default: 'all' - -env: - MATRIX_NAME: 0 - MATRIX_SPAWNIMAGE: 1 - SPAWNDATAIMAGES_DEFAULT: - '[["SQL2017","mssql-empty:v2017"],["SQL2019","mssql-empty:v2019"]]' - SQLPACKAGE_HOME: 'C:\Program Files\Microsoft SQL Server\160\DAC\bin\' - -jobs: - - set-up-env-vars: - name: set up matrix variables - #if: ${{ always() }} - - # More types found here, https://docs.github.com/en/free-pro-team@latest/actions/reference/specifications-for-github-hosted-runners - runs-on: windows-latest - - env: - TSQLT_ARTIFACT_DIR: ${{ github.workspace }}\tSQLt\Build\output\tSQLt - - steps: - - - name: create variables - id: create-vars - shell: pwsh - run: | - $spawnDataImages = '${{ github.event.inputs.spawnDataImages }}'; - Write-Host "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; - Write-Host ""; - Write-Host ("input spawnDataImages: {0}" -f $spawnDataImages); - if ([string]::IsNullOrEmpty($spawnDataImages) -or ("all" -eq $spawnDataImages)) { - $spawnDataImages = '${{ env.spawndataimages_default }}'; - } - Write-Host ("final spawnDataImages value: {0}" -f $spawnDataImages); - $matrixValues = '{{"data-image":{0} }}' -f $spawnDataImages; - Write-Host ("matrixValues value: {0}" -f $matrixValues); - Write-Host ""; - Write-Host "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; - - Write-Host ('::set-output name=matrixValues::' + $matrixValues); - - outputs: - matrixValues: ${{ steps.create-vars.outputs.matrixValues }} - - -# 💖 💖 💖 💖 💖 💖 💖 💖 💖 💖 💖 💖 💖 💖 - build-tSQLt: - name: build tSQLt - #if: ${{ false }} - - runs-on: windows-latest - - env: - PFX_PATH: ${{ github.workspace }}\tSQLt\Build\tSQLtOfficialSigningKey.pfx - ARTIFACT_DIR: ${{ github.workspace }}\artifacts - MSBUILD_HOME: C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin - - steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it. - # But this is misleading, because this variable is only really helpful as ${{ github.workspace }} - - name: Checkout tSQLt - uses: actions/checkout@v2 - with: - path: tSQLt - - # documentation: This is just what you need to do... - - name: create and install unofficial pfx file for signing dlls - shell: pwsh - run: | - $superSecurePassword = "ExtraSuperSecurePassword1!"; - $cert = New-SelfSignedCertificate -FriendlyName "tSQLt_testKey" -Type CodeSigningCert -Subject "CN=localhost" -KeyAlgorithm RSA -HashAlgorithm SHA1 -KeyLength 2048 -SmimeCapabilities -CertStoreLocation "cert:/LocalMachine" -Provider "Microsoft Strong Cryptographic Provider" - $pfxFile = Export-PfxCertificate -cert ("Cert:\LocalMachine\My\" + $cert.thumbprint) -FilePath $env:PFX_PATH -Password (ConvertTo-SecureString -String $superSecurePassword -Force -AsPlainText) - &"${{ github.workspace }}\tSQLt\Build\SnInstallPfx" "$env:PFX_PATH" $superSecurePassword tSQLt_OfficialSigningKey - - - name: ant Build\tSQLt.buildCLR.xml --> Build/output/CLRBuild/ - run: ant -buildfile "${{ github.workspace }}\tSQLt\Build\tSQLt.buildCLR.xml" -D"msbuild.path"="$env:MSBUILD_HOME" all - - - name: ant Build/tSQLt.build.xml --> Build/output/tSQLtBuild - run: ant -buildfile "${{ github.workspace }}\tSQLt\Build\tSQLt.build.xml" all -D"commit.id"="${{ github.sha }}" - - - name: copy tSQLt artifacts to ${{ env.artifact_dir }} - shell: pwsh - run: | - New-Item -ItemType Directory -Path "$env:ARTIFACT_DIR" - $basePath = "${{ github.workspace }}\tSQLt\Build\output\tSQLtBuild\"; - $artifactFiles = @("ReadMe.txt","CommitId.txt","CreateBuildLog.sql","GetFriendlySQLServerVersion.sql","tSQLt.tests.zip","tSQLtFacade.zip","tSQLtFiles.zip","tSQLtSnippets(SQLPrompt).zip","Version.txt"); - Get-ChildItem -Path ($basePath + "*") -Include $artifactFiles | Copy-Item -Destination "$env:ARTIFACT_DIR"; - - - name: Archive production artifact - uses: actions/upload-artifact@v2 - with: - if-no-files-found: error - name: tSQLtBuildArtifact - path: "${{ env.artifact_dir }}\\" - retention-days: 5 - - -# 💖 💖 💖 💖 💖 💖 💖 💖 💖 💖 💖 💖 💖 💖 - build-facades: - name: build facade dacpacs - needs: [build-tSQLt,set-up-env-vars] - #if: ${{ always() }} - - env: - DACPAC_ARTIFACT_DIR: ${{ github.workspace }}\tSQLt\Build\output\DacpacBuild - - strategy: - matrix: ${{ fromJson(needs.set-up-env-vars.outputs.matrixValues) }} - fail-fast: true - - # More types found here, https://docs.github.com/en/free-pro-team@latest/actions/reference/specifications-for-github-hosted-runners - runs-on: windows-latest - - steps: - - name: Checkout tSQLt - uses: actions/checkout@v2 - with: - path: tSQLt - - - name: load namespace variables into env - id: create-vars - shell: pwsh - run: | - $sqlcmdPath = (Get-Command sqlcmd).Path.TrimEnd("\\SQLCMD.EXE"); - Write-Host ('::set-output name=sqlcmdPath::'+$sqlcmdPath); - - - name: Install spawnctl - shell: pwsh - run: | - echo "Downloading and installing spawnctl..." - Invoke-WebRequest -Uri "https://run.spawn.cc/spawnctl.exe" -UseBasicParsing -OutFile "${{ github.workspace }}\spawnctl.exe" - & "${{ github.workspace }}\spawnctl.exe" version - - - name: Create Container - id: create-container - shell: pwsh - run: | - & "${{ github.workspace }}\spawnctl.exe" get data-images - - $dataImageName = "${{ matrix.data-image[ env.MATRIX_SPAWNIMAGE ] }}"; - - echo "Creating 'tSQLtOnLinux' Spawn data container from image '$dataImageName'"; - $containerName= & "${{ github.workspace }}\spawnctl.exe" create data-container --image $dataImageName --lifetime 30m -q - $containerJSON= & "${{ github.workspace }}\spawnctl.exe" get data-container $containerName -o json - $containerInfo = ConvertFrom-JSON -inputObject $containerJSON; - $containerFQDN=$containerInfo.host+", "+$containerInfo.port; - $containerUser=$containerInfo.user; - $containerPassword=$containerInfo.password; - echo "Successfully created Spawn data container '$containerName'" - - Write-Host ('::set-output name=containerFQDN::'+$containerFQDN); - Write-Host ('::set-output name=containerUser::'+$containerUser); - Write-Host ('::set-output name=containerPassword::'+$containerPassword); - - env: - SPAWNCTL_ACCESS_TOKEN: ${{ secrets.SPAWNCTL_ACCESS_TOKEN }} - - - name: print sql version info - shell: pwsh - run: | - Write-Host "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; - Write-Host ""; - # Write-Host Invoke-Sqlcmd -ConnectionTimeout 600 -Query "SELECT SUSER_NAME() U,SYSDATETIME() T,@@VERSION V;" -ServerInstance "${{ steps.create-container.outputs.containerFQDN }}" -Username "${{ steps.create-container.outputs.containerUser }}" -Password "${{ steps.create-container.outputs.containerPassword }}" -As DataSet - $DS = Invoke-Sqlcmd -ConnectionTimeout 600 -Query "SELECT SUSER_NAME() U,SYSDATETIME() T,@@VERSION V;" -ServerInstance "${{ steps.create-container.outputs.containerFQDN }}" -Username "${{ steps.create-container.outputs.containerUser }}" -Password "${{ steps.create-container.outputs.containerPassword }}" -As DataSet - - $DS.Tables[0].Rows | %{ echo "{ $($_['U']), $($_['T']), $($_['V']) }" } - Write-Host ""; - Write-Host "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; - - - name: download tSQLtBuildArtifact artifact - uses: actions/download-artifact@v4.1.7 - with: - name: tSQLtBuildArtifact - path: "${{ github.workspace }}\\tSQLt\\Build\\output\\tSQLtBuild" - - - name: build tSQLt and Facade Dacpacs --> Build/output/DacpacBuild/ - shell: pwsh - working-directory: ${{ github.workspace }}\tSQLt\ - run: | - .\Build\SetupDacpacBuild.ps1 -ErrorAction Stop - .\Build\FacadeBuildDacpac.ps1 -ErrorAction Stop -ServerName "${{ steps.create-container.outputs.containerFQDN }}" -DatabaseName "tSQLt_dev" -Login "-U ${{ steps.create-container.outputs.containerUser }} -P ${{ steps.create-container.outputs.containerPassword }}" -SqlCmdPath "${{ steps.create-vars.outputs.sqlcmdPath }}" -SqlPackagePath "$env:SQLPACKAGE_HOME" - .\Build\BuildtSQLtDacpac.ps1 -ErrorAction Stop -ServerName "${{ steps.create-container.outputs.containerFQDN }}" -DatabaseName "tSQLt_dev_dacpac_src" -Login "-U ${{ steps.create-container.outputs.containerUser }} -P ${{ steps.create-container.outputs.containerPassword }}" -SqlCmdPath "${{ steps.create-vars.outputs.sqlcmdPath }}" -SqlPackagePath "$env:SQLPACKAGE_HOME" - - - name: create dacpacs artifact - uses: actions/upload-artifact@v2 - with: - if-no-files-found: error - name: dacpacs - path: "${{ env.dacpac_artifact_dir }}\\" - retention-days: 5 - - -# 💖 💖 💖 💖 💖 💖 💖 💖 💖 💖 💖 💖 💖 💖 - create-artifact: - name: create artifact - needs: [build-facades] - #if: ${{ always() }} - - # More types found here, https://docs.github.com/en/free-pro-team@latest/actions/reference/specifications-for-github-hosted-runners - runs-on: windows-latest - - env: - TSQLT_PUBLIC_ARTIFACT_DIR: ${{ github.workspace }}\tSQLt\Build\output\tSQLt\public - TSQLT_VALIDATION_ARTIFACT_DIR: ${{ github.workspace }}\tSQLt\Build\output\tSQLt\validation - - steps: - - name: Checkout tSQLt - uses: actions/checkout@v2 - with: - path: tSQLt - - - name: download tSQLtBuildArtifact artifact - uses: actions/download-artifact@v4.1.7 - with: - name: tSQLtBuildArtifact - path: "${{ github.workspace }}\\tSQLt\\Build\\output\\tSQLtBuild" - - - name: download tSQLt dacpac artifact(s) - uses: actions/download-artifact@v4.1.7 - with: - name: dacpacs - path: "${{ github.workspace }}\\tSQLt\\Build\\output\\DacpacBuild" - - - name: inspect CreateArtifact directory - shell: pwsh - working-directory: ${{ github.workspace }}\tSQLt\Build\output - run: | - Get-ChildItem . -Recurse - - - name: package Build/output/tSQLt /public and /validation files - shell: pwsh - working-directory: ${{ github.workspace }}\tSQLt\ - run: | - .\Build\BuildtSQLtZip.ps1 -ErrorAction Stop - - - name: create tSQLt public artifact - uses: actions/upload-artifact@v2 - with: - if-no-files-found: error - name: tSQLtPublic - path: "${{ env.tsqlt_public_artifact_dir }}\\" - retention-days: 90 - - - name: create tSQLt validation artifact - uses: actions/upload-artifact@v2 - with: - if-no-files-found: error - name: tSQLtValidation - path: "${{ env.tsqlt_validation_artifact_dir }}\\" - retention-days: 5 - -# 💖 💖 💖 💖 💖 💖 💖 💖 💖 💖 💖 💖 💖 💖 - test-tSQLt-and-facade: - name: test build artifact - needs: [create-artifact,set-up-env-vars] - #if: ${{ always() }} - - env: - TEST_ARTIFACT_DIR: ${{ github.workspace }}\tSQLt\Build\temp\Validate\TestResults - DACPAC_ARTIFACT_DIR: ${{ github.workspace }}\tSQLt\Build\output\DacpacBuild - MATRIX_VERSIONS: ${{ format('{{"data-image":{0} }}',github.event.inputs.spawnDataImages) }} - - strategy: - matrix: ${{ fromJson(needs.set-up-env-vars.outputs.matrixValues) }} - fail-fast: false - - # More types found here, https://docs.github.com/en/free-pro-team@latest/actions/reference/specifications-for-github-hosted-runners - runs-on: windows-latest - - steps: - - name: Checkout tSQLt - uses: actions/checkout@v2 - with: - path: tSQLt - - - name: download public tSQLt artifact - uses: actions/download-artifact@v4.1.7 - with: - name: tSQLtPublic - path: "${{ github.workspace }}\\tSQLt\\Build\\output\\tSQLt\\public" - - - name: download validation tSQLt artifact - uses: actions/download-artifact@v4.1.7 - with: - name: tSQLtValidation - path: "${{ github.workspace }}\\tSQLt\\Build\\output\\tSQLt\\validation" - - - name: load namespace variables into env - id: create-vars - shell: pwsh - run: | - $sqlcmdPath = (Get-Command sqlcmd).Path.TrimEnd("\\SQLCMD.EXE"); - Write-Host ('::set-output name=sqlcmdPath::'+$sqlcmdPath); - - - name: Install spawnctl - shell: pwsh - run: | - echo "Downloading and installing spawnctl..." - Invoke-WebRequest -Uri "https://run.spawn.cc/spawnctl.exe" -UseBasicParsing -OutFile "${{ github.workspace }}\spawnctl.exe" - & "${{ github.workspace }}\spawnctl.exe" version - - - name: Create Container - id: create-container - shell: pwsh - run: | - & "${{ github.workspace }}\spawnctl.exe" get data-images - - $dataImageName = "${{ matrix.data-image[ env.MATRIX_SPAWNIMAGE] }}"; - - echo "Creating 'tSQLtOnLinux' Spawn data container from image '$dataImageName'"; - $containerName= & "${{ github.workspace }}\spawnctl.exe" create data-container --image $dataImageName --lifetime 30m -q - $containerJSON= & "${{ github.workspace }}\spawnctl.exe" get data-container $containerName -o json - $containerInfo = ConvertFrom-JSON -inputObject $containerJSON; - $containerFQDN=$containerInfo.host+", "+$containerInfo.port; - $containerUser=$containerInfo.user; - $containerPassword=$containerInfo.password; - echo "Successfully created Spawn data container '$containerName'" - - Write-Host ('::set-output name=containerFQDN::'+$containerFQDN); - Write-Host ('::set-output name=containerUser::'+$containerUser); - Write-Host ('::set-output name=containerPassword::'+$containerPassword); - - env: - SPAWNCTL_ACCESS_TOKEN: ${{ secrets.SPAWNCTL_ACCESS_TOKEN }} - - - name: print sql version info - shell: pwsh - run: | - Write-Host "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; - Write-Host ""; - # Write-Host Invoke-Sqlcmd -ConnectionTimeout 600 -Query "SELECT SUSER_NAME() U,SYSDATETIME() T,@@VERSION V;" -ServerInstance "${{ steps.create-container.outputs.containerFQDN }}" -Username "${{ steps.create-container.outputs.containerUser }}" -Password "${{ steps.create-container.outputs.containerPassword }}" -As DataSet - $DS = Invoke-Sqlcmd -ConnectionTimeout 600 -Query "SELECT SUSER_NAME() U,SYSDATETIME() T,@@VERSION V;" -ServerInstance "${{ steps.create-container.outputs.containerFQDN }}" -Username "${{ steps.create-container.outputs.containerUser }}" -Password "${{ steps.create-container.outputs.containerPassword }}" -As DataSet - - $DS.Tables[0].Rows | %{ echo "{ $($_['U']), $($_['T']), $($_['V']) }" } - Write-Host ""; - Write-Host "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; - - - name: Setup SQL Users - shell: pwsh - run: | - $DS = Invoke-Sqlcmd -ConnectionTimeout 600 -InputFile "${{ github.workspace }}\tSQLt\Build\CreateLogin(tSQLt.Build).sql" -ServerInstance "${{ steps.create-container.outputs.containerFQDN }}" -Username "${{ steps.create-container.outputs.containerUser }}" -Password "${{ steps.create-container.outputs.containerPassword }}" - - # @SM --> TODO: jUnit results - - name: ant Build/tSQLt.validatebuild.xml - working-directory: ${{ github.workspace }}\tSQLt\Build - run: ant -buildfile tSQLt.validatebuild.xml all -D"db.server"="${{ steps.create-container.outputs.containerFQDN }}" -D"db.name"=tSQLt_dev -D"db.login"="-U ${{ steps.create-container.outputs.containerUser }} -P ${{ steps.create-container.outputs.containerPassword }}" -D"sqlcmd.path"="${{ steps.create-vars.outputs.sqlcmdPath }}" -D"sqlpackage.path"="$env:SQLPACKAGE_HOME" -D"logtable.name"="tempdb.dbo.[Full Build Log]" - - - name: create junit artifact - shell: pwsh - run: | - $ArtifactPath = "$env:TEST_ARTIFACT_DIR\"; - (Get-ChildItem $basePath -recurse -include "TestResults*.xml").FullName; - - - name: archive junit test artifact - uses: actions/upload-artifact@v2 - with: - if-no-files-found: error - name: jUnitTests-${{ matrix.data-image[ env.MATRIX_NAME ] }} - path: "${{ env.test_artifact_dir }}\\" - retention-days: 90 - - -# # 💖 💖 💖 💖 💖 💖 💖 💖 💖 💖 💖 💖 💖 💖 -# drop-data-container: -# name: drop the data container -# needs: test-tSQLt -# if: ${{ always() }} -# runs-on: ubuntu-latest -# env: -# steps: -# - name: decode az sp cred -# id: cred-decode -# shell: pwsh -# run: | -# Write-Host ('#TODO'); -# # documentation: https://github.com/azure/login#configure-azure-credentials diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..0c6439730 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,170 @@ +# Builds tSQLt and runs the full validation suite against SQL Server on Linux. +# +# Mirrors the dev container setup (.devcontainer/): the CLR assemblies are signed +# with a throwaway self-signed key, so the artifacts are for validation only and +# must not be released. + +name: CI + +on: + push: + branches: + - develop + - main + pull_request: + branches: + - develop + - main + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + build-and-test: + name: build and test (SQL Server ${{ matrix.sqlserver }}) + runs-on: ubuntu-24.04 + timeout-minutes: 90 + + strategy: + fail-fast: false + matrix: + sqlserver: ['2017', '2019', '2022'] + + services: + sqlserver: + image: mcr.microsoft.com/mssql/server:${{ matrix.sqlserver }}-latest + ports: + - 1433:1433 + env: + ACCEPT_EULA: 'Y' + # Must match SA_PASSWORD below (the env context is not available to services). + MSSQL_SA_PASSWORD: 'P@ssw0rd' + + env: + # Ephemeral credentials for the service container and the self-signed signing key. + SA_PASSWORD: 'P@ssw0rd' + PFX_PASSWORD: 'P@ssw0rdP@ssw0rdP@ssw0rdP@ssw0rd' + SQL_SERVER: 'localhost,1433' + + steps: + - name: Checkout tSQLt + uses: actions/checkout@v4 + + - name: Install SqlServer PowerShell module + shell: pwsh + run: | + Set-PSRepository -Name PSGallery -InstallationPolicy Trusted + Install-Module -Name SqlServer -Scope CurrentUser -AllowClobber -Force + Get-Module -ListAvailable SqlServer | Select-Object Name, Version | Format-Table + + - name: Install sqlpackage + shell: bash + run: | + curl -sSL https://aka.ms/sqlpackage-linux -o "$RUNNER_TEMP/sqlpackage.zip" + mkdir -p "$RUNNER_TEMP/sqlpackage" + unzip -q "$RUNNER_TEMP/sqlpackage.zip" -d "$RUNNER_TEMP/sqlpackage" + chmod +x "$RUNNER_TEMP/sqlpackage/sqlpackage" + echo "$RUNNER_TEMP/sqlpackage" >> "$GITHUB_PATH" + + - name: Create self-signed signing key + shell: bash + run: | + CERT_DIR="$RUNNER_TEMP/tSQLtCert" + mkdir -p "$CERT_DIR" + cd "$CERT_DIR" + openssl req -x509 -newkey rsa:4096 -keyout tSQLtOfficialSigningKey.key -out tSQLtOfficialSigningKey.crt -days 1 -nodes -subj "/CN=tSQLt-CI" + openssl pkcs12 -export -out tSQLtOfficialSigningKey.pfx -inkey tSQLtOfficialSigningKey.key -in tSQLtOfficialSigningKey.crt -passout pass:"$PFX_PASSWORD" + echo "PFX_PATH=$CERT_DIR/tSQLtOfficialSigningKey.pfx" >> "$GITHUB_ENV" + + - name: Wait for SQL Server + shell: pwsh + run: | + $connectionString = "Server=$env:SQL_SERVER;User Id=sa;Password=$env:SA_PASSWORD;TrustServerCertificate=true;Connect Timeout=5;" + for ($i = 1; $i -le 60; $i++) { + try { + Invoke-Sqlcmd -ConnectionString $connectionString -Query "SELECT @@VERSION AS V;" -ErrorAction Stop | Format-List + exit 0 + } catch { + Write-Host "SQL Server not ready yet (attempt $i): $($_.Exception.Message)" + Start-Sleep -Seconds 5 + } + } + throw "SQL Server did not become ready in time." + + - name: Build + shell: pwsh + run: | + Start-Transcript -Path "$env:RUNNER_TEMP/tSQLt-logs/build.log" -Force + try { + ./Build/LocalBuild.ps1 ` + -ServerName $env:SQL_SERVER ` + -UserName sa ` + -Password (ConvertTo-SecureString $env:SA_PASSWORD -AsPlainText -Force) ` + -pfxFilePath $env:PFX_PATH ` + -pfxPassword (ConvertTo-SecureString $env:PFX_PASSWORD -AsPlainText -Force) + } finally { + Stop-Transcript + } + + - name: Validate + shell: pwsh + run: | + Start-Transcript -Path "$env:RUNNER_TEMP/tSQLt-logs/validate.log" -Force + try { + ./Build/LocalValidate.ps1 ` + -ServerName $env:SQL_SERVER ` + -UserName sa ` + -Password (ConvertTo-SecureString $env:SA_PASSWORD -AsPlainText -Force) + } finally { + Stop-Transcript + } + + - name: Summarize test results + if: always() + shell: pwsh + run: | + $resultFiles = Get-ChildItem -Path ./Build/temp/Validate/Results -Filter 'TestResults*.xml' -Recurse -ErrorAction SilentlyContinue + $summary = @("### tSQLt test results (SQL Server ${{ matrix.sqlserver }})", "", "| Suite | Tests | Failures | Errors | Skipped |", "|---|---:|---:|---:|---:|") + $totalTests = 0 + $totalFailures = 0 + $totalErrors = 0 + foreach ($file in $resultFiles) { + [xml]$xml = Get-Content -Path $file.FullName -Raw + $suites = @($xml.SelectNodes('//testsuite')) + $sum = { param($attribute) $total = 0; foreach ($s in $suites) { $value = $s.GetAttribute($attribute); if ($value) { $total += [int]$value } }; $total } + $summary += "| $($file.BaseName) | $(& $sum 'tests') | $(& $sum 'failures') | $(& $sum 'errors') | $(& $sum 'skipped') |" + $totalTests += & $sum 'tests' + $totalFailures += & $sum 'failures' + $totalErrors += & $sum 'errors' + } + if (-not $resultFiles) { $summary += "| (no result files) | | | | |" } + $summary -join "`n" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8 + if ($totalTests -eq 0 -or $totalFailures -gt 0 -or $totalErrors -gt 0) { + throw "Validation did not pass: $totalTests tests, $totalFailures failures, $totalErrors errors. See the test results and logs." + } + + - name: Upload test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results-sql${{ matrix.sqlserver }} + path: | + Build/temp/Validate/Results/ + Build/output/Validate/ + ${{ runner.temp }}/tSQLt-logs/ + if-no-files-found: warn + retention-days: 30 + + - name: Upload build artifact + if: success() + uses: actions/upload-artifact@v4 + with: + name: tSQLt-sql${{ matrix.sqlserver }} + path: Build/output/tSQLt/ + if-no-files-found: error + retention-days: 14 diff --git a/README.md b/README.md index cd477cb9f..a5cbba295 100644 --- a/README.md +++ b/README.md @@ -13,4 +13,32 @@ tSQLt allows you to implement unit tests in T-SQL. This is important as you do n tSQLt is licensed under Apache 2.0 (https://github.com/tSQLt-org/tSQLt/blob/master/Build/License.txt) +## Development in the masmgr fork + +`main` tracks the upstream tSQLt repository. `develop` integrates changes maintained +in this fork. Open pull requests for bug fixes and enhancements against `develop`; +keep each change in its own branch and validate it with CI before merging. + +## Continuous integration + +The GitHub Actions `CI` workflow runs on pushes to `develop` and `main`, pull +requests targeting either branch, and manual dispatch. It builds tSQLt with +`Build/LocalBuild.ps1` and runs the existing validation suite with +`Build/LocalValidate.ps1` against SQL Server 2017, 2019, and 2022 on Linux using +Ubuntu runners. No external service credentials or repository secrets are needed. + +Open the PR checks or the repository's **Actions > CI** page to view each SQL +Server job. The run summary lists test counts, failures, errors, and skipped tests. +Validation fails on test failures or errors, missing expected result files, or an +empty test run. + +Each job uploads `test-results-sql` containing the result XML files, +validation output, and build/validation logs, including available files when a +step fails. These artifacts are retained for 30 days. Successful jobs also upload +`tSQLt-sql` build artifacts, retained for 14 days. + +CI uses a temporary self-signed key. Its build artifacts are for validation only +and must not be distributed as releases. This workflow covers Linux SQL Server; +it does not establish compatibility with Windows or other SQL Server versions. + diff --git a/Tests/AssertEqualsTableTests.class.sql b/Tests/AssertEqualsTableTests.class.sql index 08fb5b046..8da70c478 100644 --- a/Tests/AssertEqualsTableTests.class.sql +++ b/Tests/AssertEqualsTableTests.class.sql @@ -2,6 +2,26 @@ GO EXEC tSQLt.NewTestClass 'AssertEqualsTableTests'; GO +CREATE PROCEDURE AssertEqualsTableTests.[test DATE values before 1753 report assertion differences without CLR overflow] +AS +BEGIN + CREATE TABLE #Expected(DateColumn DATE); + CREATE TABLE #Actual(DateColumn DATE); + INSERT INTO #Expected VALUES ('0001-01-01'), ('1752-12-31'); + INSERT INTO #Actual VALUES ('2000-01-01'); + + DECLARE @FailMessage NVARCHAR(MAX); + EXEC tSQLt_testutil.CaptureFailMessage + @Command = N'EXEC tSQLt.AssertEqualsTable N''#Expected'', N''#Actual'';', + @FailMessage = @FailMessage OUTPUT; + + EXEC tSQLt.AssertLike N'%Unexpected/missing resultset rows!%', @FailMessage; + EXEC tSQLt.AssertLike N'%0001-01-01%', @FailMessage; + EXEC tSQLt.AssertLike N'%1752-12-31%', @FailMessage; + EXEC tSQLt.AssertLike N'%2000-01-01%', @FailMessage; +END; +GO + CREATE PROCEDURE AssertEqualsTableTests.[test left table doesn't exist results in failure] AS BEGIN diff --git a/Tests/TableToTextTests.class.sql b/Tests/TableToTextTests.class.sql index a84aba4f5..6d3c19bf7 100644 --- a/Tests/TableToTextTests.class.sql +++ b/Tests/TableToTextTests.class.sql @@ -783,6 +783,31 @@ BEGIN END; GO +CREATE PROCEDURE TableToTextTests.[test CLR TableToString works for DATE values across the full supported range] +AS +BEGIN + CREATE TABLE #DoesExist(T DATE); + INSERT INTO #DoesExist (T)VALUES('0001-01-01'); + INSERT INTO #DoesExist (T)VALUES('1752-12-31'); + INSERT INTO #DoesExist (T)VALUES('1753-01-01'); + INSERT INTO #DoesExist (T)VALUES('9999-12-31'); + INSERT INTO #DoesExist (T)VALUES(NULL); + + DECLARE @result NVARCHAR(MAX); + SET @result = tSQLt.Private::TableToString('#DoesExist', 'T', ''); + + DECLARE @expected NVARCHAR(MAX); + SET @expected = '|T | ++----------+ +|!NULL! | +|0001-01-01| +|1752-12-31| +|1753-01-01| +|9999-12-31|'; + EXEC tSQLt.AssertEqualsString @expected, @result; +END; +GO + CREATE PROCEDURE TableToTextTests.[test TableToText works for CLR datatype that is not in tempdb] AS BEGIN diff --git a/tSQLtCLR/tSQLtCLR/tSQLtPrivate.cs b/tSQLtCLR/tSQLtCLR/tSQLtPrivate.cs index 6d18ddfec..dd3a9001f 100644 --- a/tSQLtCLR/tSQLtCLR/tSQLtPrivate.cs +++ b/tSQLtCLR/tSQLtCLR/tSQLtPrivate.cs @@ -320,8 +320,8 @@ private static string unquote(string columnName) return columnName.Replace("[","").Replace("]",""); } - private static string SqlDateToString(SqlDateTime dtValue) { - return String.Format("{0:yyyy-MM-dd}", dtValue.Value); + private static string SqlDateToString(DateTime dtValue) { + return dtValue.ToString("yyyy-MM-dd", System.Globalization.CultureInfo.InvariantCulture); } private static String SqlDateTimeToString(SqlDateTime dtValue) {