catch_session.cpp 11 KB

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