cli-test-framework 0.4.0__tar.gz → 0.4.2__tar.gz

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.
Files changed (99) hide show
  1. {cli_test_framework-0.4.0/src/cli_test_framework.egg-info → cli_test_framework-0.4.2}/PKG-INFO +121 -6
  2. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/README.md +120 -5
  3. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/setup.py +1 -1
  4. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/core/execution.py +22 -2
  5. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/core/parallel_runner.py +3 -1
  6. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/core/process_worker.py +2 -0
  7. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/core/test_case.py +6 -2
  8. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/core/types.py +11 -0
  9. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/file_comparator/binary_comparator.py +154 -8
  10. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/file_comparator/h5_comparator.py +233 -20
  11. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/runners/json_runner.py +2 -0
  12. cli_test_framework-0.4.2/src/cli_test_framework/runners/parallel_json_runner.py +176 -0
  13. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/runners/yaml_runner.py +2 -0
  14. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2/src/cli_test_framework.egg-info}/PKG-INFO +121 -6
  15. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/__pycache__/conftest.cpython-312-pytest-7.4.4.pyc +0 -0
  16. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/__pycache__/conftest.cpython-39-pytest-8.3.4.pyc +0 -0
  17. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/__pycache__/run_all.cpython-312.pyc +0 -0
  18. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/__pycache__/run_all.cpython-39.pyc +0 -0
  19. cli_test_framework-0.4.2/tests/e2e/__pycache__/__init__.cpython-312.pyc +0 -0
  20. cli_test_framework-0.4.2/tests/e2e/__pycache__/__init__.cpython-39.pyc +0 -0
  21. cli_test_framework-0.4.2/tests/e2e/__pycache__/test_user_flows.cpython-312-pytest-7.4.4.pyc +0 -0
  22. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/e2e/__pycache__/test_user_flows.cpython-39-pytest-8.3.4.pyc +0 -0
  23. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/integration/file_compare/__pycache__/test_binary_compare.cpython-312-pytest-7.4.4.pyc +0 -0
  24. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/integration/file_compare/__pycache__/test_binary_compare.cpython-39-pytest-8.3.4.pyc +0 -0
  25. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/integration/file_compare/__pycache__/test_h5_compare.cpython-312-pytest-7.4.4.pyc +0 -0
  26. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/integration/file_compare/__pycache__/test_h5_compare.cpython-39-pytest-8.3.4.pyc +0 -0
  27. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/integration/file_compare/__pycache__/test_json_compare.cpython-312-pytest-7.4.4.pyc +0 -0
  28. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/integration/file_compare/__pycache__/test_json_compare.cpython-39-pytest-8.3.4.pyc +0 -0
  29. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/integration/file_compare/__pycache__/test_text_compare.cpython-312-pytest-7.4.4.pyc +0 -0
  30. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/integration/file_compare/__pycache__/test_text_compare.cpython-39-pytest-8.3.4.pyc +0 -0
  31. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/integration/parallel/__pycache__/test_parallel_runner.cpython-312-pytest-7.4.4.pyc +0 -0
  32. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/integration/parallel/__pycache__/test_parallel_runner.cpython-39-pytest-8.3.4.pyc +0 -0
  33. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/integration/path_handling/__pycache__/test_spaces_in_paths.cpython-312-pytest-7.4.4.pyc +0 -0
  34. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/integration/path_handling/__pycache__/test_spaces_in_paths.cpython-39-pytest-8.3.4.pyc +0 -0
  35. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/unit/core/__pycache__/test_setup.cpython-312-pytest-7.4.4.pyc +0 -0
  36. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/unit/core/__pycache__/test_setup.cpython-39-pytest-8.3.4.pyc +0 -0
  37. cli_test_framework-0.4.2/tests/unit/runners/__pycache__/test_json_yaml_runner.cpython-312-pytest-7.4.4.pyc +0 -0
  38. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/unit/runners/__pycache__/test_json_yaml_runner.cpython-39-pytest-8.3.4.pyc +0 -0
  39. cli_test_framework-0.4.0/src/cli_test_framework/runners/parallel_json_runner.py +0 -79
  40. cli_test_framework-0.4.0/tests/e2e/__pycache__/__init__.cpython-312.pyc +0 -0
  41. cli_test_framework-0.4.0/tests/e2e/__pycache__/__init__.cpython-39.pyc +0 -0
  42. cli_test_framework-0.4.0/tests/e2e/__pycache__/test_user_flows.cpython-312-pytest-7.4.4.pyc +0 -0
  43. cli_test_framework-0.4.0/tests/unit/runners/__pycache__/test_json_yaml_runner.cpython-312-pytest-7.4.4.pyc +0 -0
  44. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/MANIFEST.in +0 -0
  45. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/docs/user_manual.md +0 -0
  46. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/pyproject.toml +0 -0
  47. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/setup.cfg +0 -0
  48. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/__init__.py +0 -0
  49. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/cli.py +0 -0
  50. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/commands/__init__.py +0 -0
  51. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/commands/compare.py +0 -0
  52. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/core/__init__.py +0 -0
  53. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/core/assertions.py +0 -0
  54. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/core/base_runner.py +0 -0
  55. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/core/setup.py +0 -0
  56. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/file_comparator/__init__.py +0 -0
  57. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/file_comparator/base_comparator.py +0 -0
  58. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/file_comparator/csv_comparator.py +0 -0
  59. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/file_comparator/factory.py +0 -0
  60. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/file_comparator/json_comparator.py +0 -0
  61. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/file_comparator/result.py +0 -0
  62. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/file_comparator/text_comparator.py +0 -0
  63. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/file_comparator/xml_comparator.py +0 -0
  64. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/runners/__init__.py +0 -0
  65. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/utils/__init__.py +0 -0
  66. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/utils/path_resolver.py +0 -0
  67. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/utils/report_generator.py +0 -0
  68. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework.egg-info/SOURCES.txt +0 -0
  69. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework.egg-info/dependency_links.txt +0 -0
  70. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework.egg-info/entry_points.txt +0 -0
  71. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework.egg-info/requires.txt +0 -0
  72. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework.egg-info/top_level.txt +0 -0
  73. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/README.md +0 -0
  74. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/__init__.py +0 -0
  75. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/__pycache__/__init__.cpython-312.pyc +0 -0
  76. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/__pycache__/__init__.cpython-39.pyc +0 -0
  77. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/__pycache__/test_parallel_runner.cpython-312-pytest-7.4.4.pyc +0 -0
  78. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/__pycache__/test_setup_module.cpython-312-pytest-7.4.4.pyc +0 -0
  79. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/conftest.py +0 -0
  80. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/demos/h5_filter_demo.py +0 -0
  81. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/demos/manual_report_example.py +0 -0
  82. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/demos/perf_parallel.py +0 -0
  83. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/e2e/__init__.py +0 -0
  84. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/e2e/test_user_flows.py +0 -0
  85. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/fixtures/test_cases.json +0 -0
  86. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/fixtures/test_cases.yaml +0 -0
  87. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/fixtures/test_cases1.json +0 -0
  88. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/fixtures/test_with_setup.json +0 -0
  89. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/fixtures/test_with_setup.yaml +0 -0
  90. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/integration/file_compare/test_binary_compare.py +0 -0
  91. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/integration/file_compare/test_h5_compare.py +0 -0
  92. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/integration/file_compare/test_json_compare.py +0 -0
  93. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/integration/file_compare/test_text_compare.py +0 -0
  94. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/integration/parallel/test_parallel_runner.py +0 -0
  95. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/integration/path_handling/test_spaces_in_paths.py +0 -0
  96. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/run_all.py +0 -0
  97. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/test_report.txt +0 -0
  98. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/unit/core/test_setup.py +0 -0
  99. {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/unit/runners/test_json_yaml_runner.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cli-test-framework
3
- Version: 0.4.0
3
+ Version: 0.4.2
4
4
  Summary: A powerful command line testing framework in Python with setup modules, parallel execution, and file comparison capabilities.
5
5
  Home-page: https://github.com/ozil111/cli-test-framework
6
6
  Author: Xiaotong Wang
@@ -53,6 +53,7 @@ This is a lightweight and extensible automated testing framework that supports d
53
53
  - **📊 Comprehensive Reports**: Detailed pass rate statistics and failure diagnostics
54
54
  - **🔧 Thread-Safe Design**: Robust concurrent execution with proper synchronization
55
55
  - **📝 Advanced File Comparison**: Support for comparing various file types (text, binary, JSON, HDF5) with detailed diff output
56
+ - **🎛️ Resource-Aware Scheduling**: Per-test timeout and resource hints (CPU cores / estimated time / memory / priority) with automatic CPU core detection and semaphore-based scheduling to prevent resource conflicts and solver "runaway" scenarios
56
57
 
57
58
  ## 3. Quick Start
58
59
 
@@ -80,16 +81,22 @@ success = runner.run_tests()
80
81
  ```python
81
82
  from src.runners.parallel_json_runner import ParallelJSONRunner
82
83
 
83
- # Multi-threaded execution (recommended for I/O-intensive tests)
84
+ # Multi-threaded execution with resource-aware scheduling
84
85
  runner = ParallelJSONRunner(
85
86
  config_file="path/to/test_cases.json",
86
87
  workspace="/project/root",
87
88
  max_workers=4, # Maximum concurrent workers
88
- execution_mode="thread" # "thread" or "process"
89
+ execution_mode="thread" # "thread" (supports CPU scheduling) or "process"
89
90
  )
90
91
  success = runner.run_tests()
91
92
  ```
92
93
 
94
+ **Resource-Aware Scheduling**: When using `execution_mode="thread"`, the framework automatically:
95
+ - Detects available CPU cores on your machine
96
+ - Manages CPU resource allocation using semaphore-based scheduling
97
+ - Prevents resource conflicts by queuing tasks that require more cores than available
98
+ - Injects environment variables to constrain solver threads (prevents "runaway" scenarios)
99
+
93
100
  ### Setup Module Usage
94
101
 
95
102
  ```python
@@ -144,6 +151,21 @@ compare-files binary1.bin binary2.bin --similarity
144
151
  "output_contains": ["Environment: development"]
145
152
  }
146
153
  },
154
+ {
155
+ "name": "Full_Car_Crash_Simulation",
156
+ "command": "radioss_solver",
157
+ "args": ["-i", "input.0000.rad"],
158
+ "timeout": 36000,
159
+ "resources": {
160
+ "cpu_cores": 4,
161
+ "estimated_time": 18000,
162
+ "min_memory_mb": 16000,
163
+ "priority": 10
164
+ },
165
+ "expected": {
166
+ "return_code": 0
167
+ }
168
+ },
147
169
  {
148
170
  "name": "File Comparison Test",
149
171
  "command": "compare-files",
@@ -187,6 +209,61 @@ test_cases:
187
209
  output_matches: ".*\\.md$"
188
210
  ```
189
211
 
212
+ ### Resource-Aware Configuration
213
+
214
+ For simulation and long-running tasks (CAE/FEA), you can specify resource requirements to enable intelligent scheduling with automatic CPU core management:
215
+
216
+ ```json
217
+ {
218
+ "name": "Full_Car_Crash_Simulation",
219
+ "command": "radioss_solver",
220
+ "args": ["-i", "input.0000.rad"],
221
+ "timeout": 36000,
222
+ "resources": {
223
+ "cpu_cores": 4,
224
+ "estimated_time": 18000,
225
+ "min_memory_mb": 16000,
226
+ "priority": 10
227
+ },
228
+ "expected": {
229
+ "return_code": 0
230
+ }
231
+ }
232
+ ```
233
+
234
+ **Field Descriptions:**
235
+
236
+ - **`timeout`** (optional, float): **Hard limit in seconds**. If the test exceeds this time, it will be killed. Default: 3600 seconds (1 hour). Set to `null` for unlimited (not recommended).
237
+ - Common values: `60` (1 min), `300` (5 min), `3600` (1 hour), `18000` (5 hours), `86400` (24 hours)
238
+
239
+ - **`resources.cpu_cores`** (optional, int): **Number of CPU cores required by this task**. The framework automatically detects available CPU cores and uses semaphore-based scheduling to manage resource allocation. Tasks that require more cores than available will wait until resources are freed. Default: `1` core.
240
+ - **How it works**: The framework automatically detects your machine's CPU count (e.g., 16 cores), reserves 2 cores for the system, and creates a resource pool with the remaining cores (e.g., 14 cores). Tasks acquire cores from this pool before execution.
241
+ - **Environment injection**: When a task starts, the framework automatically sets `OMP_NUM_THREADS`, `MKL_NUM_THREADS`, and `NPROC` environment variables to constrain solver threads, preventing "runaway" scenarios where solvers ignore Python's scheduling.
242
+ - **Example scenarios**:
243
+ - Machine with 16 cores (14 available): 3 tasks requiring 4 cores each can run concurrently (3×4=12 cores used, 2 cores free)
244
+ - Machine with 8 cores (6 available): 1 task requiring 4 cores + 1 task requiring 2 cores can run concurrently
245
+ - **Recommendations**:
246
+ - Heavy simulations: `4-8` cores
247
+ - Medium tasks: `2-4` cores
248
+ - Lightweight scripts: `1` core (default)
249
+
250
+ - **`resources.estimated_time`** (optional, float): **Estimated duration in seconds** for LPT (Longest Processing Time) scheduling. Tasks with longer estimated times are scheduled first in parallel runs to improve throughput.
251
+ - Example: `18000` = 5 hours, `3600` = 1 hour, `300` = 5 minutes
252
+
253
+ - **`resources.min_memory_mb`** (optional, float): **Estimated memory requirement in MB**. Used for OOM (Out Of Memory) risk warnings. Currently informational only.
254
+ - Example: `16000` = 16 GB, `8192` = 8 GB, `4096` = 4 GB
255
+
256
+ - **`resources.priority`** (optional, int): **Task priority** (higher number = higher priority). Currently informational only. Recommended range: 0-10.
257
+ - `10`: Critical/blocking tasks (must run first)
258
+ - `7-9`: High priority (important business paths)
259
+ - `4-6`: Normal priority
260
+ - `1-3`: Low priority / exploratory tests
261
+ - `0` or unset: Default priority
262
+
263
+ **Note:**
264
+ - All time values (`timeout`, `estimated_time`) are in **seconds**, not milliseconds. This matches Python's `subprocess.run(timeout=...)` API.
265
+ - **CPU core scheduling is only active in thread mode**. Process mode will fall back to original behavior (process-level isolation provides some resource separation).
266
+
190
267
  ## 5. File Comparison Features
191
268
 
192
269
  ### Supported File Types
@@ -354,12 +431,35 @@ except Exception as e:
354
431
  max_workers = os.cpu_count() * 2
355
432
  ```
356
433
 
357
- 2. **Test Case Design**:
434
+ 2. **Resource-Aware Scheduling**:
435
+ - **For CAE/FEA simulations**: Always specify `cpu_cores` in your test configuration to prevent resource conflicts
436
+ - **Mixed workloads**: Configure lightweight tasks with `cpu_cores: 1` and heavy simulations with appropriate core counts
437
+ - **Example mixed configuration**:
438
+ ```json
439
+ {
440
+ "test_cases": [
441
+ {
442
+ "name": "Heavy Simulation",
443
+ "command": "radioss_solver",
444
+ "resources": { "cpu_cores": 4 }
445
+ },
446
+ {
447
+ "name": "Lightweight Script",
448
+ "command": "python script.py",
449
+ "resources": { "cpu_cores": 1 }
450
+ }
451
+ ]
452
+ }
453
+ ```
454
+ - **Monitor resource usage**: The framework prints resource acquisition/release logs to help you understand scheduling behavior
455
+
456
+ 3. **Test Case Design**:
358
457
  - ✅ Ensure test independence (no dependencies between tests)
359
458
  - ✅ Avoid shared resource conflicts (different files/ports)
360
459
  - ✅ Use relative paths (framework handles resolution automatically)
460
+ - ✅ Specify `cpu_cores` for CPU-intensive tasks to enable intelligent scheduling
361
461
 
362
- 3. **Debugging**:
462
+ 4. **Debugging**:
363
463
  ```python
364
464
  # Enable verbose output for debugging
365
465
  runner = ParallelJSONRunner(
@@ -544,7 +644,22 @@ compare-files data1.h5 data2.h5 --h5-data-filter '<=0.01'
544
644
 
545
645
  # 版本更新日志
546
646
 
547
- ## 0.3.7 (Latest)
647
+ ## 0.4.2 (Latest)
648
+
649
+ ### ✨ New Features
650
+ - **Resource-Aware CPU Scheduling**: Automatic CPU core detection and semaphore-based scheduling to prevent resource conflicts
651
+ - Added `cpu_cores` field in `resources` configuration to specify CPU requirements per task
652
+ - Automatic environment variable injection (`OMP_NUM_THREADS`, `MKL_NUM_THREADS`, `NPROC`) to constrain solver threads
653
+ - Prevents solver "runaway" scenarios where solvers ignore Python's scheduling
654
+ - Intelligent resource pool management: automatically reserves 2 cores for system use
655
+ - **Enhanced execution engine**: Support for custom environment variables in test execution
656
+
657
+ ### 🔧 Improvements
658
+ - **Better resource management**: Tasks now wait for available CPU cores instead of overwhelming the system
659
+ - **Automatic CPU detection**: No manual configuration needed - framework detects available cores automatically
660
+ - **Thread-safe resource allocation**: Semaphore-based scheduling ensures thread-safe resource management
661
+
662
+ ## 0.3.7
548
663
 
549
664
  ### 🐛 Bug Fixes
550
665
  - **Fixed H5 table regex matching**: `--h5-table-regex=table1,table2` now correctly matches both `table1` and `table2` instead of treating the entire string as a single regex pattern
@@ -18,6 +18,7 @@ This is a lightweight and extensible automated testing framework that supports d
18
18
  - **📊 Comprehensive Reports**: Detailed pass rate statistics and failure diagnostics
19
19
  - **🔧 Thread-Safe Design**: Robust concurrent execution with proper synchronization
20
20
  - **📝 Advanced File Comparison**: Support for comparing various file types (text, binary, JSON, HDF5) with detailed diff output
21
+ - **🎛️ Resource-Aware Scheduling**: Per-test timeout and resource hints (CPU cores / estimated time / memory / priority) with automatic CPU core detection and semaphore-based scheduling to prevent resource conflicts and solver "runaway" scenarios
21
22
 
22
23
  ## 3. Quick Start
23
24
 
@@ -45,16 +46,22 @@ success = runner.run_tests()
45
46
  ```python
46
47
  from src.runners.parallel_json_runner import ParallelJSONRunner
47
48
 
48
- # Multi-threaded execution (recommended for I/O-intensive tests)
49
+ # Multi-threaded execution with resource-aware scheduling
49
50
  runner = ParallelJSONRunner(
50
51
  config_file="path/to/test_cases.json",
51
52
  workspace="/project/root",
52
53
  max_workers=4, # Maximum concurrent workers
53
- execution_mode="thread" # "thread" or "process"
54
+ execution_mode="thread" # "thread" (supports CPU scheduling) or "process"
54
55
  )
55
56
  success = runner.run_tests()
56
57
  ```
57
58
 
59
+ **Resource-Aware Scheduling**: When using `execution_mode="thread"`, the framework automatically:
60
+ - Detects available CPU cores on your machine
61
+ - Manages CPU resource allocation using semaphore-based scheduling
62
+ - Prevents resource conflicts by queuing tasks that require more cores than available
63
+ - Injects environment variables to constrain solver threads (prevents "runaway" scenarios)
64
+
58
65
  ### Setup Module Usage
59
66
 
60
67
  ```python
@@ -109,6 +116,21 @@ compare-files binary1.bin binary2.bin --similarity
109
116
  "output_contains": ["Environment: development"]
110
117
  }
111
118
  },
119
+ {
120
+ "name": "Full_Car_Crash_Simulation",
121
+ "command": "radioss_solver",
122
+ "args": ["-i", "input.0000.rad"],
123
+ "timeout": 36000,
124
+ "resources": {
125
+ "cpu_cores": 4,
126
+ "estimated_time": 18000,
127
+ "min_memory_mb": 16000,
128
+ "priority": 10
129
+ },
130
+ "expected": {
131
+ "return_code": 0
132
+ }
133
+ },
112
134
  {
113
135
  "name": "File Comparison Test",
114
136
  "command": "compare-files",
@@ -152,6 +174,61 @@ test_cases:
152
174
  output_matches: ".*\\.md$"
153
175
  ```
154
176
 
177
+ ### Resource-Aware Configuration
178
+
179
+ For simulation and long-running tasks (CAE/FEA), you can specify resource requirements to enable intelligent scheduling with automatic CPU core management:
180
+
181
+ ```json
182
+ {
183
+ "name": "Full_Car_Crash_Simulation",
184
+ "command": "radioss_solver",
185
+ "args": ["-i", "input.0000.rad"],
186
+ "timeout": 36000,
187
+ "resources": {
188
+ "cpu_cores": 4,
189
+ "estimated_time": 18000,
190
+ "min_memory_mb": 16000,
191
+ "priority": 10
192
+ },
193
+ "expected": {
194
+ "return_code": 0
195
+ }
196
+ }
197
+ ```
198
+
199
+ **Field Descriptions:**
200
+
201
+ - **`timeout`** (optional, float): **Hard limit in seconds**. If the test exceeds this time, it will be killed. Default: 3600 seconds (1 hour). Set to `null` for unlimited (not recommended).
202
+ - Common values: `60` (1 min), `300` (5 min), `3600` (1 hour), `18000` (5 hours), `86400` (24 hours)
203
+
204
+ - **`resources.cpu_cores`** (optional, int): **Number of CPU cores required by this task**. The framework automatically detects available CPU cores and uses semaphore-based scheduling to manage resource allocation. Tasks that require more cores than available will wait until resources are freed. Default: `1` core.
205
+ - **How it works**: The framework automatically detects your machine's CPU count (e.g., 16 cores), reserves 2 cores for the system, and creates a resource pool with the remaining cores (e.g., 14 cores). Tasks acquire cores from this pool before execution.
206
+ - **Environment injection**: When a task starts, the framework automatically sets `OMP_NUM_THREADS`, `MKL_NUM_THREADS`, and `NPROC` environment variables to constrain solver threads, preventing "runaway" scenarios where solvers ignore Python's scheduling.
207
+ - **Example scenarios**:
208
+ - Machine with 16 cores (14 available): 3 tasks requiring 4 cores each can run concurrently (3×4=12 cores used, 2 cores free)
209
+ - Machine with 8 cores (6 available): 1 task requiring 4 cores + 1 task requiring 2 cores can run concurrently
210
+ - **Recommendations**:
211
+ - Heavy simulations: `4-8` cores
212
+ - Medium tasks: `2-4` cores
213
+ - Lightweight scripts: `1` core (default)
214
+
215
+ - **`resources.estimated_time`** (optional, float): **Estimated duration in seconds** for LPT (Longest Processing Time) scheduling. Tasks with longer estimated times are scheduled first in parallel runs to improve throughput.
216
+ - Example: `18000` = 5 hours, `3600` = 1 hour, `300` = 5 minutes
217
+
218
+ - **`resources.min_memory_mb`** (optional, float): **Estimated memory requirement in MB**. Used for OOM (Out Of Memory) risk warnings. Currently informational only.
219
+ - Example: `16000` = 16 GB, `8192` = 8 GB, `4096` = 4 GB
220
+
221
+ - **`resources.priority`** (optional, int): **Task priority** (higher number = higher priority). Currently informational only. Recommended range: 0-10.
222
+ - `10`: Critical/blocking tasks (must run first)
223
+ - `7-9`: High priority (important business paths)
224
+ - `4-6`: Normal priority
225
+ - `1-3`: Low priority / exploratory tests
226
+ - `0` or unset: Default priority
227
+
228
+ **Note:**
229
+ - All time values (`timeout`, `estimated_time`) are in **seconds**, not milliseconds. This matches Python's `subprocess.run(timeout=...)` API.
230
+ - **CPU core scheduling is only active in thread mode**. Process mode will fall back to original behavior (process-level isolation provides some resource separation).
231
+
155
232
  ## 5. File Comparison Features
156
233
 
157
234
  ### Supported File Types
@@ -319,12 +396,35 @@ except Exception as e:
319
396
  max_workers = os.cpu_count() * 2
320
397
  ```
321
398
 
322
- 2. **Test Case Design**:
399
+ 2. **Resource-Aware Scheduling**:
400
+ - **For CAE/FEA simulations**: Always specify `cpu_cores` in your test configuration to prevent resource conflicts
401
+ - **Mixed workloads**: Configure lightweight tasks with `cpu_cores: 1` and heavy simulations with appropriate core counts
402
+ - **Example mixed configuration**:
403
+ ```json
404
+ {
405
+ "test_cases": [
406
+ {
407
+ "name": "Heavy Simulation",
408
+ "command": "radioss_solver",
409
+ "resources": { "cpu_cores": 4 }
410
+ },
411
+ {
412
+ "name": "Lightweight Script",
413
+ "command": "python script.py",
414
+ "resources": { "cpu_cores": 1 }
415
+ }
416
+ ]
417
+ }
418
+ ```
419
+ - **Monitor resource usage**: The framework prints resource acquisition/release logs to help you understand scheduling behavior
420
+
421
+ 3. **Test Case Design**:
323
422
  - ✅ Ensure test independence (no dependencies between tests)
324
423
  - ✅ Avoid shared resource conflicts (different files/ports)
325
424
  - ✅ Use relative paths (framework handles resolution automatically)
425
+ - ✅ Specify `cpu_cores` for CPU-intensive tasks to enable intelligent scheduling
326
426
 
327
- 3. **Debugging**:
427
+ 4. **Debugging**:
328
428
  ```python
329
429
  # Enable verbose output for debugging
330
430
  runner = ParallelJSONRunner(
@@ -509,7 +609,22 @@ compare-files data1.h5 data2.h5 --h5-data-filter '<=0.01'
509
609
 
510
610
  # 版本更新日志
511
611
 
512
- ## 0.3.7 (Latest)
612
+ ## 0.4.2 (Latest)
613
+
614
+ ### ✨ New Features
615
+ - **Resource-Aware CPU Scheduling**: Automatic CPU core detection and semaphore-based scheduling to prevent resource conflicts
616
+ - Added `cpu_cores` field in `resources` configuration to specify CPU requirements per task
617
+ - Automatic environment variable injection (`OMP_NUM_THREADS`, `MKL_NUM_THREADS`, `NPROC`) to constrain solver threads
618
+ - Prevents solver "runaway" scenarios where solvers ignore Python's scheduling
619
+ - Intelligent resource pool management: automatically reserves 2 cores for system use
620
+ - **Enhanced execution engine**: Support for custom environment variables in test execution
621
+
622
+ ### 🔧 Improvements
623
+ - **Better resource management**: Tasks now wait for available CPU cores instead of overwhelming the system
624
+ - **Automatic CPU detection**: No manual configuration needed - framework detects available cores automatically
625
+ - **Thread-safe resource allocation**: Semaphore-based scheduling ensures thread-safe resource management
626
+
627
+ ## 0.3.7
513
628
 
514
629
  ### 🐛 Bug Fixes
515
630
  - **Fixed H5 table regex matching**: `--h5-table-regex=table1,table2` now correctly matches both `table1` and `table2` instead of treating the entire string as a single regex pattern
@@ -8,7 +8,7 @@ with open(os.path.join(this_directory, 'README.md'), encoding='utf-8') as f:
8
8
 
9
9
  setup(
10
10
  name="cli-test-framework",
11
- version="0.4.0",
11
+ version="0.4.2",
12
12
  author="Xiaotong Wang",
13
13
  author_email="xiaotongwang98@gmail.com",
14
14
  description="A powerful command line testing framework in Python with setup modules, parallel execution, and file comparison capabilities.",
@@ -1,6 +1,7 @@
1
1
  import subprocess
2
2
  import time
3
- from typing import Optional
3
+ import os
4
+ from typing import Optional, Dict
4
5
 
5
6
  from .assertions import Assertions
6
7
  from .types import ExpectedResult, TestCaseData, TestResultData
@@ -23,12 +24,18 @@ def validate_result(expected: ExpectedResult, actual: TestResultData) -> None:
23
24
  assertions.matches(actual["output"], expected["output_matches"])
24
25
 
25
26
 
26
- def execute_single_test_case(case: TestCaseData, workspace: Optional[str] = None) -> TestResultData:
27
+ def execute_single_test_case(case: TestCaseData, workspace: Optional[str] = None, env: Optional[Dict[str, str]] = None) -> TestResultData:
27
28
  """
28
29
  Stateless execution of a single test case.
30
+
31
+ Args:
32
+ case: Test case data
33
+ workspace: Working directory for test execution
34
+ env: Optional environment variables to inject/override (merged with os.environ)
29
35
  """
30
36
  start_time = time.time()
31
37
  full_command = f"{case['command']} {' '.join(case['args'])}".strip()
38
+ timeout_limit = case.get("timeout", 3600)
32
39
 
33
40
  result: TestResultData = {
34
41
  "name": case["name"],
@@ -40,6 +47,12 @@ def execute_single_test_case(case: TestCaseData, workspace: Optional[str] = None
40
47
  "duration": 0.0,
41
48
  }
42
49
 
50
+ # Prepare environment variables
51
+ # Default to current environment, merge with provided env if any
52
+ current_env = os.environ.copy()
53
+ if env:
54
+ current_env.update(env)
55
+
43
56
  try:
44
57
  process = subprocess.run(
45
58
  full_command,
@@ -48,6 +61,8 @@ def execute_single_test_case(case: TestCaseData, workspace: Optional[str] = None
48
61
  text=True,
49
62
  check=False,
50
63
  shell=True,
64
+ timeout=timeout_limit if timeout_limit is not None else None,
65
+ env=current_env,
51
66
  )
52
67
 
53
68
  output = process.stdout + process.stderr
@@ -56,6 +71,11 @@ def execute_single_test_case(case: TestCaseData, workspace: Optional[str] = None
56
71
 
57
72
  validate_result(case["expected"], result)
58
73
  result["status"] = "passed"
74
+ except subprocess.TimeoutExpired as exc:
75
+ result["status"] = "timeout"
76
+ result["message"] = f"Timeout reached! Killed after {timeout_limit} seconds."
77
+ result["output"] = (exc.stdout or "") + (exc.stderr or "")
78
+ result["return_code"] = None
59
79
  except AssertionError as exc:
60
80
  result["message"] = str(exc)
61
81
  except Exception as exc:
@@ -59,7 +59,9 @@ class ParallelRunner(BaseRunner):
59
59
  "name": case.name,
60
60
  "command": case.command,
61
61
  "args": case.args,
62
- "expected": case.expected
62
+ "expected": case.expected,
63
+ "timeout": case.timeout,
64
+ "resources": case.resources
63
65
  },
64
66
  str(self.workspace) if self.workspace else None
65
67
  ): (i, case)
@@ -25,6 +25,8 @@ def run_test_in_process(test_index: int, case_data: Dict[str, Any], workspace: s
25
25
  "args": case_data["args"],
26
26
  "expected": case_data["expected"],
27
27
  "description": case_data.get("description"),
28
+ "timeout": case_data.get("timeout"),
29
+ "resources": case_data.get("resources"),
28
30
  }
29
31
 
30
32
  command_preview = f"{case['command']} {' '.join(case['args'])}".strip()
@@ -1,5 +1,5 @@
1
1
  from dataclasses import dataclass
2
- from typing import List, Dict, Any
2
+ from typing import List, Dict, Any, Optional
3
3
 
4
4
  @dataclass
5
5
  class TestCase:
@@ -8,6 +8,8 @@ class TestCase:
8
8
  args: List[str]
9
9
  expected: Dict[str, Any]
10
10
  description: str = ""
11
+ timeout: Optional[float] = None
12
+ resources: Optional[Dict[str, Any]] = None
11
13
 
12
14
  def to_dict(self) -> Dict[str, Any]:
13
15
  """Convert test case to dictionary format"""
@@ -17,5 +19,7 @@ class TestCase:
17
19
  "name": self.name,
18
20
  "command": self.command,
19
21
  "args": self.args,
20
- "expected": self.expected
22
+ "expected": self.expected,
23
+ "timeout": self.timeout,
24
+ "resources": self.resources,
21
25
  }
@@ -9,6 +9,15 @@ class ExpectedResult(TypedDict, total=False):
9
9
  output_matches: Optional[str]
10
10
 
11
11
 
12
+ class ResourceRequirements(TypedDict, total=False):
13
+ """Optional resource hints for scheduling."""
14
+
15
+ estimated_time: float # seconds, used for ordering (LPT)
16
+ min_memory_mb: float # soft hint to avoid OOM
17
+ priority: int # higher value => higher priority
18
+ cpu_cores: int # number of CPU cores required by this task
19
+
20
+
12
21
  class TestCaseData(TypedDict):
13
22
  """Input data shape for a test case after解析/路径处理."""
14
23
 
@@ -17,6 +26,8 @@ class TestCaseData(TypedDict):
17
26
  args: List[str]
18
27
  expected: ExpectedResult
19
28
  description: Optional[str]
29
+ timeout: Optional[float]
30
+ resources: Optional[ResourceRequirements]
20
31
 
21
32
 
22
33
  class SetupConfig(TypedDict):