From 15fd03260839d667df0dac008cbe40e83f451fb5 Mon Sep 17 00:00:00 2001 From: Phil Nash Date: Thu, 28 Mar 2013 22:13:31 +0000 Subject: [PATCH] Use new line wrapper to show test case list, with tags, in columns --- include/catch.hpp | 10 +-- include/catch_runner.hpp | 2 +- include/internal/catch_line_wrap.h | 6 +- include/internal/catch_line_wrap.hpp | 19 ++-- include/internal/catch_list.hpp | 104 +++++++++++++++------- include/internal/catch_test_case_info.h | 1 + include/internal/catch_test_case_info.hpp | 10 ++- projects/SelfTest/TestMain.cpp | 61 +++++++------ 8 files changed, 137 insertions(+), 76 deletions(-) diff --git a/include/catch.hpp b/include/catch.hpp index 7a95541c..9240467a 100644 --- a/include/catch.hpp +++ b/include/catch.hpp @@ -174,11 +174,11 @@ #else #define SCENARIO( name, tags ) TEST_CASE( "Scenario: " name, tags ) #endif -#define GIVEN( desc ) SECTION( "Given: " desc, "" ) -#define WHEN( desc ) SECTION( " When: " desc, "" ) -#define AND_WHEN( desc ) SECTION( " And: " desc, "" ) -#define THEN( desc ) SECTION( " Then: " desc, "" ) -#define AND_THEN( desc ) SECTION( " And: " desc, "" ) +#define GIVEN( desc ) SECTION( " Given: " desc, "" ) +#define WHEN( desc ) SECTION( " When: " desc, "" ) +#define AND_WHEN( desc ) SECTION( "And when: " desc, "" ) +#define THEN( desc ) SECTION( " Then: " desc, "" ) +#define AND_THEN( desc ) SECTION( " And: " desc, "" ) using Catch::Detail::Approx; diff --git a/include/catch_runner.hpp b/include/catch_runner.hpp index ef9c5975..30ed6be1 100644 --- a/include/catch_runner.hpp +++ b/include/catch_runner.hpp @@ -124,7 +124,7 @@ namespace Catch { // Handle list request if( config.listSpec != List::None ) { - List( config ); + list( config ); Catch::cleanUp(); return 0; } diff --git a/include/internal/catch_line_wrap.h b/include/internal/catch_line_wrap.h index ab024bcb..0ec49cdb 100644 --- a/include/internal/catch_line_wrap.h +++ b/include/internal/catch_line_wrap.h @@ -15,7 +15,6 @@ namespace Catch { class LineWrapper { public: - LineWrapper( std::size_t _right ); LineWrapper(); LineWrapper& setIndent( std::size_t _indent ); @@ -29,17 +28,22 @@ namespace Catch { const_iterator begin() const { return lines.begin(); } const_iterator end() const { return lines.end(); } + std::size_t size() const { return lines.size(); } + std::string const& operator[]( std::size_t _index ) const { return lines[_index]; } friend std::ostream& operator << ( std::ostream& _stream, LineWrapper const& _lineWrapper ); private: void wrapInternal( std::string const& _str ); void addLine( const std::string& _line ); + bool isWrapPoint( char c ); std::string indent; std::size_t right; std::size_t nextTab; std::size_t tab; + std::string wrappableChars; + int recursionCount; std::vector lines; }; diff --git a/include/internal/catch_line_wrap.hpp b/include/internal/catch_line_wrap.hpp index 1d8bcc75..a51e98a8 100644 --- a/include/internal/catch_line_wrap.hpp +++ b/include/internal/catch_line_wrap.hpp @@ -12,15 +12,12 @@ namespace Catch { - LineWrapper::LineWrapper( std::size_t _right ) - : right( _right ), - nextTab( 0 ), - tab( 0 ) - {} LineWrapper::LineWrapper() : right( CATCH_CONFIG_CONSOLE_WIDTH-1 ), nextTab( 0 ), - tab( 0 ) + tab( 0 ), + wrappableChars( " [({.," ), + recursionCount( 0 ) {} LineWrapper& LineWrapper::setIndent( std::size_t _indent ) { @@ -36,7 +33,12 @@ namespace Catch { wrapInternal( _str ); return *this; } + bool LineWrapper::isWrapPoint( char c ) { + return wrappableChars.find( c ) != std::string::npos; + } void LineWrapper::wrapInternal( std::string const& _str ) { + assert( ++recursionCount < 100 ); + std::size_t width = right - indent.size(); std::size_t wrapPoint = width-tab; for( std::size_t pos = 0; pos < _str.size(); ++pos ) { @@ -51,6 +53,9 @@ namespace Catch { addLine( _str.substr( 0, wrapPoint ) ); while( _str[++wrapPoint] == ' ' ); } + else if( isWrapPoint( _str[wrapPoint] ) ) { + addLine( _str.substr( 0, wrapPoint ) ); + } else { addLine( _str.substr( 0, --wrapPoint ) + '-' ); } @@ -61,7 +66,7 @@ namespace Catch { std::string withoutTab = _str.substr( 0, nextTab ) + _str.substr( nextTab+1 ); return wrapInternal( withoutTab ); } - else if( _str[pos] == ' ' ) { + else if( isWrapPoint( _str[pos] ) ) { wrapPoint = pos; } } diff --git a/include/internal/catch_list.hpp b/include/internal/catch_list.hpp index af53992c..f7f0c657 100644 --- a/include/internal/catch_list.hpp +++ b/include/internal/catch_list.hpp @@ -9,7 +9,10 @@ #define TWOBLUECUBES_CATCH_LIST_HPP_INCLUDED #include "catch_commandline.hpp" +#include "catch_line_wrap.h" + #include +#include namespace Catch { inline bool matchesFilters( const std::vector& filters, const TestCase& testCase ) { @@ -20,47 +23,80 @@ namespace Catch { return false; return true; } - inline void List( const ConfigData& config ) { - - if( config.listSpec & List::Reports ) { - std::cout << "Available reports:\n"; - IReporterRegistry::FactoryMap::const_iterator it = getRegistryHub().getReporterRegistry().getFactories().begin(); - IReporterRegistry::FactoryMap::const_iterator itEnd = getRegistryHub().getReporterRegistry().getFactories().end(); - for(; it != itEnd; ++it ) { - // !TBD: consider listAs() - std::cout << "\t" << it->first << "\n\t\t'" << it->second->getDescription() << "'\n"; - } - std::cout << std::endl; + + inline void listTests( const ConfigData& config ) { + if( config.filters.empty() ) + std::cout << "All available test cases:\n"; + else + std::cout << "Matching test cases:\n"; + std::vector const& allTests = getRegistryHub().getTestCaseRegistry().getAllTests(); + std::vector::const_iterator it = allTests.begin(), itEnd = allTests.end(); + + // First pass - get max tags + std::size_t maxTagLen = 0; + std::size_t maxNameLen = 0; + for(; it != itEnd; ++it ) { + maxTagLen = (std::max)( it->getTestCaseInfo().tagsAsString.size(), maxTagLen ); + maxNameLen = (std::max)( it->getTestCaseInfo().name.size(), maxNameLen ); } - if( config.listSpec & List::Tests ) { - if( config.filters.empty() ) - std::cout << "All available test cases:\n"; + // Try to fit everything in. If not shrink tag column first, down to 30 + // then shrink name column until it all fits (strings will be wrapped within column) + while( maxTagLen + maxNameLen > CATCH_CONFIG_CONSOLE_WIDTH-5 ) { + if( maxTagLen > 30 ) + --maxTagLen; else - std::cout << "Matching test cases:\n"; - std::vector::const_iterator it = getRegistryHub().getTestCaseRegistry().getAllTests().begin(); - std::vector::const_iterator itEnd = getRegistryHub().getTestCaseRegistry().getAllTests().end(); - std::size_t matchedTests = 0; - for(; it != itEnd; ++it ) { - if( matchesFilters( config.filters, *it ) ) { - matchedTests++; - // !TBD: consider listAs() - std::cout << " " << it->getTestCaseInfo().name << "\n"; - if( ( config.listSpec & List::TestNames ) != List::TestNames ) - std::cout << " '" << it->getTestCaseInfo().description << "'\n"; + --maxNameLen; + } + + std::size_t matchedTests = 0; + for( it = allTests.begin(); it != itEnd; ++it ) { + if( matchesFilters( config.filters, *it ) ) { + matchedTests++; + // !TBD: consider listAs() + LineWrapper nameWrapper; + nameWrapper.setRight( maxNameLen ).setIndent( 2 ).wrap( it->getTestCaseInfo().name ); + + LineWrapper tagsWrapper; + tagsWrapper.setRight( maxTagLen ).wrap( it->getTestCaseInfo().tagsAsString ); + + for( std::size_t i = 0; i < std::max( nameWrapper.size(), tagsWrapper.size() ); ++i ) { + std::string nameCol; + if( i < nameWrapper.size() ) + nameCol = nameWrapper[i]; + else + nameCol = " ..."; + std::cout << nameCol << " " << std::string( maxNameLen - nameCol.size(), ' ' ); + if( i < tagsWrapper.size() ) + std::cout << tagsWrapper[i]; + std::cout << "\n"; } } - if( config.filters.empty() ) - std::cout << pluralise( matchedTests, "test case" ) << std::endl; - else - std::cout << pluralise( matchedTests, "matching test case" ) << std::endl; } - - if( ( config.listSpec & List::All ) == 0 ) { - std::ostringstream oss; - oss << "Unknown list type"; - throw std::domain_error( oss.str() ); + if( config.filters.empty() ) + std::cout << pluralise( matchedTests, "test case" ) << std::endl; + else + std::cout << pluralise( matchedTests, "matching test case" ) << std::endl; + } + + inline void listReporters( const ConfigData& /*config*/ ) { + std::cout << "Available reports:\n"; + IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories(); + IReporterRegistry::FactoryMap::const_iterator it = factories.begin(), itEnd = factories.end(); + for(; it != itEnd; ++it ) { + // !TBD: consider listAs() + std::cout << "\t" << it->first << "\n\t\t'" << it->second->getDescription() << "'\n"; } + std::cout << std::endl; + } + + inline void list( const ConfigData& config ) { + if( config.listSpec & List::Tests ) + listTests( config ); + else if( config.listSpec & List::Reports ) + listReporters( config ); + else + throw std::logic_error( "Unknown list type" ); } } // end namespace Catch diff --git a/include/internal/catch_test_case_info.h b/include/internal/catch_test_case_info.h index f84106a7..fde9748a 100644 --- a/include/internal/catch_test_case_info.h +++ b/include/internal/catch_test_case_info.h @@ -36,6 +36,7 @@ namespace Catch { std::string className; std::string description; std::set tags; + std::string tagsAsString; SourceLineInfo lineInfo; bool isHidden; }; diff --git a/include/internal/catch_test_case_info.hpp b/include/internal/catch_test_case_info.hpp index 376a54fa..a5412b6f 100644 --- a/include/internal/catch_test_case_info.hpp +++ b/include/internal/catch_test_case_info.hpp @@ -44,15 +44,21 @@ namespace Catch { tags( _tags ), lineInfo( _lineInfo ), isHidden( _isHidden ) - {} + { + std::ostringstream oss; + for( std::set::const_iterator it = _tags.begin(), itEnd = _tags.end(); it != itEnd; ++it ) + oss << "[" << *it << "]"; + tagsAsString = oss.str(); + } TestCaseInfo::TestCaseInfo( const TestCaseInfo& other ) : name( other.name ), className( other.className ), description( other.description ), tags( other.tags ), + tagsAsString( other.tagsAsString ), lineInfo( other.lineInfo ), - isHidden( other.isHidden ) + isHidden( other.isHidden ) {} TestCase::TestCase( ITestCase* testCase, const TestCaseInfo& info ) : TestCaseInfo( info ), test( testCase ) {} diff --git a/projects/SelfTest/TestMain.cpp b/projects/SelfTest/TestMain.cpp index 9f1e3d4d..ad363d6a 100644 --- a/projects/SelfTest/TestMain.cpp +++ b/projects/SelfTest/TestMain.cpp @@ -432,31 +432,40 @@ TEST_CASE( "Long strings can be wrapped", "[wrap]" ) { std::string testString = "one two three four"; SECTION( "No wrapping", "" ) { - CHECK( Catch::LineWrapper( 80 ).wrap( testString ).toString() == testString ); - CHECK( Catch::LineWrapper( 18 ).wrap( testString ).toString() == testString ); + CHECK( Catch::LineWrapper().setRight( 80 ).wrap( testString ).toString() == testString ); + CHECK( Catch::LineWrapper().setRight( 18 ).wrap( testString ).toString() == testString ); } SECTION( "Wrapped once", "" ) { - CHECK( Catch::LineWrapper( 17 ).wrap( testString ).toString() == "one two three\nfour" ); - CHECK( Catch::LineWrapper( 16 ).wrap( testString ).toString() == "one two three\nfour" ); - CHECK( Catch::LineWrapper( 15 ).wrap( testString ).toString() == "one two three\nfour" ); - CHECK( Catch::LineWrapper( 14 ).wrap( testString ).toString() == "one two three\nfour" ); - CHECK( Catch::LineWrapper( 13 ).wrap( testString ).toString() == "one two\nthree four" ); + CHECK( Catch::LineWrapper().setRight( 17 ).wrap( testString ).toString() == "one two three\nfour" ); + CHECK( Catch::LineWrapper().setRight( 16 ).wrap( testString ).toString() == "one two three\nfour" ); + CHECK( Catch::LineWrapper().setRight( 15 ).wrap( testString ).toString() == "one two three\nfour" ); + CHECK( Catch::LineWrapper().setRight( 14 ).wrap( testString ).toString() == "one two three\nfour" ); + CHECK( Catch::LineWrapper().setRight( 13 ).wrap( testString ).toString() == "one two\nthree four" ); } SECTION( "Wrapped twice", "" ) { - CHECK( Catch::LineWrapper( 9 ).wrap( testString ).toString() == "one two\nthree\nfour" ); - CHECK( Catch::LineWrapper( 8 ).wrap( testString ).toString() == "one two\nthree\nfour" ); + CHECK( Catch::LineWrapper().setRight( 9 ).wrap( testString ).toString() == "one two\nthree\nfour" ); + CHECK( Catch::LineWrapper().setRight( 8 ).wrap( testString ).toString() == "one two\nthree\nfour" ); } SECTION( "Wrapped three times", "" ) { - CHECK( Catch::LineWrapper( 7 ).wrap( testString ).toString() == "one\ntwo\nthree\nfour" ); - CHECK( Catch::LineWrapper( 5 ).wrap( testString ).toString() == "one\ntwo\nthree\nfour" ); + CHECK( Catch::LineWrapper().setRight( 7 ).wrap( testString ).toString() == "one\ntwo\nthree\nfour" ); + CHECK( Catch::LineWrapper().setRight( 5 ).wrap( testString ).toString() == "one\ntwo\nthree\nfour" ); } SECTION( "Short wrap", "" ) { - CHECK( Catch::LineWrapper( 4 ).wrap( "abcdef" ).toString() == "abc-\ndef" ); - CHECK( Catch::LineWrapper( 4 ).wrap( "abcdefg" ).toString() == "abc-\ndefg" ); - CHECK( Catch::LineWrapper( 4 ).wrap("abcdefgh" ).toString() == "abc-\ndef-\ngh" ); + CHECK( Catch::LineWrapper().setRight( 4 ).wrap( "abcdef" ).toString() == "abc-\ndef" ); + CHECK( Catch::LineWrapper().setRight( 4 ).wrap( "abcdefg" ).toString() == "abc-\ndefg" ); + CHECK( Catch::LineWrapper().setRight( 4 ).wrap("abcdefgh" ).toString() == "abc-\ndef-\ngh" ); - CHECK( Catch::LineWrapper( 4 ).wrap( testString ).toString() == "one\ntwo\nthr-\nee\nfour" ); - CHECK( Catch::LineWrapper( 3 ).wrap( testString ).toString() == "one\ntwo\nth-\nree\nfo-\nur" ); + CHECK( Catch::LineWrapper().setRight( 4 ).wrap( testString ).toString() == "one\ntwo\nthr-\nee\nfour" ); + CHECK( Catch::LineWrapper().setRight( 3 ).wrap( testString ).toString() == "one\ntwo\nth-\nree\nfo-\nur" ); + } + SECTION( "As container", "" ) { + Catch::LineWrapper wrapper; + wrapper.setRight( 7 ).wrap( testString ); + CHECK( wrapper.size() == 4 ); + CHECK( wrapper[0] == "one" ); + CHECK( wrapper[1] == "two" ); + CHECK( wrapper[2] == "three" ); + CHECK( wrapper[3] == "four" ); } } @@ -466,22 +475,22 @@ TEST_CASE( "Long strings can be wrapped", "[wrap]" ) { std::string testString = "one two\nthree four"; SECTION( "No wrapping" , "" ) { - CHECK( Catch::LineWrapper( 80 ).wrap( testString ).toString() == testString ); - CHECK( Catch::LineWrapper( 18 ).wrap( testString ).toString() == testString ); - CHECK( Catch::LineWrapper( 10 ).wrap( testString ).toString() == testString ); + CHECK( Catch::LineWrapper().setRight( 80 ).wrap( testString ).toString() == testString ); + CHECK( Catch::LineWrapper().setRight( 18 ).wrap( testString ).toString() == testString ); + CHECK( Catch::LineWrapper().setRight( 10 ).wrap( testString ).toString() == testString ); } SECTION( "Trailing newline" , "" ) { - CHECK( Catch::LineWrapper( 10 ).wrap( "abcdef\n" ).toString() == "abcdef\n" ); - CHECK( Catch::LineWrapper( 6 ).wrap( "abcdef" ).toString() == "abcdef" ); - CHECK( Catch::LineWrapper( 6 ).wrap( "abcdef\n" ).toString() == "abcdef\n" ); + CHECK( Catch::LineWrapper().setRight( 10 ).wrap( "abcdef\n" ).toString() == "abcdef\n" ); + CHECK( Catch::LineWrapper().setRight( 6 ).wrap( "abcdef" ).toString() == "abcdef" ); + CHECK( Catch::LineWrapper().setRight( 6 ).wrap( "abcdef\n" ).toString() == "abcdef\n" ); } SECTION( "Wrapped once", "" ) { - CHECK( Catch::LineWrapper( 9 ).wrap( testString ).toString() == "one two\nthree\nfour" ); - CHECK( Catch::LineWrapper( 8 ).wrap( testString ).toString() == "one two\nthree\nfour" ); - CHECK( Catch::LineWrapper( 7 ).wrap( testString ).toString() == "one two\nthree\nfour" ); + CHECK( Catch::LineWrapper().setRight( 9 ).wrap( testString ).toString() == "one two\nthree\nfour" ); + CHECK( Catch::LineWrapper().setRight( 8 ).wrap( testString ).toString() == "one two\nthree\nfour" ); + CHECK( Catch::LineWrapper().setRight( 7 ).wrap( testString ).toString() == "one two\nthree\nfour" ); } SECTION( "Wrapped twice", "" ) { - CHECK( Catch::LineWrapper( 6 ).wrap( testString ).toString() == "one\ntwo\nthree\nfour" ); + CHECK( Catch::LineWrapper().setRight( 6 ).wrap( testString ).toString() == "one\ntwo\nthree\nfour" ); } }