proxygen
SubprocessTest.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2012-present 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 #include <chrono>
21 
22 #include <boost/container/flat_set.hpp>
23 #include <glog/logging.h>
24 
25 #include <folly/Exception.h>
26 #include <folly/FileUtil.h>
27 #include <folly/Format.h>
28 #include <folly/String.h>
31 #include <folly/gen/Base.h>
32 #include <folly/gen/File.h>
33 #include <folly/gen/String.h>
36 
37 FOLLY_GNU_DISABLE_WARNING("-Wdeprecated-declarations")
38 
39 using namespace folly;
40 using namespace std::chrono_literals;
41 
42 TEST(SimpleSubprocessTest, ExitsSuccessfully) {
43  Subprocess proc(std::vector<std::string>{"/bin/true"});
44  EXPECT_EQ(0, proc.wait().exitStatus());
45 }
46 
47 TEST(SimpleSubprocessTest, ExitsSuccessfullyChecked) {
48  Subprocess proc(std::vector<std::string>{"/bin/true"});
49  proc.waitChecked();
50 }
51 
52 TEST(SimpleSubprocessTest, CloneFlagsWithVfork) {
53  Subprocess proc(
54  std::vector<std::string>{"/bin/true"},
55  Subprocess::Options().useCloneWithFlags(SIGCHLD | CLONE_VFORK));
56  EXPECT_EQ(0, proc.wait().exitStatus());
57 }
58 
59 TEST(SimpleSubprocessTest, CloneFlagsWithFork) {
60  Subprocess proc(
61  std::vector<std::string>{"/bin/true"},
62  Subprocess::Options().useCloneWithFlags(SIGCHLD));
63  EXPECT_EQ(0, proc.wait().exitStatus());
64 }
65 
66 TEST(SimpleSubprocessTest, CloneFlagsSubprocessCtorExitsAfterExec) {
67  Subprocess proc(
68  std::vector<std::string>{"/bin/sleep", "3600"},
69  Subprocess::Options().useCloneWithFlags(SIGCHLD));
70  checkUnixError(::kill(proc.pid(), SIGKILL), "kill");
71  auto retCode = proc.wait();
72  EXPECT_TRUE(retCode.killed());
73 }
74 
75 TEST(SimpleSubprocessTest, ExitsWithError) {
76  Subprocess proc(std::vector<std::string>{"/bin/false"});
77  EXPECT_EQ(1, proc.wait().exitStatus());
78 }
79 
80 TEST(SimpleSubprocessTest, ExitsWithErrorChecked) {
81  Subprocess proc(std::vector<std::string>{"/bin/false"});
82  EXPECT_THROW(proc.waitChecked(), CalledProcessError);
83 }
84 
85 TEST(SimpleSubprocessTest, DefaultConstructibleProcessReturnCode) {
86  ProcessReturnCode retcode;
87  EXPECT_TRUE(retcode.notStarted());
88 }
89 
90 TEST(SimpleSubprocessTest, MoveSubprocess) {
91  Subprocess old_proc(std::vector<std::string>{"/bin/true"});
92  EXPECT_TRUE(old_proc.returnCode().running());
93  auto new_proc = std::move(old_proc);
94  EXPECT_TRUE(old_proc.returnCode().notStarted());
95  EXPECT_TRUE(new_proc.returnCode().running());
96  EXPECT_EQ(0, new_proc.wait().exitStatus());
97  // Now old_proc is destroyed, but we don't crash.
98 }
99 
100 TEST(SimpleSubprocessTest, DefaultConstructor) {
101  Subprocess proc;
103 
104  {
105  auto p1 = Subprocess(std::vector<std::string>{"/bin/true"});
106  proc = std::move(p1);
107  }
108 
109  EXPECT_TRUE(proc.returnCode().running());
110  EXPECT_EQ(0, proc.wait().exitStatus());
111 }
112 
113 #define EXPECT_SPAWN_OPT_ERROR(err, errMsg, options, cmd, ...) \
114  do { \
115  try { \
116  Subprocess proc( \
117  std::vector<std::string>{(cmd), ##__VA_ARGS__}, (options)); \
118  ADD_FAILURE() << "expected an error when running " << (cmd); \
119  } catch (const SubprocessSpawnError& ex) { \
120  EXPECT_EQ((err), ex.errnoValue()); \
121  if (StringPiece(ex.what()).find(errMsg) == StringPiece::npos) { \
122  ADD_FAILURE() << "failed to find \"" << (errMsg) \
123  << "\" in exception: \"" << ex.what() << "\""; \
124  } \
125  } \
126  } while (0)
127 
128 #define EXPECT_SPAWN_ERROR(err, errMsg, cmd, ...) \
129  EXPECT_SPAWN_OPT_ERROR(err, errMsg, Subprocess::Options(), cmd, ##__VA_ARGS__)
130 
131 TEST(SimpleSubprocessTest, ExecFails) {
133  ENOENT, "failed to execute /no/such/file:", "/no/such/file");
134  EXPECT_SPAWN_ERROR(EACCES, "failed to execute /etc/passwd:", "/etc/passwd");
136  ENOTDIR,
137  "failed to execute /etc/passwd/not/a/file:",
138  "/etc/passwd/not/a/file");
139 }
140 
141 TEST(SimpleSubprocessTest, ShellExitsSuccesssfully) {
142  Subprocess proc("true");
143  EXPECT_EQ(0, proc.wait().exitStatus());
144 }
145 
146 TEST(SimpleSubprocessTest, ShellExitsWithError) {
147  Subprocess proc("false");
148  EXPECT_EQ(1, proc.wait().exitStatus());
149 }
150 
151 TEST(SimpleSubprocessTest, ChangeChildDirectorySuccessfully) {
152  // The filesystem root normally lacks a 'true' binary
153  EXPECT_EQ(0, chdir("/"));
154  EXPECT_SPAWN_ERROR(ENOENT, "failed to execute ./true", "./true");
155  // The child can fix that by moving to /bin before exec().
156  Subprocess proc("./true", Subprocess::Options().chdir("/bin"));
157  EXPECT_EQ(0, proc.wait().exitStatus());
158 }
159 
160 TEST(SimpleSubprocessTest, ChangeChildDirectoryWithError) {
161  try {
162  Subprocess proc(
163  std::vector<std::string>{"/bin/true"},
164  Subprocess::Options().chdir("/usually/this/is/not/a/valid/directory/"));
165  ADD_FAILURE() << "expected to fail when changing the child's directory";
166  } catch (const SubprocessSpawnError& ex) {
167  EXPECT_EQ(ENOENT, ex.errnoValue());
168  const std::string expectedError =
169  "error preparing to execute /bin/true: No such file or directory";
170  if (StringPiece(ex.what()).find(expectedError) == StringPiece::npos) {
171  ADD_FAILURE() << "failed to find \"" << expectedError
172  << "\" in exception: \"" << ex.what() << "\"";
173  }
174  }
175 }
176 
177 namespace {
178 boost::container::flat_set<int> getOpenFds() {
179  auto pid = getpid();
180  auto dirname = to<std::string>("/proc/", pid, "/fd");
181 
182  boost::container::flat_set<int> fds;
183  for (fs::directory_iterator it(dirname); it != fs::directory_iterator();
184  ++it) {
185  int fd = to<int>(it->path().filename().native());
186  fds.insert(fd);
187  }
188  return fds;
189 }
190 
191 template <class Runnable>
192 void checkFdLeak(const Runnable& r) {
193  // Get the currently open fds. Check that they are the same both before and
194  // after calling the specified function. We read the open fds from /proc.
195  // (If we wanted to work even on systems that don't have /proc, we could
196  // perhaps create and immediately close a socket both before and after
197  // running the function, and make sure we got the same fd number both times.)
198  auto fdsBefore = getOpenFds();
199  r();
200  auto fdsAfter = getOpenFds();
201  EXPECT_EQ(fdsAfter.size(), fdsBefore.size());
202 }
203 } // namespace
204 
205 // Make sure Subprocess doesn't leak any file descriptors
206 TEST(SimpleSubprocessTest, FdLeakTest) {
207  // Normal execution
208  checkFdLeak([] {
209  Subprocess proc("true");
210  EXPECT_EQ(0, proc.wait().exitStatus());
211  });
212  // Normal execution with pipes
213  checkFdLeak([] {
214  Subprocess proc(
215  "echo foo; echo bar >&2",
216  Subprocess::Options().pipeStdout().pipeStderr());
217  auto p = proc.communicate();
218  EXPECT_EQ("foo\n", p.first);
219  EXPECT_EQ("bar\n", p.second);
220  proc.waitChecked();
221  });
222 
223  // Test where the exec call fails()
224  checkFdLeak(
225  [] { EXPECT_SPAWN_ERROR(ENOENT, "failed to execute", "/no/such/file"); });
226  // Test where the exec call fails() with pipes
227  checkFdLeak([] {
228  try {
229  Subprocess proc(
230  std::vector<std::string>({"/no/such/file"}),
231  Subprocess::Options().pipeStdout().stderrFd(Subprocess::PIPE));
232  ADD_FAILURE() << "expected an error when running /no/such/file";
233  } catch (const SubprocessSpawnError& ex) {
234  EXPECT_EQ(ENOENT, ex.errnoValue());
235  }
236  });
237 }
238 
239 TEST(SimpleSubprocessTest, Detach) {
241  {
242  Subprocess proc(
243  std::vector<std::string>{"/bin/sleep", "10"},
245  EXPECT_EQ(-1, proc.pid());
246  }
248  // We should be able to create and destroy the Subprocess object quickly,
249  // without waiting for the sleep process to finish. This should usually
250  // happen in a matter of milliseconds, but we allow up to 5 seconds just to
251  // provide lots of leeway on heavily loaded continuous build machines.
252  EXPECT_LE(end - start, 5s);
253 }
254 
255 TEST(SimpleSubprocessTest, DetachExecFails) {
256  // Errors executing the process should be propagated from the grandchild
257  // process back to the original parent process.
259  ENOENT,
260  "failed to execute /no/such/file:",
261  Subprocess::Options().detach(),
262  "/no/such/file");
263 }
264 
265 TEST(ParentDeathSubprocessTest, ParentDeathSignal) {
266  // Find out where we are.
267  const auto basename = "subprocess_test_parent_death_helper";
268  auto helper = fs::executable_path();
269  helper.remove_filename() /= basename;
270  if (!fs::exists(helper)) {
271  helper = helper.parent_path().parent_path() / basename / basename;
272  }
273 
274  fs::path tempFile(fs::temp_directory_path() / fs::unique_path());
275 
276  std::vector<std::string> args{helper.string(), tempFile.string()};
277  Subprocess proc(args);
278  // The helper gets killed by its child, see details in
279  // SubprocessTestParentDeathHelper.cpp
280  ASSERT_EQ(SIGKILL, proc.wait().killSignal());
281 
282  // Now wait for the file to be created, see details in
283  // SubprocessTestParentDeathHelper.cpp
284  while (!fs::exists(tempFile)) {
285  usleep(20000); // 20ms
286  }
287 
288  fs::remove(tempFile);
289 }
290 
291 TEST(PopenSubprocessTest, PopenRead) {
292  Subprocess proc("ls /", Subprocess::Options().pipeStdout());
293  int found = 0;
294  gen::byLine(File(proc.stdoutFd())) | [&](StringPiece line) {
295  if (line == "etc" || line == "bin" || line == "usr") {
296  ++found;
297  }
298  };
299  EXPECT_EQ(3, found);
300  proc.waitChecked();
301 }
302 
303 // DANGER: This class runs after fork in a child processes. Be fast, the
304 // parent thread is waiting, but remember that other parent threads are
305 // running and may mutate your state. Avoid mutating any data belonging to
306 // the parent. Avoid interacting with non-POD data that originated in the
307 // parent. Avoid any libraries that may internally reference non-POD data.
308 // Especially beware parent mutexes -- for example, glog's LOG() uses one.
311  explicit WriteFileAfterFork(std::string filename)
312  : filename_(std::move(filename)) {}
313  ~WriteFileAfterFork() override {}
314  int operator()() override {
315  return writeFile(std::string("ok"), filename_.c_str()) ? 0 : errno;
316  }
318 };
319 
320 TEST(AfterForkCallbackSubprocessTest, TestAfterForkCallbackSuccess) {
322  // Trigger a file write from the child.
323  WriteFileAfterFork write_cob("good_file");
324  Subprocess proc(
325  std::vector<std::string>{"/bin/echo"},
327  // The file gets written immediately.
328  std::string s;
329  EXPECT_TRUE(readFile(write_cob.filename_.c_str(), s));
330  EXPECT_EQ("ok", s);
331  proc.waitChecked();
332 }
333 
334 TEST(AfterForkCallbackSubprocessTest, TestAfterForkCallbackError) {
336  // The child will try to write to a file, whose directory does not exist.
337  WriteFileAfterFork write_cob("bad/file");
338  EXPECT_THROW(
339  Subprocess proc(
340  std::vector<std::string>{"/bin/echo"},
343  EXPECT_FALSE(fs::exists(write_cob.filename_));
344 }
345 
346 TEST(CommunicateSubprocessTest, SimpleRead) {
347  Subprocess proc(
348  std::vector<std::string>{"/bin/echo", "-n", "foo", "bar"},
350  auto p = proc.communicate();
351  EXPECT_EQ("foo bar", p.first);
352  proc.waitChecked();
353 }
354 
355 TEST(CommunicateSubprocessTest, BigWrite) {
356  const int numLines = 1 << 20;
357  std::string line("hello\n");
359  data.reserve(numLines * line.size());
360  for (int i = 0; i < numLines; ++i) {
361  data.append(line);
362  }
363 
364  Subprocess proc("wc -l", Subprocess::Options().pipeStdin().pipeStdout());
365  auto p = proc.communicate(data);
366  EXPECT_EQ(folly::format("{}\n", numLines).str(), p.first);
367  proc.waitChecked();
368 }
369 
370 TEST(CommunicateSubprocessTest, Duplex) {
371  // Take 10MB of data and pass them through a filter.
372  // One line, as tr is line-buffered
373  const int bytes = 10 << 20;
374  std::string line(bytes, 'x');
375 
376  Subprocess proc("tr a-z A-Z", Subprocess::Options().pipeStdin().pipeStdout());
377  auto p = proc.communicate(line);
378  EXPECT_EQ(bytes, p.first.size());
379  EXPECT_EQ(std::string::npos, p.first.find_first_not_of('X'));
380  proc.waitChecked();
381 }
382 
383 TEST(CommunicateSubprocessTest, ProcessGroupLeader) {
384  const auto testIsLeader = "test $(cut -d ' ' -f 5 /proc/$$/stat) = $$";
385  Subprocess nonLeader(testIsLeader);
387  Subprocess leader(testIsLeader, Subprocess::Options().processGroupLeader());
388  leader.waitChecked();
389 }
390 
391 TEST(CommunicateSubprocessTest, Duplex2) {
392  checkFdLeak([] {
393  // Pipe 200,000 lines through sed
394  const size_t numCopies = 100000;
395  auto iobuf = IOBuf::copyBuffer("this is a test\nanother line\n");
396  IOBufQueue input;
397  for (size_t n = 0; n < numCopies; ++n) {
398  input.append(iobuf->clone());
399  }
400 
401  std::vector<std::string> cmd({
402  "sed",
403  "-u",
404  "-e",
405  "s/a test/a successful test/",
406  "-e",
407  "/^another line/w/dev/stderr",
408  });
409  auto options =
411  Subprocess proc(cmd, options);
412  auto out = proc.communicateIOBuf(std::move(input));
413  proc.waitChecked();
414 
415  // Convert stdout and stderr to strings so we can call split() on them.
416  fbstring stdoutStr;
417  if (out.first.front()) {
418  stdoutStr = out.first.move()->moveToFbString();
419  }
420  fbstring stderrStr;
421  if (out.second.front()) {
422  stderrStr = out.second.move()->moveToFbString();
423  }
424 
425  // stdout should be a copy of stdin, with "a test" replaced by
426  // "a successful test"
427  std::vector<StringPiece> stdoutLines;
428  split('\n', stdoutStr, stdoutLines);
429  EXPECT_EQ(numCopies * 2 + 1, stdoutLines.size());
430  // Strip off the trailing empty line
431  if (!stdoutLines.empty()) {
432  EXPECT_EQ("", stdoutLines.back());
433  stdoutLines.pop_back();
434  }
435  size_t linenum = 0;
436  for (const auto& line : stdoutLines) {
437  if ((linenum & 1) == 0) {
438  EXPECT_EQ("this is a successful test", line);
439  } else {
440  EXPECT_EQ("another line", line);
441  }
442  ++linenum;
443  }
444 
445  // stderr should only contain the lines containing "another line"
446  std::vector<StringPiece> stderrLines;
447  split('\n', stderrStr, stderrLines);
448  EXPECT_EQ(numCopies + 1, stderrLines.size());
449  // Strip off the trailing empty line
450  if (!stderrLines.empty()) {
451  EXPECT_EQ("", stderrLines.back());
452  stderrLines.pop_back();
453  }
454  for (const auto& line : stderrLines) {
455  EXPECT_EQ("another line", line);
456  }
457  });
458 }
459 
460 namespace {
461 
462 bool readToString(int fd, std::string& buf, size_t maxSize) {
463  buf.resize(maxSize);
464  char* dest = &buf.front();
465  size_t remaining = maxSize;
466 
467  ssize_t n = -1;
468  while (remaining) {
469  n = ::read(fd, dest, remaining);
470  if (n == -1) {
471  if (errno == EINTR) {
472  continue;
473  }
474  if (errno == EAGAIN) {
475  break;
476  }
477  PCHECK("read failed");
478  } else if (n == 0) {
479  break;
480  }
481  dest += n;
482  remaining -= n;
483  }
484 
485  buf.resize(dest - buf.data());
486  return (n == 0);
487 }
488 
489 } // namespace
490 
491 TEST(CommunicateSubprocessTest, Chatty) {
492  checkFdLeak([] {
493  const int lineCount = 1000;
494 
495  int wcount = 0;
496  int rcount = 0;
497 
498  auto options =
500  std::vector<std::string> cmd{
501  "sed",
502  "-u",
503  "-e",
504  "s/a test/a successful test/",
505  };
506 
507  Subprocess proc(cmd, options);
508 
509  auto writeCallback = [&](int pfd, int cfd) -> bool {
510  EXPECT_EQ(0, cfd); // child stdin
511  EXPECT_EQ(rcount, wcount); // chatty, one read for every write
512 
513  auto msg = folly::to<std::string>("a test ", wcount, "\n");
514 
515  // Not entirely kosher, we should handle partial writes, but this is
516  // fine for writes <= PIPE_BUF
517  EXPECT_EQ(msg.size(), writeFull(pfd, msg.data(), msg.size()));
518 
519  ++wcount;
520  proc.enableNotifications(0, false);
521 
522  return (wcount == lineCount);
523  };
524 
525  bool eofSeen = false;
526 
527  auto readCallback = [&](int pfd, int cfd) -> bool {
528  std::string lineBuf;
529 
530  if (cfd != 1) {
531  EXPECT_EQ(2, cfd);
532  EXPECT_TRUE(readToString(pfd, lineBuf, 1));
533  EXPECT_EQ(0, lineBuf.size());
534  return true;
535  }
536 
537  EXPECT_FALSE(eofSeen);
538 
539  std::string expected;
540 
541  if (rcount < lineCount) {
542  expected = folly::to<std::string>("a successful test ", rcount++, "\n");
543  }
544 
545  EXPECT_EQ(wcount, rcount);
546 
547  // Not entirely kosher, we should handle partial reads, but this is
548  // fine for reads <= PIPE_BUF
549  bool atEof = readToString(pfd, lineBuf, expected.size() + 1);
550  if (atEof) {
551  // EOF only expected after we finished reading
552  EXPECT_EQ(lineCount, rcount);
553  eofSeen = true;
554  }
555 
556  EXPECT_EQ(expected, lineBuf);
557 
558  if (wcount != lineCount) { // still more to write...
559  proc.enableNotifications(0, true);
560  }
561 
562  return eofSeen;
563  };
564 
565  proc.communicate(readCallback, writeCallback);
566 
567  EXPECT_EQ(lineCount, wcount);
568  EXPECT_EQ(lineCount, rcount);
569  EXPECT_TRUE(eofSeen);
570 
571  EXPECT_EQ(0, proc.wait().exitStatus());
572  });
573 }
574 
575 TEST(CommunicateSubprocessTest, TakeOwnershipOfPipes) {
576  std::vector<Subprocess::ChildPipe> pipes;
577  {
578  Subprocess proc(
579  "echo $'oh\\nmy\\ncat' | wc -l &", Subprocess::Options().pipeStdout());
580  pipes = proc.takeOwnershipOfPipes();
581  proc.waitChecked();
582  }
583  EXPECT_EQ(1, pipes.size());
584  EXPECT_EQ(1, pipes[0].childFd);
585 
586  char buf[10];
587  EXPECT_EQ(2, readFull(pipes[0].pipe.fd(), buf, 10));
588  buf[2] = 0;
589  EXPECT_EQ("3\n", std::string(buf));
590 }
#define EXPECT_LE(val1, val2)
Definition: gtest.h:1928
#define FOLLY_GNU_DISABLE_WARNING(warningName)
Definition: Portability.h:180
void append(std::unique_ptr< folly::IOBuf > &&buf, bool pack=false)
Definition: IOBufQueue.cpp:143
bool readFile(int fd, Container &out, size_t num_bytes=std::numeric_limits< size_t >::max())
Definition: FileUtil.h:125
Options & chdir(const std::string &dir)
Definition: Subprocess.h:384
#define EXPECT_THROW(statement, expected_exception)
Definition: gtest.h:1843
#define ASSERT_EQ(val1, val2)
Definition: gtest.h:1956
path executable_path()
Definition: FsUtil.cpp:72
bool notStarted() const
Definition: Subprocess.h:174
~WriteFileAfterFork() override
TEST(SimpleSubprocessTest, ExitsSuccessfully)
#define EXPECT_EQ(val1, val2)
Definition: gtest.h:1922
std::chrono::steady_clock::time_point now()
constexpr detail::Map< Move > move
Definition: Base-inl.h:2567
ssize_t readFull(int fd, void *buf, size_t count)
Definition: FileUtil.cpp:126
dest
Definition: upload.py:394
STL namespace.
std::pair< std::string, std::string > communicate(StringPiece input=StringPiece())
Definition: Subprocess.cpp:740
—— Concurrent Priority Queue Implementation ——
Definition: AtomicBitSet.h:29
bool running() const
Definition: Subprocess.h:177
void enableNotifications(int childFd, bool enabled)
Definition: Subprocess.cpp:881
std::pair< IOBufQueue, IOBufQueue > communicateIOBuf(IOBufQueue input=IOBufQueue())
Definition: Subprocess.cpp:763
size_t read(T &out, folly::io::Cursor &cursor)
Definition: Types-inl.h:258
auto end(TestAdlIterable &instance)
Definition: ForeachTest.cpp:62
S split(const StringPiece source, char delimiter)
Definition: String.h:61
ssize_t writeFull(int fd, const void *buf, size_t count)
Definition: FileUtil.cpp:134
auto start
void checkUnixError(ssize_t ret, Args &&...args)
Definition: Exception.h:101
std::unique_ptr< IOBuf > copyBuffer(const folly::IOBuf &buf)
cmd
Definition: gtest-cfgcmd.txt:1
#define EXPECT_TRUE(condition)
Definition: gtest.h:1859
#define EXPECT_SPAWN_ERROR(err, errMsg, cmd,...)
auto byLine(File file, char delim= '\n')
Definition: File-inl.h:148
const char * string
Definition: Conv.cpp:212
std::vector< ChildPipe > takeOwnershipOfPipes()
Definition: Subprocess.cpp:907
static set< string > s
ProcessReturnCode returnCode() const
Definition: Subprocess.h:569
Formatter< false, Args... > format(StringPiece fmt, Args &&...args)
Definition: Format.h:271
ProcessReturnCode wait()
Definition: Subprocess.cpp:644
bool writeFile(const Container &data, const char *filename, int flags=O_WRONLY|O_CREAT|O_TRUNC, mode_t mode=0666)
Definition: FileUtil.h:211
const std::string filename_
Options & dangerousPostForkPreExecCallback(DangerousPostForkPreExecCallback *cob)
Definition: Subprocess.h:457
#define EXPECT_FALSE(condition)
Definition: gtest.h:1862
Range< const char * > StringPiece
int operator()() override
#define EXPECT_SPAWN_OPT_ERROR(err, errMsg, options, cmd,...)
#define ADD_FAILURE()
Definition: gtest.h:1808
WriteFileAfterFork(std::string filename)
int stdoutFd() const
Definition: Subprocess.h:875
static constexpr uint64_t data[1]
Definition: Fingerprint.cpp:43
void pipe(CPUExecutor cpu, IOExecutor io)