// Copyright 2012 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 "atf_result.h"

#include <sys/types.h>
#include <sys/wait.h>

#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "error.h"
#include "result.h"


// Enumeration of the different result types returned by an ATF test case.
enum atf_status {
    ATF_STATUS_EXPECTED_DEATH,
    ATF_STATUS_EXPECTED_EXIT,
    ATF_STATUS_EXPECTED_FAILURE,
    ATF_STATUS_EXPECTED_SIGNAL,
    ATF_STATUS_EXPECTED_TIMEOUT,
    ATF_STATUS_FAILED,
    ATF_STATUS_PASSED,
    ATF_STATUS_SKIPPED,

    // The broken status below is never returned by the test cases themselves.
    // We use it internally to pass around problems detected while dealing with
    // the test case itself (like an invalid result file).
    ATF_STATUS_BROKEN,
};


/// Magic number representing a missing argument to the test result status.
///
/// Use this to specify that an expected_exit or expected_signal result accepts
/// any exit code or signal, respectively.
#define NO_STATUS_ARG -1


/// Removes a trailing newline from a string (supposedly read by fgets(3)).
///
/// \param [in,out] str The string to remove the trailing newline from.
///
/// \return True if there was a newline character; false otherwise.
static bool
trim_newline(char* str)
{
    const size_t length = strlen(str);
    if (length == 0) {
        return false;
    } else {
        if (str[length - 1] == '\n') {
            str[length - 1] = '\0';
            return true;
        } else {
            return false;
        }
    }
}


/// Force read on stream to see if we are really at EOF.
///
/// A call to fgets(3) will not return EOF when it returns valid data.  But
/// because of our semantics below, we need to be able to tell if more lines are
/// available before actually reading them.
///
/// \param input The stream to check for EOF.
///
/// \return True if the stream is not at EOF yet; false otherwise.
static bool
is_really_eof(FILE* input)
{
    const int ch = getc(input);
    const bool real_eof = feof(input);
    (void)ungetc(ch, input);
    return real_eof;
}


/// Parses the optional argument to a result status.
///
/// \param str Pointer to the argument.  May be \0 in those cases where the
///     status does not have any argument.
/// \param [out] status_arg Value of the parsed argument.
///
/// \return OK if the argument exists and is valid, or if it does not exist; an
/// error otherwise.
static kyua_error_t
parse_status_arg(const char* str, int* status_arg)
{
    if (*str == '\0') {
        *status_arg = NO_STATUS_ARG;
        return kyua_error_ok();
    }

    const size_t length = strlen(str);
    if (*str != '(' || *(str + length - 1) != ')')
        return kyua_generic_error_new("Invalid status argument %s", str);
    const char* const arg = str + 1;

    char* endptr;
    const long value = strtol(arg, &endptr, 10);
    if (arg[0] == '\0' || endptr != str + length - 1)
        return kyua_generic_error_new("Invalid status argument %s: not a "
                                      "number", str);
    if (errno == ERANGE && (value == LONG_MAX || value == LONG_MIN))
        return kyua_generic_error_new("Invalid status argument %s: out of "
                                      "range", str);
    if (value < INT_MIN || value > INT_MAX)
        return kyua_generic_error_new("Invalid status argument %s: out of "
                                      "range", str);

    *status_arg = (int)value;
    return kyua_error_ok();
}


