/*
 * Copyright (C) 2013-2025 Intel Corporation
 * SPDX-License-Identifier: BSD-3-Clause
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  * Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  * 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.
 *  * Neither the name of Intel Corporation nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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.
 */

#include "ptunit.h"

#include <stdlib.h>
#include <stdio.h>
#include <inttypes.h>
#include <string.h>
#include <limits.h>


struct ptunit_srcloc ptunit_mk_srcloc(const char *file, uint32_t line)
{
	struct ptunit_srcloc srcloc;

	srcloc.file = file;
	srcloc.line = line;

	return srcloc;
}

struct ptunit_result ptunit_mk_failed_signed_int(const char *expr,
						 const char *cmp,
						 struct ptunit_srcloc where,
						 int64_t actual,
						 int64_t expected)
{
	struct ptunit_result result;

	result.type = ptur_failed_signed_int;
	result.failed.where = where;
	result.failed.variant.signed_int.expr = expr;
	result.failed.variant.signed_int.cmp = cmp;
	result.failed.variant.signed_int.expected = expected;
	result.failed.variant.signed_int.actual = actual;

	return result;
}

struct ptunit_result ptunit_mk_failed_unsigned_int(const char *expr,
						   const char *cmp,
						   struct ptunit_srcloc where,
						   uint64_t actual,
						   uint64_t expected)
{
	struct ptunit_result result;

	result.type = ptur_failed_unsigned_int;
	result.failed.where = where;
	result.failed.variant.unsigned_int.expr = expr;
	result.failed.variant.unsigned_int.cmp = cmp;
	result.failed.variant.unsigned_int.expected = expected;
	result.failed.variant.unsigned_int.actual = actual;

	return result;
}

struct ptunit_result ptunit_mk_failed_pointer(const char *expr,
					      const char *cmp,
					      struct ptunit_srcloc where,
					      const void *actual,
					      const void *expected)
{
	struct ptunit_result result;

	result.type = ptur_failed_pointer;
	result.failed.where = where;
	result.failed.variant.pointer.expr = expr;
	result.failed.variant.pointer.cmp = cmp;
	result.failed.variant.pointer.expected = expected;
	result.failed.variant.pointer.actual = actual;

	return result;
}

struct ptunit_result ptunit_mk_failed_str(const char *expr,
					  const char *cmp,
					  struct ptunit_srcloc where,
					  const char *actual,
					  const char *expected)
{
	struct ptunit_result result;

	if (!actual)
		actual = "(null)";
	if (!expected)
		expected = "(null)";

	memset(&result, 0, sizeof(result));
	result.type = ptur_failed_str;
	result.failed.where = where;
	result.failed.variant.str.expr = expr;
	result.failed.variant.str.cmp = cmp;
	result.failed.variant.str.expected = strdup(expected);
	result.failed.variant.str.actual = strdup(actual);

	return result;
}

struct ptunit_result ptunit_mk_passed(void)
{
	struct ptunit_result result;

	memset(&result, 0, sizeof(result));
	result.type = ptur_passed;

	return result;
}

struct ptunit_result ptunit_mk_skipped(void)
{
	struct ptunit_result result;

	memset(&result, 0, sizeof(result));
	result.type = ptur_skipped;

	return result;
}

struct ptunit_test ptunit_mk_test(const char *name, const char *args)
{
	struct ptunit_test test;

	test.name = name;
	test.args = args;
	test.result = ptunit_mk_skipped();

	return test;
}

void ptunit_fini_test(struct ptunit_test *test)
{
	if (!test)
		return;

	switch (test->result.type) {
	case ptur_skipped:
	case ptur_passed:
	case ptur_failed_signed_int:
	case ptur_failed_unsigned_int:
	case ptur_failed_pointer:
		break;

	case ptur_failed_str:
		free(test->result.failed.variant.str.expected);
		free(test->result.failed.variant.str.actual);
		break;
	}
}

struct ptunit_suite ptunit_mk_suite(int argc, char **argv)
{
	struct ptunit_suite suite;

	memset(&suite, 0, sizeof(suite));

	if (argc && argv)
		suite.name = argv[0];
	return suite;
}

static void ptunit_print_test(const struct ptunit_test *test)
{
	fprintf(stderr, "%s", test->name);

	if (test->args)
		fprintf(stderr, "(%s)", test->args);

	fprintf(stderr, ": ");
}

