diff options
Diffstat (limited to 'fs/hfsplus/unicode_test.c')
| -rw-r--r-- | fs/hfsplus/unicode_test.c | 1579 |
1 files changed, 1579 insertions, 0 deletions
diff --git a/fs/hfsplus/unicode_test.c b/fs/hfsplus/unicode_test.c new file mode 100644 index 000000000000..5a7a6859efe3 --- /dev/null +++ b/fs/hfsplus/unicode_test.c @@ -0,0 +1,1579 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit tests for HFS+ Unicode string operations + * + * Copyright (C) 2025 Viacheslav Dubeyko <slava@dubeyko.com> + */ + +#include <kunit/test.h> +#include <linux/nls.h> +#include <linux/dcache.h> +#include <linux/stringhash.h> +#include "hfsplus_fs.h" + +struct test_mock_string_env { + struct hfsplus_unistr str1; + struct hfsplus_unistr str2; + char *buf; + u32 buf_size; +}; + +static struct test_mock_string_env *setup_mock_str_env(u32 buf_size) +{ + struct test_mock_string_env *env; + + env = kzalloc(sizeof(struct test_mock_string_env), GFP_KERNEL); + if (!env) + return NULL; + + env->buf = kzalloc(buf_size, GFP_KERNEL); + if (!env->buf) { + kfree(env); + return NULL; + } + + env->buf_size = buf_size; + + return env; +} + +static void free_mock_str_env(struct test_mock_string_env *env) +{ + if (env->buf) + kfree(env->buf); + kfree(env); +} + +/* Helper function to create hfsplus_unistr */ +static void create_unistr(struct hfsplus_unistr *ustr, const char *ascii_str) +{ + int len = strlen(ascii_str); + int i; + + memset(ustr->unicode, 0, sizeof(ustr->unicode)); + + ustr->length = cpu_to_be16(len); + for (i = 0; i < len && i < HFSPLUS_MAX_STRLEN; i++) + ustr->unicode[i] = cpu_to_be16((u16)ascii_str[i]); +} + +static void corrupt_unistr(struct hfsplus_unistr *ustr) +{ + ustr->length = cpu_to_be16(U16_MAX); +} + +/* Test hfsplus_strcasecmp function */ +static void hfsplus_strcasecmp_test(struct kunit *test) +{ + struct test_mock_string_env *mock_env; + + mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1); + KUNIT_ASSERT_NOT_NULL(test, mock_env); + + /* Test identical strings */ + create_unistr(&mock_env->str1, "hello"); + create_unistr(&mock_env->str2, "hello"); + KUNIT_EXPECT_EQ(test, 0, hfsplus_strcasecmp(&mock_env->str1, + &mock_env->str2)); + + /* Test case insensitive comparison */ + create_unistr(&mock_env->str1, "Hello"); + create_unistr(&mock_env->str2, "hello"); + KUNIT_EXPECT_EQ(test, 0, hfsplus_strcasecmp(&mock_env->str1, + &mock_env->str2)); + + create_unistr(&mock_env->str1, "HELLO"); + create_unistr(&mock_env->str2, "hello"); + KUNIT_EXPECT_EQ(test, 0, hfsplus_strcasecmp(&mock_env->str1, + &mock_env->str2)); + + /* Test different strings */ + create_unistr(&mock_env->str1, "apple"); + create_unistr(&mock_env->str2, "banana"); + KUNIT_EXPECT_LT(test, hfsplus_strcasecmp(&mock_env->str1, + &mock_env->str2), 0); + + create_unistr(&mock_env->str1, "zebra"); + create_unistr(&mock_env->str2, "apple"); + KUNIT_EXPECT_GT(test, hfsplus_strcasecmp(&mock_env->str1, + &mock_env->str2), 0); + + /* Test different lengths */ + create_unistr(&mock_env->str1, "test"); + create_unistr(&mock_env->str2, "testing"); + KUNIT_EXPECT_LT(test, hfsplus_strcasecmp(&mock_env->str1, + &mock_env->str2), 0); + + create_unistr(&mock_env->str1, "testing"); + create_unistr(&mock_env->str2, "test"); + KUNIT_EXPECT_GT(test, hfsplus_strcasecmp(&mock_env->str1, + &mock_env->str2), 0); + + /* Test empty strings */ + create_unistr(&mock_env->str1, ""); + create_unistr(&mock_env->str2, ""); + KUNIT_EXPECT_EQ(test, 0, hfsplus_strcasecmp(&mock_env->str1, + &mock_env->str2)); + + create_unistr(&mock_env->str1, ""); + create_unistr(&mock_env->str2, "test"); + KUNIT_EXPECT_LT(test, hfsplus_strcasecmp(&mock_env->str1, + &mock_env->str2), 0); + + /* Test single characters */ + create_unistr(&mock_env->str1, "A"); + create_unistr(&mock_env->str2, "a"); + KUNIT_EXPECT_EQ(test, 0, hfsplus_strcasecmp(&mock_env->str1, + &mock_env->str2)); + + create_unistr(&mock_env->str1, "A"); + create_unistr(&mock_env->str2, "B"); + KUNIT_EXPECT_LT(test, hfsplus_strcasecmp(&mock_env->str1, + &mock_env->str2), 0); + + /* Test maximum length strings */ + memset(mock_env->buf, 'a', HFSPLUS_MAX_STRLEN); + mock_env->buf[HFSPLUS_MAX_STRLEN] = '\0'; + create_unistr(&mock_env->str1, mock_env->buf); + create_unistr(&mock_env->str2, mock_env->buf); + KUNIT_EXPECT_EQ(test, 0, hfsplus_strcasecmp(&mock_env->str1, + &mock_env->str2)); + + /* Change one character in the middle */ + mock_env->buf[HFSPLUS_MAX_STRLEN / 2] = 'b'; + create_unistr(&mock_env->str2, mock_env->buf); + KUNIT_EXPECT_LT(test, hfsplus_strcasecmp(&mock_env->str1, + &mock_env->str2), 0); + + /* Test corrupted strings */ + create_unistr(&mock_env->str1, ""); + corrupt_unistr(&mock_env->str1); + create_unistr(&mock_env->str2, ""); + KUNIT_EXPECT_NE(test, 0, hfsplus_strcasecmp(&mock_env->str1, + &mock_env->str2)); + + create_unistr(&mock_env->str1, ""); + create_unistr(&mock_env->str2, ""); + corrupt_unistr(&mock_env->str2); + KUNIT_EXPECT_NE(test, 0, hfsplus_strcasecmp(&mock_env->str1, + &mock_env->str2)); + + create_unistr(&mock_env->str1, "test"); + corrupt_unistr(&mock_env->str1); + create_unistr(&mock_env->str2, "testing"); + KUNIT_EXPECT_GT(test, hfsplus_strcasecmp(&mock_env->str1, + &mock_env->str2), 0); + + create_unistr(&mock_env->str1, "test"); + create_unistr(&mock_env->str2, "testing"); + corrupt_unistr(&mock_env->str2); + KUNIT_EXPECT_LT(test, hfsplus_strcasecmp(&mock_env->str1, + &mock_env->str2), 0); + + create_unistr(&mock_env->str1, "testing"); + corrupt_unistr(&mock_env->str1); + create_unistr(&mock_env->str2, "test"); + KUNIT_EXPECT_GT(test, hfsplus_strcasecmp(&mock_env->str1, + &mock_env->str2), 0); + + create_unistr(&mock_env->str1, "testing"); + create_unistr(&mock_env->str2, "test"); + corrupt_unistr(&mock_env->str2); + KUNIT_EXPECT_LT(test, hfsplus_strcasecmp(&mock_env->str1, + &mock_env->str2), 0); + + free_mock_str_env(mock_env); +} + +/* Test hfsplus_strcmp function (case-sensitive) */ +static void hfsplus_strcmp_test(struct kunit *test) +{ + struct test_mock_string_env *mock_env; + + mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1); + KUNIT_ASSERT_NOT_NULL(test, mock_env); + + /* Test identical strings */ + create_unistr(&mock_env->str1, "hello"); + create_unistr(&mock_env->str2, "hello"); + KUNIT_EXPECT_EQ(test, 0, hfsplus_strcmp(&mock_env->str1, + &mock_env->str2)); + + /* Test case sensitive comparison - should NOT be equal */ + create_unistr(&mock_env->str1, "Hello"); + create_unistr(&mock_env->str2, "hello"); + KUNIT_EXPECT_NE(test, 0, hfsplus_strcmp(&mock_env->str1, + &mock_env->str2)); + /* 'H' < 'h' in Unicode */ + KUNIT_EXPECT_LT(test, hfsplus_strcmp(&mock_env->str1, + &mock_env->str2), 0); + + /* Test lexicographic ordering */ + create_unistr(&mock_env->str1, "apple"); + create_unistr(&mock_env->str2, "banana"); + KUNIT_EXPECT_LT(test, hfsplus_strcmp(&mock_env->str1, + &mock_env->str2), 0); + + create_unistr(&mock_env->str1, "zebra"); + create_unistr(&mock_env->str2, "apple"); + KUNIT_EXPECT_GT(test, hfsplus_strcmp(&mock_env->str1, + &mock_env->str2), 0); + + /* Test different lengths with common prefix */ + create_unistr(&mock_env->str1, "test"); + create_unistr(&mock_env->str2, "testing"); + KUNIT_EXPECT_LT(test, hfsplus_strcmp(&mock_env->str1, + &mock_env->str2), 0); + + create_unistr(&mock_env->str1, "testing"); + create_unistr(&mock_env->str2, "test"); + KUNIT_EXPECT_GT(test, hfsplus_strcmp(&mock_env->str1, + &mock_env->str2), 0); + + /* Test empty strings */ + create_unistr(&mock_env->str1, ""); + create_unistr(&mock_env->str2, ""); + KUNIT_EXPECT_EQ(test, 0, hfsplus_strcmp(&mock_env->str1, + &mock_env->str2)); + + /* Test maximum length strings */ + memset(mock_env->buf, 'a', HFSPLUS_MAX_STRLEN); + mock_env->buf[HFSPLUS_MAX_STRLEN] = '\0'; + create_unistr(&mock_env->str1, mock_env->buf); + create_unistr(&mock_env->str2, mock_env->buf); + KUNIT_EXPECT_EQ(test, 0, hfsplus_strcmp(&mock_env->str1, + &mock_env->str2)); + + /* Change one character in the middle */ + mock_env->buf[HFSPLUS_MAX_STRLEN / 2] = 'b'; + create_unistr(&mock_env->str2, mock_env->buf); + KUNIT_EXPECT_LT(test, hfsplus_strcmp(&mock_env->str1, + &mock_env->str2), 0); + + /* Test corrupted strings */ + create_unistr(&mock_env->str1, ""); + corrupt_unistr(&mock_env->str1); + create_unistr(&mock_env->str2, ""); + KUNIT_EXPECT_NE(test, 0, hfsplus_strcmp(&mock_env->str1, + &mock_env->str2)); + + create_unistr(&mock_env->str1, ""); + create_unistr(&mock_env->str2, ""); + corrupt_unistr(&mock_env->str2); + KUNIT_EXPECT_NE(test, 0, hfsplus_strcmp(&mock_env->str1, + &mock_env->str2)); + + create_unistr(&mock_env->str1, "test"); + corrupt_unistr(&mock_env->str1); + create_unistr(&mock_env->str2, "testing"); + KUNIT_EXPECT_LT(test, hfsplus_strcmp(&mock_env->str1, + &mock_env->str2), 0); + + create_unistr(&mock_env->str1, "test"); + create_unistr(&mock_env->str2, "testing"); + corrupt_unistr(&mock_env->str2); + KUNIT_EXPECT_LT(test, hfsplus_strcmp(&mock_env->str1, + &mock_env->str2), 0); + + create_unistr(&mock_env->str1, "testing"); + corrupt_unistr(&mock_env->str1); + create_unistr(&mock_env->str2, "test"); + KUNIT_EXPECT_GT(test, hfsplus_strcmp(&mock_env->str1, + &mock_env->str2), 0); + + create_unistr(&mock_env->str1, "testing"); + create_unistr(&mock_env->str2, "test"); + corrupt_unistr(&mock_env->str2); + KUNIT_EXPECT_GT(test, hfsplus_strcmp(&mock_env->str1, + &mock_env->str2), 0); + + free_mock_str_env(mock_env); +} + +/* Test Unicode edge cases */ +static void hfsplus_unicode_edge_cases_test(struct kunit *test) +{ + struct test_mock_string_env *mock_env; + + mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1); + KUNIT_ASSERT_NOT_NULL(test, mock_env); + + /* Test with special characters */ + mock_env->str1.length = cpu_to_be16(3); + mock_env->str1.unicode[0] = cpu_to_be16(0x00E9); /* é */ + mock_env->str1.unicode[1] = cpu_to_be16(0x00F1); /* ñ */ + mock_env->str1.unicode[2] = cpu_to_be16(0x00FC); /* ü */ + + mock_env->str2.length = cpu_to_be16(3); + mock_env->str2.unicode[0] = cpu_to_be16(0x00E9); /* é */ + mock_env->str2.unicode[1] = cpu_to_be16(0x00F1); /* ñ */ + mock_env->str2.unicode[2] = cpu_to_be16(0x00FC); /* ü */ + + KUNIT_EXPECT_EQ(test, 0, hfsplus_strcmp(&mock_env->str1, + &mock_env->str2)); + KUNIT_EXPECT_EQ(test, 0, hfsplus_strcasecmp(&mock_env->str1, + &mock_env->str2)); + + /* Test with different special characters */ + mock_env->str2.unicode[1] = cpu_to_be16(0x00F2); /* ò */ + KUNIT_EXPECT_NE(test, 0, hfsplus_strcmp(&mock_env->str1, + &mock_env->str2)); + + /* Test null characters within string (should be handled correctly) */ + mock_env->str1.length = cpu_to_be16(3); + mock_env->str1.unicode[0] = cpu_to_be16('a'); + mock_env->str1.unicode[1] = cpu_to_be16(0x0000); /* null */ + mock_env->str1.unicode[2] = cpu_to_be16('b'); + + mock_env->str2.length = cpu_to_be16(3); + mock_env->str2.unicode[0] = cpu_to_be16('a'); + mock_env->str2.unicode[1] = cpu_to_be16(0x0000); /* null */ + mock_env->str2.unicode[2] = cpu_to_be16('b'); + + KUNIT_EXPECT_EQ(test, 0, hfsplus_strcmp(&mock_env->str1, + &mock_env->str2)); + + free_mock_str_env(mock_env); +} + +/* Test boundary conditions */ +static void hfsplus_unicode_boundary_test(struct kunit *test) +{ + struct test_mock_string_env *mock_env; + int i; + + mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1); + KUNIT_ASSERT_NOT_NULL(test, mock_env); + + /* Test maximum length boundary */ + mock_env->str1.length = cpu_to_be16(HFSPLUS_MAX_STRLEN); + mock_env->str2.length = cpu_to_be16(HFSPLUS_MAX_STRLEN); + + for (i = 0; i < HFSPLUS_MAX_STRLEN; i++) { + mock_env->str1.unicode[i] = cpu_to_be16('A'); + mock_env->str2.unicode[i] = cpu_to_be16('A'); + } + + KUNIT_EXPECT_EQ(test, 0, hfsplus_strcmp(&mock_env->str1, + &mock_env->str2)); + + /* Change last character */ + mock_env->str2.unicode[HFSPLUS_MAX_STRLEN - 1] = cpu_to_be16('B'); + KUNIT_EXPECT_LT(test, hfsplus_strcmp(&mock_env->str1, + &mock_env->str2), 0); + + /* Test zero length strings */ + mock_env->str1.length = cpu_to_be16(0); + mock_env->str2.length = cpu_to_be16(0); + KUNIT_EXPECT_EQ(test, 0, hfsplus_strcmp(&mock_env->str1, + &mock_env->str2)); + KUNIT_EXPECT_EQ(test, 0, hfsplus_strcasecmp(&mock_env->str1, + &mock_env->str2)); + + /* Test one character vs empty */ + mock_env->str1.length = cpu_to_be16(1); + mock_env->str1.unicode[0] = cpu_to_be16('A'); + mock_env->str2.length = cpu_to_be16(0); + KUNIT_EXPECT_GT(test, hfsplus_strcmp(&mock_env->str1, + &mock_env->str2), 0); + KUNIT_EXPECT_GT(test, hfsplus_strcasecmp(&mock_env->str1, + &mock_env->str2), 0); + + free_mock_str_env(mock_env); +} + +/* Mock superblock and NLS table for testing hfsplus_uni2asc */ +struct test_mock_sb { + struct nls_table nls; + struct hfsplus_sb_info sb_info; + struct super_block sb; +}; + +static struct test_mock_sb *setup_mock_sb(void) +{ + struct test_mock_sb *ptr; + + ptr = kzalloc(sizeof(struct test_mock_sb), GFP_KERNEL); + if (!ptr) + return NULL; + + ptr->nls.charset = "utf8"; + ptr->nls.uni2char = NULL; /* Will use default behavior */ + ptr->sb_info.nls = &ptr->nls; + ptr->sb.s_fs_info = &ptr->sb_info; + + /* Set default flags - no decomposition, no case folding */ + clear_bit(HFSPLUS_SB_NODECOMPOSE, &ptr->sb_info.flags); + clear_bit(HFSPLUS_SB_CASEFOLD, &ptr->sb_info.flags); + + return ptr; +} + +static void free_mock_sb(struct test_mock_sb *ptr) +{ + kfree(ptr); +} + +/* Simple uni2char implementation for testing */ +static int test_uni2char(wchar_t uni, unsigned char *out, int boundlen) +{ + if (boundlen <= 0) + return -ENAMETOOLONG; + + if (uni < 0x80) { + *out = (unsigned char)uni; + return 1; + } + + /* For non-ASCII, just use '?' as fallback */ + *out = '?'; + return 1; +} + +/* Test hfsplus_uni2asc basic functionality */ +static void hfsplus_uni2asc_basic_test(struct kunit *test) +{ + struct test_mock_sb *mock_sb; + struct test_mock_string_env *mock_env; + int len, result; + + mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1); + KUNIT_ASSERT_NOT_NULL(test, mock_env); + + mock_sb = setup_mock_sb(); + KUNIT_ASSERT_NOT_NULL(test, mock_sb); + + mock_sb->nls.uni2char = test_uni2char; + + /* Test simple ASCII string conversion */ + create_unistr(&mock_env->str1, "hello"); + len = mock_env->buf_size; + result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1, + mock_env->buf, &len); + + KUNIT_EXPECT_EQ(test, 0, result); + KUNIT_EXPECT_EQ(test, 5, len); + KUNIT_EXPECT_STREQ(test, "hello", mock_env->buf); + + /* Test empty string */ + create_unistr(&mock_env->str1, ""); + len = mock_env->buf_size; + result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1, + mock_env->buf, &len); + + KUNIT_EXPECT_EQ(test, 0, result); + KUNIT_EXPECT_EQ(test, 0, len); + + /* Test single character */ + create_unistr(&mock_env->str1, "A"); + len = mock_env->buf_size; + result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1, + mock_env->buf, &len); + + KUNIT_EXPECT_EQ(test, 0, result); + KUNIT_EXPECT_EQ(test, 1, len); + KUNIT_EXPECT_EQ(test, 'A', mock_env->buf[0]); + + free_mock_str_env(mock_env); + free_mock_sb(mock_sb); +} + +/* Test special character handling */ +static void hfsplus_uni2asc_special_chars_test(struct kunit *test) +{ + struct test_mock_sb *mock_sb; + struct test_mock_string_env *mock_env; + int len, result; + + mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1); + KUNIT_ASSERT_NOT_NULL(test, mock_env); + + mock_sb = setup_mock_sb(); + KUNIT_ASSERT_NOT_NULL(test, mock_sb); + + mock_sb->nls.uni2char = test_uni2char; + + /* Test null character conversion (should become 0x2400) */ + mock_env->str1.length = cpu_to_be16(1); + mock_env->str1.unicode[0] = cpu_to_be16(0x0000); + len = mock_env->buf_size; + result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1, + mock_env->buf, &len); + + KUNIT_EXPECT_EQ(test, 0, result); + KUNIT_EXPECT_EQ(test, 1, len); + /* Our test implementation returns '?' for non-ASCII */ + KUNIT_EXPECT_EQ(test, '?', mock_env->buf[0]); + + /* Test forward slash conversion (should become colon) */ + mock_env->str1.length = cpu_to_be16(1); + mock_env->str1.unicode[0] = cpu_to_be16('/'); + len = mock_env->buf_size; + result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1, + mock_env->buf, &len); + + KUNIT_EXPECT_EQ(test, 0, result); + KUNIT_EXPECT_EQ(test, 1, len); + KUNIT_EXPECT_EQ(test, ':', mock_env->buf[0]); + + /* Test string with mixed special characters */ + mock_env->str1.length = cpu_to_be16(3); + mock_env->str1.unicode[0] = cpu_to_be16('a'); + mock_env->str1.unicode[1] = cpu_to_be16('/'); + mock_env->str1.unicode[2] = cpu_to_be16('b'); + len = mock_env->buf_size; + result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1, + mock_env->buf, &len); + + KUNIT_EXPECT_EQ(test, 0, result); + KUNIT_EXPECT_EQ(test, 3, len); + KUNIT_EXPECT_EQ(test, 'a', mock_env->buf[0]); + KUNIT_EXPECT_EQ(test, ':', mock_env->buf[1]); + KUNIT_EXPECT_EQ(test, 'b', mock_env->buf[2]); + + free_mock_str_env(mock_env); + free_mock_sb(mock_sb); +} + +/* Test buffer length handling */ +static void hfsplus_uni2asc_buffer_test(struct kunit *test) +{ + struct test_mock_sb *mock_sb; + struct test_mock_string_env *mock_env; + int len, result; + + mock_env = setup_mock_str_env(10); + KUNIT_ASSERT_NOT_NULL(test, mock_env); + + mock_sb = setup_mock_sb(); + KUNIT_ASSERT_NOT_NULL(test, mock_sb); + + mock_sb->nls.uni2char = test_uni2char; + + /* Test insufficient buffer space */ + create_unistr(&mock_env->str1, "toolongstring"); + len = 5; /* Buffer too small */ + result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1, + mock_env->buf, &len); + + KUNIT_EXPECT_EQ(test, -ENAMETOOLONG, result); + KUNIT_EXPECT_EQ(test, 5, len); /* Should be set to consumed length */ + + /* Test exact buffer size */ + create_unistr(&mock_env->str1, "exact"); + len = 5; + result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1, + mock_env->buf, &len); + + KUNIT_EXPECT_EQ(test, 0, result); + KUNIT_EXPECT_EQ(test, 5, len); + + /* Test zero length buffer */ + create_unistr(&mock_env->str1, "test"); + len = 0; + result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1, + mock_env->buf, &len); + + KUNIT_EXPECT_EQ(test, -ENAMETOOLONG, result); + KUNIT_EXPECT_EQ(test, 0, len); + + free_mock_str_env(mock_env); + free_mock_sb(mock_sb); +} + +/* Test corrupted unicode string handling */ +static void hfsplus_uni2asc_corrupted_test(struct kunit *test) +{ + struct test_mock_sb *mock_sb; + struct test_mock_string_env *mock_env; + int len, result; + + mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1); + KUNIT_ASSERT_NOT_NULL(test, mock_env); + + mock_sb = setup_mock_sb(); + KUNIT_ASSERT_NOT_NULL(test, mock_sb); + + mock_sb->nls.uni2char = test_uni2char; + + /* Test corrupted length (too large) */ + create_unistr(&mock_env->str1, "test"); + corrupt_unistr(&mock_env->str1); /* Sets length to U16_MAX */ + len = mock_env->buf_size; + + result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1, + mock_env->buf, &len); + + /* Should still work but with corrected length */ + KUNIT_EXPECT_EQ(test, 0, result); + /* + * Length should be corrected to HFSPLUS_MAX_STRLEN + * and processed accordingly + */ + KUNIT_EXPECT_GT(test, len, 0); + + free_mock_str_env(mock_env); + free_mock_sb(mock_sb); +} + +/* Test edge cases and boundary conditions */ +static void hfsplus_uni2asc_edge_cases_test(struct kunit *test) +{ + struct test_mock_sb *mock_sb; + struct test_mock_string_env *mock_env; + int len, result; + int i; + + mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN * 2); + KUNIT_ASSERT_NOT_NULL(test, mock_env); + + mock_sb = setup_mock_sb(); + KUNIT_ASSERT_NOT_NULL(test, mock_sb); + + mock_sb->nls.uni2char = test_uni2char; + + /* Test maximum length string */ + mock_env->str1.length = cpu_to_be16(HFSPLUS_MAX_STRLEN); + for (i = 0; i < HFSPLUS_MAX_STRLEN; i++) + mock_env->str1.unicode[i] = cpu_to_be16('a'); + + len = mock_env->buf_size; + result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1, + mock_env->buf, &len); + + KUNIT_EXPECT_EQ(test, 0, result); + KUNIT_EXPECT_EQ(test, HFSPLUS_MAX_STRLEN, len); + + /* Verify all characters are 'a' */ + for (i = 0; i < HFSPLUS_MAX_STRLEN; i++) + KUNIT_EXPECT_EQ(test, 'a', mock_env->buf[i]); + + /* Test string with high Unicode values (non-ASCII) */ + mock_env->str1.length = cpu_to_be16(3); + mock_env->str1.unicode[0] = cpu_to_be16(0x00E9); /* é */ + mock_env->str1.unicode[1] = cpu_to_be16(0x00F1); /* ñ */ + mock_env->str1.unicode[2] = cpu_to_be16(0x00FC); /* ü */ + len = mock_env->buf_size; + result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1, + mock_env->buf, &len); + + KUNIT_EXPECT_EQ(test, 0, result); + KUNIT_EXPECT_EQ(test, 3, len); + /* Our test implementation converts non-ASCII to '?' */ + KUNIT_EXPECT_EQ(test, '?', mock_env->buf[0]); + KUNIT_EXPECT_EQ(test, '?', mock_env->buf[1]); + KUNIT_EXPECT_EQ(test, '?', mock_env->buf[2]); + + free_mock_str_env(mock_env); + free_mock_sb(mock_sb); +} + +/* Simple char2uni implementation for testing */ +static int test_char2uni(const unsigned char *rawstring, + int boundlen, wchar_t *uni) +{ + if (boundlen <= 0) + return -EINVAL; + + *uni = (wchar_t)*rawstring; + return 1; +} + +/* Helper function to check unicode string contents */ +static void check_unistr_content(struct kunit *test, + struct hfsplus_unistr *ustr, + const char *expected_ascii) +{ + int expected_len = strlen(expected_ascii); + int actual_len = be16_to_cpu(ustr->length); + int i; + + KUNIT_EXPECT_EQ(test, expected_len, actual_len); + + for (i = 0; i < expected_len && i < actual_len; i++) { + u16 expected_char = (u16)expected_ascii[i]; + u16 actual_char = be16_to_cpu(ustr->unicode[i]); + + KUNIT_EXPECT_EQ(test, expected_char, actual_char); + } +} + +/* Test hfsplus_asc2uni basic functionality */ +static void hfsplus_asc2uni_basic_test(struct kunit *test) +{ + struct test_mock_sb *mock_sb; + struct test_mock_string_env *mock_env; + int result; + + mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1); + KUNIT_ASSERT_NOT_NULL(test, mock_env); + + mock_sb = setup_mock_sb(); + KUNIT_ASSERT_NOT_NULL(test, mock_sb); + + mock_sb->nls.char2uni = test_char2uni; + + /* Test simple ASCII string conversion */ + result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1, + HFSPLUS_MAX_STRLEN, "hello", 5); + + KUNIT_EXPECT_EQ(test, 0, result); + check_unistr_content(test, &mock_env->str1, "hello"); + + /* Test empty string */ + result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1, + HFSPLUS_MAX_STRLEN, "", 0); + + KUNIT_EXPECT_EQ(test, 0, result); + KUNIT_EXPECT_EQ(test, 0, be16_to_cpu(mock_env->str1.length)); + + /* Test single character */ + result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1, + HFSPLUS_MAX_STRLEN, "A", 1); + + KUNIT_EXPECT_EQ(test, 0, result); + check_unistr_content(test, &mock_env->str1, "A"); + + /* Test null-terminated string with explicit length */ + result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1, + HFSPLUS_MAX_STRLEN, "test\0extra", 4); + + KUNIT_EXPECT_EQ(test, 0, result); + check_unistr_content(test, &mock_env->str1, "test"); + + free_mock_str_env(mock_env); + free_mock_sb(mock_sb); +} + +/* Test special character handling in asc2uni */ +static void hfsplus_asc2uni_special_chars_test(struct kunit *test) +{ + struct test_mock_sb *mock_sb; + struct test_mock_string_env *mock_env; + int result; + + mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1); + KUNIT_ASSERT_NOT_NULL(test, mock_env); + + mock_sb = setup_mock_sb(); + KUNIT_ASSERT_NOT_NULL(test, mock_sb); + + mock_sb->nls.char2uni = test_char2uni; + + /* Test colon conversion (should become forward slash) */ + result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1, + HFSPLUS_MAX_STRLEN, ":", 1); + + KUNIT_EXPECT_EQ(test, 0, result); + KUNIT_EXPECT_EQ(test, 1, be16_to_cpu(mock_env->str1.length)); + KUNIT_EXPECT_EQ(test, '/', be16_to_cpu(mock_env->str1.unicode[0])); + + /* Test string with mixed special characters */ + result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1, + HFSPLUS_MAX_STRLEN, "a:b", 3); + + KUNIT_EXPECT_EQ(test, 0, result); + KUNIT_EXPECT_EQ(test, 3, be16_to_cpu(mock_env->str1.length)); + KUNIT_EXPECT_EQ(test, 'a', be16_to_cpu(mock_env->str1.unicode[0])); + KUNIT_EXPECT_EQ(test, '/', be16_to_cpu(mock_env->str1.unicode[1])); + KUNIT_EXPECT_EQ(test, 'b', be16_to_cpu(mock_env->str1.unicode[2])); + + /* Test multiple special characters */ + result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1, + HFSPLUS_MAX_STRLEN, ":::", 3); + + KUNIT_EXPECT_EQ(test, 0, result); + KUNIT_EXPECT_EQ(test, 3, be16_to_cpu(mock_env->str1.length)); + KUNIT_EXPECT_EQ(test, '/', be16_to_cpu(mock_env->str1.unicode[0])); + KUNIT_EXPECT_EQ(test, '/', be16_to_cpu(mock_env->str1.unicode[1])); + KUNIT_EXPECT_EQ(test, '/', be16_to_cpu(mock_env->str1.unicode[2])); + + free_mock_str_env(mock_env); + free_mock_sb(mock_sb); +} + +/* Test buffer length limits */ +static void hfsplus_asc2uni_buffer_limits_test(struct kunit *test) +{ + struct test_mock_sb *mock_sb; + struct test_mock_string_env *mock_env; + int result; + + mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 10); + KUNIT_ASSERT_NOT_NULL(test, mock_env); + + mock_sb = setup_mock_sb(); + KUNIT_ASSERT_NOT_NULL(test, mock_sb); + + mock_sb->nls.char2uni = test_char2uni; + + /* Test exact maximum length */ + memset(mock_env->buf, 'a', HFSPLUS_MAX_STRLEN); + result = hfsplus_asc2uni(&mock_sb->sb, + &mock_env->str1, HFSPLUS_MAX_STRLEN, + mock_env->buf, HFSPLUS_MAX_STRLEN); + + KUNIT_EXPECT_EQ(test, 0, result); + KUNIT_EXPECT_EQ(test, HFSPLUS_MAX_STRLEN, + be16_to_cpu(mock_env->str1.length)); + + /* Test exceeding maximum length */ + memset(mock_env->buf, 'a', HFSPLUS_MAX_STRLEN + 5); + result = hfsplus_asc2uni(&mock_sb->sb, + &mock_env->str1, HFSPLUS_MAX_STRLEN, + mock_env->buf, HFSPLUS_MAX_STRLEN + 5); + + KUNIT_EXPECT_EQ(test, -ENAMETOOLONG, result); + KUNIT_EXPECT_EQ(test, HFSPLUS_MAX_STRLEN, + be16_to_cpu(mock_env->str1.length)); + + /* Test with smaller max_unistr_len */ + result = hfsplus_asc2uni(&mock_sb->sb, + &mock_env->str1, 5, "toolongstring", 13); + + KUNIT_EXPECT_EQ(test, -ENAMETOOLONG, result); + KUNIT_EXPECT_EQ(test, 5, be16_to_cpu(mock_env->str1.length)); + + /* Test zero max length */ + result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1, 0, "test", 4); + + KUNIT_EXPECT_EQ(test, -ENAMETOOLONG, result); + KUNIT_EXPECT_EQ(test, 0, be16_to_cpu(mock_env->str1.length)); + + free_mock_str_env(mock_env); + free_mock_sb(mock_sb); +} + +/* Test error handling and edge cases */ +static void hfsplus_asc2uni_edge_cases_test(struct kunit *test) +{ + struct test_mock_sb *mock_sb; + struct hfsplus_unistr ustr; + char test_str[] = {'a', '\0', 'b'}; + int result; + + mock_sb = setup_mock_sb(); + KUNIT_ASSERT_NOT_NULL(test, mock_sb); + + mock_sb->nls.char2uni = test_char2uni; + + /* Test zero length input */ + result = hfsplus_asc2uni(&mock_sb->sb, + &ustr, HFSPLUS_MAX_STRLEN, "test", 0); + + KUNIT_EXPECT_EQ(test, 0, result); + KUNIT_EXPECT_EQ(test, 0, be16_to_cpu(ustr.length)); + + /* Test input with length mismatch */ + result = hfsplus_asc2uni(&mock_sb->sb, + &ustr, HFSPLUS_MAX_STRLEN, "hello", 3); + + KUNIT_EXPECT_EQ(test, 0, result); + check_unistr_content(test, &ustr, "hel"); + + /* Test with various printable ASCII characters */ + result = hfsplus_asc2uni(&mock_sb->sb, + &ustr, HFSPLUS_MAX_STRLEN, "ABC123!@#", 9); + + KUNIT_EXPECT_EQ(test, 0, result); + check_unistr_content(test, &ustr, "ABC123!@#"); + + /* Test null character in the middle */ + result = hfsplus_asc2uni(&mock_sb->sb, + &ustr, HFSPLUS_MAX_STRLEN, test_str, 3); + + KUNIT_EXPECT_EQ(test, 0, result); + KUNIT_EXPECT_EQ(test, 3, be16_to_cpu(ustr.length)); + KUNIT_EXPECT_EQ(test, 'a', be16_to_cpu(ustr.unicode[0])); + KUNIT_EXPECT_EQ(test, 0, be16_to_cpu(ustr.unicode[1])); + KUNIT_EXPECT_EQ(test, 'b', be16_to_cpu(ustr.unicode[2])); + + free_mock_sb(mock_sb); +} + +/* Test decomposition flag behavior */ +static void hfsplus_asc2uni_decompose_test(struct kunit *test) +{ + struct test_mock_sb *mock_sb; + struct test_mock_string_env *mock_env; + int result; + + mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1); + KUNIT_ASSERT_NOT_NULL(test, mock_env); + + mock_sb = setup_mock_sb(); + KUNIT_ASSERT_NOT_NULL(test, mock_sb); + + mock_sb->nls.char2uni = test_char2uni; + + /* Test with decomposition disabled (default) */ + clear_bit(HFSPLUS_SB_NODECOMPOSE, &mock_sb->sb_info.flags); + result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1, + HFSPLUS_MAX_STRLEN, "test", 4); + + KUNIT_EXPECT_EQ(test, 0, result); + check_unistr_content(test, &mock_env->str1, "test"); + + /* Test with decomposition enabled */ + set_bit(HFSPLUS_SB_NODECOMPOSE, &mock_sb->sb_info.flags); + result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str2, + HFSPLUS_MAX_STRLEN, "test", 4); + + KUNIT_EXPECT_EQ(test, 0, result); + check_unistr_content(test, &mock_env->str2, "test"); + + /* For simple ASCII, both should produce the same result */ + KUNIT_EXPECT_EQ(test, + be16_to_cpu(mock_env->str1.length), + be16_to_cpu(mock_env->str2.length)); + + free_mock_str_env(mock_env); + free_mock_sb(mock_sb); +} + +/* Mock dentry for testing hfsplus_hash_dentry */ +static struct dentry test_dentry; + +static void setup_mock_dentry(struct super_block *sb) +{ + memset(&test_dentry, 0, sizeof(test_dentry)); + test_dentry.d_sb = sb; +} + +/* Helper function to create qstr */ +static void create_qstr(struct qstr *str, const char *name) +{ + str->name = name; + str->len = strlen(name); + str->hash = 0; /* Will be set by hash function */ +} + +/* Test hfsplus_hash_dentry basic functionality */ +static void hfsplus_hash_dentry_basic_test(struct kunit *test) +{ + struct test_mock_sb *mock_sb; + struct qstr str1, str2; + int result; + + mock_sb = setup_mock_sb(); + KUNIT_ASSERT_NOT_NULL(test, mock_sb); + + setup_mock_dentry(&mock_sb->sb); + mock_sb->nls.char2uni = test_char2uni; + + /* Test basic string hashing */ + create_qstr(&str1, "hello"); + result = hfsplus_hash_dentry(&test_dentry, &str1); + + KUNIT_EXPECT_EQ(test, 0, result); + KUNIT_EXPECT_NE(test, 0, str1.hash); + + /* Test that identical strings produce identical hashes */ + create_qstr(&str2, "hello"); + result = hfsplus_hash_dentry(&test_dentry, &str2); + + KUNIT_EXPECT_EQ(test, 0, result); + KUNIT_EXPECT_EQ(test, str1.hash, str2.hash); + + /* Test empty string */ + create_qstr(&str1, ""); + result = hfsplus_hash_dentry(&test_dentry, &str1); + + /* Empty string should still produce a hash */ + KUNIT_EXPECT_EQ(test, 0, result); + + /* Test single character */ + create_qstr(&str1, "A"); + result = hfsplus_hash_dentry(&test_dentry, &str1); + + KUNIT_EXPECT_EQ(test, 0, result); + KUNIT_EXPECT_NE(test, 0, str1.hash); + + free_mock_sb(mock_sb); +} + +/* Test case folding behavior in hash */ +static void hfsplus_hash_dentry_casefold_test(struct kunit *test) +{ + struct test_mock_sb *mock_sb; + struct qstr str1, str2; + int result; + + mock_sb = setup_mock_sb(); + KUNIT_ASSERT_NOT_NULL(test, mock_sb); + + setup_mock_dentry(&mock_sb->sb); + mock_sb->nls.char2uni = test_char2uni; + + /* Test with case folding disabled (default) */ + clear_bit(HFSPLUS_SB_CASEFOLD, &mock_sb->sb_info.flags); + + create_qstr(&str1, "Hello"); + result = hfsplus_hash_dentry(&test_dentry, &str1); + KUNIT_EXPECT_EQ(test, 0, result); + + create_qstr(&str2, "hello"); + result = hfsplus_hash_dentry(&test_dentry, &str2); + KUNIT_EXPECT_EQ(test, 0, result); + + /* + * Without case folding, different cases + * should produce different hashes + */ + KUNIT_EXPECT_NE(test, str1.hash, str2.hash); + + /* Test with case folding enabled */ + set_bit(HFSPLUS_SB_CASEFOLD, &mock_sb->sb_info.flags); + + create_qstr(&str1, "Hello"); + result = hfsplus_hash_dentry(&test_dentry, &str1); + KUNIT_EXPECT_EQ(test, 0, result); + + create_qstr(&str2, "hello"); + result = hfsplus_hash_dentry(&test_dentry, &str2); + KUNIT_EXPECT_EQ(test, 0, result); + + /* With case folding, different cases should produce same hash */ + KUNIT_EXPECT_EQ(test, str1.hash, str2.hash); + + /* Test mixed case */ + create_qstr(&str1, "HeLLo"); + result = hfsplus_hash_dentry(&test_dentry, &str1); + KUNIT_EXPECT_EQ(test, 0, result); + KUNIT_EXPECT_EQ(test, str1.hash, str2.hash); + + free_mock_sb(mock_sb); +} + +/* Test special character handling in hash */ +static void hfsplus_hash_dentry_special_chars_test(struct kunit *test) +{ + struct test_mock_sb *mock_sb; + struct qstr str1, str2; + int result; + + mock_sb = setup_mock_sb(); + KUNIT_ASSERT_NOT_NULL(test, mock_sb); + + setup_mock_dentry(&mock_sb->sb); + mock_sb->nls.char2uni = test_char2uni; + + /* Test colon conversion (: becomes /) */ + create_qstr(&str1, "file:name"); + result = hfsplus_hash_dentry(&test_dentry, &str1); + KUNIT_EXPECT_EQ(test, 0, result); + + create_qstr(&str2, "file/name"); + result = hfsplus_hash_dentry(&test_dentry, &str2); + KUNIT_EXPECT_EQ(test, 0, result); + + /* After conversion, these should produce the same hash */ + KUNIT_EXPECT_EQ(test, str1.hash, str2.hash); + + /* Test multiple special characters */ + create_qstr(&str1, ":::"); + result = hfsplus_hash_dentry(&test_dentry, &str1); + KUNIT_EXPECT_EQ(test, 0, result); + + create_qstr(&str2, "///"); + result = hfsplus_hash_dentry(&test_dentry, &str2); + KUNIT_EXPECT_EQ(test, 0, result); + + KUNIT_EXPECT_EQ(test, str1.hash, str2.hash); + + free_mock_sb(mock_sb); +} + +/* Test decomposition flag behavior in hash */ +static void hfsplus_hash_dentry_decompose_test(struct kunit *test) +{ + struct test_mock_sb *mock_sb; + struct qstr str1, str2; + int result; + + mock_sb = setup_mock_sb(); + KUNIT_ASSERT_NOT_NULL(test, mock_sb); + + setup_mock_dentry(&mock_sb->sb); + mock_sb->nls.char2uni = test_char2uni; + + /* Test with decomposition disabled (default) */ + clear_bit(HFSPLUS_SB_NODECOMPOSE, &mock_sb->sb_info.flags); + + create_qstr(&str1, "test"); + result = hfsplus_hash_dentry(&test_dentry, &str1); + KUNIT_EXPECT_EQ(test, 0, result); + + /* Test with decomposition enabled */ + set_bit(HFSPLUS_SB_NODECOMPOSE, &mock_sb->sb_info.flags); + + create_qstr(&str2, "test"); + result = hfsplus_hash_dentry(&test_dentry, &str2); + KUNIT_EXPECT_EQ(test, 0, result); + + /* + * For simple ASCII, decomposition shouldn't change + * the hash much but the function should still work correctly + */ + KUNIT_EXPECT_NE(test, 0, str2.hash); + + free_mock_sb(mock_sb); +} + +/* Test hash consistency and distribution */ +static void hfsplus_hash_dentry_consistency_test(struct kunit *test) +{ + struct test_mock_sb *mock_sb; + struct qstr str1, str2, str3; + unsigned long hash1; + int result; + + mock_sb = setup_mock_sb(); + KUNIT_ASSERT_NOT_NULL(test, mock_sb); + + setup_mock_dentry(&mock_sb->sb); + mock_sb->nls.char2uni = test_char2uni; + + /* Test that same string always produces same hash */ + create_qstr(&str1, "consistent"); + result = hfsplus_hash_dentry(&test_dentry, &str1); + KUNIT_EXPECT_EQ(test, 0, result); + hash1 = str1.hash; + + create_qstr(&str2, "consistent"); + result = hfsplus_hash_dentry(&test_dentry, &str2); + KUNIT_EXPECT_EQ(test, 0, result); + + KUNIT_EXPECT_EQ(test, hash1, str2.hash); + + /* Test that different strings produce different hashes */ + create_qstr(&str3, "different"); + result = hfsplus_hash_dentry(&test_dentry, &str3); + KUNIT_EXPECT_EQ(test, 0, result); + + KUNIT_EXPECT_NE(test, str1.hash, str3.hash); + + /* Test similar strings should have different hashes */ + create_qstr(&str1, "file1"); + result = hfsplus_hash_dentry(&test_dentry, &str1); + KUNIT_EXPECT_EQ(test, 0, result); + + create_qstr(&str2, "file2"); + result = hfsplus_hash_dentry(&test_dentry, &str2); + KUNIT_EXPECT_EQ(test, 0, result); + + KUNIT_EXPECT_NE(test, str1.hash, str2.hash); + + free_mock_sb(mock_sb); +} + +/* Test edge cases and boundary conditions */ +static void hfsplus_hash_dentry_edge_cases_test(struct kunit *test) +{ + struct test_mock_sb *mock_sb; + struct test_mock_string_env *mock_env; + struct qstr str; + int result; + + mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1); + KUNIT_ASSERT_NOT_NULL(test, mock_env); + + mock_sb = setup_mock_sb(); + KUNIT_ASSERT_NOT_NULL(test, mock_sb); + + setup_mock_dentry(&mock_sb->sb); + mock_sb->nls.char2uni = test_char2uni; + + /* Test very long filename */ + memset(mock_env->buf, 'a', mock_env->buf_size - 1); + mock_env->buf[mock_env->buf_size - 1] = '\0'; + + create_qstr(&str, mock_env->buf); + result = hfsplus_hash_dentry(&test_dentry, &str); + + KUNIT_EXPECT_EQ(test, 0, result); + KUNIT_EXPECT_NE(test, 0, str.hash); + + /* Test filename with all printable ASCII characters */ + create_qstr(&str, "!@#$%^&*()_+-=[]{}|;':\",./<>?"); + result = hfsplus_hash_dentry(&test_dentry, &str); + + KUNIT_EXPECT_EQ(test, 0, result); + KUNIT_EXPECT_NE(test, 0, str.hash); + + /* Test with embedded null (though not typical for filenames) */ + str.name = "file\0hidden"; + str.len = 11; /* Include the null and text after it */ + str.hash = 0; + result = hfsplus_hash_dentry(&test_dentry, &str); + + KUNIT_EXPECT_EQ(test, 0, result); + KUNIT_EXPECT_NE(test, 0, str.hash); + + free_mock_str_env(mock_env); + free_mock_sb(mock_sb); +} + +/* Test hfsplus_compare_dentry basic functionality */ +static void hfsplus_compare_dentry_basic_test(struct kunit *test) +{ + struct test_mock_sb *mock_sb; + struct qstr name; + int result; + + mock_sb = setup_mock_sb(); + KUNIT_ASSERT_NOT_NULL(test, mock_sb); + + setup_mock_dentry(&mock_sb->sb); + mock_sb->nls.char2uni = test_char2uni; + + /* Test identical strings */ + create_qstr(&name, "hello"); + result = hfsplus_compare_dentry(&test_dentry, 5, "hello", &name); + KUNIT_EXPECT_EQ(test, 0, result); + + /* Test different strings - lexicographic order */ + create_qstr(&name, "world"); + result = hfsplus_compare_dentry(&test_dentry, 5, "hello", &name); + KUNIT_EXPECT_LT(test, result, 0); /* "hello" < "world" */ + + result = hfsplus_compare_dentry(&test_dentry, 5, "world", &name); + KUNIT_EXPECT_EQ(test, 0, result); + + create_qstr(&name, "hello"); + result = hfsplus_compare_dentry(&test_dentry, 5, "world", &name); + KUNIT_EXPECT_GT(test, result, 0); /* "world" > "hello" */ + + /* Test empty strings */ + create_qstr(&name, ""); + result = hfsplus_compare_dentry(&test_dentry, 0, "", &name); + KUNIT_EXPECT_EQ(test, 0, result); + + /* Test one empty, one non-empty */ + create_qstr(&name, "test"); + result = hfsplus_compare_dentry(&test_dentry, 0, "", &name); + KUNIT_EXPECT_LT(test, result, 0); /* "" < "test" */ + + create_qstr(&name, ""); + result = hfsplus_compare_dentry(&test_dentry, 4, "test", &name); + KUNIT_EXPECT_GT(test, result, 0); /* "test" > "" */ + + free_mock_sb(mock_sb); +} + +/* Test case folding behavior in comparison */ +static void hfsplus_compare_dentry_casefold_test(struct kunit *test) +{ + struct test_mock_sb *mock_sb; + struct qstr name; + int result; + + mock_sb = setup_mock_sb(); + KUNIT_ASSERT_NOT_NULL(test, mock_sb); + + setup_mock_dentry(&mock_sb->sb); + mock_sb->nls.char2uni = test_char2uni; + + /* Test with case folding disabled (default) */ + clear_bit(HFSPLUS_SB_CASEFOLD, &mock_sb->sb_info.flags); + + create_qstr(&name, "hello"); + result = hfsplus_compare_dentry(&test_dentry, 5, "Hello", &name); + /* Case sensitive: "Hello" != "hello" */ + KUNIT_EXPECT_NE(test, 0, result); + + create_qstr(&name, "Hello"); + result = hfsplus_compare_dentry(&test_dentry, 5, "hello", &name); + /* Case sensitive: "hello" != "Hello" */ + KUNIT_EXPECT_NE(test, 0, result); + + /* Test with case folding enabled */ + set_bit(HFSPLUS_SB_CASEFOLD, &mock_sb->sb_info.flags); + + create_qstr(&name, "hello"); + result = hfsplus_compare_dentry(&test_dentry, 5, "Hello", &name); + /* Case insensitive: "Hello" == "hello" */ + KUNIT_EXPECT_EQ(test, 0, result); + + create_qstr(&name, "Hello"); + result = hfsplus_compare_dentry(&test_dentry, 5, "hello", &name); + /* Case insensitive: "hello" == "Hello" */ + KUNIT_EXPECT_EQ(test, 0, result); + + /* Test mixed case */ + create_qstr(&name, "TeSt"); + result = hfsplus_compare_dentry(&test_dentry, 4, "test", &name); + KUNIT_EXPECT_EQ(test, 0, result); + + create_qstr(&name, "test"); + result = hfsplus_compare_dentry(&test_dentry, 4, "TEST", &name); + KUNIT_EXPECT_EQ(test, 0, result); + + free_mock_sb(mock_sb); +} + +/* Test special character handling in comparison */ +static void hfsplus_compare_dentry_special_chars_test(struct kunit *test) +{ + struct test_mock_sb *mock_sb; + struct qstr name; + int result; + + mock_sb = setup_mock_sb(); + KUNIT_ASSERT_NOT_NULL(test, mock_sb); + + setup_mock_dentry(&mock_sb->sb); + mock_sb->nls.char2uni = test_char2uni; + + /* Test colon conversion (: becomes /) */ + create_qstr(&name, "file/name"); + result = hfsplus_compare_dentry(&test_dentry, 9, "file:name", &name); + /* "file:name" == "file/name" after conversion */ + KUNIT_EXPECT_EQ(test, 0, result); + + create_qstr(&name, "file:name"); + result = hfsplus_compare_dentry(&test_dentry, 9, "file/name", &name); + /* "file/name" == "file:name" after conversion */ + KUNIT_EXPECT_EQ(test, 0, result); + + /* Test multiple special characters */ + create_qstr(&name, "///"); + result = hfsplus_compare_dentry(&test_dentry, 3, ":::", &name); + KUNIT_EXPECT_EQ(test, 0, result); + + /* Test mixed special and regular characters */ + create_qstr(&name, "a/b:c"); + result = hfsplus_compare_dentry(&test_dentry, 5, "a:b/c", &name); + /* Both become "a/b/c" after conversion */ + KUNIT_EXPECT_EQ(test, 0, result); + + free_mock_sb(mock_sb); +} + +/* Test length differences */ +static void hfsplus_compare_dentry_length_test(struct kunit *test) +{ + struct test_mock_sb *mock_sb; + struct qstr name; + int result; + + mock_sb = setup_mock_sb(); + KUNIT_ASSERT_NOT_NULL(test, mock_sb); + + setup_mock_dentry(&mock_sb->sb); + mock_sb->nls.char2uni = test_char2uni; + + /* Test different lengths with common prefix */ + create_qstr(&name, "testing"); + result = hfsplus_compare_dentry(&test_dentry, 4, "test", &name); + KUNIT_EXPECT_LT(test, result, 0); /* "test" < "testing" */ + + create_qstr(&name, "test"); + result = hfsplus_compare_dentry(&test_dentry, 7, "testing", &name); + KUNIT_EXPECT_GT(test, result, 0); /* "testing" > "test" */ + + /* Test exact length match */ + create_qstr(&name, "exact"); + result = hfsplus_compare_dentry(&test_dentry, 5, "exact", &name); + KUNIT_EXPECT_EQ(test, 0, result); + + /* Test length parameter vs actual string content */ + create_qstr(&name, "hello"); + result = hfsplus_compare_dentry(&test_dentry, 3, "hel", &name); + KUNIT_EXPECT_LT(test, result, 0); /* "hel" < "hello" */ + + /* Test longer first string but shorter length parameter */ + create_qstr(&name, "hi"); + result = hfsplus_compare_dentry(&test_dentry, 2, "hello", &name); + /* "he" < "hi" (only first 2 chars compared) */ + KUNIT_EXPECT_LT(test, result, 0); + + free_mock_sb(mock_sb); +} + +/* Test decomposition flag behavior */ +static void hfsplus_compare_dentry_decompose_test(struct kunit *test) +{ + struct test_mock_sb *mock_sb; + struct qstr name; + int result; + + mock_sb = setup_mock_sb(); + KUNIT_ASSERT_NOT_NULL(test, mock_sb); + + setup_mock_dentry(&mock_sb->sb); + mock_sb->nls.char2uni = test_char2uni; + + /* Test with decomposition disabled (default) */ + clear_bit(HFSPLUS_SB_NODECOMPOSE, &mock_sb->sb_info.flags); + + create_qstr(&name, "test"); + result = hfsplus_compare_dentry(&test_dentry, 4, "test", &name); + KUNIT_EXPECT_EQ(test, 0, result); + + /* Test with decomposition enabled */ + set_bit(HFSPLUS_SB_NODECOMPOSE, &mock_sb->sb_info.flags); + + create_qstr(&name, "test"); + result = hfsplus_compare_dentry(&test_dentry, 4, "test", &name); + KUNIT_EXPECT_EQ(test, 0, result); + + /* For simple ASCII, decomposition shouldn't affect the result */ + create_qstr(&name, "different"); + result = hfsplus_compare_dentry(&test_dentry, 4, "test", &name); + KUNIT_EXPECT_NE(test, 0, result); + + free_mock_sb(mock_sb); +} + +/* Test edge cases and boundary conditions */ +static void hfsplus_compare_dentry_edge_cases_test(struct kunit *test) +{ + struct test_mock_sb *mock_sb; + struct qstr name; + char *long_str; + char *long_str2; + u32 str_size = HFSPLUS_MAX_STRLEN + 1; + struct qstr null_name = { + .name = "a\0b", + .len = 3, + .hash = 0 + }; + int result; + + mock_sb = setup_mock_sb(); + KUNIT_ASSERT_NOT_NULL(test, mock_sb); + + setup_mock_dentry(&mock_sb->sb); + mock_sb->nls.char2uni = test_char2uni; + + long_str = kzalloc(str_size, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, long_str); + + long_str2 = kzalloc(str_size, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, long_str2); + + /* Test very long strings */ + memset(long_str, 'a', str_size - 1); + long_str[str_size - 1] = '\0'; + + create_qstr(&name, long_str); + result = hfsplus_compare_dentry(&test_dentry, str_size - 1, + long_str, &name); + KUNIT_EXPECT_EQ(test, 0, result); + + /* Test with difference at the end of long strings */ + memset(long_str2, 'a', str_size - 1); + long_str2[str_size - 1] = '\0'; + long_str2[str_size - 2] = 'b'; + create_qstr(&name, long_str2); + result = hfsplus_compare_dentry(&test_dentry, str_size - 1, + long_str, &name); + KUNIT_EXPECT_LT(test, result, 0); /* 'a' < 'b' */ + + /* Test single character differences */ + create_qstr(&name, "b"); + result = hfsplus_compare_dentry(&test_dentry, 1, "a", &name); + KUNIT_EXPECT_LT(test, result, 0); /* 'a' < 'b' */ + + create_qstr(&name, "a"); + result = hfsplus_compare_dentry(&test_dentry, 1, "b", &name); + KUNIT_EXPECT_GT(test, result, 0); /* 'b' > 'a' */ + + /* Test with null characters in the middle */ + result = hfsplus_compare_dentry(&test_dentry, 3, "a\0b", &null_name); + KUNIT_EXPECT_EQ(test, 0, result); + + /* Test all printable ASCII characters */ + create_qstr(&name, "!@#$%^&*()"); + result = hfsplus_compare_dentry(&test_dentry, 10, "!@#$%^&*()", &name); + KUNIT_EXPECT_EQ(test, 0, result); + + kfree(long_str); + kfree(long_str2); + free_mock_sb(mock_sb); +} + +/* Test combined flag behaviors */ +static void hfsplus_compare_dentry_combined_flags_test(struct kunit *test) +{ + struct test_mock_sb *mock_sb; + struct qstr name; + int result; + + mock_sb = setup_mock_sb(); + KUNIT_ASSERT_NOT_NULL(test, mock_sb); + + setup_mock_dentry(&mock_sb->sb); + mock_sb->nls.char2uni = test_char2uni; + + /* Test with both casefold and decompose enabled */ + set_bit(HFSPLUS_SB_CASEFOLD, &mock_sb->sb_info.flags); + set_bit(HFSPLUS_SB_NODECOMPOSE, &mock_sb->sb_info.flags); + + create_qstr(&name, "hello"); + result = hfsplus_compare_dentry(&test_dentry, 5, "HELLO", &name); + KUNIT_EXPECT_EQ(test, 0, result); + + /* Test special chars with case folding */ + create_qstr(&name, "File/Name"); + result = hfsplus_compare_dentry(&test_dentry, 9, "file:name", &name); + KUNIT_EXPECT_EQ(test, 0, result); + + /* Test with both flags disabled */ + clear_bit(HFSPLUS_SB_CASEFOLD, &mock_sb->sb_info.flags); + clear_bit(HFSPLUS_SB_NODECOMPOSE, &mock_sb->sb_info.flags); + + create_qstr(&name, "hello"); + result = hfsplus_compare_dentry(&test_dentry, 5, "HELLO", &name); + KUNIT_EXPECT_NE(test, 0, result); /* Case sensitive */ + + /* But special chars should still be converted */ + create_qstr(&name, "file/name"); + result = hfsplus_compare_dentry(&test_dentry, 9, "file:name", &name); + KUNIT_EXPECT_EQ(test, 0, result); + + free_mock_sb(mock_sb); +} + +static struct kunit_case hfsplus_unicode_test_cases[] = { + KUNIT_CASE(hfsplus_strcasecmp_test), + KUNIT_CASE(hfsplus_strcmp_test), + KUNIT_CASE(hfsplus_unicode_edge_cases_test), + KUNIT_CASE(hfsplus_unicode_boundary_test), + KUNIT_CASE(hfsplus_uni2asc_basic_test), + KUNIT_CASE(hfsplus_uni2asc_special_chars_test), + KUNIT_CASE(hfsplus_uni2asc_buffer_test), + KUNIT_CASE(hfsplus_uni2asc_corrupted_test), + KUNIT_CASE(hfsplus_uni2asc_edge_cases_test), + KUNIT_CASE(hfsplus_asc2uni_basic_test), + KUNIT_CASE(hfsplus_asc2uni_special_chars_test), + KUNIT_CASE(hfsplus_asc2uni_buffer_limits_test), + KUNIT_CASE(hfsplus_asc2uni_edge_cases_test), + KUNIT_CASE(hfsplus_asc2uni_decompose_test), + KUNIT_CASE(hfsplus_hash_dentry_basic_test), + KUNIT_CASE(hfsplus_hash_dentry_casefold_test), + KUNIT_CASE(hfsplus_hash_dentry_special_chars_test), + KUNIT_CASE(hfsplus_hash_dentry_decompose_test), + KUNIT_CASE(hfsplus_hash_dentry_consistency_test), + KUNIT_CASE(hfsplus_hash_dentry_edge_cases_test), + KUNIT_CASE(hfsplus_compare_dentry_basic_test), + KUNIT_CASE(hfsplus_compare_dentry_casefold_test), + KUNIT_CASE(hfsplus_compare_dentry_special_chars_test), + KUNIT_CASE(hfsplus_compare_dentry_length_test), + KUNIT_CASE(hfsplus_compare_dentry_decompose_test), + KUNIT_CASE(hfsplus_compare_dentry_edge_cases_test), + KUNIT_CASE(hfsplus_compare_dentry_combined_flags_test), + {} +}; + +static struct kunit_suite hfsplus_unicode_test_suite = { + .name = "hfsplus_unicode", + .test_cases = hfsplus_unicode_test_cases, +}; + +kunit_test_suite(hfsplus_unicode_test_suite); + +MODULE_DESCRIPTION("KUnit tests for HFS+ Unicode string operations"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); |
