Coverage for lintro / tools / implementations / pytest / pytest_error_handler.py: 39%
23 statements
« prev ^ index » next coverage.py v7.13.0, created at 2026-04-03 18:53 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2026-04-03 18:53 +0000
1"""Pytest error handling.
3This module contains the PytestErrorHandler class that handles various error
4scenarios consistently and provides standardized error messages.
5"""
7import subprocess # nosec B404 - used safely with shell disabled
8from dataclasses import dataclass, field
10from loguru import logger
12from lintro.models.core.tool_result import ToolResult
15@dataclass
16class PytestErrorHandler:
17 """Handles pytest error scenarios consistently.
19 This class encapsulates error handling logic for various pytest execution
20 failures, providing standardized error messages and ToolResult objects.
22 Attributes:
23 tool_name: Name of the tool (e.g., "pytest").
24 """
26 tool_name: str = field(default="pytest")
28 def handle_timeout_error(
29 self,
30 timeout_val: int,
31 cmd: list[str],
32 initial_count: int = 0,
33 ) -> ToolResult:
34 """Handle timeout errors consistently.
36 Args:
37 timeout_val: The timeout value that was exceeded.
38 cmd: Command that timed out.
39 initial_count: Number of issues discovered before timeout.
41 Returns:
42 ToolResult: Standardized timeout error result.
43 """
44 # Format the command for display
45 cmd_str = " ".join(cmd[:4]) if len(cmd) >= 4 else " ".join(cmd)
46 if len(cmd) > 4:
47 cmd_str += f" ... ({len(cmd) - 4} more args)"
49 error_msg = (
50 f"❌ pytest execution timed out after {timeout_val}s\n\n"
51 f"Command: {cmd_str}\n\n"
52 "Possible causes:\n"
53 " • Tests are taking too long to run\n"
54 " • Some tests are hanging or blocked (e.g., waiting for I/O)\n"
55 " • Test discovery is slow or stuck\n"
56 " • Resource exhaustion (memory, file descriptors)\n\n"
57 "Solutions:\n"
58 " 1. Increase timeout: lintro test --tool-options timeout=600\n"
59 " 2. Run fewer tests: lintro test tests/unit/ (vs full test suite)\n"
60 " 3. Run in parallel: lintro test --tool-options workers=auto\n"
61 " 4. Skip slow tests: lintro test -m 'not slow'\n"
62 " 5. Debug directly: pytest -v --tb=short <test_file>\n"
63 )
64 logger.error(error_msg)
65 return ToolResult(
66 name=self.tool_name,
67 success=False,
68 issues=[],
69 output=error_msg,
70 issues_count=max(initial_count, 1), # Count timeout as execution failure
71 )
73 def handle_execution_error(
74 self,
75 error: Exception,
76 cmd: list[str],
77 ) -> ToolResult:
78 """Handle execution errors consistently.
80 Args:
81 error: The exception that occurred.
82 cmd: Command that failed.
84 Returns:
85 ToolResult: Standardized error result.
86 """
87 if isinstance(error, FileNotFoundError):
88 error_msg = (
89 f"pytest executable not found: {error}\n\n"
90 "Please ensure pytest is installed:\n"
91 " - Install via pip: pip install pytest\n"
92 " - Install via uv: uv add pytest\n"
93 " - Or install as dev dependency: uv add --dev pytest\n\n"
94 "After installation, verify pytest is available:\n"
95 " pytest --version"
96 )
97 elif isinstance(error, subprocess.CalledProcessError):
98 error_msg = (
99 f"pytest execution failed with return code {error.returncode}\n\n"
100 "Common causes:\n"
101 " - Syntax errors in test files\n"
102 " - Missing dependencies or imports\n"
103 " - Configuration issues in pytest.ini or pyproject.toml\n"
104 " - Permission errors accessing test files\n\n"
105 "Try running pytest directly to see detailed error:\n"
106 f" {' '.join(cmd[:3])} ..."
107 )
108 else:
109 # Generic error handling with helpful context
110 error_type = type(error).__name__
111 error_msg = (
112 f"Unexpected error running pytest: {error_type}: {error}\n\n"
113 "Please report this issue if it persists. "
114 "For troubleshooting:\n"
115 " - Verify pytest is installed: pytest --version\n"
116 " - Check test files for syntax errors\n"
117 " - Review pytest configuration files\n"
118 " - Run pytest directly to see full output"
119 )
121 logger.error(error_msg)
122 return ToolResult(
123 name=self.tool_name,
124 success=False,
125 issues=[],
126 output=error_msg,
127 issues_count=1 if isinstance(error, subprocess.TimeoutExpired) else 0,
128 )