1 /* 2 * Created by Martin on 31/08/2017. 3 * 4 * Distributed under the Boost Software License, Version 1.0. (See accompanying 5 * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 */ 7 8 #include "catch_session.h" 9 #include "catch_commandline.h" 10 #include "catch_console_colour.h" 11 #include "catch_enforce.h" 12 #include "catch_list.h" 13 #include "catch_context.h" 14 #include "catch_run_context.h" 15 #include "catch_stream.h" 16 #include "catch_test_spec.h" 17 #include "catch_version.h" 18 #include "catch_interfaces_reporter.h" 19 #include "catch_random_number_generator.h" 20 #include "catch_startup_exception_registry.h" 21 #include "catch_text.h" 22 #include "catch_stream.h" 23 #include "catch_windows_h_proxy.h" 24 #include "../reporters/catch_reporter_listening.h" 25 26 #include <cstdlib> 27 #include <iomanip> 28 29 namespace Catch { 30 31 namespace { 32 const int MaxExitCode = 255; 33 createReporter(std::string const & reporterName,IConfigPtr const & config)34 IStreamingReporterPtr createReporter(std::string const& reporterName, IConfigPtr const& config) { 35 auto reporter = Catch::getRegistryHub().getReporterRegistry().create(reporterName, config); 36 CATCH_ENFORCE(reporter, "No reporter registered with name: '" << reporterName << "'"); 37 38 return reporter; 39 } 40 makeReporter(std::shared_ptr<Config> const & config)41 IStreamingReporterPtr makeReporter(std::shared_ptr<Config> const& config) { 42 if (Catch::getRegistryHub().getReporterRegistry().getListeners().empty()) { 43 return createReporter(config->getReporterName(), config); 44 } 45 46 // On older platforms, returning std::unique_ptr<ListeningReporter> 47 // when the return type is std::unique_ptr<IStreamingReporter> 48 // doesn't compile without a std::move call. However, this causes 49 // a warning on newer platforms. Thus, we have to work around 50 // it a bit and downcast the pointer manually. 51 auto ret = std::unique_ptr<IStreamingReporter>(new ListeningReporter); 52 auto& multi = static_cast<ListeningReporter&>(*ret); 53 auto const& listeners = Catch::getRegistryHub().getReporterRegistry().getListeners(); 54 for (auto const& listener : listeners) { 55 multi.addListener(listener->create(Catch::ReporterConfig(config))); 56 } 57 multi.addReporter(createReporter(config->getReporterName(), config)); 58 return ret; 59 } 60 61 runTests(std::shared_ptr<Config> const & config)62 Catch::Totals runTests(std::shared_ptr<Config> const& config) { 63 auto reporter = makeReporter(config); 64 65 RunContext context(config, std::move(reporter)); 66 67 Totals totals; 68 69 context.testGroupStarting(config->name(), 1, 1); 70 71 TestSpec testSpec = config->testSpec(); 72 73 auto const& allTestCases = getAllTestCasesSorted(*config); 74 for (auto const& testCase : allTestCases) { 75 if (!context.aborting() && matchTest(testCase, testSpec, *config)) 76 totals += context.runTest(testCase); 77 else 78 context.reporter().skipTest(testCase); 79 } 80 81 if (config->warnAboutNoTests() && totals.testCases.total() == 0) { 82 ReusableStringStream testConfig; 83 84 bool first = true; 85 for (const auto& input : config->getTestsOrTags()) { 86 if (!first) { testConfig << ' '; } 87 first = false; 88 testConfig << input; 89 } 90 91 context.reporter().noMatchingTestCases(testConfig.str()); 92 totals.error = -1; 93 } 94 95 context.testGroupEnded(config->name(), totals, 1, 1); 96 return totals; 97 } 98 applyFilenamesAsTags(Catch::IConfig const & config)99 void applyFilenamesAsTags(Catch::IConfig const& config) { 100 auto& tests = const_cast<std::vector<TestCase>&>(getAllTestCasesSorted(config)); 101 for (auto& testCase : tests) { 102 auto tags = testCase.tags; 103 104 std::string filename = testCase.lineInfo.file; 105 auto lastSlash = filename.find_last_of("\\/"); 106 if (lastSlash != std::string::npos) { 107 filename.erase(0, lastSlash); 108 filename[0] = '#'; 109 } 110 111 auto lastDot = filename.find_last_of('.'); 112 if (lastDot != std::string::npos) { 113 filename.erase(lastDot); 114 } 115 116 tags.push_back(std::move(filename)); 117 setTags(testCase, tags); 118 } 119 } 120 121 } // anon namespace 122 Session()123 Session::Session() { 124 static bool alreadyInstantiated = false; 125 if( alreadyInstantiated ) { 126 CATCH_TRY { CATCH_INTERNAL_ERROR( "Only one instance of Catch::Session can ever be used" ); } 127 CATCH_CATCH_ALL { getMutableRegistryHub().registerStartupException(); } 128 } 129 130 // There cannot be exceptions at startup in no-exception mode. 131 #if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) 132 const auto& exceptions = getRegistryHub().getStartupExceptionRegistry().getExceptions(); 133 if ( !exceptions.empty() ) { 134 m_startupExceptions = true; 135 Colour colourGuard( Colour::Red ); 136 Catch::cerr() << "Errors occurred during startup!" << '\n'; 137 // iterate over all exceptions and notify user 138 for ( const auto& ex_ptr : exceptions ) { 139 try { 140 std::rethrow_exception(ex_ptr); 141 } catch ( std::exception const& ex ) { 142 Catch::cerr() << Column( ex.what() ).indent(2) << '\n'; 143 } 144 } 145 } 146 #endif 147 148 alreadyInstantiated = true; 149 m_cli = makeCommandLineParser( m_configData ); 150 } ~Session()151 Session::~Session() { 152 Catch::cleanUp(); 153 } 154 showHelp() const155 void Session::showHelp() const { 156 Catch::cout() 157 << "\nCatch v" << libraryVersion() << "\n" 158 << m_cli << std::endl 159 << "For more detailed usage please see the project docs\n" << std::endl; 160 } libIdentify()161 void Session::libIdentify() { 162 Catch::cout() 163 << std::left << std::setw(16) << "description: " << "A Catch test executable\n" 164 << std::left << std::setw(16) << "category: " << "testframework\n" 165 << std::left << std::setw(16) << "framework: " << "Catch Test\n" 166 << std::left << std::setw(16) << "version: " << libraryVersion() << std::endl; 167 } 168 applyCommandLine(int argc,char const * const * argv)169 int Session::applyCommandLine( int argc, char const * const * argv ) { 170 if( m_startupExceptions ) 171 return 1; 172 173 auto result = m_cli.parse( clara::Args( argc, argv ) ); 174 if( !result ) { 175 config(); 176 getCurrentMutableContext().setConfig(m_config); 177 Catch::cerr() 178 << Colour( Colour::Red ) 179 << "\nError(s) in input:\n" 180 << Column( result.errorMessage() ).indent( 2 ) 181 << "\n\n"; 182 Catch::cerr() << "Run with -? for usage\n" << std::endl; 183 return MaxExitCode; 184 } 185 186 if( m_configData.showHelp ) 187 showHelp(); 188 if( m_configData.libIdentify ) 189 libIdentify(); 190 m_config.reset(); 191 return 0; 192 } 193 194 #if defined(CATCH_CONFIG_WCHAR) && defined(WIN32) && defined(UNICODE) applyCommandLine(int argc,wchar_t const * const * argv)195 int Session::applyCommandLine( int argc, wchar_t const * const * argv ) { 196 197 char **utf8Argv = new char *[ argc ]; 198 199 for ( int i = 0; i < argc; ++i ) { 200 int bufSize = WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, NULL, 0, NULL, NULL ); 201 202 utf8Argv[ i ] = new char[ bufSize ]; 203 204 WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, utf8Argv[i], bufSize, NULL, NULL ); 205 } 206 207 int returnCode = applyCommandLine( argc, utf8Argv ); 208 209 for ( int i = 0; i < argc; ++i ) 210 delete [] utf8Argv[ i ]; 211 212 delete [] utf8Argv; 213 214 return returnCode; 215 } 216 #endif 217 useConfigData(ConfigData const & configData)218 void Session::useConfigData( ConfigData const& configData ) { 219 m_configData = configData; 220 m_config.reset(); 221 } 222 run()223 int Session::run() { 224 if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeStart ) != 0 ) { 225 Catch::cout() << "...waiting for enter/ return before starting" << std::endl; 226 static_cast<void>(std::getchar()); 227 } 228 int exitCode = runInternal(); 229 if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeExit ) != 0 ) { 230 Catch::cout() << "...waiting for enter/ return before exiting, with code: " << exitCode << std::endl; 231 static_cast<void>(std::getchar()); 232 } 233 return exitCode; 234 } 235 cli() const236 clara::Parser const& Session::cli() const { 237 return m_cli; 238 } cli(clara::Parser const & newParser)239 void Session::cli( clara::Parser const& newParser ) { 240 m_cli = newParser; 241 } configData()242 ConfigData& Session::configData() { 243 return m_configData; 244 } config()245 Config& Session::config() { 246 if( !m_config ) 247 m_config = std::make_shared<Config>( m_configData ); 248 return *m_config; 249 } 250 runInternal()251 int Session::runInternal() { 252 if( m_startupExceptions ) 253 return 1; 254 255 if (m_configData.showHelp || m_configData.libIdentify) { 256 return 0; 257 } 258 259 CATCH_TRY { 260 config(); // Force config to be constructed 261 262 seedRng( *m_config ); 263 264 if( m_configData.filenamesAsTags ) 265 applyFilenamesAsTags( *m_config ); 266 267 // Handle list request 268 if( Option<std::size_t> listed = list( m_config ) ) 269 return static_cast<int>( *listed ); 270 271 auto totals = runTests( m_config ); 272 // Note that on unices only the lower 8 bits are usually used, clamping 273 // the return value to 255 prevents false negative when some multiple 274 // of 256 tests has failed 275 return (std::min) (MaxExitCode, (std::max) (totals.error, static_cast<int>(totals.assertions.failed))); 276 } 277 #if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) 278 catch( std::exception& ex ) { 279 Catch::cerr() << ex.what() << std::endl; 280 return MaxExitCode; 281 } 282 #endif 283 } 284 285 } // end namespace Catch 286