| #include "test.h" |
| |
| #include "CLucene/store/IndexInput.h" |
| #include "CLucene/store/IndexOutput.h" |
| #include "CLucene/store/RAMDirectory.h" |
| #include "CuTest.h" |
| #include <codecvt> |
| #include <ctime> |
| #include <locale> |
| #include <string> |
| #include <vector> |
| #include <utility> |
| #include <iostream> |
| #include "CLucene/index/_TermInfosWriter.h" |
| #include "CLucene/index/_TermInfosReader.h" |
| #include "CLucene/index/_FieldInfos.h" |
| #include "CLucene/index/_TermInfo.h" |
| #include "CLucene/index/Term.h" |
| |
| using namespace lucene::store; |
| |
| // Add a helper macro for printing more detailed error messages when assertions fail |
| #define CuAssertTrueWithMessage(tc, message, condition) \ |
| do { \ |
| if (!(condition)) { \ |
| printf("Assertion failed: %s\n", message); \ |
| } \ |
| CuAssertTrue(tc, condition); \ |
| } while (0) |
| |
| static void TestUTF8WriteAndReadChars(CuTest* tc) { |
| RAMDirectory* dir = _CLNEW RAMDirectory(); |
| |
| const char* testFileName = "test_utf8_chars.dat"; |
| |
| IndexOutput* output = dir->createOutput(testFileName); |
| |
| std::wstring testString; |
| testString.push_back(L'A'); // 1 byte |
| testString.push_back(L'你'); // 3 bytes |
| testString.push_back(L'好'); // 3 bytes |
| testString.push_back((wchar_t)0x1F600); // 4 bytes |
| output->writeVInt(testString.length()); |
| output->writeSChars<TCHAR>(testString.c_str(), testString.length()); |
| |
| output->close(); |
| _CLDELETE(output); |
| |
| IndexInput* input = nullptr; |
| CLuceneError error; |
| auto result = dir->openInput(testFileName, input, error); |
| CuAssertTrue(tc, result); |
| |
| TCHAR* readBackStr = input->readString(); |
| std::wstring readBackString(readBackStr); |
| _CLDELETE_LARRAY(readBackStr); |
| printf("\n=== Unicode Character basic test ===\n"); |
| |
| CuAssertIntEquals(tc, _T("UTF-8 is not equal to the original string"), (int)testString.size(), |
| (int)readBackString.size()); |
| |
| for (size_t i = 0; i < testString.size(); i++) { |
| printf("Character #%zu: 0x%04X -> Readback: 0x%04X, %s\n", i, (unsigned int)testString[i], |
| (unsigned int)readBackString[i], |
| (testString[i] == readBackString[i] ? "Success" : "Failed")); |
| char errorMsg[256]; |
| sprintf(errorMsg, "Character mismatch - Position: %zu, Original: 0x%04X, Readback: 0x%04X", |
| i, (unsigned int)testString[i], (unsigned int)readBackString[i]); |
| CuAssertTrueWithMessage(tc, errorMsg, testString[i] == readBackString[i]); |
| } |
| |
| input->close(); |
| _CLDELETE(input); |
| _CLDELETE(dir); |
| } |
| |
| static void WriteCharsLegacy(IndexOutput* output, const TCHAR* s, int32_t length) { |
| const int32_t end = length; |
| for (int32_t i = 0; i < end; ++i) { |
| const int32_t code = (int32_t)s[i]; |
| if (code >= 0x01 && code <= 0x7F) { |
| output->writeByte((uint8_t)code); |
| } else if (((code >= 0x80) && (code <= 0x7FF)) || code == 0) { |
| output->writeByte((uint8_t)(0xC0 | (code >> 6))); |
| output->writeByte((uint8_t)(0x80 | (code & 0x3F))); |
| } else { |
| output->writeByte((uint8_t)(0xE0 | (((uint32_t)code) >> 12))); |
| output->writeByte((uint8_t)(0x80 | ((code >> 6) & 0x3F))); |
| output->writeByte((uint8_t)(0x80 | (code & 0x3F))); |
| } |
| } |
| } |
| |
| static void ReadCharsLegacy(IndexInput* input, TCHAR* buffer, int32_t start, int32_t len) { |
| const int32_t end = start + len; |
| TCHAR b; |
| for (int32_t i = start; i < end; ++i) { |
| b = input->readByte(); |
| if ((b & 0x80) == 0) { |
| b = (b & 0x7F); |
| } else if ((b & 0xE0) != 0xE0) { |
| b = (((b & 0x1F) << 6) | (input->readByte() & 0x3F)); |
| } else { |
| b = ((b & 0x0F) << 12) | ((input->readByte() & 0x3F) << 6); |
| b |= (input->readByte() & 0x3F); |
| } |
| buffer[i] = b; |
| } |
| } |
| |
| // Test encoding and decoding of various Unicode character ranges |
| static void TestUnicodeRanges(CuTest* tc) { |
| RAMDirectory* dir = _CLNEW RAMDirectory(); |
| const char* testFileName = "test_unicode_ranges.dat"; |
| |
| // Create a test string containing characters from various Unicode ranges |
| std::vector<std::pair<wchar_t, const char*>> unicodeTestChars = { |
| // ASCII range (U+0000 - U+007F) - 1 byte UTF-8 |
| // {0x0000, "NULL character (U+0000)"}, |
| {0x0001, "Start of Heading (U+0001)"}, |
| {0x007F, "Delete (U+007F)"}, |
| {L'A', "Latin letter A (U+0041)"}, |
| {L'z', "Latin letter z (U+007A)"}, |
| |
| // Latin Extended (U+0080 - U+07FF) - 2 bytes UTF-8 |
| {0x00A9, "Copyright symbol © (U+00A9)"}, |
| {0x00AE, "Registered trademark ® (U+00AE)"}, |
| {0x00F1, "Spanish letter ñ (U+00F1)"}, |
| {0x0394, "Greek letter Δ (U+0394)"}, |
| {0x03A9, "Greek letter Ω (U+03A9)"}, |
| {0x03C0, "Greek letter π (U+03C0)"}, |
| {0x0440, "Cyrillic letter р (U+0440)"}, |
| {0x0521, "Armenian letter Ց (U+0521)"}, |
| {0x05D0, "Hebrew letter א (U+05D0)"}, |
| {0x0648, "Arabic letter و (U+0648)"}, |
| |
| // Other Basic Multilingual Plane characters (U+0800 - U+FFFF) - 3 bytes UTF-8 |
| {0x0915, "Devanagari letter क (U+0915)"}, |
| {0x0E01, "Thai letter ก (U+0E01)"}, |
| {0x1100, "Hangul letter ᄀ (U+1100)"}, |
| {L'你', "Chinese character 你 (U+4F60)"}, |
| {L'好', "Chinese character 好 (U+597D)"}, |
| {0x6C34, "Chinese character 水 (U+6C34)"}, |
| {0x7389, "Chinese character 玉 (U+7389)"}, |
| {0x9999, "Chinese character 香 (U+9999)"}, |
| {0xFF01, "Fullwidth exclamation mark ! (U+FF01)"}, |
| {0xFFFF, "BMP maximum value (U+FFFF)"}, |
| |
| // Supplementary planes (U+10000 - U+10FFFF) - 4 bytes UTF-8 |
| {0x1F600, "Grinning face emoji 😀 (U+1F600)"}, |
| {0x1F64F, "Prayer hands emoji 🙏 (U+1F64F)"}, |
| {0x1F914, "Thinking face emoji 🤔 (U+1F914)"}, |
| {0x1F4A9, "Pile of poo emoji 💩 (U+1F4A9)"}, |
| {0x1F680, "Rocket emoji 🚀 (U+1F680)"}, |
| {0x10348, "Gothic letter 𐍈 (U+10348)"}, |
| {0x10400, "Deseret letter 𐐀 (U+10400)"}, |
| {0x10FFFF, "Unicode maximum value (U+10FFFF)"}, |
| |
| {0x2200, "For All ∀ (U+2200)"}, |
| {0x2211, "Summation ∑ (U+2211)"}, |
| {0x221E, "Infinity ∞ (U+221E)"}, |
| {0x2248, "Almost Equal To ≈ (U+2248)"}, |
| |
| {0x20AC, "Euro € (U+20AC)"}, |
| {0x20BD, "Russian Ruble ₽ (U+20BD)"}, |
| {0x20B9, "Indian Rupee ₹ (U+20B9)"}, |
| {0x20A9, "Won Sign ₩ (U+20A9)"}, |
| |
| {0x2190, "Left Arrow ← (U+2190)"}, |
| {0x2192, "Right Arrow → (U+2192)"}, |
| {0x2191, "Up Arrow ↑ (U+2191)"}, |
| {0x2193, "Down Arrow ↓ (U+2193)"}, |
| |
| {0x2550, "Box Drawing ═ (U+2550)"}, |
| {0x2551, "Box Drawing ║ (U+2551)"}, |
| {0x2554, "Box Drawing ╔ (U+2554)"}, |
| {0x2557, "Box Drawing ╗ (U+2557)"}, |
| |
| {0x2122, "Trade Mark ™ (U+2122)"}, |
| {0x2105, "Care Of ℅ (U+2105)"}, |
| {0x2113, "Script Small L ℓ (U+2113)"}, |
| {0x2116, "Numero Sign № (U+2116)"}, |
| |
| {0x2600, "Black Sun with Rays ☀ (U+2600)"}, |
| {0x2602, "Umbrella ☂ (U+2602)"}, |
| {0x2614, "Umbrella with Rain Drops ☔ (U+2614)"}, |
| {0x2665, "Black Heart Suit ♥ (U+2665)"}, |
| |
| {0x1D400, "Mathematical Bold Capital A 𝐀 (U+1D400)"}, |
| {0x1D538, "Mathematical Double-Struck Capital A 𝔸 (U+1D538)"}, |
| {0x1F300, "Cyclone 🌀 (U+1F300)"}, |
| {0x1F431, "Cat Face 🐱 (U+1F431)"}, |
| {0x1F52B, "Pistol 🔫 (U+1F52B)"}, |
| {0x1F697, "Automobile 🚗 (U+1F697)"}, |
| |
| {0x20000, "CJK Unified Ideograph 𠀀 (U+20000)"}, |
| {0x2A700, "CJK Unified Ideograph 𪜀 (U+2A700)"}, |
| |
| {0x2300, "Diameter Sign ⌀ (U+2300)"}, |
| {0x231B, "Hourglass ⌛ (U+231B)"}, |
| {0x2328, "Keyboard ⌨ (U+2328)"}, |
| {0x23F0, "Alarm Clock ⏰ (U+23F0)"}}; |
| |
| // Build test string |
| std::wstring testString; |
| for (const auto& pair : unicodeTestChars) { |
| testString.push_back(pair.first); |
| } |
| try { |
| // Write test string |
| IndexOutput* output = dir->createOutput(testFileName); |
| output->writeSChars<TCHAR>(testString.c_str(), testString.length()); |
| output->close(); |
| _CLDELETE(output); |
| } catch (const std::exception& e) { |
| CuAssertTrueWithMessage(tc, e.what(), false); |
| } |
| try { |
| // Read and verify |
| IndexInput* input = nullptr; |
| CLuceneError error; |
| auto result = dir->openInput(testFileName, input, error); |
| CuAssertTrue(tc, result); |
| |
| TCHAR* readBackStr = _CL_NEWARRAY(TCHAR, testString.length() + 1); |
| input->readChars(readBackStr, 0, testString.length()); |
| readBackStr[testString.length()] = 0; |
| std::wstring readBackString(readBackStr); |
| _CLDELETE_LARRAY(readBackStr); |
| |
| // Verify length |
| CuAssertIntEquals(tc, _T("Unicode string length mismatch"), (int)testString.size(), |
| (int)readBackString.size()); |
| |
| // Verify each character |
| printf("\n=== Unicode Character Encoding Test ===\n"); |
| for (size_t i = 0; i < testString.size(); i++) { |
| wchar_t original = testString[i]; |
| wchar_t readBack = readBackString[i]; |
| |
| printf("Character #%zu: 0x%04X (%s) -> Readback: 0x%04X, %s\n", i, |
| (unsigned int)original, unicodeTestChars[i].second, (unsigned int)readBack, |
| (original == readBack ? "Success" : "Failed")); |
| |
| char errorMsg[256]; |
| sprintf(errorMsg, |
| "Unicode character mismatch - Position: %zu, Character: %s, Original: 0x%04X, " |
| "Readback: 0x%04X", |
| i, unicodeTestChars[i].second, (unsigned int)original, (unsigned int)readBack); |
| CuAssertTrueWithMessage(tc, errorMsg, original == readBack); |
| } |
| |
| input->close(); |
| _CLDELETE(input); |
| _CLDELETE(dir); |
| } catch (const std::exception& e) { |
| CuAssertTrueWithMessage(tc, e.what(), false); |
| } |
| } |
| |
| // Test edge cases and special characters |
| static void TestEdgeCases(CuTest* tc) { |
| RAMDirectory* dir = _CLNEW RAMDirectory(); |
| const char* testFileName = "test_edge_cases.dat"; |
| |
| // Create a test string containing edge cases |
| std::wstring testString; |
| |
| // 1. Empty string test |
| IndexOutput* emptyOutput = dir->createOutput("empty_string.dat"); |
| emptyOutput->writeVInt(0); |
| emptyOutput->writeSChars<TCHAR>(L"", 0); |
| emptyOutput->close(); |
| _CLDELETE(emptyOutput); |
| |
| IndexInput* emptyInput = nullptr; |
| CLuceneError emptyError; |
| dir->openInput("empty_string.dat", emptyInput, emptyError); |
| TCHAR* emptyStr = emptyInput->readString(); |
| std::wstring emptyReadBack(emptyStr); |
| _CLDELETE_LARRAY(emptyStr); |
| CuAssertIntEquals(tc, _T("Empty string length should be 0"), 0, (int)emptyReadBack.size()); |
| emptyInput->close(); |
| _CLDELETE(emptyInput); |
| |
| // 2. Long string test (containing various characters) |
| std::wstring longString; |
| // Add 1000 mixed characters |
| for (int i = 0; i < 250; i++) { |
| longString.push_back(L'A' + (i % 26)); // ASCII |
| longString.push_back(0x00A0 + i % 100); // Latin extended |
| longString.push_back(0x4E00 + i % 100); // Chinese |
| longString.push_back(0x1F600 + i % 50); // Emoji |
| } |
| |
| IndexOutput* longOutput = dir->createOutput("long_string.dat"); |
| longOutput->writeVInt(longString.length()); |
| longOutput->writeSChars<TCHAR>(longString.c_str(), longString.length()); |
| longOutput->close(); |
| _CLDELETE(longOutput); |
| |
| IndexInput* longInput = nullptr; |
| CLuceneError longError; |
| dir->openInput("long_string.dat", longInput, longError); |
| TCHAR* longStr = longInput->readString(); |
| std::wstring longReadBack(longStr); |
| _CLDELETE_LARRAY(longStr); |
| |
| CuAssertIntEquals(tc, _T("Long string length mismatch"), (int)longString.size(), |
| (int)longReadBack.size()); |
| |
| // Only check some characters to avoid too much output |
| printf("\n=== Long String Test (showing first 10 characters) ===\n"); |
| for (size_t i = 0; i < 10 && i < longString.size(); i++) { |
| printf("Character #%zu: 0x%04X -> Readback: 0x%04X, %s\n", i, (unsigned int)longString[i], |
| (unsigned int)longReadBack[i], |
| (longString[i] == longReadBack[i] ? "Success" : "Failed")); |
| |
| char errorMsg[256]; |
| sprintf(errorMsg, |
| "Long string character mismatch - Position: %zu, Original: 0x%04X, Readback: " |
| "0x%04X", |
| i, (unsigned int)longString[i], (unsigned int)longReadBack[i]); |
| CuAssertTrueWithMessage(tc, errorMsg, longString[i] == longReadBack[i]); |
| } |
| |
| // Random sample check |
| for (size_t i = 0; i < longString.size(); i += 100) { |
| char errorMsg[256]; |
| sprintf(errorMsg, |
| "Long string sample check failed - Position: %zu, Original: 0x%04X, Readback: " |
| "0x%04X", |
| i, (unsigned int)longString[i], (unsigned int)longReadBack[i]); |
| CuAssertTrueWithMessage(tc, errorMsg, longString[i] == longReadBack[i]); |
| } |
| |
| longInput->close(); |
| _CLDELETE(longInput); |
| |
| _CLDELETE(dir); |
| } |
| |
| // Test UTF-8 encoding boundary cases |
| static void TestUTF8EncodingBoundaries(CuTest* tc) { |
| RAMDirectory* dir = _CLNEW RAMDirectory(); |
| const char* testFileName = "test_utf8_boundaries.dat"; |
| |
| // Test UTF-8 encoding boundary cases |
| struct BoundaryTest { |
| wchar_t codePoint; |
| const char* description; |
| int expectedBytes; |
| }; |
| |
| std::vector<BoundaryTest> boundaryTests = {// 1-byte boundaries |
| //{0x0000, "NULL (U+0000) - 1-byte lower bound", 1}, |
| {0x007F, "DELETE (U+007F) - 1-byte upper bound", 1}, |
| |
| // 2-byte boundaries |
| {0x0080, "PAD (U+0080) - 2-byte lower bound", 2}, |
| {0x07FF, "2-byte upper bound (U+07FF)", 2}, |
| |
| // 3-byte boundaries |
| {0x0800, "3-byte lower bound (U+0800)", 3}, |
| {0xFFFF, "BMP upper bound (U+FFFF)", 3}, |
| |
| // 4-byte boundaries (supplementary planes) |
| {0x10000, "SMP lower bound (U+10000)", 4}, |
| {0x10FFFF, "Unicode upper bound (U+10FFFF)", 4}}; |
| |
| // Build test string |
| std::wstring testString; |
| for (const auto& test : boundaryTests) { |
| testString.push_back(test.codePoint); |
| } |
| |
| // Write test string |
| IndexOutput* output = dir->createOutput(testFileName); |
| |
| // Manually write each character and record byte count |
| output->writeVInt(testString.length()); |
| |
| std::vector<int> actualBytes; |
| int64_t startPos, endPos; |
| |
| for (size_t i = 0; i < testString.size(); i++) { |
| startPos = output->getFilePointer(); |
| output->writeChars(&testString[i], 1); |
| endPos = output->getFilePointer(); |
| actualBytes.push_back((int)(endPos - startPos)); |
| } |
| |
| output->close(); |
| _CLDELETE(output); |
| |
| // Read and verify |
| IndexInput* input = nullptr; |
| CLuceneError error; |
| dir->openInput(testFileName, input, error); |
| |
| const int32_t len = input->readVInt(); |
| TCHAR* buffer = _CL_NEWARRAY(TCHAR, len + 1); |
| input->readChars(buffer, 0, len); |
| buffer[len] = 0; |
| std::wstring readBackString(buffer); |
| _CLDELETE_LARRAY(buffer); |
| |
| // Verify length |
| CuAssertIntEquals(tc, _T("Boundary test string length mismatch"), (int)testString.size(), |
| (int)readBackString.size()); |
| |
| // Verify each character and their byte counts |
| printf("\n=== UTF-8 Encoding Boundary Tests ===\n"); |
| for (size_t i = 0; i < testString.size(); i++) { |
| wchar_t original = testString[i]; |
| wchar_t readBack = readBackString[i]; |
| |
| printf("Character #%zu: U+%04X (%s)\n", i, (unsigned int)original, |
| boundaryTests[i].description); |
| printf(" - Expected bytes: %d, Actual bytes: %d, %s\n", boundaryTests[i].expectedBytes, |
| actualBytes[i], |
| (boundaryTests[i].expectedBytes == actualBytes[i] ? "Success" : "Failed")); |
| printf(" - Readback: U+%04X, %s\n", (unsigned int)readBack, |
| (original == readBack ? "Success" : "Failed")); |
| |
| char errorMsg[256]; |
| sprintf(errorMsg, |
| "Boundary character mismatch - Position: %zu, Description: %s, Original: 0x%04X, " |
| "Readback: 0x%04X", |
| i, boundaryTests[i].description, (unsigned int)original, (unsigned int)readBack); |
| CuAssertTrueWithMessage(tc, errorMsg, original == readBack); |
| CuAssertIntEquals(tc, _T("UTF-8 encoding byte count mismatch"), |
| boundaryTests[i].expectedBytes, actualBytes[i]); |
| } |
| |
| input->close(); |
| _CLDELETE(input); |
| _CLDELETE(dir); |
| } |
| |
| // Test special UTF-8 sequences |
| static void TestSpecialUTF8Sequences(CuTest* tc) { |
| RAMDirectory* dir = _CLNEW RAMDirectory(); |
| |
| // Test some special UTF-8 sequences |
| struct SpecialSequenceTest { |
| std::wstring str; |
| const char* description; |
| }; |
| |
| std::vector<SpecialSequenceTest> specialTests = { |
| {L"", "Empty string"}, |
| {L"Hello", "Pure ASCII string"}, |
| {L"你好世界", "Pure Chinese string"}, |
| {L"Hello, 世界!", "Mixed ASCII and Chinese"}, |
| {L"🌍🌎🌏", "Pure Emoji (4-byte characters)"}, |
| {L"Earth: 🌍 🌎 🌏", "Mixed Chinese and Emoji"}, |
| {L"A\u0000B", "String containing NULL character"}, |
| {L"سلام دنیا", "Arabic/Persian text"}, |
| {L"こんにちは世界", "Japanese and Chinese"}, |
| {L"Hello\nWorld", "String with newline"}, |
| {L"Tab\tCharacter", "String with tab character"}, |
| {std::wstring(1000, L'A'), "1000 identical characters"}, |
| {L"😀😃😄😁😆😅😂🤣", "Consecutive Emoji"}}; |
| |
| for (size_t testIndex = 0; testIndex < specialTests.size(); testIndex++) { |
| const auto& test = specialTests[testIndex]; |
| std::string testFileName = "special_test_" + std::to_string(testIndex) + ".dat"; |
| |
| // Write test string |
| IndexOutput* output = dir->createOutput(testFileName.c_str()); |
| output->writeVInt(test.str.length()); |
| output->writeSChars<TCHAR>(test.str.c_str(), test.str.length()); |
| output->close(); |
| _CLDELETE(output); |
| |
| // Read and verify |
| IndexInput* input = nullptr; |
| CLuceneError error; |
| dir->openInput(testFileName.c_str(), input, error); |
| |
| TCHAR* readBackStr = input->readString(); |
| std::wstring readBackString(readBackStr); |
| _CLDELETE_LARRAY(readBackStr); |
| |
| printf("\n=== Special UTF-8 Sequence Test #%zu: %s ===\n", testIndex, test.description); |
| printf(" - Original length: %zu, Readback length: %zu, %s\n", test.str.length(), |
| readBackString.length(), |
| (test.str.length() == readBackString.length() ? "Success" : "Failed")); |
| |
| CuAssertIntEquals(tc, _T("Special sequence length mismatch"), (int)test.str.length(), |
| (int)readBackString.length()); |
| |
| // For shorter strings, print each character for comparison |
| if (test.str.length() <= 20) { |
| for (size_t i = 0; i < test.str.length(); i++) { |
| printf(" Character #%zu: U+%04X -> Readback: U+%04X, %s\n", i, |
| (unsigned int)test.str[i], (unsigned int)readBackString[i], |
| (test.str[i] == readBackString[i] ? "Success" : "Failed")); |
| |
| char errorMsg[256]; |
| sprintf(errorMsg, |
| "Special sequence character mismatch - Test: %s, Position: %zu, Original: " |
| "0x%04X, Readback: 0x%04X", |
| test.description, i, (unsigned int)test.str[i], |
| (unsigned int)readBackString[i]); |
| CuAssertTrueWithMessage(tc, errorMsg, test.str[i] == readBackString[i]); |
| } |
| } else { |
| // For longer strings, just check equality |
| char errorMsg[256]; |
| sprintf(errorMsg, "Long special sequence mismatch - Test: %s, Length: %zu", |
| test.description, test.str.length()); |
| CuAssertTrueWithMessage(tc, errorMsg, test.str == readBackString); |
| } |
| |
| input->close(); |
| _CLDELETE(input); |
| } |
| |
| _CLDELETE(dir); |
| } |
| |
| // Test UTF-8 encoding performance |
| static void TestUTF8Performance(CuTest* tc) { |
| RAMDirectory* dir = _CLNEW RAMDirectory(); |
| const char* testFileName = "test_utf8_performance.dat"; |
| |
| // Create a large test string with various types of characters |
| std::wstring testString; |
| const int testSize = 100000; // 100,000 characters |
| |
| // Add different types of characters |
| for (int i = 0; i < testSize / 4; i++) { |
| testString.push_back(L'A' + (i % 26)); // ASCII characters |
| testString.push_back(0x00A0 + (i % 128)); // Latin extended |
| testString.push_back(0x4E00 + (i % 1000)); // Chinese characters |
| testString.push_back(0x1F600 + (i % 50)); // Emoji (4-byte characters) |
| } |
| |
| printf("\n=== UTF-8 Encoding Performance Test (String length: %zu) ===\n", testString.size()); |
| |
| // Measure write time |
| clock_t writeStart = clock(); |
| |
| IndexOutput* output = dir->createOutput(testFileName); |
| output->writeVInt(testString.length()); |
| output->writeSChars<TCHAR>(testString.c_str(), testString.length()); |
| output->close(); |
| _CLDELETE(output); |
| |
| clock_t writeEnd = clock(); |
| double writeTime = ((double)(writeEnd - writeStart)) / CLOCKS_PER_SEC; |
| printf("Write time: %.6f seconds\n", writeTime); |
| |
| // Measure read time |
| clock_t readStart = clock(); |
| |
| IndexInput* input = nullptr; |
| CLuceneError error; |
| dir->openInput(testFileName, input, error); |
| |
| TCHAR* readBackStr = input->readString(); |
| std::wstring readBackString(readBackStr); |
| _CLDELETE_LARRAY(readBackStr); |
| |
| clock_t readEnd = clock(); |
| double readTime = ((double)(readEnd - readStart)) / CLOCKS_PER_SEC; |
| printf("Read time: %.6f seconds\n", readTime); |
| |
| // Verify results |
| char lengthErrorMsg[256]; |
| sprintf(lengthErrorMsg, |
| "Performance test string length mismatch - Original: %zu, Readback: %zu", |
| testString.size(), readBackString.size()); |
| CuAssertIntEquals(tc, _T("Performance test string length mismatch"), (int)testString.size(), |
| (int)readBackString.size()); |
| printf(" %s\n", lengthErrorMsg); // Print error message directly instead of using macro |
| |
| char contentErrorMsg[256]; |
| sprintf(contentErrorMsg, "Performance test string content mismatch"); |
| CuAssertTrueWithMessage(tc, contentErrorMsg, testString == readBackString); |
| |
| // Calculate characters processed per second |
| double writeCharsPerSec = testString.size() / writeTime; |
| double readCharsPerSec = readBackString.size() / readTime; |
| |
| printf("Write speed: %.2f characters/second\n", writeCharsPerSec); |
| printf("Read speed: %.2f characters/second\n", readCharsPerSec); |
| |
| input->close(); |
| _CLDELETE(input); |
| _CLDELETE(dir); |
| } |
| |
| static void TestUTF8Compatibility(CuTest* tc) { |
| RAMDirectory* dir = _CLNEW RAMDirectory(); |
| const char* testFileName = "test_compatibility.dat"; |
| |
| // Test characters covering different ranges |
| const std::vector<wchar_t> testChars = { |
| // Basic 1-byte |
| L'A', |
| // Common 3-byte Chinese characters |
| L'你', L'好', |
| // 4-byte characters from different planes |
| 0x1F600, // 😀 Grinning Face (Emoji) |
| 0xF600, // 丽 |
| 0x1F64F, // 🙏 Folded Hands |
| 0xF64F, // 碌 |
| 0x20021, // 𠀡 CJK Unified Ideographs Extension B |
| 0x0021, // ! Basic Latin |
| 0x2A6D6, // 𪛖 CJK Unified Ideographs Extension C |
| 0xA6D6, // ꛖ Hangul Jamo Extended-B |
| 0x10123, // 𐄣 Ancient Greek Numbers |
| 0x0123, // Cuneiform |
| 0x10348, // 𐍈 Gothic Letter Hwair |
| 0x0348, // Ȉ |
| // Boundary cases |
| //0x10000, // Minimum 4-byte character |
| 0x10FFFF // Maximum valid Unicode code point |
| }; |
| |
| // Build test string with mixed character types |
| std::wstring testString; |
| for (auto ch : testChars) { |
| testString.push_back(ch); |
| } |
| printf("testString.size() = %zu\n", testString.size()); |
| // Test case 1: New code write, old code readn |
| try { |
| IndexOutput* output = dir->createOutput("new_write_old_read.dat"); |
| output->writeVInt(testString.length()); |
| output->writeChars(testString.c_str(), testString.length()); |
| output->close(); |
| _CLDELETE(output); |
| |
| IndexInput* input = nullptr; |
| CLuceneError error; |
| dir->openInput("new_write_old_read.dat", input, error); |
| |
| const int32_t len = input->readVInt(); |
| TCHAR* buffer = _CL_NEWARRAY(TCHAR, len + 1); |
| ReadCharsLegacy(input, buffer, 0, len); |
| buffer[len] = 0; |
| |
| for (int32_t i = 0; i < len; i++) { |
| printf(" Character #%d: U+%04X -> Readback: U+%04X, %s\n", i, |
| (unsigned int)testString[i], (unsigned int)buffer[i], |
| (testString[i] == buffer[i] ? "Success" : "Failed")); |
| } |
| CuAssertTrue(tc, buffer[3] != testString[3]); // old code cannot parse 4-byte character |
| _CLDELETE_LARRAY(buffer); |
| _CLDELETE(input); |
| } catch (CLuceneError& e) { |
| CuAssertTrueWithMessage(tc, e.what(), false); |
| } |
| |
| // Test case 2: Old code write, new code read |
| { |
| // Old implementation write (only handles 3 bytes) |
| IndexOutput* output = dir->createOutput("old_write_new_read.dat"); |
| output->writeVInt(testString.length()); |
| output->writeSCharsOrigin<TCHAR>(testString.c_str(), testString.length()); |
| output->close(); |
| _CLDELETE(output); |
| |
| // Read using both methods for comparison |
| std::wstring oldResult; // Old method read result |
| std::wstring newResult; // New method read result |
| |
| // Old method read |
| { |
| IndexInput* input = nullptr; |
| CLuceneError error; |
| dir->openInput("old_write_new_read.dat", input, error); |
| |
| const int32_t len = input->readVInt(); |
| TCHAR* buffer = _CL_NEWARRAY(TCHAR, len + 1); |
| ReadCharsLegacy(input, buffer, 0, len); |
| for (int32_t i = 0; i < len; i++) { |
| printf("testString[%d] = %d, buffer[%d] = %d\n", i, testString[i], i, buffer[i]); |
| } |
| buffer[len] = 0; |
| oldResult = buffer; |
| _CLDELETE_LARRAY(buffer); |
| _CLDELETE(input); |
| } |
| printf("oldResult.size() = %zu\n", oldResult.size()); |
| |
| // New method read |
| { |
| IndexInput* input = nullptr; |
| CLuceneError error; |
| dir->openInput("old_write_new_read.dat", input, error); |
| |
| const int32_t len = input->readVInt(); |
| TCHAR* buffer = _CL_NEWARRAY(TCHAR, len + 1); |
| input->readChars(buffer, 0, len); |
| buffer[len] = 0; |
| newResult = buffer; |
| _CLDELETE_LARRAY(buffer); |
| _CLDELETE(input); |
| } |
| printf("newResult.size() = %zu\n", newResult.size()); |
| printf("oldResult.size() = %zu, newResult.size() = %zu\n", oldResult.size(), |
| newResult.size()); |
| // Compare results |
| char lengthErrorMsg[256]; |
| sprintf(lengthErrorMsg, |
| "Compatibility test length mismatch - Old method: %zu, New method: %zu", |
| oldResult.size(), newResult.size()); |
| CuAssertTrueWithMessage(tc, lengthErrorMsg, oldResult.size() == newResult.size()); |
| |
| // Verify each character |
| for (size_t i = 0; i < oldResult.size(); i++) { |
| wchar_t oldChar = oldResult[i]; |
| wchar_t newChar = newResult[i]; |
| |
| printf("Character #%zu: Old method: U+%04X, New method: U+%04X, %s, Original: U+%04X\n", |
| i, (unsigned int)oldChar, (unsigned int)newChar, |
| (oldChar == newChar ? "Match" : "Mismatch"), (unsigned int)testString[i]); |
| |
| char errorMsg[256]; |
| sprintf(errorMsg, |
| "Character mismatch - Position: %zu, Old method: U+%04X, New method: U+%04X", i, |
| (unsigned int)oldChar, (unsigned int)newChar); |
| |
| CuAssertTrueWithMessage(tc, errorMsg, oldChar == newChar); |
| } |
| |
| printf("\nCompatibility test: %s\n", |
| (oldResult == newResult ? "PASSED - Methods are compatible" |
| : "FAILED - Methods are not compatible")); |
| } |
| |
| _CLDELETE(dir); |
| } |
| |
| void print_wstring(const std::wstring& wstr) { |
| std::wstring_convert<std::codecvt_utf8<wchar_t>> converter; |
| std::string str = converter.to_bytes(wstr); |
| std::cout << str << std::endl; |
| } |
| // Test STermInfosWriter with various Unicode characters (write and read) |
| static void TestSTermInfosWriterUnicode(CuTest* tc) { |
| printf("\n=== Testing STermInfosWriter<TCHAR> with Unicode (Write and Read) ===\n"); |
| |
| // Create a RAM directory for testing |
| Directory* dir = _CLNEW RAMDirectory(); |
| |
| // Create field infos |
| FieldInfos* fieldInfos = _CLNEW FieldInfos(); |
| fieldInfos->add(_T("content"), false); |
| |
| const char* segmentName = "test_unicode"; |
| |
| // Define Unicode test cases |
| struct UnicodeTermTest { |
| std::wstring str; |
| const char* description; |
| }; |
| |
| std::vector<UnicodeTermTest> testTerms = { |
| {L"A", "Basic Latin A (U+0041)"}, |
| {L"z", "Basic Latin z (U+007A)"}, |
| {L"©", "Copyright Sign (U+00A9)"}, |
| {L"é", "Latin Small E with acute (U+00E9)"}, |
| {L"œ", "Latin Small Ligature OE (U+0153)"}, |
| {L"Ω", "Greek Capital Omega (U+03A9)"}, |
| {L"π", "Greek Small Pi (U+03C0)"}, |
| {L"Я", "Cyrillic Capital Letter Ya (U+042F)"}, |
| {L"я", "Cyrillic Small Letter Ya (U+044F)"}, |
| {L"א", "Hebrew Letter Alef (U+05D0)"}, |
| {L"ا", "Arabic Letter Alef (U+0627)"}, |
| {L"अ", "Devanagari Letter A (U+0905)"}, |
| {L"一", "CJK Unified Ideograph (U+4E00)"}, |
| {L"中", "CJK Unified Ideograph (U+4E2D)"}, |
| {L"文", "CJK Unified Ideograph (U+6587)"}, |
| {L"青", "CJK Unified Ideograph (U+9752)"}, |
| {L"あ", "Hiragana Letter A (U+3042)"}, |
| {L"ア", "Katakana Letter A (U+30A2)"}, |
| {L"가", "Hangul Syllable GA (U+AC00)"}, |
| |
| {L"∀", "For All (U+2200)"}, |
| {L"∑", "Summation (U+2211)"}, |
| {L"∞", "Infinity (U+221E)"}, |
| {L"≈", "Almost Equal To (U+2248)"}, |
| {L"€", "Euro (U+20AC)"}, |
| {L"₽", "Russian Ruble (U+20BD)"}, |
| {L"₹", "Indian Rupee (U+20B9)"}, |
| {L"₩", "Won Sign (U+20A9)"}, |
| {L"←", "Left Arrow (U+2190)"}, |
| {L"→", "Right Arrow (U+2192)"}, |
| {L"═", "Box Drawing (U+2550)"}, |
| {L"║", "Box Drawing (U+2551)"}, |
| {L"☀", "Black Sun with Rays (U+2600)"}, |
| {L"♥", "Black Heart Suit (U+2665)"}, |
| |
| {L"Hello世界", "Mixed English and Chinese"}, |
| {L"Café☕", "Latin with accent and emoji"}, |
| {L"Москва🏙", "Cyrillic with emoji"}, |
| {L"こんにちは🌸", "Japanese with emoji"}, |
| {L"안녕하세요🇰🇷", "Korean with flag emoji"}, |
| {L"αβγδεζηθ", "Greek alphabet sequence"}, |
| {L"∀x∈ℝ∃y≥x", "Mathematical expression"}, |
| {L"♠♥♦♣", "Card suits"}, |
| {L"←↑→↓", "Arrow directions"}, |
| {L"╔═══╗║ ║╚═══╝", "Box drawing frame"}, |
| |
| {L"苹果🍎Apple", "Mixed Chinese, emoji and English"}, |
| {L"数学∫f(x)dx=F(x)+C", "Mathematical formula with Chinese"}, |
| {L"こんにちは世界", "Japanese and Chinese"}, |
| {L"тест тест 测试 테스트", "Mixed Cyrillic, Chinese and Korean"}, |
| {L"😀😁😂🤣😃😄😅😆", "Multiple emoji sequence"}}; |
| |
| // === WRITE PHASE === |
| printf("\n--- Write Phase ---\n"); |
| |
| // Create a TermInfosWriter |
| STermInfosWriter<TCHAR>* writer = |
| _CLNEW STermInfosWriter<TCHAR>(dir, segmentName, fieldInfos, 128); |
| writer->setEnableCorrectTermWrite(true); |
| |
| // Add terms to the writer |
| TermInfo* ti = _CLNEW TermInfo(); |
| int64_t freqPointer = 0; |
| int64_t proxPointer = 1000; |
| |
| // Store terms for verification |
| std::vector<Term*> terms; |
| std::vector<TermInfo*> termInfos; |
| |
| // Add all test terms to the writer |
| for (size_t i = 0; i < testTerms.size(); i++) { |
| // Create term |
| Term* term = _CLNEW Term(_T("content"), testTerms[i].str.c_str()); |
| terms.push_back(term); |
| |
| // Create and store term info |
| TermInfo* currentTi = _CLNEW TermInfo(); |
| currentTi->docFreq = i + 1; |
| currentTi->freqPointer = freqPointer; |
| currentTi->proxPointer = proxPointer; |
| currentTi->skipOffset = (i % 10 == 0) ? 10 : 0; |
| termInfos.push_back(currentTi); |
| |
| // Add term to writer |
| TermInfo tempTi; |
| tempTi.docFreq = currentTi->docFreq; |
| tempTi.freqPointer = currentTi->freqPointer; |
| tempTi.proxPointer = currentTi->proxPointer; |
| tempTi.skipOffset = currentTi->skipOffset; |
| writer->add(term->field(), term->text(), term->textLength(), &tempTi); |
| |
| printf("Added Term #%zu: \"", i); |
| print_wstring(testTerms[i].str); |
| printf("\" - %s\n", testTerms[i].description); |
| |
| // Increment pointers for next term |
| freqPointer += 100 + i * 10; |
| proxPointer += 200 + i * 20; |
| } |
| |
| // Close the writer |
| writer->close(); |
| _CLDELETE(writer); |
| _CLDELETE(ti); |
| |
| // === READ PHASE === |
| printf("\n--- Read Phase ---\n"); |
| |
| // Create a TermInfosReader |
| TermInfosReader* reader = _CLNEW TermInfosReader(dir, segmentName, fieldInfos); |
| |
| // Verify each term can be read back correctly |
| for (size_t i = 0; i < terms.size(); i++) { |
| Term* originalTerm = terms[i]; |
| TermInfo* originalInfo = termInfos[i]; |
| |
| // Read term info |
| auto readInfo = reader->get(originalTerm, nullptr); |
| |
| printf("Term #%zu: \"", i); |
| print_wstring(testTerms[i].str); |
| printf("\" - %s\n", readInfo ? "Found" : "NOT FOUND"); |
| |
| if (readInfo) { |
| // Verify values match |
| CuAssertTrue(tc, originalInfo->docFreq == readInfo->docFreq); |
| CuAssertTrue(tc, originalInfo->freqPointer == readInfo->freqPointer); |
| CuAssertTrue(tc, originalInfo->proxPointer == readInfo->proxPointer); |
| } |
| |
| _CLDELETE(readInfo); |
| } |
| |
| // Verify term enumeration |
| SegmentTermEnum* termEnum = reader->terms(); |
| size_t count = 0; |
| |
| printf("\n--- Term Enumeration ---\n"); |
| while (termEnum->next()) { |
| Term* term = termEnum->term(false); |
| printf("Enum #%zu: ", count); |
| print_wstring(term->text()); |
| CuAssertTrueWithMessage(tc, "Term text mismatch", |
| _tcscmp(term->text(), testTerms[count].str.c_str()) == 0); |
| count++; |
| } |
| |
| printf("Total enumerated terms: %zu (expected: %zu)\n", count, terms.size()); |
| CuAssertTrue(tc, (int)terms.size() == (int)count); |
| |
| _CLDELETE(termEnum); |
| _CLDELETE(reader); |
| |
| // Clean up |
| for (size_t i = 0; i < terms.size(); i++) { |
| _CLDELETE(terms[i]); |
| _CLDELETE(termInfos[i]); |
| } |
| |
| _CLDELETE(fieldInfos); |
| _CLDELETE(dir); |
| |
| printf("STermInfosWriter/Reader Unicode test completed successfully\n"); |
| } |
| |
| // Test STermInfosWriter with Unicode characters when correctTermWrite is disabled |
| static void TestSTermInfosWriterUnicodeDisabled(CuTest* tc) { |
| printf("\n=== Testing STermInfosWriter<TCHAR> with Unicode (Disabled Correct Term Write) " |
| "===\n"); |
| |
| // Create a RAM directory for testing |
| Directory* dir = _CLNEW RAMDirectory(); |
| |
| // Create field infos |
| FieldInfos* fieldInfos = _CLNEW FieldInfos(); |
| fieldInfos->add(_T("content"), false); |
| |
| const char* segmentName = "test_unicode_disabled"; |
| |
| // Define Unicode test cases - one normal char and one > 0xFFFF |
| struct UnicodeTermTest { |
| std::wstring str; |
| const char* description; |
| bool shouldCorruptOnRead; |
| }; |
| |
| std::vector<UnicodeTermTest> testTerms = { |
| {L"中", "CJK Unified Ideograph (U+4E2D)", false}, // Regular Unicode character |
| {L"𠜎", "CJK Unified Ideograph Extension B (U+2070E)", |
| true} // Character beyond BMP (> 0xFFFF) |
| }; |
| |
| // === WRITE PHASE === |
| printf("\n--- Write Phase ---\n"); |
| |
| // Create a TermInfosWriter with correctTermWrite disabled |
| STermInfosWriter<TCHAR>* writer = |
| _CLNEW STermInfosWriter<TCHAR>(dir, segmentName, fieldInfos, 128); |
| writer->setEnableCorrectTermWrite(false); // Disable correct term write |
| |
| // Add terms to the writer |
| int64_t freqPointer = 0; |
| int64_t proxPointer = 1000; |
| |
| // Store terms for verification |
| std::vector<Term*> terms; |
| std::vector<TermInfo*> termInfos; |
| std::vector<std::wstring> originalStrings; |
| |
| // Add all test terms to the writer |
| for (size_t i = 0; i < testTerms.size(); i++) { |
| // Store original string for later comparison |
| originalStrings.push_back(testTerms[i].str); |
| |
| // Create term |
| Term* term = _CLNEW Term(_T("content"), testTerms[i].str.c_str()); |
| terms.push_back(term); |
| |
| // Create and store term info |
| TermInfo* currentTi = _CLNEW TermInfo(); |
| currentTi->docFreq = i + 1; |
| currentTi->freqPointer = freqPointer; |
| currentTi->proxPointer = proxPointer; |
| currentTi->skipOffset = 0; |
| termInfos.push_back(currentTi); |
| |
| // Add term to writer |
| TermInfo tempTi; |
| tempTi.docFreq = currentTi->docFreq; |
| tempTi.freqPointer = currentTi->freqPointer; |
| tempTi.proxPointer = currentTi->proxPointer; |
| tempTi.skipOffset = currentTi->skipOffset; |
| writer->add(term->field(), term->text(), term->textLength(), &tempTi); |
| |
| printf("Added Term #%zu: \"", i); |
| print_wstring(testTerms[i].str); |
| printf("\" - %s\n", testTerms[i].description); |
| |
| // Increment pointers for next term |
| freqPointer += 100 + i * 10; |
| proxPointer += 200 + i * 20; |
| } |
| |
| // Close the writer |
| writer->close(); |
| _CLDELETE(writer); |
| |
| // === READ PHASE === |
| printf("\n--- Read Phase ---\n"); |
| |
| // Create a TermInfosReader |
| TermInfosReader* reader = _CLNEW TermInfosReader(dir, segmentName, fieldInfos); |
| |
| // Verify each term's read behavior |
| for (size_t i = 0; i < terms.size(); i++) { |
| Term* originalTerm = terms[i]; |
| TermInfo* originalInfo = termInfos[i]; |
| |
| // Read term info |
| auto readInfo = reader->get(originalTerm, nullptr); |
| |
| printf("Term #%zu: \"", i); |
| print_wstring(testTerms[i].str); |
| printf("\" - %s\n", readInfo ? "Found" : "NOT FOUND"); |
| |
| if (readInfo) { |
| // Verify values match |
| CuAssertTrue(tc, originalInfo->docFreq == readInfo->docFreq); |
| CuAssertTrue(tc, originalInfo->freqPointer == readInfo->freqPointer); |
| CuAssertTrue(tc, originalInfo->proxPointer == readInfo->proxPointer); |
| } |
| |
| _CLDELETE(readInfo); |
| } |
| |
| // Verify term enumeration |
| SegmentTermEnum* termEnum = reader->terms(); |
| size_t count = 0; |
| |
| printf("\n--- Term Enumeration ---\n"); |
| while (termEnum->next()) { |
| Term* term = termEnum->term(false); |
| printf("Enum #%zu: ", count); |
| print_wstring(term->text()); |
| |
| // For regular Unicode characters, they should be preserved correctly |
| if (!testTerms[count].shouldCorruptOnRead) { |
| CuAssertTrueWithMessage(tc, "Regular Unicode term text should match", |
| _tcscmp(term->text(), originalStrings[count].c_str()) == 0); |
| } else { |
| // For characters beyond BMP (>0xFFFF), they should NOT match due to disabled correctTermWrite |
| printf(" - Expected corruption for high Unicode character\n"); |
| CuAssertTrueWithMessage(tc, "High Unicode term should be corrupted", |
| _tcscmp(term->text(), originalStrings[count].c_str()) != 0); |
| } |
| count++; |
| } |
| |
| printf("Total enumerated terms: %zu (expected: %zu)\n", count, terms.size()); |
| CuAssertTrue(tc, (int)terms.size() == (int)count); |
| |
| _CLDELETE(termEnum); |
| _CLDELETE(reader); |
| |
| // Clean up |
| for (size_t i = 0; i < terms.size(); i++) { |
| _CLDELETE(terms[i]); |
| _CLDELETE(termInfos[i]); |
| } |
| |
| _CLDELETE(fieldInfos); |
| _CLDELETE(dir); |
| |
| printf("STermInfosWriter/Reader Unicode Disabled test completed\n"); |
| } |
| |
| CuSuite* testUTF8CharsSuite() { |
| CuSuite* suite = CuSuiteNew(_T("UTF-8 Character Test Suite")); |
| |
| SUITE_ADD_TEST(suite, TestUTF8WriteAndReadChars); |
| SUITE_ADD_TEST(suite, TestUnicodeRanges); |
| SUITE_ADD_TEST(suite, TestEdgeCases); |
| SUITE_ADD_TEST(suite, TestUTF8EncodingBoundaries); |
| SUITE_ADD_TEST(suite, TestSpecialUTF8Sequences); |
| SUITE_ADD_TEST(suite, TestUTF8Performance); |
| SUITE_ADD_TEST(suite, TestUTF8Compatibility); |
| SUITE_ADD_TEST(suite, TestSTermInfosWriterUnicode); |
| SUITE_ADD_TEST(suite, TestSTermInfosWriterUnicodeDisabled); |
| |
| return suite; |
| } |