@@ -4144,3 +4144,302 @@ def test_getinfo_comprehensive_edge_case_coverage(db_connection):
41444144 assert not isinstance (
41454145 e , (SystemError , MemoryError )
41464146 ), f"Info type { info_type } caused critical error: { e } "
4147+
4148+
4149+ def test_timeout_long_running_query_with_small_timeout (conn_str ):
4150+ """Test that a long-running query with small timeout (1-2 seconds) raises timeout error.
4151+
4152+ This test replicates exactly what test_timeout_bug.py does to ensure consistency.
4153+ """
4154+ import time
4155+ import mssql_python
4156+
4157+ print (f"DEBUG: Connection string: { conn_str } " )
4158+
4159+ # Test 1: Create connection with timeout parameter (like test_timeout_bug.py)
4160+ print ("DEBUG: [Test 1] Creating connection with timeout=2 seconds" )
4161+ connection = mssql_python .connect (conn_str , timeout = 2 )
4162+ print (f"DEBUG: Connection created, timeout property: { connection .timeout } " )
4163+
4164+ try :
4165+ cursor = connection .cursor ()
4166+ start_time = time .perf_counter ()
4167+ print ("DEBUG: Executing WAITFOR DELAY '00:00:05' (5 seconds)" )
4168+
4169+ try :
4170+ cursor .execute ("WAITFOR DELAY '00:00:05'" )
4171+ elapsed = time .perf_counter () - start_time
4172+ print (f"DEBUG: BUG CONFIRMED: Query completed without timeout after { elapsed :.2f} s" )
4173+ pytest .skip (
4174+ f"Timeout not enforced - query completed in { elapsed :.2f} s (expected ~2s timeout)"
4175+ )
4176+ except mssql_python .OperationalError as e :
4177+ elapsed = time .perf_counter () - start_time
4178+ print (f"DEBUG: ✓ Query timed out after { elapsed :.2f} s: { e } " )
4179+ assert elapsed < 4.0 , f"Timeout took too long: { elapsed :.2f} s"
4180+ assert "timeout" in str (e ).lower (), f"Not a timeout error: { e } "
4181+ except Exception as e :
4182+ elapsed = time .perf_counter () - start_time
4183+ print (f"DEBUG: ✓ Query raised exception after { elapsed :.2f} s: { type (e ).__name__ } : { e } " )
4184+ assert elapsed < 4.0 , f"Exception took too long: { elapsed :.2f} s"
4185+ # Accept any exception that happens quickly as it might be timeout-related
4186+ finally :
4187+ cursor .close ()
4188+ connection .close ()
4189+
4190+ except Exception as e :
4191+ print (f"DEBUG: Unexpected error in test: { e } " )
4192+ if connection :
4193+ connection .close ()
4194+ raise
4195+
4196+ # Test 2: Set timeout dynamically (like test_timeout_bug.py)
4197+ print ("DEBUG: [Test 2] Setting timeout dynamically via property" )
4198+ connection = mssql_python .connect (conn_str )
4199+ print (f"DEBUG: Initial timeout: { connection .timeout } " )
4200+ connection .timeout = 2
4201+ print (f"DEBUG: After setting: { connection .timeout } " )
4202+
4203+ try :
4204+ cursor = connection .cursor ()
4205+ start_time = time .perf_counter ()
4206+
4207+ try :
4208+ cursor .execute ("WAITFOR DELAY '00:00:05'" )
4209+ elapsed = time .perf_counter () - start_time
4210+ print (f"DEBUG: BUG CONFIRMED: Query completed without timeout after { elapsed :.2f} s" )
4211+ # This is the main test - if we get here, timeout is not working
4212+ assert (
4213+ False
4214+ ), f"Timeout should have occurred after ~2s, but query completed in { elapsed :.2f} s"
4215+ except mssql_python .OperationalError as e :
4216+ elapsed = time .perf_counter () - start_time
4217+ print (f"DEBUG: ✓ Query timed out after { elapsed :.2f} s: { e } " )
4218+ assert elapsed < 4.0 , f"Timeout took too long: { elapsed :.2f} s"
4219+ assert "timeout" in str (e ).lower (), f"Not a timeout error: { e } "
4220+ except Exception as e :
4221+ elapsed = time .perf_counter () - start_time
4222+ print (f"DEBUG: ✓ Query raised exception after { elapsed :.2f} s: { type (e ).__name__ } : { e } " )
4223+ assert elapsed < 4.0 , f"Exception took too long: { elapsed :.2f} s"
4224+ finally :
4225+ cursor .close ()
4226+ connection .close ()
4227+
4228+ except Exception as e :
4229+ print (f"DEBUG: Unexpected error in dynamic timeout test: { e } " )
4230+ if connection :
4231+ connection .close ()
4232+ raise
4233+
4234+
4235+ def test_cursor_timeout_single_execute (db_connection ):
4236+ """Test that creating a cursor with timeout set and calling execute once behaves correctly."""
4237+ cursor = db_connection .cursor ()
4238+
4239+ # Set timeout on connection which should affect cursor
4240+ original_timeout = db_connection .timeout
4241+ db_connection .timeout = 30 # 30 seconds - reasonable timeout
4242+
4243+ try :
4244+ # Test single execution with timeout set
4245+ cursor .execute ("SELECT 1 AS test_value" )
4246+ result = cursor .fetchone ()
4247+ assert result is not None , "Query should execute successfully with timeout set"
4248+ assert result [0 ] == 1 , "Query should return expected result"
4249+
4250+ # Test that cursor can be used for another query
4251+ cursor .execute ("SELECT 2 AS test_value" )
4252+ result = cursor .fetchone ()
4253+ assert result is not None , "Second query should also work"
4254+ assert result [0 ] == 2 , "Second query should return expected result"
4255+
4256+ finally :
4257+ cursor .close ()
4258+ db_connection .timeout = original_timeout
4259+
4260+
4261+ def test_cursor_timeout_multiple_executions_consistency (db_connection ):
4262+ """Test executing multiple times with same cursor and verify timeout applies consistently."""
4263+ cursor = db_connection .cursor ()
4264+
4265+ # Set a reasonable timeout
4266+ original_timeout = db_connection .timeout
4267+ db_connection .timeout = 15 # 15 seconds
4268+
4269+ try :
4270+ # Execute multiple queries in sequence to verify timeout consistency
4271+ queries = [
4272+ "SELECT 1 AS query_num" ,
4273+ "SELECT 2 AS query_num" ,
4274+ "SELECT 3 AS query_num" ,
4275+ "SELECT GETDATE() AS current_datetime" ,
4276+ "SELECT @@VERSION AS version_info" ,
4277+ ]
4278+
4279+ for i , query in enumerate (queries ):
4280+ start_time = time .perf_counter ()
4281+ cursor .execute (query )
4282+ result = cursor .fetchone ()
4283+ elapsed_time = time .perf_counter () - start_time
4284+
4285+ assert result is not None , f"Query { i + 1 } should return a result"
4286+ # All queries should complete well within the timeout
4287+ assert elapsed_time < 10 , f"Query { i + 1 } took too long: { elapsed_time :.2f} s"
4288+
4289+ # For simple queries, verify expected results
4290+ if i < 3 : # First three queries return sequential numbers
4291+ assert result [0 ] == i + 1 , f"Query { i + 1 } returned incorrect result"
4292+
4293+ print (
4294+ f"Successfully executed { len (queries )} queries consistently with timeout={ db_connection .timeout } s"
4295+ )
4296+
4297+ finally :
4298+ cursor .close ()
4299+ db_connection .timeout = original_timeout
4300+
4301+
4302+ def test_cursor_reset_timeout_behavior (db_connection ):
4303+ """Test that _reset_cursor handles timeout correctly and _set_timeout is called as intended."""
4304+ # Create initial cursor
4305+ cursor1 = db_connection .cursor ()
4306+
4307+ original_timeout = db_connection .timeout
4308+ db_connection .timeout = 20 # Set reasonable timeout
4309+
4310+ try :
4311+ # Execute a query to establish cursor state
4312+ cursor1 .execute ("SELECT 'initial_query' AS status" )
4313+ result1 = cursor1 .fetchone ()
4314+ assert result1 [0 ] == "initial_query" , "Initial query should work"
4315+ cursor1 .close () # Close to release connection resources
4316+
4317+ # Create another cursor to test that timeout is properly set on new cursors
4318+ cursor2 = db_connection .cursor ()
4319+ cursor2 .execute ("SELECT 'second_cursor' AS status" )
4320+ result2 = cursor2 .fetchone ()
4321+ assert result2 [0 ] == "second_cursor" , "Second cursor should work with timeout"
4322+ cursor2 .close () # Close to release connection resources
4323+
4324+ # Create another cursor to test reuse (simulating _reset_cursor scenario)
4325+ cursor3 = db_connection .cursor ()
4326+ cursor3 .execute ("SELECT 'reuse_test' AS status" )
4327+ result3 = cursor3 .fetchone ()
4328+ assert result3 [0 ] == "reuse_test" , "Cursor should work with timeout"
4329+
4330+ # Change timeout and verify cursor still works with new timeout
4331+ db_connection .timeout = 25
4332+ cursor3 .execute ("SELECT 'updated_timeout_test' AS status" )
4333+ result4 = cursor3 .fetchone ()
4334+ assert result4 [0 ] == "updated_timeout_test" , "Cursor should work with updated timeout"
4335+
4336+ # Test that multiple operations work consistently
4337+ for i in range (3 ):
4338+ cursor3 .execute (f"SELECT 'iteration_{ i } ' AS status" )
4339+ result = cursor3 .fetchone ()
4340+ assert result [0 ] == f"iteration_{ i } " , f"Iteration { i } should work with timeout"
4341+
4342+ print (f"Successfully tested cursor reset behavior with timeout settings" )
4343+
4344+ finally :
4345+ # Clean up cursor
4346+ try :
4347+ if "cursor3" in locals () and not cursor3 .closed :
4348+ cursor3 .close ()
4349+ except :
4350+ pass
4351+ db_connection .timeout = original_timeout
4352+
4353+
4354+ def test_timeout_compatibility_with_previous_versions (db_connection ):
4355+ """Test that timeout behavior is compatible and doesn't break existing functionality."""
4356+ cursor = db_connection .cursor ()
4357+
4358+ original_timeout = db_connection .timeout
4359+
4360+ try :
4361+ # Test with default timeout (0 = no timeout)
4362+ assert db_connection .timeout == 0 , "Default timeout should be 0"
4363+
4364+ cursor .execute ("SELECT 'default_timeout' AS test" )
4365+ result = cursor .fetchone ()
4366+ assert result [0 ] == "default_timeout" , "Should work with default timeout"
4367+
4368+ # Test setting various timeout values
4369+ timeout_values = [5 , 10 , 30 , 60 , 0 ] # Including 0 to reset
4370+
4371+ for timeout_val in timeout_values :
4372+ db_connection .timeout = timeout_val
4373+ assert db_connection .timeout == timeout_val , f"Timeout should be set to { timeout_val } "
4374+
4375+ # Execute a quick query to verify functionality
4376+ cursor .execute (f"SELECT { timeout_val } AS timeout_value" )
4377+ result = cursor .fetchone ()
4378+ assert result [0 ] == timeout_val , f"Should work with timeout={ timeout_val } "
4379+
4380+ # Test that timeout doesn't affect normal operations
4381+ test_operations = [
4382+ ("SELECT COUNT(*) FROM sys.objects" , "count query" ),
4383+ ("SELECT DB_NAME()" , "database name" ),
4384+ ("SELECT GETDATE()" , "current date" ),
4385+ ("SELECT 1 WHERE 1=1" , "conditional query" ),
4386+ ("SELECT 'test' + 'string'" , "string concatenation" ),
4387+ ]
4388+
4389+ db_connection .timeout = 10 # Set reasonable timeout
4390+
4391+ for query , description in test_operations :
4392+ cursor .execute (query )
4393+ result = cursor .fetchone ()
4394+ assert result is not None , f"Operation '{ description } ' should work with timeout"
4395+
4396+ print ("Successfully verified timeout compatibility with existing functionality" )
4397+
4398+ finally :
4399+ cursor .close ()
4400+ db_connection .timeout = original_timeout
4401+
4402+
4403+ def test_timeout_edge_cases_and_boundaries (db_connection ):
4404+ """Test timeout behavior with edge cases and boundary conditions."""
4405+ cursor = db_connection .cursor ()
4406+ original_timeout = db_connection .timeout
4407+
4408+ try :
4409+ # Test boundary timeout values
4410+ boundary_values = [0 , 1 , 2 , 5 , 10 , 30 , 60 , 120 , 300 ] # 0 to 5 minutes
4411+
4412+ for timeout_val in boundary_values :
4413+ db_connection .timeout = timeout_val
4414+ assert (
4415+ db_connection .timeout == timeout_val
4416+ ), f"Should accept timeout value { timeout_val } "
4417+
4418+ # Execute a very quick query to ensure no issues with boundary values
4419+ cursor .execute ("SELECT 1 AS boundary_test" )
4420+ result = cursor .fetchone ()
4421+ assert result [0 ] == 1 , f"Should work with boundary timeout { timeout_val } "
4422+
4423+ # Test with zero timeout (no timeout)
4424+ db_connection .timeout = 0
4425+ cursor .execute ("SELECT 'no_timeout_test' AS test" )
4426+ result = cursor .fetchone ()
4427+ assert result [0 ] == "no_timeout_test" , "Should work with zero timeout"
4428+
4429+ # Test invalid timeout values (should raise ValueError)
4430+ invalid_values = [- 1 , - 5 , - 100 ]
4431+ for invalid_val in invalid_values :
4432+ with pytest .raises (ValueError , match = "Timeout cannot be negative" ):
4433+ db_connection .timeout = invalid_val
4434+
4435+ # Test non-integer timeout values (should raise TypeError)
4436+ invalid_types = ["10" , 10.5 , None , [], {}]
4437+ for invalid_type in invalid_types :
4438+ with pytest .raises (TypeError ):
4439+ db_connection .timeout = invalid_type
4440+
4441+ print ("Successfully tested timeout edge cases and boundaries" )
4442+
4443+ finally :
4444+ cursor .close ()
4445+ db_connection .timeout = original_timeout
0 commit comments