catch_istream.cpp
1 2 // Copyright Catch2 Authors 3 // Distributed under the Boost Software License, Version 1.0. 4 // (See accompanying file LICENSE.txt or copy at 5 // https://www.boost.org/LICENSE_1_0.txt) 6 7 // SPDX-License-Identifier: BSL-1.0 8 9 #include <catch2/internal/catch_istream.hpp> 10 #include <catch2/internal/catch_enforce.hpp> 11 #include <catch2/internal/catch_debug_console.hpp> 12 #include <catch2/internal/catch_unique_ptr.hpp> 13 #include <catch2/internal/catch_stdstreams.hpp> 14 15 #include <cstdio> 16 #include <fstream> 17 #include <sstream> 18 #include <vector> 19 20 namespace Catch { 21 22 Catch::IStream::~IStream() = default; 23 24 namespace Detail { 25 namespace { 26 template<typename WriterF, std::size_t bufferSize=256> 27 class StreamBufImpl final : public std::streambuf { 28 char data[bufferSize]; 29 WriterF m_writer; 30 31 public: 32 StreamBufImpl() { 33 setp( data, data + sizeof(data) ); 34 } 35 36 ~StreamBufImpl() noexcept override { 37 StreamBufImpl::sync(); 38 } 39 40 private: 41 int overflow( int c ) override { 42 sync(); 43 44 if( c != EOF ) { 45 if( pbase() == epptr() ) 46 m_writer( std::string( 1, static_cast<char>( c ) ) ); 47 else 48 sputc( static_cast<char>( c ) ); 49 } 50 return 0; 51 } 52 53 int sync() override { 54 if( pbase() != pptr() ) { 55 m_writer( std::string( pbase(), static_cast<std::string::size_type>( pptr() - pbase() ) ) ); 56 setp( pbase(), epptr() ); 57 } 58 return 0; 59 } 60 }; 61 62 /////////////////////////////////////////////////////////////////////////// 63 64 struct OutputDebugWriter { 65 66 void operator()( std::string const& str ) { 67 if ( !str.empty() ) { 68 writeToDebugConsole( str ); 69 } 70 } 71 }; 72 73 /////////////////////////////////////////////////////////////////////////// 74 75 class FileStream final : public IStream { 76 std::ofstream m_ofs; 77 public: 78 FileStream( std::string const& filename ) { 79 m_ofs.open( filename.c_str() ); 80 CATCH_ENFORCE( !m_ofs.fail(), "Unable to open file: '" << filename << '\'' ); 81 m_ofs << std::unitbuf; 82 } 83 ~FileStream() override = default; 84 public: // IStream 85 std::ostream& stream() override { 86 return m_ofs; 87 } 88 }; 89 90 /////////////////////////////////////////////////////////////////////////// 91 92 class CoutStream final : public IStream { 93 std::ostream m_os; 94 public: 95 // Store the streambuf from cout up-front because 96 // cout may get redirected when running tests 97 CoutStream() : m_os( Catch::cout().rdbuf() ) {} 98 ~CoutStream() override = default; 99 100 public: // IStream 101 std::ostream& stream() override { return m_os; } 102 bool isConsole() const override { return true; } 103 }; 104 105 class CerrStream : public IStream { 106 std::ostream m_os; 107 108 public: 109 // Store the streambuf from cerr up-front because 110 // cout may get redirected when running tests 111 CerrStream(): m_os( Catch::cerr().rdbuf() ) {} 112 ~CerrStream() override = default; 113 114 public: // IStream 115 std::ostream& stream() override { return m_os; } 116 bool isConsole() const override { return true; } 117 }; 118 119 /////////////////////////////////////////////////////////////////////////// 120 121 class DebugOutStream final : public IStream { 122 Detail::unique_ptr<StreamBufImpl<OutputDebugWriter>> m_streamBuf; 123 std::ostream m_os; 124 public: 125 DebugOutStream() 126 : m_streamBuf( Detail::make_unique<StreamBufImpl<OutputDebugWriter>>() ), 127 m_os( m_streamBuf.get() ) 128 {} 129 130 ~DebugOutStream() override = default; 131 132 public: // IStream 133 std::ostream& stream() override { return m_os; } 134 }; 135 136 } // unnamed namespace 137 } // namespace Detail 138 139 /////////////////////////////////////////////////////////////////////////// 140 141 auto makeStream( std::string const& filename ) -> Detail::unique_ptr<IStream> { 142 if ( filename.empty() || filename == "-" ) { 143 return Detail::make_unique<Detail::CoutStream>(); 144 } 145 if( filename[0] == '%' ) { 146 if ( filename == "%debug" ) { 147 return Detail::make_unique<Detail::DebugOutStream>(); 148 } else if ( filename == "%stderr" ) { 149 return Detail::make_unique<Detail::CerrStream>(); 150 } else if ( filename == "%stdout" ) { 151 return Detail::make_unique<Detail::CoutStream>(); 152 } else { 153 CATCH_ERROR( "Unrecognised stream: '" << filename << '\'' ); 154 } 155 } 156 return Detail::make_unique<Detail::FileStream>( filename ); 157 } 158 159 }