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 | |
30 | #include <QFile> |
31 | #include <QtTest/QtTest> |
32 | |
33 | #include "../qxmlquery/TestFundament.h" |
34 | #include "../network-settings.h" |
35 | |
36 | #ifdef Q_OS_WIN |
37 | # include <qt_windows.h> |
38 | #endif |
39 | |
40 | /*! |
41 | \class tst_XmlPatterns |
42 | \internal |
43 | \since 4.4 |
44 | \brief Tests the command line interface, \c xmlpatterns, for the XQuery code. |
45 | |
46 | This test is not intended for testing the engine, but all the gluing the |
47 | command line interface do: error reporting, query output, variable bindings, exit |
48 | codes, and so on. |
49 | |
50 | In other words, if you have an engine bug; don't add it here because it won't be |
51 | tested properly. Instead add it to the test suite. |
52 | |
53 | */ |
54 | class tst_XmlPatterns : public QObject |
55 | , private TestFundament |
56 | { |
57 | Q_OBJECT |
58 | |
59 | public: |
60 | tst_XmlPatterns(); |
61 | |
62 | private Q_SLOTS: |
63 | void initTestCase(); |
64 | void xquerySupport(); |
65 | void xquerySupport_data() const; |
66 | void xsltSupport(); |
67 | void xsltSupport_data() const; |
68 | void stdoutFailure() const; |
69 | void cleanupTestCase() const; |
70 | |
71 | private: |
72 | static void createNonWritable(const QString &name); |
73 | static void removeNonWritable(QFile &outFile); |
74 | |
75 | QString filterStderr(const QString &in); |
76 | |
77 | int m_generatedTests; |
78 | /** |
79 | * Get rid of characters that complicates on various file systems. |
80 | */ |
81 | const QRegExp m_normalizeTestName; |
82 | /** |
83 | * @note Perforce disallows wildcards in the name. |
84 | */ |
85 | const QString m_command; |
86 | bool m_dontRun; |
87 | }; |
88 | |
89 | tst_XmlPatterns::tst_XmlPatterns() : m_generatedTests(0) |
90 | , m_normalizeTestName(QLatin1String("[\\*\\?#\\-\\/:; ()',&]" )) |
91 | , m_command(QLibraryInfo::location(QLibraryInfo::BinariesPath) + QLatin1String("/xmlpatterns" )) |
92 | , m_dontRun(false) |
93 | { |
94 | } |
95 | |
96 | void tst_XmlPatterns::initTestCase() |
97 | { |
98 | QVERIFY(m_normalizeTestName.isValid()); |
99 | |
100 | #ifndef QT_NO_PROCESS |
101 | QProcess process; |
102 | process.start(command: m_command); |
103 | |
104 | if(!process.waitForFinished()) |
105 | { |
106 | m_dontRun = true; |
107 | QSKIP( |
108 | qPrintable(QString( |
109 | "The command line tool (%1) could not be run, possibly because Qt was " |
110 | "incompletely built or installed. No tests will be run." |
111 | ).arg(m_command)) |
112 | ); |
113 | } |
114 | #else |
115 | QSKIP("Skipping test due to not having process support" ); |
116 | #endif // QT_NO_PROCESS |
117 | } |
118 | |
119 | #ifndef QT_NO_PROCESS |
120 | static QByteArray msgProcessError(const char *what, const QProcess &process) |
121 | { |
122 | QString result = QLatin1String(what) + QLatin1Char(' ') |
123 | + QDir::toNativeSeparators(pathName: process.program()) |
124 | + QLatin1Char(' ') + process.arguments().join(sep: QLatin1Char(' ')) |
125 | + QLatin1String(": " ) + process.errorString(); |
126 | return result.toLocal8Bit(); |
127 | } |
128 | #endif // !QT_NO_PROCESS |
129 | |
130 | void tst_XmlPatterns::xquerySupport() |
131 | { |
132 | if (QTest::currentDataTag() == QByteArray("Load query via FTP" ) |
133 | || QTest::currentDataTag() == QByteArray("Load query via HTTP" )) |
134 | QVERIFY(QtNetworkSettings::verifyTestNetworkSettings()); |
135 | |
136 | if(m_dontRun) |
137 | QSKIP("The command line utility is not in the path." ); |
138 | |
139 | #ifndef QT_NO_PROCESS |
140 | QFETCH(int, expectedExitCode); |
141 | QFETCH(QByteArray, expectedQueryOutput); |
142 | QFETCH(QStringList, arguments); |
143 | QFETCH(QString, cwd); |
144 | QFETCH(QString, outputFile); |
145 | |
146 | QProcess process; |
147 | |
148 | if(!cwd.isEmpty()) |
149 | process.setWorkingDirectory(inputFile(file: cwd)); |
150 | |
151 | QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); |
152 | env.insert(name: "QT_LOGGING_RULES" , value: "qt.network.ssl=false" ); |
153 | process.setProcessEnvironment(env); |
154 | |
155 | process.start(program: m_command, arguments); |
156 | QVERIFY2(process.waitForStarted(), msgProcessError("Failed to start" , process).constData()); |
157 | |
158 | QVERIFY2(process.waitForFinished(), msgProcessError("Timeout running" , process).constData()); |
159 | QCOMPARE(process.exitStatus(), QProcess::NormalExit); |
160 | |
161 | if(process.exitCode() != expectedExitCode) |
162 | QTextStream(stderr) << "stderr:" << process.readAllStandardError(); |
163 | |
164 | QCOMPARE(process.exitCode(), expectedExitCode); |
165 | |
166 | const QByteArray rawProducedStderr((process.readAllStandardError())); |
167 | QString fixedStderr = filterStderr(in: QString::fromLocal8Bit(str: rawProducedStderr)); |
168 | // convert Windows line endings to Unix ones |
169 | fixedStderr.replace(before: "\r\n" , after: "\n" ); |
170 | |
171 | const QString errorFileName(QFINDTESTDATA("stderrBaselines/" ) + |
172 | QString::fromUtf8(str: QTest::currentDataTag()).remove(rx: m_normalizeTestName) + |
173 | QLatin1String(".txt" )); |
174 | |
175 | QFile writeErr(errorFileName); |
176 | |
177 | if(writeErr.exists()) |
178 | { |
179 | QVERIFY(writeErr.open(QIODevice::ReadOnly)); |
180 | QString rawExpectedStdErr(QString::fromLocal8Bit(str: writeErr.readAll())); |
181 | |
182 | /* On Windows, at least MinGW, this differs. */ |
183 | if(qstrcmp(str1: QTest::currentDataTag(), str2: "-output with a non-writable file" ) == 0) |
184 | { |
185 | QVERIFY(fixedStderr == filterStderr(rawExpectedStdErr) || |
186 | fixedStderr.trimmed() == "Failed to open file notWritable.out for writing: Access is denied." ); |
187 | } |
188 | else if(qstrcmp(str1: QTest::currentDataTag(), str2: "Invoke -version" ) == 0) |
189 | { |
190 | /* There's a wide range of different version strings used. For |
191 | * instance, "4.4.0-rc1". */ |
192 | QRegExp removeVersion(QLatin1String(" Qt \\d\\.\\d.*" )); |
193 | QVERIFY(removeVersion.isValid()); |
194 | QCOMPARE(QString(fixedStderr).remove(removeVersion) + QChar('|'), rawExpectedStdErr + QChar('|')); |
195 | } |
196 | else |
197 | { |
198 | /* Qemu outputs extra information to the stderr */ |
199 | fixedStderr.remove(s: "Unsupported ioctl: cmd=0x8b07\n" ); |
200 | QCOMPARE(fixedStderr, filterStderr(rawExpectedStdErr)); |
201 | } |
202 | } |
203 | else |
204 | { |
205 | QFile writeErr(errorFileName); |
206 | QVERIFY(writeErr.open(QIODevice::WriteOnly)); |
207 | |
208 | QCOMPARE(writeErr.write(rawProducedStderr), qint64(rawProducedStderr.count())); |
209 | QTextStream(stderr) << "creating file " << errorFileName; |
210 | ++m_generatedTests; |
211 | } |
212 | |
213 | const QByteArray actual(process.readAllStandardOutput()); |
214 | |
215 | if(outputFile.isEmpty()) |
216 | { |
217 | QCOMPARE(actual, expectedQueryOutput); |
218 | return; /* We're done, this test was not creating any output file. */ |
219 | } |
220 | else |
221 | { |
222 | QVERIFY(actual.isEmpty()); |
223 | |
224 | QFile outFile(outputFile); |
225 | |
226 | QVERIFY(outFile.exists()); |
227 | QVERIFY(outFile.open(QIODevice::ReadOnly)); |
228 | |
229 | QCOMPARE(outFile.readAll(), expectedQueryOutput); |
230 | |
231 | removeNonWritable(outFile); |
232 | } |
233 | |
234 | #else |
235 | QSKIP("Skipping test due to not having process support" ); |
236 | #endif // QT_NO_PROCESS |
237 | } |
238 | |
239 | void tst_XmlPatterns::xquerySupport_data() const |
240 | { |
241 | QString path = QFINDTESTDATA("queries/" ); |
242 | |
243 | /* Check one file for existence, to avoid possible false positives. */ |
244 | QVERIFY(QFile::exists(path + QLatin1String("onePlusOne.xq" ))); |
245 | |
246 | QTest::addColumn<int>(name: "expectedExitCode" ); |
247 | QTest::addColumn<QByteArray>(name: "expectedQueryOutput" ); |
248 | QTest::addColumn<QStringList>(name: "arguments" ); |
249 | QTest::addColumn<QString>(name: "cwd" ); |
250 | QTest::addColumn<QString>(name: "outputFile" ); |
251 | |
252 | QTest::newRow(dataTag: "A simple math query" ) |
253 | << 0 |
254 | << QByteArray("2\n" ) |
255 | << QStringList((path + QLatin1String("onePlusOne.xq" ))) |
256 | << QString() |
257 | << QString(); |
258 | |
259 | QTest::newRow(dataTag: "An unbound external variable" ) |
260 | << 2 |
261 | << QByteArray() |
262 | << QStringList(path + QLatin1String("externalVariable.xq" )) |
263 | << QString() |
264 | << QString(); |
265 | |
266 | QTest::newRow(dataTag: "Bind an external variable" ) |
267 | << 0 |
268 | << QByteArray("1 4<e>1</e>true\n" ) |
269 | << (QStringList() << path + QLatin1String("externalVariable.xq" ) |
270 | << QLatin1String("-param" ) |
271 | << QLatin1String("externalVariable=1" )) |
272 | << QString() |
273 | << QString(); |
274 | |
275 | QTest::newRow(dataTag: "Bind an external variable, query appearing last" ) |
276 | << 0 |
277 | << QByteArray("1 4<e>1</e>true\n" ) |
278 | << (QStringList() << QLatin1String("-param" ) |
279 | << QLatin1String("externalVariable=1" ) |
280 | << path + QLatin1String("externalVariable.xq" )) |
281 | << QString() |
282 | << QString(); |
283 | |
284 | QTest::newRow(dataTag: "Use fn:doc" ) |
285 | << 0 |
286 | << QByteArray("<e xmlns=\"http://example.com\" xmlns:p=\"http://example.com/P\" attr=\"1\" p:attr=\"\">\n <?target data?>\n <!-- a comment -->\n <e/>text <f/>text node</e>\n" ) |
287 | << QStringList(path + QLatin1String("openDocument.xq" )) |
288 | << QString() |
289 | << QString(); |
290 | |
291 | QTest::newRow(dataTag: "Use fn:doc, together with -no-format, last" ) |
292 | << 0 |
293 | << QByteArray("<e xmlns=\"http://example.com\" xmlns:p=\"http://example.com/P\" attr=\"1\" p:attr=\"\"><?target data?><!-- a comment --><e/>text <f/>text node</e>" ) |
294 | << (QStringList() << path + QLatin1String("openDocument.xq" ) |
295 | << QLatin1String("-no-format" )) |
296 | << QString() |
297 | << QString(); |
298 | |
299 | QTest::newRow(dataTag: "Use fn:doc, together with -no-format, first" ) |
300 | << 0 |
301 | << QByteArray("<e xmlns=\"http://example.com\" xmlns:p=\"http://example.com/P\" attr=\"1\" p:attr=\"\"><?target data?><!-- a comment --><e/>text <f/>text node</e>" ) |
302 | << (QStringList() << QLatin1String("-no-format" ) |
303 | << path + QLatin1String("openDocument.xq" )) |
304 | << QString() |
305 | << QString(); |
306 | |
307 | /* This is true for the command line utility, but not QXmlQuery::setQuery(). */ |
308 | QTest::newRow(dataTag: "Make sure query paths are resolved against CWD, not the location of the executable." ) |
309 | << 0 |
310 | << QByteArray("2\n" ) |
311 | << QStringList(QFINDTESTDATA("queries/onePlusOne.xq" )) |
312 | << QFINDTESTDATA("queries" ) |
313 | << QString(); |
314 | |
315 | QTest::newRow(dataTag: "Call fn:error()" ) |
316 | << 2 |
317 | << QByteArray() |
318 | << QStringList(path + QLatin1String("errorFunction.xq" )) |
319 | << QString() |
320 | << QString(); |
321 | |
322 | QTest::newRow(dataTag: "Evaluate a library module" ) |
323 | << 2 |
324 | << QByteArray() |
325 | << QStringList(path + QLatin1String("simpleLibraryModule.xq" )) |
326 | << QString() |
327 | << QString(); |
328 | |
329 | QTest::newRow(dataTag: "Trigger a static error." ) |
330 | << 2 |
331 | << QByteArray() |
332 | << QStringList(path + QLatin1String("staticError.xq" )) |
333 | << QString() |
334 | << QString(); |
335 | |
336 | QTest::newRow(dataTag: "Pass -help" ) |
337 | << 0 |
338 | << QByteArray() |
339 | << QStringList(QLatin1String("-help" )) |
340 | << QString() |
341 | << QString(); |
342 | |
343 | QTest::newRow(dataTag: "Open an nonexistent file" ) |
344 | << 2 |
345 | << QByteArray() |
346 | << QStringList(path + QLatin1String("ThisFileDoesNotExist.xq" )) |
347 | << QString() |
348 | << QString(); |
349 | |
350 | /* The following five tests exists to test the various |
351 | * markup classes in the message. */ |
352 | QTest::newRow(dataTag: "XQuery-function message markups" ) |
353 | << 2 |
354 | << QByteArray() |
355 | << QStringList(path + QLatin1String("wrongArity.xq" )) |
356 | << QString() |
357 | << QString(); |
358 | |
359 | QTest::newRow(dataTag: "XQuery-type message markups" ) |
360 | << 2 |
361 | << QByteArray() |
362 | << QStringList(path + QLatin1String("typeError.xq" )) |
363 | << QString() |
364 | << QString(); |
365 | |
366 | QTest::newRow(dataTag: "XQuery-data & XQuery-keyword message markups" ) |
367 | << 2 |
368 | << QByteArray() |
369 | << QStringList(path + QLatin1String("zeroDivision.xq" )) |
370 | << QString() |
371 | << QString(); |
372 | |
373 | QTest::newRow(dataTag: "XQuery-uri message markups" ) |
374 | << 2 |
375 | << QByteArray() |
376 | << QStringList(path + QLatin1String("unsupportedCollation.xq" )) |
377 | << QString() |
378 | << QString(); |
379 | |
380 | QTest::newRow(dataTag: "XQuery-expression message markups" ) |
381 | << 2 |
382 | << QByteArray() |
383 | << QStringList(path + QLatin1String("invalidRegexp.xq" )) |
384 | << QString() |
385 | << QString(); |
386 | |
387 | QTest::newRow(dataTag: "Print a list of available regexp flags(The available flags are formatted in a complex way.)" ) |
388 | << 2 |
389 | << QByteArray() |
390 | << QStringList(path + QLatin1String("invalidRegexpFlag.xq" )) |
391 | << QString() |
392 | << QString(); |
393 | |
394 | QTest::newRow(dataTag: "Trigger an assert in QPatternist::ColorOutput. The query naturally contains an error; XPTY0004." ) |
395 | << 2 |
396 | << QByteArray() |
397 | << QStringList(path + QLatin1String("flwor.xq" )) |
398 | << QString() |
399 | << QString(); |
400 | |
401 | QTest::newRow(dataTag: "Trigger a second assert in QPatternist::ColorOutput. The query naturally contains XPST0003." ) |
402 | << 2 |
403 | << QByteArray() |
404 | << QStringList(path + QLatin1String("syntaxError.xq" )) |
405 | << QString() |
406 | << QString(); |
407 | |
408 | QTest::newRow(dataTag: "-param is missing so multiple queries appear" ) |
409 | << 2 |
410 | << QByteArray() |
411 | << (QStringList() << path + QLatin1String("reportGlobals.xq" ) |
412 | << QLatin1String("fileToOpen=globals.gccxml" )) |
413 | << QString() |
414 | << QString(); |
415 | |
416 | QTest::newRow(dataTag: "only -no-format" ) |
417 | << 1 |
418 | << QByteArray() |
419 | << (QStringList() << QLatin1String("-no-format" )) |
420 | << QString() |
421 | << QString(); |
422 | |
423 | QTest::newRow(dataTag: "Basic use of -output, query first" ) |
424 | << 0 |
425 | << QByteArray("2\n" ) |
426 | << (QStringList() << path + QLatin1String("onePlusOne.xq" ) |
427 | << QLatin1String("-output" ) |
428 | << QLatin1String("basicOutput.out" )) |
429 | << QString() |
430 | << QString::fromLatin1(str: "basicOutput.out" ); |
431 | |
432 | QTest::newRow(dataTag: "Basic use of -output, query last" ) |
433 | << 0 |
434 | << QByteArray("<e/>\n" ) |
435 | << (QStringList() << QLatin1String("-output" ) |
436 | << QLatin1String("basicOutput2.out" ) |
437 | << path + QLatin1String("oneElement.xq" )) |
438 | << QString() |
439 | << QString::fromLatin1(str: "basicOutput2.out" ); |
440 | |
441 | QTest::newRow(dataTag: "A single query, that does not exist" ) |
442 | << 2 |
443 | << QByteArray() |
444 | << (QStringList() << path + QLatin1String("doesNotExist.xq" )) |
445 | << QString() |
446 | << QString(); |
447 | |
448 | QTest::newRow(dataTag: "Specify two identical query names" ) |
449 | << 2 |
450 | << QByteArray() |
451 | << (QStringList() << path + QLatin1String("query.xq" ) |
452 | << path + QLatin1String("query.xq" )) |
453 | << QString() |
454 | << QString(); |
455 | |
456 | QTest::newRow(dataTag: "Specify no arguments at all." ) |
457 | << 1 |
458 | << QByteArray() |
459 | << QStringList() |
460 | << QString() |
461 | << QString(); |
462 | |
463 | QTest::newRow(dataTag: "Use -output twice" ) |
464 | << 1 |
465 | << QByteArray() |
466 | << (QStringList() << QLatin1String("-output" ) |
467 | << QLatin1String("output1" ) |
468 | << QLatin1String("-output" ) |
469 | << QLatin1String("output2" )) |
470 | << QString() |
471 | << QString(); |
472 | |
473 | { |
474 | const QString filename(QString::fromLatin1(str: "notWritable.out" )); |
475 | createNonWritable(name: filename); |
476 | |
477 | QTest::newRow(dataTag: "-output with a non-writable file" ) |
478 | << 1 |
479 | << QByteArray() |
480 | << (QStringList() << QLatin1String("-output" ) |
481 | << filename |
482 | << path + QLatin1String("onePlusOne.xq" )) |
483 | << QString() |
484 | << filename; |
485 | } |
486 | |
487 | { |
488 | const QString outName(QString::fromLatin1(str: "existingContent.out" )); |
489 | QFile outFile(outName); |
490 | QVERIFY(outFile.open(QIODevice::WriteOnly)); |
491 | QCOMPARE(outFile.write("Existing content\n" ), qint64(17)); |
492 | outFile.close(); |
493 | |
494 | QTest::newRow(dataTag: "Use -output on a file with existing content, to ensure we truncate, not append the content we produce." ) |
495 | << 0 |
496 | << QByteArray("2\n" ) |
497 | << (QStringList() << QLatin1String("-output" ) |
498 | << outName |
499 | << path + QLatin1String("onePlusOne.xq" )) |
500 | << QString() |
501 | << outName; |
502 | } |
503 | |
504 | QTest::newRow(dataTag: "one query, and a terminating dash at the end" ) |
505 | << 0 |
506 | << QByteArray("2\n" ) |
507 | << (QStringList() << path + QLatin1String("onePlusOne.xq" ) |
508 | << QLatin1String("-" )) |
509 | << QString() |
510 | << QString(); |
511 | |
512 | QTest::newRow(dataTag: "one query, with a preceding dash" ) |
513 | << 0 |
514 | << QByteArray("2\n" ) |
515 | << (QStringList() << QLatin1String("-" ) |
516 | << path + QLatin1String("onePlusOne.xq" )) |
517 | << QString() |
518 | << QString(); |
519 | |
520 | QTest::newRow(dataTag: "A single dash, that's invalid" ) |
521 | << 1 |
522 | << QByteArray() |
523 | << (QStringList() << QLatin1String("-" )) |
524 | << QString() |
525 | << QString(); |
526 | |
527 | QTest::newRow(dataTag: "Invoke -version" ) |
528 | << 0 |
529 | << QByteArray() |
530 | << (QStringList() << QLatin1String("-version" )) |
531 | << QString() |
532 | << QString(); |
533 | |
534 | QTest::newRow(dataTag: "Unknown switch; -unknown-switch" ) |
535 | << 1 |
536 | << QByteArray() |
537 | << (QStringList() << QLatin1String("-unknown-switch" )) |
538 | << QString() |
539 | << QString(); |
540 | |
541 | QTest::newRow(dataTag: "Unknown switch; -d" ) |
542 | << 1 |
543 | << QByteArray() |
544 | << (QStringList() << QLatin1String("-d" )) |
545 | << QString() |
546 | << QString(); |
547 | |
548 | QTest::newRow(dataTag: "Passing a single dash is insufficient" ) |
549 | << 1 |
550 | << QByteArray() |
551 | << (QStringList() << QLatin1String("-" )) |
552 | << QString() |
553 | << QString(); |
554 | |
555 | QTest::newRow(dataTag: "Passing two dashes, the last is interpreted as a file name" ) |
556 | << 2 |
557 | << QByteArray() |
558 | << (QStringList() << QLatin1String("-" ) |
559 | << QLatin1String("-" )) |
560 | << QString() |
561 | << QString(); |
562 | |
563 | QTest::newRow(dataTag: "Pass three dashes, the two last gets interpreted as two query arguments" ) |
564 | << 2 |
565 | << QByteArray() |
566 | << (QStringList() << QLatin1String("-" ) |
567 | << QLatin1String("-" ) |
568 | << QLatin1String("-" )) |
569 | << QString() |
570 | << QString(); |
571 | |
572 | QTest::newRow(dataTag: "Load query via data: scheme" ) |
573 | << 0 |
574 | << QByteArray("<e/>\n" ) |
575 | << (QStringList() << QLatin1String("-is-uri" ) << QLatin1String("data:application/xml,%3Ce%2F%3E" )) |
576 | << QString() |
577 | << QString(); |
578 | |
579 | QTest::newRow(dataTag: "Load query via FTP" ) |
580 | << 0 |
581 | << QByteArray("This was received via FTP\n" ) |
582 | << (QStringList() << QLatin1String("-is-uri" ) << QString("ftp://" + QtNetworkSettings::serverName() + "/pub/qxmlquery/viaFtp.xq" )) |
583 | << QString() |
584 | << QString(); |
585 | |
586 | QTest::newRow(dataTag: "Load query via HTTP" ) |
587 | << 0 |
588 | << QByteArray("This was received via HTTP.\n" ) |
589 | << (QStringList() << QLatin1String("-is-uri" ) << QString("http://" + QtNetworkSettings::serverName() + "/qxmlquery/viaHttp.xq" )) |
590 | << QString() |
591 | << QString(); |
592 | |
593 | QTest::newRow(dataTag: "We don't support -format any longer" ) |
594 | << 1 |
595 | << QByteArray() |
596 | << (QStringList() << QLatin1String("-format" )) |
597 | << QString() |
598 | << QString(); |
599 | |
600 | QTest::newRow(dataTag: "Run a query which evaluates to the empty sequence." ) |
601 | << 0 |
602 | << QByteArray("\n" ) |
603 | << (QStringList() << path + QLatin1String("emptySequence.xq" )) |
604 | << QString() |
605 | << QString(); |
606 | |
607 | QTest::newRow(dataTag: "Run a query which evaluates to a single document node with no children." ) |
608 | << 0 |
609 | << QByteArray("\n" ) |
610 | << (QStringList() << path + QLatin1String("onlyDocumentNode.xq" )) |
611 | << QString() |
612 | << QString(); |
613 | |
614 | QTest::newRow(dataTag: "Invoke with invalid -param value." ) |
615 | << 1 |
616 | << QByteArray() |
617 | << (QStringList() << path + QLatin1String("externalVariable.xq" ) |
618 | << QLatin1String("-param" ) |
619 | << QLatin1String("EqualSignIsMissing" )) |
620 | << QString() |
621 | << QString(); |
622 | |
623 | QTest::newRow(dataTag: "Invoke with colon in variable name." ) |
624 | << 1 |
625 | << QByteArray() |
626 | << (QStringList() << path + QLatin1String("externalVariable.xq" ) |
627 | << QLatin1String("-param" ) |
628 | << QLatin1String("xs:name=value" )) |
629 | << QString() |
630 | << QString(); |
631 | |
632 | QTest::newRow(dataTag: "Invoke with missing name in -param arg." ) |
633 | << 1 |
634 | << QByteArray() |
635 | << (QStringList() << path + QLatin1String("externalVariable.xq" ) |
636 | << QLatin1String("-param" ) |
637 | << QLatin1String("=value" )) |
638 | << QString() |
639 | << QString(); |
640 | |
641 | QTest::newRow(dataTag: "Invoke with -param that has two adjacent equal signs." ) |
642 | << 0 |
643 | << QByteArray("START =text END\n" ) |
644 | << (QStringList() << path + QLatin1String("externalStringVariable.xq" ) |
645 | << QLatin1String("-param" ) |
646 | << QLatin1String("externalString==text" )) |
647 | << QString() |
648 | << QString(); |
649 | |
650 | QTest::newRow(dataTag: "Pass in an external variable, but the query doesn't use it." ) |
651 | << 0 |
652 | << QByteArray("2\n" ) |
653 | << (QStringList() << path + QLatin1String("onePlusOne.xq" ) |
654 | << QLatin1String("-param" ) |
655 | << QLatin1String("externalString==text" )) |
656 | << QString() |
657 | << QString(); |
658 | |
659 | /* This is how an empty string would have been passed in. */ |
660 | QTest::newRow(dataTag: "Invoke with -param that has no value." ) |
661 | << 0 |
662 | << QByteArray("START END\n" ) |
663 | << (QStringList() << path + QLatin1String("externalStringVariable.xq" ) |
664 | << QLatin1String("-param" ) |
665 | << QLatin1String("externalString=" )) |
666 | << QString() |
667 | << QString(); |
668 | |
669 | QTest::newRow(dataTag: "Ensure -is-uri can appear after the query filename" ) |
670 | << 0 |
671 | << QByteArray("<e/>\n" ) |
672 | << (QStringList() << QLatin1String("data:application/xml,%3Ce%2F%3E" ) << QLatin1String("-is-uri" )) |
673 | << QString() |
674 | << QString(); |
675 | |
676 | QTest::newRow(dataTag: "Use a native path" ) |
677 | << 0 |
678 | << QByteArray("2\n" ) |
679 | << (QStringList() << QDir::toNativeSeparators(pathName: path + QLatin1String("onePlusOne.xq" ))) |
680 | << QString() |
681 | << QString(); |
682 | |
683 | QTest::newRow(dataTag: "Pass in invalid URI" ) |
684 | << 2 |
685 | << QByteArray() |
686 | << (QStringList() << QLatin1String("-is-uri" ) << QLatin1String("data:application/xml;base64,PGUvg===" )) |
687 | << QString() |
688 | << QString(); |
689 | |
690 | /* Not relevant anymore. |
691 | QTest::newRow("A valid, existing query, followed by a bogus one") |
692 | << 1 |
693 | << QByteArray() |
694 | << (QStringList() << path + QLatin1String("onePlusOne.xq") |
695 | << path + QLatin1String("doesNotExist.xq")) |
696 | << QString() |
697 | << QString(); |
698 | */ |
699 | |
700 | /* Not relevant anymore. |
701 | QTest::newRow("Specify two different query names") |
702 | << 1 |
703 | << QByteArray() |
704 | << (QStringList() << path + QLatin1String("query1.xq") |
705 | << path + QLatin1String("query2.xq")) |
706 | << QString() |
707 | << QString(); |
708 | */ |
709 | |
710 | // TODO use focus with xquery |
711 | // TODO fail to load focus with xquery |
712 | // TODO focus with URI with xquery |
713 | // TODO focus via FTP or so with xquery |
714 | |
715 | |
716 | QTest::newRow(dataTag: "Use -param twice" ) |
717 | << 0 |
718 | << QByteArray("param1 param2\n" ) |
719 | << (QStringList() << path + QLatin1String("twoVariables.xq" ) |
720 | << QLatin1String("-param" ) |
721 | << QLatin1String("var1=param1" ) |
722 | << QLatin1String("-param" ) |
723 | << QLatin1String("var2=param2" )) |
724 | << QString() |
725 | << QString(); |
726 | |
727 | QTest::newRow(dataTag: "Use -param thrice" ) |
728 | << 0 |
729 | << QByteArray("param1 param2 third\n" ) |
730 | << (QStringList() << path + QLatin1String("threeVariables.xq" ) |
731 | << QLatin1String("-param" ) |
732 | << QLatin1String("var1=param1" ) |
733 | << QLatin1String("-param" ) |
734 | << QLatin1String("var2=param2" ) |
735 | << QLatin1String("-param" ) |
736 | << QLatin1String("var3=third" )) |
737 | << QString() |
738 | << QString(); |
739 | |
740 | QTest::newRow(dataTag: "Specify the same parameter twice, different values" ) |
741 | << 1 |
742 | << QByteArray() |
743 | << (QStringList() << path + QLatin1String("onePlusOne.xq" ) |
744 | << QLatin1String("-param" ) |
745 | << QLatin1String("duplicated=param1" ) |
746 | << QLatin1String("-param" ) |
747 | << QLatin1String("duplicated=param2" )) |
748 | << QString() |
749 | << QString(); |
750 | |
751 | QTest::newRow(dataTag: "Specify the same parameter twice, same values" ) |
752 | << 1 |
753 | << QByteArray() |
754 | << (QStringList() << path + QLatin1String("onePlusOne.xq" ) |
755 | << QLatin1String("-param" ) |
756 | << QLatin1String("duplicated=param1" ) |
757 | << QLatin1String("-param" ) |
758 | << QLatin1String("duplicated=param2" )) |
759 | << QString() |
760 | << QString(); |
761 | |
762 | QTest::newRow(dataTag: "Open a non-existing collection." ) |
763 | << 2 |
764 | << QByteArray() |
765 | << (QStringList() << path + QLatin1String("nonexistingCollection.xq" )) |
766 | << QString() |
767 | << QString(); |
768 | |
769 | QTest::newRow(dataTag: "QTBUG-35897: literal sequence" ) |
770 | << 0 |
771 | << QByteArray("someString a b\n" ) |
772 | << QStringList((path + QStringLiteral("literalsequence.xq" ))) |
773 | << QString() |
774 | << QString(); |
775 | |
776 | // TODO https? |
777 | // TODO pass external variables that allows space around the equal sign. |
778 | // TODO run fn:trace() |
779 | // TODO Trigger warning |
780 | // TODO what can we do with queries/nodeSequence.xq? |
781 | // TODO trigger serialization error |
782 | // TODO "xmlpatterns e.xq x" gives "binding must equal .." |
783 | // |
784 | // TODO use stdout where it's connected to a non-writable file. |
785 | // TODO specify -format twice, or whatever it's called. |
786 | // TODO query name that starts with "-". |
787 | // |
788 | // TODO Consider what we should do with paths on windows. Stuff like path\filename.xml fails. |
789 | // TODO use invalid URI in query name, xmlpatterns -is-uri 'as1/#(¤/¤)("#' |
790 | |
791 | // TODO add xmlpatterns file1 file2 file3 |
792 | // TODO add xmlpatterns -is-uri file1 file2 file3 |
793 | } |
794 | |
795 | void tst_XmlPatterns::createNonWritable(const QString &name) |
796 | { |
797 | /* Create an existing, empty, non-writable file. */ |
798 | QFile outFile(name); |
799 | QVERIFY(outFile.open(QIODevice::ReadWrite)); |
800 | outFile.write(data: QByteArray("1" )); |
801 | QVERIFY(outFile.resize(0)); |
802 | outFile.close(); |
803 | QVERIFY(outFile.setPermissions(QFile::Permissions(QFile::ReadOwner))); |
804 | } |
805 | |
806 | void tst_XmlPatterns::removeNonWritable(QFile &outFile) |
807 | { |
808 | /* Kill off temporary files. */ |
809 | if(!outFile.remove()) |
810 | { |
811 | /* Since one file is used for testing that we can handle non-writable file by |
812 | * changing the permissions, we need to revert it such that we can remove it. */ |
813 | outFile.setPermissions(QFile::WriteOwner); |
814 | outFile.remove(); |
815 | } |
816 | } |
817 | |
818 | /*! |
819 | Check that we gracefully handle writing out to stdout |
820 | when the latter is not writable. |
821 | */ |
822 | void tst_XmlPatterns::stdoutFailure() const |
823 | { |
824 | #ifdef QT_NO_PROCESS |
825 | QSKIP("Skipping test due to not having process support" ); |
826 | #else |
827 | return; // TODO It's really hard to write testing code for this. |
828 | |
829 | const QString outName(QLatin1String("stdoutFailure.out" )); |
830 | createNonWritable(name: outName); |
831 | |
832 | QProcess process; |
833 | // If we enable this line, waitForFinished() fails. |
834 | //process.setStandardOutputFile(outName); |
835 | |
836 | process.setWorkingDirectory(QDir::current().absoluteFilePath(fileName: QString())); |
837 | process.start(program: m_command, arguments: QStringList(QFINDTESTDATA("queries/onePlusOne.xq" ))); |
838 | |
839 | QCOMPARE(process.exitStatus(), QProcess::NormalExit); |
840 | QVERIFY(process.waitForFinished()); |
841 | |
842 | QFile outFile(outName); |
843 | QVERIFY(outFile.open(QIODevice::ReadOnly)); |
844 | QCOMPARE(outFile.readAll(), QByteArray()); |
845 | |
846 | QCOMPARE(process.exitCode(), 1); |
847 | |
848 | removeNonWritable(outFile); |
849 | #endif // QT_NO_PROCESS |
850 | } |
851 | |
852 | void tst_XmlPatterns::cleanupTestCase() const |
853 | { |
854 | /* Remove temporaries that we create. */ |
855 | QStringList files; |
856 | files << QLatin1String("existingContent.out" ) |
857 | << QLatin1String("notWritable.out" ) |
858 | << QLatin1String("output1" ); |
859 | |
860 | for(int i = 0; i < files.count(); ++i) |
861 | { |
862 | QFile file(files.at(i)); |
863 | removeNonWritable(outFile&: file); |
864 | } |
865 | |
866 | QCOMPARE(m_generatedTests, 0); |
867 | } |
868 | |
869 | void tst_XmlPatterns::xsltSupport() |
870 | { |
871 | xquerySupport(); |
872 | } |
873 | |
874 | void tst_XmlPatterns::xsltSupport_data() const |
875 | { |
876 | if(m_dontRun) |
877 | QSKIP("The command line utility is not in the path." ); |
878 | |
879 | QString spath = QFINDTESTDATA("stylesheets/" ); |
880 | QString qpath = QFINDTESTDATA("queries/" ); |
881 | |
882 | QTest::addColumn<int>(name: "expectedExitCode" ); |
883 | QTest::addColumn<QByteArray>(name: "expectedQueryOutput" ); |
884 | QTest::addColumn<QStringList>(name: "arguments" ); |
885 | QTest::addColumn<QString>(name: "cwd" ); |
886 | QTest::addColumn<QString>(name: "outputFile" ); |
887 | |
888 | QTest::newRow(dataTag: "Evaluate a stylesheet, with no context document" ) |
889 | << 1 |
890 | << QByteArray() |
891 | << (QStringList() << QLatin1String("stylesheets/onlyRootTemplate.xsl" )) |
892 | << QString() |
893 | << QString(); |
894 | |
895 | QTest::newRow(dataTag: "Pass in a stylesheet file which contains an XQuery query" ) |
896 | << 2 |
897 | << QByteArray() |
898 | << (QStringList() << spath + QLatin1String("queryAsStylesheet.xsl" ) |
899 | << qpath + QLatin1String("simpleDocument.xml" )) |
900 | << QString() |
901 | << QString(); |
902 | |
903 | QTest::newRow(dataTag: "Pass in a stylesheet file and a focus file which doesn't exist" ) |
904 | << 2 |
905 | << QByteArray() |
906 | << (QStringList() << QLatin1String("stylesheets/onlyRootTemplate.xsl" ) |
907 | << QLatin1String("doesNotExist.Nope.xml" )) |
908 | << QString() |
909 | << QString(); |
910 | |
911 | QTest::newRow(dataTag: "-initial-template doesn't work with XQueries." ) |
912 | << 1 |
913 | << QByteArray() |
914 | << (QStringList() << QLatin1String("-initial-template" ) |
915 | << QLatin1String("name" ) |
916 | << qpath + QLatin1String("onePlusOne.xq" )) |
917 | << QString() |
918 | << QString(); |
919 | |
920 | QTest::newRow(dataTag: "-initial-template must be followed by a value" ) |
921 | << 1 |
922 | << QByteArray() |
923 | << (QStringList() << QLatin1String("-initial-template" ) |
924 | << QLatin1String("stylesheets/onlyRootTemplate.xsl" )) |
925 | << QString() |
926 | << QString(); |
927 | |
928 | QTest::newRow(dataTag: "-initial-template must be followed by a value(#2)" ) |
929 | << 1 |
930 | << QByteArray() |
931 | << (QStringList() << QLatin1String("stylesheets/onlyRootTemplate.xsl" ) |
932 | << QLatin1String("-initial-template" )) |
933 | << QString() |
934 | << QString(); |
935 | |
936 | QTest::newRow(dataTag: "Invalid template name" ) |
937 | << 1 |
938 | << QByteArray() |
939 | << (QStringList() << QLatin1String("-initial-template" ) |
940 | << QLatin1String("abc:def" ) |
941 | << QLatin1String("stylesheets/onlyRootTemplate.xsl" )) |
942 | << QString() |
943 | << QString(); |
944 | |
945 | QTest::newRow(dataTag: "Specify a named template, that exists" ) |
946 | << 0 |
947 | << QByteArray("named-template" ) |
948 | << (QStringList() << QLatin1String("-no-format" ) |
949 | << QLatin1String("-initial-template" ) |
950 | << QLatin1String("main" ) |
951 | << spath + QLatin1String("namedAndRootTemplate.xsl" ) |
952 | << spath + QLatin1String("documentElement.xml" )) |
953 | << QString() |
954 | << QString(); |
955 | |
956 | QTest::newRow(dataTag: "Specify a named template, that does not exists" ) |
957 | << 0 |
958 | << QByteArray("root-template" ) |
959 | << (QStringList() << QLatin1String("-no-format" ) |
960 | << QLatin1String("-initial-template" ) |
961 | << QLatin1String("no-template-by-this-name" ) |
962 | << spath + QLatin1String("namedAndRootTemplate.xsl" ) |
963 | << spath + QLatin1String("documentElement.xml" )) |
964 | << QString() |
965 | << QString(); |
966 | |
967 | QTest::newRow(dataTag: "Call a named template, and use no focus." ) |
968 | << 0 |
969 | << QByteArray("named-template" ) |
970 | << (QStringList() << QLatin1String("-no-format" ) |
971 | << QLatin1String("-initial-template" ) |
972 | << QLatin1String("main" ) |
973 | << spath + QLatin1String("namedAndRootTemplate.xsl" )) |
974 | << QString() |
975 | << QString(); |
976 | |
977 | QTest::newRow(dataTag: "Call a named template, and use no focus." ) |
978 | << 0 |
979 | << QByteArray("namespaced-template" ) |
980 | << (QStringList() << QLatin1String("-no-format" ) |
981 | << QLatin1String("-initial-template" ) |
982 | << QLatin1String("{http://example.com/NS}main" ) |
983 | << spath + QLatin1String("namedAndRootTemplate.xsl" )) |
984 | << QString() |
985 | << QString(); |
986 | |
987 | QTest::newRow(dataTag: "Invoke a template, and use/pass parameters." ) |
988 | << 0 |
989 | << QByteArray("defParam overridedDefaultedParam implicitlyRequiredValue\n" ) |
990 | << (QStringList() << QLatin1String("-initial-template" ) |
991 | << QLatin1String("main" ) |
992 | << spath + QLatin1String("useParameters.xsl" ) |
993 | << QLatin1String("-param" ) |
994 | << QLatin1String("overridedDefaultedParam=overridedDefaultedParam" ) |
995 | << QLatin1String("-param" ) |
996 | << QLatin1String("implicitlyRequiredValue=implicitlyRequiredValue" )) |
997 | << QString() |
998 | << QString(); |
999 | |
1000 | QTest::newRow(dataTag: "Use a simplified stylesheet module" ) |
1001 | << 0 |
1002 | << QByteArray("<output>some text</output>\n" ) |
1003 | << (QStringList() << spath + QLatin1String("simplifiedStylesheetModule.xsl" ) |
1004 | << spath + QLatin1String("simplifiedStylesheetModule.xml" )) |
1005 | << QString() |
1006 | << QString(); |
1007 | |
1008 | QTest::newRow(dataTag: "Not well-formed stylesheet, causes crash in coloring code." ) |
1009 | << 2 |
1010 | << QByteArray() |
1011 | << (QStringList() << spath + QLatin1String("notWellformed.xsl" ) |
1012 | << qpath + QLatin1String("simpleDocument.xml" )) |
1013 | << QString() |
1014 | << QString(); |
1015 | |
1016 | QTest::newRow(dataTag: "Not well-formed instance document, causes crash in coloring code." ) |
1017 | << 2 |
1018 | << QByteArray() |
1019 | << (QStringList() << spath + QLatin1String("bool070.xsl" ) |
1020 | << spath + QLatin1String("bool070.xml" )) |
1021 | << QString() |
1022 | << QString(); |
1023 | |
1024 | // TODO test -is-uris with context |
1025 | // TODO fail to load focus document when using XSL-T |
1026 | // TODO fail to load focus document when using XQuery |
1027 | // TODO focus via FTP or so with xquery |
1028 | // TODO use URI in focus |
1029 | // TODO use invalid URI in focus |
1030 | |
1031 | // TODO invoke a template which has required params. |
1032 | } |
1033 | |
1034 | /* |
1035 | Return a copy of some stderr text with some irrelevant things filtered. |
1036 | */ |
1037 | QString tst_XmlPatterns::filterStderr(const QString &in) |
1038 | { |
1039 | static const QList<QRegExp> irrelevant = QList<QRegExp>() |
1040 | |
1041 | // specific filenames |
1042 | << QRegExp(QLatin1String("file:\\/\\/.*(\\.xq|\\.gccxml|\\.xml|\\.xsl|-)(,|:)" )) |
1043 | |
1044 | // warning messages about old-style plugins |
1045 | << QRegExp(QLatin1String("Old plugin format found in lib [^\n]+\n" )) |
1046 | << QRegExp(QLatin1String("Qt plugin loader: Compatibility plugin [^\n]+\n" )) |
1047 | << QRegExp(QLatin1String("Unimplemented code.\n" )) |
1048 | ; |
1049 | |
1050 | QString out = in; |
1051 | for (const QRegExp& rx : irrelevant) |
1052 | out = out.remove(rx); |
1053 | |
1054 | #ifdef Q_OS_WIN |
1055 | // replace some Win32 error messages by standard Unix ones |
1056 | out.replace(qt_error_string(ERROR_FILE_NOT_FOUND), "No such file or directory" ); |
1057 | out.replace(qt_error_string(ERROR_PATH_NOT_FOUND), "No such file or directory" ); |
1058 | #endif |
1059 | |
1060 | return out; |
1061 | } |
1062 | |
1063 | QTEST_MAIN(tst_XmlPatterns) |
1064 | |
1065 | #include "tst_xmlpatterns.moc" |
1066 | |
1067 | // vim: et:ts=4:sw=4:sts=4 |
1068 | |