• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // © 2020 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html#License
3 
4 #include "unicode/utypes.h"
5 
6 #if !UCONFIG_NO_FORMATTING
7 
8 #include <cmath>
9 #include <iostream>
10 
11 #include "charstr.h"
12 #include "cmemory.h"
13 #include "filestrm.h"
14 #include "intltest.h"
15 #include "number_decimalquantity.h"
16 #include "putilimp.h"
17 #include "unicode/ctest.h"
18 #include "unicode/measunit.h"
19 #include "unicode/measure.h"
20 #include "unicode/unistr.h"
21 #include "unicode/unum.h"
22 #include "unicode/ures.h"
23 #include "units_complexconverter.h"
24 #include "units_converter.h"
25 #include "units_data.h"
26 #include "units_router.h"
27 #include "uparse.h"
28 #include "uresimp.h"
29 
30 struct UnitConversionTestCase {
31     const StringPiece source;
32     const StringPiece target;
33     const double inputValue;
34     const double expectedValue;
35 };
36 
37 using ::icu::number::impl::DecimalQuantity;
38 using namespace ::icu::units;
39 
40 class UnitsTest : public IntlTest {
41   public:
UnitsTest()42     UnitsTest() {}
43 
44     void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = nullptr) override;
45 
46     void testUnitConstantFreshness();
47     void testExtractConvertibility();
48     void testConversionInfo();
49     void testConverterWithCLDRTests();
50     void testComplexUnitsConverter();
51     void testComplexUnitsConverterSorting();
52     void testUnitPreferencesWithCLDRTests();
53     void testConverter();
54 };
55 
createUnitsTest()56 extern IntlTest *createUnitsTest() { return new UnitsTest(); }
57 
runIndexedTest(int32_t index,UBool exec,const char * & name,char *)58 void UnitsTest::runIndexedTest(int32_t index, UBool exec, const char *&name, char * /*par*/) {
59     if (exec) {
60         logln("TestSuite UnitsTest: ");
61     }
62     TESTCASE_AUTO_BEGIN;
63     TESTCASE_AUTO(testUnitConstantFreshness);
64     TESTCASE_AUTO(testExtractConvertibility);
65     TESTCASE_AUTO(testConversionInfo);
66     TESTCASE_AUTO(testConverterWithCLDRTests);
67     TESTCASE_AUTO(testComplexUnitsConverter);
68     TESTCASE_AUTO(testComplexUnitsConverterSorting);
69     TESTCASE_AUTO(testUnitPreferencesWithCLDRTests);
70     TESTCASE_AUTO(testConverter);
71     TESTCASE_AUTO_END;
72 }
73 
74 // Tests the hard-coded constants in the code against constants that appear in
75 // units.txt.
testUnitConstantFreshness()76 void UnitsTest::testUnitConstantFreshness() {
77     IcuTestErrorCode status(*this, "testUnitConstantFreshness");
78     LocalUResourceBundlePointer unitsBundle(ures_openDirect(nullptr, "units", status));
79     LocalUResourceBundlePointer unitConstants(
80         ures_getByKey(unitsBundle.getAlias(), "unitConstants", nullptr, status));
81 
82     while (ures_hasNext(unitConstants.getAlias())) {
83         int32_t len;
84         const char *constant = nullptr;
85         ures_getNextString(unitConstants.getAlias(), &len, &constant, status);
86 
87         Factor factor;
88         addSingleFactorConstant(constant, 1, POSITIVE, factor, status);
89         if (status.errDataIfFailureAndReset(
90                 "addSingleFactorConstant(<%s>, ...).\n\n"
91                 "If U_INVALID_FORMAT_ERROR, please check that \"icu4c/source/i18n/units_converter.cpp\" "
92                 "has all constants? Is \"%s\" a new constant?\n"
93                 "See docs/processes/release/tasks/updating-measure-unit.md for more information.\n",
94                 constant, constant)) {
95             continue;
96         }
97 
98         // Check the values of constants that have a simple numeric value
99         factor.substituteConstants();
100         int32_t uLen;
101         UnicodeString uVal = ures_getStringByKey(unitConstants.getAlias(), constant, &uLen, status);
102         CharString val;
103         val.appendInvariantChars(uVal, status);
104         if (status.errDataIfFailureAndReset("Failed to get constant value for %s.", constant)) {
105             continue;
106         }
107         DecimalQuantity dqVal;
108         UErrorCode parseStatus = U_ZERO_ERROR;
109         // TODO(units): unify with strToDouble() in units_converter.cpp
110         dqVal.setToDecNumber(val.toStringPiece(), parseStatus);
111         if (!U_SUCCESS(parseStatus)) {
112             // Not simple to parse, skip validating this constant's value. (We
113             // leave catching mistakes to the data-driven integration tests.)
114             continue;
115         }
116         double expectedNumerator = dqVal.toDouble();
117         assertEquals(UnicodeString("Constant ") + constant + u" numerator", expectedNumerator,
118                      factor.factorNum);
119         assertEquals(UnicodeString("Constant ") + constant + u" denominator", 1.0, factor.factorDen);
120     }
121 }
122 
testExtractConvertibility()123 void UnitsTest::testExtractConvertibility() {
124     IcuTestErrorCode status(*this, "UnitsTest::testExtractConvertibility");
125 
126     struct TestCase {
127         const char *const source;
128         const char *const target;
129         const Convertibility expectedState;
130     } testCases[]{
131         {"meter", "foot", CONVERTIBLE},                                              //
132         {"kilometer", "foot", CONVERTIBLE},                                          //
133         {"hectare", "square-foot", CONVERTIBLE},                                     //
134         {"kilometer-per-second", "second-per-meter", RECIPROCAL},                    //
135         {"square-meter", "square-foot", CONVERTIBLE},                                //
136         {"kilometer-per-second", "foot-per-second", CONVERTIBLE},                    //
137         {"square-hectare", "pow4-foot", CONVERTIBLE},                                //
138         {"square-kilometer-per-second", "second-per-square-meter", RECIPROCAL},      //
139         {"cubic-kilometer-per-second-meter", "second-per-square-meter", RECIPROCAL}, //
140         {"square-meter-per-square-hour", "hectare-per-square-second", CONVERTIBLE},  //
141         {"hertz", "revolution-per-second", CONVERTIBLE},                             //
142         {"millimeter", "meter", CONVERTIBLE},                                        //
143         {"yard", "meter", CONVERTIBLE},                                              //
144         {"ounce-troy", "kilogram", CONVERTIBLE},                                     //
145         {"percent", "portion", CONVERTIBLE},                                         //
146         {"ofhg", "kilogram-per-square-meter-square-second", CONVERTIBLE},            //
147         {"second-per-meter", "meter-per-second", RECIPROCAL},                        //
148         {"mile-per-hour", "meter-per-second", CONVERTIBLE},                        //
149         {"knot", "meter-per-second", CONVERTIBLE},                        //
150         {"beaufort", "meter-per-second", CONVERTIBLE},                        //
151     };
152 
153     for (const auto &testCase : testCases) {
154         MeasureUnitImpl source = MeasureUnitImpl::forIdentifier(testCase.source, status);
155         if (status.errIfFailureAndReset("source MeasureUnitImpl::forIdentifier(\"%s\", ...)",
156                                         testCase.source)) {
157             continue;
158         }
159         MeasureUnitImpl target = MeasureUnitImpl::forIdentifier(testCase.target, status);
160         if (status.errIfFailureAndReset("target MeasureUnitImpl::forIdentifier(\"%s\", ...)",
161                                         testCase.target)) {
162             continue;
163         }
164 
165         ConversionRates conversionRates(status);
166         if (status.errIfFailureAndReset("conversionRates(status)")) {
167             continue;
168         }
169         auto convertibility = extractConvertibility(source, target, conversionRates, status);
170         if (status.errIfFailureAndReset("extractConvertibility(<%s>, <%s>, ...)", testCase.source,
171                                         testCase.target)) {
172             continue;
173         }
174 
175         assertEquals(UnicodeString("Conversion Capability: ") + testCase.source + " to " +
176                          testCase.target,
177                      testCase.expectedState, convertibility);
178     }
179 }
180 
testConversionInfo()181 void UnitsTest::testConversionInfo() {
182     IcuTestErrorCode status(*this, "UnitsTest::testExtractConvertibility");
183     // Test Cases
184     struct TestCase {
185         const char *source;
186         const char *target;
187         const ConversionInfo expectedConversionInfo;
188     } testCases[]{
189         {
190             "meter",
191             "meter",
192             {1.0, 0, false},
193         },
194         {
195             "meter",
196             "foot",
197             {3.28084, 0, false},
198         },
199         {
200             "foot",
201             "meter",
202             {0.3048, 0, false},
203         },
204         {
205             "celsius",
206             "kelvin",
207             {1, 273.15, false},
208         },
209         {
210             "fahrenheit",
211             "kelvin",
212             {5.0 / 9.0, 255.372, false},
213         },
214         {
215             "fahrenheit",
216             "celsius",
217             {5.0 / 9.0, -17.7777777778, false},
218         },
219         {
220             "celsius",
221             "fahrenheit",
222             {9.0 / 5.0, 32, false},
223         },
224         {
225             "fahrenheit",
226             "fahrenheit",
227             {1.0, 0, false},
228         },
229         {
230             "mile-per-gallon",
231             "liter-per-100-kilometer",
232             {0.00425143707, 0, true},
233         },
234     };
235 
236     ConversionRates rates(status);
237     for (const auto &testCase : testCases) {
238         auto sourceImpl = MeasureUnitImpl::forIdentifier(testCase.source, status);
239         auto targetImpl = MeasureUnitImpl::forIdentifier(testCase.target, status);
240         UnitsConverter unitsConverter(sourceImpl, targetImpl, rates, status);
241 
242         if (status.errIfFailureAndReset()) {
243             continue;
244         }
245 
246         ConversionInfo actualConversionInfo = unitsConverter.getConversionInfo();
247         UnicodeString message =
248             UnicodeString("testConverter: ") + testCase.source + " to " + testCase.target;
249 
250         double maxDelta = 1e-6 * uprv_fabs(testCase.expectedConversionInfo.conversionRate);
251         if (testCase.expectedConversionInfo.conversionRate == 0) {
252             maxDelta = 1e-12;
253         }
254         assertEqualsNear(message + ", conversion rate: ", testCase.expectedConversionInfo.conversionRate,
255                          actualConversionInfo.conversionRate, maxDelta);
256 
257         maxDelta = 1e-6 * uprv_fabs(testCase.expectedConversionInfo.offset);
258         if (testCase.expectedConversionInfo.offset == 0) {
259             maxDelta = 1e-12;
260         }
261         assertEqualsNear(message + ", offset: ", testCase.expectedConversionInfo.offset, actualConversionInfo.offset,
262                          maxDelta);
263 
264         assertEquals(message + ", reciprocal: ", testCase.expectedConversionInfo.reciprocal,
265                      actualConversionInfo.reciprocal);
266     }
267 }
268 
testConverter()269 void UnitsTest::testConverter() {
270     IcuTestErrorCode status(*this, "UnitsTest::testConverter");
271 
272     // Test Cases
273     struct TestCase {
274         const char *source;
275         const char *target;
276         const double inputValue;
277         const double expectedValue;
278     } testCases[]{
279         // SI Prefixes
280         {"gram", "kilogram", 1.0, 0.001},
281         {"milligram", "kilogram", 1.0, 0.000001},
282         {"microgram", "kilogram", 1.0, 0.000000001},
283         {"megagram", "gram", 1.0, 1000000},
284         {"megagram", "kilogram", 1.0, 1000},
285         {"gigabyte", "byte", 1.0, 1000000000},
286         {"megawatt", "watt", 1.0, 1000000},
287         {"megawatt", "kilowatt", 1.0, 1000},
288         // Binary Prefixes
289         {"kilobyte", "byte", 1, 1000},
290         {"kibibyte", "byte", 1, 1024},
291         {"mebibyte", "byte", 1, 1048576},
292         {"gibibyte", "kibibyte", 1, 1048576},
293         {"pebibyte", "tebibyte", 4, 4096},
294         {"zebibyte", "pebibyte", 1.0 / 16, 65536.0},
295         {"yobibyte", "exbibyte", 1, 1048576},
296         // Mass
297         {"gram", "kilogram", 1.0, 0.001},
298         {"pound", "kilogram", 1.0, 0.453592},
299         {"pound", "kilogram", 2.0, 0.907185},
300         {"ounce", "pound", 16.0, 1.0},
301         {"ounce", "kilogram", 16.0, 0.453592},
302         {"ton", "pound", 1.0, 2000},
303         {"stone", "pound", 1.0, 14},
304         {"stone", "kilogram", 1.0, 6.35029},
305         // Speed
306         {"mile-per-hour", "meter-per-second", 1.0, 0.44704},
307         {"knot", "meter-per-second", 1.0, 0.514444},
308         {"beaufort", "meter-per-second", 1.0, 0.95},
309         {"beaufort", "meter-per-second", 4.0, 6.75},
310         {"beaufort", "meter-per-second", 7.0, 15.55},
311         {"beaufort", "meter-per-second", 10.0, 26.5},
312         {"beaufort", "meter-per-second", 13.0, 39.15},
313         {"beaufort", "mile-per-hour", 1.0, 2.12509},
314         {"beaufort", "mile-per-hour", 4.0, 15.099319971367215},
315         {"beaufort", "mile-per-hour", 7.0, 34.784359341445956},
316         {"beaufort", "mile-per-hour", 10.0, 59.2788},
317         {"beaufort", "mile-per-hour", 13.0, 87.5761},
318         // Temperature
319         {"celsius", "fahrenheit", 0.0, 32.0},
320         {"celsius", "fahrenheit", 10.0, 50.0},
321         {"celsius", "fahrenheit", 1000, 1832},
322         {"fahrenheit", "celsius", 32.0, 0.0},
323         {"fahrenheit", "celsius", 89.6, 32},
324         {"fahrenheit", "fahrenheit", 1000, 1000},
325         {"kelvin", "fahrenheit", 0.0, -459.67},
326         {"kelvin", "fahrenheit", 300, 80.33},
327         {"kelvin", "celsius", 0.0, -273.15},
328         {"kelvin", "celsius", 300.0, 26.85},
329         // Area
330         {"square-meter", "square-yard", 10.0, 11.9599},
331         {"hectare", "square-yard", 1.0, 11959.9},
332         {"square-mile", "square-foot", 0.0001, 2787.84},
333         {"hectare", "square-yard", 1.0, 11959.9},
334         {"hectare", "square-meter", 1.0, 10000},
335         {"hectare", "square-meter", 0.0, 0.0},
336         {"square-mile", "square-foot", 0.0001, 2787.84},
337         {"square-yard", "square-foot", 10, 90},
338         {"square-yard", "square-foot", 0, 0},
339         {"square-yard", "square-foot", 0.000001, 0.000009},
340         {"square-mile", "square-foot", 0.0, 0.0},
341         // Fuel Consumption
342         {"cubic-meter-per-meter", "mile-per-gallon", 2.1383143939394E-6, 1.1},
343         {"cubic-meter-per-meter", "mile-per-gallon", 2.6134953703704E-6, 0.9},
344         {"liter-per-100-kilometer", "mile-per-gallon", 6.6, 35.6386},
345         {"liter-per-100-kilometer", "mile-per-gallon", 0, uprv_getInfinity()},
346         {"mile-per-gallon", "liter-per-100-kilometer", 0, uprv_getInfinity()},
347         {"mile-per-gallon", "liter-per-100-kilometer", uprv_getInfinity(), 0},
348         // We skip testing -Inf, because the inverse conversion loses the sign:
349         // {"mile-per-gallon", "liter-per-100-kilometer", -uprv_getInfinity(), 0},
350 
351         // Test Aliases
352         // Alias is just another name to the same unit. Therefore, converting
353         // between them should be the same.
354         {"foodcalorie", "kilocalorie", 1.0, 1.0},
355         {"dot-per-centimeter", "pixel-per-centimeter", 1.0, 1.0},
356         {"dot-per-inch", "pixel-per-inch", 1.0, 1.0},
357         {"dot", "pixel", 1.0, 1.0},
358 
359     };
360 
361     for (const auto &testCase : testCases) {
362         MeasureUnitImpl source = MeasureUnitImpl::forIdentifier(testCase.source, status);
363         if (status.errIfFailureAndReset("source MeasureUnitImpl::forIdentifier(\"%s\", ...)",
364                                         testCase.source)) {
365             continue;
366         }
367         MeasureUnitImpl target = MeasureUnitImpl::forIdentifier(testCase.target, status);
368         if (status.errIfFailureAndReset("target MeasureUnitImpl::forIdentifier(\"%s\", ...)",
369                                         testCase.target)) {
370             continue;
371         }
372 
373         double maxDelta = 1e-6 * uprv_fabs(testCase.expectedValue);
374         if (testCase.expectedValue == 0) {
375             maxDelta = 1e-12;
376         }
377         double inverseMaxDelta = 1e-6 * uprv_fabs(testCase.inputValue);
378         if (testCase.inputValue == 0) {
379             inverseMaxDelta = 1e-12;
380         }
381 
382         ConversionRates conversionRates(status);
383         if (status.errIfFailureAndReset("conversionRates(status)")) {
384             continue;
385         }
386 
387         UnitsConverter converter(source, target, conversionRates, status);
388         if (status.errIfFailureAndReset("UnitsConverter(<%s>, <%s>, ...)", testCase.source,
389                                         testCase.target)) {
390             continue;
391         }
392         assertEqualsNear(UnicodeString("testConverter: ") + testCase.source + " to " + testCase.target,
393                          testCase.expectedValue, converter.convert(testCase.inputValue), maxDelta);
394         assertEqualsNear(
395             UnicodeString("testConverter inverse: ") + testCase.target + " back to " + testCase.source,
396             testCase.inputValue, converter.convertInverse(testCase.expectedValue), inverseMaxDelta);
397 
398         // Test UnitsConverter created by CLDR unit identifiers
399         UnitsConverter converter2(testCase.source, testCase.target, status);
400         if (status.errIfFailureAndReset("UnitsConverter(<%s>, <%s>, ...)", testCase.source,
401                                         testCase.target)) {
402             continue;
403         }
404         assertEqualsNear(UnicodeString("testConverter2: ") + testCase.source + " to " + testCase.target,
405                          testCase.expectedValue, converter2.convert(testCase.inputValue), maxDelta);
406         assertEqualsNear(
407             UnicodeString("testConverter2 inverse: ") + testCase.target + " back to " + testCase.source,
408             testCase.inputValue, converter2.convertInverse(testCase.expectedValue), inverseMaxDelta);
409     }
410 }
411 
412 /**
413  * Trims whitespace off of the specified string.
414  * @param field is two pointers pointing at the start and end of the string.
415  * @return A StringPiece with initial and final space characters trimmed off.
416  */
trimField(char * (& field)[2])417 StringPiece trimField(char *(&field)[2]) {
418     const char *start = field[0];
419     start = u_skipWhitespace(start);
420     if (start >= field[1]) {
421         start = field[1];
422     }
423     const char *end = field[1];
424     while ((start < end) && U_IS_INV_WHITESPACE(*(end - 1))) {
425         end--;
426     }
427     int32_t length = (int32_t)(end - start);
428     return StringPiece(start, length);
429 }
430 
431 // Used for passing context to unitsTestDataLineFn via u_parseDelimitedFile.
432 struct UnitsTestContext {
433     // Provides access to UnitsTest methods like logln.
434     UnitsTest *unitsTest;
435     // Conversion rates: does not take ownership.
436     ConversionRates *conversionRates;
437 };
438 
439 /**
440  * Deals with a single data-driven unit test for unit conversions.
441  *
442  * This is a UParseLineFn as required by u_parseDelimitedFile, intended for
443  * parsing unitsTest.txt.
444  *
445  * @param context Must point at a UnitsTestContext struct.
446  * @param fields A list of pointer-pairs, each pair pointing at the start and
447  * end of each field. End pointers are important because these are *not*
448  * null-terminated strings. (Interpreted as a null-terminated string,
449  * fields[0][0] points at the whole line.)
450  * @param fieldCount The number of fields (pointer pairs) passed to the fields
451  * parameter.
452  * @param pErrorCode Receives status.
453  */
unitsTestDataLineFn(void * context,char * fields[][2],int32_t fieldCount,UErrorCode * pErrorCode)454 void unitsTestDataLineFn(void *context, char *fields[][2], int32_t fieldCount, UErrorCode *pErrorCode) {
455     if (U_FAILURE(*pErrorCode)) {
456         return;
457     }
458     UnitsTestContext *ctx = static_cast<UnitsTestContext *>(context);
459     UnitsTest *unitsTest = ctx->unitsTest;
460     (void)fieldCount; // unused UParseLineFn variable
461     IcuTestErrorCode status(*unitsTest, "unitsTestDatalineFn");
462 
463     StringPiece quantity = trimField(fields[0]);
464     StringPiece x = trimField(fields[1]);
465     StringPiece y = trimField(fields[2]);
466     StringPiece commentConversionFormula = trimField(fields[3]);
467     StringPiece utf8Expected = trimField(fields[4]);
468 
469     UNumberFormat *nf = unum_open(UNUM_DEFAULT, nullptr, -1, "en_US", nullptr, status);
470     if (status.errIfFailureAndReset("unum_open failed")) {
471         return;
472     }
473     UnicodeString uExpected = UnicodeString::fromUTF8(utf8Expected);
474     double expected = unum_parseDouble(nf, uExpected.getBuffer(), uExpected.length(), nullptr, status);
475     unum_close(nf);
476     if (status.errIfFailureAndReset("unum_parseDouble(\"%s\") failed", utf8Expected)) {
477         return;
478     }
479 
480     CharString sourceIdent(x, status);
481     MeasureUnitImpl sourceUnit = MeasureUnitImpl::forIdentifier(x, status);
482     if (status.errIfFailureAndReset("forIdentifier(\"%.*s\")", x.length(), x.data())) {
483         return;
484     }
485 
486     CharString targetIdent(y, status);
487     MeasureUnitImpl targetUnit = MeasureUnitImpl::forIdentifier(y, status);
488     if (status.errIfFailureAndReset("forIdentifier(\"%.*s\")", y.length(), y.data())) {
489         return;
490     }
491 
492     unitsTest->logln("Quantity (Category): \"%.*s\", "
493                      "Expected value of \"1000 %.*s in %.*s\": %f, "
494                      "commentConversionFormula: \"%.*s\", ",
495                      quantity.length(), quantity.data(), x.length(), x.data(), y.length(), y.data(),
496                      expected, commentConversionFormula.length(), commentConversionFormula.data());
497 
498     // Convertibility:
499     auto convertibility = extractConvertibility(sourceUnit, targetUnit, *ctx->conversionRates, status);
500     if (status.errIfFailureAndReset("extractConvertibility(<%s>, <%s>, ...)",
501                                     sourceIdent.data(), targetIdent.data())) {
502         return;
503     }
504     CharString msg;
505     msg.append("convertible: ", status)
506         .append(sourceIdent.data(), status)
507         .append(" -> ", status)
508         .append(targetIdent.data(), status);
509     if (status.errIfFailureAndReset("msg construction")) {
510         return;
511     }
512     unitsTest->assertNotEquals(msg.data(), UNCONVERTIBLE, convertibility);
513 
514     // Conversion:
515     UnitsConverter converter(sourceUnit, targetUnit, *ctx->conversionRates, status);
516     if (status.errIfFailureAndReset("UnitsConverter(<%s>, <%s>, ...)", sourceIdent.data(),
517                                     targetIdent.data())) {
518         return;
519     }
520     double got = converter.convert(1000);
521     msg.clear();
522     msg.append("Converting 1000 ", status).append(x, status).append(" to ", status).append(y, status);
523     unitsTest->assertEqualsNear(msg.data(), expected, got, 0.0001 * expected);
524     double inverted = converter.convertInverse(got);
525     msg.clear();
526     msg.append("Converting back to ", status).append(x, status).append(" from ", status).append(y, status);
527     if (strncmp(x.data(), "beaufort", 8)
528     		&& log_knownIssue("CLDR-17454", "unitTest.txt for beaufort doesn't scale correctly") ) {
529 		unitsTest->assertEqualsNear(msg.data(), 1000, inverted, 0.0001);
530     }
531 }
532 
533 /**
534  * Runs data-driven unit tests for unit conversion. It looks for the test cases
535  * in source/test/testdata/cldr/units/unitsTest.txt, which originates in CLDR.
536  */
testConverterWithCLDRTests()537 void UnitsTest::testConverterWithCLDRTests() {
538     const char *filename = "unitsTest.txt";
539     const int32_t kNumFields = 5;
540     char *fields[kNumFields][2];
541 
542     IcuTestErrorCode errorCode(*this, "UnitsTest::testConverterWithCLDRTests");
543     const char *sourceTestDataPath = getSourceTestData(errorCode);
544     if (errorCode.errIfFailureAndReset("unable to find the source/test/testdata "
545                                        "folder (getSourceTestData())")) {
546         return;
547     }
548 
549     CharString path(sourceTestDataPath, errorCode);
550     path.appendPathPart("cldr/units", errorCode);
551     path.appendPathPart(filename, errorCode);
552 
553     ConversionRates rates(errorCode);
554     UnitsTestContext ctx = {this, &rates};
555     u_parseDelimitedFile(path.data(), ';', fields, kNumFields, unitsTestDataLineFn, &ctx, errorCode);
556     if (errorCode.errIfFailureAndReset("error parsing %s: %s\n", path.data(), u_errorName(errorCode))) {
557         return;
558     }
559 }
560 
testComplexUnitsConverter()561 void UnitsTest::testComplexUnitsConverter() {
562     IcuTestErrorCode status(*this, "UnitsTest::testComplexUnitsConverter");
563 
564     // DBL_EPSILON is approximately 2.22E-16, and is the precision of double for
565     // values in the range [1.0, 2.0), but half the precision of double for
566     // [2.0, 4.0).
567     U_ASSERT(1.0 + DBL_EPSILON > 1.0);
568     U_ASSERT(2.0 - DBL_EPSILON < 2.0);
569     U_ASSERT(2.0 + DBL_EPSILON == 2.0);
570 
571     struct TestCase {
572         const char* msg;
573         const char* input;
574         const char* output;
575         double value;
576         Measure expected[2];
577         int32_t expectedCount;
578         // For mixed units, accuracy of the smallest unit
579         double accuracy;
580     } testCases[]{
581         // Significantly less than 2.0.
582         {"1.9999",
583          "foot",
584          "foot-and-inch",
585          1.9999,
586          {Measure(1, MeasureUnit::createFoot(status), status),
587           Measure(11.9988, MeasureUnit::createInch(status), status)},
588          2,
589          0},
590 
591         // A minimal nudge under 2.0, rounding up to 2.0 ft, 0 in.
592         {"2-eps",
593          "foot",
594          "foot-and-inch",
595          2.0 - DBL_EPSILON,
596          {Measure(2, MeasureUnit::createFoot(status), status),
597           Measure(0, MeasureUnit::createInch(status), status)},
598          2,
599          0},
600 
601         // A slightly bigger nudge under 2.0, *not* rounding up to 2.0 ft!
602         {"2-3eps",
603          "foot",
604          "foot-and-inch",
605          2.0 - 3 * DBL_EPSILON,
606          {Measure(1, MeasureUnit::createFoot(status), status),
607           // We expect 12*3*DBL_EPSILON inches (7.92e-15) less than 12.
608           Measure(12 - 36 * DBL_EPSILON, MeasureUnit::createInch(status), status)},
609          2,
610          // Might accuracy be lacking with some compilers or on some systems? In
611          // case it is somehow lacking, we'll allow a delta of 12 * DBL_EPSILON.
612          12 * DBL_EPSILON},
613 
614         // Testing precision with meter and light-year.
615         //
616         // DBL_EPSILON light-years, ~2.22E-16 light-years, is ~2.1 meters
617         // (maximum precision when exponent is 0).
618         //
619         // 1e-16 light years is 0.946073 meters.
620 
621         // A 2.1 meter nudge under 2.0 light years, rounding up to 2.0 ly, 0 m.
622         {"2-eps",
623          "light-year",
624          "light-year-and-meter",
625          2.0 - DBL_EPSILON,
626          {Measure(2, MeasureUnit::createLightYear(status), status),
627           Measure(0, MeasureUnit::createMeter(status), status)},
628          2,
629          0},
630 
631         // A 2.1 meter nudge under 1.0 light years, rounding up to 1.0 ly, 0 m.
632         {"1-eps",
633          "light-year",
634          "light-year-and-meter",
635          1.0 - DBL_EPSILON,
636          {Measure(1, MeasureUnit::createLightYear(status), status),
637           Measure(0, MeasureUnit::createMeter(status), status)},
638          2,
639          0},
640 
641         // 1e-15 light years is 9.46073 meters (calculated using "bc" and the
642         // CLDR conversion factor). With double-precision maths in C++, we get
643         // 10.5. In this case, we're off by a bit more than 1 meter. With Java
644         // BigDecimal, we get accurate results.
645         {"1 + 1e-15",
646          "light-year",
647          "light-year-and-meter",
648          1.0 + 1e-15,
649          {Measure(1, MeasureUnit::createLightYear(status), status),
650           Measure(9.46073, MeasureUnit::createMeter(status), status)},
651          2,
652          1.5 /* meters, precision */},
653 
654         // 2.1 meters more than 1 light year is not rounded away.
655         {"1 + eps",
656          "light-year",
657          "light-year-and-meter",
658          1.0 + DBL_EPSILON,
659          {Measure(1, MeasureUnit::createLightYear(status), status),
660           Measure(2.1, MeasureUnit::createMeter(status), status)},
661          2,
662          0.001},
663 
664         // Negative numbers
665         {"Negative number conversion",
666          "yard",
667          "mile-and-yard",
668          -1800,
669          {Measure(-1, MeasureUnit::createMile(status), status),
670           Measure(-40, MeasureUnit::createYard(status), status)},
671          2,
672          1e-10},
673     };
674     status.assertSuccess();
675 
676     ConversionRates rates(status);
677     MeasureUnit input, output;
678     MeasureUnitImpl tempInput, tempOutput;
679     MaybeStackVector<Measure> measures;
680     auto testATestCase = [&](const ComplexUnitsConverter& converter ,StringPiece initMsg , const TestCase &testCase) {
681         measures = converter.convert(testCase.value, nullptr, status);
682 
683         CharString msg(initMsg, status);
684         msg.append(testCase.msg, status);
685         msg.append(" ", status);
686         msg.append(testCase.input, status);
687         msg.append(" -> ", status);
688         msg.append(testCase.output, status);
689 
690         CharString msgCount(msg, status);
691         msgCount.append(", measures.length()", status);
692         assertEquals(msgCount.data(), testCase.expectedCount, measures.length());
693         for (int i = 0; i < measures.length() && i < testCase.expectedCount; i++) {
694             if (i == testCase.expectedCount-1) {
695                 assertEqualsNear(msg.data(), testCase.expected[i].getNumber().getDouble(status),
696                                  measures[i]->getNumber().getDouble(status), testCase.accuracy);
697             } else {
698                 assertEquals(msg.data(), testCase.expected[i].getNumber().getDouble(status),
699                              measures[i]->getNumber().getDouble(status));
700             }
701             assertEquals(msg.data(), testCase.expected[i].getUnit().getIdentifier(),
702                          measures[i]->getUnit().getIdentifier());
703         }
704     };
705 
706     for (const auto &testCase : testCases)
707     {
708         input = MeasureUnit::forIdentifier(testCase.input, status);
709         output = MeasureUnit::forIdentifier(testCase.output, status);
710         const MeasureUnitImpl& inputImpl = MeasureUnitImpl::forMeasureUnit(input, tempInput, status);
711         const MeasureUnitImpl& outputImpl = MeasureUnitImpl::forMeasureUnit(output, tempOutput, status);
712 
713         ComplexUnitsConverter converter1(inputImpl, outputImpl, rates, status);
714         testATestCase(converter1, "ComplexUnitsConverter #1 " , testCase);
715 
716         // Test ComplexUnitsConverter created with CLDR units identifiers.
717         ComplexUnitsConverter converter2( testCase.input, testCase.output, status);
718         testATestCase(converter2, "ComplexUnitsConverter #1 " , testCase);
719     }
720     status.assertSuccess();
721 }
722 
testComplexUnitsConverterSorting()723 void UnitsTest::testComplexUnitsConverterSorting() {
724     IcuTestErrorCode status(*this, "UnitsTest::testComplexUnitsConverterSorting");
725     ConversionRates conversionRates(status);
726 
727     status.assertSuccess();
728 
729     struct TestCase {
730         const char *msg;
731         const char *input;
732         const char *output;
733         double inputValue;
734         Measure expected[3];
735         int32_t expectedCount;
736         // For mixed units, accuracy of the smallest unit
737         double accuracy;
738     } testCases[]{{"inch-and-foot",
739                    "meter",
740                    "inch-and-foot",
741                    10.0,
742                    {
743                        Measure(9.70079, MeasureUnit::createInch(status), status),
744                        Measure(32, MeasureUnit::createFoot(status), status),
745                        Measure(0, MeasureUnit::createBit(status), status),
746                    },
747                    2,
748                    0.00001},
749                   {"inch-and-yard-and-foot",
750                    "meter",
751                    "inch-and-yard-and-foot",
752                    100.0,
753                    {
754                        Measure(1.0079, MeasureUnit::createInch(status), status),
755                        Measure(109, MeasureUnit::createYard(status), status),
756                        Measure(1, MeasureUnit::createFoot(status), status),
757                    },
758                    3,
759                    0.0001}};
760 
761     for (const auto &testCase : testCases) {
762         MeasureUnitImpl inputImpl = MeasureUnitImpl::forIdentifier(testCase.input, status);
763         if (status.errIfFailureAndReset()) {
764             continue;
765         }
766         MeasureUnitImpl outputImpl = MeasureUnitImpl::forIdentifier(testCase.output, status);
767         if (status.errIfFailureAndReset()) {
768             continue;
769         }
770         ComplexUnitsConverter converter(inputImpl, outputImpl, conversionRates, status);
771         if (status.errIfFailureAndReset()) {
772             continue;
773         }
774 
775         auto actual = converter.convert(testCase.inputValue, nullptr, status);
776         if (status.errIfFailureAndReset()) {
777             continue;
778         }
779         if (actual.length() < testCase.expectedCount) {
780             errln("converter.convert(...) returned too few Measures");
781             continue;
782         }
783 
784         for (int i = 0; i < testCase.expectedCount; i++) {
785             assertEquals(testCase.msg, testCase.expected[i].getUnit().getIdentifier(),
786                          actual[i]->getUnit().getIdentifier());
787 
788             if (testCase.expected[i].getNumber().getType() == Formattable::Type::kInt64) {
789                 assertEquals(testCase.msg, testCase.expected[i].getNumber().getInt64(),
790                              actual[i]->getNumber().getInt64());
791             } else {
792                 assertEqualsNear(testCase.msg, testCase.expected[i].getNumber().getDouble(),
793                                  actual[i]->getNumber().getDouble(), testCase.accuracy);
794             }
795         }
796     }
797 }
798 
799 /**
800  * This class represents the output fields from unitPreferencesTest.txt. Please
801  * see the documentation at the top of that file for details.
802  *
803  * For "mixed units" output, there are more (repeated) output fields. The last
804  * output unit has the expected output specified as both a rational fraction and
805  * a decimal fraction. This class ignores rational fractions, and expects to
806  * find a decimal fraction for each output unit.
807  */
808 class ExpectedOutput {
809   public:
810     // Counts number of units in the output. When this is more than one, we have
811     // "mixed units" in the expected output.
812     int _compoundCount = 0;
813 
814     // Counts how many fields were skipped: we expect to skip only one per
815     // output unit type (the rational fraction).
816     int _skippedFields = 0;
817 
818     // The expected output units: more than one for "mixed units".
819     MeasureUnit _measureUnits[3];
820 
821     // The amounts of each of the output units.
822     double _amounts[3];
823 
824     /**
825      * Parse an expected output field from the test data file.
826      *
827      * @param output may be a string representation of an integer, a rational
828      * fraction, a decimal fraction, or it may be a unit identifier. Whitespace
829      * should already be trimmed. This function ignores rational fractions,
830      * saving only decimal fractions and their unit identifiers.
831      * @return true if the field was successfully parsed, false if parsing
832      * failed.
833      */
parseOutputField(StringPiece output,UErrorCode & errorCode)834     void parseOutputField(StringPiece output, UErrorCode &errorCode) {
835         if (U_FAILURE(errorCode)) return;
836         DecimalQuantity dqOutputD;
837 
838         dqOutputD.setToDecNumber(output, errorCode);
839         if (U_SUCCESS(errorCode)) {
840             _amounts[_compoundCount] = dqOutputD.toDouble();
841             return;
842         } else if (errorCode == U_DECIMAL_NUMBER_SYNTAX_ERROR) {
843             // Not a decimal fraction, it might be a rational fraction or a unit
844             // identifier: continue.
845             errorCode = U_ZERO_ERROR;
846         } else {
847             // Unexpected error, so we propagate it.
848             return;
849         }
850 
851         _measureUnits[_compoundCount] = MeasureUnit::forIdentifier(output, errorCode);
852         if (U_SUCCESS(errorCode)) {
853             _compoundCount++;
854             _skippedFields = 0;
855             return;
856         }
857         _skippedFields++;
858         if (_skippedFields < 2) {
859             // We are happy skipping one field per output unit: we want to skip
860             // rational fraction fields like "11 / 10".
861             errorCode = U_ZERO_ERROR;
862             return;
863         } else {
864             // Propagate the error.
865             return;
866         }
867     }
868 
869     /**
870      * Produces an output string for debug purposes.
871      */
toDebugString()872     std::string toDebugString() {
873         std::string result;
874         for (int i = 0; i < _compoundCount; i++) {
875             result += std::to_string(_amounts[i]);
876             result += " ";
877             result += _measureUnits[i].getIdentifier();
878             result += " ";
879         }
880         return result;
881     }
882 };
883 
884 // Checks a vector of Measure instances against ExpectedOutput.
checkOutput(UnitsTest * unitsTest,const char * msg,ExpectedOutput expected,const MaybeStackVector<Measure> & actual,double precision)885 void checkOutput(UnitsTest *unitsTest, const char *msg, ExpectedOutput expected,
886                  const MaybeStackVector<Measure> &actual, double precision) {
887     IcuTestErrorCode status(*unitsTest, "checkOutput");
888 
889     CharString testMessage("Test case \"", status);
890     testMessage.append(msg, status);
891     testMessage.append("\": expected output: ", status);
892     testMessage.append(expected.toDebugString().c_str(), status);
893     testMessage.append(", obtained output:", status);
894     for (int i = 0; i < actual.length(); i++) {
895         testMessage.append(" ", status);
896         testMessage.append(std::to_string(actual[i]->getNumber().getDouble(status)), status);
897         testMessage.append(" ", status);
898         testMessage.appendInvariantChars(actual[i]->getUnit().getIdentifier(), status);
899     }
900     if (!unitsTest->assertEquals(testMessage.data(), expected._compoundCount, actual.length())) {
901         return;
902     };
903     for (int i = 0; i < actual.length(); i++) {
904         double permittedDiff = precision * expected._amounts[i];
905         if (permittedDiff == 0) {
906             // If 0 is expected, still permit a small delta.
907             // TODO: revisit this experimentally chosen value:
908             permittedDiff = 0.00000001;
909         }
910         unitsTest->assertEqualsNear(testMessage.data(), expected._amounts[i],
911                                     actual[i]->getNumber().getDouble(status), permittedDiff);
912     }
913 }
914 
915 /**
916  * Runs a single data-driven unit test for unit preferences.
917  *
918  * This is a UParseLineFn as required by u_parseDelimitedFile, intended for
919  * parsing unitPreferencesTest.txt.
920  */
unitPreferencesTestDataLineFn(void * context,char * fields[][2],int32_t fieldCount,UErrorCode * pErrorCode)921 void unitPreferencesTestDataLineFn(void *context, char *fields[][2], int32_t fieldCount,
922                                    UErrorCode *pErrorCode) {
923     if (U_FAILURE(*pErrorCode)) return;
924     UnitsTest *unitsTest = static_cast<UnitsTest *>(context);
925     IcuTestErrorCode status(*unitsTest, "unitPreferencesTestDatalineFn");
926 
927     if (!unitsTest->assertTrue(u"unitPreferencesTestDataLineFn expects 9 fields for simple and 11 "
928                                u"fields for compound. Other field counts not yet supported. ",
929                                fieldCount == 9 || fieldCount == 11)) {
930         return;
931     }
932 
933     StringPiece quantity = trimField(fields[0]);
934     StringPiece usage = trimField(fields[1]);
935     StringPiece region = trimField(fields[2]);
936     // Unused // StringPiece inputR = trimField(fields[3]);
937     StringPiece inputD = trimField(fields[4]);
938     StringPiece inputUnit = trimField(fields[5]);
939     ExpectedOutput expected;
940     for (int i = 6; i < fieldCount; i++) {
941         expected.parseOutputField(trimField(fields[i]), status);
942     }
943     if (status.errIfFailureAndReset("parsing unitPreferencesTestData.txt test case: %s", fields[0][0])) {
944         return;
945     }
946 
947     DecimalQuantity dqInputD;
948     dqInputD.setToDecNumber(inputD, status);
949     if (status.errIfFailureAndReset("parsing decimal quantity: \"%.*s\"", inputD.length(),
950                                     inputD.data())) {
951         return;
952     }
953     double inputAmount = dqInputD.toDouble();
954 
955     MeasureUnit inputMeasureUnit = MeasureUnit::forIdentifier(inputUnit, status);
956     if (status.errIfFailureAndReset("forIdentifier(\"%.*s\")", inputUnit.length(), inputUnit.data())) {
957         return;
958     }
959 
960     unitsTest->logln("Quantity (Category): \"%.*s\", Usage: \"%.*s\", Region: \"%.*s\", "
961                      "Input: \"%f %s\", Expected Output: %s",
962                      quantity.length(), quantity.data(), usage.length(), usage.data(), region.length(),
963                      region.data(), inputAmount, inputMeasureUnit.getIdentifier(),
964                      expected.toDebugString().c_str());
965 
966     if (U_FAILURE(status)) {
967         return;
968     }
969 
970     CharString localeID;
971     localeID.append("und-", status); // append undefined language.
972     localeID.append(region, status);
973     Locale locale(localeID.data());
974     UnitsRouter router(inputMeasureUnit, locale, usage, status);
975     if (status.errIfFailureAndReset("UnitsRouter(<%s>, \"%.*s\", \"%.*s\", status)",
976                                     inputMeasureUnit.getIdentifier(), region.length(), region.data(),
977                                     usage.length(), usage.data())) {
978         return;
979     }
980 
981     CharString msg(quantity, status);
982     msg.append(" ", status);
983     msg.append(usage, status);
984     msg.append(" ", status);
985     msg.append(region, status);
986     msg.append(" ", status);
987     msg.append(inputD, status);
988     msg.append(" ", status);
989     msg.append(inputMeasureUnit.getIdentifier(), status);
990     if (status.errIfFailureAndReset("Failure before router.route")) {
991         return;
992     }
993     RouteResult routeResult = router.route(inputAmount, nullptr, status);
994     if (status.errIfFailureAndReset("router.route(inputAmount, ...)")) {
995         return;
996     }
997     // TODO: revisit this experimentally chosen precision:
998     checkOutput(unitsTest, msg.data(), expected, routeResult.measures, 0.0000000001);
999 
1000     // Test UnitsRouter created with CLDR units identifiers.
1001     CharString inputUnitIdentifier(inputUnit, status);
1002     UnitsRouter router2(inputUnitIdentifier.data(), locale, usage, status);
1003     if (status.errIfFailureAndReset("UnitsRouter2(<%s>, \"%.*s\", \"%.*s\", status)",
1004                                     inputUnitIdentifier.data(), region.length(), region.data(),
1005                                     usage.length(), usage.data())) {
1006         return;
1007     }
1008 
1009     CharString msg2(quantity, status);
1010     msg2.append(" ", status);
1011     msg2.append(usage, status);
1012     msg2.append(" ", status);
1013     msg2.append(region, status);
1014     msg2.append(" ", status);
1015     msg2.append(inputD, status);
1016     msg2.append(" ", status);
1017     msg2.append(inputUnitIdentifier.data(), status);
1018     if (status.errIfFailureAndReset("Failure before router2.route")) {
1019         return;
1020     }
1021 
1022     RouteResult routeResult2 = router2.route(inputAmount, nullptr, status);
1023     if (status.errIfFailureAndReset("router2.route(inputAmount, ...)")) {
1024         return;
1025     }
1026     // TODO: revisit this experimentally chosen precision:
1027     checkOutput(unitsTest, msg2.data(), expected, routeResult.measures, 0.0000000001);
1028 }
1029 
1030 /**
1031  * Parses the format used by unitPreferencesTest.txt, calling lineFn for each
1032  * line.
1033  *
1034  * This is a modified version of u_parseDelimitedFile, customized for
1035  * unitPreferencesTest.txt, due to it having a variable number of fields per
1036  * line.
1037  */
parsePreferencesTests(const char * filename,char delimiter,char * fields[][2],int32_t maxFieldCount,UParseLineFn * lineFn,void * context,UErrorCode * pErrorCode)1038 void parsePreferencesTests(const char *filename, char delimiter, char *fields[][2],
1039                            int32_t maxFieldCount, UParseLineFn *lineFn, void *context,
1040                            UErrorCode *pErrorCode) {
1041     FileStream *file;
1042     char line[10000];
1043     char *start, *limit;
1044     int32_t i;
1045 
1046     if (U_FAILURE(*pErrorCode)) {
1047         return;
1048     }
1049 
1050     if (fields == nullptr || lineFn == nullptr || maxFieldCount <= 0) {
1051         *pErrorCode = U_ILLEGAL_ARGUMENT_ERROR;
1052         return;
1053     }
1054 
1055     if (filename == nullptr || *filename == 0 || (*filename == '-' && filename[1] == 0)) {
1056         filename = nullptr;
1057         file = T_FileStream_stdin();
1058     } else {
1059         file = T_FileStream_open(filename, "r");
1060     }
1061     if (file == nullptr) {
1062         *pErrorCode = U_FILE_ACCESS_ERROR;
1063         return;
1064     }
1065 
1066     while (T_FileStream_readLine(file, line, sizeof(line)) != nullptr) {
1067         /* remove trailing newline characters */
1068         u_rtrim(line);
1069 
1070         start = line;
1071         *pErrorCode = U_ZERO_ERROR;
1072 
1073         /* skip this line if it is empty or a comment */
1074         if (*start == 0 || *start == '#') {
1075             continue;
1076         }
1077 
1078         /* remove in-line comments */
1079         limit = uprv_strchr(start, '#');
1080         if (limit != nullptr) {
1081             /* get white space before the pound sign */
1082             while (limit > start && U_IS_INV_WHITESPACE(*(limit - 1))) {
1083                 --limit;
1084             }
1085 
1086             /* truncate the line */
1087             *limit = 0;
1088         }
1089 
1090         /* skip lines with only whitespace */
1091         if (u_skipWhitespace(start)[0] == 0) {
1092             continue;
1093         }
1094 
1095         /* for each field, call the corresponding field function */
1096         for (i = 0; i < maxFieldCount; ++i) {
1097             /* set the limit pointer of this field */
1098             limit = start;
1099             while (*limit != delimiter && *limit != 0) {
1100                 ++limit;
1101             }
1102 
1103             /* set the field start and limit in the fields array */
1104             fields[i][0] = start;
1105             fields[i][1] = limit;
1106 
1107             /* set start to the beginning of the next field, if any */
1108             start = limit;
1109             if (*start != 0) {
1110                 ++start;
1111             } else {
1112                 break;
1113             }
1114         }
1115         if (i == maxFieldCount) {
1116             *pErrorCode = U_PARSE_ERROR;
1117         }
1118         int fieldCount = i + 1;
1119 
1120         /* call the field function */
1121         lineFn(context, fields, fieldCount, pErrorCode);
1122         if (U_FAILURE(*pErrorCode)) {
1123             break;
1124         }
1125     }
1126 
1127     if (filename != nullptr) {
1128         T_FileStream_close(file);
1129     }
1130 }
1131 
1132 /**
1133  * Runs data-driven unit tests for unit preferences. It looks for the test cases
1134  * in source/test/testdata/cldr/units/unitPreferencesTest.txt, which originates
1135  * in CLDR.
1136  */
testUnitPreferencesWithCLDRTests()1137 void UnitsTest::testUnitPreferencesWithCLDRTests() {
1138     const char *filename = "unitPreferencesTest.txt";
1139     const int32_t maxFields = 11;
1140     char *fields[maxFields][2];
1141 
1142     IcuTestErrorCode errorCode(*this, "UnitsTest::testUnitPreferencesWithCLDRTests");
1143     const char *sourceTestDataPath = getSourceTestData(errorCode);
1144     if (errorCode.errIfFailureAndReset("unable to find the source/test/testdata "
1145                                        "folder (getSourceTestData())")) {
1146         return;
1147     }
1148 
1149     CharString path(sourceTestDataPath, errorCode);
1150     path.appendPathPart("cldr/units", errorCode);
1151     path.appendPathPart(filename, errorCode);
1152 
1153     parsePreferencesTests(path.data(), ';', fields, maxFields, unitPreferencesTestDataLineFn, this,
1154                           errorCode);
1155     if (errorCode.errIfFailureAndReset("error parsing %s: %s\n", path.data(), u_errorName(errorCode))) {
1156         return;
1157     }
1158 }
1159 
1160 #endif /* #if !UCONFIG_NO_FORMATTING */
1161