/ externals / catch / src / catch2 / internal / catch_commandline.cpp
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