/**
 * @file testutil.h
 * @brief Shared assertions for the akbasic CTest programs.
 *
 * A test passes by exiting zero and fails by exiting non-zero with something
 * informative on stderr.
 *
 * TEST_ASSERT and friends expand to a `break`, so they belong directly inside an
 * ATTEMPT block, never inside a loop nested in one -- the same rule that governs
 * CATCH and the FAIL_*_BREAK macros. TEST_REQUIRE is the return-based form for
 * use inside loops and outside ATTEMPT blocks.
 */

#ifndef _AKBASIC_TESTUTIL_H_
#define _AKBASIC_TESTUTIL_H_

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <akerror.h>

/** Count of failures recorded by TEST_REQUIRE; main() returns it. */
static int akbasic_test_failures = 0;

/*
 * Mark an error handled and hand it back to the pool. Assigning the result is
 * what satisfies AKERR_NOIGNORE -- a (void) cast does not, and libakerror's
 * IGNORE() would log to stderr and pollute the test output.
 */
static void test_discard_error(akerr_ErrorContext *e)
{
    akerr_ErrorContext *released = NULL;

    if ( e == NULL ) {
	return;
    }
    e->handled = true;
    released = akerr_release_error(e);
    (void)released;
}

#define TEST_REQUIRE(__cond, ...)					\
    do {								\
	if ( !(__cond) ) {						\
	    fprintf(stderr, "%s:%d: FAIL: ", __FILE__, __LINE__);	\
	    fprintf(stderr, __VA_ARGS__);				\
	    fprintf(stderr, "\n");					\
	    akbasic_test_failures += 1;					\
	}								\
    } while ( 0 )

#define TEST_REQUIRE_STR(__got, __want)					\
    TEST_REQUIRE(strcmp((__got), (__want)) == 0,			\
		 "expected \"%s\", got \"%s\"", (__want), (__got))

#define TEST_REQUIRE_INT(__got, __want)					\
    TEST_REQUIRE((long long)(__got) == (long long)(__want),		\
		 "expected %lld, got %lld",				\
		 (long long)(__want), (long long)(__got))

#define TEST_REQUIRE_FEQ(__got, __want)					\
    TEST_REQUIRE(fabs((double)(__got) - (double)(__want)) < 1e-9,	\
		 "expected %f, got %f", (double)(__want), (double)(__got))

/*
 * Assert that a call succeeded. Releases the context on failure so the pool does
 * not drain across a long test.
 */
#define TEST_REQUIRE_OK(__call)						\
    do {								\
	akerr_ErrorContext *__e = (__call);				\
	if ( __e != NULL && __e->status != 0 ) {			\
	    fprintf(stderr, "%s:%d: FAIL: %s: unexpected error %d (%s): %s\n", \
		    __FILE__, __LINE__, #__call, __e->status,		\
		    akerr_name_for_status(__e->status, NULL), __e->message); \
	    akbasic_test_failures += 1;					\
	}								\
	test_discard_error(__e);					\
    } while ( 0 )

/** Assert that a call failed with a specific status. */
#define TEST_REQUIRE_STATUS(__call, __status)				\
    do {								\
	akerr_ErrorContext *__e = (__call);				\
	if ( __e == NULL || __e->status != (__status) ) {		\
	    fprintf(stderr, "%s:%d: FAIL: %s: expected status %d (%s), got %d\n", \
		    __FILE__, __LINE__, #__call, (int)(__status),	\
		    akerr_name_for_status((__status), NULL),		\
		    (__e == NULL ? 0 : __e->status));			\
	    akbasic_test_failures += 1;					\
	}								\
	test_discard_error(__e);					\
    } while ( 0 )

/** Assert that a call failed, without caring which status. */
#define TEST_REQUIRE_ANY_ERROR(__call)					\
    do {								\
	akerr_ErrorContext *__e = (__call);				\
	if ( __e == NULL || __e->status == 0 ) {			\
	    fprintf(stderr, "%s:%d: FAIL: %s: expected an error, got success\n", \
		    __FILE__, __LINE__, #__call);			\
	    akbasic_test_failures += 1;					\
	}								\
	test_discard_error(__e);					\
    } while ( 0 )

#endif // _AKBASIC_TESTUTIL_H_