/// Parses a textual result status.
///
/// \param str The text to parse.
/// \param [out] status Status type if the input is valid.
/// \param [out] status_arg Optional integral argument to the status.
/// \param [out] need_reason Whether the detected status requires a reason.
///
/// \return An error if the status is not valid.
static kyua_error_t
parse_status(const char* str, enum atf_status* status, int* status_arg,
             bool* need_reason)
{
    if (strcmp(str, "passed") == 0) {
        *status = ATF_STATUS_PASSED;
        *need_reason = false;
        return kyua_error_ok();
    } else if (strcmp(str, "failed") == 0) {
        *status = ATF_STATUS_FAILED;
        *need_reason = true;
        return kyua_error_ok();
    } else if (strcmp(str, "skipped") == 0) {
        *status = ATF_STATUS_SKIPPED;
        *need_reason = true;
        return kyua_error_ok();
    } else if (strcmp(str, "expected_death") == 0) {
        *status = ATF_STATUS_EXPECTED_DEATH;
        *need_reason = true;
        return kyua_error_ok();
    } else if (strncmp(str, "expected_exit", 13) == 0) {
        *status = ATF_STATUS_EXPECTED_EXIT;
        *need_reason = true;
        return parse_status_arg(str + 13, status_arg);
    } else if (strcmp(str, "expected_failure") == 0) {
        *status = ATF_STATUS_EXPECTED_FAILURE;
        *need_reason = true;
        return kyua_error_ok();
    } else if (strncmp(str, "expected_signal", 15) == 0){
        *status = ATF_STATUS_EXPECTED_SIGNAL;
        *need_reason = true;
        return parse_status_arg(str + 15, status_arg);
    } else if (strcmp(str, "expected_timeout") == 0) {
        *status = ATF_STATUS_EXPECTED_TIMEOUT;
        *need_reason = true;
        return kyua_error_ok();
    } else {
        return kyua_generic_error_new("Unknown test case result status %s",
                                      str);
    }
}


/// Advances a pointer to a buffer to its end.
///
/// \param [in,out] buffer Current buffer contents; updated on exit to point to
///     the termination character.
/// \param [in,out] buffer_size Current buffer size; updated on exit to account
///     for the decreased capacity due to the pointer increase.
static void
advance(char** buffer, size_t* buffer_size)
{
    const size_t increment = strlen(*buffer);
    *buffer += increment;
    *buffer_size -= increment;
}


/// Extracts the result reason from the input file.
///
/// \pre This can only be called for those result types that require a reason.
///
/// \param [in,out] input The file from which to read.
/// \param first_line The first line of the reason.  Because this is part of the
///     same line in which the result status is printed, this line has already
///     been read by the caller and thus must be provided here.
/// \param [out] output Buffer to which to write the full reason.
/// \param output_size Size of the output buffer.
///
/// \return An error if there was no reason in the input or if there is a
/// problem reading it.
static kyua_error_t
read_reason(FILE* input, const char* first_line, char* output,
            size_t output_size)
{
    if (first_line == NULL || *first_line == '\0')
        return kyua_generic_error_new("Test case should have reported a "
                                      "failure reason but didn't");

    snprintf(output, output_size, "%s", first_line);
    advance(&output, &output_size);

    bool had_newline = true;
    while (!is_really_eof(input)) {
        if (had_newline) {
            snprintf(output, output_size, "<<NEWLINE>>");
            advance(&output, &output_size);
        }

        if (fgets(output, output_size, input) == NULL) {
            assert(ferror(input));
            return kyua_libc_error_new(errno, "Failed to read reason from "
                                       "result file");
        }
        had_newline = trim_newline(output);
        advance(&output, &output_size);
    }

    return kyua_error_ok();
}


/// Parses a results file written by an ATF test case.
///
/// \param input_name Path to the result file to parse.
/// \param [out] status Type of result.
/// \param [out] status_arg Optional integral argument to the status.
/// \param [out] reason Textual explanation of the result, if any.
/// \param reason_size Length of the reason output buffer.
///
/// \return An error if the input_name file has an invalid syntax; OK otherwise.
static kyua_error_t
read_atf_result(const char* input_name, enum atf_status* status,
                int* status_arg, char* const reason, const size_t reason_size)
{
    kyua_error_t error = kyua_error_ok();

    FILE* input = fopen(input_name, "r");
    if (input == NULL) {
        error = kyua_generic_error_new("Premature exit");
        goto out;
    }

    char line[1024];
    if (fgets(line, sizeof(line), input) == NULL) {
        if (ferror(input)) {
            error = kyua_libc_error_new(errno, "Failed to read result from "
                                        "file %s", input_name);
            goto out_input;
        } else {
            assert(feof(input));
            error = kyua_generic_error_new("Empty result file %s", input_name);
            goto out_input;
        }
    }

    if (!trim_newline(line)) {
        error = kyua_generic_error_new("Missing newline in result file");
        goto out_input;
    }

    char* reason_start = strstr(line, ": ");
    if (reason_start != NULL) {
        *reason_start = '\0';
        *(reason_start + 1) = '\0';
        reason_start += 2;
    }

    bool need_reason = false;  // Initialize to shut up gcc warning.
    error = parse_status(line, status, status_arg, &need_reason);
    if (kyua_error_is_set(error))
        goto out_input;

    if (need_reason) {
        error = read_reason(input, reason_start, reason, reason_size);
    } else {
        if (reason_start != NULL || !is_really_eof(input)) {
            error = kyua_generic_error_new("Found unexpected reason in passed "
                                           "test result");
            goto out_input;
        }
        reason[0] = '\0';
    }

out_input:
    fclose(input);
out:
    return error;
}


