Skip to content

Commit 6010ee5

Browse files
committed
Reduce backend test warning noise
1 parent e4560c1 commit 6010ee5

17 files changed

Lines changed: 322 additions & 102 deletions

server/alembic.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
[alembic]
22
script_location = hive/db/migrations
33
prepend_sys_path = .
4+
path_separator = os
45
sqlalchemy.url = postgresql+asyncpg://hive:hive@localhost:5432/hive
56

67
[loggers]

server/conftest.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,31 @@
11
"""Root conftest: add repo root to sys.path so packages.shared is importable."""
22

33
import sys
4+
import warnings
45
from pathlib import Path
56

67
# Repo root is one level up from server/
78
_REPO_ROOT = str(Path(__file__).parent.parent)
89
if _REPO_ROOT not in sys.path:
910
sys.path.insert(0, _REPO_ROOT)
11+
12+
warnings.filterwarnings(
13+
"ignore",
14+
message=r".*datetime\.datetime\.utcfromtimestamp\(\) is deprecated.*",
15+
category=DeprecationWarning,
16+
)
17+
warnings.filterwarnings(
18+
"ignore",
19+
message=r".*There is no current event loop.*",
20+
category=DeprecationWarning,
21+
)
22+
warnings.filterwarnings(
23+
"ignore",
24+
message=r".*websockets\.InvalidStatusCode is deprecated.*",
25+
category=DeprecationWarning,
26+
)
27+
warnings.filterwarnings(
28+
"ignore",
29+
message=r".*websockets\.legacy is deprecated.*",
30+
category=DeprecationWarning,
31+
)

