/* SPDX-License-Identifier: GPL-2.0-only */

#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <stddef.h>
#include <tests/test.h>
#include <stdio.h>

/*
 * Important note: In every particular test, don't use any string-related
 * functions other than function under test. We are linking against
 * src/lib/string.c not the standard library. This is important for proper test
 * isolation.
 */

struct string_pairs_t {
	char *dst;
	char *src;
} string_pairs[] = {
	{"Hello ", "world!"},
	{"He\0llo ", "world"},
	{"", "world!"},
	{"", ""},
};

const char *strings[] = {
	"coreboot",
	"is\0very",
	"nice\n"
};

/* Used to test atol */
struct str_with_l_val_t {
	char *str;
	long value;
} str_with_l_val[] = {
	{"42", 42},
	{"four42", 0},
	{"42five5",	42},
	{"4\02", 4},
	{"+42", 42},
	{"-42", -42},
	{"\t\n\r\f\v-42", -42},
};

/* Used to test skip_atoi */
struct str_with_u_val_t {
	char *str;
	uint32_t value;
	uint32_t offset;
} str_with_u_val[] = {
	{"42aa", 42, 2},
	{"a", 0, 0},
	{"0", 0, 1},
	{"4a2", 4, 1},
};

static void test_strdup(void **state)
{
	char str[] = "Hello coreboot\n";
	char *duplicate = strdup(str);

	/* There is a more suitable Cmocka's function 'assert_string_equal()', but it
	   is using strcmp() internally. */
	assert_int_equal(0, memcmp(str, duplicate, __builtin_strlen(str)));

	free(duplicate);
}

static void test_strconcat(void **state)
{
	int i;
	size_t str_len, str2_len, res_len;
	char *result;

	for (i = 0; i < ARRAY_SIZE(string_pairs); i++) {
		str_len = __builtin_strlen(string_pairs[i].dst);
		str2_len = __builtin_strlen(string_pairs[i].src);

		result = strconcat(string_pairs[i].dst, string_pairs[i].src);
		res_len = __builtin_strlen(result);

		assert_int_equal(res_len, str_len + str2_len);
		assert_int_equal(0, memcmp(string_pairs[i].dst, result, str_len));
		assert_int_equal(0, memcmp(string_pairs[i].src, result + str_len, str2_len));

		free(result);
	}
}

static void test_strnlen(void **state)
{
	int i, n = 5;
	size_t str_len, limited_len;

	for (i = 0; i < ARRAY_SIZE(strings); i++) {
		str_len = __builtin_strlen(strings[i]);
		limited_len = MIN(n, str_len);
		assert_int_equal(limited_len, strnlen(strings[i], n));
	}
}

static void test_strlen(void **state)
{
	int i;

	for (i = 0; i < ARRAY_SIZE(strings); i++)
		assert_int_equal(__builtin_strlen(strings[i]), strlen(strings[i]));
}

static void test_strchr(void **state)
{
	char str[] = "Abracadabra!\n";

	assert_ptr_equal(str, strchr(str, 'A'));
	assert_ptr_equal(str + 3, strchr(str, 'a'));
	assert_ptr_equal(str + 12, strchr(str, '\n'));

	assert_null(strchr(str, 'z'));
}


static void test_strrchr(void **state)
{
	char str[] = "Abracadabra!\n";

	assert_ptr_equal(str, strrchr(str, 'A'));
	assert_ptr_equal(str + 9, strrchr(str, 'r'));
	assert_ptr_equal(str + 12, strrchr(str, '\n'));

	assert_null(strrchr(str, 'z'));
}

static void test_strncpy(void **state)
{
	int i;
	int n1 = 2, n2 = 8;
	char src[] = "Hello";
	char dst[sizeof(src) + 5];
	size_t src_len = __builtin_strlen(src);
	size_t dst_len = sizeof(dst);

	/* n1 case  */

	/* Needed for ensuring that characters behind the limit
	   are not overwritten */
	memset(dst, 'x', dst_len);

	strncpy(dst, src, n1);

	assert_int_equal(0, memcmp(dst, src, n1));

	for (i = n1; i < dst_len; i++)
		assert_true(dst[i] == 'x');

	/* n2 case: */

	memset(dst, 'x', dst_len);

	strncpy(dst, src, n2);

	assert_int_equal(0, memcmp(dst, src, src_len));

	for (i = src_len; i < n2; i++)
		assert_true(dst[i] == '\0');

	for (i = n2; i < dst_len; i++)
		assert_true(dst[i] == 'x');
}

static void test_strcpy(void **state)
{
	char src[] = "Hello coreboot\n";
	char dst[sizeof(src)];

	/* Make sure that strcpy() sets '\0' by initializing a whole
	   dst array to fixed, non-'\0' value */
	memset(dst, 'x', sizeof(dst));

	strcpy(dst, src);

	assert_int_equal(0, memcmp(dst, src, __builtin_strlen(src) + 1));
}

static void test_strcmp(void **state)
{
	char str[] = "Banana";
	char str2[] = "Banana";
	char str3[] = "Bananas";

	assert_true(strcmp(str, str3) < 0);
	assert_int_equal(0, strcmp(str, str2));
	assert_true(strcmp(str3, str2) > 0);
}

static void test_strncmp(void **state)
{
	char str[] = "Banana";
	char str2[] = "Bananas";

	size_t str2_len = __builtin_strlen(str2);

	assert_true(strncmp(str, str2, str2_len) < 0);
	assert_int_equal(0, strncmp(str, str2, str2_len - 1));
}

static void test_skip_atoi(void **state)
{
	int i;
	char *ptr, *copy;

	for (i = 0; i < ARRAY_SIZE(str_with_u_val); i++) {
		ptr = str_with_u_val[i].str;
		copy = ptr;
		assert_true(str_with_u_val[i].value == skip_atoi(&ptr));
		assert_int_equal(str_with_u_val[i].offset, ptr - copy);
	}
}

static void test_strspn(void **state)
{
	char str[] = "4213401234";
	char str2[] = "01234";
	char str3[] = "1234";

	assert_int_equal(5, strspn(str, str3));
	assert_int_equal(0, strspn(str2, str3));
}

static void test_strcspn(void **state)
{
	char str[] = "12340000";
	char str2[] = "00001234";
	char str3[] = "1234";

	assert_int_equal(0, strcspn(str, str3));
	assert_int_equal(4, strcspn(str2, str3));
}

/* Please bear in mind that `atol()` uses `strspn()` internally, so the result
   of `test_atol` is dependent on the result of `test_strspn`. */
static void test_atol(void **state)
{
	int i;

	for (i = 0; i < ARRAY_SIZE(str_with_l_val); i++)
		assert_int_equal(str_with_l_val[i].value, atol(str_with_l_val[i].str));
}

int main(void)
{
	const struct CMUnitTest tests[] = {
		cmocka_unit_test(test_strdup),
		cmocka_unit_test(test_strconcat),
		cmocka_unit_test(test_strnlen),
		cmocka_unit_test(test_strlen),
		cmocka_unit_test(test_strchr),
		cmocka_unit_test(test_strrchr),
		cmocka_unit_test(test_strncpy),
		cmocka_unit_test(test_strcpy),
		cmocka_unit_test(test_strcmp),
		cmocka_unit_test(test_strncmp),
		cmocka_unit_test(test_skip_atoi),
		cmocka_unit_test(test_strspn),
		cmocka_unit_test(test_strcspn),
		cmocka_unit_test(test_atol),
	};

	return cmocka_run_group_tests(tests, NULL, NULL);
}
