/* Copyright (c) 2008 The NetBSD Foundation, 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: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. 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. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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-c/tc.h" #include #include #include #include #include #include #include #include #include #include #include #include "atf-c/defs.h" #include "atf-c/detail/env.h" #include "atf-c/detail/fs.h" #include "atf-c/detail/map.h" #include "atf-c/detail/sanity.h" #include "atf-c/detail/text.h" #include "atf-c/error.h" /* --------------------------------------------------------------------- * Auxiliary functions. * --------------------------------------------------------------------- */ enum expect_type { EXPECT_PASS, EXPECT_FAIL, EXPECT_EXIT, EXPECT_SIGNAL, EXPECT_DEATH, EXPECT_TIMEOUT, }; struct context { const atf_tc_t *tc; const char *resfile; size_t fail_count; enum expect_type expect; atf_dynstr_t expect_reason; size_t expect_previous_fail_count; size_t expect_fail_count; int expect_exitcode; int expect_signo; }; static void context_init(struct context *, const atf_tc_t *, const char *); static void check_fatal_error(atf_error_t); static void report_fatal_error(const char *, ...) ATF_DEFS_ATTRIBUTE_NORETURN; static atf_error_t write_resfile(const int, const char *, const int, const atf_dynstr_t *); static void create_resfile(const char *, const char *, const int, atf_dynstr_t *); static void error_in_expect(struct context *, const char *, ...) ATF_DEFS_ATTRIBUTE_NORETURN; static void validate_expect(struct context *); static void expected_failure(struct context *, atf_dynstr_t *) ATF_DEFS_ATTRIBUTE_NORETURN; static void fail_requirement(struct context *, atf_dynstr_t *) ATF_DEFS_ATTRIBUTE_NORETURN; static void fail_check(struct context *, atf_dynstr_t *); static void pass(struct context *) ATF_DEFS_ATTRIBUTE_NORETURN; static void skip(struct context *, atf_dynstr_t *) ATF_DEFS_ATTRIBUTE_NORETURN; static void format_reason_ap(atf_dynstr_t *, const char *, const size_t, const char *, va_list); static void format_reason_fmt(atf_dynstr_t *, const char *, const size_t, const char *, ...); static void errno_test(struct context *, const char *, const size_t, const int, const char *, const bool, void (*)(struct context *, atf_dynstr_t *)); static atf_error_t check_prog_in_dir(const char *, void *); static atf_error_t check_prog(struct context *, const char *); static void context_init(struct context *ctx, const atf_tc_t *tc, const char *resfile) { ctx->tc = tc; ctx->resfile = resfile; ctx->fail_count = 0; ctx->expect = EXPECT_PASS; check_fatal_error(atf_dynstr_init(&ctx->expect_reason)); ctx->expect_previous_fail_count = 0; ctx->expect_fail_count = 0; ctx->expect_exitcode = 0; ctx->expect_signo = 0; } static void check_fatal_error(atf_error_t err) { if (atf_is_error(err)) { char buf[1024]; atf_error_format(err, buf, sizeof(buf)); fprintf(stderr, "FATAL ERROR: %s\n", buf); atf_error_free(err); abort(); } } static void report_fatal_error(const char *msg, ...) { va_list ap; fprintf(stderr, "FATAL ERROR: "); va_start(ap, msg); vfprintf(stderr, msg, ap); va_end(ap); fprintf(stderr, "\n"); abort(); } /** Writes to a results file. * * The results file is supposed to be already open. * * This function returns an error code instead of exiting in case of error * because the caller needs to clean up the reason object before terminating. */ static atf_error_t write_resfile(const int fd, const char *result, const int arg, const atf_dynstr_t *reason) { static char NL[] = "\n", CS[] = ": "; char buf[64]; const char *r; struct iovec iov[5]; ssize_t ret; int count = 0; INV(arg == -1 || reason != NULL); #define UNCONST(a) ((void *)(unsigned long)(const void *)(a)) iov[count].iov_base = UNCONST(result); iov[count++].iov_len = strlen(result); if (reason != NULL) { if (arg != -1) { iov[count].iov_base = buf; iov[count++].iov_len = snprintf(buf, sizeof(buf), "(%d)", arg); } iov[count].iov_base = CS; iov[count++].iov_len = sizeof(CS) - 1; r = atf_dynstr_cstring(reason); iov[count].iov_base = UNCONST(r); iov[count++].iov_len = strlen(r); } #undef UNCONST iov[count].iov_base = NL; iov[count++].iov_len = sizeof(NL) - 1; while ((ret = writev(fd, iov, count)) == -1 && errno == EINTR) continue; /* Retry. */ if (ret != -1) return atf_no_error(); return atf_libc_error( errno, "Failed to write results file; result %s, reason %s", result, reason == NULL ? "null" : atf_dynstr_cstring(reason)); } /** Creates a results file. * * The input reason is released in all cases. * * An error in this function is considered to be fatal, hence why it does * not return any error code. */ static void create_resfile(const char *resfile, const char *result, const int arg, atf_dynstr_t *reason) { atf_error_t err; if (strcmp("/dev/stdout", resfile) == 0) { err = write_resfile(STDOUT_FILENO, result, arg, reason); } else if (strcmp("/dev/stderr", resfile) == 0) { err = write_resfile(STDERR_FILENO, result, arg, reason); } else { const int fd = open(resfile, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); if (fd == -1) { err = atf_libc_error(errno, "Cannot create results file '%s'", resfile); } else { err = write_resfile(fd, result, arg, reason); close(fd); } } if (reason != NULL) atf_dynstr_fini(reason); check_fatal_error(err); } /** Fails a test case if validate_expect fails. */ static void error_in_expect(struct context *ctx, const char *fmt, ...) { atf_dynstr_t reason; va_list ap; va_start(ap, fmt); format_reason_ap(&reason, NULL, 0, fmt, ap); va_end(ap); ctx->expect = EXPECT_PASS; /* Ensure fail_requirement really fails. */ fail_requirement(ctx, &reason); } /** Ensures that the "expect" state is correct. * * Call this function before modifying the current value of expect. */ static void validate_expect(struct context *ctx) { if (ctx->expect == EXPECT_DEATH) { error_in_expect(ctx, "Test case was expected to terminate abruptly " "but it continued execution"); } else if (ctx->expect == EXPECT_EXIT) { error_in_expect(ctx, "Test case was expected to exit cleanly but it " "continued execution"); } else if (ctx->expect == EXPECT_FAIL) { if (ctx->expect_fail_count == ctx->expect_previous_fail_count) error_in_expect(ctx, "Test case was expecting a failure but none " "were raised"); else INV(ctx->expect_fail_count > ctx->expect_previous_fail_count); } else if (ctx->expect == EXPECT_PASS) { /* Nothing to validate. */ } else if (ctx->expect == EXPECT_SIGNAL) { error_in_expect(ctx, "Test case was expected to receive a termination " "signal but it continued execution"); } else if (ctx->expect == EXPECT_TIMEOUT) { error_in_expect(ctx, "Test case was expected to hang but it continued " "execution"); } else UNREACHABLE; } static void expected_failure(struct context *ctx, atf_dynstr_t *reason) { check_fatal_error(atf_dynstr_prepend_fmt(reason, "%s: ", atf_dynstr_cstring(&ctx->expect_reason))); create_resfile(ctx->resfile, "expected_failure", -1, reason); exit(EXIT_SUCCESS); } static void fail_requirement(struct context *ctx, atf_dynstr_t *reason) { if (ctx->expect == EXPECT_FAIL) { expected_failure(ctx, reason); } else if (ctx->expect == EXPECT_PASS) { create_resfile(ctx->resfile, "failed", -1, reason); exit(EXIT_FAILURE); } else { error_in_expect(ctx, "Test case raised a failure but was not " "expecting one; reason was %s", atf_dynstr_cstring(reason)); } UNREACHABLE; } static void fail_check(struct context *ctx, atf_dynstr_t *reason) { if (ctx->expect == EXPECT_FAIL) { fprintf(stderr, "*** Expected check failure: %s: %s\n", atf_dynstr_cstring(&ctx->expect_reason), atf_dynstr_cstring(reason)); ctx->expect_fail_count++; } else if (ctx->expect == EXPECT_PASS) { fprintf(stderr, "*** Check failed: %s\n", atf_dynstr_cstring(reason)); ctx->fail_count++; } else { error_in_expect(ctx, "Test case raised a failure but was not " "expecting one; reason was %s", atf_dynstr_cstring(reason)); } atf_dynstr_fini(reason); } static void pass(struct context *ctx) { if (ctx->expect == EXPECT_FAIL) { error_in_expect(ctx, "Test case was expecting a failure but got " "a pass instead"); } else if (ctx->expect == EXPECT_PASS) { create_resfile(ctx->resfile, "passed", -1, NULL); exit(EXIT_SUCCESS); } else { error_in_expect(ctx, "Test case asked to explicitly pass but was " "not expecting such condition"); } UNREACHABLE; } static void skip(struct context *ctx, atf_dynstr_t *reason) { if (ctx->expect == EXPECT_PASS) { create_resfile(ctx->resfile, "skipped", -1, reason); exit(EXIT_SUCCESS); } else { error_in_expect(ctx, "Can only skip a test case when running in " "expect pass mode"); } UNREACHABLE; } /** Formats a failure/skip reason message. * * The formatted reason is stored in out_reason. out_reason is initialized * in this function and is supposed to be released by the caller. In general, * the reason will eventually be fed to create_resfile, which will release * it. * * Errors in this function are fatal. Rationale being: reasons are used to * create results files; if we can't format the reason correctly, the result * of the test program will be bogus. So it's better to just exit with a * fatal error. */ static void format_reason_ap(atf_dynstr_t *out_reason, const char *source_file, const size_t source_line, const char *reason, va_list ap) { atf_error_t err; if (source_file != NULL) { err = atf_dynstr_init_fmt(out_reason, "%s:%zd: ", source_file, source_line); } else { PRE(source_line == 0); err = atf_dynstr_init(out_reason); } if (!atf_is_error(err)) { va_list ap2; va_copy(ap2, ap); err = atf_dynstr_append_ap(out_reason, reason, ap2); va_end(ap2); } check_fatal_error(err); } static void format_reason_fmt(atf_dynstr_t *out_reason, const char *source_file, const size_t source_line, const char *reason, ...) { va_list ap; va_start(ap, reason); format_reason_ap(out_reason, source_file, source_line, reason, ap); va_end(ap); } static void errno_test(struct context *ctx, const char *file, const size_t line, const int exp_errno, const char *expr_str, const bool expr_result, void (*fail_func)(struct context *, atf_dynstr_t *)) { const int actual_errno = errno; if (expr_result) { if (exp_errno != actual_errno) { atf_dynstr_t reason; format_reason_fmt(&reason, file, line, "Expected errno %d, got %d, " "in %s", exp_errno, actual_errno, expr_str); fail_func(ctx, &reason); } } else { atf_dynstr_t reason; format_reason_fmt(&reason, file, line, "Expected true value in %s", expr_str); fail_func(ctx, &reason); } } struct prog_found_pair { const char *prog; bool found; }; static atf_error_t check_prog_in_dir(const char *dir, void *data) { struct prog_found_pair *pf = data; atf_error_t err; if (pf->found) err = atf_no_error(); else { atf_fs_path_t p; err = atf_fs_path_init_fmt(&p, "%s/%s", dir, pf->prog); if (atf_is_error(err)) goto out_p; err = atf_fs_eaccess(&p, atf_fs_access_x); if (!atf_is_error(err)) pf->found = true; else { atf_error_free(err); INV(!pf->found); err = atf_no_error(); } out_p: atf_fs_path_fini(&p); } return err; } static atf_error_t check_prog(struct context *ctx, const char *prog) { atf_error_t err; atf_fs_path_t p; err = atf_fs_path_init_fmt(&p, "%s", prog); if (atf_is_error(err)) goto out; if (atf_fs_path_is_absolute(&p)) { err = atf_fs_eaccess(&p, atf_fs_access_x); if (atf_is_error(err)) { atf_dynstr_t reason; atf_error_free(err); atf_fs_path_fini(&p); format_reason_fmt(&reason, NULL, 0, "The required program %s could " "not be found", prog); skip(ctx, &reason); } } else { const char *path = atf_env_get("PATH"); struct prog_found_pair pf; atf_fs_path_t bp; err = atf_fs_path_branch_path(&p, &bp); if (atf_is_error(err)) goto out_p; if (strcmp(atf_fs_path_cstring(&bp), ".") != 0) { atf_fs_path_fini(&bp); atf_fs_path_fini(&p); report_fatal_error("Relative paths are not allowed when searching " "for a program (%s)", prog); UNREACHABLE; } pf.prog = prog; pf.found = false; err = atf_text_for_each_word(path, ":", check_prog_in_dir, &pf); if (atf_is_error(err)) goto out_bp; if (!pf.found) { atf_dynstr_t reason; atf_fs_path_fini(&bp); atf_fs_path_fini(&p); format_reason_fmt(&reason, NULL, 0, "The required program %s could " "not be found in the PATH", prog); fail_requirement(ctx, &reason); } out_bp: atf_fs_path_fini(&bp); } out_p: atf_fs_path_fini(&p); out: return err; } /* --------------------------------------------------------------------- * The "atf_tc" type. * --------------------------------------------------------------------- */ struct atf_tc_impl { const char *m_ident; atf_map_t m_vars; atf_map_t m_config; atf_tc_head_t m_head; atf_tc_body_t m_body; atf_tc_cleanup_t m_cleanup; }; /* * Constructors/destructors. */ atf_error_t atf_tc_init(atf_tc_t *tc, const char *ident, atf_tc_head_t head, atf_tc_body_t body, atf_tc_cleanup_t cleanup, const char *const *config) { atf_error_t err; tc->pimpl = malloc(sizeof(struct atf_tc_impl)); if (tc->pimpl == NULL) { err = atf_no_memory_error(); goto err; } tc->pimpl->m_ident = ident; tc->pimpl->m_head = head; tc->pimpl->m_body = body; tc->pimpl->m_cleanup = cleanup; err = atf_map_init_charpp(&tc->pimpl->m_config, config); if (atf_is_error(err)) goto err; err = atf_map_init(&tc->pimpl->m_vars); if (atf_is_error(err)) goto err_vars; err = atf_tc_set_md_var(tc, "ident", ident); if (atf_is_error(err)) goto err_map; if (cleanup != NULL) { err = atf_tc_set_md_var(tc, "has.cleanup", "true"); if (atf_is_error(err)) goto err_map; } /* XXX Should the head be able to return error codes? */ if (tc->pimpl->m_head != NULL) tc->pimpl->m_head(tc); if (strcmp(atf_tc_get_md_var(tc, "ident"), ident) != 0) { report_fatal_error("Test case head modified the read-only 'ident' " "property"); UNREACHABLE; } INV(!atf_is_error(err)); return err; err_map: atf_map_fini(&tc->pimpl->m_vars); err_vars: atf_map_fini(&tc->pimpl->m_config); err: return err; } atf_error_t atf_tc_init_pack(atf_tc_t *tc, const atf_tc_pack_t *pack, const char *const *config) { return atf_tc_init(tc, pack->m_ident, pack->m_head, pack->m_body, pack->m_cleanup, config); } void atf_tc_fini(atf_tc_t *tc) { atf_map_fini(&tc->pimpl->m_vars); atf_map_fini(&tc->pimpl->m_config); free(tc->pimpl); tc->pimpl = NULL; } /* * Getters. */ const char * atf_tc_get_ident(const atf_tc_t *tc) { return tc->pimpl->m_ident; } const char * atf_tc_get_config_var(const atf_tc_t *tc, const char *name) { const char *val; atf_map_citer_t iter; PRE(atf_tc_has_config_var(tc, name)); iter = atf_map_find_c(&tc->pimpl->m_config, name); val = atf_map_citer_data(iter); INV(val != NULL); return val; } const char * atf_tc_get_config_var_wd(const atf_tc_t *tc, const char *name, const char *defval) { const char *val; if (!atf_tc_has_config_var(tc, name)) val = defval; else val = atf_tc_get_config_var(tc, name); return val; } bool atf_tc_get_config_var_as_bool(const atf_tc_t *tc, const char *name) { bool val; const char *strval; atf_error_t err; strval = atf_tc_get_config_var(tc, name); err = atf_text_to_bool(strval, &val); if (atf_is_error(err)) { atf_error_free(err); atf_tc_fail("Configuration variable %s does not have a valid " "boolean value; found %s", name, strval); } return val; } bool atf_tc_get_config_var_as_bool_wd(const atf_tc_t *tc, const char *name, const bool defval) { bool val; if (!atf_tc_has_config_var(tc, name)) val = defval; else val = atf_tc_get_config_var_as_bool(tc, name); return val; } long atf_tc_get_config_var_as_long(const atf_tc_t *tc, const char *name) { long val; const char *strval; atf_error_t err; strval = atf_tc_get_config_var(tc, name); err = atf_text_to_long(strval, &val); if (atf_is_error(err)) { atf_error_free(err); atf_tc_fail("Configuration variable %s does not have a valid " "long value; found %s", name, strval); } return val; } long atf_tc_get_config_var_as_long_wd(const atf_tc_t *tc, const char *name, const long defval) { long val; if (!atf_tc_has_config_var(tc, name)) val = defval; else val = atf_tc_get_config_var_as_long(tc, name); return val; } const char * atf_tc_get_md_var(const atf_tc_t *tc, const char *name) { const char *val; atf_map_citer_t iter; PRE(atf_tc_has_md_var(tc, name)); iter = atf_map_find_c(&tc->pimpl->m_vars, name); val = atf_map_citer_data(iter); INV(val != NULL); return val; } char ** atf_tc_get_md_vars(const atf_tc_t *tc) { return atf_map_to_charpp(&tc->pimpl->m_vars); } bool atf_tc_has_config_var(const atf_tc_t *tc, const char *name) { atf_map_citer_t end, iter; iter = atf_map_find_c(&tc->pimpl->m_config, name); end = atf_map_end_c(&tc->pimpl->m_config); return !atf_equal_map_citer_map_citer(iter, end); } bool atf_tc_has_md_var(const atf_tc_t *tc, const char *name) { atf_map_citer_t end, iter; iter = atf_map_find_c(&tc->pimpl->m_vars, name); end = atf_map_end_c(&tc->pimpl->m_vars); return !atf_equal_map_citer_map_citer(iter, end); } /* * Modifiers. */ atf_error_t atf_tc_set_md_var(atf_tc_t *tc, const char *name, const char *fmt, ...) { atf_error_t err; char *value; va_list ap; va_start(ap, fmt); err = atf_text_format_ap(&value, fmt, ap); va_end(ap); if (!atf_is_error(err)) err = atf_map_insert(&tc->pimpl->m_vars, name, value, true); else free(value); return err; } /* --------------------------------------------------------------------- * Free functions, as they should be publicly but they can't. * --------------------------------------------------------------------- */ static void _atf_tc_fail(struct context *, const char *, va_list) ATF_DEFS_ATTRIBUTE_NORETURN; static void _atf_tc_fail_nonfatal(struct context *, const char *, va_list); static void _atf_tc_fail_check(struct context *, const char *, const size_t, const char *, va_list); static void _atf_tc_fail_requirement(struct context *, const char *, const size_t, const char *, va_list) ATF_DEFS_ATTRIBUTE_NORETURN; static void _atf_tc_pass(struct context *) ATF_DEFS_ATTRIBUTE_NORETURN; static void _atf_tc_require_prog(struct context *, const char *); static void _atf_tc_skip(struct context *, const char *, va_list) ATF_DEFS_ATTRIBUTE_NORETURN; static void _atf_tc_check_errno(struct context *, const char *, const size_t, const int, const char *, const bool); static void _atf_tc_require_errno(struct context *, const char *, const size_t, const int, const char *, const bool); static void _atf_tc_expect_pass(struct context *); static void _atf_tc_expect_fail(struct context *, const char *, va_list); static void _atf_tc_expect_exit(struct context *, const int, const char *, va_list); static void _atf_tc_expect_signal(struct context *, const int, const char *, va_list); static void _atf_tc_expect_death(struct context *, const char *, va_list); static void _atf_tc_fail(struct context *ctx, const char *fmt, va_list ap) { va_list ap2; atf_dynstr_t reason; va_copy(ap2, ap); format_reason_ap(&reason, NULL, 0, fmt, ap2); va_end(ap2); fail_requirement(ctx, &reason); UNREACHABLE; } static void _atf_tc_fail_nonfatal(struct context *ctx, const char *fmt, va_list ap) { va_list ap2; atf_dynstr_t reason; va_copy(ap2, ap); format_reason_ap(&reason, NULL, 0, fmt, ap2); va_end(ap2); fail_check(ctx, &reason); } static void _atf_tc_fail_check(struct context *ctx, const char *file, const size_t line, const char *fmt, va_list ap) { va_list ap2; atf_dynstr_t reason; va_copy(ap2, ap); format_reason_ap(&reason, file, line, fmt, ap2); va_end(ap2); fail_check(ctx, &reason); } static void _atf_tc_fail_requirement(struct context *ctx, const char *file, const size_t line, const char *fmt, va_list ap) { va_list ap2; atf_dynstr_t reason; va_copy(ap2, ap); format_reason_ap(&reason, file, line, fmt, ap2); va_end(ap2); fail_requirement(ctx, &reason); UNREACHABLE; } static void _atf_tc_pass(struct context *ctx) { pass(ctx); UNREACHABLE; } static void _atf_tc_require_prog(struct context *ctx, const char *prog) { check_fatal_error(check_prog(ctx, prog)); } static void _atf_tc_skip(struct context *ctx, const char *fmt, va_list ap) { atf_dynstr_t reason; va_list ap2; va_copy(ap2, ap); format_reason_ap(&reason, NULL, 0, fmt, ap2); va_end(ap2); skip(ctx, &reason); } static void _atf_tc_check_errno(struct context *ctx, const char *file, const size_t line, const int exp_errno, const char *expr_str, const bool expr_result) { errno_test(ctx, file, line, exp_errno, expr_str, expr_result, fail_check); } static void _atf_tc_require_errno(struct context *ctx, const char *file, const size_t line, const int exp_errno, const char *expr_str, const bool expr_result) { errno_test(ctx, file, line, exp_errno, expr_str, expr_result, fail_requirement); } static void _atf_tc_expect_pass(struct context *ctx) { validate_expect(ctx); ctx->expect = EXPECT_PASS; } static void _atf_tc_expect_fail(struct context *ctx, const char *reason, va_list ap) { va_list ap2; validate_expect(ctx); ctx->expect = EXPECT_FAIL; atf_dynstr_fini(&ctx->expect_reason); va_copy(ap2, ap); check_fatal_error(atf_dynstr_init_ap(&ctx->expect_reason, reason, ap2)); va_end(ap2); ctx->expect_previous_fail_count = ctx->expect_fail_count; } static void _atf_tc_expect_exit(struct context *ctx, const int exitcode, const char *reason, va_list ap) { va_list ap2; atf_dynstr_t formatted; validate_expect(ctx); ctx->expect = EXPECT_EXIT; va_copy(ap2, ap); check_fatal_error(atf_dynstr_init_ap(&formatted, reason, ap2)); va_end(ap2); create_resfile(ctx->resfile, "expected_exit", exitcode, &formatted); } static void _atf_tc_expect_signal(struct context *ctx, const int signo, const char *reason, va_list ap) { va_list ap2; atf_dynstr_t formatted; validate_expect(ctx); ctx->expect = EXPECT_SIGNAL; va_copy(ap2, ap); check_fatal_error(atf_dynstr_init_ap(&formatted, reason, ap2)); va_end(ap2); create_resfile(ctx->resfile, "expected_signal", signo, &formatted); } static void _atf_tc_expect_death(struct context *ctx, const char *reason, va_list ap) { va_list ap2; atf_dynstr_t formatted; validate_expect(ctx); ctx->expect = EXPECT_DEATH; va_copy(ap2, ap); check_fatal_error(atf_dynstr_init_ap(&formatted, reason, ap2)); va_end(ap2); create_resfile(ctx->resfile, "expected_death", -1, &formatted); } static void _atf_tc_expect_timeout(struct context *ctx, const char *reason, va_list ap) { va_list ap2; atf_dynstr_t formatted; validate_expect(ctx); ctx->expect = EXPECT_TIMEOUT; va_copy(ap2, ap); check_fatal_error(atf_dynstr_init_ap(&formatted, reason, ap2)); va_end(ap2); create_resfile(ctx->resfile, "expected_timeout", -1, &formatted); } /* --------------------------------------------------------------------- * Free functions. * --------------------------------------------------------------------- */ static struct context Current; atf_error_t atf_tc_run(const atf_tc_t *tc, const char *resfile) { context_init(&Current, tc, resfile); tc->pimpl->m_body(tc); validate_expect(&Current); if (Current.fail_count > 0) { atf_dynstr_t reason; format_reason_fmt(&reason, NULL, 0, "%d checks failed; see output for " "more details", Current.fail_count); fail_requirement(&Current, &reason); } else if (Current.expect_fail_count > 0) { atf_dynstr_t reason; format_reason_fmt(&reason, NULL, 0, "%d checks failed as expected; " "see output for more details", Current.expect_fail_count); expected_failure(&Current, &reason); } else { pass(&Current); } UNREACHABLE; return atf_no_error(); } atf_error_t atf_tc_cleanup(const atf_tc_t *tc) { if (tc->pimpl->m_cleanup != NULL) tc->pimpl->m_cleanup(tc); return atf_no_error(); /* XXX */ } /* --------------------------------------------------------------------- * Free functions that depend on Current. * --------------------------------------------------------------------- */ /* * All the functions below provide delegates to other internal functions * (prefixed by _) that take the current test case as an argument to * prevent them from accessing global state. This is to keep the side- * effects of the internal functions clearer and easier to understand. * * The public API should never have hid the fact that it needs access to * the current test case (other than maybe in the macros), but changing it * is hard. TODO: Revisit in the future. */ void atf_tc_fail(const char *fmt, ...) { va_list ap; PRE(Current.tc != NULL); va_start(ap, fmt); _atf_tc_fail(&Current, fmt, ap); va_end(ap); } void atf_tc_fail_nonfatal(const char *fmt, ...) { va_list ap; PRE(Current.tc != NULL); va_start(ap, fmt); _atf_tc_fail_nonfatal(&Current, fmt, ap); va_end(ap); } void atf_tc_fail_check(const char *file, const size_t line, const char *fmt, ...) { va_list ap; PRE(Current.tc != NULL); va_start(ap, fmt); _atf_tc_fail_check(&Current, file, line, fmt, ap); va_end(ap); } void atf_tc_fail_requirement(const char *file, const size_t line, const char *fmt, ...) { va_list ap; PRE(Current.tc != NULL); va_start(ap, fmt); _atf_tc_fail_requirement(&Current, file, line, fmt, ap); va_end(ap); } void atf_tc_pass(void) { PRE(Current.tc != NULL); _atf_tc_pass(&Current); } void atf_tc_require_prog(const char *prog) { PRE(Current.tc != NULL); _atf_tc_require_prog(&Current, prog); } void atf_tc_skip(const char *fmt, ...) { va_list ap; PRE(Current.tc != NULL); va_start(ap, fmt); _atf_tc_skip(&Current, fmt, ap); va_end(ap); } void atf_tc_check_errno(const char *file, const size_t line, const int exp_errno, const char *expr_str, const bool expr_result) { PRE(Current.tc != NULL); _atf_tc_check_errno(&Current, file, line, exp_errno, expr_str, expr_result); } void atf_tc_require_errno(const char *file, const size_t line, const int exp_errno, const char *expr_str, const bool expr_result) { PRE(Current.tc != NULL); _atf_tc_require_errno(&Current, file, line, exp_errno, expr_str, expr_result); } void atf_tc_expect_pass(void) { PRE(Current.tc != NULL); _atf_tc_expect_pass(&Current); } void atf_tc_expect_fail(const char *reason, ...) { va_list ap; PRE(Current.tc != NULL); va_start(ap, reason); _atf_tc_expect_fail(&Current, reason, ap); va_end(ap); } void atf_tc_expect_exit(const int exitcode, const char *reason, ...) { va_list ap; PRE(Current.tc != NULL); va_start(ap, reason); _atf_tc_expect_exit(&Current, exitcode, reason, ap); va_end(ap); } void atf_tc_expect_signal(const int signo, const char *reason, ...) { va_list ap; PRE(Current.tc != NULL); va_start(ap, reason); _atf_tc_expect_signal(&Current, signo, reason, ap); va_end(ap); } void atf_tc_expect_death(const char *reason, ...) { va_list ap; PRE(Current.tc != NULL); va_start(ap, reason); _atf_tc_expect_death(&Current, reason, ap); va_end(ap); } void atf_tc_expect_timeout(const char *reason, ...) { va_list ap; PRE(Current.tc != NULL); va_start(ap, reason); _atf_tc_expect_timeout(&Current, reason, ap); va_end(ap); }