symtest-cli 1.4.1__tar.gz → 1.5.0__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 (405) hide show
  1. {symtest_cli-1.4.1/src/symtest_cli.egg-info → symtest_cli-1.5.0}/PKG-INFO +1 -1
  2. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/docs/design.md +43 -10
  3. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/docs/user_manual.md +164 -21
  4. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/docs/user_manual_en.md +176 -22
  5. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/__init__.py +1 -1
  6. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/commands/compare.py +27 -15
  7. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/config/config_schema.py +42 -8
  8. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/core/validation/assertions.py +50 -2
  9. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/core/validation/validator.py +48 -9
  10. symtest_cli-1.5.0/src/symtest/file_comparator/__init__.py +44 -0
  11. symtest_cli-1.5.0/src/symtest/file_comparator/base_comparator.py +128 -0
  12. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/file_comparator/binary_comparator.py +6 -6
  13. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/file_comparator/csv_comparator.py +3 -4
  14. symtest_cli-1.5.0/src/symtest/file_comparator/extractor_comparator.py +199 -0
  15. symtest_cli-1.5.0/src/symtest/file_comparator/factory.py +341 -0
  16. symtest_cli-1.4.1/src/symtest/file_comparator/base_comparator.py → symtest_cli-1.5.0/src/symtest/file_comparator/file_comparator_base.py +57 -30
  17. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/file_comparator/h5_comparator.py +10 -6
  18. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/file_comparator/json_comparator.py +3 -4
  19. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/file_comparator/result.py +66 -0
  20. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/file_comparator/script_comparator.py +22 -41
  21. symtest_cli-1.5.0/src/symtest/file_comparator/script_extract_comparator.py +160 -0
  22. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/file_comparator/text_comparator.py +2 -2
  23. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/utils/report_generator.py +80 -17
  24. {symtest_cli-1.4.1 → symtest_cli-1.5.0/src/symtest_cli.egg-info}/PKG-INFO +1 -1
  25. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest_cli.egg-info/SOURCES.txt +12 -0
  26. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/file_compare/__pycache__/test_csv_compare.cpython-312-pytest-7.4.4.pyc +0 -0
  27. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/file_compare/__pycache__/test_csv_compare.cpython-312-pytest-9.0.3.pyc +0 -0
  28. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/file_compare/__pycache__/test_text_compare.cpython-312-pytest-7.4.4.pyc +0 -0
  29. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/file_compare/__pycache__/test_text_compare.cpython-312-pytest-9.0.3.pyc +0 -0
  30. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/file_compare/test_csv_compare.py +2 -2
  31. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/file_compare/test_text_compare.py +8 -1
  32. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_assertions.cpython-312-pytest-7.4.4.pyc +0 -0
  33. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_assertions.cpython-312-pytest-9.0.3.pyc +0 -0
  34. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/test_assertions.py +20 -5
  35. symtest_cli-1.5.0/tests/unit/file_comparator/__pycache__/test_extractor_channels.cpython-312-pytest-7.4.4.pyc +0 -0
  36. symtest_cli-1.5.0/tests/unit/file_comparator/__pycache__/test_extractor_channels.cpython-312-pytest-9.0.3.pyc +0 -0
  37. symtest_cli-1.5.0/tests/unit/file_comparator/__pycache__/test_plugin_lifecycle.cpython-312-pytest-7.4.4.pyc +0 -0
  38. symtest_cli-1.5.0/tests/unit/file_comparator/__pycache__/test_plugin_lifecycle.cpython-312-pytest-9.0.3.pyc +0 -0
  39. symtest_cli-1.5.0/tests/unit/file_comparator/__pycache__/test_script_comparator.cpython-312-pytest-7.4.4.pyc +0 -0
  40. symtest_cli-1.5.0/tests/unit/file_comparator/__pycache__/test_script_comparator.cpython-312-pytest-9.0.3.pyc +0 -0
  41. symtest_cli-1.5.0/tests/unit/file_comparator/__pycache__/test_script_extract.cpython-312-pytest-7.4.4.pyc +0 -0
  42. symtest_cli-1.5.0/tests/unit/file_comparator/__pycache__/test_script_extract.cpython-312-pytest-9.0.3.pyc +0 -0
  43. symtest_cli-1.5.0/tests/unit/file_comparator/__pycache__/test_workspace_plugins.cpython-312-pytest-7.4.4.pyc +0 -0
  44. symtest_cli-1.5.0/tests/unit/file_comparator/__pycache__/test_workspace_plugins.cpython-312-pytest-9.0.3.pyc +0 -0
  45. symtest_cli-1.5.0/tests/unit/file_comparator/test_extractor_channels.py +253 -0
  46. symtest_cli-1.5.0/tests/unit/file_comparator/test_plugin_lifecycle.py +390 -0
  47. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/file_comparator/test_script_comparator.py +10 -9
  48. symtest_cli-1.5.0/tests/unit/file_comparator/test_script_extract.py +187 -0
  49. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/file_comparator/test_workspace_plugins.py +29 -48
  50. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/utils/__pycache__/test_report_generator.cpython-312-pytest-7.4.4.pyc +0 -0
  51. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/utils/__pycache__/test_report_generator.cpython-312-pytest-9.0.3.pyc +0 -0
  52. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/utils/test_report_generator.py +177 -0
  53. symtest_cli-1.4.1/src/symtest/file_comparator/__init__.py +0 -10
  54. symtest_cli-1.4.1/src/symtest/file_comparator/factory.py +0 -191
  55. symtest_cli-1.4.1/tests/unit/file_comparator/__pycache__/test_script_comparator.cpython-312-pytest-7.4.4.pyc +0 -0
  56. symtest_cli-1.4.1/tests/unit/file_comparator/__pycache__/test_script_comparator.cpython-312-pytest-9.0.3.pyc +0 -0
  57. symtest_cli-1.4.1/tests/unit/file_comparator/__pycache__/test_workspace_plugins.cpython-312-pytest-7.4.4.pyc +0 -0
  58. symtest_cli-1.4.1/tests/unit/file_comparator/__pycache__/test_workspace_plugins.cpython-312-pytest-9.0.3.pyc +0 -0
  59. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/LICENSE +0 -0
  60. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/MANIFEST.in +0 -0
  61. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/README.md +0 -0
  62. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/docs/design_en.md +0 -0
  63. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/docs/tui_and_config_split_design.md +0 -0
  64. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/pyproject.toml +0 -0
  65. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/setup.cfg +0 -0
  66. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/setup.py +0 -0
  67. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/cli.py +0 -0
  68. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/commands/__init__.py +0 -0
  69. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/commands/migrate.py +0 -0
  70. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/config/__init__.py +0 -0
  71. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/config/config_io.py +0 -0
  72. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/config/import_expander.py +0 -0
  73. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/config/inheritance_expander.py +0 -0
  74. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/config/migrate.py +0 -0
  75. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/config/normalize.py +0 -0
  76. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/core/__init__.py +0 -0
  77. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/core/base_runner.py +0 -0
  78. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/core/config_loader.py +0 -0
  79. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/core/execution/__init__.py +0 -0
  80. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/core/execution/executor.py +0 -0
  81. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/core/execution/result.py +0 -0
  82. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/core/history_store.py +0 -0
  83. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/core/last_run_store.py +0 -0
  84. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/core/orchestration/__init__.py +0 -0
  85. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/core/orchestration/accept.py +0 -0
  86. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/core/orchestration/sequence.py +0 -0
  87. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/core/orchestration/single.py +0 -0
  88. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/core/parallel_runner.py +0 -0
  89. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/core/process_worker.py +0 -0
  90. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/core/result.py +0 -0
  91. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/core/sequence_state.py +0 -0
  92. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/core/setup.py +0 -0
  93. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/core/test_case.py +0 -0
  94. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/core/types.py +0 -0
  95. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/core/validation/__init__.py +0 -0
  96. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/core/validation/result.py +0 -0
  97. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/file_comparator/numeric_compare.py +0 -0
  98. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/file_comparator/xml_comparator.py +0 -0
  99. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/logging_config.py +0 -0
  100. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/reporting/__init__.py +0 -0
  101. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/reporting/diagnosis.py +0 -0
  102. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/runners/__init__.py +0 -0
  103. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/runners/config_runner.py +0 -0
  104. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/runners/json_runner.py +0 -0
  105. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/runners/parallel_config_runner.py +0 -0
  106. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/runners/parallel_json_runner.py +0 -0
  107. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/runners/parallel_yaml_runner.py +0 -0
  108. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/runners/yaml_runner.py +0 -0
  109. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/tui/__init__.py +0 -0
  110. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/tui/app.py +0 -0
  111. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/tui/controllers/__init__.py +0 -0
  112. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/tui/controllers/case_controller.py +0 -0
  113. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/tui/screens/__init__.py +0 -0
  114. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/tui/screens/case_editor.py +0 -0
  115. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/tui/screens/case_list.py +0 -0
  116. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/tui/widgets/__init__.py +0 -0
  117. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/tui/widgets/case_table.py +0 -0
  118. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/tui/widgets/expected_editor.py +0 -0
  119. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/tui/widgets/search_bar.py +0 -0
  120. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/tui/widgets/steps_editor.py +0 -0
  121. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/utils/__init__.py +0 -0
  122. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/utils/junit_xml_writer.py +0 -0
  123. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest/utils/path_resolver.py +0 -0
  124. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest_cli.egg-info/dependency_links.txt +0 -0
  125. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest_cli.egg-info/entry_points.txt +0 -0
  126. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest_cli.egg-info/requires.txt +0 -0
  127. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/src/symtest_cli.egg-info/top_level.txt +0 -0
  128. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/README.md +0 -0
  129. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/__init__.py +0 -0
  130. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/__pycache__/__init__.cpython-312.pyc +0 -0
  131. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/__pycache__/__init__.cpython-313.pyc +0 -0
  132. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/__pycache__/__init__.cpython-39.pyc +0 -0
  133. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/__pycache__/conftest.cpython-312-pytest-7.4.4.pyc +0 -0
  134. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/__pycache__/conftest.cpython-312-pytest-9.0.3.pyc +0 -0
  135. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/__pycache__/conftest.cpython-313-pytest-9.0.2.pyc +0 -0
  136. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/__pycache__/conftest.cpython-39-pytest-8.3.4.pyc +0 -0
  137. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/__pycache__/run_all.cpython-312.pyc +0 -0
  138. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/__pycache__/run_all.cpython-313.pyc +0 -0
  139. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/__pycache__/run_all.cpython-39.pyc +0 -0
  140. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/__pycache__/test_setup_module.cpython-312-pytest-7.4.4.pyc +0 -0
  141. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/conftest.py +0 -0
  142. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/demos/h5_filter_demo.py +0 -0
  143. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/demos/manual_report_example.py +0 -0
  144. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/demos/perf_parallel.py +0 -0
  145. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/e2e/__init__.py +0 -0
  146. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/e2e/__pycache__/__init__.cpython-312.pyc +0 -0
  147. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/e2e/__pycache__/__init__.cpython-313.pyc +0 -0
  148. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/e2e/__pycache__/__init__.cpython-39.pyc +0 -0
  149. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/e2e/__pycache__/test_user_flows.cpython-312-pytest-7.4.4.pyc +0 -0
  150. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/e2e/__pycache__/test_user_flows.cpython-312-pytest-9.0.3.pyc +0 -0
  151. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/e2e/__pycache__/test_user_flows.cpython-313-pytest-9.0.2.pyc +0 -0
  152. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/e2e/__pycache__/test_user_flows.cpython-39-pytest-8.3.4.pyc +0 -0
  153. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/e2e/test_user_flows.py +0 -0
  154. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/fixtures/circular_config_a.json +0 -0
  155. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/fixtures/circular_config_b.json +0 -0
  156. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/fixtures/hourglass_stdout_mismatch.txt +0 -0
  157. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/fixtures/hourglass_stdout_sample.txt +0 -0
  158. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/fixtures/inheritance_base.json +0 -0
  159. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/fixtures/inheritance_chain.json +0 -0
  160. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/fixtures/inheritance_circular.json +0 -0
  161. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/fixtures/main_config.json +0 -0
  162. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/fixtures/migration/v1/circular_config_a.json +0 -0
  163. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/fixtures/migration/v1/circular_config_b.json +0 -0
  164. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/fixtures/migration/v1/inheritance_base.json +0 -0
  165. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/fixtures/migration/v1/inheritance_chain.json +0 -0
  166. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/fixtures/migration/v1/inheritance_circular.json +0 -0
  167. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/fixtures/migration/v1/main_config.json +0 -0
  168. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/fixtures/migration/v1/missing_fields.json +0 -0
  169. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/fixtures/migration/v1/setup_main.json +0 -0
  170. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/fixtures/migration/v1/setup_sub.json +0 -0
  171. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/fixtures/migration/v1/skill_test_cases_golden_file.json +0 -0
  172. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/fixtures/migration/v1/skill_test_cases_simple.json +0 -0
  173. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/fixtures/migration/v1/skill_test_cases_simple.yaml +0 -0
  174. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/fixtures/migration/v1/skill_test_cases_steps.json +0 -0
  175. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/fixtures/migration/v1/sub_json_tests.yaml +0 -0
  176. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/fixtures/migration/v1/sub_text_tests.json +0 -0
  177. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/fixtures/migration/v1/test_cases.json +0 -0
  178. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/fixtures/migration/v1/test_cases.yaml +0 -0
  179. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/fixtures/migration/v1/test_cases1.json +0 -0
  180. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/fixtures/migration/v1/test_with_setup.json +0 -0
  181. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/fixtures/migration/v1/test_with_setup.yaml +0 -0
  182. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/fixtures/missing_fields.json +0 -0
  183. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/fixtures/mock_fail_script.py +0 -0
  184. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/fixtures/mock_pass_script.py +0 -0
  185. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/fixtures/setup_main.json +0 -0
  186. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/fixtures/setup_sub.json +0 -0
  187. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/fixtures/sub_json_tests.yaml +0 -0
  188. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/fixtures/sub_text_tests.json +0 -0
  189. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/fixtures/test_cases.json +0 -0
  190. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/fixtures/test_cases.yaml +0 -0
  191. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/fixtures/test_cases1.json +0 -0
  192. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/fixtures/test_with_setup.json +0 -0
  193. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/fixtures/test_with_setup.yaml +0 -0
  194. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/__pycache__/test_history_feature.cpython-312-pytest-7.4.4.pyc +0 -0
  195. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/__pycache__/test_history_feature.cpython-312-pytest-9.0.3.pyc +0 -0
  196. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/__pycache__/test_history_feature.cpython-313-pytest-9.0.2.pyc +0 -0
  197. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/__pycache__/test_new_cli_features.cpython-312-pytest-7.4.4.pyc +0 -0
  198. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/__pycache__/test_new_cli_features.cpython-312-pytest-9.0.3.pyc +0 -0
  199. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/__pycache__/test_new_cli_features.cpython-313-pytest-9.0.2.pyc +0 -0
  200. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/__pycache__/test_real_command_execution.cpython-312-pytest-7.4.4.pyc +0 -0
  201. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/__pycache__/test_real_command_execution.cpython-312-pytest-9.0.3.pyc +0 -0
  202. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/__pycache__/test_real_command_execution.cpython-313-pytest-9.0.2.pyc +0 -0
  203. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/__pycache__/test_sequence.cpython-312-pytest-7.4.4.pyc +0 -0
  204. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/__pycache__/test_sequence.cpython-312-pytest-9.0.3.pyc +0 -0
  205. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/__pycache__/test_sequence.cpython-313-pytest-9.0.2.pyc +0 -0
  206. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/file_compare/__pycache__/test_binary_compare.cpython-312-pytest-7.4.4.pyc +0 -0
  207. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/file_compare/__pycache__/test_binary_compare.cpython-312-pytest-9.0.3.pyc +0 -0
  208. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/file_compare/__pycache__/test_binary_compare.cpython-313-pytest-9.0.2.pyc +0 -0
  209. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/file_compare/__pycache__/test_binary_compare.cpython-39-pytest-8.3.4.pyc +0 -0
  210. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/file_compare/__pycache__/test_csv_compare.cpython-313-pytest-9.0.2.pyc +0 -0
  211. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/file_compare/__pycache__/test_h5_compare.cpython-312-pytest-7.4.4.pyc +0 -0
  212. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/file_compare/__pycache__/test_h5_compare.cpython-312-pytest-9.0.3.pyc +0 -0
  213. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/file_compare/__pycache__/test_h5_compare.cpython-313-pytest-9.0.2.pyc +0 -0
  214. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/file_compare/__pycache__/test_h5_compare.cpython-39-pytest-8.3.4.pyc +0 -0
  215. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/file_compare/__pycache__/test_json_compare.cpython-312-pytest-7.4.4.pyc +0 -0
  216. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/file_compare/__pycache__/test_json_compare.cpython-312-pytest-9.0.3.pyc +0 -0
  217. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/file_compare/__pycache__/test_json_compare.cpython-313-pytest-9.0.2.pyc +0 -0
  218. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/file_compare/__pycache__/test_json_compare.cpython-39-pytest-8.3.4.pyc +0 -0
  219. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/file_compare/__pycache__/test_text_compare.cpython-313-pytest-9.0.2.pyc +0 -0
  220. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/file_compare/__pycache__/test_text_compare.cpython-39-pytest-8.3.4.pyc +0 -0
  221. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/file_compare/__pycache__/test_xml_compare.cpython-312-pytest-7.4.4.pyc +0 -0
  222. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/file_compare/__pycache__/test_xml_compare.cpython-312-pytest-9.0.3.pyc +0 -0
  223. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/file_compare/__pycache__/test_xml_compare.cpython-313-pytest-9.0.2.pyc +0 -0
  224. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/file_compare/test_binary_compare.py +0 -0
  225. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/file_compare/test_h5_compare.py +0 -0
  226. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/file_compare/test_json_compare.py +0 -0
  227. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/file_compare/test_xml_compare.py +0 -0
  228. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/parallel/__pycache__/test_depends_on.cpython-312-pytest-7.4.4.pyc +0 -0
  229. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/parallel/__pycache__/test_depends_on.cpython-312-pytest-9.0.3.pyc +0 -0
  230. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/parallel/__pycache__/test_parallel_runner.cpython-312-pytest-7.4.4.pyc +0 -0
  231. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/parallel/__pycache__/test_parallel_runner.cpython-312-pytest-9.0.3.pyc +0 -0
  232. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/parallel/__pycache__/test_parallel_runner.cpython-313-pytest-9.0.2.pyc +0 -0
  233. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/parallel/__pycache__/test_parallel_runner.cpython-39-pytest-8.3.4.pyc +0 -0
  234. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/parallel/test_depends_on.py +0 -0
  235. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/parallel/test_parallel_runner.py +0 -0
  236. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/path_handling/__pycache__/test_spaces_in_paths.cpython-312-pytest-7.4.4.pyc +0 -0
  237. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/path_handling/__pycache__/test_spaces_in_paths.cpython-312-pytest-9.0.3.pyc +0 -0
  238. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/path_handling/__pycache__/test_spaces_in_paths.cpython-313-pytest-9.0.2.pyc +0 -0
  239. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/path_handling/__pycache__/test_spaces_in_paths.cpython-39-pytest-8.3.4.pyc +0 -0
  240. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/path_handling/test_spaces_in_paths.py +0 -0
  241. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/test_history_feature.py +0 -0
  242. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/test_new_cli_features.py +0 -0
  243. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/test_real_command_execution.py +0 -0
  244. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/integration/test_sequence.py +0 -0
  245. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/run_all.py +0 -0
  246. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/__pycache__/test_binary_comparator.cpython-312-pytest-7.4.4.pyc +0 -0
  247. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/__pycache__/test_binary_comparator.cpython-312-pytest-9.0.3.pyc +0 -0
  248. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/__pycache__/test_binary_comparator.cpython-313-pytest-9.0.2.pyc +0 -0
  249. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/__pycache__/test_cli.cpython-312-pytest-7.4.4.pyc +0 -0
  250. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/__pycache__/test_cli.cpython-312-pytest-9.0.3.pyc +0 -0
  251. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/__pycache__/test_cli.cpython-313-pytest-9.0.2.pyc +0 -0
  252. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/__pycache__/test_config_io.cpython-312-pytest-7.4.4.pyc +0 -0
  253. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/__pycache__/test_config_io.cpython-312-pytest-9.0.3.pyc +0 -0
  254. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/__pycache__/test_config_io.cpython-313-pytest-9.0.2.pyc +0 -0
  255. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/__pycache__/test_h5_comparator.cpython-312-pytest-7.4.4.pyc +0 -0
  256. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/__pycache__/test_h5_comparator.cpython-312-pytest-9.0.3.pyc +0 -0
  257. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/__pycache__/test_h5_comparator.cpython-313-pytest-9.0.2.pyc +0 -0
  258. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/__pycache__/test_import_expander.cpython-312-pytest-7.4.4.pyc +0 -0
  259. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/__pycache__/test_import_expander.cpython-312-pytest-9.0.3.pyc +0 -0
  260. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/__pycache__/test_import_expander.cpython-313-pytest-9.0.2.pyc +0 -0
  261. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/__pycache__/test_inheritance_expander.cpython-312-pytest-7.4.4.pyc +0 -0
  262. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/__pycache__/test_inheritance_expander.cpython-312-pytest-9.0.3.pyc +0 -0
  263. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/__pycache__/test_inheritance_expander.cpython-313-pytest-9.0.2.pyc +0 -0
  264. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/__pycache__/test_migrate.cpython-312-pytest-7.4.4.pyc +0 -0
  265. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/__pycache__/test_migrate.cpython-312-pytest-9.0.3.pyc +0 -0
  266. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/__pycache__/test_result.cpython-312-pytest-7.4.4.pyc +0 -0
  267. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/__pycache__/test_result.cpython-312-pytest-9.0.3.pyc +0 -0
  268. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/__pycache__/test_result.cpython-313-pytest-9.0.2.pyc +0 -0
  269. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/__pycache__/test_run_all.cpython-312-pytest-7.4.4.pyc +0 -0
  270. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/__pycache__/test_run_all.cpython-312-pytest-9.0.3.pyc +0 -0
  271. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/__pycache__/test_run_all.cpython-313-pytest-9.0.2.pyc +0 -0
  272. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/commands/__pycache__/test_compare_command.cpython-312-pytest-7.4.4.pyc +0 -0
  273. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/commands/__pycache__/test_compare_command.cpython-312-pytest-9.0.3.pyc +0 -0
  274. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/commands/__pycache__/test_compare_command.cpython-313-pytest-9.0.2.pyc +0 -0
  275. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/commands/test_compare_command.py +0 -0
  276. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_architecture_guard.cpython-312-pytest-7.4.4.pyc +0 -0
  277. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_architecture_guard.cpython-312-pytest-9.0.3.pyc +0 -0
  278. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_assertions.cpython-313-pytest-9.0.2.pyc +0 -0
  279. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_case_env.cpython-312-pytest-7.4.4.pyc +0 -0
  280. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_case_env.cpython-312-pytest-9.0.3.pyc +0 -0
  281. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_config_loader.cpython-312-pytest-7.4.4.pyc +0 -0
  282. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_config_loader.cpython-312-pytest-9.0.3.pyc +0 -0
  283. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_config_loader.cpython-313-pytest-9.0.2.pyc +0 -0
  284. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_config_loader_new_features.cpython-312-pytest-7.4.4.pyc +0 -0
  285. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_config_loader_new_features.cpython-312-pytest-9.0.3.pyc +0 -0
  286. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_config_loader_new_features.cpython-313-pytest-9.0.2.pyc +0 -0
  287. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_execution.cpython-312-pytest-7.4.4.pyc +0 -0
  288. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_execution.cpython-312-pytest-9.0.3.pyc +0 -0
  289. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_execution.cpython-312.pyc +0 -0
  290. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_execution.cpython-313-pytest-9.0.2.pyc +0 -0
  291. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_execution_new_features.cpython-312-pytest-7.4.4.pyc +0 -0
  292. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_execution_new_features.cpython-312-pytest-9.0.3.pyc +0 -0
  293. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_execution_new_features.cpython-313-pytest-9.0.2.pyc +0 -0
  294. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_history_store.cpython-312-pytest-7.4.4.pyc +0 -0
  295. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_history_store.cpython-312-pytest-9.0.3.pyc +0 -0
  296. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_history_store.cpython-313-pytest-9.0.2.pyc +0 -0
  297. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_last_run_store.cpython-312-pytest-7.4.4.pyc +0 -0
  298. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_last_run_store.cpython-312-pytest-9.0.3.pyc +0 -0
  299. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_last_run_store.cpython-313-pytest-9.0.2.pyc +0 -0
  300. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_model_v2.cpython-312-pytest-7.4.4.pyc +0 -0
  301. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_model_v2.cpython-312-pytest-9.0.3.pyc +0 -0
  302. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_orchestration_accept.cpython-312-pytest-7.4.4.pyc +0 -0
  303. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_orchestration_accept.cpython-312-pytest-9.0.3.pyc +0 -0
  304. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_process_worker.cpython-312-pytest-7.4.4.pyc +0 -0
  305. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_process_worker.cpython-312-pytest-9.0.3.pyc +0 -0
  306. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_process_worker.cpython-313-pytest-9.0.2.pyc +0 -0
  307. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_sequence_state.cpython-312-pytest-7.4.4.pyc +0 -0
  308. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_sequence_state.cpython-312-pytest-9.0.3.pyc +0 -0
  309. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_setup.cpython-312-pytest-7.4.4.pyc +0 -0
  310. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_setup.cpython-312-pytest-9.0.3.pyc +0 -0
  311. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_setup.cpython-313-pytest-9.0.2.pyc +0 -0
  312. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_setup.cpython-39-pytest-8.3.4.pyc +0 -0
  313. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_validation_error.cpython-312-pytest-7.4.4.pyc +0 -0
  314. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_validation_error.cpython-312-pytest-9.0.3.pyc +0 -0
  315. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_validation_error.cpython-313-pytest-9.0.2.pyc +0 -0
  316. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_xfail.cpython-312-pytest-7.4.4.pyc +0 -0
  317. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_xfail.cpython-312-pytest-9.0.3.pyc +0 -0
  318. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/__pycache__/test_xfail.cpython-313-pytest-9.0.2.pyc +0 -0
  319. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/test_architecture_guard.py +0 -0
  320. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/test_case_env.py +0 -0
  321. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/test_config_loader.py +0 -0
  322. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/test_config_loader_new_features.py +0 -0
  323. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/test_execution.py +0 -0
  324. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/test_execution_new_features.py +0 -0
  325. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/test_history_store.py +0 -0
  326. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/test_last_run_store.py +0 -0
  327. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/test_model_v2.py +0 -0
  328. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/test_orchestration_accept.py +0 -0
  329. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/test_process_worker.py +0 -0
  330. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/test_sequence_state.py +0 -0
  331. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/test_setup.py +0 -0
  332. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/test_validation_error.py +0 -0
  333. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/core/test_xfail.py +0 -0
  334. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/file_comparator/__pycache__/__init__.cpython-312.pyc +0 -0
  335. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/file_comparator/__pycache__/test_error_analysis.cpython-312-pytest-7.4.4.pyc +0 -0
  336. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/file_comparator/__pycache__/test_error_analysis.cpython-312-pytest-9.0.3.pyc +0 -0
  337. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/file_comparator/__pycache__/test_error_analysis.cpython-313-pytest-9.0.2.pyc +0 -0
  338. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/file_comparator/__pycache__/test_hourglass_tangent_comparator.cpython-312-pytest-7.4.4.pyc +0 -0
  339. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/file_comparator/__pycache__/test_hourglass_tangent_comparator.cpython-312-pytest-9.0.3.pyc +0 -0
  340. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/file_comparator/__pycache__/test_hourglass_tangent_comparator.cpython-313-pytest-9.0.2.pyc +0 -0
  341. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/file_comparator/__pycache__/test_numeric_compare.cpython-312-pytest-7.4.4.pyc +0 -0
  342. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/file_comparator/__pycache__/test_numeric_compare.cpython-312-pytest-9.0.3.pyc +0 -0
  343. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/file_comparator/__pycache__/test_numeric_parity.cpython-312-pytest-7.4.4.pyc +0 -0
  344. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/file_comparator/__pycache__/test_numeric_parity.cpython-312-pytest-9.0.3.pyc +0 -0
  345. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/file_comparator/__pycache__/test_script_comparator.cpython-313-pytest-9.0.2.pyc +0 -0
  346. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/file_comparator/__pycache__/test_workspace_plugins.cpython-313-pytest-9.0.2.pyc +0 -0
  347. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/file_comparator/test_error_analysis.py +0 -0
  348. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/file_comparator/test_hourglass_tangent_comparator.py +0 -0
  349. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/file_comparator/test_numeric_compare.py +0 -0
  350. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/file_comparator/test_numeric_parity.py +0 -0
  351. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/runners/__pycache__/test_json_yaml_runner.cpython-312-pytest-7.4.4.pyc +0 -0
  352. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/runners/__pycache__/test_json_yaml_runner.cpython-312-pytest-9.0.3.pyc +0 -0
  353. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/runners/__pycache__/test_json_yaml_runner.cpython-312.pyc +0 -0
  354. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/runners/__pycache__/test_json_yaml_runner.cpython-313-pytest-9.0.2.pyc +0 -0
  355. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/runners/__pycache__/test_json_yaml_runner.cpython-39-pytest-8.3.4.pyc +0 -0
  356. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/runners/__pycache__/test_test_case_filter.cpython-312-pytest-7.4.4.pyc +0 -0
  357. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/runners/__pycache__/test_test_case_filter.cpython-312-pytest-9.0.3.pyc +0 -0
  358. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/runners/__pycache__/test_test_case_filter.cpython-313-pytest-9.0.2.pyc +0 -0
  359. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/runners/__pycache__/test_test_case_tag_filter.cpython-312-pytest-7.4.4.pyc +0 -0
  360. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/runners/__pycache__/test_test_case_tag_filter.cpython-312-pytest-9.0.3.pyc +0 -0
  361. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/runners/__pycache__/test_test_case_tag_filter.cpython-313-pytest-9.0.2.pyc +0 -0
  362. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/runners/test_json_yaml_runner.py +0 -0
  363. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/runners/test_test_case_filter.py +0 -0
  364. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/runners/test_test_case_tag_filter.py +0 -0
  365. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/test_binary_comparator.py +0 -0
  366. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/test_cli.py +0 -0
  367. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/test_config_io.py +0 -0
  368. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/test_h5_comparator.py +0 -0
  369. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/test_import_expander.py +0 -0
  370. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/test_inheritance_expander.py +0 -0
  371. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/test_migrate.py +0 -0
  372. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/test_result.py +0 -0
  373. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/test_run_all.py +0 -0
  374. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/tui/__init__.py +0 -0
  375. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/tui/__pycache__/__init__.cpython-312.pyc +0 -0
  376. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/tui/__pycache__/__init__.cpython-313.pyc +0 -0
  377. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/tui/__pycache__/conftest.cpython-312-pytest-7.4.4.pyc +0 -0
  378. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/tui/__pycache__/conftest.cpython-312-pytest-9.0.3.pyc +0 -0
  379. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/tui/__pycache__/conftest.cpython-313-pytest-9.0.2.pyc +0 -0
  380. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/tui/__pycache__/test_app.cpython-312-pytest-7.4.4.pyc +0 -0
  381. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/tui/__pycache__/test_app.cpython-312-pytest-9.0.3.pyc +0 -0
  382. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/tui/__pycache__/test_app.cpython-313-pytest-9.0.2.pyc +0 -0
  383. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/tui/__pycache__/test_case_controller.cpython-312-pytest-7.4.4.pyc +0 -0
  384. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/tui/__pycache__/test_case_controller.cpython-312-pytest-9.0.3.pyc +0 -0
  385. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/tui/__pycache__/test_case_controller.cpython-313-pytest-9.0.2.pyc +0 -0
  386. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/tui/__pycache__/test_search_functions.cpython-312-pytest-7.4.4.pyc +0 -0
  387. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/tui/__pycache__/test_search_functions.cpython-312-pytest-9.0.3.pyc +0 -0
  388. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/tui/__pycache__/test_search_functions.cpython-313-pytest-9.0.2.pyc +0 -0
  389. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/tui/__pycache__/test_tui_run_test.cpython-312-pytest-7.4.4.pyc +0 -0
  390. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/tui/__pycache__/test_tui_run_test.cpython-312-pytest-9.0.3.pyc +0 -0
  391. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/tui/__pycache__/test_tui_run_test.cpython-313-pytest-9.0.2.pyc +0 -0
  392. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/tui/__pycache__/test_widgets_data.cpython-312-pytest-7.4.4.pyc +0 -0
  393. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/tui/__pycache__/test_widgets_data.cpython-312-pytest-9.0.3.pyc +0 -0
  394. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/tui/__pycache__/test_widgets_data.cpython-313-pytest-9.0.2.pyc +0 -0
  395. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/tui/conftest.py +0 -0
  396. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/tui/test_app.py +0 -0
  397. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/tui/test_case_controller.py +0 -0
  398. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/tui/test_search_functions.py +0 -0
  399. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/tui/test_tui_run_test.py +0 -0
  400. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/tui/test_widgets_data.py +0 -0
  401. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/utils/__pycache__/test_junit_xml_writer.cpython-312-pytest-7.4.4.pyc +0 -0
  402. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/utils/__pycache__/test_junit_xml_writer.cpython-312-pytest-9.0.3.pyc +0 -0
  403. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/utils/__pycache__/test_junit_xml_writer.cpython-313-pytest-9.0.2.pyc +0 -0
  404. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/utils/__pycache__/test_report_generator.cpython-313-pytest-9.0.2.pyc +0 -0
  405. {symtest_cli-1.4.1 → symtest_cli-1.5.0}/tests/unit/utils/test_junit_xml_writer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: symtest-cli