server/hive/gateway/feishu_ws.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ def schedule_on_loop(
7171
closed (e.g. during server shutdown). Never raises.
7272
"""
7373
if loop.is_closed():
74+
close = getattr(coro, "close", None)
75+
if callable(close):
76+
close()
7477
logger.warning("feishu_ws.loop_closed_dropping_event")
7578
return None
7679
return asyncio.run_coroutine_threadsafe(coro, loop)

server/hive/gateway/project_ws.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
from hive.db import queries
2121
from hive.db.models import ProjectChannel
2222
from hive.gateway.feishu import FeishuMessage
23-
from hive.gateway.feishu_ws import build_event_handler, start_ws_client
2423
from hive.tracing import generate_trace_id, get_trace_id, set_trace_id
2524

2625
logger = structlog.get_logger("hive.gateway.project_ws")
@@ -90,6 +89,8 @@ def start_channel(
9089
If False, private messages on this channel are silently ignored — the channel
9190
is used only for Scout group messages.
9291
"""
92+
from hive.gateway.feishu_ws import build_event_handler, start_ws_client
93+
9394
self.stop_channel(channel_id)
9495

9596
pool = self._pool

server/hive/runtime/command_runner.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,11 @@ class CommandRunner:
7474

7575
def __init__(self, *, shell_bin: str = "/bin/sh") -> None:
7676
self._shell_bin = shell_bin
77+
self._active_processes: dict[str, subprocess.Popen[str]] = {}
7778

7879
async def start(self, spec: CommandRunSpec) -> CommandRunHandle:
7980
command = self._normalize_command(spec.command)
81+
run_id = uuid.uuid4().hex
8082
async with span(
8183
"sandbox.command.start",
8284
cwd=spec.cwd,
@@ -91,10 +93,11 @@ async def start(self, spec: CommandRunSpec) -> CommandRunHandle:
9193
start_new_session=True,
9294
text=True,
9395
)
96+
self._active_processes[run_id] = process
9497
pid = process.pid
9598
Path(spec.pid_path).write_text(f"{pid}\n", encoding="utf-8")
9699
return CommandRunHandle(
97-
run_id=uuid.uuid4().hex,
100+
run_id=run_id,
98101
pid=pid,
99102
command=command,
100103
cwd=spec.cwd,
@@ -121,6 +124,7 @@ async def snapshot(
121124
effective_started_at = started_at or handle.started_at
122125

123126
if result_payload is not None:
127+
self._reap_process(handle.run_id)
124128
exit_code = self._as_int(result_payload.get("exit_code"))
125129
timed_out = bool(result_payload.get("timed_out", False))
126130
interrupted = bool(result_payload.get("interrupted", False))
@@ -177,6 +181,7 @@ async def cancel(self, handle: CommandRunHandle) -> None:
177181
'{"exit_code":null,"duration_ms":0,"timed_out":false,"interrupted":true}',
178182
encoding="utf-8",
179183
)
184+
self._reap_process(handle.run_id)
180185

181186
@staticmethod
182187
def _normalize_command(command: list[str]) -> list[str]:
@@ -258,6 +263,18 @@ def _build_script(self, spec: CommandRunSpec, command: list[str]) -> str:
258263
EOF
259264
"""
260265

266+
def _reap_process(self, run_id: str) -> None:
267+
process = self._active_processes.get(run_id)
268+
if process is None:
269+
return
270+
try:
271+
process.wait(timeout=0)
272+
except subprocess.TimeoutExpired:
273+
# The shell wrapper is still unwinding; keep the handle so a later
274+
# snapshot or cancel can reap it without leaking a ResourceWarning.
275+
return
276+
self._active_processes.pop(run_id, None)
277+
261278
@staticmethod
262279
def _read_pid(path: Path) -> int | None:
263280
if not path.exists():

server/pyproject.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ testpaths = ["tests"]
3737
markers = [
3838
"integration: end-to-end tests that require a running server (deselect with -m 'not integration')",
3939
]
40+
filterwarnings = [
41+
"ignore:datetime\\.datetime\\.utcfromtimestamp\\(\\) is deprecated.*:DeprecationWarning",
42+
"ignore:There is no current event loop:DeprecationWarning",
43+
"ignore:websockets\\.InvalidStatusCode is deprecated:DeprecationWarning",
44+
"ignore:websockets\\.legacy is deprecated.*:DeprecationWarning",
45+
]
4046

4147
[tool.ruff]
4248
target-version = "py312"
@@ -56,4 +62,3 @@ build-backend = "hatchling.build"
5662

5763
[tool.hatch.build.targets.wheel]
5864
packages = ["hive"]
59-

server/tests/test_agent/test_checkpoint.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,9 @@ async def _fake_upsert(table: str, **kwargs):
9696

9797
@asynccontextmanager
9898
async def fake_sf():
99-
yield AsyncMock()
99+
session = AsyncMock()
100+
session.add = MagicMock()
101+
yield session
100102

101103
with patch("hive.agent.llm.create_provider", return_value=MagicMock(
102104
context_window=200_000,

server/tests/test_agent/test_llm_usage.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,15 @@
1313
from hive.agent.scope import AgentScope
1414

1515

16+
def _session_factory() -> MagicMock:
17+
session = AsyncMock()
18+
session.__aenter__ = AsyncMock(return_value=session)
19+
session.__aexit__ = AsyncMock(return_value=None)
20+
session.add = MagicMock()
21+
session.commit = AsyncMock()
22+
return MagicMock(return_value=session)
23+
24+
1625
def _make_config(tmp_path: Path) -> AgentConfig:
1726
(tmp_path / "MEMORY.md").write_text("", encoding="utf-8")
1827
return AgentConfig(
@@ -46,7 +55,7 @@ async def test_llm_usage_inserted_on_run(tmp_path):
4655
async def fake_record_llm_usage(**kwargs):
4756
inserted_rows.append(kwargs)
4857

49-
agent.db._session_factory = MagicMock()
58+
agent.db._session_factory = _session_factory()
5059
agent.db.record_llm_usage = fake_record_llm_usage # type: ignore[method-assign]
5160
agent.db.update_one = AsyncMock(return_value=None)
5261

@@ -94,7 +103,7 @@ async def test_llm_usage_insert_failure_does_not_raise(tmp_path):
94103
"""A DB write failure for llm_usage is logged as warning, not raised."""
95104
config = _make_config(tmp_path)
96105
agent = HiveAgent(config)
97-
agent.db._session_factory = MagicMock()
106+
agent.db._session_factory = _session_factory()
98107

99108
async def failing_record_llm_usage(**kwargs):
100109
raise RuntimeError("db error")
@@ -116,7 +125,7 @@ async def test_llm_usage_trace_id_captured(tmp_path):
116125

117126
config = _make_config(tmp_path)
118127
agent = HiveAgent(config)
119-
agent.db._session_factory = MagicMock()
128+
agent.db._session_factory = _session_factory()
120129

121130
captured: list[dict] = []
122131

server/tests/test_agent/test_runtime.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,15 @@
1111
from hive.agent.scope import AgentScope
1212

1313

14+
def _session_factory() -> MagicMock:
15+
session = AsyncMock()
16+
session.__aenter__ = AsyncMock(return_value=session)
17+
session.__aexit__ = AsyncMock(return_value=None)
18+
session.add = MagicMock()
19+
session.commit = AsyncMock()
20+
return MagicMock(return_value=session)
21+
22+
1423
def _make_config(tmp_path: Path, tools: list[str] | None = None) -> AgentConfig:
1524
(tmp_path / "MEMORY.md").write_text("", encoding="utf-8")
1625
return AgentConfig(
@@ -179,6 +188,8 @@ async def test_run_writes_session_metrics_to_db(tmp_path):
179188
)
180189

181190
mock_session = _AsyncMock()
191+
mock_session.add = MagicMock()
192+
mock_session.commit = _AsyncMock()
182193

183194
@asynccontextmanager
184195
async def fake_factory():
@@ -211,7 +222,7 @@ async def test_run_writes_agent_run_and_events(tmp_path):
211222
"""agent.run() should create a run spine with ordered runtime events."""
212223
config = _make_config(tmp_path)
213224
agent = HiveAgent(config)
214-
agent.db._session_factory = MagicMock()
225+
agent.db._session_factory = _session_factory()
215226

216227
inserts: list[tuple[str, dict]] = []
217228
updates: list[tuple[str, object, dict]] = []
@@ -247,7 +258,7 @@ async def test_run_marks_scheduler_invocation_source(tmp_path):
247258
"""Scheduler-triggered runs must get a separate runtime-context channel marker."""
248259
config = _make_config(tmp_path)
249260
agent = HiveAgent(config)
250-
agent.db._session_factory = MagicMock()
261+
agent.db._session_factory = _session_factory()
251262

252263
captured_runs: list[dict] = []
253264

server/tests/test_gateway/test_api_channel.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ def _build_app(role: str = "pm", user_id: str = "pm_alice") -> tuple[FastAPI, As
7474
app = FastAPI()
7575
app.include_router(router)
7676
session = AsyncMock()
77+
session.add = MagicMock()
78+
session.commit = AsyncMock()
7779
user = {"user_id": user_id, "role": role}
7880
app.dependency_overrides[get_current_user] = lambda: user
7981
app.dependency_overrides[get_db_session] = _async_gen(session)

0 commit comments

Comments
 (0)