]> git.saurik.com Git - apple/icu.git/blobdiff - icuSources/test/intltest/localematchertest.cpp
ICU-66108.tar.gz
[apple/icu.git] / icuSources / test / intltest / localematchertest.cpp
diff --git a/icuSources/test/intltest/localematchertest.cpp b/icuSources/test/intltest/localematchertest.cpp
new file mode 100644 (file)
index 0000000..f8cb7a3
--- /dev/null
@@ -0,0 +1,589 @@
+// © 2019 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+
+// localematchertest.cpp
+// created: 2019jul04 Markus W. Scherer
+
+#include <string>
+#include <vector>
+
+#include "unicode/utypes.h"
+#include "unicode/localematcher.h"
+#include "unicode/locid.h"
+#include "charstr.h"
+#include "cmemory.h"
+#include "intltest.h"
+#include "localeprioritylist.h"
+#include "ucbuf.h"
+
+#define ARRAY_RANGE(array) (array), ((array) + UPRV_LENGTHOF(array))
+
+namespace {
+
+const char *locString(const Locale *loc) {
+    return loc != nullptr ? loc->getName() : "(null)";
+}
+
+struct TestCase {
+    int32_t lineNr = 0;
+
+    CharString supported;
+    CharString def;
+    UnicodeString favor;
+    UnicodeString threshold;
+    CharString desired;
+    CharString expMatch;
+    CharString expDesired;
+    CharString expCombined;
+
+    void reset() {
+        supported.clear();
+        def.clear();
+        favor.remove();
+        threshold.remove();
+    }
+};
+
+}  // namespace
+
+class LocaleMatcherTest : public IntlTest {
+public:
+    LocaleMatcherTest() {}
+
+    void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par=NULL);
+
+    void testEmpty();
+    void testCopyErrorTo();
+    void testBasics();
+    void testSupportedDefault();
+    void testUnsupportedDefault();
+    void testDemotion();
+    void testMatch();
+    void testResolvedLocale();
+    void testDataDriven();
+
+private:
+    UBool dataDriven(const TestCase &test, IcuTestErrorCode &errorCode);
+};
+
+extern IntlTest *createLocaleMatcherTest() {
+    return new LocaleMatcherTest();
+}
+
+void LocaleMatcherTest::runIndexedTest(int32_t index, UBool exec, const char *&name, char * /*par*/) {
+    if(exec) {
+        logln("TestSuite LocaleMatcherTest: ");
+    }
+    TESTCASE_AUTO_BEGIN;
+    TESTCASE_AUTO(testEmpty);
+    TESTCASE_AUTO(testCopyErrorTo);
+    TESTCASE_AUTO(testBasics);
+    TESTCASE_AUTO(testSupportedDefault);
+    TESTCASE_AUTO(testUnsupportedDefault);
+    TESTCASE_AUTO(testDemotion);
+    TESTCASE_AUTO(testMatch);
+    TESTCASE_AUTO(testResolvedLocale);
+    TESTCASE_AUTO(testDataDriven);
+    TESTCASE_AUTO_END;
+}
+
+void LocaleMatcherTest::testEmpty() {
+    IcuTestErrorCode errorCode(*this, "testEmpty");
+    LocaleMatcher matcher = LocaleMatcher::Builder().build(errorCode);
+    const Locale *best = matcher.getBestMatch(Locale::getFrench(), errorCode);
+    assertEquals("getBestMatch(fr)", "(null)", locString(best));
+    LocaleMatcher::Result result = matcher.getBestMatchResult("fr", errorCode);
+    assertEquals("getBestMatchResult(fr).des", "(null)", locString(result.getDesiredLocale()));
+    assertEquals("getBestMatchResult(fr).desIndex", -1, result.getDesiredIndex());
+    assertEquals("getBestMatchResult(fr).supp",
+                 "(null)", locString(result.getSupportedLocale()));
+    assertEquals("getBestMatchResult(fr).suppIndex",
+                 -1, result.getSupportedIndex());
+}
+
+void LocaleMatcherTest::testCopyErrorTo() {
+    IcuTestErrorCode errorCode(*this, "testCopyErrorTo");
+    // The builder does not set any errors except out-of-memory.
+    // Test what we can.
+    LocaleMatcher::Builder builder;
+    UErrorCode success = U_ZERO_ERROR;
+    assertFalse("no error", builder.copyErrorTo(success));
+    assertTrue("still success", U_SUCCESS(success));
+    UErrorCode failure = U_INVALID_FORMAT_ERROR;
+    assertTrue("failure passed in", builder.copyErrorTo(failure));
+    assertEquals("same failure", U_INVALID_FORMAT_ERROR, failure);
+}
+
+void LocaleMatcherTest::testBasics() {
+    IcuTestErrorCode errorCode(*this, "testBasics");
+    Locale locales[] = { "fr", "en_GB", "en" };
+    {
+        LocaleMatcher matcher = LocaleMatcher::Builder().
+            setSupportedLocales(ARRAY_RANGE(locales)).build(errorCode);
+        const Locale *best = matcher.getBestMatch("en_GB", errorCode);
+        assertEquals("fromRange.getBestMatch(en_GB)", "en_GB", locString(best));
+        best = matcher.getBestMatch("en_US", errorCode);
+        assertEquals("fromRange.getBestMatch(en_US)", "en", locString(best));
+        best = matcher.getBestMatch("fr_FR", errorCode);
+        assertEquals("fromRange.getBestMatch(fr_FR)", "fr", locString(best));
+        best = matcher.getBestMatch("ja_JP", errorCode);
+        assertEquals("fromRange.getBestMatch(ja_JP)", "fr", locString(best));
+    }
+    // Code coverage: Variations of setting supported locales.
+    {
+        std::vector<Locale> locales{ "fr", "en_GB", "en" };
+        LocaleMatcher matcher = LocaleMatcher::Builder().
+            setSupportedLocales(locales.begin(), locales.end()).build(errorCode);
+        const Locale *best = matcher.getBestMatch("en_GB", errorCode);
+        assertEquals("fromRange.getBestMatch(en_GB)", "en_GB", locString(best));
+        best = matcher.getBestMatch("en_US", errorCode);
+        assertEquals("fromRange.getBestMatch(en_US)", "en", locString(best));
+        best = matcher.getBestMatch("fr_FR", errorCode);
+        assertEquals("fromRange.getBestMatch(fr_FR)", "fr", locString(best));
+        best = matcher.getBestMatch("ja_JP", errorCode);
+        assertEquals("fromRange.getBestMatch(ja_JP)", "fr", locString(best));
+    }
+    {
+        Locale::RangeIterator<Locale *> iter(ARRAY_RANGE(locales));
+        LocaleMatcher matcher = LocaleMatcher::Builder().
+            setSupportedLocales(iter).build(errorCode);
+        const Locale *best = matcher.getBestMatch("en_GB", errorCode);
+        assertEquals("fromIter.getBestMatch(en_GB)", "en_GB", locString(best));
+        best = matcher.getBestMatch("en_US", errorCode);
+        assertEquals("fromIter.getBestMatch(en_US)", "en", locString(best));
+        best = matcher.getBestMatch("fr_FR", errorCode);
+        assertEquals("fromIter.getBestMatch(fr_FR)", "fr", locString(best));
+        best = matcher.getBestMatch("ja_JP", errorCode);
+        assertEquals("fromIter.getBestMatch(ja_JP)", "fr", locString(best));
+    }
+    {
+        Locale *pointers[] = { locales, locales + 1, locales + 2 };
+        // Lambda with explicit reference return type to prevent copy-constructing a temporary
+        // which would be destructed right away.
+        LocaleMatcher matcher = LocaleMatcher::Builder().
+            setSupportedLocalesViaConverter(
+                ARRAY_RANGE(pointers), [](const Locale *p) -> const Locale & { return *p; }).
+            build(errorCode);
+        const Locale *best = matcher.getBestMatch("en_GB", errorCode);
+        assertEquals("viaConverter.getBestMatch(en_GB)", "en_GB", locString(best));
+        best = matcher.getBestMatch("en_US", errorCode);
+        assertEquals("viaConverter.getBestMatch(en_US)", "en", locString(best));
+        best = matcher.getBestMatch("fr_FR", errorCode);
+        assertEquals("viaConverter.getBestMatch(fr_FR)", "fr", locString(best));
+        best = matcher.getBestMatch("ja_JP", errorCode);
+        assertEquals("viaConverter.getBestMatch(ja_JP)", "fr", locString(best));
+    }
+    {
+        LocaleMatcher matcher = LocaleMatcher::Builder().
+            addSupportedLocale(locales[0]).
+            addSupportedLocale(locales[1]).
+            addSupportedLocale(locales[2]).
+            build(errorCode);
+        const Locale *best = matcher.getBestMatch("en_GB", errorCode);
+        assertEquals("added.getBestMatch(en_GB)", "en_GB", locString(best));
+        best = matcher.getBestMatch("en_US", errorCode);
+        assertEquals("added.getBestMatch(en_US)", "en", locString(best));
+        best = matcher.getBestMatch("fr_FR", errorCode);
+        assertEquals("added.getBestMatch(fr_FR)", "fr", locString(best));
+        best = matcher.getBestMatch("ja_JP", errorCode);
+        assertEquals("added.getBestMatch(ja_JP)", "fr", locString(best));
+    }
+    {
+        LocaleMatcher matcher = LocaleMatcher::Builder().
+            setSupportedLocalesFromListString(
+                " el, fr;q=0.555555, en-GB ; q = 0.88  , el; q =0, en;q=0.88 , fr ").
+            build(errorCode);
+        const Locale *best = matcher.getBestMatchForListString("el, fr, fr;q=0, en-GB", errorCode);
+        assertEquals("fromList.getBestMatch(en_GB)", "en_GB", locString(best));
+        best = matcher.getBestMatch("en_US", errorCode);
+        assertEquals("fromList.getBestMatch(en_US)", "en", locString(best));
+        best = matcher.getBestMatch("fr_FR", errorCode);
+        assertEquals("fromList.getBestMatch(fr_FR)", "fr", locString(best));
+        best = matcher.getBestMatch("ja_JP", errorCode);
+        assertEquals("fromList.getBestMatch(ja_JP)", "fr", locString(best));
+    }
+    // more API coverage
+    {
+        LocalePriorityList list("fr, en-GB", errorCode);
+        LocalePriorityList::Iterator iter(list.iterator());
+        LocaleMatcher matcher = LocaleMatcher::Builder().
+            setSupportedLocales(iter).
+            addSupportedLocale(Locale::getEnglish()).
+            setDefaultLocale(&Locale::getGerman()).
+            build(errorCode);
+        const Locale *best = matcher.getBestMatch("en_GB", errorCode);
+        assertEquals("withDefault.getBestMatch(en_GB)", "en_GB", locString(best));
+        best = matcher.getBestMatch("en_US", errorCode);
+        assertEquals("withDefault.getBestMatch(en_US)", "en", locString(best));
+        best = matcher.getBestMatch("fr_FR", errorCode);
+        assertEquals("withDefault.getBestMatch(fr_FR)", "fr", locString(best));
+        best = matcher.getBestMatch("ja_JP", errorCode);
+        assertEquals("withDefault.getBestMatch(ja_JP)", "de", locString(best));
+
+        Locale desired("en_GB");  // distinct object from Locale.UK
+        LocaleMatcher::Result result = matcher.getBestMatchResult(desired, errorCode);
+        assertTrue("withDefault: exactly desired en-GB object",
+                   &desired == result.getDesiredLocale());
+        assertEquals("withDefault: en-GB desired index", 0, result.getDesiredIndex());
+        assertEquals("withDefault: en-GB supported",
+                     "en_GB", locString(result.getSupportedLocale()));
+        assertEquals("withDefault: en-GB supported index", 1, result.getSupportedIndex());
+
+        LocalePriorityList list2("ja-JP, en-US", errorCode);
+        LocalePriorityList::Iterator iter2(list2.iterator());
+        result = matcher.getBestMatchResult(iter2, errorCode);
+        assertEquals("withDefault: ja-JP, en-US desired index", 1, result.getDesiredIndex());
+        assertEquals("withDefault: ja-JP, en-US desired",
+                     "en_US", locString(result.getDesiredLocale()));
+
+        desired = Locale("en", "US");  // distinct object from Locale.US
+        result = matcher.getBestMatchResult(desired, errorCode);
+        assertTrue("withDefault: exactly desired en-US object",
+                   &desired == result.getDesiredLocale());
+        assertEquals("withDefault: en-US desired index", 0, result.getDesiredIndex());
+        assertEquals("withDefault: en-US supported", "en", locString(result.getSupportedLocale()));
+        assertEquals("withDefault: en-US supported index", 2, result.getSupportedIndex());
+
+        result = matcher.getBestMatchResult("ja_JP", errorCode);
+        assertEquals("withDefault: ja-JP desired", "(null)", locString(result.getDesiredLocale()));
+        assertEquals("withDefault: ja-JP desired index", -1, result.getDesiredIndex());
+        assertEquals("withDefault: ja-JP supported", "de", locString(result.getSupportedLocale()));
+        assertEquals("withDefault: ja-JP supported index", -1, result.getSupportedIndex());
+    }
+}
+
+void LocaleMatcherTest::testSupportedDefault() {
+    // The default locale is one of the supported locales.
+    IcuTestErrorCode errorCode(*this, "testSupportedDefault");
+    Locale locales[] = { "fr", "en_GB", "en" };
+    LocaleMatcher matcher = LocaleMatcher::Builder().
+        setSupportedLocales(ARRAY_RANGE(locales)).
+        setDefaultLocale(&locales[1]).
+        build(errorCode);
+    const Locale *best = matcher.getBestMatch("en_GB", errorCode);
+    assertEquals("getBestMatch(en_GB)", "en_GB", locString(best));
+    best = matcher.getBestMatch("en_US", errorCode);
+    assertEquals("getBestMatch(en_US)", "en", locString(best));
+    best = matcher.getBestMatch("fr_FR", errorCode);
+    assertEquals("getBestMatch(fr_FR)", "fr", locString(best));
+    best = matcher.getBestMatch("ja_JP", errorCode);
+    assertEquals("getBestMatch(ja_JP)", "en_GB", locString(best));
+    LocaleMatcher::Result result = matcher.getBestMatchResult("ja_JP", errorCode);
+    assertEquals("getBestMatchResult(ja_JP).supp",
+                 "en_GB", locString(result.getSupportedLocale()));
+    assertEquals("getBestMatchResult(ja_JP).suppIndex",
+                 1, result.getSupportedIndex());
+}
+
+void LocaleMatcherTest::testUnsupportedDefault() {
+    // The default locale does not match any of the supported locales.
+    IcuTestErrorCode errorCode(*this, "testUnsupportedDefault");
+    Locale locales[] = { "fr", "en_GB", "en" };
+    Locale def("de");
+    LocaleMatcher matcher = LocaleMatcher::Builder().
+        setSupportedLocales(ARRAY_RANGE(locales)).
+        setDefaultLocale(&def).
+        build(errorCode);
+    const Locale *best = matcher.getBestMatch("en_GB", errorCode);
+    assertEquals("getBestMatch(en_GB)", "en_GB", locString(best));
+    best = matcher.getBestMatch("en_US", errorCode);
+    assertEquals("getBestMatch(en_US)", "en", locString(best));
+    best = matcher.getBestMatch("fr_FR", errorCode);
+    assertEquals("getBestMatch(fr_FR)", "fr", locString(best));
+    best = matcher.getBestMatch("ja_JP", errorCode);
+    assertEquals("getBestMatch(ja_JP)", "de", locString(best));
+    LocaleMatcher::Result result = matcher.getBestMatchResult("ja_JP", errorCode);
+    assertEquals("getBestMatchResult(ja_JP).supp",
+                 "de", locString(result.getSupportedLocale()));
+    assertEquals("getBestMatchResult(ja_JP).suppIndex",
+                 -1, result.getSupportedIndex());
+}
+
+void LocaleMatcherTest::testDemotion() {
+    IcuTestErrorCode errorCode(*this, "testDemotion");
+    Locale supported[] = { "fr", "de-CH", "it" };
+    Locale desired[] = { "fr-CH", "de-CH", "it" };
+    {
+        LocaleMatcher noDemotion = LocaleMatcher::Builder().
+            setSupportedLocales(ARRAY_RANGE(supported)).
+            setDemotionPerDesiredLocale(ULOCMATCH_DEMOTION_NONE).build(errorCode);
+        Locale::RangeIterator<Locale *> desiredIter(ARRAY_RANGE(desired));
+        assertEquals("no demotion",
+                     "de_CH", locString(noDemotion.getBestMatch(desiredIter, errorCode)));
+    }
+
+    {
+        LocaleMatcher regionDemotion = LocaleMatcher::Builder().
+            setSupportedLocales(ARRAY_RANGE(supported)).
+            setDemotionPerDesiredLocale(ULOCMATCH_DEMOTION_REGION).build(errorCode);
+        Locale::RangeIterator<Locale *> desiredIter(ARRAY_RANGE(desired));
+        assertEquals("region demotion",
+                     "fr", locString(regionDemotion.getBestMatch(desiredIter, errorCode)));
+    }
+}
+
+void LocaleMatcherTest::testMatch() {
+    IcuTestErrorCode errorCode(*this, "testMatch");
+    LocaleMatcher matcher = LocaleMatcher::Builder().build(errorCode);
+
+    // Java test function testMatch_exact()
+    Locale en_CA("en_CA");
+    assertEquals("exact match", 1.0, matcher.internalMatch(en_CA, en_CA, errorCode));
+
+    // testMatch_none
+    Locale ar_MK("ar_MK");
+    double match = matcher.internalMatch(ar_MK, en_CA, errorCode);
+    assertTrue("mismatch: 0<=match<0.2", 0 <= match && match < 0.2);
+
+    // testMatch_matchOnMaximized
+    Locale und_TW("und_TW");
+    Locale zh("zh");
+    Locale zh_Hant("zh_Hant");
+    double matchZh = matcher.internalMatch(und_TW, zh, errorCode);
+    double matchZhHant = matcher.internalMatch(und_TW, zh_Hant, errorCode);
+    assertTrue("und_TW should be closer to zh_Hant than to zh",
+               matchZh < matchZhHant);
+    Locale en_Hant_TW("en_Hant_TW");
+    double matchEnHantTw = matcher.internalMatch(en_Hant_TW, zh_Hant, errorCode);
+    assertTrue("zh_Hant should be closer to und_TW than to en_Hant_TW",
+               matchEnHantTw < matchZhHant);
+    assertTrue("zh should be closer to und_TW than to en_Hant_TW",
+               matchEnHantTw < matchZh);
+}
+
+void LocaleMatcherTest::testResolvedLocale() {
+    IcuTestErrorCode errorCode(*this, "testResolvedLocale");
+    LocaleMatcher matcher = LocaleMatcher::Builder().
+        addSupportedLocale("ar-EG").
+        build(errorCode);
+    Locale desired("ar-SA-u-nu-latn");
+    LocaleMatcher::Result result = matcher.getBestMatchResult(desired, errorCode);
+    assertEquals("best", "ar_EG", locString(result.getSupportedLocale()));
+    Locale resolved = result.makeResolvedLocale(errorCode);
+    assertEquals("ar-EG + ar-SA-u-nu-latn = ar-SA-u-nu-latn",
+                 "ar-SA-u-nu-latn",
+                 resolved.toLanguageTag<std::string>(errorCode).data());
+}
+
+namespace {
+
+bool toInvariant(const UnicodeString &s, CharString &inv, ErrorCode &errorCode) {
+    if (errorCode.isSuccess()) {
+        inv.clear().appendInvariantChars(s, errorCode);
+        return errorCode.isSuccess();
+    }
+    return false;
+}
+
+bool getSuffixAfterPrefix(const UnicodeString &s, int32_t limit,
+                          const UnicodeString &prefix, UnicodeString &suffix) {
+    if (prefix.length() <= limit && s.startsWith(prefix)) {
+        suffix.setTo(s, prefix.length(), limit - prefix.length());
+        return true;
+    } else {
+        return false;
+    }
+}
+
+bool getInvariantSuffixAfterPrefix(const UnicodeString &s, int32_t limit,
+                                   const UnicodeString &prefix, CharString &suffix,
+                                   ErrorCode &errorCode) {
+    UnicodeString u_suffix;
+    return getSuffixAfterPrefix(s, limit, prefix, u_suffix) &&
+        toInvariant(u_suffix, suffix, errorCode);
+}
+
+bool readTestCase(const UnicodeString &line, TestCase &test, IcuTestErrorCode &errorCode) {
+    if (errorCode.isFailure()) { return false; }
+    ++test.lineNr;
+    // Start of comment, or end of line, minus trailing spaces.
+    int32_t limit = line.indexOf(u'#');
+    if (limit < 0) {
+        limit = line.length();
+        // Remove trailing CR LF.
+        char16_t c;
+        while (limit > 0 && ((c = line.charAt(limit - 1)) == u'\n' || c == u'\r')) {
+            --limit;
+        }
+    }
+    // Remove spaces before comment or at the end of the line.
+    char16_t c;
+    while (limit > 0 && ((c = line.charAt(limit - 1)) == u' ' || c == u'\t')) {
+        --limit;
+    }
+    if (limit == 0) {  // empty line
+        return false;
+    }
+    if (line.startsWith(u"** test: ")) {
+        test.reset();
+    } else if (getInvariantSuffixAfterPrefix(line, limit, u"@supported=",
+                                             test.supported, errorCode)) {
+    } else if (getInvariantSuffixAfterPrefix(line, limit, u"@default=",
+                                             test.def, errorCode)) {
+    } else if (getSuffixAfterPrefix(line, limit, u"@favor=", test.favor)) {
+    } else if (getSuffixAfterPrefix(line, limit, u"@threshold=", test.threshold)) {
+    } else {
+        int32_t matchSep = line.indexOf(u">>");
+        // >> before an inline comment, and followed by more than white space.
+        if (0 <= matchSep && (matchSep + 2) < limit) {
+            toInvariant(line.tempSubStringBetween(0, matchSep).trim(), test.desired, errorCode);
+            test.expDesired.clear();
+            test.expCombined.clear();
+            int32_t start = matchSep + 2;
+            int32_t expLimit = line.indexOf(u'|', start);
+            if (expLimit < 0) {
+                toInvariant(line.tempSubStringBetween(start, limit).trim(),
+                            test.expMatch, errorCode);
+            } else {
+                toInvariant(line.tempSubStringBetween(start, expLimit).trim(),
+                            test.expMatch, errorCode);
+                start = expLimit + 1;
+                expLimit = line.indexOf(u'|', start);
+                if (expLimit < 0) {
+                    toInvariant(line.tempSubStringBetween(start, limit).trim(),
+                                test.expDesired, errorCode);
+                } else {
+                    toInvariant(line.tempSubStringBetween(start, expLimit).trim(),
+                                test.expDesired, errorCode);
+                    toInvariant(line.tempSubStringBetween(expLimit + 1, limit).trim(),
+                                test.expCombined, errorCode);
+                }
+            }
+            return errorCode.isSuccess();
+        } else {
+            errorCode.set(U_INVALID_FORMAT_ERROR);
+        }
+    }
+    return false;
+}
+
+Locale *getLocaleOrNull(const CharString &s, Locale &locale) {
+    if (s == "null") {
+        return nullptr;
+    } else {
+        return &(locale = Locale(s.data()));
+    }
+}
+
+}  // namespace
+
+UBool LocaleMatcherTest::dataDriven(const TestCase &test, IcuTestErrorCode &errorCode) {
+    LocaleMatcher::Builder builder;
+    builder.setSupportedLocalesFromListString(test.supported.toStringPiece());
+    if (!test.def.isEmpty()) {
+        Locale defaultLocale(test.def.data());
+        builder.setDefaultLocale(&defaultLocale);
+    }
+    if (!test.favor.isEmpty()) {
+        ULocMatchFavorSubtag favor;
+        if (test.favor == u"normal") {
+            favor = ULOCMATCH_FAVOR_LANGUAGE;
+        } else if (test.favor == u"script") {
+            favor = ULOCMATCH_FAVOR_SCRIPT;
+        } else {
+            errln(UnicodeString(u"unsupported FavorSubtag value ") + test.favor);
+            return FALSE;
+        }
+        builder.setFavorSubtag(favor);
+    }
+    if (!test.threshold.isEmpty()) {
+        infoln("skipping test case on line %d with non-default threshold: not exposed via API",
+               (int)test.lineNr);
+        return TRUE;
+        // int32_t threshold = Integer.valueOf(test.threshold);
+        // builder.internalSetThresholdDistance(threshold);
+    }
+    LocaleMatcher matcher = builder.build(errorCode);
+    if (errorCode.errIfFailureAndReset("LocaleMatcher::Builder::build()")) {
+        return FALSE;
+    }
+
+    Locale expMatchLocale("");
+    Locale *expMatch = getLocaleOrNull(test.expMatch, expMatchLocale);
+    if (test.expDesired.isEmpty() && test.expCombined.isEmpty()) {
+        StringPiece desiredSP = test.desired.toStringPiece();
+        const Locale *bestSupported = matcher.getBestMatchForListString(desiredSP, errorCode);
+        if (!assertEquals("bestSupported from string",
+                          locString(expMatch), locString(bestSupported))) {
+            return FALSE;
+        }
+        LocalePriorityList desired(test.desired.toStringPiece(), errorCode);
+        LocalePriorityList::Iterator desiredIter = desired.iterator();
+        if (desired.getLength() == 1) {
+            const Locale &desiredLocale = desiredIter.next();
+            bestSupported = matcher.getBestMatch(desiredLocale, errorCode);
+            UBool ok = assertEquals("bestSupported from Locale",
+                                    locString(expMatch), locString(bestSupported));
+
+            LocaleMatcher::Result result = matcher.getBestMatchResult(desiredLocale, errorCode);
+            return ok & assertEquals("result.getSupportedLocale from Locale",
+                                     locString(expMatch), locString(result.getSupportedLocale()));
+        } else {
+            bestSupported = matcher.getBestMatch(desiredIter, errorCode);
+            return assertEquals("bestSupported from Locale iterator",
+                                locString(expMatch), locString(bestSupported));
+        }
+    } else {
+        LocalePriorityList desired(test.desired.toStringPiece(), errorCode);
+        LocalePriorityList::Iterator desiredIter = desired.iterator();
+        LocaleMatcher::Result result = matcher.getBestMatchResult(desiredIter, errorCode);
+        UBool ok = assertEquals("result.getSupportedLocale from Locales",
+                                locString(expMatch), locString(result.getSupportedLocale()));
+        if (!test.expDesired.isEmpty()) {
+            Locale expDesiredLocale("");
+            Locale *expDesired = getLocaleOrNull(test.expDesired, expDesiredLocale);
+            ok &= assertEquals("result.getDesiredLocale from Locales",
+                               locString(expDesired), locString(result.getDesiredLocale()));
+        }
+        if (!test.expCombined.isEmpty()) {
+            if (test.expMatch.contains("-u-")) {
+                logKnownIssue("20727",
+                              UnicodeString(u"ignoring makeResolvedLocale() line ") + test.lineNr);
+                return ok;
+            }
+            Locale expCombinedLocale("");
+            Locale *expCombined = getLocaleOrNull(test.expCombined, expCombinedLocale);
+            Locale combined = result.makeResolvedLocale(errorCode);
+            ok &= assertEquals("combined Locale from Locales",
+                               locString(expCombined), locString(&combined));
+        }
+        return ok;
+    }
+}
+
+void LocaleMatcherTest::testDataDriven() {
+    IcuTestErrorCode errorCode(*this, "testDataDriven");
+    CharString path(getSourceTestData(errorCode), errorCode);
+    path.appendPathPart("localeMatcherTest.txt", errorCode);
+    const char *codePage = "UTF-8";
+    LocalUCHARBUFPointer f(ucbuf_open(path.data(), &codePage, TRUE, FALSE, errorCode));
+    if(errorCode.errIfFailureAndReset("ucbuf_open(localeMatcherTest.txt)")) {
+        return;
+    }
+    int32_t lineLength;
+    const UChar *p;
+    UnicodeString line;
+    TestCase test;
+    int32_t numPassed = 0;
+    while ((p = ucbuf_readline(f.getAlias(), &lineLength, errorCode)) != nullptr &&
+            errorCode.isSuccess()) {
+        line.setTo(FALSE, p, lineLength);
+        if (!readTestCase(line, test, errorCode)) {
+            if (errorCode.errIfFailureAndReset(
+                    "test data syntax error on line %d", (int)test.lineNr)) {
+                infoln(line);
+            }
+            continue;
+        }
+        UBool ok = dataDriven(test, errorCode);
+        if (errorCode.errIfFailureAndReset("test error on line %d", (int)test.lineNr)) {
+            infoln(line);
+        } else if (!ok) {
+            infoln("test failure on line %d", (int)test.lineNr);
+            infoln(line);
+        } else {
+            ++numPassed;
+        }
+    }
+    infoln("number of passing test cases: %d", (int)numPassed);
+}