mirror of
https://github.com/open62541/open62541.git
synced 2026-10-29 05:11:36 +08:00
344 lines
11 KiB
C
344 lines
11 KiB
C
/* 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 <check.h>
|
|
#include "utf8.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
/* 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;
|
|
}
|