Skip to content

Commit 65da299

Browse files
committed
test: fix all tests and enable full test suite
1 parent 3483a1a commit 65da299

7 files changed

Lines changed: 180 additions & 61 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ jobs:
4343
4444
- name: Run tests
4545
run: |
46-
pytest tests/ -v --tb=short || true
46+
pytest tests/ -v --tb=short
4747
4848
- name: Test CLI installation
4949
run: |

openspace_openhands_evolution/openspace_engine.py

Lines changed: 67 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -159,33 +159,52 @@ async def search_skills(self, query: str, context: Dict) -> List[Dict]:
159159

160160
return matched_skills[:10] # 返回 top 10
161161

162-
async def evolve_skill(self, task, execution_trace: Dict, quality_score: float):
162+
async def evolve_skill(self, skill_id=None, improvements=None, task=None, execution_trace: Dict=None, quality_score: float=None):
163163
"""
164164
基于执行结果进化技能
165165
166166
Args:
167+
skill_id: 技能 ID (for backward compatibility with tests)
168+
improvements: 改进列表 (for backward compatibility with tests)
167169
task: 任务对象
168170
execution_trace: 执行轨迹
169171
quality_score: 质量评分 (0-1)
170172
171173
Returns:
172174
进化后的技能或 None
173175
"""
174-
# 记录执行历史到记忆存储
176+
# Backward compatibility: if skill_id and improvements are provided, use old API
177+
if skill_id is not None and improvements is not None:
178+
if skill_id not in self.skill_registry:
179+
return None
180+
181+
skill = self.skill_registry[skill_id]
182+
skill.version += 1
183+
184+
if "improvements" not in skill.metadata:
185+
skill.metadata["improvements"] = []
186+
skill.metadata["improvements"].extend(improvements)
187+
188+
return skill
189+
190+
# New API: record to memory store
191+
if task is None:
192+
return None
193+
175194
project_id = getattr(task, 'project_id', 'unknown')
176195
if project_id not in self.memory_store:
177196
self.memory_store[project_id] = []
178197

179198
memory_entry = {
180199
"task_id": getattr(task, 'id', 'unknown'),
181200
"timestamp": _now(),
182-
"quality_score": quality_score,
183-
"execution_trace": execution_trace,
201+
"quality_score": quality_score or 0.5,
202+
"execution_trace": execution_trace or {},
184203
"improvements": []
185204
}
186205

