303 lines
17 KiB
C
303 lines
17 KiB
C
#include "c_utf8.h"
|
||
#include <stdlib.h>
|
||
#include <stdio.h>
|
||
|
||
/* --- 单元测试模块集成 --- */
|
||
#define RUN_TEST(test_case, name) \
|
||
do { \
|
||
printf("[RUN] %s... ", name); \
|
||
if (test_case) { \
|
||
printf("\033[32mPASSED\033[0m\n"); \
|
||
} else { \
|
||
printf("\033[31mFAILED\033[0m (%s:%d)\n", __FILE__, __LINE__); \
|
||
return C_ERR_FAIL; \
|
||
} \
|
||
} while(0)
|
||
|
||
c_err_t c_Utf8String_UnitTest(void) {
|
||
printf("==================================================\n");
|
||
printf(" STARTING C_UTF8STRING UNIT TESTING \n");
|
||
printf("==================================================\n");
|
||
|
||
const char* sample = "NLP大模型_2026"; // 包含 3个大写英文、3个汉字、1个下划线、4个数字 = 11个字符
|
||
|
||
/* 1. c_utf8_strlen 测试 */
|
||
// 传统的 strlen(sample) 会返回 3 + 3*3 + 1 + 4 = 17 字节
|
||
RUN_TEST(c_utf8_strlen(sample) == 11, "c_utf8_strlen correctly counts character points");
|
||
RUN_TEST(c_utf8_strlen("") == 0, "c_utf8_strlen handles empty strings");
|
||
|
||
/* 2. c_utf8_strchr 测试 */
|
||
const char* find_eng = c_utf8_strchr(sample, "P");
|
||
const char* find_chn = c_utf8_strchr(sample, "模");
|
||
const char* find_none = c_utf8_strchr(sample, "国");
|
||
|
||
RUN_TEST(find_eng != NULL && *find_eng == 'P', "c_utf8_strchr locate ASCII element");
|
||
// "模" 在 "模型_2026" 头部,其后紧跟 "型"
|
||
RUN_TEST(find_chn != NULL && strncmp(find_chn, "模型", 6) == 0, "c_utf8_strchr locate multi-byte Chinese word");
|
||
RUN_TEST(find_none == NULL, "c_utf8_strchr returns NULL for non-existing chars");
|
||
|
||
/* 3. c_utf8_strncpy 安全截断测试 */
|
||
char dest_buf[64];
|
||
// 截断前 5 个字符 -> "NLP大模" (绝不会出现半个汉字或乱码断裂)
|
||
c_utf8_strncpy(dest_buf, sample, 5);
|
||
RUN_TEST(c_utf8_strlen(dest_buf) == 5, "c_utf8_strncpy slices correct character width");
|
||
RUN_TEST(strcmp(dest_buf, "NLP大模") == 0, "c_utf8_strncpy safe boundary isolation checked");
|
||
|
||
/* 4. c_utf8_strncmp 字符匹配测试 */
|
||
RUN_TEST(c_utf8_strncmp("自然语言", "自然选择", 2) == 0, "c_utf8_strncmp matches first 2 shared Chinese words");
|
||
RUN_TEST(c_utf8_strncmp("自然语言", "自然选择", 3) != 0, "c_utf8_strncmp detects variance at 3rd word slot");
|
||
|
||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||
/* */
|
||
/* 5. Lowercase Transform Verification (c_utf8_tolower) */
|
||
char case_buf[64] = "NLP大模型_2026_Go!";
|
||
c_utf8_tolower(case_buf);
|
||
RUN_TEST(strcmp(case_buf, "nlp大模型_2026_go!") == 0, "c_utf8_tolower translates ASCII while isolating Chinese layout characters");
|
||
|
||
// Cyrillic multi-byte letter test ("П" -> 0xD0 0x9F converted down to "п" -> 0xD0 0xBF)
|
||
char cyrillic_buf[8] = { (char)0xD0, (char)0x9F, '\0' };
|
||
c_utf8_tolower(cyrillic_buf);
|
||
RUN_TEST((unsigned char)cyrillic_buf[1] == 0xBF, "c_utf8_tolower successfully transforms multi-byte Cyrillic characters");
|
||
|
||
/* 6. Concatenation Verification (c_utf8_strcat) */
|
||
char cat_dest[32] = "自然";
|
||
c_utf8_strcat(cat_dest, "语言");
|
||
RUN_TEST(strcmp(cat_dest, "自然语言") == 0, "c_utf8_strcat appends string tokens cleanly");
|
||
RUN_TEST(c_utf8_strlen(cat_dest) == 4, "Post-concatenation size checks out at 4 characters total");
|
||
|
||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||
/* */
|
||
|
||
/* 24. Uppercase Transform Verification (c_utf8_toupper) */
|
||
char upper_buf[] = "nlp大模型_2026_go!";
|
||
c_utf8_toupper(upper_buf);
|
||
RUN_TEST(strcmp(upper_buf, "NLP大模型_2026_GO!") == 0, "c_utf8_toupper translates ASCII while isolating Chinese layout characters");
|
||
|
||
// Cyrillic multi-byte lowercase letter test ("п" -> 0xD0 0xBF converted up to "П" -> 0xD0 0x9F)
|
||
char cyr_upper_buf[] = { (char)0xD0, (char)0xBF, '\0' };
|
||
c_utf8_toupper(cyr_upper_buf);
|
||
RUN_TEST((unsigned char)cyr_upper_buf[1] == 0x9F, "c_utf8_toupper successfully transforms multi-byte Cyrillic lowercase characters");
|
||
|
||
|
||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||
/* */
|
||
|
||
/* 7. Character Scanning Verification (c_utf8_strchr) */
|
||
const char* scan_base = "NLP大模型_2026";
|
||
|
||
/* 8. Substring Scanning Verification (c_utf8_strstr) */
|
||
const char* match_str = c_utf8_strstr(scan_base, "大模型");
|
||
const char* miss_str = c_utf8_strstr(scan_base, "小模型");
|
||
const char* empty_str = c_utf8_strstr(scan_base, "");
|
||
|
||
RUN_TEST(match_str != NULL && strncmp(match_str, "大模型_2026", 14) == 0, "c_utf8_strstr fetches multi-byte string locations");
|
||
RUN_TEST(miss_str == NULL, "c_utf8_strstr returns NULL cleanly on substring mismatch");
|
||
RUN_TEST(empty_str == scan_base, "c_utf8_strstr returns parent head context given empty needle input");
|
||
|
||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||
/* */
|
||
|
||
/* 16. Reverse Scanning Verification (c_utf8_strrchr) */
|
||
const char* r_base = "自然_模型_自然_2026";
|
||
const char* last_match = c_utf8_strrchr(r_base, "自然");
|
||
const char* first_match = c_utf8_strchr(r_base, "自然");
|
||
|
||
RUN_TEST(last_match != NULL && last_match != first_match, "c_utf8_strrchr skips the first match to pull the last occurrence");
|
||
RUN_TEST(strncmp(last_match, "自然_2026", 11) == 0, "c_utf8_strrchr locates the correct trailing block address");
|
||
|
||
/* 17. Reentrant Tokenization Verification (c_utf8_strtok) */
|
||
char token_source[] = ",NLP,,大模型,2026,"; // Multi-byte Chinese comma delimiters
|
||
const char* delimiters = ",";
|
||
char* save_context = NULL;
|
||
|
||
// First Call
|
||
char* token = c_utf8_strtok(token_source, delimiters, &save_context);
|
||
RUN_TEST(token != NULL && strcmp(token, "NLP") == 0, "c_utf8_strtok parses the first token and skips leading delims");
|
||
|
||
// Second Call (Pass NULL to proceed)
|
||
token = c_utf8_strtok(NULL, delimiters, &save_context);
|
||
RUN_TEST(token != NULL && strcmp(token, "大模型") == 0, "c_utf8_strtok correctly extracts multi-byte Chinese '大模型'");
|
||
|
||
// Third Call
|
||
token = c_utf8_strtok(NULL, delimiters, &save_context);
|
||
RUN_TEST(token != NULL && strcmp(token, "2026") == 0, "c_utf8_strtok extracts '2026'");
|
||
|
||
// Fourth Call - Termination
|
||
token = c_utf8_strtok(NULL, delimiters, &save_context);
|
||
RUN_TEST(token == NULL, "c_utf8_strtok returns NULL cleanly when parsing is finished");
|
||
|
||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||
/* */
|
||
|
||
/* 25. Case-Insensitive Bounded Comparison Verification (c_utf8_strncasecmp) */
|
||
// Test Case 1: Simple matching mixed-case strings
|
||
RUN_TEST(c_utf8_strncasecmp("Nlp大模型", "NLP大模型", 6) == 0, "c_utf8_strncasecmp matches mixed cases up to 6 characters");
|
||
|
||
// Test Case 2: Verification of prefix character restrictions bounding
|
||
RUN_TEST(c_utf8_strncasecmp("NLP大模型_v2", "nlp大模型_v3", 6) == 0, "c_utf8_strncasecmp returns 0 if differences fall past the character count limit");
|
||
RUN_TEST(c_utf8_strncasecmp("NLP大模型_v2", "nlp大模型_v3", 9) != 0, "c_utf8_strncasecmp registers structural variance when character limits cover differences");
|
||
|
||
// Test Case 3: Mixed language case sorting behavior
|
||
RUN_TEST(c_utf8_strncasecmp("自然语言NLP", "自然语言nlp", 7) == 0, "c_utf8_strncasecmp handles matching trailing ASCII case differences after multi-byte blocks");
|
||
|
||
|
||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||
/* */
|
||
|
||
/* 18. UTF-8 to Unicode Codepoint Decoding Verification */
|
||
const char* utf8_src = "中!🚀"; // "中" (3 bytes), "!" (1 byte), "🚀" Emoji (4 bytes)
|
||
c_ucs4_t cp = 0;
|
||
c_size_t bytes_step = 0;
|
||
|
||
// Decode first character ("中" -> Expected Codepoint: U+4E2D)
|
||
c_err_t err = c_utf8_to_unicode(utf8_src, &cp, &bytes_step);
|
||
RUN_TEST(err == C_ERR_OK && bytes_step == 3, "c_utf8_to_unicode processes 3-byte Chinese characters");
|
||
RUN_TEST(cp == 0x4E2D, "Decoded codepoint matches U+4E2D ('中') accurately");
|
||
|
||
// Decode next sequence ("!" -> Expected Codepoint: U+0021)
|
||
err = c_utf8_to_unicode(utf8_src + bytes_step, &cp, &bytes_step);
|
||
RUN_TEST(err == C_ERR_OK && bytes_step == 1, "c_utf8_to_unicode processes 1-byte ASCII markers");
|
||
RUN_TEST(cp == 0x0021, "Decoded codepoint matches U+0021 ('!')");
|
||
|
||
// Decode next sequence (Rocket Emoji "🚀" -> Expected Codepoint: U+1F680)
|
||
err = c_utf8_to_unicode(utf8_src + 4, &cp, &bytes_step); // Skip forward 4 bytes total
|
||
RUN_TEST(err == C_ERR_OK && bytes_step == 4, "c_utf8_to_unicode decodes 4-byte astral plane emojis");
|
||
RUN_TEST(cp == 0x1F680, "Decoded codepoint matches U+1F680 ('🚀')");
|
||
|
||
/* 19. Unicode Codepoint to UTF-8 Encoding Verification */
|
||
char encode_buf[8];
|
||
c_size_t written_len = 0;
|
||
|
||
// Encode U+4E2D back to UTF-8
|
||
err = c_utf8_from_unicode(0x4E2D, encode_buf, &written_len);
|
||
RUN_TEST(err == C_ERR_OK && written_len == 3, "c_utf8_from_unicode encodes U+4E2D back into 3 bytes");
|
||
RUN_TEST(strcmp(encode_buf, "中") == 0, "Encoded string content matches '中' flawlessly");
|
||
|
||
// Encode U+1F680 back to UTF-8
|
||
err = c_utf8_from_unicode(0x1F680, encode_buf, &written_len);
|
||
RUN_TEST(err == C_ERR_OK && written_len == 4, "c_utf8_from_unicode encodes U+1F680 back into 4 bytes");
|
||
|
||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||
/* */
|
||
|
||
/* 20. UTF-8 String Stream <-> Unicode Array Conversions Verification */
|
||
const char* mixed_sentence = "NLP大模型!🚀"; // Length: 3 ASCII, 3 Chinese (9B), 1 ASCII, 1 Emoji (4B) = 8 characters total
|
||
c_ucs4_t uni_array[16];
|
||
c_size_t total_chars = 0;
|
||
|
||
// Test Point 1: Decode stream into codepoint container array
|
||
err = c_utf8_to_unicode_array(mixed_sentence, uni_array, 16, &total_chars);
|
||
RUN_TEST(err == C_ERR_OK && total_chars == 8, "c_utf8_to_unicode_array maps complex string streams into separate integer points");
|
||
RUN_TEST(uni_array[0] == 'N' && uni_array[3] == 0x5927 && uni_array[7] == 0x1F680, "Decoded array positions hold proper character points ('N', '大', '🚀')");
|
||
|
||
// Test Point 2: Trigger array capacity guard protection
|
||
c_ucs4_t tight_array[4];
|
||
err = c_utf8_to_unicode_array(mixed_sentence, tight_array, 4, &total_chars);
|
||
RUN_TEST(err == C_ERR_PARAM && total_chars == 4, "c_utf8_to_unicode_array safely blocks operations and returns current progress on buffer limit hits");
|
||
|
||
// Test Point 3: Reverse operation - Encode codepoint array back into native UTF-8 string layout
|
||
char reconstructed_str[64];
|
||
c_size_t written_bytes = 0;
|
||
err = c_utf8_from_unicode_array(uni_array, 8, reconstructed_str, sizeof(reconstructed_str), &written_bytes);
|
||
RUN_TEST(err == C_ERR_OK && written_bytes == 17, "c_utf8_from_unicode_array successfully packs codepoints back into 17 raw bytes");
|
||
RUN_TEST(strcmp(reconstructed_str, mixed_sentence) == 0, "Reconstructed stream data matches original expression perfectly");
|
||
|
||
|
||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||
/* */
|
||
|
||
/* 21. UTF-8 <-> UTF-16 Array Conversions Verification */
|
||
const char* utf8_sentence = "NLP大模型!🚀"; // Contains ASCII, 3-byte Chinese, and a 4-byte Astral Plane Emoji
|
||
c_uint16_t utf16_array[32];
|
||
c_size_t units_written = 0;
|
||
|
||
// Test Point 1: Convert UTF-8 stream to UTF-16 code units
|
||
// "NLP" (3 units) + "大模型" (3 units) + "!" (1 unit) + "🚀" (Surrogate pair = 2 units) = 9 units total
|
||
err = c_utf8_to_utf16(utf8_sentence, utf16_array, 32, &units_written);
|
||
RUN_TEST(err == C_ERR_OK && units_written == 9, "c_utf8_to_utf16 successfully packs characters including astral planes");
|
||
RUN_TEST(utf16_array[0] == 'N' && utf16_array[3] == 0x5927, "Verify standard BMP mapping inside UTF-16 array structure");
|
||
// Verify high and low surrogate points for the Rocket Emoji 🚀
|
||
RUN_TEST(utf16_array[7] == 0xD83D && utf16_array[8] == 0xDE80, "Verify surrogate pair matching (0xD83D 0xDE80) for U+1F680");
|
||
|
||
// Test Point 2: Convert UTF-16 array back to native UTF-8 string layout
|
||
char reconstructed_utf8[64];
|
||
c_size_t bytes_written_utf8 = 0;
|
||
err = c_utf8_from_utf16(utf16_array, units_written, reconstructed_utf8, sizeof(reconstructed_utf8), &bytes_written_utf8);
|
||
RUN_TEST(err == C_ERR_OK && bytes_written_utf8 == 17, "c_utf8_from_utf16 unpacks units back into 17 raw bytes");
|
||
RUN_TEST(strcmp(reconstructed_utf8, utf8_sentence) == 0, "Reconstructed UTF-8 matches the original string precisely");
|
||
|
||
// Test Point 3: Malformed Surrogate Pair Detection
|
||
c_uint16_t malformed_utf16[] = { 0xD83D, 'A' }; // High surrogate followed by a literal letter (invalid)
|
||
char error_buf[16];
|
||
c_size_t err_bytes = 0;
|
||
err = c_utf8_from_utf16(malformed_utf16, 2, error_buf, sizeof(error_buf), &err_bytes);
|
||
RUN_TEST(err == C_ERR_PARAM, "c_utf8_from_utf16 successfully flags and rejects malformed/orphaned surrogate code units");
|
||
|
||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||
/* */
|
||
|
||
/* 22. UTF-16 Endianness Byte-Swapping Verification (c_utf16_swap_endian) */
|
||
c_uint16_t sample_utf16[] = { 0xD83D, 0xDE80, 0x5927 }; // 🚀 and 大 in standard host endianness
|
||
c_size_t array_len = sizeof(sample_utf16) / sizeof(sample_utf16[0]);
|
||
|
||
// Test Point 1: Guard against NULL parameters
|
||
RUN_TEST(c_utf16_swap_endian(NULL, array_len) == C_ERR_PARAM, "c_utf16_swap_endian safely rejects NULL pointer arrays");
|
||
|
||
// Test Point 2: Perform initial byte-swap transformation
|
||
err = c_utf16_swap_endian(sample_utf16, array_len);
|
||
RUN_TEST(err == C_ERR_OK, "c_utf16_swap_endian executes successfully");
|
||
// 0xD83D -> 0x3DD8, 0xDE80 -> 0x80DE, 0x5927 -> 0x2759
|
||
RUN_TEST(sample_utf16[0] == 0x3DD8, "First code unit correctly bit-swapped (0xD83D -> 0x3DD8)");
|
||
RUN_TEST(sample_utf16[1] == 0x80DE, "Second code unit correctly bit-swapped (0xDE80 -> 0x80DE)");
|
||
RUN_TEST(sample_utf16[2] == 0x2759, "Third code unit correctly bit-swapped (0x5927 -> 0x2759)");
|
||
|
||
// Test Point 3: Swap back to restore the original host endianness values
|
||
err = c_utf16_swap_endian(sample_utf16, array_len);
|
||
RUN_TEST(err == C_ERR_OK, "c_utf16_swap_endian reverts state back on secondary execution pass");
|
||
RUN_TEST(sample_utf16[0] == 0xD83D && sample_utf16[1] == 0xDE80 && sample_utf16[2] == 0x5927, "Original internal value contexts perfectly preserved");
|
||
|
||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||
/* */
|
||
|
||
/* 23. UTF-16 Single-Char Unicode Conversions Verification */
|
||
c_ucs4_t decoded_cp = 0;
|
||
c_size_t units_moved = 0;
|
||
|
||
// Test Point 1: Decode Standard BMP Character ('大' -> U+5927)
|
||
c_uint16_t bmp_sample[] = { 0x5927 };
|
||
err = c_utf16_to_unicode(bmp_sample, 1, &decoded_cp, &units_moved);
|
||
RUN_TEST(err == C_ERR_OK && units_moved == 1, "c_utf16_to_unicode processes BMP code units");
|
||
RUN_TEST(decoded_cp == 0x5927, "Decoded BMP matches expected U+5927 successfully");
|
||
|
||
// Test Point 2: Decode Surrogate Pair Character (Rocket Emoji '🚀' -> High: 0xD83D, Low: 0xDE80)
|
||
c_uint16_t astral_sample[] = { 0xD83D, 0xDE80 };
|
||
err = c_utf16_to_unicode(astral_sample, 2, &decoded_cp, &units_moved);
|
||
RUN_TEST(err == C_ERR_OK && units_moved == 2, "c_utf16_to_unicode correctly handles surrogate pairs");
|
||
RUN_TEST(decoded_cp == 0x1F680, "Decoded Astral match returns U+1F680 ('🚀')");
|
||
|
||
// Test Point 3: Error validation protection against malformed surrogate chains
|
||
c_uint16_t isolated_high[] = { 0xD83D }; // Lacks matching trailing block
|
||
RUN_TEST(c_utf16_to_unicode(isolated_high, 1, &decoded_cp, &units_moved) == C_ERR_PARAM, "c_utf16_to_unicode rejects truncated surrogate sequences");
|
||
|
||
// Test Point 4: Encode Astral Plane back to UTF-16 code units
|
||
c_uint16_t encode_units[2];
|
||
units_written = 0;
|
||
err = c_utf16_from_unicode(0x1F680, encode_units, 2, &units_written);
|
||
RUN_TEST(err == C_ERR_OK && units_written == 2, "c_utf16_from_unicode builds surrogate pairs for plane codepoints");
|
||
RUN_TEST(encode_units[0] == 0xD83D && encode_units[1] == 0xDE80, "Generated high and low values match standard encoding targets");
|
||
|
||
|
||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||
/* */
|
||
|
||
printf("==================================================\n");
|
||
printf("\033[32mSUCCESS: ALL UTF-8 COMPATIBLE INTERFACES PASSED!\033[0m\n");
|
||
printf("==================================================\n");
|
||
return C_ERR_OK;
|
||
}
|
||
|
||
|
||
int main(int argc, char** argv){
|
||
return c_Utf8String_UnitTest();
|
||
}
|