summaryrefslogtreecommitdiff
path: root/utils/process/child_test.cpp
diff options
context:
space:
mode:
authorBrooks Davis <brooks@FreeBSD.org>2020-03-17 16:56:50 +0000
committerBrooks Davis <brooks@FreeBSD.org>2020-03-17 16:56:50 +0000
commit08334c51dbb99d9ecd2bb86a2d94ed06da9e167a (patch)
treec43eb24d59bd5c963583a5190caef80fc8387322 /utils/process/child_test.cpp
Notes
Diffstat (limited to 'utils/process/child_test.cpp')
-rw-r--r--utils/process/child_test.cpp846
1 files changed, 846 insertions, 0 deletions
diff --git a/utils/process/child_test.cpp b/utils/process/child_test.cpp
new file mode 100644
index 0000000000000..69de9991ae139
--- /dev/null
+++ b/utils/process/child_test.cpp
@@ -0,0 +1,846 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/process/child.ipp"
+
+extern "C" {
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <fcntl.h>
+#include <signal.h>
+#include <unistd.h>
+}
+
+#include <cstdarg>
+#include <cerrno>
+#include <cstdlib>
+#include <cstring>
+#include <fstream>
+#include <iostream>
+#include <stdexcept>
+
+#include <atf-c++.hpp>
+
+#include "utils/defs.hpp"
+#include "utils/env.hpp"
+#include "utils/format/macros.hpp"
+#include "utils/fs/operations.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/logging/macros.hpp"
+#include "utils/process/exceptions.hpp"
+#include "utils/process/status.hpp"
+#include "utils/process/system.hpp"
+#include "utils/sanity.hpp"
+#include "utils/test_utils.ipp"
+
+namespace fs = utils::fs;
+namespace logging = utils::logging;
+namespace process = utils::process;
+
+
+namespace {
+
+
+/// Checks if the current subprocess is in its own session.
+static void
+child_check_own_session(void)
+{
+ std::exit((::getsid(::getpid()) == ::getpid()) ?
+ EXIT_SUCCESS : EXIT_FAILURE);
+}
+
+
+/// Body for a process that prints a simple message and exits.
+///
+/// \tparam ExitStatus The exit status for the subprocess.
+/// \tparam Message A single character that will be prepended to the printed
+/// messages. This would ideally be a string, but we cannot templatize a
+/// function with an object nor a pointer.
+template< int ExitStatus, char Message >
+static void
+child_simple_function(void)
+{
+ std::cout << "To stdout: " << Message << "\n";
+ std::cerr << "To stderr: " << Message << "\n";
+ std::exit(ExitStatus);
+}
+
+
+/// Functor for the body of a process that prints a simple message and exits.
+class child_simple_functor {
+ /// The exit status that the subprocess will yield.
+ int _exitstatus;
+
+ /// The message to print on stdout and stderr.
+ std::string _message;
+
+public:
+ /// Constructs a new functor.
+ ///
+ /// \param exitstatus The exit status that the subprocess will yield.
+ /// \param message The message to print on stdout and stderr.
+ child_simple_functor(const int exitstatus, const std::string& message) :
+ _exitstatus(exitstatus),
+ _message(message)
+ {
+ }
+
+ /// Body for the subprocess.
+ void
+ operator()(void)
+ {
+ std::cout << "To stdout: " << _message << "\n";
+ std::cerr << "To stderr: " << _message << "\n";
+ std::exit(_exitstatus);
+ }
+};
+
+
+/// Body for a process that prints many messages to stdout and exits.
+///
+/// The goal of this body is to validate that any buffering performed on the
+/// parent process to read the output of the subprocess works correctly.
+static void
+child_printer_function(void)
+{
+ for (std::size_t i = 0; i < 100; i++)
+ std::cout << "This is a message to stdout, sequence " << i << "\n";
+ std::cout.flush();
+ std::cerr << "Exiting\n";
+ std::exit(EXIT_SUCCESS);
+}
+
+
+/// Functor for the body of a process that runs child_printer_function.
+class child_printer_functor {
+public:
+ /// Body for the subprocess.
+ void
+ operator()(void)
+ {
+ child_printer_function();
+ }
+};
+
+
+/// Body for a child process that throws an exception.
+static void
+child_throw_exception(void)
+{
+ throw std::runtime_error("A loose exception");
+}
+
+
+/// Body for a child process that creates a pidfile.
+static void
+child_write_pid(void)
+{
+ std::ofstream output("pidfile");
+ output << ::getpid() << "\n";
+ output.close();
+ std::exit(EXIT_SUCCESS);
+}
+
+
+/// A child process that returns.
+///
+/// The fork() wrappers are supposed to capture this condition and terminate the
+/// child before the code returns to the fork() call point.
+static void
+child_return(void)
+{
+}
+
+
+/// A child process that raises an exception.
+///
+/// The fork() wrappers are supposed to capture this condition and terminate the
+/// child before the code returns to the fork() call point.
+///
+/// \tparam Type The type of the exception to raise.
+/// \tparam Value The value passed to the constructor of the exception type. In
+/// general, this only makes sense if Type is a primitive type so that, in
+/// the end, the code becomes "throw int(123)".
+///
+/// \throw Type An exception of the provided type.
+template< class Type, Type Value >
+void
+child_raise_exception(void)
+{
+ throw Type(Value);
+}
+
+
+/// Calculates the path to the test helpers binary.
+///
+/// \param tc A pointer to the caller test case, needed to extract the value of
+/// the "srcdir" property.
+///
+/// \return The path to the helpers binary.
+static fs::path
+get_helpers(const atf::tests::tc* tc)
+{
+ return fs::path(tc->get_config_var("srcdir")) / "helpers";
+}
+
+
+/// Mock fork(2) that just returns an error.
+///
+/// \tparam Errno The value to set as the errno of the failed call.
+///
+/// \return Always -1.
+template< int Errno >
+static pid_t
+fork_fail(void) throw()
+{
+ errno = Errno;
+ return -1;
+}
+
+
+/// Mock open(2) that fails if the 'raise-error' file is opened.
+///
+/// \tparam Errno The value to set as the errno if the known failure triggers.
+/// \param path The path to the file to be opened.
+/// \param flags The open flags.
+/// \param ... The file mode creation, if flags contains O_CREAT.
+///
+/// \return The opened file handle or -1 on error.
+template< int Errno >
+static int
+open_fail(const char* path, const int flags, ...) throw()
+{
+ if (std::strcmp(path, "raise-error") == 0) {
+ errno = Errno;
+ return -1;
+ } else {
+ va_list ap;
+ va_start(ap, flags);
+ const int mode = va_arg(ap, int);
+ va_end(ap);
+ return ::open(path, flags, mode);
+ }
+}
+
+
+/// Mock pipe(2) that just returns an error.
+///
+/// \tparam Errno The value to set as the errno of the failed call.
+///
+/// \return Always -1.
+template< int Errno >
+static pid_t
+pipe_fail(int* /* fildes */) throw()
+{
+ errno = Errno;
+ return -1;
+}
+
+
+/// Helper for child tests to validate inheritance of stdout/stderr.
+///
+/// This function ensures that passing one of /dev/stdout or /dev/stderr to
+/// the child__fork_files fork method does the right thing. The idea is that we
+/// call fork with the given parameters and then make our child redirect one of
+/// its file descriptors to a specific file without going through the process
+/// library. We then validate if this redirection worked and got the expected
+/// output.
+///
+/// \param fork_stdout The path to pass to the fork call as the stdout file.
+/// \param fork_stderr The path to pass to the fork call as the stderr file.
+/// \param child_file The file to explicitly in the subchild.
+/// \param child_fd The file descriptor to which to attach child_file.
+static void
+do_inherit_test(const char* fork_stdout, const char* fork_stderr,
+ const char* child_file, const int child_fd)
+{
+ const pid_t pid = ::fork();
+ ATF_REQUIRE(pid != -1);
+ if (pid == 0) {
+ logging::set_inmemory();
+
+ const int fd = ::open(child_file, O_CREAT | O_WRONLY | O_TRUNC, 0644);
+ if (fd != child_fd) {
+ if (::dup2(fd, child_fd) == -1)
+ std::abort();
+ ::close(fd);
+ }
+
+ std::auto_ptr< process::child > child = process::child::fork_files(
+ child_simple_function< 123, 'Z' >,
+ fs::path(fork_stdout), fs::path(fork_stderr));
+ const process::status status = child->wait();
+ if (!status.exited() || status.exitstatus() != 123)
+ std::abort();
+ std::exit(EXIT_SUCCESS);
+ } else {
+ int status;
+ ATF_REQUIRE(::waitpid(pid, &status, 0) != -1);
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
+ ATF_REQUIRE(atf::utils::grep_file("stdout: Z", "stdout.txt"));
+ ATF_REQUIRE(atf::utils::grep_file("stderr: Z", "stderr.txt"));
+ }
+}
+
+
+/// Performs a "child__fork_capture__ok_*" test.
+///
+/// This test basically ensures that the child__fork_capture class spawns a
+/// process whose output is captured in an input stream.
+///
+/// \tparam Hook The type of the fork hook to use.
+/// \param hook The hook to the fork call.
+template< class Hook >
+static void
+child__fork_capture__ok(Hook hook)
+{
+ std::cout << "This unflushed message should not propagate to the child";
+ std::cerr << "This unflushed message should not propagate to the child";
+ std::auto_ptr< process::child > child = process::child::fork_capture(hook);
+ std::cout.flush();
+ std::cerr.flush();
+
+ std::istream& output = child->output();
+ for (std::size_t i = 0; i < 100; i++) {
+ std::string line;
+ ATF_REQUIRE(std::getline(output, line).good());
+ ATF_REQUIRE_EQ((F("This is a message to stdout, "
+ "sequence %s") % i).str(), line);
+ }
+
+ std::string line;
+ ATF_REQUIRE(std::getline(output, line).good());
+ ATF_REQUIRE_EQ("Exiting", line);
+
+ process::status status = child->wait();
+ ATF_REQUIRE(status.exited());
+ ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus());
+}
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__fork_capture__ok_function);
+ATF_TEST_CASE_BODY(child__fork_capture__ok_function)
+{
+ child__fork_capture__ok(child_printer_function);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__fork_capture__ok_functor);
+ATF_TEST_CASE_BODY(child__fork_capture__ok_functor)
+{
+ child__fork_capture__ok(child_printer_functor());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__fork_capture__catch_exceptions);
+ATF_TEST_CASE_BODY(child__fork_capture__catch_exceptions)
+{
+ std::auto_ptr< process::child > child = process::child::fork_capture(
+ child_throw_exception);
+
+ std::string message;
+ std::istream& output = child->output();
+ ATF_REQUIRE(std::getline(output, message).good());
+
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.signaled());
+ ATF_REQUIRE_EQ(SIGABRT, status.termsig());
+
+ ATF_REQUIRE_MATCH("Caught.*A loose exception", message);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__fork_capture__new_session);
+ATF_TEST_CASE_BODY(child__fork_capture__new_session)
+{
+ std::auto_ptr< process::child > child = process::child::fork_capture(
+ child_check_own_session);
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.exited());
+ ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__fork_capture__pipe_fail);
+ATF_TEST_CASE_BODY(child__fork_capture__pipe_fail)
+{
+ process::detail::syscall_pipe = pipe_fail< 23 >;
+ try {
+ process::child::fork_capture(child_simple_function< 1, 'A' >);
+ fail("Expected exception but none raised");
+ } catch (const process::system_error& e) {
+ ATF_REQUIRE(atf::utils::grep_string("pipe.*failed", e.what()));
+ ATF_REQUIRE_EQ(23, e.original_errno());
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__fork_capture__fork_cannot_exit);
+ATF_TEST_CASE_BODY(child__fork_capture__fork_cannot_exit)
+{
+ const pid_t parent_pid = ::getpid();
+ atf::utils::create_file("to-not-be-deleted", "");
+
+ std::auto_ptr< process::child > child = process::child::fork_capture(
+ child_return);
+ if (::getpid() != parent_pid) {
+ // If we enter this clause, it is because the hook returned.
+ ::unlink("to-not-be-deleted");
+ std::exit(EXIT_SUCCESS);
+ }
+
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.signaled());
+ ATF_REQUIRE(fs::exists(fs::path("to-not-be-deleted")));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__fork_capture__fork_cannot_unwind);
+ATF_TEST_CASE_BODY(child__fork_capture__fork_cannot_unwind)
+{
+ const pid_t parent_pid = ::getpid();
+ atf::utils::create_file("to-not-be-deleted", "");
+ try {
+ std::auto_ptr< process::child > child = process::child::fork_capture(
+ child_raise_exception< int, 123 >);
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.signaled());
+ ATF_REQUIRE(fs::exists(fs::path("to-not-be-deleted")));
+ } catch (const int i) {
+ // If we enter this clause, it is because an exception leaked from the
+ // hook.
+ INV(parent_pid != ::getpid());
+ INV(i == 123);
+ ::unlink("to-not-be-deleted");
+ std::exit(EXIT_SUCCESS);
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__fork_capture__fork_fail);
+ATF_TEST_CASE_BODY(child__fork_capture__fork_fail)
+{
+ process::detail::syscall_fork = fork_fail< 89 >;
+ try {
+ process::child::fork_capture(child_simple_function< 1, 'A' >);
+ fail("Expected exception but none raised");
+ } catch (const process::system_error& e) {
+ ATF_REQUIRE(atf::utils::grep_string("fork.*failed", e.what()));
+ ATF_REQUIRE_EQ(89, e.original_errno());
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__fork_files__ok_function);
+ATF_TEST_CASE_BODY(child__fork_files__ok_function)
+{
+ const fs::path file1("file1.txt");
+ const fs::path file2("file2.txt");
+
+ std::auto_ptr< process::child > child = process::child::fork_files(
+ child_simple_function< 15, 'Z' >, file1, file2);
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.exited());
+ ATF_REQUIRE_EQ(15, status.exitstatus());
+
+ ATF_REQUIRE( atf::utils::grep_file("^To stdout: Z$", file1.str()));
+ ATF_REQUIRE(!atf::utils::grep_file("^To stdout: Z$", file2.str()));
+
+ ATF_REQUIRE( atf::utils::grep_file("^To stderr: Z$", file2.str()));
+ ATF_REQUIRE(!atf::utils::grep_file("^To stderr: Z$", file1.str()));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__fork_files__ok_functor);
+ATF_TEST_CASE_BODY(child__fork_files__ok_functor)
+{
+ const fs::path filea("fileA.txt");
+ const fs::path fileb("fileB.txt");
+
+ atf::utils::create_file(filea.str(), "Initial stdout\n");
+ atf::utils::create_file(fileb.str(), "Initial stderr\n");
+
+ std::auto_ptr< process::child > child = process::child::fork_files(
+ child_simple_functor(16, "a functor"), filea, fileb);
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.exited());
+ ATF_REQUIRE_EQ(16, status.exitstatus());
+
+ ATF_REQUIRE( atf::utils::grep_file("^Initial stdout$", filea.str()));
+ ATF_REQUIRE(!atf::utils::grep_file("^Initial stdout$", fileb.str()));
+
+ ATF_REQUIRE( atf::utils::grep_file("^To stdout: a functor$", filea.str()));
+ ATF_REQUIRE(!atf::utils::grep_file("^To stdout: a functor$", fileb.str()));
+
+ ATF_REQUIRE( atf::utils::grep_file("^Initial stderr$", fileb.str()));
+ ATF_REQUIRE(!atf::utils::grep_file("^Initial stderr$", filea.str()));
+
+ ATF_REQUIRE( atf::utils::grep_file("^To stderr: a functor$", fileb.str()));
+ ATF_REQUIRE(!atf::utils::grep_file("^To stderr: a functor$", filea.str()));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__fork_files__catch_exceptions);
+ATF_TEST_CASE_BODY(child__fork_files__catch_exceptions)
+{
+ std::auto_ptr< process::child > child = process::child::fork_files(
+ child_throw_exception,
+ fs::path("unused.out"), fs::path("stderr"));
+
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.signaled());
+ ATF_REQUIRE_EQ(SIGABRT, status.termsig());
+
+ ATF_REQUIRE(atf::utils::grep_file("Caught.*A loose exception", "stderr"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__fork_files__new_session);
+ATF_TEST_CASE_BODY(child__fork_files__new_session)
+{
+ std::auto_ptr< process::child > child = process::child::fork_files(
+ child_check_own_session,
+ fs::path("unused.out"), fs::path("unused.err"));
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.exited());
+ ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__fork_files__inherit_stdout);
+ATF_TEST_CASE_BODY(child__fork_files__inherit_stdout)
+{
+ do_inherit_test("/dev/stdout", "stderr.txt", "stdout.txt", STDOUT_FILENO);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__fork_files__inherit_stderr);
+ATF_TEST_CASE_BODY(child__fork_files__inherit_stderr)
+{
+ do_inherit_test("stdout.txt", "/dev/stderr", "stderr.txt", STDERR_FILENO);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__fork_files__fork_cannot_exit);
+ATF_TEST_CASE_BODY(child__fork_files__fork_cannot_exit)
+{
+ const pid_t parent_pid = ::getpid();
+ atf::utils::create_file("to-not-be-deleted", "");
+
+ std::auto_ptr< process::child > child = process::child::fork_files(
+ child_return, fs::path("out"), fs::path("err"));
+ if (::getpid() != parent_pid) {
+ // If we enter this clause, it is because the hook returned.
+ ::unlink("to-not-be-deleted");
+ std::exit(EXIT_SUCCESS);
+ }
+
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.signaled());
+ ATF_REQUIRE(fs::exists(fs::path("to-not-be-deleted")));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__fork_files__fork_cannot_unwind);
+ATF_TEST_CASE_BODY(child__fork_files__fork_cannot_unwind)
+{
+ const pid_t parent_pid = ::getpid();
+ atf::utils::create_file("to-not-be-deleted", "");
+ try {
+ std::auto_ptr< process::child > child = process::child::fork_files(
+ child_raise_exception< int, 123 >, fs::path("out"),
+ fs::path("err"));
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.signaled());
+ ATF_REQUIRE(fs::exists(fs::path("to-not-be-deleted")));
+ } catch (const int i) {
+ // If we enter this clause, it is because an exception leaked from the
+ // hook.
+ INV(parent_pid != ::getpid());
+ INV(i == 123);
+ ::unlink("to-not-be-deleted");
+ std::exit(EXIT_SUCCESS);
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__fork_files__fork_fail);
+ATF_TEST_CASE_BODY(child__fork_files__fork_fail)
+{
+ process::detail::syscall_fork = fork_fail< 1234 >;
+ try {
+ process::child::fork_files(child_simple_function< 1, 'A' >,
+ fs::path("a.txt"), fs::path("b.txt"));
+ fail("Expected exception but none raised");
+ } catch (const process::system_error& e) {
+ ATF_REQUIRE(atf::utils::grep_string("fork.*failed", e.what()));
+ ATF_REQUIRE_EQ(1234, e.original_errno());
+ }
+ ATF_REQUIRE(!fs::exists(fs::path("a.txt")));
+ ATF_REQUIRE(!fs::exists(fs::path("b.txt")));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__fork_files__create_stdout_fail);
+ATF_TEST_CASE_BODY(child__fork_files__create_stdout_fail)
+{
+ process::detail::syscall_open = open_fail< ENOENT >;
+ std::auto_ptr< process::child > child = process::child::fork_files(
+ child_simple_function< 1, 'A' >, fs::path("raise-error"),
+ fs::path("created"));
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.signaled());
+ ATF_REQUIRE_EQ(SIGABRT, status.termsig());
+ ATF_REQUIRE(!fs::exists(fs::path("raise-error")));
+ ATF_REQUIRE(!fs::exists(fs::path("created")));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__fork_files__create_stderr_fail);
+ATF_TEST_CASE_BODY(child__fork_files__create_stderr_fail)
+{
+ process::detail::syscall_open = open_fail< ENOENT >;
+ std::auto_ptr< process::child > child = process::child::fork_files(
+ child_simple_function< 1, 'A' >, fs::path("created"),
+ fs::path("raise-error"));
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.signaled());
+ ATF_REQUIRE_EQ(SIGABRT, status.termsig());
+ ATF_REQUIRE(fs::exists(fs::path("created")));
+ ATF_REQUIRE(!fs::exists(fs::path("raise-error")));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__spawn__absolute_path);
+ATF_TEST_CASE_BODY(child__spawn__absolute_path)
+{
+ std::vector< std::string > args;
+ args.push_back("return-code");
+ args.push_back("12");
+
+ const fs::path program = get_helpers(this);
+ INV(program.is_absolute());
+ std::auto_ptr< process::child > child = process::child::spawn_files(
+ program, args, fs::path("out"), fs::path("err"));
+
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.exited());
+ ATF_REQUIRE_EQ(12, status.exitstatus());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__spawn__relative_path);
+ATF_TEST_CASE_BODY(child__spawn__relative_path)
+{
+ std::vector< std::string > args;
+ args.push_back("return-code");
+ args.push_back("13");
+
+ ATF_REQUIRE(::mkdir("root", 0755) != -1);
+ ATF_REQUIRE(::symlink(get_helpers(this).c_str(), "root/helpers") != -1);
+
+ std::auto_ptr< process::child > child = process::child::spawn_files(
+ fs::path("root/helpers"), args, fs::path("out"), fs::path("err"));
+
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.exited());
+ ATF_REQUIRE_EQ(13, status.exitstatus());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__spawn__basename_only);
+ATF_TEST_CASE_BODY(child__spawn__basename_only)
+{
+ std::vector< std::string > args;
+ args.push_back("return-code");
+ args.push_back("14");
+
+ ATF_REQUIRE(::symlink(get_helpers(this).c_str(), "helpers") != -1);
+
+ std::auto_ptr< process::child > child = process::child::spawn_files(
+ fs::path("helpers"), args, fs::path("out"), fs::path("err"));
+
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.exited());
+ ATF_REQUIRE_EQ(14, status.exitstatus());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__spawn__no_path);
+ATF_TEST_CASE_BODY(child__spawn__no_path)
+{
+ logging::set_inmemory();
+
+ std::vector< std::string > args;
+ args.push_back("return-code");
+ args.push_back("14");
+
+ const fs::path helpers = get_helpers(this);
+ utils::setenv("PATH", helpers.branch_path().c_str());
+ std::auto_ptr< process::child > child = process::child::spawn_capture(
+ fs::path(helpers.leaf_name()), args);
+
+ std::string line;
+ ATF_REQUIRE(std::getline(child->output(), line).good());
+ ATF_REQUIRE_MATCH("Failed to execute", line);
+ ATF_REQUIRE(!std::getline(child->output(), line));
+
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.signaled());
+ ATF_REQUIRE_EQ(SIGABRT, status.termsig());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__spawn__no_args);
+ATF_TEST_CASE_BODY(child__spawn__no_args)
+{
+ std::vector< std::string > args;
+ std::auto_ptr< process::child > child = process::child::spawn_capture(
+ get_helpers(this), args);
+
+ std::string line;
+ ATF_REQUIRE(std::getline(child->output(), line).good());
+ ATF_REQUIRE_EQ("Must provide a helper name", line);
+ ATF_REQUIRE(!std::getline(child->output(), line));
+
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.exited());
+ ATF_REQUIRE_EQ(EXIT_FAILURE, status.exitstatus());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__spawn__some_args);
+ATF_TEST_CASE_BODY(child__spawn__some_args)
+{
+ std::vector< std::string > args;
+ args.push_back("print-args");
+ args.push_back("foo");
+ args.push_back(" bar baz ");
+ std::auto_ptr< process::child > child = process::child::spawn_capture(
+ get_helpers(this), args);
+
+ std::string line;
+ ATF_REQUIRE(std::getline(child->output(), line).good());
+ ATF_REQUIRE_EQ("argv[0] = " + get_helpers(this).str(), line);
+ ATF_REQUIRE(std::getline(child->output(), line).good());
+ ATF_REQUIRE_EQ("argv[1] = print-args", line);
+ ATF_REQUIRE(std::getline(child->output(), line));
+ ATF_REQUIRE_EQ("argv[2] = foo", line);
+ ATF_REQUIRE(std::getline(child->output(), line));
+ ATF_REQUIRE_EQ("argv[3] = bar baz ", line);
+ ATF_REQUIRE(std::getline(child->output(), line));
+ ATF_REQUIRE_EQ("argv[4] = NULL", line);
+ ATF_REQUIRE(!std::getline(child->output(), line));
+
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.exited());
+ ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__spawn__missing_program);
+ATF_TEST_CASE_BODY(child__spawn__missing_program)
+{
+ std::vector< std::string > args;
+ std::auto_ptr< process::child > child = process::child::spawn_capture(
+ fs::path("a/b/c"), args);
+
+ std::string line;
+ ATF_REQUIRE(std::getline(child->output(), line).good());
+ const std::string exp = "Failed to execute a/b/c: ";
+ ATF_REQUIRE_EQ(exp, line.substr(0, exp.length()));
+ ATF_REQUIRE(!std::getline(child->output(), line));
+
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.signaled());
+ ATF_REQUIRE_EQ(SIGABRT, status.termsig());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__pid);
+ATF_TEST_CASE_BODY(child__pid)
+{
+ std::auto_ptr< process::child > child = process::child::fork_capture(
+ child_write_pid);
+
+ const int pid = child->pid();
+
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.exited());
+ ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus());
+
+ std::ifstream input("pidfile");
+ ATF_REQUIRE(input);
+ int read_pid;
+ input >> read_pid;
+ input.close();
+
+ ATF_REQUIRE_EQ(read_pid, pid);
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ utils::avoid_coredump_on_crash();
+
+ ATF_ADD_TEST_CASE(tcs, child__fork_capture__ok_function);
+ ATF_ADD_TEST_CASE(tcs, child__fork_capture__ok_functor);
+ ATF_ADD_TEST_CASE(tcs, child__fork_capture__catch_exceptions);
+ ATF_ADD_TEST_CASE(tcs, child__fork_capture__new_session);
+ ATF_ADD_TEST_CASE(tcs, child__fork_capture__pipe_fail);
+ ATF_ADD_TEST_CASE(tcs, child__fork_capture__fork_cannot_exit);
+ ATF_ADD_TEST_CASE(tcs, child__fork_capture__fork_cannot_unwind);
+ ATF_ADD_TEST_CASE(tcs, child__fork_capture__fork_fail);
+
+ ATF_ADD_TEST_CASE(tcs, child__fork_files__ok_function);
+ ATF_ADD_TEST_CASE(tcs, child__fork_files__ok_functor);
+ ATF_ADD_TEST_CASE(tcs, child__fork_files__catch_exceptions);
+ ATF_ADD_TEST_CASE(tcs, child__fork_files__new_session);
+ ATF_ADD_TEST_CASE(tcs, child__fork_files__inherit_stdout);
+ ATF_ADD_TEST_CASE(tcs, child__fork_files__inherit_stderr);
+ ATF_ADD_TEST_CASE(tcs, child__fork_files__fork_cannot_exit);
+ ATF_ADD_TEST_CASE(tcs, child__fork_files__fork_cannot_unwind);
+ ATF_ADD_TEST_CASE(tcs, child__fork_files__fork_fail);
+ ATF_ADD_TEST_CASE(tcs, child__fork_files__create_stdout_fail);
+ ATF_ADD_TEST_CASE(tcs, child__fork_files__create_stderr_fail);
+
+ ATF_ADD_TEST_CASE(tcs, child__spawn__absolute_path);
+ ATF_ADD_TEST_CASE(tcs, child__spawn__relative_path);
+ ATF_ADD_TEST_CASE(tcs, child__spawn__basename_only);
+ ATF_ADD_TEST_CASE(tcs, child__spawn__no_path);
+ ATF_ADD_TEST_CASE(tcs, child__spawn__no_args);
+ ATF_ADD_TEST_CASE(tcs, child__spawn__some_args);
+ ATF_ADD_TEST_CASE(tcs, child__spawn__missing_program);
+
+ ATF_ADD_TEST_CASE(tcs, child__pid);
+}