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 | |