Use new line wrapper to show test case list, with tags, in columns

This commit is contained in:
Phil Nash 2013-03-28 22:13:31 +00:00
parent 9e8abc33e7
commit 15fd032608
8 changed files with 137 additions and 76 deletions

View File

@ -174,9 +174,9 @@
#else #else
#define SCENARIO( name, tags ) TEST_CASE( "Scenario: " name, tags ) #define SCENARIO( name, tags ) TEST_CASE( "Scenario: " name, tags )
#endif #endif
#define GIVEN( desc ) SECTION( "Given: " desc, "" ) #define GIVEN( desc ) SECTION( " Given: " desc, "" )
#define WHEN( desc ) SECTION( " When: " desc, "" ) #define WHEN( desc ) SECTION( " When: " desc, "" )
#define AND_WHEN( desc ) SECTION( " And: " desc, "" ) #define AND_WHEN( desc ) SECTION( "And when: " desc, "" )
#define THEN( desc ) SECTION( " Then: " desc, "" ) #define THEN( desc ) SECTION( " Then: " desc, "" )
#define AND_THEN( desc ) SECTION( " And: " desc, "" ) #define AND_THEN( desc ) SECTION( " And: " desc, "" )

View File

@ -124,7 +124,7 @@ namespace Catch {
// Handle list request // Handle list request
if( config.listSpec != List::None ) { if( config.listSpec != List::None ) {
List( config ); list( config );
Catch::cleanUp(); Catch::cleanUp();
return 0; return 0;
} }

View File

@ -15,7 +15,6 @@ namespace Catch {
class LineWrapper { class LineWrapper {
public: public:
LineWrapper( std::size_t _right );
LineWrapper(); LineWrapper();
LineWrapper& setIndent( std::size_t _indent ); LineWrapper& setIndent( std::size_t _indent );
@ -29,17 +28,22 @@ namespace Catch {
const_iterator begin() const { return lines.begin(); } const_iterator begin() const { return lines.begin(); }
const_iterator end() const { return lines.end(); } 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 ); friend std::ostream& operator << ( std::ostream& _stream, LineWrapper const& _lineWrapper );
private: private:
void wrapInternal( std::string const& _str ); void wrapInternal( std::string const& _str );
void addLine( const std::string& _line ); void addLine( const std::string& _line );
bool isWrapPoint( char c );
std::string indent; std::string indent;
std::size_t right; std::size_t right;
std::size_t nextTab; std::size_t nextTab;
std::size_t tab; std::size_t tab;
std::string wrappableChars;
int recursionCount;
std::vector<std::string> lines; std::vector<std::string> lines;
}; };

View File

@ -12,15 +12,12 @@
namespace Catch { namespace Catch {
LineWrapper::LineWrapper( std::size_t _right )
: right( _right ),
nextTab( 0 ),
tab( 0 )
{}
LineWrapper::LineWrapper() LineWrapper::LineWrapper()
: right( CATCH_CONFIG_CONSOLE_WIDTH-1 ), : right( CATCH_CONFIG_CONSOLE_WIDTH-1 ),
nextTab( 0 ), nextTab( 0 ),
tab( 0 ) tab( 0 ),
wrappableChars( " [({.," ),
recursionCount( 0 )
{} {}
LineWrapper& LineWrapper::setIndent( std::size_t _indent ) { LineWrapper& LineWrapper::setIndent( std::size_t _indent ) {
@ -36,7 +33,12 @@ namespace Catch {
wrapInternal( _str ); wrapInternal( _str );
return *this; return *this;
} }
bool LineWrapper::isWrapPoint( char c ) {
return wrappableChars.find( c ) != std::string::npos;
}
void LineWrapper::wrapInternal( std::string const& _str ) { void LineWrapper::wrapInternal( std::string const& _str ) {
assert( ++recursionCount < 100 );
std::size_t width = right - indent.size(); std::size_t width = right - indent.size();
std::size_t wrapPoint = width-tab; std::size_t wrapPoint = width-tab;
for( std::size_t pos = 0; pos < _str.size(); ++pos ) { for( std::size_t pos = 0; pos < _str.size(); ++pos ) {
@ -51,6 +53,9 @@ namespace Catch {
addLine( _str.substr( 0, wrapPoint ) ); addLine( _str.substr( 0, wrapPoint ) );
while( _str[++wrapPoint] == ' ' ); while( _str[++wrapPoint] == ' ' );
} }
else if( isWrapPoint( _str[wrapPoint] ) ) {
addLine( _str.substr( 0, wrapPoint ) );
}
else { else {
addLine( _str.substr( 0, --wrapPoint ) + '-' ); addLine( _str.substr( 0, --wrapPoint ) + '-' );
} }
@ -61,7 +66,7 @@ namespace Catch {
std::string withoutTab = _str.substr( 0, nextTab ) + _str.substr( nextTab+1 ); std::string withoutTab = _str.substr( 0, nextTab ) + _str.substr( nextTab+1 );
return wrapInternal( withoutTab ); return wrapInternal( withoutTab );
} }
else if( _str[pos] == ' ' ) { else if( isWrapPoint( _str[pos] ) ) {
wrapPoint = pos; wrapPoint = pos;
} }
} }

View File

@ -9,7 +9,10 @@
#define TWOBLUECUBES_CATCH_LIST_HPP_INCLUDED #define TWOBLUECUBES_CATCH_LIST_HPP_INCLUDED
#include "catch_commandline.hpp" #include "catch_commandline.hpp"
#include "catch_line_wrap.h"
#include <limits> #include <limits>
#include <algorithm>
namespace Catch { namespace Catch {
inline bool matchesFilters( const std::vector<TestCaseFilters>& filters, const TestCase& testCase ) { inline bool matchesFilters( const std::vector<TestCaseFilters>& filters, const TestCase& testCase ) {
@ -20,34 +23,54 @@ namespace Catch {
return false; return false;
return true; return true;
} }
inline void List( const ConfigData& config ) {
if( config.listSpec & List::Reports ) { inline void listTests( const ConfigData& config ) {
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;
}
if( config.listSpec & List::Tests ) {
if( config.filters.empty() ) if( config.filters.empty() )
std::cout << "All available test cases:\n"; std::cout << "All available test cases:\n";
else else
std::cout << "Matching test cases:\n"; std::cout << "Matching test cases:\n";
std::vector<TestCase>::const_iterator it = getRegistryHub().getTestCaseRegistry().getAllTests().begin(); std::vector<TestCase> const& allTests = getRegistryHub().getTestCaseRegistry().getAllTests();
std::vector<TestCase>::const_iterator itEnd = getRegistryHub().getTestCaseRegistry().getAllTests().end(); std::vector<TestCase>::const_iterator it = allTests.begin(), itEnd = allTests.end();
std::size_t matchedTests = 0;
// First pass - get max tags
std::size_t maxTagLen = 0;
std::size_t maxNameLen = 0;
for(; it != itEnd; ++it ) { for(; it != itEnd; ++it ) {
maxTagLen = (std::max)( it->getTestCaseInfo().tagsAsString.size(), maxTagLen );
maxNameLen = (std::max)( it->getTestCaseInfo().name.size(), maxNameLen );
}
// 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
--maxNameLen;
}
std::size_t matchedTests = 0;
for( it = allTests.begin(); it != itEnd; ++it ) {
if( matchesFilters( config.filters, *it ) ) { if( matchesFilters( config.filters, *it ) ) {
matchedTests++; matchedTests++;
// !TBD: consider listAs() // !TBD: consider listAs()
std::cout << " " << it->getTestCaseInfo().name << "\n"; LineWrapper nameWrapper;
if( ( config.listSpec & List::TestNames ) != List::TestNames ) nameWrapper.setRight( maxNameLen ).setIndent( 2 ).wrap( it->getTestCaseInfo().name );
std::cout << " '" << it->getTestCaseInfo().description << "'\n";
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() ) if( config.filters.empty() )
@ -56,11 +79,24 @@ namespace Catch {
std::cout << pluralise( matchedTests, "matching test case" ) << std::endl; std::cout << pluralise( matchedTests, "matching test case" ) << std::endl;
} }
if( ( config.listSpec & List::All ) == 0 ) { inline void listReporters( const ConfigData& /*config*/ ) {
std::ostringstream oss; std::cout << "Available reports:\n";
oss << "Unknown list type"; IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories();
throw std::domain_error( oss.str() ); 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 } // end namespace Catch

View File

@ -36,6 +36,7 @@ namespace Catch {
std::string className; std::string className;
std::string description; std::string description;
std::set<std::string> tags; std::set<std::string> tags;
std::string tagsAsString;
SourceLineInfo lineInfo; SourceLineInfo lineInfo;
bool isHidden; bool isHidden;
}; };

View File

@ -44,13 +44,19 @@ namespace Catch {
tags( _tags ), tags( _tags ),
lineInfo( _lineInfo ), lineInfo( _lineInfo ),
isHidden( _isHidden ) isHidden( _isHidden )
{} {
std::ostringstream oss;
for( std::set<std::string>::const_iterator it = _tags.begin(), itEnd = _tags.end(); it != itEnd; ++it )
oss << "[" << *it << "]";
tagsAsString = oss.str();
}
TestCaseInfo::TestCaseInfo( const TestCaseInfo& other ) TestCaseInfo::TestCaseInfo( const TestCaseInfo& other )
: name( other.name ), : name( other.name ),
className( other.className ), className( other.className ),
description( other.description ), description( other.description ),
tags( other.tags ), tags( other.tags ),
tagsAsString( other.tagsAsString ),
lineInfo( other.lineInfo ), lineInfo( other.lineInfo ),
isHidden( other.isHidden ) isHidden( other.isHidden )
{} {}

View File

@ -432,31 +432,40 @@ TEST_CASE( "Long strings can be wrapped", "[wrap]" ) {
std::string testString = "one two three four"; std::string testString = "one two three four";
SECTION( "No wrapping", "" ) { SECTION( "No wrapping", "" ) {
CHECK( Catch::LineWrapper( 80 ).wrap( testString ).toString() == testString ); CHECK( Catch::LineWrapper().setRight( 80 ).wrap( testString ).toString() == testString );
CHECK( Catch::LineWrapper( 18 ).wrap( testString ).toString() == testString ); CHECK( Catch::LineWrapper().setRight( 18 ).wrap( testString ).toString() == testString );
} }
SECTION( "Wrapped once", "" ) { SECTION( "Wrapped once", "" ) {
CHECK( Catch::LineWrapper( 17 ).wrap( testString ).toString() == "one two three\nfour" ); CHECK( Catch::LineWrapper().setRight( 17 ).wrap( testString ).toString() == "one two three\nfour" );
CHECK( Catch::LineWrapper( 16 ).wrap( testString ).toString() == "one two three\nfour" ); CHECK( Catch::LineWrapper().setRight( 16 ).wrap( testString ).toString() == "one two three\nfour" );
CHECK( Catch::LineWrapper( 15 ).wrap( testString ).toString() == "one two three\nfour" ); CHECK( Catch::LineWrapper().setRight( 15 ).wrap( testString ).toString() == "one two three\nfour" );
CHECK( Catch::LineWrapper( 14 ).wrap( testString ).toString() == "one two three\nfour" ); CHECK( Catch::LineWrapper().setRight( 14 ).wrap( testString ).toString() == "one two three\nfour" );
CHECK( Catch::LineWrapper( 13 ).wrap( testString ).toString() == "one two\nthree four" ); CHECK( Catch::LineWrapper().setRight( 13 ).wrap( testString ).toString() == "one two\nthree four" );
} }
SECTION( "Wrapped twice", "" ) { SECTION( "Wrapped twice", "" ) {
CHECK( Catch::LineWrapper( 9 ).wrap( testString ).toString() == "one two\nthree\nfour" ); CHECK( Catch::LineWrapper().setRight( 9 ).wrap( testString ).toString() == "one two\nthree\nfour" );
CHECK( Catch::LineWrapper( 8 ).wrap( testString ).toString() == "one two\nthree\nfour" ); CHECK( Catch::LineWrapper().setRight( 8 ).wrap( testString ).toString() == "one two\nthree\nfour" );
} }
SECTION( "Wrapped three times", "" ) { SECTION( "Wrapped three times", "" ) {
CHECK( Catch::LineWrapper( 7 ).wrap( testString ).toString() == "one\ntwo\nthree\nfour" ); CHECK( Catch::LineWrapper().setRight( 7 ).wrap( testString ).toString() == "one\ntwo\nthree\nfour" );
CHECK( Catch::LineWrapper( 5 ).wrap( testString ).toString() == "one\ntwo\nthree\nfour" ); CHECK( Catch::LineWrapper().setRight( 5 ).wrap( testString ).toString() == "one\ntwo\nthree\nfour" );
} }
SECTION( "Short wrap", "" ) { SECTION( "Short wrap", "" ) {
CHECK( Catch::LineWrapper( 4 ).wrap( "abcdef" ).toString() == "abc-\ndef" ); CHECK( Catch::LineWrapper().setRight( 4 ).wrap( "abcdef" ).toString() == "abc-\ndef" );
CHECK( Catch::LineWrapper( 4 ).wrap( "abcdefg" ).toString() == "abc-\ndefg" ); CHECK( Catch::LineWrapper().setRight( 4 ).wrap( "abcdefg" ).toString() == "abc-\ndefg" );
CHECK( Catch::LineWrapper( 4 ).wrap("abcdefgh" ).toString() == "abc-\ndef-\ngh" ); 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().setRight( 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( 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"; std::string testString = "one two\nthree four";
SECTION( "No wrapping" , "" ) { SECTION( "No wrapping" , "" ) {
CHECK( Catch::LineWrapper( 80 ).wrap( testString ).toString() == testString ); CHECK( Catch::LineWrapper().setRight( 80 ).wrap( testString ).toString() == testString );
CHECK( Catch::LineWrapper( 18 ).wrap( testString ).toString() == testString ); CHECK( Catch::LineWrapper().setRight( 18 ).wrap( testString ).toString() == testString );
CHECK( Catch::LineWrapper( 10 ).wrap( testString ).toString() == testString ); CHECK( Catch::LineWrapper().setRight( 10 ).wrap( testString ).toString() == testString );
} }
SECTION( "Trailing newline" , "" ) { SECTION( "Trailing newline" , "" ) {
CHECK( Catch::LineWrapper( 10 ).wrap( "abcdef\n" ).toString() == "abcdef\n" ); CHECK( Catch::LineWrapper().setRight( 10 ).wrap( "abcdef\n" ).toString() == "abcdef\n" );
CHECK( Catch::LineWrapper( 6 ).wrap( "abcdef" ).toString() == "abcdef" ); CHECK( Catch::LineWrapper().setRight( 6 ).wrap( "abcdef" ).toString() == "abcdef" );
CHECK( Catch::LineWrapper( 6 ).wrap( "abcdef\n" ).toString() == "abcdef\n" ); CHECK( Catch::LineWrapper().setRight( 6 ).wrap( "abcdef\n" ).toString() == "abcdef\n" );
} }
SECTION( "Wrapped once", "" ) { SECTION( "Wrapped once", "" ) {
CHECK( Catch::LineWrapper( 9 ).wrap( testString ).toString() == "one two\nthree\nfour" ); CHECK( Catch::LineWrapper().setRight( 9 ).wrap( testString ).toString() == "one two\nthree\nfour" );
CHECK( Catch::LineWrapper( 8 ).wrap( testString ).toString() == "one two\nthree\nfour" ); CHECK( Catch::LineWrapper().setRight( 8 ).wrap( testString ).toString() == "one two\nthree\nfour" );
CHECK( Catch::LineWrapper( 7 ).wrap( testString ).toString() == "one two\nthree\nfour" ); CHECK( Catch::LineWrapper().setRight( 7 ).wrap( testString ).toString() == "one two\nthree\nfour" );
} }
SECTION( "Wrapped twice", "" ) { 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" );
} }
} }