/* * Copyright (C) 2013 Google Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "platform/text/BidiResolver.h" #include "platform/text/BidiTestHarness.h" #include "platform/text/TextRunIterator.h" #include "wtf/OwnPtr.h" #include #include namespace { using namespace WTF; using namespace WebCore; TEST(BidiResolver, Basic) { bool hasStrongDirectionality; String value("foo"); TextRun run(value); BidiResolver bidiResolver; bidiResolver.setStatus(BidiStatus(run.direction(), run.directionalOverride())); bidiResolver.setPositionIgnoringNestedIsolates(TextRunIterator(&run, 0)); TextDirection direction = bidiResolver.determineParagraphDirectionality(&hasStrongDirectionality); EXPECT_TRUE(hasStrongDirectionality); EXPECT_EQ(LTR, direction); } TextDirection determineParagraphDirectionality(const TextRun& textRun, bool* hasStrongDirectionality = 0) { BidiResolver resolver; resolver.setStatus(BidiStatus(LTR, false)); resolver.setPositionIgnoringNestedIsolates(TextRunIterator(&textRun, 0)); return resolver.determineParagraphDirectionality(hasStrongDirectionality); } struct TestData { UChar text[3]; size_t length; TextDirection expectedDirection; bool expectedStrong; }; void testDirectionality(const TestData& entry) { bool hasStrongDirectionality; String data(entry.text, entry.length); TextRun run(data); TextDirection direction = determineParagraphDirectionality(run, &hasStrongDirectionality); EXPECT_EQ(entry.expectedStrong, hasStrongDirectionality); EXPECT_EQ(entry.expectedDirection, direction); } TEST(BidiResolver, ParagraphDirectionSurrogates) { const TestData testData[] = { // Test strong RTL, non-BMP. (U+10858 Imperial Aramaic number one, strong RTL) { { 0xD802, 0xDC58 }, 2, RTL, true }, // Test strong LTR, non-BMP. (U+1D15F Musical symbol quarter note, strong LTR) { { 0xD834, 0xDD5F }, 2, LTR, true }, // Test broken surrogate: valid leading, invalid trail. (Lead of U+10858, space) { { 0xD802, ' ' }, 2, LTR, false }, // Test broken surrogate: invalid leading. (Trail of U+10858, U+05D0 Hebrew Alef) { { 0xDC58, 0x05D0 }, 2, RTL, true }, // Test broken surrogate: valid leading, invalid trail/valid lead, valid trail. { { 0xD802, 0xD802, 0xDC58 }, 3, RTL, true }, // Test broken surrogate: valid leading, no trail (string too short). (Lead of U+10858) { { 0xD802, 0xDC58 }, 1, LTR, false }, // Test broken surrogate: trail appearing before lead. (U+10858 units reversed) { { 0xDC58, 0xD802 }, 2, LTR, false } }; for (size_t i = 0; i < WTF_ARRAY_LENGTH(testData); ++i) testDirectionality(testData[i]); } class BidiTestRunner { public: BidiTestRunner() : m_testsRun(0) , m_testsSkipped(0) , m_ignoredCharFailures(0) , m_levelFailures(0) , m_orderFailures(0) { } void skipTestsWith(UChar codepoint) { m_skippedCodePoints.insert(codepoint); } void runTest(const std::basic_string& input, const std::vector& reorder, const std::vector& levels, bidi_test::ParagraphDirection, const std::string& line, size_t lineNumber); size_t m_testsRun; size_t m_testsSkipped; std::set m_skippedCodePoints; size_t m_ignoredCharFailures; size_t m_levelFailures; size_t m_orderFailures; }; // Blink's UBA does not filter out control characters, etc. Maybe it should? // Instead it depends on later layers of Blink to simply ignore them. // This function helps us emulate that to be compatible with BidiTest.txt expectations. static bool isNonRenderedCodePoint(UChar c) { // The tests also expect us to ignore soft-hyphen. if (c == 0xAD) return true; // Control characters are not rendered: return c >= 0x202A && c <= 0x202E; // But it seems to expect LRI, etc. to be rendered!? } std::string diffString(const std::vector& actual, const std::vector& expected) { std::ostringstream diff; diff << "actual: "; // This is the magical way to print a vector to a stream, clear, right? std::copy(actual.begin(), actual.end(), std::ostream_iterator(diff, " ")); diff << " expected: "; std::copy(expected.begin(), expected.end(), std::ostream_iterator(diff, " ")); return diff.str(); } void BidiTestRunner::runTest(const std::basic_string& input, const std::vector& expectedOrder, const std::vector& expectedLevels, bidi_test::ParagraphDirection paragraphDirection, const std::string& line, size_t lineNumber) { if (!m_skippedCodePoints.empty()) { for (size_t i = 0; i < input.size(); i++) { if (m_skippedCodePoints.count(input[i])) { m_testsSkipped++; return; } } } m_testsRun++; TextRun textRun(input.data(), input.size()); switch (paragraphDirection) { case bidi_test::DirectionAutoLTR: textRun.setDirection(determineParagraphDirectionality(textRun)); break; case bidi_test::DirectionLTR: textRun.setDirection(LTR); break; case bidi_test::DirectionRTL: textRun.setDirection(RTL); break; } BidiResolver resolver; resolver.setStatus(BidiStatus(textRun.direction(), textRun.directionalOverride())); resolver.setPositionIgnoringNestedIsolates(TextRunIterator(&textRun, 0)); BidiRunList& runs = resolver.runs(); resolver.createBidiRunsForLine(TextRunIterator(&textRun, textRun.length())); std::ostringstream errorContext; errorContext << ", line " << lineNumber << " \"" << line << "\""; errorContext << " context: " << bidi_test::nameFromParagraphDirection(paragraphDirection); std::vector actualOrder; std::vector actualLevels; actualLevels.assign(input.size(), -1); BidiCharacterRun* run = runs.firstRun(); while (run) { // Blink's UBA just makes runs, the actual ordering of the display of characters // is handled later in our pipeline, so we fake it here: bool reversed = run->reversed(false); ASSERT(run->stop() >= run->start()); size_t length = run->stop() - run->start(); for (size_t i = 0; i < length; i++) { int inputIndex = reversed ? run->stop() - i - 1 : run->start() + i; if (!isNonRenderedCodePoint(input[inputIndex])) actualOrder.push_back(inputIndex); // BidiTest.txt gives expected level data in the order of the original input. actualLevels[inputIndex] = run->level(); } run = run->next(); } if (expectedOrder.size() != actualOrder.size()) { m_ignoredCharFailures++; EXPECT_EQ(expectedOrder.size(), actualOrder.size()) << errorContext.str(); } else if (expectedOrder != actualOrder) { m_orderFailures++; printf("ORDER %s%s\n", diffString(actualOrder, expectedOrder).c_str(), errorContext.str().c_str()); } if (expectedLevels.size() != actualLevels.size()) { m_ignoredCharFailures++; EXPECT_EQ(expectedLevels.size(), actualLevels.size()) << errorContext.str(); } else { for (size_t i = 0; i < expectedLevels.size(); i++) { // level == -1 means the level should be ignored. if (expectedLevels[i] == actualLevels[i] || expectedLevels[i] == -1) continue; printf("LEVELS %s%s\n", diffString(actualLevels, expectedLevels).c_str(), errorContext.str().c_str()); m_levelFailures++; break; } } } TEST(BidiResolver, BidiTest_txt) { BidiTestRunner runner; // Blink's Unicode Bidi Algorithm (UBA) doesn't yet support the // new isolate directives from Unicode 6.3: // http://www.unicode.org/reports/tr9/#Explicit_Directional_Isolates runner.skipTestsWith(0x2066); // LRI runner.skipTestsWith(0x2067); // RLI runner.skipTestsWith(0x2068); // FSI runner.skipTestsWith(0x2069); // PDI // This code wants to use PathService from base/path_service.h // but we aren't allowed to depend on base/ directly from Blink yet. // Alternatively we could use: // blink::Platform::current()->unitTestSupport()->webKitRootDir() // and a relative path, but that would require running inside // webkit_unit_tests (to have a functioning Platform object). // The file we want is: // src/third_party/icu/source/test/testdata/BidiTest.txt // but we don't have any good way to find it from this unittest. // Just assume we're running this test manually for now. On the // bots we just print a warning that we can't find the test file. std::string bidiTestPath = "BidiTest.txt"; std::ifstream bidiTestFile(bidiTestPath.c_str()); if (!bidiTestFile.is_open()) { printf("ERROR: Failed to open BidiTest.txt, cannot run tests.\n"); return; } bidi_test::Harness harness(runner); harness.parse(bidiTestFile); bidiTestFile.close(); if (runner.m_testsSkipped) printf("WARNING: Skipped %zu tests.\n", runner.m_testsSkipped); printf("Ran %zu tests: %zu level failures %zu order failures.\n", runner.m_testsRun, runner.m_levelFailures, runner.m_orderFailures); // The unittest harness only pays attention to GTest output, so we verify // that the tests behaved as expected: EXPECT_EQ(352098u, runner.m_testsRun); EXPECT_EQ(418143u, runner.m_testsSkipped); EXPECT_EQ(0u, runner.m_ignoredCharFailures); EXPECT_EQ(44882u, runner.m_levelFailures); EXPECT_EQ(19151u, runner.m_orderFailures); } }