#pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern bool CheckExceptionMessage(const char*, TString&); namespace NUnitTest { class TTestBase; namespace NPrivate { void RaiseError(const char* what, const TString& msg, bool fatalFailure); void SetUnittestThread(bool); void SetCurrentTest(TTestBase*); TTestBase* GetCurrentTest(); } extern bool ShouldColorizeDiff; TString ColoredDiff(TStringBuf s1, TStringBuf s2, const TString& delims = TString(), bool reverse = false); TString GetFormatTag(const char* name); TString GetResetTag(); // Raise error handler // Used for testing library/cpp/testing/unittest macroses // and unittest helpers. // For all other unittests standard handler is used using TRaiseErrorHandler = std::function; void SetRaiseErrorHandler(TRaiseErrorHandler handler); inline void ClearRaiseErrorHandler() { SetRaiseErrorHandler(TRaiseErrorHandler()); } class TAssertException: public yexception { }; class ITestSuiteProcessor; struct TTestContext { TTestContext() : Processor(nullptr) { } explicit TTestContext(ITestSuiteProcessor* processor) : Processor(processor) { } using TMetrics = THashMap; TMetrics Metrics; ITestSuiteProcessor* Processor; }; class ITestSuiteProcessor { public: struct TUnit { const TString name; }; struct TTest { const TUnit* unit; const char* name; }; struct TError { const TTest* test; const char* msg; TString BackTrace; TTestContext* Context; }; struct TFinish { const TTest* test; TTestContext* Context; bool Success; }; ITestSuiteProcessor(); virtual ~ITestSuiteProcessor(); void Start(); void End(); void UnitStart(const TUnit& unit); void UnitStop(const TUnit& unit); void Error(const TError& descr); void BeforeTest(const TTest& test); void Finish(const TFinish& descr); unsigned GoodTests() const noexcept; unsigned FailTests() const noexcept; unsigned GoodTestsInCurrentUnit() const noexcept; unsigned FailTestsInCurrentUnit() const noexcept; // Should execute test suite? virtual bool CheckAccess(TString /*name*/, size_t /*num*/); // Should execute a test whitin suite? virtual bool CheckAccessTest(TString /*suite*/, const char* /*name*/); virtual void Run(std::function f, const TString& /*suite*/, const char* /*name*/, bool /*forceFork*/); // This process is forked for current test virtual bool GetIsForked() const; // --fork-tests is set (warning: this may be false, but never the less test will be forked if called inside UNIT_FORKED_TEST) virtual bool GetForkTests() const; virtual void SetForkTestsParams(bool forkTests, bool isForked); private: virtual void OnStart(); virtual void OnEnd(); virtual void OnUnitStart(const TUnit* /*unit*/); virtual void OnUnitStop(const TUnit* /*unit*/); virtual void OnError(const TError* /*error*/); virtual void OnFinish(const TFinish* /*finish*/); virtual void OnBeforeTest(const TTest* /*test*/); void AddTestError(const TTest& test); void AddTestFinish(const TTest& test); private: TMap TestErrors_; TMap CurTestErrors_; bool IsForked_ = false; bool ForkTests_ = false; }; class TTestBase; class TTestFactory; class ITestBaseFactory: public TIntrusiveListItem { public: ITestBaseFactory(); virtual ~ITestBaseFactory(); // name of test suite virtual TString Name() const noexcept = 0; virtual TTestBase* ConstructTest() = 0; private: void Register() noexcept; }; class TTestBase { friend class TTestFactory; TRusage rusage; public: TTestBase() noexcept; virtual ~TTestBase(); virtual TString TypeId() const; virtual TString Name() const noexcept = 0; virtual void Execute() = 0; virtual void SetUp(); virtual void TearDown(); virtual void GlobalSuiteSetUp() {} virtual void GlobalSuiteTearDown() {} void AddError(const char* msg, const TString& backtrace = TString(), TTestContext* context = nullptr); void AddError(const char* msg, TTestContext* context); void RunAfterTest(std::function f); // function like atexit to run after current unit test protected: bool CheckAccessTest(const char* test); void BeforeTest(const char* func); void Finish(const char* func, TTestContext* context); void AtStart(); void AtEnd(); void Run(std::function f, const TString& suite, const char* name, bool forceFork); class TCleanUp { public: explicit TCleanUp(TTestBase* base); ~TCleanUp(); private: TTestBase* Base_; }; void BeforeTest(); void AfterTest(); bool GetIsForked() const; bool GetForkTests() const; ITestSuiteProcessor* Processor() const noexcept; private: TTestFactory* Parent_; size_t TestErrors_; const char* CurrentSubtest_; TAdaptiveLock AfterTestFunctionsLock_; TVector> AfterTestFunctions_; }; #define UNIT_TEST_SUITE(N) \ typedef N TThisUnitTestSuite; \ \ public: \ static TString StaticName() noexcept { \ return TString(#N); \ } \ \ private: \ virtual TString Name() const noexcept override { \ return this->StaticName(); \ } \ \ virtual void Execute() override { \ this->AtStart(); #define UNIT_TEST_SUITE_DEMANGLE(N) \ typedef N TThisUnitTestSuite; \ \ public: \ static TString StaticName() noexcept { \ return TypeName(); \ } \ \ private: \ virtual TString Name() const noexcept override { \ return this->StaticName(); \ } \ \ virtual void Execute() override { \ this->AtStart(); #ifndef UT_SKIP_EXCEPTIONS #define CATCH_REACTION(FN, e, context) this->AddError(("(" + TypeName(e) + ") " + e.what()).data(), context) #define CATCH_REACTION_BT(FN, e, context) this->AddError(("(" + TypeName(e) + ") " + e.what()).data(), (e.BackTrace() ? e.BackTrace()->PrintToString() : TString()), context) #else #define CATCH_REACTION(FN, e, context) throw #define CATCH_REACTION_BT(FN, e, context) throw #endif #define UNIT_TEST_CHECK_TEST_IS_DECLARED_ONLY_ONCE(F) \ /* If you see this message - delete multiple UNIT_TEST(TestName) with same TestName. */ \ /* It's forbidden to declare same test twice because it breaks --fork-tests logic. */ \ int You_have_declared_test_##F##_multiple_times_This_is_forbidden; \ Y_UNUSED(You_have_declared_test_##F##_multiple_times_This_is_forbidden); #define UNIT_TEST_RUN(F, FF, context) \ this->BeforeTest((#F)); \ { \ struct T##F##Caller { \ static void X(TThisUnitTestSuite* thiz, NUnitTest::TTestContext&) { \ TCleanUp cleaner(thiz); \ thiz->F(); \ } \ }; \ this->TTestBase::Run(std::bind(&T##F##Caller::X, this, context), StaticName(), (#F), FF); \ } #define UNIT_TEST_IMPL(F, FF) \ UNIT_TEST_CHECK_TEST_IS_DECLARED_ONLY_ONCE(F) { \ NUnitTest::TTestContext context(this->TTestBase::Processor()); \ if (this->CheckAccessTest((#F))) { \ try { \ UNIT_TEST_RUN(F, FF, context) \ } catch (const ::NUnitTest::TAssertException&) { \ } catch (const yexception& e) { \ CATCH_REACTION_BT((#F), e, &context); \ } catch (const std::exception& e) { \ CATCH_REACTION((#F), e, &context); \ } catch (...) { \ this->AddError("non-std exception!", &context); \ } \ this->Finish((#F), &context); \ } \ } #define UNIT_TEST(F) UNIT_TEST_IMPL(F, false) #define UNIT_FORKED_TEST(F) UNIT_TEST_IMPL(F, true) #define UNIT_TEST_EXCEPTION(F, E) \ /* main process with "--fork-tests" flag treats exceptions as errors - it's result of forked test run */ \ if (this->GetForkTests() && !this->GetIsForked()) { \ UNIT_TEST_IMPL(F, false); \ /* forked process (or main without "--fork-tests") treats some exceptions as success - it's exception test! */ \ } else { \ NUnitTest::TTestContext context(this->TTestBase::Processor()); \ if (this->CheckAccessTest((#F))) { \ try { \ UNIT_TEST_RUN(F, false, context) \ this->AddError("exception expected", &context); \ } catch (const ::NUnitTest::TAssertException&) { \ } catch (const E& e) { \ TString err; \ if (!CheckExceptionMessage(e.what(), err)) \ this->AddError(err.c_str(), &context); \ } catch (const std::exception& e) { \ this->AddError(e.what(), &context); \ } catch (...) { \ this->AddError("non-std exception!", &context); \ } \ this->Finish((#F), &context); \ } \ } #define UNIT_TEST_SUITE_END() \ this->AtEnd(); \ } \ \ public: \ /*for ; after macros*/ void sub##F() #define UNIT_FAIL_IMPL(R, M) \ do { \ ::NUnitTest::NPrivate::RaiseError(R, ::TStringBuilder() << R << " at " << __LOCATION__ << ", " << __PRETTY_FUNCTION__ << ": " << M, true); \ } while (false) #define UNIT_FAIL_NONFATAL_IMPL(R, M) \ do { \ ::NUnitTest::NPrivate::RaiseError(R, ::TStringBuilder() << R << " at " << __LOCATION__ << ", " << __PRETTY_FUNCTION__ << ": " << M, false); \ } while (false) #define UNIT_FAIL(M) UNIT_FAIL_IMPL("forced failure", M) #define UNIT_FAIL_NONFATAL(M) UNIT_FAIL_NONFATAL_IMPL("forced failure", M) //types #define UNIT_ASSERT_TYPES_EQUAL(A, B) \ do { \ if (!std::is_same::value) { \ UNIT_FAIL_IMPL("types equal assertion failed", (::TStringBuilder() << #A << " (" << TypeName() << ") != " << #B << " (" << TypeName() << ")").data()); \ } \ } while (false) //doubles // UNIT_ASSERT_DOUBLES_EQUAL_DEPRECATED* macros do not handle NaNs correctly (see IGNIETFERRO-1419) and are for backward compatibility // only. Consider switching to regular UNIT_ASSERT_DOUBLES_EQUAL* macros if you're still using the deprecated version. #define UNIT_ASSERT_DOUBLES_EQUAL_DEPRECATED_C(E, A, D, C) \ do { \ if (std::abs((E) - (A)) > (D)) { \ const auto _es = ToString((long double)(E)); \ const auto _as = ToString((long double)(A)); \ const auto _ds = ToString((long double)(D)); \ auto&& failMsg = Sprintf("std::abs(%s - %s) > %s %s", _es.data(), _as.data(), _ds.data(), (::TStringBuilder() << C).data()); \ UNIT_FAIL_IMPL("assertion failure", failMsg); \ } \ } while (false) #define UNIT_ASSERT_DOUBLES_EQUAL_DEPRECATED(E, A, D) UNIT_ASSERT_DOUBLES_EQUAL_DEPRECATED_C(E, A, D, "") #define UNIT_ASSERT_DOUBLES_EQUAL_C(E, A, D, C) \ do { \ const auto _ed = (E); \ const auto _ad = (A); \ const auto _dd = (D); \ if (std::isnan((long double)_ed) && !std::isnan((long double)_ad)) { \ const auto _as = ToString((long double)_ad); \ auto&& failMsg = Sprintf("expected NaN, got %s %s", _as.data(), (::TStringBuilder() << C).data()); \ UNIT_FAIL_IMPL("assertion failure", failMsg); \ } \ if (!std::isnan((long double)_ed) && std::isnan((long double)_ad)) { \ const auto _es = ToString((long double)_ed); \ auto&& failMsg = Sprintf("expected %s, got NaN %s", _es.data(), (::TStringBuilder() << C).data()); \ UNIT_FAIL_IMPL("assertion failure", failMsg); \ } \ if (std::abs((_ed) - (_ad)) > (_dd)) { \ const auto _es = ToString((long double)_ed); \ const auto _as = ToString((long double)_ad); \ const auto _ds = ToString((long double)_dd); \ auto&& failMsg = Sprintf("std::abs(%s - %s) > %s %s", _es.data(), _as.data(), _ds.data(), (::TStringBuilder() << C).data()); \ UNIT_FAIL_IMPL("assertion failure", failMsg); \ } \ } while (false) #define UNIT_ASSERT_DOUBLES_EQUAL(E, A, D) UNIT_ASSERT_DOUBLES_EQUAL_C(E, A, D, "") //strings #define UNIT_ASSERT_STRINGS_EQUAL_C(A, B, C) \ do { \ const TString _a(A); /* NOLINT(performance-unnecessary-copy-initialization) */ \ const TString _b(B); /* NOLINT(performance-unnecessary-copy-initialization) */ \ if (_a != _b) { \ auto&& failMsg = Sprintf("%s != %s %s", ToString(_a).data(), ToString(_b).data(), (::TStringBuilder() << C).data()); \ UNIT_FAIL_IMPL("strings equal assertion failed", failMsg); \ } \ } while (false) #define UNIT_ASSERT_STRINGS_EQUAL(A, B) UNIT_ASSERT_STRINGS_EQUAL_C(A, B, "") #define UNIT_ASSERT_STRING_CONTAINS_C(A, B, C) \ do { \ const TString _a(A); /* NOLINT(performance-unnecessary-copy-initialization) */ \ const TString _b(B); /* NOLINT(performance-unnecessary-copy-initialization) */ \ if (!_a.Contains(_b)) { \ auto&& msg = Sprintf("\"%s\" does not contain \"%s\", %s", ToString(_a).data(), ToString(_b).data(), (::TStringBuilder() << C).data()); \ UNIT_FAIL_IMPL("strings contains assertion failed", msg); \ } \ } while (false) #define UNIT_ASSERT_STRING_CONTAINS(A, B) UNIT_ASSERT_STRING_CONTAINS_C(A, B, "") #define UNIT_ASSERT_NO_DIFF(A, B) \ do { \ const TString _a(A); /* NOLINT(performance-unnecessary-copy-initialization) */ \ const TString _b(B); /* NOLINT(performance-unnecessary-copy-initialization) */ \ if (_a != _b) { \ UNIT_FAIL_IMPL("strings (" #A ") and (" #B ") are different", Sprintf("\n%s", ::NUnitTest::ColoredDiff(_a, _b, " \t\n.,:;'\"").data())); \ } \ } while (false) //strings #define UNIT_ASSERT_STRINGS_UNEQUAL_C(A, B, C) \ do { \ const TString _a(A); /* NOLINT(performance-unnecessary-copy-initialization) */ \ const TString _b(B); /* NOLINT(performance-unnecessary-copy-initialization) */ \ if (_a == _b) { \ auto&& msg = Sprintf("%s == %s %s", ToString(_a).data(), ToString(_b).data(), (::TStringBuilder() << C).data()); \ UNIT_FAIL_IMPL("strings unequal assertion failed", msg); \ } \ } while (false) #define UNIT_ASSERT_STRINGS_UNEQUAL(A, B) UNIT_ASSERT_STRINGS_UNEQUAL_C(A, B, "") //bool #define UNIT_ASSERT_C(A, C) \ do { \ if (!(A)) { \ UNIT_FAIL_IMPL("assertion failed", Sprintf("(%s) %s", #A, (::TStringBuilder() << C).data())); \ } \ } while (false) #define UNIT_ASSERT(A) UNIT_ASSERT_C(A, "") //general #define UNIT_ASSERT_EQUAL_C(A, B, C) \ do { \ if (!((A) == (B))) { /* NOLINT(readability-container-size-empty) */ \ UNIT_FAIL_IMPL("equal assertion failed", Sprintf("%s == %s %s", #A, #B, (::TStringBuilder() << C).data())); \ } \ } while (false) #define UNIT_ASSERT_EQUAL(A, B) UNIT_ASSERT_EQUAL_C(A, B, "") #define UNIT_ASSERT_UNEQUAL_C(A, B, C) \ do { \ if ((A) == (B)) { /* NOLINT(readability-container-size-empty) */ \ UNIT_FAIL_IMPL("unequal assertion failed", Sprintf("%s != %s %s", #A, #B, (::TStringBuilder() << C).data()));\ } \ } while (false) #define UNIT_ASSERT_UNEQUAL(A, B) UNIT_ASSERT_UNEQUAL_C(A, B, "") #define UNIT_ASSERT_LT_C(A, B, C) \ do { \ if (!((A) < (B))) { \ UNIT_FAIL_IMPL("less-than assertion failed", Sprintf("%s < %s %s", #A, #B, (::TStringBuilder() << C).data())); \ } \ } while (false) #define UNIT_ASSERT_LT(A, B) UNIT_ASSERT_LT_C(A, B, "") #define UNIT_ASSERT_LE_C(A, B, C) \ do { \ if (!((A) <= (B))) { \ UNIT_FAIL_IMPL("less-or-equal assertion failed", Sprintf("%s <= %s %s", #A, #B, (::TStringBuilder() << C).data())); \ } \ } while (false) #define UNIT_ASSERT_LE(A, B) UNIT_ASSERT_LE_C(A, B, "") #define UNIT_ASSERT_GT_C(A, B, C) \ do { \ if (!((A) > (B))) { \ UNIT_FAIL_IMPL("greater-than assertion failed", Sprintf("%s > %s %s", #A, #B, (::TStringBuilder() << C).data())); \ } \ } while (false) #define UNIT_ASSERT_GT(A, B) UNIT_ASSERT_GT_C(A, B, "") #define UNIT_ASSERT_GE_C(A, B, C) \ do { \ if (!((A) >= (B))) { \ UNIT_FAIL_IMPL("greater-or-equal assertion failed", Sprintf("%s >= %s %s", #A, #B, (::TStringBuilder() << C).data())); \ } \ } while (false) #define UNIT_ASSERT_GE(A, B) UNIT_ASSERT_GE_C(A, B, "") #define UNIT_CHECK_GENERATED_EXCEPTION_C(A, E, C) \ do { \ try { \ (void)(A); \ } catch (const ::NUnitTest::TAssertException&) { \ throw; \ } catch (const E&) { \ break; \ } \ UNIT_ASSERT_C(0, "Exception hasn't been thrown, but it should have happened " << C); \ } while (false) #define UNIT_CHECK_GENERATED_EXCEPTION(A, E) UNIT_CHECK_GENERATED_EXCEPTION_C(A, E, "") #define UNIT_CHECK_GENERATED_NO_EXCEPTION_C(A, E, C) \ do { \ try { \ (void)(A); \ } catch (const ::NUnitTest::TAssertException&) { \ throw; \ } catch (const E&) { \ UNIT_ASSERT_C(0, "Exception has been thrown, but it shouldn't have happened " << C); \ } \ } while (false) #define UNIT_CHECK_GENERATED_NO_EXCEPTION(A, E) UNIT_CHECK_GENERATED_NO_EXCEPTION_C(A, E, "and exception message is:\n" << CurrentExceptionMessage()) // Same as UNIT_ASSERT_EXCEPTION_SATISFIES but prints additional string C when nothing was thrown #define UNIT_ASSERT_EXCEPTION_SATISFIES_C(A, E, pred, C) \ do { \ bool _thrown = false; \ try { \ (void)(A); \ } catch (const ::NUnitTest::TAssertException&) { \ throw; \ } catch (const E& e) { \ _thrown = true; \ UNIT_ASSERT_C(pred(e), "Exception does not satisfy predicate '" \ << #pred << "'"); \ } catch (...) { \ _thrown = true; \ UNIT_FAIL_IMPL("exception assertion failed", \ #A << " did not throw " << #E \ << ", but threw other exception " \ << "with message:\n" \ << CurrentExceptionMessage()); \ } \ if (!_thrown) { \ UNIT_FAIL_IMPL("exception assertion failed", \ #A << " did not throw any exception" \ << " (expected " << #E << ") " << C); \ } \ } while (false) // Assert that a specific exception is thrown and satisfies predicate pred(e), where e is the exception instance. // Example: // UNIT_ASSERT_EXCEPTION_SATISFIES(MakeRequest(invalidData), TError, // [](const TError& e){ return e.Status == HTTP_BAD_REQUEST; }) // This code validates that MakeRequest with invalidData throws TError with code 400. #define UNIT_ASSERT_EXCEPTION_SATISFIES(A, E, pred) \ UNIT_ASSERT_EXCEPTION_SATISFIES_C(A, E, pred, "") // Same as UNIT_ASSERT_EXCEPTION_CONTAINS but prints additional string C when nothing was thrown #define UNIT_ASSERT_EXCEPTION_CONTAINS_C(A, E, substr, C) \ do { \ const TString _substr{substr}; \ UNIT_ASSERT_EXCEPTION_SATISFIES_C(A, E, \ [&_substr](const E&){ \ if (!_substr.empty()) { \ auto cure = CurrentExceptionMessage() ; \ UNIT_ASSERT_C(cure.Contains(_substr), \ "Exception message does not contain \"" \ << _substr << "\".\n" \ << "Exception message: " \ << cure); \ } \ return true; \ }, \ C); \ } while (false) // Assert that a specific exception is thrown and CurrentExceptionMessage() contains substr #define UNIT_ASSERT_EXCEPTION_CONTAINS(A, E, substr) \ UNIT_ASSERT_EXCEPTION_CONTAINS_C(A, E, substr, "") // Same as UNIT_ASSERT_EXCEPTION but prints additional string C when nothing was thrown #define UNIT_ASSERT_EXCEPTION_C(A, E, C) UNIT_ASSERT_EXCEPTION_SATISFIES_C(A, E, [](const E&){ return true; }, C) // Assert that a specific exception is thrown #define UNIT_ASSERT_EXCEPTION(A, E) UNIT_ASSERT_EXCEPTION_C(A, E, "") #define UNIT_ASSERT_NO_EXCEPTION_RESULT_C(A, C) \ [&] () mutable -> decltype(A) { \ static_assert(!std::is_void_v); \ try { return (A); } \ catch (const ::NUnitTest::TAssertException&) { throw; } \ catch (...) { \ UNIT_FAIL_IMPL( \ "exception-free assertion failed", \ Sprintf("%s throws %s\nException message: %s", \ #A, (::TStringBuilder() << C).data(), \ CurrentExceptionMessage().data())); \ return decltype(A){}; \ } \ }() #define UNIT_ASSERT_NO_EXCEPTION_RESULT(A) UNIT_ASSERT_NO_EXCEPTION_RESULT_C(A, "") #define UNIT_ASSERT_NO_EXCEPTION_C(A, C) \ do { \ try { \ (void)(A); \ } catch (const ::NUnitTest::TAssertException&) { \ throw; \ } catch (...) { \ UNIT_FAIL_IMPL("exception-free assertion failed", Sprintf("%s throws %s\nException message: %s", #A, (::TStringBuilder() << C).data(), CurrentExceptionMessage().data())); \ } \ } while (false) #define UNIT_ASSERT_NO_EXCEPTION(A) UNIT_ASSERT_NO_EXCEPTION_C(A, "") namespace NPrivate { template struct TCompareValuesImpl { static inline bool Compare(const T& a, const U& b) { return a == b; } }; template struct TCompareValuesImpl { static inline bool Compare(const T& a, const U& b) { return ::ToString(a) == ::ToString(b); } }; template using TCompareValues = TCompareValuesImpl::value && std::is_integral::value>; template static inline bool CompareEqual(const T& a, const U& b) { return TCompareValues::Compare(a, b); } static inline bool CompareEqual(const char* a, const char* b) { return 0 == strcmp(a, b); } // helper method to avoid double evaluation of A and B expressions in UNIT_ASSERT_VALUES_EQUAL_C template static inline bool CompareAndMakeStrings(const T& a, const U& b, TString& as, TString& asInd, TString& bs, TString& bsInd, bool& usePlainDiff, bool want) { const bool have = CompareEqual(a, b); usePlainDiff = std::is_integral::value && std::is_integral::value; if (want == have) { return true; } as = ::TStringBuilder() << ::DbgDump(a); bs = ::TStringBuilder() << ::DbgDump(b); asInd = ::TStringBuilder() << ::DbgDump(a).SetIndent(true); bsInd = ::TStringBuilder() << ::DbgDump(b).SetIndent(true); return false; } } //values #define UNIT_ASSERT_VALUES_EQUAL_IMPL(A, B, C, EQflag, EQstr, NEQstr) \ do { \ TString _as; \ TString _bs; \ TString _asInd; \ TString _bsInd; \ bool _usePlainDiff; \ if (!::NUnitTest::NPrivate::CompareAndMakeStrings(A, B, _as, _asInd, _bs, _bsInd, _usePlainDiff, EQflag)) { \ auto&& failMsg = Sprintf("(%s %s %s) failed: (%s %s %s) %s", #A, EQstr, #B, _as.data(), NEQstr, _bs.data(), (::TStringBuilder() << C).data()); \ if (EQflag && !_usePlainDiff) { \ failMsg += ", with diff:\n"; \ failMsg += ::NUnitTest::ColoredDiff(_asInd, _bsInd); \ } \ UNIT_FAIL_IMPL("assertion failed", failMsg); \ } \ } while (false) #define UNIT_ASSERT_VALUES_EQUAL_C(A, B, C) \ UNIT_ASSERT_VALUES_EQUAL_IMPL(A, B, C, true, "==", "!=") #define UNIT_ASSERT_VALUES_UNEQUAL_C(A, B, C) \ UNIT_ASSERT_VALUES_EQUAL_IMPL(A, B, C, false, "!=", "==") #define UNIT_ASSERT_VALUES_EQUAL(A, B) UNIT_ASSERT_VALUES_EQUAL_C(A, B, "") #define UNIT_ASSERT_VALUES_UNEQUAL(A, B) UNIT_ASSERT_VALUES_UNEQUAL_C(A, B, "") // Checks that test will fail while executing given expression // Macro for using in unitests for ut helpers #define UNIT_ASSERT_TEST_FAILS_C(A, C) \ do { \ ::NUnitTest::TUnitTestFailChecker checker; \ try { \ auto guard = checker.InvokeGuard(); \ (void)(A); \ } catch (...) { \ UNIT_FAIL_IMPL("fail test assertion failure", \ "code is expected to generate test failure, " \ "but it throws exception with message: " \ << CurrentExceptionMessage()); \ } \ if (!checker.Failed()) { \ UNIT_FAIL_IMPL("fail test assertion failure", \ "code is expected to generate test failure"); \ } \ } while (false) #define UNIT_ASSERT_TEST_FAILS(A) UNIT_ASSERT_TEST_FAILS_C(A, "") #define UNIT_ADD_METRIC(name, value) ut_context.Metrics[name] = value class TTestFactory { friend class TTestBase; friend class ITestBaseFactory; public: static TTestFactory& Instance(); unsigned Execute(); void SetProcessor(ITestSuiteProcessor* processor); private: void Register(ITestBaseFactory* b) noexcept; ITestSuiteProcessor* Processor() const noexcept; private: explicit TTestFactory(ITestSuiteProcessor* processor); ~TTestFactory(); private: TIntrusiveList Items_; ITestSuiteProcessor* Processor_; }; template class TTestBaseFactory: public ITestBaseFactory { public: ~TTestBaseFactory() override = default; inline TTestBase* ConstructTest() override { return new T; } inline TString Name() const noexcept override { return T::StaticName(); } }; struct TBaseTestCase { // NOTE: since EACH test case is instantiated for listing tests, its // ctor/dtor are not the best place to do heavy preparations in test fixtures. // // Consider using SetUp()/TearDown() methods instead inline TBaseTestCase() : TBaseTestCase(nullptr, nullptr, false) { } inline TBaseTestCase(const char* name, std::function body, bool forceFork) : Name_(name) , Body_(std::move(body)) , ForceFork_(forceFork) { } virtual ~TBaseTestCase() = default; // Each test case is executed in 3 steps: // // 1. SetUp() (from fixture) // 2. Execute_() (test body from Y_UNIT_TEST macro) // 3. TearDown() (from fixture) // // Both SetUp() and TearDown() may use UNIT_* check macros and are only // called when the test is executed. virtual void SetUp(TTestContext& /* context */) { } virtual void TearDown(TTestContext& /* context */) { } virtual void Execute_(TTestContext& context) { Body_(context); } const char* Name_; std::function Body_; bool ForceFork_; }; using TBaseFixture = TBaseTestCase; // Class for checking that code raises unittest failure class TUnitTestFailChecker { public: struct TInvokeGuard { explicit TInvokeGuard(TUnitTestFailChecker& parent) : Parent(&parent) { Parent->SetHandler(); } TInvokeGuard(TInvokeGuard&& guard) noexcept : Parent(guard.Parent) { guard.Parent = nullptr; } ~TInvokeGuard() { if (Parent) { ClearRaiseErrorHandler(); } } TUnitTestFailChecker* Parent; }; TUnitTestFailChecker() = default; TUnitTestFailChecker(const TUnitTestFailChecker&) = delete; TUnitTestFailChecker(TUnitTestFailChecker&&) = delete; TInvokeGuard InvokeGuard() { return TInvokeGuard(*this); } const TString& What() const { return What_; } const TString& Msg() const { return Msg_; } bool FatalFailure() const { return FatalFailure_; } bool Failed() const { return Failed_; } private: void Handler(const char* what, const TString& msg, bool fatalFailure) { What_ = what; Msg_ = msg; FatalFailure_ = fatalFailure; Failed_ = true; } void SetHandler() { TRaiseErrorHandler handler = [this](const char* what, const TString& msg, bool fatalFailure) { Handler(what, msg, fatalFailure); }; SetRaiseErrorHandler(std::move(handler)); } private: TString What_; TString Msg_; bool FatalFailure_ = false; bool Failed_ = false; }; #define UNIT_TEST_SUITE_REGISTRATION(T) \ static const ::NUnitTest::TTestBaseFactory Y_GENERATE_UNIQUE_ID(UTREG_); #define Y_UNIT_TEST_SUITE_IMPL_F(N, T, F) \ namespace NTestSuite##N { \ class TCurrentTestCase: public F { \ }; \ class TCurrentTest: public T { \ private: \ typedef std::function()> TTestCaseFactory; \ typedef TVector TTests; \ \ static TTests& Tests() { \ static TTests tests; \ return tests; \ } \ \ public: \ static TString StaticName() { \ return #N; \ } \ virtual TString Name() const noexcept { \ return StaticName(); \ } \ \ static void AddTest(const char* name, \ const std::function& body, bool forceFork) \ { \ Tests().emplace_back([=]{ return MakeHolder(name, body, forceFork); }); \ } \ \ static void AddTest(TTestCaseFactory testCaseFactory) { \ Tests().push_back(std::move(testCaseFactory)); \ } \ \ virtual void Execute() { \ this->AtStart(); \ this->GlobalSuiteSetUp(); \ for (TTests::iterator it = Tests().begin(), ie = Tests().end(); it != ie; ++it) { \ const auto i = (*it)(); \ if (!this->CheckAccessTest(i->Name_)) { \ continue; \ } \ NUnitTest::TTestContext context(this->TTestBase::Processor()); \ try { \ this->BeforeTest(i->Name_); \ { \ TCleanUp cleaner(this); \ auto testCase = [this, &i, &context] { \ Y_DEFER { \ try { \ i->TearDown(context); \ } catch (const ::NUnitTest::TAssertException&) { \ } catch (const yexception& e) { \ CATCH_REACTION_BT(i->Name_, e, &context); \ } catch (const std::exception& e) { \ CATCH_REACTION(i->Name_, e, &context); \ } catch (...) { \ this->AddError("non-std exception!", &context); \ } \ }; \ i->SetUp(context); \ i->Execute_(context); \ }; \ this->T::Run(testCase, StaticName(), i->Name_, i->ForceFork_); \ } \ } catch (const ::NUnitTest::TAssertException&) { \ } catch (const yexception& e) { \ CATCH_REACTION_BT(i->Name_, e, &context); \ } catch (const std::exception& e) { \ CATCH_REACTION(i->Name_, e, &context); \ } catch (...) { \ this->AddError("non-std exception!", &context); \ } \ this->Finish(i->Name_, &context); \ } \ this->GlobalSuiteTearDown(); \ this->AtEnd(); \ } \ }; \ UNIT_TEST_SUITE_REGISTRATION(TCurrentTest) \ } \ namespace NTestSuite##N #define Y_UNIT_TEST_SUITE_IMPL(N, T) Y_UNIT_TEST_SUITE_IMPL_F(N, T, ::NUnitTest::TBaseTestCase) #define Y_UNIT_TEST_SUITE(N) Y_UNIT_TEST_SUITE_IMPL(N, TTestBase) #define Y_UNIT_TEST_SUITE_F(N, F) Y_UNIT_TEST_SUITE_IMPL_F(N, TTestBase, F) #define RUSAGE_UNIT_TEST_SUITE(N) Y_UNIT_TEST_SUITE_IMPL(N, NUnitTest::TRusageTest, ::NUnitTest::TBaseTestCase) #define Y_UNIT_TEST_IMPL_REGISTER(N, FF, F) \ struct TTestCase##N : public F { \ TTestCase##N() \ { \ Name_ = #N; \ ForceFork_ = FF; \ } \ static THolder Create() { \ return ::MakeHolder(); \ } \ void Execute_(NUnitTest::TTestContext&) override; \ }; \ struct TTestRegistration##N { \ TTestRegistration##N() { \ TCurrentTest::AddTest(TTestCase##N::Create); \ } \ }; \ static const TTestRegistration##N testRegistration##N; #define Y_UNIT_TEST_IMPL(N, FF, F) \ Y_UNIT_TEST_IMPL_REGISTER(N, FF, F) \ void TTestCase##N::Execute_(NUnitTest::TTestContext& ut_context Y_DECLARE_UNUSED) #define Y_UNIT_TEST(N) Y_UNIT_TEST_IMPL(N, false, TCurrentTestCase) #define Y_UNIT_TEST_F(N, F) Y_UNIT_TEST_IMPL(N, false, F) #define SIMPLE_UNIT_FORKED_TEST(N) Y_UNIT_TEST_IMPL(N, true, TCurrentTestCase) #define Y_UNIT_TEST_SUITE_IMPLEMENTATION(N) \ namespace NTestSuite##N #define Y_UNIT_TEST_DECLARE(N) \ struct TTestCase##N #define Y_UNIT_TEST_FRIEND(N, T) \ friend NTestSuite##N::TTestCase##T \ TString RandomString(size_t len, ui32 seed = 0); } using ::NUnitTest::TTestBase;