/* SPDX-License-Identifier: GPL-2.0 */
/*
 * Userspace test for __COMPAT_recover_truncated_enum64(). Mirrors the Rust
 * tests in rust/scx_utils/src/compat.rs (test_recover_truncated_enum64 and
 * test_enum_abi_table); the two implementations must stay behaviorally
 * identical.
 *
 * Runs on the host with no kernel or BTF dependency: the recovery path only
 * consults the autogenerated vmlinux.h ABI table.
 */
#include <scx/common.h>

static int nr_failed;

#define TEST_OK(cond, fmt, ...)						\
	do {								\
		if (cond) {						\
			printf("PASS: " fmt "\n", ##__VA_ARGS__);	\
		} else {						\
			printf("FAIL: " fmt "\n", ##__VA_ARGS__);	\
			nr_failed++;					\
		}							\
	} while (0)

static void test_substitution(void)
{
	u64 v = 0;

	/* >32-bit values with matching low bits get substituted. */
	TEST_OK(__COMPAT_recover_truncated_enum64("scx_dsq_id_flags",
						  "SCX_DSQ_LOCAL", 2, &v) &&
		v == 0x8000000000000002LLU,
		"SCX_DSQ_LOCAL lo32=2 substitutes to 0x8000000000000002");

	v = 0;
	TEST_OK(__COMPAT_recover_truncated_enum64("scx_enq_flags",
						  "SCX_ENQ_PREEMPT", 0, &v) &&
		v == 0x100000000LLU,
		"SCX_ENQ_PREEMPT lo32=0 substitutes to 0x100000000");
}

static void test_lossless(void)
{
	u64 v = 0;

	/*
	 * Sub-32-bit values truncate losslessly, so the kernel's value stays
	 * authoritative even when it disagrees with the table (e.g. an
	 * enumerator that moved across kernel versions).
	 */
	TEST_OK(__COMPAT_recover_truncated_enum64("scx_enq_flags",
						  "SCX_ENQ_HEAD", 0x20000, &v) &&
		v == 0x20000LLU,
		"SCX_ENQ_HEAD lo32=0x20000 keeps the kernel value");
}

static void test_mismatch(void)
{
	u64 v = 0;

	/* A low-32 mismatch on a >32-bit value is ABI drift; refuse. */
	TEST_OK(!__COMPAT_recover_truncated_enum64("scx_dsq_id_flags",
						   "SCX_DSQ_LOCAL", 3, &v),
		"SCX_DSQ_LOCAL lo32=3 mismatch is refused");
}

static void test_unknown(void)
{
	u64 v = 0;

	/* Unknown enumerators fail pessimistically (stale autogen table). */
	TEST_OK(!__COMPAT_recover_truncated_enum64("scx_enq_flags",
						   "SCX_ENQ_NO_SUCH_FLAG", 7, &v),
		"unknown enumerator is refused");
	TEST_OK(!__COMPAT_recover_truncated_enum64("scx_no_such_type",
						   "SCX_ENQ_PREEMPT", 0, &v),
		"unknown enum type is refused");
}

static u64 abi_table_lookup(const char *type, const char *name, bool *found)
{
	size_t i;

	for (i = 0; i < sizeof(__scx_enum_abi_vals) / sizeof(__scx_enum_abi_vals[0]); i++) {
		const struct __scx_enum_abi_val *e = &__scx_enum_abi_vals[i];

		if (!strcmp(e->type, type) && !strcmp(e->name, name)) {
			*found = true;
			return e->val;
		}
	}
	*found = false;
	return 0;
}

static void test_abi_table(void)
{
	static const struct {
		const char *type;
		const char *name;
		u64 val;
	} expected[] = {
		/*
		 * Spot-check the autogenerated table against ABI values that
		 * have been stable on every kernel that ships sched_ext.
		 */
		{ "scx_dsq_id_flags", "SCX_DSQ_FLAG_BUILTIN", 1LLU << 63 },
		{ "scx_dsq_id_flags", "SCX_DSQ_LOCAL", (1LLU << 63) | 2 },
		{ "scx_dsq_id_flags", "SCX_DSQ_LOCAL_ON", (1LLU << 63) | (1LLU << 62) },
		{ "scx_public_consts", "SCX_SLICE_INF", ~0LLU },
		{ "scx_enq_flags", "SCX_ENQ_PREEMPT", 1LLU << 32 },
	};
	size_t i;

	for (i = 0; i < sizeof(expected) / sizeof(expected[0]); i++) {
		bool found = false;
		u64 v = abi_table_lookup(expected[i].type, expected[i].name,
					 &found);

		TEST_OK(found && v == expected[i].val,
			"ABI table has %s::%s == 0x%llx",
			expected[i].type, expected[i].name,
			(unsigned long long)expected[i].val);
	}
}

int main(void)
{
	test_substitution();
	test_lossless();
	test_mismatch();
	test_unknown();
	test_abi_table();

	if (nr_failed) {
		printf("%d test(s) FAILED\n", nr_failed);
		return 1;
	}

	printf("All tests passed\n");
	return 0;
}
