auto-sync: 2026-05-17 06:03:31
This commit is contained in:
@@ -0,0 +1,212 @@
|
||||
"""F11 Bootstrap 拼装单元测试
|
||||
|
||||
按 test-plan-v2.6.md §F11:
|
||||
- T1: 各 role 拼装(P0)
|
||||
- T2: token 估算(P0)
|
||||
- T3: 缺失组件降级(P1)
|
||||
- T4: 模板变量替换(P1)
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
|
||||
from src.blackboard.models import Task
|
||||
from src.daemon.bootstrap import BootstrapBuilder, estimate_tokens
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def builder():
|
||||
return BootstrapBuilder(max_tokens=4096)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def builder_with_templates(tmp_path):
|
||||
template_dir = tmp_path / "templates"
|
||||
template_dir.mkdir()
|
||||
(template_dir / "executor.md").write_text("# Executor Role\nYou execute tasks.")
|
||||
(template_dir / "reviewer.md").write_text("# Reviewer Role\nYou review code.")
|
||||
(template_dir / "planner.md").write_text("# Planner Role\nYou plan tasks.")
|
||||
return BootstrapBuilder(template_dir=template_dir, max_tokens=4096)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# T1: 各 role 拼装
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestRoleBootstrap:
|
||||
def test_executor_bootstrap(self, builder):
|
||||
b = builder.build(
|
||||
role="executor",
|
||||
task_context={"task_id": "t1", "title": "Write tests"},
|
||||
)
|
||||
assert "Write tests" in b
|
||||
assert "t1" in b
|
||||
|
||||
def test_reviewer_bootstrap(self, builder):
|
||||
b = builder.build(
|
||||
role="reviewer",
|
||||
task_context={"task_id": "t2", "title": "Review PR"},
|
||||
)
|
||||
assert "Review PR" in b
|
||||
|
||||
def test_planner_bootstrap(self, builder):
|
||||
b = builder.build(
|
||||
role="planner",
|
||||
task_context={"task_id": "t3", "title": "Plan sprint"},
|
||||
)
|
||||
assert "Plan sprint" in b
|
||||
|
||||
def test_executor_with_guardrail(self, builder):
|
||||
b = builder.build(
|
||||
role="executor",
|
||||
guardrail_rules="## Guardrail\nNo dangerous ops",
|
||||
)
|
||||
assert "Guardrail" in b
|
||||
|
||||
def test_reviewer_no_guardrail(self, builder):
|
||||
b = builder.build(
|
||||
role="reviewer",
|
||||
guardrail_rules="## Guardrail\nNo dangerous ops",
|
||||
)
|
||||
assert "Guardrail" not in b # reviewer 不注入 guardrail
|
||||
|
||||
def test_executor_with_review_protocol(self, builder):
|
||||
b = builder.build(
|
||||
role="executor",
|
||||
review_protocols="## Review Protocol\nCheck tests",
|
||||
)
|
||||
assert "Review Protocol" in b
|
||||
|
||||
def test_reviewer_with_review_protocol(self, builder):
|
||||
b = builder.build(
|
||||
role="reviewer",
|
||||
review_protocols="## Review Protocol\nCheck quality",
|
||||
)
|
||||
assert "Review Protocol" in b
|
||||
|
||||
def test_with_template(self, builder_with_templates):
|
||||
b = builder_with_templates.build(role="executor")
|
||||
assert "Executor Role" in b
|
||||
assert "You execute tasks" in b
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# T2: token 估算
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestTokenEstimation:
|
||||
def test_estimate_tokens_basic(self):
|
||||
assert estimate_tokens("hello") > 0
|
||||
|
||||
def test_estimate_tokens_long_text(self):
|
||||
text = "a" * 1000
|
||||
tokens = estimate_tokens(text)
|
||||
assert 200 < tokens < 400 # ~333
|
||||
|
||||
def test_bootstrap_under_limit(self, builder):
|
||||
b = builder.build(
|
||||
role="executor",
|
||||
task_context={"title": "Short task"},
|
||||
)
|
||||
assert estimate_tokens(b) <= 4096
|
||||
|
||||
def test_bootstrap_over_limit_truncates(self):
|
||||
builder = BootstrapBuilder(max_tokens=10)
|
||||
b = builder.build(
|
||||
role="executor",
|
||||
task_context={"title": "A" * 10000},
|
||||
)
|
||||
assert estimate_tokens(b) <= 15 # 接近限制
|
||||
assert "truncated" in b
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# T3: 缺失组件降级
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestGracefulDegradation:
|
||||
def test_no_task_context(self, builder):
|
||||
b = builder.build(role="executor")
|
||||
assert b # 不为空
|
||||
|
||||
def test_no_project_context(self, builder):
|
||||
b = builder.build(
|
||||
role="executor",
|
||||
task_context={"title": "Task"},
|
||||
)
|
||||
assert "Task" in b
|
||||
|
||||
def test_no_template(self, builder):
|
||||
b = builder.build(role="executor")
|
||||
assert b # 没有 template 也不崩溃
|
||||
|
||||
def test_empty_experiences(self, builder):
|
||||
b = builder.build(role="executor", experiences=[])
|
||||
assert b
|
||||
|
||||
def test_template_dir_not_exists(self, tmp_path):
|
||||
builder = BootstrapBuilder(template_dir=tmp_path / "nonexistent")
|
||||
b = builder.build(role="executor")
|
||||
assert b # 不崩溃
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# T4: 便捷方法 + 项目上下文
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestBuildForTask:
|
||||
def test_build_for_task_object(self, builder):
|
||||
task = Task(
|
||||
id="t1", title="Build Feature", status="pending",
|
||||
assigned_by="daemon", task_type="coding",
|
||||
description="Implement X with tests",
|
||||
must_haves="Unit tests, Documentation",
|
||||
risk_level="high",
|
||||
)
|
||||
b = builder.build_for_task(task, role="executor")
|
||||
assert "Build Feature" in b
|
||||
assert "Implement X" in b
|
||||
assert "high" in b
|
||||
|
||||
def test_build_for_task_with_project(self, builder):
|
||||
task = Task(id="t1", title="T", status="pending", assigned_by="d")
|
||||
b = builder.build_for_task(
|
||||
task, role="executor",
|
||||
project_config={"name": "My Project", "agents": ["a1", "a2"]},
|
||||
)
|
||||
assert "My Project" in b
|
||||
assert "a1" in b
|
||||
|
||||
def test_with_experiences(self, builder):
|
||||
task = Task(id="t1", title="T", status="pending", assigned_by="d")
|
||||
experiences = [
|
||||
{"category": "pitfall", "summary": "Always test edge cases"},
|
||||
{"category": "best_practice", "summary": "Use type hints"},
|
||||
]
|
||||
b = builder.build_for_task(task, role="executor", experiences=experiences)
|
||||
assert "pitfall" in b
|
||||
assert "Always test edge cases" in b
|
||||
|
||||
def test_with_skills(self, builder):
|
||||
skills = [
|
||||
{"name": "code-review", "description": "Review code quality"},
|
||||
]
|
||||
b = builder.build(
|
||||
role="executor",
|
||||
skill_descriptions=skills,
|
||||
)
|
||||
assert "code-review" in b
|
||||
assert "Review code quality" in b
|
||||
|
||||
def test_with_depends_on_outputs(self, builder):
|
||||
b = builder.build(
|
||||
role="executor",
|
||||
task_context={
|
||||
"title": "T",
|
||||
"depends_on_outputs": [
|
||||
{"task_id": "t0", "summary": "Data downloaded"},
|
||||
],
|
||||
},
|
||||
)
|
||||
assert "前序产出" in b
|
||||
assert "Data downloaded" in b
|
||||
Reference in New Issue
Block a user