Skip to content

Commit 81febfb

Browse files
Pin remaining InterfaceError message wordings in _check_in_use
Two branches were pinned by existing tests ("another operation is in progress", "owned by another task"); the other three were not: * pool-released: "returned to the pool" * cross-loop: "bound to a different event loop" * sync context: "from within an async context" Operators triage on these substrings; SQLAlchemy's is_disconnect and downstream retry tooling inspect them too. Pin the wording so a future cleanup cannot silently change the operator-facing strings. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 1d8ec78 commit 81febfb

1 file changed

Lines changed: 81 additions & 0 deletions

File tree

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
"""Pin the exact wording of every ``InterfaceError`` raised from
2+
``DqliteConnection._check_in_use``.
3+
4+
Operators triage on substring matches in error messages; downstream
5+
tooling (SQLAlchemy's ``is_disconnect``, retry decorators, log
6+
parsers) often inspects the wording too. A future cleanup that
7+
rewords any of these branches would silently break parsers.
8+
9+
Existing tests pin the "another operation is in progress" branch
10+
(``test_check_in_use_error_includes_task_identity.py``) and the
11+
"owned by another task" branch (same file). This file pins the
12+
remaining three branches:
13+
14+
* pool-released
15+
* cross-loop binding mismatch
16+
* called from sync context (no running loop)
17+
"""
18+
19+
from __future__ import annotations
20+
21+
import asyncio
22+
import weakref
23+
from unittest.mock import MagicMock
24+
25+
import pytest
26+
27+
from dqliteclient.connection import DqliteConnection
28+
from dqliteclient.exceptions import InterfaceError
29+
30+
31+
def _make_bound_connection() -> DqliteConnection:
32+
"""A connection in the post-bind state on the running loop, with
33+
every other ``_check_in_use`` precondition relaxed."""
34+
conn = DqliteConnection.__new__(DqliteConnection)
35+
conn._pool_released = False
36+
conn._bound_loop = asyncio.get_running_loop()
37+
conn._in_use = False
38+
conn._in_transaction = False
39+
conn._tx_owner = None
40+
return conn
41+
42+
43+
@pytest.mark.asyncio
44+
async def test_pool_released_branch_message_substring() -> None:
45+
conn = _make_bound_connection()
46+
conn._pool_released = True
47+
with pytest.raises(InterfaceError, match="returned to the pool"):
48+
conn._check_in_use()
49+
50+
51+
@pytest.mark.asyncio
52+
async def test_cross_loop_branch_message_substring() -> None:
53+
conn = _make_bound_connection()
54+
# A different loop than the running one — cannot construct a real
55+
# second loop here; a sentinel that compares unequal is enough,
56+
# because ``_check_in_use`` only does ``is`` identity comparison.
57+
sentinel = MagicMock(spec=asyncio.AbstractEventLoop)
58+
conn._bound_loop = sentinel
59+
with pytest.raises(InterfaceError, match="bound to a different event loop"):
60+
conn._check_in_use()
61+
62+
63+
def test_called_from_sync_context_branch_message_substring() -> None:
64+
"""Outside a running loop, ``_check_in_use`` raises with a clear
65+
message rather than crashing on ``get_running_loop``'s
66+
RuntimeError. Run as a SYNC test so the surrounding code does
67+
not have a running loop."""
68+
conn = DqliteConnection.__new__(DqliteConnection)
69+
conn._pool_released = False
70+
conn._bound_loop = None
71+
conn._in_use = False
72+
conn._in_transaction = False
73+
conn._tx_owner = None
74+
with pytest.raises(InterfaceError, match="from within an async context"):
75+
conn._check_in_use()
76+
77+
78+
# Suppress the unused import warning — ``weakref`` is here for
79+
# future _bound_loop helpers; keep the import grouped with the
80+
# rest of the asyncio imports for symmetry with neighboring tests.
81+
_ = weakref

0 commit comments

Comments
 (0)