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