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