Integrated (all) new version of Clara

This commit is contained in:
Phil Nash 2017-06-12 23:04:24 +01:00
parent 6d9171aadb
commit 1c223b63ba
7 changed files with 1465 additions and 1418 deletions

View File

@ -112,7 +112,7 @@ CheckFileList(TOP_LEVEL_HEADERS ${HEADER_DIR})
# Please keep these ordered alphabetically # Please keep these ordered alphabetically
set(EXTERNAL_HEADERS set(EXTERNAL_HEADERS
${HEADER_DIR}/external/clara.h ${HEADER_DIR}/external/clara.hpp
${HEADER_DIR}/external/tbc_text_format.h ${HEADER_DIR}/external/tbc_text_format.h
) )
CheckFileList(EXTERNAL_HEADERS ${HEADER_DIR}/external) CheckFileList(EXTERNAL_HEADERS ${HEADER_DIR}/external)

View File

@ -96,17 +96,14 @@ namespace Catch {
} }
class Session : NonCopyable { class Session : NonCopyable {
static bool alreadyInstantiated;
public: public:
struct OnUnusedOptions { enum DoWhat { Ignore, Fail }; }; Session() {
static bool alreadyInstantiated = false;
Session()
: m_cli( makeCommandLineParser() ) {
if( alreadyInstantiated ) if( alreadyInstantiated )
CATCH_INTERNAL_ERROR( "Only one instance of Catch::Session can ever be used" ); CATCH_INTERNAL_ERROR( "Only one instance of Catch::Session can ever be used" );
alreadyInstantiated = true; alreadyInstantiated = true;
m_cli = makeCommandLineParser( m_configData );
} }
~Session() { ~Session() {
Catch::cleanUp(); Catch::cleanUp();
@ -115,29 +112,27 @@ namespace Catch {
void showHelp( std::string const& processName ) { void showHelp( std::string const& processName ) {
Catch::cout() << "\nCatch v" << libraryVersion() << "\n"; Catch::cout() << "\nCatch v" << libraryVersion() << "\n";
m_cli.usage( Catch::cout(), processName ); Catch::cout() << m_cli << std::endl;
Catch::cout() << "For more detail usage please see the project docs\n" << std::endl; Catch::cout() << "For more detail usage please see the project docs\n" << std::endl;
} }
int applyCommandLine( int argc, char const* const* const argv, OnUnusedOptions::DoWhat unusedOptionBehaviour = OnUnusedOptions::Fail ) { int applyCommandLine( int argc, char* argv[] ) {
try { auto result = m_cli.parse( clara::Args( argc, argv ) );
m_cli.setThrowOnUnrecognisedTokens( unusedOptionBehaviour == OnUnusedOptions::Fail ); if( !result ) {
m_unusedTokens = m_cli.parseInto( Clara::argsToVector( argc, argv ), m_configData );
if( m_configData.showHelp )
showHelp( m_configData.processName );
m_config.reset();
}
catch( std::exception& ex ) {
{ {
Colour colourGuard( Colour::Red ); Colour colourGuard( Colour::Red );
Catch::cerr() Catch::cerr()
<< "\nError(s) in input:\n" << "\nError(s) in input:\n"
<< Text( ex.what(), TextAttributes().setIndent(2) ) << Text( result.errorMessage(), TextAttributes().setIndent(2) )
<< "\n\n"; << "\n\n";
} }
m_cli.usage( Catch::cout(), m_configData.processName ); Catch::cerr() << m_cli << std::endl;
return (std::numeric_limits<int>::max)(); return (std::numeric_limits<int>::max)();
} }
if( m_configData.showHelp )
showHelp( m_configData.processName );
m_config.reset();
return 0; return 0;
} }
@ -146,7 +141,7 @@ namespace Catch {
m_config.reset(); m_config.reset();
} }
int run( int argc, char const* const* const argv ) { int run( int argc, char* argv[] ) {
const auto& exceptions = getRegistryHub().getStartupExceptionRegistry().getExceptions(); const auto& exceptions = getRegistryHub().getStartupExceptionRegistry().getExceptions();
if ( !exceptions.empty() ) { if ( !exceptions.empty() ) {
Catch::cerr() << "Errors occured during startup!" << '\n'; Catch::cerr() << "Errors occured during startup!" << '\n';
@ -167,7 +162,7 @@ namespace Catch {
} }
#if defined(WIN32) && defined(UNICODE) #if defined(WIN32) && defined(UNICODE)
int run( int argc, wchar_t const* const* const argv ) { int run( int argc, wchar_t* const argv[] ) {
char **utf8Argv = new char *[ argc ]; char **utf8Argv = new char *[ argc ];
@ -217,11 +212,11 @@ namespace Catch {
} }
} }
Clara::CommandLine<ConfigData> const& cli() const { clara::Parser const& cli() const {
return m_cli; return m_cli;
} }
std::vector<Clara::Parser::Token> const& unusedTokens() const { void cli( clara::Parser const& newParser ) {
return m_unusedTokens; m_cli = newParser;
} }
ConfigData& configData() { ConfigData& configData() {
return m_configData; return m_configData;
@ -232,14 +227,11 @@ namespace Catch {
return *m_config; return *m_config;
} }
private: private:
Clara::CommandLine<ConfigData> m_cli; clara::Parser m_cli;
std::vector<Clara::Parser::Token> m_unusedTokens;
ConfigData m_configData; ConfigData m_configData;
std::shared_ptr<Config> m_config; std::shared_ptr<Config> m_config;
}; };
bool Session::alreadyInstantiated = false;
} // end namespace Catch } // end namespace Catch
#endif // TWOBLUECUBES_CATCH_RUNNER_HPP_INCLUDED #endif // TWOBLUECUBES_CATCH_RUNNER_HPP_INCLUDED

1054
include/external/clara.h vendored

File diff suppressed because it is too large Load Diff

1189
include/external/clara.hpp vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -17,10 +17,7 @@
#define CLARA_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH #define CLARA_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH
// Declare Clara inside the Catch namespace #include "../external/clara.hpp"
#define STITCH_CLARA_OPEN_NAMESPACE namespace Catch {
#include "../external/clara.h"
#undef STITCH_CLARA_OPEN_NAMESPACE
// Restore Clara's value for console width, if present // Restore Clara's value for console width, if present

View File

@ -17,20 +17,90 @@
namespace Catch { namespace Catch {
inline void abortAfterFirst( ConfigData& config ) { config.abortAfter = 1; } inline clara::Parser makeCommandLineParser( ConfigData& config ) {
inline void abortAfterX( ConfigData& config, int x ) {
CATCH_ENFORCE( x >=1, "Value after -x or --abortAfter must be greater than zero" );
config.abortAfter = x;
}
inline void addTestOrTags( ConfigData& config, std::string const& testSpec ) { config.testsOrTags.push_back( testSpec ); }
inline void addSectionToRun( ConfigData& config, std::string const& sectionName ) { config.sectionsToRun.push_back( sectionName ); }
inline void addReporterName( ConfigData& config, std::string const& reporterName ) { config.reporterNames.push_back( reporterName ); }
inline void addWarning( ConfigData& config, std::string const& warning ) { auto const loadTestNamesFromFile = [&]( std::string const& filename ) {
CATCH_ENFORCE( warning == "NoAssertions", "Unrecognised warning: '" << warning << "'" ); std::ifstream f( filename.c_str() );
config.warnings = static_cast<WarnAbout::What>( config.warnings | WarnAbout::NoAssertions ); if( !f.is_open() )
return clara::ParserResult::runtimeError( "Unable to load input file: '" + filename + "'" );
std::string line;
while( std::getline( f, line ) ) {
line = trim(line);
if( !line.empty() && !startsWith( line, '#' ) ) {
if( !startsWith( line, '"' ) )
line = '"' + line + '"';
config.testsOrTags.push_back( line + ',' );
} }
inline void setOrder( ConfigData& config, std::string const& order ) { }
return clara::ParserResult::ok( clara::ParseResultType::Matched );
};
using namespace clara;
auto cli
= ExeName( config.processName )
+ Help( config.showHelp )
+ Opt( config.listTests )
["-l"]["--list-tests"]
( "list all/matching test cases" )
+ Opt( config.listTags )
["-t"]["--list-tags"]
( "list all/matching tags" )
+ Opt( config.showSuccessfulTests )
["-s"]["--success"]
( "include successful tests in output" )
+ Opt( config.shouldDebugBreak )
["-b"]["--break"]
( "break into debugger on failure" )
+ Opt( config.noThrow )
["-e"]["--nothrow"]
( "skip exception tests" )
+ Opt( config.showInvisibles )
["-i"]["--invisibles"]
( "show invisibles (tabs, newlines)" )
+ Opt( config.outputFilename, "filename" )
["-o"]["--out"]
( "output filename" )
+ Opt( config.reporterNames, "name" )
["-r"]["--reporter"]
( "reporter to use (defaults to console)" )
+ Opt( config.name, "name" )
["-n"]["--name"]
( "suite name" )
+ Opt( [&]( bool ){ config.abortAfter = 1; } )
["-a"]["--abort"]
( "abort at first failure" )
+ Opt( [&]( int x ){ config.abortAfter = x; }, "no. failures" )
["-x"]["--abortx"]
( "abort after x failures" )
+ Opt( [&]( std::string const& warning ) {
if( warning != "NoAssertions" )
return clara::ParserResult::runtimeError( "Unrecognised warning: '" + warning + "'" );
config.warnings = static_cast<WarnAbout::What>( config.warnings | WarnAbout::NoAssertions );
return clara::ParserResult::ok( ParseResultType::Matched );
}, "warning name" )
["-w"]["--warn"]
( "enable warnings" )
+ Opt( [&]( bool ) { config.showDurations = ShowDurations::Always; } )
["-d"]["--durations"]
( "show test durations" )
+ Opt( loadTestNamesFromFile, "filename" )
["-f"]["--input-file"]
( "load test names to run from a file" )
+ Opt( config.filenamesAsTags )
["-#"]["--filenames-as-tags"]
( "adds a tag for the filename" )
+ Opt( config.sectionsToRun, "section name" )
["-c"]["--section"]
( "specify section to run" )
+ Opt( config.listTestNamesOnly )
["--list-test-names-only"]
( "list all/matching test cases names only" )
+ Opt( config.listReporters )
["--list-reporters"]
( "list all reporters" )
+ Opt( [&]( std::string const& order ) {
if( startsWith( "declared", order ) ) if( startsWith( "declared", order ) )
config.runOrder = RunTests::InDeclarationOrder; config.runOrder = RunTests::InDeclarationOrder;
else if( startsWith( "lexical", order ) ) else if( startsWith( "lexical", order ) )
@ -38,30 +108,21 @@ namespace Catch {
else if( startsWith( "random", order ) ) else if( startsWith( "random", order ) )
config.runOrder = RunTests::InRandomOrder; config.runOrder = RunTests::InRandomOrder;
else else
CATCH_ENFORCE( false, "Unrecognised ordering: '" << order << '\'' ); return clara::ParserResult::runtimeError( "Unrecognised ordering: '" + order + "'" );
} return clara::ParserResult::ok( ParseResultType::Matched );
inline void setRngSeed( ConfigData& config, std::string const& seed ) { }, "decl|lex|rand" )
if( seed == "time" ) { ["--order"]
( "test case order (defaults to decl)" )
+ Opt( [&]( std::string const& seed ) {
if( seed != "time" )
return clara::detail::convertInto( seed, config.rngSeed );
config.rngSeed = static_cast<unsigned int>( std::time(0) ); config.rngSeed = static_cast<unsigned int>( std::time(0) );
} return clara::ParserResult::ok( ParseResultType::Matched );
else { }, "'time'|number" )
std::stringstream ss; ["--rng-seed"]
ss << seed; ( "set a specific seed for random numbers" )
ss >> config.rngSeed; + Opt( [&]( std::string const& useColour ) {
CATCH_ENFORCE( !ss.fail(), "Argument to --rng-seed should be the word 'time' or a number" ); auto mode = toLower( useColour );
}
}
inline void setVerbosity( ConfigData& config, int level ) {
// !TBD: accept strings?
config.verbosity = static_cast<Verbosity::Level>( level );
}
inline void setShowDurations( ConfigData& config, bool _showDurations ) {
config.showDurations = _showDurations
? ShowDurations::Always
: ShowDurations::Never;
}
inline void setUseColour( ConfigData& config, std::string const& value ) {
std::string mode = toLower( value );
if( mode == "yes" ) if( mode == "yes" )
config.useColour = UseColour::Yes; config.useColour = UseColour::Yes;
@ -70,137 +131,14 @@ namespace Catch {
else if( mode == "auto" ) else if( mode == "auto" )
config.useColour = UseColour::Auto; config.useColour = UseColour::Auto;
else else
CATCH_ENFORCE( false, "colour mode must be one of: auto, yes or no" ); return clara::ParserResult::runtimeError( "colour mode must be one of: auto, yes or no. '" + useColour + "' not recognised" );
} return clara::ParserResult::ok( ParseResultType::Matched );
inline void forceColour( ConfigData& config ) { }, "yes|no" )
config.useColour = UseColour::Yes; ["--use-colour"]
} ( "should output be colourised" )
inline void loadTestNamesFromFile( ConfigData& config, std::string const& _filename ) {
std::ifstream f( _filename.c_str() );
CATCH_ENFORCE( f.is_open(), "Unable to load input file: '" << _filename << "'" );
std::string line; + Arg( config.testsOrTags, "test name|pattern|tags" )
while( std::getline( f, line ) ) { ( "which test or tests to use" );
line = trim(line);
if( !line.empty() && !startsWith( line, '#' ) ) {
if( !startsWith( line, '"' ) )
line = '"' + line + '"';
addTestOrTags( config, line + ',' );
}
}
}
inline Clara::CommandLine<ConfigData> makeCommandLineParser() {
using namespace Clara;
CommandLine<ConfigData> cli;
cli.bindProcessName( &ConfigData::processName );
cli["-?"]["-h"]["--help"]
.describe( "display usage information" )
.bind( &ConfigData::showHelp );
cli["-l"]["--list-tests"]
.describe( "list all/matching test cases" )
.bind( &ConfigData::listTests );
cli["-t"]["--list-tags"]
.describe( "list all/matching tags" )
.bind( &ConfigData::listTags );
cli["-s"]["--success"]
.describe( "include successful tests in output" )
.bind( &ConfigData::showSuccessfulTests );
cli["-b"]["--break"]
.describe( "break into debugger on failure" )
.bind( &ConfigData::shouldDebugBreak );
cli["-e"]["--nothrow"]
.describe( "skip exception tests" )
.bind( &ConfigData::noThrow );
cli["-i"]["--invisibles"]
.describe( "show invisibles (tabs, newlines)" )
.bind( &ConfigData::showInvisibles );
cli["-o"]["--out"]
.describe( "output filename" )
.bind( &ConfigData::outputFilename, "filename" );
cli["-r"]["--reporter"]
// .placeholder( "name[:filename]" )
.describe( "reporter to use (defaults to console)" )
.bind( &addReporterName, "name" );
cli["-n"]["--name"]
.describe( "suite name" )
.bind( &ConfigData::name, "name" );
cli["-a"]["--abort"]
.describe( "abort at first failure" )
.bind( &abortAfterFirst );
cli["-x"]["--abortx"]
.describe( "abort after x failures" )
.bind( &abortAfterX, "no. failures" );
cli["-w"]["--warn"]
.describe( "enable warnings" )
.bind( &addWarning, "warning name" );
// - needs updating if reinstated
// cli.into( &setVerbosity )
// .describe( "level of verbosity (0=no output)" )
// .shortOpt( "v")
// .longOpt( "verbosity" )
// .placeholder( "level" );
cli[_]
.describe( "which test or tests to use" )
.bind( &addTestOrTags, "test name, pattern or tags" );
cli["-d"]["--durations"]
.describe( "show test durations" )
.bind( &setShowDurations, "yes|no" );
cli["-f"]["--input-file"]
.describe( "load test names to run from a file" )
.bind( &loadTestNamesFromFile, "filename" );
cli["-#"]["--filenames-as-tags"]
.describe( "adds a tag for the filename" )
.bind( &ConfigData::filenamesAsTags );
cli["-c"]["--section"]
.describe( "specify section to run" )
.bind( &addSectionToRun, "section name" );
// Less common commands which don't have a short form
cli["--list-test-names-only"]
.describe( "list all/matching test cases names only" )
.bind( &ConfigData::listTestNamesOnly );
cli["--list-reporters"]
.describe( "list all reporters" )
.bind( &ConfigData::listReporters );
cli["--order"]
.describe( "test case order (defaults to decl)" )
.bind( &setOrder, "decl|lex|rand" );
cli["--rng-seed"]
.describe( "set a specific seed for random numbers" )
.bind( &setRngSeed, "'time'|number" );
cli["--force-colour"]
.describe( "force colourised output (deprecated)" )
.bind( &forceColour );
cli["--use-colour"]
.describe( "should output be colourised" )
.bind( &setUseColour, "yes|no" );
return cli; return cli;
} }

View File

@ -25,24 +25,24 @@ CATCH_REGISTER_TAG_ALIAS( "[@tricky]", "[tricky]~[.]" )
#endif #endif
template<size_t size> //template<size_t size>
void parseIntoConfig( const char * (&argv)[size], Catch::ConfigData& config ) { //void parseIntoConfig( const char * (&argv)[size], Catch::ConfigData& config ) {
Catch::Clara::CommandLine<Catch::ConfigData> parser = Catch::makeCommandLineParser(); // auto parser = Catch::makeCommandLineParser();
parser.parseInto( Catch::Clara::argsToVector( size, argv ), config ); // parser.parseInto( Catch::Clara::argsToVector( size, argv ), config );
} //}
//
template<size_t size> //template<size_t size>
std::string parseIntoConfigAndReturnError( const char * (&argv)[size], Catch::ConfigData& config ) { //std::string parseIntoConfigAndReturnError( const char * (&argv)[size], Catch::ConfigData& config ) {
try { // try {
parseIntoConfig( argv, config ); // parseIntoConfig( argv, config );
FAIL( "expected exception" ); // FAIL( "expected exception" );
} // }
catch( std::exception& ex ) { // catch( std::exception& ex ) {
return ex.what(); // return ex.what();
} // }
return ""; // return "";
} //}
//
inline Catch::TestCase fakeTestCase( const char* name, const char* desc = "" ){ return Catch::makeTestCase( nullptr, "", name, desc, CATCH_INTERNAL_LINEINFO ); } inline Catch::TestCase fakeTestCase( const char* name, const char* desc = "" ){ return Catch::makeTestCase( nullptr, "", name, desc, CATCH_INTERNAL_LINEINFO ); }
TEST_CASE( "Process can be configured on command line", "[config][command-line]" ) { TEST_CASE( "Process can be configured on command line", "[config][command-line]" ) {
@ -50,18 +50,18 @@ TEST_CASE( "Process can be configured on command line", "[config][command-line]"
using namespace Catch::Matchers; using namespace Catch::Matchers;
Catch::ConfigData config; Catch::ConfigData config;
auto cli = Catch::makeCommandLineParser(config);
SECTION("empty args don't cause a crash") { SECTION("empty args don't cause a crash") {
Catch::Clara::CommandLine<Catch::ConfigData> parser = Catch::makeCommandLineParser(); auto result = cli.parse({""});
CHECK_NOTHROW( parser.parseInto( std::vector<std::string>(), config ) ); CHECK(result);
CHECK(config.processName == ""); CHECK(config.processName == "");
} }
SECTION( "default - no arguments", "" ) {
const char* argv[] = { "test" };
CHECK_NOTHROW( parseIntoConfig( argv, config ) );
SECTION("default - no arguments") {
auto result = cli.parse({"test"});
CHECK(result);
CHECK(config.processName == "test"); CHECK(config.processName == "test");
CHECK(config.shouldDebugBreak == false); CHECK(config.shouldDebugBreak == false);
CHECK(config.abortAfter == -1); CHECK(config.abortAfter == -1);
@ -69,27 +69,27 @@ TEST_CASE( "Process can be configured on command line", "[config][command-line]"
CHECK(config.reporterNames.empty()); CHECK(config.reporterNames.empty());
} }
SECTION( "test lists", "" ) { SECTION("test lists") {
SECTION("1 test", "Specify one test case using") { SECTION("1 test", "Specify one test case using") {
const char* argv[] = { "test", "test1" }; auto result = cli.parse({"test", "test1"});
CHECK_NOTHROW( parseIntoConfig( argv, config ) ); CHECK(result);
Catch::Config cfg(config); Catch::Config cfg(config);
REQUIRE(cfg.testSpec().matches(fakeTestCase("notIncluded")) == false); REQUIRE(cfg.testSpec().matches(fakeTestCase("notIncluded")) == false);
REQUIRE(cfg.testSpec().matches(fakeTestCase("test1"))); REQUIRE(cfg.testSpec().matches(fakeTestCase("test1")));
} }
SECTION( "Specify one test case exclusion using exclude:", "" ) { SECTION("Specify one test case exclusion using exclude:") {
const char* argv[] = { "test", "exclude:test1" }; auto result = cli.parse({"test", "exclude:test1"});
CHECK_NOTHROW( parseIntoConfig( argv, config ) ); CHECK(result);
Catch::Config cfg(config); Catch::Config cfg(config);
REQUIRE(cfg.testSpec().matches(fakeTestCase("test1")) == false); REQUIRE(cfg.testSpec().matches(fakeTestCase("test1")) == false);
REQUIRE(cfg.testSpec().matches(fakeTestCase("alwaysIncluded"))); REQUIRE(cfg.testSpec().matches(fakeTestCase("alwaysIncluded")));
} }
SECTION( "Specify one test case exclusion using ~", "" ) { SECTION("Specify one test case exclusion using ~") {
const char* argv[] = { "test", "~test1" }; auto result = cli.parse({"test", "~test1"});
CHECK_NOTHROW( parseIntoConfig( argv, config ) ); CHECK(result);
Catch::Config cfg(config); Catch::Config cfg(config);
REQUIRE(cfg.testSpec().matches(fakeTestCase("test1")) == false); REQUIRE(cfg.testSpec().matches(fakeTestCase("test1")) == false);
@ -98,107 +98,94 @@ TEST_CASE( "Process can be configured on command line", "[config][command-line]"
} }
SECTION( "reporter", "" ) { SECTION("reporter") {
SECTION( "-r/console", "" ) { SECTION("-r/console") {
const char* argv[] = { "test", "-r", "console" }; CHECK(cli.parse({"test", "-r", "console"}));
CHECK_NOTHROW( parseIntoConfig( argv, config ) );
REQUIRE(config.reporterNames[0] == "console"); REQUIRE(config.reporterNames[0] == "console");
} }
SECTION( "-r/xml", "" ) { SECTION("-r/xml") {
const char* argv[] = { "test", "-r", "xml" }; CHECK(cli.parse({"test", "-r", "xml"}));
CHECK_NOTHROW( parseIntoConfig( argv, config ) );
REQUIRE(config.reporterNames[0] == "xml"); REQUIRE(config.reporterNames[0] == "xml");
} }
SECTION( "-r xml and junit", "" ) { SECTION("-r xml and junit") {
const char* argv[] = { "test", "-r", "xml", "-r", "junit" }; CHECK(cli.parse({"test", "-r", "xml", "-r", "junit"}));
CHECK_NOTHROW( parseIntoConfig( argv, config ) );
REQUIRE(config.reporterNames.size() == 2); REQUIRE(config.reporterNames.size() == 2);
REQUIRE(config.reporterNames[0] == "xml"); REQUIRE(config.reporterNames[0] == "xml");
REQUIRE(config.reporterNames[1] == "junit"); REQUIRE(config.reporterNames[1] == "junit");
} }
SECTION( "--reporter/junit", "" ) { SECTION("--reporter/junit") {
const char* argv[] = { "test", "--reporter", "junit" }; CHECK(cli.parse({"test", "--reporter", "junit"}));
CHECK_NOTHROW( parseIntoConfig( argv, config ) );
REQUIRE(config.reporterNames[0] == "junit"); REQUIRE(config.reporterNames[0] == "junit");
} }
} }
SECTION( "debugger", "" ) {
SECTION( "-b", "" ) { SECTION("debugger") {
const char* argv[] = { "test", "-b" }; SECTION("-b") {
CHECK_NOTHROW( parseIntoConfig( argv, config ) ); CHECK(cli.parse({"test", "-b"}));
REQUIRE(config.shouldDebugBreak == true); REQUIRE(config.shouldDebugBreak == true);
} }
SECTION( "--break", "" ) { SECTION("--break") {
const char* argv[] = { "test", "--break" }; CHECK(cli.parse({"test", "--break"}));
CHECK_NOTHROW( parseIntoConfig( argv, config ) );
REQUIRE(config.shouldDebugBreak); REQUIRE(config.shouldDebugBreak);
} }
} }
SECTION( "abort", "" ) {
SECTION( "-a aborts after first failure", "" ) { SECTION("abort") {
const char* argv[] = { "test", "-a" }; SECTION("-a aborts after first failure") {
CHECK_NOTHROW( parseIntoConfig( argv, config ) ); CHECK(cli.parse({"test", "-a"}));
REQUIRE(config.abortAfter == 1); REQUIRE(config.abortAfter == 1);
} }
SECTION( "-x 2 aborts after two failures", "" ) { SECTION("-x 2 aborts after two failures") {
const char* argv[] = { "test", "-x", "2" }; CHECK(cli.parse({"test", "-x", "2"}));
CHECK_NOTHROW( parseIntoConfig( argv, config ) );
REQUIRE(config.abortAfter == 2); REQUIRE(config.abortAfter == 2);
} }
SECTION( "-x must be greater than zero", "" ) { SECTION("-x must be numeric") {
const char* argv[] = { "test", "-x", "0" }; auto result = cli.parse({"test", "-x", "oops"});
REQUIRE_THAT( parseIntoConfigAndReturnError( argv, config ), Contains( "greater than zero" ) ); CHECK(!result);
}
SECTION( "-x must be numeric", "" ) { REQUIRE_THAT(result.errorMessage(), Contains("convert") && Contains("oops"));
const char* argv[] = { "test", "-x", "oops" };
REQUIRE_THAT( parseIntoConfigAndReturnError( argv, config ), Contains( "-x" ) );
} }
} }
SECTION( "nothrow", "" ) { SECTION("nothrow") {
SECTION( "-e", "" ) { SECTION("-e") {
const char* argv[] = { "test", "-e" }; CHECK(cli.parse({"test", "-e"}));
CHECK_NOTHROW( parseIntoConfig( argv, config ) );
REQUIRE( config.noThrow == true ); REQUIRE(config.noThrow);
} }
SECTION( "--nothrow", "" ) { SECTION("--nothrow") {
const char* argv[] = { "test", "--nothrow" }; CHECK(cli.parse({"test", "--nothrow"}));
CHECK_NOTHROW( parseIntoConfig( argv, config ) );
REQUIRE( config.noThrow == true ); REQUIRE(config.noThrow);
} }
} }
SECTION( "output filename", "" ) { SECTION("output filename") {
SECTION( "-o filename", "" ) { SECTION("-o filename") {
const char* argv[] = { "test", "-o", "filename.ext" }; CHECK(cli.parse({"test", "-o", "filename.ext"}));
CHECK_NOTHROW( parseIntoConfig( argv, config ) );
REQUIRE(config.outputFilename == "filename.ext"); REQUIRE(config.outputFilename == "filename.ext");
} }
SECTION( "--out", "" ) { SECTION("--out") {
const char* argv[] = { "test", "--out", "filename.ext" }; CHECK(cli.parse({"test", "--out", "filename.ext"}));
CHECK_NOTHROW( parseIntoConfig( argv, config ) );
REQUIRE(config.outputFilename == "filename.ext"); REQUIRE(config.outputFilename == "filename.ext");
} }
} }
SECTION( "combinations", "" ) { SECTION("combinations") {
SECTION( "Single character flags can be combined", "" ) { SECTION("Single character flags can be combined") {
const char* argv[] = { "test", "-abe" }; CHECK(cli.parse({"test", "-abe"}));
CHECK_NOTHROW( parseIntoConfig( argv, config ) );
CHECK(config.abortAfter == 1); CHECK(config.abortAfter == 1);
CHECK(config.shouldDebugBreak); CHECK(config.shouldDebugBreak);
@ -206,41 +193,39 @@ TEST_CASE( "Process can be configured on command line", "[config][command-line]"
} }
} }
SECTION( "use-colour", "") {
SECTION( "use-colour") {
using Catch::UseColour; using Catch::UseColour;
SECTION( "without option", "" ) { SECTION( "without option" ) {
const char* argv[] = { "test" }; CHECK(cli.parse({"test"}));
CHECK_NOTHROW( parseIntoConfig( argv, config ) );
REQUIRE( config.useColour == UseColour::Auto ); REQUIRE( config.useColour == UseColour::Auto );
} }
SECTION( "auto", "" ) { SECTION( "auto" ) {
const char* argv[] = { "test", "--use-colour", "auto" }; CHECK(cli.parse({"test", "--use-colour", "auto"}));
CHECK_NOTHROW( parseIntoConfig( argv, config ) );
REQUIRE( config.useColour == UseColour::Auto ); REQUIRE( config.useColour == UseColour::Auto );
} }
SECTION( "yes", "" ) { SECTION( "yes" ) {
const char* argv[] = { "test", "--use-colour", "yes" }; CHECK(cli.parse({"test", "--use-colour", "yes"}));
CHECK_NOTHROW( parseIntoConfig( argv, config ) );
REQUIRE( config.useColour == UseColour::Yes ); REQUIRE( config.useColour == UseColour::Yes );
} }
SECTION( "no", "" ) { SECTION( "no" ) {
const char* argv[] = { "test", "--use-colour", "no" }; CHECK(cli.parse({"test", "--use-colour", "no"}));
CHECK_NOTHROW( parseIntoConfig( argv, config ) );
REQUIRE( config.useColour == UseColour::No ); REQUIRE( config.useColour == UseColour::No );
} }
SECTION( "error", "" ) { SECTION( "error" ) {
const char* argv[] = { "test", "--use-colour", "wrong" }; auto result = cli.parse({"test", "--use-colour", "wrong"});
REQUIRE_THROWS_WITH( parseIntoConfig( argv, config ), Contains( "colour mode must be one of" ) ); CHECK( !result );
CHECK_THAT( result.errorMessage(), Contains( "colour mode must be one of" ) );
} }
} }
} }
@ -249,31 +234,31 @@ TEST_CASE( "Process can be configured on command line", "[config][command-line]"
TEST_CASE( "Long strings can be wrapped", "[wrap]" ) { TEST_CASE( "Long strings can be wrapped", "[wrap]" ) {
using namespace Catch; using namespace Catch;
SECTION( "plain string", "" ) { SECTION( "plain string" ) {
// guide: 123456789012345678 // guide: 123456789012345678
std::string testString = "one two three four"; std::string testString = "one two three four";
SECTION( "No wrapping", "" ) { SECTION( "No wrapping" ) {
CHECK( Text( testString, TextAttributes().setWidth( 80 ) ).toString() == testString ); CHECK( Text( testString, TextAttributes().setWidth( 80 ) ).toString() == testString );
CHECK( Text( testString, TextAttributes().setWidth( 18 ) ).toString() == testString ); CHECK( Text( testString, TextAttributes().setWidth( 18 ) ).toString() == testString );
} }
SECTION( "Wrapped once", "" ) { SECTION( "Wrapped once" ) {
CHECK( Text( testString, TextAttributes().setWidth( 17 ) ).toString() == "one two three\nfour" ); CHECK( Text( testString, TextAttributes().setWidth( 17 ) ).toString() == "one two three\nfour" );
CHECK( Text( testString, TextAttributes().setWidth( 16 ) ).toString() == "one two three\nfour" ); CHECK( Text( testString, TextAttributes().setWidth( 16 ) ).toString() == "one two three\nfour" );
CHECK( Text( testString, TextAttributes().setWidth( 14 ) ).toString() == "one two three\nfour" ); CHECK( Text( testString, TextAttributes().setWidth( 14 ) ).toString() == "one two three\nfour" );
CHECK( Text( testString, TextAttributes().setWidth( 13 ) ).toString() == "one two three\nfour" ); CHECK( Text( testString, TextAttributes().setWidth( 13 ) ).toString() == "one two three\nfour" );
CHECK( Text( testString, TextAttributes().setWidth( 12 ) ).toString() == "one two\nthree four" ); CHECK( Text( testString, TextAttributes().setWidth( 12 ) ).toString() == "one two\nthree four" );
} }
SECTION( "Wrapped twice", "" ) { SECTION( "Wrapped twice" ) {
CHECK( Text( testString, TextAttributes().setWidth( 9 ) ).toString() == "one two\nthree\nfour" ); CHECK( Text( testString, TextAttributes().setWidth( 9 ) ).toString() == "one two\nthree\nfour" );
CHECK( Text( testString, TextAttributes().setWidth( 8 ) ).toString() == "one two\nthree\nfour" ); CHECK( Text( testString, TextAttributes().setWidth( 8 ) ).toString() == "one two\nthree\nfour" );
CHECK( Text( testString, TextAttributes().setWidth( 7 ) ).toString() == "one two\nthree\nfour" ); CHECK( Text( testString, TextAttributes().setWidth( 7 ) ).toString() == "one two\nthree\nfour" );
} }
SECTION( "Wrapped three times", "" ) { SECTION( "Wrapped three times" ) {
CHECK( Text( testString, TextAttributes().setWidth( 6 ) ).toString() == "one\ntwo\nthree\nfour" ); CHECK( Text( testString, TextAttributes().setWidth( 6 ) ).toString() == "one\ntwo\nthree\nfour" );
CHECK( Text( testString, TextAttributes().setWidth( 5 ) ).toString() == "one\ntwo\nthree\nfour" ); CHECK( Text( testString, TextAttributes().setWidth( 5 ) ).toString() == "one\ntwo\nthree\nfour" );
} }
SECTION( "Short wrap", "" ) { SECTION( "Short wrap" ) {
CHECK( Text( "abcdef", TextAttributes().setWidth( 4 ) ).toString() == "abc-\ndef" ); CHECK( Text( "abcdef", TextAttributes().setWidth( 4 ) ).toString() == "abc-\ndef" );
CHECK( Text( "abcdefg", TextAttributes().setWidth( 4 ) ).toString() == "abc-\ndefg" ); CHECK( Text( "abcdefg", TextAttributes().setWidth( 4 ) ).toString() == "abc-\ndefg" );
CHECK( Text( "abcdefgh", TextAttributes().setWidth( 4 ) ).toString() == "abc-\ndef-\ngh" ); CHECK( Text( "abcdefgh", TextAttributes().setWidth( 4 ) ).toString() == "abc-\ndef-\ngh" );
@ -281,7 +266,7 @@ TEST_CASE( "Long strings can be wrapped", "[wrap]" ) {
CHECK( Text( testString, TextAttributes().setWidth( 4 ) ).toString() == "one\ntwo\nthr-\nee\nfour" ); CHECK( Text( testString, TextAttributes().setWidth( 4 ) ).toString() == "one\ntwo\nthr-\nee\nfour" );
CHECK( Text( testString, TextAttributes().setWidth( 3 ) ).toString() == "one\ntwo\nth-\nree\nfo-\nur" ); CHECK( Text( testString, TextAttributes().setWidth( 3 ) ).toString() == "one\ntwo\nth-\nree\nfo-\nur" );
} }
SECTION( "As container", "" ) { SECTION( "As container" ) {
Text text( testString, TextAttributes().setWidth( 6 ) ); Text text( testString, TextAttributes().setWidth( 6 ) );
REQUIRE( text.size() == 4 ); REQUIRE( text.size() == 4 );
CHECK( text[0] == "one" ); CHECK( text[0] == "one" );
@ -289,7 +274,7 @@ TEST_CASE( "Long strings can be wrapped", "[wrap]" ) {
CHECK( text[2] == "three" ); CHECK( text[2] == "three" );
CHECK( text[3] == "four" ); CHECK( text[3] == "four" );
} }
SECTION( "Indent first line differently", "" ) { SECTION( "Indent first line differently" ) {
Text text( testString, TextAttributes() Text text( testString, TextAttributes()
.setWidth( 10 ) .setWidth( 10 )
.setIndent( 4 ) .setIndent( 4 )
@ -299,43 +284,43 @@ TEST_CASE( "Long strings can be wrapped", "[wrap]" ) {
} }
SECTION( "With newlines", "" ) { SECTION( "With newlines" ) {
// guide: 1234567890123456789 // guide: 1234567890123456789
std::string testString = "one two\nthree four"; std::string testString = "one two\nthree four";
SECTION( "No wrapping" , "" ) { SECTION( "No wrapping" ) {
CHECK( Text( testString, TextAttributes().setWidth( 80 ) ).toString() == testString ); CHECK( Text( testString, TextAttributes().setWidth( 80 ) ).toString() == testString );
CHECK( Text( testString, TextAttributes().setWidth( 18 ) ).toString() == testString ); CHECK( Text( testString, TextAttributes().setWidth( 18 ) ).toString() == testString );
CHECK( Text( testString, TextAttributes().setWidth( 10 ) ).toString() == testString ); CHECK( Text( testString, TextAttributes().setWidth( 10 ) ).toString() == testString );
} }
SECTION( "Trailing newline" , "" ) { SECTION( "Trailing newline" ) {
CHECK( Text( "abcdef\n", TextAttributes().setWidth( 10 ) ).toString() == "abcdef" ); CHECK( Text( "abcdef\n", TextAttributes().setWidth( 10 ) ).toString() == "abcdef" );
CHECK( Text( "abcdef", TextAttributes().setWidth( 6 ) ).toString() == "abcdef" ); CHECK( Text( "abcdef", TextAttributes().setWidth( 6 ) ).toString() == "abcdef" );
CHECK( Text( "abcdef\n", TextAttributes().setWidth( 6 ) ).toString() == "abcdef" ); CHECK( Text( "abcdef\n", TextAttributes().setWidth( 6 ) ).toString() == "abcdef" );
CHECK( Text( "abcdef\n", TextAttributes().setWidth( 5 ) ).toString() == "abcd-\nef" ); CHECK( Text( "abcdef\n", TextAttributes().setWidth( 5 ) ).toString() == "abcd-\nef" );
} }
SECTION( "Wrapped once", "" ) { SECTION( "Wrapped once" ) {
CHECK( Text( testString, TextAttributes().setWidth( 9 ) ).toString() == "one two\nthree\nfour" ); CHECK( Text( testString, TextAttributes().setWidth( 9 ) ).toString() == "one two\nthree\nfour" );
CHECK( Text( testString, TextAttributes().setWidth( 8 ) ).toString() == "one two\nthree\nfour" ); CHECK( Text( testString, TextAttributes().setWidth( 8 ) ).toString() == "one two\nthree\nfour" );
CHECK( Text( testString, TextAttributes().setWidth( 7 ) ).toString() == "one two\nthree\nfour" ); CHECK( Text( testString, TextAttributes().setWidth( 7 ) ).toString() == "one two\nthree\nfour" );
} }
SECTION( "Wrapped twice", "" ) { SECTION( "Wrapped twice" ) {
CHECK( Text( testString, TextAttributes().setWidth( 6 ) ).toString() == "one\ntwo\nthree\nfour" ); CHECK( Text( testString, TextAttributes().setWidth( 6 ) ).toString() == "one\ntwo\nthree\nfour" );
} }
} }
SECTION( "With wrap-before/ after characters", "" ) { SECTION( "With wrap-before/ after characters" ) {
std::string testString = "one,two(three) <here>"; std::string testString = "one,two(three) <here>";
SECTION( "No wrapping", "" ) { SECTION( "No wrapping" ) {
CHECK( Text( testString, TextAttributes().setWidth( 80 ) ).toString() == testString ); CHECK( Text( testString, TextAttributes().setWidth( 80 ) ).toString() == testString );
CHECK( Text( testString, TextAttributes().setWidth( 24 ) ).toString() == testString ); CHECK( Text( testString, TextAttributes().setWidth( 24 ) ).toString() == testString );
} }
SECTION( "Wrap before", "" ) { SECTION( "Wrap before" ) {
CHECK( Text( testString, TextAttributes().setWidth( 11 ) ).toString() == "one,two\n(three)\n<here>" ); CHECK( Text( testString, TextAttributes().setWidth( 11 ) ).toString() == "one,two\n(three)\n<here>" );
} }
SECTION( "Wrap after", "" ) { SECTION( "Wrap after" ) {
CHECK( Text( testString, TextAttributes().setWidth( 6 ) ).toString() == "one,\ntwo\n(thre-\ne)\n<here>" ); CHECK( Text( testString, TextAttributes().setWidth( 6 ) ).toString() == "one,\ntwo\n(thre-\ne)\n<here>" );
CHECK( Text( testString, TextAttributes().setWidth( 5 ) ).toString() == "one,\ntwo\n(thr-\nee)\n<her-\ne>" ); CHECK( Text( testString, TextAttributes().setWidth( 5 ) ).toString() == "one,\ntwo\n(thr-\nee)\n<her-\ne>" );
CHECK( Text( testString, TextAttributes().setWidth( 4 ) ).toString() == "one,\ntwo\n(th-\nree)\n<he-\nre>" ); CHECK( Text( testString, TextAttributes().setWidth( 4 ) ).toString() == "one,\ntwo\n(th-\nree)\n<he-\nre>" );
@ -416,7 +401,7 @@ private:
std::vector<ColourIndex> colours; std::vector<ColourIndex> colours;
}; };
TEST_CASE( "replaceInPlace", "" ) { TEST_CASE( "replaceInPlace" ) {
std::string letters = "abcdefcg"; std::string letters = "abcdefcg";
SECTION( "replace single char" ) { SECTION( "replace single char" ) {
CHECK( replaceInPlace( letters, "b", "z" ) ); CHECK( replaceInPlace( letters, "b", "z" ) );
@ -469,7 +454,7 @@ TEST_CASE( "Strings can be rendered with colour", "[.colour]" ) {
} }
TEST_CASE( "Text can be formatted using the Text class", "" ) { TEST_CASE( "Text can be formatted using the Text class" ) {
CHECK( Text( "hi there" ).toString() == "hi there" ); CHECK( Text( "hi there" ).toString() == "hi there" );