| 1 | /**************************************************************************** | 
| 2 | ** | 
| 3 | ** Copyright (C) 2020 The Qt Company Ltd. | 
| 4 | ** Contact: https://www.qt.io/licensing/ | 
| 5 | ** | 
| 6 | ** This file is part of the test suite of the Qt Toolkit. | 
| 7 | ** | 
| 8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ | 
| 9 | ** Commercial License Usage | 
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in | 
| 11 | ** accordance with the commercial license agreement provided with the | 
| 12 | ** Software or, alternatively, in accordance with the terms contained in | 
| 13 | ** a written agreement between you and The Qt Company. For licensing terms | 
| 14 | ** and conditions see https://www.qt.io/terms-conditions. For further | 
| 15 | ** information use the contact form at https://www.qt.io/contact-us. | 
| 16 | ** | 
| 17 | ** GNU General Public License Usage | 
| 18 | ** Alternatively, this file may be used under the terms of the GNU | 
| 19 | ** General Public License version 3 as published by the Free Software | 
| 20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT | 
| 21 | ** included in the packaging of this file. Please review the following | 
| 22 | ** information to ensure the GNU General Public License requirements will | 
| 23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. | 
| 24 | ** | 
| 25 | ** $QT_END_LICENSE$ | 
| 26 | ** | 
| 27 | ****************************************************************************/ | 
| 28 |  | 
| 29 | #include <QtTest/QtTest> | 
| 30 |  | 
| 31 | #include <qlocale.h> | 
| 32 | #include <qcollator.h> | 
| 33 | #include <private/qglobal_p.h> | 
| 34 |  | 
| 35 | #include <cstring> | 
| 36 |  | 
| 37 | class tst_QCollator : public QObject | 
| 38 | { | 
| 39 |     Q_OBJECT | 
| 40 |  | 
| 41 | private Q_SLOTS: | 
| 42 |     void moveSemantics(); | 
| 43 |  | 
| 44 |     void compare_data(); | 
| 45 |     void compare(); | 
| 46 |  | 
| 47 |     void state(); | 
| 48 | }; | 
| 49 |  | 
| 50 | static bool dpointer_is_null(QCollator &c) | 
| 51 | { | 
| 52 |     char mem[sizeof c]; | 
| 53 |     using namespace std; | 
| 54 |     memcpy(dest: mem, src: &c, n: sizeof c); | 
| 55 |     for (size_t i = 0; i < sizeof c; ++i) | 
| 56 |         if (mem[i]) | 
| 57 |             return false; | 
| 58 |     return true; | 
| 59 | } | 
| 60 |  | 
| 61 | void tst_QCollator::moveSemantics() | 
| 62 | { | 
| 63 |     const QLocale de_AT(QLocale::German, QLocale::Austria); | 
| 64 |  | 
| 65 |     QCollator c1(de_AT); | 
| 66 |     QCOMPARE(c1.locale(), de_AT); | 
| 67 |  | 
| 68 |     QCollator c2(std::move(c1)); | 
| 69 |     QCOMPARE(c2.locale(), de_AT); | 
| 70 |     QVERIFY(dpointer_is_null(c1)); | 
| 71 |  | 
| 72 |     QCollator c3(c1); | 
| 73 |     QVERIFY(dpointer_is_null(c3)); | 
| 74 |  | 
| 75 |     c1 = std::move(c2); | 
| 76 |     QCOMPARE(c1.locale(), de_AT); | 
| 77 |     QVERIFY(dpointer_is_null(c2)); | 
| 78 | } | 
| 79 |  | 
| 80 |  | 
| 81 | void tst_QCollator::compare_data() | 
| 82 | { | 
| 83 |     QTest::addColumn<QString>(name: "locale" ); | 
| 84 |     QTest::addColumn<QString>(name: "s1" ); | 
| 85 |     QTest::addColumn<QString>(name: "s2" ); | 
| 86 |     QTest::addColumn<int>(name: "result" ); | 
| 87 |     QTest::addColumn<int>(name: "caseInsensitiveResult" ); | 
| 88 |     QTest::addColumn<bool>(name: "numericMode" ); | 
| 89 |     QTest::addColumn<bool>(name: "ignorePunctuation" ); | 
| 90 |     QTest::addColumn<int>(name: "punctuationResult" ); // Test ignores punctuation *and case* | 
| 91 |  | 
| 92 |     /* | 
| 93 |         It's hard to test English, because it's treated differently | 
| 94 |         on different platforms. For example, on Linux, it uses the | 
| 95 |         iso14651_t1 template file, which happens to provide good | 
| 96 |         defaults for Swedish. OS X seems to do a pure bytewise | 
| 97 |         comparison of Latin-1 values, although I'm not sure. So I | 
| 98 |         just test digits to make sure that it's not totally broken. | 
| 99 |     */ | 
| 100 |     QTest::newRow(dataTag: "english1" ) << QString("en_US" ) << QString("5" ) << QString("4" ) << 1 << 1 << false << false << 1; | 
| 101 |     QTest::newRow(dataTag: "english2" ) << QString("en_US" ) << QString("4" ) << QString("6" ) << -1 << -1 << false << false << -1; | 
| 102 |     QTest::newRow(dataTag: "english3" ) << QString("en_US" ) << QString("5" ) << QString("6" ) << -1 << -1 << false << false << -1; | 
| 103 |     QTest::newRow(dataTag: "english4" ) << QString("en_US" ) << QString("a" ) << QString("b" ) << -1 << -1 << false << false << -1; | 
| 104 |     QTest::newRow(dataTag: "english5" ) << QString("en_US" ) << QString("test 9" ) << QString("test 19" ) << -1 << -1 << true << false << -1; | 
| 105 |     QTest::newRow(dataTag: "english6" ) << QString("en_US" ) << QString("test 9" ) << QString("test_19" ) << -1 << -1 << true << true << -1; | 
| 106 |     QTest::newRow(dataTag: "english7" ) << QString("en_US" ) << QString("test_19" ) << QString("test 19" ) << 1 << 1 << true << false << 1; | 
| 107 |     QTest::newRow(dataTag: "english8" ) << QString("en_US" ) << QString("test.19" ) << QString("test,19" ) << 1 << 1 << true << true << 0; | 
| 108 |     QTest::newRow(dataTag: "en-empty-word" ) << QString("en_US" ) << QString() << QString("non-empty" ) << -1 << -1 << false << true << -1; | 
| 109 |     QTest::newRow(dataTag: "en-empty-number" ) << QString("en_US" ) << QString() << QString("42" ) << -1 << -1 << true << true << -1; | 
| 110 |  | 
| 111 |     /* | 
| 112 |         In Swedish, a with ring above (E5) comes before a with | 
| 113 |         diaresis (E4), which comes before o diaresis (F6), which | 
| 114 |         all come after z. | 
| 115 |     */ | 
| 116 |     QTest::newRow(dataTag: "swedish1" ) << QString("sv_SE" ) << QString::fromLatin1(str: "\xe5" ) << QString::fromLatin1(str: "\xe4" ) << -1 << -1 << false << false << -1; | 
| 117 |     QTest::newRow(dataTag: "swedish2" ) << QString("sv_SE" ) << QString::fromLatin1(str: "\xe4" ) << QString::fromLatin1(str: "\xf6" ) << -1 << -1 << false << false << -1; | 
| 118 |     QTest::newRow(dataTag: "swedish3" ) << QString("sv_SE" ) << QString::fromLatin1(str: "\xe5" ) << QString::fromLatin1(str: "\xf6" ) << -1 << -1 << false << false << -1; | 
| 119 |     QTest::newRow(dataTag: "swedish4" ) << QString("sv_SE" ) << QString::fromLatin1(str: "z" ) << QString::fromLatin1(str: "\xe5" ) << -1 << -1 << false << false << -1; | 
| 120 |     QTest::newRow(dataTag: "swedish5" ) << QString("sv_SE" ) << QString("9" ) << QString("19" ) << -1 << -1 << true << false << -1; | 
| 121 |     QTest::newRow(dataTag: "swedish6" ) << QString("sv_SE" ) << QString("Test 9" ) << QString("Test_19" ) << -1 << -1 << true << true << -1; | 
| 122 |     QTest::newRow(dataTag: "swedish7" ) << QString("sv_SE" ) << QString("test_19" ) << QString("test 19" ) << 1 << 1 << true << false << 1; | 
| 123 |     QTest::newRow(dataTag: "swedish8" ) << QString("sv_SE" ) << QString("test.19" ) << QString("test,19" ) << 1 << 1 << true << true << 0; | 
| 124 |     QTest::newRow(dataTag: "sv-empty-word" ) << QString("sv_SE" ) << QString() << QString("mett" ) << -1 << -1 << false << true << -1; | 
| 125 |     QTest::newRow(dataTag: "sv-empty-number" ) << QString("sv_SE" ) << QString() << QString("42" ) << -1 << -1 << true << true << -1; | 
| 126 |  | 
| 127 |  | 
| 128 |     /* | 
| 129 |         In Norwegian, ae (E6) comes before o with stroke (D8), which | 
| 130 |         comes before a with ring above (E5). | 
| 131 |     */ | 
| 132 |     QTest::newRow(dataTag: "norwegian1" ) << QString("no_NO" ) << QString::fromLatin1(str: "\xe6" ) << QString::fromLatin1(str: "\xd8" ) << -1 << -1 << false << false << -1; | 
| 133 |     QTest::newRow(dataTag: "norwegian2" ) << QString("no_NO" ) << QString::fromLatin1(str: "\xd8" ) << QString::fromLatin1(str: "\xe5" ) << -1 << -1 << false << false << -1; | 
| 134 |     QTest::newRow(dataTag: "norwegian3" ) << QString("no_NO" ) << QString::fromLatin1(str: "\xe6" ) << QString::fromLatin1(str: "\xe5" ) << -1 << -1 << false << false << -1; | 
| 135 |     QTest::newRow(dataTag: "norwegian4" ) << QString("no_NO" ) << QString("9" ) << QString("19" ) << -1 << -1 << true << false << -1; | 
| 136 |     QTest::newRow(dataTag: "norwegian5" ) << QString("no_NO" ) << QString("Test 9" ) << QString("Test_19" ) << -1 << -1 << true << true << -1; | 
| 137 |     QTest::newRow(dataTag: "norwegian6" ) << QString("no_NO" ) << QString("Test 9" ) << QString("Test_19" ) << -1 << -1 << true << true << -1; | 
| 138 |     QTest::newRow(dataTag: "norwegian7" ) << QString("no_NO" ) << QString("test_19" ) << QString("test 19" ) << 1 << 1 << true << false << 1; | 
| 139 |     QTest::newRow(dataTag: "norwegian8" ) << QString("no_NO" ) << QString("test.19" ) << QString("test,19" ) << 1 << 1 << true << true << 0; | 
| 140 |     QTest::newRow(dataTag: "nb-empty-word" ) << QString("nb_NO" ) << QString() << QString("mett" ) << -1 << -1 << false << true << -1; | 
| 141 |     QTest::newRow(dataTag: "nb-empty-number" ) << QString("nb_NO" ) << QString() << QString("42" ) << -1 << -1 << true << true << -1; | 
| 142 |  | 
| 143 |     /* | 
| 144 |         In German, z comes *after* a with diaresis (E4), | 
| 145 |         which comes before o diaresis (F6). | 
| 146 |     */ | 
| 147 |     QTest::newRow(dataTag: "german1" ) << QString("de_DE" ) << QString::fromLatin1(str: "a" ) << QString::fromLatin1(str: "\xe4" ) << -1 << -1 << false << false << -1; | 
| 148 |     QTest::newRow(dataTag: "german2" ) << QString("de_DE" ) << QString::fromLatin1(str: "b" ) << QString::fromLatin1(str: "\xe4" ) << 1 << 1 << false << false << 1; | 
| 149 |     QTest::newRow(dataTag: "german3" ) << QString("de_DE" ) << QString::fromLatin1(str: "z" ) << QString::fromLatin1(str: "\xe4" ) << 1 << 1 << false << false << 1; | 
| 150 |     QTest::newRow(dataTag: "german4" ) << QString("de_DE" ) << QString::fromLatin1(str: "\xe4" ) << QString::fromLatin1(str: "\xf6" ) << -1 << -1 << false << false << -1; | 
| 151 |     QTest::newRow(dataTag: "german5" ) << QString("de_DE" ) << QString::fromLatin1(str: "z" ) << QString::fromLatin1(str: "\xf6" ) << 1 << 1 << false << false << 1; | 
| 152 |     QTest::newRow(dataTag: "german6" ) << QString("de_DE" ) << QString::fromLatin1(str: "\xc0" ) << QString::fromLatin1(str: "\xe0" ) << 1 << 0 << false << false << 0; | 
| 153 |     QTest::newRow(dataTag: "german7" ) << QString("de_DE" ) << QString::fromLatin1(str: "\xd6" ) << QString::fromLatin1(str: "\xf6" ) << 1 << 0 << false << false << 0; | 
| 154 |     QTest::newRow(dataTag: "german8" ) << QString("de_DE" ) << QString::fromLatin1(str: "oe" ) << QString::fromLatin1(str: "\xf6" ) << 1 << 1 << false << false << 1; | 
| 155 |     QTest::newRow(dataTag: "german9" ) << QString("de_DE" ) << QString("A" ) << QString("a" ) << 1 << 0 << false << false << 0; | 
| 156 |     QTest::newRow(dataTag: "german10" ) << QString("de_DE" ) << QString("9" ) << QString("19" ) << -1 << -1 << true << false << -1; | 
| 157 |     QTest::newRow(dataTag: "german11" ) << QString("de_DE" ) << QString("Test 9" ) << QString("Test_19" ) << -1 << -1 << true << true << -1; | 
| 158 |     QTest::newRow(dataTag: "german12" ) << QString("de_DE" ) << QString("test_19" ) << QString("test 19" ) << 1 << 1 << true << false << 1; | 
| 159 |     QTest::newRow(dataTag: "german13" ) << QString("de_DE" ) << QString("test.19" ) << QString("test,19" ) << 1 << 1 << true << true << 0; | 
| 160 |     QTest::newRow(dataTag: "de-empty-word" ) << QString("de_DE" ) << QString() << QString("satt" ) << -1 << -1 << false << true << -1; | 
| 161 |     QTest::newRow(dataTag: "de-empty-number" ) << QString("de_DE" ) << QString() << QString("42" ) << -1 << -1 << true << true << -1; | 
| 162 |  | 
| 163 |     /* | 
| 164 |         French sorting of e and e with acute accent | 
| 165 |     */ | 
| 166 |     QTest::newRow(dataTag: "french1" ) << QString("fr_FR" ) << QString::fromLatin1(str: "\xe9" ) << QString::fromLatin1(str: "e" ) << 1 << 1 << false << false << 1; | 
| 167 |     QTest::newRow(dataTag: "french2" ) << QString("fr_FR" ) << QString::fromLatin1(str: "\xe9t" ) << QString::fromLatin1(str: "et" ) << 1 << 1 << false << false << 1; | 
| 168 |     QTest::newRow(dataTag: "french3" ) << QString("fr_FR" ) << QString::fromLatin1(str: "\xe9" ) << QString::fromLatin1(str: "d" ) << 1 << 1 << false << false << 1; | 
| 169 |     QTest::newRow(dataTag: "french4" ) << QString("fr_FR" ) << QString::fromLatin1(str: "\xe9" ) << QString::fromLatin1(str: "f" ) << -1 << -1 << false << false << -1; | 
| 170 |     QTest::newRow(dataTag: "french5" ) << QString("fr_FR" ) << QString("9" ) << QString("19" ) << -1 << -1 << true << false << -1; | 
| 171 |     QTest::newRow(dataTag: "french6" ) << QString("fr_FR" ) << QString("Test 9" ) << QString("Test_19" ) << -1 << -1 << true << true << -1; | 
| 172 |     QTest::newRow(dataTag: "french7" ) << QString("fr_FR" ) << QString("test_19" ) << QString("test 19" ) << 1 << 1 << true << false << 1; | 
| 173 |     QTest::newRow(dataTag: "french8" ) << QString("fr_FR" ) << QString("test.19" ) << QString("test,19" ) << 1 << 1 << true << true << 0; | 
| 174 |     QTest::newRow(dataTag: "fr-empty-word" ) << QString("fr_FR" ) << QString() << QString("plein" ) << -1 << -1 << false << true << -1; | 
| 175 |     QTest::newRow(dataTag: "fr-empty-number" ) << QString("fr_FR" ) << QString() << QString("42" ) << -1 << -1 << true << true << -1; | 
| 176 |  | 
| 177 |     // C locale: case sensitive [A-Z] < [a-z] but case insensitive [Aa] < [Bb] <...< [Zz] | 
| 178 |     const QString C = QStringLiteral("C" ); | 
| 179 |     QTest::newRow(dataTag: "C:ABBA:AaaA" ) << C << QStringLiteral("ABBA" ) << QStringLiteral("AaaA" ) << -1 << 1 << false << false << 1; | 
| 180 |     QTest::newRow(dataTag: "C:AZa:aAZ" ) << C << QStringLiteral("AZa" ) << QStringLiteral("aAZ" ) << -1 << 1 << false << false << 1; | 
| 181 |     QTest::newRow(dataTag: "C-empty-word" ) << QString(C) << QString() << QString("non-empty" ) << -1 << -1 << false << true << -1; | 
| 182 |     QTest::newRow(dataTag: "C-empty-number" ) << QString(C) << QString() << QString("42" ) << -1 << -1 << true << true << -1; | 
| 183 | } | 
| 184 |  | 
| 185 | void tst_QCollator::compare() | 
| 186 | { | 
| 187 |     QFETCH(QString, locale); | 
| 188 |     QFETCH(QString, s1); | 
| 189 |     QFETCH(QString, s2); | 
| 190 |     QFETCH(int, result); | 
| 191 |     QFETCH(int, caseInsensitiveResult); | 
| 192 |     QFETCH(bool, numericMode); | 
| 193 |     QFETCH(bool, ignorePunctuation); | 
| 194 |     QFETCH(int, punctuationResult); | 
| 195 |  | 
| 196 |     QCollator collator(locale); | 
| 197 |     // Need to canonicalize sign to -1, 0 or 1, as .compare() can produce any -ve for <, any +ve for >. | 
| 198 |     auto asSign = [](int compared) { | 
| 199 |         return compared < 0 ? -1 : compared > 0 ? 1 : 0; | 
| 200 |     }; | 
| 201 |  | 
| 202 | #if defined(Q_OS_ANDROID) && !defined(Q_OS_ANDROID_EMBEDDED) | 
| 203 |     if (collator.locale() != QLocale()) | 
| 204 |         QSKIP("Posix implementation of collation only supports default locale" ); | 
| 205 | #endif | 
| 206 |  | 
| 207 |     if (numericMode) | 
| 208 |         collator.setNumericMode(true); | 
| 209 |  | 
| 210 |     QCOMPARE(asSign(collator.compare(s1, s2)), result); | 
| 211 |     collator.setCaseSensitivity(Qt::CaseInsensitive); | 
| 212 |     QCOMPARE(asSign(collator.compare(s1, s2)), caseInsensitiveResult); | 
| 213 | #if !QT_CONFIG(iconv) | 
| 214 |     collator.setIgnorePunctuation(ignorePunctuation); | 
| 215 |     QCOMPARE(asSign(collator.compare(s1, s2)), punctuationResult); | 
| 216 | #endif | 
| 217 | } | 
| 218 |  | 
| 219 |  | 
| 220 | void tst_QCollator::state() | 
| 221 | { | 
| 222 |     QCollator c; | 
| 223 |     c.setCaseSensitivity(Qt::CaseInsensitive); | 
| 224 |     c.setLocale(QLocale::German); | 
| 225 |  | 
| 226 |     c.compare(s1: QString("a" ), s2: QString("b" )); | 
| 227 |  | 
| 228 |     QCOMPARE(c.caseSensitivity(), Qt::CaseInsensitive); | 
| 229 |     QCOMPARE(c.locale(), QLocale(QLocale::German)); | 
| 230 |  | 
| 231 |     c.setLocale(QLocale::French); | 
| 232 |     c.setNumericMode(true); | 
| 233 |     c.setIgnorePunctuation(true); | 
| 234 |     c.setLocale(QLocale::NorwegianBokmal); | 
| 235 |  | 
| 236 |     QCOMPARE(c.caseSensitivity(), Qt::CaseInsensitive); | 
| 237 |     QCOMPARE(c.numericMode(), true); | 
| 238 |     QCOMPARE(c.ignorePunctuation(), true); | 
| 239 |     QCOMPARE(c.locale(), QLocale(QLocale::NorwegianBokmal)); | 
| 240 |  | 
| 241 | } | 
| 242 |  | 
| 243 | QTEST_APPLESS_MAIN(tst_QCollator) | 
| 244 |  | 
| 245 | #include "tst_qcollator.moc" | 
| 246 |  |