aboutsummaryrefslogtreecommitdiffstats
path: root/library/cpp/testing/unittest
diff options
context:
space:
mode:
authorgalaxycrab <UgnineSirdis@ydb.tech>2023-03-20 21:40:37 +0300
committergalaxycrab <UgnineSirdis@ydb.tech>2023-03-20 21:40:37 +0300
commitde1da5bba65d071a1fa8a3dde559560c1c4ba8a2 (patch)
tree055beacf08a82444b3bced0ee9fa7b367f691bdc /library/cpp/testing/unittest
parente39b77e20d0ff8cd09554e97153f2c0a1770ee3d (diff)
downloadydb-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.cpp361
-rw-r--r--library/cpp/testing/unittest/junit.h64
-rw-r--r--library/cpp/testing/unittest/registar.cpp9
-rw-r--r--library/cpp/testing/unittest/registar.h4
-rw-r--r--library/cpp/testing/unittest/utmain.cpp163
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);