Martin Hořeňovský | e871742 | 2017-08-31 10:31:52 +0200 | [diff] [blame] | 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" |
Phil Nash | c5608f0 | 2017-09-07 11:24:33 +0100 | [diff] [blame] | 9 | #include "catch_commandline.h" |
| 10 | #include "catch_console_colour.h" |
Martin Hořeňovský | e871742 | 2017-08-31 10:31:52 +0200 | [diff] [blame] | 11 | #include "catch_enforce.h" |
| 12 | #include "catch_list.h" |
Phil Nash | c5608f0 | 2017-09-07 11:24:33 +0100 | [diff] [blame] | 13 | #include "catch_run_context.h" |
Martin Hořeňovský | e871742 | 2017-08-31 10:31:52 +0200 | [diff] [blame] | 14 | #include "catch_stream.h" |
Phil Nash | c5608f0 | 2017-09-07 11:24:33 +0100 | [diff] [blame] | 15 | #include "catch_test_spec.h" |
Martin Hořeňovský | e871742 | 2017-08-31 10:31:52 +0200 | [diff] [blame] | 16 | #include "catch_version.h" |
| 17 | #include "catch_interfaces_reporter.h" |
| 18 | #include "catch_random_number_generator.h" |
| 19 | #include "catch_startup_exception_registry.h" |
| 20 | #include "catch_text.h" |
| 21 | |
| 22 | #include <cstdlib> |
| 23 | #include <iomanip> |
| 24 | |
Phil Nash | 5e06361 | 2017-12-05 23:26:21 +0000 | [diff] [blame] | 25 | namespace Catch { |
Martin Hořeňovský | e871742 | 2017-08-31 10:31:52 +0200 | [diff] [blame] | 26 | |
Phil Nash | 5e06361 | 2017-12-05 23:26:21 +0000 | [diff] [blame] | 27 | namespace { |
| 28 | const int MaxExitCode = 255; |
Martin Hořeňovský | e871742 | 2017-08-31 10:31:52 +0200 | [diff] [blame] | 29 | |
Phil Nash | 5e06361 | 2017-12-05 23:26:21 +0000 | [diff] [blame] | 30 | IStreamingReporterPtr createReporter(std::string const& reporterName, IConfigPtr const& config) { |
| 31 | auto reporter = Catch::getRegistryHub().getReporterRegistry().create(reporterName, config); |
| 32 | CATCH_ENFORCE(reporter, "No reporter registered with name: '" << reporterName << "'"); |
Martin Hořeňovský | e871742 | 2017-08-31 10:31:52 +0200 | [diff] [blame] | 33 | |
Phil Nash | 5e06361 | 2017-12-05 23:26:21 +0000 | [diff] [blame] | 34 | return reporter; |
| 35 | } |
Martin Hořeňovský | e871742 | 2017-08-31 10:31:52 +0200 | [diff] [blame] | 36 | |
| 37 | #ifndef CATCH_CONFIG_DEFAULT_REPORTER |
| 38 | #define CATCH_CONFIG_DEFAULT_REPORTER "console" |
| 39 | #endif |
| 40 | |
Phil Nash | 5e06361 | 2017-12-05 23:26:21 +0000 | [diff] [blame] | 41 | IStreamingReporterPtr makeReporter(std::shared_ptr<Config> const& config) { |
| 42 | auto const& reporterNames = config->getReporterNames(); |
| 43 | if (reporterNames.empty()) |
| 44 | return createReporter(CATCH_CONFIG_DEFAULT_REPORTER, config); |
Martin Hořeňovský | e871742 | 2017-08-31 10:31:52 +0200 | [diff] [blame] | 45 | |
Phil Nash | 5e06361 | 2017-12-05 23:26:21 +0000 | [diff] [blame] | 46 | IStreamingReporterPtr reporter; |
| 47 | for (auto const& name : reporterNames) |
| 48 | addReporter(reporter, createReporter(name, config)); |
| 49 | return reporter; |
| 50 | } |
Martin Hořeňovský | e8ec6bd | 2017-08-31 11:46:37 +0200 | [diff] [blame] | 51 | |
| 52 | #undef CATCH_CONFIG_DEFAULT_REPORTER |
| 53 | |
Phil Nash | 5e06361 | 2017-12-05 23:26:21 +0000 | [diff] [blame] | 54 | void addListeners(IStreamingReporterPtr& reporters, IConfigPtr const& config) { |
| 55 | auto const& listeners = Catch::getRegistryHub().getReporterRegistry().getListeners(); |
| 56 | for (auto const& listener : listeners) |
| 57 | addReporter(reporters, listener->create(Catch::ReporterConfig(config))); |
Martin Hořeňovský | e871742 | 2017-08-31 10:31:52 +0200 | [diff] [blame] | 58 | } |
| 59 | |
Martin Hořeňovský | e871742 | 2017-08-31 10:31:52 +0200 | [diff] [blame] | 60 | |
Phil Nash | 5e06361 | 2017-12-05 23:26:21 +0000 | [diff] [blame] | 61 | Catch::Totals runTests(std::shared_ptr<Config> const& config) { |
| 62 | IStreamingReporterPtr reporter = makeReporter(config); |
| 63 | addListeners(reporter, config); |
Martin Hořeňovský | e871742 | 2017-08-31 10:31:52 +0200 | [diff] [blame] | 64 | |
Phil Nash | 5e06361 | 2017-12-05 23:26:21 +0000 | [diff] [blame] | 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 | if (!testSpec.hasFilters()) |
| 73 | testSpec = TestSpecParser(ITagAliasRegistry::get()).parse("~[.]").testSpec(); // All not hidden tests |
| 74 | |
| 75 | auto const& allTestCases = getAllTestCasesSorted(*config); |
| 76 | for (auto const& testCase : allTestCases) { |
| 77 | if (!context.aborting() && matchTest(testCase, testSpec, *config)) |
| 78 | totals += context.runTest(testCase); |
| 79 | else |
| 80 | context.reporter().skipTest(testCase); |
Martin Hořeňovský | e8ec6bd | 2017-08-31 11:46:37 +0200 | [diff] [blame] | 81 | } |
Martin Hořeňovský | e871742 | 2017-08-31 10:31:52 +0200 | [diff] [blame] | 82 | |
Phil Nash | 5e06361 | 2017-12-05 23:26:21 +0000 | [diff] [blame] | 83 | context.testGroupEnded(config->name(), totals, 1, 1); |
| 84 | return totals; |
Martin Hořeňovský | e871742 | 2017-08-31 10:31:52 +0200 | [diff] [blame] | 85 | } |
Martin Hořeňovský | e871742 | 2017-08-31 10:31:52 +0200 | [diff] [blame] | 86 | |
Phil Nash | 5e06361 | 2017-12-05 23:26:21 +0000 | [diff] [blame] | 87 | void applyFilenamesAsTags(Catch::IConfig const& config) { |
| 88 | auto& tests = const_cast<std::vector<TestCase>&>(getAllTestCasesSorted(config)); |
| 89 | for (auto& testCase : tests) { |
| 90 | auto tags = testCase.tags; |
Martin Hořeňovský | e8ec6bd | 2017-08-31 11:46:37 +0200 | [diff] [blame] | 91 | |
Phil Nash | 5e06361 | 2017-12-05 23:26:21 +0000 | [diff] [blame] | 92 | std::string filename = testCase.lineInfo.file; |
| 93 | auto lastSlash = filename.find_last_of("\\/"); |
| 94 | if (lastSlash != std::string::npos) { |
| 95 | filename.erase(0, lastSlash); |
| 96 | filename[0] = '#'; |
| 97 | } |
| 98 | |
| 99 | auto lastDot = filename.find_last_of('.'); |
| 100 | if (lastDot != std::string::npos) { |
| 101 | filename.erase(lastDot); |
| 102 | } |
| 103 | |
| 104 | tags.push_back(std::move(filename)); |
| 105 | setTags(testCase, tags); |
| 106 | } |
| 107 | } |
| 108 | |
| 109 | } // anon namespace |
Martin Hořeňovský | e871742 | 2017-08-31 10:31:52 +0200 | [diff] [blame] | 110 | |
| 111 | Session::Session() { |
| 112 | static bool alreadyInstantiated = false; |
Phil Nash | fe05062 | 2017-11-02 17:57:52 +0000 | [diff] [blame] | 113 | if( alreadyInstantiated ) { |
| 114 | try { CATCH_INTERNAL_ERROR( "Only one instance of Catch::Session can ever be used" ); } |
| 115 | catch(...) { getMutableRegistryHub().registerStartupException(); } |
| 116 | } |
| 117 | |
| 118 | const auto& exceptions = getRegistryHub().getStartupExceptionRegistry().getExceptions(); |
| 119 | if ( !exceptions.empty() ) { |
| 120 | m_startupExceptions = true; |
| 121 | Colour colourGuard( Colour::Red ); |
| 122 | Catch::cerr() << "Errors occured during startup!" << '\n'; |
| 123 | // iterate over all exceptions and notify user |
| 124 | for ( const auto& ex_ptr : exceptions ) { |
| 125 | try { |
| 126 | std::rethrow_exception(ex_ptr); |
| 127 | } catch ( std::exception const& ex ) { |
| 128 | Catch::cerr() << Column( ex.what() ).indent(2) << '\n'; |
| 129 | } |
| 130 | } |
| 131 | } |
| 132 | |
Martin Hořeňovský | e871742 | 2017-08-31 10:31:52 +0200 | [diff] [blame] | 133 | alreadyInstantiated = true; |
| 134 | m_cli = makeCommandLineParser( m_configData ); |
| 135 | } |
| 136 | Session::~Session() { |
| 137 | Catch::cleanUp(); |
| 138 | } |
| 139 | |
| 140 | void Session::showHelp() const { |
| 141 | Catch::cout() |
| 142 | << "\nCatch v" << libraryVersion() << "\n" |
| 143 | << m_cli << std::endl |
| 144 | << "For more detailed usage please see the project docs\n" << std::endl; |
| 145 | } |
| 146 | void Session::libIdentify() { |
| 147 | Catch::cout() |
| 148 | << std::left << std::setw(16) << "description: " << "A Catch test executable\n" |
| 149 | << std::left << std::setw(16) << "category: " << "testframework\n" |
| 150 | << std::left << std::setw(16) << "framework: " << "Catch Test\n" |
| 151 | << std::left << std::setw(16) << "version: " << libraryVersion() << std::endl; |
| 152 | } |
| 153 | |
| 154 | int Session::applyCommandLine( int argc, char* argv[] ) { |
Phil Nash | fe05062 | 2017-11-02 17:57:52 +0000 | [diff] [blame] | 155 | if( m_startupExceptions ) |
| 156 | return 1; |
| 157 | |
Martin Hořeňovský | e871742 | 2017-08-31 10:31:52 +0200 | [diff] [blame] | 158 | auto result = m_cli.parse( clara::Args( argc, argv ) ); |
| 159 | if( !result ) { |
| 160 | Catch::cerr() |
| 161 | << Colour( Colour::Red ) |
| 162 | << "\nError(s) in input:\n" |
| 163 | << Column( result.errorMessage() ).indent( 2 ) |
| 164 | << "\n\n"; |
| 165 | Catch::cerr() << "Run with -? for usage\n" << std::endl; |
| 166 | return MaxExitCode; |
| 167 | } |
| 168 | |
| 169 | if( m_configData.showHelp ) |
| 170 | showHelp(); |
| 171 | if( m_configData.libIdentify ) |
| 172 | libIdentify(); |
| 173 | m_config.reset(); |
| 174 | return 0; |
| 175 | } |
| 176 | |
| 177 | void Session::useConfigData( ConfigData const& configData ) { |
| 178 | m_configData = configData; |
| 179 | m_config.reset(); |
| 180 | } |
| 181 | |
| 182 | int Session::run( int argc, char* argv[] ) { |
Phil Nash | fe05062 | 2017-11-02 17:57:52 +0000 | [diff] [blame] | 183 | if( m_startupExceptions ) |
Martin Hořeňovský | e871742 | 2017-08-31 10:31:52 +0200 | [diff] [blame] | 184 | return 1; |
Martin Hořeňovský | e871742 | 2017-08-31 10:31:52 +0200 | [diff] [blame] | 185 | int returnCode = applyCommandLine( argc, argv ); |
| 186 | if( returnCode == 0 ) |
| 187 | returnCode = run(); |
| 188 | return returnCode; |
| 189 | } |
| 190 | |
| 191 | #if defined(WIN32) && defined(UNICODE) |
| 192 | int Session::run( int argc, wchar_t* const argv[] ) { |
| 193 | |
| 194 | char **utf8Argv = new char *[ argc ]; |
| 195 | |
| 196 | for ( int i = 0; i < argc; ++i ) { |
| 197 | int bufSize = WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, NULL, 0, NULL, NULL ); |
| 198 | |
| 199 | utf8Argv[ i ] = new char[ bufSize ]; |
| 200 | |
| 201 | WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, utf8Argv[i], bufSize, NULL, NULL ); |
| 202 | } |
| 203 | |
| 204 | int returnCode = run( argc, utf8Argv ); |
| 205 | |
| 206 | for ( int i = 0; i < argc; ++i ) |
| 207 | delete [] utf8Argv[ i ]; |
| 208 | |
| 209 | delete [] utf8Argv; |
| 210 | |
| 211 | return returnCode; |
| 212 | } |
| 213 | #endif |
| 214 | int Session::run() { |
| 215 | if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeStart ) != 0 ) { |
| 216 | Catch::cout() << "...waiting for enter/ return before starting" << std::endl; |
| 217 | static_cast<void>(std::getchar()); |
| 218 | } |
| 219 | int exitCode = runInternal(); |
| 220 | if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeExit ) != 0 ) { |
| 221 | Catch::cout() << "...waiting for enter/ return before exiting, with code: " << exitCode << std::endl; |
| 222 | static_cast<void>(std::getchar()); |
| 223 | } |
| 224 | return exitCode; |
| 225 | } |
| 226 | |
| 227 | clara::Parser const& Session::cli() const { |
| 228 | return m_cli; |
| 229 | } |
| 230 | void Session::cli( clara::Parser const& newParser ) { |
| 231 | m_cli = newParser; |
| 232 | } |
| 233 | ConfigData& Session::configData() { |
| 234 | return m_configData; |
| 235 | } |
| 236 | Config& Session::config() { |
| 237 | if( !m_config ) |
| 238 | m_config = std::make_shared<Config>( m_configData ); |
| 239 | return *m_config; |
| 240 | } |
| 241 | |
| 242 | int Session::runInternal() { |
Phil Nash | fe05062 | 2017-11-02 17:57:52 +0000 | [diff] [blame] | 243 | if( m_startupExceptions ) |
| 244 | return 1; |
| 245 | |
Martin Hořeňovský | e871742 | 2017-08-31 10:31:52 +0200 | [diff] [blame] | 246 | if( m_configData.showHelp || m_configData.libIdentify ) |
| 247 | return 0; |
| 248 | |
| 249 | try |
| 250 | { |
| 251 | config(); // Force config to be constructed |
| 252 | |
| 253 | seedRng( *m_config ); |
| 254 | |
| 255 | if( m_configData.filenamesAsTags ) |
| 256 | applyFilenamesAsTags( *m_config ); |
| 257 | |
| 258 | // Handle list request |
| 259 | if( Option<std::size_t> listed = list( config() ) ) |
| 260 | return static_cast<int>( *listed ); |
| 261 | |
Martin Hořeňovský | 4575594 | 2018-01-12 11:49:48 +0100 | [diff] [blame^] | 262 | // Note that on unices only the lower 8 bits are usually used, clamping |
| 263 | // the return value to 255 prevents false negative when some multiple |
| 264 | // of 256 tests has failed |
Martin Hořeňovský | e871742 | 2017-08-31 10:31:52 +0200 | [diff] [blame] | 265 | return (std::min)( MaxExitCode, static_cast<int>( runTests( m_config ).assertions.failed ) ); |
| 266 | } |
| 267 | catch( std::exception& ex ) { |
| 268 | Catch::cerr() << ex.what() << std::endl; |
| 269 | return MaxExitCode; |
| 270 | } |
| 271 | } |
| 272 | |
| 273 | } // end namespace Catch |