示例#1
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();
}
示例#2
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;
}
示例#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
/// Rewrites the test cases list from the input to the output.
///
/// \param [in,out] input Stream from which to read the test program's test
///     cases list.  The current location must be after the header and at the
///     first identifier (if any).
/// \param [out] output Stream to which to write the generic list.
///
/// \return An error object.
static kyua_error_t
parse_tests(FILE* input, FILE* output)
{
    char line[512];  // 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, "Empty test cases list");
    }

    kyua_error_t error;

    do {
        char* key = NULL; char* value = NULL;
        error = parse_property(line, &key, &value);
        if (kyua_error_is_set(error))
            break;

        if (strcmp(key, "ident") == 0) {
            error = parse_test_case(input, output, value);
        } else {
            error = kyua_generic_error_new("Expected ident property, got %s",
                                           key);
        }
    } while (!kyua_error_is_set(error) &&
             fgets_no_newline(line, sizeof(line), input) != NULL);

    if (!kyua_error_is_set(error)) {
        if (ferror(input))
            error = kyua_libc_error_new(errno, "fgets failed");
        else
            assert(feof(input));
    }

    return error;
}
示例#5
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;
}
示例#6
0
ATF_TC_BODY(generic_error_format__args, tc)
{
    kyua_error_t error = kyua_generic_error_new("%s message %d", "A", 123);
    char buffer[1024];
    kyua_error_format(error, buffer, sizeof(buffer));
    ATF_REQUIRE_STREQ("A message 123", buffer);
    kyua_error_free(error);
}
示例#7
0
ATF_TC_BODY(generic_error_format__plain, tc)
{
    kyua_error_t error = kyua_generic_error_new("Test message");
    char buffer[1024];
    kyua_error_format(error, buffer, sizeof(buffer));
    ATF_REQUIRE_STREQ("Test message", buffer);
    kyua_error_free(error);
}
示例#8
0
/// Generates an error for the case where fgets() returns NULL.
///
/// \param input Stream on which fgets() returned an error.
/// \param message Error message.
///
/// \return An error object with the error message and any relevant details.
static kyua_error_t
fgets_error(FILE* input, const char* message)
{
    if (feof(input)) {
        return kyua_generic_error_new("%s: unexpected EOF", message);
    } else {
        assert(ferror(input));
        return kyua_libc_error_new(errno, "%s", message);
    }
}
示例#9
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();
}
示例#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
/// 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();
}
示例#12
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();
}
示例#13
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);
    }
}
示例#14
0
ATF_TC_BODY(generic_error_type, tc)
{
    kyua_error_t error = kyua_generic_error_new("Nothing");
    ATF_REQUIRE(kyua_error_is_type(error, kyua_generic_error_type));
    kyua_error_free(error);
}
示例#15
0
文件: atf_main.c 项目: Bhudipta/minix
/// 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;
}