diff --git a/CMakeLists.txt b/CMakeLists.txt
index de5e6709..f63d1d44 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -35,7 +35,7 @@ if(CMAKE_BINARY_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
endif()
project(Catch2
- VERSION 3.15.2 # CML version placeholder, don't delete
+ VERSION 3.15.3 # CML version placeholder, don't delete
LANGUAGES CXX
HOMEPAGE_URL "https://github.com/catchorg/Catch2"
DESCRIPTION "A modern, C++-native, unit test framework."
diff --git a/docs/release-notes.md b/docs/release-notes.md
index ffa06430..9fafa0cd 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -2,6 +2,7 @@
# Release notes
**Contents**
+[3.15.3](#3153)
[3.15.2](#3152)
[3.15.1](#3151)
[3.15.0](#3150)
@@ -77,6 +78,28 @@
[Even Older versions](#even-older-versions)
+
+## 3.15.3
+
+### Fixes
+* The JSON reporter's number handling is locale-independent. (#3176)
+* Removed leftover debug message from `catch_discover_tests` (#3177)
+* Fixed typo in the "Could not jump to the Nth element" exception message (#3181)
+
+### Improvements
+* `catch_discover_tests` registers tests in deterministic (alphabetical) order.
+* `catch_discover_tests` has been rewritten to be massively faster.
+ * Preparing the actual CTest script is significantly faster.
+ * Parsing the JSON test array is significantly faster.
+ * Running without `ADD_TAGS_AS_LABELS` set is 10-15% faster (as opposed to being the same speed) (#3169)
+ * The new implementation is about 4x-5x faster, so registering 500 tests now takes ~350ms (down from 1.1s).
+* JSON writing is faster
+ * Small improvement in writing non-string values
+ * ~6-40% improvement in writing string values that do not need escaping
+ * ~1-6% improvement in writing string values that do need escaping
+* Better support for infs and NaNs in JSON output
+
+
## 3.15.2
### Fixes
diff --git a/extras/catch_amalgamated.cpp b/extras/catch_amalgamated.cpp
index 4e1e631e..abf540b4 100644
--- a/extras/catch_amalgamated.cpp
+++ b/extras/catch_amalgamated.cpp
@@ -6,8 +6,8 @@
// SPDX-License-Identifier: BSL-1.0
-// Catch v3.15.2
-// Generated: 2026-07-07 20:39:49.445081
+// Catch v3.15.3
+// Generated: 2026-07-26 22:17:52.418168
// ----------------------------------------------------------
// This file is an amalgamation of multiple different files.
// You probably shouldn't edit it directly.
@@ -2394,7 +2394,7 @@ namespace Catch {
}
Version const& libraryVersion() {
- static Version version( 3, 15, 2, "", 0 );
+ static Version version( 3, 15, 3, "", 0 );
return version;
}
@@ -2541,7 +2541,7 @@ namespace Catch {
bool isValid = next();
if ( !isValid ) {
Detail::throw_generator_exception(
- "Coud not jump to Nth element: not enough elements" );
+ "Could not jump to Nth element: not enough elements" );
}
}
}
@@ -4678,12 +4678,28 @@ namespace Detail {
+#include
+#include
+
namespace Catch {
namespace {
- static bool needsEscape( char c ) {
- return c == '"' || c == '\\' || c == '\b' || c == '\f' ||
- c == '\n' || c == '\r' || c == '\t';
+ struct EscapeLUT {
+ bool escape[256] = {};
+ constexpr EscapeLUT() {
+ escape[static_cast( '"' )] = true;
+ escape[static_cast( '\\' )] = true;
+ escape[static_cast( '\b' )] = true;
+ escape[static_cast( '\f' )] = true;
+ escape[static_cast( '\n' )] = true;
+ escape[static_cast( '\r' )] = true;
+ escape[static_cast( '\t' )] = true;
+ }
+ };
+ static constexpr EscapeLUT escapeLUT{};
+
+ static constexpr bool needsEscape( char c ) {
+ return escapeLUT.escape[static_cast( c )];
}
static Catch::StringRef makeEscapeStringRef( char c ) {
@@ -4795,7 +4811,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 };
@@ -4809,26 +4828,55 @@ namespace Catch {
writeImpl( value, true );
}
+ void JsonValueWriter::write( float value ) && {
+ writeFloatingPoint( value );
+ }
+
+ void JsonValueWriter::write( double value ) && {
+ writeFloatingPoint( value );
+ }
+
void JsonValueWriter::write( bool value ) && {
writeImpl( value ? "true"_sr : "false"_sr, false );
}
+ template
+ void JsonValueWriter::writeFloatingPoint( T value ) {
+ if ( Catch::isnan( value ) ) {
+ writeImpl( "NaN"_sr, false );
+ } else if ( std::isinf( value ) ) {
+ writeImpl( value < 0 ? "-Infinity"_sr : "Infinity"_sr, false );
+ } else {
+ m_sstream << value;
+ writeImpl( m_sstream.str(), false );
+ }
+ }
+
void JsonValueWriter::writeImpl( Catch::StringRef value, bool quote ) {
- if ( quote ) { m_os << '"'; }
- size_t current_start = 0;
- for ( size_t i = 0; i < value.size(); ++i ) {
- if ( needsEscape( value[i] ) ) {
- if ( current_start < i ) {
- m_os << value.substr( current_start, i - current_start );
+ // Escaping only makes sense for actual strings, which are passed
+ // with `quote == true`. Non-quoted values are things like bools
+ // and numbers, which cannot create inputs that need escaping.
+ if ( !quote ) {
+ m_os << value;
+ } else {
+ m_os << '"';
+ size_t current_start = 0;
+ for ( size_t i = 0; i < value.size(); ++i ) {
+ if ( needsEscape( value[i] ) ) {
+ if ( current_start < i ) {
+ m_os
+ << value.substr( current_start, i - current_start );
+ }
+ m_os << makeEscapeStringRef( value[i] );
+ current_start = i + 1;
}
- m_os << makeEscapeStringRef( value[i] );
- current_start = i + 1;
}
+ if ( current_start < value.size() ) {
+ m_os << value.substr( current_start,
+ value.size() - current_start );
+ }
+ m_os << '"';
}
- if ( current_start < value.size() ) {
- m_os << value.substr( current_start, value.size() - current_start );
- }
- if ( quote ) { m_os << '"'; }
}
} // namespace Catch
diff --git a/extras/catch_amalgamated.hpp b/extras/catch_amalgamated.hpp
index 7e65eb93..a2d9a848 100644
--- a/extras/catch_amalgamated.hpp
+++ b/extras/catch_amalgamated.hpp
@@ -6,8 +6,8 @@
// SPDX-License-Identifier: BSL-1.0
-// Catch v3.15.2
-// Generated: 2026-07-07 20:39:49.020441
+// Catch v3.15.3
+// Generated: 2026-07-26 22:17:52.004020
// ----------------------------------------------------------
// This file is an amalgamation of multiple different files.
// You probably shouldn't edit it directly.
@@ -7572,7 +7572,7 @@ namespace Catch {
#define CATCH_VERSION_MAJOR 3
#define CATCH_VERSION_MINOR 15
-#define CATCH_VERSION_PATCH 2
+#define CATCH_VERSION_PATCH 3
#endif // CATCH_VERSION_MACROS_HPP_INCLUDED
@@ -7828,7 +7828,7 @@ namespace Generators {
void skipToNthElementImpl( std::size_t n ) override {
if ( n >= m_values.size() ) {
Detail::throw_generator_exception(
- "Coud not jump to Nth element: not enough elements" );
+ "Could not jump to Nth element: not enough elements" );
}
m_idx = n;
}
@@ -8011,7 +8011,7 @@ namespace Generators {
void skipToNthElementImpl( std::size_t n ) override {
if ( n >= m_target ) {
Detail::throw_generator_exception(
- "Coud not jump to Nth element: not enough elements" );
+ "Could not jump to Nth element: not enough elements" );
}
m_generator.skipToNthElement( n );
@@ -10309,11 +10309,19 @@ namespace Catch {
writeImpl( value, !std::is_arithmetic::value );
}
void write( StringRef value ) &&;
+ void write( float value ) &&;
+ void write( double value ) &&;
void write( bool value ) &&;
private:
void writeImpl( StringRef value, bool quote );
+ // Helper to deal with non-finite floating point values, which
+ // are not standard JSON, but we use JS/Python/etc. approach of
+ // emitting `NaN`, `Infinity`, `-Infinity` as number(like).
+ template
+ void writeFloatingPoint( T value );
+
// Without this SFINAE, this overload is a better match
// for `std::string`, `char const*`, `char const[N]` args.
// While it would still work, it would cause code bloat
diff --git a/meson.build b/meson.build
index 25fb3c2d..2f01f2c8 100644
--- a/meson.build
+++ b/meson.build
@@ -8,7 +8,7 @@
project(
'catch2',
'cpp',
- version: '3.15.2', # CML version placeholder, don't delete
+ version: '3.15.3', # CML version placeholder, don't delete
license: 'BSL-1.0',
meson_version: '>=0.54.1',
)
diff --git a/src/catch2/catch_version.cpp b/src/catch2/catch_version.cpp
index 47aef8e8..f3a9f408 100644
--- a/src/catch2/catch_version.cpp
+++ b/src/catch2/catch_version.cpp
@@ -36,7 +36,7 @@ namespace Catch {
}
Version const& libraryVersion() {
- static Version version( 3, 15, 2, "", 0 );
+ static Version version( 3, 15, 3, "", 0 );
return version;
}
diff --git a/src/catch2/catch_version_macros.hpp b/src/catch2/catch_version_macros.hpp
index 71103dbf..333e113f 100644
--- a/src/catch2/catch_version_macros.hpp
+++ b/src/catch2/catch_version_macros.hpp
@@ -10,6 +10,6 @@
#define CATCH_VERSION_MAJOR 3
#define CATCH_VERSION_MINOR 15
-#define CATCH_VERSION_PATCH 2
+#define CATCH_VERSION_PATCH 3
#endif // CATCH_VERSION_MACROS_HPP_INCLUDED