765 lines
25 KiB
C++
765 lines
25 KiB
C++
|
// Copyright 2010 Google Inc.
|
||
|
// 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 <atf-c++.hpp>
|
||
|
|
||
|
#include "utils/defs.hpp"
|
||
|
#include "utils/env.hpp"
|
||
|
#include "utils/format/macros.hpp"
|
||
|
#include "utils/fs/operations.hpp"
|
||
|
#include "utils/logging/macros.hpp"
|
||
|
#include "utils/process/exceptions.hpp"
|
||
|
#include "utils/process/system.hpp"
|
||
|
#include "utils/sanity.hpp"
|
||
|
|
||
|
namespace fs = utils::fs;
|
||
|
namespace logging = utils::logging;
|
||
|
namespace process = utils::process;
|
||
|
|
||
|
|
||
|
namespace {
|
||
|
|
||
|
|
||
|
/// 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 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.
|
||
|
/// \param [out] unused_fildes A pointer to a 2-integer array.
|
||
|
///
|
||
|
/// \return Always -1.
|
||
|
template< int Errno >
|
||
|
static pid_t
|
||
|
pipe_fail(int* UTILS_UNUSED_PARAM(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 << std::endl;
|
||
|
std::cerr << std::endl;
|
||
|
|
||
|
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__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__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)
|
||
|
{
|
||
|
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__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__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);
|
||
|
}
|