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