mirror of
				https://github.com/catchorg/Catch2.git
				synced 2025-11-04 05:59:32 +01:00 
			
		
		
		
	WIP: rebase pipe redirect onto current redirect interface
This commit is contained in:
		@@ -17,12 +17,18 @@
 | 
				
			|||||||
#include <iosfwd>
 | 
					#include <iosfwd>
 | 
				
			||||||
#include <sstream>
 | 
					#include <sstream>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#if defined( CATCH_CONFIG_NEW_CAPTURE )
 | 
					#if defined(CATCH_CONFIG_USE_ASYNC)
 | 
				
			||||||
 | 
					#include <thread>
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#if defined( CATCH_CONFIG_NEW_CAPTURE ) || defined(CATCH_CONFIG_USE_ASYNC)
 | 
				
			||||||
#    if defined( _MSC_VER )
 | 
					#    if defined( _MSC_VER )
 | 
				
			||||||
#        include <io.h> //_dup and _dup2
 | 
					#        include <io.h> //_dup and _dup2
 | 
				
			||||||
 | 
					#        include <fcntl.h> // _O_BINARY
 | 
				
			||||||
#        define dup _dup
 | 
					#        define dup _dup
 | 
				
			||||||
#        define dup2 _dup2
 | 
					#        define dup2 _dup2
 | 
				
			||||||
#        define fileno _fileno
 | 
					#        define fileno _fileno
 | 
				
			||||||
 | 
					#        define close _close
 | 
				
			||||||
#    else
 | 
					#    else
 | 
				
			||||||
#        include <unistd.h> // dup and dup2
 | 
					#        include <unistd.h> // dup and dup2
 | 
				
			||||||
#    endif
 | 
					#    endif
 | 
				
			||||||
