Skip to content

Commit 5d60284

Browse files
ashen-forestXiaJunjie2020
authored andcommitted
test(datasource): cover SQLAlchemy pool disposal
1 parent b744ca6 commit 5d60284

1 file changed

Lines changed: 103 additions & 0 deletions

File tree

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
"""Regression tests for SQLAlchemy connection pool lifecycle management."""
2+
3+
import ast
4+
import os
5+
import threading
6+
from collections import OrderedDict
7+
from types import SimpleNamespace
8+
from unittest.mock import Mock
9+
10+
from sqlalchemy import create_engine
11+
from sqlalchemy.orm import sessionmaker
12+
13+
14+
_SRC_PATH = os.path.join(
15+
os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
16+
"apps",
17+
"db",
18+
"db.py",
19+
)
20+
21+
22+
def _load_connection_pool_manager():
23+
"""Load only ConnectionPoolManager to avoid importing database drivers."""
24+
with open(_SRC_PATH, encoding="utf-8") as file:
25+
source = file.read()
26+
27+
tree = ast.parse(source)
28+
class_node = next(
29+
node
30+
for node in tree.body
31+
if isinstance(node, ast.ClassDef) and node.name == "ConnectionPoolManager"
32+
)
33+
34+
class CoreDatasource:
35+
pass
36+
37+
class AssistantOutDsSchema:
38+
pass
39+
40+
namespace = {
41+
"threading": threading,
42+
"OrderedDict": OrderedDict,
43+
"CoreDatasource": CoreDatasource,
44+
"AssistantOutDsSchema": AssistantOutDsSchema,
45+
"sessionmaker": sessionmaker,
46+
"get_engine": None,
47+
}
48+
module = ast.Module(body=[class_node], type_ignores=[])
49+
ast.fix_missing_locations(module)
50+
exec(compile(module, _SRC_PATH, "exec"), namespace)
51+
return namespace["ConnectionPoolManager"], namespace
52+
53+
54+
ConnectionPoolManager, _namespace = _load_connection_pool_manager()
55+
56+
57+
def _engine_factory(created_engines):
58+
def get_engine(ds, use_pool=False):
59+
assert use_pool is True
60+
engine = create_engine("sqlite://")
61+
engine.dispose = Mock(wraps=engine.dispose)
62+
created_engines.append(engine)
63+
return engine
64+
65+
return get_engine
66+
67+
68+
def test_remove_pool_disposes_bound_engine():
69+
created_engines = []
70+
_namespace["get_engine"] = _engine_factory(created_engines)
71+
manager = ConnectionPoolManager()
72+
73+
manager.get_pool(SimpleNamespace(id="ds-1"))
74+
manager.remove_pool("ds-1")
75+
76+
created_engines[0].dispose.assert_called_once_with()
77+
assert "ds-1" not in manager._pools
78+
79+
80+
def test_lru_eviction_disposes_oldest_engine():
81+
created_engines = []
82+
_namespace["get_engine"] = _engine_factory(created_engines)
83+
manager = ConnectionPoolManager(max_pools=1)
84+
85+
manager.get_pool(SimpleNamespace(id="ds-1"))
86+
manager.get_pool(SimpleNamespace(id="ds-2"))
87+
88+
created_engines[0].dispose.assert_called_once_with()
89+
created_engines[1].dispose.assert_not_called()
90+
91+
92+
def test_close_all_disposes_every_engine():
93+
created_engines = []
94+
_namespace["get_engine"] = _engine_factory(created_engines)
95+
manager = ConnectionPoolManager(max_pools=2)
96+
97+
manager.get_pool(SimpleNamespace(id="ds-1"))
98+
manager.get_pool(SimpleNamespace(id="ds-2"))
99+
manager.close_all()
100+
101+
for engine in created_engines:
102+
engine.dispose.assert_called_once_with()
103+
assert not manager._pools

0 commit comments

Comments
 (0)