cli-test-framework 0.4.5__tar.gz → 0.5.1__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.5/src/cli_test_framework.egg-info → cli_test_framework-0.5.1}/PKG-INFO +5 -3
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/docs/user_manual.md +137 -8
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/setup.py +6 -4
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/src/cli_test_framework/cli.py +8 -3
- cli_test_framework-0.5.1/src/cli_test_framework/core/base_runner.py +162 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/src/cli_test_framework/core/parallel_runner.py +19 -3
- cli_test_framework-0.5.1/src/cli_test_framework/core/process_worker.py +111 -0
- cli_test_framework-0.5.1/src/cli_test_framework/core/test_case.py +47 -0
- cli_test_framework-0.5.1/src/cli_test_framework/runners/json_runner.py +94 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/src/cli_test_framework/runners/parallel_json_runner.py +134 -38
- cli_test_framework-0.5.1/src/cli_test_framework/runners/yaml_runner.py +93 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1/src/cli_test_framework.egg-info}/PKG-INFO +5 -3
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/src/cli_test_framework.egg-info/SOURCES.txt +17 -1
- cli_test_framework-0.5.1/src/cli_test_framework.egg-info/requires.txt +11 -0
- cli_test_framework-0.5.1/tests/__pycache__/conftest.cpython-312-pytest-9.0.3.pyc +0 -0
- cli_test_framework-0.5.1/tests/e2e/__pycache__/test_user_flows.cpython-312-pytest-9.0.3.pyc +0 -0
- cli_test_framework-0.5.1/tests/integration/__pycache__/test_sequence.cpython-312-pytest-7.4.4.pyc +0 -0
- cli_test_framework-0.5.1/tests/integration/__pycache__/test_sequence.cpython-312-pytest-9.0.3.pyc +0 -0
- cli_test_framework-0.5.1/tests/integration/file_compare/__pycache__/test_binary_compare.cpython-312-pytest-9.0.3.pyc +0 -0
- cli_test_framework-0.5.1/tests/integration/file_compare/__pycache__/test_h5_compare.cpython-312-pytest-9.0.3.pyc +0 -0
- cli_test_framework-0.5.1/tests/integration/file_compare/__pycache__/test_json_compare.cpython-312-pytest-9.0.3.pyc +0 -0
- cli_test_framework-0.5.1/tests/integration/file_compare/__pycache__/test_text_compare.cpython-312-pytest-9.0.3.pyc +0 -0
- cli_test_framework-0.5.1/tests/integration/parallel/__pycache__/test_parallel_runner.cpython-312-pytest-9.0.3.pyc +0 -0
- cli_test_framework-0.5.1/tests/integration/path_handling/__pycache__/test_spaces_in_paths.cpython-312-pytest-9.0.3.pyc +0 -0
- cli_test_framework-0.5.1/tests/integration/test_sequence.py +298 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/run_all.py +3 -1
- cli_test_framework-0.5.1/tests/unit/core/__pycache__/test_setup.cpython-312-pytest-9.0.3.pyc +0 -0
- cli_test_framework-0.5.1/tests/unit/runners/__pycache__/test_json_yaml_runner.cpython-312-pytest-9.0.3.pyc +0 -0
- cli_test_framework-0.5.1/tests/unit/runners/__pycache__/test_test_case_filter.cpython-312-pytest-7.4.4.pyc +0 -0
- cli_test_framework-0.5.1/tests/unit/runners/__pycache__/test_test_case_filter.cpython-312-pytest-9.0.3.pyc +0 -0
- cli_test_framework-0.5.1/tests/unit/runners/test_test_case_filter.py +193 -0
- cli_test_framework-0.4.5/src/cli_test_framework/core/base_runner.py +0 -81
- cli_test_framework-0.4.5/src/cli_test_framework/core/process_worker.py +0 -45
- cli_test_framework-0.4.5/src/cli_test_framework/core/test_case.py +0 -25
- cli_test_framework-0.4.5/src/cli_test_framework/runners/json_runner.py +0 -65
- cli_test_framework-0.4.5/src/cli_test_framework/runners/yaml_runner.py +0 -64
- cli_test_framework-0.4.5/src/cli_test_framework.egg-info/requires.txt +0 -5
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/MANIFEST.in +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/README.md +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/pyproject.toml +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/setup.cfg +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/src/cli_test_framework/__init__.py +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/src/cli_test_framework/commands/__init__.py +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/src/cli_test_framework/commands/compare.py +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/src/cli_test_framework/core/__init__.py +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/src/cli_test_framework/core/assertions.py +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/src/cli_test_framework/core/execution.py +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/src/cli_test_framework/core/setup.py +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/src/cli_test_framework/core/types.py +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/src/cli_test_framework/file_comparator/__init__.py +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/src/cli_test_framework/file_comparator/base_comparator.py +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/src/cli_test_framework/file_comparator/binary_comparator.py +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/src/cli_test_framework/file_comparator/csv_comparator.py +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/src/cli_test_framework/file_comparator/factory.py +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/src/cli_test_framework/file_comparator/h5_comparator.py +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/src/cli_test_framework/file_comparator/json_comparator.py +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/src/cli_test_framework/file_comparator/result.py +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/src/cli_test_framework/file_comparator/text_comparator.py +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/src/cli_test_framework/file_comparator/xml_comparator.py +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/src/cli_test_framework/runners/__init__.py +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/src/cli_test_framework/utils/__init__.py +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/src/cli_test_framework/utils/path_resolver.py +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/src/cli_test_framework/utils/report_generator.py +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/src/cli_test_framework.egg-info/dependency_links.txt +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/src/cli_test_framework.egg-info/entry_points.txt +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/src/cli_test_framework.egg-info/top_level.txt +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/README.md +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/__init__.py +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/__pycache__/__init__.cpython-312.pyc +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/__pycache__/__init__.cpython-39.pyc +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/__pycache__/conftest.cpython-312-pytest-7.4.4.pyc +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/__pycache__/conftest.cpython-39-pytest-8.3.4.pyc +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/__pycache__/run_all.cpython-312.pyc +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/__pycache__/run_all.cpython-39.pyc +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/__pycache__/test_parallel_runner.cpython-312-pytest-7.4.4.pyc +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/__pycache__/test_setup_module.cpython-312-pytest-7.4.4.pyc +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/conftest.py +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/demos/h5_filter_demo.py +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/demos/manual_report_example.py +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/demos/perf_parallel.py +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/e2e/__init__.py +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/e2e/__pycache__/__init__.cpython-312.pyc +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/e2e/__pycache__/__init__.cpython-39.pyc +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/e2e/__pycache__/test_user_flows.cpython-312-pytest-7.4.4.pyc +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/e2e/__pycache__/test_user_flows.cpython-39-pytest-8.3.4.pyc +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/e2e/test_user_flows.py +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/fixtures/test_cases.json +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/fixtures/test_cases.yaml +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/fixtures/test_cases1.json +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/fixtures/test_with_setup.json +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/fixtures/test_with_setup.yaml +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/integration/file_compare/__pycache__/test_binary_compare.cpython-312-pytest-7.4.4.pyc +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/integration/file_compare/__pycache__/test_binary_compare.cpython-39-pytest-8.3.4.pyc +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/integration/file_compare/__pycache__/test_h5_compare.cpython-312-pytest-7.4.4.pyc +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/integration/file_compare/__pycache__/test_h5_compare.cpython-39-pytest-8.3.4.pyc +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/integration/file_compare/__pycache__/test_json_compare.cpython-312-pytest-7.4.4.pyc +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/integration/file_compare/__pycache__/test_json_compare.cpython-39-pytest-8.3.4.pyc +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/integration/file_compare/__pycache__/test_text_compare.cpython-312-pytest-7.4.4.pyc +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/integration/file_compare/__pycache__/test_text_compare.cpython-39-pytest-8.3.4.pyc +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/integration/file_compare/test_binary_compare.py +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/integration/file_compare/test_h5_compare.py +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/integration/file_compare/test_json_compare.py +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/integration/file_compare/test_text_compare.py +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/integration/parallel/__pycache__/test_parallel_runner.cpython-312-pytest-7.4.4.pyc +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/integration/parallel/__pycache__/test_parallel_runner.cpython-39-pytest-8.3.4.pyc +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/integration/parallel/test_parallel_runner.py +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/integration/path_handling/__pycache__/test_spaces_in_paths.cpython-312-pytest-7.4.4.pyc +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/integration/path_handling/__pycache__/test_spaces_in_paths.cpython-39-pytest-8.3.4.pyc +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/integration/path_handling/test_spaces_in_paths.py +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/test_report.txt +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/unit/core/__pycache__/test_setup.cpython-312-pytest-7.4.4.pyc +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/unit/core/__pycache__/test_setup.cpython-39-pytest-8.3.4.pyc +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/unit/core/test_setup.py +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/unit/runners/__pycache__/test_json_yaml_runner.cpython-312-pytest-7.4.4.pyc +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/unit/runners/__pycache__/test_json_yaml_runner.cpython-39-pytest-8.3.4.pyc +0 -0
- {cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/tests/unit/runners/test_json_yaml_runner.py +0 -0
{cli_test_framework-0.4.5/src/cli_test_framework.egg-info → cli_test_framework-0.5.1}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cli-test-framework
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.1
|
|
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
|
|
@@ -18,8 +18,10 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
18
18
|
Requires-Python: >=3.9
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
20
|
Requires-Dist: dukpy==0.5.0
|
|
21
|
-
Requires-Dist: h5py
|
|
22
|
-
Requires-Dist:
|
|
21
|
+
Requires-Dist: h5py<4.0.0,>=3.8.0; python_version < "3.12"
|
|
22
|
+
Requires-Dist: h5py<4.0.0,>=3.10.0; python_version >= "3.12"
|
|
23
|
+
Requires-Dist: numpy<2.0.0,>=1.21.0; python_version < "3.12"
|
|
24
|
+
Requires-Dist: numpy<2.0.0,>=1.26.0; python_version >= "3.12"
|
|
23
25
|
Requires-Dist: setuptools>=75.8.0
|
|
24
26
|
Requires-Dist: wheel>=0.45.1
|
|
25
27
|
Dynamic: author
|
|
@@ -7,11 +7,14 @@
|
|
|
7
7
|
4. [Test Case Definition](#test-case-definition)
|
|
8
8
|
5. [Setup Module](#setup-module)
|
|
9
9
|
6. [Parallel Testing](#parallel-testing)
|
|
10
|
-
7. [
|
|
11
|
-
8. [
|
|
12
|
-
9. [
|
|
13
|
-
10. [
|
|
14
|
-
11. [
|
|
10
|
+
7. [Sequence Testing](#sequence-testing)
|
|
11
|
+
8. [File Comparison](#file-comparison)
|
|
12
|
+
9. [Advanced Features](#advanced-features)
|
|
13
|
+
10. [Troubleshooting](#troubleshooting)
|
|
14
|
+
11. [API Reference](#api-reference)
|
|
15
|
+
12. [Examples](#examples)
|
|
16
|
+
|
|
17
|
+
|
|
15
18
|
|
|
16
19
|
## Introduction
|
|
17
20
|
|
|
@@ -19,6 +22,7 @@ The CLI Testing Framework is a powerful tool designed for testing command-line a
|
|
|
19
22
|
|
|
20
23
|
### Key Features
|
|
21
24
|
- Parallel test execution with thread and process support
|
|
25
|
+
- Sequence test execution with multi-step fail-fast
|
|
22
26
|
- JSON/YAML test case definition
|
|
23
27
|
- Advanced file comparison capabilities
|
|
24
28
|
- Comprehensive reporting
|
|
@@ -74,6 +78,18 @@ runner = JSONRunner(
|
|
|
74
78
|
success = runner.run_tests()
|
|
75
79
|
```
|
|
76
80
|
|
|
81
|
+
3. Run only specified test case(s):
|
|
82
|
+
```python
|
|
83
|
+
from cli_test_framework.runners import JSONRunner
|
|
84
|
+
|
|
85
|
+
runner = JSONRunner(
|
|
86
|
+
config_file="test_cases.json",
|
|
87
|
+
workspace="/path/to/workspace",
|
|
88
|
+
test_case_filter=["test_ls_command", "test_echo_command"]
|
|
89
|
+
)
|
|
90
|
+
success = runner.run_tests()
|
|
91
|
+
```
|
|
92
|
+
|
|
77
93
|
### Using the Command Line
|
|
78
94
|
|
|
79
95
|
```bash
|
|
@@ -82,6 +98,12 @@ cli-test run test_cases.json
|
|
|
82
98
|
|
|
83
99
|
# Run tests in parallel
|
|
84
100
|
cli-test run test_cases.json --parallel --workers 4
|
|
101
|
+
|
|
102
|
+
# Run only specified test case(s)
|
|
103
|
+
cli-test run test_cases.json --test-case test_ls_command
|
|
104
|
+
|
|
105
|
+
# Run multiple specified test cases
|
|
106
|
+
cli-test run test_cases.json -t test_ls_command -t test_echo_command
|
|
85
107
|
```
|
|
86
108
|
|
|
87
109
|
## Test Case Definition
|
|
@@ -292,6 +314,54 @@ runner = ParallelJSONRunner(
|
|
|
292
314
|
success = runner.run_tests()
|
|
293
315
|
```
|
|
294
316
|
|
|
317
|
+
## Sequence Testing
|
|
318
|
+
|
|
319
|
+
Sequence testing allows a single test case to contain multiple ordered steps that execute sequentially. If any step fails, subsequent steps are skipped (fail-fast).
|
|
320
|
+
|
|
321
|
+
### JSON Format
|
|
322
|
+
|
|
323
|
+
```json
|
|
324
|
+
{
|
|
325
|
+
"test_cases": [
|
|
326
|
+
{
|
|
327
|
+
"name": "Multi-step Test",
|
|
328
|
+
"steps": [
|
|
329
|
+
{
|
|
330
|
+
"command": "echo",
|
|
331
|
+
"args": ["step1"],
|
|
332
|
+
"expected": { "return_code": 0, "output_contains": ["step1"] }
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
"command": "echo",
|
|
336
|
+
"args": ["step2"],
|
|
337
|
+
"expected": { "return_code": 0, "output_contains": ["step2"] }
|
|
338
|
+
}
|
|
339
|
+
]
|
|
340
|
+
}
|
|
341
|
+
]
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### YAML Format
|
|
346
|
+
|
|
347
|
+
```yaml
|
|
348
|
+
test_cases:
|
|
349
|
+
- name: Multi-step Test
|
|
350
|
+
steps:
|
|
351
|
+
- command: echo
|
|
352
|
+
args: ["step1"]
|
|
353
|
+
expected:
|
|
354
|
+
return_code: 0
|
|
355
|
+
output_contains: ["step1"]
|
|
356
|
+
- command: echo
|
|
357
|
+
args: ["step2"]
|
|
358
|
+
expected:
|
|
359
|
+
return_code: 0
|
|
360
|
+
output_contains: ["step2"]
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
Each step supports `command`, `args`, `expected`, and `timeout` fields. When a step fails, the overall result reports which step failed (e.g., "Failed at step 2/3: ...").
|
|
364
|
+
|
|
295
365
|
## File Comparison
|
|
296
366
|
|
|
297
367
|
### Basic File Comparison
|
|
@@ -355,6 +425,52 @@ compare-files binary1.bin binary2.bin --chunk-size 16384
|
|
|
355
425
|
|
|
356
426
|
## Advanced Features
|
|
357
427
|
|
|
428
|
+
### Specifying Test Cases
|
|
429
|
+
|
|
430
|
+
You can run only specific test cases by name using the `test_case_filter` parameter (Python API) or the `--test-case` / `-t` flag (CLI).
|
|
431
|
+
|
|
432
|
+
#### CLI Usage
|
|
433
|
+
|
|
434
|
+
```bash
|
|
435
|
+
# Run a single test case by name
|
|
436
|
+
cli-test run test_cases.json --test-case test_ls_command
|
|
437
|
+
|
|
438
|
+
# Run multiple test cases (repeat the flag)
|
|
439
|
+
cli-test run test_cases.json -t test_ls_command -t test_echo_command
|
|
440
|
+
|
|
441
|
+
# Works with parallel mode too
|
|
442
|
+
cli-test run test_cases.json --parallel --test-case test_ls_command
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
#### Python API Usage
|
|
446
|
+
|
|
447
|
+
```python
|
|
448
|
+
from cli_test_framework.runners import JSONRunner, ParallelJSONRunner, YAMLRunner
|
|
449
|
+
|
|
450
|
+
# With JSONRunner
|
|
451
|
+
runner = JSONRunner(
|
|
452
|
+
config_file="test_cases.json",
|
|
453
|
+
test_case_filter=["test_ls_command", "test_echo_command"]
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
# With YAMLRunner
|
|
457
|
+
runner = YAMLRunner(
|
|
458
|
+
config_file="test_cases.yaml",
|
|
459
|
+
test_case_filter=["test_case_1"]
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
# With ParallelJSONRunner
|
|
463
|
+
runner = ParallelJSONRunner(
|
|
464
|
+
config_file="test_cases.json",
|
|
465
|
+
test_case_filter=["heavy_test"],
|
|
466
|
+
max_workers=4
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
success = runner.run_tests()
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
If a specified test case name is not found, a warning will be printed. The filter matches test case names exactly.
|
|
473
|
+
|
|
358
474
|
### Custom Assertions
|
|
359
475
|
```python
|
|
360
476
|
from cli_test_framework.assertions import BaseAssertion
|
|
@@ -422,24 +538,37 @@ runner = JSONRunner(
|
|
|
422
538
|
#### JSONRunner
|
|
423
539
|
```python
|
|
424
540
|
class JSONRunner:
|
|
425
|
-
def __init__(self, config_file, workspace=None,
|
|
541
|
+
def __init__(self, config_file, workspace=None, test_case_filter=None):
|
|
426
542
|
"""
|
|
427
543
|
Initialize JSONRunner
|
|
428
544
|
:param config_file: Path to JSON test case file
|
|
429
545
|
:param workspace: Working directory for test execution
|
|
430
|
-
:param
|
|
546
|
+
:param test_case_filter: Optional list of test case names to run (runs all if None)
|
|
547
|
+
"""
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
#### YAMLRunner
|
|
551
|
+
```python
|
|
552
|
+
class YAMLRunner:
|
|
553
|
+
def __init__(self, config_file, workspace=None, test_case_filter=None):
|
|
554
|
+
"""
|
|
555
|
+
Initialize YAMLRunner
|
|
556
|
+
:param config_file: Path to YAML test case file
|
|
557
|
+
:param workspace: Working directory for test execution
|
|
558
|
+
:param test_case_filter: Optional list of test case names to run (runs all if None)
|
|
431
559
|
"""
|
|
432
560
|
```
|
|
433
561
|
|
|
434
562
|
#### ParallelJSONRunner
|
|
435
563
|
```python
|
|
436
564
|
class ParallelJSONRunner:
|
|
437
|
-
def __init__(self, config_file, max_workers=None, execution_mode="thread"):
|
|
565
|
+
def __init__(self, config_file, max_workers=None, execution_mode="thread", test_case_filter=None):
|
|
438
566
|
"""
|
|
439
567
|
Initialize ParallelJSONRunner
|
|
440
568
|
:param config_file: Path to JSON test case file
|
|
441
569
|
:param max_workers: Maximum number of parallel workers
|
|
442
570
|
:param execution_mode: "thread" or "process"
|
|
571
|
+
:param test_case_filter: Optional list of test case names to run (runs all if None)
|
|
443
572
|
"""
|
|
444
573
|
```
|
|
445
574
|
|
|
@@ -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.
|
|
11
|
+
version="0.5.1",
|
|
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.",
|
|
@@ -19,8 +19,10 @@ setup(
|
|
|
19
19
|
package_dir={"": "src"},
|
|
20
20
|
install_requires=[
|
|
21
21
|
"dukpy==0.5.0",
|
|
22
|
-
"h5py>=3.8.0",
|
|
23
|
-
"
|
|
22
|
+
"h5py>=3.8.0,<4.0.0; python_version<'3.12'",
|
|
23
|
+
"h5py>=3.10.0,<4.0.0; python_version>='3.12'",
|
|
24
|
+
"numpy>=1.21.0,<2.0.0; python_version<'3.12'",
|
|
25
|
+
"numpy>=1.26.0,<2.0.0; python_version>='3.12'",
|
|
24
26
|
"setuptools>=75.8.0",
|
|
25
27
|
"wheel>=0.45.1"
|
|
26
28
|
],
|
|
@@ -45,4 +47,4 @@ setup(
|
|
|
45
47
|
'Source': 'https://github.com/ozil111/cli-test-framework',
|
|
46
48
|
'Tracker': 'https://github.com/ozil111/cli-test-framework/issues',
|
|
47
49
|
},
|
|
48
|
-
)
|
|
50
|
+
)
|
|
@@ -40,6 +40,8 @@ Examples:
|
|
|
40
40
|
help='Parallel execution mode (default: thread)')
|
|
41
41
|
run_parser.add_argument('--output-format', choices=['text', 'json', 'html'], default='text',
|
|
42
42
|
help='Output format for test results')
|
|
43
|
+
run_parser.add_argument('--test-case', '-t', action='append', default=None,
|
|
44
|
+
help='Run only specified test case(s) by name (can be used multiple times)')
|
|
43
45
|
run_parser.add_argument('--verbose', '-v', action='store_true', help='Enable verbose output')
|
|
44
46
|
run_parser.add_argument('--debug', action='store_true', help='Enable debug mode')
|
|
45
47
|
|
|
@@ -64,19 +66,22 @@ def run_tests(args):
|
|
|
64
66
|
config_file=str(config_file),
|
|
65
67
|
workspace=args.workspace,
|
|
66
68
|
max_workers=args.workers,
|
|
67
|
-
execution_mode=args.execution_mode
|
|
69
|
+
execution_mode=args.execution_mode,
|
|
70
|
+
test_case_filter=args.test_case
|
|
68
71
|
)
|
|
69
72
|
else:
|
|
70
73
|
# Use appropriate single-threaded runner
|
|
71
74
|
if file_ext in ['.json']:
|
|
72
75
|
runner = JSONRunner(
|
|
73
76
|
config_file=str(config_file),
|
|
74
|
-
workspace=args.workspace
|
|
77
|
+
workspace=args.workspace,
|
|
78
|
+
test_case_filter=args.test_case
|
|
75
79
|
)
|
|
76
80
|
elif file_ext in ['.yaml', '.yml']:
|
|
77
81
|
runner = YAMLRunner(
|
|
78
82
|
config_file=str(config_file),
|
|
79
|
-
workspace=args.workspace
|
|
83
|
+
workspace=args.workspace,
|
|
84
|
+
test_case_filter=args.test_case
|
|
80
85
|
)
|
|
81
86
|
else:
|
|
82
87
|
print(f"Error: Unsupported configuration file format: {file_ext}")
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import List, Dict, Any, Optional
|
|
4
|
+
from .test_case import TestCase
|
|
5
|
+
from .assertions import Assertions
|
|
6
|
+
from .setup import SetupManager, EnvironmentSetup
|
|
7
|
+
from .execution import execute_single_test_case
|
|
8
|
+
|
|
9
|
+
class BaseRunner(ABC):
|
|
10
|
+
def __init__(self, config_file: str, workspace: Optional[str] = None,
|
|
11
|
+
test_case_filter: Optional[List[str]] = None):
|
|
12
|
+
if workspace:
|
|
13
|
+
self.workspace = Path(workspace)
|
|
14
|
+
else:
|
|
15
|
+
self.workspace = Path(__file__).parent.parent.parent
|
|
16
|
+
self.config_path = self.workspace / config_file
|
|
17
|
+
self.test_cases: List[TestCase] = []
|
|
18
|
+
self.test_case_filter: Optional[List[str]] = test_case_filter
|
|
19
|
+
self.results: Dict[str, Any] = {
|
|
20
|
+
"total": 0,
|
|
21
|
+
"passed": 0,
|
|
22
|
+
"failed": 0,
|
|
23
|
+
"details": []
|
|
24
|
+
}
|
|
25
|
+
self.assertions = Assertions()
|
|
26
|
+
self.setup_manager = SetupManager()
|
|
27
|
+
|
|
28
|
+
@abstractmethod
|
|
29
|
+
def load_test_cases(self) -> None:
|
|
30
|
+
"""Load test cases from configuration file"""
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
def load_setup_from_config(self, config: Dict[str, Any]) -> None:
|
|
34
|
+
"""从配置文件加载setup配置"""
|
|
35
|
+
setup_config = config.get("setup", {})
|
|
36
|
+
|
|
37
|
+
# 处理环境变量设置
|
|
38
|
+
if "environment_variables" in setup_config:
|
|
39
|
+
env_setup = EnvironmentSetup({"environment_variables": setup_config["environment_variables"]})
|
|
40
|
+
self.setup_manager.add_setup(env_setup)
|
|
41
|
+
|
|
42
|
+
# 这里可以扩展支持其他类型的setup插件
|
|
43
|
+
# 例如:
|
|
44
|
+
# if "custom_setups" in setup_config:
|
|
45
|
+
# for custom_setup_config in setup_config["custom_setups"]:
|
|
46
|
+
# # 动态加载自定义setup插件
|
|
47
|
+
# pass
|
|
48
|
+
|
|
49
|
+
def _apply_test_case_filter(self) -> None:
|
|
50
|
+
"""根据 test_case_filter 过滤测试用例"""
|
|
51
|
+
if self.test_case_filter:
|
|
52
|
+
original_count = len(self.test_cases)
|
|
53
|
+
self.test_cases = [tc for tc in self.test_cases if tc.name in self.test_case_filter]
|
|
54
|
+
filtered_out = original_count - len(self.test_cases)
|
|
55
|
+
if filtered_out > 0:
|
|
56
|
+
print(f"Filtered out {filtered_out} test case(s). Running {len(self.test_cases)} specified case(s).")
|
|
57
|
+
if not self.test_cases:
|
|
58
|
+
print(f"Warning: No matching test cases found for: {self.test_case_filter}")
|
|
59
|
+
|
|
60
|
+
def run_tests(self) -> bool:
|
|
61
|
+
"""Run all test cases and return whether all tests passed"""
|
|
62
|
+
try:
|
|
63
|
+
self.load_test_cases()
|
|
64
|
+
self._apply_test_case_filter()
|
|
65
|
+
self.results["total"] = len(self.test_cases)
|
|
66
|
+
|
|
67
|
+
if self.results["total"] == 0:
|
|
68
|
+
print("No test cases to run.")
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
# 执行setup任务
|
|
72
|
+
self.setup_manager.setup_all()
|
|
73
|
+
|
|
74
|
+
print(f"\nStarting test execution... Total tests: {self.results['total']}")
|
|
75
|
+
print("=" * 50)
|
|
76
|
+
|
|
77
|
+
for i, case in enumerate(self.test_cases, 1):
|
|
78
|
+
print(f"\nRunning test {i}/{self.results['total']}: {case.name}")
|
|
79
|
+
result = self.run_single_test(case)
|
|
80
|
+
self.results["details"].append(result)
|
|
81
|
+
if result["status"] == "passed":
|
|
82
|
+
self.results["passed"] += 1
|
|
83
|
+
print(f"✓ Test passed: {case.name}")
|
|
84
|
+
else:
|
|
85
|
+
self.results["failed"] += 1
|
|
86
|
+
print(f"✗ Test failed: {case.name}")
|
|
87
|
+
if result["message"]:
|
|
88
|
+
print(f" Error: {result['message']}")
|
|
89
|
+
|
|
90
|
+
print("\n" + "=" * 50)
|
|
91
|
+
print(f"Test execution completed. Passed: {self.results['passed']}, Failed: {self.results['failed']}")
|
|
92
|
+
return self.results["failed"] == 0
|
|
93
|
+
finally:
|
|
94
|
+
# 确保teardown总是被执行
|
|
95
|
+
self.setup_manager.teardown_all()
|
|
96
|
+
|
|
97
|
+
def _run_sequence(self, case: TestCase) -> Dict[str, Any]:
|
|
98
|
+
"""Run a sequence test case with multiple steps (fail-fast)."""
|
|
99
|
+
combined_output = ""
|
|
100
|
+
total_duration = 0.0
|
|
101
|
+
all_passed = True
|
|
102
|
+
last_result = None
|
|
103
|
+
failed_step = None
|
|
104
|
+
|
|
105
|
+
for i, step in enumerate(case.steps):
|
|
106
|
+
step_name = f"{case.name} [step {i+1}/{len(case.steps)}]"
|
|
107
|
+
case_data = {
|
|
108
|
+
"name": step_name,
|
|
109
|
+
"command": step.command,
|
|
110
|
+
"args": step.args,
|
|
111
|
+
"expected": step.expected,
|
|
112
|
+
"description": None,
|
|
113
|
+
"timeout": step.timeout,
|
|
114
|
+
"resources": None,
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
command_preview = f"{step.command} {' '.join(step.args)}".strip()
|
|
118
|
+
print(f" Executing step {i+1}/{len(case.steps)}: {command_preview}")
|
|
119
|
+
|
|
120
|
+
result = execute_single_test_case(
|
|
121
|
+
case_data, str(self.workspace) if self.workspace else None
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
if result["output"].strip():
|
|
125
|
+
print(" Command output:")
|
|
126
|
+
for line in result["output"].splitlines():
|
|
127
|
+
print(f" {line}")
|
|
128
|
+
|
|
129
|
+
combined_output += result["output"]
|
|
130
|
+
total_duration += result["duration"]
|
|
131
|
+
last_result = result
|
|
132
|
+
|
|
133
|
+
if result["status"] != "passed":
|
|
134
|
+
all_passed = False
|
|
135
|
+
failed_step = i + 1
|
|
136
|
+
if result.get("message"):
|
|
137
|
+
print(f" Error at step {i+1}: {result['message']}")
|
|
138
|
+
break
|
|
139
|
+
|
|
140
|
+
status = "passed" if all_passed else last_result["status"]
|
|
141
|
+
message = ""
|
|
142
|
+
if not all_passed:
|
|
143
|
+
message = f"Failed at step {failed_step}/{len(case.steps)}: {last_result['message']}"
|
|
144
|
+
|
|
145
|
+
command_summary = " -> ".join(
|
|
146
|
+
f"{s.command} {' '.join(s.args)}".strip() for s in case.steps
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
"name": case.name,
|
|
151
|
+
"status": status,
|
|
152
|
+
"message": message,
|
|
153
|
+
"command": command_summary,
|
|
154
|
+
"output": combined_output,
|
|
155
|
+
"return_code": last_result["return_code"] if last_result else None,
|
|
156
|
+
"duration": total_duration,
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
@abstractmethod
|
|
160
|
+
def run_single_test(self, case: TestCase) -> Dict[str, str]:
|
|
161
|
+
"""Run a single test case and return the result"""
|
|
162
|
+
pass
|
{cli_test_framework-0.4.5 → cli_test_framework-0.5.1}/src/cli_test_framework/core/parallel_runner.py
RENAMED
|
@@ -12,7 +12,8 @@ class ParallelRunner(BaseRunner):
|
|
|
12
12
|
|
|
13
13
|
def __init__(self, config_file: str, workspace: Optional[str] = None,
|
|
14
14
|
max_workers: Optional[int] = None,
|
|
15
|
-
execution_mode: str = "thread"
|
|
15
|
+
execution_mode: str = "thread",
|
|
16
|
+
test_case_filter: Optional[List[str]] = None):
|
|
16
17
|
"""
|
|
17
18
|
初始化并行运行器
|
|
18
19
|
|
|
@@ -21,8 +22,9 @@ class ParallelRunner(BaseRunner):
|
|
|
21
22
|
workspace: 工作目录
|
|
22
23
|
max_workers: 最大并发数,默认为CPU核心数
|
|
23
24
|
execution_mode: 执行模式,'thread'(线程) 或 'process'(进程)
|
|
25
|
+
test_case_filter: 只运行指定名称的测试用例
|
|
24
26
|
"""
|
|
25
|
-
super().__init__(config_file, workspace)
|
|
27
|
+
super().__init__(config_file, workspace, test_case_filter)
|
|
26
28
|
self.max_workers = max_workers
|
|
27
29
|
self.execution_mode = execution_mode
|
|
28
30
|
self.lock = threading.Lock() # 用于线程安全的结果更新
|
|
@@ -31,8 +33,13 @@ class ParallelRunner(BaseRunner):
|
|
|
31
33
|
"""并行运行所有测试用例"""
|
|
32
34
|
try:
|
|
33
35
|
self.load_test_cases()
|
|
36
|
+
self._apply_test_case_filter()
|
|
34
37
|
self.results["total"] = len(self.test_cases)
|
|
35
38
|
|
|
39
|
+
if self.results["total"] == 0:
|
|
40
|
+
print("No test cases to run.")
|
|
41
|
+
return False
|
|
42
|
+
|
|
36
43
|
# 执行setup任务
|
|
37
44
|
self.setup_manager.setup_all()
|
|
38
45
|
|
|
@@ -61,7 +68,16 @@ class ParallelRunner(BaseRunner):
|
|
|
61
68
|
"args": case.args,
|
|
62
69
|
"expected": case.expected,
|
|
63
70
|
"timeout": case.timeout,
|
|
64
|
-
"resources": case.resources
|
|
71
|
+
"resources": case.resources,
|
|
72
|
+
"steps": [
|
|
73
|
+
{
|
|
74
|
+
"command": s.command,
|
|
75
|
+
"args": s.args,
|
|
76
|
+
"expected": s.expected,
|
|
77
|
+
"timeout": s.timeout,
|
|
78
|
+
}
|
|
79
|
+
for s in case.steps
|
|
80
|
+
] if case.steps else None,
|
|
65
81
|
},
|
|
66
82
|
str(self.workspace) if self.workspace else None
|
|
67
83
|
): (i, case)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""
|
|
2
|
+
进程工作器模块
|
|
3
|
+
用于多进程并行测试执行,避免序列化问题
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Dict, Any, List
|
|
7
|
+
from .execution import execute_single_test_case
|
|
8
|
+
from .types import TestCaseData
|
|
9
|
+
|
|
10
|
+
def _run_sequence_in_process(test_index: int, case_data: Dict[str, Any], workspace: str = None) -> Dict[str, Any]:
|
|
11
|
+
"""Run a sequence test case with multiple steps (fail-fast) in a process worker."""
|
|
12
|
+
steps: List[Dict[str, Any]] = case_data["steps"]
|
|
13
|
+
combined_output = ""
|
|
14
|
+
total_duration = 0.0
|
|
15
|
+
all_passed = True
|
|
16
|
+
last_result = None
|
|
17
|
+
failed_step = None
|
|
18
|
+
|
|
19
|
+
for i, step in enumerate(steps):
|
|
20
|
+
step_name = f"{case_data['name']} [step {i+1}/{len(steps)}]"
|
|
21
|
+
step_case: TestCaseData = {
|
|
22
|
+
"name": step_name,
|
|
23
|
+
"command": step["command"],
|
|
24
|
+
"args": step["args"],
|
|
25
|
+
"expected": step["expected"],
|
|
26
|
+
"description": None,
|
|
27
|
+
"timeout": step.get("timeout"),
|
|
28
|
+
"resources": None,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
command_preview = f"{step['command']} {' '.join(step['args'])}".strip()
|
|
32
|
+
print(f" [Process Worker {test_index}] Executing step {i+1}/{len(steps)}: {command_preview}")
|
|
33
|
+
|
|
34
|
+
result = execute_single_test_case(step_case, workspace)
|
|
35
|
+
|
|
36
|
+
if result["output"].strip():
|
|
37
|
+
print(f" [Process Worker {test_index}] Command output for {step_name}:")
|
|
38
|
+
for line in result["output"].splitlines():
|
|
39
|
+
print(f" {line}")
|
|
40
|
+
|
|
41
|
+
combined_output += result["output"]
|
|
42
|
+
total_duration += result["duration"]
|
|
43
|
+
last_result = result
|
|
44
|
+
|
|
45
|
+
if result["status"] != "passed":
|
|
46
|
+
all_passed = False
|
|
47
|
+
failed_step = i + 1
|
|
48
|
+
if result.get("message"):
|
|
49
|
+
print(f" [Process Worker {test_index}] Error at step {i+1}: {result['message']}")
|
|
50
|
+
break
|
|
51
|
+
|
|
52
|
+
status = "passed" if all_passed else last_result["status"]
|
|
53
|
+
message = ""
|
|
54
|
+
if not all_passed:
|
|
55
|
+
message = f"Failed at step {failed_step}/{len(steps)}: {last_result['message']}"
|
|
56
|
+
|
|
57
|
+
command_summary = " -> ".join(
|
|
58
|
+
f"{s['command']} {' '.join(s['args'])}".strip() for s in steps
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
"name": case_data["name"],
|
|
63
|
+
"status": status,
|
|
64
|
+
"message": message,
|
|
65
|
+
"command": command_summary,
|
|
66
|
+
"output": combined_output,
|
|
67
|
+
"return_code": last_result["return_code"] if last_result else None,
|
|
68
|
+
"duration": total_duration,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
def run_test_in_process(test_index: int, case_data: Dict[str, Any], workspace: str = None) -> Dict[str, Any]:
|
|
72
|
+
"""
|
|
73
|
+
在独立进程中运行单个测试用例
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
test_index: 测试索引
|
|
77
|
+
case_data: 测试用例数据字典
|
|
78
|
+
workspace: 工作目录
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
测试结果字典
|
|
82
|
+
"""
|
|
83
|
+
# Sequence mode
|
|
84
|
+
if case_data.get("steps"):
|
|
85
|
+
return _run_sequence_in_process(test_index, case_data, workspace)
|
|
86
|
+
|
|
87
|
+
# Single command mode
|
|
88
|
+
case: TestCaseData = {
|
|
89
|
+
"name": case_data["name"],
|
|
90
|
+
"command": case_data["command"],
|
|
91
|
+
"args": case_data["args"],
|
|
92
|
+
"expected": case_data["expected"],
|
|
93
|
+
"description": case_data.get("description"),
|
|
94
|
+
"timeout": case_data.get("timeout"),
|
|
95
|
+
"resources": case_data.get("resources"),
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
command_preview = f"{case['command']} {' '.join(case['args'])}".strip()
|
|
99
|
+
print(f" [Process Worker {test_index}] Executing command: {command_preview}")
|
|
100
|
+
|
|
101
|
+
result = execute_single_test_case(case, workspace)
|
|
102
|
+
|
|
103
|
+
if result["output"].strip():
|
|
104
|
+
print(f" [Process Worker {test_index}] Command output for {case['name']}:")
|
|
105
|
+
for line in result["output"].splitlines():
|
|
106
|
+
print(f" {line}")
|
|
107
|
+
|
|
108
|
+
if result["status"] != "passed" and result.get("message"):
|
|
109
|
+
print(f" [Process Worker {test_index}] Error for {case['name']}: {result['message']}")
|
|
110
|
+
|
|
111
|
+
return result
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import List, Dict, Any, Optional
|
|
3
|
+
|
|
4
|
+
@dataclass
|
|
5
|
+
class TestCaseStep:
|
|
6
|
+
"""A single step within a sequence test case."""
|
|
7
|
+
__test__ = False
|
|
8
|
+
command: str
|
|
9
|
+
args: List[str]
|
|
10
|
+
expected: Dict[str, Any]
|
|
11
|
+
timeout: Optional[float] = None
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class TestCase:
|
|
15
|
+
__test__ = False
|
|
16
|
+
name: str
|
|
17
|
+
command: str = ""
|
|
18
|
+
args: List[str] = field(default_factory=list)
|
|
19
|
+
expected: Dict[str, Any] = field(default_factory=dict)
|
|
20
|
+
description: str = ""
|
|
21
|
+
timeout: Optional[float] = None
|
|
22
|
+
resources: Optional[Dict[str, Any]] = None
|
|
23
|
+
steps: Optional[List[TestCaseStep]] = None
|
|
24
|
+
|
|
25
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
26
|
+
"""Convert test case to dictionary format"""
|
|
27
|
+
print("Convert test case to dictionary format")
|
|
28
|
+
print(self.command)
|
|
29
|
+
result = {
|
|
30
|
+
"name": self.name,
|
|
31
|
+
"command": self.command,
|
|
32
|
+
"args": self.args,
|
|
33
|
+
"expected": self.expected,
|
|
34
|
+
"timeout": self.timeout,
|
|
35
|
+
"resources": self.resources,
|
|
36
|
+
}
|
|
37
|
+
if self.steps is not None:
|
|
38
|
+
result["steps"] = [
|
|
39
|
+
{
|
|
40
|
+
"command": s.command,
|
|
41
|
+
"args": s.args,
|
|
42
|
+
"expected": s.expected,
|
|
43
|
+
"timeout": s.timeout,
|
|
44
|
+
}
|
|
45
|
+
for s in self.steps
|
|
46
|
+
]
|
|
47
|
+
return result
|