From 1b956899bbb57ed0be01bf8cf2fd5085970cc162 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Mon, 16 Feb 2026 15:45:49 -0500 Subject: [PATCH 1/3] Add CI pipeline for multi-version SQL Server testing - sql-tests.yml: Matrix testing on SQL Server 2017/2019/2022/2025 - Regenerates DarlingData.sql from current branch via Merge-All.ps1 - Installs all procs and verifies 9 exist via OBJECT_ID checks - test_help_output.sql: Validates @help=1 for all 9 procedures - test_basic_execution.sql: Default execution for 6 safe procedures All tests verified locally against sql2022 (9/9 help, 6/6 execution). See issue #664 for details. Co-Authored-By: Claude Opus 4.6 --- .github/sql/test_basic_execution.sql | 110 +++++++++++++++++++++ .github/sql/test_help_output.sql | 143 +++++++++++++++++++++++++++ .github/workflows/sql-tests.yml | 108 ++++++++++++++++++++ 3 files changed, 361 insertions(+) create mode 100644 .github/sql/test_basic_execution.sql create mode 100644 .github/sql/test_help_output.sql create mode 100644 .github/workflows/sql-tests.yml diff --git a/.github/sql/test_basic_execution.sql b/.github/sql/test_basic_execution.sql new file mode 100644 index 00000000..cbe13e32 --- /dev/null +++ b/.github/sql/test_basic_execution.sql @@ -0,0 +1,110 @@ +/* +CI Test: Run default execution for procedures that are safe to execute without special setup. +Procs requiring extended events or special data (sp_HumanEvents, sp_HumanEventsBlockViewer, +sp_QueryReproBuilder) are tested with @help = 1 only (see test_help_output.sql). +Uses a temp table to track results across GO batches. +*/ + +SET NOCOUNT ON; + +CREATE TABLE #exec_results (proc_name VARCHAR(100) NOT NULL, passed BIT NOT NULL); +GO + +PRINT '========================================'; +PRINT 'Testing default execution'; +PRINT '========================================'; +PRINT ''; +GO + +/* sp_PressureDetector - detects CPU and memory pressure */ +BEGIN TRY + EXEC dbo.sp_PressureDetector; + INSERT #exec_results VALUES ('sp_PressureDetector', 1); + PRINT 'PASS: sp_PressureDetector (default)'; +END TRY +BEGIN CATCH + INSERT #exec_results VALUES ('sp_PressureDetector', 0); + PRINT 'FAIL: sp_PressureDetector - ' + ERROR_MESSAGE(); +END CATCH; +GO + +/* sp_PerfCheck - comprehensive performance health check */ +BEGIN TRY + EXEC dbo.sp_PerfCheck; + INSERT #exec_results VALUES ('sp_PerfCheck', 1); + PRINT 'PASS: sp_PerfCheck (default)'; +END TRY +BEGIN CATCH + INSERT #exec_results VALUES ('sp_PerfCheck', 0); + PRINT 'FAIL: sp_PerfCheck - ' + ERROR_MESSAGE(); +END CATCH; +GO + +/* sp_HealthParser - analyzes system health extended event */ +BEGIN TRY + EXEC dbo.sp_HealthParser; + INSERT #exec_results VALUES ('sp_HealthParser', 1); + PRINT 'PASS: sp_HealthParser (default)'; +END TRY +BEGIN CATCH + INSERT #exec_results VALUES ('sp_HealthParser', 0); + PRINT 'FAIL: sp_HealthParser - ' + ERROR_MESSAGE(); +END CATCH; +GO + +/* sp_LogHunter - searches error logs */ +BEGIN TRY + EXEC dbo.sp_LogHunter; + INSERT #exec_results VALUES ('sp_LogHunter', 1); + PRINT 'PASS: sp_LogHunter (default)'; +END TRY +BEGIN CATCH + INSERT #exec_results VALUES ('sp_LogHunter', 0); + PRINT 'FAIL: sp_LogHunter - ' + ERROR_MESSAGE(); +END CATCH; +GO + +/* sp_IndexCleanup - identifies unused/duplicate indexes */ +BEGIN TRY + EXEC dbo.sp_IndexCleanup + @database_name = N'DarlingData_CI_Test'; + INSERT #exec_results VALUES ('sp_IndexCleanup', 1); + PRINT 'PASS: sp_IndexCleanup (default)'; +END TRY +BEGIN CATCH + INSERT #exec_results VALUES ('sp_IndexCleanup', 0); + PRINT 'FAIL: sp_IndexCleanup - ' + ERROR_MESSAGE(); +END CATCH; +GO + +/* sp_QuickieStore - navigates Query Store data */ +BEGIN TRY + EXEC dbo.sp_QuickieStore + @database_name = N'DarlingData_CI_Test'; + INSERT #exec_results VALUES ('sp_QuickieStore', 1); + PRINT 'PASS: sp_QuickieStore (default)'; +END TRY +BEGIN CATCH + INSERT #exec_results VALUES ('sp_QuickieStore', 0); + PRINT 'FAIL: sp_QuickieStore - ' + ERROR_MESSAGE(); +END CATCH; +GO + +/* Summary - fail the build if any test failed */ +PRINT ''; +PRINT '========================================'; + +DECLARE @failed int = (SELECT COUNT(*) FROM #exec_results WHERE passed = 0); +DECLARE @total int = (SELECT COUNT(*) FROM #exec_results); + +PRINT 'Basic execution: ' + CONVERT(varchar(10), @total - @failed) + '/' + CONVERT(varchar(10), @total) + ' passed'; + +IF @failed > 0 + RAISERROR('%d procedure(s) failed default execution', 16, 1, @failed); +ELSE + PRINT 'All procedures passed'; + +PRINT '========================================'; + +DROP TABLE #exec_results; +GO diff --git a/.github/sql/test_help_output.sql b/.github/sql/test_help_output.sql new file mode 100644 index 00000000..91c9d5c9 --- /dev/null +++ b/.github/sql/test_help_output.sql @@ -0,0 +1,143 @@ +/* +CI Test: Validate @help = 1 output for all procedures. +Uses a temp table to track results across GO batches. +Fails with RAISERROR if any proc errors on @help = 1. +*/ + +SET NOCOUNT ON; + +CREATE TABLE #help_results (proc_name VARCHAR(100) NOT NULL, passed BIT NOT NULL); +GO + +PRINT '========================================'; +PRINT 'Testing @help = 1 for all procedures'; +PRINT '========================================'; +PRINT ''; +GO + +/* sp_HealthParser */ +BEGIN TRY + EXEC dbo.sp_HealthParser @help = 1; + INSERT #help_results VALUES ('sp_HealthParser', 1); + PRINT 'PASS: sp_HealthParser @help = 1'; +END TRY +BEGIN CATCH + INSERT #help_results VALUES ('sp_HealthParser', 0); + PRINT 'FAIL: sp_HealthParser @help = 1 - ' + ERROR_MESSAGE(); +END CATCH; +GO + +/* sp_HumanEvents */ +BEGIN TRY + EXEC dbo.sp_HumanEvents @help = 1; + INSERT #help_results VALUES ('sp_HumanEvents', 1); + PRINT 'PASS: sp_HumanEvents @help = 1'; +END TRY +BEGIN CATCH + INSERT #help_results VALUES ('sp_HumanEvents', 0); + PRINT 'FAIL: sp_HumanEvents @help = 1 - ' + ERROR_MESSAGE(); +END CATCH; +GO + +/* sp_HumanEventsBlockViewer */ +BEGIN TRY + EXEC dbo.sp_HumanEventsBlockViewer @help = 1; + INSERT #help_results VALUES ('sp_HumanEventsBlockViewer', 1); + PRINT 'PASS: sp_HumanEventsBlockViewer @help = 1'; +END TRY +BEGIN CATCH + INSERT #help_results VALUES ('sp_HumanEventsBlockViewer', 0); + PRINT 'FAIL: sp_HumanEventsBlockViewer @help = 1 - ' + ERROR_MESSAGE(); +END CATCH; +GO + +/* sp_IndexCleanup */ +BEGIN TRY + EXEC dbo.sp_IndexCleanup @help = 1; + INSERT #help_results VALUES ('sp_IndexCleanup', 1); + PRINT 'PASS: sp_IndexCleanup @help = 1'; +END TRY +BEGIN CATCH + INSERT #help_results VALUES ('sp_IndexCleanup', 0); + PRINT 'FAIL: sp_IndexCleanup @help = 1 - ' + ERROR_MESSAGE(); +END CATCH; +GO + +/* sp_LogHunter */ +BEGIN TRY + EXEC dbo.sp_LogHunter @help = 1; + INSERT #help_results VALUES ('sp_LogHunter', 1); + PRINT 'PASS: sp_LogHunter @help = 1'; +END TRY +BEGIN CATCH + INSERT #help_results VALUES ('sp_LogHunter', 0); + PRINT 'FAIL: sp_LogHunter @help = 1 - ' + ERROR_MESSAGE(); +END CATCH; +GO + +/* sp_PerfCheck */ +BEGIN TRY + EXEC dbo.sp_PerfCheck @help = 1; + INSERT #help_results VALUES ('sp_PerfCheck', 1); + PRINT 'PASS: sp_PerfCheck @help = 1'; +END TRY +BEGIN CATCH + INSERT #help_results VALUES ('sp_PerfCheck', 0); + PRINT 'FAIL: sp_PerfCheck @help = 1 - ' + ERROR_MESSAGE(); +END CATCH; +GO + +/* sp_PressureDetector */ +BEGIN TRY + EXEC dbo.sp_PressureDetector @help = 1; + INSERT #help_results VALUES ('sp_PressureDetector', 1); + PRINT 'PASS: sp_PressureDetector @help = 1'; +END TRY +BEGIN CATCH + INSERT #help_results VALUES ('sp_PressureDetector', 0); + PRINT 'FAIL: sp_PressureDetector @help = 1 - ' + ERROR_MESSAGE(); +END CATCH; +GO + +/* sp_QueryReproBuilder */ +BEGIN TRY + EXEC dbo.sp_QueryReproBuilder @help = 1; + INSERT #help_results VALUES ('sp_QueryReproBuilder', 1); + PRINT 'PASS: sp_QueryReproBuilder @help = 1'; +END TRY +BEGIN CATCH + INSERT #help_results VALUES ('sp_QueryReproBuilder', 0); + PRINT 'FAIL: sp_QueryReproBuilder @help = 1 - ' + ERROR_MESSAGE(); +END CATCH; +GO + +/* sp_QuickieStore */ +BEGIN TRY + EXEC dbo.sp_QuickieStore @help = 1; + INSERT #help_results VALUES ('sp_QuickieStore', 1); + PRINT 'PASS: sp_QuickieStore @help = 1'; +END TRY +BEGIN CATCH + INSERT #help_results VALUES ('sp_QuickieStore', 0); + PRINT 'FAIL: sp_QuickieStore @help = 1 - ' + ERROR_MESSAGE(); +END CATCH; +GO + +/* Summary - fail the build if any test failed */ +PRINT ''; +PRINT '========================================'; + +DECLARE @failed int = (SELECT COUNT(*) FROM #help_results WHERE passed = 0); +DECLARE @total int = (SELECT COUNT(*) FROM #help_results); + +PRINT 'Help output: ' + CONVERT(varchar(10), @total - @failed) + '/' + CONVERT(varchar(10), @total) + ' passed'; + +IF @failed > 0 + RAISERROR('%d procedure(s) failed @help = 1', 16, 1, @failed); +ELSE + PRINT 'All procedures passed'; + +PRINT '========================================'; + +DROP TABLE #help_results; +GO diff --git a/.github/workflows/sql-tests.yml b/.github/workflows/sql-tests.yml new file mode 100644 index 00000000..faaa49e9 --- /dev/null +++ b/.github/workflows/sql-tests.yml @@ -0,0 +1,108 @@ +name: SQL Tests + +on: + push: + branches: [dev] + pull_request: + branches: [dev, main] + +jobs: + test: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - version: '2017' + image: mcr.microsoft.com/mssql/server:2017-latest + health_cmd: /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P 'CI_Test#2026!' -Q 'SELECT 1' -b + - version: '2019' + image: mcr.microsoft.com/mssql/server:2019-latest + health_cmd: /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P 'CI_Test#2026!' -Q 'SELECT 1' -b + - version: '2022' + image: mcr.microsoft.com/mssql/server:2022-latest + health_cmd: /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'CI_Test#2026!' -C -No -Q 'SELECT 1' -b + - version: '2025' + image: mcr.microsoft.com/mssql/server:2025-latest + health_cmd: /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'CI_Test#2026!' -C -No -Q 'SELECT 1' -b + + name: SQL Server ${{ matrix.version }} + + services: + sqlserver: + image: ${{ matrix.image }} + env: + ACCEPT_EULA: Y + MSSQL_SA_PASSWORD: CI_Test#2026! + ports: + - 1433:1433 + options: >- + --health-cmd "${{ matrix.health_cmd }}" + --health-interval 10s + --health-timeout 5s + --health-retries 10 + + steps: + - uses: actions/checkout@v4 + + - name: Install sqlcmd + run: | + curl -sSL https://packages.microsoft.com/keys/microsoft.asc | sudo tee /etc/apt/trusted.gpg.d/microsoft.asc > /dev/null + source /etc/os-release + echo "deb [arch=amd64] https://packages.microsoft.com/ubuntu/${VERSION_ID}/prod ${VERSION_CODENAME} main" | sudo tee /etc/apt/sources.list.d/mssql-release.list + sudo apt-get update + sudo ACCEPT_EULA=Y apt-get install -y mssql-tools18 + + - name: Create test database + env: + SA_PASSWORD: CI_Test#2026! + run: | + /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P "$SA_PASSWORD" -C -No -b -Q "CREATE DATABASE DarlingData_CI_Test;" + /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P "$SA_PASSWORD" -C -No -b -d DarlingData_CI_Test -Q "ALTER DATABASE DarlingData_CI_Test SET QUERY_STORE = ON;" + + - name: Regenerate DarlingData.sql from current branch + shell: pwsh + run: | + Push-Location Install-All + ./Merge-All.ps1 + Pop-Location + + - name: Install all procedures + env: + SA_PASSWORD: CI_Test#2026! + run: | + /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P "$SA_PASSWORD" -C -No -b -d DarlingData_CI_Test -i "Install-All/DarlingData.sql" + + - name: Verify procedures exist + env: + SA_PASSWORD: CI_Test#2026! + run: | + /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P "$SA_PASSWORD" -C -No -b -d DarlingData_CI_Test -Q " + SET NOCOUNT ON; + DECLARE @missing int = 0; + IF OBJECT_ID(N'dbo.sp_HealthParser', N'P') IS NULL BEGIN SET @missing += 1; PRINT 'MISSING: sp_HealthParser'; END; + IF OBJECT_ID(N'dbo.sp_HumanEvents', N'P') IS NULL BEGIN SET @missing += 1; PRINT 'MISSING: sp_HumanEvents'; END; + IF OBJECT_ID(N'dbo.sp_HumanEventsBlockViewer', N'P') IS NULL BEGIN SET @missing += 1; PRINT 'MISSING: sp_HumanEventsBlockViewer'; END; + IF OBJECT_ID(N'dbo.sp_IndexCleanup', N'P') IS NULL BEGIN SET @missing += 1; PRINT 'MISSING: sp_IndexCleanup'; END; + IF OBJECT_ID(N'dbo.sp_LogHunter', N'P') IS NULL BEGIN SET @missing += 1; PRINT 'MISSING: sp_LogHunter'; END; + IF OBJECT_ID(N'dbo.sp_PerfCheck', N'P') IS NULL BEGIN SET @missing += 1; PRINT 'MISSING: sp_PerfCheck'; END; + IF OBJECT_ID(N'dbo.sp_PressureDetector', N'P') IS NULL BEGIN SET @missing += 1; PRINT 'MISSING: sp_PressureDetector'; END; + IF OBJECT_ID(N'dbo.sp_QueryReproBuilder', N'P') IS NULL BEGIN SET @missing += 1; PRINT 'MISSING: sp_QueryReproBuilder'; END; + IF OBJECT_ID(N'dbo.sp_QuickieStore', N'P') IS NULL BEGIN SET @missing += 1; PRINT 'MISSING: sp_QuickieStore'; END; + IF @missing > 0 + RAISERROR('Missing %d procedure(s)', 16, 1, @missing); + ELSE + PRINT 'All 9 procedures installed successfully'; + " + + - name: Test help output + env: + SA_PASSWORD: CI_Test#2026! + run: | + /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P "$SA_PASSWORD" -C -No -b -d DarlingData_CI_Test -i ".github/sql/test_help_output.sql" + + - name: Test basic execution + env: + SA_PASSWORD: CI_Test#2026! + run: | + /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P "$SA_PASSWORD" -C -No -b -d DarlingData_CI_Test -i ".github/sql/test_basic_execution.sql" From 57c9e67edd2213a0b128468c14cfe225ffd4a809 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Mon, 16 Feb 2026 15:54:00 -0500 Subject: [PATCH 2/3] Fix CI: use log-based health check and handle pre-configured Microsoft apt repo - Health check: grep errorlog instead of sqlcmd (works across all container versions) - Install sqlcmd: skip repo setup if Microsoft repo already configured (Ubuntu 24.04) - Increase health retries from 10 to 15 for slower container starts Co-Authored-By: Claude Opus 4.6 --- .github/workflows/sql-tests.yml | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/.github/workflows/sql-tests.yml b/.github/workflows/sql-tests.yml index faaa49e9..420f3118 100644 --- a/.github/workflows/sql-tests.yml +++ b/.github/workflows/sql-tests.yml @@ -15,16 +15,12 @@ jobs: include: - version: '2017' image: mcr.microsoft.com/mssql/server:2017-latest - health_cmd: /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P 'CI_Test#2026!' -Q 'SELECT 1' -b - version: '2019' image: mcr.microsoft.com/mssql/server:2019-latest - health_cmd: /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P 'CI_Test#2026!' -Q 'SELECT 1' -b - version: '2022' image: mcr.microsoft.com/mssql/server:2022-latest - health_cmd: /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'CI_Test#2026!' -C -No -Q 'SELECT 1' -b - version: '2025' image: mcr.microsoft.com/mssql/server:2025-latest - health_cmd: /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'CI_Test#2026!' -C -No -Q 'SELECT 1' -b name: SQL Server ${{ matrix.version }} @@ -37,19 +33,22 @@ jobs: ports: - 1433:1433 options: >- - --health-cmd "${{ matrix.health_cmd }}" + --health-cmd "grep -q 'SQL Server is now ready for client connections' /var/opt/mssql/log/errorlog || exit 1" --health-interval 10s --health-timeout 5s - --health-retries 10 + --health-retries 15 steps: - uses: actions/checkout@v4 - name: Install sqlcmd run: | - curl -sSL https://packages.microsoft.com/keys/microsoft.asc | sudo tee /etc/apt/trusted.gpg.d/microsoft.asc > /dev/null - source /etc/os-release - echo "deb [arch=amd64] https://packages.microsoft.com/ubuntu/${VERSION_ID}/prod ${VERSION_CODENAME} main" | sudo tee /etc/apt/sources.list.d/mssql-release.list + # Ubuntu 24.04 runners have Microsoft repo pre-configured; avoid Signed-By conflicts + if ! grep -rql 'packages.microsoft.com' /etc/apt/sources.list.d/ 2>/dev/null; then + curl -sSL https://packages.microsoft.com/keys/microsoft.asc | sudo gpg --dearmor -o /usr/share/keyrings/microsoft-prod.gpg + source /etc/os-release + echo "deb [arch=amd64,signed-by=/usr/share/keyrings/microsoft-prod.gpg] https://packages.microsoft.com/ubuntu/${VERSION_ID}/prod ${VERSION_CODENAME} main" | sudo tee /etc/apt/sources.list.d/mssql-release.list + fi sudo apt-get update sudo ACCEPT_EULA=Y apt-get install -y mssql-tools18 From 4d0b3e9f77155477adfd7179467e6ba9c836a572 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Mon, 16 Feb 2026 16:00:07 -0500 Subject: [PATCH 3/3] Remove sp_PerfCheck from basic execution tests (requires default trace) sp_PerfCheck reads the default trace file which does not exist in Docker containers. Move to help-only testing (already covered in test_help_output.sql). Co-Authored-By: Claude Opus 4.6 --- .github/sql/test_basic_execution.sql | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/.github/sql/test_basic_execution.sql b/.github/sql/test_basic_execution.sql index cbe13e32..4fda240f 100644 --- a/.github/sql/test_basic_execution.sql +++ b/.github/sql/test_basic_execution.sql @@ -1,7 +1,9 @@ /* CI Test: Run default execution for procedures that are safe to execute without special setup. -Procs requiring extended events or special data (sp_HumanEvents, sp_HumanEventsBlockViewer, -sp_QueryReproBuilder) are tested with @help = 1 only (see test_help_output.sql). +Procs requiring extended events, special data, or host features not available in Docker +(sp_HumanEvents, sp_HumanEventsBlockViewer, sp_QueryReproBuilder, sp_PerfCheck) +are tested with @help = 1 only (see test_help_output.sql). +sp_PerfCheck reads the default trace which does not exist in Docker containers. Uses a temp table to track results across GO batches. */ @@ -28,18 +30,6 @@ BEGIN CATCH END CATCH; GO -/* sp_PerfCheck - comprehensive performance health check */ -BEGIN TRY - EXEC dbo.sp_PerfCheck; - INSERT #exec_results VALUES ('sp_PerfCheck', 1); - PRINT 'PASS: sp_PerfCheck (default)'; -END TRY -BEGIN CATCH - INSERT #exec_results VALUES ('sp_PerfCheck', 0); - PRINT 'FAIL: sp_PerfCheck - ' + ERROR_MESSAGE(); -END CATCH; -GO - /* sp_HealthParser - analyzes system health extended event */ BEGIN TRY EXEC dbo.sp_HealthParser;