xref: /linux/fs/hfsplus/unicode_test.c (revision 12d5fc96ebbb3b948861805b32a4c3a96105ccd9)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * KUnit tests for HFS+ Unicode string operations
4  *
5  * Copyright (C) 2025 Viacheslav Dubeyko <slava@dubeyko.com>
6  */
7 
8 #include <kunit/test.h>
9 #include <linux/nls.h>
10 #include <linux/dcache.h>
11 #include <linux/stringhash.h>
12 #include "hfsplus_fs.h"
13 #include "unicode.h"
14 
15 struct test_mock_string_env {
16 	struct hfsplus_unistr str1;
17 	struct hfsplus_unistr str2;
18 	char *buf;
19 	u32 buf_size;
20 };
21 
22 static struct test_mock_string_env *setup_mock_str_env(u32 buf_size)
23 {
24 	struct test_mock_string_env *env;
25 
26 	env = kzalloc_obj(struct test_mock_string_env);
27 	if (!env)
28 		return NULL;
29 
30 	env->buf = kzalloc(buf_size, GFP_KERNEL);
31 	if (!env->buf) {
32 		kfree(env);
33 		return NULL;
34 	}
35 
36 	env->buf_size = buf_size;
37 
38 	return env;
39 }
40 
41 static void free_mock_str_env(struct test_mock_string_env *env)
42 {
43 	kfree(env->buf);
44 	kfree(env);
45 }
46 
47 /* Helper function to create hfsplus_unistr */
48 static void create_unistr(struct hfsplus_unistr *ustr, const char *ascii_str)
49 {
50 	int len = strlen(ascii_str);
51 	int i;
52 
53 	memset(ustr->unicode, 0, sizeof(ustr->unicode));
54 
55 	ustr->length = cpu_to_be16(len);
56 	for (i = 0; i < len && i < HFSPLUS_MAX_STRLEN; i++)
57 		ustr->unicode[i] = cpu_to_be16((u16)ascii_str[i]);
58 }
59 
60 static void corrupt_unistr(struct hfsplus_unistr *ustr)
61 {
62 	ustr->length = cpu_to_be16(U16_MAX);
63 }
64 
65 /* Test hfsplus_strcasecmp function */
66 static void hfsplus_strcasecmp_test(struct kunit *test)
67 {
68 	struct test_mock_string_env *mock_env;
69 
70 	mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1);
71 	KUNIT_ASSERT_NOT_NULL(test, mock_env);
72 
73 	/* Test identical strings */
74 	create_unistr(&mock_env->str1, "hello");
75 	create_unistr(&mock_env->str2, "hello");
76 	KUNIT_EXPECT_EQ(test, 0, hfsplus_strcasecmp(&mock_env->str1,
77 						    &mock_env->str2));
78 
79 	/* Test case insensitive comparison */
80 	create_unistr(&mock_env->str1, "Hello");
81 	create_unistr(&mock_env->str2, "hello");
82 	KUNIT_EXPECT_EQ(test, 0, hfsplus_strcasecmp(&mock_env->str1,
83 						    &mock_env->str2));
84 
85 	create_unistr(&mock_env->str1, "HELLO");
86 	create_unistr(&mock_env->str2, "hello");
87 	KUNIT_EXPECT_EQ(test, 0, hfsplus_strcasecmp(&mock_env->str1,
88 						    &mock_env->str2));
89 
90 	/* Test different strings */
91 	create_unistr(&mock_env->str1, "apple");
92 	create_unistr(&mock_env->str2, "banana");
93 	KUNIT_EXPECT_LT(test, hfsplus_strcasecmp(&mock_env->str1,
94 						 &mock_env->str2), 0);
95 
96 	create_unistr(&mock_env->str1, "zebra");
97 	create_unistr(&mock_env->str2, "apple");
98 	KUNIT_EXPECT_GT(test, hfsplus_strcasecmp(&mock_env->str1,
99 						 &mock_env->str2), 0);
100 
101 	/* Test different lengths */
102 	create_unistr(&mock_env->str1, "test");
103 	create_unistr(&mock_env->str2, "testing");
104 	KUNIT_EXPECT_LT(test, hfsplus_strcasecmp(&mock_env->str1,
105 						 &mock_env->str2), 0);
106 
107 	create_unistr(&mock_env->str1, "testing");
108 	create_unistr(&mock_env->str2, "test");
109 	KUNIT_EXPECT_GT(test, hfsplus_strcasecmp(&mock_env->str1,
110 						 &mock_env->str2), 0);
111 
112 	/* Test empty strings */
113 	create_unistr(&mock_env->str1, "");
114 	create_unistr(&mock_env->str2, "");
115 	KUNIT_EXPECT_EQ(test, 0, hfsplus_strcasecmp(&mock_env->str1,
116 						    &mock_env->str2));
117 
118 	create_unistr(&mock_env->str1, "");
119 	create_unistr(&mock_env->str2, "test");
120 	KUNIT_EXPECT_LT(test, hfsplus_strcasecmp(&mock_env->str1,
121 						 &mock_env->str2), 0);
122 
123 	/* Test single characters */
124 	create_unistr(&mock_env->str1, "A");
125 	create_unistr(&mock_env->str2, "a");
126 	KUNIT_EXPECT_EQ(test, 0, hfsplus_strcasecmp(&mock_env->str1,
127 						    &mock_env->str2));
128 
129 	create_unistr(&mock_env->str1, "A");
130 	create_unistr(&mock_env->str2, "B");
131 	KUNIT_EXPECT_LT(test, hfsplus_strcasecmp(&mock_env->str1,
132 						 &mock_env->str2), 0);
133 
134 	/* Test maximum length strings */
135 	memset(mock_env->buf, 'a', HFSPLUS_MAX_STRLEN);
136 	mock_env->buf[HFSPLUS_MAX_STRLEN] = '\0';
137 	create_unistr(&mock_env->str1, mock_env->buf);
138 	create_unistr(&mock_env->str2, mock_env->buf);
139 	KUNIT_EXPECT_EQ(test, 0, hfsplus_strcasecmp(&mock_env->str1,
140 						    &mock_env->str2));
141 
142 	/* Change one character in the middle */
143 	mock_env->buf[HFSPLUS_MAX_STRLEN / 2] = 'b';
144 	create_unistr(&mock_env->str2, mock_env->buf);
145 	KUNIT_EXPECT_LT(test, hfsplus_strcasecmp(&mock_env->str1,
146 						 &mock_env->str2), 0);
147 
148 	/* Test corrupted strings */
149 	create_unistr(&mock_env->str1, "");
150 	corrupt_unistr(&mock_env->str1);
151 	create_unistr(&mock_env->str2, "");
152 	KUNIT_EXPECT_NE(test, 0, hfsplus_strcasecmp(&mock_env->str1,
153 						    &mock_env->str2));
154 
155 	create_unistr(&mock_env->str1, "");
156 	create_unistr(&mock_env->str2, "");
157 	corrupt_unistr(&mock_env->str2);
158 	KUNIT_EXPECT_NE(test, 0, hfsplus_strcasecmp(&mock_env->str1,
159 						    &mock_env->str2));
160 
161 	create_unistr(&mock_env->str1, "test");
162 	corrupt_unistr(&mock_env->str1);
163 	create_unistr(&mock_env->str2, "testing");
164 	KUNIT_EXPECT_GT(test, hfsplus_strcasecmp(&mock_env->str1,
165 						 &mock_env->str2), 0);
166 
167 	create_unistr(&mock_env->str1, "test");
168 	create_unistr(&mock_env->str2, "testing");
169 	corrupt_unistr(&mock_env->str2);
170 	KUNIT_EXPECT_LT(test, hfsplus_strcasecmp(&mock_env->str1,
171 						 &mock_env->str2), 0);
172 
173 	create_unistr(&mock_env->str1, "testing");
174 	corrupt_unistr(&mock_env->str1);
175 	create_unistr(&mock_env->str2, "test");
176 	KUNIT_EXPECT_GT(test, hfsplus_strcasecmp(&mock_env->str1,
177 						 &mock_env->str2), 0);
178 
179 	create_unistr(&mock_env->str1, "testing");
180 	create_unistr(&mock_env->str2, "test");
181 	corrupt_unistr(&mock_env->str2);
182 	KUNIT_EXPECT_LT(test, hfsplus_strcasecmp(&mock_env->str1,
183 						 &mock_env->str2), 0);
184 
185 	free_mock_str_env(mock_env);
186 }
187 
188 /* Test hfsplus_strcmp function (case-sensitive) */
189 static void hfsplus_strcmp_test(struct kunit *test)
190 {
191 	struct test_mock_string_env *mock_env;
192 
193 	mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1);
194 	KUNIT_ASSERT_NOT_NULL(test, mock_env);
195 
196 	/* Test identical strings */
197 	create_unistr(&mock_env->str1, "hello");
198 	create_unistr(&mock_env->str2, "hello");
199 	KUNIT_EXPECT_EQ(test, 0, hfsplus_strcmp(&mock_env->str1,
200 						&mock_env->str2));
201 
202 	/* Test case sensitive comparison - should NOT be equal */
203 	create_unistr(&mock_env->str1, "Hello");
204 	create_unistr(&mock_env->str2, "hello");
205 	KUNIT_EXPECT_NE(test, 0, hfsplus_strcmp(&mock_env->str1,
206 						&mock_env->str2));
207 	 /* 'H' < 'h' in Unicode */
208 	KUNIT_EXPECT_LT(test, hfsplus_strcmp(&mock_env->str1,
209 					     &mock_env->str2), 0);
210 
211 	/* Test lexicographic ordering */
212 	create_unistr(&mock_env->str1, "apple");
213 	create_unistr(&mock_env->str2, "banana");
214 	KUNIT_EXPECT_LT(test, hfsplus_strcmp(&mock_env->str1,
215 					     &mock_env->str2), 0);
216 
217 	create_unistr(&mock_env->str1, "zebra");
218 	create_unistr(&mock_env->str2, "apple");
219 	KUNIT_EXPECT_GT(test, hfsplus_strcmp(&mock_env->str1,
220 					     &mock_env->str2), 0);
221 
222 	/* Test different lengths with common prefix */
223 	create_unistr(&mock_env->str1, "test");
224 	create_unistr(&mock_env->str2, "testing");
225 	KUNIT_EXPECT_LT(test, hfsplus_strcmp(&mock_env->str1,
226 					     &mock_env->str2), 0);
227 
228 	create_unistr(&mock_env->str1, "testing");
229 	create_unistr(&mock_env->str2, "test");
230 	KUNIT_EXPECT_GT(test, hfsplus_strcmp(&mock_env->str1,
231 					     &mock_env->str2), 0);
232 
233 	/* Test empty strings */
234 	create_unistr(&mock_env->str1, "");
235 	create_unistr(&mock_env->str2, "");
236 	KUNIT_EXPECT_EQ(test, 0, hfsplus_strcmp(&mock_env->str1,
237 						&mock_env->str2));
238 
239 	/* Test maximum length strings */
240 	memset(mock_env->buf, 'a', HFSPLUS_MAX_STRLEN);
241 	mock_env->buf[HFSPLUS_MAX_STRLEN] = '\0';
242 	create_unistr(&mock_env->str1, mock_env->buf);
243 	create_unistr(&mock_env->str2, mock_env->buf);
244 	KUNIT_EXPECT_EQ(test, 0, hfsplus_strcmp(&mock_env->str1,
245 						&mock_env->str2));
246 
247 	/* Change one character in the middle */
248 	mock_env->buf[HFSPLUS_MAX_STRLEN / 2] = 'b';
249 	create_unistr(&mock_env->str2, mock_env->buf);
250 	KUNIT_EXPECT_LT(test, hfsplus_strcmp(&mock_env->str1,
251 					     &mock_env->str2), 0);
252 
253 	/* Test corrupted strings */
254 	create_unistr(&mock_env->str1, "");
255 	corrupt_unistr(&mock_env->str1);
256 	create_unistr(&mock_env->str2, "");
257 	KUNIT_EXPECT_NE(test, 0, hfsplus_strcmp(&mock_env->str1,
258 						&mock_env->str2));
259 
260 	create_unistr(&mock_env->str1, "");
261 	create_unistr(&mock_env->str2, "");
262 	corrupt_unistr(&mock_env->str2);
263 	KUNIT_EXPECT_NE(test, 0, hfsplus_strcmp(&mock_env->str1,
264 						&mock_env->str2));
265 
266 	create_unistr(&mock_env->str1, "test");
267 	corrupt_unistr(&mock_env->str1);
268 	create_unistr(&mock_env->str2, "testing");
269 	KUNIT_EXPECT_LT(test, hfsplus_strcmp(&mock_env->str1,
270 					     &mock_env->str2), 0);
271 
272 	create_unistr(&mock_env->str1, "test");
273 	create_unistr(&mock_env->str2, "testing");
274 	corrupt_unistr(&mock_env->str2);
275 	KUNIT_EXPECT_LT(test, hfsplus_strcmp(&mock_env->str1,
276 					     &mock_env->str2), 0);
277 
278 	create_unistr(&mock_env->str1, "testing");
279 	corrupt_unistr(&mock_env->str1);
280 	create_unistr(&mock_env->str2, "test");
281 	KUNIT_EXPECT_GT(test, hfsplus_strcmp(&mock_env->str1,
282 					     &mock_env->str2), 0);
283 
284 	create_unistr(&mock_env->str1, "testing");
285 	create_unistr(&mock_env->str2, "test");
286 	corrupt_unistr(&mock_env->str2);
287 	KUNIT_EXPECT_GT(test, hfsplus_strcmp(&mock_env->str1,
288 					     &mock_env->str2), 0);
289 
290 	free_mock_str_env(mock_env);
291 }
292 
293 /* Test Unicode edge cases */
294 static void hfsplus_unicode_edge_cases_test(struct kunit *test)
295 {
296 	struct test_mock_string_env *mock_env;
297 
298 	mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1);
299 	KUNIT_ASSERT_NOT_NULL(test, mock_env);
300 
301 	/* Test with special characters */
302 	mock_env->str1.length = cpu_to_be16(3);
303 	mock_env->str1.unicode[0] = cpu_to_be16(0x00E9); /* é */
304 	mock_env->str1.unicode[1] = cpu_to_be16(0x00F1); /* ñ */
305 	mock_env->str1.unicode[2] = cpu_to_be16(0x00FC); /* ü */
306 
307 	mock_env->str2.length = cpu_to_be16(3);
308 	mock_env->str2.unicode[0] = cpu_to_be16(0x00E9); /* é */
309 	mock_env->str2.unicode[1] = cpu_to_be16(0x00F1); /* ñ */
310 	mock_env->str2.unicode[2] = cpu_to_be16(0x00FC); /* ü */
311 
312 	KUNIT_EXPECT_EQ(test, 0, hfsplus_strcmp(&mock_env->str1,
313 						&mock_env->str2));
314 	KUNIT_EXPECT_EQ(test, 0, hfsplus_strcasecmp(&mock_env->str1,
315 						    &mock_env->str2));
316 
317 	/* Test with different special characters */
318 	mock_env->str2.unicode[1] = cpu_to_be16(0x00F2); /* ò */
319 	KUNIT_EXPECT_NE(test, 0, hfsplus_strcmp(&mock_env->str1,
320 						&mock_env->str2));
321 
322 	/* Test null characters within string (should be handled correctly) */
323 	mock_env->str1.length = cpu_to_be16(3);
324 	mock_env->str1.unicode[0] = cpu_to_be16('a');
325 	mock_env->str1.unicode[1] = cpu_to_be16(0x0000); /* null */
326 	mock_env->str1.unicode[2] = cpu_to_be16('b');
327 
328 	mock_env->str2.length = cpu_to_be16(3);
329 	mock_env->str2.unicode[0] = cpu_to_be16('a');
330 	mock_env->str2.unicode[1] = cpu_to_be16(0x0000); /* null */
331 	mock_env->str2.unicode[2] = cpu_to_be16('b');
332 
333 	KUNIT_EXPECT_EQ(test, 0, hfsplus_strcmp(&mock_env->str1,
334 						&mock_env->str2));
335 
336 	free_mock_str_env(mock_env);
337 }
338 
339 /* Test boundary conditions */
340 static void hfsplus_unicode_boundary_test(struct kunit *test)
341 {
342 	struct test_mock_string_env *mock_env;
343 	int i;
344 
345 	mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1);
346 	KUNIT_ASSERT_NOT_NULL(test, mock_env);
347 
348 	/* Test maximum length boundary */
349 	mock_env->str1.length = cpu_to_be16(HFSPLUS_MAX_STRLEN);
350 	mock_env->str2.length = cpu_to_be16(HFSPLUS_MAX_STRLEN);
351 
352 	for (i = 0; i < HFSPLUS_MAX_STRLEN; i++) {
353 		mock_env->str1.unicode[i] = cpu_to_be16('A');
354 		mock_env->str2.unicode[i] = cpu_to_be16('A');
355 	}
356 
357 	KUNIT_EXPECT_EQ(test, 0, hfsplus_strcmp(&mock_env->str1,
358 						&mock_env->str2));
359 
360 	/* Change last character */
361 	mock_env->str2.unicode[HFSPLUS_MAX_STRLEN - 1] = cpu_to_be16('B');
362 	KUNIT_EXPECT_LT(test, hfsplus_strcmp(&mock_env->str1,
363 					     &mock_env->str2), 0);
364 
365 	/* Test zero length strings */
366 	mock_env->str1.length = cpu_to_be16(0);
367 	mock_env->str2.length = cpu_to_be16(0);
368 	KUNIT_EXPECT_EQ(test, 0, hfsplus_strcmp(&mock_env->str1,
369 						&mock_env->str2));
370 	KUNIT_EXPECT_EQ(test, 0, hfsplus_strcasecmp(&mock_env->str1,
371 						    &mock_env->str2));
372 
373 	/* Test one character vs empty */
374 	mock_env->str1.length = cpu_to_be16(1);
375 	mock_env->str1.unicode[0] = cpu_to_be16('A');
376 	mock_env->str2.length = cpu_to_be16(0);
377 	KUNIT_EXPECT_GT(test, hfsplus_strcmp(&mock_env->str1,
378 					     &mock_env->str2), 0);
379 	KUNIT_EXPECT_GT(test, hfsplus_strcasecmp(&mock_env->str1,
380 						 &mock_env->str2), 0);
381 
382 	free_mock_str_env(mock_env);
383 }
384 
385 /* Mock superblock and NLS table for testing hfsplus_uni2asc */
386 struct test_mock_sb {
387 	struct nls_table nls;
388 	struct hfsplus_sb_info sb_info;
389 	struct super_block sb;
390 };
391 
392 static struct test_mock_sb *setup_mock_sb(void)
393 {
394 	struct test_mock_sb *ptr;
395 
396 	ptr = kzalloc_obj(struct test_mock_sb);
397 	if (!ptr)
398 		return NULL;
399 
400 	ptr->nls.charset = "utf8";
401 	ptr->nls.uni2char = NULL; /* Will use default behavior */
402 	ptr->sb_info.nls = &ptr->nls;
403 	ptr->sb.s_fs_info = &ptr->sb_info;
404 
405 	/* Set default flags - no decomposition, no case folding */
406 	clear_bit(HFSPLUS_SB_NODECOMPOSE, &ptr->sb_info.flags);
407 	clear_bit(HFSPLUS_SB_CASEFOLD, &ptr->sb_info.flags);
408 
409 	return ptr;
410 }
411 
412 static void free_mock_sb(struct test_mock_sb *ptr)
413 {
414 	kfree(ptr);
415 }
416 
417 /* Simple uni2char implementation for testing */
418 static int test_uni2char(wchar_t uni, unsigned char *out, int boundlen)
419 {
420 	if (boundlen <= 0)
421 		return -ENAMETOOLONG;
422 
423 	if (uni < 0x80) {
424 		*out = (unsigned char)uni;
425 		return 1;
426 	}
427 
428 	/* For non-ASCII, just use '?' as fallback */
429 	*out = '?';
430 	return 1;
431 }
432 
433 /* Test hfsplus_uni2asc basic functionality */
434 static void hfsplus_uni2asc_basic_test(struct kunit *test)
435 {
436 	struct test_mock_sb *mock_sb;
437 	struct test_mock_string_env *mock_env;
438 	int len, result;
439 
440 	mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1);
441 	KUNIT_ASSERT_NOT_NULL(test, mock_env);
442 
443 	mock_sb = setup_mock_sb();
444 	KUNIT_ASSERT_NOT_NULL(test, mock_sb);
445 
446 	mock_sb->nls.uni2char = test_uni2char;
447 
448 	/* Test simple ASCII string conversion */
449 	create_unistr(&mock_env->str1, "hello");
450 	len = mock_env->buf_size;
451 	result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1,
452 				     mock_env->buf, &len);
453 
454 	KUNIT_EXPECT_EQ(test, 0, result);
455 	KUNIT_EXPECT_EQ(test, 5, len);
456 	KUNIT_EXPECT_STREQ(test, "hello", mock_env->buf);
457 
458 	/* Test empty string */
459 	create_unistr(&mock_env->str1, "");
460 	len = mock_env->buf_size;
461 	result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1,
462 				     mock_env->buf, &len);
463 
464 	KUNIT_EXPECT_EQ(test, 0, result);
465 	KUNIT_EXPECT_EQ(test, 0, len);
466 
467 	/* Test single character */
468 	create_unistr(&mock_env->str1, "A");
469 	len = mock_env->buf_size;
470 	result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1,
471 				     mock_env->buf, &len);
472 
473 	KUNIT_EXPECT_EQ(test, 0, result);
474 	KUNIT_EXPECT_EQ(test, 1, len);
475 	KUNIT_EXPECT_EQ(test, 'A', mock_env->buf[0]);
476 
477 	free_mock_str_env(mock_env);
478 	free_mock_sb(mock_sb);
479 }
480 
481 /* Test special character handling */
482 static void hfsplus_uni2asc_special_chars_test(struct kunit *test)
483 {
484 	struct test_mock_sb *mock_sb;
485 	struct test_mock_string_env *mock_env;
486 	int len, result;
487 
488 	mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1);
489 	KUNIT_ASSERT_NOT_NULL(test, mock_env);
490 
491 	mock_sb = setup_mock_sb();
492 	KUNIT_ASSERT_NOT_NULL(test, mock_sb);
493 
494 	mock_sb->nls.uni2char = test_uni2char;
495 
496 	/* Test null character conversion (should become 0x2400) */
497 	mock_env->str1.length = cpu_to_be16(1);
498 	mock_env->str1.unicode[0] = cpu_to_be16(0x0000);
499 	len = mock_env->buf_size;
500 	result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1,
501 				     mock_env->buf, &len);
502 
503 	KUNIT_EXPECT_EQ(test, 0, result);
504 	KUNIT_EXPECT_EQ(test, 1, len);
505 	/* Our test implementation returns '?' for non-ASCII */
506 	KUNIT_EXPECT_EQ(test, '?', mock_env->buf[0]);
507 
508 	/* Test forward slash conversion (should become colon) */
509 	mock_env->str1.length = cpu_to_be16(1);
510 	mock_env->str1.unicode[0] = cpu_to_be16('/');
511 	len = mock_env->buf_size;
512 	result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1,
513 				     mock_env->buf, &len);
514 
515 	KUNIT_EXPECT_EQ(test, 0, result);
516 	KUNIT_EXPECT_EQ(test, 1, len);
517 	KUNIT_EXPECT_EQ(test, ':', mock_env->buf[0]);
518 
519 	/* Test string with mixed special characters */
520 	mock_env->str1.length = cpu_to_be16(3);
521 	mock_env->str1.unicode[0] = cpu_to_be16('a');
522 	mock_env->str1.unicode[1] = cpu_to_be16('/');
523 	mock_env->str1.unicode[2] = cpu_to_be16('b');
524 	len = mock_env->buf_size;
525 	result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1,
526 				     mock_env->buf, &len);
527 
528 	KUNIT_EXPECT_EQ(test, 0, result);
529 	KUNIT_EXPECT_EQ(test, 3, len);
530 	KUNIT_EXPECT_EQ(test, 'a', mock_env->buf[0]);
531 	KUNIT_EXPECT_EQ(test, ':', mock_env->buf[1]);
532 	KUNIT_EXPECT_EQ(test, 'b', mock_env->buf[2]);
533 
534 	free_mock_str_env(mock_env);
535 	free_mock_sb(mock_sb);
536 }
537 
538 /* Test buffer length handling */
539 static void hfsplus_uni2asc_buffer_test(struct kunit *test)
540 {
541 	struct test_mock_sb *mock_sb;
542 	struct test_mock_string_env *mock_env;
543 	int len, result;
544 
545 	mock_env = setup_mock_str_env(10);
546 	KUNIT_ASSERT_NOT_NULL(test, mock_env);
547 
548 	mock_sb = setup_mock_sb();
549 	KUNIT_ASSERT_NOT_NULL(test, mock_sb);
550 
551 	mock_sb->nls.uni2char = test_uni2char;
552 
553 	/* Test insufficient buffer space */
554 	create_unistr(&mock_env->str1, "toolongstring");
555 	len = 5; /* Buffer too small */
556 	result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1,
557 				     mock_env->buf, &len);
558 
559 	KUNIT_EXPECT_EQ(test, -ENAMETOOLONG, result);
560 	KUNIT_EXPECT_EQ(test, 5, len); /* Should be set to consumed length */
561 
562 	/* Test exact buffer size */
563 	create_unistr(&mock_env->str1, "exact");
564 	len = 5;
565 	result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1,
566 				     mock_env->buf, &len);
567 
568 	KUNIT_EXPECT_EQ(test, 0, result);
569 	KUNIT_EXPECT_EQ(test, 5, len);
570 
571 	/* Test zero length buffer */
572 	create_unistr(&mock_env->str1, "test");
573 	len = 0;
574 	result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1,
575 				     mock_env->buf, &len);
576 
577 	KUNIT_EXPECT_EQ(test, -ENAMETOOLONG, result);
578 	KUNIT_EXPECT_EQ(test, 0, len);
579 
580 	free_mock_str_env(mock_env);
581 	free_mock_sb(mock_sb);
582 }
583 
584 /* Test corrupted unicode string handling */
585 static void hfsplus_uni2asc_corrupted_test(struct kunit *test)
586 {
587 	struct test_mock_sb *mock_sb;
588 	struct test_mock_string_env *mock_env;
589 	int len, result;
590 
591 	mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1);
592 	KUNIT_ASSERT_NOT_NULL(test, mock_env);
593 
594 	mock_sb = setup_mock_sb();
595 	KUNIT_ASSERT_NOT_NULL(test, mock_sb);
596 
597 	mock_sb->nls.uni2char = test_uni2char;
598 
599 	/* Test corrupted length (too large) */
600 	create_unistr(&mock_env->str1, "test");
601 	corrupt_unistr(&mock_env->str1); /* Sets length to U16_MAX */
602 	len = mock_env->buf_size;
603 
604 	result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1,
605 				     mock_env->buf, &len);
606 
607 	/* Should still work but with corrected length */
608 	KUNIT_EXPECT_EQ(test, 0, result);
609 	/*
610 	 * Length should be corrected to HFSPLUS_MAX_STRLEN
611 	 * and processed accordingly
612 	 */
613 	KUNIT_EXPECT_GT(test, len, 0);
614 
615 	free_mock_str_env(mock_env);
616 	free_mock_sb(mock_sb);
617 }
618 
619 /* Test edge cases and boundary conditions */
620 static void hfsplus_uni2asc_edge_cases_test(struct kunit *test)
621 {
622 	struct test_mock_sb *mock_sb;
623 	struct test_mock_string_env *mock_env;
624 	int len, result;
625 	int i;
626 
627 	mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN * 2);
628 	KUNIT_ASSERT_NOT_NULL(test, mock_env);
629 
630 	mock_sb = setup_mock_sb();
631 	KUNIT_ASSERT_NOT_NULL(test, mock_sb);
632 
633 	mock_sb->nls.uni2char = test_uni2char;
634 
635 	/* Test maximum length string */
636 	mock_env->str1.length = cpu_to_be16(HFSPLUS_MAX_STRLEN);
637 	for (i = 0; i < HFSPLUS_MAX_STRLEN; i++)
638 		mock_env->str1.unicode[i] = cpu_to_be16('a');
639 
640 	len = mock_env->buf_size;
641 	result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1,
642 				     mock_env->buf, &len);
643 
644 	KUNIT_EXPECT_EQ(test, 0, result);
645 	KUNIT_EXPECT_EQ(test, HFSPLUS_MAX_STRLEN, len);
646 
647 	/* Verify all characters are 'a' */
648 	for (i = 0; i < HFSPLUS_MAX_STRLEN; i++)
649 		KUNIT_EXPECT_EQ(test, 'a', mock_env->buf[i]);
650 
651 	/* Test string with high Unicode values (non-ASCII) */
652 	mock_env->str1.length = cpu_to_be16(3);
653 	mock_env->str1.unicode[0] = cpu_to_be16(0x00E9); /* é */
654 	mock_env->str1.unicode[1] = cpu_to_be16(0x00F1); /* ñ */
655 	mock_env->str1.unicode[2] = cpu_to_be16(0x00FC); /* ü */
656 	len = mock_env->buf_size;
657 	result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1,
658 				     mock_env->buf, &len);
659 
660 	KUNIT_EXPECT_EQ(test, 0, result);
661 	KUNIT_EXPECT_EQ(test, 3, len);
662 	/* Our test implementation converts non-ASCII to '?' */
663 	KUNIT_EXPECT_EQ(test, '?', mock_env->buf[0]);
664 	KUNIT_EXPECT_EQ(test, '?', mock_env->buf[1]);
665 	KUNIT_EXPECT_EQ(test, '?', mock_env->buf[2]);
666 
667 	free_mock_str_env(mock_env);
668 	free_mock_sb(mock_sb);
669 }
670 
671 /* Simple char2uni implementation for testing */
672 static int test_char2uni(const unsigned char *rawstring,
673 			 int boundlen, wchar_t *uni)
674 {
675 	if (boundlen <= 0)
676 		return -EINVAL;
677 
678 	*uni = (wchar_t)*rawstring;
679 	return 1;
680 }
681 
682 /* Helper function to check unicode string contents */
683 static void check_unistr_content(struct kunit *test,
684 				 struct hfsplus_unistr *ustr,
685 				 const char *expected_ascii)
686 {
687 	int expected_len = strlen(expected_ascii);
688 	int actual_len = be16_to_cpu(ustr->length);
689 	int i;
690 
691 	KUNIT_EXPECT_EQ(test, expected_len, actual_len);
692 
693 	for (i = 0; i < expected_len && i < actual_len; i++) {
694 		u16 expected_char = (u16)expected_ascii[i];
695 		u16 actual_char = be16_to_cpu(ustr->unicode[i]);
696 
697 		KUNIT_EXPECT_EQ(test, expected_char, actual_char);
698 	}
699 }
700 
701 /* Test hfsplus_asc2uni basic functionality */
702 static void hfsplus_asc2uni_basic_test(struct kunit *test)
703 {
704 	struct test_mock_sb *mock_sb;
705 	struct test_mock_string_env *mock_env;
706 	int result;
707 
708 	mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1);
709 	KUNIT_ASSERT_NOT_NULL(test, mock_env);
710 
711 	mock_sb = setup_mock_sb();
712 	KUNIT_ASSERT_NOT_NULL(test, mock_sb);
713 
714 	mock_sb->nls.char2uni = test_char2uni;
715 
716 	/* Test simple ASCII string conversion */
717 	result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1,
718 				 HFSPLUS_MAX_STRLEN, "hello", 5,
719 				 HFS_REGULAR_NAME);
720 
721 	KUNIT_EXPECT_EQ(test, 0, result);
722 	check_unistr_content(test, &mock_env->str1, "hello");
723 
724 	/* Test empty string */
725 	result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1,
726 				 HFSPLUS_MAX_STRLEN, "", 0,
727 				 HFS_REGULAR_NAME);
728 
729 	KUNIT_EXPECT_EQ(test, 0, result);
730 	KUNIT_EXPECT_EQ(test, 0, be16_to_cpu(mock_env->str1.length));
731 
732 	/* Test single character */
733 	result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1,
734 				 HFSPLUS_MAX_STRLEN, "A", 1,
735 				 HFS_REGULAR_NAME);
736 
737 	KUNIT_EXPECT_EQ(test, 0, result);
738 	check_unistr_content(test, &mock_env->str1, "A");
739 
740 	/* Test null-terminated string with explicit length */
741 	result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1,
742 				 HFSPLUS_MAX_STRLEN, "test\0extra", 4,
743 				 HFS_REGULAR_NAME);
744 
745 	KUNIT_EXPECT_EQ(test, 0, result);
746 	check_unistr_content(test, &mock_env->str1, "test");
747 
748 	free_mock_str_env(mock_env);
749 	free_mock_sb(mock_sb);
750 }
751 
752 /* Test special character handling in asc2uni */
753 static void hfsplus_asc2uni_special_chars_test(struct kunit *test)
754 {
755 	struct test_mock_sb *mock_sb;
756 	struct test_mock_string_env *mock_env;
757 	int result;
758 
759 	mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1);
760 	KUNIT_ASSERT_NOT_NULL(test, mock_env);
761 
762 	mock_sb = setup_mock_sb();
763 	KUNIT_ASSERT_NOT_NULL(test, mock_sb);
764 
765 	mock_sb->nls.char2uni = test_char2uni;
766 
767 	/* Test colon conversion (should become forward slash) */
768 	result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1,
769 				 HFSPLUS_MAX_STRLEN, ":", 1,
770 				 HFS_REGULAR_NAME);
771 
772 	KUNIT_EXPECT_EQ(test, 0, result);
773 	KUNIT_EXPECT_EQ(test, 1, be16_to_cpu(mock_env->str1.length));
774 	KUNIT_EXPECT_EQ(test, '/', be16_to_cpu(mock_env->str1.unicode[0]));
775 
776 	/* Test string with mixed special characters */
777 	result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1,
778 				 HFSPLUS_MAX_STRLEN, "a:b", 3,
779 				 HFS_REGULAR_NAME);
780 
781 	KUNIT_EXPECT_EQ(test, 0, result);
782 	KUNIT_EXPECT_EQ(test, 3, be16_to_cpu(mock_env->str1.length));
783 	KUNIT_EXPECT_EQ(test, 'a', be16_to_cpu(mock_env->str1.unicode[0]));
784 	KUNIT_EXPECT_EQ(test, '/', be16_to_cpu(mock_env->str1.unicode[1]));
785 	KUNIT_EXPECT_EQ(test, 'b', be16_to_cpu(mock_env->str1.unicode[2]));
786 
787 	/* Test multiple special characters */
788 	result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1,
789 				 HFSPLUS_MAX_STRLEN, ":::", 3,
790 				 HFS_REGULAR_NAME);
791 
792 	KUNIT_EXPECT_EQ(test, 0, result);
793 	KUNIT_EXPECT_EQ(test, 3, be16_to_cpu(mock_env->str1.length));
794 	KUNIT_EXPECT_EQ(test, '/', be16_to_cpu(mock_env->str1.unicode[0]));
795 	KUNIT_EXPECT_EQ(test, '/', be16_to_cpu(mock_env->str1.unicode[1]));
796 	KUNIT_EXPECT_EQ(test, '/', be16_to_cpu(mock_env->str1.unicode[2]));
797 
798 	free_mock_str_env(mock_env);
799 	free_mock_sb(mock_sb);
800 }
801 
802 /* Test buffer length limits */
803 static void hfsplus_asc2uni_buffer_limits_test(struct kunit *test)
804 {
805 	struct test_mock_sb *mock_sb;
806 	struct test_mock_string_env *mock_env;
807 	int result;
808 
809 	mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 10);
810 	KUNIT_ASSERT_NOT_NULL(test, mock_env);
811 
812 	mock_sb = setup_mock_sb();
813 	KUNIT_ASSERT_NOT_NULL(test, mock_sb);
814 
815 	mock_sb->nls.char2uni = test_char2uni;
816 
817 	/* Test exact maximum length */
818 	memset(mock_env->buf, 'a', HFSPLUS_MAX_STRLEN);
819 	result = hfsplus_asc2uni(&mock_sb->sb,
820 				 &mock_env->str1, HFSPLUS_MAX_STRLEN,
821 				 mock_env->buf, HFSPLUS_MAX_STRLEN,
822 				 HFS_REGULAR_NAME);
823 
824 	KUNIT_EXPECT_EQ(test, 0, result);
825 	KUNIT_EXPECT_EQ(test, HFSPLUS_MAX_STRLEN,
826 			be16_to_cpu(mock_env->str1.length));
827 
828 	/* Test exceeding maximum length */
829 	memset(mock_env->buf, 'a', HFSPLUS_MAX_STRLEN + 5);
830 	result = hfsplus_asc2uni(&mock_sb->sb,
831 				 &mock_env->str1, HFSPLUS_MAX_STRLEN,
832 				 mock_env->buf, HFSPLUS_MAX_STRLEN + 5,
833 				 HFS_REGULAR_NAME);
834 
835 	KUNIT_EXPECT_EQ(test, -ENAMETOOLONG, result);
836 	KUNIT_EXPECT_EQ(test, HFSPLUS_MAX_STRLEN,
837 			be16_to_cpu(mock_env->str1.length));
838 
839 	/* Test with smaller max_unistr_len */
840 	result = hfsplus_asc2uni(&mock_sb->sb,
841 				 &mock_env->str1, 5, "toolongstring", 13,
842 				 HFS_REGULAR_NAME);
843 
844 	KUNIT_EXPECT_EQ(test, -ENAMETOOLONG, result);
845 	KUNIT_EXPECT_EQ(test, 5, be16_to_cpu(mock_env->str1.length));
846 
847 	/* Test zero max length */
848 	result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1, 0, "test", 4,
849 				 HFS_REGULAR_NAME);
850 
851 	KUNIT_EXPECT_EQ(test, -ENAMETOOLONG, result);
852 	KUNIT_EXPECT_EQ(test, 0, be16_to_cpu(mock_env->str1.length));
853 
854 	free_mock_str_env(mock_env);
855 	free_mock_sb(mock_sb);
856 }
857 
858 /* Test error handling and edge cases */
859 static void hfsplus_asc2uni_edge_cases_test(struct kunit *test)
860 {
861 	struct test_mock_sb *mock_sb;
862 	struct hfsplus_unistr ustr;
863 	char test_str[] = {'a', '\0', 'b'};
864 	int result;
865 
866 	mock_sb = setup_mock_sb();
867 	KUNIT_ASSERT_NOT_NULL(test, mock_sb);
868 
869 	mock_sb->nls.char2uni = test_char2uni;
870 
871 	/* Test zero length input */
872 	result = hfsplus_asc2uni(&mock_sb->sb,
873 				 &ustr, HFSPLUS_MAX_STRLEN, "test", 0,
874 				 HFS_REGULAR_NAME);
875 
876 	KUNIT_EXPECT_EQ(test, 0, result);
877 	KUNIT_EXPECT_EQ(test, 0, be16_to_cpu(ustr.length));
878 
879 	/* Test input with length mismatch */
880 	result = hfsplus_asc2uni(&mock_sb->sb,
881 				 &ustr, HFSPLUS_MAX_STRLEN, "hello", 3,
882 				 HFS_REGULAR_NAME);
883 
884 	KUNIT_EXPECT_EQ(test, 0, result);
885 	check_unistr_content(test, &ustr, "hel");
886 
887 	/* Test with various printable ASCII characters */
888 	result = hfsplus_asc2uni(&mock_sb->sb,
889 				 &ustr, HFSPLUS_MAX_STRLEN, "ABC123!@#", 9,
890 				 HFS_REGULAR_NAME);
891 
892 	KUNIT_EXPECT_EQ(test, 0, result);
893 	check_unistr_content(test, &ustr, "ABC123!@#");
894 
895 	/* Test null character in the middle */
896 	result = hfsplus_asc2uni(&mock_sb->sb,
897 				 &ustr, HFSPLUS_MAX_STRLEN, test_str, 3,
898 				 HFS_REGULAR_NAME);
899 
900 	KUNIT_EXPECT_EQ(test, 0, result);
901 	KUNIT_EXPECT_EQ(test, 3, be16_to_cpu(ustr.length));
902 	KUNIT_EXPECT_EQ(test, 'a', be16_to_cpu(ustr.unicode[0]));
903 	KUNIT_EXPECT_EQ(test, 0, be16_to_cpu(ustr.unicode[1]));
904 	KUNIT_EXPECT_EQ(test, 'b', be16_to_cpu(ustr.unicode[2]));
905 
906 	free_mock_sb(mock_sb);
907 }
908 
909 /* Test decomposition flag behavior */
910 static void hfsplus_asc2uni_decompose_test(struct kunit *test)
911 {
912 	struct test_mock_sb *mock_sb;
913 	struct test_mock_string_env *mock_env;
914 	int result;
915 
916 	mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1);
917 	KUNIT_ASSERT_NOT_NULL(test, mock_env);
918 
919 	mock_sb = setup_mock_sb();
920 	KUNIT_ASSERT_NOT_NULL(test, mock_sb);
921 
922 	mock_sb->nls.char2uni = test_char2uni;
923 
924 	/* Test with decomposition disabled (default) */
925 	clear_bit(HFSPLUS_SB_NODECOMPOSE, &mock_sb->sb_info.flags);
926 	result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1,
927 				 HFSPLUS_MAX_STRLEN, "test", 4,
928 				 HFS_REGULAR_NAME);
929 
930 	KUNIT_EXPECT_EQ(test, 0, result);
931 	check_unistr_content(test, &mock_env->str1, "test");
932 
933 	/* Test with decomposition enabled */
934 	set_bit(HFSPLUS_SB_NODECOMPOSE, &mock_sb->sb_info.flags);
935 	result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str2,
936 				 HFSPLUS_MAX_STRLEN, "test", 4,
937 				 HFS_REGULAR_NAME);
938 
939 	KUNIT_EXPECT_EQ(test, 0, result);
940 	check_unistr_content(test, &mock_env->str2, "test");
941 
942 	/* For simple ASCII, both should produce the same result */
943 	KUNIT_EXPECT_EQ(test,
944 			be16_to_cpu(mock_env->str1.length),
945 			be16_to_cpu(mock_env->str2.length));
946 
947 	free_mock_str_env(mock_env);
948 	free_mock_sb(mock_sb);
949 }
950 
951 /*
952  * Real UTF-8 <-> wchar_t conversion, mirroring what the "utf8" NLS table
953  * actually does (see char2uni()/uni2char() in fs/nls/nls_utf8.c). The
954  * test_char2uni()/test_uni2char() stand-ins above only handle one raw
955  * byte per character, which cannot represent the combining marks (e.g.
956  * U+0301, U+0323) the canonical-reordering tests below need.
957  */
958 static int test_char2uni_utf8(const unsigned char *rawstring, int boundlen,
959 			      wchar_t *uni)
960 {
961 	unicode_t u;
962 	int n = utf8_to_utf32(rawstring, boundlen, &u);
963 
964 	if (n < 0 || u > MAX_WCHAR_T) {
965 		*uni = 0x3f; /* ? */
966 		return -EINVAL;
967 	}
968 	*uni = (wchar_t)u;
969 	return n;
970 }
971 
972 static int test_uni2char_utf8(wchar_t uni, unsigned char *out, int boundlen)
973 {
974 	int n = utf32_to_utf8(uni, out, boundlen);
975 
976 	if (n < 0) {
977 		*out = '?';
978 		return -EINVAL;
979 	}
980 	return n;
981 }
982 
983 /*
984  * Test that hfsplus_asc2uni() brings combining marks contributed by
985  * different source characters into Unicode canonical (combining-class)
986  * order, instead of just storing them in whatever order they were typed.
987  *
988  * U+0301 (COMBINING ACUTE ACCENT) has combining class 230; U+0323
989  * (COMBINING DOT BELOW) has combining class 220. Since 220 < 230,
990  * canonical order requires the dot-below before the acute. A name
991  * created with them typed in the "wrong" order must still end up stored
992  * in canonical order - this is exactly what macOS's own fsck_hfs
993  * (FixDecomps() in CatalogCheck.c) requires and flags as "Illegal name"
994  * when it isn't true.
995  */
996 static void hfsplus_asc2uni_combining_reorder_test(struct kunit *test)
997 {
998 	struct test_mock_sb *mock_sb;
999 	struct hfsplus_unistr ustr;
1000 	/* "e" + COMBINING ACUTE ACCENT (U+0301) + COMBINING DOT BELOW (U+0323) */
1001 	static const char wrong_order[] = "e\xcc\x81\xcc\xa3";
1002 	/* "e" + COMBINING DOT BELOW (U+0323) + COMBINING ACUTE ACCENT (U+0301) */
1003 	static const char canonical_order[] = "e\xcc\xa3\xcc\x81";
1004 	/* Two class-230 marks: GRAVE (U+0300) then ACUTE (U+0301) */
1005 	static const char same_class[] = "e\xcc\x80\xcc\x81";
1006 	int result;
1007 
1008 	mock_sb = setup_mock_sb();
1009 	KUNIT_ASSERT_NOT_NULL(test, mock_sb);
1010 
1011 	mock_sb->nls.char2uni = test_char2uni_utf8;
1012 
1013 	/* Typed out of canonical order: must be reordered on storage. */
1014 	result = hfsplus_asc2uni(&mock_sb->sb, &ustr, HFSPLUS_MAX_STRLEN,
1015 				 wrong_order, strlen(wrong_order),
1016 				 HFS_REGULAR_NAME);
1017 
1018 	KUNIT_EXPECT_EQ(test, 0, result);
1019 	KUNIT_EXPECT_EQ(test, 3, be16_to_cpu(ustr.length));
1020 	KUNIT_EXPECT_EQ(test, 'e', be16_to_cpu(ustr.unicode[0]));
1021 	KUNIT_EXPECT_EQ(test, 0x0323, be16_to_cpu(ustr.unicode[1]));
1022 	KUNIT_EXPECT_EQ(test, 0x0301, be16_to_cpu(ustr.unicode[2]));
1023 
1024 	/* Already in canonical order: must come out unchanged. */
1025 	result = hfsplus_asc2uni(&mock_sb->sb, &ustr, HFSPLUS_MAX_STRLEN,
1026 				 canonical_order, strlen(canonical_order),
1027 				 HFS_REGULAR_NAME);
1028 
1029 	KUNIT_EXPECT_EQ(test, 0, result);
1030 	KUNIT_EXPECT_EQ(test, 3, be16_to_cpu(ustr.length));
1031 	KUNIT_EXPECT_EQ(test, 'e', be16_to_cpu(ustr.unicode[0]));
1032 	KUNIT_EXPECT_EQ(test, 0x0323, be16_to_cpu(ustr.unicode[1]));
1033 	KUNIT_EXPECT_EQ(test, 0x0301, be16_to_cpu(ustr.unicode[2]));
1034 
1035 	/* Two marks of equal combining class must keep their relative
1036 	 * (input) order - the sort must be stable, not just "sorted".
1037 	 */
1038 	result = hfsplus_asc2uni(&mock_sb->sb, &ustr, HFSPLUS_MAX_STRLEN,
1039 				 same_class, strlen(same_class),
1040 				 HFS_REGULAR_NAME);
1041 
1042 	KUNIT_EXPECT_EQ(test, 0, result);
1043 	KUNIT_EXPECT_EQ(test, 3, be16_to_cpu(ustr.length));
1044 	KUNIT_EXPECT_EQ(test, 'e', be16_to_cpu(ustr.unicode[0]));
1045 	KUNIT_EXPECT_EQ(test, 0x0300, be16_to_cpu(ustr.unicode[1]));
1046 	KUNIT_EXPECT_EQ(test, 0x0301, be16_to_cpu(ustr.unicode[2]));
1047 
1048 	free_mock_sb(mock_sb);
1049 }
1050 
1051 /*
1052  * Test that a name written with combining marks in non-canonical order
1053  * still reads back as valid, correct UTF-8 once reordered - i.e. that
1054  * hfsplus_asc2uni() and hfsplus_uni2asc_str() stay consistent with each
1055  * other across the reordering.
1056  *
1057  * NODECOMPOSE is set so hfsplus_uni2asc_str() doesn't also recompose
1058  * "e" + COMBINING DOT BELOW back into the precomposed U+1EB9 ("e"): that
1059  * composition behavior is real (and already covered elsewhere), but it
1060  * would obscure what this test is actually checking.
1061  */
1062 static void hfsplus_unicode_combining_reorder_roundtrip_test(struct kunit *test)
1063 {
1064 	struct test_mock_sb *mock_sb;
1065 	struct hfsplus_unistr ustr;
1066 	static const char wrong_order[] = "e\xcc\x81\xcc\xa3";
1067 	static const char expected[] = "e\xcc\xa3\xcc\x81"; /* canonical order */
1068 	char buf[32];
1069 	int len = sizeof(buf);
1070 	int result;
1071 
1072 	mock_sb = setup_mock_sb();
1073 	KUNIT_ASSERT_NOT_NULL(test, mock_sb);
1074 
1075 	set_bit(HFSPLUS_SB_NODECOMPOSE, &mock_sb->sb_info.flags);
1076 	mock_sb->nls.char2uni = test_char2uni_utf8;
1077 	mock_sb->nls.uni2char = test_uni2char_utf8;
1078 
1079 	result = hfsplus_asc2uni(&mock_sb->sb, &ustr, HFSPLUS_MAX_STRLEN,
1080 				 wrong_order, strlen(wrong_order),
1081 				 HFS_REGULAR_NAME);
1082 	KUNIT_EXPECT_EQ(test, 0, result);
1083 
1084 	result = hfsplus_uni2asc_str(&mock_sb->sb, &ustr, buf, &len);
1085 
1086 	KUNIT_EXPECT_EQ(test, 0, result);
1087 	KUNIT_EXPECT_EQ(test, (int)strlen(expected), len);
1088 	KUNIT_EXPECT_MEMEQ(test, expected, buf, len);
1089 
1090 	free_mock_sb(mock_sb);
1091 }
1092 
1093 /*
1094  * Test that hfsplus_asc2uni() decomposes a character using the corrected
1095  * (post-2002/"Jaguar") canonical decomposition even when Apple Technote
1096  * #1150's own decomposition table doesn't have an entry for it.
1097  *
1098  * U+01F8 (LATIN CAPITAL LETTER N WITH GRAVE) is exactly one of the
1099  * characters macOS's own fsck_hfs (FixDecomps() in CatalogCheck.c) has
1100  * flagged "Illegal name" for since 2002 when found stored undecomposed.
1101  */
1102 static void hfsplus_asc2uni_legacy_decomp_test(struct kunit *test)
1103 {
1104 	struct test_mock_sb *mock_sb;
1105 	struct hfsplus_unistr ustr;
1106 	static const char input[] = "\xc7\xb8"; /* U+01F8 */
1107 	int result;
1108 
1109 	mock_sb = setup_mock_sb();
1110 	KUNIT_ASSERT_NOT_NULL(test, mock_sb);
1111 
1112 	mock_sb->nls.char2uni = test_char2uni_utf8;
1113 
1114 	result = hfsplus_asc2uni(&mock_sb->sb, &ustr, HFSPLUS_MAX_STRLEN,
1115 				 input, strlen(input), HFS_REGULAR_NAME);
1116 
1117 	KUNIT_EXPECT_EQ(test, 0, result);
1118 	KUNIT_EXPECT_EQ(test, 2, be16_to_cpu(ustr.length));
1119 	KUNIT_EXPECT_EQ(test, 'N', be16_to_cpu(ustr.unicode[0]));
1120 	KUNIT_EXPECT_EQ(test, 0x0300, be16_to_cpu(ustr.unicode[1]));
1121 
1122 	free_mock_sb(mock_sb);
1123 }
1124 
1125 /*
1126  * Test that hfsplus_asc2uni() corrects two more of fsck_hfs's known
1127  * legacy decomposition sequences once combining marks from independently
1128  * typed characters end up adjacent:
1129  *
1130  *  - GREEK SMALL LETTER ALPHA (U+03B1) + COMBINING VERTICAL LINE ABOVE
1131  *    (U+030D) must become U+03B1 + COMBINING ACUTE ACCENT (U+0301).
1132  *  - BENGALI LETTER BA (U+09AC) + BENGALI SIGN NUKTA (U+09BC) must become
1133  *    the single character BENGALI LETTER RA WITH MIDDLE DIAGONAL (U+09B0).
1134  */
1135 static void hfsplus_asc2uni_legacy_seq_fixup_test(struct kunit *test)
1136 {
1137 	struct test_mock_sb *mock_sb;
1138 	struct hfsplus_unistr ustr;
1139 	static const char greek_input[] = "\xce\xb1\xcc\x8d"; /* U+03B1 U+030D */
1140 	static const char bengali_input[] = "\xe0\xa6\xac\xe0\xa6\xbc"; /* U+09AC U+09BC */
1141 	int result;
1142 
1143 	mock_sb = setup_mock_sb();
1144 	KUNIT_ASSERT_NOT_NULL(test, mock_sb);
1145 
1146 	mock_sb->nls.char2uni = test_char2uni_utf8;
1147 
1148 	result = hfsplus_asc2uni(&mock_sb->sb, &ustr, HFSPLUS_MAX_STRLEN,
1149 				 greek_input, strlen(greek_input),
1150 				 HFS_REGULAR_NAME);
1151 
1152 	KUNIT_EXPECT_EQ(test, 0, result);
1153 	KUNIT_EXPECT_EQ(test, 2, be16_to_cpu(ustr.length));
1154 	KUNIT_EXPECT_EQ(test, 0x03b1, be16_to_cpu(ustr.unicode[0]));
1155 	KUNIT_EXPECT_EQ(test, 0x0301, be16_to_cpu(ustr.unicode[1]));
1156 
1157 	result = hfsplus_asc2uni(&mock_sb->sb, &ustr, HFSPLUS_MAX_STRLEN,
1158 				 bengali_input, strlen(bengali_input),
1159 				 HFS_REGULAR_NAME);
1160 
1161 	KUNIT_EXPECT_EQ(test, 0, result);
1162 	KUNIT_EXPECT_EQ(test, 1, be16_to_cpu(ustr.length));
1163 	KUNIT_EXPECT_EQ(test, 0x09b0, be16_to_cpu(ustr.unicode[0]));
1164 
1165 	free_mock_sb(mock_sb);
1166 }
1167 
1168 /* Mock dentry for testing hfsplus_hash_dentry */
1169 static struct dentry test_dentry;
1170 
1171 static void setup_mock_dentry(struct super_block *sb)
1172 {
1173 	memset(&test_dentry, 0, sizeof(test_dentry));
1174 	test_dentry.d_sb = sb;
1175 }
1176 
1177 /* Helper function to create qstr */
1178 static void create_qstr(struct qstr *str, const char *name)
1179 {
1180 	str->name = name;
1181 	str->len = strlen(name);
1182 	str->hash = 0; /* Will be set by hash function */
1183 }
1184 
1185 /* Test hfsplus_hash_dentry basic functionality */
1186 static void hfsplus_hash_dentry_basic_test(struct kunit *test)
1187 {
1188 	struct test_mock_sb *mock_sb;
1189 	struct qstr str1, str2;
1190 	int result;
1191 
1192 	mock_sb = setup_mock_sb();
1193 	KUNIT_ASSERT_NOT_NULL(test, mock_sb);
1194 
1195 	setup_mock_dentry(&mock_sb->sb);
1196 	mock_sb->nls.char2uni = test_char2uni;
1197 
1198 	/* Test basic string hashing */
1199 	create_qstr(&str1, "hello");
1200 	result = hfsplus_hash_dentry(&test_dentry, &str1);
1201 
1202 	KUNIT_EXPECT_EQ(test, 0, result);
1203 	KUNIT_EXPECT_NE(test, 0, str1.hash);
1204 
1205 	/* Test that identical strings produce identical hashes */
1206 	create_qstr(&str2, "hello");
1207 	result = hfsplus_hash_dentry(&test_dentry, &str2);
1208 
1209 	KUNIT_EXPECT_EQ(test, 0, result);
1210 	KUNIT_EXPECT_EQ(test, str1.hash, str2.hash);
1211 
1212 	/* Test empty string */
1213 	create_qstr(&str1, "");
1214 	result = hfsplus_hash_dentry(&test_dentry, &str1);
1215 
1216 	/* Empty string should still produce a hash */
1217 	KUNIT_EXPECT_EQ(test, 0, result);
1218 
1219 	/* Test single character */
1220 	create_qstr(&str1, "A");
1221 	result = hfsplus_hash_dentry(&test_dentry, &str1);
1222 
1223 	KUNIT_EXPECT_EQ(test, 0, result);
1224 	KUNIT_EXPECT_NE(test, 0, str1.hash);
1225 
1226 	free_mock_sb(mock_sb);
1227 }
1228 
1229 /* Test case folding behavior in hash */
1230 static void hfsplus_hash_dentry_casefold_test(struct kunit *test)
1231 {
1232 	struct test_mock_sb *mock_sb;
1233 	struct qstr str1, str2;
1234 	int result;
1235 
1236 	mock_sb = setup_mock_sb();
1237 	KUNIT_ASSERT_NOT_NULL(test, mock_sb);
1238 
1239 	setup_mock_dentry(&mock_sb->sb);
1240 	mock_sb->nls.char2uni = test_char2uni;
1241 
1242 	/* Test with case folding disabled (default) */
1243 	clear_bit(HFSPLUS_SB_CASEFOLD, &mock_sb->sb_info.flags);
1244 
1245 	create_qstr(&str1, "Hello");
1246 	result = hfsplus_hash_dentry(&test_dentry, &str1);
1247 	KUNIT_EXPECT_EQ(test, 0, result);
1248 
1249 	create_qstr(&str2, "hello");
1250 	result = hfsplus_hash_dentry(&test_dentry, &str2);
1251 	KUNIT_EXPECT_EQ(test, 0, result);
1252 
1253 	/*
1254 	 * Without case folding, different cases
1255 	 * should produce different hashes
1256 	 */
1257 	KUNIT_EXPECT_NE(test, str1.hash, str2.hash);
1258 
1259 	/* Test with case folding enabled */
1260 	set_bit(HFSPLUS_SB_CASEFOLD, &mock_sb->sb_info.flags);
1261 
1262 	create_qstr(&str1, "Hello");
1263 	result = hfsplus_hash_dentry(&test_dentry, &str1);
1264 	KUNIT_EXPECT_EQ(test, 0, result);
1265 
1266 	create_qstr(&str2, "hello");
1267 	result = hfsplus_hash_dentry(&test_dentry, &str2);
1268 	KUNIT_EXPECT_EQ(test, 0, result);
1269 
1270 	/* With case folding, different cases should produce same hash */
1271 	KUNIT_EXPECT_EQ(test, str1.hash, str2.hash);
1272 
1273 	/* Test mixed case */
1274 	create_qstr(&str1, "HeLLo");
1275 	result = hfsplus_hash_dentry(&test_dentry, &str1);
1276 	KUNIT_EXPECT_EQ(test, 0, result);
1277 	KUNIT_EXPECT_EQ(test, str1.hash, str2.hash);
1278 
1279 	free_mock_sb(mock_sb);
1280 }
1281 
1282 /* Test special character handling in hash */
1283 static void hfsplus_hash_dentry_special_chars_test(struct kunit *test)
1284 {
1285 	struct test_mock_sb *mock_sb;
1286 	struct qstr str1, str2;
1287 	int result;
1288 
1289 	mock_sb = setup_mock_sb();
1290 	KUNIT_ASSERT_NOT_NULL(test, mock_sb);
1291 
1292 	setup_mock_dentry(&mock_sb->sb);
1293 	mock_sb->nls.char2uni = test_char2uni;
1294 
1295 	/* Test colon conversion (: becomes /) */
1296 	create_qstr(&str1, "file:name");
1297 	result = hfsplus_hash_dentry(&test_dentry, &str1);
1298 	KUNIT_EXPECT_EQ(test, 0, result);
1299 
1300 	create_qstr(&str2, "file/name");
1301 	result = hfsplus_hash_dentry(&test_dentry, &str2);
1302 	KUNIT_EXPECT_EQ(test, 0, result);
1303 
1304 	/* After conversion, these should produce the same hash */
1305 	KUNIT_EXPECT_EQ(test, str1.hash, str2.hash);
1306 
1307 	/* Test multiple special characters */
1308 	create_qstr(&str1, ":::");
1309 	result = hfsplus_hash_dentry(&test_dentry, &str1);
1310 	KUNIT_EXPECT_EQ(test, 0, result);
1311 
1312 	create_qstr(&str2, "///");
1313 	result = hfsplus_hash_dentry(&test_dentry, &str2);
1314 	KUNIT_EXPECT_EQ(test, 0, result);
1315 
1316 	KUNIT_EXPECT_EQ(test, str1.hash, str2.hash);
1317 
1318 	free_mock_sb(mock_sb);
1319 }
1320 
1321 /* Test decomposition flag behavior in hash */
1322 static void hfsplus_hash_dentry_decompose_test(struct kunit *test)
1323 {
1324 	struct test_mock_sb *mock_sb;
1325 	struct qstr str1, str2;
1326 	int result;
1327 
1328 	mock_sb = setup_mock_sb();
1329 	KUNIT_ASSERT_NOT_NULL(test, mock_sb);
1330 
1331 	setup_mock_dentry(&mock_sb->sb);
1332 	mock_sb->nls.char2uni = test_char2uni;
1333 
1334 	/* Test with decomposition disabled (default) */
1335 	clear_bit(HFSPLUS_SB_NODECOMPOSE, &mock_sb->sb_info.flags);
1336 
1337 	create_qstr(&str1, "test");
1338 	result = hfsplus_hash_dentry(&test_dentry, &str1);
1339 	KUNIT_EXPECT_EQ(test, 0, result);
1340 
1341 	/* Test with decomposition enabled */
1342 	set_bit(HFSPLUS_SB_NODECOMPOSE, &mock_sb->sb_info.flags);
1343 
1344 	create_qstr(&str2, "test");
1345 	result = hfsplus_hash_dentry(&test_dentry, &str2);
1346 	KUNIT_EXPECT_EQ(test, 0, result);
1347 
1348 	/*
1349 	 * For simple ASCII, decomposition shouldn't change
1350 	 * the hash much but the function should still work correctly
1351 	 */
1352 	KUNIT_EXPECT_NE(test, 0, str2.hash);
1353 
1354 	free_mock_sb(mock_sb);
1355 }
1356 
1357 /* Test hash consistency and distribution */
1358 static void hfsplus_hash_dentry_consistency_test(struct kunit *test)
1359 {
1360 	struct test_mock_sb *mock_sb;
1361 	struct qstr str1, str2, str3;
1362 	unsigned long hash1;
1363 	int result;
1364 
1365 	mock_sb = setup_mock_sb();
1366 	KUNIT_ASSERT_NOT_NULL(test, mock_sb);
1367 
1368 	setup_mock_dentry(&mock_sb->sb);
1369 	mock_sb->nls.char2uni = test_char2uni;
1370 
1371 	/* Test that same string always produces same hash */
1372 	create_qstr(&str1, "consistent");
1373 	result = hfsplus_hash_dentry(&test_dentry, &str1);
1374 	KUNIT_EXPECT_EQ(test, 0, result);
1375 	hash1 = str1.hash;
1376 
1377 	create_qstr(&str2, "consistent");
1378 	result = hfsplus_hash_dentry(&test_dentry, &str2);
1379 	KUNIT_EXPECT_EQ(test, 0, result);
1380 
1381 	KUNIT_EXPECT_EQ(test, hash1, str2.hash);
1382 
1383 	/* Test that different strings produce different hashes */
1384 	create_qstr(&str3, "different");
1385 	result = hfsplus_hash_dentry(&test_dentry, &str3);
1386 	KUNIT_EXPECT_EQ(test, 0, result);
1387 
1388 	KUNIT_EXPECT_NE(test, str1.hash, str3.hash);
1389 
1390 	/* Test similar strings should have different hashes */
1391 	create_qstr(&str1, "file1");
1392 	result = hfsplus_hash_dentry(&test_dentry, &str1);
1393 	KUNIT_EXPECT_EQ(test, 0, result);
1394 
1395 	create_qstr(&str2, "file2");
1396 	result = hfsplus_hash_dentry(&test_dentry, &str2);
1397 	KUNIT_EXPECT_EQ(test, 0, result);
1398 
1399 	KUNIT_EXPECT_NE(test, str1.hash, str2.hash);
1400 
1401 	free_mock_sb(mock_sb);
1402 }
1403 
1404 /* Test edge cases and boundary conditions */
1405 static void hfsplus_hash_dentry_edge_cases_test(struct kunit *test)
1406 {
1407 	struct test_mock_sb *mock_sb;
1408 	struct test_mock_string_env *mock_env;
1409 	struct qstr str;
1410 	int result;
1411 
1412 	mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1);
1413 	KUNIT_ASSERT_NOT_NULL(test, mock_env);
1414 
1415 	mock_sb = setup_mock_sb();
1416 	KUNIT_ASSERT_NOT_NULL(test, mock_sb);
1417 
1418 	setup_mock_dentry(&mock_sb->sb);
1419 	mock_sb->nls.char2uni = test_char2uni;
1420 
1421 	/* Test very long filename */
1422 	memset(mock_env->buf, 'a', mock_env->buf_size - 1);
1423 	mock_env->buf[mock_env->buf_size - 1] = '\0';
1424 
1425 	create_qstr(&str, mock_env->buf);
1426 	result = hfsplus_hash_dentry(&test_dentry, &str);
1427 
1428 	KUNIT_EXPECT_EQ(test, 0, result);
1429 	KUNIT_EXPECT_NE(test, 0, str.hash);
1430 
1431 	/* Test filename with all printable ASCII characters */
1432 	create_qstr(&str, "!@#$%^&*()_+-=[]{}|;':\",./<>?");
1433 	result = hfsplus_hash_dentry(&test_dentry, &str);
1434 
1435 	KUNIT_EXPECT_EQ(test, 0, result);
1436 	KUNIT_EXPECT_NE(test, 0, str.hash);
1437 
1438 	/* Test with embedded null (though not typical for filenames) */
1439 	str.name = "file\0hidden";
1440 	str.len = 11; /* Include the null and text after it */
1441 	str.hash = 0;
1442 	result = hfsplus_hash_dentry(&test_dentry, &str);
1443 
1444 	KUNIT_EXPECT_EQ(test, 0, result);
1445 	KUNIT_EXPECT_NE(test, 0, str.hash);
1446 
1447 	free_mock_str_env(mock_env);
1448 	free_mock_sb(mock_sb);
1449 }
1450 
1451 /*
1452  * Test that hfsplus_hash_dentry() hashes two names identically when they
1453  * differ only in the (non-canonical) typed order of the same combining
1454  * marks - both must canonicalize to the same stored form, so they must
1455  * hash the same or dcache lookups would spuriously miss.
1456  */
1457 static void hfsplus_hash_dentry_combining_reorder_test(struct kunit *test)
1458 {
1459 	struct test_mock_sb *mock_sb;
1460 	struct qstr str1, str2;
1461 	int result;
1462 
1463 	mock_sb = setup_mock_sb();
1464 	KUNIT_ASSERT_NOT_NULL(test, mock_sb);
1465 
1466 	setup_mock_dentry(&mock_sb->sb);
1467 	mock_sb->nls.char2uni = test_char2uni_utf8;
1468 
1469 	create_qstr(&str1, "e\xcc\x81\xcc\xa3"); /* acute, then dot-below */
1470 	result = hfsplus_hash_dentry(&test_dentry, &str1);
1471 	KUNIT_EXPECT_EQ(test, 0, result);
1472 
1473 	create_qstr(&str2, "e\xcc\xa3\xcc\x81"); /* dot-below, then acute */
1474 	result = hfsplus_hash_dentry(&test_dentry, &str2);
1475 	KUNIT_EXPECT_EQ(test, 0, result);
1476 
1477 	KUNIT_EXPECT_EQ(test, str1.hash, str2.hash);
1478 
1479 	free_mock_sb(mock_sb);
1480 }
1481 
1482 /* Test hfsplus_compare_dentry basic functionality */
1483 static void hfsplus_compare_dentry_basic_test(struct kunit *test)
1484 {
1485 	struct test_mock_sb *mock_sb;
1486 	struct qstr name;
1487 	int result;
1488 
1489 	mock_sb = setup_mock_sb();
1490 	KUNIT_ASSERT_NOT_NULL(test, mock_sb);
1491 
1492 	setup_mock_dentry(&mock_sb->sb);
1493 	mock_sb->nls.char2uni = test_char2uni;
1494 
1495 	/* Test identical strings */
1496 	create_qstr(&name, "hello");
1497 	result = hfsplus_compare_dentry(&test_dentry, 5, "hello", &name);
1498 	KUNIT_EXPECT_EQ(test, 0, result);
1499 
1500 	/* Test different strings - lexicographic order */
1501 	create_qstr(&name, "world");
1502 	result = hfsplus_compare_dentry(&test_dentry, 5, "hello", &name);
1503 	KUNIT_EXPECT_LT(test, result, 0); /* "hello" < "world" */
1504 
1505 	result = hfsplus_compare_dentry(&test_dentry, 5, "world", &name);
1506 	KUNIT_EXPECT_EQ(test, 0, result);
1507 
1508 	create_qstr(&name, "hello");
1509 	result = hfsplus_compare_dentry(&test_dentry, 5, "world", &name);
1510 	KUNIT_EXPECT_GT(test, result, 0); /* "world" > "hello" */
1511 
1512 	/* Test empty strings */
1513 	create_qstr(&name, "");
1514 	result = hfsplus_compare_dentry(&test_dentry, 0, "", &name);
1515 	KUNIT_EXPECT_EQ(test, 0, result);
1516 
1517 	/* Test one empty, one non-empty */
1518 	create_qstr(&name, "test");
1519 	result = hfsplus_compare_dentry(&test_dentry, 0, "", &name);
1520 	KUNIT_EXPECT_LT(test, result, 0); /* "" < "test" */
1521 
1522 	create_qstr(&name, "");
1523 	result = hfsplus_compare_dentry(&test_dentry, 4, "test", &name);
1524 	KUNIT_EXPECT_GT(test, result, 0); /* "test" > "" */
1525 
1526 	free_mock_sb(mock_sb);
1527 }
1528 
1529 /* Test case folding behavior in comparison */
1530 static void hfsplus_compare_dentry_casefold_test(struct kunit *test)
1531 {
1532 	struct test_mock_sb *mock_sb;
1533 	struct qstr name;
1534 	int result;
1535 
1536 	mock_sb = setup_mock_sb();
1537 	KUNIT_ASSERT_NOT_NULL(test, mock_sb);
1538 
1539 	setup_mock_dentry(&mock_sb->sb);
1540 	mock_sb->nls.char2uni = test_char2uni;
1541 
1542 	/* Test with case folding disabled (default) */
1543 	clear_bit(HFSPLUS_SB_CASEFOLD, &mock_sb->sb_info.flags);
1544 
1545 	create_qstr(&name, "hello");
1546 	result = hfsplus_compare_dentry(&test_dentry, 5, "Hello", &name);
1547 	/* Case sensitive: "Hello" != "hello" */
1548 	KUNIT_EXPECT_NE(test, 0, result);
1549 
1550 	create_qstr(&name, "Hello");
1551 	result = hfsplus_compare_dentry(&test_dentry, 5, "hello", &name);
1552 	/* Case sensitive: "hello" != "Hello" */
1553 	KUNIT_EXPECT_NE(test, 0, result);
1554 
1555 	/* Test with case folding enabled */
1556 	set_bit(HFSPLUS_SB_CASEFOLD, &mock_sb->sb_info.flags);
1557 
1558 	create_qstr(&name, "hello");
1559 	result = hfsplus_compare_dentry(&test_dentry, 5, "Hello", &name);
1560 	/* Case insensitive: "Hello" == "hello" */
1561 	KUNIT_EXPECT_EQ(test, 0, result);
1562 
1563 	create_qstr(&name, "Hello");
1564 	result = hfsplus_compare_dentry(&test_dentry, 5, "hello", &name);
1565 	/* Case insensitive: "hello" == "Hello" */
1566 	KUNIT_EXPECT_EQ(test, 0, result);
1567 
1568 	/* Test mixed case */
1569 	create_qstr(&name, "TeSt");
1570 	result = hfsplus_compare_dentry(&test_dentry, 4, "test", &name);
1571 	KUNIT_EXPECT_EQ(test, 0, result);
1572 
1573 	create_qstr(&name, "test");
1574 	result = hfsplus_compare_dentry(&test_dentry, 4, "TEST", &name);
1575 	KUNIT_EXPECT_EQ(test, 0, result);
1576 
1577 	free_mock_sb(mock_sb);
1578 }
1579 
1580 /* Test special character handling in comparison */
1581 static void hfsplus_compare_dentry_special_chars_test(struct kunit *test)
1582 {
1583 	struct test_mock_sb *mock_sb;
1584 	struct qstr name;
1585 	int result;
1586 
1587 	mock_sb = setup_mock_sb();
1588 	KUNIT_ASSERT_NOT_NULL(test, mock_sb);
1589 
1590 	setup_mock_dentry(&mock_sb->sb);
1591 	mock_sb->nls.char2uni = test_char2uni;
1592 
1593 	/* Test colon conversion (: becomes /) */
1594 	create_qstr(&name, "file/name");
1595 	result = hfsplus_compare_dentry(&test_dentry, 9, "file:name", &name);
1596 	/* "file:name" == "file/name" after conversion */
1597 	KUNIT_EXPECT_EQ(test, 0, result);
1598 
1599 	create_qstr(&name, "file:name");
1600 	result = hfsplus_compare_dentry(&test_dentry, 9, "file/name", &name);
1601 	/* "file/name" == "file:name" after conversion */
1602 	KUNIT_EXPECT_EQ(test, 0, result);
1603 
1604 	/* Test multiple special characters */
1605 	create_qstr(&name, "///");
1606 	result = hfsplus_compare_dentry(&test_dentry, 3, ":::", &name);
1607 	KUNIT_EXPECT_EQ(test, 0, result);
1608 
1609 	/* Test mixed special and regular characters */
1610 	create_qstr(&name, "a/b:c");
1611 	result = hfsplus_compare_dentry(&test_dentry, 5, "a:b/c", &name);
1612 	/* Both become "a/b/c" after conversion */
1613 	KUNIT_EXPECT_EQ(test, 0, result);
1614 
1615 	free_mock_sb(mock_sb);
1616 }
1617 
1618 /* Test length differences */
1619 static void hfsplus_compare_dentry_length_test(struct kunit *test)
1620 {
1621 	struct test_mock_sb *mock_sb;
1622 	struct qstr name;
1623 	int result;
1624 
1625 	mock_sb = setup_mock_sb();
1626 	KUNIT_ASSERT_NOT_NULL(test, mock_sb);
1627 
1628 	setup_mock_dentry(&mock_sb->sb);
1629 	mock_sb->nls.char2uni = test_char2uni;
1630 
1631 	/* Test different lengths with common prefix */
1632 	create_qstr(&name, "testing");
1633 	result = hfsplus_compare_dentry(&test_dentry, 4, "test", &name);
1634 	KUNIT_EXPECT_LT(test, result, 0); /* "test" < "testing" */
1635 
1636 	create_qstr(&name, "test");
1637 	result = hfsplus_compare_dentry(&test_dentry, 7, "testing", &name);
1638 	KUNIT_EXPECT_GT(test, result, 0); /* "testing" > "test" */
1639 
1640 	/* Test exact length match */
1641 	create_qstr(&name, "exact");
1642 	result = hfsplus_compare_dentry(&test_dentry, 5, "exact", &name);
1643 	KUNIT_EXPECT_EQ(test, 0, result);
1644 
1645 	/* Test length parameter vs actual string content */
1646 	create_qstr(&name, "hello");
1647 	result = hfsplus_compare_dentry(&test_dentry, 3, "hel", &name);
1648 	KUNIT_EXPECT_LT(test, result, 0); /* "hel" < "hello" */
1649 
1650 	/* Test longer first string but shorter length parameter */
1651 	create_qstr(&name, "hi");
1652 	result = hfsplus_compare_dentry(&test_dentry, 2, "hello", &name);
1653 	/* "he" < "hi" (only first 2 chars compared) */
1654 	KUNIT_EXPECT_LT(test, result, 0);
1655 
1656 	free_mock_sb(mock_sb);
1657 }
1658 
1659 /* Test decomposition flag behavior */
1660 static void hfsplus_compare_dentry_decompose_test(struct kunit *test)
1661 {
1662 	struct test_mock_sb *mock_sb;
1663 	struct qstr name;
1664 	int result;
1665 
1666 	mock_sb = setup_mock_sb();
1667 	KUNIT_ASSERT_NOT_NULL(test, mock_sb);
1668 
1669 	setup_mock_dentry(&mock_sb->sb);
1670 	mock_sb->nls.char2uni = test_char2uni;
1671 
1672 	/* Test with decomposition disabled (default) */
1673 	clear_bit(HFSPLUS_SB_NODECOMPOSE, &mock_sb->sb_info.flags);
1674 
1675 	create_qstr(&name, "test");
1676 	result = hfsplus_compare_dentry(&test_dentry, 4, "test", &name);
1677 	KUNIT_EXPECT_EQ(test, 0, result);
1678 
1679 	/* Test with decomposition enabled */
1680 	set_bit(HFSPLUS_SB_NODECOMPOSE, &mock_sb->sb_info.flags);
1681 
1682 	create_qstr(&name, "test");
1683 	result = hfsplus_compare_dentry(&test_dentry, 4, "test", &name);
1684 	KUNIT_EXPECT_EQ(test, 0, result);
1685 
1686 	/* For simple ASCII, decomposition shouldn't affect the result */
1687 	create_qstr(&name, "different");
1688 	result = hfsplus_compare_dentry(&test_dentry, 4, "test", &name);
1689 	KUNIT_EXPECT_NE(test, 0, result);
1690 
1691 	free_mock_sb(mock_sb);
1692 }
1693 
1694 /* Test edge cases and boundary conditions */
1695 static void hfsplus_compare_dentry_edge_cases_test(struct kunit *test)
1696 {
1697 	struct test_mock_sb *mock_sb;
1698 	struct qstr name;
1699 	char *long_str;
1700 	char *long_str2;
1701 	u32 str_size = HFSPLUS_MAX_STRLEN + 1;
1702 	struct qstr null_name = {
1703 		.name = "a\0b",
1704 		.len = 3,
1705 		.hash = 0
1706 	};
1707 	int result;
1708 
1709 	mock_sb = setup_mock_sb();
1710 	KUNIT_ASSERT_NOT_NULL(test, mock_sb);
1711 
1712 	setup_mock_dentry(&mock_sb->sb);
1713 	mock_sb->nls.char2uni = test_char2uni;
1714 
1715 	long_str = kzalloc(str_size, GFP_KERNEL);
1716 	KUNIT_ASSERT_NOT_NULL(test, long_str);
1717 
1718 	long_str2 = kzalloc(str_size, GFP_KERNEL);
1719 	KUNIT_ASSERT_NOT_NULL(test, long_str2);
1720 
1721 	/* Test very long strings */
1722 	memset(long_str, 'a', str_size - 1);
1723 	long_str[str_size - 1] = '\0';
1724 
1725 	create_qstr(&name, long_str);
1726 	result = hfsplus_compare_dentry(&test_dentry, str_size - 1,
1727 					long_str, &name);
1728 	KUNIT_EXPECT_EQ(test, 0, result);
1729 
1730 	/* Test with difference at the end of long strings */
1731 	memset(long_str2, 'a', str_size - 1);
1732 	long_str2[str_size - 1] = '\0';
1733 	long_str2[str_size - 2] = 'b';
1734 	create_qstr(&name, long_str2);
1735 	result = hfsplus_compare_dentry(&test_dentry, str_size - 1,
1736 					long_str, &name);
1737 	KUNIT_EXPECT_LT(test, result, 0); /* 'a' < 'b' */
1738 
1739 	/* Test single character differences */
1740 	create_qstr(&name, "b");
1741 	result = hfsplus_compare_dentry(&test_dentry, 1, "a", &name);
1742 	KUNIT_EXPECT_LT(test, result, 0); /* 'a' < 'b' */
1743 
1744 	create_qstr(&name, "a");
1745 	result = hfsplus_compare_dentry(&test_dentry, 1, "b", &name);
1746 	KUNIT_EXPECT_GT(test, result, 0); /* 'b' > 'a' */
1747 
1748 	/* Test with null characters in the middle */
1749 	result = hfsplus_compare_dentry(&test_dentry, 3, "a\0b", &null_name);
1750 	KUNIT_EXPECT_EQ(test, 0, result);
1751 
1752 	/* Test all printable ASCII characters */
1753 	create_qstr(&name, "!@#$%^&*()");
1754 	result = hfsplus_compare_dentry(&test_dentry, 10, "!@#$%^&*()", &name);
1755 	KUNIT_EXPECT_EQ(test, 0, result);
1756 
1757 	kfree(long_str);
1758 	kfree(long_str2);
1759 	free_mock_sb(mock_sb);
1760 }
1761 
1762 /* Test combined flag behaviors */
1763 static void hfsplus_compare_dentry_combined_flags_test(struct kunit *test)
1764 {
1765 	struct test_mock_sb *mock_sb;
1766 	struct qstr name;
1767 	int result;
1768 
1769 	mock_sb = setup_mock_sb();
1770 	KUNIT_ASSERT_NOT_NULL(test, mock_sb);
1771 
1772 	setup_mock_dentry(&mock_sb->sb);
1773 	mock_sb->nls.char2uni = test_char2uni;
1774 
1775 	/* Test with both casefold and decompose enabled */
1776 	set_bit(HFSPLUS_SB_CASEFOLD, &mock_sb->sb_info.flags);
1777 	set_bit(HFSPLUS_SB_NODECOMPOSE, &mock_sb->sb_info.flags);
1778 
1779 	create_qstr(&name, "hello");
1780 	result = hfsplus_compare_dentry(&test_dentry, 5, "HELLO", &name);
1781 	KUNIT_EXPECT_EQ(test, 0, result);
1782 
1783 	/* Test special chars with case folding */
1784 	create_qstr(&name, "File/Name");
1785 	result = hfsplus_compare_dentry(&test_dentry, 9, "file:name", &name);
1786 	KUNIT_EXPECT_EQ(test, 0, result);
1787 
1788 	/* Test with both flags disabled */
1789 	clear_bit(HFSPLUS_SB_CASEFOLD, &mock_sb->sb_info.flags);
1790 	clear_bit(HFSPLUS_SB_NODECOMPOSE, &mock_sb->sb_info.flags);
1791 
1792 	create_qstr(&name, "hello");
1793 	result = hfsplus_compare_dentry(&test_dentry, 5, "HELLO", &name);
1794 	KUNIT_EXPECT_NE(test, 0, result); /* Case sensitive */
1795 
1796 	/* But special chars should still be converted */
1797 	create_qstr(&name, "file/name");
1798 	result = hfsplus_compare_dentry(&test_dentry, 9, "file:name", &name);
1799 	KUNIT_EXPECT_EQ(test, 0, result);
1800 
1801 	free_mock_sb(mock_sb);
1802 }
1803 
1804 /*
1805  * Test that hfsplus_compare_dentry() treats two names as equal when they
1806  * differ only in the (non-canonical) typed order of the same combining
1807  * marks, since both refer to the same canonically-ordered catalog entry.
1808  */
1809 static void hfsplus_compare_dentry_combining_reorder_test(struct kunit *test)
1810 {
1811 	struct test_mock_sb *mock_sb;
1812 	struct qstr name;
1813 	int result;
1814 
1815 	mock_sb = setup_mock_sb();
1816 	KUNIT_ASSERT_NOT_NULL(test, mock_sb);
1817 
1818 	setup_mock_dentry(&mock_sb->sb);
1819 	mock_sb->nls.char2uni = test_char2uni_utf8;
1820 
1821 	create_qstr(&name, "e\xcc\xa3\xcc\x81"); /* dot-below, then acute */
1822 	result = hfsplus_compare_dentry(&test_dentry, 5, "e\xcc\x81\xcc\xa3",
1823 					&name); /* acute, then dot-below */
1824 	KUNIT_EXPECT_EQ(test, 0, result);
1825 
1826 	free_mock_sb(mock_sb);
1827 }
1828 
1829 /*
1830  * Test that a precomposed character using one of fsck_hfs's known legacy
1831  * decompositions compares equal to the already-decomposed form of the
1832  * same character - i.e. that hfsplus_compare_dentry() applies the same
1833  * legacy-decomposition correction as hfsplus_asc2uni() does on storage,
1834  * so a lookup finds the entry regardless of which form was typed.
1835  */
1836 static void hfsplus_compare_dentry_legacy_decomp_test(struct kunit *test)
1837 {
1838 	struct test_mock_sb *mock_sb;
1839 	struct qstr name;
1840 	int result;
1841 
1842 	mock_sb = setup_mock_sb();
1843 	KUNIT_ASSERT_NOT_NULL(test, mock_sb);
1844 
1845 	setup_mock_dentry(&mock_sb->sb);
1846 	mock_sb->nls.char2uni = test_char2uni_utf8;
1847 
1848 	/* "N" + COMBINING GRAVE ACCENT (U+0300), already decomposed */
1849 	create_qstr(&name, "N\xcc\x80");
1850 	/* U+01F8, precomposed */
1851 	result = hfsplus_compare_dentry(&test_dentry, 2, "\xc7\xb8", &name);
1852 	KUNIT_EXPECT_EQ(test, 0, result);
1853 
1854 	free_mock_sb(mock_sb);
1855 }
1856 
1857 static struct kunit_case hfsplus_unicode_test_cases[] = {
1858 	KUNIT_CASE(hfsplus_strcasecmp_test),
1859 	KUNIT_CASE(hfsplus_strcmp_test),
1860 	KUNIT_CASE(hfsplus_unicode_edge_cases_test),
1861 	KUNIT_CASE(hfsplus_unicode_boundary_test),
1862 	KUNIT_CASE(hfsplus_uni2asc_basic_test),
1863 	KUNIT_CASE(hfsplus_uni2asc_special_chars_test),
1864 	KUNIT_CASE(hfsplus_uni2asc_buffer_test),
1865 	KUNIT_CASE(hfsplus_uni2asc_corrupted_test),
1866 	KUNIT_CASE(hfsplus_uni2asc_edge_cases_test),
1867 	KUNIT_CASE(hfsplus_asc2uni_basic_test),
1868 	KUNIT_CASE(hfsplus_asc2uni_special_chars_test),
1869 	KUNIT_CASE(hfsplus_asc2uni_buffer_limits_test),
1870 	KUNIT_CASE(hfsplus_asc2uni_edge_cases_test),
1871 	KUNIT_CASE(hfsplus_asc2uni_decompose_test),
1872 	KUNIT_CASE(hfsplus_asc2uni_combining_reorder_test),
1873 	KUNIT_CASE(hfsplus_unicode_combining_reorder_roundtrip_test),
1874 	KUNIT_CASE(hfsplus_asc2uni_legacy_decomp_test),
1875 	KUNIT_CASE(hfsplus_asc2uni_legacy_seq_fixup_test),
1876 	KUNIT_CASE(hfsplus_hash_dentry_basic_test),
1877 	KUNIT_CASE(hfsplus_hash_dentry_casefold_test),
1878 	KUNIT_CASE(hfsplus_hash_dentry_special_chars_test),
1879 	KUNIT_CASE(hfsplus_hash_dentry_decompose_test),
1880 	KUNIT_CASE(hfsplus_hash_dentry_consistency_test),
1881 	KUNIT_CASE(hfsplus_hash_dentry_edge_cases_test),
1882 	KUNIT_CASE(hfsplus_hash_dentry_combining_reorder_test),
1883 	KUNIT_CASE(hfsplus_compare_dentry_basic_test),
1884 	KUNIT_CASE(hfsplus_compare_dentry_casefold_test),
1885 	KUNIT_CASE(hfsplus_compare_dentry_special_chars_test),
1886 	KUNIT_CASE(hfsplus_compare_dentry_length_test),
1887 	KUNIT_CASE(hfsplus_compare_dentry_decompose_test),
1888 	KUNIT_CASE(hfsplus_compare_dentry_edge_cases_test),
1889 	KUNIT_CASE(hfsplus_compare_dentry_combined_flags_test),
1890 	KUNIT_CASE(hfsplus_compare_dentry_combining_reorder_test),
1891 	KUNIT_CASE(hfsplus_compare_dentry_legacy_decomp_test),
1892 	{}
1893 };
1894 
1895 static struct kunit_suite hfsplus_unicode_test_suite = {
1896 	.name = "hfsplus_unicode",
1897 	.test_cases = hfsplus_unicode_test_cases,
1898 };
1899 
1900 kunit_test_suite(hfsplus_unicode_test_suite);
1901 
1902 MODULE_DESCRIPTION("KUnit tests for HFS+ Unicode string operations");
1903 MODULE_LICENSE("GPL");
1904 MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
1905