@@ -719,6 +719,372 @@ def test_all_handle_types_comprehensive(self, conn_str):
719719 assert "=== Exiting ===" in result .stdout
720720 print (f"PASS: Comprehensive all handle types test" )
721721
722+ def test_cleanup_connections_normal_flow (self , conn_str ):
723+ """
724+ Test _cleanup_connections() with normal active connections.
725+
726+ Validates that:
727+ 1. Active connections (_closed=False) are properly closed
728+ 2. The cleanup function is registered with atexit
729+ 3. Connections can be registered and tracked
730+ """
731+ script = textwrap .dedent (
732+ f"""
733+ import mssql_python
734+
735+ # Verify cleanup infrastructure exists
736+ assert hasattr(mssql_python, '_active_connections'), "Missing _active_connections"
737+ assert hasattr(mssql_python, '_cleanup_connections'), "Missing _cleanup_connections"
738+ assert hasattr(mssql_python, '_register_connection'), "Missing _register_connection"
739+
740+ # Create mock connection to test registration and cleanup
741+ class MockConnection:
742+ def __init__(self):
743+ self._closed = False
744+ self.close_called = False
745+
746+ def close(self):
747+ self.close_called = True
748+ self._closed = True
749+
750+ # Register connection
751+ mock_conn = MockConnection()
752+ mssql_python._register_connection(mock_conn)
753+ assert mock_conn in mssql_python._active_connections, "Connection not registered"
754+
755+ # Test cleanup
756+ mssql_python._cleanup_connections()
757+ assert mock_conn.close_called, "close() should have been called"
758+ assert mock_conn._closed, "Connection should be marked as closed"
759+
760+ print("Normal flow: PASSED")
761+ """
762+ )
763+
764+ result = subprocess .run (
765+ [sys .executable , "-c" , script ], capture_output = True , text = True , timeout = 10
766+ )
767+
768+ assert result .returncode == 0 , f"Test failed. stderr: { result .stderr } "
769+ assert "Normal flow: PASSED" in result .stdout
770+ print (f"PASS: Cleanup connections normal flow" )
771+
772+ def test_cleanup_connections_already_closed (self , conn_str ):
773+ """
774+ Test _cleanup_connections() with already closed connections.
775+
776+ Validates that connections with _closed=True are skipped
777+ and close() is not called again.
778+ """
779+ script = textwrap .dedent (
780+ f"""
781+ import mssql_python
782+
783+ class MockConnection:
784+ def __init__(self):
785+ self._closed = True # Already closed
786+ self.close_called = False
787+
788+ def close(self):
789+ self.close_called = True
790+ raise AssertionError("close() should not be called on closed connection")
791+
792+ # Register already-closed connection
793+ mock_conn = MockConnection()
794+ mssql_python._register_connection(mock_conn)
795+
796+ # Cleanup should skip this connection
797+ mssql_python._cleanup_connections()
798+ assert not mock_conn.close_called, "close() should NOT have been called"
799+
800+ print("Already closed: PASSED")
801+ """
802+ )
803+
804+ result = subprocess .run (
805+ [sys .executable , "-c" , script ], capture_output = True , text = True , timeout = 10
806+ )
807+
808+ assert result .returncode == 0 , f"Test failed. stderr: { result .stderr } "
809+ assert "Already closed: PASSED" in result .stdout
810+ print (f"PASS: Cleanup connections already closed" )
811+
812+ def test_cleanup_connections_missing_attribute (self , conn_str ):
813+ """
814+ Test _cleanup_connections() with connections missing _closed attribute.
815+
816+ Validates that hasattr() check prevents AttributeError and
817+ cleanup continues gracefully.
818+ """
819+ script = textwrap .dedent (
820+ f"""
821+ import mssql_python
822+
823+ class MinimalConnection:
824+ # No _closed attribute
825+ def close(self):
826+ pass
827+
828+ # Register connection without _closed
829+ mock_conn = MinimalConnection()
830+ mssql_python._register_connection(mock_conn)
831+
832+ # Should not crash
833+ mssql_python._cleanup_connections()
834+
835+ print("Missing attribute: PASSED")
836+ """
837+ )
838+
839+ result = subprocess .run (
840+ [sys .executable , "-c" , script ], capture_output = True , text = True , timeout = 10
841+ )
842+
843+ assert result .returncode == 0 , f"Test failed. stderr: { result .stderr } "
844+ assert "Missing attribute: PASSED" in result .stdout
845+ print (f"PASS: Cleanup connections missing _closed attribute" )
846+
847+ def test_cleanup_connections_exception_handling (self , conn_str ):
848+ """
849+ Test _cleanup_connections() exception handling.
850+
851+ Validates that:
852+ 1. Exceptions during close() are caught and silently ignored
853+ 2. One failing connection doesn't prevent cleanup of others
854+ 3. The function completes successfully despite errors
855+ """
856+ script = textwrap .dedent (
857+ f"""
858+ import mssql_python
859+
860+ class GoodConnection:
861+ def __init__(self):
862+ self._closed = False
863+ self.close_called = False
864+
865+ def close(self):
866+ self.close_called = True
867+ self._closed = True
868+
869+ class BadConnection:
870+ def __init__(self):
871+ self._closed = False
872+
873+ def close(self):
874+ raise RuntimeError("Simulated error during close")
875+
876+ # Register both good and bad connections
877+ good_conn = GoodConnection()
878+ bad_conn = BadConnection()
879+ mssql_python._register_connection(bad_conn)
880+ mssql_python._register_connection(good_conn)
881+
882+ # Cleanup should handle exception and continue
883+ try:
884+ mssql_python._cleanup_connections()
885+ # Should not raise despite bad_conn throwing exception
886+ assert good_conn.close_called, "Good connection should still be closed"
887+ print("Exception handling: PASSED")
888+ except Exception as e:
889+ print(f"Exception handling: FAILED - Exception escaped: {{e}}")
890+ raise
891+ """
892+ )
893+
894+ result = subprocess .run (
895+ [sys .executable , "-c" , script ], capture_output = True , text = True , timeout = 10
896+ )
897+
898+ assert result .returncode == 0 , f"Test failed. stderr: { result .stderr } "
899+ assert "Exception handling: PASSED" in result .stdout
900+ print (f"PASS: Cleanup connections exception handling" )
901+
902+ def test_cleanup_connections_multiple_connections (self , conn_str ):
903+ """
904+ Test _cleanup_connections() with multiple connections.
905+
906+ Validates that all registered connections are processed
907+ and closed in the cleanup iteration.
908+ """
909+ script = textwrap .dedent (
910+ f"""
911+ import mssql_python
912+
913+ class TestConnection:
914+ count = 0
915+
916+ def __init__(self, conn_id):
917+ self.conn_id = conn_id
918+ self._closed = False
919+ self.close_called = False
920+
921+ def close(self):
922+ self.close_called = True
923+ self._closed = True
924+ TestConnection.count += 1
925+
926+ # Register multiple connections
927+ connections = [TestConnection(i) for i in range(5)]
928+ for conn in connections:
929+ mssql_python._register_connection(conn)
930+
931+ # Cleanup all
932+ mssql_python._cleanup_connections()
933+
934+ assert TestConnection.count == 5, f"All 5 connections should be closed, got {{TestConnection.count}}"
935+ assert all(c.close_called for c in connections), "All connections should have close() called"
936+
937+ print("Multiple connections: PASSED")
938+ """
939+ )
940+
941+ result = subprocess .run (
942+ [sys .executable , "-c" , script ], capture_output = True , text = True , timeout = 10
943+ )
944+
945+ assert result .returncode == 0 , f"Test failed. stderr: { result .stderr } "
946+ assert "Multiple connections: PASSED" in result .stdout
947+ print (f"PASS: Cleanup connections multiple connections" )
948+
949+ def test_cleanup_connections_weakset_behavior (self , conn_str ):
950+ """
951+ Test _cleanup_connections() WeakSet behavior.
952+
953+ Validates that:
954+ 1. WeakSet automatically removes garbage collected connections
955+ 2. Only live references are processed during cleanup
956+ 3. No crashes occur with GC'd connections
957+ """
958+ script = textwrap .dedent (
959+ f"""
960+ import mssql_python
961+ import gc
962+
963+ class TestConnection:
964+ def __init__(self):
965+ self._closed = False
966+
967+ def close(self):
968+ pass
969+
970+ # Register connection then let it be garbage collected
971+ conn = TestConnection()
972+ mssql_python._register_connection(conn)
973+ initial_count = len(mssql_python._active_connections)
974+
975+ del conn
976+ gc.collect() # Force garbage collection
977+
978+ final_count = len(mssql_python._active_connections)
979+ assert final_count < initial_count, "WeakSet should auto-remove GC'd connections"
980+
981+ # Cleanup should not crash with removed connections
982+ mssql_python._cleanup_connections()
983+
984+ print("WeakSet behavior: PASSED")
985+ """
986+ )
987+
988+ result = subprocess .run (
989+ [sys .executable , "-c" , script ], capture_output = True , text = True , timeout = 10
990+ )
991+
992+ assert result .returncode == 0 , f"Test failed. stderr: { result .stderr } "
993+ assert "WeakSet behavior: PASSED" in result .stdout
994+ print (f"PASS: Cleanup connections WeakSet behavior" )
995+
996+ def test_cleanup_connections_empty_list (self , conn_str ):
997+ """
998+ Test _cleanup_connections() with empty connections list.
999+
1000+ Validates that cleanup completes successfully with no registered
1001+ connections without any errors.
1002+ """
1003+ script = textwrap .dedent (
1004+ f"""
1005+ import mssql_python
1006+
1007+ # Clear any existing connections
1008+ mssql_python._active_connections.clear()
1009+
1010+ # Should not crash with empty set
1011+ mssql_python._cleanup_connections()
1012+
1013+ print("Empty list: PASSED")
1014+ """
1015+ )
1016+
1017+ result = subprocess .run (
1018+ [sys .executable , "-c" , script ], capture_output = True , text = True , timeout = 10
1019+ )
1020+
1021+ assert result .returncode == 0 , f"Test failed. stderr: { result .stderr } "
1022+ assert "Empty list: PASSED" in result .stdout
1023+ print (f"PASS: Cleanup connections empty list" )
1024+
1025+ def test_cleanup_connections_mixed_scenario (self , conn_str ):
1026+ """
1027+ Test _cleanup_connections() with mixed connection states.
1028+
1029+ Validates handling of:
1030+ - Open connections (should be closed)
1031+ - Already closed connections (should be skipped)
1032+ - Connections that throw exceptions (should be caught)
1033+ - All in one cleanup run
1034+ """
1035+ script = textwrap .dedent (
1036+ f"""
1037+ import mssql_python
1038+
1039+ class OpenConnection:
1040+ def __init__(self):
1041+ self._closed = False
1042+ self.close_called = False
1043+
1044+ def close(self):
1045+ self.close_called = True
1046+ self._closed = True
1047+
1048+ class ClosedConnection:
1049+ def __init__(self):
1050+ self._closed = True
1051+
1052+ def close(self):
1053+ raise AssertionError("Should not be called")
1054+
1055+ class ErrorConnection:
1056+ def __init__(self):
1057+ self._closed = False
1058+
1059+ def close(self):
1060+ raise RuntimeError("Simulated error")
1061+
1062+ # Register all types
1063+ open_conn = OpenConnection()
1064+ closed_conn = ClosedConnection()
1065+ error_conn = ErrorConnection()
1066+
1067+ mssql_python._register_connection(open_conn)
1068+ mssql_python._register_connection(closed_conn)
1069+ mssql_python._register_connection(error_conn)
1070+
1071+ # Cleanup should handle all scenarios
1072+ mssql_python._cleanup_connections()
1073+
1074+ assert open_conn.close_called, "Open connection should have been closed"
1075+
1076+ print("Mixed scenario: PASSED")
1077+ """
1078+ )
1079+
1080+ result = subprocess .run (
1081+ [sys .executable , "-c" , script ], capture_output = True , text = True , timeout = 10
1082+ )
1083+
1084+ assert result .returncode == 0 , f"Test failed. stderr: { result .stderr } "
1085+ assert "Mixed scenario: PASSED" in result .stdout
1086+ print (f"PASS: Cleanup connections mixed scenario" )
1087+
7221088
7231089if __name__ == "__main__" :
7241090 # Allow running test directly for debugging
@@ -754,9 +1120,19 @@ def test_all_handle_types_comprehensive(self, conn_str):
7541120 test .test_gc_during_shutdown_with_circular_refs (conn_str )
7551121 test .test_all_handle_types_comprehensive (conn_str )
7561122
1123+ print ("\n --- CLEANUP CONNECTIONS COVERAGE TESTS ---\n " )
1124+ test .test_cleanup_connections_normal_flow (conn_str )
1125+ test .test_cleanup_connections_already_closed (conn_str )
1126+ test .test_cleanup_connections_missing_attribute (conn_str )
1127+ test .test_cleanup_connections_exception_handling (conn_str )
1128+ test .test_cleanup_connections_multiple_connections (conn_str )
1129+ test .test_cleanup_connections_weakset_behavior (conn_str )
1130+ test .test_cleanup_connections_empty_list (conn_str )
1131+ test .test_cleanup_connections_mixed_scenario (conn_str )
1132+
7571133 print ("\n " + "=" * 70 )
7581134 print ("PASS: ALL TESTS PASSED - No segfaults detected" )
7591135 print ("=" * 70 + "\n " )
7601136 except AssertionError as e :
761- print (f"\n ✗ TEST FAILED: { e } " )
1137+ print (f"\n FAIL: TEST FAILED: { e } " )
7621138 sys .exit (1 )
0 commit comments