} // namespace detail
-CaptureFD::CaptureFD(int fd) : fd_(fd), readOffset_(0) {
+CaptureFD::CaptureFD(int fd, ChunkCob chunk_cob)
+ : chunkCob_(std::move(chunk_cob)), fd_(fd), readOffset_(0) {
oldFDCopy_ = dup(fd_);
PCHECK(oldFDCopy_ != -1) << "Could not copy FD " << fd_;
void CaptureFD::release() {
if (oldFDCopy_ != fd_) {
+ readIncremental(); // Feed chunkCob_
PCHECK(dup2(oldFDCopy_, fd_) != -1) << "Could not restore old FD "
<< oldFDCopy_ << " into " << fd_;
PCHECK(close(oldFDCopy_) != -1) << "Could not close " << oldFDCopy_;
release();
}
-std::string CaptureFD::read() {
+std::string CaptureFD::read() const {
std::string contents;
std::string filename = file_.path().string();
PCHECK(folly::readFile(filename.c_str(), contents));
auto bytes_read = folly::preadFull(f.fd(), buf.get(), size, readOffset_);
PCHECK(size == bytes_read);
readOffset_ += size;
+ chunkCob_(StringPiece(buf.get(), buf.get() + size));
return std::string(buf.get(), size);
}
/**
* Temporarily capture a file descriptor by redirecting it into a file.
- * You can consume its output either all-at-once or incrementally.
+ * You can consume its entire output thus far via read(), incrementally
+ * via readIncremental(), or via callback using chunk_cob.
* Great for testing logging (see also glog*Pattern()).
*/
class CaptureFD {
+private:
+ struct NoOpChunkCob { void operator()(StringPiece) {} };
public:
- explicit CaptureFD(int fd);
+ using ChunkCob = std::function<void(folly::StringPiece)>;
+
+ /**
+ * chunk_cob is is guaranteed to consume all the captured output. It is
+ * invoked on each readIncremental(), and also on FD release to capture
+ * as-yet unread lines. Chunks can be empty.
+ */
+ explicit CaptureFD(int fd, ChunkCob chunk_cob = NoOpChunkCob());
~CaptureFD();
/**
/**
* Reads the whole file into a string, but does not remove the redirect.
*/
- std::string read();
+ std::string read() const;
/**
* Read any bytes that were appended to the file since the last
std::string readIncremental();
private:
+ ChunkCob chunkCob_;
TemporaryFile file_;
int fd_;
}
}
+TEST(CaptureFD, ChunkCob) {
+ std::vector<std::string> chunks;
+ {
+ CaptureFD stderr(2, [&](StringPiece p) {
+ chunks.emplace_back(p.str());
+ switch (chunks.size()) {
+ case 1:
+ EXPECT_PCRE_MATCH(".*foo.*bar.*", p);
+ break;
+ case 2:
+ EXPECT_PCRE_MATCH("[^\n]*baz.*", p);
+ break;
+ default:
+ FAIL() << "Got too many chunks: " << chunks.size();
+ }
+ });
+ LOG(INFO) << "foo";
+ LOG(INFO) << "bar";
+ EXPECT_PCRE_MATCH(".*foo.*bar.*", stderr.read());
+ auto chunk = stderr.readIncremental();
+ EXPECT_EQ(chunks.at(0), chunk);
+ LOG(INFO) << "baz";
+ EXPECT_PCRE_MATCH(".*foo.*bar.*baz.*", stderr.read());
+ }
+ EXPECT_EQ(2, chunks.size());
+}
+
+
class EnvVarSaverTest : public testing::Test {};
TEST_F(EnvVarSaverTest, ExampleNew) {