Linux Audio

Check our new training course

Embedded Linux Audio

Check our new training course
with Creative Commons CC-BY-SA
lecture materials

Bootlin logo

Elixir Cross Referencer

Loading...
/*
 * 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
__kernel static struct k_thread ztest_thread;
static K_THREAD_STACK_DEFINE(thread_stack, CONFIG_ZTEST_STACKSIZE +
			     CONFIG_TEST_EXTRA_STACKSIZE);

static int test_result;
__kernel 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);
	k_object_access_all_grant(&test_end_signal);
}

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, test->thread_options | K_INHERIT_PERMS,
			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