diff --git a/eng/pipelines/pr-validation-pipeline.yml b/eng/pipelines/pr-validation-pipeline.yml index d2ede2470..2f09b48fe 100644 --- a/eng/pipelines/pr-validation-pipeline.yml +++ b/eng/pipelines/pr-validation-pipeline.yml @@ -50,32 +50,112 @@ jobs: pool: vmImage: 'windows-latest' + strategy: + matrix: + LocalDB: + sqlVersion: 'LocalDB' + pythonVersion: '3.13' + SQLServer2022: + sqlVersion: 'SQL2022' + pythonVersion: '3.13' + LocalDB_Python314: + sqlVersion: 'LocalDB' + pythonVersion: '3.14' + steps: - task: UsePythonVersion@0 inputs: - versionSpec: '3.13' + versionSpec: '$(pythonVersion)' addToPath: true githubToken: $(GITHUB_TOKEN) - displayName: 'Use Python 3.13' + displayName: 'Use Python $(pythonVersion)' - script: | python -m pip install --upgrade pip pip install -r requirements.txt displayName: 'Install dependencies' - # Start LocalDB instance + # Start LocalDB instance (for LocalDB matrix) - powershell: | sqllocaldb create MSSQLLocalDB sqllocaldb start MSSQLLocalDB displayName: 'Start LocalDB instance' + condition: eq(variables['sqlVersion'], 'LocalDB') - # Create database and user + # Create database and user for LocalDB - powershell: | sqlcmd -S "(localdb)\MSSQLLocalDB" -Q "CREATE DATABASE TestDB" sqlcmd -S "(localdb)\MSSQLLocalDB" -Q "CREATE LOGIN testuser WITH PASSWORD = '$(DB_PASSWORD)'" sqlcmd -S "(localdb)\MSSQLLocalDB" -d TestDB -Q "CREATE USER testuser FOR LOGIN testuser" sqlcmd -S "(localdb)\MSSQLLocalDB" -d TestDB -Q "ALTER ROLE db_owner ADD MEMBER testuser" - displayName: 'Setup database and user' + displayName: 'Setup database and user for LocalDB' + condition: eq(variables['sqlVersion'], 'LocalDB') + env: + DB_PASSWORD: $(DB_PASSWORD) + + # Install SQL Server 2022 (for SQL2022 matrix) + - powershell: | + Write-Host "Downloading SQL Server 2022 Express..." + # Download SQL Server 2022 Express installer + $ProgressPreference = 'SilentlyContinue' + Invoke-WebRequest -Uri "https://go.microsoft.com/fwlink/p/?linkid=2216019" -OutFile "SQL2022-SSEI-Expr.exe" + + Write-Host "Installing SQL Server 2022 Express..." + # Install SQL Server 2022 Express with basic features + Start-Process -FilePath "SQL2022-SSEI-Expr.exe" -ArgumentList "/Action=Download","/MediaPath=$env:TEMP","/MediaType=Core","/Quiet" -Wait + + # Find the downloaded setup file + $setupFile = Get-ChildItem -Path $env:TEMP -Filter "SQLEXPR_x64_ENU.exe" -Recurse | Select-Object -First 1 + + if ($setupFile) { + Write-Host "Extracting SQL Server setup files..." + Start-Process -FilePath $setupFile.FullName -ArgumentList "/x:$env:TEMP\SQLSetup","/u" -Wait + + Write-Host "Running SQL Server setup..." + Start-Process -FilePath "$env:TEMP\SQLSetup\setup.exe" -ArgumentList "/Q","/ACTION=Install","/FEATURES=SQLEngine","/INSTANCENAME=MSSQLSERVER","/SQLSVCACCOUNT=`"NT AUTHORITY\SYSTEM`"","/SQLSYSADMINACCOUNTS=`"BUILTIN\Administrators`"","/TCPENABLED=1","/SECURITYMODE=SQL","/SAPWD=$(DB_PASSWORD)","/IACCEPTSQLSERVERLICENSETERMS" -Wait + } else { + Write-Error "Failed to download SQL Server setup file" + exit 1 + } + + Write-Host "SQL Server 2022 installation completed" + displayName: 'Install SQL Server 2022 Express' + condition: eq(variables['sqlVersion'], 'SQL2022') + env: + DB_PASSWORD: $(DB_PASSWORD) + + # Create database for SQL Server 2022 + - powershell: | + # Wait for SQL Server to start + $maxAttempts = 30 + $attempt = 0 + $connected = $false + + Write-Host "Waiting for SQL Server 2022 to start..." + while (-not $connected -and $attempt -lt $maxAttempts) { + try { + sqlcmd -S "localhost" -U "sa" -P "$(DB_PASSWORD)" -Q "SELECT 1" -C + $connected = $true + Write-Host "SQL Server is ready!" + } catch { + $attempt++ + Write-Host "Waiting... ($attempt/$maxAttempts)" + Start-Sleep -Seconds 2 + } + } + + if (-not $connected) { + Write-Error "Failed to connect to SQL Server after $maxAttempts attempts" + exit 1 + } + + # Create database and user + sqlcmd -S "localhost" -U "sa" -P "$(DB_PASSWORD)" -Q "CREATE DATABASE TestDB" -C + sqlcmd -S "localhost" -U "sa" -P "$(DB_PASSWORD)" -Q "CREATE LOGIN testuser WITH PASSWORD = '$(DB_PASSWORD)'" -C + sqlcmd -S "localhost" -U "sa" -P "$(DB_PASSWORD)" -d TestDB -Q "CREATE USER testuser FOR LOGIN testuser" -C + sqlcmd -S "localhost" -U "sa" -P "$(DB_PASSWORD)" -d TestDB -Q "ALTER ROLE db_owner ADD MEMBER testuser" -C + displayName: 'Setup database and user for SQL Server 2022' + condition: eq(variables['sqlVersion'], 'SQL2022') env: DB_PASSWORD: $(DB_PASSWORD) @@ -84,31 +164,48 @@ jobs: build.bat x64 displayName: 'Build .pyd file' + # Run tests for LocalDB - script: | - python -m pytest -v --junitxml=test-results.xml --cov=. --cov-report=xml --capture=tee-sys --cache-clear - displayName: 'Run tests with coverage' + python -m pytest -v --junitxml=test-results-localdb.xml --cov=. --cov-report=xml:coverage-localdb.xml --capture=tee-sys --cache-clear + displayName: 'Run tests with coverage on LocalDB' + condition: eq(variables['sqlVersion'], 'LocalDB') env: DB_CONNECTION_STRING: 'Server=(localdb)\MSSQLLocalDB;Database=TestDB;Uid=testuser;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes' - - task: PublishBuildArtifacts@1 + # Run tests for SQL Server 2022 + - script: | + python -m pytest -v --junitxml=test-results-sql2022.xml --cov=. --cov-report=xml:coverage-sql2022.xml --capture=tee-sys --cache-clear + displayName: 'Run tests with coverage on SQL Server 2022' + condition: eq(variables['sqlVersion'], 'SQL2022') + env: + DB_CONNECTION_STRING: 'Server=localhost;Database=TestDB;Uid=testuser;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes' + + - task: CopyFiles@2 inputs: - PathtoPublish: 'mssql_python/ddbc_bindings.cp313-amd64.pyd' - ArtifactName: 'ddbc_bindings' - publishLocation: 'Container' - displayName: 'Publish pyd file as artifact' + SourceFolder: 'mssql_python' + Contents: 'ddbc_bindings.cp*-amd64.pyd' + TargetFolder: '$(Build.ArtifactStagingDirectory)' + displayName: 'Copy pyd file to staging' + + - task: CopyFiles@2 + inputs: + SourceFolder: 'mssql_python' + Contents: 'ddbc_bindings.cp*-amd64.pdb' + TargetFolder: '$(Build.ArtifactStagingDirectory)' + displayName: 'Copy pdb file to staging' - task: PublishBuildArtifacts@1 inputs: - PathtoPublish: 'mssql_python/ddbc_bindings.cp313-amd64.pdb' + PathtoPublish: '$(Build.ArtifactStagingDirectory)' ArtifactName: 'ddbc_bindings' publishLocation: 'Container' - displayName: 'Publish pdb file as artifact' + displayName: 'Publish build artifacts' - task: PublishTestResults@2 condition: succeededOrFailed() inputs: - testResultsFiles: '**/test-results.xml' - testRunTitle: 'Publish test results' + testResultsFiles: '**/test-results-*.xml' + testRunTitle: 'Publish test results for Windows $(sqlVersion)' # - task: PublishCodeCoverageResults@1 # inputs: @@ -190,7 +287,7 @@ jobs: python -m pytest -v --junitxml=test-results.xml --cov=. --cov-report=xml --capture=tee-sys --cache-clear displayName: 'Run pytest with coverage' env: - DB_CONNECTION_STRING: 'Driver=ODBC Driver 18 for SQL Server;Server=tcp:127.0.0.1,1433;Database=master;Uid=SA;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes' + DB_CONNECTION_STRING: 'Server=tcp:127.0.0.1,1433;Database=master;Uid=SA;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes' DB_PASSWORD: $(DB_PASSWORD) - task: PublishTestResults@2 @@ -209,9 +306,23 @@ jobs: Ubuntu: dockerImage: 'ubuntu:22.04' distroName: 'Ubuntu' + sqlServerImage: 'mcr.microsoft.com/mssql/server:2022-latest' + useAzureSQL: 'false' + Ubuntu_SQL2025: + dockerImage: 'ubuntu:22.04' + distroName: 'Ubuntu-SQL2025' + sqlServerImage: 'mcr.microsoft.com/mssql/server:2025-latest' + useAzureSQL: 'false' + Ubuntu_AzureSQL: + dockerImage: 'ubuntu:22.04' + distroName: 'Ubuntu-AzureSQL' + sqlServerImage: '' + useAzureSQL: 'true' Debian: dockerImage: 'debian:12' distroName: 'Debian' + sqlServerImage: 'mcr.microsoft.com/mssql/server:2022-latest' + useAzureSQL: 'false' steps: - script: | @@ -230,7 +341,7 @@ jobs: -e ACCEPT_EULA=Y \ -e MSSQL_SA_PASSWORD="$(DB_PASSWORD)" \ -p 1433:1433 \ - mcr.microsoft.com/mssql/server:2022-latest + $(sqlServerImage) # Wait for SQL Server to be ready echo "Waiting for SQL Server to start..." @@ -256,6 +367,7 @@ jobs: -P "$(DB_PASSWORD)" \ -C -Q "CREATE DATABASE TestDB" displayName: 'Start SQL Server container for $(distroName)' + condition: eq(variables['useAzureSQL'], 'false') env: DB_PASSWORD: $(DB_PASSWORD) @@ -354,20 +466,35 @@ jobs: - script: | # Run tests in the container - # Get SQL Server container IP - SQLSERVER_IP=$(docker inspect sqlserver-$(distroName) --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}') - echo "SQL Server IP: $SQLSERVER_IP" - - docker exec \ - -e DB_CONNECTION_STRING="Driver=ODBC Driver 18 for SQL Server;Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes" \ - -e DB_PASSWORD="$(DB_PASSWORD)" \ - test-container-$(distroName) bash -c " - source /opt/venv/bin/activate - echo 'Build successful, running tests now on $(distroName)' - echo 'Using connection string: Driver=ODBC Driver 18 for SQL Server;Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=***;TrustServerCertificate=yes' - python -m pytest -v --junitxml=test-results-$(distroName).xml --cov=. --cov-report=xml:coverage-$(distroName).xml --capture=tee-sys --cache-clear - " + if [ "$(useAzureSQL)" = "true" ]; then + # Azure SQL Database testing + echo "Testing against Azure SQL Database" + + docker exec \ + -e DB_CONNECTION_STRING="$(AZURE_CONNECTION_STRING)" \ + test-container-$(distroName) bash -c " + source /opt/venv/bin/activate + echo 'Build successful, running tests now on $(distroName) with Azure SQL' + echo 'Using Azure SQL connection string' + python -m pytest -v --junitxml=test-results-$(distroName).xml --cov=. --cov-report=xml:coverage-$(distroName).xml --capture=tee-sys --cache-clear + " + else + # Local SQL Server testing + SQLSERVER_IP=$(docker inspect sqlserver-$(distroName) --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}') + echo "SQL Server IP: $SQLSERVER_IP" + + docker exec \ + -e DB_CONNECTION_STRING="Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes" \ + -e DB_PASSWORD="$(DB_PASSWORD)" \ + test-container-$(distroName) bash -c " + source /opt/venv/bin/activate + echo 'Build successful, running tests now on $(distroName)' + echo 'Using connection string: Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=***;TrustServerCertificate=yes' + python -m pytest -v --junitxml=test-results-$(distroName).xml --cov=. --cov-report=xml:coverage-$(distroName).xml --capture=tee-sys --cache-clear + " + fi displayName: 'Run pytest with coverage in $(distroName) container' + condition: or(eq(variables['useAzureSQL'], 'false'), and(eq(variables['useAzureSQL'], 'true'), ne(variables['AZURE_CONNECTION_STRING'], ''))) env: DB_PASSWORD: $(DB_PASSWORD) @@ -382,8 +509,10 @@ jobs: # Clean up containers docker stop test-container-$(distroName) || true docker rm test-container-$(distroName) || true - docker stop sqlserver-$(distroName) || true - docker rm sqlserver-$(distroName) || true + if [ "$(useAzureSQL)" = "false" ]; then + docker stop sqlserver-$(distroName) || true + docker rm sqlserver-$(distroName) || true + fi displayName: 'Clean up $(distroName) containers' condition: always() @@ -570,13 +699,13 @@ jobs: echo "SQL Server IP: $SQLSERVER_IP" docker exec \ - -e DB_CONNECTION_STRING="Driver=ODBC Driver 18 for SQL Server;Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes" \ + -e DB_CONNECTION_STRING="Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes" \ -e DB_PASSWORD="$(DB_PASSWORD)" \ test-container-$(distroName)-$(archName) bash -c " source /opt/venv/bin/activate echo 'Build successful, running tests now on $(distroName) ARM64' echo 'Architecture:' \$(uname -m) - echo 'Using connection string: Driver=ODBC Driver 18 for SQL Server;Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=***;TrustServerCertificate=yes' + echo 'Using connection string: Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=***;TrustServerCertificate=yes' python main.py python -m pytest -v --junitxml=test-results-$(distroName)-$(archName).xml --cov=. --cov-report=xml:coverage-$(distroName)-$(archName).xml --capture=tee-sys --cache-clear " @@ -778,12 +907,12 @@ jobs: echo "SQL Server IP: $SQLSERVER_IP" docker exec \ - -e DB_CONNECTION_STRING="Driver=ODBC Driver 18 for SQL Server;Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes" \ + -e DB_CONNECTION_STRING="Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes" \ -e DB_PASSWORD="$(DB_PASSWORD)" \ test-container-rhel9 bash -c " source myvenv/bin/activate echo 'Build successful, running tests now on RHEL 9' - echo 'Using connection string: Driver=ODBC Driver 18 for SQL Server;Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=***;TrustServerCertificate=yes' + echo 'Using connection string: Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=***;TrustServerCertificate=yes' python main.py python -m pytest -v --junitxml=test-results-rhel9.xml --cov=. --cov-report=xml:coverage-rhel9.xml --capture=tee-sys --cache-clear " @@ -997,13 +1126,13 @@ jobs: echo "SQL Server IP: $SQLSERVER_IP" docker exec \ - -e DB_CONNECTION_STRING="Driver=ODBC Driver 18 for SQL Server;Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes" \ + -e DB_CONNECTION_STRING="Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes" \ -e DB_PASSWORD="$(DB_PASSWORD)" \ test-container-rhel9-arm64 bash -c " source myvenv/bin/activate echo 'Build successful, running tests now on RHEL 9 ARM64' echo 'Architecture:' \$(uname -m) - echo 'Using connection string: Driver=ODBC Driver 18 for SQL Server;Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=***;TrustServerCertificate=yes' + echo 'Using connection string: Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=***;TrustServerCertificate=yes' python -m pytest -v --junitxml=test-results-rhel9-arm64.xml --cov=. --cov-report=xml:coverage-rhel9-arm64.xml --capture=tee-sys --cache-clear " displayName: 'Run pytest with coverage in RHEL 9 ARM64 container' @@ -1225,13 +1354,13 @@ jobs: echo "SQL Server IP: $SQLSERVER_IP" docker exec \ - -e DB_CONNECTION_STRING="Driver=ODBC Driver 18 for SQL Server;Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes" \ + -e DB_CONNECTION_STRING="Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes" \ -e DB_PASSWORD="$(DB_PASSWORD)" \ test-container-alpine bash -c " echo 'Build successful, running tests now on Alpine x86_64' echo 'Architecture:' \$(uname -m) echo 'Alpine version:' \$(cat /etc/alpine-release) - echo 'Using connection string: Driver=ODBC Driver 18 for SQL Server;Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=***;TrustServerCertificate=yes' + echo 'Using connection string: Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=***;TrustServerCertificate=yes' # Activate virtual environment source /workspace/venv/bin/activate @@ -1467,13 +1596,13 @@ jobs: echo "SQL Server IP: $SQLSERVER_IP" docker exec \ - -e DB_CONNECTION_STRING="Driver=ODBC Driver 18 for SQL Server;Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes" \ + -e DB_CONNECTION_STRING="Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes" \ -e DB_PASSWORD="$(DB_PASSWORD)" \ test-container-alpine-arm64 bash -c " echo 'Build successful, running tests now on Alpine ARM64' echo 'Architecture:' \$(uname -m) echo 'Alpine version:' \$(cat /etc/alpine-release) - echo 'Using connection string: Driver=ODBC Driver 18 for SQL Server;Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=***;TrustServerCertificate=yes' + echo 'Using connection string: Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=***;TrustServerCertificate=yes' # Activate virtual environment source /workspace/venv/bin/activate @@ -1574,7 +1703,7 @@ jobs: lcov_cobertura total.info --output unified-coverage/coverage.xml displayName: 'Generate unified coverage (Python + C++)' env: - DB_CONNECTION_STRING: 'Driver=ODBC Driver 18 for SQL Server;Server=tcp:127.0.0.1,1433;Database=master;Uid=SA;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes' + DB_CONNECTION_STRING: 'Server=tcp:127.0.0.1,1433;Database=master;Uid=SA;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes' DB_PASSWORD: $(DB_PASSWORD) - task: PublishTestResults@2 diff --git a/tests/conftest.py b/tests/conftest.py index 20b589d54..44a24fbb7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,14 +5,27 @@ - conn_str: Fixture to get the connection string from environment variables. - db_connection: Fixture to create and yield a database connection. - cursor: Fixture to create and yield a cursor from the database connection. +- is_azure_sql_connection: Helper function to detect Azure SQL Database connections. """ import pytest import os +import re from mssql_python import connect import time +def is_azure_sql_connection(conn_str): + """Helper function to detect if connection string is for Azure SQL Database""" + if not conn_str: + return False + # Check if database.windows.net appears in the Server parameter + conn_str_lower = conn_str.lower() + # Look for Server= or server= followed by database.windows.net + server_match = re.search(r'server\s*=\s*[^;]*database\.windows\.net', conn_str_lower) + return server_match is not None + + def pytest_configure(config): # Add any necessary configuration here pass diff --git a/tests/test_003_connection.py b/tests/test_003_connection.py index 9526d1584..d631ea362 100644 --- a/tests/test_003_connection.py +++ b/tests/test_003_connection.py @@ -43,6 +43,7 @@ import struct from datetime import datetime, timedelta, timezone from mssql_python.constants import ConstantsDDBC +from conftest import is_azure_sql_connection @pytest.fixture(autouse=True) @@ -415,10 +416,11 @@ def test_connection_timeout_invalid_password(conn_str): with pytest.raises(Exception): connect(bad_conn_str) elapsed = time.perf_counter() - start - # Should fail quickly (within 10 seconds) + # Azure SQL takes longer to timeout, so use different thresholds + timeout_threshold = 30 if is_azure_sql_connection(conn_str) else 10 assert ( - elapsed < 10 - ), f"Connection with invalid password took too long: {elapsed:.2f}s" + elapsed < timeout_threshold + ), f"Connection with invalid password took too long: {elapsed:.2f}s (threshold: {timeout_threshold}s)" def test_connection_timeout_invalid_host(conn_str): @@ -3639,7 +3641,7 @@ def test_execute_after_connection_close(conn_str): ), "Error should mention the connection is closed" -def test_execute_multiple_simultaneous_cursors(db_connection): +def test_execute_multiple_simultaneous_cursors(db_connection, conn_str): """Test creating and using many cursors simultaneously through Connection.execute ⚠️ WARNING: This test has several limitations: @@ -3648,12 +3650,16 @@ def test_execute_multiple_simultaneous_cursors(db_connection): 3. Memory measurement requires the optional 'psutil' package 4. Creates cursors sequentially rather than truly concurrently 5. Results may vary based on system resources, SQL Server version, and ODBC driver + 6. Skipped for Azure SQL due to connection pool and throttling limitations The test verifies that: - Multiple cursors can be created and used simultaneously - Connection tracks created cursors appropriately - Connection remains stable after intensive cursor operations """ + # Skip this test for Azure SQL Database + if is_azure_sql_connection(conn_str): + pytest.skip("Skipping for Azure SQL - connection limits cause this test to hang") import gc import sys @@ -3712,7 +3718,7 @@ def test_execute_multiple_simultaneous_cursors(db_connection): final_cursor.close() -def test_execute_with_large_parameters(db_connection): +def test_execute_with_large_parameters(db_connection, conn_str): """Test executing queries with very large parameter sets ⚠️ WARNING: This test has several limitations: @@ -3722,12 +3728,16 @@ def test_execute_with_large_parameters(db_connection): 4. No streaming parameter support is tested 5. Only tests with 10,000 rows, which is small compared to production scenarios 6. Performance measurements are affected by system load and environment + 7. Skipped for Azure SQL due to connection pool and throttling limitations The test verifies: - Handling of a large number of parameters in batch inserts - Working with parameters near but under the size limit - Processing large result sets """ + # Skip this test for Azure SQL Database + if is_azure_sql_connection(conn_str): + pytest.skip("Skipping for Azure SQL - large parameter tests may cause timeouts") # Test with a temporary table for large data cursor = db_connection.execute( @@ -4289,7 +4299,7 @@ def test_batch_execute_input_validation(db_connection): cursor.close() -def test_batch_execute_large_batch(db_connection): +def test_batch_execute_large_batch(db_connection, conn_str): """Test batch_execute with a large number of statements ⚠️ WARNING: This test has several limitations: @@ -4299,12 +4309,16 @@ def test_batch_execute_large_batch(db_connection): 4. Results must be fully consumed between statements to avoid "Connection is busy" errors 5. Driver-specific limitations may exist for maximum batch sizes 6. Network timeouts during long-running batches aren't tested + 7. Skipped for Azure SQL due to connection pool and throttling limitations The test verifies: - The method can handle multiple statements in sequence - Results are correctly returned for all statements - Memory usage remains reasonable during batch processing """ + # Skip this test for Azure SQL Database + if is_azure_sql_connection(conn_str): + pytest.skip("Skipping for Azure SQL - large batch tests may cause timeouts") # Create a batch of 50 statements statements = ["SELECT " + str(i) for i in range(50)] @@ -4848,92 +4862,6 @@ def test_timeout_from_constructor(conn_str): conn.close() -def test_timeout_long_query(db_connection): - """Test that a query exceeding the timeout raises an exception if supported by driver""" - - cursor = db_connection.cursor() - - try: - # First execute a simple query to check if we can run tests - cursor.execute("SELECT 1") - cursor.fetchall() - except Exception as e: - pytest.skip(f"Skipping timeout test due to connection issue: {e}") - - # Set a short timeout - original_timeout = db_connection.timeout - db_connection.timeout = 2 # 2 seconds - - try: - # Try several different approaches to test timeout - start_time = time.perf_counter() - try: - # Method 1: CPU-intensive query with REPLICATE and large result set - cpu_intensive_query = """ - WITH numbers AS ( - SELECT TOP 1000000 ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS n - FROM sys.objects a CROSS JOIN sys.objects b - ) - SELECT COUNT(*) FROM numbers WHERE n % 2 = 0 - """ - cursor.execute(cpu_intensive_query) - cursor.fetchall() - - elapsed_time = time.perf_counter() - start_time - - # If we get here without an exception, try a different approach - if elapsed_time < 4.5: - - # Method 2: Try with WAITFOR - start_time = time.perf_counter() - cursor.execute("WAITFOR DELAY '00:00:05'") - cursor.fetchall() - elapsed_time = time.perf_counter() - start_time - - # If we still get here, try one more approach - if elapsed_time < 4.5: - - # Method 3: Try with a join that generates many rows - start_time = time.perf_counter() - cursor.execute( - """ - SELECT COUNT(*) FROM sys.objects a, sys.objects b, sys.objects c - WHERE a.object_id = b.object_id * c.object_id - """ - ) - cursor.fetchall() - elapsed_time = time.perf_counter() - start_time - - # If we still get here without an exception - if elapsed_time < 4.5: - pytest.skip("Timeout feature not enforced by database driver") - - except Exception as e: - # Verify this is a timeout exception - elapsed_time = time.perf_counter() - start_time - assert elapsed_time < 4.5, "Exception occurred but after expected timeout" - error_text = str(e).lower() - - # Check for various error messages that might indicate timeout - timeout_indicators = [ - "timeout", - "timed out", - "hyt00", - "hyt01", - "cancel", - "operation canceled", - "execution terminated", - "query limit", - ] - - assert any( - indicator in error_text for indicator in timeout_indicators - ), f"Exception occurred but doesn't appear to be a timeout error: {e}" - finally: - # Reset timeout for other tests - db_connection.timeout = original_timeout - - def test_timeout_affects_all_cursors(db_connection): """Test that changing timeout on connection affects all new cursors""" # Create a cursor with default timeout @@ -5449,6 +5377,9 @@ def test_timeout_long_query(db_connection): try: # Try several different approaches to test timeout start_time = time.perf_counter() + max_retries = 3 + retry_count = 0 + try: # Method 1: CPU-intensive query with REPLICATE and large result set cpu_intensive_query = """ @@ -5476,21 +5407,43 @@ def test_timeout_long_query(db_connection): if elapsed_time < 4.5: # Method 3: Try with a join that generates many rows - start_time = time.perf_counter() - cursor.execute( - """ - SELECT COUNT(*) FROM sys.objects a, sys.objects b, sys.objects c - WHERE a.object_id = b.object_id * c.object_id - """ - ) - cursor.fetchall() - elapsed_time = time.perf_counter() - start_time + # Retry this method multiple times if we get DataError (arithmetic overflow) + while retry_count < max_retries: + start_time = time.perf_counter() + try: + cursor.execute( + """ + SELECT COUNT(*) FROM sys.objects a, sys.objects b, sys.objects c + WHERE a.object_id = b.object_id * c.object_id + """ + ) + cursor.fetchall() + elapsed_time = time.perf_counter() - start_time + break # Success, exit retry loop + except Exception as retry_e: + from mssql_python.exceptions import DataError + if isinstance(retry_e, DataError) and "overflow" in str(retry_e).lower(): + retry_count += 1 + if retry_count >= max_retries: + # After max retries with overflow, skip this method + break + # Wait a bit and retry + import time as time_module + time_module.sleep(0.1) + else: + # Not an overflow error, re-raise to be handled by outer exception handler + raise # If we still get here without an exception if elapsed_time < 4.5: pytest.skip("Timeout feature not enforced by database driver") except Exception as e: + from mssql_python.exceptions import DataError + # Check if this is a DataError with overflow (flaky test condition) + if isinstance(e, DataError) and "overflow" in str(e).lower(): + pytest.skip(f"Skipping timeout test due to arithmetic overflow in test query: {e}") + # Verify this is a timeout exception elapsed_time = time.perf_counter() - start_time assert elapsed_time < 4.5, "Exception occurred but after expected timeout" @@ -7927,8 +7880,12 @@ def test_set_attr_access_mode_after_connect(db_connection): assert result[0][0] == 1 -def test_set_attr_current_catalog_after_connect(db_connection): +def test_set_attr_current_catalog_after_connect(db_connection, conn_str): """Test setting current catalog after connection via set_attr.""" + # Skip this test for Azure SQL Database - it doesn't support changing database after connection + if is_azure_sql_connection(conn_str): + pytest.skip("Skipping for Azure SQL - SQL_ATTR_CURRENT_CATALOG not supported after connection") + # Get current database name cursor = db_connection.cursor() cursor.execute("SELECT DB_NAME()") diff --git a/tests/test_004_cursor.py b/tests/test_004_cursor.py index b52b0656c..f096bf7c2 100644 --- a/tests/test_004_cursor.py +++ b/tests/test_004_cursor.py @@ -15,6 +15,9 @@ from contextlib import closing import mssql_python import uuid +import re +from conftest import is_azure_sql_connection + # Setup test table TEST_TABLE = """ @@ -4928,8 +4931,12 @@ def test_cursor_commit_performance_patterns(cursor, db_connection): pass -def test_cursor_rollback_error_scenarios(cursor, db_connection): +def test_cursor_rollback_error_scenarios(cursor, db_connection, conn_str): """Test cursor rollback error scenarios and recovery""" + # Skip this test for Azure SQL Database + if is_azure_sql_connection(conn_str): + pytest.skip("Skipping for Azure SQL - transaction-heavy tests may cause timeouts") + try: # Set autocommit to False original_autocommit = db_connection.autocommit @@ -5005,8 +5012,12 @@ def test_cursor_rollback_error_scenarios(cursor, db_connection): pass -def test_cursor_rollback_with_method_chaining(cursor, db_connection): +def test_cursor_rollback_with_method_chaining(cursor, db_connection, conn_str): """Test cursor rollback in method chaining scenarios""" + # Skip this test for Azure SQL Database + if is_azure_sql_connection(conn_str): + pytest.skip("Skipping for Azure SQL - transaction-heavy tests may cause timeouts") + try: # Set autocommit to False original_autocommit = db_connection.autocommit @@ -5493,8 +5504,12 @@ def test_cursor_rollback_data_consistency(cursor, db_connection): pass -def test_cursor_rollback_large_transaction(cursor, db_connection): +def test_cursor_rollback_large_transaction(cursor, db_connection, conn_str): """Test cursor rollback with large transaction""" + # Skip this test for Azure SQL Database + if is_azure_sql_connection(conn_str): + pytest.skip("Skipping for Azure SQL - large transaction tests may cause timeouts") + try: # Set autocommit to False original_autocommit = db_connection.autocommit