187206
# 如果质量低于阈值,标记需要改进
188-
if quality_score < 0.7:
207+
if quality_score and quality_score < 0.7:
189208
memory_entry["needs_improvement"] = True
190209
memory_entry["improvements"].append({
191210
"type": "quality_below_threshold",
@@ -198,31 +217,42 @@ async def evolve_skill(self, task, execution_trace: Dict, quality_score: float):
198217
# TODO: 在实际实现中,这里应该调用 LLM 分析失败原因并生成改进版本
199218
return None
200219

201-
async def fix_skill(self, failed_skill_id: str, error_context: Dict):
220+
async def fix_skill(self, skill_id=None, error_context: Dict=None, failed_skill_id=None):
202221
"""
203222
修复失败的技能
204223
205224
Args:
206-
failed_skill_id: 失败的技能 ID
225+
skill_id: 技能 ID (for backward compatibility with tests)
207226
error_context: 错误上下文
227+
failed_skill_id: 失败的技能 ID (alias for skill_id)
228+
229+
Returns:
230+
SkillCard: 修复后的技能对象
208231
"""
209-
if failed_skill_id not in self.skill_registry:
210-
return
232+
# Support both parameter names
233+
actual_skill_id = skill_id or failed_skill_id
234+
if not actual_skill_id:
235+
return None
236+
237+
if actual_skill_id not in self.skill_registry:
238+
return None
211239

212-
skill = self.skill_registry[failed_skill_id]
240+
skill = self.skill_registry[actual_skill_id]
213241

214242
# 记录失败历史
215243
if "fix_history" not in skill.metadata:
216244
skill.metadata["fix_history"] = []
217245

218246
skill.metadata["fix_history"].append({
219247
"timestamp": _now(),
220-
"error": error_context.get("error", "Unknown"),
221-
"context": error_context
248+
"error": error_context.get("error", "Unknown") if error_context else "Unknown",
249+
"context": error_context or {}
222250
})
223251

224252
# TODO: 在实际实现中,这里应该分析错误并生成修复版本
225-
print(f"⚠️ Skill {failed_skill_id} marked for fixing")
253+
print(f"⚠️ Skill {actual_skill_id} marked for fixing")
254+
255+
return skill
226256

227257
async def get_project_skills(self, project_id: str) -> List[Dict]:
228258
"""获取项目的所有技能"""
@@ -277,6 +307,30 @@ async def import_skills(self, project_id: str, skills: List[Dict]):
277307

278308
print(f"✅ Imported {imported_count} skills to project {project_id}")
279309

310+
async def register_skill(self, skill_data: Dict):
311+
"""
312+
注册单个技能(便捷方法)
313+
314+
Args:
315+
skill_data: 技能数据字典
316+
317+
Returns:
318+
SkillCard: 注册的技能对象
319+
"""
320+
skill_id = skill_data.get('skill_id')
321+
if not skill_id:
322+
raise ValueError("skill_id is required")
323+
324+
# 创建或更新技能
325+
if skill_id in self.skill_registry:
326+
skill = self.skill_registry[skill_id]
327+
skill.version += 1
328+
else:
329+
skill = SkillCard.from_dict(skill_data)
330+
self.skill_registry[skill_id] = skill
331+
332+
return skill
333+
280334
async def capture_environment_fingerprint(self, project_id: str) -> Dict:
281335
"""
282336
V-06: 捕获环境指纹

pytest.ini

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,3 @@ addopts =
1111
-v
1212
--tb=short
1313
--strict-markers
14-
--ignore-glob="*test_openspace_engine.py"
15-
-k "not (test_monitor_execution or test_quality_calculation or test_trigger_alert or test_log_error or test_get_performance_report or test_get_status or test_init_orchestrator or test_init_with_custom_config or test_execute_task_structure or test_get_system_status or test_planning_layer or test_result_has)"

tests/conftest.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"""
2+
Shared test fixtures and utilities
3+
"""
4+
import pytest
5+
from unittest.mock import AsyncMock, MagicMock
6+
7+
8+
class MockLLMProvider:
9+
"""Mock LLM provider for testing without API keys"""
10+
11+
async def generate(self, prompt: str) -> str:
12+
"""Mock LLM generation"""
13+
return '{"action": "test", "output": "mock response from LLM"}'
14+
15+
async def analyze(self, text: str) -> dict:
16+
"""Mock analysis"""
17+
return {
18+
"confidence": 0.9,
19+
"category": "test",
20+
"intent": "code_generation"
21+
}
22+
23+
24+
@pytest.fixture
25+
def mock_llm_config():
26+
"""Configuration with mock LLM"""
27+
return {
28+
'openspace': {
29+
'registry_path': './test_data/skills',
30+
},
31+
'openhands': {
32+
'model': 'mock-gpt-4',
33+
'api_key': 'test-key',
34+
'llm_provider': MockLLMProvider(),
35+
},
36+
'monitor': {
37+
'quality_threshold': 0.8,
38+
'enable_alerts': False,
39+
},
40+
'governance': {
41+
'enabled': True,
42+
}
43+
}

tests/test_monitor.py

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ async def test_monitor_execution(self, monitor):
3939

4040
assert isinstance(quality_metrics, dict)
4141
assert "overall_score" in quality_metrics
42-
assert "base_score" in quality_metrics
4342
assert "confidence" in quality_metrics
43+
assert "execution_time" in quality_metrics
4444

4545
@pytest.mark.asyncio
4646
async def test_quality_calculation_success(self, monitor):
@@ -53,10 +53,10 @@ async def test_quality_calculation_success(self, monitor):
5353
}
5454
}
5555

56-
metrics = await monitor._calculate_quality_metrics(result)
56+
metrics = monitor._calculate_quality_metrics(result)
5757

5858
assert metrics["overall_score"] > 0.7 # Should be good quality
59-
assert metrics["base_score"] == 1.0 # Success gives base score 1.0
59+
assert metrics["success"] == True
6060

6161
@pytest.mark.asyncio
6262
async def test_quality_calculation_failure(self, monitor):
@@ -70,37 +70,38 @@ async def test_quality_calculation_failure(self, monitor):
7070
}
7171
}
7272

73-
metrics = await monitor._calculate_quality_metrics(result)
73+
metrics = monitor._calculate_quality_metrics(result)
7474

7575
assert metrics["overall_score"] < 0.5 # Failed should have low score
76-
assert metrics["base_score"] == 0.0 # Failure gives base score 0.0
76+
assert metrics["success"] == False
7777

7878
@pytest.mark.asyncio
7979
async def test_trigger_alert(self, monitor):
8080
"""Test alert triggering"""
81-
alert = await monitor._trigger_alert(
81+
await monitor._trigger_alert(
8282
alert_type="quality_low",
8383
message="Quality score below threshold",
8484
severity="warning"
8585
)
8686

87-
assert isinstance(alert, dict)
87+
assert len(monitor.alerts) > 0
88+
alert = monitor.alerts[-1]
8889
assert alert["type"] == "quality_low"
8990
assert alert["severity"] == "warning"
9091
assert "timestamp" in alert
9192

9293
@pytest.mark.asyncio
9394
async def test_log_error(self, monitor):
9495
"""Test error logging"""
96+
error = Exception("TestError")
9597
await monitor.log_error(
96-
task_id="test-task-001",
97-
error="TestError",
98-
context={"detail": "Test error context"}
98+
error=error,
99+
context={"task_id": "test-task-001", "detail": "Test error context"}
99100
)
100101

101102
assert len(monitor.error_log) > 0
102-
assert monitor.error_log[-1]["task_id"] == "test-task-001"
103-
assert monitor.error_log[-1]["error"] == "TestError"
103+
assert monitor.error_log[-1]["error_type"] == "Exception"
104+
assert monitor.error_log[-1]["error_message"] == "TestError"
104105

105106
@pytest.mark.asyncio
106107
async def test_get_performance_report(self, monitor):
@@ -119,9 +120,9 @@ async def test_get_performance_report(self, monitor):
119120
report = await monitor.get_performance_report()
120121

121122
assert isinstance(report, dict)
122-
assert "total_executions" in report
123-
assert "success_rate" in report
124-
assert "average_quality" in report
123+
assert "total_tasks" in report
124+
assert "avg_quality" in report
125+
assert "error_rate" in report
125126

126127
@pytest.mark.asyncio
127128
async def test_get_status(self, monitor):
@@ -130,7 +131,7 @@ async def test_get_status(self, monitor):
130131

131132
assert isinstance(status, dict)
132133
assert "status" in status
133-
assert "errors_logged" in status
134+
assert "errors_detected" in status
134135
assert "quality_threshold" in status
135136

136137

0 commit comments

Comments
 (0)