// bsls_outputredirector.h                                            -*-C++-*-
#ifndef INCLUDED_BSLS_OUTPUTREDIRECTOR
#define INCLUDED_BSLS_OUTPUTREDIRECTOR

#include <bsls_ident.h>
BSLS_IDENT("$Id: $")

//@PURPOSE: Provide a means for test drivers to redirect and inspect output.
//
//@CLASSES:
//  bsls::OutputRedirector: namespace for low-level logging functions
//
//@DESCRIPTION: This component provides a mechanism, 'bsls::OutputRedirector',
// to redirect output sent to either of the standard out or error streams and
// capture that output so that it can be read back and parsed, such as to allow
// a test driver to verify the expected output of a streaming operation.
//
// THIS COMPONENT MUST NOT BE USED OUTSIDE OF BDE TEST DRIVERS
//
///Usage
///-----
// This component is an implementation detail of 'bsls' and is *not* intended
// for direct client use.  It is subject to change without notice.  As such, a
// usage example is not provided.

#include <bsls_platform.h>

#include <limits.h>
#include <stddef.h>
#include <stdio.h>

namespace BloombergLP {
namespace bsls {

                         // ======================
                         // class OutputRedirector
                         // ======================

class OutputRedirector {
    // This class provides a facility for redirecting 'stdout' and 'stderr' to
    // temporary files, retrieving output from the respective temporary file
    // and comparing the output to user-supplied character buffers.  An
    // 'OutputRedirector' object can be in an un-redirected state or a
    // redirected state.  If the redirector is in a redirected state, it will
    // redirect either 'stdout' or 'stderr', but not both simultaneously.  An
    // 'OutputRedirector' object has the concept of a scratch buffer, where
    // output captured from the process' 'stdout' or 'stderr' stream is stored
    // when the 'OutputRedirector' object is in the redirected state.
    // Throughout this class, the term "captured output" refers to data that
    // has been written to the 'stdout' or 'stderr' stream and is waiting to be
    // loaded into the scratch buffer.  Each time the 'load' method is called,
    // the scratch buffer is truncated, and the captured output is moved into
    // the scratch buffer.  When this is done, there is no longer any captured
    // output.

  public:
    // TYPES
    enum Stream {
        // The 'enum' 'Stream' represents the specific stream that our object
        // is responsible for redirecting.
        e_STDOUT_STREAM,
        e_STDERR_STREAM
    };

    static const size_t k_OUTPUT_REDIRECTOR_BUFFER_SIZE          = 4096;
        // The size of the buffer used to store the captured values loaded in
        // the 'stdout' and 'stderr' error streams.

#ifdef BSLS_PLATFORM_OS_WINDOWS
    static const size_t k_PATH_BUFFER_SIZE = 512;
        // The size of the buffer used to hold a file name.
#else
    static const size_t k_PATH_BUFFER_SIZE = PATH_MAX + 1;
        // The size of the buffer used to hold a file name.
#endif

  private:
    // DATA
    char d_fileName[k_PATH_BUFFER_SIZE];      // name of temporary capture file

    char d_outputBuffer[k_OUTPUT_REDIRECTOR_BUFFER_SIZE];
    // 'd_outputBuffer' is the buffer that will hold the captured output.

    const Stream     d_stream;                // the stream for which this
                                              // object is responsible

    bool             d_isRedirectingFlag;     // Is this object currently
                                              // redirecting?

    bool             d_isFileCreatedFlag;     // has a temp file been created?

    bool             d_isOutputReadyFlag;     // has output been read from temp
                                              // file?

    size_t           d_outputSize;            // size of output loaded into
                                              // 'd_outputBuffer'

    int              d_duplicatedOriginalFd;  // a file descriptor that is
                                              // associated with a duplicate of
                                              // the original target of the
                                              // redirected stream.  This is
                                              // made by calling 'dup' on the
                                              // original stream before any
                                              // redirection happens

    bool             d_verbose;               // verbose flag (if any) for test
                                              // driver.

    bool             d_veryVerbose;           // very verbose flag (if any) for
                                              // test driver.


    // PRIVATE MANIPULATORS
    void cleanup();
        // If the redirector is in a redirected state, restore the original
        // target of the redirected stream.  If the temporary file has been
        // created, delete it.

    void cleanupFiles();
        // Delete the temporary file, if it has been created.

    bool generateTempFileName();
        // Load into 'd_fileName' a file name string corresponding to the name
        // of a valid temp file on the system.  Return 'true' if the name was
        // successfully loaded, or 'false' otherwise.

  private:
    // NOT IMPLEMENTED
    OutputRedirector(const OutputRedirector&);                     // = delete;
    OutputRedirector& operator=(const OutputRedirector&);          // = delete;

