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 22namespace { 23 24const char *locString(const Locale *loc) { 25 return loc != nullptr ? loc->getName() : "(null)"; 26} 27 28struct 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 40 void reset() { 41 supported.clear(); 42 def.clear(); 43 favor.remove(); 44 threshold.remove(); 45 } 46}; 47 48} // namespace 49 50class LocaleMatcherTest : public IntlTest { 51public: 52 LocaleMatcherTest() {} 53 54 void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par=NULL) override; 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 69private: 70 UBool dataDriven(const TestCase &test, IcuTestErrorCode &errorCode); 71}; 72 73extern IntlTest *createLocaleMatcherTest() { 74 return new LocaleMatcherTest(); 75} 76 77void 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 97void 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 111void 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 124void 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 262void 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 285void 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 309void 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 332void 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 355void 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 385void 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 415void 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 not match und_TW or en_Hant_TW", 441 matchZh == 0.0 && matchEnHantTw == 0.0); // with changes in CLDR-1435 442} 443 444void 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 458namespace { 459 460bool 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 468bool 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 478bool 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 486bool 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 550Locale *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 560UBool 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 644void 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