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
37class tst_QCollator : public QObject
38{
39 Q_OBJECT
40
41private Q_SLOTS:
42 void moveSemantics();
43
44 void compare_data();
45 void compare();
46
47 void state();
48};
49
50static 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
61void 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
81void 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
185void 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
220void 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
243QTEST_APPLESS_MAIN(tst_QCollator)
244
245#include "tst_qcollator.moc"
246

source code of qtbase/tests/auto/corelib/text/qcollator/tst_qcollator.cpp