cli-test-framework 1.0.1__tar.gz → 1.0.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 (222) hide show
  1. {cli_test_framework-1.0.1/src/cli_test_framework.egg-info → cli_test_framework-1.0.2}/PKG-INFO +1 -1
  2. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/docs/user_manual.md +6 -2
  3. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/setup.py +1 -1
  4. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/core/base_runner.py +4 -1
  5. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/core/config_loader.py +5 -0
  6. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/core/execution.py +49 -9
  7. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/core/parallel_runner.py +2 -0
  8. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/core/process_worker.py +1 -0
  9. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/core/test_case.py +5 -0
  10. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/core/types.py +1 -0
  11. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/tui/screens/case_editor.py +8 -0
  12. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/tui/widgets/steps_editor.py +11 -1
  13. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2/src/cli_test_framework.egg-info}/PKG-INFO +1 -1
  14. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework.egg-info/SOURCES.txt +1 -0
  15. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/integration/__pycache__/test_sequence.cpython-312-pytest-9.0.3.pyc +0 -0
  16. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/integration/test_sequence.py +7 -2
  17. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/__pycache__/test_binary_comparator.cpython-312-pytest-9.0.3.pyc +0 -0
  18. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/__pycache__/test_cli.cpython-312-pytest-9.0.3.pyc +0 -0
  19. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/__pycache__/test_config_io.cpython-312-pytest-9.0.3.pyc +0 -0
  20. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/__pycache__/test_import_expander.cpython-312-pytest-9.0.3.pyc +0 -0
  21. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/__pycache__/test_result.cpython-312-pytest-9.0.3.pyc +0 -0
  22. cli_test_framework-1.0.2/tests/unit/core/__pycache__/test_config_loader.cpython-312-pytest-9.0.3.pyc +0 -0
  23. cli_test_framework-1.0.2/tests/unit/core/__pycache__/test_execution.cpython-312-pytest-9.0.3.pyc +0 -0
  24. cli_test_framework-1.0.2/tests/unit/core/__pycache__/test_execution.cpython-312.pyc +0 -0
  25. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/core/__pycache__/test_process_worker.cpython-312-pytest-9.0.3.pyc +0 -0
  26. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/core/test_config_loader.py +87 -2
  27. cli_test_framework-1.0.2/tests/unit/core/test_execution.py +241 -0
  28. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/core/test_process_worker.py +37 -0
  29. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/runners/__pycache__/test_json_yaml_runner.cpython-312-pytest-9.0.3.pyc +0 -0
  30. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/runners/__pycache__/test_test_case_tag_filter.cpython-312-pytest-9.0.3.pyc +0 -0
  31. cli_test_framework-1.0.2/tests/unit/tui/__pycache__/__init__.cpython-312.pyc +0 -0
  32. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/tui/__pycache__/conftest.cpython-312-pytest-9.0.3.pyc +0 -0
  33. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/tui/__pycache__/test_app.cpython-312-pytest-9.0.3.pyc +0 -0
  34. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/tui/__pycache__/test_case_controller.cpython-312-pytest-9.0.3.pyc +0 -0
  35. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/tui/__pycache__/test_search_functions.cpython-312-pytest-9.0.3.pyc +0 -0
  36. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/tui/__pycache__/test_tui_run_test.cpython-312-pytest-9.0.3.pyc +0 -0
  37. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/tui/__pycache__/test_widgets_data.cpython-312-pytest-9.0.3.pyc +0 -0
  38. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/tui/test_widgets_data.py +14 -1
  39. cli_test_framework-1.0.1/tests/unit/core/__pycache__/test_config_loader.cpython-312-pytest-9.0.3.pyc +0 -0
  40. cli_test_framework-1.0.1/tests/unit/core/__pycache__/test_execution.cpython-312-pytest-9.0.3.pyc +0 -0
  41. cli_test_framework-1.0.1/tests/unit/core/test_execution.py +0 -70
  42. cli_test_framework-1.0.1/tests/unit/tui/__pycache__/__init__.cpython-312.pyc +0 -0
  43. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/MANIFEST.in +0 -0
  44. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/README.md +0 -0
  45. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/docs/design.md +0 -0
  46. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/docs/design_en.md +0 -0
  47. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/docs/tui_and_config_split_design.md +0 -0
  48. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/docs/user_manual_en.md +0 -0
  49. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/pyproject.toml +0 -0
  50. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/setup.cfg +0 -0
  51. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/__init__.py +0 -0
  52. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/cli.py +0 -0
  53. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/commands/__init__.py +0 -0
  54. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/commands/compare.py +0 -0
  55. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/config/__init__.py +0 -0
  56. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/config/config_io.py +0 -0
  57. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/config/import_expander.py +0 -0
  58. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/core/__init__.py +0 -0
  59. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/core/assertions.py +0 -0
  60. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/core/history_store.py +0 -0
  61. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/core/setup.py +0 -0
  62. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/file_comparator/__init__.py +0 -0
  63. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/file_comparator/base_comparator.py +0 -0
  64. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/file_comparator/binary_comparator.py +0 -0
  65. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/file_comparator/csv_comparator.py +0 -0
  66. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/file_comparator/factory.py +0 -0
  67. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/file_comparator/h5_comparator.py +0 -0
  68. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/file_comparator/json_comparator.py +0 -0
  69. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/file_comparator/result.py +0 -0
  70. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/file_comparator/text_comparator.py +0 -0
  71. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/file_comparator/xml_comparator.py +0 -0
  72. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/logging_config.py +0 -0
  73. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/runners/__init__.py +0 -0
  74. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/runners/config_runner.py +0 -0
  75. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/runners/json_runner.py +0 -0
  76. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/runners/parallel_config_runner.py +0 -0
  77. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/runners/parallel_json_runner.py +0 -0
  78. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/runners/parallel_yaml_runner.py +0 -0
  79. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/runners/yaml_runner.py +0 -0
  80. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/tui/__init__.py +0 -0
  81. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/tui/app.py +0 -0
  82. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/tui/controllers/__init__.py +0 -0
  83. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/tui/controllers/case_controller.py +0 -0
  84. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/tui/screens/__init__.py +0 -0
  85. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/tui/screens/case_list.py +0 -0
  86. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/tui/widgets/__init__.py +0 -0
  87. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/tui/widgets/case_table.py +0 -0
  88. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/tui/widgets/expected_editor.py +0 -0
  89. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/tui/widgets/search_bar.py +0 -0
  90. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/utils/__init__.py +0 -0
  91. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/utils/junit_xml_writer.py +0 -0
  92. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/utils/path_resolver.py +0 -0
  93. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework/utils/report_generator.py +0 -0
  94. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework.egg-info/dependency_links.txt +0 -0
  95. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework.egg-info/entry_points.txt +0 -0
  96. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework.egg-info/requires.txt +0 -0
  97. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/src/cli_test_framework.egg-info/top_level.txt +0 -0
  98. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/README.md +0 -0
  99. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/__init__.py +0 -0
  100. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/__pycache__/__init__.cpython-312.pyc +0 -0
  101. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/__pycache__/__init__.cpython-39.pyc +0 -0
  102. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/__pycache__/conftest.cpython-312-pytest-7.4.4.pyc +0 -0
  103. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/__pycache__/conftest.cpython-312-pytest-9.0.3.pyc +0 -0
  104. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/__pycache__/conftest.cpython-39-pytest-8.3.4.pyc +0 -0
  105. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/__pycache__/run_all.cpython-312.pyc +0 -0
  106. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/__pycache__/run_all.cpython-39.pyc +0 -0
  107. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/__pycache__/test_parallel_runner.cpython-312-pytest-7.4.4.pyc +0 -0
  108. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/__pycache__/test_setup_module.cpython-312-pytest-7.4.4.pyc +0 -0
  109. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/conftest.py +0 -0
  110. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/demos/h5_filter_demo.py +0 -0
  111. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/demos/manual_report_example.py +0 -0
  112. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/demos/perf_parallel.py +0 -0
  113. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/e2e/__init__.py +0 -0
  114. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/e2e/__pycache__/__init__.cpython-312.pyc +0 -0
  115. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/e2e/__pycache__/__init__.cpython-39.pyc +0 -0
  116. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/e2e/__pycache__/test_user_flows.cpython-312-pytest-7.4.4.pyc +0 -0
  117. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/e2e/__pycache__/test_user_flows.cpython-312-pytest-9.0.3.pyc +0 -0
  118. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/e2e/__pycache__/test_user_flows.cpython-39-pytest-8.3.4.pyc +0 -0
  119. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/e2e/test_user_flows.py +0 -0
  120. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/fixtures/circular_config_a.json +0 -0
  121. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/fixtures/circular_config_b.json +0 -0
  122. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/fixtures/main_config.json +0 -0
  123. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/fixtures/missing_fields.json +0 -0
  124. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/fixtures/setup_main.json +0 -0
  125. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/fixtures/setup_sub.json +0 -0
  126. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/fixtures/sub_json_tests.yaml +0 -0
  127. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/fixtures/sub_text_tests.json +0 -0
  128. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/fixtures/test_cases.json +0 -0
  129. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/fixtures/test_cases.yaml +0 -0
  130. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/fixtures/test_cases1.json +0 -0
  131. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/fixtures/test_with_setup.json +0 -0
  132. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/fixtures/test_with_setup.yaml +0 -0
  133. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/integration/__pycache__/test_history_feature.cpython-312-pytest-7.4.4.pyc +0 -0
  134. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/integration/__pycache__/test_history_feature.cpython-312-pytest-9.0.3.pyc +0 -0
  135. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/integration/__pycache__/test_real_command_execution.cpython-312-pytest-7.4.4.pyc +0 -0
  136. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/integration/__pycache__/test_real_command_execution.cpython-312-pytest-9.0.3.pyc +0 -0
  137. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/integration/__pycache__/test_sequence.cpython-312-pytest-7.4.4.pyc +0 -0
  138. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/integration/file_compare/__pycache__/test_binary_compare.cpython-312-pytest-7.4.4.pyc +0 -0
  139. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/integration/file_compare/__pycache__/test_binary_compare.cpython-312-pytest-9.0.3.pyc +0 -0
  140. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/integration/file_compare/__pycache__/test_binary_compare.cpython-39-pytest-8.3.4.pyc +0 -0
  141. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/integration/file_compare/__pycache__/test_csv_compare.cpython-312-pytest-7.4.4.pyc +0 -0
  142. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/integration/file_compare/__pycache__/test_csv_compare.cpython-312-pytest-9.0.3.pyc +0 -0
  143. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/integration/file_compare/__pycache__/test_h5_compare.cpython-312-pytest-7.4.4.pyc +0 -0
  144. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/integration/file_compare/__pycache__/test_h5_compare.cpython-312-pytest-9.0.3.pyc +0 -0
  145. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/integration/file_compare/__pycache__/test_h5_compare.cpython-39-pytest-8.3.4.pyc +0 -0
  146. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/integration/file_compare/__pycache__/test_json_compare.cpython-312-pytest-7.4.4.pyc +0 -0
  147. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/integration/file_compare/__pycache__/test_json_compare.cpython-312-pytest-9.0.3.pyc +0 -0
  148. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/integration/file_compare/__pycache__/test_json_compare.cpython-39-pytest-8.3.4.pyc +0 -0
  149. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/integration/file_compare/__pycache__/test_text_compare.cpython-312-pytest-7.4.4.pyc +0 -0
  150. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/integration/file_compare/__pycache__/test_text_compare.cpython-312-pytest-9.0.3.pyc +0 -0
  151. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/integration/file_compare/__pycache__/test_text_compare.cpython-39-pytest-8.3.4.pyc +0 -0
  152. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/integration/file_compare/__pycache__/test_xml_compare.cpython-312-pytest-7.4.4.pyc +0 -0
  153. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/integration/file_compare/__pycache__/test_xml_compare.cpython-312-pytest-9.0.3.pyc +0 -0
  154. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/integration/file_compare/test_binary_compare.py +0 -0
  155. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/integration/file_compare/test_csv_compare.py +0 -0
  156. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/integration/file_compare/test_h5_compare.py +0 -0
  157. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/integration/file_compare/test_json_compare.py +0 -0
  158. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/integration/file_compare/test_text_compare.py +0 -0
  159. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/integration/file_compare/test_xml_compare.py +0 -0
  160. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/integration/parallel/__pycache__/test_parallel_runner.cpython-312-pytest-7.4.4.pyc +0 -0
  161. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/integration/parallel/__pycache__/test_parallel_runner.cpython-312-pytest-9.0.3.pyc +0 -0
  162. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/integration/parallel/__pycache__/test_parallel_runner.cpython-39-pytest-8.3.4.pyc +0 -0
  163. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/integration/parallel/test_parallel_runner.py +0 -0
  164. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/integration/path_handling/__pycache__/test_spaces_in_paths.cpython-312-pytest-7.4.4.pyc +0 -0
  165. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/integration/path_handling/__pycache__/test_spaces_in_paths.cpython-312-pytest-9.0.3.pyc +0 -0
  166. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/integration/path_handling/__pycache__/test_spaces_in_paths.cpython-39-pytest-8.3.4.pyc +0 -0
  167. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/integration/path_handling/test_spaces_in_paths.py +0 -0
  168. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/integration/test_history_feature.py +0 -0
  169. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/integration/test_real_command_execution.py +0 -0
  170. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/run_all.py +0 -0
  171. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/test_report.txt +0 -0
  172. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/__pycache__/test_cli.cpython-312-pytest-7.4.4.pyc +0 -0
  173. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/__pycache__/test_h5_comparator.cpython-312-pytest-7.4.4.pyc +0 -0
  174. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/__pycache__/test_h5_comparator.cpython-312-pytest-9.0.3.pyc +0 -0
  175. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/__pycache__/test_import_expander.cpython-312-pytest-7.4.4.pyc +0 -0
  176. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/__pycache__/test_run_all.cpython-312-pytest-7.4.4.pyc +0 -0
  177. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/__pycache__/test_run_all.cpython-312-pytest-9.0.3.pyc +0 -0
  178. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/commands/__pycache__/test_compare_command.cpython-312-pytest-7.4.4.pyc +0 -0
  179. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/commands/__pycache__/test_compare_command.cpython-312-pytest-9.0.3.pyc +0 -0
  180. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/commands/test_compare_command.py +0 -0
  181. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/core/__pycache__/test_assertions.cpython-312-pytest-7.4.4.pyc +0 -0
  182. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/core/__pycache__/test_assertions.cpython-312-pytest-9.0.3.pyc +0 -0
  183. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/core/__pycache__/test_history_store.cpython-312-pytest-7.4.4.pyc +0 -0
  184. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/core/__pycache__/test_history_store.cpython-312-pytest-9.0.3.pyc +0 -0
  185. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/core/__pycache__/test_process_worker.cpython-312-pytest-7.4.4.pyc +0 -0
  186. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/core/__pycache__/test_setup.cpython-312-pytest-7.4.4.pyc +0 -0
  187. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/core/__pycache__/test_setup.cpython-312-pytest-9.0.3.pyc +0 -0
  188. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/core/__pycache__/test_setup.cpython-39-pytest-8.3.4.pyc +0 -0
  189. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/core/test_assertions.py +0 -0
  190. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/core/test_history_store.py +0 -0
  191. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/core/test_setup.py +0 -0
  192. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/runners/__pycache__/test_json_yaml_runner.cpython-312-pytest-7.4.4.pyc +0 -0
  193. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/runners/__pycache__/test_json_yaml_runner.cpython-312.pyc +0 -0
  194. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/runners/__pycache__/test_json_yaml_runner.cpython-39-pytest-8.3.4.pyc +0 -0
  195. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/runners/__pycache__/test_test_case_filter.cpython-312-pytest-7.4.4.pyc +0 -0
  196. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/runners/__pycache__/test_test_case_filter.cpython-312-pytest-9.0.3.pyc +0 -0
  197. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/runners/test_json_yaml_runner.py +0 -0
  198. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/runners/test_test_case_filter.py +0 -0
  199. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/runners/test_test_case_tag_filter.py +0 -0
  200. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/test_binary_comparator.py +0 -0
  201. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/test_cli.py +0 -0
  202. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/test_config_io.py +0 -0
  203. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/test_h5_comparator.py +0 -0
  204. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/test_import_expander.py +0 -0
  205. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/test_result.py +0 -0
  206. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/test_run_all.py +0 -0
  207. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/tui/__init__.py +0 -0
  208. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/tui/__pycache__/test_app.cpython-312-pytest-7.4.4.pyc +0 -0
  209. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/tui/__pycache__/test_case_controller.cpython-312-pytest-7.4.4.pyc +0 -0
  210. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/tui/__pycache__/test_search_functions.cpython-312-pytest-7.4.4.pyc +0 -0
  211. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/tui/__pycache__/test_widgets_data.cpython-312-pytest-7.4.4.pyc +0 -0
  212. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/tui/conftest.py +0 -0
  213. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/tui/test_app.py +0 -0
  214. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/tui/test_case_controller.py +0 -0
  215. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/tui/test_search_functions.py +0 -0
  216. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/tui/test_tui_run_test.py +0 -0
  217. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/utils/__pycache__/test_junit_xml_writer.cpython-312-pytest-7.4.4.pyc +0 -0
  218. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/utils/__pycache__/test_junit_xml_writer.cpython-312-pytest-9.0.3.pyc +0 -0
  219. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/utils/__pycache__/test_report_generator.cpython-312-pytest-7.4.4.pyc +0 -0
  220. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/utils/__pycache__/test_report_generator.cpython-312-pytest-9.0.3.pyc +0 -0
  221. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/utils/test_junit_xml_writer.py +0 -0
  222. {cli_test_framework-1.0.1 → cli_test_framework-1.0.2}/tests/unit/utils/test_report_generator.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cli-test-framework
