hud-python 0.4.1__py3-none-any.whl → 0.4.3__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.
Potentially problematic release.
This version of hud-python might be problematic. Click here for more details.
- hud/__init__.py +22 -22
- hud/agents/__init__.py +13 -15
- hud/agents/base.py +599 -599
- hud/agents/claude.py +373 -373
- hud/agents/langchain.py +261 -250
- hud/agents/misc/__init__.py +7 -7
- hud/agents/misc/response_agent.py +82 -80
- hud/agents/openai.py +352 -352
- hud/agents/openai_chat_generic.py +154 -154
- hud/agents/tests/__init__.py +1 -1
- hud/agents/tests/test_base.py +742 -742
- hud/agents/tests/test_claude.py +324 -324
- hud/agents/tests/test_client.py +363 -363
- hud/agents/tests/test_openai.py +237 -237
- hud/cli/__init__.py +617 -617
- hud/cli/__main__.py +8 -8
- hud/cli/analyze.py +371 -371
- hud/cli/analyze_metadata.py +230 -230
- hud/cli/build.py +498 -427
- hud/cli/clone.py +185 -185
- hud/cli/cursor.py +92 -92
- hud/cli/debug.py +392 -392
- hud/cli/docker_utils.py +83 -83
- hud/cli/init.py +280 -281
- hud/cli/interactive.py +353 -353
- hud/cli/mcp_server.py +764 -756
- hud/cli/pull.py +330 -336
- hud/cli/push.py +404 -370
- hud/cli/remote_runner.py +311 -311
- hud/cli/runner.py +160 -160
- hud/cli/tests/__init__.py +3 -3
- hud/cli/tests/test_analyze.py +284 -284
- hud/cli/tests/test_cli_init.py +265 -265
- hud/cli/tests/test_cli_main.py +27 -27
- hud/cli/tests/test_clone.py +142 -142
- hud/cli/tests/test_cursor.py +253 -253
- hud/cli/tests/test_debug.py +453 -453
- hud/cli/tests/test_mcp_server.py +139 -139
- hud/cli/tests/test_utils.py +388 -388
- hud/cli/utils.py +263 -263
- hud/clients/README.md +143 -143
- hud/clients/__init__.py +16 -16
- hud/clients/base.py +378 -379
- hud/clients/fastmcp.py +222 -222
- hud/clients/mcp_use.py +298 -278
- hud/clients/tests/__init__.py +1 -1
- hud/clients/tests/test_client_integration.py +111 -111
- hud/clients/tests/test_fastmcp.py +342 -342
- hud/clients/tests/test_protocol.py +188 -188
- hud/clients/utils/__init__.py +1 -1
- hud/clients/utils/retry_transport.py +160 -160
- hud/datasets.py +327 -322
- hud/misc/__init__.py +1 -1
- hud/misc/claude_plays_pokemon.py +292 -292
- hud/otel/__init__.py +35 -35
- hud/otel/collector.py +142 -142
- hud/otel/config.py +164 -164
- hud/otel/context.py +536 -536
- hud/otel/exporters.py +366 -366
- hud/otel/instrumentation.py +97 -97
- hud/otel/processors.py +118 -118
- hud/otel/tests/__init__.py +1 -1
- hud/otel/tests/test_processors.py +197 -197
- hud/server/__init__.py +5 -5
- hud/server/context.py +114 -114
- hud/server/helper/__init__.py +5 -5
- hud/server/low_level.py +132 -132
- hud/server/server.py +170 -166
- hud/server/tests/__init__.py +3 -3
- hud/settings.py +73 -73
- hud/shared/__init__.py +5 -5
- hud/shared/exceptions.py +180 -180
- hud/shared/requests.py +264 -264
- hud/shared/tests/test_exceptions.py +157 -157
- hud/shared/tests/test_requests.py +275 -275
- hud/telemetry/__init__.py +25 -25
- hud/telemetry/instrument.py +379 -379
- hud/telemetry/job.py +309 -309
- hud/telemetry/replay.py +74 -74
- hud/telemetry/trace.py +83 -83
- hud/tools/__init__.py +33 -33
- hud/tools/base.py +365 -365
- hud/tools/bash.py +161 -161
- hud/tools/computer/__init__.py +15 -15
- hud/tools/computer/anthropic.py +437 -437
- hud/tools/computer/hud.py +376 -376
- hud/tools/computer/openai.py +295 -295
- hud/tools/computer/settings.py +82 -82
- hud/tools/edit.py +314 -314
- hud/tools/executors/__init__.py +30 -30
- hud/tools/executors/base.py +539 -539
- hud/tools/executors/pyautogui.py +621 -621
- hud/tools/executors/tests/__init__.py +1 -1
- hud/tools/executors/tests/test_base_executor.py +338 -338
- hud/tools/executors/tests/test_pyautogui_executor.py +165 -165
- hud/tools/executors/xdo.py +511 -511
- hud/tools/playwright.py +412 -412
- hud/tools/tests/__init__.py +3 -3
- hud/tools/tests/test_base.py +282 -282
- hud/tools/tests/test_bash.py +158 -158
- hud/tools/tests/test_bash_extended.py +197 -197
- hud/tools/tests/test_computer.py +425 -425
- hud/tools/tests/test_computer_actions.py +34 -34
- hud/tools/tests/test_edit.py +259 -259
- hud/tools/tests/test_init.py +27 -27
- hud/tools/tests/test_playwright_tool.py +183 -183
- hud/tools/tests/test_tools.py +145 -145
- hud/tools/tests/test_utils.py +156 -156
- hud/tools/types.py +72 -72
- hud/tools/utils.py +50 -50
- hud/types.py +136 -136
- hud/utils/__init__.py +10 -10
- hud/utils/async_utils.py +65 -65
- hud/utils/design.py +236 -168
- hud/utils/mcp.py +55 -55
- hud/utils/progress.py +149 -149
- hud/utils/telemetry.py +66 -66
- hud/utils/tests/test_async_utils.py +173 -173
- hud/utils/tests/test_init.py +17 -17
- hud/utils/tests/test_progress.py +261 -261
- hud/utils/tests/test_telemetry.py +82 -82
- hud/utils/tests/test_version.py +8 -8
- hud/version.py +7 -7
- {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/METADATA +10 -8
- hud_python-0.4.3.dist-info/RECORD +131 -0
- {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/licenses/LICENSE +21 -21
- hud/agents/art.py +0 -101
- hud_python-0.4.1.dist-info/RECORD +0 -132
- {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/WHEEL +0 -0
- {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/entry_points.txt +0 -0
hud/utils/tests/test_progress.py
CHANGED
|
@@ -1,261 +1,261 @@
|
|
|
1
|
-
"""Tests for the progress tracking utilities."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import pytest
|
|
6
|
-
|
|
7
|
-
from hud.utils.progress import StepProgressTracker
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
@pytest.fixture
|
|
11
|
-
def tracker():
|
|
12
|
-
return StepProgressTracker(total_tasks=2, max_steps_per_task=10)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def test_invalid_inputs_init():
|
|
16
|
-
with pytest.raises(ValueError, match="total_tasks must be positive"):
|
|
17
|
-
StepProgressTracker(total_tasks=0, max_steps_per_task=10)
|
|
18
|
-
|
|
19
|
-
with pytest.raises(ValueError, match="max_steps_per_task must be positive"):
|
|
20
|
-
StepProgressTracker(total_tasks=5, max_steps_per_task=0)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def test_start_task(tracker):
|
|
24
|
-
assert tracker.start_time is None
|
|
25
|
-
assert tracker._tasks_started == 0
|
|
26
|
-
|
|
27
|
-
tracker.start_task("task1")
|
|
28
|
-
|
|
29
|
-
assert tracker.start_time is not None
|
|
30
|
-
assert tracker._tasks_started == 1
|
|
31
|
-
assert tracker._task_steps["task1"] == 0
|
|
32
|
-
assert not tracker._finished_tasks["task1"]
|
|
33
|
-
|
|
34
|
-
tracker.start_task("task2")
|
|
35
|
-
assert tracker._tasks_started == 2
|
|
36
|
-
assert tracker._task_steps["task2"] == 0
|
|
37
|
-
assert not tracker._finished_tasks["task2"]
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def test_increment_step(tracker):
|
|
41
|
-
tracker.start_task("task1")
|
|
42
|
-
assert tracker.current_total_steps == 0
|
|
43
|
-
|
|
44
|
-
tracker.increment_step("task1")
|
|
45
|
-
assert tracker._task_steps["task1"] == 1
|
|
46
|
-
assert tracker.current_total_steps == 1
|
|
47
|
-
|
|
48
|
-
tracker.increment_step("task1")
|
|
49
|
-
tracker.increment_step("task1")
|
|
50
|
-
assert tracker._task_steps["task1"] == 3
|
|
51
|
-
assert tracker.current_total_steps == 3
|
|
52
|
-
|
|
53
|
-
tracker.start_task("task2")
|
|
54
|
-
tracker.increment_step("task2")
|
|
55
|
-
assert tracker._task_steps["task2"] == 1
|
|
56
|
-
assert tracker.current_total_steps == 4
|
|
57
|
-
|
|
58
|
-
tracker.finish_task("task1")
|
|
59
|
-
initial_steps = tracker.current_total_steps
|
|
60
|
-
tracker.increment_step("task1")
|
|
61
|
-
assert tracker.current_total_steps == initial_steps
|
|
62
|
-
|
|
63
|
-
for _ in range(15):
|
|
64
|
-
tracker.increment_step("task2")
|
|
65
|
-
assert tracker._task_steps["task2"] <= tracker.max_steps_per_task
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def test_finish_task(tracker):
|
|
69
|
-
tracker.start_task("task1")
|
|
70
|
-
tracker.start_task("task2")
|
|
71
|
-
|
|
72
|
-
tracker.increment_step("task1")
|
|
73
|
-
tracker.increment_step("task1")
|
|
74
|
-
initial_steps = tracker._task_steps["task1"]
|
|
75
|
-
|
|
76
|
-
tracker.finish_task("task1")
|
|
77
|
-
|
|
78
|
-
assert tracker._finished_tasks["task1"]
|
|
79
|
-
assert tracker._tasks_finished == 1
|
|
80
|
-
assert tracker._task_steps["task1"] == tracker.max_steps_per_task
|
|
81
|
-
assert tracker.current_total_steps > initial_steps
|
|
82
|
-
|
|
83
|
-
current_steps = tracker.current_total_steps
|
|
84
|
-
tracker.finish_task("task1")
|
|
85
|
-
assert tracker._tasks_finished == 1
|
|
86
|
-
assert tracker.current_total_steps == current_steps
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
def test_get_progress(tracker):
|
|
90
|
-
steps, total, percentage = tracker.get_progress()
|
|
91
|
-
assert steps == 0
|
|
92
|
-
assert total == tracker.total_potential_steps
|
|
93
|
-
assert percentage == 0.0
|
|
94
|
-
|
|
95
|
-
tracker.start_task("task1")
|
|
96
|
-
tracker.increment_step("task1")
|
|
97
|
-
steps, total, percentage = tracker.get_progress()
|
|
98
|
-
assert steps == 1
|
|
99
|
-
assert total == tracker.total_potential_steps
|
|
100
|
-
assert percentage == (1 / tracker.total_potential_steps) * 100
|
|
101
|
-
|
|
102
|
-
tracker.finish_task("task1")
|
|
103
|
-
steps, total, percentage = tracker.get_progress()
|
|
104
|
-
assert steps == tracker.max_steps_per_task
|
|
105
|
-
assert total == tracker.total_potential_steps
|
|
106
|
-
assert percentage == (tracker.max_steps_per_task / tracker.total_potential_steps) * 100
|
|
107
|
-
|
|
108
|
-
tracker.start_task("task2")
|
|
109
|
-
tracker.finish_task("task2")
|
|
110
|
-
steps, total, percentage = tracker.get_progress()
|
|
111
|
-
assert steps == tracker.total_potential_steps
|
|
112
|
-
assert percentage == 100.0
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def test_get_stats_no_progress(tracker):
|
|
116
|
-
from unittest.mock import patch
|
|
117
|
-
|
|
118
|
-
rate, eta = tracker.get_stats()
|
|
119
|
-
assert rate == 0.0
|
|
120
|
-
assert eta is None
|
|
121
|
-
|
|
122
|
-
with patch("time.monotonic", return_value=100.0):
|
|
123
|
-
tracker.start_task("task1")
|
|
124
|
-
|
|
125
|
-
rate, eta = tracker.get_stats()
|
|
126
|
-
assert rate == 0.0
|
|
127
|
-
assert eta is None
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
def test_get_stats_with_progress():
|
|
131
|
-
from unittest.mock import patch
|
|
132
|
-
|
|
133
|
-
with patch("time.monotonic") as mock_time:
|
|
134
|
-
mock_time.return_value = 100.0
|
|
135
|
-
|
|
136
|
-
tracker = StepProgressTracker(total_tasks=1, max_steps_per_task=10)
|
|
137
|
-
tracker.start_task("task1")
|
|
138
|
-
|
|
139
|
-
mock_time.return_value = 160.0
|
|
140
|
-
for _ in range(5):
|
|
141
|
-
tracker.increment_step("task1")
|
|
142
|
-
|
|
143
|
-
rate, eta = tracker.get_stats()
|
|
144
|
-
|
|
145
|
-
assert rate == pytest.approx(5.0)
|
|
146
|
-
assert eta == pytest.approx(60.0)
|
|
147
|
-
|
|
148
|
-
for _ in range(5):
|
|
149
|
-
tracker.increment_step("task1")
|
|
150
|
-
|
|
151
|
-
rate, eta = tracker.get_stats()
|
|
152
|
-
assert rate == pytest.approx(10.0)
|
|
153
|
-
assert eta == pytest.approx(0.0)
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
def test_is_finished(tracker):
|
|
157
|
-
assert not tracker.is_finished()
|
|
158
|
-
|
|
159
|
-
tracker.start_task("task1")
|
|
160
|
-
tracker.finish_task("task1")
|
|
161
|
-
assert not tracker.is_finished()
|
|
162
|
-
|
|
163
|
-
tracker.start_task("task2")
|
|
164
|
-
tracker.finish_task("task2")
|
|
165
|
-
assert tracker.is_finished()
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
def test_display(tracker):
|
|
169
|
-
from unittest.mock import patch
|
|
170
|
-
|
|
171
|
-
with patch("time.monotonic") as mock_time:
|
|
172
|
-
mock_time.return_value = 100.0
|
|
173
|
-
tracker.start_task("task1")
|
|
174
|
-
|
|
175
|
-
mock_time.return_value = 130.0
|
|
176
|
-
tracker.increment_step("task1")
|
|
177
|
-
tracker.increment_step("task1")
|
|
178
|
-
|
|
179
|
-
display_str = tracker.display()
|
|
180
|
-
|
|
181
|
-
assert "%" in display_str
|
|
182
|
-
assert "2/20" in display_str
|
|
183
|
-
assert "0:30" in display_str
|
|
184
|
-
assert "steps/min" in display_str
|
|
185
|
-
|
|
186
|
-
tracker.finish_task("task1")
|
|
187
|
-
display_str = tracker.display()
|
|
188
|
-
assert "10/20" in display_str
|
|
189
|
-
|
|
190
|
-
tracker.start_task("task2")
|
|
191
|
-
tracker.finish_task("task2")
|
|
192
|
-
display_str = tracker.display()
|
|
193
|
-
assert "100%" in display_str
|
|
194
|
-
assert "20/20" in display_str
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
def test_complex_workflow():
|
|
198
|
-
tracker = StepProgressTracker(total_tasks=5, max_steps_per_task=20)
|
|
199
|
-
|
|
200
|
-
for i in range(5):
|
|
201
|
-
tracker.start_task(f"task{i}")
|
|
202
|
-
|
|
203
|
-
for _ in range(10):
|
|
204
|
-
tracker.increment_step("task0")
|
|
205
|
-
|
|
206
|
-
for _ in range(5):
|
|
207
|
-
tracker.increment_step("task1")
|
|
208
|
-
|
|
209
|
-
tracker.finish_task("task2")
|
|
210
|
-
|
|
211
|
-
for _ in range(15):
|
|
212
|
-
tracker.increment_step("task3")
|
|
213
|
-
|
|
214
|
-
tracker.finish_task("task3")
|
|
215
|
-
|
|
216
|
-
steps, total, percentage = tracker.get_progress()
|
|
217
|
-
expected_steps = 10 + 5 + 20 + 20 + 0
|
|
218
|
-
assert steps == expected_steps
|
|
219
|
-
assert total == 5 * 20
|
|
220
|
-
assert percentage == (expected_steps / total) * 100
|
|
221
|
-
|
|
222
|
-
assert tracker._tasks_finished == 2
|
|
223
|
-
assert not tracker.is_finished()
|
|
224
|
-
|
|
225
|
-
tracker.finish_task("task0")
|
|
226
|
-
tracker.finish_task("task1")
|
|
227
|
-
tracker.finish_task("task4")
|
|
228
|
-
|
|
229
|
-
assert tracker.is_finished()
|
|
230
|
-
assert tracker.get_progress()[2] == 100.0
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
def test_display_eta_when_finished(tracker):
|
|
234
|
-
from unittest.mock import patch
|
|
235
|
-
|
|
236
|
-
"""Test that ETA shows 0:00 when progress is finished."""
|
|
237
|
-
|
|
238
|
-
with patch("time.monotonic") as mock_time:
|
|
239
|
-
mock_time.return_value = 100.0
|
|
240
|
-
|
|
241
|
-
# Start and complete all tasks
|
|
242
|
-
tracker.start_task("task1")
|
|
243
|
-
for _ in range(10):
|
|
244
|
-
tracker.increment_step("task1")
|
|
245
|
-
tracker.finish_task("task1")
|
|
246
|
-
|
|
247
|
-
tracker.start_task("task2")
|
|
248
|
-
for _ in range(10):
|
|
249
|
-
tracker.increment_step("task2")
|
|
250
|
-
tracker.finish_task("task2")
|
|
251
|
-
|
|
252
|
-
# Some time has passed
|
|
253
|
-
mock_time.return_value = 120.0
|
|
254
|
-
|
|
255
|
-
display = tracker.display()
|
|
256
|
-
|
|
257
|
-
# When finished, ETA should be 0:00 (not ??:??)
|
|
258
|
-
assert tracker.is_finished()
|
|
259
|
-
assert "0:00" in display
|
|
260
|
-
assert "100%" in display
|
|
261
|
-
assert "20/20" in display
|
|
1
|
+
"""Tests for the progress tracking utilities."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from hud.utils.progress import StepProgressTracker
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.fixture
|
|
11
|
+
def tracker():
|
|
12
|
+
return StepProgressTracker(total_tasks=2, max_steps_per_task=10)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_invalid_inputs_init():
|
|
16
|
+
with pytest.raises(ValueError, match="total_tasks must be positive"):
|
|
17
|
+
StepProgressTracker(total_tasks=0, max_steps_per_task=10)
|
|
18
|
+
|
|
19
|
+
with pytest.raises(ValueError, match="max_steps_per_task must be positive"):
|
|
20
|
+
StepProgressTracker(total_tasks=5, max_steps_per_task=0)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_start_task(tracker):
|
|
24
|
+
assert tracker.start_time is None
|
|
25
|
+
assert tracker._tasks_started == 0
|
|
26
|
+
|
|
27
|
+
tracker.start_task("task1")
|
|
28
|
+
|
|
29
|
+
assert tracker.start_time is not None
|
|
30
|
+
assert tracker._tasks_started == 1
|
|
31
|
+
assert tracker._task_steps["task1"] == 0
|
|
32
|
+
assert not tracker._finished_tasks["task1"]
|
|
33
|
+
|
|
34
|
+
tracker.start_task("task2")
|
|
35
|
+
assert tracker._tasks_started == 2
|
|
36
|
+
assert tracker._task_steps["task2"] == 0
|
|
37
|
+
assert not tracker._finished_tasks["task2"]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_increment_step(tracker):
|
|
41
|
+
tracker.start_task("task1")
|
|
42
|
+
assert tracker.current_total_steps == 0
|
|
43
|
+
|
|
44
|
+
tracker.increment_step("task1")
|
|
45
|
+
assert tracker._task_steps["task1"] == 1
|
|
46
|
+
assert tracker.current_total_steps == 1
|
|
47
|
+
|
|
48
|
+
tracker.increment_step("task1")
|
|
49
|
+
tracker.increment_step("task1")
|
|
50
|
+
assert tracker._task_steps["task1"] == 3
|
|
51
|
+
assert tracker.current_total_steps == 3
|
|
52
|
+
|
|
53
|
+
tracker.start_task("task2")
|
|
54
|
+
tracker.increment_step("task2")
|
|
55
|
+
assert tracker._task_steps["task2"] == 1
|
|
56
|
+
assert tracker.current_total_steps == 4
|
|
57
|
+
|
|
58
|
+
tracker.finish_task("task1")
|
|
59
|
+
initial_steps = tracker.current_total_steps
|
|
60
|
+
tracker.increment_step("task1")
|
|
61
|
+
assert tracker.current_total_steps == initial_steps
|
|
62
|
+
|
|
63
|
+
for _ in range(15):
|
|
64
|
+
tracker.increment_step("task2")
|
|
65
|
+
assert tracker._task_steps["task2"] <= tracker.max_steps_per_task
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_finish_task(tracker):
|
|
69
|
+
tracker.start_task("task1")
|
|
70
|
+
tracker.start_task("task2")
|
|
71
|
+
|
|
72
|
+
tracker.increment_step("task1")
|
|
73
|
+
tracker.increment_step("task1")
|
|
74
|
+
initial_steps = tracker._task_steps["task1"]
|
|
75
|
+
|
|
76
|
+
tracker.finish_task("task1")
|
|
77
|
+
|
|
78
|
+
assert tracker._finished_tasks["task1"]
|
|
79
|
+
assert tracker._tasks_finished == 1
|
|
80
|
+
assert tracker._task_steps["task1"] == tracker.max_steps_per_task
|
|
81
|
+
assert tracker.current_total_steps > initial_steps
|
|
82
|
+
|
|
83
|
+
current_steps = tracker.current_total_steps
|
|
84
|
+
tracker.finish_task("task1")
|
|
85
|
+
assert tracker._tasks_finished == 1
|
|
86
|
+
assert tracker.current_total_steps == current_steps
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_get_progress(tracker):
|
|
90
|
+
steps, total, percentage = tracker.get_progress()
|
|
91
|
+
assert steps == 0
|
|
92
|
+
assert total == tracker.total_potential_steps
|
|
93
|
+
assert percentage == 0.0
|
|
94
|
+
|
|
95
|
+
tracker.start_task("task1")
|
|
96
|
+
tracker.increment_step("task1")
|
|
97
|
+
steps, total, percentage = tracker.get_progress()
|
|
98
|
+
assert steps == 1
|
|
99
|
+
assert total == tracker.total_potential_steps
|
|
100
|
+
assert percentage == (1 / tracker.total_potential_steps) * 100
|
|
101
|
+
|
|
102
|
+
tracker.finish_task("task1")
|
|
103
|
+
steps, total, percentage = tracker.get_progress()
|
|
104
|
+
assert steps == tracker.max_steps_per_task
|
|
105
|
+
assert total == tracker.total_potential_steps
|
|
106
|
+
assert percentage == (tracker.max_steps_per_task / tracker.total_potential_steps) * 100
|
|
107
|
+
|
|
108
|
+
tracker.start_task("task2")
|
|
109
|
+
tracker.finish_task("task2")
|
|
110
|
+
steps, total, percentage = tracker.get_progress()
|
|
111
|
+
assert steps == tracker.total_potential_steps
|
|
112
|
+
assert percentage == 100.0
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def test_get_stats_no_progress(tracker):
|
|
116
|
+
from unittest.mock import patch
|
|
117
|
+
|
|
118
|
+
rate, eta = tracker.get_stats()
|
|
119
|
+
assert rate == 0.0
|
|
120
|
+
assert eta is None
|
|
121
|
+
|
|
122
|
+
with patch("time.monotonic", return_value=100.0):
|
|
123
|
+
tracker.start_task("task1")
|
|
124
|
+
|
|
125
|
+
rate, eta = tracker.get_stats()
|
|
126
|
+
assert rate == 0.0
|
|
127
|
+
assert eta is None
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def test_get_stats_with_progress():
|
|
131
|
+
from unittest.mock import patch
|
|
132
|
+
|
|
133
|
+
with patch("time.monotonic") as mock_time:
|
|
134
|
+
mock_time.return_value = 100.0
|
|
135
|
+
|
|
136
|
+
tracker = StepProgressTracker(total_tasks=1, max_steps_per_task=10)
|
|
137
|
+
tracker.start_task("task1")
|
|
138
|
+
|
|
139
|
+
mock_time.return_value = 160.0
|
|
140
|
+
for _ in range(5):
|
|
141
|
+
tracker.increment_step("task1")
|
|
142
|
+
|
|
143
|
+
rate, eta = tracker.get_stats()
|
|
144
|
+
|
|
145
|
+
assert rate == pytest.approx(5.0)
|
|
146
|
+
assert eta == pytest.approx(60.0)
|
|
147
|
+
|
|
148
|
+
for _ in range(5):
|
|
149
|
+
tracker.increment_step("task1")
|
|
150
|
+
|
|
151
|
+
rate, eta = tracker.get_stats()
|
|
152
|
+
assert rate == pytest.approx(10.0)
|
|
153
|
+
assert eta == pytest.approx(0.0)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def test_is_finished(tracker):
|
|
157
|
+
assert not tracker.is_finished()
|
|
158
|
+
|
|
159
|
+
tracker.start_task("task1")
|
|
160
|
+
tracker.finish_task("task1")
|
|
161
|
+
assert not tracker.is_finished()
|
|
162
|
+
|
|
163
|
+
tracker.start_task("task2")
|
|
164
|
+
tracker.finish_task("task2")
|
|
165
|
+
assert tracker.is_finished()
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def test_display(tracker):
|
|
169
|
+
from unittest.mock import patch
|
|
170
|
+
|
|
171
|
+
with patch("time.monotonic") as mock_time:
|
|
172
|
+
mock_time.return_value = 100.0
|
|
173
|
+
tracker.start_task("task1")
|
|
174
|
+
|
|
175
|
+
mock_time.return_value = 130.0
|
|
176
|
+
tracker.increment_step("task1")
|
|
177
|
+
tracker.increment_step("task1")
|
|
178
|
+
|
|
179
|
+
display_str = tracker.display()
|
|
180
|
+
|
|
181
|
+
assert "%" in display_str
|
|
182
|
+
assert "2/20" in display_str
|
|
183
|
+
assert "0:30" in display_str
|
|
184
|
+
assert "steps/min" in display_str
|
|
185
|
+
|
|
186
|
+
tracker.finish_task("task1")
|
|
187
|
+
display_str = tracker.display()
|
|
188
|
+
assert "10/20" in display_str
|
|
189
|
+
|
|
190
|
+
tracker.start_task("task2")
|
|
191
|
+
tracker.finish_task("task2")
|
|
192
|
+
display_str = tracker.display()
|
|
193
|
+
assert "100%" in display_str
|
|
194
|
+
assert "20/20" in display_str
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def test_complex_workflow():
|
|
198
|
+
tracker = StepProgressTracker(total_tasks=5, max_steps_per_task=20)
|
|
199
|
+
|
|
200
|
+
for i in range(5):
|
|
201
|
+
tracker.start_task(f"task{i}")
|
|
202
|
+
|
|
203
|
+
for _ in range(10):
|
|
204
|
+
tracker.increment_step("task0")
|
|
205
|
+
|
|
206
|
+
for _ in range(5):
|
|
207
|
+
tracker.increment_step("task1")
|
|
208
|
+
|
|
209
|
+
tracker.finish_task("task2")
|
|
210
|
+
|
|
211
|
+
for _ in range(15):
|
|
212
|
+
tracker.increment_step("task3")
|
|
213
|
+
|
|
214
|
+
tracker.finish_task("task3")
|
|
215
|
+
|
|
216
|
+
steps, total, percentage = tracker.get_progress()
|
|
217
|
+
expected_steps = 10 + 5 + 20 + 20 + 0
|
|
218
|
+
assert steps == expected_steps
|
|
219
|
+
assert total == 5 * 20
|
|
220
|
+
assert percentage == (expected_steps / total) * 100
|
|
221
|
+
|
|
222
|
+
assert tracker._tasks_finished == 2
|
|
223
|
+
assert not tracker.is_finished()
|
|
224
|
+
|
|
225
|
+
tracker.finish_task("task0")
|
|
226
|
+
tracker.finish_task("task1")
|
|
227
|
+
tracker.finish_task("task4")
|
|
228
|
+
|
|
229
|
+
assert tracker.is_finished()
|
|
230
|
+
assert tracker.get_progress()[2] == 100.0
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def test_display_eta_when_finished(tracker):
|
|
234
|
+
from unittest.mock import patch
|
|
235
|
+
|
|
236
|
+
"""Test that ETA shows 0:00 when progress is finished."""
|
|
237
|
+
|
|
238
|
+
with patch("time.monotonic") as mock_time:
|
|
239
|
+
mock_time.return_value = 100.0
|
|
240
|
+
|
|
241
|
+
# Start and complete all tasks
|
|
242
|
+
tracker.start_task("task1")
|
|
243
|
+
for _ in range(10):
|
|
244
|
+
tracker.increment_step("task1")
|
|
245
|
+
tracker.finish_task("task1")
|
|
246
|
+
|
|
247
|
+
tracker.start_task("task2")
|
|
248
|
+
for _ in range(10):
|
|
249
|
+
tracker.increment_step("task2")
|
|
250
|
+
tracker.finish_task("task2")
|
|
251
|
+
|
|
252
|
+
# Some time has passed
|
|
253
|
+
mock_time.return_value = 120.0
|
|
254
|
+
|
|
255
|
+
display = tracker.display()
|
|
256
|
+
|
|
257
|
+
# When finished, ETA should be 0:00 (not ??:??)
|
|
258
|
+
assert tracker.is_finished()
|
|
259
|
+
assert "0:00" in display
|
|
260
|
+
assert "100%" in display
|
|
261
|
+
assert "20/20" in display
|