fishertools 0.2.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.
- fishertools/__init__.py +82 -0
- fishertools/config/__init__.py +24 -0
- fishertools/config/manager.py +247 -0
- fishertools/config/models.py +96 -0
- fishertools/config/parser.py +265 -0
- fishertools/decorators.py +93 -0
- fishertools/documentation/__init__.py +38 -0
- fishertools/documentation/api.py +242 -0
- fishertools/documentation/generator.py +502 -0
- fishertools/documentation/models.py +126 -0
- fishertools/documentation/visual.py +583 -0
- fishertools/errors/__init__.py +29 -0
- fishertools/errors/exceptions.py +191 -0
- fishertools/errors/explainer.py +303 -0
- fishertools/errors/formatters.py +386 -0
- fishertools/errors/models.py +228 -0
- fishertools/errors/patterns.py +119 -0
- fishertools/errors/recovery.py +467 -0
- fishertools/examples/__init__.py +22 -0
- fishertools/examples/models.py +118 -0
- fishertools/examples/repository.py +770 -0
- fishertools/helpers.py +116 -0
- fishertools/integration.py +451 -0
- fishertools/learn/__init__.py +18 -0
- fishertools/learn/examples.py +550 -0
- fishertools/learn/tips.py +281 -0
- fishertools/learning/__init__.py +32 -0
- fishertools/learning/core.py +349 -0
- fishertools/learning/models.py +112 -0
- fishertools/learning/progress.py +314 -0
- fishertools/learning/session.py +500 -0
- fishertools/learning/tutorial.py +626 -0
- fishertools/legacy/__init__.py +76 -0
- fishertools/legacy/deprecated.py +261 -0
- fishertools/legacy/deprecation.py +149 -0
- fishertools/safe/__init__.py +16 -0
- fishertools/safe/collections.py +242 -0
- fishertools/safe/files.py +240 -0
- fishertools/safe/strings.py +15 -0
- fishertools/utils.py +57 -0
- fishertools-0.2.1.dist-info/METADATA +256 -0
- fishertools-0.2.1.dist-info/RECORD +81 -0
- fishertools-0.2.1.dist-info/WHEEL +5 -0
- fishertools-0.2.1.dist-info/licenses/LICENSE +21 -0
- fishertools-0.2.1.dist-info/top_level.txt +2 -0
- tests/__init__.py +6 -0
- tests/conftest.py +25 -0
- tests/test_config/__init__.py +3 -0
- tests/test_config/test_basic_config.py +57 -0
- tests/test_config/test_config_error_handling.py +287 -0
- tests/test_config/test_config_properties.py +435 -0
- tests/test_documentation/__init__.py +3 -0
- tests/test_documentation/test_documentation_properties.py +253 -0
- tests/test_documentation/test_visual_documentation_properties.py +444 -0
- tests/test_errors/__init__.py +3 -0
- tests/test_errors/test_api.py +301 -0
- tests/test_errors/test_error_handling.py +354 -0
- tests/test_errors/test_explainer.py +173 -0
- tests/test_errors/test_formatters.py +338 -0
- tests/test_errors/test_models.py +248 -0
- tests/test_errors/test_patterns.py +270 -0
- tests/test_examples/__init__.py +3 -0
- tests/test_examples/test_example_repository_properties.py +204 -0
- tests/test_examples/test_specific_examples.py +303 -0
- tests/test_integration.py +298 -0
- tests/test_integration_enhancements.py +462 -0
- tests/test_learn/__init__.py +3 -0
- tests/test_learn/test_examples.py +221 -0
- tests/test_learn/test_tips.py +285 -0
- tests/test_learning/__init__.py +3 -0
- tests/test_learning/test_interactive_learning_properties.py +337 -0
- tests/test_learning/test_learning_system_properties.py +194 -0
- tests/test_learning/test_progress_tracking_properties.py +279 -0
- tests/test_legacy/__init__.py +3 -0
- tests/test_legacy/test_backward_compatibility.py +236 -0
- tests/test_legacy/test_deprecation_warnings.py +208 -0
- tests/test_safe/__init__.py +3 -0
- tests/test_safe/test_collections_properties.py +189 -0
- tests/test_safe/test_files.py +104 -0
- tests/test_structure.py +58 -0
- tests/test_structure_enhancements.py +115 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for ErrorExplainer class and exception handling.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from hypothesis import given, strategies as st
|
|
7
|
+
|
|
8
|
+
from fishertools.errors.explainer import ErrorExplainer
|
|
9
|
+
from fishertools.errors.models import ExplainerConfig
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# Common Python exception types for testing
|
|
13
|
+
COMMON_EXCEPTION_TYPES = [
|
|
14
|
+
TypeError, ValueError, AttributeError, IndexError, KeyError, ImportError, SyntaxError
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
# Custom exception types for fallback testing
|
|
18
|
+
class CustomError(Exception):
|
|
19
|
+
"""Custom exception for testing fallback behavior."""
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
class AnotherCustomError(Exception):
|
|
23
|
+
"""Another custom exception for testing."""
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@pytest.mark.property
|
|
28
|
+
class TestComprehensiveExceptionSupport:
|
|
29
|
+
"""Property tests for comprehensive exception support."""
|
|
30
|
+
|
|
31
|
+
@given(
|
|
32
|
+
exception_type=st.sampled_from(COMMON_EXCEPTION_TYPES),
|
|
33
|
+
error_message=st.text(min_size=1, max_size=200)
|
|
34
|
+
)
|
|
35
|
+
def test_comprehensive_exception_support(self, exception_type, error_message):
|
|
36
|
+
"""
|
|
37
|
+
Property 4: Comprehensive Exception Support
|
|
38
|
+
For any common Python exception type (TypeError, ValueError, AttributeError,
|
|
39
|
+
IndexError, KeyError, ImportError, SyntaxError), the Error_Explainer should
|
|
40
|
+
provide a specific, contextual explanation.
|
|
41
|
+
|
|
42
|
+
Feature: fishertools-refactor, Property 4: Comprehensive Exception Support
|
|
43
|
+
Validates: Requirements 2.4, 6.1, 6.2, 6.3, 6.4, 6.5, 6.6, 6.7
|
|
44
|
+
"""
|
|
45
|
+
# Create exception instance
|
|
46
|
+
exception = exception_type(error_message)
|
|
47
|
+
|
|
48
|
+
# Create explainer
|
|
49
|
+
explainer = ErrorExplainer()
|
|
50
|
+
|
|
51
|
+
# Get explanation
|
|
52
|
+
explanation = explainer.explain(exception)
|
|
53
|
+
|
|
54
|
+
# Property: Explanation must be generated without errors
|
|
55
|
+
assert explanation is not None
|
|
56
|
+
|
|
57
|
+
# Property: Explanation must contain all required components
|
|
58
|
+
assert explanation.original_error is not None
|
|
59
|
+
assert explanation.error_type is not None
|
|
60
|
+
assert explanation.simple_explanation is not None
|
|
61
|
+
assert explanation.fix_tip is not None
|
|
62
|
+
assert explanation.code_example is not None
|
|
63
|
+
|
|
64
|
+
# Property: Error type must match the exception type
|
|
65
|
+
assert explanation.error_type == exception_type.__name__
|
|
66
|
+
|
|
67
|
+
# Property: Original error must contain the exception message
|
|
68
|
+
assert error_message in explanation.original_error or str(exception) == explanation.original_error
|
|
69
|
+
|
|
70
|
+
# Property: Explanation must be non-empty and meaningful
|
|
71
|
+
assert len(explanation.simple_explanation.strip()) > 0
|
|
72
|
+
assert len(explanation.fix_tip.strip()) > 0
|
|
73
|
+
assert len(explanation.code_example.strip()) > 0
|
|
74
|
+
|
|
75
|
+
# Property: Explanation should be in Russian (contains Cyrillic characters)
|
|
76
|
+
cyrillic_chars = set('абвгдеёжзийклмнопрстуфхцчшщъыьэюяАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ')
|
|
77
|
+
explanation_text = explanation.simple_explanation + explanation.fix_tip
|
|
78
|
+
assert any(char in cyrillic_chars for char in explanation_text)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@pytest.mark.property
|
|
82
|
+
class TestGracefulFallbackForUnknownExceptions:
|
|
83
|
+
"""Property tests for graceful fallback behavior."""
|
|
84
|
+
|
|
85
|
+
@given(
|
|
86
|
+
error_message=st.text(min_size=1, max_size=200)
|
|
87
|
+
)
|
|
88
|
+
def test_graceful_fallback_for_unknown_exceptions(self, error_message):
|
|
89
|
+
"""
|
|
90
|
+
Property 5: Graceful Fallback for Unknown Exceptions
|
|
91
|
+
For any unsupported or custom exception type, the Error_Explainer should
|
|
92
|
+
provide a generic but helpful message instead of failing.
|
|
93
|
+
|
|
94
|
+
Feature: fishertools-refactor, Property 5: Graceful Fallback for Unknown Exceptions
|
|
95
|
+
Validates: Requirements 2.5
|
|
96
|
+
"""
|
|
97
|
+
# Create custom exception (not in common types)
|
|
98
|
+
exception = CustomError(error_message)
|
|
99
|
+
|
|
100
|
+
# Create explainer
|
|
101
|
+
explainer = ErrorExplainer()
|
|
102
|
+
|
|
103
|
+
# Get explanation - should not raise any exceptions
|
|
104
|
+
explanation = explainer.explain(exception)
|
|
105
|
+
|
|
106
|
+
# Property: Explanation must be generated without errors
|
|
107
|
+
assert explanation is not None
|
|
108
|
+
|
|
109
|
+
# Property: Explanation must contain all required components
|
|
110
|
+
assert explanation.original_error is not None
|
|
111
|
+
assert explanation.error_type is not None
|
|
112
|
+
assert explanation.simple_explanation is not None
|
|
113
|
+
assert explanation.fix_tip is not None
|
|
114
|
+
assert explanation.code_example is not None
|
|
115
|
+
|
|
116
|
+
# Property: Error type must match the custom exception type
|
|
117
|
+
assert explanation.error_type == "CustomError"
|
|
118
|
+
|
|
119
|
+
# Property: Original error must contain the exception message
|
|
120
|
+
assert error_message in explanation.original_error or str(exception) == explanation.original_error
|
|
121
|
+
|
|
122
|
+
# Property: Fallback explanation must be helpful and non-empty
|
|
123
|
+
assert len(explanation.simple_explanation.strip()) > 0
|
|
124
|
+
assert len(explanation.fix_tip.strip()) > 0
|
|
125
|
+
assert len(explanation.code_example.strip()) > 0
|
|
126
|
+
|
|
127
|
+
# Property: Fallback should mention the error type
|
|
128
|
+
assert "CustomError" in explanation.simple_explanation or "CustomError" in explanation.code_example
|
|
129
|
+
|
|
130
|
+
# Property: Fallback should be in Russian
|
|
131
|
+
cyrillic_chars = set('абвгдеёжзийклмнопрстуфхцчшщъыьэюяАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ')
|
|
132
|
+
explanation_text = explanation.simple_explanation + explanation.fix_tip
|
|
133
|
+
assert any(char in cyrillic_chars for char in explanation_text)
|
|
134
|
+
|
|
135
|
+
# Property: Additional info should provide guidance
|
|
136
|
+
if explanation.additional_info:
|
|
137
|
+
assert len(explanation.additional_info.strip()) > 0
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class TestExplainerUnitTests:
|
|
141
|
+
"""Unit tests for specific explainer functionality."""
|
|
142
|
+
|
|
143
|
+
def test_explainer_with_config(self):
|
|
144
|
+
"""Test explainer initialization with custom config."""
|
|
145
|
+
config = ExplainerConfig(language='en', use_colors=False)
|
|
146
|
+
explainer = ErrorExplainer(config)
|
|
147
|
+
|
|
148
|
+
assert explainer.config.language == 'en'
|
|
149
|
+
assert explainer.config.use_colors is False
|
|
150
|
+
|
|
151
|
+
def test_explainer_fallback_behavior(self):
|
|
152
|
+
"""Test specific fallback behavior with known custom exception."""
|
|
153
|
+
exception = AnotherCustomError("test message")
|
|
154
|
+
explainer = ErrorExplainer()
|
|
155
|
+
|
|
156
|
+
explanation = explainer.explain(exception)
|
|
157
|
+
|
|
158
|
+
assert explanation.error_type == "AnotherCustomError"
|
|
159
|
+
assert "test message" in explanation.original_error
|
|
160
|
+
assert "AnotherCustomError" in explanation.simple_explanation
|
|
161
|
+
|
|
162
|
+
def test_explainer_handles_empty_patterns(self):
|
|
163
|
+
"""Test that explainer works even with no patterns loaded."""
|
|
164
|
+
explainer = ErrorExplainer()
|
|
165
|
+
# Patterns list should be empty since no patterns are defined yet
|
|
166
|
+
assert isinstance(explainer.patterns, list)
|
|
167
|
+
|
|
168
|
+
# Should still handle exceptions gracefully
|
|
169
|
+
exception = TypeError("test error")
|
|
170
|
+
explanation = explainer.explain(exception)
|
|
171
|
+
|
|
172
|
+
assert explanation is not None
|
|
173
|
+
assert explanation.error_type == "TypeError"
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Property-based tests for output formatting system.
|
|
3
|
+
|
|
4
|
+
Tests Properties 10, 11, and 12 from the design document.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
from hypothesis import given, strategies as st, assume
|
|
9
|
+
import re
|
|
10
|
+
from fishertools.errors.formatters import (
|
|
11
|
+
ConsoleFormatter, PlainFormatter, JsonFormatter, get_formatter, Colors
|
|
12
|
+
)
|
|
13
|
+
from fishertools.errors.models import ErrorExplanation
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TestConsoleFormatter:
|
|
17
|
+
"""Tests for ConsoleFormatter with color support."""
|
|
18
|
+
|
|
19
|
+
def test_formatter_initialization(self):
|
|
20
|
+
"""Test formatter can be initialized with different color settings."""
|
|
21
|
+
formatter_with_colors = ConsoleFormatter(use_colors=True)
|
|
22
|
+
formatter_without_colors = ConsoleFormatter(use_colors=False)
|
|
23
|
+
|
|
24
|
+
assert isinstance(formatter_with_colors, ConsoleFormatter)
|
|
25
|
+
assert isinstance(formatter_without_colors, ConsoleFormatter)
|
|
26
|
+
|
|
27
|
+
@given(
|
|
28
|
+
original_error=st.text(min_size=0, max_size=100).filter(lambda x: '\r' not in x and '\n' not in x),
|
|
29
|
+
error_type=st.text(min_size=1, max_size=50).filter(lambda x: x.strip() and '\r' not in x and '\n' not in x),
|
|
30
|
+
simple_explanation=st.text(min_size=1, max_size=200).filter(lambda x: x.strip() and '\r' not in x and '\n' not in x),
|
|
31
|
+
fix_tip=st.text(min_size=1, max_size=200).filter(lambda x: x.strip() and '\r' not in x and '\n' not in x),
|
|
32
|
+
code_example=st.text(min_size=1, max_size=300).filter(lambda x: x.strip() and '\r' not in x and '\n' not in x),
|
|
33
|
+
additional_info=st.one_of(st.none(), st.text(min_size=0, max_size=200).filter(lambda x: '\r' not in x and '\n' not in x))
|
|
34
|
+
)
|
|
35
|
+
def test_structured_output_format(self, original_error, error_type, simple_explanation,
|
|
36
|
+
fix_tip, code_example, additional_info):
|
|
37
|
+
"""
|
|
38
|
+
**Property 10: Structured Output Format**
|
|
39
|
+
For any error explanation output, it should contain clearly delineated
|
|
40
|
+
sections for explanation, tip, example, and original error message.
|
|
41
|
+
**Validates: Requirements 7.1, 7.3**
|
|
42
|
+
"""
|
|
43
|
+
explanation = ErrorExplanation(
|
|
44
|
+
original_error=original_error,
|
|
45
|
+
error_type=error_type,
|
|
46
|
+
simple_explanation=simple_explanation,
|
|
47
|
+
fix_tip=fix_tip,
|
|
48
|
+
code_example=code_example,
|
|
49
|
+
additional_info=additional_info
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
formatter = ConsoleFormatter(use_colors=False) # Test without colors for clarity
|
|
53
|
+
output = formatter.format(explanation)
|
|
54
|
+
|
|
55
|
+
# Check that output contains structured sections
|
|
56
|
+
assert "Ошибка Python:" in output
|
|
57
|
+
assert "=== Что это означает ===" in output
|
|
58
|
+
assert "=== Как исправить ===" in output
|
|
59
|
+
assert "=== Пример ===" in output
|
|
60
|
+
|
|
61
|
+
# Check that the content appears in the output (normalize whitespace for comparison)
|
|
62
|
+
normalized_output = ' '.join(output.split())
|
|
63
|
+
normalized_explanation = ' '.join(simple_explanation.split())
|
|
64
|
+
normalized_tip = ' '.join(fix_tip.split())
|
|
65
|
+
normalized_code = ' '.join(code_example.split())
|
|
66
|
+
|
|
67
|
+
assert normalized_explanation in normalized_output
|
|
68
|
+
assert normalized_tip in normalized_output
|
|
69
|
+
assert normalized_code in normalized_output
|
|
70
|
+
|
|
71
|
+
# If original error is not empty, it should appear
|
|
72
|
+
if original_error.strip():
|
|
73
|
+
assert "=== Сообщение об ошибке ===" in output
|
|
74
|
+
normalized_error = ' '.join(original_error.split())
|
|
75
|
+
assert normalized_error in normalized_output
|
|
76
|
+
|
|
77
|
+
# If additional info is provided, it should appear
|
|
78
|
+
if additional_info and additional_info.strip():
|
|
79
|
+
assert "=== Дополнительная информация ===" in output
|
|
80
|
+
normalized_info = ' '.join(additional_info.split())
|
|
81
|
+
assert normalized_info in normalized_output
|
|
82
|
+
|
|
83
|
+
@given(
|
|
84
|
+
original_error=st.text(min_size=0, max_size=100).filter(lambda x: '\r' not in x and '\n' not in x),
|
|
85
|
+
error_type=st.text(min_size=1, max_size=50).filter(lambda x: x.strip() and '\r' not in x and '\n' not in x),
|
|
86
|
+
simple_explanation=st.text(min_size=1, max_size=200).filter(lambda x: x.strip() and '\r' not in x and '\n' not in x),
|
|
87
|
+
fix_tip=st.text(min_size=1, max_size=200).filter(lambda x: x.strip() and '\r' not in x and '\n' not in x),
|
|
88
|
+
code_example=st.text(min_size=1, max_size=300).filter(lambda x: x.strip() and '\r' not in x and '\n' not in x),
|
|
89
|
+
use_colors=st.booleans()
|
|
90
|
+
)
|
|
91
|
+
def test_enhanced_output_readability(self, original_error, error_type, simple_explanation,
|
|
92
|
+
fix_tip, code_example, use_colors):
|
|
93
|
+
"""
|
|
94
|
+
**Property 11: Enhanced Output Readability**
|
|
95
|
+
For any error explanation output, it should include formatting elements
|
|
96
|
+
(colors, highlighting, or other visual enhancements) to improve readability.
|
|
97
|
+
**Validates: Requirements 7.2, 7.4**
|
|
98
|
+
"""
|
|
99
|
+
explanation = ErrorExplanation(
|
|
100
|
+
original_error=original_error,
|
|
101
|
+
error_type=error_type,
|
|
102
|
+
simple_explanation=simple_explanation,
|
|
103
|
+
fix_tip=fix_tip,
|
|
104
|
+
code_example=code_example
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
formatter = ConsoleFormatter(use_colors=use_colors)
|
|
108
|
+
output = formatter.format(explanation)
|
|
109
|
+
|
|
110
|
+
# Check for visual enhancement elements
|
|
111
|
+
if use_colors and formatter.use_colors:
|
|
112
|
+
# Should contain ANSI color codes for enhanced readability
|
|
113
|
+
ansi_pattern = r'\033\[[0-9;]*m'
|
|
114
|
+
assert re.search(ansi_pattern, output), "Output should contain ANSI color codes when colors enabled"
|
|
115
|
+
|
|
116
|
+
# Should contain visual separators and structure
|
|
117
|
+
assert "═══" in output or "===" in output, "Output should contain section separators"
|
|
118
|
+
assert "🚨" in output, "Output should contain emoji for visual appeal"
|
|
119
|
+
assert "💡" in output, "Output should contain tip emoji"
|
|
120
|
+
|
|
121
|
+
# Code blocks should have visual boundaries
|
|
122
|
+
if code_example.strip():
|
|
123
|
+
assert "┌─" in output and "└─" in output, "Code examples should have visual boundaries"
|
|
124
|
+
|
|
125
|
+
@given(
|
|
126
|
+
code_example=st.text(min_size=1, max_size=500).filter(lambda x: x.strip() and '\r' not in x and '\n' not in x),
|
|
127
|
+
error_type=st.text(min_size=1, max_size=50).filter(lambda x: x.strip() and '\r' not in x and '\n' not in x),
|
|
128
|
+
simple_explanation=st.text(min_size=1, max_size=200).filter(lambda x: x.strip() and '\r' not in x and '\n' not in x),
|
|
129
|
+
fix_tip=st.text(min_size=1, max_size=200).filter(lambda x: x.strip() and '\r' not in x and '\n' not in x)
|
|
130
|
+
)
|
|
131
|
+
def test_proper_code_example_formatting(self, code_example, error_type,
|
|
132
|
+
simple_explanation, fix_tip):
|
|
133
|
+
"""
|
|
134
|
+
**Property 12: Proper Code Example Formatting**
|
|
135
|
+
For any code example in error explanations, it should be formatted
|
|
136
|
+
with proper indentation and syntax highlighting.
|
|
137
|
+
**Validates: Requirements 7.5**
|
|
138
|
+
"""
|
|
139
|
+
explanation = ErrorExplanation(
|
|
140
|
+
original_error="test error",
|
|
141
|
+
error_type=error_type,
|
|
142
|
+
simple_explanation=simple_explanation,
|
|
143
|
+
fix_tip=fix_tip,
|
|
144
|
+
code_example=code_example
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
formatter = ConsoleFormatter(use_colors=True)
|
|
148
|
+
output = formatter.format(explanation)
|
|
149
|
+
|
|
150
|
+
# Check for proper code block formatting
|
|
151
|
+
assert "┌─ Пример кода ─┐" in output, "Code should have proper header"
|
|
152
|
+
assert "└────────────────┘" in output, "Code should have proper footer"
|
|
153
|
+
|
|
154
|
+
# Check for indentation - code lines should be indented
|
|
155
|
+
code_section_start = output.find("┌─ Пример кода ─┐")
|
|
156
|
+
code_section_end = output.find("└────────────────┘", code_section_start)
|
|
157
|
+
|
|
158
|
+
if code_section_start != -1 and code_section_end != -1:
|
|
159
|
+
code_section = output[code_section_start:code_section_end]
|
|
160
|
+
code_lines = code_section.split('\n')[1:] # Skip header line
|
|
161
|
+
|
|
162
|
+
# Non-empty code lines should be indented
|
|
163
|
+
for line in code_lines:
|
|
164
|
+
if line.strip(): # Only check non-empty lines
|
|
165
|
+
assert line.startswith(' '), f"Code line should be indented: '{line}'"
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class TestPlainFormatter:
|
|
169
|
+
"""Tests for PlainFormatter without colors."""
|
|
170
|
+
|
|
171
|
+
@given(
|
|
172
|
+
original_error=st.text(min_size=0, max_size=100).filter(lambda x: '\r' not in x and '\n' not in x),
|
|
173
|
+
error_type=st.text(min_size=1, max_size=50).filter(lambda x: x.strip() and '\r' not in x and '\n' not in x),
|
|
174
|
+
simple_explanation=st.text(min_size=1, max_size=200).filter(lambda x: x.strip() and '\r' not in x and '\n' not in x),
|
|
175
|
+
fix_tip=st.text(min_size=1, max_size=200).filter(lambda x: x.strip() and '\r' not in x and '\n' not in x),
|
|
176
|
+
code_example=st.text(min_size=1, max_size=300).filter(lambda x: x.strip() and '\r' not in x and '\n' not in x)
|
|
177
|
+
)
|
|
178
|
+
def test_plain_formatter_structure(self, original_error, error_type, simple_explanation,
|
|
179
|
+
fix_tip, code_example):
|
|
180
|
+
"""Test that PlainFormatter produces structured output without colors."""
|
|
181
|
+
# Filter out any ANSI escape sequences from input
|
|
182
|
+
import re
|
|
183
|
+
ansi_pattern = r'\033\[[0-9;]*m'
|
|
184
|
+
|
|
185
|
+
original_error = re.sub(ansi_pattern, '', original_error)
|
|
186
|
+
error_type = re.sub(ansi_pattern, '', error_type)
|
|
187
|
+
simple_explanation = re.sub(ansi_pattern, '', simple_explanation)
|
|
188
|
+
fix_tip = re.sub(ansi_pattern, '', fix_tip)
|
|
189
|
+
code_example = re.sub(ansi_pattern, '', code_example)
|
|
190
|
+
|
|
191
|
+
# Ensure strings are not empty after cleaning
|
|
192
|
+
if not error_type.strip():
|
|
193
|
+
error_type = "TestError"
|
|
194
|
+
if not simple_explanation.strip():
|
|
195
|
+
simple_explanation = "Test explanation"
|
|
196
|
+
if not fix_tip.strip():
|
|
197
|
+
fix_tip = "Test tip"
|
|
198
|
+
if not code_example.strip():
|
|
199
|
+
code_example = "# test code"
|
|
200
|
+
|
|
201
|
+
explanation = ErrorExplanation(
|
|
202
|
+
original_error=original_error,
|
|
203
|
+
error_type=error_type,
|
|
204
|
+
simple_explanation=simple_explanation,
|
|
205
|
+
fix_tip=fix_tip,
|
|
206
|
+
code_example=code_example
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
formatter = PlainFormatter()
|
|
210
|
+
output = formatter.format(explanation)
|
|
211
|
+
|
|
212
|
+
# Should not contain ANSI color codes
|
|
213
|
+
assert not re.search(ansi_pattern, output), "Plain formatter should not contain color codes"
|
|
214
|
+
|
|
215
|
+
# Should contain structured sections
|
|
216
|
+
assert "Ошибка Python:" in output
|
|
217
|
+
assert "Что это означает:" in output
|
|
218
|
+
assert "Как исправить:" in output
|
|
219
|
+
assert "Пример:" in output
|
|
220
|
+
|
|
221
|
+
# Content should be present (normalize whitespace for comparison)
|
|
222
|
+
normalized_output = ' '.join(output.split())
|
|
223
|
+
normalized_explanation = ' '.join(simple_explanation.split())
|
|
224
|
+
normalized_tip = ' '.join(fix_tip.split())
|
|
225
|
+
normalized_code = ' '.join(code_example.split())
|
|
226
|
+
|
|
227
|
+
assert normalized_explanation in normalized_output
|
|
228
|
+
assert normalized_tip in normalized_output
|
|
229
|
+
assert normalized_code in normalized_output
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class TestJsonFormatter:
|
|
233
|
+
"""Tests for JsonFormatter."""
|
|
234
|
+
|
|
235
|
+
@given(
|
|
236
|
+
original_error=st.text(min_size=0, max_size=100).filter(lambda x: '\r' not in x and '\n' not in x),
|
|
237
|
+
error_type=st.text(min_size=1, max_size=50).filter(lambda x: x.strip() and '\r' not in x and '\n' not in x),
|
|
238
|
+
simple_explanation=st.text(min_size=1, max_size=200).filter(lambda x: x.strip() and '\r' not in x and '\n' not in x),
|
|
239
|
+
fix_tip=st.text(min_size=1, max_size=200).filter(lambda x: x.strip() and '\r' not in x and '\n' not in x),
|
|
240
|
+
code_example=st.text(min_size=1, max_size=300).filter(lambda x: x.strip() and '\r' not in x and '\n' not in x)
|
|
241
|
+
)
|
|
242
|
+
def test_json_formatter_structure(self, original_error, error_type, simple_explanation,
|
|
243
|
+
fix_tip, code_example):
|
|
244
|
+
"""Test that JsonFormatter produces valid JSON output."""
|
|
245
|
+
explanation = ErrorExplanation(
|
|
246
|
+
original_error=original_error,
|
|
247
|
+
error_type=error_type,
|
|
248
|
+
simple_explanation=simple_explanation,
|
|
249
|
+
fix_tip=fix_tip,
|
|
250
|
+
code_example=code_example
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
formatter = JsonFormatter()
|
|
254
|
+
output = formatter.format(explanation)
|
|
255
|
+
|
|
256
|
+
# Should be valid JSON
|
|
257
|
+
import json
|
|
258
|
+
parsed = json.loads(output)
|
|
259
|
+
|
|
260
|
+
# Should contain all required fields
|
|
261
|
+
assert parsed['original_error'] == original_error
|
|
262
|
+
assert parsed['error_type'] == error_type
|
|
263
|
+
assert parsed['simple_explanation'] == simple_explanation
|
|
264
|
+
assert parsed['fix_tip'] == fix_tip
|
|
265
|
+
assert parsed['code_example'] == code_example
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
class TestFormatterFactory:
|
|
269
|
+
"""Tests for get_formatter factory function."""
|
|
270
|
+
|
|
271
|
+
def test_get_formatter_console(self):
|
|
272
|
+
"""Test getting console formatter."""
|
|
273
|
+
formatter = get_formatter('console')
|
|
274
|
+
assert isinstance(formatter, ConsoleFormatter)
|
|
275
|
+
|
|
276
|
+
formatter_no_colors = get_formatter('console', use_colors=False)
|
|
277
|
+
assert isinstance(formatter_no_colors, ConsoleFormatter)
|
|
278
|
+
assert not formatter_no_colors.use_colors or not formatter_no_colors._supports_color()
|
|
279
|
+
|
|
280
|
+
def test_get_formatter_plain(self):
|
|
281
|
+
"""Test getting plain formatter."""
|
|
282
|
+
formatter = get_formatter('plain')
|
|
283
|
+
assert isinstance(formatter, PlainFormatter)
|
|
284
|
+
|
|
285
|
+
def test_get_formatter_json(self):
|
|
286
|
+
"""Test getting JSON formatter."""
|
|
287
|
+
formatter = get_formatter('json')
|
|
288
|
+
assert isinstance(formatter, JsonFormatter)
|
|
289
|
+
|
|
290
|
+
def test_get_formatter_invalid(self):
|
|
291
|
+
"""Test getting invalid formatter raises error."""
|
|
292
|
+
from fishertools.errors.exceptions import FormattingError
|
|
293
|
+
with pytest.raises(FormattingError, match="Неподдерживаемый тип форматтера"):
|
|
294
|
+
get_formatter('invalid')
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
class TestColorSupport:
|
|
298
|
+
"""Tests for color support functionality."""
|
|
299
|
+
|
|
300
|
+
def test_colors_class_constants(self):
|
|
301
|
+
"""Test that Colors class has required constants."""
|
|
302
|
+
assert hasattr(Colors, 'RED')
|
|
303
|
+
assert hasattr(Colors, 'GREEN')
|
|
304
|
+
assert hasattr(Colors, 'YELLOW')
|
|
305
|
+
assert hasattr(Colors, 'BLUE')
|
|
306
|
+
assert hasattr(Colors, 'BOLD')
|
|
307
|
+
assert hasattr(Colors, 'RESET')
|
|
308
|
+
|
|
309
|
+
# All color codes should be strings
|
|
310
|
+
assert isinstance(Colors.RED, str)
|
|
311
|
+
assert isinstance(Colors.RESET, str)
|
|
312
|
+
|
|
313
|
+
def test_colorize_functionality(self):
|
|
314
|
+
"""Test colorize method works correctly."""
|
|
315
|
+
formatter = ConsoleFormatter(use_colors=True)
|
|
316
|
+
|
|
317
|
+
# Test colorizing text
|
|
318
|
+
colored_text = formatter._colorize("test", Colors.RED)
|
|
319
|
+
if formatter.use_colors:
|
|
320
|
+
assert Colors.RED in colored_text
|
|
321
|
+
assert Colors.RESET in colored_text
|
|
322
|
+
assert "test" in colored_text
|
|
323
|
+
else:
|
|
324
|
+
assert colored_text == "test"
|
|
325
|
+
|
|
326
|
+
def test_syntax_highlighting(self):
|
|
327
|
+
"""Test Python syntax highlighting."""
|
|
328
|
+
formatter = ConsoleFormatter(use_colors=True)
|
|
329
|
+
|
|
330
|
+
code_line = " def test_function():"
|
|
331
|
+
highlighted = formatter._highlight_python_syntax(code_line)
|
|
332
|
+
|
|
333
|
+
if formatter.use_colors:
|
|
334
|
+
# Should contain color codes for 'def' keyword
|
|
335
|
+
assert Colors.MAGENTA in highlighted or "def" in highlighted
|
|
336
|
+
|
|
337
|
+
# Original text should still be present
|
|
338
|
+
assert "test_function" in highlighted
|