Commit 338a2603 authored by Alexey Spiridonov's avatar Alexey Spiridonov Committed by facebook-github-bot-1

Add "consume all captured output" callback to CaptureFD

Summary: I noticed myself trying to fake this kind of callback for a log-based test I was writing. It seems much nicer to add the callback to `CaptureFD` than roll ugly wrappers around it to do the same thing.

Reviewed By: @yfeldblum

Differential Revision: D2506106
parent 62504e55
...@@ -142,7 +142,8 @@ bool hasNoPCREPatternMatch(StringPiece pattern, StringPiece target) { ...@@ -142,7 +142,8 @@ bool hasNoPCREPatternMatch(StringPiece pattern, StringPiece target) {
} // namespace detail } // 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_); oldFDCopy_ = dup(fd_);
PCHECK(oldFDCopy_ != -1) << "Could not copy FD " << fd_; PCHECK(oldFDCopy_ != -1) << "Could not copy FD " << fd_;
...@@ -154,6 +155,7 @@ CaptureFD::CaptureFD(int fd) : fd_(fd), readOffset_(0) { ...@@ -154,6 +155,7 @@ CaptureFD::CaptureFD(int fd) : fd_(fd), readOffset_(0) {
void CaptureFD::release() { void CaptureFD::release() {
if (oldFDCopy_ != fd_) { if (oldFDCopy_ != fd_) {
readIncremental(); // Feed chunkCob_
PCHECK(dup2(oldFDCopy_, fd_) != -1) << "Could not restore old FD " PCHECK(dup2(oldFDCopy_, fd_) != -1) << "Could not restore old FD "
<< oldFDCopy_ << " into " << fd_; << oldFDCopy_ << " into " << fd_;
PCHECK(close(oldFDCopy_) != -1) << "Could not close " << oldFDCopy_; PCHECK(close(oldFDCopy_) != -1) << "Could not close " << oldFDCopy_;
...@@ -165,7 +167,7 @@ CaptureFD::~CaptureFD() { ...@@ -165,7 +167,7 @@ CaptureFD::~CaptureFD() {
release(); release();
} }
std::string CaptureFD::read() { std::string CaptureFD::read() const {
std::string contents; std::string contents;
std::string filename = file_.path().string(); std::string filename = file_.path().string();
PCHECK(folly::readFile(filename.c_str(), contents)); PCHECK(folly::readFile(filename.c_str(), contents));
...@@ -181,6 +183,7 @@ std::string CaptureFD::readIncremental() { ...@@ -181,6 +183,7 @@ std::string CaptureFD::readIncremental() {
auto bytes_read = folly::preadFull(f.fd(), buf.get(), size, readOffset_); auto bytes_read = folly::preadFull(f.fd(), buf.get(), size, readOffset_);
PCHECK(size == bytes_read); PCHECK(size == bytes_read);
readOffset_ += size; readOffset_ += size;
chunkCob_(StringPiece(buf.get(), buf.get() + size));
return std::string(buf.get(), size); return std::string(buf.get(), size);
} }
......
...@@ -157,12 +157,22 @@ inline std::string glogErrOrWarnPattern() { return ".*(^|\n)[EW][0-9].*"; } ...@@ -157,12 +157,22 @@ inline std::string glogErrOrWarnPattern() { return ".*(^|\n)[EW][0-9].*"; }
/** /**
* Temporarily capture a file descriptor by redirecting it into a file. * 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()). * Great for testing logging (see also glog*Pattern()).
*/ */
class CaptureFD { class CaptureFD {
private:
struct NoOpChunkCob { void operator()(StringPiece) {} };
public: 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(); ~CaptureFD();
/** /**
...@@ -175,7 +185,7 @@ public: ...@@ -175,7 +185,7 @@ public:
/** /**
* Reads the whole file into a string, but does not remove the redirect. * 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 * Read any bytes that were appended to the file since the last
...@@ -184,6 +194,7 @@ public: ...@@ -184,6 +194,7 @@ public:
std::string readIncremental(); std::string readIncremental();
private: private:
ChunkCob chunkCob_;
TemporaryFile file_; TemporaryFile file_;
int fd_; int fd_;
......
...@@ -136,6 +136,34 @@ TEST(CaptureFD, GlogPatterns) { ...@@ -136,6 +136,34 @@ TEST(CaptureFD, GlogPatterns) {
} }
} }
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 {}; class EnvVarSaverTest : public testing::Test {};
TEST_F(EnvVarSaverTest, ExampleNew) { TEST_F(EnvVarSaverTest, ExampleNew) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment