ColourGuard is no longer constructed engaged

Forcing it to be engaged explicitly, either via `op<<`, or by
`ColourGuard::engage`, fixes an issue with multiple `ColourGuard`s
being constructed in a single expression. Because the construction
of the `ColourGuard` instances can happen in arbitrary order,
colours would be applied in arbitrary order too. However, a chain
of `op<<`s has strict call orders, fixing this issue.
This commit is contained in:
Martin Hořeňovský 2022-03-21 23:20:08 +01:00
parent 4d8acafecb
commit 081a1e9aba
No known key found for this signature in database
GPG Key ID: DE48307B8B0D381A
7 changed files with 126 additions and 58 deletions

View File

@ -153,7 +153,7 @@ namespace Catch {
m_startupExceptions = true; m_startupExceptions = true;
auto errStream = makeStream( "%stderr" ); auto errStream = makeStream( "%stderr" );
auto colourImpl = makeColourImpl( &config(), errStream.get() ); auto colourImpl = makeColourImpl( &config(), errStream.get() );
auto guard = colourImpl->startColour( Colour::Red ); auto guard = colourImpl->guardColour( Colour::Red );
errStream->stream() << "Errors occurred during startup!" << '\n'; errStream->stream() << "Errors occurred during startup!" << '\n';
// iterate over all exceptions and notify user // iterate over all exceptions and notify user
for ( const auto& ex_ptr : exceptions ) { for ( const auto& ex_ptr : exceptions ) {
@ -200,7 +200,7 @@ namespace Catch {
auto colour = makeColourImpl( &config(), errStream.get() ); auto colour = makeColourImpl( &config(), errStream.get() );
errStream->stream() errStream->stream()
<< colour->startColour( Colour::Red ) << colour->guardColour( Colour::Red )
<< "\nError(s) in input:\n" << "\nError(s) in input:\n"
<< TextFlow::Column( result.errorMessage() ).indent( 2 ) << TextFlow::Column( result.errorMessage() ).indent( 2 )
<< "\n\n"; << "\n\n";

View File

@ -27,7 +27,7 @@ namespace Catch {
ColourImpl::~ColourImpl() = default; ColourImpl::~ColourImpl() = default;
ColourImpl::ColourGuard ColourImpl::startColour( Colour::Code colourCode ) { ColourImpl::ColourGuard ColourImpl::guardColour( Colour::Code colourCode ) {
return ColourGuard(colourCode, this ); return ColourGuard(colourCode, this );
} }
@ -45,27 +45,53 @@ namespace Catch {
} // namespace } // namespace
void ColourImpl::ColourGuard::engageImpl( std::ostream& stream ) {
assert( &stream == &m_colourImpl->m_stream->stream() &&
"Engaging colour guard for different stream than used by the "
"parent colour implementation" );
static_cast<void>( stream );
m_engaged = true;
m_colourImpl->use( m_code );
}
ColourImpl::ColourGuard::ColourGuard( Colour::Code code, ColourImpl::ColourGuard::ColourGuard( Colour::Code code,
ColourImpl const* colour ): ColourImpl const* colour ):
m_colourImpl( colour ) { m_colourImpl( colour ), m_code( code ) {
m_colourImpl->use( code );
} }
ColourImpl::ColourGuard::ColourGuard( ColourGuard&& rhs ): ColourImpl::ColourGuard::ColourGuard( ColourGuard&& rhs ) noexcept:
m_colourImpl( rhs.m_colourImpl ) { m_colourImpl( rhs.m_colourImpl ),
rhs.m_moved = true; m_code( rhs.m_code ),
m_engaged( rhs.m_engaged ) {
rhs.m_engaged = false;
} }
ColourImpl::ColourGuard& ColourImpl::ColourGuard&
ColourImpl::ColourGuard::operator=( ColourGuard&& rhs ) { ColourImpl::ColourGuard::operator=( ColourGuard&& rhs ) noexcept {
m_colourImpl = rhs.m_colourImpl; using std::swap;
rhs.m_moved = true; swap( m_colourImpl, rhs.m_colourImpl );
swap( m_code, rhs.m_code );
swap( m_engaged, rhs.m_engaged );
return *this; return *this;
} }
ColourImpl::ColourGuard::~ColourGuard() { ColourImpl::ColourGuard::~ColourGuard() {
if ( !m_moved ) { if ( m_engaged ) {
m_colourImpl->use( Colour::None ); m_colourImpl->use( Colour::None );
} }
} }
ColourImpl::ColourGuard&
ColourImpl::ColourGuard::engage( std::ostream& stream ) & {
engageImpl( stream );
return *this;
}
ColourImpl::ColourGuard&&
ColourImpl::ColourGuard::engage( std::ostream& stream ) && {
engageImpl( stream );
return CATCH_MOVE(*this);
}
} // namespace Catch } // namespace Catch
#if !defined( CATCH_CONFIG_COLOUR_NONE ) && !defined( CATCH_CONFIG_COLOUR_WINDOWS ) && !defined( CATCH_CONFIG_COLOUR_ANSI ) #if !defined( CATCH_CONFIG_COLOUR_NONE ) && !defined( CATCH_CONFIG_COLOUR_WINDOWS ) && !defined( CATCH_CONFIG_COLOUR_ANSI )

View File

@ -62,25 +62,66 @@ namespace Catch {
public: public:
ColourImpl( IStream const* stream ): m_stream( stream ) {} ColourImpl( IStream const* stream ): m_stream( stream ) {}
//! RAII wrapper around writing specific colour of text using specific
//! colour impl into a stream.
class ColourGuard { class ColourGuard {
ColourImpl const* m_colourImpl; ColourImpl const* m_colourImpl;
bool m_moved = false; Colour::Code m_code;
friend std::ostream& operator<<(std::ostream& lhs, bool m_engaged = false;
ColourGuard const&) {
return lhs;
}
public: public:
//! Does **not** engage the guard/start the colour
ColourGuard( Colour::Code code, ColourGuard( Colour::Code code,
ColourImpl const* colour ); ColourImpl const* colour );
ColourGuard( ColourGuard const& rhs ) = delete; ColourGuard( ColourGuard const& rhs ) = delete;
ColourGuard& operator=( ColourGuard const& rhs ) = delete; ColourGuard& operator=( ColourGuard const& rhs ) = delete;
ColourGuard( ColourGuard&& rhs );
ColourGuard& operator=( ColourGuard&& rhs ); ColourGuard( ColourGuard&& rhs ) noexcept;
ColourGuard& operator=( ColourGuard&& rhs ) noexcept;
//! Removes colour _if_ the guard was engaged
~ColourGuard(); ~ColourGuard();
/**
* Explicitly engages colour for given stream.
*
* The API based on operator<< should be preferred.
*/
ColourGuard& engage( std::ostream& stream ) &;
/**
* Explicitly engages colour for given stream.
*
* The API based on operator<< should be preferred.
*/
ColourGuard&& engage( std::ostream& stream ) &&;
private:
//! Engages the guard and starts using colour
friend std::ostream& operator<<( std::ostream& lhs,
ColourGuard& guard ) {
guard.engageImpl( lhs );
return lhs;
}
//! Engages the guard and starts using colour
friend std::ostream& operator<<( std::ostream& lhs,
ColourGuard&& guard) {
guard.engageImpl( lhs );
return lhs;
}
void engageImpl( std::ostream& stream );
}; };
virtual ~ColourImpl(); // = default virtual ~ColourImpl(); // = default
ColourGuard startColour( Colour::Code colourCode ); /**
* Creates a guard object for given colour and this colour impl
*
* **Important:**
* the guard starts disengaged, and has to be engaged explicitly.
*/
ColourGuard guardColour( Colour::Code colourCode );
private: private:
virtual void use( Colour::Code colourCode ) const = 0; virtual void use( Colour::Code colourCode ) const = 0;

View File

@ -194,7 +194,7 @@ namespace Catch {
Colour::Code colour = testCaseInfo.isHidden() Colour::Code colour = testCaseInfo.isHidden()
? Colour::SecondaryText ? Colour::SecondaryText
: Colour::None; : Colour::None;
auto colourGuard = streamColour->startColour( colour ); auto colourGuard = streamColour->guardColour( colour ).engage( out );
out << TextFlow::Column(testCaseInfo.name).initialIndent(2).indent(4) << '\n'; out << TextFlow::Column(testCaseInfo.name).initialIndent(2).indent(4) << '\n';
if (verbosity >= Verbosity::High) { if (verbosity >= Verbosity::High) {

View File

@ -56,7 +56,7 @@ void printTotals(std::ostream& out, const Totals& totals, ColourImpl* colourImpl
if (totals.testCases.total() == 0) { if (totals.testCases.total() == 0) {
out << "No tests ran."; out << "No tests ran.";
} else if (totals.testCases.failed == totals.testCases.total()) { } else if (totals.testCases.failed == totals.testCases.total()) {
auto guard = colourImpl->startColour( Colour::ResultError ); auto guard = colourImpl->guardColour( Colour::ResultError ).engage( out );
const StringRef qualify_assertions_failed = const StringRef qualify_assertions_failed =
totals.assertions.failed == totals.assertions.total() ? totals.assertions.failed == totals.assertions.total() ?
bothOrAll(totals.assertions.failed) : StringRef{}; bothOrAll(totals.assertions.failed) : StringRef{};
@ -71,11 +71,11 @@ void printTotals(std::ostream& out, const Totals& totals, ColourImpl* colourImpl
<< pluralise(totals.testCases.total(), "test case"_sr) << pluralise(totals.testCases.total(), "test case"_sr)
<< " (no assertions)."; << " (no assertions).";
} else if (totals.assertions.failed) { } else if (totals.assertions.failed) {
out << colourImpl->startColour( Colour::ResultError ) << out << colourImpl->guardColour( Colour::ResultError ) <<
"Failed " << pluralise(totals.testCases.failed, "test case"_sr) << ", " "Failed " << pluralise(totals.testCases.failed, "test case"_sr) << ", "
"failed " << pluralise(totals.assertions.failed, "assertion"_sr) << '.'; "failed " << pluralise(totals.assertions.failed, "assertion"_sr) << '.';
} else { } else {
out << colourImpl->startColour( Colour::ResultSuccess ) << out << colourImpl->guardColour( Colour::ResultSuccess ) <<
"Passed " << bothOrAll(totals.testCases.passed) "Passed " << bothOrAll(totals.testCases.passed)
<< pluralise(totals.testCases.passed, "test case"_sr) << << pluralise(totals.testCases.passed, "test case"_sr) <<
" with " << pluralise(totals.assertions.passed, "assertion"_sr) << '.'; " with " << pluralise(totals.assertions.passed, "assertion"_sr) << '.';
@ -166,13 +166,13 @@ public:
private: private:
void printSourceInfo() const { void printSourceInfo() const {
stream << colourImpl->startColour( Colour::FileName ) stream << colourImpl->guardColour( Colour::FileName )
<< result.getSourceInfo() << ':'; << result.getSourceInfo() << ':';
} }
void printResultType(Colour::Code colour, StringRef passOrFail) const { void printResultType(Colour::Code colour, StringRef passOrFail) const {
if (!passOrFail.empty()) { if (!passOrFail.empty()) {
stream << colourImpl->startColour(colour) << ' ' << passOrFail; stream << colourImpl->guardColour(colour) << ' ' << passOrFail;
stream << ':'; stream << ':';
} }
} }
@ -185,7 +185,7 @@ private:
if (result.hasExpression()) { if (result.hasExpression()) {
stream << ';'; stream << ';';
{ {
stream << colourImpl->startColour(compactDimColour) << " expression was:"; stream << colourImpl->guardColour(compactDimColour) << " expression was:";
} }
printOriginalExpression(); printOriginalExpression();
} }
@ -199,7 +199,7 @@ private:
void printReconstructedExpression() const { void printReconstructedExpression() const {
if (result.hasExpandedExpression()) { if (result.hasExpandedExpression()) {
stream << colourImpl->startColour(compactDimColour) << " for: "; stream << colourImpl->guardColour(compactDimColour) << " for: ";
stream << result.getExpandedExpression(); stream << result.getExpandedExpression();
} }
} }
@ -218,7 +218,7 @@ private:
const auto itEnd = messages.cend(); const auto itEnd = messages.cend();
const auto N = static_cast<std::size_t>(std::distance(itMessage, itEnd)); const auto N = static_cast<std::size_t>(std::distance(itMessage, itEnd));
stream << colourImpl->startColour( colour ) << " with " stream << colourImpl->guardColour( colour ) << " with "
<< pluralise( N, "message"_sr ) << ':'; << pluralise( N, "message"_sr ) << ':';
while (itMessage != itEnd) { while (itMessage != itEnd) {
@ -226,7 +226,7 @@ private:
if (printInfoMessages || itMessage->type != ResultWas::Info) { if (printInfoMessages || itMessage->type != ResultWas::Info) {
printMessage(); printMessage();
if (itMessage != itEnd) { if (itMessage != itEnd) {
stream << colourImpl->startColour(compactDimColour) << " and"; stream << colourImpl->guardColour(compactDimColour) << " and";
} }
continue; continue;
} }

View File

@ -135,19 +135,19 @@ public:
private: private:
void printResultType() const { void printResultType() const {
if (!passOrFail.empty()) { if (!passOrFail.empty()) {
stream << colourImpl->startColour(colour) << passOrFail << ":\n"; stream << colourImpl->guardColour(colour) << passOrFail << ":\n";
} }
} }
void printOriginalExpression() const { void printOriginalExpression() const {
if (result.hasExpression()) { if (result.hasExpression()) {
stream << colourImpl->startColour( Colour::OriginalExpression ) stream << colourImpl->guardColour( Colour::OriginalExpression )
<< " " << result.getExpressionInMacro() << '\n'; << " " << result.getExpressionInMacro() << '\n';
} }
} }
void printReconstructedExpression() const { void printReconstructedExpression() const {
if (result.hasExpandedExpression()) { if (result.hasExpandedExpression()) {
stream << "with expansion:\n"; stream << "with expansion:\n";
stream << colourImpl->startColour( Colour::ReconstructedExpression ) stream << colourImpl->guardColour( Colour::ReconstructedExpression )
<< TextFlow::Column( result.getExpandedExpression() ) << TextFlow::Column( result.getExpandedExpression() )
.indent( 2 ) .indent( 2 )
<< '\n'; << '\n';
@ -163,7 +163,7 @@ private:
} }
} }
void printSourceInfo() const { void printSourceInfo() const {
stream << colourImpl->startColour( Colour::FileName ) stream << colourImpl->guardColour( Colour::FileName )
<< result.getSourceInfo() << ": "; << result.getSourceInfo() << ": ";
} }
@ -418,7 +418,8 @@ void ConsoleReporter::sectionEnded(SectionStats const& _sectionStats) {
m_tablePrinter->close(); m_tablePrinter->close();
if (_sectionStats.missingAssertions) { if (_sectionStats.missingAssertions) {
lazyPrint(); lazyPrint();
auto guard = m_colour->startColour( Colour::ResultError ); auto guard =
m_colour->guardColour( Colour::ResultError ).engage( m_stream );
if (m_sectionStack.size() > 1) if (m_sectionStack.size() > 1)
m_stream << "\nNo assertions in section"; m_stream << "\nNo assertions in section";
else else
@ -476,7 +477,7 @@ void ConsoleReporter::benchmarkEnded(BenchmarkStats<> const& stats) {
} }
void ConsoleReporter::benchmarkFailed( StringRef error ) { void ConsoleReporter::benchmarkFailed( StringRef error ) {
auto guard = m_colour->startColour( Colour::Red ); auto guard = m_colour->guardColour( Colour::Red ).engage( m_stream );
(*m_tablePrinter) (*m_tablePrinter)
<< "Benchmark failed (" << error << ')' << "Benchmark failed (" << error << ')'
<< ColumnBreak() << RowBreak(); << ColumnBreak() << RowBreak();
@ -517,7 +518,7 @@ void ConsoleReporter::lazyPrintWithoutClosingBenchmarkTable() {
void ConsoleReporter::lazyPrintRunInfo() { void ConsoleReporter::lazyPrintRunInfo() {
m_stream << '\n' m_stream << '\n'
<< lineOfChars( '~' ) << '\n' << lineOfChars( '~' ) << '\n'
<< m_colour->startColour( Colour::SecondaryText ) << m_colour->guardColour( Colour::SecondaryText )
<< currentTestRunInfo.name << " is a Catch2 v" << libraryVersion() << currentTestRunInfo.name << " is a Catch2 v" << libraryVersion()
<< " host application.\n" << " host application.\n"
<< "Run with -? for options\n\n" << "Run with -? for options\n\n"
@ -530,7 +531,7 @@ void ConsoleReporter::printTestCaseAndSectionHeader() {
printOpenHeader(currentTestCaseInfo->name); printOpenHeader(currentTestCaseInfo->name);
if (m_sectionStack.size() > 1) { if (m_sectionStack.size() > 1) {
auto guard = m_colour->startColour( Colour::Headers ); auto guard = m_colour->guardColour( Colour::Headers ).engage( m_stream );
auto auto
it = m_sectionStack.begin() + 1, // Skip first section (test case) it = m_sectionStack.begin() + 1, // Skip first section (test case)
@ -543,7 +544,7 @@ void ConsoleReporter::printTestCaseAndSectionHeader() {
m_stream << lineOfChars( '-' ) << '\n' m_stream << lineOfChars( '-' ) << '\n'
<< m_colour->startColour( Colour::FileName ) << lineInfo << '\n' << m_colour->guardColour( Colour::FileName ) << lineInfo << '\n'
<< lineOfChars( '.' ) << "\n\n" << lineOfChars( '.' ) << "\n\n"
<< std::flush; << std::flush;
} }
@ -555,7 +556,7 @@ void ConsoleReporter::printClosedHeader(std::string const& _name) {
void ConsoleReporter::printOpenHeader(std::string const& _name) { void ConsoleReporter::printOpenHeader(std::string const& _name) {
m_stream << lineOfChars('-') << '\n'; m_stream << lineOfChars('-') << '\n';
{ {
auto guard = m_colour->startColour( Colour::Headers ); auto guard = m_colour->guardColour( Colour::Headers ).engage( m_stream );
printHeaderString(_name); printHeaderString(_name);
} }
} }
@ -620,10 +621,10 @@ struct SummaryColumn {
void ConsoleReporter::printTotals( Totals const& totals ) { void ConsoleReporter::printTotals( Totals const& totals ) {
if (totals.testCases.total() == 0) { if (totals.testCases.total() == 0) {
m_stream << m_colour->startColour( Colour::Warning ) m_stream << m_colour->guardColour( Colour::Warning )
<< "No tests ran\n"; << "No tests ran\n";
} else if (totals.assertions.total() > 0 && totals.testCases.allPassed()) { } else if (totals.assertions.total() > 0 && totals.testCases.allPassed()) {
m_stream << m_colour->startColour( Colour::ResultSuccess ) m_stream << m_colour->guardColour( Colour::ResultSuccess )
<< "All tests passed"; << "All tests passed";
m_stream << " (" m_stream << " ("
<< pluralise(totals.assertions.passed, "assertion"_sr) << " in " << pluralise(totals.assertions.passed, "assertion"_sr) << " in "
@ -657,12 +658,12 @@ void ConsoleReporter::printSummaryRow(StringRef label, std::vector<SummaryColumn
if ( value != "0" ) { if ( value != "0" ) {
m_stream << value; m_stream << value;
} else { } else {
m_stream << m_colour->startColour( Colour::Warning ) m_stream << m_colour->guardColour( Colour::Warning )
<< "- none -"; << "- none -";
} }
} else if (value != "0") { } else if (value != "0") {
m_stream << m_colour->startColour( Colour::LightGrey ) << " | "; m_stream << m_colour->guardColour( Colour::LightGrey ) << " | "
m_stream << m_colour->startColour( col.colour ) << value << ' ' << m_colour->guardColour( col.colour ) << value << ' '
<< col.label; << col.label;
} }
} }
@ -679,19 +680,19 @@ void ConsoleReporter::printTotalsDivider(Totals const& totals) {
while (failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH - 1) while (failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH - 1)
findMax(failedRatio, failedButOkRatio, passedRatio)--; findMax(failedRatio, failedButOkRatio, passedRatio)--;
m_stream << m_colour->startColour( Colour::Error ) m_stream << m_colour->guardColour( Colour::Error )
<< std::string( failedRatio, '=' ) << std::string( failedRatio, '=' )
<< m_colour->startColour( Colour::ResultExpectedFailure ) << m_colour->guardColour( Colour::ResultExpectedFailure )
<< std::string( failedButOkRatio, '=' ); << std::string( failedButOkRatio, '=' );
if ( totals.testCases.allPassed() ) { if ( totals.testCases.allPassed() ) {
m_stream << m_colour->startColour( Colour::ResultSuccess ) m_stream << m_colour->guardColour( Colour::ResultSuccess )
<< std::string( passedRatio, '=' ); << std::string( passedRatio, '=' );
} else { } else {
m_stream << m_colour->startColour( Colour::Success ) m_stream << m_colour->guardColour( Colour::Success )
<< std::string( passedRatio, '=' ); << std::string( passedRatio, '=' );
} }
} else { } else {
m_stream << m_colour->startColour( Colour::Warning ) m_stream << m_colour->guardColour( Colour::Warning )
<< std::string( CATCH_CONFIG_CONSOLE_WIDTH - 1, '=' ); << std::string( CATCH_CONFIG_CONSOLE_WIDTH - 1, '=' );
} }
m_stream << '\n'; m_stream << '\n';
@ -702,7 +703,7 @@ void ConsoleReporter::printSummaryDivider() {
void ConsoleReporter::printTestFilters() { void ConsoleReporter::printTestFilters() {
if (m_config->testSpec().hasFilters()) { if (m_config->testSpec().hasFilters()) {
m_stream << m_colour->startColour( Colour::BrightYellow ) << "Filters: " m_stream << m_colour->guardColour( Colour::BrightYellow ) << "Filters: "
<< serializeFilters( m_config->getTestsOrTags() ) << '\n'; << serializeFilters( m_config->getTestsOrTags() ) << '\n';
} }
} }

View File

@ -107,7 +107,7 @@ namespace Catch {
private: private:
void printSourceInfo() const { void printSourceInfo() const {
stream << colourImpl->startColour( tapDimColour ) stream << colourImpl->guardColour( tapDimColour )
<< result.getSourceInfo() << ':'; << result.getSourceInfo() << ':';
} }
@ -124,7 +124,7 @@ namespace Catch {
void printExpressionWas() { void printExpressionWas() {
if (result.hasExpression()) { if (result.hasExpression()) {
stream << ';'; stream << ';';
stream << colourImpl->startColour( tapDimColour ) stream << colourImpl->guardColour( tapDimColour )
<< " expression was:"; << " expression was:";
printOriginalExpression(); printOriginalExpression();
} }
@ -138,7 +138,7 @@ namespace Catch {
void printReconstructedExpression() const { void printReconstructedExpression() const {
if (result.hasExpandedExpression()) { if (result.hasExpandedExpression()) {
stream << colourImpl->startColour( tapDimColour ) << " for: "; stream << colourImpl->guardColour( tapDimColour ) << " for: ";
std::string expr = result.getExpandedExpression(); std::string expr = result.getExpandedExpression();
std::replace(expr.begin(), expr.end(), '\n', ' '); std::replace(expr.begin(), expr.end(), '\n', ' ');
@ -162,7 +162,7 @@ namespace Catch {
std::vector<MessageInfo>::const_iterator itEnd = messages.end(); std::vector<MessageInfo>::const_iterator itEnd = messages.end();
const std::size_t N = static_cast<std::size_t>(std::distance(itMessage, itEnd)); const std::size_t N = static_cast<std::size_t>(std::distance(itMessage, itEnd));
stream << colourImpl->startColour( colour ) << " with " stream << colourImpl->guardColour( colour ) << " with "
<< pluralise( N, "message"_sr ) << ':'; << pluralise( N, "message"_sr ) << ':';
for (; itMessage != itEnd; ) { for (; itMessage != itEnd; ) {
@ -170,7 +170,7 @@ namespace Catch {
if (printInfoMessages || itMessage->type != ResultWas::Info) { if (printInfoMessages || itMessage->type != ResultWas::Info) {
stream << " '" << itMessage->message << '\''; stream << " '" << itMessage->message << '\'';
if (++itMessage != itEnd) { if (++itMessage != itEnd) {
stream << colourImpl->startColour(tapDimColour) << " and"; stream << colourImpl->guardColour(tapDimColour) << " and";
} }
} }
} }