File size: 10,289 Bytes
d2426db
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
"""
Code Tester Module - Run tests and generate coverage reports.

This module handles:
- Running pytest tests
- Generating code coverage reports
- Running linting checks (pylint, flake8)
- Collecting test results
"""

import os
import subprocess
import logging
from pathlib import Path
from typing import Dict, Any, List

logger = logging.getLogger(__name__)


class CodeTester:
    """Run tests and generate coverage reports."""
    
    def __init__(self, config: Dict[str, Any], project_root: str):
        """
        Initialize the code tester.
        
        Args:
            config: Configuration dictionary for code tester
            project_root: Root directory of the project
        """
        self.config = config
        self.project_root = Path(project_root)
        self.results = {
            "pytest_results": {},
            "coverage_results": {},
            "pylint_results": {},
            "flake8_results": {},
            "total_tests": 0,
            "passed_tests": 0,
            "failed_tests": 0,
            "coverage_percentage": 0.0,
            "errors": []
        }
    
    def run(self) -> Dict[str, Any]:
        """
        Run all code tests.
        
        Returns:
            Dictionary containing results of code tests
        """
        logger.info("Starting code tests...")
        
        if not self.config.get("enabled", True):
            logger.info("Code tester is disabled in configuration")
            return self.results
        
        # Run pytest
        if self.config.get("run_pytest", True):
            self._run_pytest()
        
        # Run pylint
        if self.config.get("run_pylint", True):
            self._run_pylint()
        
        # Run flake8
        if self.config.get("run_flake8", True):
            self._run_flake8()
        
        logger.info(f"Code tests completed. {self.results['passed_tests']}/{self.results['total_tests']} tests passed")
        return self.results
    
    def _run_pytest(self):
        """Run pytest with coverage."""
        logger.info("Running pytest...")
        
        try:
            test_dirs = self.config.get("test_directories", ["backend"])
            pytest_args = self.config.get("pytest_args", ["-v"])
            generate_coverage = self.config.get("generate_coverage", True)
            
            # Build pytest command
            cmd = ["pytest"]
            cmd.extend(pytest_args)
            
            if generate_coverage:
                cmd.extend([
                    "--cov=.",
                    "--cov-report=html:quality_reports/coverage",
                    "--cov-report=term"
                ])
            
            # Add test directories
            for test_dir in test_dirs:
                test_path = self.project_root / test_dir
                if test_path.exists():
                    cmd.append(str(test_path))
            
            result = subprocess.run(
                cmd,
                capture_output=True,
                text=True,
                cwd=self.project_root
            )
            
            # Parse pytest output
            output_lines = result.stdout.split('\n')
            
            # Look for test results
            for line in output_lines:
                if " passed" in line or " failed" in line:
                    # Extract test counts
                    parts = line.split()
                    for i, part in enumerate(parts):
                        if part == "passed":
                            try:
                                self.results["passed_tests"] = int(parts[i-1])
                            except (ValueError, IndexError):
                                pass
                        elif part == "failed":
                            try:
                                self.results["failed_tests"] = int(parts[i-1])
                            except (ValueError, IndexError):
                                pass
                
                # Look for coverage percentage
                if "TOTAL" in line and "%" in line:
                    parts = line.split()
                    for part in parts:
                        if "%" in part:
                            try:
                                self.results["coverage_percentage"] = float(part.replace("%", ""))
                            except ValueError:
                                pass
            
            self.results["total_tests"] = self.results["passed_tests"] + self.results["failed_tests"]
            
            self.results["pytest_results"] = {
                "status": "PASSED" if result.returncode == 0 else "FAILED",
                "return_code": result.returncode,
                "output": result.stdout,
                "total_tests": self.results["total_tests"],
                "passed": self.results["passed_tests"],
                "failed": self.results["failed_tests"]
            }
            
            if result.returncode == 0:
                logger.info(f"βœ“ Pytest passed - {self.results['passed_tests']} tests")
            else:
                logger.warning(f"βœ— Pytest failed - {self.results['failed_tests']} failures")
            
            if generate_coverage:
                logger.info(f"Code coverage: {self.results['coverage_percentage']}%")
                
        except FileNotFoundError:
            error_msg = "pytest is not installed. Install with: pip install pytest pytest-cov"
            logger.error(error_msg)
            self.results["errors"].append(error_msg)
        except Exception as e:
            logger.error(f"Error running pytest: {str(e)}")
            self.results["errors"].append(f"pytest error: {str(e)}")
    
    def _run_pylint(self):
        """Run pylint for code quality checks."""
        logger.info("Running pylint...")
        
        try:
            # Find Python files
            python_files = []
            for test_dir in self.config.get("test_directories", ["backend"]):
                test_path = self.project_root / test_dir
                if test_path.exists():
                    python_files.extend(test_path.glob("**/*.py"))
            
            if not python_files:
                logger.warning("No Python files found for pylint")
                return
            
            cmd = [
                "pylint",
                *[str(f) for f in python_files[:20]]  # Limit to first 20 files
            ]
            
            result = subprocess.run(
                cmd,
                capture_output=True,
                text=True,
                cwd=self.project_root
            )
            
            # Parse pylint output for score
            output_lines = result.stdout.split('\n')
            score = 0.0
            
            for line in output_lines:
                if "Your code has been rated at" in line:
                    try:
                        score_str = line.split("rated at ")[1].split("/")[0]
                        score = float(score_str)
                    except (IndexError, ValueError):
                        pass
            
            threshold = self.config.get("pylint_threshold", 7.0)
            
            self.results["pylint_results"] = {
                "status": "PASSED" if score >= threshold else "FAILED",
                "score": score,
                "threshold": threshold,
                "output": result.stdout[:1000]  # Limit output
            }
            
            if score >= threshold:
                logger.info(f"βœ“ Pylint passed - Score: {score}/10")
            else:
                logger.warning(f"βœ— Pylint failed - Score: {score}/10 (threshold: {threshold})")
                
        except FileNotFoundError:
            error_msg = "pylint is not installed. Install with: pip install pylint"
            logger.error(error_msg)
            self.results["errors"].append(error_msg)
        except Exception as e:
            logger.error(f"Error running pylint: {str(e)}")
            self.results["errors"].append(f"pylint error: {str(e)}")
    
    def _run_flake8(self):
        """Run flake8 for style checks."""
        logger.info("Running flake8...")
        
        try:
            test_dirs = self.config.get("test_directories", ["backend"])
            
            cmd = ["flake8"]
            
            for test_dir in test_dirs:
                test_path = self.project_root / test_dir
                if test_path.exists():
                    cmd.append(str(test_path))
            
            result = subprocess.run(
                cmd,
                capture_output=True,
                text=True,
                cwd=self.project_root
            )
            
            # Count issues
            issues = result.stdout.split('\n')
            issue_count = len([line for line in issues if line.strip()])
            
            self.results["flake8_results"] = {
                "status": "PASSED" if result.returncode == 0 else "FAILED",
                "issue_count": issue_count,
                "output": result.stdout[:1000]  # Limit output
            }
            
            if result.returncode == 0:
                logger.info("βœ“ Flake8 passed - No style issues")
            else:
                logger.warning(f"βœ— Flake8 found {issue_count} style issues")
                
        except FileNotFoundError:
            error_msg = "flake8 is not installed. Install with: pip install flake8"
            logger.error(error_msg)
            self.results["errors"].append(error_msg)
        except Exception as e:
            logger.error(f"Error running flake8: {str(e)}")
            self.results["errors"].append(f"flake8 error: {str(e)}")
    
    def get_summary(self) -> str:
        """Get a summary of code test results."""
        summary = f"""
Code Test Summary:
- Total tests: {self.results['total_tests']}
- Passed: {self.results['passed_tests']}
- Failed: {self.results['failed_tests']}
- Code coverage: {self.results['coverage_percentage']}%

Quality Checks:
- Pylint score: {self.results.get('pylint_results', {}).get('score', 'N/A')}/10
- Flake8 issues: {self.results.get('flake8_results', {}).get('issue_count', 'N/A')}
"""
        return summary