diff --git a/.github/sql/test_basic_execution.sql b/.github/sql/test_basic_execution.sql new file mode 100644 index 00000000..4fda240f --- /dev/null +++ b/.github/sql/test_basic_execution.sql @@ -0,0 +1,100 @@ +/* +CI Test: Run default execution for procedures that are safe to execute without special setup. +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. +*/ + +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_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..420f3118 --- /dev/null +++ b/.github/workflows/sql-tests.yml @@ -0,0 +1,107 @@ +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 + - version: '2019' + image: mcr.microsoft.com/mssql/server:2019-latest + - version: '2022' + image: mcr.microsoft.com/mssql/server:2022-latest + - version: '2025' + image: mcr.microsoft.com/mssql/server:2025-latest + + 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 "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 15 + + steps: + - uses: actions/checkout@v4 + + - name: Install sqlcmd + run: | + # 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 + + - 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"