Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow specifying separate outputs for reporters #669

Draft
wants to merge 2 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
134 changes: 90 additions & 44 deletions doctest/doctest.h
Original file line number Diff line number Diff line change
Expand Up @@ -869,13 +869,11 @@ namespace detail {

struct ContextOptions //!OCLINT too many fields
{
std::ostream* cout = nullptr; // stdout stream
String binary_name; // the test binary name

const detail::TestCase* currentTest = nullptr;

// == parameters from the command line
String out; // output filename
String order_by; // how tests should be ordered
unsigned rand_seed; // the seed for rand ordering

Expand Down Expand Up @@ -2054,13 +2052,13 @@ struct DOCTEST_INTERFACE IReporter
};

namespace detail {
using reporterCreatorFunc = IReporter* (*)(const ContextOptions&);
using reporterCreatorFunc = IReporter* (*)(const ContextOptions&, std::ostream*);

DOCTEST_INTERFACE void registerReporterImpl(const char* name, int prio, reporterCreatorFunc c, bool isReporter);

template <typename Reporter>
IReporter* reporterCreator(const ContextOptions& o) {
return new Reporter(o);
IReporter* reporterCreator(const ContextOptions& o, std::ostream* ostr) {
return new Reporter(o, ostr);
}
} // namespace detail

Expand Down Expand Up @@ -3112,6 +3110,7 @@ DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN
#include <math.h>
#endif // __BORLANDC__
#include <new>
#include <memory>
#include <cstdio>
#include <cstdlib>
#include <cstring>
Expand Down Expand Up @@ -3315,7 +3314,7 @@ namespace detail {

namespace timer_large_integer
{

#if defined(DOCTEST_PLATFORM_WINDOWS)
using type = ULONGLONG;
#else // DOCTEST_PLATFORM_WINDOWS
Expand Down Expand Up @@ -3462,6 +3461,7 @@ using ticks_t = timer_large_integer::type;
MultiLaneAtomic<int> numAssertsFailedCurrentTest_atomic;

std::vector<std::vector<String>> filters = decltype(filters)(9); // 9 different filters
std::map<String, String> out; // map of filters to output filenames

std::vector<IReporter*> reporters_currently_used;

Expand Down Expand Up @@ -4105,6 +4105,16 @@ namespace {
return false;
}

// checks if the name matches any of the filters, and returns the matching output stream
std::fstream* matchesAny(const char* name,
std::map<String, std::unique_ptr<std::fstream>>& output_filters,
bool caseSensitive) {
for (auto& curr : output_filters)
if (wildcmp(name, curr.first.c_str(), caseSensitive))
return curr.second.get();
return nullptr;
}

unsigned long long hash(unsigned long long a, unsigned long long b) {
return (a << 5) + b;
}
Expand Down Expand Up @@ -4949,7 +4959,7 @@ namespace detail {
m_string = tlssPop();
logged = true;
}

DOCTEST_ITERATE_THROUGH_REPORTERS(log_message, *this);

const bool isWarn = m_severity & assertType::is_warn;
Expand Down Expand Up @@ -5363,10 +5373,11 @@ namespace {
const ContextOptions& opt;
const TestCaseData* tc = nullptr;

XmlReporter(const ContextOptions& co)
: xml(*co.cout)
XmlReporter(const ContextOptions& co, std::ostream* ostr = nullptr)
: xml(ostr ? *ostr : std::cout)
, opt(co) {}


void log_contexts() {
int num_contexts = get_num_active_contexts();
if(num_contexts) {
Expand Down Expand Up @@ -5500,7 +5511,7 @@ namespace {
test_case_start_impl(in);
xml.ensureTagClosed();
}

void test_case_reenter(const TestCaseData&) override {}

void test_case_end(const CurrentTestCaseStats& st) override {
Expand Down Expand Up @@ -5737,8 +5748,8 @@ namespace {
const ContextOptions& opt;
const TestCaseData* tc = nullptr;

JUnitReporter(const ContextOptions& co)
: xml(*co.cout)
JUnitReporter(const ContextOptions& co, std::ostream* ostr = nullptr)
: xml(ostr ? *ostr : std::cout)
, opt(co) {}

unsigned line(unsigned l) const { return opt.no_line_numbers ? 0 : l; }
Expand Down Expand Up @@ -5894,12 +5905,8 @@ namespace {
const ContextOptions& opt;
const TestCaseData* tc;

ConsoleReporter(const ContextOptions& co)
: s(*co.cout)
, opt(co) {}

ConsoleReporter(const ContextOptions& co, std::ostream& ostr)
: s(ostr)
ConsoleReporter(const ContextOptions& co, std::ostream* ostr = nullptr)
: s(ostr ? *ostr : std::cout)
, opt(co) {}

// =========================================================================================
Expand Down Expand Up @@ -6055,8 +6062,8 @@ namespace {
<< Whitespace(sizePrefixDisplay*1) << "filters OUT subcases by their name\n";
s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "r, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "reporters=<filters> "
<< Whitespace(sizePrefixDisplay*1) << "reporters to use (console is default)\n";
s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "o, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "out=<string> "
<< Whitespace(sizePrefixDisplay*1) << "output filename\n";
s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "o, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "out=<filter>[<string>]... "
<< Whitespace(sizePrefixDisplay*1) << "output filenames for reporters\n";
s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ob, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "order-by=<string> "
<< Whitespace(sizePrefixDisplay*1) << "how the tests should be ordered\n";
s << Whitespace(sizePrefixDisplay*3) << " <string> - [file/suite/name/rand/none]\n";
Expand Down Expand Up @@ -6222,7 +6229,7 @@ namespace {
subcasesStack.clear();
currentSubcaseLevel = 0;
}

void test_case_reenter(const TestCaseData&) override {
subcasesStack.clear();
}
Expand Down Expand Up @@ -6343,7 +6350,7 @@ namespace {
DOCTEST_THREAD_LOCAL static std::ostringstream oss;

DebugOutputWindowReporter(const ContextOptions& co)
: ConsoleReporter(co, oss) {}
: ConsoleReporter(co, &oss) {}

#define DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(func, type, arg) \
void func(type arg) override { \
Expand Down Expand Up @@ -6472,6 +6479,42 @@ namespace {
return false;
}

bool parseCommaSepArgsTwoPart(int argc, const char* const* argv,
const char* pattern,
std::map<String, String>& res) {
std::vector<String> args;
if (!parseCommaSepArgs(argc, argv, pattern, args)) {
return false;
}

for (auto& str : args) {
if (str.size() == 0)
return false;

const auto close = str.rfind(']');
if (close == str.size()-1) {
const auto open = str.rfind('[', close);
if (open != str.npos) {
auto filter = str.substr(0, open);
auto filename = str.substr(open+1, close-open-1);
auto where_inserted = res.emplace(filter, filename);
// already have an output for this filter
if (!where_inserted.second)
return false;
} else {
return false;
}
} else {
auto where_inserted = res.emplace("*", str);
// already have an output for the '*' filter
if (!where_inserted.second)
return false;
}
}

return true;
}

enum optionType
{
option_bool,
Expand Down Expand Up @@ -6556,6 +6599,8 @@ void Context::parseArgs(int argc, const char* const* argv, bool withDefaults) {
parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sce=", p->filters[7]);
parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "reporters=", p->filters[8]);
parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "r=", p->filters[8]);
parseCommaSepArgsTwoPart(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "out=", p->out);
parseCommaSepArgsTwoPart(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "o=", p->out);
// clang-format on

int intRes = 0;
Expand Down Expand Up @@ -6585,7 +6630,6 @@ void Context::parseArgs(int argc, const char* const* argv, bool withDefaults) {
p->var = strRes

// clang-format off
DOCTEST_PARSE_STR_OPTION("out", "o", out, "");
DOCTEST_PARSE_STR_OPTION("order-by", "ob", order_by, "file");
DOCTEST_PARSE_INT_OPTION("rand-seed", "rs", rand_seed, 0);

Expand Down Expand Up @@ -6692,8 +6736,6 @@ void Context::setAsDefaultForAssertsOutOfTestCases() { g_cs = p; }

void Context::setAssertHandler(detail::assert_handler ah) { p->ah = ah; }

void Context::setCout(std::ostream* out) { p->cout = out; }

static class DiscardOStream : public std::ostream
{
private:
Expand Down Expand Up @@ -6730,27 +6772,21 @@ int Context::run() {
g_no_colors = p->no_colors;
p->resetRunData();

std::fstream fstr;
if(p->cout == nullptr) {
if(p->quiet) {
p->cout = &discardOut;
} else if(p->out.size()) {
// to a file if specified
fstr.open(p->out.c_str(), std::fstream::out);
p->cout = &fstr;
} else {
// stdout by default
p->cout = &std::cout;
}
std::map<String, std::unique_ptr<std::fstream>> reporter_streams;
for (const auto& filter_output : p->out) {
const auto& filter = filter_output.first;
const auto& output = filter_output.second;
reporter_streams[filter].reset(new std::fstream(output.c_str(), std::fstream::out));
}

FatalConditionHandler::allocateAltStackMem();

auto cleanup_and_return = [&]() {
FatalConditionHandler::freeAltStackMem();

if(fstr.is_open())
fstr.close();
for (auto& fstr : reporter_streams)
if(fstr.second->is_open())
fstr.second->close();

// restore context
g_cs = old_cs;
Expand All @@ -6772,15 +6808,25 @@ int Context::run() {

// check to see if any of the registered reporters has been selected
for(auto& curr : getReporters()) {
if(matchesAny(curr.first.second.c_str(), p->filters[8], false, p->case_sensitive))
p->reporters_currently_used.push_back(curr.second(*g_cs));
// stdout by default
std::ostream* cout = &std::cout;
if (p->quiet) {
cout = &discardOut;
}
else if(std::ostream* out = matchesAny(curr.first.second.c_str(), reporter_streams, p->case_sensitive)) {
cout = out;
}

if(matchesAny(curr.first.second.c_str(), p->filters[8], false, p->case_sensitive)) {
p->reporters_currently_used.push_back(curr.second(*g_cs, cout));
}
}

// TODO: check if there is nothing in reporters_currently_used

// prepend all listeners
for(auto& curr : getListeners())
p->reporters_currently_used.insert(p->reporters_currently_used.begin(), curr.second(*g_cs));
p->reporters_currently_used.insert(p->reporters_currently_used.begin(), curr.second(*g_cs, nullptr));

#ifdef DOCTEST_PLATFORM_WINDOWS
if(isDebuggerActive() && p->no_debug_output == false)
Expand Down Expand Up @@ -6905,7 +6951,7 @@ int Context::run() {
DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_start, tc);

p->timer.start();

bool run_test = true;

do {
Expand Down Expand Up @@ -6946,7 +6992,7 @@ DOCTEST_MSVC_SUPPRESS_WARNING_POP
run_test = false;
p->failure_flags |= TestCaseFailureReason::TooManyFailedAsserts;
}

if(!p->nextSubcaseStack.empty() && run_test)
DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_reenter, tc);
if(p->nextSubcaseStack.empty())
Expand Down