Beispiel #1
0
ATF_TC_BODY(fork_wait__core_size, tc)
{
    struct rlimit rl;
    rl.rlim_cur = 0;
    rl.rlim_max = RLIM_INFINITY;
    if (setrlimit(RLIMIT_CORE, &rl) == -1)
        atf_tc_skip("Failed to lower the core size limit");

    kyua_run_params_t run_params;
    kyua_run_params_init(&run_params);

    pid_t pid;
    kyua_error_t error = kyua_run_fork(&run_params, &pid);
    if (!kyua_error_is_set(error) && pid == 0)
        abort();

    ATF_REQUIRE(!kyua_error_is_set(error));
    int status; bool timed_out;
    error = kyua_run_wait(pid, &status, &timed_out);
    if (kyua_error_is_set(error))
        atf_tc_fail("wait failed; unexpected problem during exec?");

    ATF_REQUIRE(!timed_out);
    ATF_REQUIRE(WIFSIGNALED(status));
    ATF_REQUIRE_MSG(WCOREDUMP(status), "Core not dumped as expected");
}
Beispiel #2
0
ATF_TC_BODY(fork_wait__timeout, tc)
{
    kyua_run_params_t run_params;
    kyua_run_params_init(&run_params);
    run_params.timeout_seconds = 1;

    pid_t pid;
    kyua_error_t error = kyua_run_fork(&run_params, &pid);
    if (!kyua_error_is_set(error) && pid == 0) {
        sigset_t mask;
        sigemptyset(&mask);
        for (;;)
            sigsuspend(&mask);
    }
    ATF_REQUIRE(!kyua_error_is_set(error));
    int status; bool timed_out;
    kyua_run_wait(pid, &status, &timed_out);
    ATF_REQUIRE(timed_out);
    ATF_REQUIRE(WIFSIGNALED(status));
    ATF_REQUIRE_EQ(SIGKILL, WTERMSIG(status));
}
Beispiel #3
0
/// Uses kyua_fork, kyua_exec and kyua_wait to execute a subprocess.
///
/// \param program Path to the program to run.
/// \param args Arguments to the program.
/// \param [out] exitstatus The exit status of the subprocess, if it exits
///     successfully without timing out nor receiving a signal.
///
/// \return Returns the error code of kyua_run_wait (which should have the
///     error representation of the exec call in the subprocess).
static kyua_error_t
exec_check(const char* program, const char* const* args, int* exitstatus)
{
    kyua_run_params_t run_params;
    kyua_run_params_init(&run_params);

    pid_t pid;
    kyua_error_t error = kyua_run_fork(&run_params, &pid);
    if (!kyua_error_is_set(error) && pid == 0)
        kyua_run_exec(program, args);
    ATF_REQUIRE(!kyua_error_is_set(error));
    int status; bool timed_out;
    error = kyua_run_wait(pid, &status, &timed_out);
    if (!kyua_error_is_set(error)) {
        ATF_REQUIRE(!timed_out);
        ATF_REQUIRE_MSG(WIFEXITED(status),
                        "Subprocess expected to exit successfully");
        *exitstatus = WEXITSTATUS(status);
    }
    return error;
}
Beispiel #4
0
/// Forks and executes the cleanup of a test case in a controlled manner.
///
/// \param test_program Path to the test program to execute.
/// \param test_case Name of the test case to run.
/// \param result_file Path to the ATF result file created by the body of the
///     test case.  The cleanup may update such file if it fails.
/// \param user_variables Set of configuration variables to pass to the test.
/// \param run_params Settings to control the subprocess.
/// \param body_success The success value returned by run_body().
/// \param [out] success Set to true if the test case runs properly and returns
///     a result that is to be considered as successful.
///
/// \return OK if all goes well, an error otherwise.  Note that a failed test
/// case cleanup is denoted by setting success to false on exit, not by
/// returning an error.
static kyua_error_t
run_cleanup(const char* test_program, const char* test_case,
            const char* result_file, const char* const user_variables[],
            const kyua_run_params_t* run_params, const bool body_success,
            bool* success)
{
    kyua_error_t error;

    pid_t pid;
    error = kyua_run_fork(run_params, &pid);
    if (!kyua_error_is_set(error) && pid == 0) {
        exec_cleanup(test_program, test_case, user_variables);
    }
    assert(pid != -1 && pid != 0);
    if (kyua_error_is_set(error))
        goto out;

    int status; bool timed_out;
    error = kyua_run_wait(pid, &status, &timed_out);
    if (kyua_error_is_set(error))
        goto out;

    if (WIFSIGNALED(status) && WCOREDUMP(status)) {
        kyua_stacktrace_dump(test_program, pid, run_params, stderr);
    }

    if (body_success) {
        // If the body has reported a successful result, we inspect the status
        // of the cleanup routine.  If the cleanup has failed, then we need to
        // mark the test as broken.  However, if the body itself had failed, we
        // don't do this to give preference to the original result, which is
        // probably more informative.
        error = kyua_atf_result_cleanup_rewrite(result_file, status,
                                                timed_out, success);
    }

out:
    return error;
}
Beispiel #5
0
/// Forks and executes the body of a test case in a controlled manner.
///
/// \param test_program Path to the test program to execute.
/// \param test_case Name of the test case to run.
/// \param result_file Path to the ATF result file to be created.
/// \param user_variables Set of configuration variables to pass to the test.
/// \param run_params Settings to control the subprocess.
/// \param [out] success Set to true if the test case runs properly and returns
///     a result that is to be considered as successful.
///
/// \return OK if all goes well, an error otherwise.  Note that a failed test
/// case is denoted by setting success to false on exit, not by returning an
/// error.
static kyua_error_t
run_body(const char* test_program, const char* test_case,
         const char* result_file, const char* const user_variables[],
         const kyua_run_params_t* run_params, bool* success)
{
    kyua_error_t error;

    char* tmp_result_file;
    error = kyua_fs_concat(&tmp_result_file, run_params->work_directory,
                           "result.txt", NULL);
    if (kyua_error_is_set(error))
        goto out;

    pid_t pid;
    error = kyua_run_fork(run_params, &pid);
    if (!kyua_error_is_set(error) && pid == 0) {
        exec_body(test_program, test_case, tmp_result_file, user_variables);
    }
    assert(pid != -1 && pid != 0);
    if (kyua_error_is_set(error))
        goto out_tmp_result_file;

    int status; bool timed_out;
    error = kyua_run_wait(pid, &status, &timed_out);
    if (kyua_error_is_set(error))
        goto out_tmp_result_file;

    if (WIFSIGNALED(status) && WCOREDUMP(status)) {
        kyua_stacktrace_dump(test_program, pid, run_params, stderr);
    }

    error = kyua_atf_result_rewrite(tmp_result_file, result_file, status,
                                    timed_out, success);

out_tmp_result_file:
    free(tmp_result_file);
out:
    return error;
}
Beispiel #6
0
/// Performs a signal delivery test to the work directory handling code.
///
/// \param signo The signal to deliver.
static void
work_directory_signal_check(const int signo)
{
    char* tmpdir;
    RE(kyua_fs_make_absolute("worktest", &tmpdir));
    ATF_REQUIRE(mkdir(tmpdir, 0755) != -1);
    RE(kyua_env_set("TMPDIR", tmpdir));

    char* work_directory;
    RE(kyua_run_work_directory_enter("template.XXXXXX", getuid(), getgid(),
                                     &work_directory));

    kyua_run_params_t run_params;
    kyua_run_params_init(&run_params);
    run_params.work_directory = work_directory;

    pid_t pid;
    RE(kyua_run_fork(&run_params, &pid));
    if (pid == 0) {
        sleep(run_params.timeout_seconds * 2);
        abort();
    }

    // This should cause the handled installed by the work_directory management
    // code to terminate the subprocess so that we get a chance to run the
    // cleanup code ourselves.
    kill(getpid(), signo);

    int status; bool timed_out;
    RE(kyua_run_wait(pid, &status, &timed_out));
    ATF_REQUIRE(!timed_out);
    ATF_REQUIRE(WIFSIGNALED(status));
    ATF_REQUIRE_EQ(SIGKILL, WTERMSIG(status));

    ATF_REQUIRE(rmdir(tmpdir) == -1);  // Not yet empty.
    RE(kyua_run_work_directory_leave(&work_directory));
    ATF_REQUIRE(rmdir(tmpdir) != -1);
    free(tmpdir);
}
Beispiel #7
0
/// Uses kyua_fork and kyua_wait to spawn a subprocess.
///
/// \param run_params The parameters to configure the subprocess.  Can be NULL
///     to indicate to use the default set of parameters.
/// \param hook Any of the check_* functions provided in this module.
/// \param cookie The data to pass to the hook.
///
/// \return True if the subprocess exits successfully; false otherwise.
static bool
fork_check(const kyua_run_params_t* run_params,
           void (*hook)(const void*), const void* cookie)
{
    kyua_run_params_t default_run_params;
    if (run_params == NULL) {
        kyua_run_params_init(&default_run_params);
        run_params = &default_run_params;
    }

    pid_t pid;
    kyua_error_t error = kyua_run_fork(run_params, &pid);
    if (!kyua_error_is_set(error) && pid == 0)
        hook(cookie);
    ATF_REQUIRE(!kyua_error_is_set(error));
    int status; bool timed_out;
    error = kyua_run_wait(pid, &status, &timed_out);
    if (kyua_error_is_set(error))
        atf_tc_fail("wait failed; unexpected problem during exec?");
    ATF_REQUIRE(!timed_out);
    return WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS;
}
Beispiel #8
0
/// Lists the test cases in a test program.
///
/// \param test_program Path to the test program for which to list the test
///     cases.  Should be absolute.
/// \param run_params Execution parameters to configure the test process.
///
/// \return An error if the listing fails; OK otherwise.
static kyua_error_t
list_test_cases(const char* test_program, const kyua_run_params_t* run_params)
{
    kyua_error_t error;

    char* work_directory;
    error = kyua_run_work_directory_enter(WORKDIR_TEMPLATE,
                                          run_params->unprivileged_user,
                                          run_params->unprivileged_group,
                                          &work_directory);
    if (kyua_error_is_set(error))
        goto out;
    kyua_run_params_t real_run_params = *run_params;
    real_run_params.work_directory = work_directory;

    int stdout_fds[2];
    if (pipe(stdout_fds) == -1) {
        error = kyua_libc_error_new(errno, "pipe failed");
        goto out_work_directory;
    }

    pid_t pid;
    error = kyua_run_fork(&real_run_params, &pid);
    if (!kyua_error_is_set(error) && pid == 0) {
        run_list(test_program, stdout_fds);
    }
    assert(pid != -1 && pid != 0);
    if (kyua_error_is_set(error))
        goto out_stdout_fds;

    FILE* tmp_output = NULL;  // Initialize to shut up gcc warning.
    error = create_file_in_work_directory(real_run_params.work_directory,
                                          "list.txt", "w+", &tmp_output);
    if (kyua_error_is_set(error))
        goto out_stdout_fds;

    close(stdout_fds[1]); stdout_fds[1] = -1;
    kyua_error_t parse_error = atf_list_parse(stdout_fds[0], tmp_output);
    stdout_fds[0] = -1;  // Guaranteed closed by atf_list_parse.
    // Delay reporting of parse errors to later.  If we detect a problem while
    // waiting for the test program, we know that the parsing has most likely
    // failed and therefore the error with the program is more important for
    // reporting purposes.

    int status; bool timed_out;
    error = kyua_run_wait(pid, &status, &timed_out);
    if (kyua_error_is_set(error))
        goto out_tmp_output;
    if (!WIFEXITED(status) || WEXITSTATUS(status) != EXIT_SUCCESS) {
        error = kyua_generic_error_new("Test program list did not return "
                                       "success");
        goto out_tmp_output;
    }

    error = kyua_error_subsume(error, parse_error);
    if (!kyua_error_is_set(error)) {
        rewind(tmp_output);
        error = dump_file(tmp_output, stdout);
    }

out_tmp_output:
    fclose(tmp_output);
out_stdout_fds:
    if (stdout_fds[0] != -1)
        close(stdout_fds[0]);
    if (stdout_fds[1] != -1)
        close(stdout_fds[1]);
out_work_directory:
    error = kyua_error_subsume(error,
        kyua_run_work_directory_leave(&work_directory));
out:
    return error;
}
Beispiel #9
0
/// Gathers a stacktrace of a crashed program.
///
/// \param program The name of the binary that crashed and dumped a core file.
///     Can be either absolute or relative.
/// \param dead_pid The PID of the process that dumped core.
/// \param original_run_params Parameters with which the original binary was
///     executed.  These are reused to run GDB, but adjusted with GDB-specific
///     settings.  Of special interest, the work directory is used to search for
///     the core file.
/// \param output Stream into which to dump the stack trace and any additional
///     information.
///
/// \post If anything goes wrong, the diagnostic messages are written to the
/// output.  This function returns no errors.
void
kyua_stacktrace_dump(const char* program, const pid_t dead_pid,
                     const kyua_run_params_t* original_run_params, FILE* output)
{
    fprintf(output, "Process with PID %d dumped core; attempting to gather "
            "stack trace\n", dead_pid);

    const kyua_run_params_t run_params = gdb_run_params(original_run_params);

    kyua_error_t error = kyua_error_ok();

    char* core_file = kyua_stacktrace_find_core(const_basename(program),
                                                run_params.work_directory,
                                                dead_pid);
    if (core_file == NULL) {
        fprintf(output, "Cannot find any core file\n");
        goto out;
    }

    // We must flush the output stream right before invoking fork, so that the
    // subprocess does not have any unflushed data.  Failure to do so results in
    // the messages above being written twice to the output.
    fflush(output);
    pid_t pid;
    error = kyua_run_fork(&run_params, &pid);
    if (!kyua_error_is_set(error) && pid == 0) {
        run_gdb(program, core_file, output);
    }
    assert(pid != -1 && pid != 0);
    if (kyua_error_is_set(error))
        goto out_core_file;

    int status; bool timed_out;
    error = kyua_run_wait(pid, &status, &timed_out);
    if (kyua_error_is_set(error))
        goto out_core_file;

    if (timed_out) {
        fprintf(output, "GDB failed; timed out\n");
    } else {
        if (WIFEXITED(status)) {
            if (WEXITSTATUS(status) == EXIT_SUCCESS)
                fprintf(output, "GDB exited successfully\n");
            else
                fprintf(output, "GDB failed with code %d; see output above for "
                        "details\n", WEXITSTATUS(status));
        } else {
            assert(WIFSIGNALED(status));
            fprintf(output, "GDB received signal %d; see output above for "
                    "details\n", WTERMSIG(status));
        }
    }

out_core_file:
    free(core_file);
out:
    if (kyua_error_is_set(error)) {
        kyua_error_fprintf(output, error, "Failed to gather stacktrace");
        free(error);
    }
}