opnsense-src/contrib/kyua/utils/process/child_test.cpp
Brooks Davis b0d29bc47d Import the kyua test framework.
Having kyua in the base system will simplify automated testing in CI and
eliminates bootstrapping issues on new platforms.

The build of kyua is controlled by WITH(OUT)_TESTS_SUPPORT.

Reviewed by:	emaste
Obtained from:	CheriBSD
Sponsored by:	DARPA
Differential Revision:	https://reviews.freebsd.org/D24103
2020-03-23 19:01:23 +00:00

846 lines
27 KiB
C++

// 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);
}