Skip to content

Commit 459d8ef

Browse files
committed
fixup! test: Integrate the pytest-global-fixture as postgres fixture
1 parent 042cbf8 commit 459d8ef

File tree

8 files changed

+59
-40
lines changed

8 files changed

+59
-40
lines changed

contrib/pytest-global-fixture/pytest_global_fixture/base.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from abc import ABC, abstractmethod
22
from typing import Any, Dict
33

4+
45
class InfrastructureService(ABC):
56
"""
67
Interface that all shared resources must implement.
@@ -27,12 +28,12 @@ def stop_global(self) -> None:
2728
def create_tenant(self, tenant_id: str) -> Dict[str, Any]:
2829
"""
2930
Create a logical isolation unit (Database, Schema, VHost).
30-
31+
3132
Args:
3233
tenant_id: A unique string identifier for the requester (e.g., 'gw0_test_uuid').
33-
34+
3435
Returns:
35-
A JSON-serializable dictionary containing connection details
36+
A JSON-serializable dictionary containing connection details
3637
(host, port, user, password, db_name, etc.)
3738
"""
3839
pass

contrib/pytest-global-fixture/pytest_global_fixture/manager.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
import threading
22
import importlib
33
import sys
4-
from typing import Dict, Set
4+
from typing import Dict
55
from .base import InfrastructureService
66

7+
78
class ServiceManager:
89
"""
910
Runs on the Master process.
1011
Manages the lifecycle of services and exposes them via XML-RPC.
1112
"""
13+
1214
def __init__(self):
1315
self._services: Dict[str, InfrastructureService] = {} # Map path -> Instance
1416
self._lock = threading.Lock()
@@ -26,7 +28,7 @@ def _load_class(self, class_path: str) -> InfrastructureService:
2628
# Ensure the current directory is in path so we can import local tests
2729
if "." not in sys.path:
2830
sys.path.insert(0, ".")
29-
31+
3032
module = importlib.import_module(module_name)
3133
cls = getattr(module, class_name)
3234
return cls() # Instantiate
@@ -44,16 +46,16 @@ def rpc_provision(self, class_path: str, tenant_id: str) -> Dict:
4446
if class_path not in self._services:
4547
print(f"[Coordinator] Dynamically loading: {class_path}")
4648
service = self._load_class(class_path)
47-
49+
4850
print(f"[Coordinator] Starting Global Resource: {class_path}")
4951
try:
5052
service.start_global()
5153
except Exception as e:
5254
print(f"[Coordinator] Failed to start {class_path}: {e}")
5355
raise e
54-
56+
5557
self._services[class_path] = service
56-
58+
5759
service = self._services[class_path]
5860

5961
# 2. Create Tenant

contrib/pytest-global-fixture/pytest_global_fixture/plugin.py

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,62 @@
11
import pytest
22
import threading
33
import uuid
4-
import sys
54
from xmlrpc.server import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler
65
import xmlrpc.client
76
from .manager import ServiceManager
87

98
# --- RPC Server Setup (Runs on Master) ---
109

10+
1111
class QuietXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
1212
"""Suppress standard logging from XML-RPC server."""
13+
1314
def log_message(self, format, *args):
1415
pass
1516

17+
1618
def pytest_configure(config):
1719
"""
1820
If this is the Master/Coordinator process, start the RPC server.
1921
"""
2022
# Check if we are a worker (xdist). If no workerinput, we are Master.
2123
if not hasattr(config, "workerinput"):
22-
print("\n" + "="*80)
24+
print("\n" + "=" * 80)
2325
print("PYTEST-GLOBAL-FIXTURE: Coordinator mode - managing shared resources")
24-
print("="*80)
26+
print("=" * 80)
2527
manager = ServiceManager()
26-
28+
2729
# Bind to port 0 (ephemeral)
2830
server = SimpleXMLRPCServer(
29-
("localhost", 0),
30-
requestHandler=QuietXMLRPCRequestHandler,
31+
("localhost", 0),
32+
requestHandler=QuietXMLRPCRequestHandler,
3133
allow_none=True,
32-
logRequests=False
34+
logRequests=False,
3335
)
3436
server.register_instance(manager)
35-
37+
3638
# Run server in daemon thread
3739
t = threading.Thread(target=server.serve_forever, daemon=True)
3840
t.start()
39-
41+
4042
host, port = server.server_address
4143
rpc_addr = f"http://{host}:{port}/"
42-
44+
4345
# Store in config to pass to workers/hooks
4446
config.infra_rpc_addr = rpc_addr
4547
config.infra_manager = manager
46-
48+
4749
print(f"--- [Coordinator] Infrastructure Manager listening at {rpc_addr} ---")
4850

51+
4952
def pytest_configure_node(node):
5053
"""
5154
This runs on Master for each Worker node being created.
5255
Pass the RPC address to the worker.
5356
"""
5457
node.workerinput["infra_rpc_addr"] = node.config.infra_rpc_addr
5558

59+
5660
def pytest_unconfigure(config):
5761
"""
5862
Run teardown on Master when session ends.
@@ -63,6 +67,7 @@ def pytest_unconfigure(config):
6367

