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>
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
};
auto const setColourUsage = [ & ]( std :: string const & useColour ) {
auto mode = toLower ( useColour );
if ( mode == "yes" )
config . useColour = UseColour :: Yes ;
else if ( mode == "no" )
config . useColour = UseColour :: No ;
else if ( mode == "auto" )
config . useColour = UseColour :: Auto ;
else
return ParserResult :: runtimeError ( "colour mode must be one of: auto, yes or no. '" + useColour + "' not recognised" );
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." );
}
2018-10-22 15:59:01 +02:00
IReporterRegistry :: FactoryMap const & factories = getRegistryHub (). getReporterRegistry (). getFactories ();
2021-02-06 20:12:07 +01:00
// clear the default reporter
if ( ! config . _nonDefaultReporterSpecifications ) {
config . reporterSpecifications . clear ();
config . _nonDefaultReporterSpecifications = true ;
}
2021-12-30 23:50:40 +01:00
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
}
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 . listTests )
2017-07-10 14:25:38 +02:00
[ "-l" ][ "--list-tests" ]
( "list all/matching test cases" )
2017-09-25 15:12:00 -07:00
| Opt ( config . listTags )
2017-07-10 14:25:38 +02:00
[ "-t" ][ "--list-tags" ]
( "list all/matching tags" )
2017-09-25 15:12:00 -07:00
| 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" )
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" )
2017-09-25 15:12:00 -07:00
| Opt ( setColourUsage , "yes|no" )
2017-07-10 14:25:38 +02:00
[ "--use-colour" ]
( "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