Handle ANSI escape sequences when performing column wrapping
## Description
This PR adds functionality to skip around ANSI escape sequences in catch_textflow so they do not contribute to line length and line wrapping code does not split escape sequences in the middle. I've implemented this by creating a `AnsiSkippingString` abstraction that has a bidirectional iterator that can skip around escape sequences while iterating. Additionally I refactored `Column::const_iterator` to be iterator-based rather than index-based so this abstraction is a simple drop-in for `std::string`.

Currently only color sequences are handled, other escape sequences are left unaffected.

Motivation: Text with ANSI color sequences gets messed up when being output by Catch2 #2833.


For example, the current behavior of string with lots of escapes:
![image](https://github.com/catchorg/Catch2/assets/51220084/fbd9320f-2ca9-45ae-b741-ae00679c572c)
The example I have handy is a syntax-highlighted assertion from another library.

Behavior with this PR:
![image](https://github.com/catchorg/Catch2/assets/51220084/0a60148b-9e3b-4d9f-bade-da094be6e9c1)



## GitHub Issues
Closes #2833
{"label": "catchorg:devel", "ref": "devel", "sha": "7ce35799767de7b9c6ba836c72e479c5f70219a3"}
**Description**

I'd like to provide diagnostic messages from a `Catch::Matchers::MatcherBase::describe` function that are colored with ANSI sequences, however Catch2's line wrapping interferes with this.

Would a PR to update the line wrapping logic be welcomed?

**Additional context**

I have an assertion library that I'd like to provide a catch2 integration for. Without color codes, with my current attempt to make it work in Catch2, it looks like this:

![image](https://github.com/catchorg/Catch2/assets/51220084/4f410b0c-a0c4-472d-935a-37e0068770ba)

However with color codes the lines are messed up and escape sequences are split in the middle:

![image](https://github.com/catchorg/Catch2/assets/51220084/a600caf7-f047-4c66-90cf-dd41f9eef258)
Handle ANSI escape sequences during text wrapping
diff --git a/src/catch2/internal/catch_textflow.cpp b/src/catch2/internal/catch_textflow.cpp
index 857fd2b9f4..1c21d20e56 100644
--- a/src/catch2/internal/catch_textflow.cpp
+++ b/src/catch2/internal/catch_textflow.cpp
@@ -26,117 +26,228 @@ namespace {
         return std::memchr( chars, c, sizeof( chars ) - 1 ) != nullptr;
     }
 
-    bool isBoundary( std::string const& line, size_t at ) {
-        assert( at > 0 );
-        assert( at <= line.size() );
-
-        return at == line.size() ||
-               ( isWhitespace( line[at] ) && !isWhitespace( line[at - 1] ) ) ||
-               isBreakableBefore( line[at] ) ||
-               isBreakableAfter( line[at - 1] );
-    }
-
 } // namespace
 
 namespace Catch {
     namespace TextFlow {
+        void AnsiSkippingString::preprocessString() {
+            for ( auto it = m_string.begin(); it != m_string.end(); ) {
+                // try to read through an ansi sequence
+                while ( it != m_string.end() && *it == '\033' &&
+                        it + 1 != m_string.end() && *( it + 1 ) == '[' ) {
+                    auto cursor = it + 2;
+                    while ( cursor != m_string.end() &&
+                            ( isdigit( *cursor ) || *cursor == ';' ) ) {
+                        ++cursor;
+                    }
+                    if ( cursor == m_string.end() || *cursor != 'm' ) {
+                        break;
+                    }
+                    // 'm' -> 0xff
+                    *cursor = AnsiSkippingString::sentinel;
+                    // if we've read an ansi sequence, set the iterator and
+                    // return to the top of the loop
+                    it = cursor + 1;
+                }
+                if ( it != m_string.end() ) {
+                    ++m_size;
+                    ++it;
+                }
+            }
+        }
+
+        AnsiSkippingString::AnsiSkippingString( std::string const& text ):
+            m_string( text ) {
+            preprocessString();
+        }
+
+        AnsiSkippingString::AnsiSkippingString( std::string&& text ):
+            m_string( CATCH_MOVE( text ) ) {
+            preprocessString();
+        }
+
+        AnsiSkippingString::const_iterator AnsiSkippingString::begin() const {
+            return const_iterator( m_string );
+        }
+
+        AnsiSkippingString::const_iterator AnsiSkippingString::end() const {
+            return const_iterator( m_string, const_iterator::EndTag{} );
+        }
+
+        std::string AnsiSkippingString::substring( const_iterator begin,
+                                                   const_iterator end ) const {
+            // There's one caveat here to an otherwise simple substring: when
+            // making a begin iterator we might have skipped ansi sequences at
+            // the start. If `begin` here is a begin iterator, skipped over
+            // initial ansi sequences, we'll use the true beginning of the
+            // string. Lastly: We need to transform any chars we replaced with
+            // 0xff back to 'm'
+            auto str = std::string( begin == this->begin() ? m_string.begin()
+                                                           : begin.m_it,
+                                    end.m_it );
+            std::transform( str.begin(), str.end(), str.begin(), []( char c ) {
+                return c == AnsiSkippingString::sentinel ? 'm' : c;
+            } );
+            return str;
+        }
+
+        void AnsiSkippingString::const_iterator::tryParseAnsiEscapes() {
+            // check if we've landed on an ansi sequence, and if so read through
+            // it
+            while ( m_it != m_string->end() && *m_it == '\033' &&
+                    m_it + 1 != m_string->end() &&  *( m_it + 1 ) == '[' ) {
+                auto cursor = m_it + 2;
+                while ( cursor != m_string->end() &&
+                        ( isdigit( *cursor ) || *cursor == ';' ) ) {
+                    ++cursor;
+                }
+                if ( cursor == m_string->end() ||
+                     *cursor != AnsiSkippingString::sentinel ) {
+                    break;
+                }
+                // if we've read an ansi sequence, set the iterator and
+                // return to the top of the loop
+                m_it = cursor + 1;
+            }
+        }
+
+        void AnsiSkippingString::const_iterator::advance() {
+            assert( m_it != m_string->end() );
+            m_it++;
+            tryParseAnsiEscapes();
+        }
+
+        void AnsiSkippingString::const_iterator::unadvance() {
+            assert( m_it != m_string->begin() );
+            m_it--;
+            // if *m_it is 0xff, scan back to the \033 and then m_it-- once more
+            // (and repeat check)
+            while ( *m_it == AnsiSkippingString::sentinel ) {
+                while ( *m_it != '\033' ) {
+                    assert( m_it != m_string->begin() );
+                    m_it--;
+                }
+                // if this happens, we must have been a begin iterator that had
+                // skipped over ansi sequences at the start of a string
+                assert( m_it != m_string->begin() );
+                assert( *m_it == '\033' );
+                m_it--;
+            }
+        }
+
+        static bool isBoundary( AnsiSkippingString const& line,
+                                AnsiSkippingString::const_iterator it ) {
+            return it == line.end() ||
+                   ( isWhitespace( *it ) &&
+                     !isWhitespace( *it.oneBefore() ) ) ||
+                   isBreakableBefore( *it ) ||
+                   isBreakableAfter( *it.oneBefore() );
+        }
 
         void Column::const_iterator::calcLength() {
             m_addHyphen = false;
             m_parsedTo = m_lineStart;
+            AnsiSkippingString const& current_line = m_column.m_string;
 
-            std::string const& current_line = m_column.m_string;
-            if ( current_line[m_lineStart] == '\n' ) {
-                ++m_parsedTo;
+            if ( m_parsedTo == current_line.end() ) {
+                m_lineEnd = m_parsedTo;
+                return;
             }
 
+            assert( m_lineStart != current_line.end() );
+            if ( *m_lineStart == '\n' ) { ++m_parsedTo; }
+
             const auto maxLineLength = m_column.m_width - indentSize();
-            const auto maxParseTo = std::min(current_line.size(), m_lineStart + maxLineLength);
-            while ( m_parsedTo < maxParseTo &&
-                    current_line[m_parsedTo] != '\n' ) {
+            std::size_t lineLength = 0;
+            while ( m_parsedTo != current_line.end() &&
+                    lineLength < maxLineLength && *m_parsedTo != '\n' ) {
                 ++m_parsedTo;
+                ++lineLength;
             }
 
             // If we encountered a newline before the column is filled,
             // then we linebreak at the newline and consider this line
             // finished.
-            if ( m_parsedTo < m_lineStart + maxLineLength ) {
-                m_lineLength = m_parsedTo - m_lineStart;
+            if ( lineLength < maxLineLength ) {
+                m_lineEnd = m_parsedTo;
             } else {
                 // Look for a natural linebreak boundary in the column
                 // (We look from the end, so that the first found boundary is
                 // the right one)
-                size_t newLineLength = maxLineLength;
-                while ( newLineLength > 0 && !isBoundary( current_line, m_lineStart + newLineLength ) ) {
-                    --newLineLength;
+                m_lineEnd = m_parsedTo;
+                while ( lineLength > 0 &&
+                        !isBoundary( current_line, m_lineEnd ) ) {
+                    --lineLength;
+                    --m_lineEnd;
                 }
-                while ( newLineLength > 0 &&
-                        isWhitespace( current_line[m_lineStart + newLineLength - 1] ) ) {
-                    --newLineLength;
+                while ( lineLength > 0 &&
+                        isWhitespace( *m_lineEnd.oneBefore() ) ) {
+                    --lineLength;
+                    --m_lineEnd;
                 }
 
-                // If we found one, then that is where we linebreak
-                if ( newLineLength > 0 ) {
-                    m_lineLength = newLineLength;
-                } else {
-                    // Otherwise we have to split text with a hyphen
+                // If we found one, then that is where we linebreak, otherwise
+                // we have to split text with a hyphen
+                if ( lineLength == 0 ) {
                     m_addHyphen = true;
-                    m_lineLength = maxLineLength - 1;
+                    m_lineEnd = m_parsedTo.oneBefore();
                 }
             }
         }
 
         size_t Column::const_iterator::indentSize() const {
-            auto initial =
-                m_lineStart == 0 ? m_column.m_initialIndent : std::string::npos;
+            auto initial = m_lineStart == m_column.m_string.begin()
+                               ? m_column.m_initialIndent
+                               : std::string::npos;
             return initial == std::string::npos ? m_column.m_indent : initial;
         }
 
-        std::string
-        Column::const_iterator::addIndentAndSuffix( size_t position,
-                                              size_t length ) const {
+        std::string Column::const_iterator::addIndentAndSuffix(
+            AnsiSkippingString::const_iterator start,
+            AnsiSkippingString::const_iterator end ) const {
             std::string ret;
             const auto desired_indent = indentSize();
-            ret.reserve( desired_indent + length + m_addHyphen );
+            // ret.reserve( desired_indent + (end - start) + m_addHyphen );
             ret.append( desired_indent, ' ' );
-            ret.append( m_column.m_string, position, length );
-            if ( m_addHyphen ) {
-                ret.push_back( '-' );
-            }
+            // ret.append( start, end );
+            ret += m_column.m_string.substring( start, end );
+            if ( m_addHyphen ) { ret.push_back( '-' ); }
 
             return ret;
         }
 
-        Column::const_iterator::const_iterator( Column const& column ): m_column( column ) {
+        Column::const_iterator::const_iterator( Column const& column ):
+            m_column( column ),
+            m_lineStart( column.m_string.begin() ),
+            m_lineEnd( column.m_string.begin() ),
+            m_parsedTo( column.m_string.begin() ) {
             assert( m_column.m_width > m_column.m_indent );
             assert( m_column.m_initialIndent == std::string::npos ||
                     m_column.m_width > m_column.m_initialIndent );
             calcLength();
-            if ( m_lineLength == 0 ) {
-                m_lineStart = m_column.m_string.size();
+            if ( m_lineStart == m_lineEnd ) {
+                m_lineStart = m_column.m_string.end();
             }
         }
 
         std::string Column::const_iterator::operator*() const {
             assert( m_lineStart <= m_parsedTo );
-            return addIndentAndSuffix( m_lineStart, m_lineLength );
+            return addIndentAndSuffix( m_lineStart, m_lineEnd );
         }
 
         Column::const_iterator& Column::const_iterator::operator++() {
-            m_lineStart += m_lineLength;
-            std::string const& current_line = m_column.m_string;
-            if ( m_lineStart < current_line.size() && current_line[m_lineStart] == '\n' ) {
-                m_lineStart += 1;
+            m_lineStart = m_lineEnd;
+            AnsiSkippingString const& current_line = m_column.m_string;
+            if ( m_lineStart != current_line.end() && *m_lineStart == '\n' ) {
+                m_lineStart++;
             } else {
-                while ( m_lineStart < current_line.size() &&
-                        isWhitespace( current_line[m_lineStart] ) ) {
+                while ( m_lineStart != current_line.end() &&
+                        isWhitespace( *m_lineStart ) ) {
                     ++m_lineStart;
                 }
             }
 
-            if ( m_lineStart != current_line.size() ) {
-                calcLength();
-            }
+            if ( m_lineStart != current_line.end() ) { calcLength(); }
             return *this;
         }
 
@@ -233,25 +344,25 @@ namespace Catch {
             return os;
         }
 
-        Columns operator+(Column const& lhs, Column const& rhs) {
+        Columns operator+( Column const& lhs, Column const& rhs ) {
             Columns cols;
             cols += lhs;
             cols += rhs;
             return cols;
         }
-        Columns operator+(Column&& lhs, Column&& rhs) {
+        Columns operator+( Column&& lhs, Column&& rhs ) {
             Columns cols;
             cols += CATCH_MOVE( lhs );
             cols += CATCH_MOVE( rhs );
             return cols;
         }
 
-        Columns& operator+=(Columns& lhs, Column const& rhs) {
+        Columns& operator+=( Columns& lhs, Column const& rhs ) {
             lhs.m_columns.push_back( rhs );
             return lhs;
         }
-        Columns& operator+=(Columns& lhs, Column&& rhs) {
-            lhs.m_columns.push_back( CATCH_MOVE(rhs) );
+        Columns& operator+=( Columns& lhs, Column&& rhs ) {
+            lhs.m_columns.push_back( CATCH_MOVE( rhs ) );
             return lhs;
         }
         Columns operator+( Columns const& lhs, Column const& rhs ) {
diff --git a/src/catch2/internal/catch_textflow.hpp b/src/catch2/internal/catch_textflow.hpp
index a78451d559..2d9d78a50a 100644
--- a/src/catch2/internal/catch_textflow.hpp
+++ b/src/catch2/internal/catch_textflow.hpp
@@ -20,6 +20,107 @@ namespace Catch {
 
         class Columns;
 
+        /**
+         * Abstraction for a string with ansi escape sequences that
+         * automatically skips over escapes when iterating. Only graphical
+         * escape sequences are considered.
+         *
+         * Internal representation:
+         * An escape sequence looks like \033[39;49m
+         * We need bidirectional iteration and the unbound length of escape
+         * sequences poses a problem for operator-- To make this work we'll
+         * replace the last `m` with a 0xff (this is a codepoint that won't have
+         * any utf-8 meaning).
+         */
+        class AnsiSkippingString {
+            std::string m_string;
+            std::size_t m_size = 0;
+
+            // perform 0xff replacement and calculate m_size
+            void preprocessString();
+
+        public:
+            class const_iterator;
+            using iterator = const_iterator;
+            // note: must be u-suffixed or this will cause a "truncation of
+            // constant value" warning on MSVC
+            static constexpr char sentinel = static_cast<char>( 0xffu );
+
+            explicit AnsiSkippingString( std::string const& text );
+            explicit AnsiSkippingString( std::string&& text );
+
+            const_iterator begin() const;
+            const_iterator end() const;
+
+            size_t size() const { return m_size; }
+
+            std::string substring( const_iterator begin,
+                                   const_iterator end ) const;
+        };
+
+        class AnsiSkippingString::const_iterator {
+            friend AnsiSkippingString;
+            struct EndTag {};
+
+            const std::string* m_string;
+            std::string::const_iterator m_it;
+
+            explicit const_iterator( const std::string& string, EndTag ):
+                m_string( &string ), m_it( string.end() ) {}
+
+            void tryParseAnsiEscapes();
+            void advance();
+            void unadvance();
+
+        public:
+            using difference_type = std::ptrdiff_t;
+            using value_type = char;
+            using pointer = value_type*;
+            using reference = value_type&;
+            using iterator_category = std::bidirectional_iterator_tag;
+
+            explicit const_iterator( const std::string& string ):
+                m_string( &string ), m_it( string.begin() ) {
+                tryParseAnsiEscapes();
+            }
+
+            char operator*() const { return *m_it; }
+
+            const_iterator& operator++() {
+                advance();
+                return *this;
+            }
+            const_iterator operator++( int ) {
+                iterator prev( *this );
+                operator++();
+                return prev;
+            }
+            const_iterator& operator--() {
+                unadvance();
+                return *this;
+            }
+            const_iterator operator--( int ) {
+                iterator prev( *this );
+                operator--();
+                return prev;
+            }
+
+            bool operator==( const_iterator const& other ) const {
+                return m_it == other.m_it;
+            }
+            bool operator!=( const_iterator const& other ) const {
+                return !operator==( other );
+            }
+            bool operator<=( const_iterator const& other ) const {
+                return m_it <= other.m_it;
+            }
+
+            const_iterator oneBefore() const {
+                auto it = *this;
+                return --it;
+            }
+        };
+
         /**
          * Represents a column of text with specific width and indentation
          *
@@ -29,10 +130,11 @@ namespace Catch {
          */
         class Column {
             // String to be written out
-            std::string m_string;
+            AnsiSkippingString m_string;
             // Width of the column for linebreaking
             size_t m_width = CATCH_CONFIG_CONSOLE_WIDTH - 1;
-            // Indentation of other lines (including first if initial indent is unset)
+            // Indentation of other lines (including first if initial indent is
+            // unset)
             size_t m_indent = 0;
             // Indentation of the first line
             size_t m_initialIndent = std::string::npos;
@@ -47,16 +149,19 @@ namespace Catch {
 
                 Column const& m_column;
                 // Where does the current line start?
-                size_t m_lineStart = 0;
+                AnsiSkippingString::const_iterator m_lineStart;
                 // How long should the current line be?
-                size_t m_lineLength = 0;
+                AnsiSkippingString::const_iterator m_lineEnd;
                 // How far have we checked the string to iterate?
-                size_t m_parsedTo = 0;
+                AnsiSkippingString::const_iterator m_parsedTo;
                 // Should a '-' be appended to the line?
                 bool m_addHyphen = false;
 
                 const_iterator( Column const& column, EndTag ):
-                    m_column( column ), m_lineStart( m_column.m_string.size() ) {}
+                    m_column( column ),
+                    m_lineStart( m_column.m_string.end() ),
+                    m_lineEnd( column.m_string.end() ),
+                    m_parsedTo( column.m_string.end() ) {}
 
                 // Calculates the length of the current line
                 void calcLength();
@@ -66,8 +171,9 @@ namespace Catch {
 
                 // Creates an indented and (optionally) suffixed string from
                 // current iterator position, indentation and length.
-                std::string addIndentAndSuffix( size_t position,
-                                                size_t length ) const;
+                std::string addIndentAndSuffix(
+                    AnsiSkippingString::const_iterator start,
+                    AnsiSkippingString::const_iterator end ) const;
 
             public:
                 using difference_type = std::ptrdiff_t;
@@ -84,7 +190,8 @@ namespace Catch {
                 const_iterator operator++( int );
 
                 bool operator==( const_iterator const& other ) const {
-                    return m_lineStart == other.m_lineStart && &m_column == &other.m_column;
+                    return m_lineStart == other.m_lineStart &&
+                           &m_column == &other.m_column;
                 }
                 bool operator!=( const_iterator const& other ) const {
                     return !operator==( other );
@@ -94,7 +201,7 @@ namespace Catch {
 
             explicit Column( std::string const& text ): m_string( text ) {}
             explicit Column( std::string&& text ):
-                m_string( CATCH_MOVE(text)) {}
+                m_string( CATCH_MOVE( text ) ) {}
 
             Column& width( size_t newWidth ) & {
                 assert( newWidth > 0 );
@@ -125,7 +232,9 @@ namespace Catch {
 
             size_t width() const { return m_width; }
             const_iterator begin() const { return const_iterator( *this ); }
-            const_iterator end() const { return { *this, const_iterator::EndTag{} }; }
+            const_iterator end() const {
+                return { *this, const_iterator::EndTag{} };
+            }
 
             friend std::ostream& operator<<( std::ostream& os,
                                              Column const& col );

diff --git a/tests/SelfTest/IntrospectiveTests/TextFlow.tests.cpp b/tests/SelfTest/IntrospectiveTests/TextFlow.tests.cpp
index 653f65ba4c..de03ed09af 100644
--- a/tests/SelfTest/IntrospectiveTests/TextFlow.tests.cpp
+++ b/tests/SelfTest/IntrospectiveTests/TextFlow.tests.cpp
@@ -12,6 +12,7 @@
 #include <sstream>
 
 using Catch::TextFlow::Column;
+using Catch::TextFlow::AnsiSkippingString;
 
 namespace {
     static std::string as_written(Column const& c) {
@@ -198,3 +199,202 @@ TEST_CASE( "#1400 - TextFlow::Column wrapping would sometimes duplicate words",
             "  in \n"
             "  convallis posuere, libero nisi ultricies orci, nec lobortis.");
 }
+
+TEST_CASE( "TextFlow::AnsiSkippingString skips ansi sequences",
+           "[TextFlow][ansiskippingstring][approvals]" ) {
+
+    SECTION("basic string") {
+        std::string text = "a\033[38;2;98;174;239mb\033[38mc\033[0md\033[me";
+        AnsiSkippingString str(text);
+
+        SECTION( "iterates forward" ) {
+            auto it = str.begin();
+            CHECK(*it == 'a');
+            ++it;
+            CHECK(*it == 'b');
+            ++it;
+            CHECK(*it == 'c');
+            ++it;
+            CHECK(*it == 'd');
+            ++it;
+            CHECK(*it == 'e');
+            ++it;
+            CHECK(it == str.end());
+        }
+        SECTION( "iterates backwards" ) {
+            auto it = str.end();
+            --it;
+            CHECK(*it == 'e');
+            --it;
+            CHECK(*it == 'd');
+            --it;
+            CHECK(*it == 'c');
+            --it;
+            CHECK(*it == 'b');
+            --it;
+            CHECK(*it == 'a');
+            CHECK(it == str.begin());
+        }
+    }
+
+    SECTION( "ansi escape sequences at the start" ) {
+        std::string text = "\033[38;2;98;174;239ma\033[38;2;98;174;239mb\033[38mc\033[0md\033[me";
+        AnsiSkippingString str(text);
+        auto it = str.begin();
+        CHECK(*it == 'a');
+        ++it;
+        CHECK(*it == 'b');
+        ++it;
+        CHECK(*it == 'c');
+        ++it;
+        CHECK(*it == 'd');
+        ++it;
+        CHECK(*it == 'e');
+        ++it;
+        CHECK(it == str.end());
+        --it;
+        CHECK(*it == 'e');
+        --it;
+        CHECK(*it == 'd');
+        --it;
+        CHECK(*it == 'c');
+        --it;
+        CHECK(*it == 'b');
+        --it;
+        CHECK(*it == 'a');
+        CHECK(it == str.begin());
+    }
+
+    SECTION( "ansi escape sequences at the end" ) {
+        std::string text = "a\033[38;2;98;174;239mb\033[38mc\033[0md\033[me\033[38;2;98;174;239m";
+        AnsiSkippingString str(text);
+        auto it = str.begin();
+        CHECK(*it == 'a');
+        ++it;
+        CHECK(*it == 'b');
+        ++it;
+        CHECK(*it == 'c');
+        ++it;
+        CHECK(*it == 'd');
+        ++it;
+        CHECK(*it == 'e');
+        ++it;
+        CHECK(it == str.end());
+        --it;
+        CHECK(*it == 'e');
+        --it;
+        CHECK(*it == 'd');
+        --it;
+        CHECK(*it == 'c');
+        --it;
+        CHECK(*it == 'b');
+        --it;
+        CHECK(*it == 'a');
+        CHECK(it == str.begin());
+    }
+
+    SECTION( "skips consecutive escapes" ) {
+        std::string text = "\033[38;2;98;174;239m\033[38;2;98;174;239ma\033[38;2;98;174;239mb\033[38m\033[38m\033[38mc\033[0md\033[me";
+        AnsiSkippingString str(text);
+        auto it = str.begin();
+        CHECK(*it == 'a');
+        ++it;
+        CHECK(*it == 'b');
+        ++it;
+        CHECK(*it == 'c');
+        ++it;
+        CHECK(*it == 'd');
+        ++it;
+        CHECK(*it == 'e');
+        ++it;
+        CHECK(it == str.end());
+        --it;
+        CHECK(*it == 'e');
+        --it;
+        CHECK(*it == 'd');
+        --it;
+        CHECK(*it == 'c');
+        --it;
+        CHECK(*it == 'b');
+        --it;
+        CHECK(*it == 'a');
+        CHECK(it == str.begin());
+    }
+
+    SECTION( "handles incomplete ansi sequences" ) {
+        std::string text = "a\033[b\033[30c\033[30;d\033[30;2e";
+        AnsiSkippingString str(text);
+        CHECK(std::string(str.begin(), str.end()) == text);
+    }
+}
+
+TEST_CASE( "TextFlow::AnsiSkippingString computes the size properly",
+           "[TextFlow][ansiskippingstring][approvals]" ) {
+    std::string text = "\033[38;2;98;174;239m\033[38;2;98;174;239ma\033[38;2;98;174;239mb\033[38m\033[38m\033[38mc\033[0md\033[me";
+    AnsiSkippingString str(text);
+    CHECK(str.size() == 5);
+}
+
+TEST_CASE( "TextFlow::AnsiSkippingString substrings properly",
+           "[TextFlow][ansiskippingstring][approvals]" ) {
+    SECTION("basic test") {
+        std::string text = "a\033[38;2;98;174;239mb\033[38mc\033[0md\033[me";
+        AnsiSkippingString str(text);
+        auto a = str.begin();
+        auto b = str.begin();
+        ++b;
+        ++b;
+        CHECK(str.substring(a, b) == "a\033[38;2;98;174;239mb\033[38m");
+        ++a;
+        ++b;
+        CHECK(str.substring(a, b) == "b\033[38mc\033[0m");
+        CHECK(str.substring(a, str.end()) == "b\033[38mc\033[0md\033[me");
+        CHECK(str.substring(str.begin(), str.end()) == text);
+    }
+    SECTION("escapes at the start") {
+        std::string text = "\033[38;2;98;174;239m\033[38;2;98;174;239ma\033[38;2;98;174;239mb\033[38m\033[38m\033[38mc\033[0md\033[me";
+        AnsiSkippingString str(text);
+        auto a = str.begin();
+        auto b = str.begin();
+        ++b;
+        ++b;
+        CHECK(str.substring(a, b) == "\033[38;2;98;174;239m\033[38;2;98;174;239ma\033[38;2;98;174;239mb\033[38m\033[38m\033[38m");
+        ++a;
+        ++b;
+        CHECK(str.substring(a, b) == "b\033[38m\033[38m\033[38mc\033[0m");
+        CHECK(str.substring(a, str.end()) == "b\033[38m\033[38m\033[38mc\033[0md\033[me");
+        CHECK(str.substring(str.begin(), str.end()) == text);
+    }
+    SECTION("escapes at the end") {
+        std::string text = "a\033[38;2;98;174;239mb\033[38mc\033[0md\033[me\033[38m";
+        AnsiSkippingString str(text);
+        auto a = str.begin();
+        auto b = str.begin();
+        ++b;
+        ++b;
+        CHECK(str.substring(a, b) == "a\033[38;2;98;174;239mb\033[38m");
+        ++a;
+        ++b;
+        CHECK(str.substring(a, b) == "b\033[38mc\033[0m");
+        CHECK(str.substring(a, str.end()) == "b\033[38mc\033[0md\033[me\033[38m");
+        CHECK(str.substring(str.begin(), str.end()) == text);
+    }
+}
+
+TEST_CASE( "TextFlow::Column skips ansi escape sequences",
+           "[TextFlow][column][approvals]" ) {
+    std::string text = "\033[38;2;98;174;239m\033[38;2;198;120;221mThe quick brown \033[38;2;198;120;221mfox jumped over the lazy dog\033[0m";
+    Column col(text);
+
+    SECTION( "width=20" ) {
+        col.width( 20 );
+        REQUIRE( as_written( col ) == "\033[38;2;98;174;239m\033[38;2;198;120;221mThe quick brown \033[38;2;198;120;221mfox\n"
+                                      "jumped over the lazy\n"
+                                      "dog\033[0m" );
+    }
+
+    SECTION( "width=80" ) {
+        col.width( 80 );
+        REQUIRE( as_written( col ) == text );
+    }
+}

{"testspecs::nomatchedtestsfail": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "randomtestordering": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::reporters::output": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "libidentitytest": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "reporters:rngseed:compact": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "reporters:rngseed:xml": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "unmatchedoutputfilter": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "filenameastagsmatching": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::tests::xmloutput": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::tags::exitcode": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "warnings::unmatchedtestspecisaccepted": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "reporters:filters:xml": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::tests::output": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "reporters:rngseed:json": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::listeners::xmloutput": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "escapespecialcharactersintestnames": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "warnings::multiplewarningscanbespecified": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "noassertions": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "reporters::reporterspecificcolouroverridesdefaultcolour": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "reporters:filters:json": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "benchmarking::failurereporting::failedassertion": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "errorhandling::invalidtestspecexitsearly": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "multireporter::capturingreportersdontpropagatestdout": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "multireporter::noncapturingreporterspropagatestdout": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "reporters:rngseed:console": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "runtests": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "testspecs::overridefailurewithnomatchedtests": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "testspecs::combiningmatchingandnonmatchingisok-2": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "testsinfile::escapespecialcharacters": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "testsinfile::invalidtestnames-1": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "filteredsection-2": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::tags::xmloutput": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "colours::colourmodecanbeexplicitlysettoansi": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "reporters:filters:console": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "reporters::junit::namespacesarenormalized": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "reporters:rngseed:junit": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "reporters:filters:junit": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "filteredsection-1": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "versioncheck": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "benchmarking::failurereporting::failmacro": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "reporters:filters:sonarqube": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "checkconvenienceheaders": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::tags::output": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::listeners::exitcode": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "reporters:filters:compact": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "approvaltests": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "testspecs::warnunmatchedtestspecfailswithunmatchedtestspec": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "benchmarking::failurereporting::shouldfailisrespected": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "negativespecnohiddentests": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "outputs::dashasoutlocationsendsoutputtostdout": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "filteredsection::generatorsdontcauseinfiniteloop-2": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "testspecs::combiningmatchingandnonmatchingisok-1": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::tests::exitcode": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "reporters:rngseed:sonarqube": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "reporters:rngseed:tap": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "reporters:filters:tap": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::tests::quiet": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "testsinfile::simplespecs": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "reporters::dashaslocationinreporterspecsendsoutputtostdout": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "regressioncheck-1670": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::reporters::exitcode": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "testspecs::overrideallskipfailure": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "filenameastagstest": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "testspecs::nonmatchingtestspecisroundtrippable": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::listeners::output": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "benchmarking::skipbenchmarkmacros": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "reporters::unrecognizedoptioninspeccauseserror": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "tagalias": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::reporters::xmloutput": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "benchmarking::failurereporting::throwingbenchmark": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "filteredsection::generatorsdontcauseinfiniteloop-1": {"run": "PASS", "test": "NONE", "fix": "PASS"}}
{"have_flag__wfloat_equal": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag__wunused_function": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag__wmissing_declarations": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag__wmismatched_tags": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag__wold_style_cast": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag__wstrict_aliasing": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag__wparentheses": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag__wredundant_decls": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag__wextra_semi": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag__wundef": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag__wdeprecated": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag__wuninitialized": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag__wmismatched_new_delete": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag__wexceptions": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag__wunused_parameter": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag__winit_self": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag__wextra": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag__wsubobject_linkage": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag__wpedantic": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag__wvla": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag__wall": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag__wnull_dereference": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag__wmissing_braces": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag__woverloaded_virtual": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag__wcast_align": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag__wmisleading_indentation": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag__wshadow": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag__wmissing_noreturn": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag__ffile_prefix_map__home_catch2__": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag__wunused": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag__wsuggest_override": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag__wcatch_value": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag__wreorder": {"run": "PASS", "test": "PASS", "fix": "PASS"}}
{"testspecs::nomatchedtestsfail": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "randomtestordering": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::reporters::output": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "libidentitytest": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "reporters:rngseed:compact": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "reporters:rngseed:xml": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "unmatchedoutputfilter": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "filenameastagsmatching": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::tests::xmloutput": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::tags::exitcode": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "warnings::unmatchedtestspecisaccepted": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "reporters:filters:xml": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::tests::output": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "reporters:rngseed:json": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::listeners::xmloutput": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "escapespecialcharactersintestnames": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "warnings::multiplewarningscanbespecified": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "noassertions": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "reporters::reporterspecificcolouroverridesdefaultcolour": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "reporters:filters:json": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "benchmarking::failurereporting::failedassertion": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "errorhandling::invalidtestspecexitsearly": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "multireporter::capturingreportersdontpropagatestdout": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "multireporter::noncapturingreporterspropagatestdout": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "reporters:rngseed:console": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "runtests": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "testspecs::overridefailurewithnomatchedtests": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "testspecs::combiningmatchingandnonmatchingisok-2": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "testsinfile::escapespecialcharacters": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "testsinfile::invalidtestnames-1": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "filteredsection-2": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::tags::xmloutput": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "colours::colourmodecanbeexplicitlysettoansi": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "reporters:filters:console": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "reporters::junit::namespacesarenormalized": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "reporters:rngseed:junit": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "reporters:filters:junit": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "filteredsection-1": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "versioncheck": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "benchmarking::failurereporting::failmacro": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "reporters:filters:sonarqube": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "checkconvenienceheaders": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::tags::output": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::listeners::exitcode": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "reporters:filters:compact": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "approvaltests": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "testspecs::warnunmatchedtestspecfailswithunmatchedtestspec": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "benchmarking::failurereporting::shouldfailisrespected": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "negativespecnohiddentests": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "outputs::dashasoutlocationsendsoutputtostdout": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "filteredsection::generatorsdontcauseinfiniteloop-2": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "testspecs::combiningmatchingandnonmatchingisok-1": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::tests::exitcode": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "reporters:rngseed:sonarqube": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "reporters:rngseed:tap": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "reporters:filters:tap": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::tests::quiet": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "testsinfile::simplespecs": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "reporters::dashaslocationinreporterspecsendsoutputtostdout": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "regressioncheck-1670": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::reporters::exitcode": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "testspecs::overrideallskipfailure": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "filenameastagstest": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "testspecs::nonmatchingtestspecisroundtrippable": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::listeners::output": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "benchmarking::skipbenchmarkmacros": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "reporters::unrecognizedoptioninspeccauseserror": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "tagalias": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::reporters::xmloutput": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "benchmarking::failurereporting::throwingbenchmark": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "filteredsection::generatorsdontcauseinfiniteloop-1": {"run": "PASS", "test": "NONE", "fix": "PASS"}}
{"passed_count": 104, "failed_count": 14, "skipped_count": 0, "passed_tests": ["testspecs::nomatchedtestsfail", "randomtestordering", "libidentitytest", "have_flag__wfloat_equal", "reporters:rngseed:compact", "reporters:rngseed:xml", "unmatchedoutputfilter", "have_flag__wmissing_declarations", "filenameastagsmatching", "have_flag__wmismatched_tags", "list::tests::output", "have_flag__wold_style_cast", "have_flag__wstrict_aliasing", "noassertions", "reporters:filters:json", "benchmarking::failurereporting::failedassertion", "reporters:rngseed:console", "runtests", "testspecs::combiningmatchingandnonmatchingisok-2", "testsinfile::escapespecialcharacters", "testsinfile::invalidtestnames-1", "filteredsection-2", "list::tags::xmloutput", "colours::colourmodecanbeexplicitlysettoansi", "reporters:filters:console", "have_flag__wuninitialized", "have_flag__wmismatched_new_delete", "have_flag__wexceptions", "filteredsection-1", "benchmarking::failurereporting::failmacro", "reporters:filters:sonarqube", "have_flag__winit_self", "have_flag__wextra", "list::listeners::exitcode", "reporters:filters:compact", "approvaltests", "testspecs::warnunmatchedtestspecfailswithunmatchedtestspec", "benchmarking::failurereporting::shouldfailisrespected", "have_flag__wvla", "have_flag__wall", "negativespecnohiddentests", "outputs::dashasoutlocationsendsoutputtostdout", "filteredsection::generatorsdontcauseinfiniteloop-2", "have_flag__wnull_dereference", "testspecs::combiningmatchingandnonmatchingisok-1", "have_flag__wmissing_braces", "reporters:filters:tap", "testsinfile::simplespecs", "reporters::dashaslocationinreporterspecsendsoutputtostdout", "have_flag__wcast_align", "have_flag__wmisleading_indentation", "regressioncheck-1670", "have_flag__wmissing_noreturn", "list::listeners::output", "have_flag__wsuggest_override", "benchmarking::skipbenchmarkmacros", "reporters::unrecognizedoptioninspeccauseserror", "tagalias", "have_flag__wreorder", "list::reporters::output", "have_flag__wunused_function", "list::tests::xmloutput", "list::tags::exitcode", "warnings::unmatchedtestspecisaccepted", "reporters:filters:xml", "reporters:rngseed:json", "list::listeners::xmloutput", "escapespecialcharactersintestnames", "have_flag__wparentheses", "warnings::multiplewarningscanbespecified", "reporters::reporterspecificcolouroverridesdefaultcolour", "have_flag__wredundant_decls", "errorhandling::invalidtestspecexitsearly", "have_flag__wextra_semi", "have_flag__wundef", "multireporter::capturingreportersdontpropagatestdout", "multireporter::noncapturingreporterspropagatestdout", "testspecs::overridefailurewithnomatchedtests", "have_flag__wdeprecated", "reporters::junit::namespacesarenormalized", "reporters:rngseed:junit", "reporters:filters:junit", "versioncheck", "have_flag__wunused_parameter", "checkconvenienceheaders", "list::tags::output", "have_flag__wsubobject_linkage", "have_flag__wpedantic", "list::tests::exitcode", "reporters:rngseed:sonarqube", "reporters:rngseed:tap", "list::tests::quiet", "have_flag__woverloaded_virtual", "have_flag__wshadow", "list::reporters::exitcode", "testspecs::overrideallskipfailure", "filenameastagstest", "have_flag__ffile_prefix_map__home_catch2__", "testspecs::nonmatchingtestspecisroundtrippable", "have_flag__wunused", "have_flag__wcatch_value", "list::reporters::xmloutput", "benchmarking::failurereporting::throwingbenchmark", "filteredsection::generatorsdontcauseinfiniteloop-1"], "failed_tests": ["have_flag__wdeprecated_register", "have_flag__wdangling", "have_flag__wsuggest_destructor_override", "have_flag__wglobal_constructors", "have_flag__wmissing_prototypes", "have_flag__wreturn_std_move", "have_flag__wabsolute_value", "have_flag__wweak_vtables", "have_flag__wunneeded_internal_declaration", "have_flag__wmismatched_return_types", "have_flag__wcall_to_pure_virtual_from_ctor_dtor", "have_flag__wunreachable_code_aggressive", "have_flag__wmissing_variable_declarations", "have_flag__wexit_time_destructors"], "skipped_tests": []}
{"passed_count": 33, "failed_count": 14, "skipped_count": 0, "passed_tests": ["have_flag__wunused_function", "have_flag__wfloat_equal", "have_flag__wmissing_declarations", "have_flag__winit_self", "have_flag__wmismatched_tags", "have_flag__wextra", "have_flag__wsubobject_linkage", "have_flag__wpedantic", "have_flag__wold_style_cast", "have_flag__wstrict_aliasing", "have_flag__wparentheses", "have_flag__wvla", "have_flag__wall", "have_flag__wredundant_decls", "have_flag__wnull_dereference", "have_flag__wextra_semi", "have_flag__wmissing_braces", "have_flag__wundef", "have_flag__woverloaded_virtual", "have_flag__wcast_align", "have_flag__wmisleading_indentation", "have_flag__wdeprecated", "have_flag__wshadow", "have_flag__wmissing_noreturn", "have_flag__ffile_prefix_map__home_catch2__", "have_flag__wmismatched_new_delete", "have_flag__wuninitialized", "have_flag__wunused", "have_flag__wsuggest_override", "have_flag__wexceptions", "have_flag__wcatch_value", "have_flag__wunused_parameter", "have_flag__wreorder"], "failed_tests": ["have_flag__wdeprecated_register", "have_flag__wdangling", "have_flag__wsuggest_destructor_override", "have_flag__wglobal_constructors", "have_flag__wmissing_prototypes", "have_flag__wreturn_std_move", "have_flag__wabsolute_value", "have_flag__wweak_vtables", "have_flag__wunneeded_internal_declaration", "have_flag__wmismatched_return_types", "have_flag__wcall_to_pure_virtual_from_ctor_dtor", "have_flag__wunreachable_code_aggressive", "have_flag__wmissing_variable_declarations", "have_flag__wexit_time_destructors"], "skipped_tests": []}
{"passed_count": 104, "failed_count": 14, "skipped_count": 0, "passed_tests": ["testspecs::nomatchedtestsfail", "randomtestordering", "libidentitytest", "have_flag__wfloat_equal", "reporters:rngseed:compact", "reporters:rngseed:xml", "unmatchedoutputfilter", "have_flag__wmissing_declarations", "filenameastagsmatching", "have_flag__wmismatched_tags", "list::tests::output", "have_flag__wold_style_cast", "have_flag__wstrict_aliasing", "noassertions", "reporters:filters:json", "benchmarking::failurereporting::failedassertion", "reporters:rngseed:console", "runtests", "testspecs::combiningmatchingandnonmatchingisok-2", "testsinfile::escapespecialcharacters", "testsinfile::invalidtestnames-1", "filteredsection-2", "list::tags::xmloutput", "colours::colourmodecanbeexplicitlysettoansi", "reporters:filters:console", "have_flag__wuninitialized", "have_flag__wmismatched_new_delete", "have_flag__wexceptions", "filteredsection-1", "benchmarking::failurereporting::failmacro", "reporters:filters:sonarqube", "have_flag__winit_self", "have_flag__wextra", "list::listeners::exitcode", "reporters:filters:compact", "approvaltests", "testspecs::warnunmatchedtestspecfailswithunmatchedtestspec", "benchmarking::failurereporting::shouldfailisrespected", "have_flag__wvla", "have_flag__wall", "negativespecnohiddentests", "outputs::dashasoutlocationsendsoutputtostdout", "filteredsection::generatorsdontcauseinfiniteloop-2", "have_flag__wnull_dereference", "testspecs::combiningmatchingandnonmatchingisok-1", "have_flag__wmissing_braces", "reporters:filters:tap", "testsinfile::simplespecs", "reporters::dashaslocationinreporterspecsendsoutputtostdout", "have_flag__wcast_align", "have_flag__wmisleading_indentation", "regressioncheck-1670", "have_flag__wmissing_noreturn", "list::listeners::output", "have_flag__wsuggest_override", "benchmarking::skipbenchmarkmacros", "reporters::unrecognizedoptioninspeccauseserror", "tagalias", "have_flag__wreorder", "list::reporters::output", "have_flag__wunused_function", "list::tests::xmloutput", "list::tags::exitcode", "warnings::unmatchedtestspecisaccepted", "reporters:filters:xml", "reporters:rngseed:json", "list::listeners::xmloutput", "escapespecialcharactersintestnames", "have_flag__wparentheses", "warnings::multiplewarningscanbespecified", "reporters::reporterspecificcolouroverridesdefaultcolour", "have_flag__wredundant_decls", "errorhandling::invalidtestspecexitsearly", "have_flag__wextra_semi", "have_flag__wundef", "multireporter::capturingreportersdontpropagatestdout", "multireporter::noncapturingreporterspropagatestdout", "testspecs::overridefailurewithnomatchedtests", "have_flag__wdeprecated", "reporters::junit::namespacesarenormalized", "reporters:rngseed:junit", "reporters:filters:junit", "versioncheck", "have_flag__wunused_parameter", "checkconvenienceheaders", "list::tags::output", "have_flag__wsubobject_linkage", "have_flag__wpedantic", "list::tests::exitcode", "reporters:rngseed:sonarqube", "reporters:rngseed:tap", "list::tests::quiet", "have_flag__woverloaded_virtual", "have_flag__wshadow", "list::reporters::exitcode", "testspecs::overrideallskipfailure", "filenameastagstest", "have_flag__ffile_prefix_map__home_catch2__", "testspecs::nonmatchingtestspecisroundtrippable", "have_flag__wunused", "have_flag__wcatch_value", "list::reporters::xmloutput", "benchmarking::failurereporting::throwingbenchmark", "filteredsection::generatorsdontcauseinfiniteloop-1"], "failed_tests": ["have_flag__wdeprecated_register", "have_flag__wdangling", "have_flag__wsuggest_destructor_override", "have_flag__wglobal_constructors", "have_flag__wmissing_prototypes", "have_flag__wreturn_std_move", "have_flag__wabsolute_value", "have_flag__wweak_vtables", "have_flag__wunneeded_internal_declaration", "have_flag__wmismatched_return_types", "have_flag__wcall_to_pure_virtual_from_ctor_dtor", "have_flag__wunreachable_code_aggressive", "have_flag__wmissing_variable_declarations", "have_flag__wexit_time_destructors"], "skipped_tests": []}