6468
# --- Fixture (Runs on Workers) ---
6569

70+
6671
@pytest.fixture(scope="session")
6772
def coordinator_client(request):
6873
"""
@@ -71,14 +76,17 @@ def coordinator_client(request):
7176
if hasattr(request.config, "workerinput"):
7277
addr = request.config.workerinput["infra_rpc_addr"]
7378
worker_id = request.config.workerinput.get("workerid", "unknown")
74-
print(f"[{worker_id}] PYTEST-GLOBAL-FIXTURE: Worker connecting to coordinator at {addr}")
79+
print(
80+
f"[{worker_id}] PYTEST-GLOBAL-FIXTURE: Worker connecting to coordinator at {addr}"
81+
)
7582
else:
7683
# We are running sequentially (no xdist), or we are the master
7784
addr = request.config.infra_rpc_addr
7885
print(f"PYTEST-GLOBAL-FIXTURE: Sequential mode, using coordinator at {addr}")
7986

8087
return xmlrpc.client.ServerProxy(addr)
8188

89+
8290
@pytest.fixture(scope="function")
8391
def global_resource(request):
8492
"""
@@ -98,7 +106,6 @@ def global_resource(request):
98106
def _provision(class_path):
99107
# Create unique tenant ID: "gwX_testName_UUID"
100108
worker_id = getattr(request.config, "workerinput", {}).get("workerid", "master")
101-
test_name = request.node.name.replace("[", "_").replace("]", "_")
102109
# Short uuid for uniqueness
103110
uid = uuid.uuid4().hex[:6]
104111
tenant_id = f"{worker_id}_{uid}"

contrib/pytest-global-fixture/pytest_global_fixture/postgres_service.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import subprocess
1313
import tempfile
1414
import time
15-
from pathlib import Path
1615
from psycopg2 import sql
1716
from typing import Dict
1817
from .base import InfrastructureService

contrib/pytest-global-fixture/tests/conftest.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import pytest
22
import psycopg2
33

4+
45
@pytest.fixture(scope="function")
56
def postgres_db(global_resource):
67
"""
@@ -12,12 +13,12 @@ def postgres_db(global_resource):
1213
"""
1314
# Request the service by Class Path
1415
db_config = global_resource("tests.resources:PostgresService")
15-
16+
1617
# Create the actual connection object for the test to use
1718
conn = psycopg2.connect(**db_config)
1819
conn.autocommit = True
19-
20+
2021
yield conn
21-
22+
2223
conn.close()
2324
# After yield, 'global_resource' fixture automatically calls remove_tenant

contrib/pytest-global-fixture/tests/resources.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from testcontainers.postgres import PostgresContainer
55
from pytest_global_fixture.base import InfrastructureService
66

