First cut of new AssertionHandler/ Decomposer

- integrated into INTERNAL_CATCH_TEST. Needs more work to fully replace existing stuff
This commit is contained in:
Phil Nash 2017-08-08 17:53:01 +01:00
parent f8148ebae1
commit f247ce5bff
14 changed files with 469 additions and 39 deletions

View File

@ -122,6 +122,8 @@ CheckFileList(EXTERNAL_HEADERS ${HEADER_DIR}/external)
# Please keep these ordered alphabetically
set(INTERNAL_HEADERS
${HEADER_DIR}/internal/catch_approx.hpp
${HEADER_DIR}/internal/catch_assertionhandler.h
${HEADER_DIR}/internal/catch_assertioninfo.h
${HEADER_DIR}/internal/catch_assertionresult.h
${HEADER_DIR}/internal/catch_capture.hpp
${HEADER_DIR}/internal/catch_clara.h
@ -132,6 +134,7 @@ set(INTERNAL_HEADERS
${HEADER_DIR}/internal/catch_console_colour.hpp
${HEADER_DIR}/internal/catch_context.h
${HEADER_DIR}/internal/catch_debugger.h
${HEADER_DIR}/internal/catch_decomposer.h
${HEADER_DIR}/internal/catch_default_main.hpp
${HEADER_DIR}/internal/catch_enforce.h
${HEADER_DIR}/internal/catch_errno_guard.h
@ -198,6 +201,7 @@ set(INTERNAL_HEADERS
)
set(IMPL_SOURCES
${HEADER_DIR}/internal/catch_approx.cpp
${HEADER_DIR}/internal/catch_assertionhandler.cpp
${HEADER_DIR}/internal/catch_assertionresult.cpp
${HEADER_DIR}/internal/catch_benchmark.cpp
${HEADER_DIR}/internal/catch_commandline.cpp
@ -206,6 +210,7 @@ set(IMPL_SOURCES
${HEADER_DIR}/internal/catch_console_colour.cpp
${HEADER_DIR}/internal/catch_context.cpp
${HEADER_DIR}/internal/catch_debugger.cpp
${HEADER_DIR}/internal/catch_decomposer.cpp
${HEADER_DIR}/internal/catch_errno_guard.cpp
${HEADER_DIR}/internal/catch_evaluate.cpp
${HEADER_DIR}/internal/catch_exception_translator_registry.cpp

View File

@ -0,0 +1,135 @@
/*
* Created by Phil on 8/8/2017.
* Copyright 2017 Two Blue Cubes Ltd. All rights reserved.
*
* Distributed under the Boost Software License, Version 1.0. (See accompanying
* file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
*/
#include "catch_assertionhandler.h"
#include "catch_assertionresult.h"
#include "catch_interfaces_capture.h"
#include "catch_interfaces_runner.h"
#include "catch_context.h"
#include "catch_debugger.h"
#include "catch_interfaces_registry_hub.h"
#include <iostream> // !TBD
namespace Catch {
auto operator <<( std::ostream& os, ITransientExpression const& expr ) -> std::ostream& {
expr.streamReconstructedExpression( os );
return os;
}
LazyExpression::LazyExpression( bool isNegated )
: m_isNegated( isNegated )
{}
LazyExpression::LazyExpression( LazyExpression const& other ) : m_isNegated( other.m_isNegated ) {}
LazyExpression::operator bool() const {
return m_transientExpression != nullptr;
}
auto operator << ( std::ostream& os, LazyExpression const& lazyExpr ) -> std::ostream& {
if( lazyExpr.m_isNegated )
os << "!";
if( lazyExpr ) {
if( lazyExpr.m_isNegated && lazyExpr.m_transientExpression->isBinaryExpression() )
os << "(" << *lazyExpr.m_transientExpression << ")";
else
os << *lazyExpr.m_transientExpression;
}
else {
os << "{** error - unchecked empty expression requested **}";
}
return os;
}
AssertionHandler::AssertionHandler
( StringRef macroName,
SourceLineInfo const& lineInfo,
StringRef capturedExpression,
ResultDisposition::Flags resultDisposition )
: m_assertionInfo{ macroName, lineInfo, capturedExpression, resultDisposition }
{
getCurrentContext().getResultCapture()->assertionStarting( m_assertionInfo );
}
void AssertionHandler::handle( ITransientExpression const& expr ) {
bool negated = isFalseTest( m_assertionInfo.resultDisposition );
bool result = expr.getResult() != negated;
AssertionResultData data( result ? ResultWas::Ok : ResultWas::ExpressionFailed, LazyExpression( negated ) );
// Deprecated
// data.negated = negated;
// data.parenthesized = negated && expr.isBinaryExpression(); // !TBD: needed?
// data.decomposedExpression = nullptr; // !TBD
// data.reconstructedExpression = ""; // !TBD
getResultCapture().assertionRun();
AssertionResult assertionResult{ m_assertionInfo, data };
assertionResult.m_resultData.lazyExpression.m_transientExpression = &expr;
getResultCapture().assertionEnded( assertionResult );
if( !assertionResult.isOk() ) {
m_shouldDebugBreak = getCurrentContext().getConfig()->shouldDebugBreak();
m_shouldThrow =
getCurrentContext().getRunner()->aborting() ||
(m_assertionInfo.resultDisposition & ResultDisposition::Normal);
}
}
auto AssertionHandler::shouldDebugBreak() const -> bool {
return m_shouldDebugBreak;
}
void AssertionHandler::reactWithDebugBreak() const {
if (m_shouldDebugBreak) {
///////////////////////////////////////////////////////////////////
// To inspect the state during test, you need to go one level up the callstack
// To go back to the test and change execution, jump over the reactWithoutDebugBreak() call
///////////////////////////////////////////////////////////////////
CATCH_BREAK_INTO_DEBUGGER();
}
reactWithoutDebugBreak();
}
void AssertionHandler::reactWithoutDebugBreak() const {
if( m_shouldThrow )
throw Catch::TestFailureException();
}
void AssertionHandler::useActiveException( ResultDisposition::Flags resultDisposition ) {
m_assertionInfo.resultDisposition = resultDisposition;
useActiveException();
}
void AssertionHandler::useActiveException() {
bool negated = isFalseTest( m_assertionInfo.resultDisposition );
AssertionResultData data( ResultWas::ThrewException, LazyExpression( negated ) );
data.message = Catch::translateActiveException();
//data.decomposedExpression = &expr; // for lazy reconstruction
AssertionResult result( m_assertionInfo, data );
getResultCapture().assertionEnded( result );
// !TBD: factor this out? handleResult()?
if( !result.isOk() ) {
if( getCurrentContext().getConfig()->shouldDebugBreak() )
m_shouldDebugBreak = true;
if( getCurrentContext().getRunner()->aborting() || (m_assertionInfo.resultDisposition & ResultDisposition::Normal) )
m_shouldThrow = true;
}
}
} // namespace Catch

View File

@ -0,0 +1,61 @@
/*
* Created by Phil on 8/8/2017.
* Copyright 2017 Two Blue Cubes Ltd. All rights reserved.
*
* Distributed under the Boost Software License, Version 1.0. (See accompanying
* file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
*/
#ifndef TWOBLUECUBES_CATCH_ASSERTIONHANDLER_H_INCLUDED
#define TWOBLUECUBES_CATCH_ASSERTIONHANDLER_H_INCLUDED
#include "catch_decomposer.h"
#include "catch_assertioninfo.h"
namespace Catch {
struct TestFailureException{};
class LazyExpression {
friend class AssertionHandler;
friend struct AssertionStats;
ITransientExpression const* m_transientExpression = nullptr;
bool m_isNegated;
public:
LazyExpression( bool isNegated );
LazyExpression( LazyExpression const& other );
LazyExpression& operator = ( LazyExpression const& ) = delete;
explicit operator bool() const;
friend auto operator << ( std::ostream& os, LazyExpression const& lazyExpr ) -> std::ostream&;
};
class AssertionHandler {
AssertionInfo m_assertionInfo;
bool m_shouldDebugBreak = false;
bool m_shouldThrow = false;
public:
AssertionHandler
( StringRef macroName,
SourceLineInfo const& lineInfo,
StringRef capturedExpression,
ResultDisposition::Flags resultDisposition );
void handle( ITransientExpression const& expr );
template<typename T>
void handle( ExprLhs<T> const& expr ) {
handle( expr.makeUnaryExpr() );
}
auto shouldDebugBreak() const -> bool;
void reactWithDebugBreak() const;
void reactWithoutDebugBreak() const;
void useActiveException( ResultDisposition::Flags resultDisposition );
void useActiveException();
};
} // namespace Catch
#endif // TWOBLUECUBES_CATCH_ASSERTIONHANDLER_H_INCLUDED

View File

@ -0,0 +1,29 @@
/*
* Created by Phil on 8/8/2017.
* Copyright 2017 Two Blue Cubes Ltd. All rights reserved.
*
* Distributed under the Boost Software License, Version 1.0. (See accompanying
* file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
*/
#ifndef TWOBLUECUBES_CATCH_ASSERTIONINFO_H_INCLUDED
#define TWOBLUECUBES_CATCH_ASSERTIONINFO_H_INCLUDED
#include "catch_result_type.h"
#include "catch_common.h"
#include "catch_stringref.h"
namespace Catch {
struct AssertionInfo
{
StringRef macroName;
SourceLineInfo lineInfo;
StringRef capturedExpression;
ResultDisposition::Flags resultDisposition;
AssertionInfo() = delete;
};
} // end namespace Catch
#endif // TWOBLUECUBES_CATCH_ASSERTIONINFO_H_INCLUDED

View File

@ -24,17 +24,30 @@ namespace Catch {
resultType = ResultWas::Ok;
}
std::string const& AssertionResultData::reconstructExpression() const {
if( decomposedExpression != nullptr ) {
decomposedExpression->reconstructExpression( reconstructedExpression );
if( parenthesized ) {
reconstructedExpression.insert( 0, 1, '(' );
reconstructedExpression.append( 1, ')' );
std::string AssertionResultData::reconstructExpression() const {
if( reconstructedExpression.empty() ) {
// Try new LazyExpression first...
if( lazyExpression ) {
// !TBD Use stringstream for now, but rework above to pass stream in
std::ostringstream oss;
oss << lazyExpression;
reconstructedExpression = oss.str();
}
if( negated ) {
reconstructedExpression.insert( 0, 1, '!' );
// ... if not, fall back to decomposedExpression
else if( decomposedExpression != nullptr ) {
decomposedExpression->reconstructExpression( reconstructedExpression );
if( parenthesized ) {
reconstructedExpression.insert( 0, 1, '(' );
reconstructedExpression.append( 1, ')' );
}
if( negated ) {
reconstructedExpression.insert( 0, 1, '!' );
}
decomposedExpression = nullptr;
}
decomposedExpression = nullptr;
}
return reconstructedExpression;
}
@ -85,7 +98,10 @@ namespace Catch {
}
std::string AssertionResult::getExpandedExpression() const {
return m_resultData.reconstructExpression();
std::string expr = m_resultData.reconstructExpression();
return expr.empty()
? getExpression()
: expr;
}
std::string AssertionResult::getMessage() const {

View File

@ -9,9 +9,11 @@
#define TWOBLUECUBES_CATCH_ASSERTIONRESULT_H_INCLUDED
#include <string>
#include "catch_assertioninfo.h"
#include "catch_result_type.h"
#include "catch_common.h"
#include "catch_stringref.h"
#include "catch_assertionhandler.h"
namespace Catch {
@ -37,28 +39,32 @@ namespace Catch {
template<typename T> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( T const& );
template<typename T> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( T const& );
};
}
struct AssertionInfo
{
StringRef macroName;
SourceLineInfo lineInfo;
StringRef capturedExpression;
ResultDisposition::Flags resultDisposition;
AssertionInfo() = delete;
};
namespace Catch {
struct AssertionResultData
{
void negate( bool parenthesize );
std::string const& reconstructExpression() const;
AssertionResultData() = delete;
// !TBD We won't need this constructor once the deprecated fields are removed
AssertionResultData( ResultWas::OfType _resultType, LazyExpression const& _lazyExpression )
: resultType( _resultType ),
lazyExpression( _lazyExpression )
{}
mutable DecomposedExpression const* decomposedExpression = nullptr;
mutable std::string reconstructedExpression;
std::string message;
ResultWas::OfType resultType = ResultWas::Unknown;
std::string message;
LazyExpression lazyExpression;
// deprecated:
bool negated = false;
bool parenthesized = false;
void negate( bool parenthesize );
std::string reconstructExpression() const;
mutable DecomposedExpression const* decomposedExpression = nullptr;
mutable std::string reconstructedExpression;
};
class AssertionResult {
@ -81,7 +87,7 @@ namespace Catch {
void discardDecomposedExpression() const;
void expandDecomposedExpression() const;
protected:
//protected:
AssertionInfo m_info;
AssertionResultData m_resultData;
};

View File

@ -8,6 +8,7 @@
#ifndef TWOBLUECUBES_CATCH_CAPTURE_HPP_INCLUDED
#define TWOBLUECUBES_CATCH_CAPTURE_HPP_INCLUDED
#include "catch_assertionhandler.h"
#include "catch_result_builder.h"
#include "catch_message.h"
#include "catch_interfaces_capture.h"
@ -44,6 +45,9 @@
#define INTERNAL_CATCH_REACT( resultBuilder ) \
if( resultBuilder.shouldDebugBreak() ) CATCH_BREAK_INTO_DEBUGGER(); \
resultBuilder.react();
#define INTERNAL_CATCH_REACT2( handler ) \
if( handler.shouldDebugBreak() ) CATCH_BREAK_INTO_DEBUGGER(); \
handler.reactWithDebugBreak();
#define INTERNAL_CATCH_TRY try
#define INTERNAL_CATCH_CATCH( capturer, disposition ) catch(...) { capturer.useActiveException( disposition ); }
@ -53,13 +57,13 @@
///////////////////////////////////////////////////////////////////////////////
#define INTERNAL_CATCH_TEST( macroName, resultDisposition, ... ) \
do { \
Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #__VA_ARGS__, resultDisposition ); \
Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, #__VA_ARGS__, resultDisposition ); \
INTERNAL_CATCH_TRY { \
CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \
( __catchResult <= __VA_ARGS__ ).endExpression(); \
catchAssertionHandler.handle( Catch::Decomposer() <= __VA_ARGS__ ); \
CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \
} INTERNAL_CATCH_CATCH( __catchResult, resultDisposition ) \
INTERNAL_CATCH_REACT( __catchResult ) \
} INTERNAL_CATCH_CATCH( catchAssertionHandler, resultDisposition ) \
INTERNAL_CATCH_REACT2( catchAssertionHandler ) \
} while( Catch::isTrue( false && static_cast<bool>( !!(__VA_ARGS__) ) ) ) // the expression here is never evaluated at runtime but it forces the compiler to give it a look
// The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&.

View File

@ -0,0 +1,23 @@
/*
* Created by Phil Nash on 8/8/2017.
* Copyright 2017 Two Blue Cubes Ltd. All rights reserved.
*
* Distributed under the Boost Software License, Version 1.0. (See accompanying
* file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
*/
#include "catch_decomposer.h"
#include "catch_config.hpp"
namespace Catch {
void formatReconstructedExpression( std::ostream &os, std::string const& lhs, std::string const& op, std::string const& rhs ) {
if( lhs.size() + rhs.size() < 40 &&
lhs.find('\n') == std::string::npos &&
rhs.find('\n') == std::string::npos )
os << lhs << " " << op << " " << rhs;
else
os << lhs << "\n" << op << "\n" << rhs;
}
}

View File

@ -0,0 +1,149 @@
/*
* Created by Phil Nash on 8/8/2017.
* Copyright 2017 Two Blue Cubes Ltd. All rights reserved.
*
* Distributed under the Boost Software License, Version 1.0. (See accompanying
* file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
*/
#ifndef TWOBLUECUBES_CATCH_DECOMPOSER_H_INCLUDED
#define TWOBLUECUBES_CATCH_DECOMPOSER_H_INCLUDED
#include "catch_tostring.h"
#include "catch_stringref.h"
#include <ostream>
namespace Catch {
struct ITransientExpression {
virtual auto isBinaryExpression() const -> bool = 0;
virtual auto getResult() const -> bool = 0;
virtual void streamReconstructedExpression( std::ostream &os ) const = 0;
};
void formatReconstructedExpression( std::ostream &os, std::string const& lhs, std::string const& op, std::string const& rhs );
template<typename LhsT, typename RhsT>
class BinaryExpr : public ITransientExpression {
bool m_result;
LhsT m_lhs;
std::string m_op;
RhsT m_rhs;
auto isBinaryExpression() const -> bool override { return true; }
auto getResult() const -> bool override { return m_result; }
void streamReconstructedExpression( std::ostream &os ) const override {
formatReconstructedExpression
( os, Catch::Detail::stringify( m_lhs ), m_op, Catch::Detail::stringify( m_rhs ) );
}
public:
BinaryExpr( bool comparisionResult, LhsT lhs, StringRef op, RhsT rhs )
: m_result( comparisionResult ),
m_lhs( lhs ),
m_op( op.c_str() ),
m_rhs( rhs )
{}
};
template<typename LhsT>
class UnaryExpr : public ITransientExpression {
LhsT m_lhs;
auto isBinaryExpression() const -> bool override { return false; }
auto getResult() const -> bool override { return m_lhs ? true : false; }
void streamReconstructedExpression( std::ostream &os ) const override {
os << Catch::Detail::stringify( m_lhs );
}
public:
UnaryExpr( LhsT lhs ) : m_lhs( lhs ) {}
};
// Specialised comparison functions to handle equality comparisons between ints and pointers (NULL deduces as an int)
template<typename LhsT, typename RhsT>
auto compareEqual( LhsT lhs, RhsT&& rhs ) -> bool { return lhs == rhs; };
template<typename T>
auto compareEqual( T* const& lhs, int rhs ) -> bool { return lhs == reinterpret_cast<void const*>( rhs ); };
template<typename T>
auto compareEqual( int lhs, T* const& rhs ) -> bool { return reinterpret_cast<void const*>( lhs ) == rhs; };
template<typename LhsT, typename RhsT>
auto compareNotEqual( LhsT lhs, RhsT&& rhs ) -> bool { return lhs != rhs; };
template<typename T>
auto compareNotEqual( T* const& lhs, int rhs ) -> bool { return lhs != reinterpret_cast<void const*>( rhs ); };
template<typename T>
auto compareNotEqual( int lhs, T* const& rhs ) -> bool { return reinterpret_cast<void const*>( lhs ) != rhs; };
template<typename LhsT>
class ExprLhs {
LhsT m_lhs;
public:
ExprLhs( LhsT lhs ) : m_lhs( lhs ) {}
template<typename RhsT>
auto operator == ( RhsT&& rhs ) -> BinaryExpr<LhsT, RhsT&> const {
return BinaryExpr<LhsT, RhsT&>( compareEqual( m_lhs, rhs ), m_lhs, "==", rhs );
}
auto operator == ( bool rhs ) -> BinaryExpr<LhsT, bool> const {
return BinaryExpr<LhsT, bool>( m_lhs == rhs, m_lhs, "==", rhs );
}
template<typename RhsT>
auto operator != ( RhsT&& rhs ) -> BinaryExpr<LhsT, RhsT&> const {
return BinaryExpr<LhsT, RhsT&>( compareNotEqual( m_lhs, rhs ), m_lhs, "!=", rhs );
}
auto operator != ( bool rhs ) -> BinaryExpr<LhsT, bool> const {
return BinaryExpr<LhsT, bool>( m_lhs != rhs, m_lhs, "!=", rhs );
}
template<typename RhsT>
auto operator > ( RhsT&& rhs ) -> BinaryExpr<LhsT, RhsT&> const {
return BinaryExpr<LhsT, RhsT&>( m_lhs > rhs, m_lhs, ">", rhs );
}
template<typename RhsT>
auto operator < ( RhsT&& rhs ) -> BinaryExpr<LhsT, RhsT&> const {
return BinaryExpr<LhsT, RhsT&>( m_lhs < rhs, m_lhs, "<", rhs );
}
template<typename RhsT>
auto operator >= ( RhsT&& rhs ) -> BinaryExpr<LhsT, RhsT&> const {
return BinaryExpr<LhsT, RhsT&>( m_lhs >= rhs, m_lhs, ">=", rhs );
}
template<typename RhsT>
auto operator <= ( RhsT&& rhs ) -> BinaryExpr<LhsT, RhsT&> const {
return BinaryExpr<LhsT, RhsT&>( m_lhs <= rhs, m_lhs, "<=", rhs );
}
auto makeUnaryExpr() const -> UnaryExpr<LhsT> {
return UnaryExpr<LhsT>( m_lhs );
}
};
void handleExpression( ITransientExpression const& expr );
template<typename T>
void handleExpression( ExprLhs<T> const& expr ) {
handleExpression( expr.makeUnaryExpr() );
}
struct Decomposer {
template<typename T>
auto operator <= ( T& lhs ) -> ExprLhs<T&> {
return ExprLhs<T&>( lhs );
}
template<typename T>
auto operator <= ( T const& lhs ) -> ExprLhs<T const&> {
return ExprLhs<T const&>( lhs );
}
auto operator <=( bool value ) -> ExprLhs<bool> {
return ExprLhs<bool>( value );
}
};
} // end namespace Catch
#endif // TWOBLUECUBES_CATCH_DECOMPOSER_H_INCLUDED

View File

@ -37,6 +37,8 @@ namespace Catch {
infoMessages( _infoMessages ),
totals( _totals )
{
assertionResult.m_resultData.lazyExpression.m_transientExpression = _assertionResult.m_resultData.lazyExpression.m_transientExpression;
if( assertionResult.hasMessage() ) {
// Copy message into messages list.
// !TBD This should have been done earlier, somewhere

View File

@ -13,7 +13,6 @@
#include "catch_interfaces_capture.h"
#include "catch_interfaces_registry_hub.h"
#include "catch_matchers_string.h"
#include "catch_wildcard_pattern.hpp"
#include "catch_debugger.h"
#include <cassert>
@ -34,7 +33,8 @@ namespace Catch {
SourceLineInfo const& lineInfo,
StringRef capturedExpression,
ResultDisposition::Flags resultDisposition )
: m_assertionInfo{ macroName, lineInfo, capturedExpression, resultDisposition }
: m_assertionInfo{ macroName, lineInfo, capturedExpression, resultDisposition },
m_data( ResultWas::Unknown, LazyExpression( false ) )
{
getCurrentContext().getResultCapture()->assertionStarting( m_assertionInfo );
}

View File

@ -8,8 +8,10 @@
#ifndef TWOBLUECUBES_CATCH_RESULT_BUILDER_H_INCLUDED
#define TWOBLUECUBES_CATCH_RESULT_BUILDER_H_INCLUDED
#include "catch_result_type.h"
#include "catch_assertionresult.h"
#include "catch_decomposer.h"
#include "catch_result_type.h"
#include "catch_common.h"
#include "catch_matchers.hpp"
@ -17,8 +19,6 @@
namespace Catch {
struct TestFailureException{};
template<typename T> class ExpressionLhs;
struct CopyableStream {

View File

@ -192,7 +192,7 @@ namespace Catch {
void RunContext::handleFatalErrorCondition(std::string const & message) {
// Don't rebuild the result -- the stringification itself can cause more fatal errors
// Instead, fake a result data.
AssertionResultData tempResult;
AssertionResultData tempResult( ResultWas::Unknown, { false } );
tempResult.resultType = ResultWas::FatalErrorCondition;
tempResult.message = message;
AssertionResult result(m_lastAssertionInfo, tempResult);

View File

@ -193,14 +193,14 @@ namespace Catch {
bool assertionEnded(AssertionStats const& assertionStats) override {
assert(!m_sectionStack.empty());
SectionNode& sectionNode = *m_sectionStack.back();
sectionNode.assertions.push_back(assertionStats);
// AssertionResult holds a pointer to a temporary DecomposedExpression,
// which getExpandedExpression() calls to build the expression string.
// Our section stack copy of the assertionResult will likely outlive the
// temporary, so it must be expanded or discarded now to avoid calling
// a destroyed object later.
prepareExpandedExpression(sectionNode.assertions.back().assertionResult);
prepareExpandedExpression(const_cast<AssertionResult&>( assertionStats.assertionResult ) );
SectionNode& sectionNode = *m_sectionStack.back();
sectionNode.assertions.push_back(assertionStats);
return true;
}
void sectionEnded(SectionStats const& sectionStats) override {