1/****************************************************************************
2**
3** Copyright (C) 2019 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#include <QDir>
31#include <QFile>
32#include <QProcess>
33#include <QString>
34#include <QTemporaryDir>
35
36#include <util.h>
37
38class TestQmlformat: public QQmlDataTest
39{
40 Q_OBJECT
41
42private Q_SLOTS:
43 void initTestCase() override;
44
45 void testFormat();
46 void testFormatNoSort();
47 void testAnnotations();
48 void testAnnotationsNoSort();
49 void testLineEndings();
50 void testFrontInline();
51 void testIfBlocks();
52 void testMultilineComments();
53
54 void testReadOnlyProps();
55 void testVerbatimStrings();
56 void testLargeBindings();
57 void testInlineComponents();
58
59 void testQtbug85003();
60
61 void testNestedIf();
62
63 void testNestedFunctions();
64 void testForOf();
65 void testPropertyNames();
66
67#if !defined(QTEST_CROSS_COMPILED) // sources not available when cross compiled
68 void testExample();
69 void testExample_data();
70#endif
71
72private:
73 QString readTestFile(const QString &path);
74 QString runQmlformat(const QString &fileToFormat, bool sortImports, bool shouldSucceed, const QString &newlineFormat = "native");
75
76 QString m_qmlformatPath;
77 QStringList m_excludedDirs;
78 QStringList m_invalidFiles;
79
80 QStringList findFiles(const QDir &);
81 bool isInvalidFile(const QFileInfo &fileName) const;
82};
83
84void TestQmlformat::initTestCase()
85{
86 QQmlDataTest::initTestCase();
87 m_qmlformatPath = QLibraryInfo::location(QLibraryInfo::BinariesPath) + QLatin1String("/qmlformat");
88#ifdef Q_OS_WIN
89 m_qmlformatPath += QLatin1String(".exe");
90#endif
91 if (!QFileInfo(m_qmlformatPath).exists()) {
92 QString message = QStringLiteral("qmlformat executable not found (looked for %0)").arg(a: m_qmlformatPath);
93 QFAIL(qPrintable(message));
94 }
95
96 // Add directories you want excluded here
97
98 // These snippets are not expected to run on their own.
99 m_excludedDirs << "doc/src/snippets/qml/visualdatamodel_rootindex";
100 m_excludedDirs << "doc/src/snippets/qml/qtbinding";
101 m_excludedDirs << "doc/src/snippets/qml/imports";
102 m_excludedDirs << "doc/src/snippets/qtquick1/visualdatamodel_rootindex";
103 m_excludedDirs << "doc/src/snippets/qtquick1/qtbinding";
104 m_excludedDirs << "doc/src/snippets/qtquick1/imports";
105 m_excludedDirs << "tests/manual/v4";
106 m_excludedDirs << "tests/auto/qml/ecmascripttests";
107 m_excludedDirs << "tests/auto/qml/qmllint";
108
109 // Add invalid files (i.e. files with syntax errors)
110 m_invalidFiles << "tests/auto/quick/qquickloader/data/InvalidSourceComponent.qml";
111 m_invalidFiles << "tests/auto/qml/qqmllanguage/data/signal.2.qml";
112 m_invalidFiles << "tests/auto/qml/qqmllanguage/data/signal.3.qml";
113 m_invalidFiles << "tests/auto/qml/qqmllanguage/data/signal.5.qml";
114 m_invalidFiles << "tests/auto/qml/qqmllanguage/data/property.4.qml";
115 m_invalidFiles << "tests/auto/qml/qqmllanguage/data/empty.qml";
116 m_invalidFiles << "tests/auto/qml/qqmllanguage/data/missingObject.qml";
117 m_invalidFiles << "tests/auto/qml/qqmllanguage/data/insertedSemicolon.1.qml";
118 m_invalidFiles << "tests/auto/qml/qqmllanguage/data/nonexistantProperty.5.qml";
119 m_invalidFiles << "tests/auto/qml/qqmllanguage/data/invalidRoot.1.qml";
120 m_invalidFiles << "tests/auto/qml/qqmllanguage/data/invalidQmlEnumValue.1.qml";
121 m_invalidFiles << "tests/auto/qml/qqmllanguage/data/invalidQmlEnumValue.2.qml";
122 m_invalidFiles << "tests/auto/qml/qquickfolderlistmodel/data/dummy.qml";
123 m_invalidFiles << "tests/auto/qml/qqmlecmascript/data/stringParsing_error.1.qml";
124 m_invalidFiles << "tests/auto/qml/qqmlecmascript/data/stringParsing_error.2.qml";
125 m_invalidFiles << "tests/auto/qml/qqmlecmascript/data/stringParsing_error.3.qml";
126 m_invalidFiles << "tests/auto/qml/qqmlecmascript/data/stringParsing_error.4.qml";
127 m_invalidFiles << "tests/auto/qml/qqmlecmascript/data/stringParsing_error.5.qml";
128 m_invalidFiles << "tests/auto/qml/qqmlecmascript/data/stringParsing_error.6.qml";
129 m_invalidFiles << "tests/auto/qml/qqmlecmascript/data/numberParsing_error.1.qml";
130 m_invalidFiles << "tests/auto/qml/qqmlecmascript/data/numberParsing_error.2.qml";
131 m_invalidFiles << "tests/auto/qml/qqmlecmascript/data/incrDecrSemicolon_error1.qml";
132 m_invalidFiles << "tests/auto/qml/qqmlecmascript/data/incrDecrSemicolon_error1.qml";
133 m_invalidFiles << "tests/auto/qml/debugger/qqmlpreview/data/broken.qml";
134 m_invalidFiles << "tests/auto/qml/qqmllanguage/data/fuzzed.2.qml";
135 m_invalidFiles << "tests/auto/qml/qqmllanguage/data/fuzzed.3.qml";
136 m_invalidFiles << "tests/auto/qml/qqmllanguage/data/requiredProperties.2.qml";
137 m_invalidFiles << "tests/auto/qml/qqmllanguage/data/nullishCoalescing_LHS_And.qml";
138 m_invalidFiles << "tests/auto/qml/qqmllanguage/data/nullishCoalescing_LHS_And.qml";
139 m_invalidFiles << "tests/auto/qml/qqmllanguage/data/nullishCoalescing_LHS_Or.qml";
140 m_invalidFiles << "tests/auto/qml/qqmllanguage/data/nullishCoalescing_RHS_And.qml";
141 m_invalidFiles << "tests/auto/qml/qqmllanguage/data/nullishCoalescing_RHS_Or.qml";
142 m_invalidFiles << "tests/auto/qml/qqmllanguage/data/typeAnnotations.2.qml";
143 m_invalidFiles << "tests/auto/qml/qqmlparser/data/disallowedtypeannotations/qmlnestedfunction.qml";
144
145 // These files rely on exact formatting
146 m_invalidFiles << "tests/auto/qml/qqmlecmascript/data/incrDecrSemicolon1.qml";
147 m_invalidFiles << "tests/auto/qml/qqmlecmascript/data/incrDecrSemicolon_error1.qml";
148 m_invalidFiles << "tests/auto/qml/qqmlecmascript/data/incrDecrSemicolon2.qml";
149}
150
151QStringList TestQmlformat::findFiles(const QDir &d)
152{
153 for (int ii = 0; ii < m_excludedDirs.count(); ++ii) {
154 QString s = m_excludedDirs.at(i: ii);
155 if (d.absolutePath().endsWith(s))
156 return QStringList();
157 }
158
159 QStringList rv;
160
161 QStringList files = d.entryList(nameFilters: QStringList() << QLatin1String("*.qml"),
162 filters: QDir::Files);
163 foreach (const QString &file, files) {
164 rv << d.absoluteFilePath(fileName: file);
165 }
166
167 QStringList dirs = d.entryList(filters: QDir::Dirs | QDir::NoDotAndDotDot |
168 QDir::NoSymLinks);
169 foreach (const QString &dir, dirs) {
170 QDir sub = d;
171 sub.cd(dirName: dir);
172 rv << findFiles(d: sub);
173 }
174
175 return rv;
176}
177
178bool TestQmlformat::isInvalidFile(const QFileInfo &fileName) const
179{
180 for (const QString &invalidFile : m_invalidFiles) {
181 if (fileName.absoluteFilePath().endsWith(s: invalidFile))
182 return true;
183 }
184 return false;
185}
186
187QString TestQmlformat::readTestFile(const QString &path)
188{
189 QFile file(testFile(fileName: path));
190
191 if (!file.open(flags: QIODevice::ReadOnly))
192 return "";
193
194 return QString::fromUtf8(str: file.readAll());
195}
196
197void TestQmlformat::testFormat()
198{
199 QCOMPARE(runQmlformat(testFile("Example1.qml"), true, true), readTestFile("Example1.formatted.qml"));
200}
201
202void TestQmlformat::testFormatNoSort()
203{
204 QCOMPARE(runQmlformat(testFile("Example1.qml"), false, true), readTestFile("Example1.formatted.nosort.qml"));
205}
206
207void TestQmlformat::testAnnotations()
208{
209 QCOMPARE(runQmlformat(testFile("Annotations.qml"), true, true), readTestFile("Annotations.formatted.qml"));
210}
211
212void TestQmlformat::testAnnotationsNoSort()
213{
214 QCOMPARE(runQmlformat(testFile("Annotations.qml"), false, true), readTestFile("Annotations.formatted.nosort.qml"));
215}
216
217void TestQmlformat::testFrontInline()
218{
219 QCOMPARE(runQmlformat(testFile("FrontInline.qml"), false, true), readTestFile("FrontInline.formatted.qml"));
220}
221
222void TestQmlformat::testIfBlocks()
223{
224 QCOMPARE(runQmlformat(testFile("IfBlocks.qml"), false, true), readTestFile("IfBlocks.formatted.qml"));
225}
226
227void TestQmlformat::testMultilineComments()
228{
229 QCOMPARE(runQmlformat(testFile("multilineComment.qml"), false, true), readTestFile("multilineComment.formatted.qml"));
230}
231
232
233void TestQmlformat::testReadOnlyProps()
234{
235 QCOMPARE(runQmlformat(testFile("readOnlyProps.qml"), false, true), readTestFile("readOnlyProps.formatted.qml"));
236}
237
238void TestQmlformat::testVerbatimStrings()
239{
240 QCOMPARE(runQmlformat(testFile("verbatimString.qml"), false, true),
241 readTestFile("verbatimString.formatted.qml"));
242}
243
244void TestQmlformat::testInlineComponents()
245{
246 QCOMPARE(runQmlformat(testFile("inlineComponents.qml"), false, true),
247 readTestFile("inlineComponents.formatted.qml"));
248}
249
250void TestQmlformat::testLargeBindings()
251{
252 QCOMPARE(runQmlformat(testFile("largeBindings.qml"), false, true),
253 readTestFile("largeBindings.formatted.qml"));
254}
255
256void TestQmlformat::testNestedIf()
257{
258 QCOMPARE(runQmlformat(testFile("nestedIf.qml"), false, true),
259 readTestFile("nestedIf.formatted.qml"));
260}
261
262void TestQmlformat::testLineEndings()
263{
264 // macos
265 const QString macosContents = runQmlformat(fileToFormat: testFile(fileName: "Example1.formatted.qml"), sortImports: false, shouldSucceed: true, newlineFormat: "macos");
266 QVERIFY(!macosContents.contains("\n"));
267 QVERIFY(macosContents.contains("\r"));
268
269 // windows
270 const QString windowsContents = runQmlformat(fileToFormat: testFile(fileName: "Example1.formatted.qml"), sortImports: false, shouldSucceed: true, newlineFormat: "windows");
271 QVERIFY(windowsContents.contains("\r\n"));
272
273 // unix
274 const QString unixContents = runQmlformat(fileToFormat: testFile(fileName: "Example1.formatted.qml"), sortImports: false, shouldSucceed: true, newlineFormat: "unix");
275 QVERIFY(unixContents.contains("\n"));
276 QVERIFY(!unixContents.contains("\r"));
277}
278
279void TestQmlformat::testQtbug85003()
280{
281 QCOMPARE(runQmlformat(testFile("QtBug85003.qml"), false, true),
282 readTestFile("QtBug85003.formatted.qml"));
283}
284
285void TestQmlformat::testNestedFunctions()
286{
287 QCOMPARE(runQmlformat(testFile("nestedFunctions.qml"), false, true),
288 readTestFile("nestedFunctions.formatted.qml"));
289}
290
291void TestQmlformat::testForOf()
292{
293 QCOMPARE(runQmlformat(testFile("forOf.qml"), false, true),
294 readTestFile("forOf.formatted.qml"));
295}
296
297void TestQmlformat::testPropertyNames()
298{
299 QCOMPARE(runQmlformat(testFile("propertyNames.qml"), false, true),
300 readTestFile("propertyNames.formatted.qml"));
301}
302
303#if !defined(QTEST_CROSS_COMPILED) // sources not available when cross compiled
304void TestQmlformat::testExample_data()
305{
306 QTest::addColumn<QString>(name: "file");
307
308 QString examples = QLatin1String(SRCDIR) + "/../../../../examples/";
309 QString tests = QLatin1String(SRCDIR) + "/../../../../tests/";
310
311 QStringList files;
312 files << findFiles(d: QDir(examples));
313 files << findFiles(d: QDir(tests));
314
315 for (const QString &file : files)
316 QTest::newRow(qPrintable(file)) << file;
317}
318#endif
319
320#if !defined(QTEST_CROSS_COMPILED) // sources not available when cross compiled
321void TestQmlformat::testExample()
322{
323 QFETCH(QString, file);
324 QString output = runQmlformat(fileToFormat: file, sortImports: true, shouldSucceed: !isInvalidFile(fileName: file));
325
326 if (!isInvalidFile(fileName: file))
327 QVERIFY(!output.isEmpty());
328}
329#endif
330
331QString TestQmlformat::runQmlformat(const QString &fileToFormat, bool sortImports, bool shouldSucceed, const QString &newlineFormat)
332{
333 // Copy test file to temporary location
334 QTemporaryDir tempDir;
335 const QString tempFile = tempDir.path() + QDir::separator() + "to_format.qml";
336 QFile::copy(fileName: fileToFormat, newName: tempFile);
337
338 QStringList args;
339 args << "-i";
340 args << tempFile;
341
342 if (!sortImports)
343 args << "-n";
344
345 args << "-l" << newlineFormat;
346
347 auto verify = [&]() {
348 QProcess process;
349 process.start(program: m_qmlformatPath, arguments: args);
350 QVERIFY(process.waitForFinished());
351 QCOMPARE(process.exitStatus(), QProcess::NormalExit);
352 if (shouldSucceed)
353 QCOMPARE(process.exitCode(), 0);
354 };
355 verify();
356
357 QFile temp(tempFile);
358
359 temp.open(flags: QIODevice::ReadOnly);
360 QString formatted = QString::fromUtf8(str: temp.readAll());
361
362 return formatted;
363}
364
365QTEST_MAIN(TestQmlformat)
366#include "tst_qmlformat.moc"
367

source code of qtdeclarative/tests/auto/qml/qmlformat/tst_qmlformat.cpp