123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773 |
- #include "junit.h"
- #include <library/cpp/json/json_reader.h>
- #include <library/cpp/json/writer/json.h>
- #include <library/cpp/json/writer/json_value.h>
- #include <util/charset/utf8.h>
- #include <util/generic/scope.h>
- #include <util/generic/size_literals.h>
- #include <util/stream/file.h>
- #include <util/stream/input.h>
- #include <util/system/backtrace.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>
- #include <signal.h>
- #if defined(_win_)
- #include <io.h>
- #endif
- namespace NUnitTest {
- extern const TString Y_UNITTEST_OUTPUT_CMDLINE_OPTION = "Y_UNITTEST_OUTPUT";
- extern const TString Y_UNITTEST_TEST_FILTER_FILE_OPTION = "Y_UNITTEST_FILTER_FILE";
- static bool IsAllowed(wchar32 c) {
- // https://en.wikipedia.org/wiki/Valid_characters_in_XML
- return c == 0x9
- || c == 0xA
- || c == 0xD
- || c >= 0x20 && c <= 0xD7FF
- || c >= 0xE000 && c <= 0xFFFD
- || c >= 0x10000 && c <= 0x10FFFF;
- }
- static TString SanitizeString(TString s) {
- TString escaped;
- bool fixedSomeChars = false;
- const unsigned char* i = reinterpret_cast<const unsigned char*>(s.data());
- const unsigned char* end = i + s.size();
- auto replaceChar = [&]() {
- if (!fixedSomeChars) {
- fixedSomeChars = true;
- escaped.reserve(s.size());
- escaped.insert(escaped.end(), s.data(), reinterpret_cast<const char*>(i));
- }
- escaped.push_back('?');
- };
- while (i < end) {
- wchar32 rune;
- size_t runeLen;
- const RECODE_RESULT result = SafeReadUTF8Char(rune, runeLen, i, end);
- if (result == RECODE_OK) {
- if (IsAllowed(rune)) {
- if (fixedSomeChars) {
- escaped.insert(escaped.end(), reinterpret_cast<const char*>(i), reinterpret_cast<const char*>(i + runeLen));
- }
- } else {
- replaceChar();
- }
- i += runeLen;
- } else {
- replaceChar();
- ++i;
- }
- }
- if (fixedSomeChars) {
- return escaped;
- } else {
- return s;
- }
- }
- struct TJUnitProcessor::TOutputCapturer {
- static constexpr int STDOUT_FD = 1;
- static constexpr int STDERR_FD = 2;
- TOutputCapturer(int fd)
- : FdToCapture(fd)
- , TmpFile(MakeTempName())
- {
- {
- #if defined(_win_)
- TFileHandle f((FHANDLE)_get_osfhandle(FdToCapture));
- #else
- TFileHandle f(FdToCapture);
- #endif
- TFileHandle other(f.Duplicate());
- Original.Swap(other);
- f.Release();
- }
- 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 GetTmpFileName() {
- Uncapture();
- return TmpFile.Name();
- }
- TString GetCapturedString() {
- Uncapture();
- TFile captured(TmpFile.Name(), EOpenModeFlag::RdOnly);
- i64 len = captured.GetLength();
- if (len > 0) {
- try {
- constexpr size_t LIMIT = 10_KB;
- constexpr size_t PART_LIMIT = 5_KB;
- TStringBuilder out;
- if (static_cast<size_t>(len) <= LIMIT) {
- out.resize(len);
- captured.Read((void*)out.data(), len);
- } else {
- // Read first 5_KB
- {
- TString first;
- first.resize(PART_LIMIT);
- captured.Read((void*)first.data(), PART_LIMIT);
- size_t lastNewLine = first.find_last_of('\n');
- if (lastNewLine == TString::npos) {
- out << first << Endl;
- } else {
- out << TStringBuf(first.c_str(), lastNewLine);
- }
- }
- out << Endl << Endl << "...SKIPPED..." << Endl << Endl;
- // Read last 5_KB
- {
- TString last;
- last.resize(PART_LIMIT);
- captured.Seek(-PART_LIMIT, sEnd);
- captured.Read((void*)last.data(), PART_LIMIT);
- size_t newLine = last.find_first_of('\n');
- if (newLine == TString::npos) {
- out << last << Endl;
- } else {
- out << TStringBuf(last.c_str() + newLine + 1);
- }
- }
- }
- if (out.back() != '\n') {
- out << Endl;
- }
- return std::move(out);
- } catch (const std::exception& ex) {
- Cerr << "Failed to read from captured output: " << ex.what() << Endl;
- }
- }
- return {};
- }
- const int FdToCapture;
- TFileHandle Original;
- TTempFile TmpFile;
- };
- TJUnitProcessor::TJUnitProcessor(TString file, TString exec, EOutputFormat outputFormat)
- : FileName(file)
- , ExecName(exec)
- , OutputFormat(outputFormat)
- {
- }
- TJUnitProcessor::~TJUnitProcessor() {
- Save();
- }
- void TJUnitProcessor::OnBeforeTest(const TTest* test) {
- CurrentTest.emplace(test);
- CaptureSignal(this);
- 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 = SanitizeString(descr->msg);
- failure.BackTrace = SanitizeString(descr->BackTrace);
- }
- }
- void TJUnitProcessor::TransferFromCapturer(THolder<TJUnitProcessor::TOutputCapturer>& capturer, TString& out, IOutputStream& outStream) {
- if (capturer) {
- capturer->Uncapture();
- {
- TFileInput fileStream(capturer->GetTmpFileName());
- TransferData(&fileStream, &outStream);
- out = SanitizeString(capturer->GetCapturedString());
- }
- capturer = nullptr;
- }
- }
- 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();
- TransferFromCapturer(StdOutCapturer, testCase->StdOut, Cout);
- TransferFromCapturer(StdErrCapturer, testCase->StdErr, Cerr);
- } else {
- MergeSubprocessReport();
- }
- UncaptureSignal();
- }
- TString TJUnitProcessor::BuildFileName(size_t index, const TStringBuf extension) const {
- TStringBuilder result;
- result << FileName << ExecName;
- if (index > 0) {
- result << "-"sv << index;
- }
- result << extension;
- return std::move(result);
- }
- TStringBuf TJUnitProcessor::GetFileExtension() const {
- switch (OutputFormat) {
- case EOutputFormat::Xml:
- return ".xml"sv;
- case EOutputFormat::Json:
- return ".json"sv;
- }
- return TStringBuf();
- }
- 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, GetFileExtension());
- 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_ABORT("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() << "json:" << TmpReportFile->Name());
- }
- }
- static TJUnitProcessor* CurrentJUnitProcessor = nullptr;
- void TJUnitProcessor::CaptureSignal(TJUnitProcessor* processor) {
- CurrentJUnitProcessor = processor;
- processor->PrevAbortHandler = signal(SIGABRT, &TJUnitProcessor::SignalHandler);
- if (processor->PrevAbortHandler == SIG_ERR) {
- processor->PrevAbortHandler = nullptr;
- }
- processor->PrevSegvHandler = signal(SIGSEGV, &TJUnitProcessor::SignalHandler);
- if (processor->PrevSegvHandler == SIG_ERR) {
- processor->PrevSegvHandler = nullptr;
- }
- }
- void TJUnitProcessor::UncaptureSignal() {
- if (CurrentJUnitProcessor) {
- if (CurrentJUnitProcessor->PrevAbortHandler != nullptr) {
- signal(SIGABRT, CurrentJUnitProcessor->PrevAbortHandler);
- } else {
- signal(SIGABRT, SIG_DFL);
- }
- if (CurrentJUnitProcessor->PrevSegvHandler != nullptr) {
- signal(SIGSEGV, CurrentJUnitProcessor->PrevSegvHandler);
- } else {
- signal(SIGSEGV, SIG_DFL);
- }
- }
- }
- void TJUnitProcessor::SignalHandler(int signal) {
- if (CurrentJUnitProcessor) {
- if (CurrentJUnitProcessor->CurrentTest) {
- TError errDesc;
- errDesc.test = *CurrentJUnitProcessor->CurrentTest;
- if (signal == SIGABRT) {
- errDesc.msg = "Test aborted";
- } else {
- errDesc.msg = "Segmentation fault";
- PrintBackTrace();
- }
- CurrentJUnitProcessor->OnError(&errDesc);
- TFinish finishDesc;
- finishDesc.Success = false;
- finishDesc.test = *CurrentJUnitProcessor->CurrentTest;
- CurrentJUnitProcessor->OnFinish(&finishDesc);
- }
- CurrentJUnitProcessor->Save();
- if (signal == SIGABRT) {
- if (CurrentJUnitProcessor->PrevAbortHandler) {
- CurrentJUnitProcessor->PrevAbortHandler(signal);
- }
- } else {
- if (CurrentJUnitProcessor->PrevSegvHandler) {
- CurrentJUnitProcessor->PrevSegvHandler(signal);
- }
- }
- }
- }
- void TJUnitProcessor::SerializeToFile() {
- switch (OutputFormat) {
- case EOutputFormat::Json:
- SerializeToJson();
- break;
- case EOutputFormat::Xml:
- [[fallthrough]];
- default:
- SerializeToXml();
- break;
- }
- }
- void TJUnitProcessor::SerializeToJson() {
- TFileOutput out(ResultReportFileName);
- NJsonWriter::TBuf json(NJsonWriter::HEM_UNSAFE, &out);
- json.SetIndentSpaces(1);
- json.BeginObject();
- {
- json.WriteKey("tests"sv).WriteInt(GetTestsCount());
- json.WriteKey("failures"sv).WriteInt(GetFailuresCount());
- json.WriteKey("testsuites"sv).BeginList();
- for (const auto& [suiteName, suite] : Suites) {
- json.BeginObject();
- json.WriteKey("name"sv).WriteString(suiteName);
- json.WriteKey("id"sv).WriteString(suiteName);
- json.WriteKey("tests"sv).WriteInt(suite.GetTestsCount());
- json.WriteKey("failures"sv).WriteInt(suite.GetFailuresCount());
- json.WriteKey("time"sv).WriteDouble(suite.GetDurationSeconds());
- json.WriteKey("testcases"sv).BeginList();
- for (const auto& [testName, test] : suite.Cases) {
- json.BeginObject();
- json.WriteKey("classname"sv).WriteString(suiteName);
- json.WriteKey("name"sv).WriteString(testName);
- json.WriteKey("id"sv).WriteString(testName);
- json.WriteKey("time"sv).WriteDouble(test.DurationSecods);
- json.WriteKey("failures"sv).BeginList();
- for (const auto& failure : test.Failures) {
- json.BeginObject();
- json.WriteKey("message"sv).WriteString(failure.Message);
- json.WriteKey("type"sv).WriteString("ERROR"sv);
- if (failure.BackTrace) {
- json.WriteKey("backtrace"sv).WriteString(failure.BackTrace);
- }
- json.EndObject();
- }
- json.EndList();
- if (!test.StdOut.empty()) {
- json.WriteKey("system-out"sv).WriteString(test.StdOut);
- }
- if (!test.StdErr.empty()) {
- json.WriteKey("system-err"sv).WriteString(test.StdErr);
- }
- json.EndObject();
- }
- json.EndList();
- json.EndObject();
- }
- json.EndList();
- }
- json.EndObject();
- }
- class TXmlWriter {
- public:
- class TTag {
- friend class TXmlWriter;
- explicit TTag(TXmlWriter* parent, TStringBuf name, size_t indent)
- : Parent(parent)
- , Name(name)
- , Indent(indent)
- {
- Start();
- }
- public:
- TTag(TTag&& tag)
- : Parent(tag.Parent)
- , Name(tag.Name)
- {
- tag.Parent = nullptr;
- }
- ~TTag() {
- if (Parent) {
- End();
- }
- }
- template <class T>
- TTag& Attribute(TStringBuf name, const T& value) {
- return Attribute(name, TStringBuf(ToString(value)));
- }
- TTag& Attribute(TStringBuf name, const TStringBuf& value) {
- Y_ABORT_UNLESS(!HasChildren);
- Parent->Out << ' ';
- Parent->Escape(name);
- Parent->Out << "=\"";
- Parent->Escape(value);
- Parent->Out << '\"';
- return *this;
- }
- TTag Tag(TStringBuf name) {
- if (!HasChildren) {
- HasChildren = true;
- Close();
- }
- return TTag(Parent, name, Indent + 1);
- }
- TTag& Text(TStringBuf text) {
- if (!HasChildren) {
- HasChildren = true;
- Close();
- }
- Parent->Escape(text);
- if (!text.empty() && text.back() == '\n') {
- NewLineBeforeIndent = false;
- }
- return *this;
- }
- private:
- void Start() {
- Parent->Indent(Indent);
- Parent->Out << '<';
- Parent->Escape(Name);
- }
- void Close() {
- Parent->Out << '>';
- }
- void End() {
- if (HasChildren) {
- Parent->Indent(Indent, NewLineBeforeIndent);
- Parent->Out << "</";
- Parent->Escape(Name);
- Parent->Out << ">";
- } else {
- Parent->Out << "/>";
- }
- }
- private:
- TXmlWriter* Parent = nullptr;
- TStringBuf Name;
- size_t Indent = 0;
- bool HasChildren = false;
- bool NewLineBeforeIndent = true;
- };
- public:
- explicit TXmlWriter(const TString& fileName)
- : Out(fileName)
- {
- StartFile();
- }
- ~TXmlWriter() {
- Out << '\n';
- }
- TTag Tag(TStringBuf name) {
- return TTag(this, name, 0);
- }
- private:
- void StartFile() {
- Out << R"(<?xml version="1.0" encoding="UTF-8"?>)"sv;
- }
- void Indent(size_t count, bool insertNewLine = true) {
- if (insertNewLine) {
- Out << '\n';
- }
- while (count--) {
- Out << ' ';
- }
- }
- void Escape(const TStringBuf str) {
- const unsigned char* i = reinterpret_cast<const unsigned char*>(str.data());
- const unsigned char* end = i + str.size();
- while (i < end) {
- wchar32 rune;
- size_t runeLen;
- const RECODE_RESULT result = SafeReadUTF8Char(rune, runeLen, i, end);
- if (result == RECODE_OK) { // string is expected not to have unallowed characters now
- switch (rune) {
- case '\'':
- Out.Write("'");
- break;
- case '\"':
- Out.Write(""");
- break;
- case '<':
- Out.Write("<");
- break;
- case '>':
- Out.Write(">");
- break;
- case '&':
- Out.Write("&");
- break;
- default:
- Out.Write(i, runeLen);
- break;
- }
- i += runeLen;
- }
- }
- }
- private:
- TFileOutput Out;
- };
- void TJUnitProcessor::SerializeToXml() {
- TXmlWriter report(ResultReportFileName);
- TXmlWriter::TTag testSuites = report.Tag("testsuites"sv);
- testSuites
- .Attribute("tests"sv, GetTestsCount())
- .Attribute("failures"sv, GetFailuresCount());
- for (const auto& [suiteName, suite] : Suites) {
- auto testSuite = testSuites.Tag("testsuite"sv);
- testSuite
- .Attribute("name"sv, suiteName)
- .Attribute("id"sv, suiteName)
- .Attribute("tests"sv, suite.GetTestsCount())
- .Attribute("failures"sv, suite.GetFailuresCount())
- .Attribute("time"sv, suite.GetDurationSeconds());
- for (const auto& [testName, test] : suite.Cases) {
- auto testCase = testSuite.Tag("testcase"sv);
- testCase
- .Attribute("classname"sv, suiteName)
- .Attribute("name"sv, testName)
- .Attribute("id"sv, testName)
- .Attribute("time"sv, test.DurationSecods);
- for (const auto& failure : test.Failures) {
- auto testFailure = testCase.Tag("failure"sv);
- testFailure
- .Attribute("message"sv, failure.Message)
- .Attribute("type"sv, "ERROR"sv);
- if (!failure.BackTrace.empty()) {
- testFailure.Text(failure.BackTrace);
- }
- }
- if (!test.StdOut.empty()) {
- testCase.Tag("system-out"sv).Text(test.StdOut);
- }
- if (!test.StdErr.empty()) {
- testCase.Tag("system-err"sv).Text(test.StdErr);
- }
- }
- }
- }
- 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();
- };
- NJson::TJsonValue testsReportJson;
- {
- TFileInput in(TmpReportFile->Name());
- if (!NJson::ReadJsonTree(&in, &testsReportJson)) {
- Cerr << "Failed to read json report for subprocess" << Endl;
- return;
- }
- }
- if (!testsReportJson.IsMap()) {
- Cerr << "Invalid subprocess report format: report is not a map" << Endl;
- return;
- }
- const NJson::TJsonValue* testSuitesJson = nullptr;
- if (!testsReportJson.GetValuePointer("testsuites"sv, &testSuitesJson)) {
- // no tests for some reason
- Cerr << "No tests found in subprocess report" << Endl;
- return;
- }
- if (!testSuitesJson->IsArray()) {
- Cerr << "Invalid subprocess report format: testsuites is not an array" << Endl;
- return;
- }
- for (const NJson::TJsonValue& suiteJson : testSuitesJson->GetArray()) {
- if (!suiteJson.IsMap()) {
- Cerr << "Invalid subprocess report format: suite is not a map" << Endl;
- continue;
- }
- const NJson::TJsonValue* suiteIdJson = nullptr;
- if (!suiteJson.GetValuePointer("id"sv, &suiteIdJson)) {
- Cerr << "Invalid subprocess report format: suite does not have id" << Endl;
- continue;
- }
- const TString& suiteId = suiteIdJson->GetString();
- if (suiteId.empty()) {
- Cerr << "Invalid subprocess report format: suite has empty id" << Endl;
- continue;
- }
- TTestSuite& suiteInfo = Suites[suiteId];
- const NJson::TJsonValue* testCasesJson = nullptr;
- if (!suiteJson.GetValuePointer("testcases"sv, &testCasesJson)) {
- Cerr << "No test cases found in suite \"" << suiteId << "\"" << Endl;
- continue;
- }
- if (!testCasesJson->IsArray()) {
- Cerr << "Invalid subprocess report format: testcases value is not an array" << Endl;
- continue;
- }
- for (const NJson::TJsonValue& testCaseJson : testCasesJson->GetArray()) {
- const NJson::TJsonValue* testCaseIdJson = nullptr;
- if (!testCaseJson.GetValuePointer("id"sv, &testCaseIdJson)) {
- Cerr << "Invalid subprocess report format: test case does not have id" << Endl;
- continue;
- }
- const TString& testCaseId = testCaseIdJson->GetString();
- if (testCaseId.empty()) {
- Cerr << "Invalid subprocess report format: test case has empty id" << Endl;
- continue;
- }
- TTestCase& testCaseInfo = suiteInfo.Cases[testCaseId];
- const NJson::TJsonValue* testCaseDurationJson = nullptr;
- if (testCaseJson.GetValuePointer("time"sv, &testCaseDurationJson)) {
- testCaseInfo.DurationSecods = testCaseDurationJson->GetDouble(); // Will handle also integers as double
- }
- const NJson::TJsonValue* stdOutJson = nullptr;
- if (testCaseJson.GetValuePointer("system-out"sv, &stdOutJson)) {
- testCaseInfo.StdOut = stdOutJson->GetString();
- }
- const NJson::TJsonValue* stdErrJson = nullptr;
- if (testCaseJson.GetValuePointer("system-err"sv, &stdErrJson)) {
- testCaseInfo.StdErr = stdErrJson->GetString();
- }
- const NJson::TJsonValue* failuresJson = nullptr;
- if (!testCaseJson.GetValuePointer("failures"sv, &failuresJson)) {
- continue;
- }
- if (!failuresJson->IsArray()) {
- Cerr << "Invalid subprocess report format: failures is not an array" << Endl;
- continue;
- }
- for (const NJson::TJsonValue& failureJson : failuresJson->GetArray()) {
- TFailure& failureInfo = testCaseInfo.Failures.emplace_back();
- const NJson::TJsonValue* messageJson = nullptr;
- if (failureJson.GetValuePointer("message"sv, &messageJson)) {
- failureInfo.Message = messageJson->GetString();
- }
- const NJson::TJsonValue* backtraceJson = nullptr;
- if (failureJson.GetValuePointer("backtrace"sv, &backtraceJson)) {
- failureInfo.BackTrace = backtraceJson->GetString();
- }
- }
- }
- }
- }
- } // namespace NUnitTest
|