2020-08-30 15:43:45 +02:00
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
2020-03-30 10:34:21 +02:00
# include <catch2/internal/catch_commandline.hpp>
2017-07-10 14:25:38 +02:00
2021-10-06 00:00:46 +02:00
# include <catch2/internal/catch_compiler_capabilities.hpp>
2020-07-29 16:29:41 +02:00
# include <catch2/catch_config.hpp>
2020-03-30 10:34:21 +02:00
# include <catch2/internal/catch_string_manip.hpp>
# include <catch2/interfaces/catch_interfaces_registry_hub.hpp>
2020-08-23 18:16:10 +02:00
# include <catch2/interfaces/catch_interfaces_reporter_registry.hpp>
2020-03-30 10:34:21 +02:00
# include <catch2/interfaces/catch_interfaces_reporter.hpp>
2022-03-27 23:35:41 +02:00
# include <catch2/internal/catch_console_colour.hpp>
2022-04-04 15:38:25 +02:00
# include <catch2/internal/catch_reporter_spec_parser.hpp>
2018-10-22 15:59:01 +02:00
2021-02-06 20:12:07 +01:00
# include <algorithm>
2017-07-10 14:25:38 +02:00
# include <fstream>
2021-10-06 00:00:46 +02:00
# include <string>
2017-07-10 14:25:38 +02:00
namespace Catch {
2020-08-08 18:18:27 +02:00
Clara : : Parser makeCommandLineParser ( ConfigData & config ) {
2017-07-10 14:25:38 +02:00
2020-08-08 18:18:27 +02:00
using namespace Clara ;
2017-07-10 14:25:38 +02:00
auto const setWarning = [ & ] ( std : : string const & warning ) {
2021-12-13 15:15:23 +01:00
if ( warning = = " NoAssertions " ) {
2021-12-18 20:48:43 +01:00
config . warnings = static_cast < WarnAbout : : What > ( config . warnings | WarnAbout : : NoAssertions ) ;
2017-07-10 14:25:38 +02:00
return ParserResult : : ok ( ParseResultType : : Matched ) ;
2021-12-18 19:59:23 +01:00
} else if ( warning = = " UnmatchedTestSpec " ) {
2021-12-18 20:48:43 +01:00
config . warnings = static_cast < WarnAbout : : What > ( config . warnings | WarnAbout : : UnmatchedTestSpec ) ;
2021-12-18 19:59:23 +01:00
return ParserResult : : ok ( ParseResultType : : Matched ) ;
2021-12-13 15:15:23 +01:00
}
return ParserResult : : runtimeError (
" Unrecognised warning option: ' " + warning + ' \' ' ) ;
} ;
2017-07-10 14:25:38 +02:00
auto const loadTestNamesFromFile = [ & ] ( std : : string const & filename ) {
std : : ifstream f ( filename . c_str ( ) ) ;
if ( ! f . is_open ( ) )
2021-10-03 10:09:10 +02:00
return ParserResult : : runtimeError ( " Unable to load input file: ' " + filename + ' \' ' ) ;
2017-07-10 14:25:38 +02:00
std : : string line ;
while ( std : : getline ( f , line ) ) {
line = trim ( line ) ;
if ( ! line . empty ( ) & & ! startsWith ( line , ' # ' ) ) {
if ( ! startsWith ( line , ' " ' ) )
line = ' " ' + line + ' " ' ;
2019-10-19 16:50:46 +03:00
config . testsOrTags . push_back ( line ) ;
2020-01-21 21:04:42 +01:00
config . testsOrTags . emplace_back ( " , " ) ;
2017-07-10 14:25:38 +02:00
}
}
2019-10-19 16:50:46 +03:00
//Remove comma in the end
if ( ! config . testsOrTags . empty ( ) )
config . testsOrTags . erase ( config . testsOrTags . end ( ) - 1 ) ;
2020-01-21 21:04:42 +01:00
2017-07-10 14:25:38 +02:00
return ParserResult : : ok ( ParseResultType : : Matched ) ;
} ;
auto const setTestOrder = [ & ] ( std : : string const & order ) {
if ( startsWith ( " declared " , order ) )
2020-08-11 15:46:03 +02:00
config . runOrder = TestRunOrder : : Declared ;
2017-07-10 14:25:38 +02:00
else if ( startsWith ( " lexical " , order ) )
2020-08-11 15:46:03 +02:00
config . runOrder = TestRunOrder : : LexicographicallySorted ;
2017-07-10 14:25:38 +02:00
else if ( startsWith ( " random " , order ) )
2020-08-11 15:46:03 +02:00
config . runOrder = TestRunOrder : : Randomized ;
2017-07-10 14:25:38 +02:00
else
2021-10-03 10:09:10 +02:00
return ParserResult : : runtimeError ( " Unrecognised ordering: ' " + order + ' \' ' ) ;
2017-07-10 14:25:38 +02:00
return ParserResult : : ok ( ParseResultType : : Matched ) ;
} ;
auto const setRngSeed = [ & ] ( std : : string const & seed ) {
2021-10-06 00:00:46 +02:00
if ( seed = = " time " ) {
2021-10-08 21:32:03 +02:00
config . rngSeed = generateRandomSeed ( GenerateFrom : : Time ) ;
return ParserResult : : ok ( ParseResultType : : Matched ) ;
} else if ( seed = = " random-device " ) {
config . rngSeed = generateRandomSeed ( GenerateFrom : : RandomDevice ) ;
2021-10-06 00:00:46 +02:00
return ParserResult : : ok ( ParseResultType : : Matched ) ;
}
CATCH_TRY {
std : : size_t parsedTo = 0 ;
unsigned long parsedSeed = std : : stoul ( seed , & parsedTo , 0 ) ;
if ( parsedTo ! = seed . size ( ) ) {
return ParserResult : : runtimeError ( " Could not parse ' " + seed + " ' as seed " ) ;
}
// TODO: Ideally we could parse unsigned int directly,
// but the stdlib doesn't provide helper for that
// type. After this is refactored to use fixed size
// type, we should check the parsed value is in range
// of the underlying type.
config . rngSeed = static_cast < unsigned int > ( parsedSeed ) ;
return ParserResult : : ok ( ParseResultType : : Matched ) ;
} CATCH_CATCH_ANON ( std : : exception const & ) {
return ParserResult : : runtimeError ( " Could not parse ' " + seed + " ' as seed " ) ;
}
2017-07-10 14:25:38 +02:00
} ;
2022-03-27 23:35:41 +02:00
auto const setColourMode = [ & ] ( std : : string const &
colourMode ) {
2022-04-04 15:38:25 +02:00
Optional < ColourMode > maybeMode = Catch : : Detail : : stringToColourMode ( toLower ( colourMode ) ) ;
if ( ! maybeMode ) {
2022-03-27 23:35:41 +02:00
return ParserResult : : runtimeError (
" colour mode must be one of: default, ansi, win32, "
" or none. ' " +
2022-04-04 15:38:25 +02:00
colourMode + " ' is not recognised " ) ;
}
auto mode = * maybeMode ;
if ( ! isColourImplAvailable ( mode ) ) {
return ParserResult : : runtimeError (
" colour mode ' " + colourMode +
" ' is not supported in this binary " ) ;
2022-03-27 23:35:41 +02:00
}
2022-04-04 15:38:25 +02:00
config . colourMode = mode ;
2022-03-27 23:35:41 +02:00
return ParserResult : : ok ( ParseResultType : : Matched ) ;
} ;
2017-08-11 19:55:55 +01:00
auto const setWaitForKeypress = [ & ] ( std : : string const & keypress ) {
auto keypressLc = toLower ( keypress ) ;
2020-02-15 20:42:57 +01:00
if ( keypressLc = = " never " )
config . waitForKeypress = WaitForKeypress : : Never ;
else if ( keypressLc = = " start " )
2017-08-11 19:55:55 +01:00
config . waitForKeypress = WaitForKeypress : : BeforeStart ;
else if ( keypressLc = = " exit " )
config . waitForKeypress = WaitForKeypress : : BeforeExit ;
else if ( keypressLc = = " both " )
config . waitForKeypress = WaitForKeypress : : BeforeStartAndExit ;
else
2020-02-15 20:42:57 +01:00
return ParserResult : : runtimeError ( " keypress argument must be one of: never, start, exit or both. ' " + keypress + " ' not recognised " ) ;
2017-08-11 19:55:55 +01:00
return ParserResult : : ok ( ParseResultType : : Matched ) ;
} ;
2017-07-10 14:25:38 +02:00
auto const setVerbosity = [ & ] ( std : : string const & verbosity ) {
auto lcVerbosity = toLower ( verbosity ) ;
if ( lcVerbosity = = " quiet " )
config . verbosity = Verbosity : : Quiet ;
else if ( lcVerbosity = = " normal " )
config . verbosity = Verbosity : : Normal ;
else if ( lcVerbosity = = " high " )
config . verbosity = Verbosity : : High ;
else
2021-10-03 10:09:10 +02:00
return ParserResult : : runtimeError ( " Unrecognised verbosity, ' " + verbosity + ' \' ' ) ;
2017-07-10 14:25:38 +02:00
return ParserResult : : ok ( ParseResultType : : Matched ) ;
} ;
2021-02-06 20:12:07 +01:00
auto const setReporter = [ & ] ( std : : string const & reporterSpec ) {
2021-12-30 23:50:40 +01:00
if ( reporterSpec . empty ( ) ) {
return ParserResult : : runtimeError ( " Received empty reporter spec. " ) ;
}
2021-02-06 20:12:07 +01:00
// Exactly one of the reporters may be specified without an output
// file, in which case it defaults to the output specified by "-o"
// (or standard output).
static constexpr auto separator = " :: " ;
static constexpr size_t separatorSize = 2 ;
2021-12-30 23:50:40 +01:00
auto fileNameSeparatorPos = reporterSpec . find ( separator ) ;
const bool containsFileName = fileNameSeparatorPos ! = reporterSpec . npos ;
if ( containsFileName ) {
auto nextSeparatorPos = reporterSpec . find (
separator , fileNameSeparatorPos + separatorSize ) ;
if ( nextSeparatorPos ! = reporterSpec . npos ) {
return ParserResult : : runtimeError (
" Too many separators in reporter spec ' " + reporterSpec + ' \' ' ) ;
}
}
2021-02-06 20:12:07 +01:00
std : : string reporterName ;
Optional < std : : string > outputFileName ;
2021-12-30 23:50:40 +01:00
reporterName = reporterSpec . substr ( 0 , fileNameSeparatorPos ) ;
if ( reporterName . empty ( ) ) {
return ParserResult : : runtimeError ( " Reporter name cannot be empty. " ) ;
}
if ( containsFileName ) {
2021-02-06 20:12:07 +01:00
outputFileName = reporterSpec . substr (
2021-12-30 23:50:40 +01:00
fileNameSeparatorPos + separatorSize , reporterSpec . size ( ) ) ;
2021-02-06 20:12:07 +01:00
}
2022-02-17 20:44:27 +01:00
IReporterRegistry : : FactoryMap const & factories =
getRegistryHub ( ) . getReporterRegistry ( ) . getFactories ( ) ;
2021-02-06 20:12:07 +01:00
auto result = factories . find ( reporterName ) ;
if ( result = = factories . end ( ) )
return ParserResult : : runtimeError ( " Unrecognized reporter, ' " + reporterName + " '. Check available with --list-reporters " ) ;
if ( containsFileName & & outputFileName - > empty ( ) )
return ParserResult : : runtimeError ( " Reporter ' " + reporterName + " ' has empty filename specified as its output. Supply a filename or remove the colons to use the default output. " ) ;
config . reporterSpecifications . push_back ( { std : : move ( reporterName ) , std : : move ( outputFileName ) } ) ;
// It would be enough to check this only once at the very end, but there is
// not a place where we could call this check, so do it every time it could fail.
// For valid inputs, this is still called at most once.
if ( ! containsFileName ) {
int n_reporters_without_file = 0 ;
for ( auto const & spec : config . reporterSpecifications ) {
if ( spec . outputFileName . none ( ) ) {
n_reporters_without_file + + ;
}
}
if ( n_reporters_without_file > 1 ) {
return ParserResult : : runtimeError ( " Only one reporter may have unspecified output file. " ) ;
}
}
2018-10-22 15:59:01 +02:00
return ParserResult : : ok ( ParseResultType : : Matched ) ;
} ;
2021-07-11 12:46:05 -07:00
auto const setShardCount = [ & ] ( std : : string const & shardCount ) {
2021-10-26 23:26:07 +02:00
CATCH_TRY {
std : : size_t parsedTo = 0 ;
int64_t parsedCount = std : : stoll ( shardCount , & parsedTo , 0 ) ;
if ( parsedTo ! = shardCount . size ( ) ) {
return ParserResult : : runtimeError ( " Could not parse ' " + shardCount + " ' as shard count " ) ;
}
if ( parsedCount < = 0 ) {
return ParserResult : : runtimeError ( " Shard count must be a positive number " ) ;
}
2021-07-11 12:46:05 -07:00
2021-10-26 23:26:07 +02:00
config . shardCount = static_cast < unsigned int > ( parsedCount ) ;
return ParserResult : : ok ( ParseResultType : : Matched ) ;
} CATCH_CATCH_ANON ( std : : exception const & ) {
return ParserResult : : runtimeError ( " Could not parse ' " + shardCount + " ' as shard count " ) ;
2021-07-11 12:46:05 -07:00
}
} ;
2017-07-10 14:25:38 +02:00
2021-10-26 23:26:07 +02:00
auto const setShardIndex = [ & ] ( std : : string const & shardIndex ) {
CATCH_TRY {
std : : size_t parsedTo = 0 ;
int64_t parsedIndex = std : : stoll ( shardIndex , & parsedTo , 0 ) ;
if ( parsedTo ! = shardIndex . size ( ) ) {
return ParserResult : : runtimeError ( " Could not parse ' " + shardIndex + " ' as shard index " ) ;
}
if ( parsedIndex < 0 ) {
return ParserResult : : runtimeError ( " Shard index must be a non-negative number " ) ;
}
config . shardIndex = static_cast < unsigned int > ( parsedIndex ) ;
return ParserResult : : ok ( ParseResultType : : Matched ) ;
} CATCH_CATCH_ANON ( std : : exception const & ) {
return ParserResult : : runtimeError ( " Could not parse ' " + shardIndex + " ' as shard index " ) ;
}
} ;
2017-07-10 14:25:38 +02:00
auto cli
= ExeName ( config . processName )
2017-09-25 15:12:00 -07:00
| Help ( config . showHelp )
| Opt ( config . showSuccessfulTests )
2017-07-10 14:25:38 +02:00
[ " -s " ] [ " --success " ]
( " include successful tests in output " )
2017-09-25 15:12:00 -07:00
| Opt ( config . shouldDebugBreak )
2017-07-10 14:25:38 +02:00
[ " -b " ] [ " --break " ]
( " break into debugger on failure " )
2017-09-25 15:12:00 -07:00
| Opt ( config . noThrow )
2017-07-10 14:25:38 +02:00
[ " -e " ] [ " --nothrow " ]
( " skip exception tests " )
2017-09-25 15:12:00 -07:00
| Opt ( config . showInvisibles )
2017-07-10 14:25:38 +02:00
[ " -i " ] [ " --invisibles " ]
( " show invisibles (tabs, newlines) " )
2021-02-06 20:12:07 +01:00
| Opt ( config . defaultOutputFilename , " filename " )
2017-07-10 14:25:38 +02:00
[ " -o " ] [ " --out " ]
2021-02-06 20:12:07 +01:00
( " default output filename " )
| Opt ( accept_many , setReporter , " name[:output-file] " )
2017-07-10 14:25:38 +02:00
[ " -r " ] [ " --reporter " ]
( " reporter to use (defaults to console) " )
2017-09-25 15:12:00 -07:00
| Opt ( config . name , " name " )
2017-07-10 14:25:38 +02:00
[ " -n " ] [ " --name " ]
( " suite name " )
2017-09-25 15:12:00 -07:00
| Opt ( [ & ] ( bool ) { config . abortAfter = 1 ; } )
2017-07-10 14:25:38 +02:00
[ " -a " ] [ " --abort " ]
( " abort at first failure " )
2017-09-25 15:12:00 -07:00
| Opt ( [ & ] ( int x ) { config . abortAfter = x ; } , " no. failures " )
2017-07-10 14:25:38 +02:00
[ " -x " ] [ " --abortx " ]
( " abort after x failures " )
2021-12-18 20:48:43 +01:00
| Opt ( accept_many , setWarning , " warning name " )
2017-07-10 14:25:38 +02:00
[ " -w " ] [ " --warn " ]
( " enable warnings " )
2017-09-25 15:12:00 -07:00
| Opt ( [ & ] ( bool flag ) { config . showDurations = flag ? ShowDurations : : Always : ShowDurations : : Never ; } , " yes|no " )
2017-07-10 14:25:38 +02:00
[ " -d " ] [ " --durations " ]
( " show test durations " )
2020-04-13 08:34:27 -04:00
| Opt ( config . minDuration , " seconds " )
[ " -D " ] [ " --min-duration " ]
( " show test durations for tests taking at least the given number of seconds " )
2017-09-25 15:12:00 -07:00
| Opt ( loadTestNamesFromFile , " filename " )
2017-07-10 14:25:38 +02:00
[ " -f " ] [ " --input-file " ]
( " load test names to run from a file " )
2017-09-25 15:12:00 -07:00
| Opt ( config . filenamesAsTags )
2017-07-10 14:25:38 +02:00
[ " -# " ] [ " --filenames-as-tags " ]
( " adds a tag for the filename " )
2017-09-25 15:12:00 -07:00
| Opt ( config . sectionsToRun , " section name " )
2017-07-10 14:25:38 +02:00
[ " -c " ] [ " --section " ]
( " specify section to run " )
2017-09-25 15:12:00 -07:00
| Opt ( setVerbosity , " quiet|normal|high " )
2017-07-10 14:25:38 +02:00
[ " -v " ] [ " --verbosity " ]
( " set output verbosity " )
2022-01-02 21:19:22 +01:00
| Opt ( config . listTests )
[ " --list-tests " ]
( " list all/matching test cases " )
| Opt ( config . listTags )
[ " --list-tags " ]
( " list all/matching tags " )
2017-09-25 15:12:00 -07:00
| Opt ( config . listReporters )
2017-07-10 14:25:38 +02:00
[ " --list-reporters " ]
( " list all reporters " )
2017-09-25 15:12:00 -07:00
| Opt ( setTestOrder , " decl|lex|rand " )
2017-07-10 14:25:38 +02:00
[ " --order " ]
( " test case order (defaults to decl) " )
2021-10-08 21:32:03 +02:00
| Opt ( setRngSeed , " 'time'|'random-device'|number " )
2017-07-10 14:25:38 +02:00
[ " --rng-seed " ]
( " set a specific seed for random numbers " )
2022-03-27 23:35:41 +02:00
| Opt ( setColourMode , " ansi|win32|none|default " )
[ " --colour-mode " ]
2017-07-10 14:25:38 +02:00
( " should output be colourised " )
2017-09-25 15:12:00 -07:00
| Opt ( config . libIdentify )
2017-08-11 19:55:55 +01:00
[ " --libidentify " ]
( " report name and version according to libidentify standard " )
2020-02-15 20:42:57 +01:00
| Opt ( setWaitForKeypress , " never|start|exit|both " )
2017-08-11 19:55:55 +01:00
[ " --wait-for-keypress " ]
( " waits for a keypress before exiting " )
2019-04-23 23:41:13 +02:00
| Opt ( config . benchmarkSamples , " samples " )
[ " --benchmark-samples " ]
( " number of samples to collect (default: 100) " )
| Opt ( config . benchmarkResamples , " resamples " )
[ " --benchmark-resamples " ]
( " number of resamples for the bootstrap (default: 100000) " )
| Opt ( config . benchmarkConfidenceInterval , " confidence interval " )
[ " --benchmark-confidence-interval " ]
( " confidence interval for the bootstrap (between 0 and 1, default: 0.95) " )
| Opt ( config . benchmarkNoAnalysis )
[ " --benchmark-no-analysis " ]
( " perform only measurements; do not perform any analysis " )
2020-01-27 15:43:27 +01:00
| Opt ( config . benchmarkWarmupTime , " benchmarkWarmupTime " )
[ " --benchmark-warmup-time " ]
( " amount of time in milliseconds spent on warming up each test (default: 100) " )
2021-07-11 12:46:05 -07:00
| Opt ( setShardCount , " shard count " )
[ " --shard-count " ]
( " split the tests to execute into this many groups " )
2021-10-26 23:26:07 +02:00
| Opt ( setShardIndex , " shard index " )
2021-07-11 12:46:05 -07:00
[ " --shard-index " ]
2021-12-13 15:15:23 +01:00
( " index of the group of tests to execute (see --shard-count) " ) |
Opt ( config . allowZeroTests )
[ " --allow-running-no-tests " ]
( " Treat 'No tests run' as a success " )
2020-01-21 21:04:42 +01:00
| Arg ( config . testsOrTags , " test name|pattern|tags " )
2017-07-10 14:25:38 +02:00
( " which test or tests to use " ) ;
return cli ;
}
} // end namespace Catch