hud-python 0.3.5__py3-none-any.whl → 0.4.0__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.

Files changed (192) hide show
  1. hud/__init__.py +22 -89
  2. hud/agents/__init__.py +17 -0
  3. hud/agents/art.py +101 -0
  4. hud/agents/base.py +599 -0
  5. hud/{mcp → agents}/claude.py +373 -321
  6. hud/{mcp → agents}/langchain.py +250 -250
  7. hud/agents/misc/__init__.py +7 -0
  8. hud/{agent → agents}/misc/response_agent.py +80 -80
  9. hud/{mcp → agents}/openai.py +352 -334
  10. hud/agents/openai_chat_generic.py +154 -0
  11. hud/{mcp → agents}/tests/__init__.py +1 -1
  12. hud/agents/tests/test_base.py +742 -0
  13. hud/agents/tests/test_claude.py +324 -0
  14. hud/{mcp → agents}/tests/test_client.py +363 -324
  15. hud/{mcp → agents}/tests/test_openai.py +237 -238
  16. hud/cli/__init__.py +617 -0
  17. hud/cli/__main__.py +8 -0
  18. hud/cli/analyze.py +371 -0
  19. hud/cli/analyze_metadata.py +230 -0
  20. hud/cli/build.py +427 -0
  21. hud/cli/clone.py +185 -0
  22. hud/cli/cursor.py +92 -0
  23. hud/cli/debug.py +392 -0
  24. hud/cli/docker_utils.py +83 -0
  25. hud/cli/init.py +281 -0
  26. hud/cli/interactive.py +353 -0
  27. hud/cli/mcp_server.py +756 -0
  28. hud/cli/pull.py +336 -0
  29. hud/cli/push.py +379 -0
  30. hud/cli/remote_runner.py +311 -0
  31. hud/cli/runner.py +160 -0
  32. hud/cli/tests/__init__.py +3 -0
  33. hud/cli/tests/test_analyze.py +284 -0
  34. hud/cli/tests/test_cli_init.py +265 -0
  35. hud/cli/tests/test_cli_main.py +27 -0
  36. hud/cli/tests/test_clone.py +142 -0
  37. hud/cli/tests/test_cursor.py +253 -0
  38. hud/cli/tests/test_debug.py +453 -0
  39. hud/cli/tests/test_mcp_server.py +139 -0
  40. hud/cli/tests/test_utils.py +388 -0
  41. hud/cli/utils.py +263 -0
  42. hud/clients/README.md +143 -0
  43. hud/clients/__init__.py +16 -0
  44. hud/clients/base.py +354 -0
  45. hud/clients/fastmcp.py +202 -0
  46. hud/clients/mcp_use.py +278 -0
  47. hud/clients/tests/__init__.py +1 -0
  48. hud/clients/tests/test_client_integration.py +111 -0
  49. hud/clients/tests/test_fastmcp.py +342 -0
  50. hud/clients/tests/test_protocol.py +188 -0
  51. hud/clients/utils/__init__.py +1 -0
  52. hud/clients/utils/retry_transport.py +160 -0
  53. hud/datasets.py +322 -192
  54. hud/misc/__init__.py +1 -0
  55. hud/{agent → misc}/claude_plays_pokemon.py +292 -283
  56. hud/otel/__init__.py +35 -0
  57. hud/otel/collector.py +142 -0
  58. hud/otel/config.py +164 -0
  59. hud/otel/context.py +536 -0
  60. hud/otel/exporters.py +366 -0
  61. hud/otel/instrumentation.py +97 -0
  62. hud/otel/processors.py +118 -0
  63. hud/otel/tests/__init__.py +1 -0
  64. hud/otel/tests/test_processors.py +197 -0
  65. hud/server/__init__.py +5 -5
  66. hud/server/context.py +114 -0
  67. hud/server/helper/__init__.py +5 -0
  68. hud/server/low_level.py +132 -0
  69. hud/server/server.py +166 -0
  70. hud/server/tests/__init__.py +3 -0
  71. hud/settings.py +73 -79
  72. hud/shared/__init__.py +5 -0
  73. hud/{exceptions.py → shared/exceptions.py} +180 -180
  74. hud/{server → shared}/requests.py +264 -264
  75. hud/shared/tests/test_exceptions.py +157 -0
  76. hud/{server → shared}/tests/test_requests.py +275 -275
  77. hud/telemetry/__init__.py +25 -30
  78. hud/telemetry/instrument.py +379 -0
  79. hud/telemetry/job.py +309 -141
  80. hud/telemetry/replay.py +74 -0
  81. hud/telemetry/trace.py +83 -0
  82. hud/tools/__init__.py +33 -34
  83. hud/tools/base.py +365 -65
  84. hud/tools/bash.py +161 -137
  85. hud/tools/computer/__init__.py +15 -13
  86. hud/tools/computer/anthropic.py +437 -420
  87. hud/tools/computer/hud.py +376 -334
  88. hud/tools/computer/openai.py +295 -292
  89. hud/tools/computer/settings.py +82 -0
  90. hud/tools/edit.py +314 -290
  91. hud/tools/executors/__init__.py +30 -30
  92. hud/tools/executors/base.py +539 -532
  93. hud/tools/executors/pyautogui.py +621 -619
  94. hud/tools/executors/tests/__init__.py +1 -1
  95. hud/tools/executors/tests/test_base_executor.py +338 -338
  96. hud/tools/executors/tests/test_pyautogui_executor.py +165 -165
  97. hud/tools/executors/xdo.py +511 -503
  98. hud/tools/{playwright_tool.py → playwright.py} +412 -379
  99. hud/tools/tests/__init__.py +3 -3
  100. hud/tools/tests/test_base.py +282 -0
  101. hud/tools/tests/test_bash.py +158 -152
  102. hud/tools/tests/test_bash_extended.py +197 -0
  103. hud/tools/tests/test_computer.py +425 -52
  104. hud/tools/tests/test_computer_actions.py +34 -34
  105. hud/tools/tests/test_edit.py +259 -240
  106. hud/tools/tests/test_init.py +27 -27
  107. hud/tools/tests/test_playwright_tool.py +183 -183
  108. hud/tools/tests/test_tools.py +145 -157
  109. hud/tools/tests/test_utils.py +156 -156
  110. hud/tools/types.py +72 -0
  111. hud/tools/utils.py +50 -50
  112. hud/types.py +136 -89
  113. hud/utils/__init__.py +10 -16
  114. hud/utils/async_utils.py +65 -0
  115. hud/utils/design.py +168 -0
  116. hud/utils/mcp.py +55 -0
  117. hud/utils/progress.py +149 -149
  118. hud/utils/telemetry.py +66 -66
  119. hud/utils/tests/test_async_utils.py +173 -0
  120. hud/utils/tests/test_init.py +17 -21
  121. hud/utils/tests/test_progress.py +261 -225
  122. hud/utils/tests/test_telemetry.py +82 -37
  123. hud/utils/tests/test_version.py +8 -8
  124. hud/version.py +7 -7
  125. hud_python-0.4.0.dist-info/METADATA +474 -0
  126. hud_python-0.4.0.dist-info/RECORD +132 -0
  127. hud_python-0.4.0.dist-info/entry_points.txt +3 -0
  128. {hud_python-0.3.5.dist-info → hud_python-0.4.0.dist-info}/licenses/LICENSE +21 -21
  129. hud/adapters/__init__.py +0 -8
  130. hud/adapters/claude/__init__.py +0 -5
  131. hud/adapters/claude/adapter.py +0 -180
  132. hud/adapters/claude/tests/__init__.py +0 -1
  133. hud/adapters/claude/tests/test_adapter.py +0 -519
  134. hud/adapters/common/__init__.py +0 -6
  135. hud/adapters/common/adapter.py +0 -178
  136. hud/adapters/common/tests/test_adapter.py +0 -289
  137. hud/adapters/common/types.py +0 -446
  138. hud/adapters/operator/__init__.py +0 -5
  139. hud/adapters/operator/adapter.py +0 -108
  140. hud/adapters/operator/tests/__init__.py +0 -1
  141. hud/adapters/operator/tests/test_adapter.py +0 -370
  142. hud/agent/__init__.py +0 -19
  143. hud/agent/base.py +0 -126
  144. hud/agent/claude.py +0 -271
  145. hud/agent/langchain.py +0 -215
  146. hud/agent/misc/__init__.py +0 -3
  147. hud/agent/operator.py +0 -268
  148. hud/agent/tests/__init__.py +0 -1
  149. hud/agent/tests/test_base.py +0 -202
  150. hud/env/__init__.py +0 -11
  151. hud/env/client.py +0 -35
  152. hud/env/docker_client.py +0 -349
  153. hud/env/environment.py +0 -446
  154. hud/env/local_docker_client.py +0 -358
  155. hud/env/remote_client.py +0 -212
  156. hud/env/remote_docker_client.py +0 -292
  157. hud/gym.py +0 -130
  158. hud/job.py +0 -773
  159. hud/mcp/__init__.py +0 -17
  160. hud/mcp/base.py +0 -631
  161. hud/mcp/client.py +0 -312
  162. hud/mcp/tests/test_base.py +0 -512
  163. hud/mcp/tests/test_claude.py +0 -294
  164. hud/task.py +0 -149
  165. hud/taskset.py +0 -237
  166. hud/telemetry/_trace.py +0 -347
  167. hud/telemetry/context.py +0 -230
  168. hud/telemetry/exporter.py +0 -575
  169. hud/telemetry/instrumentation/__init__.py +0 -3
  170. hud/telemetry/instrumentation/mcp.py +0 -259
  171. hud/telemetry/instrumentation/registry.py +0 -59
  172. hud/telemetry/mcp_models.py +0 -270
  173. hud/telemetry/tests/__init__.py +0 -1
  174. hud/telemetry/tests/test_context.py +0 -210
  175. hud/telemetry/tests/test_trace.py +0 -312
  176. hud/tools/helper/README.md +0 -56
  177. hud/tools/helper/__init__.py +0 -9
  178. hud/tools/helper/mcp_server.py +0 -78
  179. hud/tools/helper/server_initialization.py +0 -115
  180. hud/tools/helper/utils.py +0 -58
  181. hud/trajectory.py +0 -94
  182. hud/utils/agent.py +0 -37
  183. hud/utils/common.py +0 -256
  184. hud/utils/config.py +0 -120
  185. hud/utils/deprecation.py +0 -115
  186. hud/utils/misc.py +0 -53
  187. hud/utils/tests/test_common.py +0 -277
  188. hud/utils/tests/test_config.py +0 -129
  189. hud_python-0.3.5.dist-info/METADATA +0 -284
  190. hud_python-0.3.5.dist-info/RECORD +0 -120
  191. /hud/{adapters/common → shared}/tests/__init__.py +0 -0
  192. {hud_python-0.3.5.dist-info → hud_python-0.4.0.dist-info}/WHEEL +0 -0