/// Writes a generic result file for an ATF broken result.
///
/// \param reason Textual explanation of the result.
/// \param status Exit code of the test program as returned by wait().
/// \param output Path to the generic result file to create.
/// \param [out] success Whether the result should be considered a success or
///     not; e.g. passed and skipped are successful, but failed is not.
///
/// \return An error if the conversion fails; OK otherwise.
static kyua_error_t
convert_broken(const char* reason, int status, const char* output,
               bool* success)
{
    if (WIFEXITED(status)) {
        *success = false;
        return kyua_result_write(
            output, KYUA_RESULT_BROKEN, "%s; test case exited with code %d",
            reason, WEXITSTATUS(status));
    } else {
        assert(WIFSIGNALED(status));
        *success = false;
        return kyua_result_write(
            output, KYUA_RESULT_BROKEN, "%s; test case received signal %d%s",
            reason, WTERMSIG(status),
            WCOREDUMP(status) ? " (core dumped)" : "");
    }
}


/// Writes a generic result file for an ATF expected_death result.
///
/// \param reason Textual explanation of the result.
/// \param status Exit code of the test program as returned by wait().
/// \param output Path to the generic result file to create.
/// \param [out] success Whether the result should be considered a success or
///     not; e.g. passed and skipped are successful, but failed is not.
///
/// \return An error if the conversion fails; OK otherwise.
static kyua_error_t
convert_expected_death(const char* reason, int status, const char* output,
                       bool* success)
{
    if (WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS) {
        *success = false;
        return kyua_result_write(
            output, KYUA_RESULT_FAILED, "Test case expected to die but exited "
            "successfully");
    } else {
        *success = true;
        return kyua_result_write(
            output, KYUA_RESULT_EXPECTED_FAILURE, "%s", reason);
    }
}


/// Writes a generic result file for an ATF expected_exit result
///
/// \param status_arg Optional integral argument to the status.
/// \param reason Textual explanation of the result.
/// \param status Exit code of the test program as returned by wait().
/// \param output Path to the generic result file to create.
/// \param [out] success Whether the result should be considered a success or
///     not; e.g. passed and skipped are successful, but failed is not.
///
/// \return An error if the conversion fails; OK otherwise.
static kyua_error_t
convert_expected_exit(const int status_arg, const char* reason, int status,
                      const char* output, bool* success)
{
    if (WIFEXITED(status)) {
        if (status_arg == NO_STATUS_ARG || status_arg == WEXITSTATUS(status)) {
            *success = true;
            return kyua_result_write(
                output, KYUA_RESULT_EXPECTED_FAILURE, "%s", reason);
        } else {
            *success = false;
            return kyua_result_write(
                output, KYUA_RESULT_FAILED, "Test case expected to exit with "
                "code %d but got code %d", status_arg, WEXITSTATUS(status));
        }
    } else {
        assert(WIFSIGNALED(status));
        *success = false;
        return kyua_result_write(
            output, KYUA_RESULT_FAILED, "Test case expected to exit normally "
            "but received signal %d%s", WTERMSIG(status),
            WCOREDUMP(status) ? " (core dumped)" : "");
    }
}


