refactor: extract minimal pure helpers from _continue_task, _task_wait, _task_thread_read and _metrics
_continue_task 226->168 via _validate_continue_options; _task_wait 158->139 via _validate_wait_params; _task_thread_read 187->152 via _summarize_trace_entries; _metrics 123->104 via _summarize_metrics_tasks. No behaviour, message, status or route change; error strings verbatim. New tests: test_longfunc_extra (9). Cloud full suite: 1439 OK, skipped=4. Follows #229 pattern; must merge after #229 and be rebased/checked then.
This commit is contained in:
1 parent
f96960c9c5
commit
1accfac844
2 files changed
+380
-154
No files matched your search
@@ -0,0 +1,177 @@
|
||||
"""长函数小提取第二轨(#229 之后):四处纯函数钉住测试。
|
||||
|
||||
只钉住本轨抽出的四个纯函数(成功路径与错误串),不改原有
|
||||
断言语义;原路由行为不变由 test_task_continue /
|
||||
test_thread_wait_ops / test_thread_ops 等定向套件覆盖。
|
||||
"""
|
||||
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
from cloud.server import (
|
||||
TASK_STATUS_KEYS,
|
||||
_summarize_metrics_tasks,
|
||||
_summarize_trace_entries,
|
||||
_validate_continue_options,
|
||||
_validate_wait_params,
|
||||
)
|
||||
except ImportError: # discover -s cloud/tests 时把项目根目录补进 sys.path
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
|
||||
from cloud.server import (
|
||||
TASK_STATUS_KEYS,
|
||||
_summarize_metrics_tasks,
|
||||
_summarize_trace_entries,
|
||||
_validate_continue_options,
|
||||
_validate_wait_params,
|
||||
)
|
||||
|
||||
|
||||
def _task(**over):
|
||||
task = {"plan": 0, "goal_mode": 0, "approval": "ask"}
|
||||
task.update(over)
|
||||
return task
|
||||
|
||||
|
||||
class ValidateContinueOptionsTestCase(unittest.TestCase):
|
||||
def test_success_defaults_from_task(self):
|
||||
opts, err = _validate_continue_options({}, _task(), None)
|
||||
self.assertIsNone(err)
|
||||
self.assertIsNone(opts["model"])
|
||||
self.assertIsNone(opts["effort"])
|
||||
self.assertFalse(opts["child_model_present"])
|
||||
self.assertFalse(opts["child_effort_present"])
|
||||
self.assertIsNone(opts["approval"])
|
||||
self.assertIsNone(opts["plan"])
|
||||
self.assertIsNone(opts["goal_mode"])
|
||||
|
||||
def test_success_explicit_and_empty_child_clears(self):
|
||||
opts, err = _validate_continue_options(
|
||||
{"model": " m ", "effort": "low", "child_model": "",
|
||||
"child_effort": "", "approval": "auto", "plan": True,
|
||||
"goal_mode": 0},
|
||||
_task(), None)
|
||||
self.assertIsNone(err)
|
||||
self.assertEqual(opts["model"], "m")
|
||||
self.assertEqual(opts["effort"], "low")
|
||||
self.assertIsNone(opts["child_model"])
|
||||
self.assertTrue(opts["child_model_present"])
|
||||
self.assertIsNone(opts["child_effort"])
|
||||
self.assertTrue(opts["child_effort_present"])
|
||||
self.assertEqual(opts["plan"], 1)
|
||||
self.assertEqual(opts["goal_mode"], 0)
|
||||
|
||||
def test_goal_command_forces_goal_mode(self):
|
||||
opts, err = _validate_continue_options(
|
||||
{"goal_mode": 0}, _task(), "do X")
|
||||
self.assertIsNone(err)
|
||||
self.assertEqual(opts["goal_mode"], 1)
|
||||
|
||||
def test_error_strings_verbatim(self):
|
||||
cases = [
|
||||
({"model": 1}, "model must be a string"),
|
||||
({"effort": "huge"},
|
||||
"effort must be low, medium, high, xhigh or max"),
|
||||
({"child_model": 1}, "child_model must be a string"),
|
||||
({"child_effort": "huge"},
|
||||
"child_effort must be low, medium, high, xhigh or max"),
|
||||
({"approval": "all"}, "approval must be ask, auto or full"),
|
||||
({"plan": True, "approval": "full"},
|
||||
"plan 模式与 approval=full 冲突(只读 vs 完全访问)"),
|
||||
({"plan": True, "goal_mode": 1}, "plan 模式与 goal 模式冲突"),
|
||||
]
|
||||
for body, want in cases:
|
||||
with self.subTest(body=body):
|
||||
opts, err = _validate_continue_options(body, _task(), None)
|
||||
self.assertIsNone(opts)
|
||||
self.assertEqual(err, want)
|
||||
|
||||
|
||||
class ValidateWaitParamsTestCase(unittest.TestCase):
|
||||
def test_default_and_valid(self):
|
||||
timeout, requested, err = _validate_wait_params({})
|
||||
self.assertEqual(timeout, 30)
|
||||
self.assertIsNone(requested)
|
||||
self.assertIsNone(err)
|
||||
timeout, requested, err = _validate_wait_params(
|
||||
{"timeout_seconds": 5, "task_ids": ["a"]})
|
||||
self.assertEqual((timeout, requested, err), (5, ["a"], None))
|
||||
|
||||
def test_error_payloads_verbatim(self):
|
||||
cases = [
|
||||
({"timeout_seconds": True},
|
||||
{"error": "timeout_seconds must be a number"}),
|
||||
({"timeout_seconds": 121},
|
||||
{"error": "timeout_seconds must be between 0 and 120"}),
|
||||
({"task_ids": "a"}, {"error": "task_ids must be a list"}),
|
||||
({"task_ids": [1]}, {"error": "task_ids must be a list"}),
|
||||
({"task_ids": []}, {"error": "task_ids must not be empty"}),
|
||||
({"task_ids": ["a"] * 9},
|
||||
{"error": "at most 8 threads per wait", "count": 9}),
|
||||
]
|
||||
for body, want in cases:
|
||||
with self.subTest(body=body):
|
||||
_, _, err = _validate_wait_params(body)
|
||||
self.assertEqual(err, want)
|
||||
|
||||
|
||||
class SummarizeTraceEntriesTestCase(unittest.TestCase):
|
||||
def test_changed_commands_activity(self):
|
||||
entries = [
|
||||
{"kind": "file", "path": "a.py", "change": "mod",
|
||||
"status": "ok"},
|
||||
{"kind": "file", "path": "a.py", "change": "mod2",
|
||||
"status": "ok2"},
|
||||
{"kind": "exec", "command": "pytest", "exitCode": 0,
|
||||
"durationMs": 7},
|
||||
{"kind": "tool", "server": "s", "tool": "t",
|
||||
"status": "done", "id": "x"},
|
||||
{"kind": "message", "text": "hi", "id": "y"},
|
||||
"not-a-dict",
|
||||
]
|
||||
changed, commands, activity = _summarize_trace_entries(entries)
|
||||
self.assertEqual(changed, [{"path": "a.py", "change": "mod2",
|
||||
"status": "ok2"}])
|
||||
self.assertEqual(commands, [{"command": "pytest", "exit_code": 0,
|
||||
"duration_ms": 7}])
|
||||
self.assertEqual(activity[3],
|
||||
{"kind": "tool", "id": "x", "text": "s/t: done"})
|
||||
self.assertEqual(activity[4],
|
||||
{"kind": "message", "id": "y", "text": "hi"})
|
||||
self.assertEqual(activity[5], {"kind": "unknown"})
|
||||
|
||||
def test_truncation_windows(self):
|
||||
entries = [{"kind": "message", "text": "t%d" % i, "id": i}
|
||||
for i in range(15)]
|
||||
_, _, activity = _summarize_trace_entries(entries)
|
||||
self.assertEqual(len(activity), 10)
|
||||
self.assertEqual(activity[0]["text"], "t5")
|
||||
|
||||
|
||||
class SummarizeMetricsTasksTestCase(unittest.TestCase):
|
||||
def test_aggregation_and_member_filter(self):
|
||||
now = 1000000.0
|
||||
rows = [
|
||||
{"project_id": "p1", "status": "completed",
|
||||
"started_at": 100.0, "finished_at": 160.0},
|
||||
{"project_id": "p1", "status": "failed",
|
||||
"started_at": None, "finished_at": now - 100},
|
||||
{"project_id": "p1", "status": "running",
|
||||
"started_at": now - 400, "finished_at": None},
|
||||
{"project_id": "p2", "status": "completed",
|
||||
"started_at": 0.0, "finished_at": 10.0},
|
||||
]
|
||||
by_status, durations, failures, over = _summarize_metrics_tasks(
|
||||
rows, {"p1"}, now)
|
||||
self.assertEqual(set(by_status), set(TASK_STATUS_KEYS))
|
||||
self.assertEqual(by_status["completed"], 1)
|
||||
self.assertEqual(by_status["failed"], 1)
|
||||
self.assertEqual(by_status["running"], 1)
|
||||
self.assertEqual(durations, [60.0])
|
||||
self.assertEqual(failures, 1)
|
||||
self.assertEqual(over, 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user