@@ -1,21 +1,17 @@
1
- """Test utils package imports."""
2
-
3
- from __future__ import annotations
4
-
5
-
6
- def test_utils_imports():
7
- """Test that utils package can be imported."""
8
- import hud.utils
9
-
10
- # Check that the module exists
11
- assert hud.utils is not None
12
-
13
- # Try importing submodules
14
- from hud.utils import agent, common, config, misc, progress, telemetry
15
-
16
- assert agent is not None
17
- assert common is not None
18
- assert config is not None
19
- assert misc is not None
20
- assert progress is not None
21
- assert telemetry is not None
1
+ """Test utils package imports."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ def test_utils_imports():
7
+ """Test that utils package can be imported."""
8
+ import hud.utils
9
+
10
+ # Check that the module exists
11
+ assert hud.utils is not None
12
+
13
+ # Try importing submodules
14
+ from hud.utils import progress, telemetry
15
+
16
+ assert progress is not None
17
+ assert telemetry is not None
@@ -1,225 +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, mocker):
116
- rate, eta = tracker.get_stats()
117
- assert rate == 0.0
118
- assert eta is None
119
-
120
- mocker.patch("time.monotonic", return_value=100.0)
121
- tracker.start_task("task1")
122
-
123
- mocker.patch("time.monotonic", return_value=100.0)
124
- rate, eta = tracker.get_stats()
125
- assert rate == 0.0
126
- assert eta is None
127
-
128
-
129
- def test_get_stats_with_progress(mocker):
130
- mock_time = mocker.patch("time.monotonic")
131
- mock_time.return_value = 100.0
132
-
133
- tracker = StepProgressTracker(total_tasks=1, max_steps_per_task=10)
134
- tracker.start_task("task1")
135
-
136
- mock_time.return_value = 160.0
137
- for _ in range(5):
138
- tracker.increment_step("task1")
139
-
140
- rate, eta = tracker.get_stats()
141
-
142
- assert rate == pytest.approx(5.0)
143
- assert eta == pytest.approx(60.0)
144
-
145
- for _ in range(5):
146
- tracker.increment_step("task1")
147
-
148
- rate, eta = tracker.get_stats()
149
- assert rate == pytest.approx(10.0)
150
- assert eta == pytest.approx(0.0)
151
-
152
-
153
- def test_is_finished(tracker):
154
- assert not tracker.is_finished()
155
-
156
- tracker.start_task("task1")
157
- tracker.finish_task("task1")
158
- assert not tracker.is_finished()
159
-
160
- tracker.start_task("task2")
161
- tracker.finish_task("task2")
162
- assert tracker.is_finished()
163
-
164
-
165
- def test_display(tracker, mocker):
166
- mock_time = mocker.patch("time.monotonic")
167
- mock_time.return_value = 100.0
168
- tracker.start_task("task1")
169
-
170
- mock_time.return_value = 130.0
171
- tracker.increment_step("task1")
172
- tracker.increment_step("task1")
173
-
174
- display_str = tracker.display()
175
-
176
- assert "%" in display_str
177
- assert "2/20" in display_str
178
- assert "0:30" in display_str
179
- assert "steps/min" in display_str
180
-
181
- tracker.finish_task("task1")
182
- display_str = tracker.display()
183
- assert "10/20" in display_str
184
-
185
- tracker.start_task("task2")
186
- tracker.finish_task("task2")
187
- display_str = tracker.display()
188
- assert "100%" in display_str
189
- assert "20/20" in display_str
190
-
191
-
192
- def test_complex_workflow():
193
- tracker = StepProgressTracker(total_tasks=5, max_steps_per_task=20)
194
-
195
- for i in range(5):
196
- tracker.start_task(f"task{i}")
197
-
198
- for _ in range(10):
199
- tracker.increment_step("task0")
200
-
201
- for _ in range(5):
202
- tracker.increment_step("task1")
203
-
204
- tracker.finish_task("task2")
205
-
206
- for _ in range(15):
207
- tracker.increment_step("task3")
208
-
209
- tracker.finish_task("task3")
210
-
211
- steps, total, percentage = tracker.get_progress()
212
- expected_steps = 10 + 5 + 20 + 20 + 0
213
- assert steps == expected_steps
214
- assert total == 5 * 20
215
- assert percentage == (expected_steps / total) * 100
216
-
217
- assert tracker._tasks_finished == 2
218
- assert not tracker.is_finished()
219
-
220
- tracker.finish_task("task0")
221
- tracker.finish_task("task1")
222
- tracker.finish_task("task4")
223
-
224
- assert tracker.is_finished()
225
- assert tracker.get_progress()[2] == 100.0
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,37 +1,82 @@
1
- from __future__ import annotations
2
-
3
- from hud.utils.telemetry import stream
4
-
5
-
6
- def test_stream():
7
- html_content = stream("https://example.com")
8
- assert html_content is not None
9
- assert "<div style=" in html_content
10
- assert 'src="https://example.com"' in html_content
11
-
12
-
13
- def test_display_screenshot():
14
- from hud.utils.telemetry import display_screenshot
15
-
16
- # This is a simple 1x1 transparent PNG image in base64 format
17
- base64_image = (
18
- "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQ"
19
- "AAABJRU5ErkJggg=="
20
- )
21
-
22
- html_content = display_screenshot(base64_image)
23
- assert html_content is not None
24
- assert "<div style=" in html_content
25
- assert "width: 960px" in html_content
26
- assert "height: 540px" in html_content
27
- assert f"data:image/png;base64,{base64_image}" in html_content
28
-
29
- # Test with custom dimensions
30
- custom_html = display_screenshot(base64_image, width=800, height=600)
31
- assert "width: 800px" in custom_html
32
- assert "height: 600px" in custom_html
33
-
34
- # Test with data URI already included
35
- data_uri = f"data:image/png;base64,{base64_image}"
36
- uri_html = display_screenshot(data_uri)
37
- assert data_uri in uri_html
1
+ from __future__ import annotations
2
+
3
+ from unittest.mock import patch
4
+
5
+ from hud.utils.telemetry import stream
6
+
7
+
8
+ def test_stream():
9
+ html_content = stream("https://example.com")
10
+ assert html_content is not None
11
+ assert "<div style=" in html_content
12
+ assert 'src="https://example.com"' in html_content
13
+
14
+
15
+ def test_stream_with_display_exception():
16
+ """Test stream when IPython display raises an exception."""
17
+ with (
18
+ patch("IPython.display.display", side_effect=Exception("Display error")),
19
+ patch("hud.utils.telemetry.logger") as mock_logger,
20
+ ):
21
+ html_content = stream("https://example.com")
22
+
23
+ # Should still return the HTML content
24
+ assert html_content is not None
25
+ assert 'src="https://example.com"' in html_content
26
+
27
+ # Should log the warning
28
+ mock_logger.warning.assert_called_once()
29
+ args = mock_logger.warning.call_args[0]
30
+ assert "Display error" in str(args[0])
31
+
32
+
33
+ def test_display_screenshot():
34
+ from hud.utils.telemetry import display_screenshot
35
+
36
+ # This is a simple 1x1 transparent PNG image in base64 format
37
+ base64_image = (
38
+ "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQ"
39
+ "AAABJRU5ErkJggg=="
40
+ )
41
+
42
+ html_content = display_screenshot(base64_image)
43
+ assert html_content is not None
44
+ assert "<div style=" in html_content
45
+ assert "width: 960px" in html_content
46
+ assert "height: 540px" in html_content
47
+ assert f"data:image/png;base64,{base64_image}" in html_content
48
+
49
+ # Test with custom dimensions
50
+ custom_html = display_screenshot(base64_image, width=800, height=600)
51
+ assert "width: 800px" in custom_html
52
+ assert "height: 600px" in custom_html
53
+
54
+ # Test with data URI already included
55
+ data_uri = f"data:image/png;base64,{base64_image}"
56
+ uri_html = display_screenshot(data_uri)
57
+ assert data_uri in uri_html
58
+
59
+
60
+ def test_display_screenshot_with_exception():
61
+ """Test display_screenshot when IPython display raises an exception."""
62
+ from hud.utils.telemetry import display_screenshot
63
+
64
+ base64_image = (
65
+ "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQ"
66
+ "AAABJRU5ErkJggg=="
67
+ )
68
+
69
+ with (
70
+ patch("IPython.display.display", side_effect=Exception("Display error")),
71
+ patch("hud.utils.telemetry.logger") as mock_logger,
72
+ ):
73
+ html_content = display_screenshot(base64_image)
74
+
75
+ # Should still return the HTML content
76
+ assert html_content is not None
77
+ assert f"data:image/png;base64,{base64_image}" in html_content
78
+
79
+ # Should log the warning
80
+ mock_logger.warning.assert_called_once()
81
+ args = mock_logger.warning.call_args[0]
82
+ assert "Display error" in str(args[0])
@@ -1,8 +1,8 @@
1
- from __future__ import annotations
2
-
3
-
4
- def test_import():
5
- """Test that the package can be imported."""
6
- import hud
7
-
8
- assert hud.__version__ == "0.3.5"
1
+ from __future__ import annotations
2
+
3
+
4
+ def test_import():
5
+ """Test that the package can be imported."""
6
+ import hud
7
+
8
+ assert hud.__version__ == "0.4.0"