1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the Qt Linguist 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 | #if CHECK_SIMTEXTH |
30 | #include "../shared/simtexth.h" |
31 | #endif |
32 | |
33 | #include <QtCore/QDir> |
34 | #include <QtCore/QDebug> |
35 | #include <QtCore/QFile> |
36 | #include <QtCore/QByteArray> |
37 | |
38 | #include <QtTest/QtTest> |
39 | |
40 | class tst_lupdate : public QObject |
41 | { |
42 | Q_OBJECT |
43 | public: |
44 | tst_lupdate(); |
45 | |
46 | private slots: |
47 | void good_data(); |
48 | void good(); |
49 | #if CHECK_SIMTEXTH |
50 | void simtexth(); |
51 | void simtexth_data(); |
52 | #endif |
53 | |
54 | private: |
55 | QString m_cmdLupdate; |
56 | QString m_basePath; |
57 | |
58 | void doCompare(QStringList actual, const QString &expectedFn, bool err); |
59 | void doCompare(const QString &actualFn, const QString &expectedFn, bool err); |
60 | }; |
61 | |
62 | |
63 | tst_lupdate::tst_lupdate() |
64 | { |
65 | QString binPath = QLibraryInfo::location(QLibraryInfo::BinariesPath); |
66 | m_cmdLupdate = binPath + QLatin1String("/lupdate" ); |
67 | m_basePath = QFINDTESTDATA("testdata/" ); |
68 | } |
69 | |
70 | static bool prepareMatch(const QString &expect, QString *tmpl, int *require, int *accept) |
71 | { |
72 | if (expect.startsWith(c: QLatin1Char('\\'))) { |
73 | *tmpl = expect.mid(position: 1); |
74 | *require = *accept = 1; |
75 | } else if (expect.startsWith(c: QLatin1Char('?'))) { |
76 | *tmpl = expect.mid(position: 1); |
77 | *require = 0; |
78 | *accept = 1; |
79 | } else if (expect.startsWith(c: QLatin1Char('*'))) { |
80 | *tmpl = expect.mid(position: 1); |
81 | *require = 0; |
82 | *accept = INT_MAX; |
83 | } else if (expect.startsWith(c: QLatin1Char('+'))) { |
84 | *tmpl = expect.mid(position: 1); |
85 | *require = 1; |
86 | *accept = INT_MAX; |
87 | } else if (expect.startsWith(c: QLatin1Char('{'))) { |
88 | int brc = expect.indexOf(c: QLatin1Char('}'), from: 1); |
89 | if (brc < 0) |
90 | return false; |
91 | *tmpl = expect.mid(position: brc + 1); |
92 | QString sub = expect.mid(position: 1, n: brc - 1); |
93 | int com = sub.indexOf(c: QLatin1Char(',')); |
94 | bool ok; |
95 | if (com < 0) { |
96 | *require = *accept = sub.toInt(ok: &ok); |
97 | return ok; |
98 | } else { |
99 | *require = sub.left(n: com).toInt(); |
100 | *accept = sub.mid(position: com + 1).toInt(ok: &ok); |
101 | if (!ok) |
102 | *accept = INT_MAX; |
103 | return *accept >= *require; |
104 | } |
105 | } else { |
106 | *tmpl = expect; |
107 | *require = *accept = 1; |
108 | } |
109 | return true; |
110 | } |
111 | |
112 | void tst_lupdate::doCompare(QStringList actual, const QString &expectedFn, bool err) |
113 | { |
114 | QFile file(expectedFn); |
115 | QVERIFY2(file.open(QIODevice::ReadOnly | QIODevice::Text), qPrintable(expectedFn)); |
116 | QStringList expected = QString(file.readAll()).split(sep: '\n'); |
117 | |
118 | for (int i = actual.size() - 1; i >= 0; --i) { |
119 | if (actual.at(i).startsWith(s: QLatin1String("Info: creating stash file " ))) |
120 | actual.removeAt(i); |
121 | } |
122 | |
123 | int ei = 0, ai = 0, em = expected.size(), am = actual.size(); |
124 | int oei = 0, oai = 0, oem = em, oam = am; |
125 | int require = 0, accept = 0; |
126 | QString tmpl; |
127 | forever { |
128 | if (!accept) { |
129 | oei = ei, oai = ai; |
130 | if (ei == em) { |
131 | if (ai == am) |
132 | return; |
133 | break; |
134 | } |
135 | if (!prepareMatch(expect: expected.at(i: ei++), tmpl: &tmpl, require: &require, accept: &accept)) |
136 | QFAIL(qPrintable(QString("Malformed expected %1 at %3:%2" ) |
137 | .arg(err ? "output" : "result" ).arg(ei).arg(expectedFn))); |
138 | } |
139 | if (ai == am) { |
140 | if (require <= 0) { |
141 | accept = 0; |
142 | continue; |
143 | } |
144 | break; |
145 | } |
146 | if (err ? !QRegExp(tmpl).exactMatch(str: actual.at(i: ai)) : (actual.at(i: ai) != tmpl)) { |
147 | if (require <= 0) { |
148 | accept = 0; |
149 | continue; |
150 | } |
151 | ei--; |
152 | require = accept = 0; |
153 | forever { |
154 | if (!accept) { |
155 | oem = em, oam = am; |
156 | if (ei == em) |
157 | break; |
158 | if (!prepareMatch(expect: expected.at(i: --em), tmpl: &tmpl, require: &require, accept: &accept)) |
159 | QFAIL(qPrintable(QString("Malformed expected %1 at %3:%2" ) |
160 | .arg(err ? "output" : "result" ) |
161 | .arg(em + 1).arg(expectedFn))); |
162 | } |
163 | if (ai == am || (err ? !QRegExp(tmpl).exactMatch(str: actual.at(i: am - 1)) : |
164 | (actual.at(i: am - 1) != tmpl))) { |
165 | if (require <= 0) { |
166 | accept = 0; |
167 | continue; |
168 | } |
169 | break; |
170 | } |
171 | accept--; |
172 | require--; |
173 | am--; |
174 | } |
175 | break; |
176 | } |
177 | accept--; |
178 | require--; |
179 | ai++; |
180 | } |
181 | QString diff; |
182 | for (int j = qMax(a: 0, b: oai - 3); j < oai; j++) |
183 | diff += actual.at(i: j) + '\n'; |
184 | diff += "<<<<<<< got\n" ; |
185 | for (int j = oai; j < oam; j++) { |
186 | diff += actual.at(i: j) + '\n'; |
187 | if (j >= oai + 5) { |
188 | diff += "...\n" ; |
189 | break; |
190 | } |
191 | } |
192 | diff += "=========\n" ; |
193 | for (int j = oei; j < oem; j++) { |
194 | diff += expected.at(i: j) + '\n'; |
195 | if (j >= oei + 5) { |
196 | diff += "...\n" ; |
197 | break; |
198 | } |
199 | } |
200 | diff += ">>>>>>> expected\n" ; |
201 | for (int j = oam; j < qMin(a: oam + 3, b: actual.size()); j++) |
202 | diff += actual.at(i: j) + '\n'; |
203 | QFAIL(qPrintable((err ? "Output for " : "Result for " ) + expectedFn + " does not meet expectations:\n" + diff)); |
204 | } |
205 | |
206 | void tst_lupdate::doCompare(const QString &actualFn, const QString &expectedFn, bool err) |
207 | { |
208 | QFile afile(actualFn); |
209 | QVERIFY2(afile.open(QIODevice::ReadOnly | QIODevice::Text), qPrintable(actualFn)); |
210 | QStringList actual = QString(afile.readAll()).split(sep: '\n'); |
211 | |
212 | doCompare(actual, expectedFn, err); |
213 | } |
214 | |
215 | void tst_lupdate::good_data() |
216 | { |
217 | QTest::addColumn<QString>(name: "directory" ); |
218 | |
219 | QDir parsingDir(m_basePath + "good" ); |
220 | QStringList dirs = parsingDir.entryList(filters: QDir::Dirs | QDir::NoDotAndDotDot, sort: QDir::Name); |
221 | |
222 | #ifndef Q_OS_WIN |
223 | dirs.removeAll(t: QLatin1String("backslashes" )); |
224 | #endif |
225 | #ifndef Q_OS_MACOS |
226 | dirs.removeAll(t: QLatin1String("parseobjc" )); |
227 | #endif |
228 | |
229 | for (const QString &dir : qAsConst(t&: dirs)) |
230 | QTest::newRow(dataTag: dir.toLocal8Bit()) << dir; |
231 | } |
232 | |
233 | void tst_lupdate::good() |
234 | { |
235 | QFETCH(QString, directory); |
236 | |
237 | QString dir = m_basePath + "good/" + directory; |
238 | |
239 | qDebug() << "Checking..." ; |
240 | |
241 | QString workDir = dir; |
242 | QStringList generatedtsfiles(QLatin1String("project.ts" )); |
243 | QStringList lupdateArguments; |
244 | |
245 | QFile file(dir + "/lupdatecmd" ); |
246 | if (file.exists()) { |
247 | QVERIFY2(file.open(QIODevice::ReadOnly | QIODevice::Text), qPrintable(file.fileName())); |
248 | while (!file.atEnd()) { |
249 | QByteArray cmdstring = file.readLine().simplified(); |
250 | if (cmdstring.startsWith(c: '#')) |
251 | continue; |
252 | if (cmdstring.startsWith(c: "lupdate" )) { |
253 | for (auto argument : cmdstring.mid(index: 8).simplified().split(sep: ' ')) |
254 | lupdateArguments += argument; |
255 | break; |
256 | } else if (cmdstring.startsWith(c: "TRANSLATION:" )) { |
257 | cmdstring.remove(index: 0, len: 12); |
258 | generatedtsfiles.clear(); |
259 | const auto parts = cmdstring.split(sep: ' '); |
260 | for (const QByteArray &s : parts) |
261 | if (!s.isEmpty()) |
262 | generatedtsfiles << s; |
263 | } else if (cmdstring.startsWith(c: "cd " )) { |
264 | cmdstring.remove(index: 0, len: 3); |
265 | workDir = QDir::cleanPath(path: dir + QLatin1Char('/') + cmdstring); |
266 | } |
267 | } |
268 | file.close(); |
269 | } |
270 | |
271 | for (const QString &ts : qAsConst(t&: generatedtsfiles)) { |
272 | QString genTs = workDir + QLatin1Char('/') + ts; |
273 | QFile::remove(fileName: genTs); |
274 | QString beforetsfile = dir + QLatin1Char('/') + ts + QLatin1String(".before" ); |
275 | if (QFile::exists(fileName: beforetsfile)) |
276 | QVERIFY2(QFile::copy(beforetsfile, genTs), qPrintable(beforetsfile)); |
277 | } |
278 | |
279 | file.setFileName(workDir + QStringLiteral("/.qmake.cache" )); |
280 | QVERIFY(file.open(QIODevice::WriteOnly)); |
281 | file.close(); |
282 | |
283 | if (lupdateArguments.isEmpty()) |
284 | lupdateArguments.append(t: QLatin1String("project.pro" )); |
285 | lupdateArguments.prepend(t: "-silent" ); |
286 | |
287 | QProcess proc; |
288 | proc.setWorkingDirectory(workDir); |
289 | proc.setProcessChannelMode(QProcess::MergedChannels); |
290 | const QString command = m_cmdLupdate + ' ' + lupdateArguments.join(sep: ' '); |
291 | proc.start(program: m_cmdLupdate, arguments: lupdateArguments, mode: QIODevice::ReadWrite | QIODevice::Text); |
292 | QVERIFY2(proc.waitForStarted(), qPrintable(command + QLatin1String(" :" ) + proc.errorString())); |
293 | QVERIFY2(proc.waitForFinished(30000), qPrintable(command)); |
294 | const QString output = QString::fromLocal8Bit(str: proc.readAll()); |
295 | QVERIFY2(proc.exitStatus() == QProcess::NormalExit, |
296 | qPrintable(QLatin1Char('"') + command + "\" crashed\n" + output)); |
297 | QVERIFY2(!proc.exitCode(), |
298 | qPrintable(QLatin1Char('"') + command + "\" exited with code " + |
299 | QString::number(proc.exitCode()) + '\n' + output)); |
300 | |
301 | // If the file expectedoutput.txt exists, compare the |
302 | // console output with the content of that file |
303 | QFile outfile(dir + "/expectedoutput.txt" ); |
304 | if (outfile.exists()) { |
305 | QStringList errslist = output.split(sep: QLatin1Char('\n')); |
306 | doCompare(actual: errslist, expectedFn: outfile.fileName(), err: true); |
307 | if (QTest::currentTestFailed()) |
308 | return; |
309 | } |
310 | |
311 | for (const QString &ts : qAsConst(t&: generatedtsfiles)) |
312 | doCompare(actualFn: workDir + QLatin1Char('/') + ts, |
313 | expectedFn: dir + QLatin1Char('/') + ts + QLatin1String(".result" ), err: false); |
314 | } |
315 | |
316 | #if CHECK_SIMTEXTH |
317 | void tst_lupdate::simtexth() |
318 | { |
319 | QFETCH(QString, one); |
320 | QFETCH(QString, two); |
321 | QFETCH(int, expected); |
322 | |
323 | int measured = getSimilarityScore(one, two.toLatin1()); |
324 | QCOMPARE(measured, expected); |
325 | } |
326 | |
327 | |
328 | void tst_lupdate::simtexth_data() |
329 | { |
330 | using namespace QTest; |
331 | |
332 | addColumn<QString>("one" ); |
333 | addColumn<QString>("two" ); |
334 | addColumn<int>("expected" ); |
335 | |
336 | newRow("00" ) << "" << "" << 1024; |
337 | newRow("01" ) << "a" << "a" << 1024; |
338 | newRow("02" ) << "ab" << "ab" << 1024; |
339 | newRow("03" ) << "abc" << "abc" << 1024; |
340 | newRow("04" ) << "abcd" << "abcd" << 1024; |
341 | } |
342 | #endif |
343 | |
344 | QTEST_MAIN(tst_lupdate) |
345 | #include "tst_lupdate.moc" |
346 | |