blob: 0fe678e59f27df63ffb980947726608ef2155118 [file] [log] [blame]
/*
* Copyright (c) 2016 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <ztest.h>
#include <stdio.h>
enum {
TEST_PHASE_SETUP,
TEST_PHASE_TEST,
TEST_PHASE_TEARDOWN,
TEST_PHASE_FRAMEWORK
} phase = TEST_PHASE_FRAMEWORK;
static int test_status;
static int cleanup_test(struct unit_test *test)
{
int ret = TC_PASS;
int mock_status;
mock_status = _cleanup_mock();
if (!ret && mock_status == 1) {
PRINT("Test %s failed: Unused mock parameter values\n",
test->name);
ret = TC_FAIL;
} else if (!ret && mock_status == 2) {
PRINT("Test %s failed: Unused mock return values\n",
test->name);
ret = TC_FAIL;
}
return ret;
}
static void run_test_functions(struct unit_test *test)
{
phase = TEST_PHASE_SETUP;
test->setup();
phase = TEST_PHASE_TEST;
test->test();
phase = TEST_PHASE_TEARDOWN;
test->teardown();
phase = TEST_PHASE_FRAMEWORK;
}
#ifndef KERNEL
#include <setjmp.h>
#include <signal.h>
#include <string.h>
#include <stdlib.h>
#define FAIL_FAST 0
static jmp_buf test_fail;
static jmp_buf test_pass;
static jmp_buf stack_fail;
void ztest_test_fail(void)
{
raise(SIGABRT);
}
void ztest_test_pass(void)
{
longjmp(test_pass, 1);
}
static void handle_signal(int sig)
{
static const char *const phase_str[] = {
"setup",
"unit test",
"teardown",
};
PRINT(" %s", strsignal(sig));
switch (phase) {
case TEST_PHASE_SETUP:
case TEST_PHASE_TEST:
case TEST_PHASE_TEARDOWN:
PRINT(" at %s function\n", phase_str[phase]);
longjmp(test_fail, 1);
case TEST_PHASE_FRAMEWORK:
PRINT("\n");
longjmp(stack_fail, 1);
}
}
static void init_testing(void)
{
signal(SIGABRT, handle_signal);
signal(SIGSEGV, handle_signal);
if (setjmp(stack_fail)) {
PRINT("Test suite crashed.");
exit(1);
}
}
static int run_test(struct unit_test *test)
{
int ret = TC_PASS;
TC_START(test->name);
if (setjmp(test_fail)) {
ret = TC_FAIL;
goto out;
}
if (setjmp(test_pass)) {
ret = TC_PASS;
goto out;
}
run_test_functions(test);
out:
ret |= cleanup_test(test);
_TC_END_RESULT(ret, test->name);
return ret;
}
#else /* KERNEL */
/* Zephyr's probably going to cause all tests to fail if one test fails, so
* skip the rest of tests if one of them fails
*/
#ifdef CONFIG_ZTEST_FAIL_FAST
#define FAIL_FAST 1
#else
#define FAIL_FAST 0
#endif
#if CONFIG_ZTEST_STACKSIZE & (STACK_ALIGN - 1)
#error "CONFIG_ZTEST_STACKSIZE must be a multiple of the stack alignment"
#endif
static struct k_thread ztest_thread;
static K_THREAD_STACK_DEFINE(thread_stack, CONFIG_ZTEST_STACKSIZE +
CONFIG_TEST_EXTRA_STACKSIZE);
static int test_result;
static struct k_sem test_end_signal;
void ztest_test_fail(void)
{
test_result = -1;
k_sem_give(&test_end_signal);
k_thread_abort(k_current_get());
}
void ztest_test_pass(void)
{
test_result = 0;
k_sem_give(&test_end_signal);
k_thread_abort(k_current_get());
}
static void init_testing(void)
{
k_sem_init(&test_end_signal, 0, 1);
}
static void test_cb(void *a, void *dummy2, void *dummy)
{
struct unit_test *test = (struct unit_test *)a;
ARG_UNUSED(dummy2);
ARG_UNUSED(dummy);
test_result = 1;
run_test_functions(test);
test_result = 0;
k_sem_give(&test_end_signal);
}
static int run_test(struct unit_test *test)
{
int ret = TC_PASS;
TC_START(test->name);
k_thread_create(&ztest_thread, thread_stack,
K_THREAD_STACK_SIZEOF(thread_stack),
(k_thread_entry_t) test_cb, (struct unit_test *)test,
NULL, NULL, -1, 0, 0);
/*
* There is an implicit expectation here that the thread that was
* spawned is still higher priority than the current thread.
*
* If that is not the case, it will have given the semaphore, which
* will have caused the current thread to run, _if_ the test case
* thread is preemptible, since it is higher priority. If there is
* another test case to be run after the current one finishes, the
* thread_stack will be reused for that new test case while the current
* test case has not finished running yet (it has given the semaphore,
* but has _not_ gone back to _thread_entry() and completed it's "abort
* phase": this will corrupt the kernel ready queue.
*/
k_sem_take(&test_end_signal, K_FOREVER);
if (test_result) {
ret = TC_FAIL;
}
if (!test_result || !FAIL_FAST) {
ret |= cleanup_test(test);
}
_TC_END_RESULT(ret, test->name);
return ret;
}
#endif /* !KERNEL */
void _ztest_run_test_suite(const char *name, struct unit_test *suite)
{
int fail = 0;
if (test_status < 0) {
return;
}
init_testing();
PRINT("Running test suite %s\n", name);
PRINT_LINE;
while (suite->test) {
fail += run_test(suite);
suite++;
if (fail && FAIL_FAST) {
break;
}
}
if (fail) {
TC_END_REPORT(TC_FAIL);
} else {
TC_END_REPORT(TC_PASS);
}
test_status = (test_status || fail) ? 1 : 0;
}
void test_main(void);
#ifndef KERNEL
int main(void)
{
_init_mock();
test_main();
return test_status;
}
#else
void main(void)
{
_init_mock();
test_main();
}
#endif