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 <QBuffer> |
30 | #include <QUrl> |
31 | #include <QXmlAttributes> |
32 | #include <QXmlQuery> |
33 | #include <QXmlResultItems> |
34 | #include <QXmlSerializer> |
35 | #include <private/qxmlquery_p.h> |
36 | #include <algorithm> |
37 | |
38 | #include "DebugExpressionFactory.h" |
39 | #include "ExternalSourceLoader.h" |
40 | #include "Global.h" |
41 | #include "TestSuite.h" |
42 | #include "XMLWriter.h" |
43 | |
44 | #include "TestCase.h" |
45 | |
46 | using namespace QPatternistSDK; |
47 | using namespace QPatternist; |
48 | |
49 | // STATIC DATA |
50 | static const DebugExpressionFactory::Ptr s_exprFact(new DebugExpressionFactory()); |
51 | |
52 | TestCase::TestCase() : m_result(0) |
53 | { |
54 | } |
55 | |
56 | TestCase::~TestCase() |
57 | { |
58 | delete m_result; |
59 | } |
60 | |
61 | static bool lessThan(const char *a, const char *b) |
62 | { |
63 | return qstrcmp(str1: a, str2: b) < 0; |
64 | } |
65 | |
66 | TestResult::List TestCase::execute(const ExecutionStage stage, |
67 | TestSuite *) |
68 | { |
69 | ++TestCase::executions; |
70 | |
71 | if ((TestCase::executions < TestCase::executeRange.first) || (TestCase::executions > TestCase::executeRange.second)) { |
72 | qDebug(msg: "Skipping test case #%6d" , TestCase::executions); |
73 | return TestResult::List(); |
74 | } |
75 | |
76 | const QByteArray nm = name().toLatin1(); |
77 | |
78 | if(name() == QLatin1String("Constr-cont-document-3" )) |
79 | { |
80 | TestResult::List result; |
81 | result.append(t: createTestResult(status: TestResult::Fail, comment: QLatin1String("Skipped this test, because we loop infinitely on it." ))); |
82 | return result; |
83 | } |
84 | else if(name() == QLatin1String("Axes089" )) |
85 | { |
86 | TestResult::List result; |
87 | result.append(t: createTestResult(status: TestResult::Fail, comment: QLatin1String("Skipped this test, we crash on it." ))); |
88 | return result; |
89 | } |
90 | else if (name() == QLatin1String("op-numeric-unary-minus-1" )) |
91 | { |
92 | TestResult::List result; |
93 | result.append(t: createTestResult(status: TestResult::Fail, comment: QLatin1String("Skipped this test, we crash on it." ))); |
94 | return result; |
95 | } |
96 | else if (name() == QLatin1String("emptyorderdecl-13" )) |
97 | { |
98 | TestResult::List result; |
99 | result.append(t: createTestResult(status: TestResult::Fail, comment: QLatin1String("Skipped this test, we crash on it." ))); |
100 | return result; |
101 | } |
102 | else if (name() == QLatin1String("emptyorderdecl-21" )) |
103 | { |
104 | TestResult::List result; |
105 | result.append(t: createTestResult(status: TestResult::Fail, comment: QLatin1String("Skipped this test, we crash on it." ))); |
106 | return result; |
107 | } |
108 | else { |
109 | // Should be sorted in the order that std::binary_search expects |
110 | static const char *crashes[] = {"Constr-attr-content-4" , |
111 | "K2-DirectConElem-12" , |
112 | "K2-DirectConElem-50" , |
113 | "K2-DirectConElemAttr-10" , |
114 | "K2-DirectConElemAttr-18" , |
115 | "K2-DirectConElemAttr-19" , |
116 | "K2-DirectConElemAttr-20" , |
117 | "K2-DirectConElemAttr-21" |
118 | }; |
119 | |
120 | const bool skip = std::binary_search(first: &crashes[0], last: &crashes[sizeof(crashes)/sizeof(crashes[0])], val: nm.constData(), comp: lessThan); |
121 | if (skip) { |
122 | TestResult::List result; |
123 | result.append(t: createTestResult(status: TestResult::Fail, comment: QLatin1String("Skipped this test, we crash on it." ))); |
124 | return result; |
125 | } |
126 | } |
127 | |
128 | |
129 | qDebug(msg: "Running test case #%6d: %s" , TestCase::executions, nm.constData()); |
130 | return execute(stage); |
131 | |
132 | Q_ASSERT(false); |
133 | return TestResult::List(); |
134 | } |
135 | |
136 | TestResult *TestCase::createTestResult(const TestResult::Status status, |
137 | const QString &) const |
138 | { |
139 | TestResult *const result = new TestResult(name(), |
140 | status, |
141 | 0 /* We don't have an AST. */, |
142 | ErrorHandler::Message::List(), |
143 | QPatternist::Item::List(), |
144 | QString()); |
145 | result->setComment(comment); |
146 | return result; |
147 | } |
148 | |
149 | TestResult::List TestCase::execute(const ExecutionStage stage) |
150 | { |
151 | ErrorHandler errHandler; |
152 | ErrorHandler::installQtMessageHandler(handler: &errHandler); |
153 | |
154 | pDebug() << "TestCase::execute()" ; |
155 | delete m_result; |
156 | |
157 | QXmlQuery query(language(), Global::namePoolAsPublic()); |
158 | |
159 | query.d->setExpressionFactory(s_exprFact); |
160 | query.setInitialTemplateName(initialTemplateName()); |
161 | |
162 | QXmlQuery openDoc(query.namePool()); |
163 | |
164 | if(contextItemSource().isValid()) |
165 | { |
166 | openDoc.setQuery(sourceCode: QString::fromLatin1(str: "doc('" ) + contextItemSource().toString() + QLatin1String("')" )); |
167 | Q_ASSERT(openDoc.isValid()); |
168 | QXmlResultItems result; |
169 | |
170 | openDoc.evaluateTo(result: &result); |
171 | const QXmlItem item(result.next()); |
172 | Q_ASSERT(!item.isNull()); |
173 | query.setFocus(item); |
174 | } |
175 | |
176 | TestResult::List retval; |
177 | |
178 | const Scenario scen(scenario()); |
179 | TestResult::Status resultStatus = TestResult::Unknown; |
180 | |
181 | bool ok = false; |
182 | const QString queryString(sourceCode(ok)); |
183 | |
184 | if(!ok) |
185 | { |
186 | /* Loading the query file failed, or similar. */ |
187 | resultStatus = TestResult::Fail; |
188 | |
189 | m_result = new TestResult(name(), resultStatus, s_exprFact->astTree(), |
190 | errHandler.messages(), QPatternist::Item::List(), QString()); |
191 | retval.append(t: m_result); |
192 | ErrorHandler::installQtMessageHandler(handler: 0); |
193 | changed(item: this); |
194 | return retval; |
195 | } |
196 | |
197 | query.setMessageHandler(&errHandler); |
198 | QXmlNamePool namePool(query.namePool()); |
199 | |
200 | /* Bind variables. */ |
201 | QPatternist::ExternalVariableLoader::Ptr loader(externalVariableLoader()); |
202 | if(loader) |
203 | { |
204 | Q_ASSERT(loader); |
205 | const ExternalSourceLoader::VariableMap vMap(static_cast<const ExternalSourceLoader *>(loader.data())->variableMap()); |
206 | const QStringList variables(vMap.keys()); |
207 | |
208 | for(int i = 0; i < variables.count(); ++i) |
209 | { |
210 | const QXmlName name(namePool, variables.at(i)); |
211 | const QXmlItem val(QPatternist::Item::toPublic(i: loader->evaluateSingleton(name, context: QPatternist::DynamicContext::Ptr()))); |
212 | query.bindVariable(name, value: val); |
213 | } |
214 | } |
215 | |
216 | /* We pass in the testCasePath(), such that the base URI is correct fort |
217 | * XSL-T stylesheets. */ |
218 | query.setQuery(sourceCode: queryString, documentURI: testCasePath()); |
219 | |
220 | if(!query.isValid()) |
221 | { |
222 | pDebug() << "Got compilation exception." ; |
223 | resultStatus = TestBaseLine::scanErrors(errors: errHandler.messages(), lines: baseLines()); |
224 | |
225 | Q_ASSERT(resultStatus != TestResult::Unknown); |
226 | m_result = new TestResult(name(), resultStatus, s_exprFact->astTree(), |
227 | errHandler.messages(), QPatternist::Item::List(), QString()); |
228 | retval.append(t: m_result); |
229 | ErrorHandler::installQtMessageHandler(handler: 0); |
230 | changed(item: this); |
231 | return retval; |
232 | } |
233 | |
234 | if(stage == CompileOnly) |
235 | { |
236 | m_result = new TestResult(name(), TestResult::Fail, s_exprFact->astTree(), |
237 | errHandler.messages(), QPatternist::Item::List(), QString()); |
238 | retval.append(t: m_result); |
239 | return retval; |
240 | } |
241 | |
242 | Q_ASSERT(stage == CompileAndRun); |
243 | |
244 | if(scen == ParseError) /* We're supposed to have received an error |
245 | at this point. */ |
246 | { |
247 | m_result = new TestResult(name(), TestResult::Fail, s_exprFact->astTree(), |
248 | errHandler.messages(), QPatternist::Item::List(), QString()); |
249 | ErrorHandler::installQtMessageHandler(handler: 0); |
250 | retval.append(t: m_result); |
251 | changed(item: this); |
252 | return retval; |
253 | } |
254 | |
255 | QPatternist::Item::List itemList; |
256 | |
257 | QByteArray output; |
258 | QBuffer buffer(&output); |
259 | buffer.open(openMode: QIODevice::WriteOnly); |
260 | |
261 | QXmlSerializer serializer(query, &buffer); |
262 | |
263 | pDebug() << "-------------------------- evaluateToPushCallback() ---------------------------- " ; |
264 | const bool success = query.evaluateTo(callback: &serializer); |
265 | pDebug() << "------------------------------------------------------------------------------------ " ; |
266 | |
267 | buffer.close(); |
268 | |
269 | const QString serialized(QString::fromUtf8(str: output.constData(), size: output.size())); |
270 | |
271 | if(!success) |
272 | { |
273 | resultStatus = TestBaseLine::scanErrors(errors: errHandler.messages(), lines: baseLines()); |
274 | |
275 | Q_ASSERT(resultStatus != TestResult::Unknown); |
276 | m_result = new TestResult(name(), resultStatus, s_exprFact->astTree(), |
277 | errHandler.messages(), QPatternist::Item::List(), serialized); |
278 | retval.append(t: m_result); |
279 | ErrorHandler::installQtMessageHandler(handler: 0); |
280 | changed(item: this); |
281 | return retval; |
282 | } |
283 | |
284 | /* It's a regular test. */ |
285 | Q_ASSERT(scen == Standard || scen == RuntimeError); |
286 | |
287 | resultStatus = TestBaseLine::scan(serialized, lines: baseLines()); |
288 | Q_ASSERT(resultStatus != TestResult::Unknown); |
289 | |
290 | /* Check that errHandler()->messages() at most only contains |
291 | * warnings, since it shouldn't have errors at this point. */ |
292 | const ErrorHandler::Message::List errors (errHandler.messages()); |
293 | const ErrorHandler::Message::List::const_iterator end(errors.constEnd()); |
294 | ErrorHandler::Message::List::const_iterator it(errors.constBegin()); |
295 | |
296 | for(; it != end; ++it) |
297 | { |
298 | const QtMsgType type = (*it).type(); |
299 | if(type == QtFatalMsg) |
300 | { |
301 | m_result = new TestResult(name(), TestResult::Fail, s_exprFact->astTree(), |
302 | errHandler.messages(), itemList, serialized); |
303 | retval.append(t: m_result); |
304 | ErrorHandler::installQtMessageHandler(handler: 0); |
305 | changed(item: this); |
306 | return retval; |
307 | } |
308 | } |
309 | |
310 | m_result = new TestResult(name(), resultStatus, s_exprFact->astTree(), |
311 | errHandler.messages(), itemList, serialized); |
312 | retval.append(t: m_result); |
313 | ErrorHandler::installQtMessageHandler(handler: 0); |
314 | changed(item: this); |
315 | return retval; |
316 | } |
317 | |
318 | TestCase::Scenario TestCase::scenarioFromString(const QString &string) |
319 | { |
320 | if(string == QLatin1String("standard" )) |
321 | return Standard; |
322 | else if(string == QLatin1String("parse-error" )) |
323 | return ParseError; |
324 | else if(string == QLatin1String("runtime-error" )) |
325 | return RuntimeError; |
326 | else if(string == QLatin1String("trivial" )) |
327 | return Trivial; |
328 | else |
329 | { |
330 | Q_ASSERT_X(false, Q_FUNC_INFO, |
331 | qPrintable(QString::fromLatin1("Invalid string representation for the scenario-enum: %1" ).arg(string))); |
332 | return ParseError; /* Silence GCC. */ |
333 | } |
334 | } |
335 | |
336 | void TestCase::toXML(XMLWriter &receiver) const |
337 | { |
338 | /* <test-case> */ |
339 | QXmlStreamAttributes test_caseAtts; |
340 | test_caseAtts.append(qualifiedName: QLatin1String("is-XPath2" ), value: isXPath() ? QLatin1String("true" ) |
341 | : QLatin1String("false" )); |
342 | test_caseAtts.append(qualifiedName: QLatin1String("name" ), value: name()); |
343 | test_caseAtts.append(qualifiedName: QLatin1String("creator" ), value: creator()); |
344 | QString scen; |
345 | switch(scenario()) |
346 | { |
347 | case Standard: |
348 | { |
349 | scen = QLatin1String("standard" ); |
350 | break; |
351 | } |
352 | case ParseError: |
353 | { |
354 | scen = QLatin1String("parse-error" ); |
355 | break; |
356 | } |
357 | case RuntimeError: |
358 | { |
359 | scen = QLatin1String("runtime-error" ); |
360 | break; |
361 | } |
362 | case Trivial: |
363 | { |
364 | scen = QLatin1String("trivial" ); |
365 | break; |
366 | } |
367 | default: /* includes 'AnyError' */ |
368 | Q_ASSERT(false); |
369 | } |
370 | test_caseAtts.append(qualifiedName: QLatin1String("scenario" ), value: scen); |
371 | test_caseAtts.append(qualifiedName: QLatin1String("FilePath" ), value: QString()); |
372 | receiver.startElement(qName: QLatin1String("test-case" ), atts: test_caseAtts); |
373 | |
374 | /* <description> */ |
375 | receiver.startElement(qName: QLatin1String("description" ), atts: test_caseAtts); |
376 | receiver.characters(ch: description()); |
377 | |
378 | /* </description> */ |
379 | receiver.endElement(qName: QLatin1String("description" )); |
380 | |
381 | /* <query> */ |
382 | QXmlStreamAttributes queryAtts; |
383 | queryAtts.append(qualifiedName: QLatin1String("date" ), /* This date is a dummy. */ |
384 | value: QDate::currentDate().toString(format: Qt::ISODate)); |
385 | queryAtts.append(qualifiedName: QLatin1String("name" ), value: testCasePath().toString()); |
386 | receiver.startElement(qName: QLatin1String("query" ), atts: queryAtts); |
387 | |
388 | /* </query> */ |
389 | receiver.endElement(qName: QLatin1String("query" )); |
390 | |
391 | /* Note: this is invalid, we don't add spec-citation. */ |
392 | TestBaseLine::List bls(baseLines()); |
393 | const TestBaseLine::List::const_iterator end(bls.constEnd()); |
394 | TestBaseLine::List::const_iterator it(bls.constBegin()); |
395 | |
396 | for(; it != end; ++it) |
397 | (*it)->toXML(receiver); |
398 | |
399 | /* </test-case> */ |
400 | receiver.endElement(qName: QLatin1String("test-case" )); |
401 | } |
402 | |
403 | QString TestCase::displayName(const Scenario scen) |
404 | { |
405 | switch(scen) |
406 | { |
407 | case Standard: |
408 | return QLatin1String("Standard" ); |
409 | case ParseError: |
410 | return QLatin1String("Parse Error" ); |
411 | case RuntimeError: |
412 | return QLatin1String("Runtime Error" ); |
413 | case Trivial: |
414 | return QLatin1String("Trivial" ); |
415 | case AnyError: |
416 | { |
417 | Q_ASSERT(false); |
418 | return QString(); |
419 | } |
420 | } |
421 | |
422 | Q_ASSERT(false); |
423 | return QString(); |
424 | } |
425 | |
426 | TestItem::ResultSummary TestCase::resultSummary() const |
427 | { |
428 | if(m_result) |
429 | return ResultSummary(m_result->status() == TestResult::Pass ? 1 : 0, |
430 | 1); |
431 | |
432 | return ResultSummary(0, 1); |
433 | } |
434 | |
435 | void TestCase::appendChild(TreeItem *) |
436 | { |
437 | Q_ASSERT_X(false, Q_FUNC_INFO, "Makes no sense to call appendChild() for TestCase." ); |
438 | } |
439 | |
440 | TreeItem *TestCase::child(const unsigned int) const |
441 | { |
442 | return 0; /* Silence GCC */ |
443 | } |
444 | |
445 | TreeItem::List TestCase::children() const |
446 | { |
447 | return TreeItem::List(); |
448 | } |
449 | |
450 | unsigned int TestCase::childCount() const |
451 | { |
452 | return 0; |
453 | } |
454 | |
455 | TestResult *TestCase::testResult() const |
456 | { |
457 | return m_result; |
458 | } |
459 | |
460 | bool TestCase::isFinalNode() const |
461 | { |
462 | return true; |
463 | } |
464 | |
465 | QXmlQuery::QueryLanguage TestCase::language() const |
466 | { |
467 | return QXmlQuery::XQuery10; |
468 | } |
469 | |
470 | QXmlName TestCase::initialTemplateName() const |
471 | { |
472 | return QXmlName(); |
473 | } |
474 | |
475 | // vim: et:ts=4:sw=4:sts=4 |
476 | |
477 | |