cli-test-framework 0.8.0__tar.gz → 0.8.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/PKG-INFO +1 -1
  2. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/docs/user_manual.md +279 -16
  3. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/setup.py +1 -1
  4. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework/core/execution.py +1 -0
  5. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework.egg-info/PKG-INFO +1 -1
  6. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework.egg-info/SOURCES.txt +4 -0
  7. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/integration/__pycache__/test_history_feature.cpython-312-pytest-7.4.4.pyc +0 -0
  8. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/integration/__pycache__/test_history_feature.cpython-312-pytest-9.0.3.pyc +0 -0
  9. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/integration/__pycache__/test_sequence.cpython-312-pytest-7.4.4.pyc +0 -0
  10. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/integration/__pycache__/test_sequence.cpython-312-pytest-9.0.3.pyc +0 -0
  11. cli_test_framework-0.8.1/tests/integration/file_compare/__pycache__/test_binary_compare.cpython-312-pytest-7.4.4.pyc +0 -0
  12. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/integration/file_compare/__pycache__/test_binary_compare.cpython-312-pytest-9.0.3.pyc +0 -0
  13. cli_test_framework-0.8.1/tests/unit/__pycache__/test_cli.cpython-312-pytest-7.4.4.pyc +0 -0
  14. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/unit/__pycache__/test_cli.cpython-312-pytest-9.0.3.pyc +0 -0
  15. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/unit/__pycache__/test_run_all.cpython-312-pytest-7.4.4.pyc +0 -0
  16. cli_test_framework-0.8.1/tests/unit/core/__pycache__/test_assertions.cpython-312-pytest-7.4.4.pyc +0 -0
  17. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/unit/core/__pycache__/test_assertions.cpython-312-pytest-9.0.3.pyc +0 -0
  18. cli_test_framework-0.8.1/tests/unit/core/__pycache__/test_execution.cpython-312-pytest-9.0.3.pyc +0 -0
  19. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/unit/core/__pycache__/test_process_worker.cpython-312-pytest-7.4.4.pyc +0 -0
  20. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/unit/core/__pycache__/test_process_worker.cpython-312-pytest-9.0.3.pyc +0 -0
  21. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/unit/core/__pycache__/test_setup.cpython-312-pytest-7.4.4.pyc +0 -0
  22. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/unit/core/__pycache__/test_setup.cpython-312-pytest-9.0.3.pyc +0 -0
  23. cli_test_framework-0.8.1/tests/unit/core/test_execution.py +70 -0
  24. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/unit/runners/__pycache__/test_json_yaml_runner.cpython-312-pytest-7.4.4.pyc +0 -0
  25. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/unit/runners/__pycache__/test_json_yaml_runner.cpython-312-pytest-9.0.3.pyc +0 -0
  26. cli_test_framework-0.8.1/tests/unit/runners/__pycache__/test_test_case_filter.cpython-312-pytest-7.4.4.pyc +0 -0
  27. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/unit/runners/__pycache__/test_test_case_filter.cpython-312-pytest-9.0.3.pyc +0 -0
  28. cli_test_framework-0.8.1/tests/unit/utils/__pycache__/test_junit_xml_writer.cpython-312-pytest-7.4.4.pyc +0 -0
  29. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/unit/utils/__pycache__/test_junit_xml_writer.cpython-312-pytest-9.0.3.pyc +0 -0
  30. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/unit/utils/__pycache__/test_report_generator.cpython-312-pytest-7.4.4.pyc +0 -0
  31. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/unit/utils/__pycache__/test_report_generator.cpython-312-pytest-9.0.3.pyc +0 -0
  32. cli_test_framework-0.8.0/tests/integration/file_compare/__pycache__/test_binary_compare.cpython-312-pytest-7.4.4.pyc +0 -0
  33. cli_test_framework-0.8.0/tests/unit/__pycache__/test_cli.cpython-312-pytest-7.4.4.pyc +0 -0
  34. cli_test_framework-0.8.0/tests/unit/runners/__pycache__/test_test_case_filter.cpython-312-pytest-7.4.4.pyc +0 -0
  35. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/MANIFEST.in +0 -0
  36. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/README.md +0 -0
  37. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/docs/design.md +0 -0
  38. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/docs/design_en.md +0 -0
  39. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/docs/user_manual_en.md +0 -0
  40. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/pyproject.toml +0 -0
  41. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/setup.cfg +0 -0
  42. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework/__init__.py +0 -0
  43. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework/cli.py +0 -0
  44. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework/commands/__init__.py +0 -0
  45. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework/commands/compare.py +0 -0
  46. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework/core/__init__.py +0 -0
  47. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework/core/assertions.py +0 -0
  48. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework/core/base_runner.py +0 -0
  49. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework/core/config_loader.py +0 -0
  50. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework/core/history_store.py +0 -0
  51. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework/core/parallel_runner.py +0 -0
  52. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework/core/process_worker.py +0 -0
  53. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework/core/setup.py +0 -0
  54. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework/core/test_case.py +0 -0
  55. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework/core/types.py +0 -0
  56. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework/file_comparator/__init__.py +0 -0
  57. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework/file_comparator/base_comparator.py +0 -0
  58. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework/file_comparator/binary_comparator.py +0 -0
  59. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework/file_comparator/csv_comparator.py +0 -0
  60. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework/file_comparator/factory.py +0 -0
  61. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework/file_comparator/h5_comparator.py +0 -0
  62. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework/file_comparator/json_comparator.py +0 -0
  63. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework/file_comparator/result.py +0 -0
  64. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework/file_comparator/text_comparator.py +0 -0
  65. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework/file_comparator/xml_comparator.py +0 -0
  66. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework/logging_config.py +0 -0
  67. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework/runners/__init__.py +0 -0
  68. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework/runners/config_runner.py +0 -0
  69. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework/runners/json_runner.py +0 -0
  70. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework/runners/parallel_config_runner.py +0 -0
  71. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework/runners/parallel_json_runner.py +0 -0
  72. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework/runners/parallel_yaml_runner.py +0 -0
  73. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework/runners/yaml_runner.py +0 -0
  74. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework/utils/__init__.py +0 -0
  75. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework/utils/junit_xml_writer.py +0 -0
  76. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework/utils/path_resolver.py +0 -0
  77. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework/utils/report_generator.py +0 -0
  78. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework.egg-info/dependency_links.txt +0 -0
  79. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework.egg-info/entry_points.txt +0 -0
  80. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework.egg-info/requires.txt +0 -0
  81. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/src/cli_test_framework.egg-info/top_level.txt +0 -0
  82. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/README.md +0 -0
  83. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/__init__.py +0 -0
  84. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/__pycache__/__init__.cpython-312.pyc +0 -0
  85. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/__pycache__/__init__.cpython-39.pyc +0 -0
  86. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/__pycache__/conftest.cpython-312-pytest-7.4.4.pyc +0 -0
  87. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/__pycache__/conftest.cpython-312-pytest-9.0.3.pyc +0 -0
  88. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/__pycache__/conftest.cpython-39-pytest-8.3.4.pyc +0 -0
  89. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/__pycache__/run_all.cpython-312.pyc +0 -0
  90. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/__pycache__/run_all.cpython-39.pyc +0 -0
  91. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/__pycache__/test_parallel_runner.cpython-312-pytest-7.4.4.pyc +0 -0
  92. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/__pycache__/test_setup_module.cpython-312-pytest-7.4.4.pyc +0 -0
  93. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/conftest.py +0 -0
  94. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/demos/h5_filter_demo.py +0 -0
  95. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/demos/manual_report_example.py +0 -0
  96. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/demos/perf_parallel.py +0 -0
  97. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/e2e/__init__.py +0 -0
  98. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/e2e/__pycache__/__init__.cpython-312.pyc +0 -0
  99. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/e2e/__pycache__/__init__.cpython-39.pyc +0 -0
  100. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/e2e/__pycache__/test_user_flows.cpython-312-pytest-7.4.4.pyc +0 -0
  101. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/e2e/__pycache__/test_user_flows.cpython-312-pytest-9.0.3.pyc +0 -0
  102. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/e2e/__pycache__/test_user_flows.cpython-39-pytest-8.3.4.pyc +0 -0
  103. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/e2e/test_user_flows.py +0 -0
  104. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/fixtures/test_cases.json +0 -0
  105. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/fixtures/test_cases.yaml +0 -0
  106. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/fixtures/test_cases1.json +0 -0
  107. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/fixtures/test_with_setup.json +0 -0
  108. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/fixtures/test_with_setup.yaml +0 -0
  109. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/integration/__pycache__/test_real_command_execution.cpython-312-pytest-7.4.4.pyc +0 -0
  110. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/integration/__pycache__/test_real_command_execution.cpython-312-pytest-9.0.3.pyc +0 -0
  111. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/integration/file_compare/__pycache__/test_binary_compare.cpython-39-pytest-8.3.4.pyc +0 -0
  112. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/integration/file_compare/__pycache__/test_csv_compare.cpython-312-pytest-7.4.4.pyc +0 -0
  113. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/integration/file_compare/__pycache__/test_csv_compare.cpython-312-pytest-9.0.3.pyc +0 -0
  114. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/integration/file_compare/__pycache__/test_h5_compare.cpython-312-pytest-7.4.4.pyc +0 -0
  115. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/integration/file_compare/__pycache__/test_h5_compare.cpython-312-pytest-9.0.3.pyc +0 -0
  116. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/integration/file_compare/__pycache__/test_h5_compare.cpython-39-pytest-8.3.4.pyc +0 -0
  117. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/integration/file_compare/__pycache__/test_json_compare.cpython-312-pytest-7.4.4.pyc +0 -0
  118. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/integration/file_compare/__pycache__/test_json_compare.cpython-312-pytest-9.0.3.pyc +0 -0
  119. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/integration/file_compare/__pycache__/test_json_compare.cpython-39-pytest-8.3.4.pyc +0 -0
  120. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/integration/file_compare/__pycache__/test_text_compare.cpython-312-pytest-7.4.4.pyc +0 -0
  121. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/integration/file_compare/__pycache__/test_text_compare.cpython-312-pytest-9.0.3.pyc +0 -0
  122. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/integration/file_compare/__pycache__/test_text_compare.cpython-39-pytest-8.3.4.pyc +0 -0
  123. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/integration/file_compare/__pycache__/test_xml_compare.cpython-312-pytest-7.4.4.pyc +0 -0
  124. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/integration/file_compare/__pycache__/test_xml_compare.cpython-312-pytest-9.0.3.pyc +0 -0
  125. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/integration/file_compare/test_binary_compare.py +0 -0
  126. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/integration/file_compare/test_csv_compare.py +0 -0
  127. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/integration/file_compare/test_h5_compare.py +0 -0
  128. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/integration/file_compare/test_json_compare.py +0 -0
  129. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/integration/file_compare/test_text_compare.py +0 -0
  130. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/integration/file_compare/test_xml_compare.py +0 -0
  131. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/integration/parallel/__pycache__/test_parallel_runner.cpython-312-pytest-7.4.4.pyc +0 -0
  132. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/integration/parallel/__pycache__/test_parallel_runner.cpython-312-pytest-9.0.3.pyc +0 -0
  133. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/integration/parallel/__pycache__/test_parallel_runner.cpython-39-pytest-8.3.4.pyc +0 -0
  134. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/integration/parallel/test_parallel_runner.py +0 -0
  135. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/integration/path_handling/__pycache__/test_spaces_in_paths.cpython-312-pytest-7.4.4.pyc +0 -0
  136. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/integration/path_handling/__pycache__/test_spaces_in_paths.cpython-312-pytest-9.0.3.pyc +0 -0
  137. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/integration/path_handling/__pycache__/test_spaces_in_paths.cpython-39-pytest-8.3.4.pyc +0 -0
  138. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/integration/path_handling/test_spaces_in_paths.py +0 -0
  139. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/integration/test_history_feature.py +0 -0
  140. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/integration/test_real_command_execution.py +0 -0
  141. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/integration/test_sequence.py +0 -0
  142. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/run_all.py +0 -0
  143. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/test_report.txt +0 -0
  144. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/unit/__pycache__/test_h5_comparator.cpython-312-pytest-7.4.4.pyc +0 -0
  145. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/unit/__pycache__/test_h5_comparator.cpython-312-pytest-9.0.3.pyc +0 -0
  146. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/unit/__pycache__/test_run_all.cpython-312-pytest-9.0.3.pyc +0 -0
  147. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/unit/commands/__pycache__/test_compare_command.cpython-312-pytest-7.4.4.pyc +0 -0
  148. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/unit/commands/__pycache__/test_compare_command.cpython-312-pytest-9.0.3.pyc +0 -0
  149. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/unit/commands/test_compare_command.py +0 -0
  150. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/unit/core/__pycache__/test_history_store.cpython-312-pytest-7.4.4.pyc +0 -0
  151. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/unit/core/__pycache__/test_history_store.cpython-312-pytest-9.0.3.pyc +0 -0
  152. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/unit/core/__pycache__/test_setup.cpython-39-pytest-8.3.4.pyc +0 -0
  153. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/unit/core/test_assertions.py +0 -0
  154. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/unit/core/test_history_store.py +0 -0
  155. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/unit/core/test_process_worker.py +0 -0
  156. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/unit/core/test_setup.py +0 -0
  157. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/unit/runners/__pycache__/test_json_yaml_runner.cpython-39-pytest-8.3.4.pyc +0 -0
  158. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/unit/runners/test_json_yaml_runner.py +0 -0
  159. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/unit/runners/test_test_case_filter.py +0 -0
  160. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/unit/test_cli.py +0 -0
  161. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/unit/test_h5_comparator.py +0 -0
  162. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/unit/test_run_all.py +0 -0
  163. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/tests/unit/utils/test_junit_xml_writer.py +0 -0
  164. {cli_test_framework-0.8.0 → cli_test_framework-0.8.1}/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: 0.8.0
