// Copyright Catch2 Authors // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE.txt or copy at // https://www.boost.org/LICENSE_1_0.txt) // SPDX-License-Identifier: BSL-1.0 #include #include #include #include #include #include #include #include #include #include namespace Catch { Clara::Parser makeCommandLineParser( ConfigData& config ) { using namespace Clara; auto const setWarning = [&]( std::string const& warning ) { if ( warning == "NoAssertions" ) { config.warnings = static_cast(config.warnings | WarnAbout::NoAssertions); return ParserResult::ok( ParseResultType::Matched ); } else if ( warning == "UnmatchedTestSpec" ) { config.warnings = static_cast(config.warnings | WarnAbout::UnmatchedTestSpec); return ParserResult::ok( ParseResultType::Matched ); } return ParserResult ::runtimeError( "Unrecognised warning option: '" + warning + '\'' ); }; auto const loadTestNamesFromFile = [&]( std::string const& filename ) { std::ifstream f( filename.c_str() ); if( !f.is_open() ) return 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 ); config.testsOrTags.emplace_back( "," ); } } //Remove comma in the end if(!config.testsOrTags.empty()) config.testsOrTags.erase( config.testsOrTags.end()-1 ); return ParserResult::ok( ParseResultType::Matched ); }; auto const setTestOrder = [&]( std::string const& order ) { if( startsWith( "declared", order ) ) config.runOrder = TestRunOrder::Declared; else if( startsWith( "lexical", order ) ) config.runOrder = TestRunOrder::LexicographicallySorted; else if( startsWith( "random", order ) ) config.runOrder = TestRunOrder::Randomized; else return ParserResult::runtimeError( "Unrecognised ordering: '" + order + '\'' ); return ParserResult::ok( ParseResultType::Matched ); }; auto const setRngSeed = [&]( std::string const& seed ) { if( seed == "time" ) { config.rngSeed = generateRandomSeed(GenerateFrom::Time); return ParserResult::ok(ParseResultType::Matched); } else if (seed == "random-device") { config.rngSeed = generateRandomSeed(GenerateFrom::RandomDevice); return ParserResult::ok(ParseResultType::Matched); } // TODO: ideally we should be parsing uint32_t directly // fix this later when we add new parse overload auto parsedSeed = parseUInt( seed, 0 ); if ( !parsedSeed ) { return ParserResult::runtimeError( "Could not parse '" + seed + "' as seed" ); } config.rngSeed = *parsedSeed; return ParserResult::ok( ParseResultType::Matched ); }; auto const setDefaultColourMode = [&]( std::string const& colourMode ) { Optional maybeMode = Catch::Detail::stringToColourMode(toLower( colourMode )); if ( !maybeMode ) { return ParserResult::runtimeError( "colour mode must be one of: default, ansi, win32, " "or none. '" + colourMode + "' is not recognised" ); } auto mode = *maybeMode; if ( !isColourImplAvailable( mode ) ) { return ParserResult::runtimeError( "colour mode '" + colourMode + "' is not supported in this binary" ); } config.defaultColourMode = mode; return ParserResult::ok( ParseResultType::Matched ); }; auto const setWaitForKeypress = [&]( std::string const& keypress ) { auto keypressLc = toLower( keypress ); if (keypressLc == "never") config.waitForKeypress = WaitForKeypress::Never; else if( keypressLc == "start" ) config.waitForKeypress = WaitForKeypress::BeforeStart; else if( keypressLc == "exit" ) config.waitForKeypress = WaitForKeypress::BeforeExit; else if( keypressLc == "both" ) config.waitForKeypress = WaitForKeypress::BeforeStartAndExit; else return ParserResult::runtimeError( "keypress argument must be one of: never, start, exit or both. '" + keypress + "' not recognised" ); return ParserResult::ok( ParseResultType::Matched ); }; 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 return ParserResult::runtimeError( "Unrecognised verbosity, '" + verbosity + '\'' ); return ParserResult::ok( ParseResultType::Matched ); }; auto const setReporter = [&]( std::string const& userReporterSpec ) { if ( userReporterSpec.empty() ) { return ParserResult::runtimeError( "Received empty reporter spec." ); } Optional parsed = parseReporterSpec( userReporterSpec ); if ( !parsed ) { return ParserResult::runtimeError( "Could not parse reporter spec '" + userReporterSpec + "'" ); } auto const& reporterSpec = *parsed; auto const& factories = getRegistryHub().getReporterRegistry().getFactories(); auto result = factories.find( reporterSpec.name() ); if ( result == factories.end() ) { return ParserResult::runtimeError( "Unrecognized reporter, '" + reporterSpec.name() + "'. Check available with --list-reporters" ); } const bool hadOutputFile = reporterSpec.outputFile().some(); config.reporterSpecifications.push_back( CATCH_MOVE( *parsed ) ); // 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 (!hadOutputFile) { int n_reporters_without_file = 0; for (auto const& spec : config.reporterSpecifications) { if (spec.outputFile().none()) { n_reporters_without_file++; } } if (n_reporters_without_file > 1) { return ParserResult::runtimeError( "Only one reporter may have unspecified output file." ); } } return ParserResult::ok( ParseResultType::Matched ); }; auto const setShardCount = [&]( std::string const& shardCount ) { auto parsedCount = parseUInt( shardCount ); if ( !parsedCount ) { return ParserResult::runtimeError( "Could not parse '" + shardCount + "' as shard count" ); } if ( *parsedCount == 0 ) { return ParserResult::runtimeError( "Shard count must be positive" ); } config.shardCount = *parsedCount; return ParserResult::ok( ParseResultType::Matched ); }; auto const setShardIndex = [&](std::string const& shardIndex) { auto parsedIndex = parseUInt( shardIndex ); if ( !parsedIndex ) { return ParserResult::runtimeError( "Could not parse '" + shardIndex + "' as shard index" ); } config.shardIndex = *parsedIndex; return ParserResult::ok( ParseResultType::Matched ); }; auto cli = ExeName( config.processName ) | Help( config.showHelp ) | 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.defaultOutputFilename, "filename" ) ["-o"]["--out"] ( "default output filename" ) | Opt( accept_many, setReporter, "name[::key=value]*" ) ["-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( accept_many, setWarning, "warning name" ) ["-w"]["--warn"] ( "enable warnings" ) | Opt( [&]( bool flag ) { config.showDurations = flag ? ShowDurations::Always : ShowDurations::Never; }, "yes|no" ) ["-d"]["--durations"] ( "show test durations" ) | Opt( config.minDuration, "seconds" ) ["-D"]["--min-duration"] ( "show test durations for tests taking at least the given number of seconds" ) | 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( setVerbosity, "quiet|normal|high" ) ["-v"]["--verbosity"] ( "set output verbosity" ) | Opt( config.listTests ) ["--list-tests"] ( "list all/matching test cases" ) | Opt( config.listTags ) ["--list-tags"] ( "list all/matching tags" ) | Opt( config.listReporters ) ["--list-reporters"] ( "list all available reporters" ) | Opt( config.listListeners ) ["--list-listeners"] ( "list all listeners" ) | Opt( setTestOrder, "decl|lex|rand" ) ["--order"] ( "test case order (defaults to decl)" ) | Opt( setRngSeed, "'time'|'random-device'|number" ) ["--rng-seed"] ( "set a specific seed for random numbers" ) | Opt( setDefaultColourMode, "ansi|win32|none|default" ) ["--colour-mode"] ( "what color mode should be used as default" ) | Opt( config.libIdentify ) ["--libidentify"] ( "report name and version according to libidentify standard" ) | Opt( setWaitForKeypress, "never|start|exit|both" ) ["--wait-for-keypress"] ( "waits for a keypress before exiting" ) | Opt( config.skipBenchmarks) ["--skip-benchmarks"] ( "disable running benchmarks") | 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" ) | Opt( config.benchmarkWarmupTime, "benchmarkWarmupTime" ) ["--benchmark-warmup-time"] ( "amount of time in milliseconds spent on warming up each test (default: 100)" ) | Opt( setShardCount, "shard count" ) ["--shard-count"] ( "split the tests to execute into this many groups" ) | Opt( setShardIndex, "shard index" ) ["--shard-index"] ( "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" ) | Arg( config.testsOrTags, "test name|pattern|tags" ) ( "which test or tests to use" ); return cli; } } // end namespace Catch