Fix SimpleBarrier
[folly.git] / folly / test / SubprocessTest.cpp
1 /*
2  * Copyright 2016 Facebook, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include <folly/Subprocess.h>
18
19 #include <sys/types.h>
20
21 #include <boost/container/flat_set.hpp>
22 #include <glog/logging.h>
23
24 #include <folly/Exception.h>
25 #include <folly/Format.h>
26 #include <folly/FileUtil.h>
27 #include <folly/String.h>
28 #include <folly/gen/Base.h>
29 #include <folly/gen/File.h>
30 #include <folly/gen/String.h>
31 #include <folly/experimental/TestUtil.h>
32 #include <folly/experimental/io/FsUtil.h>
33 #include <folly/portability/GTest.h>
34 #include <folly/portability/Unistd.h>
35
36 FOLLY_GCC_DISABLE_WARNING(deprecated-declarations)
37
38 using namespace folly;
39
40 TEST(SimpleSubprocessTest, ExitsSuccessfully) {
41   Subprocess proc(std::vector<std::string>{ "/bin/true" });
42   EXPECT_EQ(0, proc.wait().exitStatus());
43 }
44
45 TEST(SimpleSubprocessTest, ExitsSuccessfullyChecked) {
46   Subprocess proc(std::vector<std::string>{ "/bin/true" });
47   proc.waitChecked();
48 }
49
50 TEST(SimpleSubprocessTest, ExitsWithError) {
51   Subprocess proc(std::vector<std::string>{ "/bin/false" });
52   EXPECT_EQ(1, proc.wait().exitStatus());
53 }
54
55 TEST(SimpleSubprocessTest, ExitsWithErrorChecked) {
56   Subprocess proc(std::vector<std::string>{ "/bin/false" });
57   EXPECT_THROW(proc.waitChecked(), CalledProcessError);
58 }
59
60 TEST(SimpleSubprocessTest, DefaultConstructibleProcessReturnCode) {
61   ProcessReturnCode retcode;
62   EXPECT_TRUE(retcode.notStarted());
63 }
64
65 TEST(SimpleSubprocessTest, MoveSubprocess) {
66   Subprocess old_proc(std::vector<std::string>{ "/bin/true" });
67   EXPECT_TRUE(old_proc.returnCode().running());
68   auto new_proc = std::move(old_proc);
69   EXPECT_TRUE(old_proc.returnCode().notStarted());
70   EXPECT_TRUE(new_proc.returnCode().running());
71   EXPECT_EQ(0, new_proc.wait().exitStatus());
72   // Now old_proc is destroyed, but we don't crash.
73 }
74
75 TEST(SimpleSubprocessTest, DefaultConstructor) {
76   Subprocess proc;
77   EXPECT_TRUE(proc.returnCode().notStarted());
78
79   {
80     auto p1 = Subprocess(std::vector<std::string>{"/bin/true"});
81     proc = std::move(p1);
82   }
83
84   EXPECT_TRUE(proc.returnCode().running());
85   EXPECT_EQ(0, proc.wait().exitStatus());
86 }
87
88 #define EXPECT_SPAWN_ERROR(err, errMsg, cmd, ...) \
89   do { \
90     try { \
91       Subprocess proc(std::vector<std::string>{ (cmd), ## __VA_ARGS__ }); \
92       ADD_FAILURE() << "expected an error when running " << (cmd); \
93     } catch (const SubprocessSpawnError& ex) { \
94       EXPECT_EQ((err), ex.errnoValue()); \
95       if (StringPiece(ex.what()).find(errMsg) == StringPiece::npos) { \
96         ADD_FAILURE() << "failed to find \"" << (errMsg) << \
97           "\" in exception: \"" << ex.what() << "\""; \
98       } \
99     } \
100   } while (0)
101
102 TEST(SimpleSubprocessTest, ExecFails) {
103   EXPECT_SPAWN_ERROR(ENOENT, "failed to execute /no/such/file:",
104                      "/no/such/file");
105   EXPECT_SPAWN_ERROR(EACCES, "failed to execute /etc/passwd:",
106                      "/etc/passwd");
107   EXPECT_SPAWN_ERROR(ENOTDIR, "failed to execute /etc/passwd/not/a/file:",
108                      "/etc/passwd/not/a/file");
109 }
110
111 TEST(SimpleSubprocessTest, ShellExitsSuccesssfully) {
112   Subprocess proc("true");
113   EXPECT_EQ(0, proc.wait().exitStatus());
114 }
115
116 TEST(SimpleSubprocessTest, ShellExitsWithError) {
117   Subprocess proc("false");
118   EXPECT_EQ(1, proc.wait().exitStatus());
119 }
120
121 TEST(SimpleSubprocessTest, ChangeChildDirectorySuccessfully) {
122   // The filesystem root normally lacks a 'true' binary
123   EXPECT_EQ(0, chdir("/"));
124   EXPECT_SPAWN_ERROR(ENOENT, "failed to execute ./true", "./true");
125   // The child can fix that by moving to /bin before exec().
126   Subprocess proc("./true", Subprocess::Options().chdir("/bin"));
127   EXPECT_EQ(0, proc.wait().exitStatus());
128 }
129
130 TEST(SimpleSubprocessTest, ChangeChildDirectoryWithError) {
131   try {
132     Subprocess proc(
133       std::vector<std::string>{"/bin/true"},
134       Subprocess::Options().chdir("/usually/this/is/not/a/valid/directory/")
135     );
136     ADD_FAILURE() << "expected to fail when changing the child's directory";
137   } catch (const SubprocessSpawnError& ex) {
138     EXPECT_EQ(ENOENT, ex.errnoValue());
139     const std::string expectedError =
140       "error preparing to execute /bin/true: No such file or directory";
141     if (StringPiece(ex.what()).find(expectedError) == StringPiece::npos) {
142       ADD_FAILURE() << "failed to find \"" << expectedError <<
143         "\" in exception: \"" << ex.what() << "\"";
144     }
145   }
146 }
147
148 namespace {
149 boost::container::flat_set<int> getOpenFds() {
150   auto pid = getpid();
151   auto dirname = to<std::string>("/proc/", pid, "/fd");
152
153   boost::container::flat_set<int> fds;
154   for (fs::directory_iterator it(dirname);
155        it != fs::directory_iterator();
156        ++it) {
157     int fd = to<int>(it->path().filename().native());
158     fds.insert(fd);
159   }
160   return fds;
161 }
162
163 template<class Runnable>
164 void checkFdLeak(const Runnable& r) {
165   // Get the currently open fds.  Check that they are the same both before and
166   // after calling the specified function.  We read the open fds from /proc.
167   // (If we wanted to work even on systems that don't have /proc, we could
168   // perhaps create and immediately close a socket both before and after
169   // running the function, and make sure we got the same fd number both times.)
170   auto fdsBefore = getOpenFds();
171   r();
172   auto fdsAfter = getOpenFds();
173   EXPECT_EQ(fdsAfter.size(), fdsBefore.size());
174 }
175 }
176
177 // Make sure Subprocess doesn't leak any file descriptors
178 TEST(SimpleSubprocessTest, FdLeakTest) {
179   // Normal execution
180   checkFdLeak([] {
181     Subprocess proc("true");
182     EXPECT_EQ(0, proc.wait().exitStatus());
183   });
184   // Normal execution with pipes
185   checkFdLeak([] {
186     Subprocess proc("echo foo; echo bar >&2",
187                     Subprocess::pipeStdout() | Subprocess::pipeStderr());
188     auto p = proc.communicate();
189     EXPECT_EQ("foo\n", p.first);
190     EXPECT_EQ("bar\n", p.second);
191     proc.waitChecked();
192   });
193
194   // Test where the exec call fails()
195   checkFdLeak([] {
196     EXPECT_SPAWN_ERROR(ENOENT, "failed to execute", "/no/such/file");
197   });
198   // Test where the exec call fails() with pipes
199   checkFdLeak([] {
200     try {
201       Subprocess proc(std::vector<std::string>({"/no/such/file"}),
202                       Subprocess::pipeStdout().stderrFd(Subprocess::PIPE));
203       ADD_FAILURE() << "expected an error when running /no/such/file";
204     } catch (const SubprocessSpawnError& ex) {
205       EXPECT_EQ(ENOENT, ex.errnoValue());
206     }
207   });
208 }
209
210 TEST(ParentDeathSubprocessTest, ParentDeathSignal) {
211   // Find out where we are.
212   const auto basename = "subprocess_test_parent_death_helper";
213   auto helper = fs::executable_path();
214   helper.remove_filename() /= basename;
215   if (!fs::exists(helper)) {
216     helper = helper.parent_path().parent_path() / basename / basename;
217   }
218
219   fs::path tempFile(fs::temp_directory_path() / fs::unique_path());
220
221   std::vector<std::string> args {helper.string(), tempFile.string()};
222   Subprocess proc(args);
223   // The helper gets killed by its child, see details in
224   // SubprocessTestParentDeathHelper.cpp
225   ASSERT_EQ(SIGKILL, proc.wait().killSignal());
226
227   // Now wait for the file to be created, see details in
228   // SubprocessTestParentDeathHelper.cpp
229   while (!fs::exists(tempFile)) {
230     usleep(20000);  // 20ms
231   }
232
233   fs::remove(tempFile);
234 }
235
236 TEST(PopenSubprocessTest, PopenRead) {
237   Subprocess proc("ls /", Subprocess::pipeStdout());
238   int found = 0;
239   gen::byLine(File(proc.stdoutFd())) |
240     [&] (StringPiece line) {
241       if (line == "etc" || line == "bin" || line == "usr") {
242         ++found;
243       }
244     };
245   EXPECT_EQ(3, found);
246   proc.waitChecked();
247 }
248
249 // DANGER: This class runs after fork in a child processes. Be fast, the
250 // parent thread is waiting, but remember that other parent threads are
251 // running and may mutate your state.  Avoid mutating any data belonging to
252 // the parent.  Avoid interacting with non-POD data that originated in the
253 // parent.  Avoid any libraries that may internally reference non-POD data.
254 // Especially beware parent mutexes -- for example, glog's LOG() uses one.
255 struct WriteFileAfterFork
256     : public Subprocess::DangerousPostForkPreExecCallback {
257   explicit WriteFileAfterFork(std::string filename)
258     : filename_(std::move(filename)) {}
259   virtual ~WriteFileAfterFork() {}
260   int operator()() override {
261     return writeFile(std::string("ok"), filename_.c_str()) ? 0 : errno;
262   }
263   const std::string filename_;
264 };
265
266 TEST(AfterForkCallbackSubprocessTest, TestAfterForkCallbackSuccess) {
267   test::ChangeToTempDir td;
268   // Trigger a file write from the child.
269   WriteFileAfterFork write_cob("good_file");
270   Subprocess proc(
271     std::vector<std::string>{"/bin/echo"},
272     Subprocess::Options().dangerousPostForkPreExecCallback(&write_cob)
273   );
274   // The file gets written immediately.
275   std::string s;
276   EXPECT_TRUE(readFile(write_cob.filename_.c_str(), s));
277   EXPECT_EQ("ok", s);
278   proc.waitChecked();
279 }
280
281 TEST(AfterForkCallbackSubprocessTest, TestAfterForkCallbackError) {
282   test::ChangeToTempDir td;
283   // The child will try to write to a file, whose directory does not exist.
284   WriteFileAfterFork write_cob("bad/file");
285   EXPECT_THROW(
286     Subprocess proc(
287       std::vector<std::string>{"/bin/echo"},
288       Subprocess::Options().dangerousPostForkPreExecCallback(&write_cob)
289     ),
290     SubprocessSpawnError
291   );
292   EXPECT_FALSE(fs::exists(write_cob.filename_));
293 }
294
295 TEST(CommunicateSubprocessTest, SimpleRead) {
296   Subprocess proc(std::vector<std::string>{ "/bin/echo", "-n", "foo", "bar"},
297                   Subprocess::pipeStdout());
298   auto p = proc.communicate();
299   EXPECT_EQ("foo bar", p.first);
300   proc.waitChecked();
301 }
302
303 TEST(CommunicateSubprocessTest, BigWrite) {
304   const int numLines = 1 << 20;
305   std::string line("hello\n");
306   std::string data;
307   data.reserve(numLines * line.size());
308   for (int i = 0; i < numLines; ++i) {
309     data.append(line);
310   }
311
312   Subprocess proc("wc -l", Subprocess::pipeStdin() | Subprocess::pipeStdout());
313   auto p = proc.communicate(data);
314   EXPECT_EQ(folly::format("{}\n", numLines).str(), p.first);
315   proc.waitChecked();
316 }
317
318 TEST(CommunicateSubprocessTest, Duplex) {
319   // Take 10MB of data and pass them through a filter.
320   // One line, as tr is line-buffered
321   const int bytes = 10 << 20;
322   std::string line(bytes, 'x');
323
324   Subprocess proc("tr a-z A-Z",
325                   Subprocess::pipeStdin() | Subprocess::pipeStdout());
326   auto p = proc.communicate(line);
327   EXPECT_EQ(bytes, p.first.size());
328   EXPECT_EQ(std::string::npos, p.first.find_first_not_of('X'));
329   proc.waitChecked();
330 }
331
332 TEST(CommunicateSubprocessTest, ProcessGroupLeader) {
333   const auto testIsLeader = "test $(cut -d ' ' -f 5 /proc/$$/stat) = $$";
334   Subprocess nonLeader(testIsLeader);
335   EXPECT_THROW(nonLeader.waitChecked(), CalledProcessError);
336   Subprocess leader(testIsLeader, Subprocess::Options().processGroupLeader());
337   leader.waitChecked();
338 }
339
340 TEST(CommunicateSubprocessTest, Duplex2) {
341   checkFdLeak([] {
342     // Pipe 200,000 lines through sed
343     const size_t numCopies = 100000;
344     auto iobuf = IOBuf::copyBuffer("this is a test\nanother line\n");
345     IOBufQueue input;
346     for (size_t n = 0; n < numCopies; ++n) {
347       input.append(iobuf->clone());
348     }
349
350     std::vector<std::string> cmd({
351       "sed", "-u",
352       "-e", "s/a test/a successful test/",
353       "-e", "/^another line/w/dev/stderr",
354     });
355     auto options = Subprocess::pipeStdin().pipeStdout().pipeStderr().usePath();
356     Subprocess proc(cmd, options);
357     auto out = proc.communicateIOBuf(std::move(input));
358     proc.waitChecked();
359
360     // Convert stdout and stderr to strings so we can call split() on them.
361     fbstring stdoutStr;
362     if (out.first.front()) {
363       stdoutStr = out.first.move()->moveToFbString();
364     }
365     fbstring stderrStr;
366     if (out.second.front()) {
367       stderrStr = out.second.move()->moveToFbString();
368     }
369
370     // stdout should be a copy of stdin, with "a test" replaced by
371     // "a successful test"
372     std::vector<StringPiece> stdoutLines;
373     split('\n', stdoutStr, stdoutLines);
374     EXPECT_EQ(numCopies * 2 + 1, stdoutLines.size());
375     // Strip off the trailing empty line
376     if (!stdoutLines.empty()) {
377       EXPECT_EQ("", stdoutLines.back());
378       stdoutLines.pop_back();
379     }
380     size_t linenum = 0;
381     for (const auto& line : stdoutLines) {
382       if ((linenum & 1) == 0) {
383         EXPECT_EQ("this is a successful test", line);
384       } else {
385         EXPECT_EQ("another line", line);
386       }
387       ++linenum;
388     }
389
390     // stderr should only contain the lines containing "another line"
391     std::vector<StringPiece> stderrLines;
392     split('\n', stderrStr, stderrLines);
393     EXPECT_EQ(numCopies + 1, stderrLines.size());
394     // Strip off the trailing empty line
395     if (!stderrLines.empty()) {
396       EXPECT_EQ("", stderrLines.back());
397       stderrLines.pop_back();
398     }
399     for (const auto& line : stderrLines) {
400       EXPECT_EQ("another line", line);
401     }
402   });
403 }
404
405 namespace {
406
407 bool readToString(int fd, std::string& buf, size_t maxSize) {
408   buf.resize(maxSize);
409   char* dest = &buf.front();
410   size_t remaining = maxSize;
411
412   ssize_t n = -1;
413   while (remaining) {
414     n = ::read(fd, dest, remaining);
415     if (n == -1) {
416       if (errno == EINTR) {
417         continue;
418       }
419       if (errno == EAGAIN) {
420         break;
421       }
422       PCHECK("read failed");
423     } else if (n == 0) {
424       break;
425     }
426     dest += n;
427     remaining -= n;
428   }
429
430   buf.resize(dest - buf.data());
431   return (n == 0);
432 }
433
434 }  // namespace
435
436 TEST(CommunicateSubprocessTest, Chatty) {
437   checkFdLeak([] {
438     const int lineCount = 1000;
439
440     int wcount = 0;
441     int rcount = 0;
442
443     auto options = Subprocess::pipeStdin().pipeStdout().pipeStderr().usePath();
444     std::vector<std::string> cmd {
445       "sed",
446       "-u",
447       "-e",
448       "s/a test/a successful test/",
449     };
450
451     Subprocess proc(cmd, options);
452
453     auto writeCallback = [&] (int pfd, int cfd) -> bool {
454       EXPECT_EQ(0, cfd);  // child stdin
455       EXPECT_EQ(rcount, wcount);  // chatty, one read for every write
456
457       auto msg = folly::to<std::string>("a test ", wcount, "\n");
458
459       // Not entirely kosher, we should handle partial writes, but this is
460       // fine for writes <= PIPE_BUF
461       EXPECT_EQ(msg.size(), writeFull(pfd, msg.data(), msg.size()));
462
463       ++wcount;
464       proc.enableNotifications(0, false);
465
466       return (wcount == lineCount);
467     };
468
469     bool eofSeen = false;
470
471     auto readCallback = [&] (int pfd, int cfd) -> bool {
472       std::string lineBuf;
473
474       if (cfd != 1) {
475         EXPECT_EQ(2, cfd);
476         EXPECT_TRUE(readToString(pfd, lineBuf, 1));
477         EXPECT_EQ(0, lineBuf.size());
478         return true;
479       }
480
481       EXPECT_FALSE(eofSeen);
482
483       std::string expected;
484
485       if (rcount < lineCount) {
486         expected = folly::to<std::string>("a successful test ", rcount++, "\n");
487       }
488
489       EXPECT_EQ(wcount, rcount);
490
491       // Not entirely kosher, we should handle partial reads, but this is
492       // fine for reads <= PIPE_BUF
493       bool atEof = readToString(pfd, lineBuf, expected.size() + 1);
494       if (atEof) {
495         // EOF only expected after we finished reading
496         EXPECT_EQ(lineCount, rcount);
497         eofSeen = true;
498       }
499
500       EXPECT_EQ(expected, lineBuf);
501
502       if (wcount != lineCount) {  // still more to write...
503         proc.enableNotifications(0, true);
504       }
505
506       return eofSeen;
507     };
508
509     proc.communicate(readCallback, writeCallback);
510
511     EXPECT_EQ(lineCount, wcount);
512     EXPECT_EQ(lineCount, rcount);
513     EXPECT_TRUE(eofSeen);
514
515     EXPECT_EQ(0, proc.wait().exitStatus());
516   });
517 }
518
519 TEST(CommunicateSubprocessTest, TakeOwnershipOfPipes) {
520   std::vector<Subprocess::ChildPipe> pipes;
521   {
522     Subprocess proc(
523       "echo $'oh\\nmy\\ncat' | wc -l &", Subprocess::pipeStdout()
524     );
525     pipes = proc.takeOwnershipOfPipes();
526     proc.waitChecked();
527   }
528   EXPECT_EQ(1, pipes.size());
529   EXPECT_EQ(1, pipes[0].childFd);
530
531   char buf[10];
532   EXPECT_EQ(2, readFull(pipes[0].pipe.fd(), buf, 10));
533   buf[2] = 0;
534   EXPECT_EQ("3\n", std::string(buf));
535 }