2020-09-08 15:53:08 +02:00
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
2022-01-03 23:16:39 +01:00
// Catch v3.0.0-preview.4
// Generated: 2022-01-03 23:14:23.198909
2020-09-08 15:53:08 +02:00
// ----------------------------------------------------------
// This file is an amalgamation of multiple different files.
// You probably shouldn't edit it directly.
// ----------------------------------------------------------
#include "catch_amalgamated.hpp"
// Adapted from donated nonius code.
#include <cassert>
#include <iterator>
#include <random>
#if defined(CATCH_CONFIG_USE_ASYNC)
#include <future>
#endif
namespace {
using Catch :: Benchmark :: Detail :: sample ;
template < typename URng , typename Estimator >
2022-01-03 23:16:39 +01:00
sample resample ( URng & rng , unsigned int resamples , std :: vector < double >:: iterator first , std :: vector < double >:: iterator last , Estimator & estimator ) {
auto n = static_cast < size_t > ( last - first );
2020-09-08 15:53:08 +02:00
std :: uniform_int_distribution < decltype ( n ) > dist ( 0 , n - 1 );
sample out ;
out . reserve ( resamples );
std :: generate_n ( std :: back_inserter ( out ), resamples , [ n , first , & estimator , & dist , & rng ] {
std :: vector < double > resampled ;
resampled . reserve ( n );
2022-01-03 23:16:39 +01:00
std :: generate_n ( std :: back_inserter ( resampled ), n , [ first , & dist , & rng ] { return first [ static_cast < std :: ptrdiff_t > ( dist ( rng ))]; });
2020-09-08 15:53:08 +02:00
return estimator ( resampled . begin (), resampled . end ());
});
std :: sort ( out . begin (), out . end ());
return out ;
}
double erf_inv ( double x ) {
// Code accompanying the article "Approximating the erfinv function" in GPU Computing Gems, Volume 2
double w , p ;
w = - log (( 1.0 - x ) * ( 1.0 + x ));
if ( w < 6.250000 ) {
w = w - 3.125000 ;
p = - 3.6444120640178196996e-21 ;
p = - 1.685059138182016589e-19 + p * w ;
p = 1.2858480715256400167e-18 + p * w ;
p = 1.115787767802518096e-17 + p * w ;
p = - 1.333171662854620906e-16 + p * w ;
p = 2.0972767875968561637e-17 + p * w ;
p = 6.6376381343583238325e-15 + p * w ;
p = - 4.0545662729752068639e-14 + p * w ;
p = - 8.1519341976054721522e-14 + p * w ;
p = 2.6335093153082322977e-12 + p * w ;
p = - 1.2975133253453532498e-11 + p * w ;
p = - 5.4154120542946279317e-11 + p * w ;
p = 1.051212273321532285e-09 + p * w ;
p = - 4.1126339803469836976e-09 + p * w ;
p = - 2.9070369957882005086e-08 + p * w ;
p = 4.2347877827932403518e-07 + p * w ;
p = - 1.3654692000834678645e-06 + p * w ;
p = - 1.3882523362786468719e-05 + p * w ;
p = 0.0001867342080340571352 + p * w ;
p = - 0.00074070253416626697512 + p * w ;
p = - 0.0060336708714301490533 + p * w ;
p = 0.24015818242558961693 + p * w ;
p = 1.6536545626831027356 + p * w ;
} else if ( w < 16.000000 ) {
w = sqrt ( w ) - 3.250000 ;
p = 2.2137376921775787049e-09 ;
p = 9.0756561938885390979e-08 + p * w ;
p = - 2.7517406297064545428e-07 + p * w ;
p = 1.8239629214389227755e-08 + p * w ;
p = 1.5027403968909827627e-06 + p * w ;
p = - 4.013867526981545969e-06 + p * w ;
p = 2.9234449089955446044e-06 + p * w ;
p = 1.2475304481671778723e-05 + p * w ;
p = - 4.7318229009055733981e-05 + p * w ;
p = 6.8284851459573175448e-05 + p * w ;
p = 2.4031110387097893999e-05 + p * w ;
p = - 0.0003550375203628474796 + p * w ;
p = 0.00095328937973738049703 + p * w ;
p = - 0.0016882755560235047313 + p * w ;
p = 0.0024914420961078508066 + p * w ;
p = - 0.0037512085075692412107 + p * w ;
p = 0.005370914553590063617 + p * w ;
p = 1.0052589676941592334 + p * w ;
p = 3.0838856104922207635 + p * w ;
} else {
w = sqrt ( w ) - 5.000000 ;
p = - 2.7109920616438573243e-11 ;
p = - 2.5556418169965252055e-10 + p * w ;
p = 1.5076572693500548083e-09 + p * w ;
p = - 3.7894654401267369937e-09 + p * w ;
p = 7.6157012080783393804e-09 + p * w ;
p = - 1.4960026627149240478e-08 + p * w ;
p = 2.9147953450901080826e-08 + p * w ;
p = - 6.7711997758452339498e-08 + p * w ;
p = 2.2900482228026654717e-07 + p * w ;
p = - 9.9298272942317002539e-07 + p * w ;
p = 4.5260625972231537039e-06 + p * w ;
p = - 1.9681778105531670567e-05 + p * w ;
p = 7.5995277030017761139e-05 + p * w ;
p = - 0.00021503011930044477347 + p * w ;
p = - 0.00013871931833623122026 + p * w ;
p = 1.0103004648645343977 + p * w ;
p = 4.8499064014085844221 + p * w ;
}
return p * x ;
}
double standard_deviation ( std :: vector < double >:: iterator first , std :: vector < double >:: iterator last ) {
auto m = Catch :: Benchmark :: Detail :: mean ( first , last );
double variance = std :: accumulate ( first , last , 0. , [ m ]( double a , double b ) {
double diff = b - m ;
return a + diff * diff ;
}) / ( last - first );
return std :: sqrt ( variance );
}
}
namespace Catch {
namespace Benchmark {
namespace Detail {
double weighted_average_quantile ( int k , int q , std :: vector < double >:: iterator first , std :: vector < double >:: iterator last ) {
auto count = last - first ;
double idx = ( count - 1 ) * k / static_cast < double > ( q );
int j = static_cast < int > ( idx );
double g = idx - j ;
std :: nth_element ( first , first + j , last );
auto xj = first [ j ];
if ( g == 0 ) return xj ;
auto xj1 = * std :: min_element ( first + ( j + 1 ), last );
return xj + g * ( xj1 - xj );
}
double erfc_inv ( double x ) {
return erf_inv ( 1.0 - x );
}
double normal_quantile ( double p ) {
static const double ROOT_TWO = std :: sqrt ( 2.0 );
double result = 0.0 ;
assert ( p >= 0 && p <= 1 );
if ( p < 0 || p > 1 ) {
return result ;
}
result = - erfc_inv ( 2.0 * p );
// result *= normal distribution standard deviation (1.0) * sqrt(2)
result *= /*sd * */ ROOT_TWO ;
// result += normal disttribution mean (0)
return result ;
}
double outlier_variance ( Estimate < double > mean , Estimate < double > stddev , int n ) {
double sb = stddev . point ;
double mn = mean . point / n ;
double mg_min = mn / 2. ;
2022-01-03 23:16:39 +01:00
double sg = ( std :: min )( mg_min / 4. , sb / std :: sqrt ( n ));
2020-09-08 15:53:08 +02:00
double sg2 = sg * sg ;
double sb2 = sb * sb ;
auto c_max = [ n , mn , sb2 , sg2 ]( double x ) -> double {
double k = mn - x ;
double d = k * k ;
double nd = n * d ;
double k0 = - n * nd ;
double k1 = sb2 - n * sg2 + nd ;
double det = k1 * k1 - 4 * sg2 * k0 ;
2022-01-03 23:16:39 +01:00
return static_cast < int > ( - 2. * k0 / ( k1 + std :: sqrt ( det )));
2020-09-08 15:53:08 +02:00
};
auto var_out = [ n , sb2 , sg2 ]( double c ) {
double nc = n - c ;
return ( nc / n ) * ( sb2 - nc * sg2 );
};
2022-01-03 23:16:39 +01:00
return ( std :: min )( var_out ( 1 ), var_out (( std :: min )( c_max ( 0. ), c_max ( mg_min )))) / sb2 ;
2020-09-08 15:53:08 +02:00
}
2022-01-03 23:16:39 +01:00
bootstrap_analysis analyse_samples ( double confidence_level , unsigned int n_resamples , std :: vector < double >:: iterator first , std :: vector < double >:: iterator last ) {
2020-09-08 15:53:08 +02:00
CATCH_INTERNAL_START_WARNINGS_SUPPRESSION
CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS
static std :: random_device entropy ;
CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION
auto n = static_cast < int > ( last - first ); // seriously, one can't use integral types without hell in C++
auto mean = & Detail :: mean < std :: vector < double >:: iterator > ;
auto stddev = & standard_deviation ;
#if defined(CATCH_CONFIG_USE_ASYNC)
auto Estimate = [ = ]( double ( * f )( std :: vector < double >:: iterator , std :: vector < double >:: iterator )) {
auto seed = entropy ();
return std :: async ( std :: launch :: async , [ = ] {
std :: mt19937 rng ( seed );
auto resampled = resample ( rng , n_resamples , first , last , f );
return bootstrap ( confidence_level , first , last , resampled , f );
});
};
auto mean_future = Estimate ( mean );
auto stddev_future = Estimate ( stddev );
auto mean_estimate = mean_future . get ();
auto stddev_estimate = stddev_future . get ();
#else
auto Estimate = [ = ]( double ( * f )( std :: vector < double >:: iterator , std :: vector < double >:: iterator )) {
auto seed = entropy ();
std :: mt19937 rng ( seed );
auto resampled = resample ( rng , n_resamples , first , last , f );
return bootstrap ( confidence_level , first , last , resampled , f );
};
auto mean_estimate = Estimate ( mean );
auto stddev_estimate = Estimate ( stddev );
#endif // CATCH_USE_ASYNC
double outlier_variance = Detail :: outlier_variance ( mean_estimate , stddev_estimate , n );
return { mean_estimate , stddev_estimate , outlier_variance };
}
} // namespace Detail
} // namespace Benchmark
} // namespace Catch
/** \file
* This is a special TU that combines what would otherwise be a very
* small benchmarking-related TUs into one bigger TU.
*
* The reason for this is compilation performance improvements by
* avoiding reparsing headers for many small TUs, instead having this
* one TU include bit more, but having it all parsed only once.
*
* To avoid heavy-tail problem with compilation times, each "subpart"
* of Catch2 has its own combined TU like this.
*/
////////////////////////////////////////////
// vvv formerly catch_chronometer.cpp vvv //
////////////////////////////////////////////
namespace Catch {
namespace Benchmark {
namespace Detail {
ChronometerConcept ::~ ChronometerConcept () = default ;
} // namespace Detail
} // namespace Benchmark
} // namespace Catch
///////////////////////////////////////////////////
// vvv formerly catch_benchmark_function.cpp vvv //
///////////////////////////////////////////////////
namespace Catch {
namespace Benchmark {
namespace Detail {
BenchmarkFunction :: callable ::~ callable () = default ;
} // namespace Detail
} // namespace Benchmark
} // namespace Catch
/////////////////////////////////////////////////
// vvv formerly catch_run_for_at_least.cpp vvv //
/////////////////////////////////////////////////
#include <exception>
namespace Catch {
namespace Benchmark {
namespace Detail {
struct optimized_away_error : std :: exception {
const char * what () const noexcept override ;
};
const char * optimized_away_error :: what () const noexcept {
return "could not measure benchmark, maybe it was optimized away" ;
}
void throw_optimized_away_error () {
Catch :: throw_exception ( optimized_away_error {});
}
} // namespace Detail
} // namespace Benchmark
} // namespace Catch
#include <cmath>
#include <limits>
namespace {
// Performs equivalent check of std::fabs(lhs - rhs) <= margin
// But without the subtraction to allow for INFINITY in comparison
bool marginComparison ( double lhs , double rhs , double margin ) {
return ( lhs + margin >= rhs ) && ( rhs + margin >= lhs );
}
}
namespace Catch {
Approx :: Approx ( double value )
2022-01-03 23:16:39 +01:00
: m_epsilon ( std :: numeric_limits < float >:: epsilon () * 100. ),
2020-09-08 15:53:08 +02:00
m_margin ( 0.0 ),
m_scale ( 0.0 ),
m_value ( value )
{}
Approx Approx :: custom () {
return Approx ( 0 );
}
Approx Approx :: operator - () const {
auto temp ( * this );
temp . m_value = - temp . m_value ;
return temp ;
}
std :: string Approx :: toString () const {
ReusableStringStream rss ;
rss << "Approx( " << :: Catch :: Detail :: stringify ( m_value ) << " )" ;
return rss . str ();
}
bool Approx :: equalityComparisonImpl ( const double other ) const {
// First try with fixed margin, then compute margin based on epsilon, scale and Approx's value
// Thanks to Richard Harris for his help refining the scaled margin value
return marginComparison ( m_value , other , m_margin )
|| marginComparison ( m_value , other , m_epsilon * ( m_scale + std :: fabs ( std :: isinf ( m_value ) ? 0 : m_value )));
}
void Approx :: setMargin ( double newMargin ) {
CATCH_ENFORCE ( newMargin >= 0 ,
"Invalid Approx::margin: " << newMargin << '.'
<< " Approx::Margin has to be non-negative." );
m_margin = newMargin ;
}
void Approx :: setEpsilon ( double newEpsilon ) {
CATCH_ENFORCE ( newEpsilon >= 0 && newEpsilon <= 1.0 ,
"Invalid Approx::epsilon: " << newEpsilon << '.'
<< " Approx::epsilon has to be in [0, 1]" );
m_epsilon = newEpsilon ;
}
namespace literals {
Approx operator "" _a ( long double val ) {
return Approx ( val );
}
Approx operator "" _a ( unsigned long long val ) {
return Approx ( val );
}
} // end namespace literals
std :: string StringMaker < Catch :: Approx >:: convert ( Catch :: Approx const & value ) {
return value . toString ();
}
} // end namespace Catch
namespace Catch {
AssertionResultData :: AssertionResultData ( ResultWas :: OfType _resultType , LazyExpression const & _lazyExpression ) :
lazyExpression ( _lazyExpression ),
resultType ( _resultType ) {}
std :: string AssertionResultData :: reconstructExpression () const {
if ( reconstructedExpression . empty () ) {
if ( lazyExpression ) {
ReusableStringStream rss ;
rss << lazyExpression ;
reconstructedExpression = rss . str ();
}
}
return reconstructedExpression ;
}
AssertionResult :: AssertionResult ( AssertionInfo const & info , AssertionResultData const & data )
: m_info ( info ),
m_resultData ( data )
{}
// Result was a success
bool AssertionResult :: succeeded () const {
return Catch :: isOk ( m_resultData . resultType );
}
// Result was a success, or failure is suppressed
bool AssertionResult :: isOk () const {
return Catch :: isOk ( m_resultData . resultType ) || shouldSuppressFailure ( m_info . resultDisposition );
}
ResultWas :: OfType AssertionResult :: getResultType () const {
return m_resultData . resultType ;
}
bool AssertionResult :: hasExpression () const {
return ! m_info . capturedExpression . empty ();
}
bool AssertionResult :: hasMessage () const {
return ! m_resultData . message . empty ();
}
std :: string AssertionResult :: getExpression () const {
// Possibly overallocating by 3 characters should be basically free
std :: string expr ; expr . reserve ( m_info . capturedExpression . size () + 3 );
if ( isFalseTest ( m_info . resultDisposition )) {
expr += "!(" ;
}
expr += m_info . capturedExpression ;
if ( isFalseTest ( m_info . resultDisposition )) {
expr += ')' ;
}
return expr ;
}
std :: string AssertionResult :: getExpressionInMacro () const {
std :: string expr ;
if ( m_info . macroName . empty () )
expr = static_cast < std :: string > ( m_info . capturedExpression );
else {
expr . reserve ( m_info . macroName . size () + m_info . capturedExpression . size () + 4 );
expr += m_info . macroName ;
expr += "( " ;
expr += m_info . capturedExpression ;
expr += " )" ;
}
return expr ;
}
bool AssertionResult :: hasExpandedExpression () const {
return hasExpression () && getExpandedExpression () != getExpression ();
}
std :: string AssertionResult :: getExpandedExpression () const {
std :: string expr = m_resultData . reconstructExpression ();
return expr . empty ()
? getExpression ()
: expr ;
}
2022-01-03 23:16:39 +01:00
StringRef AssertionResult :: getMessage () const {
2020-09-08 15:53:08 +02:00
return m_resultData . message ;
}
SourceLineInfo AssertionResult :: getSourceInfo () const {
return m_info . lineInfo ;
}
StringRef AssertionResult :: getTestMacroName () const {
return m_info . macroName ;
}
} // end namespace Catch
2022-01-03 23:16:39 +01:00
#include <ostream>
2020-09-08 15:53:08 +02:00
namespace Catch {
2022-01-03 23:16:39 +01:00
namespace Detail {
namespace {
class RDBufStream : public IStream {
mutable std :: ostream m_os ;
2020-09-08 15:53:08 +02:00
2022-01-03 23:16:39 +01:00
public :
//! The streambuf `sb` must outlive the constructed object.
RDBufStream ( std :: streambuf * sb ) : m_os ( sb ) {}
~ RDBufStream () override = default ;
public : // IStream
std :: ostream & stream () const override { return m_os ; }
};
} // unnamed namespace
} // namespace Detail
std :: ostream & operator << ( std :: ostream & os ,
ConfigData :: ReporterAndFile const & reporter ) {
os << "{ " << reporter . reporterName << ", " ;
if ( reporter . outputFileName ) {
os << * reporter . outputFileName ;
} else {
os << "<default-output>" ;
}
return os << " }" ;
}
Config :: Config ( ConfigData const & data ) :
m_data ( data ),
m_defaultStream ( openStream ( data . defaultOutputFilename ) ) {
2020-09-08 15:53:08 +02:00
// We need to trim filter specs to avoid trouble with superfluous
// whitespace (esp. important for bdd macros, as those are manually
// aligned with whitespace).
for ( auto & elem : m_data . testsOrTags ) {
elem = trim ( elem );
}
for ( auto & elem : m_data . sectionsToRun ) {
elem = trim ( elem );
}
TestSpecParser parser ( ITagAliasRegistry :: get ());
if ( ! m_data . testsOrTags . empty ()) {
m_hasTestFilters = true ;
for ( auto const & testOrTags : m_data . testsOrTags ) {
parser . parse ( testOrTags );
}
}
m_testSpec = parser . testSpec ();
2022-01-03 23:16:39 +01:00
m_reporterStreams . reserve ( m_data . reporterSpecifications . size () );
for ( auto const & reporterAndFile : m_data . reporterSpecifications ) {
if ( reporterAndFile . outputFileName . none () ) {
m_reporterStreams . emplace_back ( new Detail :: RDBufStream (
m_defaultStream -> stream (). rdbuf () ) );
} else {
m_reporterStreams . emplace_back (
openStream ( * reporterAndFile . outputFileName ) );
}
}
2020-09-08 15:53:08 +02:00
}
Config ::~ Config () = default ;
bool Config :: listTests () const { return m_data . listTests ; }
bool Config :: listTags () const { return m_data . listTags ; }
bool Config :: listReporters () const { return m_data . listReporters ; }
std :: vector < std :: string > const & Config :: getTestsOrTags () const { return m_data . testsOrTags ; }
std :: vector < std :: string > const & Config :: getSectionsToRun () const { return m_data . sectionsToRun ; }
2022-01-03 23:16:39 +01:00
std :: vector < ConfigData :: ReporterAndFile > const & Config :: getReportersAndOutputFiles () const {
return m_data . reporterSpecifications ;
}
std :: ostream & Config :: getReporterOutputStream ( std :: size_t reporterIdx ) const {
return m_reporterStreams . at ( reporterIdx ) -> stream ();
}
2020-09-08 15:53:08 +02:00
TestSpec const & Config :: testSpec () const { return m_testSpec ; }
bool Config :: hasTestFilters () const { return m_hasTestFilters ; }
bool Config :: showHelp () const { return m_data . showHelp ; }
// IConfig interface
bool Config :: allowThrows () const { return ! m_data . noThrow ; }
2022-01-03 23:16:39 +01:00
std :: ostream & Config :: defaultStream () const { return m_defaultStream -> stream (); }
StringRef Config :: name () const { return m_data . name . empty () ? m_data . processName : m_data . name ; }
2020-09-08 15:53:08 +02:00
bool Config :: includeSuccessfulResults () const { return m_data . showSuccessfulTests ; }
2022-01-03 23:16:39 +01:00
bool Config :: warnAboutMissingAssertions () const {
return !! ( m_data . warnings & WarnAbout :: NoAssertions );
}
bool Config :: warnAboutUnmatchedTestSpecs () const {
return !! ( m_data . warnings & WarnAbout :: UnmatchedTestSpec );
}
bool Config :: zeroTestsCountAsSuccess () const { return m_data . allowZeroTests ; }
2020-09-08 15:53:08 +02:00
ShowDurations Config :: showDurations () const { return m_data . showDurations ; }
double Config :: minDuration () const { return m_data . minDuration ; }
TestRunOrder Config :: runOrder () const { return m_data . runOrder ; }
2022-01-03 23:16:39 +01:00
uint32_t Config :: rngSeed () const { return m_data . rngSeed ; }
unsigned int Config :: shardCount () const { return m_data . shardCount ; }
unsigned int Config :: shardIndex () const { return m_data . shardIndex ; }
2020-09-08 15:53:08 +02:00
UseColour Config :: useColour () const { return m_data . useColour ; }
bool Config :: shouldDebugBreak () const { return m_data . shouldDebugBreak ; }
int Config :: abortAfter () const { return m_data . abortAfter ; }
bool Config :: showInvisibles () const { return m_data . showInvisibles ; }
Verbosity Config :: verbosity () const { return m_data . verbosity ; }
bool Config :: benchmarkNoAnalysis () const { return m_data . benchmarkNoAnalysis ; }
2022-01-03 23:16:39 +01:00
unsigned int Config :: benchmarkSamples () const { return m_data . benchmarkSamples ; }
2020-09-08 15:53:08 +02:00
double Config :: benchmarkConfidenceInterval () const { return m_data . benchmarkConfidenceInterval ; }
unsigned int Config :: benchmarkResamples () const { return m_data . benchmarkResamples ; }
std :: chrono :: milliseconds Config :: benchmarkWarmupTime () const { return std :: chrono :: milliseconds ( m_data . benchmarkWarmupTime ); }
2022-01-03 23:16:39 +01:00
Detail :: unique_ptr < IStream const > Config :: openStream ( std :: string const & outputFileName ) {
return Catch :: makeStream ( outputFileName );
2020-09-08 15:53:08 +02:00
}
} // end namespace Catch
#include <cassert>
#include <stack>
namespace Catch {
////////////////////////////////////////////////////////////////////////////
ScopedMessage :: ScopedMessage ( MessageBuilder const & builder ) :
m_info ( builder . m_info ) {
m_info . message = builder . m_stream . str ();
getResultCapture (). pushScopedMessage ( m_info );
}
ScopedMessage :: ScopedMessage ( ScopedMessage && old ) noexcept :
2022-01-03 23:16:39 +01:00
m_info ( CATCH_MOVE ( old . m_info ) ) {
2020-09-08 15:53:08 +02:00
old . m_moved = true ;
}
ScopedMessage ::~ ScopedMessage () {
if ( ! uncaught_exceptions () && ! m_moved ){
getResultCapture (). popScopedMessage ( m_info );
}
}
Capturer :: Capturer ( StringRef macroName , SourceLineInfo const & lineInfo , ResultWas :: OfType resultType , StringRef names ) {
auto trimmed = [ & ] ( size_t start , size_t end ) {
while ( names [ start ] == ',' || isspace ( static_cast < unsigned char > ( names [ start ]))) {
++ start ;
}
while ( names [ end ] == ',' || isspace ( static_cast < unsigned char > ( names [ end ]))) {
-- end ;
}
return names . substr ( start , end - start + 1 );
};
auto skipq = [ & ] ( size_t start , char quote ) {
for ( auto i = start + 1 ; i < names . size () ; ++ i ) {
if ( names [ i ] == quote )
return i ;
if ( names [ i ] == '\\' )
++ i ;
}
CATCH_INTERNAL_ERROR ( "CAPTURE parsing encountered unmatched quote" );
};
size_t start = 0 ;
std :: stack < char > openings ;
for ( size_t pos = 0 ; pos < names . size (); ++ pos ) {
char c = names [ pos ];
switch ( c ) {
case '[' :
case '{' :
case '(' :
// It is basically impossible to disambiguate between
// comparison and start of template args in this context
// case '<':
openings . push ( c );
break ;
case ']' :
case '}' :
case ')' :
// case '>':
openings . pop ();
break ;
case '"' :
case '\'' :
pos = skipq ( pos , c );
break ;
case ',' :
if ( start != pos && openings . empty ()) {
m_messages . emplace_back ( macroName , lineInfo , resultType );
m_messages . back (). message = static_cast < std :: string > ( trimmed ( start , pos ));
m_messages . back (). message += " := " ;
start = pos ;
}
}
}
assert ( openings . empty () && "Mismatched openings" );
m_messages . emplace_back ( macroName , lineInfo , resultType );
m_messages . back (). message = static_cast < std :: string > ( trimmed ( start , names . size () - 1 ));
m_messages . back (). message += " := " ;
}
Capturer ::~ Capturer () {
if ( ! uncaught_exceptions () ){
assert ( m_captured == m_messages . size () );
for ( size_t i = 0 ; i < m_captured ; ++ i )
m_resultCapture . popScopedMessage ( m_messages [ i ] );
}
}
void Capturer :: captureValue ( size_t index , std :: string const & value ) {
assert ( index < m_messages . size () );
m_messages [ index ]. message += value ;
m_resultCapture . pushScopedMessage ( m_messages [ index ] );
m_captured ++ ;
}
} // end namespace Catch
namespace Catch {
namespace {
class RegistryHub : public IRegistryHub ,
public IMutableRegistryHub ,
private Detail :: NonCopyable {
public : // IRegistryHub
RegistryHub () = default ;
IReporterRegistry const & getReporterRegistry () const override {
return m_reporterRegistry ;
}
ITestCaseRegistry const & getTestCaseRegistry () const override {
return m_testCaseRegistry ;
}
IExceptionTranslatorRegistry const & getExceptionTranslatorRegistry () const override {
return m_exceptionTranslatorRegistry ;
}
ITagAliasRegistry const & getTagAliasRegistry () const override {
return m_tagAliasRegistry ;
}
StartupExceptionRegistry const & getStartupExceptionRegistry () const override {
return m_exceptionRegistry ;
}
public : // IMutableRegistryHub
void registerReporter ( std :: string const & name , IReporterFactoryPtr factory ) override {
2022-01-03 23:16:39 +01:00
m_reporterRegistry . registerReporter ( name , CATCH_MOVE ( factory ) );
2020-09-08 15:53:08 +02:00
}
void registerListener ( IReporterFactoryPtr factory ) override {
2022-01-03 23:16:39 +01:00
m_reporterRegistry . registerListener ( CATCH_MOVE ( factory ) );
2020-09-08 15:53:08 +02:00
}
void registerTest ( Detail :: unique_ptr < TestCaseInfo >&& testInfo , Detail :: unique_ptr < ITestInvoker >&& invoker ) override {
2022-01-03 23:16:39 +01:00
m_testCaseRegistry . registerTest ( CATCH_MOVE ( testInfo ), CATCH_MOVE ( invoker ) );
2020-09-08 15:53:08 +02:00
}
2022-01-03 23:16:39 +01:00
void registerTranslator ( Detail :: unique_ptr < IExceptionTranslator >&& translator ) override {
m_exceptionTranslatorRegistry . registerTranslator ( CATCH_MOVE ( translator ) );
2020-09-08 15:53:08 +02:00
}
void registerTagAlias ( std :: string const & alias , std :: string const & tag , SourceLineInfo const & lineInfo ) override {
m_tagAliasRegistry . add ( alias , tag , lineInfo );
}
void registerStartupException () noexcept override {
#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)
m_exceptionRegistry . add ( std :: current_exception ());
#else
CATCH_INTERNAL_ERROR ( "Attempted to register active exception under CATCH_CONFIG_DISABLE_EXCEPTIONS!" );
#endif
}
IMutableEnumValuesRegistry & getMutableEnumValuesRegistry () override {
return m_enumValuesRegistry ;
}
private :
TestRegistry m_testCaseRegistry ;
ReporterRegistry m_reporterRegistry ;
ExceptionTranslatorRegistry m_exceptionTranslatorRegistry ;
TagAliasRegistry m_tagAliasRegistry ;
StartupExceptionRegistry m_exceptionRegistry ;
Detail :: EnumValuesRegistry m_enumValuesRegistry ;
};
}
using RegistryHubSingleton = Singleton < RegistryHub , IRegistryHub , IMutableRegistryHub > ;
IRegistryHub const & getRegistryHub () {
return RegistryHubSingleton :: get ();
}
IMutableRegistryHub & getMutableRegistryHub () {
return RegistryHubSingleton :: getMutable ();
}
void cleanUp () {
cleanupSingletons ();
cleanUpContext ();
}
std :: string translateActiveException () {
return getRegistryHub (). getExceptionTranslatorRegistry (). translateActiveException ();
}
} // end namespace Catch
#include <algorithm>
2022-01-03 23:16:39 +01:00
#include <cassert>
2020-09-08 15:53:08 +02:00
#include <iomanip>
#include <set>
namespace Catch {
namespace {
const int MaxExitCode = 255 ;
2022-01-03 23:16:39 +01:00
IStreamingReporterPtr createReporter ( std :: string const & reporterName , ReporterConfig const & config ) {
2020-09-08 15:53:08 +02:00
auto reporter = Catch :: getRegistryHub (). getReporterRegistry (). create ( reporterName , config );
2022-01-03 23:16:39 +01:00
CATCH_ENFORCE ( reporter , "No reporter registered with name: '" << reporterName << '\'' );
2020-09-08 15:53:08 +02:00
return reporter ;
}
IStreamingReporterPtr makeReporter ( Config const * config ) {
2022-01-03 23:16:39 +01:00
if ( Catch :: getRegistryHub (). getReporterRegistry (). getListeners (). empty ()
&& config -> getReportersAndOutputFiles (). size () == 1 ) {
auto & stream = config -> getReporterOutputStream ( 0 );
return createReporter ( config -> getReportersAndOutputFiles ()[ 0 ]. reporterName , ReporterConfig ( config , stream ));
2020-09-08 15:53:08 +02:00
}
2022-01-03 23:16:39 +01:00
auto multi = Detail :: make_unique < ListeningReporter > ( config );
2020-09-08 15:53:08 +02:00
auto const & listeners = Catch :: getRegistryHub (). getReporterRegistry (). getListeners ();
for ( auto const & listener : listeners ) {
2022-01-03 23:16:39 +01:00
multi -> addListener ( listener -> create ( Catch :: ReporterConfig ( config , config -> defaultStream ())));
2020-09-08 15:53:08 +02:00
}
2022-01-03 23:16:39 +01:00
std :: size_t reporterIdx = 0 ;
for ( auto const & reporterAndFile : config -> getReportersAndOutputFiles ()) {
auto & stream = config -> getReporterOutputStream ( reporterIdx );
multi -> addReporter ( createReporter ( reporterAndFile . reporterName , ReporterConfig ( config , stream )));
reporterIdx ++ ;
}
return multi ;
2020-09-08 15:53:08 +02:00
}
class TestGroup {
public :
explicit TestGroup ( IStreamingReporterPtr && reporter , Config const * config ) :
m_reporter ( reporter . get ()),
m_config { config },
2022-01-03 23:16:39 +01:00
m_context { config , CATCH_MOVE ( reporter )} {
assert ( m_config -> testSpec (). getInvalidSpecs (). empty () &&
"Invalid test specs should be handled before running tests" );
2020-09-08 15:53:08 +02:00
auto const & allTestCases = getAllTestCasesSorted ( * m_config );
2022-01-03 23:16:39 +01:00
auto const & testSpec = m_config -> testSpec ();
if ( ! testSpec . hasFilters () ) {
for ( auto const & test : allTestCases ) {
if ( ! test . getTestCaseInfo (). isHidden () ) {
m_tests . emplace ( & test );
}
}
2020-09-08 15:53:08 +02:00
} else {
2022-01-03 23:16:39 +01:00
m_matches =
testSpec . matchesByFilter ( allTestCases , * m_config );
for ( auto const & match : m_matches ) {
m_tests . insert ( match . tests . begin (),
match . tests . end () );
}
2020-09-08 15:53:08 +02:00
}
2022-01-03 23:16:39 +01:00
m_tests = createShard ( m_tests , m_config -> shardCount (), m_config -> shardIndex ());
2020-09-08 15:53:08 +02:00
}
Totals execute () {
Totals totals ;
for ( auto const & testCase : m_tests ) {
if ( ! m_context . aborting ())
totals += m_context . runTest ( * testCase );
else
m_reporter -> skipTest ( testCase -> getTestCaseInfo ());
}
for ( auto const & match : m_matches ) {
if ( match . tests . empty ()) {
2022-01-03 23:16:39 +01:00
m_unmatchedTestSpecs = true ;
m_reporter -> noMatchingTestCases ( match . name );
2020-09-08 15:53:08 +02:00
}
}
return totals ;
}
2022-01-03 23:16:39 +01:00
bool hadUnmatchedTestSpecs () const {
return m_unmatchedTestSpecs ;
}
2020-09-08 15:53:08 +02:00
2022-01-03 23:16:39 +01:00
private :
2020-09-08 15:53:08 +02:00
IStreamingReporter * m_reporter ;
Config const * m_config ;
RunContext m_context ;
2022-01-03 23:16:39 +01:00
std :: set < TestCaseHandle const *> m_tests ;
2020-09-08 15:53:08 +02:00
TestSpec :: Matches m_matches ;
2022-01-03 23:16:39 +01:00
bool m_unmatchedTestSpecs = false ;
2020-09-08 15:53:08 +02:00
};
void applyFilenamesAsTags () {
for ( auto const & testInfo : getRegistryHub (). getTestCaseRegistry (). getAllInfos ()) {
testInfo -> addFilenameTag ();
}
}
} // anon namespace
Session :: Session () {
static bool alreadyInstantiated = false ;
if ( alreadyInstantiated ) {
CATCH_TRY { CATCH_INTERNAL_ERROR ( "Only one instance of Catch::Session can ever be used" ); }
CATCH_CATCH_ALL { getMutableRegistryHub (). registerStartupException (); }
}
// There cannot be exceptions at startup in no-exception mode.
#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)
const auto & exceptions = getRegistryHub (). getStartupExceptionRegistry (). getExceptions ();
if ( ! exceptions . empty () ) {
config ();
getCurrentMutableContext (). setConfig ( m_config . get ());
m_startupExceptions = true ;
Colour colourGuard ( Colour :: Red );
Catch :: cerr () << "Errors occurred during startup!" << '\n' ;
// iterate over all exceptions and notify user
for ( const auto & ex_ptr : exceptions ) {
try {
std :: rethrow_exception ( ex_ptr );
} catch ( std :: exception const & ex ) {
Catch :: cerr () << TextFlow :: Column ( ex . what () ). indent ( 2 ) << '\n' ;
}
}
}
#endif
alreadyInstantiated = true ;
m_cli = makeCommandLineParser ( m_configData );
}
Session ::~ Session () {
Catch :: cleanUp ();
}
void Session :: showHelp () const {
Catch :: cout ()
2022-01-03 23:16:39 +01:00
<< " \n Catch v" << libraryVersion () << '\n'
<< m_cli << '\n'
<< "For more detailed usage please see the project docs \n\n " << std :: flush ;
2020-09-08 15:53:08 +02:00
}
void Session :: libIdentify () {
Catch :: cout ()
<< std :: left << std :: setw ( 16 ) << "description: " << "A Catch2 test executable \n "
<< std :: left << std :: setw ( 16 ) << "category: " << "testframework \n "
<< std :: left << std :: setw ( 16 ) << "framework: " << "Catch Test \n "
2022-01-03 23:16:39 +01:00
<< std :: left << std :: setw ( 16 ) << "version: " << libraryVersion () << '\n' << std :: flush ;
2020-09-08 15:53:08 +02:00
}
int Session :: applyCommandLine ( int argc , char const * const * argv ) {
if ( m_startupExceptions )
return 1 ;
auto result = m_cli . parse ( Clara :: Args ( argc , argv ) );
2022-01-03 23:16:39 +01:00
2020-09-08 15:53:08 +02:00
if ( ! result ) {
config ();
getCurrentMutableContext (). setConfig ( m_config . get ());
Catch :: cerr ()
<< Colour ( Colour :: Red )
<< " \n Error(s) in input: \n "
<< TextFlow :: Column ( result . errorMessage () ). indent ( 2 )
<< " \n\n " ;
2022-01-03 23:16:39 +01:00
Catch :: cerr () << "Run with -? for usage \n\n " << std :: flush ;
2020-09-08 15:53:08 +02:00
return MaxExitCode ;
}
if ( m_configData . showHelp )
showHelp ();
if ( m_configData . libIdentify )
libIdentify ();
2022-01-03 23:16:39 +01:00
2020-09-08 15:53:08 +02:00
m_config . reset ();
return 0 ;
}
#if defined(CATCH_CONFIG_WCHAR) && defined(_WIN32) && defined(UNICODE)
int Session :: applyCommandLine ( int argc , wchar_t const * const * argv ) {
char ** utf8Argv = new char * [ argc ];
for ( int i = 0 ; i < argc ; ++ i ) {
int bufSize = WideCharToMultiByte ( CP_UTF8 , 0 , argv [ i ], - 1 , nullptr , 0 , nullptr , nullptr );
utf8Argv [ i ] = new char [ bufSize ];
WideCharToMultiByte ( CP_UTF8 , 0 , argv [ i ], - 1 , utf8Argv [ i ], bufSize , nullptr , nullptr );
}
int returnCode = applyCommandLine ( argc , utf8Argv );
for ( int i = 0 ; i < argc ; ++ i )
delete [] utf8Argv [ i ];
delete [] utf8Argv ;
return returnCode ;
}
#endif
void Session :: useConfigData ( ConfigData const & configData ) {
m_configData = configData ;
m_config . reset ();
}
int Session :: run () {
if ( ( m_configData . waitForKeypress & WaitForKeypress :: BeforeStart ) != 0 ) {
2022-01-03 23:16:39 +01:00
Catch :: cout () << "...waiting for enter/ return before starting \n " << std :: flush ;
2020-09-08 15:53:08 +02:00
static_cast < void > ( std :: getchar ());
}
int exitCode = runInternal ();
if ( ( m_configData . waitForKeypress & WaitForKeypress :: BeforeExit ) != 0 ) {
2022-01-03 23:16:39 +01:00
Catch :: cout () << "...waiting for enter/ return before exiting, with code: " << exitCode << '\n' << std :: flush ;
2020-09-08 15:53:08 +02:00
static_cast < void > ( std :: getchar ());
}
return exitCode ;
}
Clara :: Parser const & Session :: cli () const {
return m_cli ;
}
void Session :: cli ( Clara :: Parser const & newParser ) {
m_cli = newParser ;
}
ConfigData & Session :: configData () {
return m_configData ;
}
Config & Session :: config () {
if ( ! m_config )
m_config = Detail :: make_unique < Config > ( m_configData );
return * m_config ;
}
int Session :: runInternal () {
if ( m_startupExceptions )
return 1 ;
if ( m_configData . showHelp || m_configData . libIdentify ) {
return 0 ;
}
2022-01-03 23:16:39 +01:00
if ( m_configData . shardIndex >= m_configData . shardCount ) {
Catch :: cerr () << "The shard count (" << m_configData . shardCount
<< ") must be greater than the shard index ("
<< m_configData . shardIndex << ") \n "
<< std :: flush ;
return 1 ;
}
2020-09-08 15:53:08 +02:00
CATCH_TRY {
config (); // Force config to be constructed
seedRng ( * m_config );
if ( m_configData . filenamesAsTags ) {
applyFilenamesAsTags ();
}
// Set up global config instance before we start calling into other functions
getCurrentMutableContext (). setConfig ( m_config . get ());
// Create reporter(s) so we can route listings through them
auto reporter = makeReporter ( m_config . get ());
2022-01-03 23:16:39 +01:00
auto const & invalidSpecs = m_config -> testSpec (). getInvalidSpecs ();
if ( ! invalidSpecs . empty () ) {
for ( auto const & spec : invalidSpecs ) {
reporter -> reportInvalidTestSpec ( spec );
}
return 1 ;
}
2020-09-08 15:53:08 +02:00
// Handle list request
if ( list ( * reporter , * m_config )) {
return 0 ;
}
2022-01-03 23:16:39 +01:00
TestGroup tests { CATCH_MOVE ( reporter ), m_config . get () };
2020-09-08 15:53:08 +02:00
auto const totals = tests . execute ();
2022-01-03 23:16:39 +01:00
if ( tests . hadUnmatchedTestSpecs ()
&& m_config -> warnAboutUnmatchedTestSpecs () ) {
return 3 ;
}
if ( totals . testCases . total () == 0
&& ! m_config -> zeroTestsCountAsSuccess () ) {
2020-09-08 15:53:08 +02:00
return 2 ;
2022-01-03 23:16:39 +01:00
}
2020-09-08 15:53:08 +02:00
// Note that on unices only the lower 8 bits are usually used, clamping
// the return value to 255 prevents false negative when some multiple
// of 256 tests has failed
return ( std :: min ) ( MaxExitCode , ( std :: max ) ( totals . error , static_cast < int > ( totals . assertions . failed )));
}
#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)
catch ( std :: exception & ex ) {
2022-01-03 23:16:39 +01:00
Catch :: cerr () << ex . what () << '\n' << std :: flush ;
2020-09-08 15:53:08 +02:00
return MaxExitCode ;
}
#endif
}
} // end namespace Catch
#include <cassert>
#include <cctype>
#include <algorithm>
namespace Catch {
namespace {
using TCP_underlying_type = uint8_t ;
static_assert ( sizeof ( TestCaseProperties ) == sizeof ( TCP_underlying_type ),
"The size of the TestCaseProperties is different from the assumed size" );
TestCaseProperties operator | ( TestCaseProperties lhs , TestCaseProperties rhs ) {
return static_cast < TestCaseProperties > (
static_cast < TCP_underlying_type > ( lhs ) | static_cast < TCP_underlying_type > ( rhs )
);
}
TestCaseProperties & operator |= ( TestCaseProperties & lhs , TestCaseProperties rhs ) {
lhs = static_cast < TestCaseProperties > (
static_cast < TCP_underlying_type > ( lhs ) | static_cast < TCP_underlying_type > ( rhs )
);
return lhs ;
}
TestCaseProperties operator & ( TestCaseProperties lhs , TestCaseProperties rhs ) {
return static_cast < TestCaseProperties > (
static_cast < TCP_underlying_type > ( lhs ) & static_cast < TCP_underlying_type > ( rhs )
);
}
bool applies ( TestCaseProperties tcp ) {
static_assert ( static_cast < TCP_underlying_type > ( TestCaseProperties :: None ) == 0 ,
"TestCaseProperties::None must be equal to 0" );
return tcp != TestCaseProperties :: None ;
}
TestCaseProperties parseSpecialTag ( StringRef tag ) {
if ( ! tag . empty () && tag [ 0 ] == '.' )
return TestCaseProperties :: IsHidden ;
else if ( tag == "!throws" _sr )
return TestCaseProperties :: Throws ;
else if ( tag == "!shouldfail" _sr )
return TestCaseProperties :: ShouldFail ;
else if ( tag == "!mayfail" _sr )
return TestCaseProperties :: MayFail ;
else if ( tag == "!nonportable" _sr )
return TestCaseProperties :: NonPortable ;
else if ( tag == "!benchmark" _sr )
return static_cast < TestCaseProperties > ( TestCaseProperties :: Benchmark | TestCaseProperties :: IsHidden );
else
return TestCaseProperties :: None ;
}
bool isReservedTag ( StringRef tag ) {
return parseSpecialTag ( tag ) == TestCaseProperties :: None
&& tag . size () > 0
&& ! std :: isalnum ( static_cast < unsigned char > ( tag [ 0 ]) );
}
void enforceNotReservedTag ( StringRef tag , SourceLineInfo const & _lineInfo ) {
CATCH_ENFORCE ( ! isReservedTag ( tag ),
"Tag name: [" << tag << "] is not allowed. \n "
<< "Tag names starting with non alphanumeric characters are reserved \n "
<< _lineInfo );
}
std :: string makeDefaultName () {
static size_t counter = 0 ;
return "Anonymous test case " + std :: to_string ( ++ counter );
}
StringRef extractFilenamePart ( StringRef filename ) {
size_t lastDot = filename . size ();
while ( lastDot > 0 && filename [ lastDot - 1 ] != '.' ) {
-- lastDot ;
}
-- lastDot ;
size_t nameStart = lastDot ;
while ( nameStart > 0 && filename [ nameStart - 1 ] != '/' && filename [ nameStart - 1 ] != '\\' ) {
-- nameStart ;
}
return filename . substr ( nameStart , lastDot - nameStart );
}
// Returns the upper bound on size of extra tags ([#file]+[.])
size_t sizeOfExtraTags ( StringRef filepath ) {
// [.] is 3, [#] is another 3
const size_t extras = 3 + 3 ;
return extractFilenamePart ( filepath ). size () + extras ;
}
2022-01-03 23:16:39 +01:00
} // end unnamed namespace
bool operator < ( Tag const & lhs , Tag const & rhs ) {
Detail :: CaseInsensitiveLess cmp ;
return cmp ( lhs . original , rhs . original );
}
bool operator == ( Tag const & lhs , Tag const & rhs ) {
Detail :: CaseInsensitiveEqualTo cmp ;
return cmp ( lhs . original , rhs . original );
2020-09-08 15:53:08 +02:00
}
Detail :: unique_ptr < TestCaseInfo >
2022-01-03 23:16:39 +01:00
makeTestCaseInfo ( StringRef _className ,
2020-09-08 15:53:08 +02:00
NameAndTags const & nameAndTags ,
SourceLineInfo const & _lineInfo ) {
2021-05-28 23:10:33 +02:00
return Detail :: make_unique < TestCaseInfo > ( _className , nameAndTags , _lineInfo );
2020-09-08 15:53:08 +02:00
}
2022-01-03 23:16:39 +01:00
TestCaseInfo :: TestCaseInfo ( StringRef _className ,
2020-09-08 15:53:08 +02:00
NameAndTags const & _nameAndTags ,
SourceLineInfo const & _lineInfo ) :
name ( _nameAndTags . name . empty () ? makeDefaultName () : _nameAndTags . name ),
className ( _className ),
lineInfo ( _lineInfo )
{
StringRef originalTags = _nameAndTags . tags ;
// We need to reserve enough space to store all of the tags
// (including optional hidden tag and filename tag)
auto requiredSize = originalTags . size () + sizeOfExtraTags ( _lineInfo . file );
backingTags . reserve ( requiredSize );
// We cannot copy the tags directly, as we need to normalize
// some tags, so that [.foo] is copied as [.][foo].
size_t tagStart = 0 ;
size_t tagEnd = 0 ;
bool inTag = false ;
for ( size_t idx = 0 ; idx < originalTags . size (); ++ idx ) {
auto c = originalTags [ idx ];
if ( c == '[' ) {
assert ( ! inTag );
inTag = true ;
tagStart = idx ;
}
if ( c == ']' ) {
assert ( inTag );
inTag = false ;
tagEnd = idx ;
assert ( tagStart < tagEnd );
// We need to check the tag for special meanings, copy
// it over to backing storage and actually reference the
// backing storage in the saved tags
StringRef tagStr = originalTags . substr ( tagStart + 1 , tagEnd - tagStart - 1 );
2022-01-03 23:16:39 +01:00
CATCH_ENFORCE ( ! tagStr . empty (), "Empty tags are not allowed" );
2020-09-08 15:53:08 +02:00
enforceNotReservedTag ( tagStr , lineInfo );
properties |= parseSpecialTag ( tagStr );
// When copying a tag to the backing storage, we need to
// check if it is a merged hide tag, such as [.foo], and
// if it is, we need to handle it as if it was [foo].
if ( tagStr . size () > 1 && tagStr [ 0 ] == '.' ) {
tagStr = tagStr . substr ( 1 , tagStr . size () - 1 );
}
// We skip over dealing with the [.] tag, as we will add
// it later unconditionally and then sort and unique all
// the tags.
internalAppendTag ( tagStr );
}
( void ) inTag ; // Silence "set-but-unused" warning in release mode.
}
// Add [.] if relevant
if ( isHidden ()) {
internalAppendTag ( "." _sr );
}
// Sort and prepare tags
2022-01-03 23:16:39 +01:00
std :: sort ( begin ( tags ), end ( tags ));
tags . erase ( std :: unique ( begin ( tags ), end ( tags )),
2020-09-08 15:53:08 +02:00
end ( tags ));
}
bool TestCaseInfo :: isHidden () const {
return applies ( properties & TestCaseProperties :: IsHidden );
}
bool TestCaseInfo :: throws () const {
return applies ( properties & TestCaseProperties :: Throws );
}
bool TestCaseInfo :: okToFail () const {
return applies ( properties & ( TestCaseProperties :: ShouldFail | TestCaseProperties :: MayFail ) );
}
bool TestCaseInfo :: expectedToFail () const {
return applies ( properties & ( TestCaseProperties :: ShouldFail ) );
}
void TestCaseInfo :: addFilenameTag () {
std :: string combined ( "#" );
combined += extractFilenamePart ( lineInfo . file );
internalAppendTag ( combined );
}
std :: string TestCaseInfo :: tagsAsString () const {
std :: string ret ;
// '[' and ']' per tag
std :: size_t full_size = 2 * tags . size ();
for ( const auto & tag : tags ) {
full_size += tag . original . size ();
}
ret . reserve ( full_size );
for ( const auto & tag : tags ) {
ret . push_back ( '[' );
ret += tag . original ;
ret . push_back ( ']' );
}
return ret ;
}
void TestCaseInfo :: internalAppendTag ( StringRef tagStr ) {
backingTags += '[' ;
const auto backingStart = backingTags . size ();
backingTags += tagStr ;
const auto backingEnd = backingTags . size ();
backingTags += ']' ;
2022-01-03 23:16:39 +01:00
tags . emplace_back ( StringRef ( backingTags . c_str () + backingStart , backingEnd - backingStart ));
2020-09-08 15:53:08 +02:00
}
2022-01-03 23:16:39 +01:00
bool operator < ( TestCaseInfo const & lhs , TestCaseInfo const & rhs ) {
// We want to avoid redoing the string comparisons multiple times,
// so we store the result of a three-way comparison before using
// it in the actual comparison logic.
const auto cmpName = lhs . name . compare ( rhs . name );
if ( cmpName != 0 ) {
return cmpName < 0 ;
}
const auto cmpClassName = lhs . className . compare ( rhs . className );
if ( cmpClassName != 0 ) {
return cmpClassName < 0 ;
}
return lhs . tags < rhs . tags ;
2020-09-08 15:53:08 +02:00
}
TestCaseInfo const & TestCaseHandle :: getTestCaseInfo () const {
return * m_info ;
}
} // end namespace Catch
#include <algorithm>
#include <string>
#include <vector>
namespace Catch {
TestSpec :: Pattern :: Pattern ( std :: string const & name )
: m_name ( name )
{}
TestSpec :: Pattern ::~ Pattern () = default ;
std :: string const & TestSpec :: Pattern :: name () const {
return m_name ;
}
TestSpec :: NamePattern :: NamePattern ( std :: string const & name , std :: string const & filterString )
: Pattern ( filterString )
, m_wildcardPattern ( toLower ( name ), CaseSensitive :: No )
{}
bool TestSpec :: NamePattern :: matches ( TestCaseInfo const & testCase ) const {
return m_wildcardPattern . matches ( testCase . name );
}
TestSpec :: TagPattern :: TagPattern ( std :: string const & tag , std :: string const & filterString )
: Pattern ( filterString )
2022-01-03 23:16:39 +01:00
, m_tag ( tag )
2020-09-08 15:53:08 +02:00
{}
bool TestSpec :: TagPattern :: matches ( TestCaseInfo const & testCase ) const {
2022-01-03 23:16:39 +01:00
return std :: find ( begin ( testCase . tags ),
end ( testCase . tags ),
Tag ( m_tag ) ) != end ( testCase . tags );
2020-09-08 15:53:08 +02:00
}
bool TestSpec :: Filter :: matches ( TestCaseInfo const & testCase ) const {
bool should_use = ! testCase . isHidden ();
for ( auto const & pattern : m_required ) {
should_use = true ;
if ( ! pattern -> matches ( testCase )) {
return false ;
}
}
for ( auto const & pattern : m_forbidden ) {
if ( pattern -> matches ( testCase )) {
return false ;
}
}
return should_use ;
}
std :: string TestSpec :: Filter :: name () const {
std :: string name ;
for ( auto const & p : m_required ) {
name += p -> name ();
}
for ( auto const & p : m_forbidden ) {
name += p -> name ();
}
return name ;
}
bool TestSpec :: hasFilters () const {
return ! m_filters . empty ();
}
bool TestSpec :: matches ( TestCaseInfo const & testCase ) const {
return std :: any_of ( m_filters . begin (), m_filters . end (), [ & ]( Filter const & f ){ return f . matches ( testCase ); } );
}
TestSpec :: Matches TestSpec :: matchesByFilter ( std :: vector < TestCaseHandle > const & testCases , IConfig const & config ) const
{
Matches matches ( m_filters . size () );
std :: transform ( m_filters . begin (), m_filters . end (), matches . begin (), [ & ]( Filter const & filter ){
std :: vector < TestCaseHandle const *> currentMatches ;
for ( auto const & test : testCases )
if ( isThrowSafe ( test , config ) && filter . matches ( test . getTestCaseInfo () ) )
currentMatches . emplace_back ( & test );
return FilterMatch { filter . name (), currentMatches };
} );
return matches ;
}
2022-01-03 23:16:39 +01:00
const TestSpec :: vectorStrings & TestSpec :: getInvalidSpecs () const {
return m_invalidSpecs ;
2020-09-08 15:53:08 +02:00
}
}
#include <chrono>
namespace Catch {
namespace {
2022-01-03 23:16:39 +01:00
static auto getCurrentNanosecondsSinceEpoch () -> uint64_t {
return std :: chrono :: duration_cast < std :: chrono :: nanoseconds > ( std :: chrono :: high_resolution_clock :: now (). time_since_epoch ()). count ();
2020-09-08 15:53:08 +02:00
}
2022-01-03 23:16:39 +01:00
} // end unnamed namespace
2020-09-08 15:53:08 +02:00
void Timer :: start () {
m_nanoseconds = getCurrentNanosecondsSinceEpoch ();
}
auto Timer :: getElapsedNanoseconds () const -> uint64_t {
return getCurrentNanosecondsSinceEpoch () - m_nanoseconds ;
}
auto Timer :: getElapsedMicroseconds () const -> uint64_t {
return getElapsedNanoseconds () / 1000 ;
}
auto Timer :: getElapsedMilliseconds () const -> unsigned int {
return static_cast < unsigned int > ( getElapsedMicroseconds () / 1000 );
}
auto Timer :: getElapsedSeconds () const -> double {
return getElapsedMicroseconds () / 1000000.0 ;
}
} // namespace Catch
#include <cmath>
#include <iomanip>
namespace Catch {
namespace Detail {
namespace {
const int hexThreshold = 255 ;
struct Endianness {
enum Arch { Big , Little };
static Arch which () {
int one = 1 ;
// If the lowest byte we read is non-zero, we can assume
// that little endian format is used.
auto value = * reinterpret_cast < char *> ( & one );
return value ? Little : Big ;
}
};
template < typename T >
std :: string fpToString ( T value , int precision ) {
if ( Catch :: isnan ( value )) {
return "nan" ;
}
ReusableStringStream rss ;
rss << std :: setprecision ( precision )
<< std :: fixed
<< value ;
std :: string d = rss . str ();
std :: size_t i = d . find_last_not_of ( '0' );
if ( i != std :: string :: npos && i != d . size () - 1 ) {
if ( d [ i ] == '.' )
i ++ ;
d = d . substr ( 0 , i + 1 );
}
return d ;
}
} // end unnamed namespace
2022-01-03 23:16:39 +01:00
std :: string convertIntoString ( StringRef string , bool escape_invisibles ) {
std :: string ret ;
// This is enough for the "don't escape invisibles" case, and a good
// lower bound on the "escape invisibles" case.
ret . reserve ( string . size () + 2 );
if ( ! escape_invisibles ) {
ret += '"' ;
ret += string ;
ret += '"' ;
return ret ;
}
ret += '"' ;
for ( char c : string ) {
switch ( c ) {
case '\r' :
ret . append ( " \\ r" );
break ;
case '\n' :
ret . append ( " \\ n" );
break ;
case '\t' :
ret . append ( " \\ t" );
break ;
case '\f' :
ret . append ( " \\ f" );
break ;
default :
ret . push_back ( c );
break ;
}
}
ret += '"' ;
return ret ;
}
std :: string convertIntoString ( StringRef string ) {
return convertIntoString ( string , getCurrentContext (). getConfig () -> showInvisibles ());
}
2020-09-08 15:53:08 +02:00
std :: string rawMemoryToString ( const void * object , std :: size_t size ) {
// Reverse order for little endian architectures
int i = 0 , end = static_cast < int > ( size ), inc = 1 ;
if ( Endianness :: which () == Endianness :: Little ) {
i = end - 1 ;
end = inc = - 1 ;
}
unsigned char const * bytes = static_cast < unsigned char const *> ( object );
ReusableStringStream rss ;
rss << "0x" << std :: setfill ( '0' ) << std :: hex ;
for ( ; i != end ; i += inc )
rss << std :: setw ( 2 ) << static_cast < unsigned > ( bytes [ i ]);
return rss . str ();
}
} // end Detail namespace
//// ======================================================= ////
//
// Out-of-line defs for full specialization of StringMaker
//
//// ======================================================= ////
std :: string StringMaker < std :: string >:: convert ( const std :: string & str ) {
2022-01-03 23:16:39 +01:00
return Detail :: convertIntoString ( str );
2020-09-08 15:53:08 +02:00
}
#ifdef CATCH_CONFIG_CPP17_STRING_VIEW
std :: string StringMaker < std :: string_view >:: convert ( std :: string_view str ) {
2022-01-03 23:16:39 +01:00
return Detail :: convertIntoString ( StringRef ( str . data (), str . size () ) );
2020-09-08 15:53:08 +02:00
}
#endif
std :: string StringMaker < char const *>:: convert ( char const * str ) {
if ( str ) {
2022-01-03 23:16:39 +01:00
return Detail :: convertIntoString ( str );
2020-09-08 15:53:08 +02:00
} else {
return { "{null string}" };
}
}
std :: string StringMaker < char *>:: convert ( char * str ) {
if ( str ) {
2022-01-03 23:16:39 +01:00
return Detail :: convertIntoString ( str );
2020-09-08 15:53:08 +02:00
} else {
return { "{null string}" };
}
}
#ifdef CATCH_CONFIG_WCHAR
std :: string StringMaker < std :: wstring >:: convert ( const std :: wstring & wstr ) {
std :: string s ;
s . reserve ( wstr . size ());
for ( auto c : wstr ) {
s += ( c <= 0xff ) ? static_cast < char > ( c ) : '?' ;
}
return :: Catch :: Detail :: stringify ( s );
}
# ifdef CATCH_CONFIG_CPP17_STRING_VIEW
std :: string StringMaker < std :: wstring_view >:: convert ( std :: wstring_view str ) {
return StringMaker < std :: wstring >:: convert ( std :: wstring ( str ));
}
# endif
std :: string StringMaker < wchar_t const *>:: convert ( wchar_t const * str ) {
if ( str ) {
return :: Catch :: Detail :: stringify ( std :: wstring { str });
} else {
return { "{null string}" };
}
}
std :: string StringMaker < wchar_t *>:: convert ( wchar_t * str ) {
if ( str ) {
return :: Catch :: Detail :: stringify ( std :: wstring { str });
} else {
return { "{null string}" };
}
}
#endif
#if defined(CATCH_CONFIG_CPP17_BYTE)
#include <cstddef>
std :: string StringMaker < std :: byte >:: convert ( std :: byte value ) {
return :: Catch :: Detail :: stringify ( std :: to_integer < unsigned long long > ( value ));
}
#endif // defined(CATCH_CONFIG_CPP17_BYTE)
std :: string StringMaker < int >:: convert ( int value ) {
return :: Catch :: Detail :: stringify ( static_cast < long long > ( value ));
}
std :: string StringMaker < long >:: convert ( long value ) {
return :: Catch :: Detail :: stringify ( static_cast < long long > ( value ));
}
std :: string StringMaker < long long >:: convert ( long long value ) {
ReusableStringStream rss ;
rss << value ;
if ( value > Detail :: hexThreshold ) {
rss << " (0x" << std :: hex << value << ')' ;
}
return rss . str ();
}
std :: string StringMaker < unsigned int >:: convert ( unsigned int value ) {
return :: Catch :: Detail :: stringify ( static_cast < unsigned long long > ( value ));
}
std :: string StringMaker < unsigned long >:: convert ( unsigned long value ) {
return :: Catch :: Detail :: stringify ( static_cast < unsigned long long > ( value ));
}
std :: string StringMaker < unsigned long long >:: convert ( unsigned long long value ) {
ReusableStringStream rss ;
rss << value ;
if ( value > Detail :: hexThreshold ) {
rss << " (0x" << std :: hex << value << ')' ;
}
return rss . str ();
}
std :: string StringMaker < signed char >:: convert ( signed char value ) {
if ( value == '\r' ) {
return "' \\ r'" ;
} else if ( value == '\f' ) {
return "' \\ f'" ;
} else if ( value == '\n' ) {
return "' \\ n'" ;
} else if ( value == '\t' ) {
return "' \\ t'" ;
} else if ( '\0' <= value && value < ' ' ) {
return :: Catch :: Detail :: stringify ( static_cast < unsigned int > ( value ));
} else {
char chstr [] = "' '" ;
chstr [ 1 ] = value ;
return chstr ;
}
}
std :: string StringMaker < char >:: convert ( char c ) {
return :: Catch :: Detail :: stringify ( static_cast < signed char > ( c ));
}
std :: string StringMaker < unsigned char >:: convert ( unsigned char c ) {
return :: Catch :: Detail :: stringify ( static_cast < char > ( c ));
}
int StringMaker < float >:: precision = 5 ;
std :: string StringMaker < float >:: convert ( float value ) {
return Detail :: fpToString ( value , precision ) + 'f' ;
}
int StringMaker < double >:: precision = 10 ;
std :: string StringMaker < double >:: convert ( double value ) {
return Detail :: fpToString ( value , precision );
}
} // end namespace Catch
namespace Catch {
Counts Counts :: operator - ( Counts const & other ) const {
Counts diff ;
diff . passed = passed - other . passed ;
diff . failed = failed - other . failed ;
diff . failedButOk = failedButOk - other . failedButOk ;
return diff ;
}
Counts & Counts :: operator += ( Counts const & other ) {
passed += other . passed ;
failed += other . failed ;
failedButOk += other . failedButOk ;
return * this ;
}
2022-01-03 23:16:39 +01:00
std :: uint64_t Counts :: total () const {
2020-09-08 15:53:08 +02:00
return passed + failed + failedButOk ;
}
bool Counts :: allPassed () const {
return failed == 0 && failedButOk == 0 ;
}
bool Counts :: allOk () const {
return failed == 0 ;
}
Totals Totals :: operator - ( Totals const & other ) const {
Totals diff ;
diff . assertions = assertions - other . assertions ;
diff . testCases = testCases - other . testCases ;
return diff ;
}
Totals & Totals :: operator += ( Totals const & other ) {
assertions += other . assertions ;
testCases += other . testCases ;
return * this ;
}
Totals Totals :: delta ( Totals const & prevTotals ) const {
Totals diff = * this - prevTotals ;
if ( diff . assertions . failed > 0 )
++ diff . testCases . failed ;
else if ( diff . assertions . failedButOk > 0 )
++ diff . testCases . failedButOk ;
else
++ diff . testCases . passed ;
return diff ;
}
}
#include <ostream>
namespace Catch {
Version :: Version
( unsigned int _majorVersion ,
unsigned int _minorVersion ,
unsigned int _patchNumber ,
char const * const _branchName ,
unsigned int _buildNumber )
: majorVersion ( _majorVersion ),
minorVersion ( _minorVersion ),
patchNumber ( _patchNumber ),
branchName ( _branchName ),
buildNumber ( _buildNumber )
{}
std :: ostream & operator << ( std :: ostream & os , Version const & version ) {
os << version . majorVersion << '.'
<< version . minorVersion << '.'
<< version . patchNumber ;
// branchName is never null -> 0th char is \0 if it is empty
if ( version . branchName [ 0 ]) {
os << '-' << version . branchName
<< '.' << version . buildNumber ;
}
return os ;
}
Version const & libraryVersion () {
2022-01-03 23:16:39 +01:00
static Version version ( 3 , 0 , 0 , "preview" , 4 );
2020-09-08 15:53:08 +02:00
return version ;
}
}
/** \file
* This is a special TU that combines what would otherwise be a very
* small generator-related TUs into one bigger TU.
*
* The reason for this is compilation performance improvements by
* avoiding reparsing headers for many small TUs, instead having this
* one TU include bit more, but having it all parsed only once.
*
* To avoid heavy-tail problem with compilation times, each "subpart"
* of Catch2 has its own combined TU like this.
*/
////////////////////////////////////////////////////
// vvv formerly catch_generator_exception.cpp vvv //
////////////////////////////////////////////////////
namespace Catch {
const char * GeneratorException :: what () const noexcept {
return m_msg ;
}
} // end namespace Catch
///////////////////////////////////////////
// vvv formerly catch_generators.cpp vvv //
///////////////////////////////////////////
namespace Catch {
IGeneratorTracker ::~ IGeneratorTracker () = default ;
namespace Generators {
namespace Detail {
[[noreturn]]
void throw_generator_exception ( char const * msg ) {
Catch :: throw_exception ( GeneratorException { msg });
}
} // end namespace Detail
GeneratorUntypedBase ::~ GeneratorUntypedBase () = default ;
auto acquireGeneratorTracker ( StringRef generatorName , SourceLineInfo const & lineInfo ) -> IGeneratorTracker & {
return getResultCapture (). acquireGeneratorTracker ( generatorName , lineInfo );
}
} // namespace Generators
} // namespace Catch
/** \file
* This is a special TU that combines what would otherwise be a very
* small interfaces-related TUs into one bigger TU.
*
* The reason for this is compilation performance improvements by
* avoiding reparsing headers for many small TUs, instead having this
* one TU include bit more, but having it all parsed only once.
*
* To avoid heavy-tail problem with compilation times, each "subpart"
* of Catch2 has its own combined TU like this.
*/
///////////////////////////////////////////////////
// vvv formerly catch_interfaces_capture.cpp vvv //
///////////////////////////////////////////////////
namespace Catch {
IResultCapture ::~ IResultCapture () = default ;
}
//////////////////////////////////////////////////
// vvv formerly catch_interfaces_config.cpp vvv //
//////////////////////////////////////////////////
namespace Catch {
IConfig ::~ IConfig () = default ;
}
/////////////////////////////////////////////////////
// vvv formerly catch_interfaces_exception.cpp vvv //
/////////////////////////////////////////////////////
namespace Catch {
IExceptionTranslator ::~ IExceptionTranslator () = default ;
IExceptionTranslatorRegistry ::~ IExceptionTranslatorRegistry () = default ;
}
////////////////////////////////////////////////////////
// vvv formerly catch_interfaces_registry_hub.cpp vvv //
////////////////////////////////////////////////////////
namespace Catch {
IRegistryHub ::~ IRegistryHub () = default ;
IMutableRegistryHub ::~ IMutableRegistryHub () = default ;
}
////////////////////////////////////////////////////
// vvv formerly catch_interfaces_testcase.cpp vvv //
////////////////////////////////////////////////////
namespace Catch {
ITestInvoker ::~ ITestInvoker () = default ;
ITestCaseRegistry ::~ ITestCaseRegistry () = default ;
}
2022-01-03 23:16:39 +01:00
2020-09-08 15:53:08 +02:00
namespace Catch {
IReporterRegistry ::~ IReporterRegistry () = default ;
}
namespace Catch {
IReporterFactory ::~ IReporterFactory () = default ;
}
#include <algorithm>
#include <iomanip>
namespace Catch {
ReporterConfig :: ReporterConfig ( IConfig const * _fullConfig , std :: ostream & _stream )
: m_stream ( & _stream ), m_fullConfig ( _fullConfig ) {}
std :: ostream & ReporterConfig :: stream () const { return * m_stream ; }
IConfig const * ReporterConfig :: fullConfig () const { return m_fullConfig ; }
2022-01-03 23:16:39 +01:00
AssertionStats :: AssertionStats ( AssertionResult const & _assertionResult ,
std :: vector < MessageInfo > const & _infoMessages ,
Totals const & _totals )
2020-09-08 15:53:08 +02:00
: assertionResult ( _assertionResult ),
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
MessageBuilder builder ( assertionResult . getTestMacroName (), assertionResult . getSourceInfo (), assertionResult . getResultType () );
builder << assertionResult . getMessage ();
builder . m_info . message = builder . m_stream . str ();
infoMessages . push_back ( builder . m_info );
}
}
SectionStats :: SectionStats ( SectionInfo const & _sectionInfo ,
Counts const & _assertions ,
double _durationInSeconds ,
bool _missingAssertions )
: sectionInfo ( _sectionInfo ),
assertions ( _assertions ),
durationInSeconds ( _durationInSeconds ),
missingAssertions ( _missingAssertions )
{}
TestCaseStats :: TestCaseStats ( TestCaseInfo const & _testInfo ,
Totals const & _totals ,
std :: string const & _stdOut ,
std :: string const & _stdErr ,
bool _aborting )
: testInfo ( & _testInfo ),
totals ( _totals ),
stdOut ( _stdOut ),
stdErr ( _stdErr ),
aborting ( _aborting )
{}
TestRunStats :: TestRunStats ( TestRunInfo const & _runInfo ,
Totals const & _totals ,
bool _aborting )
: runInfo ( _runInfo ),
totals ( _totals ),
aborting ( _aborting )
{}
2022-01-03 23:16:39 +01:00
IStreamingReporter ::~ IStreamingReporter () = default ;
2020-09-08 15:53:08 +02:00
} // end namespace Catch
namespace Catch {
AssertionHandler :: AssertionHandler
2022-01-03 23:16:39 +01:00
( StringRef macroName ,
2020-09-08 15:53:08 +02:00
SourceLineInfo const & lineInfo ,
StringRef capturedExpression ,
ResultDisposition :: Flags resultDisposition )
: m_assertionInfo { macroName , lineInfo , capturedExpression , resultDisposition },
m_resultCapture ( getResultCapture () )
{}
void AssertionHandler :: handleExpr ( ITransientExpression const & expr ) {
m_resultCapture . handleExpr ( m_assertionInfo , expr , m_reaction );
}
2022-01-03 23:16:39 +01:00
void AssertionHandler :: handleMessage ( ResultWas :: OfType resultType , StringRef message ) {
2020-09-08 15:53:08 +02:00
m_resultCapture . handleMessage ( m_assertionInfo , resultType , message , m_reaction );
}
auto AssertionHandler :: allowThrows () const -> bool {
return getCurrentContext (). getConfig () -> allowThrows ();
}
void AssertionHandler :: complete () {
setCompleted ();
if ( m_reaction . shouldDebugBreak ) {
// If you find your debugger stopping you here then go one level up on the
// call-stack for the code that caused it (typically a failed assertion)
// (To go back to the test and change execution, jump over the throw, next)
CATCH_BREAK_INTO_DEBUGGER ();
}
if ( m_reaction . shouldThrow ) {
#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)
throw Catch :: TestFailureException ();
#else
CATCH_ERROR ( "Test failure requires aborting test!" );
#endif
}
}
void AssertionHandler :: setCompleted () {
m_completed = true ;
}
void AssertionHandler :: handleUnexpectedInflightException () {
m_resultCapture . handleUnexpectedInflightException ( m_assertionInfo , Catch :: translateActiveException (), m_reaction );
}
void AssertionHandler :: handleExceptionThrownAsExpected () {
m_resultCapture . handleNonExpr ( m_assertionInfo , ResultWas :: Ok , m_reaction );
}
void AssertionHandler :: handleExceptionNotThrownAsExpected () {
m_resultCapture . handleNonExpr ( m_assertionInfo , ResultWas :: Ok , m_reaction );
}
void AssertionHandler :: handleUnexpectedExceptionNotThrown () {
m_resultCapture . handleUnexpectedExceptionNotThrown ( m_assertionInfo , m_reaction );
}
void AssertionHandler :: handleThrowingCallSkipped () {
m_resultCapture . handleNonExpr ( m_assertionInfo , ResultWas :: Ok , m_reaction );
}
// This is the overload that takes a string and infers the Equals matcher from it
// The more general overload, that takes any string matcher, is in catch_capture_matchers.cpp
2022-01-03 23:16:39 +01:00
void handleExceptionMatchExpr ( AssertionHandler & handler , std :: string const & str , StringRef matcherString ) {
2020-09-08 15:53:08 +02:00
handleExceptionMatchExpr ( handler , Matchers :: Equals ( str ), matcherString );
}
} // namespace Catch
2022-01-03 23:16:39 +01:00
#include <algorithm>
namespace Catch {
namespace Detail {
bool CaseInsensitiveLess :: operator ()( StringRef lhs ,
StringRef rhs ) const {
return std :: lexicographical_compare (
lhs . begin (), lhs . end (),
rhs . begin (), rhs . end (),
[]( char l , char r ) { return toLower ( l ) < toLower ( r ); } );
}
bool
CaseInsensitiveEqualTo :: operator ()( StringRef lhs ,
StringRef rhs ) const {
return std :: equal (
lhs . begin (), lhs . end (),
rhs . begin (), rhs . end (),
[]( char l , char r ) { return toLower ( l ) == toLower ( r ); } );
}
} // namespace Detail
} // namespace Catch
2020-09-08 15:53:08 +02:00
#include <algorithm>
namespace {
bool isOptPrefix ( char c ) {
return c == '-'
#ifdef CATCH_PLATFORM_WINDOWS
|| c == '/'
#endif
;
}
std :: string normaliseOpt ( std :: string const & optName ) {
#ifdef CATCH_PLATFORM_WINDOWS
if ( optName [ 0 ] == '/' )
return "-" + optName . substr ( 1 );
else
#endif
return optName ;
}
} // namespace
namespace Catch {
namespace Clara {
namespace Detail {
void TokenStream :: loadBuffer () {
m_tokenBuffer . clear ();
// Skip any empty strings
while ( it != itEnd && it -> empty () ) {
++ it ;
}
if ( it != itEnd ) {
auto const & next = * it ;
if ( isOptPrefix ( next [ 0 ] ) ) {
auto delimiterPos = next . find_first_of ( " :=" );
if ( delimiterPos != std :: string :: npos ) {
m_tokenBuffer . push_back (
{ TokenType :: Option ,
next . substr ( 0 , delimiterPos ) } );
m_tokenBuffer . push_back (
{ TokenType :: Argument ,
next . substr ( delimiterPos + 1 ) } );
} else {
if ( next [ 1 ] != '-' && next . size () > 2 ) {
std :: string opt = "- " ;
for ( size_t i = 1 ; i < next . size (); ++ i ) {
opt [ 1 ] = next [ i ];
m_tokenBuffer . push_back (
{ TokenType :: Option , opt } );
}
} else {
m_tokenBuffer . push_back (
{ TokenType :: Option , next } );
}
}
} else {
m_tokenBuffer . push_back (
{ TokenType :: Argument , next } );
}
}
}
TokenStream :: TokenStream ( Args const & args ) :
TokenStream ( args . m_args . begin (), args . m_args . end () ) {}
TokenStream :: TokenStream ( Iterator it_ , Iterator itEnd_ ) :
it ( it_ ), itEnd ( itEnd_ ) {
loadBuffer ();
}
TokenStream & TokenStream :: operator ++ () {
if ( m_tokenBuffer . size () >= 2 ) {
m_tokenBuffer . erase ( m_tokenBuffer . begin () );
} else {
if ( it != itEnd )
++ it ;
loadBuffer ();
}
return * this ;
}
ParserResult convertInto ( std :: string const & source ,
std :: string & target ) {
target = source ;
return ParserResult :: ok ( ParseResultType :: Matched );
}
ParserResult convertInto ( std :: string const & source ,
bool & target ) {
std :: string srcLC = toLower ( source );
if ( srcLC == "y" || srcLC == "1" || srcLC == "true" ||
srcLC == "yes" || srcLC == "on" ) {
target = true ;
} else if ( srcLC == "n" || srcLC == "0" || srcLC == "false" ||
srcLC == "no" || srcLC == "off" ) {
target = false ;
} else {
return ParserResult :: runtimeError (
"Expected a boolean value but did not recognise: '" +
2022-01-03 23:16:39 +01:00
source + '\'' );
2020-09-08 15:53:08 +02:00
}
return ParserResult :: ok ( ParseResultType :: Matched );
}
size_t ParserBase :: cardinality () const { return 1 ; }
InternalParseResult ParserBase :: parse ( Args const & args ) const {
return parse ( args . exeName (), TokenStream ( args ) );
}
ParseState :: ParseState ( ParseResultType type ,
TokenStream const & remainingTokens ) :
m_type ( type ), m_remainingTokens ( remainingTokens ) {}
ParserResult BoundFlagRef :: setFlag ( bool flag ) {
m_ref = flag ;
return ParserResult :: ok ( ParseResultType :: Matched );
}
ResultBase ::~ ResultBase () = default ;
bool BoundRef :: isContainer () const { return false ; }
bool BoundRef :: isFlag () const { return false ; }
bool BoundFlagRefBase :: isFlag () const { return true ; }
} // namespace Detail
Detail :: InternalParseResult Arg :: parse ( std :: string const & ,
Detail :: TokenStream const & tokens ) const {
auto validationResult = validate ();
if ( ! validationResult )
return Detail :: InternalParseResult ( validationResult );
auto remainingTokens = tokens ;
auto const & token = * remainingTokens ;
if ( token . type != Detail :: TokenType :: Argument )
return Detail :: InternalParseResult :: ok ( Detail :: ParseState (
ParseResultType :: NoMatch , remainingTokens ));
assert ( ! m_ref -> isFlag ());
auto valueRef =
static_cast < Detail :: BoundValueRefBase *> ( m_ref . get ());
auto result = valueRef -> setValue ( remainingTokens -> token );
if ( ! result )
return Detail :: InternalParseResult ( result );
else
return Detail :: InternalParseResult :: ok ( Detail :: ParseState (
ParseResultType :: Matched , ++ remainingTokens ));
}
Opt :: Opt ( bool & ref ) :
ParserRefImpl ( std :: make_shared < Detail :: BoundFlagRef > ( ref )) {}
std :: vector < Detail :: HelpColumns > Opt :: getHelpColumns () const {
std :: ostringstream oss ;
bool first = true ;
for ( auto const & opt : m_optNames ) {
if ( first )
first = false ;
else
oss << ", " ;
oss << opt ;
}
if ( ! m_hint . empty ())
oss << " <" << m_hint << '>' ;
return { { oss . str (), m_description } };
}
bool Opt :: isMatch ( std :: string const & optToken ) const {
auto normalisedToken = normaliseOpt ( optToken );
for ( auto const & name : m_optNames ) {
if ( normaliseOpt ( name ) == normalisedToken )
return true ;
}
return false ;
}
Detail :: InternalParseResult Opt :: parse ( std :: string const & ,
Detail :: TokenStream const & tokens ) const {
auto validationResult = validate ();
if ( ! validationResult )
return Detail :: InternalParseResult ( validationResult );
auto remainingTokens = tokens ;
if ( remainingTokens &&
remainingTokens -> type == Detail :: TokenType :: Option ) {
auto const & token = * remainingTokens ;
if ( isMatch ( token . token )) {
if ( m_ref -> isFlag ()) {
auto flagRef =
static_cast < Detail :: BoundFlagRefBase *> (
m_ref . get ());
auto result = flagRef -> setFlag ( true );
if ( ! result )
return Detail :: InternalParseResult ( result );
if ( result . value () ==
ParseResultType :: ShortCircuitAll )
return Detail :: InternalParseResult :: ok ( Detail :: ParseState (
result . value (), remainingTokens ));
} else {
auto valueRef =
static_cast < Detail :: BoundValueRefBase *> (
m_ref . get ());
++ remainingTokens ;
if ( ! remainingTokens )
return Detail :: InternalParseResult :: runtimeError (
"Expected argument following " +
token . token );
auto const & argToken = * remainingTokens ;
if ( argToken . type != Detail :: TokenType :: Argument )
return Detail :: InternalParseResult :: runtimeError (
"Expected argument following " +
token . token );
auto result = valueRef -> setValue ( argToken . token );
if ( ! result )
return Detail :: InternalParseResult ( result );
if ( result . value () ==
ParseResultType :: ShortCircuitAll )
return Detail :: InternalParseResult :: ok ( Detail :: ParseState (
result . value (), remainingTokens ));
}
return Detail :: InternalParseResult :: ok ( Detail :: ParseState (
ParseResultType :: Matched , ++ remainingTokens ));
}
}
return Detail :: InternalParseResult :: ok (
Detail :: ParseState ( ParseResultType :: NoMatch , remainingTokens ));
}
Detail :: Result Opt :: validate () const {
if ( m_optNames . empty ())
return Detail :: Result :: logicError ( "No options supplied to Opt" );
for ( auto const & name : m_optNames ) {
if ( name . empty ())
return Detail :: Result :: logicError (
"Option name cannot be empty" );
#ifdef CATCH_PLATFORM_WINDOWS
if ( name [ 0 ] != '-' && name [ 0 ] != '/' )
return Detail :: Result :: logicError (
"Option name must begin with '-' or '/'" );
#else
if ( name [ 0 ] != '-' )
return Detail :: Result :: logicError (
"Option name must begin with '-'" );
#endif
}
return ParserRefImpl :: validate ();
}
ExeName :: ExeName () :
m_name ( std :: make_shared < std :: string > ( "<executable>" )) {}
ExeName :: ExeName ( std :: string & ref ) : ExeName () {
m_ref = std :: make_shared < Detail :: BoundValueRef < std :: string >> ( ref );
}
Detail :: InternalParseResult
ExeName :: parse ( std :: string const & ,
Detail :: TokenStream const & tokens ) const {
return Detail :: InternalParseResult :: ok (
Detail :: ParseState ( ParseResultType :: NoMatch , tokens ));
}
ParserResult ExeName :: set ( std :: string const & newName ) {
auto lastSlash = newName . find_last_of ( " \\ /" );
auto filename = ( lastSlash == std :: string :: npos )
? newName
: newName . substr ( lastSlash + 1 );
* m_name = filename ;
if ( m_ref )
return m_ref -> setValue ( filename );
else
return ParserResult :: ok ( ParseResultType :: Matched );
}
Parser & Parser :: operator |= ( Parser const & other ) {
m_options . insert ( m_options . end (),
other . m_options . begin (),
other . m_options . end () );
m_args . insert (
m_args . end (), other . m_args . begin (), other . m_args . end () );
return * this ;
}
std :: vector < Detail :: HelpColumns > Parser :: getHelpColumns () const {
std :: vector < Detail :: HelpColumns > cols ;
for ( auto const & o : m_options ) {
auto childCols = o . getHelpColumns ();
cols . insert ( cols . end (), childCols . begin (), childCols . end () );
}
return cols ;
}
void Parser :: writeToStream ( std :: ostream & os ) const {
if ( ! m_exeName . name (). empty () ) {
os << "usage: \n "
<< " " << m_exeName . name () << ' ' ;
bool required = true , first = true ;
for ( auto const & arg : m_args ) {
if ( first )
first = false ;
else
os << ' ' ;
if ( arg . isOptional () && required ) {
os << '[' ;
required = false ;
}
os << '<' << arg . hint () << '>' ;
if ( arg . cardinality () == 0 )
os << " ... " ;
}
if ( ! required )
os << ']' ;
if ( ! m_options . empty () )
os << " options" ;
os << " \n\n where options are: \n " ;
}
auto rows = getHelpColumns ();
size_t consoleWidth = CATCH_CONFIG_CONSOLE_WIDTH ;
size_t optWidth = 0 ;
for ( auto const & cols : rows )
optWidth = ( std :: max )( optWidth , cols . left . size () + 2 );
optWidth = ( std :: min )( optWidth , consoleWidth / 2 );
for ( auto const & cols : rows ) {
auto row = TextFlow :: Column ( cols . left )
. width ( optWidth )
. indent ( 2 ) +
TextFlow :: Spacer ( 4 ) +
TextFlow :: Column ( cols . right )
. width ( consoleWidth - 7 - optWidth );
os << row << '\n' ;
}
}
Detail :: Result Parser :: validate () const {
for ( auto const & opt : m_options ) {
auto result = opt . validate ();
if ( ! result )
return result ;
}
for ( auto const & arg : m_args ) {
auto result = arg . validate ();
if ( ! result )
return result ;
}
return Detail :: Result :: ok ();
}
Detail :: InternalParseResult
Parser :: parse ( std :: string const & exeName ,
Detail :: TokenStream const & tokens ) const {
struct ParserInfo {
ParserBase const * parser = nullptr ;
size_t count = 0 ;
};
std :: vector < ParserInfo > parseInfos ;
parseInfos . reserve ( m_options . size () + m_args . size () );
for ( auto const & opt : m_options ) {
parseInfos . push_back ( { & opt , 0 } );
}
for ( auto const & arg : m_args ) {
parseInfos . push_back ( { & arg , 0 } );
}
m_exeName . set ( exeName );
auto result = Detail :: InternalParseResult :: ok (
Detail :: ParseState ( ParseResultType :: NoMatch , tokens ) );
while ( result . value (). remainingTokens () ) {
bool tokenParsed = false ;
for ( auto & parseInfo : parseInfos ) {
if ( parseInfo . parser -> cardinality () == 0 ||
parseInfo . count < parseInfo . parser -> cardinality () ) {
result = parseInfo . parser -> parse (
exeName , result . value (). remainingTokens () );
if ( ! result )
return result ;
if ( result . value (). type () !=
ParseResultType :: NoMatch ) {
tokenParsed = true ;
++ parseInfo . count ;
break ;
}
}
}
if ( result . value (). type () == ParseResultType :: ShortCircuitAll )
return result ;
if ( ! tokenParsed )
return Detail :: InternalParseResult :: runtimeError (
"Unrecognised token: " +
result . value (). remainingTokens () -> token );
}
// !TBD Check missing required options
return result ;
}
Args :: Args ( int argc , char const * const * argv ) :
m_exeName ( argv [ 0 ]), m_args ( argv + 1 , argv + argc ) {}
Args :: Args ( std :: initializer_list < std :: string > args ) :
m_exeName ( * args . begin ()),
m_args ( args . begin () + 1 , args . end ()) {}
Help :: Help ( bool & showHelpFlag ) :
Opt ( [ & ]( bool flag ) {
showHelpFlag = flag ;
return ParserResult :: ok ( ParseResultType :: ShortCircuitAll );
} ) {
static_cast < Opt &> ( * this )(
"display usage information" )[ "-?" ][ "-h" ][ "--help" ]
. optional ();
}
} // namespace Clara
} // namespace Catch
/** \file
* This is a special TU that combines what would otherwise be a very
* small top-level TUs into one bigger TU.
*
* The reason for this is compilation performance improvements by
* avoiding reparsing headers for many small TUs, instead having this
* one TU include bit more, but having it all parsed only once.
*
* To avoid heavy-tail problem with compilation times, each "subpart"
* of Catch2 has its own combined TU like this.
*/
////////////////////////////////////////////////////////
// vvv formerly catch_tag_alias_autoregistrar.cpp vvv //
////////////////////////////////////////////////////////
namespace Catch {
RegistrarForTagAliases :: RegistrarForTagAliases ( char const * alias , char const * tag , SourceLineInfo const & lineInfo ) {
CATCH_TRY {
getMutableRegistryHub (). registerTagAlias ( alias , tag , lineInfo );
} CATCH_CATCH_ALL {
// Do not throw when constructing global objects, instead register the exception to be processed later
getMutableRegistryHub (). registerStartupException ();
}
}
}
//////////////////////////////////////////
// vvv formerly catch_polyfills.cpp vvv //
//////////////////////////////////////////
#include <cmath>
namespace Catch {
#if !defined(CATCH_CONFIG_POLYFILL_ISNAN)
bool isnan ( float f ) {
return std :: isnan ( f );
}
bool isnan ( double d ) {
return std :: isnan ( d );
}
#else
// For now we only use this for embarcadero
bool isnan ( float f ) {
return std :: _isnan ( f );
}
bool isnan ( double d ) {
return std :: _isnan ( d );
}
#endif
} // end namespace Catch
////////////////////////////////////////////////////
// vvv formerly catch_uncaught_exceptions.cpp vvv //
////////////////////////////////////////////////////
2020-10-08 15:26:30 +02:00
2020-09-08 15:53:08 +02:00
#include <exception>
namespace Catch {
bool uncaught_exceptions () {
#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)
return false ;
2020-10-08 15:26:30 +02:00
#elif defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS)
2020-09-08 15:53:08 +02:00
return std :: uncaught_exceptions () > 0 ;
#else
return std :: uncaught_exception ();
#endif
}
} // end namespace Catch
////////////////////////////////////////////
// vvv formerly catch_errno_guard.cpp vvv //
////////////////////////////////////////////
#include <cerrno>
namespace Catch {
ErrnoGuard :: ErrnoGuard () : m_oldErrno ( errno ){}
ErrnoGuard ::~ ErrnoGuard () { errno = m_oldErrno ; }
}
///////////////////////////////////////////
// vvv formerly catch_decomposer.cpp vvv //
///////////////////////////////////////////
namespace Catch {
ITransientExpression ::~ ITransientExpression () = default ;
void formatReconstructedExpression ( std :: ostream & os , std :: string const & lhs , StringRef 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 ;
}
}
///////////////////////////////////////////////////////////
// vvv formerly catch_startup_exception_registry.cpp vvv //
///////////////////////////////////////////////////////////
namespace Catch {
#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)
void StartupExceptionRegistry :: add ( std :: exception_ptr const & exception ) noexcept {
CATCH_TRY {
m_exceptions . push_back ( exception );
} CATCH_CATCH_ALL {
// If we run out of memory during start-up there's really not a lot more we can do about it
std :: terminate ();
}
}
std :: vector < std :: exception_ptr > const & StartupExceptionRegistry :: getExceptions () const noexcept {
return m_exceptions ;
}
#endif
} // end namespace Catch
//////////////////////////////////////////////
// vvv formerly catch_leak_detector.cpp vvv //
//////////////////////////////////////////////
#ifdef CATCH_CONFIG_WINDOWS_CRTDBG
#include <crtdbg.h>
namespace Catch {
LeakDetector :: LeakDetector () {
int flag = _CrtSetDbgFlag ( _CRTDBG_REPORT_FLAG );
flag |= _CRTDBG_LEAK_CHECK_DF ;
flag |= _CRTDBG_ALLOC_MEM_DF ;
_CrtSetDbgFlag ( flag );
_CrtSetReportMode ( _CRT_WARN , _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG );
_CrtSetReportFile ( _CRT_WARN , _CRTDBG_FILE_STDERR );
// Change this to leaking allocation's number to break there
_CrtSetBreakAlloc ( - 1 );
}
}
#else // ^^ Windows crt debug heap enabled // Windows crt debug heap disabled vv
Catch :: LeakDetector :: LeakDetector () {}
#endif // CATCH_CONFIG_WINDOWS_CRTDBG
Catch :: LeakDetector ::~ LeakDetector () {
Catch :: cleanUp ();
}
/////////////////////////////////////////////
// vvv formerly catch_message_info.cpp vvv //
/////////////////////////////////////////////
namespace Catch {
2022-01-03 23:16:39 +01:00
MessageInfo :: MessageInfo ( StringRef _macroName ,
2020-09-08 15:53:08 +02:00
SourceLineInfo const & _lineInfo ,
ResultWas :: OfType _type )
: macroName ( _macroName ),
lineInfo ( _lineInfo ),
type ( _type ),
sequence ( ++ globalCount )
{}
// This may need protecting if threading support is added
unsigned int MessageInfo :: globalCount = 0 ;
} // end namespace Catch
//////////////////////////////////////////
// vvv formerly catch_lazy_expr.cpp vvv //
//////////////////////////////////////////
namespace Catch {
auto operator << ( std :: ostream & os , LazyExpression const & lazyExpr ) -> std :: ostream & {
if ( lazyExpr . m_isNegated )
2022-01-03 23:16:39 +01:00
os << '!' ;
2020-09-08 15:53:08 +02:00
if ( lazyExpr ) {
if ( lazyExpr . m_isNegated && lazyExpr . m_transientExpression -> isBinaryExpression ())
2022-01-03 23:16:39 +01:00
os << '(' << * lazyExpr . m_transientExpression << ')' ;
2020-09-08 15:53:08 +02:00
else
os << * lazyExpr . m_transientExpression ;
} else {
os << "{** error - unchecked empty expression requested **}" ;
}
return os ;
}
} // namespace Catch
2022-01-03 23:16:39 +01:00
#include <algorithm>
2020-09-08 15:53:08 +02:00
#include <fstream>
2022-01-03 23:16:39 +01:00
#include <string>
2020-09-08 15:53:08 +02:00
namespace Catch {
Clara :: Parser makeCommandLineParser ( ConfigData & config ) {
using namespace Clara ;
auto const setWarning = [ & ]( std :: string const & warning ) {
2022-01-03 23:16:39 +01:00
if ( warning == "NoAssertions" ) {
config . warnings = static_cast < WarnAbout :: What > ( config . warnings | WarnAbout :: NoAssertions );
2020-09-08 15:53:08 +02:00
return ParserResult :: ok ( ParseResultType :: Matched );
2022-01-03 23:16:39 +01:00
} else if ( warning == "UnmatchedTestSpec" ) {
config . warnings = static_cast < WarnAbout :: What > ( config . warnings | WarnAbout :: UnmatchedTestSpec );
return ParserResult :: ok ( ParseResultType :: Matched );
}
return ParserResult :: runtimeError (
"Unrecognised warning option: '" + warning + '\'' );
};
2020-09-08 15:53:08 +02:00
auto const loadTestNamesFromFile = [ & ]( std :: string const & filename ) {
std :: ifstream f ( filename . c_str () );
if ( ! f . is_open () )
2022-01-03 23:16:39 +01:00
return ParserResult :: runtimeError ( "Unable to load input file: '" + filename + '\'' );
2020-09-08 15:53:08 +02:00
std :: string line ;
while ( std :: getline ( f , line ) ) {
line = trim ( line );
if ( ! line . empty () && ! startsWith ( line , '#' ) ) {
if ( ! startsWith ( line , '"' ) )
line = '"' + line + '"' ;
config . testsOrTags . push_back ( line );
config . testsOrTags . emplace_back ( "," );
}
}
//Remove comma in the end
if ( ! config . testsOrTags . empty ())
config . testsOrTags . erase ( config . testsOrTags . end () - 1 );
return ParserResult :: ok ( ParseResultType :: Matched );
};
auto const setTestOrder = [ & ]( std :: string const & order ) {
if ( startsWith ( "declared" , order ) )
config . runOrder = TestRunOrder :: Declared ;
else if ( startsWith ( "lexical" , order ) )
config . runOrder = TestRunOrder :: LexicographicallySorted ;
else if ( startsWith ( "random" , order ) )
config . runOrder = TestRunOrder :: Randomized ;
else
2022-01-03 23:16:39 +01:00
return ParserResult :: runtimeError ( "Unrecognised ordering: '" + order + '\'' );
2020-09-08 15:53:08 +02:00
return ParserResult :: ok ( ParseResultType :: Matched );
};
auto const setRngSeed = [ & ]( std :: string const & seed ) {
2022-01-03 23:16:39 +01:00
if ( seed == "time" ) {
config . rngSeed = generateRandomSeed ( GenerateFrom :: Time );
return ParserResult :: ok ( ParseResultType :: Matched );
} else if ( seed == "random-device" ) {
config . rngSeed = generateRandomSeed ( GenerateFrom :: RandomDevice );
return ParserResult :: ok ( ParseResultType :: Matched );
}
CATCH_TRY {
std :: size_t parsedTo = 0 ;
unsigned long parsedSeed = std :: stoul ( seed , & parsedTo , 0 );
if ( parsedTo != seed . size ()) {
return ParserResult :: runtimeError ( "Could not parse '" + seed + "' as seed" );
}
// TODO: Ideally we could parse unsigned int directly,
// but the stdlib doesn't provide helper for that
// type. After this is refactored to use fixed size
// type, we should check the parsed value is in range
// of the underlying type.
config . rngSeed = static_cast < unsigned int > ( parsedSeed );
return ParserResult :: ok ( ParseResultType :: Matched );
} CATCH_CATCH_ANON ( std :: exception const & ) {
return ParserResult :: runtimeError ( "Could not parse '" + seed + "' as seed" );
}
2020-09-08 15:53:08 +02:00
};
auto const setColourUsage = [ & ]( std :: string const & useColour ) {
auto mode = toLower ( useColour );
if ( mode == "yes" )
config . useColour = UseColour :: Yes ;
else if ( mode == "no" )
config . useColour = UseColour :: No ;
else if ( mode == "auto" )
config . useColour = UseColour :: Auto ;
else
return ParserResult :: runtimeError ( "colour mode must be one of: auto, yes or no. '" + useColour + "' not recognised" );
return ParserResult :: ok ( ParseResultType :: Matched );
};
auto const setWaitForKeypress = [ & ]( std :: string const & keypress ) {
auto keypressLc = toLower ( keypress );
if ( keypressLc == "never" )
config . waitForKeypress = WaitForKeypress :: Never ;
else if ( keypressLc == "start" )
config . waitForKeypress = WaitForKeypress :: BeforeStart ;
else if ( keypressLc == "exit" )
config . waitForKeypress = WaitForKeypress :: BeforeExit ;
else if ( keypressLc == "both" )
config . waitForKeypress = WaitForKeypress :: BeforeStartAndExit ;
else
return ParserResult :: runtimeError ( "keypress argument must be one of: never, start, exit or both. '" + keypress + "' not recognised" );
return ParserResult :: ok ( ParseResultType :: Matched );
};
auto const setVerbosity = [ & ]( std :: string const & verbosity ) {
auto lcVerbosity = toLower ( verbosity );
if ( lcVerbosity == "quiet" )
config . verbosity = Verbosity :: Quiet ;
else if ( lcVerbosity == "normal" )
config . verbosity = Verbosity :: Normal ;
else if ( lcVerbosity == "high" )
config . verbosity = Verbosity :: High ;
else
2022-01-03 23:16:39 +01:00
return ParserResult :: runtimeError ( "Unrecognised verbosity, '" + verbosity + '\'' );
2020-09-08 15:53:08 +02:00
return ParserResult :: ok ( ParseResultType :: Matched );
};
2022-01-03 23:16:39 +01:00
auto const setReporter = [ & ]( std :: string const & reporterSpec ) {
if ( reporterSpec . empty () ) {
return ParserResult :: runtimeError ( "Received empty reporter spec." );
}
2020-09-08 15:53:08 +02:00
IReporterRegistry :: FactoryMap const & factories = getRegistryHub (). getReporterRegistry (). getFactories ();
2022-01-03 23:16:39 +01:00
// clear the default reporter
if ( ! config . _nonDefaultReporterSpecifications ) {
config . reporterSpecifications . clear ();
config . _nonDefaultReporterSpecifications = true ;
}
// Exactly one of the reporters may be specified without an output
// file, in which case it defaults to the output specified by "-o"
// (or standard output).
static constexpr auto separator = "::" ;
static constexpr size_t separatorSize = 2 ;
auto fileNameSeparatorPos = reporterSpec . find ( separator );
const bool containsFileName = fileNameSeparatorPos != reporterSpec . npos ;
if ( containsFileName ) {
auto nextSeparatorPos = reporterSpec . find (
separator , fileNameSeparatorPos + separatorSize );
if ( nextSeparatorPos != reporterSpec . npos ) {
return ParserResult :: runtimeError (
"Too many separators in reporter spec '" + reporterSpec + '\'' );
}
}
std :: string reporterName ;
Optional < std :: string > outputFileName ;
reporterName = reporterSpec . substr ( 0 , fileNameSeparatorPos );
if ( reporterName . empty () ) {
return ParserResult :: runtimeError ( "Reporter name cannot be empty." );
}
if ( containsFileName ) {
outputFileName = reporterSpec . substr (
fileNameSeparatorPos + separatorSize , reporterSpec . size () );
}
auto result = factories . find ( reporterName );
if ( result == factories . end () )
return ParserResult :: runtimeError ( "Unrecognized reporter, '" + reporterName + "'. Check available with --list-reporters" );
if ( containsFileName && outputFileName -> empty () )
return ParserResult :: runtimeError ( "Reporter '" + reporterName + "' has empty filename specified as its output. Supply a filename or remove the colons to use the default output." );
config . reporterSpecifications . push_back ({ std :: move ( reporterName ), std :: move ( outputFileName ) });
// It would be enough to check this only once at the very end, but there is
// not a place where we could call this check, so do it every time it could fail.
// For valid inputs, this is still called at most once.
if ( ! containsFileName ) {
int n_reporters_without_file = 0 ;
for ( auto const & spec : config . reporterSpecifications ) {
if ( spec . outputFileName . none ()) {
n_reporters_without_file ++ ;
}
}
if ( n_reporters_without_file > 1 ) {
return ParserResult :: runtimeError ( "Only one reporter may have unspecified output file." );
}
}
2020-09-08 15:53:08 +02:00
return ParserResult :: ok ( ParseResultType :: Matched );
};
2022-01-03 23:16:39 +01:00
auto const setShardCount = [ & ]( std :: string const & shardCount ) {
CATCH_TRY {
std :: size_t parsedTo = 0 ;
int64_t parsedCount = std :: stoll ( shardCount , & parsedTo , 0 );
if ( parsedTo != shardCount . size ()) {
return ParserResult :: runtimeError ( "Could not parse '" + shardCount + "' as shard count" );
}
if ( parsedCount <= 0 ) {
return ParserResult :: runtimeError ( "Shard count must be a positive number" );
}
config . shardCount = static_cast < unsigned int > ( parsedCount );
return ParserResult :: ok ( ParseResultType :: Matched );
} CATCH_CATCH_ANON ( std :: exception const & ) {
return ParserResult :: runtimeError ( "Could not parse '" + shardCount + "' as shard count" );
}
};
auto const setShardIndex = [ & ]( std :: string const & shardIndex ) {
CATCH_TRY {
std :: size_t parsedTo = 0 ;
int64_t parsedIndex = std :: stoll ( shardIndex , & parsedTo , 0 );
if ( parsedTo != shardIndex . size ()) {
return ParserResult :: runtimeError ( "Could not parse '" + shardIndex + "' as shard index" );
}
if ( parsedIndex < 0 ) {
return ParserResult :: runtimeError ( "Shard index must be a non-negative number" );
}
config . shardIndex = static_cast < unsigned int > ( parsedIndex );
return ParserResult :: ok ( ParseResultType :: Matched );
} CATCH_CATCH_ANON ( std :: exception const & ) {
return ParserResult :: runtimeError ( "Could not parse '" + shardIndex + "' as shard index" );
}
};
2020-09-08 15:53:08 +02:00
auto cli
= ExeName ( config . processName )
| Help ( config . showHelp )
| Opt ( config . showSuccessfulTests )
[ "-s" ][ "--success" ]
( "include successful tests in output" )
| Opt ( config . shouldDebugBreak )
[ "-b" ][ "--break" ]
( "break into debugger on failure" )
| Opt ( config . noThrow )
[ "-e" ][ "--nothrow" ]
( "skip exception tests" )
| Opt ( config . showInvisibles )
[ "-i" ][ "--invisibles" ]
( "show invisibles (tabs, newlines)" )
2022-01-03 23:16:39 +01:00
| Opt ( config . defaultOutputFilename , "filename" )
2020-09-08 15:53:08 +02:00
[ "-o" ][ "--out" ]
2022-01-03 23:16:39 +01:00
( "default output filename" )
| Opt ( accept_many , setReporter , "name[:output-file]" )
2020-09-08 15:53:08 +02:00
[ "-r" ][ "--reporter" ]
( "reporter to use (defaults to console)" )
| Opt ( config . name , "name" )
[ "-n" ][ "--name" ]
( "suite name" )
| Opt ( [ & ]( bool ){ config . abortAfter = 1 ; } )
[ "-a" ][ "--abort" ]
( "abort at first failure" )
| Opt ( [ & ]( int x ){ config . abortAfter = x ; }, "no. failures" )
[ "-x" ][ "--abortx" ]
( "abort after x failures" )
2022-01-03 23:16:39 +01:00
| Opt ( accept_many , setWarning , "warning name" )
2020-09-08 15:53:08 +02:00
[ "-w" ][ "--warn" ]
( "enable warnings" )
| Opt ( [ & ]( bool flag ) { config . showDurations = flag ? ShowDurations :: Always : ShowDurations :: Never ; }, "yes|no" )
[ "-d" ][ "--durations" ]
( "show test durations" )
| Opt ( config . minDuration , "seconds" )
[ "-D" ][ "--min-duration" ]
( "show test durations for tests taking at least the given number of seconds" )
| Opt ( loadTestNamesFromFile , "filename" )
[ "-f" ][ "--input-file" ]
( "load test names to run from a file" )
| Opt ( config . filenamesAsTags )
[ "-#" ][ "--filenames-as-tags" ]
( "adds a tag for the filename" )
| Opt ( config . sectionsToRun , "section name" )
[ "-c" ][ "--section" ]
( "specify section to run" )
| Opt ( setVerbosity , "quiet|normal|high" )
[ "-v" ][ "--verbosity" ]
( "set output verbosity" )
2022-01-03 23:16:39 +01:00
| Opt ( config . listTests )
[ "--list-tests" ]
( "list all/matching test cases" )
| Opt ( config . listTags )
[ "--list-tags" ]
( "list all/matching tags" )
2020-09-08 15:53:08 +02:00
| Opt ( config . listReporters )
[ "--list-reporters" ]
( "list all reporters" )
| Opt ( setTestOrder , "decl|lex|rand" )
[ "--order" ]
( "test case order (defaults to decl)" )
2022-01-03 23:16:39 +01:00
| Opt ( setRngSeed , "'time'|'random-device'|number" )
2020-09-08 15:53:08 +02:00
[ "--rng-seed" ]
( "set a specific seed for random numbers" )
| Opt ( setColourUsage , "yes|no" )
[ "--use-colour" ]
( "should output be colourised" )
| Opt ( config . libIdentify )
[ "--libidentify" ]
( "report name and version according to libidentify standard" )
| Opt ( setWaitForKeypress , "never|start|exit|both" )
[ "--wait-for-keypress" ]
( "waits for a keypress before exiting" )
| Opt ( config . benchmarkSamples , "samples" )
[ "--benchmark-samples" ]
( "number of samples to collect (default: 100)" )
| Opt ( config . benchmarkResamples , "resamples" )
[ "--benchmark-resamples" ]
( "number of resamples for the bootstrap (default: 100000)" )
| Opt ( config . benchmarkConfidenceInterval , "confidence interval" )
[ "--benchmark-confidence-interval" ]
( "confidence interval for the bootstrap (between 0 and 1, default: 0.95)" )
| Opt ( config . benchmarkNoAnalysis )
[ "--benchmark-no-analysis" ]
( "perform only measurements; do not perform any analysis" )
| Opt ( config . benchmarkWarmupTime , "benchmarkWarmupTime" )
[ "--benchmark-warmup-time" ]
( "amount of time in milliseconds spent on warming up each test (default: 100)" )
2022-01-03 23:16:39 +01:00
| Opt ( setShardCount , "shard count" )
[ "--shard-count" ]
( "split the tests to execute into this many groups" )
| Opt ( setShardIndex , "shard index" )
[ "--shard-index" ]
( "index of the group of tests to execute (see --shard-count)" ) |
Opt ( config . allowZeroTests )
[ "--allow-running-no-tests" ]
( "Treat 'No tests run' as a success" )
2020-09-08 15:53:08 +02:00
| Arg ( config . testsOrTags , "test name|pattern|tags" )
( "which test or tests to use" );
return cli ;
}
} // end namespace Catch
#if defined(__clang__)
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wexit-time-destructors"
#endif
#include <ostream>
namespace Catch {
namespace {
struct IColourImpl {
virtual ~ IColourImpl () = default ;
virtual void use ( Colour :: Code _colourCode ) = 0 ;
};
struct NoColourImpl : IColourImpl {
void use ( Colour :: Code ) override {}
static IColourImpl * instance () {
static NoColourImpl s_instance ;
return & s_instance ;
}
};
} // anon namespace
} // namespace Catch
#if !defined( CATCH_CONFIG_COLOUR_NONE ) && !defined( CATCH_CONFIG_COLOUR_WINDOWS ) && !defined( CATCH_CONFIG_COLOUR_ANSI )
# ifdef CATCH_PLATFORM_WINDOWS
# define CATCH_CONFIG_COLOUR_WINDOWS
# else
# define CATCH_CONFIG_COLOUR_ANSI
# endif
#endif
#if defined ( CATCH_CONFIG_COLOUR_WINDOWS ) /////////////////////////////////////////
namespace Catch {
namespace {
class Win32ColourImpl : public IColourImpl {
public :
Win32ColourImpl () : stdoutHandle ( GetStdHandle ( STD_OUTPUT_HANDLE ) )
{
CONSOLE_SCREEN_BUFFER_INFO csbiInfo ;
GetConsoleScreenBufferInfo ( stdoutHandle , & csbiInfo );
originalForegroundAttributes = csbiInfo . wAttributes & ~ ( BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY );
originalBackgroundAttributes = csbiInfo . wAttributes & ~ ( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY );
}
void use ( Colour :: Code _colourCode ) override {
switch ( _colourCode ) {
case Colour :: None : return setTextAttribute ( originalForegroundAttributes );
case Colour :: White : return setTextAttribute ( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE );
case Colour :: Red : return setTextAttribute ( FOREGROUND_RED );
case Colour :: Green : return setTextAttribute ( FOREGROUND_GREEN );
case Colour :: Blue : return setTextAttribute ( FOREGROUND_BLUE );
case Colour :: Cyan : return setTextAttribute ( FOREGROUND_BLUE | FOREGROUND_GREEN );
case Colour :: Yellow : return setTextAttribute ( FOREGROUND_RED | FOREGROUND_GREEN );
case Colour :: Grey : return setTextAttribute ( 0 );
case Colour :: LightGrey : return setTextAttribute ( FOREGROUND_INTENSITY );
case Colour :: BrightRed : return setTextAttribute ( FOREGROUND_INTENSITY | FOREGROUND_RED );
case Colour :: BrightGreen : return setTextAttribute ( FOREGROUND_INTENSITY | FOREGROUND_GREEN );
case Colour :: BrightWhite : return setTextAttribute ( FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE );
case Colour :: BrightYellow : return setTextAttribute ( FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN );
case Colour :: Bright : CATCH_INTERNAL_ERROR ( "not a colour" );
default :
CATCH_ERROR ( "Unknown colour requested" );
}
}
private :
void setTextAttribute ( WORD _textAttribute ) {
SetConsoleTextAttribute ( stdoutHandle , _textAttribute | originalBackgroundAttributes );
}
HANDLE stdoutHandle ;
WORD originalForegroundAttributes ;
WORD originalBackgroundAttributes ;
};
IColourImpl * platformColourInstance () {
static Win32ColourImpl s_instance ;
auto const * config = getCurrentContext (). getConfig ();
UseColour colourMode = config ?
config -> useColour () : UseColour :: Auto ;
if ( colourMode == UseColour :: Auto )
colourMode = UseColour :: Yes ;
return colourMode == UseColour :: Yes
? & s_instance
: NoColourImpl :: instance ();
}
} // end anon namespace
} // end namespace Catch
#elif defined( CATCH_CONFIG_COLOUR_ANSI ) //////////////////////////////////////
#include <unistd.h>
namespace Catch {
namespace {
// use POSIX/ ANSI console terminal codes
// Thanks to Adam Strzelecki for original contribution
// (http://github.com/nanoant)
// https://github.com/philsquared/Catch/pull/131
class PosixColourImpl : public IColourImpl {
public :
void use ( Colour :: Code _colourCode ) override {
switch ( _colourCode ) {
case Colour :: None :
case Colour :: White : return setColour ( "[0m" );
case Colour :: Red : return setColour ( "[0;31m" );
case Colour :: Green : return setColour ( "[0;32m" );
case Colour :: Blue : return setColour ( "[0;34m" );
case Colour :: Cyan : return setColour ( "[0;36m" );
case Colour :: Yellow : return setColour ( "[0;33m" );
case Colour :: Grey : return setColour ( "[1;30m" );
case Colour :: LightGrey : return setColour ( "[0;37m" );
case Colour :: BrightRed : return setColour ( "[1;31m" );
case Colour :: BrightGreen : return setColour ( "[1;32m" );
case Colour :: BrightWhite : return setColour ( "[1;37m" );
case Colour :: BrightYellow : return setColour ( "[1;33m" );
case Colour :: Bright : CATCH_INTERNAL_ERROR ( "not a colour" );
default : CATCH_INTERNAL_ERROR ( "Unknown colour requested" );
}
}
static IColourImpl * instance () {
static PosixColourImpl s_instance ;
return & s_instance ;
}
private :
void setColour ( const char * _escapeCode ) {
// The escape sequence must be flushed to console, otherwise if
// stdin and stderr are intermixed, we'd get accidentally coloured output.
2022-01-03 23:16:39 +01:00
getCurrentContext (). getConfig () -> defaultStream ()
2020-09-08 15:53:08 +02:00
<< '\033' << _escapeCode << std :: flush ;
}
};
bool useColourOnPlatform () {
return
#if defined(CATCH_PLATFORM_MAC) || defined(CATCH_PLATFORM_IPHONE)
! isDebuggerActive () &&
#endif
#if !(defined(__DJGPP__) && defined(__STRICT_ANSI__))
isatty ( STDOUT_FILENO )
#else
false
#endif
;
}
IColourImpl * platformColourInstance () {
ErrnoGuard guard ;
auto const * config = getCurrentContext (). getConfig ();
UseColour colourMode = config
? config -> useColour ()
: UseColour :: Auto ;
if ( colourMode == UseColour :: Auto )
colourMode = useColourOnPlatform ()
? UseColour :: Yes
: UseColour :: No ;
return colourMode == UseColour :: Yes
? PosixColourImpl :: instance ()
: NoColourImpl :: instance ();
}
} // end anon namespace
} // end namespace Catch
#else // not Windows or ANSI ///////////////////////////////////////////////
namespace Catch {
static IColourImpl * platformColourInstance () { return NoColourImpl :: instance (); }
} // end namespace Catch
#endif // Windows/ ANSI/ None
namespace Catch {
Colour :: Colour ( Code _colourCode ) { use ( _colourCode ); }
Colour :: Colour ( Colour && other ) noexcept {
m_moved = other . m_moved ;
other . m_moved = true ;
}
Colour & Colour :: operator = ( Colour && other ) noexcept {
m_moved = other . m_moved ;
other . m_moved = true ;
return * this ;
}
Colour ::~ Colour (){ if ( ! m_moved ) use ( None ); }
void Colour :: use ( Code _colourCode ) {
static IColourImpl * impl = platformColourInstance ();
// Strictly speaking, this cannot possibly happen.
// However, under some conditions it does happen (see #1626),
// and this change is small enough that we can let practicality
// triumph over purity in this case.
if ( impl != nullptr ) {
impl -> use ( _colourCode );
}
}
std :: ostream & operator << ( std :: ostream & os , Colour const & ) {
return os ;
}
} // end namespace Catch
#if defined(__clang__)
# pragma clang diagnostic pop
#endif
namespace Catch {
class Context : public IMutableContext , private Detail :: NonCopyable {
public : // IContext
IResultCapture * getResultCapture () override {
return m_resultCapture ;
}
IConfig const * getConfig () const override {
return m_config ;
}
~ Context () override ;
public : // IMutableContext
void setResultCapture ( IResultCapture * resultCapture ) override {
m_resultCapture = resultCapture ;
}
void setConfig ( IConfig const * config ) override {
m_config = config ;
}
friend IMutableContext & getCurrentMutableContext ();
private :
IConfig const * m_config = nullptr ;
IResultCapture * m_resultCapture = nullptr ;
};
IMutableContext * IMutableContext :: currentContext = nullptr ;
void IMutableContext :: createContext ()
{
currentContext = new Context ();
}
void cleanUpContext () {
delete IMutableContext :: currentContext ;
IMutableContext :: currentContext = nullptr ;
}
IContext ::~ IContext () = default ;
IMutableContext ::~ IMutableContext () = default ;
Context ::~ Context () = default ;
SimplePcg32 & rng () {
static SimplePcg32 s_rng ;
return s_rng ;
}
}
#if defined(CATCH_CONFIG_ANDROID_LOGWRITE)
#include <android/log.h>
namespace Catch {
void writeToDebugConsole ( std :: string const & text ) {
__android_log_write ( ANDROID_LOG_DEBUG , "Catch" , text . c_str () );
}
}
#elif defined(CATCH_PLATFORM_WINDOWS)
namespace Catch {
void writeToDebugConsole ( std :: string const & text ) {
:: OutputDebugStringA ( text . c_str () );
}
}
#else
namespace Catch {
void writeToDebugConsole ( std :: string const & text ) {
// !TBD: Need a version for Mac/ XCode and other IDEs
Catch :: cout () << text ;
}
}
#endif // Platform
#if defined(CATCH_PLATFORM_MAC) || defined(CATCH_PLATFORM_IPHONE)
# include <cassert>
# include <sys/types.h>
# include <unistd.h>
# include <cstddef>
# include <ostream>
#ifdef __apple_build_version__
// These headers will only compile with AppleClang (XCode)
// For other compilers (Clang, GCC, ... ) we need to exclude them
# include <sys/sysctl.h>
#endif
namespace Catch {
#ifdef __apple_build_version__
// The following function is taken directly from the following technical note:
// https://developer.apple.com/library/archive/qa/qa1361/_index.html
// Returns true if the current process is being debugged (either
// running under the debugger or has a debugger attached post facto).
bool isDebuggerActive (){
int mib [ 4 ];
struct kinfo_proc info ;
std :: size_t size ;
// Initialize the flags so that, if sysctl fails for some bizarre
// reason, we get a predictable result.
info . kp_proc . p_flag = 0 ;
// Initialize mib, which tells sysctl the info we want, in this case
// we're looking for information about a specific process ID.
mib [ 0 ] = CTL_KERN ;
mib [ 1 ] = KERN_PROC ;
mib [ 2 ] = KERN_PROC_PID ;
mib [ 3 ] = getpid ();
// Call sysctl.
size = sizeof ( info );
if ( sysctl ( mib , sizeof ( mib ) / sizeof ( * mib ), & info , & size , nullptr , 0 ) != 0 ) {
2022-01-03 23:16:39 +01:00
Catch :: cerr () << " \n ** Call to sysctl failed - unable to determine if debugger is active ** \n\n " << std :: flush ;
2020-09-08 15:53:08 +02:00
return false ;
}
// We're being debugged if the P_TRACED flag is set.
return ( ( info . kp_proc . p_flag & P_TRACED ) != 0 );
}
#else
bool isDebuggerActive () {
// We need to find another way to determine this for non-appleclang compilers on macOS
return false ;
}
#endif
} // namespace Catch
#elif defined(CATCH_PLATFORM_LINUX)
#include <fstream>
#include <string>
namespace Catch {
// The standard POSIX way of detecting a debugger is to attempt to
// ptrace() the process, but this needs to be done from a child and not
// this process itself to still allow attaching to this process later
// if wanted, so is rather heavy. Under Linux we have the PID of the
// "debugger" (which doesn't need to be gdb, of course, it could also
// be strace, for example) in /proc/$PID/status, so just get it from
// there instead.
bool isDebuggerActive (){
// Libstdc++ has a bug, where std::ifstream sets errno to 0
// This way our users can properly assert over errno values
ErrnoGuard guard ;
std :: ifstream in ( "/proc/self/status" );
for ( std :: string line ; std :: getline ( in , line ); ) {
static const int PREFIX_LEN = 11 ;
if ( line . compare ( 0 , PREFIX_LEN , "TracerPid: \t " ) == 0 ) {
// We're traced if the PID is not 0 and no other PID starts
// with 0 digit, so it's enough to check for just a single
// character.
return line . length () > PREFIX_LEN && line [ PREFIX_LEN ] != '0' ;
}
}
return false ;
}
} // namespace Catch
#elif defined(_MSC_VER)
extern "C" __declspec ( dllimport ) int __stdcall IsDebuggerPresent ();
namespace Catch {
bool isDebuggerActive () {
return IsDebuggerPresent () != 0 ;
}
}
#elif defined(__MINGW32__)
extern "C" __declspec ( dllimport ) int __stdcall IsDebuggerPresent ();
namespace Catch {
bool isDebuggerActive () {
return IsDebuggerPresent () != 0 ;
}
}
#else
namespace Catch {
bool isDebuggerActive () { return false ; }
}
#endif // Platform
#include <stdexcept>
namespace Catch {
#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS_CUSTOM_HANDLER)
[[noreturn]]
void throw_exception ( std :: exception const & e ) {
Catch :: cerr () << "Catch will terminate because it needed to throw an exception. \n "
<< "The message was: " << e . what () << '\n' ;
std :: terminate ();
}
#endif
[[noreturn]]
void throw_logic_error ( std :: string const & msg ) {
throw_exception ( std :: logic_error ( msg ));
}
[[noreturn]]
void throw_domain_error ( std :: string const & msg ) {
throw_exception ( std :: domain_error ( msg ));
}
[[noreturn]]
void throw_runtime_error ( std :: string const & msg ) {
throw_exception ( std :: runtime_error ( msg ));
}
} // namespace Catch;
#include <cassert>
namespace Catch {
2022-01-03 23:16:39 +01:00
IMutableEnumValuesRegistry ::~ IMutableEnumValuesRegistry () = default ;
2020-09-08 15:53:08 +02:00
namespace Detail {
namespace {
// Extracts the actual name part of an enum instance
// In other words, it returns the Blue part of Bikeshed::Colour::Blue
StringRef extractInstanceName ( StringRef enumInstance ) {
2022-01-03 23:16:39 +01:00
// Find last occurrence of ":"
2020-09-08 15:53:08 +02:00
size_t name_start = enumInstance . size ();
while ( name_start > 0 && enumInstance [ name_start - 1 ] != ':' ) {
-- name_start ;
}
return enumInstance . substr ( name_start , enumInstance . size () - name_start );
}
}
std :: vector < StringRef > parseEnums ( StringRef enums ) {
auto enumValues = splitStringRef ( enums , ',' );
std :: vector < StringRef > parsed ;
parsed . reserve ( enumValues . size () );
for ( auto const & enumValue : enumValues ) {
parsed . push_back ( trim ( extractInstanceName ( enumValue )));
}
return parsed ;
}
EnumInfo ::~ EnumInfo () {}
StringRef EnumInfo :: lookup ( int value ) const {
for ( auto const & valueToName : m_values ) {
if ( valueToName . first == value )
return valueToName . second ;
}
return "{** unexpected enum value **}" _sr ;
}
Catch :: Detail :: unique_ptr < EnumInfo > makeEnumInfo ( StringRef enumName , StringRef allValueNames , std :: vector < int > const & values ) {
auto enumInfo = Catch :: Detail :: make_unique < EnumInfo > ();
enumInfo -> m_name = enumName ;
enumInfo -> m_values . reserve ( values . size () );
const auto valueNames = Catch :: Detail :: parseEnums ( allValueNames );
assert ( valueNames . size () == values . size () );
std :: size_t i = 0 ;
for ( auto value : values )
enumInfo -> m_values . emplace_back ( value , valueNames [ i ++ ]);
return enumInfo ;
}
EnumInfo const & EnumValuesRegistry :: registerEnum ( StringRef enumName , StringRef allValueNames , std :: vector < int > const & values ) {
m_enumInfos . push_back ( makeEnumInfo ( enumName , allValueNames , values ));
return * m_enumInfos . back ();
}
} // Detail
} // Catch
namespace Catch {
ExceptionTranslatorRegistry ::~ ExceptionTranslatorRegistry () {
}
2022-01-03 23:16:39 +01:00
void ExceptionTranslatorRegistry :: registerTranslator ( Detail :: unique_ptr < IExceptionTranslator >&& translator ) {
m_translators . push_back ( CATCH_MOVE ( translator ) );
2020-09-08 15:53:08 +02:00
}
#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)
std :: string ExceptionTranslatorRegistry :: translateActiveException () const {
2022-01-03 23:16:39 +01:00
// Compiling a mixed mode project with MSVC means that CLR
// exceptions will be caught in (...) as well. However, these do
// do not fill-in std::current_exception and thus lead to crash
// when attempting rethrow.
// /EHa switch also causes structured exceptions to be caught
// here, but they fill-in current_exception properly, so
// at worst the output should be a little weird, instead of
// causing a crash.
if ( std :: current_exception () == nullptr ) {
return "Non C++ exception. Possibly a CLR exception." ;
}
// First we try user-registered translators. If none of them can
// handle the exception, it will be rethrown handled by our defaults.
2020-09-08 15:53:08 +02:00
try {
return tryTranslators ();
}
2022-01-03 23:16:39 +01:00
// To avoid having to handle TFE explicitly everywhere, we just
// rethrow it so that it goes back up the caller.
2020-09-08 15:53:08 +02:00
catch ( TestFailureException & ) {
std :: rethrow_exception ( std :: current_exception ());
}
2022-01-03 23:16:39 +01:00
catch ( std :: exception const & ex ) {
2020-09-08 15:53:08 +02:00
return ex . what ();
}
2022-01-03 23:16:39 +01:00
catch ( std :: string const & msg ) {
2020-09-08 15:53:08 +02:00
return msg ;
}
catch ( const char * msg ) {
return msg ;
}
catch (...) {
return "Unknown exception" ;
}
}
std :: string ExceptionTranslatorRegistry :: tryTranslators () const {
if ( m_translators . empty ()) {
std :: rethrow_exception ( std :: current_exception ());
} else {
return m_translators [ 0 ] -> translate ( m_translators . begin () + 1 , m_translators . end ());
}
}
#else // ^^ Exceptions are enabled // Exceptions are disabled vv
std :: string ExceptionTranslatorRegistry :: translateActiveException () const {
CATCH_INTERNAL_ERROR ( "Attempted to translate active exception under CATCH_CONFIG_DISABLE_EXCEPTIONS!" );
}
std :: string ExceptionTranslatorRegistry :: tryTranslators () const {
CATCH_INTERNAL_ERROR ( "Attempted to use exception translators under CATCH_CONFIG_DISABLE_EXCEPTIONS!" );
}
#endif
}
2022-01-03 23:16:39 +01:00
/** \file
* This file provides platform specific implementations of FatalConditionHandler
*
* This means that there is a lot of conditional compilation, and platform
* specific code. Currently, Catch2 supports a dummy handler (if no
* handler is desired), and 2 platform specific handlers:
* * Windows' SEH
* * POSIX signals
*
* Consequently, various pieces of code below are compiled if either of
* the platform specific handlers is enabled, or if none of them are
* enabled. It is assumed that both cannot be enabled at the same time,
* and doing so should cause a compilation error.
*
* If another platform specific handler is added, the compile guards
* below will need to be updated taking these assumptions into account.
*/
2020-09-08 15:53:08 +02:00
2022-01-03 23:16:39 +01:00
#include <algorithm>
#if !defined( CATCH_CONFIG_WINDOWS_SEH ) && !defined( CATCH_CONFIG_POSIX_SIGNALS )
namespace Catch {
// If neither SEH nor signal handling is required, the handler impls
// do not have to do anything, and can be empty.
void FatalConditionHandler :: engage_platform () {}
void FatalConditionHandler :: disengage_platform () {}
FatalConditionHandler :: FatalConditionHandler () = default ;
FatalConditionHandler ::~ FatalConditionHandler () = default ;
} // end namespace Catch
#endif // !CATCH_CONFIG_WINDOWS_SEH && !CATCH_CONFIG_POSIX_SIGNALS
#if defined( CATCH_CONFIG_WINDOWS_SEH ) && defined( CATCH_CONFIG_POSIX_SIGNALS )
#error "Inconsistent configuration: Windows' SEH handling and POSIX signals cannot be enabled at the same time"
#endif // CATCH_CONFIG_WINDOWS_SEH && CATCH_CONFIG_POSIX_SIGNALS
2020-09-08 15:53:08 +02:00
#if defined( CATCH_CONFIG_WINDOWS_SEH ) || defined( CATCH_CONFIG_POSIX_SIGNALS )
namespace {
2022-01-03 23:16:39 +01:00
//! Signals fatal error message to the run context
2020-09-08 15:53:08 +02:00
void reportFatal ( char const * const message ) {
Catch :: getCurrentContext (). getResultCapture () -> handleFatalErrorCondition ( message );
}
2022-01-03 23:16:39 +01:00
//! Minimal size Catch2 needs for its own fatal error handling.
//! Picked empirically, so it might not be sufficient on all
//! platforms, and for all configurations.
constexpr std :: size_t minStackSizeForErrors = 32 * 1024 ;
} // end unnamed namespace
#endif // CATCH_CONFIG_WINDOWS_SEH || CATCH_CONFIG_POSIX_SIGNALS
2020-09-08 15:53:08 +02:00
#if defined( CATCH_CONFIG_WINDOWS_SEH )
namespace Catch {
2022-01-03 23:16:39 +01:00
2020-09-08 15:53:08 +02:00
struct SignalDefs { DWORD id ; const char * name ; };
// There is no 1-1 mapping between signals and windows exceptions.
// Windows can easily distinguish between SO and SigSegV,
// but SigInt, SigTerm, etc are handled differently.
static SignalDefs signalDefs [] = {
{ static_cast < DWORD > ( EXCEPTION_ILLEGAL_INSTRUCTION ), "SIGILL - Illegal instruction signal" },
{ static_cast < DWORD > ( EXCEPTION_STACK_OVERFLOW ), "SIGSEGV - Stack overflow" },
{ static_cast < DWORD > ( EXCEPTION_ACCESS_VIOLATION ), "SIGSEGV - Segmentation violation signal" },
{ static_cast < DWORD > ( EXCEPTION_INT_DIVIDE_BY_ZERO ), "Divide by zero error" },
};
2022-01-03 23:16:39 +01:00
static LONG CALLBACK topLevelExceptionFilter ( PEXCEPTION_POINTERS ExceptionInfo ) {
2020-09-08 15:53:08 +02:00
for ( auto const & def : signalDefs ) {
if ( ExceptionInfo -> ExceptionRecord -> ExceptionCode == def . id ) {
reportFatal ( def . name );
}
}
// If its not an exception we care about, pass it along.
// This stops us from eating debugger breaks etc.
return EXCEPTION_CONTINUE_SEARCH ;
}
2022-01-03 23:16:39 +01:00
// Since we do not support multiple instantiations, we put these
// into global variables and rely on cleaning them up in outlined
// constructors/destructors
static LPTOP_LEVEL_EXCEPTION_FILTER previousTopLevelExceptionFilter = nullptr ;
2020-09-08 15:53:08 +02:00
2022-01-03 23:16:39 +01:00
// For MSVC, we reserve part of the stack memory for handling
// memory overflow structured exception.
FatalConditionHandler :: FatalConditionHandler () {
ULONG guaranteeSize = static_cast < ULONG > ( minStackSizeForErrors );
if ( ! SetThreadStackGuarantee ( & guaranteeSize )) {
// We do not want to fully error out, because needing
// the stack reserve should be rare enough anyway.
Catch :: cerr ()
<< "Failed to reserve piece of stack."
<< " Stack overflows will not be reported successfully." ;
2020-09-08 15:53:08 +02:00
}
}
2022-01-03 23:16:39 +01:00
// We do not attempt to unset the stack guarantee, because
// Windows does not support lowering the stack size guarantee.
FatalConditionHandler ::~ FatalConditionHandler () = default ;
2020-09-08 15:53:08 +02:00
2022-01-03 23:16:39 +01:00
void FatalConditionHandler :: engage_platform () {
// Register as a the top level exception filter.
previousTopLevelExceptionFilter = SetUnhandledExceptionFilter ( topLevelExceptionFilter );
}
2020-09-08 15:53:08 +02:00
2022-01-03 23:16:39 +01:00
void FatalConditionHandler :: disengage_platform () {
if ( SetUnhandledExceptionFilter ( reinterpret_cast < LPTOP_LEVEL_EXCEPTION_FILTER > ( previousTopLevelExceptionFilter )) != topLevelExceptionFilter ) {
CATCH_RUNTIME_ERROR ( "Could not restore previous top level exception filter" );
}
previousTopLevelExceptionFilter = nullptr ;
}
} // end namespace Catch
#endif // CATCH_CONFIG_WINDOWS_SEH
#if defined( CATCH_CONFIG_POSIX_SIGNALS )
#include <signal.h>
2020-09-08 15:53:08 +02:00
namespace Catch {
struct SignalDefs {
int id ;
const char * name ;
};
static SignalDefs signalDefs [] = {
{ SIGINT , "SIGINT - Terminal interrupt signal" },
{ SIGILL , "SIGILL - Illegal instruction signal" },
{ SIGFPE , "SIGFPE - Floating point error signal" },
{ SIGSEGV , "SIGSEGV - Segmentation violation signal" },
{ SIGTERM , "SIGTERM - Termination request signal" },
{ SIGABRT , "SIGABRT - Abort (abnormal termination) signal" }
};
2022-01-03 23:16:39 +01:00
// Older GCCs trigger -Wmissing-field-initializers for T foo = {}
// which is zero initialization, but not explicit. We want to avoid
// that.
#if defined(__GNUC__)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wmissing-field-initializers"
#endif
2020-09-08 15:53:08 +02:00
2022-01-03 23:16:39 +01:00
static char * altStackMem = nullptr ;
static std :: size_t altStackSize = 0 ;
static stack_t oldSigStack {};
static struct sigaction oldSigActions [ sizeof ( signalDefs ) / sizeof ( SignalDefs )]{};
static void restorePreviousSignalHandlers () {
// We set signal handlers back to the previous ones. Hopefully
// nobody overwrote them in the meantime, and doesn't expect
// their signal handlers to live past ours given that they
// installed them after ours..
for ( std :: size_t i = 0 ; i < sizeof ( signalDefs ) / sizeof ( SignalDefs ); ++ i ) {
sigaction ( signalDefs [ i ]. id , & oldSigActions [ i ], nullptr );
}
// Return the old stack
sigaltstack ( & oldSigStack , nullptr );
}
static void handleSignal ( int sig ) {
2020-09-08 15:53:08 +02:00
char const * name = "<unknown signal>" ;
for ( auto const & def : signalDefs ) {
if ( sig == def . id ) {
name = def . name ;
break ;
}
}
2022-01-03 23:16:39 +01:00
// We need to restore previous signal handlers and let them do
// their thing, so that the users can have the debugger break
// when a signal is raised, and so on.
restorePreviousSignalHandlers ();
reportFatal ( name );
2020-09-08 15:53:08 +02:00
raise ( sig );
}
FatalConditionHandler :: FatalConditionHandler () {
2022-01-03 23:16:39 +01:00
assert ( ! altStackMem && "Cannot initialize POSIX signal handler when one already exists" );
if ( altStackSize == 0 ) {
altStackSize = std :: max ( static_cast < size_t > ( SIGSTKSZ ), minStackSizeForErrors );
}
altStackMem = new char [ altStackSize ]();
}
FatalConditionHandler ::~ FatalConditionHandler () {
delete [] altStackMem ;
// We signal that another instance can be constructed by zeroing
// out the pointer.
altStackMem = nullptr ;
}
void FatalConditionHandler :: engage_platform () {
2020-09-08 15:53:08 +02:00
stack_t sigStack ;
sigStack . ss_sp = altStackMem ;
2022-01-03 23:16:39 +01:00
sigStack . ss_size = altStackSize ;
2020-09-08 15:53:08 +02:00
sigStack . ss_flags = 0 ;
sigaltstack ( & sigStack , & oldSigStack );
struct sigaction sa = { };
sa . sa_handler = handleSignal ;
sa . sa_flags = SA_ONSTACK ;
for ( std :: size_t i = 0 ; i < sizeof ( signalDefs ) / sizeof ( SignalDefs ); ++ i ) {
sigaction ( signalDefs [ i ]. id , & sa , & oldSigActions [ i ]);
}
}
#if defined(__GNUC__)
# pragma GCC diagnostic pop
#endif
2022-01-03 23:16:39 +01:00
void FatalConditionHandler :: disengage_platform () {
restorePreviousSignalHandlers ();
}
} // end namespace Catch
#endif // CATCH_CONFIG_POSIX_SIGNALS
#include <cstring>
namespace Catch {
namespace Detail {
uint32_t convertToBits ( float f ) {
static_assert ( sizeof ( float ) == sizeof ( uint32_t ), "Important ULP matcher assumption violated" );
uint32_t i ;
std :: memcpy ( & i , & f , sizeof ( f ));
return i ;
}
uint64_t convertToBits ( double d ) {
static_assert ( sizeof ( double ) == sizeof ( uint64_t ), "Important ULP matcher assumption violated" );
uint64_t i ;
std :: memcpy ( & i , & d , sizeof ( d ));
return i ;
}
} // end namespace Detail
} // end namespace Catch
2020-09-08 15:53:08 +02:00
namespace Catch {
namespace {
void listTests ( IStreamingReporter & reporter , IConfig const & config ) {
auto const & testSpec = config . testSpec ();
auto matchedTestCases = filterTests ( getAllTestCasesSorted ( config ), testSpec , config );
2022-01-03 23:16:39 +01:00
reporter . listTests ( matchedTestCases );
2020-09-08 15:53:08 +02:00
}
void listTags ( IStreamingReporter & reporter , IConfig const & config ) {
auto const & testSpec = config . testSpec ();
std :: vector < TestCaseHandle > matchedTestCases = filterTests ( getAllTestCasesSorted ( config ), testSpec , config );
2022-01-03 23:16:39 +01:00
std :: map < StringRef , TagInfo , Detail :: CaseInsensitiveLess > tagCounts ;
2020-09-08 15:53:08 +02:00
for ( auto const & testCase : matchedTestCases ) {
for ( auto const & tagName : testCase . getTestCaseInfo (). tags ) {
2022-01-03 23:16:39 +01:00
auto it = tagCounts . find ( tagName . original );
2020-09-08 15:53:08 +02:00
if ( it == tagCounts . end ())
2022-01-03 23:16:39 +01:00
it = tagCounts . insert ( std :: make_pair ( tagName . original , TagInfo ())). first ;
2020-09-08 15:53:08 +02:00
it -> second . add ( tagName . original );
}
}
std :: vector < TagInfo > infos ; infos . reserve ( tagCounts . size ());
for ( auto & tagc : tagCounts ) {
2022-01-03 23:16:39 +01:00
infos . push_back ( CATCH_MOVE ( tagc . second ));
2020-09-08 15:53:08 +02:00
}
2022-01-03 23:16:39 +01:00
reporter . listTags ( infos );
2020-09-08 15:53:08 +02:00
}
2022-01-03 23:16:39 +01:00
void listReporters ( IStreamingReporter & reporter ) {
2020-09-08 15:53:08 +02:00
std :: vector < ReporterDescription > descriptions ;
IReporterRegistry :: FactoryMap const & factories = getRegistryHub (). getReporterRegistry (). getFactories ();
descriptions . reserve ( factories . size ());
for ( auto const & fac : factories ) {
descriptions . push_back ({ fac . first , fac . second -> getDescription () });
}
2022-01-03 23:16:39 +01:00
reporter . listReporters ( descriptions );
2020-09-08 15:53:08 +02:00
}
} // end anonymous namespace
void TagInfo :: add ( StringRef spelling ) {
++ count ;
spellings . insert ( spelling );
}
std :: string TagInfo :: all () const {
// 2 per tag for brackets '[' and ']'
size_t size = spellings . size () * 2 ;
for ( auto const & spelling : spellings ) {
size += spelling . size ();
}
std :: string out ; out . reserve ( size );
for ( auto const & spelling : spellings ) {
out += '[' ;
out += spelling ;
out += ']' ;
}
return out ;
}
bool list ( IStreamingReporter & reporter , Config const & config ) {
bool listed = false ;
if ( config . listTests ()) {
listed = true ;
listTests ( reporter , config );
}
if ( config . listTags ()) {
listed = true ;
listTags ( reporter , config );
}
if ( config . listReporters ()) {
listed = true ;
2022-01-03 23:16:39 +01:00
listReporters ( reporter );
2020-09-08 15:53:08 +02:00
}
return listed ;
}
} // end namespace Catch
namespace Catch {
CATCH_INTERNAL_START_WARNINGS_SUPPRESSION
CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS
LeakDetector leakDetector ;
CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION
}
2022-01-03 23:16:39 +01:00
// Allow users of amalgamated .cpp file to remove our main and provide their own.
#if !defined(CATCH_AMALGAMATED_CUSTOM_MAIN)
2020-09-08 15:53:08 +02:00
#if defined(CATCH_CONFIG_WCHAR) && defined(CATCH_PLATFORM_WINDOWS) && defined(_UNICODE) && !defined(DO_NOT_USE_WMAIN)
// Standard C/C++ Win32 Unicode wmain entry point
2022-01-03 23:16:39 +01:00
extern "C" int __cdecl wmain ( int argc , wchar_t * argv [], wchar_t * []) {
2020-09-08 15:53:08 +02:00
#else
// Standard C/C++ main entry point
int main ( int argc , char * argv []) {
#endif
// We want to force the linker not to discard the global variable
// and its constructor, as it (optionally) registers leak detector
( void ) & Catch :: leakDetector ;
return Catch :: Session (). run ( argc , argv );
}
2022-01-03 23:16:39 +01:00
#endif // !defined(CATCH_AMALGAMATED_CUSTOM_MAIN
2020-09-08 15:53:08 +02:00
#include <cstdio>
#include <cstring>
#include <sstream>
#if defined(CATCH_CONFIG_NEW_CAPTURE)
#if defined(_MSC_VER)
#include <io.h> //_dup and _dup2
#define dup _dup
#define dup2 _dup2
#define fileno _fileno
#else
#include <unistd.h> // dup and dup2
#endif
#endif
namespace Catch {
RedirectedStream :: RedirectedStream ( std :: ostream & originalStream , std :: ostream & redirectionStream )
: m_originalStream ( originalStream ),
m_redirectionStream ( redirectionStream ),
m_prevBuf ( m_originalStream . rdbuf () )
{
m_originalStream . rdbuf ( m_redirectionStream . rdbuf () );
}
RedirectedStream ::~ RedirectedStream () {
m_originalStream . rdbuf ( m_prevBuf );
}
RedirectedStdOut :: RedirectedStdOut () : m_cout ( Catch :: cout (), m_rss . get () ) {}
auto RedirectedStdOut :: str () const -> std :: string { return m_rss . str (); }
RedirectedStdErr :: RedirectedStdErr ()
: m_cerr ( Catch :: cerr (), m_rss . get () ),
m_clog ( Catch :: clog (), m_rss . get () )
{}
auto RedirectedStdErr :: str () const -> std :: string { return m_rss . str (); }
RedirectedStreams :: RedirectedStreams ( std :: string & redirectedCout , std :: string & redirectedCerr )
: m_redirectedCout ( redirectedCout ),
m_redirectedCerr ( redirectedCerr )
{}
RedirectedStreams ::~ RedirectedStreams () {
m_redirectedCout += m_redirectedStdOut . str ();
m_redirectedCerr += m_redirectedStdErr . str ();
}
#if defined(CATCH_CONFIG_NEW_CAPTURE)
#if defined(_MSC_VER)
TempFile :: TempFile () {
if ( tmpnam_s ( m_buffer )) {
CATCH_RUNTIME_ERROR ( "Could not get a temp filename" );
}
2020-10-08 15:26:30 +02:00
if ( fopen_s ( & m_file , m_buffer , "w+" )) {
2020-09-08 15:53:08 +02:00
char buffer [ 100 ];
if ( strerror_s ( buffer , errno )) {
CATCH_RUNTIME_ERROR ( "Could not translate errno to a string" );
}
CATCH_RUNTIME_ERROR ( "Could not open the temp file: '" << m_buffer << "' because: " << buffer );
}
}
#else
TempFile :: TempFile () {
m_file = std :: tmpfile ();
if ( ! m_file ) {
CATCH_RUNTIME_ERROR ( "Could not create a temp file." );
}
}
#endif
TempFile ::~ TempFile () {
// TBD: What to do about errors here?
std :: fclose ( m_file );
// We manually create the file on Windows only, on Linux
// it will be autodeleted
#if defined(_MSC_VER)
std :: remove ( m_buffer );
#endif
}
FILE * TempFile :: getFile () {
return m_file ;
}
std :: string TempFile :: getContents () {
std :: stringstream sstr ;
char buffer [ 100 ] = {};
std :: rewind ( m_file );
while ( std :: fgets ( buffer , sizeof ( buffer ), m_file )) {
sstr << buffer ;
}
return sstr . str ();
}
OutputRedirect :: OutputRedirect ( std :: string & stdout_dest , std :: string & stderr_dest ) :
m_originalStdout ( dup ( 1 )),
m_originalStderr ( dup ( 2 )),
m_stdoutDest ( stdout_dest ),
m_stderrDest ( stderr_dest ) {
dup2 ( fileno ( m_stdoutFile . getFile ()), 1 );
dup2 ( fileno ( m_stderrFile . getFile ()), 2 );
}
OutputRedirect ::~ OutputRedirect () {
Catch :: cout () << std :: flush ;
fflush ( stdout );
// Since we support overriding these streams, we flush cerr
// even though std::cerr is unbuffered
Catch :: cerr () << std :: flush ;
Catch :: clog () << std :: flush ;
fflush ( stderr );
dup2 ( m_originalStdout , 1 );
dup2 ( m_originalStderr , 2 );
m_stdoutDest += m_stdoutFile . getContents ();
m_stderrDest += m_stderrFile . getContents ();
}
#endif // CATCH_CONFIG_NEW_CAPTURE
} // namespace Catch
#if defined(CATCH_CONFIG_NEW_CAPTURE)
#if defined(_MSC_VER)
#undef dup
#undef dup2
#undef fileno
#endif
#endif
namespace Catch {
namespace {
#if defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable:4146) // we negate uint32 during the rotate
#endif
// Safe rotr implementation thanks to John Regehr
uint32_t rotate_right ( uint32_t val , uint32_t count ) {
const uint32_t mask = 31 ;
count &= mask ;
return ( val >> count ) | ( val << ( - count & mask ));
}
#if defined(_MSC_VER)
#pragma warning(pop)
#endif
}
SimplePcg32 :: SimplePcg32 ( result_type seed_ ) {
seed ( seed_ );
}
void SimplePcg32 :: seed ( result_type seed_ ) {
m_state = 0 ;
( * this )();
m_state += seed_ ;
( * this )();
}
void SimplePcg32 :: discard ( uint64_t skip ) {
// We could implement this to run in O(log n) steps, but this
// should suffice for our use case.
for ( uint64_t s = 0 ; s < skip ; ++ s ) {
static_cast < void > (( * this )());
}
}
SimplePcg32 :: result_type SimplePcg32 :: operator ()() {
// prepare the output value
const uint32_t xorshifted = static_cast < uint32_t > ((( m_state >> 18u ) ^ m_state ) >> 27u );
const auto output = rotate_right ( xorshifted , m_state >> 59u );
// advance state
m_state = m_state * 6364136223846793005ULL + s_inc ;
return output ;
}
bool operator == ( SimplePcg32 const & lhs , SimplePcg32 const & rhs ) {
return lhs . m_state == rhs . m_state ;
}
bool operator != ( SimplePcg32 const & lhs , SimplePcg32 const & rhs ) {
return lhs . m_state != rhs . m_state ;
}
}
2022-01-03 23:16:39 +01:00
#include <ctime>
#include <random>
namespace Catch {
std :: uint32_t generateRandomSeed ( GenerateFrom from ) {
switch ( from ) {
case GenerateFrom :: Time :
return static_cast < std :: uint32_t > ( std :: time ( nullptr ) );
case GenerateFrom :: Default :
case GenerateFrom :: RandomDevice :
// In theory, a platform could have random_device that returns just
// 16 bits. That is still some randomness, so we don't care too much
return static_cast < std :: uint32_t > ( std :: random_device {}() );
default :
CATCH_ERROR ( "Unknown generation method" );
}
}
} // end namespace Catch
2020-09-08 15:53:08 +02:00
namespace Catch {
ReporterRegistry :: ReporterRegistry () {
// Because it is impossible to move out of initializer list,
// we have to add the elements manually
2022-01-03 23:16:39 +01:00
m_factories [ "Automake" ] = Detail :: make_unique < ReporterFactory < AutomakeReporter >> ();
2020-09-08 15:53:08 +02:00
m_factories [ "compact" ] = Detail :: make_unique < ReporterFactory < CompactReporter >> ();
m_factories [ "console" ] = Detail :: make_unique < ReporterFactory < ConsoleReporter >> ();
2022-01-03 23:16:39 +01:00
m_factories [ "JUnit" ] = Detail :: make_unique < ReporterFactory < JunitReporter >> ();
m_factories [ "SonarQube" ] = Detail :: make_unique < ReporterFactory < SonarQubeReporter >> ();
m_factories [ "TAP" ] = Detail :: make_unique < ReporterFactory < TAPReporter >> ();
m_factories [ "TeamCity" ] = Detail :: make_unique < ReporterFactory < TeamCityReporter >> ();
m_factories [ "XML" ] = Detail :: make_unique < ReporterFactory < XmlReporter >> ();
2020-09-08 15:53:08 +02:00
}
ReporterRegistry ::~ ReporterRegistry () = default ;
2022-01-03 23:16:39 +01:00
IStreamingReporterPtr ReporterRegistry :: create ( std :: string const & name , ReporterConfig const & config ) const {
2020-09-08 15:53:08 +02:00
auto it = m_factories . find ( name );
if ( it == m_factories . end () )
return nullptr ;
2022-01-03 23:16:39 +01:00
return it -> second -> create ( config );
2020-09-08 15:53:08 +02:00
}
void ReporterRegistry :: registerReporter ( std :: string const & name , IReporterFactoryPtr factory ) {
2022-01-03 23:16:39 +01:00
m_factories . emplace ( name , CATCH_MOVE ( factory ));
2020-09-08 15:53:08 +02:00
}
void ReporterRegistry :: registerListener ( IReporterFactoryPtr factory ) {
2022-01-03 23:16:39 +01:00
m_listeners . push_back ( CATCH_MOVE ( factory ) );
2020-09-08 15:53:08 +02:00
}
IReporterRegistry :: FactoryMap const & ReporterRegistry :: getFactories () const {
return m_factories ;
}
IReporterRegistry :: Listeners const & ReporterRegistry :: getListeners () const {
return m_listeners ;
}
}
namespace Catch {
bool isOk ( ResultWas :: OfType resultType ) {
return ( resultType & ResultWas :: FailureBit ) == 0 ;
}
bool isJustInfo ( int flags ) {
return flags == ResultWas :: Info ;
}
ResultDisposition :: Flags operator | ( ResultDisposition :: Flags lhs , ResultDisposition :: Flags rhs ) {
return static_cast < ResultDisposition :: Flags > ( static_cast < int > ( lhs ) | static_cast < int > ( rhs ) );
}
bool shouldContinueOnFailure ( int flags ) { return ( flags & ResultDisposition :: ContinueOnFailure ) != 0 ; }
bool shouldSuppressFailure ( int flags ) { return ( flags & ResultDisposition :: SuppressFail ) != 0 ; }
} // end namespace Catch
#include <cassert>
#include <algorithm>
namespace Catch {
namespace Generators {
struct GeneratorTracker : TestCaseTracking :: TrackerBase , IGeneratorTracker {
GeneratorBasePtr m_generator ;
GeneratorTracker ( TestCaseTracking :: NameAndLocation const & nameAndLocation , TrackerContext & ctx , ITracker * parent )
: TrackerBase ( nameAndLocation , ctx , parent )
{}
~ GeneratorTracker ();
static GeneratorTracker & acquire ( TrackerContext & ctx , TestCaseTracking :: NameAndLocation const & nameAndLocation ) {
2022-01-03 23:16:39 +01:00
GeneratorTracker * tracker ;
2020-09-08 15:53:08 +02:00
ITracker & currentTracker = ctx . currentTracker ();
// Under specific circumstances, the generator we want
// to acquire is also the current tracker. If this is
// the case, we have to avoid looking through current
// tracker's children, and instead return the current
// tracker.
// A case where this check is important is e.g.
// for (int i = 0; i < 5; ++i) {
// int n = GENERATE(1, 2);
// }
//
// without it, the code above creates 5 nested generators.
2022-01-03 23:16:39 +01:00
if ( currentTracker . nameAndLocation () == nameAndLocation ) {
auto thisTracker =
currentTracker . parent () -> findChild ( nameAndLocation );
assert ( thisTracker );
assert ( thisTracker -> isGeneratorTracker () );
tracker = static_cast < GeneratorTracker *> ( thisTracker );
} else if ( ITracker * childTracker =
currentTracker . findChild ( nameAndLocation ) ) {
2020-09-08 15:53:08 +02:00
assert ( childTracker );
assert ( childTracker -> isGeneratorTracker () );
2022-01-03 23:16:39 +01:00
tracker = static_cast < GeneratorTracker *> ( childTracker );
2020-09-08 15:53:08 +02:00
} else {
2022-01-03 23:16:39 +01:00
auto newTracker =
Catch :: Detail :: make_unique < GeneratorTracker > (
nameAndLocation , ctx , & currentTracker );
tracker = newTracker . get ();
currentTracker . addChild ( CATCH_MOVE ( newTracker ) );
2020-09-08 15:53:08 +02:00
}
if ( ! tracker -> isComplete () ) {
tracker -> open ();
}
return * tracker ;
}
// TrackerBase interface
bool isGeneratorTracker () const override { return true ; }
auto hasGenerator () const -> bool override {
return !! m_generator ;
}
void close () override {
TrackerBase :: close ();
// If a generator has a child (it is followed by a section)
// and none of its children have started, then we must wait
// until later to start consuming its values.
// This catches cases where `GENERATE` is placed between two
// `SECTION`s.
// **The check for m_children.empty cannot be removed**.
// doing so would break `GENERATE` _not_ followed by `SECTION`s.
2022-01-03 23:16:39 +01:00
const bool should_wait_for_child = [ & ]() {
// No children -> nobody to wait for
if ( m_children . empty () ) {
return false ;
}
// If at least one child started executing, don't wait
if ( std :: find_if (
m_children . begin (),
m_children . end (),
[]( TestCaseTracking :: ITrackerPtr const & tracker ) {
return tracker -> hasStarted ();
} ) != m_children . end () ) {
return false ;
}
// No children have started. We need to check if they _can_
// start, and thus we should wait for them, or they cannot
// start (due to filters), and we shouldn't wait for them
ITracker * parent = m_parent ;
// This is safe: there is always at least one section
// tracker in a test case tracking tree
while ( ! parent -> isSectionTracker () ) {
parent = parent -> parent ();
}
assert ( parent &&
"Missing root (test case) level section" );
auto const & parentSection =
static_cast < SectionTracker const &> ( * parent );
auto const & filters = parentSection . getFilters ();
// No filters -> no restrictions on running sections
if ( filters . empty () ) {
return true ;
}
for ( auto const & child : m_children ) {
if ( child -> isSectionTracker () &&
std :: find (
filters . begin (),
filters . end (),
static_cast < SectionTracker const &> ( * child )
. trimmedName () ) != filters . end () ) {
return true ;
}
}
return false ;
}();
2020-09-08 15:53:08 +02:00
// This check is a bit tricky, because m_generator->next()
// has a side-effect, where it consumes generator's current
// value, but we do not want to invoke the side-effect if
// this generator is still waiting for any child to start.
if ( should_wait_for_child ||
( m_runState == CompletedSuccessfully &&
m_generator -> next () ) ) {
m_children . clear ();
m_runState = Executing ;
}
}
// IGeneratorTracker interface
auto getGenerator () const -> GeneratorBasePtr const & override {
return m_generator ;
}
void setGenerator ( GeneratorBasePtr && generator ) override {
2022-01-03 23:16:39 +01:00
m_generator = CATCH_MOVE ( generator );
2020-09-08 15:53:08 +02:00
}
};
2022-01-03 23:16:39 +01:00
GeneratorTracker ::~ GeneratorTracker () = default ;
2020-09-08 15:53:08 +02:00
}
RunContext :: RunContext ( IConfig const * _config , IStreamingReporterPtr && reporter )
: m_runInfo ( _config -> name ()),
m_context ( getCurrentMutableContext ()),
m_config ( _config ),
2022-01-03 23:16:39 +01:00
m_reporter ( CATCH_MOVE ( reporter )),
2020-09-08 15:53:08 +02:00
m_lastAssertionInfo { StringRef (), SourceLineInfo ( "" , 0 ), StringRef (), ResultDisposition :: Normal },
m_includeSuccessfulResults ( m_config -> includeSuccessfulResults () || m_reporter -> getPreferences (). shouldReportAllAssertions )
{
m_context . setResultCapture ( this );
m_reporter -> testRunStarting ( m_runInfo );
}
RunContext ::~ RunContext () {
m_reporter -> testRunEnded ( TestRunStats ( m_runInfo , m_totals , aborting ()));
}
Totals RunContext :: runTest ( TestCaseHandle const & testCase ) {
2022-01-03 23:16:39 +01:00
const Totals prevTotals = m_totals ;
2020-09-08 15:53:08 +02:00
std :: string redirectedCout ;
std :: string redirectedCerr ;
auto const & testInfo = testCase . getTestCaseInfo ();
m_reporter -> testCaseStarting ( testInfo );
m_activeTestCase = & testCase ;
ITracker & rootTracker = m_trackerContext . startRun ();
assert ( rootTracker . isSectionTracker ());
static_cast < SectionTracker &> ( rootTracker ). addInitialFilters ( m_config -> getSectionsToRun ());
2022-01-03 23:16:39 +01:00
uint64_t testRuns = 0 ;
2020-09-08 15:53:08 +02:00
do {
m_trackerContext . startCycle ();
m_testCaseTracker = & SectionTracker :: acquire ( m_trackerContext , TestCaseTracking :: NameAndLocation ( testInfo . name , testInfo . lineInfo ));
2022-01-03 23:16:39 +01:00
m_reporter -> testCasePartialStarting ( testInfo , testRuns );
const auto beforeRunTotals = m_totals ;
std :: string oneRunCout , oneRunCerr ;
runCurrentTest ( oneRunCout , oneRunCerr );
redirectedCout += oneRunCout ;
redirectedCerr += oneRunCerr ;
const auto singleRunTotals = m_totals . delta ( beforeRunTotals );
auto statsForOneRun = TestCaseStats ( testInfo , singleRunTotals , oneRunCout , oneRunCerr , aborting ());
m_reporter -> testCasePartialEnded ( statsForOneRun , testRuns );
++ testRuns ;
2020-09-08 15:53:08 +02:00
} while ( ! m_testCaseTracker -> isSuccessfullyCompleted () && ! aborting ());
Totals deltaTotals = m_totals . delta ( prevTotals );
if ( testInfo . expectedToFail () && deltaTotals . testCases . passed > 0 ) {
deltaTotals . assertions . failed ++ ;
deltaTotals . testCases . passed -- ;
deltaTotals . testCases . failed ++ ;
}
m_totals . testCases += deltaTotals . testCases ;
m_reporter -> testCaseEnded ( TestCaseStats ( testInfo ,
deltaTotals ,
redirectedCout ,
redirectedCerr ,
aborting ()));
m_activeTestCase = nullptr ;
m_testCaseTracker = nullptr ;
return deltaTotals ;
}
void RunContext :: assertionEnded ( AssertionResult const & result ) {
if ( result . getResultType () == ResultWas :: Ok ) {
m_totals . assertions . passed ++ ;
m_lastAssertionPassed = true ;
2022-01-03 23:16:39 +01:00
} else if ( ! result . succeeded ()) {
2020-09-08 15:53:08 +02:00
m_lastAssertionPassed = false ;
2022-01-03 23:16:39 +01:00
if ( result . isOk ()) {
}
else if ( m_activeTestCase -> getTestCaseInfo (). okToFail () )
2020-09-08 15:53:08 +02:00
m_totals . assertions . failedButOk ++ ;
else
m_totals . assertions . failed ++ ;
}
else {
m_lastAssertionPassed = true ;
}
2022-01-03 23:16:39 +01:00
m_reporter -> assertionEnded ( AssertionStats ( result , m_messages , m_totals ));
2020-09-08 15:53:08 +02:00
if ( result . getResultType () != ResultWas :: Warning )
m_messageScopes . clear ();
// Reset working state
resetAssertionInfo ();
m_lastResult = result ;
}
void RunContext :: resetAssertionInfo () {
m_lastAssertionInfo . macroName = StringRef ();
m_lastAssertionInfo . capturedExpression = "{Unknown expression after the reported line}" _sr ;
}
bool RunContext :: sectionStarted ( SectionInfo const & sectionInfo , Counts & assertions ) {
ITracker & sectionTracker = SectionTracker :: acquire ( m_trackerContext , TestCaseTracking :: NameAndLocation ( sectionInfo . name , sectionInfo . lineInfo ));
if ( ! sectionTracker . isOpen ())
return false ;
m_activeSections . push_back ( & sectionTracker );
m_lastAssertionInfo . lineInfo = sectionInfo . lineInfo ;
m_reporter -> sectionStarting ( sectionInfo );
assertions = m_totals . assertions ;
return true ;
}
auto RunContext :: acquireGeneratorTracker ( StringRef generatorName , SourceLineInfo const & lineInfo ) -> IGeneratorTracker & {
using namespace Generators ;
GeneratorTracker & tracker = GeneratorTracker :: acquire ( m_trackerContext ,
TestCaseTracking :: NameAndLocation ( static_cast < std :: string > ( generatorName ), lineInfo ) );
m_lastAssertionInfo . lineInfo = lineInfo ;
return tracker ;
}
bool RunContext :: testForMissingAssertions ( Counts & assertions ) {
if ( assertions . total () != 0 )
return false ;
if ( ! m_config -> warnAboutMissingAssertions ())
return false ;
if ( m_trackerContext . currentTracker (). hasChildren ())
return false ;
m_totals . assertions . failed ++ ;
assertions . failed ++ ;
return true ;
}
void RunContext :: sectionEnded ( SectionEndInfo const & endInfo ) {
Counts assertions = m_totals . assertions - endInfo . prevAssertions ;
bool missingAssertions = testForMissingAssertions ( assertions );
if ( ! m_activeSections . empty ()) {
m_activeSections . back () -> close ();
m_activeSections . pop_back ();
}
m_reporter -> sectionEnded ( SectionStats ( endInfo . sectionInfo , assertions , endInfo . durationInSeconds , missingAssertions ));
m_messages . clear ();
m_messageScopes . clear ();
}
void RunContext :: sectionEndedEarly ( SectionEndInfo const & endInfo ) {
if ( m_unfinishedSections . empty ())
m_activeSections . back () -> fail ();
else
m_activeSections . back () -> close ();
m_activeSections . pop_back ();
m_unfinishedSections . push_back ( endInfo );
}
2022-01-03 23:16:39 +01:00
void RunContext :: benchmarkPreparing ( StringRef name ) {
2020-09-08 15:53:08 +02:00
m_reporter -> benchmarkPreparing ( name );
}
void RunContext :: benchmarkStarting ( BenchmarkInfo const & info ) {
m_reporter -> benchmarkStarting ( info );
}
void RunContext :: benchmarkEnded ( BenchmarkStats <> const & stats ) {
m_reporter -> benchmarkEnded ( stats );
}
2022-01-03 23:16:39 +01:00
void RunContext :: benchmarkFailed ( StringRef error ) {
m_reporter -> benchmarkFailed ( error );
2020-09-08 15:53:08 +02:00
}
void RunContext :: pushScopedMessage ( MessageInfo const & message ) {
m_messages . push_back ( message );
}
void RunContext :: popScopedMessage ( MessageInfo const & message ) {
m_messages . erase ( std :: remove ( m_messages . begin (), m_messages . end (), message ), m_messages . end ());
}
void RunContext :: emplaceUnscopedMessage ( MessageBuilder const & builder ) {
m_messageScopes . emplace_back ( builder );
}
std :: string RunContext :: getCurrentTestName () const {
return m_activeTestCase
? m_activeTestCase -> getTestCaseInfo (). name
: std :: string ();
}
const AssertionResult * RunContext :: getLastResult () const {
return & ( * m_lastResult );
}
void RunContext :: exceptionEarlyReported () {
m_shouldReportUnexpected = false ;
}
void RunContext :: handleFatalErrorCondition ( StringRef message ) {
// First notify reporter that bad things happened
m_reporter -> fatalErrorEncountered ( message );
// Don't rebuild the result -- the stringification itself can cause more fatal errors
// Instead, fake a result data.
AssertionResultData tempResult ( ResultWas :: FatalErrorCondition , { false } );
tempResult . message = static_cast < std :: string > ( message );
AssertionResult result ( m_lastAssertionInfo , tempResult );
assertionEnded ( result );
handleUnfinishedSections ();
// Recreate section for test case (as we will lose the one that was in scope)
auto const & testCaseInfo = m_activeTestCase -> getTestCaseInfo ();
SectionInfo testCaseSection ( testCaseInfo . lineInfo , testCaseInfo . name );
Counts assertions ;
assertions . failed = 1 ;
SectionStats testCaseSectionStats ( testCaseSection , assertions , 0 , false );
m_reporter -> sectionEnded ( testCaseSectionStats );
auto const & testInfo = m_activeTestCase -> getTestCaseInfo ();
Totals deltaTotals ;
deltaTotals . testCases . failed = 1 ;
deltaTotals . assertions . failed = 1 ;
m_reporter -> testCaseEnded ( TestCaseStats ( testInfo ,
deltaTotals ,
std :: string (),
std :: string (),
false ));
m_totals . testCases . failed ++ ;
m_reporter -> testRunEnded ( TestRunStats ( m_runInfo , m_totals , false ));
}
bool RunContext :: lastAssertionPassed () {
return m_lastAssertionPassed ;
}
void RunContext :: assertionPassed () {
m_lastAssertionPassed = true ;
++ m_totals . assertions . passed ;
resetAssertionInfo ();
m_messageScopes . clear ();
}
bool RunContext :: aborting () const {
return m_totals . assertions . failed >= static_cast < std :: size_t > ( m_config -> abortAfter ());
}
void RunContext :: runCurrentTest ( std :: string & redirectedCout , std :: string & redirectedCerr ) {
auto const & testCaseInfo = m_activeTestCase -> getTestCaseInfo ();
SectionInfo testCaseSection ( testCaseInfo . lineInfo , testCaseInfo . name );
m_reporter -> sectionStarting ( testCaseSection );
Counts prevAssertions = m_totals . assertions ;
double duration = 0 ;
m_shouldReportUnexpected = true ;
m_lastAssertionInfo = { "TEST_CASE" _sr , testCaseInfo . lineInfo , StringRef (), ResultDisposition :: Normal };
seedRng ( * m_config );
Timer timer ;
CATCH_TRY {
if ( m_reporter -> getPreferences (). shouldRedirectStdOut ) {
#if !defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT)
RedirectedStreams redirectedStreams ( redirectedCout , redirectedCerr );
timer . start ();
invokeActiveTestCase ();
#else
OutputRedirect r ( redirectedCout , redirectedCerr );
timer . start ();
invokeActiveTestCase ();
#endif
} else {
timer . start ();
invokeActiveTestCase ();
}
duration = timer . getElapsedSeconds ();
} CATCH_CATCH_ANON ( TestFailureException & ) {
// This just means the test was aborted due to failure
} CATCH_CATCH_ALL {
// Under CATCH_CONFIG_FAST_COMPILE, unexpected exceptions under REQUIRE assertions
// are reported without translation at the point of origin.
if ( m_shouldReportUnexpected ) {
AssertionReaction dummyReaction ;
handleUnexpectedInflightException ( m_lastAssertionInfo , translateActiveException (), dummyReaction );
}
}
Counts assertions = m_totals . assertions - prevAssertions ;
bool missingAssertions = testForMissingAssertions ( assertions );
m_testCaseTracker -> close ();
handleUnfinishedSections ();
m_messages . clear ();
m_messageScopes . clear ();
SectionStats testCaseSectionStats ( testCaseSection , assertions , duration , missingAssertions );
m_reporter -> sectionEnded ( testCaseSectionStats );
}
void RunContext :: invokeActiveTestCase () {
2022-01-03 23:16:39 +01:00
// We need to engage a handler for signals/structured exceptions
2020-09-08 15:53:08 +02:00
// before running the tests themselves, or the binary can crash
// without failed test being reported.
2022-01-03 23:16:39 +01:00
FatalConditionHandlerGuard _ ( & m_fatalConditionhandler );
2020-09-08 15:53:08 +02:00
m_activeTestCase -> invoke ();
}
void RunContext :: handleUnfinishedSections () {
// If sections ended prematurely due to an exception we stored their
// infos here so we can tear them down outside the unwind process.
for ( auto it = m_unfinishedSections . rbegin (),
itEnd = m_unfinishedSections . rend ();
it != itEnd ;
++ it )
sectionEnded ( * it );
m_unfinishedSections . clear ();
}
void RunContext :: handleExpr (
AssertionInfo const & info ,
ITransientExpression const & expr ,
AssertionReaction & reaction
) {
m_reporter -> assertionStarting ( info );
bool negated = isFalseTest ( info . resultDisposition );
bool result = expr . getResult () != negated ;
if ( result ) {
if ( ! m_includeSuccessfulResults ) {
assertionPassed ();
}
else {
reportExpr ( info , ResultWas :: Ok , & expr , negated );
}
}
else {
reportExpr ( info , ResultWas :: ExpressionFailed , & expr , negated );
populateReaction ( reaction );
}
}
void RunContext :: reportExpr (
AssertionInfo const & info ,
ResultWas :: OfType resultType ,
ITransientExpression const * expr ,
bool negated ) {
m_lastAssertionInfo = info ;
AssertionResultData data ( resultType , LazyExpression ( negated ) );
AssertionResult assertionResult { info , data };
assertionResult . m_resultData . lazyExpression . m_transientExpression = expr ;
assertionEnded ( assertionResult );
}
void RunContext :: handleMessage (
AssertionInfo const & info ,
ResultWas :: OfType resultType ,
2022-01-03 23:16:39 +01:00
StringRef message ,
2020-09-08 15:53:08 +02:00
AssertionReaction & reaction
) {
m_reporter -> assertionStarting ( info );
m_lastAssertionInfo = info ;
AssertionResultData data ( resultType , LazyExpression ( false ) );
data . message = static_cast < std :: string > ( message );
AssertionResult assertionResult { m_lastAssertionInfo , data };
assertionEnded ( assertionResult );
if ( ! assertionResult . isOk () )
populateReaction ( reaction );
}
void RunContext :: handleUnexpectedExceptionNotThrown (
AssertionInfo const & info ,
AssertionReaction & reaction
) {
handleNonExpr ( info , Catch :: ResultWas :: DidntThrowException , reaction );
}
void RunContext :: handleUnexpectedInflightException (
AssertionInfo const & info ,
std :: string const & message ,
AssertionReaction & reaction
) {
m_lastAssertionInfo = info ;
AssertionResultData data ( ResultWas :: ThrewException , LazyExpression ( false ) );
data . message = message ;
AssertionResult assertionResult { info , data };
assertionEnded ( assertionResult );
populateReaction ( reaction );
}
void RunContext :: populateReaction ( AssertionReaction & reaction ) {
reaction . shouldDebugBreak = m_config -> shouldDebugBreak ();
reaction . shouldThrow = aborting () || ( m_lastAssertionInfo . resultDisposition & ResultDisposition :: Normal );
}
void RunContext :: handleIncomplete (
AssertionInfo const & info
) {
m_lastAssertionInfo = info ;
AssertionResultData data ( ResultWas :: ThrewException , LazyExpression ( false ) );
data . message = "Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE" ;
AssertionResult assertionResult { info , data };
assertionEnded ( assertionResult );
}
void RunContext :: handleNonExpr (
AssertionInfo const & info ,
ResultWas :: OfType resultType ,
AssertionReaction & reaction
) {
m_lastAssertionInfo = info ;
AssertionResultData data ( resultType , LazyExpression ( false ) );
AssertionResult assertionResult { info , data };
assertionEnded ( assertionResult );
if ( ! assertionResult . isOk () )
populateReaction ( reaction );
}
IResultCapture & getResultCapture () {
if ( auto * capture = getCurrentContext (). getResultCapture ())
return * capture ;
else
CATCH_INTERNAL_ERROR ( "No result capture instance" );
}
void seedRng ( IConfig const & config ) {
if ( config . rngSeed () != 0 ) {
std :: srand ( config . rngSeed ());
rng (). seed ( config . rngSeed ());
}
}
unsigned int rngSeed () {
return getCurrentContext (). getConfig () -> rngSeed ();
}
}
namespace Catch {
Section :: Section ( SectionInfo && info ) :
2022-01-03 23:16:39 +01:00
m_info ( CATCH_MOVE ( info ) ),
2020-09-08 15:53:08 +02:00
m_sectionIncluded (
getResultCapture (). sectionStarted ( m_info , m_assertions ) ) {
// Non-"included" sections will not use the timing information
// anyway, so don't bother with the potential syscall.
if ( m_sectionIncluded ) {
m_timer . start ();
}
}
Section ::~ Section () {
if ( m_sectionIncluded ) {
SectionEndInfo endInfo { m_info , m_assertions , m_timer . getElapsedSeconds () };
if ( uncaught_exceptions () )
getResultCapture (). sectionEndedEarly ( endInfo );
else
getResultCapture (). sectionEnded ( endInfo );
}
}
// This indicates whether the section should be executed or not
Section :: operator bool () const {
return m_sectionIncluded ;
}
} // end namespace Catch
#include <vector>
namespace Catch {
namespace {
static auto getSingletons () -> std :: vector < ISingleton *>*& {
static std :: vector < ISingleton *>* g_singletons = nullptr ;
if ( ! g_singletons )
g_singletons = new std :: vector < ISingleton *> ();
return g_singletons ;
}
}
2022-01-03 23:16:39 +01:00
ISingleton ::~ ISingleton () = default ;
2020-09-08 15:53:08 +02:00
void addSingleton ( ISingleton * singleton ) {
getSingletons () -> push_back ( singleton );
}
void cleanupSingletons () {
auto & singletons = getSingletons ();
for ( auto singleton : * singletons )
delete singleton ;
delete singletons ;
singletons = nullptr ;
}
} // namespace Catch
2022-01-03 23:16:39 +01:00
#include <cstring>
#include <ostream>
namespace Catch {
bool SourceLineInfo :: operator == ( SourceLineInfo const & other ) const noexcept {
return line == other . line && ( file == other . file || std :: strcmp ( file , other . file ) == 0 );
}
bool SourceLineInfo :: operator < ( SourceLineInfo const & other ) const noexcept {
// We can assume that the same file will usually have the same pointer.
// Thus, if the pointers are the same, there is no point in calling the strcmp
return line < other . line || ( line == other . line && file != other . file && ( std :: strcmp ( file , other . file ) < 0 ));
}
std :: ostream & operator << ( std :: ostream & os , SourceLineInfo const & info ) {
#ifndef __GNUG__
os << info . file << '(' << info . line << ')' ;
#else
os << info . file << ':' << info . line ;
#endif
return os ;
}
} // end namespace Catch
2020-09-08 15:53:08 +02:00
#include <cstdio>
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
namespace Catch {
Catch :: IStream ::~ IStream () = default ;
namespace Detail {
namespace {
template < typename WriterF , std :: size_t bufferSize = 256 >
class StreamBufImpl : public std :: streambuf {
char data [ bufferSize ];
WriterF m_writer ;
public :
StreamBufImpl () {
setp ( data , data + sizeof ( data ) );
}
~ StreamBufImpl () noexcept {
StreamBufImpl :: sync ();
}
private :
int overflow ( int c ) override {
sync ();
if ( c != EOF ) {
if ( pbase () == epptr () )
m_writer ( std :: string ( 1 , static_cast < char > ( c ) ) );
else
sputc ( static_cast < char > ( c ) );
}
return 0 ;
}
int sync () override {
if ( pbase () != pptr () ) {
m_writer ( std :: string ( pbase (), static_cast < std :: string :: size_type > ( pptr () - pbase () ) ) );
setp ( pbase (), epptr () );
}
return 0 ;
}
};
///////////////////////////////////////////////////////////////////////////
struct OutputDebugWriter {
2022-01-03 23:16:39 +01:00
void operator ()( std :: string const & str ) {
if ( ! str . empty () ) {
writeToDebugConsole ( str );
}
2020-09-08 15:53:08 +02:00
}
};
///////////////////////////////////////////////////////////////////////////
class FileStream : public IStream {
mutable std :: ofstream m_ofs ;
public :
2022-01-03 23:16:39 +01:00
FileStream ( std :: string const & filename ) {
2020-09-08 15:53:08 +02:00
m_ofs . open ( filename . c_str () );
2022-01-03 23:16:39 +01:00
CATCH_ENFORCE ( ! m_ofs . fail (), "Unable to open file: '" << filename << '\'' );
2020-09-08 15:53:08 +02:00
}
~ FileStream () override = default ;
public : // IStream
std :: ostream & stream () const override {
return m_ofs ;
}
};
///////////////////////////////////////////////////////////////////////////
class CoutStream : public IStream {
mutable std :: ostream m_os ;
public :
// Store the streambuf from cout up-front because
// cout may get redirected when running tests
CoutStream () : m_os ( Catch :: cout (). rdbuf () ) {}
~ CoutStream () override = default ;
public : // IStream
std :: ostream & stream () const override { return m_os ; }
};
///////////////////////////////////////////////////////////////////////////
class DebugOutStream : public IStream {
Detail :: unique_ptr < StreamBufImpl < OutputDebugWriter >> m_streamBuf ;
mutable std :: ostream m_os ;
public :
DebugOutStream ()
: m_streamBuf ( Detail :: make_unique < StreamBufImpl < OutputDebugWriter >> () ),
m_os ( m_streamBuf . get () )
{}
~ DebugOutStream () override = default ;
public : // IStream
std :: ostream & stream () const override { return m_os ; }
};
} // unnamed namespace
} // namespace Detail
///////////////////////////////////////////////////////////////////////////
2022-01-03 23:16:39 +01:00
auto makeStream ( std :: string const & filename ) -> Detail :: unique_ptr < IStream const > {
if ( filename . empty () || filename == "-" ) {
return Detail :: make_unique < Detail :: CoutStream > ();
}
2020-09-08 15:53:08 +02:00
else if ( filename [ 0 ] == '%' ) {
if ( filename == "%debug" )
2022-01-03 23:16:39 +01:00
return Detail :: make_unique < Detail :: DebugOutStream > ();
2020-09-08 15:53:08 +02:00
else
2022-01-03 23:16:39 +01:00
CATCH_ERROR ( "Unrecognised stream: '" << filename << '\'' );
2020-09-08 15:53:08 +02:00
}
else
2022-01-03 23:16:39 +01:00
return Detail :: make_unique < Detail :: FileStream > ( filename );
2020-09-08 15:53:08 +02:00
}
// This class encapsulates the idea of a pool of ostringstreams that can be reused.
struct StringStreams {
std :: vector < Detail :: unique_ptr < std :: ostringstream >> m_streams ;
std :: vector < std :: size_t > m_unused ;
std :: ostringstream m_referenceStream ; // Used for copy state/ flags from
auto add () -> std :: size_t {
if ( m_unused . empty () ) {
2022-01-03 23:16:39 +01:00
m_streams . push_back ( Detail :: make_unique < std :: ostringstream > () );
2020-09-08 15:53:08 +02:00
return m_streams . size () - 1 ;
}
else {
auto index = m_unused . back ();
m_unused . pop_back ();
return index ;
}
}
void release ( std :: size_t index ) {
m_streams [ index ] -> copyfmt ( m_referenceStream ); // Restore initial flags and other state
m_unused . push_back ( index );
}
};
ReusableStringStream :: ReusableStringStream ()
: m_index ( Singleton < StringStreams >:: getMutable (). add () ),
m_oss ( Singleton < StringStreams >:: getMutable (). m_streams [ m_index ]. get () )
{}
ReusableStringStream ::~ ReusableStringStream () {
static_cast < std :: ostringstream *> ( m_oss ) -> str ( "" );
m_oss -> clear ();
Singleton < StringStreams >:: getMutable (). release ( m_index );
}
std :: string ReusableStringStream :: str () const {
return static_cast < std :: ostringstream *> ( m_oss ) -> str ();
}
void ReusableStringStream :: str ( std :: string const & str ) {
static_cast < std :: ostringstream *> ( m_oss ) -> str ( str );
}
///////////////////////////////////////////////////////////////////////////
#ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement these functions
std :: ostream & cout () { return std :: cout ; }
std :: ostream & cerr () { return std :: cerr ; }
std :: ostream & clog () { return std :: clog ; }
#endif
}
#include <algorithm>
#include <ostream>
#include <cstring>
#include <cctype>
#include <vector>
namespace Catch {
bool startsWith ( std :: string const & s , std :: string const & prefix ) {
return s . size () >= prefix . size () && std :: equal ( prefix . begin (), prefix . end (), s . begin ());
}
2022-01-03 23:16:39 +01:00
bool startsWith ( StringRef s , char prefix ) {
2020-09-08 15:53:08 +02:00
return ! s . empty () && s [ 0 ] == prefix ;
}
bool endsWith ( std :: string const & s , std :: string const & suffix ) {
return s . size () >= suffix . size () && std :: equal ( suffix . rbegin (), suffix . rend (), s . rbegin ());
}
bool endsWith ( std :: string const & s , char suffix ) {
return ! s . empty () && s [ s . size () - 1 ] == suffix ;
}
bool contains ( std :: string const & s , std :: string const & infix ) {
return s . find ( infix ) != std :: string :: npos ;
}
void toLowerInPlace ( std :: string & s ) {
2022-01-03 23:16:39 +01:00
std :: transform ( s . begin (), s . end (), s . begin (), []( char c ) {
return toLower ( c );
} );
2020-09-08 15:53:08 +02:00
}
std :: string toLower ( std :: string const & s ) {
std :: string lc = s ;
toLowerInPlace ( lc );
return lc ;
}
2022-01-03 23:16:39 +01:00
char toLower ( char c ) {
return static_cast < char > ( std :: tolower ( static_cast < unsigned char > ( c )));
}
2020-09-08 15:53:08 +02:00
std :: string trim ( std :: string const & str ) {
static char const * whitespaceChars = " \n\r\t " ;
std :: string :: size_type start = str . find_first_not_of ( whitespaceChars );
std :: string :: size_type end = str . find_last_not_of ( whitespaceChars );
return start != std :: string :: npos ? str . substr ( start , 1 + end - start ) : std :: string ();
}
StringRef trim ( StringRef ref ) {
const auto is_ws = []( char c ) {
return c == ' ' || c == '\t' || c == '\n' || c == '\r' ;
};
size_t real_begin = 0 ;
while ( real_begin < ref . size () && is_ws ( ref [ real_begin ])) { ++ real_begin ; }
size_t real_end = ref . size ();
while ( real_end > real_begin && is_ws ( ref [ real_end - 1 ])) { -- real_end ; }
return ref . substr ( real_begin , real_end - real_begin );
}
bool replaceInPlace ( std :: string & str , std :: string const & replaceThis , std :: string const & withThis ) {
bool replaced = false ;
std :: size_t i = str . find ( replaceThis );
while ( i != std :: string :: npos ) {
replaced = true ;
str = str . substr ( 0 , i ) + withThis + str . substr ( i + replaceThis . size () );
if ( i < str . size () - withThis . size () )
i = str . find ( replaceThis , i + withThis . size () );
else
i = std :: string :: npos ;
}
return replaced ;
}
std :: vector < StringRef > splitStringRef ( StringRef str , char delimiter ) {
std :: vector < StringRef > subStrings ;
std :: size_t start = 0 ;
for ( std :: size_t pos = 0 ; pos < str . size (); ++ pos ) {
if ( str [ pos ] == delimiter ) {
if ( pos - start > 1 )
subStrings . push_back ( str . substr ( start , pos - start ) );
start = pos + 1 ;
}
}
if ( start < str . size () )
subStrings . push_back ( str . substr ( start , str . size () - start ) );
return subStrings ;
}
std :: ostream & operator << ( std :: ostream & os , pluralise const & pluraliser ) {
os << pluraliser . m_count << ' ' << pluraliser . m_label ;
if ( pluraliser . m_count != 1 )
os << 's' ;
return os ;
}
}
#include <algorithm>
#include <ostream>
#include <cstring>
#include <cstdint>
namespace Catch {
StringRef :: StringRef ( char const * rawChars ) noexcept
: StringRef ( rawChars , static_cast < StringRef :: size_type > ( std :: strlen ( rawChars ) ) )
{}
2022-01-03 23:16:39 +01:00
auto StringRef :: operator == ( StringRef other ) const noexcept -> bool {
2020-09-08 15:53:08 +02:00
return m_size == other . m_size
&& ( std :: memcmp ( m_start , other . m_start , m_size ) == 0 );
}
2022-01-03 23:16:39 +01:00
bool StringRef :: operator < ( StringRef rhs ) const noexcept {
2020-09-08 15:53:08 +02:00
if ( m_size < rhs . m_size ) {
return strncmp ( m_start , rhs . m_start , m_size ) <= 0 ;
}
return strncmp ( m_start , rhs . m_start , rhs . m_size ) < 0 ;
}
2022-01-03 23:16:39 +01:00
int StringRef :: compare ( StringRef rhs ) const {
auto cmpResult =
strncmp ( m_start , rhs . m_start , std :: min ( m_size , rhs . m_size ) );
// This means that strncmp found a difference before the strings
// ended, and we can return it directly
if ( cmpResult != 0 ) {
return cmpResult ;
}
// If strings are equal up to length, then their comparison results on
// their size
if ( m_size < rhs . m_size ) {
return - 1 ;
} else if ( m_size > rhs . m_size ) {
return 1 ;
} else {
return 0 ;
}
}
auto operator << ( std :: ostream & os , StringRef str ) -> std :: ostream & {
return os . write ( str . data (), static_cast < std :: streamsize > ( str . size ()));
2020-09-08 15:53:08 +02:00
}
std :: string operator + ( StringRef lhs , StringRef rhs ) {
std :: string ret ;
ret . reserve ( lhs . size () + rhs . size ());
ret += lhs ;
ret += rhs ;
return ret ;
}
2022-01-03 23:16:39 +01:00
auto operator += ( std :: string & lhs , StringRef rhs ) -> std :: string & {
2020-09-08 15:53:08 +02:00
lhs . append ( rhs . data (), rhs . size ());
return lhs ;
}
} // namespace Catch
namespace Catch {
TagAliasRegistry ::~ TagAliasRegistry () {}
TagAlias const * TagAliasRegistry :: find ( std :: string const & alias ) const {
auto it = m_registry . find ( alias );
if ( it != m_registry . end () )
return & ( it -> second );
else
return nullptr ;
}
std :: string TagAliasRegistry :: expandAliases ( std :: string const & unexpandedTestSpec ) const {
std :: string expandedTestSpec = unexpandedTestSpec ;
for ( auto const & registryKvp : m_registry ) {
std :: size_t pos = expandedTestSpec . find ( registryKvp . first );
if ( pos != std :: string :: npos ) {
expandedTestSpec = expandedTestSpec . substr ( 0 , pos ) +
registryKvp . second . tag +
expandedTestSpec . substr ( pos + registryKvp . first . size () );
}
}
return expandedTestSpec ;
}
void TagAliasRegistry :: add ( std :: string const & alias , std :: string const & tag , SourceLineInfo const & lineInfo ) {
CATCH_ENFORCE ( startsWith ( alias , "[@" ) && endsWith ( alias , ']' ),
"error: tag alias, '" << alias << "' is not of the form [@alias name]. \n " << lineInfo );
CATCH_ENFORCE ( m_registry . insert ( std :: make_pair ( alias , TagAlias ( tag , lineInfo ))). second ,
"error: tag alias, '" << alias << "' already registered. \n "
<< " \t First seen at: " << find ( alias ) -> lineInfo << " \n "
<< " \t Redefined at: " << lineInfo );
}
2022-01-03 23:16:39 +01:00
ITagAliasRegistry ::~ ITagAliasRegistry () = default ;
2020-09-08 15:53:08 +02:00
ITagAliasRegistry const & ITagAliasRegistry :: get () {
return getRegistryHub (). getTagAliasRegistry ();
}
} // end namespace Catch
#include <algorithm>
#include <set>
namespace Catch {
namespace {
2022-01-03 23:16:39 +01:00
struct TestHasher {
using hash_t = uint64_t ;
2020-09-08 15:53:08 +02:00
2022-01-03 23:16:39 +01:00
explicit TestHasher ( hash_t hashSuffix ) :
m_hashSuffix ( hashSuffix ) {}
2020-09-08 15:53:08 +02:00
2022-01-03 23:16:39 +01:00
uint64_t m_hashSuffix ;
uint32_t operator ()( TestCaseInfo const & t ) const {
// FNV-1a hash with multiplication fold.
const hash_t prime = 1099511628211u ;
hash_t hash = 14695981039346656037u ;
2020-09-08 15:53:08 +02:00
for ( const char c : t . name ) {
hash ^= c ;
hash *= prime ;
}
2022-01-03 23:16:39 +01:00
hash ^= m_hashSuffix ;
hash *= prime ;
const uint32_t low { static_cast < uint32_t > ( hash ) };
const uint32_t high { static_cast < uint32_t > ( hash >> 32 ) };
return low * high ;
2020-09-08 15:53:08 +02:00
}
};
} // end anonymous namespace
std :: vector < TestCaseHandle > sortTests ( IConfig const & config , std :: vector < TestCaseHandle > const & unsortedTestCases ) {
switch ( config . runOrder ()) {
case TestRunOrder :: Declared :
return unsortedTestCases ;
case TestRunOrder :: LexicographicallySorted : {
std :: vector < TestCaseHandle > sorted = unsortedTestCases ;
2022-01-03 23:16:39 +01:00
std :: sort (
sorted . begin (),
sorted . end (),
[]( TestCaseHandle const & lhs , TestCaseHandle const & rhs ) {
return lhs . getTestCaseInfo () < rhs . getTestCaseInfo ();
}
);
2020-09-08 15:53:08 +02:00
return sorted ;
}
case TestRunOrder :: Randomized : {
seedRng ( config );
2022-01-03 23:16:39 +01:00
using TestWithHash = std :: pair < TestHasher :: hash_t , TestCaseHandle > ;
TestHasher h { config . rngSeed () };
std :: vector < TestWithHash > indexed_tests ;
2020-09-08 15:53:08 +02:00
indexed_tests . reserve ( unsortedTestCases . size ());
for ( auto const & handle : unsortedTestCases ) {
indexed_tests . emplace_back ( h ( handle . getTestCaseInfo ()), handle );
}
2022-01-03 23:16:39 +01:00
std :: sort ( indexed_tests . begin (),
indexed_tests . end (),
[]( TestWithHash const & lhs , TestWithHash const & rhs ) {
if ( lhs . first == rhs . first ) {
return lhs . second . getTestCaseInfo () <
rhs . second . getTestCaseInfo ();
}
return lhs . first < rhs . first ;
} );
2020-09-08 15:53:08 +02:00
std :: vector < TestCaseHandle > randomized ;
randomized . reserve ( indexed_tests . size ());
for ( auto const & indexed : indexed_tests ) {
randomized . push_back ( indexed . second );
}
return randomized ;
}
}
CATCH_INTERNAL_ERROR ( "Unknown test order value!" );
}
bool isThrowSafe ( TestCaseHandle const & testCase , IConfig const & config ) {
return ! testCase . getTestCaseInfo (). throws () || config . allowThrows ();
}
bool matchTest ( TestCaseHandle const & testCase , TestSpec const & testSpec , IConfig const & config ) {
return testSpec . matches ( testCase . getTestCaseInfo () ) && isThrowSafe ( testCase , config );
}
2022-01-03 23:16:39 +01:00
void
enforceNoDuplicateTestCases ( std :: vector < TestCaseHandle > const & tests ) {
auto testInfoCmp = []( TestCaseInfo const * lhs ,
TestCaseInfo const * rhs ) {
return * lhs < * rhs ;
};
std :: set < TestCaseInfo const * , decltype ( testInfoCmp ) > seenTests ( testInfoCmp );
for ( auto const & test : tests ) {
const auto infoPtr = & test . getTestCaseInfo ();
const auto prev = seenTests . insert ( infoPtr );
CATCH_ENFORCE (
prev . second ,
"error: test case \" " << infoPtr -> name << " \" , with tags \" "
<< infoPtr -> tagsAsString () << " \" already defined. \n "
<< " \t First seen at " << ( * prev . first ) -> lineInfo << " \n "
<< " \t Redefined at " << infoPtr -> lineInfo );
2020-09-08 15:53:08 +02:00
}
}
std :: vector < TestCaseHandle > filterTests ( std :: vector < TestCaseHandle > const & testCases , TestSpec const & testSpec , IConfig const & config ) {
std :: vector < TestCaseHandle > filtered ;
filtered . reserve ( testCases . size () );
for ( auto const & testCase : testCases ) {
if (( ! testSpec . hasFilters () && ! testCase . getTestCaseInfo (). isHidden ()) ||
( testSpec . hasFilters () && matchTest ( testCase , testSpec , config ))) {
filtered . push_back ( testCase );
}
}
2022-01-03 23:16:39 +01:00
return createShard ( filtered , config . shardCount (), config . shardIndex ());
2020-09-08 15:53:08 +02:00
}
std :: vector < TestCaseHandle > const & getAllTestCasesSorted ( IConfig const & config ) {
return getRegistryHub (). getTestCaseRegistry (). getAllTestsSorted ( config );
}
void TestRegistry :: registerTest ( Detail :: unique_ptr < TestCaseInfo > testInfo , Detail :: unique_ptr < ITestInvoker > testInvoker ) {
m_handles . emplace_back ( testInfo . get (), testInvoker . get ());
m_viewed_test_infos . push_back ( testInfo . get ());
2022-01-03 23:16:39 +01:00
m_owned_test_infos . push_back ( CATCH_MOVE ( testInfo ));
m_invokers . push_back ( CATCH_MOVE ( testInvoker ));
2020-09-08 15:53:08 +02:00
}
std :: vector < TestCaseInfo *> const & TestRegistry :: getAllInfos () const {
return m_viewed_test_infos ;
}
std :: vector < TestCaseHandle > const & TestRegistry :: getAllTests () const {
return m_handles ;
}
std :: vector < TestCaseHandle > const & TestRegistry :: getAllTestsSorted ( IConfig const & config ) const {
if ( m_sortedFunctions . empty () )
enforceNoDuplicateTestCases ( m_handles );
if ( m_currentSortOrder != config . runOrder () || m_sortedFunctions . empty () ) {
m_sortedFunctions = sortTests ( config , m_handles );
m_currentSortOrder = config . runOrder ();
}
return m_sortedFunctions ;
}
///////////////////////////////////////////////////////////////////////////
void TestInvokerAsFunction :: invoke () const {
m_testAsFunction ();
}
} // end namespace Catch
#include <algorithm>
#include <cassert>
#if defined(__clang__)
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wexit-time-destructors"
#endif
namespace Catch {
namespace TestCaseTracking {
NameAndLocation :: NameAndLocation ( std :: string const & _name , SourceLineInfo const & _location )
: name ( _name ),
location ( _location )
{}
ITracker ::~ ITracker () = default ;
2022-01-03 23:16:39 +01:00
void ITracker :: markAsNeedingAnotherRun () {
m_runState = NeedsAnotherRun ;
2020-09-08 15:53:08 +02:00
}
2022-01-03 23:16:39 +01:00
void ITracker :: addChild ( ITrackerPtr && child ) {
m_children . push_back ( CATCH_MOVE ( child ) );
}
ITracker * ITracker :: findChild ( NameAndLocation const & nameAndLocation ) {
2020-09-08 15:53:08 +02:00
auto it = std :: find_if (
m_children . begin (),
m_children . end (),
[ & nameAndLocation ]( ITrackerPtr const & tracker ) {
return tracker -> nameAndLocation (). location ==
nameAndLocation . location &&
tracker -> nameAndLocation (). name == nameAndLocation . name ;
} );
2022-01-03 23:16:39 +01:00
return ( it != m_children . end () ) ? it -> get () : nullptr ;
2020-09-08 15:53:08 +02:00
}
2022-01-03 23:16:39 +01:00
bool ITracker :: isSectionTracker () const { return false ; }
bool ITracker :: isGeneratorTracker () const { return false ; }
2020-09-08 15:53:08 +02:00
2022-01-03 23:16:39 +01:00
bool ITracker :: isSuccessfullyCompleted () const {
return m_runState == CompletedSuccessfully ;
}
bool ITracker :: isOpen () const {
return m_runState != NotStarted && ! isComplete ();
}
bool ITracker :: hasStarted () const { return m_runState != NotStarted ; }
void ITracker :: openChild () {
if ( m_runState != ExecutingChildren ) {
m_runState = ExecutingChildren ;
if ( m_parent ) {
m_parent -> openChild ();
}
}
}
2020-09-08 15:53:08 +02:00
ITracker & TrackerContext :: startRun () {
using namespace std :: string_literals ;
2022-01-03 23:16:39 +01:00
m_rootTracker = Catch :: Detail :: make_unique < SectionTracker > (
NameAndLocation ( "{root}" s , CATCH_INTERNAL_LINEINFO ),
* this ,
nullptr );
2020-09-08 15:53:08 +02:00
m_currentTracker = nullptr ;
m_runState = Executing ;
return * m_rootTracker ;
}
void TrackerContext :: endRun () {
m_rootTracker . reset ();
m_currentTracker = nullptr ;
m_runState = NotStarted ;
}
void TrackerContext :: startCycle () {
m_currentTracker = m_rootTracker . get ();
m_runState = Executing ;
}
void TrackerContext :: completeCycle () {
m_runState = CompletedCycle ;
}
bool TrackerContext :: completedCycle () const {
return m_runState == CompletedCycle ;
}
ITracker & TrackerContext :: currentTracker () {
return * m_currentTracker ;
}
void TrackerContext :: setCurrentTracker ( ITracker * tracker ) {
m_currentTracker = tracker ;
}
TrackerBase :: TrackerBase ( NameAndLocation const & nameAndLocation , TrackerContext & ctx , ITracker * parent ) :
2022-01-03 23:16:39 +01:00
ITracker ( nameAndLocation , parent ),
m_ctx ( ctx )
2020-09-08 15:53:08 +02:00
{}
bool TrackerBase :: isComplete () const {
return m_runState == CompletedSuccessfully || m_runState == Failed ;
}
void TrackerBase :: open () {
m_runState = Executing ;
moveToThis ();
if ( m_parent )
m_parent -> openChild ();
}
void TrackerBase :: close () {
// Close any still open children (e.g. generators)
while ( & m_ctx . currentTracker () != this )
m_ctx . currentTracker (). close ();
switch ( m_runState ) {
case NeedsAnotherRun :
break ;
case Executing :
m_runState = CompletedSuccessfully ;
break ;
case ExecutingChildren :
if ( std :: all_of ( m_children . begin (), m_children . end (), []( ITrackerPtr const & t ){ return t -> isComplete (); }) )
m_runState = CompletedSuccessfully ;
break ;
case NotStarted :
case CompletedSuccessfully :
case Failed :
CATCH_INTERNAL_ERROR ( "Illogical state: " << m_runState );
default :
CATCH_INTERNAL_ERROR ( "Unknown state: " << m_runState );
}
moveToParent ();
m_ctx . completeCycle ();
}
void TrackerBase :: fail () {
m_runState = Failed ;
if ( m_parent )
m_parent -> markAsNeedingAnotherRun ();
moveToParent ();
m_ctx . completeCycle ();
}
void TrackerBase :: moveToParent () {
assert ( m_parent );
m_ctx . setCurrentTracker ( m_parent );
}
void TrackerBase :: moveToThis () {
m_ctx . setCurrentTracker ( this );
}
SectionTracker :: SectionTracker ( NameAndLocation const & nameAndLocation , TrackerContext & ctx , ITracker * parent )
: TrackerBase ( nameAndLocation , ctx , parent ),
m_trimmed_name ( trim ( nameAndLocation . name ))
{
if ( parent ) {
while ( ! parent -> isSectionTracker () )
2022-01-03 23:16:39 +01:00
parent = parent -> parent ();
2020-09-08 15:53:08 +02:00
SectionTracker & parentSection = static_cast < SectionTracker &> ( * parent );
addNextFilters ( parentSection . m_filters );
}
}
bool SectionTracker :: isComplete () const {
bool complete = true ;
if ( m_filters . empty ()
2022-01-03 23:16:39 +01:00
|| m_filters [ 0 ]. empty ()
2020-09-08 15:53:08 +02:00
|| std :: find ( m_filters . begin (), m_filters . end (), m_trimmed_name ) != m_filters . end ()) {
complete = TrackerBase :: isComplete ();
}
return complete ;
}
bool SectionTracker :: isSectionTracker () const { return true ; }
SectionTracker & SectionTracker :: acquire ( TrackerContext & ctx , NameAndLocation const & nameAndLocation ) {
2022-01-03 23:16:39 +01:00
SectionTracker * section ;
2020-09-08 15:53:08 +02:00
ITracker & currentTracker = ctx . currentTracker ();
2022-01-03 23:16:39 +01:00
if ( ITracker * childTracker =
currentTracker . findChild ( nameAndLocation ) ) {
2020-09-08 15:53:08 +02:00
assert ( childTracker );
assert ( childTracker -> isSectionTracker () );
2022-01-03 23:16:39 +01:00
section = static_cast < SectionTracker *> ( childTracker );
} else {
auto newSection = Catch :: Detail :: make_unique < SectionTracker > (
nameAndLocation , ctx , & currentTracker );
section = newSection . get ();
currentTracker . addChild ( CATCH_MOVE ( newSection ) );
2020-09-08 15:53:08 +02:00
}
if ( ! ctx . completedCycle () )
section -> tryOpen ();
return * section ;
}
void SectionTracker :: tryOpen () {
if ( ! isComplete () )
open ();
}
void SectionTracker :: addInitialFilters ( std :: vector < std :: string > const & filters ) {
if ( ! filters . empty () ) {
m_filters . reserve ( m_filters . size () + filters . size () + 2 );
2022-01-03 23:16:39 +01:00
m_filters . emplace_back ( StringRef {}); // Root - should never be consulted
m_filters . emplace_back ( StringRef {}); // Test Case - not a section filter
2020-09-08 15:53:08 +02:00
m_filters . insert ( m_filters . end (), filters . begin (), filters . end () );
}
}
2022-01-03 23:16:39 +01:00
void SectionTracker :: addNextFilters ( std :: vector < StringRef > const & filters ) {
2020-09-08 15:53:08 +02:00
if ( filters . size () > 1 )
m_filters . insert ( m_filters . end (), filters . begin () + 1 , filters . end () );
}
2022-01-03 23:16:39 +01:00
std :: vector < StringRef > const & SectionTracker :: getFilters () const {
return m_filters ;
}
2020-09-08 15:53:08 +02:00
2022-01-03 23:16:39 +01:00
StringRef SectionTracker :: trimmedName () const {
return m_trimmed_name ;
}
} // namespace TestCaseTracking
2020-09-08 15:53:08 +02:00
} // namespace Catch
#if defined(__clang__)
# pragma clang diagnostic pop
#endif
2022-01-03 23:16:39 +01:00
#include <algorithm>
#include <iterator>
2020-09-08 15:53:08 +02:00
namespace Catch {
2022-01-03 23:16:39 +01:00
namespace {
StringRef extractClassName ( StringRef classOrMethodName ) {
if ( ! startsWith ( classOrMethodName , '&' ) ) {
return classOrMethodName ;
}
// Remove the leading '&' to avoid having to special case it later
const auto methodName =
classOrMethodName . substr ( 1 , classOrMethodName . size () );
auto reverseStart = std :: make_reverse_iterator ( methodName . end () );
auto reverseEnd = std :: make_reverse_iterator ( methodName . begin () );
// We make a simplifying assumption that ":" is only present
// in the input as part of "::" from C++ typenames (this is
// relatively safe assumption because the input is generated
// as stringification of type through preprocessor).
auto lastColons = std :: find ( reverseStart , reverseEnd , ':' ) + 1 ;
auto secondLastColons =
std :: find ( lastColons + 1 , reverseEnd , ':' );
auto const startIdx = reverseEnd - secondLastColons ;
auto const classNameSize = secondLastColons - lastColons - 1 ;
return methodName . substr (
static_cast < std :: size_t > ( startIdx ),
static_cast < std :: size_t > ( classNameSize ) );
}
} // namespace
2020-09-08 15:53:08 +02:00
Detail :: unique_ptr < ITestInvoker > makeTestInvoker ( void ( * testAsFunction )() ) {
2022-01-03 23:16:39 +01:00
return Detail :: make_unique < TestInvokerAsFunction > ( testAsFunction );
2020-09-08 15:53:08 +02:00
}
2022-01-03 23:16:39 +01:00
AutoReg :: AutoReg ( Detail :: unique_ptr < ITestInvoker > invoker , SourceLineInfo const & lineInfo , StringRef classOrMethod , NameAndTags const & nameAndTags ) noexcept {
2020-09-08 15:53:08 +02:00
CATCH_TRY {
getMutableRegistryHub ()
. registerTest (
makeTestCaseInfo (
extractClassName ( classOrMethod ),
nameAndTags ,
lineInfo ),
2022-01-03 23:16:39 +01:00
CATCH_MOVE ( invoker )
2020-09-08 15:53:08 +02:00
);
} CATCH_CATCH_ALL {
// Do not throw when constructing global objects, instead register the exception to be processed later
getMutableRegistryHub (). registerStartupException ();
}
}
}
namespace Catch {
TestSpecParser :: TestSpecParser ( ITagAliasRegistry const & tagAliases ) : m_tagAliases ( & tagAliases ) {}
TestSpecParser & TestSpecParser :: parse ( std :: string const & arg ) {
m_mode = None ;
m_exclusion = false ;
m_arg = m_tagAliases -> expandAliases ( arg );
m_escapeChars . clear ();
m_substring . reserve ( m_arg . size ());
m_patternName . reserve ( m_arg . size ());
m_realPatternPos = 0 ;
for ( m_pos = 0 ; m_pos < m_arg . size (); ++ m_pos )
//if visitChar fails
if ( ! visitChar ( m_arg [ m_pos ] ) ){
2022-01-03 23:16:39 +01:00
m_testSpec . m_invalidSpecs . push_back ( arg );
2020-09-08 15:53:08 +02:00
break ;
}
endMode ();
return * this ;
}
TestSpec TestSpecParser :: testSpec () {
addFilter ();
2022-01-03 23:16:39 +01:00
return CATCH_MOVE ( m_testSpec );
2020-09-08 15:53:08 +02:00
}
bool TestSpecParser :: visitChar ( char c ) {
if ( ( m_mode != EscapedName ) && ( c == '\\' ) ) {
escape ();
addCharToPattern ( c );
return true ;
} else if (( m_mode != EscapedName ) && ( c == ',' ) ) {
return separate ();
}
switch ( m_mode ) {
case None :
if ( processNoneChar ( c ) )
return true ;
break ;
case Name :
processNameChar ( c );
break ;
case EscapedName :
endMode ();
addCharToPattern ( c );
return true ;
default :
case Tag :
case QuotedName :
if ( processOtherChar ( c ) )
return true ;
break ;
}
m_substring += c ;
if ( ! isControlChar ( c ) ) {
m_patternName += c ;
m_realPatternPos ++ ;
}
return true ;
}
// Two of the processing methods return true to signal the caller to return
// without adding the given character to the current pattern strings
bool TestSpecParser :: processNoneChar ( char c ) {
switch ( c ) {
case ' ' :
return true ;
case '~' :
m_exclusion = true ;
return false ;
case '[' :
startNewMode ( Tag );
return false ;
case '"' :
startNewMode ( QuotedName );
return false ;
default :
startNewMode ( Name );
return false ;
}
}
void TestSpecParser :: processNameChar ( char c ) {
if ( c == '[' ) {
if ( m_substring == "exclude:" )
m_exclusion = true ;
else
endMode ();
startNewMode ( Tag );
}
}
bool TestSpecParser :: processOtherChar ( char c ) {
if ( ! isControlChar ( c ) )
return false ;
m_substring += c ;
endMode ();
return true ;
}
void TestSpecParser :: startNewMode ( Mode mode ) {
m_mode = mode ;
}
void TestSpecParser :: endMode () {
switch ( m_mode ) {
case Name :
case QuotedName :
return addNamePattern ();
case Tag :
return addTagPattern ();
case EscapedName :
revertBackToLastMode ();
return ;
case None :
default :
return startNewMode ( None );
}
}
void TestSpecParser :: escape () {
saveLastMode ();
m_mode = EscapedName ;
m_escapeChars . push_back ( m_realPatternPos );
}
bool TestSpecParser :: isControlChar ( char c ) const {
switch ( m_mode ) {
default :
return false ;
case None :
return c == '~' ;
case Name :
return c == '[' ;
case EscapedName :
return true ;
case QuotedName :
return c == '"' ;
case Tag :
return c == '[' || c == ']' ;
}
}
void TestSpecParser :: addFilter () {
if ( ! m_currentFilter . m_required . empty () || ! m_currentFilter . m_forbidden . empty () ) {
2022-01-03 23:16:39 +01:00
m_testSpec . m_filters . push_back ( CATCH_MOVE ( m_currentFilter ) );
2020-09-08 15:53:08 +02:00
m_currentFilter = TestSpec :: Filter ();
}
}
void TestSpecParser :: saveLastMode () {
lastMode = m_mode ;
}
void TestSpecParser :: revertBackToLastMode () {
m_mode = lastMode ;
}
bool TestSpecParser :: separate () {
if ( ( m_mode == QuotedName ) || ( m_mode == Tag ) ){
//invalid argument, signal failure to previous scope.
m_mode = None ;
m_pos = m_arg . size ();
m_substring . clear ();
m_patternName . clear ();
m_realPatternPos = 0 ;
return false ;
}
endMode ();
addFilter ();
return true ; //success
}
std :: string TestSpecParser :: preprocessPattern () {
std :: string token = m_patternName ;
for ( std :: size_t i = 0 ; i < m_escapeChars . size (); ++ i )
token = token . substr ( 0 , m_escapeChars [ i ] - i ) + token . substr ( m_escapeChars [ i ] - i + 1 );
m_escapeChars . clear ();
if ( startsWith ( token , "exclude:" )) {
m_exclusion = true ;
token = token . substr ( 8 );
}
m_patternName . clear ();
m_realPatternPos = 0 ;
return token ;
}
void TestSpecParser :: addNamePattern () {
auto token = preprocessPattern ();
if ( ! token . empty ()) {
if ( m_exclusion ) {
m_currentFilter . m_forbidden . emplace_back ( Detail :: make_unique < TestSpec :: NamePattern > ( token , m_substring ));
} else {
m_currentFilter . m_required . emplace_back ( Detail :: make_unique < TestSpec :: NamePattern > ( token , m_substring ));
}
}
m_substring . clear ();
m_exclusion = false ;
m_mode = None ;
}
void TestSpecParser :: addTagPattern () {
auto token = preprocessPattern ();
if ( ! token . empty ()) {
// If the tag pattern is the "hide and tag" shorthand (e.g. [.foo])
// we have to create a separate hide tag and shorten the real one
if ( token . size () > 1 && token [ 0 ] == '.' ) {
token . erase ( token . begin ());
if ( m_exclusion ) {
m_currentFilter . m_forbidden . emplace_back ( Detail :: make_unique < TestSpec :: TagPattern > ( "." , m_substring ));
m_currentFilter . m_forbidden . emplace_back ( Detail :: make_unique < TestSpec :: TagPattern > ( token , m_substring ));
} else {
m_currentFilter . m_required . emplace_back ( Detail :: make_unique < TestSpec :: TagPattern > ( "." , m_substring ));
m_currentFilter . m_required . emplace_back ( Detail :: make_unique < TestSpec :: TagPattern > ( token , m_substring ));
}
}
if ( m_exclusion ) {
m_currentFilter . m_forbidden . emplace_back ( Detail :: make_unique < TestSpec :: TagPattern > ( token , m_substring ));
} else {
m_currentFilter . m_required . emplace_back ( Detail :: make_unique < TestSpec :: TagPattern > ( token , m_substring ));
}
}
m_substring . clear ();
m_exclusion = false ;
m_mode = None ;
}
TestSpec parseTestSpec ( std :: string const & arg ) {
return TestSpecParser ( ITagAliasRegistry :: get () ). parse ( arg ). testSpec ();
}
} // namespace Catch
2022-01-03 23:16:39 +01:00
#include <algorithm>
2020-09-08 15:53:08 +02:00
#include <cstring>
#include <ostream>
namespace {
bool isWhitespace ( char c ) {
return c == ' ' || c == '\t' || c == '\n' || c == '\r' ;
}
bool isBreakableBefore ( char c ) {
static const char chars [] = "[({<|" ;
return std :: memchr ( chars , c , sizeof ( chars ) - 1 ) != nullptr ;
}
bool isBreakableAfter ( char c ) {
static const char chars [] = "])}>.,:;*+-=&/ \\ " ;
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 {
2022-01-03 23:16:39 +01:00
void Column :: const_iterator :: calcLength () {
m_addHyphen = false ;
m_parsedTo = m_lineStart ;
2020-09-08 15:53:08 +02:00
std :: string const & current_line = m_column . m_string ;
2022-01-03 23:16:39 +01:00
if ( current_line [ m_lineStart ] == '\n' ) {
++ m_parsedTo ;
2020-09-08 15:53:08 +02:00
}
2022-01-03 23:16:39 +01:00
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' ) {
++ m_parsedTo ;
}
// 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 ;
2020-09-08 15:53:08 +02:00
} else {
2022-01-03 23:16:39 +01:00
// 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 ;
2020-09-08 15:53:08 +02:00
}
2022-01-03 23:16:39 +01:00
while ( newLineLength > 0 &&
isWhitespace ( current_line [ m_lineStart + newLineLength - 1 ] ) ) {
-- newLineLength ;
2020-09-08 15:53:08 +02:00
}
2022-01-03 23:16:39 +01:00
// If we found one, then that is where we linebreak
if ( newLineLength > 0 ) {
m_lineLength = newLineLength ;
2020-09-08 15:53:08 +02:00
} else {
2022-01-03 23:16:39 +01:00
// Otherwise we have to split text with a hyphen
m_addHyphen = true ;
m_lineLength = maxLineLength - 1 ;
2020-09-08 15:53:08 +02:00
}
}
}
2022-01-03 23:16:39 +01:00
size_t Column :: const_iterator :: indentSize () const {
2020-09-08 15:53:08 +02:00
auto initial =
2022-01-03 23:16:39 +01:00
m_lineStart == 0 ? m_column . m_initialIndent : std :: string :: npos ;
2020-09-08 15:53:08 +02:00
return initial == std :: string :: npos ? m_column . m_indent : initial ;
}
std :: string
2022-01-03 23:16:39 +01:00
Column :: const_iterator :: addIndentAndSuffix ( size_t position ,
2020-09-08 15:53:08 +02:00
size_t length ) const {
std :: string ret ;
2022-01-03 23:16:39 +01:00
const auto desired_indent = indentSize ();
ret . reserve ( desired_indent + length + m_addHyphen );
2020-09-08 15:53:08 +02:00
ret . append ( desired_indent , ' ' );
ret . append ( m_column . m_string , position , length );
2022-01-03 23:16:39 +01:00
if ( m_addHyphen ) {
2020-09-08 15:53:08 +02:00
ret . push_back ( '-' );
}
return ret ;
}
2022-01-03 23:16:39 +01:00
Column :: const_iterator :: const_iterator ( Column const & column ) : m_column ( column ) {
2020-09-08 15:53:08 +02:00
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 ();
2022-01-03 23:16:39 +01:00
if ( m_lineLength == 0 ) {
m_lineStart = m_column . m_string . size ();
2020-09-08 15:53:08 +02:00
}
}
2022-01-03 23:16:39 +01:00
std :: string Column :: const_iterator :: operator * () const {
assert ( m_lineStart <= m_parsedTo );
return addIndentAndSuffix ( m_lineStart , m_lineLength );
2020-09-08 15:53:08 +02:00
}
2022-01-03 23:16:39 +01:00
Column :: const_iterator & Column :: const_iterator :: operator ++ () {
m_lineStart += m_lineLength ;
2020-09-08 15:53:08 +02:00
std :: string const & current_line = m_column . m_string ;
2022-01-03 23:16:39 +01:00
if ( m_lineStart < current_line . size () && current_line [ m_lineStart ] == '\n' ) {
m_lineStart += 1 ;
2020-09-08 15:53:08 +02:00
} else {
2022-01-03 23:16:39 +01:00
while ( m_lineStart < current_line . size () &&
isWhitespace ( current_line [ m_lineStart ] ) ) {
++ m_lineStart ;
2020-09-08 15:53:08 +02:00
}
}
2022-01-03 23:16:39 +01:00
if ( m_lineStart != current_line . size () ) {
2020-09-08 15:53:08 +02:00
calcLength ();
}
return * this ;
}
2022-01-03 23:16:39 +01:00
Column :: const_iterator Column :: const_iterator :: operator ++ ( int ) {
const_iterator prev ( * this );
2020-09-08 15:53:08 +02:00
operator ++ ();
return prev ;
}
std :: ostream & operator << ( std :: ostream & os , Column const & col ) {
bool first = true ;
for ( auto line : col ) {
if ( first ) {
first = false ;
} else {
os << '\n' ;
}
os << line ;
}
return os ;
}
Column Spacer ( size_t spaceWidth ) {
Column ret { "" };
ret . width ( spaceWidth );
return ret ;
}
Columns :: iterator :: iterator ( Columns const & columns , EndTag ) :
m_columns ( columns . m_columns ), m_activeIterators ( 0 ) {
m_iterators . reserve ( m_columns . size () );
for ( auto const & col : m_columns ) {
m_iterators . push_back ( col . end () );
}
}
Columns :: iterator :: iterator ( Columns const & columns ) :
m_columns ( columns . m_columns ),
m_activeIterators ( m_columns . size () ) {
m_iterators . reserve ( m_columns . size () );
for ( auto const & col : m_columns ) {
m_iterators . push_back ( col . begin () );
}
}
std :: string Columns :: iterator :: operator * () const {
std :: string row , padding ;
for ( size_t i = 0 ; i < m_columns . size (); ++ i ) {
const auto width = m_columns [ i ]. width ();
if ( m_iterators [ i ] != m_columns [ i ]. end () ) {
std :: string col = * m_iterators [ i ];
row += padding ;
row += col ;
padding . clear ();
if ( col . size () < width ) {
padding . append ( width - col . size (), ' ' );
}
} else {
padding . append ( width , ' ' );
}
}
return row ;
}
Columns :: iterator & Columns :: iterator :: operator ++ () {
for ( size_t i = 0 ; i < m_columns . size (); ++ i ) {
if ( m_iterators [ i ] != m_columns [ i ]. end () ) {
++ m_iterators [ i ];
}
}
return * this ;
}
Columns :: iterator Columns :: iterator :: operator ++ ( int ) {
iterator prev ( * this );
operator ++ ();
return prev ;
}
std :: ostream & operator << ( std :: ostream & os , Columns const & cols ) {
bool first = true ;
for ( auto line : cols ) {
if ( first ) {
first = false ;
} else {
os << '\n' ;
}
os << line ;
}
return os ;
}
Columns Column :: operator + ( Column const & other ) {
Columns cols ;
cols += * this ;
cols += other ;
return cols ;
}
Columns & Columns :: operator += ( Column const & col ) {
m_columns . push_back ( col );
return * this ;
}
Columns Columns :: operator + ( Column const & col ) {
Columns combined = * this ;
combined += col ;
return combined ;
}
} // namespace TextFlow
} // namespace Catch
namespace Catch {
WildcardPattern :: WildcardPattern ( std :: string const & pattern ,
CaseSensitive caseSensitivity )
: m_caseSensitivity ( caseSensitivity ),
m_pattern ( normaliseString ( pattern ) )
{
if ( startsWith ( m_pattern , '*' ) ) {
m_pattern = m_pattern . substr ( 1 );
m_wildcard = WildcardAtStart ;
}
if ( endsWith ( m_pattern , '*' ) ) {
m_pattern = m_pattern . substr ( 0 , m_pattern . size () - 1 );
m_wildcard = static_cast < WildcardPosition > ( m_wildcard | WildcardAtEnd );
}
}
bool WildcardPattern :: matches ( std :: string const & str ) const {
switch ( m_wildcard ) {
case NoWildcard :
return m_pattern == normaliseString ( str );
case WildcardAtStart :
return endsWith ( normaliseString ( str ), m_pattern );
case WildcardAtEnd :
return startsWith ( normaliseString ( str ), m_pattern );
case WildcardAtBothEnds :
return contains ( normaliseString ( str ), m_pattern );
default :
CATCH_INTERNAL_ERROR ( "Unknown enum" );
}
}
std :: string WildcardPattern :: normaliseString ( std :: string const & str ) const {
return trim ( m_caseSensitivity == CaseSensitive :: No ? toLower ( str ) : str );
}
}
2022-01-03 23:16:39 +01:00
// Note: swapping these two includes around causes MSVC to error out
// while in /permissive- mode. No, I don't know why.
// Tested on VS 2019, 18.{3, 4}.x
2020-09-08 15:53:08 +02:00
#include <iomanip>
#include <type_traits>
namespace Catch {
namespace {
size_t trailingBytes ( unsigned char c ) {
if (( c & 0xE0 ) == 0xC0 ) {
return 2 ;
}
if (( c & 0xF0 ) == 0xE0 ) {
return 3 ;
}
if (( c & 0xF8 ) == 0xF0 ) {
return 4 ;
}
CATCH_INTERNAL_ERROR ( "Invalid multibyte utf-8 start byte encountered" );
}
uint32_t headerValue ( unsigned char c ) {
if (( c & 0xE0 ) == 0xC0 ) {
return c & 0x1F ;
}
if (( c & 0xF0 ) == 0xE0 ) {
return c & 0x0F ;
}
if (( c & 0xF8 ) == 0xF0 ) {
return c & 0x07 ;
}
CATCH_INTERNAL_ERROR ( "Invalid multibyte utf-8 start byte encountered" );
}
void hexEscapeChar ( std :: ostream & os , unsigned char c ) {
std :: ios_base :: fmtflags f ( os . flags ());
os << " \\ x"
<< std :: uppercase << std :: hex << std :: setfill ( '0' ) << std :: setw ( 2 )
<< static_cast < int > ( c );
os . flags ( f );
}
bool shouldNewline ( XmlFormatting fmt ) {
return !! ( static_cast < std :: underlying_type_t < XmlFormatting >> ( fmt & XmlFormatting :: Newline ));
}
bool shouldIndent ( XmlFormatting fmt ) {
return !! ( static_cast < std :: underlying_type_t < XmlFormatting >> ( fmt & XmlFormatting :: Indent ));
}
} // anonymous namespace
XmlFormatting operator | ( XmlFormatting lhs , XmlFormatting rhs ) {
return static_cast < XmlFormatting > (
static_cast < std :: underlying_type_t < XmlFormatting >> ( lhs ) |
static_cast < std :: underlying_type_t < XmlFormatting >> ( rhs )
);
}
XmlFormatting operator & ( XmlFormatting lhs , XmlFormatting rhs ) {
return static_cast < XmlFormatting > (
static_cast < std :: underlying_type_t < XmlFormatting >> ( lhs ) &
static_cast < std :: underlying_type_t < XmlFormatting >> ( rhs )
);
}
2022-01-03 23:16:39 +01:00
XmlEncode :: XmlEncode ( StringRef str , ForWhat forWhat )
2020-09-08 15:53:08 +02:00
: m_str ( str ),
m_forWhat ( forWhat )
{}
void XmlEncode :: encodeTo ( std :: ostream & os ) const {
// Apostrophe escaping not necessary if we always use " to write attributes
// (see: http://www.w3.org/TR/xml/#syntax)
for ( std :: size_t idx = 0 ; idx < m_str . size (); ++ idx ) {
2022-01-03 23:16:39 +01:00
unsigned char c = static_cast < unsigned char > ( m_str [ idx ]);
2020-09-08 15:53:08 +02:00
switch ( c ) {
case '<' : os << "<" ; break ;
case '&' : os << "&" ; break ;
case '>' :
// See: http://www.w3.org/TR/xml/#syntax
if ( idx > 2 && m_str [ idx - 1 ] == ']' && m_str [ idx - 2 ] == ']' )
os << ">" ;
else
os << c ;
break ;
case '\"' :
if ( m_forWhat == ForAttributes )
os << """ ;
else
os << c ;
break ;
default :
// Check for control characters and invalid utf-8
// Escape control characters in standard ascii
// see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0
if ( c < 0x09 || ( c > 0x0D && c < 0x20 ) || c == 0x7F ) {
hexEscapeChar ( os , c );
break ;
}
// Plain ASCII: Write it to stream
if ( c < 0x7F ) {
os << c ;
break ;
}
// UTF-8 territory
// Check if the encoding is valid and if it is not, hex escape bytes.
// Important: We do not check the exact decoded values for validity, only the encoding format
// First check that this bytes is a valid lead byte:
// This means that it is not encoded as 1111 1XXX
// Or as 10XX XXXX
if ( c < 0xC0 ||
c >= 0xF8 ) {
hexEscapeChar ( os , c );
break ;
}
auto encBytes = trailingBytes ( c );
// Are there enough bytes left to avoid accessing out-of-bounds memory?
if ( idx + encBytes - 1 >= m_str . size ()) {
hexEscapeChar ( os , c );
break ;
}
// The header is valid, check data
// The next encBytes bytes must together be a valid utf-8
// This means: bitpattern 10XX XXXX and the extracted value is sane (ish)
bool valid = true ;
uint32_t value = headerValue ( c );
for ( std :: size_t n = 1 ; n < encBytes ; ++ n ) {
2022-01-03 23:16:39 +01:00
unsigned char nc = static_cast < unsigned char > ( m_str [ idx + n ]);
2020-09-08 15:53:08 +02:00
valid &= (( nc & 0xC0 ) == 0x80 );
value = ( value << 6 ) | ( nc & 0x3F );
}
if (
// Wrong bit pattern of following bytes
( ! valid ) ||
// Overlong encodings
( value < 0x80 ) ||
( 0x80 <= value && value < 0x800 && encBytes > 2 ) ||
( 0x800 < value && value < 0x10000 && encBytes > 3 ) ||
// Encoded value out of range
( value >= 0x110000 )
) {
hexEscapeChar ( os , c );
break ;
}
// If we got here, this is in fact a valid(ish) utf-8 sequence
for ( std :: size_t n = 0 ; n < encBytes ; ++ n ) {
os << m_str [ idx + n ];
}
idx += encBytes - 1 ;
break ;
}
}
}
std :: ostream & operator << ( std :: ostream & os , XmlEncode const & xmlEncode ) {
xmlEncode . encodeTo ( os );
return os ;
}
XmlWriter :: ScopedElement :: ScopedElement ( XmlWriter * writer , XmlFormatting fmt )
: m_writer ( writer ),
m_fmt ( fmt )
{}
XmlWriter :: ScopedElement :: ScopedElement ( ScopedElement && other ) noexcept
: m_writer ( other . m_writer ),
m_fmt ( other . m_fmt )
{
other . m_writer = nullptr ;
other . m_fmt = XmlFormatting :: None ;
}
XmlWriter :: ScopedElement & XmlWriter :: ScopedElement :: operator = ( ScopedElement && other ) noexcept {
if ( m_writer ) {
m_writer -> endElement ();
}
m_writer = other . m_writer ;
other . m_writer = nullptr ;
m_fmt = other . m_fmt ;
other . m_fmt = XmlFormatting :: None ;
return * this ;
}
XmlWriter :: ScopedElement ::~ ScopedElement () {
if ( m_writer ) {
m_writer -> endElement ( m_fmt );
}
}
2022-01-03 23:16:39 +01:00
XmlWriter :: ScopedElement &
XmlWriter :: ScopedElement :: writeText ( StringRef text , XmlFormatting fmt ) {
2020-09-08 15:53:08 +02:00
m_writer -> writeText ( text , fmt );
return * this ;
}
2022-01-03 23:16:39 +01:00
XmlWriter :: ScopedElement &
XmlWriter :: ScopedElement :: writeAttribute ( StringRef name ,
StringRef attribute ) {
m_writer -> writeAttribute ( name , attribute );
return * this ;
}
2020-09-08 15:53:08 +02:00
XmlWriter :: XmlWriter ( std :: ostream & os ) : m_os ( os )
{
writeDeclaration ();
}
XmlWriter ::~ XmlWriter () {
while ( ! m_tags . empty ()) {
endElement ();
}
newlineIfNecessary ();
}
XmlWriter & XmlWriter :: startElement ( std :: string const & name , XmlFormatting fmt ) {
ensureTagClosed ();
newlineIfNecessary ();
if ( shouldIndent ( fmt )) {
m_os << m_indent ;
m_indent += " " ;
}
m_os << '<' << name ;
m_tags . push_back ( name );
m_tagIsOpen = true ;
applyFormatting ( fmt );
return * this ;
}
XmlWriter :: ScopedElement XmlWriter :: scopedElement ( std :: string const & name , XmlFormatting fmt ) {
ScopedElement scoped ( this , fmt );
startElement ( name , fmt );
return scoped ;
}
XmlWriter & XmlWriter :: endElement ( XmlFormatting fmt ) {
m_indent = m_indent . substr ( 0 , m_indent . size () - 2 );
if ( m_tagIsOpen ) {
m_os << "/>" ;
m_tagIsOpen = false ;
} else {
newlineIfNecessary ();
if ( shouldIndent ( fmt )) {
m_os << m_indent ;
}
2022-01-03 23:16:39 +01:00
m_os << "</" << m_tags . back () << '>' ;
2020-09-08 15:53:08 +02:00
}
m_os << std :: flush ;
applyFormatting ( fmt );
m_tags . pop_back ();
return * this ;
}
2022-01-03 23:16:39 +01:00
XmlWriter & XmlWriter :: writeAttribute ( StringRef name ,
StringRef attribute ) {
2020-09-08 15:53:08 +02:00
if ( ! name . empty () && ! attribute . empty () )
m_os << ' ' << name << "= \" " << XmlEncode ( attribute , XmlEncode :: ForAttributes ) << '"' ;
return * this ;
}
2022-01-03 23:16:39 +01:00
XmlWriter & XmlWriter :: writeAttribute ( StringRef name , bool attribute ) {
writeAttribute ( name , ( attribute ? "true" _sr : "false" _sr ));
2020-09-08 15:53:08 +02:00
return * this ;
}
2022-01-03 23:16:39 +01:00
XmlWriter & XmlWriter :: writeAttribute ( StringRef name ,
char const * attribute ) {
writeAttribute ( name , StringRef ( attribute ) );
return * this ;
}
XmlWriter & XmlWriter :: writeText ( StringRef text , XmlFormatting fmt ) {
CATCH_ENFORCE ( ! m_tags . empty (), "Cannot write text as top level element" );
2020-09-08 15:53:08 +02:00
if ( ! text . empty () ){
bool tagWasOpen = m_tagIsOpen ;
ensureTagClosed ();
if ( tagWasOpen && shouldIndent ( fmt )) {
m_os << m_indent ;
}
2022-01-03 23:16:39 +01:00
m_os << XmlEncode ( text , XmlEncode :: ForTextNodes );
2020-09-08 15:53:08 +02:00
applyFormatting ( fmt );
}
return * this ;
}
2022-01-03 23:16:39 +01:00
XmlWriter & XmlWriter :: writeComment ( StringRef text , XmlFormatting fmt ) {
2020-09-08 15:53:08 +02:00
ensureTagClosed ();
if ( shouldIndent ( fmt )) {
m_os << m_indent ;
}
2022-01-03 23:16:39 +01:00
m_os << "<!-- " << text << " -->" ;
2020-09-08 15:53:08 +02:00
applyFormatting ( fmt );
return * this ;
}
2022-01-03 23:16:39 +01:00
void XmlWriter :: writeStylesheetRef ( StringRef url ) {
m_os << R "(<?xml-stylesheet type=" text / xsl " href=" ) " << url << R" ( "?>)" << '\n' ;
2020-09-08 15:53:08 +02:00
}
void XmlWriter :: ensureTagClosed () {
if ( m_tagIsOpen ) {
m_os << '>' << std :: flush ;
newlineIfNecessary ();
m_tagIsOpen = false ;
}
}
void XmlWriter :: applyFormatting ( XmlFormatting fmt ) {
m_needsNewline = shouldNewline ( fmt );
}
void XmlWriter :: writeDeclaration () {
2022-01-03 23:16:39 +01:00
m_os << R "(<?xml version=" 1.0 " encoding=" UTF - 8 "?>)" << '\n' ;
2020-09-08 15:53:08 +02:00
}
void XmlWriter :: newlineIfNecessary () {
if ( m_needsNewline ) {
2022-01-03 23:16:39 +01:00
m_os << '\n' << std :: flush ;
2020-09-08 15:53:08 +02:00
m_needsNewline = false ;
}
}
}
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <cstdint>
#include <sstream>
#include <iomanip>
#include <limits>
namespace Catch {
namespace {
template < typename FP >
bool almostEqualUlps ( FP lhs , FP rhs , uint64_t maxUlpDiff ) {
// Comparison with NaN should always be false.
// This way we can rule it out before getting into the ugly details
if ( Catch :: isnan ( lhs ) || Catch :: isnan ( rhs )) {
return false ;
}
2022-01-03 23:16:39 +01:00
// This should also handle positive and negative zeros, infinities
const auto ulpDist = ulpDistance ( lhs , rhs );
2020-09-08 15:53:08 +02:00
2022-01-03 23:16:39 +01:00
return ulpDist <= maxUlpDiff ;
2020-09-08 15:53:08 +02:00
}
#if defined(CATCH_CONFIG_GLOBAL_NEXTAFTER)
float nextafter ( float x , float y ) {
return :: nextafterf ( x , y );
}
double nextafter ( double x , double y ) {
return :: nextafter ( x , y );
}
#endif // ^^^ CATCH_CONFIG_GLOBAL_NEXTAFTER ^^^
template < typename FP >
FP step ( FP start , FP direction , uint64_t steps ) {
for ( uint64_t i = 0 ; i < steps ; ++ i ) {
#if defined(CATCH_CONFIG_GLOBAL_NEXTAFTER)
start = Catch :: nextafter ( start , direction );
#else
start = std :: nextafter ( start , direction );
#endif
}
return start ;
}
// Performs equivalent check of std::fabs(lhs - rhs) <= margin
// But without the subtraction to allow for INFINITY in comparison
bool marginComparison ( double lhs , double rhs , double margin ) {
return ( lhs + margin >= rhs ) && ( rhs + margin >= lhs );
}
template < typename FloatingPoint >
void write ( std :: ostream & out , FloatingPoint num ) {
out << std :: scientific
<< std :: setprecision ( std :: numeric_limits < FloatingPoint >:: max_digits10 - 1 )
<< num ;
}
} // end anonymous namespace
namespace Matchers {
namespace Detail {
enum class FloatingPointKind : uint8_t {
Float ,
Double
};
} // end namespace Detail
WithinAbsMatcher :: WithinAbsMatcher ( double target , double margin )
: m_target { target }, m_margin { margin } {
CATCH_ENFORCE ( margin >= 0 , "Invalid margin: " << margin << '.'
<< " Margin has to be non-negative." );
}
// Performs equivalent check of std::fabs(lhs - rhs) <= margin
// But without the subtraction to allow for INFINITY in comparison
bool WithinAbsMatcher :: match ( double const & matchee ) const {
return ( matchee + m_margin >= m_target ) && ( m_target + m_margin >= matchee );
}
std :: string WithinAbsMatcher :: describe () const {
return "is within " + :: Catch :: Detail :: stringify ( m_margin ) + " of " + :: Catch :: Detail :: stringify ( m_target );
}
WithinUlpsMatcher :: WithinUlpsMatcher ( double target , uint64_t ulps , Detail :: FloatingPointKind baseType )
: m_target { target }, m_ulps { ulps }, m_type { baseType } {
CATCH_ENFORCE ( m_type == Detail :: FloatingPointKind :: Double
|| m_ulps < ( std :: numeric_limits < uint32_t >:: max )(),
"Provided ULP is impossibly large for a float comparison." );
2022-01-03 23:16:39 +01:00
CATCH_ENFORCE ( std :: numeric_limits < double >:: is_iec559 ,
"WithinUlp matcher only supports platforms with "
"IEEE-754 compatible floating point representation" );
2020-09-08 15:53:08 +02:00
}
#if defined(__clang__)
#pragma clang diagnostic push
// Clang <3.5 reports on the default branch in the switch below
#pragma clang diagnostic ignored "-Wunreachable-code"
#endif
bool WithinUlpsMatcher :: match ( double const & matchee ) const {
switch ( m_type ) {
case Detail :: FloatingPointKind :: Float :
return almostEqualUlps < float > ( static_cast < float > ( matchee ), static_cast < float > ( m_target ), m_ulps );
case Detail :: FloatingPointKind :: Double :
return almostEqualUlps < double > ( matchee , m_target , m_ulps );
default :
CATCH_INTERNAL_ERROR ( "Unknown Detail::FloatingPointKind value" );
}
}
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
std :: string WithinUlpsMatcher :: describe () const {
std :: stringstream ret ;
ret << "is within " << m_ulps << " ULPs of " ;
if ( m_type == Detail :: FloatingPointKind :: Float ) {
write ( ret , static_cast < float > ( m_target ));
ret << 'f' ;
} else {
write ( ret , m_target );
}
ret << " ([" ;
if ( m_type == Detail :: FloatingPointKind :: Double ) {
write ( ret , step ( m_target , static_cast < double > ( - INFINITY ), m_ulps ));
ret << ", " ;
write ( ret , step ( m_target , static_cast < double > ( INFINITY ), m_ulps ));
} else {
// We have to cast INFINITY to float because of MinGW, see #1782
write ( ret , step ( static_cast < float > ( m_target ), static_cast < float > ( - INFINITY ), m_ulps ));
ret << ", " ;
write ( ret , step ( static_cast < float > ( m_target ), static_cast < float > ( INFINITY ), m_ulps ));
}
ret << "])" ;
return ret . str ();
}
WithinRelMatcher :: WithinRelMatcher ( double target , double epsilon ) :
m_target ( target ),
m_epsilon ( epsilon ){
CATCH_ENFORCE ( m_epsilon >= 0. , "Relative comparison with epsilon < 0 does not make sense." );
CATCH_ENFORCE ( m_epsilon < 1. , "Relative comparison with epsilon >= 1 does not make sense." );
}
bool WithinRelMatcher :: match ( double const & matchee ) const {
const auto relMargin = m_epsilon * ( std :: max )( std :: fabs ( matchee ), std :: fabs ( m_target ));
return marginComparison ( matchee , m_target ,
std :: isinf ( relMargin ) ? 0 : relMargin );
}
std :: string WithinRelMatcher :: describe () const {
Catch :: ReusableStringStream sstr ;
sstr << "and " << m_target << " are within " << m_epsilon * 100. << "% of each other" ;
return sstr . str ();
}
WithinUlpsMatcher WithinULP ( double target , uint64_t maxUlpDiff ) {
return WithinUlpsMatcher ( target , maxUlpDiff , Detail :: FloatingPointKind :: Double );
}
WithinUlpsMatcher WithinULP ( float target , uint64_t maxUlpDiff ) {
return WithinUlpsMatcher ( target , maxUlpDiff , Detail :: FloatingPointKind :: Float );
}
WithinAbsMatcher WithinAbs ( double target , double margin ) {
return WithinAbsMatcher ( target , margin );
}
WithinRelMatcher WithinRel ( double target , double eps ) {
return WithinRelMatcher ( target , eps );
}
WithinRelMatcher WithinRel ( double target ) {
return WithinRelMatcher ( target , std :: numeric_limits < double >:: epsilon () * 100 );
}
WithinRelMatcher WithinRel ( float target , float eps ) {
return WithinRelMatcher ( target , eps );
}
WithinRelMatcher WithinRel ( float target ) {
return WithinRelMatcher ( target , std :: numeric_limits < float >:: epsilon () * 100 );
}
} // namespace Matchers
} // namespace Catch
#include <regex>
namespace Catch {
namespace Matchers {
CasedString :: CasedString ( std :: string const & str , CaseSensitive caseSensitivity )
: m_caseSensitivity ( caseSensitivity ),
m_str ( adjustString ( str ) )
{}
std :: string CasedString :: adjustString ( std :: string const & str ) const {
return m_caseSensitivity == CaseSensitive :: No
? toLower ( str )
: str ;
}
StringRef CasedString :: caseSensitivitySuffix () const {
return m_caseSensitivity == CaseSensitive :: Yes
? StringRef ()
: " (case insensitive)" _sr ;
}
StringMatcherBase :: StringMatcherBase ( std :: string const & operation , CasedString const & comparator )
: m_comparator ( comparator ),
m_operation ( operation ) {
}
std :: string StringMatcherBase :: describe () const {
std :: string description ;
description . reserve ( 5 + m_operation . size () + m_comparator . m_str . size () +
m_comparator . caseSensitivitySuffix (). size ());
description += m_operation ;
description += ": \" " ;
description += m_comparator . m_str ;
2022-01-03 23:16:39 +01:00
description += '"' ;
2020-09-08 15:53:08 +02:00
description += m_comparator . caseSensitivitySuffix ();
return description ;
}
StringEqualsMatcher :: StringEqualsMatcher ( CasedString const & comparator ) : StringMatcherBase ( "equals" , comparator ) {}
bool StringEqualsMatcher :: match ( std :: string const & source ) const {
return m_comparator . adjustString ( source ) == m_comparator . m_str ;
}
StringContainsMatcher :: StringContainsMatcher ( CasedString const & comparator ) : StringMatcherBase ( "contains" , comparator ) {}
bool StringContainsMatcher :: match ( std :: string const & source ) const {
return contains ( m_comparator . adjustString ( source ), m_comparator . m_str );
}
StartsWithMatcher :: StartsWithMatcher ( CasedString const & comparator ) : StringMatcherBase ( "starts with" , comparator ) {}
bool StartsWithMatcher :: match ( std :: string const & source ) const {
return startsWith ( m_comparator . adjustString ( source ), m_comparator . m_str );
}
EndsWithMatcher :: EndsWithMatcher ( CasedString const & comparator ) : StringMatcherBase ( "ends with" , comparator ) {}
bool EndsWithMatcher :: match ( std :: string const & source ) const {
return endsWith ( m_comparator . adjustString ( source ), m_comparator . m_str );
}
2022-01-03 23:16:39 +01:00
RegexMatcher :: RegexMatcher ( std :: string regex , CaseSensitive caseSensitivity ) : m_regex ( CATCH_MOVE ( regex )), m_caseSensitivity ( caseSensitivity ) {}
2020-09-08 15:53:08 +02:00
bool RegexMatcher :: match ( std :: string const & matchee ) const {
auto flags = std :: regex :: ECMAScript ; // ECMAScript is the default syntax option anyway
if ( m_caseSensitivity == CaseSensitive :: No ) {
flags |= std :: regex :: icase ;
}
auto reg = std :: regex ( m_regex , flags );
return std :: regex_match ( matchee , reg );
}
std :: string RegexMatcher :: describe () const {
return "matches " + :: Catch :: Detail :: stringify ( m_regex ) + (( m_caseSensitivity == CaseSensitive :: Yes ) ? " case sensitively" : " case insensitively" );
}
StringEqualsMatcher Equals ( std :: string const & str , CaseSensitive caseSensitivity ) {
return StringEqualsMatcher ( CasedString ( str , caseSensitivity ) );
}
2022-01-03 23:16:39 +01:00
StringContainsMatcher ContainsSubstring ( std :: string const & str , CaseSensitive caseSensitivity ) {
2020-09-08 15:53:08 +02:00
return StringContainsMatcher ( CasedString ( str , caseSensitivity ) );
}
EndsWithMatcher EndsWith ( std :: string const & str , CaseSensitive caseSensitivity ) {
return EndsWithMatcher ( CasedString ( str , caseSensitivity ) );
}
StartsWithMatcher StartsWith ( std :: string const & str , CaseSensitive caseSensitivity ) {
return StartsWithMatcher ( CasedString ( str , caseSensitivity ) );
}
RegexMatcher Matches ( std :: string const & regex , CaseSensitive caseSensitivity ) {
return RegexMatcher ( regex , caseSensitivity );
}
} // namespace Matchers
} // namespace Catch
namespace Catch {
namespace Matchers {
MatcherGenericBase ::~ MatcherGenericBase () = default ;
namespace Detail {
std :: string describe_multi_matcher ( StringRef combine , std :: string const * descriptions_begin , std :: string const * descriptions_end ) {
std :: string description ;
std :: size_t combined_size = 4 ;
for ( auto desc = descriptions_begin ; desc != descriptions_end ; ++ desc ) {
combined_size += desc -> size ();
}
2022-01-03 23:16:39 +01:00
combined_size += static_cast < size_t > ( descriptions_end - descriptions_begin - 1 ) * combine . size ();
2020-09-08 15:53:08 +02:00
description . reserve ( combined_size );
description += "( " ;
bool first = true ;
for ( auto desc = descriptions_begin ; desc != descriptions_end ; ++ desc ) {
if ( first )
first = false ;
else
description += combine ;
description += * desc ;
}
description += " )" ;
return description ;
}
} // namespace Detail
} // namespace Matchers
} // namespace Catch
/** \file
* This is a special TU that combines what would otherwise be a very
* small matcher-related TUs into one bigger TU.
*
* The reason for this is compilation performance improvements by
* avoiding reparsing headers for many small TUs, instead having this
* one TU include bit more, but having it all parsed only once.
*
* To avoid heavy-tail problem with compilation times, each "subpart"
* of Catch2 has its own combined TU like this.
*/
//////////////////////////////////////////////
// vvv formerly catch_matchers_impl.cpp vvv //
//////////////////////////////////////////////
namespace Catch {
// This is the general overload that takes a any string matcher
// There is another overload, in catch_assertionhandler.h/.cpp, that only takes a string and infers
// the Equals matcher (so the header does not mention matchers)
2022-01-03 23:16:39 +01:00
void handleExceptionMatchExpr ( AssertionHandler & handler , StringMatcher const & matcher , StringRef matcherString ) {
2020-09-08 15:53:08 +02:00
std :: string exceptionMessage = Catch :: translateActiveException ();
2022-01-03 23:16:39 +01:00
MatchExpr < std :: string , StringMatcher const &> expr ( CATCH_MOVE ( exceptionMessage ), matcher , matcherString );
2020-09-08 15:53:08 +02:00
handler . handleExpr ( expr );
}
} // namespace Catch
//////////////////////////////////////////////////////////////
// vvv formerly catch_matchers_container_properties.cpp vvv //
//////////////////////////////////////////////////////////////
namespace Catch {
namespace Matchers {
std :: string IsEmptyMatcher :: describe () const {
return "is empty" ;
}
std :: string HasSizeMatcher :: describe () const {
ReusableStringStream sstr ;
sstr << "has size == " << m_target_size ;
return sstr . str ();
}
IsEmptyMatcher IsEmpty () {
return {};
}
HasSizeMatcher SizeIs ( std :: size_t sz ) {
return HasSizeMatcher { sz };
}
} // end namespace Matchers
} // end namespace Catch
/////////////////////////////////////////
// vvv formerly catch_matchers.cpp vvv //
/////////////////////////////////////////
namespace Catch {
namespace Matchers {
std :: string MatcherUntypedBase :: toString () const {
if ( m_cachedToString . empty ()) {
m_cachedToString = describe ();
}
return m_cachedToString ;
}
MatcherUntypedBase ::~ MatcherUntypedBase () = default ;
} // namespace Matchers
} // namespace Catch
///////////////////////////////////////////////////
// vvv formerly catch_matchers_predicate.cpp vvv //
///////////////////////////////////////////////////
std :: string Catch :: Matchers :: Detail :: finalizeDescription ( const std :: string & desc ) {
if ( desc . empty ()) {
return "matches undescribed predicate" ;
} else {
return "matches predicate: \" " + desc + '"' ;
}
}
///////////////////////////////////////////////////
// vvv formerly catch_matchers_exception.cpp vvv //
///////////////////////////////////////////////////
namespace Catch {
namespace Matchers {
bool ExceptionMessageMatcher :: match ( std :: exception const & ex ) const {
return ex . what () == m_message ;
}
std :: string ExceptionMessageMatcher :: describe () const {
2022-01-03 23:16:39 +01:00
return "exception message matches \" " + m_message + '"' ;
2020-09-08 15:53:08 +02:00
}
ExceptionMessageMatcher Message ( std :: string const & message ) {
return ExceptionMessageMatcher ( message );
}
} // namespace Matchers
} // namespace Catch
#include <ostream>
namespace Catch {
AutomakeReporter ::~ AutomakeReporter () {}
void AutomakeReporter :: testCaseEnded ( TestCaseStats const & _testCaseStats ) {
// Possible values to emit are PASS, XFAIL, SKIP, FAIL, XPASS and ERROR.
2022-01-03 23:16:39 +01:00
m_stream << ":test-result: " ;
2020-09-08 15:53:08 +02:00
if ( _testCaseStats . totals . assertions . allPassed ()) {
2022-01-03 23:16:39 +01:00
m_stream << "PASS" ;
2020-09-08 15:53:08 +02:00
} else if ( _testCaseStats . totals . assertions . allOk ()) {
2022-01-03 23:16:39 +01:00
m_stream << "XFAIL" ;
2020-09-08 15:53:08 +02:00
} else {
2022-01-03 23:16:39 +01:00
m_stream << "FAIL" ;
2020-09-08 15:53:08 +02:00
}
2022-01-03 23:16:39 +01:00
m_stream << ' ' << _testCaseStats . testInfo -> name << '\n' ;
2020-09-08 15:53:08 +02:00
StreamingReporterBase :: testCaseEnded ( _testCaseStats );
}
void AutomakeReporter :: skipTest ( TestCaseInfo const & testInfo ) {
2022-01-03 23:16:39 +01:00
m_stream << ":test-result: SKIP " << testInfo . name << '\n' ;
2020-09-08 15:53:08 +02:00
}
} // end namespace Catch
/** \file
* This is a special TU that combines what would otherwise be a very
* small reporter-related TUs into one bigger TU.
*
* The reason for this is compilation performance improvements by
* avoiding reparsing headers for many small TUs, instead having this
* one TU include bit more, but having it all parsed only once.
*
* To avoid heavy-tail problem with compilation times, each "subpart"
* of Catch2 has its own combined TU like this.
*/
2022-01-03 23:16:39 +01:00
#include <algorithm>
2020-09-08 15:53:08 +02:00
#include <cfloat>
#include <cstdio>
#include <ostream>
2022-01-03 23:16:39 +01:00
#include <iomanip>
2020-09-08 15:53:08 +02:00
namespace Catch {
2022-01-03 23:16:39 +01:00
namespace {
void listTestNamesOnly ( std :: ostream & out ,
std :: vector < TestCaseHandle > const & tests ) {
for ( auto const & test : tests ) {
auto const & testCaseInfo = test . getTestCaseInfo ();
if ( startsWith ( testCaseInfo . name , '#' )) {
out << '"' << testCaseInfo . name << '"' ;
} else {
out << testCaseInfo . name ;
}
out << '\n' ;
}
out << std :: flush ;
}
} // end unnamed namespace
2020-09-08 15:53:08 +02:00
// Because formatting using c++ streams is stateful, drop down to C is
// required Alternatively we could use stringstream, but its performance
// is... not good.
std :: string getFormattedDuration ( double duration ) {
// Max exponent + 1 is required to represent the whole part
// + 1 for decimal point
// + 3 for the 3 decimal places
// + 1 for null terminator
const std :: size_t maxDoubleSize = DBL_MAX_10_EXP + 1 + 1 + 3 + 1 ;
char buffer [ maxDoubleSize ];
// Save previous errno, to prevent sprintf from overwriting it
ErrnoGuard guard ;
#ifdef _MSC_VER
2022-01-03 23:16:39 +01:00
size_t printedLength = static_cast < size_t > (
sprintf_s ( buffer , "%.3f" , duration ) );
2020-09-08 15:53:08 +02:00
#else
2022-01-03 23:16:39 +01:00
size_t printedLength = static_cast < size_t > (
std :: snprintf ( buffer , maxDoubleSize , "%.3f" , duration ) );
2020-09-08 15:53:08 +02:00
#endif
2022-01-03 23:16:39 +01:00
return std :: string ( buffer , printedLength );
2020-09-08 15:53:08 +02:00
}
bool shouldShowDuration ( IConfig const & config , double duration ) {
if ( config . showDurations () == ShowDurations :: Always ) {
return true ;
}
if ( config . showDurations () == ShowDurations :: Never ) {
return false ;
}
const double min = config . minDuration ();
return min >= 0 && duration >= min ;
}
std :: string serializeFilters ( std :: vector < std :: string > const & filters ) {
// We add a ' ' separator between each filter
size_t serialized_size = filters . size () - 1 ;
for ( auto const & filter : filters ) {
serialized_size += filter . size ();
}
std :: string serialized ;
serialized . reserve ( serialized_size );
bool first = true ;
for ( auto const & filter : filters ) {
if ( ! first ) {
serialized . push_back ( ' ' );
}
first = false ;
serialized . append ( filter );
}
return serialized ;
}
std :: ostream & operator << ( std :: ostream & out , lineOfChars value ) {
for ( size_t idx = 0 ; idx < CATCH_CONFIG_CONSOLE_WIDTH - 1 ; ++ idx ) {
out . put ( value . c );
}
return out ;
}
2022-01-03 23:16:39 +01:00
void
defaultListReporters ( std :: ostream & out ,
std :: vector < ReporterDescription > const & descriptions ,
Verbosity verbosity ) {
out << "Available reporters: \n " ;
const auto maxNameLen =
std :: max_element ( descriptions . begin (),
descriptions . end (),
[]( ReporterDescription const & lhs ,
ReporterDescription const & rhs ) {
return lhs . name . size () < rhs . name . size ();
} )
-> name . size ();
for ( auto const & desc : descriptions ) {
if ( verbosity == Verbosity :: Quiet ) {
out << TextFlow :: Column ( desc . name )
. indent ( 2 )
. width ( 5 + maxNameLen )
<< '\n' ;
} else {
out << TextFlow :: Column ( desc . name + ':' )
. indent ( 2 )
. width ( 5 + maxNameLen ) +
TextFlow :: Column ( desc . description )
. initialIndent ( 0 )
. indent ( 2 )
. width ( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen - 8 )
<< '\n' ;
}
}
out << '\n' << std :: flush ;
}
void defaultListTags ( std :: ostream & out ,
std :: vector < TagInfo > const & tags ,
bool isFiltered ) {
if ( isFiltered ) {
out << "Tags for matching test cases: \n " ;
} else {
out << "All available tags: \n " ;
}
for ( auto const & tagCount : tags ) {
ReusableStringStream rss ;
rss << " " << std :: setw ( 2 ) << tagCount . count << " " ;
auto str = rss . str ();
auto wrapper = TextFlow :: Column ( tagCount . all () )
. initialIndent ( 0 )
. indent ( str . size () )
. width ( CATCH_CONFIG_CONSOLE_WIDTH - 10 );
out << str << wrapper << '\n' ;
}
out << pluralise ( tags . size (), "tag" _sr ) << " \n\n " << std :: flush ;
}
void defaultListTests ( std :: ostream & out , std :: vector < TestCaseHandle > const & tests , bool isFiltered , Verbosity verbosity ) {
// We special case this to provide the equivalent of old
// `--list-test-names-only`, which could then be used by the
// `--input-file` option.
if ( verbosity == Verbosity :: Quiet ) {
listTestNamesOnly ( out , tests );
return ;
}
if ( isFiltered ) {
out << "Matching test cases: \n " ;
} else {
out << "All available test cases: \n " ;
}
for ( auto const & test : tests ) {
auto const & testCaseInfo = test . getTestCaseInfo ();
Colour :: Code colour = testCaseInfo . isHidden ()
? Colour :: SecondaryText
: Colour :: None ;
Colour colourGuard ( colour );
out << TextFlow :: Column ( testCaseInfo . name ). initialIndent ( 2 ). indent ( 4 ) << '\n' ;
if ( verbosity >= Verbosity :: High ) {
out << TextFlow :: Column ( Catch :: Detail :: stringify ( testCaseInfo . lineInfo )). indent ( 4 ) << '\n' ;
}
if ( ! testCaseInfo . tags . empty () &&
verbosity > Verbosity :: Quiet ) {
out << TextFlow :: Column ( testCaseInfo . tagsAsString ()). indent ( 6 ) << '\n' ;
}
}
if ( isFiltered ) {
out << pluralise ( tests . size (), "matching test case" _sr );
} else {
out << pluralise ( tests . size (), "test case" _sr );
}
out << " \n\n " << std :: flush ;
}
2020-09-08 15:53:08 +02:00
} // namespace Catch
namespace Catch {
2022-01-03 23:16:39 +01:00
void EventListenerBase :: fatalErrorEncountered ( StringRef ) {}
void EventListenerBase :: benchmarkPreparing ( StringRef ) {}
void EventListenerBase :: benchmarkStarting ( BenchmarkInfo const & ) {}
void EventListenerBase :: benchmarkEnded ( BenchmarkStats <> const & ) {}
void EventListenerBase :: benchmarkFailed ( StringRef ) {}
2020-09-08 15:53:08 +02:00
void EventListenerBase :: assertionStarting ( AssertionInfo const & ) {}
2022-01-03 23:16:39 +01:00
void EventListenerBase :: assertionEnded ( AssertionStats const & ) {}
void EventListenerBase :: listReporters (
std :: vector < ReporterDescription > const & ) {}
void EventListenerBase :: listTests ( std :: vector < TestCaseHandle > const & ) {}
void EventListenerBase :: listTags ( std :: vector < TagInfo > const & ) {}
void EventListenerBase :: noMatchingTestCases ( StringRef ) {}
void EventListenerBase :: reportInvalidTestSpec ( StringRef ) {}
2020-09-08 15:53:08 +02:00
void EventListenerBase :: testRunStarting ( TestRunInfo const & ) {}
void EventListenerBase :: testCaseStarting ( TestCaseInfo const & ) {}
2022-01-03 23:16:39 +01:00
void EventListenerBase :: testCasePartialStarting ( TestCaseInfo const & , uint64_t ) {}
2020-09-08 15:53:08 +02:00
void EventListenerBase :: sectionStarting ( SectionInfo const & ) {}
void EventListenerBase :: sectionEnded ( SectionStats const & ) {}
2022-01-03 23:16:39 +01:00
void EventListenerBase :: testCasePartialEnded ( TestCaseStats const & , uint64_t ) {}
2020-09-08 15:53:08 +02:00
void EventListenerBase :: testCaseEnded ( TestCaseStats const & ) {}
void EventListenerBase :: testRunEnded ( TestRunStats const & ) {}
void EventListenerBase :: skipTest ( TestCaseInfo const & ) {}
} // namespace Catch
#include <ostream>
namespace {
// Colour::LightGrey
2022-01-03 23:16:39 +01:00
constexpr Catch :: Colour :: Code dimColour () { return Catch :: Colour :: FileName ; }
2020-09-08 15:53:08 +02:00
2022-01-03 23:16:39 +01:00
constexpr Catch :: StringRef bothOrAll ( std :: uint64_t count ) {
2020-09-08 15:53:08 +02:00
switch ( count ) {
case 1 :
return Catch :: StringRef {};
case 2 :
return "both " _catch_sr ;
default :
return "all " _catch_sr ;
}
}
} // anon namespace
namespace Catch {
namespace {
#ifdef CATCH_PLATFORM_MAC
static constexpr Catch :: StringRef compactFailedString = "FAILED" _sr ;
static constexpr Catch :: StringRef compactPassedString = "PASSED" _sr ;
#else
static constexpr Catch :: StringRef compactFailedString = "failed" _sr ;
static constexpr Catch :: StringRef compactPassedString = "passed" _sr ;
#endif
// Colour, message variants:
// - white: No tests ran.
// - red: Failed [both/all] N test cases, failed [both/all] M assertions.
// - white: Passed [both/all] N test cases (no assertions).
// - red: Failed N tests cases, failed M assertions.
// - green: Passed [both/all] N tests cases with M assertions.
void printTotals ( std :: ostream & out , const Totals & totals ) {
if ( totals . testCases . total () == 0 ) {
out << "No tests ran." ;
} else if ( totals . testCases . failed == totals . testCases . total ()) {
Colour colour ( Colour :: ResultError );
const StringRef qualify_assertions_failed =
totals . assertions . failed == totals . assertions . total () ?
bothOrAll ( totals . assertions . failed ) : StringRef {};
out <<
"Failed " << bothOrAll ( totals . testCases . failed )
2022-01-03 23:16:39 +01:00
<< pluralise ( totals . testCases . failed , "test case" _sr ) << ", "
2020-09-08 15:53:08 +02:00
"failed " << qualify_assertions_failed <<
2022-01-03 23:16:39 +01:00
pluralise ( totals . assertions . failed , "assertion" _sr ) << '.' ;
2020-09-08 15:53:08 +02:00
} else if ( totals . assertions . total () == 0 ) {
out <<
"Passed " << bothOrAll ( totals . testCases . total ())
2022-01-03 23:16:39 +01:00
<< pluralise ( totals . testCases . total (), "test case" _sr )
2020-09-08 15:53:08 +02:00
<< " (no assertions)." ;
} else if ( totals . assertions . failed ) {
Colour colour ( Colour :: ResultError );
out <<
2022-01-03 23:16:39 +01:00
"Failed " << pluralise ( totals . testCases . failed , "test case" _sr ) << ", "
"failed " << pluralise ( totals . assertions . failed , "assertion" _sr ) << '.' ;
2020-09-08 15:53:08 +02:00
} else {
Colour colour ( Colour :: ResultSuccess );
out <<
"Passed " << bothOrAll ( totals . testCases . passed )
2022-01-03 23:16:39 +01:00
<< pluralise ( totals . testCases . passed , "test case" _sr ) <<
" with " << pluralise ( totals . assertions . passed , "assertion" _sr ) << '.' ;
2020-09-08 15:53:08 +02:00
}
}
// Implementation of CompactReporter formatting
class AssertionPrinter {
public :
AssertionPrinter & operator = ( AssertionPrinter const & ) = delete ;
AssertionPrinter ( AssertionPrinter const & ) = delete ;
AssertionPrinter ( std :: ostream & _stream , AssertionStats const & _stats , bool _printInfoMessages )
: stream ( _stream )
, result ( _stats . assertionResult )
, messages ( _stats . infoMessages )
, itMessage ( _stats . infoMessages . begin ())
, printInfoMessages ( _printInfoMessages ) {}
void print () {
printSourceInfo ();
itMessage = messages . begin ();
switch ( result . getResultType ()) {
case ResultWas :: Ok :
printResultType ( Colour :: ResultSuccess , compactPassedString );
printOriginalExpression ();
printReconstructedExpression ();
if ( ! result . hasExpression ())
printRemainingMessages ( Colour :: None );
else
printRemainingMessages ();
break ;
case ResultWas :: ExpressionFailed :
if ( result . isOk ())
printResultType ( Colour :: ResultSuccess , compactFailedString + " - but was ok" _sr );
else
printResultType ( Colour :: Error , compactFailedString );
printOriginalExpression ();
printReconstructedExpression ();
printRemainingMessages ();
break ;
case ResultWas :: ThrewException :
printResultType ( Colour :: Error , compactFailedString );
printIssue ( "unexpected exception with message:" );
printMessage ();
printExpressionWas ();
printRemainingMessages ();
break ;
case ResultWas :: FatalErrorCondition :
printResultType ( Colour :: Error , compactFailedString );
printIssue ( "fatal error condition with message:" );
printMessage ();
printExpressionWas ();
printRemainingMessages ();
break ;
case ResultWas :: DidntThrowException :
printResultType ( Colour :: Error , compactFailedString );
printIssue ( "expected exception, got none" );
printExpressionWas ();
printRemainingMessages ();
break ;
case ResultWas :: Info :
printResultType ( Colour :: None , "info" _sr );
printMessage ();
printRemainingMessages ();
break ;
case ResultWas :: Warning :
printResultType ( Colour :: None , "warning" _sr );
printMessage ();
printRemainingMessages ();
break ;
case ResultWas :: ExplicitFailure :
printResultType ( Colour :: Error , compactFailedString );
printIssue ( "explicitly" );
printRemainingMessages ( Colour :: None );
break ;
// These cases are here to prevent compiler warnings
case ResultWas :: Unknown :
case ResultWas :: FailureBit :
case ResultWas :: Exception :
printResultType ( Colour :: Error , "** internal error **" );
break ;
}
}
private :
void printSourceInfo () const {
Colour colourGuard ( Colour :: FileName );
stream << result . getSourceInfo () << ':' ;
}
void printResultType ( Colour :: Code colour , StringRef passOrFail ) const {
if ( ! passOrFail . empty ()) {
{
Colour colourGuard ( colour );
stream << ' ' << passOrFail ;
}
stream << ':' ;
}
}
void printIssue ( char const * issue ) const {
stream << ' ' << issue ;
}
void printExpressionWas () {
if ( result . hasExpression ()) {
stream << ';' ;
{
Colour colour ( dimColour ());
stream << " expression was:" ;
}
printOriginalExpression ();
}
}
void printOriginalExpression () const {
if ( result . hasExpression ()) {
stream << ' ' << result . getExpression ();
}
}
void printReconstructedExpression () const {
if ( result . hasExpandedExpression ()) {
{
Colour colour ( dimColour ());
stream << " for: " ;
}
stream << result . getExpandedExpression ();
}
}
void printMessage () {
if ( itMessage != messages . end ()) {
stream << " '" << itMessage -> message << '\'' ;
++ itMessage ;
}
}
void printRemainingMessages ( Colour :: Code colour = dimColour ()) {
if ( itMessage == messages . end ())
return ;
const auto itEnd = messages . cend ();
const auto N = static_cast < std :: size_t > ( std :: distance ( itMessage , itEnd ));
{
Colour colourGuard ( colour );
2022-01-03 23:16:39 +01:00
stream << " with " << pluralise ( N , "message" _sr ) << ':' ;
2020-09-08 15:53:08 +02:00
}
while ( itMessage != itEnd ) {
// If this assertion is a warning ignore any INFO messages
if ( printInfoMessages || itMessage -> type != ResultWas :: Info ) {
printMessage ();
if ( itMessage != itEnd ) {
Colour colourGuard ( dimColour ());
stream << " and" ;
}
continue ;
}
++ itMessage ;
}
}
private :
std :: ostream & stream ;
AssertionResult const & result ;
std :: vector < MessageInfo > messages ;
std :: vector < MessageInfo >:: const_iterator itMessage ;
bool printInfoMessages ;
};
} // anon namespace
std :: string CompactReporter :: getDescription () {
return "Reports test results on a single line, suitable for IDEs" ;
}
2022-01-03 23:16:39 +01:00
void CompactReporter :: noMatchingTestCases ( StringRef unmatchedSpec ) {
m_stream << "No test cases matched '" << unmatchedSpec << "' \n " ;
2020-09-08 15:53:08 +02:00
}
2022-01-03 23:16:39 +01:00
void CompactReporter :: assertionEnded ( AssertionStats const & _assertionStats ) {
2020-09-08 15:53:08 +02:00
AssertionResult const & result = _assertionStats . assertionResult ;
bool printInfoMessages = true ;
// Drop out if result was successful and we're not printing those
if ( ! m_config -> includeSuccessfulResults () && result . isOk () ) {
if ( result . getResultType () != ResultWas :: Warning )
2022-01-03 23:16:39 +01:00
return ;
2020-09-08 15:53:08 +02:00
printInfoMessages = false ;
}
2022-01-03 23:16:39 +01:00
AssertionPrinter printer ( m_stream , _assertionStats , printInfoMessages );
2020-09-08 15:53:08 +02:00
printer . print ();
2022-01-03 23:16:39 +01:00
m_stream << '\n' << std :: flush ;
2020-09-08 15:53:08 +02:00
}
void CompactReporter :: sectionEnded ( SectionStats const & _sectionStats ) {
double dur = _sectionStats . durationInSeconds ;
if ( shouldShowDuration ( * m_config , dur ) ) {
2022-01-03 23:16:39 +01:00
m_stream << getFormattedDuration ( dur ) << " s: " << _sectionStats . sectionInfo . name << '\n' << std :: flush ;
2020-09-08 15:53:08 +02:00
}
}
void CompactReporter :: testRunEnded ( TestRunStats const & _testRunStats ) {
2022-01-03 23:16:39 +01:00
printTotals ( m_stream , _testRunStats . totals );
m_stream << " \n\n " << std :: flush ;
2020-09-08 15:53:08 +02:00
StreamingReporterBase :: testRunEnded ( _testRunStats );
}
CompactReporter ::~ CompactReporter () {}
} // end namespace Catch
#include <cstdio>
#if defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch
// Note that 4062 (not all labels are handled and default is missing) is enabled
#endif
#if defined(__clang__)
# pragma clang diagnostic push
// For simplicity, benchmarking-only helpers are always enabled
# pragma clang diagnostic ignored "-Wunused-function"
#endif
namespace Catch {
namespace {
// Formatter impl for ConsoleReporter
class ConsoleAssertionPrinter {
public :
ConsoleAssertionPrinter & operator = ( ConsoleAssertionPrinter const & ) = delete ;
ConsoleAssertionPrinter ( ConsoleAssertionPrinter const & ) = delete ;
ConsoleAssertionPrinter ( std :: ostream & _stream , AssertionStats const & _stats , bool _printInfoMessages )
: stream ( _stream ),
stats ( _stats ),
result ( _stats . assertionResult ),
colour ( Colour :: None ),
message ( result . getMessage ()),
messages ( _stats . infoMessages ),
printInfoMessages ( _printInfoMessages ) {
switch ( result . getResultType ()) {
case ResultWas :: Ok :
colour = Colour :: Success ;
2022-01-03 23:16:39 +01:00
passOrFail = "PASSED" _sr ;
2020-09-08 15:53:08 +02:00
//if( result.hasMessage() )
if ( _stats . infoMessages . size () == 1 )
messageLabel = "with message" ;
if ( _stats . infoMessages . size () > 1 )
messageLabel = "with messages" ;
break ;
case ResultWas :: ExpressionFailed :
if ( result . isOk ()) {
colour = Colour :: Success ;
2022-01-03 23:16:39 +01:00
passOrFail = "FAILED - but was ok" _sr ;
2020-09-08 15:53:08 +02:00
} else {
colour = Colour :: Error ;
2022-01-03 23:16:39 +01:00
passOrFail = "FAILED" _sr ;
2020-09-08 15:53:08 +02:00
}
if ( _stats . infoMessages . size () == 1 )
messageLabel = "with message" ;
if ( _stats . infoMessages . size () > 1 )
messageLabel = "with messages" ;
break ;
case ResultWas :: ThrewException :
colour = Colour :: Error ;
2022-01-03 23:16:39 +01:00
passOrFail = "FAILED" _sr ;
2020-09-08 15:53:08 +02:00
messageLabel = "due to unexpected exception with " ;
if ( _stats . infoMessages . size () == 1 )
messageLabel += "message" ;
if ( _stats . infoMessages . size () > 1 )
messageLabel += "messages" ;
break ;
case ResultWas :: FatalErrorCondition :
colour = Colour :: Error ;
2022-01-03 23:16:39 +01:00
passOrFail = "FAILED" _sr ;
2020-09-08 15:53:08 +02:00
messageLabel = "due to a fatal error condition" ;
break ;
case ResultWas :: DidntThrowException :
colour = Colour :: Error ;
2022-01-03 23:16:39 +01:00
passOrFail = "FAILED" _sr ;
2020-09-08 15:53:08 +02:00
messageLabel = "because no exception was thrown where one was expected" ;
break ;
case ResultWas :: Info :
messageLabel = "info" ;
break ;
case ResultWas :: Warning :
messageLabel = "warning" ;
break ;
case ResultWas :: ExplicitFailure :
2022-01-03 23:16:39 +01:00
passOrFail = "FAILED" _sr ;
2020-09-08 15:53:08 +02:00
colour = Colour :: Error ;
if ( _stats . infoMessages . size () == 1 )
messageLabel = "explicitly with message" ;
if ( _stats . infoMessages . size () > 1 )
messageLabel = "explicitly with messages" ;
break ;
// These cases are here to prevent compiler warnings
case ResultWas :: Unknown :
case ResultWas :: FailureBit :
case ResultWas :: Exception :
2022-01-03 23:16:39 +01:00
passOrFail = "** internal error **" _sr ;
2020-09-08 15:53:08 +02:00
colour = Colour :: Error ;
break ;
}
}
void print () const {
printSourceInfo ();
if ( stats . totals . assertions . total () > 0 ) {
printResultType ();
printOriginalExpression ();
printReconstructedExpression ();
} else {
stream << '\n' ;
}
printMessage ();
}
private :
void printResultType () const {
if ( ! passOrFail . empty ()) {
Colour colourGuard ( colour );
stream << passOrFail << ": \n " ;
}
}
void printOriginalExpression () const {
if ( result . hasExpression ()) {
Colour colourGuard ( Colour :: OriginalExpression );
stream << " " ;
stream << result . getExpressionInMacro ();
stream << '\n' ;
}
}
void printReconstructedExpression () const {
if ( result . hasExpandedExpression ()) {
stream << "with expansion: \n " ;
Colour colourGuard ( Colour :: ReconstructedExpression );
stream << TextFlow :: Column ( result . getExpandedExpression ()). indent ( 2 ) << '\n' ;
}
}
void printMessage () const {
if ( ! messageLabel . empty ())
stream << messageLabel << ':' << '\n' ;
for ( auto const & msg : messages ) {
// If this assertion is a warning ignore any INFO messages
if ( printInfoMessages || msg . type != ResultWas :: Info )
stream << TextFlow :: Column ( msg . message ). indent ( 2 ) << '\n' ;
}
}
void printSourceInfo () const {
Colour colourGuard ( Colour :: FileName );
stream << result . getSourceInfo () << ": " ;
}
std :: ostream & stream ;
AssertionStats const & stats ;
AssertionResult const & result ;
Colour :: Code colour ;
2022-01-03 23:16:39 +01:00
StringRef passOrFail ;
2020-09-08 15:53:08 +02:00
std :: string messageLabel ;
std :: string message ;
std :: vector < MessageInfo > messages ;
bool printInfoMessages ;
};
2022-01-03 23:16:39 +01:00
std :: size_t makeRatio ( std :: uint64_t number , std :: uint64_t total ) {
const auto ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number / total : 0 ;
return ( ratio == 0 && number > 0 ) ? 1 : static_cast < std :: size_t > ( ratio );
2020-09-08 15:53:08 +02:00
}
2022-01-03 23:16:39 +01:00
std :: size_t & findMax ( std :: size_t & i , std :: size_t & j , std :: size_t & k ) {
2020-09-08 15:53:08 +02:00
if ( i > j && i > k )
return i ;
else if ( j > k )
return j ;
else
return k ;
}
enum class Justification { Left , Right };
struct ColumnInfo {
std :: string name ;
2022-01-03 23:16:39 +01:00
std :: size_t width ;
2020-09-08 15:53:08 +02:00
Justification justification ;
};
struct ColumnBreak {};
struct RowBreak {};
class Duration {
enum class Unit {
Auto ,
Nanoseconds ,
Microseconds ,
Milliseconds ,
Seconds ,
Minutes
};
static const uint64_t s_nanosecondsInAMicrosecond = 1000 ;
static const uint64_t s_nanosecondsInAMillisecond = 1000 * s_nanosecondsInAMicrosecond ;
static const uint64_t s_nanosecondsInASecond = 1000 * s_nanosecondsInAMillisecond ;
static const uint64_t s_nanosecondsInAMinute = 60 * s_nanosecondsInASecond ;
double m_inNanoseconds ;
Unit m_units ;
public :
explicit Duration ( double inNanoseconds , Unit units = Unit :: Auto )
: m_inNanoseconds ( inNanoseconds ),
m_units ( units ) {
if ( m_units == Unit :: Auto ) {
if ( m_inNanoseconds < s_nanosecondsInAMicrosecond )
m_units = Unit :: Nanoseconds ;
else if ( m_inNanoseconds < s_nanosecondsInAMillisecond )
m_units = Unit :: Microseconds ;
else if ( m_inNanoseconds < s_nanosecondsInASecond )
m_units = Unit :: Milliseconds ;
else if ( m_inNanoseconds < s_nanosecondsInAMinute )
m_units = Unit :: Seconds ;
else
m_units = Unit :: Minutes ;
}
}
auto value () const -> double {
switch ( m_units ) {
case Unit :: Microseconds :
return m_inNanoseconds / static_cast < double > ( s_nanosecondsInAMicrosecond );
case Unit :: Milliseconds :
return m_inNanoseconds / static_cast < double > ( s_nanosecondsInAMillisecond );
case Unit :: Seconds :
return m_inNanoseconds / static_cast < double > ( s_nanosecondsInASecond );
case Unit :: Minutes :
return m_inNanoseconds / static_cast < double > ( s_nanosecondsInAMinute );
default :
return m_inNanoseconds ;
}
}
StringRef unitsAsString () const {
switch ( m_units ) {
case Unit :: Nanoseconds :
return "ns" _sr ;
case Unit :: Microseconds :
return "us" _sr ;
case Unit :: Milliseconds :
return "ms" _sr ;
case Unit :: Seconds :
return "s" _sr ;
case Unit :: Minutes :
return "m" _sr ;
default :
return "** internal error **" _sr ;
}
}
friend auto operator << ( std :: ostream & os , Duration const & duration ) -> std :: ostream & {
return os << duration . value () << ' ' << duration . unitsAsString ();
}
};
} // end anon namespace
class TablePrinter {
std :: ostream & m_os ;
std :: vector < ColumnInfo > m_columnInfos ;
ReusableStringStream m_oss ;
int m_currentColumn = - 1 ;
bool m_isOpen = false ;
public :
TablePrinter ( std :: ostream & os , std :: vector < ColumnInfo > columnInfos )
: m_os ( os ),
2022-01-03 23:16:39 +01:00
m_columnInfos ( CATCH_MOVE ( columnInfos ) ) {}
2020-09-08 15:53:08 +02:00
auto columnInfos () const -> std :: vector < ColumnInfo > const & {
return m_columnInfos ;
}
void open () {
if ( ! m_isOpen ) {
m_isOpen = true ;
* this << RowBreak ();
TextFlow :: Columns headerCols ;
auto spacer = TextFlow :: Spacer ( 2 );
for ( auto const & info : m_columnInfos ) {
2022-01-03 23:16:39 +01:00
assert ( info . width > 2 );
headerCols += TextFlow :: Column ( info . name ). width ( info . width - 2 );
2020-09-08 15:53:08 +02:00
headerCols += spacer ;
}
m_os << headerCols << '\n' ;
m_os << lineOfChars ( '-' ) << '\n' ;
}
}
void close () {
if ( m_isOpen ) {
* this << RowBreak ();
2022-01-03 23:16:39 +01:00
m_os << '\n' << std :: flush ;
2020-09-08 15:53:08 +02:00
m_isOpen = false ;
}
}
template < typename T >
friend TablePrinter & operator << ( TablePrinter & tp , T const & value ) {
tp . m_oss << value ;
return tp ;
}
friend TablePrinter & operator << ( TablePrinter & tp , ColumnBreak ) {
auto colStr = tp . m_oss . str ();
const auto strSize = colStr . size ();
tp . m_oss . str ( "" );
tp . open ();
if ( tp . m_currentColumn == static_cast < int > ( tp . m_columnInfos . size () - 1 )) {
tp . m_currentColumn = - 1 ;
tp . m_os << '\n' ;
}
tp . m_currentColumn ++ ;
auto colInfo = tp . m_columnInfos [ tp . m_currentColumn ];
2022-01-03 23:16:39 +01:00
auto padding = ( strSize + 1 < colInfo . width )
2020-09-08 15:53:08 +02:00
? std :: string ( colInfo . width - ( strSize + 1 ), ' ' )
: std :: string ();
if ( colInfo . justification == Justification :: Left )
tp . m_os << colStr << padding << ' ' ;
else
tp . m_os << padding << colStr << ' ' ;
return tp ;
}
friend TablePrinter & operator << ( TablePrinter & tp , RowBreak ) {
if ( tp . m_currentColumn > 0 ) {
tp . m_os << '\n' ;
tp . m_currentColumn = - 1 ;
}
return tp ;
}
};
ConsoleReporter :: ConsoleReporter ( ReporterConfig const & config )
: StreamingReporterBase ( config ),
2022-01-03 23:16:39 +01:00
m_tablePrinter ( Detail :: make_unique < TablePrinter > ( config . stream (),
2020-09-08 15:53:08 +02:00
[ & config ]() -> std :: vector < ColumnInfo > {
if ( config . fullConfig () -> benchmarkNoAnalysis ())
{
return {
{ "benchmark name" , CATCH_CONFIG_CONSOLE_WIDTH - 43 , Justification :: Left },
{ " samples" , 14 , Justification :: Right },
{ " iterations" , 14 , Justification :: Right },
{ " mean" , 14 , Justification :: Right }
};
}
else
{
return {
{ "benchmark name" , CATCH_CONFIG_CONSOLE_WIDTH - 43 , Justification :: Left },
{ "samples mean std dev" , 14 , Justification :: Right },
{ "iterations low mean low std dev" , 14 , Justification :: Right },
{ "estimated high mean high std dev" , 14 , Justification :: Right }
};
}
}())) {}
ConsoleReporter ::~ ConsoleReporter () = default ;
std :: string ConsoleReporter :: getDescription () {
return "Reports test results as plain lines of text" ;
}
2022-01-03 23:16:39 +01:00
void ConsoleReporter :: noMatchingTestCases ( StringRef unmatchedSpec ) {
m_stream << "No test cases matched '" << unmatchedSpec << "' \n " ;
2020-09-08 15:53:08 +02:00
}
2022-01-03 23:16:39 +01:00
void ConsoleReporter :: reportInvalidTestSpec ( StringRef arg ) {
m_stream << "Invalid Filter: " << arg << '\n' ;
2020-09-08 15:53:08 +02:00
}
void ConsoleReporter :: assertionStarting ( AssertionInfo const & ) {}
2022-01-03 23:16:39 +01:00
void ConsoleReporter :: assertionEnded ( AssertionStats const & _assertionStats ) {
2020-09-08 15:53:08 +02:00
AssertionResult const & result = _assertionStats . assertionResult ;
bool includeResults = m_config -> includeSuccessfulResults () || ! result . isOk ();
// Drop out if result was successful but we're not printing them.
if ( ! includeResults && result . getResultType () != ResultWas :: Warning )
2022-01-03 23:16:39 +01:00
return ;
2020-09-08 15:53:08 +02:00
lazyPrint ();
2022-01-03 23:16:39 +01:00
ConsoleAssertionPrinter printer ( m_stream , _assertionStats , includeResults );
2020-09-08 15:53:08 +02:00
printer . print ();
2022-01-03 23:16:39 +01:00
m_stream << '\n' << std :: flush ;
2020-09-08 15:53:08 +02:00
}
void ConsoleReporter :: sectionStarting ( SectionInfo const & _sectionInfo ) {
m_tablePrinter -> close ();
m_headerPrinted = false ;
StreamingReporterBase :: sectionStarting ( _sectionInfo );
}
void ConsoleReporter :: sectionEnded ( SectionStats const & _sectionStats ) {
m_tablePrinter -> close ();
if ( _sectionStats . missingAssertions ) {
lazyPrint ();
Colour colour ( Colour :: ResultError );
if ( m_sectionStack . size () > 1 )
2022-01-03 23:16:39 +01:00
m_stream << " \n No assertions in section" ;
2020-09-08 15:53:08 +02:00
else
2022-01-03 23:16:39 +01:00
m_stream << " \n No assertions in test case" ;
m_stream << " '" << _sectionStats . sectionInfo . name << "' \n\n " << std :: flush ;
2020-09-08 15:53:08 +02:00
}
double dur = _sectionStats . durationInSeconds ;
if ( shouldShowDuration ( * m_config , dur )) {
2022-01-03 23:16:39 +01:00
m_stream << getFormattedDuration ( dur ) << " s: " << _sectionStats . sectionInfo . name << '\n' << std :: flush ;
2020-09-08 15:53:08 +02:00
}
if ( m_headerPrinted ) {
m_headerPrinted = false ;
}
StreamingReporterBase :: sectionEnded ( _sectionStats );
}
2022-01-03 23:16:39 +01:00
void ConsoleReporter :: benchmarkPreparing ( StringRef name ) {
2020-09-08 15:53:08 +02:00
lazyPrintWithoutClosingBenchmarkTable ();
2022-01-03 23:16:39 +01:00
auto nameCol = TextFlow :: Column ( static_cast < std :: string > ( name ) )
. width ( m_tablePrinter -> columnInfos ()[ 0 ]. width - 2 );
2020-09-08 15:53:08 +02:00
bool firstLine = true ;
for ( auto line : nameCol ) {
if ( ! firstLine )
( * m_tablePrinter ) << ColumnBreak () << ColumnBreak () << ColumnBreak ();
else
firstLine = false ;
( * m_tablePrinter ) << line << ColumnBreak ();
}
}
void ConsoleReporter :: benchmarkStarting ( BenchmarkInfo const & info ) {
( * m_tablePrinter ) << info . samples << ColumnBreak ()
<< info . iterations << ColumnBreak ();
if ( ! m_config -> benchmarkNoAnalysis ())
( * m_tablePrinter ) << Duration ( info . estimatedDuration ) << ColumnBreak ();
}
void ConsoleReporter :: benchmarkEnded ( BenchmarkStats <> const & stats ) {
if ( m_config -> benchmarkNoAnalysis ())
{
( * m_tablePrinter ) << Duration ( stats . mean . point . count ()) << ColumnBreak ();
}
else
{
( * m_tablePrinter ) << ColumnBreak ()
<< Duration ( stats . mean . point . count ()) << ColumnBreak ()
<< Duration ( stats . mean . lower_bound . count ()) << ColumnBreak ()
<< Duration ( stats . mean . upper_bound . count ()) << ColumnBreak () << ColumnBreak ()
<< Duration ( stats . standardDeviation . point . count ()) << ColumnBreak ()
<< Duration ( stats . standardDeviation . lower_bound . count ()) << ColumnBreak ()
<< Duration ( stats . standardDeviation . upper_bound . count ()) << ColumnBreak () << ColumnBreak () << ColumnBreak () << ColumnBreak () << ColumnBreak ();
}
}
2022-01-03 23:16:39 +01:00
void ConsoleReporter :: benchmarkFailed ( StringRef error ) {
2020-09-08 15:53:08 +02:00
Colour colour ( Colour :: Red );
( * m_tablePrinter )
<< "Benchmark failed (" << error << ')'
<< ColumnBreak () << RowBreak ();
}
void ConsoleReporter :: testCaseEnded ( TestCaseStats const & _testCaseStats ) {
m_tablePrinter -> close ();
StreamingReporterBase :: testCaseEnded ( _testCaseStats );
m_headerPrinted = false ;
}
void ConsoleReporter :: testRunEnded ( TestRunStats const & _testRunStats ) {
printTotalsDivider ( _testRunStats . totals );
printTotals ( _testRunStats . totals );
2022-01-03 23:16:39 +01:00
m_stream << '\n' << std :: flush ;
2020-09-08 15:53:08 +02:00
StreamingReporterBase :: testRunEnded ( _testRunStats );
}
void ConsoleReporter :: testRunStarting ( TestRunInfo const & _testInfo ) {
StreamingReporterBase :: testRunStarting ( _testInfo );
printTestFilters ();
}
void ConsoleReporter :: lazyPrint () {
m_tablePrinter -> close ();
lazyPrintWithoutClosingBenchmarkTable ();
}
void ConsoleReporter :: lazyPrintWithoutClosingBenchmarkTable () {
2022-01-03 23:16:39 +01:00
if ( ! m_testRunInfoPrinted ) {
2020-09-08 15:53:08 +02:00
lazyPrintRunInfo ();
2022-01-03 23:16:39 +01:00
}
2020-09-08 15:53:08 +02:00
if ( ! m_headerPrinted ) {
printTestCaseAndSectionHeader ();
m_headerPrinted = true ;
}
}
void ConsoleReporter :: lazyPrintRunInfo () {
2022-01-03 23:16:39 +01:00
m_stream << '\n' << lineOfChars ( '~' ) << '\n' ;
2020-09-08 15:53:08 +02:00
Colour colour ( Colour :: SecondaryText );
2022-01-03 23:16:39 +01:00
m_stream << currentTestRunInfo . name
2020-09-08 15:53:08 +02:00
<< " is a Catch v" << libraryVersion () << " host application. \n "
<< "Run with -? for options \n\n " ;
2022-01-03 23:16:39 +01:00
m_stream << "Randomness seeded to: " << m_config -> rngSeed () << " \n\n " ;
2020-09-08 15:53:08 +02:00
2022-01-03 23:16:39 +01:00
m_testRunInfoPrinted = true ;
2020-09-08 15:53:08 +02:00
}
void ConsoleReporter :: printTestCaseAndSectionHeader () {
assert ( ! m_sectionStack . empty ());
printOpenHeader ( currentTestCaseInfo -> name );
if ( m_sectionStack . size () > 1 ) {
Colour colourGuard ( Colour :: Headers );
auto
it = m_sectionStack . begin () + 1 , // Skip first section (test case)
itEnd = m_sectionStack . end ();
for (; it != itEnd ; ++ it )
printHeaderString ( it -> name , 2 );
}
SourceLineInfo lineInfo = m_sectionStack . back (). lineInfo ;
2022-01-03 23:16:39 +01:00
m_stream << lineOfChars ( '-' ) << '\n' ;
2020-09-08 15:53:08 +02:00
Colour colourGuard ( Colour :: FileName );
2022-01-03 23:16:39 +01:00
m_stream << lineInfo << '\n' ;
m_stream << lineOfChars ( '.' ) << " \n\n " << std :: flush ;
2020-09-08 15:53:08 +02:00
}
void ConsoleReporter :: printClosedHeader ( std :: string const & _name ) {
printOpenHeader ( _name );
2022-01-03 23:16:39 +01:00
m_stream << lineOfChars ( '.' ) << '\n' ;
2020-09-08 15:53:08 +02:00
}
void ConsoleReporter :: printOpenHeader ( std :: string const & _name ) {
2022-01-03 23:16:39 +01:00
m_stream << lineOfChars ( '-' ) << '\n' ;
2020-09-08 15:53:08 +02:00
{
Colour colourGuard ( Colour :: Headers );
printHeaderString ( _name );
}
}
void ConsoleReporter :: printHeaderString ( std :: string const & _string , std :: size_t indent ) {
2022-01-03 23:16:39 +01:00
// We want to get a bit fancy with line breaking here, so that subsequent
// lines start after ":" if one is present, e.g.
// ```
// blablabla: Fancy
// linebreaking
// ```
// but we also want to avoid problems with overly long indentation causing
// the text to take up too many lines, e.g.
// ```
// blablabla: F
// a
// n
// c
// y
// .
// .
// .
// ```
// So we limit the prefix indentation check to first quarter of the possible
// width
std :: size_t idx = _string . find ( ": " );
if ( idx != std :: string :: npos && idx < CATCH_CONFIG_CONSOLE_WIDTH / 4 ) {
idx += 2 ;
} else {
idx = 0 ;
}
m_stream << TextFlow :: Column ( _string )
. indent ( indent + idx )
. initialIndent ( indent )
<< '\n' ;
2020-09-08 15:53:08 +02:00
}
struct SummaryColumn {
SummaryColumn ( std :: string _label , Colour :: Code _colour )
2022-01-03 23:16:39 +01:00
: label ( CATCH_MOVE ( _label ) ),
2020-09-08 15:53:08 +02:00
colour ( _colour ) {}
2022-01-03 23:16:39 +01:00
SummaryColumn addRow ( std :: uint64_t count ) {
2020-09-08 15:53:08 +02:00
ReusableStringStream rss ;
rss << count ;
std :: string row = rss . str ();
for ( auto & oldRow : rows ) {
while ( oldRow . size () < row . size ())
oldRow = ' ' + oldRow ;
while ( oldRow . size () > row . size ())
row = ' ' + row ;
}
rows . push_back ( row );
return * this ;
}
std :: string label ;
Colour :: Code colour ;
std :: vector < std :: string > rows ;
};
void ConsoleReporter :: printTotals ( Totals const & totals ) {
if ( totals . testCases . total () == 0 ) {
2022-01-03 23:16:39 +01:00
m_stream << Colour ( Colour :: Warning ) << "No tests ran \n " ;
2020-09-08 15:53:08 +02:00
} else if ( totals . assertions . total () > 0 && totals . testCases . allPassed ()) {
2022-01-03 23:16:39 +01:00
m_stream << Colour ( Colour :: ResultSuccess ) << "All tests passed" ;
m_stream << " ("
<< pluralise ( totals . assertions . passed , "assertion" _sr ) << " in "
<< pluralise ( totals . testCases . passed , "test case" _sr ) << ')'
2020-09-08 15:53:08 +02:00
<< '\n' ;
} else {
std :: vector < SummaryColumn > columns ;
columns . push_back ( SummaryColumn ( "" , Colour :: None )
. addRow ( totals . testCases . total ())
. addRow ( totals . assertions . total ()));
columns . push_back ( SummaryColumn ( "passed" , Colour :: Success )
. addRow ( totals . testCases . passed )
. addRow ( totals . assertions . passed ));
columns . push_back ( SummaryColumn ( "failed" , Colour :: ResultError )
. addRow ( totals . testCases . failed )
. addRow ( totals . assertions . failed ));
columns . push_back ( SummaryColumn ( "failed as expected" , Colour :: ResultExpectedFailure )
. addRow ( totals . testCases . failedButOk )
. addRow ( totals . assertions . failedButOk ));
2022-01-03 23:16:39 +01:00
printSummaryRow ( "test cases" _sr , columns , 0 );
printSummaryRow ( "assertions" _sr , columns , 1 );
2020-09-08 15:53:08 +02:00
}
}
2022-01-03 23:16:39 +01:00
void ConsoleReporter :: printSummaryRow ( StringRef label , std :: vector < SummaryColumn > const & cols , std :: size_t row ) {
2020-09-08 15:53:08 +02:00
for ( auto col : cols ) {
std :: string value = col . rows [ row ];
if ( col . label . empty ()) {
2022-01-03 23:16:39 +01:00
m_stream << label << ": " ;
2020-09-08 15:53:08 +02:00
if ( value != "0" )
2022-01-03 23:16:39 +01:00
m_stream << value ;
2020-09-08 15:53:08 +02:00
else
2022-01-03 23:16:39 +01:00
m_stream << Colour ( Colour :: Warning ) << "- none -" ;
2020-09-08 15:53:08 +02:00
} else if ( value != "0" ) {
2022-01-03 23:16:39 +01:00
m_stream << Colour ( Colour :: LightGrey ) << " | " ;
m_stream << Colour ( col . colour )
2020-09-08 15:53:08 +02:00
<< value << ' ' << col . label ;
}
}
2022-01-03 23:16:39 +01:00
m_stream << '\n' ;
2020-09-08 15:53:08 +02:00
}
void ConsoleReporter :: printTotalsDivider ( Totals const & totals ) {
if ( totals . testCases . total () > 0 ) {
std :: size_t failedRatio = makeRatio ( totals . testCases . failed , totals . testCases . total ());
std :: size_t failedButOkRatio = makeRatio ( totals . testCases . failedButOk , totals . testCases . total ());
std :: size_t passedRatio = makeRatio ( totals . testCases . passed , totals . testCases . total ());
while ( failedRatio + failedButOkRatio + passedRatio < CATCH_CONFIG_CONSOLE_WIDTH - 1 )
findMax ( failedRatio , failedButOkRatio , passedRatio ) ++ ;
while ( failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH - 1 )
findMax ( failedRatio , failedButOkRatio , passedRatio ) -- ;
2022-01-03 23:16:39 +01:00
m_stream << Colour ( Colour :: Error ) << std :: string ( failedRatio , '=' );
m_stream << Colour ( Colour :: ResultExpectedFailure ) << std :: string ( failedButOkRatio , '=' );
2020-09-08 15:53:08 +02:00
if ( totals . testCases . allPassed ())
2022-01-03 23:16:39 +01:00
m_stream << Colour ( Colour :: ResultSuccess ) << std :: string ( passedRatio , '=' );
2020-09-08 15:53:08 +02:00
else
2022-01-03 23:16:39 +01:00
m_stream << Colour ( Colour :: Success ) << std :: string ( passedRatio , '=' );
2020-09-08 15:53:08 +02:00
} else {
2022-01-03 23:16:39 +01:00
m_stream << Colour ( Colour :: Warning ) << std :: string ( CATCH_CONFIG_CONSOLE_WIDTH - 1 , '=' );
2020-09-08 15:53:08 +02:00
}
2022-01-03 23:16:39 +01:00
m_stream << '\n' ;
2020-09-08 15:53:08 +02:00
}
void ConsoleReporter :: printSummaryDivider () {
2022-01-03 23:16:39 +01:00
m_stream << lineOfChars ( '-' ) << '\n' ;
2020-09-08 15:53:08 +02:00
}
void ConsoleReporter :: printTestFilters () {
if ( m_config -> testSpec (). hasFilters ()) {
Colour guard ( Colour :: BrightYellow );
2022-01-03 23:16:39 +01:00
m_stream << "Filters: " << serializeFilters ( m_config -> getTestsOrTags ()) << '\n' ;
2020-09-08 15:53:08 +02:00
}
}
} // end namespace Catch
#if defined(_MSC_VER)
#pragma warning(pop)
#endif
#if defined(__clang__)
# pragma clang diagnostic pop
#endif
#include <algorithm>
#include <cassert>
namespace Catch {
namespace {
struct BySectionInfo {
BySectionInfo ( SectionInfo const & other ) : m_other ( other ) {}
BySectionInfo ( BySectionInfo const & other ) :
m_other ( other . m_other ) {}
bool operator ()(
2022-01-03 23:16:39 +01:00
Detail :: unique_ptr < CumulativeReporterBase :: SectionNode > const &
2020-09-08 15:53:08 +02:00
node ) const {
return (
( node -> stats . sectionInfo . name == m_other . name ) &&
( node -> stats . sectionInfo . lineInfo == m_other . lineInfo ) );
}
void operator = ( BySectionInfo const & ) = delete ;
private :
SectionInfo const & m_other ;
};
} // namespace
2022-01-03 23:16:39 +01:00
namespace Detail {
AssertionOrBenchmarkResult :: AssertionOrBenchmarkResult (
AssertionStats const & assertion ) :
m_assertion ( assertion ) {}
AssertionOrBenchmarkResult :: AssertionOrBenchmarkResult (
BenchmarkStats <> const & benchmark ) :
m_benchmark ( benchmark ) {}
bool AssertionOrBenchmarkResult :: isAssertion () const {
return m_assertion . some ();
}
bool AssertionOrBenchmarkResult :: isBenchmark () const {
return m_benchmark . some ();
}
AssertionStats const & AssertionOrBenchmarkResult :: asAssertion () const {
assert ( m_assertion . some ());
return * m_assertion ;
}
BenchmarkStats <> const & AssertionOrBenchmarkResult :: asBenchmark () const {
assert ( m_benchmark . some ());
return * m_benchmark ;
}
}
2020-09-08 15:53:08 +02:00
CumulativeReporterBase ::~ CumulativeReporterBase () = default ;
2022-01-03 23:16:39 +01:00
void CumulativeReporterBase :: benchmarkEnded ( BenchmarkStats <> const & benchmarkStats ) {
m_sectionStack . back () -> assertionsAndBenchmarks . emplace_back ( benchmarkStats );
}
2020-09-08 15:53:08 +02:00
void
CumulativeReporterBase :: sectionStarting ( SectionInfo const & sectionInfo ) {
SectionStats incompleteStats ( sectionInfo , Counts (), 0 , false );
2022-01-03 23:16:39 +01:00
SectionNode * node ;
2020-09-08 15:53:08 +02:00
if ( m_sectionStack . empty () ) {
2022-01-03 23:16:39 +01:00
if ( ! m_rootSection ) {
2020-09-08 15:53:08 +02:00
m_rootSection =
2022-01-03 23:16:39 +01:00
Detail :: make_unique < SectionNode > ( incompleteStats );
}
node = m_rootSection . get ();
2020-09-08 15:53:08 +02:00
} else {
SectionNode & parentNode = * m_sectionStack . back ();
auto it = std :: find_if ( parentNode . childSections . begin (),
parentNode . childSections . end (),
BySectionInfo ( sectionInfo ) );
if ( it == parentNode . childSections . end () ) {
2022-01-03 23:16:39 +01:00
auto newNode =
Detail :: make_unique < SectionNode > ( incompleteStats );
node = newNode . get ();
parentNode . childSections . push_back ( CATCH_MOVE ( newNode ) );
2020-09-08 15:53:08 +02:00
} else {
2022-01-03 23:16:39 +01:00
node = it -> get ();
2020-09-08 15:53:08 +02:00
}
}
2022-01-03 23:16:39 +01:00
m_deepestSection = node ;
2020-09-08 15:53:08 +02:00
m_sectionStack . push_back ( node );
}
2022-01-03 23:16:39 +01:00
void CumulativeReporterBase :: assertionEnded (
2020-09-08 15:53:08 +02:00
AssertionStats const & assertionStats ) {
assert ( ! m_sectionStack . empty () );
// 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.
2022-01-03 23:16:39 +01:00
if ( m_shouldStoreFailedAssertions &&
! assertionStats . assertionResult . isOk () ) {
static_cast < void > (
assertionStats . assertionResult . getExpandedExpression () );
}
if ( m_shouldStoreSuccesfulAssertions &&
assertionStats . assertionResult . isOk () ) {
static_cast < void > (
assertionStats . assertionResult . getExpandedExpression () );
}
2020-09-08 15:53:08 +02:00
SectionNode & sectionNode = * m_sectionStack . back ();
2022-01-03 23:16:39 +01:00
sectionNode . assertionsAndBenchmarks . emplace_back ( assertionStats );
2020-09-08 15:53:08 +02:00
}
void CumulativeReporterBase :: sectionEnded ( SectionStats const & sectionStats ) {
assert ( ! m_sectionStack . empty () );
SectionNode & node = * m_sectionStack . back ();
node . stats = sectionStats ;
m_sectionStack . pop_back ();
}
void CumulativeReporterBase :: testCaseEnded (
TestCaseStats const & testCaseStats ) {
2022-01-03 23:16:39 +01:00
auto node = Detail :: make_unique < TestCaseNode > ( testCaseStats );
2020-09-08 15:53:08 +02:00
assert ( m_sectionStack . size () == 0 );
2022-01-03 23:16:39 +01:00
node -> children . push_back ( CATCH_MOVE ( m_rootSection ) );
m_testCases . push_back ( CATCH_MOVE ( node ) );
2020-09-08 15:53:08 +02:00
assert ( m_deepestSection );
m_deepestSection -> stdOut = testCaseStats . stdOut ;
m_deepestSection -> stdErr = testCaseStats . stdErr ;
}
void CumulativeReporterBase :: testRunEnded ( TestRunStats const & testRunStats ) {
2022-01-03 23:16:39 +01:00
assert ( ! m_testRun && "CumulativeReporterBase assumes there can only be one test run" );
m_testRun = Detail :: make_unique < TestRunNode > ( testRunStats );
m_testRun -> children . swap ( m_testCases );
2020-09-08 15:53:08 +02:00
testRunEndedCumulative ();
}
2022-01-03 23:16:39 +01:00
void CumulativeReporterBase :: listReporters ( std :: vector < ReporterDescription > const & descriptions ) {
defaultListReporters ( m_stream , descriptions , m_config -> verbosity ());
}
void CumulativeReporterBase :: listTests ( std :: vector < TestCaseHandle > const & tests ) {
defaultListTests ( m_stream ,
tests ,
m_config -> hasTestFilters (),
m_config -> verbosity ());
}
void CumulativeReporterBase :: listTags ( std :: vector < TagInfo > const & tags ) {
defaultListTags ( m_stream , tags , m_config -> hasTestFilters () );
}
bool CumulativeReporterBase :: SectionNode :: hasAnyAssertions () const {
return std :: any_of (
assertionsAndBenchmarks . begin (),
assertionsAndBenchmarks . end (),
[]( Detail :: AssertionOrBenchmarkResult const & res ) {
return res . isAssertion ();
} );
}
2020-09-08 15:53:08 +02:00
} // end namespace Catch
#include <cassert>
#include <ctime>
#include <algorithm>
2022-01-03 23:16:39 +01:00
#include <iomanip>
2020-09-08 15:53:08 +02:00
namespace Catch {
namespace {
std :: string getCurrentTimestamp () {
time_t rawtime ;
std :: time ( & rawtime );
std :: tm timeInfo = {};
2022-01-03 23:16:39 +01:00
#if defined (_MSC_VER) || defined (__MINGW32__)
2020-09-08 15:53:08 +02:00
gmtime_s ( & timeInfo , & rawtime );
#else
2022-01-03 23:16:39 +01:00
gmtime_r ( & rawtime , & timeInfo );
2020-09-08 15:53:08 +02:00
#endif
2022-01-03 23:16:39 +01:00
auto const timeStampSize = sizeof ( "2017-01-16T17:06:45Z" );
2020-09-08 15:53:08 +02:00
char timeStamp [ timeStampSize ];
const char * const fmt = "%Y-%m-%dT%H:%M:%SZ" ;
std :: strftime ( timeStamp , timeStampSize , fmt , & timeInfo );
2022-01-03 23:16:39 +01:00
return std :: string ( timeStamp , timeStampSize - 1 );
2020-09-08 15:53:08 +02:00
}
std :: string fileNameTag ( std :: vector < Tag > const & tags ) {
auto it = std :: find_if ( begin ( tags ),
end ( tags ),
[] ( Tag const & tag ) {
return tag . original . size () > 0
&& tag . original [ 0 ] == '#' ; });
if ( it != tags . end ()) {
return static_cast < std :: string > (
it -> original . substr ( 1 , it -> original . size () - 1 )
);
}
return std :: string ();
}
2022-01-03 23:16:39 +01:00
// Formats the duration in seconds to 3 decimal places.
// This is done because some genius defined Maven Surefire schema
// in a way that only accepts 3 decimal places, and tools like
// Jenkins use that schema for validation JUnit reporter output.
std :: string formatDuration ( double seconds ) {
ReusableStringStream rss ;
rss << std :: fixed << std :: setprecision ( 3 ) << seconds ;
return rss . str ();
}
2020-09-08 15:53:08 +02:00
} // anonymous namespace
JunitReporter :: JunitReporter ( ReporterConfig const & _config )
: CumulativeReporterBase ( _config ),
2022-01-03 23:16:39 +01:00
xml ( m_stream )
2020-09-08 15:53:08 +02:00
{
m_preferences . shouldRedirectStdOut = true ;
m_preferences . shouldReportAllAssertions = true ;
2022-01-03 23:16:39 +01:00
m_shouldStoreSuccesfulAssertions = false ;
2020-09-08 15:53:08 +02:00
}
std :: string JunitReporter :: getDescription () {
return "Reports test results in an XML format that looks like Ant's junitreport target" ;
}
void JunitReporter :: testRunStarting ( TestRunInfo const & runInfo ) {
CumulativeReporterBase :: testRunStarting ( runInfo );
xml . startElement ( "testsuites" );
suiteTimer . start ();
stdOutForSuite . clear ();
stdErrForSuite . clear ();
unexpectedExceptions = 0 ;
}
void JunitReporter :: testCaseStarting ( TestCaseInfo const & testCaseInfo ) {
m_okToFail = testCaseInfo . okToFail ();
}
2022-01-03 23:16:39 +01:00
void JunitReporter :: assertionEnded ( AssertionStats const & assertionStats ) {
2020-09-08 15:53:08 +02:00
if ( assertionStats . assertionResult . getResultType () == ResultWas :: ThrewException && ! m_okToFail )
unexpectedExceptions ++ ;
2022-01-03 23:16:39 +01:00
CumulativeReporterBase :: assertionEnded ( assertionStats );
2020-09-08 15:53:08 +02:00
}
void JunitReporter :: testCaseEnded ( TestCaseStats const & testCaseStats ) {
stdOutForSuite += testCaseStats . stdOut ;
stdErrForSuite += testCaseStats . stdErr ;
CumulativeReporterBase :: testCaseEnded ( testCaseStats );
}
void JunitReporter :: testRunEndedCumulative () {
2022-01-03 23:16:39 +01:00
const auto suiteTime = suiteTimer . getElapsedSeconds ();
writeRun ( * m_testRun , suiteTime );
2020-09-08 15:53:08 +02:00
xml . endElement ();
}
2022-01-03 23:16:39 +01:00
void JunitReporter :: writeRun ( TestRunNode const & testRunNode , double suiteTime ) {
2020-09-08 15:53:08 +02:00
XmlWriter :: ScopedElement e = xml . scopedElement ( "testsuite" );
2022-01-03 23:16:39 +01:00
TestRunStats const & stats = testRunNode . value ;
xml . writeAttribute ( "name" _sr , stats . runInfo . name );
xml . writeAttribute ( "errors" _sr , unexpectedExceptions );
xml . writeAttribute ( "failures" _sr , stats . totals . assertions . failed - unexpectedExceptions );
xml . writeAttribute ( "tests" _sr , stats . totals . assertions . total () );
xml . writeAttribute ( "hostname" _sr , "tbd" _sr ); // !TBD
2020-09-08 15:53:08 +02:00
if ( m_config -> showDurations () == ShowDurations :: Never )
2022-01-03 23:16:39 +01:00
xml . writeAttribute ( "time" _sr , "" _sr );
2020-09-08 15:53:08 +02:00
else
2022-01-03 23:16:39 +01:00
xml . writeAttribute ( "time" _sr , formatDuration ( suiteTime ) );
xml . writeAttribute ( "timestamp" _sr , getCurrentTimestamp () );
2020-09-08 15:53:08 +02:00
2022-01-03 23:16:39 +01:00
// Write properties
{
2020-09-08 15:53:08 +02:00
auto properties = xml . scopedElement ( "properties" );
2022-01-03 23:16:39 +01:00
xml . scopedElement ( "property" )
. writeAttribute ( "name" _sr , "random-seed" _sr )
. writeAttribute ( "value" _sr , m_config -> rngSeed ());
2020-09-08 15:53:08 +02:00
if ( m_config -> hasTestFilters ()) {
xml . scopedElement ( "property" )
2022-01-03 23:16:39 +01:00
. writeAttribute ( "name" _sr , "filters" _sr )
. writeAttribute ( "value" _sr , serializeFilters ( m_config -> getTestsOrTags ()));
2020-09-08 15:53:08 +02:00
}
}
// Write test cases
2022-01-03 23:16:39 +01:00
for ( auto const & child : testRunNode . children )
2020-09-08 15:53:08 +02:00
writeTestCase ( * child );
xml . scopedElement ( "system-out" ). writeText ( trim ( stdOutForSuite ), XmlFormatting :: Newline );
xml . scopedElement ( "system-err" ). writeText ( trim ( stdErrForSuite ), XmlFormatting :: Newline );
}
void JunitReporter :: writeTestCase ( TestCaseNode const & testCaseNode ) {
TestCaseStats const & stats = testCaseNode . value ;
// All test cases have exactly one section - which represents the
// test case itself. That section may have 0-n nested sections
assert ( testCaseNode . children . size () == 1 );
SectionNode const & rootSection = * testCaseNode . children . front ();
2022-01-03 23:16:39 +01:00
std :: string className =
static_cast < std :: string > ( stats . testInfo -> className );
2020-09-08 15:53:08 +02:00
if ( className . empty () ) {
className = fileNameTag ( stats . testInfo -> tags );
2022-01-03 23:16:39 +01:00
if ( className . empty () ) {
2020-09-08 15:53:08 +02:00
className = "global" ;
2022-01-03 23:16:39 +01:00
}
2020-09-08 15:53:08 +02:00
}
if ( ! m_config -> name (). empty () )
2022-01-03 23:16:39 +01:00
className = static_cast < std :: string > ( m_config -> name ()) + '.' + className ;
2020-09-08 15:53:08 +02:00
2022-01-03 23:16:39 +01:00
writeSection ( className , "" , rootSection , stats . testInfo -> okToFail () );
2020-09-08 15:53:08 +02:00
}
2022-01-03 23:16:39 +01:00
void JunitReporter :: writeSection ( std :: string const & className ,
std :: string const & rootName ,
SectionNode const & sectionNode ,
bool testOkToFail ) {
2020-09-08 15:53:08 +02:00
std :: string name = trim ( sectionNode . stats . sectionInfo . name );
if ( ! rootName . empty () )
name = rootName + '/' + name ;
2022-01-03 23:16:39 +01:00
if ( sectionNode . hasAnyAssertions ()
|| ! sectionNode . stdOut . empty ()
|| ! sectionNode . stdErr . empty () ) {
2020-09-08 15:53:08 +02:00
XmlWriter :: ScopedElement e = xml . scopedElement ( "testcase" );
if ( className . empty () ) {
2022-01-03 23:16:39 +01:00
xml . writeAttribute ( "classname" _sr , name );
xml . writeAttribute ( "name" _sr , "root" _sr );
2020-09-08 15:53:08 +02:00
}
else {
2022-01-03 23:16:39 +01:00
xml . writeAttribute ( "classname" _sr , className );
xml . writeAttribute ( "name" _sr , name );
2020-09-08 15:53:08 +02:00
}
2022-01-03 23:16:39 +01:00
xml . writeAttribute ( "time" _sr , formatDuration ( sectionNode . stats . durationInSeconds ) );
2020-09-08 15:53:08 +02:00
// This is not ideal, but it should be enough to mimic gtest's
// junit output.
// Ideally the JUnit reporter would also handle `skipTest`
// events and write those out appropriately.
2022-01-03 23:16:39 +01:00
xml . writeAttribute ( "status" _sr , "run" _sr );
if ( sectionNode . stats . assertions . failedButOk ) {
xml . scopedElement ( "skipped" )
. writeAttribute ( "message" , "TEST_CASE tagged with !mayfail" );
}
2020-09-08 15:53:08 +02:00
writeAssertions ( sectionNode );
2022-01-03 23:16:39 +01:00
2020-09-08 15:53:08 +02:00
if ( ! sectionNode . stdOut . empty () )
xml . scopedElement ( "system-out" ). writeText ( trim ( sectionNode . stdOut ), XmlFormatting :: Newline );
if ( ! sectionNode . stdErr . empty () )
xml . scopedElement ( "system-err" ). writeText ( trim ( sectionNode . stdErr ), XmlFormatting :: Newline );
}
for ( auto const & childNode : sectionNode . childSections )
if ( className . empty () )
2022-01-03 23:16:39 +01:00
writeSection ( name , "" , * childNode , testOkToFail );
2020-09-08 15:53:08 +02:00
else
2022-01-03 23:16:39 +01:00
writeSection ( className , name , * childNode , testOkToFail );
2020-09-08 15:53:08 +02:00
}
void JunitReporter :: writeAssertions ( SectionNode const & sectionNode ) {
2022-01-03 23:16:39 +01:00
for ( auto const & assertionOrBenchmark : sectionNode . assertionsAndBenchmarks ) {
if ( assertionOrBenchmark . isAssertion ()) {
writeAssertion ( assertionOrBenchmark . asAssertion ());
}
}
2020-09-08 15:53:08 +02:00
}
void JunitReporter :: writeAssertion ( AssertionStats const & stats ) {
AssertionResult const & result = stats . assertionResult ;
if ( ! result . isOk () ) {
std :: string elementName ;
switch ( result . getResultType () ) {
case ResultWas :: ThrewException :
case ResultWas :: FatalErrorCondition :
elementName = "error" ;
break ;
case ResultWas :: ExplicitFailure :
case ResultWas :: ExpressionFailed :
case ResultWas :: DidntThrowException :
elementName = "failure" ;
break ;
// We should never see these here:
case ResultWas :: Info :
case ResultWas :: Warning :
case ResultWas :: Ok :
case ResultWas :: Unknown :
case ResultWas :: FailureBit :
case ResultWas :: Exception :
elementName = "internalError" ;
break ;
}
XmlWriter :: ScopedElement e = xml . scopedElement ( elementName );
2022-01-03 23:16:39 +01:00
xml . writeAttribute ( "message" _sr , result . getExpression () );
xml . writeAttribute ( "type" _sr , result . getTestMacroName () );
2020-09-08 15:53:08 +02:00
ReusableStringStream rss ;
if ( stats . totals . assertions . total () > 0 ) {
rss << "FAILED" << ": \n " ;
if ( result . hasExpression ()) {
rss << " " ;
rss << result . getExpressionInMacro ();
rss << '\n' ;
}
if ( result . hasExpandedExpression ()) {
rss << "with expansion: \n " ;
rss << TextFlow :: Column ( result . getExpandedExpression ()). indent ( 2 ) << '\n' ;
}
} else {
rss << '\n' ;
}
if ( ! result . getMessage (). empty () )
rss << result . getMessage () << '\n' ;
for ( auto const & msg : stats . infoMessages )
if ( msg . type == ResultWas :: Info )
rss << msg . message << '\n' ;
rss << "at " << result . getSourceInfo ();
xml . writeText ( rss . str (), XmlFormatting :: Newline );
}
}
} // end namespace Catch
2022-01-03 23:16:39 +01:00
2020-09-08 15:53:08 +02:00
#include <cassert>
namespace Catch {
2022-01-03 23:16:39 +01:00
void ListeningReporter :: updatePreferences ( IStreamingReporter const & reporterish ) {
m_preferences . shouldRedirectStdOut |=
reporterish . getPreferences (). shouldRedirectStdOut ;
m_preferences . shouldReportAllAssertions |=
reporterish . getPreferences (). shouldReportAllAssertions ;
2020-09-08 15:53:08 +02:00
}
void ListeningReporter :: addListener ( IStreamingReporterPtr && listener ) {
2022-01-03 23:16:39 +01:00
updatePreferences ( * listener );
m_reporterLikes . insert ( m_reporterLikes . begin () + m_insertedListeners , CATCH_MOVE ( listener ) );
++ m_insertedListeners ;
2020-09-08 15:53:08 +02:00
}
2022-01-03 23:16:39 +01:00
void ListeningReporter :: addReporter ( IStreamingReporterPtr && reporter ) {
updatePreferences ( * reporter );
// We will need to output the captured stdout if there are reporters
// that do not want it captured.
// We do not consider listeners, because it is generally assumed that
// listeners are output-transparent, even though they can ask for stdout
// capture to do something with it.
m_haveNoncapturingReporters |= ! reporter -> getPreferences (). shouldRedirectStdOut ;
// Reporters can always be placed to the back without breaking the
// reporting order
m_reporterLikes . push_back ( CATCH_MOVE ( reporter ) );
2020-09-08 15:53:08 +02:00
}
2022-01-03 23:16:39 +01:00
void ListeningReporter :: noMatchingTestCases ( StringRef unmatchedSpec ) {
for ( auto & reporterish : m_reporterLikes ) {
reporterish -> noMatchingTestCases ( unmatchedSpec );
2020-09-08 15:53:08 +02:00
}
}
2022-01-03 23:16:39 +01:00
void ListeningReporter :: fatalErrorEncountered ( StringRef error ) {
for ( auto & reporterish : m_reporterLikes ) {
reporterish -> fatalErrorEncountered ( error );
2020-09-08 15:53:08 +02:00
}
}
2022-01-03 23:16:39 +01:00
void ListeningReporter :: reportInvalidTestSpec ( StringRef arg ) {
for ( auto & reporterish : m_reporterLikes ) {
reporterish -> reportInvalidTestSpec ( arg );
}
}
void ListeningReporter :: benchmarkPreparing ( StringRef name ) {
for ( auto & reporterish : m_reporterLikes ) {
reporterish -> benchmarkPreparing ( name );
2020-09-08 15:53:08 +02:00
}
}
void ListeningReporter :: benchmarkStarting ( BenchmarkInfo const & benchmarkInfo ) {
2022-01-03 23:16:39 +01:00
for ( auto & reporterish : m_reporterLikes ) {
reporterish -> benchmarkStarting ( benchmarkInfo );
2020-09-08 15:53:08 +02:00
}
}
void ListeningReporter :: benchmarkEnded ( BenchmarkStats <> const & benchmarkStats ) {
2022-01-03 23:16:39 +01:00
for ( auto & reporterish : m_reporterLikes ) {
reporterish -> benchmarkEnded ( benchmarkStats );
2020-09-08 15:53:08 +02:00
}
}
2022-01-03 23:16:39 +01:00
void ListeningReporter :: benchmarkFailed ( StringRef error ) {
for ( auto & reporterish : m_reporterLikes ) {
reporterish -> benchmarkFailed ( error );
2020-09-08 15:53:08 +02:00
}
}
void ListeningReporter :: testRunStarting ( TestRunInfo const & testRunInfo ) {
2022-01-03 23:16:39 +01:00
for ( auto & reporterish : m_reporterLikes ) {
reporterish -> testRunStarting ( testRunInfo );
2020-09-08 15:53:08 +02:00
}
}
void ListeningReporter :: testCaseStarting ( TestCaseInfo const & testInfo ) {
2022-01-03 23:16:39 +01:00
for ( auto & reporterish : m_reporterLikes ) {
reporterish -> testCaseStarting ( testInfo );
}
}
void
ListeningReporter :: testCasePartialStarting ( TestCaseInfo const & testInfo ,
uint64_t partNumber ) {
for ( auto & reporterish : m_reporterLikes ) {
reporterish -> testCasePartialStarting ( testInfo , partNumber );
2020-09-08 15:53:08 +02:00
}
}
void ListeningReporter :: sectionStarting ( SectionInfo const & sectionInfo ) {
2022-01-03 23:16:39 +01:00
for ( auto & reporterish : m_reporterLikes ) {
reporterish -> sectionStarting ( sectionInfo );
2020-09-08 15:53:08 +02:00
}
}
void ListeningReporter :: assertionStarting ( AssertionInfo const & assertionInfo ) {
2022-01-03 23:16:39 +01:00
for ( auto & reporterish : m_reporterLikes ) {
reporterish -> assertionStarting ( assertionInfo );
2020-09-08 15:53:08 +02:00
}
}
// The return value indicates if the messages buffer should be cleared:
2022-01-03 23:16:39 +01:00
void ListeningReporter :: assertionEnded ( AssertionStats const & assertionStats ) {
const bool reportByDefault =
assertionStats . assertionResult . getResultType () != ResultWas :: Ok ||
m_config -> includeSuccessfulResults ();
for ( auto & reporterish : m_reporterLikes ) {
if ( reportByDefault ||
reporterish -> getPreferences (). shouldReportAllAssertions ) {
reporterish -> assertionEnded ( assertionStats );
}
2020-09-08 15:53:08 +02:00
}
}
void ListeningReporter :: sectionEnded ( SectionStats const & sectionStats ) {
2022-01-03 23:16:39 +01:00
for ( auto & reporterish : m_reporterLikes ) {
reporterish -> sectionEnded ( sectionStats );
}
}
void ListeningReporter :: testCasePartialEnded ( TestCaseStats const & testStats ,
uint64_t partNumber ) {
if ( m_preferences . shouldRedirectStdOut &&
m_haveNoncapturingReporters ) {
if ( ! testStats . stdOut . empty () ) {
Catch :: cout () << testStats . stdOut << std :: flush ;
}
if ( ! testStats . stdErr . empty () ) {
Catch :: cerr () << testStats . stdErr << std :: flush ;
}
}
for ( auto & reporterish : m_reporterLikes ) {
reporterish -> testCasePartialEnded ( testStats , partNumber );
2020-09-08 15:53:08 +02:00
}
}
void ListeningReporter :: testCaseEnded ( TestCaseStats const & testCaseStats ) {
2022-01-03 23:16:39 +01:00
for ( auto & reporterish : m_reporterLikes ) {
reporterish -> testCaseEnded ( testCaseStats );
2020-09-08 15:53:08 +02:00
}
}
void ListeningReporter :: testRunEnded ( TestRunStats const & testRunStats ) {
2022-01-03 23:16:39 +01:00
for ( auto & reporterish : m_reporterLikes ) {
reporterish -> testRunEnded ( testRunStats );
2020-09-08 15:53:08 +02:00
}
}
void ListeningReporter :: skipTest ( TestCaseInfo const & testInfo ) {
2022-01-03 23:16:39 +01:00
for ( auto & reporterish : m_reporterLikes ) {
reporterish -> skipTest ( testInfo );
2020-09-08 15:53:08 +02:00
}
}
2022-01-03 23:16:39 +01:00
void ListeningReporter :: listReporters ( std :: vector < ReporterDescription > const & descriptions ) {
for ( auto & reporterish : m_reporterLikes ) {
reporterish -> listReporters ( descriptions );
2020-09-08 15:53:08 +02:00
}
}
2022-01-03 23:16:39 +01:00
void ListeningReporter :: listTests ( std :: vector < TestCaseHandle > const & tests ) {
for ( auto & reporterish : m_reporterLikes ) {
reporterish -> listTests ( tests );
2020-09-08 15:53:08 +02:00
}
}
2022-01-03 23:16:39 +01:00
void ListeningReporter :: listTags ( std :: vector < TagInfo > const & tags ) {
for ( auto & reporterish : m_reporterLikes ) {
reporterish -> listTags ( tags );
2020-09-08 15:53:08 +02:00
}
}
} // end namespace Catch
#include <map>
namespace Catch {
void SonarQubeReporter :: testRunStarting ( TestRunInfo const & testRunInfo ) {
CumulativeReporterBase :: testRunStarting ( testRunInfo );
xml . startElement ( "testExecutions" );
2022-01-03 23:16:39 +01:00
xml . writeAttribute ( "version" _sr , '1' );
2020-09-08 15:53:08 +02:00
}
2022-01-03 23:16:39 +01:00
void SonarQubeReporter :: writeRun ( TestRunNode const & runNode ) {
std :: map < std :: string , std :: vector < TestCaseNode const *>> testsPerFile ;
for ( auto const & child : runNode . children ) {
testsPerFile [ child -> value . testInfo -> lineInfo . file ]. push_back (
child . get () );
}
for ( auto const & kv : testsPerFile ) {
writeTestFile ( kv . first , kv . second );
}
2020-09-08 15:53:08 +02:00
}
2022-01-03 23:16:39 +01:00
void SonarQubeReporter :: writeTestFile ( std :: string const & filename , std :: vector < TestCaseNode const *> const & testCaseNodes ) {
2020-09-08 15:53:08 +02:00
XmlWriter :: ScopedElement e = xml . scopedElement ( "file" );
2022-01-03 23:16:39 +01:00
xml . writeAttribute ( "path" _sr , filename );
2020-09-08 15:53:08 +02:00
for ( auto const & child : testCaseNodes )
writeTestCase ( * child );
}
void SonarQubeReporter :: writeTestCase ( TestCaseNode const & testCaseNode ) {
// All test cases have exactly one section - which represents the
// test case itself. That section may have 0-n nested sections
assert ( testCaseNode . children . size () == 1 );
SectionNode const & rootSection = * testCaseNode . children . front ();
writeSection ( "" , rootSection , testCaseNode . value . testInfo -> okToFail ());
}
void SonarQubeReporter :: writeSection ( std :: string const & rootName , SectionNode const & sectionNode , bool okToFail ) {
std :: string name = trim ( sectionNode . stats . sectionInfo . name );
if ( ! rootName . empty ())
name = rootName + '/' + name ;
2022-01-03 23:16:39 +01:00
if ( sectionNode . hasAnyAssertions ()
|| ! sectionNode . stdOut . empty ()
|| ! sectionNode . stdErr . empty () ) {
2020-09-08 15:53:08 +02:00
XmlWriter :: ScopedElement e = xml . scopedElement ( "testCase" );
2022-01-03 23:16:39 +01:00
xml . writeAttribute ( "name" _sr , name );
xml . writeAttribute ( "duration" _sr , static_cast < long > ( sectionNode . stats . durationInSeconds * 1000 ));
2020-09-08 15:53:08 +02:00
writeAssertions ( sectionNode , okToFail );
}
for ( auto const & childNode : sectionNode . childSections )
writeSection ( name , * childNode , okToFail );
}
void SonarQubeReporter :: writeAssertions ( SectionNode const & sectionNode , bool okToFail ) {
2022-01-03 23:16:39 +01:00
for ( auto const & assertionOrBenchmark : sectionNode . assertionsAndBenchmarks ) {
if ( assertionOrBenchmark . isAssertion ()) {
writeAssertion ( assertionOrBenchmark . asAssertion (), okToFail );
}
}
2020-09-08 15:53:08 +02:00
}
void SonarQubeReporter :: writeAssertion ( AssertionStats const & stats , bool okToFail ) {
AssertionResult const & result = stats . assertionResult ;
if ( ! result . isOk ()) {
std :: string elementName ;
if ( okToFail ) {
elementName = "skipped" ;
} else {
switch ( result . getResultType ()) {
case ResultWas :: ThrewException :
case ResultWas :: FatalErrorCondition :
elementName = "error" ;
break ;
case ResultWas :: ExplicitFailure :
elementName = "failure" ;
break ;
case ResultWas :: ExpressionFailed :
elementName = "failure" ;
break ;
case ResultWas :: DidntThrowException :
elementName = "failure" ;
break ;
// We should never see these here:
case ResultWas :: Info :
case ResultWas :: Warning :
case ResultWas :: Ok :
case ResultWas :: Unknown :
case ResultWas :: FailureBit :
case ResultWas :: Exception :
elementName = "internalError" ;
break ;
}
}
XmlWriter :: ScopedElement e = xml . scopedElement ( elementName );
ReusableStringStream messageRss ;
2022-01-03 23:16:39 +01:00
messageRss << result . getTestMacroName () << '(' << result . getExpression () << ')' ;
xml . writeAttribute ( "message" _sr , messageRss . str ());
2020-09-08 15:53:08 +02:00
ReusableStringStream textRss ;
if ( stats . totals . assertions . total () > 0 ) {
textRss << "FAILED: \n " ;
if ( result . hasExpression ()) {
2022-01-03 23:16:39 +01:00
textRss << '\t' << result . getExpressionInMacro () << '\n' ;
2020-09-08 15:53:08 +02:00
}
if ( result . hasExpandedExpression ()) {
2022-01-03 23:16:39 +01:00
textRss << "with expansion: \n\t " << result . getExpandedExpression () << '\n' ;
2020-09-08 15:53:08 +02:00
}
}
if ( ! result . getMessage (). empty ())
2022-01-03 23:16:39 +01:00
textRss << result . getMessage () << '\n' ;
2020-09-08 15:53:08 +02:00
for ( auto const & msg : stats . infoMessages )
if ( msg . type == ResultWas :: Info )
2022-01-03 23:16:39 +01:00
textRss << msg . message << '\n' ;
2020-09-08 15:53:08 +02:00
textRss << "at " << result . getSourceInfo ();
xml . writeText ( textRss . str (), XmlFormatting :: Newline );
}
}
} // end namespace Catch
namespace Catch {
StreamingReporterBase ::~ StreamingReporterBase () = default ;
void
StreamingReporterBase :: testRunStarting ( TestRunInfo const & _testRunInfo ) {
currentTestRunInfo = _testRunInfo ;
}
void StreamingReporterBase :: testRunEnded ( TestRunStats const & ) {
currentTestCaseInfo = nullptr ;
2022-01-03 23:16:39 +01:00
}
void StreamingReporterBase :: listReporters ( std :: vector < ReporterDescription > const & descriptions ) {
defaultListReporters ( m_stream , descriptions , m_config -> verbosity () );
}
void StreamingReporterBase :: listTests ( std :: vector < TestCaseHandle > const & tests ) {
defaultListTests ( m_stream ,
tests ,
m_config -> hasTestFilters (),
m_config -> verbosity ());
}
void StreamingReporterBase :: listTags ( std :: vector < TagInfo > const & tags ) {
defaultListTags ( m_stream , tags , m_config -> hasTestFilters () );
2020-09-08 15:53:08 +02:00
}
} // end namespace Catch
#include <algorithm>
#include <ostream>
namespace Catch {
namespace {
// Yes, this has to be outside the class and namespaced by naming.
// Making older compiler happy is hard.
static constexpr StringRef tapFailedString = "not ok" _sr ;
static constexpr StringRef tapPassedString = "ok" _sr ;
class TapAssertionPrinter {
public :
TapAssertionPrinter & operator = ( TapAssertionPrinter const & ) = delete ;
TapAssertionPrinter ( TapAssertionPrinter const & ) = delete ;
TapAssertionPrinter ( std :: ostream & _stream , AssertionStats const & _stats , std :: size_t _counter )
: stream ( _stream )
, result ( _stats . assertionResult )
, messages ( _stats . infoMessages )
, itMessage ( _stats . infoMessages . begin ())
, printInfoMessages ( true )
, counter ( _counter ) {}
void print () {
itMessage = messages . begin ();
switch ( result . getResultType ()) {
case ResultWas :: Ok :
printResultType ( tapPassedString );
printOriginalExpression ();
printReconstructedExpression ();
if ( ! result . hasExpression ())
printRemainingMessages ( Colour :: None );
else
printRemainingMessages ();
break ;
case ResultWas :: ExpressionFailed :
if ( result . isOk ()) {
printResultType ( tapPassedString );
} else {
printResultType ( tapFailedString );
}
printOriginalExpression ();
printReconstructedExpression ();
if ( result . isOk ()) {
printIssue ( " # TODO" );
}
printRemainingMessages ();
break ;
case ResultWas :: ThrewException :
printResultType ( tapFailedString );
printIssue ( "unexpected exception with message:" _sr );
printMessage ();
printExpressionWas ();
printRemainingMessages ();
break ;
case ResultWas :: FatalErrorCondition :
printResultType ( tapFailedString );
printIssue ( "fatal error condition with message:" _sr );
printMessage ();
printExpressionWas ();
printRemainingMessages ();
break ;
case ResultWas :: DidntThrowException :
printResultType ( tapFailedString );
printIssue ( "expected exception, got none" _sr );
printExpressionWas ();
printRemainingMessages ();
break ;
case ResultWas :: Info :
printResultType ( "info" _sr );
printMessage ();
printRemainingMessages ();
break ;
case ResultWas :: Warning :
printResultType ( "warning" _sr );
printMessage ();
printRemainingMessages ();
break ;
case ResultWas :: ExplicitFailure :
printResultType ( tapFailedString );
printIssue ( "explicitly" _sr );
printRemainingMessages ( Colour :: None );
break ;
// These cases are here to prevent compiler warnings
case ResultWas :: Unknown :
case ResultWas :: FailureBit :
case ResultWas :: Exception :
printResultType ( "** internal error **" _sr );
break ;
}
}
private :
static Colour :: Code dimColour () { return Colour :: FileName ; }
void printSourceInfo () const {
Colour colourGuard ( dimColour ());
stream << result . getSourceInfo () << ':' ;
}
void printResultType ( StringRef passOrFail ) const {
if ( ! passOrFail . empty ()) {
stream << passOrFail << ' ' << counter << " -" ;
}
}
void printIssue ( StringRef issue ) const {
stream << ' ' << issue ;
}
void printExpressionWas () {
if ( result . hasExpression ()) {
stream << ';' ;
{
Colour colour ( dimColour ());
stream << " expression was:" ;
}
printOriginalExpression ();
}
}
void printOriginalExpression () const {
if ( result . hasExpression ()) {
stream << ' ' << result . getExpression ();
}
}
void printReconstructedExpression () const {
if ( result . hasExpandedExpression ()) {
{
Colour colour ( dimColour ());
stream << " for: " ;
}
std :: string expr = result . getExpandedExpression ();
std :: replace ( expr . begin (), expr . end (), '\n' , ' ' );
stream << expr ;
}
}
void printMessage () {
if ( itMessage != messages . end ()) {
stream << " '" << itMessage -> message << '\'' ;
++ itMessage ;
}
}
void printRemainingMessages ( Colour :: Code colour = dimColour ()) {
if ( itMessage == messages . end ()) {
return ;
}
// using messages.end() directly (or auto) yields compilation error:
std :: vector < MessageInfo >:: const_iterator itEnd = messages . end ();
const std :: size_t N = static_cast < std :: size_t > ( std :: distance ( itMessage , itEnd ));
{
Colour colourGuard ( colour );
2022-01-03 23:16:39 +01:00
stream << " with " << pluralise ( N , "message" _sr ) << ':' ;
2020-09-08 15:53:08 +02:00
}
for (; itMessage != itEnd ; ) {
// If this assertion is a warning ignore any INFO messages
if ( printInfoMessages || itMessage -> type != ResultWas :: Info ) {
stream << " '" << itMessage -> message << '\'' ;
if ( ++ itMessage != itEnd ) {
Colour colourGuard ( dimColour ());
stream << " and" ;
}
}
}
}
private :
std :: ostream & stream ;
AssertionResult const & result ;
std :: vector < MessageInfo > messages ;
std :: vector < MessageInfo >:: const_iterator itMessage ;
bool printInfoMessages ;
std :: size_t counter ;
};
} // End anonymous namespace
2022-01-03 23:16:39 +01:00
void TAPReporter :: noMatchingTestCases ( StringRef unmatchedSpec ) {
m_stream << "# No test cases matched '" << unmatchedSpec << "' \n " ;
2020-09-08 15:53:08 +02:00
}
2022-01-03 23:16:39 +01:00
void TAPReporter :: assertionEnded ( AssertionStats const & _assertionStats ) {
2020-09-08 15:53:08 +02:00
++ counter ;
2022-01-03 23:16:39 +01:00
m_stream << "# " << currentTestCaseInfo -> name << '\n' ;
TapAssertionPrinter printer ( m_stream , _assertionStats , counter );
2020-09-08 15:53:08 +02:00
printer . print ();
2022-01-03 23:16:39 +01:00
m_stream << '\n' << std :: flush ;
2020-09-08 15:53:08 +02:00
}
void TAPReporter :: testRunEnded ( TestRunStats const & _testRunStats ) {
2022-01-03 23:16:39 +01:00
m_stream << "1.." << _testRunStats . totals . assertions . total ();
2020-09-08 15:53:08 +02:00
if ( _testRunStats . totals . testCases . total () == 0 ) {
2022-01-03 23:16:39 +01:00
m_stream << " # Skipped: No tests ran." ;
2020-09-08 15:53:08 +02:00
}
2022-01-03 23:16:39 +01:00
m_stream << " \n\n " << std :: flush ;
2020-09-08 15:53:08 +02:00
StreamingReporterBase :: testRunEnded ( _testRunStats );
}
} // end namespace Catch
#include <cassert>
namespace Catch {
namespace {
// if string has a : in first line will set indent to follow it on
// subsequent lines
void printHeaderString ( std :: ostream & os , std :: string const & _string , std :: size_t indent = 0 ) {
std :: size_t i = _string . find ( ": " );
if ( i != std :: string :: npos )
i += 2 ;
else
i = 0 ;
os << TextFlow :: Column ( _string )
. indent ( indent + i )
. initialIndent ( indent ) << '\n' ;
}
2022-01-03 23:16:39 +01:00
std :: string escape ( StringRef str ) {
std :: string escaped = static_cast < std :: string > ( str );
2020-09-08 15:53:08 +02:00
replaceInPlace ( escaped , "|" , "||" );
replaceInPlace ( escaped , "'" , "|'" );
replaceInPlace ( escaped , " \n " , "|n" );
replaceInPlace ( escaped , " \r " , "|r" );
replaceInPlace ( escaped , "[" , "|[" );
replaceInPlace ( escaped , "]" , "|]" );
return escaped ;
}
} // end anonymous namespace
TeamCityReporter ::~ TeamCityReporter () {}
2022-01-03 23:16:39 +01:00
void TeamCityReporter :: testRunStarting ( TestRunInfo const & runInfo ) {
m_stream << "##teamcity[testSuiteStarted name='" << escape ( runInfo . name )
<< "'] \n " ;
2020-09-08 15:53:08 +02:00
}
2022-01-03 23:16:39 +01:00
void TeamCityReporter :: testRunEnded ( TestRunStats const & runStats ) {
m_stream << "##teamcity[testSuiteFinished name='"
<< escape ( runStats . runInfo . name ) << "'] \n " ;
2020-09-08 15:53:08 +02:00
}
2022-01-03 23:16:39 +01:00
void TeamCityReporter :: assertionEnded ( AssertionStats const & assertionStats ) {
2020-09-08 15:53:08 +02:00
AssertionResult const & result = assertionStats . assertionResult ;
if ( ! result . isOk ()) {
ReusableStringStream msg ;
if ( ! m_headerPrintedForThisSection )
printSectionHeader ( msg . get ());
m_headerPrintedForThisSection = true ;
msg << result . getSourceInfo () << '\n' ;
switch ( result . getResultType ()) {
case ResultWas :: ExpressionFailed :
msg << "expression failed" ;
break ;
case ResultWas :: ThrewException :
msg << "unexpected exception" ;
break ;
case ResultWas :: FatalErrorCondition :
msg << "fatal error condition" ;
break ;
case ResultWas :: DidntThrowException :
msg << "no exception was thrown where one was expected" ;
break ;
case ResultWas :: ExplicitFailure :
msg << "explicit failure" ;
break ;
// We shouldn't get here because of the isOk() test
case ResultWas :: Ok :
case ResultWas :: Info :
case ResultWas :: Warning :
CATCH_ERROR ( "Internal error in TeamCity reporter" );
// These cases are here to prevent compiler warnings
case ResultWas :: Unknown :
case ResultWas :: FailureBit :
case ResultWas :: Exception :
CATCH_ERROR ( "Not implemented" );
}
if ( assertionStats . infoMessages . size () == 1 )
msg << " with message:" ;
if ( assertionStats . infoMessages . size () > 1 )
msg << " with messages:" ;
for ( auto const & messageInfo : assertionStats . infoMessages )
msg << " \n \" " << messageInfo . message << '"' ;
if ( result . hasExpression ()) {
msg <<
" \n " << result . getExpressionInMacro () << " \n "
"with expansion: \n "
" " << result . getExpandedExpression () << '\n' ;
}
if ( currentTestCaseInfo -> okToFail ()) {
msg << "- failure ignore as test marked as 'ok to fail' \n " ;
2022-01-03 23:16:39 +01:00
m_stream << "##teamcity[testIgnored"
2020-09-08 15:53:08 +02:00
<< " name='" << escape ( currentTestCaseInfo -> name ) << '\''
<< " message='" << escape ( msg . str ()) << '\''
<< "] \n " ;
} else {
2022-01-03 23:16:39 +01:00
m_stream << "##teamcity[testFailed"
2020-09-08 15:53:08 +02:00
<< " name='" << escape ( currentTestCaseInfo -> name ) << '\''
<< " message='" << escape ( msg . str ()) << '\''
<< "] \n " ;
}
}
2022-01-03 23:16:39 +01:00
m_stream . flush ();
2020-09-08 15:53:08 +02:00
}
void TeamCityReporter :: testCaseStarting ( TestCaseInfo const & testInfo ) {
m_testTimer . start ();
StreamingReporterBase :: testCaseStarting ( testInfo );
2022-01-03 23:16:39 +01:00
m_stream << "##teamcity[testStarted name='"
2020-09-08 15:53:08 +02:00
<< escape ( testInfo . name ) << "'] \n " ;
2022-01-03 23:16:39 +01:00
m_stream . flush ();
2020-09-08 15:53:08 +02:00
}
void TeamCityReporter :: testCaseEnded ( TestCaseStats const & testCaseStats ) {
StreamingReporterBase :: testCaseEnded ( testCaseStats );
auto const & testCaseInfo = * testCaseStats . testInfo ;
if ( ! testCaseStats . stdOut . empty ())
2022-01-03 23:16:39 +01:00
m_stream << "##teamcity[testStdOut name='"
2020-09-08 15:53:08 +02:00
<< escape ( testCaseInfo . name )
<< "' out='" << escape ( testCaseStats . stdOut ) << "'] \n " ;
if ( ! testCaseStats . stdErr . empty ())
2022-01-03 23:16:39 +01:00
m_stream << "##teamcity[testStdErr name='"
2020-09-08 15:53:08 +02:00
<< escape ( testCaseInfo . name )
<< "' out='" << escape ( testCaseStats . stdErr ) << "'] \n " ;
2022-01-03 23:16:39 +01:00
m_stream << "##teamcity[testFinished name='"
2020-09-08 15:53:08 +02:00
<< escape ( testCaseInfo . name ) << "' duration='"
<< m_testTimer . getElapsedMilliseconds () << "'] \n " ;
2022-01-03 23:16:39 +01:00
m_stream . flush ();
2020-09-08 15:53:08 +02:00
}
void TeamCityReporter :: printSectionHeader ( std :: ostream & os ) {
assert ( ! m_sectionStack . empty ());
if ( m_sectionStack . size () > 1 ) {
os << lineOfChars ( '-' ) << '\n' ;
std :: vector < SectionInfo >:: const_iterator
it = m_sectionStack . begin () + 1 , // Skip first section (test case)
itEnd = m_sectionStack . end ();
for (; it != itEnd ; ++ it )
printHeaderString ( os , it -> name );
os << lineOfChars ( '-' ) << '\n' ;
}
SourceLineInfo lineInfo = m_sectionStack . front (). lineInfo ;
os << lineInfo << '\n' ;
os << lineOfChars ( '.' ) << " \n\n " ;
}
} // end namespace Catch
#if defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch
// Note that 4062 (not all labels are handled
// and default is missing) is enabled
#endif
namespace Catch {
XmlReporter :: XmlReporter ( ReporterConfig const & _config )
: StreamingReporterBase ( _config ),
m_xml ( _config . stream ())
{
m_preferences . shouldRedirectStdOut = true ;
m_preferences . shouldReportAllAssertions = true ;
}
XmlReporter ::~ XmlReporter () = default ;
std :: string XmlReporter :: getDescription () {
return "Reports test results as an XML document" ;
}
std :: string XmlReporter :: getStylesheetRef () const {
return std :: string ();
}
void XmlReporter :: writeSourceInfo ( SourceLineInfo const & sourceInfo ) {
m_xml
2022-01-03 23:16:39 +01:00
. writeAttribute ( "filename" _sr , sourceInfo . file )
. writeAttribute ( "line" _sr , sourceInfo . line );
2020-09-08 15:53:08 +02:00
}
void XmlReporter :: testRunStarting ( TestRunInfo const & testInfo ) {
StreamingReporterBase :: testRunStarting ( testInfo );
std :: string stylesheetRef = getStylesheetRef ();
if ( ! stylesheetRef . empty () )
m_xml . writeStylesheetRef ( stylesheetRef );
2022-01-03 23:16:39 +01:00
m_xml . startElement ( "Catch2TestRun" )
. writeAttribute ( "name" _sr , m_config -> name ())
. writeAttribute ( "rng-seed" _sr , m_config -> rngSeed ());
2020-09-08 15:53:08 +02:00
if ( m_config -> testSpec (). hasFilters ())
2022-01-03 23:16:39 +01:00
m_xml . writeAttribute ( "filters" _sr , serializeFilters ( m_config -> getTestsOrTags () ) );
2020-09-08 15:53:08 +02:00
}
void XmlReporter :: testCaseStarting ( TestCaseInfo const & testInfo ) {
StreamingReporterBase :: testCaseStarting ( testInfo );
m_xml . startElement ( "TestCase" )
2022-01-03 23:16:39 +01:00
. writeAttribute ( "name" _sr , trim ( testInfo . name ) )
. writeAttribute ( "tags" _sr , testInfo . tagsAsString () );
2020-09-08 15:53:08 +02:00
writeSourceInfo ( testInfo . lineInfo );
if ( m_config -> showDurations () == ShowDurations :: Always )
m_testCaseTimer . start ();
m_xml . ensureTagClosed ();
}
void XmlReporter :: sectionStarting ( SectionInfo const & sectionInfo ) {
StreamingReporterBase :: sectionStarting ( sectionInfo );
if ( m_sectionDepth ++ > 0 ) {
m_xml . startElement ( "Section" )
2022-01-03 23:16:39 +01:00
. writeAttribute ( "name" _sr , trim ( sectionInfo . name ) );
2020-09-08 15:53:08 +02:00
writeSourceInfo ( sectionInfo . lineInfo );
m_xml . ensureTagClosed ();
}
}
void XmlReporter :: assertionStarting ( AssertionInfo const & ) { }
2022-01-03 23:16:39 +01:00
void XmlReporter :: assertionEnded ( AssertionStats const & assertionStats ) {
2020-09-08 15:53:08 +02:00
AssertionResult const & result = assertionStats . assertionResult ;
bool includeResults = m_config -> includeSuccessfulResults () || ! result . isOk ();
if ( includeResults || result . getResultType () == ResultWas :: Warning ) {
// Print any info messages in <Info> tags.
for ( auto const & msg : assertionStats . infoMessages ) {
if ( msg . type == ResultWas :: Info && includeResults ) {
m_xml . scopedElement ( "Info" )
. writeText ( msg . message );
} else if ( msg . type == ResultWas :: Warning ) {
m_xml . scopedElement ( "Warning" )
. writeText ( msg . message );
}
}
}
// Drop out if result was successful but we're not printing them.
if ( ! includeResults && result . getResultType () != ResultWas :: Warning )
2022-01-03 23:16:39 +01:00
return ;
2020-09-08 15:53:08 +02:00
// Print the expression if there is one.
if ( result . hasExpression () ) {
m_xml . startElement ( "Expression" )
2022-01-03 23:16:39 +01:00
. writeAttribute ( "success" _sr , result . succeeded () )
. writeAttribute ( "type" _sr , result . getTestMacroName () );
2020-09-08 15:53:08 +02:00
writeSourceInfo ( result . getSourceInfo () );
m_xml . scopedElement ( "Original" )
. writeText ( result . getExpression () );
m_xml . scopedElement ( "Expanded" )
. writeText ( result . getExpandedExpression () );
}
// And... Print a result applicable to each result type.
switch ( result . getResultType () ) {
case ResultWas :: ThrewException :
m_xml . startElement ( "Exception" );
writeSourceInfo ( result . getSourceInfo () );
m_xml . writeText ( result . getMessage () );
m_xml . endElement ();
break ;
case ResultWas :: FatalErrorCondition :
m_xml . startElement ( "FatalErrorCondition" );
writeSourceInfo ( result . getSourceInfo () );
m_xml . writeText ( result . getMessage () );
m_xml . endElement ();
break ;
case ResultWas :: Info :
m_xml . scopedElement ( "Info" )
2022-01-03 23:16:39 +01:00
. writeText ( result . getMessage () );
2020-09-08 15:53:08 +02:00
break ;
case ResultWas :: Warning :
// Warning will already have been written
break ;
case ResultWas :: ExplicitFailure :
m_xml . startElement ( "Failure" );
writeSourceInfo ( result . getSourceInfo () );
m_xml . writeText ( result . getMessage () );
m_xml . endElement ();
break ;
default :
break ;
}
if ( result . hasExpression () )
m_xml . endElement ();
}
void XmlReporter :: sectionEnded ( SectionStats const & sectionStats ) {
StreamingReporterBase :: sectionEnded ( sectionStats );
if ( -- m_sectionDepth > 0 ) {
XmlWriter :: ScopedElement e = m_xml . scopedElement ( "OverallResults" );
2022-01-03 23:16:39 +01:00
e . writeAttribute ( "successes" _sr , sectionStats . assertions . passed );
e . writeAttribute ( "failures" _sr , sectionStats . assertions . failed );
e . writeAttribute ( "expectedFailures" _sr , sectionStats . assertions . failedButOk );
2020-09-08 15:53:08 +02:00
if ( m_config -> showDurations () == ShowDurations :: Always )
2022-01-03 23:16:39 +01:00
e . writeAttribute ( "durationInSeconds" _sr , sectionStats . durationInSeconds );
2020-09-08 15:53:08 +02:00
m_xml . endElement ();
}
}
void XmlReporter :: testCaseEnded ( TestCaseStats const & testCaseStats ) {
StreamingReporterBase :: testCaseEnded ( testCaseStats );
XmlWriter :: ScopedElement e = m_xml . scopedElement ( "OverallResult" );
2022-01-03 23:16:39 +01:00
e . writeAttribute ( "success" _sr , testCaseStats . totals . assertions . allOk () );
2020-09-08 15:53:08 +02:00
if ( m_config -> showDurations () == ShowDurations :: Always )
2022-01-03 23:16:39 +01:00
e . writeAttribute ( "durationInSeconds" _sr , m_testCaseTimer . getElapsedSeconds () );
2020-09-08 15:53:08 +02:00
if ( ! testCaseStats . stdOut . empty () )
m_xml . scopedElement ( "StdOut" ). writeText ( trim ( testCaseStats . stdOut ), XmlFormatting :: Newline );
if ( ! testCaseStats . stdErr . empty () )
m_xml . scopedElement ( "StdErr" ). writeText ( trim ( testCaseStats . stdErr ), XmlFormatting :: Newline );
m_xml . endElement ();
}
void XmlReporter :: testRunEnded ( TestRunStats const & testRunStats ) {
StreamingReporterBase :: testRunEnded ( testRunStats );
m_xml . scopedElement ( "OverallResults" )
2022-01-03 23:16:39 +01:00
. writeAttribute ( "successes" _sr , testRunStats . totals . assertions . passed )
. writeAttribute ( "failures" _sr , testRunStats . totals . assertions . failed )
. writeAttribute ( "expectedFailures" _sr , testRunStats . totals . assertions . failedButOk );
2020-09-08 15:53:08 +02:00
m_xml . scopedElement ( "OverallResultsCases" )
2022-01-03 23:16:39 +01:00
. writeAttribute ( "successes" _sr , testRunStats . totals . testCases . passed )
. writeAttribute ( "failures" _sr , testRunStats . totals . testCases . failed )
. writeAttribute ( "expectedFailures" _sr , testRunStats . totals . testCases . failedButOk );
2020-09-08 15:53:08 +02:00
m_xml . endElement ();
}
2022-01-03 23:16:39 +01:00
void XmlReporter :: benchmarkPreparing ( StringRef name ) {
2020-09-08 15:53:08 +02:00
m_xml . startElement ( "BenchmarkResults" )
2022-01-03 23:16:39 +01:00
. writeAttribute ( "name" _sr , name );
2020-09-08 15:53:08 +02:00
}
void XmlReporter :: benchmarkStarting ( BenchmarkInfo const & info ) {
2022-01-03 23:16:39 +01:00
m_xml . writeAttribute ( "samples" _sr , info . samples )
. writeAttribute ( "resamples" _sr , info . resamples )
. writeAttribute ( "iterations" _sr , info . iterations )
. writeAttribute ( "clockResolution" _sr , info . clockResolution )
. writeAttribute ( "estimatedDuration" _sr , info . estimatedDuration )
. writeComment ( "All values in nano seconds" _sr );
2020-09-08 15:53:08 +02:00
}
void XmlReporter :: benchmarkEnded ( BenchmarkStats <> const & benchmarkStats ) {
m_xml . startElement ( "mean" )
2022-01-03 23:16:39 +01:00
. writeAttribute ( "value" _sr , benchmarkStats . mean . point . count ())
. writeAttribute ( "lowerBound" _sr , benchmarkStats . mean . lower_bound . count ())
. writeAttribute ( "upperBound" _sr , benchmarkStats . mean . upper_bound . count ())
. writeAttribute ( "ci" _sr , benchmarkStats . mean . confidence_interval );
2020-09-08 15:53:08 +02:00
m_xml . endElement ();
m_xml . startElement ( "standardDeviation" )
2022-01-03 23:16:39 +01:00
. writeAttribute ( "value" _sr , benchmarkStats . standardDeviation . point . count ())
. writeAttribute ( "lowerBound" _sr , benchmarkStats . standardDeviation . lower_bound . count ())
. writeAttribute ( "upperBound" _sr , benchmarkStats . standardDeviation . upper_bound . count ())
. writeAttribute ( "ci" _sr , benchmarkStats . standardDeviation . confidence_interval );
2020-09-08 15:53:08 +02:00
m_xml . endElement ();
m_xml . startElement ( "outliers" )
2022-01-03 23:16:39 +01:00
. writeAttribute ( "variance" _sr , benchmarkStats . outlierVariance )
. writeAttribute ( "lowMild" _sr , benchmarkStats . outliers . low_mild )
. writeAttribute ( "lowSevere" _sr , benchmarkStats . outliers . low_severe )
. writeAttribute ( "highMild" _sr , benchmarkStats . outliers . high_mild )
. writeAttribute ( "highSevere" _sr , benchmarkStats . outliers . high_severe );
2020-09-08 15:53:08 +02:00
m_xml . endElement ();
m_xml . endElement ();
}
2022-01-03 23:16:39 +01:00
void XmlReporter :: benchmarkFailed ( StringRef error ) {
2020-09-08 15:53:08 +02:00
m_xml . scopedElement ( "failed" ).
2022-01-03 23:16:39 +01:00
writeAttribute ( "message" _sr , error );
2020-09-08 15:53:08 +02:00
m_xml . endElement ();
}
2022-01-03 23:16:39 +01:00
void XmlReporter :: listReporters ( std :: vector < ReporterDescription > const & descriptions ) {
2020-09-08 15:53:08 +02:00
auto outerTag = m_xml . scopedElement ( "AvailableReporters" );
for ( auto const & reporter : descriptions ) {
auto inner = m_xml . scopedElement ( "Reporter" );
m_xml . startElement ( "Name" , XmlFormatting :: Indent )
. writeText ( reporter . name , XmlFormatting :: None )
. endElement ( XmlFormatting :: Newline );
m_xml . startElement ( "Description" , XmlFormatting :: Indent )
. writeText ( reporter . description , XmlFormatting :: None )
. endElement ( XmlFormatting :: Newline );
}
}
2022-01-03 23:16:39 +01:00
void XmlReporter :: listTests ( std :: vector < TestCaseHandle > const & tests ) {
2020-09-08 15:53:08 +02:00
auto outerTag = m_xml . scopedElement ( "MatchingTests" );
for ( auto const & test : tests ) {
auto innerTag = m_xml . scopedElement ( "TestCase" );
auto const & testInfo = test . getTestCaseInfo ();
m_xml . startElement ( "Name" , XmlFormatting :: Indent )
. writeText ( testInfo . name , XmlFormatting :: None )
. endElement ( XmlFormatting :: Newline );
m_xml . startElement ( "ClassName" , XmlFormatting :: Indent )
. writeText ( testInfo . className , XmlFormatting :: None )
. endElement ( XmlFormatting :: Newline );
m_xml . startElement ( "Tags" , XmlFormatting :: Indent )
. writeText ( testInfo . tagsAsString (), XmlFormatting :: None )
. endElement ( XmlFormatting :: Newline );
auto sourceTag = m_xml . scopedElement ( "SourceInfo" );
m_xml . startElement ( "File" , XmlFormatting :: Indent )
. writeText ( testInfo . lineInfo . file , XmlFormatting :: None )
. endElement ( XmlFormatting :: Newline );
m_xml . startElement ( "Line" , XmlFormatting :: Indent )
. writeText ( std :: to_string ( testInfo . lineInfo . line ), XmlFormatting :: None )
. endElement ( XmlFormatting :: Newline );
}
}
2022-01-03 23:16:39 +01:00
void XmlReporter :: listTags ( std :: vector < TagInfo > const & tags ) {
2020-09-08 15:53:08 +02:00
auto outerTag = m_xml . scopedElement ( "TagsFromMatchingTests" );
for ( auto const & tag : tags ) {
auto innerTag = m_xml . scopedElement ( "Tag" );
m_xml . startElement ( "Count" , XmlFormatting :: Indent )
. writeText ( std :: to_string ( tag . count ), XmlFormatting :: None )
. endElement ( XmlFormatting :: Newline );
auto aliasTag = m_xml . scopedElement ( "Aliases" );
for ( auto const & alias : tag . spellings ) {
m_xml . startElement ( "Alias" , XmlFormatting :: Indent )
2022-01-03 23:16:39 +01:00
. writeText ( alias , XmlFormatting :: None )
2020-09-08 15:53:08 +02:00
. endElement ( XmlFormatting :: Newline );
}
}
}
} // end namespace Catch
#if defined(_MSC_VER)
#pragma warning(pop)
#endif