X-Git-Url: http://plrg.eecs.uci.edu/git/?a=blobdiff_plain;f=folly%2Ftest%2FSubprocessTest.cpp;h=b3a0cf6de7f14113739308c5c406756ca78766f4;hb=5d9dbb73c161a0c9d3f4b3d7275350c16969ae24;hp=0137bed2aef86189439a3cbf8781dbc40ab7beb0;hpb=675a137da29d6c33f903d1ea9f58e5c1778c3770;p=folly.git diff --git a/folly/test/SubprocessTest.cpp b/folly/test/SubprocessTest.cpp index 0137bed2..b3a0cf6d 100644 --- a/folly/test/SubprocessTest.cpp +++ b/folly/test/SubprocessTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2015 Facebook, Inc. + * Copyright 2017 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,13 +16,10 @@ #include -#include #include -#include #include #include -#include #include #include @@ -31,7 +28,12 @@ #include #include #include +#include #include +#include +#include + +FOLLY_GCC_DISABLE_WARNING(deprecated-declarations) using namespace folly; @@ -55,6 +57,34 @@ TEST(SimpleSubprocessTest, ExitsWithErrorChecked) { EXPECT_THROW(proc.waitChecked(), CalledProcessError); } +TEST(SimpleSubprocessTest, DefaultConstructibleProcessReturnCode) { + ProcessReturnCode retcode; + EXPECT_TRUE(retcode.notStarted()); +} + +TEST(SimpleSubprocessTest, MoveSubprocess) { + Subprocess old_proc(std::vector{ "/bin/true" }); + EXPECT_TRUE(old_proc.returnCode().running()); + auto new_proc = std::move(old_proc); + EXPECT_TRUE(old_proc.returnCode().notStarted()); + EXPECT_TRUE(new_proc.returnCode().running()); + EXPECT_EQ(0, new_proc.wait().exitStatus()); + // Now old_proc is destroyed, but we don't crash. +} + +TEST(SimpleSubprocessTest, DefaultConstructor) { + Subprocess proc; + EXPECT_TRUE(proc.returnCode().notStarted()); + + { + auto p1 = Subprocess(std::vector{"/bin/true"}); + proc = std::move(p1); + } + + EXPECT_TRUE(proc.returnCode().running()); + EXPECT_EQ(0, proc.wait().exitStatus()); +} + #define EXPECT_SPAWN_ERROR(err, errMsg, cmd, ...) \ do { \ try { \ @@ -169,7 +199,7 @@ TEST(SimpleSubprocessTest, FdLeakTest) { checkFdLeak([] { try { Subprocess proc(std::vector({"/no/such/file"}), - Subprocess::pipeStdout().stderr(Subprocess::PIPE)); + Subprocess::pipeStdout().stderrFd(Subprocess::PIPE)); ADD_FAILURE() << "expected an error when running /no/such/file"; } catch (const SubprocessSpawnError& ex) { EXPECT_EQ(ENOENT, ex.errnoValue()); @@ -179,15 +209,12 @@ TEST(SimpleSubprocessTest, FdLeakTest) { TEST(ParentDeathSubprocessTest, ParentDeathSignal) { // Find out where we are. - static constexpr size_t pathLength = 2048; - char buf[pathLength + 1]; - int r = readlink("/proc/self/exe", buf, pathLength); - CHECK_ERR(r); - buf[r] = '\0'; - - fs::path helper(buf); - helper.remove_filename(); - helper /= "subprocess_test_parent_death_helper"; + const auto basename = "subprocess_test_parent_death_helper"; + auto helper = fs::executable_path(); + helper.remove_filename() /= basename; + if (!fs::exists(helper)) { + helper = helper.parent_path().parent_path() / basename / basename; + } fs::path tempFile(fs::temp_directory_path() / fs::unique_path()); @@ -209,7 +236,7 @@ TEST(ParentDeathSubprocessTest, ParentDeathSignal) { TEST(PopenSubprocessTest, PopenRead) { Subprocess proc("ls /", Subprocess::pipeStdout()); int found = 0; - gen::byLine(File(proc.stdout())) | + gen::byLine(File(proc.stdoutFd())) | [&] (StringPiece line) { if (line == "etc" || line == "bin" || line == "usr") { ++found; @@ -219,6 +246,52 @@ TEST(PopenSubprocessTest, PopenRead) { proc.waitChecked(); } +// DANGER: This class runs after fork in a child processes. Be fast, the +// parent thread is waiting, but remember that other parent threads are +// running and may mutate your state. Avoid mutating any data belonging to +// the parent. Avoid interacting with non-POD data that originated in the +// parent. Avoid any libraries that may internally reference non-POD data. +// Especially beware parent mutexes -- for example, glog's LOG() uses one. +struct WriteFileAfterFork + : public Subprocess::DangerousPostForkPreExecCallback { + explicit WriteFileAfterFork(std::string filename) + : filename_(std::move(filename)) {} + virtual ~WriteFileAfterFork() {} + int operator()() override { + return writeFile(std::string("ok"), filename_.c_str()) ? 0 : errno; + } + const std::string filename_; +}; + +TEST(AfterForkCallbackSubprocessTest, TestAfterForkCallbackSuccess) { + test::ChangeToTempDir td; + // Trigger a file write from the child. + WriteFileAfterFork write_cob("good_file"); + Subprocess proc( + std::vector{"/bin/echo"}, + Subprocess::Options().dangerousPostForkPreExecCallback(&write_cob) + ); + // The file gets written immediately. + std::string s; + EXPECT_TRUE(readFile(write_cob.filename_.c_str(), s)); + EXPECT_EQ("ok", s); + proc.waitChecked(); +} + +TEST(AfterForkCallbackSubprocessTest, TestAfterForkCallbackError) { + test::ChangeToTempDir td; + // The child will try to write to a file, whose directory does not exist. + WriteFileAfterFork write_cob("bad/file"); + EXPECT_THROW( + Subprocess proc( + std::vector{"/bin/echo"}, + Subprocess::Options().dangerousPostForkPreExecCallback(&write_cob) + ), + SubprocessSpawnError + ); + EXPECT_FALSE(fs::exists(write_cob.filename_)); +} + TEST(CommunicateSubprocessTest, SimpleRead) { Subprocess proc(std::vector{ "/bin/echo", "-n", "foo", "bar"}, Subprocess::pipeStdout()); @@ -332,8 +405,6 @@ 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;