hud-python 0.4.52__py3-none-any.whl → 0.4.54__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 (70) hide show
  1. hud/agents/base.py +9 -2
  2. hud/agents/openai_chat_generic.py +15 -3
  3. hud/agents/tests/test_base.py +15 -0
  4. hud/agents/tests/test_base_runtime.py +164 -0
  5. hud/cli/__init__.py +20 -12
  6. hud/cli/build.py +35 -27
  7. hud/cli/dev.py +13 -31
  8. hud/cli/eval.py +85 -84
  9. hud/cli/tests/test_analyze_module.py +120 -0
  10. hud/cli/tests/test_build.py +24 -2
  11. hud/cli/tests/test_build_failure.py +41 -0
  12. hud/cli/tests/test_build_module.py +50 -0
  13. hud/cli/tests/test_cli_more_wrappers.py +30 -0
  14. hud/cli/tests/test_cli_root.py +134 -0
  15. hud/cli/tests/test_eval.py +6 -6
  16. hud/cli/tests/test_mcp_server.py +8 -7
  17. hud/cli/tests/test_push_happy.py +74 -0
  18. hud/cli/tests/test_push_wrapper.py +23 -0
  19. hud/cli/utils/docker.py +120 -1
  20. hud/cli/utils/runner.py +1 -1
  21. hud/cli/utils/tests/__init__.py +0 -0
  22. hud/cli/utils/tests/test_config.py +58 -0
  23. hud/cli/utils/tests/test_docker.py +93 -0
  24. hud/cli/utils/tests/test_docker_hints.py +71 -0
  25. hud/cli/utils/tests/test_env_check.py +74 -0
  26. hud/cli/utils/tests/test_environment.py +42 -0
  27. hud/cli/utils/tests/test_interactive_module.py +60 -0
  28. hud/cli/utils/tests/test_local_runner.py +50 -0
  29. hud/cli/utils/tests/test_logging_utils.py +23 -0
  30. hud/cli/utils/tests/test_metadata.py +49 -0
  31. hud/cli/utils/tests/test_package_runner.py +35 -0
  32. hud/cli/utils/tests/test_registry_utils.py +49 -0
  33. hud/cli/utils/tests/test_remote_runner.py +25 -0
  34. hud/cli/utils/tests/test_runner_modules.py +52 -0
  35. hud/cli/utils/tests/test_source_hash.py +36 -0
  36. hud/cli/utils/tests/test_tasks.py +80 -0
  37. hud/cli/utils/version_check.py +2 -2
  38. hud/datasets/tests/__init__.py +0 -0
  39. hud/datasets/tests/test_runner.py +106 -0
  40. hud/datasets/tests/test_utils.py +228 -0
  41. hud/otel/tests/__init__.py +0 -1
  42. hud/otel/tests/test_instrumentation.py +207 -0
  43. hud/server/tests/test_server_extra.py +2 -0
  44. hud/shared/exceptions.py +35 -4
  45. hud/shared/hints.py +25 -0
  46. hud/shared/requests.py +15 -3
  47. hud/shared/tests/test_exceptions.py +31 -23
  48. hud/shared/tests/test_hints.py +167 -0
  49. hud/telemetry/tests/test_async_context.py +242 -0
  50. hud/telemetry/tests/test_instrument.py +414 -0
  51. hud/telemetry/tests/test_job.py +609 -0
  52. hud/telemetry/tests/test_trace.py +183 -5
  53. hud/tools/computer/settings.py +2 -2
  54. hud/tools/tests/test_submit.py +85 -0
  55. hud/tools/tests/test_types.py +193 -0
  56. hud/types.py +17 -1
  57. hud/utils/agent_factories.py +1 -3
  58. hud/utils/mcp.py +1 -1
  59. hud/utils/tests/test_agent_factories.py +60 -0
  60. hud/utils/tests/test_mcp.py +4 -6
  61. hud/utils/tests/test_pretty_errors.py +186 -0
  62. hud/utils/tests/test_tasks.py +187 -0
  63. hud/utils/tests/test_tool_shorthand.py +154 -0
  64. hud/utils/tests/test_version.py +1 -1
  65. hud/version.py +1 -1
  66. {hud_python-0.4.52.dist-info → hud_python-0.4.54.dist-info}/METADATA +49 -49
  67. {hud_python-0.4.52.dist-info → hud_python-0.4.54.dist-info}/RECORD +70 -32
  68. {hud_python-0.4.52.dist-info → hud_python-0.4.54.dist-info}/WHEEL +0 -0
  69. {hud_python-0.4.52.dist-info → hud_python-0.4.54.dist-info}/entry_points.txt +0 -0
  70. {hud_python-0.4.52.dist-info → hud_python-0.4.54.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,609 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime
4
+ from unittest.mock import AsyncMock, patch
5
+
6
+ import pytest
7
+
8
+ from hud.telemetry.job import (
9
+ Job,
10
+ _print_job_complete_url,
11
+ _print_job_url,
12
+ create_job,
13
+ get_current_job,
14
+ job,
15
+ job_decorator,
16
+ )
17
+
18
+
19
+ def test_job_initialization():
20
+ """Test Job initialization with all parameters."""
21
+ job_obj = Job(
22
+ job_id="test-id",
23
+ name="Test Job",
24
+ metadata={"key": "value"},
25
+ dataset_link="test/dataset",
26
+ )
27
+
28
+ assert job_obj.id == "test-id"
29
+ assert job_obj.name == "Test Job"
30
+ assert job_obj.metadata == {"key": "value"}
31
+ assert job_obj.dataset_link == "test/dataset"
32
+ assert job_obj.status == "created"
33
+ assert isinstance(job_obj.created_at, datetime)
34
+ assert job_obj.tasks == []
35
+
36
+
37
+ def test_job_initialization_minimal():
38
+ """Test Job initialization with minimal parameters."""
39
+ job_obj = Job(job_id="test-id", name="Test")
40
+
41
+ assert job_obj.metadata == {}
42
+ assert job_obj.dataset_link is None
43
+
44
+
45
+ def test_job_add_task():
46
+ """Test adding tasks to a job."""
47
+ job_obj = Job(job_id="test-id", name="Test")
48
+
49
+ job_obj.add_task("task1")
50
+ job_obj.add_task("task2")
51
+
52
+ assert job_obj.tasks == ["task1", "task2"]
53
+
54
+
55
+ @pytest.mark.asyncio
56
+ async def test_job_update_status_async():
57
+ """Test async status update."""
58
+ job_obj = Job(job_id="test-id", name="Test")
59
+
60
+ with (
61
+ patch("hud.telemetry.job.settings") as mock_settings,
62
+ patch("hud.telemetry.job.make_request", new_callable=AsyncMock) as mock_request,
63
+ ):
64
+ mock_settings.telemetry_enabled = True
65
+ mock_settings.api_key = "test_key"
66
+ mock_settings.hud_telemetry_url = "https://test.com"
67
+
68
+ await job_obj.update_status("running")
69
+
70
+ assert job_obj.status == "running"
71
+ mock_request.assert_called_once()
72
+ call_kwargs = mock_request.call_args[1]
73
+ assert call_kwargs["method"] == "POST"
74
+ assert "test-id" in call_kwargs["url"]
75
+ assert call_kwargs["json"]["status"] == "running"
76
+
77
+
78
+ @pytest.mark.asyncio
79
+ async def test_job_update_status_async_with_dataset():
80
+ """Test async status update includes dataset link."""
81
+ job_obj = Job(job_id="test-id", name="Test", dataset_link="test/dataset")
82
+
83
+ with (
84
+ patch("hud.telemetry.job.settings") as mock_settings,
85
+ patch("hud.telemetry.job.make_request", new_callable=AsyncMock) as mock_request,
86
+ ):
87
+ mock_settings.telemetry_enabled = True
88
+ mock_settings.api_key = "test_key"
89
+ mock_settings.hud_telemetry_url = "https://test.com"
90
+
91
+ await job_obj.update_status("running")
92
+
93
+ call_kwargs = mock_request.call_args[1]
94
+ assert call_kwargs["json"]["dataset_link"] == "test/dataset"
95
+
96
+
97
+ @pytest.mark.asyncio
98
+ async def test_job_update_status_async_telemetry_disabled():
99
+ """Test async status update when telemetry is disabled."""
100
+ job_obj = Job(job_id="test-id", name="Test")
101
+
102
+ with (
103
+ patch("hud.telemetry.job.settings") as mock_settings,
104
+ patch("hud.telemetry.job.make_request", new_callable=AsyncMock) as mock_request,
105
+ ):
106
+ mock_settings.telemetry_enabled = False
107
+
108
+ await job_obj.update_status("running")
109
+
110
+ assert job_obj.status == "running"
111
+ mock_request.assert_not_called()
112
+
113
+
114
+ @pytest.mark.asyncio
115
+ async def test_job_update_status_async_error():
116
+ """Test async status update handles errors gracefully."""
117
+ job_obj = Job(job_id="test-id", name="Test")
118
+
119
+ with (
120
+ patch("hud.telemetry.job.settings") as mock_settings,
121
+ patch("hud.telemetry.job.make_request", new_callable=AsyncMock) as mock_request,
122
+ ):
123
+ mock_settings.telemetry_enabled = True
124
+ mock_settings.api_key = "test_key"
125
+ mock_settings.hud_telemetry_url = "https://test.com"
126
+ mock_request.side_effect = Exception("Network error")
127
+
128
+ # Should not raise
129
+ await job_obj.update_status("running")
130
+ assert job_obj.status == "running"
131
+
132
+
133
+ def test_job_update_status_sync():
134
+ """Test sync status update."""
135
+ job_obj = Job(job_id="test-id", name="Test")
136
+
137
+ with (
138
+ patch("hud.telemetry.job.settings") as mock_settings,
139
+ patch("hud.telemetry.job.make_request_sync") as mock_request,
140
+ ):
141
+ mock_settings.telemetry_enabled = True
142
+ mock_settings.api_key = "test_key"
143
+ mock_settings.hud_telemetry_url = "https://test.com"
144
+
145
+ job_obj.update_status_sync("completed")
146
+
147
+ assert job_obj.status == "completed"
148
+ mock_request.assert_called_once()
149
+
150
+
151
+ def test_job_update_status_sync_with_dataset():
152
+ """Test sync status update includes dataset link."""
153
+ job_obj = Job(job_id="test-id", name="Test", dataset_link="test/dataset")
154
+
155
+ with (
156
+ patch("hud.telemetry.job.settings") as mock_settings,
157
+ patch("hud.telemetry.job.make_request_sync") as mock_request,
158
+ ):
159
+ mock_settings.telemetry_enabled = True
160
+ mock_settings.api_key = "test_key"
161
+ mock_settings.hud_telemetry_url = "https://test.com"
162
+
163
+ job_obj.update_status_sync("completed")
164
+
165
+ call_kwargs = mock_request.call_args[1]
166
+ assert call_kwargs["json"]["dataset_link"] == "test/dataset"
167
+
168
+
169
+ def test_job_update_status_sync_telemetry_disabled():
170
+ """Test sync status update when telemetry is disabled."""
171
+ job_obj = Job(job_id="test-id", name="Test")
172
+
173
+ with (
174
+ patch("hud.telemetry.job.settings") as mock_settings,
175
+ patch("hud.telemetry.job.make_request_sync") as mock_request,
176
+ ):
177
+ mock_settings.telemetry_enabled = False
178
+
179
+ job_obj.update_status_sync("completed")
180
+
181
+ mock_request.assert_not_called()
182
+
183
+
184
+ def test_job_update_status_sync_error():
185
+ """Test sync status update handles errors gracefully."""
186
+ job_obj = Job(job_id="test-id", name="Test")
187
+
188
+ with (
189
+ patch("hud.telemetry.job.settings") as mock_settings,
190
+ patch("hud.telemetry.job.make_request_sync") as mock_request,
191
+ ):
192
+ mock_settings.telemetry_enabled = True
193
+ mock_settings.api_key = "test_key"
194
+ mock_settings.hud_telemetry_url = "https://test.com"
195
+ mock_request.side_effect = Exception("Network error")
196
+
197
+ # Should not raise
198
+ job_obj.update_status_sync("completed")
199
+
200
+
201
+ def test_job_update_status_fire_and_forget():
202
+ """Test fire-and-forget status update."""
203
+ job_obj = Job(job_id="test-id", name="Test")
204
+
205
+ with (
206
+ patch("hud.telemetry.job.settings") as mock_settings,
207
+ patch("hud.utils.async_utils.fire_and_forget") as mock_fire,
208
+ ):
209
+ mock_settings.telemetry_enabled = True
210
+
211
+ job_obj.update_status_fire_and_forget("running")
212
+
213
+ assert job_obj.status == "running"
214
+ mock_fire.assert_called_once()
215
+
216
+
217
+ def test_job_update_status_fire_and_forget_with_dataset():
218
+ """Test fire-and-forget update includes dataset link."""
219
+ job_obj = Job(job_id="test-id", name="Test", dataset_link="test/dataset")
220
+
221
+ with (
222
+ patch("hud.telemetry.job.settings") as mock_settings,
223
+ patch("hud.utils.async_utils.fire_and_forget"),
224
+ ):
225
+ mock_settings.telemetry_enabled = True
226
+
227
+ job_obj.update_status_fire_and_forget("running")
228
+
229
+ assert job_obj.status == "running"
230
+
231
+
232
+ def test_job_update_status_fire_and_forget_telemetry_disabled():
233
+ """Test fire-and-forget when telemetry is disabled."""
234
+ job_obj = Job(job_id="test-id", name="Test")
235
+
236
+ with (
237
+ patch("hud.telemetry.job.settings") as mock_settings,
238
+ patch("hud.utils.async_utils.fire_and_forget") as mock_fire,
239
+ ):
240
+ mock_settings.telemetry_enabled = False
241
+
242
+ job_obj.update_status_fire_and_forget("running")
243
+
244
+ mock_fire.assert_not_called()
245
+
246
+
247
+ @pytest.mark.asyncio
248
+ async def test_job_log():
249
+ """Test async log method."""
250
+ job_obj = Job(job_id="test-id", name="Test")
251
+
252
+ with (
253
+ patch("hud.telemetry.job.settings") as mock_settings,
254
+ patch("hud.telemetry.job.make_request", new_callable=AsyncMock) as mock_request,
255
+ ):
256
+ mock_settings.telemetry_enabled = True
257
+ mock_settings.api_key = "test_key"
258
+ mock_settings.hud_telemetry_url = "https://test.com"
259
+
260
+ await job_obj.log({"loss": 0.5, "accuracy": 0.95})
261
+
262
+ mock_request.assert_called_once()
263
+ call_kwargs = mock_request.call_args[1]
264
+ assert call_kwargs["json"]["metrics"] == {"loss": 0.5, "accuracy": 0.95}
265
+ assert "timestamp" in call_kwargs["json"]
266
+
267
+
268
+ @pytest.mark.asyncio
269
+ async def test_job_log_telemetry_disabled():
270
+ """Test async log when telemetry is disabled."""
271
+ job_obj = Job(job_id="test-id", name="Test")
272
+
273
+ with (
274
+ patch("hud.telemetry.job.settings") as mock_settings,
275
+ patch("hud.telemetry.job.make_request", new_callable=AsyncMock) as mock_request,
276
+ ):
277
+ mock_settings.telemetry_enabled = False
278
+
279
+ await job_obj.log({"loss": 0.5})
280
+
281
+ mock_request.assert_not_called()
282
+
283
+
284
+ @pytest.mark.asyncio
285
+ async def test_job_log_error():
286
+ """Test async log handles errors gracefully."""
287
+ job_obj = Job(job_id="test-id", name="Test")
288
+
289
+ with (
290
+ patch("hud.telemetry.job.settings") as mock_settings,
291
+ patch("hud.telemetry.job.make_request", new_callable=AsyncMock) as mock_request,
292
+ ):
293
+ mock_settings.telemetry_enabled = True
294
+ mock_settings.api_key = "test_key"
295
+ mock_settings.hud_telemetry_url = "https://test.com"
296
+ mock_request.side_effect = Exception("Network error")
297
+
298
+ # Should not raise
299
+ await job_obj.log({"loss": 0.5})
300
+
301
+
302
+ def test_job_log_sync():
303
+ """Test sync log method."""
304
+ job_obj = Job(job_id="test-id", name="Test")
305
+
306
+ with (
307
+ patch("hud.telemetry.job.settings") as mock_settings,
308
+ patch("hud.telemetry.job.make_request_sync") as mock_request,
309
+ ):
310
+ mock_settings.telemetry_enabled = True
311
+ mock_settings.api_key = "test_key"
312
+ mock_settings.hud_telemetry_url = "https://test.com"
313
+
314
+ job_obj.log_sync({"loss": 0.5, "accuracy": 0.95})
315
+
316
+ mock_request.assert_called_once()
317
+ call_kwargs = mock_request.call_args[1]
318
+ assert call_kwargs["json"]["metrics"] == {"loss": 0.5, "accuracy": 0.95}
319
+
320
+
321
+ def test_job_log_sync_telemetry_disabled():
322
+ """Test sync log when telemetry is disabled."""
323
+ job_obj = Job(job_id="test-id", name="Test")
324
+
325
+ with (
326
+ patch("hud.telemetry.job.settings") as mock_settings,
327
+ patch("hud.telemetry.job.make_request_sync") as mock_request,
328
+ ):
329
+ mock_settings.telemetry_enabled = False
330
+
331
+ job_obj.log_sync({"loss": 0.5})
332
+
333
+ mock_request.assert_not_called()
334
+
335
+
336
+ def test_job_log_sync_error():
337
+ """Test sync log handles errors gracefully."""
338
+ job_obj = Job(job_id="test-id", name="Test")
339
+
340
+ with (
341
+ patch("hud.telemetry.job.settings") as mock_settings,
342
+ patch("hud.telemetry.job.make_request_sync") as mock_request,
343
+ ):
344
+ mock_settings.telemetry_enabled = True
345
+ mock_settings.api_key = "test_key"
346
+ mock_settings.hud_telemetry_url = "https://test.com"
347
+ mock_request.side_effect = Exception("Network error")
348
+
349
+ # Should not raise
350
+ job_obj.log_sync({"loss": 0.5})
351
+
352
+
353
+ def test_job_repr():
354
+ """Test Job __repr__."""
355
+ job_obj = Job(job_id="test-id", name="Test Job")
356
+ job_obj.status = "running"
357
+
358
+ repr_str = repr(job_obj)
359
+ assert "test-id" in repr_str
360
+ assert "Test Job" in repr_str
361
+ assert "running" in repr_str
362
+
363
+
364
+ def test_print_job_url_enabled():
365
+ """Test _print_job_url when telemetry is enabled."""
366
+ with (
367
+ patch("hud.telemetry.job.settings") as mock_settings,
368
+ patch("builtins.print") as mock_print,
369
+ ):
370
+ mock_settings.telemetry_enabled = True
371
+ mock_settings.api_key = "test_key"
372
+
373
+ _print_job_url("job-123", "My Job")
374
+
375
+ # Should print multiple lines (box)
376
+ assert mock_print.call_count > 0
377
+
378
+
379
+ def test_print_job_url_disabled():
380
+ """Test _print_job_url when telemetry is disabled."""
381
+ with (
382
+ patch("hud.telemetry.job.settings") as mock_settings,
383
+ patch("builtins.print") as mock_print,
384
+ ):
385
+ mock_settings.telemetry_enabled = False
386
+
387
+ _print_job_url("job-123", "My Job")
388
+
389
+ mock_print.assert_not_called()
390
+
391
+
392
+ def test_print_job_url_no_api_key():
393
+ """Test _print_job_url when no API key is set."""
394
+ with (
395
+ patch("hud.telemetry.job.settings") as mock_settings,
396
+ patch("builtins.print") as mock_print,
397
+ ):
398
+ mock_settings.telemetry_enabled = True
399
+ mock_settings.api_key = None
400
+
401
+ _print_job_url("job-123", "My Job")
402
+
403
+ mock_print.assert_not_called()
404
+
405
+
406
+ def test_print_job_complete_url_success():
407
+ """Test _print_job_complete_url for successful completion."""
408
+ with (
409
+ patch("hud.telemetry.job.settings") as mock_settings,
410
+ patch("builtins.print") as mock_print,
411
+ ):
412
+ mock_settings.telemetry_enabled = True
413
+ mock_settings.api_key = "test_key"
414
+
415
+ _print_job_complete_url("job-123", "My Job", error_occurred=False)
416
+
417
+ mock_print.assert_called_once()
418
+ call_str = str(mock_print.call_args)
419
+ assert "complete" in call_str.lower() or "✓" in call_str
420
+
421
+
422
+ def test_print_job_complete_url_failure():
423
+ """Test _print_job_complete_url for failed completion."""
424
+ with (
425
+ patch("hud.telemetry.job.settings") as mock_settings,
426
+ patch("builtins.print") as mock_print,
427
+ ):
428
+ mock_settings.telemetry_enabled = True
429
+ mock_settings.api_key = "test_key"
430
+
431
+ _print_job_complete_url("job-123", "My Job", error_occurred=True)
432
+
433
+ mock_print.assert_called_once()
434
+ call_str = str(mock_print.call_args)
435
+ assert "fail" in call_str.lower() or "✗" in call_str
436
+
437
+
438
+ def test_print_job_complete_url_disabled():
439
+ """Test _print_job_complete_url when telemetry is disabled."""
440
+ with (
441
+ patch("hud.telemetry.job.settings") as mock_settings,
442
+ patch("builtins.print") as mock_print,
443
+ ):
444
+ mock_settings.telemetry_enabled = False
445
+
446
+ _print_job_complete_url("job-123", "My Job")
447
+
448
+ mock_print.assert_not_called()
449
+
450
+
451
+ def test_get_current_job_none():
452
+ """Test get_current_job when no job is active."""
453
+ result = get_current_job()
454
+ assert result is None
455
+
456
+
457
+ def test_job_context_manager():
458
+ """Test job context manager."""
459
+ with (
460
+ patch("hud.telemetry.job.settings") as mock_settings,
461
+ patch("hud.utils.async_utils.fire_and_forget"),
462
+ patch("builtins.print"),
463
+ ):
464
+ mock_settings.telemetry_enabled = True
465
+ mock_settings.api_key = "test_key"
466
+
467
+ with job("Test Job", {"key": "value"}) as job_obj:
468
+ assert job_obj.name == "Test Job"
469
+ assert job_obj.metadata == {"key": "value"}
470
+ assert get_current_job() == job_obj
471
+
472
+ # After context, job should be cleared
473
+ assert get_current_job() is None
474
+
475
+
476
+ def test_job_context_manager_with_job_id():
477
+ """Test job context manager with explicit job_id."""
478
+ with (
479
+ patch("hud.telemetry.job.settings") as mock_settings,
480
+ patch("hud.utils.async_utils.fire_and_forget"),
481
+ patch("builtins.print"),
482
+ ):
483
+ mock_settings.telemetry_enabled = True
484
+ mock_settings.api_key = "test_key"
485
+
486
+ with job("Test", job_id="my-custom-id") as job_obj:
487
+ assert job_obj.id == "my-custom-id"
488
+
489
+
490
+ def test_job_context_manager_with_dataset_link():
491
+ """Test job context manager with dataset link."""
492
+ with (
493
+ patch("hud.telemetry.job.settings") as mock_settings,
494
+ patch("hud.utils.async_utils.fire_and_forget"),
495
+ patch("builtins.print"),
496
+ ):
497
+ mock_settings.telemetry_enabled = True
498
+ mock_settings.api_key = "test_key"
499
+
500
+ with job("Test", dataset_link="test/dataset") as job_obj:
501
+ assert job_obj.dataset_link == "test/dataset"
502
+
503
+
504
+ def test_job_context_manager_exception():
505
+ """Test job context manager handles exceptions."""
506
+ with (
507
+ patch("hud.telemetry.job.settings") as mock_settings,
508
+ patch("hud.utils.async_utils.fire_and_forget"),
509
+ patch("builtins.print"),
510
+ ):
511
+ mock_settings.telemetry_enabled = True
512
+ mock_settings.api_key = "test_key"
513
+
514
+ with pytest.raises(ValueError), job("Test"):
515
+ raise ValueError("Test error")
516
+
517
+ # Job should be cleared even after exception
518
+ assert get_current_job() is None
519
+
520
+
521
+ def test_create_job():
522
+ """Test create_job function."""
523
+ job_obj = create_job("Test Job", {"key": "value"}, dataset_link="test/dataset")
524
+
525
+ assert job_obj.name == "Test Job"
526
+ assert job_obj.metadata == {"key": "value"}
527
+ assert job_obj.dataset_link == "test/dataset"
528
+ assert job_obj.id # Should have an auto-generated ID
529
+
530
+
531
+ def test_create_job_with_job_id():
532
+ """Test create_job with explicit job_id."""
533
+ job_obj = create_job("Test", job_id="custom-id")
534
+
535
+ assert job_obj.id == "custom-id"
536
+
537
+
538
+ @pytest.mark.asyncio
539
+ async def test_job_decorator_async():
540
+ """Test job_decorator on async function."""
541
+ with (
542
+ patch("hud.telemetry.job.settings") as mock_settings,
543
+ patch("hud.utils.async_utils.fire_and_forget"),
544
+ patch("builtins.print"),
545
+ ):
546
+ mock_settings.telemetry_enabled = True
547
+ mock_settings.api_key = "test_key"
548
+
549
+ @job_decorator("test_job", model="gpt-4")
550
+ async def test_func(x: int) -> int:
551
+ return x * 2
552
+
553
+ result = await test_func(5)
554
+ assert result == 10
555
+
556
+
557
+ def test_job_decorator_sync():
558
+ """Test job_decorator on sync function."""
559
+ with (
560
+ patch("hud.telemetry.job.settings") as mock_settings,
561
+ patch("hud.utils.async_utils.fire_and_forget"),
562
+ patch("builtins.print"),
563
+ ):
564
+ mock_settings.telemetry_enabled = True
565
+ mock_settings.api_key = "test_key"
566
+
567
+ @job_decorator("test_job", model="gpt-4")
568
+ def test_func(x: int) -> int:
569
+ return x * 2
570
+
571
+ result = test_func(5)
572
+ assert result == 10
573
+
574
+
575
+ @pytest.mark.asyncio
576
+ async def test_job_decorator_async_default_name():
577
+ """Test job_decorator uses function name as default."""
578
+ with (
579
+ patch("hud.telemetry.job.settings") as mock_settings,
580
+ patch("hud.utils.async_utils.fire_and_forget"),
581
+ patch("builtins.print"),
582
+ ):
583
+ mock_settings.telemetry_enabled = True
584
+ mock_settings.api_key = "test_key"
585
+
586
+ @job_decorator()
587
+ async def my_function():
588
+ return "success"
589
+
590
+ result = await my_function()
591
+ assert result == "success"
592
+
593
+
594
+ def test_job_decorator_sync_default_name():
595
+ """Test job_decorator sync uses function name as default."""
596
+ with (
597
+ patch("hud.telemetry.job.settings") as mock_settings,
598
+ patch("hud.utils.async_utils.fire_and_forget"),
599
+ patch("builtins.print"),
600
+ ):
601
+ mock_settings.telemetry_enabled = True
602
+ mock_settings.api_key = "test_key"
603
+
604
+ @job_decorator()
605
+ def my_function():
606
+ return "success"
607
+
608
+ result = my_function()
609
+ assert result == "success"