1 /*
2  *  Created by Phil on 31/10/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_RUNNER_HPP_INCLUDED
9 #define TWOBLUECUBES_CATCH_RUNNER_HPP_INCLUDED
10 
11 #include "internal/catch_commandline.hpp"
12 #include "internal/catch_list.hpp"
13 #include "internal/catch_run_context.hpp"
14 #include "internal/catch_test_spec.hpp"
15 #include "internal/catch_version.h"
16 #include "internal/catch_text.h"
17 
18 #include <fstream>
19 #include <stdlib.h>
20 #include <limits>
21 
22 namespace Catch {
23 
createReporter(std::string const & reporterName,Ptr<Config> const & config)24     Ptr<IStreamingReporter> createReporter( std::string const& reporterName, Ptr<Config> const& config ) {
25         Ptr<IStreamingReporter> reporter = getRegistryHub().getReporterRegistry().create( reporterName, config.get() );
26         if( !reporter ) {
27             std::ostringstream oss;
28             oss << "No reporter registered with name: '" << reporterName << "'";
29             throw std::domain_error( oss.str() );
30         }
31         return reporter;
32     }
33 
34 #if !defined(CATCH_CONFIG_DEFAULT_REPORTER)
35 #define CATCH_CONFIG_DEFAULT_REPORTER "console"
36 #endif
37 
makeReporter(Ptr<Config> const & config)38     Ptr<IStreamingReporter> makeReporter( Ptr<Config> const& config ) {
39         std::vector<std::string> reporters = config->getReporterNames();
40         if( reporters.empty() )
41             reporters.push_back( CATCH_CONFIG_DEFAULT_REPORTER );
42 
43         Ptr<IStreamingReporter> reporter;
44         for( std::vector<std::string>::const_iterator it = reporters.begin(), itEnd = reporters.end();
45                 it != itEnd;
46                 ++it )
47             reporter = addReporter( reporter, createReporter( *it, config ) );
48         return reporter;
49     }
addListeners(Ptr<IConfig const> const & config,Ptr<IStreamingReporter> reporters)50     Ptr<IStreamingReporter> addListeners( Ptr<IConfig const> const& config, Ptr<IStreamingReporter> reporters ) {
51         IReporterRegistry::Listeners listeners = getRegistryHub().getReporterRegistry().getListeners();
52         for( IReporterRegistry::Listeners::const_iterator it = listeners.begin(), itEnd = listeners.end();
53                 it != itEnd;
54                 ++it )
55             reporters = addReporter(reporters, (*it)->create( ReporterConfig( config ) ) );
56         return reporters;
57     }
58 
59 
runTests(Ptr<Config> const & config)60     Totals runTests( Ptr<Config> const& config ) {
61 
62         Ptr<IConfig const> iconfig = config.get();
63 
64         Ptr<IStreamingReporter> reporter = makeReporter( config );
65         reporter = addListeners( iconfig, reporter );
66 
67         RunContext context( iconfig, reporter );
68 
69         Totals totals;
70 
71         context.testGroupStarting( config->name(), 1, 1 );
72 
73         TestSpec testSpec = config->testSpec();
74         if( !testSpec.hasFilters() )
75             testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "~[.]" ).testSpec(); // All not hidden tests
76 
77         std::vector<TestCase> const& allTestCases = getAllTestCasesSorted( *iconfig );
78         for( std::vector<TestCase>::const_iterator it = allTestCases.begin(), itEnd = allTestCases.end();
79                 it != itEnd;
80                 ++it ) {
81             if( !context.aborting() && matchTest( *it, testSpec, *iconfig ) )
82                 totals += context.runTest( *it );
83             else
84                 reporter->skipTest( *it );
85         }
86 
87         context.testGroupEnded( iconfig->name(), totals, 1, 1 );
88         return totals;
89     }
90 
applyFilenamesAsTags(IConfig const & config)91     void applyFilenamesAsTags( IConfig const& config ) {
92         std::vector<TestCase> const& tests = getAllTestCasesSorted( config );
93         for(std::size_t i = 0; i < tests.size(); ++i ) {
94             TestCase& test = const_cast<TestCase&>( tests[i] );
95             std::set<std::string> tags = test.tags;
96 
97             std::string filename = test.lineInfo.file;
98             std::string::size_type lastSlash = filename.find_last_of( "\\/" );
99             if( lastSlash != std::string::npos )
100                 filename = filename.substr( lastSlash+1 );
101 
102             std::string::size_type lastDot = filename.find_last_of( '.' );
103             if( lastDot != std::string::npos )
104                 filename = filename.substr( 0, lastDot );
105 
106             tags.insert( '#' + filename );
107             setTags( test, tags );
108         }
109     }
110 
111     class Session : NonCopyable {
112         static bool alreadyInstantiated;
113 
114     public:
115 
116         struct OnUnusedOptions { enum DoWhat { Ignore, Fail }; };
117 
Session()118         Session()
119         : m_cli( makeCommandLineParser() ) {
120             if( alreadyInstantiated ) {
121                 std::string msg = "Only one instance of Catch::Session can ever be used";
122                 Catch::cerr() << msg << std::endl;
123                 throw std::logic_error( msg );
124             }
125             alreadyInstantiated = true;
126         }
~Session()127         ~Session() {
128             Catch::cleanUp();
129         }
130 
showHelp(std::string const & processName)131         void showHelp( std::string const& processName ) {
132             Catch::cout() << "\nCatch v" << libraryVersion() << "\n";
133 
134             m_cli.usage( Catch::cout(), processName );
135             Catch::cout() << "For more detail usage please see the project docs\n" << std::endl;
136         }
libIdentify()137         void libIdentify() {
138             Catch::cout()
139                     << std::left << std::setw(16) << "description: " << "A Catch test executable\n"
140                     << std::left << std::setw(16) << "category: " << "testframework\n"
141                     << std::left << std::setw(16) << "framework: " << "Catch Test\n"
142                     << std::left << std::setw(16) << "version: " << libraryVersion() << std::endl;
143         }
144 
applyCommandLine(int argc,char const * const * const argv,OnUnusedOptions::DoWhat unusedOptionBehaviour=OnUnusedOptions::Fail)145         int applyCommandLine( int argc, char const* const* const argv, OnUnusedOptions::DoWhat unusedOptionBehaviour = OnUnusedOptions::Fail ) {
146             try {
147                 m_cli.setThrowOnUnrecognisedTokens( unusedOptionBehaviour == OnUnusedOptions::Fail );
148                 m_unusedTokens = m_cli.parseInto( Clara::argsToVector( argc, argv ), m_configData );
149                 if( m_configData.showHelp )
150                     showHelp( m_configData.processName );
151                 if( m_configData.libIdentify )
152                     libIdentify();
153                 m_config.reset();
154             }
155             catch( std::exception& ex ) {
156                 {
157                     Colour colourGuard( Colour::Red );
158                     Catch::cerr()
159                         << "\nError(s) in input:\n"
160                         << Text( ex.what(), TextAttributes().setIndent(2) )
161                         << "\n\n";
162                 }
163                 m_cli.usage( Catch::cout(), m_configData.processName );
164                 return (std::numeric_limits<int>::max)();
165             }
166             return 0;
167         }
168 
useConfigData(ConfigData const & _configData)169         void useConfigData( ConfigData const& _configData ) {
170             m_configData = _configData;
171             m_config.reset();
172         }
173 
run(int argc,char const * const * const argv)174         int run( int argc, char const* const* const argv ) {
175 
176             int returnCode = applyCommandLine( argc, argv );
177             if( returnCode == 0 )
178                 returnCode = run();
179             return returnCode;
180         }
181 
182     #if defined(WIN32) && defined(UNICODE)
run(int argc,wchar_t const * const * const argv)183         int run( int argc, wchar_t const* const* const argv ) {
184 
185             char **utf8Argv = new char *[ argc ];
186 
187             for ( int i = 0; i < argc; ++i ) {
188                 int bufSize = WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, NULL, 0, NULL, NULL );
189 
190                 utf8Argv[ i ] = new char[ bufSize ];
191 
192                 WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, utf8Argv[i], bufSize, NULL, NULL );
193             }
194 
195             int returnCode = applyCommandLine( argc, utf8Argv );
196             if( returnCode == 0 )
197                 returnCode = run();
198 
199             for ( int i = 0; i < argc; ++i )
200                 delete [] utf8Argv[ i ];
201 
202             delete [] utf8Argv;
203 
204             return returnCode;
205         }
206     #endif
207 
run()208         int run() {
209             if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeStart ) != 0 ) {
210                 Catch::cout() << "...waiting for enter/ return before starting" << std::endl;
211                 static_cast<void>(std::getchar());
212             }
213             int exitCode = runInternal();
214             if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeExit ) != 0 ) {
215                 Catch::cout() << "...waiting for enter/ return before exiting, with code: " << exitCode << std::endl;
216                 static_cast<void>(std::getchar());
217             }
218             return exitCode;
219         }
220 
cli() const221         Clara::CommandLine<ConfigData> const& cli() const {
222             return m_cli;
223         }
unusedTokens() const224         std::vector<Clara::Parser::Token> const& unusedTokens() const {
225             return m_unusedTokens;
226         }
configData()227         ConfigData& configData() {
228             return m_configData;
229         }
config()230         Config& config() {
231             if( !m_config )
232                 m_config = new Config( m_configData );
233             return *m_config;
234         }
235     private:
236 
runInternal()237         int runInternal() {
238             if( m_configData.showHelp || m_configData.libIdentify )
239                 return 0;
240 
241             try
242             {
243                 config(); // Force config to be constructed
244 
245                 seedRng( *m_config );
246 
247                 if( m_configData.filenamesAsTags )
248                     applyFilenamesAsTags( *m_config );
249 
250                 // Handle list request
251                 if( Option<std::size_t> listed = list( config() ) )
252                     return static_cast<int>( *listed );
253 
254                 return static_cast<int>( runTests( m_config ).assertions.failed );
255             }
256             catch( std::exception& ex ) {
257                 Catch::cerr() << ex.what() << std::endl;
258                 return (std::numeric_limits<int>::max)();
259             }
260         }
261 
262         Clara::CommandLine<ConfigData> m_cli;
263         std::vector<Clara::Parser::Token> m_unusedTokens;
264         ConfigData m_configData;
265         Ptr<Config> m_config;
266     };
267 
268     bool Session::alreadyInstantiated = false;
269 
270 } // end namespace Catch
271 
272 #endif // TWOBLUECUBES_CATCH_RUNNER_HPP_INCLUDED
273