empathy-framework 4.6.6__py3-none-any.whl → 4.7.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- empathy_framework-4.7.1.dist-info/METADATA +690 -0
- empathy_framework-4.7.1.dist-info/RECORD +379 -0
- {empathy_framework-4.6.6.dist-info → empathy_framework-4.7.1.dist-info}/top_level.txt +1 -2
- empathy_healthcare_plugin/monitors/monitoring/__init__.py +9 -9
- empathy_llm_toolkit/agent_factory/__init__.py +6 -6
- empathy_llm_toolkit/agent_factory/adapters/wizard_adapter.py +7 -10
- empathy_llm_toolkit/agents_md/__init__.py +22 -0
- empathy_llm_toolkit/agents_md/loader.py +218 -0
- empathy_llm_toolkit/agents_md/parser.py +271 -0
- empathy_llm_toolkit/agents_md/registry.py +307 -0
- empathy_llm_toolkit/commands/__init__.py +51 -0
- empathy_llm_toolkit/commands/context.py +375 -0
- empathy_llm_toolkit/commands/loader.py +301 -0
- empathy_llm_toolkit/commands/models.py +231 -0
- empathy_llm_toolkit/commands/parser.py +371 -0
- empathy_llm_toolkit/commands/registry.py +429 -0
- empathy_llm_toolkit/config/__init__.py +8 -8
- empathy_llm_toolkit/config/unified.py +3 -7
- empathy_llm_toolkit/context/__init__.py +22 -0
- empathy_llm_toolkit/context/compaction.py +455 -0
- empathy_llm_toolkit/context/manager.py +434 -0
- empathy_llm_toolkit/hooks/__init__.py +24 -0
- empathy_llm_toolkit/hooks/config.py +306 -0
- empathy_llm_toolkit/hooks/executor.py +289 -0
- empathy_llm_toolkit/hooks/registry.py +302 -0
- empathy_llm_toolkit/hooks/scripts/__init__.py +39 -0
- empathy_llm_toolkit/hooks/scripts/evaluate_session.py +201 -0
- empathy_llm_toolkit/hooks/scripts/first_time_init.py +285 -0
- empathy_llm_toolkit/hooks/scripts/pre_compact.py +207 -0
- empathy_llm_toolkit/hooks/scripts/session_end.py +183 -0
- empathy_llm_toolkit/hooks/scripts/session_start.py +163 -0
- empathy_llm_toolkit/hooks/scripts/suggest_compact.py +225 -0
- empathy_llm_toolkit/learning/__init__.py +30 -0
- empathy_llm_toolkit/learning/evaluator.py +438 -0
- empathy_llm_toolkit/learning/extractor.py +514 -0
- empathy_llm_toolkit/learning/storage.py +560 -0
- empathy_llm_toolkit/providers.py +4 -11
- empathy_llm_toolkit/security/__init__.py +17 -17
- empathy_llm_toolkit/utils/tokens.py +2 -5
- empathy_os/__init__.py +202 -70
- empathy_os/cache_monitor.py +5 -3
- empathy_os/cli/__init__.py +11 -55
- empathy_os/cli/__main__.py +29 -15
- empathy_os/cli/commands/inspection.py +21 -12
- empathy_os/cli/commands/memory.py +4 -12
- empathy_os/cli/commands/profiling.py +198 -0
- empathy_os/cli/commands/utilities.py +27 -7
- empathy_os/cli.py +28 -57
- empathy_os/cli_unified.py +525 -1164
- empathy_os/cost_tracker.py +9 -3
- empathy_os/dashboard/server.py +200 -2
- empathy_os/hot_reload/__init__.py +7 -7
- empathy_os/hot_reload/config.py +6 -7
- empathy_os/hot_reload/integration.py +35 -35
- empathy_os/hot_reload/reloader.py +57 -57
- empathy_os/hot_reload/watcher.py +28 -28
- empathy_os/hot_reload/websocket.py +2 -2
- empathy_os/memory/__init__.py +11 -4
- empathy_os/memory/claude_memory.py +1 -1
- empathy_os/memory/cross_session.py +8 -12
- empathy_os/memory/edges.py +6 -6
- empathy_os/memory/file_session.py +770 -0
- empathy_os/memory/graph.py +30 -30
- empathy_os/memory/nodes.py +6 -6
- empathy_os/memory/short_term.py +15 -9
- empathy_os/memory/unified.py +606 -140
- empathy_os/meta_workflows/agent_creator.py +3 -9
- empathy_os/meta_workflows/cli_meta_workflows.py +113 -53
- empathy_os/meta_workflows/form_engine.py +6 -18
- empathy_os/meta_workflows/intent_detector.py +64 -24
- empathy_os/meta_workflows/models.py +3 -1
- empathy_os/meta_workflows/pattern_learner.py +13 -31
- empathy_os/meta_workflows/plan_generator.py +55 -47
- empathy_os/meta_workflows/session_context.py +2 -3
- empathy_os/meta_workflows/workflow.py +20 -51
- empathy_os/models/cli.py +2 -2
- empathy_os/models/tasks.py +1 -2
- empathy_os/models/telemetry.py +4 -1
- empathy_os/models/token_estimator.py +3 -1
- empathy_os/monitoring/alerts.py +938 -9
- empathy_os/monitoring/alerts_cli.py +346 -183
- empathy_os/orchestration/execution_strategies.py +12 -29
- empathy_os/orchestration/pattern_learner.py +20 -26
- empathy_os/orchestration/real_tools.py +6 -15
- empathy_os/platform_utils.py +2 -1
- empathy_os/plugins/__init__.py +2 -2
- empathy_os/plugins/base.py +64 -64
- empathy_os/plugins/registry.py +32 -32
- empathy_os/project_index/index.py +49 -15
- empathy_os/project_index/models.py +1 -2
- empathy_os/project_index/reports.py +1 -1
- empathy_os/project_index/scanner.py +1 -0
- empathy_os/redis_memory.py +10 -7
- empathy_os/resilience/__init__.py +1 -1
- empathy_os/resilience/health.py +10 -10
- empathy_os/routing/__init__.py +7 -7
- empathy_os/routing/chain_executor.py +37 -37
- empathy_os/routing/classifier.py +36 -36
- empathy_os/routing/smart_router.py +40 -40
- empathy_os/routing/{wizard_registry.py → workflow_registry.py} +47 -47
- empathy_os/scaffolding/__init__.py +8 -8
- empathy_os/scaffolding/__main__.py +1 -1
- empathy_os/scaffolding/cli.py +28 -28
- empathy_os/socratic/__init__.py +3 -19
- empathy_os/socratic/ab_testing.py +25 -36
- empathy_os/socratic/blueprint.py +38 -38
- empathy_os/socratic/cli.py +34 -20
- empathy_os/socratic/collaboration.py +30 -28
- empathy_os/socratic/domain_templates.py +9 -1
- empathy_os/socratic/embeddings.py +17 -13
- empathy_os/socratic/engine.py +135 -70
- empathy_os/socratic/explainer.py +70 -60
- empathy_os/socratic/feedback.py +24 -19
- empathy_os/socratic/forms.py +15 -10
- empathy_os/socratic/generator.py +51 -35
- empathy_os/socratic/llm_analyzer.py +25 -23
- empathy_os/socratic/mcp_server.py +99 -159
- empathy_os/socratic/session.py +19 -13
- empathy_os/socratic/storage.py +98 -67
- empathy_os/socratic/success.py +38 -27
- empathy_os/socratic/visual_editor.py +51 -39
- empathy_os/socratic/web_ui.py +99 -66
- empathy_os/telemetry/cli.py +3 -1
- empathy_os/telemetry/usage_tracker.py +1 -3
- empathy_os/test_generator/__init__.py +3 -3
- empathy_os/test_generator/cli.py +28 -28
- empathy_os/test_generator/generator.py +64 -66
- empathy_os/test_generator/risk_analyzer.py +11 -11
- empathy_os/vscode_bridge 2.py +173 -0
- empathy_os/vscode_bridge.py +173 -0
- empathy_os/workflows/__init__.py +212 -120
- empathy_os/workflows/batch_processing.py +8 -24
- empathy_os/workflows/bug_predict.py +1 -1
- empathy_os/workflows/code_review.py +20 -5
- empathy_os/workflows/code_review_pipeline.py +13 -8
- empathy_os/workflows/keyboard_shortcuts/workflow.py +6 -2
- empathy_os/workflows/manage_documentation.py +1 -0
- empathy_os/workflows/orchestrated_health_check.py +6 -11
- empathy_os/workflows/orchestrated_release_prep.py +3 -3
- empathy_os/workflows/pr_review.py +18 -10
- empathy_os/workflows/progressive/README 2.md +454 -0
- empathy_os/workflows/progressive/__init__ 2.py +92 -0
- empathy_os/workflows/progressive/__init__.py +2 -12
- empathy_os/workflows/progressive/cli 2.py +242 -0
- empathy_os/workflows/progressive/cli.py +14 -37
- empathy_os/workflows/progressive/core 2.py +488 -0
- empathy_os/workflows/progressive/core.py +12 -12
- empathy_os/workflows/progressive/orchestrator 2.py +701 -0
- empathy_os/workflows/progressive/orchestrator.py +166 -144
- empathy_os/workflows/progressive/reports 2.py +528 -0
- empathy_os/workflows/progressive/reports.py +22 -31
- empathy_os/workflows/progressive/telemetry 2.py +280 -0
- empathy_os/workflows/progressive/telemetry.py +8 -14
- empathy_os/workflows/progressive/test_gen 2.py +514 -0
- empathy_os/workflows/progressive/test_gen.py +29 -48
- empathy_os/workflows/progressive/workflow 2.py +628 -0
- empathy_os/workflows/progressive/workflow.py +31 -70
- empathy_os/workflows/release_prep.py +21 -6
- empathy_os/workflows/release_prep_crew.py +1 -0
- empathy_os/workflows/secure_release.py +13 -6
- empathy_os/workflows/security_audit.py +8 -3
- empathy_os/workflows/test_coverage_boost_crew.py +3 -2
- empathy_os/workflows/test_maintenance_crew.py +1 -0
- empathy_os/workflows/test_runner.py +16 -12
- empathy_software_plugin/SOFTWARE_PLUGIN_README.md +25 -703
- empathy_software_plugin/cli.py +0 -122
- patterns/README.md +119 -0
- patterns/__init__.py +95 -0
- patterns/behavior.py +298 -0
- patterns/code_review_memory.json +441 -0
- patterns/core.py +97 -0
- patterns/debugging.json +3763 -0
- patterns/empathy.py +268 -0
- patterns/health_check_memory.json +505 -0
- patterns/input.py +161 -0
- patterns/memory_graph.json +8 -0
- patterns/refactoring_memory.json +1113 -0
- patterns/registry.py +663 -0
- patterns/security_memory.json +8 -0
- patterns/structural.py +415 -0
- patterns/validation.py +194 -0
- coach_wizards/__init__.py +0 -45
- coach_wizards/accessibility_wizard.py +0 -91
- coach_wizards/api_wizard.py +0 -91
- coach_wizards/base_wizard.py +0 -209
- coach_wizards/cicd_wizard.py +0 -91
- coach_wizards/code_reviewer_README.md +0 -60
- coach_wizards/code_reviewer_wizard.py +0 -180
- coach_wizards/compliance_wizard.py +0 -91
- coach_wizards/database_wizard.py +0 -91
- coach_wizards/debugging_wizard.py +0 -91
- coach_wizards/documentation_wizard.py +0 -91
- coach_wizards/generate_wizards.py +0 -347
- coach_wizards/localization_wizard.py +0 -173
- coach_wizards/migration_wizard.py +0 -91
- coach_wizards/monitoring_wizard.py +0 -91
- coach_wizards/observability_wizard.py +0 -91
- coach_wizards/performance_wizard.py +0 -91
- coach_wizards/prompt_engineering_wizard.py +0 -661
- coach_wizards/refactoring_wizard.py +0 -91
- coach_wizards/scaling_wizard.py +0 -90
- coach_wizards/security_wizard.py +0 -92
- coach_wizards/testing_wizard.py +0 -91
- empathy_framework-4.6.6.dist-info/METADATA +0 -1597
- empathy_framework-4.6.6.dist-info/RECORD +0 -410
- empathy_llm_toolkit/wizards/__init__.py +0 -43
- empathy_llm_toolkit/wizards/base_wizard.py +0 -364
- empathy_llm_toolkit/wizards/customer_support_wizard.py +0 -190
- empathy_llm_toolkit/wizards/healthcare_wizard.py +0 -378
- empathy_llm_toolkit/wizards/patient_assessment_README.md +0 -64
- empathy_llm_toolkit/wizards/patient_assessment_wizard.py +0 -193
- empathy_llm_toolkit/wizards/technology_wizard.py +0 -209
- empathy_os/wizard_factory_cli.py +0 -170
- empathy_software_plugin/wizards/__init__.py +0 -42
- empathy_software_plugin/wizards/advanced_debugging_wizard.py +0 -395
- empathy_software_plugin/wizards/agent_orchestration_wizard.py +0 -511
- empathy_software_plugin/wizards/ai_collaboration_wizard.py +0 -503
- empathy_software_plugin/wizards/ai_context_wizard.py +0 -441
- empathy_software_plugin/wizards/ai_documentation_wizard.py +0 -503
- empathy_software_plugin/wizards/base_wizard.py +0 -288
- empathy_software_plugin/wizards/book_chapter_wizard.py +0 -519
- empathy_software_plugin/wizards/code_review_wizard.py +0 -604
- empathy_software_plugin/wizards/debugging/__init__.py +0 -50
- empathy_software_plugin/wizards/debugging/bug_risk_analyzer.py +0 -414
- empathy_software_plugin/wizards/debugging/config_loaders.py +0 -446
- empathy_software_plugin/wizards/debugging/fix_applier.py +0 -469
- empathy_software_plugin/wizards/debugging/language_patterns.py +0 -385
- empathy_software_plugin/wizards/debugging/linter_parsers.py +0 -470
- empathy_software_plugin/wizards/debugging/verification.py +0 -369
- empathy_software_plugin/wizards/enhanced_testing_wizard.py +0 -537
- empathy_software_plugin/wizards/memory_enhanced_debugging_wizard.py +0 -816
- empathy_software_plugin/wizards/multi_model_wizard.py +0 -501
- empathy_software_plugin/wizards/pattern_extraction_wizard.py +0 -422
- empathy_software_plugin/wizards/pattern_retriever_wizard.py +0 -400
- empathy_software_plugin/wizards/performance/__init__.py +0 -9
- empathy_software_plugin/wizards/performance/bottleneck_detector.py +0 -221
- empathy_software_plugin/wizards/performance/profiler_parsers.py +0 -278
- empathy_software_plugin/wizards/performance/trajectory_analyzer.py +0 -429
- empathy_software_plugin/wizards/performance_profiling_wizard.py +0 -305
- empathy_software_plugin/wizards/prompt_engineering_wizard.py +0 -425
- empathy_software_plugin/wizards/rag_pattern_wizard.py +0 -461
- empathy_software_plugin/wizards/security/__init__.py +0 -32
- empathy_software_plugin/wizards/security/exploit_analyzer.py +0 -290
- empathy_software_plugin/wizards/security/owasp_patterns.py +0 -241
- empathy_software_plugin/wizards/security/vulnerability_scanner.py +0 -604
- empathy_software_plugin/wizards/security_analysis_wizard.py +0 -322
- empathy_software_plugin/wizards/security_learning_wizard.py +0 -740
- empathy_software_plugin/wizards/tech_debt_wizard.py +0 -726
- empathy_software_plugin/wizards/testing/__init__.py +0 -27
- empathy_software_plugin/wizards/testing/coverage_analyzer.py +0 -459
- empathy_software_plugin/wizards/testing/quality_analyzer.py +0 -525
- empathy_software_plugin/wizards/testing/test_suggester.py +0 -533
- empathy_software_plugin/wizards/testing_wizard.py +0 -274
- wizards/__init__.py +0 -82
- wizards/admission_assessment_wizard.py +0 -644
- wizards/care_plan.py +0 -321
- wizards/clinical_assessment.py +0 -769
- wizards/discharge_planning.py +0 -77
- wizards/discharge_summary_wizard.py +0 -468
- wizards/dosage_calculation.py +0 -497
- wizards/incident_report_wizard.py +0 -454
- wizards/medication_reconciliation.py +0 -85
- wizards/nursing_assessment.py +0 -171
- wizards/patient_education.py +0 -654
- wizards/quality_improvement.py +0 -705
- wizards/sbar_report.py +0 -324
- wizards/sbar_wizard.py +0 -608
- wizards/shift_handoff_wizard.py +0 -535
- wizards/soap_note_wizard.py +0 -679
- wizards/treatment_plan.py +0 -15
- {empathy_framework-4.6.6.dist-info → empathy_framework-4.7.1.dist-info}/WHEEL +0 -0
- {empathy_framework-4.6.6.dist-info → empathy_framework-4.7.1.dist-info}/entry_points.txt +0 -0
- {empathy_framework-4.6.6.dist-info → empathy_framework-4.7.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,525 +0,0 @@
|
|
|
1
|
-
"""Test Quality Analyzer for Enhanced Testing Wizard
|
|
2
|
-
|
|
3
|
-
Analyzes test quality including flakiness detection, assertion quality,
|
|
4
|
-
test isolation, and execution performance.
|
|
5
|
-
|
|
6
|
-
Copyright 2025 Smart-AI-Memory
|
|
7
|
-
Licensed under Fair Source License 0.9
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
import re
|
|
11
|
-
from dataclasses import dataclass, field
|
|
12
|
-
from enum import Enum
|
|
13
|
-
from pathlib import Path
|
|
14
|
-
from typing import Any
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class TestQualityIssue(Enum):
|
|
18
|
-
"""Types of test quality issues"""
|
|
19
|
-
|
|
20
|
-
FLAKY = "flaky" # Test has inconsistent results
|
|
21
|
-
NO_ASSERTIONS = "no_assertions" # Test doesn't verify anything
|
|
22
|
-
WEAK_ASSERTIONS = "weak_assertions" # Only uses assertTrue/assertFalse
|
|
23
|
-
SLOW = "slow" # Takes >1 second
|
|
24
|
-
NOT_ISOLATED = "not_isolated" # Depends on external state
|
|
25
|
-
HARDCODED_VALUES = "hardcoded" # Uses magic numbers/strings
|
|
26
|
-
SLEEP_USAGE = "sleep_usage" # Uses time.sleep()
|
|
27
|
-
RANDOM_USAGE = "random_usage" # Uses random without seed
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
@dataclass
|
|
31
|
-
class TestFunction:
|
|
32
|
-
"""Represents a single test function"""
|
|
33
|
-
|
|
34
|
-
name: str
|
|
35
|
-
file_path: str
|
|
36
|
-
line_number: int
|
|
37
|
-
assertions_count: int
|
|
38
|
-
execution_time: float | None = None
|
|
39
|
-
is_async: bool = False
|
|
40
|
-
uses_fixtures: list[str] = field(default_factory=list)
|
|
41
|
-
issues: list[TestQualityIssue] = field(default_factory=list)
|
|
42
|
-
|
|
43
|
-
@property
|
|
44
|
-
def quality_score(self) -> float:
|
|
45
|
-
"""Calculate quality score (0-100)
|
|
46
|
-
|
|
47
|
-
Factors:
|
|
48
|
-
- Has assertions: +30
|
|
49
|
-
- Good assertion count (2-10): +20
|
|
50
|
-
- Fast (<1s): +20
|
|
51
|
-
- No issues: +30
|
|
52
|
-
"""
|
|
53
|
-
score = 0.0
|
|
54
|
-
|
|
55
|
-
# Assertions
|
|
56
|
-
if self.assertions_count > 0:
|
|
57
|
-
score += 30
|
|
58
|
-
if 2 <= self.assertions_count <= 10:
|
|
59
|
-
score += 20
|
|
60
|
-
elif self.assertions_count == 1:
|
|
61
|
-
score += 10
|
|
62
|
-
|
|
63
|
-
# Performance
|
|
64
|
-
if self.execution_time is not None:
|
|
65
|
-
if self.execution_time < 0.1:
|
|
66
|
-
score += 20 # Very fast
|
|
67
|
-
elif self.execution_time < 1.0:
|
|
68
|
-
score += 15 # Fast enough
|
|
69
|
-
elif self.execution_time < 5.0:
|
|
70
|
-
score += 5 # Acceptable
|
|
71
|
-
|
|
72
|
-
# Issues penalty
|
|
73
|
-
issue_penalty = len(self.issues) * 10
|
|
74
|
-
score = max(0, score + 30 - issue_penalty)
|
|
75
|
-
|
|
76
|
-
return min(100.0, score)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
@dataclass
|
|
80
|
-
class TestQualityReport:
|
|
81
|
-
"""Complete test quality analysis report"""
|
|
82
|
-
|
|
83
|
-
total_tests: int
|
|
84
|
-
high_quality_tests: int # Score ≥80
|
|
85
|
-
medium_quality_tests: int # Score 50-79
|
|
86
|
-
low_quality_tests: int # Score <50
|
|
87
|
-
flaky_tests: list[str]
|
|
88
|
-
slow_tests: list[str]
|
|
89
|
-
tests_without_assertions: list[str]
|
|
90
|
-
isolated_tests: int
|
|
91
|
-
average_quality_score: float
|
|
92
|
-
issues_by_type: dict[TestQualityIssue, int]
|
|
93
|
-
test_functions: dict[str, TestFunction]
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
class TestQualityAnalyzer:
|
|
97
|
-
"""Analyzes test code quality to identify improvements.
|
|
98
|
-
|
|
99
|
-
Detects:
|
|
100
|
-
- Flaky tests (timing-dependent, random, external state)
|
|
101
|
-
- Missing or weak assertions
|
|
102
|
-
- Slow tests
|
|
103
|
-
- Poor isolation
|
|
104
|
-
- Anti-patterns
|
|
105
|
-
"""
|
|
106
|
-
|
|
107
|
-
def __init__(self):
|
|
108
|
-
# Thresholds
|
|
109
|
-
self.slow_threshold = 1.0 # seconds
|
|
110
|
-
self.min_assertions = 1
|
|
111
|
-
self.max_assertions = 20 # Too many might indicate test doing too much
|
|
112
|
-
|
|
113
|
-
# Patterns for detection
|
|
114
|
-
self.assertion_patterns = [
|
|
115
|
-
r"assert\s+",
|
|
116
|
-
r"assertEqual",
|
|
117
|
-
r"assertTrue",
|
|
118
|
-
r"assertFalse",
|
|
119
|
-
r"assertIn",
|
|
120
|
-
r"assertRaises",
|
|
121
|
-
r"assertIsNone",
|
|
122
|
-
r"assertIsNotNone",
|
|
123
|
-
r"expect\(",
|
|
124
|
-
]
|
|
125
|
-
|
|
126
|
-
self.flakiness_indicators = [
|
|
127
|
-
(r"time\.sleep\(", TestQualityIssue.SLEEP_USAGE),
|
|
128
|
-
(r"random\.", TestQualityIssue.RANDOM_USAGE),
|
|
129
|
-
(r"datetime\.now\(\)", TestQualityIssue.NOT_ISOLATED),
|
|
130
|
-
(r"uuid\.uuid4\(\)", TestQualityIssue.RANDOM_USAGE),
|
|
131
|
-
]
|
|
132
|
-
|
|
133
|
-
def analyze_test_file(self, file_path: Path) -> list[TestFunction]:
|
|
134
|
-
"""Analyze a test file for quality issues
|
|
135
|
-
|
|
136
|
-
Args:
|
|
137
|
-
file_path: Path to test file
|
|
138
|
-
|
|
139
|
-
Returns:
|
|
140
|
-
List of TestFunction objects with issues identified
|
|
141
|
-
|
|
142
|
-
"""
|
|
143
|
-
if not file_path.exists():
|
|
144
|
-
raise FileNotFoundError(f"Test file not found: {file_path}")
|
|
145
|
-
|
|
146
|
-
with open(file_path, encoding="utf-8") as f:
|
|
147
|
-
content = f.read()
|
|
148
|
-
|
|
149
|
-
return self._parse_test_functions(content, str(file_path))
|
|
150
|
-
|
|
151
|
-
def _parse_test_functions(self, content: str, file_path: str) -> list[TestFunction]:
|
|
152
|
-
"""Parse test functions from file content"""
|
|
153
|
-
test_functions = []
|
|
154
|
-
lines = content.split("\n")
|
|
155
|
-
|
|
156
|
-
# Find test function definitions
|
|
157
|
-
test_func_pattern = re.compile(r"^(\s*)(async\s+)?def\s+(test_\w+)\s*\(")
|
|
158
|
-
|
|
159
|
-
i = 0
|
|
160
|
-
while i < len(lines):
|
|
161
|
-
match = test_func_pattern.match(lines[i])
|
|
162
|
-
if match:
|
|
163
|
-
indent = len(match.group(1))
|
|
164
|
-
is_async = match.group(2) is not None
|
|
165
|
-
func_name = match.group(3)
|
|
166
|
-
line_num = i + 1
|
|
167
|
-
|
|
168
|
-
# Extract function body
|
|
169
|
-
func_body, end_line = self._extract_function_body(lines, i, indent)
|
|
170
|
-
|
|
171
|
-
# Analyze function
|
|
172
|
-
test_func = self._analyze_test_function(
|
|
173
|
-
func_name,
|
|
174
|
-
file_path,
|
|
175
|
-
line_num,
|
|
176
|
-
func_body,
|
|
177
|
-
is_async,
|
|
178
|
-
)
|
|
179
|
-
test_functions.append(test_func)
|
|
180
|
-
|
|
181
|
-
i = end_line
|
|
182
|
-
else:
|
|
183
|
-
i += 1
|
|
184
|
-
|
|
185
|
-
return test_functions
|
|
186
|
-
|
|
187
|
-
def _extract_function_body(
|
|
188
|
-
self,
|
|
189
|
-
lines: list[str],
|
|
190
|
-
start_line: int,
|
|
191
|
-
base_indent: int,
|
|
192
|
-
) -> tuple[str, int]:
|
|
193
|
-
"""Extract the body of a function"""
|
|
194
|
-
body_lines = [lines[start_line]]
|
|
195
|
-
i = start_line + 1
|
|
196
|
-
|
|
197
|
-
while i < len(lines):
|
|
198
|
-
line = lines[i]
|
|
199
|
-
|
|
200
|
-
# Skip empty lines
|
|
201
|
-
if not line.strip():
|
|
202
|
-
body_lines.append(line)
|
|
203
|
-
i += 1
|
|
204
|
-
continue
|
|
205
|
-
|
|
206
|
-
# Check indentation
|
|
207
|
-
current_indent = len(line) - len(line.lstrip())
|
|
208
|
-
|
|
209
|
-
# If less or equal indent and not empty, function ended
|
|
210
|
-
if current_indent <= base_indent and line.strip():
|
|
211
|
-
break
|
|
212
|
-
|
|
213
|
-
body_lines.append(line)
|
|
214
|
-
i += 1
|
|
215
|
-
|
|
216
|
-
return "\n".join(body_lines), i
|
|
217
|
-
|
|
218
|
-
def _analyze_test_function(
|
|
219
|
-
self,
|
|
220
|
-
func_name: str,
|
|
221
|
-
file_path: str,
|
|
222
|
-
line_num: int,
|
|
223
|
-
func_body: str,
|
|
224
|
-
is_async: bool,
|
|
225
|
-
) -> TestFunction:
|
|
226
|
-
"""Analyze a single test function"""
|
|
227
|
-
issues: list[TestQualityIssue] = []
|
|
228
|
-
|
|
229
|
-
# Count assertions
|
|
230
|
-
assertions_count = self._count_assertions(func_body)
|
|
231
|
-
|
|
232
|
-
# Check for no assertions
|
|
233
|
-
if assertions_count == 0:
|
|
234
|
-
issues.append(TestQualityIssue.NO_ASSERTIONS)
|
|
235
|
-
|
|
236
|
-
# Check for weak assertions (only assertTrue/False)
|
|
237
|
-
if self._has_only_weak_assertions(func_body):
|
|
238
|
-
issues.append(TestQualityIssue.WEAK_ASSERTIONS)
|
|
239
|
-
|
|
240
|
-
# Check for flakiness indicators
|
|
241
|
-
for pattern, issue_type in self.flakiness_indicators:
|
|
242
|
-
if re.search(pattern, func_body):
|
|
243
|
-
if issue_type not in issues:
|
|
244
|
-
issues.append(issue_type)
|
|
245
|
-
|
|
246
|
-
# Check for hardcoded values (magic numbers/strings)
|
|
247
|
-
if self._has_hardcoded_values(func_body):
|
|
248
|
-
issues.append(TestQualityIssue.HARDCODED_VALUES)
|
|
249
|
-
|
|
250
|
-
# Extract fixtures used
|
|
251
|
-
fixtures = self._extract_fixtures(func_body)
|
|
252
|
-
|
|
253
|
-
return TestFunction(
|
|
254
|
-
name=func_name,
|
|
255
|
-
file_path=file_path,
|
|
256
|
-
line_number=line_num,
|
|
257
|
-
assertions_count=assertions_count,
|
|
258
|
-
is_async=is_async,
|
|
259
|
-
uses_fixtures=fixtures,
|
|
260
|
-
issues=issues,
|
|
261
|
-
)
|
|
262
|
-
|
|
263
|
-
def _count_assertions(self, func_body: str) -> int:
|
|
264
|
-
"""Count number of assertions in function"""
|
|
265
|
-
count = 0
|
|
266
|
-
for pattern in self.assertion_patterns:
|
|
267
|
-
matches = re.findall(pattern, func_body)
|
|
268
|
-
count += len(matches)
|
|
269
|
-
return count
|
|
270
|
-
|
|
271
|
-
def _has_only_weak_assertions(self, func_body: str) -> bool:
|
|
272
|
-
"""Check if function only uses weak assertions (assertTrue/False)"""
|
|
273
|
-
weak_assertions = re.findall(r"assert(True|False)\(", func_body)
|
|
274
|
-
all_assertions = sum(
|
|
275
|
-
len(re.findall(pattern, func_body)) for pattern in self.assertion_patterns
|
|
276
|
-
)
|
|
277
|
-
|
|
278
|
-
# If all assertions are weak and there are some
|
|
279
|
-
return len(weak_assertions) == all_assertions and all_assertions > 0
|
|
280
|
-
|
|
281
|
-
def _has_hardcoded_values(self, func_body: str) -> bool:
|
|
282
|
-
"""Detect hardcoded magic values"""
|
|
283
|
-
# Look for literal numbers (except common ones like 0, 1, 2)
|
|
284
|
-
magic_numbers = re.findall(r"\b[3-9]\d*\b", func_body)
|
|
285
|
-
|
|
286
|
-
# Look for hardcoded strings that aren't test names or common words
|
|
287
|
-
magic_strings = re.findall(r'["\']([a-zA-Z0-9]{10,})["\']', func_body)
|
|
288
|
-
|
|
289
|
-
# Threshold: more than 3 magic values is suspicious
|
|
290
|
-
return len(magic_numbers) + len(magic_strings) > 3
|
|
291
|
-
|
|
292
|
-
def _extract_fixtures(self, func_body: str) -> list[str]:
|
|
293
|
-
"""Extract pytest fixtures used in function signature"""
|
|
294
|
-
# Match function signature parameters
|
|
295
|
-
sig_match = re.search(r"def\s+\w+\s*\(([^)]*)\)", func_body)
|
|
296
|
-
if not sig_match:
|
|
297
|
-
return []
|
|
298
|
-
|
|
299
|
-
params = sig_match.group(1).split(",")
|
|
300
|
-
fixtures = []
|
|
301
|
-
|
|
302
|
-
for param in params:
|
|
303
|
-
param = param.strip()
|
|
304
|
-
# Skip 'self' and empty params
|
|
305
|
-
if param and param != "self":
|
|
306
|
-
# Extract parameter name (before type hint if any)
|
|
307
|
-
fixture_name = param.split(":")[0].strip()
|
|
308
|
-
fixtures.append(fixture_name)
|
|
309
|
-
|
|
310
|
-
return fixtures
|
|
311
|
-
|
|
312
|
-
def analyze_test_execution(self, test_results: list[dict[str, Any]]) -> list[TestFunction]:
|
|
313
|
-
"""Analyze test execution results (from pytest JSON report)
|
|
314
|
-
|
|
315
|
-
Args:
|
|
316
|
-
test_results: List of test result dicts with fields:
|
|
317
|
-
- nodeid: test identifier
|
|
318
|
-
- duration: execution time in seconds
|
|
319
|
-
- outcome: passed/failed/skipped
|
|
320
|
-
- call: execution details
|
|
321
|
-
|
|
322
|
-
Returns:
|
|
323
|
-
List of TestFunction objects with execution data
|
|
324
|
-
|
|
325
|
-
"""
|
|
326
|
-
test_functions = []
|
|
327
|
-
|
|
328
|
-
for result in test_results:
|
|
329
|
-
# Parse nodeid (e.g., "tests/test_core.py::test_function_name")
|
|
330
|
-
nodeid = str(result.get("nodeid", ""))
|
|
331
|
-
parts = nodeid.split("::")
|
|
332
|
-
|
|
333
|
-
if len(parts) < 2:
|
|
334
|
-
continue
|
|
335
|
-
|
|
336
|
-
file_path = parts[0]
|
|
337
|
-
func_name = parts[1]
|
|
338
|
-
|
|
339
|
-
duration = result.get("duration", 0.0)
|
|
340
|
-
|
|
341
|
-
# Create basic TestFunction (would be enriched with code analysis)
|
|
342
|
-
test_func = TestFunction(
|
|
343
|
-
name=func_name,
|
|
344
|
-
file_path=file_path,
|
|
345
|
-
line_number=0, # Would need code analysis to get this
|
|
346
|
-
assertions_count=0, # Would need code analysis
|
|
347
|
-
execution_time=duration,
|
|
348
|
-
)
|
|
349
|
-
|
|
350
|
-
# Check if slow
|
|
351
|
-
if duration > self.slow_threshold:
|
|
352
|
-
test_func.issues.append(TestQualityIssue.SLOW)
|
|
353
|
-
|
|
354
|
-
# Detect flakiness from multiple runs
|
|
355
|
-
# (This would require historical data)
|
|
356
|
-
|
|
357
|
-
test_functions.append(test_func)
|
|
358
|
-
|
|
359
|
-
return test_functions
|
|
360
|
-
|
|
361
|
-
def detect_flaky_tests(self, historical_results: list[list[dict[str, Any]]]) -> list[str]:
|
|
362
|
-
"""Detect flaky tests from historical test runs
|
|
363
|
-
|
|
364
|
-
A test is considered flaky if it has inconsistent results across runs
|
|
365
|
-
with the same code.
|
|
366
|
-
|
|
367
|
-
Args:
|
|
368
|
-
historical_results: List of test result lists from multiple runs
|
|
369
|
-
|
|
370
|
-
Returns:
|
|
371
|
-
List of test names that are flaky
|
|
372
|
-
|
|
373
|
-
"""
|
|
374
|
-
if len(historical_results) < 2:
|
|
375
|
-
return []
|
|
376
|
-
|
|
377
|
-
# Track outcomes for each test
|
|
378
|
-
test_outcomes: dict[str, list[str]] = {}
|
|
379
|
-
|
|
380
|
-
for run_results in historical_results:
|
|
381
|
-
for result in run_results:
|
|
382
|
-
nodeid = result.get("nodeid", "")
|
|
383
|
-
outcome = result.get("outcome", "unknown")
|
|
384
|
-
|
|
385
|
-
if nodeid not in test_outcomes:
|
|
386
|
-
test_outcomes[nodeid] = []
|
|
387
|
-
|
|
388
|
-
test_outcomes[nodeid].append(outcome)
|
|
389
|
-
|
|
390
|
-
# Find tests with inconsistent outcomes
|
|
391
|
-
flaky_tests = []
|
|
392
|
-
|
|
393
|
-
for nodeid, outcomes in test_outcomes.items():
|
|
394
|
-
# If outcomes vary, test is flaky
|
|
395
|
-
unique_outcomes = set(outcomes)
|
|
396
|
-
if len(unique_outcomes) > 1:
|
|
397
|
-
# Exclude if only failed once (might be legitimate)
|
|
398
|
-
fail_count = outcomes.count("failed")
|
|
399
|
-
if fail_count > 1 or (fail_count == 1 and len(outcomes) > 2):
|
|
400
|
-
flaky_tests.append(nodeid)
|
|
401
|
-
|
|
402
|
-
return flaky_tests
|
|
403
|
-
|
|
404
|
-
def generate_quality_report(self, test_functions: list[TestFunction]) -> TestQualityReport:
|
|
405
|
-
"""Generate comprehensive quality report
|
|
406
|
-
|
|
407
|
-
Args:
|
|
408
|
-
test_functions: List of analyzed test functions
|
|
409
|
-
|
|
410
|
-
Returns:
|
|
411
|
-
TestQualityReport with statistics and categorization
|
|
412
|
-
|
|
413
|
-
"""
|
|
414
|
-
high_quality = []
|
|
415
|
-
medium_quality = []
|
|
416
|
-
low_quality = []
|
|
417
|
-
flaky_tests = []
|
|
418
|
-
slow_tests = []
|
|
419
|
-
no_assertions = []
|
|
420
|
-
isolated_count = 0
|
|
421
|
-
|
|
422
|
-
issues_by_type: dict[TestQualityIssue, int] = dict.fromkeys(TestQualityIssue, 0)
|
|
423
|
-
|
|
424
|
-
test_functions_dict = {}
|
|
425
|
-
|
|
426
|
-
for test_func in test_functions:
|
|
427
|
-
quality_score = test_func.quality_score
|
|
428
|
-
test_id = f"{test_func.file_path}::{test_func.name}"
|
|
429
|
-
test_functions_dict[test_id] = test_func
|
|
430
|
-
|
|
431
|
-
# Categorize by quality
|
|
432
|
-
if quality_score >= 80:
|
|
433
|
-
high_quality.append(test_id)
|
|
434
|
-
elif quality_score >= 50:
|
|
435
|
-
medium_quality.append(test_id)
|
|
436
|
-
else:
|
|
437
|
-
low_quality.append(test_id)
|
|
438
|
-
|
|
439
|
-
# Track specific issues
|
|
440
|
-
if TestQualityIssue.NO_ASSERTIONS in test_func.issues:
|
|
441
|
-
no_assertions.append(test_id)
|
|
442
|
-
|
|
443
|
-
if TestQualityIssue.SLOW in test_func.issues:
|
|
444
|
-
slow_tests.append(test_id)
|
|
445
|
-
|
|
446
|
-
if any(
|
|
447
|
-
issue in test_func.issues
|
|
448
|
-
for issue in [
|
|
449
|
-
TestQualityIssue.FLAKY,
|
|
450
|
-
TestQualityIssue.SLEEP_USAGE,
|
|
451
|
-
TestQualityIssue.RANDOM_USAGE,
|
|
452
|
-
TestQualityIssue.NOT_ISOLATED,
|
|
453
|
-
]
|
|
454
|
-
):
|
|
455
|
-
flaky_tests.append(test_id)
|
|
456
|
-
|
|
457
|
-
# Check isolation (no external dependencies)
|
|
458
|
-
if TestQualityIssue.NOT_ISOLATED not in test_func.issues:
|
|
459
|
-
isolated_count += 1
|
|
460
|
-
|
|
461
|
-
# Count issues by type
|
|
462
|
-
for issue in test_func.issues:
|
|
463
|
-
issues_by_type[issue] += 1
|
|
464
|
-
|
|
465
|
-
# Calculate average quality score
|
|
466
|
-
if test_functions:
|
|
467
|
-
avg_score = sum(tf.quality_score for tf in test_functions) / len(test_functions)
|
|
468
|
-
else:
|
|
469
|
-
avg_score = 0.0
|
|
470
|
-
|
|
471
|
-
return TestQualityReport(
|
|
472
|
-
total_tests=len(test_functions),
|
|
473
|
-
high_quality_tests=len(high_quality),
|
|
474
|
-
medium_quality_tests=len(medium_quality),
|
|
475
|
-
low_quality_tests=len(low_quality),
|
|
476
|
-
flaky_tests=flaky_tests,
|
|
477
|
-
slow_tests=slow_tests,
|
|
478
|
-
tests_without_assertions=no_assertions,
|
|
479
|
-
isolated_tests=isolated_count,
|
|
480
|
-
average_quality_score=avg_score,
|
|
481
|
-
issues_by_type=issues_by_type,
|
|
482
|
-
test_functions=test_functions_dict,
|
|
483
|
-
)
|
|
484
|
-
|
|
485
|
-
def generate_summary(self, report: TestQualityReport) -> str:
|
|
486
|
-
"""Generate human-readable quality summary"""
|
|
487
|
-
summary = []
|
|
488
|
-
summary.append("=" * 60)
|
|
489
|
-
summary.append("TEST QUALITY ANALYSIS SUMMARY")
|
|
490
|
-
summary.append("=" * 60)
|
|
491
|
-
summary.append(f"Total Tests: {report.total_tests}")
|
|
492
|
-
summary.append(f"Average Quality Score: {report.average_quality_score:.1f}/100")
|
|
493
|
-
summary.append("")
|
|
494
|
-
summary.append("Quality Distribution:")
|
|
495
|
-
summary.append(f" ✅ High (≥80): {report.high_quality_tests} tests")
|
|
496
|
-
summary.append(f" ⚠️ Medium (50-79): {report.medium_quality_tests} tests")
|
|
497
|
-
summary.append(f" ❌ Low (<50): {report.low_quality_tests} tests")
|
|
498
|
-
summary.append("")
|
|
499
|
-
|
|
500
|
-
if report.tests_without_assertions:
|
|
501
|
-
summary.append(f"⚠️ Tests Without Assertions: {len(report.tests_without_assertions)}")
|
|
502
|
-
for test_id in report.tests_without_assertions[:3]:
|
|
503
|
-
summary.append(f" - {test_id}")
|
|
504
|
-
if len(report.tests_without_assertions) > 3:
|
|
505
|
-
summary.append(f" ... and {len(report.tests_without_assertions) - 3} more")
|
|
506
|
-
summary.append("")
|
|
507
|
-
|
|
508
|
-
if report.flaky_tests:
|
|
509
|
-
summary.append(f"🔴 Potentially Flaky Tests: {len(report.flaky_tests)}")
|
|
510
|
-
for test_id in report.flaky_tests[:3]:
|
|
511
|
-
summary.append(f" - {test_id}")
|
|
512
|
-
if len(report.flaky_tests) > 3:
|
|
513
|
-
summary.append(f" ... and {len(report.flaky_tests) - 3} more")
|
|
514
|
-
summary.append("")
|
|
515
|
-
|
|
516
|
-
if report.slow_tests:
|
|
517
|
-
summary.append(f"🐌 Slow Tests (>{self.slow_threshold}s): {len(report.slow_tests)}")
|
|
518
|
-
for test_id in report.slow_tests[:3]:
|
|
519
|
-
summary.append(f" - {test_id}")
|
|
520
|
-
if len(report.slow_tests) > 3:
|
|
521
|
-
summary.append(f" ... and {len(report.slow_tests) - 3} more")
|
|
522
|
-
|
|
523
|
-
summary.append("=" * 60)
|
|
524
|
-
|
|
525
|
-
return "\n".join(summary)
|