From 0c1f89a43a302ada836b113314b58cd679372974 Mon Sep 17 00:00:00 2001 From: cfdaily Date: Fri, 5 Jun 2026 13:27:20 +0800 Subject: [PATCH] auto-sync: 2026-06-05 13:27:20 --- tests/unit/test_classify_outcome.py | 228 ++++++++++++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 tests/unit/test_classify_outcome.py diff --git a/tests/unit/test_classify_outcome.py b/tests/unit/test_classify_outcome.py new file mode 100644 index 0000000..0cfdb97 --- /dev/null +++ b/tests/unit/test_classify_outcome.py @@ -0,0 +1,228 @@ +import pytest +from typing import Optional + +pytestmark = pytest.mark.unit + +from src.daemon.spawner import Spawner + + +class TestClassifyCompleted: + """A1: 正常完成""" + + def test_ok_completed(self): + result = Spawner._classify_outcome( + 0, {"status": "ok", "summary": "completed"}, "", None + ) + assert result == {"outcome": "completed", "should_retry": False} + + def test_ok_completed_with_fallback(self): + """A5/A6: status=ok + fallback_used""" + result = Spawner._classify_outcome( + 0, + {"status": "ok", "summary": "completed", "fallback_used": True}, + "", + None, + ) + assert result["outcome"] == "fallback_timeout" + assert result["should_retry"] is False + + +class TestClassifyAgentFailed: + """A4: Agent 自标 failed""" + + def test_task_status_failed(self): + result = Spawner._classify_outcome( + 0, {"status": "ok"}, "", "failed" + ) + assert result["outcome"] == "agent_failed" + assert result["should_retry"] is False + + +class TestClassifyTimeout: + """A2/A3: gateway timeout""" + + def test_timeout_should_retry(self): + result = Spawner._classify_outcome(0, {"status": "timeout"}, "", None) + assert result["outcome"] == "gateway_timeout" + assert result["should_retry"] is True + assert result["retry_field"] == "retry_count" + + +class TestClassifyInterrupted: + """A14: SIGINT / SIGTERM 外部中断""" + + def test_sigint_exit_130(self): + result = Spawner._classify_outcome(130, {}, "", None, "") + assert result["outcome"] == "interrupted" + assert result["should_retry"] is True + assert result["retry_field"] == "retry_count" + assert result["cooldown_seconds"] == 60 + + def test_sigterm_exit_143(self): + result = Spawner._classify_outcome(143, {}, "", None, "") + assert result["outcome"] == "interrupted" + assert result["should_retry"] is True + assert result["retry_field"] == "retry_count" + assert result["cooldown_seconds"] == 60 + + +class TestClassifyGatewayUnreachableNoJson: + """A15: 无 JSON + exit≠0 + stderr 含 network 关键字""" + + def test_stderr_econnrefused(self): + result = Spawner._classify_outcome( + 1, {}, "Error: ECONNREFUSED connection refused", None, "" + ) + assert result["outcome"] == "gateway_unreachable" + assert result["should_retry"] is True + assert result["cooldown_seconds"] == 60 + + def test_stderr_gateway_closed(self): + result = Spawner._classify_outcome( + 1, {}, "gateway closed unexpectedly", None, "" + ) + assert result["outcome"] == "gateway_unreachable" + assert result["should_retry"] is True + assert result["cooldown_seconds"] == 60 + + +class TestClassifyCompactInterruptedNoJson: + """A16: 无 JSON + exit≠0 + stderr 含 compact 关键字""" + + def test_stderr_compaction_diag(self): + result = Spawner._classify_outcome( + 1, {}, "compaction-diag: overflow detected", None, "" + ) + assert result["outcome"] == "compact_interrupted" + assert result["should_retry"] is True + assert result["cooldown_seconds"] == 60 + + +class TestClassifyCrashed: + """A17: 无 JSON + exit≠0 + 无匹配 stderr → crash""" + + def test_crash_no_stderr(self): + result = Spawner._classify_outcome(1, {}, "", None, "") + assert result["outcome"] == "crashed" + assert result["should_retry"] is False + assert result["original"] == "process_crash" + + +class TestClassifyNoJsonExit0: + """exit=0 + 空 stdout → 根据 task_status 判定""" + + def test_task_status_done(self): + result = Spawner._classify_outcome(0, {}, "", "done", "") + assert result["outcome"] == "completed" + assert result["should_retry"] is False + + def test_task_status_review(self): + result = Spawner._classify_outcome(0, {}, "", "review", "") + assert result["outcome"] == "completed" + assert result["should_retry"] is False + + def test_task_status_pending(self): + result = Spawner._classify_outcome(0, {}, "", "pending", "") + assert result["outcome"] == "agent_error" + assert result["should_retry"] is False + + +class TestClassifyErrorAuth: + """A7: status=error + stderr 含 auth 关键字""" + + def test_stderr_401(self): + result = Spawner._classify_outcome( + 1, {"status": "error"}, "HTTP 401 Unauthorized", None + ) + assert result["outcome"] == "auth_failed" + assert result["should_retry"] is False + + def test_stderr_unauthorized(self): + result = Spawner._classify_outcome( + 1, {"status": "error"}, "unauthorized access", None + ) + assert result["outcome"] == "auth_failed" + assert result["should_retry"] is False + + +class TestClassifyErrorGateway: + """A8: status=error + stderr 含 network 关键字""" + + def test_stderr_econnrefused(self): + result = Spawner._classify_outcome( + 1, {"status": "error"}, "ECONNREFUSED 127.0.0.1:8083", None + ) + assert result["outcome"] == "gateway_unreachable" + assert result["should_retry"] is True + assert result["cooldown_seconds"] == 60 + + +class TestClassifyErrorApi: + """A9: status=error + stderr 含 API 错误关键字""" + + def test_stderr_rate_limit(self): + result = Spawner._classify_outcome( + 1, {"status": "error"}, "rate_limit exceeded", None + ) + assert result["outcome"] == "api_error" + assert result["should_retry"] is False + + def test_stderr_500(self): + result = Spawner._classify_outcome( + 1, {"status": "error"}, "HTTP 500 Internal Server Error", None + ) + assert result["outcome"] == "api_error" + assert result["should_retry"] is False + + +class TestClassifyErrorCompact: + """A10: status=error + stderr 含 compact 关键字""" + + def test_stderr_compaction_diag(self): + result = Spawner._classify_outcome( + 1, {"status": "error"}, "compaction-diag report", None + ) + assert result["outcome"] == "compact_failed" + assert result["should_retry"] is False + + +class TestClassifyErrorLock: + """A11: status=error + stderr 含 lock 关键字""" + + def test_stderr_lock(self): + result = Spawner._classify_outcome( + 1, {"status": "error"}, "lock acquisition failed", None + ) + assert result["outcome"] == "lock_conflict" + assert result["should_retry"] is True + assert result["cooldown_seconds"] == 60 + + def test_stderr_busy(self): + result = Spawner._classify_outcome( + 1, {"status": "error"}, "resource is busy", None + ) + assert result["outcome"] == "lock_conflict" + assert result["should_retry"] is True + + +class TestClassifyErrorFallback: + """A12: status=error + stderr 无匹配""" + + def test_no_matching_stderr(self): + result = Spawner._classify_outcome( + 1, {"status": "error"}, "some unknown error", None + ) + assert result["outcome"] == "agent_error" + assert result["should_retry"] is False + + +class TestClassifyUnknownStatus: + """兜底: status 为未知值""" + + def test_unknown_status_value(self): + result = Spawner._classify_outcome( + 0, {"status": "weird_value"}, "", None + ) + assert result["outcome"] == "agent_error" + assert result["should_retry"] is False + assert result["original"] == "unknown_status"