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
40class tst_lupdate : public QObject
41{
42 Q_OBJECT
43public:
44 tst_lupdate();
45
46private slots:
47 void good_data();
48 void good();
49#if CHECK_SIMTEXTH
50 void simtexth();
51 void simtexth_data();
52#endif
53
54private:
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
63tst_lupdate::tst_lupdate()
64{
65 QString binPath = QLibraryInfo::location(QLibraryInfo::BinariesPath);
66 m_cmdLupdate = binPath + QLatin1String("/lupdate");
67 m_basePath = QFINDTESTDATA("testdata/");
68}
69
70static 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
112void 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
206void 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
215void 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
233void 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
317void 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
328void 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
344QTEST_MAIN(tst_lupdate)
345#include "tst_lupdate.moc"
346

source code of qttools/tests/auto/linguist/lupdate/tst_lupdate.cpp