/*
 * Copyright (c) 2020 Intel Corporation
 *
 * SPDX-License-Identifier: Apache-2.0
 */
#include <zephyr/kernel.h>
#include <zephyr/ztest.h>


#if defined(CONFIG_ZTEST_FATAL_HOOK)
/* This is a flag indicate if treating fatal error as expected, then take
 * action dealing with it. It's SMP-safe.
 */
ZTEST_BMEM volatile bool fault_in_isr;
ZTEST_BMEM volatile k_tid_t valid_fault_tid;

static inline void reset_stored_fault_status(void)
{
	valid_fault_tid = NULL;
	fault_in_isr = false;
}

void z_impl_ztest_set_fault_valid(bool valid)
{
	if (valid) {
		if (k_is_in_isr()) {
			fault_in_isr = true;
		} else {
			valid_fault_tid = k_current_get();
		}
	} else {
		reset_stored_fault_status();
	}
}

#if defined(CONFIG_USERSPACE)
static inline void z_vrfy_ztest_set_fault_valid(bool valid)
{
	z_impl_ztest_set_fault_valid(valid);
}
#include <syscalls/ztest_set_fault_valid_mrsh.c>
#endif

__weak void ztest_post_fatal_error_hook(unsigned int reason,
		const z_arch_esf_t *pEsf)
{
}

void k_sys_fatal_error_handler(unsigned int reason, const z_arch_esf_t *pEsf)
{
	k_tid_t curr_tid = k_current_get();
	bool valid_fault = (curr_tid == valid_fault_tid) || fault_in_isr;

	printk("Caught system error -- reason %d %d\n", reason, valid_fault);

	if (valid_fault) {
		printk("Fatal error expected as part of test case.\n");

		/* reset back to normal */
		reset_stored_fault_status();

		/* do some action after expected fatal error happened */
		ztest_post_fatal_error_hook(reason, pEsf);
	} else {
		printk("Fatal error was unexpected, aborting...\n");
		k_fatal_halt(reason);
	}
}
#endif


#if defined(CONFIG_ZTEST_ASSERT_HOOK)
/* This is a flag indicate if treating assert fail as expected, then take
 * action dealing with it. It's SMP-safe.
 */
ZTEST_BMEM volatile bool assert_in_isr;
ZTEST_BMEM volatile k_tid_t valid_assert_tid;

static inline void reset_stored_assert_status(void)
{
	valid_assert_tid = NULL;
	assert_in_isr = 0;
}

void z_impl_ztest_set_assert_valid(bool valid)
{
	if (valid) {
		if (k_is_in_isr()) {
			assert_in_isr = true;
		} else {
			valid_assert_tid = k_current_get();
		}
	} else {
		reset_stored_assert_status();
	}
}

#if defined(CONFIG_USERSPACE)
static inline void z_vrfy_ztest_set_assert_valid(bool valid)
{
	z_impl_ztest_set_assert_valid(valid);
}
#include <syscalls/ztest_set_assert_valid_mrsh.c>
#endif

__weak void ztest_post_assert_fail_hook(void)
{
	k_thread_abort(k_current_get());
}

#ifdef CONFIG_ASSERT_NO_FILE_INFO
void assert_post_action(void)
#else
void assert_post_action(const char *file, unsigned int line)
#endif
{
#ifndef CONFIG_ASSERT_NO_FILE_INFO
	ARG_UNUSED(file);
	ARG_UNUSED(line);
#endif

	printk("Caught assert failed\n");

	if ((k_current_get() == valid_assert_tid) || assert_in_isr) {
		printk("Assert error expected as part of test case.\n");

		/* reset back to normal */
		reset_stored_assert_status();

		/* It won't go back to caller when assert failed, and it
		 * will terminate the thread.
		 */
		ztest_post_assert_fail_hook();
	} else {
		printk("Assert failed was unexpected, aborting...\n");
#ifdef CONFIG_USERSPACE
	/* User threads aren't allowed to induce kernel panics; generate
	 * an oops instead.
	 */
		if (k_is_user_context()) {
			k_oops();
		}
#endif
		k_panic();
	}
}
#endif
