diff --git a/CHANGELOG.md b/CHANGELOG.md index 27ac6e1..167eb80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ # UNRELEASED -- Catch2: add support for catch version 3 ([#115](https://github.com/pytest-dev/pytest-cpp/pull/115)) -- Catch2: support exception handling ([#117](https://github.com/pytest-dev/pytest-cpp/pull/117)) +- Catch2: add support for catch version 3 ([#115](https://github.com/pytest-dev/pytest-cpp/pull/115)). +- Catch2: support exception handling ([#117](https://github.com/pytest-dev/pytest-cpp/pull/117)). +- Catch2: Correctly handle test names that contain special characters ([#123](https://github.com/pytest-dev/pytest-cpp/issues/123)). - Added support for Python 3.12. # 2.4.0 (2023-09-08) diff --git a/src/pytest_cpp/catch2.py b/src/pytest_cpp/catch2.py index 5af50b6..ec65768 100644 --- a/src/pytest_cpp/catch2.py +++ b/src/pytest_cpp/catch2.py @@ -16,6 +16,15 @@ from pytest_cpp.helpers import make_cmdline +# Map each special character's Unicode ordinal to the escaped character. +_special_chars_map: dict[int, str] = {i: "\\" + chr(i) for i in b'[]*,~\\"'} + + +def escape(test_id: str) -> str: + """Escape special characters in test names (see #123).""" + return test_id.translate(_special_chars_map) + + class Catch2Version(enum.Enum): V2 = "v2" V3 = "v3" @@ -115,14 +124,14 @@ def run_test( xml_filename = os.path.join(os.path.relpath(temp_dir), "cpp-report.xml") except ValueError: xml_filename = os.path.join(temp_dir, "cpp-report.xml") - args = list( - make_cmdline( - harness, - executable, - [test_id, "--success", "--reporter=xml", f"--out {xml_filename}"], - ) - ) - args.extend(test_args) + exec_args = [ + escape(test_id), + "--success", + "--reporter=xml", + f"--out={xml_filename}", + ] + exec_args.extend(test_args) + args = make_cmdline(harness, executable, exec_args) try: output = subprocess.check_output( diff --git a/tests/SConstruct b/tests/SConstruct index d5603ad..526cf77 100644 --- a/tests/SConstruct +++ b/tests/SConstruct @@ -47,10 +47,15 @@ for env, label in [(c3env, "_v3"), (c2env, "")]: catch2_success = env.Object(f'catch2_success{label}', 'catch2_success.cpp') catch2_failure = env.Object(f'catch2_failure{label}', 'catch2_failure.cpp') catch2_error = env.Object(f'catch2_error{label}', 'catch2_error.cpp') + catch2_special_chars = env.Object( + f'catch2_special_chars{label}', + 'catch2_special_chars.cpp' + ) env.Program(catch2_success) env.Program(catch2_failure) env.Program(catch2_error) + env.Program(catch2_special_chars) SConscript('acceptance/googletest-samples/SConscript') SConscript('acceptance/boosttest-samples/SConscript') diff --git a/tests/catch2_special_chars.cpp b/tests/catch2_special_chars.cpp new file mode 100644 index 0000000..4c61f52 --- /dev/null +++ b/tests/catch2_special_chars.cpp @@ -0,0 +1,26 @@ +#define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file +#include "catch.hpp" + +TEST_CASE( "Brackets in [test] name" ) { + REQUIRE( true ); +} + +TEST_CASE( "**Star in test name**" ) { + REQUIRE( true ); +} + +TEST_CASE( "~Tilde in test name" ) { + REQUIRE( true ); +} + +TEST_CASE( "Comma, in, test, name" ) { + REQUIRE( true ); +} + +TEST_CASE( R"(Backslash\ in\ test\ name)" ) { + REQUIRE( true ); +} + +TEST_CASE( "\"Quotes\" in test name" ) { + REQUIRE( true ); +} diff --git a/tests/test_pytest_cpp.py b/tests/test_pytest_cpp.py index 1801a10..d16075e 100644 --- a/tests/test_pytest_cpp.py +++ b/tests/test_pytest_cpp.py @@ -635,6 +635,24 @@ def test_catch2_failure(exes): assert_catch2_failure(fail1.get_lines()[1], "a runtime error", colors) +@pytest.mark.parametrize("suffix", ["", "_v3"]) +@pytest.mark.parametrize( + "test_id", + [ + "Brackets in [test] name", + "**Star in test name**", + "~Tilde in test name", + "Comma, in, test, name", + r"Backslash\ in\ test\ name", + '"Quotes" in test name', + ], +) +def test_catch2_special_chars(suffix, test_id, exes): + facade = Catch2Facade() + exe = exes.get("catch2_special_chars" + suffix) + assert facade.run_test(exe, test_id)[0] is None + + class TestError: def test_get_whitespace(self): assert error.get_left_whitespace(" foo") == " "