| // © 2020 and later: Unicode, Inc. and others. | 
 | // License & terms of use: http://www.unicode.org/copyright.html#License | 
 |  | 
 | #include "unicode/utypes.h" | 
 |  | 
 | #if !UCONFIG_NO_FORMATTING | 
 |  | 
 | #include <cmath> | 
 | #include <iostream> | 
 |  | 
 | #include "charstr.h" | 
 | #include "cmemory.h" | 
 | #include "filestrm.h" | 
 | #include "intltest.h" | 
 | #include "number_decimalquantity.h" | 
 | #include "putilimp.h" | 
 | #include "unicode/ctest.h" | 
 | #include "unicode/measunit.h" | 
 | #include "unicode/measure.h" | 
 | #include "unicode/unistr.h" | 
 | #include "unicode/unum.h" | 
 | #include "unicode/ures.h" | 
 | #include "units_complexconverter.h" | 
 | #include "units_converter.h" | 
 | #include "units_data.h" | 
 | #include "units_router.h" | 
 | #include "uparse.h" | 
 | #include "uresimp.h" | 
 |  | 
 | struct UnitConversionTestCase { | 
 |     const StringPiece source; | 
 |     const StringPiece target; | 
 |     const double inputValue; | 
 |     const double expectedValue; | 
 | }; | 
 |  | 
 | using ::icu::number::impl::DecimalQuantity; | 
 | using namespace ::icu::units; | 
 |  | 
 | class UnitsTest : public IntlTest { | 
 |   public: | 
 |     UnitsTest() {} | 
 |  | 
 |     void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = nullptr) override; | 
 |  | 
 |     void testUnitConstantFreshness(); | 
 |     void testExtractConvertibility(); | 
 |     void testConversionInfo(); | 
 |     void testConverterWithCLDRTests(); | 
 |     void testComplexUnitsConverter(); | 
 |     void testComplexUnitsConverterSorting(); | 
 |     void testUnitPreferencesWithCLDRTests(); | 
 |     void testConverter(); | 
 | }; | 
 |  | 
 | extern IntlTest *createUnitsTest() { return new UnitsTest(); } | 
 |  | 
 | void UnitsTest::runIndexedTest(int32_t index, UBool exec, const char *&name, char * /*par*/) { | 
 |     if (exec) { | 
 |         logln("TestSuite UnitsTest: "); | 
 |     } | 
 |     TESTCASE_AUTO_BEGIN; | 
 |     TESTCASE_AUTO(testUnitConstantFreshness); | 
 |     TESTCASE_AUTO(testExtractConvertibility); | 
 |     TESTCASE_AUTO(testConversionInfo); | 
 |     TESTCASE_AUTO(testConverterWithCLDRTests); | 
 |     TESTCASE_AUTO(testComplexUnitsConverter); | 
 |     TESTCASE_AUTO(testComplexUnitsConverterSorting); | 
 |     TESTCASE_AUTO(testUnitPreferencesWithCLDRTests); | 
 |     TESTCASE_AUTO(testConverter); | 
 |     TESTCASE_AUTO_END; | 
 | } | 
 |  | 
 | // Tests the hard-coded constants in the code against constants that appear in | 
 | // units.txt. | 
 | void UnitsTest::testUnitConstantFreshness() { | 
 |     IcuTestErrorCode status(*this, "testUnitConstantFreshness"); | 
 |     LocalUResourceBundlePointer unitsBundle(ures_openDirect(nullptr, "units", status)); | 
 |     LocalUResourceBundlePointer unitConstants( | 
 |         ures_getByKey(unitsBundle.getAlias(), "unitConstants", nullptr, status)); | 
 |  | 
 |     while (ures_hasNext(unitConstants.getAlias())) { | 
 |         int32_t len; | 
 |         const char *constant = nullptr; | 
 |         ures_getNextString(unitConstants.getAlias(), &len, &constant, status); | 
 |  | 
 |         Factor factor; | 
 |         addSingleFactorConstant(constant, 1, POSITIVE, factor, status); | 
 |         if (status.errDataIfFailureAndReset( | 
 |                 "addSingleFactorConstant(<%s>, ...).\n\n" | 
 |                 "If U_INVALID_FORMAT_ERROR, please check that \"icu4c/source/i18n/units_converter.cpp\" " | 
 |                 "has all constants? Is \"%s\" a new constant?\n" | 
 |                 "See docs/processes/release/tasks/updating-measure-unit.md for more information.\n", | 
 |                 constant, constant)) { | 
 |             continue; | 
 |         } | 
 |  | 
 |         // Check the values of constants that have a simple numeric value | 
 |         factor.substituteConstants(); | 
 |         int32_t uLen; | 
 |         UnicodeString uVal = ures_getStringByKey(unitConstants.getAlias(), constant, &uLen, status); | 
 |         CharString val; | 
 |         val.appendInvariantChars(uVal, status); | 
 |         if (status.errDataIfFailureAndReset("Failed to get constant value for %s.", constant)) { | 
 |             continue; | 
 |         } | 
 |         DecimalQuantity dqVal; | 
 |         UErrorCode parseStatus = U_ZERO_ERROR; | 
 |         // TODO(units): unify with strToDouble() in units_converter.cpp | 
 |         dqVal.setToDecNumber(val.toStringPiece(), parseStatus); | 
 |         if (!U_SUCCESS(parseStatus)) { | 
 |             // Not simple to parse, skip validating this constant's value. (We | 
 |             // leave catching mistakes to the data-driven integration tests.) | 
 |             continue; | 
 |         } | 
 |         double expectedNumerator = dqVal.toDouble(); | 
 |         assertEquals(UnicodeString("Constant ") + constant + u" numerator", expectedNumerator, | 
 |                      factor.factorNum); | 
 |         assertEquals(UnicodeString("Constant ") + constant + u" denominator", 1.0, factor.factorDen); | 
 |     } | 
 | } | 
 |  | 
 | void UnitsTest::testExtractConvertibility() { | 
 |     IcuTestErrorCode status(*this, "UnitsTest::testExtractConvertibility"); | 
 |  | 
 |     struct TestCase { | 
 |         const char *const source; | 
 |         const char *const target; | 
 |         const Convertibility expectedState; | 
 |     } testCases[]{ | 
 |         {"meter", "foot", CONVERTIBLE},                                              // | 
 |         {"kilometer", "foot", CONVERTIBLE},                                          // | 
 |         {"hectare", "square-foot", CONVERTIBLE},                                     // | 
 |         {"kilometer-per-second", "second-per-meter", RECIPROCAL},                    // | 
 |         {"square-meter", "square-foot", CONVERTIBLE},                                // | 
 |         {"kilometer-per-second", "foot-per-second", CONVERTIBLE},                    // | 
 |         {"square-hectare", "pow4-foot", CONVERTIBLE},                                // | 
 |         {"square-kilometer-per-second", "second-per-square-meter", RECIPROCAL},      // | 
 |         {"cubic-kilometer-per-second-meter", "second-per-square-meter", RECIPROCAL}, // | 
 |         {"square-meter-per-square-hour", "hectare-per-square-second", CONVERTIBLE},  // | 
 |         {"hertz", "revolution-per-second", CONVERTIBLE},                             // | 
 |         {"millimeter", "meter", CONVERTIBLE},                                        // | 
 |         {"yard", "meter", CONVERTIBLE},                                              // | 
 |         {"ounce-troy", "kilogram", CONVERTIBLE},                                     // | 
 |         {"percent", "portion", CONVERTIBLE},                                         // | 
 |         {"ofhg", "kilogram-per-square-meter-square-second", CONVERTIBLE},            // | 
 |         {"second-per-meter", "meter-per-second", RECIPROCAL},                        // | 
 |     }; | 
 |  | 
 |     for (const auto &testCase : testCases) { | 
 |         MeasureUnitImpl source = MeasureUnitImpl::forIdentifier(testCase.source, status); | 
 |         if (status.errIfFailureAndReset("source MeasureUnitImpl::forIdentifier(\"%s\", ...)", | 
 |                                         testCase.source)) { | 
 |             continue; | 
 |         } | 
 |         MeasureUnitImpl target = MeasureUnitImpl::forIdentifier(testCase.target, status); | 
 |         if (status.errIfFailureAndReset("target MeasureUnitImpl::forIdentifier(\"%s\", ...)", | 
 |                                         testCase.target)) { | 
 |             continue; | 
 |         } | 
 |  | 
 |         ConversionRates conversionRates(status); | 
 |         if (status.errIfFailureAndReset("conversionRates(status)")) { | 
 |             continue; | 
 |         } | 
 |         auto convertibility = extractConvertibility(source, target, conversionRates, status); | 
 |         if (status.errIfFailureAndReset("extractConvertibility(<%s>, <%s>, ...)", testCase.source, | 
 |                                         testCase.target)) { | 
 |             continue; | 
 |         } | 
 |  | 
 |         assertEquals(UnicodeString("Conversion Capability: ") + testCase.source + " to " + | 
 |                          testCase.target, | 
 |                      testCase.expectedState, convertibility); | 
 |     } | 
 | } | 
 |  | 
 | void UnitsTest::testConversionInfo() { | 
 |     IcuTestErrorCode status(*this, "UnitsTest::testExtractConvertibility"); | 
 |     // Test Cases | 
 |     struct TestCase { | 
 |         const char *source; | 
 |         const char *target; | 
 |         const ConversionInfo expectedConversionInfo; | 
 |     } testCases[]{ | 
 |         { | 
 |             "meter", | 
 |             "meter", | 
 |             {1.0, 0, false}, | 
 |         }, | 
 |         { | 
 |             "meter", | 
 |             "foot", | 
 |             {3.28084, 0, false}, | 
 |         }, | 
 |         { | 
 |             "foot", | 
 |             "meter", | 
 |             {0.3048, 0, false}, | 
 |         }, | 
 |         { | 
 |             "celsius", | 
 |             "kelvin", | 
 |             {1, 273.15, false}, | 
 |         }, | 
 |         { | 
 |             "fahrenheit", | 
 |             "kelvin", | 
 |             {5.0 / 9.0, 255.372, false}, | 
 |         }, | 
 |         { | 
 |             "fahrenheit", | 
 |             "celsius", | 
 |             {5.0 / 9.0, -17.7777777778, false}, | 
 |         }, | 
 |         { | 
 |             "celsius", | 
 |             "fahrenheit", | 
 |             {9.0 / 5.0, 32, false}, | 
 |         }, | 
 |         { | 
 |             "fahrenheit", | 
 |             "fahrenheit", | 
 |             {1.0, 0, false}, | 
 |         }, | 
 |         { | 
 |             "mile-per-gallon", | 
 |             "liter-per-100-kilometer", | 
 |             {0.00425143707, 0, true}, | 
 |         }, | 
 |     }; | 
 |  | 
 |     ConversionRates rates(status); | 
 |     for (const auto &testCase : testCases) { | 
 |         auto sourceImpl = MeasureUnitImpl::forIdentifier(testCase.source, status); | 
 |         auto targetImpl = MeasureUnitImpl::forIdentifier(testCase.target, status); | 
 |         UnitsConverter unitsConverter(sourceImpl, targetImpl, rates, status); | 
 |  | 
 |         if (status.errIfFailureAndReset()) { | 
 |             continue; | 
 |         } | 
 |  | 
 |         ConversionInfo actualConversionInfo = unitsConverter.getConversionInfo(); | 
 |         UnicodeString message = | 
 |             UnicodeString("testConverter: ") + testCase.source + " to " + testCase.target; | 
 |  | 
 |         double maxDelta = 1e-6 * uprv_fabs(testCase.expectedConversionInfo.conversionRate); | 
 |         if (testCase.expectedConversionInfo.conversionRate == 0) { | 
 |             maxDelta = 1e-12; | 
 |         } | 
 |         assertEqualsNear(message + ", conversion rate: ", testCase.expectedConversionInfo.conversionRate, | 
 |                          actualConversionInfo.conversionRate, maxDelta); | 
 |  | 
 |         maxDelta = 1e-6 * uprv_fabs(testCase.expectedConversionInfo.offset); | 
 |         if (testCase.expectedConversionInfo.offset == 0) { | 
 |             maxDelta = 1e-12; | 
 |         } | 
 |         assertEqualsNear(message + ", offset: ", testCase.expectedConversionInfo.offset, actualConversionInfo.offset, | 
 |                          maxDelta); | 
 |  | 
 |         assertEquals(message + ", reciprocal: ", testCase.expectedConversionInfo.reciprocal, | 
 |                      actualConversionInfo.reciprocal); | 
 |     } | 
 | } | 
 |  | 
 | void UnitsTest::testConverter() { | 
 |     IcuTestErrorCode status(*this, "UnitsTest::testConverter"); | 
 |  | 
 |     // Test Cases | 
 |     struct TestCase { | 
 |         const char *source; | 
 |         const char *target; | 
 |         const double inputValue; | 
 |         const double expectedValue; | 
 |     } testCases[]{ | 
 |         // SI Prefixes | 
 |         {"gram", "kilogram", 1.0, 0.001}, | 
 |         {"milligram", "kilogram", 1.0, 0.000001}, | 
 |         {"microgram", "kilogram", 1.0, 0.000000001}, | 
 |         {"megagram", "gram", 1.0, 1000000}, | 
 |         {"megagram", "kilogram", 1.0, 1000}, | 
 |         {"gigabyte", "byte", 1.0, 1000000000}, | 
 |         {"megawatt", "watt", 1.0, 1000000}, | 
 |         {"megawatt", "kilowatt", 1.0, 1000}, | 
 |         // Binary Prefixes | 
 |         {"kilobyte", "byte", 1, 1000}, | 
 |         {"kibibyte", "byte", 1, 1024}, | 
 |         {"mebibyte", "byte", 1, 1048576}, | 
 |         {"gibibyte", "kibibyte", 1, 1048576}, | 
 |         {"pebibyte", "tebibyte", 4, 4096}, | 
 |         {"zebibyte", "pebibyte", 1.0 / 16, 65536.0}, | 
 |         {"yobibyte", "exbibyte", 1, 1048576}, | 
 |         // Mass | 
 |         {"gram", "kilogram", 1.0, 0.001}, | 
 |         {"pound", "kilogram", 1.0, 0.453592}, | 
 |         {"pound", "kilogram", 2.0, 0.907185}, | 
 |         {"ounce", "pound", 16.0, 1.0}, | 
 |         {"ounce", "kilogram", 16.0, 0.453592}, | 
 |         {"ton", "pound", 1.0, 2000}, | 
 |         {"stone", "pound", 1.0, 14}, | 
 |         {"stone", "kilogram", 1.0, 6.35029}, | 
 |         // Temperature | 
 |         {"celsius", "fahrenheit", 0.0, 32.0}, | 
 |         {"celsius", "fahrenheit", 10.0, 50.0}, | 
 |         {"celsius", "fahrenheit", 1000, 1832}, | 
 |         {"fahrenheit", "celsius", 32.0, 0.0}, | 
 |         {"fahrenheit", "celsius", 89.6, 32}, | 
 |         {"fahrenheit", "fahrenheit", 1000, 1000}, | 
 |         {"kelvin", "fahrenheit", 0.0, -459.67}, | 
 |         {"kelvin", "fahrenheit", 300, 80.33}, | 
 |         {"kelvin", "celsius", 0.0, -273.15}, | 
 |         {"kelvin", "celsius", 300.0, 26.85}, | 
 |         // Area | 
 |         {"square-meter", "square-yard", 10.0, 11.9599}, | 
 |         {"hectare", "square-yard", 1.0, 11959.9}, | 
 |         {"square-mile", "square-foot", 0.0001, 2787.84}, | 
 |         {"hectare", "square-yard", 1.0, 11959.9}, | 
 |         {"hectare", "square-meter", 1.0, 10000}, | 
 |         {"hectare", "square-meter", 0.0, 0.0}, | 
 |         {"square-mile", "square-foot", 0.0001, 2787.84}, | 
 |         {"square-yard", "square-foot", 10, 90}, | 
 |         {"square-yard", "square-foot", 0, 0}, | 
 |         {"square-yard", "square-foot", 0.000001, 0.000009}, | 
 |         {"square-mile", "square-foot", 0.0, 0.0}, | 
 |         // Fuel Consumption | 
 |         {"cubic-meter-per-meter", "mile-per-gallon", 2.1383143939394E-6, 1.1}, | 
 |         {"cubic-meter-per-meter", "mile-per-gallon", 2.6134953703704E-6, 0.9}, | 
 |         {"liter-per-100-kilometer", "mile-per-gallon", 6.6, 35.6386}, | 
 |         {"liter-per-100-kilometer", "mile-per-gallon", 0, uprv_getInfinity()}, | 
 |         {"mile-per-gallon", "liter-per-100-kilometer", 0, uprv_getInfinity()}, | 
 |         {"mile-per-gallon", "liter-per-100-kilometer", uprv_getInfinity(), 0}, | 
 |         // We skip testing -Inf, because the inverse conversion loses the sign: | 
 |         // {"mile-per-gallon", "liter-per-100-kilometer", -uprv_getInfinity(), 0}, | 
 |  | 
 |         // Test Aliases | 
 |         // Alias is just another name to the same unit. Therefore, converting | 
 |         // between them should be the same. | 
 |         {"foodcalorie", "kilocalorie", 1.0, 1.0}, | 
 |         {"dot-per-centimeter", "pixel-per-centimeter", 1.0, 1.0}, | 
 |         {"dot-per-inch", "pixel-per-inch", 1.0, 1.0}, | 
 |         {"dot", "pixel", 1.0, 1.0}, | 
 |  | 
 |     }; | 
 |  | 
 |     for (const auto &testCase : testCases) { | 
 |         MeasureUnitImpl source = MeasureUnitImpl::forIdentifier(testCase.source, status); | 
 |         if (status.errIfFailureAndReset("source MeasureUnitImpl::forIdentifier(\"%s\", ...)", | 
 |                                         testCase.source)) { | 
 |             continue; | 
 |         } | 
 |         MeasureUnitImpl target = MeasureUnitImpl::forIdentifier(testCase.target, status); | 
 |         if (status.errIfFailureAndReset("target MeasureUnitImpl::forIdentifier(\"%s\", ...)", | 
 |                                         testCase.target)) { | 
 |             continue; | 
 |         } | 
 |  | 
 |         double maxDelta = 1e-6 * uprv_fabs(testCase.expectedValue); | 
 |         if (testCase.expectedValue == 0) { | 
 |             maxDelta = 1e-12; | 
 |         } | 
 |         double inverseMaxDelta = 1e-6 * uprv_fabs(testCase.inputValue); | 
 |         if (testCase.inputValue == 0) { | 
 |             inverseMaxDelta = 1e-12; | 
 |         } | 
 |  | 
 |         ConversionRates conversionRates(status); | 
 |         if (status.errIfFailureAndReset("conversionRates(status)")) { | 
 |             continue; | 
 |         } | 
 |  | 
 |         UnitsConverter converter(source, target, conversionRates, status); | 
 |         if (status.errIfFailureAndReset("UnitsConverter(<%s>, <%s>, ...)", testCase.source, | 
 |                                         testCase.target)) { | 
 |             continue; | 
 |         } | 
 |         assertEqualsNear(UnicodeString("testConverter: ") + testCase.source + " to " + testCase.target, | 
 |                          testCase.expectedValue, converter.convert(testCase.inputValue), maxDelta); | 
 |         assertEqualsNear( | 
 |             UnicodeString("testConverter inverse: ") + testCase.target + " back to " + testCase.source, | 
 |             testCase.inputValue, converter.convertInverse(testCase.expectedValue), inverseMaxDelta); | 
 |  | 
 |         // Test UnitsConverter created by CLDR unit identifiers | 
 |         UnitsConverter converter2(testCase.source, testCase.target, status); | 
 |         if (status.errIfFailureAndReset("UnitsConverter(<%s>, <%s>, ...)", testCase.source, | 
 |                                         testCase.target)) { | 
 |             continue; | 
 |         } | 
 |         assertEqualsNear(UnicodeString("testConverter2: ") + testCase.source + " to " + testCase.target, | 
 |                          testCase.expectedValue, converter2.convert(testCase.inputValue), maxDelta); | 
 |         assertEqualsNear( | 
 |             UnicodeString("testConverter2 inverse: ") + testCase.target + " back to " + testCase.source, | 
 |             testCase.inputValue, converter2.convertInverse(testCase.expectedValue), inverseMaxDelta); | 
 |     } | 
 | } | 
 |  | 
 | /** | 
 |  * Trims whitespace off of the specified string. | 
 |  * @param field is two pointers pointing at the start and end of the string. | 
 |  * @return A StringPiece with initial and final space characters trimmed off. | 
 |  */ | 
 | StringPiece trimField(char *(&field)[2]) { | 
 |     const char *start = field[0]; | 
 |     start = u_skipWhitespace(start); | 
 |     if (start >= field[1]) { | 
 |         start = field[1]; | 
 |     } | 
 |     const char *end = field[1]; | 
 |     while ((start < end) && U_IS_INV_WHITESPACE(*(end - 1))) { | 
 |         end--; | 
 |     } | 
 |     int32_t length = (int32_t)(end - start); | 
 |     return StringPiece(start, length); | 
 | } | 
 |  | 
 | // Used for passing context to unitsTestDataLineFn via u_parseDelimitedFile. | 
 | struct UnitsTestContext { | 
 |     // Provides access to UnitsTest methods like logln. | 
 |     UnitsTest *unitsTest; | 
 |     // Conversion rates: does not take ownership. | 
 |     ConversionRates *conversionRates; | 
 | }; | 
 |  | 
 | /** | 
 |  * Deals with a single data-driven unit test for unit conversions. | 
 |  * | 
 |  * This is a UParseLineFn as required by u_parseDelimitedFile, intended for | 
 |  * parsing unitsTest.txt. | 
 |  * | 
 |  * @param context Must point at a UnitsTestContext struct. | 
 |  * @param fields A list of pointer-pairs, each pair pointing at the start and | 
 |  * end of each field. End pointers are important because these are *not* | 
 |  * null-terminated strings. (Interpreted as a null-terminated string, | 
 |  * fields[0][0] points at the whole line.) | 
 |  * @param fieldCount The number of fields (pointer pairs) passed to the fields | 
 |  * parameter. | 
 |  * @param pErrorCode Receives status. | 
 |  */ | 
 | void unitsTestDataLineFn(void *context, char *fields[][2], int32_t fieldCount, UErrorCode *pErrorCode) { | 
 |     if (U_FAILURE(*pErrorCode)) { | 
 |         return; | 
 |     } | 
 |     UnitsTestContext *ctx = static_cast<UnitsTestContext *>(context); | 
 |     UnitsTest *unitsTest = ctx->unitsTest; | 
 |     (void)fieldCount; // unused UParseLineFn variable | 
 |     IcuTestErrorCode status(*unitsTest, "unitsTestDatalineFn"); | 
 |  | 
 |     StringPiece quantity = trimField(fields[0]); | 
 |     StringPiece x = trimField(fields[1]); | 
 |     StringPiece y = trimField(fields[2]); | 
 |     StringPiece commentConversionFormula = trimField(fields[3]); | 
 |     StringPiece utf8Expected = trimField(fields[4]); | 
 |  | 
 |     UNumberFormat *nf = unum_open(UNUM_DEFAULT, nullptr, -1, "en_US", nullptr, status); | 
 |     if (status.errIfFailureAndReset("unum_open failed")) { | 
 |         return; | 
 |     } | 
 |     UnicodeString uExpected = UnicodeString::fromUTF8(utf8Expected); | 
 |     double expected = unum_parseDouble(nf, uExpected.getBuffer(), uExpected.length(), 0, status); | 
 |     unum_close(nf); | 
 |     if (status.errIfFailureAndReset("unum_parseDouble(\"%s\") failed", utf8Expected)) { | 
 |         return; | 
 |     } | 
 |  | 
 |     CharString sourceIdent(x, status); | 
 |     MeasureUnitImpl sourceUnit = MeasureUnitImpl::forIdentifier(x, status); | 
 |     if (status.errIfFailureAndReset("forIdentifier(\"%.*s\")", x.length(), x.data())) { | 
 |         return; | 
 |     } | 
 |  | 
 |     CharString targetIdent(y, status); | 
 |     MeasureUnitImpl targetUnit = MeasureUnitImpl::forIdentifier(y, status); | 
 |     if (status.errIfFailureAndReset("forIdentifier(\"%.*s\")", y.length(), y.data())) { | 
 |         return; | 
 |     } | 
 |  | 
 |     unitsTest->logln("Quantity (Category): \"%.*s\", " | 
 |                      "Expected value of \"1000 %.*s in %.*s\": %f, " | 
 |                      "commentConversionFormula: \"%.*s\", ", | 
 |                      quantity.length(), quantity.data(), x.length(), x.data(), y.length(), y.data(), | 
 |                      expected, commentConversionFormula.length(), commentConversionFormula.data()); | 
 |  | 
 |     // Convertibility: | 
 |     auto convertibility = extractConvertibility(sourceUnit, targetUnit, *ctx->conversionRates, status); | 
 |     if (status.errIfFailureAndReset("extractConvertibility(<%s>, <%s>, ...)", | 
 |                                     sourceIdent.data(), targetIdent.data())) { | 
 |         return; | 
 |     } | 
 |     CharString msg; | 
 |     msg.append("convertible: ", status) | 
 |         .append(sourceIdent.data(), status) | 
 |         .append(" -> ", status) | 
 |         .append(targetIdent.data(), status); | 
 |     if (status.errIfFailureAndReset("msg construction")) { | 
 |         return; | 
 |     } | 
 |     unitsTest->assertNotEquals(msg.data(), UNCONVERTIBLE, convertibility); | 
 |  | 
 |     // Conversion: | 
 |     UnitsConverter converter(sourceUnit, targetUnit, *ctx->conversionRates, status); | 
 |     if (status.errIfFailureAndReset("UnitsConverter(<%s>, <%s>, ...)", sourceIdent.data(), | 
 |                                     targetIdent.data())) { | 
 |         return; | 
 |     } | 
 |     double got = converter.convert(1000); | 
 |     msg.clear(); | 
 |     msg.append("Converting 1000 ", status).append(x, status).append(" to ", status).append(y, status); | 
 |     unitsTest->assertEqualsNear(msg.data(), expected, got, 0.0001 * expected); | 
 |     double inverted = converter.convertInverse(got); | 
 |     msg.clear(); | 
 |     msg.append("Converting back to ", status).append(x, status).append(" from ", status).append(y, status); | 
 |     unitsTest->assertEqualsNear(msg.data(), 1000, inverted, 0.0001); | 
 | } | 
 |  | 
 | /** | 
 |  * Runs data-driven unit tests for unit conversion. It looks for the test cases | 
 |  * in source/test/testdata/cldr/units/unitsTest.txt, which originates in CLDR. | 
 |  */ | 
 | void UnitsTest::testConverterWithCLDRTests() { | 
 |     const char *filename = "unitsTest.txt"; | 
 |     const int32_t kNumFields = 5; | 
 |     char *fields[kNumFields][2]; | 
 |  | 
 |     IcuTestErrorCode errorCode(*this, "UnitsTest::testConverterWithCLDRTests"); | 
 |     const char *sourceTestDataPath = getSourceTestData(errorCode); | 
 |     if (errorCode.errIfFailureAndReset("unable to find the source/test/testdata " | 
 |                                        "folder (getSourceTestData())")) { | 
 |         return; | 
 |     } | 
 |  | 
 |     CharString path(sourceTestDataPath, errorCode); | 
 |     path.appendPathPart("cldr/units", errorCode); | 
 |     path.appendPathPart(filename, errorCode); | 
 |  | 
 |     ConversionRates rates(errorCode); | 
 |     UnitsTestContext ctx = {this, &rates}; | 
 |     u_parseDelimitedFile(path.data(), ';', fields, kNumFields, unitsTestDataLineFn, &ctx, errorCode); | 
 |     if (errorCode.errIfFailureAndReset("error parsing %s: %s\n", path.data(), u_errorName(errorCode))) { | 
 |         return; | 
 |     } | 
 | } | 
 |  | 
 | void UnitsTest::testComplexUnitsConverter() { | 
 |     IcuTestErrorCode status(*this, "UnitsTest::testComplexUnitsConverter"); | 
 |  | 
 |     // DBL_EPSILON is approximately 2.22E-16, and is the precision of double for | 
 |     // values in the range [1.0, 2.0), but half the precision of double for | 
 |     // [2.0, 4.0). | 
 |     U_ASSERT(1.0 + DBL_EPSILON > 1.0); | 
 |     U_ASSERT(2.0 - DBL_EPSILON < 2.0); | 
 |     U_ASSERT(2.0 + DBL_EPSILON == 2.0); | 
 |  | 
 |     struct TestCase { | 
 |         const char* msg; | 
 |         const char* input; | 
 |         const char* output; | 
 |         double value; | 
 |         Measure expected[2]; | 
 |         int32_t expectedCount; | 
 |         // For mixed units, accuracy of the smallest unit | 
 |         double accuracy; | 
 |     } testCases[]{ | 
 |         // Significantly less than 2.0. | 
 |         {"1.9999", | 
 |          "foot", | 
 |          "foot-and-inch", | 
 |          1.9999, | 
 |          {Measure(1, MeasureUnit::createFoot(status), status), | 
 |           Measure(11.9988, MeasureUnit::createInch(status), status)}, | 
 |          2, | 
 |          0}, | 
 |  | 
 |         // A minimal nudge under 2.0, rounding up to 2.0 ft, 0 in. | 
 |         {"2-eps", | 
 |          "foot", | 
 |          "foot-and-inch", | 
 |          2.0 - DBL_EPSILON, | 
 |          {Measure(2, MeasureUnit::createFoot(status), status), | 
 |           Measure(0, MeasureUnit::createInch(status), status)}, | 
 |          2, | 
 |          0}, | 
 |  | 
 |         // A slightly bigger nudge under 2.0, *not* rounding up to 2.0 ft! | 
 |         {"2-3eps", | 
 |          "foot", | 
 |          "foot-and-inch", | 
 |          2.0 - 3 * DBL_EPSILON, | 
 |          {Measure(1, MeasureUnit::createFoot(status), status), | 
 |           // We expect 12*3*DBL_EPSILON inches (7.92e-15) less than 12. | 
 |           Measure(12 - 36 * DBL_EPSILON, MeasureUnit::createInch(status), status)}, | 
 |          2, | 
 |          // Might accuracy be lacking with some compilers or on some systems? In | 
 |          // case it is somehow lacking, we'll allow a delta of 12 * DBL_EPSILON. | 
 |          12 * DBL_EPSILON}, | 
 |  | 
 |         // Testing precision with meter and light-year. | 
 |         // | 
 |         // DBL_EPSILON light-years, ~2.22E-16 light-years, is ~2.1 meters | 
 |         // (maximum precision when exponent is 0). | 
 |         // | 
 |         // 1e-16 light years is 0.946073 meters. | 
 |  | 
 |         // A 2.1 meter nudge under 2.0 light years, rounding up to 2.0 ly, 0 m. | 
 |         {"2-eps", | 
 |          "light-year", | 
 |          "light-year-and-meter", | 
 |          2.0 - DBL_EPSILON, | 
 |          {Measure(2, MeasureUnit::createLightYear(status), status), | 
 |           Measure(0, MeasureUnit::createMeter(status), status)}, | 
 |          2, | 
 |          0}, | 
 |  | 
 |         // A 2.1 meter nudge under 1.0 light years, rounding up to 1.0 ly, 0 m. | 
 |         {"1-eps", | 
 |          "light-year", | 
 |          "light-year-and-meter", | 
 |          1.0 - DBL_EPSILON, | 
 |          {Measure(1, MeasureUnit::createLightYear(status), status), | 
 |           Measure(0, MeasureUnit::createMeter(status), status)}, | 
 |          2, | 
 |          0}, | 
 |  | 
 |         // 1e-15 light years is 9.46073 meters (calculated using "bc" and the | 
 |         // CLDR conversion factor). With double-precision maths in C++, we get | 
 |         // 10.5. In this case, we're off by a bit more than 1 meter. With Java | 
 |         // BigDecimal, we get accurate results. | 
 |         {"1 + 1e-15", | 
 |          "light-year", | 
 |          "light-year-and-meter", | 
 |          1.0 + 1e-15, | 
 |          {Measure(1, MeasureUnit::createLightYear(status), status), | 
 |           Measure(9.46073, MeasureUnit::createMeter(status), status)}, | 
 |          2, | 
 |          1.5 /* meters, precision */}, | 
 |  | 
 |         // 2.1 meters more than 1 light year is not rounded away. | 
 |         {"1 + eps", | 
 |          "light-year", | 
 |          "light-year-and-meter", | 
 |          1.0 + DBL_EPSILON, | 
 |          {Measure(1, MeasureUnit::createLightYear(status), status), | 
 |           Measure(2.1, MeasureUnit::createMeter(status), status)}, | 
 |          2, | 
 |          0.001}, | 
 |  | 
 |         // Negative numbers | 
 |         {"Negative number conversion", | 
 |          "yard", | 
 |          "mile-and-yard", | 
 |          -1800, | 
 |          {Measure(-1, MeasureUnit::createMile(status), status), | 
 |           Measure(-40, MeasureUnit::createYard(status), status)}, | 
 |          2, | 
 |          1e-10}, | 
 |     }; | 
 |     status.assertSuccess(); | 
 |  | 
 |     ConversionRates rates(status); | 
 |     MeasureUnit input, output; | 
 |     MeasureUnitImpl tempInput, tempOutput; | 
 |     MaybeStackVector<Measure> measures; | 
 |     auto testATestCase = [&](const ComplexUnitsConverter& converter ,StringPiece initMsg , const TestCase &testCase) { | 
 |         measures = converter.convert(testCase.value, nullptr, status); | 
 |  | 
 |         CharString msg(initMsg, status); | 
 |         msg.append(testCase.msg, status); | 
 |         msg.append(" ", status); | 
 |         msg.append(testCase.input, status); | 
 |         msg.append(" -> ", status); | 
 |         msg.append(testCase.output, status); | 
 |  | 
 |         CharString msgCount(msg, status); | 
 |         msgCount.append(", measures.length()", status); | 
 |         assertEquals(msgCount.data(), testCase.expectedCount, measures.length()); | 
 |         for (int i = 0; i < measures.length() && i < testCase.expectedCount; i++) { | 
 |             if (i == testCase.expectedCount-1) { | 
 |                 assertEqualsNear(msg.data(), testCase.expected[i].getNumber().getDouble(status), | 
 |                                  measures[i]->getNumber().getDouble(status), testCase.accuracy); | 
 |             } else { | 
 |                 assertEquals(msg.data(), testCase.expected[i].getNumber().getDouble(status), | 
 |                              measures[i]->getNumber().getDouble(status)); | 
 |             } | 
 |             assertEquals(msg.data(), testCase.expected[i].getUnit().getIdentifier(), | 
 |                          measures[i]->getUnit().getIdentifier()); | 
 |         } | 
 |     }; | 
 |  | 
 |     for (const auto &testCase : testCases) | 
 |     { | 
 |         input = MeasureUnit::forIdentifier(testCase.input, status); | 
 |         output = MeasureUnit::forIdentifier(testCase.output, status); | 
 |         const MeasureUnitImpl& inputImpl = MeasureUnitImpl::forMeasureUnit(input, tempInput, status); | 
 |         const MeasureUnitImpl& outputImpl = MeasureUnitImpl::forMeasureUnit(output, tempOutput, status); | 
 |  | 
 |         ComplexUnitsConverter converter1(inputImpl, outputImpl, rates, status); | 
 |         testATestCase(converter1, "ComplexUnitsConverter #1 " , testCase); | 
 |  | 
 |         // Test ComplexUnitsConverter created with CLDR units identifiers. | 
 |         ComplexUnitsConverter converter2( testCase.input, testCase.output, status); | 
 |         testATestCase(converter2, "ComplexUnitsConverter #1 " , testCase); | 
 |     } | 
 |     status.assertSuccess(); | 
 | } | 
 |  | 
 | void UnitsTest::testComplexUnitsConverterSorting() { | 
 |     IcuTestErrorCode status(*this, "UnitsTest::testComplexUnitsConverterSorting"); | 
 |     ConversionRates conversionRates(status); | 
 |  | 
 |     status.assertSuccess(); | 
 |  | 
 |     struct TestCase { | 
 |         const char *msg; | 
 |         const char *input; | 
 |         const char *output; | 
 |         double inputValue; | 
 |         Measure expected[3]; | 
 |         int32_t expectedCount; | 
 |         // For mixed units, accuracy of the smallest unit | 
 |         double accuracy; | 
 |     } testCases[]{{"inch-and-foot", | 
 |                    "meter", | 
 |                    "inch-and-foot", | 
 |                    10.0, | 
 |                    { | 
 |                        Measure(9.70079, MeasureUnit::createInch(status), status), | 
 |                        Measure(32, MeasureUnit::createFoot(status), status), | 
 |                        Measure(0, MeasureUnit::createBit(status), status), | 
 |                    }, | 
 |                    2, | 
 |                    0.00001}, | 
 |                   {"inch-and-yard-and-foot", | 
 |                    "meter", | 
 |                    "inch-and-yard-and-foot", | 
 |                    100.0, | 
 |                    { | 
 |                        Measure(1.0079, MeasureUnit::createInch(status), status), | 
 |                        Measure(109, MeasureUnit::createYard(status), status), | 
 |                        Measure(1, MeasureUnit::createFoot(status), status), | 
 |                    }, | 
 |                    3, | 
 |                    0.0001}}; | 
 |  | 
 |     for (const auto &testCase : testCases) { | 
 |         MeasureUnitImpl inputImpl = MeasureUnitImpl::forIdentifier(testCase.input, status); | 
 |         if (status.errIfFailureAndReset()) { | 
 |             continue; | 
 |         } | 
 |         MeasureUnitImpl outputImpl = MeasureUnitImpl::forIdentifier(testCase.output, status); | 
 |         if (status.errIfFailureAndReset()) { | 
 |             continue; | 
 |         } | 
 |         ComplexUnitsConverter converter(inputImpl, outputImpl, conversionRates, status); | 
 |         if (status.errIfFailureAndReset()) { | 
 |             continue; | 
 |         } | 
 |  | 
 |         auto actual = converter.convert(testCase.inputValue, nullptr, status); | 
 |         if (status.errIfFailureAndReset()) { | 
 |             continue; | 
 |         } | 
 |         if (actual.length() < testCase.expectedCount) { | 
 |             errln("converter.convert(...) returned too few Measures"); | 
 |             continue; | 
 |         } | 
 |  | 
 |         for (int i = 0; i < testCase.expectedCount; i++) { | 
 |             assertEquals(testCase.msg, testCase.expected[i].getUnit().getIdentifier(), | 
 |                          actual[i]->getUnit().getIdentifier()); | 
 |  | 
 |             if (testCase.expected[i].getNumber().getType() == Formattable::Type::kInt64) { | 
 |                 assertEquals(testCase.msg, testCase.expected[i].getNumber().getInt64(), | 
 |                              actual[i]->getNumber().getInt64()); | 
 |             } else { | 
 |                 assertEqualsNear(testCase.msg, testCase.expected[i].getNumber().getDouble(), | 
 |                                  actual[i]->getNumber().getDouble(), testCase.accuracy); | 
 |             } | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | /** | 
 |  * This class represents the output fields from unitPreferencesTest.txt. Please | 
 |  * see the documentation at the top of that file for details. | 
 |  * | 
 |  * For "mixed units" output, there are more (repeated) output fields. The last | 
 |  * output unit has the expected output specified as both a rational fraction and | 
 |  * a decimal fraction. This class ignores rational fractions, and expects to | 
 |  * find a decimal fraction for each output unit. | 
 |  */ | 
 | class ExpectedOutput { | 
 |   public: | 
 |     // Counts number of units in the output. When this is more than one, we have | 
 |     // "mixed units" in the expected output. | 
 |     int _compoundCount = 0; | 
 |  | 
 |     // Counts how many fields were skipped: we expect to skip only one per | 
 |     // output unit type (the rational fraction). | 
 |     int _skippedFields = 0; | 
 |  | 
 |     // The expected output units: more than one for "mixed units". | 
 |     MeasureUnit _measureUnits[3]; | 
 |  | 
 |     // The amounts of each of the output units. | 
 |     double _amounts[3]; | 
 |  | 
 |     /** | 
 |      * Parse an expected output field from the test data file. | 
 |      * | 
 |      * @param output may be a string representation of an integer, a rational | 
 |      * fraction, a decimal fraction, or it may be a unit identifier. Whitespace | 
 |      * should already be trimmed. This function ignores rational fractions, | 
 |      * saving only decimal fractions and their unit identifiers. | 
 |      * @return true if the field was successfully parsed, false if parsing | 
 |      * failed. | 
 |      */ | 
 |     void parseOutputField(StringPiece output, UErrorCode &errorCode) { | 
 |         if (U_FAILURE(errorCode)) return; | 
 |         DecimalQuantity dqOutputD; | 
 |  | 
 |         dqOutputD.setToDecNumber(output, errorCode); | 
 |         if (U_SUCCESS(errorCode)) { | 
 |             _amounts[_compoundCount] = dqOutputD.toDouble(); | 
 |             return; | 
 |         } else if (errorCode == U_DECIMAL_NUMBER_SYNTAX_ERROR) { | 
 |             // Not a decimal fraction, it might be a rational fraction or a unit | 
 |             // identifier: continue. | 
 |             errorCode = U_ZERO_ERROR; | 
 |         } else { | 
 |             // Unexpected error, so we propagate it. | 
 |             return; | 
 |         } | 
 |  | 
 |         _measureUnits[_compoundCount] = MeasureUnit::forIdentifier(output, errorCode); | 
 |         if (U_SUCCESS(errorCode)) { | 
 |             _compoundCount++; | 
 |             _skippedFields = 0; | 
 |             return; | 
 |         } | 
 |         _skippedFields++; | 
 |         if (_skippedFields < 2) { | 
 |             // We are happy skipping one field per output unit: we want to skip | 
 |             // rational fraction fields like "11 / 10". | 
 |             errorCode = U_ZERO_ERROR; | 
 |             return; | 
 |         } else { | 
 |             // Propagate the error. | 
 |             return; | 
 |         } | 
 |     } | 
 |  | 
 |     /** | 
 |      * Produces an output string for debug purposes. | 
 |      */ | 
 |     std::string toDebugString() { | 
 |         std::string result; | 
 |         for (int i = 0; i < _compoundCount; i++) { | 
 |             result += std::to_string(_amounts[i]); | 
 |             result += " "; | 
 |             result += _measureUnits[i].getIdentifier(); | 
 |             result += " "; | 
 |         } | 
 |         return result; | 
 |     } | 
 | }; | 
 |  | 
 | // Checks a vector of Measure instances against ExpectedOutput. | 
 | void checkOutput(UnitsTest *unitsTest, const char *msg, ExpectedOutput expected, | 
 |                  const MaybeStackVector<Measure> &actual, double precision) { | 
 |     IcuTestErrorCode status(*unitsTest, "checkOutput"); | 
 |  | 
 |     CharString testMessage("Test case \"", status); | 
 |     testMessage.append(msg, status); | 
 |     testMessage.append("\": expected output: ", status); | 
 |     testMessage.append(expected.toDebugString().c_str(), status); | 
 |     testMessage.append(", obtained output:", status); | 
 |     for (int i = 0; i < actual.length(); i++) { | 
 |         testMessage.append(" ", status); | 
 |         testMessage.append(std::to_string(actual[i]->getNumber().getDouble(status)), status); | 
 |         testMessage.append(" ", status); | 
 |         testMessage.appendInvariantChars(actual[i]->getUnit().getIdentifier(), status); | 
 |     } | 
 |     if (!unitsTest->assertEquals(testMessage.data(), expected._compoundCount, actual.length())) { | 
 |         return; | 
 |     }; | 
 |     for (int i = 0; i < actual.length(); i++) { | 
 |         double permittedDiff = precision * expected._amounts[i]; | 
 |         if (permittedDiff == 0) { | 
 |             // If 0 is expected, still permit a small delta. | 
 |             // TODO: revisit this experimentally chosen value: | 
 |             permittedDiff = 0.00000001; | 
 |         } | 
 |         unitsTest->assertEqualsNear(testMessage.data(), expected._amounts[i], | 
 |                                     actual[i]->getNumber().getDouble(status), permittedDiff); | 
 |     } | 
 | } | 
 |  | 
 | /** | 
 |  * Runs a single data-driven unit test for unit preferences. | 
 |  * | 
 |  * This is a UParseLineFn as required by u_parseDelimitedFile, intended for | 
 |  * parsing unitPreferencesTest.txt. | 
 |  */ | 
 | void unitPreferencesTestDataLineFn(void *context, char *fields[][2], int32_t fieldCount, | 
 |                                    UErrorCode *pErrorCode) { | 
 |     if (U_FAILURE(*pErrorCode)) return; | 
 |     UnitsTest *unitsTest = static_cast<UnitsTest *>(context); | 
 |     IcuTestErrorCode status(*unitsTest, "unitPreferencesTestDatalineFn"); | 
 |  | 
 |     if (!unitsTest->assertTrue(u"unitPreferencesTestDataLineFn expects 9 fields for simple and 11 " | 
 |                                u"fields for compound. Other field counts not yet supported. ", | 
 |                                fieldCount == 9 || fieldCount == 11)) { | 
 |         return; | 
 |     } | 
 |  | 
 |     StringPiece quantity = trimField(fields[0]); | 
 |     StringPiece usage = trimField(fields[1]); | 
 |     StringPiece region = trimField(fields[2]); | 
 |     // Unused // StringPiece inputR = trimField(fields[3]); | 
 |     StringPiece inputD = trimField(fields[4]); | 
 |     StringPiece inputUnit = trimField(fields[5]); | 
 |     ExpectedOutput expected; | 
 |     for (int i = 6; i < fieldCount; i++) { | 
 |         expected.parseOutputField(trimField(fields[i]), status); | 
 |     } | 
 |     if (status.errIfFailureAndReset("parsing unitPreferencesTestData.txt test case: %s", fields[0][0])) { | 
 |         return; | 
 |     } | 
 |  | 
 |     DecimalQuantity dqInputD; | 
 |     dqInputD.setToDecNumber(inputD, status); | 
 |     if (status.errIfFailureAndReset("parsing decimal quantity: \"%.*s\"", inputD.length(), | 
 |                                     inputD.data())) { | 
 |         return; | 
 |     } | 
 |     double inputAmount = dqInputD.toDouble(); | 
 |  | 
 |     MeasureUnit inputMeasureUnit = MeasureUnit::forIdentifier(inputUnit, status); | 
 |     if (status.errIfFailureAndReset("forIdentifier(\"%.*s\")", inputUnit.length(), inputUnit.data())) { | 
 |         return; | 
 |     } | 
 |  | 
 |     unitsTest->logln("Quantity (Category): \"%.*s\", Usage: \"%.*s\", Region: \"%.*s\", " | 
 |                      "Input: \"%f %s\", Expected Output: %s", | 
 |                      quantity.length(), quantity.data(), usage.length(), usage.data(), region.length(), | 
 |                      region.data(), inputAmount, inputMeasureUnit.getIdentifier(), | 
 |                      expected.toDebugString().c_str()); | 
 |  | 
 |     if (U_FAILURE(status)) { | 
 |         return; | 
 |     } | 
 |  | 
 |     CharString localeID; | 
 |     localeID.append("und-", status); // append undefined language. | 
 |     localeID.append(region, status); | 
 |     Locale locale(localeID.data()); | 
 |     UnitsRouter router(inputMeasureUnit, locale, usage, status); | 
 |     if (status.errIfFailureAndReset("UnitsRouter(<%s>, \"%.*s\", \"%.*s\", status)", | 
 |                                     inputMeasureUnit.getIdentifier(), region.length(), region.data(), | 
 |                                     usage.length(), usage.data())) { | 
 |         return; | 
 |     } | 
 |  | 
 |     CharString msg(quantity, status); | 
 |     msg.append(" ", status); | 
 |     msg.append(usage, status); | 
 |     msg.append(" ", status); | 
 |     msg.append(region, status); | 
 |     msg.append(" ", status); | 
 |     msg.append(inputD, status); | 
 |     msg.append(" ", status); | 
 |     msg.append(inputMeasureUnit.getIdentifier(), status); | 
 |     if (status.errIfFailureAndReset("Failure before router.route")) { | 
 |         return; | 
 |     } | 
 |     RouteResult routeResult = router.route(inputAmount, nullptr, status); | 
 |     if (status.errIfFailureAndReset("router.route(inputAmount, ...)")) { | 
 |         return; | 
 |     } | 
 |     // TODO: revisit this experimentally chosen precision: | 
 |     checkOutput(unitsTest, msg.data(), expected, routeResult.measures, 0.0000000001); | 
 |  | 
 |     // Test UnitsRouter created with CLDR units identifiers. | 
 |     CharString inputUnitIdentifier(inputUnit, status); | 
 |     UnitsRouter router2(inputUnitIdentifier.data(), locale, usage, status); | 
 |     if (status.errIfFailureAndReset("UnitsRouter2(<%s>, \"%.*s\", \"%.*s\", status)", | 
 |                                     inputUnitIdentifier.data(), region.length(), region.data(), | 
 |                                     usage.length(), usage.data())) { | 
 |         return; | 
 |     } | 
 |  | 
 |     CharString msg2(quantity, status); | 
 |     msg2.append(" ", status); | 
 |     msg2.append(usage, status); | 
 |     msg2.append(" ", status); | 
 |     msg2.append(region, status); | 
 |     msg2.append(" ", status); | 
 |     msg2.append(inputD, status); | 
 |     msg2.append(" ", status); | 
 |     msg2.append(inputUnitIdentifier.data(), status); | 
 |     if (status.errIfFailureAndReset("Failure before router2.route")) { | 
 |         return; | 
 |     } | 
 |  | 
 |     RouteResult routeResult2 = router2.route(inputAmount, nullptr, status); | 
 |     if (status.errIfFailureAndReset("router2.route(inputAmount, ...)")) { | 
 |         return; | 
 |     } | 
 |     // TODO: revisit this experimentally chosen precision: | 
 |     checkOutput(unitsTest, msg2.data(), expected, routeResult.measures, 0.0000000001); | 
 | } | 
 |  | 
 | /** | 
 |  * Parses the format used by unitPreferencesTest.txt, calling lineFn for each | 
 |  * line. | 
 |  * | 
 |  * This is a modified version of u_parseDelimitedFile, customized for | 
 |  * unitPreferencesTest.txt, due to it having a variable number of fields per | 
 |  * line. | 
 |  */ | 
 | void parsePreferencesTests(const char *filename, char delimiter, char *fields[][2], | 
 |                            int32_t maxFieldCount, UParseLineFn *lineFn, void *context, | 
 |                            UErrorCode *pErrorCode) { | 
 |     FileStream *file; | 
 |     char line[10000]; | 
 |     char *start, *limit; | 
 |     int32_t i; | 
 |  | 
 |     if (U_FAILURE(*pErrorCode)) { | 
 |         return; | 
 |     } | 
 |  | 
 |     if (fields == nullptr || lineFn == nullptr || maxFieldCount <= 0) { | 
 |         *pErrorCode = U_ILLEGAL_ARGUMENT_ERROR; | 
 |         return; | 
 |     } | 
 |  | 
 |     if (filename == nullptr || *filename == 0 || (*filename == '-' && filename[1] == 0)) { | 
 |         filename = nullptr; | 
 |         file = T_FileStream_stdin(); | 
 |     } else { | 
 |         file = T_FileStream_open(filename, "r"); | 
 |     } | 
 |     if (file == nullptr) { | 
 |         *pErrorCode = U_FILE_ACCESS_ERROR; | 
 |         return; | 
 |     } | 
 |  | 
 |     while (T_FileStream_readLine(file, line, sizeof(line)) != nullptr) { | 
 |         /* remove trailing newline characters */ | 
 |         u_rtrim(line); | 
 |  | 
 |         start = line; | 
 |         *pErrorCode = U_ZERO_ERROR; | 
 |  | 
 |         /* skip this line if it is empty or a comment */ | 
 |         if (*start == 0 || *start == '#') { | 
 |             continue; | 
 |         } | 
 |  | 
 |         /* remove in-line comments */ | 
 |         limit = uprv_strchr(start, '#'); | 
 |         if (limit != nullptr) { | 
 |             /* get white space before the pound sign */ | 
 |             while (limit > start && U_IS_INV_WHITESPACE(*(limit - 1))) { | 
 |                 --limit; | 
 |             } | 
 |  | 
 |             /* truncate the line */ | 
 |             *limit = 0; | 
 |         } | 
 |  | 
 |         /* skip lines with only whitespace */ | 
 |         if (u_skipWhitespace(start)[0] == 0) { | 
 |             continue; | 
 |         } | 
 |  | 
 |         /* for each field, call the corresponding field function */ | 
 |         for (i = 0; i < maxFieldCount; ++i) { | 
 |             /* set the limit pointer of this field */ | 
 |             limit = start; | 
 |             while (*limit != delimiter && *limit != 0) { | 
 |                 ++limit; | 
 |             } | 
 |  | 
 |             /* set the field start and limit in the fields array */ | 
 |             fields[i][0] = start; | 
 |             fields[i][1] = limit; | 
 |  | 
 |             /* set start to the beginning of the next field, if any */ | 
 |             start = limit; | 
 |             if (*start != 0) { | 
 |                 ++start; | 
 |             } else { | 
 |                 break; | 
 |             } | 
 |         } | 
 |         if (i == maxFieldCount) { | 
 |             *pErrorCode = U_PARSE_ERROR; | 
 |         } | 
 |         int fieldCount = i + 1; | 
 |  | 
 |         /* call the field function */ | 
 |         lineFn(context, fields, fieldCount, pErrorCode); | 
 |         if (U_FAILURE(*pErrorCode)) { | 
 |             break; | 
 |         } | 
 |     } | 
 |  | 
 |     if (filename != nullptr) { | 
 |         T_FileStream_close(file); | 
 |     } | 
 | } | 
 |  | 
 | /** | 
 |  * Runs data-driven unit tests for unit preferences. It looks for the test cases | 
 |  * in source/test/testdata/cldr/units/unitPreferencesTest.txt, which originates | 
 |  * in CLDR. | 
 |  */ | 
 | void UnitsTest::testUnitPreferencesWithCLDRTests() { | 
 |     const char *filename = "unitPreferencesTest.txt"; | 
 |     const int32_t maxFields = 11; | 
 |     char *fields[maxFields][2]; | 
 |  | 
 |     IcuTestErrorCode errorCode(*this, "UnitsTest::testUnitPreferencesWithCLDRTests"); | 
 |     const char *sourceTestDataPath = getSourceTestData(errorCode); | 
 |     if (errorCode.errIfFailureAndReset("unable to find the source/test/testdata " | 
 |                                        "folder (getSourceTestData())")) { | 
 |         return; | 
 |     } | 
 |  | 
 |     CharString path(sourceTestDataPath, errorCode); | 
 |     path.appendPathPart("cldr/units", errorCode); | 
 |     path.appendPathPart(filename, errorCode); | 
 |  | 
 |     parsePreferencesTests(path.data(), ';', fields, maxFields, unitPreferencesTestDataLineFn, this, | 
 |                           errorCode); | 
 |     if (errorCode.errIfFailureAndReset("error parsing %s: %s\n", path.data(), u_errorName(errorCode))) { | 
 |         return; | 
 |     } | 
 | } | 
 |  | 
 | #endif /* #if !UCONFIG_NO_FORMATTING */ |