static const char *basename(const char *file)
{
	const char *base;
	size_t len;

	if (!file)
		return NULL;

	len = strnlen(file, FILENAME_MAX);
	if (FILENAME_MAX <= len)
		return NULL;

	for (base = file + len; base != file; base -= 1) {
		char ch;

		ch = base[-1];
		if ((ch == '/') || (ch == '\\'))
			break;
	}

	return base;
}

static void ptunit_print_srcloc(const struct ptunit_test *test)
{
	const char *file, *base;
	int prec;

	switch (test->result.type) {
	case ptur_passed:
	case ptur_skipped:
		fprintf(stderr, "n/a: ");
		break;

	case ptur_failed_signed_int:
	case ptur_failed_unsigned_int:
	case ptur_failed_pointer:
	case ptur_failed_str:
		file = test->result.failed.where.file;
		if (!file)
			file = "<unknown>";

		base = basename(file);
		if (!base)
			base = file;

		prec = (INT_MAX < FILENAME_MAX ? INT_MAX : FILENAME_MAX);

		fprintf(stderr, "%.*s:%" PRIu32 ": ", prec, base,
			test->result.failed.where.line);
		break;
	}
}

static void ptunit_report_test(const struct ptunit_test *test)
{
	switch (test->result.type) {
	case ptur_skipped:
	case ptur_passed:
		return;

	case ptur_failed_signed_int:
		ptunit_print_test(test);
		ptunit_print_srcloc(test);
		fprintf(stderr, "%s [%" PRId64 "%s%" PRId64 "] failed.\n",
			test->result.failed.variant.signed_int.expr,
			test->result.failed.variant.signed_int.actual,
			test->result.failed.variant.signed_int.cmp,
			test->result.failed.variant.signed_int.expected);
		return;

	case ptur_failed_unsigned_int:
		ptunit_print_test(test);
		ptunit_print_srcloc(test);
		fprintf(stderr, "%s [0x%" PRIx64 "%s0x%" PRIx64 "] failed.\n",
			test->result.failed.variant.unsigned_int.expr,
			test->result.failed.variant.unsigned_int.actual,
			test->result.failed.variant.unsigned_int.cmp,
			test->result.failed.variant.unsigned_int.expected);
		return;

	case ptur_failed_pointer:
		ptunit_print_test(test);
		ptunit_print_srcloc(test);
		fprintf(stderr, "%s [%p%s%p] failed.\n",
			test->result.failed.variant.pointer.expr,
			test->result.failed.variant.pointer.actual,
			test->result.failed.variant.pointer.cmp,
			test->result.failed.variant.pointer.expected);
		return;

	case ptur_failed_str:
		ptunit_print_test(test);
		ptunit_print_srcloc(test);
		fprintf(stderr, "%s [%s%s%s] failed.\n",
			test->result.failed.variant.str.expr,
			test->result.failed.variant.str.actual,
			test->result.failed.variant.str.cmp,
			test->result.failed.variant.str.expected);
		return;
	}

	ptunit_print_test(test);
	fprintf(stderr, "bad result type: 0x%" PRIx32 ".\n", test->result.type);
}

void ptunit_log_test(struct ptunit_suite *suite,
		     const struct ptunit_test *test)
{
	if (!test)
		return;

	if (suite) {
		suite->nr_tests += 1;

		if (test->result.type == ptur_skipped)
			suite->nr_skips += 1;
		else if (test->result.type != ptur_passed)
			suite->nr_fails += 1;
	}

	ptunit_report_test(test);
}

int ptunit_report(const struct ptunit_suite *suite)
{
	if (!suite)
		return -1;

	if (suite->name)
		fprintf(stdout, "%s: ", suite->name);

	fprintf(stdout,
		"tests: %" PRIu32 ", passes: %" PRIu32 ", fails: %" PRIu32,
		suite->nr_tests,
		suite->nr_tests - (suite->nr_fails + suite->nr_skips),
		suite->nr_fails);

	if (suite->nr_skips)
		fprintf(stdout, " (skipped: %" PRIu32 ")", suite->nr_skips);

	fprintf(stdout, "\n");

	if (INT32_MAX < suite->nr_fails)
		return INT32_MAX;

	return (int) suite->nr_fails;
}
