2

What I am looking for

A pipe like stream, connecting std::istream with std::ostream. I should be able to write something to the std::ostream part and then read it from the std::istream part. When calling std::getline() on the istream part, it should block until data from the std::ostream part is available.

std::stringstream does pretty much what I want, except for it doesn't block. When calling std::getline() on an empty std::stringstream, it just returns and sets the eof() flag.

Why I need this

For testing a Console class that does input/output with streams, I need the counterparts to read the output of the Console class and produce input for it. std::getline() on std::cin is blocking, so the pipe stream should also block if it is empty.

Details

I have a Console class which handles console input and output. A simplified version is like

class Console {
public:
  Console(): Console(std::cin, std::cout) {}
  Console(istream &istr, ostream &ostr): _istr(istr), _ostr(ostr) {}

  string ask(const string &question) {
    _ostr << question << "\n";
    string answer;
    std::getline(_istr, answer);
    return answer;
  }
private:
  istream &_istr;
  ostream &_ostr;
};

In production, _istr will be set to std::cin and _ostr will be set to std::cout. In test cases, I will set them to own in-memory stream classes.

For this, I wanted to use std::stringstream, because I can then test the output with

void EXPECT_OUTPUT_LINE(const string &expected) {
  string actual;
  std::getline(ostr, actual);
  EXPECT_EQ(expected, actual);
}

and send input with

void sendInputLine(const string &line) {
  istr << line << "\n";
}

In this case, the Console class runs in a different thread, because it should block its thread when trying to read from _istr. The general test case would then look like

TEST(ConsoleTest, MyTest) {
  stringstream istr, ostr;
  future<string> answer = std::async(std::launch::async, [&] () {
    Console(ostr, istr).ask("Question?");
  });
  EXPECT_OUTPUT_LINE("Question?");
  sendInputLine("Answer");
  EXPECT_EQ("Answer", answer.get());
}

However, it turns out std::getline() isn't blocking. When used with std::cin, it does block. When used with an empty std::stringstream, std::getline immediately returns and sets the eof flag.

So even if my test case says sendInputLine("bla"), this doesn't have any effect, because the Console class doesn't wait for it and its std::getline() call might already have returned in the past on the empty stringstream.

The same problem is in the EXPECT_OUTPUT_LINE. It calls std::getline to get the output of the Console class, but it doesn't wait for it. If this happens before the Console class actually made any output, then it just returns and sets the eof flag for the stringstream.

Heinzi
  • 5,793
  • 4
  • 40
  • 69
  • it surprises me a bit that `std::getline(ostr, actual);` is compiling, according to the docs, `getline` needs an `istream` and `std::cout` is definitly not one – WorldSEnder Jun 30 '15 at 22:58
  • 1
    maybe related? For the tests surely useful: http://stackoverflow.com/questions/12410961/c-connect-output-stream-to-input-stream – WorldSEnder Jun 30 '15 at 22:59
  • How would you unblock it? Are you writing into the `stringstream` from another thread? Would you like some fries with your race condition? – DanielKO Jul 01 '15 at 05:09
  • @WorldSEnder oh I wrote the streams in the wrong order. Fixed in the question. You're link is related. They propose std::stringstream though, which is unfortunately not helping me because of the blocking issue. The std::streambuf solution is quite complicated and I'm wondering whether there isn't a simpler way. – Heinzi Jul 01 '15 at 10:59
  • @DanielKO Yes, I would write to it from the other thread. It is only a race condition because it doesn't block. If it blocked until I wrote to it from the other end, then it wouldn't be a race condition. – Heinzi Jul 01 '15 at 11:00
  • 2
    I think you're going to need to write your own `streambuf` type. – Jonathan Wakely Jul 01 '15 at 11:06
  • 1
    That makes no sense. `stringstream` is not thread-safe, why would it be a synchronization primitive between threads? Nothing else in the standard library is "gratuitously synchronized". "Blocking" would not help with thread-safety, you would need a full mutex + condition variable combo to ensure synchronization. – DanielKO Jul 01 '15 at 12:38

0 Answers0