aboutsummaryrefslogtreecommitdiffstats
path: root/src/catch2/internal/catch_commandline.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/catch2/internal/catch_commandline.cpp')
-rw-r--r--src/catch2/internal/catch_commandline.cpp314
1 files changed, 314 insertions, 0 deletions
diff --git a/src/catch2/internal/catch_commandline.cpp b/src/catch2/internal/catch_commandline.cpp
new file mode 100644
index 0000000..212f177
--- /dev/null
+++ b/src/catch2/internal/catch_commandline.cpp
@@ -0,0 +1,314 @@
+
+// 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 <catch2/internal/catch_commandline.hpp>
+
+#include <catch2/catch_config.hpp>
+#include <catch2/internal/catch_string_manip.hpp>
+#include <catch2/interfaces/catch_interfaces_config.hpp>
+#include <catch2/interfaces/catch_interfaces_registry_hub.hpp>
+#include <catch2/internal/catch_reporter_registry.hpp>
+#include <catch2/internal/catch_console_colour.hpp>
+#include <catch2/internal/catch_parse_numbers.hpp>
+#include <catch2/internal/catch_reporter_spec_parser.hpp>
+
+#include <fstream>
+#include <string>
+
+namespace Catch {
+
+ Clara::Parser makeCommandLineParser( ConfigData& config ) {
+
+ using namespace Clara;
+
+ auto const setWarning = [&]( std::string const& warning ) {
+ if ( warning == "NoAssertions" ) {
+ config.warnings = static_cast<WarnAbout::What>(config.warnings | WarnAbout::NoAssertions);
+ return ParserResult::ok( ParseResultType::Matched );
+ } else if ( warning == "UnmatchedTestSpec" ) {
+ config.warnings = static_cast<WarnAbout::What>(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 = '"' + CATCH_MOVE(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<ColourMode> 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<ReporterSpec> 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