aboutsummaryrefslogblamecommitdiffstats
path: root/util/generic/yexception_ut.cpp
blob: 80754d313e175b3430c71a6c4e0f937d5dc46710 (plain) (tree)
1
2
3
4
5
6
7
8
9
10

                                     
                                                            

                                     
                                                                       
 
                                                  
 
                                   
                                 
                               
                              
                              
 
                          
                         
 
                                                        
      


                                
                                     
 

                           

     


                                                  
                                                                




                                     
                                       
                                                 

                                                        
                                                                      
                                
                                
                              
                                               
                                                 




                                     
                                            




                                     
                                                                         

         


                                                                     


                                                              
                                                                               





                           



























                                                               
                                                 









                                                             













                                                                                                


                                                                               










                                                                          
                                                                                        







                                                                                          
                                                                   





                                                                                          


















































































                                                        
                                                          



                                                   
                                                                           
                                                


                                                                               



                               
                                                                                         








                                           
                               
                       
                                                                                      
                  
         
     
 








                                                                                             
         
     











                                                                                  




                                                          
     











                                                               
                                                                         
         





















                                                                









                                                                                                                         
                                                                        














                                                                                            
                                                                                                        







                                                                                          
                                                                                                                                     





                                                                 
                                   











                                                                                     
                                                                        





                                                                                     

                                             
#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);