74

Is it possible to capture the stdout and stderr when using the googletest framework?

For example, I would like to call a function that writes errors to the console (stderr). Now, when calling the function in the tests, I want to assert that no output appears there.

Or, maybe I want to test the error behaviour and want to assert that a certain string gets printed when I (deliberately) produce an error.

Jan Rüegg
  • 9,587
  • 8
  • 63
  • 105
  • 6
    From a design point of view, I would suggest modifying the implementation so that switching to log files be less painful. Using the `ostream` interface would make it easier for example. – Matthieu M. Sep 27 '10 at 12:01

7 Answers7

129

Googletest offers functions for this:

testing::internal::CaptureStdout();
std::cout << "My test";
std::string output = testing::internal::GetCapturedStdout();
Nicolas Holthaus
  • 7,763
  • 4
  • 42
  • 97
Heinzi
  • 5,793
  • 4
  • 40
  • 69
41

I have used this snippet before to redirect cout calls to a stringstream when testing output. Hopefully it might spark some ideas. I've never used googletest before.

// This can be an ofstream as well or any other ostream
std::stringstream buffer;

// Save cout's buffer here
std::streambuf *sbuf = std::cout.rdbuf();

// Redirect cout to our stringstream buffer or any other ostream
std::cout.rdbuf(buffer.rdbuf());

// Use cout as usual
std::cout << "Hello World";

// When done redirect cout to its old self
std::cout.rdbuf(sbuf);

Before redirecting back to the original output use your google test to check the output in buffer.

Wgaffa
  • 592
  • 4
  • 16
  • 1
    This doesn't work for googletest since gtest uses printf which goes straight to stdout, circumventing your redirect. But it is a nice solution if you want to intercept the output of `cout << ...`. I would create a helper class though to automatically restore the original streambuf in the destructor... – Florin T. Oct 24 '18 at 07:39
  • I've added a way one might use this with Google Test barring the scenario commented above. – Jim Daehn Oct 14 '19 at 03:08
8

Avoiding having to do this is always a good design idea. If you really want to do it the following works:

#include <cstdio>
#include <cassert>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <iostream>

int main() {
   int fd = open("my_file.log", O_WRONLY|O_CREAT|O_TRUNC, 0660);
   assert(fd >= 0);
   int ret = dup2(fd, 1);
   assert(ret >= 0);
   printf("This is stdout now!\n");
   std::cout << "This is C++ iostream cout now!" << std::endl;
   close(fd);
}

To use stderr instead of stdout change the second argument to dup2 to be 2. For capturing without going via a file you could use a pipe pair instead.

Flexo
  • 87,323
  • 22
  • 191
  • 272
4

Rather than do this, use dependency injection to remove the direct use of std::cout. In your test code use a mock object of class std:ostringstream as a mock object instead of the real std::cout.

So instead of this:

 void func() {
    ...
    std::cout << "message";
    ...
 }

 int main (int argc, char **argv) {
    ...
    func();
    ...
 }

have this:

 void func(std::ostream &out) {
    ...
    out << "message";
    ...
 }

 int main(int argc, char **argv) {
    ...
    func(std::cout);
    ...
 }
Raedwald
  • 46,613
  • 43
  • 151
  • 237
  • While this is a good idea in general, it won't work in his case because gtest is an external library (from his viewpoint) and he wants to capture the output of the framework without modifying the source code. – Florin T. Oct 24 '18 at 07:41
  • What if he/she has many functions that prints some error messages or logs? This won't work in that case as adding extra parameter just for capturing a stream is not a good idea. One thing he/she can do is to use global stream object and change/redirect it according to needs. std::cout, std::cerr and std::clog are such global objects. It's better to use std::cout and redirect it to another stream when needed. – Keshav Sahu Mar 16 '22 at 16:31
  • @KeshacSabu if there are many such functions, there are many design faults in the code to be fixed. Fixing them *is* a good idea. – Raedwald Mar 27 '22 at 07:58
2

Putting Wgaffa's suggestion (which I like) to a Google Test fixture, one might write:

namespace {

    class MyTestFixture : public ::testing::Test {
    protected:
        MyTestFixture() : sbuf{nullptr} {
            // intentionally empty
        }

        ~MyTestFixture() override = default;

        // Called before each unit test
        void SetUp() override {
            // Save cout's buffer...
            sbuf = std::cout.rdbuf();
            // Redirect cout to our stringstream buffer or any other ostream
            std::cout.rdbuf(buffer.rdbuf());
        }

        // Called after each unit test
        void TearDown() override {
            // When done redirect cout to its old self
            std::cout.rdbuf(sbuf);
            sbuf = nullptr;
        }

        // The following objects can be reused in each unit test

        // This can be an ofstream as well or any other ostream
        std::stringstream buffer{};
        // Save cout's buffer here
        std::streambuf *sbuf;
    };

    TEST_F(MyTestFixture, StackOverflowTest) {
        std::string expected{"Hello"};
        // Use cout as usual
        std::cout << expected;
        std::string actual{buffer.str()};
        EXPECT_EQ(expected, actual);
    }
} // end namespace

Jim Daehn
  • 196
  • 1
  • 7
0

We are doing exactly what you are referring to.

First we created some macros:

    #define CAPTURE_STDOUT StdoutRedirect::instance().redirect();
    #define RELEASE_STDOUT StdoutRedirect::instance().reset();
    #define ASSERT_INFO( COUNT, TARGET )   \
      ASSERT_PRED_FORMAT2(OurTestPredicates::AssertInfoMsgOutput, TARGET, COUNT );

See this answer for capturing stdout and stderr: https://stackoverflow.com/a/5419409/9796918 Just use their BeginCapture(), EndCapture() in place of our redirect() and reset().

In the AssertInfoMsgOutput method:

    AssertionResult OurTestPredicates::AssertInfoMsgOutput( const char* TARGET,
        const char* d1,
        const char* d2,
        int         COUNT )
    {
      int count = 0;
      bool match = false;
      std::string StdOutMessagge = GetCapture();
      // Here is where you process the stdout/stderr info for the TARGET, and for
      // COUNT instances of that TARGET message, and set count and match
      // appropriately
      ...
      if (( count == COUNT ) && match )
      {
        return ::testing::AssertionSuccess();
      }
      return :: testing::AssertionFailure() << "not found";
    }

Now in your unit test just wrap your calls that you want to capture stdout/stderr with:

    CAPTURE_STDOUT
    // Make your call to your code to test / capture here
    ASSERT_INFO( 1, "Foo bar" );
    RELEASE_STDOUT
Tom D
  • 21
  • 4
0

Based on the answer of Wgaffa I made this helper class which can be constructed with either std::cout or std::cerr:

class CaptureHelper
{
public:
  CaptureHelper(std::ostream& ioStream)
    : mStream(ioStream),
    mIsCapturing(false)
  { }

  ~CaptureHelper()
  {
    release();
  }

  void capture()
  {
    if (!mIsCapturing)
    {
      mOriginalBuffer = mStream.rdbuf();
      mStream.rdbuf(mRedirectStream.rdbuf());
      mIsCapturing = true;
    }
  }

  std::string release()
  {
    if (mIsCapturing)
    {
      std::string wOutput = mRedirectStream.str();
      mStream.rdbuf(mOriginalBuffer);
      mIsCapturing = false;
      return wOutput;
    }
  }

private:
  std::ostream& mStream;
  bool mIsCapturing;
  std::stringstream mRedirectStream;
  std::streambuf* mOriginalBuffer;

};
Kevin Pastor
  • 761
  • 3
  • 18