diff --git a/icu4c/source/test/intltest/numbertest.h b/icu4c/source/test/intltest/numbertest.h index 67f8ce71867..1d0f80ae670 100644 --- a/icu4c/source/test/intltest/numbertest.h +++ b/icu4c/source/test/intltest/numbertest.h @@ -70,7 +70,7 @@ class NumberFormatterApiTest : public IntlTest { void scale(); void locale(); void formatTypes(); - void fieldPosition(); + void fieldPositionLogic(); void toFormat(); void errors(); void validRanges(); @@ -111,11 +111,15 @@ class NumberFormatterApiTest : public IntlTest { void assertFormatDescendingBig(const char16_t* message, const char16_t* skeleton, const UnlocalizedNumberFormatter& f, Locale locale, ...); - void assertFormatSingle(const char16_t* message, const char16_t* skeleton, - const UnlocalizedNumberFormatter& f, Locale locale, double input, - const UnicodeString& expected); + FormattedNumber + assertFormatSingle(const char16_t* message, const char16_t* skeleton, + const UnlocalizedNumberFormatter& f, Locale locale, double input, + const UnicodeString& expected); void assertUndefinedSkeleton(const UnlocalizedNumberFormatter& f); + + void assertFieldPositions(const char16_t* message, const FormattedNumber& formattedNumber, + const UFieldPosition* expectedFieldPositions, int32_t length); }; class DecimalQuantityTest : public IntlTest { diff --git a/icu4c/source/test/intltest/numbertest_api.cpp b/icu4c/source/test/intltest/numbertest_api.cpp index 8d4f602d98e..992464f357d 100644 --- a/icu4c/source/test/intltest/numbertest_api.cpp +++ b/icu4c/source/test/intltest/numbertest_api.cpp @@ -84,7 +84,7 @@ void NumberFormatterApiTest::runIndexedTest(int32_t index, UBool exec, const cha TESTCASE_AUTO(scale); TESTCASE_AUTO(locale); TESTCASE_AUTO(formatTypes); - TESTCASE_AUTO(fieldPosition); + TESTCASE_AUTO(fieldPositionLogic); TESTCASE_AUTO(toFormat); TESTCASE_AUTO(errors); TESTCASE_AUTO(validRanges); @@ -2164,10 +2164,18 @@ void NumberFormatterApiTest::formatTypes() { assertEquals("Format decNumber to 40 digits", str, actual); } -void NumberFormatterApiTest::fieldPosition() { - IcuTestErrorCode status(*this, "fieldPosition"); - FormattedNumber fmtd = NumberFormatter::withLocale("en").formatDouble(-9876543210.12, status); - assertEquals("Should have expected format output", u"-9,876,543,210.12", fmtd.toString(status)); +void NumberFormatterApiTest::fieldPositionLogic() { + IcuTestErrorCode status(*this, "fieldPositionLogic"); + + const char16_t* message = u"Field position logic test"; + + FormattedNumber fmtd = assertFormatSingle( + message, + u"", + NumberFormatter::with(), + Locale::getEnglish(), + -9876543210.12, + u"-9,876,543,210.12"); static const UFieldPosition expectedFieldPositions[] = { // field, begin index, end index @@ -2179,56 +2187,15 @@ void NumberFormatterApiTest::fieldPosition() { {UNUM_DECIMAL_SEPARATOR_FIELD, 14, 15}, {UNUM_FRACTION_FIELD, 15, 17}}; - FieldPositionIterator fpi; - fmtd.getAllFieldPositions(fpi, status); - int32_t i = 0; - FieldPosition actual; - while (fpi.next(actual)) { - UFieldPosition expected = expectedFieldPositions[i++]; - assertEquals( - UnicodeString(u"Field, case #") + Int64ToUnicodeString(i), - expected.field, - actual.getField()); - assertEquals( - UnicodeString(u"Iterator, begin index, case #") + Int64ToUnicodeString(i), - expected.beginIndex, - actual.getBeginIndex()); - assertEquals( - UnicodeString(u"Iterator, end index, case #") + Int64ToUnicodeString(i), - expected.endIndex, - actual.getEndIndex()); - - // Check for the first location of the field - if (expected.field != UNUM_GROUPING_SEPARATOR_FIELD) { - FieldPosition actual2(expected.field); - UBool found = fmtd.nextFieldPosition(actual2, status); - assertEquals( - UnicodeString(u"Next, found first time, case #") + Int64ToUnicodeString(i), - (UBool) TRUE, - found); - assertEquals( - UnicodeString(u"Next, begin index, case #") + Int64ToUnicodeString(i), - expected.beginIndex, - actual2.getBeginIndex()); - assertEquals( - UnicodeString(u"Next, end index, case #") + Int64ToUnicodeString(i), - expected.endIndex, - actual2.getEndIndex()); - found = fmtd.nextFieldPosition(actual2, status); - assertEquals( - UnicodeString(u"Next, found second time, case #") + Int64ToUnicodeString(i), - (UBool) FALSE, - found); - } - } - assertEquals( - "Should have seen every field position", - sizeof(expectedFieldPositions) / sizeof(*expectedFieldPositions), - i); + assertFieldPositions( + message, + fmtd, + expectedFieldPositions, + sizeof(expectedFieldPositions)/sizeof(*expectedFieldPositions)); // Test the iteration functionality of nextFieldPosition - actual = {UNUM_GROUPING_SEPARATOR_FIELD}; - i = 1; + FieldPosition actual = {UNUM_GROUPING_SEPARATOR_FIELD}; + int32_t i = 1; while (fmtd.nextFieldPosition(actual, status)) { UFieldPosition expected = expectedFieldPositions[i++]; assertEquals( @@ -2614,15 +2581,17 @@ void NumberFormatterApiTest::assertFormatDescendingBig(const char16_t* umessage, } } -void NumberFormatterApiTest::assertFormatSingle(const char16_t* umessage, const char16_t* uskeleton, - const UnlocalizedNumberFormatter& f, Locale locale, - double input, const UnicodeString& expected) { +FormattedNumber +NumberFormatterApiTest::assertFormatSingle(const char16_t* umessage, const char16_t* uskeleton, + const UnlocalizedNumberFormatter& f, Locale locale, + double input, const UnicodeString& expected) { UnicodeString message(TRUE, umessage, -1); const LocalizedNumberFormatter l1 = f.threshold(0).locale(locale); // no self-regulation const LocalizedNumberFormatter l2 = f.threshold(1).locale(locale); // all self-regulation IcuTestErrorCode status(*this, "assertFormatSingle"); status.setScope(message); - UnicodeString actual1 = l1.formatDouble(input, status).toString(); + FormattedNumber result1 = l1.formatDouble(input, status); + UnicodeString actual1 = result1.toString(); assertSuccess(message + u": Unsafe Path", status); assertEquals(message + u": Unsafe Path", expected, actual1); UnicodeString actual2 = l2.formatDouble(input, status).toString(); @@ -2640,6 +2609,7 @@ void NumberFormatterApiTest::assertFormatSingle(const char16_t* umessage, const } else { assertUndefinedSkeleton(f); } + return result1; } void NumberFormatterApiTest::assertUndefinedSkeleton(const UnlocalizedNumberFormatter& f) { @@ -2651,4 +2621,67 @@ void NumberFormatterApiTest::assertUndefinedSkeleton(const UnlocalizedNumberForm status); } +void NumberFormatterApiTest::assertFieldPositions( + const char16_t* message, const FormattedNumber& formattedNumber, + const UFieldPosition* expectedFieldPositions, int32_t length) { + IcuTestErrorCode status(*this, "assertFieldPositions"); + UnicodeString baseMessage = UnicodeString(message) + u": " + formattedNumber.toString(status) + u": "; + FieldPositionIterator fpi; + formattedNumber.getAllFieldPositions(fpi, status); + int32_t i = 0; + FieldPosition actual; + while (fpi.next(actual)) { + UFieldPosition expected = expectedFieldPositions[i++]; + assertEquals( + baseMessage + UnicodeString(u"Field, case #") + Int64ToUnicodeString(i), + expected.field, + actual.getField()); + assertEquals( + baseMessage + UnicodeString(u"Iterator, begin, case #") + Int64ToUnicodeString(i), + expected.beginIndex, + actual.getBeginIndex()); + assertEquals( + baseMessage + UnicodeString(u"Iterator, end, case #") + Int64ToUnicodeString(i), + expected.endIndex, + actual.getEndIndex()); + + // Check for the first location of the field + FieldPosition actual2(expected.field); + // Fast-forward the field to skip previous occurrences of the field: + actual2.setBeginIndex(expected.beginIndex); + actual2.setEndIndex(expected.beginIndex); + UBool found = formattedNumber.nextFieldPosition(actual2, status); + assertEquals( + baseMessage + UnicodeString(u"Next, found first, case #") + Int64ToUnicodeString(i), + (UBool) TRUE, + found); + assertEquals( + baseMessage + UnicodeString(u"Next, begin, case #") + Int64ToUnicodeString(i), + expected.beginIndex, + actual2.getBeginIndex()); + assertEquals( + baseMessage + UnicodeString(u"Next, end, case #") + Int64ToUnicodeString(i), + expected.endIndex, + actual2.getEndIndex()); + + // The next position should be empty unless the field occurs again + UBool occursAgain = false; + for (int32_t j=i; j allAttributes = fpi.getAllAttributeKeys(); - assertEquals("All known fields should be in the iterator", 5, allAttributes.size()); - assertEquals("Iterator should have length of string output", 17, fpi.getEndIndex()); - int i = 0; - for (char c = fpi.first(); c != AttributedCharacterIterator.DONE; c = fpi.next(), i++) { - Set currentAttributes = fpi.getAttributes().keySet(); - int attributesRemaining = currentAttributes.size(); - for (Object[] cas : expectedFieldPositions) { - NumberFormat.Field expectedField = (NumberFormat.Field) cas[0]; - int expectedBeginIndex = (Integer) cas[1]; - int expectedEndIndex = (Integer) cas[2]; - if (expectedBeginIndex > i || expectedEndIndex <= i) { - // Field position does not overlap with the current character - continue; - } - - assertTrue("Current character should have expected field", currentAttributes.contains(expectedField)); - assertTrue("Field should be a known attribute", allAttributes.contains(expectedField)); - int actualBeginIndex = fpi.getRunStart(expectedField); - int actualEndIndex = fpi.getRunLimit(expectedField); - assertEquals(expectedField + " begin index @" + i, expectedBeginIndex, actualBeginIndex); - assertEquals(expectedField + " end index @" + i, expectedEndIndex, actualEndIndex); - attributesRemaining--; - } - assertEquals("Should have looked at every field", 0, attributesRemaining); - } - assertEquals("Should have looked at every character", 17, i); + assertFieldPositions(message, fmtd, expectedFieldPositions); // Test the iteration functionality of nextFieldPosition FieldPosition actual = new FieldPosition(NumberFormat.Field.GROUPING_SEPARATOR); - i = 1; + int i = 1; while (fmtd.nextFieldPosition(actual)) { Object[] cas = expectedFieldPositions[i++]; NumberFormat.Field expectedField = (NumberFormat.Field) cas[0]; @@ -2444,7 +2424,7 @@ public class NumberFormatterApiTest { } } - static void assertFormatSingle( + static FormattedNumber assertFormatSingle( String message, String skeleton, UnlocalizedNumberFormatter f, @@ -2453,7 +2433,8 @@ public class NumberFormatterApiTest { String expected) { LocalizedNumberFormatter l1 = f.threshold(0L).locale(locale); // no self-regulation LocalizedNumberFormatter l2 = f.threshold(1L).locale(locale); // all self-regulation - String actual1 = l1.format(input).toString(); + FormattedNumber result1 = l1.format(input); + String actual1 = result1.toString(); assertEquals(message + ": Unsafe Path: " + input, expected, actual1); String actual2 = l2.format(input).toString(); assertEquals(message + ": Safe Path: " + input, expected, actual2); @@ -2468,6 +2449,7 @@ public class NumberFormatterApiTest { } else { assertUndefinedSkeleton(f); } + return result1; } static void assertFormatSingleMeasure( @@ -2502,4 +2484,44 @@ public class NumberFormatterApiTest { fail("Expected toSkeleton to fail, but it passed, producing: " + skeleton); } catch (UnsupportedOperationException expected) {} } + + static void assertFieldPositions(String message, FormattedNumber formattedNumber, Object[][] expectedFieldPositions) { + // Calculate some initial expected values + int stringLength = formattedNumber.toString().length(); + HashSet uniqueFields = new HashSet<>(); + for (int i=0; i allAttributes = fpi.getAllAttributeKeys(); + assertEquals(baseMessage + "All known fields should be in the iterator", uniqueFields.size(), allAttributes.size()); + assertEquals(baseMessage + "Iterator should have length of string output", stringLength, fpi.getEndIndex()); + int i = 0; + for (char c = fpi.first(); c != AttributedCharacterIterator.DONE; c = fpi.next(), i++) { + Set currentAttributes = fpi.getAttributes().keySet(); + int attributesRemaining = currentAttributes.size(); + for (Object[] cas : expectedFieldPositions) { + NumberFormat.Field expectedField = (NumberFormat.Field) cas[0]; + int expectedBeginIndex = (Integer) cas[1]; + int expectedEndIndex = (Integer) cas[2]; + if (expectedBeginIndex > i || expectedEndIndex <= i) { + // Field position does not overlap with the current character + continue; + } + + assertTrue(baseMessage + "Current character should have expected field", currentAttributes.contains(expectedField)); + assertTrue(baseMessage + "Field should be a known attribute", allAttributes.contains(expectedField)); + int actualBeginIndex = fpi.getRunStart(expectedField); + int actualEndIndex = fpi.getRunLimit(expectedField); + assertEquals(baseMessage + expectedField + " begin @" + i, expectedBeginIndex, actualBeginIndex); + assertEquals(baseMessage + expectedField + " end @" + i, expectedEndIndex, actualEndIndex); + attributesRemaining--; + } + assertEquals(baseMessage + "Should have looked at every field", 0, attributesRemaining); + } + assertEquals(baseMessage + "Should have looked at every character", stringLength, i); + } }