cli-test-framework 0.5.1__tar.gz → 0.5.3__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.5.3/PKG-INFO +146 -0
- cli_test_framework-0.5.3/README.md +109 -0
- cli_test_framework-0.5.3/docs/design.md +452 -0
- cli_test_framework-0.5.3/docs/design_en.md +401 -0
- cli_test_framework-0.5.3/docs/user_manual.md +558 -0
- cli_test_framework-0.5.3/docs/user_manual_en.md +475 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/setup.py +1 -1
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/src/cli_test_framework/cli.py +20 -5
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/src/cli_test_framework/commands/compare.py +26 -5
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/src/cli_test_framework/core/base_runner.py +35 -4
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/src/cli_test_framework/core/execution.py +9 -1
- cli_test_framework-0.5.3/src/cli_test_framework/core/history_store.py +85 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/src/cli_test_framework/core/parallel_runner.py +13 -4
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/src/cli_test_framework/file_comparator/csv_comparator.py +16 -1
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/src/cli_test_framework/file_comparator/factory.py +5 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/src/cli_test_framework/file_comparator/h5_comparator.py +1 -1
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/src/cli_test_framework/file_comparator/json_comparator.py +0 -5
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/src/cli_test_framework/file_comparator/result.py +11 -1
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/src/cli_test_framework/runners/json_runner.py +4 -2
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/src/cli_test_framework/runners/parallel_json_runner.py +24 -8
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/src/cli_test_framework/runners/yaml_runner.py +4 -2
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/src/cli_test_framework/utils/path_resolver.py +12 -5
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/src/cli_test_framework/utils/report_generator.py +6 -3
- cli_test_framework-0.5.3/src/cli_test_framework.egg-info/PKG-INFO +146 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/src/cli_test_framework.egg-info/SOURCES.txt +27 -1
- cli_test_framework-0.5.3/tests/__pycache__/run_all.cpython-312.pyc +0 -0
- cli_test_framework-0.5.3/tests/e2e/__pycache__/test_user_flows.cpython-312-pytest-9.0.3.pyc +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/e2e/test_user_flows.py +15 -24
- cli_test_framework-0.5.3/tests/integration/__pycache__/test_history_feature.cpython-312-pytest-9.0.3.pyc +0 -0
- cli_test_framework-0.5.3/tests/integration/__pycache__/test_real_command_execution.cpython-312-pytest-9.0.3.pyc +0 -0
- cli_test_framework-0.5.3/tests/integration/file_compare/__pycache__/test_csv_compare.cpython-312-pytest-9.0.3.pyc +0 -0
- cli_test_framework-0.5.3/tests/integration/file_compare/__pycache__/test_xml_compare.cpython-312-pytest-9.0.3.pyc +0 -0
- cli_test_framework-0.5.3/tests/integration/file_compare/test_csv_compare.py +111 -0
- cli_test_framework-0.5.3/tests/integration/file_compare/test_xml_compare.py +72 -0
- cli_test_framework-0.5.3/tests/integration/parallel/__pycache__/test_parallel_runner.cpython-312-pytest-9.0.3.pyc +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/integration/parallel/test_parallel_runner.py +34 -0
- cli_test_framework-0.5.3/tests/integration/path_handling/__pycache__/test_spaces_in_paths.cpython-312-pytest-9.0.3.pyc +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/integration/path_handling/test_spaces_in_paths.py +31 -0
- cli_test_framework-0.5.3/tests/integration/test_history_feature.py +176 -0
- cli_test_framework-0.5.3/tests/integration/test_real_command_execution.py +124 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/run_all.py +2 -1
- cli_test_framework-0.5.3/tests/unit/__pycache__/test_cli.cpython-312-pytest-9.0.3.pyc +0 -0
- cli_test_framework-0.5.3/tests/unit/__pycache__/test_h5_comparator.cpython-312-pytest-9.0.3.pyc +0 -0
- cli_test_framework-0.5.3/tests/unit/__pycache__/test_run_all.cpython-312-pytest-9.0.3.pyc +0 -0
- cli_test_framework-0.5.3/tests/unit/commands/__pycache__/test_compare_command.cpython-312-pytest-9.0.3.pyc +0 -0
- cli_test_framework-0.5.3/tests/unit/commands/test_compare_command.py +73 -0
- cli_test_framework-0.5.3/tests/unit/core/__pycache__/test_history_store.cpython-312-pytest-9.0.3.pyc +0 -0
- cli_test_framework-0.5.3/tests/unit/core/__pycache__/test_process_worker.cpython-312-pytest-9.0.3.pyc +0 -0
- cli_test_framework-0.5.3/tests/unit/core/test_history_store.py +140 -0
- cli_test_framework-0.5.3/tests/unit/core/test_process_worker.py +106 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/unit/runners/__pycache__/test_test_case_filter.cpython-312-pytest-9.0.3.pyc +0 -0
- cli_test_framework-0.5.3/tests/unit/test_cli.py +109 -0
- cli_test_framework-0.5.3/tests/unit/test_h5_comparator.py +696 -0
- cli_test_framework-0.5.3/tests/unit/test_run_all.py +18 -0
- cli_test_framework-0.5.3/tests/unit/utils/__pycache__/test_report_generator.cpython-312-pytest-9.0.3.pyc +0 -0
- cli_test_framework-0.5.3/tests/unit/utils/test_report_generator.py +59 -0
- cli_test_framework-0.5.1/PKG-INFO +0 -682
- cli_test_framework-0.5.1/README.md +0 -645
- cli_test_framework-0.5.1/docs/user_manual.md +0 -663
- cli_test_framework-0.5.1/src/cli_test_framework.egg-info/PKG-INFO +0 -682
- cli_test_framework-0.5.1/tests/__pycache__/run_all.cpython-312.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/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 → cli_test_framework-0.5.3}/MANIFEST.in +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/pyproject.toml +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/setup.cfg +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/src/cli_test_framework/__init__.py +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/src/cli_test_framework/commands/__init__.py +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/src/cli_test_framework/core/__init__.py +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/src/cli_test_framework/core/assertions.py +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/src/cli_test_framework/core/process_worker.py +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/src/cli_test_framework/core/setup.py +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/src/cli_test_framework/core/test_case.py +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/src/cli_test_framework/core/types.py +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/src/cli_test_framework/file_comparator/__init__.py +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/src/cli_test_framework/file_comparator/base_comparator.py +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/src/cli_test_framework/file_comparator/binary_comparator.py +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/src/cli_test_framework/file_comparator/text_comparator.py +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/src/cli_test_framework/file_comparator/xml_comparator.py +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/src/cli_test_framework/runners/__init__.py +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/src/cli_test_framework/utils/__init__.py +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/src/cli_test_framework.egg-info/dependency_links.txt +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/src/cli_test_framework.egg-info/entry_points.txt +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/src/cli_test_framework.egg-info/requires.txt +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/src/cli_test_framework.egg-info/top_level.txt +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/README.md +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/__init__.py +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/__pycache__/__init__.cpython-312.pyc +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/__pycache__/__init__.cpython-39.pyc +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/__pycache__/conftest.cpython-312-pytest-7.4.4.pyc +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/__pycache__/conftest.cpython-312-pytest-9.0.3.pyc +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/__pycache__/conftest.cpython-39-pytest-8.3.4.pyc +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/__pycache__/run_all.cpython-39.pyc +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/__pycache__/test_parallel_runner.cpython-312-pytest-7.4.4.pyc +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/__pycache__/test_setup_module.cpython-312-pytest-7.4.4.pyc +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/conftest.py +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/demos/h5_filter_demo.py +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/demos/manual_report_example.py +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/demos/perf_parallel.py +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/e2e/__init__.py +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/e2e/__pycache__/__init__.cpython-312.pyc +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/e2e/__pycache__/__init__.cpython-39.pyc +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/e2e/__pycache__/test_user_flows.cpython-312-pytest-7.4.4.pyc +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/e2e/__pycache__/test_user_flows.cpython-39-pytest-8.3.4.pyc +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/fixtures/test_cases.json +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/fixtures/test_cases.yaml +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/fixtures/test_cases1.json +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/fixtures/test_with_setup.json +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/fixtures/test_with_setup.yaml +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/integration/__pycache__/test_sequence.cpython-312-pytest-7.4.4.pyc +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/integration/__pycache__/test_sequence.cpython-312-pytest-9.0.3.pyc +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/integration/file_compare/__pycache__/test_binary_compare.cpython-312-pytest-7.4.4.pyc +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/integration/file_compare/__pycache__/test_binary_compare.cpython-312-pytest-9.0.3.pyc +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/integration/file_compare/__pycache__/test_binary_compare.cpython-39-pytest-8.3.4.pyc +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/integration/file_compare/__pycache__/test_h5_compare.cpython-312-pytest-7.4.4.pyc +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/integration/file_compare/__pycache__/test_h5_compare.cpython-312-pytest-9.0.3.pyc +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/integration/file_compare/__pycache__/test_h5_compare.cpython-39-pytest-8.3.4.pyc +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/integration/file_compare/__pycache__/test_json_compare.cpython-312-pytest-7.4.4.pyc +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/integration/file_compare/__pycache__/test_json_compare.cpython-312-pytest-9.0.3.pyc +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/integration/file_compare/__pycache__/test_json_compare.cpython-39-pytest-8.3.4.pyc +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/integration/file_compare/__pycache__/test_text_compare.cpython-312-pytest-7.4.4.pyc +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/integration/file_compare/__pycache__/test_text_compare.cpython-312-pytest-9.0.3.pyc +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/integration/file_compare/__pycache__/test_text_compare.cpython-39-pytest-8.3.4.pyc +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/integration/file_compare/test_binary_compare.py +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/integration/file_compare/test_h5_compare.py +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/integration/file_compare/test_json_compare.py +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/integration/file_compare/test_text_compare.py +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/integration/parallel/__pycache__/test_parallel_runner.cpython-312-pytest-7.4.4.pyc +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/integration/parallel/__pycache__/test_parallel_runner.cpython-39-pytest-8.3.4.pyc +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/integration/path_handling/__pycache__/test_spaces_in_paths.cpython-312-pytest-7.4.4.pyc +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/integration/path_handling/__pycache__/test_spaces_in_paths.cpython-39-pytest-8.3.4.pyc +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/integration/test_sequence.py +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/test_report.txt +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/unit/core/__pycache__/test_setup.cpython-312-pytest-7.4.4.pyc +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/unit/core/__pycache__/test_setup.cpython-312-pytest-9.0.3.pyc +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/unit/core/__pycache__/test_setup.cpython-39-pytest-8.3.4.pyc +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/unit/core/test_setup.py +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/unit/runners/__pycache__/test_json_yaml_runner.cpython-312-pytest-7.4.4.pyc +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/unit/runners/__pycache__/test_json_yaml_runner.cpython-312-pytest-9.0.3.pyc +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/unit/runners/__pycache__/test_json_yaml_runner.cpython-39-pytest-8.3.4.pyc +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/unit/runners/__pycache__/test_test_case_filter.cpython-312-pytest-7.4.4.pyc +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/unit/runners/test_json_yaml_runner.py +0 -0
- {cli_test_framework-0.5.1 → cli_test_framework-0.5.3}/tests/unit/runners/test_test_case_filter.py +0 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cli-test-framework
|
|
3
|
+
Version: 0.5.3
|
|
4
|
+
Summary: A powerful command line testing framework in Python with setup modules, parallel execution, and file comparison capabilities.
|
|
5
|
+
Home-page: https://github.com/ozil111/cli-test-framework
|
|
6
|
+
Author: Xiaotong Wang
|
|
7
|
+
Author-email: xiaotongwang98@gmail.com
|
|
8
|
+
Project-URL: Documentation, https://github.com/ozil111/cli-test-framework/blob/main/docs/user_manual.md
|
|
9
|
+
Project-URL: Source, https://github.com/ozil111/cli-test-framework
|
|
10
|
+
Project-URL: Tracker, https://github.com/ozil111/cli-test-framework/issues
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Topic :: Software Development :: Testing
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
+
Requires-Python: >=3.9
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Requires-Dist: dukpy==0.5.0
|
|
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"
|
|
25
|
+
Requires-Dist: setuptools>=75.8.0
|
|
26
|
+
Requires-Dist: wheel>=0.45.1
|
|
27
|
+
Dynamic: author
|
|
28
|
+
Dynamic: author-email
|
|
29
|
+
Dynamic: classifier
|
|
30
|
+
Dynamic: description
|
|
31
|
+
Dynamic: description-content-type
|
|
32
|
+
Dynamic: home-page
|
|
33
|
+
Dynamic: project-url
|
|
34
|
+
Dynamic: requires-dist
|
|
35
|
+
Dynamic: requires-python
|
|
36
|
+
Dynamic: summary
|
|
37
|
+
|
|
38
|
+
# CLI Test Framework
|
|
39
|
+
|
|
40
|
+
A lightweight automated testing framework for command-line applications. Define test cases in JSON/YAML, run all validations with a single command.
|
|
41
|
+
|
|
42
|
+
Particularly suited for scientific computing — deep HDF5 support with regex table matching, data filtering, and tolerance-based comparison, making simulation result verification effortless.
|
|
43
|
+
|
|
44
|
+
## Highlights
|
|
45
|
+
|
|
46
|
+
- **Parallel Execution** — Multi-thread / multi-process, 3-5x speedup
|
|
47
|
+
- **Resource-Aware Scheduling** — Automatic CPU core management, prevents solver thread runaway
|
|
48
|
+
- **Sequence Steps** — Multi-step execution within a single test case, fail-fast
|
|
49
|
+
- **Setup Module** — Auto-configure environment variables before tests, auto-cleanup after
|
|
50
|
+
- **File Comparison** — Text / JSON / HDF5 / Binary, ready to use from command line
|
|
51
|
+
- **Filtered Execution** — Run specific test cases by name
|
|
52
|
+
|
|
53
|
+
## Quick Start
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install cli-test-framework
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 30-Second Setup
|
|
60
|
+
|
|
61
|
+
1. Create `test_cases.json`:
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"test_cases": [
|
|
66
|
+
{
|
|
67
|
+
"name": "hello",
|
|
68
|
+
"command": "echo",
|
|
69
|
+
"args": ["Hello World"],
|
|
70
|
+
"expected": {
|
|
71
|
+
"return_code": 0,
|
|
72
|
+
"output_contains": ["Hello World"]
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
2. Run:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
cli-test run test_cases.json
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Parallel Execution
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
cli-test run test_cases.json --parallel --workers 4
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Python API
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
from cli_test_framework.runners import JSONRunner, ParallelJSONRunner
|
|
95
|
+
|
|
96
|
+
# Sequential
|
|
97
|
+
runner = JSONRunner(config_file="test_cases.json")
|
|
98
|
+
success = runner.run_tests()
|
|
99
|
+
|
|
100
|
+
# Parallel
|
|
101
|
+
runner = ParallelJSONRunner(config_file="test_cases.json", max_workers=4, execution_mode="thread")
|
|
102
|
+
success = runner.run_tests()
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### File Comparison
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
compare-files result1.h5 result2.h5 --h5-table-regex "output_.*" --h5-rtol 1e-5
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
📖 **Full Documentation**: [docs/user_manual.md](docs/user_manual_en.md)
|
|
112
|
+
|
|
113
|
+
## Changelog
|
|
114
|
+
|
|
115
|
+
### 0.5.2
|
|
116
|
+
|
|
117
|
+
- Runtime history tracking (`--history-dir`): persist per-case execution time in `.symtest`, enable smart scheduling & regression detection
|
|
118
|
+
- Regression warning: alert when a case runs slower than historical average × threshold (`--regression-threshold`, default 1.5)
|
|
119
|
+
- Smart scheduling: parallel runner prioritizes historical `avg_duration` over config `estimated_time` for task ordering
|
|
120
|
+
- Per-case duration now shown in test result output
|
|
121
|
+
|
|
122
|
+
### 0.5.1
|
|
123
|
+
- Run specific test cases by name (`-t` / `test_case_filter`)
|
|
124
|
+
|
|
125
|
+
### 0.5.0
|
|
126
|
+
- Steps feature: sequential multi-command execution within a single test case, fail-fast
|
|
127
|
+
|
|
128
|
+
### 0.4.2
|
|
129
|
+
- Resource-aware scheduling: auto-detect CPU cores, semaphore-based core allocation
|
|
130
|
+
- Auto-inject `OMP_NUM_THREADS` / `MKL_NUM_THREADS` / `NPROC` to prevent solver thread runaway
|
|
131
|
+
- Per-test `timeout` support to prevent hanging
|
|
132
|
+
|
|
133
|
+
### 0.4.1
|
|
134
|
+
- Multi-thread / multi-process parallel execution, 3-5x speedup
|
|
135
|
+
|
|
136
|
+
## Contributing
|
|
137
|
+
|
|
138
|
+
Before submitting a PR, please make sure all tests pass:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
python tests\run_all.py
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## License
|
|
145
|
+
|
|
146
|
+
MIT
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# CLI Test Framework
|
|
2
|
+
|
|
3
|
+
A lightweight automated testing framework for command-line applications. Define test cases in JSON/YAML, run all validations with a single command.
|
|
4
|
+
|
|
5
|
+
Particularly suited for scientific computing — deep HDF5 support with regex table matching, data filtering, and tolerance-based comparison, making simulation result verification effortless.
|
|
6
|
+
|
|
7
|
+
## Highlights
|
|
8
|
+
|
|
9
|
+
- **Parallel Execution** — Multi-thread / multi-process, 3-5x speedup
|
|
10
|
+
- **Resource-Aware Scheduling** — Automatic CPU core management, prevents solver thread runaway
|
|
11
|
+
- **Sequence Steps** — Multi-step execution within a single test case, fail-fast
|
|
12
|
+
- **Setup Module** — Auto-configure environment variables before tests, auto-cleanup after
|
|
13
|
+
- **File Comparison** — Text / JSON / HDF5 / Binary, ready to use from command line
|
|
14
|
+
- **Filtered Execution** — Run specific test cases by name
|
|
15
|
+
|
|
16
|
+
## Quick Start
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install cli-test-framework
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### 30-Second Setup
|
|
23
|
+
|
|
24
|
+
1. Create `test_cases.json`:
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"test_cases": [
|
|
29
|
+
{
|
|
30
|
+
"name": "hello",
|
|
31
|
+
"command": "echo",
|
|
32
|
+
"args": ["Hello World"],
|
|
33
|
+
"expected": {
|
|
34
|
+
"return_code": 0,
|
|
35
|
+
"output_contains": ["Hello World"]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
2. Run:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
cli-test run test_cases.json
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Parallel Execution
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
cli-test run test_cases.json --parallel --workers 4
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Python API
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from cli_test_framework.runners import JSONRunner, ParallelJSONRunner
|
|
58
|
+
|
|
59
|
+
# Sequential
|
|
60
|
+
runner = JSONRunner(config_file="test_cases.json")
|
|
61
|
+
success = runner.run_tests()
|
|
62
|
+
|
|
63
|
+
# Parallel
|
|
64
|
+
runner = ParallelJSONRunner(config_file="test_cases.json", max_workers=4, execution_mode="thread")
|
|
65
|
+
success = runner.run_tests()
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### File Comparison
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
compare-files result1.h5 result2.h5 --h5-table-regex "output_.*" --h5-rtol 1e-5
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
📖 **Full Documentation**: [docs/user_manual.md](docs/user_manual_en.md)
|
|
75
|
+
|
|
76
|
+
## Changelog
|
|
77
|
+
|
|
78
|
+
### 0.5.2
|
|
79
|
+
|
|
80
|
+
- Runtime history tracking (`--history-dir`): persist per-case execution time in `.symtest`, enable smart scheduling & regression detection
|
|
81
|
+
- Regression warning: alert when a case runs slower than historical average × threshold (`--regression-threshold`, default 1.5)
|
|
82
|
+
- Smart scheduling: parallel runner prioritizes historical `avg_duration` over config `estimated_time` for task ordering
|
|
83
|
+
- Per-case duration now shown in test result output
|
|
84
|
+
|
|
85
|
+
### 0.5.1
|
|
86
|
+
- Run specific test cases by name (`-t` / `test_case_filter`)
|
|
87
|
+
|
|
88
|
+
### 0.5.0
|
|
89
|
+
- Steps feature: sequential multi-command execution within a single test case, fail-fast
|
|
90
|
+
|
|
91
|
+
### 0.4.2
|
|
92
|
+
- Resource-aware scheduling: auto-detect CPU cores, semaphore-based core allocation
|
|
93
|
+
- Auto-inject `OMP_NUM_THREADS` / `MKL_NUM_THREADS` / `NPROC` to prevent solver thread runaway
|
|
94
|
+
- Per-test `timeout` support to prevent hanging
|
|
95
|
+
|
|
96
|
+
### 0.4.1
|
|
97
|
+
- Multi-thread / multi-process parallel execution, 3-5x speedup
|
|
98
|
+
|
|
99
|
+
## Contributing
|
|
100
|
+
|
|
101
|
+
Before submitting a PR, please make sure all tests pass:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
python tests\run_all.py
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## License
|
|
108
|
+
|
|
109
|
+
MIT
|
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
# CLI Test Framework 设计文档
|
|
2
|
+
|
|
3
|
+
## 1. 架构总览
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
┌─────────────────────────────────────────────────┐
|
|
7
|
+
│ CLI 入口 │
|
|
8
|
+
│ cli-test run / compare-files │
|
|
9
|
+
└──────────┬──────────────────────┬───────────────┘
|
|
10
|
+
│ │
|
|
11
|
+
┌──────────▼──────────┐ ┌───────▼────────────────┐
|
|
12
|
+
│ Runner 体系 │ │ File Comparator │
|
|
13
|
+
│ ┌───────────────┐ │ │ ┌──────────────────┐ │
|
|
14
|
+
│ │ BaseRunner │ │ │ │ BaseComparator │ │
|
|
15
|
+
│ │ ├JSONRunner │ │ │ │ ├TextComparator │ │
|
|
16
|
+
│ │ ├YAMLRunner │ │ │ │ ├JsonComparator │ │
|
|
17
|
+
│ │ └Parallel──► │ │ │ │ ├H5Comparator │ │
|
|
18
|
+
│ │ └P-JSONRnr │ │ │ │ └BinaryComparator│ │
|
|
19
|
+
│ └───────────────┘ │ │ └──────────────────┘ │
|
|
20
|
+
│ │ │ ComparatorFactory │
|
|
21
|
+
└──────────┬──────────┘ └────────────────────────┘
|
|
22
|
+
│
|
|
23
|
+
┌──────────▼──────────────────────────────────────┐
|
|
24
|
+
│ Core 层 │
|
|
25
|
+
│ TestCase │ Assertions │ Setup │ PathResolver │
|
|
26
|
+
│ Execution│ Types │Manager│ ReportGenerator │
|
|
27
|
+
│ HistoryStore │
|
|
28
|
+
└─────────────────────────────────────────────────┘
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
框架分为三层:**CLI 入口层**、**Runner / Comparator 业务层**、**Core 基础层**。
|
|
32
|
+
|
|
33
|
+
## 2. 模块职责
|
|
34
|
+
|
|
35
|
+
### 2.1 目录结构
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
src/cli_test_framework/
|
|
39
|
+
├── __init__.py # 包入口,导出公共 API
|
|
40
|
+
├── cli.py # cli-test 命令入口
|
|
41
|
+
├── core/ # 核心抽象与基础组件
|
|
42
|
+
│ ├── base_runner.py # BaseRunner 抽象基类
|
|
43
|
+
│ ├── parallel_runner.py # ParallelRunner 并行基类
|
|
44
|
+
│ ├── execution.py # 单测试执行逻辑
|
|
45
|
+
│ ├── process_worker.py # 多进程 worker
|
|
46
|
+
│ ├── assertions.py # 断言引擎
|
|
47
|
+
│ ├── setup.py # Setup 插件体系
|
|
48
|
+
│ ├── test_case.py # TestCase 数据类
|
|
49
|
+
│ ├── history_store.py # .symtest 历史记录存储
|
|
50
|
+
│ └── types.py # TypedDict 类型定义
|
|
51
|
+
├── runners/ # 具体运行器
|
|
52
|
+
│ ├── json_runner.py # JSONRunner
|
|
53
|
+
│ ├── yaml_runner.py # YAMLRunner
|
|
54
|
+
│ └── parallel_json_runner.py # ParallelJSONRunner
|
|
55
|
+
├── file_comparator/ # 文件比较子系统
|
|
56
|
+
│ ├── base_comparator.py # BaseComparator 抽象基类
|
|
57
|
+
│ ├── result.py # ComparisonResult / Difference
|
|
58
|
+
│ ├── factory.py # ComparatorFactory 工厂
|
|
59
|
+
│ ├── text_comparator.py # 文本比较
|
|
60
|
+
│ ├── json_comparator.py # JSON 比较
|
|
61
|
+
│ ├── csv_comparator.py # CSV 比较
|
|
62
|
+
│ ├── xml_comparator.py # XML 比较
|
|
63
|
+
│ ├── binary_comparator.py # 二进制比较
|
|
64
|
+
│ └── h5_comparator.py # HDF5 比较
|
|
65
|
+
├── commands/ # CLI 子命令
|
|
66
|
+
│ └── compare.py # compare-files 入口
|
|
67
|
+
└── utils/ # 工具模块
|
|
68
|
+
├── path_resolver.py # 路径解析
|
|
69
|
+
└── report_generator.py # 报告生成
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 2.2 入口点
|
|
73
|
+
|
|
74
|
+
| 命令 | 映射 |
|
|
75
|
+
|---|---|
|
|
76
|
+
| `cli-test` | `cli_test_framework.cli:main` |
|
|
77
|
+
| `compare-files` | `cli_test_framework.commands.compare:main` |
|
|
78
|
+
|
|
79
|
+
## 3. 核心类设计
|
|
80
|
+
|
|
81
|
+
### 3.1 Runner 继承体系
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
BaseRunner (ABC)
|
|
85
|
+
├── JSONRunner
|
|
86
|
+
├── YAMLRunner
|
|
87
|
+
└── ParallelRunner
|
|
88
|
+
└── ParallelJSONRunner
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
#### BaseRunner
|
|
92
|
+
|
|
93
|
+
所有 Runner 的抽象基类,定义测试执行的模板流程。
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
class BaseRunner(ABC):
|
|
97
|
+
def __init__(self, config_file: str, workspace: Optional[str] = None,
|
|
98
|
+
test_case_filter: Optional[List[str]] = None,
|
|
99
|
+
history_dir: Optional[str] = None,
|
|
100
|
+
regression_threshold: float = 1.5)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**模板方法 `run_tests()`**:
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
load_test_cases() → _apply_test_case_filter() → setup_manager.setup_all()
|
|
107
|
+
→ [run_single_test(case) for case in test_cases] # 顺序执行
|
|
108
|
+
→ setup_manager.teardown_all()
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**关键属性**:
|
|
112
|
+
|
|
113
|
+
| 属性 | 类型 | 说明 |
|
|
114
|
+
|---|---|---|
|
|
115
|
+
| `workspace` | `Path` | 工作目录 |
|
|
116
|
+
| `test_cases` | `List[TestCase]` | 加载后的测试用例 |
|
|
117
|
+
| `results` | `Dict` | 运行结果 `{total_tests, passed, failed, details}` |
|
|
118
|
+
| `assertions` | `Assertions` | 断言引擎实例 |
|
|
119
|
+
| `setup_manager` | `SetupManager` | Setup 管理器 |
|
|
120
|
+
| `history_dir` | `Optional[str]` | `.symtest` 历史记录目录,`None` 时禁用 |
|
|
121
|
+
| `regression_threshold` | `float` | 回归检测阈值倍数,默认 1.5 |
|
|
122
|
+
|
|
123
|
+
**抽象方法**:
|
|
124
|
+
|
|
125
|
+
| 方法 | 职责 |
|
|
126
|
+
|---|---|
|
|
127
|
+
| `load_test_cases()` | 解析配置文件,填充 `self.test_cases` |
|
|
128
|
+
| `run_single_test(case)` | 执行单个测试,返回结果字典 |
|
|
129
|
+
|
|
130
|
+
**步骤序列执行 `_run_sequence(case)`**:
|
|
131
|
+
|
|
132
|
+
当 TestCase 含 `steps` 字段时,按顺序执行每个 step,某步失败则跳过后续(fail-fast)。结果标注失败步骤编号。
|
|
133
|
+
|
|
134
|
+
#### ParallelRunner
|
|
135
|
+
|
|
136
|
+
继承 BaseRunner,覆写 `run_tests()` 为并行版本。
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
class ParallelRunner(BaseRunner):
|
|
140
|
+
def __init__(self, config_file, workspace=None,
|
|
141
|
+
max_workers=None, execution_mode="thread",
|
|
142
|
+
test_case_filter=None,
|
|
143
|
+
history_dir=None, regression_threshold=1.5)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
- 线程模式:`ThreadPoolExecutor`,共享内存,支持资源调度
|
|
147
|
+
- 进程模式:`ProcessPoolExecutor` + `process_worker.run_test_in_process()`,进程隔离
|
|
148
|
+
- 线程安全:`_results_lock` / `_print_lock` 保护共享状态
|
|
149
|
+
- 回退方法:`run_tests_sequential()`
|
|
150
|
+
|
|
151
|
+
#### ParallelJSONRunner
|
|
152
|
+
|
|
153
|
+
在 ParallelRunner 基础上增加**资源感知调度**:
|
|
154
|
+
|
|
155
|
+
1. 加载用例后按 `estimated_time` 降序排序(LPT 策略);若启用 `history_dir`,优先使用 `.symtest` 中的历史 `avg_duration` 排序
|
|
156
|
+
2. 创建 `Semaphore(safe_capacity)` 资源池,`safe_capacity = max(1, cpu_count - 2)`
|
|
157
|
+
3. 每个 case 执行前 acquire `cpu_cores` 个信号量,执行后 release
|
|
158
|
+
4. 自动注入 `OMP_NUM_THREADS`、`MKL_NUM_THREADS`、`NPROC` 环境变量
|
|
159
|
+
|
|
160
|
+
### 3.2 TestCase 数据模型
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
@dataclass
|
|
164
|
+
class TestCaseStep:
|
|
165
|
+
command: str
|
|
166
|
+
args: Optional[List[str]] = None
|
|
167
|
+
expected: Optional[Dict] = None
|
|
168
|
+
timeout: Optional[float] = None
|
|
169
|
+
|
|
170
|
+
@dataclass
|
|
171
|
+
class TestCase:
|
|
172
|
+
name: str
|
|
173
|
+
command: Optional[str] = None
|
|
174
|
+
args: Optional[List[str]] = None
|
|
175
|
+
expected: Optional[Dict] = None
|
|
176
|
+
timeout: Optional[float] = None
|
|
177
|
+
steps: Optional[List[TestCaseStep]] = None
|
|
178
|
+
resources: Optional[Dict] = None
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
两种模式:
|
|
182
|
+
- **单命令模式**:`command` + `args` + `expected`
|
|
183
|
+
- **步骤序列模式**:`steps` 列表,每个 step 含 `command` + `args` + `expected`
|
|
184
|
+
|
|
185
|
+
### 3.3 Assertions
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
class Assertions:
|
|
189
|
+
def assert_return_code(self, actual, expected) -> bool
|
|
190
|
+
def assert_output_contains(self, output, expected_strings) -> bool
|
|
191
|
+
def assert_output_matches(self, output, expected_patterns) -> bool
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
断言逻辑:返回码精确匹配,`output_contains` 做子串匹配,`output_matches` 做正则匹配。所有断言均为可选,未指定的字段不做校验。
|
|
195
|
+
|
|
196
|
+
### 3.4 Setup 插件体系
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
class BaseSetup(ABC):
|
|
200
|
+
def __init__(self, config: Dict)
|
|
201
|
+
@abstractmethod
|
|
202
|
+
def setup(self) -> None
|
|
203
|
+
@abstractmethod
|
|
204
|
+
def teardown(self) -> None
|
|
205
|
+
|
|
206
|
+
class EnvironmentSetup(BaseSetup):
|
|
207
|
+
# setup(): 设置环境变量(保存旧值)
|
|
208
|
+
# teardown(): 恢复环境变量
|
|
209
|
+
|
|
210
|
+
class SetupManager:
|
|
211
|
+
def add_setup(self, setup: BaseSetup) -> None
|
|
212
|
+
def setup_all(self) -> None # 按添加顺序执行
|
|
213
|
+
def teardown_all(self) -> None # 逆序执行,保证即使出错也继续清理
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
**配置文件集成**:BaseRunner.load_setup_from_config() 从 JSON/YAML 的 `setup.environment_variables` 字段自动创建 EnvironmentSetup 并注册。
|
|
217
|
+
|
|
218
|
+
### 3.5 PathResolver
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
class PathResolver:
|
|
222
|
+
SYSTEM_COMMANDS = {'echo', 'python', 'node', 'java', ...}
|
|
223
|
+
|
|
224
|
+
def resolve_command(self, command: str) -> str
|
|
225
|
+
def resolve_path(self, path: str) -> str
|
|
226
|
+
def parse_command_string(self, cmd_str: str) -> Tuple[str, List[str]]
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
职责:
|
|
230
|
+
- 系统命令原样返回,非系统命令拼接到 workspace 路径
|
|
231
|
+
- 复合命令(如 `"python ./script.py"`)拆分后分别解析
|
|
232
|
+
|
|
233
|
+
### 3.6 Execution
|
|
234
|
+
|
|
235
|
+
`execute_single_test_case(case, workspace)` — 单测试的独立执行函数:
|
|
236
|
+
|
|
237
|
+
1. PathResolver 解析命令和参数
|
|
238
|
+
2. `subprocess.run()` 执行,捕获 stdout/stderr/returncode
|
|
239
|
+
3. Assertions 逐项校验
|
|
240
|
+
4. 返回结果字典
|
|
241
|
+
|
|
242
|
+
### 3.7 HistoryStore
|
|
243
|
+
|
|
244
|
+
`.symtest` 历史记录存储模块,用于持久化每个 case 的运行时间,支持智能调度和回归检测。
|
|
245
|
+
|
|
246
|
+
```python
|
|
247
|
+
# .symtest 文件格式 (JSON):
|
|
248
|
+
# {
|
|
249
|
+
# "version": 1,
|
|
250
|
+
# "cases": {
|
|
251
|
+
# "case_name": {
|
|
252
|
+
# "avg_duration": 3.5, # 累计平均耗时
|
|
253
|
+
# "last_duration": 3.2, # 最近一次耗时
|
|
254
|
+
# "run_count": 5 # 运行次数
|
|
255
|
+
# }
|
|
256
|
+
# }
|
|
257
|
+
# }
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
核心接口:
|
|
261
|
+
|
|
262
|
+
| 函数 | 说明 |
|
|
263
|
+
|---|---|
|
|
264
|
+
| `ensure_symtest(history_dir)` | 如果目录下没有 `.symtest` 就创建(含空结构) |
|
|
265
|
+
| `load_history(history_dir)` | 读取 `.symtest`,不存在则自动初始化并返回空结构 |
|
|
266
|
+
| `save_history(history_dir, history)` | 写回 `.symtest` |
|
|
267
|
+
| `update_case(history, name, duration)` | 用累计平均更新单条记录:`avg = (旧均值×次数 + 新耗时) / (次数+1)` |
|
|
268
|
+
| `check_regression(history, name, duration, threshold)` | 如果 `duration > avg * threshold`,返回 warning 消息;否则返回 `None` |
|
|
269
|
+
|
|
270
|
+
与 Runner 的集成:
|
|
271
|
+
- `BaseRunner._update_history()`:在 `run_tests()` 末尾调用,遍历 `results["details"]`,先检测回归再更新历史
|
|
272
|
+
- `ParallelJSONRunner.load_test_cases()`:排序时优先使用历史 `avg_duration`,fallback 到配置中的 `estimated_time`
|
|
273
|
+
- 所有 Runner(JSON/YAML/Parallel)均支持 `history_dir` 和 `regression_threshold` 参数
|
|
274
|
+
|
|
275
|
+
### 3.8 ReportGenerator
|
|
276
|
+
|
|
277
|
+
```python
|
|
278
|
+
class ReportGenerator:
|
|
279
|
+
@staticmethod
|
|
280
|
+
def generate(results: Dict, format: str) -> str # "text" / "json" / "html"
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
## 4. 文件比较子系统
|
|
284
|
+
|
|
285
|
+
### 4.1 类继承
|
|
286
|
+
|
|
287
|
+
```
|
|
288
|
+
BaseComparator (ABC)
|
|
289
|
+
├── TextComparator # 基于difflib行级比较
|
|
290
|
+
│ ├── JsonComparator # 按key字段对齐后比较
|
|
291
|
+
│ ├── CsvComparator # CSV结构化比较
|
|
292
|
+
│ └── XmlComparator # XML结构化比较
|
|
293
|
+
├── H5Comparator # HDF5科学数据比较
|
|
294
|
+
└── BinaryComparator # 二进制流式分块+LCS相似度
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### 4.2 BaseComparator
|
|
298
|
+
|
|
299
|
+
```python
|
|
300
|
+
class BaseComparator(ABC):
|
|
301
|
+
def __init__(self, encoding="utf-8", chunk_size=8192, verbose=False)
|
|
302
|
+
|
|
303
|
+
@abstractmethod
|
|
304
|
+
def read_content(self, file_path, start_line, end_line, start_column, end_column)
|
|
305
|
+
|
|
306
|
+
@abstractmethod
|
|
307
|
+
def compare_content(self, content1, content2) -> Tuple[bool, List[Difference]]
|
|
308
|
+
|
|
309
|
+
def compare_files(self, file1, file2, start_line, end_line,
|
|
310
|
+
start_column, end_column) -> ComparisonResult
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
模板方法:`compare_files()` → `read_content()` × 2 → `compare_content()` → `ComparisonResult`
|
|
314
|
+
|
|
315
|
+
### 4.3 ComparatorFactory
|
|
316
|
+
|
|
317
|
+
```python
|
|
318
|
+
class ComparatorFactory:
|
|
319
|
+
@staticmethod
|
|
320
|
+
def create_comparator(file_type: str, **kwargs) -> BaseComparator
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
`file_type` 取值:`"text"` / `"json"` / `"h5"` / `"binary"`
|
|
324
|
+
|
|
325
|
+
### 4.4 ComparisonResult
|
|
326
|
+
|
|
327
|
+
```python
|
|
328
|
+
class ComparisonResult:
|
|
329
|
+
file1: str
|
|
330
|
+
file2: str
|
|
331
|
+
identical: bool
|
|
332
|
+
differences: List[Difference]
|
|
333
|
+
error: Optional[str]
|
|
334
|
+
# 支持输出: str() / to_json() / to_html()
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### 4.5 H5Comparator 特殊设计
|
|
338
|
+
|
|
339
|
+
HDF5 比较器是框架中实现最复杂的比较器,针对科学计算场景:
|
|
340
|
+
|
|
341
|
+
- **表格选择**:`tables`(精确匹配)+ `table_regex`(正则匹配,逗号分隔多模式)
|
|
342
|
+
- **路径展开**:默认展开 group 下所有子数据集,可 `expand_path=False` 关闭
|
|
343
|
+
- **数值容差**:`rtol`(相对)+ `atol`(绝对),`np.allclose` 语义
|
|
344
|
+
- **数据过滤**:`data_filter` 表达式(`>1e-6`、`abs>1e-9` 等),过滤后再比较
|
|
345
|
+
- **分块读取**:大数据集分块处理,避免内存溢出
|
|
346
|
+
- **结构比较**:`structure_only=True` 只比较层级结构
|
|
347
|
+
|
|
348
|
+
## 5. 数据流
|
|
349
|
+
|
|
350
|
+
### 5.1 测试执行流
|
|
351
|
+
|
|
352
|
+
```
|
|
353
|
+
配置文件 (JSON/YAML)
|
|
354
|
+
│
|
|
355
|
+
▼
|
|
356
|
+
load_test_cases() # 解析为 List[TestCase]
|
|
357
|
+
│
|
|
358
|
+
▼
|
|
359
|
+
_apply_test_case_filter() # 按名称过滤
|
|
360
|
+
│
|
|
361
|
+
▼
|
|
362
|
+
setup_manager.setup_all() # 环境变量 + 自定义插件
|
|
363
|
+
│
|
|
364
|
+
▼
|
|
365
|
+
[如果 history_dir] load .symtest → 读取历史 avg_duration(用于调度排序)
|
|
366
|
+
│
|
|
367
|
+
▼
|
|
368
|
+
┌─────────────────────────────┐
|
|
369
|
+
│ for each TestCase: │
|
|
370
|
+
│ PathResolver 解析命令 │
|
|
371
|
+
│ subprocess.run() 执行 │
|
|
372
|
+
│ Assertions 校验结果 │
|
|
373
|
+
│ 收集到 results["details"] │
|
|
374
|
+
└─────────────────────────────┘
|
|
375
|
+
│
|
|
376
|
+
▼
|
|
377
|
+
setup_manager.teardown_all() # 逆序清理
|
|
378
|
+
│
|
|
379
|
+
▼
|
|
380
|
+
[如果 history_dir] _update_history()
|
|
381
|
+
→ check_regression() → 打印 warning(如果慢太多)
|
|
382
|
+
→ update_case() → save_history()
|
|
383
|
+
│
|
|
384
|
+
▼
|
|
385
|
+
ReportGenerator.generate() # text / json / html
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### 5.2 并行执行流
|
|
389
|
+
|
|
390
|
+
```
|
|
391
|
+
ParallelJSONRunner.run_tests()
|
|
392
|
+
│
|
|
393
|
+
▼
|
|
394
|
+
LPT 排序 (历史 avg_duration 优先,fallback 到 estimated_time 降序)
|
|
395
|
+
│
|
|
396
|
+
▼
|
|
397
|
+
┌──────────────────────────────────────┐
|
|
398
|
+
│ ThreadPoolExecutor.map(): │
|
|
399
|
+
│ Semaphore.acquire(cpu_cores) │
|
|
400
|
+
│ 注入 OMP/MKL/NPROC 环境变量 │
|
|
401
|
+
│ execute_single_test_case() │
|
|
402
|
+
│ Semaphore.release(cpu_cores) │
|
|
403
|
+
│ _update_results() (线程安全) │
|
|
404
|
+
└──────────────────────────────────────┘
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### 5.3 文件比较流
|
|
408
|
+
|
|
409
|
+
```
|
|
410
|
+
compare-files file1 file2 [options]
|
|
411
|
+
│
|
|
412
|
+
▼
|
|
413
|
+
自动检测 / 指定 file_type
|
|
414
|
+
│
|
|
415
|
+
▼
|
|
416
|
+
ComparatorFactory.create_comparator(file_type, **kwargs)
|
|
417
|
+
│
|
|
418
|
+
▼
|
|
419
|
+
comparator.compare_files(file1, file2, ...)
|
|
420
|
+
│
|
|
421
|
+
├── read_content() × 2
|
|
422
|
+
├── compare_content()
|
|
423
|
+
└── ComparisonResult
|
|
424
|
+
│
|
|
425
|
+
▼
|
|
426
|
+
format_result(result, output_format) # text / json / html
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
## 6. 扩展点
|
|
430
|
+
|
|
431
|
+
| 扩展点 | 基类 | 用途 |
|
|
432
|
+
|---|---|---|
|
|
433
|
+
| 新配置格式 | `BaseRunner` | 支持新的测试定义格式(如 XML、TOML) |
|
|
434
|
+
| 自定义 Setup | `BaseSetup` | 数据库初始化、服务启停等 |
|
|
435
|
+
| 自定义断言 | `BaseAssertion` | 特定业务校验逻辑 |
|
|
436
|
+
| 新比较器 | `BaseComparator` | 支持新的文件格式比较 |
|
|
437
|
+
| 新 Runner | `ParallelRunner` | 自定义并行策略 |
|
|
438
|
+
|
|
439
|
+
## 7. 设计决策
|
|
440
|
+
|
|
441
|
+
| 决策 | 原因 |
|
|
442
|
+
|---|---|
|
|
443
|
+
| Runner 用模板方法模式 | 统一执行流程(load → filter → setup → run → teardown),子类只需实现配置解析和单测试执行 |
|
|
444
|
+
| Setup 逆序 teardown | 类似栈语义,后初始化的依赖先清理 |
|
|
445
|
+
| 信号量管理 CPU 核心 | 比线程池 worker 数更精细,允许不同 case 声明不同核心需求 |
|
|
446
|
+
| LPT 调度策略 | 长任务先启动,减少尾延迟,提升整体吞吐;优先使用 `.symtest` 历史数据,比手写 `estimated_time` 更准确 |
|
|
447
|
+
| 累计平均更新历史 | 直觉简单,随运行次数增多单次异常自然稀释,无需手动配置衰减因子 |
|
|
448
|
+
| 回归检测在更新前执行 | 先与旧均值比较再更新,确保对比的是"历史基线"而非"已包含本次的均值" |
|
|
449
|
+
| `.symtest` 隐藏文件 | 不干扰用户目录视图,同时使用 JSON 格式便于调试时直接查看 |
|
|
450
|
+
| 环境变量注入 | 科学计算求解器常忽略 Python 级线程控制,需通过 `OMP_NUM_THREADS` 等底层变量约束 |
|
|
451
|
+
| Comparator 工厂模式 | 按文件类型创建比较器,CLI 和 Python API 共用 |
|
|
452
|
+
| subprocess 隔离执行 | 每个 test case 独立子进程,保证测试间互不影响 |
|