/// Writes a generic result file for an ATF expected_failure result.
///
/// \param reason Textual explanation of the result.
/// \param status Exit code of the test program as returned by wait().
/// \param output Path to the generic result file to create.
/// \param [out] success Whether the result should be considered a success or
///     not; e.g. passed and skipped are successful, but failed is not.
///
/// \return An error if the conversion fails; OK otherwise.
static kyua_error_t
convert_expected_failure(const char* reason, int status, const char* output,
                         bool* success)
{
    if (WIFEXITED(status)) {
        if (WEXITSTATUS(status) == EXIT_SUCCESS) {
            *success = true;
            return kyua_result_write(
                output, KYUA_RESULT_EXPECTED_FAILURE, "%s", reason);
        } else {
            *success = false;
            return kyua_result_write(
                output, KYUA_RESULT_FAILED, "Test case expected a failure but "
                "exited with error code %d", WEXITSTATUS(status));
        }
    } else {
        assert(WIFSIGNALED(status));
        *success = false;
        return kyua_result_write(
            output, KYUA_RESULT_FAILED, "Test case expected a failure but "
            "received signal %d%s", WTERMSIG(status),
            WCOREDUMP(status) ? " (core dumped)" : "");
    }
}


/// Writes a generic result file for an ATF expected_signal result.
///
/// \param status_arg Optional integral argument to the status.
/// \param reason Textual explanation of the result.
/// \param status Exit code of the test program as returned by wait().
/// \param output Path to the generic result file to create.
/// \param [out] success Whether the result should be considered a success or
///     not; e.g. passed and skipped are successful, but failed is not.
///
/// \return An error if the conversion fails; OK otherwise.
static kyua_error_t
convert_expected_signal(const int status_arg, const char* reason, int status,
                        const char* output, bool* success)
{
    if (WIFSIGNALED(status)) {
        if (status_arg == NO_STATUS_ARG || status_arg == WTERMSIG(status)) {
            *success = true;
            return kyua_result_write(
                output, KYUA_RESULT_EXPECTED_FAILURE, "%s", reason);
        } else {
            *success = false;
            return kyua_result_write(
                output, KYUA_RESULT_FAILED, "Test case expected to receive "
                "signal %d but got %d", status_arg, WTERMSIG(status));
        }
    } else {
        assert(WIFEXITED(status));
        *success = false;
        return kyua_result_write(
            output, KYUA_RESULT_FAILED, "Test case expected to receive a "
            "signal but exited with code %d", WEXITSTATUS(status));
    }
}


/// Writes a generic result file for an ATF expected_timeout result.
///
/// \param status Exit code of the test program as returned by wait().
/// \param output Path to the generic result file to create.
/// \param [out] success Whether the result should be considered a success or
///     not; e.g. passed and skipped are successful, but failed is not.
///
/// \return An error if the conversion fails; OK otherwise.
static kyua_error_t
convert_expected_timeout(int status, const char* output, bool* success)
{
    if (WIFEXITED(status)) {
        *success = false;
        return kyua_result_write(
            output, KYUA_RESULT_FAILED, "Test case expected to time out but "
            "exited with code %d", WEXITSTATUS(status));
    } else {
        assert(WIFSIGNALED(status));
        *success = false;
        return kyua_result_write(
            output, KYUA_RESULT_FAILED, "Test case expected to time out but "
            "received signal %d%s", WTERMSIG(status),
            WCOREDUMP(status) ? " (core dumped)" : "");
    }
}


/// Writes a generic result file for an ATF failed result.
///
/// \param reason Textual explanation of the result.
/// \param status Exit code of the test program as returned by wait().
/// \param output Path to the generic result file to create.
/// \param [out] success Whether the result should be considered a success or
///     not; e.g. passed and skipped are successful, but failed is not.
///
/// \return An error if the conversion fails; OK otherwise.
static kyua_error_t
convert_failed(const char* reason, int status, const char* output,
               bool* success)
{
    if (WIFEXITED(status)) {
        if (WEXITSTATUS(status) == EXIT_SUCCESS) {
            *success = false;
            return kyua_result_write(
                output, KYUA_RESULT_BROKEN, "Test case reported a failed "
                "result but exited with a successful exit code");
        } else {
            *success = false;
            return kyua_result_write(
                output, KYUA_RESULT_FAILED, "%s", reason);
        }
    } else {
        assert(WIFSIGNALED(status));
        *success = false;
        return kyua_result_write(
            output, KYUA_RESULT_BROKEN, "Test case reported a failed result "
            "but received signal %d%s", WTERMSIG(status),
            WCOREDUMP(status) ? " (core dumped)" : "");
    }
}


