1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2018 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtTest module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
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 Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include "qtaptestlogger_p.h" |
41 | |
42 | #include "qtestlog_p.h" |
43 | #include "qtestresult_p.h" |
44 | #include "qtestassert.h" |
45 | |
46 | #if QT_CONFIG(regularexpression) |
47 | # include <QtCore/qregularexpression.h> |
48 | #endif |
49 | |
50 | QT_BEGIN_NAMESPACE |
51 | |
52 | QTapTestLogger::QTapTestLogger(const char *filename) |
53 | : QAbstractTestLogger(filename) |
54 | , m_wasExpectedFail(false) |
55 | { |
56 | } |
57 | |
58 | QTapTestLogger::~QTapTestLogger() = default; |
59 | |
60 | void QTapTestLogger::startLogging() |
61 | { |
62 | QAbstractTestLogger::startLogging(); |
63 | |
64 | QTestCharBuffer preamble; |
65 | QTest::qt_asprintf(buf: &preamble, format: "TAP version 13\n" |
66 | // By convention, test suite names are output as diagnostics lines |
67 | // This is a pretty poor convention, as consumers will then treat |
68 | // actual diagnostics, e.g. qDebug, as test suite names o_O |
69 | "# %s\n" , QTestResult::currentTestObjectName()); |
70 | outputString(msg: preamble.data()); |
71 | } |
72 | |
73 | void QTapTestLogger::stopLogging() |
74 | { |
75 | const int total = QTestLog::totalCount(); |
76 | |
77 | QTestCharBuffer testPlanAndStats; |
78 | QTest::qt_asprintf(buf: &testPlanAndStats, |
79 | format: "1..%d\n" |
80 | "# tests %d\n" |
81 | "# pass %d\n" |
82 | "# fail %d\n" , |
83 | total, total, QTestLog::passCount(), QTestLog::failCount()); |
84 | outputString(msg: testPlanAndStats.data()); |
85 | |
86 | QAbstractTestLogger::stopLogging(); |
87 | } |
88 | |
89 | void QTapTestLogger::enterTestFunction(const char *function) |
90 | { |
91 | Q_UNUSED(function); |
92 | m_wasExpectedFail = false; |
93 | } |
94 | |
95 | void QTapTestLogger::enterTestData(QTestData *data) |
96 | { |
97 | Q_UNUSED(data); |
98 | m_wasExpectedFail = false; |
99 | } |
100 | |
101 | using namespace QTestPrivate; |
102 | |
103 | void QTapTestLogger::outputTestLine(bool ok, int testNumber, QTestCharBuffer &directive) |
104 | { |
105 | QTestCharBuffer testIdentifier; |
106 | QTestPrivate::generateTestIdentifier(identifier: &testIdentifier, parts: TestFunction | TestDataTag); |
107 | |
108 | QTestCharBuffer testLine; |
109 | QTest::qt_asprintf(buf: &testLine, format: "%s %d - %s%s\n" , |
110 | ok ? "ok" : "not ok" , testNumber, testIdentifier.data(), directive.data()); |
111 | |
112 | outputString(msg: testLine.data()); |
113 | } |
114 | |
115 | void QTapTestLogger::addIncident(IncidentTypes type, const char *description, |
116 | const char *file, int line) |
117 | { |
118 | if (m_wasExpectedFail && type == Pass) { |
119 | // XFail comes with a corresponding Pass incident, but we only want |
120 | // to emit a single test point for it, so skip the this pass. |
121 | return; |
122 | } |
123 | |
124 | bool ok = type == Pass || type == XPass || type == BlacklistedPass || type == BlacklistedXPass; |
125 | |
126 | QTestCharBuffer directive; |
127 | if (type == XFail || type == XPass || type == BlacklistedFail || type == BlacklistedPass |
128 | || type == BlacklistedXFail || type == BlacklistedXPass) { |
129 | // We treat expected or blacklisted failures/passes as TODO-failures/passes, |
130 | // which should be treated as soft issues by consumers. Not all do though :/ |
131 | QTest::qt_asprintf(buf: &directive, format: " # TODO %s" , description); |
132 | } |
133 | |
134 | int testNumber = QTestLog::totalCount(); |
135 | if (type == XFail) { |
136 | // The global test counter hasn't been updated yet for XFail |
137 | testNumber += 1; |
138 | } |
139 | |
140 | outputTestLine(ok, testNumber, directive); |
141 | |
142 | if (!ok) { |
143 | // All failures need a diagnostics sections to not confuse consumers |
144 | |
145 | // The indent needs to be two spaces for maximum compatibility |
146 | #define YAML_INDENT " " |
147 | |
148 | outputString(YAML_INDENT "---\n" ); |
149 | |
150 | if (type != XFail) { |
151 | #if QT_CONFIG(regularexpression) |
152 | // This is fragile, but unfortunately testlib doesn't plumb |
153 | // the expected and actual values to the loggers (yet). |
154 | static QRegularExpression verifyRegex( |
155 | QLatin1String("^'(?<actualexpression>.*)' returned (?<actual>\\w+).+\\((?<message>.*)\\)$" )); |
156 | |
157 | static QRegularExpression comparRegex( |
158 | QLatin1String("^(?<message>.*)\n" |
159 | "\\s*Actual\\s+\\((?<actualexpression>.*)\\)\\s*: (?<actual>.*)\n" |
160 | "\\s*Expected\\s+\\((?<expectedexpresssion>.*)\\)\\s*: (?<expected>.*)$" )); |
161 | |
162 | QString descriptionString = QString::fromUtf8(str: description); |
163 | QRegularExpressionMatch match = verifyRegex.match(subject: descriptionString); |
164 | if (!match.hasMatch()) |
165 | match = comparRegex.match(subject: descriptionString); |
166 | |
167 | if (match.hasMatch()) { |
168 | bool isVerify = match.regularExpression() == verifyRegex; |
169 | QString message = match.captured(name: QLatin1String("message" )); |
170 | QString expected; |
171 | QString actual; |
172 | |
173 | if (isVerify) { |
174 | QString expression = QLatin1String(" (" ) |
175 | % match.captured(name: QLatin1String("actualexpression" )) % QLatin1Char(')') ; |
176 | actual = match.captured(name: QLatin1String("actual" )).toLower() % expression; |
177 | expected = (actual.startsWith(s: QLatin1String("true" )) ? QLatin1String("false" ) : QLatin1String("true" )) % expression; |
178 | if (message.isEmpty()) |
179 | message = QLatin1String("Verification failed" ); |
180 | } else { |
181 | expected = match.captured(name: QLatin1String("expected" )) |
182 | % QLatin1String(" (" ) % match.captured(name: QLatin1String("expectedexpresssion" )) % QLatin1Char(')'); |
183 | actual = match.captured(name: QLatin1String("actual" )) |
184 | % QLatin1String(" (" ) % match.captured(name: QLatin1String("actualexpression" )) % QLatin1Char(')'); |
185 | } |
186 | |
187 | QTestCharBuffer diagnosticsYamlish; |
188 | QTest::qt_asprintf(buf: &diagnosticsYamlish, |
189 | YAML_INDENT "type: %s\n" |
190 | YAML_INDENT "message: %s\n" |
191 | |
192 | // Some consumers understand 'wanted/found', while others need |
193 | // 'expected/actual', so we do both for maximum compatibility. |
194 | YAML_INDENT "wanted: %s\n" |
195 | YAML_INDENT "found: %s\n" |
196 | YAML_INDENT "expected: %s\n" |
197 | YAML_INDENT "actual: %s\n" , |
198 | |
199 | isVerify ? "QVERIFY" : "QCOMPARE" , |
200 | qPrintable(message), |
201 | qPrintable(expected), qPrintable(actual), |
202 | qPrintable(expected), qPrintable(actual) |
203 | ); |
204 | |
205 | outputString(msg: diagnosticsYamlish.data()); |
206 | } else { |
207 | QTestCharBuffer unparsableDescription; |
208 | QTest::qt_asprintf(buf: &unparsableDescription, |
209 | YAML_INDENT "# %s\n" , description); |
210 | outputString(msg: unparsableDescription.data()); |
211 | } |
212 | #else |
213 | QTestCharBuffer unparsableDescription; |
214 | QTest::qt_asprintf(&unparsableDescription, |
215 | YAML_INDENT "# %s\n" , description); |
216 | outputString(unparsableDescription.data()); |
217 | #endif |
218 | } |
219 | |
220 | if (file) { |
221 | QTestCharBuffer location; |
222 | QTest::qt_asprintf(buf: &location, |
223 | // The generic 'at' key is understood by most consumers. |
224 | YAML_INDENT "at: %s::%s() (%s:%d)\n" |
225 | |
226 | // The file and line keys are for consumers that are able |
227 | // to read more granular location info. |
228 | YAML_INDENT "file: %s\n" |
229 | YAML_INDENT "line: %d\n" , |
230 | |
231 | QTestResult::currentTestObjectName(), |
232 | QTestResult::currentTestFunction(), |
233 | file, line, file, line |
234 | ); |
235 | outputString(msg: location.data()); |
236 | } |
237 | |
238 | outputString(YAML_INDENT "...\n" ); |
239 | } |
240 | |
241 | m_wasExpectedFail = type == XFail; |
242 | } |
243 | |
244 | void QTapTestLogger::addMessage(MessageTypes type, const QString &message, |
245 | const char *file, int line) |
246 | { |
247 | Q_UNUSED(file); |
248 | Q_UNUSED(line); |
249 | |
250 | if (type == Skip) { |
251 | QTestCharBuffer directive; |
252 | QTest::qt_asprintf(buf: &directive, format: " # SKIP %s" , message.toUtf8().constData()); |
253 | outputTestLine(/* ok = */ true, testNumber: QTestLog::totalCount(), directive); |
254 | return; |
255 | } |
256 | |
257 | QTestCharBuffer diagnostics; |
258 | QTest::qt_asprintf(buf: &diagnostics, format: "# %s\n" , qPrintable(message)); |
259 | outputString(msg: diagnostics.data()); |
260 | } |
261 | |
262 | QT_END_NAMESPACE |
263 | |
264 | |