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