/// Writes a generic result file for an ATF passed result.
///
/// \param status Exit code of the test program as returned by wait().
/// \param output Path to the generic result file to create.
/// \param [out] success Whether the result should be considered a success or
///     not; e.g. passed and skipped are successful, but failed is not.
///
/// \return An error if the conversion fails; OK otherwise.
static kyua_error_t
convert_passed(int status, const char* output, bool* success)
{
    if (WIFEXITED(status)) {
        if (WEXITSTATUS(status) == EXIT_SUCCESS) {
            *success = true;
            return kyua_result_write(output, KYUA_RESULT_PASSED, NULL);
        } else {
            *success = false;
            return kyua_result_write(
                output, KYUA_RESULT_BROKEN, "Test case reported a passed "
                "result but returned a non-zero exit code %d",
                WEXITSTATUS(status));
        }
    } else {
        assert(WIFSIGNALED(status));
        *success = false;
        return kyua_result_write(
            output, KYUA_RESULT_BROKEN, "Test case reported a passed result "
            "but received signal %d%s", WTERMSIG(status),
            WCOREDUMP(status) ? " (core dumped)" : "");
    }
}


/// Writes a generic result file for an ATF skipped result.
///
/// \param reason Textual explanation of the result.
/// \param status Exit code of the test program as returned by wait().
/// \param output Path to the generic result file to create.
/// \param [out] success Whether the result should be considered a success or
///     not; e.g. passed and skipped are successful, but failed is not.
///
/// \return An error if the conversion fails; OK otherwise.
static kyua_error_t
convert_skipped(const char* reason, int status, const char* output,
                bool* success)
{
    if (WIFEXITED(status)) {
        if (WEXITSTATUS(status) == EXIT_SUCCESS) {
            *success = true;
            return kyua_result_write(output, KYUA_RESULT_SKIPPED, "%s", reason);
        } else {
            *success = false;
            return kyua_result_write(
                output, KYUA_RESULT_BROKEN, "Test case reported a skipped "
                "result but returned a non-zero exit code %d",
                WEXITSTATUS(status));
        }
    } else {
        *success = false;
        assert(WIFSIGNALED(status));
        return kyua_result_write(
            output, KYUA_RESULT_BROKEN, "Test case reported a skipped result "
            "but received signal %d%s", WTERMSIG(status),
            WCOREDUMP(status) ? " (core dumped)" : "");
    }
}


/// Writes a generic result file based on an ATF result and an exit code.
///
/// \param status Type of the ATF result.
/// \param status_arg Optional integral argument to the status.
/// \param reason Textual explanation of the result.
/// \param wait_status Exit code of the test program as returned by wait().
/// \param timed_out Whether the test program timed out or not.
/// \param output Path to the generic result file to create.
/// \param [out] success Whether the result should be considered a success or
///     not; e.g. passed and skipped are successful, but failed is not.
///
/// \return An error if the conversion fails; OK otherwise.
static kyua_error_t
convert_result(const enum atf_status status, const int status_arg,
               const char* reason, const int wait_status, const bool timed_out,
               const char* output, bool* success)
{
    if (timed_out) {
        if (status == ATF_STATUS_EXPECTED_TIMEOUT) {
            *success = true;
            return kyua_result_write(
                output, KYUA_RESULT_EXPECTED_FAILURE, "%s", reason);
        } else {
            assert(status == ATF_STATUS_BROKEN);
            *success = false;
            return kyua_result_write(
                output, KYUA_RESULT_BROKEN, "Test case body timed out");
        }
    }

    switch (status) {
    case ATF_STATUS_BROKEN:
        return convert_broken(reason, wait_status, output, success);

    case ATF_STATUS_EXPECTED_DEATH:
        return convert_expected_death(reason, wait_status, output, success);

    case ATF_STATUS_EXPECTED_EXIT:
        return convert_expected_exit(status_arg, reason, wait_status, output,
                                     success);

    case ATF_STATUS_EXPECTED_FAILURE:
        return convert_expected_failure(reason, wait_status, output, success);

    case ATF_STATUS_EXPECTED_SIGNAL:
        return convert_expected_signal(status_arg, reason, wait_status, output,
                                       success);

    case ATF_STATUS_EXPECTED_TIMEOUT:
        return convert_expected_timeout(wait_status, output, success);

    case ATF_STATUS_FAILED:
        return convert_failed(reason, wait_status, output, success);

    case ATF_STATUS_PASSED:
        return convert_passed(wait_status, output, success);

    case ATF_STATUS_SKIPPED:
        return convert_skipped(reason, wait_status, output, success);
    }

    assert(false);
}


