| // 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"); |