@@ -48,13 +54,13 @@ namespace Catch {
 | 
				
			|||||||
         * that the underlying stream's rdbuf aren't changed by other
 | 
					         * that the underlying stream's rdbuf aren't changed by other
 | 
				
			||||||
         * users.
 | 
					         * users.
 | 
				
			||||||
         */
 | 
					         */
 | 
				
			||||||
        class RedirectedStreamNew {
 | 
					        class RedirectedStream {
 | 
				
			||||||
            std::ostream& m_originalStream;
 | 
					            std::ostream& m_originalStream;
 | 
				
			||||||
            std::ostream& m_redirectionStream;
 | 
					            std::ostream& m_redirectionStream;
 | 
				
			||||||
            std::streambuf* m_prevBuf;
 | 
					            std::streambuf* m_prevBuf;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public:
 | 
					        public:
 | 
				
			||||||
            RedirectedStreamNew( std::ostream& originalStream,
 | 
					            RedirectedStream( std::ostream& originalStream,
 | 
				
			||||||
                                 std::ostream& redirectionStream ):
 | 
					                                 std::ostream& redirectionStream ):
 | 
				
			||||||
                m_originalStream( originalStream ),
 | 
					                m_originalStream( originalStream ),
 | 
				
			||||||
                m_redirectionStream( redirectionStream ),
 | 
					                m_redirectionStream( redirectionStream ),
 | 
				
			||||||
@@ -72,7 +78,7 @@ namespace Catch {
 | 
				
			|||||||
         */
 | 
					         */
 | 
				
			||||||
        class StreamRedirect : public OutputRedirect {
 | 
					        class StreamRedirect : public OutputRedirect {
 | 
				
			||||||
            ReusableStringStream m_redirectedOut, m_redirectedErr;
 | 
					            ReusableStringStream m_redirectedOut, m_redirectedErr;
 | 
				
			||||||
            RedirectedStreamNew m_cout, m_cerr, m_clog;
 | 
					            RedirectedStream m_cout, m_cerr, m_clog;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public:
 | 
					        public:
 | 
				
			||||||
            StreamRedirect():
 | 
					            StreamRedirect():
 | 
				
			||||||
@@ -244,6 +250,246 @@ namespace Catch {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#endif // CATCH_CONFIG_NEW_CAPTURE
 | 
					#endif // CATCH_CONFIG_NEW_CAPTURE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#if defined( CATCH_CONFIG_USE_ASYNC )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        struct UniqueFileDescriptor final {
 | 
				
			||||||
 | 
					            constexpr UniqueFileDescriptor() noexcept;
 | 
				
			||||||
 | 
					            explicit UniqueFileDescriptor( int value ) noexcept;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            UniqueFileDescriptor( UniqueFileDescriptor const& ) = delete;
 | 
				
			||||||
 | 
					            constexpr UniqueFileDescriptor(
 | 
				
			||||||
 | 
					                UniqueFileDescriptor&& other ) noexcept;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            ~UniqueFileDescriptor() noexcept;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            UniqueFileDescriptor&
 | 
				
			||||||
 | 
					            operator=( UniqueFileDescriptor const& ) = delete;
 | 
				
			||||||
 | 
					            UniqueFileDescriptor&
 | 
				
			||||||
 | 
					            operator=( UniqueFileDescriptor&& other ) noexcept;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            constexpr int get();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private:
 | 
				
			||||||
 | 
					            int m_value;
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        struct OutputFileRedirector final {
 | 
				
			||||||
 | 
					            explicit OutputFileRedirector( std::FILE* file,
 | 
				
			||||||
 | 
					                                           std::string& result );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            OutputFileRedirector( OutputFileRedirector const& ) = delete;
 | 
				
			||||||
 | 
					            OutputFileRedirector( OutputFileRedirector&& ) = delete;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            ~OutputFileRedirector() noexcept;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            OutputFileRedirector&
 | 
				
			||||||
 | 
					            operator=( OutputFileRedirector const& ) = delete;
 | 
				
			||||||
 | 
					            OutputFileRedirector& operator=( OutputFileRedirector&& ) = delete;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private:
 | 
				
			||||||
 | 
					            std::FILE* m_file;
 | 
				
			||||||
 | 
					            int m_fd;
 | 
				
			||||||
 | 
					            UniqueFileDescriptor m_previous;
 | 
				
			||||||
 | 
					            std::thread m_readThread;
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        struct PipeRedirect final {
 | 
				
			||||||
 | 
					            PipeRedirect( std::string& output, std::string& error ):
 | 
				
			||||||
 | 
					                m_output{ stdout, output }, m_error{ stderr, error } {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private:
 | 
				
			||||||
 | 
					            OutputFileRedirector m_output;
 | 
				
			||||||
 | 
					            OutputFileRedirector m_error;
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        class PipeRedirectWrapper : public OutputRedirect {
 | 
				
			||||||
 | 
					            void activateImpl() override { m_redirect = Detail::make_unique<PipeRedirect>( m_stdout, m_stderr ); }
 | 
				
			||||||
 | 
					            void deactivateImpl() override { m_redirect.reset(); }
 | 
				
			||||||
 | 
					            std::string getStdout() override { return m_stdout; }
 | 
				
			||||||
 | 
					            std::string getStderr() override { return m_stderr; }
 | 
				
			||||||
 | 
					            void clearBuffers() override {
 | 
				
			||||||
 | 
					                m_stdout.clear();
 | 
				
			||||||
 | 
					                m_stderr.clear();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            Detail::unique_ptr<PipeRedirect> m_redirect;
 | 
				
			||||||
 | 
					            std::string m_stdout, m_stderr;
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        static inline void close_or_throw( int descriptor ) {
 | 
				
			||||||
 | 
					            if ( close( descriptor ) ) {
 | 
				
			||||||
 | 
					                CATCH_INTERNAL_ERROR( "close-or-throw" );
 | 
				
			||||||
 | 
					                //CATCH_SYSTEM_ERROR( errno, std::generic_category() );
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        static inline int dup_or_throw( int descriptor ) {
 | 
				
			||||||
 | 
					            int result{ dup( descriptor ) };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if ( result == -1 ) {
 | 
				
			||||||
 | 
					                CATCH_INTERNAL_ERROR( "dup-or-throw" );
 | 
				
			||||||
 | 
					                //CATCH_SYSTEM_ERROR( errno, std::generic_category() );
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return result;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        static inline int dup2_or_throw( int sourceDescriptor,
 | 
				
			||||||
 | 
					                                         int destinationDescriptor ) {
 | 
				
			||||||
 | 
					            int result{ dup2( sourceDescriptor, destinationDescriptor ) };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if ( result == -1 ) {
 | 
				
			||||||
 | 
					                CATCH_INTERNAL_ERROR( "dup2-or-throw" );
 | 
				
			||||||
 | 
					                //CATCH_SYSTEM_ERROR( errno, std::generic_category() );
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return result;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        static inline int fileno_or_throw( std::FILE* file ) {
 | 
				
			||||||
 | 
					            int result{ fileno( file ) };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if ( result == -1 ) {
 | 
				
			||||||
 | 
					                CATCH_INTERNAL_ERROR( "fileno-or-throw" );
 | 
				
			||||||
 | 
					                //CATCH_SYSTEM_ERROR( errno, std::generic_category() );
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return result;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        static inline void pipe_or_throw( int descriptors[2] ) {
 | 
				
			||||||
 | 
					#    if defined( _MSC_VER )
 | 
				
			||||||
 | 
					            constexpr int defaultPipeSize{ 0 };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            int result{ _pipe( descriptors, defaultPipeSize, _O_BINARY ) };
 | 
				
			||||||
 | 
					#    else
 | 
				
			||||||
 | 
					            int result{ pipe( descriptors ) };
 | 
				
			||||||
 | 
					#    endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if ( result ) {
 | 
				
			||||||
 | 
					                CATCH_INTERNAL_ERROR( "pipe-or-throw" );
 | 
				
			||||||
 | 
					                // CATCH_SYSTEM_ERROR( errno, std::generic_category() );
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        static inline size_t
 | 
				
			||||||
 | 
					        read_or_throw( int descriptor, void* buffer, size_t size ) {
 | 
				
			||||||
 | 
					#    if defined( _MSC_VER )
 | 
				
			||||||
 | 
					            int result{
 | 
				
			||||||
 | 
					                _read( descriptor, buffer, static_cast<unsigned>( size ) ) };
 | 
				
			||||||
 | 
					#    else
 | 
				
			||||||
 | 
					            ssize_t result{ read( descriptor, buffer, size ) };
 | 
				
			||||||
 | 
					#    endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if ( result == -1 ) {
 | 
				
			||||||
 | 
					                CATCH_INTERNAL_ERROR( "read-or-throw" );
 | 
				
			||||||
 | 
					                //CATCH_SYSTEM_ERROR( errno, std::generic_category() );
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return static_cast<size_t>( result );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        static inline void fflush_or_throw( std::FILE* file ) {
 | 
				
			||||||
 | 
					            if ( std::fflush( file ) ) {
 | 
				
			||||||
 | 
					                CATCH_INTERNAL_ERROR( "fflush-or-throw" );
 | 
				
			||||||
 | 
					                // CATCH_SYSTEM_ERROR( errno, std::generic_category() );
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        constexpr UniqueFileDescriptor::UniqueFileDescriptor() noexcept:
 | 
				
			||||||
 | 
					            m_value{} {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        UniqueFileDescriptor::UniqueFileDescriptor( int value ) noexcept:
 | 
				
			||||||
 | 
					            m_value{ value } {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        constexpr UniqueFileDescriptor::UniqueFileDescriptor(
 | 
				
			||||||
 | 
					            UniqueFileDescriptor&& other ) noexcept:
 | 
				
			||||||
 | 
					            m_value{ other.m_value } {
 | 
				
			||||||
 | 
					            other.m_value = 0;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        UniqueFileDescriptor::~UniqueFileDescriptor() noexcept {
 | 
				
			||||||
 | 
					            if ( m_value == 0 ) { return; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            close_or_throw(
 | 
				
			||||||
 | 
					                m_value ); // std::terminate on failure (due to noexcept)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        UniqueFileDescriptor& UniqueFileDescriptor::operator=(
 | 
				
			||||||
 | 
					            UniqueFileDescriptor&& other ) noexcept {
 | 
				
			||||||
 | 
					            std::swap( m_value, other.m_value );
 | 
				
			||||||
 | 
					            return *this;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        constexpr int UniqueFileDescriptor::get() { return m_value; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        static inline void
 | 
				
			||||||
 | 
					        create_pipe( UniqueFileDescriptor& readDescriptor,
 | 
				
			||||||
 | 
					                     UniqueFileDescriptor& writeDescriptor ) {
 | 
				
			||||||
 | 
					            readDescriptor = {};
 | 
				
			||||||
 | 
					            writeDescriptor = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            int descriptors[2];
 | 
				
			||||||
 | 
					            pipe_or_throw( descriptors );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            readDescriptor = UniqueFileDescriptor{ descriptors[0] };
 | 
				
			||||||
 | 
					            writeDescriptor = UniqueFileDescriptor{ descriptors[1] };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        static inline void read_thread( UniqueFileDescriptor&& file,
 | 
				
			||||||
 | 
					                                        std::string& result ) {
 | 
				
			||||||
 | 
					            std::string buffer{};
 | 
				
			||||||
 | 
					            constexpr size_t bufferSize{ 4096 };
 | 
				
			||||||
 | 
					            buffer.resize( bufferSize );
 | 
				
			||||||
 | 
					            size_t sizeRead{};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            while ( ( sizeRead = read_or_throw(
 | 
				
			||||||
 | 
					                          file.get(), &buffer[0], bufferSize ) ) != 0 ) {
 | 
				
			||||||
 | 
					                result.append( buffer.data(), sizeRead );
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        OutputFileRedirector::OutputFileRedirector( FILE* file,
 | 
				
			||||||
 | 
					                                                    std::string& result ):
 | 
				
			||||||
 | 
					            m_file{ file },
 | 
				
			||||||
 | 
					            m_fd{ fileno_or_throw( m_file ) },
 | 
				
			||||||
 | 
					            m_previous{ dup_or_throw( m_fd ) } {
 | 
				
			||||||
 | 
					            fflush_or_throw( m_file );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            UniqueFileDescriptor readDescriptor{};
 | 
				
			||||||
 | 
					            UniqueFileDescriptor writeDescriptor{};
 | 
				
			||||||
 | 
					            create_pipe( readDescriptor, writeDescriptor );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Anonymous pipes have a limited buffer and require an active
 | 
				
			||||||
 | 
					            // reader to ensure the writer does not become blocked. Use a
 | 
				
			||||||
 | 
					            // separate thread to ensure the buffer does not get stuck full.
 | 
				
			||||||
 | 
					            m_readThread =
 | 
				
			||||||
 | 
					                std::thread{ [readDescriptor{ CATCH_MOVE( readDescriptor ) },
 | 
				
			||||||
 | 
					                              &result]() mutable {
 | 
				
			||||||
 | 
					                    read_thread( CATCH_MOVE( readDescriptor ), result );
 | 
				
			||||||
 | 
					                } };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Replace the stdout or stderr file descriptor with the write end
 | 
				
			||||||
 | 
					            // of the pipe.
 | 
				
			||||||
 | 
					            dup2_or_throw( writeDescriptor.get(), m_fd );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        OutputFileRedirector::~OutputFileRedirector() noexcept {
 | 
				
			||||||
 | 
					            fflush_or_throw(
 | 
				
			||||||
 | 
					                m_file ); // std::terminate on failure (due to noexcept)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Restore the original stdout or stderr file descriptor.
 | 
				
			||||||
 | 
					            dup2_or_throw(
 | 
				
			||||||
 | 
					                m_previous.get(),
 | 
				
			||||||
 | 
					                m_fd ); // std::terminate on failure (due to noexcept)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if ( m_readThread.joinable() ) { m_readThread.join(); }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif // CATCH_CONFIG_USE_ASYNC
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    } // end namespace
 | 
					    } // end namespace
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    bool isRedirectAvailable( OutputRedirect::Kind kind ) {
 | 
					    bool isRedirectAvailable( OutputRedirect::Kind kind ) {
 | 
				
			||||||
@@ -255,6 +501,10 @@ namespace Catch {
 | 
				
			|||||||
#if defined( CATCH_CONFIG_NEW_CAPTURE )
 | 
					#if defined( CATCH_CONFIG_NEW_CAPTURE )
 | 
				
			||||||
        case OutputRedirect::FileDescriptors:
 | 
					        case OutputRedirect::FileDescriptors:
 | 
				
			||||||
            return true;
 | 
					            return true;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#if defined(CATCH_CONFIG_USE_ASYNC)
 | 
				
			||||||
 | 
					        case OutputRedirect::Pipes:
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
        default:
 | 
					        default:
 | 
				
			||||||
            return false;
 | 
					            return false;
 | 
				
			||||||
@@ -267,7 +517,8 @@ namespace Catch {
 | 
				
			|||||||
#if defined( CATCH_CONFIG_NEW_CAPTURE )
 | 
					#if defined( CATCH_CONFIG_NEW_CAPTURE )
 | 
				
			||||||
            return Detail::make_unique<FileRedirect>();
 | 
					            return Detail::make_unique<FileRedirect>();
 | 
				
			||||||
#else
 | 
					#else
 | 
				
			||||||
            return Detail::make_unique<StreamRedirect>();
 | 
					            //return Detail::make_unique<StreamRedirect>();
 | 
				
			||||||
 | 
					            return Detail::make_unique<PipeRedirectWrapper>();
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            return Detail::make_unique<NoopRedirect>();
 | 
					            return Detail::make_unique<NoopRedirect>();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,8 +25,10 @@ namespace Catch {
 | 
				
			|||||||
            None,
 | 
					            None,
 | 
				
			||||||
            //! Redirect std::cout/std::cerr/std::clog streams internally
 | 
					            //! Redirect std::cout/std::cerr/std::clog streams internally
 | 
				
			||||||
            Streams,
 | 
					            Streams,
 | 
				
			||||||
            //! Redirect the stdout/stderr file descriptors into files
 | 
					            //! Redirect the stdout/stderr file descriptors into temp files
 | 
				
			||||||
            FileDescriptors,
 | 
					            FileDescriptors,
 | 
				
			||||||
 | 
					            //! Redirect the stdout/stderr file descriptors into anonymous pipes
 | 
				
			||||||
 | 
					            Pipes,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        virtual ~OutputRedirect(); // = default;
 | 
					        virtual ~OutputRedirect(); // = default;
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user