3
- Version: 1.4.1
3
+ Version: 1.5.0
4
4
  Summary: Regression testing for CLI applications, with multi-step workflows and numerical file comparison.
5
5
  Home-page: https://github.com/ozil111/symtest-cli
6
6
  Author: Xiaotong Wang
@@ -175,24 +175,57 @@ PathResolver 解析(系统命令直通、shell builtin 平台包装、复合
175
175
 
176
176
  ## 6. 文件比较子系统
177
177
 
178
- ### 6.1 比较器分层
178
+ ### 6.1 三泳道比较器架构
179
179
 
180
- - 文本系比较器共享 difflib 行级基底,json / csv / xml 为其结构化特化(键对齐 / 列结构 / DOM 对齐)
181
- - h5 面向科学数据集;binary 流式分块 + LCS 相似度;script 委托外部脚本
182
- - 统一返回 ComparisonResult(identical / differences / error / script command_output),支持 text / json / html 渲染;支持行列窗口范围参数截取后比较
180
+ 所有比较器共享根契约 `ComparatorBase.compare(ctx) -> ComparisonResult`:
181
+ 框架构建 `CompareContext`(workspace / actual / baseline / params / error_analysis);
182
+ `actual`/`baseline` 以及插件 `path_params` 声明的构造参数在**比较器构造之前**
183
+ 按 workspace 统一解析,插件不得自行对 CWD resolve。配置单一事实源:
184
+ 比较器配置只存在于构造器捕获的实例状态,`ctx` 仅承载调用级上下文。
185
+ 三条泳道职责互斥:
183
186
 
184
- ### 6.2 工厂与插件发现
187
+ | 泳道 | 基类 | 契约 | verdict 归属 | 内置实例 |
188
+ |---|---|---|---|---|
189
+ | 文件泳道 | `FileComparator` | `read_content` + `compare_content`(两文件模型,行列窗口、line N 偏移、chunk_size、similarity 均属本泳道) | 插件 | text / json / csv / xml / h5 / binary |
190
+ | 数据泳道 | `ExtractorComparator` | `extract(ctx) -> {channel: ChannelData}`;框架逐通道跑 `compare_numeric`,per-channel 容差(`channels` / `default_channel`),聚合 `ChannelResult` | **框架**(容差语义对 AI 消费端可预期) | script_extract、extractor 插件 |
191
+ | 自主泳道 | 直接继承根 | `compare(ctx)` 全权判定 | 插件 | script、hourglass 式分析插件 |
185
192
 
186
- - `file_type` 取值:`text` / `json` / `csv` / `xml` / `h5` / `binary` / `script`;工厂按类型分发,支持动态注册与全局 reset(测试用)
193
+ - 文本系共享 difflib 行级基底,json / csv / xml 为其结构化特化;h5 面向科学数据集;binary 流式分块 + LCS 相似度
194
+ - 统一返回 `ComparisonResult`(identical / differences / error / error_stats / command_output / channels),支持 text / json / html 渲染
195
+ - 数据泳道插件可通过 `ChannelData.extra_stats` 附带自定义误差指标,存放在独立命名空间(`ChannelResult.extra_stats`),不可覆盖框架规范指标;但 verdict 仍由框架容差持有——需要自定 verdict 的走自主泳道
196
+
197
+ ### 6.2 通道协议(数据泳道)
198
+
199
+ - 通道判定:`identical = all(channels.passed)`;差异 position 带通道前缀(`channel S33`);`error_stats` 按通道名嵌套
200
+ - 内置 `script_extract` 类型:子进程执行用户脚本(零改动接入),脚本 stdout 输出约定 JSON:
201
+
202
+ ```json
203
+ {"channels": {"S11": {"expected": [...], "actual": [...], "extra_stats": {...}}}}
204
+ ```
205
+
206
+ 非零退出、超时、畸形 JSON 一律判为比较 error(绝不静默通过);`actual`/`baseline` 按惯例以 baseline 在前的顺序追加为尾部 argv 槽位(可缺省)
207
+ - 结果粒度:一条 compareSpec = 一条断言;`ComparisonResult.channels` 携带通道级子结果,报告逐通道展示 pass/fail + stats;JSON 输出中通道 differences 按 per-channel 配额截断
208
+
209
+ ### 6.3 工厂与插件发现
210
+
211
+ - `file_type` 取值:`text` / `json` / `csv` / `xml` / `h5` / `binary` / `script` / `script_extract`;工厂按类型分发,支持动态注册与全局 reset(测试用)
187
212
  - 插件发现四处来源:内置 `*_comparator.py` 自动发现、`workspace/comparators/` 自动扫描、`--plugin-dir` CLI 参数、`CLITEST_PLUGIN_DIRS` 环境变量(供进程模式 worker 使用)
188
- - 命名约定:`*_comparator.py` + `*Comparator` 类名
213
+ - 命名约定:`*_comparator.py` + `*Comparator` 类名;可用类属性 `comparator_type` 显式指定类型名(如 `script_extract`);抽象基类自动跳过注册
214
+ - 配置传递与严格校验:`actual`/`baseline`/`type`/`options` 之外的键转发给比较器构造函数(kwargs);构造器参数是**严格**的——未声明的键(拼写错误)在构造时大声失败并给出支持参数清单。`options` 是框架持有的插件配置命名空间(并入构造参数,显式顶层键优先),核心 schema 不为单个插件增加专属字段;`channels`/`default_channel` 属数据泳道框架结构
189
215
 
190
- ### 6.3 script 比较协议(对外契约)
216
+ ### 6.4 script 比较协议(自主泳道对外契约)
191
217
 
192
- - 子进程方式执行 `<interpreter> <script> <actual> <baseline>`
218
+ - 子进程方式执行 `<interpreter> <script> <baseline> <actual>`(文件槽位可缺省)
193
219
  - 默认 exit code 0 → 通过;可选 `pass_pattern` / `fail_pattern` 正则匹配 stdout 细化判定
194
220
  - 超时可配
195
221
 
222
+ ### 6.5 插件公共 API
223
+
224
+ `symtest.file_comparator` 包导出:`ComparatorBase` / `CompareContext` /
225
+ `FileComparator` / `ExtractorComparator` / `ChannelData` / `ChannelResult` /
226
+ `compare_numeric` / `NumericComparisonStats` / `parse_data_filter`。
227
+ 数据泳道插件只需依赖 `ExtractorComparator` + `ChannelData`,数值判定全部复用框架核心。
228
+
196
229
  ## 7. TUI 子系统
197
230
 
198
231
  基于 Textual 的终端交互界面:App + Controller(load / create / update /
@@ -210,7 +243,7 @@ TUI 与 runner 共用同一解析器;宽松的展示形态在 TUI 侧自行处
210
243
  | 新配置格式 | `BaseRunner` | 支持新的测试定义格式(如 XML、TOML) |
211
244
  | 自定义 Setup | `BaseSetup` | 数据库初始化、服务启停等 |
212
245
  | 自定义断言 | 扩展 `Assertions` | 特定业务校验逻辑 |
213
- | 新比较器 | `BaseComparator` | 支持新的文件格式比较,放入 `comparators/` 目录自动发现 |
246
+ | 新比较器 | `FileComparator` / `ExtractorComparator` / `ComparatorBase` | 按泳道选择基类(文件格式 / 数据提取+通道 / 全权分析),放入 `comparators/` 目录自动发现 |
214
247
  | 新 Runner | `ParallelRunner` / `BaseRunner` | 自定义并行调度策略 |
215
248
  | TUI 扩展 | `CaseController` / Widgets | 扩展终端管理界面 |
216
249
 
@@ -1972,11 +1972,31 @@ class MySetup(BaseSetup):
1972
1972
 
1973
1973
  ### 自定义文件比较器
1974
1974
 
1975
- 框架支持三种方式扩展比较能力:
1975
+ #### 三泳道架构(v2)
1976
1976
 
1977
- #### 方式一:工作区插件目录(推荐)
1977
+ 比较器插件体系围绕根契约 `ComparatorBase.compare(ctx) -> ComparisonResult` 组织,
1978
+ 按判定权归属分为三条泳道:
1978
1979
 
1979
- 在 workspace 下创建 `comparators/` 目录,放入 `*_comparator.py` 文件(命名与内置比较器一致),框架会在首次使用时自动发现并注册其中的 `*Comparator` 类。
1980
+ | 泳道 | 基类 | 适用场景 | verdict 归属 |
1981
+ |---|---|---|---|
1982
+ | **文件泳道** | `FileComparator` | 比较两个同类文件(text/json/csv/xml/h5/binary 内置即此类) | 插件 |
1983
+ | **数据泳道** | `ExtractorComparator` | 从任意上游(文件/求解器输出/跨文件整理)提取数值数据,**复用框架数值核心**做通道化容差判定 | **框架** |
1984
+ | **自主泳道** | 直接继承 `ComparatorBase` | 独特判定逻辑(阈值组合、标签解析、渐近分析等) | 插件 |
1985
+
1986
+ 选型决策:
1987
+
1988
+ - 数据本来就是 `(expected, actual)` 数值数组、只需 rtol/atol 判定 → **数据泳道**(可享受通道、per-channel 容差、统一 error_stats 与报告渲染)
1989
+ - 需要自定 verdict(比如"主分量紧、EAS 分量松"的组合判定、符号/渐近检查)→ **自主泳道**
1990
+ - 只是想让现成脚本快速接入 → 优先考虑内置 `script`(自定判定)或 `script_extract`(框架容差判定),零 Python 插件代码
1991
+
1992
+ **路径解析约定**:`actual`/`baseline` 以及插件用 `path_params` 类属性声明的路径参数,
1993
+ 由框架在**比较器构造之前**统一按 workspace 解析为绝对路径——构造器捕获的状态
1994
+ (`self.script`、`self.cwd` 等)持有的已是解析后的绝对路径。插件内部
1995
+ **禁止**对 CWD 做 `Path.resolve()`——并行/process 模式下 CWD 不可靠。
1996
+
1997
+ #### 插件发现方式(三种泳道通用)
1998
+
1999
+ 在 workspace 下创建 `comparators/` 目录,放入 `*_comparator.py` 文件,框架会在首次使用时自动发现并注册其中的 `*Comparator` 类。
1980
2000
 
1981
2001
  ```
1982
2002
  your-workspace/
@@ -1993,26 +2013,148 @@ symtest run test_config.json --plugin-dir ./extra_plugins
1993
2013
 
1994
2014
  插件也会通过环境变量 `CLITEST_PLUGIN_DIRS` 自动继承到 process 模式子进程。
1995
2015
 
1996
- **插件开发注意事项**:
1997
- - 继承 `symtest.file_comparator.BaseComparator`
2016
+ **命名约定**:
2017
+ - 文件名必须以 `_comparator.py` 结尾(如 `my_analysis_comparator.py`)
1998
2018
  - 类名必须以 `Comparator` 结尾(如 `MyAnalysisComparator`)
1999
- - 注册的 type 名 = 类名去掉 `Comparator` 再小写(如 `myanalysis`)
2000
- - 推荐重写 `compare_files(file1, file2, **kwargs)` 方法而非 `read_content`/`compare_content`(若比较器不使用两文件模型)
2001
- - 通过 `from symtest.file_comparator import ComparisonResult, Difference` 构造结构化结果
2002
- - `extra_kwargs` 自动从 config `compareSpec` 透传
2019
+ - 注册的 type 名 = 类名去掉 `Comparator` 再小写(如 `myanalysis`);也可用类属性 `comparator_type = "my_analysis"` 显式指定
2020
+ - 抽象基类(未实现全部抽象方法)自动跳过注册
2003
2021
 
2004
- 在配置中直接使用注册的类型名:
2022
+ 在配置中直接使用注册的类型名。比较器构造参数是**严格**的:`actual`/`baseline`/
2023
+ `type` 以外的键会透传给插件构造函数,但插件未声明的参数(如拼写错误
2024
+ `pass_threhsold`)会在构造时**大声失败**(错误信息包含比较器类型名与支持的
2025
+ 参数清单),绝不静默回退到默认值。插件自有配置建议放入 `options` 命名空间
2026
+ (其条目并入构造参数,显式顶层键优先),避免向核心 schema 增加插件专属字段:
2005
2027
 
2006
2028
  ```json
2007
2029
  {
2008
2030
  "type": "myanalysis",
2009
2031
  "actual": "optional_for_plugins",
2010
2032
  "baseline": "optional_for_plugins",
2011
- "param1": "value1"
2033
+ "options": {"param1": "value1", "param2": 42}
2034
+ }
2035
+ ```
2036
+
2037
+ #### 数据泳道:通道提取器插件
2038
+
2039
+ 适用场景:一个脚本/插件产出多通道数据(如 CSV 的不同列、不同物理量),
2040
+ 每通道需要独立的容差与误差分析。插件只负责"提取",判定完全由框架完成。
2041
+
2042
+ ```python
2043
+ # comparators/uel_stress_comparator.py
2044
+ import numpy as np
2045
+ from symtest.file_comparator import (
2046
+ ExtractorComparator, ChannelData, CompareContext,
2047
+ )
2048
+
2049
+ class UelStressComparator(ExtractorComparator):
2050
+ path_params = ("uel_csv", "ref_csv") # 由框架按 workspace 解析
2051
+
2052
+ def __init__(self, uel_csv="", ref_csv="", **kwargs):
2053
+ super().__init__(**kwargs)
2054
+ self.uel_csv = uel_csv
2055
+ self.ref_csv = ref_csv
2056
+
2057
+ def extract(self, ctx: CompareContext):
2058
+ uel = np.loadtxt(self.uel_csv, delimiter=",")
2059
+ ref = np.loadtxt(self.ref_csv, delimiter=",")
2060
+ return {
2061
+ "S11": ChannelData(expected=ref[:, 0], actual=uel[:, 0]),
2062
+ "S33": ChannelData(
2063
+ expected=ref[:, 2], actual=uel[:, 2],
2064
+ extra_stats={"hydrostatic_shift": float(uel[:, 2].mean())},
2065
+ ),
2066
+ }
2067
+ ```
2068
+
2069
+ 配置(per-channel 容差按名路由,未声明的通道用 `default_channel`):
2070
+
2071
+ ```json
2072
+ {
2073
+ "type": "uelstress",
2074
+ "uel_csv": "out/uel.csv",
2075
+ "ref_csv": "out/native.csv",
2076
+ "channels": {
2077
+ "S33": {"atol": 600.0, "data_filter": "abs>1e-12"}
2078
+ },
2079
+ "default_channel": {"rtol": 1e-5, "atol": 1e-8}
2080
+ }
2081
+ ```
2082
+
2083
+ **判定与结果语义**:
2084
+ - 每通道独立跑 `compare_numeric`(对称容差 `|a-b| <= max(rtol*max(|a|,|b|), atol)`)
2085
+ - `identical = 所有通道全过`;一条 compareSpec 仍是一条断言
2086
+ - 差异 position 带通道前缀(`channel S33`);`error_stats` 按通道名嵌套
2087
+ - 报告逐通道展示 pass/fail、容差与统计;JSON 输出中通道差异按 per-channel 配额截断
2088
+ - 自定义误差指标通过 `ChannelData.extra_stats` 附带,存放在**独立命名空间**
2089
+ (`ChannelResult.extra_stats`),不可能覆盖框架规范指标(`max_abs_error`、
2090
+ `total` 等);报告逐通道分别渲染两个命名空间
2091
+ - verdict 不可由插件干预——需要自定 verdict 请走自主泳道
2092
+
2093
+ #### 数据泳道:内置 `script_extract` 类型(零改动脚本接入)
2094
+
2095
+ 现成分析脚本无需包装成 Python 插件:脚本以子进程执行,向 stdout 打印约定
2096
+ JSON,各通道即进入上述框架判定管线。
2097
+
2098
+ ```json
2099
+ {
2100
+ "type": "script_extract",
2101
+ "script": "extract_channels.py",
2102
+ "args": ["--frame", "10"],
2103
+ "cwd": "case/subdir",
2104
+ "channels": {"S33": {"atol": 600.0}},
2105
+ "default_channel": {"rtol": 1e-5, "atol": 1e-8},
2106
+ "timeout": 600
2107
+ }
2108
+ ```
2109
+
2110
+ **stdout JSON 协议**:
2111
+
2112
+ ```json
2113
+ {
2114
+ "channels": {
2115
+ "S11": {"expected": [1.0, 2.0], "actual": [1.0, 2.0]},
2116
+ "S33": {"expected": [...], "actual": [...], "extra_stats": {"asymmetry": 2e-13}}
2117
+ }
2012
2118
  }
2013
2119
  ```
2014
2120
 
2015
- #### 方式二:内置 `script` 类型比较器
2121
+ **错误语义**:非零退出码、超时、畸形 JSON、缺 `channels` 键或通道缺
2122
+ `expected`/`actual` 数组 → 一律判为比较 error(绝不静默通过)。
2123
+ `actual`/`baseline`(若配置)以 baseline 在前的顺序追加为脚本尾部参数,可缺省。
2124
+
2125
+ #### 自主泳道:全权判定插件
2126
+
2127
+ 适用场景:独特判定逻辑。插件实现 `compare(ctx)` 并直接返回结果。
2128
+
2129
+ ```python
2130
+ # comparators/my_analysis_comparator.py
2131
+ from symtest.file_comparator import ComparatorBase, CompareContext, ComparisonResult, Difference
2132
+
2133
+ class MyAnalysisComparator(ComparatorBase):
2134
+ path_params = ("script", "case_dir")
2135
+
2136
+ def __init__(self, script="", case_dir=None, pass_threshold=1e-6):
2137
+ super().__init__() # 参数严格:未声明/拼错的配置键在构造时报错
2138
+ self.script = script # 构造前已由框架按 workspace 解析
2139
+ self.case_dir = case_dir
2140
+ self.pass_threshold = pass_threshold
2141
+
2142
+ def compare(self, ctx: CompareContext) -> ComparisonResult:
2143
+ result = ComparisonResult(
2144
+ file1=ctx.baseline, # 无文件输入时保持 None,不伪造空串
2145
+ file2=ctx.actual,
2146
+ )
2147
+ # ... 执行分析、解析指标 ...
2148
+ result.identical = True # 或 False + differences
2149
+ result.error_stats = {"full_rel": 8e-8} # 自由 dict,报告通用渲染
2150
+ return result
2151
+ ```
2152
+
2153
+ `ctx` 字段(仅调用级上下文):`workspace` / `actual` / `baseline`(可空,
2154
+ 无文件输入时为 `None`)/ `params`(文件泳道窗口参数)/ `error_analysis`。
2155
+ 比较器配置的唯一权威在构造器捕获的实例状态中——`ctx` 不携带配置副本。
2156
+
2157
+ #### 内置 `script` 类型比较器(自主泳道开箱即用)
2016
2158
 
2017
2159
  适用于独立分析脚本快速接入,无需编写比较器类:
2018
2160
 
@@ -2035,8 +2177,8 @@ symtest run test_config.json --plugin-dir ./extra_plugins
2035
2177
  | 参数 | 必填 | 默认值 | 说明 |
2036
2178
  |---|---|---|---|
2037
2179
  | `script` | 是 | — | 脚本路径(相对 workspace 或绝对) |
2038
- | `actual` | 否 | — | 传给脚本的第一个文件参数 |
2039
- | `baseline` | 否 | — | 传给脚本的第二个文件参数 |
2180
+ | `actual` | 否 | — | 传给脚本的文件参数(尾部第二位) |
2181
+ | `baseline` | 否 | — | 传给脚本的文件参数(尾部第一位) |
2040
2182
  | `cwd` | 否 | — | 脚本工作目录 |
2041
2183
  | `interpreter` | 否 | `sys.executable` | Python 解释器 |
2042
2184
  | `pass_exit_code` | 否 | `0` | 判定为通过的退出码 |
@@ -2052,15 +2194,15 @@ symtest run test_config.json --plugin-dir ./extra_plugins
2052
2194
 
2053
2195
  脚本的 stdout 和 stderr 会完整捕获到 `Comparator Output` 区块中,在报告渲染时限 20 行展示。
2054
2196
 
2055
- #### 方式三:手动注册(编程方式)
2197
+ #### 手动注册(编程方式)
2056
2198
 
2057
2199
  ```python
2058
- from symtest.file_comparator import ComparatorFactory
2059
- from symtest.file_comparator.base_comparator import BaseComparator
2200
+ from symtest.file_comparator import ComparatorFactory, ComparatorBase, CompareContext
2201
+ from symtest.file_comparator.result import ComparisonResult
2060
2202
 
2061
- class FooComparator(BaseComparator):
2062
- # 实现 read_content / compare_content 等方法
2063
- pass
2203
+ class FooComparator(ComparatorBase):
2204
+ def compare(self, ctx: CompareContext) -> ComparisonResult:
2205
+ ...
2064
2206
 
2065
2207
  ComparatorFactory.register_comparator("foo", FooComparator)
2066
2208
 
@@ -2070,7 +2212,7 @@ comparator = ComparatorFactory.create_comparator("foo")
2070
2212
 
2071
2213
  #### 专用插件示例:hourglass 切线刚度分析
2072
2214
 
2073
- `examples/plugins/hourglass_tangent_comparator.py` 是一个完整的工作区插件示例,展示了如何将专用的 `analyze_*_tangent.py` 分析脚本接入框架:
2215
+ `examples/plugins/hourglass_tangent_comparator.py` 是一个完整的自主泳道工作区插件示例,展示了如何将专用的 `analyze_*_tangent.py` 分析脚本接入框架:
2074
2216
 
2075
2217
  ```json
2076
2218
  {
@@ -2085,6 +2227,7 @@ comparator = ComparatorFactory.create_comparator("foo")
2085
2227
  **特点**:
2086
2228
  - 通过 subprocess 调分析脚本(**零改动** analyze 代码),捕获 stdout 后用正则解析 `RESULT:` 行和 `full_rel`/`aa_rel`/`hh_rel`/`asymmetry` 等数值指标
2087
2229
  - 构造结构化 `ComparisonResult`:`identical` 基于 `full_rel < pass_threshold` 判定;`differences` 列出超限指标;`error_stats` 包含全部数值
2230
+ - `path_params` 声明 `script`/`case_dir`,路径由框架按 workspace 解析
2088
2231
  - 脚本 stdout 进入 `Comparator Output` 区块
2089
2232
 
2090
2233
  使用方法:将插件文件复制到 workspace 的 `comparators/` 目录下即可自动发现,无需改框架代码。
@@ -1970,11 +1970,35 @@ class MySetup(BaseSetup):
1970
1970
 
1971
1971
  ### Custom File Comparator
1972
1972
 
1973
- The framework supports three ways to extend comparison capabilities:
1973
+ #### Three-Lane Architecture (v2)
1974
1974
 
1975
- #### Method 1: Workspace Plugin Directory (Recommended)
1975
+ The comparator plugin system is organized around the root contract
1976
+ `ComparatorBase.compare(ctx) -> ComparisonResult`, split by verdict ownership
1977
+ into three lanes:
1976
1978
 
1977
- Create a `comparators/` directory under your workspace and place `*_comparator.py` files inside (following the same naming convention as built-in comparators). The framework auto-discovers and registers the `*Comparator` classes on first use.
1979
+ | Lane | Base class | Use case | Verdict owned by |
1980
+ |---|---|---|---|
1981
+ | **File lane** | `FileComparator` | Comparing two same-kind files (built-in text/json/csv/xml/h5/binary) | Plugin |
1982
+ | **Data lane** | `ExtractorComparator` | Extracting numeric data from any upstream (files / solver output / cross-file aggregation), reusing the framework numeric core for per-channel tolerance verdicts | **Framework** |
1983
+ | **Autonomous lane** | extend `ComparatorBase` directly | Custom verdict logic (composite thresholds, label parsing, sign/asymptotic checks) | Plugin |
1984
+
1985
+ Selection guide:
1986
+
1987
+ - Data is already `(expected, actual)` numeric arrays needing only rtol/atol → **data lane** (channels, per-channel tolerances, unified error_stats and report rendering)
1988
+ - Custom verdict needed (e.g. "tight on main components, loose on EAS components", sign checks) → **autonomous lane**
1989
+ - Just want to wire an existing script in quickly → built-in `script` (custom verdict) or `script_extract` (framework tolerance verdict), zero plugin code
1990
+
1991
+ **Path resolution convention**: `actual`/`baseline` and any constructor params
1992
+ declared in the plugin's `path_params` class attribute are resolved against
1993
+ the workspace by the framework **before the comparator is constructed** —
1994
+ constructor-captured state (`self.script`, `self.cwd`, ...) already holds
1995
+ absolute paths. Plugins must
1996
+ **never** resolve paths against CWD — it is unreliable under parallel/process
1997
+ execution.
1998
+
1999
+ #### Plugin Discovery (common to all lanes)
2000
+
2001
+ Create a `comparators/` directory under your workspace and place `*_comparator.py` files inside. The framework auto-discovers and registers the `*Comparator` classes on first use.
1978
2002
 
1979
2003
  ```
1980
2004
  your-workspace/
@@ -1991,26 +2015,155 @@ symtest run test_config.json --plugin-dir ./extra_plugins
1991
2015
 
1992
2016
  Plugins are also automatically inherited by process-mode child processes via the `CLITEST_PLUGIN_DIRS` environment variable.
1993
2017
 
1994
- **Plugin development notes**:
1995
- - Inherit from `symtest.file_comparator.BaseComparator`
2018
+ **Naming conventions**:
2019
+ - File name must end with `_comparator.py` (e.g., `my_analysis_comparator.py`)
1996
2020
  - Class name must end with `Comparator` (e.g., `MyAnalysisComparator`)
1997
- - The registered type name = class name without `Comparator`, lowercased (e.g., `myanalysis`)
1998
- - Prefer overriding the `compare_files(file1, file2, **kwargs)` method over `read_content`/`compare_content` (if your comparator doesn't use the two-file model)
1999
- - Construct structured results via `from symtest.file_comparator import ComparisonResult, Difference`
2000
- - `extra_kwargs` are auto-passed from the config `compareSpec`
2001
-
2002
- Use the registered type name directly in your config:
2021
+ - The registered type name = class name without `Comparator`, lowercased (e.g., `myanalysis`); a `comparator_type = "my_analysis"` class attribute overrides it
2022
+ - Abstract base classes (with unimplemented abstract methods) are skipped automatically
2023
+
2024
+ Use the registered type name directly in your config. Comparator constructor
2025
+ parameters are **strict**: keys other than `actual`/`baseline`/`type` are
2026
+ forwarded to the plugin constructor, but a parameter the plugin does not
2027
+ declare (e.g. a typo like `pass_threhsold`) **fails loudly** at construction
2028
+ (the error names the comparator type and its supported parameters) — it never
2029
+ silently falls back to defaults. Plugin-owned configuration should go into
2030
+ the `options` namespace (entries merged into constructor kwargs; explicit
2031
+ top-level keys take precedence), keeping the core schema generic:
2003
2032
 
2004
2033
  ```json
2005
2034
  {
2006
2035
  "type": "myanalysis",
2007
2036
  "actual": "optional_for_plugins",
2008
2037
  "baseline": "optional_for_plugins",
2009
- "param1": "value1"
2038
+ "options": {"param1": "value1", "param2": 42}
2010
2039
  }
2011
2040
  ```
2012
2041
 
2013
- #### Method 2: Built-in `script` Type Comparator
2042
+ #### Data Lane: Channel Extractor Plugin
2043
+
2044
+ Use case: one script/plugin produces multi-channel data (e.g. different CSV
2045
+ columns, different physical quantities), each channel needing independent
2046
+ tolerances and error analysis. The plugin only EXTRACTS; the framework
2047
+ owns the verdict entirely.
2048
+
2049
+ ```python
2050
+ # comparators/uel_stress_comparator.py
2051
+ import numpy as np
2052
+ from symtest.file_comparator import (
2053
+ ExtractorComparator, ChannelData, CompareContext,
2054
+ )
2055
+
2056
+ class UelStressComparator(ExtractorComparator):
2057
+ path_params = ("uel_csv", "ref_csv") # workspace-resolved by framework
2058
+
2059
+ def __init__(self, uel_csv="", ref_csv="", **kwargs):
2060
+ super().__init__(**kwargs)
2061
+ self.uel_csv = uel_csv
2062
+ self.ref_csv = ref_csv
2063
+
2064
+ def extract(self, ctx: CompareContext):
2065
+ uel = np.loadtxt(self.uel_csv, delimiter=",")
2066
+ ref = np.loadtxt(self.ref_csv, delimiter=",")
2067
+ return {
2068
+ "S11": ChannelData(expected=ref[:, 0], actual=uel[:, 0]),
2069
+ "S33": ChannelData(
2070
+ expected=ref[:, 2], actual=uel[:, 2],
2071
+ extra_stats={"hydrostatic_shift": float(uel[:, 2].mean())},
2072
+ ),
2073
+ }
2074
+ ```
2075
+
2076
+ Config (per-channel tolerances routed by name; unlisted channels use `default_channel`):
2077
+
2078
+ ```json
2079
+ {
2080
+ "type": "uelstress",
2081
+ "uel_csv": "out/uel.csv",
2082
+ "ref_csv": "out/native.csv",
2083
+ "channels": {
2084
+ "S33": {"atol": 600.0, "data_filter": "abs>1e-12"}
2085
+ },
2086
+ "default_channel": {"rtol": 1e-5, "atol": 1e-8}
2087
+ }
2088
+ ```
2089
+
2090
+ **Verdict and result semantics**:
2091
+ - Each channel runs `compare_numeric` independently (symmetric tolerance `|a-b| <= max(rtol*max(|a|,|b|), atol)`)
2092
+ - `identical = all channels pass`; one compareSpec is still one assertion
2093
+ - Difference positions carry the channel prefix (`channel S33`); `error_stats` nests by channel name
2094
+ - The report shows per-channel pass/fail, tolerances and stats; channel differences are trimmed per channel in JSON output
2095
+ - Custom error metrics ride along via `ChannelData.extra_stats`, kept in a SEPARATE namespace (`ChannelResult.extra_stats`) that can never overwrite framework canonical metrics (`max_abs_error`, `total`, ...); the report renders both namespaces per channel
2096
+ - The verdict itself cannot be altered by the plugin — custom verdicts belong to the autonomous lane
2097
+
2098
+ #### Data Lane: Built-in `script_extract` Type (Zero-Modification Script Access)
2099
+
2100
+ Existing analysis scripts need no Python plugin wrapper: the script runs as a
2101
+ subprocess and prints a JSON channel payload on stdout; each channel then
2102
+ enters the framework-owned tolerance pipeline described above.
2103
+
2104
+ ```json
2105
+ {
2106
+ "type": "script_extract",
2107
+ "script": "extract_channels.py",
2108
+ "args": ["--frame", "10"],
2109
+ "cwd": "case/subdir",
2110
+ "channels": {"S33": {"atol": 600.0}},
2111
+ "default_channel": {"rtol": 1e-5, "atol": 1e-8},
2112
+ "timeout": 600
2113
+ }
2114
+ ```
2115
+
2116
+ **stdout JSON protocol**:
2117
+
2118
+ ```json
2119
+ {
2120
+ "channels": {
2121
+ "S11": {"expected": [1.0, 2.0], "actual": [1.0, 2.0]},
2122
+ "S33": {"expected": [...], "actual": [...], "extra_stats": {"asymmetry": 2e-13}}
2123
+ }
2124
+ }
2125
+ ```
2126
+
2127
+ **Error semantics**: non-zero exit code, timeout, malformed JSON, a missing
2128
+ `channels` key, or a channel missing `expected`/`actual` arrays → always a
2129
+ comparison error (never a silent pass). `actual`/`baseline` (if configured)
2130
+ are appended as trailing script arguments with baseline first; both optional.
2131
+
2132
+ #### Autonomous Lane: Full-Verdict Plugin
2133
+
2134
+ Use case: custom verdict logic. The plugin implements `compare(ctx)` and
2135
+ returns the result directly.
2136
+
2137
+ ```python
2138
+ # comparators/my_analysis_comparator.py
2139
+ from symtest.file_comparator import ComparatorBase, CompareContext, ComparisonResult, Difference
2140
+
2141
+ class MyAnalysisComparator(ComparatorBase):
2142
+ path_params = ("script", "case_dir")
2143
+
2144
+ def __init__(self, script="", case_dir=None, pass_threshold=1e-6):
2145
+ super().__init__() # strict: undeclared/misspelled config keys fail loudly
2146
+ self.script = script # workspace-resolved by the framework before construction
2147
+ self.case_dir = case_dir
2148
+ self.pass_threshold = pass_threshold
2149
+
2150
+ def compare(self, ctx: CompareContext) -> ComparisonResult:
2151
+ result = ComparisonResult(
2152
+ file1=ctx.baseline, # stays None when there is no file input — no fake ""
2153
+ file2=ctx.actual,
2154
+ )
2155
+ # ... run analysis, parse metrics ...
2156
+ result.identical = True # or False + differences
2157
+ result.error_stats = {"full_rel": 8e-8} # free-form dict, generic rendering
2158
+ return result
2159
+ ```
2160
+
2161
+ `ctx` fields (invocation-level context only): `workspace` / `actual` /
2162
+ `baseline` (`None` when absent) / `params` (file-lane window ranges) /
2163
+ `error_analysis`. The single authoritative copy of comparator configuration
2164
+ lives in constructor-captured state — `ctx` never duplicates it.
2165
+
2166
+ #### Built-in `script` Type Comparator (Autonomous Lane Out of the Box)
2014
2167
 
2015
2168
  For quickly integrating standalone analysis scripts without writing a comparator class:
2016
2169
 
@@ -2033,8 +2186,8 @@ For quickly integrating standalone analysis scripts without writing a comparator
2033
2186
  | Parameter | Required | Default | Description |
2034
2187
  |---|---|---|---|
2035
2188
  | `script` | Yes | — | Script path (relative to workspace or absolute) |
2036
- | `actual` | No | — | First file argument passed to the script |
2037
- | `baseline` | No | — | Second file argument passed to the script |
2189
+ | `actual` | No | — | File argument passed to the script (trailing second slot) |
2190
+ | `baseline` | No | — | File argument passed to the script (trailing first slot) |
2038
2191
  | `cwd` | No | — | Script working directory |
2039
2192
  | `interpreter` | No | `sys.executable` | Python interpreter |
2040
2193
  | `pass_exit_code` | No | `0` | Exit code considered a pass |
@@ -2050,15 +2203,15 @@ For quickly integrating standalone analysis scripts without writing a comparator
2050
2203
 
2051
2204
  The script's stdout and stderr are fully captured in the `Comparator Output` section, displayed up to 20 lines in the rendered report.
2052
2205
 
2053
- #### Method 3: Manual Registration (Programmatic)
2206
+ #### Manual Registration (Programmatic)
2054
2207
 
2055
2208
  ```python
2056
- from symtest.file_comparator import ComparatorFactory
2057
- from symtest.file_comparator.base_comparator import BaseComparator
2209
+ from symtest.file_comparator import ComparatorFactory, ComparatorBase, CompareContext
2210
+ from symtest.file_comparator.result import ComparisonResult
2058
2211
 
2059
- class FooComparator(BaseComparator):
2060
- # Implement read_content / compare_content etc.
2061
- pass
2212
+ class FooComparator(ComparatorBase):
2213
+ def compare(self, ctx: CompareContext) -> ComparisonResult:
2214
+ ...
2062
2215
 
2063
2216
  ComparatorFactory.register_comparator("foo", FooComparator)
2064
2217
 
@@ -2068,7 +2221,7 @@ comparator = ComparatorFactory.create_comparator("foo")
2068
2221
 
2069
2222
  #### Specialized Plugin Example: Hourglass Tangent Stiffness Analysis
2070
2223
 
2071
- `examples/plugins/hourglass_tangent_comparator.py` is a complete workspace plugin example demonstrating how to integrate a dedicated `analyze_*_tangent.py` analysis script into the framework:
2224
+ `examples/plugins/hourglass_tangent_comparator.py` is a complete autonomous-lane workspace plugin example demonstrating how to integrate a dedicated `analyze_*_tangent.py` analysis script into the framework:
2072
2225
 
2073
2226
  ```json
2074
2227
  {
@@ -2083,6 +2236,7 @@ comparator = ComparatorFactory.create_comparator("foo")
2083
2236
  **Features**:
2084
2237
  - Calls the analysis script via subprocess (**zero changes** to analyze code), parses `RESULT:` lines and numerical metrics like `full_rel`/`aa_rel`/`hh_rel`/`asymmetry` from stdout using regex
2085
2238
  - Constructs a structured `ComparisonResult`: `identical` determined by `full_rel < pass_threshold`; `differences` lists exceeded metrics; `error_stats` contains all numeric values
2239
+ - `path_params` declares `script`/`case_dir`, resolved against the workspace by the framework
2086
2240
  - Script stdout goes into the `Comparator Output` section
2087
2241
 
2088
2242
  Usage: copy the plugin file into your workspace's `comparators/` directory for auto-discovery — no framework code changes needed.
@@ -16,7 +16,7 @@ runtime. Library users can enable console logging explicitly::
16
16
  setup_console_logging(level=logging.DEBUG)
17
17
  """
18
18
 
19
- __version__ = "1.4.1"
19
+ __version__ = "1.5.0"
20
20
  __author__ = "Xiaotong Wang"
21
21
  __email__ = "xiaotongwang98@gmail.com"
22
22