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 #include <set> 29 #include <iterator> 30 31 namespace Catch { 32 33 namespace { 34 const int MaxExitCode = 255; 35 createReporter(std::string const & reporterName,IConfigPtr const & config)36 IStreamingReporterPtr createReporter(std::string const& reporterName, IConfigPtr const& config) { 37 auto reporter = Catch::getRegistryHub().getReporterRegistry().create(reporterName, config); 38 CATCH_ENFORCE(reporter, "No reporter registered with name: '" << reporterName << "'"); 39 40 return reporter; 41 } 42 makeReporter(std::shared_ptr<Config> const & config)43 IStreamingReporterPtr makeReporter(std::shared_ptr<Config> const& config) { 44 if (Catch::getRegistryHub().getReporterRegistry().getListeners().empty()) { 45 return createReporter(config->getReporterName(), config); 46 } 47 48 // On older platforms, returning std::unique_ptr<ListeningReporter> 49 // when the return type is std::unique_ptr<IStreamingReporter> 50 // doesn't compile without a std::move call. However, this causes 51 // a warning on newer platforms. Thus, we have to work around 52 // it a bit and downcast the pointer manually. 53 auto ret = std::unique_ptr<IStreamingReporter>(new ListeningReporter); 54 auto& multi = static_cast<ListeningReporter&>(*ret); 55 auto const& listeners = Catch::getRegistryHub().getReporterRegistry().getListeners(); 56 for (auto const& listener : listeners) { 57 multi.addListener(listener->create(Catch::ReporterConfig(config))); 58 } 59 multi.addReporter(createReporter(config->getReporterName(), config)); 60 return ret; 61 } 62 63 class TestGroup { 64 public: TestGroup(std::shared_ptr<Config> const & config)65 explicit TestGroup(std::shared_ptr<Config> const& config) 66 : m_config{config} 67 , m_context{config, makeReporter(config)} 68 { 69 auto const& allTestCases = getAllTestCasesSorted(*m_config); 70 m_matches = m_config->testSpec().matchesByFilter(allTestCases, *m_config); 71 auto const& invalidArgs = m_config->testSpec().getInvalidArgs(); 72 73 if (m_matches.empty() && invalidArgs.empty()) { 74 for (auto const& test : allTestCases) 75 if (!test.isHidden()) 76 m_tests.emplace(&test); 77 } else { 78 for (auto const& match : m_matches) 79 m_tests.insert(match.tests.begin(), match.tests.end()); 80 } 81 } 82 execute()83 Totals execute() { 84 auto const& invalidArgs = m_config->testSpec().getInvalidArgs(); 85 Totals totals; 86 m_context.testGroupStarting(m_config->name(), 1, 1); 87 for (auto const& testCase : m_tests) { 88 if (!m_context.aborting()) 89 totals += m_context.runTest(*testCase); 90 else 91 m_context.reporter().skipTest(*testCase); 92 } 93 94 for (auto const& match : m_matches) { 95 if (match.tests.empty()) { 96 m_context.reporter().noMatchingTestCases(match.name); 97 totals.error = -1; 98 } 99 } 100 101 if (!invalidArgs.empty()) { 102 for (auto const& invalidArg: invalidArgs) 103 m_context.reporter().reportInvalidArguments(invalidArg); 104 } 105 106 m_context.testGroupEnded(m_config->name(), totals, 1, 1); 107 return totals; 108 } 109 110 private: 111 using Tests = std::set<TestCase const*>; 112 113 std::shared_ptr<Config> m_config; 114 RunContext m_context; 115 Tests m_tests; 116 TestSpec::Matches m_matches; 117 }; 118 applyFilenamesAsTags(Catch::IConfig const & config)119 void applyFilenamesAsTags(Catch::IConfig const& config) { 120 auto& tests = const_cast<std::vector<TestCase>&>(getAllTestCasesSorted(config)); 121 for (auto& testCase : tests) { 122 auto tags = testCase.tags; 123 124 std::string filename = testCase.lineInfo.file; 125 auto lastSlash = filename.find_last_of("\\/"); 126 if (lastSlash != std::string::npos) { 127 filename.erase(0, lastSlash); 128 filename[0] = '#'; 129 } 130 131 auto lastDot = filename.find_last_of('.'); 132 if (lastDot != std::string::npos) { 133 filename.erase(lastDot); 134 } 135 136 tags.push_back(std::move(filename)); 137 setTags(testCase, tags); 138 } 139 } 140 141 } // anon namespace 142 Session()143 Session::Session() { 144 static bool alreadyInstantiated = false; 145 if( alreadyInstantiated ) { 146 CATCH_TRY { CATCH_INTERNAL_ERROR( "Only one instance of Catch::Session can ever be used" ); } 147 CATCH_CATCH_ALL { getMutableRegistryHub().registerStartupException(); } 148 } 149 150 // There cannot be exceptions at startup in no-exception mode. 151 #if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) 152 const auto& exceptions = getRegistryHub().getStartupExceptionRegistry().getExceptions(); 153 if ( !exceptions.empty() ) { 154 config(); 155 getCurrentMutableContext().setConfig(m_config); 156 157 m_startupExceptions = true; 158 Colour colourGuard( Colour::Red ); 159 Catch::cerr() << "Errors occurred during startup!" << '\n'; 160 // iterate over all exceptions and notify user 161 for ( const auto& ex_ptr : exceptions ) { 162 try { 163 std::rethrow_exception(ex_ptr); 164 } catch ( std::exception const& ex ) { 165 Catch::cerr() << Column( ex.what() ).indent(2) << '\n'; 166 } 167 } 168 } 169 #endif 170 171 alreadyInstantiated = true; 172 m_cli = makeCommandLineParser( m_configData ); 173 } ~Session()174 Session::~Session() { 175 Catch::cleanUp(); 176 } 177 showHelp() const178 void Session::showHelp() const { 179 Catch::cout() 180 << "\nCatch v" << libraryVersion() << "\n" 181 << m_cli << std::endl 182 << "For more detailed usage please see the project docs\n" << std::endl; 183 } libIdentify()184 void Session::libIdentify() { 185 Catch::cout() 186 << std::left << std::setw(16) << "description: " << "A Catch2 test executable\n" 187 << std::left << std::setw(16) << "category: " << "testframework\n" 188 << std::left << std::setw(16) << "framework: " << "Catch Test\n" 189 << std::left << std::setw(16) << "version: " << libraryVersion() << std::endl; 190 } 191 applyCommandLine(int argc,char const * const * argv)192 int Session::applyCommandLine( int argc, char const * const * argv ) { 193 if( m_startupExceptions ) 194 return 1; 195 196 auto result = m_cli.parse( clara::Args( argc, argv ) ); 197 if( !result ) { 198 config(); 199 getCurrentMutableContext().setConfig(m_config); 200 Catch::cerr() 201 << Colour( Colour::Red ) 202 << "\nError(s) in input:\n" 203 << Column( result.errorMessage() ).indent( 2 ) 204 << "\n\n"; 205 Catch::cerr() << "Run with -? for usage\n" << std::endl; 206 return MaxExitCode; 207 } 208 209 if( m_configData.showHelp ) 210 showHelp(); 211 if( m_configData.libIdentify ) 212 libIdentify(); 213 m_config.reset(); 214 return 0; 215 } 216 217 #if defined(CATCH_CONFIG_WCHAR) && defined(_WIN32) && defined(UNICODE) applyCommandLine(int argc,wchar_t const * const * argv)218 int Session::applyCommandLine( int argc, wchar_t const * const * argv ) { 219 220 char **utf8Argv = new char *[ argc ]; 221 222 for ( int i = 0; i < argc; ++i ) { 223 int bufSize = WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, nullptr, 0, nullptr, nullptr ); 224 225 utf8Argv[ i ] = new char[ bufSize ]; 226 227 WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, utf8Argv[i], bufSize, nullptr, nullptr ); 228 } 229 230 int returnCode = applyCommandLine( argc, utf8Argv ); 231 232 for ( int i = 0; i < argc; ++i ) 233 delete [] utf8Argv[ i ]; 234 235 delete [] utf8Argv; 236 237 return returnCode; 238 } 239 #endif 240 useConfigData(ConfigData const & configData)241 void Session::useConfigData( ConfigData const& configData ) { 242 m_configData = configData; 243 m_config.reset(); 244 } 245 run()246 int Session::run() { 247 if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeStart ) != 0 ) { 248 Catch::cout() << "...waiting for enter/ return before starting" << std::endl; 249 static_cast<void>(std::getchar()); 250 } 251 int exitCode = runInternal(); 252 if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeExit ) != 0 ) { 253 Catch::cout() << "...waiting for enter/ return before exiting, with code: " << exitCode << std::endl; 254 static_cast<void>(std::getchar()); 255 } 256 return exitCode; 257 } 258 cli() const259 clara::Parser const& Session::cli() const { 260 return m_cli; 261 } cli(clara::Parser const & newParser)262 void Session::cli( clara::Parser const& newParser ) { 263 m_cli = newParser; 264 } configData()265 ConfigData& Session::configData() { 266 return m_configData; 267 } config()268 Config& Session::config() { 269 if( !m_config ) 270 m_config = std::make_shared<Config>( m_configData ); 271 return *m_config; 272 } 273 runInternal()274 int Session::runInternal() { 275 if( m_startupExceptions ) 276 return 1; 277 278 if (m_configData.showHelp || m_configData.libIdentify) { 279 return 0; 280 } 281 282 CATCH_TRY { 283 config(); // Force config to be constructed 284 285 seedRng( *m_config ); 286 287 if( m_configData.filenamesAsTags ) 288 applyFilenamesAsTags( *m_config ); 289 290 // Handle list request 291 if( Option<std::size_t> listed = list( m_config ) ) 292 return static_cast<int>( *listed ); 293 294 TestGroup tests { m_config }; 295 auto const totals = tests.execute(); 296 297 if( m_config->warnAboutNoTests() && totals.error == -1 ) 298 return 2; 299 300 // Note that on unices only the lower 8 bits are usually used, clamping 301 // the return value to 255 prevents false negative when some multiple 302 // of 256 tests has failed 303 return (std::min) (MaxExitCode, (std::max) (totals.error, static_cast<int>(totals.assertions.failed))); 304 } 305 #if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) 306 catch( std::exception& ex ) { 307 Catch::cerr() << ex.what() << std::endl; 308 return MaxExitCode; 309 } 310 #endif 311 } 312 313 } // end namespace Catch 314