Skip to content
1 change: 1 addition & 0 deletions eng/pipelines/pr-validation-pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,7 @@ jobs:
python benchmarks/perf-benchmarking.py --baseline benchmark_baseline.json --json benchmark_results.json
displayName: 'Run performance benchmarks on macOS $(sqlVersion)'
condition: or(eq(variables['sqlVersion'], 'SQL2022'), eq(variables['sqlVersion'], 'SQL2025'))
timeoutInMinutes: 20
continueOnError: true
env:
DB_CONNECTION_STRING: 'Server=tcp:127.0.0.1,1433;Database=AdventureWorks2022;Uid=SA;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes'
Expand Down
19 changes: 19 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,25 @@
import time


def is_qemu_emulated():
"""Detect if running under QEMU user-mode emulation (e.g. ARM64 on x86_64 host).

QEMU reports CPU implementer 0x51 in /proc/cpuinfo. Native ARM64 hardware
uses vendor-specific IDs (0x41 ARM, 0x61 Apple, etc.).
"""
try:
with open("/proc/cpuinfo") as f:
for line in f:
if line.startswith("CPU implementer") and "0x51" in line:
return True
except (FileNotFoundError, PermissionError):
pass
return False


QEMU = is_qemu_emulated()


def is_azure_sql_connection(conn_str):
"""Helper function to detect if connection string is for Azure SQL Database"""
if not conn_str:
Expand Down
36 changes: 21 additions & 15 deletions tests/test_013_SqlHandle_free_shutdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,13 @@

import pytest

from conftest import QEMU


@pytest.mark.skipif(
QEMU,
reason="Subprocess shutdown tests SIGSEGV under QEMU user-mode emulation — not reproducible on native ARM64",
)
class TestHandleFreeShutdown:
"""Test SqlHandle::free() behavior for all handle types during Python shutdown."""

Expand Down Expand Up @@ -85,7 +91,7 @@ def test_aggressive_dbc_segfault_reproduction(self, conn_str):
""")

result = subprocess.run(
[sys.executable, "-c", script], capture_output=True, text=True, timeout=5
[sys.executable, "-c", script], capture_output=True, text=True, timeout=15
)

# Check for segfault
Expand Down Expand Up @@ -141,7 +147,7 @@ def on_exit():
""")

result = subprocess.run(
[sys.executable, "-c", script], capture_output=True, text=True, timeout=5
[sys.executable, "-c", script], capture_output=True, text=True, timeout=15
)

if result.returncode < 0:
Expand Down Expand Up @@ -205,7 +211,7 @@ def test_force_gc_finalization_order_issue(self, conn_str):
""")

result = subprocess.run(
[sys.executable, "-c", script], capture_output=True, text=True, timeout=5
[sys.executable, "-c", script], capture_output=True, text=True, timeout=15
)

if result.returncode < 0:
Expand Down Expand Up @@ -247,7 +253,7 @@ def test_stmt_handle_cleanup_at_shutdown(self, conn_str):
""")

result = subprocess.run(
[sys.executable, "-c", script], capture_output=True, text=True, timeout=5
[sys.executable, "-c", script], capture_output=True, text=True, timeout=15
)

assert result.returncode == 0, f"Process crashed. stderr: {result.stderr}"
Expand Down Expand Up @@ -290,7 +296,7 @@ def test_dbc_handle_cleanup_at_shutdown(self, conn_str):
""")

result = subprocess.run(
[sys.executable, "-c", script], capture_output=True, text=True, timeout=5
[sys.executable, "-c", script], capture_output=True, text=True, timeout=15
)

assert result.returncode == 0, f"Process crashed. stderr: {result.stderr}"
Expand Down Expand Up @@ -338,7 +344,7 @@ def test_env_handle_cleanup_at_shutdown(self, conn_str):
""")

result = subprocess.run(
[sys.executable, "-c", script], capture_output=True, text=True, timeout=5
[sys.executable, "-c", script], capture_output=True, text=True, timeout=15
)

