aboutsummaryrefslogtreecommitdiffstats
path: root/library/cpp/testing/unittest/registar.h
diff options
context:
space:
mode:
authorDevtools Arcadia <arcadia-devtools@yandex-team.ru>2022-02-07 18:08:42 +0300
committerDevtools Arcadia <arcadia-devtools@mous.vla.yp-c.yandex.net>2022-02-07 18:08:42 +0300
commit1110808a9d39d4b808aef724c861a2e1a38d2a69 (patch)
treee26c9fed0de5d9873cce7e00bc214573dc2195b7 /library/cpp/testing/unittest/registar.h
downloadydb-1110808a9d39d4b808aef724c861a2e1a38d2a69.tar.gz
intermediate changes
ref:cde9a383711a11544ce7e107a78147fb96cc4029
Diffstat (limited to 'library/cpp/testing/unittest/registar.h')
-rw-r--r--library/cpp/testing/unittest/registar.h1013
1 files changed, 1013 insertions, 0 deletions
diff --git a/library/cpp/testing/unittest/registar.h b/library/cpp/testing/unittest/registar.h
new file mode 100644
index 0000000000..332485bdf3
--- /dev/null
+++ b/library/cpp/testing/unittest/registar.h
@@ -0,0 +1,1013 @@
+#pragma once
+
+#include <library/cpp/dbg_output/dump.h>
+
+#include <util/generic/bt_exception.h>
+#include <util/generic/hash.h>
+#include <util/generic/intrlist.h>
+#include <util/generic/map.h>
+#include <util/generic/ptr.h>
+#include <util/generic/set.h>
+#include <util/generic/typetraits.h>
+#include <util/generic/vector.h>
+#include <util/generic/yexception.h>
+
+#include <util/string/builder.h>
+#include <util/string/cast.h>
+#include <util/string/printf.h>
+
+#include <util/system/defaults.h>
+#include <util/system/type_name.h>
+#include <util/system/spinlock.h>
+#include <util/system/src_location.h>
+
+#include <util/system/rusage.h>
+
+#include <cmath>
+#include <cstdio>
+#include <functional>
+
+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;
+ extern bool ContinueOnFail;
+ 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(const char*, const TString&, bool)>;
+
+ 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<TString, double>;
+ 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<void()> 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;
+
+ 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<TString, size_t> TestErrors_;
+ TMap<TString, size_t> CurTestErrors_;
+ };
+
+ class TTestBase;
+ class TTestFactory;
+
+ class ITestBaseFactory: public TIntrusiveListItem<ITestBaseFactory> {
+ 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();
+
+ void AddError(const char* msg, const TString& backtrace = TString(), TTestContext* context = nullptr);
+
+ void AddError(const char* msg, TTestContext* context);
+
+ void RunAfterTest(std::function<void()> 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<void()> 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<std::function<void()>> 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<N>(); \
+ } \
+ \
+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<A, B>::value) { \
+ UNIT_FAIL_IMPL("types equal assertion failed", (::TStringBuilder() << #A << " (" << TypeName<A>() << ") != " << #B << " (" << TypeName<B>() << ")").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); \
+ const TString _b(B); \
+ 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); \
+ const TString _b(B); \
+ 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); \
+ const TString _b(B); \
+ 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); \
+ const TString _b(B); \
+ 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))) { \
+ 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)) { \
+ 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()) { \
+ UNIT_ASSERT_C(CurrentExceptionMessage() \
+ .Contains(_substr), \
+ "Exception message does not contain \"" \
+ << _substr << "\".\n" \
+ << "Exception message: " \
+ << CurrentExceptionMessage()); \
+ } \
+ 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_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 <class T, class U, bool Integers>
+ struct TCompareValuesImpl {
+ static inline bool Compare(const T& a, const U& b) {
+ return a == b;
+ }
+ };
+
+ template <class T, class U>
+ struct TCompareValuesImpl<T, U, true> {
+ static inline bool Compare(const T& a, const U& b) {
+ return ::ToString(a) == ::ToString(b);
+ }
+ };
+
+ template <class T, class U>
+ using TCompareValues = TCompareValuesImpl<T, U, std::is_integral<T>::value && std::is_integral<U>::value>;
+
+ template <typename T, typename U>
+ static inline bool CompareEqual(const T& a, const U& b) {
+ return TCompareValues<T, U>::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 <typename T, typename U>
+ 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<T>::value && std::is_integral<U>::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<ITestBaseFactory> Items_;
+ ITestSuiteProcessor* Processor_;
+ };
+
+ template <class T>
+ 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<void(TTestContext&)> 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<void(TTestContext&)> 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<T> 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<THolder<NUnitTest::TBaseTestCase>()> TTestCaseFactory; \
+ typedef TVector<TTestCaseFactory> 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<void(NUnitTest::TTestContext&)>& body, bool forceFork) \
+ { \
+ Tests().push_back([=]{ return MakeHolder<NUnitTest::TBaseTestCase>(name, body, forceFork); }); \
+ } \
+ \
+ static void AddTest(TTestCaseFactory testCaseFactory) { \
+ Tests().push_back(std::move(testCaseFactory)); \
+ } \
+ \
+ virtual void Execute() { \
+ this->AtStart(); \
+ 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 = [&i, &context] { \
+ i->SetUp(context); \
+ i->Execute_(context); \
+ i->TearDown(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->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() \
+ : F() \
+ { \
+ Name_ = #N; \
+ ForceFork_ = FF; \
+ } \
+ static THolder<NUnitTest::TBaseTestCase> Create() { \
+ return ::MakeHolder<TTestCase##N>(); \
+ } \
+ 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;