/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include "utf8.h" #include #include /* utf8_to_codepoint tests */ START_TEST(decodeAscii) { unsigned codepoint = 0; unsigned len = utf8_to_codepoint((const unsigned char *)"A", 1, &codepoint); ck_assert_uint_eq(len, 1); ck_assert_uint_eq(codepoint, 0x41); } END_TEST START_TEST(decodeAsciiNull) { unsigned codepoint = 42; unsigned char null_byte = 0; unsigned len = utf8_to_codepoint(&null_byte, 1, &codepoint); ck_assert_uint_eq(len, 1); ck_assert_uint_eq(codepoint, 0); } END_TEST START_TEST(decodeAsciiMax) { unsigned codepoint = 0; unsigned char byte = 0x7F; unsigned len = utf8_to_codepoint(&byte, 1, &codepoint); ck_assert_uint_eq(len, 1); ck_assert_uint_eq(codepoint, 0x7F); } END_TEST START_TEST(decode2Byte) { /* U+00DF = ß = 0xC3 0x9F */ unsigned char str[] = {0xC3, 0x9F}; unsigned codepoint = 0; unsigned len = utf8_to_codepoint(str, 2, &codepoint); ck_assert_uint_eq(len, 2); ck_assert_uint_eq(codepoint, 0x00DF); } END_TEST START_TEST(decode2ByteMin) { /* U+0080 = 0xC2 0x80 (smallest 2-byte) */ unsigned char str[] = {0xC2, 0x80}; unsigned codepoint = 0; unsigned len = utf8_to_codepoint(str, 2, &codepoint); ck_assert_uint_eq(len, 2); ck_assert_uint_eq(codepoint, 0x0080); } END_TEST START_TEST(decode2ByteMax) { /* U+07FF = 0xDF 0xBF (largest 2-byte) */ unsigned char str[] = {0xDF, 0xBF}; unsigned codepoint = 0; unsigned len = utf8_to_codepoint(str, 2, &codepoint); ck_assert_uint_eq(len, 2); ck_assert_uint_eq(codepoint, 0x07FF); } END_TEST START_TEST(decode3Byte) { /* U+4E16 = δΈ– = 0xE4 0xB8 0x96 */ unsigned char str[] = {0xE4, 0xB8, 0x96}; unsigned codepoint = 0; unsigned len = utf8_to_codepoint(str, 3, &codepoint); ck_assert_uint_eq(len, 3); ck_assert_uint_eq(codepoint, 0x4E16); } END_TEST START_TEST(decode3ByteMin) { /* U+0800 = 0xE0 0xA0 0x80 (smallest 3-byte) */ unsigned char str[] = {0xE0, 0xA0, 0x80}; unsigned codepoint = 0; unsigned len = utf8_to_codepoint(str, 3, &codepoint); ck_assert_uint_eq(len, 3); ck_assert_uint_eq(codepoint, 0x0800); } END_TEST START_TEST(decode3ByteMax) { /* U+FFFF = 0xEF 0xBF 0xBF (largest 3-byte) */ unsigned char str[] = {0xEF, 0xBF, 0xBF}; unsigned codepoint = 0; unsigned len = utf8_to_codepoint(str, 3, &codepoint); ck_assert_uint_eq(len, 3); ck_assert_uint_eq(codepoint, 0xFFFF); } END_TEST START_TEST(decode4Byte) { /* U+1F600 = πŸ˜€ = 0xF0 0x9F 0x98 0x80 */ unsigned char str[] = {0xF0, 0x9F, 0x98, 0x80}; unsigned codepoint = 0; unsigned len = utf8_to_codepoint(str, 4, &codepoint); ck_assert_uint_eq(len, 4); ck_assert_uint_eq(codepoint, 0x1F600); } END_TEST START_TEST(decode4ByteMin) { /* U+10000 = 0xF0 0x90 0x80 0x80 (smallest 4-byte) */ unsigned char str[] = {0xF0, 0x90, 0x80, 0x80}; unsigned codepoint = 0; unsigned len = utf8_to_codepoint(str, 4, &codepoint); ck_assert_uint_eq(len, 4); ck_assert_uint_eq(codepoint, 0x10000); } END_TEST START_TEST(decode4ByteMax) { /* U+10FFFF = 0xF4 0x8F 0xBF 0xBF (largest valid Unicode) */ unsigned char str[] = {0xF4, 0x8F, 0xBF, 0xBF}; unsigned codepoint = 0; unsigned len = utf8_to_codepoint(str, 4, &codepoint); ck_assert_uint_eq(len, 4); ck_assert_uint_eq(codepoint, 0x10FFFF); } END_TEST START_TEST(decodeEmptyInput) { unsigned codepoint = 42; unsigned len = utf8_to_codepoint((const unsigned char *)"", 0, &codepoint); ck_assert_uint_eq(len, 0); } END_TEST START_TEST(decodeContinuationAtStart) { /* 0x80 is a continuation byte, invalid at start */ unsigned char str[] = {0x80}; unsigned codepoint = 0; unsigned len = utf8_to_codepoint(str, 1, &codepoint); ck_assert_uint_eq(len, 0); } END_TEST START_TEST(decodeContinuationC1) { /* 0xC1 is the max overlong 2-byte prefix β†’ rejected */ unsigned char str[] = {0xC1, 0x80}; unsigned codepoint = 0; unsigned len = utf8_to_codepoint(str, 2, &codepoint); ck_assert_uint_eq(len, 0); } END_TEST START_TEST(decodeOverlong2Byte) { /* 0xC0 0x80 encodes U+0000 as 2 bytes (overlong) */ unsigned char str[] = {0xC0, 0x80}; unsigned codepoint = 0; unsigned len = utf8_to_codepoint(str, 2, &codepoint); ck_assert_uint_eq(len, 0); } END_TEST START_TEST(decodeTruncated2Byte) { /* 2-byte sequence with only 1 byte available */ unsigned char str[] = {0xC3}; unsigned codepoint = 0; unsigned len = utf8_to_codepoint(str, 1, &codepoint); ck_assert_uint_eq(len, 0); } END_TEST START_TEST(decodeTruncated3Byte) { /* 3-byte sequence with only 2 bytes available */ unsigned char str[] = {0xE4, 0xB8}; unsigned codepoint = 0; unsigned len = utf8_to_codepoint(str, 2, &codepoint); ck_assert_uint_eq(len, 0); } END_TEST START_TEST(decodeTruncated4Byte) { /* 4-byte sequence with only 3 bytes available */ unsigned char str[] = {0xF0, 0x9F, 0x98}; unsigned codepoint = 0; unsigned len = utf8_to_codepoint(str, 3, &codepoint); ck_assert_uint_eq(len, 0); } END_TEST START_TEST(decodeInvalidByte) { /* 0xF5+ is invalid UTF-8 start byte */ unsigned char str[] = {0xF5, 0x80, 0x80, 0x80}; unsigned codepoint = 0; unsigned len = utf8_to_codepoint(str, 4, &codepoint); ck_assert_uint_eq(len, 0); } END_TEST START_TEST(decodeBadContinuation) { /* Valid start byte but invalid continuation: * 0xC3 followed by 0x00 (not a continuation byte) */ unsigned char str[] = {0xC3, 0x00}; unsigned codepoint = 0; unsigned len = utf8_to_codepoint(str, 2, &codepoint); ck_assert_uint_eq(len, 0); } END_TEST /* utf8_from_codepoint tests (inline in header) */ START_TEST(encodeAscii) { unsigned char buf[4]; unsigned len = utf8_from_codepoint(buf, 0x41); ck_assert_uint_eq(len, 1); ck_assert_uint_eq(buf[0], 'A'); } END_TEST START_TEST(encode2ByteCP) { unsigned char buf[4]; unsigned len = utf8_from_codepoint(buf, 0x00DF); ck_assert_uint_eq(len, 2); ck_assert_uint_eq(buf[0], 0xC3); ck_assert_uint_eq(buf[1], 0x9F); } END_TEST START_TEST(encode3ByteCP) { unsigned char buf[4]; unsigned len = utf8_from_codepoint(buf, 0x4E16); ck_assert_uint_eq(len, 3); ck_assert_uint_eq(buf[0], 0xE4); ck_assert_uint_eq(buf[1], 0xB8); ck_assert_uint_eq(buf[2], 0x96); } END_TEST START_TEST(encode4ByteCP) { unsigned char buf[4]; unsigned len = utf8_from_codepoint(buf, 0x1F600); ck_assert_uint_eq(len, 4); ck_assert_uint_eq(buf[0], 0xF0); ck_assert_uint_eq(buf[1], 0x9F); ck_assert_uint_eq(buf[2], 0x98); ck_assert_uint_eq(buf[3], 0x80); } END_TEST START_TEST(encodeOutOfRange) { unsigned char buf[4]; unsigned len = utf8_from_codepoint(buf, 0x110000); ck_assert_uint_eq(len, 0); } END_TEST START_TEST(encodeMaxValid) { unsigned char buf[4]; unsigned len = utf8_from_codepoint(buf, 0x10FFFF); ck_assert_uint_eq(len, 4); } END_TEST /* utf8_length tests (inline in header) */ START_TEST(lengthAscii) { ck_assert_uint_eq(utf8_length(0x41), 1); ck_assert_uint_eq(utf8_length(0x7F), 1); ck_assert_uint_eq(utf8_length(0x00), 1); } END_TEST START_TEST(length2Byte) { ck_assert_uint_eq(utf8_length(0x80), 2); ck_assert_uint_eq(utf8_length(0x7FF), 2); } END_TEST START_TEST(length3Byte) { ck_assert_uint_eq(utf8_length(0x800), 3); ck_assert_uint_eq(utf8_length(0xFFFF), 3); } END_TEST START_TEST(length4Byte) { ck_assert_uint_eq(utf8_length(0x10000), 4); ck_assert_uint_eq(utf8_length(0x10FFFF), 4); } END_TEST START_TEST(lengthOutOfRange) { ck_assert_uint_eq(utf8_length(0x110000), 0); } END_TEST /* Roundtrip tests */ START_TEST(roundtripAll) { unsigned codepoints[] = { 0x00, 0x41, 0x7F, /* ASCII */ 0x80, 0xDF, 0x7FF, /* 2-byte */ 0x800, 0x4E16, 0xFFFF, /* 3-byte */ 0x10000, 0x1F600, 0x10FFFF /* 4-byte */ }; for(size_t i = 0; i < sizeof(codepoints)/sizeof(codepoints[0]); i++) { unsigned char buf[4]; unsigned enc_len = utf8_from_codepoint(buf, codepoints[i]); ck_assert_uint_gt(enc_len, 0); unsigned decoded = 0; unsigned dec_len = utf8_to_codepoint(buf, enc_len, &decoded); ck_assert_uint_eq(dec_len, enc_len); ck_assert_uint_eq(decoded, codepoints[i]); } } END_TEST static Suite *testSuite_utf8(void) { TCase *tc_decode = tcase_create("utf8_decode"); tcase_add_test(tc_decode, decodeAscii); tcase_add_test(tc_decode, decodeAsciiNull); tcase_add_test(tc_decode, decodeAsciiMax); tcase_add_test(tc_decode, decode2Byte); tcase_add_test(tc_decode, decode2ByteMin); tcase_add_test(tc_decode, decode2ByteMax); tcase_add_test(tc_decode, decode3Byte); tcase_add_test(tc_decode, decode3ByteMin); tcase_add_test(tc_decode, decode3ByteMax); tcase_add_test(tc_decode, decode4Byte); tcase_add_test(tc_decode, decode4ByteMin); tcase_add_test(tc_decode, decode4ByteMax); tcase_add_test(tc_decode, decodeEmptyInput); tcase_add_test(tc_decode, decodeContinuationAtStart); tcase_add_test(tc_decode, decodeContinuationC1); tcase_add_test(tc_decode, decodeOverlong2Byte); tcase_add_test(tc_decode, decodeTruncated2Byte); tcase_add_test(tc_decode, decodeTruncated3Byte); tcase_add_test(tc_decode, decodeTruncated4Byte); tcase_add_test(tc_decode, decodeInvalidByte); tcase_add_test(tc_decode, decodeBadContinuation); TCase *tc_encode = tcase_create("utf8_encode"); tcase_add_test(tc_encode, encodeAscii); tcase_add_test(tc_encode, encode2ByteCP); tcase_add_test(tc_encode, encode3ByteCP); tcase_add_test(tc_encode, encode4ByteCP); tcase_add_test(tc_encode, encodeOutOfRange); tcase_add_test(tc_encode, encodeMaxValid); TCase *tc_length = tcase_create("utf8_length"); tcase_add_test(tc_length, lengthAscii); tcase_add_test(tc_length, length2Byte); tcase_add_test(tc_length, length3Byte); tcase_add_test(tc_length, length4Byte); tcase_add_test(tc_length, lengthOutOfRange); TCase *tc_roundtrip = tcase_create("utf8_roundtrip"); tcase_add_test(tc_roundtrip, roundtripAll); Suite *s = suite_create("Test UTF-8 encoding/decoding"); suite_add_tcase(s, tc_decode); suite_add_tcase(s, tc_encode); suite_add_tcase(s, tc_length); suite_add_tcase(s, tc_roundtrip); return s; } int main(void) { int number_failed = 0; Suite *s = testSuite_utf8(); SRunner *sr = srunner_create(s); srunner_set_fork_status(sr, CK_NOFORK); srunner_run_all(sr, CK_NORMAL); number_failed += srunner_ntests_failed(sr); srunner_free(sr); return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; }