exception wrapper
[folly.git] / folly / test / SubprocessTest.cpp
index f17f3a51da37dda83886694e0777058542bb0c50..098b66b3aaf4b721f8caaa61e365a22a3ace2a58 100644 (file)
@@ -26,6 +26,7 @@
 
 #include "folly/Exception.h"
 #include "folly/Format.h"
+#include "folly/FileUtil.h"
 #include "folly/String.h"
 #include "folly/gen/Base.h"
 #include "folly/gen/File.h"
@@ -87,6 +88,33 @@ TEST(SimpleSubprocessTest, ShellExitsWithError) {
   EXPECT_EQ(1, proc.wait().exitStatus());
 }
 
+TEST(SimpleSubprocessTest, ChangeChildDirectorySuccessfully) {
+  // The filesystem root normally lacks a 'true' binary
+  EXPECT_EQ(0, chdir("/"));
+  EXPECT_SPAWN_ERROR(ENOENT, "failed to execute ./true", "./true");
+  // The child can fix that by moving to /bin before exec().
+  Subprocess proc("./true", Subprocess::Options().chdir("/bin"));
+  EXPECT_EQ(0, proc.wait().exitStatus());
+}
+
+TEST(SimpleSubprocessTest, ChangeChildDirectoryWithError) {
+  try {
+    Subprocess proc(
+      std::vector<std::string>{"/bin/true"},
+      Subprocess::Options().chdir("/usually/this/is/not/a/valid/directory/")
+    );
+    ADD_FAILURE() << "expected to fail when changing the child's directory";
+  } catch (const SubprocessSpawnError& ex) {
+    EXPECT_EQ(ENOENT, ex.errnoValue());
+    const std::string expectedError =
+      "error preparing to execute /bin/true: No such file or directory";
+    if (StringPiece(ex.what()).find(expectedError) == StringPiece::npos) {
+      ADD_FAILURE() << "failed to find \"" << expectedError <<
+        "\" in exception: \"" << ex.what() << "\"";
+    }
+  }
+}
+
 namespace {
 boost::container::flat_set<int> getOpenFds() {
   auto pid = getpid();
@@ -292,3 +320,119 @@ TEST(CommunicateSubprocessTest, Duplex2) {
     }
   });
 }
+
+namespace {
+
+bool readToString(int fd, std::string& buf, size_t maxSize) {
+  size_t bytesRead = 0;
+
+  buf.resize(maxSize);
+  char* dest = &buf.front();
+  size_t remaining = maxSize;
+
+  ssize_t n = -1;
+  while (remaining) {
+    n = ::read(fd, dest, remaining);
+    if (n == -1) {
+      if (errno == EINTR) {
+        continue;
+      }
+      if (errno == EAGAIN) {
+        break;
+      }
+      PCHECK("read failed");
+    } else if (n == 0) {
+      break;
+    }
+    dest += n;
+    remaining -= n;
+  }
+
+  buf.resize(dest - buf.data());
+  return (n == 0);
+}
+
+}  // namespace
+
+TEST(CommunicateSubprocessTest, Chatty) {
+  checkFdLeak([] {
+    const int lineCount = 1000;
+
+    int wcount = 0;
+    int rcount = 0;
+
+    auto options = Subprocess::pipeStdin().pipeStdout().pipeStderr().usePath();
+    std::vector<std::string> cmd {
+      "sed",
+      "-u",
+      "-e",
+      "s/a test/a successful test/",
+    };
+
+    Subprocess proc(cmd, options);
+
+    auto writeCallback = [&] (int pfd, int cfd) -> bool {
+      EXPECT_EQ(0, cfd);  // child stdin
+      EXPECT_EQ(rcount, wcount);  // chatty, one read for every write
+
+      auto msg = folly::to<std::string>("a test ", wcount, "\n");
+
+      // Not entirely kosher, we should handle partial writes, but this is
+      // fine for writes <= PIPE_BUF
+      EXPECT_EQ(msg.size(), writeFull(pfd, msg.data(), msg.size()));
+
+      ++wcount;
+      proc.enableNotifications(0, false);
+
+      return (wcount == lineCount);
+    };
+
+    bool eofSeen = false;
+
+    auto readCallback = [&] (int pfd, int cfd) -> bool {
+      std::string lineBuf;
+
+      if (cfd != 1) {
+        EXPECT_EQ(2, cfd);
+        EXPECT_TRUE(readToString(pfd, lineBuf, 1));
+        EXPECT_EQ(0, lineBuf.size());
+        return true;
+      }
+
+      EXPECT_FALSE(eofSeen);
+
+      std::string expected;
+
+      if (rcount < lineCount) {
+        expected = folly::to<std::string>("a successful test ", rcount++, "\n");
+      }
+
+      EXPECT_EQ(wcount, rcount);
+
+      // Not entirely kosher, we should handle partial reads, but this is
+      // fine for reads <= PIPE_BUF
+      bool atEof = readToString(pfd, lineBuf, expected.size() + 1);
+      if (atEof) {
+        // EOF only expected after we finished reading
+        EXPECT_EQ(lineCount, rcount);
+        eofSeen = true;
+      }
+
+      EXPECT_EQ(expected, lineBuf);
+
+      if (wcount != lineCount) {  // still more to write...
+        proc.enableNotifications(0, true);
+      }
+
+      return eofSeen;
+    };
+
+    proc.communicate(readCallback, writeCallback);
+
+    EXPECT_EQ(lineCount, wcount);
+    EXPECT_EQ(lineCount, rcount);
+    EXPECT_TRUE(eofSeen);
+
+    EXPECT_EQ(0, proc.wait().exitStatus());
+  });
+}