diff --git a/.circleci/config.yml b/.circleci/config.yml index d93209d3..de70ce74 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -82,17 +82,19 @@ jobs: cd .build cmake $CMAKE_ARGS .. make - ctest --output-on-failure + env $RUN_ARGS ctest --output-on-failure test_tsan: <<: *test_sanitizer_base environment: CMAKE_ARGS: -DBUILD_TESTING=ON -DSANITIZE_THREAD=On -DSANITIZE_UNDEFINED=On + RUN_ARGS: TSAN_OPTIONS=detect_deadlocks=1:second_deadlock_stack=1 test_asan: <<: *test_sanitizer_base environment: CMAKE_ARGS: -DBUILD_TESTING=ON -DSANITIZE_ADDRESS=On + RUN ARGS: integration_test_nginx: working_directory: ~/dd-opentracing-cpp diff --git a/3rd_party/include/catch2/catch.hpp b/3rd_party/include/catch2/catch.hpp index ecd8907e..4191607a 100644 --- a/3rd_party/include/catch2/catch.hpp +++ b/3rd_party/include/catch2/catch.hpp @@ -1,6 +1,6 @@ /* - * Catch v2.2.2 - * Generated: 2018-04-06 12:05:03.186665 + * Catch v2.4.1 + * Generated: 2018-09-28 15:50:15.645795 * ---------------------------------------------------------- * This file has been merged from multiple headers. Please don't edit it directly * Copyright (c) 2018 Two Blue Cubes Ltd. All rights reserved. @@ -14,8 +14,8 @@ #define CATCH_VERSION_MAJOR 2 -#define CATCH_VERSION_MINOR 2 -#define CATCH_VERSION_PATCH 2 +#define CATCH_VERSION_MINOR 4 +#define CATCH_VERSION_PATCH 1 #ifdef __clang__ # pragma clang system_header @@ -30,13 +30,15 @@ # pragma warning(push) # pragma warning(disable: 161 1682) # else // __ICC -# pragma clang diagnostic ignored "-Wunused-variable" # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wpadded" # pragma clang diagnostic ignored "-Wswitch-enum" # pragma clang diagnostic ignored "-Wcovered-switch-default" # endif #elif defined __GNUC__ + // GCC likes to warn on REQUIREs, and we cannot suppress them + // locally because g++'s support for _Pragma is lacking in older, + // still supported, versions # pragma GCC diagnostic ignored "-Wparentheses" # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wunused-variable" @@ -55,7 +57,9 @@ # if defined(CATCH_CONFIG_DISABLE_MATCHERS) # undef CATCH_CONFIG_DISABLE_MATCHERS # endif -# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +# if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) +# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +# endif #endif #if !defined(CATCH_CONFIG_IMPL_ONLY) @@ -72,7 +76,7 @@ #elif defined(linux) || defined(__linux) || defined(__linux__) # define CATCH_PLATFORM_LINUX -#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) +#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__) # define CATCH_PLATFORM_WINDOWS #endif @@ -104,6 +108,7 @@ namespace Catch { // CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? // CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? // CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? +// CATCH_CONFIG_DISABLE_EXCEPTIONS : Are exceptions enabled? // **************** // Note to maintainers: if new toggles are added please document them // in configuration.md, too @@ -116,11 +121,11 @@ namespace Catch { #ifdef __cplusplus -# if __cplusplus >= 201402L +# if (__cplusplus >= 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L) # define CATCH_CPP14_OR_GREATER # endif -# if __cplusplus >= 201703L +# if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) # define CATCH_CPP17_OR_GREATER # endif @@ -145,6 +150,12 @@ namespace Catch { # define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ _Pragma( "clang diagnostic pop" ) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + _Pragma( "clang diagnostic push" ) \ + _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" ) +# define CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS \ + _Pragma( "clang diagnostic pop" ) + #endif // __clang__ //////////////////////////////////////////////////////////////////////////////// @@ -164,6 +175,24 @@ namespace Catch { # define CATCH_CONFIG_COLOUR_NONE #endif +//////////////////////////////////////////////////////////////////////////////// +// Android somehow still does not support std::to_string +#if defined(__ANDROID__) +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Not all Windows environments support SEH properly +#if defined(__MINGW32__) +# define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH +#endif + +//////////////////////////////////////////////////////////////////////////////// +// PS4 +#if defined(__ORBIS__) +# define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE +#endif + //////////////////////////////////////////////////////////////////////////////// // Cygwin #ifdef __CYGWIN__ @@ -171,7 +200,14 @@ namespace Catch { // Required for some versions of Cygwin to declare gettimeofday // see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin # define _BSD_SOURCE +// some versions of cygwin (most) do not support std::to_string. Use the libstd check. +// https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813 +# if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) \ + && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)) +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING + +# endif #endif // __CYGWIN__ //////////////////////////////////////////////////////////////////////////////// @@ -193,7 +229,12 @@ namespace Catch { #endif // _MSC_VER //////////////////////////////////////////////////////////////////////////////// +// Check if we are compiled with -fno-exceptions or equivalent +#if defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) +# define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED +#endif +//////////////////////////////////////////////////////////////////////////////// // DJGPP #ifdef __DJGPP__ # define CATCH_INTERNAL_CONFIG_NO_WCHAR @@ -210,10 +251,36 @@ namespace Catch { #define CATCH_INTERNAL_CONFIG_COUNTER #endif +//////////////////////////////////////////////////////////////////////////////// +// Check if string_view is available and usable +// The check is split apart to work around v140 (VS2015) preprocessor issue... +#if defined(__has_include) +#if __has_include() && defined(CATCH_CPP17_OR_GREATER) +# define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW +#endif +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Check if variant is available and usable +#if defined(__has_include) +# if __has_include() && defined(CATCH_CPP17_OR_GREATER) +# if defined(__clang__) && (__clang_major__ < 8) + // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852 + // fix should be in clang 8, workaround in libstdc++ 8.2 +# include +# if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) +# define CATCH_CONFIG_NO_CPP17_VARIANT +# else +# define CATCH_INTERNAL_CONFIG_CPP17_VARIANT +# endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) +# endif // defined(__clang__) && (__clang_major__ < 8) +# endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) +#endif // __has_include + #if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) # define CATCH_CONFIG_COUNTER #endif -#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) +#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH) # define CATCH_CONFIG_WINDOWS_SEH #endif // This is set by default, because we assume that unix compilers are posix-signal-compatible by default. @@ -225,10 +292,34 @@ namespace Catch { # define CATCH_CONFIG_WCHAR #endif +#if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING) +# define CATCH_CONFIG_CPP11_TO_STRING +#endif + #if defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) # define CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS #endif +#if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW) +# define CATCH_CONFIG_CPP17_STRING_VIEW +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && !defined(CATCH_CONFIG_CPP17_VARIANT) +# define CATCH_CONFIG_CPP17_VARIANT +#endif + +#if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) +# define CATCH_INTERNAL_CONFIG_NEW_CAPTURE +#endif + +#if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE) +# define CATCH_CONFIG_NEW_CAPTURE +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +# define CATCH_CONFIG_DISABLE_EXCEPTIONS +#endif + #if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) # define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS # define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS @@ -237,6 +328,20 @@ namespace Catch { # define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS # define CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS #endif +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS +# define CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS +#endif + +#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +#define CATCH_TRY if ((true)) +#define CATCH_CATCH_ALL if ((false)) +#define CATCH_CATCH_ANON(type) if ((false)) +#else +#define CATCH_TRY try +#define CATCH_CATCH_ALL catch (...) +#define CATCH_CATCH_ANON(type) catch (type) +#endif // end catch_compiler_capabilities.h #define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line @@ -478,6 +583,10 @@ namespace Catch { } // namespace Catch +inline auto operator "" _catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef { + return Catch::StringRef( rawChars, size ); +} + // end catch_stringref.h namespace Catch { @@ -513,12 +622,17 @@ struct AutoReg : NonCopyable { } // end namespace Catch +#define INTERNAL_CATCH_EXPAND1(param) INTERNAL_CATCH_EXPAND2(param) +#define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO## __VA_ARGS__ +#define INTERNAL_CATCH_DEF(...) INTERNAL_CATCH_DEF __VA_ARGS__ +#define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF + #if defined(CATCH_CONFIG_DISABLE) #define INTERNAL_CATCH_TESTCASE_NO_REGISTRATION( TestName, ... ) \ static void TestName() #define INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION( TestName, ClassName, ... ) \ namespace{ \ - struct TestName : ClassName { \ + struct TestName : INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF ClassName) { \ void test(); \ }; \ } \ @@ -530,7 +644,7 @@ struct AutoReg : NonCopyable { #define INTERNAL_CATCH_TESTCASE2( TestName, ... ) \ static void TestName(); \ CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &TestName ), CATCH_INTERNAL_LINEINFO, "", Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &TestName ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \ CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ static void TestName() #define INTERNAL_CATCH_TESTCASE( ... ) \ @@ -546,7 +660,7 @@ struct AutoReg : NonCopyable { #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestName, ClassName, ... )\ CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ namespace{ \ - struct TestName : ClassName{ \ + struct TestName : INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF ClassName) { \ void test(); \ }; \ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( Catch::makeTestInvoker( &TestName::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \ @@ -559,7 +673,7 @@ struct AutoReg : NonCopyable { /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \ CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( Function ), CATCH_INTERNAL_LINEINFO, "", Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( Function ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \ CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS // end catch_test_registry.h @@ -676,13 +790,15 @@ namespace Catch { return *this; } auto get() -> std::ostream& { return *m_oss; } - - static void cleanup(); }; } // end catch_stream.h +#ifdef CATCH_CONFIG_CPP17_STRING_VIEW +#include +#endif + #ifdef __OBJC__ // start catch_objc_arc.hpp @@ -770,16 +886,22 @@ namespace Catch { std::string convertUnknownEnumToString( E e ); template - typename std::enable_if::value, std::string>::type convertUnstreamable( T const& value ) { -#if !defined(CATCH_CONFIG_FALLBACK_STRINGIFIER) - (void)value; + typename std::enable_if< + !std::is_enum::value && !std::is_base_of::value, + std::string>::type convertUnstreamable( T const& ) { return Detail::unprintableString; -#else - return CATCH_CONFIG_FALLBACK_STRINGIFIER(value); -#endif } template - typename std::enable_if::value, std::string>::type convertUnstreamable( T const& value ) { + typename std::enable_if< + !std::is_enum::value && std::is_base_of::value, + std::string>::type convertUnstreamable(T const& ex) { + return ex.what(); + } + + template + typename std::enable_if< + std::is_enum::value + , std::string>::type convertUnstreamable( T const& value ) { return convertUnknownEnumToString( value ); } @@ -805,7 +927,9 @@ namespace Catch { typename std::enable_if<::Catch::Detail::IsStreamInsertable::value, std::string>::type convert(const Fake& value) { ReusableStringStream rss; - rss << value; + // NB: call using the function-like syntax to avoid ambiguity with + // user-defined templated operator<< under clang. + rss.operator<<(value); return rss.str(); } @@ -813,7 +937,11 @@ namespace Catch { static typename std::enable_if::value, std::string>::type convert( const Fake& value ) { - return Detail::convertUnstreamable( value ); +#if !defined(CATCH_CONFIG_FALLBACK_STRINGIFIER) + return Detail::convertUnstreamable(value); +#else + return CATCH_CONFIG_FALLBACK_STRINGIFIER(value); +#endif } }; @@ -846,10 +974,11 @@ namespace Catch { struct StringMaker { static std::string convert(const std::string& str); }; -#ifdef CATCH_CONFIG_WCHAR + +#ifdef CATCH_CONFIG_CPP17_STRING_VIEW template<> - struct StringMaker { - static std::string convert(const std::wstring& wstr); + struct StringMaker { + static std::string convert(std::string_view str); }; #endif @@ -863,6 +992,18 @@ namespace Catch { }; #ifdef CATCH_CONFIG_WCHAR + template<> + struct StringMaker { + static std::string convert(const std::wstring& wstr); + }; + +# ifdef CATCH_CONFIG_CPP17_STRING_VIEW + template<> + struct StringMaker { + static std::string convert(std::wstring_view str); + }; +# endif + template<> struct StringMaker { static std::string convert(wchar_t const * str); @@ -1031,6 +1172,7 @@ namespace Catch { #if defined(CATCH_CONFIG_ENABLE_ALL_STRINGMAKERS) # define CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER # define CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER +# define CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER # define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER #endif @@ -1094,6 +1236,34 @@ namespace Catch { } #endif // CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER +#if defined(CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_VARIANT) +#include +namespace Catch { + template<> + struct StringMaker { + static std::string convert(const std::monostate&) { + return "{ }"; + } + }; + + template + struct StringMaker> { + static std::string convert(const std::variant& variant) { + if (variant.valueless_by_exception()) { + return "{valueless variant}"; + } else { + return std::visit( + [](const auto& value) { + return ::Catch::Detail::stringify(value); + }, + variant + ); + } + } + }; +} +#endif // CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER + namespace Catch { struct not_this_one {}; // Tag type for detecting which begin/ end are being selected @@ -1453,8 +1623,10 @@ namespace Catch { struct BenchmarkInfo; struct BenchmarkStats; struct AssertionReaction; + struct SourceLineInfo; struct ITransientExpression; + struct IGeneratorTracker; struct IResultCapture { @@ -1465,6 +1637,8 @@ namespace Catch { virtual void sectionEnded( SectionEndInfo const& endInfo ) = 0; virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) = 0; + virtual auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker& = 0; + virtual void benchmarkStarting( BenchmarkInfo const& info ) = 0; virtual void benchmarkEnded( BenchmarkStats const& stats ) = 0; @@ -1546,7 +1720,7 @@ namespace Catch { public: AssertionHandler - ( StringRef macroName, + ( StringRef const& macroName, SourceLineInfo const& lineInfo, StringRef capturedExpression, ResultDisposition::Flags resultDisposition ); @@ -1577,7 +1751,7 @@ namespace Catch { auto allowThrows() const -> bool; }; - void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str, StringRef matcherString ); + void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str, StringRef const& matcherString ); } // namespace Catch @@ -1585,15 +1759,16 @@ namespace Catch { // start catch_message.h #include +#include namespace Catch { struct MessageInfo { - MessageInfo( std::string const& _macroName, + MessageInfo( StringRef const& _macroName, SourceLineInfo const& _lineInfo, ResultWas::OfType _type ); - std::string macroName; + StringRef macroName; std::string message; SourceLineInfo lineInfo; ResultWas::OfType type; @@ -1617,7 +1792,7 @@ namespace Catch { }; struct MessageBuilder : MessageStream { - MessageBuilder( std::string const& macroName, + MessageBuilder( StringRef const& macroName, SourceLineInfo const& lineInfo, ResultWas::OfType type ); @@ -1638,6 +1813,28 @@ namespace Catch { MessageInfo m_info; }; + class Capturer { + std::vector m_messages; + IResultCapture& m_resultCapture = getResultCapture(); + size_t m_captured = 0; + public: + Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names ); + ~Capturer(); + + void captureValue( size_t index, StringRef value ); + + template + void captureValues( size_t index, T&& value ) { + captureValue( index, Catch::Detail::stringify( value ) ); + } + + template + void captureValues( size_t index, T&& value, Ts&&... values ) { + captureValues( index, value ); + captureValues( index+1, values... ); + } + }; + } // end namespace Catch // end catch_message.h @@ -1649,7 +1846,7 @@ namespace Catch { #define CATCH_INTERNAL_STRINGIFY(...) "Disabled by CATCH_CONFIG_DISABLE_STRINGIFICATION" #endif -#if defined(CATCH_CONFIG_FAST_COMPILE) +#if defined(CATCH_CONFIG_FAST_COMPILE) || defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) /////////////////////////////////////////////////////////////////////////////// // Another way to speed-up compilation is to omit local try-catch for REQUIRE* @@ -1669,7 +1866,7 @@ namespace Catch { /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TEST( macroName, resultDisposition, ... ) \ do { \ - Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \ INTERNAL_CATCH_TRY { \ CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ catchAssertionHandler.handleExpr( Catch::Decomposer() <= __VA_ARGS__ ); \ @@ -1692,7 +1889,7 @@ namespace Catch { /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_NO_THROW( macroName, resultDisposition, ... ) \ do { \ - Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \ try { \ static_cast(__VA_ARGS__); \ catchAssertionHandler.handleExceptionNotThrownAsExpected(); \ @@ -1706,7 +1903,7 @@ namespace Catch { /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_THROWS( macroName, resultDisposition, ... ) \ do { \ - Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition); \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition); \ if( catchAssertionHandler.allowThrows() ) \ try { \ static_cast(__VA_ARGS__); \ @@ -1723,7 +1920,7 @@ namespace Catch { /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_THROWS_AS( macroName, exceptionType, resultDisposition, expr ) \ do { \ - Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(expr) ", " CATCH_INTERNAL_STRINGIFY(exceptionType), resultDisposition ); \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(expr) ", " CATCH_INTERNAL_STRINGIFY(exceptionType), resultDisposition ); \ if( catchAssertionHandler.allowThrows() ) \ try { \ static_cast(expr); \ @@ -1743,27 +1940,32 @@ namespace Catch { /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_MSG( macroName, messageType, resultDisposition, ... ) \ do { \ - Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::StringRef(), resultDisposition ); \ catchAssertionHandler.handleMessage( messageType, ( Catch::MessageStream() << __VA_ARGS__ + ::Catch::StreamEndStop() ).m_stream.str() ); \ INTERNAL_CATCH_REACT( catchAssertionHandler ) \ } while( false ) +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_CAPTURE( varName, macroName, ... ) \ + auto varName = Catch::Capturer( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info, #__VA_ARGS__ ); \ + varName.captureValues( 0, __VA_ARGS__ ) + /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_INFO( macroName, log ) \ - Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage )( Catch::MessageBuilder( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log ); + Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage )( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log ); /////////////////////////////////////////////////////////////////////////////// // Although this is matcher-based, it can be used with just a string #define INTERNAL_CATCH_THROWS_STR_MATCHES( macroName, resultDisposition, matcher, ... ) \ do { \ - Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ if( catchAssertionHandler.allowThrows() ) \ try { \ static_cast(__VA_ARGS__); \ catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ } \ catch( ... ) { \ - Catch::handleExceptionMatchExpr( catchAssertionHandler, matcher, #matcher ); \ + Catch::handleExceptionMatchExpr( catchAssertionHandler, matcher, #matcher##_catch_sr ); \ } \ else \ catchAssertionHandler.handleThrowingCallSkipped(); \ @@ -1815,19 +2017,22 @@ namespace Catch { namespace Catch { struct SectionInfo { + SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name ); + + // Deprecated SectionInfo ( SourceLineInfo const& _lineInfo, std::string const& _name, - std::string const& _description = std::string() ); + std::string const& ) : SectionInfo( _lineInfo, _name ) {} std::string name; - std::string description; + std::string description; // !Deprecated: this will always be empty SourceLineInfo lineInfo; }; struct SectionEndInfo { - SectionEndInfo( SectionInfo const& _sectionInfo, Counts const& _prevAssertions, double _durationInSeconds ); - SectionInfo sectionInfo; Counts prevAssertions; double durationInSeconds; @@ -1881,8 +2086,15 @@ namespace Catch { } // end namespace Catch - #define INTERNAL_CATCH_SECTION( ... ) \ - if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) ) +#define INTERNAL_CATCH_SECTION( ... ) \ + CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) ) \ + CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS + +#define INTERNAL_CATCH_DYNAMIC_SECTION( ... ) \ + CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, (Catch::ReusableStringStream() << __VA_ARGS__).str() ) ) \ + CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS // end catch_section.h // start catch_benchmark.h @@ -1958,7 +2170,7 @@ namespace Catch { virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0; virtual ITagAliasRegistry const& getTagAliasRegistry() const = 0; - virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() = 0; + virtual IExceptionTranslatorRegistry const& getExceptionTranslatorRegistry() const = 0; virtual StartupExceptionRegistry const& getStartupExceptionRegistry() const = 0; }; @@ -1973,7 +2185,7 @@ namespace Catch { virtual void registerStartupException() noexcept = 0; }; - IRegistryHub& getRegistryHub(); + IRegistryHub const& getRegistryHub(); IMutableRegistryHub& getMutableRegistryHub(); void cleanUp(); std::string translateActiveException(); @@ -2055,7 +2267,6 @@ namespace Catch { // start catch_approx.h #include -#include namespace Catch { namespace Detail { @@ -2063,18 +2274,26 @@ namespace Detail { class Approx { private: bool equalityComparisonImpl(double other) const; + // Validates the new margin (margin >= 0) + // out-of-line to avoid including stdexcept in the header + void setMargin(double margin); + // Validates the new epsilon (0 < epsilon < 1) + // out-of-line to avoid including stdexcept in the header + void setEpsilon(double epsilon); public: explicit Approx ( double value ); static Approx custom(); + Approx operator-() const; + template ::value>::type> Approx operator()( T const& value ) { Approx approx( static_cast(value) ); - approx.epsilon( m_epsilon ); - approx.margin( m_margin ); - approx.scale( m_scale ); + approx.m_epsilon = m_epsilon; + approx.m_margin = m_margin; + approx.m_scale = m_scale; return approx; } @@ -2126,27 +2345,14 @@ namespace Detail { template ::value>::type> Approx& epsilon( T const& newEpsilon ) { double epsilonAsDouble = static_cast(newEpsilon); - if( epsilonAsDouble < 0 || epsilonAsDouble > 1.0 ) { - throw std::domain_error - ( "Invalid Approx::epsilon: " + - Catch::Detail::stringify( epsilonAsDouble ) + - ", Approx::epsilon has to be between 0 and 1" ); - } - m_epsilon = epsilonAsDouble; + setEpsilon(epsilonAsDouble); return *this; } template ::value>::type> Approx& margin( T const& newMargin ) { double marginAsDouble = static_cast(newMargin); - if( marginAsDouble < 0 ) { - throw std::domain_error - ( "Invalid Approx::margin: " + - Catch::Detail::stringify( marginAsDouble ) + - ", Approx::Margin has to be non-negative." ); - - } - m_margin = marginAsDouble; + setMargin(marginAsDouble); return *this; } @@ -2164,7 +2370,12 @@ namespace Detail { double m_scale; double m_value; }; -} +} // end namespace Detail + +namespace literals { + Detail::Approx operator "" _a(long double val); + Detail::Approx operator "" _a(unsigned long long val); +} // end namespace literals template<> struct StringMaker { @@ -2231,6 +2442,11 @@ namespace Matchers { mutable std::string m_cachedToString; }; +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wnon-virtual-dtor" +#endif + template struct MatcherMethod { virtual bool match( ObjectT const& arg ) const = 0; @@ -2240,6 +2456,10 @@ namespace Matchers { virtual bool match( PtrT* arg ) const = 0; }; +#ifdef __clang__ +# pragma clang diagnostic pop +#endif + template struct MatcherBase : MatcherUntypedBase, MatcherMethod { @@ -2628,7 +2848,7 @@ namespace Matchers { auto lfirst = m_target.begin(), llast = m_target.end(); auto rfirst = vec.begin(), rlast = vec.end(); // Cut common prefix to optimize checking of permuted parts - while (lfirst != llast && *lfirst != *rfirst) { + while (lfirst != llast && *lfirst == *rfirst) { ++lfirst; ++rfirst; } if (lfirst == llast) { @@ -2693,7 +2913,7 @@ namespace Catch { MatcherT m_matcher; StringRef m_matcherString; public: - MatchExpr( ArgT const& arg, MatcherT const& matcher, StringRef matcherString ) + MatchExpr( ArgT const& arg, MatcherT const& matcher, StringRef const& matcherString ) : ITransientExpression{ true, matcher.match( arg ) }, m_arg( arg ), m_matcher( matcher ), @@ -2712,10 +2932,10 @@ namespace Catch { using StringMatcher = Matchers::Impl::MatcherBase; - void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, StringRef matcherString ); + void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, StringRef const& matcherString ); template - auto makeMatchExpr( ArgT const& arg, MatcherT const& matcher, StringRef matcherString ) -> MatchExpr { + auto makeMatchExpr( ArgT const& arg, MatcherT const& matcher, StringRef const& matcherString ) -> MatchExpr { return MatchExpr( arg, matcher, matcherString ); } @@ -2724,9 +2944,9 @@ namespace Catch { /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CHECK_THAT( macroName, matcher, resultDisposition, arg ) \ do { \ - Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(arg) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(arg) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ INTERNAL_CATCH_TRY { \ - catchAssertionHandler.handleExpr( Catch::makeMatchExpr( arg, matcher, #matcher ) ); \ + catchAssertionHandler.handleExpr( Catch::makeMatchExpr( arg, matcher, #matcher##_catch_sr ) ); \ } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \ INTERNAL_CATCH_REACT( catchAssertionHandler ) \ } while( false ) @@ -2734,14 +2954,14 @@ namespace Catch { /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_THROWS_MATCHES( macroName, exceptionType, resultDisposition, matcher, ... ) \ do { \ - Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(exceptionType) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(exceptionType) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ if( catchAssertionHandler.allowThrows() ) \ try { \ static_cast(__VA_ARGS__ ); \ catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ } \ catch( exceptionType const& ex ) { \ - catchAssertionHandler.handleExpr( Catch::makeMatchExpr( ex, matcher, #matcher ) ); \ + catchAssertionHandler.handleExpr( Catch::makeMatchExpr( ex, matcher, #matcher##_catch_sr ) ); \ } \ catch( ... ) { \ catchAssertionHandler.handleUnexpectedInflightException(); \ @@ -2753,6 +2973,304 @@ namespace Catch { // end catch_capture_matchers.h #endif +// start catch_generators.hpp + +// start catch_interfaces_generatortracker.h + + +#include + +namespace Catch { + + namespace Generators { + class GeneratorBase { + protected: + size_t m_size = 0; + + public: + GeneratorBase( size_t size ) : m_size( size ) {} + virtual ~GeneratorBase(); + auto size() const -> size_t { return m_size; } + }; + using GeneratorBasePtr = std::unique_ptr; + + } // namespace Generators + + struct IGeneratorTracker { + virtual ~IGeneratorTracker(); + virtual auto hasGenerator() const -> bool = 0; + virtual auto getGenerator() const -> Generators::GeneratorBasePtr const& = 0; + virtual void setGenerator( Generators::GeneratorBasePtr&& generator ) = 0; + virtual auto getIndex() const -> std::size_t = 0; + }; + +} // namespace Catch + +// end catch_interfaces_generatortracker.h +// start catch_enforce.h + +#include + +namespace Catch { +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + template + [[noreturn]] + void throw_exception(Ex const& e) { + throw e; + } +#else // ^^ Exceptions are enabled // Exceptions are disabled vv + [[noreturn]] + void throw_exception(std::exception const& e); +#endif +} // namespace Catch; + +#define CATCH_PREPARE_EXCEPTION( type, msg ) \ + type( ( Catch::ReusableStringStream() << msg ).str() ) +#define CATCH_INTERNAL_ERROR( msg ) \ + Catch::throw_exception(CATCH_PREPARE_EXCEPTION( std::logic_error, CATCH_INTERNAL_LINEINFO << ": Internal Catch error: " << msg)) +#define CATCH_ERROR( msg ) \ + Catch::throw_exception(CATCH_PREPARE_EXCEPTION( std::domain_error, msg )) +#define CATCH_RUNTIME_ERROR( msg ) \ + Catch::throw_exception(CATCH_PREPARE_EXCEPTION( std::runtime_error, msg )) +#define CATCH_ENFORCE( condition, msg ) \ + do{ if( !(condition) ) CATCH_ERROR( msg ); } while(false) + +// end catch_enforce.h +#include +#include +#include + +#include + +namespace Catch { +namespace Generators { + + // !TBD move this into its own location? + namespace pf{ + template + std::unique_ptr make_unique( Args&&... args ) { + return std::unique_ptr(new T(std::forward(args)...)); + } + } + + template + struct IGenerator { + virtual ~IGenerator() {} + virtual auto get( size_t index ) const -> T = 0; + }; + + template + class SingleValueGenerator : public IGenerator { + T m_value; + public: + SingleValueGenerator( T const& value ) : m_value( value ) {} + + auto get( size_t ) const -> T override { + return m_value; + } + }; + + template + class FixedValuesGenerator : public IGenerator { + std::vector m_values; + + public: + FixedValuesGenerator( std::initializer_list values ) : m_values( values ) {} + + auto get( size_t index ) const -> T override { + return m_values[index]; + } + }; + + template + class RangeGenerator : public IGenerator { + T const m_first; + T const m_last; + + public: + RangeGenerator( T const& first, T const& last ) : m_first( first ), m_last( last ) { + assert( m_last > m_first ); + } + + auto get( size_t index ) const -> T override { + // ToDo:: introduce a safe cast to catch potential overflows + return static_cast(m_first+index); + } + }; + + template + struct NullGenerator : IGenerator { + auto get( size_t ) const -> T override { + CATCH_INTERNAL_ERROR("A Null Generator is always empty"); + } + }; + + template + class Generator { + std::unique_ptr> m_generator; + size_t m_size; + + public: + Generator( size_t size, std::unique_ptr> generator ) + : m_generator( std::move( generator ) ), + m_size( size ) + {} + + auto size() const -> size_t { return m_size; } + auto operator[]( size_t index ) const -> T { + assert( index < m_size ); + return m_generator->get( index ); + } + }; + + std::vector randomiseIndices( size_t selectionSize, size_t sourceSize ); + + template + class GeneratorRandomiser : public IGenerator { + Generator m_baseGenerator; + + std::vector m_indices; + public: + GeneratorRandomiser( Generator&& baseGenerator, size_t numberOfItems ) + : m_baseGenerator( std::move( baseGenerator ) ), + m_indices( randomiseIndices( numberOfItems, m_baseGenerator.size() ) ) + {} + + auto get( size_t index ) const -> T override { + return m_baseGenerator[m_indices[index]]; + } + }; + + template + struct RequiresASpecialisationFor; + + template + auto all() -> Generator { return RequiresASpecialisationFor(); } + + template<> + auto all() -> Generator; + + template + auto range( T const& first, T const& last ) -> Generator { + return Generator( (last-first), pf::make_unique>( first, last ) ); + } + + template + auto random( T const& first, T const& last ) -> Generator { + auto gen = range( first, last ); + auto size = gen.size(); + + return Generator( size, pf::make_unique>( std::move( gen ), size ) ); + } + template + auto random( size_t size ) -> Generator { + return Generator( size, pf::make_unique>( all(), size ) ); + } + + template + auto values( std::initializer_list values ) -> Generator { + return Generator( values.size(), pf::make_unique>( values ) ); + } + template + auto value( T const& val ) -> Generator { + return Generator( 1, pf::make_unique>( val ) ); + } + + template + auto as() -> Generator { + return Generator( 0, pf::make_unique>() ); + } + + template + auto table( std::initializer_list>&& tuples ) -> Generator> { + return values>( std::forward>>( tuples ) ); + } + + template + struct Generators : GeneratorBase { + std::vector> m_generators; + + using type = T; + + Generators() : GeneratorBase( 0 ) {} + + void populate( T&& val ) { + m_size += 1; + m_generators.emplace_back( value( std::move( val ) ) ); + } + template + void populate( U&& val ) { + populate( T( std::move( val ) ) ); + } + void populate( Generator&& generator ) { + m_size += generator.size(); + m_generators.emplace_back( std::move( generator ) ); + } + + template + void populate( U&& valueOrGenerator, Gs... moreGenerators ) { + populate( std::forward( valueOrGenerator ) ); + populate( std::forward( moreGenerators )... ); + } + + auto operator[]( size_t index ) const -> T { + size_t sizes = 0; + for( auto const& gen : m_generators ) { + auto localIndex = index-sizes; + sizes += gen.size(); + if( index < sizes ) + return gen[localIndex]; + } + CATCH_INTERNAL_ERROR("Index '" << index << "' is out of range (" << sizes << ')'); + } + }; + + template + auto makeGenerators( Generator&& generator, Gs... moreGenerators ) -> Generators { + Generators generators; + generators.m_generators.reserve( 1+sizeof...(Gs) ); + generators.populate( std::move( generator ), std::forward( moreGenerators )... ); + return generators; + } + template + auto makeGenerators( Generator&& generator ) -> Generators { + Generators generators; + generators.populate( std::move( generator ) ); + return generators; + } + template + auto makeGenerators( T&& val, Gs... moreGenerators ) -> Generators { + return makeGenerators( value( std::forward( val ) ), std::forward( moreGenerators )... ); + } + template + auto makeGenerators( U&& val, Gs... moreGenerators ) -> Generators { + return makeGenerators( value( T( std::forward( val ) ) ), std::forward( moreGenerators )... ); + } + + auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker&; + + template + // Note: The type after -> is weird, because VS2015 cannot parse + // the expression used in the typedef inside, when it is in + // return type. Yeah, ¯\_(ツ)_/¯ + auto generate( SourceLineInfo const& lineInfo, L const& generatorExpression ) -> decltype(std::declval()[0]) { + using UnderlyingType = typename decltype(generatorExpression())::type; + + IGeneratorTracker& tracker = acquireGeneratorTracker( lineInfo ); + if( !tracker.hasGenerator() ) + tracker.setGenerator( pf::make_unique>( generatorExpression() ) ); + + auto const& generator = static_cast const&>( *tracker.getGenerator() ); + return generator[tracker.getIndex()]; + } + +} // namespace Generators +} // namespace Catch + +#define GENERATE( ... ) \ + Catch::Generators::generate( CATCH_INTERNAL_LINEINFO, []{ using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); } ) + +// end catch_generators.hpp // These files are included here so the single_include script doesn't put them // in the conditionally compiled sections @@ -2929,7 +3447,7 @@ namespace Catch { std::string desc = Detail::getAnnotation( cls, "Description", testCaseName ); const char* className = class_getName( cls ); - getMutableRegistryHub().registerTest( makeTestCase( new OcMethod( cls, selector ), className, name.c_str(), desc.c_str(), SourceLineInfo("",0) ) ); + getMutableRegistryHub().registerTest( makeTestCase( new OcMethod( cls, selector ), className, NameAndTags( name.c_str(), desc.c_str() ), SourceLineInfo("",0) ) ); noTestMethods++; } } @@ -3354,8 +3872,12 @@ namespace Catch { std::string outputFilename; std::string name; std::string processName; +#ifndef CATCH_CONFIG_DEFAULT_REPORTER +#define CATCH_CONFIG_DEFAULT_REPORTER "console" +#endif + std::string reporterName = CATCH_CONFIG_DEFAULT_REPORTER; +#undef CATCH_CONFIG_DEFAULT_REPORTER - std::vector reporterNames; std::vector testsOrTags; std::vector sectionsToRun; }; @@ -3375,8 +3897,8 @@ namespace Catch { bool listReporters() const; std::string getProcessName() const; + std::string const& getReporterName() const; - std::vector const& getReporterNames() const; std::vector const& getTestsOrTags() const; std::vector const& getSectionsToRun() const override; @@ -3549,6 +4071,7 @@ namespace Catch { struct ReporterPreferences { bool shouldRedirectStdOut = false; + bool shouldReportAllAssertions = false; }; template @@ -3733,8 +4256,6 @@ namespace Catch { virtual Listeners const& getListeners() const = 0; }; - void addReporter( IStreamingReporterPtr& existingReporter, IStreamingReporterPtr&& additionalReporter ); - } // end namespace Catch // end catch_interfaces_reporter.h @@ -3742,7 +4263,7 @@ namespace Catch { #include #include #include -#include +#include #include #include @@ -3761,7 +4282,7 @@ namespace Catch { { m_reporterPrefs.shouldRedirectStdOut = false; if( !DerivedT::getSupportedVerbosities().count( m_config->verbosity() ) ) - throw std::domain_error( "Verbosity level not supported by this reporter" ); + CATCH_ERROR( "Verbosity level not supported by this reporter" ); } ReporterPreferences getPreferences() const override { @@ -3875,7 +4396,7 @@ namespace Catch { { m_reporterPrefs.shouldRedirectStdOut = false; if( !DerivedT::getSupportedVerbosities().count( m_config->verbosity() ) ) - throw std::domain_error( "Verbosity level not supported by this reporter" ); + CATCH_ERROR( "Verbosity level not supported by this reporter" ); } ~CumulativeReporterBase() override = default; @@ -4515,13 +5036,6 @@ namespace TestCaseTracking { Failed }; - class TrackerHasName { - NameAndLocation m_nameAndLocation; - public: - TrackerHasName( NameAndLocation const& nameAndLocation ); - bool operator ()( ITrackerPtr const& tracker ) const; - }; - using Children = std::vector; NameAndLocation m_nameAndLocation; TrackerContext& m_ctx; @@ -4641,6 +5155,12 @@ namespace Detail { 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 ) << " )"; @@ -4653,8 +5173,31 @@ namespace Detail { return marginComparison(m_value, other, m_margin) || marginComparison(m_value, other, m_epsilon * (m_scale + std::fabs(m_value))); } + void Approx::setMargin(double margin) { + CATCH_ENFORCE(margin >= 0, + "Invalid Approx::margin: " << margin << '.' + << " Approx::Margin has to be non-negative."); + m_margin = margin; + } + + void Approx::setEpsilon(double epsilon) { + CATCH_ENFORCE(epsilon >= 0 && epsilon <= 1.0, + "Invalid Approx::epsilon: " << epsilon << '.' + << " Approx::epsilon has to be in [0, 1]"); + m_epsilon = epsilon; + } + } // end namespace Detail +namespace literals { + Detail::Approx operator "" _a(long double val) { + return Detail::Approx(val); + } + Detail::Approx operator "" _a(unsigned long long val) { + return Detail::Approx(val); + } +} // end namespace literals + std::string StringMaker::convert(Catch::Detail::Approx const& value) { return value.toString(); } @@ -4896,6 +5439,8 @@ namespace Catch { void sectionEnded( SectionEndInfo const& endInfo ) override; void sectionEndedEarly( SectionEndInfo const& endInfo ) override; + auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker& override; + void benchmarkStarting( BenchmarkInfo const& info ) override; void benchmarkEnded( BenchmarkStats const& stats ) override; @@ -4963,9 +5508,11 @@ namespace Catch { // end catch_run_context.h namespace Catch { - auto operator <<( std::ostream& os, ITransientExpression const& expr ) -> std::ostream& { - expr.streamReconstructedExpression( os ); - return os; + namespace { + auto operator <<( std::ostream& os, ITransientExpression const& expr ) -> std::ostream& { + expr.streamReconstructedExpression( os ); + return os; + } } LazyExpression::LazyExpression( bool isNegated ) @@ -4995,7 +5542,7 @@ namespace Catch { } AssertionHandler::AssertionHandler - ( StringRef macroName, + ( StringRef const& macroName, SourceLineInfo const& lineInfo, StringRef capturedExpression, ResultDisposition::Flags resultDisposition ) @@ -5024,8 +5571,13 @@ namespace Catch { // (To go back to the test and change execution, jump over the throw, next) CATCH_BREAK_INTO_DEBUGGER(); } - if( m_reaction.shouldThrow ) + 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; @@ -5052,7 +5604,7 @@ namespace Catch { // 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 - void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str, StringRef matcherString ) { + void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str, StringRef const& matcherString ) { handleExceptionMatchExpr( handler, Matchers::Equals( str ), matcherString ); } @@ -5184,7 +5736,7 @@ 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) - void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, StringRef matcherString ) { + void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, StringRef const& matcherString ) { std::string exceptionMessage = Catch::translateActiveException(); MatchExpr expr( exceptionMessage, matcher, matcherString ); handler.handleExpr( expr ); @@ -6592,7 +7144,7 @@ namespace Catch { | Opt( config.outputFilename, "filename" ) ["-o"]["--out"] ( "output filename" ) - | Opt( config.reporterNames, "name" ) + | Opt( config.reporterName, "name" ) ["-r"]["--reporter"] ( "reporter to use (defaults to console)" ) | Opt( config.name, "name" ) @@ -6669,7 +7221,9 @@ namespace Catch { return line == other.line && (file == other.file || std::strcmp(file, other.file) == 0); } bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const noexcept { - return line < other.line || ( line == other.line && (std::strcmp(file, other.file) < 0)); + // 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 ) { @@ -6692,20 +7246,6 @@ namespace Catch { // end catch_common.cpp // start catch_config.cpp -// start catch_enforce.h - -#include - -#define CATCH_PREPARE_EXCEPTION( type, msg ) \ - type( ( Catch::ReusableStringStream() << msg ).str() ) -#define CATCH_INTERNAL_ERROR( msg ) \ - throw CATCH_PREPARE_EXCEPTION( std::logic_error, CATCH_INTERNAL_LINEINFO << ": Internal Catch error: " << msg); -#define CATCH_ERROR( msg ) \ - throw CATCH_PREPARE_EXCEPTION( std::domain_error, msg ) -#define CATCH_ENFORCE( condition, msg ) \ - do{ if( !(condition) ) CATCH_ERROR( msg ); } while(false) - -// end catch_enforce.h namespace Catch { Config::Config( ConfigData const& data ) @@ -6734,8 +7274,8 @@ namespace Catch { bool Config::listReporters() const { return m_data.listReporters; } std::string Config::getProcessName() const { return m_data.processName; } + std::string const& Config::getReporterName() const { return m_data.reporterName; } - std::vector const& Config::getReporterNames() const { return m_data.reporterNames; } std::vector const& Config::getTestsOrTags() const { return m_data.testsOrTags; } std::vector const& Config::getSectionsToRun() const { return m_data.sectionsToRun; } @@ -7203,6 +7743,19 @@ namespace Catch { } } // end catch_decomposer.cpp +// start catch_enforce.cpp + +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 +} // namespace Catch; +// end catch_enforce.cpp // start catch_errno_guard.cpp #include @@ -7248,6 +7801,7 @@ namespace Catch { m_translators.push_back( std::unique_ptr( translator ) ); } +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) std::string ExceptionTranslatorRegistry::translateActiveException() const { try { #ifdef __OBJC__ @@ -7290,6 +7844,12 @@ namespace Catch { } } +#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!"); + } +#endif + std::string ExceptionTranslatorRegistry::tryTranslators() const { if( m_translators.empty() ) std::rethrow_exception(std::current_exception()); @@ -7381,6 +7941,11 @@ namespace Catch { int id; const char* name; }; + + // 32kb for the alternate stack seems to be sufficient. However, this value + // is experimentally determined, so that's not guaranteed. + constexpr static std::size_t sigStackSize = 32768 >= MINSIGSTKSZ ? 32768 : MINSIGSTKSZ; + static SignalDefs signalDefs[] = { { SIGINT, "SIGINT - Terminal interrupt signal" }, { SIGILL, "SIGILL - Illegal instruction signal" }, @@ -7407,7 +7972,7 @@ namespace Catch { isSet = true; stack_t sigStack; sigStack.ss_sp = altStackMem; - sigStack.ss_size = SIGSTKSZ; + sigStack.ss_size = sigStackSize; sigStack.ss_flags = 0; sigaltstack(&sigStack, &oldSigStack); struct sigaction sa = { }; @@ -7438,7 +8003,7 @@ namespace Catch { bool FatalConditionHandler::isSet = false; struct sigaction FatalConditionHandler::oldSigActions[sizeof(signalDefs)/sizeof(SignalDefs)] = {}; stack_t FatalConditionHandler::oldSigStack = {}; - char FatalConditionHandler::altStackMem[SIGSTKSZ] = {}; + char FatalConditionHandler::altStackMem[sigStackSize] = {}; } // namespace Catch @@ -7454,6 +8019,64 @@ namespace Catch { # pragma GCC diagnostic pop #endif // end catch_fatal_condition.cpp +// start catch_generators.cpp + +// start catch_random_number_generator.h + +#include +#include + +namespace Catch { + + struct IConfig; + + std::mt19937& rng(); + void seedRng( IConfig const& config ); + unsigned int rngSeed(); + +} + +// end catch_random_number_generator.h +#include +#include + +namespace Catch { + +IGeneratorTracker::~IGeneratorTracker() {} + +namespace Generators { + + GeneratorBase::~GeneratorBase() {} + + std::vector randomiseIndices( size_t selectionSize, size_t sourceSize ) { + + assert( selectionSize <= sourceSize ); + std::vector indices; + indices.reserve( selectionSize ); + std::uniform_int_distribution uid( 0, sourceSize-1 ); + + std::set seen; + // !TBD: improve this algorithm + while( indices.size() < selectionSize ) { + auto index = uid( rng() ); + if( seen.insert( index ).second ) + indices.push_back( index ); + } + return indices; + } + + auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker& { + return getResultCapture().acquireGeneratorTracker( lineInfo ); + } + + template<> + auto all() -> Generator { + return range( std::numeric_limits::min(), std::numeric_limits::max() ); + } + +} // namespace Generators +} // namespace Catch +// end catch_generators.cpp // start catch_interfaces_capture.cpp namespace Catch { @@ -7482,16 +8105,21 @@ namespace Catch { // end catch_interfaces_registry_hub.cpp // start catch_interfaces_reporter.cpp -// start catch_reporter_multi.h +// start catch_reporter_listening.h namespace Catch { - class MultipleReporters : public IStreamingReporter { + class ListeningReporter : public IStreamingReporter { using Reporters = std::vector; - Reporters m_reporters; + Reporters m_listeners; + IStreamingReporterPtr m_reporter = nullptr; + ReporterPreferences m_preferences; public: - void add( IStreamingReporterPtr&& reporter ); + ListeningReporter(); + + void addListener( IStreamingReporterPtr&& listener ); + void addReporter( IStreamingReporterPtr&& reporter ); public: // IStreamingReporter @@ -7524,7 +8152,7 @@ namespace Catch { } // end namespace Catch -// end catch_reporter_multi.h +// end catch_reporter_listening.h namespace Catch { ReporterConfig::ReporterConfig( IConfigPtr const& _fullConfig ) @@ -7625,27 +8253,6 @@ namespace Catch { IReporterFactory::~IReporterFactory() = default; IReporterRegistry::~IReporterRegistry() = default; - void addReporter( IStreamingReporterPtr& existingReporter, IStreamingReporterPtr&& additionalReporter ) { - - if( !existingReporter ) { - existingReporter = std::move( additionalReporter ); - return; - } - - MultipleReporters* multi = nullptr; - - if( existingReporter->isMulti() ) { - multi = static_cast( existingReporter.get() ); - } - else { - auto newMulti = std::unique_ptr( new MultipleReporters ); - newMulti->add( std::move( existingReporter ) ); - multi = newMulti.get(); - existingReporter = std::move( newMulti ); - } - multi->add( std::move( additionalReporter ) ); - } - } // end namespace Catch // end catch_interfaces_reporter.cpp // start catch_interfaces_runner.cpp @@ -7668,16 +8275,16 @@ namespace Catch { 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); - } + 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 @@ -7887,10 +8494,27 @@ using Matchers::Impl::MatcherBase; // end catch_matchers.cpp // start catch_matchers_floating.cpp +// start catch_to_string.hpp + +#include + +namespace Catch { + template + std::string to_string(T const& t) { +#if defined(CATCH_CONFIG_CPP11_TO_STRING) + return std::to_string(t); +#else + ReusableStringStream rss; + rss << t; + return rss.str(); +#endif + } +} // end namespace Catch + +// end catch_to_string.hpp #include #include #include -#include namespace Catch { namespace Matchers { @@ -7958,9 +8582,8 @@ namespace Matchers { namespace Floating { WithinAbsMatcher::WithinAbsMatcher(double target, double margin) :m_target{ target }, m_margin{ margin } { - if (m_margin < 0) { - throw std::domain_error("Allowed margin difference has to be >= 0"); - } + CATCH_ENFORCE(margin >= 0, "Invalid margin: " << margin << '.' + << " Margin has to be non-negative."); } // Performs equivalent check of std::fabs(lhs - rhs) <= margin @@ -7975,11 +8598,16 @@ namespace Floating { WithinUlpsMatcher::WithinUlpsMatcher(double target, int ulps, FloatingPointKind baseType) :m_target{ target }, m_ulps{ ulps }, m_type{ baseType } { - if (m_ulps < 0) { - throw std::domain_error("Allowed ulp difference has to be >= 0"); - } + CATCH_ENFORCE(ulps >= 0, "Invalid ULP setting: " << ulps << '.' + << " ULPs have to be non-negative."); } +#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 FloatingPointKind::Float: @@ -7987,12 +8615,16 @@ namespace Floating { case FloatingPointKind::Double: return almostEqualUlps(matchee, m_target, m_ulps); default: - throw std::domain_error("Unknown FloatingPointKind value"); + CATCH_INTERNAL_ERROR( "Unknown FloatingPointKind value" ); } } +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + std::string WithinUlpsMatcher::describe() const { - return "is within " + std::to_string(m_ulps) + " ULPs of " + ::Catch::Detail::stringify(m_target) + ((m_type == FloatingPointKind::Float)? "f" : ""); + return "is within " + Catch::to_string(m_ulps) + " ULPs of " + ::Catch::Detail::stringify(m_target) + ((m_type == FloatingPointKind::Float)? "f" : ""); } }// namespace Floating @@ -8134,9 +8766,11 @@ namespace Catch { } // end namespace Catch // end catch_uncaught_exceptions.h +#include + namespace Catch { - MessageInfo::MessageInfo( std::string const& _macroName, + MessageInfo::MessageInfo( StringRef const& _macroName, SourceLineInfo const& _lineInfo, ResultWas::OfType _type ) : macroName( _macroName ), @@ -8145,92 +8779,309 @@ namespace Catch { sequence( ++globalCount ) {} - bool MessageInfo::operator==( MessageInfo const& other ) const { - return sequence == other.sequence; + bool MessageInfo::operator==( MessageInfo const& other ) const { + return sequence == other.sequence; + } + + bool MessageInfo::operator<( MessageInfo const& other ) const { + return sequence < other.sequence; + } + + // This may need protecting if threading support is added + unsigned int MessageInfo::globalCount = 0; + + //////////////////////////////////////////////////////////////////////////// + + Catch::MessageBuilder::MessageBuilder( StringRef const& macroName, + SourceLineInfo const& lineInfo, + ResultWas::OfType type ) + :m_info(macroName, lineInfo, type) {} + + //////////////////////////////////////////////////////////////////////////// + + ScopedMessage::ScopedMessage( MessageBuilder const& builder ) + : m_info( builder.m_info ) + { + m_info.message = builder.m_stream.str(); + getResultCapture().pushScopedMessage( m_info ); + } + + ScopedMessage::~ScopedMessage() { + if ( !uncaught_exceptions() ){ + getResultCapture().popScopedMessage(m_info); + } + } + + Capturer::Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names ) { + auto start = std::string::npos; + for( size_t pos = 0; pos <= names.size(); ++pos ) { + char c = names[pos]; + if( pos == names.size() || c == ' ' || c == '\t' || c == ',' || c == ']' ) { + if( start != std::string::npos ) { + m_messages.push_back( MessageInfo( macroName, lineInfo, resultType ) ); + m_messages.back().message = names.substr( start, pos-start) + " := "; + start = std::string::npos; + } + } + else if( c != '[' && c != ']' && start == std::string::npos ) + start = pos; + } + } + 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, StringRef value ) { + assert( index < m_messages.size() ); + m_messages[index].message += value; + m_resultCapture.pushScopedMessage( m_messages[index] ); + m_captured++; + } + +} // end namespace Catch +// end catch_message.cpp +// start catch_output_redirect.cpp + +// start catch_output_redirect.h +#ifndef TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H +#define TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H + +#include +#include +#include + +namespace Catch { + + class RedirectedStream { + std::ostream& m_originalStream; + std::ostream& m_redirectionStream; + std::streambuf* m_prevBuf; + + public: + RedirectedStream( std::ostream& originalStream, std::ostream& redirectionStream ); + ~RedirectedStream(); + }; + + class RedirectedStdOut { + ReusableStringStream m_rss; + RedirectedStream m_cout; + public: + RedirectedStdOut(); + auto str() const -> std::string; + }; + + // StdErr has two constituent streams in C++, std::cerr and std::clog + // This means that we need to redirect 2 streams into 1 to keep proper + // order of writes + class RedirectedStdErr { + ReusableStringStream m_rss; + RedirectedStream m_cerr; + RedirectedStream m_clog; + public: + RedirectedStdErr(); + auto str() const -> std::string; + }; + +#if defined(CATCH_CONFIG_NEW_CAPTURE) + + // Windows's implementation of std::tmpfile is terrible (it tries + // to create a file inside system folder, thus requiring elevated + // privileges for the binary), so we have to use tmpnam(_s) and + // create the file ourselves there. + class TempFile { + public: + TempFile(TempFile const&) = delete; + TempFile& operator=(TempFile const&) = delete; + TempFile(TempFile&&) = delete; + TempFile& operator=(TempFile&&) = delete; + + TempFile(); + ~TempFile(); + + std::FILE* getFile(); + std::string getContents(); + + private: + std::FILE* m_file = nullptr; + #if defined(_MSC_VER) + char m_buffer[L_tmpnam] = { 0 }; + #endif + }; + + class OutputRedirect { + public: + OutputRedirect(OutputRedirect const&) = delete; + OutputRedirect& operator=(OutputRedirect const&) = delete; + OutputRedirect(OutputRedirect&&) = delete; + OutputRedirect& operator=(OutputRedirect&&) = delete; + + OutputRedirect(std::string& stdout_dest, std::string& stderr_dest); + ~OutputRedirect(); + + private: + int m_originalStdout = -1; + int m_originalStderr = -1; + TempFile m_stdoutFile; + TempFile m_stderrFile; + std::string& m_stdoutDest; + std::string& m_stderrDest; + }; + +#endif + +} // end namespace Catch + +#endif // TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H +// end catch_output_redirect.h +#include +#include +#include +#include +#include + +#if defined(CATCH_CONFIG_NEW_CAPTURE) + #if defined(_MSC_VER) + #include //_dup and _dup2 + #define dup _dup + #define dup2 _dup2 + #define fileno _fileno + #else + #include // 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() ); } - bool MessageInfo::operator<( MessageInfo const& other ) const { - return sequence < other.sequence; + RedirectedStream::~RedirectedStream() { + m_originalStream.rdbuf( m_prevBuf ); } - // This may need protecting if threading support is added - unsigned int MessageInfo::globalCount = 0; - - //////////////////////////////////////////////////////////////////////////// + RedirectedStdOut::RedirectedStdOut() : m_cout( Catch::cout(), m_rss.get() ) {} + auto RedirectedStdOut::str() const -> std::string { return m_rss.str(); } - Catch::MessageBuilder::MessageBuilder( std::string const& macroName, - SourceLineInfo const& lineInfo, - ResultWas::OfType type ) - :m_info(macroName, lineInfo, type) {} + 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(); } - //////////////////////////////////////////////////////////////////////////// +#if defined(CATCH_CONFIG_NEW_CAPTURE) - ScopedMessage::ScopedMessage( MessageBuilder const& builder ) - : m_info( builder.m_info ) - { - m_info.message = builder.m_stream.str(); - getResultCapture().pushScopedMessage( m_info ); +#if defined(_MSC_VER) + TempFile::TempFile() { + if (tmpnam_s(m_buffer)) { + CATCH_RUNTIME_ERROR("Could not get a temp filename"); + } + if (fopen_s(&m_file, m_buffer, "w")) { + char buffer[100]; + if (strerror_s(buffer, errno)) { + CATCH_RUNTIME_ERROR("Could not translate errno to a string"); + } + CATCH_RUNTIME_ERROR("Coul dnot open the temp file: '" << m_buffer << "' because: " << buffer); + } } - - ScopedMessage::~ScopedMessage() { - if ( !uncaught_exceptions() ){ - getResultCapture().popScopedMessage(m_info); +#else + TempFile::TempFile() { + m_file = std::tmpfile(); + if (!m_file) { + CATCH_RUNTIME_ERROR("Could not create a temp file."); } } -} // end namespace Catch -// end catch_message.cpp -// start catch_random_number_generator.cpp - -// start catch_random_number_generator.h -#include +#endif -namespace Catch { + 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 + } - struct IConfig; + FILE* TempFile::getFile() { + return m_file; + } - void seedRng( IConfig const& config ); + 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(); + } - unsigned int rngSeed(); + 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); + } - struct RandomNumberGenerator { - using result_type = unsigned int; + 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); - static constexpr result_type (min)() { return 0; } - static constexpr result_type (max)() { return 1000000; } + dup2(m_originalStdout, 1); + dup2(m_originalStderr, 2); - result_type operator()( result_type n ) const; - result_type operator()() const; + m_stdoutDest += m_stdoutFile.getContents(); + m_stderrDest += m_stderrFile.getContents(); + } - template - static void shuffle( V& vector ) { - RandomNumberGenerator rng; - std::shuffle( vector.begin(), vector.end(), rng ); - } - }; +#endif // CATCH_CONFIG_NEW_CAPTURE -} +} // namespace Catch -// end catch_random_number_generator.h -#include +#if defined(CATCH_CONFIG_NEW_CAPTURE) + #if defined(_MSC_VER) + #undef dup + #undef dup2 + #undef fileno + #endif +#endif +// end catch_output_redirect.cpp +// start catch_random_number_generator.cpp namespace Catch { + std::mt19937& rng() { + static std::mt19937 s_rng; + return s_rng; + } + void seedRng( IConfig const& config ) { - if( config.rngSeed() != 0 ) + if( config.rngSeed() != 0 ) { std::srand( config.rngSeed() ); + rng().seed( config.rngSeed() ); + } } + unsigned int rngSeed() { return getCurrentContext().getConfig()->rngSeed(); } - - RandomNumberGenerator::result_type RandomNumberGenerator::operator()( result_type n ) const { - return std::rand() % n; - } - RandomNumberGenerator::result_type RandomNumberGenerator::operator()() const { - return std::rand() % (max)(); - } - } // end catch_random_number_generator.cpp // start catch_registry_hub.cpp @@ -8370,6 +9221,41 @@ namespace Catch { } // end namespace Catch // end catch_startup_exception_registry.h +// start catch_singletons.hpp + +namespace Catch { + + struct ISingleton { + virtual ~ISingleton(); + }; + + void addSingleton( ISingleton* singleton ); + void cleanupSingletons(); + + template + class Singleton : SingletonImplT, public ISingleton { + + static auto getInternal() -> Singleton* { + static Singleton* s_instance = nullptr; + if( !s_instance ) { + s_instance = new Singleton; + addSingleton( s_instance ); + } + return s_instance; + } + + public: + static auto get() -> InterfaceT const& { + return *getInternal(); + } + static auto getMutable() -> MutableInterfaceT& { + return *getInternal(); + } + }; + +} // namespace Catch + +// end catch_singletons.hpp namespace Catch { namespace { @@ -8385,7 +9271,7 @@ namespace Catch { ITestCaseRegistry const& getTestCaseRegistry() const override { return m_testCaseRegistry; } - IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() override { + IExceptionTranslatorRegistry const& getExceptionTranslatorRegistry() const override { return m_exceptionTranslatorRegistry; } ITagAliasRegistry const& getTagAliasRegistry() const override { @@ -8422,27 +9308,19 @@ namespace Catch { TagAliasRegistry m_tagAliasRegistry; StartupExceptionRegistry m_exceptionRegistry; }; - - // Single, global, instance - RegistryHub*& getTheRegistryHub() { - static RegistryHub* theRegistryHub = nullptr; - if( !theRegistryHub ) - theRegistryHub = new RegistryHub(); - return theRegistryHub; - } } - IRegistryHub& getRegistryHub() { - return *getTheRegistryHub(); + using RegistryHubSingleton = Singleton; + + IRegistryHub const& getRegistryHub() { + return RegistryHubSingleton::get(); } IMutableRegistryHub& getMutableRegistryHub() { - return *getTheRegistryHub(); + return RegistryHubSingleton::getMutable(); } void cleanUp() { - delete getTheRegistryHub(); - getTheRegistryHub() = nullptr; + cleanupSingletons(); cleanUpContext(); - ReusableStringStream::cleanup(); } std::string translateActiveException() { return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException(); @@ -8507,46 +9385,68 @@ namespace Catch { namespace Catch { - class RedirectedStream { - std::ostream& m_originalStream; - std::ostream& m_redirectionStream; - std::streambuf* m_prevBuf; + namespace Generators { + struct GeneratorTracker : TestCaseTracking::TrackerBase, IGeneratorTracker { + size_t m_index = static_cast( -1 ); + GeneratorBasePtr m_generator; - public: - 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() { - m_originalStream.rdbuf( m_prevBuf ); - } - }; + GeneratorTracker( TestCaseTracking::NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) + : TrackerBase( nameAndLocation, ctx, parent ) + {} + ~GeneratorTracker(); - class RedirectedStdOut { - ReusableStringStream m_rss; - RedirectedStream m_cout; - public: - RedirectedStdOut() : m_cout( Catch::cout(), m_rss.get() ) {} - auto str() const -> std::string { return m_rss.str(); } - }; + static GeneratorTracker& acquire( TrackerContext& ctx, TestCaseTracking::NameAndLocation const& nameAndLocation ) { + std::shared_ptr tracker; - // StdErr has two constituent streams in C++, std::cerr and std::clog - // This means that we need to redirect 2 streams into 1 to keep proper - // order of writes - class RedirectedStdErr { - ReusableStringStream m_rss; - RedirectedStream m_cerr; - RedirectedStream m_clog; - public: - RedirectedStdErr() - : m_cerr( Catch::cerr(), m_rss.get() ), - m_clog( Catch::clog(), m_rss.get() ) - {} - auto str() const -> std::string { return m_rss.str(); } - }; + ITracker& currentTracker = ctx.currentTracker(); + if( TestCaseTracking::ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) { + assert( childTracker ); + assert( childTracker->isIndexTracker() ); + tracker = std::static_pointer_cast( childTracker ); + } + else { + tracker = std::make_shared( nameAndLocation, ctx, ¤tTracker ); + currentTracker.addChild( tracker ); + } + + if( !ctx.completedCycle() && !tracker->isComplete() ) { + if( tracker->m_runState != ExecutingChildren && tracker->m_runState != NeedsAnotherRun ) + tracker->moveNext(); + tracker->open(); + } + + return *tracker; + } + + void moveNext() { + m_index++; + m_children.clear(); + } + + // TrackerBase interface + bool isIndexTracker() const override { return true; } + auto hasGenerator() const -> bool override { + return !!m_generator; + } + void close() override { + TrackerBase::close(); + if( m_runState == CompletedSuccessfully && m_index < m_generator->size()-1 ) + m_runState = Executing; + } + + // IGeneratorTracker interface + auto getGenerator() const -> GeneratorBasePtr const& override { + return m_generator; + } + void setGenerator( GeneratorBasePtr&& generator ) override { + m_generator = std::move( generator ); + } + auto getIndex() const -> size_t override { + return m_index; + } + }; + GeneratorTracker::~GeneratorTracker() {} + } RunContext::RunContext(IConfigPtr const& _config, IStreamingReporterPtr&& reporter) : m_runInfo(_config->name()), @@ -8554,7 +9454,7 @@ namespace Catch { m_config(_config), m_reporter(std::move(reporter)), m_lastAssertionInfo{ StringRef(), SourceLineInfo("",0), StringRef(), ResultDisposition::Normal }, - m_includeSuccessfulResults( m_config->includeSuccessfulResults() ) + m_includeSuccessfulResults( m_config->includeSuccessfulResults() || m_reporter->getPreferences().shouldReportAllAssertions ) { m_context.setRunner(this); m_context.setConfig(m_config); @@ -8664,6 +9564,13 @@ namespace Catch { return true; } + auto RunContext::acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker& { + using namespace Generators; + GeneratorTracker& tracker = GeneratorTracker::acquire( m_trackerContext, TestCaseTracking::NameAndLocation( "generator", lineInfo ) ); + assert( tracker.isOpen() ); + m_lastAssertionInfo.lineInfo = lineInfo; + return tracker; + } bool RunContext::testForMissingAssertions(Counts& assertions) { if (assertions.total() != 0) @@ -8744,7 +9651,7 @@ namespace Catch { // 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, testCaseInfo.description); + SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name); Counts assertions; assertions.failed = 1; @@ -8777,12 +9684,12 @@ namespace Catch { } bool RunContext::aborting() const { - return m_totals.assertions.failed == static_cast(m_config->abortAfter()); + return m_totals.assertions.failed >= static_cast(m_config->abortAfter()); } void RunContext::runCurrentTest(std::string & redirectedCout, std::string & redirectedCerr) { auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); - SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description); + SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name); m_reporter->sectionStarting(testCaseSection); Counts prevAssertions = m_totals.assertions; double duration = 0; @@ -8792,23 +9699,29 @@ namespace Catch { seedRng(*m_config); Timer timer; - try { + CATCH_TRY { if (m_reporter->getPreferences().shouldRedirectStdOut) { +#if !defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) RedirectedStdOut redirectedStdOut; RedirectedStdErr redirectedStdErr; + timer.start(); invokeActiveTestCase(); redirectedCout += redirectedStdOut.str(); redirectedCerr += redirectedStdErr.str(); - +#else + OutputRedirect r(redirectedCout, redirectedCerr); + timer.start(); + invokeActiveTestCase(); +#endif } else { timer.start(); invokeActiveTestCase(); } duration = timer.getElapsedSeconds(); - } catch (TestFailureException&) { + } CATCH_CATCH_ANON (TestFailureException&) { // This just means the test was aborted due to failure - } catch (...) { + } 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 ) { @@ -8971,7 +9884,7 @@ namespace Catch { Section::~Section() { if( m_sectionIncluded ) { - SectionEndInfo endInfo( m_info, m_assertions, m_timer.getElapsedSeconds() ); + SectionEndInfo endInfo{ m_info, m_assertions, m_timer.getElapsedSeconds() }; if( uncaught_exceptions() ) getResultCapture().sectionEndedEarly( endInfo ); else @@ -8992,17 +9905,11 @@ namespace Catch { SectionInfo::SectionInfo ( SourceLineInfo const& _lineInfo, - std::string const& _name, - std::string const& _description ) + std::string const& _name ) : name( _name ), - description( _description ), lineInfo( _lineInfo ) {} - SectionEndInfo::SectionEndInfo( SectionInfo const& _sectionInfo, Counts const& _prevAssertions, double _durationInSeconds ) - : sectionInfo( _sectionInfo ), prevAssertions( _prevAssertions ), durationInSeconds( _durationInSeconds ) - {} - } // end namespace Catch // end catch_section_info.cpp // start catch_session.cpp @@ -9094,32 +10001,25 @@ namespace Catch { return reporter; } -#ifndef CATCH_CONFIG_DEFAULT_REPORTER -#define CATCH_CONFIG_DEFAULT_REPORTER "console" -#endif - IStreamingReporterPtr makeReporter(std::shared_ptr const& config) { - auto const& reporterNames = config->getReporterNames(); - if (reporterNames.empty()) - return createReporter(CATCH_CONFIG_DEFAULT_REPORTER, config); - - IStreamingReporterPtr reporter; - for (auto const& name : reporterNames) - addReporter(reporter, createReporter(name, config)); - return reporter; - } + if (Catch::getRegistryHub().getReporterRegistry().getListeners().empty()) { + return createReporter(config->getReporterName(), config); + } -#undef CATCH_CONFIG_DEFAULT_REPORTER + auto multi = std::unique_ptr(new ListeningReporter); - void addListeners(IStreamingReporterPtr& reporters, IConfigPtr const& config) { auto const& listeners = Catch::getRegistryHub().getReporterRegistry().getListeners(); - for (auto const& listener : listeners) - addReporter(reporters, listener->create(Catch::ReporterConfig(config))); + for (auto const& listener : listeners) { + multi->addListener(listener->create(Catch::ReporterConfig(config))); + } + multi->addReporter(createReporter(config->getReporterName(), config)); + return std::move(multi); } Catch::Totals runTests(std::shared_ptr const& config) { - IStreamingReporterPtr reporter = makeReporter(config); - addListeners(reporter, config); + // FixMe: Add listeners in order first, then add reporters. + + auto reporter = makeReporter(config); RunContext context(config, std::move(reporter)); @@ -9182,10 +10082,12 @@ namespace Catch { Session::Session() { static bool alreadyInstantiated = false; if( alreadyInstantiated ) { - try { CATCH_INTERNAL_ERROR( "Only one instance of Catch::Session can ever be used" ); } - catch(...) { getMutableRegistryHub().registerStartupException(); } + 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() ) { m_startupExceptions = true; @@ -9200,6 +10102,7 @@ namespace Catch { } } } +#endif alreadyInstantiated = true; m_cli = makeCommandLineParser( m_configData ); @@ -9314,11 +10217,11 @@ namespace Catch { if( m_startupExceptions ) return 1; - if( m_configData.showHelp || m_configData.libIdentify ) + if (m_configData.showHelp || m_configData.libIdentify) { return 0; + } - try - { + CATCH_TRY { config(); // Force config to be constructed seedRng( *m_config ); @@ -9336,22 +10239,53 @@ namespace Catch { // of 256 tests has failed return (std::min) (MaxExitCode, (std::max) (totals.error, static_cast(totals.assertions.failed))); } +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) catch( std::exception& ex ) { Catch::cerr() << ex.what() << std::endl; return MaxExitCode; } +#endif } } // end namespace Catch // end catch_session.cpp +// start catch_singletons.cpp + +#include + +namespace Catch { + + namespace { + static auto getSingletons() -> std::vector*& { + static std::vector* g_singletons = nullptr; + if( !g_singletons ) + g_singletons = new std::vector(); + return g_singletons; + } + } + + ISingleton::~ISingleton() {} + + 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 +// end catch_singletons.cpp // start catch_startup_exception_registry.cpp namespace Catch { - void StartupExceptionRegistry::add( std::exception_ptr const& exception ) noexcept { - try { +void StartupExceptionRegistry::add( std::exception_ptr const& exception ) noexcept { + CATCH_TRY { m_exceptions.push_back(exception); - } - catch(...) { + } 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(); } @@ -9372,11 +10306,6 @@ namespace Catch { #include #include -#if defined(__clang__) -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wexit-time-destructors" -#endif - namespace Catch { Catch::IStream::~IStream() = default; @@ -9496,7 +10425,6 @@ namespace Catch { std::vector> m_streams; std::vector m_unused; std::ostringstream m_referenceStream; // Used for copy state/ flags from - static StringStreams* s_instance; auto add() -> std::size_t { if( m_unused.empty() ) { @@ -9514,34 +10442,17 @@ namespace Catch { m_streams[index]->copyfmt( m_referenceStream ); // Restore initial flags and other state m_unused.push_back(index); } - - // !TBD: put in TLS - static auto instance() -> StringStreams& { - if( !s_instance ) - s_instance = new StringStreams(); - return *s_instance; - } - static void cleanup() { - delete s_instance; - s_instance = nullptr; - } }; - StringStreams* StringStreams::s_instance = nullptr; - - void ReusableStringStream::cleanup() { - StringStreams::cleanup(); - } - ReusableStringStream::ReusableStringStream() - : m_index( StringStreams::instance().add() ), - m_oss( StringStreams::instance().m_streams[m_index].get() ) + : m_index( Singleton::getMutable().add() ), + m_oss( Singleton::getMutable().m_streams[m_index].get() ) {} ReusableStringStream::~ReusableStringStream() { static_cast( m_oss )->str(""); m_oss->clear(); - StringStreams::instance().release( m_index ); + Singleton::getMutable().release( m_index ); } auto ReusableStringStream::str() const -> std::string { @@ -9556,10 +10467,6 @@ namespace Catch { std::ostream& clog() { return std::clog; } #endif } - -#if defined(__clang__) -# pragma clang diagnostic pop -#endif // end catch_stream.cpp // start catch_string_manip.cpp @@ -9570,6 +10477,12 @@ namespace Catch { namespace Catch { + namespace { + char toLowerCh(char c) { + return static_cast( std::tolower( c ) ); + } + } + bool startsWith( std::string const& s, std::string const& prefix ) { return s.size() >= prefix.size() && std::equal(prefix.begin(), prefix.end(), s.begin()); } @@ -9585,9 +10498,6 @@ namespace Catch { bool contains( std::string const& s, std::string const& infix ) { return s.find( infix ) != std::string::npos; } - char toLowerCh(char c) { - return static_cast( std::tolower( c ) ); - } void toLowerInPlace( std::string& s ) { std::transform( s.begin(), s.end(), s.begin(), toLowerCh ); } @@ -9763,9 +10673,9 @@ namespace Catch { namespace Catch { RegistrarForTagAliases::RegistrarForTagAliases(char const* alias, char const* tag, SourceLineInfo const& lineInfo) { - try { + CATCH_TRY { getMutableRegistryHub().registerTagAlias(alias, tag, lineInfo); - } catch (...) { + } CATCH_CATCH_ALL { // Do not throw when constructing global objects, instead register the exception to be processed later getMutableRegistryHub().registerStartupException(); } @@ -9829,31 +10739,33 @@ namespace Catch { namespace Catch { - TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) { - if( startsWith( tag, '.' ) || - tag == "!hide" ) - return TestCaseInfo::IsHidden; - else if( tag == "!throws" ) - return TestCaseInfo::Throws; - else if( tag == "!shouldfail" ) - return TestCaseInfo::ShouldFail; - else if( tag == "!mayfail" ) - return TestCaseInfo::MayFail; - else if( tag == "!nonportable" ) - return TestCaseInfo::NonPortable; - else if( tag == "!benchmark" ) - return static_cast( TestCaseInfo::Benchmark | TestCaseInfo::IsHidden ); - else - return TestCaseInfo::None; - } - bool isReservedTag( std::string const& tag ) { - return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !std::isalnum( tag[0] ); - } - void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) { - CATCH_ENFORCE( !isReservedTag(tag), - "Tag name: [" << tag << "] is not allowed.\n" - << "Tag names starting with non alpha-numeric characters are reserved\n" - << _lineInfo ); + namespace { + TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) { + if( startsWith( tag, '.' ) || + tag == "!hide" ) + return TestCaseInfo::IsHidden; + else if( tag == "!throws" ) + return TestCaseInfo::Throws; + else if( tag == "!shouldfail" ) + return TestCaseInfo::ShouldFail; + else if( tag == "!mayfail" ) + return TestCaseInfo::MayFail; + else if( tag == "!nonportable" ) + return TestCaseInfo::NonPortable; + else if( tag == "!benchmark" ) + return static_cast( TestCaseInfo::Benchmark | TestCaseInfo::IsHidden ); + else + return TestCaseInfo::None; + } + bool isReservedTag( std::string const& tag ) { + return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !std::isalnum( static_cast(tag[0]) ); + } + void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) { + CATCH_ENFORCE( !isReservedTag(tag), + "Tag name: [" << tag << "] is not allowed.\n" + << "Tag names starting with non alpha-numeric characters are reserved\n" + << _lineInfo ); + } } TestCase makeTestCase( ITestInvoker* _testCase, @@ -10001,7 +10913,7 @@ namespace Catch { break; case RunTests::InRandomOrder: seedRng( config ); - RandomNumberGenerator::shuffle( sorted ); + std::shuffle( sorted.begin(), sorted.end(), rng() ); break; case RunTests::InDeclarationOrder: // already in declaration order @@ -10085,7 +10997,7 @@ namespace Catch { // start catch_test_case_tracker.cpp #include -#include +#include #include #include #include @@ -10141,13 +11053,6 @@ namespace TestCaseTracking { m_currentTracker = tracker; } - TrackerBase::TrackerHasName::TrackerHasName( NameAndLocation const& nameAndLocation ) : m_nameAndLocation( nameAndLocation ) {} - bool TrackerBase::TrackerHasName::operator ()( ITrackerPtr const& tracker ) const { - return - tracker->nameAndLocation().name == m_nameAndLocation.name && - tracker->nameAndLocation().location == m_nameAndLocation.location; - } - TrackerBase::TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) : m_nameAndLocation( nameAndLocation ), m_ctx( ctx ), @@ -10175,7 +11080,12 @@ namespace TestCaseTracking { } ITrackerPtr TrackerBase::findChild( NameAndLocation const& nameAndLocation ) { - auto it = std::find_if( m_children.begin(), m_children.end(), TrackerHasName( nameAndLocation ) ); + 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; + } ); return( it != m_children.end() ) ? *it : nullptr; @@ -10367,7 +11277,7 @@ namespace Catch { NameAndTags::NameAndTags( StringRef const& name_ , StringRef const& tags_ ) noexcept : name( name_ ), tags( tags_ ) {} AutoReg::AutoReg( ITestInvoker* invoker, SourceLineInfo const& lineInfo, StringRef const& classOrMethod, NameAndTags const& nameAndTags ) noexcept { - try { + CATCH_TRY { getMutableRegistryHub() .registerTest( makeTestCase( @@ -10375,7 +11285,7 @@ namespace Catch { extractClassName( classOrMethod ), nameAndTags, lineInfo)); - } catch (...) { + } CATCH_CATCH_ALL { // Do not throw when constructing global objects, instead register the exception to be processed later getMutableRegistryHub().registerStartupException(); } @@ -10529,34 +11439,36 @@ namespace Catch { return std::chrono::duration_cast( std::chrono::high_resolution_clock::now().time_since_epoch() ).count(); } - auto estimateClockResolution() -> uint64_t { - uint64_t sum = 0; - static const uint64_t iterations = 1000000; + namespace { + auto estimateClockResolution() -> uint64_t { + uint64_t sum = 0; + static const uint64_t iterations = 1000000; - auto startTime = getCurrentNanosecondsSinceEpoch(); + auto startTime = getCurrentNanosecondsSinceEpoch(); - for( std::size_t i = 0; i < iterations; ++i ) { + for( std::size_t i = 0; i < iterations; ++i ) { - uint64_t ticks; - uint64_t baseTicks = getCurrentNanosecondsSinceEpoch(); - do { - ticks = getCurrentNanosecondsSinceEpoch(); - } while( ticks == baseTicks ); + uint64_t ticks; + uint64_t baseTicks = getCurrentNanosecondsSinceEpoch(); + do { + ticks = getCurrentNanosecondsSinceEpoch(); + } while( ticks == baseTicks ); - auto delta = ticks - baseTicks; - sum += delta; + auto delta = ticks - baseTicks; + sum += delta; - // If we have been calibrating for over 3 seconds -- the clock - // is terrible and we should move on. - // TBD: How to signal that the measured resolution is probably wrong? - if (ticks > startTime + 3 * nanosecondsInSecond) { - return sum / i; + // If we have been calibrating for over 3 seconds -- the clock + // is terrible and we should move on. + // TBD: How to signal that the measured resolution is probably wrong? + if (ticks > startTime + 3 * nanosecondsInSecond) { + return sum / i; + } } - } - // We're just taking the mean, here. To do better we could take the std. dev and exclude outliers - // - and potentially do more iterations if there's a high variance. - return sum/iterations; + // We're just taking the mean, here. To do better we could take the std. dev and exclude outliers + // - and potentially do more iterations if there's a high variance. + return sum/iterations; + } } auto getEstimatedClockResolution() -> uint64_t { static auto s_resolution = estimateClockResolution(); @@ -10687,14 +11599,9 @@ std::string StringMaker::convert(const std::string& str) { return s; } -#ifdef CATCH_CONFIG_WCHAR -std::string StringMaker::convert(const std::wstring& wstr) { - std::string s; - s.reserve(wstr.size()); - for (auto c : wstr) { - s += (c <= 0xff) ? static_cast(c) : '?'; - } - return ::Catch::Detail::stringify(s); +#ifdef CATCH_CONFIG_CPP17_STRING_VIEW +std::string StringMaker::convert(std::string_view str) { + return ::Catch::Detail::stringify(std::string{ str }); } #endif @@ -10712,7 +11619,23 @@ std::string StringMaker::convert(char* str) { return{ "{null string}" }; } } + #ifdef CATCH_CONFIG_WCHAR +std::string StringMaker::convert(const std::wstring& wstr) { + std::string s; + s.reserve(wstr.size()); + for (auto c : wstr) { + s += (c <= 0xff) ? static_cast(c) : '?'; + } + return ::Catch::Detail::stringify(s); +} + +# ifdef CATCH_CONFIG_CPP17_STRING_VIEW +std::string StringMaker::convert(std::wstring_view str) { + return StringMaker::convert(std::wstring(str)); +} +# endif + std::string StringMaker::convert(wchar_t const * str) { if (str) { return ::Catch::Detail::stringify(std::wstring{ str }); @@ -10800,8 +11723,8 @@ std::string StringMaker::convert(double value) { std::string ratio_string::symbol() { return "a"; } std::string ratio_string::symbol() { return "f"; } -std::string ratio_string::symbol() { return "p"; } -std::string ratio_string::symbol() { return "n"; } +std::string ratio_string::symbol() { return "p"; } +std::string ratio_string::symbol() { return "n"; } std::string ratio_string::symbol() { return "u"; } std::string ratio_string::symbol() { return "m"; } @@ -10913,7 +11836,7 @@ namespace Catch { } Version const& libraryVersion() { - static Version version( 2, 2, 2, "", 0 ); + static Version version( 2, 4, 1, "", 0 ); return version; } @@ -11240,7 +12163,7 @@ namespace { #include #include #include -#include +#include #include namespace Catch { @@ -11515,9 +12438,7 @@ class AssertionPrinter { } ReporterPreferences CompactReporter::getPreferences() const { - ReporterPreferences prefs; - prefs.shouldRedirectStdOut = false; - return prefs; + return m_reporterPrefs; } void CompactReporter::noMatchingTestCases( std::string const& spec ) { @@ -12183,7 +13104,7 @@ CATCH_REGISTER_REPORTER("console", ConsoleReporter) // end catch_reporter_console.cpp // start catch_reporter_junit.cpp -#include +#include #include #include #include @@ -12232,6 +13153,7 @@ namespace Catch { xml( _config.stream() ) { m_reporterPrefs.shouldRedirectStdOut = true; + m_reporterPrefs.shouldReportAllAssertions = true; } JunitReporter::~JunitReporter() {} @@ -12416,100 +13338,139 @@ namespace Catch { } // end namespace Catch // end catch_reporter_junit.cpp -// start catch_reporter_multi.cpp +// start catch_reporter_listening.cpp + +#include namespace Catch { - void MultipleReporters::add( IStreamingReporterPtr&& reporter ) { - m_reporters.push_back( std::move( reporter ) ); + ListeningReporter::ListeningReporter() { + // We will assume that listeners will always want all assertions + m_preferences.shouldReportAllAssertions = true; + } + + void ListeningReporter::addListener( IStreamingReporterPtr&& listener ) { + m_listeners.push_back( std::move( listener ) ); + } + + void ListeningReporter::addReporter(IStreamingReporterPtr&& reporter) { + assert(!m_reporter && "Listening reporter can wrap only 1 real reporter"); + m_reporter = std::move( reporter ); + m_preferences.shouldRedirectStdOut = m_reporter->getPreferences().shouldRedirectStdOut; } - ReporterPreferences MultipleReporters::getPreferences() const { - return m_reporters[0]->getPreferences(); + ReporterPreferences ListeningReporter::getPreferences() const { + return m_preferences; } - std::set MultipleReporters::getSupportedVerbosities() { + std::set ListeningReporter::getSupportedVerbosities() { return std::set{ }; } - void MultipleReporters::noMatchingTestCases( std::string const& spec ) { - for( auto const& reporter : m_reporters ) - reporter->noMatchingTestCases( spec ); + void ListeningReporter::noMatchingTestCases( std::string const& spec ) { + for ( auto const& listener : m_listeners ) { + listener->noMatchingTestCases( spec ); + } + m_reporter->noMatchingTestCases( spec ); } - void MultipleReporters::benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) { - for( auto const& reporter : m_reporters ) - reporter->benchmarkStarting( benchmarkInfo ); + void ListeningReporter::benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) { + for ( auto const& listener : m_listeners ) { + listener->benchmarkStarting( benchmarkInfo ); + } + m_reporter->benchmarkStarting( benchmarkInfo ); } - void MultipleReporters::benchmarkEnded( BenchmarkStats const& benchmarkStats ) { - for( auto const& reporter : m_reporters ) - reporter->benchmarkEnded( benchmarkStats ); + void ListeningReporter::benchmarkEnded( BenchmarkStats const& benchmarkStats ) { + for ( auto const& listener : m_listeners ) { + listener->benchmarkEnded( benchmarkStats ); + } + m_reporter->benchmarkEnded( benchmarkStats ); } - void MultipleReporters::testRunStarting( TestRunInfo const& testRunInfo ) { - for( auto const& reporter : m_reporters ) - reporter->testRunStarting( testRunInfo ); + void ListeningReporter::testRunStarting( TestRunInfo const& testRunInfo ) { + for ( auto const& listener : m_listeners ) { + listener->testRunStarting( testRunInfo ); + } + m_reporter->testRunStarting( testRunInfo ); } - void MultipleReporters::testGroupStarting( GroupInfo const& groupInfo ) { - for( auto const& reporter : m_reporters ) - reporter->testGroupStarting( groupInfo ); + void ListeningReporter::testGroupStarting( GroupInfo const& groupInfo ) { + for ( auto const& listener : m_listeners ) { + listener->testGroupStarting( groupInfo ); + } + m_reporter->testGroupStarting( groupInfo ); } - void MultipleReporters::testCaseStarting( TestCaseInfo const& testInfo ) { - for( auto const& reporter : m_reporters ) - reporter->testCaseStarting( testInfo ); + void ListeningReporter::testCaseStarting( TestCaseInfo const& testInfo ) { + for ( auto const& listener : m_listeners ) { + listener->testCaseStarting( testInfo ); + } + m_reporter->testCaseStarting( testInfo ); } - void MultipleReporters::sectionStarting( SectionInfo const& sectionInfo ) { - for( auto const& reporter : m_reporters ) - reporter->sectionStarting( sectionInfo ); + void ListeningReporter::sectionStarting( SectionInfo const& sectionInfo ) { + for ( auto const& listener : m_listeners ) { + listener->sectionStarting( sectionInfo ); + } + m_reporter->sectionStarting( sectionInfo ); } - void MultipleReporters::assertionStarting( AssertionInfo const& assertionInfo ) { - for( auto const& reporter : m_reporters ) - reporter->assertionStarting( assertionInfo ); + void ListeningReporter::assertionStarting( AssertionInfo const& assertionInfo ) { + for ( auto const& listener : m_listeners ) { + listener->assertionStarting( assertionInfo ); + } + m_reporter->assertionStarting( assertionInfo ); } // The return value indicates if the messages buffer should be cleared: - bool MultipleReporters::assertionEnded( AssertionStats const& assertionStats ) { - bool clearBuffer = false; - for( auto const& reporter : m_reporters ) - clearBuffer |= reporter->assertionEnded( assertionStats ); - return clearBuffer; + bool ListeningReporter::assertionEnded( AssertionStats const& assertionStats ) { + for( auto const& listener : m_listeners ) { + static_cast( listener->assertionEnded( assertionStats ) ); + } + return m_reporter->assertionEnded( assertionStats ); } - void MultipleReporters::sectionEnded( SectionStats const& sectionStats ) { - for( auto const& reporter : m_reporters ) - reporter->sectionEnded( sectionStats ); + void ListeningReporter::sectionEnded( SectionStats const& sectionStats ) { + for ( auto const& listener : m_listeners ) { + listener->sectionEnded( sectionStats ); + } + m_reporter->sectionEnded( sectionStats ); } - void MultipleReporters::testCaseEnded( TestCaseStats const& testCaseStats ) { - for( auto const& reporter : m_reporters ) - reporter->testCaseEnded( testCaseStats ); + void ListeningReporter::testCaseEnded( TestCaseStats const& testCaseStats ) { + for ( auto const& listener : m_listeners ) { + listener->testCaseEnded( testCaseStats ); + } + m_reporter->testCaseEnded( testCaseStats ); } - void MultipleReporters::testGroupEnded( TestGroupStats const& testGroupStats ) { - for( auto const& reporter : m_reporters ) - reporter->testGroupEnded( testGroupStats ); + void ListeningReporter::testGroupEnded( TestGroupStats const& testGroupStats ) { + for ( auto const& listener : m_listeners ) { + listener->testGroupEnded( testGroupStats ); + } + m_reporter->testGroupEnded( testGroupStats ); } - void MultipleReporters::testRunEnded( TestRunStats const& testRunStats ) { - for( auto const& reporter : m_reporters ) - reporter->testRunEnded( testRunStats ); + void ListeningReporter::testRunEnded( TestRunStats const& testRunStats ) { + for ( auto const& listener : m_listeners ) { + listener->testRunEnded( testRunStats ); + } + m_reporter->testRunEnded( testRunStats ); } - void MultipleReporters::skipTest( TestCaseInfo const& testInfo ) { - for( auto const& reporter : m_reporters ) - reporter->skipTest( testInfo ); + void ListeningReporter::skipTest( TestCaseInfo const& testInfo ) { + for ( auto const& listener : m_listeners ) { + listener->skipTest( testInfo ); + } + m_reporter->skipTest( testInfo ); } - bool MultipleReporters::isMulti() const { + bool ListeningReporter::isMulti() const { return true; } } // end namespace Catch -// end catch_reporter_multi.cpp +// end catch_reporter_listening.cpp // start catch_reporter_xml.cpp #if defined(_MSC_VER) @@ -12525,6 +13486,7 @@ namespace Catch { m_xml(_config.stream()) { m_reporterPrefs.shouldRedirectStdOut = true; + m_reporterPrefs.shouldReportAllAssertions = true; } XmlReporter::~XmlReporter() = default; @@ -12581,8 +13543,7 @@ namespace Catch { StreamingReporterBase::sectionStarting( sectionInfo ); if( m_sectionDepth++ > 0 ) { m_xml.startElement( "Section" ) - .writeAttribute( "name", trim( sectionInfo.name ) ) - .writeAttribute( "description", sectionInfo.description ); + .writeAttribute( "name", trim( sectionInfo.name ) ); writeSourceInfo( sectionInfo.lineInfo ); m_xml.ensureTagClosed(); } @@ -12817,13 +13778,14 @@ int main (int argc, char * const argv[]) { #define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( "CATCH_INFO", msg ) #define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( "CATCH_WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) -#define CATCH_CAPTURE( msg ) INTERNAL_CATCH_INFO( "CATCH_CAPTURE", #msg " := " << ::Catch::Detail::stringify(msg) ) +#define CATCH_CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CATCH_CAPTURE",__VA_ARGS__ ) #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) #define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) #define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) #define CATCH_REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) #define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) +#define CATCH_DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ ) #define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) #define CATCH_FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) #define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( "CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) @@ -12833,11 +13795,12 @@ int main (int argc, char * const argv[]) { // "BDD-style" convenience wrappers #define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( "Scenario: " __VA_ARGS__ ) #define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) -#define CATCH_GIVEN( desc ) CATCH_SECTION( std::string( "Given: ") + desc ) -#define CATCH_WHEN( desc ) CATCH_SECTION( std::string( " When: ") + desc ) -#define CATCH_AND_WHEN( desc ) CATCH_SECTION( std::string( " And: ") + desc ) -#define CATCH_THEN( desc ) CATCH_SECTION( std::string( " Then: ") + desc ) -#define CATCH_AND_THEN( desc ) CATCH_SECTION( std::string( " And: ") + desc ) +#define CATCH_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Given: " << desc ) +#define CATCH_AND_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( "And given: " << desc ) +#define CATCH_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " When: " << desc ) +#define CATCH_AND_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And when: " << desc ) +#define CATCH_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Then: " << desc ) +#define CATCH_AND_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And: " << desc ) // If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required #else @@ -12875,13 +13838,14 @@ int main (int argc, char * const argv[]) { #define INFO( msg ) INTERNAL_CATCH_INFO( "INFO", msg ) #define WARN( msg ) INTERNAL_CATCH_MSG( "WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) -#define CAPTURE( msg ) INTERNAL_CATCH_INFO( "CAPTURE", #msg " := " << ::Catch::Detail::stringify(msg) ) +#define CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CAPTURE",__VA_ARGS__ ) #define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) #define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) #define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) #define REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) #define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) +#define DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ ) #define FAIL( ... ) INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) #define FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) #define SUCCEED( ... ) INTERNAL_CATCH_MSG( "SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) @@ -12895,15 +13859,17 @@ int main (int argc, char * const argv[]) { #define SCENARIO( ... ) TEST_CASE( "Scenario: " __VA_ARGS__ ) #define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) -#define GIVEN( desc ) SECTION( std::string(" Given: ") + desc ) -#define WHEN( desc ) SECTION( std::string(" When: ") + desc ) -#define AND_WHEN( desc ) SECTION( std::string("And when: ") + desc ) -#define THEN( desc ) SECTION( std::string(" Then: ") + desc ) -#define AND_THEN( desc ) SECTION( std::string(" And: ") + desc ) +#define GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Given: " << desc ) +#define AND_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( "And given: " << desc ) +#define WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " When: " << desc ) +#define AND_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And when: " << desc ) +#define THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Then: " << desc ) +#define AND_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And: " << desc ) using Catch::Detail::Approx; -#else +#else // CATCH_CONFIG_DISABLE + ////// // If this config identifier is defined then all CATCH macros are prefixed with CATCH_ #ifdef CATCH_CONFIG_PREFIX_ALL @@ -12948,6 +13914,7 @@ using Catch::Detail::Approx; #define CATCH_METHOD_AS_TEST_CASE( method, ... ) #define CATCH_REGISTER_TEST_CASE( Function, ... ) (void)(0) #define CATCH_SECTION( ... ) +#define CATCH_DYNAMIC_SECTION( ... ) #define CATCH_FAIL( ... ) (void)(0) #define CATCH_FAIL_CHECK( ... ) (void)(0) #define CATCH_SUCCEED( ... ) (void)(0) @@ -12958,6 +13925,7 @@ using Catch::Detail::Approx; #define CATCH_SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) #define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), className ) #define CATCH_GIVEN( desc ) +#define CATCH_AND_GIVEN( desc ) #define CATCH_WHEN( desc ) #define CATCH_AND_WHEN( desc ) #define CATCH_THEN( desc ) @@ -13006,6 +13974,7 @@ using Catch::Detail::Approx; #define METHOD_AS_TEST_CASE( method, ... ) #define REGISTER_TEST_CASE( Function, ... ) (void)(0) #define SECTION( ... ) +#define DYNAMIC_SECTION( ... ) #define FAIL( ... ) (void)(0) #define FAIL_CHECK( ... ) (void)(0) #define SUCCEED( ... ) (void)(0) @@ -13020,6 +13989,7 @@ using Catch::Detail::Approx; #define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), className ) #define GIVEN( desc ) +#define AND_GIVEN( desc ) #define WHEN( desc ) #define AND_WHEN( desc ) #define THEN( desc ) diff --git a/src/noopspan.cpp b/src/noopspan.cpp index 17fa12ae..cc756f3d 100644 --- a/src/noopspan.cpp +++ b/src/noopspan.cpp @@ -40,6 +40,13 @@ const ot::SpanContext &NoopSpan::context() const noexcept { return context_; } const ot::Tracer &NoopSpan::tracer() const noexcept { return *tracer_; } uint64_t NoopSpan::traceId() const { return trace_id_; } +uint64_t NoopSpan::spanId() const { return span_id_; } + +OptionalSamplingPriority NoopSpan::setSamplingPriority( + std::unique_ptr priority) { + return nullptr; +}; +OptionalSamplingPriority NoopSpan::getSamplingPriority() const { return nullptr; }; } // namespace opentracing } // namespace datadog diff --git a/src/noopspan.h b/src/noopspan.h index 06a925ac..f0c99399 100644 --- a/src/noopspan.h +++ b/src/noopspan.h @@ -5,6 +5,7 @@ #include #include #include "propagation.h" +#include "span.h" namespace ot = opentracing; @@ -14,7 +15,7 @@ namespace opentracing { class Tracer; // A NoopSpan, provides a noop implementation of opentracing::Span methods -class NoopSpan : public ot::Span { +class NoopSpan : public DatadogSpan { public: // Creates a new NoopSpan, usually called by Tracer::StartSpanWithOptions. NoopSpan(std::shared_ptr tracer, uint64_t span_id, uint64_t trace_id, @@ -31,7 +32,11 @@ class NoopSpan : public ot::Span { void Log(std::initializer_list> fields) noexcept override; const ot::SpanContext &context() const noexcept override; const ot::Tracer &tracer() const noexcept override; - uint64_t traceId() const; + uint64_t traceId() const override; + uint64_t spanId() const override; + OptionalSamplingPriority setSamplingPriority( + std::unique_ptr priority) override; + OptionalSamplingPriority getSamplingPriority() const override; private: std::shared_ptr tracer_; diff --git a/src/propagation.cpp b/src/propagation.cpp index 84d0d292..33960195 100644 --- a/src/propagation.cpp +++ b/src/propagation.cpp @@ -2,6 +2,9 @@ #include #include #include +#include +#include "sample.h" +#include "span_buffer.h" namespace ot = opentracing; using json = nlohmann::json; @@ -52,33 +55,30 @@ OptionalSamplingPriority asSamplingPriority(int i) { } SpanContext::SpanContext(uint64_t id, uint64_t trace_id, - OptionalSamplingPriority sampling_priority, std::unordered_map &&baggage) - : id_(id), - trace_id_(trace_id), - sampling_priority_(std::move(sampling_priority)), - baggage_(std::move(baggage)) {} + : id_(id), trace_id_(trace_id), baggage_(std::move(baggage)) {} SpanContext SpanContext::NginxOpenTracingCompatibilityHackSpanContext( - uint64_t id, uint64_t trace_id, OptionalSamplingPriority sampling_priority, - std::unordered_map &&baggage) { - SpanContext c = SpanContext(id, trace_id, std::move(sampling_priority), std::move(baggage)); + uint64_t id, uint64_t trace_id, std::unordered_map &&baggage) { + SpanContext c = SpanContext{id, trace_id, std::move(baggage)}; c.nginx_opentracing_compatibility_hack_ = true; return c; } SpanContext::SpanContext(SpanContext &&other) : nginx_opentracing_compatibility_hack_(other.nginx_opentracing_compatibility_hack_), + propagated_sampling_priority_(std::move(other.propagated_sampling_priority_)), + has_propagated_(other.has_propagated_), id_(other.id_), trace_id_(other.trace_id_), - sampling_priority_(std::move(other.sampling_priority_)), baggage_(std::move(other.baggage_)) {} SpanContext &SpanContext::operator=(SpanContext &&other) { std::lock_guard lock{mutex_}; id_ = other.id_; trace_id_ = other.trace_id_; - sampling_priority_ = std::move(other.sampling_priority_); + propagated_sampling_priority_ = std::move(other.propagated_sampling_priority_); + has_propagated_ = other.has_propagated_; baggage_ = std::move(other.baggage_); nginx_opentracing_compatibility_hack_ = other.nginx_opentracing_compatibility_hack_; return *this; @@ -99,26 +99,18 @@ uint64_t SpanContext::id() const { return id_; } -uint64_t SpanContext::trace_id() const { +uint64_t SpanContext::traceId() const { // Not locked, since trace_id_ never modified. return trace_id_; } -OptionalSamplingPriority SpanContext::getSamplingPriority() const { - std::lock_guard lock{mutex_}; - if (sampling_priority_ == nullptr) { - return nullptr; - } - return std::make_unique(*sampling_priority_); -} - -void SpanContext::setSamplingPriority(OptionalSamplingPriority p) { - std::lock_guard lock{mutex_}; - if (p == nullptr) { - sampling_priority_.reset(nullptr); - } else { - sampling_priority_.reset(new SamplingPriority(*p)); +std::pair SpanContext::getPropagationStatus() const { + // Not locked. Both these members are only ever written in the constructor/builder. + OptionalSamplingPriority p = nullptr; + if (propagated_sampling_priority_ != nullptr) { + p.reset(new SamplingPriority(*propagated_sampling_priority_)); } + return std::make_pair(has_propagated_, std::move(p)); } void SpanContext::setBaggageItem(ot::string_view key, ot::string_view value) noexcept try { @@ -139,15 +131,17 @@ std::string SpanContext::baggageItem(ot::string_view key) const { SpanContext SpanContext::withId(uint64_t id) const { std::lock_guard lock{mutex_}; auto baggage = baggage_; // (Shallow) copy baggage. - // Copy sampling_priority_. - std::unique_ptr p = nullptr; - if (sampling_priority_ != nullptr) { - p.reset(new SamplingPriority(*sampling_priority_)); + SpanContext context{id, trace_id_, std::move(baggage)}; + if (propagated_sampling_priority_ != nullptr) { + context.propagated_sampling_priority_.reset( + new SamplingPriority(*propagated_sampling_priority_)); } - return SpanContext{id, trace_id_, std::move(p), std::move(baggage)}; + context.has_propagated_ = has_propagated_; + return context; } -ot::expected SpanContext::serialize(std::ostream &writer) const { +ot::expected SpanContext::serialize(std::ostream &writer, + const std::shared_ptr pending_traces) const { // check ostream state if (!writer.good()) { return ot::make_unexpected(std::make_error_code(std::errc::io_error)); @@ -157,8 +151,9 @@ ot::expected SpanContext::serialize(std::ostream &writer) const { // JSON numbers only support 64bit IEEE 754, so we encode these as strings. j[json_trace_id_key] = std::to_string(trace_id_); j[json_parent_id_key] = std::to_string(id_); - if (sampling_priority_ != nullptr) { - j[json_sampling_priority_key] = static_cast(*sampling_priority_); + OptionalSamplingPriority sampling_priority = pending_traces->getSamplingPriority(trace_id_); + if (sampling_priority != nullptr) { + j[json_sampling_priority_key] = static_cast(*sampling_priority); } j[json_baggage_key] = baggage_; @@ -171,7 +166,8 @@ ot::expected SpanContext::serialize(std::ostream &writer) const { return {}; } -ot::expected SpanContext::serialize(const ot::TextMapWriter &writer) const { +ot::expected SpanContext::serialize(const ot::TextMapWriter &writer, + const std::shared_ptr pending_traces) const { std::lock_guard lock{mutex_}; auto result = writer.Set(trace_id_header, std::to_string(trace_id_)); if (!result) { @@ -184,9 +180,10 @@ ot::expected SpanContext::serialize(const ot::TextMapWriter &writer) const return result; } - if (sampling_priority_ != nullptr) { - result = writer.Set(sampling_priority_header, - std::to_string(static_cast(*sampling_priority_))); + OptionalSamplingPriority sampling_priority = pending_traces->getSamplingPriority(trace_id_); + if (sampling_priority != nullptr) { + result = + writer.Set(sampling_priority_header, std::to_string(static_cast(*sampling_priority))); if (!result) { return result; } @@ -253,9 +250,10 @@ ot::expected> SpanContext::deserialize(std::ist baggage = j[json_baggage_key].get>(); } - return std::unique_ptr(std::make_unique( - parent_id, trace_id, std::move(sampling_priority), std::move(baggage))); - + auto context = std::make_unique(parent_id, trace_id, std::move(baggage)); + context->has_propagated_ = true; + context->propagated_sampling_priority_ = std::move(sampling_priority); + return std::unique_ptr(std::move(context)); } catch (const json::parse_error &) { return ot::make_unexpected(std::make_error_code(std::errc::invalid_argument)); } catch (const std::invalid_argument &ia) { @@ -309,8 +307,10 @@ ot::expected> SpanContext::deserialize( // Partial context, this shouldn't happen. return ot::make_unexpected(ot::span_context_corrupted_error); } - return std::unique_ptr(std::make_unique( - parent_id, trace_id, std::move(sampling_priority), std::move(baggage))); + auto context = std::make_unique(parent_id, trace_id, std::move(baggage)); + context->has_propagated_ = true; + context->propagated_sampling_priority_ = std::move(sampling_priority); + return std::unique_ptr(std::move(context)); } catch (const std::bad_alloc &) { return ot::make_unexpected(std::make_error_code(std::errc::not_enough_memory)); } diff --git a/src/propagation.h b/src/propagation.h index 2d9cd224..9bbeb9f7 100644 --- a/src/propagation.h +++ b/src/propagation.h @@ -10,6 +10,10 @@ namespace ot = opentracing; namespace datadog { namespace opentracing { +class SpanBuffer; +class SampleProvider; +class SpanData; + enum class SamplingPriority : int { UserDrop = -1, SamplerDrop = 0, @@ -20,6 +24,12 @@ enum class SamplingPriority : int { MaximumValue = UserKeep, }; +// A SamplingPriority that encompasses only values that may be directly set by users. +enum class UserSamplingPriority : int { + UserDrop = static_cast(SamplingPriority::UserDrop), + UserKeep = static_cast(SamplingPriority::UserKeep), +}; + // Move to std::optional in C++17 when it has better compiler support. using OptionalSamplingPriority = std::unique_ptr; @@ -27,13 +37,12 @@ OptionalSamplingPriority asSamplingPriority(int i); class SpanContext : public ot::SpanContext { public: - SpanContext(uint64_t id, uint64_t trace_id, OptionalSamplingPriority sampling_priority, + SpanContext(uint64_t id, uint64_t trace_id, std::unordered_map &&baggage); // Enables a hack, see the comment below on nginx_opentracing_compatibility_hack_. static SpanContext NginxOpenTracingCompatibilityHackSpanContext( - uint64_t id, uint64_t trace_id, OptionalSamplingPriority sampling_priority, - std::unordered_map &&baggage); + uint64_t id, uint64_t trace_id, std::unordered_map &&baggage); SpanContext(SpanContext &&other); SpanContext &operator=(SpanContext &&other); @@ -46,8 +55,10 @@ class SpanContext : public ot::SpanContext { std::string baggageItem(ot::string_view key) const; // Serializes the context into the given writer. - ot::expected serialize(std::ostream &writer) const; - ot::expected serialize(const ot::TextMapWriter &writer) const; + ot::expected serialize(std::ostream &writer, + const std::shared_ptr pending_traces) const; + ot::expected serialize(const ot::TextMapWriter &writer, + const std::shared_ptr pending_traces) const; SpanContext withId(uint64_t id) const; @@ -57,9 +68,11 @@ class SpanContext : public ot::SpanContext { const ot::TextMapReader &reader); uint64_t id() const; - uint64_t trace_id() const; - OptionalSamplingPriority getSamplingPriority() const; - void setSamplingPriority(OptionalSamplingPriority p); + uint64_t traceId() const; + // Returns a pair of: + // * bool, true if this SpanContext may have arrived via propagation. + // * an OptionalSamplingPriority, the propagated sampling priority. + std::pair getPropagationStatus() const; private: // Terrible, terrible hack; to get around: @@ -80,11 +93,14 @@ class SpanContext : public ot::SpanContext { // make it more of a pain to do and less obvious what's happening. bool nginx_opentracing_compatibility_hack_ = false; + OptionalSamplingPriority propagated_sampling_priority_ = nullptr; + bool has_propagated_ = false; + uint64_t id_; uint64_t trace_id_; - OptionalSamplingPriority sampling_priority_; - std::unordered_map baggage_; + mutable std::mutex mutex_; + std::unordered_map baggage_; }; } // namespace opentracing diff --git a/src/sample.cpp b/src/sample.cpp index d4703ed9..f5fc2143 100644 --- a/src/sample.cpp +++ b/src/sample.cpp @@ -34,7 +34,7 @@ bool DiscardRateSampler::discard(const SpanContext& context) const { // rather than generating a new random number here. It's a bit faster, and more importantly it's // cargo-culted from the agent. However it does still seem too "clever", and makes testing a // bit awkward. - uint64_t hashed_id = context.trace_id() * constant_rate_hash_factor; + uint64_t hashed_id = context.traceId() * constant_rate_hash_factor; if (hashed_id < max_trace_id_) { return false; } diff --git a/src/span.cpp b/src/span.cpp index 5c41399a..bf84501b 100644 --- a/src/span.cpp +++ b/src/span.cpp @@ -1,7 +1,9 @@ #include "span.h" +#include #include #include #include +#include #include "sample.h" #include "span_buffer.h" #include "tracer.h" @@ -18,7 +20,6 @@ const std::string datadog_resource_name_tag = "resource.name"; const std::string datadog_service_name_tag = "service.name"; const std::string http_url_tag = "http.url"; const std::string operation_name_tag = "operation"; -const std::string sampling_priority_metric = "_sampling_priority_v1"; } // namespace const std::string environment_tag = "environment"; @@ -77,7 +78,7 @@ Span::Span(std::shared_ptr tracer, std::shared_ptr buf std::chrono::duration_cast( start_time_.absolute_time.time_since_epoch()) .count())) { - buffer_->registerSpan(*span_.get()); // Doesn't keep reference. + buffer_->registerSpan(context_); } Span::~Span() { @@ -126,12 +127,6 @@ void Span::FinishWithOptions(const ot::FinishSpanOptions &finish_span_options) n span_->name = operation_name_override_; span_->resource = operation_name_override_; } - // Check for sampling. - assignSamplingPriority(); - OptionalSamplingPriority sampling_priority = context_.getSamplingPriority(); - if (sampling_priority != nullptr) { - span_->metrics[sampling_priority_metric] = static_cast(*sampling_priority); - } // Apply special tags. // If we add any more cases; then abstract this. For now, KISS. auto tag = span_->meta.find(datadog_span_type_tag); @@ -151,7 +146,7 @@ void Span::FinishWithOptions(const ot::FinishSpanOptions &finish_span_options) n } // Audit and finish span. audit(span_.get()); - buffer_->finishSpan(std::move(span_)); + buffer_->finishSpan(std::move(span_), sampler_); // According to the OT lifecycle, no more methods should be called on this Span. But just in case // let's make sure that span_ isn't nullptr. Fine line between defensive programming and voodoo. span_ = stubSpanData(); @@ -282,6 +277,28 @@ void Span::SetTag(ot::string_view key, const ot::Value &value) noexcept { std::lock_guard lock_guard{mutex_}; span_->meta[key] = result; } + + // Normally special tags are processed at Span Finish, but this cannot be done for + // sampling.priority because if no sampling is set before the Span Finishes then one is + // assigned immutably. + // Doesn't need to be in the same mutex lock as above. + if (key == ::ot::ext::sampling_priority) { + // https://github.com/opentracing/specification/blob/master/semantic_conventions.md#span-tags-table + // "sampling.priority" + try { + std::unique_ptr sampling_priority = nullptr; + if (result != "") { + sampling_priority = std::make_unique( + std::stoi(result) == 0 ? UserSamplingPriority::UserDrop + : UserSamplingPriority::UserKeep); + } + setSamplingPriority(std::move(sampling_priority)); + } catch (const std::invalid_argument &ia) { + std::cerr << "Unable to parse " << ::ot::ext::sampling_priority << " tag" << std::endl; + } catch (const std::out_of_range &oor) { + std::cerr << "Unable to parse " << ::ot::ext::sampling_priority << " tag" << std::endl; + } + } } void Span::SetBaggageItem(ot::string_view restricted_key, ot::string_view value) noexcept { @@ -294,12 +311,19 @@ std::string Span::BaggageItem(ot::string_view restricted_key) const noexcept { void Span::Log(std::initializer_list> fields) noexcept {} -void Span::assignSamplingPriority() const { - bool is_root_span = span_->parent_id == 0; - bool sampling_priority_unset = context_.getSamplingPriority() == nullptr; - if (is_root_span && sampling_priority_unset) { - context_.setSamplingPriority(sampler_->sample(span_->env(), span_->service, span_->trace_id)); +OptionalSamplingPriority Span::setSamplingPriority( + std::unique_ptr user_priority) { + std::lock_guard lock_guard{mutex_}; + OptionalSamplingPriority priority(nullptr); + if (user_priority != nullptr) { + priority = asSamplingPriority(static_cast(*user_priority)); } + return buffer_->setSamplingPriority(context_.traceId(), std::move(priority)); +} + +OptionalSamplingPriority Span::getSamplingPriority() const { + std::lock_guard lock_guard{mutex_}; + return buffer_->getSamplingPriority(context_.traceId()); } const ot::SpanContext &Span::context() const noexcept { @@ -311,8 +335,7 @@ const ot::SpanContext &Span::context() const noexcept { // here, when the context is fetched before being serialized. The negative side-effect is that if // anything else happens to want to get and/or serialize a SpanContext, that will end up having // this spooky action at a distance of assigning a SamplingPriority. - // For this reason this method can't be const unless context_ is mutable. - assignSamplingPriority(); + buffer_->assignSamplingPriority(sampler_, span_.get() /* Doesn't take ownership */); return context_; } diff --git a/src/span.h b/src/span.h index b40edb97..6dbd9c85 100644 --- a/src/span.h +++ b/src/span.h @@ -60,8 +60,36 @@ struct SpanData { trace_id, parent_id, error) }; +// A common interface for Datadog-specific Span operations. +class DatadogSpan : public ot::Span { + public: + // ot::Span methods. + virtual void FinishWithOptions( + const ot::FinishSpanOptions &finish_span_options) noexcept override = 0; + virtual void SetOperationName(ot::string_view name) noexcept override = 0; + virtual void SetTag(ot::string_view key, const ot::Value &value) noexcept override = 0; + virtual void SetBaggageItem(ot::string_view restricted_key, + ot::string_view value) noexcept override = 0; + virtual std::string BaggageItem(ot::string_view restricted_key) const noexcept override = 0; + virtual void Log( + std::initializer_list> fields) noexcept override = 0; + virtual const ot::SpanContext &context() const noexcept override = 0; + virtual const ot::Tracer &tracer() const noexcept override = 0; + + // Datadog methods. + + // Sets the SamplingPriority. If priority is null, then unsets SamplingPriority. Returns the + // value of the SamplingPriority; this may not be the same as the given parameter if this trace + // has propagated from a remote origin and already has a SamplingPriority. + virtual OptionalSamplingPriority setSamplingPriority( + std::unique_ptr priority) = 0; + virtual OptionalSamplingPriority getSamplingPriority() const = 0; + virtual uint64_t traceId() const = 0; + virtual uint64_t spanId() const = 0; +}; + // A Span, a component of a trace, a single instrumented event. -class Span : public ot::Span { +class Span : public DatadogSpan { public: // Creates a new Span. Span(std::shared_ptr tracer, std::shared_ptr buffer, @@ -90,11 +118,15 @@ class Span : public ot::Span { const ot::Tracer &tracer() const noexcept override; - uint64_t traceId() const; - uint64_t spanId() const; + uint64_t traceId() const override; + uint64_t spanId() const override; + OptionalSamplingPriority setSamplingPriority( + std::unique_ptr priority) override; + OptionalSamplingPriority getSamplingPriority() const override; private: - void assignSamplingPriority() const; // Sooo not const. See definition of method Span::context. + OptionalSamplingPriority assignSamplingPriority() + const; // Sooo not const. See definition of method Span::context. mutable std::mutex mutex_; std::atomic is_finished_{false}; @@ -104,7 +136,7 @@ class Span : public ot::Span { std::shared_ptr buffer_; TimeProvider get_time_; std::shared_ptr sampler_; - mutable SpanContext context_; // Mutable as a hack. See definition of method Span::context. + SpanContext context_; TimePoint start_time_; std::string operation_name_override_; diff --git a/src/span_buffer.cpp b/src/span_buffer.cpp index 1737433b..d620ec6d 100644 --- a/src/span_buffer.cpp +++ b/src/span_buffer.cpp @@ -1,25 +1,48 @@ #include "span_buffer.h" #include +#include "sample.h" #include "span.h" #include "writer.h" namespace datadog { namespace opentracing { +namespace { +const std::string sampling_priority_metric = "_sampling_priority_v1"; +} // namespace + +void PendingTrace::finish(const std::shared_ptr& sampler) { + if (finished_spans->size() == 0) { + std::cerr << "finish called on trace with no spans" << std::endl; + return; // I don't know why this would ever happen. + } + // Check for sampling. + if (sampling_priority != nullptr) { + // Set the metric for every span in the trace. + for (auto& span : *finished_spans) { + span->metrics[sampling_priority_metric] = static_cast(*sampling_priority); + } + } +} + WritingSpanBuffer::WritingSpanBuffer(std::shared_ptr writer) : writer_(writer) {} -void WritingSpanBuffer::registerSpan(const SpanData& span) { +void WritingSpanBuffer::registerSpan(const SpanContext& context) { std::lock_guard lock_guard{mutex_}; - uint64_t trace_id = span.traceId(); + uint64_t trace_id = context.traceId(); auto trace = traces_.find(trace_id); if (trace == traces_.end()) { traces_.emplace(std::make_pair(trace_id, PendingTrace{})); trace = traces_.find(trace_id); + auto propagation_status = context.getPropagationStatus(); + trace->second.sampling_priority_locked = propagation_status.first; + trace->second.sampling_priority = std::move(propagation_status.second); } - trace->second.all_spans.insert(span.spanId()); + trace->second.all_spans.insert(context.id()); } -void WritingSpanBuffer::finishSpan(std::unique_ptr span) { +void WritingSpanBuffer::finishSpan(std::unique_ptr span, + const std::shared_ptr& sampler) { std::lock_guard lock_guard{mutex_}; auto trace_iter = traces_.find(span->traceId()); if (trace_iter == traces_.end()) { @@ -31,11 +54,86 @@ void WritingSpanBuffer::finishSpan(std::unique_ptr span) { std::cerr << "A Span that was not registered was submitted to WritingSpanBuffer" << std::endl; return; } + uint64_t trace_id = span->traceId(); trace.finished_spans->push_back(std::move(span)); if (trace.finished_spans->size() == trace.all_spans.size()) { - writer_->write(std::move(trace.finished_spans)); - traces_.erase(trace_iter); + assignSamplingPriorityImpl(sampler, trace.finished_spans->back().get()); + trace.finish(sampler); + unbufferAndWriteTrace(trace_id, sampler); + } +} + +void WritingSpanBuffer::unbufferAndWriteTrace(uint64_t trace_id, + const std::shared_ptr& sampler) { + auto trace_iter = traces_.find(trace_id); + if (trace_iter == traces_.end()) { + return; + } + auto& trace = trace_iter->second; + writer_->write(std::move(trace.finished_spans)); + traces_.erase(trace_iter); +} + +OptionalSamplingPriority WritingSpanBuffer::getSamplingPriority(uint64_t trace_id) const { + std::lock_guard lock_guard{mutex_}; + return getSamplingPriorityImpl(trace_id); +} +OptionalSamplingPriority WritingSpanBuffer::getSamplingPriorityImpl(uint64_t trace_id) const { + auto trace = traces_.find(trace_id); + if (trace == traces_.end()) { + std::cerr << "Missing trace in getSamplingPriority" << std::endl; + return nullptr; + } + if (trace->second.sampling_priority == nullptr) { + return nullptr; + } + return std::make_unique(*trace->second.sampling_priority); +} + +OptionalSamplingPriority WritingSpanBuffer::setSamplingPriority( + uint64_t trace_id, OptionalSamplingPriority priority) { + std::lock_guard lock_guard{mutex_}; + return setSamplingPriorityImpl(trace_id, std::move(priority)); +} + +OptionalSamplingPriority WritingSpanBuffer::setSamplingPriorityImpl( + uint64_t trace_id, OptionalSamplingPriority priority) { + auto trace_entry = traces_.find(trace_id); + if (trace_entry == traces_.end()) { + std::cerr << "Missing trace in setSamplingPriority" << std::endl; + return nullptr; + } + PendingTrace& trace = trace_entry->second; + if (trace.sampling_priority_locked) { + std::cerr << "Sampling priority locked, trace already propagated" << std::endl; + return getSamplingPriorityImpl(trace_id); + } + if (priority == nullptr) { + trace.sampling_priority.reset(nullptr); + } else { + trace.sampling_priority.reset(new SamplingPriority(*priority)); + if (*priority == SamplingPriority::SamplerDrop || *priority == SamplingPriority::SamplerKeep) { + // This is an automatically-assigned sampling priority. + trace.sampling_priority_locked = true; + } + } + return getSamplingPriorityImpl(trace_id); +} + +OptionalSamplingPriority WritingSpanBuffer::assignSamplingPriority( + const std::shared_ptr& sampler, const SpanData* span) { + std::lock_guard lock{mutex_}; + return assignSamplingPriorityImpl(sampler, span); +} + +OptionalSamplingPriority WritingSpanBuffer::assignSamplingPriorityImpl( + const std::shared_ptr& sampler, const SpanData* span) { + bool sampling_priority_unset = getSamplingPriorityImpl(span->trace_id) == nullptr; + if (sampling_priority_unset) { + setSamplingPriorityImpl(span->trace_id, + sampler->sample(span->env(), span->service, span->trace_id)); } + return getSamplingPriorityImpl(span->trace_id); } } // namespace opentracing diff --git a/src/span_buffer.h b/src/span_buffer.h index eb7b960b..69c9bfc4 100644 --- a/src/span_buffer.h +++ b/src/span_buffer.h @@ -6,20 +6,26 @@ #include #include #include +#include "span.h" namespace datadog { namespace opentracing { class Writer; -class SpanData; +class SpanContext; +class SampleProvider; using Trace = std::unique_ptr>>; struct PendingTrace { PendingTrace() : finished_spans(Trace{new std::vector>()}), all_spans() {} + void finish(const std::shared_ptr& sampler); + Trace finished_spans; std::unordered_set all_spans; + OptionalSamplingPriority sampling_priority; + bool sampling_priority_locked = false; }; // Keeps track of Spans until there is a complete trace. @@ -27,8 +33,14 @@ class SpanBuffer { public: SpanBuffer() {} virtual ~SpanBuffer() {} - virtual void registerSpan(const SpanData& span) = 0; - virtual void finishSpan(std::unique_ptr span) = 0; + virtual void registerSpan(const SpanContext& context) = 0; + virtual void finishSpan(std::unique_ptr span, + const std::shared_ptr& sampler) = 0; + virtual OptionalSamplingPriority getSamplingPriority(uint64_t trace_id) const = 0; + virtual OptionalSamplingPriority setSamplingPriority(uint64_t trace_id, + OptionalSamplingPriority priority) = 0; + virtual OptionalSamplingPriority assignSamplingPriority( + const std::shared_ptr& sampler, const SpanData* span) = 0; }; // A SpanBuffer that sends completed traces to a Writer. @@ -36,13 +48,34 @@ class WritingSpanBuffer : public SpanBuffer { public: WritingSpanBuffer(std::shared_ptr writer); - void registerSpan(const SpanData& span) override; - void finishSpan(std::unique_ptr span) override; + void registerSpan(const SpanContext& context) override; + void finishSpan(std::unique_ptr span, + const std::shared_ptr& sampler) override; + + OptionalSamplingPriority getSamplingPriority(uint64_t trace_id) const override; + OptionalSamplingPriority setSamplingPriority(uint64_t trace_id, + OptionalSamplingPriority priority) override; + OptionalSamplingPriority assignSamplingPriority(const std::shared_ptr& sampler, + const SpanData* span) override; private: + // These xImpl methods exist so we can avoid using reentrant locks. + OptionalSamplingPriority getSamplingPriorityImpl(uint64_t trace_id) const; + OptionalSamplingPriority setSamplingPriorityImpl(uint64_t trace_id, + OptionalSamplingPriority priority); + OptionalSamplingPriority assignSamplingPriorityImpl( + const std::shared_ptr& sampler, const SpanData* span); + std::shared_ptr writer_; - std::unordered_map traces_; mutable std::mutex mutex_; + + protected: + // Exists to make it easy for a subclass (ie, our testing mock) to override on-trace-finish + // behaviour. + virtual void unbufferAndWriteTrace(uint64_t trace_id, + const std::shared_ptr& sampler); + + std::unordered_map traces_; }; } // namespace opentracing diff --git a/src/tracer.cpp b/src/tracer.cpp index 7a73c866..d3099e47 100644 --- a/src/tracer.cpp +++ b/src/tracer.cpp @@ -58,11 +58,10 @@ std::unique_ptr Tracer::StartSpanWithOptions(ot::string_view operation // Get a new span id. auto span_id = get_id_(); - SpanContext span_context = SpanContext{span_id, span_id, nullptr /* No sampling_priority */, {}}; + SpanContext span_context = SpanContext{span_id, span_id, {}}; // See the comment in propagation.h on nginx_opentracing_compatibility_hack_. if (operation_name == "dummySpan") { - span_context = - SpanContext::NginxOpenTracingCompatibilityHackSpanContext(span_id, span_id, nullptr, {}); + span_context = SpanContext::NginxOpenTracingCompatibilityHackSpanContext(span_id, span_id, {}); } auto trace_id = span_id; auto parent_id = uint64_t{0}; @@ -71,7 +70,7 @@ std::unique_ptr Tracer::StartSpanWithOptions(ot::string_view operation for (auto &reference : options.references) { if (auto parent_context = dynamic_cast(reference.second)) { span_context = parent_context->withId(span_id); - trace_id = parent_context->trace_id(); + trace_id = parent_context->traceId(); parent_id = parent_context->id(); break; } diff --git a/src/tracer.h b/src/tracer.h index 98d2fc63..d298ad3d 100644 --- a/src/tracer.h +++ b/src/tracer.h @@ -62,13 +62,13 @@ class Tracer : public ot::Tracer, public std::enable_shared_from_this { void Close() noexcept override; private: - template - ot::expected inject(const ot::SpanContext &sc, Writer &writer) const try { + template + ot::expected inject(const ot::SpanContext &sc, Carrier &carrier) const try { auto span_context = dynamic_cast(&sc); if (span_context == nullptr) { return ot::make_unexpected(ot::invalid_span_context_error); } - return span_context->serialize(writer); + return span_context->serialize(carrier, buffer_); } catch (const std::bad_alloc &) { return ot::make_unexpected(std::make_error_code(std::errc::not_enough_memory)); } diff --git a/test/mocks.h b/test/mocks.h index 64b9e462..461b3415 100644 --- a/test/mocks.h +++ b/test/mocks.h @@ -32,31 +32,6 @@ struct TestSpanData : public SpanData { trace_id, parent_id, error); }; -struct MockBuffer : public SpanBuffer { - MockBuffer(){}; - - void registerSpan(const SpanData& span) override { - uint64_t trace_id = span.traceId(); - auto trace = traces.find(trace_id); - if (trace == traces.end()) { - traces.emplace(std::make_pair(trace_id, PendingTrace{})); - trace = traces.find(trace_id); - } - trace->second.all_spans.insert(span.spanId()); - } - - void finishSpan(std::unique_ptr span) override { - auto trace = traces.find(span->traceId()); - if (trace == traces.end()) { - std::cerr << "Missing trace for finished span" << std::endl; - return; - } - trace->second.finished_spans->push_back(std::move(span)); - } - - std::unordered_map traces; -}; - struct MockSampler : public PrioritySampler { MockSampler() {} @@ -94,6 +69,21 @@ struct MockWriter : public Writer { mutable std::mutex mutex_; }; +struct MockBuffer : public WritingSpanBuffer { + MockBuffer() + : WritingSpanBuffer(std::make_shared(std::make_shared())){}; + + void unbufferAndWriteTrace(uint64_t trace_id, + const std::shared_ptr& sampler) override { + // Haha NOPE. + // Leave the trace inside the traces map instead of deleting it. + } + + std::unordered_map& traces() { return traces_; }; + + PendingTrace& traces(uint64_t id) { return traces_[id]; }; +}; + // Advances the relative (steady_clock) time in the given TimePoint by the given number of seconds. // Ignores the absolute/system time. void advanceSeconds(TimePoint& t, int s) { diff --git a/test/propagation_test.cpp b/test/propagation_test.cpp index d6f0d275..886d2235 100644 --- a/test/propagation_test.cpp +++ b/test/propagation_test.cpp @@ -1,7 +1,8 @@ #include "../src/propagation.h" -#include "mocks.h" - +#include #include +#include "../src/tracer.h" +#include "mocks.h" #define CATCH_CONFIG_MAIN #include @@ -21,22 +22,23 @@ dict getBaggage(SpanContext* ctx) { TEST_CASE("SpanContext") { MockTextMapCarrier carrier{}; - SpanContext context{420, - 123, - std::make_unique(SamplingPriority::SamplerKeep), - {{"ayy", "lmao"}, {"hi", "haha"}}}; + auto buffer = std::make_shared(); + buffer->traces()[123].sampling_priority = + std::make_unique(SamplingPriority::SamplerKeep); + SpanContext context{420, 123, {{"ayy", "lmao"}, {"hi", "haha"}}}; SECTION("can be serialized") { - REQUIRE(context.serialize(carrier)); + REQUIRE(context.serialize(carrier, buffer)); SECTION("can be deserialized") { auto sc = SpanContext::deserialize(carrier); auto received_context = dynamic_cast(sc->get()); REQUIRE(received_context); REQUIRE(received_context->id() == 420); - REQUIRE(received_context->trace_id() == 123); - REQUIRE(received_context->getSamplingPriority() != nullptr); - REQUIRE(*received_context->getSamplingPriority() == SamplingPriority::SamplerKeep); + REQUIRE(received_context->traceId() == 123); + auto status = received_context->getPropagationStatus(); + REQUIRE(status.first == true); + REQUIRE(*status.second == SamplingPriority::SamplerKeep); REQUIRE(getBaggage(received_context) == dict{{"ayy", "lmao"}, {"hi", "haha"}}); SECTION("even with extra keys") { @@ -45,7 +47,7 @@ TEST_CASE("SpanContext") { auto received_context = dynamic_cast(sc->get()); REQUIRE(received_context); REQUIRE(received_context->id() == 420); - REQUIRE(received_context->trace_id() == 123); + REQUIRE(received_context->traceId() == 123); REQUIRE(getBaggage(received_context) == dict{{"ayy", "lmao"}, {"hi", "haha"}}); } } @@ -54,14 +56,14 @@ TEST_CASE("SpanContext") { SECTION("serialise fails") { SECTION("when setting trace id fails") { carrier.set_fails_after = 0; - auto err = context.serialize(carrier); + auto err = context.serialize(carrier, buffer); REQUIRE(!err); REQUIRE(err.error() == std::error_code(6, ot::propagation_error_category())); } SECTION("when setting parent id fails") { carrier.set_fails_after = 1; - auto err = context.serialize(carrier); + auto err = context.serialize(carrier, buffer); REQUIRE(!err); REQUIRE(err.error() == std::error_code(6, ot::propagation_error_category())); } @@ -95,22 +97,23 @@ TEST_CASE("SpanContext") { TEST_CASE("Binary Span Context") { std::stringstream carrier{}; - SpanContext context{420, - 123, - std::make_unique(SamplingPriority::SamplerKeep), - {{"ayy", "lmao"}, {"hi", "haha"}}}; + auto buffer = std::make_shared(); + SpanContext context{420, 123, {{"ayy", "lmao"}, {"hi", "haha"}}}; + buffer->traces()[123].sampling_priority = + std::make_unique(SamplingPriority::SamplerKeep); SECTION("can be serialized") { - REQUIRE(context.serialize(carrier)); + REQUIRE(context.serialize(carrier, buffer)); SECTION("can be deserialized") { auto sc = SpanContext::deserialize(carrier); auto received_context = dynamic_cast(sc->get()); REQUIRE(received_context); REQUIRE(received_context->id() == 420); - REQUIRE(received_context->trace_id() == 123); - REQUIRE(received_context->getSamplingPriority() != nullptr); - REQUIRE(*received_context->getSamplingPriority() == SamplingPriority::SamplerKeep); + REQUIRE(received_context->traceId() == 123); + auto status = received_context->getPropagationStatus(); + REQUIRE(status.first == true); + REQUIRE(*status.second == SamplingPriority::SamplerKeep); REQUIRE(getBaggage(received_context) == dict{{"ayy", "lmao"}, {"hi", "haha"}}); } } @@ -118,7 +121,7 @@ TEST_CASE("Binary Span Context") { SECTION("serialise fails") { SECTION("when the writer is not 'good'") { carrier.clear(carrier.badbit); - auto err = context.serialize(carrier); + auto err = context.serialize(carrier, buffer); REQUIRE(!err); REQUIRE(err.error() == std::make_error_code(std::errc::io_error)); carrier.clear(carrier.goodbit); @@ -126,7 +129,7 @@ TEST_CASE("Binary Span Context") { } SECTION("deserialize fails") { - SECTION("when trace_id is missing") { + SECTION("when traceId is missing") { carrier << "{ \"parent_id\": \"420\" }"; auto err = SpanContext::deserialize(carrier); REQUIRE(!err); @@ -155,3 +158,214 @@ TEST_CASE("Binary Span Context") { } } } + +TEST_CASE("sampling behaviour") { + auto sampler = std::make_shared(); + auto writer = std::make_shared(sampler); + auto buffer = std::make_shared(writer); + TracerOptions tracer_options{"", 0, "service_name", "web"}; + std::shared_ptr tracer{new Tracer{tracer_options, buffer, getRealTime, getId, sampler}}; + ot::Tracer::InitGlobal(tracer); + + // There's two ways we can set the sampling priority. Either directly using the method, or + // through a tag. Test both. + auto setSamplingPriority = GENERATE( + values< + std::function)>>({ + [](Span* span, std::unique_ptr p) { + return span->setSamplingPriority(std::move(p)); + }, + [](Span* span, std::unique_ptr p) { + if (p != nullptr) { + span->SetTag("sampling.priority", static_cast(*p)); + } else { + span->SetTag("sampling.priority", ""); + } + return span->getSamplingPriority(); + }, + })); + + SECTION("sampling priority can be set on a root span") { + // Root: ##########x + // ^ Set here, should succeed, not overridden by automatic set + auto span = ot::Tracer::Global()->StartSpan("operation_name"); + auto p = setSamplingPriority( + static_cast(span.get()), + std::make_unique(UserSamplingPriority::UserKeep)); + REQUIRE(p); + REQUIRE(*p == SamplingPriority::UserKeep); + span->Finish(); + + auto& result = writer->traces[0][0]; + REQUIRE(result->metrics["_sampling_priority_v1"] == + static_cast(SamplingPriority::UserKeep)); + } + + SECTION("sampling priority is assigned on a root span if otherwise unset") { + // Root: ##########x + // ^ Set here automatically if priority sampling enabled, should succeed + sampler->sampling_priority = std::make_unique(SamplingPriority::SamplerKeep); + auto span = ot::Tracer::Global()->StartSpan("operation_name"); + span->Finish(); + + auto& result = writer->traces[0][0]; + REQUIRE(result->metrics["_sampling_priority_v1"] == + static_cast(SamplingPriority::SamplerKeep)); + } + + SECTION("sampling priority can not be set on a finished root span") { + // Root: ##########x + // not set-^ ^-Cannot be set here + sampler->sampling_priority = nullptr; + auto span = ot::Tracer::Global()->StartSpan("operation_name"); + span->Finish(); + auto p = setSamplingPriority( + static_cast(span.get()), + std::make_unique(UserSamplingPriority::UserKeep)); + REQUIRE(!p); + + auto& result = writer->traces[0][0]; + REQUIRE(result->metrics.find("_sampling_priority_v1") == result->metrics.end()); + } + + SECTION("sampling priority is assigned to a propagated root span (and then cannot be set)") { + // Root: ####P#####x + // ^ ^ Cannot set, since already propagated + // | Set here automatically, should succeed + sampler->sampling_priority = std::make_unique(SamplingPriority::SamplerKeep); + auto span = ot::Tracer::Global()->StartSpan("operation_name"); + + MockTextMapCarrier carrier; + auto err = tracer->Inject(span->context(), carrier); + + // setSamplingPriority should fail, since it's already set & locked, and should return the + // assigned value. + REQUIRE(*setSamplingPriority(static_cast(span.get()), nullptr) == + SamplingPriority::SamplerKeep); + // Double-checking! + REQUIRE(carrier.text_map["x-datadog-sampling-priority"] == "1"); + + span->Finish(); + + auto& result = writer->traces[0][0]; + REQUIRE(result->metrics["_sampling_priority_v1"] == + static_cast(SamplingPriority::SamplerKeep)); + } + + SECTION("sampling priority for an entire trace can be set on a child span") { + // Root: ##########x + // Child: .#######x.. + // ^ Set here, should succeed + auto span = ot::Tracer::Global()->StartSpan("operation_name"); + auto child_span = tracer->StartSpan("childA", {ot::ChildOf(&span->context())}); + + auto p = setSamplingPriority( + static_cast(child_span.get()), + std::make_unique(UserSamplingPriority::UserKeep)); + REQUIRE(p); + REQUIRE(*p == SamplingPriority::UserKeep); + + child_span->Finish(); + span->Finish(); + + auto& trace = writer->traces[0]; + REQUIRE(trace[0]->metrics["_sampling_priority_v1"] == + static_cast(SamplingPriority::UserKeep)); + REQUIRE(trace[1]->metrics["_sampling_priority_v1"] == + static_cast(SamplingPriority::UserKeep)); + } + + SECTION("sampling priority is assigned on a trace if otherwise unset") { + // Root: ##########x + // Child: .#######x.^ + // | Set here automatically + sampler->sampling_priority = std::make_unique(SamplingPriority::SamplerKeep); + auto span = ot::Tracer::Global()->StartSpan("operation_name"); + auto child_span = tracer->StartSpan("childA", {ot::ChildOf(&span->context())}); + child_span->Finish(); + span->Finish(); + + auto& trace = writer->traces[0]; + REQUIRE(trace[0]->metrics["_sampling_priority_v1"] == + static_cast(SamplingPriority::SamplerKeep)); + REQUIRE(trace[1]->metrics["_sampling_priority_v1"] == + static_cast(SamplingPriority::SamplerKeep)); + } + + SECTION("sampling priority can be set until the root trace finishes") { + // Root: ##############x + // Child: .#######x..^... + // | Set here, should succeed + auto span = ot::Tracer::Global()->StartSpan("operation_name"); + auto child_span = tracer->StartSpan("childA", {ot::ChildOf(&span->context())}); + child_span->Finish(); + + auto p = setSamplingPriority( + static_cast(span.get()), + std::make_unique(UserSamplingPriority::UserKeep)); + REQUIRE(p); + REQUIRE(*p == SamplingPriority::UserKeep); + + span->Finish(); + + auto& trace = writer->traces[0]; + REQUIRE(trace[0]->metrics["_sampling_priority_v1"] == + static_cast(SamplingPriority::UserKeep)); + REQUIRE(trace[1]->metrics["_sampling_priority_v1"] == + static_cast(SamplingPriority::UserKeep)); + } + + SECTION("sampling priority can not be set on a finished trace") { + // Root: ##########x + // Child: .#######x.. + // ^ Cannot be set here + sampler->sampling_priority = nullptr; + auto span = ot::Tracer::Global()->StartSpan("operation_name"); + auto child_span = tracer->StartSpan("childA", {ot::ChildOf(&span->context())}); + child_span->Finish(); + span->Finish(); + REQUIRE(!setSamplingPriority( + static_cast(span.get()), + std::make_unique(UserSamplingPriority::UserKeep))); + REQUIRE(!setSamplingPriority( + static_cast(child_span.get()), + std::make_unique(UserSamplingPriority::UserKeep))); + + auto& trace = writer->traces[0]; + REQUIRE(trace[0]->metrics.find("_sampling_priority_v1") == trace[0]->metrics.end()); + REQUIRE(trace[1]->metrics.find("_sampling_priority_v1") == trace[1]->metrics.end()); + } + + SECTION( + "sampling priority is assigned to a trace (and then cannot be set) when a child " + "propagates") { + // Root: ##########x + // Child: .##P#####x.. + // ^ ^ Cannot be set here, nor on Root. + // | Assigned automatically here + sampler->sampling_priority = std::make_unique(SamplingPriority::SamplerKeep); + auto span = ot::Tracer::Global()->StartSpan("operation_name"); + auto child_span = tracer->StartSpan("childA", {ot::ChildOf(&span->context())}); + + MockTextMapCarrier carrier; + auto err = tracer->Inject(child_span->context(), carrier); + + // setSamplingPriority should fail, since it's already set & locked, and should return the + // assigned value. + REQUIRE(*setSamplingPriority(static_cast(span.get()), nullptr) == + SamplingPriority::SamplerKeep); + REQUIRE(*setSamplingPriority(static_cast(child_span.get()), nullptr) == + SamplingPriority::SamplerKeep); + // Double-checking! + REQUIRE(carrier.text_map["x-datadog-sampling-priority"] == "1"); + + child_span->Finish(); + span->Finish(); + + auto& trace = writer->traces[0]; + REQUIRE(trace[0]->metrics["_sampling_priority_v1"] == + static_cast(SamplingPriority::SamplerKeep)); + REQUIRE(trace[1]->metrics["_sampling_priority_v1"] == + static_cast(SamplingPriority::SamplerKeep)); + } +} diff --git a/test/sample_test.cpp b/test/sample_test.cpp index da821b05..e92a42fe 100644 --- a/test/sample_test.cpp +++ b/test/sample_test.cpp @@ -15,7 +15,7 @@ TEST_CASE("sample") { std::tm start{0, 0, 0, 12, 2, 107}; // Starting calendar time 2007-03-12 00:00:00 TimePoint time{std::chrono::system_clock::from_time_t(timegm(&start)), std::chrono::steady_clock::time_point{}}; - auto buffer = new MockBuffer(); + auto buffer = std::make_shared(); TimeProvider get_time = [&time]() { return time; }; // Mock clock. IdProvider get_id = [&id]() { return id++; }; // Mock ID provider. TracerOptions tracer_options{"", 0, "service_name", "web"}; @@ -23,15 +23,15 @@ TEST_CASE("sample") { SECTION("keep all traces") { std::shared_ptr tracer{ - new Tracer{tracer_options, std::shared_ptr{buffer}, get_time, get_id, + new Tracer{tracer_options, buffer, get_time, get_id, std::shared_ptr{new KeepAllSampler()}}}; auto span = tracer->StartSpanWithOptions("/should_be_kept", span_options); const ot::FinishSpanOptions finish_options; span->FinishWithOptions(finish_options); - REQUIRE(buffer->traces.size() == 1); - auto &result = buffer->traces[100].finished_spans->at(0); + REQUIRE(buffer->traces().size() == 1); + auto &result = buffer->traces(100).finished_spans->at(0); REQUIRE(result->type == "web"); REQUIRE(result->service == "service_name"); REQUIRE(result->name == "/should_be_kept"); @@ -42,20 +42,20 @@ TEST_CASE("sample") { SECTION("discard all tracer") { std::shared_ptr tracer{ - new Tracer{tracer_options, std::shared_ptr{buffer}, get_time, get_id, + new Tracer{tracer_options, buffer, get_time, get_id, std::shared_ptr{new DiscardAllSampler()}}}; auto span = tracer->StartSpanWithOptions("/should_be_discarded", span_options); const ot::FinishSpanOptions finish_options; span->FinishWithOptions(finish_options); - REQUIRE(buffer->traces.size() == 0); + REQUIRE(buffer->traces().size() == 0); } SECTION("discard rate sampler") { double rate = 0.75; std::shared_ptr tracer{ - new Tracer{tracer_options, std::shared_ptr{buffer}, get_time, get_id, + new Tracer{tracer_options, buffer, get_time, get_id, std::shared_ptr{new DiscardRateSampler(rate)}}}; for (int i = 0; i < 100; i++) { @@ -64,7 +64,7 @@ TEST_CASE("sample") { span->FinishWithOptions(finish_options); } - auto size = buffer->traces.size(); + auto size = buffer->traces().size(); // allow for a tiny bit of variance. double brackets because of macro REQUIRE((size >= 24 && size <= 26)); } @@ -72,7 +72,7 @@ TEST_CASE("sample") { SECTION("discard rate sampler applied to child spans within same trace") { double rate = 0; std::shared_ptr tracer{ - new Tracer{tracer_options, std::shared_ptr{buffer}, get_time, get_id, + new Tracer{tracer_options, buffer, get_time, get_id, std::shared_ptr{new DiscardRateSampler(rate)}}}; auto ot_root_span = tracer->StartSpan("/discard_rate_sample"); uint64_t trace_id = (dynamic_cast(ot_root_span.get()))->traceId(); @@ -83,14 +83,14 @@ TEST_CASE("sample") { ot_root_span->Finish(); // One trace should have been captured. - REQUIRE(buffer->traces.size() == 1); + REQUIRE(buffer->traces().size() == 1); // Both spans should be recorded under the same trace. - REQUIRE(buffer->traces[trace_id].finished_spans->size() == 2); + REQUIRE(buffer->traces(trace_id).finished_spans->size() == 2); // The trace id should be the same. - auto &root_span = buffer->traces[trace_id].finished_spans->at(1); - auto &child_span = buffer->traces[trace_id].finished_spans->at(0); + auto &root_span = buffer->traces(trace_id).finished_spans->at(1); + auto &child_span = buffer->traces(trace_id).finished_spans->at(0); REQUIRE(root_span->traceId() == child_span->traceId()); // The span id should be different. REQUIRE(root_span->spanId() != child_span->spanId()); @@ -99,14 +99,15 @@ TEST_CASE("sample") { TEST_CASE("priority sampler unit test") { PrioritySampler sampler; + auto buffer = std::make_shared(); SECTION("doesn't discard") { - for (const SpanContext &ctx : - {SpanContext{1, 1, nullptr, {}}, SpanContext{1, 2, asSamplingPriority(-1), {}}, - SpanContext{1, 2, asSamplingPriority(0), {}}, - SpanContext{1, 2, asSamplingPriority(1), {}}, - SpanContext{1, 2, asSamplingPriority(2), {}}}) { - REQUIRE(!sampler.discard(ctx)); + for (const std::string &p : {"", R"(, "sampling_priority": -1)", R"(, "sampling_priority": 0)", + R"(, "sampling_priority": 1)", R"(, "sampling_priority": 2)"}) { + std::istringstream ctx(R"({"trace_id": "100", "parent_id": "100")" + p + "}"); + auto context = SpanContext::deserialize(ctx); + + REQUIRE(!sampler.discard(std::move(*static_cast(context.value().get())))); } } diff --git a/test/span_buffer_test.cpp b/test/span_buffer_test.cpp index 98f131a4..d2e6d2db 100644 --- a/test/span_buffer_test.cpp +++ b/test/span_buffer_test.cpp @@ -10,13 +10,17 @@ TEST_CASE("span buffer") { auto sampler = std::make_shared(); auto writer_ptr = std::make_shared(sampler); MockWriter* writer = writer_ptr.get(); - WritingSpanBuffer buffer{writer_ptr}; + auto buffer = std::make_shared(writer_ptr); + + auto context_from_span = [](const TestSpanData& span) -> SpanContext { + return SpanContext{span.span_id, span.trace_id, {}}; + }; SECTION("can write a single-span trace") { auto span = std::make_unique("type", "service", "resource", "name", 420, 420, 0, 123, 456, 0); - buffer.registerSpan(*span); - buffer.finishSpan(std::move(span)); + buffer->registerSpan(context_from_span(*span)); + buffer->finishSpan(std::move(span), sampler); REQUIRE(writer->traces.size() == 1); REQUIRE(writer->traces[0].size() == 1); auto& result = writer->traces[0][0]; @@ -36,12 +40,12 @@ TEST_CASE("span buffer") { SECTION("can write a multi-span trace") { auto rootSpan = std::make_unique("type", "service", "resource", "name", 420, 420, 0, 123, 456, 0); - buffer.registerSpan(*rootSpan); + buffer->registerSpan(context_from_span(*rootSpan)); auto childSpan = std::make_unique("type", "service", "resource", "name", 420, 421, 0, 124, 455, 0); - buffer.registerSpan(*childSpan); - buffer.finishSpan(std::move(childSpan)); - buffer.finishSpan(std::move(rootSpan)); + buffer->registerSpan(context_from_span(*childSpan)); + buffer->finishSpan(std::move(childSpan), sampler); + buffer->finishSpan(std::move(rootSpan), sampler); REQUIRE(writer->traces.size() == 1); REQUIRE(writer->traces[0].size() == 2); // Although order doesn't actually matter. @@ -52,12 +56,12 @@ TEST_CASE("span buffer") { SECTION("can write a multi-span trace, even if the root finishes before a child") { auto rootSpan = std::make_unique("type", "service", "resource", "name", 420, 420, 0, 123, 456, 0); - buffer.registerSpan(*rootSpan); + buffer->registerSpan(context_from_span(*rootSpan)); auto childSpan = std::make_unique("type", "service", "resource", "name", 420, 421, 0, 124, 455, 0); - buffer.registerSpan(*childSpan); - buffer.finishSpan(std::move(rootSpan)); - buffer.finishSpan(std::move(childSpan)); + buffer->registerSpan(context_from_span(*childSpan)); + buffer->finishSpan(std::move(rootSpan), sampler); + buffer->finishSpan(std::move(childSpan), sampler); REQUIRE(writer->traces.size() == 1); REQUIRE(writer->traces[0].size() == 2); // Although order doesn't actually matter. @@ -68,20 +72,20 @@ TEST_CASE("span buffer") { SECTION("doesn't write an unfinished trace") { auto rootSpan = std::make_unique("type", "service", "resource", "name", 420, 420, 0, 123, 456, 0); - buffer.registerSpan(*rootSpan); + buffer->registerSpan(context_from_span(*rootSpan)); auto childSpan = std::make_unique("type", "service", "resource", "name", 420, 421, 0, 124, 455, 0); - buffer.registerSpan(*childSpan); - buffer.finishSpan(std::move(childSpan)); + buffer->registerSpan(context_from_span(*childSpan)); + buffer->finishSpan(std::move(childSpan), sampler); REQUIRE(writer->traces.size() == 0); // rootSpan still outstanding auto childSpan2 = std::make_unique("type", "service", "resource", "name", 420, 422, 0, 125, 457, 0); - buffer.registerSpan(*childSpan2); - buffer.finishSpan(std::move(rootSpan)); + buffer->registerSpan(context_from_span(*childSpan2)); + buffer->finishSpan(std::move(rootSpan), sampler); // Root span finished, but *after* childSpan2 was registered, so childSpan2 still oustanding. REQUIRE(writer->traces.size() == 0); // Ok now we're done! - buffer.finishSpan(std::move(childSpan2)); + buffer->finishSpan(std::move(childSpan2), sampler); REQUIRE(writer->traces.size() == 1); REQUIRE(writer->traces[0].size() == 3); } @@ -94,17 +98,17 @@ TEST_CASE("span buffer") { SECTION("not even a trace") { auto rootSpan = std::make_unique("type", "service", "resource", "name", 420, 420, 0, 123, 456, 0); - buffer.finishSpan(std::move(rootSpan)); + buffer->finishSpan(std::move(rootSpan), sampler); REQUIRE(writer->traces.size() == 0); } SECTION("there's a trace but no startSpan call") { auto rootSpan = std::make_unique("type", "service", "resource", "name", 420, 420, 0, 123, 456, 0); - buffer.registerSpan(*rootSpan); + buffer->registerSpan(context_from_span(*rootSpan)); auto childSpan = std::make_unique("type", "service", "resource", "name", 420, 421, 0, 124, 455, 0); - buffer.finishSpan(std::move(childSpan)); - buffer.finishSpan(std::move(rootSpan)); + buffer->finishSpan(std::move(childSpan), sampler); + buffer->finishSpan(std::move(rootSpan), sampler); REQUIRE(writer->traces.size() == 1); REQUIRE(writer->traces[0].size() == 1); // Only rootSpan got written. REQUIRE(writer->traces[0][0]->span_id == 420); @@ -116,13 +120,13 @@ TEST_CASE("span buffer") { SECTION("spans written after a trace is submitted just start a new trace") { auto rootSpan = std::make_unique("type", "service", "resource", "name", 420, 420, 0, 123, 456, 0); - buffer.registerSpan(*rootSpan); - buffer.finishSpan(std::move(rootSpan)); + buffer->registerSpan(context_from_span(*rootSpan)); + buffer->finishSpan(std::move(rootSpan), sampler); REQUIRE(writer->traces.size() == 1); auto childSpan = std::make_unique("type", "service", "resource", "name", 420, 421, 0, 123, 456, 0); - buffer.registerSpan(*childSpan); - buffer.finishSpan(std::move(childSpan)); + buffer->registerSpan(context_from_span(*childSpan)); + buffer->finishSpan(std::move(childSpan), sampler); REQUIRE(writer->traces.size() == 2); } @@ -139,7 +143,7 @@ TEST_CASE("span buffer") { [&](uint64_t span_id) { auto span = std::make_unique( "type", "service", "resource", "name", trace_id, span_id, 0, 123, 456, 0); - buffer.registerSpan(*span); + buffer->registerSpan(context_from_span(*span)); }, span_id); } @@ -153,7 +157,7 @@ TEST_CASE("span buffer") { [&](uint64_t span_id) { auto span = std::make_unique( "type", "service", "resource", "name", trace_id, span_id, 0, 123, 456, 0); - buffer.finishSpan(std::move(span)); + buffer->finishSpan(std::move(span), sampler); }, span_id); } diff --git a/test/span_test.cpp b/test/span_test.cpp index 973129a7..2e12eddd 100644 --- a/test/span_test.cpp +++ b/test/span_test.cpp @@ -16,23 +16,20 @@ TEST_CASE("span") { TimePoint time{std::chrono::system_clock::from_time_t(timegm(&start)), std::chrono::steady_clock::time_point{}}; auto sampler = std::make_shared(); - auto buffer = new MockBuffer(); + auto buffer = std::make_shared(); TimeProvider get_time = [&time]() { return time; }; // Mock clock. IdProvider get_id = [&id]() { return id++; }; // Mock ID provider. const ot::FinishSpanOptions finish_options; SECTION("receives id") { auto span_id = get_id(); - Span span{nullptr, std::shared_ptr{buffer}, - get_time, sampler, - span_id, span_id, - 0, std::move(SpanContext{span_id, span_id, nullptr, {}}), - get_time(), "", - "", "", + Span span{nullptr, buffer, get_time, sampler, + span_id, span_id, 0, std::move(SpanContext{span_id, span_id, {}}), + get_time(), "", "", "", "", ""}; span.FinishWithOptions(finish_options); - auto& result = buffer->traces[100].finished_spans->at(0); + auto& result = buffer->traces(100).finished_spans->at(0); REQUIRE(result->span_id == 100); REQUIRE(result->trace_id == 100); REQUIRE(result->parent_id == 0); @@ -40,32 +37,26 @@ TEST_CASE("span") { SECTION("registers with SpanBuffer") { auto span_id = get_id(); - Span span{nullptr, std::shared_ptr{buffer}, - get_time, sampler, - span_id, span_id, - 0, std::move(SpanContext{span_id, span_id, nullptr, {}}), - get_time(), "", - "", "", + Span span{nullptr, buffer, get_time, sampler, + span_id, span_id, 0, std::move(SpanContext{span_id, span_id, {}}), + get_time(), "", "", "", "", ""}; - REQUIRE(buffer->traces.size() == 1); - REQUIRE(buffer->traces.find(100) != buffer->traces.end()); - REQUIRE(buffer->traces[100].finished_spans->size() == 0); - REQUIRE(buffer->traces[100].all_spans.size() == 1); + REQUIRE(buffer->traces().size() == 1); + REQUIRE(buffer->traces().find(100) != buffer->traces().end()); + REQUIRE(buffer->traces(100).finished_spans->size() == 0); + REQUIRE(buffer->traces(100).all_spans.size() == 1); } SECTION("timed correctly") { auto span_id = get_id(); - Span span{nullptr, std::shared_ptr{buffer}, - get_time, sampler, - span_id, span_id, - 0, std::move(SpanContext{span_id, span_id, nullptr, {}}), - get_time(), "", - "", "", + Span span{nullptr, buffer, get_time, sampler, + span_id, span_id, 0, std::move(SpanContext{span_id, span_id, {}}), + get_time(), "", "", "", "", ""}; advanceSeconds(time, 10); span.FinishWithOptions(finish_options); - auto& result = buffer->traces[100].finished_spans->at(0); + auto& result = buffer->traces(100).finished_spans->at(0); REQUIRE(result->duration == 10000000000); } @@ -98,47 +89,40 @@ TEST_CASE("span") { std::shared_ptr buffer_ptr{buffer}; for (auto& test_case : test_cases) { auto span_id = get_id(); - Span span{ - nullptr, buffer_ptr, get_time, sampler, - span_id, span_id, 0, std::move(SpanContext{span_id, span_id, nullptr, {}}), - get_time(), "", "", "", - "", ""}; + Span span{nullptr, buffer_ptr, get_time, sampler, + span_id, span_id, 0, std::move(SpanContext{span_id, span_id, {}}), + get_time(), "", "", "", + "", ""}; span.SetTag("http.url", test_case.first); const ot::FinishSpanOptions finish_options; span.FinishWithOptions(finish_options); - auto& result = buffer->traces[span_id].finished_spans->back(); + auto& result = buffer->traces(span_id).finished_spans->back(); REQUIRE(result->meta.find("http.url")->second == test_case.second); } } SECTION("finishes once") { auto span_id = get_id(); - Span span{nullptr, std::shared_ptr{buffer}, - get_time, sampler, - span_id, span_id, - 0, std::move(SpanContext{span_id, span_id, nullptr, {}}), - get_time(), "", - "", "", + Span span{nullptr, buffer, get_time, sampler, + span_id, span_id, 0, std::move(SpanContext{span_id, span_id, {}}), + get_time(), "", "", "", "", ""}; std::vector threads; for (int i = 0; i < 10; i++) { threads.emplace_back([&]() { span.FinishWithOptions(finish_options); }); } std::for_each(threads.begin(), threads.end(), std::mem_fn(&std::thread::join)); - REQUIRE(buffer->traces.size() == 1); - REQUIRE(buffer->traces.find(100) != buffer->traces.end()); - REQUIRE(buffer->traces[100].finished_spans->size() == 1); + REQUIRE(buffer->traces().size() == 1); + REQUIRE(buffer->traces().find(100) != buffer->traces().end()); + REQUIRE(buffer->traces(100).finished_spans->size() == 1); } SECTION("handles tags") { auto span_id = get_id(); - Span span{nullptr, std::shared_ptr{buffer}, - get_time, sampler, - span_id, span_id, - 0, std::move(SpanContext{span_id, span_id, nullptr, {}}), - get_time(), "", - "", "", + Span span{nullptr, buffer, get_time, sampler, + span_id, span_id, 0, std::move(SpanContext{span_id, span_id, {}}), + get_time(), "", "", "", "", ""}; span.SetTag("bool", true); @@ -156,7 +140,7 @@ TEST_CASE("span") { span.FinishWithOptions(finish_options); - auto& result = buffer->traces[100].finished_spans->at(0); + auto& result = buffer->traces(100).finished_spans->at(0); // Check "map" seperately, because JSON key order is non-deterministic therefore we can't do // simple string matching. REQUIRE(json::parse(result->meta["map"]) == @@ -178,13 +162,13 @@ TEST_CASE("span") { SECTION("maps datadog tags to span data") { auto span_id = get_id(); Span span{nullptr, - std::shared_ptr{buffer}, + buffer, get_time, sampler, span_id, span_id, 0, - std::move(SpanContext{span_id, span_id, nullptr, {}}), + std::move(SpanContext{span_id, span_id, {}}), get_time(), "original service", "original type", @@ -198,7 +182,7 @@ TEST_CASE("span") { span.FinishWithOptions(finish_options); - auto& result = buffer->traces[100].finished_spans->at(0); + auto& result = buffer->traces(100).finished_spans->at(0); // Datadog special tags aren't kept, they just set the Span values. REQUIRE(result->meta == std::unordered_map{ {"tag with no special meaning", "ayy lmao"}}); @@ -211,13 +195,13 @@ TEST_CASE("span") { SECTION("operation name can be overridden") { auto span_id = get_id(); Span span{nullptr, - std::shared_ptr{buffer}, + buffer, get_time, sampler, span_id, span_id, 0, - std::move(SpanContext{span_id, span_id, nullptr, {}}), + std::move(SpanContext{span_id, span_id, {}}), get_time(), "original service", "original type", @@ -227,7 +211,7 @@ TEST_CASE("span") { span.FinishWithOptions(finish_options); - auto& result = buffer->traces[100].finished_spans->at(0); + auto& result = buffer->traces(100).finished_spans->at(0); REQUIRE(result->meta == std::unordered_map{{"operation", "original span name"}}); REQUIRE(result->name == "overridden operation name"); @@ -239,13 +223,13 @@ TEST_CASE("span") { SECTION("special resource tag has priority over operation name override") { auto span_id = get_id(); Span span{nullptr, - std::shared_ptr{buffer}, + buffer, get_time, sampler, span_id, span_id, 0, - std::move(SpanContext{span_id, span_id, nullptr, {}}), + std::move(SpanContext{span_id, span_id, {}}), get_time(), "original service", "original type", @@ -256,7 +240,7 @@ TEST_CASE("span") { span.SetTag("resource.name", "new resource"); span.FinishWithOptions(finish_options); - auto& result = buffer->traces[100].finished_spans->at(0); + auto& result = buffer->traces(100).finished_spans->at(0); REQUIRE(result->meta == std::unordered_map{{"operation", "original span name"}}); REQUIRE(result->name == "overridden operation name"); @@ -268,13 +252,13 @@ TEST_CASE("span") { SECTION("OpenTracing operation name works") { auto span_id = get_id(); Span span{nullptr, - std::shared_ptr{buffer}, + buffer, get_time, sampler, span_id, span_id, 0, - std::move(SpanContext{span_id, span_id, nullptr, {}}), + std::move(SpanContext{span_id, span_id, {}}), get_time(), "original service", "original type", @@ -287,7 +271,7 @@ TEST_CASE("span") { const ot::FinishSpanOptions finish_options; span.FinishWithOptions(finish_options); - auto& result = buffer->traces[100].finished_spans->at(0); + auto& result = buffer->traces(100).finished_spans->at(0); REQUIRE(result->name == "operation name"); REQUIRE(result->resource == "operation name"); } @@ -297,7 +281,7 @@ TEST_CASE("span") { const ot::FinishSpanOptions finish_options; span.FinishWithOptions(finish_options); - auto& result = buffer->traces[100].finished_spans->at(0); + auto& result = buffer->traces(100).finished_spans->at(0); REQUIRE(result->name == "operation name"); REQUIRE(result->resource == "resource tag override"); } @@ -309,60 +293,72 @@ TEST_CASE("span") { std::make_unique(SamplingPriority::SamplerKeep); SECTION("root spans may be sampled") { - Span span{nullptr, std::shared_ptr{buffer}, + Span span{nullptr, buffer, get_time, priority_sampler, + 100, 100, 0, std::move(SpanContext{100, 100, {}}), + get_time(), "", "", "", + "", ""}; + span.FinishWithOptions(finish_options); + + auto& result = buffer->traces(100).finished_spans->at(0); + REQUIRE(result->metrics == + std::unordered_map{{"_sampling_priority_v1", 1}}); + } + + SECTION("non-root spans may be sampled, as long as the trace is not yet distributed") { + Span span{nullptr, buffer, get_time, priority_sampler, - 100, 100, - 0, std::move(SpanContext{100, 100, nullptr, {}}), + 100, 42, + 42, std::move(SpanContext{100, 42, {}}), // Non-distributed SpanContext get_time(), "", "", "", "", ""}; span.FinishWithOptions(finish_options); - auto& result = buffer->traces[100].finished_spans->at(0); + auto& result = buffer->traces(42).finished_spans->at(0); REQUIRE(result->metrics == std::unordered_map{{"_sampling_priority_v1", 1}}); } - SECTION("non-root spans may not be sampled") { - Span span{nullptr, - std::shared_ptr{buffer}, - get_time, - priority_sampler, - 100, - 100, - 42 /* Totally not a root span! */, - std::move(SpanContext{100, 100, nullptr, {}}), - get_time(), - "", - "", - "", - "", - ""}; + SECTION("non-root spans may not be sampled if they are distributed") { + // parent_id is decoded to span_id, and the tracer will create a child context with the + // span_id set to the span it's for. In this case we're deserializing (so as to simulate + // propagation) but directly passing to the Span; so we encode parent_id as the id of the + // span we're passing to. + std::istringstream ctx(R"({ + "trace_id": "42", + "parent_id": "100" + })"); + auto context = SpanContext::deserialize(ctx); + Span span{nullptr, buffer, + get_time, priority_sampler, + 100, 42, + 42, std::move(*static_cast(context.value().get())), + get_time(), "", + "", "", + "", ""}; span.FinishWithOptions(finish_options); - auto& result = buffer->traces[100].finished_spans->at(0); + auto& result = buffer->traces(42).finished_spans->at(0); REQUIRE(result->metrics == std::unordered_map{}); } SECTION("spans with an existing sampling priority may not be given a new one at Finish") { - Span span{nullptr, - std::shared_ptr{buffer}, - get_time, - priority_sampler, - 100, - 100, - 0, - std::move(SpanContext{ - 100, 100, std::make_unique(SamplingPriority::UserDrop), {}}), - get_time(), - "", - "", - "", - "", - ""}; + std::istringstream ctx(R"({ + "trace_id": "100", + "parent_id": "100", + "sampling_priority": -1 + })"); + auto context = SpanContext::deserialize(ctx); + Span span{nullptr, buffer, + get_time, priority_sampler, + 100, 100, + 0, std::move(*static_cast(context.value().get())), + get_time(), "", + "", "", + "", ""}; span.FinishWithOptions(finish_options); - auto& result = buffer->traces[100].finished_spans->at(0); + auto& result = buffer->traces(100).finished_spans->at(0); REQUIRE(result->metrics == std::unordered_map{{"_sampling_priority_v1", -1}}); } diff --git a/test/tracer_test.cpp b/test/tracer_test.cpp index 764de7a2..90906bf4 100644 --- a/test/tracer_test.cpp +++ b/test/tracer_test.cpp @@ -13,13 +13,12 @@ TEST_CASE("tracer") { std::tm start{0, 0, 0, 12, 2, 107}; // Starting calendar time 2007-03-12 00:00:00 TimePoint time{std::chrono::system_clock::from_time_t(timegm(&start)), std::chrono::steady_clock::time_point{}}; - auto buffer = new MockBuffer(); + auto buffer = std::make_shared(); TimeProvider get_time = [&time]() { return time; }; // Mock clock. IdProvider get_id = [&id]() { return id++; }; // Mock ID provider. auto sampler = std::make_shared(); TracerOptions tracer_options{"", 0, "service_name", "web"}; - std::shared_ptr tracer{ - new Tracer{tracer_options, std::shared_ptr{buffer}, get_time, get_id, sampler}}; + std::shared_ptr tracer{new Tracer{tracer_options, buffer, get_time, get_id, sampler}}; const ot::StartSpanOptions span_options; SECTION("names spans correctly") { @@ -27,7 +26,7 @@ TEST_CASE("tracer") { const ot::FinishSpanOptions finish_options; span->FinishWithOptions(finish_options); - auto& result = buffer->traces[100].finished_spans->at(0); + auto& result = buffer->traces(100).finished_spans->at(0); REQUIRE(result->type == "web"); REQUIRE(result->service == "service_name"); REQUIRE(result->name == "/what_up"); @@ -39,7 +38,7 @@ TEST_CASE("tracer") { const ot::FinishSpanOptions finish_options; span->FinishWithOptions(finish_options); - auto& result = buffer->traces[100].finished_spans->at(0); + auto& result = buffer->traces(100).finished_spans->at(0); REQUIRE(result->span_id == 100); REQUIRE(result->trace_id == 100); REQUIRE(result->parent_id == 0); @@ -47,7 +46,7 @@ TEST_CASE("tracer") { SECTION("span context is propagated") { MockTextMapCarrier carrier; - SpanContext context{420, 69, nullptr, {{"ayy", "lmao"}, {"hi", "haha"}}}; + SpanContext context{420, 69, {{"ayy", "lmao"}, {"hi", "haha"}}}; auto success = tracer->Inject(context, carrier); REQUIRE(success); auto span_context_maybe = tracer->Extract(carrier); @@ -55,7 +54,7 @@ TEST_CASE("tracer") { auto span = tracer->StartSpan("fred", {ChildOf(span_context_maybe->get())}); const ot::FinishSpanOptions finish_options; span->FinishWithOptions(finish_options); - auto& result = buffer->traces[69].finished_spans->at(0); + auto& result = buffer->traces(69).finished_spans->at(0); REQUIRE(result->span_id == 100); REQUIRE(result->trace_id == 69); REQUIRE(result->parent_id == 420); @@ -67,7 +66,7 @@ TEST_CASE("tracer") { auto span = tracer->StartSpan("fred", {ChildOf(span_context_maybe->get())}); const ot::FinishSpanOptions finish_options; span->FinishWithOptions(finish_options); - auto& result = buffer->traces[100].finished_spans->at(0); + auto& result = buffer->traces(100).finished_spans->at(0); REQUIRE(result->span_id == 100); REQUIRE(result->trace_id == 100); REQUIRE(result->parent_id == 0);