Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qtestblacklist.cpp
Go to the documentation of this file.
1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4#include <QtTest/qtestcase.h>
5#include <QtCore/qbytearray.h>
6#include <QtCore/qfile.h>
7#include <QtCore/qset.h>
8#include <QtCore/qcoreapplication.h>
9#include <QtCore/qvariant.h>
10#include <QtCore/QSysInfo>
11#include <QtCore/QOperatingSystemVersion>
12
13#include <set>
14
15QT_BEGIN_NAMESPACE
16
17using namespace Qt::StringLiterals;
18
19/*
20 The BLACKLIST file format is a grouped listing of keywords.
21
22 Blank lines and everything after # is simply ignored. An initial #-line
23 referring to this documentation is kind to readers. Comments can also be used
24 to indicate the reasons for ignoring particular cases. Please scope comments
25 to keywords if possible, to avoid confusion when additional keywords or tests
26 cases are added later.
27
28 Each blacklist line is interpreted as a list of keywords in an AND-relationship.
29 To blacklist a test for multiple platforms (OR-relationship), use separate lines.
30
31 The key "ci" applies only when run by COIN. Other keys name platforms, operating systems,
32 distributions, tool-chains or architectures; a ! prefix reverses what it
33 checks. A version, joined to a key (at present, only for distributions and
34 for msvc) with a hyphen, limits the key to the specific version. A keyword
35 line matches if every key on it applies to the present run. Successive lines
36 are alternate conditions for ignoring a test.
37
38 Ungrouped lines at the beginning of a file apply to the whole testcase. A
39 group starts with a [square-bracketed] identification of a test function to
40 ignore. For data-driven tests, this identification can be narrowed by the
41 inclusion of global and local data row tags, separated from the function name
42 and each other by colons. If both global and function-specific data rows tags
43 are supplied, the global one comes first (as in the tag reported in test
44 output, albeit in parentheses after the function name). Even when a test does
45 have global and local data tags, you can omit either or both. (If a global
46 data row's name coincides with that of a local data row, some unintended
47 matches may result; try to keep your data-row tags distinct.)
48
49 Subsequent lines give conditions for ignoring this test. You need at least
50 one or the group has no effect.
51
52 # See qtbase/src/testlib/qtestblacklist.cpp for format
53 # Test doesn't work on QNX at all
54 qnx
55
56 [testFunction]
57 linux # QTBUG-12345
58 windows 64bit # QTBUG-12345
59
60 [testSlowly]
61 macos ci # Flaky in COIN on macOS, not reproducible by developers
62
63 [testfunction2:testData]
64 msvc-2010 # Needs basic C++11 support
65
66 [getFile:withProxy SSL:localhost]
67 android
68
69 QML test functions are identified using the following format:
70
71 <TestCase name>::<function name>:<data tag>
72
73 For example, to blacklist a QML test on RHEL 7.6:
74
75 [Button::test_display:TextOnly]
76 ci rhel-7.6 # QTBUG-12345
77
78 Keys are lower-case. Distribution name and version are supported if
79 QSysInfo's productType() and productVersion() return them.
80
81 Keys can be added via the space-separated QTEST_ENVIRONMENT
82 environment variable:
83
84 QTEST_ENVIRONMENT=ci ./tst_stuff
85
86 This can be used to "mock" a test environment. In the example above,
87 we add "ci" to the list of keys for the test environment, making it
88 possible to test BLACKLIST files that blacklist tests in a CI environment.
89
90 The other known keys are listed below:
91*/
92
94{
95 // this list can be extended with new keywords as required
96 QSet<QByteArray> set = QSet<QByteArray>()
97 << "*"
98#ifdef Q_OS_LINUX
99 << "linux"
100#endif
101#ifdef Q_OS_MACOS
102 << "osx"
103 << "macos"
104#endif
105#if defined(Q_OS_WIN)
106 << "windows"
107#endif
108#ifdef Q_OS_IOS
109 << "ios"
110#endif
111#ifdef Q_OS_TVOS
112 << "tvos"
113#endif
114#ifdef Q_OS_WATCHOS
115 << "watchos"
116#endif
117#ifdef Q_OS_VISIONOS
118 << "visionos"
119#endif
120#ifdef Q_OS_ANDROID
121 << "android"
122#endif
123#ifdef Q_OS_QNX
124 << "qnx"
125#endif
126#ifdef Q_OS_VXWORKS
127 << "vxworks"
128#endif
129#ifdef Q_OS_WEBOS
130 << "webos"
131#endif
132#ifdef Q_OS_HARMONY
133 << "harmonyos"
134#endif
135
136#if QT_POINTER_SIZE == 8
137 << "64bit"
138#else
139 << "32bit"
140#endif
141
142#ifdef Q_CC_GNU
143 << "gcc"
144#endif
145#ifdef Q_CC_CLANG
146 << "clang"
147#endif
148#ifdef Q_CC_MSVC
149 << "msvc"
150# if _MSC_VER <= 1600
151 << "msvc-2010"
152# elif _MSC_VER <= 1700
153 << "msvc-2012"
154# elif _MSC_VER <= 1800
155 << "msvc-2013"
156# elif _MSC_VER <= 1900
157 << "msvc-2015"
158# elif _MSC_VER <= 1916
159 << "msvc-2017"
160# elif _MSC_VER <= 1929
161 << "msvc-2019"
162# elif _MSC_VER <= 1949
163 << "msvc-2022"
164# else
165 << "msvc-2026"
166# endif
167#endif
168
169#ifdef Q_PROCESSOR_X86
170 << "x86"
171#endif
172#ifdef Q_PROCESSOR_ARM
173 << "arm"
174#endif
175
176#ifdef QT_BUILD_INTERNAL
177 << "developer-build"
178#endif
179 ;
180
181 QCoreApplication *app = QCoreApplication::instance();
182 if (app) {
183 const QVariant platformName = app->property("platformName");
184 if (platformName.isValid())
185 set << platformName.toByteArray();
186 }
187
188 return set;
189}
190
192{
193 QSet<QByteArray> result = keywords();
194
195 QByteArray distributionName = QSysInfo::productType().toLower().toUtf8();
196 QByteArray distributionRelease = QSysInfo::productVersion().toLower().toUtf8();
197 if (!distributionName.isEmpty()) {
198 if (result.find(distributionName) == result.end())
199 result.insert(distributionName);
200 // backwards compatibility with Qt 5
201 if (distributionName == "macos") {
202 if (result.find(distributionName) == result.end())
203 result.insert("osx");
204 const auto version = QOperatingSystemVersion::current();
205 if (version.majorVersion() >= 11)
206 distributionRelease = QByteArray::number(version.majorVersion());
207 }
208 if (!distributionRelease.isEmpty()) {
209 QByteArray versioned = distributionName + "-" + distributionRelease;
210 if (result.find(versioned) == result.end())
211 result.insert(versioned);
212 if (distributionName == "macos") {
213 QByteArray versioned = "osx-" + distributionRelease;
214 if (result.find(versioned) == result.end())
215 result.insert(versioned);
216 }
217 }
218 }
219
220 if (qEnvironmentVariableIsSet("QTEST_ENVIRONMENT")) {
221 for (const QByteArray &k : qgetenv("QTEST_ENVIRONMENT").split(' '))
222 result.insert(k);
223 }
224
225 return result;
226}
227
228static bool checkCondition(const QByteArray &condition)
229{
230 static const QSet<QByteArray> matchedConditions = activeConditions();
231 QList<QByteArray> conds = condition.split(' ');
232
233 for (QByteArray c : conds) {
234 bool result = c.startsWith('!');
235 if (result)
236 c.remove(0, 1);
237
238 result ^= matchedConditions.contains(c);
239 if (!result)
240 return false;
241 }
242 return true;
243}
244
245static bool ignoreAll = false;
247
248namespace QTestPrivate {
249
251{
252 return activeConditions();
253}
254
256{
257 const QString filename = QTest::qFindTestData(QStringLiteral("BLACKLIST"));
258 if (filename.isEmpty())
259 return;
260 QFile ignored(filename);
261 if (!ignored.open(QIODevice::ReadOnly))
262 return;
263
264 QByteArray function;
265 QByteArray line;
266
267 while (ignored.readLineInto(&line)) {
268 const int commentPosition = line.indexOf('#');
269 if (commentPosition >= 0)
270 line.truncate(commentPosition);
271 line = line.simplified();
272 if (line.isEmpty())
273 continue;
274 if (line.startsWith('[')) {
275 function = line.mid(1, line.size() - 2);
276 continue;
277 }
278 const bool condition = checkCondition(line);
279 if (condition) {
280 if (!function.size()) {
281 ignoreAll = true;
282 } else {
283 if (!ignoredTests)
284 ignoredTests = std::make_unique<std::set<QByteArray>>();
285 ignoredTests->insert(function);
286 }
287 }
288 }
289}
290
291// Returns \c true if this test-case is blacklisted.
292bool checkBlackLists(const char *slot, const char *data, const char *global)
293{
294 bool ignore = ignoreAll;
295
296 if (!ignore && ignoredTests) {
297 QByteArray s = slot;
298 ignore = ignoredTests->find(s) != ignoredTests->end();
299 if (!ignore && data) {
300 s = (s + ':') + data;
301 ignore = ignoredTests->find(s) != ignoredTests->end();
302 }
303
304 if (!ignore && global) {
305 s = slot + ":"_ba + global;
306 ignore = ignoredTests->find(s) != ignoredTests->end();
307 if (!ignore && data) {
308 s = (s + ':') + data;
309 ignore = ignoredTests->find(s) != ignoredTests->end();
310 }
311 }
312 }
313
314 return ignore;
315}
316
317} // QTestPrivate
318
319QT_END_NAMESPACE
QSet< QByteArray > blacklistKeywords()
bool checkBlackLists(const char *slot, const char *data, const char *global)
static std::unique_ptr< std::set< QByteArray > > ignoredTests
static QSet< QByteArray > keywords()
static bool ignoreAll
static bool checkCondition(const QByteArray &condition)
static QSet< QByteArray > activeConditions()