  public:
    // CREATORS
    explicit OutputRedirector(Stream which,
                              bool   verbose     = false,
                              bool   veryVerbose = false);
        // Create an 'OutputRedirector' in an un-redirected state, and with an
        // empty scratch buffer.  Upon a call to 'enable', this redirector will
        // be responsible for redirecting the stream associated with the
        // specified 'which' to a temporary file.  The behavior is undefined
        // unless 'which' is equal to 'OutputRedirector::e_STDOUT_STREAM' or
        // 'OutputRedirector::e_STDERR_STREAM'.

    ~OutputRedirector();
        // Destroy this 'OutputRedirector' object.  If the object is in a
        // redirected state, the original stream will be restored to its
        // initial target and the temporary file to which the stream was
        // redirected will be deleted.

    // MANIPULATORS
    void disable();
        // If the redirector is in a redirected state, restore the original
        // target of the redirected stream and close the temporary buffer.  If
        // the redirector is not in a redirected state, this method is a no-op.
        // Calling this method invalidates all output in the temporary file, so
        // a call to 'load' after the next successful 'enable' call will not
        // load any output that was previously written to the file.  This
        // method does not clear the scratch buffer, so one may call 'load'
        // before calling 'disable', and the contents will be available after
        // 'disable' is called.  If 'disable' fails to disable the redirection,
        // it will end the program by calling 'std::abort'.

    void enable();
        // If the 'Stream' specified at construction was 'e_STDOUT_STREAM',
        // redirect 'stdout' to a temporary file.  If the 'Stream' specified at
        // construction was 'e_STDERR_STREAM', redirect 'stderr' to a temporary
        // file.  The temporary file to which the stream is redirected will be
        // created the first time 'enable' is called, and will be deleted when
        // this object is destroyed.  If 'enable' fails to redirect either
        // 'stdout' or 'stderr' it will end the program by calling
        // 'std::abort'.

    bool load();
        // Read captured output into the scratch buffer.  Return 'true' if all
        // captured output was successfully loaded, and 'false' otherwise.
        // Note that captured output is allowed to have zero length.  The
        // behavior is undefined unless 'enable' has been previously called
        // successfully (after the latest call to 'disable', if 'disable' has
        // been called successfully).

    void clear();
        // Reset the scratch buffer to empty.  The behavior is undefined unless
        // 'enable' has been previously called successfully (after the latest
        // call to 'disable' if 'disable' has been called successfully).

    // ACCESSORS
    int compare(const char *expected, size_t expectedLength) const;
        // Compare the character buffer pointed to by the specified pointer
        // 'expected' with any output that has been loaded into the scratch
        // buffer.  The length of the 'expected' buffer is supplied in the
        // specified 'expectedLength'.  Return 0 if the 'expected' buffer has
        // the same length and contents as the scratch buffer, and non-zero
        // otherwise.  Note that the 'expected' buffer is allowed to contain
        // embedded nulls.  The behavior is undefined unless 'enable' has
        // previously been called successfully.

    int compare(const char *expected) const;
        // Compare the character buffer pointed to by the specified pointer
        // 'expected' with any output that has been loaded into the scratch
        // buffer.  The 'expected' buffer is assumed to be a NTBS, and its
        // length is taken to be the string length of the NTBS.  Return 0 if
        // the 'expected' buffer has the same length and contents as the
        // scratch buffer, and non-zero otherwise.  The behavior is undefined
        // unless 'enable' has previously been called successfully.

    const char *getOutput() const;
        // Return the address of the scratch buffer.

    bool isOutputReady() const;
        // Return 'true' if the captured output has been loaded into the
        // scratch buffer, and 'false' otherwise.

    bool isRedirecting() const;
        // Return 'true' if 'stdout' or 'stderr' has been successfully
        // redirected, and 'false' otherwise.

    FILE *nonRedirectedStream() const;
        // Return the value of the global 'stdout' or 'stderr' corresponding to
        // the stream that is not intended to be redirected by this object.

    size_t outputSize() const;
        // Return the number of bytes currently loaded into the scratch buffer.

    FILE *redirectedStream() const;
        // Return the value of the global 'stdout' or 'stderr' corresponding to
        // the stream that is intended to be redirected by this object.

    OutputRedirector::Stream redirectedStreamId() const;
        // Return 'OutputRedirector::e_STDOUT_STREAM' if this object is
        // responsible for redirecting 'stdout', and
        // 'OutputRedirector::e_STDERR_STREAM' if this object is responsible
        // for redirecting 'stderr'.
};


}  // close package namespace
}  // close enterprise namespace

#endif

// ----------------------------------------------------------------------------
// Copyright 2017 Bloomberg Finance L.P.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ----------------------------- END-OF-FILE ----------------------------------