1 /*
2  *  Created by Phil on 17/01/2011.
3  *  Copyright 2011 Two Blue Cubes Ltd. All rights reserved.
4  *
5  *  Distributed under the Boost Software License, Version 1.0. (See accompanying
6  *  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7  *
8  */
9 
10 #include "catch_common.h"
11 #include "catch_enforce.h"
12 #include "catch_stream.h"
13 #include "catch_debug_console.h"
14 #include "catch_stringref.h"
15 
16 #include <cstdio>
17 #include <iostream>
18 #include <fstream>
19 #include <sstream>
20 #include <vector>
21 #include <memory>
22 
23 #if defined(__clang__)
24 #    pragma clang diagnostic push
25 #    pragma clang diagnostic ignored "-Wexit-time-destructors"
26 #endif
27 
28 namespace Catch {
29 
30     Catch::IStream::~IStream() = default;
31 
32     namespace detail { namespace {
33         template<typename WriterF, std::size_t bufferSize=256>
34         class StreamBufImpl : public std::streambuf {
35             char data[bufferSize];
36             WriterF m_writer;
37 
38         public:
StreamBufImpl()39             StreamBufImpl() {
40                 setp( data, data + sizeof(data) );
41             }
42 
~StreamBufImpl()43             ~StreamBufImpl() noexcept {
44                 StreamBufImpl::sync();
45             }
46 
47         private:
overflow(int c)48             int overflow( int c ) override {
49                 sync();
50 
51                 if( c != EOF ) {
52                     if( pbase() == epptr() )
53                         m_writer( std::string( 1, static_cast<char>( c ) ) );
54                     else
55                         sputc( static_cast<char>( c ) );
56                 }
57                 return 0;
58             }
59 
sync()60             int sync() override {
61                 if( pbase() != pptr() ) {
62                     m_writer( std::string( pbase(), static_cast<std::string::size_type>( pptr() - pbase() ) ) );
63                     setp( pbase(), epptr() );
64                 }
65                 return 0;
66             }
67         };
68 
69         ///////////////////////////////////////////////////////////////////////////
70 
71         struct OutputDebugWriter {
72 
operator ()Catch::detail::__anon55f7acec0111::OutputDebugWriter73             void operator()( std::string const&str ) {
74                 writeToDebugConsole( str );
75             }
76         };
77 
78         ///////////////////////////////////////////////////////////////////////////
79 
80         class FileStream : public IStream {
81             mutable std::ofstream m_ofs;
82         public:
FileStream(StringRef filename)83             FileStream( StringRef filename ) {
84                 m_ofs.open( filename.c_str() );
85                 CATCH_ENFORCE( !m_ofs.fail(), "Unable to open file: '" << filename << "'" );
86             }
87             ~FileStream() override = default;
88         public: // IStream
stream() const89             std::ostream& stream() const override {
90                 return m_ofs;
91             }
92         };
93 
94         ///////////////////////////////////////////////////////////////////////////
95 
96         class CoutStream : public IStream {
97             mutable std::ostream m_os;
98         public:
99             // Store the streambuf from cout up-front because
100             // cout may get redirected when running tests
CoutStream()101             CoutStream() : m_os( Catch::cout().rdbuf() ) {}
102             ~CoutStream() override = default;
103 
104         public: // IStream
stream() const105             std::ostream& stream() const override { return m_os; }
106         };
107 
108         ///////////////////////////////////////////////////////////////////////////
109 
110         class DebugOutStream : public IStream {
111             std::unique_ptr<StreamBufImpl<OutputDebugWriter>> m_streamBuf;
112             mutable std::ostream m_os;
113         public:
DebugOutStream()114             DebugOutStream()
115             :   m_streamBuf( new StreamBufImpl<OutputDebugWriter>() ),
116                 m_os( m_streamBuf.get() )
117             {}
118 
119             ~DebugOutStream() override = default;
120 
121         public: // IStream
stream() const122             std::ostream& stream() const override { return m_os; }
123         };
124 
125     }} // namespace anon::detail
126 
127     ///////////////////////////////////////////////////////////////////////////
128 
makeStream(StringRef const & filename)129     auto makeStream( StringRef const &filename ) -> IStream const* {
130         if( filename.empty() )
131             return new detail::CoutStream();
132         else if( filename[0] == '%' ) {
133             if( filename == "%debug" )
134                 return new detail::DebugOutStream();
135             else
136                 CATCH_ERROR( "Unrecognised stream: '" << filename << "'" );
137         }
138         else
139             return new detail::FileStream( filename );
140     }
141 
142 
143     // This class encapsulates the idea of a pool of ostringstreams that can be reused.
144     struct StringStreams {
145         std::vector<std::unique_ptr<std::ostringstream>> m_streams;
146         std::vector<std::size_t> m_unused;
147         std::ostringstream m_referenceStream; // Used for copy state/ flags from
148         static StringStreams* s_instance;
149 
addCatch::StringStreams150         auto add() -> std::size_t {
151             if( m_unused.empty() ) {
152                 m_streams.push_back( std::unique_ptr<std::ostringstream>( new std::ostringstream ) );
153                 return m_streams.size()-1;
154             }
155             else {
156                 auto index = m_unused.back();
157                 m_unused.pop_back();
158                 return index;
159             }
160         }
161 
releaseCatch::StringStreams162         void release( std::size_t index ) {
163             m_streams[index]->copyfmt( m_referenceStream ); // Restore initial flags and other state
164             m_unused.push_back(index);
165         }
166 
167         // !TBD: put in TLS
instanceCatch::StringStreams168         static auto instance() -> StringStreams& {
169             if( !s_instance )
170                 s_instance = new StringStreams();
171             return *s_instance;
172         }
cleanupCatch::StringStreams173         static void cleanup() {
174             delete s_instance;
175             s_instance = nullptr;
176         }
177     };
178 
179     StringStreams* StringStreams::s_instance = nullptr;
180 
cleanup()181     void ReusableStringStream::cleanup() {
182         StringStreams::cleanup();
183     }
184 
ReusableStringStream()185     ReusableStringStream::ReusableStringStream()
186     :   m_index( StringStreams::instance().add() ),
187         m_oss( StringStreams::instance().m_streams[m_index].get() )
188     {}
189 
~ReusableStringStream()190     ReusableStringStream::~ReusableStringStream() {
191         static_cast<std::ostringstream*>( m_oss )->str("");
192         m_oss->clear();
193         StringStreams::instance().release( m_index );
194     }
195 
str() const196     auto ReusableStringStream::str() const -> std::string {
197         return static_cast<std::ostringstream*>( m_oss )->str();
198     }
199 
200 
201     ///////////////////////////////////////////////////////////////////////////
202 
203 
204 #ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement these functions
cout()205     std::ostream& cout() { return std::cout; }
cerr()206     std::ostream& cerr() { return std::cerr; }
clog()207     std::ostream& clog() { return std::clog; }
208 #endif
209 }
210 
211 #if defined(__clang__)
212 #    pragma clang diagnostic pop
213 #endif
214