/*
* Automated Testing Framework (atf)
*
* 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.
*/
/** 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;
/** 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;
/** 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
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;
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.
* --------------------------------------------------------------------- */
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", "%s", 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;
}
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);
}
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);
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.
* --------------------------------------------------------------------- */
/* ---------------------------------------------------------------------
* 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.
*/