From c241a44018c1bc6ba0c16d5d947fed1f06e94ce0 Mon Sep 17 00:00:00 2001 From: Viktor Kirilov Date: Sun, 11 Aug 2019 19:37:06 +0300 Subject: [PATCH] fixed #257 - listeners can be registered with REGISTER_LISTENER and all registered listeners will be executed before any reporters --- doc/markdown/reporters.md | 7 ++- doctest/doctest.h | 56 +++++++++++++++---- doctest/parts/doctest.cpp | 38 ++++++++++--- doctest/parts/doctest_fwd.h | 18 ++++-- .../all_features/reporters_and_listeners.cpp | 3 + .../test_output/list_reporters.txt | 2 + .../test_output/list_reporters_xml.txt | 1 + 7 files changed, 99 insertions(+), 26 deletions(-) diff --git a/doc/markdown/reporters.md b/doc/markdown/reporters.md index 11196e36a..5f4432db5 100644 --- a/doc/markdown/reporters.md +++ b/doc/markdown/reporters.md @@ -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 @@ -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). diff --git a/doctest/doctest.h b/doctest/doctest.h index c75b5c69d..ca4987a4b 100644 --- a/doctest/doctest.h +++ b/doctest/doctest.h @@ -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 IReporter* reporterCreator(const ContextOptions& o) { @@ -1843,8 +1843,8 @@ namespace detail { } // namespace detail template -int registerReporter(const char* name, int priority) { - detail::registerReporterImpl(name, priority, detail::reporterCreator); +int registerReporter(const char* name, int priority, bool isReporter) { + detail::registerReporterImpl(name, priority, detail::reporterCreator, isReporter); return 0; } } // namespace doctest @@ -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(name, priority); \ + doctest::registerReporter(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(name, priority, false); \ DOCTEST_GLOBAL_NO_WARNINGS_END() typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) // for logging @@ -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) @@ -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 @@ -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, reporterCreatorFunc> reporterMap; - reporterMap& getReporters() { + + reporterMap& getReporters() { + static reporterMap data; + return data; + } + reporterMap& getListeners() { static reporterMap data; return data; } @@ -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) @@ -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() { @@ -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)); @@ -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 diff --git a/doctest/parts/doctest.cpp b/doctest/parts/doctest.cpp index fc642dcab..cd04ff770 100644 --- a/doctest/parts/doctest.cpp +++ b/doctest/parts/doctest.cpp @@ -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, reporterCreatorFunc> reporterMap; - reporterMap& getReporters() { + + reporterMap& getReporters() { + static reporterMap data; + return data; + } + reporterMap& getListeners() { static reporterMap data; return data; } @@ -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) @@ -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() { @@ -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)); @@ -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 diff --git a/doctest/parts/doctest_fwd.h b/doctest/parts/doctest_fwd.h index 67957e4e0..3c28460d6 100644 --- a/doctest/parts/doctest_fwd.h +++ b/doctest/parts/doctest_fwd.h @@ -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 IReporter* reporterCreator(const ContextOptions& o) { @@ -1840,8 +1840,8 @@ namespace detail { } // namespace detail template -int registerReporter(const char* name, int priority) { - detail::registerReporterImpl(name, priority, detail::reporterCreator); +int registerReporter(const char* name, int priority, bool isReporter) { + detail::registerReporterImpl(name, priority, detail::reporterCreator, isReporter); return 0; } } // namespace doctest @@ -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(name, priority); \ + doctest::registerReporter(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(name, priority, false); \ DOCTEST_GLOBAL_NO_WARNINGS_END() typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) // for logging @@ -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) @@ -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 diff --git a/examples/all_features/reporters_and_listeners.cpp b/examples/all_features/reporters_and_listeners.cpp index 00dcf83e1..5d38a8ddc 100644 --- a/examples/all_features/reporters_and_listeners.cpp +++ b/examples/all_features/reporters_and_listeners.cpp @@ -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); diff --git a/examples/all_features/test_output/list_reporters.txt b/examples/all_features/test_output/list_reporters.txt index 952db2cec..5dceedb6e 100644 --- a/examples/all_features/test_output/list_reporters.txt +++ b/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 diff --git a/examples/all_features/test_output/list_reporters_xml.txt b/examples/all_features/test_output/list_reporters_xml.txt index 9e07dae62..bd88f8374 100644 --- a/examples/all_features/test_output/list_reporters_xml.txt +++ b/examples/all_features/test_output/list_reporters_xml.txt @@ -1,6 +1,7 @@ +