3
- Version: 1.0.1
3
+ Version: 1.0.2
4
4
  Summary: A powerful command line testing framework in Python with setup modules, parallel execution, and file comparison capabilities.
5
5
  Home-page: https://github.com/ozil111/cli-test-framework
6
6
  Author: Xiaotong Wang
@@ -49,6 +49,7 @@ HDF5 文件比较依赖 `h5py`(已随框架安装)。如需在无 HDF5 环
49
49
  "command": "echo",
50
50
  "args": ["Hello"],
51
51
  "timeout": 60,
52
+ "retry_count": 0,
52
53
  "resources": {
53
54
  "cpu_cores": 2,
54
55
  "estimated_time": 300,
@@ -80,6 +81,7 @@ test_cases:
80
81
  command: echo
81
82
  args: ["Hello"]
82
83
  timeout: 60
84
+ retry_count: 0
83
85
  resources:
84
86
  cpu_cores: 2
85
87
  estimated_time: 300
@@ -105,6 +107,7 @@ test_cases:
105
107
  | `args` | 否 | 命令参数列表 |
106
108
  | `description` | 否 | 测试用例描述 |
107
109
  | `timeout` | 否 | 超时秒数,默认 3600,设 `null` 无限制 |
110
+ | `retry_count` | 否 | 失败自动重试次数,默认 0(不重试)。单命令模式作用于整个 case,步骤模式可作用于每个 step |
108
111
  | `tags` | 否 | 标签列表,用于批量过滤(如 `["smoke", "fast"]`) |
109
112
  | `resources` | 否 | 资源配置,见[资源感知调度](#资源感知调度) |
110
113
  | `expected.return_code` | 否 | 期望返回码 |
@@ -801,7 +804,8 @@ runner.run_tests_sequential()
801
804
  {
802
805
  "command": "echo",
803
806
  "args": ["step2"],
804
- "expected": { "return_code": 0 }
807
+ "expected": { "return_code": 0 },
808
+ "retry_count": 2
805
809
  }
806
810
  ]
807
811
  }
