示例#1
0
文件: env.c 项目: namore/kyua
/// Sets an environment variable.
///
/// \param name Name of the environment variable to set.
/// \param value Value to be set.
///
/// \return An error object.
kyua_error_t
kyua_env_set(const char* name, const char* value)
{
    kyua_error_t error;

#if defined(HAVE_SETENV)
    if (setenv(name, value, 1) == -1)
        error = kyua_libc_error_new(
            errno, "Failed to set environment variable %s to %s", name, value);
    else
        error = kyua_error_ok();
#elif defined(HAVE_PUTENV)
    const size_t length = strlen(name) + strlen(value) + 2;
    char* buffer = (char*)malloc(length);
    if (buffer == NULL)
        error = kyua_oom_error_new();
    else {
        const size_t printed_length = snprintf(buffer, length, "%s=%s", name,
                                               value);
        assert(length == printed_length + 1);
        if (putenv(buffer) == -1) {
            error = kyua_libc_error_new(
                errno, "Failed to set environment variable %s to %s",
                name, value);
            free(buffer);
        } else
            error = kyua_error_ok();
    }
#else
#   error "Don't know how to set an environment variable."
#endif

    return error;
}
示例#2
0
/// 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();
}
示例#3
0
/// 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();
}
示例#4
0
/// 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)" : "");
        }
    }
}
示例#5
0
文件: env.c 项目: namore/kyua
/// Sets a collection of configuration variables in the environment.
///
/// \param user_variables Set of configuration variables to pass to the test.
///     This is an array of strings of the form var=value and must have been
///     previously sanity-checked with kyua_env_check_configuration.
///
/// \return An error if there is a problem allocating memory.
///
/// \post The environment contains a new collection of TEST_ENV_* variables that
/// matches the input user_variables.
kyua_error_t
kyua_env_set_configuration(const char* const user_variables[])
{
    const char* const* iter;
    for (iter = user_variables; *iter != NULL; ++iter) {
        kyua_error_t error;

        char* var_value = strdup(*iter);
        if (var_value == NULL)
            return kyua_oom_error_new();

        char* value = strchr(var_value, '=');
        assert(value != NULL);  // Must have been validated.
        *value = '\0';
        value += 1;

        char* var;
        error = kyua_text_printf(&var, "TEST_ENV_%s", var_value);
        if (kyua_error_is_set(error)) {
            free(var_value);
            return error;
        }

        error = kyua_env_set(var, value);
        if (kyua_error_is_set(error)) {
            free(var_value);
            return error;
        }
    }
    return kyua_error_ok();
}
示例#6
0
/// Parses a single test case and writes it to the output.
///
/// This has to be called after the ident property has been read, and takes care
/// of reading the rest of the test case and printing the parsed result.
///
/// Be aware that this consumes the newline after the test case.  The caller
/// should not look for it.
///
/// \param [in,out] input File from which to read the header.
/// \param [in,out] output File to which to write the parsed test case.
/// \param [in,out] name The name of the test case.  This is a non-const pointer
///     and the input string is modified to simplify tokenization.
///
/// \return OK if the parsing succeeds; an error otherwise.
static kyua_error_t
parse_test_case(FILE* input, FILE* output, char* name)
{
    kyua_error_t error;
    char line[1024];  // It's ugly to have a limit, but it's easier this way.

    fprintf(output, "test_case{name=");
    print_quoted(name, output, true);

    error = kyua_error_ok();
    while (!kyua_error_is_set(error) &&
           fgets_no_newline(line, sizeof(line), input) != NULL &&
           strcmp(line, "") != 0) {
        char* key = NULL; char* value = NULL;
        error = parse_property(line, &key, &value);
        if (!kyua_error_is_set(error)) {
            const char* out_key = rewrite_property(key);
            if (out_key == rewrite_error) {
                error = kyua_generic_error_new("Unknown ATF property %s", key);
            } else if (out_key == NULL) {
                fprintf(output, ", ['custom.");
                print_quoted(key, output, false);
                fprintf(output, "']=");
                print_quoted(value, output, true);
            } else {
                fprintf(output, ", %s=", out_key);
                print_quoted(value, output, true);
            }
        }
    }

    fprintf(output, "}\n");

    return error;
}
示例#7
0
文件: text.c 项目: Bhudipta/minix
/// Generates a string from a format string and its replacements.
///
/// \param [out] output Pointer to the dynamically-allocated string with the
///     result of the operation.  The caller must release this with free().
/// \param format The formatting string.
/// \param ap List of to apply to the formatting string.
///
/// \return OK if the string could be formatted; an error otherwise.
kyua_error_t
kyua_text_vprintf(char** output, const char* format, va_list ap)
{
    va_list ap2;

    va_copy(ap2, ap);
    const int length = calculate_length(format, ap2);
    va_end(ap2);
    if (length < 0)
        return kyua_libc_error_new(errno, "Could not calculate length of "
                                   "string with format '%s'", format);

    char* buffer = (char*)malloc(length + 1);
    if (buffer == NULL)
        return kyua_oom_error_new();

    va_copy(ap2, ap);
    const int printed_length = vsnprintf(buffer, length + 1, format, ap2);
    va_end(ap2);
    assert(printed_length == length);
    if (printed_length < 0) {
        free(buffer);
        return kyua_libc_error_new(errno, "Could generate string with format "
                                   "'%s'", format);
    }

    *output = buffer;
    return kyua_error_ok();
}
示例#8
0
ATF_TC_BODY(error_subsume__secondary, tc)
{
    kyua_error_t primary = kyua_error_ok();
    kyua_error_t secondary = kyua_error_new("secondary_error", NULL, 0, NULL);
    kyua_error_t error = kyua_error_subsume(primary, secondary);
    ATF_REQUIRE(kyua_error_is_type(error, "secondary_error"));
    kyua_error_free(error);
}
示例#9
0
/// 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;
}
示例#10
0
/// Parses a property from the test cases list.
///
/// The property is of the form "name: value", where the value extends to the
/// end of the line without quotations.
///
/// \param [in,out] line The line to be parsed.  This is a non-const pointer
///     and the input string is modified to simplify tokenization.
/// \param [out] key The name of the property if the parsing succeeds.  This
///     is a pointer within the input line.
/// \param [out] value The value of the property if the parsing succeeds.  This
///     is a pointer within the input line.
///
/// \return OK if the line contains a valid property; an error otherwise.
/// In case of success, both key and value are updated.
static kyua_error_t
parse_property(char* line, char** const key, char** const value)
{
    char* delim = strstr(line, ": ");
    if (delim == NULL)
        return kyua_generic_error_new("Invalid property '%s'", line);
    *delim = '\0'; *(delim + 1) = '\0';

    *key = line;
    *value = delim + 2;
    return kyua_error_ok();
}
示例#11
0
文件: env.c 项目: namore/kyua
/// Unsets an environment variable.
///
/// \param name Name of the environment variable to unset.
///
/// \return An error object.
kyua_error_t
kyua_env_unset(const char* name)
{
#if defined(HAVE_UNSETENV)
    if (unsetenv(name) == -1)
        return kyua_libc_error_new(
            errno, "Failed to unset environment variable %s", name);
    else
        return kyua_error_ok();
#elif defined(HAVE_PUTENV)
    return kyua_env_set(name, "");
#else
#   error "Don't know how to unset an environment variable."
#endif
}
示例#12
0
文件: env.c 项目: namore/kyua
/// Validates that the configuration variables can be set in the environment.
///
/// \param user_variables Set of configuration variables to pass to the test.
///     This is an array of strings of the form var=value.
///
/// \return An error if there is a syntax error in the variables.
kyua_error_t
kyua_env_check_configuration(const char* const user_variables[])
{
    const char* const* iter;
    for (iter = user_variables; *iter != NULL; ++iter) {
        const char* var_value = *iter;
        if (strlen(var_value) == 0 ||
            (var_value)[0] == '=' ||
            strchr(var_value, '=') == NULL) {
            return kyua_generic_error_new("Invalid variable '%s'; must be of "
                                          "the form var=value", var_value);
        }
    }
    return kyua_error_ok();
}
示例#13
0
文件: result.c 项目: namore/kyua
/// Creates a file with the result of the test.
///
/// \param path Path to the file to be created.
/// \param type The type of the result.
///
/// \return An error object.
kyua_error_t
kyua_result_write(const char* path, const enum kyua_result_type_t type)
{
    assert(type == KYUA_RESULT_PASSED);

    FILE* file = fopen(path, "w");
    if (file == NULL)
        return kyua_libc_error_new(errno, "Cannot create result file '%s'",
                                   path);

    fprintf(file, "%s\n", type_to_name[type]);

    fclose(file);

    return kyua_error_ok();
}
示例#14
0
文件: atf_main.c 项目: Bhudipta/minix
/// Dumps the contents of the input file into the output.
///
/// \param input File from which to read.
/// \param output File to which to write.
///
/// \return An error if there is a problem.
static kyua_error_t
dump_file(FILE* input, FILE* output)
{
    char buffer[1024];
    size_t length;

    while ((length = fread(buffer, 1, sizeof(buffer), input)) > 0) {
        if (fwrite(buffer, 1, length, output) != length) {
            return kyua_generic_error_new("Failed to write to output file");
        }
    }
    if (ferror(input))
        return kyua_libc_error_new(errno, "Failed to read test cases list");

    return kyua_error_ok();
}
示例#15
0
/// Reads the header of the test cases list.
///
/// The header does not carry any useful information, so all this function does
/// is ensure the header is valid.
///
/// \param [in,out] input File from which to read the header.
///
/// \return OK if the header is valid; an error if it is not.
static kyua_error_t
parse_header(FILE* input)
{
    char line[80];  // It's ugly to have a limit, but it's easier this way.

    if (fgets_no_newline(line, sizeof(line), input) == NULL)
        return fgets_error(input, "fgets failed to read test cases list "
                           "header");
    if (strcmp(line, TP_LIST_HEADER) != 0)
        return kyua_generic_error_new("Invalid test cases list header '%s'",
                                      line);

    if (fgets_no_newline(line, sizeof(line), input) == NULL)
        return fgets_error(input, "fgets failed to read test cases list "
                           "header");
    if (strcmp(line, "") != 0)
        return kyua_generic_error_new("Incomplete test cases list header");

    return kyua_error_ok();
}
示例#16
0
文件: result.c 项目: namore/kyua
/// Creates a file with the result of the test.
///
/// \param path Path to the file to be created.
/// \param type The type of the result.
/// \param reason Textual explanation of the reason behind the result.  Must be
///     NULL with KYUA_RESULT_PASSED, or else non-NULL.
///
/// \return An error object.
kyua_error_t
kyua_result_write_with_reason(const char* path,
                              const enum kyua_result_type_t type,
                              const char* reason, ...)
{
    assert(type != KYUA_RESULT_PASSED);
    assert(reason != NULL);

    FILE* file = fopen(path, "w");
    if (file == NULL)
        return kyua_libc_error_new(errno, "Cannot create result file '%s'",
                                   path);

    char buffer[1024];
    va_list ap;
    va_start(ap, reason);
    (void)vsnprintf(buffer, sizeof(buffer), reason, ap);
    va_end(ap);
    fprintf(file, "%s: %s\n", type_to_name[type], buffer);

    fclose(file);

    return kyua_error_ok();
}
示例#17
0
/// 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);
    }
}
示例#18
0
ATF_TC_BODY(error_subsume__none, tc)
{
    kyua_error_t primary = kyua_error_ok();
    kyua_error_t secondary = kyua_error_ok();
    ATF_REQUIRE(!kyua_error_is_set(kyua_error_subsume(primary, secondary)));
}
示例#19
0
ATF_TC_BODY(error_is_set__no, tc)
{
    kyua_error_t error = kyua_error_ok();
    ATF_REQUIRE(!kyua_error_is_set(error));
}
示例#20
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);
    }
}