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.

Files changed (130) hide show
  1. hud/__init__.py +22 -22
  2. hud/agents/__init__.py +13 -15
  3. hud/agents/base.py +599 -599
  4. hud/agents/claude.py +373 -373
  5. hud/agents/langchain.py +261 -250
  6. hud/agents/misc/__init__.py +7 -7
  7. hud/agents/misc/response_agent.py +82 -80
  8. hud/agents/openai.py +352 -352
  9. hud/agents/openai_chat_generic.py +154 -154
  10. hud/agents/tests/__init__.py +1 -1
  11. hud/agents/tests/test_base.py +742 -742
  12. hud/agents/tests/test_claude.py +324 -324
  13. hud/agents/tests/test_client.py +363 -363
  14. hud/agents/tests/test_openai.py +237 -237
  15. hud/cli/__init__.py +617 -617
  16. hud/cli/__main__.py +8 -8
  17. hud/cli/analyze.py +371 -371
  18. hud/cli/analyze_metadata.py +230 -230
  19. hud/cli/build.py +498 -427
  20. hud/cli/clone.py +185 -185
  21. hud/cli/cursor.py +92 -92
  22. hud/cli/debug.py +392 -392
  23. hud/cli/docker_utils.py +83 -83
  24. hud/cli/init.py +280 -281
  25. hud/cli/interactive.py +353 -353
  26. hud/cli/mcp_server.py +764 -756
  27. hud/cli/pull.py +330 -336
  28. hud/cli/push.py +404 -370
  29. hud/cli/remote_runner.py +311 -311
  30. hud/cli/runner.py +160 -160
  31. hud/cli/tests/__init__.py +3 -3
  32. hud/cli/tests/test_analyze.py +284 -284
  33. hud/cli/tests/test_cli_init.py +265 -265
  34. hud/cli/tests/test_cli_main.py +27 -27
  35. hud/cli/tests/test_clone.py +142 -142
  36. hud/cli/tests/test_cursor.py +253 -253
  37. hud/cli/tests/test_debug.py +453 -453
  38. hud/cli/tests/test_mcp_server.py +139 -139
  39. hud/cli/tests/test_utils.py +388 -388
  40. hud/cli/utils.py +263 -263
  41. hud/clients/README.md +143 -143
  42. hud/clients/__init__.py +16 -16
  43. hud/clients/base.py +378 -379
  44. hud/clients/fastmcp.py +222 -222
  45. hud/clients/mcp_use.py +298 -278
  46. hud/clients/tests/__init__.py +1 -1
  47. hud/clients/tests/test_client_integration.py +111 -111
  48. hud/clients/tests/test_fastmcp.py +342 -342
  49. hud/clients/tests/test_protocol.py +188 -188
  50. hud/clients/utils/__init__.py +1 -1
  51. hud/clients/utils/retry_transport.py +160 -160
  52. hud/datasets.py +327 -322
  53. hud/misc/__init__.py +1 -1
  54. hud/misc/claude_plays_pokemon.py +292 -292
  55. hud/otel/__init__.py +35 -35
  56. hud/otel/collector.py +142 -142
  57. hud/otel/config.py +164 -164
  58. hud/otel/context.py +536 -536
  59. hud/otel/exporters.py +366 -366
  60. hud/otel/instrumentation.py +97 -97
  61. hud/otel/processors.py +118 -118
  62. hud/otel/tests/__init__.py +1 -1
  63. hud/otel/tests/test_processors.py +197 -197
  64. hud/server/__init__.py +5 -5
  65. hud/server/context.py +114 -114
  66. hud/server/helper/__init__.py +5 -5
  67. hud/server/low_level.py +132 -132
  68. hud/server/server.py +170 -166
  69. hud/server/tests/__init__.py +3 -3
  70. hud/settings.py +73 -73
  71. hud/shared/__init__.py +5 -5
  72. hud/shared/exceptions.py +180 -180
  73. hud/shared/requests.py +264 -264
  74. hud/shared/tests/test_exceptions.py +157 -157
  75. hud/shared/tests/test_requests.py +275 -275
  76. hud/telemetry/__init__.py +25 -25
  77. hud/telemetry/instrument.py +379 -379
  78. hud/telemetry/job.py +309 -309
  79. hud/telemetry/replay.py +74 -74
  80. hud/telemetry/trace.py +83 -83
  81. hud/tools/__init__.py +33 -33
  82. hud/tools/base.py +365 -365
  83. hud/tools/bash.py +161 -161
  84. hud/tools/computer/__init__.py +15 -15
  85. hud/tools/computer/anthropic.py +437 -437
  86. hud/tools/computer/hud.py +376 -376
  87. hud/tools/computer/openai.py +295 -295
  88. hud/tools/computer/settings.py +82 -82
  89. hud/tools/edit.py +314 -314
  90. hud/tools/executors/__init__.py +30 -30
  91. hud/tools/executors/base.py +539 -539
  92. hud/tools/executors/pyautogui.py +621 -621
  93. hud/tools/executors/tests/__init__.py +1 -1
  94. hud/tools/executors/tests/test_base_executor.py +338 -338
  95. hud/tools/executors/tests/test_pyautogui_executor.py +165 -165
  96. hud/tools/executors/xdo.py +511 -511
  97. hud/tools/playwright.py +412 -412
  98. hud/tools/tests/__init__.py +3 -3
  99. hud/tools/tests/test_base.py +282 -282
  100. hud/tools/tests/test_bash.py +158 -158
  101. hud/tools/tests/test_bash_extended.py +197 -197
  102. hud/tools/tests/test_computer.py +425 -425
  103. hud/tools/tests/test_computer_actions.py +34 -34
  104. hud/tools/tests/test_edit.py +259 -259
  105. hud/tools/tests/test_init.py +27 -27
  106. hud/tools/tests/test_playwright_tool.py +183 -183
  107. hud/tools/tests/test_tools.py +145 -145
  108. hud/tools/tests/test_utils.py +156 -156
  109. hud/tools/types.py +72 -72
  110. hud/tools/utils.py +50 -50
  111. hud/types.py +136 -136
  112. hud/utils/__init__.py +10 -10
  113. hud/utils/async_utils.py +65 -65
  114. hud/utils/design.py +236 -168
  115. hud/utils/mcp.py +55 -55
  116. hud/utils/progress.py +149 -149
  117. hud/utils/telemetry.py +66 -66
  118. hud/utils/tests/test_async_utils.py +173 -173
  119. hud/utils/tests/test_init.py +17 -17
  120. hud/utils/tests/test_progress.py +261 -261
  121. hud/utils/tests/test_telemetry.py +82 -82
  122. hud/utils/tests/test_version.py +8 -8
  123. hud/version.py +7 -7
  124. {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/METADATA +10 -8
  125. hud_python-0.4.3.dist-info/RECORD +131 -0
  126. {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/licenses/LICENSE +21 -21
  127. hud/agents/art.py +0 -101
  128. hud_python-0.4.1.dist-info/RECORD +0 -132
  129. {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/WHEEL +0 -0
  130. {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/entry_points.txt +0 -0
@@ -1,388 +1,388 @@
1
- """Tests for hud.cli.utils module."""
2
-
3
- from __future__ import annotations
4
-
5
- import sys
6
- from unittest.mock import patch
7
-
8
- import pytest
9
-
10
- from hud.cli.utils import HINT_REGISTRY, CaptureLogger, Colors, analyze_error_for_hints
11
-
12
-
13
- class TestColors:
14
- """Test ANSI color codes."""
15
-
16
- def test_color_constants(self) -> None:
17
- """Test that color constants are defined."""
18
- assert Colors.HEADER == "\033[95m"
19
- assert Colors.BLUE == "\033[94m"
20
- assert Colors.CYAN == "\033[96m"
21
- assert Colors.GREEN == "\033[92m"
22
- assert Colors.YELLOW == "\033[93m"
23
- assert Colors.GOLD == "\033[33m"
24
- assert Colors.RED == "\033[91m"
25
- assert Colors.GRAY == "\033[90m"
26
- assert Colors.ENDC == "\033[0m"
27
- assert Colors.BOLD == "\033[1m"
28
-
29
-
30
- class TestCaptureLogger:
31
- """Test CaptureLogger functionality."""
32
-
33
- def test_logger_print_mode(self) -> None:
34
- """Test logger in print mode."""
35
- logger = CaptureLogger(print_output=True)
36
-
37
- with patch("builtins.print") as mock_print:
38
- logger._log("Test message", Colors.GREEN)
39
- mock_print.assert_called_once_with(f"{Colors.GREEN}Test message{Colors.ENDC}")
40
-
41
- def test_logger_capture_mode(self) -> None:
42
- """Test logger in capture-only mode."""
43
- logger = CaptureLogger(print_output=False)
44
-
45
- with patch("builtins.print") as mock_print:
46
- logger._log("Test message", Colors.GREEN)
47
- mock_print.assert_not_called()
48
-
49
- output = logger.get_output()
50
- assert "Test message" in output
51
-
52
- def test_strip_ansi(self) -> None:
53
- """Test ANSI code stripping."""
54
- logger = CaptureLogger(print_output=False)
55
-
56
- # Test various ANSI sequences
57
- text_with_ansi = (
58
- f"{Colors.GREEN}Green text{Colors.ENDC} normal {Colors.BOLD}bold{Colors.ENDC}"
59
- )
60
- clean_text = logger._strip_ansi(text_with_ansi)
61
- assert clean_text == "Green text normal bold"
62
-
63
- def test_timestamp(self) -> None:
64
- """Test timestamp generation."""
65
- logger = CaptureLogger(print_output=False)
66
-
67
- timestamp = logger.timestamp()
68
- # Should be in HH:MM:SS format
69
- assert len(timestamp) == 8
70
- assert timestamp[2] == ":"
71
- assert timestamp[5] == ":"
72
-
73
- def test_phase_logging(self) -> None:
74
- """Test phase header logging."""
75
- logger = CaptureLogger(print_output=False)
76
-
77
- logger.phase(1, "Test Phase")
78
- output = logger.get_output()
79
-
80
- assert "=" * 80 in output
81
- assert "PHASE 1: Test Phase" in output
82
-
83
- def test_command_logging(self) -> None:
84
- """Test command logging."""
85
- logger = CaptureLogger(print_output=False)
86
-
87
- logger.command(["python", "script.py", "--arg", "value"])
88
- output = logger.get_output()
89
-
90
- assert "$ python script.py --arg value" in output
91
-
92
- def test_success_logging(self) -> None:
93
- """Test success message logging."""
94
- logger = CaptureLogger(print_output=False)
95
-
96
- logger.success("Operation completed")
97
- output = logger.get_output()
98
-
99
- assert "✅ Operation completed" in output
100
-
101
- def test_error_logging(self) -> None:
102
- """Test error message logging."""
103
- logger = CaptureLogger(print_output=False)
104
-
105
- logger.error("Operation failed")
106
- output = logger.get_output()
107
-
108
- assert "❌ Operation failed" in output
109
-
110
- def test_info_logging(self) -> None:
111
- """Test info message logging with timestamp."""
112
- logger = CaptureLogger(print_output=False)
113
-
114
- with patch.object(logger, "timestamp", return_value="12:34:56"):
115
- logger.info("Information message")
116
- output = logger.get_output()
117
-
118
- assert "[12:34:56] Information message" in output
119
-
120
- def test_stdio_logging(self) -> None:
121
- """Test STDIO communication logging."""
122
- logger = CaptureLogger(print_output=False)
123
-
124
- logger.stdio("JSON-RPC message")
125
- output = logger.get_output()
126
-
127
- assert "[STDIO] JSON-RPC message" in output
128
-
129
- def test_stderr_logging(self) -> None:
130
- """Test STDERR output logging."""
131
- logger = CaptureLogger(print_output=False)
132
-
133
- logger.stderr("Error output from server")
134
- output = logger.get_output()
135
-
136
- assert "[STDERR] Error output from server" in output
137
-
138
- def test_hint_logging(self) -> None:
139
- """Test hint message logging."""
140
- logger = CaptureLogger(print_output=False)
141
-
142
- logger.hint("Try checking the configuration")
143
- output = logger.get_output()
144
-
145
- assert "💡 Hint: Try checking the configuration" in output
146
-
147
- def test_progress_bar(self) -> None:
148
- """Test progress bar visualization."""
149
- logger = CaptureLogger(print_output=False)
150
-
151
- # Test partial progress
152
- logger.progress_bar(3, 5)
153
- output = logger.get_output()
154
-
155
- assert "Progress: [███░░] 3/5 phases (60%)" in output
156
- assert "Failed at Phase 4" in output
157
-
158
- # Test complete progress
159
- logger = CaptureLogger(print_output=False)
160
- logger.progress_bar(5, 5)
161
- output = logger.get_output()
162
-
163
- assert "Progress: [█████] 5/5 phases (100%)" in output
164
- assert "All phases completed successfully!" in output
165
-
166
- def test_progress_bar_failure_messages(self) -> None:
167
- """Test progress bar failure messages at different phases."""
168
- test_cases = [
169
- (0, "Failed at Phase 1 - Server startup"),
170
- (1, "Failed at Phase 2 - MCP initialization"),
171
- (2, "Failed at Phase 3 - Tool discovery"),
172
- (3, "Failed at Phase 4 - Remote deployment readiness"),
173
- (4, "Failed at Phase 5 - Concurrent clients & resources"),
174
- ]
175
-
176
- for completed, expected_msg in test_cases:
177
- logger = CaptureLogger(print_output=False)
178
- logger.progress_bar(completed, 5)
179
- output = logger.get_output()
180
- assert expected_msg in output
181
-
182
- def test_get_output(self) -> None:
183
- """Test getting accumulated output."""
184
- logger = CaptureLogger(print_output=False)
185
-
186
- logger.info("First message")
187
- logger.error("Second message")
188
- logger.success("Third message")
189
-
190
- output = logger.get_output()
191
- assert "First message" in output
192
- assert "Second message" in output
193
- assert "Third message" in output
194
-
195
-
196
- class TestAnalyzeErrorForHints:
197
- """Test error analysis and hint generation."""
198
-
199
- def test_x11_display_errors(self) -> None:
200
- """Test X11/display related error hints."""
201
- errors = [
202
- "Can't connect to display :0",
203
- "X11 connection rejected",
204
- "DISPLAY environment variable not set",
205
- "Xlib.error.DisplayConnectionError",
206
- ]
207
-
208
- for error in errors:
209
- hint = analyze_error_for_hints(error)
210
- assert hint is not None
211
- assert "GUI environment needs X11" in hint
212
- assert "Xvfb" in hint
213
-
214
- def test_import_errors(self) -> None:
215
- """Test import/module error hints."""
216
- errors = [
217
- "ModuleNotFoundError: No module named 'requests'",
218
- "ImportError: cannot import name 'api'",
219
- "No module named numpy",
220
- ]
221
-
222
- for error in errors:
223
- hint = analyze_error_for_hints(error)
224
- assert hint is not None
225
- assert "Missing Python dependencies" in hint
226
- assert "pyproject.toml" in hint
227
-
228
- def test_json_errors(self) -> None:
229
- """Test JSON parsing error hints."""
230
- errors = [
231
- "json.decoder.JSONDecodeError: Expecting value",
232
- "JSONDecodeError: Expecting value: line 1 column 1",
233
- ]
234
-
235
- for error in errors:
236
- hint = analyze_error_for_hints(error)
237
- assert hint is not None
238
- assert "Invalid JSON-RPC communication" in hint
239
- assert "proper JSON-RPC format" in hint
240
-
241
- def test_permission_errors(self) -> None:
242
- """Test permission error hints."""
243
- errors = [
244
- "Permission denied: /var/log/app.log",
245
- "EACCES: permission denied",
246
- "Operation not permitted",
247
- ]
248
-
249
- for error in errors:
250
- hint = analyze_error_for_hints(error)
251
- assert hint is not None
252
- assert "Permission issues" in hint
253
- assert "Check file permissions" in hint
254
-
255
- def test_memory_errors(self) -> None:
256
- """Test memory/resource error hints."""
257
- errors = ["Cannot allocate memory", "Process killed", "Container OOMKilled"]
258
-
259
- for error in errors:
260
- hint = analyze_error_for_hints(error)
261
- assert hint is not None
262
- assert "Resource limits exceeded" in hint
263
- assert "memory limits" in hint
264
-
265
- def test_port_errors(self) -> None:
266
- """Test port binding error hints."""
267
- errors = [
268
- "bind: address already in use",
269
- "EADDRINUSE: address already in use",
270
- "port 8080 already allocated",
271
- ]
272
-
273
- for error in errors:
274
- hint = analyze_error_for_hints(error)
275
- assert hint is not None
276
- assert "Port conflict detected" in hint
277
- assert "different port" in hint
278
-
279
- def test_file_not_found_errors(self) -> None:
280
- """Test file not found error hints."""
281
- errors = [
282
- "FileNotFoundError: [Errno 2] No such file or directory",
283
- "No such file or directory: config.json",
284
- ]
285
-
286
- for error in errors:
287
- hint = analyze_error_for_hints(error)
288
- assert hint is not None
289
- assert "File or directory missing" in hint
290
- assert "required files exist" in hint
291
-
292
- def test_traceback_errors(self) -> None:
293
- """Test general traceback error hints."""
294
- error = """Traceback (most recent call last):
295
- File "app.py", line 10, in <module>
296
- import missing_module
297
- ImportError: No module named missing_module"""
298
-
299
- hint = analyze_error_for_hints(error)
300
- assert hint is not None
301
- # Should match both traceback and import patterns
302
- # Import has higher priority
303
- assert "Missing Python dependencies" in hint
304
-
305
- def test_timeout_errors(self) -> None:
306
- """Test timeout error hints."""
307
- errors = ["Operation timed out after 30 seconds", "Connection timeout", "Request timed out"]
308
-
309
- for error in errors:
310
- hint = analyze_error_for_hints(error)
311
- assert hint is not None
312
- assert "Server taking too long to start" in hint
313
- assert "slow operations" in hint
314
-
315
- def test_no_error_text(self) -> None:
316
- """Test with empty or None error text."""
317
- assert analyze_error_for_hints("") is None
318
- assert analyze_error_for_hints(None) is None
319
-
320
- def test_no_matching_pattern(self) -> None:
321
- """Test with error that doesn't match any pattern."""
322
- hint = analyze_error_for_hints("Some random error message")
323
- assert hint is None
324
-
325
- def test_priority_ordering(self) -> None:
326
- """Test that higher priority hints are returned."""
327
- # This error matches both "No module" (priority 9) and "Exception" (priority 2)
328
- error = "Exception: No module named requests"
329
- hint = analyze_error_for_hints(error)
330
- assert hint is not None
331
- # Should get the higher priority hint (import error)
332
- assert "Missing Python dependencies" in hint
333
-
334
- def test_case_insensitive_matching(self) -> None:
335
- """Test that pattern matching is case insensitive."""
336
- errors = ["PERMISSION DENIED", "permission denied", "Permission Denied"]
337
-
338
- for error in errors:
339
- hint = analyze_error_for_hints(error)
340
- assert hint is not None
341
- assert "Permission issues" in hint
342
-
343
-
344
- class TestHintRegistry:
345
- """Test the hint registry structure."""
346
-
347
- def test_hint_registry_structure(self) -> None:
348
- """Test that HINT_REGISTRY has correct structure."""
349
- assert isinstance(HINT_REGISTRY, list)
350
- assert len(HINT_REGISTRY) > 0
351
-
352
- for hint_data in HINT_REGISTRY:
353
- assert "patterns" in hint_data
354
- assert "priority" in hint_data
355
- assert "hint" in hint_data
356
-
357
- assert isinstance(hint_data["patterns"], list)
358
- assert isinstance(hint_data["priority"], int)
359
- assert isinstance(hint_data["hint"], str)
360
-
361
- # All patterns should be strings
362
- for pattern in hint_data["patterns"]:
363
- assert isinstance(pattern, str)
364
-
365
- def test_hint_priorities_unique(self) -> None:
366
- """Test that hint priorities are reasonable."""
367
- priorities = [hint["priority"] for hint in HINT_REGISTRY]
368
-
369
- # Priorities should be positive
370
- assert all(p > 0 for p in priorities)
371
-
372
- # Should have a range of priorities
373
- assert max(priorities) > min(priorities)
374
-
375
-
376
- class TestWindowsSupport:
377
- """Test Windows-specific functionality."""
378
-
379
- @pytest.mark.skipif(sys.platform != "win32", reason="Windows only test")
380
- def test_windows_ansi_enable(self) -> None:
381
- """Test that ANSI is enabled on Windows."""
382
- # The module should call os.system("") on import
383
- # This is hard to test directly, but we can check platform detection
384
- assert sys.platform == "win32"
385
-
386
-
387
- if __name__ == "__main__":
388
- pytest.main([__file__])
1
+ """Tests for hud.cli.utils module."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from unittest.mock import patch
7
+
8
+ import pytest
9
+
10
+ from hud.cli.utils import HINT_REGISTRY, CaptureLogger, Colors, analyze_error_for_hints
11
+
12
+
13
+ class TestColors:
14
+ """Test ANSI color codes."""
15
+
16
+ def test_color_constants(self) -> None:
17
+ """Test that color constants are defined."""
18
+ assert Colors.HEADER == "\033[95m"
19
+ assert Colors.BLUE == "\033[94m"
20
+ assert Colors.CYAN == "\033[96m"
21
+ assert Colors.GREEN == "\033[92m"
22
+ assert Colors.YELLOW == "\033[93m"
23
+ assert Colors.GOLD == "\033[33m"
24
+ assert Colors.RED == "\033[91m"
25
+ assert Colors.GRAY == "\033[90m"
26
+ assert Colors.ENDC == "\033[0m"
27
+ assert Colors.BOLD == "\033[1m"
28
+
29
+
30
+ class TestCaptureLogger:
31
+ """Test CaptureLogger functionality."""
32
+
33
+ def test_logger_print_mode(self) -> None:
34
+ """Test logger in print mode."""
35
+ logger = CaptureLogger(print_output=True)
36
+
37
+ with patch("builtins.print") as mock_print:
38
+ logger._log("Test message", Colors.GREEN)
39
+ mock_print.assert_called_once_with(f"{Colors.GREEN}Test message{Colors.ENDC}")
40
+
41
+ def test_logger_capture_mode(self) -> None:
42
+ """Test logger in capture-only mode."""
43
+ logger = CaptureLogger(print_output=False)
44
+
45
+ with patch("builtins.print") as mock_print:
46
+ logger._log("Test message", Colors.GREEN)
47
+ mock_print.assert_not_called()
48
+
49
+ output = logger.get_output()
50
+ assert "Test message" in output
51
+
52
+ def test_strip_ansi(self) -> None:
53
+ """Test ANSI code stripping."""
54
+ logger = CaptureLogger(print_output=False)
55
+
56
+ # Test various ANSI sequences
57
+ text_with_ansi = (
58
+ f"{Colors.GREEN}Green text{Colors.ENDC} normal {Colors.BOLD}bold{Colors.ENDC}"
59
+ )
60
+ clean_text = logger._strip_ansi(text_with_ansi)
61
+ assert clean_text == "Green text normal bold"
62
+
63
+ def test_timestamp(self) -> None:
64
+ """Test timestamp generation."""
65
+ logger = CaptureLogger(print_output=False)
66
+
67
+ timestamp = logger.timestamp()
68
+ # Should be in HH:MM:SS format
69
+ assert len(timestamp) == 8
70
+ assert timestamp[2] == ":"
71
+ assert timestamp[5] == ":"
72
+
73
+ def test_phase_logging(self) -> None:
74
+ """Test phase header logging."""
75
+ logger = CaptureLogger(print_output=False)
76
+
77
+ logger.phase(1, "Test Phase")
78
+ output = logger.get_output()
79
+
80
+ assert "=" * 80 in output
81
+ assert "PHASE 1: Test Phase" in output
82
+
83
+ def test_command_logging(self) -> None:
84
+ """Test command logging."""
85
+ logger = CaptureLogger(print_output=False)
86
+
87
+ logger.command(["python", "script.py", "--arg", "value"])
88
+ output = logger.get_output()
89
+
90
+ assert "$ python script.py --arg value" in output
91
+
92
+ def test_success_logging(self) -> None:
93
+ """Test success message logging."""
94
+ logger = CaptureLogger(print_output=False)
95
+
96
+ logger.success("Operation completed")
97
+ output = logger.get_output()
98
+
99
+ assert "✅ Operation completed" in output
100
+
101
+ def test_error_logging(self) -> None:
102
+ """Test error message logging."""
103
+ logger = CaptureLogger(print_output=False)
104
+
105
+ logger.error("Operation failed")
106
+ output = logger.get_output()
107
+
108
+ assert "❌ Operation failed" in output
109
+
110
+ def test_info_logging(self) -> None:
111
+ """Test info message logging with timestamp."""
112
+ logger = CaptureLogger(print_output=False)
113
+
114
+ with patch.object(logger, "timestamp", return_value="12:34:56"):
115
+ logger.info("Information message")
116
+ output = logger.get_output()
117
+
118
+ assert "[12:34:56] Information message" in output
119
+
120
+ def test_stdio_logging(self) -> None:
121
+ """Test STDIO communication logging."""
122
+ logger = CaptureLogger(print_output=False)
123
+
124
+ logger.stdio("JSON-RPC message")
125
+ output = logger.get_output()
126
+
127
+ assert "[STDIO] JSON-RPC message" in output
128
+
129
+ def test_stderr_logging(self) -> None:
130
+ """Test STDERR output logging."""
131
+ logger = CaptureLogger(print_output=False)
132
+
133
+ logger.stderr("Error output from server")
134
+ output = logger.get_output()
135
+
136
+ assert "[STDERR] Error output from server" in output
137
+
138
+ def test_hint_logging(self) -> None:
139
+ """Test hint message logging."""
140
+ logger = CaptureLogger(print_output=False)
141
+
142
+ logger.hint("Try checking the configuration")
143
+ output = logger.get_output()
144
+
145
+ assert "💡 Hint: Try checking the configuration" in output
146
+
147
+ def test_progress_bar(self) -> None:
148
+ """Test progress bar visualization."""
149
+ logger = CaptureLogger(print_output=False)
150
+
151
+ # Test partial progress
152
+ logger.progress_bar(3, 5)
153
+ output = logger.get_output()
154
+
155
+ assert "Progress: [███░░] 3/5 phases (60%)" in output
156
+ assert "Failed at Phase 4" in output
157
+
158
+ # Test complete progress
159
+ logger = CaptureLogger(print_output=False)
160
+ logger.progress_bar(5, 5)
161
+ output = logger.get_output()
162
+
163
+ assert "Progress: [█████] 5/5 phases (100%)" in output
164
+ assert "All phases completed successfully!" in output
165
+
166
+ def test_progress_bar_failure_messages(self) -> None:
167
+ """Test progress bar failure messages at different phases."""
168
+ test_cases = [
169
+ (0, "Failed at Phase 1 - Server startup"),
170
+ (1, "Failed at Phase 2 - MCP initialization"),
171
+ (2, "Failed at Phase 3 - Tool discovery"),
172
+ (3, "Failed at Phase 4 - Remote deployment readiness"),
173
+ (4, "Failed at Phase 5 - Concurrent clients & resources"),
174
+ ]
175
+
176
+ for completed, expected_msg in test_cases:
177
+ logger = CaptureLogger(print_output=False)
178
+ logger.progress_bar(completed, 5)
179
+ output = logger.get_output()
180
+ assert expected_msg in output
181
+
182
+ def test_get_output(self) -> None:
183
+ """Test getting accumulated output."""
184
+ logger = CaptureLogger(print_output=False)
185
+
186
+ logger.info("First message")
187
+ logger.error("Second message")
188
+ logger.success("Third message")
189
+
190
+ output = logger.get_output()
191
+ assert "First message" in output
192
+ assert "Second message" in output
193
+ assert "Third message" in output
194
+
195
+
196
+ class TestAnalyzeErrorForHints:
197
+ """Test error analysis and hint generation."""
198
+
199
+ def test_x11_display_errors(self) -> None:
200
+ """Test X11/display related error hints."""
201
+ errors = [
202
+ "Can't connect to display :0",
203
+ "X11 connection rejected",
204
+ "DISPLAY environment variable not set",
205
+ "Xlib.error.DisplayConnectionError",
206
+ ]
207
+
208
+ for error in errors:
209
+ hint = analyze_error_for_hints(error)
210
+ assert hint is not None
211
+ assert "GUI environment needs X11" in hint
212
+ assert "Xvfb" in hint
213
+
214
+ def test_import_errors(self) -> None:
215
+ """Test import/module error hints."""
216
+ errors = [
217
+ "ModuleNotFoundError: No module named 'requests'",
218
+ "ImportError: cannot import name 'api'",
219
+ "No module named numpy",
220
+ ]
221
+
222
+ for error in errors:
223
+ hint = analyze_error_for_hints(error)
224
+ assert hint is not None
225
+ assert "Missing Python dependencies" in hint
226
+ assert "pyproject.toml" in hint
227
+
228
+ def test_json_errors(self) -> None:
229
+ """Test JSON parsing error hints."""
230
+ errors = [
231
+ "json.decoder.JSONDecodeError: Expecting value",
232
+ "JSONDecodeError: Expecting value: line 1 column 1",
233
+ ]
234
+
235
+ for error in errors:
236
+ hint = analyze_error_for_hints(error)
237
+ assert hint is not None
238
+ assert "Invalid JSON-RPC communication" in hint
239
+ assert "proper JSON-RPC format" in hint
240
+
241
+ def test_permission_errors(self) -> None:
242
+ """Test permission error hints."""
243
+ errors = [
244
+ "Permission denied: /var/log/app.log",
245
+ "EACCES: permission denied",
246
+ "Operation not permitted",
247
+ ]
248
+
249
+ for error in errors:
250
+ hint = analyze_error_for_hints(error)
251
+ assert hint is not None
252
+ assert "Permission issues" in hint
253
+ assert "Check file permissions" in hint
254
+
255
+ def test_memory_errors(self) -> None:
256
+ """Test memory/resource error hints."""
257
+ errors = ["Cannot allocate memory", "Process killed", "Container OOMKilled"]
258
+
259
+ for error in errors:
260
+ hint = analyze_error_for_hints(error)
261
+ assert hint is not None
262
+ assert "Resource limits exceeded" in hint
263
+ assert "memory limits" in hint
264
+
265
+ def test_port_errors(self) -> None:
266
+ """Test port binding error hints."""
267
+ errors = [
268
+ "bind: address already in use",
269
+ "EADDRINUSE: address already in use",
270
+ "port 8080 already allocated",
271
+ ]
272
+
273
+ for error in errors:
274
+ hint = analyze_error_for_hints(error)
275
+ assert hint is not None
276
+ assert "Port conflict detected" in hint
277
+ assert "different port" in hint
278
+
279
+ def test_file_not_found_errors(self) -> None:
280
+ """Test file not found error hints."""
281
+ errors = [
282
+ "FileNotFoundError: [Errno 2] No such file or directory",
283
+ "No such file or directory: config.json",
284
+ ]
285
+
286
+ for error in errors:
287
+ hint = analyze_error_for_hints(error)
288
+ assert hint is not None
289
+ assert "File or directory missing" in hint
290
+ assert "required files exist" in hint
291
+
292
+ def test_traceback_errors(self) -> None:
293
+ """Test general traceback error hints."""
294
+ error = """Traceback (most recent call last):
295
+ File "app.py", line 10, in <module>
296
+ import missing_module
297
+ ImportError: No module named missing_module"""
298
+
299
+ hint = analyze_error_for_hints(error)
300
+ assert hint is not None
301
+ # Should match both traceback and import patterns
302
+ # Import has higher priority
303
+ assert "Missing Python dependencies" in hint
304
+
305
+ def test_timeout_errors(self) -> None:
306
+ """Test timeout error hints."""
307
+ errors = ["Operation timed out after 30 seconds", "Connection timeout", "Request timed out"]
308
+
309
+ for error in errors:
310
+ hint = analyze_error_for_hints(error)
311
+ assert hint is not None
312
+ assert "Server taking too long to start" in hint
313
+ assert "slow operations" in hint
314
+
315
+ def test_no_error_text(self) -> None:
316
+ """Test with empty or None error text."""
317
+ assert analyze_error_for_hints("") is None
318
+ assert analyze_error_for_hints(None) is None
319
+
320
+ def test_no_matching_pattern(self) -> None:
321
+ """Test with error that doesn't match any pattern."""
322
+ hint = analyze_error_for_hints("Some random error message")
323
+ assert hint is None
324
+
325
+ def test_priority_ordering(self) -> None:
326
+ """Test that higher priority hints are returned."""
327
+ # This error matches both "No module" (priority 9) and "Exception" (priority 2)
328
+ error = "Exception: No module named requests"
329
+ hint = analyze_error_for_hints(error)
330
+ assert hint is not None
331
+ # Should get the higher priority hint (import error)
332
+ assert "Missing Python dependencies" in hint
333
+
334
+ def test_case_insensitive_matching(self) -> None:
335
+ """Test that pattern matching is case insensitive."""
336
+ errors = ["PERMISSION DENIED", "permission denied", "Permission Denied"]
337
+
338
+ for error in errors:
339
+ hint = analyze_error_for_hints(error)
340
+ assert hint is not None
341
+ assert "Permission issues" in hint
342
+
343
+
344
+ class TestHintRegistry:
345
+ """Test the hint registry structure."""
346
+
347
+ def test_hint_registry_structure(self) -> None:
348
+ """Test that HINT_REGISTRY has correct structure."""
349
+ assert isinstance(HINT_REGISTRY, list)
350
+ assert len(HINT_REGISTRY) > 0
351
+
352
+ for hint_data in HINT_REGISTRY:
353
+ assert "patterns" in hint_data
354
+ assert "priority" in hint_data
355
+ assert "hint" in hint_data
356
+
357
+ assert isinstance(hint_data["patterns"], list)
358
+ assert isinstance(hint_data["priority"], int)
359
+ assert isinstance(hint_data["hint"], str)
360
+
361
+ # All patterns should be strings
362
+ for pattern in hint_data["patterns"]:
363
+ assert isinstance(pattern, str)
364
+
365
+ def test_hint_priorities_unique(self) -> None:
366
+ """Test that hint priorities are reasonable."""
367
+ priorities = [hint["priority"] for hint in HINT_REGISTRY]
368
+
369
+ # Priorities should be positive
370
+ assert all(p > 0 for p in priorities)
371
+
372
+ # Should have a range of priorities
373
+ assert max(priorities) > min(priorities)
374
+
375
+
376
+ class TestWindowsSupport:
377
+ """Test Windows-specific functionality."""
378
+
379
+ @pytest.mark.skipif(sys.platform != "win32", reason="Windows only test")
380
+ def test_windows_ansi_enable(self) -> None:
381
+ """Test that ANSI is enabled on Windows."""
382
+ # The module should call os.system("") on import
383
+ # This is hard to test directly, but we can check platform detection
384
+ assert sys.platform == "win32"
385
+
386
+
387
+ if __name__ == "__main__":
388
+ pytest.main([__file__])