xref: /illumos-gate/usr/src/test/libc-tests/tests/uchar.c (revision 5400c85432c0a689ecefb8c19b8f9861809ca093)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2026 Oxide Computer Company
14  */
15 
16 /*
17  * Test the implementation of various pieces of uchar.h(3HEAD) functionality.
18  */
19 
20 #include <locale.h>
21 #include <err.h>
22 #include <stdlib.h>
23 #include <sys/types.h>
24 #include <sys/sysmacros.h>
25 #include <strings.h>
26 #include <wchar.h>
27 #include <uchar.h>
28 #include <errno.h>
29 #include <string.h>
30 
31 static const char *uchar_wide = "光";
32 static const char32_t uchar_value = 0x5149;
33 static const char *uchar_hello = "hello";
34 static const char16_t uchar_euro = 0x20ac;
35 
36 static void
37 update_locale(const char *loc)
38 {
39 	const char *newloc = setlocale(LC_CTYPE, loc);
40 	if (newloc == NULL) {
41 		err(EXIT_FAILURE, "TEST FAILED: failed to update locale to %s",
42 		    loc);
43 	}
44 
45 	if (strcmp(newloc, loc) != 0) {
46 		errx(EXIT_FAILURE, "TEST FAILED: locale set to %s, but got %s",
47 		    loc, newloc);
48 	}
49 }
50 
51 static boolean_t
52 mbrtoc32_ascii(mbstate_t *mbs)
53 {
54 	char32_t out;
55 	size_t len;
56 	boolean_t ret = B_TRUE;
57 
58 	if ((len = mbrtoc32(&out, uchar_hello, 5, mbs)) != 1) {
59 		warnx("expected mbrtoc32 to return 1, returned %zu", len);
60 		ret = B_FALSE;
61 	}
62 
63 	if (out != 'h') {
64 		warnx("got bad char32_t, expected 0x%x, found 0x%x", 'h',
65 		    out);
66 		ret = B_FALSE;
67 	}
68 
69 	if ((len = mbrtoc32(&out, uchar_hello + 1, 4, mbs)) != 1) {
70 		warnx("expected mbrtoc32 to return 1, returned %zu", len);
71 		ret = B_FALSE;
72 	}
73 
74 	if (out != 'e') {
75 		warnx("got bad char32_t, expected 0x%x, found 0x%x", 'h',
76 		    out);
77 		ret = B_FALSE;
78 	}
79 
80 	return (ret);
81 }
82 
83 static boolean_t
84 mbrtoc32_ascii_internal(void)
85 {
86 	return (mbrtoc32_ascii(NULL));
87 }
88 
89 static boolean_t
90 mbrtoc32_ascii_mbstate(void)
91 {
92 	mbstate_t mbs;
93 
94 	bzero(&mbs, sizeof (mbs));
95 	return (mbrtoc32_ascii(&mbs));
96 }
97 
98 static boolean_t
99 mbrtoc32_badseq_utf8(void)
100 {
101 	mbstate_t mbs;
102 	size_t len;
103 	char32_t out;
104 	boolean_t ret = B_TRUE;
105 	char *badstr;
106 
107 	bzero(&mbs, sizeof (mbs));
108 	len = mbrtoc32(&out, "\xa9", 1, &mbs);
109 	if (len != (size_t)-1) {
110 		warnx("mbrtoc32 returned %zu, not %zu", len, (size_t)-1);
111 		ret = B_FALSE;
112 	}
113 
114 	if (errno != EILSEQ) {
115 		warnx("found bad errno: expected %s, found %s",
116 		    strerrorname_np(EILSEQ), strerrorname_np(errno));
117 		ret = B_FALSE;
118 	}
119 
120 	badstr = strdup(uchar_wide);
121 	if (badstr == NULL) {
122 		warn("failed to duplicate uchar_wide");
123 		return (B_FALSE);
124 	}
125 
126 	badstr[1] = '?';
127 	bzero(&mbs, sizeof (mbs));
128 	len = mbrtoc32(&out, badstr, strlen(badstr), &mbs);
129 	free(badstr);
130 	if (len != (size_t)-1) {
131 		warnx("mbrtoc32 returned %zu, not %zu", len, (size_t)-1);
132 		ret = B_FALSE;
133 	}
134 
135 	if (errno != EILSEQ) {
136 		warnx("found bad errno: expected %s, found %s",
137 		    strerrorname_np(EILSEQ), strerrorname_np(errno));
138 		ret = B_FALSE;
139 	}
140 
141 	return (ret);
142 }
143 
144 static boolean_t
145 mbrtoc32_badseq_8859(void)
146 {
147 	mbstate_t mbs;
148 	size_t len;
149 	char32_t out;
150 	boolean_t ret = B_TRUE;
151 
152 	bzero(&mbs, sizeof (mbs));
153 	len = mbrtoc32(&out, "\x87", 1, &mbs);
154 	if (len != (size_t)-1) {
155 		warnx("mbrtoc32 returned %zu, not %zu", len, (size_t)-1);
156 		ret = B_FALSE;
157 	}
158 
159 	if (errno != EILSEQ) {
160 		warnx("found bad errno: expected %s, found %s",
161 		    strerrorname_np(EILSEQ), strerrorname_np(errno));
162 		ret = B_FALSE;
163 	}
164 
165 	return (ret);
166 }
167 
168 static boolean_t
169 mbrtoc32_roundtrip(void)
170 {
171 	char32_t out;
172 	size_t len, clen;
173 	mbstate_t mbs;
174 	char buf[MB_CUR_MAX];
175 	boolean_t ret = B_TRUE;
176 
177 	bzero(&mbs, sizeof (mbs));
178 	len = mbrtoc32(&out, uchar_wide, strlen(uchar_wide), &mbs);
179 	if (len != 3) {
180 		warnx("mbrtoc32 returned %zu, expected %u", len, 3);
181 		ret = B_FALSE;
182 	}
183 
184 	if (out != uchar_value) {
185 		warnx("mbrtoc32 converted character to 0x%x not 0x%x",
186 		    out, uchar_value);
187 		ret = B_FALSE;
188 	}
189 
190 	bzero(&mbs, sizeof (mbs));
191 	clen = c32rtomb(buf, out, &mbs);
192 	if (clen != len) {
193 		warnx("c32rtomb returned %zu bytes, but we originally used %zu",
194 		    clen, len);
195 		ret = B_FALSE;
196 	}
197 
198 	if (strncmp(buf, uchar_wide, len) != 0) {
199 		warnx("round trip string comparison failed");
200 		ret = B_FALSE;
201 	}
202 
203 	return (ret);
204 }
205 
206 static boolean_t
207 mbrtoc32_partial(void)
208 {
209 	char32_t out;
210 	size_t len, i;
211 	mbstate_t mbs;
212 	boolean_t ret = B_TRUE;
213 
214 	bzero(&mbs, sizeof (mbs));
215 	for (i = 0; i < strlen(uchar_wide) - 1; i++) {
216 		len = mbrtoc32(&out, uchar_wide + i, 1, &mbs);
217 		if (len != (size_t)-2) {
218 			warnx("partial mbrtoc32 returned %zu, not -2", len);
219 			ret = B_FALSE;
220 		}
221 	}
222 
223 	len = mbrtoc32(&out, uchar_wide + i, 1, &mbs);
224 	if (len != 1) {
225 		warnx("partial mbrtoc32 returned %zu, not 1", len);
226 		ret = B_FALSE;
227 	}
228 
229 	if (out != uchar_value) {
230 		warnx("mbrtoc32 converted character to 0x%x not 0x%x",
231 		    out, uchar_value);
232 		ret = B_FALSE;
233 	}
234 
235 	return (ret);
236 }
237 
238 static boolean_t
239 mbrtoc32_zero(void)
240 {
241 	char32_t out, exp = L'\0';
242 	size_t len;
243 	mbstate_t mbs;
244 	boolean_t ret = B_TRUE;
245 
246 	bzero(&mbs, sizeof (mbs));
247 	len = mbrtoc32(&out, "", 1, &mbs);
248 	if (len != 0) {
249 		warnx("partial mbrtoc32 returned %zu, not 0", len);
250 		ret = B_FALSE;
251 	}
252 
253 	if (out != exp) {
254 		warnx("mbrtoc32 converted character to 0x%x not 0x%x",
255 		    out, exp);
256 		ret = B_FALSE;
257 	}
258 
259 	return (ret);
260 }
261 
262 static boolean_t
263 mbrtoc32_zero_len(void)
264 {
265 	char32_t out = 0x12345, exp = 0x12345;
266 	size_t len;
267 	mbstate_t mbs;
268 	boolean_t ret = B_TRUE;
269 
270 	bzero(&mbs, sizeof (mbs));
271 	len = mbrtoc32(&out, uchar_wide, 0, &mbs);
272 	if (len != (size_t)-2) {
273 		warnx("partial mbrtoc32 returned %zu, not -2", len);
274 		ret = B_FALSE;
275 	}
276 
277 	if (out != exp) {
278 		warnx("mbrtoc32 incorrectly wrote to char32_t value with "
279 		    "zero string, found 0x%x not 0x%x", out, exp);
280 		ret = B_FALSE;
281 	}
282 
283 	return (ret);
284 }
285 
286 static boolean_t
287 mbrtoc32_null(void)
288 {
289 	char32_t out = 0x123456, exp = 0x123456;
290 	size_t len;
291 	mbstate_t mbs;
292 	boolean_t ret = B_TRUE;
293 
294 	bzero(&mbs, sizeof (mbs));
295 	len = mbrtoc32(&out, NULL, 1, &mbs);
296 	if (len != 0) {
297 		warnx("partial mbrtoc32 returned %zu, not 0", len);
298 		ret = B_FALSE;
299 	}
300 
301 	if (out != exp) {
302 		warnx("mbrtoc32 incorrectly wrote to char32_t value with "
303 		    "null string, found 0x%x not 0x%x", out, exp);
304 		ret = B_FALSE;
305 	}
306 
307 	return (ret);
308 }
309 
310 /*
311  * Verify that a character string in 8859-1 (which nominally overlaps with UTF)
312  * can properly go through iconv and come out as what we expect on the other
313  * side.
314  */
315 static boolean_t
316 mbrtoc32_iconv_8859(void)
317 {
318 	mbstate_t mbs;
319 	size_t len;
320 	char32_t out, exp = L'\xa6';
321 	boolean_t ret = B_TRUE;
322 
323 	bzero(&mbs, sizeof (mbs));
324 	len = mbrtoc32(&out, "\xa6", 1, &mbs);
325 	if (len != 1) {
326 		warnx("mbrtoc32 returned %zu, not 1", len);
327 		ret = B_FALSE;
328 	}
329 
330 	if (out != exp) {
331 		warnx("mbrtoc32 converted character to 0x%x not 0x%x",
332 		    out, exp);
333 		ret = B_FALSE;
334 	}
335 
336 	return (ret);
337 }
338 
339 /*
340  * Similar to the above but here we actually expect it to be something else
341  * entirely and not a 1:1 translation. This verifies that iconv is firing and
342  * getting us more than one character.
343  */
344 static boolean_t
345 mbrtoc32_iconv_euro(void)
346 {
347 	mbstate_t mbs;
348 	size_t len;
349 	char32_t out, exp = uchar_euro;
350 	boolean_t ret = B_TRUE;
351 
352 	bzero(&mbs, sizeof (mbs));
353 	len = mbrtoc32(&out, "\xa4", 1, &mbs);
354 	if (len != 1) {
355 		warnx("mbrtoc32 returned %zu, not 1", len);
356 		ret = B_FALSE;
357 	}
358 
359 	if (out != exp) {
360 		warnx("mbrtoc32 converted character to 0x%x not 0x%x",
361 		    out, exp);
362 		ret = B_FALSE;
363 	}
364 
365 	return (ret);
366 }
367 
368 static boolean_t
369 mbrtoc16_ascii(mbstate_t *mbs)
370 {
371 	char16_t out;
372 	size_t len;
373 	boolean_t ret = B_TRUE;
374 
375 	if ((len = mbrtoc16(&out, uchar_hello, 5, mbs)) != 1) {
376 		warnx("expected mbrtoc16 to return 1, returned %zu", len);
377 		ret = B_FALSE;
378 	}
379 
380 	if (out != 'h') {
381 		warnx("got bad char16_t, expected 0x%x, found 0x%x", 'h',
382 		    out);
383 		ret = B_FALSE;
384 	}
385 
386 	if ((len = mbrtoc16(&out, uchar_hello + 1, 4, mbs)) != 1) {
387 		warnx("expected mbrtoc16 to return 1, returned %zu", len);
388 		ret = B_FALSE;
389 	}
390 
391 	if (out != 'e') {
392 		warnx("got bad char16_t, expected 0x%x, found 0x%x", 'h',
393 		    out);
394 		ret = B_FALSE;
395 	}
396 
397 	return (ret);
398 }
399 
400 static boolean_t
401 mbrtoc16_ascii_internal(void)
402 {
403 	return (mbrtoc16_ascii(NULL));
404 }
405 
406 static boolean_t
407 mbrtoc16_ascii_mbstate(void)
408 {
409 	mbstate_t mbs;
410 
411 	bzero(&mbs, sizeof (mbs));
412 	return (mbrtoc16_ascii(&mbs));
413 }
414 
415 static boolean_t
416 mbrtoc16_null(void)
417 {
418 	char16_t out = 0x1234, exp = 0x1234;
419 	size_t len;
420 	mbstate_t mbs;
421 	boolean_t ret = B_TRUE;
422 
423 	bzero(&mbs, sizeof (mbs));
424 	len = mbrtoc16(&out, NULL, 1, &mbs);
425 	if (len != 0) {
426 		warnx("partial mbrtoc16 returned %zu, not 0", len);
427 		ret = B_FALSE;
428 	}
429 
430 	if (out != exp) {
431 		warnx("mbrtoc16 incorrectly wrote to char16_t value with "
432 		    "null string, found 0x%x not 0x%x", out, exp);
433 		ret = B_FALSE;
434 	}
435 
436 	return (ret);
437 }
438 
439 static boolean_t
440 mbrtoc16_zero(void)
441 {
442 	char16_t out, exp = L'\0';
443 	size_t len;
444 	mbstate_t mbs;
445 	boolean_t ret = B_TRUE;
446 
447 	bzero(&mbs, sizeof (mbs));
448 	len = mbrtoc16(&out, "", 1, &mbs);
449 	if (len != 0) {
450 		warnx("partial mbrtoc16 returned %zu, not 0", len);
451 		ret = B_FALSE;
452 	}
453 
454 	if (out != exp) {
455 		warnx("mbrtoc16 converted character to 0x%x not 0x%x",
456 		    out, exp);
457 		ret = B_FALSE;
458 	}
459 
460 	return (ret);
461 }
462 
463 static boolean_t
464 mbrtoc16_zero_len(void)
465 {
466 	char16_t out = 0x5432, exp = 0x5432;
467 	size_t len;
468 	mbstate_t mbs;
469 	boolean_t ret = B_TRUE;
470 
471 	bzero(&mbs, sizeof (mbs));
472 	len = mbrtoc16(&out, uchar_wide, 0, &mbs);
473 	if (len != (size_t)-2) {
474 		warnx("partial mbrtoc16 returned %zu, not -2", len);
475 		ret = B_FALSE;
476 	}
477 
478 	if (out != exp) {
479 		warnx("mbrtoc16 incorrectly wrote to char16_t value with "
480 		    "zero length string, found 0x%x not 0x%x", out, exp);
481 		ret = B_FALSE;
482 	}
483 
484 	return (ret);
485 }
486 
487 static boolean_t
488 mbrtoc16_roundtrip(void)
489 {
490 	char16_t out;
491 	size_t len, clen;
492 	mbstate_t mbs;
493 	char buf[MB_CUR_MAX];
494 	boolean_t ret = B_TRUE;
495 
496 	bzero(&mbs, sizeof (mbs));
497 	len = mbrtoc16(&out, uchar_wide, strlen(uchar_wide), &mbs);
498 	if (len != 3) {
499 		warnx("mbrtoc16 returned %zu, expected %u", len, 3);
500 		ret = B_FALSE;
501 	}
502 
503 	if (out != uchar_value) {
504 		warnx("mbrtoc16 converted character to 0x%x not 0x%x",
505 		    out, uchar_value);
506 		ret = B_FALSE;
507 	}
508 
509 	clen = c16rtomb(buf, out, &mbs);
510 	if (clen != len) {
511 		warnx("c16rtomb returned %zu bytes, but we originally used %zu",
512 		    clen, len);
513 		ret = B_FALSE;
514 	}
515 
516 	if (strncmp(buf, uchar_wide, len) != 0) {
517 		warnx("round trip string comparison failed");
518 		ret = B_FALSE;
519 	}
520 
521 	return (ret);
522 }
523 
524 static boolean_t
525 mbrtoc16_partial(void)
526 {
527 	char16_t out;
528 	size_t len, i;
529 	mbstate_t mbs;
530 	boolean_t ret = B_TRUE;
531 
532 	bzero(&mbs, sizeof (mbs));
533 	for (i = 0; i < strlen(uchar_wide) - 1; i++) {
534 		len = mbrtoc16(&out, uchar_wide + i, 1, &mbs);
535 		if (len != (size_t)-2) {
536 			warnx("partial mbrtoc16 returned %zu, not -2", len);
537 			ret = B_FALSE;
538 		}
539 	}
540 
541 	len = mbrtoc16(&out, uchar_wide + i, 1, &mbs);
542 	if (len != 1) {
543 		warnx("partial mbrtoc16 returned %zu, not 1", len);
544 		ret = B_FALSE;
545 	}
546 
547 	if (out != uchar_value) {
548 		warnx("mbrtoc16 converted character to 0x%x not 0x%x",
549 		    out, uchar_value);
550 		ret = B_FALSE;
551 	}
552 
553 	return (ret);
554 }
555 
556 static boolean_t
557 mbrtoc16_surrogate(void)
558 {
559 	char16_t out0, out1;
560 	size_t len, clen;
561 	mbstate_t mbs;
562 	const char *surrogate = "\xF0\x9F\x92\xA9";
563 	char16_t exp0 = 0xd83d, exp1 = 0xdca9;
564 	size_t slen = strlen(surrogate);
565 	boolean_t ret = B_TRUE;
566 	char buf[MB_CUR_MAX];
567 
568 	bzero(&mbs, sizeof (mbs));
569 	len = mbrtoc16(&out0, surrogate, slen, &mbs);
570 	if (len != slen) {
571 		warnx("mbrtoc16 returned %zu, expected %zu", len, slen);
572 		ret = B_FALSE;
573 	}
574 
575 	if (out0 != exp0) {
576 		warnx("mbrtoc16 converted character to 0x%x not 0x%x",
577 		    out0, exp0);
578 		ret = B_FALSE;
579 	}
580 
581 	if (mbsinit(&mbs) != 0) {
582 		warnx("mb state with a surrogate character is somehow in the "
583 		    "initial state");
584 		ret = B_FALSE;
585 	}
586 
587 	len = mbrtoc16(&out1, uchar_wide, strlen(uchar_wide), &mbs);
588 	if (len != (size_t)-3) {
589 		warnx("mbrtoc16 returned %zu, expected -3", len);
590 		ret = B_FALSE;
591 	}
592 
593 	if (mbsinit(&mbs) == 0) {
594 		warnx("mb state with after both surrogate characters isn't "
595 		    "in initial state");
596 		ret = B_FALSE;
597 	}
598 
599 	if (out1 != exp1) {
600 		warnx("mbrtoc32 converted character to 0x%x not 0x%x",
601 		    out1, exp1);
602 		ret = B_FALSE;
603 	}
604 
605 	clen = c16rtomb(buf, out0, &mbs);
606 	if (clen != 0) {
607 		warnx("c16rtomb returned %zu bytes, but expected zero for the "
608 		    "first surrogate", clen);
609 		ret = B_FALSE;
610 	}
611 
612 	if (mbsinit(&mbs) != 0) {
613 		warnx("mb state with a surrogate character is somehow in the "
614 		    "initial state");
615 		ret = B_FALSE;
616 	}
617 
618 	clen = c16rtomb(buf, out1, &mbs);
619 	if (clen != slen) {
620 		warnx("c16rtomb returned %zu, expected %zu", len, slen);
621 		ret = B_FALSE;
622 	}
623 
624 	if (mbsinit(&mbs) == 0) {
625 		warnx("mb state with after both surrogate characters isn't "
626 		    "in initial state");
627 		ret = B_FALSE;
628 	}
629 
630 	if (strncmp(buf, surrogate, slen) != 0) {
631 		warnx("round trip string comparison failed");
632 		ret = B_FALSE;
633 	}
634 
635 	return (ret);
636 }
637 
638 static boolean_t
639 c32rtomb_eilseq_iso8859(void)
640 {
641 	char buf[MB_CUR_MAX];
642 	mbstate_t mbs;
643 	size_t len;
644 	boolean_t ret = B_TRUE;
645 
646 	bzero(&mbs, sizeof (mbs));
647 	len = c32rtomb(buf, uchar_value, &mbs);
648 	if (len != (size_t)-1) {
649 		warnx("c32rtomb returned %zd, expected -1", len);
650 		ret = B_FALSE;
651 	}
652 
653 	if (errno != EILSEQ) {
654 		warnx("found bad errno: expected %s, found %s",
655 		    strerrorname_np(EILSEQ), strerrorname_np(errno));
656 		ret = B_FALSE;
657 	}
658 
659 	return (ret);
660 }
661 
662 static boolean_t
663 c16rtomb_eilseq_iso8859(void)
664 {
665 	char buf[MB_CUR_MAX];
666 	mbstate_t mbs;
667 	size_t len;
668 	boolean_t ret = B_TRUE;
669 
670 	bzero(&mbs, sizeof (mbs));
671 	len = c32rtomb(buf, (char16_t)uchar_value, &mbs);
672 	if (len != (size_t)-1) {
673 		warnx("c32rtomb returned %zd, expected -1", len);
674 		ret = B_FALSE;
675 	}
676 
677 	if (errno != EILSEQ) {
678 		warnx("found bad errno: expected %s, found %s",
679 		    strerrorname_np(EILSEQ), strerrorname_np(errno));
680 		ret = B_FALSE;
681 	}
682 
683 	return (ret);
684 }
685 
686 static boolean_t
687 c32rtomb_eilseq_utf8(void)
688 {
689 	char buf[MB_CUR_MAX];
690 	mbstate_t mbs;
691 	size_t len;
692 	boolean_t ret = B_TRUE;
693 
694 	bzero(&mbs, sizeof (mbs));
695 	len = c32rtomb(buf, UINT32_MAX, &mbs);
696 	if (len != (size_t)-1) {
697 		warnx("c32rtomb returned %zd, expected -1", len);
698 		ret = B_FALSE;
699 	}
700 
701 	if (errno != EILSEQ) {
702 		warnx("found bad errno: expected %s, found %s",
703 		    strerrorname_np(EILSEQ), strerrorname_np(errno));
704 		ret = B_FALSE;
705 	}
706 
707 	return (ret);
708 }
709 
710 static boolean_t
711 c16rtomb_bad_first(void)
712 {
713 	char buf[MB_CUR_MAX];
714 	mbstate_t mbs;
715 	size_t len, i;
716 	char16_t first = 0xd83d;
717 	char16_t bad[] = { 0x0, 0xd7ff, 0xd83d, 0xd900, 0xffff };
718 	boolean_t ret = B_TRUE;
719 
720 	for (i = 0; i < ARRAY_SIZE(bad); i++) {
721 		bzero(&mbs, sizeof (mbs));
722 		len = c16rtomb(buf, first, &mbs);
723 		if (len != 0) {
724 			warnx("c16rtomb returned %zd, expected 0", len);
725 			ret = B_FALSE;
726 		}
727 
728 		len = c16rtomb(buf, bad[i], &mbs);
729 		if (len != (size_t)-1) {
730 			warnx("c16rtomb surrogate %x returned %zd, expected "
731 			    "-1", bad[i], len);
732 			ret = B_FALSE;
733 		}
734 
735 		if (errno != EILSEQ) {
736 			warnx("found bad errno: expected %s, found %s",
737 			    strerrorname_np(EILSEQ), strerrorname_np(errno));
738 			ret = B_FALSE;
739 		}
740 	}
741 
742 	return (ret);
743 }
744 
745 static boolean_t
746 c16rtomb_bad_second(void)
747 {
748 	char buf[MB_CUR_MAX];
749 	mbstate_t mbs;
750 	size_t len, i;
751 	char16_t bad[] = { 0xdc00, 0xdd34, 0xdfff };
752 	boolean_t ret = B_TRUE;
753 
754 	for (i = 0; i < ARRAY_SIZE(bad); i++) {
755 		bzero(&mbs, sizeof (mbs));
756 		len = c16rtomb(buf, bad[i], &mbs);
757 		if (len != (size_t)-1) {
758 			warnx("c16rtomb surrogate %x returned %zd, expected -1",
759 			    bad[i], len);
760 			ret = B_FALSE;
761 		}
762 
763 		if (errno != EILSEQ) {
764 			warnx("found bad errno: expected %s, found %s",
765 			    strerrorname_np(EILSEQ), strerrorname_np(errno));
766 			ret = B_FALSE;
767 		}
768 	}
769 
770 	return (ret);
771 }
772 
773 static boolean_t
774 c32rtomb_null(void)
775 {
776 	size_t len;
777 	mbstate_t mbs;
778 	boolean_t ret = B_TRUE;
779 
780 	bzero(&mbs, sizeof (mbs));
781 	len = c32rtomb(NULL, uchar_value, &mbs);
782 	if (len != 1) {
783 		warnx("c32rtomb returned %zu, expected %d", len, 1);
784 		ret = B_FALSE;
785 	}
786 
787 	return (ret);
788 }
789 
790 static boolean_t
791 c32rtomb_null_euro(void)
792 {
793 	size_t len;
794 	mbstate_t mbs;
795 	boolean_t ret = B_TRUE;
796 
797 	bzero(&mbs, sizeof (mbs));
798 	len = c32rtomb(NULL, uchar_euro, &mbs);
799 	if (len != 1) {
800 		warnx("c32rtomb returned %zu, expected %d", len, 1);
801 		ret = B_FALSE;
802 	}
803 
804 	return (ret);
805 }
806 
807 static boolean_t
808 c16rtomb_null(void)
809 {
810 	size_t len;
811 	mbstate_t mbs;
812 	boolean_t ret = B_TRUE;
813 
814 	bzero(&mbs, sizeof (mbs));
815 	len = c16rtomb(NULL, uchar_value, &mbs);
816 	if (len != 1) {
817 		warnx("c16rtomb returned %zu, expected %d", len, 1);
818 		ret = B_FALSE;
819 	}
820 
821 	return (ret);
822 }
823 
824 static boolean_t
825 c16rtomb_null_euro(void)
826 {
827 	size_t len;
828 	mbstate_t mbs;
829 	boolean_t ret = B_TRUE;
830 
831 	bzero(&mbs, sizeof (mbs));
832 	len = c16rtomb(NULL, uchar_euro, &mbs);
833 	if (len != 1) {
834 		warnx("c16rtomb returned %zu, expected %d", len, 1);
835 		ret = B_FALSE;
836 	}
837 
838 	return (ret);
839 }
840 
841 static boolean_t
842 c32rtomb_iconv_ascii(void)
843 {
844 	char buf[MB_CUR_MAX];
845 	const char *exp = "h";
846 	mbstate_t mbs;
847 	size_t len;
848 	boolean_t ret = B_TRUE;
849 
850 	bzero(&mbs, sizeof (mbs));
851 	len = c32rtomb(buf, L'h', &mbs);
852 	if (len != 1) {
853 		warnx("c32rtomb returned %zd, expected 1", len);
854 		ret = B_FALSE;
855 	}
856 
857 	if (strncmp(buf, exp, len) != 0) {
858 		warnx("c32rtomb didn't return expected string");
859 		ret = B_FALSE;
860 	}
861 
862 	return (ret);
863 }
864 
865 static boolean_t
866 c32rtomb_iconv_euro(void)
867 {
868 	char buf[MB_CUR_MAX];
869 	const char *exp = "\xa4";
870 	mbstate_t mbs;
871 	size_t len;
872 	boolean_t ret = B_TRUE;
873 
874 	bzero(&mbs, sizeof (mbs));
875 	len = c32rtomb(buf, uchar_euro, &mbs);
876 	if (len != 1) {
877 		warnx("c32rtomb returned %zd, expected 1", len);
878 		ret = B_FALSE;
879 	}
880 
881 	if (strncmp(buf, exp, len) != 0) {
882 		warnx("c32rtomb didn't return expected string");
883 		ret = B_FALSE;
884 	}
885 
886 	return (ret);
887 }
888 
889 /*
890  * Do a full round trip of our wide character into a full UTF-8 sequence and
891  * then back again.
892  */
893 static boolean_t
894 mbrtoc8_roundtrip(const char *input, size_t inlen, const char8_t *out,
895     size_t outlen)
896 {
897 	char8_t c8[4];
898 	size_t exp_mbret[4];
899 	size_t exp_csret[4];
900 	char str[MB_CUR_MAX];
901 	mbstate_t mbs;
902 	boolean_t ret = B_TRUE;
903 
904 	/*
905 	 * Construct results based on the given lengths. We will need to call
906 	 * mbrtoc8 one time for each output character. The first time we call it
907 	 * though it'll output the input length as it should consume it all. The
908 	 * opposite is true when we go in reverse.
909 	 */
910 	for (size_t i = 0; i < outlen; i++) {
911 		if (i == 0) {
912 			exp_mbret[i] = inlen;
913 		} else {
914 			exp_mbret[i] = (size_t)-3;
915 		}
916 	}
917 
918 	for (size_t i = 0; i < outlen; i++) {
919 		if (i == outlen - 1) {
920 			exp_csret[i] = inlen;
921 		} else {
922 			exp_csret[i] = 0;
923 		}
924 	}
925 
926 	bzero(&mbs, sizeof (mbs));
927 	for (size_t i = 0; i < outlen; i++) {
928 		size_t len = mbrtoc8(&c8[i], input, inlen, &mbs);
929 		if (len != exp_mbret[i]) {
930 			warnx("mbrtoc8[%zu] returned %zu, expected %zd", i, len,
931 			    exp_mbret[i]);
932 			ret = B_FALSE;
933 		}
934 
935 		if (c8[i] != out[i]) {
936 			warnx("mbrtoc8[%zu] converted character to 0x%x not "
937 			    "0x%x", i, c8[i], out[i]);
938 			ret = B_FALSE;
939 		}
940 	}
941 
942 	bzero(&mbs, sizeof (mbs));
943 	for (size_t i = 0; i < outlen; i++) {
944 		size_t len = c8rtomb(str, c8[i], &mbs);
945 		if (len != exp_csret[i]) {
946 			warnx("c8rtomb[%zu] returned %zu, expected %zd", i, len,
947 			    exp_csret[i]);
948 			ret = B_FALSE;
949 		}
950 	}
951 
952 	if (strncmp(str, input, inlen) != 0) {
953 		warnx("round trip string comparison failed");
954 		ret = B_FALSE;
955 	}
956 
957 	return (ret);
958 }
959 
960 static boolean_t
961 mbrtoc8_roundtrip_1b(void)
962 {
963 	const char8_t out[1] = { 'h' };
964 	return (mbrtoc8_roundtrip(uchar_hello, 1, out, ARRAY_SIZE(out)));
965 }
966 
967 static boolean_t
968 mbrtoc8_roundtrip_2b(void)
969 {
970 	const char *in = "ũ";
971 	const char8_t out[2] = { 0xc5, 0xa9 };
972 	return (mbrtoc8_roundtrip(in, strlen(in), out, ARRAY_SIZE(out)));
973 }
974 
975 static boolean_t
976 mbrtoc8_roundtrip_3b(void)
977 {
978 	const char8_t out[3] = { 0xe5, 0x85, 0x89 };
979 	return (mbrtoc8_roundtrip(uchar_wide, strlen(uchar_wide), out,
980 	    ARRAY_SIZE(out)));
981 }
982 
983 static boolean_t
984 mbrtoc8_roundtrip_4b(void)
985 {
986 	/* U+1B001 */
987 	const char four[4] = { 0xf0, 0x9b, 0x80, 0x81 };
988 	const char8_t out[4] = { 0xf0, 0x9b, 0x80, 0x81 };
989 	return (mbrtoc8_roundtrip(four, ARRAY_SIZE(four), out,
990 	    ARRAY_SIZE(out)));
991 }
992 
993 static boolean_t
994 mbrtoc8_roundtrip_euro(void)
995 {
996 	const char *in = "\xa4";
997 	const char8_t out[3] = { 0xe2, 0x82, 0xac };
998 	return (mbrtoc8_roundtrip(in, strlen(in), out, ARRAY_SIZE(out)));
999 }
1000 
1001 typedef struct {
1002 	const char *bu_desc;
1003 	uint8_t bu_nchar;
1004 	char8_t bu_chars[4];
1005 } bad_utf8_t;
1006 
1007 /*
1008  * This covers various bad orderings of characters. See c8rtomb.c in libc. One
1009  * caveat is that we can't use a 0x00 character in here. That will be
1010  * interpreted per the standard as a null character regardless of what was
1011  * already there.
1012  */
1013 static const bad_utf8_t bad_utf[] = {
1014 	{ "invalid starting (1)", 1, { 0x80 } },
1015 	{ "invalid starting (2)", 1, { 0xff } },
1016 	{ "invalid starting (3)", 1, { 0xc1 } },
1017 	{ "invalid 2-byte (1)", 2, { 0xc2, 0x01 } },
1018 	{ "invalid 2-byte (2)", 2, { 0xc2, 0x42 } },
1019 	{ "invalid 2-byte (3)", 2, { 0xc2, 0xc0 } },
1020 	{ "invalid 2-byte (4)", 2, { 0xdd, 0xdd } },
1021 	{ "invalid 2-byte (5)", 2, { 0xde, 0xed } },
1022 	{ "invalid 2-byte (6)", 2, { 0xdf, 0xfd } },
1023 	{ "invalid 3-byte e0 (1)", 2, { 0xe0, 0x9f } },
1024 	{ "invalid 3-byte e0 (2)", 2, { 0xe0, 0xc0 } },
1025 	{ "invalid 3-byte e0 (3)", 3, { 0xe0, 0xaa, 0x07 } },
1026 	{ "invalid 3-byte e0 (4)", 3, { 0xe0, 0xbf, 0xfb } },
1027 	{ "invalid 3-byte ed (1)", 2, { 0xed, 0x7f } },
1028 	{ "invalid 3-byte ed (2)", 2, { 0xed, 0xa0 } },
1029 	{ "invalid 3-byte ed (3)", 3, { 0xed, 0x98, 0x07 } },
1030 	{ "invalid 3-byte ed (4)", 3, { 0xed, 0x87, 0xcd } },
1031 	{ "invalid 3-byte (1)", 2, { 0xea, 0xea } },
1032 	{ "invalid 3-byte (2)", 2, { 0xe9, 0x23 } },
1033 	{ "invalid 3-byte (3)", 2, { 0xe8, 0xdc } },
1034 	{ "invalid 3-byte (4)", 3, { 0xe8, 0x80, 0x08 } },
1035 	{ "invalid 3-byte (5)", 3, { 0xe8, 0x81, 0xc0 } },
1036 	{ "invalid 3-byte (6)", 3, { 0xe2, 0xaa, 0xce } },
1037 	{ "invalid 4-byte f0 (1)", 2, { 0xf0, 0x89 } },
1038 	{ "invalid 4-byte f0 (2)", 2, { 0xf0, 0x17 } },
1039 	{ "invalid 4-byte f0 (3)", 2, { 0xf0, 0xc0 } },
1040 	{ "invalid 4-byte f0 (4)", 3, { 0xf0, 0x90, 0x23 } },
1041 	{ "invalid 4-byte f0 (5)", 3, { 0xf0, 0x90, 0x42 } },
1042 	{ "invalid 4-byte f0 (6)", 3, { 0xf0, 0xa0, 0xc0 } },
1043 	{ "invalid 4-byte f0 (7)", 3, { 0xf0, 0xb0, 0xdf } },
1044 	{ "invalid 4-byte f0 (9)", 4, { 0xf0, 0xa1, 0x81, 0x18 } },
1045 	{ "invalid 4-byte f0 (10)", 4, { 0xf0, 0x92, 0x82, 0x2b } },
1046 	{ "invalid 4-byte f0 (11)", 4, { 0xf0, 0xa3, 0x92, 0xc9 } },
1047 	{ "invalid 4-byte f0 (12)", 4, { 0xf0, 0xb4, 0xbf, 0xfb } },
1048 	{ "invalid 4-byte f4 (1)", 2, { 0xf4, 0xa2 } },
1049 	{ "invalid 4-byte f4 (2)", 2, { 0xf4, 0x17 } },
1050 	{ "invalid 4-byte f4 (3)", 2, { 0xf4, 0xc0 } },
1051 	{ "invalid 4-byte f4 (4)", 3, { 0xf4, 0x80, 0x23 } },
1052 	{ "invalid 4-byte f4 (5)", 3, { 0xf4, 0x81, 0x42 } },
1053 	{ "invalid 4-byte f4 (6)", 3, { 0xf4, 0x82, 0xc0 } },
1054 	{ "invalid 4-byte f4 (7)", 3, { 0xf4, 0x83, 0xdf } },
1055 	{ "invalid 4-byte f4 (9)", 4, { 0xf4, 0x84, 0x81, 0x18 } },
1056 	{ "invalid 4-byte f4 (10)", 4, { 0xf4, 0x85, 0x82, 0x2b } },
1057 	{ "invalid 4-byte f4 (11)", 4, { 0xf4, 0x86, 0x92, 0xc9 } },
1058 	{ "invalid 4-byte f4 (12)", 4, { 0xf4, 0x87, 0xbf, 0xfb } },
1059 	{ "invalid 4-byte (1)", 2, { 0xf1, 0xcc } },
1060 	{ "invalid 4-byte (2)", 2, { 0xf2, 0x17 } },
1061 	{ "invalid 4-byte (3)", 2, { 0xf3, 0xc0 } },
1062 	{ "invalid 4-byte (4)", 3, { 0xf2, 0x80, 0x23 } },
1063 	{ "invalid 4-byte (5)", 3, { 0xf1, 0x91, 0x42 } },
1064 	{ "invalid 4-byte (6)", 3, { 0xf2, 0xa2, 0xc0 } },
1065 	{ "invalid 4-byte (7)", 3, { 0xf3, 0xb3, 0xdf } },
1066 	{ "invalid 4-byte (9)", 4, { 0xf2, 0xa4, 0x81, 0x18 } },
1067 	{ "invalid 4-byte (10)", 4, { 0xf1, 0x95, 0x82, 0x2b } },
1068 	{ "invalid 4-byte (11)", 4, { 0xf2, 0x86, 0x92, 0xc9 } },
1069 	{ "invalid 4-byte (12)", 4, { 0xf3, 0xa7, 0xbf, 0xfb } },
1070 };
1071 
1072 static boolean_t
1073 c8rtomb_bad_utf8(void)
1074 {
1075 	boolean_t ret = B_TRUE;
1076 
1077 	for (size_t i = 0; i < ARRAY_SIZE(bad_utf); i++) {
1078 		const bad_utf8_t *t = &bad_utf[i];
1079 		char buf[MB_CUR_MAX];
1080 		mbstate_t mbs = { 0 };
1081 		boolean_t cont = B_TRUE;
1082 
1083 		/*
1084 		 * Only the last character in the list should cause a failure.
1085 		 * Make sure all the preceding ones are accepted, but don't
1086 		 * form a complete character.
1087 		 */
1088 		for (size_t c = 0; c < t->bu_nchar - 1; c++) {
1089 			size_t r = c8rtomb(buf, t->bu_chars[c], &mbs);
1090 			if (r != 0) {
1091 				warnx("%s: c8rtomb char[%zu] (0x%x) returned "
1092 				    "%zd, not 0", t->bu_desc, c,
1093 				    t->bu_chars[c], r);
1094 				ret = cont = B_FALSE;
1095 				break;
1096 			}
1097 		}
1098 
1099 		if (!cont)
1100 			continue;
1101 
1102 		size_t r = c8rtomb(buf, t->bu_chars[t->bu_nchar - 1], &mbs);
1103 		if (r != (size_t)-1) {
1104 			warnx("%s: c8rtomb final char (0x%x) returned %zu, "
1105 			    "not -1", t->bu_desc, t->bu_chars[t->bu_nchar - 1],
1106 			    r);
1107 			ret = B_FALSE;
1108 		} else if (errno != EILSEQ) {
1109 			warnx("%s: c8rtomb failed with %s, not %s", t->bu_desc,
1110 			    strerrorname_np(errno), strerrorname_np(EILSEQ));
1111 			ret = B_FALSE;
1112 		}
1113 	}
1114 
1115 	return (ret);
1116 }
1117 
1118 static boolean_t
1119 mbrtoc8_null(void)
1120 {
1121 	char8_t out = 0x23, exp = 0x23;
1122 	size_t len;
1123 	mbstate_t mbs = { 0 };
1124 	boolean_t ret = B_TRUE;
1125 
1126 	len = mbrtoc8(&out, NULL, 1, &mbs);
1127 	if (len != 0) {
1128 		warnx("partial mbrtoc8 returned %zu, not 0", len);
1129 		ret = B_FALSE;
1130 	}
1131 
1132 	if (out != exp) {
1133 		warnx("mbrtoc8 incorrectly wrote to char8_t value with "
1134 		    "null string, found 0x%x not 0x%x", out, exp);
1135 		ret = B_FALSE;
1136 	}
1137 
1138 	return (ret);
1139 }
1140 
1141 static boolean_t
1142 mbrtoc8_zero(void)
1143 {
1144 	char8_t out, exp = '\0';
1145 	size_t len;
1146 	mbstate_t mbs;
1147 	boolean_t ret = B_TRUE;
1148 
1149 	bzero(&mbs, sizeof (mbs));
1150 	len = mbrtoc8(&out, "", 1, &mbs);
1151 	if (len != 0) {
1152 		warnx("partial mbrtoc8 returned %zu, not 0", len);
1153 		ret = B_FALSE;
1154 	}
1155 
1156 	if (out != exp) {
1157 		warnx("mbrtoc8 converted character to 0x%x not 0x%x",
1158 		    out, exp);
1159 		ret = B_FALSE;
1160 	}
1161 
1162 	return (ret);
1163 }
1164 
1165 static boolean_t
1166 mbrtoc8_zero_len(void)
1167 {
1168 	char8_t out = 0x23, exp = 0x23;
1169 	size_t len;
1170 	mbstate_t mbs = { 0 };
1171 	boolean_t ret = B_TRUE;
1172 
1173 	len = mbrtoc8(&out, uchar_wide, 0, &mbs);
1174 	if (len != (size_t)-2) {
1175 		warnx("partial mbrtoc8 returned %zu, not -2", len);
1176 		ret = B_FALSE;
1177 	}
1178 
1179 	if (out != exp) {
1180 		warnx("mbrtoc8 incorrectly wrote to char8_t value with "
1181 		    "zero length string, found 0x%x not 0x%x", out, exp);
1182 		ret = B_FALSE;
1183 	}
1184 
1185 	return (ret);
1186 }
1187 
1188 static boolean_t
1189 c8rtomb_null(void)
1190 {
1191 	size_t len;
1192 	mbstate_t mbs = { 0 };
1193 	boolean_t ret = B_TRUE;
1194 
1195 	len = c8rtomb(NULL, 0x42, &mbs);
1196 	if (len != 1) {
1197 		warnx("c32rtomb returned %zu, expected %d", len, 1);
1198 		ret = B_FALSE;
1199 	}
1200 
1201 	return (ret);
1202 }
1203 
1204 
1205 typedef boolean_t (*uchar_test_f)(void);
1206 
1207 typedef struct uchar_test {
1208 	uchar_test_f	ut_func;
1209 	const char	*ut_test;
1210 	const char	*ut_locale;
1211 } uchar_test_t;
1212 
1213 static const uchar_test_t uchar_tests[] = {
1214 	{ mbrtoc32_ascii_mbstate, "mbrtoc32: ascii conversion" },
1215 	{ mbrtoc32_ascii_internal, "mbrtoc32: ascii conversion (internal "
1216 	    "mbstate_t)" },
1217 	{ mbrtoc32_ascii_mbstate, "mbrtoc32: ascii conversion (C locale)",
1218 	    "C" },
1219 	{ mbrtoc32_ascii_internal, "mbrtoc32: ascii conversion (internal "
1220 	    "mbstate_t) (C Locale)", "C" },
1221 	{ mbrtoc32_badseq_utf8, "mbrtoc32: bad locale sequence (UTF-8)" },
1222 	{ mbrtoc32_badseq_8859, "mbrtoc32: bad locale sequence (8859-1)" },
1223 	{ mbrtoc32_roundtrip, "mbrtoc32: round trip conversion" },
1224 	{ mbrtoc32_partial, "mbrtoc32: correctly consume partial sequences" },
1225 	{ mbrtoc32_zero, "mbrtoc32: correctly handle L'\\0'" },
1226 	{ mbrtoc32_zero, "mbrtoc32: correctly handle L'\\0' (C)", "C" },
1227 	{ mbrtoc32_zero, "mbrtoc32: correctly handle L'\\0' (8859-15)",
1228 	    "en_US.ISO8859-15" },
1229 	{ mbrtoc32_zero_len, "mbrtoc32: correctly handle length of zero" },
1230 	{ mbrtoc32_zero_len, "mbrtoc32: correctly handle length of zero (C)",
1231 	    "C" },
1232 	{ mbrtoc32_zero_len, "mbrtoc32: correctly handle length of zero "
1233 	    "(8859-1)", "en_US.ISO8859-15" },
1234 	{ mbrtoc32_null, "mbrtoc32: correctly handle null string" },
1235 	{ mbrtoc32_null, "mbrtoc32: correctly handle null string (C)", "C" },
1236 	{ mbrtoc32_null, "mbrtoc32: correctly handle null string (8859-15)",
1237 	    "en_US.ISO8859-15" },
1238 	{ mbrtoc32_iconv_8859, "mbrtoc32: correctly convert data from 8859-1",
1239 	    "en_US.ISO8859-1" },
1240 	{ mbrtoc32_iconv_euro, "mbrtoc32: correctly convert data from 8859-15",
1241 	    "en_US.ISO8859-15" },
1242 	{ mbrtoc16_ascii_mbstate, "mbrtoc16: ascii conversion" },
1243 	{ mbrtoc16_ascii_internal, "mbrtoc16: ascii conversion (internal "
1244 	    "mbstate_t)" },
1245 	{ mbrtoc16_ascii_mbstate, "mbrtoc16: ascii conversion (C)", "C" },
1246 	{ mbrtoc16_ascii_internal, "mbrtoc16: ascii conversion (internal "
1247 	    "mbstate_t) (C)", "C" },
1248 	{ mbrtoc16_null, "mbrtoc16: correctly handle null string" },
1249 	{ mbrtoc16_null, "mbrtoc16: correctly handle null string (C)", "C" },
1250 	{ mbrtoc16_null, "mbrtoc16: correctly handle null string (8859-15)",
1251 	    "en_US.ISO8859-15" },
1252 	{ mbrtoc16_zero, "mbrtoc16: correctly handle L'\\0'" },
1253 	{ mbrtoc16_zero, "mbrtoc16: correctly handle L'\\0' (C)", "C" },
1254 	{ mbrtoc16_zero, "mbrtoc16: correctly handle L'\\0' (8859-15)"
1255 	    "en_US.ISO8859-15" },
1256 	{ mbrtoc16_zero_len, "mbrtoc16: correctly handle length of zero" },
1257 	{ mbrtoc16_zero_len, "mbrtoc16: correctly handle length of zero (C)",
1258 	    "C" },
1259 	{ mbrtoc16_zero_len, "mbrtoc16: correctly handle length of zero "
1260 	    "(8859-15)", "en_US.ISO8859-15" },
1261 	{ mbrtoc16_roundtrip, "mbrtoc16: round trip conversion" },
1262 	{ mbrtoc16_partial, "mbrtoc16: correctly consume partial sequences" },
1263 	{ mbrtoc16_surrogate, "mbrtoc16: correctly generate surrogate pairs "
1264 	    "and round trip conversion" },
1265 	{ c32rtomb_eilseq_iso8859, "c32rtomb: character outside of locale is "
1266 	    "caught (8859-15)", "en_US.ISO8859-15" },
1267 	{ c16rtomb_eilseq_iso8859, "c16rtomb: character outside of locale is "
1268 	    "caught (8859-1)", "en_US.ISO8859-1" },
1269 	{ c32rtomb_eilseq_utf8, "c32rtomb: character outside of locale is "
1270 	    "caught (UTF-8)" },
1271 	{ c16rtomb_bad_first, "c16rtomb: bad first surrogate pair" },
1272 	{ c16rtomb_bad_second, "c16rtomb: bad second surrogate pair" },
1273 	{ c32rtomb_null, "c32rtomb: correctly handle null buffer" },
1274 	{ c32rtomb_null_euro, "c32rtomb: correctly handle null buffer "
1275 	    "(8859-15)", "en_US.ISO8859-15" },
1276 	{ c16rtomb_null, "c16rtomb: correctly handle null buffer" },
1277 	{ c16rtomb_null_euro, "c16rtomb: correctly handle null buffer "
1278 	    "(8859-15)", "en_US.ISO8859-15" },
1279 	{ c32rtomb_iconv_ascii, "c32rtomb: correctly convert data from C",
1280 	    "C" },
1281 	{ c32rtomb_iconv_euro, "c32rtomb: correctly convert data from 8859-15",
1282 	    "en_US.ISO8859-15" },
1283 	{ mbrtoc8_roundtrip_1b, "mbrtoc8 roundtrip ascii" },
1284 	{ mbrtoc8_roundtrip_1b, "mbrtoc8 roundtrip ascii (C)", "C" },
1285 	{ mbrtoc8_roundtrip_1b, "mbrtoc8 roundtrip ascii (8859-1)",
1286 	    "en_US.ISO8859-1" },
1287 	{ mbrtoc8_roundtrip_2b, "mbrtoc8 roundtrip 2b (UTF-8)" },
1288 	{ mbrtoc8_roundtrip_3b, "mbrtoc8 roundtrip 3b (UTF-8)" },
1289 	{ mbrtoc8_roundtrip_4b, "mbrtoc8 roundtrip 4b (UTF-8)" },
1290 	{ mbrtoc8_roundtrip_euro, "mbrtoc8 roundtrip conv 8859-15",
1291 	    "en_US.ISO8859-15" },
1292 	{ c8rtomb_bad_utf8, "invalid UTF-8 sequences" },
1293 	/* Even if these are representable, they should still fail */
1294 	{ c8rtomb_bad_utf8, "invalid UTF-8 sequences (C)", "C" },
1295 	{ mbrtoc8_null, "mbrtoc8: correctly handle null string" },
1296 	{ mbrtoc8_null, "mbrtoc8: correctly handle null string (C)", "C" },
1297 	{ mbrtoc8_null, "mbrtoc8: correctly handle null string (8859-15)",
1298 	    "en_US.ISO8859-15" },
1299 	{ c8rtomb_null, "c8rtomb: correctly handle null string" },
1300 	{ c8rtomb_null, "c8rtomb: correctly handle null string (C)", "C" },
1301 	{ c8rtomb_null, "c8rtomb: correctly handle null string (8859-15)",
1302 	    "en_US.ISO8859-15" },
1303 	{ mbrtoc8_zero, "mbrtoc8: correctly handle '\\0' string" },
1304 	{ mbrtoc8_zero, "mbrtoc8: correctly handle '\\0' string (C)", "C" },
1305 	{ mbrtoc8_zero, "mbrtoc8: correctly handle '\\0' string (8859-15)",
1306 	    "en_US.ISO8859-15" },
1307 
1308 	{ mbrtoc8_zero_len, "mbrtoc8: correctly handle zero length string" },
1309 	{ mbrtoc8_zero_len, "mbrtoc8: correctly handle zero length string (C)",
1310 	    "C" },
1311 	{ mbrtoc8_zero_len, "mbrtoc8: correctly handle zero length string "
1312 	    "(8859-15)", "en_US.ISO8859-15" },
1313 };
1314 
1315 int
1316 main(void)
1317 {
1318 	uint_t i;
1319 	uint_t passes = 0;
1320 	uint_t ntests = ARRAY_SIZE(uchar_tests);
1321 
1322 	for (i = 0; i < ntests; i++) {
1323 		boolean_t r;
1324 
1325 		/*
1326 		 * Default to a standard UTF-8 locale if none is requested by
1327 		 * the test.
1328 		 */
1329 		if (uchar_tests[i].ut_locale != NULL) {
1330 			update_locale(uchar_tests[i].ut_locale);
1331 		} else {
1332 			update_locale("en_US.UTF-8");
1333 		}
1334 
1335 		r = uchar_tests[i].ut_func();
1336 		(void) fprintf(stderr, "TEST %s: %s\n", r ? "PASSED" : "FAILED",
1337 		    uchar_tests[i].ut_test);
1338 		if (r) {
1339 			passes++;
1340 		}
1341 	}
1342 
1343 	(void) printf("%d/%d test%s passed\n", passes, ntests,
1344 	    passes > 1 ? "s" : "");
1345 	return (passes == ntests ? EXIT_SUCCESS : EXIT_FAILURE);
1346 
1347 }
1348