#include "c_NlpTrie.h" #include #include #include #include /* --- 自定义测试断言宏 --- */ #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) /** * @brief c_NlpTrie 模块标准单元测试函数 * @return c_err_t 返回 C_ERR_OK 表示全部通过,返回 C_ERROR 表示有测试项失败 */ c_err_t c_NlpTrie_UnitTest(void) { c_NlpTrie_t trie; c_err_t err; printf("========================================\n"); printf(" STARTING c_NlpTrie UNIT TESTS \n"); printf("========================================\n"); /* 1. 防御性边界测试 (Null Pointer Protection) */ RUN_TEST(c_NlpTrie_Init(NULL) == C_ERR_PARAM, "Init with NULL pointer"); RUN_TEST(c_NlpTrie_Insert(NULL, "test") == C_ERR_PARAM, "Insert with NULL self"); RUN_TEST(c_NlpTrie_Insert(&trie, NULL) == C_ERR_PARAM, "Insert with NULL word (before init)"); RUN_TEST(c_NlpTrie_Contains(NULL, "test") == C_FALSE, "Search with NULL self"); RUN_TEST(c_NlpTrie_HasStartWith(NULL, "test") == C_FALSE, "HasStartWith with NULL self"); /* 2. 初始化测试 (Initialization) */ err = c_NlpTrie_Init(&trie); RUN_TEST(err == C_ERR_OK && trie.root != NULL, "Normal initialization"); /* 3. 基础插入与精确查找测试 (Basic Insert & Search) */ err = c_NlpTrie_Insert(&trie, "nlp"); RUN_TEST(err == C_ERR_OK, "Insert normal word 'nlp'"); RUN_TEST(c_NlpTrie_Contains(&trie, "nlp") == C_TRUE, "Search existing word 'nlp'"); RUN_TEST(c_NlpTrie_Contains(&trie, "nl") == C_FALSE, "Search non-existing shorter word 'nl'"); RUN_TEST(c_NlpTrie_Contains(&trie, "nlps") == C_FALSE, "Search non-existing longer word 'nlps'"); /* 4. 前缀包含关系与空字符串测试 (Prefix & Edge cases) */ err = c_NlpTrie_Insert(&trie, "apple"); err |= c_NlpTrie_Insert(&trie, "app"); RUN_TEST(err == C_ERR_OK, "Insert words with shared prefix ('apple', 'app')"); RUN_TEST(c_NlpTrie_Contains(&trie, "app") == C_TRUE, "Search shared prefix word 'app'"); RUN_TEST(c_NlpTrie_Contains(&trie, "apple") == C_TRUE, "Search full word 'apple'"); // 空字符串通常作为根节点本身的结尾标记(如果允许插入) err = c_NlpTrie_Insert(&trie, ""); RUN_TEST(err == C_ERR_OK, "Insert empty string ''"); RUN_TEST(c_NlpTrie_Contains(&trie, "") == C_TRUE, "Search empty string ''"); /* 5. 256 全字符集测试 (UTF-8 Chinese & ASCII Symbols) */ // 包含:全角符号、大写英文、空格、数字、扩展 ASCII err = c_NlpTrie_Insert(&trie, "自然语言处理_v2.0"); RUN_TEST(err == C_ERR_OK, "Insert complex UTF-8 word with symbols and numbers"); RUN_TEST(c_NlpTrie_Contains(&trie, "自然语言处理_v2.0") == C_TRUE, "Search complex UTF-8 word"); RUN_TEST(c_NlpTrie_Contains(&trie, "自然语言") == C_FALSE, "Search non-existing sub-word '自然语言'"); /* 6. 前缀查找功能测试 (HasStartWith) */ RUN_TEST(c_NlpTrie_HasStartWith(&trie, "自然") == C_TRUE, "HasStartWith existing Chinese prefix"); RUN_TEST(c_NlpTrie_HasStartWith(&trie, "自然语言处理_v2.0") == C_TRUE, "HasStartWith full match as prefix"); RUN_TEST(c_NlpTrie_HasStartWith(&trie, "自燃") == C_FALSE, "HasStartWith non-existing Chinese prefix"); RUN_TEST(c_NlpTrie_HasStartWith(&trie, "nl") == C_TRUE, "HasStartWith existing English prefix"); RUN_TEST(c_NlpTrie_HasStartWith(&trie, "xyz") == C_FALSE, "HasStartWith non-existing English prefix"); /* 7. 销毁与悬空安全测试 (Destroy & Safety) */ c_NlpTrie_Destroy(&trie); RUN_TEST(trie.root == NULL, "Trie root set to NULL after destroy"); // 销毁后的二次防御调用不应引发崩溃 RUN_TEST(c_NlpTrie_Contains(&trie, "nlp") == C_FALSE, "Search on destroyed trie"); RUN_TEST(c_NlpTrie_HasStartWith(&trie, "nlp") == C_FALSE, "HasStartWith on destroyed trie"); /* ------------------------------------------------------------------------------------------------------------------ */ /* */ c_NlpTrie_Init(&trie); /* 8. 最长前缀匹配测试 (LongestPrefixOf) */ char res_buf[128]; // 准备数据 c_NlpTrie_Insert(&trie, "自然"); c_NlpTrie_Insert(&trie, "自然语言"); c_NlpTrie_Insert(&trie, "自然语言处理"); c_NlpTrie_Insert(&trie, "nlp"); // 测试点 1:有多重匹配时,应当贪婪匹配最长的一个 c_NlpTrie_LongestPrefixOf(&trie, "自然语言处理核心技术", res_buf, sizeof(res_buf)); RUN_TEST(strcmp(res_buf, "自然语言处理") == 0, "LongestPrefixOf greedy match '自然语言处理'"); // 测试点 2:部分输入匹配,落到中途的有效单词上 c_NlpTrie_LongestPrefixOf(&trie, "自然语言学习", res_buf, sizeof(res_buf)); RUN_TEST(strcmp(res_buf, "自然语言") == 0, "LongestPrefixOf sub-match '自然语言'"); // 测试点 3:完全无法匹配的情况 c_NlpTrie_LongestPrefixOf(&trie, "人工智能", res_buf, sizeof(res_buf)); RUN_TEST(strcmp(res_buf, "") == 0, "LongestPrefixOf no match returns empty string"); // 测试点 4:英文前缀匹配 c_NlpTrie_LongestPrefixOf(&trie, "nlpsolver", res_buf, sizeof(res_buf)); RUN_TEST(strcmp(res_buf, "nlp") == 0, "LongestPrefixOf English word 'nlp'"); c_NlpTrie_Destroy(&trie); /* ------------------------------------------------------------------------------------------------------------------ */ /* */ /* 9. Autocomplete Prefix Matching Tests (KeysWithPrefix) */ c_NlpTrie_Init(&trie); c_NlpStringList_t matches; c_NlpTrie_Insert(&trie, "app"); c_NlpTrie_Insert(&trie, "apple"); c_NlpTrie_Insert(&trie, "apricot"); c_NlpTrie_Insert(&trie, "banana"); c_NlpTrie_Insert(&trie, "自然语言"); c_NlpTrie_Insert(&trie, "自然语言处理"); // Test Point 1: Match English Prefix 'ap' (Expect: app, apple, apricot) err = c_NlpTrie_KeysWithPrefix(&trie, "ap", &matches); RUN_TEST(err == C_ERR_OK && matches.count == 3, "KeysWithPrefix found 3 matches for 'ap'"); RUN_TEST(strcmp(matches.items[0], "app") == 0, "Matches item 0 is 'app'"); RUN_TEST(strcmp(matches.items[1], "apple") == 0, "Matches item 1 is 'apple'"); RUN_TEST(strcmp(matches.items[2], "apricot") == 0, "Matches item 2 is 'apricot'"); c_NlpStringList_Destroy(&matches); // Test Point 2: Match UTF-8 Chinese Prefix (Expect: 自然语言, 自然语言处理) err = c_NlpTrie_KeysWithPrefix(&trie, "自然", &matches); RUN_TEST(err == C_ERR_OK && matches.count == 2, "KeysWithPrefix found 2 matches for '自然'"); c_NlpStringList_Destroy(&matches); // Test Point 3: Search non-existent prefix (Expect: 0 items found, no memory leaks) err = c_NlpTrie_KeysWithPrefix(&trie, "unknown", &matches); RUN_TEST(err == C_ERR_OK && matches.count == 0, "KeysWithPrefix returned 0 items on empty mismatch"); c_NlpStringList_Destroy(&matches); c_NlpTrie_Destroy(&trie); /* ------------------------------------------------------------------------------------------------------------------ */ /* */ c_NlpTrie_Init(&trie); /* 13. Wildcard Query Traversal Validation (KeysThatMatch) */ c_NlpStringList_t query_results; c_NlpTrie_Insert(&trie, "cat"); c_NlpTrie_Insert(&trie, "cot"); c_NlpTrie_Insert(&trie, "coat"); c_NlpTrie_Insert(&trie, "dog"); c_NlpTrie_Insert(&trie, "自然"); c_NlpTrie_Insert(&trie, "自燃"); // Test Case 1: Simple single wildcard slot match (Expect: "cat", "cot") err = c_NlpTrie_KeysThatMatch(&trie, "c.t", &query_results); RUN_TEST(err == C_ERR_OK && query_results.count == 2, "KeysThatMatch finds exactly 2 terms matching pattern 'c.t'"); RUN_TEST(strcmp(query_results.items[0], "cat") == 0, "First matching match found: 'cat'"); RUN_TEST(strcmp(query_results.items[1], "cot") == 0, "Second matching match found: 'cot'"); c_NlpStringList_Destroy(&query_results); // Test Case 2: Full wildcard string constraint match length (Expect: "dog") err = c_NlpTrie_KeysThatMatch(&trie, "...", &query_results); RUN_TEST(err == C_ERR_OK && query_results.count == 3, "Pattern '...' pulls exact length matches ('cat', 'cot', 'dog')"); c_NlpStringList_Destroy(&query_results); // Test Case 3: Complex multi-byte matching (Remember: in UTF-8, 1 Chinese Character = 3 Bytes) // To match a single trailing Chinese character change on "自*", we need 3 dots "自..." err = c_NlpTrie_KeysThatMatch(&trie, "自...", &query_results); RUN_TEST(err == C_ERR_OK && query_results.count == 2, "Multi-byte pattern verification matches both '自然' and '自燃'"); c_NlpStringList_Destroy(&query_results); // Test Case 4: Zero match behavior err = c_NlpTrie_KeysThatMatch(&trie, "c..t", &query_results); // Matches "coat" RUN_TEST(err == C_ERR_OK && query_results.count == 1, "Pattern 'c..t' correctly identifies structural length match 'coat'"); c_NlpStringList_Destroy(&query_results); c_NlpTrie_Destroy(&trie); /* ------------------------------------------------------------------------------------------------------------------ */ /* */ /* 14. Dictionary Node Deletion & Path Pruning Verification (c_NlpTrie_Delete) */ c_NlpTrie_Init(&trie); c_NlpTrie_Insert(&trie, "app"); c_NlpTrie_Insert(&trie, "apple"); c_NlpTrie_Insert(&trie, "banana"); // Test Point 1: Parameter validation checking RUN_TEST(c_NlpTrie_Delete(NULL, "app") == C_ERR_PARAM, "Delete handles NULL self context pointer"); RUN_TEST(c_NlpTrie_Delete(&trie, NULL) == C_ERR_PARAM, "Delete handles NULL string inputs"); // Test Point 2: Deleting a word that is a prefix of another word ("app") // Expected: "app" flag goes off, but "apple" nodes must stay intact err = c_NlpTrie_Delete(&trie, "app"); RUN_TEST(err == C_ERR_OK, "Delete intermediate prefix word 'app' successfully"); RUN_TEST(c_NlpTrie_Contains(&trie, "app") == C_FALSE, "Word 'app' is no longer searchable"); RUN_TEST(c_NlpTrie_Contains(&trie, "apple") == C_TRUE, "Longer word 'apple' remains fully searchable"); // Test Point 3: Deleting a word that leaves isolated nodes behind ("apple") // Expected: The path nodes matching 'l' and 'e' must be pruned to avoid memory leaks err = c_NlpTrie_Delete(&trie, "apple"); RUN_TEST(err == C_ERR_OK, "Delete trailing leaf word 'apple' successfully"); RUN_TEST(c_NlpTrie_Contains(&trie, "apple") == C_FALSE, "Word 'apple' is no longer searchable"); RUN_TEST(c_NlpTrie_HasStartWith(&trie, "ap") == C_FALSE, "Prefix path 'ap' is completely pruned"); // Test Point 4: Non-existent word cleanup verification err = c_NlpTrie_Delete(&trie, "orange"); RUN_TEST(err == C_ERR_OK, "Deleting non-existent word exits cleanly without modification"); RUN_TEST(c_NlpTrie_Contains(&trie, "banana") == C_TRUE, "Unrelated word 'banana' is unaffected"); c_NlpTrie_Destroy(&trie); /* ------------------------------------------------------------------------------------------------------------------ */ /* */ /* 15. Trie Flush Re-initialization Verification (c_NlpTrie_Clear) */ c_NlpTrie_Init(&trie); c_NlpTrie_Insert(&trie, "nlp"); c_NlpTrie_Insert(&trie, "自然语言"); // Test Point 1: Parameter validation checking RUN_TEST(c_NlpTrie_Clear(NULL) == C_ERR_PARAM, "Clear handles NULL self context pointer safely"); // Test Point 2: Execution clearance tracking err = c_NlpTrie_Clear(&trie); RUN_TEST(err == C_ERR_OK, "Clear flushes all sub-branch contents successfully"); RUN_TEST(trie.root != NULL, "Trie structural base root node is preserved"); RUN_TEST(c_NlpTrie_Contains(&trie, "nlp") == C_FALSE, "Previously stored word 'nlp' is no longer found"); RUN_TEST(c_NlpTrie_HasStartWith(&trie, "自然") == C_FALSE, "Prefix indicators successfully cleared"); // Test Point 3: Verification of re-insertion stability post-clearance err = c_NlpTrie_Insert(&trie, "reborn"); RUN_TEST(err == C_ERR_OK, "Trie accepts new entry additions seamlessly after being cleared"); RUN_TEST(c_NlpTrie_Contains(&trie, "reborn") == C_TRUE, "Newly added post-clearance key is fully searchable"); // Test Point 4: Idempotent clearing check (sequential empty clears) err = c_NlpTrie_Clear(&trie); err |= c_NlpTrie_Clear(&trie); RUN_TEST(err == C_ERR_OK, "Continuous back-to-back clear calls execute safely with no side-effects"); c_NlpTrie_Destroy(&trie); /* ------------------------------------------------------------------------------------------------------------------ */ /* */ // 允许重复销毁(幂等性保护) c_NlpTrie_Destroy(&trie); printf("========================================\n"); printf("\033[32mALL c_NlpTrie TESTS PASSED SUCCESSFULLY!\033[0m\n"); printf("========================================\n"); return C_ERR_OK; } /* --- 测试执行入口 --- */ int main(void) { // 执行单元测试 c_err_t result = c_NlpTrie_UnitTest(); if (result != C_ERR_OK) { return -1; } return 0; }