#include "yexception.h" static inline void Throw1DontMove() { ythrow yexception() << "blabla"; // don't move this line } static inline void Throw2DontMove() { ythrow yexception() << 1 << " qw " << 12.1; // don't move this line } #include <library/cpp/testing/unittest/registar.h> #include <util/generic/algorithm.h> #include <util/memory/tempbuf.h> #include <util/random/mersenne.h> #include <util/stream/output.h> #include <util/string/subst.h> #include <util/string/split.h> #include "yexception_ut.h" #include "bt_exception.h" #if defined(_MSC_VER) #pragma warning(disable : 4702) /*unreachable code*/ #endif static void CallbackFun(int i) { throw i; } static IOutputStream* OUTS = nullptr; namespace NOuter::NInner { void Compare10And20() { Y_ENSURE(10 > 20); } } class TExceptionTest: public TTestBase { UNIT_TEST_SUITE(TExceptionTest); UNIT_TEST_EXCEPTION(TestException, yexception) UNIT_TEST_EXCEPTION(TestLineInfo, yexception) UNIT_TEST(TestCurrentExceptionMessageWhenThereisNoException) UNIT_TEST(TestFormat1) UNIT_TEST(TestRaise1) UNIT_TEST(TestVirtuality) UNIT_TEST(TestVirtualInheritance) UNIT_TEST(TestMixedCode) UNIT_TEST(TestBackTrace) UNIT_TEST(TestEnsureWithBackTrace1) UNIT_TEST(TestEnsureWithBackTrace2) #ifdef _YNDX_LIBUNWIND_ENABLE_EXCEPTION_BACKTRACE UNIT_TEST(TestFormatCurrentException) #endif UNIT_TEST(TestFormatCurrentExceptionWithNoException) #ifdef _YNDX_LIBUNWIND_ENABLE_EXCEPTION_BACKTRACE UNIT_TEST(TestFormatCurrentExceptionWithInvalidBacktraceFormatter) #endif UNIT_TEST(TestRethrowAppend) UNIT_TEST(TestMacroOverload) UNIT_TEST(TestMessageCrop) UNIT_TEST(TestTIoSystemErrorSpecialMethods) UNIT_TEST(TestCurrentExceptionTypeNameMethod) UNIT_TEST_SUITE_END(); private: inline void TestRethrowAppend() { try { try { ythrow yexception() << "it"; } catch (yexception& e) { e << "happens"; throw; } } catch (...) { UNIT_ASSERT(CurrentExceptionMessage().Contains("ithappens")); } } inline void TestCurrentExceptionMessageWhenThereisNoException() { UNIT_ASSERT(CurrentExceptionMessage() == "(NO EXCEPTION)"); } inline void TestBackTrace() { try { ythrow TWithBackTrace<TIoSystemError>() << "test"; } catch (...) { UNIT_ASSERT(CurrentExceptionMessage().find('\n') != TString::npos); return; } UNIT_ASSERT(false); } template <typename TException> static void EnsureCurrentExceptionHasBackTrace() { auto exceptionPtr = std::current_exception(); UNIT_ASSERT_C(exceptionPtr != nullptr, "No exception"); try { std::rethrow_exception(exceptionPtr); } catch (const TException& e) { const TBackTrace* bt = e.BackTrace(); UNIT_ASSERT(bt != nullptr); } catch (...) { UNIT_ASSERT_C(false, "Unexpected exception type"); } }; inline void TestEnsureWithBackTrace1() { try { Y_ENSURE_BT(4 > 6); } catch (...) { const TString msg = CurrentExceptionMessage(); UNIT_ASSERT(msg.Contains("4 > 6")); UNIT_ASSERT(msg.Contains("\n")); EnsureCurrentExceptionHasBackTrace<yexception>(); return; } UNIT_ASSERT(false); } inline void TestEnsureWithBackTrace2() { try { Y_ENSURE_BT(4 > 6, "custom " << "message"); } catch (...) { const TString msg = CurrentExceptionMessage(); UNIT_ASSERT(!msg.Contains("4 > 6")); UNIT_ASSERT(msg.Contains("custom message")); UNIT_ASSERT(msg.Contains("\n")); EnsureCurrentExceptionHasBackTrace<yexception>(); return; } UNIT_ASSERT(false); } // TODO(svkrasnov): the output should be canonized after https://st.yandex-team.ru/YMAKE-103 #ifdef _YNDX_LIBUNWIND_ENABLE_EXCEPTION_BACKTRACE void TestFormatCurrentException() { try { throw std::logic_error("some exception"); // is instance of std::exception UNIT_ASSERT(false); } catch (...) { TString exceptionMessage = FormatCurrentException(); UNIT_ASSERT(exceptionMessage.Contains("(std::logic_error) some exception")); TVector<TString> backtraceStrs = StringSplitter(exceptionMessage).Split('\n'); UNIT_ASSERT(backtraceStrs.size() > 1); } } #endif void TestFormatCurrentExceptionWithNoException() { UNIT_ASSERT_VALUES_EQUAL(FormatCurrentException(), "(NO EXCEPTION)\n"); } #ifdef _YNDX_LIBUNWIND_ENABLE_EXCEPTION_BACKTRACE void TestFormatCurrentExceptionWithInvalidBacktraceFormatter() { auto invalidFormatter = [](IOutputStream*, void* const*, size_t) { Throw2DontMove(); }; SetFormatBackTraceFn(invalidFormatter); try { Throw1DontMove(); UNIT_ASSERT(false); } catch (...) { TString expected = "Caught:\n" "(yexception) util/generic/yexception_ut.cpp:4: blabla\n" "Failed to print backtrace: " "(yexception) util/generic/yexception_ut.cpp:8: 1 qw 12.1"; UNIT_ASSERT_EQUAL(FormatCurrentException(), expected); } try { throw std::logic_error("std exception"); UNIT_ASSERT(false); } catch (...) { TString expected = "Caught:\n" "(std::logic_error) std exception\n" "Failed to print backtrace: " "(yexception) util/generic/yexception_ut.cpp:8: 1 qw 12.1"; UNIT_ASSERT_EQUAL(FormatCurrentException(), expected); } } #endif inline void TestVirtualInheritance() { TStringStream ss; OUTS = &ss; class TA { public: inline TA() { *OUTS << "A"; } }; class TB { public: inline TB() { *OUTS << "B"; } }; class TC: public virtual TB, public virtual TA { public: inline TC() { *OUTS << "C"; } }; class TD: public virtual TA { public: inline TD() { *OUTS << "D"; } }; class TE: public TC, public TD { public: inline TE() { *OUTS << "E"; } }; TE e; UNIT_ASSERT_EQUAL(ss.Str(), "BACDE"); } inline void TestVirtuality() { try { ythrow TFileError() << "1"; UNIT_ASSERT(false); } catch (const TIoException&) { } catch (...) { UNIT_ASSERT(false); } try { ythrow TFileError() << 1; UNIT_ASSERT(false); } catch (const TSystemError&) { } catch (...) { UNIT_ASSERT(false); } try { ythrow TFileError() << '1'; UNIT_ASSERT(false); } catch (const yexception&) { } catch (...) { UNIT_ASSERT(false); } try { ythrow TFileError() << 1.0; UNIT_ASSERT(false); } catch (const TFileError&) { } catch (...) { UNIT_ASSERT(false); } } inline void TestFormat1() { try { throw yexception() << 1 << " qw " << 12.1; UNIT_ASSERT(false); } catch (...) { const TString err = CurrentExceptionMessage(); UNIT_ASSERT(err.Contains("1 qw 12.1")); } } static inline void CheckCurrentExceptionContains(const char* message) { TString err = CurrentExceptionMessage(); SubstGlobal(err, '\\', '/'); // remove backslashes from path in message UNIT_ASSERT(err.Contains(message)); } inline void TestRaise1() { try { Throw2DontMove(); UNIT_ASSERT(false); } catch (...) { CheckCurrentExceptionContains("util/generic/yexception_ut.cpp:8: 1 qw 12.1"); } } inline void TestException() { ythrow yexception() << "blablabla"; } inline void TestLineInfo() { try { Throw1DontMove(); UNIT_ASSERT(false); } catch (...) { CheckCurrentExceptionContains("util/generic/yexception_ut.cpp:4: blabla"); throw; } } //! tests propagation of an exception through C code //! @note on some platforms, for example GCC on 32-bit Linux without -fexceptions option, //! throwing an exception from a C++ callback through C code aborts program inline void TestMixedCode() { const int N = 26082009; try { TestCallback(&CallbackFun, N); UNIT_ASSERT(false); } catch (int i) { UNIT_ASSERT_VALUES_EQUAL(i, N); } } void TestMacroOverload() { try { Y_ENSURE(10 > 20); } catch (const yexception& e) { UNIT_ASSERT(e.AsStrBuf().Contains("10 > 20")); } try { Y_ENSURE(10 > 20, "exception message to search for"); } catch (const yexception& e) { UNIT_ASSERT(e.AsStrBuf().Contains("exception message to search for")); } try { NOuter::NInner::Compare10And20(); } catch (const yexception& e) { UNIT_ASSERT(e.AsStrBuf().Contains("10 > 20")); } } void TestMessageCrop() { TTempBuf tmp; size_t size = tmp.Size() * 1.5; TString s; s.reserve(size); TMersenne<ui64> generator(42); for (int j = 0; j < 50; ++j) { for (size_t i = 0; i < size; ++i) { s += static_cast<char>('a' + generator() % 26); } yexception e; e << s; UNIT_ASSERT_EQUAL(e.AsStrBuf(), s.substr(0, tmp.Size() - 1)); } } void TestTIoSystemErrorSpecialMethods() { TString testStr{"systemError"}; TIoSystemError err; err << testStr; UNIT_ASSERT(err.AsStrBuf().Contains(testStr)); TIoSystemError errCopy{err}; UNIT_ASSERT(err.AsStrBuf().Contains(testStr)); UNIT_ASSERT(errCopy.AsStrBuf().Contains(testStr)); TIoSystemError errAssign; errAssign = err; UNIT_ASSERT(err.AsStrBuf().Contains(testStr)); UNIT_ASSERT(errAssign.AsStrBuf().Contains(testStr)); TIoSystemError errMove{std::move(errCopy)}; UNIT_ASSERT(errMove.AsStrBuf().Contains(testStr)); TIoSystemError errMoveAssign; errMoveAssign = std::move(errMove); UNIT_ASSERT(errMoveAssign.AsStrBuf().Contains(testStr)); } inline void TestCurrentExceptionTypeNameMethod() { //Basic test of getting the correct exception type name. try { throw std::runtime_error("Test Runtime Error Exception"); } catch (...) { UNIT_ASSERT_STRING_CONTAINS(CurrentExceptionTypeName(), "std::runtime_error"); } //Test when exception has an unusual type. Under Linux it should return "int" and under other OSs "unknown type". try { throw int(1); } catch (...) { #if defined(LIBCXX_BUILDING_LIBCXXRT) || defined(LIBCXX_BUILDING_LIBGCC) UNIT_ASSERT_VALUES_EQUAL(CurrentExceptionTypeName(), "int"); #else UNIT_ASSERT_VALUES_EQUAL(CurrentExceptionTypeName(), "unknown type"); #endif } //Test when the caught exception is rethrown with std::rethrow_exception. try { throw std::logic_error("Test Logic Error Exception"); } catch (...) { try { std::rethrow_exception(std::current_exception()); } catch (...) { UNIT_ASSERT_STRING_CONTAINS(CurrentExceptionTypeName(), "std::logic_error"); } } //Test when the caught exception is rethrown with throw; . //This test is different from the previous one because of the interaction with cxxabi specifics. try { throw std::bad_alloc(); } catch (...) { try { throw; } catch (...) { UNIT_ASSERT_STRING_CONTAINS(CurrentExceptionTypeName(), "std::bad_alloc"); } } // For exceptions thrown by std::rethrow_exception() a nullptr will be returned by libcxxrt's __cxa_current_exception_type(). // Adding an explicit test for the case. try { throw int(1); } catch (...) { try { std::rethrow_exception(std::current_exception()); } catch (...) { #if defined(LIBCXX_BUILDING_LIBGCC) UNIT_ASSERT_VALUES_EQUAL(CurrentExceptionTypeName(), "int"); #else UNIT_ASSERT_VALUES_EQUAL(CurrentExceptionTypeName(), "unknown type"); #endif } } //Test when int is rethrown with throw; . try { throw int(1); } catch (...) { try { throw; } catch (...) { #if defined(LIBCXX_BUILDING_LIBCXXRT) || defined(LIBCXX_BUILDING_LIBGCC) UNIT_ASSERT_VALUES_EQUAL(CurrentExceptionTypeName(), "int"); #else UNIT_ASSERT_VALUES_EQUAL(CurrentExceptionTypeName(), "unknown type"); #endif } } } }; UNIT_TEST_SUITE_REGISTRATION(TExceptionTest);