catch_commandline.cpp
1 2 // Copyright Catch2 Authors 3 // Distributed under the Boost Software License, Version 1.0. 4 // (See accompanying file LICENSE.txt or copy at 5 // https://www.boost.org/LICENSE_1_0.txt) 6 7 // SPDX-License-Identifier: BSL-1.0 8 #include <catch2/internal/catch_commandline.hpp> 9 10 #include <catch2/catch_config.hpp> 11 #include <catch2/internal/catch_string_manip.hpp> 12 #include <catch2/interfaces/catch_interfaces_registry_hub.hpp> 13 #include <catch2/internal/catch_reporter_registry.hpp> 14 #include <catch2/internal/catch_console_colour.hpp> 15 #include <catch2/internal/catch_parse_numbers.hpp> 16 #include <catch2/internal/catch_reporter_spec_parser.hpp> 17 18 #include <fstream> 19 #include <string> 20 21 namespace Catch { 22 23 Clara::Parser makeCommandLineParser( ConfigData& config ) { 24 25 using namespace Clara; 26 27 auto const setWarning = [&]( std::string const& warning ) { 28 if ( warning == "NoAssertions" ) { 29 config.warnings = static_cast<WarnAbout::What>(config.warnings | WarnAbout::NoAssertions); 30 return ParserResult::ok( ParseResultType::Matched ); 31 } else if ( warning == "UnmatchedTestSpec" ) { 32 config.warnings = static_cast<WarnAbout::What>(config.warnings | WarnAbout::UnmatchedTestSpec); 33 return ParserResult::ok( ParseResultType::Matched ); 34 } 35 36 return ParserResult ::runtimeError( 37 "Unrecognised warning option: '" + warning + '\'' ); 38 }; 39 auto const loadTestNamesFromFile = [&]( std::string const& filename ) { 40 std::ifstream f( filename.c_str() ); 41 if( !f.is_open() ) 42 return ParserResult::runtimeError( "Unable to load input file: '" + filename + '\'' ); 43 44 std::string line; 45 while( std::getline( f, line ) ) { 46 line = trim(line); 47 if( !line.empty() && !startsWith( line, '#' ) ) { 48 if( !startsWith( line, '"' ) ) 49 line = '"' + line + '"'; 50 config.testsOrTags.push_back( line ); 51 config.testsOrTags.emplace_back( "," ); 52 } 53 } 54 //Remove comma in the end 55 if(!config.testsOrTags.empty()) 56 config.testsOrTags.erase( config.testsOrTags.end()-1 ); 57 58 return ParserResult::ok( ParseResultType::Matched ); 59 }; 60 auto const setTestOrder = [&]( std::string const& order ) { 61 if( startsWith( "declared", order ) ) 62 config.runOrder = TestRunOrder::Declared; 63 else if( startsWith( "lexical", order ) ) 64 config.runOrder = TestRunOrder::LexicographicallySorted; 65 else if( startsWith( "random", order ) ) 66 config.runOrder = TestRunOrder::Randomized; 67 else 68 return ParserResult::runtimeError( "Unrecognised ordering: '" + order + '\'' ); 69 return ParserResult::ok( ParseResultType::Matched ); 70 }; 71 auto const setRngSeed = [&]( std::string const& seed ) { 72 if( seed == "time" ) { 73 config.rngSeed = generateRandomSeed(GenerateFrom::Time); 74 return ParserResult::ok(ParseResultType::Matched); 75 } else if (seed == "random-device") { 76 config.rngSeed = generateRandomSeed(GenerateFrom::RandomDevice); 77 return ParserResult::ok(ParseResultType::Matched); 78 } 79 80 // TODO: ideally we should be parsing uint32_t directly 81 // fix this later when we add new parse overload 82 auto parsedSeed = parseUInt( seed, 0 ); 83 if ( !parsedSeed ) { 84 return ParserResult::runtimeError( "Could not parse '" + seed + "' as seed" ); 85 } 86 config.rngSeed = *parsedSeed; 87 return ParserResult::ok( ParseResultType::Matched ); 88 }; 89 auto const setDefaultColourMode = [&]( std::string const& colourMode ) { 90 Optional<ColourMode> maybeMode = Catch::Detail::stringToColourMode(toLower( colourMode )); 91 if ( !maybeMode ) { 92 return ParserResult::runtimeError( 93 "colour mode must be one of: default, ansi, win32, " 94 "or none. '" + 95 colourMode + "' is not recognised" ); 96 } 97 auto mode = *maybeMode; 98 if ( !isColourImplAvailable( mode ) ) { 99 return ParserResult::runtimeError( 100 "colour mode '" + colourMode + 101 "' is not supported in this binary" ); 102 } 103 config.defaultColourMode = mode; 104 return ParserResult::ok( ParseResultType::Matched ); 105 }; 106 auto const setWaitForKeypress = [&]( std::string const& keypress ) { 107 auto keypressLc = toLower( keypress ); 108 if (keypressLc == "never") 109 config.waitForKeypress = WaitForKeypress::Never; 110 else if( keypressLc == "start" ) 111 config.waitForKeypress = WaitForKeypress::BeforeStart; 112 else if( keypressLc == "exit" ) 113 config.waitForKeypress = WaitForKeypress::BeforeExit; 114 else if( keypressLc == "both" ) 115 config.waitForKeypress = WaitForKeypress::BeforeStartAndExit; 116 else 117 return ParserResult::runtimeError( "keypress argument must be one of: never, start, exit or both. '" + keypress + "' not recognised" ); 118 return ParserResult::ok( ParseResultType::Matched ); 119 }; 120 auto const setVerbosity = [&]( std::string const& verbosity ) { 121 auto lcVerbosity = toLower( verbosity ); 122 if( lcVerbosity == "quiet" ) 123 config.verbosity = Verbosity::Quiet; 124 else if( lcVerbosity == "normal" ) 125 config.verbosity = Verbosity::Normal; 126 else if( lcVerbosity == "high" ) 127 config.verbosity = Verbosity::High; 128 else 129 return ParserResult::runtimeError( "Unrecognised verbosity, '" + verbosity + '\'' ); 130 return ParserResult::ok( ParseResultType::Matched ); 131 }; 132 auto const setReporter = [&]( std::string const& userReporterSpec ) { 133 if ( userReporterSpec.empty() ) { 134 return ParserResult::runtimeError( "Received empty reporter spec." ); 135 } 136 137 Optional<ReporterSpec> parsed = 138 parseReporterSpec( userReporterSpec ); 139 if ( !parsed ) { 140 return ParserResult::runtimeError( 141 "Could not parse reporter spec '" + userReporterSpec + 142 "'" ); 143 } 144 145 auto const& reporterSpec = *parsed; 146 147 auto const& factories = 148 getRegistryHub().getReporterRegistry().getFactories(); 149 auto result = factories.find( reporterSpec.name() ); 150 151 if ( result == factories.end() ) { 152 return ParserResult::runtimeError( 153 "Unrecognized reporter, '" + reporterSpec.name() + 154 "'. Check available with --list-reporters" ); 155 } 156 157 158 const bool hadOutputFile = reporterSpec.outputFile().some(); 159 config.reporterSpecifications.push_back( CATCH_MOVE( *parsed ) ); 160 // It would be enough to check this only once at the very end, but 161 // there is not a place where we could call this check, so do it 162 // every time it could fail. For valid inputs, this is still called 163 // at most once. 164 if (!hadOutputFile) { 165 int n_reporters_without_file = 0; 166 for (auto const& spec : config.reporterSpecifications) { 167 if (spec.outputFile().none()) { 168 n_reporters_without_file++; 169 } 170 } 171 if (n_reporters_without_file > 1) { 172 return ParserResult::runtimeError( "Only one reporter may have unspecified output file." ); 173 } 174 } 175 176 return ParserResult::ok( ParseResultType::Matched ); 177 }; 178 auto const setShardCount = [&]( std::string const& shardCount ) { 179 auto parsedCount = parseUInt( shardCount ); 180 if ( !parsedCount ) { 181 return ParserResult::runtimeError( 182 "Could not parse '" + shardCount + "' as shard count" ); 183 } 184 if ( *parsedCount == 0 ) { 185 return ParserResult::runtimeError( 186 "Shard count must be positive" ); 187 } 188 config.shardCount = *parsedCount; 189 return ParserResult::ok( ParseResultType::Matched ); 190 }; 191 192 auto const setShardIndex = [&](std::string const& shardIndex) { 193 auto parsedIndex = parseUInt( shardIndex ); 194 if ( !parsedIndex ) { 195 return ParserResult::runtimeError( 196 "Could not parse '" + shardIndex + "' as shard index" ); 197 } 198 config.shardIndex = *parsedIndex; 199 return ParserResult::ok( ParseResultType::Matched ); 200 }; 201 202 auto cli 203 = ExeName( config.processName ) 204 | Help( config.showHelp ) 205 | Opt( config.showSuccessfulTests ) 206 ["-s"]["--success"] 207 ( "include successful tests in output" ) 208 | Opt( config.shouldDebugBreak ) 209 ["-b"]["--break"] 210 ( "break into debugger on failure" ) 211 | Opt( config.noThrow ) 212 ["-e"]["--nothrow"] 213 ( "skip exception tests" ) 214 | Opt( config.showInvisibles ) 215 ["-i"]["--invisibles"] 216 ( "show invisibles (tabs, newlines)" ) 217 | Opt( config.defaultOutputFilename, "filename" ) 218 ["-o"]["--out"] 219 ( "default output filename" ) 220 | Opt( accept_many, setReporter, "name[::key=value]*" ) 221 ["-r"]["--reporter"] 222 ( "reporter to use (defaults to console)" ) 223 | Opt( config.name, "name" ) 224 ["-n"]["--name"] 225 ( "suite name" ) 226 | Opt( [&]( bool ){ config.abortAfter = 1; } ) 227 ["-a"]["--abort"] 228 ( "abort at first failure" ) 229 | Opt( [&]( int x ){ config.abortAfter = x; }, "no. failures" ) 230 ["-x"]["--abortx"] 231 ( "abort after x failures" ) 232 | Opt( accept_many, setWarning, "warning name" ) 233 ["-w"]["--warn"] 234 ( "enable warnings" ) 235 | Opt( [&]( bool flag ) { config.showDurations = flag ? ShowDurations::Always : ShowDurations::Never; }, "yes|no" ) 236 ["-d"]["--durations"] 237 ( "show test durations" ) 238 | Opt( config.minDuration, "seconds" ) 239 ["-D"]["--min-duration"] 240 ( "show test durations for tests taking at least the given number of seconds" ) 241 | Opt( loadTestNamesFromFile, "filename" ) 242 ["-f"]["--input-file"] 243 ( "load test names to run from a file" ) 244 | Opt( config.filenamesAsTags ) 245 ["-#"]["--filenames-as-tags"] 246 ( "adds a tag for the filename" ) 247 | Opt( config.sectionsToRun, "section name" ) 248 ["-c"]["--section"] 249 ( "specify section to run" ) 250 | Opt( setVerbosity, "quiet|normal|high" ) 251 ["-v"]["--verbosity"] 252 ( "set output verbosity" ) 253 | Opt( config.listTests ) 254 ["--list-tests"] 255 ( "list all/matching test cases" ) 256 | Opt( config.listTags ) 257 ["--list-tags"] 258 ( "list all/matching tags" ) 259 | Opt( config.listReporters ) 260 ["--list-reporters"] 261 ( "list all available reporters" ) 262 | Opt( config.listListeners ) 263 ["--list-listeners"] 264 ( "list all listeners" ) 265 | Opt( setTestOrder, "decl|lex|rand" ) 266 ["--order"] 267 ( "test case order (defaults to decl)" ) 268 | Opt( setRngSeed, "'time'|'random-device'|number" ) 269 ["--rng-seed"] 270 ( "set a specific seed for random numbers" ) 271 | Opt( setDefaultColourMode, "ansi|win32|none|default" ) 272 ["--colour-mode"] 273 ( "what color mode should be used as default" ) 274 | Opt( config.libIdentify ) 275 ["--libidentify"] 276 ( "report name and version according to libidentify standard" ) 277 | Opt( setWaitForKeypress, "never|start|exit|both" ) 278 ["--wait-for-keypress"] 279 ( "waits for a keypress before exiting" ) 280 | Opt( config.skipBenchmarks) 281 ["--skip-benchmarks"] 282 ( "disable running benchmarks") 283 | Opt( config.benchmarkSamples, "samples" ) 284 ["--benchmark-samples"] 285 ( "number of samples to collect (default: 100)" ) 286 | Opt( config.benchmarkResamples, "resamples" ) 287 ["--benchmark-resamples"] 288 ( "number of resamples for the bootstrap (default: 100000)" ) 289 | Opt( config.benchmarkConfidenceInterval, "confidence interval" ) 290 ["--benchmark-confidence-interval"] 291 ( "confidence interval for the bootstrap (between 0 and 1, default: 0.95)" ) 292 | Opt( config.benchmarkNoAnalysis ) 293 ["--benchmark-no-analysis"] 294 ( "perform only measurements; do not perform any analysis" ) 295 | Opt( config.benchmarkWarmupTime, "benchmarkWarmupTime" ) 296 ["--benchmark-warmup-time"] 297 ( "amount of time in milliseconds spent on warming up each test (default: 100)" ) 298 | Opt( setShardCount, "shard count" ) 299 ["--shard-count"] 300 ( "split the tests to execute into this many groups" ) 301 | Opt( setShardIndex, "shard index" ) 302 ["--shard-index"] 303 ( "index of the group of tests to execute (see --shard-count)" ) | 304 Opt( config.allowZeroTests ) 305 ["--allow-running-no-tests"] 306 ( "Treat 'No tests run' as a success" ) 307 | Arg( config.testsOrTags, "test name|pattern|tags" ) 308 ( "which test or tests to use" ); 309 310 return cli; 311 } 312 313 } // end namespace Catch