humalab 0.1.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.
@@ -0,0 +1,792 @@
1
+ import unittest
2
+ import numpy as np
3
+ from humalab.scenarios.scenario import Scenario
4
+
5
+
6
+ class ScenarioTest(unittest.TestCase):
7
+ """Unit tests for Scenario class."""
8
+
9
+ def setUp(self):
10
+ """Set up test fixtures before each test method."""
11
+ self.scenario = Scenario()
12
+
13
+ def tearDown(self):
14
+ """Clean up after each test method."""
15
+ self.scenario._clear_resolvers()
16
+
17
+ def test_init_should_initialize_with_empty_scenario(self):
18
+ """Test that init() initializes with empty scenario when none provided."""
19
+ # Pre-condition
20
+ self.assertIsNone(self.scenario._scenario_id)
21
+
22
+ # In-test
23
+ self.scenario.init(
24
+ scenario=None,
25
+ seed=42
26
+ )
27
+
28
+ # Post-condition
29
+ self.assertIsNotNone(self.scenario._scenario_id)
30
+ self.assertEqual(len(self.scenario._scenario_template), 0)
31
+
32
+ def test_init_should_initialize_with_dict_scenario(self):
33
+ """Test that init() correctly processes dict-based scenario."""
34
+ # Pre-condition
35
+ scenario_dict = {
36
+ "test_key": "test_value",
37
+ "nested": {"inner_key": "inner_value"}
38
+ }
39
+
40
+ # In-test
41
+ self.scenario.init(
42
+ scenario=scenario_dict,
43
+ seed=42
44
+ )
45
+
46
+ # Post-condition
47
+ resolved, _ = self.scenario.resolve()
48
+ self.assertEqual(resolved.test_key, "test_value")
49
+ self.assertEqual(resolved.nested.inner_key, "inner_value")
50
+
51
+ def test_init_should_use_provided_scenario_id(self):
52
+ """Test that init() uses provided scenario_id."""
53
+ # Pre-condition
54
+ custom_id = "custom_scenario_id"
55
+
56
+ # In-test
57
+ self.scenario.init(
58
+ scenario={},
59
+ scenario_id=custom_id
60
+ )
61
+
62
+ # Post-condition
63
+ self.assertEqual(self.scenario._scenario_id, custom_id)
64
+
65
+ def test_init_should_set_seed_for_reproducibility(self):
66
+ """Test that init() with same seed produces reproducible results."""
67
+ # Pre-condition
68
+ scenario_config = {"value": "${uniform: 0.0, 1.0}"}
69
+ seed = 42
70
+
71
+ # In-test
72
+ scenario1 = Scenario()
73
+ scenario1.init(
74
+ scenario=scenario_config,
75
+ seed=seed
76
+ )
77
+ resolved1, _ = scenario1.resolve()
78
+ value1 = resolved1.value
79
+
80
+ scenario2 = Scenario()
81
+ scenario2.init(
82
+ scenario=scenario_config,
83
+ seed=seed
84
+ )
85
+ resolved2, _ = scenario2.resolve()
86
+ value2 = resolved2.value
87
+
88
+ # Post-condition
89
+ self.assertEqual(value1, value2)
90
+
91
+ # Cleanup
92
+ scenario1._clear_resolvers()
93
+ scenario2._clear_resolvers()
94
+
95
+ def test_uniform_distribution_should_resolve_correctly(self):
96
+ """Test that uniform distribution resolver works correctly."""
97
+ # Pre-condition
98
+ scenario_config = {
99
+ "uniform_value": "${uniform: 0.0, 1.0}"
100
+ }
101
+
102
+ # In-test
103
+ self.scenario.init(
104
+ scenario=scenario_config,
105
+ seed=42
106
+ )
107
+
108
+ # Post-condition
109
+ resolved, _ = self.scenario.resolve()
110
+ value = resolved.uniform_value
111
+ self.assertIsInstance(value, (int, float))
112
+ self.assertGreaterEqual(value, 0.0)
113
+ self.assertLessEqual(value, 1.0)
114
+
115
+ def test_uniform_distribution_should_handle_size_parameter(self):
116
+ """Test that uniform_1d distribution returns list."""
117
+ # Pre-condition
118
+ scenario_config = {
119
+ "uniform_array": "${uniform_1d: 0.0, 1.0}"
120
+ }
121
+
122
+ # In-test
123
+ self.scenario.init(
124
+ scenario=scenario_config,
125
+ seed=42
126
+ )
127
+
128
+ # Post-condition
129
+ resolved, _ = self.scenario.resolve()
130
+ value = resolved.uniform_array
131
+ # Convert to list if it's a ListConfig
132
+ value_list = list(value) if hasattr(value, '__iter__') and not isinstance(value, str) else [value]
133
+ self.assertGreaterEqual(len(value_list), 1)
134
+ for v in value_list:
135
+ self.assertGreaterEqual(v, 0.0)
136
+ self.assertLessEqual(v, 1.0)
137
+
138
+ def test_gaussian_distribution_should_resolve_correctly(self):
139
+ """Test that gaussian distribution resolver works correctly."""
140
+ # Pre-condition
141
+ scenario_config = {
142
+ "gaussian_value": "${gaussian: 0.0, 1.0}"
143
+ }
144
+
145
+ # In-test
146
+ self.scenario.init(
147
+ scenario=scenario_config,
148
+ seed=42
149
+ )
150
+
151
+ # Post-condition
152
+ resolved, _ = self.scenario.resolve()
153
+ value = resolved.gaussian_value
154
+ self.assertIsInstance(value, (int, float))
155
+
156
+ def test_gaussian_distribution_should_handle_size_parameter(self):
157
+ """Test that gaussian_1d distribution returns list."""
158
+ # Pre-condition
159
+ scenario_config = {
160
+ "gaussian_array": "${gaussian_1d: 0.0, 1.0}"
161
+ }
162
+
163
+ # In-test
164
+ self.scenario.init(
165
+ scenario=scenario_config,
166
+ seed=42
167
+ )
168
+
169
+ # Post-condition
170
+ resolved, _ = self.scenario.resolve()
171
+ value = resolved.gaussian_array
172
+ # Convert to list if it's a ListConfig
173
+ value_list = list(value) if hasattr(value, '__iter__') and not isinstance(value, str) else [value]
174
+ self.assertGreaterEqual(len(value_list), 1)
175
+
176
+ def test_bernoulli_distribution_should_resolve_correctly(self):
177
+ """Test that bernoulli distribution resolver works correctly."""
178
+ # Pre-condition
179
+ scenario_config = {
180
+ "bernoulli_value": "${bernoulli: 0.5}"
181
+ }
182
+
183
+ # In-test
184
+ self.scenario.init(
185
+ scenario=scenario_config,
186
+ seed=42
187
+ )
188
+
189
+ # Post-condition
190
+ resolved, _ = self.scenario.resolve()
191
+ value = resolved.bernoulli_value
192
+ self.assertIn(value, [0, 1, True, False])
193
+
194
+ def test_resolve_should_regenerate_distribution_values(self):
195
+ """Test that resolve() regenerates new values from distributions."""
196
+ # Pre-condition
197
+ scenario_config = {
198
+ "random_value": "${uniform: 0.0, 100.0}"
199
+ }
200
+ self.scenario.init(
201
+ scenario=scenario_config,
202
+ seed=None # No seed for randomness
203
+ )
204
+
205
+ # In-test - First resolve
206
+ resolved1, _ = self.scenario.resolve()
207
+ value1 = resolved1.random_value
208
+
209
+ # Second resolve
210
+ resolved2, _ = self.scenario.resolve()
211
+ value2 = resolved2.random_value
212
+
213
+ # Post-condition
214
+ # Values should be different (statistically very unlikely to be same)
215
+ # Note: There's a tiny chance they could be equal, but extremely unlikely
216
+ self.assertIsInstance(value1, (int, float))
217
+ self.assertIsInstance(value2, (int, float))
218
+
219
+ def test_template_property_should_access_scenario_template(self):
220
+ """Test that template property allows access to scenario values."""
221
+ # Pre-condition
222
+ scenario_config = {
223
+ "test_attribute": "test_value"
224
+ }
225
+ self.scenario.init(
226
+ scenario=scenario_config
227
+ )
228
+
229
+ # In-test
230
+ value = self.scenario.template.test_attribute
231
+
232
+ # Post-condition
233
+ self.assertEqual(value, "test_value")
234
+
235
+ def test_template_should_contain_unresolved_distributions(self):
236
+ """Test that template contains unresolved distribution strings."""
237
+ # Pre-condition
238
+ scenario_config = {
239
+ "test_key": "${uniform: 0.0, 1.0}"
240
+ }
241
+ self.scenario.init(
242
+ scenario=scenario_config
243
+ )
244
+
245
+ # In-test
246
+ yaml_str = self.scenario.yaml
247
+
248
+ # Post-condition
249
+ self.assertIn("uniform", yaml_str)
250
+
251
+ def test_resolve_should_return_resolved_values(self):
252
+ """Test that resolve() returns dict-style access to resolved values."""
253
+ # Pre-condition
254
+ scenario_config = {
255
+ "test_key": "test_value"
256
+ }
257
+ self.scenario.init(
258
+ scenario=scenario_config
259
+ )
260
+
261
+ # In-test
262
+ resolved, _ = self.scenario.resolve()
263
+ value = resolved["test_key"]
264
+
265
+ # Post-condition
266
+ self.assertEqual(value, "test_value")
267
+
268
+ def test_resolve_should_raise_error_for_missing_key(self):
269
+ """Test that resolve() result raises KeyError for missing keys."""
270
+ # Pre-condition
271
+ self.scenario.init(
272
+ scenario={}
273
+ )
274
+
275
+ # In-test & Post-condition
276
+ resolved, _ = self.scenario.resolve()
277
+ with self.assertRaises(KeyError):
278
+ _ = resolved["nonexistent_key"]
279
+
280
+ def test_get_final_size_should_handle_none_size_without_num_env(self):
281
+ """Test _get_final_size with None size and no num_env."""
282
+ # Pre-condition
283
+ self.scenario.init(
284
+ scenario={}
285
+ )
286
+
287
+ # In-test
288
+ result = self.scenario._get_final_size(None)
289
+
290
+ # Post-condition
291
+ self.assertIsNone(result)
292
+
293
+ def test_get_final_size_should_handle_int_size_without_num_env(self):
294
+ """Test _get_final_size with int size and no num_env."""
295
+ # Pre-condition
296
+ self.scenario.init(
297
+ scenario={}
298
+ )
299
+
300
+ # In-test
301
+ result = self.scenario._get_final_size(3)
302
+
303
+ # Post-condition
304
+ self.assertEqual(result, 3)
305
+
306
+ def test_get_final_size_should_handle_tuple_size_without_num_env(self):
307
+ """Test _get_final_size with tuple size and no num_env."""
308
+ # Pre-condition
309
+ self.scenario.init(
310
+ scenario={}
311
+ )
312
+
313
+ # In-test
314
+ result = self.scenario._get_final_size((2, 3))
315
+
316
+ # Post-condition
317
+ self.assertEqual(result, (2, 3))
318
+
319
+ def test_get_final_size_should_handle_size_without_num_env(self):
320
+ """Test _get_final_size with size but no num_env."""
321
+ # Pre-condition
322
+ self.scenario.init(
323
+ scenario={}
324
+ )
325
+
326
+ # In-test
327
+ result = self.scenario._get_final_size(5)
328
+
329
+ # Post-condition
330
+ self.assertEqual(result, 5)
331
+
332
+ def test_convert_to_python_should_handle_numpy_scalar(self):
333
+ """Test _convert_to_python with numpy scalar."""
334
+ # Pre-condition
335
+ np_scalar = np.float64(3.14)
336
+
337
+ # In-test
338
+ result = Scenario._convert_to_python(np_scalar)
339
+
340
+ # Post-condition
341
+ self.assertIsInstance(result, float)
342
+ self.assertEqual(result, 3.14)
343
+
344
+ def test_convert_to_python_should_handle_numpy_array(self):
345
+ """Test _convert_to_python with numpy array."""
346
+ # Pre-condition
347
+ np_array = np.array([1, 2, 3])
348
+
349
+ # In-test
350
+ result = Scenario._convert_to_python(np_array)
351
+
352
+ # Post-condition
353
+ self.assertIsInstance(result, list)
354
+ self.assertEqual(result, [1, 2, 3])
355
+
356
+ def test_convert_to_python_should_handle_zero_dim_array(self):
357
+ """Test _convert_to_python with 0-dimensional numpy array."""
358
+ # Pre-condition
359
+ np_zero_dim = np.array(42)
360
+
361
+ # In-test
362
+ result = Scenario._convert_to_python(np_zero_dim)
363
+
364
+ # Post-condition
365
+ self.assertIsInstance(result, int)
366
+ self.assertEqual(result, 42)
367
+
368
+ def test_convert_to_python_should_handle_regular_python_types(self):
369
+ """Test _convert_to_python with regular Python types."""
370
+ # Pre-condition
371
+ regular_values = [42, 3.14, "string", [1, 2, 3], {"key": "value"}]
372
+
373
+ # In-test & Post-condition
374
+ for value in regular_values:
375
+ result = Scenario._convert_to_python(value)
376
+ self.assertEqual(result, value)
377
+
378
+ def test_get_node_path_should_find_simple_key(self):
379
+ """Test _get_node_path with simple dictionary key."""
380
+ # Pre-condition
381
+ root = {"key1": "target_node", "key2": "other"}
382
+ self.scenario.init(
383
+ scenario={}
384
+ )
385
+
386
+ # In-test
387
+ path = self.scenario._get_node_path(root, "target_node")
388
+
389
+ # Post-condition
390
+ self.assertEqual(path, "key1")
391
+
392
+ def test_get_node_path_should_find_nested_key(self):
393
+ """Test _get_node_path with nested dictionary."""
394
+ # Pre-condition
395
+ root = {"level1": {"level2": "target_node"}}
396
+ self.scenario.init(
397
+ scenario={}
398
+ )
399
+
400
+ # In-test
401
+ path = self.scenario._get_node_path(root, "target_node")
402
+
403
+ # Post-condition
404
+ self.assertEqual(path, "level1.level2")
405
+
406
+ def test_get_node_path_should_find_in_list(self):
407
+ """Test _get_node_path with list containing target."""
408
+ # Pre-condition
409
+ root = {"key": ["item1", "target_node", "item3"]}
410
+ self.scenario.init(
411
+ scenario={}
412
+ )
413
+
414
+ # In-test
415
+ path = self.scenario._get_node_path(root, "target_node")
416
+
417
+ # Post-condition
418
+ self.assertEqual(path, "key[1]")
419
+
420
+ def test_get_node_path_should_return_empty_for_missing_node(self):
421
+ """Test _get_node_path returns empty string when node not found."""
422
+ # Pre-condition
423
+ root = {"key": "value"}
424
+ self.scenario.init(
425
+ scenario={}
426
+ )
427
+
428
+ # In-test
429
+ path = self.scenario._get_node_path(root, "nonexistent")
430
+
431
+ # Post-condition
432
+ self.assertEqual(path, "")
433
+
434
+ def test_template_property_should_return_scenario_template(self):
435
+ """Test that template property returns the scenario template."""
436
+ # Pre-condition
437
+ scenario_config = {"key": "value"}
438
+ self.scenario.init(
439
+ scenario=scenario_config
440
+ )
441
+
442
+ # In-test
443
+ template = self.scenario.template
444
+
445
+ # Post-condition
446
+ self.assertIsNotNone(template)
447
+ self.assertEqual(template.key, "value")
448
+
449
+ def test_resolve_should_return_resolved_scenario(self):
450
+ """Test that resolve() returns the resolved scenario."""
451
+ # Pre-condition
452
+ scenario_config = {"key": "value"}
453
+ self.scenario.init(
454
+ scenario=scenario_config
455
+ )
456
+
457
+ # In-test
458
+ resolved, _ = self.scenario.resolve()
459
+
460
+ # Post-condition
461
+ self.assertIsNotNone(resolved)
462
+ self.assertEqual(resolved.key, "value")
463
+
464
+ def test_yaml_property_should_return_yaml_representation(self):
465
+ """Test that yaml property returns YAML string."""
466
+ # Pre-condition
467
+ scenario_config = {"key": "value"}
468
+ self.scenario.init(
469
+ scenario=scenario_config
470
+ )
471
+
472
+ # In-test
473
+ yaml_str = self.scenario.yaml
474
+
475
+ # Post-condition
476
+ self.assertIsInstance(yaml_str, str)
477
+ self.assertIn("key:", yaml_str)
478
+ self.assertIn("value", yaml_str)
479
+
480
+ def test_resolve_returns_episode_vals(self):
481
+ """Test that resolve() returns episode values for distributions."""
482
+ # Pre-condition
483
+ scenario_config = {
484
+ "dist_value": "${uniform: 0.0, 1.0}"
485
+ }
486
+ self.scenario.init(
487
+ scenario=scenario_config,
488
+ seed=42
489
+ )
490
+
491
+ # In-test
492
+ resolved, episode_vals = self.scenario.resolve()
493
+
494
+ # Post-condition
495
+ # Verify resolved scenario has the value
496
+ self.assertIsNotNone(resolved.dist_value)
497
+ # Verify episode_vals dict contains the distribution samples
498
+ self.assertGreater(len(episode_vals), 0)
499
+
500
+ def test_nested_scenario_access_should_work(self):
501
+ """Test accessing deeply nested scenario values."""
502
+ # Pre-condition
503
+ scenario_config = {
504
+ "level1": {
505
+ "level2": {
506
+ "level3": "deep_value"
507
+ }
508
+ }
509
+ }
510
+ self.scenario.init(
511
+ scenario=scenario_config
512
+ )
513
+
514
+ # In-test
515
+ resolved, _ = self.scenario.resolve()
516
+ value = resolved.level1.level2.level3
517
+
518
+ # Post-condition
519
+ self.assertEqual(value, "deep_value")
520
+
521
+ def test_multiple_distributions_should_work_together(self):
522
+ """Test scenario with multiple different distributions."""
523
+ # Pre-condition
524
+ scenario_config = {
525
+ "uniform_val": "${uniform: 0.0, 1.0}",
526
+ "gaussian_val": "${gaussian: 0.0, 1.0}",
527
+ "bernoulli_val": "${bernoulli: 0.5}"
528
+ }
529
+
530
+ # In-test
531
+ self.scenario.init(
532
+ scenario=scenario_config,
533
+ seed=42
534
+ )
535
+
536
+ # Post-condition
537
+ resolved, _ = self.scenario.resolve()
538
+ self.assertIsInstance(resolved.uniform_val, (int, float))
539
+ self.assertIsInstance(resolved.gaussian_val, (int, float))
540
+ self.assertIn(resolved.bernoulli_val, [0, 1, True, False])
541
+
542
+ def test_clear_resolvers_should_clear_dist_cache(self):
543
+ """Test that _clear_resolvers clears the distribution cache."""
544
+ # Pre-condition
545
+ scenario_config = {"value": "${uniform: 0.0, 1.0}"}
546
+ self.scenario.init(
547
+ scenario=scenario_config,
548
+ seed=42
549
+ )
550
+ _ = self.scenario.resolve() # Trigger cache population
551
+
552
+ # In-test
553
+ self.scenario._clear_resolvers()
554
+
555
+ # Post-condition
556
+ self.assertEqual(len(Scenario.dist_cache), 0)
557
+
558
+ def test_main_script_scenario_should_initialize_with_nested_structure(self):
559
+ """Test scenario initialization matching the __main__ script example."""
560
+ # Pre-condition
561
+ scenario_config = {
562
+ "scenario": {
563
+ "scenario_id": "scenario_1",
564
+ "cup_x": "${uniform: 0.7, 1.5}",
565
+ "cup_y": "${uniform: 0.3, 0.7}",
566
+ }
567
+ }
568
+
569
+ # In-test
570
+ self.scenario.init(
571
+ scenario=scenario_config,
572
+ seed=42
573
+ )
574
+
575
+ # Post-condition
576
+ # Verify scenario structure exists
577
+ resolved, _ = self.scenario.resolve()
578
+ self.assertIsNotNone(resolved.scenario)
579
+ self.assertEqual(resolved.scenario.scenario_id, "scenario_1")
580
+
581
+ # Verify cup_x and cup_y are resolved
582
+ cup_x = resolved.scenario.cup_x
583
+ cup_y = resolved.scenario.cup_y
584
+
585
+ # Verify values are in expected ranges
586
+ self.assertGreaterEqual(cup_x, 0.7)
587
+ self.assertLessEqual(cup_x, 1.5)
588
+ self.assertGreaterEqual(cup_y, 0.3)
589
+ self.assertLessEqual(cup_y, 0.7)
590
+
591
+ def test_main_script_scenario_should_allow_both_access_methods(self):
592
+ """Test that both attribute and dict access work as shown in __main__ script."""
593
+ # Pre-condition
594
+ scenario_config = {
595
+ "scenario": {
596
+ "scenario_id": "scenario_1",
597
+ "cup_x": "${uniform: 0.7, 1.5}",
598
+ }
599
+ }
600
+
601
+ # In-test
602
+ self.scenario.init(
603
+ scenario=scenario_config,
604
+ seed=42
605
+ )
606
+
607
+ # Post-condition
608
+ # Both access methods should return the same value
609
+ resolved, _ = self.scenario.resolve()
610
+ cup_x_attr = resolved.scenario.cup_x
611
+ cup_x_dict = resolved["scenario"].cup_x
612
+
613
+ self.assertEqual(cup_x_attr, cup_x_dict)
614
+
615
+ def test_main_script_scenario_should_regenerate_on_resolve(self):
616
+ """Test that resolve regenerates values as shown in __main__ script."""
617
+ # Pre-condition
618
+ scenario_config = {
619
+ "scenario": {
620
+ "cup_x": "${uniform: 0.7, 1.5}",
621
+ }
622
+ }
623
+ self.scenario.init(
624
+ scenario=scenario_config,
625
+ seed=None # No seed for random values
626
+ )
627
+
628
+ resolved1, _ = self.scenario.resolve()
629
+ first_cup_x = resolved1.scenario.cup_x
630
+
631
+ # In-test
632
+ resolved2, _ = self.scenario.resolve()
633
+ second_cup_x = resolved2.scenario.cup_x
634
+
635
+ # Post-condition
636
+ # Values should be in valid range
637
+ self.assertGreaterEqual(first_cup_x, 0.7)
638
+ self.assertLessEqual(first_cup_x, 1.5)
639
+ self.assertGreaterEqual(second_cup_x, 0.7)
640
+ self.assertLessEqual(second_cup_x, 1.5)
641
+
642
+ def test_main_script_scenario_should_convert_to_numpy_array(self):
643
+ """Test that scenario values can be converted to numpy arrays."""
644
+ # Pre-condition
645
+ scenario_config = {
646
+ "scenario": {
647
+ "cup_x": "${uniform_1d: 0.7, 1.5}",
648
+ }
649
+ }
650
+ self.scenario.init(
651
+ scenario=scenario_config,
652
+ seed=42
653
+ )
654
+
655
+ # In-test
656
+ resolved, _ = self.scenario.resolve()
657
+ cup_x = resolved.scenario.cup_x
658
+ np_array = np.array(cup_x)
659
+
660
+ # Post-condition
661
+ self.assertIsInstance(np_array, np.ndarray)
662
+
663
+ # Verify values are in expected range
664
+ for val in np.atleast_1d(np_array):
665
+ self.assertGreaterEqual(val, 0.7)
666
+ self.assertLessEqual(val, 1.5)
667
+
668
+ def test_main_script_scenario_should_produce_valid_yaml(self):
669
+ """Test that scenario.yaml returns valid YAML string."""
670
+ # Pre-condition
671
+ scenario_config = {
672
+ "scenario": {
673
+ "scenario_id": "scenario_1",
674
+ "cup_x": "${uniform: 0.7, 1.5}",
675
+ "cup_y": "${uniform: 0.3, 0.7}",
676
+ }
677
+ }
678
+ self.scenario.init(
679
+ scenario=scenario_config,
680
+ seed=42
681
+ )
682
+
683
+ # In-test
684
+ yaml_str = self.scenario.yaml
685
+
686
+ # Post-condition
687
+ self.assertIsInstance(yaml_str, str)
688
+ self.assertIn("scenario:", yaml_str)
689
+ self.assertIn("scenario_id:", yaml_str)
690
+ self.assertIn("scenario_1", yaml_str)
691
+ self.assertIn("cup_x:", yaml_str)
692
+ self.assertIn("cup_y:", yaml_str)
693
+
694
+ def test_main_script_scenario_should_handle_multiple_resolves(self):
695
+ """Test multiple resolve calls as shown in __main__ script."""
696
+ # Pre-condition
697
+ scenario_config = {
698
+ "scenario": {
699
+ "cup_x": "${uniform: 0.7, 1.5}",
700
+ }
701
+ }
702
+ self.scenario.init(
703
+ scenario=scenario_config,
704
+ seed=42
705
+ )
706
+
707
+ resolved1, _ = self.scenario.resolve()
708
+ first_value = resolved1.scenario.cup_x
709
+
710
+ # In-test - First resolve
711
+ resolved2, _ = self.scenario.resolve()
712
+ second_value = resolved2.scenario.cup_x
713
+
714
+ # In-test - Second resolve
715
+ resolved3, _ = self.scenario.resolve()
716
+ third_value = resolved3.scenario.cup_x
717
+
718
+ # Post-condition
719
+ # All values should be in range
720
+ for val in [first_value, second_value, third_value]:
721
+ self.assertGreaterEqual(val, 0.7)
722
+ self.assertLessEqual(val, 1.5)
723
+
724
+ def test_main_script_scenario_should_reinitialize_with_none(self):
725
+ """Test reinitializing scenario with None as shown in __main__ script."""
726
+ # Pre-condition
727
+ scenario_config = {
728
+ "scenario": {
729
+ "cup_x": "${uniform: 0.7, 1.5}",
730
+ }
731
+ }
732
+ self.scenario.init(
733
+ scenario=scenario_config,
734
+ seed=42
735
+ )
736
+
737
+ # Verify initial scenario has content
738
+ first_yaml = self.scenario.yaml
739
+ self.assertIn("cup_x:", first_yaml)
740
+
741
+ # In-test - Reinitialize with None
742
+ self.scenario.init(
743
+ scenario=None,
744
+ seed=42
745
+ )
746
+
747
+ # Post-condition
748
+ # Should have an empty scenario
749
+ second_yaml = self.scenario.yaml
750
+ self.assertEqual(second_yaml.strip(), "{}")
751
+
752
+ def test_main_script_scenario_should_handle_seed_consistency(self):
753
+ """Test that same seed produces consistent results across resolves."""
754
+ # Pre-condition
755
+ scenario_config = {
756
+ "scenario": {
757
+ "cup_x": "${uniform: 0.7, 1.5}",
758
+ "cup_y": "${uniform: 0.3, 0.7}",
759
+ }
760
+ }
761
+
762
+ # Create first scenario with seed
763
+ scenario1 = Scenario()
764
+ scenario1.init(
765
+ scenario=scenario_config,
766
+ seed=42
767
+ )
768
+ resolved1, _ = scenario1.resolve()
769
+ values1_x = resolved1.scenario.cup_x
770
+ values1_y = resolved1.scenario.cup_y
771
+
772
+ # Create second scenario with same seed
773
+ scenario2 = Scenario()
774
+ scenario2.init(
775
+ scenario=scenario_config,
776
+ seed=42
777
+ )
778
+ resolved2, _ = scenario2.resolve()
779
+ values2_x = resolved2.scenario.cup_x
780
+ values2_y = resolved2.scenario.cup_y
781
+
782
+ # Post-condition
783
+ self.assertEqual(values1_x, values2_x)
784
+ self.assertEqual(values1_y, values2_y)
785
+
786
+ # Cleanup
787
+ scenario1._clear_resolvers()
788
+ scenario2._clear_resolvers()
789
+
790
+
791
+ if __name__ == "__main__":
792
+ unittest.main()