@@ -825,7 +829,7 @@ test_cases:
825
829
  return_code: 0
826
830
  ```
827
831
 
828
- 每个 step 支持 `command`、`args`、`expected`、`timeout` 字段。失败时结果会标注失败步骤编号,如 "Failed at step 2/3"。
832
+ 每个 step 支持 `command`、`args`、`expected`、`timeout`、`retry_count` 字段。失败时结果会标注失败步骤编号,如 "Failed at step 2/3"。
829
833
 
830
834
  ## 资源感知调度
831
835
 
@@ -8,7 +8,7 @@ with open(os.path.join(this_directory, 'README.md'), encoding='utf-8') as f:
8
8
 
9
9
  setup(
10
10
  name="cli-test-framework",
11
- version="1.0.1",
11
+ version="1.0.2",
12
12
  author="Xiaotong Wang",
13
13
  author_email="xiaotongwang98@gmail.com",
14
14
  description="A powerful command line testing framework in Python with setup modules, parallel execution, and file comparison capabilities.",
@@ -129,11 +129,14 @@ class BaseRunner(ABC):
129
129
  self.setup_manager.teardown_all()
130
130
 
131
131
  def _update_history(self) -> None:
132
- """Update .symtest history with current run results and check for regressions."""
132
+ """Update .symtest history with successful run results and check for regressions."""
133
133
  if not self.history_dir:
134
134
  return
135
135
  history = load_history(self.history_dir)
136
136
  for result in self.results["details"]:
137
+ # Only record successful cases in history; skip failed ones
138
+ if result["status"] != "passed":
139
+ continue
137
140
  duration = result.get("duration", 0)
138
141
  # Check regression BEFORE updating (compare against old avg)
139
142
  warning = check_regression(history, result["name"], duration, self.regression_threshold)
@@ -115,6 +115,7 @@ def parse_test_cases(
115
115
  args=resolved_args,
116
116
  expected=step["expected"],
117
117
  timeout=step.get("timeout"),
118
+ retry_count=step.get("retry_count", 0),
118
119
  ))
119
120
  else:
120
121
  steps.append(TestCaseStep(
@@ -122,6 +123,7 @@ def parse_test_cases(
122
123
  args=step.get("args", []),
123
124
  expected=step.get("expected", {}),
124
125
  timeout=step.get("timeout"),
126
+ retry_count=step.get("retry_count", 0),
125
127
  ))
126
128
  cases.append(TestCase(
127
129
  name=case.get("name", ""),
@@ -151,6 +153,7 @@ def parse_test_cases(
151
153
  timeout=case.get("timeout"),
152
154
  resources=case.get("resources"),
153
155
  tags=case.get("tags", []),
156
+ retry_count=case.get("retry_count", 0),
154
157
  ))
155
158
  else:
156
159
  cases.append(TestCase(
@@ -162,6 +165,7 @@ def parse_test_cases(
162
165
  timeout=case.get("timeout"),
163
166
  resources=case.get("resources"),
164
167
  tags=case.get("tags", []),
168
+ retry_count=case.get("retry_count", 0),
165
169
  ))
166
170
 
167
171
  return cases
@@ -236,6 +240,7 @@ def execute_sequence(
236
240
  "description": None,
237
241
  "timeout": _step_attr(step, "timeout"),
238
242
  "resources": None,
243
+ "retry_count": _step_attr(step, "retry_count", 0),
239
244
  }
240
245
 
241
246
  command_preview = (
@@ -3,11 +3,14 @@ import signal
3
3
  import time
4
4
  import os
5
5
  import shlex
6
+ import logging
6
7
  from typing import Any, List, Optional, Dict
7
8
 
8
9
  from .assertions import Assertions
9
10
  from .types import ExpectedResult, TestCaseData, TestResultData
10
11
 
12
+ logger = logging.getLogger("cli_test_framework.core.execution")
13
+
11
14
  # Commands that are shell builtins (not real executables).
12
15
  # With shell=False, these must be wrapped via the platform shell.
13
16
  if os.name == 'nt':
@@ -80,15 +83,12 @@ def _dispatch_file_compare(
80
83
  )
81
84
 
82
85
 
83
- def execute_single_test_case(case: TestCaseData, workspace: Optional[str] = None, env: Optional[Dict[str, str]] = None) -> TestResultData:
84
- """
85
- Stateless execution of a single test case.
86
-
87
- Args:
88
- case: Test case data
89
- workspace: Working directory for test execution
90
- env: Optional environment variables to inject/override (merged with os.environ)
91
- """
86
+ def _execute_command_once(
87
+ case: TestCaseData,
88
+ workspace: Optional[str] = None,
89
+ env: Optional[Dict[str, str]] = None,
90
+ ) -> TestResultData:
91
+ """Execute a single command once (no retry logic)."""
92
92
  start_time = time.time()
93
93
  cmd_list = _normalize_cmd_list(case["command"], [str(arg) for arg in case["args"]])
94
94
  timeout_limit = case.get("timeout", 3600)
@@ -157,3 +157,43 @@ def execute_single_test_case(case: TestCaseData, workspace: Optional[str] = None
157
157
 
158
158
  return result
159
159
 
160
+
161
+ def execute_single_test_case(
162
+ case: TestCaseData,
163
+ workspace: Optional[str] = None,
164
+ env: Optional[Dict[str, str]] = None,
165
+ ) -> TestResultData:
166
+ """
167
+ Stateless execution of a single test case with optional retry.
168
+
169
+ Args:
170
+ case: Test case data (may include ``retry_count`` for automatic retries).
171
+ workspace: Working directory for test execution.
172
+ env: Optional environment variables to inject/override (merged with os.environ).
173
+
174
+ ``retry_count`` (defaults to 0) controls how many additional times the
175
+ command is re-run after the first failure. ``retry_count=0`` means no
176
+ retry (behaviour identical to previous versions).
177
+ """
178
+ retry_count: int = case.get("retry_count", 0)
179
+ max_attempts = retry_count + 1
180
+ total_duration = 0.0
181
+ last_result: Optional[TestResultData] = None
182
+
183
+ for attempt in range(1, max_attempts + 1):
184
+ result = _execute_command_once(case, workspace, env)
185
+ total_duration += result["duration"]
186
+ last_result = result
187
+
188
+ if result["status"] == "passed":
189
+ break
190
+
191
+ if attempt < max_attempts:
192
+ logger.info(
193
+ "Retrying '%s' (attempt %d/%d)...",
194
+ case["name"], attempt + 1, max_attempts,
195
+ )
196
+
197
+ last_result["duration"] = total_duration
198
+ return last_result
199
+
@@ -138,12 +138,14 @@ class ParallelRunner(BaseRunner):
138
138
  "expected": case.expected,
139
139
  "timeout": case.timeout,
140
140
  "resources": case.resources,
141
+ "retry_count": case.retry_count,
141
142
  "steps": [
142
143
  {
143
144
  "command": s.command,
144
145
  "args": s.args,
145
146
  "expected": s.expected,
146
147
  "timeout": s.timeout,
148
+ "retry_count": s.retry_count,
147
149
  }
148
150
  for s in case.steps
149
151
  ] if case.steps else None,
@@ -46,6 +46,7 @@ def run_test_in_process(test_index: int, case_data: Dict[str, Any], workspace: s
46
46
  "description": case_data.get("description"),
47
47
  "timeout": case_data.get("timeout"),
48
48
  "resources": case_data.get("resources"),
49
+ "retry_count": case_data.get("retry_count", 0),
49
50
  }
50
51
 
51
52
  command_preview = f"{case['command']} {' '.join(case['args'])}".strip()
@@ -9,6 +9,7 @@ class TestCaseStep:
9
9
  args: List[str]
10
10
  expected: Dict[str, Any]
11
11
  timeout: Optional[float] = None
12
+ retry_count: int = 0
12
13
 
13
14
  @dataclass
14
15
  class TestCase:
@@ -22,6 +23,7 @@ class TestCase:
22
23
  resources: Optional[Dict[str, Any]] = None
23
24
  steps: Optional[List[TestCaseStep]] = None
24
25
  tags: List[str] = field(default_factory=list)
26
+ retry_count: int = 0
25
27
 
26
28
  def to_dict(self) -> Dict[str, Any]:
27
29
  """Convert test case to dictionary format"""
@@ -33,6 +35,7 @@ class TestCase:
33
35
  "timeout": self.timeout,
34
36
  "resources": self.resources,
35
37
  "tags": self.tags,
38
+ "retry_count": self.retry_count,
36
39
  }
37
40
  if self.steps is not None:
38
41
  result["steps"] = [
@@ -41,6 +44,7 @@ class TestCase:
41
44
  "args": s.args,
42
45
  "expected": s.expected,
43
46
  "timeout": s.timeout,
47
+ "retry_count": s.retry_count,
44
48
  }
45
49
  for s in self.steps
46
50
  ]
@@ -60,4 +64,5 @@ class TestCase:
60
64
  "description": self.description or None,
61
65
  "timeout": self.timeout,
62
66
  "resources": self.resources,
67
+ "retry_count": self.retry_count,
63
68
  }
@@ -37,6 +37,7 @@ class TestCaseData(TypedDict):
37
37
  description: Optional[str]
38
38
  timeout: Optional[float]
39
39
  resources: Optional[ResourceRequirements]
40
+ retry_count: int
40
41
 
41
42
 
42
43
  class SetupConfig(TypedDict):
@@ -90,6 +90,8 @@ class CaseEditorScreen(Screen):
90
90
  TextArea("", id="case-description"),
91
91
  Label("Timeout (seconds):", id="timeout-label"),
92
92
  Input(placeholder="", id="case-timeout"),
93
+ Label("Retry count:", id="retry-label"),
94
+ Input(placeholder="0", id="case-retry-count"),
93
95
  ExpectedEditor(id="expected-editor"),
94
96
  StepsEditor(id="steps-editor"),
95
97
  id="editor-fields",
@@ -127,6 +129,7 @@ class CaseEditorScreen(Screen):
127
129
  self.query_one("#case-tags", Input).value = ",".join(tc.tags) if tc.tags else ""
128
130
  self.query_one("#case-description", TextArea).text = tc.description or ""
129
131
  self.query_one("#case-timeout", Input).value = str(tc.timeout) if tc.timeout else ""
132
+ self.query_one("#case-retry-count", Input).value = str(tc.retry_count) if tc.retry_count else ""
130
133
 
131
134
  self.query_one("#expected-editor", ExpectedEditor).load(tc.expected or {})
132
135
 
@@ -147,6 +150,9 @@ class CaseEditorScreen(Screen):
147
150
  timeout_text = self.query_one("#case-timeout", Input).value.strip()
148
151
  timeout = float(timeout_text) if timeout_text else None
149
152
 
153
+ retry_text = self.query_one("#case-retry-count", Input).value.strip()
154
+ retry_count = int(retry_text) if retry_text else 0
155
+
150
156
  description = self.query_one("#case-description", TextArea).text.strip()
151
157
 
152
158
  if self._is_sequence:
@@ -157,6 +163,7 @@ class CaseEditorScreen(Screen):
157
163
  description=description or "",
158
164
  resources=self._case.resources if self._case else None,
159
165
  tags=tags,
166
+ retry_count=retry_count,
160
167
  )
161
168
  else:
162
169
  command = self.query_one("#case-command", Input).value.strip()
@@ -173,6 +180,7 @@ class CaseEditorScreen(Screen):
173
180
  timeout=timeout,
174
181
  resources=self._case.resources if self._case else None,
175
182
  tags=tags,
183
+ retry_count=retry_count,
176
184
  )
177
185
 
178
186
  def _update_mode_visibility(self) -> None:
@@ -74,6 +74,8 @@ class StepsEditor(Vertical):
74
74
  yield TextArea('{"return_code": 0}', id="step-expected")
75
75
  yield Static("Timeout (seconds):", classes="label")
76
76
  yield Input(placeholder="", id="step-timeout")
77
+ yield Static("Retry count:", classes="label")
78
+ yield Input(placeholder="0", id="step-retry-count")
77
79
  yield Horizontal(
78
80
  Button("Save Step", id="step-save", variant="primary"),
79
81
  Button("Cancel", id="step-cancel"),
@@ -114,6 +116,7 @@ class StepsEditor(Vertical):
114
116
  if len(exp_preview) > 40:
115
117
  exp_preview = exp_preview[:37] + "..."
116
118
  timeout_str = f", timeout={step.timeout}s" if step.timeout else ""
119
+ retry_str = f", retry={step.retry_count}" if step.retry_count else ""
117
120
 
118
121
  row = Horizontal(
119
122
  Static(f"[Step {i+1}/{len(self._steps)}]"),
@@ -136,6 +139,7 @@ class StepsEditor(Vertical):
136
139
  self.query_one("#step-args", Input).value = ""
137
140
  self.query_one("#step-expected", TextArea).text = '{"return_code": 0}'
138
141
  self.query_one("#step-timeout", Input).value = ""
142
+ self.query_one("#step-retry-count", Input).value = ""
139
143
  status = self.query_one("#step-editor-status", Static)
140
144
  status.update("")
141
145
 
@@ -148,6 +152,7 @@ class StepsEditor(Vertical):
148
152
  step.expected, ensure_ascii=False
149
153
  )
150
154
  self.query_one("#step-timeout", Input).value = str(step.timeout) if step.timeout else ""
155
+ self.query_one("#step-retry-count", Input).value = str(step.retry_count) if step.retry_count else ""
151
156
  status = self.query_one("#step-editor-status", Static)
152
157
  status.update(f"Editing Step {idx+1}")
153
158
 
@@ -169,7 +174,12 @@ class StepsEditor(Vertical):
169
174
  timeout_text = self.query_one("#step-timeout", Input).value.strip()
170
175
  timeout = float(timeout_text) if timeout_text else None
171
176
 
172
- new_step = TestCaseStep(command=cmd, args=args, expected=expected, timeout=timeout)
177
+ retry_text = self.query_one("#step-retry-count", Input).value.strip()
178
+ retry_count = int(retry_text) if retry_text else 0
179
+
180
+ new_step = TestCaseStep(
181
+ command=cmd, args=args, expected=expected, timeout=timeout, retry_count=retry_count,
182
+ )
173
183
 
174
184
  if self._editing_idx >= 0:
175
185
  self._steps[self._editing_idx] = new_step
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cli-test-framework
3
- Version: 1.0.1
3
+ Version: 1.0.2
4
4
  Summary: A powerful command line testing framework in Python with setup modules, parallel execution, and file comparison capabilities.
5
5
  Home-page: https://github.com/ozil111/cli-test-framework
6
6
  Author: Xiaotong Wang
@@ -172,6 +172,7 @@ tests/unit/core/__pycache__/test_assertions.cpython-312-pytest-7.4.4.pyc
172
172
  tests/unit/core/__pycache__/test_assertions.cpython-312-pytest-9.0.3.pyc
173
173
  tests/unit/core/__pycache__/test_config_loader.cpython-312-pytest-9.0.3.pyc
174
174
  tests/unit/core/__pycache__/test_execution.cpython-312-pytest-9.0.3.pyc
175
+ tests/unit/core/__pycache__/test_execution.cpython-312.pyc
175
176
  tests/unit/core/__pycache__/test_history_store.cpython-312-pytest-7.4.4.pyc
176
177
  tests/unit/core/__pycache__/test_history_store.cpython-312-pytest-9.0.3.pyc
177
178
  tests/unit/core/__pycache__/test_process_worker.cpython-312-pytest-7.4.4.pyc
@@ -286,16 +286,21 @@ class TestTestCaseStepDataclass(unittest.TestCase):
286
286
  def test_test_case_to_dict_with_steps(self):
287
287
  steps = [
288
288
  TestCaseStep(command="echo", args=["a"], expected={"return_code": 0}),
289
+ TestCaseStep(command="echo", args=["b"], expected={"return_code": 0}, retry_count=2),
289
290
  ]
290
- case = TestCase(name="seq", steps=steps)
291
+ case = TestCase(name="seq", steps=steps, retry_count=3)
291
292
  d = case.to_dict()
292
293
  self.assertIn("steps", d)
293
294
  self.assertEqual(d["steps"][0]["command"], "echo")
295
+ self.assertEqual(d["steps"][0]["retry_count"], 0)
296
+ self.assertEqual(d["steps"][1]["retry_count"], 2)
297
+ self.assertEqual(d["retry_count"], 3)
294
298
 
295
299
  def test_test_case_to_dict_without_steps(self):
296
- case = TestCase(name="s", command="echo", args=["x"], expected={"return_code": 0})
300
+ case = TestCase(name="s", command="echo", args=["x"], expected={"return_code": 0}, retry_count=5)
297
301
  d = case.to_dict()
298
302
  self.assertNotIn("steps", d)
303
+ self.assertEqual(d["retry_count"], 5)
299
304
 
300
305
 
301
306
  if __name__ == "__main__":
@@ -1,8 +1,8 @@
1
- """Unit tests for cli_test_framework.core.config_loader — substitute_placeholders."""
1
+ """Unit tests for cli_test_framework.core.config_loader — substitute_placeholders, parse_test_cases."""
2
2
 
3
3
  import pytest
4
4
 
5
- from cli_test_framework.core.config_loader import substitute_placeholders
5
+ from cli_test_framework.core.config_loader import substitute_placeholders, parse_test_cases
6
6
 
7
7
 
8
8
  # ---------------------------------------------------------------------------
@@ -134,3 +134,88 @@ class TestPlaceholderInTestCases:
134
134
  assert steps[0]["args"] == ["--parse", "model.dat"]
135
135
  assert steps[1]["command"] == "/opt/solver"
136
136
  assert steps[1]["args"] == ["--solve", "model.dat"]
137
+
138
+
139
+ # ---------------------------------------------------------------------------
140
+ # parse_test_cases — retry_count
141
+ # ---------------------------------------------------------------------------
142
+
143
+ class TestParseRetryCount:
144
+ """Tests for ``retry_count`` parsing in ``parse_test_cases``."""
145
+
146
+ def test_single_command_retry_count(self):
147
+ config = {
148
+ "test_cases": [
149
+ {
150
+ "name": "retry-case",
151
+ "command": "echo",
152
+ "args": ["ok"],
153
+ "expected": {"return_code": 0},
154
+ "retry_count": 3,
155
+ }
156
+ ]
157
+ }
158
+ cases = parse_test_cases(config)
159
+ assert len(cases) == 1
160
+ assert cases[0].retry_count == 3
161
+
162
+ def test_single_command_default_retry_count(self):
163
+ """Without ``retry_count``, it defaults to 0."""
164
+ config = {
165
+ "test_cases": [
166
+ {
167
+ "name": "no-retry",
168
+ "command": "echo",
169
+ "args": ["ok"],
170
+ "expected": {"return_code": 0},
171
+ }
172
+ ]
173
+ }
174
+ cases = parse_test_cases(config)
175
+ assert cases[0].retry_count == 0
176
+
177
+ def test_step_retry_count(self):
178
+ config = {
179
+ "test_cases": [
180
+ {
181
+ "name": "step-case",
182
+ "steps": [
183
+ {
184
+ "command": "echo",
185
+ "args": ["s1"],
186
+ "expected": {"return_code": 0},
187
+ },
188
+ {
189
+ "command": "echo",
190
+ "args": ["s2"],
191
+ "expected": {"return_code": 0},
192
+ "retry_count": 2,
193
+ },
194
+ ],
195
+ }
196
+ ]
197
+ }
198
+ cases = parse_test_cases(config)
199
+ assert len(cases) == 1
200
+ steps = cases[0].steps
201
+ assert len(steps) == 2
202
+ assert steps[0].retry_count == 0 # default
203
+ assert steps[1].retry_count == 2
204
+
205
+ def test_step_default_retry_count(self):
206
+ config = {
207
+ "test_cases": [
208
+ {
209
+ "name": "step-no-retry",
210
+ "steps": [
211
+ {
212
+ "command": "echo",
213
+ "args": ["s1"],
214
+ "expected": {"return_code": 0},
215
+ }
216
+ ],
217
+ }
218
+ ]
219
+ }
220
+ cases = parse_test_cases(config)
221
+ assert cases[0].steps[0].retry_count == 0