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
133#if QT_POINTER_SIZE == 8
134 << "64bit"
135#else
136 << "32bit"
137#endif
138
139#ifdef Q_CC_GNU
140 << "gcc"
141#endif
142#ifdef Q_CC_CLANG
143 << "clang"
144#endif
145#ifdef Q_CC_MSVC
146 << "msvc"
147# if _MSC_VER <= 1600
148 << "msvc-2010"
149# elif _MSC_VER <= 1700
150 << "msvc-2012"
151# elif _MSC_VER <= 1800
152 << "msvc-2013"
153# elif _MSC_VER <= 1900
154 << "msvc-2015"
155# elif _MSC_VER <= 1916
156 << "msvc-2017"
157# elif _MSC_VER <= 1929
158 << "msvc-2019"
159# elif _MSC_VER <= 1949
160 << "msvc-2022"
161# else
162 << "msvc-2026"
163# endif
164#endif
165
166#ifdef Q_PROCESSOR_X86
167 << "x86"
168#endif
169#ifdef Q_PROCESSOR_ARM
170 << "arm"
171#endif
172
173#ifdef QT_BUILD_INTERNAL
174 << "developer-build"
175#endif
176 ;
177
178 QCoreApplication *app = QCoreApplication::instance();
179 if (app) {
180 const QVariant platformName = app->property("platformName");
181 if (platformName.isValid())
182 set << platformName.toByteArray();
183 }
184
185 return set;
186}
187
189{
190 QSet<QByteArray> result = keywords();
191
192 QByteArray distributionName = QSysInfo::productType().toLower().toUtf8();
193 QByteArray distributionRelease = QSysInfo::productVersion().toLower().toUtf8();
194 if (!distributionName.isEmpty()) {
195 if (result.find(distributionName) == result.end())
196 result.insert(distributionName);
197 // backwards compatibility with Qt 5
198 if (distributionName == "macos") {
199 if (result.find(distributionName) == result.end())
200 result.insert("osx");
201 const auto version = QOperatingSystemVersion::current();
202 if (version.majorVersion() >= 11)
203 distributionRelease = QByteArray::number(version.majorVersion());
204 }
205 if (!distributionRelease.isEmpty()) {
206 QByteArray versioned = distributionName + "-" + distributionRelease;
207 if (result.find(versioned) == result.end())
208 result.insert(versioned);
209 if (distributionName == "macos") {
210 QByteArray versioned = "osx-" + distributionRelease;
211 if (result.find(versioned) == result.end())
212 result.insert(versioned);
213 }
214 }
215 }
216
217 if (qEnvironmentVariableIsSet("QTEST_ENVIRONMENT")) {
218 for (const QByteArray &k : qgetenv("QTEST_ENVIRONMENT").split(' '))
219 result.insert(k);
220 }
221
222 return result;
223}
224
225static bool checkCondition(const QByteArray &condition)
226{
227 static const QSet<QByteArray> matchedConditions = activeConditions();
228 QList<QByteArray> conds = condition.split(' ');
229
230 for (QByteArray c : conds) {
231 bool result = c.startsWith('!');
232 if (result)
233 c.remove(0, 1);
234
235 result ^= matchedConditions.contains(c);
236 if (!result)
237 return false;
238 }
239 return true;
240}
241
242static bool ignoreAll = false;
243static std::set<QByteArray> *ignoredTests = nullptr;
244
245namespace QTestPrivate {
246
248{
249 return activeConditions();
250}
251
253{
254 const QString filename = QTest::qFindTestData(QStringLiteral("BLACKLIST"));
255 if (filename.isEmpty())
256 return;
257 QFile ignored(filename);
258 if (!ignored.open(QIODevice::ReadOnly))
259 return;
260
261 QByteArray function;
262 QByteArray line;
263
264 while (ignored.readLineInto(&line)) {
265 const int commentPosition = line.indexOf('#');
266 if (commentPosition >= 0)
267 line.truncate(commentPosition);
268 line = line.simplified();
269 if (line.isEmpty())
270 continue;
271 if (line.startsWith('[')) {
272 function = line.mid(1, line.size() - 2);
273 continue;
274 }
275 const bool condition = checkCondition(line);
276 if (condition) {
277 if (!function.size()) {
278 ignoreAll = true;
279 } else {
280 if (!ignoredTests)
281 ignoredTests = new std::set<QByteArray>;
282 ignoredTests->insert(function);
283 }
284 }
285 }
286}
287
288// Returns \c true if this test-case is blacklisted.
289bool checkBlackLists(const char *slot, const char *data, const char *global)
290{
291 bool ignore = ignoreAll;
292
293 if (!ignore && ignoredTests) {
294 QByteArray s = slot;
295 ignore = ignoredTests->find(s) != ignoredTests->end();
296 if (!ignore && data) {
297 s = (s + ':') + data;
298 ignore = ignoredTests->find(s) != ignoredTests->end();
299 }
300
301 if (!ignore && global) {
302 s = slot + ":"_ba + global;
303 ignore = ignoredTests->find(s) != ignoredTests->end();
304 if (!ignore && data) {
305 s = (s + ':') + data;
306 ignore = ignoredTests->find(s) != ignoredTests->end();
307 }
308 }
309 }
310
311 return ignore;
312}
313
314} // QTestPrivate
315
316QT_END_NAMESPACE
QSet< QByteArray > blacklistKeywords()
bool checkBlackLists(const char *slot, const char *data, const char *global)
static std::set< QByteArray > * ignoredTests
static QSet< QByteArray > keywords()
static bool ignoreAll
static bool checkCondition(const QByteArray &condition)
static QSet< QByteArray > activeConditions()