|
2 | 2 | from contextlib import contextmanager |
3 | 3 | from collections import OrderedDict |
4 | 4 | import datetime |
| 5 | +import gc |
5 | 6 | import io |
6 | 7 | import logging |
7 | 8 | import os |
@@ -1155,6 +1156,45 @@ def test_row_limit_with_arrow_smaller_result(self): |
1155 | 1156 |
|
1156 | 1157 | # use a RetrySuite to encapsulate these tests which we'll typically want to run together; however keep |
1157 | 1158 | # the 429/503 subsuites separate since they execute under different circumstances. |
| 1159 | +class TestPySQLConnectionFailureSuite(PySQLPytestTestCase): |
| 1160 | + """Regression tests for connection construction failures (issue #746).""" |
| 1161 | + |
| 1162 | + def test_del_does_not_raise_when_session_never_set(self): |
| 1163 | + """A constructor-stage connection failure must not leave __del__ raising |
| 1164 | + AttributeError when the half-constructed Connection is garbage-collected. |
| 1165 | +
|
| 1166 | + Username/password auth raises ValueError inside the Session constructor, |
| 1167 | + i.e. before ``self.session`` is assigned in Connection.__init__. The |
| 1168 | + original connection error must propagate, and __del__ on the |
| 1169 | + half-constructed Connection must not raise (which Python would report as |
| 1170 | + an 'Exception ignored in __del__' referencing the missing ``session`` |
| 1171 | + attribute). |
| 1172 | + """ |
| 1173 | + # Use the live-warehouse connection params, but inject username/password |
| 1174 | + # which triggers a client-side ValueError inside Session.__init__. |
| 1175 | + params = dict(self.connection_params(), username="user", password="pass") |
| 1176 | + |
| 1177 | + unraisable = [] |
| 1178 | + original_hook = sys.unraisablehook |
| 1179 | + sys.unraisablehook = lambda unraisable_hook_args: unraisable.append( |
| 1180 | + unraisable_hook_args |
| 1181 | + ) |
| 1182 | + try: |
| 1183 | + with pytest.raises(ValueError): |
| 1184 | + sql.connect(**params) |
| 1185 | + |
| 1186 | + # Force collection of the half-constructed Connection so __del__ runs. |
| 1187 | + gc.collect() |
| 1188 | + finally: |
| 1189 | + sys.unraisablehook = original_hook |
| 1190 | + |
| 1191 | + messages = [ |
| 1192 | + "{}: {}".format(type(u.exc_value).__name__, u.exc_value) |
| 1193 | + for u in unraisable |
| 1194 | + ] |
| 1195 | + assert not unraisable, "Exception(s) raised in __del__: {}".format(messages) |
| 1196 | + |
| 1197 | + |
1158 | 1198 | class TestPySQLRetrySuite: |
1159 | 1199 | class HTTP429Suite(Client429ResponseMixin, PySQLPytestTestCase): |
1160 | 1200 | pass # Mixin covers all |
|
0 commit comments