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.
- {cli_test_framework-0.4.0/src/cli_test_framework.egg-info → cli_test_framework-0.4.2}/PKG-INFO +121 -6
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/README.md +120 -5
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/setup.py +1 -1
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/core/execution.py +22 -2
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/core/parallel_runner.py +3 -1
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/core/process_worker.py +2 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/core/test_case.py +6 -2
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/core/types.py +11 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/file_comparator/binary_comparator.py +154 -8
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/file_comparator/h5_comparator.py +233 -20
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/runners/json_runner.py +2 -0
- cli_test_framework-0.4.2/src/cli_test_framework/runners/parallel_json_runner.py +176 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/runners/yaml_runner.py +2 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2/src/cli_test_framework.egg-info}/PKG-INFO +121 -6
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/__pycache__/conftest.cpython-312-pytest-7.4.4.pyc +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/__pycache__/conftest.cpython-39-pytest-8.3.4.pyc +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/__pycache__/run_all.cpython-312.pyc +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/__pycache__/run_all.cpython-39.pyc +0 -0
- cli_test_framework-0.4.2/tests/e2e/__pycache__/__init__.cpython-312.pyc +0 -0
- cli_test_framework-0.4.2/tests/e2e/__pycache__/__init__.cpython-39.pyc +0 -0
- cli_test_framework-0.4.2/tests/e2e/__pycache__/test_user_flows.cpython-312-pytest-7.4.4.pyc +0 -0
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- cli_test_framework-0.4.2/tests/unit/runners/__pycache__/test_json_yaml_runner.cpython-312-pytest-7.4.4.pyc +0 -0
- {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
- cli_test_framework-0.4.0/src/cli_test_framework/runners/parallel_json_runner.py +0 -79
- cli_test_framework-0.4.0/tests/e2e/__pycache__/__init__.cpython-312.pyc +0 -0
- cli_test_framework-0.4.0/tests/e2e/__pycache__/__init__.cpython-39.pyc +0 -0
- cli_test_framework-0.4.0/tests/e2e/__pycache__/test_user_flows.cpython-312-pytest-7.4.4.pyc +0 -0
- cli_test_framework-0.4.0/tests/unit/runners/__pycache__/test_json_yaml_runner.cpython-312-pytest-7.4.4.pyc +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/MANIFEST.in +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/docs/user_manual.md +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/pyproject.toml +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/setup.cfg +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/__init__.py +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/cli.py +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/commands/__init__.py +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/commands/compare.py +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/core/__init__.py +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/core/assertions.py +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/core/base_runner.py +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/core/setup.py +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/file_comparator/__init__.py +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/file_comparator/base_comparator.py +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/file_comparator/csv_comparator.py +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/file_comparator/factory.py +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/file_comparator/json_comparator.py +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/file_comparator/result.py +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/file_comparator/text_comparator.py +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/file_comparator/xml_comparator.py +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/runners/__init__.py +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/utils/__init__.py +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/utils/path_resolver.py +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/utils/report_generator.py +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework.egg-info/SOURCES.txt +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework.egg-info/dependency_links.txt +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework.egg-info/entry_points.txt +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework.egg-info/requires.txt +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework.egg-info/top_level.txt +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/README.md +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/__init__.py +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/__pycache__/__init__.cpython-312.pyc +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/__pycache__/__init__.cpython-39.pyc +0 -0
- {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
- {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
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/conftest.py +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/demos/h5_filter_demo.py +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/demos/manual_report_example.py +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/demos/perf_parallel.py +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/e2e/__init__.py +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/e2e/test_user_flows.py +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/fixtures/test_cases.json +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/fixtures/test_cases.yaml +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/fixtures/test_cases1.json +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/fixtures/test_with_setup.json +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/fixtures/test_with_setup.yaml +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/integration/file_compare/test_binary_compare.py +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/integration/file_compare/test_h5_compare.py +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/integration/file_compare/test_json_compare.py +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/integration/file_compare/test_text_compare.py +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/integration/parallel/test_parallel_runner.py +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/integration/path_handling/test_spaces_in_paths.py +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/run_all.py +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/test_report.txt +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/unit/core/test_setup.py +0 -0
- {cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/tests/unit/runners/test_json_yaml_runner.py +0 -0
{cli_test_framework-0.4.0/src/cli_test_framework.egg-info → cli_test_framework-0.4.2}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cli-test-framework
|
|
3
|
-
Version: 0.4.
|
|
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
|
|
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. **
|
|
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
|
-
|
|
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.
|
|
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
|
|
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. **
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.",
|
{cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/core/execution.py
RENAMED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import subprocess
|
|
2
2
|
import time
|
|
3
|
-
|
|
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:
|
{cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/core/parallel_runner.py
RENAMED
|
@@ -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)
|
{cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/core/process_worker.py
RENAMED
|
@@ -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()
|
{cli_test_framework-0.4.0 → cli_test_framework-0.4.2}/src/cli_test_framework/core/test_case.py
RENAMED
|
@@ -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):
|