7+
78
class PostgresService(InfrastructureService):
89
def __init__(self):
910
self.container = None
@@ -17,16 +18,16 @@ def start_global(self) -> None:
1718
time.sleep(30)
1819
self.container = PostgresContainer("postgres:15-alpine")
1920
self.container.start()
20-
21+
2122
# Connection info for the Superuser
2223
self.master_config = {
2324
"host": self.container.get_container_host_ip(),
2425
"port": self.container.get_exposed_port(5432),
2526
"user": self.container.username,
2627
"password": self.container.password,
27-
"dbname": self.container.dbname
28+
"dbname": self.container.dbname,
2829
}
29-
30+
3031
# Wait for readiness
3132
self._wait_for_ready()
3233

@@ -47,7 +48,9 @@ def create_tenant(self, tenant_id: str) -> dict:
4748
with conn.cursor() as cur:
4849
# Sanitize the tenant_id slightly for SQL (simple alphanumeric check is best)
4950
safe_name = tenant_id.replace("-", "_")
50-
cur.execute(sql.SQL("CREATE DATABASE {}").format(sql.Identifier(safe_name)))
51+
cur.execute(
52+
sql.SQL("CREATE DATABASE {}").format(sql.Identifier(safe_name))
53+
)
5154
finally:
5255
conn.close()
5356

@@ -66,14 +69,20 @@ def remove_tenant(self, tenant_id: str) -> None:
6669
try:
6770
with conn.cursor() as cur:
6871
# Force disconnect users before dropping
69-
cur.execute(sql.SQL("""
72+
cur.execute(
73+
sql.SQL("""
7074
SELECT pg_terminate_backend(pg_stat_activity.pid)
7175
FROM pg_stat_activity
7276
WHERE pg_stat_activity.datname = {}
7377
AND pid <> pg_backend_pid();
74-
""").format(sql.Literal(safe_name)))
75-
76-
cur.execute(sql.SQL("DROP DATABASE IF EXISTS {}").format(sql.Identifier(safe_name)))
78+
""").format(sql.Literal(safe_name))
79+
)
80+
81+
cur.execute(
82+
sql.SQL("DROP DATABASE IF EXISTS {}").format(
83+
sql.Identifier(safe_name)
84+
)
85+
)
7786
finally:
7887
conn.close()
7988

contrib/pytest-global-fixture/tests/test_postgres.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import pytest
2-
import os
1+
32

43
def test_table_isolation_a(postgres_db, worker_id):
54
"""
@@ -11,13 +10,14 @@ def test_table_isolation_a(postgres_db, worker_id):
1110
cur.execute("SELECT current_database();")
1211
db_name = cur.fetchone()[0]
1312
print(f"Worker {worker_id} connected to {db_name}")
14-
13+
1514
cur.execute("CREATE TABLE items (id serial PRIMARY KEY, name text);")
1615
cur.execute("INSERT INTO items (name) VALUES ('item_from_a');")
17-
16+
1817
cur.execute("SELECT count(*) FROM items;")
1918
assert cur.fetchone()[0] == 1
2019

20+
2121
def test_table_isolation_b(postgres_db, worker_id):
2222
"""
2323
Runs in parallel with A.
@@ -36,8 +36,8 @@ def test_table_isolation_b(postgres_db, worker_id):
3636
except Exception:
3737
exists = False
3838
# Reset transaction if error occurred
39-
postgres_db.rollback()
40-
39+
postgres_db.rollback()
40+
4141
if not exists:
4242
# Good, table doesn't exist, let's create our own
4343
cur.execute("CREATE TABLE items (id serial PRIMARY KEY, name text);")

tests/fixtures.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from utils import TEST_NETWORK, VALGRIND # noqa: F401,F403
22
from pyln.testing.fixtures import directory, test_base_dir, test_name, chainparams, node_factory, bitcoind, teardown_checks, db_provider, executor, setup_logging, jsonschemas # noqa: F401,F403
33
from pyln.testing import utils
4-
from pytest_global_fixture.plugin import global_resource, coordinator_client
4+
from pytest_global_fixture.plugin import global_resource, coordinator_client # noqa: F401
55
from utils import COMPAT
66
from pathlib import Path
77

0 commit comments

Comments
 (0)