diff options
author | Devtools Arcadia <arcadia-devtools@yandex-team.ru> | 2022-02-07 18:08:42 +0300 |
---|---|---|
committer | Devtools Arcadia <arcadia-devtools@mous.vla.yp-c.yandex.net> | 2022-02-07 18:08:42 +0300 |
commit | 1110808a9d39d4b808aef724c861a2e1a38d2a69 (patch) | |
tree | e26c9fed0de5d9873cce7e00bc214573dc2195b7 /library/cpp/testing/unittest/registar.cpp | |
download | ydb-1110808a9d39d4b808aef724c861a2e1a38d2a69.tar.gz |
intermediate changes
ref:cde9a383711a11544ce7e107a78147fb96cc4029
Diffstat (limited to 'library/cpp/testing/unittest/registar.cpp')
-rw-r--r-- | library/cpp/testing/unittest/registar.cpp | 513 |
1 files changed, 513 insertions, 0 deletions
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_; +} |