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