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,248 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for error explanation data models.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from hypothesis import given, strategies as st
|
|
7
|
+
|
|
8
|
+
from fishertools.errors.models import ErrorPattern, ErrorExplanation, ExplainerConfig
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# Hypothesis strategies for generating test data
|
|
12
|
+
exception_types = st.sampled_from([
|
|
13
|
+
TypeError, ValueError, AttributeError, IndexError, KeyError, ImportError, SyntaxError
|
|
14
|
+
])
|
|
15
|
+
|
|
16
|
+
russian_text = st.text(
|
|
17
|
+
alphabet="абвгдеёжзийклмнопрстуфхцчшщъыьэюяАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ ",
|
|
18
|
+
min_size=10, max_size=200
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
code_example = st.text(
|
|
22
|
+
alphabet="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789()[]{}=+-*/.,:;#' \n",
|
|
23
|
+
min_size=5, max_size=100
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class TestErrorPattern:
|
|
28
|
+
"""Tests for ErrorPattern data model."""
|
|
29
|
+
|
|
30
|
+
def test_error_pattern_creation(self):
|
|
31
|
+
"""Test basic ErrorPattern creation."""
|
|
32
|
+
pattern = ErrorPattern(
|
|
33
|
+
error_type=TypeError,
|
|
34
|
+
error_keywords=["cannot", "interpreted"],
|
|
35
|
+
explanation="Объяснение типа ошибки",
|
|
36
|
+
tip="Совет по исправлению",
|
|
37
|
+
example="# Пример кода",
|
|
38
|
+
common_causes=["Неправильный тип данных"]
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
assert pattern.error_type == TypeError
|
|
42
|
+
assert "cannot" in pattern.error_keywords
|
|
43
|
+
assert pattern.explanation == "Объяснение типа ошибки"
|
|
44
|
+
|
|
45
|
+
def test_pattern_matches_correct_exception(self):
|
|
46
|
+
"""Test that pattern correctly matches exceptions."""
|
|
47
|
+
pattern = ErrorPattern(
|
|
48
|
+
error_type=TypeError,
|
|
49
|
+
error_keywords=["cannot", "interpreted"],
|
|
50
|
+
explanation="Test explanation",
|
|
51
|
+
tip="Test tip",
|
|
52
|
+
example="# Test example",
|
|
53
|
+
common_causes=["Test cause"]
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# Should match TypeError with matching keywords
|
|
57
|
+
exception = TypeError("'str' object cannot be interpreted as an integer")
|
|
58
|
+
assert pattern.matches(exception) is True
|
|
59
|
+
|
|
60
|
+
# Should not match different exception type
|
|
61
|
+
value_error = ValueError("some error")
|
|
62
|
+
assert pattern.matches(value_error) is False
|
|
63
|
+
|
|
64
|
+
# Should not match TypeError without matching keywords
|
|
65
|
+
type_error_no_match = TypeError("different error message")
|
|
66
|
+
assert pattern.matches(type_error_no_match) is False
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class TestErrorExplanation:
|
|
70
|
+
"""Tests for ErrorExplanation data model."""
|
|
71
|
+
|
|
72
|
+
def test_error_explanation_creation(self):
|
|
73
|
+
"""Test basic ErrorExplanation creation."""
|
|
74
|
+
explanation = ErrorExplanation(
|
|
75
|
+
original_error="TypeError: test error",
|
|
76
|
+
error_type="TypeError",
|
|
77
|
+
simple_explanation="Простое объяснение",
|
|
78
|
+
fix_tip="Совет по исправлению",
|
|
79
|
+
code_example="# Пример кода"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
assert explanation.original_error == "TypeError: test error"
|
|
83
|
+
assert explanation.error_type == "TypeError"
|
|
84
|
+
assert explanation.simple_explanation == "Простое объяснение"
|
|
85
|
+
|
|
86
|
+
def test_error_explanation_to_dict(self):
|
|
87
|
+
"""Test ErrorExplanation serialization to dictionary."""
|
|
88
|
+
explanation = ErrorExplanation(
|
|
89
|
+
original_error="Test error",
|
|
90
|
+
error_type="TestError",
|
|
91
|
+
simple_explanation="Test explanation",
|
|
92
|
+
fix_tip="Test tip",
|
|
93
|
+
code_example="# Test code"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
result_dict = explanation.to_dict()
|
|
97
|
+
|
|
98
|
+
assert isinstance(result_dict, dict)
|
|
99
|
+
assert result_dict["original_error"] == "Test error"
|
|
100
|
+
assert result_dict["error_type"] == "TestError"
|
|
101
|
+
assert result_dict["additional_info"] is None
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class TestExplainerConfig:
|
|
105
|
+
"""Tests for ExplainerConfig data model."""
|
|
106
|
+
|
|
107
|
+
def test_explainer_config_defaults(self):
|
|
108
|
+
"""Test ExplainerConfig default values."""
|
|
109
|
+
config = ExplainerConfig()
|
|
110
|
+
|
|
111
|
+
assert config.language == 'ru'
|
|
112
|
+
assert config.format_type == 'console'
|
|
113
|
+
assert config.show_original_error is True
|
|
114
|
+
assert config.show_traceback is False
|
|
115
|
+
assert config.use_colors is True
|
|
116
|
+
assert config.max_explanation_length == 200
|
|
117
|
+
|
|
118
|
+
def test_explainer_config_custom_values(self):
|
|
119
|
+
"""Test ExplainerConfig with custom values."""
|
|
120
|
+
config = ExplainerConfig(
|
|
121
|
+
language='en',
|
|
122
|
+
format_type='json',
|
|
123
|
+
show_original_error=False,
|
|
124
|
+
use_colors=False,
|
|
125
|
+
max_explanation_length=150
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
assert config.language == 'en'
|
|
129
|
+
assert config.format_type == 'json'
|
|
130
|
+
assert config.show_original_error is False
|
|
131
|
+
assert config.use_colors is False
|
|
132
|
+
assert config.max_explanation_length == 150
|
|
133
|
+
|
|
134
|
+
def test_explainer_config_to_dict(self):
|
|
135
|
+
"""Test ExplainerConfig serialization to dictionary."""
|
|
136
|
+
config = ExplainerConfig()
|
|
137
|
+
result_dict = config.to_dict()
|
|
138
|
+
|
|
139
|
+
assert isinstance(result_dict, dict)
|
|
140
|
+
assert result_dict["language"] == 'ru'
|
|
141
|
+
assert result_dict["format_type"] == 'console'
|
|
142
|
+
|
|
143
|
+
@given(
|
|
144
|
+
language=st.sampled_from(['ru', 'en']),
|
|
145
|
+
format_type=st.sampled_from(['console', 'json', 'plain']),
|
|
146
|
+
show_original_error=st.booleans(),
|
|
147
|
+
show_traceback=st.booleans(),
|
|
148
|
+
use_colors=st.booleans(),
|
|
149
|
+
max_explanation_length=st.integers(min_value=1, max_value=1000)
|
|
150
|
+
)
|
|
151
|
+
@pytest.mark.property
|
|
152
|
+
def test_explainer_config_property_serialization(self, language, format_type,
|
|
153
|
+
show_original_error, show_traceback,
|
|
154
|
+
use_colors, max_explanation_length):
|
|
155
|
+
"""
|
|
156
|
+
Property test: ExplainerConfig should always serialize to dict correctly.
|
|
157
|
+
Feature: fishertools-refactor, Property: Config serialization consistency
|
|
158
|
+
"""
|
|
159
|
+
config = ExplainerConfig(
|
|
160
|
+
language=language,
|
|
161
|
+
format_type=format_type,
|
|
162
|
+
show_original_error=show_original_error,
|
|
163
|
+
show_traceback=show_traceback,
|
|
164
|
+
use_colors=use_colors,
|
|
165
|
+
max_explanation_length=max_explanation_length
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
result_dict = config.to_dict()
|
|
169
|
+
|
|
170
|
+
# Property: serialization should always produce a dict with correct keys
|
|
171
|
+
assert isinstance(result_dict, dict)
|
|
172
|
+
assert result_dict["language"] == language
|
|
173
|
+
assert result_dict["format_type"] == format_type
|
|
174
|
+
assert result_dict["show_original_error"] == show_original_error
|
|
175
|
+
assert result_dict["show_traceback"] == show_traceback
|
|
176
|
+
assert result_dict["use_colors"] == use_colors
|
|
177
|
+
assert result_dict["max_explanation_length"] == max_explanation_length
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@pytest.mark.property
|
|
181
|
+
class TestCompleteErrorExplanationStructure:
|
|
182
|
+
"""Property tests for complete error explanation structure."""
|
|
183
|
+
|
|
184
|
+
@given(
|
|
185
|
+
original_error=st.text(min_size=0, max_size=200), # Allow empty strings
|
|
186
|
+
error_type=st.text(min_size=1, max_size=50).filter(lambda x: x.strip()), # Ensure non-empty after strip
|
|
187
|
+
simple_explanation=russian_text,
|
|
188
|
+
fix_tip=russian_text,
|
|
189
|
+
code_example=code_example,
|
|
190
|
+
additional_info=st.one_of(st.none(), russian_text)
|
|
191
|
+
)
|
|
192
|
+
def test_complete_error_explanation_structure(self, original_error, error_type,
|
|
193
|
+
simple_explanation, fix_tip,
|
|
194
|
+
code_example, additional_info):
|
|
195
|
+
"""
|
|
196
|
+
Property 3: Complete Error Explanation Structure
|
|
197
|
+
For any supported Python exception, the explain_error() function should produce
|
|
198
|
+
output containing a Russian explanation, a practical fix tip, and a relevant code example.
|
|
199
|
+
|
|
200
|
+
Feature: fishertools-refactor, Property 3: Complete Error Explanation Structure
|
|
201
|
+
Validates: Requirements 2.1, 2.2, 2.3
|
|
202
|
+
"""
|
|
203
|
+
# Create ErrorExplanation with all required components
|
|
204
|
+
explanation = ErrorExplanation(
|
|
205
|
+
original_error=original_error,
|
|
206
|
+
error_type=error_type,
|
|
207
|
+
simple_explanation=simple_explanation,
|
|
208
|
+
fix_tip=fix_tip,
|
|
209
|
+
code_example=code_example,
|
|
210
|
+
additional_info=additional_info
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
# Property: ErrorExplanation must contain all required components
|
|
214
|
+
assert explanation.original_error is not None
|
|
215
|
+
assert explanation.error_type is not None
|
|
216
|
+
assert explanation.simple_explanation is not None
|
|
217
|
+
assert explanation.fix_tip is not None
|
|
218
|
+
assert explanation.code_example is not None
|
|
219
|
+
|
|
220
|
+
# Property: All text fields must be non-empty strings (except original_error which can be empty)
|
|
221
|
+
assert isinstance(explanation.original_error, str)
|
|
222
|
+
assert isinstance(explanation.error_type, str)
|
|
223
|
+
assert isinstance(explanation.simple_explanation, str)
|
|
224
|
+
assert isinstance(explanation.fix_tip, str)
|
|
225
|
+
assert isinstance(explanation.code_example, str)
|
|
226
|
+
|
|
227
|
+
# original_error can be empty, but others must have content
|
|
228
|
+
assert len(explanation.error_type.strip()) > 0
|
|
229
|
+
assert len(explanation.simple_explanation.strip()) > 0
|
|
230
|
+
assert len(explanation.fix_tip.strip()) > 0
|
|
231
|
+
assert len(explanation.code_example.strip()) > 0
|
|
232
|
+
|
|
233
|
+
# Property: Serialization must preserve all components
|
|
234
|
+
serialized = explanation.to_dict()
|
|
235
|
+
assert "original_error" in serialized
|
|
236
|
+
assert "error_type" in serialized
|
|
237
|
+
assert "simple_explanation" in serialized
|
|
238
|
+
assert "fix_tip" in serialized
|
|
239
|
+
assert "code_example" in serialized
|
|
240
|
+
assert "additional_info" in serialized
|
|
241
|
+
|
|
242
|
+
# Property: Serialized data must match original data
|
|
243
|
+
assert serialized["original_error"] == explanation.original_error
|
|
244
|
+
assert serialized["error_type"] == explanation.error_type
|
|
245
|
+
assert serialized["simple_explanation"] == explanation.simple_explanation
|
|
246
|
+
assert serialized["fix_tip"] == explanation.fix_tip
|
|
247
|
+
assert serialized["code_example"] == explanation.code_example
|
|
248
|
+
assert serialized["additional_info"] == explanation.additional_info
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit tests for error pattern matching functionality.
|
|
3
|
+
|
|
4
|
+
Tests specific error patterns and their matching behavior for common Python exceptions.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
from fishertools.errors.patterns import load_default_patterns, DEFAULT_PATTERNS
|
|
9
|
+
from fishertools.errors.explainer import ErrorExplainer
|
|
10
|
+
from fishertools.errors.models import ErrorPattern
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestErrorPatternMatching:
|
|
14
|
+
"""Unit tests for specific error pattern matching."""
|
|
15
|
+
|
|
16
|
+
def test_load_default_patterns(self):
|
|
17
|
+
"""Test that default patterns are loaded correctly."""
|
|
18
|
+
patterns = load_default_patterns()
|
|
19
|
+
|
|
20
|
+
# Should have patterns for all required exception types
|
|
21
|
+
assert len(patterns) > 0
|
|
22
|
+
|
|
23
|
+
# Check that we have patterns for all required exception types
|
|
24
|
+
exception_types = {pattern.error_type for pattern in patterns}
|
|
25
|
+
required_types = {TypeError, ValueError, AttributeError, IndexError, KeyError, ImportError, SyntaxError}
|
|
26
|
+
|
|
27
|
+
assert required_types.issubset(exception_types), f"Missing patterns for: {required_types - exception_types}"
|
|
28
|
+
|
|
29
|
+
def test_type_error_operand_pattern_matching(self):
|
|
30
|
+
"""Test TypeError pattern matching for operand type errors."""
|
|
31
|
+
explainer = ErrorExplainer()
|
|
32
|
+
|
|
33
|
+
# Test unsupported operand type error
|
|
34
|
+
exception = TypeError("unsupported operand type(s) for +: 'int' and 'str'")
|
|
35
|
+
explanation = explainer.explain(exception)
|
|
36
|
+
|
|
37
|
+
assert explanation.error_type == "TypeError"
|
|
38
|
+
assert "несовместимыми типами" in explanation.simple_explanation
|
|
39
|
+
assert "преобразования типов" in explanation.fix_tip
|
|
40
|
+
assert "int(" in explanation.code_example or "str(" in explanation.code_example
|
|
41
|
+
|
|
42
|
+
def test_type_error_function_arguments_pattern_matching(self):
|
|
43
|
+
"""Test TypeError pattern matching for function argument errors."""
|
|
44
|
+
explainer = ErrorExplainer()
|
|
45
|
+
|
|
46
|
+
# Test missing positional argument error
|
|
47
|
+
exception = TypeError("greet() missing 1 required positional argument: 'age'")
|
|
48
|
+
explanation = explainer.explain(exception)
|
|
49
|
+
|
|
50
|
+
assert explanation.error_type == "TypeError"
|
|
51
|
+
assert "неправильным количеством аргументов" in explanation.simple_explanation
|
|
52
|
+
assert "правильное количество аргументов" in explanation.fix_tip
|
|
53
|
+
assert "def " in explanation.code_example
|
|
54
|
+
|
|
55
|
+
def test_type_error_not_callable_pattern_matching(self):
|
|
56
|
+
"""Test TypeError pattern matching for 'not callable' errors."""
|
|
57
|
+
explainer = ErrorExplainer()
|
|
58
|
+
|
|
59
|
+
# Test object not callable error
|
|
60
|
+
exception = TypeError("'list' object is not callable")
|
|
61
|
+
explanation = explainer.explain(exception)
|
|
62
|
+
|
|
63
|
+
assert explanation.error_type == "TypeError"
|
|
64
|
+
assert "не является" in explanation.simple_explanation and "функцией" in explanation.simple_explanation
|
|
65
|
+
assert "функцию" in explanation.fix_tip
|
|
66
|
+
assert "len(" in explanation.code_example
|
|
67
|
+
|
|
68
|
+
def test_value_error_conversion_pattern_matching(self):
|
|
69
|
+
"""Test ValueError pattern matching for conversion errors."""
|
|
70
|
+
explainer = ErrorExplainer()
|
|
71
|
+
|
|
72
|
+
# Test invalid literal for int conversion
|
|
73
|
+
exception = ValueError("invalid literal for int() with base 10: 'abc'")
|
|
74
|
+
explanation = explainer.explain(exception)
|
|
75
|
+
|
|
76
|
+
assert explanation.error_type == "ValueError"
|
|
77
|
+
assert "преобразовать строку в число" in explanation.simple_explanation
|
|
78
|
+
assert "цифры" in explanation.fix_tip
|
|
79
|
+
assert "isdigit()" in explanation.code_example
|
|
80
|
+
|
|
81
|
+
def test_value_error_unpacking_pattern_matching(self):
|
|
82
|
+
"""Test ValueError pattern matching for unpacking errors."""
|
|
83
|
+
explainer = ErrorExplainer()
|
|
84
|
+
|
|
85
|
+
# Test too many values to unpack
|
|
86
|
+
exception = ValueError("too many values to unpack (expected 2)")
|
|
87
|
+
explanation = explainer.explain(exception)
|
|
88
|
+
|
|
89
|
+
assert explanation.error_type == "ValueError"
|
|
90
|
+
assert "Количество переменных" in explanation.simple_explanation
|
|
91
|
+
assert "количество переменных" in explanation.fix_tip
|
|
92
|
+
assert "=" in explanation.code_example
|
|
93
|
+
|
|
94
|
+
def test_attribute_error_pattern_matching(self):
|
|
95
|
+
"""Test AttributeError pattern matching."""
|
|
96
|
+
explainer = ErrorExplainer()
|
|
97
|
+
|
|
98
|
+
# Test attribute error
|
|
99
|
+
exception = AttributeError("'str' object has no attribute 'append'")
|
|
100
|
+
explanation = explainer.explain(exception)
|
|
101
|
+
|
|
102
|
+
assert explanation.error_type == "AttributeError"
|
|
103
|
+
assert "атрибуту или методу" in explanation.simple_explanation
|
|
104
|
+
assert "правильность написания" in explanation.fix_tip
|
|
105
|
+
assert "dir()" in explanation.fix_tip
|
|
106
|
+
|
|
107
|
+
def test_index_error_pattern_matching(self):
|
|
108
|
+
"""Test IndexError pattern matching."""
|
|
109
|
+
explainer = ErrorExplainer()
|
|
110
|
+
|
|
111
|
+
# Test list index out of range
|
|
112
|
+
exception = IndexError("list index out of range")
|
|
113
|
+
explanation = explainer.explain(exception)
|
|
114
|
+
|
|
115
|
+
assert explanation.error_type == "IndexError"
|
|
116
|
+
assert "индексу, который не существует" in explanation.simple_explanation
|
|
117
|
+
assert "len()" in explanation.fix_tip
|
|
118
|
+
assert "len(" in explanation.code_example
|
|
119
|
+
|
|
120
|
+
def test_key_error_pattern_matching(self):
|
|
121
|
+
"""Test KeyError pattern matching."""
|
|
122
|
+
explainer = ErrorExplainer()
|
|
123
|
+
|
|
124
|
+
# Test key error
|
|
125
|
+
exception = KeyError("'missing_key'")
|
|
126
|
+
explanation = explainer.explain(exception)
|
|
127
|
+
|
|
128
|
+
assert explanation.error_type == "KeyError"
|
|
129
|
+
assert "ключу, которого в словаре не существует" in explanation.simple_explanation
|
|
130
|
+
assert "get()" in explanation.fix_tip or "'in'" in explanation.fix_tip
|
|
131
|
+
assert "get(" in explanation.code_example or " in " in explanation.code_example
|
|
132
|
+
|
|
133
|
+
def test_import_error_pattern_matching(self):
|
|
134
|
+
"""Test ImportError pattern matching."""
|
|
135
|
+
explainer = ErrorExplainer()
|
|
136
|
+
|
|
137
|
+
# Test module not found error
|
|
138
|
+
exception = ImportError("No module named 'nonexistent_module'")
|
|
139
|
+
explanation = explainer.explain(exception)
|
|
140
|
+
|
|
141
|
+
assert explanation.error_type == "ImportError"
|
|
142
|
+
assert "не может найти модуль" in explanation.simple_explanation
|
|
143
|
+
assert "установлен" in explanation.fix_tip
|
|
144
|
+
assert "pip install" in explanation.code_example or "import" in explanation.code_example
|
|
145
|
+
|
|
146
|
+
def test_syntax_error_pattern_matching(self):
|
|
147
|
+
"""Test SyntaxError pattern matching."""
|
|
148
|
+
explainer = ErrorExplainer()
|
|
149
|
+
|
|
150
|
+
# Test syntax error
|
|
151
|
+
exception = SyntaxError("invalid syntax")
|
|
152
|
+
explanation = explainer.explain(exception)
|
|
153
|
+
|
|
154
|
+
assert explanation.error_type == "SyntaxError"
|
|
155
|
+
assert "синтаксическая ошибка" in explanation.simple_explanation
|
|
156
|
+
assert "скобки" in explanation.fix_tip or "отступы" in explanation.fix_tip
|
|
157
|
+
assert ":" in explanation.code_example
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class TestErrorPatternValidation:
|
|
161
|
+
"""Tests for error pattern validation and structure."""
|
|
162
|
+
|
|
163
|
+
def test_all_patterns_have_required_fields(self):
|
|
164
|
+
"""Test that all patterns have required fields populated."""
|
|
165
|
+
patterns = load_default_patterns()
|
|
166
|
+
|
|
167
|
+
for pattern in patterns:
|
|
168
|
+
assert isinstance(pattern, ErrorPattern)
|
|
169
|
+
assert pattern.error_type is not None
|
|
170
|
+
# Allow empty keywords for certain patterns (like KeyError)
|
|
171
|
+
assert pattern.error_keywords is not None
|
|
172
|
+
assert pattern.explanation.strip() != ""
|
|
173
|
+
assert pattern.tip.strip() != ""
|
|
174
|
+
assert pattern.example.strip() != ""
|
|
175
|
+
assert len(pattern.common_causes) > 0
|
|
176
|
+
|
|
177
|
+
def test_patterns_have_russian_content(self):
|
|
178
|
+
"""Test that all patterns contain Russian text."""
|
|
179
|
+
patterns = load_default_patterns()
|
|
180
|
+
cyrillic_chars = set('абвгдеёжзийклмнопрстуфхцчшщъыьэюяАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ')
|
|
181
|
+
|
|
182
|
+
for pattern in patterns:
|
|
183
|
+
# Check explanation contains Russian text
|
|
184
|
+
assert any(char in cyrillic_chars for char in pattern.explanation)
|
|
185
|
+
# Check tip contains Russian text
|
|
186
|
+
assert any(char in cyrillic_chars for char in pattern.tip)
|
|
187
|
+
|
|
188
|
+
def test_pattern_matching_logic(self):
|
|
189
|
+
"""Test the pattern matching logic works correctly."""
|
|
190
|
+
patterns = load_default_patterns()
|
|
191
|
+
|
|
192
|
+
# Find a TypeError pattern for operand types
|
|
193
|
+
type_error_pattern = None
|
|
194
|
+
for pattern in patterns:
|
|
195
|
+
if pattern.error_type == TypeError and "operand" in pattern.error_keywords[0]:
|
|
196
|
+
type_error_pattern = pattern
|
|
197
|
+
break
|
|
198
|
+
|
|
199
|
+
assert type_error_pattern is not None
|
|
200
|
+
|
|
201
|
+
# Test that it matches appropriate exceptions
|
|
202
|
+
matching_exception = TypeError("unsupported operand type(s) for +: 'int' and 'str'")
|
|
203
|
+
assert type_error_pattern.matches(matching_exception)
|
|
204
|
+
|
|
205
|
+
# Test that it doesn't match inappropriate exceptions
|
|
206
|
+
non_matching_exception = ValueError("invalid literal")
|
|
207
|
+
assert not type_error_pattern.matches(non_matching_exception)
|
|
208
|
+
|
|
209
|
+
# Test that it doesn't match TypeError with different message
|
|
210
|
+
different_type_error = TypeError("takes 1 positional argument but 2 were given")
|
|
211
|
+
# This should not match the operand pattern (should match a different TypeError pattern)
|
|
212
|
+
assert not type_error_pattern.matches(different_type_error)
|
|
213
|
+
|
|
214
|
+
def test_pattern_coverage_for_requirements(self):
|
|
215
|
+
"""Test that we have patterns covering all required exception types from requirements."""
|
|
216
|
+
patterns = load_default_patterns()
|
|
217
|
+
covered_types = {pattern.error_type for pattern in patterns}
|
|
218
|
+
|
|
219
|
+
# Requirements 6.1-6.7 specify these exception types
|
|
220
|
+
required_types = {
|
|
221
|
+
TypeError, # 6.1
|
|
222
|
+
ValueError, # 6.2
|
|
223
|
+
AttributeError, # 6.3
|
|
224
|
+
IndexError, # 6.4
|
|
225
|
+
KeyError, # 6.5
|
|
226
|
+
ImportError, # 6.6
|
|
227
|
+
SyntaxError # 6.7
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
assert required_types.issubset(covered_types), f"Missing coverage for: {required_types - covered_types}"
|
|
231
|
+
|
|
232
|
+
# Ensure we have multiple patterns for TypeError (most common)
|
|
233
|
+
type_error_patterns = [p for p in patterns if p.error_type == TypeError]
|
|
234
|
+
assert len(type_error_patterns) >= 2, "Should have multiple TypeError patterns for different scenarios"
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
class TestPatternIntegration:
|
|
238
|
+
"""Integration tests for patterns with the explainer system."""
|
|
239
|
+
|
|
240
|
+
def test_explainer_uses_patterns_correctly(self):
|
|
241
|
+
"""Test that explainer correctly uses loaded patterns."""
|
|
242
|
+
explainer = ErrorExplainer()
|
|
243
|
+
|
|
244
|
+
# Verify patterns are loaded
|
|
245
|
+
assert len(explainer.patterns) > 0
|
|
246
|
+
|
|
247
|
+
# Test with a specific exception that should match a pattern
|
|
248
|
+
exception = TypeError("unsupported operand type(s) for +: 'int' and 'str'")
|
|
249
|
+
explanation = explainer.explain(exception)
|
|
250
|
+
|
|
251
|
+
# Should get a pattern-based explanation, not fallback
|
|
252
|
+
assert "несовместимыми типами" in explanation.simple_explanation
|
|
253
|
+
assert explanation.additional_info is not None
|
|
254
|
+
assert "Частые причины:" in explanation.additional_info
|
|
255
|
+
|
|
256
|
+
def test_explainer_fallback_when_no_pattern_matches(self):
|
|
257
|
+
"""Test that explainer falls back correctly when no pattern matches."""
|
|
258
|
+
explainer = ErrorExplainer()
|
|
259
|
+
|
|
260
|
+
# Create an exception that won't match any pattern
|
|
261
|
+
class UnknownError(Exception):
|
|
262
|
+
pass
|
|
263
|
+
|
|
264
|
+
exception = UnknownError("some unknown error")
|
|
265
|
+
explanation = explainer.explain(exception)
|
|
266
|
+
|
|
267
|
+
# Should get fallback explanation
|
|
268
|
+
assert "UnknownError" in explanation.simple_explanation
|
|
269
|
+
assert "что-то пошло не так" in explanation.simple_explanation
|
|
270
|
+
assert explanation.additional_info is not None
|