From 59087f74d9ee8ed956d3df09b044c502b71862fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ho=C5=99e=C5=88ovsk=C3=BD?= Date: Mon, 19 Nov 2018 23:06:06 +0100 Subject: [PATCH] Fix CAPTURE macro for nontrivial uses The previous implemetation was just plain broken for most of possible uses, the new one should work (even though it is ugly as all hell, and should be improved ASAP). Fixes #1436 --- docs/logging.md | 33 +++++++--- include/internal/catch_message.cpp | 50 ++++++++++++--- include/internal/catch_message.h | 8 +-- .../Baselines/compact.sw.approved.txt | 2 + .../Baselines/console.std.approved.txt | 4 +- .../Baselines/console.sw.approved.txt | 36 ++++++++++- .../SelfTest/Baselines/junit.sw.approved.txt | 4 +- .../SelfTest/Baselines/xml.sw.approved.txt | 52 +++++++++++++++- .../SelfTest/UsageTests/Message.tests.cpp | 61 +++++++++++++++++-- 9 files changed, 217 insertions(+), 33 deletions(-) diff --git a/docs/logging.md b/docs/logging.md index 39ae5c7a..423ce6a8 100644 --- a/docs/logging.md +++ b/docs/logging.md @@ -57,20 +57,37 @@ The message is reported and the test case fails. AS `FAIL`, but does not abort the test -## Quickly capture a variable value +## Quickly capture value of variables or expressions -**CAPTURE(** _expression_ **)** +**CAPTURE(** _expression1_, _expression2_, ... **)** -Sometimes you just want to log the name and value of a variable. While you can easily do this with the INFO macro, above, as a convenience the CAPTURE macro handles the stringising of the variable name for you (actually it works with any expression, not just variables). +Sometimes you just want to log a value of variable, or expression. For +convenience, we provide the `CAPTURE` macro, that can take a variable, +or an expression, and prints out that variable/expression and its value +at the time of capture. -E.g. -```c++ -CAPTURE( theAnswer ); +e.g. `CAPTURE( theAnswer );` will log message "theAnswer := 42", while +```cpp +int a = 1, b = 2, c = 3; +CAPTURE( a, b, c, a + b, c > b, a == 1); +``` +will log a total of 6 messages: +``` +a := 1 +b := 2 +c := 3 +a + b := 3 +c > b := true +a == 1 := true ``` -This would log something like: +You can also capture expressions that use commas inside parentheses +(e.g. function calls), brackets, or braces (e.g. initializers). To +properly capture expression that contains template parameters list +(in other words, it contains commas between angle brackets), you need +to enclose the expression inside parentheses: +`CAPTURE( (std::pair{1, 2}) );` -
"theAnswer := 42"
--- diff --git a/include/internal/catch_message.cpp b/include/internal/catch_message.cpp index df905bf6..98a4dae1 100644 --- a/include/internal/catch_message.cpp +++ b/include/internal/catch_message.cpp @@ -11,6 +11,7 @@ #include "catch_uncaught_exceptions.h" #include +#include namespace Catch { @@ -60,19 +61,48 @@ namespace Catch { Capturer::Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names ) { - auto start = std::string::npos; - for( size_t pos = 0; pos <= names.size(); ++pos ) { + auto trimmed = [&] (size_t start, size_t end) { + while (names[start] == ',' || isspace(names[start])) { + ++start; + } + while (names[end] == ',' || isspace(names[end])) { + --end; + } + return names.substr(start, end - start + 1); + }; + + size_t start = 0; + std::stack openings; + for (size_t pos = 0; pos < names.size(); ++pos) { char c = names[pos]; - if( pos == names.size() || c == ' ' || c == '\t' || c == ',' || c == ']' ) { - if( start != std::string::npos ) { - m_messages.push_back( MessageInfo( macroName, lineInfo, resultType ) ); - m_messages.back().message = names.substr( start, pos-start) + " := "; - start = std::string::npos; + switch (c) { + case '[': + case '{': + case '(': + // It is basically impossible to disambiguate between + // comparison and start of template args in this context +// case '<': + openings.push(c); + break; + case ']': + case '}': + case ')': +// case '>': + openings.pop(); + break; + case ',': + if (start != pos && openings.size() == 0) { + m_messages.emplace_back(macroName, lineInfo, resultType); + m_messages.back().message = trimmed(start, pos); + m_messages.back().message += " := "; + start = pos; } } - else if( c != '[' && c != ']' && start == std::string::npos ) - start = pos; } + assert(openings.size() == 0 && "Mismatched openings"); + m_messages.emplace_back(macroName, lineInfo, resultType); + m_messages.back().message = trimmed(start, names.size() - 1); + m_messages.back().message += " := "; } Capturer::~Capturer() { if ( !uncaught_exceptions() ){ @@ -82,7 +112,7 @@ namespace Catch { } } - void Capturer::captureValue( size_t index, StringRef value ) { + void Capturer::captureValue( size_t index, std::string const& value ) { assert( index < m_messages.size() ); m_messages[index].message += value; m_resultCapture.pushScopedMessage( m_messages[index] ); diff --git a/include/internal/catch_message.h b/include/internal/catch_message.h index 73f9efd1..e81069bf 100644 --- a/include/internal/catch_message.h +++ b/include/internal/catch_message.h @@ -77,16 +77,16 @@ namespace Catch { Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names ); ~Capturer(); - void captureValue( size_t index, StringRef value ); + void captureValue( size_t index, std::string const& value ); template - void captureValues( size_t index, T&& value ) { + void captureValues( size_t index, T const& value ) { captureValue( index, Catch::Detail::stringify( value ) ); } template - void captureValues( size_t index, T&& value, Ts&&... values ) { - captureValues( index, value ); + void captureValues( size_t index, T const& value, Ts const&... values ) { + captureValue( index, Catch::Detail::stringify(value) ); captureValues( index+1, values... ); } }; diff --git a/projects/SelfTest/Baselines/compact.sw.approved.txt b/projects/SelfTest/Baselines/compact.sw.approved.txt index 496ead91..1b32b42e 100644 --- a/projects/SelfTest/Baselines/compact.sw.approved.txt +++ b/projects/SelfTest/Baselines/compact.sw.approved.txt @@ -228,6 +228,8 @@ Approx.tests.cpp:: passed: NAN != Approx(NAN) for: nanf != Approx( Approx.tests.cpp:: passed: !(NAN == Approx(NAN)) for: !(nanf == Approx( nan )) Tricky.tests.cpp:: passed: y.v == 0 for: 0 == 0 Tricky.tests.cpp:: passed: 0 == y.v for: 0 == 0 +Message.tests.cpp:: passed: with 7 messages: 'a := 1' and 'b := 2' and 'c := 3' and 'a + b := 3' and 'a+b := 3' and 'c > b := true' and 'a == 1 := true' +Message.tests.cpp:: passed: with 7 messages: 'std::vector{1, 2, 3}[0, 1, 2] := 3' and 'std::vector{1, 2, 3}[(0, 1)] := 2' and 'std::vector{1, 2, 3}[0] := 1' and '(helper_1436{12, -12}) := { 12, -12 }' and '(helper_1436(-12, 12)) := { -12, 12 }' and '(1, 2) := 2' and '(2, 3) := 3' ToStringGeneral.tests.cpp:: passed: true with 1 message: 'i := 2' ToStringGeneral.tests.cpp:: passed: true with 1 message: '3' ToStringGeneral.tests.cpp:: passed: tab == '\t' for: '\t' == '\t' diff --git a/projects/SelfTest/Baselines/console.std.approved.txt b/projects/SelfTest/Baselines/console.std.approved.txt index f48a98ec..5e82417e 100644 --- a/projects/SelfTest/Baselines/console.std.approved.txt +++ b/projects/SelfTest/Baselines/console.std.approved.txt @@ -1126,6 +1126,6 @@ due to unexpected exception with message: Why would you throw a std::string? =============================================================================== -test cases: 226 | 170 passed | 52 failed | 4 failed as expected -assertions: 1308 | 1176 passed | 111 failed | 21 failed as expected +test cases: 228 | 172 passed | 52 failed | 4 failed as expected +assertions: 1310 | 1178 passed | 111 failed | 21 failed as expected diff --git a/projects/SelfTest/Baselines/console.sw.approved.txt b/projects/SelfTest/Baselines/console.sw.approved.txt index e99ea050..28638425 100644 --- a/projects/SelfTest/Baselines/console.sw.approved.txt +++ b/projects/SelfTest/Baselines/console.sw.approved.txt @@ -2108,6 +2108,38 @@ Tricky.tests.cpp:: PASSED: with expansion: 0 == 0 +------------------------------------------------------------------------------- +CAPTURE can deal with complex expressions +------------------------------------------------------------------------------- +Message.tests.cpp: +............................................................................... + +Message.tests.cpp:: PASSED: +with messages: + a := 1 + b := 2 + c := 3 + a + b := 3 + a+b := 3 + c > b := true + a == 1 := true + +------------------------------------------------------------------------------- +CAPTURE can deal with complex expressions involving commas +------------------------------------------------------------------------------- +Message.tests.cpp: +............................................................................... + +Message.tests.cpp:: PASSED: +with messages: + std::vector{1, 2, 3}[0, 1, 2] := 3 + std::vector{1, 2, 3}[(0, 1)] := 2 + std::vector{1, 2, 3}[0] := 1 + (helper_1436{12, -12}) := { 12, -12 } + (helper_1436(-12, 12)) := { -12, 12 } + (1, 2) := 2 + (2, 3) := 3 + ------------------------------------------------------------------------------- Capture and info messages Capture should stringify like assertions @@ -10432,6 +10464,6 @@ Misc.tests.cpp: Misc.tests.cpp:: PASSED: =============================================================================== -test cases: 226 | 157 passed | 65 failed | 4 failed as expected -assertions: 1322 | 1176 passed | 125 failed | 21 failed as expected +test cases: 228 | 159 passed | 65 failed | 4 failed as expected +assertions: 1324 | 1178 passed | 125 failed | 21 failed as expected diff --git a/projects/SelfTest/Baselines/junit.sw.approved.txt b/projects/SelfTest/Baselines/junit.sw.approved.txt index 5984b3db..cb03cdb7 100644 --- a/projects/SelfTest/Baselines/junit.sw.approved.txt +++ b/projects/SelfTest/Baselines/junit.sw.approved.txt @@ -1,7 +1,7 @@ - + @@ -141,6 +141,8 @@ Exception.tests.cpp: + + diff --git a/projects/SelfTest/Baselines/xml.sw.approved.txt b/projects/SelfTest/Baselines/xml.sw.approved.txt index d89b4e18..7e02b772 100644 --- a/projects/SelfTest/Baselines/xml.sw.approved.txt +++ b/projects/SelfTest/Baselines/xml.sw.approved.txt @@ -1986,6 +1986,54 @@ + + + a := 1 + + + b := 2 + + + c := 3 + + + a + b := 3 + + + a+b := 3 + + + c > b := true + + + a == 1 := true + + + + + + std::vector<int>{1, 2, 3}[0, 1, 2] := 3 + + + std::vector<int>{1, 2, 3}[(0, 1)] := 2 + + + std::vector<int>{1, 2, 3}[0] := 1 + + + (helper_1436<int, int>{12, -12}) := { 12, -12 } + + + (helper_1436<int, int>(-12, 12)) := { -12, 12 } + + + (1, 2) := 2 + + + (2, 3) := 3 + + +
@@ -12051,7 +12099,7 @@ loose text artifact
- + - + diff --git a/projects/SelfTest/UsageTests/Message.tests.cpp b/projects/SelfTest/UsageTests/Message.tests.cpp index f3ac02a1..002fb875 100644 --- a/projects/SelfTest/UsageTests/Message.tests.cpp +++ b/projects/SelfTest/UsageTests/Message.tests.cpp @@ -9,10 +9,6 @@ #include "catch.hpp" #include -#ifdef __clang__ -#pragma clang diagnostic ignored "-Wc++98-compat-pedantic" -#endif - TEST_CASE( "INFO and WARN do not abort tests", "[messages][.]" ) { INFO( "this is a " << "message" ); // This should output the message if a failure occurs WARN( "this is a " << "warning" ); // This should always output the message but then continue @@ -135,3 +131,60 @@ TEST_CASE( "Pointers can be converted to strings", "[messages][.][approvals]" ) WARN( "actual address of p: " << &p ); WARN( "toString(p): " << ::Catch::Detail::stringify( &p ) ); } + +TEST_CASE( "CAPTURE can deal with complex expressions", "[messages][capture]" ) { + int a = 1; + int b = 2; + int c = 3; + CAPTURE( a, b, c, a + b, a+b, c > b, a == 1 ); + SUCCEED(); +} + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-value" // In (1, 2), the "1" is unused ... +#endif +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-value" // All the comma operators are side-effect free +#endif +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4709) // comma in indexing operator +#endif + +template +struct helper_1436 { + helper_1436(T1 t1, T2 t2): + t1{ t1 }, + t2{ t2 } + {} + T1 t1; + T2 t2; +}; + +template +std::ostream& operator<<(std::ostream& out, helper_1436 const& helper) { + out << "{ " << helper.t1 << ", " << helper.t2 << " }"; + return out; +} + +TEST_CASE("CAPTURE can deal with complex expressions involving commas", "[messages][capture]") { + CAPTURE(std::vector{1, 2, 3}[0, 1, 2], + std::vector{1, 2, 3}[(0, 1)], + std::vector{1, 2, 3}[0]); + CAPTURE((helper_1436{12, -12}), + (helper_1436(-12, 12))); + CAPTURE( (1, 2), (2, 3) ); + SUCCEED(); +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif +#ifdef _MSC_VER +#pragma warning(pop) +#endif