cli-test-framework 0.5.0__tar.gz → 0.5.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. cli_test_framework-0.5.2/PKG-INFO +146 -0
  2. cli_test_framework-0.5.2/README.md +109 -0
  3. cli_test_framework-0.5.2/docs/design.md +452 -0
  4. cli_test_framework-0.5.2/docs/design_en.md +401 -0
  5. cli_test_framework-0.5.2/docs/user_manual.md +558 -0
  6. cli_test_framework-0.5.2/docs/user_manual_en.md +475 -0
  7. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/setup.py +1 -1
  8. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/src/cli_test_framework/cli.py +25 -5
  9. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/src/cli_test_framework/commands/compare.py +9 -5
  10. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/src/cli_test_framework/core/base_runner.py +50 -4
  11. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/src/cli_test_framework/core/execution.py +9 -1
  12. cli_test_framework-0.5.2/src/cli_test_framework/core/history_store.py +85 -0
  13. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/src/cli_test_framework/core/parallel_runner.py +20 -4
  14. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/src/cli_test_framework/core/test_case.py +2 -0
  15. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/src/cli_test_framework/file_comparator/h5_comparator.py +1 -1
  16. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/src/cli_test_framework/file_comparator/json_comparator.py +0 -5
  17. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/src/cli_test_framework/file_comparator/result.py +11 -1
  18. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/src/cli_test_framework/runners/json_runner.py +5 -2
  19. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/src/cli_test_framework/runners/parallel_json_runner.py +26 -8
  20. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/src/cli_test_framework/runners/yaml_runner.py +5 -2
  21. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/src/cli_test_framework/utils/path_resolver.py +12 -5
  22. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/src/cli_test_framework/utils/report_generator.py +6 -3
  23. cli_test_framework-0.5.2/src/cli_test_framework.egg-info/PKG-INFO +146 -0
  24. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/src/cli_test_framework.egg-info/SOURCES.txt +33 -1
  25. cli_test_framework-0.5.2/tests/__pycache__/run_all.cpython-312.pyc +0 -0
  26. cli_test_framework-0.5.2/tests/e2e/__pycache__/test_user_flows.cpython-312-pytest-9.0.3.pyc +0 -0
  27. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/e2e/test_user_flows.py +15 -24
  28. cli_test_framework-0.5.2/tests/integration/__pycache__/test_history_feature.cpython-312-pytest-9.0.3.pyc +0 -0
  29. cli_test_framework-0.5.2/tests/integration/__pycache__/test_real_command_execution.cpython-312-pytest-9.0.3.pyc +0 -0
  30. cli_test_framework-0.5.2/tests/integration/__pycache__/test_sequence.cpython-312-pytest-7.4.4.pyc +0 -0
  31. cli_test_framework-0.5.2/tests/integration/__pycache__/test_sequence.cpython-312-pytest-9.0.3.pyc +0 -0
  32. cli_test_framework-0.5.2/tests/integration/file_compare/__pycache__/test_csv_compare.cpython-312-pytest-9.0.3.pyc +0 -0
  33. cli_test_framework-0.5.2/tests/integration/file_compare/__pycache__/test_xml_compare.cpython-312-pytest-9.0.3.pyc +0 -0
  34. cli_test_framework-0.5.2/tests/integration/file_compare/test_csv_compare.py +65 -0
  35. cli_test_framework-0.5.2/tests/integration/file_compare/test_xml_compare.py +72 -0
  36. cli_test_framework-0.5.2/tests/integration/parallel/__pycache__/test_parallel_runner.cpython-312-pytest-9.0.3.pyc +0 -0
  37. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/integration/parallel/test_parallel_runner.py +34 -0
  38. cli_test_framework-0.5.2/tests/integration/path_handling/__pycache__/test_spaces_in_paths.cpython-312-pytest-9.0.3.pyc +0 -0
  39. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/integration/path_handling/test_spaces_in_paths.py +31 -0
  40. cli_test_framework-0.5.2/tests/integration/test_history_feature.py +176 -0
  41. cli_test_framework-0.5.2/tests/integration/test_real_command_execution.py +124 -0
  42. cli_test_framework-0.5.2/tests/integration/test_sequence.py +298 -0
  43. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/run_all.py +6 -5
  44. cli_test_framework-0.5.2/tests/unit/__pycache__/test_cli.cpython-312-pytest-9.0.3.pyc +0 -0
  45. cli_test_framework-0.5.2/tests/unit/__pycache__/test_h5_comparator.cpython-312-pytest-9.0.3.pyc +0 -0
  46. cli_test_framework-0.5.2/tests/unit/__pycache__/test_run_all.cpython-312-pytest-9.0.3.pyc +0 -0
  47. cli_test_framework-0.5.2/tests/unit/commands/__pycache__/test_compare_command.cpython-312-pytest-9.0.3.pyc +0 -0
  48. cli_test_framework-0.5.2/tests/unit/commands/test_compare_command.py +73 -0
  49. cli_test_framework-0.5.2/tests/unit/core/__pycache__/test_history_store.cpython-312-pytest-9.0.3.pyc +0 -0
  50. cli_test_framework-0.5.2/tests/unit/core/__pycache__/test_process_worker.cpython-312-pytest-9.0.3.pyc +0 -0
  51. cli_test_framework-0.5.2/tests/unit/core/test_history_store.py +140 -0
  52. cli_test_framework-0.5.2/tests/unit/core/test_process_worker.py +106 -0
  53. cli_test_framework-0.5.2/tests/unit/runners/__pycache__/test_test_case_filter.cpython-312-pytest-7.4.4.pyc +0 -0
  54. cli_test_framework-0.5.2/tests/unit/runners/__pycache__/test_test_case_filter.cpython-312-pytest-9.0.3.pyc +0 -0
  55. cli_test_framework-0.5.2/tests/unit/runners/test_test_case_filter.py +193 -0
  56. cli_test_framework-0.5.2/tests/unit/test_cli.py +109 -0
  57. cli_test_framework-0.5.2/tests/unit/test_h5_comparator.py +696 -0
  58. cli_test_framework-0.5.2/tests/unit/test_run_all.py +18 -0
  59. cli_test_framework-0.5.2/tests/unit/utils/__pycache__/test_report_generator.cpython-312-pytest-9.0.3.pyc +0 -0
  60. cli_test_framework-0.5.2/tests/unit/utils/test_report_generator.py +59 -0
  61. cli_test_framework-0.5.0/PKG-INFO +0 -682
  62. cli_test_framework-0.5.0/README.md +0 -645
  63. cli_test_framework-0.5.0/docs/user_manual.md +0 -534
  64. cli_test_framework-0.5.0/src/cli_test_framework.egg-info/PKG-INFO +0 -682
  65. cli_test_framework-0.5.0/tests/__pycache__/run_all.cpython-312.pyc +0 -0
  66. cli_test_framework-0.5.0/tests/e2e/__pycache__/test_user_flows.cpython-312-pytest-9.0.3.pyc +0 -0
  67. cli_test_framework-0.5.0/tests/integration/parallel/__pycache__/test_parallel_runner.cpython-312-pytest-9.0.3.pyc +0 -0
  68. cli_test_framework-0.5.0/tests/integration/path_handling/__pycache__/test_spaces_in_paths.cpython-312-pytest-9.0.3.pyc +0 -0
  69. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/MANIFEST.in +0 -0
  70. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/pyproject.toml +0 -0
  71. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/setup.cfg +0 -0
  72. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/src/cli_test_framework/__init__.py +0 -0
  73. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/src/cli_test_framework/commands/__init__.py +0 -0
  74. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/src/cli_test_framework/core/__init__.py +0 -0
  75. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/src/cli_test_framework/core/assertions.py +0 -0
  76. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/src/cli_test_framework/core/process_worker.py +0 -0
  77. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/src/cli_test_framework/core/setup.py +0 -0
  78. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/src/cli_test_framework/core/types.py +0 -0
  79. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/src/cli_test_framework/file_comparator/__init__.py +0 -0
  80. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/src/cli_test_framework/file_comparator/base_comparator.py +0 -0
  81. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/src/cli_test_framework/file_comparator/binary_comparator.py +0 -0
  82. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/src/cli_test_framework/file_comparator/csv_comparator.py +0 -0
  83. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/src/cli_test_framework/file_comparator/factory.py +0 -0
  84. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/src/cli_test_framework/file_comparator/text_comparator.py +0 -0
  85. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/src/cli_test_framework/file_comparator/xml_comparator.py +0 -0
  86. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/src/cli_test_framework/runners/__init__.py +0 -0
  87. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/src/cli_test_framework/utils/__init__.py +0 -0
  88. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/src/cli_test_framework.egg-info/dependency_links.txt +0 -0
  89. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/src/cli_test_framework.egg-info/entry_points.txt +0 -0
  90. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/src/cli_test_framework.egg-info/requires.txt +0 -0
  91. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/src/cli_test_framework.egg-info/top_level.txt +0 -0
  92. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/README.md +0 -0
  93. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/__init__.py +0 -0
  94. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/__pycache__/__init__.cpython-312.pyc +0 -0
  95. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/__pycache__/__init__.cpython-39.pyc +0 -0
  96. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/__pycache__/conftest.cpython-312-pytest-7.4.4.pyc +0 -0
  97. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/__pycache__/conftest.cpython-312-pytest-9.0.3.pyc +0 -0
  98. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/__pycache__/conftest.cpython-39-pytest-8.3.4.pyc +0 -0
  99. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/__pycache__/run_all.cpython-39.pyc +0 -0
  100. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/__pycache__/test_parallel_runner.cpython-312-pytest-7.4.4.pyc +0 -0
  101. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/__pycache__/test_setup_module.cpython-312-pytest-7.4.4.pyc +0 -0
  102. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/conftest.py +0 -0
  103. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/demos/h5_filter_demo.py +0 -0
  104. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/demos/manual_report_example.py +0 -0
  105. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/demos/perf_parallel.py +0 -0
  106. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/e2e/__init__.py +0 -0
  107. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/e2e/__pycache__/__init__.cpython-312.pyc +0 -0
  108. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/e2e/__pycache__/__init__.cpython-39.pyc +0 -0
  109. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/e2e/__pycache__/test_user_flows.cpython-312-pytest-7.4.4.pyc +0 -0
  110. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/e2e/__pycache__/test_user_flows.cpython-39-pytest-8.3.4.pyc +0 -0
  111. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/fixtures/test_cases.json +0 -0
  112. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/fixtures/test_cases.yaml +0 -0
  113. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/fixtures/test_cases1.json +0 -0
  114. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/fixtures/test_with_setup.json +0 -0
  115. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/fixtures/test_with_setup.yaml +0 -0
  116. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/integration/file_compare/__pycache__/test_binary_compare.cpython-312-pytest-7.4.4.pyc +0 -0
  117. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/integration/file_compare/__pycache__/test_binary_compare.cpython-312-pytest-9.0.3.pyc +0 -0
  118. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/integration/file_compare/__pycache__/test_binary_compare.cpython-39-pytest-8.3.4.pyc +0 -0
  119. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/integration/file_compare/__pycache__/test_h5_compare.cpython-312-pytest-7.4.4.pyc +0 -0
  120. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/integration/file_compare/__pycache__/test_h5_compare.cpython-312-pytest-9.0.3.pyc +0 -0
  121. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/integration/file_compare/__pycache__/test_h5_compare.cpython-39-pytest-8.3.4.pyc +0 -0
  122. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/integration/file_compare/__pycache__/test_json_compare.cpython-312-pytest-7.4.4.pyc +0 -0
  123. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/integration/file_compare/__pycache__/test_json_compare.cpython-312-pytest-9.0.3.pyc +0 -0
  124. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/integration/file_compare/__pycache__/test_json_compare.cpython-39-pytest-8.3.4.pyc +0 -0
  125. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/integration/file_compare/__pycache__/test_text_compare.cpython-312-pytest-7.4.4.pyc +0 -0
  126. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/integration/file_compare/__pycache__/test_text_compare.cpython-312-pytest-9.0.3.pyc +0 -0
  127. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/integration/file_compare/__pycache__/test_text_compare.cpython-39-pytest-8.3.4.pyc +0 -0
  128. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/integration/file_compare/test_binary_compare.py +0 -0
  129. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/integration/file_compare/test_h5_compare.py +0 -0
  130. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/integration/file_compare/test_json_compare.py +0 -0
  131. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/integration/file_compare/test_text_compare.py +0 -0
  132. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/integration/parallel/__pycache__/test_parallel_runner.cpython-312-pytest-7.4.4.pyc +0 -0
  133. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/integration/parallel/__pycache__/test_parallel_runner.cpython-39-pytest-8.3.4.pyc +0 -0
  134. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/integration/path_handling/__pycache__/test_spaces_in_paths.cpython-312-pytest-7.4.4.pyc +0 -0
  135. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/integration/path_handling/__pycache__/test_spaces_in_paths.cpython-39-pytest-8.3.4.pyc +0 -0
  136. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/test_report.txt +0 -0
  137. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/unit/core/__pycache__/test_setup.cpython-312-pytest-7.4.4.pyc +0 -0
  138. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/unit/core/__pycache__/test_setup.cpython-312-pytest-9.0.3.pyc +0 -0
  139. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/unit/core/__pycache__/test_setup.cpython-39-pytest-8.3.4.pyc +0 -0
  140. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/unit/core/test_setup.py +0 -0
  141. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/unit/runners/__pycache__/test_json_yaml_runner.cpython-312-pytest-7.4.4.pyc +0 -0
  142. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/unit/runners/__pycache__/test_json_yaml_runner.cpython-312-pytest-9.0.3.pyc +0 -0
  143. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/unit/runners/__pycache__/test_json_yaml_runner.cpython-39-pytest-8.3.4.pyc +0 -0
  144. {cli_test_framework-0.5.0 → cli_test_framework-0.5.2}/tests/unit/runners/test_json_yaml_runner.py +0 -0
@@ -0,0 +1,146 @@
1
+ Metadata-Version: 2.4
2
+ Name: cli-test-framework
3
+ Version: 0.5.2
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 独立子进程,保证测试间互不影响 |