• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // © 2019 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html#License
3 
4 // localematchertest.cpp
5 // created: 2019jul04 Markus W. Scherer
6 
7 #include <string>
8 #include <vector>
9 #include <utility>
10 
11 #include "unicode/utypes.h"
12 #include "unicode/localematcher.h"
13 #include "unicode/locid.h"
14 #include "charstr.h"
15 #include "cmemory.h"
16 #include "intltest.h"
17 #include "localeprioritylist.h"
18 #include "ucbuf.h"
19 
20 #define ARRAY_RANGE(array) (array), ((array) + UPRV_LENGTHOF(array))
21 
22 namespace {
23 
locString(const Locale * loc)24 const char *locString(const Locale *loc) {
25     return loc != nullptr ? loc->getName() : "(null)";
26 }
27 
28 struct TestCase {
29     int32_t lineNr = 0;
30 
31     CharString supported;
32     CharString def;
33     UnicodeString favor;
34     UnicodeString threshold;
35     CharString desired;
36     CharString expMatch;
37     CharString expDesired;
38     CharString expCombined;
39 
reset__anonf8fac5b80111::TestCase40     void reset() {
41         supported.clear();
42         def.clear();
43         favor.remove();
44         threshold.remove();
45     }
46 };
47 
48 }  // namespace
49 
50 class LocaleMatcherTest : public IntlTest {
51 public:
LocaleMatcherTest()52     LocaleMatcherTest() {}
53 
54     void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par=NULL);
55 
56     void testEmpty();
57     void testCopyErrorTo();
58     void testBasics();
59     void testSupportedDefault();
60     void testUnsupportedDefault();
61     void testDemotion();
62     void testDirection();
63     void testMatch();
64     void testResolvedLocale();
65     void testDataDriven();
66 
67 private:
68     UBool dataDriven(const TestCase &test, IcuTestErrorCode &errorCode);
69 };
70 
createLocaleMatcherTest()71 extern IntlTest *createLocaleMatcherTest() {
72     return new LocaleMatcherTest();
73 }
74 
runIndexedTest(int32_t index,UBool exec,const char * & name,char *)75 void LocaleMatcherTest::runIndexedTest(int32_t index, UBool exec, const char *&name, char * /*par*/) {
76     if(exec) {
77         logln("TestSuite LocaleMatcherTest: ");
78     }
79     TESTCASE_AUTO_BEGIN;
80     TESTCASE_AUTO(testEmpty);
81     TESTCASE_AUTO(testCopyErrorTo);
82     TESTCASE_AUTO(testBasics);
83     TESTCASE_AUTO(testSupportedDefault);
84     TESTCASE_AUTO(testUnsupportedDefault);
85     TESTCASE_AUTO(testDemotion);
86     TESTCASE_AUTO(testDirection);
87     TESTCASE_AUTO(testMatch);
88     TESTCASE_AUTO(testResolvedLocale);
89     TESTCASE_AUTO(testDataDriven);
90     TESTCASE_AUTO_END;
91 }
92 
testEmpty()93 void LocaleMatcherTest::testEmpty() {
94     IcuTestErrorCode errorCode(*this, "testEmpty");
95     LocaleMatcher matcher = LocaleMatcher::Builder().build(errorCode);
96     const Locale *best = matcher.getBestMatch(Locale::getFrench(), errorCode);
97     assertEquals("getBestMatch(fr)", "(null)", locString(best));
98     LocaleMatcher::Result result = matcher.getBestMatchResult("fr", errorCode);
99     assertEquals("getBestMatchResult(fr).des", "(null)", locString(result.getDesiredLocale()));
100     assertEquals("getBestMatchResult(fr).desIndex", -1, result.getDesiredIndex());
101     assertEquals("getBestMatchResult(fr).supp",
102                  "(null)", locString(result.getSupportedLocale()));
103     assertEquals("getBestMatchResult(fr).suppIndex",
104                  -1, result.getSupportedIndex());
105 }
106 
testCopyErrorTo()107 void LocaleMatcherTest::testCopyErrorTo() {
108     IcuTestErrorCode errorCode(*this, "testCopyErrorTo");
109     // The builder does not set any errors except out-of-memory.
110     // Test what we can.
111     LocaleMatcher::Builder builder;
112     UErrorCode success = U_ZERO_ERROR;
113     assertFalse("no error", builder.copyErrorTo(success));
114     assertTrue("still success", U_SUCCESS(success));
115     UErrorCode failure = U_INVALID_FORMAT_ERROR;
116     assertTrue("failure passed in", builder.copyErrorTo(failure));
117     assertEquals("same failure", U_INVALID_FORMAT_ERROR, failure);
118 }
119 
testBasics()120 void LocaleMatcherTest::testBasics() {
121     IcuTestErrorCode errorCode(*this, "testBasics");
122     Locale locales[] = { "fr", "en_GB", "en" };
123     {
124         LocaleMatcher matcher = LocaleMatcher::Builder().
125             setSupportedLocales(ARRAY_RANGE(locales)).build(errorCode);
126         const Locale *best = matcher.getBestMatch("en_GB", errorCode);
127         assertEquals("fromRange.getBestMatch(en_GB)", "en_GB", locString(best));
128         best = matcher.getBestMatch("en_US", errorCode);
129         assertEquals("fromRange.getBestMatch(en_US)", "en", locString(best));
130         best = matcher.getBestMatch("fr_FR", errorCode);
131         assertEquals("fromRange.getBestMatch(fr_FR)", "fr", locString(best));
132         best = matcher.getBestMatch("ja_JP", errorCode);
133         assertEquals("fromRange.getBestMatch(ja_JP)", "fr", locString(best));
134     }
135     // Code coverage: Variations of setting supported locales.
136     {
137         std::vector<Locale> locales{ "fr", "en_GB", "en" };
138         LocaleMatcher matcher = LocaleMatcher::Builder().
139             setSupportedLocales(locales.begin(), locales.end()).build(errorCode);
140         const Locale *best = matcher.getBestMatch("en_GB", errorCode);
141         assertEquals("fromRange.getBestMatch(en_GB)", "en_GB", locString(best));
142         best = matcher.getBestMatch("en_US", errorCode);
143         assertEquals("fromRange.getBestMatch(en_US)", "en", locString(best));
144         best = matcher.getBestMatch("fr_FR", errorCode);
145         assertEquals("fromRange.getBestMatch(fr_FR)", "fr", locString(best));
146         best = matcher.getBestMatch("ja_JP", errorCode);
147         assertEquals("fromRange.getBestMatch(ja_JP)", "fr", locString(best));
148     }
149     {
150         Locale::RangeIterator<Locale *> iter(ARRAY_RANGE(locales));
151         LocaleMatcher matcher = LocaleMatcher::Builder().
152             setSupportedLocales(iter).build(errorCode);
153         const Locale *best = matcher.getBestMatch("en_GB", errorCode);
154         assertEquals("fromIter.getBestMatch(en_GB)", "en_GB", locString(best));
155         best = matcher.getBestMatch("en_US", errorCode);
156         assertEquals("fromIter.getBestMatch(en_US)", "en", locString(best));
157         best = matcher.getBestMatch("fr_FR", errorCode);
158         assertEquals("fromIter.getBestMatch(fr_FR)", "fr", locString(best));
159         best = matcher.getBestMatch("ja_JP", errorCode);
160         assertEquals("fromIter.getBestMatch(ja_JP)", "fr", locString(best));
161     }
162     {
163         Locale *pointers[] = { locales, locales + 1, locales + 2 };
164         // Lambda with explicit reference return type to prevent copy-constructing a temporary
165         // which would be destructed right away.
166         LocaleMatcher matcher = LocaleMatcher::Builder().
167             setSupportedLocalesViaConverter(
168                 ARRAY_RANGE(pointers), [](const Locale *p) -> const Locale & { return *p; }).
169             build(errorCode);
170         const Locale *best = matcher.getBestMatch("en_GB", errorCode);
171         assertEquals("viaConverter.getBestMatch(en_GB)", "en_GB", locString(best));
172         best = matcher.getBestMatch("en_US", errorCode);
173         assertEquals("viaConverter.getBestMatch(en_US)", "en", locString(best));
174         best = matcher.getBestMatch("fr_FR", errorCode);
175         assertEquals("viaConverter.getBestMatch(fr_FR)", "fr", locString(best));
176         best = matcher.getBestMatch("ja_JP", errorCode);
177         assertEquals("viaConverter.getBestMatch(ja_JP)", "fr", locString(best));
178     }
179     {
180         LocaleMatcher matcher = LocaleMatcher::Builder().
181             addSupportedLocale(locales[0]).
182             addSupportedLocale(locales[1]).
183             addSupportedLocale(locales[2]).
184             build(errorCode);
185         const Locale *best = matcher.getBestMatch("en_GB", errorCode);
186         assertEquals("added.getBestMatch(en_GB)", "en_GB", locString(best));
187         best = matcher.getBestMatch("en_US", errorCode);
188         assertEquals("added.getBestMatch(en_US)", "en", locString(best));
189         best = matcher.getBestMatch("fr_FR", errorCode);
190         assertEquals("added.getBestMatch(fr_FR)", "fr", locString(best));
191         best = matcher.getBestMatch("ja_JP", errorCode);
192         assertEquals("added.getBestMatch(ja_JP)", "fr", locString(best));
193     }
194     {
195         LocaleMatcher matcher = LocaleMatcher::Builder().
196             setSupportedLocalesFromListString(
197                 " el, fr;q=0.555555, en-GB ; q = 0.88  , el; q =0, en;q=0.88 , fr ").
198             build(errorCode);
199         const Locale *best = matcher.getBestMatchForListString("el, fr, fr;q=0, en-GB", errorCode);
200         assertEquals("fromList.getBestMatch(en_GB)", "en_GB", locString(best));
201         best = matcher.getBestMatch("en_US", errorCode);
202         assertEquals("fromList.getBestMatch(en_US)", "en", locString(best));
203         best = matcher.getBestMatch("fr_FR", errorCode);
204         assertEquals("fromList.getBestMatch(fr_FR)", "fr", locString(best));
205         best = matcher.getBestMatch("ja_JP", errorCode);
206         assertEquals("fromList.getBestMatch(ja_JP)", "fr", locString(best));
207     }
208     // more API coverage
209     {
210         LocalePriorityList list("fr, en-GB", errorCode);
211         LocalePriorityList::Iterator iter(list.iterator());
212         LocaleMatcher matcher = LocaleMatcher::Builder().
213             setSupportedLocales(iter).
214             addSupportedLocale(Locale::getEnglish()).
215             setDefaultLocale(&Locale::getGerman()).
216             build(errorCode);
217         const Locale *best = matcher.getBestMatch("en_GB", errorCode);
218         assertEquals("withDefault.getBestMatch(en_GB)", "en_GB", locString(best));
219         best = matcher.getBestMatch("en_US", errorCode);
220         assertEquals("withDefault.getBestMatch(en_US)", "en", locString(best));
221         best = matcher.getBestMatch("fr_FR", errorCode);
222         assertEquals("withDefault.getBestMatch(fr_FR)", "fr", locString(best));
223         best = matcher.getBestMatch("ja_JP", errorCode);
224         assertEquals("withDefault.getBestMatch(ja_JP)", "de", locString(best));
225 
226         Locale desired("en_GB");  // distinct object from Locale.UK
227         LocaleMatcher::Result result = matcher.getBestMatchResult(desired, errorCode);
228         assertTrue("withDefault: exactly desired en-GB object",
229                    &desired == result.getDesiredLocale());
230         assertEquals("withDefault: en-GB desired index", 0, result.getDesiredIndex());
231         assertEquals("withDefault: en-GB supported",
232                      "en_GB", locString(result.getSupportedLocale()));
233         assertEquals("withDefault: en-GB supported index", 1, result.getSupportedIndex());
234 
235         LocalePriorityList list2("ja-JP, en-US", errorCode);
236         LocalePriorityList::Iterator iter2(list2.iterator());
237         result = matcher.getBestMatchResult(iter2, errorCode);
238         assertEquals("withDefault: ja-JP, en-US desired index", 1, result.getDesiredIndex());
239         assertEquals("withDefault: ja-JP, en-US desired",
240                      "en_US", locString(result.getDesiredLocale()));
241 
242         desired = Locale("en", "US");  // distinct object from Locale.US
243         result = matcher.getBestMatchResult(desired, errorCode);
244         assertTrue("withDefault: exactly desired en-US object",
245                    &desired == result.getDesiredLocale());
246         assertEquals("withDefault: en-US desired index", 0, result.getDesiredIndex());
247         assertEquals("withDefault: en-US supported", "en", locString(result.getSupportedLocale()));
248         assertEquals("withDefault: en-US supported index", 2, result.getSupportedIndex());
249 
250         result = matcher.getBestMatchResult("ja_JP", errorCode);
251         assertEquals("withDefault: ja-JP desired", "(null)", locString(result.getDesiredLocale()));
252         assertEquals("withDefault: ja-JP desired index", -1, result.getDesiredIndex());
253         assertEquals("withDefault: ja-JP supported", "de", locString(result.getSupportedLocale()));
254         assertEquals("withDefault: ja-JP supported index", -1, result.getSupportedIndex());
255     }
256 }
257 
testSupportedDefault()258 void LocaleMatcherTest::testSupportedDefault() {
259     // The default locale is one of the supported locales.
260     IcuTestErrorCode errorCode(*this, "testSupportedDefault");
261     Locale locales[] = { "fr", "en_GB", "en" };
262     LocaleMatcher matcher = LocaleMatcher::Builder().
263         setSupportedLocales(ARRAY_RANGE(locales)).
264         setDefaultLocale(&locales[1]).
265         build(errorCode);
266     const Locale *best = matcher.getBestMatch("en_GB", errorCode);
267     assertEquals("getBestMatch(en_GB)", "en_GB", locString(best));
268     best = matcher.getBestMatch("en_US", errorCode);
269     assertEquals("getBestMatch(en_US)", "en", locString(best));
270     best = matcher.getBestMatch("fr_FR", errorCode);
271     assertEquals("getBestMatch(fr_FR)", "fr", locString(best));
272     best = matcher.getBestMatch("ja_JP", errorCode);
273     assertEquals("getBestMatch(ja_JP)", "en_GB", locString(best));
274     LocaleMatcher::Result result = matcher.getBestMatchResult("ja_JP", errorCode);
275     assertEquals("getBestMatchResult(ja_JP).supp",
276                  "en_GB", locString(result.getSupportedLocale()));
277     assertEquals("getBestMatchResult(ja_JP).suppIndex",
278                  -1, result.getSupportedIndex());
279 }
280 
testUnsupportedDefault()281 void LocaleMatcherTest::testUnsupportedDefault() {
282     // The default locale does not match any of the supported locales.
283     IcuTestErrorCode errorCode(*this, "testUnsupportedDefault");
284     Locale locales[] = { "fr", "en_GB", "en" };
285     Locale def("de");
286     LocaleMatcher matcher = LocaleMatcher::Builder().
287         setSupportedLocales(ARRAY_RANGE(locales)).
288         setDefaultLocale(&def).
289         build(errorCode);
290     const Locale *best = matcher.getBestMatch("en_GB", errorCode);
291     assertEquals("getBestMatch(en_GB)", "en_GB", locString(best));
292     best = matcher.getBestMatch("en_US", errorCode);
293     assertEquals("getBestMatch(en_US)", "en", locString(best));
294     best = matcher.getBestMatch("fr_FR", errorCode);
295     assertEquals("getBestMatch(fr_FR)", "fr", locString(best));
296     best = matcher.getBestMatch("ja_JP", errorCode);
297     assertEquals("getBestMatch(ja_JP)", "de", locString(best));
298     LocaleMatcher::Result result = matcher.getBestMatchResult("ja_JP", errorCode);
299     assertEquals("getBestMatchResult(ja_JP).supp",
300                  "de", locString(result.getSupportedLocale()));
301     assertEquals("getBestMatchResult(ja_JP).suppIndex",
302                  -1, result.getSupportedIndex());
303 }
304 
testDemotion()305 void LocaleMatcherTest::testDemotion() {
306     IcuTestErrorCode errorCode(*this, "testDemotion");
307     Locale supported[] = { "fr", "de-CH", "it" };
308     Locale desired[] = { "fr-CH", "de-CH", "it" };
309     {
310         LocaleMatcher noDemotion = LocaleMatcher::Builder().
311             setSupportedLocales(ARRAY_RANGE(supported)).
312             setDemotionPerDesiredLocale(ULOCMATCH_DEMOTION_NONE).build(errorCode);
313         Locale::RangeIterator<Locale *> desiredIter(ARRAY_RANGE(desired));
314         assertEquals("no demotion",
315                      "de_CH", locString(noDemotion.getBestMatch(desiredIter, errorCode)));
316     }
317 
318     {
319         LocaleMatcher regionDemotion = LocaleMatcher::Builder().
320             setSupportedLocales(ARRAY_RANGE(supported)).
321             setDemotionPerDesiredLocale(ULOCMATCH_DEMOTION_REGION).build(errorCode);
322         Locale::RangeIterator<Locale *> desiredIter(ARRAY_RANGE(desired));
323         assertEquals("region demotion",
324                      "fr", locString(regionDemotion.getBestMatch(desiredIter, errorCode)));
325     }
326 }
327 
testDirection()328 void LocaleMatcherTest::testDirection() {
329     IcuTestErrorCode errorCode(*this, "testDirection");
330     Locale supported[] = { "ar", "nn" };
331     Locale desired[] = { "arz-EG", "nb-DK" };
332     LocaleMatcher::Builder builder;
333     builder.setSupportedLocales(ARRAY_RANGE(supported));
334     {
335         // arz is a close one-way match to ar, and the region matches.
336         // (Egyptian Arabic vs. Arabic)
337         // Also explicitly exercise the move copy constructor.
338         LocaleMatcher built = builder.build(errorCode);
339         LocaleMatcher withOneWay(std::move(built));
340         Locale::RangeIterator<Locale *> desiredIter(ARRAY_RANGE(desired));
341         assertEquals("with one-way", "ar",
342                      locString(withOneWay.getBestMatch(desiredIter, errorCode)));
343     }
344     {
345         // nb is a less close two-way match to nn, and the regions differ.
346         // (Norwegian Bokmal vs. Nynorsk)
347         // Also explicitly exercise the move assignment operator.
348         LocaleMatcher onlyTwoWay = builder.build(errorCode);
349         LocaleMatcher built =
350             builder.setDirection(ULOCMATCH_DIRECTION_ONLY_TWO_WAY).build(errorCode);
351         onlyTwoWay = std::move(built);
352         Locale::RangeIterator<Locale *> desiredIter(ARRAY_RANGE(desired));
353         assertEquals("only two-way", "nn",
354                      locString(onlyTwoWay.getBestMatch(desiredIter, errorCode)));
355     }
356 }
357 
testMatch()358 void LocaleMatcherTest::testMatch() {
359     IcuTestErrorCode errorCode(*this, "testMatch");
360     LocaleMatcher matcher = LocaleMatcher::Builder().build(errorCode);
361 
362     // Java test function testMatch_exact()
363     Locale en_CA("en_CA");
364     assertEquals("exact match", 1.0, matcher.internalMatch(en_CA, en_CA, errorCode));
365 
366     // testMatch_none
367     Locale ar_MK("ar_MK");
368     double match = matcher.internalMatch(ar_MK, en_CA, errorCode);
369     assertTrue("mismatch: 0<=match<0.2", 0 <= match && match < 0.2);
370 
371     // testMatch_matchOnMaximized
372     Locale und_TW("und_TW");
373     Locale zh("zh");
374     Locale zh_Hant("zh_Hant");
375     double matchZh = matcher.internalMatch(und_TW, zh, errorCode);
376     double matchZhHant = matcher.internalMatch(und_TW, zh_Hant, errorCode);
377     assertTrue("und_TW should be closer to zh_Hant than to zh",
378                matchZh < matchZhHant);
379     Locale en_Hant_TW("en_Hant_TW");
380     double matchEnHantTw = matcher.internalMatch(en_Hant_TW, zh_Hant, errorCode);
381     assertTrue("zh_Hant should be closer to und_TW than to en_Hant_TW",
382                matchEnHantTw < matchZhHant);
383     assertTrue("zh should be closer to und_TW than to en_Hant_TW",
384                matchEnHantTw < matchZh);
385 }
386 
testResolvedLocale()387 void LocaleMatcherTest::testResolvedLocale() {
388     IcuTestErrorCode errorCode(*this, "testResolvedLocale");
389     LocaleMatcher matcher = LocaleMatcher::Builder().
390         addSupportedLocale("ar-EG").
391         build(errorCode);
392     Locale desired("ar-SA-u-nu-latn");
393     LocaleMatcher::Result result = matcher.getBestMatchResult(desired, errorCode);
394     assertEquals("best", "ar_EG", locString(result.getSupportedLocale()));
395     Locale resolved = result.makeResolvedLocale(errorCode);
396     assertEquals("ar-EG + ar-SA-u-nu-latn = ar-SA-u-nu-latn",
397                  "ar-SA-u-nu-latn",
398                  resolved.toLanguageTag<std::string>(errorCode).data());
399 }
400 
401 namespace {
402 
toInvariant(const UnicodeString & s,CharString & inv,ErrorCode & errorCode)403 bool toInvariant(const UnicodeString &s, CharString &inv, ErrorCode &errorCode) {
404     if (errorCode.isSuccess()) {
405         inv.clear().appendInvariantChars(s, errorCode);
406         return errorCode.isSuccess();
407     }
408     return false;
409 }
410 
getSuffixAfterPrefix(const UnicodeString & s,int32_t limit,const UnicodeString & prefix,UnicodeString & suffix)411 bool getSuffixAfterPrefix(const UnicodeString &s, int32_t limit,
412                           const UnicodeString &prefix, UnicodeString &suffix) {
413     if (prefix.length() <= limit && s.startsWith(prefix)) {
414         suffix.setTo(s, prefix.length(), limit - prefix.length());
415         return true;
416     } else {
417         return false;
418     }
419 }
420 
getInvariantSuffixAfterPrefix(const UnicodeString & s,int32_t limit,const UnicodeString & prefix,CharString & suffix,ErrorCode & errorCode)421 bool getInvariantSuffixAfterPrefix(const UnicodeString &s, int32_t limit,
422                                    const UnicodeString &prefix, CharString &suffix,
423                                    ErrorCode &errorCode) {
424     UnicodeString u_suffix;
425     return getSuffixAfterPrefix(s, limit, prefix, u_suffix) &&
426         toInvariant(u_suffix, suffix, errorCode);
427 }
428 
readTestCase(const UnicodeString & line,TestCase & test,IcuTestErrorCode & errorCode)429 bool readTestCase(const UnicodeString &line, TestCase &test, IcuTestErrorCode &errorCode) {
430     if (errorCode.isFailure()) { return false; }
431     ++test.lineNr;
432     // Start of comment, or end of line, minus trailing spaces.
433     int32_t limit = line.indexOf(u'#');
434     if (limit < 0) {
435         limit = line.length();
436         // Remove trailing CR LF.
437         char16_t c;
438         while (limit > 0 && ((c = line.charAt(limit - 1)) == u'\n' || c == u'\r')) {
439             --limit;
440         }
441     }
442     // Remove spaces before comment or at the end of the line.
443     char16_t c;
444     while (limit > 0 && ((c = line.charAt(limit - 1)) == u' ' || c == u'\t')) {
445         --limit;
446     }
447     if (limit == 0) {  // empty line
448         return false;
449     }
450     if (line.startsWith(u"** test: ")) {
451         test.reset();
452     } else if (getInvariantSuffixAfterPrefix(line, limit, u"@supported=",
453                                              test.supported, errorCode)) {
454     } else if (getInvariantSuffixAfterPrefix(line, limit, u"@default=",
455                                              test.def, errorCode)) {
456     } else if (getSuffixAfterPrefix(line, limit, u"@favor=", test.favor)) {
457     } else if (getSuffixAfterPrefix(line, limit, u"@threshold=", test.threshold)) {
458     } else {
459         int32_t matchSep = line.indexOf(u">>");
460         // >> before an inline comment, and followed by more than white space.
461         if (0 <= matchSep && (matchSep + 2) < limit) {
462             toInvariant(line.tempSubStringBetween(0, matchSep).trim(), test.desired, errorCode);
463             test.expDesired.clear();
464             test.expCombined.clear();
465             int32_t start = matchSep + 2;
466             int32_t expLimit = line.indexOf(u'|', start);
467             if (expLimit < 0) {
468                 toInvariant(line.tempSubStringBetween(start, limit).trim(),
469                             test.expMatch, errorCode);
470             } else {
471                 toInvariant(line.tempSubStringBetween(start, expLimit).trim(),
472                             test.expMatch, errorCode);
473                 start = expLimit + 1;
474                 expLimit = line.indexOf(u'|', start);
475                 if (expLimit < 0) {
476                     toInvariant(line.tempSubStringBetween(start, limit).trim(),
477                                 test.expDesired, errorCode);
478                 } else {
479                     toInvariant(line.tempSubStringBetween(start, expLimit).trim(),
480                                 test.expDesired, errorCode);
481                     toInvariant(line.tempSubStringBetween(expLimit + 1, limit).trim(),
482                                 test.expCombined, errorCode);
483                 }
484             }
485             return errorCode.isSuccess();
486         } else {
487             errorCode.set(U_INVALID_FORMAT_ERROR);
488         }
489     }
490     return false;
491 }
492 
getLocaleOrNull(const CharString & s,Locale & locale)493 Locale *getLocaleOrNull(const CharString &s, Locale &locale) {
494     if (s == "null") {
495         return nullptr;
496     } else {
497         return &(locale = Locale(s.data()));
498     }
499 }
500 
501 }  // namespace
502 
dataDriven(const TestCase & test,IcuTestErrorCode & errorCode)503 UBool LocaleMatcherTest::dataDriven(const TestCase &test, IcuTestErrorCode &errorCode) {
504     LocaleMatcher::Builder builder;
505     builder.setSupportedLocalesFromListString(test.supported.toStringPiece());
506     if (!test.def.isEmpty()) {
507         Locale defaultLocale(test.def.data());
508         builder.setDefaultLocale(&defaultLocale);
509     }
510     if (!test.favor.isEmpty()) {
511         ULocMatchFavorSubtag favor;
512         if (test.favor == u"normal") {
513             favor = ULOCMATCH_FAVOR_LANGUAGE;
514         } else if (test.favor == u"script") {
515             favor = ULOCMATCH_FAVOR_SCRIPT;
516         } else {
517             errln(UnicodeString(u"unsupported FavorSubtag value ") + test.favor);
518             return FALSE;
519         }
520         builder.setFavorSubtag(favor);
521     }
522     if (!test.threshold.isEmpty()) {
523         infoln("skipping test case on line %d with non-default threshold: not exposed via API",
524                (int)test.lineNr);
525         return TRUE;
526         // int32_t threshold = Integer.valueOf(test.threshold);
527         // builder.internalSetThresholdDistance(threshold);
528     }
529     LocaleMatcher matcher = builder.build(errorCode);
530     if (errorCode.errIfFailureAndReset("LocaleMatcher::Builder::build()")) {
531         return FALSE;
532     }
533 
534     Locale expMatchLocale("");
535     Locale *expMatch = getLocaleOrNull(test.expMatch, expMatchLocale);
536     if (test.expDesired.isEmpty() && test.expCombined.isEmpty()) {
537         StringPiece desiredSP = test.desired.toStringPiece();
538         const Locale *bestSupported = matcher.getBestMatchForListString(desiredSP, errorCode);
539         if (!assertEquals("bestSupported from string",
540                           locString(expMatch), locString(bestSupported))) {
541             return FALSE;
542         }
543         LocalePriorityList desired(test.desired.toStringPiece(), errorCode);
544         LocalePriorityList::Iterator desiredIter = desired.iterator();
545         if (desired.getLength() == 1) {
546             const Locale &desiredLocale = desiredIter.next();
547             bestSupported = matcher.getBestMatch(desiredLocale, errorCode);
548             UBool ok = assertEquals("bestSupported from Locale",
549                                     locString(expMatch), locString(bestSupported));
550 
551             LocaleMatcher::Result result = matcher.getBestMatchResult(desiredLocale, errorCode);
552             return ok & assertEquals("result.getSupportedLocale from Locale",
553                                      locString(expMatch), locString(result.getSupportedLocale()));
554         } else {
555             bestSupported = matcher.getBestMatch(desiredIter, errorCode);
556             return assertEquals("bestSupported from Locale iterator",
557                                 locString(expMatch), locString(bestSupported));
558         }
559     } else {
560         LocalePriorityList desired(test.desired.toStringPiece(), errorCode);
561         LocalePriorityList::Iterator desiredIter = desired.iterator();
562         LocaleMatcher::Result result = matcher.getBestMatchResult(desiredIter, errorCode);
563         UBool ok = assertEquals("result.getSupportedLocale from Locales",
564                                 locString(expMatch), locString(result.getSupportedLocale()));
565         if (!test.expDesired.isEmpty()) {
566             Locale expDesiredLocale("");
567             Locale *expDesired = getLocaleOrNull(test.expDesired, expDesiredLocale);
568             ok &= assertEquals("result.getDesiredLocale from Locales",
569                                locString(expDesired), locString(result.getDesiredLocale()));
570         }
571         if (!test.expCombined.isEmpty()) {
572             if (test.expMatch.contains("-u-")) {
573                 logKnownIssue("20727",
574                               UnicodeString(u"ignoring makeResolvedLocale() line ") + test.lineNr);
575                 return ok;
576             }
577             Locale expCombinedLocale("");
578             Locale *expCombined = getLocaleOrNull(test.expCombined, expCombinedLocale);
579             Locale combined = result.makeResolvedLocale(errorCode);
580             ok &= assertEquals("combined Locale from Locales",
581                                locString(expCombined), locString(&combined));
582         }
583         return ok;
584     }
585 }
586 
testDataDriven()587 void LocaleMatcherTest::testDataDriven() {
588     IcuTestErrorCode errorCode(*this, "testDataDriven");
589     CharString path(getSourceTestData(errorCode), errorCode);
590     path.appendPathPart("localeMatcherTest.txt", errorCode);
591     const char *codePage = "UTF-8";
592     LocalUCHARBUFPointer f(ucbuf_open(path.data(), &codePage, TRUE, FALSE, errorCode));
593     if(errorCode.errIfFailureAndReset("ucbuf_open(localeMatcherTest.txt)")) {
594         return;
595     }
596     int32_t lineLength;
597     const UChar *p;
598     UnicodeString line;
599     TestCase test;
600     int32_t numPassed = 0;
601     while ((p = ucbuf_readline(f.getAlias(), &lineLength, errorCode)) != nullptr &&
602             errorCode.isSuccess()) {
603         line.setTo(FALSE, p, lineLength);
604         if (!readTestCase(line, test, errorCode)) {
605             if (errorCode.errIfFailureAndReset(
606                     "test data syntax error on line %d", (int)test.lineNr)) {
607                 infoln(line);
608             }
609             continue;
610         }
611         UBool ok = dataDriven(test, errorCode);
612         if (errorCode.errIfFailureAndReset("test error on line %d", (int)test.lineNr)) {
613             infoln(line);
614         } else if (!ok) {
615             infoln("test failure on line %d", (int)test.lineNr);
616             infoln(line);
617         } else {
618             ++numPassed;
619         }
620     }
621     infoln("number of passing test cases: %d", (int)numPassed);
622 }
623