aboutsummaryrefslogtreecommitdiffstats
path: root/library/cpp/testing/unittest
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
downloadydb-1110808a9d39d4b808aef724c861a2e1a38d2a69.tar.gz
intermediate changes
ref:cde9a383711a11544ce7e107a78147fb96cc4029
Diffstat (limited to 'library/cpp/testing/unittest')
-rw-r--r--library/cpp/testing/unittest/checks.cpp31
-rw-r--r--library/cpp/testing/unittest/env.h3
-rw-r--r--library/cpp/testing/unittest/example_ut.cpp12
-rw-r--r--library/cpp/testing/unittest/fat/test_port_manager.cpp36
-rw-r--r--library/cpp/testing/unittest/fat/ya.make19
-rw-r--r--library/cpp/testing/unittest/gtest.cpp67
-rw-r--r--library/cpp/testing/unittest/gtest.h108
-rw-r--r--library/cpp/testing/unittest/plugin.cpp50
-rw-r--r--library/cpp/testing/unittest/plugin.h29
-rw-r--r--library/cpp/testing/unittest/registar.cpp513
-rw-r--r--library/cpp/testing/unittest/registar.h1013
-rw-r--r--library/cpp/testing/unittest/registar_ut.cpp360
-rw-r--r--library/cpp/testing/unittest/simple.h39
-rw-r--r--library/cpp/testing/unittest/tests_data.cpp104
-rw-r--r--library/cpp/testing/unittest/tests_data.h54
-rw-r--r--library/cpp/testing/unittest/ut/main.cpp93
-rw-r--r--library/cpp/testing/unittest/ut/ya.make10
-rw-r--r--library/cpp/testing/unittest/utmain.cpp771
-rw-r--r--library/cpp/testing/unittest/utmain.h5
-rw-r--r--library/cpp/testing/unittest/ya.make33
20 files changed, 3350 insertions, 0 deletions
diff --git a/library/cpp/testing/unittest/checks.cpp b/library/cpp/testing/unittest/checks.cpp
new file mode 100644
index 0000000000..c5712ae9d2
--- /dev/null
+++ b/library/cpp/testing/unittest/checks.cpp
@@ -0,0 +1,31 @@
+#include <util/generic/string.h>
+#include <util/string/type.h>
+
+bool CheckExceptionMessage(const char* msg, TString& err) {
+ static const char* badMsg[] = {
+ // Операция успешно завершена [cp1251]
+ "\xce\xef\xe5\xf0\xe0\xf6\xe8\xff\x20\xf3\xf1\xef\xe5\xf8\xed\xee\x20\xe7\xe0\xe2\xe5\xf0\xf8\xe5\xed\xe0",
+ "The operation completed successfully",
+ "No error"};
+
+ err.clear();
+
+ if (msg == nullptr) {
+ err = "Error message is null";
+ return false;
+ }
+
+ if (IsSpace(msg)) {
+ err = "Error message is empty";
+ return false;
+ }
+
+ for (auto& i : badMsg) {
+ if (strstr(msg, i) != nullptr) {
+ err = "Invalid error message: " + TString(msg);
+ return false;
+ }
+ }
+
+ return true;
+}
diff --git a/library/cpp/testing/unittest/env.h b/library/cpp/testing/unittest/env.h
new file mode 100644
index 0000000000..4807539ab2
--- /dev/null
+++ b/library/cpp/testing/unittest/env.h
@@ -0,0 +1,3 @@
+// just shortcut
+#include <library/cpp/testing/common/env.h>
+
diff --git a/library/cpp/testing/unittest/example_ut.cpp b/library/cpp/testing/unittest/example_ut.cpp
new file mode 100644
index 0000000000..bcc1ce33f0
--- /dev/null
+++ b/library/cpp/testing/unittest/example_ut.cpp
@@ -0,0 +1,12 @@
+#include <library/cpp/testing/unittest/registar.h>
+
+/*
+ * just copy-paste it for good start point
+ */
+
+Y_UNIT_TEST_SUITE(TUnitTest) {
+ Y_UNIT_TEST(TestEqual) {
+ UNIT_ASSERT_EQUAL(0, 0);
+ UNIT_ASSERT_EQUAL(1, 1);
+ }
+}
diff --git a/library/cpp/testing/unittest/fat/test_port_manager.cpp b/library/cpp/testing/unittest/fat/test_port_manager.cpp
new file mode 100644
index 0000000000..f77d2e3a25
--- /dev/null
+++ b/library/cpp/testing/unittest/fat/test_port_manager.cpp
@@ -0,0 +1,36 @@
+#include <library/cpp/testing/unittest/registar.h>
+#include <library/cpp/testing/unittest/tests_data.h>
+
+bool IsFreePort(ui16 port) {
+ TInet6StreamSocket sock;
+ TSockAddrInet6 addr("::", port);
+ Y_ENSURE(SetSockOpt(sock, SOL_SOCKET, SO_REUSEADDR, 1) == 0);
+ SetReuseAddressAndPort(sock);
+ if (sock.Bind(&addr) == 0) {
+ return true;
+ }
+ return false;
+}
+
+void get_port_ranges() {
+ for (int i = 1; i < 10; ++i) {
+ TPortManager pm;
+ ui16 port = pm.GetPortsRange(1024, i);
+ for (int p = port; p < port + i; ++p) {
+ UNIT_ASSERT(IsFreePort(p));
+ }
+ }
+}
+
+Y_UNIT_TEST_SUITE(TestTPortManager) {
+ Y_UNIT_TEST(ParallelRun0) {get_port_ranges();}
+ Y_UNIT_TEST(ParallelRun1) {get_port_ranges();}
+ Y_UNIT_TEST(ParallelRun2) {get_port_ranges();}
+ Y_UNIT_TEST(ParallelRun3) {get_port_ranges();}
+ Y_UNIT_TEST(ParallelRun4) {get_port_ranges();}
+ Y_UNIT_TEST(ParallelRun5) {get_port_ranges();}
+ Y_UNIT_TEST(ParallelRun6) {get_port_ranges();}
+ Y_UNIT_TEST(ParallelRun7) {get_port_ranges();}
+ Y_UNIT_TEST(ParallelRun8) {get_port_ranges();}
+ Y_UNIT_TEST(ParallelRun9) {get_port_ranges();}
+}
diff --git a/library/cpp/testing/unittest/fat/ya.make b/library/cpp/testing/unittest/fat/ya.make
new file mode 100644
index 0000000000..d405e599ee
--- /dev/null
+++ b/library/cpp/testing/unittest/fat/ya.make
@@ -0,0 +1,19 @@
+UNITTEST()
+
+OWNER(g:yatool)
+
+SRCS(
+ test_port_manager.cpp
+)
+
+SIZE(LARGE)
+
+# We need to run tests at the same time on the single machine
+FORK_SUBTESTS()
+
+TAG(
+ ya:fat
+ ya:force_sandbox
+)
+
+END()
diff --git a/library/cpp/testing/unittest/gtest.cpp b/library/cpp/testing/unittest/gtest.cpp
new file mode 100644
index 0000000000..ebad1ea4d6
--- /dev/null
+++ b/library/cpp/testing/unittest/gtest.cpp
@@ -0,0 +1,67 @@
+#include "gtest.h"
+#include "simple.h"
+
+#include <util/generic/map.h>
+#include <util/generic/vector.h>
+#include <util/system/type_name.h>
+
+using namespace NUnitTest;
+using namespace NUnitTest::NPrivate;
+
+IGTestFactory::~IGTestFactory() {
+}
+
+namespace {
+ struct TCurrentTest: public TSimpleTestExecutor {
+ inline TCurrentTest(TStringBuf name)
+ : MyName(name)
+ {
+ }
+
+ TString TypeId() const override {
+ return TypeName(*this) + "-" + MyName;
+ }
+
+ TString Name() const noexcept override {
+ return TString(MyName);
+ }
+
+ const TStringBuf MyName;
+ };
+
+ struct TGTestFactory: public IGTestFactory {
+ inline TGTestFactory(TStringBuf name)
+ : Test(name)
+ {
+ }
+
+ ~TGTestFactory() override {
+ }
+
+ TString Name() const noexcept override {
+ return Test.Name();
+ }
+
+ TTestBase* ConstructTest() override {
+ return new TCurrentTest(Test);
+ }
+
+ void AddTest(const char* name, void (*body)(TTestContext&), bool forceFork) override {
+ Test.Tests.push_back(TBaseTestCase(name, body, forceFork));
+ }
+
+ TCurrentTest Test;
+ };
+}
+
+IGTestFactory* NUnitTest::NPrivate::ByName(const char* name) {
+ static TMap<TStringBuf, TAutoPtr<TGTestFactory>> tests;
+
+ auto& ret = tests[name];
+
+ if (!ret) {
+ ret = new TGTestFactory(name);
+ }
+
+ return ret.Get();
+}
diff --git a/library/cpp/testing/unittest/gtest.h b/library/cpp/testing/unittest/gtest.h
new file mode 100644
index 0000000000..b6768b1bf0
--- /dev/null
+++ b/library/cpp/testing/unittest/gtest.h
@@ -0,0 +1,108 @@
+#pragma once
+
+// WARNING: this is a legacy header that tries to mimic the gtest interface while using unittest
+// under the hood. Avoid using this interface -- use the genuine gtest instead (the GTEST macro).
+// If you're already using GTEST macro and you've found yourself here, you probably meant
+// to include `library/cpp/testing/gtest/gtest.h`.
+
+#include "registar.h"
+
+#include <util/generic/ymath.h>
+#include <util/generic/ylimits.h>
+
+namespace NUnitTest {
+ namespace NPrivate {
+ struct IGTestFactory: public ITestBaseFactory {
+ ~IGTestFactory() override;
+
+ virtual void AddTest(const char* name, void (*body)(TTestContext&), bool forceFork) = 0;
+ };
+
+ IGTestFactory* ByName(const char* name);
+ }
+}
+
+namespace NTesting {
+ struct TTest {
+ virtual void SetUp() {
+ }
+
+ virtual void TearDown() {
+ }
+
+ inline TTest* _This() noexcept {
+ return this;
+ }
+ };
+}
+
+namespace testing {
+ struct Test: public ::NTesting::TTest {
+ };
+}
+
+#define TEST_IMPL(N, NN, FF) \
+ void Test##N##NN(NUnitTest::TTestContext&); \
+ namespace NTestSuite##N##NN { \
+ struct TReg { \
+ inline TReg() { \
+ ::NUnitTest::NPrivate::ByName(#N)->AddTest(#NN, &(Test##N##NN), FF); \
+ } \
+ }; \
+ static TReg reg; \
+ } \
+ void Test##N##NN(NUnitTest::TTestContext&)
+
+#define TEST_F_IMPL(N, NN, FF) \
+ namespace NTestSuite##N##NN { \
+ struct TTestSuite: public N { \
+ inline TTestSuite() { \
+ this->_This()->SetUp(); \
+ } \
+ inline ~TTestSuite() { \
+ this->_This()->TearDown(); \
+ } \
+ void NN(); \
+ }; \
+ }; \
+ TEST_IMPL(N, NN, FF) { \
+ NTestSuite##N##NN::TTestSuite().NN(); \
+ } \
+ void NTestSuite##N##NN::TTestSuite::NN()
+
+#define TEST(A, B) TEST_IMPL(A, B, false)
+#define TEST_FORKED(A, B) TEST_IMPL(A, B, true)
+
+#define TEST_F(A, B) TEST_F_IMPL(A, B, false)
+#define TEST_F_FORKED(A, B) TEST_F_IMPL(A, B, true)
+
+#define EXPECT_EQ(A, B) UNIT_ASSERT_VALUES_EQUAL(A, B)
+#define EXPECT_NE(A, B) UNIT_ASSERT_UNEQUAL(A, B)
+#define EXPECT_LE(A, B) UNIT_ASSERT((A) <= (B))
+#define EXPECT_LT(A, B) UNIT_ASSERT((A) < (B))
+#define EXPECT_GE(A, B) UNIT_ASSERT((A) >= (B))
+#define EXPECT_GT(A, B) UNIT_ASSERT((A) > (B))
+#define EXPECT_NO_THROW(A) UNIT_ASSERT_NO_EXCEPTION(A)
+#define EXPECT_THROW(A, B) UNIT_ASSERT_EXCEPTION(A, B)
+#define EXPECT_NEAR(A, B, D) UNIT_ASSERT_DOUBLES_EQUAL(A, B, D)
+#define EXPECT_STREQ(A, B) UNIT_ASSERT_VALUES_EQUAL(A, B)
+
+#define EXPECT_DOUBLE_EQ_TOLERANCE(A, B, tolerance) UNIT_ASSERT_C(fabs((A) - (B)) < tolerance * std::numeric_limits<decltype(A)>::epsilon(), TString("\n") + ToString(A) + " <> " + ToString(B))
+#define EXPECT_DOUBLE_EQ(A, B) EXPECT_DOUBLE_EQ_TOLERANCE(A, B, 4.0)
+
+//conflicts with util/system/defaults.h
+#undef EXPECT_TRUE
+#define EXPECT_TRUE(X) UNIT_ASSERT(X)
+#undef EXPECT_FALSE
+#define EXPECT_FALSE(X) UNIT_ASSERT(!(X))
+
+#define ASSERT_EQ(A, B) EXPECT_EQ(A, B)
+#define ASSERT_NE(A, B) EXPECT_NE(A, B)
+#define ASSERT_GT(A, B) EXPECT_GT(A, B)
+#define ASSERT_LT(A, B) EXPECT_LT(A, B)
+#define ASSERT_FALSE(X) EXPECT_FALSE(X)
+#define ASSERT_TRUE(X) EXPECT_TRUE(X)
+#define ASSERT_THROW(A, B) EXPECT_THROW(A, B)
+#define ASSERT_NO_THROW(A) EXPECT_NO_THROW(A)
+#define ASSERT_DOUBLE_EQ(A, B) EXPECT_DOUBLE_EQ(A, B)
+#define ASSERT_STREQ(A, B) EXPECT_STREQ(A, B)
diff --git a/library/cpp/testing/unittest/plugin.cpp b/library/cpp/testing/unittest/plugin.cpp
new file mode 100644
index 0000000000..543112f7ac
--- /dev/null
+++ b/library/cpp/testing/unittest/plugin.cpp
@@ -0,0 +1,50 @@
+#include "plugin.h"
+
+#include <util/generic/singleton.h>
+#include <util/generic/vector.h>
+#include <util/generic/utility.h>
+
+namespace NUnitTest {
+ namespace NPlugin {
+ namespace {
+ class TPlugins {
+ public:
+ void OnStartMain(int argc, char* argv[]) const {
+ for (const auto& plugin : Plugins) {
+ plugin->OnStartMain(argc, argv);
+ }
+ }
+
+ void OnStopMain(int argc, char* argv[]) const {
+ for (const auto& plugin : Plugins) {
+ plugin->OnStopMain(argc, argv);
+ }
+ }
+
+ void Register(TSimpleSharedPtr<IPlugin> plugin) {
+ Plugins.emplace_back(std::move(plugin));
+ }
+
+ static TPlugins& Instance() {
+ return *Singleton<TPlugins>();
+ }
+
+ private:
+ TVector<TSimpleSharedPtr<IPlugin>> Plugins;
+ };
+ } // anonymous namespace
+
+ TPluginRegistrator::TPluginRegistrator(TSimpleSharedPtr<IPlugin> plugin) {
+ TPlugins::Instance().Register(std::move(plugin));
+ }
+
+ void OnStartMain(int argc, char* argv[]) {
+ TPlugins::Instance().OnStartMain(argc, argv);
+ }
+
+ void OnStopMain(int argc, char* argv[]) {
+ TPlugins::Instance().OnStopMain(argc, argv);
+ }
+
+ }
+}
diff --git a/library/cpp/testing/unittest/plugin.h b/library/cpp/testing/unittest/plugin.h
new file mode 100644
index 0000000000..102f2c1469
--- /dev/null
+++ b/library/cpp/testing/unittest/plugin.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include <util/generic/ptr.h>
+
+namespace NUnitTest {
+ // Plugins are deprecated, please use Y_TEST_HOOK_* from library/cpp/hook/hook.h
+ namespace NPlugin {
+ class IPlugin {
+ public:
+ virtual ~IPlugin() {
+ }
+
+ virtual void OnStartMain(int /*argc*/, char* /*argv*/ []) {
+ }
+
+ virtual void OnStopMain(int /*argc*/, char* /*argv*/ []) {
+ }
+ };
+
+ void OnStartMain(int argc, char* argv[]);
+ void OnStopMain(int argc, char* argv[]);
+
+ class TPluginRegistrator {
+ public:
+ TPluginRegistrator(TSimpleSharedPtr<IPlugin> plugin);
+ };
+
+ }
+}
diff --git a/library/cpp/testing/unittest/registar.cpp b/library/cpp/testing/unittest/registar.cpp
new file mode 100644
index 0000000000..3679b768ed
--- /dev/null
+++ b/library/cpp/testing/unittest/registar.cpp
@@ -0,0 +1,513 @@
+#include "registar.h"
+
+#include <library/cpp/diff/diff.h>
+#include <library/cpp/colorizer/colors.h>
+
+#include <util/generic/bt_exception.h>
+#include <util/random/fast.h>
+#include <util/string/printf.h>
+#include <util/system/backtrace.h>
+#include <util/system/guard.h>
+#include <util/system/tls.h>
+#include <util/system/error.h>
+#include <util/string/cast.h>
+
+bool NUnitTest::ShouldColorizeDiff = true;
+bool NUnitTest::ContinueOnFail = false;
+
+TString NUnitTest::RandomString(size_t len, ui32 seed) {
+ TReallyFastRng32 rand(seed);
+ TString ret;
+
+ ret.reserve(len);
+
+ for (size_t i = 0; i < len; ++i) {
+ ret.push_back(char(rand.Uniform(1, 128)));
+ }
+
+ return ret;
+}
+
+Y_POD_STATIC_THREAD(bool)
+UnittestThread;
+Y_POD_STATIC_THREAD(NUnitTest::TTestBase*)
+currentTest;
+::NUnitTest::TRaiseErrorHandler RaiseErrorHandler;
+
+void ::NUnitTest::NPrivate::RaiseError(const char* what, const TString& msg, bool fatalFailure) {
+ Y_VERIFY(UnittestThread, "%s in non-unittest thread with message:\n%s", what, msg.data());
+ Y_VERIFY(GetCurrentTest());
+
+ if (RaiseErrorHandler) {
+ RaiseErrorHandler(what, msg, fatalFailure);
+ return;
+ }
+
+ // Default handler
+ TBackTrace bt;
+ bt.Capture();
+ GetCurrentTest()->AddError(msg.data(), bt.PrintToString());
+ if (::NUnitTest::ContinueOnFail || !fatalFailure) {
+ return;
+ }
+ throw TAssertException();
+}
+
+void ::NUnitTest::SetRaiseErrorHandler(::NUnitTest::TRaiseErrorHandler handler) {
+ Y_VERIFY(UnittestThread);
+ RaiseErrorHandler = std::move(handler);
+}
+
+void ::NUnitTest::NPrivate::SetUnittestThread(bool unittestThread) {
+ Y_VERIFY(UnittestThread != unittestThread, "state check");
+ UnittestThread = unittestThread;
+}
+
+void ::NUnitTest::NPrivate::SetCurrentTest(TTestBase* test) {
+ Y_VERIFY(!test || !currentTest, "state check");
+ currentTest = test;
+}
+
+NUnitTest::TTestBase* ::NUnitTest::NPrivate::GetCurrentTest() {
+ return currentTest;
+}
+
+struct TDiffColorizer {
+ NColorizer::TColors Colors;
+ bool Reverse = false;
+
+ explicit TDiffColorizer(bool reverse = false)
+ : Reverse(reverse)
+ {
+ }
+
+ TString Special(TStringBuf str) const {
+ return ToString(Colors.YellowColor()) + str;
+ }
+
+ TString Common(TArrayRef<const char> str) const {
+ return ToString(Colors.OldColor()) + TString(str.begin(), str.end());
+ }
+
+ TString Left(TArrayRef<const char> str) const {
+ return ToString(GetLeftColor()) + TString(str.begin(), str.end());
+ }
+
+ TString Right(TArrayRef<const char> str) const {
+ return ToString(GetRightColor()) + TString(str.begin(), str.end());
+ }
+
+ TStringBuf GetLeftColor() const {
+ return Reverse ? Colors.RedColor() : Colors.GreenColor();
+ }
+
+ TStringBuf GetRightColor() const {
+ return Reverse ? Colors.GreenColor() : Colors.RedColor();
+ }
+};
+
+struct TTraceDiffFormatter {
+ bool Reverse = false;
+
+ explicit TTraceDiffFormatter(bool reverse = false)
+ : Reverse(reverse)
+ {
+ }
+
+ TString Special(TStringBuf str) const {
+ return ToString(str);
+ }
+
+ TString Common(TArrayRef<const char> str) const {
+ return TString(str.begin(), str.end());
+ }
+
+ TString Left(TArrayRef<const char> str) const {
+ return NUnitTest::GetFormatTag("good") +
+ TString(str.begin(), str.end()) +
+ NUnitTest::GetResetTag();
+ }
+
+ TString Right(TArrayRef<const char> str) const {
+ return NUnitTest::GetFormatTag("bad") +
+ TString(str.begin(), str.end()) +
+ NUnitTest::GetResetTag();
+ }
+};
+
+TString NUnitTest::GetFormatTag(const char* name) {
+ return Sprintf("[[%s]]", name);
+}
+
+TString NUnitTest::GetResetTag() {
+ return TString("[[rst]]");
+}
+
+TString NUnitTest::ColoredDiff(TStringBuf s1, TStringBuf s2, const TString& delims, bool reverse) {
+ TStringStream res;
+ TVector<NDiff::TChunk<char>> chunks;
+ NDiff::InlineDiff(chunks, s1, s2, delims);
+ if (NUnitTest::ShouldColorizeDiff) {
+ NDiff::PrintChunks(res, TDiffColorizer(reverse), chunks);
+ } else {
+ res << NUnitTest::GetResetTag();
+ NDiff::PrintChunks(res, TTraceDiffFormatter(reverse), chunks);
+ }
+ return res.Str();
+}
+
+static TString MakeTestName(const NUnitTest::ITestSuiteProcessor::TTest& test) {
+ return TStringBuilder() << test.unit->name << "::" << test.name;
+}
+
+static size_t CountTests(const TMap<TString, size_t>& testErrors, bool succeeded) {
+ size_t cnt = 0;
+ for (const auto& t : testErrors) {
+ if (succeeded && t.second == 0) {
+ ++cnt;
+ } else if (!succeeded && t.second > 0) {
+ ++cnt;
+ }
+ }
+ return cnt;
+}
+
+NUnitTest::ITestSuiteProcessor::ITestSuiteProcessor() = default;
+
+NUnitTest::ITestSuiteProcessor::~ITestSuiteProcessor() = default;
+
+void NUnitTest::ITestSuiteProcessor::Start() {
+ OnStart();
+}
+
+void NUnitTest::ITestSuiteProcessor::End() {
+ OnEnd();
+}
+
+void NUnitTest::ITestSuiteProcessor::UnitStart(const TUnit& unit) {
+ CurTestErrors_.clear();
+
+ OnUnitStart(&unit);
+}
+
+void NUnitTest::ITestSuiteProcessor::UnitStop(const TUnit& unit) {
+ OnUnitStop(&unit);
+}
+
+void NUnitTest::ITestSuiteProcessor::Error(const TError& descr) {
+ AddTestError(*descr.test);
+
+ OnError(&descr);
+}
+
+void NUnitTest::ITestSuiteProcessor::BeforeTest(const TTest& test) {
+ OnBeforeTest(&test);
+}
+
+void NUnitTest::ITestSuiteProcessor::Finish(const TFinish& descr) {
+ AddTestFinish(*descr.test);
+
+ OnFinish(&descr);
+}
+
+unsigned NUnitTest::ITestSuiteProcessor::GoodTests() const noexcept {
+ return CountTests(TestErrors_, true);
+}
+
+unsigned NUnitTest::ITestSuiteProcessor::FailTests() const noexcept {
+ return CountTests(TestErrors_, false);
+}
+
+unsigned NUnitTest::ITestSuiteProcessor::GoodTestsInCurrentUnit() const noexcept {
+ return CountTests(CurTestErrors_, true);
+}
+
+unsigned NUnitTest::ITestSuiteProcessor::FailTestsInCurrentUnit() const noexcept {
+ return CountTests(CurTestErrors_, false);
+}
+
+bool NUnitTest::ITestSuiteProcessor::CheckAccess(TString /*name*/, size_t /*num*/) {
+ return true;
+}
+
+bool NUnitTest::ITestSuiteProcessor::CheckAccessTest(TString /*suite*/, const char* /*name*/) {
+ return true;
+}
+
+void NUnitTest::ITestSuiteProcessor::Run(std::function<void()> f, const TString& /*suite*/, const char* /*name*/, const bool /*forceFork*/) {
+ f();
+}
+
+bool NUnitTest::ITestSuiteProcessor::GetIsForked() const {
+ return false;
+}
+
+bool NUnitTest::ITestSuiteProcessor::GetForkTests() const {
+ return false;
+}
+
+void NUnitTest::ITestSuiteProcessor::OnStart() {
+}
+
+void NUnitTest::ITestSuiteProcessor::OnEnd() {
+}
+
+void NUnitTest::ITestSuiteProcessor::OnUnitStart(const TUnit* /*unit*/) {
+}
+
+void NUnitTest::ITestSuiteProcessor::OnUnitStop(const TUnit* /*unit*/) {
+}
+
+void NUnitTest::ITestSuiteProcessor::OnError(const TError* /*error*/) {
+}
+
+void NUnitTest::ITestSuiteProcessor::OnFinish(const TFinish* /*finish*/) {
+}
+
+void NUnitTest::ITestSuiteProcessor::OnBeforeTest(const TTest* /*test*/) {
+}
+
+void NUnitTest::ITestSuiteProcessor::AddTestError(const TTest& test) {
+ const TString name = MakeTestName(test);
+ ++TestErrors_[name];
+ ++CurTestErrors_[name];
+}
+
+void NUnitTest::ITestSuiteProcessor::AddTestFinish(const TTest& test) {
+ const TString name = MakeTestName(test);
+ TestErrors_[name]; // zero errors if not touched
+ CurTestErrors_[name]; // zero errors if not touched
+}
+
+NUnitTest::ITestBaseFactory::ITestBaseFactory() {
+ Register();
+}
+
+NUnitTest::ITestBaseFactory::~ITestBaseFactory() = default;
+
+void NUnitTest::ITestBaseFactory::Register() noexcept {
+ TTestFactory::Instance().Register(this);
+}
+
+NUnitTest::TTestBase::TTestBase() noexcept
+ : Parent_(nullptr)
+ , TestErrors_()
+ , CurrentSubtest_()
+{
+}
+
+NUnitTest::TTestBase::~TTestBase() = default;
+
+TString NUnitTest::TTestBase::TypeId() const {
+ return TypeName(*this);
+}
+
+void NUnitTest::TTestBase::SetUp() {
+}
+
+void NUnitTest::TTestBase::TearDown() {
+}
+
+void NUnitTest::TTestBase::AddError(const char* msg, const TString& backtrace, TTestContext* context) {
+ ++TestErrors_;
+ const NUnitTest::ITestSuiteProcessor::TUnit unit = {Name()};
+ const NUnitTest::ITestSuiteProcessor::TTest test = {&unit, CurrentSubtest_};
+ const NUnitTest::ITestSuiteProcessor::TError err = {&test, msg, backtrace, context};
+
+ Processor()->Error(err);
+}
+
+void NUnitTest::TTestBase::AddError(const char* msg, TTestContext* context) {
+ AddError(msg, TString(), context);
+}
+
+void NUnitTest::TTestBase::RunAfterTest(std::function<void()> f) {
+ with_lock (AfterTestFunctionsLock_) {
+ AfterTestFunctions_.emplace_back(std::move(f));
+ }
+}
+
+bool NUnitTest::TTestBase::CheckAccessTest(const char* test) {
+ return Processor()->CheckAccessTest(Name(), test);
+}
+
+void NUnitTest::TTestBase::BeforeTest(const char* func) {
+ const NUnitTest::ITestSuiteProcessor::TUnit unit = {Name()};
+ const NUnitTest::ITestSuiteProcessor::TTest test = {&unit, func};
+ rusage.Fill();
+ Processor()->BeforeTest(test);
+}
+
+void NUnitTest::TTestBase::Finish(const char* func, TTestContext* context) {
+ TRusage finishRusage = TRusage::Get();
+ context->Metrics["ru_rss"] = finishRusage.MaxRss - rusage.MaxRss;
+ context->Metrics["ru_major_pagefaults"] = finishRusage.MajorPageFaults - rusage.MajorPageFaults;
+ context->Metrics["ru_utime"] = (finishRusage.Utime - rusage.Utime).MicroSeconds();
+ context->Metrics["ru_stime"] = (finishRusage.Stime - rusage.Stime).MicroSeconds();
+
+ const NUnitTest::ITestSuiteProcessor::TUnit unit = {Name()};
+ const NUnitTest::ITestSuiteProcessor::TTest test = {&unit, func};
+ const NUnitTest::ITestSuiteProcessor::TFinish finish = {&test, context, TestErrors_ == 0};
+
+ Processor()->Finish(finish);
+}
+
+void NUnitTest::TTestBase::AtStart() {
+ const NUnitTest::ITestSuiteProcessor::TUnit unit = {Name()};
+
+ Processor()->UnitStart(unit);
+}
+
+void NUnitTest::TTestBase::AtEnd() {
+ const NUnitTest::ITestSuiteProcessor::TUnit unit = {Name()};
+
+ Processor()->UnitStop(unit);
+}
+
+void NUnitTest::TTestBase::Run(std::function<void()> f, const TString& suite, const char* name, const bool forceFork) {
+ TestErrors_ = 0;
+ CurrentSubtest_ = name;
+ Processor()->Run(f, suite, name, forceFork);
+}
+
+void NUnitTest::TTestBase::BeforeTest() {
+ SetUp();
+}
+
+void NUnitTest::TTestBase::AfterTest() {
+ TearDown();
+
+ TVector<std::function<void()>> afterTestFunctions;
+ with_lock (AfterTestFunctionsLock_) {
+ afterTestFunctions.swap(AfterTestFunctions_);
+ }
+
+ for (auto i = afterTestFunctions.rbegin(); i != afterTestFunctions.rend(); ++i) {
+ std::function<void()>& f = *i;
+ if (f) {
+ f();
+ }
+ }
+}
+
+bool NUnitTest::TTestBase::GetIsForked() const {
+ return Processor()->GetIsForked();
+}
+
+bool NUnitTest::TTestBase::GetForkTests() const {
+ return Processor()->GetForkTests();
+}
+
+NUnitTest::ITestSuiteProcessor* NUnitTest::TTestBase::Processor() const noexcept {
+ return Parent_->Processor();
+}
+
+NUnitTest::TTestBase::TCleanUp::TCleanUp(TTestBase* base)
+ : Base_(base)
+{
+ ::NUnitTest::NPrivate::SetCurrentTest(base);
+ ::NUnitTest::NPrivate::SetUnittestThread(true);
+ Base_->BeforeTest();
+}
+
+NUnitTest::TTestBase::TCleanUp::~TCleanUp() {
+ try {
+ Base_->AfterTest();
+ } catch (...) {
+ Base_->AddError(CurrentExceptionMessage().data());
+ }
+ ::NUnitTest::NPrivate::SetUnittestThread(false);
+ ::NUnitTest::NPrivate::SetCurrentTest(nullptr);
+}
+
+namespace {
+ /*
+ * by default do nothing
+ */
+ class TCommonProcessor: public NUnitTest::ITestSuiteProcessor {
+ public:
+ TCommonProcessor() = default;
+
+ ~TCommonProcessor() override = default;
+ };
+
+ struct TCmp {
+ template <class T>
+ inline bool operator()(const T& l, const T& r) const noexcept {
+ return stricmp(Fix(l.Name().data()), Fix(r.Name().data())) < 0;
+ }
+
+ static inline const char* Fix(const char* n) noexcept {
+ if (*n == 'T') {
+ return n + 1;
+ }
+
+ return n;
+ }
+ };
+}
+
+NUnitTest::TTestFactory::TTestFactory(ITestSuiteProcessor* processor)
+ : Processor_(processor)
+{
+}
+
+NUnitTest::TTestFactory::~TTestFactory() = default;
+
+NUnitTest::TTestFactory& NUnitTest::TTestFactory::Instance() {
+ static TCommonProcessor p;
+ static TTestFactory f(&p);
+
+ return f;
+}
+
+unsigned NUnitTest::TTestFactory::Execute() {
+ Items_.QuickSort(TCmp());
+ Processor_->Start();
+
+ TSet<TString> types;
+ size_t cnt = 0;
+
+ for (TIntrusiveList<ITestBaseFactory>::TIterator factory = Items_.Begin(); factory != Items_.End(); ++factory) {
+ if (!Processor_->CheckAccess(factory->Name(), cnt++)) {
+ continue;
+ }
+
+ THolder<TTestBase> test(factory->ConstructTest());
+
+#ifdef _unix_ // on Windows RTTI causes memory leaks
+ TString type = test->TypeId();
+ if (types.insert(type).second == false) {
+ warnx("Duplicate suite found: %s (%s). Probably you have copy-pasted suite without changing it name", factory->Name().c_str(), type.c_str());
+ return 1;
+ }
+#endif // _unix_
+
+ test->Parent_ = this;
+
+#ifdef UT_SKIP_EXCEPTIONS
+ try {
+#endif
+ test->Execute();
+#ifdef UT_SKIP_EXCEPTIONS
+ } catch (...) {
+ }
+#endif
+ }
+
+ Processor_->End();
+
+ return bool(Processor_->FailTests());
+}
+
+void NUnitTest::TTestFactory::SetProcessor(ITestSuiteProcessor* processor) {
+ Processor_ = processor;
+}
+
+void NUnitTest::TTestFactory::Register(ITestBaseFactory* b) noexcept {
+ Items_.PushBack(b);
+}
+
+NUnitTest::ITestSuiteProcessor* NUnitTest::TTestFactory::Processor() const noexcept {
+ return Processor_;
+}
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;
diff --git a/library/cpp/testing/unittest/registar_ut.cpp b/library/cpp/testing/unittest/registar_ut.cpp
new file mode 100644
index 0000000000..aada471030
--- /dev/null
+++ b/library/cpp/testing/unittest/registar_ut.cpp
@@ -0,0 +1,360 @@
+#include <library/cpp/testing/unittest/registar.h>
+
+Y_UNIT_TEST_SUITE(TUnitTestMacroTest) {
+ Y_UNIT_TEST(Assert) {
+ auto unitAssert = [] {
+ UNIT_ASSERT(false);
+ };
+ UNIT_ASSERT_TEST_FAILS(unitAssert());
+
+ UNIT_ASSERT(true);
+ }
+
+ Y_UNIT_TEST(TypesEqual) {
+ auto typesEqual = [] {
+ UNIT_ASSERT_TYPES_EQUAL(int, long);
+ };
+ UNIT_ASSERT_TEST_FAILS(typesEqual());
+
+ UNIT_ASSERT_TYPES_EQUAL(TString, TString);
+ }
+
+ Y_UNIT_TEST(DoublesEqual) {
+ auto doublesEqual = [](double d1, double d2, double precision) {
+ UNIT_ASSERT_DOUBLES_EQUAL(d1, d2, precision);
+ };
+ UNIT_ASSERT_TEST_FAILS(doublesEqual(0.0, 0.5, 0.1));
+ UNIT_ASSERT_TEST_FAILS(doublesEqual(0.1, -0.1, 0.1));
+
+ UNIT_ASSERT_DOUBLES_EQUAL(0.0, 0.01, 0.1);
+ UNIT_ASSERT_DOUBLES_EQUAL(0.01, 0.0, 0.1);
+
+ constexpr auto nan = std::numeric_limits<double>::quiet_NaN();
+ UNIT_ASSERT_TEST_FAILS(doublesEqual(nan, 0.5, 0.1));
+ UNIT_ASSERT_TEST_FAILS(doublesEqual(0.5, nan, 0.1));
+ UNIT_ASSERT_DOUBLES_EQUAL(nan, nan, 0.1);
+ }
+
+ Y_UNIT_TEST(StringsEqual) {
+ auto stringsEqual = [](auto s1, auto s2) {
+ UNIT_ASSERT_STRINGS_EQUAL(s1, s2);
+ };
+ UNIT_ASSERT_TEST_FAILS(stringsEqual("q", "w"));
+ UNIT_ASSERT_TEST_FAILS(stringsEqual("q", TString("w")));
+ UNIT_ASSERT_TEST_FAILS(stringsEqual(TString("q"), "w"));
+ UNIT_ASSERT_TEST_FAILS(stringsEqual(TString("a"), TString("b")));
+ UNIT_ASSERT_TEST_FAILS(stringsEqual(TString("a"), TStringBuf("b")));
+ UNIT_ASSERT_TEST_FAILS(stringsEqual("a", TStringBuf("b")));
+ UNIT_ASSERT_TEST_FAILS(stringsEqual(TStringBuf("a"), "b"));
+
+ TString empty;
+ TStringBuf emptyBuf;
+ UNIT_ASSERT_STRINGS_EQUAL("", empty);
+ UNIT_ASSERT_STRINGS_EQUAL(empty, emptyBuf);
+ UNIT_ASSERT_STRINGS_EQUAL("", static_cast<const char*>(nullptr));
+ }
+
+ Y_UNIT_TEST(StringContains) {
+ auto stringContains = [](auto s, auto substr) {
+ UNIT_ASSERT_STRING_CONTAINS(s, substr);
+ };
+ UNIT_ASSERT_TEST_FAILS(stringContains("", "a"));
+ UNIT_ASSERT_TEST_FAILS(stringContains("lurkmore", "moar"));
+
+ UNIT_ASSERT_STRING_CONTAINS("", "");
+ UNIT_ASSERT_STRING_CONTAINS("a", "");
+ UNIT_ASSERT_STRING_CONTAINS("failure", "fail");
+ UNIT_ASSERT_STRING_CONTAINS("lurkmore", "more");
+ }
+
+ Y_UNIT_TEST(NoDiff) {
+ auto noDiff = [](auto s1, auto s2) {
+ UNIT_ASSERT_NO_DIFF(s1, s2);
+ };
+ UNIT_ASSERT_TEST_FAILS(noDiff("q", "w"));
+ UNIT_ASSERT_TEST_FAILS(noDiff("q", ""));
+
+ UNIT_ASSERT_NO_DIFF("", "");
+ UNIT_ASSERT_NO_DIFF("a", "a");
+ }
+
+ Y_UNIT_TEST(StringsUnequal) {
+ auto stringsUnequal = [](auto s1, auto s2) {
+ UNIT_ASSERT_STRINGS_UNEQUAL(s1, s2);
+ };
+ UNIT_ASSERT_TEST_FAILS(stringsUnequal("1", "1"));
+ UNIT_ASSERT_TEST_FAILS(stringsUnequal("", ""));
+ UNIT_ASSERT_TEST_FAILS(stringsUnequal("42", TString("42")));
+ UNIT_ASSERT_TEST_FAILS(stringsUnequal(TString("4"), "4"));
+ UNIT_ASSERT_TEST_FAILS(stringsUnequal("d", TStringBuf("d")));
+ UNIT_ASSERT_TEST_FAILS(stringsUnequal(TStringBuf("yandex"), "yandex"));
+ UNIT_ASSERT_TEST_FAILS(stringsUnequal(TStringBuf("index"), TString("index")));
+ UNIT_ASSERT_TEST_FAILS(stringsUnequal(TString("diff"), TStringBuf("diff")));
+
+ UNIT_ASSERT_STRINGS_UNEQUAL("1", "2");
+ UNIT_ASSERT_STRINGS_UNEQUAL("", "3");
+ UNIT_ASSERT_STRINGS_UNEQUAL("green", TStringBuf("red"));
+ UNIT_ASSERT_STRINGS_UNEQUAL(TStringBuf("solomon"), "golovan");
+ UNIT_ASSERT_STRINGS_UNEQUAL("d", TString("f"));
+ UNIT_ASSERT_STRINGS_UNEQUAL(TString("yandex"), "index");
+ UNIT_ASSERT_STRINGS_UNEQUAL(TString("mail"), TStringBuf("yandex"));
+ UNIT_ASSERT_STRINGS_UNEQUAL(TStringBuf("C++"), TString("python"));
+ }
+
+ Y_UNIT_TEST(Equal) {
+ auto equal = [](auto v1, auto v2) {
+ UNIT_ASSERT_EQUAL(v1, v2);
+ };
+ UNIT_ASSERT_TEST_FAILS(equal("1", TString("2")));
+ UNIT_ASSERT_TEST_FAILS(equal(1, 2));
+ UNIT_ASSERT_TEST_FAILS(equal(42ul, static_cast<unsigned short>(24)));
+
+ UNIT_ASSERT_EQUAL("abc", TString("abc"));
+ UNIT_ASSERT_EQUAL(12l, 12);
+ UNIT_ASSERT_EQUAL(55, 55);
+ }
+
+ Y_UNIT_TEST(Unequal) {
+ auto unequal = [](auto v1, auto v2) {
+ UNIT_ASSERT_UNEQUAL(v1, v2);
+ };
+ UNIT_ASSERT_TEST_FAILS(unequal("x", TString("x")));
+ UNIT_ASSERT_TEST_FAILS(unequal(1, 1));
+ UNIT_ASSERT_TEST_FAILS(unequal(static_cast<unsigned short>(42), 42ul));
+
+ UNIT_ASSERT_UNEQUAL("abc", TString("cba"));
+ UNIT_ASSERT_UNEQUAL(12l, 10);
+ UNIT_ASSERT_UNEQUAL(33, 50);
+ }
+
+ Y_UNIT_TEST(LessThan) {
+ auto lt = [](auto v1, auto v2) {
+ UNIT_ASSERT_LT(v1, v2);
+ };
+
+ // less than
+ UNIT_ASSERT_LT(TStringBuf("1"), "2");
+ UNIT_ASSERT_LT("2", TString("3"));
+ UNIT_ASSERT_LT("abc", TString("azz"));
+ UNIT_ASSERT_LT(2, 4);
+ UNIT_ASSERT_LT(42ul, static_cast<unsigned short>(48));
+
+ // equals
+ UNIT_ASSERT_TEST_FAILS(lt(TStringBuf("2"), "2"));
+ UNIT_ASSERT_TEST_FAILS(lt("2", TString("2")));
+ UNIT_ASSERT_TEST_FAILS(lt("abc", TString("abc")));
+ UNIT_ASSERT_TEST_FAILS(lt(2, 2));
+ UNIT_ASSERT_TEST_FAILS(lt(42ul, static_cast<unsigned short>(42)));
+
+ // greater than
+ UNIT_ASSERT_TEST_FAILS(lt(TStringBuf("2"), "1"));
+ UNIT_ASSERT_TEST_FAILS(lt("3", TString("2")));
+ UNIT_ASSERT_TEST_FAILS(lt("azz", TString("abc")));
+ UNIT_ASSERT_TEST_FAILS(lt(5, 2));
+ UNIT_ASSERT_TEST_FAILS(lt(100ul, static_cast<unsigned short>(42)));
+ }
+
+ Y_UNIT_TEST(LessOrEqual) {
+ auto le = [](auto v1, auto v2) {
+ UNIT_ASSERT_LE(v1, v2);
+ };
+
+ // less than
+ UNIT_ASSERT_LE(TStringBuf("1"), "2");
+ UNIT_ASSERT_LE("2", TString("3"));
+ UNIT_ASSERT_LE("abc", TString("azz"));
+ UNIT_ASSERT_LE(2, 4);
+ UNIT_ASSERT_LE(42ul, static_cast<unsigned short>(48));
+
+ // equals
+ UNIT_ASSERT_LE(TStringBuf("2"), "2");
+ UNIT_ASSERT_LE("2", TString("2"));
+ UNIT_ASSERT_LE("abc", TString("abc"));
+ UNIT_ASSERT_LE(2, 2);
+ UNIT_ASSERT_LE(42ul, static_cast<unsigned short>(42));
+
+ // greater than
+ UNIT_ASSERT_TEST_FAILS(le(TStringBuf("2"), "1"));
+ UNIT_ASSERT_TEST_FAILS(le("3", TString("2")));
+ UNIT_ASSERT_TEST_FAILS(le("azz", TString("abc")));
+ UNIT_ASSERT_TEST_FAILS(le(5, 2));
+ UNIT_ASSERT_TEST_FAILS(le(100ul, static_cast<unsigned short>(42)));
+ }
+
+ Y_UNIT_TEST(GreaterThan) {
+ auto gt = [](auto v1, auto v2) {
+ UNIT_ASSERT_GT(v1, v2);
+ };
+
+ // less than
+ UNIT_ASSERT_TEST_FAILS(gt(TStringBuf("1"), "2"));
+ UNIT_ASSERT_TEST_FAILS(gt("2", TString("3")));
+ UNIT_ASSERT_TEST_FAILS(gt("abc", TString("azz")));
+ UNIT_ASSERT_TEST_FAILS(gt(2, 4));
+ UNIT_ASSERT_TEST_FAILS(gt(42ul, static_cast<unsigned short>(48)));
+
+ // equals
+ UNIT_ASSERT_TEST_FAILS(gt(TStringBuf("2"), "2"));
+ UNIT_ASSERT_TEST_FAILS(gt("2", TString("2")));
+ UNIT_ASSERT_TEST_FAILS(gt("abc", TString("abc")));
+ UNIT_ASSERT_TEST_FAILS(gt(2, 2));
+ UNIT_ASSERT_TEST_FAILS(gt(42ul, static_cast<unsigned short>(42)));
+
+ // greater than
+ UNIT_ASSERT_GT(TStringBuf("2"), "1");
+ UNIT_ASSERT_GT("3", TString("2"));
+ UNIT_ASSERT_GT("azz", TString("abc"));
+ UNIT_ASSERT_GT(5, 2);
+ UNIT_ASSERT_GT(100ul, static_cast<unsigned short>(42));
+ }
+
+ Y_UNIT_TEST(GreaterOrEqual) {
+ auto ge = [](auto v1, auto v2) {
+ UNIT_ASSERT_GE(v1, v2);
+ };
+
+ // less than
+ UNIT_ASSERT_TEST_FAILS(ge(TStringBuf("1"), "2"));
+ UNIT_ASSERT_TEST_FAILS(ge("2", TString("3")));
+ UNIT_ASSERT_TEST_FAILS(ge("abc", TString("azz")));
+ UNIT_ASSERT_TEST_FAILS(ge(2, 4));
+ UNIT_ASSERT_TEST_FAILS(ge(42ul, static_cast<unsigned short>(48)));
+
+ // equals
+ UNIT_ASSERT_GE(TStringBuf("2"), "2");
+ UNIT_ASSERT_GE("2", TString("2"));
+ UNIT_ASSERT_GE("abc", TString("abc"));
+ UNIT_ASSERT_GE(2, 2);
+ UNIT_ASSERT_GE(42ul, static_cast<unsigned short>(42));
+
+ // greater than
+ UNIT_ASSERT_GE(TStringBuf("2"), "1");
+ UNIT_ASSERT_GE("3", TString("2"));
+ UNIT_ASSERT_GE("azz", TString("abc"));
+ UNIT_ASSERT_GE(5, 2);
+ UNIT_ASSERT_GE(100ul, static_cast<unsigned short>(42));
+ }
+
+ Y_UNIT_TEST(ValuesEqual) {
+ auto valuesEqual = [](auto v1, auto v2) {
+ UNIT_ASSERT_VALUES_EQUAL(v1, v2);
+ };
+ UNIT_ASSERT_TEST_FAILS(valuesEqual(1, 2));
+ UNIT_ASSERT_TEST_FAILS(valuesEqual(1l, static_cast<short>(2)));
+
+ UNIT_ASSERT_VALUES_EQUAL("yandex", TString("yandex"));
+ UNIT_ASSERT_VALUES_EQUAL(1.0, 1.0);
+ }
+
+ Y_UNIT_TEST(ValuesUnequal) {
+ auto valuesUnequal = [](auto v1, auto v2) {
+ UNIT_ASSERT_VALUES_UNEQUAL(v1, v2);
+ };
+ UNIT_ASSERT_TEST_FAILS(valuesUnequal(5, 5));
+ UNIT_ASSERT_TEST_FAILS(valuesUnequal(static_cast<char>(5), 5l));
+ TString test("test");
+ UNIT_ASSERT_TEST_FAILS(valuesUnequal("test", test.data()));
+
+ UNIT_ASSERT_VALUES_UNEQUAL("UNIT_ASSERT_VALUES_UNEQUAL", "UNIT_ASSERT_VALUES_EQUAL");
+ UNIT_ASSERT_VALUES_UNEQUAL(1.0, 1.1);
+ }
+
+ class TTestException: public yexception {
+ public:
+ TTestException(const TString& text = "test exception", bool throwMe = true)
+ : ThrowMe(throwMe)
+ {
+ *this << text;
+ }
+
+ virtual ~TTestException() = default;
+
+ virtual void Throw() {
+ if (ThrowMe) {
+ throw *this;
+ }
+ }
+
+ void AssertNoException() {
+ UNIT_ASSERT_NO_EXCEPTION(Throw());
+ }
+
+ template <class TExpectedException>
+ void AssertException() {
+ UNIT_ASSERT_EXCEPTION(Throw(), TExpectedException);
+ }
+
+ template <class TExpectedException, class T>
+ void AssertExceptionContains(const T& substr) {
+ UNIT_ASSERT_EXCEPTION_CONTAINS(Throw(), TExpectedException, substr);
+ }
+
+ template <class TExpectedException, class P>
+ void AssertExceptionSatisfies(const P& predicate) {
+ UNIT_ASSERT_EXCEPTION_SATISFIES(Throw(), TExpectedException, predicate);
+ }
+
+ int GetValue() const {
+ return 5; // just some value for predicate testing
+ }
+
+ bool ThrowMe;
+ };
+
+ class TOtherTestException: public TTestException {
+ public:
+ using TTestException::TTestException;
+
+ // Throws other type of exception
+ void Throw() override {
+ if (ThrowMe) {
+ throw *this;
+ }
+ }
+ };
+
+ Y_UNIT_TEST(Exception) {
+ UNIT_ASSERT_TEST_FAILS(TTestException("", false).AssertException<TTestException>());
+ UNIT_ASSERT_TEST_FAILS(TTestException().AssertException<TOtherTestException>());
+
+ UNIT_ASSERT_EXCEPTION(TOtherTestException().Throw(), TTestException);
+ UNIT_ASSERT_EXCEPTION(TTestException().Throw(), TTestException);
+ }
+
+ Y_UNIT_TEST(ExceptionAssertionContainsOtherExceptionMessage) {
+ NUnitTest::TUnitTestFailChecker checker;
+ {
+ auto guard = checker.InvokeGuard();
+ TTestException("custom exception message").AssertException<TOtherTestException>();
+ }
+ UNIT_ASSERT(checker.Failed());
+ UNIT_ASSERT_STRING_CONTAINS(checker.Msg(), "custom exception message");
+ }
+
+ Y_UNIT_TEST(NoException) {
+ UNIT_ASSERT_TEST_FAILS(TTestException().AssertNoException());
+
+ UNIT_ASSERT_NO_EXCEPTION(TTestException("", false).Throw());
+ }
+
+ Y_UNIT_TEST(ExceptionContains) {
+ UNIT_ASSERT_TEST_FAILS(TTestException("abc").AssertExceptionContains<TTestException>("cba"));
+ UNIT_ASSERT_TEST_FAILS(TTestException("abc").AssertExceptionContains<TTestException>(TStringBuf("cba")));
+ UNIT_ASSERT_TEST_FAILS(TTestException("abc").AssertExceptionContains<TTestException>(TString("cba")));
+ UNIT_ASSERT_TEST_FAILS(TTestException("abc").AssertExceptionContains<TTestException>(TStringBuilder() << "cba"));
+
+ UNIT_ASSERT_TEST_FAILS(TTestException("abc", false).AssertExceptionContains<TTestException>("bc"));
+
+ UNIT_ASSERT_TEST_FAILS(TTestException("abc").AssertExceptionContains<TOtherTestException>("b"));
+
+ UNIT_ASSERT_EXCEPTION_CONTAINS(TTestException("abc").Throw(), TTestException, "a");
+ }
+
+ Y_UNIT_TEST(ExceptionSatisfies) {
+ const auto goodPredicate = [](const TTestException& e) { return e.GetValue() == 5; };
+ const auto badPredicate = [](const TTestException& e) { return e.GetValue() != 5; };
+ UNIT_ASSERT_NO_EXCEPTION(TTestException().AssertExceptionSatisfies<TTestException>(goodPredicate));
+ UNIT_ASSERT_TEST_FAILS(TTestException().AssertExceptionSatisfies<TTestException>(badPredicate));
+ UNIT_ASSERT_TEST_FAILS(TTestException().AssertExceptionSatisfies<TOtherTestException>(goodPredicate));
+ }
+}
diff --git a/library/cpp/testing/unittest/simple.h b/library/cpp/testing/unittest/simple.h
new file mode 100644
index 0000000000..2a5300d886
--- /dev/null
+++ b/library/cpp/testing/unittest/simple.h
@@ -0,0 +1,39 @@
+#pragma once
+
+#include "registar.h"
+
+namespace NUnitTest {
+ struct TSimpleTestExecutor: public TTestBase {
+ typedef TVector<TBaseTestCase> TTests;
+
+ TTests Tests;
+
+ virtual void Execute() override final {
+ AtStart();
+
+ for (typename TTests::iterator i = Tests.begin(), ie = Tests.end(); i != ie; ++i) {
+ if (!CheckAccessTest(i->Name_)) {
+ continue;
+ }
+ TTestContext context(this->Processor());
+ try {
+ BeforeTest(i->Name_);
+ {
+ TCleanUp cleaner(this);
+ TTestBase::Run([i, &context] { i->Body_(context); }, Name(), 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 (...) {
+ AddError("non-std exception!", &context);
+ }
+ Finish(i->Name_, &context);
+ }
+
+ AtEnd();
+ }
+ };
+}
diff --git a/library/cpp/testing/unittest/tests_data.cpp b/library/cpp/testing/unittest/tests_data.cpp
new file mode 100644
index 0000000000..b51cbc4b87
--- /dev/null
+++ b/library/cpp/testing/unittest/tests_data.cpp
@@ -0,0 +1,104 @@
+#include "tests_data.h"
+#include "registar.h"
+
+#include <library/cpp/testing/common/network.h>
+
+#include <util/system/env.h>
+#include <util/system/mutex.h>
+
+class TPortManager::TPortManagerImpl {
+public:
+ TPortManagerImpl(bool reservePortsForCurrentTest)
+ : EnableReservePortsForCurrentTest(reservePortsForCurrentTest)
+ , DisableRandomPorts(!GetEnv("NO_RANDOM_PORTS").empty())
+ {
+ }
+
+ ui16 GetPort(ui16 port) {
+ if (port && DisableRandomPorts) {
+ return port;
+ }
+
+ TAtomicSharedPtr<NTesting::IPort> holder(NTesting::GetFreePort().Release());
+ ReservePortForCurrentTest(holder);
+
+ TGuard<TMutex> g(Lock);
+ ReservedPorts.push_back(holder);
+ return holder->Get();
+ }
+
+ ui16 GetUdpPort(ui16 port) {
+ return GetPort(port);
+ }
+
+ ui16 GetTcpPort(ui16 port) {
+ return GetPort(port);
+ }
+
+ ui16 GetTcpAndUdpPort(ui16 port) {
+ return GetPort(port);
+ }
+
+ ui16 GetPortsRange(const ui16 startPort, const ui16 range) {
+ Y_UNUSED(startPort);
+ auto ports = NTesting::NLegacy::GetFreePortsRange(range);
+ ui16 first = ports[0];
+ TGuard<TMutex> g(Lock);
+ for (auto& port : ports) {
+ ReservedPorts.emplace_back(port.Release());
+ ReservePortForCurrentTest(ReservedPorts.back());
+ }
+ return first;
+ }
+
+private:
+ void ReservePortForCurrentTest(const TAtomicSharedPtr<NTesting::IPort>& portGuard) {
+ if (EnableReservePortsForCurrentTest) {
+ TTestBase* currentTest = NUnitTest::NPrivate::GetCurrentTest();
+ if (currentTest != nullptr) {
+ currentTest->RunAfterTest([guard = portGuard]() mutable {
+ guard = nullptr; // remove reference for allocated port
+ });
+ }
+ }
+ }
+
+private:
+ TMutex Lock;
+ TVector<TAtomicSharedPtr<NTesting::IPort>> ReservedPorts;
+ const bool EnableReservePortsForCurrentTest;
+ const bool DisableRandomPorts;
+};
+
+TPortManager::TPortManager(bool reservePortsForCurrentTest)
+ : Impl_(new TPortManagerImpl(reservePortsForCurrentTest))
+{
+}
+
+TPortManager::~TPortManager() {
+}
+
+ui16 TPortManager::GetPort(ui16 port) {
+ return Impl_->GetTcpPort(port);
+}
+
+ui16 TPortManager::GetTcpPort(ui16 port) {
+ return Impl_->GetTcpPort(port);
+}
+
+ui16 TPortManager::GetUdpPort(ui16 port) {
+ return Impl_->GetUdpPort(port);
+}
+
+ui16 TPortManager::GetTcpAndUdpPort(ui16 port) {
+ return Impl_->GetTcpAndUdpPort(port);
+}
+
+ui16 TPortManager::GetPortsRange(const ui16 startPort, const ui16 range) {
+ return Impl_->GetPortsRange(startPort, range);
+}
+
+ui16 GetRandomPort() {
+ TPortManager* pm = Singleton<TPortManager>(false);
+ return pm->GetPort();
+}
diff --git a/library/cpp/testing/unittest/tests_data.h b/library/cpp/testing/unittest/tests_data.h
new file mode 100644
index 0000000000..6536bc1ae6
--- /dev/null
+++ b/library/cpp/testing/unittest/tests_data.h
@@ -0,0 +1,54 @@
+#pragma once
+
+#include <library/cpp/testing/common/env.h>
+
+#include <util/generic/noncopyable.h>
+#include <util/generic/ptr.h>
+#include <util/generic/string.h>
+#include <util/network/sock.h>
+
+class TInet6StreamSocket;
+
+// set two options: SO_REUSEADDR and SO_REUSEPORT, both are required for
+// correct implementation of TPortManager because of different operating systems
+// incompatibility: singe SO_REUSEADDR is enough for Linux, but not enough for Darwin
+template <class TSocketType>
+void SetReuseAddressAndPort(const TSocketType& sock) {
+ const int retAddr = SetSockOpt(sock, SOL_SOCKET, SO_REUSEADDR, 1);
+ if (retAddr < 0) {
+ ythrow yexception() << "can't set SO_REUSEADDR: " << LastSystemErrorText(-retAddr);
+ }
+
+#ifdef SO_REUSEPORT
+ const int retPort = SetSockOpt(sock, SOL_SOCKET, SO_REUSEPORT, 1);
+ if (retPort < 0) {
+ ythrow yexception() << "can't set SO_REUSEPORT: " << LastSystemErrorText(-retPort);
+ }
+#endif
+}
+
+class TPortManager: public TNonCopyable {
+public:
+ TPortManager(bool reservePortsForCurrentTest = true);
+ ~TPortManager();
+
+ // Gets free TCP port
+ ui16 GetPort(ui16 port = 0);
+
+ // Gets free TCP port
+ ui16 GetTcpPort(ui16 port = 0);
+
+ // Gets free UDP port
+ ui16 GetUdpPort(ui16 port = 0);
+
+ // Gets one free port for use in both TCP and UDP protocols
+ ui16 GetTcpAndUdpPort(ui16 port = 0);
+
+ ui16 GetPortsRange(const ui16 startPort, const ui16 range);
+
+private:
+ class TPortManagerImpl;
+ THolder<TPortManagerImpl> Impl_;
+};
+
+ui16 GetRandomPort();
diff --git a/library/cpp/testing/unittest/ut/main.cpp b/library/cpp/testing/unittest/ut/main.cpp
new file mode 100644
index 0000000000..e303e21e30
--- /dev/null
+++ b/library/cpp/testing/unittest/ut/main.cpp
@@ -0,0 +1,93 @@
+#include <library/cpp/testing/unittest/gtest.h>
+#include <library/cpp/testing/unittest/registar.h>
+#include <library/cpp/testing/unittest/tests_data.h>
+
+#include <util/generic/set.h>
+#include <util/network/sock.h>
+#include <util/system/env.h>
+#include <util/system/fs.h>
+
+TEST(GTest, Test1) {
+ UNIT_ASSERT_EQUAL(1, 1);
+}
+
+TEST(GTest, Test2) {
+ UNIT_ASSERT_EQUAL(2, 2);
+}
+
+namespace {
+ struct TFixture : ::testing::Test {
+ TFixture()
+ : I(0)
+ {
+ }
+
+ void SetUp() override {
+ I = 5;
+ }
+
+ int I;
+ };
+
+ struct TSimpleFixture : public NUnitTest::TBaseFixture {
+ size_t Value = 24;
+ };
+
+ struct TOtherFixture : public NUnitTest::TBaseFixture {
+ size_t TheAnswer = 42;
+ };
+
+ struct TSetUpTearDownFixture : public NUnitTest::TBaseFixture {
+ int Magic = 3;
+
+ void SetUp(NUnitTest::TTestContext&) override {
+ UNIT_ASSERT_VALUES_EQUAL(Magic, 3);
+ Magic = 17;
+ }
+
+ void TearDown(NUnitTest::TTestContext&) override {
+ UNIT_ASSERT_VALUES_EQUAL(Magic, 42);
+ Magic = 100;
+ }
+ };
+}
+
+TEST_F(TFixture, Test1) {
+ ASSERT_EQ(I, 5);
+}
+
+TEST(ETest, Test1) {
+ UNIT_CHECK_GENERATED_EXCEPTION(ythrow yexception(), yexception);
+ UNIT_CHECK_GENERATED_NO_EXCEPTION(true, yexception);
+}
+
+Y_UNIT_TEST_SUITE(TestSingleTestFixture)
+{
+ Y_UNIT_TEST_F(Test3, TSimpleFixture) {
+ UNIT_ASSERT_EQUAL(Value, 24);
+ }
+}
+
+Y_UNIT_TEST_SUITE_F(TestSuiteFixture, TSimpleFixture)
+{
+ Y_UNIT_TEST(Test1) {
+ UNIT_ASSERT(Value == 24);
+ Value = 25;
+ }
+
+ Y_UNIT_TEST(Test2) {
+ UNIT_ASSERT_EQUAL(Value, 24);
+ }
+
+ Y_UNIT_TEST_F(Test3, TOtherFixture) {
+ UNIT_ASSERT_EQUAL(TheAnswer, 42);
+ }
+}
+
+Y_UNIT_TEST_SUITE(TestSetUpTearDownFixture)
+{
+ Y_UNIT_TEST_F(Test1, TSetUpTearDownFixture) {
+ UNIT_ASSERT_VALUES_EQUAL(Magic, 17);
+ Magic = 42;
+ }
+}
diff --git a/library/cpp/testing/unittest/ut/ya.make b/library/cpp/testing/unittest/ut/ya.make
new file mode 100644
index 0000000000..6d4c0959cc
--- /dev/null
+++ b/library/cpp/testing/unittest/ut/ya.make
@@ -0,0 +1,10 @@
+UNITTEST_FOR(library/cpp/testing/unittest)
+
+OWNER(snowball)
+
+SRCS(
+ main.cpp
+ registar_ut.cpp
+)
+
+END()
diff --git a/library/cpp/testing/unittest/utmain.cpp b/library/cpp/testing/unittest/utmain.cpp
new file mode 100644
index 0000000000..305bc6b40f
--- /dev/null
+++ b/library/cpp/testing/unittest/utmain.cpp
@@ -0,0 +1,771 @@
+#include "plugin.h"
+#include "registar.h"
+#include "utmain.h"
+
+#include <library/cpp/colorizer/colors.h>
+
+#include <library/cpp/json/writer/json.h>
+#include <library/cpp/json/writer/json_value.h>
+#include <library/cpp/testing/common/env.h>
+#include <library/cpp/testing/hook/hook.h>
+
+#include <util/datetime/base.h>
+
+#include <util/generic/hash.h>
+#include <util/generic/hash_set.h>
+#include <util/generic/scope.h>
+#include <util/generic/string.h>
+#include <util/generic/yexception.h>
+
+#include <util/network/init.h>
+
+#include <util/stream/file.h>
+#include <util/stream/output.h>
+#include <util/string/join.h>
+#include <util/string/util.h>
+
+#include <util/system/defaults.h>
+#include <util/system/execpath.h>
+#include <util/system/valgrind.h>
+#include <util/system/shellcommand.h>
+
+#if defined(_win_)
+#include <fcntl.h>
+#include <io.h>
+#include <windows.h>
+#include <crtdbg.h>
+#endif
+
+#if defined(_unix_)
+#include <unistd.h>
+#endif
+
+#ifdef WITH_VALGRIND
+#define NOTE_IN_VALGRIND(test) VALGRIND_PRINTF("%s::%s", test->unit->name.data(), test->name)
+#else
+#define NOTE_IN_VALGRIND(test)
+#endif
+
+const size_t MAX_COMMENT_MESSAGE_LENGTH = 1024 * 1024; // 1 MB
+
+using namespace NUnitTest;
+
+class TNullTraceWriterProcessor: public ITestSuiteProcessor {
+};
+
+class TTraceWriterProcessor: public ITestSuiteProcessor {
+public:
+ inline TTraceWriterProcessor(const char* traceFilePath, EOpenMode mode)
+ : PrevTime(TInstant::Now())
+ {
+ TraceFile = new TUnbufferedFileOutput(TFile(traceFilePath, mode | WrOnly | Seq));
+ }
+
+private:
+ TAutoPtr<TUnbufferedFileOutput> TraceFile;
+ TString TraceFilePath;
+ TInstant PrevTime;
+ TVector<TString> ErrorMessages;
+
+ inline void Trace(const TString eventName, const NJson::TJsonValue eventValue) {
+ NJsonWriter::TBuf json(NJsonWriter::HEM_UNSAFE);
+ json.BeginObject();
+
+ json.WriteKey("name").WriteString(eventName);
+ json.WriteKey("value").WriteJsonValue(&eventValue);
+ json.WriteKey("timestamp").WriteDouble(TInstant::Now().SecondsFloat(), PREC_NDIGITS, 14);
+
+ json.EndObject();
+
+ json.FlushTo(TraceFile.Get());
+ *TraceFile << "\n";
+ }
+
+ inline void TraceSubtestFinished(const char* className, const char* subtestName, const char* status, const TString comment, const TTestContext* context) {
+ const TInstant now = TInstant::Now();
+ NJson::TJsonValue event;
+ event.InsertValue("class", className);
+ event.InsertValue("subtest", subtestName);
+ event.InsertValue("status", status);
+ event.InsertValue("comment", comment.data());
+ event.InsertValue("time", (now - PrevTime).SecondsFloat());
+ if (context) {
+ for (const auto& metric : context->Metrics) {
+ event["metrics"].InsertValue(metric.first, metric.second);
+ }
+ }
+ Trace("subtest-finished", event);
+
+ PrevTime = now;
+ TString marker = Join("", "\n###subtest-finished:", className, "::", subtestName, "\n");
+ Cout << marker;
+ Cout.Flush();
+ Cerr << comment;
+ Cerr << marker;
+ Cerr.Flush();
+ }
+
+ virtual TString BuildComment(const char* message, const char* backTrace) {
+ return NUnitTest::GetFormatTag("bad") +
+ TString(message).substr(0, MAX_COMMENT_MESSAGE_LENGTH) +
+ NUnitTest::GetResetTag() +
+ TString("\n") +
+ NUnitTest::GetFormatTag("alt1") +
+ TString(backTrace).substr(0, MAX_COMMENT_MESSAGE_LENGTH) +
+ NUnitTest::GetResetTag();
+ }
+
+ void OnBeforeTest(const TTest* test) override {
+ NJson::TJsonValue event;
+ event.InsertValue("class", test->unit->name);
+ event.InsertValue("subtest", test->name);
+ Trace("subtest-started", event);
+ TString marker = Join("", "\n###subtest-started:", test->unit->name, "::", test->name, "\n");
+ Cout << marker;
+ Cout.Flush();
+ Cerr << marker;
+ Cerr.Flush();
+ }
+
+ void OnUnitStart(const TUnit* unit) override {
+ NJson::TJsonValue event;
+ event.InsertValue("class", unit->name);
+ Trace("test-started", event);
+ }
+
+ void OnUnitStop(const TUnit* unit) override {
+ NJson::TJsonValue event;
+ event.InsertValue("class", unit->name);
+ Trace("test-finished", event);
+ }
+
+ void OnError(const TError* descr) override {
+ const TString comment = BuildComment(descr->msg, descr->BackTrace.data());
+ ErrorMessages.push_back(comment);
+ }
+
+ void OnFinish(const TFinish* descr) override {
+ if (descr->Success) {
+ TraceSubtestFinished(descr->test->unit->name.data(), descr->test->name, "good", "", descr->Context);
+ } else {
+ TStringBuilder msgs;
+ for (const TString& m : ErrorMessages) {
+ if (msgs) {
+ msgs << TStringBuf("\n");
+ }
+ msgs << m;
+ }
+ if (msgs) {
+ msgs << TStringBuf("\n");
+ }
+ TraceSubtestFinished(descr->test->unit->name.data(), descr->test->name, "fail", msgs, descr->Context);
+ ErrorMessages.clear();
+ }
+ }
+};
+
+class TColoredProcessor: public ITestSuiteProcessor, public NColorizer::TColors {
+public:
+ inline TColoredProcessor(const TString& appName)
+ : PrintBeforeSuite_(true)
+ , PrintBeforeTest_(true)
+ , PrintAfterTest_(true)
+ , PrintAfterSuite_(true)
+ , PrintTimes_(false)
+ , PrintSummary_(true)
+ , PrevTime_(TInstant::Now())
+ , ShowFails(true)
+ , Start(0)
+ , End(Max<size_t>())
+ , AppName(appName)
+ , ForkTests(false)
+ , IsForked(false)
+ , Loop(false)
+ , ForkExitedCorrectly(false)
+ , TraceProcessor(new TNullTraceWriterProcessor())
+ {
+ }
+
+ ~TColoredProcessor() override {
+ }
+
+ inline void Disable(const char* name) {
+ size_t colon = TString(name).find("::");
+ if (colon == TString::npos) {
+ DisabledSuites_.insert(name);
+ } else {
+ TString suite = TString(name).substr(0, colon);
+ DisabledTests_.insert(name);
+ }
+ }
+
+ inline void Enable(const char* name) {
+ size_t colon = TString(name).rfind("::");
+ if (colon == TString::npos) {
+ EnabledSuites_.insert(name);
+ EnabledTests_.insert(TString() + name + "::*");
+ } else {
+ TString suite = TString(name).substr(0, colon);
+ EnabledSuites_.insert(suite);
+ EnabledSuites_.insert(name);
+ EnabledTests_.insert(name);
+ EnabledTests_.insert(TString() + name + "::*");
+ }
+ }
+
+ inline void SetPrintBeforeSuite(bool print) {
+ PrintBeforeSuite_ = print;
+ }
+
+ inline void SetPrintAfterSuite(bool print) {
+ PrintAfterSuite_ = print;
+ }
+
+ inline void SetPrintBeforeTest(bool print) {
+ PrintBeforeTest_ = print;
+ }
+
+ inline void SetPrintAfterTest(bool print) {
+ PrintAfterTest_ = print;
+ }
+
+ inline void SetPrintTimes(bool print) {
+ PrintTimes_ = print;
+ }
+
+ inline void SetPrintSummary(bool print) {
+ PrintSummary_ = print;
+ }
+
+ inline bool GetPrintSummary() {
+ return PrintSummary_;
+ }
+
+ inline void SetShowFails(bool show) {
+ ShowFails = show;
+ }
+
+ void SetContinueOnFail(bool val) {
+ NUnitTest::ContinueOnFail = val;
+ }
+
+ inline void BeQuiet() {
+ SetPrintTimes(false);
+ SetPrintBeforeSuite(false);
+ SetPrintAfterSuite(false);
+ SetPrintBeforeTest(false);
+ SetPrintAfterTest(false);
+ SetPrintSummary(false);
+ }
+
+ inline void SetStart(size_t val) {
+ Start = val;
+ }
+
+ inline void SetEnd(size_t val) {
+ End = val;
+ }
+
+ inline void SetForkTests(bool val) {
+ ForkTests = val;
+ }
+
+ inline bool GetForkTests() const override {
+ return ForkTests;
+ }
+
+ inline void SetIsForked(bool val) {
+ IsForked = val;
+ SetIsTTY(IsForked || CalcIsTTY(stderr));
+ }
+
+ inline bool GetIsForked() const override {
+ return IsForked;
+ }
+
+ inline void SetLoop(bool loop) {
+ Loop = loop;
+ }
+
+ inline bool IsLoop() const {
+ return Loop;
+ }
+
+ inline void SetTraceProcessor(TAutoPtr<ITestSuiteProcessor> traceProcessor) {
+ TraceProcessor = traceProcessor;
+ }
+
+private:
+ void OnUnitStart(const TUnit* unit) override {
+ TraceProcessor->UnitStart(*unit);
+ if (IsForked) {
+ return;
+ }
+ if (PrintBeforeSuite_ || PrintBeforeTest_) {
+ fprintf(stderr, "%s<-----%s %s\n", LightBlueColor().data(), OldColor().data(), unit->name.data());
+ }
+ }
+
+ void OnUnitStop(const TUnit* unit) override {
+ TraceProcessor->UnitStop(*unit);
+ if (IsForked) {
+ return;
+ }
+ if (!PrintAfterSuite_) {
+ return;
+ }
+
+ fprintf(stderr, "%s----->%s %s -> ok: %s%u%s",
+ LightBlueColor().data(), OldColor().data(), unit->name.data(),
+ LightGreenColor().data(), GoodTestsInCurrentUnit(), OldColor().data());
+ if (FailTestsInCurrentUnit()) {
+ fprintf(stderr, ", err: %s%u%s",
+ LightRedColor().data(), FailTestsInCurrentUnit(), OldColor().data());
+ }
+ fprintf(stderr, "\n");
+ }
+
+ void OnBeforeTest(const TTest* test) override {
+ TraceProcessor->BeforeTest(*test);
+ if (IsForked) {
+ return;
+ }
+ if (PrintBeforeTest_) {
+ fprintf(stderr, "[%sexec%s] %s::%s...\n", LightBlueColor().data(), OldColor().data(), test->unit->name.data(), test->name);
+ }
+ }
+
+ void OnError(const TError* descr) override {
+ TraceProcessor->Error(*descr);
+ if (!IsForked && ForkExitedCorrectly) {
+ return;
+ }
+ if (!PrintAfterTest_) {
+ return;
+ }
+
+ const TString err = Sprintf("[%sFAIL%s] %s::%s -> %s%s%s\n%s%s%s", LightRedColor().data(), OldColor().data(),
+ descr->test->unit->name.data(),
+ descr->test->name,
+ LightRedColor().data(), descr->msg, OldColor().data(), LightCyanColor().data(), descr->BackTrace.data(), OldColor().data());
+ const TDuration test_duration = SaveTestDuration();
+ if (ShowFails) {
+ if (PrintTimes_) {
+ Fails.push_back(Sprintf("%s %s", test_duration.ToString().data(), err.data()));
+ } else {
+ Fails.push_back(err);
+ }
+ }
+ fprintf(stderr, "%s", err.data());
+ NOTE_IN_VALGRIND(descr->test);
+ PrintTimes(test_duration);
+ if (IsForked) {
+ fprintf(stderr, "%s", ForkCorrectExitMsg);
+ }
+ }
+
+ void OnFinish(const TFinish* descr) override {
+ TraceProcessor->Finish(*descr);
+ if (!IsForked && ForkExitedCorrectly) {
+ return;
+ }
+ if (!PrintAfterTest_) {
+ return;
+ }
+
+ if (descr->Success) {
+ fprintf(stderr, "[%sgood%s] %s::%s\n", LightGreenColor().data(), OldColor().data(),
+ descr->test->unit->name.data(),
+ descr->test->name);
+ NOTE_IN_VALGRIND(descr->test);
+ PrintTimes(SaveTestDuration());
+ if (IsForked) {
+ fprintf(stderr, "%s", ForkCorrectExitMsg);
+ }
+ }
+ }
+
+ inline TDuration SaveTestDuration() {
+ const TInstant now = TInstant::Now();
+ TDuration d = now - PrevTime_;
+ PrevTime_ = now;
+ return d;
+ }
+
+ inline void PrintTimes(TDuration d) {
+ if (!PrintTimes_) {
+ return;
+ }
+
+ Cerr << d << "\n";
+ }
+
+ void OnEnd() override {
+ TraceProcessor->End();
+ if (IsForked) {
+ return;
+ }
+
+ if (!PrintSummary_) {
+ return;
+ }
+
+ fprintf(stderr, "[%sDONE%s] ok: %s%u%s",
+ YellowColor().data(), OldColor().data(),
+ LightGreenColor().data(), GoodTests(), OldColor().data());
+ if (FailTests())
+ fprintf(stderr, ", err: %s%u%s",
+ LightRedColor().data(), FailTests(), OldColor().data());
+ fprintf(stderr, "\n");
+
+ if (ShowFails) {
+ for (size_t i = 0; i < Fails.size(); ++i) {
+ printf("%s", Fails[i].data());
+ }
+ }
+ }
+
+ bool CheckAccess(TString name, size_t num) override {
+ if (num < Start) {
+ return false;
+ }
+
+ if (num >= End) {
+ return false;
+ }
+
+ if (DisabledSuites_.find(name.data()) != DisabledSuites_.end()) {
+ return false;
+ }
+
+ if (EnabledSuites_.empty()) {
+ return true;
+ }
+
+ return EnabledSuites_.find(name.data()) != EnabledSuites_.end();
+ }
+
+ bool CheckAccessTest(TString suite, const char* test) override {
+ TString name = suite + "::" + test;
+ if (DisabledTests_.find(name) != DisabledTests_.end()) {
+ return false;
+ }
+
+ if (EnabledTests_.empty()) {
+ return true;
+ }
+
+ if (EnabledTests_.find(TString() + suite + "::*") != EnabledTests_.end()) {
+ return true;
+ }
+
+ return EnabledTests_.find(name) != EnabledTests_.end();
+ }
+
+ void Run(std::function<void()> f, const TString& suite, const char* name, const bool forceFork) override {
+ if (!(ForkTests || forceFork) || GetIsForked()) {
+ return f();
+ }
+
+ TList<TString> args(1, "--is-forked-internal");
+ args.push_back(Sprintf("+%s::%s", suite.data(), name));
+
+ // stdin is ignored - unittest should not need them...
+ TShellCommand cmd(AppName, args,
+ TShellCommandOptions().SetUseShell(false).SetCloseAllFdsOnExec(true).SetAsync(false).SetLatency(1));
+ cmd.Run();
+
+ const TString& err = cmd.GetError();
+ const size_t msgIndex = err.find(ForkCorrectExitMsg);
+
+ // everything is printed by parent process except test's result output ("good" or "fail")
+ // which is printed by child. If there was no output - parent process prints default message.
+ ForkExitedCorrectly = msgIndex != TString::npos;
+
+ // TODO: stderr output is always printed after stdout
+ Cout.Write(cmd.GetOutput());
+ Cerr.Write(err.c_str(), Min(msgIndex, err.size()));
+
+ // do not use default case, so gcc will warn if new element in enum will be added
+ switch (cmd.GetStatus()) {
+ case TShellCommand::SHELL_FINISHED: {
+ // test could fail with zero status if it calls exit(0) in the middle.
+ if (ForkExitedCorrectly)
+ break;
+ [[fallthrough]];
+ }
+ case TShellCommand::SHELL_ERROR: {
+ ythrow yexception() << "Forked test failed";
+ }
+
+ case TShellCommand::SHELL_NONE: {
+ ythrow yexception() << "Forked test finished with unknown status";
+ }
+ case TShellCommand::SHELL_RUNNING: {
+ Y_VERIFY(false, "This can't happen, we used sync mode, it's a bug!");
+ }
+ case TShellCommand::SHELL_INTERNAL_ERROR: {
+ ythrow yexception() << "Forked test failed with internal error: " << cmd.GetInternalError();
+ }
+ }
+ }
+
+private:
+ bool PrintBeforeSuite_;
+ bool PrintBeforeTest_;
+ bool PrintAfterTest_;
+ bool PrintAfterSuite_;
+ bool PrintTimes_;
+ bool PrintSummary_;
+ THashSet<TString> DisabledSuites_;
+ THashSet<TString> EnabledSuites_;
+ THashSet<TString> DisabledTests_;
+ THashSet<TString> EnabledTests_;
+ TInstant PrevTime_;
+ bool ShowFails;
+ TVector<TString> Fails;
+ size_t Start;
+ size_t End;
+ TString AppName;
+ bool ForkTests;
+ bool IsForked;
+ bool Loop;
+ static const char* const ForkCorrectExitMsg;
+ bool ForkExitedCorrectly;
+ TAutoPtr<ITestSuiteProcessor> TraceProcessor;
+};
+
+const char* const TColoredProcessor::ForkCorrectExitMsg = "--END--";
+
+class TEnumeratingProcessor: public ITestSuiteProcessor {
+public:
+ TEnumeratingProcessor(bool verbose, IOutputStream& stream) noexcept
+ : Verbose_(verbose)
+ , Stream_(stream)
+ {
+ }
+
+ ~TEnumeratingProcessor() override {
+ }
+
+ bool CheckAccess(TString name, size_t /*num*/) override {
+ if (Verbose_) {
+ return true;
+ } else {
+ Stream_ << name << "\n";
+ return false;
+ }
+ }
+
+ bool CheckAccessTest(TString suite, const char* name) override {
+ Stream_ << suite << "::" << name << "\n";
+ return false;
+ }
+
+private:
+ bool Verbose_;
+ IOutputStream& Stream_;
+};
+
+#ifdef _win_
+class TWinEnvironment {
+public:
+ TWinEnvironment()
+ : OutputCP(GetConsoleOutputCP())
+ {
+ setmode(fileno(stdout), _O_BINARY);
+ SetConsoleOutputCP(CP_UTF8);
+
+ _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
+
+ if (!IsDebuggerPresent()) {
+ _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
+ _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR);
+ _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
+ _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR);
+ }
+ }
+ ~TWinEnvironment() {
+ if (!IsDebuggerPresent()) {
+ _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
+ _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR);
+ }
+
+ SetConsoleOutputCP(OutputCP); // restore original output CP at program exit
+ }
+
+private:
+ UINT OutputCP; // original codepage
+};
+static const TWinEnvironment Instance;
+#endif // _win_
+
+static int DoList(bool verbose, IOutputStream& stream) {
+ TEnumeratingProcessor eproc(verbose, stream);
+ TTestFactory::Instance().SetProcessor(&eproc);
+ TTestFactory::Instance().Execute();
+ return 0;
+}
+
+static int DoUsage(const char* progname) {
+ Cout << "Usage: " << progname << " [options] [[+|-]test]...\n\n"
+ << "Options:\n"
+ << " -h, --help print this help message\n"
+ << " -l, --list print a list of available tests\n"
+ << " -A --list-verbose print a list of available subtests\n"
+ << " --print-before-test print each test name before running it\n"
+ << " --print-before-suite print each test suite name before running it\n"
+ << " --show-fails print a list of all failed tests at the end\n"
+ << " --dont-show-fails do not print a list of all failed tests at the end\n"
+ << " --continue-on-fail print a message and continue running test suite instead of break\n"
+ << " --print-times print wall clock duration of each test\n"
+ << " --fork-tests run each test in a separate process\n"
+ << " --trace-path path to the trace file to be generated\n"
+ << " --trace-path-append path to the trace file to be appended\n";
+ return 0;
+}
+
+#if defined(_linux_) && defined(CLANG_COVERAGE)
+extern "C" int __llvm_profile_write_file(void);
+
+static void GracefulShutdownHandler(int) {
+ try {
+ __llvm_profile_write_file();
+ } catch (...) {
+ }
+ abort();
+}
+#endif
+
+int NUnitTest::RunMain(int argc, char** argv) {
+#if defined(_linux_) && defined(CLANG_COVERAGE)
+ {
+ struct sigaction sa;
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = GracefulShutdownHandler;
+ sa.sa_flags = SA_SIGINFO | SA_RESTART;
+ Y_VERIFY(!sigaction(SIGUSR2, &sa, nullptr));
+ }
+#endif
+ NTesting::THook::CallBeforeInit();
+ InitNetworkSubSystem();
+
+ try {
+ GetExecPath();
+ } catch (...) {
+ }
+
+#ifndef UT_SKIP_EXCEPTIONS
+ try {
+#endif
+ NTesting::THook::CallBeforeRun();
+ Y_DEFER { NTesting::THook::CallAfterRun(); };
+
+ NPlugin::OnStartMain(argc, argv);
+ Y_DEFER { NPlugin::OnStopMain(argc, argv); };
+
+ TColoredProcessor processor(GetExecPath());
+ IOutputStream* listStream = &Cout;
+ THolder<IOutputStream> listFile;
+
+ enum EListType {
+ DONT_LIST,
+ LIST,
+ LIST_VERBOSE
+ };
+ EListType listTests = DONT_LIST;
+
+ for (size_t i = 1; i < (size_t)argc; ++i) {
+ const char* name = argv[i];
+
+ if (name && *name) {
+ if (strcmp(name, "--help") == 0 || strcmp(name, "-h") == 0) {
+ return DoUsage(argv[0]);
+ } else if (strcmp(name, "--list") == 0 || strcmp(name, "-l") == 0) {
+ listTests = LIST;
+ } else if (strcmp(name, "--list-verbose") == 0 || strcmp(name, "-A") == 0) {
+ listTests = LIST_VERBOSE;
+ } else if (strcmp(name, "--print-before-suite=false") == 0) {
+ processor.SetPrintBeforeSuite(false);
+ } else if (strcmp(name, "--print-before-test=false") == 0) {
+ processor.SetPrintBeforeTest(false);
+ } else if (strcmp(name, "--print-before-suite") == 0) {
+ processor.SetPrintBeforeSuite(true);
+ } else if (strcmp(name, "--print-before-test") == 0) {
+ processor.SetPrintBeforeTest(true);
+ } else if (strcmp(name, "--show-fails") == 0) {
+ processor.SetShowFails(true);
+ } else if (strcmp(name, "--dont-show-fails") == 0) {
+ processor.SetShowFails(false);
+ } else if (strcmp(name, "--continue-on-fail") == 0) {
+ processor.SetContinueOnFail(true);
+ } else if (strcmp(name, "--print-times") == 0) {
+ processor.SetPrintTimes(true);
+ } else if (strcmp(name, "--from") == 0) {
+ ++i;
+ processor.SetStart(FromString<size_t>(argv[i]));
+ } else if (strcmp(name, "--to") == 0) {
+ ++i;
+ processor.SetEnd(FromString<size_t>(argv[i]));
+ } else if (strcmp(name, "--fork-tests") == 0) {
+ processor.SetForkTests(true);
+ } else if (strcmp(name, "--is-forked-internal") == 0) {
+ processor.SetIsForked(true);
+ } else if (strcmp(name, "--loop") == 0) {
+ processor.SetLoop(true);
+ } else if (strcmp(name, "--trace-path") == 0) {
+ ++i;
+ processor.BeQuiet();
+ NUnitTest::ShouldColorizeDiff = false;
+ processor.SetTraceProcessor(new TTraceWriterProcessor(argv[i], CreateAlways));
+ } else if (strcmp(name, "--trace-path-append") == 0) {
+ ++i;
+ processor.BeQuiet();
+ NUnitTest::ShouldColorizeDiff = false;
+ processor.SetTraceProcessor(new TTraceWriterProcessor(argv[i], OpenAlways | ForAppend));
+ } else if (strcmp(name, "--list-path") == 0) {
+ ++i;
+ listFile = MakeHolder<TFixedBufferFileOutput>(argv[i]);
+ listStream = listFile.Get();
+ } else if (strcmp(name, "--test-param") == 0) {
+ ++i;
+ TString param(argv[i]);
+ size_t assign = param.find('=');
+ Singleton<::NPrivate::TTestEnv>()->AddTestParam(param.substr(0, assign), param.substr(assign + 1));
+ } else if (TString(name).StartsWith("--")) {
+ return DoUsage(argv[0]), 1;
+ } else if (*name == '-') {
+ processor.Disable(name + 1);
+ } else if (*name == '+') {
+ processor.Enable(name + 1);
+ } else {
+ processor.Enable(name);
+ }
+ }
+ }
+ if (listTests != DONT_LIST) {
+ return DoList(listTests == LIST_VERBOSE, *listStream);
+ }
+
+ TTestFactory::Instance().SetProcessor(&processor);
+
+ unsigned ret;
+ for (;;) {
+ ret = TTestFactory::Instance().Execute();
+ if (!processor.GetIsForked() && ret && processor.GetPrintSummary()) {
+ Cerr << "SOME TESTS FAILED!!!!" << Endl;
+ }
+
+ if (0 != ret || !processor.IsLoop()) {
+ break;
+ }
+ }
+ return ret;
+#ifndef UT_SKIP_EXCEPTIONS
+ } catch (...) {
+ Cerr << "caught exception in test suite(" << CurrentExceptionMessage() << ")" << Endl;
+ }
+#endif
+
+ return 1;
+}
diff --git a/library/cpp/testing/unittest/utmain.h b/library/cpp/testing/unittest/utmain.h
new file mode 100644
index 0000000000..65e8082ee1
--- /dev/null
+++ b/library/cpp/testing/unittest/utmain.h
@@ -0,0 +1,5 @@
+#pragma once
+
+namespace NUnitTest {
+ int RunMain(int argc, char** argv);
+}
diff --git a/library/cpp/testing/unittest/ya.make b/library/cpp/testing/unittest/ya.make
new file mode 100644
index 0000000000..aaa4f2ba85
--- /dev/null
+++ b/library/cpp/testing/unittest/ya.make
@@ -0,0 +1,33 @@
+LIBRARY()
+
+PROVIDES(test_framework)
+
+OWNER(
+ pg
+ galaxycrab
+)
+
+PEERDIR(
+ library/cpp/colorizer
+ library/cpp/dbg_output
+ library/cpp/diff
+ library/cpp/json/writer
+ library/cpp/testing/common
+ library/cpp/testing/hook
+)
+
+SRCS(
+ gtest.cpp
+ checks.cpp
+ plugin.cpp
+ registar.cpp
+ tests_data.cpp
+ utmain.cpp
+)
+
+END()
+
+RECURSE_FOR_TESTS(
+ fat
+ ut
+)