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/unistr.h"
20 #include "unicode/unum.h"
21 #include "unicode/ures.h"
22 #include "units_complexconverter.h"
23 #include "units_converter.h"
24 #include "units_data.h"
25 #include "units_router.h"
26 #include "uparse.h"
27 #include "uresimp.h"
28 
29 struct UnitConversionTestCase {
30     const StringPiece source;
31     const StringPiece target;
32     const double inputValue;
33     const double expectedValue;
34 };
35 
36 using ::icu::number::impl::DecimalQuantity;
37 using namespace ::icu::units;
38 
39 class UnitsTest : public IntlTest {
40   public:
UnitsTest()41     UnitsTest() {}
42 
43     void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = NULL);
44 
45     void testUnitConstantFreshness();
46     void testConversionCapability();
47     void testConversions();
48     void testComplexUnitsConverter();
49     void testComplexUnitConverterSorting();
50     void testPreferences();
51     void testSiPrefixes();
52     void testMass();
53     void testTemperature();
54     void testArea();
55 };
56 
createUnitsTest()57 extern IntlTest *createUnitsTest() { return new UnitsTest(); }
58 
runIndexedTest(int32_t index,UBool exec,const char * & name,char *)59 void UnitsTest::runIndexedTest(int32_t index, UBool exec, const char *&name, char * /*par*/) {
60     if (exec) {
61         logln("TestSuite UnitsTest: ");
62     }
63     TESTCASE_AUTO_BEGIN;
64     TESTCASE_AUTO(testUnitConstantFreshness);
65     TESTCASE_AUTO(testConversionCapability);
66     TESTCASE_AUTO(testConversions);
67     TESTCASE_AUTO(testComplexUnitsConverter);
68     TESTCASE_AUTO(testComplexUnitConverterSorting);
69     TESTCASE_AUTO(testPreferences);
70     TESTCASE_AUTO(testSiPrefixes);
71     TESTCASE_AUTO(testMass);
72     TESTCASE_AUTO(testTemperature);
73     TESTCASE_AUTO(testArea);
74     TESTCASE_AUTO_END;
75 }
76 
77 // Tests the hard-coded constants in the code against constants that appear in
78 // units.txt.
testUnitConstantFreshness()79 void UnitsTest::testUnitConstantFreshness() {
80     IcuTestErrorCode status(*this, "testUnitConstantFreshness");
81     LocalUResourceBundlePointer unitsBundle(ures_openDirect(NULL, "units", status));
82     LocalUResourceBundlePointer unitConstants(
83         ures_getByKey(unitsBundle.getAlias(), "unitConstants", NULL, status));
84 
85     while (ures_hasNext(unitConstants.getAlias())) {
86         int32_t len;
87         const char *constant = NULL;
88         ures_getNextString(unitConstants.getAlias(), &len, &constant, status);
89 
90         Factor factor;
91         addSingleFactorConstant(constant, 1, POSITIVE, factor, status);
92         if (status.errDataIfFailureAndReset(
93                 "addSingleFactorConstant(<%s>, ...).\n\n"
94                 "If U_INVALID_FORMAT_ERROR, please check that \"icu4c/source/i18n/units_converter.cpp\" "
95                 "has all constants? Is \"%s\" a new constant?\n",
96                 constant, constant)) {
97             continue;
98         }
99 
100         // Check the values of constants that have a simple numeric value
101         factor.substituteConstants();
102         int32_t uLen;
103         UnicodeString uVal = ures_getStringByKey(unitConstants.getAlias(), constant, &uLen, status);
104         CharString val;
105         val.appendInvariantChars(uVal, status);
106         if (status.errDataIfFailureAndReset("Failed to get constant value for %s.", constant)) {
107             continue;
108         }
109         DecimalQuantity dqVal;
110         UErrorCode parseStatus = U_ZERO_ERROR;
111         // TODO(units): unify with strToDouble() in units_converter.cpp
112         dqVal.setToDecNumber(val.toStringPiece(), parseStatus);
113         if (!U_SUCCESS(parseStatus)) {
114             // Not simple to parse, skip validating this constant's value. (We
115             // leave catching mistakes to the data-driven integration tests.)
116             continue;
117         }
118         double expectedNumerator = dqVal.toDouble();
119         assertEquals(UnicodeString("Constant ") + constant + u" numerator", expectedNumerator,
120                      factor.factorNum);
121         assertEquals(UnicodeString("Constant ") + constant + u" denominator", 1.0, factor.factorDen);
122     }
123 }
124 
testConversionCapability()125 void UnitsTest::testConversionCapability() {
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     };
141 
142     for (const auto &testCase : testCases) {
143         UErrorCode status = U_ZERO_ERROR;
144 
145         MeasureUnitImpl source = MeasureUnitImpl::forIdentifier(testCase.source, status);
146         MeasureUnitImpl target = MeasureUnitImpl::forIdentifier(testCase.target, status);
147 
148         ConversionRates conversionRates(status);
149         auto convertibility = extractConvertibility(source, target, conversionRates, status);
150 
151         assertEquals(UnicodeString("Conversion Capability: ") + testCase.source + " to " +
152                          testCase.target,
153                      testCase.expectedState, convertibility);
154     }
155 }
156 
testSiPrefixes()157 void UnitsTest::testSiPrefixes() {
158     IcuTestErrorCode status(*this, "Units testSiPrefixes");
159     // Test Cases
160     struct TestCase {
161         const char *source;
162         const char *target;
163         const double inputValue;
164         const double expectedValue;
165     } testCases[]{
166         {"gram", "kilogram", 1.0, 0.001},            //
167         {"milligram", "kilogram", 1.0, 0.000001},    //
168         {"microgram", "kilogram", 1.0, 0.000000001}, //
169         {"megagram", "gram", 1.0, 1000000},          //
170         {"megagram", "kilogram", 1.0, 1000},         //
171         {"gigabyte", "byte", 1.0, 1000000000},       //
172         // TODO: Fix `watt` probelms.
173         // {"megawatt", "watt", 1.0, 1000000},          //
174         // {"megawatt", "kilowatt", 1.0, 1000},         //
175     };
176 
177     for (const auto &testCase : testCases) {
178         UErrorCode status = U_ZERO_ERROR;
179 
180         MeasureUnitImpl source = MeasureUnitImpl::forIdentifier(testCase.source, status);
181         MeasureUnitImpl target = MeasureUnitImpl::forIdentifier(testCase.target, status);
182 
183         ConversionRates conversionRates(status);
184         UnitConverter converter(source, target, conversionRates, status);
185 
186         assertEqualsNear(UnicodeString("testSiPrefixes: ") + testCase.source + " to " + testCase.target,
187                          testCase.expectedValue, converter.convert(testCase.inputValue),
188                          0.0001 * testCase.expectedValue);
189     }
190 }
191 
testMass()192 void UnitsTest::testMass() {
193     IcuTestErrorCode status(*this, "Units testMass");
194 
195     // Test Cases
196     struct TestCase {
197         const char *source;
198         const char *target;
199         const double inputValue;
200         const double expectedValue;
201     } testCases[]{
202         {"gram", "kilogram", 1.0, 0.001},      //
203         {"pound", "kilogram", 1.0, 0.453592},  //
204         {"pound", "kilogram", 2.0, 0.907185},  //
205         {"ounce", "pound", 16.0, 1.0},         //
206         {"ounce", "kilogram", 16.0, 0.453592}, //
207         {"ton", "pound", 1.0, 2000},           //
208         {"stone", "pound", 1.0, 14},           //
209         {"stone", "kilogram", 1.0, 6.35029}    //
210     };
211 
212     for (const auto &testCase : testCases) {
213         UErrorCode status = U_ZERO_ERROR;
214 
215         MeasureUnitImpl source = MeasureUnitImpl::forIdentifier(testCase.source, status);
216         MeasureUnitImpl target = MeasureUnitImpl::forIdentifier(testCase.target, status);
217 
218         ConversionRates conversionRates(status);
219         UnitConverter converter(source, target, conversionRates, status);
220 
221         assertEqualsNear(UnicodeString("testMass: ") + testCase.source + " to " + testCase.target,
222                          testCase.expectedValue, converter.convert(testCase.inputValue),
223                          0.0001 * testCase.expectedValue);
224     }
225 }
226 
testTemperature()227 void UnitsTest::testTemperature() {
228     IcuTestErrorCode status(*this, "Units testTemperature");
229     // Test Cases
230     struct TestCase {
231         const char *source;
232         const char *target;
233         const double inputValue;
234         const double expectedValue;
235     } testCases[]{
236         {"celsius", "fahrenheit", 0.0, 32.0},   //
237         {"celsius", "fahrenheit", 10.0, 50.0},  //
238         {"fahrenheit", "celsius", 32.0, 0.0},   //
239         {"fahrenheit", "celsius", 89.6, 32},    //
240         {"kelvin", "fahrenheit", 0.0, -459.67}, //
241         {"kelvin", "fahrenheit", 300, 80.33},   //
242         {"kelvin", "celsius", 0.0, -273.15},    //
243         {"kelvin", "celsius", 300.0, 26.85}     //
244     };
245 
246     for (const auto &testCase : testCases) {
247         UErrorCode status = U_ZERO_ERROR;
248 
249         MeasureUnitImpl source = MeasureUnitImpl::forIdentifier(testCase.source, status);
250         MeasureUnitImpl target = MeasureUnitImpl::forIdentifier(testCase.target, status);
251 
252         ConversionRates conversionRates(status);
253         UnitConverter converter(source, target, conversionRates, status);
254 
255         assertEqualsNear(UnicodeString("testTemperature: ") + testCase.source + " to " + testCase.target,
256                          testCase.expectedValue, converter.convert(testCase.inputValue),
257                          0.0001 * uprv_fabs(testCase.expectedValue));
258     }
259 }
260 
testArea()261 void UnitsTest::testArea() {
262     IcuTestErrorCode status(*this, "Units Area");
263 
264     // Test Cases
265     struct TestCase {
266         const char *source;
267         const char *target;
268         const double inputValue;
269         const double expectedValue;
270     } testCases[]{
271         {"square-meter", "square-yard", 10.0, 11.9599},     //
272         {"hectare", "square-yard", 1.0, 11959.9},           //
273         {"square-mile", "square-foot", 0.0001, 2787.84},    //
274         {"hectare", "square-yard", 1.0, 11959.9},           //
275         {"hectare", "square-meter", 1.0, 10000},            //
276         {"hectare", "square-meter", 0.0, 0.0},              //
277         {"square-mile", "square-foot", 0.0001, 2787.84},    //
278         {"square-yard", "square-foot", 10, 90},             //
279         {"square-yard", "square-foot", 0, 0},               //
280         {"square-yard", "square-foot", 0.000001, 0.000009}, //
281         {"square-mile", "square-foot", 0.0, 0.0},           //
282     };
283 
284     for (const auto &testCase : testCases) {
285         UErrorCode status = U_ZERO_ERROR;
286 
287         MeasureUnitImpl source = MeasureUnitImpl::forIdentifier(testCase.source, status);
288         MeasureUnitImpl target = MeasureUnitImpl::forIdentifier(testCase.target, status);
289 
290         ConversionRates conversionRates(status);
291         UnitConverter converter(source, target, conversionRates, status);
292 
293         assertEqualsNear(UnicodeString("testArea: ") + testCase.source + " to " + testCase.target,
294                          testCase.expectedValue, converter.convert(testCase.inputValue),
295                          0.0001 * testCase.expectedValue);
296     }
297 }
298 
299 /**
300  * Trims whitespace off of the specified string.
301  * @param field is two pointers pointing at the start and end of the string.
302  * @return A StringPiece with initial and final space characters trimmed off.
303  */
trimField(char * (& field)[2])304 StringPiece trimField(char *(&field)[2]) {
305     const char *start = field[0];
306     start = u_skipWhitespace(start);
307     if (start >= field[1]) {
308         start = field[1];
309     }
310     const char *end = field[1];
311     while ((start < end) && U_IS_INV_WHITESPACE(*(end - 1))) {
312         end--;
313     }
314     int32_t length = (int32_t)(end - start);
315     return StringPiece(start, length);
316 }
317 
318 // Used for passing context to unitsTestDataLineFn via u_parseDelimitedFile.
319 struct UnitsTestContext {
320     // Provides access to UnitsTest methods like logln.
321     UnitsTest *unitsTest;
322     // Conversion rates: does not take ownership.
323     ConversionRates *conversionRates;
324 };
325 
326 /**
327  * Deals with a single data-driven unit test for unit conversions.
328  *
329  * This is a UParseLineFn as required by u_parseDelimitedFile, intended for
330  * parsing unitsTest.txt.
331  *
332  * @param context Must point at a UnitsTestContext struct.
333  * @param fields A list of pointer-pairs, each pair pointing at the start and
334  * end of each field. End pointers are important because these are *not*
335  * null-terminated strings. (Interpreted as a null-terminated string,
336  * fields[0][0] points at the whole line.)
337  * @param fieldCount The number of fields (pointer pairs) passed to the fields
338  * parameter.
339  * @param pErrorCode Receives status.
340  */
unitsTestDataLineFn(void * context,char * fields[][2],int32_t fieldCount,UErrorCode * pErrorCode)341 void unitsTestDataLineFn(void *context, char *fields[][2], int32_t fieldCount, UErrorCode *pErrorCode) {
342     if (U_FAILURE(*pErrorCode)) {
343         return;
344     }
345     UnitsTestContext *ctx = (UnitsTestContext *)context;
346     UnitsTest *unitsTest = ctx->unitsTest;
347     (void)fieldCount; // unused UParseLineFn variable
348     IcuTestErrorCode status(*unitsTest, "unitsTestDatalineFn");
349 
350     StringPiece quantity = trimField(fields[0]);
351     StringPiece x = trimField(fields[1]);
352     StringPiece y = trimField(fields[2]);
353     StringPiece commentConversionFormula = trimField(fields[3]);
354     StringPiece utf8Expected = trimField(fields[4]);
355 
356     UNumberFormat *nf = unum_open(UNUM_DEFAULT, NULL, -1, "en_US", NULL, status);
357     if (status.errIfFailureAndReset("unum_open failed")) {
358         return;
359     }
360     UnicodeString uExpected = UnicodeString::fromUTF8(utf8Expected);
361     double expected = unum_parseDouble(nf, uExpected.getBuffer(), uExpected.length(), 0, status);
362     unum_close(nf);
363     if (status.errIfFailureAndReset("unum_parseDouble(\"%s\") failed", utf8Expected)) {
364         return;
365     }
366 
367     CharString sourceIdent(x, status);
368     MeasureUnitImpl sourceUnit = MeasureUnitImpl::forIdentifier(x, status);
369     if (status.errIfFailureAndReset("forIdentifier(\"%.*s\")", x.length(), x.data())) {
370         return;
371     }
372 
373     CharString targetIdent(y, status);
374     MeasureUnitImpl targetUnit = MeasureUnitImpl::forIdentifier(y, status);
375     if (status.errIfFailureAndReset("forIdentifier(\"%.*s\")", y.length(), y.data())) {
376         return;
377     }
378 
379     unitsTest->logln("Quantity (Category): \"%.*s\", "
380                      "Expected value of \"1000 %.*s in %.*s\": %f, "
381                      "commentConversionFormula: \"%.*s\", ",
382                      quantity.length(), quantity.data(), x.length(), x.data(), y.length(), y.data(),
383                      expected, commentConversionFormula.length(), commentConversionFormula.data());
384 
385     // Convertibility:
386     auto convertibility = extractConvertibility(sourceUnit, targetUnit, *ctx->conversionRates, status);
387     if (status.errIfFailureAndReset("extractConvertibility(<%s>, <%s>, ...)",
388                                     sourceIdent.data(), targetIdent.data())) {
389         return;
390     }
391     CharString msg;
392     msg.append("convertible: ", status)
393         .append(sourceIdent.data(), status)
394         .append(" -> ", status)
395         .append(targetIdent.data(), status);
396     if (status.errIfFailureAndReset("msg construction")) {
397         return;
398     }
399     unitsTest->assertNotEquals(msg.data(), UNCONVERTIBLE, convertibility);
400 
401     // Conversion:
402     UnitConverter converter(sourceUnit, targetUnit, *ctx->conversionRates, status);
403     if (status.errIfFailureAndReset("constructor: UnitConverter(<%s>, <%s>, status)",
404                                     sourceIdent.data(), targetIdent.data())) {
405         return;
406     }
407     double got = converter.convert(1000);
408     msg.clear();
409     msg.append("Converting 1000 ", status).append(x, status).append(" to ", status).append(y, status);
410     unitsTest->assertEqualsNear(msg.data(), expected, got, 0.0001 * expected);
411     double inverted = converter.convertInverse(got);
412     msg.clear();
413     msg.append("Converting back to ", status).append(x, status).append(" from ", status).append(y, status);
414     unitsTest->assertEqualsNear(msg.data(), 1000, inverted, 0.0001);
415 }
416 
417 /**
418  * Runs data-driven unit tests for unit conversion. It looks for the test cases
419  * in source/test/testdata/cldr/units/unitsTest.txt, which originates in CLDR.
420  */
testConversions()421 void UnitsTest::testConversions() {
422     const char *filename = "unitsTest.txt";
423     const int32_t kNumFields = 5;
424     char *fields[kNumFields][2];
425 
426     IcuTestErrorCode errorCode(*this, "UnitsTest::testConversions");
427     const char *sourceTestDataPath = getSourceTestData(errorCode);
428     if (errorCode.errIfFailureAndReset("unable to find the source/test/testdata "
429                                        "folder (getSourceTestData())")) {
430         return;
431     }
432 
433     CharString path(sourceTestDataPath, errorCode);
434     path.appendPathPart("cldr/units", errorCode);
435     path.appendPathPart(filename, errorCode);
436 
437     ConversionRates rates(errorCode);
438     UnitsTestContext ctx = {this, &rates};
439     u_parseDelimitedFile(path.data(), ';', fields, kNumFields, unitsTestDataLineFn, &ctx, errorCode);
440     if (errorCode.errIfFailureAndReset("error parsing %s: %s\n", path.data(), u_errorName(errorCode))) {
441         return;
442     }
443 }
444 
testComplexUnitsConverter()445 void UnitsTest::testComplexUnitsConverter() {
446     IcuTestErrorCode status(*this, "UnitsTest::testComplexUnitsConverter");
447     ConversionRates rates(status);
448     MeasureUnit input = MeasureUnit::getFoot();
449     MeasureUnit output = MeasureUnit::forIdentifier("foot-and-inch", status);
450     MeasureUnitImpl tempInput, tempOutput;
451     const MeasureUnitImpl &inputImpl = MeasureUnitImpl::forMeasureUnit(input, tempInput, status);
452     const MeasureUnitImpl &outputImpl = MeasureUnitImpl::forMeasureUnit(output, tempOutput, status);
453     auto converter = ComplexUnitsConverter(inputImpl, outputImpl, rates, status);
454 
455     // Significantly less than 2.0.
456     MaybeStackVector<Measure> measures = converter.convert(1.9999, nullptr, status);
457     assertEquals("measures length", 2, measures.length());
458     if (2 == measures.length()) {
459         assertEquals("1.9999: measures[0] value", 1.0, measures[0]->getNumber().getDouble(status));
460         assertEquals("1.9999: measures[0] unit", MeasureUnit::getFoot().getIdentifier(),
461                      measures[0]->getUnit().getIdentifier());
462         assertEqualsNear("1.9999: measures[1] value", 11.9988,
463                          measures[1]->getNumber().getDouble(status), 0.0001);
464         assertEquals("1.9999: measures[1] unit", MeasureUnit::getInch().getIdentifier(),
465                      measures[1]->getUnit().getIdentifier());
466     }
467 
468     // TODO(icu-units#100): consider factoring out the set of tests to make this function more
469     // data-driven.
470 
471     // A minimal nudge under 2.0.
472     measures = converter.convert((2.0 - DBL_EPSILON), nullptr, status);
473     assertEquals("measures length", 2, measures.length());
474     if (2 == measures.length()) {
475         assertEquals("1 - eps: measures[0] value", 2.0, measures[0]->getNumber().getDouble(status));
476         assertEquals("1 - eps: measures[0] unit", MeasureUnit::getFoot().getIdentifier(),
477                      measures[0]->getUnit().getIdentifier());
478         assertEquals("1 - eps: measures[1] value", 0.0, measures[1]->getNumber().getDouble(status));
479         assertEquals("1 - eps: measures[1] unit", MeasureUnit::getInch().getIdentifier(),
480                      measures[1]->getUnit().getIdentifier());
481     }
482 
483     // Testing precision with meter and light-year. 1e-16 light years is
484     // 0.946073 meters, and double precision can provide only ~15 decimal
485     // digits, so we don't expect to get anything less than 1 meter.
486 
487     // An epsilon's nudge under one light-year: should give 1 ly, 0 m.
488     input = MeasureUnit::getLightYear();
489     output = MeasureUnit::forIdentifier("light-year-and-meter", status);
490     const MeasureUnitImpl &inputImpl3 = MeasureUnitImpl::forMeasureUnit(input, tempInput, status);
491     const MeasureUnitImpl &outputImpl3 = MeasureUnitImpl::forMeasureUnit(output, tempOutput, status);
492     converter = ComplexUnitsConverter(inputImpl3, outputImpl3, rates, status);
493     measures = converter.convert((2.0 - DBL_EPSILON), nullptr, status);
494     assertEquals("measures length", 2, measures.length());
495     if (2 == measures.length()) {
496         assertEquals("light-year test: measures[0] value", 2.0,
497                      measures[0]->getNumber().getDouble(status));
498         assertEquals("light-year test: measures[0] unit", MeasureUnit::getLightYear().getIdentifier(),
499                      measures[0]->getUnit().getIdentifier());
500         assertEquals("light-year test: measures[1] value", 0.0,
501                      measures[1]->getNumber().getDouble(status));
502         assertEquals("light-year test: measures[1] unit", MeasureUnit::getMeter().getIdentifier(),
503                      measures[1]->getUnit().getIdentifier());
504     }
505 
506     // 1e-15 light years is 9.46073 meters (calculated using "bc" and the CLDR
507     // conversion factor). With double-precision maths, we get 10.5. In this
508     // case, we're off by almost 1 meter.
509     measures = converter.convert((1.0 + 1e-15), nullptr, status);
510     assertEquals("measures length", 2, measures.length());
511     if (2 == measures.length()) {
512         assertEquals("light-year test: measures[0] value", 1.0,
513                      measures[0]->getNumber().getDouble(status));
514         assertEquals("light-year test: measures[0] unit", MeasureUnit::getLightYear().getIdentifier(),
515                      measures[0]->getUnit().getIdentifier());
516         assertEqualsNear("light-year test: measures[1] value", 10,
517                          measures[1]->getNumber().getDouble(status), 1);
518         assertEquals("light-year test: measures[1] unit", MeasureUnit::getMeter().getIdentifier(),
519                      measures[1]->getUnit().getIdentifier());
520     }
521 
522     // 2e-16 light years is 1.892146 meters. We consider this in the noise, and
523     // thus expect a 0. (This test fails when 2e-16 is increased to 4e-16.)
524     measures = converter.convert((1.0 + 2e-16), nullptr, status);
525     assertEquals("measures length", 2, measures.length());
526     if (2 == measures.length()) {
527         assertEquals("light-year test: measures[0] value", 1.0,
528                      measures[0]->getNumber().getDouble(status));
529         assertEquals("light-year test: measures[0] unit", MeasureUnit::getLightYear().getIdentifier(),
530                      measures[0]->getUnit().getIdentifier());
531         assertEquals("light-year test: measures[1] value", 0.0,
532                      measures[1]->getNumber().getDouble(status));
533         assertEquals("light-year test: measures[1] unit", MeasureUnit::getMeter().getIdentifier(),
534                      measures[1]->getUnit().getIdentifier());
535     }
536 
537     // TODO(icu-units#63): test negative numbers!
538 }
539 
testComplexUnitConverterSorting()540 void UnitsTest::testComplexUnitConverterSorting() {
541     IcuTestErrorCode status(*this, "UnitsTest::testComplexUnitConverterSorting");
542 
543     MeasureUnitImpl source = MeasureUnitImpl::forIdentifier("meter", status);
544     MeasureUnitImpl target = MeasureUnitImpl::forIdentifier("inch-and-foot", status);
545     ConversionRates conversionRates(status);
546 
547     ComplexUnitsConverter complexConverter(source, target, conversionRates, status);
548     auto measures = complexConverter.convert(10.0, nullptr, status);
549 
550     if (2 == measures.length()) {
551         assertEquals("inch-and-foot unit 0", "inch", measures[0]->getUnit().getIdentifier());
552         assertEquals("inch-and-foot unit 1", "foot", measures[1]->getUnit().getIdentifier());
553 
554         assertEqualsNear("inch-and-foot value 0", 9.7008, measures[0]->getNumber().getDouble(), 0.0001);
555         assertEqualsNear("inch-and-foot value 1", 32, measures[1]->getNumber().getInt64(), 0.00001);
556     }
557 }
558 
559 /**
560  * This class represents the output fields from unitPreferencesTest.txt. Please
561  * see the documentation at the top of that file for details.
562  *
563  * For "mixed units" output, there are more (repeated) output fields. The last
564  * output unit has the expected output specified as both a rational fraction and
565  * a decimal fraction. This class ignores rational fractions, and expects to
566  * find a decimal fraction for each output unit.
567  */
568 class ExpectedOutput {
569   public:
570     // Counts number of units in the output. When this is more than one, we have
571     // "mixed units" in the expected output.
572     int _compoundCount = 0;
573 
574     // Counts how many fields were skipped: we expect to skip only one per
575     // output unit type (the rational fraction).
576     int _skippedFields = 0;
577 
578     // The expected output units: more than one for "mixed units".
579     MeasureUnit _measureUnits[3];
580 
581     // The amounts of each of the output units.
582     double _amounts[3];
583 
584     /**
585      * Parse an expected output field from the test data file.
586      *
587      * @param output may be a string representation of an integer, a rational
588      * fraction, a decimal fraction, or it may be a unit identifier. Whitespace
589      * should already be trimmed. This function ignores rational fractions,
590      * saving only decimal fractions and their unit identifiers.
591      * @return true if the field was successfully parsed, false if parsing
592      * failed.
593      */
parseOutputField(StringPiece output,UErrorCode & errorCode)594     void parseOutputField(StringPiece output, UErrorCode &errorCode) {
595         if (U_FAILURE(errorCode)) return;
596         DecimalQuantity dqOutputD;
597 
598         dqOutputD.setToDecNumber(output, errorCode);
599         if (U_SUCCESS(errorCode)) {
600             _amounts[_compoundCount] = dqOutputD.toDouble();
601             return;
602         } else if (errorCode == U_DECIMAL_NUMBER_SYNTAX_ERROR) {
603             // Not a decimal fraction, it might be a rational fraction or a unit
604             // identifier: continue.
605             errorCode = U_ZERO_ERROR;
606         } else {
607             // Unexpected error, so we propagate it.
608             return;
609         }
610 
611         _measureUnits[_compoundCount] = MeasureUnit::forIdentifier(output, errorCode);
612         if (U_SUCCESS(errorCode)) {
613             _compoundCount++;
614             _skippedFields = 0;
615             return;
616         }
617         _skippedFields++;
618         if (_skippedFields < 2) {
619             // We are happy skipping one field per output unit: we want to skip
620             // rational fraction fields like "11 / 10".
621             errorCode = U_ZERO_ERROR;
622             return;
623         } else {
624             // Propagate the error.
625             return;
626         }
627     }
628 
629     /**
630      * Produces an output string for debug purposes.
631      */
toDebugString()632     std::string toDebugString() {
633         std::string result;
634         for (int i = 0; i < _compoundCount; i++) {
635             result += std::to_string(_amounts[i]);
636             result += " ";
637             result += _measureUnits[i].getIdentifier();
638             result += " ";
639         }
640         return result;
641     }
642 };
643 
644 // Checks a vector of Measure instances against ExpectedOutput.
checkOutput(UnitsTest * unitsTest,const char * msg,ExpectedOutput expected,const MaybeStackVector<Measure> & actual,double precision)645 void checkOutput(UnitsTest *unitsTest, const char *msg, ExpectedOutput expected,
646                  const MaybeStackVector<Measure> &actual, double precision) {
647     IcuTestErrorCode status(*unitsTest, "checkOutput");
648 
649     CharString testMessage("Test case \"", status);
650     testMessage.append(msg, status);
651     testMessage.append("\": expected output: ", status);
652     testMessage.append(expected.toDebugString().c_str(), status);
653     testMessage.append(", obtained output:", status);
654     for (int i = 0; i < actual.length(); i++) {
655         testMessage.append(" ", status);
656         testMessage.append(std::to_string(actual[i]->getNumber().getDouble(status)), status);
657         testMessage.append(" ", status);
658         testMessage.appendInvariantChars(actual[i]->getUnit().getIdentifier(), status);
659     }
660     if (!unitsTest->assertEquals(testMessage.data(), expected._compoundCount, actual.length())) {
661         return;
662     };
663     for (int i = 0; i < actual.length(); i++) {
664         double permittedDiff = precision * expected._amounts[i];
665         if (permittedDiff == 0) {
666             // If 0 is expected, still permit a small delta.
667             // TODO: revisit this experimentally chosen value:
668             permittedDiff = 0.00000001;
669         }
670         unitsTest->assertEqualsNear(testMessage.data(), expected._amounts[i],
671                                     actual[i]->getNumber().getDouble(status), permittedDiff);
672     }
673 }
674 
675 /**
676  * Runs a single data-driven unit test for unit preferences.
677  *
678  * This is a UParseLineFn as required by u_parseDelimitedFile, intended for
679  * parsing unitPreferencesTest.txt.
680  */
unitPreferencesTestDataLineFn(void * context,char * fields[][2],int32_t fieldCount,UErrorCode * pErrorCode)681 void unitPreferencesTestDataLineFn(void *context, char *fields[][2], int32_t fieldCount,
682                                    UErrorCode *pErrorCode) {
683     if (U_FAILURE(*pErrorCode)) return;
684     UnitsTest *unitsTest = (UnitsTest *)context;
685     IcuTestErrorCode status(*unitsTest, "unitPreferencesTestDatalineFn");
686 
687     if (!unitsTest->assertTrue(u"unitPreferencesTestDataLineFn expects 9 fields for simple and 11 "
688                                u"fields for compound. Other field counts not yet supported. ",
689                                fieldCount == 9 || fieldCount == 11)) {
690         return;
691     }
692 
693     StringPiece quantity = trimField(fields[0]);
694     StringPiece usage = trimField(fields[1]);
695     StringPiece region = trimField(fields[2]);
696     // Unused // StringPiece inputR = trimField(fields[3]);
697     StringPiece inputD = trimField(fields[4]);
698     StringPiece inputUnit = trimField(fields[5]);
699     ExpectedOutput expected;
700     for (int i = 6; i < fieldCount; i++) {
701         expected.parseOutputField(trimField(fields[i]), status);
702     }
703     if (status.errIfFailureAndReset("parsing unitPreferencesTestData.txt test case: %s", fields[0][0])) {
704         return;
705     }
706 
707     DecimalQuantity dqInputD;
708     dqInputD.setToDecNumber(inputD, status);
709     if (status.errIfFailureAndReset("parsing decimal quantity: \"%.*s\"", inputD.length(),
710                                     inputD.data())) {
711         return;
712     }
713     double inputAmount = dqInputD.toDouble();
714 
715     MeasureUnit inputMeasureUnit = MeasureUnit::forIdentifier(inputUnit, status);
716     if (status.errIfFailureAndReset("forIdentifier(\"%.*s\")", inputUnit.length(), inputUnit.data())) {
717         return;
718     }
719 
720     unitsTest->logln("Quantity (Category): \"%.*s\", Usage: \"%.*s\", Region: \"%.*s\", "
721                      "Input: \"%f %s\", Expected Output: %s",
722                      quantity.length(), quantity.data(), usage.length(), usage.data(), region.length(),
723                      region.data(), inputAmount, inputMeasureUnit.getIdentifier(),
724                      expected.toDebugString().c_str());
725 
726     if (U_FAILURE(status)) {
727         return;
728     }
729 
730     UnitsRouter router(inputMeasureUnit, region, usage, status);
731     if (status.errIfFailureAndReset("UnitsRouter(<%s>, \"%.*s\", \"%.*s\", status)",
732                                     inputMeasureUnit.getIdentifier(), region.length(), region.data(),
733                                     usage.length(), usage.data())) {
734         return;
735     }
736 
737     CharString msg(quantity, status);
738     msg.append(" ", status);
739     msg.append(usage, status);
740     msg.append(" ", status);
741     msg.append(region, status);
742     msg.append(" ", status);
743     msg.append(inputD, status);
744     msg.append(" ", status);
745     msg.append(inputMeasureUnit.getIdentifier(), status);
746     if (status.errIfFailureAndReset("Failure before router.route")) {
747         return;
748     }
749     RouteResult routeResult = router.route(inputAmount, nullptr, status);
750     if (status.errIfFailureAndReset("router.route(inputAmount, ...)")) {
751         return;
752     }
753     // TODO: revisit this experimentally chosen precision:
754     checkOutput(unitsTest, msg.data(), expected, routeResult.measures, 0.0000000001);
755 }
756 
757 /**
758  * Parses the format used by unitPreferencesTest.txt, calling lineFn for each
759  * line.
760  *
761  * This is a modified version of u_parseDelimitedFile, customized for
762  * unitPreferencesTest.txt, due to it having a variable number of fields per
763  * line.
764  */
parsePreferencesTests(const char * filename,char delimiter,char * fields[][2],int32_t maxFieldCount,UParseLineFn * lineFn,void * context,UErrorCode * pErrorCode)765 void parsePreferencesTests(const char *filename, char delimiter, char *fields[][2],
766                            int32_t maxFieldCount, UParseLineFn *lineFn, void *context,
767                            UErrorCode *pErrorCode) {
768     FileStream *file;
769     char line[10000];
770     char *start, *limit;
771     int32_t i;
772 
773     if (U_FAILURE(*pErrorCode)) {
774         return;
775     }
776 
777     if (fields == NULL || lineFn == NULL || maxFieldCount <= 0) {
778         *pErrorCode = U_ILLEGAL_ARGUMENT_ERROR;
779         return;
780     }
781 
782     if (filename == NULL || *filename == 0 || (*filename == '-' && filename[1] == 0)) {
783         filename = NULL;
784         file = T_FileStream_stdin();
785     } else {
786         file = T_FileStream_open(filename, "r");
787     }
788     if (file == NULL) {
789         *pErrorCode = U_FILE_ACCESS_ERROR;
790         return;
791     }
792 
793     while (T_FileStream_readLine(file, line, sizeof(line)) != NULL) {
794         /* remove trailing newline characters */
795         u_rtrim(line);
796 
797         start = line;
798         *pErrorCode = U_ZERO_ERROR;
799 
800         /* skip this line if it is empty or a comment */
801         if (*start == 0 || *start == '#') {
802             continue;
803         }
804 
805         /* remove in-line comments */
806         limit = uprv_strchr(start, '#');
807         if (limit != NULL) {
808             /* get white space before the pound sign */
809             while (limit > start && U_IS_INV_WHITESPACE(*(limit - 1))) {
810                 --limit;
811             }
812 
813             /* truncate the line */
814             *limit = 0;
815         }
816 
817         /* skip lines with only whitespace */
818         if (u_skipWhitespace(start)[0] == 0) {
819             continue;
820         }
821 
822         /* for each field, call the corresponding field function */
823         for (i = 0; i < maxFieldCount; ++i) {
824             /* set the limit pointer of this field */
825             limit = start;
826             while (*limit != delimiter && *limit != 0) {
827                 ++limit;
828             }
829 
830             /* set the field start and limit in the fields array */
831             fields[i][0] = start;
832             fields[i][1] = limit;
833 
834             /* set start to the beginning of the next field, if any */
835             start = limit;
836             if (*start != 0) {
837                 ++start;
838             } else {
839                 break;
840             }
841         }
842         if (i == maxFieldCount) {
843             *pErrorCode = U_PARSE_ERROR;
844         }
845         int fieldCount = i + 1;
846 
847         /* call the field function */
848         lineFn(context, fields, fieldCount, pErrorCode);
849         if (U_FAILURE(*pErrorCode)) {
850             break;
851         }
852     }
853 
854     if (filename != NULL) {
855         T_FileStream_close(file);
856     }
857 }
858 
859 /**
860  * Runs data-driven unit tests for unit preferences. It looks for the test cases
861  * in source/test/testdata/cldr/units/unitPreferencesTest.txt, which originates
862  * in CLDR.
863  */
testPreferences()864 void UnitsTest::testPreferences() {
865     const char *filename = "unitPreferencesTest.txt";
866     const int32_t maxFields = 11;
867     char *fields[maxFields][2];
868 
869     IcuTestErrorCode errorCode(*this, "UnitsTest::testPreferences");
870     const char *sourceTestDataPath = getSourceTestData(errorCode);
871     if (errorCode.errIfFailureAndReset("unable to find the source/test/testdata "
872                                        "folder (getSourceTestData())")) {
873         return;
874     }
875 
876     CharString path(sourceTestDataPath, errorCode);
877     path.appendPathPart("cldr/units", errorCode);
878     path.appendPathPart(filename, errorCode);
879 
880     parsePreferencesTests(path.data(), ';', fields, maxFields, unitPreferencesTestDataLineFn, this,
881                           errorCode);
882     if (errorCode.errIfFailureAndReset("error parsing %s: %s\n", path.data(), u_errorName(errorCode))) {
883         return;
884     }
885 }
886 
887 #endif /* #if !UCONFIG_NO_FORMATTING */
888