process-cpp  3.0.0
A simple convenience library for handling processes in C++11.
posix_process_test.cpp
Go to the documentation of this file.
1 /*
2  * Copyright © 2013 Canonical Ltd.
3  *
4  * This program is free software: you can redistribute it and/or modify it
5  * under the terms of the GNU Lesser General Public License version 3,
6  * as published by the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU Lesser General Public License for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  *
16  * Authored by: Thomas Voß <thomas.voss@canonical.com>
17  */
18 
19 #include <core/posix/exec.h>
20 #include <core/posix/fork.h>
21 #include <core/posix/process.h>
22 #include <core/posix/signal.h>
23 
24 #include <gmock/gmock.h>
25 #include <gtest/gtest.h>
26 
27 #include <chrono>
28 #include <map>
29 #include <thread>
30 
31 namespace
32 {
33 ::testing::AssertionResult is_error(const std::error_code& ec)
34 {
35  return ec ? ::testing::AssertionResult{true} : ::testing::AssertionResult{false};
36 }
37 
38 struct ForkedSpinningProcess : public ::testing::Test
39 {
40  void SetUp()
41  {
42  child = core::posix::fork(
43  []() { std::cout << "Child" << std::endl; while(true) {} return core::posix::exit::Status::failure;},
45  }
46 
47  void TearDown()
48  {
49  }
50 
52 };
53 
54 struct Init
55 {
56  Init()
57  : signal_trap(
60  death_observer(
62  signal_trap))
63  {
64  }
65 
66  std::shared_ptr<core::posix::SignalTrap> signal_trap;
67  std::unique_ptr<core::posix::ChildProcess::DeathObserver> death_observer;
68 } init;
69 
70 }
71 
72 TEST(PosixProcess, ctor_throws_for_invalid_pid)
73 {
74  pid_t invalid_pid{-1};
76 }
77 
78 TEST(PosixProcess, this_process_instance_reports_correct_pid)
79 {
80  EXPECT_EQ(getpid(), core::posix::this_process::instance().pid());
81 }
82 
83 TEST(PosixProcess, this_process_instance_reports_correct_parent)
84 {
85  EXPECT_EQ(getppid(), core::posix::this_process::parent().pid());
86 }
87 
88 TEST(PosixProcess, throwing_access_to_process_group_id_of_this_process_works)
89 {
90  EXPECT_EQ(getpgrp(), core::posix::this_process::instance().process_group_or_throw().id());
91 }
92 
93 TEST(PosixProcess, non_throwing_access_to_process_group_id_of_this_process_works)
94 {
95  std::error_code se;
97  EXPECT_FALSE(is_error(se));
98  EXPECT_EQ(getpgrp(), pg.id());
99 }
100 
101 TEST(PosixProcess, trying_to_access_process_group_of_invalid_process_throws)
102 {
103  EXPECT_ANY_THROW(core::posix::Process::invalid().process_group_or_throw());
104 }
105 
106 TEST(PosixProcess, trying_to_access_process_group_of_invalid_process_reports_error)
107 {
108  std::error_code se;
110  EXPECT_TRUE(is_error(se));
111 }
112 
113 TEST_F(ForkedSpinningProcess, throwing_access_to_process_group_id_of_a_forked_process_works)
114 {
115  auto pg = child.process_group_or_throw();
116  EXPECT_EQ(getpgrp(), pg.id());
117 }
118 
119 TEST_F(ForkedSpinningProcess, non_throwing_access_to_process_group_id_of_a_forked_process_works)
120 {
121  std::error_code se;
122  auto pg = child.process_group(se);
123 
124  EXPECT_FALSE(se);
125  EXPECT_EQ(getpgrp(), pg.id());
126 }
127 
128 TEST(PosixProcess, accessing_streams_of_this_process_works)
129 {
130  {
131  std::stringstream ss;
132  auto old = core::posix::this_process::cout().rdbuf(ss.rdbuf());
133  core::posix::this_process::cout() << "core::posix::this_process::instance().cout()\n";
134  EXPECT_EQ(ss.str(), "core::posix::this_process::instance().cout()\n");
135  core::posix::this_process::cout().rdbuf(old);
136  }
137 
138  {
139  std::stringstream ss;
140  auto old = core::posix::this_process::cerr().rdbuf(ss.rdbuf());
141  core::posix::this_process::cerr() << "core::posix::this_process::instance().cerr()" << std::endl;
142  EXPECT_EQ(ss.str(), "core::posix::this_process::instance().cerr()\n");
143  core::posix::this_process::cerr().rdbuf(old);
144  }
145 }
146 
147 TEST(Self, non_mutable_access_to_the_environment_returns_correct_results)
148 {
149  static const char* home = "HOME";
150  static const char* totally_not_existent = "totally_not_existent_42";
151  EXPECT_EQ(getenv("HOME"), core::posix::this_process::env::get(home));
152  EXPECT_EQ("", core::posix::this_process::env::get(totally_not_existent));
153 }
154 
155 TEST(Self, mutable_access_to_the_environment_alters_the_environment)
156 {
157  static const char* totally_not_existent = "totally_not_existent_42";
158  static const char* totally_not_existent_value = "42";
159 
160  EXPECT_EQ("", core::posix::this_process::env::get(totally_not_existent));
162  totally_not_existent,
163  totally_not_existent_value));
164  EXPECT_EQ(totally_not_existent_value,
165  core::posix::this_process::env::get(totally_not_existent));
166 
167  EXPECT_NO_THROW(
169  totally_not_existent));
170  EXPECT_EQ("",
171  core::posix::this_process::env::get(totally_not_existent));
172 }
173 
174 TEST(Self, getting_env_var_for_empty_key_does_not_throw)
175 {
176  EXPECT_NO_THROW(core::posix::this_process::env::get(""));
177 }
178 
179 TEST(Self, setting_env_var_for_empty_key_throws)
180 {
182  "",
183  "uninteresting"));
184 }
185 
186 TEST(ChildProcess, fork_returns_process_object_with_valid_pid_and_wait_for_returns_correct_result)
187 {
189  []() { std::cout << "Child" << std::endl; return core::posix::exit::Status::success; },
191  EXPECT_TRUE(child.pid() > 0);
192 
193  auto result = child.wait_for(core::posix::wait::Flags::untraced);
195  result.status);
197  result.detail.if_exited.status);
198 
199  child = core::posix::fork(
200  []() { std::cout << "Child" << std::endl; return core::posix::exit::Status::failure; },
202  EXPECT_TRUE(child.pid() > 0);
203 
206  result.status);
208  result.detail.if_exited.status);
209 }
210 
211 TEST(ChildProcess, fork_does_not_run_atexit_handlers) {
212  auto child = core::posix::fork(
213  [] () {
214  std::atexit([] () {
215  // Let's say that this handler tries to pthread_cancel()
216  // a no-longer-exists thread after a fork.
217  raise(SIGSEGV);
218  });
219 
222  EXPECT_TRUE(child.pid() > 0);
223 
224  auto result = child.wait_for(core::posix::wait::Flags::untraced);
226  result.status);
228  result.detail.if_exited.status);
229 }
230 
231 TEST_F(ForkedSpinningProcess, signalling_a_forked_child_makes_wait_for_return_correct_result)
232 {
233  EXPECT_NO_THROW(child.send_signal_or_throw(core::posix::Signal::sig_kill));
234  auto result = child.wait_for(core::posix::wait::Flags::untraced);
236  result.status);
238  result.detail.if_signaled.signal);
239 
240  child = core::posix::fork(
241  []() { std::cout << "Child" << std::endl; while(true) {} return core::posix::exit::Status::failure;},
243  EXPECT_TRUE(child.pid() > 0);
244 
245  EXPECT_NO_THROW(child.send_signal_or_throw(core::posix::Signal::sig_term));
248  result.status);
250  result.detail.if_signaled.signal);
251 }
252 
253 TEST(ChildProcess, stopping_a_forked_child_makes_wait_for_return_correct_result)
254 {
256  []()
257  {
258  std::string line;
259  while(true)
260  {
261  std::cin >> line;
262  std::cout << line << std::endl;
263  }
265  },
267  EXPECT_TRUE(child.pid() > 0);
268 
269  const std::string echo_value{"42"};
270  child.cin() << echo_value << std::endl;
271  std::string line; child.cout() >> line;
272 
273  EXPECT_EQ(echo_value, line);
274 
275  EXPECT_NO_THROW(child.send_signal_or_throw(core::posix::Signal::sig_stop));
276  auto result = child.wait_for(core::posix::wait::Flags::untraced);
278  result.status);
280  result.detail.if_stopped.signal);
281 
282  EXPECT_NO_THROW(child.send_signal_or_throw(core::posix::Signal::sig_kill));
285  result.status);
287  result.detail.if_signaled.signal);
288 }
289 
290 TEST(ChildProcess, exec_returns_process_object_with_valid_pid_and_wait_for_returns_correct_result)
291 {
292  const std::string program{"/usr/bin/sleep"};
293  const std::vector<std::string> argv = {"10"};
294  std::map<std::string, std::string> env;
295  core::posix::this_process::env::for_each([&env](const std::string& key, const std::string& value)
296  {
297  env.insert(std::make_pair(key, value));
298  });
299 
301  argv,
302  env,
304  EXPECT_TRUE(child.pid() > 0);
305  EXPECT_NO_THROW(child.send_signal_or_throw(core::posix::Signal::sig_kill));
306  auto result = child.wait_for(core::posix::wait::Flags::untraced);
308  result.status);
310  result.detail.if_signaled.signal);
311 }
312 
313 TEST(ChildProcess, exec_child_setup)
314 {
315  const std::string program{"/usr/bin/sleep"};
316  const std::vector<std::string> argv = {"10"};
317  std::map<std::string, std::string> env;
318  std::function<void()> child_setup = []()
319  {
320  std::cout << "hello_there" << std::endl;
321  };
322 
324  argv,
325  env,
327  child_setup);
328  EXPECT_TRUE(child.pid() > 0);
329  std::string output;
330  child.cout() >> output;
331  EXPECT_EQ("hello_there", output);
332 }
333 
334 TEST(ChildProcess, signalling_an_execd_child_makes_wait_for_return_correct_result)
335 {
336  const std::string program{"/usr/bin/env"};
337  const std::vector<std::string> argv = {};
338  std::map<std::string, std::string> env;
339  core::posix::this_process::env::for_each([&env](const std::string& key, const std::string& value)
340  {
341  env.insert(std::make_pair(key, value));
342  });
343 
345  program,
346  argv,
347  env,
349 
350  EXPECT_TRUE(child.pid() > 0);
351 
352  EXPECT_NO_THROW(child.send_signal_or_throw(core::posix::Signal::sig_kill));
353  auto result = child.wait_for(core::posix::wait::Flags::untraced);
355  result.status);
357  result.detail.if_signaled.signal);
358 
359  child = core::posix::exec(program,
360  argv,
361  env,
363  EXPECT_TRUE(child.pid() > 0);
364 
365  EXPECT_NO_THROW(child.send_signal_or_throw(core::posix::Signal::sig_term));
366  result = child.wait_for(core::posix::wait::Flags::untraced);
368  result.status);
370  result.detail.if_signaled.signal);
371 }
372 
373 TEST(ChildProcess, stopping_an_execd_child_makes_wait_for_return_correct_result)
374 {
375  const std::string program{"/usr/bin/sleep"};
376  const std::vector<std::string> argv = {"10"};
377  std::map<std::string, std::string> env;
378  core::posix::this_process::env::for_each([&env](const std::string& key, const std::string& value)
379  {
380  env.insert(std::make_pair(key, value));
381  });
382 
384  argv,
385  env,
387  EXPECT_TRUE(child.pid() > 0);
388 
389  EXPECT_NO_THROW(child.send_signal_or_throw(core::posix::Signal::sig_stop));
390  auto result = child.wait_for(core::posix::wait::Flags::untraced);
392  result.status);
394  result.detail.if_signaled.signal);
395  EXPECT_NO_THROW(child.send_signal_or_throw(core::posix::Signal::sig_kill));
396  result = child.wait_for(core::posix::wait::Flags::untraced);
398  result.status);
400  result.detail.if_signaled.signal);
401 }
402 
403 namespace
404 {
405 struct ChildDeathObserverEventCollector
406 {
407  MOCK_METHOD1(on_child_died,void(const core::posix::Process&));
408 };
409 }
410 
411 TEST_F(ForkedSpinningProcess, observing_child_processes_for_death_works_if_child_is_signalled_with_sigkill)
412 {
413  using namespace ::testing;
414 
415  ChildDeathObserverEventCollector event_collector;
416 
417  core::ScopedConnection sc
418  {
419  init.death_observer->child_died().connect([&event_collector](core::posix::ChildProcess cp)
420  {
421  event_collector.on_child_died(cp);
422  })
423  };
424 
425  EXPECT_TRUE(init.death_observer->add(child));
426  EXPECT_CALL(event_collector, on_child_died(_))
427  .Times(1)
428  .WillOnce(
429  InvokeWithoutArgs(
430  init.signal_trap.get(),
432 
433  std::thread worker{[]() { init.signal_trap->run(); }};
434 
435  child.send_signal_or_throw(core::posix::Signal::sig_kill);
436 
437  if (worker.joinable())
438  worker.join();
439 }
440 
441 TEST_F(ForkedSpinningProcess, observing_child_processes_for_death_works_if_child_is_signalled_with_sigterm)
442 {
443  using namespace ::testing;
444 
445  ChildDeathObserverEventCollector signal_trap;
446 
447  EXPECT_TRUE(init.death_observer->add(child));
448 
449  core::ScopedConnection sc
450  {
451  init.death_observer->child_died().connect([&signal_trap](const core::posix::ChildProcess& child)
452  {
453  signal_trap.on_child_died(child);
454  })
455  };
456 
457  EXPECT_CALL(signal_trap, on_child_died(_))
458  .Times(1)
459  .WillOnce(
460  InvokeWithoutArgs(
461  init.signal_trap.get(),
463 
464  std::thread worker{[]() { init.signal_trap->run(); }};
465 
466  child.send_signal_or_throw(core::posix::Signal::sig_term);
467 
468  if (worker.joinable())
469  worker.join();
470 }
471 
472 TEST(ChildProcess, ensure_that_forked_children_are_cleaned_up)
473 {
474  static const unsigned int child_process_count = 100;
475  unsigned int counter = 1;
476 
477  core::ScopedConnection sc
478  {
479  init.death_observer->child_died().connect([&counter](const core::posix::ChildProcess&)
480  {
481  counter++;
482 
483  if (counter == child_process_count)
484  {
485  init.signal_trap->stop();
486  }
487  })
488  };
489 
490  std::thread t([]() {init.signal_trap->run();});
491 
492  for (unsigned int i = 0; i < child_process_count; i++)
493  {
494  auto child = core::posix::fork(
495  []() { return core::posix::exit::Status::success; },
497  init.death_observer->add(child);
498  // A bit ugly but we have to ensure that no signal is lost.
499  // And thus, we keep the process object alive.
500  std::this_thread::sleep_for(std::chrono::milliseconds{5});
501  }
502 
503  if (t.joinable())
504  t.join();
505 
506  EXPECT_EQ(child_process_count, counter);
507 }
508 
509 TEST(StreamRedirect, redirecting_stdin_stdout_stderr_works)
510 {
512  []()
513  {
514  std::string line;
515  while(true)
516  {
517  std::cin >> line;
518  std::cout << line << std::endl;
519  std::cerr << line << std::endl;
520  }
522  },
524 
525  ASSERT_TRUE(child.pid() > 0);
526 
527  const std::string echo_value{"42"};
528  child.cin() << echo_value << std::endl;
529  std::string line;
530  EXPECT_NO_THROW(child.cout() >> line);
531  EXPECT_EQ(echo_value, line);
532  EXPECT_NO_THROW(child.cerr() >> line);
533  EXPECT_EQ(echo_value, line);
534  EXPECT_NO_THROW(child.send_signal_or_throw(core::posix::Signal::sig_kill));
536 }
537 
538 TEST(Environment, iterating_the_environment_does_not_throw)
539 {
541  [](const std::string& key, const std::string& value)
542  {
543  std::cout << key << " -> " << value << std::endl;
544  }););
545 }
546 
547 TEST(Environment, specifying_default_value_for_get_returns_correct_result)
548 {
549  const std::string expected_value{"42"};
550  EXPECT_EQ(expected_value,
551  core::posix::this_process::env::get("totally_non_existant_key_in_env_blubb", expected_value));
552 }
553 
554 TEST(Environment, for_each_returns_correct_results)
555 {
556  std::array<std::string, 3> env_keys = {"totally_non_existant_key_in_env_blubb0",
557  "totally_non_existant_key_in_env_blubb1",
558  "totally_non_existant_key_in_env_blubb2"};
559  std::array<std::string, 3> env_vars = {env_keys[0] + "=" + "hello",
560  env_keys[1] + "=" + "",
561  env_keys[2] + "=" + "string=hello"};
562  for( auto const& env_var : env_vars )
563  {
564  ::putenv(const_cast<char*>(env_var.c_str()));
565  }
566 
567  core::posix::this_process::env::for_each([env_keys](const std::string& key, const std::string& value)
568  {
569  if (key == env_keys[0])
570  {
571  EXPECT_EQ("hello", value);
572  }
573  else if (key == env_keys[1])
574  {
575  EXPECT_EQ("", value);
576  }
577  else if (key == env_keys[2])
578  {
579  EXPECT_EQ("string=hello", value);
580  }
581  });
582 }
static ChildProcess invalid()
Creates an invalid ChildProcess.
static Process invalid()
Returns an invalid instance for testing purposes.
Definition: process.cpp:38
std::istream & cout()
Access this process&#39;s stdout.
The process was signalled and stopped.
std::ostream & cin()
Access this process&#39;s stdin.
The Process class models a process and possible operations on it.
Definition: process.h:44
The process exited normally.
static std::unique_ptr< DeathObserver > create_once_with_signal_trap(std::shared_ptr< SignalTrap > trap)
Creates the unique instance of class DeathObserver.
The process was signalled and terminated.
CORE_POSIX_DLL_PUBLIC std::istream & cin() noexcept(true)
Access this process&#39;s stdin.
EXPECT_ANY_THROW(auto death_observer=core::posix::ChildProcess::DeathObserver::create_once_with_signal_trap(trap))
CORE_POSIX_DLL_PUBLIC void for_each(const std::function< void(const std::string &, const std::string &)> &functor) noexcept(true)
for_each invokes a functor for every key-value pair in the environment.
CORE_POSIX_DLL_PUBLIC ChildProcess fork(const std::function< posix::exit::Status()> &main, const StandardStream &flags)
fork forks a new process and executes the provided main function in the newly forked process...
Definition: fork.cpp:57
CORE_POSIX_DLL_PUBLIC std::ostream & cerr() noexcept(true)
Access this process&#39;s stderr.
CORE_POSIX_DLL_PUBLIC void set_or_throw(const std::string &key, const std::string &value)
set_or_throw will adjust the contents of the variable identified by key to the provided value...
TEST_F(ForkedSpinningProcess, throwing_access_to_process_group_id_of_a_forked_process_works)
CORE_POSIX_DLL_PUBLIC ChildProcess exec(const std::string &fn, const std::vector< std::string > &argv, const std::map< std::string, std::string > &env, const StandardStream &flags)
exec execve&#39;s the executable with the provided arguments and environment.
Definition: exec.cpp:33
virtual pid_t pid() const
Query the pid of the process.
Definition: process.cpp:59
virtual void stop()=0
Stops execution of the signal trap.
virtual ProcessGroup process_group(std::error_code &se) const noexcept(true)
Queries the id of the process group this process belongs to.
Definition: process.cpp:74
std::istream & cerr()
Access this process&#39;s stderr.
CORE_POSIX_DLL_PUBLIC Process instance() noexcept(true)
Returns a Process instance corresponding to this process.
CORE_POSIX_DLL_PUBLIC void unset_or_throw(const std::string &key)
unset_or_throw removes the variable with name key from the environment.
wait::Result wait_for(const wait::Flags &flags)
Wait for the child process to change state.
The Process class models a child process of this process.
Definition: child_process.h:43
CORE_POSIX_DLL_PUBLIC std::shared_ptr< SignalTrap > trap_signals_for_all_subsequent_threads(std::initializer_list< core::posix::Signal > blocked_signals)
Traps the specified signals for the current thread, and inherits the respective signal mask to all ch...
Definition: signal.cpp:210
CORE_POSIX_DLL_PUBLIC std::ostream & cout() noexcept(true)
Access this process&#39;s stdout.
CORE_POSIX_DLL_PUBLIC std::string get(const std::string &key, const std::string &default_value=std::string()) noexcept(true)
get queries the value of an environment variable.
Also wait for state changes in untraced children.
CORE_POSIX_DLL_PUBLIC Process parent() noexcept(true)
Query the parent of the process.
TEST(PosixProcess, ctor_throws_for_invalid_pid)
virtual void send_signal_or_throw(Signal signal)
Sends a signal to this signalable object.
Definition: signalable.cpp:34