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
31namespace
32{
33::testing::AssertionResult is_error(const std::error_code& ec)
34{
35 return ec ? ::testing::AssertionResult{true} : ::testing::AssertionResult{false};
36}
37
38struct 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
51 core::posix::ChildProcess child = core::posix::ChildProcess::invalid();
52};
53
54struct 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
72TEST(PosixProcess, ctor_throws_for_invalid_pid)
73{
74 pid_t invalid_pid{-1};
76}
77
78TEST(PosixProcess, this_process_instance_reports_correct_pid)
79{
80 EXPECT_EQ(getpid(), core::posix::this_process::instance().pid());
81}
82
83TEST(PosixProcess, this_process_instance_reports_correct_parent)
84{
85 EXPECT_EQ(getppid(), core::posix::this_process::parent().pid());
86}
87
88TEST(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
93TEST(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
101TEST(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
106TEST(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
113TEST_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
119TEST_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
128TEST(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");
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");
144 }
145}
146
147TEST(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
155TEST(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
174TEST(Self, getting_env_var_for_empty_key_does_not_throw)
175{
176 EXPECT_NO_THROW(core::posix::this_process::env::get(""));
177}
178
179TEST(Self, setting_env_var_for_empty_key_throws)
180{
182 "",
183 "uninteresting"));
184}
185
186TEST(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
211TEST(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
231TEST_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));
246 result = child.wait_for(core::posix::wait::Flags::untraced);
248 result.status);
250 result.detail.if_signaled.signal);
251}
252
253TEST(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
276 auto result = child.wait_for(core::posix::wait::Flags::untraced);
278 result.status);
280 result.detail.if_stopped.signal);
281
285 result.status);
287 result.detail.if_signaled.signal);
288}
289
290TEST(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
313TEST(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
334TEST(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
373TEST(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
403namespace
404{
405struct ChildDeathObserverEventCollector
406{
407 MOCK_METHOD1(on_child_died,void(const core::posix::Process&));
408};
409}
410
411TEST_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
441TEST_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
472TEST(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
509TEST(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);
536}
537
538TEST(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
547TEST(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
554TEST(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 std::unique_ptr< DeathObserver > create_once_with_signal_trap(std::shared_ptr< SignalTrap > trap)
Creates the unique instance of class DeathObserver.
The Process class models a child process of this process.
static ChildProcess invalid()
Creates an invalid ChildProcess.
wait::Result wait_for(const wait::Flags &flags)
Wait for the child process to change state.
std::ostream & cin()
Access this process's stdin.
std::istream & cerr()
Access this process's stderr.
std::istream & cout()
Access this process's stdout.
The Process class models a process and possible operations on it.
Definition process.h:45
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
static Process invalid()
Returns an invalid instance for testing purposes.
Definition process.cpp:38
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 void send_signal_or_throw(Signal signal)
Sends a signal to this signalable object.
EXPECT_ANY_THROW(auto death_observer=core::posix::ChildProcess::DeathObserver::create_once_with_signal_trap(trap))
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.
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.
CORE_POSIX_DLL_PUBLIC void unset_or_throw(const std::string &key)
unset_or_throw removes the variable with name key from the environment.
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 std::ostream & cout() noexcept(true)
Access this process's stdout.
CORE_POSIX_DLL_PUBLIC Process parent() noexcept(true)
Query the parent of the process.
CORE_POSIX_DLL_PUBLIC std::ostream & cerr() noexcept(true)
Access this process's stderr.
CORE_POSIX_DLL_PUBLIC Process instance() noexcept(true)
Returns a Process instance corresponding to this process.
@ untraced
Also wait for state changes in untraced children.
Definition wait.h:45
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 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 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's the executable with the provided arguments and environment.
Definition exec.cpp:33
TEST(PosixProcess, ctor_throws_for_invalid_pid)
TEST_F(ForkedSpinningProcess, throwing_access_to_process_group_id_of_a_forked_process_works)
@ signaled
The process was signalled and terminated.
Definition wait.h:64
@ exited
The process exited normally.
Definition wait.h:63
@ stopped
The process was signalled and stopped.
Definition wait.h:65