3
+ Version: 0.8.1
4
4
  Summary: A powerful command line testing framework in Python with setup modules, parallel execution, and file comparison capabilities.
5
5
  Home-page: https://github.com/ozil111/cli-test-framework
6
6
  Author: Xiaotong Wang
@@ -10,8 +10,11 @@
10
10
  - [顺序步骤测试](#顺序步骤测试)
11
11
  - [资源感知调度](#资源感知调度)
12
12
  - [历史记录与回归检测](#历史记录与回归检测)
13
+ - [JUnit XML 报告](#junit-xml-报告)
14
+ - [日志配置](#日志配置)
13
15
  - [文件比较](#文件比较)
14
16
  - [扩展开发](#扩展开发)
17
+ - [运行框架自带测试](#运行框架自带测试)
15
18
 
16
19
  ## 安装
17
20
 
@@ -27,6 +30,8 @@ YAML 支持需额外安装:
27
30
  pip install pyyaml
28
31
  ```
29
32
 
33
+ HDF5 文件比较依赖 `h5py`(已随框架安装)。如需在无 HDF5 环境下使用其他比较功能,可单独卸载,但 HDF5 比较将不可用。
34
+
30
35
  ## 测试用例定义
31
36
 
32
37
  ### JSON 格式
@@ -48,7 +53,14 @@ pip install pyyaml
48
53
  "expected": {
49
54
  "return_code": 0,
50
55
  "output_contains": ["Hello"],
51
- "output_matches": [".*regex.*"]
56
+ "output_matches": ".*regex.*",
57
+ "compare_files": [
58
+ {
59
+ "actual": "output.txt",
60
+ "baseline": "baseline.txt",
61
+ "type": "text"
62
+ }
63
+ ]
52
64
  }
53
65
  }
54
66
  ]
@@ -72,8 +84,11 @@ test_cases:
72
84
  return_code: 0
73
85
  output_contains:
74
86
  - "Hello"
75
- output_matches:
76
- - ".*regex.*"
87
+ output_matches: ".*regex.*"
88
+ compare_files:
89
+ - actual: output.txt
90
+ baseline: baseline.txt
91
+ type: text
77
92
  ```
78
93
 
79
94
  ### 字段说明
@@ -81,13 +96,48 @@ test_cases:
81
96
  | 字段 | 必填 | 说明 |
82
97
  |---|---|---|
83
98
  | `name` | 是 | 测试用例名称 |
84
- | `command` | 是 | 要执行的命令 |
99
+ | `command` | 是 | 要执行的命令(支持带参数的命令字符串,如 `python ./run.py`,框架会自动拆分并解析路径) |
85
100
  | `args` | 否 | 命令参数列表 |
101
+ | `description` | 否 | 测试用例描述 |
86
102
  | `timeout` | 否 | 超时秒数,默认 3600,设 `null` 无限制 |
87
103
  | `resources` | 否 | 资源配置,见[资源感知调度](#资源感知调度) |
88
104
  | `expected.return_code` | 否 | 期望返回码 |
89
105
  | `expected.output_contains` | 否 | 输出需包含的字符串列表 |
90
- | `expected.output_matches` | 否 | 输出需匹配的正则列表 |
106
+ | `expected.output_matches` | 否 | 输出需匹配的正则表达式(单个字符串) |
107
+ | `expected.compare_files` | 否 | 文件比较断言列表,见下文 |
108
+
109
+ ### 文件比较断言(compare_files)
110
+
111
+ 在 `expected` 中通过 `compare_files` 可声明一条或多条文件比较规则,框架会在命令执行后自动用对应的比较器对比实际产出文件与基线文件。所有比较通过才算用例通过,可与 `return_code`、`output_contains` 等断言共存。
112
+
113
+ 每个比较规则的字段:
114
+
115
+ | 字段 | 必填 | 说明 |
116
+ |---|---|---|
117
+ | `actual` | 是 | 测试命令产出的文件路径(相对路径按 workspace 解析) |
118
+ | `baseline` | 是 | 基线/参考文件路径(相对路径按 workspace 解析) |
119
+ | `type` | 否 | 比较器类型:`text`、`json`、`csv`、`xml`、`h5`、`binary`;省略时按扩展名自动识别 |
120
+ | 其他 | 否 | 透传给对应比较器的参数,如 `rtol`、`atol`、`encoding`、`tables`、`data_filter` 等 |
121
+
122
+ ```json
123
+ "expected": {
124
+ "return_code": 0,
125
+ "compare_files": [
126
+ {
127
+ "actual": "result.h5",
128
+ "baseline": "baseline/result.h5",
129
+ "type": "h5",
130
+ "rtol": 1e-5,
131
+ "atol": 1e-8,
132
+ "tables": ["/stress", "/displacement"]
133
+ },
134
+ {
135
+ "actual": "summary.csv",
136
+ "baseline": "baseline/summary.csv"
137
+ }
138
+ ]
139
+ }
140
+ ```
91
141
 
92
142
  ## 运行测试
93
143
 
@@ -126,6 +176,9 @@ cli-test run test_cases.json --history-dir ./hist
126
176
 
127
177
  # 自定义回归检测阈值(默认 1.5 倍)
128
178
  cli-test run test_cases.json --history-dir ./hist --regression-threshold 2.0
179
+
180
+ # 输出 JUnit XML 报告(可供 Jenkins/GitLab CI 等工具解析)
181
+ cli-test run test_cases.json --junit-xml report.xml
129
182
  ```
130
183
 
131
184
  ### Python API
@@ -146,7 +199,7 @@ success = runner.run_tests()
146
199
  # YAML 格式
147
200
  runner = YAMLRunner(config_file="test_cases.yaml")
148
201
 
149
- # 并行运行
202
+ # 并行运行(JSON)
150
203
  runner = ParallelJSONRunner(
151
204
  config_file="test_cases.json",
152
205
  max_workers=4, # 可选,默认 CPU 核心数
@@ -156,6 +209,15 @@ runner = ParallelJSONRunner(
156
209
  regression_threshold=2.0, # 可选,回归阈值倍数,默认 1.5
157
210
  )
158
211
  success = runner.run_tests()
212
+
213
+ # 并行运行(YAML)
214
+ from cli_test_framework.runners import ParallelYAMLRunner
215
+ runner = ParallelYAMLRunner(
216
+ config_file="test_cases.yaml",
217
+ max_workers=4,
218
+ execution_mode="thread",
219
+ )
220
+ success = runner.run_tests()
159
221
  ```
160
222
 
161
223
  ### 获取结果
@@ -164,7 +226,7 @@ success = runner.run_tests()
164
226
  runner.run_tests()
165
227
 
166
228
  # 汇总
167
- runner.results["total_tests"]
229
+ runner.results["total"]
168
230
  runner.results["passed"]
169
231
  runner.results["failed"]
170
232
 
@@ -405,17 +467,108 @@ success = runner.run_tests()
405
467
 
406
468
  不传 `--history-dir` 时行为与之前完全一致,不创建任何额外文件。
407
469
 
408
- ### 命令行工具 `compare-files`
470
+ ## JUnit XML 报告
471
+
472
+ 通过 `--junit-xml` 可在运行测试的同时输出 JUnit 格式的 XML 报告,兼容 Jenkins、GitLab CI、CircleCI 等 CI 工具。
473
+
474
+ ### CLI 用法
475
+
476
+ ```bash
477
+ cli-test run test_cases.json --junit-xml report.xml
478
+ ```
479
+
480
+ `--junit-xml` 是补充输出,与 `--output-format`(text/json/html)并存,不影响控制台报告。
481
+
482
+ ### Python API
483
+
484
+ ```python
485
+ from cli_test_framework import write_junit_xml
486
+
487
+ runner.run_tests()
488
+ write_junit_xml(runner.results, "report.xml", suite_name="my_suite")
489
+ ```
490
+
491
+ 状态映射:`passed` 记为通过;`failed`(断言失败)记为 failure;`timeout` 与执行错误记为 error。每个 testcase 元素附带命令输出与失败原因。
492
+
493
+ ## 日志配置
494
+
495
+ 框架所有诊断与状态信息都通过 Python 标准 `logging` 模块输出,统一挂在 `cli_test_framework` 命名空间下。日志默认写入 **stderr**,因此 `stdout` 始终保持干净,可安全配合 `--output-format json` 做机器可读输出。
496
+
497
+ ### 命令行控制日志级别
498
+
499
+ `run` 与 `compare` 子命令均支持:
500
+
501
+ | 选项 | 说明 |
502
+ |---|---|
503
+ | `--verbose` / `-v` | 详细输出,日志级别提升至 DEBUG |
504
+ | `--debug` | 调试模式,同样提升至 DEBUG,并在出错时打印完整堆栈 |
505
+
506
+ 默认级别为 INFO,仅显示关键进度与错误;加 `--verbose` 或 `--debug` 后会输出命令输出、调度细节等 DEBUG 级信息。
409
507
 
410
508
  ```bash
509
+ # 详细模式(含命令输出等 DEBUG 信息)
510
+ cli-test run test_cases.json --verbose
511
+
512
+ # 调试模式(出错时打印堆栈)
513
+ cli-test run test_cases.json --debug
514
+ ```
515
+
516
+ ### 库使用方式
517
+
518
+ 作为库被 import 时,框架默认只挂载 `NullHandler`,不产生任何输出(符合库的礼貌日志规范)。需要看到日志时,调用 `setup_console_logging()` 启用控制台输出:
519
+
520
+ ```python
521
+ import logging
522
+ from cli_test_framework.logging_config import setup_console_logging, get_logger
523
+
524
+ # 启用控制台日志(stderr),可指定级别
525
+ setup_console_logging(level=logging.DEBUG)
526
+
527
+ logger = get_logger(__name__) # 自动归入 cli_test_framework 命名空间
528
+ logger.info("自定义日志信息")
529
+ ```
530
+
531
+ ### 输出到日志文件
532
+
533
+ 框架未内置 `--log-file` 选项,但可借助 Python 标准 `logging` 自行为 `cli_test_framework` logger 添加文件处理器:
534
+
535
+ ```python
536
+ import logging
537
+ from cli_test_framework.logging_config import get_logger
538
+
539
+ file_handler = logging.FileHandler("run.log", encoding="utf-8")
540
+ file_handler.setLevel(logging.DEBUG)
541
+ file_handler.setFormatter(
542
+ logging.Formatter("%(asctime)s %(levelname)-7s %(name)s %(message)s")
543
+ )
544
+
545
+ # 给框架根 logger 加文件处理器,所有子 logger 都会继承
546
+ logging.getLogger("cli_test_framework").addHandler(file_handler)
547
+ ```
548
+
549
+ 上述代码既适用于库调用,也可放在脚本中配合 `cli-test` 一起使用。控制台与文件处理器可并存。
550
+
551
+ ## 文件比较
552
+
553
+ 框架提供独立的文件比较能力,支持文本、JSON、CSV、XML、HDF5、二进制等多种格式。既可通过命令行工具使用,也可在测试用例的 `expected.compare_files` 中自动调用(见[文件比较断言](#文件比较断言compare_files))。
554
+
555
+ ### 命令行工具
556
+
557
+ 有两种等价的调用方式,参数完全一致:
558
+
559
+ ```bash
560
+ # 独立命令
411
561
  compare-files <file1> <file2> [选项]
562
+
563
+ # cli-test 子命令
564
+ cli-test compare <file1> <file2> [选项]
412
565
  ```
413
566
 
414
567
  ### 通用选项
415
568
 
416
569
  | 选项 | 说明 |
417
570
  |---|---|
418
- | `--file-type` | 文件类型:`auto`(默认)、`text`、`json`、`h5`、`binary` |
571
+ | `--file-type` | 文件类型:`auto`(默认)、`text`、`json`、`csv`、`xml`、`h5`、`binary` |
419
572
  | `--start-line` | 起始行号(1-based),默认 1 |
420
573
  | `--end-line` | 结束行号(1-based) |
421
574
  | `--start-column` | 起始列号(1-based),默认 1 |
@@ -447,6 +600,40 @@ compare-files data1.json data2.json --json-compare-mode key-based --json-key-fie
447
600
  | `--json-compare-mode` | `exact`(默认)或 `key-based` |
448
601
  | `--json-key-field` | key-based 模式的匹配字段,支持逗号分隔多字段 |
449
602
 
603
+ ### CSV 文件比较
604
+
605
+ ```bash
606
+ # 基本比较
607
+ compare-files data1.csv data2.csv
608
+
609
+ # 自定义分隔符与数值容差
610
+ compare-files data1.csv data2.csv --csv-delimiter ';' --csv-rtol 1e-4 --csv-atol 1e-6
611
+
612
+ # TSV 文件(自动识别为 csv 类型)
613
+ compare-files data1.tsv data2.tsv
614
+ ```
615
+
616
+ | 选项 | 说明 |
617
+ |---|---|
618
+ | `--csv-rtol` | 数值相对容差,默认 1e-5 |
619
+ | `--csv-atol` | 数值绝对容差,默认 1e-8 |
620
+ | `--csv-delimiter` | 字段分隔符,默认 `,` |
621
+ | `--csv-quotechar` | 引用字符,默认 `"` |
622
+
623
+ CSV 比较按行列结构逐单元格比对;数值单元格在容差范围内视为相等。差异报告包含行数、列数不匹配与单元格不一致,最多列出 10 条。
624
+
625
+ ### XML 文件比较
626
+
627
+ ```bash
628
+ # 结构化比较(标签、属性、文本、子元素)
629
+ compare-files config1.xml config2.xml
630
+
631
+ # HTML 文件(自动识别为 xml 类型)
632
+ compare-files page1.html page2.html
633
+ ```
634
+
635
+ XML 比较按 DOM 结构递归比对标签、属性、文本内容与子元素数量。差异报告定位到具体路径(如 `/root/item[0]/@id`),最多列出 10 条。
636
+
450
637
  ### HDF5 文件比较
451
638
 
452
639
  ```bash
@@ -506,6 +693,14 @@ result = comparator.compare_files("file1.txt", "file2.txt")
506
693
  comparator = ComparatorFactory.create_comparator("json", compare_mode="key-based", key_field="id")
507
694
  result = comparator.compare_files("data1.json", "data2.json")
508
695
 
696
+ # CSV 比较
697
+ comparator = ComparatorFactory.create_comparator("csv", rtol=1e-5, atol=1e-8, delimiter=",")
698
+ result = comparator.compare_files("data1.csv", "data2.csv")
699
+
700
+ # XML 比较
701
+ comparator = ComparatorFactory.create_comparator("xml", encoding="utf-8")
702
+ result = comparator.compare_files("config1.xml", "config2.xml")
703
+
509
704
  # HDF5 比较
510
705
  comparator = ComparatorFactory.create_comparator("h5", tables=["table1"], rtol=1e-5)
511
706
  result = comparator.compare_files("data1.h5", "data2.h5")
@@ -520,7 +715,7 @@ result.differences # list
520
715
  ### 自定义 Runner
521
716
 
522
717
  ```python
523
- from cli_test_framework.runners import BaseRunner
718
+ from cli_test_framework.core.base_runner import BaseRunner
524
719
 
525
720
  class CustomRunner(BaseRunner):
526
721
  def load_test_cases(self):
@@ -546,13 +741,81 @@ class MySetup(BaseSetup):
546
741
  pass
547
742
  ```
548
743
 
549
- ### 自定义断言
744
+ ### 自定义文件比较器
745
+
746
+ `ComparatorFactory` 会在首次使用时自动扫描 `file_comparator` 包内所有 `*_comparator.py` 模块并注册其中的 `*Comparator` 类。如需注册自定义比较器,调用 `register_comparator` 即可:
550
747
 
551
748
  ```python
552
- from cli_test_framework.assertions import BaseAssertion
749
+ from cli_test_framework.file_comparator import ComparatorFactory
750
+ from cli_test_framework.file_comparator.base_comparator import BaseComparator
553
751
 
554
- class CustomAssertion(BaseAssertion):
555
- def assert_custom_condition(self, actual, expected):
556
- if not self._check(actual, expected):
557
- raise AssertionError("Condition not met")
752
+ class FooComparator(BaseComparator):
753
+ # 实现 read_content / compare_content 等方法
754
+ pass
755
+
756
+ ComparatorFactory.register_comparator("foo", FooComparator)
757
+
758
+ # 之后即可在 compare_files 断言或命令行 --file-type foo 中使用
759
+ comparator = ComparatorFactory.create_comparator("foo")
760
+ ```
761
+
762
+ ### 断言与文件比较
763
+
764
+ `Assertions` 类提供静态断言方法,`expected` 中的校验均由其完成:
765
+
766
+ ```python
767
+ from cli_test_framework.core.assertions import Assertions
768
+
769
+ Assertions.return_code_equals(actual_code, 0)
770
+ Assertions.contains(output, "expected text")
771
+ Assertions.matches(output, r".*regex.*")
772
+ Assertions.compare_files("actual.txt", "baseline.txt", file_type="text", workspace="/ws")
773
+ ```
774
+
775
+ `compare_files` 会自动按扩展名识别类型(`.h5/.hdf5/.hdf`→h5、`.json`→json、`.csv/.tsv`→csv、`.xml/.html/.htm`→xml、`.txt/.log/.out/.py`→text、其余→binary),相对路径按 `workspace` 解析,额外参数透传给比较器。
776
+
777
+ ## 运行框架自带测试
778
+
779
+ 项目自带统一测试入口 `tests/run_all.py`,通过 `--scope` 选择测试范围(test target),并可用 `--extra` 透传任意 pytest 参数。
780
+
781
+ ### 测试范围
782
+
783
+ | scope | 说明 | 对应目录 |
784
+ |---|---|---|
785
+ | `unit` | 单元测试(core、runners 等) | `tests/unit` |
786
+ | `integration` | 集成测试(文件比较、并行、路径处理等) | `tests/integration` |
787
+ | `e2e` | 端到端测试(用户流程) | `tests/e2e` |
788
+ | `all` | 运行上述全部范围(默认) | 三者合集 |
789
+
790
+ > 注:`tests/demos/` 下的脚本为手动/交互演示,不纳入 scope 运行,需单独执行。
791
+
792
+ ### 用法
793
+
794
+ ```bash
795
+ # 运行全部测试(默认)
796
+ python tests/run_all.py
797
+
798
+ # 只运行单元测试
799
+ python tests/run_all.py --scope unit
800
+
801
+ # 只运行集成测试
802
+ python tests/run_all.py --scope integration
803
+
804
+ # 只运行端到端测试
805
+ python tests/run_all.py --scope e2e
806
+
807
+ # 透传 pytest 参数,例如按关键字过滤
808
+ python tests/run_all.py --scope integration --extra "-k h5"
809
+
810
+ # 透传多个 pytest 参数
811
+ python tests/run_all.py --scope unit --extra "-v -k assertions"
812
+ ```
813
+
814
+ `--extra` 接收的字符串会经 `shlex` 拆分后追加到 pytest 命令行。脚本通过当前解释器(`sys.executable -m pytest`)调用 pytest,确保使用激活的环境而非 PATH 中首个 `pytest`。
815
+
816
+ 测试环境需先激活 conda 环境:
817
+
818
+ ```bash
819
+ conda activate xiaotong
820
+ python tests/run_all.py
558
821
  ```
@@ -8,7 +8,7 @@ with open(os.path.join(this_directory, 'README.md'), encoding='utf-8') as f:
8
8
 
9
9
  setup(
10
10
  name="cli-test-framework",
11
- version="0.8.0",
11
+ version="0.8.1",
12
12
  author="Xiaotong Wang",
13
13
  author_email="xiaotongwang98@gmail.com",
14
14
  description="A powerful command line testing framework in Python with setup modules, parallel execution, and file comparison capabilities.",
@@ -118,6 +118,7 @@ def execute_single_test_case(case: TestCaseData, workspace: Optional[str] = None
118
118
  stdout=subprocess.PIPE,
119
119
  stderr=subprocess.PIPE,
120
120
  text=True,
121
+ errors="replace",
121
122
  start_new_session=True,
122
123
  env=current_env,
123
124
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cli-test-framework
3
- Version: 0.8.0
3
+ Version: 0.8.1
4
4
  Summary: A powerful command line testing framework in Python with setup modules, parallel execution, and file comparison capabilities.
5
5
  Home-page: https://github.com/ozil111/cli-test-framework
6
6
  Author: Xiaotong Wang
@@ -130,10 +130,13 @@ tests/unit/commands/test_compare_command.py
130
130
  tests/unit/commands/__pycache__/test_compare_command.cpython-312-pytest-7.4.4.pyc
131
131
  tests/unit/commands/__pycache__/test_compare_command.cpython-312-pytest-9.0.3.pyc
132
132
  tests/unit/core/test_assertions.py
133
+ tests/unit/core/test_execution.py
133
134
  tests/unit/core/test_history_store.py
134
135
  tests/unit/core/test_process_worker.py
135
136
  tests/unit/core/test_setup.py
137
+ tests/unit/core/__pycache__/test_assertions.cpython-312-pytest-7.4.4.pyc
136
138
  tests/unit/core/__pycache__/test_assertions.cpython-312-pytest-9.0.3.pyc
139
+ tests/unit/core/__pycache__/test_execution.cpython-312-pytest-9.0.3.pyc
137
140
  tests/unit/core/__pycache__/test_history_store.cpython-312-pytest-7.4.4.pyc
138
141
  tests/unit/core/__pycache__/test_history_store.cpython-312-pytest-9.0.3.pyc
139
142
  tests/unit/core/__pycache__/test_process_worker.cpython-312-pytest-7.4.4.pyc
@@ -150,6 +153,7 @@ tests/unit/runners/__pycache__/test_test_case_filter.cpython-312-pytest-7.4.4.py
150
153
  tests/unit/runners/__pycache__/test_test_case_filter.cpython-312-pytest-9.0.3.pyc
151
154
  tests/unit/utils/test_junit_xml_writer.py
152
155
  tests/unit/utils/test_report_generator.py
156
+ tests/unit/utils/__pycache__/test_junit_xml_writer.cpython-312-pytest-7.4.4.pyc
153
157
  tests/unit/utils/__pycache__/test_junit_xml_writer.cpython-312-pytest-9.0.3.pyc
154
158
  tests/unit/utils/__pycache__/test_report_generator.cpython-312-pytest-7.4.4.pyc
155
159
  tests/unit/utils/__pycache__/test_report_generator.cpython-312-pytest-9.0.3.pyc
@@ -0,0 +1,70 @@
1
+ """Tests for cli_test_framework.core.execution.
2
+
3
+ Covers the ``errors="replace"`` behaviour: when a subprocess emits bytes that
4
+ cannot be decoded by the platform's default encoding, execution must not crash
5
+ with ``UnicodeDecodeError``; the offending bytes are replaced and the legal
6
+ portions of the output are preserved.
7
+ """
8
+ import sys
9
+
10
+ from cli_test_framework.core.execution import execute_single_test_case
11
+
12
+
13
+ def _run(command: str, args, expected=None, workspace=None):
14
+ case = {
15
+ "name": "undecodable-output",
16
+ "command": command,
17
+ "args": list(args),
18
+ "expected": expected or {"return_code": 0},
19
+ }
20
+ return execute_single_test_case(case, workspace=workspace)
21
+
22
+
23
+ def test_undecodable_output_bytes_do_not_crash(tmp_path):
24
+ """Subprocess output containing bytes illegal under the default encoding
25
+ must not raise UnicodeDecodeError; execution should still succeed and the
26
+ decodable parts of the output must be preserved.
27
+
28
+ ``0x80`` is illegal under both UTF-8 and GBK/cp936 (the two encodings most
29
+ commonly implicated in the original crash), so this test is meaningful on
30
+ both Linux CI and Chinese-Windows environments. Note: bytes like ``0x88``
31
+ are *not* suitable because under GBK they act as a valid lead byte and
32
+ consume the following byte, corrupting the suffix.
33
+ """
34
+ # Write raw bytes: ASCII prefix, an illegal byte, ASCII suffix.
35
+ script = (
36
+ "import sys; "
37
+ "sys.stdout.buffer.write(b'ok-before\\x80ok-after'); "
38
+ "sys.stdout.buffer.flush()"
39
+ )
40
+ result = _run(sys.executable, ["-c", script], workspace=str(tmp_path))
41
+
42
+ assert result["status"] == "passed"
43
+ assert result["return_code"] == 0
44
+ # No execution error leaked into the message.
45
+ assert "UnicodeDecodeError" not in result["message"]
46
+ # The decodable portions survive.
47
+ assert "ok-before" in result["output"]
48
+ assert "ok-after" in result["output"]
49
+
50
+
51
+ def test_undecodable_bytes_still_allow_output_contains_assertion(tmp_path):
52
+ """Even with garbage bytes mixed in, a valid ``output_contains`` assertion
53
+ against the decodable part should pass — proving the output stream is usable
54
+ for validation after the fix.
55
+ """
56
+ script = (
57
+ "import sys; "
58
+ "sys.stdout.buffer.write(b'DONE\\xff\\xfe'); "
59
+ "sys.stdout.buffer.flush()"
60
+ )
61
+ result = _run(
62
+ sys.executable,
63
+ ["-c", script],
64
+ expected={"return_code": 0, "output_contains": ["DONE"]},
65
+ workspace=str(tmp_path),
66
+ )
67
+
68
+ assert result["status"] == "passed"
69
+ assert "DONE" in result["output"]
70
+ assert "UnicodeDecodeError" not in result["message"]