From 83d3e74bce445d74a82e7cf6012d5d5fa7204624 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Sat, 18 Jul 2026 05:58:26 -0400 Subject: [PATCH 1/7] CI: run the sp_PerfCheck and sp_HumanEvents assertion harnesses The sql-tests workflow only ran basic-execution and help-output smoke tests, so the assertion harnesses added recently ran only when someone remembered to. That gap is exactly how behavioral regressions shipped: a harness existed but nothing ran it. This wires the two self-contained harnesses into CI so they run on every push to dev and every PR, across the 2017/2019/2022/2025 matrix. PerfCheck and HumanEvents build and drop their own scratch databases, Extended Events sessions, and configuration, so they need no external data - unlike the IndexCleanup and ReproBuilder harnesses, which depend on StackOverflow2013 that the CI containers do not have (a later phase). The harnesses were written and validated against the go-based sqlcmd, so CI now installs that package (already available from the Microsoft apt repo it configures) alongside mssql-tools18, rather than risk output-parsing differences from a different client. The harnesses' sqlcmd invocation is made configurable through two environment variables - SQLCMD_BIN (default "sqlcmd") and SQLCMD_CONN_ARGS (default empty) - so the same code runs locally unchanged and in CI with "-C" to trust the container's self-signed certificate. Both changes are no-ops locally: harnesses still pass 53/53 and 195/195 against SQL2022. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/sql-tests.yml | 30 ++++++++++++++++++++++++++++-- sp_HumanEvents/tests/run_tests.py | 16 ++++++++++++++-- sp_PerfCheck/tests/run_tests.py | 16 ++++++++++++++-- 3 files changed, 56 insertions(+), 6 deletions(-) diff --git a/.github/workflows/sql-tests.yml b/.github/workflows/sql-tests.yml index 420f3118..c35c2909 100644 --- a/.github/workflows/sql-tests.yml +++ b/.github/workflows/sql-tests.yml @@ -41,7 +41,7 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Install sqlcmd + - name: Install sqlcmd tools 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 @@ -50,7 +50,10 @@ jobs: 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 + # mssql-tools18 for the .sql smoke tests below; the go-based sqlcmd + # (on PATH as /usr/bin/sqlcmd) for the Python assertion harnesses, + # which were written and validated against that tool. + sudo ACCEPT_EULA=Y apt-get install -y mssql-tools18 sqlcmd - name: Create test database env: @@ -105,3 +108,26 @@ jobs: 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" + + # Assertion harnesses. These EXECUTE the output the procedures generate + # (findings, session DDL) rather than just running them, which is what + # catches the "reads correctly but is wrong when run" bugs the smoke tests + # above cannot. They are self-contained (build and drop their own scratch + # databases, sessions, and config); only PerfCheck and HumanEvents run + # here so far - IndexCleanup and ReproBuilder need a data source the CI + # containers do not have yet. They use the go-based sqlcmd via the + # SQLCMD_* environment overrides; -C trusts the container's self-signed + # certificate. + - name: sp_PerfCheck assertion harness + env: + SA_PASSWORD: CI_Test#2026! + SQLCMD_CONN_ARGS: "-C" + run: | + python3 sp_PerfCheck/tests/run_tests.py --server localhost --password "$SA_PASSWORD" + + - name: sp_HumanEvents assertion harness + env: + SA_PASSWORD: CI_Test#2026! + SQLCMD_CONN_ARGS: "-C" + run: | + python3 sp_HumanEvents/tests/run_tests.py --server localhost --password "$SA_PASSWORD" diff --git a/sp_HumanEvents/tests/run_tests.py b/sp_HumanEvents/tests/run_tests.py index 69df0b59..8656514c 100644 --- a/sp_HumanEvents/tests/run_tests.py +++ b/sp_HumanEvents/tests/run_tests.py @@ -53,7 +53,9 @@ """ import argparse +import os import re +import shlex import subprocess import sys @@ -79,6 +81,16 @@ def find_sql_errors(text): # ---------------------------------------------------------------- sqlcmd plumbing +def _sqlcmd_prefix(): + """The sqlcmd binary plus any connection args, overridable via environment so + one harness runs both locally and in CI. Locally SQLCMD_BIN defaults to the + go-based 'sqlcmd' on PATH and SQLCMD_CONN_ARGS is empty; CI points SQLCMD_BIN + at its own binary and sets SQLCMD_CONN_ARGS to the cert-trust flag its + container connection needs (e.g. '-C').""" + return [os.environ.get("SQLCMD_BIN", "sqlcmd")] + shlex.split( + os.environ.get("SQLCMD_CONN_ARGS", "")) + + def _sqlcmd(server, password, sql, database="master", query_timeout=None, subprocess_timeout=120): """Run a batch and return (stdout, stderr) decoded as UTF-8. @@ -90,8 +102,8 @@ def _sqlcmd(server, password, sql, database="master", cancels the batch server-side -- used to stop the never-ending collector loop); subprocess_timeout is a hard backstop on the whole process. """ - cmd = [ - "sqlcmd", "-S", server, "-U", "sa", "-P", password, + cmd = _sqlcmd_prefix() + [ + "-S", server, "-U", "sa", "-P", password, "-d", database, "-W", # trim trailing spaces "-h", "-1", # no result-set headers diff --git a/sp_PerfCheck/tests/run_tests.py b/sp_PerfCheck/tests/run_tests.py index 01c741ff..bdf3a653 100644 --- a/sp_PerfCheck/tests/run_tests.py +++ b/sp_PerfCheck/tests/run_tests.py @@ -47,7 +47,9 @@ """ import argparse +import os import re +import shlex import subprocess import sys @@ -92,6 +94,16 @@ def find_sql_errors(text): return re.findall(r"Msg \d+, Level 1[6-9][^\n]*", text) +def _sqlcmd_prefix(): + """The sqlcmd binary plus any connection args, overridable via environment so + one harness runs both locally and in CI. Locally SQLCMD_BIN defaults to the + go-based 'sqlcmd' on PATH and SQLCMD_CONN_ARGS is empty; CI points SQLCMD_BIN + at its own binary and sets SQLCMD_CONN_ARGS to the cert-trust flag its + container connection needs (e.g. '-C').""" + return [os.environ.get("SQLCMD_BIN", "sqlcmd")] + shlex.split( + os.environ.get("SQLCMD_CONN_ARGS", "")) + + def _sqlcmd(server, password, sql, headers=True): """Run a batch and return (stdout, stderr) decoded as UTF-8. @@ -100,8 +112,8 @@ def _sqlcmd(server, password, sql, headers=True): that). Output is tab-delimited and trimmed (-W -s TAB) and the line width is maxed (-w 65535) so wide rows are not wrapped mid-row. """ - cmd = [ - "sqlcmd", "-S", server, "-U", "sa", "-P", password, + cmd = _sqlcmd_prefix() + [ + "-S", server, "-U", "sa", "-P", password, "-d", "master", "-W", # trim trailing spaces "-w", "65535", # do not wrap wide rows From d6a3e964abe14402e96ac4c5446f9cc60c741abb Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Sat, 18 Jul 2026 06:02:16 -0400 Subject: [PATCH 2/7] CI: install go-sqlcmd from GitHub release (apt package unavailable on runners) The 'sqlcmd' apt package is not in the Microsoft repo for the ubuntu-noble runners (E: Unable to locate package sqlcmd). Pull the latest linux-amd64 go-sqlcmd release binary instead, install to /usr/local/bin/sqlcmd, and point the harness steps at it explicitly. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/sql-tests.yml | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/.github/workflows/sql-tests.yml b/.github/workflows/sql-tests.yml index c35c2909..ba72f3a9 100644 --- a/.github/workflows/sql-tests.yml +++ b/.github/workflows/sql-tests.yml @@ -50,10 +50,30 @@ jobs: 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 - # mssql-tools18 for the .sql smoke tests below; the go-based sqlcmd - # (on PATH as /usr/bin/sqlcmd) for the Python assertion harnesses, - # which were written and validated against that tool. - sudo ACCEPT_EULA=Y apt-get install -y mssql-tools18 sqlcmd + sudo ACCEPT_EULA=Y apt-get install -y mssql-tools18 + + - name: Install go-sqlcmd (for the assertion harnesses) + env: + GH_TOKEN: ${{ github.token }} + run: | + # The Python assertion harnesses were written and validated against the + # go-based sqlcmd, not mssql-tools18, so install that to avoid + # output-parsing differences. It is not in the apt repo for these + # runners, so pull the latest linux-amd64 release binary. + set -euxo pipefail + assets=$(curl -fsSL -H "Authorization: Bearer $GH_TOKEN" \ + https://api.github.com/repos/microsoft/go-sqlcmd/releases/latest \ + | grep -oE 'https://[^"]+' | grep -iE 'linux[-_]amd64.*\.tar\.(bz2|gz)$') + echo "$assets" + url=$(echo "$assets" | head -1) + test -n "$url" + mkdir -p /tmp/gosqlcmd + curl -fsSL -o /tmp/gosqlcmd.tar "$url" + tar -xf /tmp/gosqlcmd.tar -C /tmp/gosqlcmd + bin=$(find /tmp/gosqlcmd -type f -name sqlcmd | head -1) + test -n "$bin" + sudo install "$bin" /usr/local/bin/sqlcmd + /usr/local/bin/sqlcmd --version - name: Create test database env: @@ -121,6 +141,7 @@ jobs: - name: sp_PerfCheck assertion harness env: SA_PASSWORD: CI_Test#2026! + SQLCMD_BIN: /usr/local/bin/sqlcmd SQLCMD_CONN_ARGS: "-C" run: | python3 sp_PerfCheck/tests/run_tests.py --server localhost --password "$SA_PASSWORD" @@ -128,6 +149,7 @@ jobs: - name: sp_HumanEvents assertion harness env: SA_PASSWORD: CI_Test#2026! + SQLCMD_BIN: /usr/local/bin/sqlcmd SQLCMD_CONN_ARGS: "-C" run: | python3 sp_HumanEvents/tests/run_tests.py --server localhost --password "$SA_PASSWORD" From ca3b1ddc527137d92933132e1f0c7b43dd0e316b Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Sat, 18 Jul 2026 06:04:57 -0400 Subject: [PATCH 3/7] CI: install the bundle into master so the assertion harnesses find the procs The harnesses connect to master (as on a local box), but CI installed the procs only into DarlingData_CI_Test, so the preflight reported sp_PerfCheck missing. Add a master install before the harness steps. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/sql-tests.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/sql-tests.yml b/.github/workflows/sql-tests.yml index ba72f3a9..f04f9edd 100644 --- a/.github/workflows/sql-tests.yml +++ b/.github/workflows/sql-tests.yml @@ -129,6 +129,15 @@ jobs: 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" + # The harnesses connect to master and run EXECUTE dbo.sp_... there (as they + # do on a local box, where these procs live in master), so install the + # bundle into master too. The smoke tests above use DarlingData_CI_Test. + - name: Install procedures into master (for the assertion harnesses) + env: + SA_PASSWORD: CI_Test#2026! + run: | + /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P "$SA_PASSWORD" -C -No -b -d master -i "Install-All/DarlingData.sql" + # Assertion harnesses. These EXECUTE the output the procedures generate # (findings, session DDL) rather than just running them, which is what # catches the "reads correctly but is wrong when run" bugs the smoke tests From c02ca1b96db8e8d84f495bc06620406eaa666778 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Sat, 18 Jul 2026 06:09:31 -0400 Subject: [PATCH 4/7] CI: temp continue-on-error on PerfCheck harness to confirm HumanEvents runs on Linux Co-Authored-By: Claude Opus 4.8 --- .github/workflows/sql-tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/sql-tests.yml b/.github/workflows/sql-tests.yml index f04f9edd..1b128b50 100644 --- a/.github/workflows/sql-tests.yml +++ b/.github/workflows/sql-tests.yml @@ -148,6 +148,7 @@ jobs: # SQLCMD_* environment overrides; -C trusts the container's self-signed # certificate. - name: sp_PerfCheck assertion harness + continue-on-error: true # TEMP: sp_PerfCheck errors on Linux containers (default trace); diagnosing env: SA_PASSWORD: CI_Test#2026! SQLCMD_BIN: /usr/local/bin/sqlcmd From 45f80c7da8ae13e9c8cb70d0dc78978be1952bca Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Sat, 18 Jul 2026 06:39:11 -0400 Subject: [PATCH 5/7] sp_PerfCheck: degrade gracefully when the default trace is unreadable On Linux SQL Server sys.traces still lists a default trace, but fn_trace_gettable cannot read it there and raises Msg 19049, which aborted the whole procedure before it returned any findings. Wrap the default-trace read in TRY/CATCH: on failure leave #trace_events empty (the trace-based checks below then simply find nothing) and record one informational finding (check_id 5004) noting the trace was unreadable. Also covers a missing or corrupt trace file on Windows. Co-Authored-By: Claude Opus 4.8 --- sp_PerfCheck/sp_PerfCheck.sql | 161 +++++++++++++++++++++------------- 1 file changed, 98 insertions(+), 63 deletions(-) diff --git a/sp_PerfCheck/sp_PerfCheck.sql b/sp_PerfCheck/sp_PerfCheck.sql index b37244df..86c10332 100644 --- a/sp_PerfCheck/sp_PerfCheck.sql +++ b/sp_PerfCheck/sp_PerfCheck.sql @@ -1,4 +1,4 @@ -SET ANSI_NULLS ON; +SET ANSI_NULLS ON; SET ANSI_PADDING ON; SET ANSI_WARNINGS ON; SET ARITHABORT ON; @@ -1541,70 +1541,105 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. (116, N'DBCC Event', N'Database'), (137, N'Server Memory Change', N'Server'); - /* Get relevant events from default trace */ - INSERT INTO - #trace_events - ( - event_time, - event_class, - event_subclass, - database_name, - database_id, - file_name, - object_name, - object_type, - duration_ms, - severity, - success, - error, - text_data, - file_growth, - is_auto, - spid - ) - SELECT - event_time = t.StartTime, - event_class = t.EventClass, - event_subclass = t.EventSubClass, - database_name = DB_NAME(t.DatabaseID), - database_id = t.DatabaseID, - file_name = t.FileName, - object_name = t.ObjectName, - object_type = t.ObjectType, - duration_ms = t.Duration / 1000, /* Duration is in microseconds, convert to ms */ - severity = t.Severity, - success = t.Success, - error = t.Error, - text_data = t.TextData, - file_growth = t.IntegerData, /* Size of growth in Data/Log Auto Grow event */ - is_auto = t.IsSystem, - spid = t.SPID - FROM sys.fn_trace_gettable(@trace_path, DEFAULT) AS t - WHERE - ( - /* Auto-grow and auto-shrink events */ - t.EventClass IN (92, 93, 94, 95) - /* DBCC Events */ - OR + /* + Get relevant events from the default trace. On Linux SQL Server + sys.traces still lists a default trace, but fn_trace_gettable cannot + read it there and raises Msg 19049; the same guard also covers a + missing or unreadable trace file. On failure, leave #trace_events + empty - the trace-based checks below then simply find nothing - and + record one informational note, rather than letting the error abort + the whole procedure before it returns any findings. + */ + BEGIN TRY + INSERT INTO + #trace_events ( - t.EventClass = 116 - AND - ( - t.TextData LIKE N'%FREEPROCCACHE%' - OR t.TextData LIKE N'%FREESYSTEMCACHE%' - OR t.TextData LIKE N'%DROPCLEANBUFFERS%' - OR t.TextData LIKE N'%SHRINKDATABASE%' - OR t.TextData LIKE N'%SHRINKFILE%' - OR t.TextData LIKE N'%WRITEPAGE%' - ) + event_time, + event_class, + event_subclass, + database_name, + database_id, + file_name, + object_name, + object_type, + duration_ms, + severity, + success, + error, + text_data, + file_growth, + is_auto, + spid ) - /* Server memory change events */ - OR t.EventClass = 137 - /* Deadlock events - typically not in default trace but including for completeness */ - OR t.EventClass = 148 - ) - /* Look back at the past 7 days of events at most */ - AND t.StartTime > DATEADD(DAY, -7, SYSDATETIME()); + SELECT + event_time = t.StartTime, + event_class = t.EventClass, + event_subclass = t.EventSubClass, + database_name = DB_NAME(t.DatabaseID), + database_id = t.DatabaseID, + file_name = t.FileName, + object_name = t.ObjectName, + object_type = t.ObjectType, + duration_ms = t.Duration / 1000, /* Duration is in microseconds, convert to ms */ + severity = t.Severity, + success = t.Success, + error = t.Error, + text_data = t.TextData, + file_growth = t.IntegerData, /* Size of growth in Data/Log Auto Grow event */ + is_auto = t.IsSystem, + spid = t.SPID + FROM sys.fn_trace_gettable(@trace_path, DEFAULT) AS t + WHERE + ( + /* Auto-grow and auto-shrink events */ + t.EventClass IN (92, 93, 94, 95) + /* DBCC Events */ + OR + ( + t.EventClass = 116 + AND + ( + t.TextData LIKE N'%FREEPROCCACHE%' + OR t.TextData LIKE N'%FREESYSTEMCACHE%' + OR t.TextData LIKE N'%DROPCLEANBUFFERS%' + OR t.TextData LIKE N'%SHRINKDATABASE%' + OR t.TextData LIKE N'%SHRINKFILE%' + OR t.TextData LIKE N'%WRITEPAGE%' + ) + ) + /* Server memory change events */ + OR t.EventClass = 137 + /* Deadlock events - typically not in default trace but including for completeness */ + OR t.EventClass = 148 + ) + /* Look back at the past 7 days of events at most */ + AND t.StartTime > DATEADD(DAY, -7, SYSDATETIME()); + END TRY + BEGIN CATCH + INSERT INTO + #results + ( + check_id, + priority, + category, + finding, + details, + url + ) + VALUES + ( + 5004, + 50, /* Informational: platform limitation, not a problem */ + N'Default Trace', + N'Default Trace Not Readable', + N'The default trace is registered but could not be read (' + + ERROR_MESSAGE() + + N'). Its auto-grow, auto-shrink, and DBCC findings are ' + + N'unavailable. This is expected on Linux SQL Server, where ' + + N'the default trace cannot be read via fn_trace_gettable.', + N'https://erikdarling.com/sp_perfcheck/' + ); + END CATCH; /* Update event names from map */ UPDATE From 5f1109b9e82f2dd015b365e097eaec8a4e4d33c2 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Sat, 18 Jul 2026 06:39:22 -0400 Subject: [PATCH 6/7] CI: connect the assertion harnesses with encryption disabled The go-sqlcmd the harnesses use failed to connect to the SQL Server 2017 container instantly: the modern Go TLS stack rejects that old container's self-signed certificate outright, and -C (trust cert) does not bypass it, so the harness read the empty result as "proc not installed." Disable encryption (-N disable) so no TLS handshake happens - there is nothing to secure on a localhost throwaway container. Removes the temporary continue-on-error on the sp_PerfCheck step now that it degrades cleanly on Linux and passes there. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/sql-tests.yml | 11 ++++++----- sp_HumanEvents/tests/run_tests.py | 5 +++-- sp_PerfCheck/tests/run_tests.py | 5 +++-- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/.github/workflows/sql-tests.yml b/.github/workflows/sql-tests.yml index 1b128b50..11399427 100644 --- a/.github/workflows/sql-tests.yml +++ b/.github/workflows/sql-tests.yml @@ -145,14 +145,15 @@ jobs: # databases, sessions, and config); only PerfCheck and HumanEvents run # here so far - IndexCleanup and ReproBuilder need a data source the CI # containers do not have yet. They use the go-based sqlcmd via the - # SQLCMD_* environment overrides; -C trusts the container's self-signed - # certificate. + # SQLCMD_* environment overrides. Encryption is disabled (-N disable): + # the modern Go TLS stack rejects the SQL Server 2017 container's + # self-signed certificate outright, and -C (trust cert) does not bypass + # that; there is nothing to secure on a localhost throwaway container. - name: sp_PerfCheck assertion harness - continue-on-error: true # TEMP: sp_PerfCheck errors on Linux containers (default trace); diagnosing env: SA_PASSWORD: CI_Test#2026! SQLCMD_BIN: /usr/local/bin/sqlcmd - SQLCMD_CONN_ARGS: "-C" + SQLCMD_CONN_ARGS: "-C -N disable" run: | python3 sp_PerfCheck/tests/run_tests.py --server localhost --password "$SA_PASSWORD" @@ -160,6 +161,6 @@ jobs: env: SA_PASSWORD: CI_Test#2026! SQLCMD_BIN: /usr/local/bin/sqlcmd - SQLCMD_CONN_ARGS: "-C" + SQLCMD_CONN_ARGS: "-C -N disable" run: | python3 sp_HumanEvents/tests/run_tests.py --server localhost --password "$SA_PASSWORD" diff --git a/sp_HumanEvents/tests/run_tests.py b/sp_HumanEvents/tests/run_tests.py index 8656514c..b2e72ca2 100644 --- a/sp_HumanEvents/tests/run_tests.py +++ b/sp_HumanEvents/tests/run_tests.py @@ -85,8 +85,9 @@ def _sqlcmd_prefix(): """The sqlcmd binary plus any connection args, overridable via environment so one harness runs both locally and in CI. Locally SQLCMD_BIN defaults to the go-based 'sqlcmd' on PATH and SQLCMD_CONN_ARGS is empty; CI points SQLCMD_BIN - at its own binary and sets SQLCMD_CONN_ARGS to the cert-trust flag its - container connection needs (e.g. '-C').""" + at its own binary and sets SQLCMD_CONN_ARGS to '-C -N disable' -- trust the + self-signed cert and disable encryption, since the modern Go TLS stack + rejects the SQL Server 2017 container's certificate outright.""" return [os.environ.get("SQLCMD_BIN", "sqlcmd")] + shlex.split( os.environ.get("SQLCMD_CONN_ARGS", "")) diff --git a/sp_PerfCheck/tests/run_tests.py b/sp_PerfCheck/tests/run_tests.py index bdf3a653..39f4c885 100644 --- a/sp_PerfCheck/tests/run_tests.py +++ b/sp_PerfCheck/tests/run_tests.py @@ -98,8 +98,9 @@ def _sqlcmd_prefix(): """The sqlcmd binary plus any connection args, overridable via environment so one harness runs both locally and in CI. Locally SQLCMD_BIN defaults to the go-based 'sqlcmd' on PATH and SQLCMD_CONN_ARGS is empty; CI points SQLCMD_BIN - at its own binary and sets SQLCMD_CONN_ARGS to the cert-trust flag its - container connection needs (e.g. '-C').""" + at its own binary and sets SQLCMD_CONN_ARGS to '-C -N disable' -- trust the + self-signed cert and disable encryption, since the modern Go TLS stack + rejects the SQL Server 2017 container's certificate outright.""" return [os.environ.get("SQLCMD_BIN", "sqlcmd")] + shlex.split( os.environ.get("SQLCMD_CONN_ARGS", "")) From 3c65b1c0c82f1b37f4ec6022515869dfeceb7e3c Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Sat, 18 Jul 2026 06:47:44 -0400 Subject: [PATCH 7/7] sp_PerfCheck harness: flush pending config before baselining The "zero net configuration change" meta-assertion failed on the SQL Server 2017 CI container: its image ships 'clr strict security' configured on but not yet in use (value=1, value_in_use=0), a staged change. The harness's own RECONFIGURE (for its SAFE_OPTIONS) applied that pending change mid-run, so it surfaced as a net config change the harness never made. Issue RECONFIGURE before capturing the baseline so the comparison measures only the harness's own effect. Verified by staging an equivalent pending change locally: fails without this, passes with it. Co-Authored-By: Claude Opus 4.8 --- sp_PerfCheck/tests/run_tests.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sp_PerfCheck/tests/run_tests.py b/sp_PerfCheck/tests/run_tests.py index 39f4c885..97db6da9 100644 --- a/sp_PerfCheck/tests/run_tests.py +++ b/sp_PerfCheck/tests/run_tests.py @@ -316,6 +316,14 @@ def structural_tests(server, password, R): def forced_condition_tests(server, password, R): """Bidirectional forced-condition assertions on the check_id 1000 Non-Default Configuration check. Captures and restores exact originals.""" + # Flush any pre-existing pending config change (value <> value_in_use) before + # baselining. Some images ship one: the SQL Server 2017 CI container has + # 'clr strict security' configured on but not yet in use. The harness's own + # RECONFIGURE calls would apply that staged change mid-run, and it would then + # surface as a spurious net change the harness never made. _sqlcmd does not + # raise on a SQL error, so a rejected RECONFIGURE is tolerated here. + _sqlcmd(server, password, "RECONFIGURE;", headers=False) + # Snapshot the entire config before touching anything. before = snapshot_config(server, password)