1010Note: The cursor function is not yet implemented, so related tests are commented out.
1111"""
1212
13+ from mssql_python .exceptions import InterfaceError
1314import pytest
1415import time
1516from mssql_python import Connection , connect , pooling
1617
1718def drop_table_if_exists (cursor , table_name ):
1819 """Drop the table if it exists"""
1920 try :
20- cursor .execute (f"IF OBJECT_ID(' { table_name } ', 'U') IS NOT NULL DROP TABLE { table_name } " )
21+ cursor .execute (f"DROP TABLE IF EXISTS { table_name } " )
2122 except Exception as e :
2223 pytest .fail (f"Failed to drop table { table_name } : { e } " )
2324
@@ -223,4 +224,172 @@ def test_connection_pooling_basic(conn_str):
223224
224225 conn1 .close ()
225226 conn2 .close ()
226-
227+
228+ # Add these tests at the end of the file
229+
230+ def test_cursor_cleanup_on_connection_close (conn_str ):
231+ """Test that cursors are properly cleaned up when connection is closed"""
232+ # Create a new connection for this test
233+ conn = connect (conn_str )
234+
235+ # Create multiple cursors
236+ cursor1 = conn .cursor ()
237+ cursor2 = conn .cursor ()
238+ cursor3 = conn .cursor ()
239+
240+ # Execute something on each cursor to ensure they have statement handles
241+ # Option 1: Fetch results immediately to free the connection
242+ cursor1 .execute ("SELECT 1" )
243+ cursor1 .fetchall ()
244+
245+ cursor2 .execute ("SELECT 2" )
246+ cursor2 .fetchall ()
247+
248+ cursor3 .execute ("SELECT 3" )
249+ cursor3 .fetchall ()
250+
251+ # Close one cursor explicitly
252+ cursor1 .close ()
253+ assert cursor1 .closed is True , "Cursor1 should be closed"
254+
255+ # Close the connection (should clean up remaining cursors)
256+ conn .close ()
257+
258+ # Verify all cursors are closed
259+ assert cursor1 .closed is True , "Cursor1 should remain closed"
260+ assert cursor2 .closed is True , "Cursor2 should be closed by connection.close()"
261+ assert cursor3 .closed is True , "Cursor3 should be closed by connection.close()"
262+
263+ def test_cursor_weakref_cleanup (conn_str ):
264+ """Test that WeakSet properly removes garbage collected cursors"""
265+ conn = connect (conn_str )
266+
267+ # Create cursors
268+ cursor1 = conn .cursor ()
269+ cursor2 = conn .cursor ()
270+
271+ # Check initial cursor count
272+ assert len (conn ._cursors ) == 2 , "Should have 2 cursors"
273+
274+ # Delete reference to cursor1 (should be garbage collected)
275+ cursor1_id = id (cursor1 )
276+ del cursor1
277+
278+ # Force garbage collection
279+ import gc
280+ gc .collect ()
281+
282+ # Check cursor count after garbage collection
283+ assert len (conn ._cursors ) == 1 , "Should have 1 cursor after garbage collection"
284+
285+ # Verify cursor2 is still there
286+ assert cursor2 in conn ._cursors , "Cursor2 should still be in the set"
287+
288+ conn .close ()
289+
290+ def test_cursor_cleanup_order_no_segfault (conn_str ):
291+ """Test that proper cleanup order prevents segfaults"""
292+ # This test ensures cursors are cleaned before connection
293+ conn = connect (conn_str )
294+
295+ # Create multiple cursors with active statements
296+ cursors = []
297+ for i in range (5 ):
298+ cursor = conn .cursor ()
299+ cursor .execute (f"SELECT { i } " )
300+ cursor .fetchall ()
301+ cursors .append (cursor )
302+
303+ # Don't close any cursors explicitly
304+ # Just close the connection - it should handle cleanup properly
305+ conn .close ()
306+
307+ # Verify all cursors were closed
308+ for cursor in cursors :
309+ assert cursor .closed is True , "All cursors should be closed"
310+
311+ def test_cursor_close_removes_from_connection (conn_str ):
312+ """Test that closing a cursor properly cleans up references"""
313+ conn = connect (conn_str )
314+
315+ # Create cursors
316+ cursor1 = conn .cursor ()
317+ cursor2 = conn .cursor ()
318+ cursor3 = conn .cursor ()
319+
320+ assert len (conn ._cursors ) == 3 , "Should have 3 cursors"
321+
322+ # Close cursor2
323+ cursor2 .close ()
324+
325+ # cursor2 should still be in the WeakSet (until garbage collected)
326+ # but it should be marked as closed
327+ assert cursor2 .closed is True , "Cursor2 should be closed"
328+
329+ # Delete the reference and force garbage collection
330+ del cursor2
331+ import gc
332+ gc .collect ()
333+
334+ # Now should have 2 cursors
335+ assert len (conn ._cursors ) == 2 , "Should have 2 cursors after closing and GC"
336+
337+ conn .close ()
338+
339+ def test_connection_close_idempotent (conn_str ):
340+ """Test that calling close() multiple times is safe"""
341+ conn = connect (conn_str )
342+ cursor = conn .cursor ()
343+ cursor .execute ("SELECT 1" )
344+
345+ # First close
346+ conn .close ()
347+ assert conn ._closed is True , "Connection should be closed"
348+
349+ # Second close (should not raise exception)
350+ conn .close ()
351+ assert conn ._closed is True , "Connection should remain closed"
352+
353+ # Cursor should also be closed
354+ assert cursor .closed is True , "Cursor should be closed"
355+
356+ def test_cursor_after_connection_close (conn_str ):
357+ """Test that creating cursor after connection close raises error"""
358+ conn = connect (conn_str )
359+ conn .close ()
360+
361+ # Should raise exception when trying to create cursor on closed connection
362+ with pytest .raises (InterfaceError ) as excinfo :
363+ cursor = conn .cursor ()
364+
365+ assert "closed connection" in str (excinfo .value ).lower (), "Should mention closed connection"
366+
367+ def test_multiple_cursor_operations_cleanup (conn_str ):
368+ """Test cleanup with multiple cursor operations"""
369+ conn = connect (conn_str )
370+
371+ # Create table for testing
372+ cursor_setup = conn .cursor ()
373+ drop_table_if_exists (cursor_setup , "#test_cleanup" )
374+ cursor_setup .execute ("CREATE TABLE #test_cleanup (id INT, value VARCHAR(50))" )
375+ cursor_setup .close ()
376+
377+ # Create multiple cursors doing different operations
378+ cursor_insert = conn .cursor ()
379+ cursor_insert .execute ("INSERT INTO #test_cleanup VALUES (1, 'test1'), (2, 'test2')" )
380+
381+ cursor_select1 = conn .cursor ()
382+ cursor_select1 .execute ("SELECT * FROM #test_cleanup WHERE id = 1" )
383+ cursor_select1 .fetchall ()
384+
385+ cursor_select2 = conn .cursor ()
386+ cursor_select2 .execute ("SELECT * FROM #test_cleanup WHERE id = 2" )
387+ cursor_select2 .fetchall ()
388+
389+ # Close connection without closing cursors
390+ conn .close ()
391+
392+ # All cursors should be closed
393+ assert cursor_insert .closed is True
394+ assert cursor_select1 .closed is True
395+ assert cursor_select2 .closed is True
0 commit comments