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 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 <qtest.h> |
30 | #include <QLibraryInfo> |
31 | #include <QDir> |
32 | #include <QDebug> |
33 | #include <QtQuick/QQuickItem> |
34 | #include <QtQuick/QQuickView> |
35 | #include <QQmlComponent> |
36 | #include <QQmlEngine> |
37 | #include <QQmlError> |
38 | |
39 | static QtMessageHandler testlibMsgHandler = nullptr; |
40 | void msgHandlerFilter(QtMsgType type, const QMessageLogContext &ctxt, const QString &msg) |
41 | { |
42 | if (type == QtCriticalMsg || type == QtFatalMsg) |
43 | (*testlibMsgHandler)(type, ctxt, msg); |
44 | } |
45 | |
46 | class tst_examples : public QObject |
47 | { |
48 | Q_OBJECT |
49 | public: |
50 | tst_examples(); |
51 | ~tst_examples(); |
52 | |
53 | private slots: |
54 | void init(); |
55 | void cleanup(); |
56 | |
57 | void sgexamples_data(); |
58 | void sgexamples(); |
59 | void sgsnippets_data(); |
60 | void sgsnippets(); |
61 | |
62 | void namingConvention(); |
63 | private: |
64 | QStringList excludedDirs; |
65 | QStringList excludedFiles; |
66 | |
67 | void namingConvention(const QDir &); |
68 | QStringList findQmlFiles(const QDir &); |
69 | |
70 | QQmlEngine engine; |
71 | }; |
72 | |
73 | tst_examples::tst_examples() |
74 | { |
75 | // Add files to exclude here |
76 | excludedFiles << "snippets/qml/listmodel/listmodel.qml" ; //Just a ListModel, no root QQuickItem |
77 | excludedFiles << "snippets/qml/tablemodel/fruit-example-delegatechooser.qml" ; // Requires QtQuick.Controls import. |
78 | |
79 | // Add directories you want excluded here |
80 | excludedDirs << "shared" ; //Not an example |
81 | excludedDirs << "snippets/qml/path" ; //No root QQuickItem |
82 | excludedDirs << "examples/qml/qmlextensionplugins" ; //Requires special import search path |
83 | |
84 | // These snippets are not expected to run on their own. |
85 | excludedDirs << "snippets/qml/visualdatamodel_rootindex" ; |
86 | excludedDirs << "snippets/qml/qtbinding" ; |
87 | excludedDirs << "snippets/qml/imports" ; |
88 | excludedFiles << "snippets/qml/image-ext.qml" ; |
89 | excludedFiles << "examples/quick/shapes/content/main.qml" ; // relies on resources |
90 | excludedFiles << "examples/quick/shapes/content/interactive.qml" ; // relies on resources |
91 | |
92 | #if !QT_CONFIG(opengl) |
93 | //No support for Particles |
94 | excludedFiles << "examples/qml/dynamicscene/dynamicscene.qml" ; |
95 | excludedFiles << "examples/quick/animation/basics/color-animation.qml" ; |
96 | excludedFiles << "examples/quick/particles/affectors/content/age.qml" ; |
97 | excludedFiles << "examples/quick/touchinteraction/multipointtouch/bearwhack.qml" ; |
98 | excludedFiles << "examples/quick/touchinteraction/multipointtouch/multiflame.qml" ; |
99 | excludedDirs << "examples/quick/particles" ; |
100 | // No Support for ShaderEffect |
101 | excludedFiles << "src/quick/doc/snippets/qml/animators.qml" ; |
102 | #endif |
103 | |
104 | } |
105 | |
106 | tst_examples::~tst_examples() |
107 | { |
108 | } |
109 | |
110 | void tst_examples::init() |
111 | { |
112 | if (!qstrcmp(str1: QTest::currentTestFunction(), str2: "sgsnippets" )) |
113 | testlibMsgHandler = qInstallMessageHandler(msgHandlerFilter); |
114 | } |
115 | |
116 | void tst_examples::cleanup() |
117 | { |
118 | if (!qstrcmp(str1: QTest::currentTestFunction(), str2: "sgsnippets" )) |
119 | qInstallMessageHandler(testlibMsgHandler); |
120 | } |
121 | |
122 | /* |
123 | This tests that the examples follow the naming convention required |
124 | to have them tested by the examples() test. |
125 | */ |
126 | void tst_examples::namingConvention(const QDir &d) |
127 | { |
128 | for (int ii = 0; ii < excludedDirs.count(); ++ii) { |
129 | QString s = excludedDirs.at(i: ii); |
130 | if (d.absolutePath().endsWith(s)) |
131 | return; |
132 | } |
133 | |
134 | QStringList files = d.entryList(nameFilters: QStringList() << QLatin1String("*.qml" ), |
135 | filters: QDir::Files); |
136 | |
137 | bool seenQml = !files.isEmpty(); |
138 | bool seenLowercase = false; |
139 | |
140 | foreach (const QString &file, files) { |
141 | if (file.at(i: 0).isLower()) |
142 | seenLowercase = true; |
143 | } |
144 | |
145 | if (!seenQml) { |
146 | QStringList dirs = d.entryList(filters: QDir::Dirs | QDir::NoDotAndDotDot | |
147 | QDir::NoSymLinks); |
148 | foreach (const QString &dir, dirs) { |
149 | QDir sub = d; |
150 | sub.cd(dirName: dir); |
151 | namingConvention(d: sub); |
152 | } |
153 | } else if(!seenLowercase) { |
154 | // QTBUG-28271 don't fail, but rather warn only |
155 | qWarning() << QString( |
156 | "Directory %1 violates naming convention; expected at least one qml file " |
157 | "starting with lower case, got: %2" |
158 | ).arg(a: d.absolutePath()).arg(a: files.join(sep: "," )); |
159 | |
160 | // QFAIL(qPrintable(QString( |
161 | // "Directory %1 violates naming convention; expected at least one qml file " |
162 | // "starting with lower case, got: %2" |
163 | // ).arg(d.absolutePath()).arg(files.join(",")))); |
164 | } |
165 | } |
166 | |
167 | void tst_examples::namingConvention() |
168 | { |
169 | QStringList examplesLocations; |
170 | examplesLocations << QLibraryInfo::location(QLibraryInfo::ExamplesPath) + QLatin1String("/qml" ); |
171 | examplesLocations << QLibraryInfo::location(QLibraryInfo::ExamplesPath) + QLatin1String("/quick" ); |
172 | |
173 | foreach(const QString &examples, examplesLocations) { |
174 | QDir d(examples); |
175 | if (d.exists()) |
176 | namingConvention(d); |
177 | } |
178 | } |
179 | |
180 | QStringList tst_examples::findQmlFiles(const QDir &d) |
181 | { |
182 | for (int ii = 0; ii < excludedDirs.count(); ++ii) { |
183 | QString s = excludedDirs.at(i: ii); |
184 | if (d.absolutePath().endsWith(s)) |
185 | return QStringList(); |
186 | } |
187 | |
188 | QStringList rv; |
189 | |
190 | QStringList cppfiles = d.entryList(nameFilters: QStringList() << QLatin1String("*.cpp" ), filters: QDir::Files); |
191 | if (cppfiles.isEmpty()) { |
192 | QStringList files = d.entryList(nameFilters: QStringList() << QLatin1String("*.qml" ), |
193 | filters: QDir::Files); |
194 | foreach (const QString &file, files) { |
195 | if (file.at(i: 0).isLower()) { |
196 | bool superContinue = false; |
197 | for (int ii = 0; ii < excludedFiles.count(); ++ii) { |
198 | QString e = excludedFiles.at(i: ii); |
199 | if (d.absoluteFilePath(fileName: file).endsWith(s: e)) { |
200 | superContinue = true; |
201 | break; |
202 | } |
203 | } |
204 | if (superContinue) |
205 | continue; |
206 | rv << d.absoluteFilePath(fileName: file); |
207 | } |
208 | } |
209 | } |
210 | |
211 | |
212 | QStringList dirs = d.entryList(filters: QDir::Dirs | QDir::NoDotAndDotDot | |
213 | QDir::NoSymLinks); |
214 | foreach (const QString &dir, dirs) { |
215 | QDir sub = d; |
216 | sub.cd(dirName: dir); |
217 | rv << findQmlFiles(d: sub); |
218 | } |
219 | |
220 | return rv; |
221 | } |
222 | |
223 | /* |
224 | This test runs all the examples in the QtQml UI source tree and ensures |
225 | that they start and exit cleanly. |
226 | |
227 | Examples are any .qml files under the examples/ directory that start |
228 | with a lower case letter. |
229 | */ |
230 | void tst_examples::sgexamples_data() |
231 | { |
232 | QTest::addColumn<QString>(name: "file" ); |
233 | |
234 | QString examples = QLatin1String(SRCDIR) + "/../../../../examples/" ; |
235 | |
236 | QStringList files; |
237 | files << findQmlFiles(d: QDir(examples)); |
238 | |
239 | foreach (const QString &file, files) |
240 | QTest::newRow(qPrintable(file)) << file; |
241 | } |
242 | |
243 | void tst_examples::sgexamples() |
244 | { |
245 | QFETCH(QString, file); |
246 | QQuickWindow window; |
247 | window.setPersistentOpenGLContext(true); |
248 | window.setPersistentSceneGraph(true); |
249 | |
250 | QQmlComponent component(&engine, QUrl::fromLocalFile(localfile: file)); |
251 | if (component.status() == QQmlComponent::Error) |
252 | qWarning() << component.errors(); |
253 | QCOMPARE(component.status(), QQmlComponent::Ready); |
254 | |
255 | QScopedPointer<QObject> object(component.beginCreate(engine.rootContext())); |
256 | QQuickItem *root = qobject_cast<QQuickItem *>(object: object.data()); |
257 | if (!root) |
258 | component.completeCreate(); |
259 | QVERIFY(root); |
260 | |
261 | window.resize(w: 240, h: 320); |
262 | window.show(); |
263 | QVERIFY(QTest::qWaitForWindowExposed(&window)); |
264 | |
265 | root->setParentItem(window.contentItem()); |
266 | component.completeCreate(); |
267 | |
268 | qApp->processEvents(); |
269 | } |
270 | |
271 | void tst_examples::sgsnippets_data() |
272 | { |
273 | QTest::addColumn<QString>(name: "file" ); |
274 | |
275 | QString snippets = QLatin1String(SRCDIR) + "/../../../../src/qml/doc/snippets/qml" ; |
276 | QStringList files; |
277 | files << findQmlFiles(d: QDir(snippets)); |
278 | foreach (const QString &file, files) |
279 | QTest::newRow(qPrintable(file)) << file; |
280 | |
281 | snippets = QLatin1String(SRCDIR) + "/../../../../src/quick/doc/snippets/qml" ; |
282 | files.clear(); |
283 | files << findQmlFiles(d: QDir(snippets)); |
284 | foreach (const QString &file, files) |
285 | QTest::newRow(qPrintable(file)) << file; |
286 | } |
287 | |
288 | void tst_examples::sgsnippets() |
289 | { |
290 | |
291 | QFETCH(QString, file); |
292 | |
293 | QQmlComponent component(&engine, QUrl::fromLocalFile(localfile: file)); |
294 | if (component.status() == QQmlComponent::Error) |
295 | qWarning() << component.errors(); |
296 | QCOMPARE(component.status(), QQmlComponent::Ready); |
297 | |
298 | QScopedPointer<QObject> object(component.beginCreate(engine.rootContext())); |
299 | QQuickWindow *window = qobject_cast<QQuickWindow*>(object: object.data()); |
300 | QQuickItem *root = qobject_cast<QQuickItem *>(object: object.data()); |
301 | if (!root && !window) { |
302 | component.completeCreate(); |
303 | QVERIFY(false); |
304 | } |
305 | if (!window) |
306 | window = new QQuickWindow; |
307 | |
308 | window->resize(w: 240, h: 320); |
309 | window->show(); |
310 | QVERIFY(QTest::qWaitForWindowExposed(window)); |
311 | |
312 | if (root) |
313 | root->setParentItem(window->contentItem()); |
314 | component.completeCreate(); |
315 | |
316 | qApp->processEvents(); |
317 | if (root) |
318 | delete window; |
319 | } |
320 | |
321 | QTEST_MAIN(tst_examples) |
322 | |
323 | #include "tst_examples.moc" |
324 | |