Skip to content

Commit

Permalink
fixed #257 - listeners can be registered with REGISTER_LISTENER and a…
Browse files Browse the repository at this point in the history
…ll registered listeners will be executed before any reporters
  • Loading branch information
Viktor Kirilov committed Aug 11, 2019
1 parent 9260898 commit c241a44
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 26 deletions.
7 changes: 5 additions & 2 deletions doc/markdown/reporters.md
Expand Up @@ -2,7 +2,7 @@

Doctest has a modular reporter/listener system with which users can write their own reporters and register them. The reporter interface can also be used for "listening" to events.

You can list all registered reporters with ```--list-reporters```. There are a few implemented reporters in the framework:
You can list all registered reporters/listeners with ```--list-reporters```. There are a few implemented reporters in the framework:
- ```console``` - streaming - writes normal lines of text with coloring if a capable terminal is detected
- ```xml``` - streaming - writes in xml format tailored to doctest

Expand Down Expand Up @@ -79,9 +79,12 @@ struct MyXmlReporter : public IReporter

// "1" is the priority - used for ordering when multiple reporters/listeners are used
REGISTER_REPORTER("my_xml", 1, MyXmlReporter);

// registering the same class as a reporter and as a listener is nonsense but it's possible
REGISTER_LISTENER("my_listener", 1, MyXmlReporter);
```
Multiple reporters can be used at the same time - just specify them through the ```--reporters=...``` [**command line filtering option**](commandline.md) using commas to separate them like this: ```--reporters=myListener,xml``` and their order of execution will be based on their priority - that is the number "1" in the case of the example reporter above (lower means earlier - the default console/xml reporters from the framework have 0 as their priority and negative numbers are accepted as well).
Multiple reporters can be used at the same time - just specify them through the ```--reporters=...``` [**command line filtering option**](commandline.md) using commas to separate them like this: ```--reporters=myListener,xml``` and their order of execution will be based on their priority - that is the number "1" in the case of the example reporter above (lower means earlier - the default console/xml reporters from the framework have 0 as their priority and negative numbers are accepted as well). All registered listeners (```REGISTER_LISTENER```) will be executed before any reporter - they do not need to be specified and cannot be filtered through the command line.
When implementing a reporter users are advised to follow the comments from the example above and look at the few implemented reporters in the framework itself. Also check out the [**example**](../../examples/all_features/reporters_and_listeners.cpp).
Expand Down
56 changes: 44 additions & 12 deletions doctest/doctest.h
Expand Up @@ -1834,7 +1834,7 @@ struct DOCTEST_INTERFACE IReporter
namespace detail {
typedef IReporter* (*reporterCreatorFunc)(const ContextOptions&);

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

template <typename Reporter>
IReporter* reporterCreator(const ContextOptions& o) {
Expand All @@ -1843,8 +1843,8 @@ namespace detail {
} // namespace detail

template <typename Reporter>
int registerReporter(const char* name, int priority) {
detail::registerReporterImpl(name, priority, detail::reporterCreator<Reporter>);
int registerReporter(const char* name, int priority, bool isReporter) {
detail::registerReporterImpl(name, priority, detail::reporterCreator<Reporter>, isReporter);
return 0;
}
} // namespace doctest
Expand Down Expand Up @@ -2050,10 +2050,16 @@ int registerReporter(const char* name, int priority) {
DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_), \
signature)

// for registering
// for registering reporters
#define DOCTEST_REGISTER_REPORTER(name, priority, reporter) \
DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_REPORTER_)) = \
doctest::registerReporter<reporter>(name, priority); \
doctest::registerReporter<reporter>(name, priority, true); \
DOCTEST_GLOBAL_NO_WARNINGS_END() typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_)

// for registering listeners
#define DOCTEST_REGISTER_LISTENER(name, priority, reporter) \
DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_REPORTER_)) = \
doctest::registerReporter<reporter>(name, priority, false); \
DOCTEST_GLOBAL_NO_WARNINGS_END() typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_)

// for logging
Expand Down Expand Up @@ -2402,6 +2408,7 @@ constexpr T to_lvalue = x;
static inline doctest::String DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_)(signature)

#define DOCTEST_REGISTER_REPORTER(name, priority, reporter)
#define DOCTEST_REGISTER_LISTENER(name, priority, reporter)

#define DOCTEST_INFO(x) ((void)0)
#define DOCTEST_CAPTURE(x) ((void)0)
Expand Down Expand Up @@ -2542,6 +2549,7 @@ constexpr T to_lvalue = x;
#define TEST_SUITE_END DOCTEST_TEST_SUITE_END
#define REGISTER_EXCEPTION_TRANSLATOR DOCTEST_REGISTER_EXCEPTION_TRANSLATOR
#define REGISTER_REPORTER DOCTEST_REGISTER_REPORTER
#define REGISTER_LISTENER DOCTEST_REGISTER_LISTENER
#define INFO DOCTEST_INFO
#define CAPTURE DOCTEST_CAPTURE
#define ADD_MESSAGE_AT DOCTEST_ADD_MESSAGE_AT
Expand Down Expand Up @@ -3508,7 +3516,12 @@ namespace {
// the int (priority) is part of the key for automatic sorting - sadly one can register a
// reporter with a duplicate name and a different priority but hopefully that won't happen often :|
typedef std::map<std::pair<int, String>, reporterCreatorFunc> reporterMap;
reporterMap& getReporters() {

reporterMap& getReporters() {
static reporterMap data;
return data;
}
reporterMap& getListeners() {
static reporterMap data;
return data;
}
Expand Down Expand Up @@ -4813,6 +4826,10 @@ namespace {
void report_query(const QueryData& in) override {
test_run_start();
if(opt.list_reporters) {
for(auto& curr : getListeners())
xml.scopedElement("Listener")
.writeAttribute("priority", curr.first.first)
.writeAttribute("name", curr.first.second);
for(auto& curr : getReporters())
xml.scopedElement("Reporter")
.writeAttribute("priority", curr.first.first)
Expand Down Expand Up @@ -5194,10 +5211,16 @@ namespace {

void printRegisteredReporters() {
printVersion();
s << Color::Cyan << "[doctest] " << Color::None << "listing all registered reporters\n";
for(auto& curr : getReporters())
s << "priority: " << std::setw(5) << curr.first.first
<< " name: " << curr.first.second << "\n";
auto printReporters = [this] (const reporterMap& reporters, const char* type) {
if(reporters.size()) {
s << Color::Cyan << "[doctest] " << Color::None << "listing all registered " << type << "\n";
for(auto& curr : reporters)
s << "priority: " << std::setw(5) << curr.first.first
<< " name: " << curr.first.second << "\n";
}
};
printReporters(getListeners(), "listeners");
printReporters(getReporters(), "reporters");
}

void list_query_results() {
Expand Down Expand Up @@ -5799,6 +5822,12 @@ int Context::run() {
p->reporters_currently_used.push_back(curr.second(*g_cs));
}

// 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));

#ifdef DOCTEST_PLATFORM_WINDOWS
if(isDebuggerActive())
p->reporters_currently_used.push_back(new DebugOutputWindowReporter(*g_cs));
Expand Down Expand Up @@ -6003,8 +6032,11 @@ const String* IReporter::get_stringified_contexts() {
}

namespace detail {
void registerReporterImpl(const char* name, int priority, reporterCreatorFunc c) {
getReporters().insert(reporterMap::value_type(reporterMap::key_type(priority, name), c));
void registerReporterImpl(const char* name, int priority, reporterCreatorFunc c, bool isReporter) {
if(isReporter)
getReporters().insert(reporterMap::value_type(reporterMap::key_type(priority, name), c));
else
getListeners().insert(reporterMap::value_type(reporterMap::key_type(priority, name), c));
}
} // namespace detail

Expand Down
38 changes: 31 additions & 7 deletions doctest/parts/doctest.cpp
Expand Up @@ -811,7 +811,12 @@ namespace {
// the int (priority) is part of the key for automatic sorting - sadly one can register a
// reporter with a duplicate name and a different priority but hopefully that won't happen often :|
typedef std::map<std::pair<int, String>, reporterCreatorFunc> reporterMap;
reporterMap& getReporters() {

reporterMap& getReporters() {
static reporterMap data;
return data;
}
reporterMap& getListeners() {
static reporterMap data;
return data;
}
Expand Down Expand Up @@ -2116,6 +2121,10 @@ namespace {
void report_query(const QueryData& in) override {
test_run_start();
if(opt.list_reporters) {
for(auto& curr : getListeners())
xml.scopedElement("Listener")
.writeAttribute("priority", curr.first.first)
.writeAttribute("name", curr.first.second);
for(auto& curr : getReporters())
xml.scopedElement("Reporter")
.writeAttribute("priority", curr.first.first)
Expand Down Expand Up @@ -2497,10 +2506,16 @@ namespace {

void printRegisteredReporters() {
printVersion();
s << Color::Cyan << "[doctest] " << Color::None << "listing all registered reporters\n";
for(auto& curr : getReporters())
s << "priority: " << std::setw(5) << curr.first.first
<< " name: " << curr.first.second << "\n";
auto printReporters = [this] (const reporterMap& reporters, const char* type) {
if(reporters.size()) {
s << Color::Cyan << "[doctest] " << Color::None << "listing all registered " << type << "\n";
for(auto& curr : reporters)
s << "priority: " << std::setw(5) << curr.first.first
<< " name: " << curr.first.second << "\n";
}
};
printReporters(getListeners(), "listeners");
printReporters(getReporters(), "reporters");
}

void list_query_results() {
Expand Down Expand Up @@ -3102,6 +3117,12 @@ int Context::run() {
p->reporters_currently_used.push_back(curr.second(*g_cs));
}

// 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));

#ifdef DOCTEST_PLATFORM_WINDOWS
if(isDebuggerActive())
p->reporters_currently_used.push_back(new DebugOutputWindowReporter(*g_cs));
Expand Down Expand Up @@ -3306,8 +3327,11 @@ const String* IReporter::get_stringified_contexts() {
}

namespace detail {
void registerReporterImpl(const char* name, int priority, reporterCreatorFunc c) {
getReporters().insert(reporterMap::value_type(reporterMap::key_type(priority, name), c));
void registerReporterImpl(const char* name, int priority, reporterCreatorFunc c, bool isReporter) {
if(isReporter)
getReporters().insert(reporterMap::value_type(reporterMap::key_type(priority, name), c));
else
getListeners().insert(reporterMap::value_type(reporterMap::key_type(priority, name), c));
}
} // namespace detail

Expand Down
18 changes: 13 additions & 5 deletions doctest/parts/doctest_fwd.h
Expand Up @@ -1831,7 +1831,7 @@ struct DOCTEST_INTERFACE IReporter
namespace detail {
typedef IReporter* (*reporterCreatorFunc)(const ContextOptions&);

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

template <typename Reporter>
IReporter* reporterCreator(const ContextOptions& o) {
Expand All @@ -1840,8 +1840,8 @@ namespace detail {
} // namespace detail

template <typename Reporter>
int registerReporter(const char* name, int priority) {
detail::registerReporterImpl(name, priority, detail::reporterCreator<Reporter>);
int registerReporter(const char* name, int priority, bool isReporter) {
detail::registerReporterImpl(name, priority, detail::reporterCreator<Reporter>, isReporter);
return 0;
}
} // namespace doctest
Expand Down Expand Up @@ -2047,10 +2047,16 @@ int registerReporter(const char* name, int priority) {
DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_), \
signature)

// for registering
// for registering reporters
#define DOCTEST_REGISTER_REPORTER(name, priority, reporter) \
DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_REPORTER_)) = \
doctest::registerReporter<reporter>(name, priority); \
doctest::registerReporter<reporter>(name, priority, true); \
DOCTEST_GLOBAL_NO_WARNINGS_END() typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_)

// for registering listeners
#define DOCTEST_REGISTER_LISTENER(name, priority, reporter) \
DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_REPORTER_)) = \
doctest::registerReporter<reporter>(name, priority, false); \
DOCTEST_GLOBAL_NO_WARNINGS_END() typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_)

// for logging
Expand Down Expand Up @@ -2399,6 +2405,7 @@ constexpr T to_lvalue = x;
static inline doctest::String DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_)(signature)

#define DOCTEST_REGISTER_REPORTER(name, priority, reporter)
#define DOCTEST_REGISTER_LISTENER(name, priority, reporter)

#define DOCTEST_INFO(x) ((void)0)
#define DOCTEST_CAPTURE(x) ((void)0)
Expand Down Expand Up @@ -2539,6 +2546,7 @@ constexpr T to_lvalue = x;
#define TEST_SUITE_END DOCTEST_TEST_SUITE_END
#define REGISTER_EXCEPTION_TRANSLATOR DOCTEST_REGISTER_EXCEPTION_TRANSLATOR
#define REGISTER_REPORTER DOCTEST_REGISTER_REPORTER
#define REGISTER_LISTENER DOCTEST_REGISTER_LISTENER
#define INFO DOCTEST_INFO
#define CAPTURE DOCTEST_CAPTURE
#define ADD_MESSAGE_AT DOCTEST_ADD_MESSAGE_AT
Expand Down
3 changes: 3 additions & 0 deletions examples/all_features/reporters_and_listeners.cpp
Expand Up @@ -78,3 +78,6 @@ struct MyXmlReporter : public IReporter

// "1" is the priority - used for ordering when multiple reporters/listeners are used
REGISTER_REPORTER("my_xml", 1, MyXmlReporter);

// registering the same class as a reporter and as a listener is nonsense but it's possible
REGISTER_LISTENER("my_listener", 1, MyXmlReporter);
2 changes: 2 additions & 0 deletions examples/all_features/test_output/list_reporters.txt
@@ -1,3 +1,5 @@
[doctest] listing all registered listeners
priority: 1 name: my_listener
[doctest] listing all registered reporters
priority: 0 name: console
priority: 0 name: xml
Expand Down
1 change: 1 addition & 0 deletions examples/all_features/test_output/list_reporters_xml.txt
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<doctest binary="all_features">
<Options order_by="file" rand_seed="324" first="0" last="4294967295" abort_after="0" subcase_filter_levels="2147483647" case_sensitive="false" no_throw="false" no_skip="false"/>
<Listener priority="1" name="my_listener"/>
<Reporter priority="0" name="console"/>
<Reporter priority="0" name="xml"/>
<Reporter priority="1" name="my_xml"/>
Expand Down

0 comments on commit c241a44

Please sign in to comment.