diff --git a/src/catch2/internal/catch_jsonwriter.cpp b/src/catch2/internal/catch_jsonwriter.cpp index 6915c9c3..6a994f11 100644 --- a/src/catch2/internal/catch_jsonwriter.cpp +++ b/src/catch2/internal/catch_jsonwriter.cpp @@ -9,6 +9,8 @@ #include #include +#include + namespace Catch { namespace { @@ -126,7 +128,10 @@ namespace Catch { JsonValueWriter::JsonValueWriter( std::ostream& os, std::uint64_t indent_level ): - m_os{ os }, m_indent_level{ indent_level } {} + m_os{ os }, m_indent_level{ indent_level } { + // We use C locale so that writing of numerical values is locale-independent. + m_sstream.imbue( std::locale::classic() ); + } JsonObjectWriter JsonValueWriter::writeObject() && { return JsonObjectWriter{ m_os, m_indent_level }; diff --git a/tests/SelfTest/Baselines/automake.sw.approved.txt b/tests/SelfTest/Baselines/automake.sw.approved.txt index 5018a19a..b0028998 100644 --- a/tests/SelfTest/Baselines/automake.sw.approved.txt +++ b/tests/SelfTest/Baselines/automake.sw.approved.txt @@ -190,6 +190,7 @@ Nor would this :test-result: PASS Inequality checks that should succeed :test-result: PASS JsonWriter :test-result: PASS JsonWriter escapes characters in strings properly +:test-result: PASS JsonWriter serializes numbers independently of the global locale :test-result: PASS Lambdas in assertions :test-result: PASS Less-than inequalities with different epsilons :test-result: PASS ManuallyRegistered diff --git a/tests/SelfTest/Baselines/automake.sw.multi.approved.txt b/tests/SelfTest/Baselines/automake.sw.multi.approved.txt index 4f9a9441..9602395a 100644 --- a/tests/SelfTest/Baselines/automake.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/automake.sw.multi.approved.txt @@ -188,6 +188,7 @@ :test-result: PASS Inequality checks that should succeed :test-result: PASS JsonWriter :test-result: PASS JsonWriter escapes characters in strings properly +:test-result: PASS JsonWriter serializes numbers independently of the global locale :test-result: PASS Lambdas in assertions :test-result: PASS Less-than inequalities with different epsilons :test-result: PASS ManuallyRegistered diff --git a/tests/SelfTest/Baselines/compact.sw.approved.txt b/tests/SelfTest/Baselines/compact.sw.approved.txt index cb90a0f5..d669bce4 100644 --- a/tests/SelfTest/Baselines/compact.sw.approved.txt +++ b/tests/SelfTest/Baselines/compact.sw.approved.txt @@ -1248,6 +1248,20 @@ Json.tests.cpp:: passed: sstream.str() == "\"\\n\"" for: ""\n"" == Json.tests.cpp:: passed: sstream.str() == "\"\\r\"" for: ""\r"" == ""\r"" Json.tests.cpp:: passed: sstream.str() == "\"\\t\"" for: ""\t"" == ""\t"" Json.tests.cpp:: passed: sstream.str() == "\"\\\\/\\t\\r\\n\"" for: ""\\/\t\r\n"" == ""\\/\t\r\n"" +Json.tests.cpp:: passed: sstream.str(), ContainsSubstring( "\"double\": 1.5," ) && ContainsSubstring( "\"int\": 1234567," ) && ContainsSubstring( "\"bool-1\": true," ) && ContainsSubstring( "\"bool-2\": false," ) && ContainsSubstring( "\"array\": [\n 2.5,\n 1234567\n ]\n}" ) for: "{ + "double": 1.5, + "int": 1234567, + "bool-1": true, + "bool-2": false, + "array": [ + 2.5, + 1234567 + ] +}" ( contains: ""double": 1.5," and contains: ""int": 1234567," and contains: ""bool-1": true," and contains: ""bool-2": false," and contains: ""array": [ + 2.5, + 1234567 + ] +}" ) Compilation.tests.cpp:: passed: []() { return true; }() for: true Approx.tests.cpp:: passed: d <= Approx( 1.24 ) for: 1.22999999999999998 <= @@ -3000,7 +3014,7 @@ InternalBenchmark.tests.cpp:: passed: med == 18. for: 18.0 == 18.0 InternalBenchmark.tests.cpp:: passed: q3 == 23. for: 23.0 == 23.0 Misc.tests.cpp:: passed: Misc.tests.cpp:: passed: -test cases: 451 | 331 passed | 96 failed | 6 skipped | 18 failed as expected -assertions: 2416 | 2215 passed | 158 failed | 43 failed as expected +test cases: 452 | 332 passed | 96 failed | 6 skipped | 18 failed as expected +assertions: 2417 | 2216 passed | 158 failed | 43 failed as expected diff --git a/tests/SelfTest/Baselines/compact.sw.multi.approved.txt b/tests/SelfTest/Baselines/compact.sw.multi.approved.txt index 39c260af..712b1259 100644 --- a/tests/SelfTest/Baselines/compact.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/compact.sw.multi.approved.txt @@ -1246,6 +1246,20 @@ Json.tests.cpp:: passed: sstream.str() == "\"\\n\"" for: ""\n"" == Json.tests.cpp:: passed: sstream.str() == "\"\\r\"" for: ""\r"" == ""\r"" Json.tests.cpp:: passed: sstream.str() == "\"\\t\"" for: ""\t"" == ""\t"" Json.tests.cpp:: passed: sstream.str() == "\"\\\\/\\t\\r\\n\"" for: ""\\/\t\r\n"" == ""\\/\t\r\n"" +Json.tests.cpp:: passed: sstream.str(), ContainsSubstring( "\"double\": 1.5," ) && ContainsSubstring( "\"int\": 1234567," ) && ContainsSubstring( "\"bool-1\": true," ) && ContainsSubstring( "\"bool-2\": false," ) && ContainsSubstring( "\"array\": [\n 2.5,\n 1234567\n ]\n}" ) for: "{ + "double": 1.5, + "int": 1234567, + "bool-1": true, + "bool-2": false, + "array": [ + 2.5, + 1234567 + ] +}" ( contains: ""double": 1.5," and contains: ""int": 1234567," and contains: ""bool-1": true," and contains: ""bool-2": false," and contains: ""array": [ + 2.5, + 1234567 + ] +}" ) Compilation.tests.cpp:: passed: []() { return true; }() for: true Approx.tests.cpp:: passed: d <= Approx( 1.24 ) for: 1.22999999999999998 <= @@ -2989,7 +3003,7 @@ InternalBenchmark.tests.cpp:: passed: med == 18. for: 18.0 == 18.0 InternalBenchmark.tests.cpp:: passed: q3 == 23. for: 23.0 == 23.0 Misc.tests.cpp:: passed: Misc.tests.cpp:: passed: -test cases: 451 | 331 passed | 96 failed | 6 skipped | 18 failed as expected -assertions: 2416 | 2215 passed | 158 failed | 43 failed as expected +test cases: 452 | 332 passed | 96 failed | 6 skipped | 18 failed as expected +assertions: 2417 | 2216 passed | 158 failed | 43 failed as expected diff --git a/tests/SelfTest/Baselines/console.std.approved.txt b/tests/SelfTest/Baselines/console.std.approved.txt index 78c3d640..a833734a 100644 --- a/tests/SelfTest/Baselines/console.std.approved.txt +++ b/tests/SelfTest/Baselines/console.std.approved.txt @@ -1743,6 +1743,6 @@ due to unexpected exception with message: Why would you throw a std::string? =============================================================================== -test cases: 451 | 349 passed | 76 failed | 7 skipped | 19 failed as expected -assertions: 2394 | 2215 passed | 136 failed | 43 failed as expected +test cases: 452 | 350 passed | 76 failed | 7 skipped | 19 failed as expected +assertions: 2395 | 2216 passed | 136 failed | 43 failed as expected diff --git a/tests/SelfTest/Baselines/console.sw.approved.txt b/tests/SelfTest/Baselines/console.sw.approved.txt index add1eaaf..2de0a3a5 100644 --- a/tests/SelfTest/Baselines/console.sw.approved.txt +++ b/tests/SelfTest/Baselines/console.sw.approved.txt @@ -8219,6 +8219,31 @@ Json.tests.cpp:: PASSED: with expansion: ""\\/\t\r\n"" == ""\\/\t\r\n"" +------------------------------------------------------------------------------- +JsonWriter serializes numbers independently of the global locale +------------------------------------------------------------------------------- +Json.tests.cpp: +............................................................................... + +Json.tests.cpp:: PASSED: + REQUIRE_THAT( sstream.str(), ContainsSubstring( "\"double\": 1.5," ) && ContainsSubstring( "\"int\": 1234567," ) && ContainsSubstring( "\"bool-1\": true," ) && ContainsSubstring( "\"bool-2\": false," ) && ContainsSubstring( "\"array\": [\n 2.5,\n 1234567\n ]\n}" ) ) +with expansion: + "{ + "double": 1.5, + "int": 1234567, + "bool-1": true, + "bool-2": false, + "array": [ + 2.5, + 1234567 + ] + }" ( contains: ""double": 1.5," and contains: ""int": 1234567," and contains: + ""bool-1": true," and contains: ""bool-2": false," and contains: ""array": [ + 2.5, + 1234567 + ] + }" ) + ------------------------------------------------------------------------------- Lambdas in assertions ------------------------------------------------------------------------------- @@ -20134,6 +20159,6 @@ Misc.tests.cpp: Misc.tests.cpp:: PASSED: =============================================================================== -test cases: 451 | 331 passed | 96 failed | 6 skipped | 18 failed as expected -assertions: 2416 | 2215 passed | 158 failed | 43 failed as expected +test cases: 452 | 332 passed | 96 failed | 6 skipped | 18 failed as expected +assertions: 2417 | 2216 passed | 158 failed | 43 failed as expected diff --git a/tests/SelfTest/Baselines/console.sw.multi.approved.txt b/tests/SelfTest/Baselines/console.sw.multi.approved.txt index 07195577..9b6f023c 100644 --- a/tests/SelfTest/Baselines/console.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/console.sw.multi.approved.txt @@ -8217,6 +8217,31 @@ Json.tests.cpp:: PASSED: with expansion: ""\\/\t\r\n"" == ""\\/\t\r\n"" +------------------------------------------------------------------------------- +JsonWriter serializes numbers independently of the global locale +------------------------------------------------------------------------------- +Json.tests.cpp: +............................................................................... + +Json.tests.cpp:: PASSED: + REQUIRE_THAT( sstream.str(), ContainsSubstring( "\"double\": 1.5," ) && ContainsSubstring( "\"int\": 1234567," ) && ContainsSubstring( "\"bool-1\": true," ) && ContainsSubstring( "\"bool-2\": false," ) && ContainsSubstring( "\"array\": [\n 2.5,\n 1234567\n ]\n}" ) ) +with expansion: + "{ + "double": 1.5, + "int": 1234567, + "bool-1": true, + "bool-2": false, + "array": [ + 2.5, + 1234567 + ] + }" ( contains: ""double": 1.5," and contains: ""int": 1234567," and contains: + ""bool-1": true," and contains: ""bool-2": false," and contains: ""array": [ + 2.5, + 1234567 + ] + }" ) + ------------------------------------------------------------------------------- Lambdas in assertions ------------------------------------------------------------------------------- @@ -20123,6 +20148,6 @@ Misc.tests.cpp: Misc.tests.cpp:: PASSED: =============================================================================== -test cases: 451 | 331 passed | 96 failed | 6 skipped | 18 failed as expected -assertions: 2416 | 2215 passed | 158 failed | 43 failed as expected +test cases: 452 | 332 passed | 96 failed | 6 skipped | 18 failed as expected +assertions: 2417 | 2216 passed | 158 failed | 43 failed as expected diff --git a/tests/SelfTest/Baselines/junit.sw.approved.txt b/tests/SelfTest/Baselines/junit.sw.approved.txt index 328b466c..c84565d5 100644 --- a/tests/SelfTest/Baselines/junit.sw.approved.txt +++ b/tests/SelfTest/Baselines/junit.sw.approved.txt @@ -1,7 +1,7 @@ - + @@ -774,6 +774,7 @@ at Condition.tests.cpp: + diff --git a/tests/SelfTest/Baselines/junit.sw.multi.approved.txt b/tests/SelfTest/Baselines/junit.sw.multi.approved.txt index 403ed251..f4ff03d6 100644 --- a/tests/SelfTest/Baselines/junit.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/junit.sw.multi.approved.txt @@ -1,6 +1,6 @@ - + @@ -773,6 +773,7 @@ at Condition.tests.cpp: + diff --git a/tests/SelfTest/Baselines/sonarqube.sw.approved.txt b/tests/SelfTest/Baselines/sonarqube.sw.approved.txt index f9e851fc..dceaca40 100644 --- a/tests/SelfTest/Baselines/sonarqube.sw.approved.txt +++ b/tests/SelfTest/Baselines/sonarqube.sw.approved.txt @@ -262,6 +262,7 @@ at AssertionHandler.tests.cpp: + diff --git a/tests/SelfTest/Baselines/sonarqube.sw.multi.approved.txt b/tests/SelfTest/Baselines/sonarqube.sw.multi.approved.txt index e1ede12c..250b45ae 100644 --- a/tests/SelfTest/Baselines/sonarqube.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/sonarqube.sw.multi.approved.txt @@ -261,6 +261,7 @@ at AssertionHandler.tests.cpp: + diff --git a/tests/SelfTest/Baselines/tap.sw.approved.txt b/tests/SelfTest/Baselines/tap.sw.approved.txt index 7624d140..7e9a19d6 100644 --- a/tests/SelfTest/Baselines/tap.sw.approved.txt +++ b/tests/SelfTest/Baselines/tap.sw.approved.txt @@ -2054,6 +2054,8 @@ ok {test-number} - sstream.str() == "\"\\r\"" for: ""\r"" == ""\r"" ok {test-number} - sstream.str() == "\"\\t\"" for: ""\t"" == ""\t"" # JsonWriter escapes characters in strings properly ok {test-number} - sstream.str() == "\"\\\\/\\t\\r\\n\"" for: ""\\/\t\r\n"" == ""\\/\t\r\n"" +# JsonWriter serializes numbers independently of the global locale +ok {test-number} - sstream.str(), ContainsSubstring( "\"double\": 1.5," ) && ContainsSubstring( "\"int\": 1234567," ) && ContainsSubstring( "\"bool-1\": true," ) && ContainsSubstring( "\"bool-2\": false," ) && ContainsSubstring( "\"array\": [\n 2.5,\n 1234567\n ]\n}" ) for: "{ "double": 1.5, "int": 1234567, "bool-1": true, "bool-2": false, "array": [ 2.5, 1234567 ] }" ( contains: ""double": 1.5," and contains: ""int": 1234567," and contains: ""bool-1": true," and contains: ""bool-2": false," and contains: ""array": [ 2.5, 1234567 ] }" ) # Lambdas in assertions ok {test-number} - []() { return true; }() for: true # Less-than inequalities with different epsilons @@ -4851,5 +4853,5 @@ ok {test-number} - q3 == 23. for: 23.0 == 23.0 ok {test-number} - # xmlentitycheck ok {test-number} - -1..2428 +1..2429 diff --git a/tests/SelfTest/Baselines/tap.sw.multi.approved.txt b/tests/SelfTest/Baselines/tap.sw.multi.approved.txt index ed653421..edcb7813 100644 --- a/tests/SelfTest/Baselines/tap.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/tap.sw.multi.approved.txt @@ -2052,6 +2052,8 @@ ok {test-number} - sstream.str() == "\"\\r\"" for: ""\r"" == ""\r"" ok {test-number} - sstream.str() == "\"\\t\"" for: ""\t"" == ""\t"" # JsonWriter escapes characters in strings properly ok {test-number} - sstream.str() == "\"\\\\/\\t\\r\\n\"" for: ""\\/\t\r\n"" == ""\\/\t\r\n"" +# JsonWriter serializes numbers independently of the global locale +ok {test-number} - sstream.str(), ContainsSubstring( "\"double\": 1.5," ) && ContainsSubstring( "\"int\": 1234567," ) && ContainsSubstring( "\"bool-1\": true," ) && ContainsSubstring( "\"bool-2\": false," ) && ContainsSubstring( "\"array\": [\n 2.5,\n 1234567\n ]\n}" ) for: "{ "double": 1.5, "int": 1234567, "bool-1": true, "bool-2": false, "array": [ 2.5, 1234567 ] }" ( contains: ""double": 1.5," and contains: ""int": 1234567," and contains: ""bool-1": true," and contains: ""bool-2": false," and contains: ""array": [ 2.5, 1234567 ] }" ) # Lambdas in assertions ok {test-number} - []() { return true; }() for: true # Less-than inequalities with different epsilons @@ -4840,5 +4842,5 @@ ok {test-number} - q3 == 23. for: 23.0 == 23.0 ok {test-number} - # xmlentitycheck ok {test-number} - -1..2428 +1..2429 diff --git a/tests/SelfTest/Baselines/teamcity.sw.approved.txt b/tests/SelfTest/Baselines/teamcity.sw.approved.txt index bbd587ff..fc65c1c6 100644 --- a/tests/SelfTest/Baselines/teamcity.sw.approved.txt +++ b/tests/SelfTest/Baselines/teamcity.sw.approved.txt @@ -470,6 +470,8 @@ ##teamcity[testFinished name='JsonWriter' duration="{duration}"] ##teamcity[testStarted name='JsonWriter escapes characters in strings properly'] ##teamcity[testFinished name='JsonWriter escapes characters in strings properly' duration="{duration}"] +##teamcity[testStarted name='JsonWriter serializes numbers independently of the global locale'] +##teamcity[testFinished name='JsonWriter serializes numbers independently of the global locale' duration="{duration}"] ##teamcity[testStarted name='Lambdas in assertions'] ##teamcity[testFinished name='Lambdas in assertions' duration="{duration}"] ##teamcity[testStarted name='Less-than inequalities with different epsilons'] diff --git a/tests/SelfTest/Baselines/teamcity.sw.multi.approved.txt b/tests/SelfTest/Baselines/teamcity.sw.multi.approved.txt index fb73387f..f0a28ed7 100644 --- a/tests/SelfTest/Baselines/teamcity.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/teamcity.sw.multi.approved.txt @@ -470,6 +470,8 @@ ##teamcity[testFinished name='JsonWriter' duration="{duration}"] ##teamcity[testStarted name='JsonWriter escapes characters in strings properly'] ##teamcity[testFinished name='JsonWriter escapes characters in strings properly' duration="{duration}"] +##teamcity[testStarted name='JsonWriter serializes numbers independently of the global locale'] +##teamcity[testFinished name='JsonWriter serializes numbers independently of the global locale' duration="{duration}"] ##teamcity[testStarted name='Lambdas in assertions'] ##teamcity[testFinished name='Lambdas in assertions' duration="{duration}"] ##teamcity[testStarted name='Less-than inequalities with different epsilons'] diff --git a/tests/SelfTest/Baselines/xml.sw.approved.txt b/tests/SelfTest/Baselines/xml.sw.approved.txt index 933094d3..31801eef 100644 --- a/tests/SelfTest/Baselines/xml.sw.approved.txt +++ b/tests/SelfTest/Baselines/xml.sw.approved.txt @@ -9894,6 +9894,30 @@ Approx( 3.14150000000000018 ) + + + + sstream.str(), ContainsSubstring( "\"double\": 1.5," ) && ContainsSubstring( "\"int\": 1234567," ) && ContainsSubstring( "\"bool-1\": true," ) && ContainsSubstring( "\"bool-2\": false," ) && ContainsSubstring( "\"array\": [\n 2.5,\n 1234567\n ]\n}" ) + + + "{ + "double": 1.5, + "int": 1234567, + "bool-1": true, + "bool-2": false, + "array": [ + 2.5, + 1234567 + ] +}" ( contains: ""double": 1.5," and contains: ""int": 1234567," and contains: ""bool-1": true," and contains: ""bool-2": false," and contains: ""array": [ + 2.5, + 1234567 + ] +}" ) + + + + @@ -23385,6 +23409,6 @@ Approx( -1.95996398454005449 ) - - + + diff --git a/tests/SelfTest/Baselines/xml.sw.multi.approved.txt b/tests/SelfTest/Baselines/xml.sw.multi.approved.txt index 646f0c72..b24ec091 100644 --- a/tests/SelfTest/Baselines/xml.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/xml.sw.multi.approved.txt @@ -9894,6 +9894,30 @@ Approx( 3.14150000000000018 ) + + + + sstream.str(), ContainsSubstring( "\"double\": 1.5," ) && ContainsSubstring( "\"int\": 1234567," ) && ContainsSubstring( "\"bool-1\": true," ) && ContainsSubstring( "\"bool-2\": false," ) && ContainsSubstring( "\"array\": [\n 2.5,\n 1234567\n ]\n}" ) + + + "{ + "double": 1.5, + "int": 1234567, + "bool-1": true, + "bool-2": false, + "array": [ + 2.5, + 1234567 + ] +}" ( contains: ""double": 1.5," and contains: ""int": 1234567," and contains: ""bool-1": true," and contains: ""bool-2": false," and contains: ""array": [ + 2.5, + 1234567 + ] +}" ) + + + + @@ -23384,6 +23408,6 @@ Approx( -1.95996398454005449 ) - - + + diff --git a/tests/SelfTest/IntrospectiveTests/Json.tests.cpp b/tests/SelfTest/IntrospectiveTests/Json.tests.cpp index fae1c843..4f6789ec 100644 --- a/tests/SelfTest/IntrospectiveTests/Json.tests.cpp +++ b/tests/SelfTest/IntrospectiveTests/Json.tests.cpp @@ -12,6 +12,7 @@ #include #include +#include #include namespace { @@ -20,6 +21,27 @@ namespace { return os << "custom"; } + // Obviously wrong numpunct if it is actually used by the JSON writer. + class TestNumpunct : public std::numpunct { + char do_decimal_point() const override { return '?'; } + char do_thousands_sep() const override { return '!'; } + std::string do_grouping() const override { return "\1"; } + std::string do_truename() const override { return "real"; } + std::string do_falsename() const override { return "fake"; } + }; + + class LocaleGuard { + std::locale m_previous_locale; + + public: + explicit LocaleGuard( std::locale const& locale ): + m_previous_locale{ std::locale() } { + std::locale::global( locale ); + } + + ~LocaleGuard() { std::locale::global( m_previous_locale ); } + }; + TEST_CASE( "JsonWriter", "[JSON][JsonWriter]" ) { std::stringstream stream; @@ -152,6 +174,31 @@ TEST_CASE( "JsonWriter escapes characters in strings properly", "[JsonWriter]" ) } } +TEST_CASE( "JsonWriter serializes numbers independently of the global locale", + "[JsonWriter]" ) { + using Catch::Matchers::ContainsSubstring; + + LocaleGuard locale_guard{ std::locale{ std::locale(), new TestNumpunct } }; + + std::stringstream sstream; + { + auto writer = Catch::JsonValueWriter{ sstream }.writeObject(); + writer.write( "double" ).write( 1.5 ); + writer.write( "int" ).write( 1234567 ); + writer.write( "bool-1" ).write( true ); + writer.write( "bool-2" ).write( false ); + writer.write( "array" ).writeArray().write( 2.5 ).write( 1234567 ); + } + + REQUIRE_THAT( sstream.str(), + ContainsSubstring( "\"double\": 1.5," ) && + ContainsSubstring( "\"int\": 1234567," ) && + ContainsSubstring( "\"bool-1\": true," ) && + ContainsSubstring( "\"bool-2\": false," ) && + ContainsSubstring( + "\"array\": [\n 2.5,\n 1234567\n ]\n}" ) ); +} + TEST_CASE( "JsonWriter benchmarks", "[JsonWriter][!benchmark]" ) { const auto input_length = GENERATE( as{}, 10, 100, 10'000 ); std::string test_input( input_length, 'a' );