From 2ba7c29ab34a7cf538c49a374cb062ff1b0b21dc Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: 2025年10月29日 15:29:10 +0530 Subject: [PATCH 01/10] FEAT: Enhance Testing to add new SQL Server Versions and Types --- eng/pipelines/pr-validation-pipeline.yml | 115 +++++++++++++++++++++-- 1 file changed, 107 insertions(+), 8 deletions(-) diff --git a/eng/pipelines/pr-validation-pipeline.yml b/eng/pipelines/pr-validation-pipeline.yml index d2ede247..30867a2e 100644 --- a/eng/pipelines/pr-validation-pipeline.yml +++ b/eng/pipelines/pr-validation-pipeline.yml @@ -50,6 +50,13 @@ jobs: pool: vmImage: 'windows-latest' + strategy: + matrix: + LocalDB: + sqlVersion: 'LocalDB' + SQLServer2022: + sqlVersion: 'SQL2022' + steps: - task: UsePythonVersion@0 inputs: @@ -63,19 +70,87 @@ jobs: 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,12 +159,30 @@ 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' + # 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' + + # Run tests for Azure SQL Database (if AZURE_CONNECTION_STRING is provided) + - script: | + python -m pytest -v --junitxml=test-results-azure.xml --cov=. --cov-report=xml:coverage-azure.xml --capture=tee-sys --cache-clear + displayName: 'Run tests with coverage on Azure SQL Database' + condition: and(succeeded(), ne(variables['AZURE_CONNECTION_STRING'], '')) + env: + DB_CONNECTION_STRING: $(AZURE_CONNECTION_STRING) + - task: PublishBuildArtifacts@1 inputs: PathtoPublish: 'mssql_python/ddbc_bindings.cp313-amd64.pyd' @@ -107,8 +200,8 @@ jobs: - 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: @@ -209,9 +302,15 @@ jobs: Ubuntu: dockerImage: 'ubuntu:22.04' distroName: 'Ubuntu' + sqlServerImage: 'mcr.microsoft.com/mssql/server:2022-latest' + Ubuntu_SQL2025: + dockerImage: 'ubuntu:22.04' + distroName: 'Ubuntu-SQL2025' + sqlServerImage: 'mcr.microsoft.com/mssql/server:2025-latest' Debian: dockerImage: 'debian:12' distroName: 'Debian' + sqlServerImage: 'mcr.microsoft.com/mssql/server:2022-latest' steps: - script: | @@ -230,7 +329,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..." From 049f0bca7feb4d7a64dc2f9e012af45a29561497 Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: 2025年10月29日 15:39:08 +0530 Subject: [PATCH 02/10] added a new step itself for azure conn string --- eng/pipelines/pr-validation-pipeline.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/eng/pipelines/pr-validation-pipeline.yml b/eng/pipelines/pr-validation-pipeline.yml index 30867a2e..fbb8f4b1 100644 --- a/eng/pipelines/pr-validation-pipeline.yml +++ b/eng/pipelines/pr-validation-pipeline.yml @@ -56,6 +56,8 @@ jobs: sqlVersion: 'LocalDB' SQLServer2022: sqlVersion: 'SQL2022' + AzureSQL: + sqlVersion: 'AzureSQL' steps: - task: UsePythonVersion@0 @@ -175,11 +177,11 @@ jobs: env: DB_CONNECTION_STRING: 'Server=localhost;Database=TestDB;Uid=testuser;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes' - # Run tests for Azure SQL Database (if AZURE_CONNECTION_STRING is provided) + # Run tests for Azure SQL Database (requires AZURE_CONNECTION_STRING variable) - script: | python -m pytest -v --junitxml=test-results-azure.xml --cov=. --cov-report=xml:coverage-azure.xml --capture=tee-sys --cache-clear displayName: 'Run tests with coverage on Azure SQL Database' - condition: and(succeeded(), ne(variables['AZURE_CONNECTION_STRING'], '')) + condition: and(eq(variables['sqlVersion'], 'AzureSQL'), ne(variables['AZURE_CONNECTION_STRING'], '')) env: DB_CONNECTION_STRING: $(AZURE_CONNECTION_STRING) From 30c452c8523d1a69dc317e9032439568d59b7328 Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: 2025年10月29日 18:59:12 +0530 Subject: [PATCH 03/10] do the Azure SQL testing on ubuntu --- eng/pipelines/pr-validation-pipeline.yml | 66 +++++++++++++++--------- 1 file changed, 41 insertions(+), 25 deletions(-) diff --git a/eng/pipelines/pr-validation-pipeline.yml b/eng/pipelines/pr-validation-pipeline.yml index fbb8f4b1..1067d2e0 100644 --- a/eng/pipelines/pr-validation-pipeline.yml +++ b/eng/pipelines/pr-validation-pipeline.yml @@ -56,8 +56,6 @@ jobs: sqlVersion: 'LocalDB' SQLServer2022: sqlVersion: 'SQL2022' - AzureSQL: - sqlVersion: 'AzureSQL' steps: - task: UsePythonVersion@0 @@ -177,14 +175,6 @@ jobs: env: DB_CONNECTION_STRING: 'Server=localhost;Database=TestDB;Uid=testuser;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes' - # Run tests for Azure SQL Database (requires AZURE_CONNECTION_STRING variable) - - script: | - python -m pytest -v --junitxml=test-results-azure.xml --cov=. --cov-report=xml:coverage-azure.xml --capture=tee-sys --cache-clear - displayName: 'Run tests with coverage on Azure SQL Database' - condition: and(eq(variables['sqlVersion'], 'AzureSQL'), ne(variables['AZURE_CONNECTION_STRING'], '')) - env: - DB_CONNECTION_STRING: $(AZURE_CONNECTION_STRING) - - task: PublishBuildArtifacts@1 inputs: PathtoPublish: 'mssql_python/ddbc_bindings.cp313-amd64.pyd' @@ -305,14 +295,22 @@ jobs: 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: | @@ -357,6 +355,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) @@ -455,20 +454,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="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 + " + 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) @@ -483,8 +497,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() From 6f99ab5b4f8f40aac33b61340872b622c629c279 Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: 2025年10月29日 19:27:12 +0530 Subject: [PATCH 04/10] skip large tests for azure sql --- tests/test_003_connection.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/tests/test_003_connection.py b/tests/test_003_connection.py index 9526d158..4a93e0d7 100644 --- a/tests/test_003_connection.py +++ b/tests/test_003_connection.py @@ -3639,7 +3639,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 +3648,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 conn_str and "database.windows.net" in conn_str.lower(): + pytest.skip("Skipping for Azure SQL - connection limits cause this test to hang") import gc import sys @@ -3712,7 +3716,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 +3726,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 conn_str and "database.windows.net" in conn_str.lower(): + 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 +4297,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 +4307,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 conn_str and "database.windows.net" in conn_str.lower(): + 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)] From 30a10241d22d043618e2bd1115729016eae6d836 Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: 2025年10月29日 20:13:44 +0530 Subject: [PATCH 05/10] skip large tests for azure sql and fix tests --- tests/test_003_connection.py | 31 ++++++++++++++++++++++++------- tests/test_004_cursor.py | 30 +++++++++++++++++++++++++++--- 2 files changed, 51 insertions(+), 10 deletions(-) diff --git a/tests/test_003_connection.py b/tests/test_003_connection.py index 4a93e0d7..bc6af761 100644 --- a/tests/test_003_connection.py +++ b/tests/test_003_connection.py @@ -45,6 +45,18 @@ from mssql_python.constants import ConstantsDDBC +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 + import re + server_match = re.search(r'server\s*=\s*[^;]*database\.windows\.net', conn_str_lower) + return server_match is not None + + @pytest.fixture(autouse=True) def clean_connection_state(db_connection): """Ensure connection is in a clean state before each test""" @@ -415,10 +427,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): @@ -3656,7 +3669,7 @@ def test_execute_multiple_simultaneous_cursors(db_connection, conn_str): - Connection remains stable after intensive cursor operations """ # Skip this test for Azure SQL Database - if conn_str and "database.windows.net" in conn_str.lower(): + if is_azure_sql_connection(conn_str): pytest.skip("Skipping for Azure SQL - connection limits cause this test to hang") import gc import sys @@ -3734,7 +3747,7 @@ def test_execute_with_large_parameters(db_connection, conn_str): - Processing large result sets """ # Skip this test for Azure SQL Database - if conn_str and "database.windows.net" in conn_str.lower(): + 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 @@ -4315,7 +4328,7 @@ def test_batch_execute_large_batch(db_connection, conn_str): - Memory usage remains reasonable during batch processing """ # Skip this test for Azure SQL Database - if conn_str and "database.windows.net" in conn_str.lower(): + 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)] @@ -7939,8 +7952,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 b52b0656..63da9650 100644 --- a/tests/test_004_cursor.py +++ b/tests/test_004_cursor.py @@ -15,6 +15,18 @@ from contextlib import closing import mssql_python import uuid +import re + + +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() + server_match = re.search(r'server\s*=\s*[^;]*database\.windows\.net', conn_str_lower) + return server_match is not None + # Setup test table TEST_TABLE = """ @@ -4928,8 +4940,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 +5021,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 +5513,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 From 6caebc2d09ebe6dd226b8c69d6fbced26cf424a9 Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: 2025年10月30日 11:17:42 +0530 Subject: [PATCH 06/10] test python 3.14 --- eng/pipelines/pr-validation-pipeline.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/eng/pipelines/pr-validation-pipeline.yml b/eng/pipelines/pr-validation-pipeline.yml index 1067d2e0..7a4b0b44 100644 --- a/eng/pipelines/pr-validation-pipeline.yml +++ b/eng/pipelines/pr-validation-pipeline.yml @@ -54,16 +54,21 @@ jobs: 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 From 405a878c7910eea622b3e0ae56f157a14d83ce19 Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: 2025年10月30日 11:40:50 +0530 Subject: [PATCH 07/10] python 3.14 artifacts --- eng/pipelines/pr-validation-pipeline.yml | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/eng/pipelines/pr-validation-pipeline.yml b/eng/pipelines/pr-validation-pipeline.yml index 7a4b0b44..4cbaa0b8 100644 --- a/eng/pipelines/pr-validation-pipeline.yml +++ b/eng/pipelines/pr-validation-pipeline.yml @@ -180,19 +180,26 @@ jobs: env: DB_CONNECTION_STRING: 'Server=localhost;Database=TestDB;Uid=testuser;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes' - - task: PublishBuildArtifacts@1 + - 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() From e238c0d873c020d9bc3073b34fedbc83fb543671 Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: 2025年10月30日 14:01:56 +0530 Subject: [PATCH 08/10] Removed Driver param --- eng/pipelines/pr-validation-pipeline.yml | 28 ++++++++++++------------ 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/eng/pipelines/pr-validation-pipeline.yml b/eng/pipelines/pr-validation-pipeline.yml index 4cbaa0b8..2f09b48f 100644 --- a/eng/pipelines/pr-validation-pipeline.yml +++ b/eng/pipelines/pr-validation-pipeline.yml @@ -287,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 @@ -484,12 +484,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-$(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' + 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 @@ -699,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 " @@ -907,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 " @@ -1126,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' @@ -1354,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 @@ -1596,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 @@ -1703,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 From 7f1b6c51fc84bcdbcf948dc1d4c5fc39acb4a269 Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: 2025年10月30日 15:30:50 +0530 Subject: [PATCH 09/10] fix flaky test --- tests/test_003_connection.py | 129 +++++++++-------------------------- 1 file changed, 34 insertions(+), 95 deletions(-) diff --git a/tests/test_003_connection.py b/tests/test_003_connection.py index bc6af761..b86d7254 100644 --- a/tests/test_003_connection.py +++ b/tests/test_003_connection.py @@ -4873,92 +4873,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 @@ -5474,6 +5388,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 = """ @@ -5501,21 +5418,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" From 198e7dec30ccc9a9ad587d885dff7f1b368b9925 Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: 2025年10月30日 15:50:49 +0530 Subject: [PATCH 10/10] commonize is_az_sql_conn --- tests/conftest.py | 13 +++++++++++++ tests/test_003_connection.py | 13 +------------ tests/test_004_cursor.py | 11 +---------- 3 files changed, 15 insertions(+), 22 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 20b589d5..44a24fbb 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 b86d7254..d631ea36 100644 --- a/tests/test_003_connection.py +++ b/tests/test_003_connection.py @@ -43,18 +43,7 @@ import struct from datetime import datetime, timedelta, timezone from mssql_python.constants import ConstantsDDBC - - -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 - import re - server_match = re.search(r'server\s*=\s*[^;]*database\.windows\.net', conn_str_lower) - return server_match is not None +from conftest import is_azure_sql_connection @pytest.fixture(autouse=True) diff --git a/tests/test_004_cursor.py b/tests/test_004_cursor.py index 63da9650..f096bf7c 100644 --- a/tests/test_004_cursor.py +++ b/tests/test_004_cursor.py @@ -16,16 +16,7 @@ import mssql_python import uuid import re - - -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() - server_match = re.search(r'server\s*=\s*[^;]*database\.windows\.net', conn_str_lower) - return server_match is not None +from conftest import is_azure_sql_connection # Setup test table

AltStyle によって変換されたページ (->オリジナル) /