/// Writes a generic result file based on an ATF result file and an exit code.
///
/// \param input_name Path to the ATF result file to parse.
/// \param output_name Path to the generic result file to create.
/// \param wait_status Exit code of the test program as returned by wait().
/// \param timed_out Whether the test program timed out or not.
/// \param [out] success Whether the result should be considered a success or
///     not; e.g. passed and skipped are successful, but failed is not.
///
/// \return An error if the conversion fails; OK otherwise.
kyua_error_t
kyua_atf_result_rewrite(const char* input_name, const char* output_name,
                        const int wait_status, const bool timed_out,
                        bool* success)
{
    enum atf_status status; int status_arg; char reason[1024];
    status = ATF_STATUS_BROKEN;  // Initialize to shut up gcc warning.
    const kyua_error_t error = read_atf_result(input_name, &status, &status_arg,
                                               reason, sizeof(reason));
    if (kyua_error_is_set(error)) {
        // Errors while parsing the ATF result file can often be attributed to
        // the result file being bogus.  Therefore, just mark the test case as
        // broken, because it possibly is.
        status = ATF_STATUS_BROKEN;
        kyua_error_format(error, reason, sizeof(reason));
        kyua_error_free(error);
    }

    // Errors converting the loaded result to the final result file are not due
    // to a bad test program: they are because our own code fails (e.g. cannot
    // create the output file).  These need to be returned to the caller.
    return convert_result(status, status_arg, reason, wait_status, timed_out,
                          output_name, success);
}


/// Creates a result file for a failed cleanup routine.
///
/// This function is supposed to be invoked after the body has had a chance to
/// create its own result file, and only if the body has terminated with a
/// non-failure result.
///
/// \param output_name Path to the generic result file to create.
/// \param wait_status Exit code of the test program as returned by wait().
/// \param timed_out Whether the test program timed out or not.
/// \param [out] success Whether the result should be considered a success or
///     not; i.e. a clean exit is successful, but anything else is a failure.
///
/// \return An error if there is a problem writing the result; OK otherwise.
kyua_error_t
kyua_atf_result_cleanup_rewrite(const char* output_name, int wait_status,
                                const bool timed_out, bool* success)
{
    if (timed_out) {
        *success = false;
        return kyua_result_write(
            output_name, KYUA_RESULT_BROKEN, "Test case cleanup timed out");
    } else {
        if (WIFEXITED(wait_status)) {
            if (WEXITSTATUS(wait_status) == EXIT_SUCCESS) {
                *success = true;
                // Reuse the result file created by the body.  I.e. avoid
                // creating a new file here.
                return kyua_error_ok();
            } else {
                *success = false;
                return kyua_result_write(
                    output_name, KYUA_RESULT_BROKEN, "Test case cleanup exited "
                    "with code %d", WEXITSTATUS(wait_status));
            }
        } else {
            *success = false;
            return kyua_result_write(
                output_name, KYUA_RESULT_BROKEN, "Test case cleanup received "
                "signal %d%s", WTERMSIG(wait_status),
                WCOREDUMP(wait_status) ? " (core dumped)" : "");
        }
    }
}