assert result.returncode == 0, f"Process crashed. stderr: {result.stderr}"
Expand Down Expand Up @@ -410,7 +416,7 @@ def test_mixed_handle_cleanup_at_shutdown(self, conn_str):
""")

result = subprocess.run(
[sys.executable, "-c", script], capture_output=True, text=True, timeout=5
[sys.executable, "-c", script], capture_output=True, text=True, timeout=15
)

assert result.returncode == 0, f"Process crashed. stderr: {result.stderr}"
Expand Down Expand Up @@ -463,7 +469,7 @@ def test_rapid_connection_churn_with_shutdown(self, conn_str):
""")

result = subprocess.run(
[sys.executable, "-c", script], capture_output=True, text=True, timeout=5
[sys.executable, "-c", script], capture_output=True, text=True, timeout=15
)

assert result.returncode == 0, f"Process crashed. stderr: {result.stderr}"
Expand Down Expand Up @@ -502,7 +508,7 @@ def test_exception_during_query_with_shutdown(self, conn_str):
""")

result = subprocess.run(
[sys.executable, "-c", script], capture_output=True, text=True, timeout=5
[sys.executable, "-c", script], capture_output=True, text=True, timeout=15
)

assert result.returncode == 0, f"Process crashed. stderr: {result.stderr}"
Expand Down Expand Up @@ -555,7 +561,7 @@ def callback(ref):
""")

result = subprocess.run(
[sys.executable, "-c", script], capture_output=True, text=True, timeout=5
[sys.executable, "-c", script], capture_output=True, text=True, timeout=15
)

assert result.returncode == 0, f"Process crashed. stderr: {result.stderr}"
Expand Down Expand Up @@ -613,7 +619,7 @@ def execute_query(self):
""")

result = subprocess.run(
[sys.executable, "-c", script], capture_output=True, text=True, timeout=5
[sys.executable, "-c", script], capture_output=True, text=True, timeout=15
)

assert result.returncode == 0, f"Process crashed. stderr: {result.stderr}"
Expand Down Expand Up @@ -685,7 +691,7 @@ def test_all_handle_types_comprehensive(self, conn_str):
""")

result = subprocess.run(
[sys.executable, "-c", script], capture_output=True, text=True, timeout=5
[sys.executable, "-c", script], capture_output=True, text=True, timeout=15
)

assert result.returncode == 0, f"Process crashed. stderr: {result.stderr}"
Expand Down Expand Up @@ -940,7 +946,7 @@ def test_cleanup_connections_scenarios(self, conn_str, scenario, test_code, expe
""")

result = subprocess.run(
[sys.executable, "-c", script], capture_output=True, text=True, timeout=3
[sys.executable, "-c", script], capture_output=True, text=True, timeout=15
)

assert result.returncode == 0, f"Test failed. stderr: {result.stderr}"
Expand Down Expand Up @@ -1126,7 +1132,7 @@ def close(self):
""")

result = subprocess.run(
[sys.executable, "-c", script], capture_output=True, text=True, timeout=3
[sys.executable, "-c", script], capture_output=True, text=True, timeout=15
)

assert result.returncode == 0, f"Test failed. stderr: {result.stderr}"
Expand Down Expand Up @@ -1216,7 +1222,7 @@ def close(self):
""")

result = subprocess.run(
[sys.executable, "-c", script], capture_output=True, text=True, timeout=3
[sys.executable, "-c", script], capture_output=True, text=True, timeout=15
)

assert result.returncode == 0, f"Test failed. stderr: {result.stderr}"
Expand Down
2 changes: 2 additions & 0 deletions tests/test_022_concurrent_query_gil_release.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ def _run_waitfor(conn_str: str) -> float:
# ============================================================================


@pytest.mark.stress # Heartbeat tick counts flake under CI CPU contention (macOS Py3.14)
def test_query_does_not_block_other_python_threads(conn_str):
"""
While one thread executes a 2-second ``WAITFOR DELAY``, a second pure-Python
Expand Down Expand Up @@ -134,6 +135,7 @@ def run_query():
# ============================================================================


@pytest.mark.stress # Heartbeat tick counts flake under CI CPU contention (macOS Py3.14)
def test_commit_does_not_block_other_python_threads(conn_str):
"""
Smoke test for the SQLEndTran GIL-release added to ``Connection::commit``
Expand Down
Loading