diff --git a/.claude/rules/backend/test.md b/.claude/rules/backend/test.md index 44b48d31..585e1665 100644 --- a/.claude/rules/backend/test.md +++ b/.claude/rules/backend/test.md @@ -50,3 +50,4 @@ pytest は標準 SQLite + `Base.metadata.create_all` で動くため、**本番 - `try / except Exception: pass` をテストコード内で使う(失敗を隠す) - `time.sleep` での同期待ち(フレーキーになる。`AsyncMock` / `monkeypatch` を使う) - 過剰モック: SQLAlchemy セッション全体をモックする等。実 DB セッションを使うこと +- **テストで `asyncio.set_event_loop` / `get_event_loop`(グローバル event loop)を触る**: mutmut 3.x は同一プロセスでスイートを複数回実行するため、グローバル loop 状態が実行間に漏れて clean test が `RuntimeError: There is no current event loop` で落ちる(`make mutation-backend` が `Failed to run clean test` で停止した実績)。async は `loop = asyncio.new_event_loop(); try: loop.run_until_complete(...); finally: loop.close()` の分離パターンで実行する(グローバル loop を設定・復元しない) diff --git a/backend/tests/security/test_ssrf_github.py b/backend/tests/security/test_ssrf_github.py index 750b928b..c6347343 100644 --- a/backend/tests/security/test_ssrf_github.py +++ b/backend/tests/security/test_ssrf_github.py @@ -32,16 +32,18 @@ def _run(coro): - """既存テストの event loop 前提を壊さず非同期関数を実行する。""" - original = asyncio.get_event_loop_policy().get_event_loop() + """async 関数を専用 event loop で同期実行する(グローバル loop を触らない)。 + + mutmut 3.x は同一プロセスで pytest スイートを複数回実行するため、 + ``asyncio.set_event_loop`` / ``get_event_loop`` でグローバル loop 状態に + 触れると実行間に汚染が漏れて clean test が落ちる。ローカル loop を作って + 閉じるだけの分離パターンにして反復実行に対して冪等にする。 + """ loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) try: return loop.run_until_complete(coro) finally: loop.close() - # 一時 loop を閉じたら元の loop へ戻す(新規 loop を作って放置すると leak する)。 - asyncio.set_event_loop(original) @pytest.mark.parametrize("bad", _MALICIOUS_OWNERS)