1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Kevin Krammer <kevin.krammer@kdab.com> |
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 | #include <qtest.h> |
29 | #include <QtQml/qqmlcomponent.h> |
30 | #include <QtQml/qqmlengine.h> |
31 | #include <QtQml/private/qqmlmetatype_p.h> |
32 | #include <QtCore/QDebug> |
33 | #include <QtCore/QHash> |
34 | #include <QtCore/QSet> |
35 | |
36 | class tst_PropertyRequirements : public QObject |
37 | { |
38 | Q_OBJECT |
39 | public: |
40 | tst_PropertyRequirements(); |
41 | |
42 | private slots: |
43 | void constantOrNotifyableMain(); |
44 | void constantOrNotifyableFull(); |
45 | |
46 | private: |
47 | typedef QList<QQmlType> Failures; |
48 | |
49 | /*! |
50 | All properties that do not pass the check and the affected QML types |
51 | |
52 | Key: Property name formatted as C++-class-name::property-name |
53 | Value: List of QQmlType which have the property |
54 | */ |
55 | typedef QHash<QString, Failures> FailuresByProperty; |
56 | |
57 | enum TestDepth { |
58 | MainTypeOnly, //! Check only the properties of the main C++ type for each QML type |
59 | WithSuperClasses //! Check the super classes for each C++ type as well |
60 | }; |
61 | |
62 | void testAllQmlTypes(TestDepth testDepth, FailuresByProperty &failuresByProperty); |
63 | void testQmlType(TestDepth testDepth, const QQmlType &qmlType, FailuresByProperty &failuresByProperty); |
64 | }; |
65 | |
66 | |
67 | tst_PropertyRequirements::tst_PropertyRequirements() |
68 | { |
69 | } |
70 | |
71 | void tst_PropertyRequirements::constantOrNotifyableMain() |
72 | { |
73 | FailuresByProperty failuresByProperty; |
74 | |
75 | // test |
76 | testAllQmlTypes(testDepth: MainTypeOnly, failuresByProperty); |
77 | |
78 | // report |
79 | QHash<QString, QStringList> occurrences; |
80 | for (auto it = failuresByProperty.constBegin(); it != failuresByProperty.constEnd(); ++it) { |
81 | |
82 | occurrences[it.value().at(i: 0).qmlTypeName()].append(t: it.key()); |
83 | } |
84 | |
85 | QStringList messages; |
86 | for (auto it = occurrences.constBegin(); it != occurrences.constEnd(); ++it) { |
87 | const QString occurrencePattern("%1:\n\t%2" ); |
88 | |
89 | QStringList properties = it.value(); |
90 | properties.sort(); |
91 | messages.append(t: occurrencePattern.arg(args: it.key(), args: properties.join(sep: "\n\t" ))); |
92 | } |
93 | messages.sort(); |
94 | |
95 | const QString message("\nThe following QML Types have properties which are neither CONSTANT nor NOTIFYable:\n" ); |
96 | QWARN(qPrintable(message + messages.join("\n" ))); |
97 | |
98 | // TODO enable once technical debt is fixes |
99 | // QCOMPARE(failuresByProperty.count(), 0); |
100 | } |
101 | |
102 | void tst_PropertyRequirements::constantOrNotifyableFull() |
103 | { |
104 | FailuresByProperty failuresByProperty; |
105 | |
106 | // test |
107 | testAllQmlTypes(testDepth: WithSuperClasses, failuresByProperty); |
108 | |
109 | // report |
110 | QStringList messages; |
111 | for (auto it = failuresByProperty.constBegin(); it != failuresByProperty.constEnd(); ++it) { |
112 | |
113 | QSet<QString> occurrences; |
114 | for (const QQmlType &qmlType : it.value()) { |
115 | static const QString occurrencePattern("%1 (%2)" ); |
116 | |
117 | occurrences.insert(value: occurrencePattern.arg(args: qmlType.metaObject()->className(), |
118 | args: qmlType.qmlTypeName())); |
119 | |
120 | } |
121 | |
122 | static const QString messagePattern("\nProperty %1 neither CONSTANT nor NOTIFYable. Affected types:\n\t%2" ); |
123 | QStringList occurrencesList = occurrences.values(); |
124 | occurrencesList.sort(); |
125 | messages.append(t: messagePattern.arg(args: it.key(), args: occurrencesList.join(sep: "\n\t" ))); |
126 | |
127 | } |
128 | messages.sort(); |
129 | |
130 | QWARN(qPrintable(messages.join("\n" ))); |
131 | |
132 | // TODO enable once technical debt is fixes |
133 | // QCOMPARE(failuresByProperty.count(), 0); |
134 | } |
135 | |
136 | void tst_PropertyRequirements::testAllQmlTypes(TestDepth testDepth, FailuresByProperty &failuresByProperty) |
137 | { |
138 | const QVector<QByteArray> qmlData { |
139 | "import QtQml 2.2\nQtObject {}" , |
140 | "import QtQml.Models 2.2\nListModel {}" , |
141 | "import QtQuick 2.5\nItem {}" , |
142 | "import QtQuick.Window 2.2\nWindow {}" , |
143 | "import QtQuick.Dialogs 1.2\nDialog {}" , |
144 | "import QtQuick.Layouts 1.2\nGridLayout {}" , |
145 | "import QtQuick.Controls 2.2\nButton {}" , |
146 | "import QtQuick.Templates 2.2\nButton {}" |
147 | }; |
148 | |
149 | QQmlEngine engine; |
150 | QSet<QString> seenTypes; |
151 | |
152 | for (const QByteArray &data : qmlData) { |
153 | QQmlComponent component(&engine); |
154 | component.setData(data, baseUrl: QUrl()); |
155 | |
156 | for (const QQmlType &qmlType : QQmlMetaType::qmlTypes()) { |
157 | if (!seenTypes.contains(value: qmlType.qmlTypeName())) { |
158 | testQmlType(testDepth, qmlType, failuresByProperty); |
159 | } |
160 | } |
161 | const auto &typeNameList = QQmlMetaType::qmlTypeNames(); |
162 | seenTypes.unite(other: QSet<QString>(typeNameList.cbegin(), typeNameList.cend())); |
163 | } |
164 | } |
165 | |
166 | void tst_PropertyRequirements::testQmlType(TestDepth testDepth, const QQmlType &qmlType, FailuresByProperty &failuresByProperty) |
167 | { |
168 | QList<const QMetaObject*> inheritanceHierarchy; |
169 | |
170 | const QMetaObject *mo = qmlType.metaObject(); |
171 | while (mo) { |
172 | inheritanceHierarchy.prepend(t: mo); |
173 | mo = mo->superClass(); |
174 | } |
175 | |
176 | // check if this type is derived from QObject and even can have signals |
177 | // i.e. weed out the Q_GADGET classes |
178 | if (inheritanceHierarchy.isEmpty() |
179 | || inheritanceHierarchy.constFirst()->className() != QByteArrayLiteral("QObject" )) { |
180 | return; |
181 | } |
182 | |
183 | if (testDepth == MainTypeOnly) { |
184 | inheritanceHierarchy.clear(); |
185 | inheritanceHierarchy.append(t: qmlType.metaObject()); |
186 | } |
187 | |
188 | for (const QMetaObject *metaClass : qAsConst(t&: inheritanceHierarchy)) { |
189 | for (int idx = metaClass->propertyOffset(); idx < metaClass->propertyCount(); ++idx) { |
190 | const QMetaProperty property = metaClass->property(index: idx); |
191 | |
192 | // needs to be either CONSTANT or have a NOTIFY signal |
193 | if (!property.isConstant() && !property.hasNotifySignal()) { |
194 | static const QString fullNamePattern("%1::%2" ); |
195 | const QString fullPropertyName = fullNamePattern.arg(args: metaClass->className(), args: property.name()); |
196 | |
197 | failuresByProperty[fullPropertyName].append(t: qmlType); |
198 | } |
199 | } |
200 | } |
201 | } |
202 | |
203 | QTEST_MAIN(tst_PropertyRequirements) |
204 | |
205 | #include "tst_propertyrequirements.moc" |
206 | |