cli-test-framework 0.9.0__tar.gz → 0.9.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 (167) hide show
  1. {cli_test_framework-0.9.0/src/cli_test_framework.egg-info → cli_test_framework-0.9.1}/PKG-INFO +1 -1
  2. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/docs/user_manual.md +94 -0
  3. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/setup.py +1 -1
  4. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/src/cli_test_framework/cli.py +23 -0
  5. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/src/cli_test_framework/core/config_loader.py +35 -0
  6. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/src/cli_test_framework/runners/config_runner.py +5 -1
  7. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/src/cli_test_framework/runners/parallel_config_runner.py +5 -1
  8. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1/src/cli_test_framework.egg-info}/PKG-INFO +1 -1
  9. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/src/cli_test_framework.egg-info/SOURCES.txt +3 -0
  10. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/unit/__pycache__/test_cli.cpython-312-pytest-9.0.3.pyc +0 -0
  11. cli_test_framework-0.9.1/tests/unit/core/__pycache__/test_config_loader.cpython-312-pytest-9.0.3.pyc +0 -0
  12. cli_test_framework-0.9.1/tests/unit/core/test_config_loader.py +136 -0
  13. cli_test_framework-0.9.1/tests/unit/runners/__pycache__/test_json_yaml_runner.cpython-312-pytest-9.0.3.pyc +0 -0
  14. cli_test_framework-0.9.1/tests/unit/runners/__pycache__/test_json_yaml_runner.cpython-312.pyc +0 -0
  15. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/unit/runners/__pycache__/test_test_case_tag_filter.cpython-312-pytest-9.0.3.pyc +0 -0
  16. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/unit/runners/test_json_yaml_runner.py +50 -0
  17. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/unit/test_cli.py +46 -0
  18. cli_test_framework-0.9.0/tests/unit/runners/__pycache__/test_json_yaml_runner.cpython-312-pytest-9.0.3.pyc +0 -0
  19. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/MANIFEST.in +0 -0
  20. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/README.md +0 -0
  21. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/docs/design.md +0 -0
  22. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/docs/design_en.md +0 -0
  23. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/docs/user_manual_en.md +0 -0
  24. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/pyproject.toml +0 -0
  25. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/setup.cfg +0 -0
  26. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/src/cli_test_framework/__init__.py +0 -0
  27. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/src/cli_test_framework/commands/__init__.py +0 -0
  28. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/src/cli_test_framework/commands/compare.py +0 -0
  29. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/src/cli_test_framework/core/__init__.py +0 -0
  30. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/src/cli_test_framework/core/assertions.py +0 -0
  31. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/src/cli_test_framework/core/base_runner.py +0 -0
  32. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/src/cli_test_framework/core/execution.py +0 -0
  33. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/src/cli_test_framework/core/history_store.py +0 -0
  34. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/src/cli_test_framework/core/parallel_runner.py +0 -0
  35. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/src/cli_test_framework/core/process_worker.py +0 -0
  36. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/src/cli_test_framework/core/setup.py +0 -0
  37. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/src/cli_test_framework/core/test_case.py +0 -0
  38. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/src/cli_test_framework/core/types.py +0 -0
  39. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/src/cli_test_framework/file_comparator/__init__.py +0 -0
  40. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/src/cli_test_framework/file_comparator/base_comparator.py +0 -0
  41. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/src/cli_test_framework/file_comparator/binary_comparator.py +0 -0
  42. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/src/cli_test_framework/file_comparator/csv_comparator.py +0 -0
  43. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/src/cli_test_framework/file_comparator/factory.py +0 -0
  44. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/src/cli_test_framework/file_comparator/h5_comparator.py +0 -0
  45. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/src/cli_test_framework/file_comparator/json_comparator.py +0 -0
  46. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/src/cli_test_framework/file_comparator/result.py +0 -0
  47. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/src/cli_test_framework/file_comparator/text_comparator.py +0 -0
  48. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/src/cli_test_framework/file_comparator/xml_comparator.py +0 -0
  49. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/src/cli_test_framework/logging_config.py +0 -0
  50. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/src/cli_test_framework/runners/__init__.py +0 -0
  51. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/src/cli_test_framework/runners/json_runner.py +0 -0
  52. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/src/cli_test_framework/runners/parallel_json_runner.py +0 -0
  53. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/src/cli_test_framework/runners/parallel_yaml_runner.py +0 -0
  54. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/src/cli_test_framework/runners/yaml_runner.py +0 -0
  55. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/src/cli_test_framework/utils/__init__.py +0 -0
  56. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/src/cli_test_framework/utils/junit_xml_writer.py +0 -0
  57. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/src/cli_test_framework/utils/path_resolver.py +0 -0
  58. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/src/cli_test_framework/utils/report_generator.py +0 -0
  59. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/src/cli_test_framework.egg-info/dependency_links.txt +0 -0
  60. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/src/cli_test_framework.egg-info/entry_points.txt +0 -0
  61. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/src/cli_test_framework.egg-info/requires.txt +0 -0
  62. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/src/cli_test_framework.egg-info/top_level.txt +0 -0
  63. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/README.md +0 -0
  64. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/__init__.py +0 -0
  65. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/__pycache__/__init__.cpython-312.pyc +0 -0
  66. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/__pycache__/__init__.cpython-39.pyc +0 -0
  67. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/__pycache__/conftest.cpython-312-pytest-7.4.4.pyc +0 -0
  68. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/__pycache__/conftest.cpython-312-pytest-9.0.3.pyc +0 -0
  69. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/__pycache__/conftest.cpython-39-pytest-8.3.4.pyc +0 -0
  70. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/__pycache__/run_all.cpython-312.pyc +0 -0
  71. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/__pycache__/run_all.cpython-39.pyc +0 -0
  72. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/__pycache__/test_parallel_runner.cpython-312-pytest-7.4.4.pyc +0 -0
  73. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/__pycache__/test_setup_module.cpython-312-pytest-7.4.4.pyc +0 -0
  74. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/conftest.py +0 -0
  75. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/demos/h5_filter_demo.py +0 -0
  76. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/demos/manual_report_example.py +0 -0
  77. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/demos/perf_parallel.py +0 -0
  78. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/e2e/__init__.py +0 -0
  79. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/e2e/__pycache__/__init__.cpython-312.pyc +0 -0
  80. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/e2e/__pycache__/__init__.cpython-39.pyc +0 -0
  81. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/e2e/__pycache__/test_user_flows.cpython-312-pytest-7.4.4.pyc +0 -0
  82. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/e2e/__pycache__/test_user_flows.cpython-312-pytest-9.0.3.pyc +0 -0
  83. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/e2e/__pycache__/test_user_flows.cpython-39-pytest-8.3.4.pyc +0 -0
  84. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/e2e/test_user_flows.py +0 -0
  85. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/fixtures/test_cases.json +0 -0
  86. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/fixtures/test_cases.yaml +0 -0
  87. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/fixtures/test_cases1.json +0 -0
  88. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/fixtures/test_with_setup.json +0 -0
  89. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/fixtures/test_with_setup.yaml +0 -0
  90. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/integration/__pycache__/test_history_feature.cpython-312-pytest-7.4.4.pyc +0 -0
  91. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/integration/__pycache__/test_history_feature.cpython-312-pytest-9.0.3.pyc +0 -0
  92. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/integration/__pycache__/test_real_command_execution.cpython-312-pytest-7.4.4.pyc +0 -0
  93. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/integration/__pycache__/test_real_command_execution.cpython-312-pytest-9.0.3.pyc +0 -0
  94. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/integration/__pycache__/test_sequence.cpython-312-pytest-7.4.4.pyc +0 -0
  95. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/integration/__pycache__/test_sequence.cpython-312-pytest-9.0.3.pyc +0 -0
  96. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/integration/file_compare/__pycache__/test_binary_compare.cpython-312-pytest-7.4.4.pyc +0 -0
  97. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/integration/file_compare/__pycache__/test_binary_compare.cpython-312-pytest-9.0.3.pyc +0 -0
  98. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/integration/file_compare/__pycache__/test_binary_compare.cpython-39-pytest-8.3.4.pyc +0 -0
  99. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/integration/file_compare/__pycache__/test_csv_compare.cpython-312-pytest-7.4.4.pyc +0 -0
  100. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/integration/file_compare/__pycache__/test_csv_compare.cpython-312-pytest-9.0.3.pyc +0 -0
  101. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/integration/file_compare/__pycache__/test_h5_compare.cpython-312-pytest-7.4.4.pyc +0 -0
  102. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/integration/file_compare/__pycache__/test_h5_compare.cpython-312-pytest-9.0.3.pyc +0 -0
  103. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/integration/file_compare/__pycache__/test_h5_compare.cpython-39-pytest-8.3.4.pyc +0 -0
  104. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/integration/file_compare/__pycache__/test_json_compare.cpython-312-pytest-7.4.4.pyc +0 -0
  105. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/integration/file_compare/__pycache__/test_json_compare.cpython-312-pytest-9.0.3.pyc +0 -0
  106. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/integration/file_compare/__pycache__/test_json_compare.cpython-39-pytest-8.3.4.pyc +0 -0
  107. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/integration/file_compare/__pycache__/test_text_compare.cpython-312-pytest-7.4.4.pyc +0 -0
  108. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/integration/file_compare/__pycache__/test_text_compare.cpython-312-pytest-9.0.3.pyc +0 -0
  109. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/integration/file_compare/__pycache__/test_text_compare.cpython-39-pytest-8.3.4.pyc +0 -0
  110. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/integration/file_compare/__pycache__/test_xml_compare.cpython-312-pytest-7.4.4.pyc +0 -0
  111. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/integration/file_compare/__pycache__/test_xml_compare.cpython-312-pytest-9.0.3.pyc +0 -0
  112. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/integration/file_compare/test_binary_compare.py +0 -0
  113. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/integration/file_compare/test_csv_compare.py +0 -0
  114. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/integration/file_compare/test_h5_compare.py +0 -0
  115. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/integration/file_compare/test_json_compare.py +0 -0
  116. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/integration/file_compare/test_text_compare.py +0 -0
  117. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/integration/file_compare/test_xml_compare.py +0 -0
  118. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/integration/parallel/__pycache__/test_parallel_runner.cpython-312-pytest-7.4.4.pyc +0 -0
  119. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/integration/parallel/__pycache__/test_parallel_runner.cpython-312-pytest-9.0.3.pyc +0 -0
  120. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/integration/parallel/__pycache__/test_parallel_runner.cpython-39-pytest-8.3.4.pyc +0 -0
  121. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/integration/parallel/test_parallel_runner.py +0 -0
  122. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/integration/path_handling/__pycache__/test_spaces_in_paths.cpython-312-pytest-7.4.4.pyc +0 -0
  123. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/integration/path_handling/__pycache__/test_spaces_in_paths.cpython-312-pytest-9.0.3.pyc +0 -0
  124. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/integration/path_handling/__pycache__/test_spaces_in_paths.cpython-39-pytest-8.3.4.pyc +0 -0
  125. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/integration/path_handling/test_spaces_in_paths.py +0 -0
  126. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/integration/test_history_feature.py +0 -0
  127. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/integration/test_real_command_execution.py +0 -0
  128. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/integration/test_sequence.py +0 -0
  129. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/run_all.py +0 -0
  130. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/test_report.txt +0 -0
  131. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/unit/__pycache__/test_cli.cpython-312-pytest-7.4.4.pyc +0 -0
  132. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/unit/__pycache__/test_h5_comparator.cpython-312-pytest-7.4.4.pyc +0 -0
  133. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/unit/__pycache__/test_h5_comparator.cpython-312-pytest-9.0.3.pyc +0 -0
  134. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/unit/__pycache__/test_run_all.cpython-312-pytest-7.4.4.pyc +0 -0
  135. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/unit/__pycache__/test_run_all.cpython-312-pytest-9.0.3.pyc +0 -0
  136. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/unit/commands/__pycache__/test_compare_command.cpython-312-pytest-7.4.4.pyc +0 -0
  137. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/unit/commands/__pycache__/test_compare_command.cpython-312-pytest-9.0.3.pyc +0 -0
  138. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/unit/commands/test_compare_command.py +0 -0
  139. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/unit/core/__pycache__/test_assertions.cpython-312-pytest-7.4.4.pyc +0 -0
  140. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/unit/core/__pycache__/test_assertions.cpython-312-pytest-9.0.3.pyc +0 -0
  141. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/unit/core/__pycache__/test_execution.cpython-312-pytest-9.0.3.pyc +0 -0
  142. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/unit/core/__pycache__/test_history_store.cpython-312-pytest-7.4.4.pyc +0 -0
  143. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/unit/core/__pycache__/test_history_store.cpython-312-pytest-9.0.3.pyc +0 -0
  144. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/unit/core/__pycache__/test_process_worker.cpython-312-pytest-7.4.4.pyc +0 -0
  145. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/unit/core/__pycache__/test_process_worker.cpython-312-pytest-9.0.3.pyc +0 -0
  146. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/unit/core/__pycache__/test_setup.cpython-312-pytest-7.4.4.pyc +0 -0
  147. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/unit/core/__pycache__/test_setup.cpython-312-pytest-9.0.3.pyc +0 -0
  148. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/unit/core/__pycache__/test_setup.cpython-39-pytest-8.3.4.pyc +0 -0
  149. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/unit/core/test_assertions.py +0 -0
  150. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/unit/core/test_execution.py +0 -0
  151. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/unit/core/test_history_store.py +0 -0
  152. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/unit/core/test_process_worker.py +0 -0
  153. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/unit/core/test_setup.py +0 -0
  154. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/unit/runners/__pycache__/test_json_yaml_runner.cpython-312-pytest-7.4.4.pyc +0 -0
  155. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/unit/runners/__pycache__/test_json_yaml_runner.cpython-39-pytest-8.3.4.pyc +0 -0
  156. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/unit/runners/__pycache__/test_test_case_filter.cpython-312-pytest-7.4.4.pyc +0 -0
  157. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/unit/runners/__pycache__/test_test_case_filter.cpython-312-pytest-9.0.3.pyc +0 -0
  158. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/unit/runners/test_test_case_filter.py +0 -0
  159. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/unit/runners/test_test_case_tag_filter.py +0 -0
  160. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/unit/test_h5_comparator.py +0 -0
  161. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/unit/test_run_all.py +0 -0
  162. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/unit/utils/__pycache__/test_junit_xml_writer.cpython-312-pytest-7.4.4.pyc +0 -0
  163. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/unit/utils/__pycache__/test_junit_xml_writer.cpython-312-pytest-9.0.3.pyc +0 -0
  164. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/unit/utils/__pycache__/test_report_generator.cpython-312-pytest-7.4.4.pyc +0 -0
  165. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/unit/utils/__pycache__/test_report_generator.cpython-312-pytest-9.0.3.pyc +0 -0
  166. {cli_test_framework-0.9.0 → cli_test_framework-0.9.1}/tests/unit/utils/test_junit_xml_writer.py +0 -0
  167. {cli_test_framework-0.9.0 → cli_test_framework-0.9.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.9.0
3
+ Version: 0.9.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
@@ -5,6 +5,7 @@
5
5
  - [安装](#安装)
6
6
  - [测试用例定义](#测试用例定义)
7
7
  - [运行测试](#运行测试)
8
+ - [占位符(变量替换)](#占位符变量替换)
8
9
  - [标签过滤](#标签过滤)
9
10
  - [Setup 模块](#setup-模块)
10
11
  - [并行测试](#并行测试)
@@ -245,6 +246,99 @@ for detail in runner.results["details"]:
245
246
  print(detail["name"], detail["status"], detail.get("message", ""))
246
247
  ```
247
248
 
249
+ ## 占位符(变量替换)
250
+
251
+ 当同一个配置文件需要在不同环境下使用不同参数(如求解器路径、模型文件路径等)时,可以用 `{变量名}` 占位符编写配置,运行时通过 `--var` 或 `variables` 参数传入实际值。
252
+
253
+ ### 编写含占位符的配置
254
+
255
+ JSON:
256
+
257
+ ```json
258
+ {
259
+ "test_cases": [
260
+ {
261
+ "name": "求解器测试",
262
+ "command": "{solver}",
263
+ "args": ["--input", "{model}", "--output", "{output}"],
264
+ "expected": { "return_code": 0 }
265
+ }
266
+ ]
267
+ }
268
+ ```
269
+
270
+ YAML:
271
+
272
+ ```yaml
273
+ test_cases:
274
+ - name: 求解器测试
275
+ command: "{solver}"
276
+ args: ["--input", "{model}", "--output", "{output}"]
277
+ expected:
278
+ return_code: 0
279
+ ```
280
+
281
+ 占位符 `{变量名}` 可出现在配置文件的任意字符串值中,包括 `command`、`args`、`name`、`expected.output_contains` 等。支持同一个字符串中使用多个占位符,如 `"{solver} --input {model}"`。
282
+
283
+ > **安全设计**:只有 `variables` 字典中存在的 key 才会被替换。`{xxx}` 若无匹配不会报错,而是原样保留。因此 `expected.output_matches` 中的正则模式(如 `{2,}`、`\d{4}`)不受影响。
284
+
285
+ ### 用法
286
+
287
+ #### CLI
288
+
289
+ ```bash
290
+ # 单个变量
291
+ cli-test run test_cases.json --var solver=/opt/solver/bin/solver.exe
292
+
293
+ # 多个变量
294
+ cli-test run test_cases.json --var solver=/opt/solver/bin/solver.exe --var model=./data/model.dat
295
+
296
+ # 与并行模式、标签过滤等组合使用
297
+ cli-test run test_cases.json --var solver=solver.exe --parallel --workers 4 --tag smoke
298
+ ```
299
+
300
+ `--var` 格式为 `KEY=VALUE`,可多次使用。等号分隔,key 和 value 两侧的空格会被自动去除。
301
+
302
+ #### Python API
303
+
304
+ ```python
305
+ from cli_test_framework.runners import JSONRunner, YAMLRunner, ParallelJSONRunner, ParallelYAMLRunner
306
+
307
+ # 顺序运行
308
+ runner = JSONRunner(
309
+ config_file="test_cases.json",
310
+ variables={
311
+ "solver": "/opt/solver/bin/solver.exe",
312
+ "model": "./data/model.dat",
313
+ "output": "./results/output.dat",
314
+ },
315
+ )
316
+ success = runner.run_tests()
317
+
318
+ # 并行运行
319
+ runner = ParallelJSONRunner(
320
+ config_file="test_cases.json",
321
+ variables={"solver": "/opt/solver/bin/solver.exe"},
322
+ )
323
+ success = runner.run_tests()
324
+
325
+ # YAML 同样支持
326
+ runner = YAMLRunner(
327
+ config_file="test_cases.yaml",
328
+ variables={"solver": "/opt/solver/bin/solver.exe"},
329
+ )
330
+ success = runner.run_tests()
331
+ ```
332
+
333
+ ### 适用场景
334
+
335
+ | 场景 | 示例 |
336
+ |---|---|
337
+ | 不同求解器版本测试 | `--var solver=v1.0/solver.exe` ↔ `--var solver=v2.0/solver.exe` |
338
+ | 不同输入数据 | `--var model=case_1.dat` ↔ `--var model=case_2.dat` |
339
+ | CI/CD 环境适配 | 本地 `/opt/solver.exe`,CI `/runner/solver.exe` |
340
+ | 多平台路径 | Windows `--var solver=C:\solver.exe`,Linux `--var solver=/opt/solver.exe` |
341
+
248
342
  ## 标签过滤
249
343
 
250
344
  通过标签(tags)可以对测试用例进行分类,并在运行时按标签批量过滤。标签过滤与名称过滤可同时使用,满足 AND 关系(两个条件都必须满足)。
@@ -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.9.0",
11
+ version="0.9.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.",
@@ -22,6 +22,18 @@ from .utils.junit_xml_writer import write_junit_xml
22
22
  logger = logging.getLogger("cli_test_framework.cli")
23
23
 
24
24
 
25
+ def _parse_vars(var_list):
26
+ """Parse ``['solver=/path', 'model=./m.dat']`` → ``{'solver': '/path', ...}``."""
27
+ variables = {}
28
+ for item in var_list or []:
29
+ if '=' not in item:
30
+ logger.warning("Ignoring invalid --var '%s' (expected KEY=VALUE)", item)
31
+ continue
32
+ key, _, value = item.partition('=')
33
+ variables[key.strip()] = value.strip()
34
+ return variables
35
+
36
+
25
37
  def create_parser():
26
38
  """Create and configure the argument parser"""
27
39
  parser = argparse.ArgumentParser(
@@ -61,6 +73,11 @@ Examples:
61
73
  run_parser.add_argument('--debug', action='store_true', help='Enable debug mode')
62
74
  run_parser.add_argument('--junit-xml', dest='junit_xml',
63
75
  help='Write JUnit XML report to the specified file path')
76
+ run_parser.add_argument('--var', action='append', default=[],
77
+ metavar='KEY=VALUE',
78
+ help='Set a variable for config placeholder substitution, '
79
+ 'e.g. --var solver=/path/to/solver '
80
+ '(can be used multiple times)')
64
81
 
65
82
  # ---- Compare command ----
66
83
  compare_parser = subparsers.add_parser('compare', help='Compare two files')
@@ -136,6 +153,8 @@ def run_tests(args):
136
153
  # construct Namespace objects without the newer arguments.
137
154
  history_dir = getattr(args, 'history_dir', None)
138
155
  regression_threshold = getattr(args, 'regression_threshold', 1.5)
156
+ var_list = getattr(args, 'var', [])
157
+ variables = _parse_vars(var_list)
139
158
 
140
159
  try:
141
160
  if args.parallel:
@@ -150,6 +169,7 @@ def run_tests(args):
150
169
  test_case_tag_filter=args.tag,
151
170
  history_dir=history_dir,
152
171
  regression_threshold=regression_threshold,
172
+ variables=variables,
153
173
  )
154
174
  elif file_ext in ['.yaml', '.yml']:
155
175
  runner = ParallelYAMLRunner(
@@ -161,6 +181,7 @@ def run_tests(args):
161
181
  test_case_tag_filter=args.tag,
162
182
  history_dir=history_dir,
163
183
  regression_threshold=regression_threshold,
184
+ variables=variables,
164
185
  )
165
186
  else:
166
187
  logger.error("Unsupported configuration file format for parallel mode: %s", file_ext)
@@ -175,6 +196,7 @@ def run_tests(args):
175
196
  test_case_tag_filter=args.tag,
176
197
  history_dir=history_dir,
177
198
  regression_threshold=regression_threshold,
199
+ variables=variables,
178
200
  )
179
201
  elif file_ext in ['.yaml', '.yml']:
180
202
  runner = YAMLRunner(
@@ -184,6 +206,7 @@ def run_tests(args):
184
206
  test_case_tag_filter=args.tag,
185
207
  history_dir=history_dir,
186
208
  regression_threshold=regression_threshold,
209
+ variables=variables,
187
210
  )
188
211
  else:
189
212
  logger.error("Unsupported configuration file format: %s", file_ext)
@@ -11,6 +11,7 @@ Backward-compatible: the runner classes still expose ``load_test_cases()`` and
11
11
  from __future__ import annotations
12
12
 
13
13
  import logging
14
+ import re
14
15
  from pathlib import Path
15
16
  from typing import Any, Dict, List, Optional, Tuple
16
17
 
@@ -20,6 +21,40 @@ from ..utils.path_resolver import resolve_paths
20
21
 
21
22
  logger = logging.getLogger("cli_test_framework.core.config_loader")
22
23
 
24
+ # ---------------------------------------------------------------------------
25
+ # Placeholder substitution
26
+ # ---------------------------------------------------------------------------
27
+
28
+ _PLACEHOLDER_RE = re.compile(r'\{(\w+)\}')
29
+
30
+
31
+ def substitute_placeholders(
32
+ config: Dict[str, Any],
33
+ variables: Optional[Dict[str, Any]] = None,
34
+ ) -> Dict[str, Any]:
35
+ """递归替换 config 中字符串值的 ``{placeholder}`` 占位符。
36
+
37
+ 只替换 ``variables`` 中存在的 key,未匹配的 ``{xxx}`` 原样保留,
38
+ 不会影响 ``expected.matches`` 等字段中的正则模式(如 ``{2}``)。
39
+ """
40
+ if not variables:
41
+ return config
42
+
43
+ def _sub(value: Any) -> Any:
44
+ if isinstance(value, str):
45
+ return _PLACEHOLDER_RE.sub(
46
+ lambda m: str(variables[m.group(1)])
47
+ if m.group(1) in variables else m.group(0),
48
+ value,
49
+ )
50
+ if isinstance(value, list):
51
+ return [_sub(item) for item in value]
52
+ if isinstance(value, dict):
53
+ return {k: _sub(v) for k, v in value.items()}
54
+ return value
55
+
56
+ return _sub(config)
57
+
23
58
 
24
59
  # ---------------------------------------------------------------------------
25
60
  # Test-case parsing (loaded dict → list[TestCase])
@@ -10,7 +10,7 @@ import logging
10
10
  from typing import Optional, Dict, Any, Callable, BinaryIO
11
11
 
12
12
  from ..core.base_runner import BaseRunner
13
- from ..core.config_loader import parse_test_cases
13
+ from ..core.config_loader import parse_test_cases, substitute_placeholders
14
14
  from ..core.test_case import TestCase
15
15
  from ..core.execution import execute_single_test_case
16
16
  from ..core.types import TestCaseData
@@ -30,9 +30,11 @@ class ConfigRunner(BaseRunner):
30
30
  def __init__(self, config_file: str = "test_cases.json",
31
31
  workspace: Optional[str] = None,
32
32
  config_loader: Optional[Callable[[BinaryIO], Dict[str, Any]]] = None,
33
+ variables: Optional[Dict[str, Any]] = None,
33
34
  **kwargs):
34
35
  super().__init__(config_file, workspace, **kwargs)
35
36
  self._config_loader = config_loader
37
+ self._variables = variables or {}
36
38
  # Backward-compatible attribute for tests that patch path_resolver
37
39
  self.path_resolver = PathResolver(self.workspace)
38
40
 
@@ -46,6 +48,8 @@ class ConfigRunner(BaseRunner):
46
48
  with open(self.config_path, 'r', encoding='utf-8') as f:
47
49
  config = self._config_loader(f)
48
50
 
51
+ config = substitute_placeholders(config, self._variables)
52
+
49
53
  self.load_setup_from_config(config)
50
54
  self.test_cases = parse_test_cases(
51
55
  config, self.workspace, self.path_resolver,
@@ -10,7 +10,7 @@ import logging
10
10
  from typing import Optional, Dict, Any, Callable, BinaryIO
11
11
 
12
12
  from ..core.parallel_runner import ParallelRunner, AtomicSemaphore
13
- from ..core.config_loader import parse_test_cases, execute_sequence
13
+ from ..core.config_loader import parse_test_cases, execute_sequence, substitute_placeholders
14
14
  from ..core.test_case import TestCase
15
15
  from ..core.execution import execute_single_test_case
16
16
  from ..core.types import TestCaseData
@@ -32,6 +32,7 @@ class ParallelConfigRunner(ParallelRunner):
32
32
  max_workers: Optional[int] = None,
33
33
  execution_mode: str = "thread",
34
34
  config_loader: Optional[Callable[[BinaryIO], Dict[str, Any]]] = None,
35
+ variables: Optional[Dict[str, Any]] = None,
35
36
  **kwargs):
36
37
  # Auto-detect physical CPU cores (reserve 2 for OS)
37
38
  self.total_physical = os.cpu_count() or 4
@@ -43,6 +44,7 @@ class ParallelConfigRunner(ParallelRunner):
43
44
  super().__init__(config_file, workspace, max_workers,
44
45
  execution_mode, **kwargs)
45
46
  self._config_loader = config_loader
47
+ self._variables = variables or {}
46
48
  # Backward-compatible attribute for tests that patch path_resolver
47
49
  self.path_resolver = PathResolver(self.workspace)
48
50
 
@@ -121,6 +123,8 @@ class ParallelConfigRunner(ParallelRunner):
121
123
  with open(self.config_path, 'r', encoding='utf-8') as f:
122
124
  config = self._config_loader(f)
123
125
 
126
+ config = substitute_placeholders(config, self._variables)
127
+
124
128
  self.load_setup_from_config(config)
125
129
  self.test_cases = parse_test_cases(
126
130
  config, self.workspace, self.path_resolver,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cli-test-framework
3
- Version: 0.9.0
3
+ Version: 0.9.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,12 +130,14 @@ 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_config_loader.py
133
134
  tests/unit/core/test_execution.py
134
135
  tests/unit/core/test_history_store.py
135
136
  tests/unit/core/test_process_worker.py
136
137
  tests/unit/core/test_setup.py
137
138
  tests/unit/core/__pycache__/test_assertions.cpython-312-pytest-7.4.4.pyc
138
139
  tests/unit/core/__pycache__/test_assertions.cpython-312-pytest-9.0.3.pyc
140
+ tests/unit/core/__pycache__/test_config_loader.cpython-312-pytest-9.0.3.pyc
139
141
  tests/unit/core/__pycache__/test_execution.cpython-312-pytest-9.0.3.pyc
140
142
  tests/unit/core/__pycache__/test_history_store.cpython-312-pytest-7.4.4.pyc
141
143
  tests/unit/core/__pycache__/test_history_store.cpython-312-pytest-9.0.3.pyc
@@ -149,6 +151,7 @@ tests/unit/runners/test_test_case_filter.py
149
151
  tests/unit/runners/test_test_case_tag_filter.py
150
152
  tests/unit/runners/__pycache__/test_json_yaml_runner.cpython-312-pytest-7.4.4.pyc
151
153
  tests/unit/runners/__pycache__/test_json_yaml_runner.cpython-312-pytest-9.0.3.pyc
154
+ tests/unit/runners/__pycache__/test_json_yaml_runner.cpython-312.pyc
152
155
  tests/unit/runners/__pycache__/test_json_yaml_runner.cpython-39-pytest-8.3.4.pyc
153
156
  tests/unit/runners/__pycache__/test_test_case_filter.cpython-312-pytest-7.4.4.pyc
154
157
  tests/unit/runners/__pycache__/test_test_case_filter.cpython-312-pytest-9.0.3.pyc
@@ -0,0 +1,136 @@
1
+ """Unit tests for cli_test_framework.core.config_loader — substitute_placeholders."""
2
+
3
+ import pytest
4
+
5
+ from cli_test_framework.core.config_loader import substitute_placeholders
6
+
7
+
8
+ # ---------------------------------------------------------------------------
9
+ # Basic substitution
10
+ # ---------------------------------------------------------------------------
11
+
12
+ class TestSubstitutePlaceholders:
13
+ """Tests for the substitute_placeholders utility."""
14
+
15
+ def test_no_variables_returns_unchanged(self):
16
+ config = {"command": "{solver}", "args": ["--input", "{model}"]}
17
+ result = substitute_placeholders(config, None)
18
+ assert result == config
19
+
20
+ def test_empty_variables_returns_unchanged(self):
21
+ config = {"command": "{solver}", "args": ["--input", "{model}"]}
22
+ result = substitute_placeholders(config, {})
23
+ assert result == config
24
+
25
+ def test_simple_string_substitution(self):
26
+ config = {"command": "{solver}"}
27
+ result = substitute_placeholders(config, {"solver": "/usr/bin/solver"})
28
+ assert result["command"] == "/usr/bin/solver"
29
+
30
+ def test_multiple_placeholders_in_one_string(self):
31
+ config = {"command": "{solver} --input {model}"}
32
+ result = substitute_placeholders(
33
+ config, {"solver": "/usr/bin/solver", "model": "./data.dat"},
34
+ )
35
+ assert result["command"] == "/usr/bin/solver --input ./data.dat"
36
+
37
+ def test_placeholder_in_list(self):
38
+ config = {"args": ["--solver", "{solver}", "--input", "{model}"]}
39
+ result = substitute_placeholders(
40
+ config, {"solver": "/usr/bin/solver", "model": "./data.dat"},
41
+ )
42
+ assert result["args"] == ["--solver", "/usr/bin/solver", "--input", "./data.dat"]
43
+
44
+ def test_placeholder_in_nested_dict(self):
45
+ config = {
46
+ "test_cases": [
47
+ {
48
+ "name": "test-{tag}",
49
+ "command": "{solver}",
50
+ "args": ["{model}"],
51
+ "expected": {"return_code": 0},
52
+ }
53
+ ]
54
+ }
55
+ result = substitute_placeholders(
56
+ config, {"solver": "/usr/bin/solver", "model": "./data.dat", "tag": "alpha"},
57
+ )
58
+ case = result["test_cases"][0]
59
+ assert case["name"] == "test-alpha"
60
+ assert case["command"] == "/usr/bin/solver"
61
+ assert case["args"] == ["./data.dat"]
62
+
63
+ def test_unmatched_placeholder_preserved(self):
64
+ """Unmatched {xxx} should be kept as-is (safe for regex patterns like {2})."""
65
+ config = {"expected": {"output_matches": r"\d{2,4}"}}
66
+ result = substitute_placeholders(config, {"solver": "/bin/solver"})
67
+ assert result["expected"]["output_matches"] == r"\d{2,4}"
68
+
69
+ def test_partial_match_preserved(self):
70
+ """Only entirely matching keys are substituted; partial matches stay."""
71
+ config = {"command": "{solver_extra}"}
72
+ result = substitute_placeholders(config, {"solver": "/bin/solver"})
73
+ assert result["command"] == "{solver_extra}"
74
+
75
+ def test_non_string_values_unchanged(self):
76
+ config = {"timeout": 60, "priority": 3.5, "enabled": True, "data": None}
77
+ result = substitute_placeholders(config, {"timeout": "999"})
78
+ assert result["timeout"] == 60
79
+ assert result["priority"] == 3.5
80
+ assert result["enabled"] is True
81
+ assert result["data"] is None
82
+
83
+ def test_original_config_not_mutated(self):
84
+ original = {"command": "{solver}", "args": ["{model}"]}
85
+ result = substitute_placeholders(
86
+ original, {"solver": "/usr/bin/solver", "model": "./data.dat"},
87
+ )
88
+ # Original should be unchanged
89
+ assert original["command"] == "{solver}"
90
+ assert original["args"] == ["{model}"]
91
+ # Result should have substitutions
92
+ assert result["command"] == "/usr/bin/solver"
93
+ assert result["args"] == ["./data.dat"]
94
+
95
+ def test_variable_value_converted_to_str(self):
96
+ """Non-string variable values should be converted via str()."""
97
+ config = {"command": "{count} {flag}"}
98
+ result = substitute_placeholders(config, {"count": 42, "flag": True})
99
+ assert result["command"] == "42 True"
100
+
101
+
102
+ # ---------------------------------------------------------------------------
103
+ # Integration-style: full config dict with placeholders
104
+ # ---------------------------------------------------------------------------
105
+
106
+ class TestPlaceholderInTestCases:
107
+ """End-to-end style tests with realistic config dicts."""
108
+
109
+ def test_full_json_config_with_steps(self):
110
+ config = {
111
+ "test_cases": [
112
+ {
113
+ "name": "Multi-step with placeholders",
114
+ "steps": [
115
+ {
116
+ "command": "{solver}",
117
+ "args": ["--parse", "{input}"],
118
+ "expected": {"return_code": 0},
119
+ },
120
+ {
121
+ "command": "{solver}",
122
+ "args": ["--solve", "{input}"],
123
+ "expected": {"output_contains": ["Optimal"]},
124
+ },
125
+ ],
126
+ }
127
+ ]
128
+ }
129
+ result = substitute_placeholders(
130
+ config, {"solver": "/opt/solver", "input": "model.dat"},
131
+ )
132
+ steps = result["test_cases"][0]["steps"]
133
+ assert steps[0]["command"] == "/opt/solver"
134
+ assert steps[0]["args"] == ["--parse", "model.dat"]
135
+ assert steps[1]["command"] == "/opt/solver"
136
+ assert steps[1]["args"] == ["--solve", "model.dat"]
@@ -82,6 +82,56 @@ class TestYAMLRunner(unittest.TestCase):
82
82
  self.temp_dir.cleanup()
83
83
 
84
84
 
85
+ class TestPlaceholderSubstitution(unittest.TestCase):
86
+ """Integration test: verify that variables flow through runner's load_test_cases."""
87
+
88
+ def setUp(self):
89
+ self.temp_dir = tempfile.TemporaryDirectory()
90
+ config = {
91
+ "test_cases": [
92
+ {
93
+ "name": "placeholder-test",
94
+ "command": "{solver}",
95
+ "args": ["--input", "{flag}"],
96
+ "expected": {"return_code": 0},
97
+ }
98
+ ]
99
+ }
100
+ self.config_path = Path(self.temp_dir.name) / "placeholder_config.json"
101
+ self.config_path.write_text(json.dumps(config), encoding="utf-8")
102
+
103
+ def test_placeholders_substituted_in_loaded_cases(self):
104
+ runner = JSONRunner(
105
+ str(self.config_path),
106
+ workspace=self.temp_dir.name,
107
+ variables={"solver": "/usr/bin/solver", "flag": "--verbose"},
108
+ )
109
+ runner.load_test_cases()
110
+
111
+ self.assertEqual(len(runner.test_cases), 1)
112
+ case = runner.test_cases[0]
113
+ self.assertIn("/usr/bin/solver", case.command)
114
+ self.assertIn("--verbose", case.args)
115
+ self.assertNotIn("{solver}", case.command)
116
+ self.assertNotIn("{flag}", case.args)
117
+
118
+ def test_no_variables_still_works(self):
119
+ """Without variables, placeholders should remain as-is."""
120
+ runner = JSONRunner(
121
+ str(self.config_path),
122
+ workspace=self.temp_dir.name,
123
+ )
124
+ runner.load_test_cases()
125
+
126
+ self.assertEqual(len(runner.test_cases), 1)
127
+ case = runner.test_cases[0]
128
+ self.assertIn("{solver}", case.command)
129
+ self.assertIn("{flag}", case.args)
130
+
131
+ def tearDown(self):
132
+ self.temp_dir.cleanup()
133
+
134
+
85
135
  if __name__ == "__main__":
86
136
  unittest.main()
87
137
 
@@ -109,3 +109,49 @@ def test_main_exits_nonzero_without_command(monkeypatch):
109
109
 
110
110
  assert exc.value.code == 1
111
111
 
112
+
113
+ def test_parse_vars_single():
114
+ result = cli._parse_vars(["solver=/opt/solver"])
115
+ assert result == {"solver": "/opt/solver"}
116
+
117
+
118
+ def test_parse_vars_multiple():
119
+ result = cli._parse_vars(["solver=/opt/solver", "model=./data.dat"])
120
+ assert result == {"solver": "/opt/solver", "model": "./data.dat"}
121
+
122
+
123
+ def test_parse_vars_empty():
124
+ result = cli._parse_vars([])
125
+ assert result == {}
126
+
127
+
128
+ def test_parse_vars_invalid_warns(caplog):
129
+ result = cli._parse_vars(["bad_entry"])
130
+ assert result == {}
131
+ assert "Ignoring invalid --var" in caplog.text
132
+
133
+
134
+ def test_parse_vars_key_with_spaces():
135
+ result = cli._parse_vars([" solver = /opt/solver "])
136
+ assert result == {"solver": "/opt/solver"}
137
+
138
+
139
+ def test_run_tests_passes_variables_to_runner(tmp_path, monkeypatch):
140
+ config = tmp_path / "cases.json"
141
+ config.write_text('{"test_cases": []}', encoding="utf-8")
142
+ captured = {}
143
+
144
+ class VarRunner(DummyRunner):
145
+ def __init__(self, *args, **kwargs):
146
+ super().__init__(*args, **kwargs)
147
+ captured.update(kwargs)
148
+
149
+ def run_tests(self):
150
+ return True
151
+
152
+ monkeypatch.setattr(cli, "JSONRunner", VarRunner)
153
+
154
+ cli.run_tests(make_args(config, var=["solver=/opt/solver"]))
155
+
156
+ assert captured.get("variables") == {"solver": "/opt/solver"}
157
+