• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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