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#include <QtTest/QtTest>
30#include <QtCore/QFile>
31
32class tst_lconvert : public QObject
33{
34 Q_OBJECT
35
36public:
37 tst_lconvert()
38 : dataDir(QFINDTESTDATA("data/"))
39 , lconvert(QLibraryInfo::location(QLibraryInfo::BinariesPath) + "/lconvert") {}
40
41private slots:
42 void initTestCase();
43 void readverifies_data();
44 void readverifies();
45 void converts_data();
46 void converts();
47 void roundtrips_data();
48 void roundtrips();
49 void chains_data();
50 void chains();
51 void merge();
52
53private:
54 void doWait(QProcess *cvt, int stage);
55 void doCompare(QIODevice *actual, const QString &expectedFn);
56 void verifyReadFail(const QString &fn);
57 // args can be empty or have one element less than stations
58 void convertChain(const QString &inFileName, const QString &outFileName,
59 const QStringList &stations, const QList<QStringList> &args);
60 void convertRoundtrip(const QString &fileName, const QStringList &stations,
61 const QList<QStringList> &args);
62
63 QString dataDir;
64 QString lconvert;
65};
66
67void tst_lconvert::initTestCase()
68{
69 if (!QFile::exists(fileName: dataDir + QLatin1String("plural-1.po")))
70 QProcess::execute(program: QLatin1String("perl"), arguments: QStringList() << dataDir + QLatin1String("makeplurals.pl") << dataDir + QLatin1String(""));
71 QVERIFY(QFile::exists(dataDir + QLatin1String("plural-1.po")));
72}
73
74void tst_lconvert::doWait(QProcess *cvt, int stage)
75{
76 if (QTest::currentTestFailed()) {
77 cvt->kill();
78 cvt->waitForFinished();
79 } else {
80 QVERIFY2(cvt->waitForFinished(3000),
81 qPrintable(QString("Process %1 hung").arg(stage)));
82 QVERIFY2(cvt->exitStatus() == QProcess::NormalExit,
83 qPrintable(QString("Process %1 crashed").arg(stage)));
84 QVERIFY2(cvt->exitCode() == 0,
85 qPrintable(QString("Process %1 exited with status %2. Errors:\n%3")
86 .arg(stage).arg(cvt->exitCode())
87 .arg(QString::fromUtf8(cvt->readAllStandardError()))));
88 }
89}
90
91void tst_lconvert::doCompare(QIODevice *actualDev, const QString &expectedFn)
92{
93 QList<QByteArray> actual = actualDev->readAll().split(sep: '\n');
94
95 QFile file(expectedFn);
96 QVERIFY(file.open(QIODevice::ReadOnly | QIODevice::Text));
97 QList<QByteArray> expected = file.readAll().split(sep: '\n');
98
99 int i = 0, ei = expected.size(), gi = actual.size();
100 for (; ; i++) {
101 if (i == gi) {
102 if (i == ei)
103 return;
104 gi = 0;
105 break;
106 } else if (i == ei) {
107 ei = 0;
108 break;
109 } else if (actual.at(i) != expected.at(i)) {
110 while ((ei - 1) >= i && (gi - 1) >= i && actual.at(i: gi - 1) == expected.at(i: ei - 1))
111 ei--, gi--;
112 break;
113 }
114 }
115 QByteArray diff;
116 for (int j = qMax(a: 0, b: i - 3); j < i; j++)
117 diff += expected.at(i: j) + '\n';
118 diff += "<<<<<<< got\n";
119 for (int j = i; j < gi; j++) {
120 diff += actual.at(i: j) + '\n';
121 if (j >= i + 5) {
122 diff += "...\n";
123 break;
124 }
125 }
126 diff += "=========\n";
127 for (int j = i; j < ei; j++) {
128 diff += expected.at(i: j) + '\n';
129 if (j >= i + 5) {
130 diff += "...\n";
131 break;
132 }
133 }
134 diff += ">>>>>>> expected\n";
135 for (int j = ei; j < qMin(a: ei + 3, b: expected.size()); j++)
136 diff += expected.at(i: j) + '\n';
137 QFAIL(qPrintable("Output for " + expectedFn + " does not meet expectations:\n" + diff));
138}
139
140void tst_lconvert::verifyReadFail(const QString &fn)
141{
142 QProcess cvt;
143 cvt.start(program: lconvert, arguments: QStringList() << (dataDir + fn));
144 QVERIFY2(cvt.waitForStarted(), qPrintable(cvt.errorString()));
145 QVERIFY(cvt.waitForFinished(10000));
146 QVERIFY(cvt.exitStatus() == QProcess::NormalExit);
147 QVERIFY2(cvt.exitCode() == 2, "Accepted invalid input");
148}
149
150void tst_lconvert::convertChain(const QString &_inFileName, const QString &_outFileName,
151 const QStringList &stations, const QList<QStringList> &argList)
152{
153 QList<QProcess *> cvts;
154
155 QString fileName = dataDir + _inFileName;
156 QString outFileName = dataDir + _outFileName;
157
158 for (int i = 0; i < stations.size() - 1; i++) {
159 QProcess *cvt = new QProcess(this);
160 if (cvts.isEmpty())
161 cvt->setStandardInputFile(fileName);
162 else
163 cvts.last()->setStandardOutputProcess(cvt);
164 cvts.append(t: cvt);
165 }
166 for (int i = 0; i < stations.size() - 1; i++) {
167 QStringList args;
168 if (!argList.isEmpty())
169 args += argList[i];
170 args << "-if" << stations[i] << "-i" << "-" << "-of" << stations[i + 1];
171 cvts.at(i)->start(program: lconvert, arguments: args, mode: QIODevice::ReadWrite | QIODevice::Text);
172 }
173 for (QProcess *cvt : qAsConst(t&: cvts))
174 QVERIFY2(cvt->waitForStarted(), qPrintable(cvt->errorString()));
175 int st = 0;
176 for (QProcess *cvt : qAsConst(t&: cvts))
177 doWait(cvt, stage: ++st);
178
179 if (!QTest::currentTestFailed())
180 doCompare(actualDev: cvts.last(), expectedFn: outFileName);
181
182 qDeleteAll(c: cvts);
183}
184
185void tst_lconvert::convertRoundtrip(const QString &_fileName, const QStringList &stations,
186 const QList<QStringList> &argList)
187{
188 convertChain(inFileName: _fileName, outFileName: _fileName, stations, argList);
189}
190
191void tst_lconvert::readverifies_data()
192{
193 QTest::addColumn<QString>(name: "fileName");
194 QTest::addColumn<QString>(name: "format");
195
196 QTest::newRow(dataTag: "ts") << "test20.ts" << "ts";
197 QTest::newRow(dataTag: "empty comment") << "test-empty-comment.po" << "po";
198 QTest::newRow(dataTag: "translator comment") << "test-translator-comment.po" << "po";
199 QTest::newRow(dataTag: "developer comment") << "test-developer-comment.po" << "po";
200 QTest::newRow(dataTag: "kde context") << "test-kde-ctxt.po" << "po";
201 QTest::newRow(dataTag: "kde fuzzy") << "test-kde-fuzzy.po" << "po";
202 QTest::newRow(dataTag: "kde plurals") << "test-kde-plurals.po" << "po";
203 QTest::newRow(dataTag: "kde multiline") << "test-kde-multiline.po" << "po";
204 QTest::newRow(dataTag: "po linewrapping") << "wrapping.po" << "po";
205 QTest::newRow(dataTag: "relative locations") << "relative.ts" << "ts";
206 QTest::newRow(dataTag: "message ids") << "msgid.ts" << "ts";
207 QTest::newRow(dataTag: "length variants") << "variants.ts" << "ts";
208 QTest::newRow(dataTag: "qph") << "phrasebook.qph" << "qph";
209}
210
211void tst_lconvert::readverifies()
212{
213 QFETCH(QString, fileName);
214 QFETCH(QString, format);
215
216 convertRoundtrip(fileName: fileName, stations: QStringList() << format << format, argList: QList<QStringList>());
217}
218
219void tst_lconvert::converts_data()
220{
221 QTest::addColumn<QString>(name: "inFileName");
222 QTest::addColumn<QString>(name: "outFileName");
223 QTest::addColumn<QString>(name: "format");
224
225 QTest::newRow(dataTag: "broken utf8") << "test-broken-utf8.po" << "test-broken-utf8.po.out" << "po";
226 QTest::newRow(dataTag: "line joins") << "test-slurp.po" << "test-slurp.po.out" << "po";
227 QTest::newRow(dataTag: "escapes") << "test-escapes.po" << "test-escapes.po.out" << "po";
228}
229
230void tst_lconvert::converts()
231{
232 QFETCH(QString, inFileName);
233 QFETCH(QString, outFileName);
234 QFETCH(QString, format);
235
236 QString outFileNameFq = dataDir + outFileName;
237
238 QProcess cvt;
239 cvt.start(program: lconvert,
240 arguments: QStringList() << "-i" << (dataDir + inFileName) << "-of" << format,
241 mode: QIODevice::ReadWrite | QIODevice::Text);
242 QVERIFY2(cvt.waitForStarted(), qPrintable(cvt.errorString()));
243 doWait(cvt: &cvt, stage: 0);
244 if (QTest::currentTestFailed())
245 return;
246
247 doCompare(actualDev: &cvt, expectedFn: outFileNameFq);
248}
249
250Q_DECLARE_METATYPE(QList<QStringList>);
251
252void tst_lconvert::chains_data()
253{
254 QTest::addColumn<QString>(name: "inFileName");
255 QTest::addColumn<QString>(name: "outFileName");
256 QTest::addColumn<QStringList>(name: "stations");
257 QTest::addColumn<QList<QStringList> >(name: "args");
258
259 QTest::newRow(dataTag: "no-untranslated") << "untranslated.ts" << "untranslated.ts.out"
260 << QStringList({"ts", "ts"})
261 << QList<QStringList>({QStringList("-no-untranslated")});
262}
263
264void tst_lconvert::chains()
265{
266 QFETCH(QString, inFileName);
267 QFETCH(QString, outFileName);
268 QFETCH(QStringList, stations);
269 QFETCH(QList<QStringList>, args);
270
271 convertChain(inFileName: inFileName, outFileName: outFileName, stations, argList: args);
272}
273
274void tst_lconvert::roundtrips_data()
275{
276 QTest::addColumn<QString>(name: "fileName");
277 QTest::addColumn<QStringList>(name: "stations");
278 QTest::addColumn<QList<QStringList> >(name: "args");
279
280 QStringList poTsPo; poTsPo << "po" << "ts" << "po";
281 QStringList poXlfPo; poXlfPo << "po" << "xlf" << "po";
282 QStringList tsPoTs; tsPoTs << "ts" << "po" << "ts";
283 QStringList tsXlfTs; tsXlfTs << "ts" << "xlf" << "ts";
284 QStringList tsQmTs; tsQmTs << "ts" << "qm" << "ts";
285 QStringList qmTsQm; qmTsQm << "qm" << "ts" << "qm";
286
287 QList<QStringList> noArgs;
288 QList<QStringList> filterPoArgs; filterPoArgs << QStringList() << (QStringList() << "-drop-tag" << "po:*");
289 QList<QStringList> outDeArgs; outDeArgs << QStringList() << (QStringList() << "-target-language" << "de");
290 QList<QStringList> outCnArgs; outCnArgs << QStringList() << (QStringList() << "-target-language" << "cn");
291
292 QTest::newRow(dataTag: "po-ts-po (translator comment)") << "test-translator-comment.po" << poTsPo << noArgs;
293 QTest::newRow(dataTag: "po-xliff-po (translator comment)") << "test-translator-comment.po" << poXlfPo << noArgs;
294 QTest::newRow(dataTag: "po-ts-po (developer comment)") << "test-developer-comment.po" << poTsPo << noArgs;
295 QTest::newRow(dataTag: "po-xliff-po (developer comment)") << "test-developer-comment.po" << poXlfPo << noArgs;
296
297 QTest::newRow(dataTag: "ts20-po-ts20") << "test20.ts" << tsPoTs << filterPoArgs;
298 QTest::newRow(dataTag: "po-ts-po (de)") << "test1-de.po" << poTsPo << noArgs;
299 QTest::newRow(dataTag: "po-ts-po (cn)") << "test1-cn.po" << poTsPo << noArgs;
300 QTest::newRow(dataTag: "po-xliff-po (de)") << "test1-de.po" << poXlfPo << noArgs;
301 QTest::newRow(dataTag: "po-xliff-po (cn)") << "test1-cn.po" << poXlfPo << noArgs;
302
303 QTest::newRow(dataTag: "po-ts-po (singular)") << "singular.po" << poTsPo << noArgs;
304 QTest::newRow(dataTag: "po-ts-po (plural-1)") << "plural-1.po" << poTsPo << noArgs;
305 QTest::newRow(dataTag: "po-ts-po (plural-2)") << "plural-2.po" << poTsPo << noArgs;
306 QTest::newRow(dataTag: "po-ts-po (plural-3)") << "plural-3.po" << poTsPo << noArgs;
307 QTest::newRow(dataTag: "po-xliff-po (singular)") << "singular.po" << poXlfPo << noArgs;
308 QTest::newRow(dataTag: "po-xliff-po (plural-1)") << "plural-1.po" << poXlfPo << noArgs;
309 QTest::newRow(dataTag: "po-xliff-po (plural-2)") << "plural-2.po" << poXlfPo << noArgs;
310 QTest::newRow(dataTag: "po-xliff-po (plural-3)") << "plural-3.po" << poXlfPo << noArgs;
311
312 QTest::newRow(dataTag: "po-ts-po (references)") << "test-refs.po" << poTsPo << noArgs;
313
314 QTest::newRow(dataTag: "ts-qm-ts (plurals-de)") << "plurals-de.ts" << tsQmTs << outDeArgs;
315 QTest::newRow(dataTag: "ts-qm-ts (plurals-cn)") << "plurals-cn.ts" << tsQmTs << outCnArgs;
316 QTest::newRow(dataTag: "ts-qm-ts (variants)") << "variants.ts" << tsQmTs << outDeArgs;
317 QTest::newRow(dataTag: "ts-po-ts (msgid)") << "msgid.ts" << tsPoTs << noArgs;
318 QTest::newRow(dataTag: "ts-xliff-ts (msgid)") << "msgid.ts" << tsXlfTs << noArgs;
319
320 QTest::newRow(dataTag: "ts-po-ts (endless loop)") << "endless-po-loop.ts" << tsPoTs << noArgs;
321 QTest::newRow(dataTag: "ts-qm-ts (whitespace)") << "whitespace.ts" << tsQmTs << noArgs;
322 QTest::newRow(dataTag: "qm-ts-qm (untranslated)") << "untranslated.qm" << qmTsQm << noArgs;
323}
324
325void tst_lconvert::roundtrips()
326{
327 QFETCH(QString, fileName);
328 QFETCH(QStringList, stations);
329 QFETCH(QList<QStringList>, args);
330
331 convertRoundtrip(fileName: fileName, stations, argList: args);
332}
333
334void tst_lconvert::merge()
335{
336 QProcess cvt;
337 QStringList args;
338 args << (dataDir + "idxmerge.ts") << (dataDir + "idxmerge-add.ts");
339 cvt.start(program: lconvert, arguments: args, mode: QIODevice::ReadWrite | QIODevice::Text);
340 doWait(cvt: &cvt, stage: 1);
341 if (!QTest::currentTestFailed())
342 doCompare(actualDev: &cvt, expectedFn: dataDir + "idxmerge.ts.out");
343}
344
345QTEST_APPLESS_MAIN(tst_lconvert)
346
347#include "tst_lconvert.moc"
348

source code of qttools/tests/auto/linguist/lconvert/tst_lconvert.cpp