diff options
author | galaxycrab <UgnineSirdis@ydb.tech> | 2023-03-20 21:40:37 +0300 |
---|---|---|
committer | galaxycrab <UgnineSirdis@ydb.tech> | 2023-03-20 21:40:37 +0300 |
commit | de1da5bba65d071a1fa8a3dde559560c1c4ba8a2 (patch) | |
tree | 055beacf08a82444b3bced0ee9fa7b367f691bdc /library/cpp/testing/unittest | |
parent | e39b77e20d0ff8cd09554e97153f2c0a1770ee3d (diff) | |
download | ydb-de1da5bba65d071a1fa8a3dde559560c1c4ba8a2.tar.gz |
Support --fork-tests in JUnitProcessor. Support more than one trace processor. Capture stderr/stdout logs, backtrace and time in JUnitProcesor
Diffstat (limited to 'library/cpp/testing/unittest')
-rw-r--r-- | library/cpp/testing/unittest/junit.cpp | 361 | ||||
-rw-r--r-- | library/cpp/testing/unittest/junit.h | 64 | ||||
-rw-r--r-- | library/cpp/testing/unittest/registar.cpp | 9 | ||||
-rw-r--r-- | library/cpp/testing/unittest/registar.h | 4 | ||||
-rw-r--r-- | library/cpp/testing/unittest/utmain.cpp | 163 |
5 files changed, 483 insertions, 118 deletions
diff --git a/library/cpp/testing/unittest/junit.cpp b/library/cpp/testing/unittest/junit.cpp index 984f1ec5ead..ff1d50fb211 100644 --- a/library/cpp/testing/unittest/junit.cpp +++ b/library/cpp/testing/unittest/junit.cpp @@ -1,66 +1,225 @@ #include "junit.h" +#include <libxml/parser.h> #include <libxml/xmlwriter.h> + +#include <util/generic/scope.h> +#include <util/generic/size_literals.h> +#include <util/system/env.h> +#include <util/system/file.h> #include <util/system/fs.h> #include <util/system/file.h> +#include <util/system/fstat.h> +#include <util/system/tempfile.h> + +#include <stdio.h> + +#if defined(_win_) +#include <io.h> +#endif namespace NUnitTest { -#define CHECK_CALL(expr) if ((expr) < 0) { \ - Cerr << "Faield to write to xml" << Endl; \ - return; \ -} +extern const TString Y_UNITTEST_OUTPUT_CMDLINE_OPTION = "Y_UNITTEST_OUTPUT"; -#define XML_STR(s) ((const xmlChar*)(s)) +struct TJUnitProcessor::TOutputCapturer { + static constexpr int STDOUT_FD = 1; + static constexpr int STDERR_FD = 2; -void TJUnitProcessor::Save() { - TString path = FileName; - auto sz = path.size(); - TFile lockFile; - TFile reportFile; - TString lockFileName; - const int MaxReps = 200; + TOutputCapturer(int fd) + : FdToCapture(fd) + , TmpFile(MakeTempName()) + { + { #if defined(_win_) - const char dirSeparator = '\\'; + TFileHandle f((FHANDLE)_get_osfhandle(FdToCapture)); #else - const char dirSeparator = '/'; + TFileHandle f(FdToCapture); #endif - if ((sz == 0) or (path[sz - 1] == dirSeparator)) { - if (sz > 0) { - NFs::MakeDirectoryRecursive(path); + TFileHandle other(f.Duplicate()); + Original.Swap(other); + f.Release(); } - TString reportFileName; - for (int i = 0; i < MaxReps; i++) { - TString suffix = (i > 0) ? ("-" + std::to_string(i)) : ""; - lockFileName = path + ExecName + suffix + ".lock"; + + TFileHandle captured(TmpFile.Name(), EOpenModeFlag::OpenAlways | EOpenModeFlag::RdWr); + + fflush(nullptr); + captured.Duplicate2Posix(FdToCapture); + } + + ~TOutputCapturer() { + Uncapture(); + } + + void Uncapture() { + if (Original.IsOpen()) { + fflush(nullptr); + Original.Duplicate2Posix(FdToCapture); + Original.Close(); + } + } + + TString GetCapturedString() { + Uncapture(); + + TFile captured(TmpFile.Name(), EOpenModeFlag::RdOnly); + i64 len = captured.GetLength(); + if (len > 0) { + TString out; + if (static_cast<size_t>(len) > 10_KB) { + len = static_cast<i64>(10_KB); + } + out.resize(len); try { - lockFile = TFile(lockFileName, EOpenModeFlag::CreateNew); - } catch (const TFileError&) {} - if (lockFile.IsOpen()) { - // Inside a lock, ensure the .xml file does not exist - reportFileName = path + ExecName + suffix + ".xml"; - try { - reportFile = TFile(reportFileName, EOpenModeFlag::OpenExisting | EOpenModeFlag::RdOnly); - } catch (const TFileError&) { - break; - } - reportFile.Close(); - lockFile.Close(); - NFs::Remove(lockFileName); + captured.Read((void*)out.data(), len); + return out; + } catch (const std::exception& ex) { + Cerr << "Failed to read from captured output: " << ex.what() << Endl; } } - if (!lockFile.IsOpen()) { - Cerr << "Could not find a vacant file name to write report for path " << path << ", maximum number of reports: " << MaxReps << Endl; - Y_FAIL("Cannot write report"); + return {}; + } + + const int FdToCapture; + TFileHandle Original; + TTempFile TmpFile; +}; + +TJUnitProcessor::TJUnitProcessor(TString file, TString exec) + : FileName(file) + , ExecName(exec) +{ +} + +TJUnitProcessor::~TJUnitProcessor() { + Save(); +} + +void TJUnitProcessor::OnBeforeTest(const TTest* test) { + Y_UNUSED(test); + if (!GetForkTests() || GetIsForked()) { + StdErrCapturer = MakeHolder<TOutputCapturer>(TOutputCapturer::STDERR_FD); + StdOutCapturer = MakeHolder<TOutputCapturer>(TOutputCapturer::STDOUT_FD); + StartCurrentTestTime = TInstant::Now(); + } +} + +void TJUnitProcessor::OnError(const TError* descr) { + if (!GetForkTests() || GetIsForked()) { + auto* testCase = GetTestCase(descr->test); + TFailure& failure = testCase->Failures.emplace_back(); + failure.Message = descr->msg; + failure.BackTrace = descr->BackTrace; + } +} + +void TJUnitProcessor::OnFinish(const TFinish* descr) { + if (!GetForkTests() || GetIsForked()) { + auto* testCase = GetTestCase(descr->test); + testCase->Success = descr->Success; + if (StartCurrentTestTime != TInstant::Zero()) { + testCase->DurationSecods = (TInstant::Now() - StartCurrentTestTime).SecondsFloat(); + } + StartCurrentTestTime = TInstant::Zero(); + if (StdOutCapturer) { + testCase->StdOut = StdOutCapturer->GetCapturedString(); + StdOutCapturer = nullptr; + Cout.Write(testCase->StdOut); + } + if (StdErrCapturer) { + testCase->StdErr = StdErrCapturer->GetCapturedString(); + StdErrCapturer = nullptr; + Cerr.Write(testCase->StdErr); } - path = reportFileName; + } else { + MergeSubprocessReport(); + } +} + +TString TJUnitProcessor::BuildFileName(size_t index, const TStringBuf extension) const { + TStringBuilder result; + result << FileName << ExecName; + if (index > 0) { + result << "-"sv << index; } - auto file = xmlNewTextWriterFilename(path.c_str(), 0); + result << extension; + return std::move(result); +} + +void TJUnitProcessor::MakeReportFileName() { + constexpr size_t MaxReps = 200; + +#if defined(_win_) + constexpr char DirSeparator = '\\'; +#else + constexpr char DirSeparator = '/'; +#endif + + if (!ResultReportFileName.empty()) { + return; + } + + if (GetIsForked() || !FileName.empty() && FileName.back() != DirSeparator) { + ResultReportFileName = FileName; + } else { // Directory is specified, => make unique report name + if (!FileName.empty()) { + NFs::MakeDirectoryRecursive(FileName); + } + for (size_t i = 0; i < MaxReps; ++i) { + TString uniqReportFileName = BuildFileName(i, ".xml"sv); + try { + TFile newUniqReportFile(uniqReportFileName, EOpenModeFlag::CreateNew); + newUniqReportFile.Close(); + ResultReportFileName = std::move(uniqReportFileName); + break; + } catch (const TFileError&) { + // File already exists => try next name + } + } + } + + if (ResultReportFileName.empty()) { + Cerr << "Could not find a vacant file name to write report for path " << FileName << ", maximum number of reports: " << MaxReps << Endl; + Y_FAIL("Cannot write report"); + } +} + +void TJUnitProcessor::Save() { + MakeReportFileName(); + SerializeToFile(); +} + +void TJUnitProcessor::SetForkTestsParams(bool forkTests, bool isForked) { + ITestSuiteProcessor::SetForkTestsParams(forkTests, isForked); + MakeTmpFileNameForForkedTests(); +} + +void TJUnitProcessor::MakeTmpFileNameForForkedTests() { + if (GetForkTests() && !GetIsForked()) { + TmpReportFile.ConstructInPlace(MakeTempName()); + // Replace option for child processes + SetEnv(Y_UNITTEST_OUTPUT_CMDLINE_OPTION, TStringBuilder() << "xml:" << TmpReportFile->Name()); + } +} + +#define CHECK_CALL(expr) if (int resultCode = (expr); resultCode < 0) { \ + Cerr << "Faield to write to xml. Result code: " << resultCode << Endl; \ + return; \ +} + +#define XML_STR(s) ((const xmlChar*)(s)) + +void TJUnitProcessor::SerializeToFile() { + auto file = xmlNewTextWriterFilename(ResultReportFileName.c_str(), 0); if (!file) { - Cerr << "Failed to open xml file for writing: " << path.c_str() << Endl; + Cerr << "Failed to open xml file for writing: " << ResultReportFileName << Endl; return; } + Y_DEFER { + xmlFreeTextWriter(file); + }; + CHECK_CALL(xmlTextWriterStartDocument(file, nullptr, "UTF-8", nullptr)); CHECK_CALL(xmlTextWriterStartElement(file, XML_STR("testsuites"))); CHECK_CALL(xmlTextWriterWriteAttribute(file, XML_STR("tests"), XML_STR(ToString(GetTestsCount()).c_str()))); @@ -72,19 +231,31 @@ void TJUnitProcessor::Save() { CHECK_CALL(xmlTextWriterWriteAttribute(file, XML_STR("id"), XML_STR(suiteName.c_str()))); CHECK_CALL(xmlTextWriterWriteAttribute(file, XML_STR("tests"), XML_STR(ToString(suite.GetTestsCount()).c_str()))); CHECK_CALL(xmlTextWriterWriteAttribute(file, XML_STR("failures"), XML_STR(ToString(suite.GetFailuresCount()).c_str()))); + CHECK_CALL(xmlTextWriterWriteAttribute(file, XML_STR("time"), XML_STR(ToString(suite.GetDurationSeconds()).c_str()))); for (const auto& [testName, test] : suite.Cases) { CHECK_CALL(xmlTextWriterStartElement(file, XML_STR("testcase"))); CHECK_CALL(xmlTextWriterWriteAttribute(file, XML_STR("name"), XML_STR(testName.c_str()))); CHECK_CALL(xmlTextWriterWriteAttribute(file, XML_STR("id"), XML_STR(testName.c_str()))); + CHECK_CALL(xmlTextWriterWriteAttribute(file, XML_STR("time"), XML_STR(ToString(test.DurationSecods).c_str()))); for (const auto& failure : test.Failures) { CHECK_CALL(xmlTextWriterStartElement(file, XML_STR("failure"))); - CHECK_CALL(xmlTextWriterWriteAttribute(file, XML_STR("message"), XML_STR(failure.c_str()))); + CHECK_CALL(xmlTextWriterWriteAttribute(file, XML_STR("message"), XML_STR(failure.Message.c_str()))); CHECK_CALL(xmlTextWriterWriteAttribute(file, XML_STR("type"), XML_STR("ERROR"))); + if (failure.BackTrace) { + CHECK_CALL(xmlTextWriterWriteString(file, XML_STR(failure.BackTrace.c_str()))); + } CHECK_CALL(xmlTextWriterEndElement(file)); } + if (!test.StdOut.empty()) { + CHECK_CALL(xmlTextWriterWriteElement(file, XML_STR("system-out"), XML_STR(test.StdOut.c_str()))); + } + if (!test.StdErr.empty()) { + CHECK_CALL(xmlTextWriterWriteElement(file, XML_STR("system-err"), XML_STR(test.StdErr.c_str()))); + } + CHECK_CALL(xmlTextWriterEndElement(file)); } @@ -93,11 +264,111 @@ void TJUnitProcessor::Save() { CHECK_CALL(xmlTextWriterEndElement(file)); CHECK_CALL(xmlTextWriterEndDocument(file)); - xmlFreeTextWriter(file); +} + +#define C_STR(s) ((const char*)(s)) +#define STRBUF(s) TStringBuf(C_STR(s)) + +#define NODE_NAME(node) STRBUF((node)->name) +#define SAFE_CONTENT(node) (node && node->children ? STRBUF(node->children->content) : TStringBuf()) - if (lockFile.IsOpen()) { - lockFile.Close(); - NFs::Remove(lockFileName.c_str()); +#define CHECK_NODE_NAME(node, expectedName) if (NODE_NAME(node) != (expectedName)) { \ + ythrow yexception() << "Expected node name: \"" << (expectedName) \ + << "\", but got \"" << TStringBuf(C_STR((node)->name)) << "\""; \ +} + +static TString GetAttrValue(xmlNodePtr node, TStringBuf name, bool required = true) { + for (xmlAttrPtr attr = node->properties; attr != nullptr; attr = attr->next) { + if (NODE_NAME(attr) == name) { + return TString(SAFE_CONTENT(attr)); + } + } + if (required) { + ythrow yexception() << "Attribute \"" << name << "\" was not found"; + } + return {}; +} + +void TJUnitProcessor::MergeSubprocessReport() { + { + const i64 len = GetFileLength(TmpReportFile->Name()); + if (len < 0) { + Cerr << "Failed to get length of the output file for subprocess" << Endl; + return; + } + if (len == 0) { + return; // Empty file + } + } + + Y_DEFER { + TFile file(TmpReportFile->Name(), EOpenModeFlag::TruncExisting); + file.Close(); + }; + + xmlDocPtr doc = xmlParseFile(TmpReportFile->Name().c_str()); + if (!doc) { + Cerr << "Failed to parse xml output for subprocess" << Endl; + return; + } + + Y_DEFER { + xmlFreeDoc(doc); + }; + + xmlNodePtr root = xmlDocGetRootElement(doc); + if (!root) { + Cerr << "Failed to parse xml output for subprocess: empty document" << Endl; + return; + } + + CHECK_NODE_NAME(root, "testsuites"); + for (xmlNodePtr suite = root->children; suite != nullptr; suite = suite->next) { + try { + CHECK_NODE_NAME(suite, "testsuite"); + TString suiteName = GetAttrValue(suite, "id"); + TTestSuite& suiteInfo = Suites[suiteName]; + + // Test cases + for (xmlNodePtr testCase = suite->children; testCase != nullptr; testCase = testCase->next) { + try { + CHECK_NODE_NAME(testCase, "testcase"); + TString caseName = GetAttrValue(testCase, "id"); + TTestCase& testCaseInfo = suiteInfo.Cases[caseName]; + + if (TString duration = GetAttrValue(testCase, "time")) { + TryFromString(duration, testCaseInfo.DurationSecods); + } + + // Failures/stderr/stdout + for (xmlNodePtr testProp = testCase->children; testProp != nullptr; testProp = testProp->next) { + try { + if (NODE_NAME(testProp) == "failure") { + TString message = GetAttrValue(testProp, "message"); + auto& failure = testCaseInfo.Failures.emplace_back(); + failure.Message = message; + failure.BackTrace = TString(SAFE_CONTENT(testProp)); + } else if (NODE_NAME(testProp) == "system-out") { + testCaseInfo.StdOut = TString(SAFE_CONTENT(testProp)); + } else if (NODE_NAME(testProp) == "system-err") { + testCaseInfo.StdErr = TString(SAFE_CONTENT(testProp)); + } else { + ythrow yexception() << "Unknown test case subprop: \"" << NODE_NAME(testProp) << "\""; + } + } catch (const std::exception& ex) { + Cerr << "Failed to load test case " << caseName << " failure in suite " << suiteName << ": " << ex.what() << Endl; + continue; + } + } + } catch (const std::exception& ex) { + Cerr << "Failed to load test case info in suite " << suiteName << ": " << ex.what() << Endl; + continue; + } + } + } catch (const std::exception& ex) { + Cerr << "Failed to load test suite info from xml: " << ex.what() << Endl; + continue; + } } } diff --git a/library/cpp/testing/unittest/junit.h b/library/cpp/testing/unittest/junit.h index f5113c290ed..2eca875310d 100644 --- a/library/cpp/testing/unittest/junit.h +++ b/library/cpp/testing/unittest/junit.h @@ -1,12 +1,26 @@ #include "registar.h" +#include <util/datetime/base.h> +#include <util/generic/maybe.h> +#include <util/system/tempfile.h> + namespace NUnitTest { +extern const TString Y_UNITTEST_OUTPUT_CMDLINE_OPTION; + class TJUnitProcessor : public ITestSuiteProcessor { + struct TFailure { + TString Message; + TString BackTrace; + }; + struct TTestCase { TString Name; bool Success; - TVector<TString> Failures; + TVector<TFailure> Failures; + TString StdOut; + TString StdErr; + double DurationSecods = 0.0; size_t GetFailuresCount() const { return Failures.size(); @@ -27,27 +41,27 @@ class TJUnitProcessor : public ITestSuiteProcessor { } return sum; } + + double GetDurationSeconds() const { + double sum = 0.0; + for (const auto& [name, testCase] : Cases) { + sum += testCase.DurationSecods; + } + return sum; + } }; -public: - TJUnitProcessor(TString file, TString exec) - : FileName(file) - , ExecName(exec) - { - } + struct TOutputCapturer; - ~TJUnitProcessor() { - Save(); - } +public: + TJUnitProcessor(TString file, TString exec); + ~TJUnitProcessor(); - void OnError(const TError* descr) override { - auto* testCase = GetTestCase(descr->test); - testCase->Failures.emplace_back(descr->msg); - } + void SetForkTestsParams(bool forkTests, bool isForked) override; - void OnFinish(const TFinish* descr) override { - GetTestCase(descr->test)->Success = descr->Success; - } + void OnBeforeTest(const TTest* test) override; + void OnError(const TError* descr) override; + void OnFinish(const TFinish* descr) override; private: TTestCase* GetTestCase(const TTest* test) { @@ -73,10 +87,22 @@ private: return sum; } + void SerializeToFile(); + void MergeSubprocessReport(); + + TString BuildFileName(size_t index, const TStringBuf extension) const; + void MakeReportFileName(); + void MakeTmpFileNameForForkedTests(); + private: - TString FileName; - TString ExecName; + const TString FileName; // cmd line param + const TString ExecName; // cmd line param + TString ResultReportFileName; + TMaybe<TTempFile> TmpReportFile; TMap<TString, TTestSuite> Suites; + THolder<TOutputCapturer> StdErrCapturer; + THolder<TOutputCapturer> StdOutCapturer; + TInstant StartCurrentTestTime; }; } // namespace NUnitTest diff --git a/library/cpp/testing/unittest/registar.cpp b/library/cpp/testing/unittest/registar.cpp index 93771145757..4f032fa00b4 100644 --- a/library/cpp/testing/unittest/registar.cpp +++ b/library/cpp/testing/unittest/registar.cpp @@ -237,12 +237,17 @@ void NUnitTest::ITestSuiteProcessor::Run(std::function<void()> f, const TString& f(); } +void NUnitTest::ITestSuiteProcessor::SetForkTestsParams(bool forkTests, bool isForked) { + ForkTests_ = forkTests; + IsForked_ = isForked; +} + bool NUnitTest::ITestSuiteProcessor::GetIsForked() const { - return false; + return IsForked_; } bool NUnitTest::ITestSuiteProcessor::GetForkTests() const { - return false; + return ForkTests_; } void NUnitTest::ITestSuiteProcessor::OnStart() { diff --git a/library/cpp/testing/unittest/registar.h b/library/cpp/testing/unittest/registar.h index 842535915cf..580d8040618 100644 --- a/library/cpp/testing/unittest/registar.h +++ b/library/cpp/testing/unittest/registar.h @@ -143,6 +143,8 @@ namespace NUnitTest { // --fork-tests is set (warning: this may be false, but never the less test will be forked if called inside UNIT_FORKED_TEST) virtual bool GetForkTests() const; + virtual void SetForkTestsParams(bool forkTests, bool isForked); + private: virtual void OnStart(); @@ -165,6 +167,8 @@ namespace NUnitTest { private: TMap<TString, size_t> TestErrors_; TMap<TString, size_t> CurTestErrors_; + bool IsForked_ = false; + bool ForkTests_ = false; }; class TTestBase; diff --git a/library/cpp/testing/unittest/utmain.cpp b/library/cpp/testing/unittest/utmain.cpp index 62968de3dfe..99245c80520 100644 --- a/library/cpp/testing/unittest/utmain.cpp +++ b/library/cpp/testing/unittest/utmain.cpp @@ -26,6 +26,7 @@ #include <util/string/util.h> #include <util/system/defaults.h> +#include <util/system/env.h> #include <util/system/execpath.h> #include <util/system/valgrind.h> #include <util/system/shellcommand.h> @@ -56,6 +57,67 @@ using namespace NUnitTest; class TNullTraceWriterProcessor: public ITestSuiteProcessor { }; +class TMultiTraceProcessor: public ITestSuiteProcessor { +public: + TMultiTraceProcessor(std::vector<std::shared_ptr<ITestSuiteProcessor>>&& processors) + : Processors(std::move(processors)) + { + } + + void SetForkTestsParams(bool forkTests, bool isForked) override { + ITestSuiteProcessor::SetForkTestsParams(forkTests, isForked); + for (const auto& proc : Processors) { + proc->SetForkTestsParams(forkTests, isForked); + } + } + +private: + void OnStart() override { + for (const auto& proc : Processors) { + proc->Start(); + } + } + + void OnEnd() override { + for (const auto& proc : Processors) { + proc->End(); + } + } + + void OnUnitStart(const TUnit* unit) override { + for (const auto& proc : Processors) { + proc->UnitStart(*unit); + } + } + + void OnUnitStop(const TUnit* unit) override { + for (const auto& proc : Processors) { + proc->UnitStop(*unit); + } + } + + void OnError(const TError* error) override { + for (const auto& proc : Processors) { + proc->Error(*error); + } + } + + void OnFinish(const TFinish* finish) override { + for (const auto& proc : Processors) { + proc->Finish(*finish); + } + } + + void OnBeforeTest(const TTest* test) override { + for (const auto& proc : Processors) { + proc->BeforeTest(*test); + } + } + +private: + std::vector<std::shared_ptr<ITestSuiteProcessor>> Processors; +}; + class TTraceWriterProcessor: public ITestSuiteProcessor { public: inline TTraceWriterProcessor(const char* traceFilePath, EOpenMode mode) @@ -179,8 +241,6 @@ public: , Start(0) , End(Max<size_t>()) , AppName(appName) - , ForkTests(false) - , IsForked(false) , Loop(false) , ForkExitedCorrectly(false) , TraceProcessor(new TNullTraceWriterProcessor()) @@ -263,21 +323,11 @@ public: End = val; } - inline void SetForkTests(bool val) { - ForkTests = val; - } + inline void SetForkTestsParams(bool forkTests, bool isForked) override { + ITestSuiteProcessor::SetForkTestsParams(forkTests, isForked); + TraceProcessor->SetForkTestsParams(forkTests, isForked); - 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; + SetIsTTY(GetIsForked() || CalcIsTTY(stderr)); } inline void SetLoop(bool loop) { @@ -288,14 +338,14 @@ public: return Loop; } - inline void SetTraceProcessor(TAutoPtr<ITestSuiteProcessor> traceProcessor) { - TraceProcessor = traceProcessor; + inline void SetTraceProcessor(std::shared_ptr<ITestSuiteProcessor> traceProcessor) { + TraceProcessor = std::move(traceProcessor); } private: void OnUnitStart(const TUnit* unit) override { TraceProcessor->UnitStart(*unit); - if (IsForked) { + if (GetIsForked()) { return; } if (PrintBeforeSuite_ || PrintBeforeTest_) { @@ -305,7 +355,7 @@ private: void OnUnitStop(const TUnit* unit) override { TraceProcessor->UnitStop(*unit); - if (IsForked) { + if (GetIsForked()) { return; } if (!PrintAfterSuite_) { @@ -323,18 +373,15 @@ private: } void OnBeforeTest(const TTest* test) override { - TraceProcessor->BeforeTest(*test); - if (IsForked) { - return; - } - if (PrintBeforeTest_) { + if (!GetIsForked() && PrintBeforeTest_) { fprintf(stderr, "[%sexec%s] %s::%s...\n", LightBlueColor().data(), OldColor().data(), test->unit->name.data(), test->name); } + TraceProcessor->BeforeTest(*test); } void OnError(const TError* descr) override { TraceProcessor->Error(*descr); - if (!IsForked && ForkExitedCorrectly) { + if (!GetIsForked() && ForkExitedCorrectly) { return; } if (!PrintAfterTest_) { @@ -356,14 +403,14 @@ private: fprintf(stderr, "%s", err.data()); NOTE_IN_VALGRIND(descr->test); PrintTimes(test_duration); - if (IsForked) { + if (GetIsForked()) { fprintf(stderr, "%s", ForkCorrectExitMsg); } } void OnFinish(const TFinish* descr) override { TraceProcessor->Finish(*descr); - if (!IsForked && ForkExitedCorrectly) { + if (!GetIsForked() && ForkExitedCorrectly) { return; } if (!PrintAfterTest_) { @@ -376,7 +423,7 @@ private: descr->test->name); NOTE_IN_VALGRIND(descr->test); PrintTimes(SaveTestDuration()); - if (IsForked) { + if (GetIsForked()) { fprintf(stderr, "%s", ForkCorrectExitMsg); } } @@ -399,7 +446,7 @@ private: void OnEnd() override { TraceProcessor->End(); - if (IsForked) { + if (GetIsForked()) { return; } @@ -460,7 +507,7 @@ private: } void Run(std::function<void()> f, const TString& suite, const char* name, const bool forceFork) override { - if (!(ForkTests || forceFork) || GetIsForked()) { + if (!(GetForkTests() || forceFork) || GetIsForked()) { return f(); } @@ -468,8 +515,18 @@ private: 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)); + TShellCommandOptions options; + options + .SetUseShell(false) + .SetCloseAllFdsOnExec(true) + .SetAsync(false) + .SetLatency(1); + + if (TString output = GetEnv(Y_UNITTEST_OUTPUT_CMDLINE_OPTION)) { + options.Environment[Y_UNITTEST_OUTPUT_CMDLINE_OPTION] = output; + } + + TShellCommand cmd(AppName, args, options); cmd.Run(); const TString& err = cmd.GetError(); @@ -524,12 +581,10 @@ private: size_t Start; size_t End; TString AppName; - bool ForkTests; - bool IsForked; bool Loop; static const char* const ForkCorrectExitMsg; bool ForkExitedCorrectly; - TAutoPtr<ITestSuiteProcessor> TraceProcessor; + std::shared_ptr<ITestSuiteProcessor> TraceProcessor; }; const char* const TColoredProcessor::ForkCorrectExitMsg = "--END--"; @@ -671,8 +726,10 @@ int NUnitTest::RunMain(int argc, char** argv) { }; EListType listTests = DONT_LIST; - bool processorSet = false; + bool hasJUnitProcessor = false; + bool forkTests = false; bool isForked = false; + std::vector<std::shared_ptr<ITestSuiteProcessor>> traceProcessors; for (size_t i = 1; i < (size_t)argc; ++i) { const char* name = argv[i]; @@ -705,9 +762,8 @@ int NUnitTest::RunMain(int argc, char** argv) { ++i; processor.SetEnd(FromString<size_t>(argv[i])); } else if (strcmp(name, "--fork-tests") == 0) { - processor.SetForkTests(true); + forkTests = true; } else if (strcmp(name, "--is-forked-internal") == 0) { - processor.SetIsForked(true); isForked = true; } else if (strcmp(name, "--loop") == 0) { processor.SetLoop(true); @@ -715,14 +771,12 @@ int NUnitTest::RunMain(int argc, char** argv) { ++i; processor.BeQuiet(); NUnitTest::ShouldColorizeDiff = false; - processor.SetTraceProcessor(new TTraceWriterProcessor(argv[i], CreateAlways)); - processorSet = true; + traceProcessors.push_back(std::make_shared<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)); - processorSet = true; + traceProcessors.push_back(std::make_shared<TTraceWriterProcessor>(argv[i], OpenAlways | ForAppend)); } else if (strcmp(name, "--list-path") == 0) { ++i; listFile = MakeHolder<TFixedBufferFileOutput>(argv[i]); @@ -736,14 +790,13 @@ int NUnitTest::RunMain(int argc, char** argv) { ++i; Y_ENSURE((int)i < argc); TString param(argv[i]); - if (param.StartsWith("xml:")) { + if (param.StartsWith("xml:") && !hasJUnitProcessor) { TStringBuf fileName = param; fileName = fileName.SubString(4, TStringBuf::npos); - processor.BeQuiet(); NUnitTest::ShouldColorizeDiff = false; - processor.SetTraceProcessor(new TJUnitProcessor(TString(fileName), argv[0])); + traceProcessors.push_back(std::make_shared<TJUnitProcessor>(TString(fileName), argv[0])); } - processorSet = true; + hasJUnitProcessor = true; } else if (TString(name).StartsWith("--")) { return DoUsage(argv[0]), 1; } else if (*name == '-') { @@ -759,18 +812,24 @@ int NUnitTest::RunMain(int argc, char** argv) { return DoList(listTests == LIST_VERBOSE, *listStream); } - if (!processorSet && !isForked) { - TString oo(getenv("Y_UNITTEST_OUTPUT")); + if (!hasJUnitProcessor) { + TString oo(GetEnv(Y_UNITTEST_OUTPUT_CMDLINE_OPTION)); if (oo.StartsWith("xml:")) { TStringBuf fileName = oo; fileName = fileName.SubString(4, TStringBuf::npos); - processor.BeQuiet(); NUnitTest::ShouldColorizeDiff = false; - processor.SetTraceProcessor(new TJUnitProcessor(TString(fileName), - std::filesystem::path(argv[0]).stem().string())); + traceProcessors.push_back(std::make_shared<TJUnitProcessor>(TString(fileName), + std::filesystem::path(argv[0]).stem().string())); } } + if (traceProcessors.size() > 1) { + processor.SetTraceProcessor(std::make_shared<TMultiTraceProcessor>(std::move(traceProcessors))); + } else if (traceProcessors.size() == 1) { + processor.SetTraceProcessor(std::move(traceProcessors[0])); + } + + processor.SetForkTestsParams(forkTests, isForked); TTestFactory::Instance().SetProcessor(&processor); |