1 /*
2  *  Created by Phil on 02/11/2010.
3  *  Copyright 2010 Two Blue Cubes Ltd. All rights reserved.
4  *
5  *  Distributed under the Boost Software License, Version 1.0. (See accompanying
6  *  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7  */
8 #ifndef TWOBLUECUBES_CATCH_COMMANDLINE_HPP_INCLUDED
9 #define TWOBLUECUBES_CATCH_COMMANDLINE_HPP_INCLUDED
10 
11 #include "catch_config.hpp"
12 #include "catch_common.h"
13 #include "catch_clara.h"
14 
15 #include <fstream>
16 #include <ctime>
17 
18 namespace Catch {
19 
abortAfterFirst(ConfigData & config)20     inline void abortAfterFirst( ConfigData& config ) { config.abortAfter = 1; }
abortAfterX(ConfigData & config,int x)21     inline void abortAfterX( ConfigData& config, int x ) {
22         if( x < 1 )
23             throw std::runtime_error( "Value after -x or --abortAfter must be greater than zero" );
24         config.abortAfter = x;
25     }
addTestOrTags(ConfigData & config,std::string const & _testSpec)26     inline void addTestOrTags( ConfigData& config, std::string const& _testSpec ) { config.testsOrTags.push_back( _testSpec ); }
addSectionToRun(ConfigData & config,std::string const & sectionName)27     inline void addSectionToRun( ConfigData& config, std::string const& sectionName ) { config.sectionsToRun.push_back( sectionName ); }
addReporterName(ConfigData & config,std::string const & _reporterName)28     inline void addReporterName( ConfigData& config, std::string const& _reporterName ) { config.reporterNames.push_back( _reporterName ); }
29 
addWarning(ConfigData & config,std::string const & _warning)30     inline void addWarning( ConfigData& config, std::string const& _warning ) {
31         if( _warning == "NoAssertions" )
32             config.warnings = static_cast<WarnAbout::What>( config.warnings | WarnAbout::NoAssertions );
33         else
34             throw std::runtime_error( "Unrecognised warning: '" + _warning + '\'' );
35     }
setOrder(ConfigData & config,std::string const & order)36     inline void setOrder( ConfigData& config, std::string const& order ) {
37         if( startsWith( "declared", order ) )
38             config.runOrder = RunTests::InDeclarationOrder;
39         else if( startsWith( "lexical", order ) )
40             config.runOrder = RunTests::InLexicographicalOrder;
41         else if( startsWith( "random", order ) )
42             config.runOrder = RunTests::InRandomOrder;
43         else
44             throw std::runtime_error( "Unrecognised ordering: '" + order + '\'' );
45     }
setRngSeed(ConfigData & config,std::string const & seed)46     inline void setRngSeed( ConfigData& config, std::string const& seed ) {
47         if( seed == "time" ) {
48             config.rngSeed = static_cast<unsigned int>( std::time(0) );
49         }
50         else {
51             std::stringstream ss;
52             ss << seed;
53             ss >> config.rngSeed;
54             if( ss.fail() )
55                 throw std::runtime_error( "Argument to --rng-seed should be the word 'time' or a number" );
56         }
57     }
setVerbosity(ConfigData & config,int level)58     inline void setVerbosity( ConfigData& config, int level ) {
59         // !TBD: accept strings?
60         config.verbosity = static_cast<Verbosity::Level>( level );
61     }
setShowDurations(ConfigData & config,bool _showDurations)62     inline void setShowDurations( ConfigData& config, bool _showDurations ) {
63         config.showDurations = _showDurations
64             ? ShowDurations::Always
65             : ShowDurations::Never;
66     }
setUseColour(ConfigData & config,std::string const & value)67     inline void setUseColour( ConfigData& config, std::string const& value ) {
68         std::string mode = toLower( value );
69 
70         if( mode == "yes" )
71             config.useColour = UseColour::Yes;
72         else if( mode == "no" )
73             config.useColour = UseColour::No;
74         else if( mode == "auto" )
75             config.useColour = UseColour::Auto;
76         else
77             throw std::runtime_error( "colour mode must be one of: auto, yes or no" );
78     }
setWaitForKeypress(ConfigData & config,std::string const & keypress)79     inline void setWaitForKeypress( ConfigData& config, std::string const& keypress ) {
80         std::string keypressLc = toLower( keypress );
81         if( keypressLc == "start" )
82             config.waitForKeypress = WaitForKeypress::BeforeStart;
83         else if( keypressLc == "exit" )
84             config.waitForKeypress = WaitForKeypress::BeforeExit;
85         else if( keypressLc == "both" )
86             config.waitForKeypress = WaitForKeypress::BeforeStartAndExit;
87         else
88             throw std::runtime_error( "keypress argument must be one of: start, exit or both. '" + keypress + "' not recognised" );
89     };
90 
91 
forceColour(ConfigData & config)92     inline void forceColour( ConfigData& config ) {
93         config.useColour = UseColour::Yes;
94     }
loadTestNamesFromFile(ConfigData & config,std::string const & _filename)95     inline void loadTestNamesFromFile( ConfigData& config, std::string const& _filename ) {
96         std::ifstream f( _filename.c_str() );
97         if( !f.is_open() )
98             throw std::domain_error( "Unable to load input file: " + _filename );
99 
100         std::string line;
101         while( std::getline( f, line ) ) {
102             line = trim(line);
103             if( !line.empty() && !startsWith( line, '#' ) ) {
104                 if( !startsWith( line, '"' ) )
105                     line = '"' + line + '"';
106                 addTestOrTags( config, line + ',' );
107             }
108         }
109     }
110 
makeCommandLineParser()111     inline Clara::CommandLine<ConfigData> makeCommandLineParser() {
112 
113         using namespace Clara;
114         CommandLine<ConfigData> cli;
115 
116         cli.bindProcessName( &ConfigData::processName );
117 
118         cli["-?"]["-h"]["--help"]
119             .describe( "display usage information" )
120             .bind( &ConfigData::showHelp );
121 
122         cli["-l"]["--list-tests"]
123             .describe( "list all/matching test cases" )
124             .bind( &ConfigData::listTests );
125 
126         cli["-t"]["--list-tags"]
127             .describe( "list all/matching tags" )
128             .bind( &ConfigData::listTags );
129 
130         cli["-s"]["--success"]
131             .describe( "include successful tests in output" )
132             .bind( &ConfigData::showSuccessfulTests );
133 
134         cli["-b"]["--break"]
135             .describe( "break into debugger on failure" )
136             .bind( &ConfigData::shouldDebugBreak );
137 
138         cli["-e"]["--nothrow"]
139             .describe( "skip exception tests" )
140             .bind( &ConfigData::noThrow );
141 
142         cli["-i"]["--invisibles"]
143             .describe( "show invisibles (tabs, newlines)" )
144             .bind( &ConfigData::showInvisibles );
145 
146         cli["-o"]["--out"]
147             .describe( "output filename" )
148             .bind( &ConfigData::outputFilename, "filename" );
149 
150         cli["-r"]["--reporter"]
151 //            .placeholder( "name[:filename]" )
152             .describe( "reporter to use (defaults to console)" )
153             .bind( &addReporterName, "name" );
154 
155         cli["-n"]["--name"]
156             .describe( "suite name" )
157             .bind( &ConfigData::name, "name" );
158 
159         cli["-a"]["--abort"]
160             .describe( "abort at first failure" )
161             .bind( &abortAfterFirst );
162 
163         cli["-x"]["--abortx"]
164             .describe( "abort after x failures" )
165             .bind( &abortAfterX, "no. failures" );
166 
167         cli["-w"]["--warn"]
168             .describe( "enable warnings" )
169             .bind( &addWarning, "warning name" );
170 
171 // - needs updating if reinstated
172 //        cli.into( &setVerbosity )
173 //            .describe( "level of verbosity (0=no output)" )
174 //            .shortOpt( "v")
175 //            .longOpt( "verbosity" )
176 //            .placeholder( "level" );
177 
178         cli[_]
179             .describe( "which test or tests to use" )
180             .bind( &addTestOrTags, "test name, pattern or tags" );
181 
182         cli["-d"]["--durations"]
183             .describe( "show test durations" )
184             .bind( &setShowDurations, "yes|no" );
185 
186         cli["-f"]["--input-file"]
187             .describe( "load test names to run from a file" )
188             .bind( &loadTestNamesFromFile, "filename" );
189 
190         cli["-#"]["--filenames-as-tags"]
191             .describe( "adds a tag for the filename" )
192             .bind( &ConfigData::filenamesAsTags );
193 
194         cli["-c"]["--section"]
195                 .describe( "specify section to run" )
196                 .bind( &addSectionToRun, "section name" );
197 
198         // Less common commands which don't have a short form
199         cli["--list-test-names-only"]
200             .describe( "list all/matching test cases names only" )
201             .bind( &ConfigData::listTestNamesOnly );
202 
203         cli["--list-extra-info"]
204             .describe( "list all/matching test cases with more info" )
205             .bind( &ConfigData::listExtraInfo );
206 
207         cli["--list-reporters"]
208             .describe( "list all reporters" )
209             .bind( &ConfigData::listReporters );
210 
211         cli["--order"]
212             .describe( "test case order (defaults to decl)" )
213             .bind( &setOrder, "decl|lex|rand" );
214 
215         cli["--rng-seed"]
216             .describe( "set a specific seed for random numbers" )
217             .bind( &setRngSeed, "'time'|number" );
218 
219         cli["--force-colour"]
220             .describe( "force colourised output (deprecated)" )
221             .bind( &forceColour );
222 
223         cli["--use-colour"]
224             .describe( "should output be colourised" )
225             .bind( &setUseColour, "yes|no" );
226 
227         cli["--libidentify"]
228             .describe( "report name and version according to libidentify standard" )
229             .bind( &ConfigData::libIdentify );
230 
231         cli["--wait-for-keypress"]
232                 .describe( "waits for a keypress before exiting" )
233                 .bind( &setWaitForKeypress, "start|exit|both" );
234 
235         return cli;
236     }
237 
238 } // end namespace Catch
239 
240 #endif // TWOBLUECUBES_CATCH_COMMANDLINE_HPP_INCLUDED
241