debug-dojo 0.8.0__tar.gz → 0.9.0__tar.gz
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.
- {debug_dojo-0.8.0/src/debug_dojo.egg-info → debug_dojo-0.9.0}/PKG-INFO +1 -2
- {debug_dojo-0.8.0 → debug_dojo-0.9.0}/pyproject.toml +3 -3
- {debug_dojo-0.8.0 → debug_dojo-0.9.0}/src/debug_dojo/_compare.py +31 -7
- {debug_dojo-0.8.0 → debug_dojo-0.9.0}/src/debug_dojo/_config.py +1 -1
- {debug_dojo-0.8.0 → debug_dojo-0.9.0}/src/debug_dojo/_config_models.py +6 -1
- {debug_dojo-0.8.0 → debug_dojo-0.9.0}/src/debug_dojo/_execution.py +3 -4
- {debug_dojo-0.8.0 → debug_dojo-0.9.0}/src/debug_dojo/_gamification.py +12 -9
- {debug_dojo-0.8.0 → debug_dojo-0.9.0}/src/debug_dojo/_installers.py +3 -3
- {debug_dojo-0.8.0 → debug_dojo-0.9.0/src/debug_dojo.egg-info}/PKG-INFO +1 -2
- {debug_dojo-0.8.0 → debug_dojo-0.9.0}/src/debug_dojo.egg-info/requires.txt +0 -1
- debug_dojo-0.9.0/tests/test_compare.py +74 -0
- {debug_dojo-0.8.0 → debug_dojo-0.9.0}/tests/test_config.py +6 -1
- {debug_dojo-0.8.0 → debug_dojo-0.9.0}/tests/test_gamification.py +14 -27
- debug_dojo-0.8.0/tests/test_compare.py +0 -75
- {debug_dojo-0.8.0 → debug_dojo-0.9.0}/LICENSE +0 -0
- {debug_dojo-0.8.0 → debug_dojo-0.9.0}/README.md +0 -0
- {debug_dojo-0.8.0 → debug_dojo-0.9.0}/setup.cfg +0 -0
- {debug_dojo-0.8.0 → debug_dojo-0.9.0}/src/debug_dojo/__init__.py +0 -0
- {debug_dojo-0.8.0 → debug_dojo-0.9.0}/src/debug_dojo/__main__.py +0 -0
- {debug_dojo-0.8.0 → debug_dojo-0.9.0}/src/debug_dojo/_cli.py +0 -0
- {debug_dojo-0.8.0 → debug_dojo-0.9.0}/src/debug_dojo/install.py +0 -0
- {debug_dojo-0.8.0 → debug_dojo-0.9.0}/src/debug_dojo/py.typed +0 -0
- {debug_dojo-0.8.0 → debug_dojo-0.9.0}/src/debug_dojo.egg-info/SOURCES.txt +0 -0
- {debug_dojo-0.8.0 → debug_dojo-0.9.0}/src/debug_dojo.egg-info/dependency_links.txt +0 -0
- {debug_dojo-0.8.0 → debug_dojo-0.9.0}/src/debug_dojo.egg-info/entry_points.txt +0 -0
- {debug_dojo-0.8.0 → debug_dojo-0.9.0}/src/debug_dojo.egg-info/top_level.txt +0 -0
- {debug_dojo-0.8.0 → debug_dojo-0.9.0}/tests/test_gamification_config.py +0 -0
- {debug_dojo-0.8.0 → debug_dojo-0.9.0}/tests/test_installers.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: debug-dojo
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: 🏣 debug dojo, a place for zen debugging
|
|
5
5
|
Author-email: bwrob <bartosz.marcin.wroblewski@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -22,7 +22,6 @@ Requires-Python: <3.15,>=3.10
|
|
|
22
22
|
Description-Content-Type: text/markdown
|
|
23
23
|
License-File: LICENSE
|
|
24
24
|
Requires-Dist: dacite>=1.0.0
|
|
25
|
-
Requires-Dist: pyscn>=1.5.2
|
|
26
25
|
Requires-Dist: rich>=10.0.0
|
|
27
26
|
Requires-Dist: tomlkit>=0.7.0
|
|
28
27
|
Requires-Dist: typer>=0.3.0
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
]
|
|
16
16
|
dependencies = [
|
|
17
17
|
"dacite>=1.0.0",
|
|
18
|
-
"pyscn>=1.5.2",
|
|
19
18
|
"rich>=10.0.0",
|
|
20
19
|
"tomlkit>=0.7.0",
|
|
21
20
|
"typer>=0.3.0",
|
|
@@ -32,7 +31,7 @@
|
|
|
32
31
|
name = "debug-dojo"
|
|
33
32
|
readme = "README.md"
|
|
34
33
|
requires-python = ">=3.10,<3.15"
|
|
35
|
-
version = "0.
|
|
34
|
+
version = "0.9.0"
|
|
36
35
|
|
|
37
36
|
[project.urls]
|
|
38
37
|
Changelog = "https://bwrob.github.io/debug-dojo/changelog/"
|
|
@@ -74,7 +73,8 @@
|
|
|
74
73
|
"mkdocstrings-python>=1.17.0",
|
|
75
74
|
"pymdown-extensions>=10.16.1",
|
|
76
75
|
"typst>=0.14.2",
|
|
77
|
-
|
|
76
|
+
"refurb>=2.3.0",
|
|
77
|
+
]
|
|
78
78
|
|
|
79
79
|
|
|
80
80
|
[tool.uv]
|
|
@@ -71,14 +71,24 @@ def get_object_methods(obj: object) -> list[str]:
|
|
|
71
71
|
|
|
72
72
|
|
|
73
73
|
def _is_basic_type(obj: object) -> bool:
|
|
74
|
-
"""Check if the object is a basic Python type.
|
|
74
|
+
"""Check if the object is a basic Python type.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
bool: True if the object is a basic Python type, False otherwise.
|
|
78
|
+
|
|
79
|
+
"""
|
|
75
80
|
return (
|
|
76
81
|
isinstance(obj, (str, int, float, list, dict, tuple, set, bool)) or obj is None
|
|
77
82
|
)
|
|
78
83
|
|
|
79
84
|
|
|
80
85
|
def _get_basic_info(obj: object) -> list[Text]:
|
|
81
|
-
"""Get information for basic types.
|
|
86
|
+
"""Get information for basic types.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
list[Text]: A list of Rich Text objects representing the basic info.
|
|
90
|
+
|
|
91
|
+
"""
|
|
82
92
|
return [
|
|
83
93
|
Text("Value:", style="bold"),
|
|
84
94
|
Text(f" {obj!r}", style="yellow"),
|
|
@@ -88,7 +98,12 @@ def _get_basic_info(obj: object) -> list[Text]:
|
|
|
88
98
|
|
|
89
99
|
|
|
90
100
|
def _format_section(title: str, items: list[str], empty_message: str) -> list[Text]:
|
|
91
|
-
"""Format a section of the inspection output.
|
|
101
|
+
"""Format a section of the inspection output.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
list[Text]: A list of Rich Text objects for the formatted section.
|
|
105
|
+
|
|
106
|
+
"""
|
|
92
107
|
lines: list[Text] = []
|
|
93
108
|
if items:
|
|
94
109
|
lines.append(Text(title, style="bold"))
|
|
@@ -100,7 +115,12 @@ def _format_section(title: str, items: list[str], empty_message: str) -> list[Te
|
|
|
100
115
|
|
|
101
116
|
|
|
102
117
|
def _get_attributes_section(obj: object) -> list[Text]:
|
|
103
|
-
"""Get the attributes section for the object info.
|
|
118
|
+
"""Get the attributes section for the object info.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
list[Text]: A list of Rich Text objects for the attributes section.
|
|
122
|
+
|
|
123
|
+
"""
|
|
104
124
|
return _format_section(
|
|
105
125
|
"Attributes:",
|
|
106
126
|
get_object_attributes(obj),
|
|
@@ -109,7 +129,12 @@ def _get_attributes_section(obj: object) -> list[Text]:
|
|
|
109
129
|
|
|
110
130
|
|
|
111
131
|
def _get_methods_section(obj: object) -> list[Text]:
|
|
112
|
-
"""Get the methods section for the object info.
|
|
132
|
+
"""Get the methods section for the object info.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
list[Text]: A list of Rich Text objects for the methods section.
|
|
136
|
+
|
|
137
|
+
"""
|
|
113
138
|
return _format_section(
|
|
114
139
|
"Methods:",
|
|
115
140
|
[f"{method}()" for method in get_object_methods(obj)],
|
|
@@ -132,8 +157,7 @@ def get_simplified_object_info(obj: object) -> list[Text]:
|
|
|
132
157
|
"""
|
|
133
158
|
info_lines: list[Text] = []
|
|
134
159
|
obj_type: str = type(obj).__name__
|
|
135
|
-
info_lines.
|
|
136
|
-
info_lines.append(Text(""))
|
|
160
|
+
info_lines.extend((Text(f"<class '{obj_type}'>", style="cyan bold"), Text("")))
|
|
137
161
|
|
|
138
162
|
if _is_basic_type(obj):
|
|
139
163
|
info_lines.extend(_get_basic_info(obj))
|
|
@@ -114,7 +114,7 @@ def load_raw_config(config_path: Path) -> JSON:
|
|
|
114
114
|
|
|
115
115
|
# If config is in [tool.debug_dojo] (pyproject.toml), extract it.
|
|
116
116
|
try:
|
|
117
|
-
dojo_config = cast(
|
|
117
|
+
dojo_config = cast(JSON, config_data["tool"]["debug_dojo"])
|
|
118
118
|
except KeyError:
|
|
119
119
|
return {}
|
|
120
120
|
else:
|
|
@@ -154,7 +154,12 @@ class DebugDojoConfigV1:
|
|
|
154
154
|
"""Features to install for debugging."""
|
|
155
155
|
|
|
156
156
|
def update(self) -> DebugDojoConfigV2:
|
|
157
|
-
"""Update the configuration to the latest version.
|
|
157
|
+
"""Update the configuration to the latest version.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
DebugDojoConfigV2: The updated configuration object.
|
|
161
|
+
|
|
162
|
+
"""
|
|
158
163
|
v2_exceptions = ExceptionsConfig(
|
|
159
164
|
rich_traceback=self.features.rich_traceback,
|
|
160
165
|
)
|
|
@@ -131,6 +131,9 @@ def _safe_execute(
|
|
|
131
131
|
target_name (str): The name of the target.
|
|
132
132
|
config (DebugDojoConfig): The configuration object.
|
|
133
133
|
|
|
134
|
+
Raises:
|
|
135
|
+
Exit: If an error occurs or the user interrupts the execution.
|
|
136
|
+
|
|
134
137
|
"""
|
|
135
138
|
try:
|
|
136
139
|
_ = runner(target_name, run_name="__main__")
|
|
@@ -167,10 +170,6 @@ def execute_with_debug(
|
|
|
167
170
|
verbose (bool): If True, print verbose output.
|
|
168
171
|
config (DebugDojoConfig): The debug-dojo configuration.
|
|
169
172
|
|
|
170
|
-
Raises:
|
|
171
|
-
typer.Exit: If the target file is not found, or if an import error occurs,
|
|
172
|
-
or if the script exits with a non-zero code.
|
|
173
|
-
|
|
174
173
|
"""
|
|
175
174
|
_configure_sys_argv(target_name, target_args)
|
|
176
175
|
_install_debug_tools(target_name, target_args, verbose=verbose, config=config)
|
|
@@ -94,7 +94,12 @@ class GamificationManager:
|
|
|
94
94
|
self.stats = self._load_stats()
|
|
95
95
|
|
|
96
96
|
def _load_stats(self) -> DojoStats:
|
|
97
|
-
"""Load stats from disk.
|
|
97
|
+
"""Load stats from disk.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
DojoStats: The loaded user statistics.
|
|
101
|
+
|
|
102
|
+
"""
|
|
98
103
|
if not self.stats_file.exists():
|
|
99
104
|
return DojoStats()
|
|
100
105
|
|
|
@@ -104,17 +109,15 @@ class GamificationManager:
|
|
|
104
109
|
if not isinstance(data, dict):
|
|
105
110
|
return DojoStats()
|
|
106
111
|
|
|
107
|
-
data_dict = cast(
|
|
112
|
+
data_dict = cast(dict[str, object], data)
|
|
108
113
|
|
|
109
|
-
history_raw = cast(
|
|
114
|
+
history_raw = cast(list[dict[str, object]], data_dict.get("history", []))
|
|
110
115
|
history: list[SessionInfo] = []
|
|
111
116
|
for s in history_raw:
|
|
112
117
|
# Support migration from duration_seconds
|
|
113
|
-
duration = float(cast(
|
|
118
|
+
duration = float(cast(float, s.get("duration_minutes", 0.0)))
|
|
114
119
|
if "duration_seconds" in s and duration == 0:
|
|
115
|
-
duration = (
|
|
116
|
-
float(cast("float", s.get("duration_seconds", 0.0))) / 60.0
|
|
117
|
-
)
|
|
120
|
+
duration = float(cast(float, s.get("duration_seconds", 0.0))) / 60.0
|
|
118
121
|
|
|
119
122
|
history.append(
|
|
120
123
|
SessionInfo(
|
|
@@ -125,8 +128,8 @@ class GamificationManager:
|
|
|
125
128
|
)
|
|
126
129
|
|
|
127
130
|
return DojoStats(
|
|
128
|
-
sessions=int(cast(
|
|
129
|
-
bugs_crushed=int(cast(
|
|
131
|
+
sessions=int(cast(int, data_dict.get("sessions", 0))),
|
|
132
|
+
bugs_crushed=int(cast(int, data_dict.get("bugs_crushed", 0))),
|
|
130
133
|
history=history,
|
|
131
134
|
)
|
|
132
135
|
|
|
@@ -52,7 +52,7 @@ def use_pdb(config: PdbConfig) -> None:
|
|
|
52
52
|
import pdb
|
|
53
53
|
|
|
54
54
|
os.environ[BREAKPOINT_ENV_VAR] = config.set_trace_hook
|
|
55
|
-
sys.breakpointhook = cast(
|
|
55
|
+
sys.breakpointhook = cast(Any, pdb.set_trace) # pyright: ignore[reportExplicitAny]
|
|
56
56
|
|
|
57
57
|
|
|
58
58
|
def use_pudb(config: PudbConfig) -> None:
|
|
@@ -72,7 +72,7 @@ def use_pudb(config: PudbConfig) -> None:
|
|
|
72
72
|
return
|
|
73
73
|
|
|
74
74
|
os.environ[BREAKPOINT_ENV_VAR] = config.set_trace_hook
|
|
75
|
-
sys.breakpointhook = cast(
|
|
75
|
+
sys.breakpointhook = cast(Any, pudb.set_trace) # pyright: ignore[reportExplicitAny]
|
|
76
76
|
|
|
77
77
|
|
|
78
78
|
def use_ipdb(config: IpdbConfig) -> None:
|
|
@@ -93,7 +93,7 @@ def use_ipdb(config: IpdbConfig) -> None:
|
|
|
93
93
|
|
|
94
94
|
os.environ[BREAKPOINT_ENV_VAR] = config.set_trace_hook
|
|
95
95
|
os.environ[IPDB_CONTEXT_SIZE] = str(config.context_lines)
|
|
96
|
-
sys.breakpointhook = cast(
|
|
96
|
+
sys.breakpointhook = cast(Any, ipdb.set_trace) # pyright: ignore[reportExplicitAny]
|
|
97
97
|
|
|
98
98
|
|
|
99
99
|
def use_debugpy(config: DebugpyConfig) -> None:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: debug-dojo
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: 🏣 debug dojo, a place for zen debugging
|
|
5
5
|
Author-email: bwrob <bartosz.marcin.wroblewski@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -22,7 +22,6 @@ Requires-Python: <3.15,>=3.10
|
|
|
22
22
|
Description-Content-Type: text/markdown
|
|
23
23
|
License-File: LICENSE
|
|
24
24
|
Requires-Dist: dacite>=1.0.0
|
|
25
|
-
Requires-Dist: pyscn>=1.5.2
|
|
26
25
|
Requires-Dist: rich>=10.0.0
|
|
27
26
|
Requires-Dist: tomlkit>=0.7.0
|
|
28
27
|
Requires-Dist: typer>=0.3.0
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Test the compare utilities."""
|
|
2
|
+
|
|
3
|
+
from rich.text import Text
|
|
4
|
+
|
|
5
|
+
from debug_dojo._compare import (
|
|
6
|
+
get_object_attributes,
|
|
7
|
+
get_object_methods,
|
|
8
|
+
get_simplified_object_info,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_get_object_attributes() -> None:
|
|
13
|
+
"""Test that object attributes are correctly extracted."""
|
|
14
|
+
|
|
15
|
+
class MyClass:
|
|
16
|
+
"""A simple class for testing."""
|
|
17
|
+
|
|
18
|
+
x: int = 10
|
|
19
|
+
y: str = "hello"
|
|
20
|
+
|
|
21
|
+
def my_method(self) -> None:
|
|
22
|
+
"""Do something."""
|
|
23
|
+
|
|
24
|
+
obj = MyClass()
|
|
25
|
+
attributes = get_object_attributes(obj)
|
|
26
|
+
assert "x=10" in attributes
|
|
27
|
+
assert "y='hello'" in attributes
|
|
28
|
+
assert "my_method" not in attributes
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_get_object_methods() -> None:
|
|
32
|
+
"""Test that object methods are correctly extracted."""
|
|
33
|
+
|
|
34
|
+
class MyClass:
|
|
35
|
+
"""A simple class for testing."""
|
|
36
|
+
|
|
37
|
+
x: int = 10
|
|
38
|
+
|
|
39
|
+
def my_method(self) -> None:
|
|
40
|
+
"""Do something."""
|
|
41
|
+
|
|
42
|
+
def _private_method(self) -> None:
|
|
43
|
+
"""Do something else."""
|
|
44
|
+
|
|
45
|
+
obj = MyClass()
|
|
46
|
+
methods = get_object_methods(obj)
|
|
47
|
+
assert "my_method" in methods
|
|
48
|
+
assert "_private_method" not in methods
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_get_simplified_object_info_basic_type() -> None:
|
|
52
|
+
"""Test simplified object info for basic types."""
|
|
53
|
+
info = get_simplified_object_info(123)
|
|
54
|
+
assert any(isinstance(line, Text) and "123" in line.plain for line in info)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_get_simplified_object_info_custom_object() -> None:
|
|
58
|
+
"""Test simplified object info for custom objects."""
|
|
59
|
+
|
|
60
|
+
class MyClass:
|
|
61
|
+
"""A simple class for testing."""
|
|
62
|
+
|
|
63
|
+
x: int = 10
|
|
64
|
+
|
|
65
|
+
def my_method(self) -> None:
|
|
66
|
+
"""Do something."""
|
|
67
|
+
|
|
68
|
+
obj = MyClass()
|
|
69
|
+
info = get_simplified_object_info(obj)
|
|
70
|
+
|
|
71
|
+
# Check that basic info is present
|
|
72
|
+
assert any(isinstance(line, Text) and "MyClass" in line.plain for line in info)
|
|
73
|
+
assert any(isinstance(line, Text) and "x=10" in line.plain for line in info)
|
|
74
|
+
assert any(isinstance(line, Text) and "my_method" in line.plain for line in info)
|
|
@@ -17,7 +17,12 @@ from debug_dojo._config_models import DebugDojoConfig, DebuggerType
|
|
|
17
17
|
|
|
18
18
|
@pytest.fixture
|
|
19
19
|
def mock_config_file(tmp_path: Path) -> Path:
|
|
20
|
-
"""Create a mock config file.
|
|
20
|
+
"""Create a mock config file.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Path: The path to the created mock configuration file.
|
|
24
|
+
|
|
25
|
+
"""
|
|
21
26
|
config_content = """
|
|
22
27
|
[debuggers]
|
|
23
28
|
default = "pudb"
|
|
@@ -11,7 +11,12 @@ from debug_dojo._gamification import BELTS, GamificationManager, SessionInfo
|
|
|
11
11
|
|
|
12
12
|
@pytest.fixture
|
|
13
13
|
def temp_stats_file(tmp_path: Path) -> Path:
|
|
14
|
-
"""Fixture providing a temporary stats file path.
|
|
14
|
+
"""Fixture providing a temporary stats file path.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
Path: The path to a temporary statistics file.
|
|
18
|
+
|
|
19
|
+
"""
|
|
15
20
|
return tmp_path / "stats.json"
|
|
16
21
|
|
|
17
22
|
|
|
@@ -26,7 +31,7 @@ def test_initial_stats(temp_stats_file: Path) -> None:
|
|
|
26
31
|
assert belt_name == "White Belt"
|
|
27
32
|
assert rank == 0
|
|
28
33
|
assert next_sessions == BELTS[1][1]
|
|
29
|
-
assert next_mins == float(BELTS[1][2])
|
|
34
|
+
assert next_mins == pytest.approx(float(BELTS[1][2])) # pyright: ignore[reportUnknownMemberType]
|
|
30
35
|
|
|
31
36
|
|
|
32
37
|
def test_increment_session(temp_stats_file: Path) -> None:
|
|
@@ -34,39 +39,28 @@ def test_increment_session(temp_stats_file: Path) -> None:
|
|
|
34
39
|
manager = GamificationManager(stats_path=temp_stats_file)
|
|
35
40
|
|
|
36
41
|
duration_seconds = 120.0
|
|
37
|
-
|
|
38
42
|
expected_minutes = 2.0
|
|
39
|
-
|
|
40
43
|
expected_command = "dojo run script.py"
|
|
41
|
-
|
|
42
44
|
manager.increment_session(
|
|
43
45
|
duration_seconds=duration_seconds, command=expected_command
|
|
44
46
|
)
|
|
45
47
|
|
|
46
48
|
assert manager.stats.sessions == 1
|
|
47
|
-
|
|
48
49
|
assert len(manager.stats.history) == 1
|
|
49
|
-
|
|
50
50
|
assert manager.stats.history[0].duration_minutes == expected_minutes
|
|
51
|
-
|
|
52
51
|
assert manager.stats.history[0].command == expected_command
|
|
53
|
-
|
|
54
52
|
assert manager.stats.total_minutes == expected_minutes
|
|
55
53
|
|
|
56
54
|
# Verify file persistence
|
|
57
|
-
|
|
58
55
|
assert temp_stats_file.exists()
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
data: dict[str, object] = cast(
|
|
57
|
+
dict[str, object], json.loads(temp_stats_file.read_text(encoding="utf-8"))
|
|
58
|
+
)
|
|
62
59
|
assert data["sessions"] == 1
|
|
63
60
|
|
|
64
|
-
history = cast(
|
|
65
|
-
|
|
61
|
+
history = cast(list[dict[str, object]], data["history"])
|
|
66
62
|
assert len(history) == 1
|
|
67
|
-
|
|
68
63
|
assert history[0]["duration_minutes"] == expected_minutes
|
|
69
|
-
|
|
70
64
|
assert history[0]["command"] == expected_command
|
|
71
65
|
|
|
72
66
|
|
|
@@ -92,18 +86,13 @@ def test_load_existing_stats(temp_stats_file: Path) -> None:
|
|
|
92
86
|
],
|
|
93
87
|
}
|
|
94
88
|
|
|
95
|
-
_ = temp_stats_file.write_text(json.dumps(data))
|
|
89
|
+
_ = temp_stats_file.write_text(json.dumps(data), encoding="utf-8")
|
|
96
90
|
|
|
97
91
|
manager = GamificationManager(stats_path=temp_stats_file)
|
|
98
|
-
|
|
99
92
|
assert manager.stats.sessions == expected_sessions
|
|
100
|
-
|
|
101
93
|
assert manager.stats.bugs_crushed == expected_bugs
|
|
102
|
-
|
|
103
94
|
assert len(manager.stats.history) == 1
|
|
104
|
-
|
|
105
95
|
assert manager.stats.history[0].command == "dojo run test.py"
|
|
106
|
-
|
|
107
96
|
assert manager.stats.total_minutes == expected_mins
|
|
108
97
|
|
|
109
98
|
|
|
@@ -122,10 +111,8 @@ def test_load_legacy_stats(temp_stats_file: Path) -> None:
|
|
|
122
111
|
],
|
|
123
112
|
}
|
|
124
113
|
|
|
125
|
-
_ = temp_stats_file.write_text(json.dumps(data))
|
|
126
|
-
|
|
114
|
+
_ = temp_stats_file.write_text(json.dumps(data), encoding="utf-8")
|
|
127
115
|
manager = GamificationManager(stats_path=temp_stats_file)
|
|
128
|
-
|
|
129
116
|
assert manager.stats.total_minutes == expected_mins
|
|
130
117
|
|
|
131
118
|
|
|
@@ -153,7 +140,7 @@ def test_belt_progression(temp_stats_file: Path) -> None:
|
|
|
153
140
|
|
|
154
141
|
def test_corrupt_stats_file(temp_stats_file: Path) -> None:
|
|
155
142
|
"""Test that corrupt stats file is handled gracefully."""
|
|
156
|
-
_ = temp_stats_file.write_text("invalid json")
|
|
143
|
+
_ = temp_stats_file.write_text("invalid json", encoding="utf-8")
|
|
157
144
|
|
|
158
145
|
manager = GamificationManager(stats_path=temp_stats_file)
|
|
159
146
|
assert manager.stats.sessions == 0
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
"""Test the `_compare` module."""
|
|
2
|
-
|
|
3
|
-
from rich.text import Text
|
|
4
|
-
|
|
5
|
-
from debug_dojo._compare import (
|
|
6
|
-
get_object_attributes,
|
|
7
|
-
get_object_methods,
|
|
8
|
-
get_simplified_object_info,
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class TestCompare:
|
|
13
|
-
"""Test the compare utilities."""
|
|
14
|
-
|
|
15
|
-
def test_get_object_attributes(self) -> None:
|
|
16
|
-
"""Test that object attributes are correctly extracted."""
|
|
17
|
-
|
|
18
|
-
class MyClass:
|
|
19
|
-
"""A simple class for testing."""
|
|
20
|
-
|
|
21
|
-
x: int = 10
|
|
22
|
-
y: str = "hello"
|
|
23
|
-
|
|
24
|
-
def my_method(self) -> None:
|
|
25
|
-
"""Do something."""
|
|
26
|
-
|
|
27
|
-
obj = MyClass()
|
|
28
|
-
attributes = get_object_attributes(obj)
|
|
29
|
-
assert "x=10" in attributes
|
|
30
|
-
assert "y='hello'" in attributes
|
|
31
|
-
assert "my_method" not in attributes
|
|
32
|
-
|
|
33
|
-
def test_get_object_methods(self) -> None:
|
|
34
|
-
"""Test that object methods are correctly extracted."""
|
|
35
|
-
|
|
36
|
-
class MyClass:
|
|
37
|
-
"""A simple class for testing."""
|
|
38
|
-
|
|
39
|
-
x: int = 10
|
|
40
|
-
|
|
41
|
-
def my_method(self) -> None:
|
|
42
|
-
"""Do something."""
|
|
43
|
-
|
|
44
|
-
def _private_method(self) -> None:
|
|
45
|
-
"""Do something else."""
|
|
46
|
-
|
|
47
|
-
obj = MyClass()
|
|
48
|
-
methods = get_object_methods(obj)
|
|
49
|
-
assert "my_method" in methods
|
|
50
|
-
assert "_private_method" not in methods
|
|
51
|
-
|
|
52
|
-
def test_get_simplified_object_info_basic_type(self) -> None:
|
|
53
|
-
"""Test simplified object info for basic types."""
|
|
54
|
-
info = get_simplified_object_info(123)
|
|
55
|
-
assert any(isinstance(line, Text) and "123" in line.plain for line in info)
|
|
56
|
-
|
|
57
|
-
def test_get_simplified_object_info_custom_object(self) -> None:
|
|
58
|
-
"""Test simplified object info for custom objects."""
|
|
59
|
-
|
|
60
|
-
class MyClass:
|
|
61
|
-
"""A simple class for testing."""
|
|
62
|
-
|
|
63
|
-
x: int = 10
|
|
64
|
-
|
|
65
|
-
def my_method(self) -> None:
|
|
66
|
-
"""Do something."""
|
|
67
|
-
|
|
68
|
-
obj = MyClass()
|
|
69
|
-
info = get_simplified_object_info(obj)
|
|
70
|
-
plain_info = "\n".join(line.plain for line in info)
|
|
71
|
-
assert "<class 'MyClass'>" in plain_info
|
|
72
|
-
assert "Attributes:" in plain_info
|
|
73
|
-
assert "x=10" in plain_info
|
|
74
|
-
assert "Methods:" in plain_info
|
|
75
|
-
assert "my_method()" in plain_info
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|