mardi 30 juin 2015

Make std::getline() on std::stringstream block on eof (or find alternative stream class)

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) {
    _istr << question << "\n";
    string answer;
    std::getline(_ostr, 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.

Aucun commentaire:

Enregistrer un commentaire