diff options
Diffstat (limited to 'tests/auto/qxmlquery/tst_qxmlquery.cpp')
-rw-r--r-- | tests/auto/qxmlquery/tst_qxmlquery.cpp | 3481 |
1 files changed, 3481 insertions, 0 deletions
diff --git a/tests/auto/qxmlquery/tst_qxmlquery.cpp b/tests/auto/qxmlquery/tst_qxmlquery.cpp new file mode 100644 index 0000000..e3c97d2 --- /dev/null +++ b/tests/auto/qxmlquery/tst_qxmlquery.cpp @@ -0,0 +1,3481 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include <QtTest/QtTest> + +#ifdef QTEST_XMLPATTERNS + +#include <QAbstractMessageHandler> +#include <QFileInfo> +#include <QNetworkReply> +#include <QtNetwork/QTcpServer> +#include <QtNetwork/QTcpSocket> +#include <QXmlFormatter> +#include <QXmlItem> +#include <QXmlName> +#include <QXmlQuery> +#include <QXmlResultItems> +#include <QXmlSerializer> + +#include "MessageSilencer.h" +#include "MessageValidator.h" +#include "NetworkOverrider.h" +#include "PushBaseliner.h" +#include "../qabstracturiresolver/TestURIResolver.h" +#include "../qsimplexmlnodemodel/TestSimpleNodeModel.h" +#include "TestFundament.h" +#include "../network-settings.h" + +#if defined(Q_OS_SYMBIAN) +#define SRCDIR "" +#define XMLPATTERNSDIR "xmlpatterns" +#else +#define XMLPATTERNSDIR SRCDIR "../xmlpatterns" +#endif + +/*! + \class tst_QXmlQuery + \internal + \since 4.4 + \brief Tests class QXmlQuery. + + This test is not intended for testing the engine, but the functionality specific + to the QXmlQuery class. + + In other words, if you have an engine bug; don't add it here because it won't be + tested properly. Instead add it to the test suite. + + */ +class tst_QXmlQuery : public QObject + , private TestFundament +{ + Q_OBJECT + +public: + inline tst_QXmlQuery() : m_generatedBaselines(0) + , m_pushTestsCount(0) + , m_testNetwork(true) + { + Q_SET_DEFAULT_IAP + } + +private Q_SLOTS: + void defaultConstructor() const; + void copyConstructor() const; + void constructorQXmlNamePool() const; + void constructorQXmlNamePoolQueryLanguage() const; + void constructorQXmlNamePoolWithinQSimpleXmlNodeModel() const; + void assignmentOperator() const; + void isValid() const; + void sequentialExecution() const; + void bindVariableQString() const; + void bindVariableQStringNoExternalDeclaration() const; + void bindVariableQXmlName() const; + void bindVariableQXmlNameTriggerWarnings() const; + void bindVariableQStringQIODevice() const; + void bindVariableQStringQIODeviceWithByteArray() const; + void bindVariableQStringQIODeviceWithString() const; + void bindVariableQStringQIODeviceWithQFile() const; + void bindVariableQXmlNameQIODevice() const; + void bindVariableQXmlNameQIODeviceTriggerWarnings() const; + void bindVariableXSLTSuccess() const; + void bindVariableTemporaryNode() const; + void setMessageHandler() const; + void messageHandler() const; + void evaluateToQAbstractXmlReceiverTriggerWarnings() const; + void evaluateToQXmlResultItems() const; + void evaluateToQXmlResultItemsTriggerWarnings() const; + void evaluateToQXmlResultItemsErrorAtEnd() const; + void evaluateToReceiver(); + void evaluateToReceiver_data() const; + void evaluateToReceiverOnInvalidQuery() const; + void evaluateToQStringTriggerError() const; + void evaluateToQString() const; + void evaluateToQString_data() const; + void evaluateToQStringSignature() const; + void checkGeneratedBaselines() const; + void basicXQueryToQtTypeCheck() const; + void basicQtToXQueryTypeCheck() const; + void bindNode() const; + void relativeBaseURI() const; + void emptyBaseURI() const; + void roundTripDateWithinQXmlItem() const; + void bindingMissing() const; + void bindDefaultConstructedItem() const; + void bindDefaultConstructedItem_data() const; + void bindEmptyNullString() const; + void bindEmptyString() const; + void rebindVariableSameType() const; + void rebindVariableDifferentType() const; + void rebindVariableWithNullItem() const; + void eraseQXmlItemBinding() const; + void eraseDeviceBinding() const; + void constCorrectness() const; + void objectSize() const; + void setUriResolver() const; + void uriResolver() const; + void messageXML() const; + void resultItemsDeallocatedQuery() const; + void copyCheckMessageHandler() const; + void shadowedVariables() const; + void setFocusQXmlItem() const; + void setFocusQUrl() const; + void setFocusQIODevice() const; + void setFocusQIODeviceAvoidVariableClash() const; + void setFocusQIODeviceFailure() const; + void setFocusQIODeviceTriggerWarnings() const; + void setFocusQString() const; + void setFocusQStringFailure() const; + void setFocusQStringSignature() const; + void recompilationWithEvaluateToResultFailing() const; + void secondEvaluationWithEvaluateToResultFailing() const; + void recompilationWithEvaluateToReceiver() const; + void fnDocOnQIODeviceTimeout() const; + void evaluateToQStringListOnInvalidQuery() const; + void evaluateToQStringList() const; + void evaluateToQStringListTriggerWarnings() const; + void evaluateToQStringList_data() const; + void evaluateToQStringListNoConversion() const; + void evaluateToQIODevice() const; + void evaluateToQIODeviceTriggerWarnings() const; + void evaluateToQIODeviceSignature() const; + void evaluateToQIODeviceOnInvalidQuery() const; + void setQueryQIODeviceQUrl() const; + void setQueryQIODeviceQUrlTriggerWarnings() const; + void setQueryQString() const; + void setQueryQUrlSuccess() const; + void setQueryQUrlSuccess_data() const; + void setQueryQUrlFailSucceed() const; + void setQueryQUrlFailure() const; + void setQueryQUrlFailure_data() const; + void setQueryQUrlBaseURI() const; + void setQueryQUrlBaseURI_data() const; + void setQueryWithNonExistentQUrlOnValidQuery() const; + void setQueryWithInvalidQueryFromQUrlOnValidQuery() const; + void retrieveNameFromQuery() const; + void retrieveNameFromQuery_data() const; + void cleanupTestCase() const; + void declareUnavailableExternal() const; + void msvcCacheIssue() const; + void unavailableExternalVariable() const; + void useUriResolver() const; + void queryWithFocusAndVariable() const; + void undefinedFocus() const; + void basicFocusUsage() const; + + void queryLanguage() const; + void queryLanguageSignature() const; + void enumQueryLanguage() const; + + void setNetworkAccessManager() const; + void networkAccessManagerSignature() const; + void networkAccessManagerDefaultValue() const; + void networkAccessManager() const; + + void setInitialTemplateNameQXmlName() const; + void setInitialTemplateNameQXmlNameSignature() const; + void setInitialTemplateNameQString() const; + void setInitialTemplateNameQStringSignature() const; + void initialTemplateName() const; + void initialTemplateNameSignature() const; + + void fnDocNetworkAccessSuccess() const; + void fnDocNetworkAccessSuccess_data() const; + void fnDocNetworkAccessFailure() const; + void fnDocNetworkAccessFailure_data() const; + void multipleDocsAndFocus() const; + void multipleEvaluationsWithDifferentFocus() const; + void bindVariableQXmlQuery() const; + void bindVariableQXmlQuery_data() const; + void bindVariableQStringQXmlQuerySignature() const; + void bindVariableQXmlNameQXmlQuerySignature() const; + void bindVariableQXmlNameQXmlQuery() const; + void bindVariableQXmlQueryInvalidate() const; + void unknownSourceLocation() const; + + void identityConstraintSuccess() const; + void identityConstraintFailure() const; + void identityConstraintFailure_data() const; + + // TODO call all URI resolving functions where 1) the URI resolver return a null QUrl(); 2) resolves into valid, existing URI, 3) invalid, non-existing URI. + // TODO bind stringlists, variant lists, both ways. + // TODO trigger serialization error, or any error in evaluateToushCallback(). + // TODO let items travle between two queries, as seen in the SDK + // TODO what happens if the query declares local variable and external ones are provided? + +private: + enum + { + /** + * One excluded, since we skip static-base-uri.xq. + */ + ExpectedQueryCount = 29 + }; + + static void checkBaseURI(const QUrl &baseURI, const QString &candidate); + static QStringList queries(); + static const char *const queriesDirectory; + + int m_generatedBaselines; + int m_pushTestsCount; + const bool m_testNetwork; +}; + +void tst_QXmlQuery::checkBaseURI(const QUrl &baseURI, const QString &candidate) +{ + /* The use of QFileInfo::canonicalFilePath() takes into account that drive letters + * on Windows may have different cases. */ + QVERIFY(QDir(baseURI.toLocalFile()).relativeFilePath(QFileInfo(candidate).canonicalFilePath()).startsWith("../")); +} + +const char *const tst_QXmlQuery::queriesDirectory = XMLPATTERNSDIR "/queries/"; + +QStringList tst_QXmlQuery::queries() +{ + QDir dir; + dir.cd(inputFile(QLatin1String(queriesDirectory))); + + return dir.entryList(QStringList(QLatin1String("*.xq"))); +} + +void tst_QXmlQuery::defaultConstructor() const +{ + /* Allocate instance in different orders. */ + { + QXmlQuery query; + } + + { + QXmlQuery query1; + QXmlQuery query2; + } + + { + QXmlQuery query1; + QXmlQuery query2; + QXmlQuery query3; + } +} + +void tst_QXmlQuery::copyConstructor() const +{ + /* Verify that we can take a const reference, and simply do a copy of a default constructed object. */ + { + const QXmlQuery query1; + QXmlQuery query2(query1); + } + + /* Copy twice. */ + { + const QXmlQuery query1; + QXmlQuery query2(query1); + QXmlQuery query3(query2); + } + + /* Verify that copying default values works. */ + { + const QXmlQuery query1; + const QXmlQuery query2(query1); + QCOMPARE(query2.messageHandler(), query1.messageHandler()); + QCOMPARE(query2.uriResolver(), query1.uriResolver()); + QCOMPARE(query2.queryLanguage(), query1.queryLanguage()); + QCOMPARE(query2.initialTemplateName(), query1.initialTemplateName()); + QCOMPARE(query2.networkAccessManager(), query1.networkAccessManager()); + } + + /* Check that the + * + * - name pool + * - URI resolver + * - message handler + * - query language + * - initial template name + * + * sticks with the copy. */ + { + MessageSilencer silencer; + TestURIResolver resolver; + QNetworkAccessManager networkManager; + QXmlQuery query1(QXmlQuery::XSLT20); + QXmlNamePool np1(query1.namePool()); + + query1.setMessageHandler(&silencer); + query1.setUriResolver(&resolver); + query1.setNetworkAccessManager(&networkManager); + + const QXmlName name(np1, QLatin1String("localName"), + QLatin1String("http://example.com/"), + QLatin1String("prefix")); + query1.setInitialTemplateName(name); + + const QXmlQuery query2(query1); + QCOMPARE(query2.messageHandler(), static_cast<QAbstractMessageHandler *>(&silencer)); + QCOMPARE(query2.uriResolver(), static_cast<const QAbstractUriResolver *>(&resolver)); + QCOMPARE(query2.queryLanguage(), QXmlQuery::XSLT20); + QCOMPARE(query2.initialTemplateName(), name); + QCOMPARE(query2.networkAccessManager(), &networkManager); + + QXmlNamePool np2(query2.namePool()); + + QCOMPARE(name.namespaceUri(np2), QString::fromLatin1("http://example.com/")); + QCOMPARE(name.localName(np2), QString::fromLatin1("localName")); + QCOMPARE(name.prefix(np2), QString::fromLatin1("prefix")); + } + + { + QXmlQuery original; + + original.setFocus(QXmlItem(4)); + original.setQuery(QLatin1String(".")); + QVERIFY(original.isValid()); + + const QXmlQuery copy(original); + + QXmlResultItems result; + copy.evaluateTo(&result); + QCOMPARE(result.next().toAtomicValue(), QVariant(4)); + QVERIFY(result.next().isNull()); + QVERIFY(!result.hasError()); + } + + /* Copy, set, compare. Check that copies are independent. */ + { + // TODO all members except queryLanguage(). + } +} + +void tst_QXmlQuery::constructorQXmlNamePool() const +{ + /* Check that the namepool we are passed, is actually used. */ + QXmlNamePool np; + + QXmlQuery query(np); + const QXmlName name(np, QLatin1String("localName"), + QLatin1String("http://example.com/"), + QLatin1String("prefix")); + + QXmlNamePool np2(query.namePool()); + QCOMPARE(name.namespaceUri(np2), QString::fromLatin1("http://example.com/")); + QCOMPARE(name.localName(np2), QString::fromLatin1("localName")); + QCOMPARE(name.prefix(np2), QString::fromLatin1("prefix")); +} + +/*! + Ensure that the internal variable loading mechanisms uses the user-supplied + name pool. + + If that is not the case, different name pools are used and the code crashes. + + \since 4.5 + */ +void tst_QXmlQuery::constructorQXmlNamePoolQueryLanguage() const +{ + QXmlNamePool np; + QXmlName name(np, QLatin1String("arbitraryName")); + + QXmlQuery query(QXmlQuery::XQuery10, np); + + QBuffer input; + input.setData("<yall/>"); + + QVERIFY(input.open(QIODevice::ReadOnly)); + query.bindVariable(name, &input); + query.setQuery("string(doc($arbitraryName))"); + + QStringList result; + query.evaluateTo(&result); +} + +void tst_QXmlQuery::constructorQXmlNamePoolWithinQSimpleXmlNodeModel() const +{ + class TestCTOR : public TestSimpleNodeModel + { + public: + TestCTOR(const QXmlNamePool &np) : TestSimpleNodeModel(np) + { + } + + void checkCTOR() const + { + /* If this fails to compile, the constructor has trouble with taking + * a mutable reference. + * + * The reason we use the this pointer explicitly, is to avoid a compiler + * warnings with MSVC 2005. */ + QXmlQuery(this->namePool()); + } + }; + + QXmlNamePool np; + TestCTOR ctor(np); + ctor.checkCTOR(); +} + +void tst_QXmlQuery::assignmentOperator() const +{ + class ReturnURI : public QAbstractUriResolver + { + public: + ReturnURI() {} + virtual QUrl resolve(const QUrl &relative, + const QUrl &baseURI) const + { + return baseURI.resolved(relative); + } + }; + + /* Assign this to this. */ + { + QXmlQuery query; + query = query; + query = query; + query = query; + + /* Just call a couple of functions to give valgrind + * something to check. */ + QVERIFY(!query.isValid()); + query.messageHandler(); + } + + /* Assign null instances a couple of times. */ + { + QXmlQuery query1; + QXmlQuery query2; + query1 = query2; + query1 = query2; + query1 = query2; + + /* Just call a couple of functions to give valgrind + * something to check. */ + QVERIFY(!query1.isValid()); + query1.messageHandler(); + + /* Just call a couple of functions to give valgrind + * something to check. */ + QVERIFY(!query2.isValid()); + query2.messageHandler(); + } + + /* Create a query, set all the things it stores, and ensure it + * travels over to the new instance. */ + { + MessageSilencer silencer; + const ReturnURI returnURI; + QXmlNamePool namePool; + + QBuffer documentDevice; + documentDevice.setData(QByteArray("<e>a</e>")); + QVERIFY(documentDevice.open(QIODevice::ReadOnly)); + + QXmlQuery original(namePool); + QXmlName testName(namePool, QLatin1String("somethingToCheck")); + + original.setMessageHandler(&silencer); + original.bindVariable(QLatin1String("var"), QXmlItem(1)); + original.bindVariable(QLatin1String("device"), &documentDevice); + original.setUriResolver(&returnURI); + original.setFocus(QXmlItem(3)); + original.setQuery(QLatin1String("$var, 1 + 1, ., string(doc($device))")); + + /* Do a copy, and check that everything followed on into the copy. No modification + * of the copy. */ + { + QXmlQuery copy; + + /* We use assignment operator, not copy constructor. */ + copy = original; + + QVERIFY(copy.isValid()); + QCOMPARE(copy.uriResolver(), static_cast<const QAbstractUriResolver *>(&returnURI)); + QCOMPARE(copy.messageHandler(), static_cast<QAbstractMessageHandler *>(&silencer)); + QCOMPARE(testName.localName(copy.namePool()), QString::fromLatin1("somethingToCheck")); + + QXmlResultItems result; + copy.evaluateTo(&result); + QCOMPARE(result.next().toAtomicValue(), QVariant(1)); + QCOMPARE(result.next().toAtomicValue(), QVariant(2)); + QCOMPARE(result.next().toAtomicValue(), QVariant(3)); + QCOMPARE(result.next().toAtomicValue(), QVariant(QString::fromLatin1("a"))); + QVERIFY(result.next().isNull()); + QVERIFY(!result.hasError()); + } + + /* Copy, and change values. Things should detach. */ + { + /* Evaluate the copy. */ + { + MessageSilencer secondSilencer; + const ReturnURI secondUriResolver; + QBuffer documentDeviceCopy; + documentDeviceCopy.setData(QByteArray("<e>b</e>")); + QVERIFY(documentDeviceCopy.open(QIODevice::ReadOnly)); + + QXmlQuery copy; + copy = original; + + copy.setMessageHandler(&secondSilencer); + /* Here we rebind variable values. */ + copy.bindVariable(QLatin1String("var"), QXmlItem(4)); + copy.bindVariable(QLatin1String("device"), &documentDeviceCopy); + copy.setUriResolver(&secondUriResolver); + copy.setFocus(QXmlItem(6)); + copy.setQuery(QLatin1String("$var, 1 + 1, ., string(doc($device))")); + + /* Check that the copy picked up the new things. */ + QVERIFY(copy.isValid()); + QCOMPARE(copy.uriResolver(), static_cast<const QAbstractUriResolver *>(&secondUriResolver)); + QCOMPARE(copy.messageHandler(), static_cast<QAbstractMessageHandler *>(&secondSilencer)); + + QXmlResultItems resultCopy; + copy.evaluateTo(&resultCopy); + QCOMPARE(resultCopy.next().toAtomicValue(), QVariant(4)); + QCOMPARE(resultCopy.next().toAtomicValue(), QVariant(2)); + QCOMPARE(resultCopy.next().toAtomicValue(), QVariant(6)); + const QString stringedDevice(resultCopy.next().toAtomicValue().toString()); + QCOMPARE(stringedDevice, QString::fromLatin1("b")); + QVERIFY(resultCopy.next().isNull()); + QVERIFY(!resultCopy.hasError()); + } + + /* Evaluate the original. */ + { + /* Check that the original is unchanged. */ + QVERIFY(original.isValid()); + QCOMPARE(original.uriResolver(), static_cast<const QAbstractUriResolver *>(&returnURI)); + QCOMPARE(original.messageHandler(), static_cast<QAbstractMessageHandler *>(&silencer)); + + QXmlResultItems resultOriginal; + original.evaluateTo(&resultOriginal); + QCOMPARE(resultOriginal.next().toAtomicValue(), QVariant(1)); + QCOMPARE(resultOriginal.next().toAtomicValue(), QVariant(2)); + QCOMPARE(resultOriginal.next().toAtomicValue(), QVariant(3)); + QCOMPARE(resultOriginal.next().toAtomicValue(), QVariant(QString::fromLatin1("a"))); + QVERIFY(resultOriginal.next().isNull()); + QVERIFY(!resultOriginal.hasError()); + } + } + } +} + +/*! + Since QXmlQuery doesn't seek devices to position 0, this code triggers a bug + where document caching doesn't work. Since the document caching doesn't work, + the device will be read twice, and the second time the device is at the end, + hence premature end of document. + */ +void tst_QXmlQuery::sequentialExecution() const +{ + QBuffer inBuffer; + inBuffer.setData(QByteArray("<input/>")); + QVERIFY(inBuffer.open(QIODevice::ReadOnly)); + + QXmlQuery query; + query.bindVariable("inputDocument", &inBuffer); + + QByteArray outArray; + QBuffer outBuffer(&outArray); + outBuffer.open(QIODevice::WriteOnly); + + const QString queryString(QLatin1String("doc($inputDocument)")); + query.setQuery(queryString); + + QXmlFormatter formatter(query, &outBuffer); + + QVERIFY(query.evaluateTo(&formatter)); + + /* If this line is removed, the bug isn't triggered. */ + query.setQuery(queryString); + + QVERIFY(query.evaluateTo(&formatter)); +} + +void tst_QXmlQuery::isValid() const +{ + /* Check default value. */ + QXmlQuery query; + QVERIFY(!query.isValid()); +} + +void tst_QXmlQuery::bindVariableQString() const +{ + { + QXmlQuery query; + /* Bind with a null QXmlItem. */ + query.bindVariable(QLatin1String("name"), QXmlItem()); + } + + { + QXmlQuery query; + /* Bind with a null QVariant. */ + query.bindVariable(QLatin1String("name"), QXmlItem(QVariant())); + } + + { + QXmlQuery query; + /* Bind with a null QXmlNodeModelIndex. */ + query.bindVariable(QLatin1String("name"), QXmlItem(QXmlNodeModelIndex())); + } +} + +void tst_QXmlQuery::bindVariableQStringNoExternalDeclaration() const +{ + QXmlQuery query; + query.bindVariable(QLatin1String("foo"), QXmlItem(QLatin1String("Variable Value"))); + query.setQuery(QLatin1String("$foo")); + + QVERIFY(query.isValid()); + + QStringList result; + QVERIFY(query.evaluateTo(&result)); + + QCOMPARE(result, QStringList() << QLatin1String("Variable Value")); +} + +void tst_QXmlQuery::bindVariableQXmlName() const +{ + // TODO +} + +void tst_QXmlQuery::bindVariableQXmlNameTriggerWarnings() const +{ + QXmlQuery query; + + QTest::ignoreMessage(QtWarningMsg, "The variable name cannot be null."); + query.bindVariable(QXmlName(), QVariant()); +} + +void tst_QXmlQuery::bindVariableQStringQIODeviceWithByteArray() const +{ + QXmlQuery query; + + QByteArray in("<e/>"); + QBuffer device(&in); + QVERIFY(device.open(QIODevice::ReadOnly)); + + query.bindVariable("doc", &device); + + query.setQuery(QLatin1String("declare variable $doc external; $doc")); + + QVERIFY(query.isValid()); + + /* Check the URI corresponding to the variable. */ + { + QXmlResultItems items; + query.evaluateTo(&items); + + QCOMPARE(items.next().toAtomicValue().toString(), QString::fromLatin1("tag:trolltech.com,2007:QtXmlPatterns:QIODeviceVariable:doc")); + } + + /* Now, actually load the document. We use the same QXmlQuery just to stress recompilation a bit. */ + { + query.setQuery(QLatin1String("declare variable $doc external; doc($doc)")); + + QByteArray out; + QBuffer outBuffer(&out); + QVERIFY(outBuffer.open(QIODevice::WriteOnly)); + + QXmlSerializer serializer(query, &outBuffer); + + QVERIFY(query.evaluateTo(&serializer)); + QCOMPARE(out, in); + } +} + +void tst_QXmlQuery::bindVariableQStringQIODeviceWithString() const +{ + QXmlQuery query; + + QString in("<qstring/>"); + QByteArray inUtf8(in.toUtf8()); + QBuffer inDevice(&inUtf8); + + QVERIFY(inDevice.open(QIODevice::ReadOnly)); + + query.bindVariable("doc", &inDevice); + + query.setQuery(QLatin1String("declare variable $doc external; doc($doc)")); + + QByteArray out; + QBuffer outBuffer(&out); + QVERIFY(outBuffer.open(QIODevice::WriteOnly)); + + QXmlSerializer serializer(query, &outBuffer); + QVERIFY(query.evaluateTo(&serializer)); + + QCOMPARE(out, inUtf8); +} + +void tst_QXmlQuery::bindVariableQStringQIODeviceWithQFile() const +{ + QXmlQuery query; + QFile inDevice(QLatin1String(SRCDIR "input.xml")); + + QVERIFY(inDevice.open(QIODevice::ReadOnly)); + + query.bindVariable("doc", &inDevice); + + query.setQuery(QLatin1String("declare variable $doc external; doc($doc)")); + + QByteArray out; + QBuffer outBuffer(&out); + QVERIFY(outBuffer.open(QIODevice::WriteOnly)); + + QXmlSerializer serializer(query, &outBuffer); + QVERIFY(query.evaluateTo(&serializer)); + outBuffer.close(); + + QCOMPARE(out, QByteArray("<!-- This is just a file for testing. --><input/>")); +} + +void tst_QXmlQuery::bindVariableQStringQIODevice() const +{ + QXmlQuery query; + + /* Rebind the variable. */ + { + /* First evaluation. */ + { + QByteArray in1("<e1/>"); + QBuffer inDevice1(&in1); + QVERIFY(inDevice1.open(QIODevice::ReadOnly)); + + query.bindVariable("in", &inDevice1); + query.setQuery(QLatin1String("doc($in)")); + + QByteArray out1; + QBuffer outDevice1(&out1); + QVERIFY(outDevice1.open(QIODevice::WriteOnly)); + + QXmlSerializer serializer(query, &outDevice1); + query.evaluateTo(&serializer); + QCOMPARE(out1, in1); + } + + /* Second evaluation, rebind variable. */ + { + QByteArray in2("<e2/>"); + QBuffer inDevice2(&in2); + QVERIFY(inDevice2.open(QIODevice::ReadOnly)); + + query.bindVariable(QLatin1String("in"), &inDevice2); + + QByteArray out2; + QBuffer outDevice2(&out2); + QVERIFY(outDevice2.open(QIODevice::WriteOnly)); + + QXmlSerializer serializer(query, &outDevice2); + QVERIFY(query.evaluateTo(&serializer)); + QCOMPARE(out2, in2); + } + } + + // TODO trigger recompilation when setting qiodevices., and qiodevice overwritten by other type, etc. +} + +void tst_QXmlQuery::bindVariableQXmlNameQIODevice() const +{ + // TODO +} + +void tst_QXmlQuery::bindVariableQXmlNameQIODeviceTriggerWarnings() const +{ + QXmlNamePool np; + QXmlQuery query(np); + + QBuffer buffer; + QTest::ignoreMessage(QtWarningMsg, "A null, or readable QIODevice must be passed."); + query.bindVariable(QXmlName(np, QLatin1String("foo")), &buffer); + + QTest::ignoreMessage(QtWarningMsg, "The variable name cannot be null."); + query.bindVariable(QXmlName(), 0); +} + +void tst_QXmlQuery::bindVariableXSLTSuccess() const +{ + QXmlQuery stylesheet(QXmlQuery::XSLT20); + stylesheet.setInitialTemplateName(QLatin1String("main")); + + stylesheet.bindVariable(QLatin1String("variableNoSelectNoBodyBoundWithBindVariable"), + QVariant(QLatin1String("MUST NOT SHOW 1"))); + + stylesheet.bindVariable(QLatin1String("variableSelectBoundWithBindVariable"), + QVariant(QLatin1String("MUST NOT SHOW 2"))); + + stylesheet.bindVariable(QLatin1String("variableSelectWithTypeIntBoundWithBindVariable"), + QVariant(QLatin1String("MUST NOT SHOW 3"))); + + stylesheet.bindVariable(QLatin1String("paramNoSelectNoBodyBoundWithBindVariable"), + QVariant(QLatin1String("param1"))); + + stylesheet.bindVariable(QLatin1String("paramNoSelectNoBodyBoundWithBindVariableRequired"), + QVariant(QLatin1String("param1"))); + + stylesheet.bindVariable(QLatin1String("paramSelectBoundWithBindVariable"), + QVariant(QLatin1String("param2"))); + + stylesheet.bindVariable(QLatin1String("paramSelectBoundWithBindVariableRequired"), + QVariant(QLatin1String("param3"))); + + stylesheet.bindVariable(QLatin1String("paramSelectWithTypeIntBoundWithBindVariable"), + QVariant(4)); + + stylesheet.bindVariable(QLatin1String("paramSelectWithTypeIntBoundWithBindVariableRequired"), + QVariant(QLatin1String("param5"))); + + stylesheet.setQuery(QUrl(inputFileAsURI(QLatin1String(XMLPATTERNSDIR "/stylesheets/parameters.xsl")))); + + QVERIFY(stylesheet.isValid()); + + QBuffer deviceOut; + QVERIFY(deviceOut.open(QIODevice::ReadWrite)); + + QVERIFY(stylesheet.evaluateTo(&deviceOut)); + + const QString result(QString::fromUtf8(deviceOut.data().constData())); + + QCOMPARE(result, + QString::fromLatin1("Variables: variableSelectsDefaultValue variableSelectsDefaultValue2 3 4 " + "Parameters: param1 param1 param2 param3 4 param5")); +} + +void tst_QXmlQuery::bindVariableTemporaryNode() const +{ + /* First we do it with QXmlResultItems staying in scope. */; + { + QXmlQuery query1; + query1.setQuery("<anElement/>"); + + QXmlResultItems result1; + query1.evaluateTo(&result1); + + QXmlQuery query2(query1); + query2.bindVariable("fromQuery1", result1.next()); + query2.setQuery("$fromQuery1"); + + QString output; + QVERIFY(query2.evaluateTo(&output)); + + QCOMPARE(output, QString::fromLatin1("<anElement/>\n")); + } + + /* And now with it deallocating, so its internal DynamicContext pointer is + * released. This doesn't work in Qt 4.5 and is ok. */ + { + QXmlQuery query1; + query1.setQuery("<anElement/>"); + + QXmlQuery query2; + + { + QXmlResultItems result1; + query1.evaluateTo(&result1); + + query2.bindVariable("fromQuery1", result1.next()); + query2.setQuery("$fromQuery1"); + } + + QString output; + return; // See comment above. + QVERIFY(query2.evaluateTo(&output)); + + QCOMPARE(output, QString::fromLatin1("<anElement/>\n")); + } +} + +void tst_QXmlQuery::messageHandler() const +{ + { + /* Check default value. */ + QXmlQuery query; + QCOMPARE(query.messageHandler(), static_cast<QAbstractMessageHandler *>(0)); + } +} + +void tst_QXmlQuery::setMessageHandler() const +{ + QXmlQuery query; + MessageSilencer silencer; + query.setMessageHandler(&silencer); + QCOMPARE(static_cast<QAbstractMessageHandler *>(&silencer), query.messageHandler()); +} + +void tst_QXmlQuery::evaluateToReceiver() +{ + QFETCH(QString, inputQuery); + + /* This query prints a URI specific to the local system. */ + if(inputQuery == QLatin1String("static-base-uri.xq")) + return; + + ++m_pushTestsCount; + const QString queryURI(inputFile(QLatin1String(queriesDirectory) + inputQuery)); + QFile queryFile(queryURI); + + QVERIFY(queryFile.exists()); + QVERIFY(queryFile.open(QIODevice::ReadOnly)); + + QXmlQuery query; + + MessageSilencer receiver; + query.setMessageHandler(&receiver); + query.setQuery(&queryFile, QUrl::fromLocalFile(queryURI)); + + /* We read all the queries, and some of them are invalid. However, we + * only want those that compile. */ + if(!query.isValid()) + return; + + QString produced; + QTextStream stream(&produced, QIODevice::WriteOnly); + PushBaseliner push(stream, query.namePool()); + query.evaluateTo(&push); + + const QString baselineName(inputFile(QLatin1String(SRCDIR "pushBaselines/") + inputQuery.left(inputQuery.length() - 2) + QString::fromLatin1("ref"))); + QFile baseline(baselineName); + + if(baseline.exists()) + { + QVERIFY(baseline.open(QIODevice::ReadOnly | QIODevice::Text)); + const QString stringedBaseline(QString::fromUtf8(baseline.readAll())); + QCOMPARE(produced, stringedBaseline); + } + else + { + QVERIFY(baseline.open(QIODevice::WriteOnly)); + /* This is intentionally a warning, don't remove it. Update the baselines instead. */ + qWarning() << "Generated baseline for:" << baselineName; + ++m_generatedBaselines; + + baseline.write(produced.toUtf8()); + } +} + +void tst_QXmlQuery::evaluateToReceiver_data() const +{ + QTest::addColumn<QString>("inputQuery"); + + const QStringList qs(queries()); + + for(int i = 0; i < qs.size(); ++i) + { + /* This outputs a URI specific to the environment, so we can't use it for this + * particular test. */ + if(qs.at(i) != QLatin1String("staticBaseURI.xq")) + QTest::newRow(qs.at(i).toUtf8().constData()) << qs.at(i); + } +} + +void tst_QXmlQuery::evaluateToReceiverOnInvalidQuery() const +{ + /* Invoke on a default constructed object. */ + { + QByteArray out; + QBuffer buffer(&out); + buffer.open(QIODevice::WriteOnly); + + QXmlQuery query; + QXmlSerializer serializer(query, &buffer); + QVERIFY(!query.evaluateTo(&serializer)); + } + + /* Invoke on an invalid query; compile time error. */ + { + QByteArray out; + QBuffer buffer(&out); + buffer.open(QIODevice::WriteOnly); + MessageSilencer silencer; + + QXmlQuery query; + query.setMessageHandler(&silencer); + query.setQuery(QLatin1String("1 + ")); + QXmlSerializer serializer(query, &buffer); + QVERIFY(!query.evaluateTo(&serializer)); + } + + /* Invoke on an invalid query; runtime error. */ + { + QByteArray out; + QBuffer buffer(&out); + buffer.open(QIODevice::WriteOnly); + MessageSilencer silencer; + + QXmlQuery query; + query.setMessageHandler(&silencer); + query.setQuery(QLatin1String("error()")); + QXmlSerializer serializer(query, &buffer); + QVERIFY(!query.evaluateTo(&serializer)); + } +} + +void tst_QXmlQuery::evaluateToQStringTriggerError() const +{ + /* Invoke on a default constructed object. */ + { + QXmlQuery query; + QString out; + QVERIFY(!query.evaluateTo(&out)); + } + + /* Invoke on an invalid query; compile time error. */ + { + QXmlQuery query; + MessageSilencer silencer; + query.setMessageHandler(&silencer); + + query.setQuery(QLatin1String("1 + ")); + + QString out; + QVERIFY(!query.evaluateTo(&out)); + } + + /* Invoke on an invalid query; runtime error. */ + { + QXmlQuery query; + MessageSilencer silencer; + query.setMessageHandler(&silencer); + + query.setQuery(QLatin1String("error()")); + + QString out; + QVERIFY(!query.evaluateTo(&out)); + } +} + +void tst_QXmlQuery::evaluateToQString() const +{ + QFETCH(QString, query); + QFETCH(QString, expectedOutput); + + QXmlQuery queryInstance; + queryInstance.setQuery(query); + QVERIFY(queryInstance.isValid()); + + QString result; + QVERIFY(queryInstance.evaluateTo(&result)); + + QCOMPARE(result, expectedOutput); +} + +void tst_QXmlQuery::evaluateToQString_data() const +{ + QTest::addColumn<QString>("query"); + QTest::addColumn<QString>("expectedOutput"); + + QTest::newRow("Two atomics") + << QString::fromLatin1("1, 'two'") + << QString::fromLatin1("1 two\n"); + + QTest::newRow("An element") + << QString::fromLatin1("<e>{1}</e>") + << QString::fromLatin1("<e>1</e>\n"); +} + +void tst_QXmlQuery::evaluateToQStringSignature() const +{ + const QXmlQuery query; + + QString output; + + /* evaluateTo(QString *) should be a const function. */ + query.evaluateTo(&output); +} + +void tst_QXmlQuery::evaluateToQAbstractXmlReceiverTriggerWarnings() const +{ + QXmlQuery query; + + /* We check the return value as well as warning message here. */ + QTest::ignoreMessage(QtWarningMsg, "A non-null callback must be passed."); + QCOMPARE(query.evaluateTo(static_cast<QAbstractXmlReceiver *>(0)), + false); +} + +void tst_QXmlQuery::evaluateToQXmlResultItems() const +{ + /* Invoke on a default constructed object. */ + { + QXmlQuery query; + QXmlResultItems result; + query.evaluateTo(&result); + QVERIFY(result.next().isNull()); + } +} + +void tst_QXmlQuery::evaluateToQXmlResultItemsTriggerWarnings() const +{ + QTest::ignoreMessage(QtWarningMsg, "A null pointer cannot be passed."); + QXmlQuery query; + query.evaluateTo(static_cast<QXmlResultItems *>(0)); +} + +void tst_QXmlQuery::evaluateToQXmlResultItemsErrorAtEnd() const +{ + QXmlQuery query; + MessageSilencer silencer; + query.setMessageHandler(&silencer); + query.setQuery(QLatin1String("1 to 100, fn:error()")); + QVERIFY(query.isValid()); + + QXmlResultItems it; + query.evaluateTo(&it); + + while(!it.next().isNull()) + { + } +} + +/*! + If baselines were generated, we flag it as a failure such that it gets + attention, and that they are adjusted accordingly. + */ +void tst_QXmlQuery::checkGeneratedBaselines() const +{ + QCOMPARE(m_generatedBaselines, 0); + + /* If this check fails, the auto test setup is misconfigured, or files have + * been added/removed without this number being updated. */ + QCOMPARE(m_pushTestsCount, int(ExpectedQueryCount)); +} + +void tst_QXmlQuery::basicXQueryToQtTypeCheck() const +{ + QFile queryFile(QLatin1String(queriesDirectory) + QString::fromLatin1("allAtomics.xq")); + QVERIFY(queryFile.open(QIODevice::ReadOnly)); + + QXmlQuery query; + query.setQuery(&queryFile); + QVERIFY(query.isValid()); + + QXmlResultItems it; + query.evaluateTo(&it); + + QVariantList expectedValues; + expectedValues.append(QString::fromLatin1("xs:untypedAtomic")); + expectedValues.append(QDateTime(QDate(2002, 10, 10), QTime(23, 2, 11), Qt::UTC)); + expectedValues.append(QDate(2002, 10, 10)); + expectedValues.append(QVariant()); /* We currently doesn't support xs:time through the API. */ + + expectedValues.append(QVariant()); /* xs:duration */ + expectedValues.append(QVariant()); /* xs:dayTimeDuration */ + expectedValues.append(QVariant()); /* xs:yearMonthDuration */ + + if(sizeof(qreal) == sizeof(float)) {//ARM casts to Float not to double + expectedValues.append(QVariant(float(3e3))); /* xs:float */ + expectedValues.append(QVariant(float(4e4))); /* xs:double */ + expectedValues.append(QVariant(float(2))); /* xs:decimal */ + } else { + expectedValues.append(QVariant(double(3e3))); /* xs:float */ + expectedValues.append(QVariant(double(4e4))); /* xs:double */ + expectedValues.append(QVariant(double(2))); /* xs:decimal */ + } + + /* xs:integer and its sub-types. */ + expectedValues.append(QVariant(qlonglong(16))); + expectedValues.append(QVariant(qlonglong(-6))); + expectedValues.append(QVariant(qlonglong(-4))); + expectedValues.append(QVariant(qlonglong(5))); + expectedValues.append(QVariant(qlonglong(6))); + expectedValues.append(QVariant(qlonglong(7))); + expectedValues.append(QVariant(qlonglong(8))); + expectedValues.append(QVariant(qlonglong(9))); + expectedValues.append(QVariant(qulonglong(10))); + expectedValues.append(QVariant(qlonglong(11))); + expectedValues.append(QVariant(qlonglong(12))); + expectedValues.append(QVariant(qlonglong(13))); + expectedValues.append(QVariant(qlonglong(14))); + + expectedValues.append(QVariant()); /* xs:gYearMonth("1976-02"), */ + expectedValues.append(QVariant()); /* xs:gYear("2005-12:00"), */ + expectedValues.append(QVariant()); /* xs:gMonthDay("--12-25-14:00"), */ + expectedValues.append(QVariant()); /* xs:gDay("---25-14:00"), */ + expectedValues.append(QVariant()); /* xs:gMonth("--12-14:00"), */ + expectedValues.append(true); /* xs:boolean("true"), */ + expectedValues.append(QVariant(QByteArray::fromBase64(QByteArray("aaaa")))); /* xs:base64Binary("aaaa"), */ + expectedValues.append(QVariant(QByteArray::fromHex(QByteArray("FFFF")))); /* xs:hexBinary("FFFF"), */ + expectedValues.append(QVariant(QString::fromLatin1("http://example.com/"))); /* xs:anyURI("http://example.com/"), */ + QXmlNamePool np(query.namePool()); + expectedValues.append(QVariant(qVariantFromValue(QXmlName(np, QLatin1String("localName"), + QLatin1String("http://example.com/2"), + QLatin1String("prefix"))))); + + expectedValues.append(QVariant(QString::fromLatin1("An xs:string"))); + expectedValues.append(QVariant(QString::fromLatin1("normalizedString"))); + expectedValues.append(QVariant(QString::fromLatin1("token"))); + expectedValues.append(QVariant(QString::fromLatin1("language"))); + expectedValues.append(QVariant(QString::fromLatin1("NMTOKEN"))); + expectedValues.append(QVariant(QString::fromLatin1("Name"))); + expectedValues.append(QVariant(QString::fromLatin1("NCName"))); + expectedValues.append(QVariant(QString::fromLatin1("ID"))); + expectedValues.append(QVariant(QString::fromLatin1("IDREF"))); + expectedValues.append(QVariant(QString::fromLatin1("ENTITY"))); + + int i = 0; + QXmlItem item(it.next()); + + while(!item.isNull()) + { + QVERIFY(item.isAtomicValue()); + const QVariant produced(item.toAtomicValue()); + + const QVariant &expected = expectedValues.at(i); + + /* For the cases where we can't represent a value in the XDM with Qt, + * we return an invalid QVariant. */ + QCOMPARE(expected.isValid(), produced.isValid()); + + QCOMPARE(produced.type(), expected.type()); + + if(expected.isValid()) + { + /* This is only needed for xs:decimal though, for some reason. Probably + * just artifacts created somewhere. */ + if(produced.type() == QVariant::Double) + QVERIFY(qFuzzyCompare(produced.toDouble(), expected.toDouble())); + else if(qVariantCanConvert<QXmlName>(produced)) + { + /* QVariant::operator==() does identity comparison, it doesn't delegate to operator==() of + * the contained type, unless it's hardcoded into QVariant. */ + const QXmlName n1 = qVariantValue<QXmlName>(produced); + const QXmlName n2 = qVariantValue<QXmlName>(expected); + QCOMPARE(n1, n2); + } + else + QCOMPARE(produced, expected); + } + + ++i; + item = it.next(); + } + + QCOMPARE(i, expectedValues.count()); +} + +/*! + Send values from Qt into XQuery. + */ +void tst_QXmlQuery::basicQtToXQueryTypeCheck() const +{ + QFile queryFile(QLatin1String(queriesDirectory) + QLatin1String("allAtomicsExternally.xq")); + QVERIFY(queryFile.exists()); + QVERIFY(queryFile.open(QIODevice::ReadOnly)); + + QCOMPARE(QVariant(QDate(1999, 9, 10)).type(), QVariant::Date); + + QXmlQuery query; + + QXmlNamePool np(query.namePool()); + + const QXmlName name(np, QLatin1String("localname"), + QLatin1String("http://example.com"), + QLatin1String("prefix")); + + query.bindVariable(QLatin1String("fromQUrl"), QXmlItem(QUrl(QString::fromLatin1("http://example.com/")))); + query.bindVariable(QLatin1String("fromQByteArray"), QXmlItem(QByteArray("AAAA"))); + query.bindVariable(QLatin1String("fromBool"), QXmlItem(bool(true))); + query.bindVariable(QLatin1String("fromQDate"), QXmlItem(QDate(2000, 10, 11))); + // TODO Do with different QDateTime time specs + query.bindVariable(QLatin1String("fromQDateTime"), QXmlItem(QDateTime(QDate(2001, 9, 10), QTime(1, 2, 3)))); + query.bindVariable(QLatin1String("fromDouble"), QXmlItem(double(3))); + query.bindVariable(QLatin1String("fromFloat"), QXmlItem(float(4))); + query.bindVariable(QLatin1String("integer"), QXmlItem(5)); + query.bindVariable(QLatin1String("fromQString"), QXmlItem(QString::fromLatin1("A QString"))); + query.bindVariable(QLatin1String("fromQChar"), QXmlItem(QChar::fromLatin1('C'))); + + query.bindVariable(QLatin1String("fromIntLiteral"), QXmlItem(QVariant(654))); + + { + QVariant ui(uint(5)); + QCOMPARE(ui.type(), QVariant::UInt); + query.bindVariable(QLatin1String("fromUInt"), ui); + } + + { + QVariant ulnglng(qulonglong(6)); + QCOMPARE(ulnglng.type(), QVariant::ULongLong); + query.bindVariable(QLatin1String("fromULongLong"), ulnglng); + } + + { + QVariant qlnglng(qlonglong(7)); + QCOMPARE(qlnglng.type(), QVariant::LongLong); + query.bindVariable(QLatin1String("fromLongLong"), qlnglng); + } + + query.setQuery(&queryFile); + + // TODO do queries which declares external variables with types. Tons of combos here. + // TODO ensure that binding with QXmlItem() doesn't make a binding available. + // TODO test rebinding a variable. + + QVERIFY(query.isValid()); + + QXmlResultItems it; + query.evaluateTo(&it); + QXmlItem item(it.next()); + QVERIFY(!item.isNull()); + QVERIFY(item.isAtomicValue()); + + if(sizeof(qreal) == sizeof(float)) //ARM casts to Float not to double + QCOMPARE(item.toAtomicValue().toString(), + QLatin1String("4 true 3 654 7 41414141 C 2000-10-11Z 2001-09-10T01:02:03 " + "A QString http://example.com/ 5 6 true false false true true true true true true true " + "true true true")); + else + QCOMPARE(item.toAtomicValue().toString(), + QLatin1String("4 true 3 654 7 41414141 C 2000-10-11Z 2001-09-10T01:02:03 " + "A QString http://example.com/ 5 6 true true true true true true true true true true " + "true true true")); + +} + +void tst_QXmlQuery::bindNode() const +{ + QXmlQuery query; + TestSimpleNodeModel nodeModel(query.namePool()); + + query.bindVariable(QLatin1String("node"), nodeModel.root()); + QByteArray out; + QBuffer buff(&out); + QVERIFY(buff.open(QIODevice::WriteOnly)); + + query.setQuery(QLatin1String("declare variable $node external; $node")); + QXmlSerializer serializer(query, &buff); + + QVERIFY(query.evaluateTo(&serializer)); + QCOMPARE(out, QByteArray("<nodeName/>")); +} + +/*! + Pass in a relative URI, and make sure it is resolved against the current application directory. + */ +void tst_QXmlQuery::relativeBaseURI() const +{ + QXmlQuery query; + query.setQuery(QLatin1String("fn:static-base-uri()"), QUrl(QLatin1String("a/relative/uri.weirdExtension"))); + QVERIFY(query.isValid()); + + QByteArray result; + QBuffer buffer(&result); + QVERIFY(buffer.open(QIODevice::ReadWrite)); + + QXmlSerializer serializer(query, &buffer); + QVERIFY(query.evaluateTo(&serializer)); + + const QUrl loaded(QUrl::fromEncoded(result)); + QUrl appPath(QUrl::fromLocalFile(QCoreApplication::applicationFilePath())); + + QVERIFY(loaded.isValid()); + QVERIFY(appPath.isValid()); + QVERIFY(!loaded.isRelative()); + QVERIFY(!appPath.isRelative()); + + QFileInfo dir(appPath.toLocalFile()); + dir.setFile(QString()); + + /* We can't use QUrl::isParentOf() because it doesn't do what we want it to */ + if(!loaded.toLocalFile().startsWith(dir.absoluteFilePath())) + QTextStream(stderr) << "dir.absoluteFilePath():" << dir.absoluteFilePath() << "loaded.toLocalFile():" << loaded.toLocalFile(); + + checkBaseURI(loaded, dir.absoluteFilePath()); +} + +void tst_QXmlQuery::emptyBaseURI() const +{ + QXmlQuery query; + query.setQuery(QLatin1String("fn:static-base-uri()"), QUrl()); + QVERIFY(query.isValid()); + + QByteArray result; + QBuffer buffer(&result); + QVERIFY(buffer.open(QIODevice::ReadWrite)); + + QXmlSerializer serializer(query, &buffer); + QVERIFY(query.evaluateTo(&serializer)); + + const QUrl loaded(QUrl::fromEncoded(result)); + QUrl appPath(QUrl::fromLocalFile(QCoreApplication::applicationFilePath())); + + QVERIFY(loaded.isValid()); + QVERIFY(appPath.isValid()); + QVERIFY(!loaded.isRelative()); + QVERIFY(!appPath.isRelative()); + + QFileInfo dir(appPath.toLocalFile()); + dir.setFile(QString()); + + QCOMPARE(loaded, appPath); +} + +/*! + Ensure that QDate comes out as QDateTime. + */ +void tst_QXmlQuery::roundTripDateWithinQXmlItem() const +{ + const QDate date(1999, 9, 10); + QVERIFY(date.isValid()); + + const QVariant variant(date); + QVERIFY(variant.isValid()); + QCOMPARE(variant.type(), QVariant::Date); + + const QXmlItem item(variant); + QVERIFY(!item.isNull()); + QVERIFY(item.isAtomicValue()); + + const QVariant out(item.toAtomicValue()); + QVERIFY(out.isValid()); + QCOMPARE(out.type(), QVariant::Date); + QCOMPARE(out.toDate(), date); +} + +/*! + Check whether a query is valid, which uses an unbound variable. + */ +void tst_QXmlQuery::bindingMissing() const +{ + QXmlQuery query; + MessageSilencer messageHandler; + query.setMessageHandler(&messageHandler); + + QFile queryFile(QLatin1String(queriesDirectory) + QString::fromLatin1("externalVariable.xq")); + QVERIFY(queryFile.open(QIODevice::ReadOnly)); + query.setQuery(&queryFile); + + QVERIFY(!query.isValid()); +} + +void tst_QXmlQuery::bindDefaultConstructedItem() const +{ + QFETCH(QXmlItem, item); + + QXmlQuery query; + MessageSilencer messageHandler; + query.setMessageHandler(&messageHandler); + + QFile queryFile(QLatin1String(queriesDirectory) + QString::fromLatin1("externalVariable.xq")); + QVERIFY(queryFile.open(QIODevice::ReadOnly)); + query.setQuery(&queryFile); + query.bindVariable(QLatin1String("externalVariable"), item); + + QVERIFY(!query.isValid()); +} + +void tst_QXmlQuery::bindDefaultConstructedItem_data() const +{ + QTest::addColumn<QXmlItem>("item"); + + QTest::newRow("QXmlItem()") << QXmlItem(); + QTest::newRow("QXmlItem(QVariant())") << QXmlItem(QVariant()); + QTest::newRow("QXmlItem(QXmlNodeModelIndex())") << QXmlItem(QXmlNodeModelIndex()); +} + +/*! + Remove a binding by binding QXmlItem() with the same name. + */ +void tst_QXmlQuery::eraseQXmlItemBinding() const +{ + QXmlQuery query; + MessageSilencer messageHandler; + query.setMessageHandler(&messageHandler); + + QFile queryFile(QLatin1String(queriesDirectory) + QString::fromLatin1("externalVariable.xq")); + QVERIFY(queryFile.open(QIODevice::ReadOnly)); + query.bindVariable(QLatin1String("externalVariable"), QXmlItem(3)); + query.setQuery(&queryFile); + QVERIFY(query.isValid()); + + QByteArray result; + QBuffer buffer(&result); + QVERIFY(buffer.open(QIODevice::ReadWrite)); + + QXmlSerializer serializer(query, &buffer); + QVERIFY(query.evaluateTo(&serializer)); + + QCOMPARE(result, QByteArray("3 6<e>3</e>false")); + + query.bindVariable(QLatin1String("externalVariable"), QXmlItem()); + QVERIFY(!query.isValid()); +} + +/*! + Erase a variable binding + */ +void tst_QXmlQuery::eraseDeviceBinding() const +{ + /* Erase an existing QIODevice binding with another QIODevice binding. */ + { + QXmlQuery query; + + QByteArray doc("<e/>"); + QBuffer buffer(&doc); + QVERIFY(buffer.open(QIODevice::ReadOnly)); + + query.bindVariable(QLatin1String("in"), &buffer); + query.setQuery(QLatin1String("$in")); + QVERIFY(query.isValid()); + + query.bindVariable(QLatin1String("in"), 0); + QVERIFY(!query.isValid()); + } + + /* Erase an existing QXmlItem binding with another QIODevice binding. */ + { + QXmlQuery query; + + query.bindVariable(QLatin1String("in"), QXmlItem(5)); + query.setQuery(QLatin1String("$in")); + QVERIFY(query.isValid()); + + query.bindVariable(QLatin1String("in"), 0); + QVERIFY(!query.isValid()); + } +} + +/*! + Bind a variable, evaluate, bind with a different value but same type, and evaluate again. + */ +void tst_QXmlQuery::rebindVariableSameType() const +{ + QXmlQuery query; + MessageSilencer messageHandler; + query.setMessageHandler(&messageHandler); + + query.bindVariable(QLatin1String("externalVariable"), QXmlItem(3)); + + { + QFile queryFile(QLatin1String(queriesDirectory) + QString::fromLatin1("externalVariable.xq")); + QVERIFY(queryFile.open(QIODevice::ReadOnly)); + query.setQuery(&queryFile); + } + + QVERIFY(query.isValid()); + + { + QByteArray result; + QBuffer buffer(&result); + QVERIFY(buffer.open(QIODevice::ReadWrite)); + + QXmlSerializer serializer(query, &buffer); + QVERIFY(query.evaluateTo(&serializer)); + + QCOMPARE(result, QByteArray("3 6<e>3</e>false")); + } + + { + query.bindVariable(QLatin1String("externalVariable"), QXmlItem(5)); + QByteArray result; + QBuffer buffer(&result); + QVERIFY(buffer.open(QIODevice::ReadWrite)); + + QXmlSerializer serializer(query, &buffer); + QVERIFY(query.evaluateTo(&serializer)); + + QCOMPARE(result, QByteArray("5 8<e>5</e>false")); + } + +} + +void tst_QXmlQuery::rebindVariableDifferentType() const +{ + /* Rebind QXmlItem variable with QXmlItem variable. */ + { + QXmlQuery query; + query.bindVariable(QLatin1String("in"), QXmlItem(3)); + query.setQuery(QLatin1String("$in")); + QVERIFY(query.isValid()); + + query.bindVariable(QLatin1String("in"), QXmlItem("A string")); + QVERIFY(!query.isValid()); + } + + /* Rebind QIODevice variable with QXmlItem variable. */ + { + QXmlQuery query; + QBuffer buffer; + buffer.setData(QByteArray("<e/>")); + QVERIFY(buffer.open(QIODevice::ReadOnly)); + + query.bindVariable(QLatin1String("in"), &buffer); + query.setQuery(QLatin1String("$in")); + QVERIFY(query.isValid()); + + query.bindVariable(QLatin1String("in"), QXmlItem("A string")); + QVERIFY(!query.isValid()); + } + + /* Rebind QXmlItem variable with QIODevice variable. The type of the + * variable changes, so a recompile is necessary. */ + { + QXmlQuery query; + + query.bindVariable(QLatin1String("in"), QXmlItem(QLatin1String("A string"))); + query.setQuery(QLatin1String("$in")); + QVERIFY(query.isValid()); + + QBuffer buffer; + buffer.setData(QByteArray("<e/>")); + QVERIFY(buffer.open(QIODevice::ReadOnly)); + query.bindVariable(QLatin1String("in"), &buffer); + QVERIFY(!query.isValid()); + } +} + +void tst_QXmlQuery::rebindVariableWithNullItem() const +{ + QXmlQuery query; + + query.bindVariable(QLatin1String("name"), QXmlItem(5)); + query.bindVariable(QLatin1String("name"), QXmlItem()); +} + +void tst_QXmlQuery::constCorrectness() const +{ + QXmlResultItems result; + QXmlQuery tmp; + tmp.setQuery(QLatin1String("1")); /* Just so we have a valid query. */ + const QXmlQuery query(tmp); + + /* These functions should be const. */ + query.isValid(); + query.evaluateTo(&result); + query.namePool(); + query.uriResolver(); + query.messageHandler(); + + { + QString dummyString; + QTextStream dummyStream(&dummyString); + PushBaseliner dummy(dummyStream, query.namePool()); + query.evaluateTo(&dummy); + } +} + +void tst_QXmlQuery::objectSize() const +{ + /* We have a d pointer. */ + QCOMPARE(sizeof(QXmlQuery), sizeof(void *)); +} + +void tst_QXmlQuery::setUriResolver() const +{ + /* Set a null resolver, and make sure it can take a const pointer. */ + { + QXmlQuery query; + query.setUriResolver(static_cast<const QAbstractUriResolver *>(0)); + QCOMPARE(query.uriResolver(), static_cast<const QAbstractUriResolver *>(0)); + } + + { + TestURIResolver resolver; + QXmlQuery query; + query.setUriResolver(&resolver); + QCOMPARE(query.uriResolver(), static_cast<const QAbstractUriResolver *>(&resolver)); + } +} + +void tst_QXmlQuery::uriResolver() const +{ + /* Check default value. */ + { + QXmlQuery query; + QCOMPARE(query.uriResolver(), static_cast<const QAbstractUriResolver *>(0)); + } +} + +void tst_QXmlQuery::messageXML() const +{ + QXmlQuery query; + + MessageValidator messageValidator; + query.setMessageHandler(&messageValidator); + + query.setQuery(QLatin1String("1basicSyntaxError")); + + const QRegExp removeFilename(QLatin1String("Location: file:.*\\#")); + QVERIFY(removeFilename.isValid()); + + QVERIFY(messageValidator.success()); + QCOMPARE(messageValidator.received().remove(removeFilename), + QString::fromLatin1("Type:3\n" + "Description: <html xmlns='http://www.w3.org/1999/xhtml/'><body><p>syntax error, unexpected unknown keyword</p></body></html>\n" + "Identifier: http://www.w3.org/2005/xqt-errors#XPST0003\n" + "1,1")); +} + +/*! + 1. Allocate QXmlResultItems + 2. Allocate QXmlQuery + 3. evaluate to the QXmlResultItems instance + 4. Dellocate the QXmlQuery instance + 5. Ensure QXmlResultItems works + */ +void tst_QXmlQuery::resultItemsDeallocatedQuery() const +{ + QXmlResultItems result; + + { + QXmlQuery query; + query.setQuery(QLatin1String("1, 2, xs:integer(<e>3</e>)")); + query.evaluateTo(&result); + } + + QCOMPARE(result.next().toAtomicValue(), QVariant(1)); + QCOMPARE(result.next().toAtomicValue(), QVariant(2)); + QCOMPARE(result.next().toAtomicValue(), QVariant(3)); + QVERIFY(result.next().isNull()); + QVERIFY(!result.hasError()); +} + +/*! + 1. Bind variable with bindVariable() + 2. setQuery that has 'declare variable' with same name. + 3. Ensure the value inside the query is used. We don't guarantee this behavior + but that's what we lock. + */ +void tst_QXmlQuery::shadowedVariables() const +{ + QXmlQuery query; + query.bindVariable("varName", QXmlItem(3)); + query.setQuery(QLatin1String("declare variable $varName := 5; $varName")); + + QXmlResultItems result; + query.evaluateTo(&result); + + QCOMPARE(result.next().toAtomicValue(), QVariant(5)); +} + +void tst_QXmlQuery::setFocusQXmlItem() const +{ + /* Make sure we can take a const reference. */ + { + QXmlQuery query; + const QXmlItem item; + query.setFocus(item); + } + + // TODO evaluate with atomic value, check type + // TODO evaluate with node, check type + // TODO ensure that setFocus() triggers query recompilation, as appropriate. + // TODO let the focus be undefined, call isvalid, call evaluate anyway + // TODO let the focus be undefined, call evaluate directly +} + +void tst_QXmlQuery::setFocusQUrl() const +{ + /* Load a focus which isn't well-formed. */ + { + QXmlQuery query; + MessageSilencer silencer; + + query.setMessageHandler(&silencer); + + QVERIFY(!query.setFocus(QUrl(QLatin1String("data/notWellformed.xml")))); + } + + /* Ensure the same URI resolver is used. */ + { + QXmlQuery query(QXmlQuery::XSLT20); + + const TestURIResolver resolver(QUrl(inputFileAsURI(QLatin1String(XMLPATTERNSDIR "/stylesheets/documentElement.xml")))); + query.setUriResolver(&resolver); + + QVERIFY(query.setFocus(QUrl(QLatin1String("arbitraryURI")))); + query.setQuery(QUrl(inputFileAsURI(QLatin1String(XMLPATTERNSDIR "/stylesheets/copyWholeDocument.xsl")))); + QVERIFY(query.isValid()); + + QBuffer result; + QVERIFY(result.open(QIODevice::ReadWrite)); + QXmlSerializer serializer(query, &result); + query.evaluateTo(&serializer); + + QCOMPARE(result.data(), QByteArray("<doc/>")); + } + + // TODO ensure that the focus type doesn't change from XSLT20 on the main instance. +} + +/*! + This code poses a challenge wrt. to internal caching. + */ +void tst_QXmlQuery::setFocusQIODevice() const +{ + QXmlQuery query; + + { + QBuffer focus; + focus.setData(QByteArray("<e>abc</e>")); + QVERIFY(focus.open(QIODevice::ReadOnly)); + query.setFocus(&focus); + query.setQuery(QLatin1String("string()")); + QVERIFY(query.isValid()); + + QString output; + query.evaluateTo(&output); + + QCOMPARE(output, QString::fromLatin1("abc\n")); + } + + /* Set a new focus, make sure it changes & works. */ + { + QBuffer focus2; + focus2.setData(QByteArray("<e>abc2</e>")); + QVERIFY(focus2.open(QIODevice::ReadOnly)); + query.setFocus(&focus2); + QVERIFY(query.isValid()); + + QString output; + query.evaluateTo(&output); + + QCOMPARE(output, QString::fromLatin1("abc2\n")); + } +} + +/*! + Since we internally use variable bindings for implementing the focus, we need + to make sure we don't clash in this area. +*/ +void tst_QXmlQuery::setFocusQIODeviceAvoidVariableClash() const +{ + QBuffer buffer; + buffer.setData("<e>focus</e>"); + QVERIFY(buffer.open(QIODevice::ReadOnly)); + + /* First we bind the variable name, then the focus. */ + { + QXmlQuery query; + query.bindVariable(QString(QLatin1Char('u')), QVariant(1)); + query.setFocus(&buffer); + query.setQuery(QLatin1String("string()")); + + QString out; + query.evaluateTo(&out); + + QCOMPARE(out, QString::fromLatin1("focus\n")); + } + + /* First we bind the focus, then the variable name. */ + { + QXmlQuery query; + QVERIFY(buffer.open(QIODevice::ReadOnly)); + query.setFocus(&buffer); + query.bindVariable(QString(QLatin1Char('u')), QVariant(1)); + query.setQuery(QLatin1String("string()")); + + QString out; + query.evaluateTo(&out); + + QCOMPARE(out, QString::fromLatin1("focus\n")); + } +} + +void tst_QXmlQuery::setFocusQIODeviceFailure() const +{ + /* A not well-formed input document. */ + { + QXmlQuery query; + + MessageSilencer silencer; + query.setMessageHandler(&silencer); + + QBuffer input; + input.setData("<e"); + QVERIFY(input.open(QIODevice::ReadOnly)); + + QCOMPARE(query.setFocus(&input), false); + } +} + +void tst_QXmlQuery::setFocusQString() const +{ + QXmlQuery query; + + /* Basic use of focus. */ + { + QVERIFY(query.setFocus(QLatin1String("<e>textNode</e>"))); + query.setQuery(QLatin1String("string()")); + QVERIFY(query.isValid()); + QString out; + query.evaluateTo(&out); + QCOMPARE(out, QString::fromLatin1("textNode\n")); + } + + /* Set to a new focus, make sure it changes and works. */ + { + QVERIFY(query.setFocus(QLatin1String("<e>newFocus</e>"))); + QString out; + query.evaluateTo(&out); + QCOMPARE(out, QString::fromLatin1("newFocus\n")); + } +} + +void tst_QXmlQuery::setFocusQStringFailure() const +{ + QXmlQuery query; + MessageSilencer silencer; + + query.setMessageHandler(&silencer); + QVERIFY(!query.setFocus(QLatin1String("<notWellformed"))); + + /* Let's try the slight special case of a null string. */ + QVERIFY(!query.setFocus(QString())); +} + +void tst_QXmlQuery::setFocusQStringSignature() const +{ + QXmlQuery query; + MessageSilencer silencer; + query.setMessageHandler(&silencer); + + const QString argument; + /* We should take a const ref. */ + query.setFocus(argument); + + /* We should return a bool. */ + static_cast<bool>(query.setFocus(QString())); +} + +void tst_QXmlQuery::setFocusQIODeviceTriggerWarnings() const +{ + /* A null pointer. */ + { + QXmlQuery query; + + QTest::ignoreMessage(QtWarningMsg, "A null QIODevice pointer cannot be passed."); + QCOMPARE(query.setFocus(static_cast<QIODevice *>(0)), false); + } + + /* A non opened-device. */ + { + QXmlQuery query; + + QBuffer notReadable; + QVERIFY(!notReadable.isReadable()); + + QTest::ignoreMessage(QtWarningMsg, "The device must be readable."); + QCOMPARE(query.setFocus(¬Readable), false); + } +} + +void tst_QXmlQuery::fnDocNetworkAccessSuccess() const +{ +#if defined(Q_OS_WINCE) && !defined(_X86_) + QStringList testsToSkip; + testsToSkip << "http scheme" << "ftp scheme"; + if (testsToSkip.contains(QTest::currentDataTag())) + QSKIP("Network tests are currently unsupported on Windows CE.", SkipSingle); +#endif + + QFETCH(QUrl, uriToOpen); + QFETCH(QByteArray, expectedOutput); + + if(!uriToOpen.isValid()) + qDebug() << "uriToOpen:" << uriToOpen; + + QVERIFY(uriToOpen.isValid()); + + QXmlQuery query; + query.bindVariable(QLatin1String("uri"), QVariant(uriToOpen)); + query.setQuery(QLatin1String("declare variable $uri external;\ndoc($uri)")); + QVERIFY(query.isValid()); + + QByteArray result; + QBuffer buffer(&result); + QVERIFY(buffer.open(QIODevice::WriteOnly)); + + QXmlSerializer serializer(query, &buffer); + QVERIFY(query.evaluateTo(&serializer)); + + QCOMPARE(result, expectedOutput); +} + +void tst_QXmlQuery::fnDocNetworkAccessSuccess_data() const +{ + QTest::addColumn<QUrl>("uriToOpen"); + QTest::addColumn<QByteArray>("expectedOutput"); + + QTest::newRow("file scheme") + << inputFileAsURI(QLatin1String(SRCDIR "input.xml")) + << QByteArray("<!-- This is just a file for testing. --><input/>"); + + QTest::newRow("data scheme with ASCII") + /* QUrl::toPercentEncoding(QLatin1String("<e/>")) yields "%3Ce%2F%3E". */ + << QUrl::fromEncoded("data:application/xml,%3Ce%2F%3E") + << QByteArray("<e/>"); + + QTest::newRow("data scheme with ASCII no MIME type") + << QUrl::fromEncoded("data:,%3Ce%2F%3E") + << QByteArray("<e/>"); + + QTest::newRow("data scheme with base 64") + << QUrl::fromEncoded("data:application/xml;base64,PGUvPg==") + << QByteArray("<e/>"); + + QTest::newRow("qrc scheme") + << QUrl::fromEncoded("qrc:/QXmlQueryTestData/data/oneElement.xml") + << QByteArray("<oneElement/>"); + + if(!m_testNetwork) + return; + + QTest::newRow("http scheme") + << QUrl(QString("http://" + QtNetworkSettings::serverName() + "/qtest/qxmlquery/wellFormed.xml")) + << QByteArray("<!-- a comment --><e from=\"http\">Some Text</e>"); + + QTest::newRow("ftp scheme") + << QUrl(QString("ftp://" + QtNetworkSettings::serverName() + "/pub/qxmlquery/wellFormed.xml")) + << QByteArray("<!-- a comment --><e from=\"ftp\">Some Text</e>"); + +} + +void tst_QXmlQuery::fnDocNetworkAccessFailure() const +{ + QFETCH(QUrl, uriToOpen); + + QVERIFY(uriToOpen.isValid()); + + QXmlQuery query; + MessageSilencer silencer; + query.setMessageHandler(&silencer); + query.bindVariable(QLatin1String("uri"), QVariant(uriToOpen)); + query.setQuery(QLatin1String("declare variable $uri external;\ndoc($uri)")); + QVERIFY(query.isValid()); + + QXmlResultItems result; + query.evaluateTo(&result); + + while(!result.next().isNull()) + { + /* Just loop until the end. */ + } + + // TODO do something that triggers a /timeout/. + QVERIFY(result.hasError()); +} + +void tst_QXmlQuery::fnDocNetworkAccessFailure_data() const +{ + QTest::addColumn<QUrl>("uriToOpen"); + + QTest::newRow("data scheme, not-well-formed") + << QUrl(QLatin1String("data:application/xml;base64,PGUvg===")); + + QTest::newRow("file scheme, non-existant file") + << QUrl(QLatin1String("file:///example.com/does/notExist.xml")); + + QTest::newRow("http scheme, file not found") + << QUrl(QLatin1String("http://www.example.com/does/not/exist.xml")); + + QTest::newRow("http scheme, nonexistent host") + << QUrl(QLatin1String("http://this.host.does.not.exist.I.SWear")); + + QTest::newRow("qrc scheme, not well-formed") + << QUrl(QLatin1String("qrc:/QXmlQueryTestData/notWellformed.xml")); + + QTest::newRow("'qrc:/', non-existing file") + << QUrl(QLatin1String(":/QXmlQueryTestData/data/thisFileDoesNotExist.xml")); + + QTest::newRow("':/', this scheme is not supported") + << QUrl(QLatin1String(":/QXmlQueryTestData/data/notWellformed.xml")); + + if(!m_testNetwork) + return; + + QTest::newRow("http scheme, not well-formed") + << QUrl(QString("http://" + QtNetworkSettings::serverName() + "/qtest/qxmlquery/notWellformed.xml")); + + QTest::newRow("https scheme, not well-formed") + << QUrl(QString("https://" + QtNetworkSettings::serverName() + "/qtest/qxmlquery/notWellformedViaHttps.xml")); + + QTest::newRow("https scheme, nonexistent host") + << QUrl(QLatin1String("https://this.host.does.not.exist.I.SWear")); + + QTest::newRow("ftp scheme, nonexistent host") + << QUrl(QLatin1String("ftp://this.host.does.not.exist.I.SWear")); + + QTest::newRow("ftp scheme, not well-formed") + << QUrl(QString("ftp://" + QtNetworkSettings::serverName() + "/pub/qxmlquery/notWellFormed.xml")); +} + +/*! + Create a network timeout from a QIODevice binding such + that we ensure we don't hang infinitely. + */ +void tst_QXmlQuery::fnDocOnQIODeviceTimeout() const +{ + if(!m_testNetwork) + return; + + QTcpServer server; + server.listen(QHostAddress::LocalHost, 1088); + + QTcpSocket client; + client.connectToHost("localhost", 1088); + QVERIFY(client.isReadable()); + + QXmlQuery query; + + MessageSilencer silencer; + query.setMessageHandler(&silencer); + + query.bindVariable(QLatin1String("inDevice"), &client); + query.setQuery(QLatin1String("declare variable $inDevice external;\ndoc($inDevice)")); + QVERIFY(query.isValid()); + + QXmlResultItems result; + query.evaluateTo(&result); + QXmlItem next(result.next()); + + while(!next.isNull()) + { + next = result.next(); + } + + QVERIFY(result.hasError()); +} + +/*! + When changing query, the static context must change too, such that + the source locations are updated. + */ +void tst_QXmlQuery::recompilationWithEvaluateToResultFailing() const +{ + QXmlQuery query; + MessageSilencer silencer; + query.setMessageHandler(&silencer); + + query.setQuery(QLatin1String("1 + 1")); /* An arbitrary valid query. */ + QVERIFY(query.isValid()); /* Trigger query compilation. */ + + query.setQuery(QLatin1String("fn:doc('doesNotExist.example.com.xml')")); /* An arbitrary invalid query that make use of a source location. */ + QVERIFY(query.isValid()); /* Trigger second compilation. */ + + QXmlResultItems items; + query.evaluateTo(&items); + items.next(); + QVERIFY(items.hasError()); +} + +void tst_QXmlQuery::secondEvaluationWithEvaluateToResultFailing() const +{ + QXmlQuery query; + MessageSilencer silencer; + query.setMessageHandler(&silencer); + + query.setQuery(QLatin1String("1 + 1")); /* An arbitrary valid query. */ + QVERIFY(query.isValid()); /* Trigger query compilation. */ + + query.setQuery(QLatin1String("fn:doc('doesNotExist.example.com.xml')")); /* An arbitrary invalid query that make use of a source location. */ + /* We don't call isValid(). */ +QXmlResultItems items; + query.evaluateTo(&items); + items.next(); + QVERIFY(items.hasError()); +} + +/*! + Compilation is triggered in the evaluation function due to no call to QXmlQuery::isValid(). + */ +void tst_QXmlQuery::recompilationWithEvaluateToReceiver() const +{ + QXmlQuery query; + MessageSilencer silencer; + query.setMessageHandler(&silencer); + + query.setQuery(QLatin1String("1 + 1")); /* An arbitrary valid query. */ + QVERIFY(query.isValid()); /* Trigger query compilation. */ + + query.setQuery(QLatin1String("fn:doc('doesNotExist.example.com.xml')")); /* An arbitrary invalid query that make use of a source location. */ + /* We don't call isValid(). */ + + QByteArray dummy; + QBuffer buffer(&dummy); + buffer.open(QIODevice::WriteOnly); + + QXmlSerializer serializer(query, &buffer); + + QVERIFY(!query.evaluateTo(&serializer)); +} + +void tst_QXmlQuery::evaluateToQStringListOnInvalidQuery() const +{ + MessageSilencer silencer; + + /* Invoke on a default constructed object. */ + { + QXmlQuery query; + QStringList out; + QVERIFY(!query.evaluateTo(&out)); + } + + /* Invoke on a syntactically invalid query. */ + { + QXmlQuery query; + QStringList out; + MessageSilencer silencer; + + query.setMessageHandler(&silencer); + query.setQuery(QLatin1String("1 + ")); + + QVERIFY(!query.evaluateTo(&out)); + } + + /* Invoke on a query with the wrong type, one atomic. */ + { + QXmlQuery query; + QStringList out; + + query.setQuery(QLatin1String("1")); + query.setMessageHandler(&silencer); + QVERIFY(!query.evaluateTo(&out)); + } + + /* Invoke on a query with the wrong type, one element. */ + { + QXmlQuery query; + QStringList out; + + query.setQuery(QLatin1String("<e/>")); + QVERIFY(!query.evaluateTo(&out)); + } + + /* Invoke on a query with the wrong type, mixed nodes & atomics. */ + { + QXmlQuery query; + QStringList out; + + query.setQuery(QLatin1String("<e/>, 1, 'a string'")); + query.setMessageHandler(&silencer); + QVERIFY(!query.evaluateTo(&out)); + } + + /* Evaluate the empty sequence. */ + { + QXmlQuery query; + QStringList out; + + query.setQuery(QLatin1String("()")); + QVERIFY(!query.evaluateTo(&out)); + QVERIFY(out.isEmpty()); + } +} + +void tst_QXmlQuery::evaluateToQStringList() const +{ + QFETCH(QString, queryString); + QFETCH(QStringList, expectedOutput); + + QXmlQuery query; + query.setQuery(queryString); + QStringList out; + QVERIFY(query.isValid()); + + QVERIFY(query.evaluateTo(&out)); + + QCOMPARE(out, expectedOutput); +} + +void tst_QXmlQuery::evaluateToQStringListTriggerWarnings() const +{ + QXmlQuery query; + + QTest::ignoreMessage(QtWarningMsg, "A non-null callback must be passed."); + QCOMPARE(query.evaluateTo(static_cast<QStringList *>(0)), + false); +} + +void tst_QXmlQuery::evaluateToQStringList_data() const +{ + QTest::addColumn<QString>("queryString"); + QTest::addColumn<QStringList>("expectedOutput"); + + QTest::newRow("One atomic") + << QString::fromLatin1("(1 + 1) cast as xs:string") + << QStringList(QString::fromLatin1("2")); + + { + QStringList expected; + expected << QLatin1String("2"); + expected << QLatin1String("a string"); + + QTest::newRow("Two atomics") + << QString::fromLatin1("(1 + 1) cast as xs:string, 'a string'") + << expected; + } + + QTest::newRow("A query which evaluates to sub-types of xs:string.") + << QString::fromLatin1("xs:NCName('NCName'), xs:normalizedString(' a b c ')") + << (QStringList() << QString::fromLatin1("NCName") + << QString::fromLatin1(" a b c ")); + + QTest::newRow("A query which evaluates to two elements.") + << QString::fromLatin1("string(<e>theString1</e>), string(<e>theString2</e>)") + << (QStringList() << QString::fromLatin1("theString1") + << QString::fromLatin1("theString2")); +} + +/*! + Ensure that we don't automatically convert non-xs:string values. + */ +void tst_QXmlQuery::evaluateToQStringListNoConversion() const +{ + QXmlQuery query; + query.setQuery(QString::fromLatin1("<e/>")); + QVERIFY(query.isValid()); + QStringList result; + QVERIFY(!query.evaluateTo(&result)); +} + +void tst_QXmlQuery::evaluateToQIODevice() const +{ + /* an XQuery, check that no indentation is performed. */ + { + QBuffer out; + QVERIFY(out.open(QIODevice::ReadWrite)); + + QXmlQuery query; + query.setQuery(QLatin1String("<a><b/></a>")); + QVERIFY(query.isValid()); + QVERIFY(query.evaluateTo(&out)); + QCOMPARE(out.data(), QByteArray("<a><b/></a>")); + } +} + +void tst_QXmlQuery::evaluateToQIODeviceTriggerWarnings() const +{ + QXmlQuery query; + + QTest::ignoreMessage(QtWarningMsg, "The pointer to the device cannot be null."); + QCOMPARE(query.evaluateTo(static_cast<QIODevice *>(0)), + false); + + QBuffer buffer; + + QTest::ignoreMessage(QtWarningMsg, "The device must be writable."); + QCOMPARE(query.evaluateTo(&buffer), + false); +} + +void tst_QXmlQuery::evaluateToQIODeviceSignature() const +{ + /* The function should be const. */ + { + QBuffer out; + QVERIFY(out.open(QIODevice::ReadWrite)); + + const QXmlQuery query; + + query.evaluateTo(&out); + } +} + +void tst_QXmlQuery::evaluateToQIODeviceOnInvalidQuery() const +{ + QBuffer out; + QVERIFY(out.open(QIODevice::WriteOnly)); + + /* On syntactically invalid query. */ + { + QXmlQuery query; + MessageSilencer silencer; + query.setMessageHandler(&silencer); + query.setQuery(QLatin1String("1 +")); + QVERIFY(!query.isValid()); + QVERIFY(!query.evaluateTo(&out)); + } + + /* On null QXmlQuery instance. */ + { + QXmlQuery query; + QVERIFY(!query.evaluateTo(&out)); + } + +} + +void tst_QXmlQuery::setQueryQIODeviceQUrl() const +{ + /* Basic test. */ + { + QBuffer buffer; + buffer.setData("1, 2, 2 + 1"); + QVERIFY(buffer.open(QIODevice::ReadOnly)); + + QXmlQuery query; + query.setQuery(&buffer); + QVERIFY(query.isValid()); + + QXmlResultItems result; + query.evaluateTo(&result); + QCOMPARE(result.next().toAtomicValue(), QVariant(1)); + QCOMPARE(result.next().toAtomicValue(), QVariant(2)); + QCOMPARE(result.next().toAtomicValue(), QVariant(3)); + QVERIFY(result.next().isNull()); + QVERIFY(!result.hasError()); + } + + /* Set query that is invalid. */ + { + QBuffer buffer; + buffer.setData("1, "); + QVERIFY(buffer.open(QIODevice::ReadOnly)); + + QXmlQuery query; + MessageSilencer silencer; + query.setMessageHandler(&silencer); + query.setQuery(&buffer); + QVERIFY(!query.isValid()); + } + + /* Check that the base URI passes through. */ + { + QBuffer buffer; + buffer.setData("string(static-base-uri())"); + QVERIFY(buffer.open(QIODevice::ReadOnly)); + + QXmlQuery query; + query.setQuery(&buffer, QUrl::fromEncoded("http://www.example.com/QIODeviceQUrl")); + QVERIFY(query.isValid()); + + QStringList result; + query.evaluateTo(&result); + QCOMPARE(result, QStringList(QLatin1String("http://www.example.com/QIODeviceQUrl"))); + } +} + +void tst_QXmlQuery::setQueryQIODeviceQUrlTriggerWarnings() const +{ + QXmlQuery query; + QTest::ignoreMessage(QtWarningMsg, "A null QIODevice pointer cannot be passed."); + query.setQuery(0); + + QBuffer buffer; + QTest::ignoreMessage(QtWarningMsg, "The device must be readable."); + query.setQuery(&buffer); +} + +void tst_QXmlQuery::setQueryQString() const +{ + /* Basic test. */ + { + QXmlQuery query; + query.setQuery(QLatin1String("1, 2, 2 + 1")); + QVERIFY(query.isValid()); + + QXmlResultItems result; + query.evaluateTo(&result); + QCOMPARE(result.next().toAtomicValue(), QVariant(1)); + QCOMPARE(result.next().toAtomicValue(), QVariant(2)); + QCOMPARE(result.next().toAtomicValue(), QVariant(3)); + QVERIFY(result.next().isNull()); + QVERIFY(!result.hasError()); + } + + /* Set query that is invalid. */ + { + MessageSilencer silencer; + QXmlQuery query; + query.setMessageHandler(&silencer); + query.setQuery(QLatin1String("1, ")); + QVERIFY(!query.isValid()); + } + + /* Check that the base URI passes through. */ + { + QXmlQuery query; + query.setQuery(QLatin1String("string(static-base-uri())"), QUrl::fromEncoded("http://www.example.com/QIODeviceQUrl")); + QVERIFY(query.isValid()); + + QStringList result; + query.evaluateTo(&result); + QCOMPARE(result, QStringList(QLatin1String("http://www.example.com/QIODeviceQUrl"))); + } +} + +void tst_QXmlQuery::setQueryQUrlSuccess() const +{ +#if defined(Q_OS_WINCE) && !defined(_X86_) + QStringList testsToSkip; + testsToSkip << "A valid query via the ftp scheme" << "A valid query via the http scheme"; + if (testsToSkip.contains(QTest::currentDataTag())) + QSKIP("Network tests are currently unsupported on Windows CE.", SkipSingle); +#endif + + QFETCH(QUrl, queryURI); + QFETCH(QByteArray, expectedOutput); + + QVERIFY(queryURI.isValid()); + + QXmlQuery query; + + MessageSilencer silencer; + query.setMessageHandler(&silencer); + + query.setQuery(queryURI); + QVERIFY(query.isValid()); + + QByteArray out; + QBuffer buffer(&out); + QVERIFY(buffer.open(QIODevice::WriteOnly)); + QXmlSerializer serializer(query, &buffer); + + query.evaluateTo(&serializer); + QCOMPARE(out, expectedOutput); +} + +void tst_QXmlQuery::setQueryQUrlSuccess_data() const +{ + QTest::addColumn<QUrl>("queryURI"); + QTest::addColumn<QByteArray>("expectedOutput"); + + QTest::newRow("A valid query via the data scheme") + << QUrl::fromEncoded("data:application/xml,1%20%2B%201") /* "1 + 1" */ + << QByteArray("2"); + + QTest::newRow("A valid query via the file scheme") + << QUrl::fromLocalFile(inputFile(QLatin1String(queriesDirectory) + QLatin1String("onePlusOne.xq"))) + << QByteArray("2"); + + if(!m_testNetwork) + return; + + QTest::newRow("A valid query via the ftp scheme") + << QUrl::fromEncoded(QString("ftp://" + QtNetworkSettings::serverName() + "/pub/qxmlquery/viaFtp.xq").toLatin1()) + << QByteArray("This was received via FTP"); + + QTest::newRow("A valid query via the http scheme") + << QUrl::fromEncoded(QString("http://" + QtNetworkSettings::serverName() + "/qtest/qxmlquery/viaHttp.xq").toLatin1()) + << QByteArray("This was received via HTTP."); +} + +void tst_QXmlQuery::setQueryQUrlFailSucceed() const +{ + QXmlQuery query; + MessageSilencer silencer; + + query.setMessageHandler(&silencer); + + query.setQuery(QLatin1String("1 + 1")); + QVERIFY(query.isValid()); + + query.setQuery(QUrl::fromEncoded("file://example.com/does/not/exist")); + QVERIFY(!query.isValid()); +} + +void tst_QXmlQuery::setQueryQUrlFailure() const +{ + QFETCH(QUrl, queryURI); + + MessageSilencer silencer; + + QXmlQuery query; + query.setMessageHandler(&silencer); + query.setQuery(queryURI); + QVERIFY(!query.isValid()); +} + +void tst_QXmlQuery::setQueryQUrlFailure_data() const +{ + QTest::addColumn<QUrl>("queryURI"); + + QTest::newRow("Query via file:// that does not exist.") + << QUrl::fromEncoded("file://example.com/does/not/exist"); + + QTest::newRow("A query via file:// that is completely empty, but readable.") + << QUrl::fromLocalFile(QCoreApplication::applicationFilePath()).resolved(QUrl("../xmlpatterns/queries/completelyEmptyQuery.xq")); + + { + const QString name(QLatin1String("nonReadableFile.xq")); + QFile outFile(name); + QVERIFY(outFile.open(QIODevice::WriteOnly)); + outFile.write(QByteArray("1")); + outFile.close(); + /* On some windows versions, this fails, so we don't check that this works with QVERIFY. */ + outFile.setPermissions(QFile::Permissions(QFile::Permissions())); + + QTest::newRow("Query via file:/ that does not have read permissions.") + << QUrl::fromLocalFile(QCoreApplication::applicationFilePath()).resolved(QUrl("nonReadableFile.xq")); + } + + if(!m_testNetwork) + return; + + QTest::newRow("Query via HTTP that does not exist.") + << QUrl::fromEncoded("http://example.com/NoQuery/ISWear"); + + /* + QTest::newRow("Query via FTP that does not exist.") + << QUrl::fromEncoded("ftp://example.com/NoQuery/ISWear"); + */ + + QTest::newRow("A query via http:// that is completely empty, but readable.") + << QUrl::fromEncoded(QString( + "http://" + QtNetworkSettings::serverName() + "/qtest/qxmlquery/completelyEmptyQuery.xq").toLatin1()); + + QTest::newRow("A query via ftp:// that is completely empty, but readable.") + << QUrl::fromEncoded(QString( + "ftp://" + QtNetworkSettings::serverName() + "/pub/qxmlquery/completelyEmptyQuery.xq").toLatin1()); + +} + +void tst_QXmlQuery::setQueryQUrlBaseURI() const +{ + QFETCH(QUrl, inputBaseURI); + QFETCH(QUrl, expectedBaseURI); + + QXmlQuery query; + + query.setQuery(QUrl(QLatin1String("qrc:/QXmlQueryTestData/queries/staticBaseURI.xq")), inputBaseURI); + QVERIFY(query.isValid()); + + QStringList result; + QVERIFY(query.evaluateTo(&result)); + QCOMPARE(result.count(), 1); + + if(qstrcmp(QTest::currentDataTag(), "Relative base URI") == 0) + checkBaseURI(QUrl(result.first()), QCoreApplication::applicationFilePath()); + else + QCOMPARE(result.first(), expectedBaseURI.toString()); +} + +void tst_QXmlQuery::setQueryQUrlBaseURI_data() const +{ + QTest::addColumn<QUrl>("inputBaseURI"); + QTest::addColumn<QUrl>("expectedBaseURI"); + + QTest::newRow("absolute HTTP") + << QUrl(QLatin1String("http://www.example.com/")) + << QUrl(QLatin1String("http://www.example.com/")); + + QTest::newRow("None, so the query URI is used") + << QUrl() + << QUrl(QLatin1String("qrc:/QXmlQueryTestData/queries/staticBaseURI.xq")); + + QTest::newRow("Relative base URI") + << QUrl(QLatin1String("../data/relative.uri")) + << QUrl(); +} + +/*! + 1. Create a valid query. + 2. Call setQuery(QUrl), with a query file that doesn't exist. + 3. Verify that the query has changed state into invalid. + */ +void tst_QXmlQuery::setQueryWithNonExistentQUrlOnValidQuery() const +{ + QXmlQuery query; + + MessageSilencer messageSilencer; + query.setMessageHandler(&messageSilencer); + + query.setQuery(QLatin1String("1 + 1")); + QVERIFY(query.isValid()); + + query.setQuery(QUrl::fromEncoded("qrc:/QXmlQueryTestData/DOESNOTEXIST.xq")); + QVERIFY(!query.isValid()); +} + +/*! + 1. Create a valid query. + 2. Call setQuery(QUrl), with a query file that is invalid. + 3. Verify that the query has changed state into invalid. + */ +void tst_QXmlQuery::setQueryWithInvalidQueryFromQUrlOnValidQuery() const +{ + QXmlQuery query; + + MessageSilencer messageSilencer; + query.setMessageHandler(&messageSilencer); + + query.setQuery(QLatin1String("1 + 1")); + QVERIFY(query.isValid()); + + query.setQuery(QUrl::fromEncoded("qrc:/QXmlQueryTestData/queries/syntaxError.xq")); + QVERIFY(!query.isValid()); +} + +/*! + This triggered two bugs: + + - First, the DynamicContext wasn't assigned to QXmlResultItems, meaning it went out of + scope and therefore deallocated the document pool, and calls + to QXmlResultItems::next() would use dangling pointers. + + - Conversion between QPatternist::Item and QXmlItem was incorrectly done, leading to nodes + being treated as atomic values, and subsequent crashes. + + */ +void tst_QXmlQuery::retrieveNameFromQuery() const +{ + QFETCH(QString, queryString); + QFETCH(QString, expectedName); + + QXmlQuery query; + query.setQuery(queryString); + QVERIFY(query.isValid()); + QXmlResultItems result; + query.evaluateTo(&result); + + QVERIFY(!result.hasError()); + + const QXmlItem item(result.next()); + QVERIFY(!result.hasError()); + QVERIFY(!item.isNull()); + QVERIFY(item.isNode()); + + const QXmlNodeModelIndex node(item.toNodeModelIndex()); + QVERIFY(!node.isNull()); + + QCOMPARE(node.model()->name(node).localName(query.namePool()), expectedName); +} + +void tst_QXmlQuery::retrieveNameFromQuery_data() const +{ + QTest::addColumn<QString>("queryString"); + QTest::addColumn<QString>("expectedName"); + + QTest::newRow("Document-node") + << QString::fromLatin1("document{<elementName/>}") + << QString(); + + QTest::newRow("Element") + << QString::fromLatin1("document{<elementName/>}/*") + << QString::fromLatin1("elementName"); +} + +/*! + Binding a null QString leads to no variable binding, but an + empty non-null QString is possible. + */ +void tst_QXmlQuery::bindEmptyNullString() const +{ + MessageSilencer messageHandler; + QXmlQuery query; + query.setMessageHandler(&messageHandler); + query.setQuery(QLatin1String("declare variable $v external; $v")); + /* Here, we effectively pass an invalid QVariant. */ + query.bindVariable(QLatin1String("v"), QVariant(QString())); + QVERIFY(!query.isValid()); + + QStringList result; + QVERIFY(!query.evaluateTo(&result)); +} + +void tst_QXmlQuery::bindEmptyString() const +{ + QXmlQuery query; + query.bindVariable(QLatin1String("v"), QVariant(QString(QLatin1String("")))); + query.setQuery(QLatin1String("declare variable $v external; $v")); + QVERIFY(query.isValid()); + + QStringList result; + QVERIFY(query.evaluateTo(&result)); + QStringList expected((QString())); + QCOMPARE(result, expected); +} + +void tst_QXmlQuery::cleanupTestCase() const +{ + /* Remove a weird file we created. */ + const QString name(QLatin1String("nonReadableFile.xq")); + + if(QFile::exists(name)) + { + QFile file(name); + QVERIFY(file.setPermissions(QFile::WriteOwner)); + QVERIFY(file.remove()); + } +} + +void tst_QXmlQuery::declareUnavailableExternal() const +{ + QXmlQuery query; + MessageSilencer silencer; + query.setMessageHandler(&silencer); + query.setQuery(QLatin1String("declare variable $var external;" + "1 + 1")); + /* We do not bind $var with QXmlQuery::bindVariable(). */ + QVERIFY(!query.isValid()); +} + +/*! + This test triggers an assert in one of the cache iterator + with MSVC 2005 when compiled in debug mode. + */ +void tst_QXmlQuery::msvcCacheIssue() const +{ + QXmlQuery query; + query.bindVariable(QLatin1String("externalVariable"), QXmlItem("Variable Value")); + query.setQuery(QUrl::fromLocalFile(QLatin1String(queriesDirectory) + QString::fromLatin1("externalVariableUsedTwice.xq"))); + QStringList result; + QVERIFY(query.evaluateTo(&result)); + + QCOMPARE(result, + QStringList() << QString::fromLatin1("Variable Value") << QString::fromLatin1("Variable Value")); +} + +void tst_QXmlQuery::unavailableExternalVariable() const +{ + QXmlQuery query; + + MessageSilencer silencer; + query.setMessageHandler(&silencer); + + query.setQuery(QLatin1String("declare variable $foo external; 1")); + + QVERIFY(!query.isValid()); +} + +/*! + Ensure that setUriResolver() affects \c fn:doc() and \c fn:doc-available(). + */ +void tst_QXmlQuery::useUriResolver() const +{ + class TestUriResolver : public QAbstractUriResolver + , private TestFundament + { + public: + TestUriResolver() {} + virtual QUrl resolve(const QUrl &relative, + const QUrl &baseURI) const + { + Q_UNUSED(relative); + return baseURI.resolved(inputFile(QLatin1String(queriesDirectory) + QLatin1String("simpleDocument.xml"))); + } + }; + + const TestUriResolver uriResolver; + QXmlQuery query; + + query.setUriResolver(&uriResolver); + query.setQuery(QLatin1String("let $i := 'http://www.example.com/DoesNotExist'" + "return (string(doc($i)), doc-available($i))")); + + + QXmlResultItems result; + query.evaluateTo(&result); + + QVERIFY(!result.hasError()); + QCOMPARE(result.next().toAtomicValue().toString(), QString::fromLatin1("text text node")); + QCOMPARE(result.next().toAtomicValue().toBool(), true); + QVERIFY(result.next().isNull()); + QVERIFY(!result.hasError()); +} + +void tst_QXmlQuery::queryWithFocusAndVariable() const +{ + QXmlQuery query; + query.setFocus(QXmlItem(5)); + query.bindVariable(QLatin1String("var"), QXmlItem(2)); + + query.setQuery(QLatin1String("string(. * $var)")); + + QStringList result; + + QVERIFY(query.evaluateTo(&result)); + + QCOMPARE(result, QStringList(QLatin1String("10"))); +} + +void tst_QXmlQuery::undefinedFocus() const +{ + QXmlQuery query; + + MessageSilencer silencer; + query.setMessageHandler(&silencer); + + query.setQuery(QLatin1String(".")); + QVERIFY(!query.isValid()); +} + +void tst_QXmlQuery::basicFocusUsage() const +{ + QXmlQuery query; + + MessageSilencer silencer; + query.setMessageHandler(&silencer); + + query.setFocus(QXmlItem(5)); + query.setQuery(QLatin1String("string(. * .)")); + QVERIFY(query.isValid()); + + QStringList result; + QVERIFY(query.evaluateTo(&result)); + + QCOMPARE(result, QStringList(QLatin1String("25"))); +} + +/*! + Triggers an ownership related crash. + */ +void tst_QXmlQuery::copyCheckMessageHandler() const +{ + QXmlQuery query; + QCOMPARE(query.messageHandler(), static_cast<QAbstractMessageHandler *>(0)); + + query.setQuery(QLatin1String("doc('qrc:/QXmlQueryTestData/data/oneElement.xml')")); + /* By now, we should have set the builtin message handler. */ + const QAbstractMessageHandler *const messageHandler = query.messageHandler(); + QVERIFY(messageHandler); + + { + /* This copies QXmlQueryPrivate::m_ownerObject, and its destructor + * will delete it, and hence the builtin message handler attached to it. */ + QXmlQuery copy(query); + } + + QXmlResultItems result; + query.evaluateTo(&result); + + while(!result.next().isNull()) + { + } + QVERIFY(!result.hasError()); +} + +void tst_QXmlQuery::queryLanguage() const +{ + /* Check default value. */ + { + const QXmlQuery query; + QCOMPARE(query.queryLanguage(), QXmlQuery::XQuery10); + } + + /* Check default value of copies default instance. */ + { + const QXmlQuery query1; + const QXmlQuery query2(query1); + + QCOMPARE(query1.queryLanguage(), QXmlQuery::XQuery10); + QCOMPARE(query2.queryLanguage(), QXmlQuery::XQuery10); + } +} + +void tst_QXmlQuery::queryLanguageSignature() const +{ + /* This getter should be const. */ + QXmlQuery query; + query.queryLanguage(); +} + +void tst_QXmlQuery::enumQueryLanguage() const +{ + /* These enum values should be possible to OR for future plans. */ + QCOMPARE(int(QXmlQuery::XQuery10), 1); + QCOMPARE(int(QXmlQuery::XSLT20), 2); + QCOMPARE(int(QXmlQuery::XmlSchema11IdentityConstraintSelector), 1024); + QCOMPARE(int(QXmlQuery::XmlSchema11IdentityConstraintField), 2048); + QCOMPARE(int(QXmlQuery::XPath20), 4096); +} + +void tst_QXmlQuery::setInitialTemplateNameQXmlName() const +{ + QXmlQuery query(QXmlQuery::XSLT20); + QXmlNamePool np(query.namePool()); + const QXmlName name(np, QLatin1String("main")); + + query.setInitialTemplateName(name); + + QCOMPARE(query.initialTemplateName(), name); + + query.setQuery(QUrl(inputFileAsURI(QLatin1String(XMLPATTERNSDIR "/stylesheets/namedTemplate.xsl")))); + QVERIFY(query.isValid()); + + QBuffer result; + QVERIFY(result.open(QIODevice::ReadWrite)); + QXmlSerializer serializer(query, &result); + query.evaluateTo(&serializer); + + QCOMPARE(result.data(), QByteArray("1 2 3 4 5")); + + // TODO invoke a template which has required params. +} + +void tst_QXmlQuery::setInitialTemplateNameQXmlNameSignature() const +{ + QXmlQuery query; + QXmlNamePool np(query.namePool()); + const QXmlName name(np, QLatin1String("foo")); + + /* The signature should take a const reference. */ + query.setInitialTemplateName(name); +} + +void tst_QXmlQuery::setInitialTemplateNameQString() const +{ + QXmlQuery query; + QXmlNamePool np(query.namePool()); + query.setInitialTemplateName(QLatin1String("foo")); + + QCOMPARE(query.initialTemplateName(), QXmlName(np, QLatin1String("foo"))); +} + +void tst_QXmlQuery::setInitialTemplateNameQStringSignature() const +{ + const QString name(QLatin1String("name")); + QXmlQuery query; + + /* We should take a const reference. */ + query.setInitialTemplateName(name); +} + +void tst_QXmlQuery::initialTemplateName() const +{ + /* Check our default value. */ + QXmlQuery query; + QCOMPARE(query.initialTemplateName(), QXmlName()); + QVERIFY(query.initialTemplateName().isNull()); +} + +void tst_QXmlQuery::initialTemplateNameSignature() const +{ + const QXmlQuery query; + /* This should be a const member. */ + query.initialTemplateName(); +} + +void tst_QXmlQuery::setNetworkAccessManager() const +{ + + /* Ensure fn:doc() picks up the right QNetworkAccessManager. */ + { + NetworkOverrider networkOverrider(QUrl(QLatin1String("tag:example.com:DOESNOTEXIST")), + QUrl(inputFileAsURI(QLatin1String(XMLPATTERNSDIR "/queries/simpleDocument.xml")))); + + QXmlQuery query; + query.setNetworkAccessManager(&networkOverrider); + query.setQuery(QLatin1String("string(doc('tag:example.com:DOESNOTEXIST'))")); + QVERIFY(query.isValid()); + + QStringList result; + QVERIFY(query.evaluateTo(&result)); + + QCOMPARE(result, QStringList(QLatin1String("text text node"))); + } + + /* Ensure setQuery() is using the right network manager. */ + { + NetworkOverrider networkOverrider(QUrl(QLatin1String("tag:example.com:DOESNOTEXIST")), + QUrl(inputFileAsURI(QLatin1String(XMLPATTERNSDIR "/queries/concat.xq")))); + + QXmlQuery query; + query.setNetworkAccessManager(&networkOverrider); + query.setQuery(QUrl("tag:example.com:DOESNOTEXIST")); + QVERIFY(query.isValid()); + + QStringList result; + QVERIFY(query.evaluateTo(&result)); + + QCOMPARE(result, QStringList(QLatin1String("abcdef"))); + } +} +void tst_QXmlQuery::networkAccessManagerSignature() const +{ + /* Const object. */ + const QXmlQuery query; + + /* The function should be const. */ + query.networkAccessManager(); +} + +void tst_QXmlQuery::networkAccessManagerDefaultValue() const +{ + const QXmlQuery query; + + QCOMPARE(query.networkAccessManager(), static_cast<QNetworkAccessManager *>(0)); +} + +void tst_QXmlQuery::networkAccessManager() const +{ + /* Test that we return the network manager that was set. */ + { + QNetworkAccessManager manager; + QXmlQuery query; + query.setNetworkAccessManager(&manager); + QCOMPARE(query.networkAccessManager(), &manager); + } +} + +/*! + \internal + \since 4.5 + + 1. Load a document into QXmlQuery's document cache, by executing a query which does it. + 2. Set a focus + 3. Change query, to one which uses the focus + 4. Evaluate + + Used to crash. + */ +void tst_QXmlQuery::multipleDocsAndFocus() const +{ + QXmlQuery query; + + /* We use string concatenation, since variable bindings might disturb what + * we're testing. */ + query.setQuery(QLatin1String("string(doc('") + + inputFile(QLatin1String(XMLPATTERNSDIR "/queries/simpleDocument.xml")) + + QLatin1String("'))")); + query.setFocus(QUrl(inputFileAsURI(QLatin1String(XMLPATTERNSDIR "/stylesheets/documentElement.xml")))); + query.setQuery(QLatin1String("string(.)")); + + QStringList result; + QVERIFY(query.evaluateTo(&result)); +} + +/*! + \internal + \since 4.5 + + 1. Set a focus + 2. Set a query + 3. Evaluate + 4. Change focus + 5. Evaluate + + Used to crash. + */ +void tst_QXmlQuery::multipleEvaluationsWithDifferentFocus() const +{ + QXmlQuery query; + QStringList result; + + query.setFocus(QUrl(inputFileAsURI(QLatin1String(XMLPATTERNSDIR "/stylesheets/documentElement.xml")))); + query.setQuery(QLatin1String("string(.)")); + QVERIFY(query.evaluateTo(&result)); + + query.setFocus(QUrl(inputFileAsURI(QLatin1String(XMLPATTERNSDIR "/stylesheets/documentElement.xml")))); + QVERIFY(query.evaluateTo(&result)); +} + +void tst_QXmlQuery::bindVariableQXmlQuery() const +{ + QFETCH(QString, query1); + QFETCH(QString, query2); + QFETCH(QString, expectedOutput); + QFETCH(bool, expectedSuccess); + + MessageSilencer silencer; + QXmlQuery xmlQuery1; + xmlQuery1.setMessageHandler(&silencer); + xmlQuery1.setQuery(query1); + + QXmlQuery xmlQuery2(xmlQuery1); + xmlQuery2.bindVariable("query1", xmlQuery1); + xmlQuery2.setQuery(query2); + + QString output; + const bool querySuccess = xmlQuery2.evaluateTo(&output); + + QCOMPARE(querySuccess, expectedSuccess); + + if(querySuccess) + QCOMPARE(output, expectedOutput); +} + +void tst_QXmlQuery::bindVariableQXmlQuery_data() const +{ + QTest::addColumn<QString>("query1"); + QTest::addColumn<QString>("query2"); + QTest::addColumn<QString>("expectedOutput"); + QTest::addColumn<bool>("expectedSuccess"); + + QTest::newRow("First query has one atomic value.") + << "2" + << "1, $query1, 3" + << "1 2 3\n" + << true; + + QTest::newRow("First query has two atomic values.") + << "2, 3" + << "1, $query1, 4" + << "1 2 3 4\n" + << true; + + QTest::newRow("First query is a node.") + << "<e/>" + << "1, $query1, 3" + << "1<e/>3\n" + << true; + + /* This is a good test, because it triggers the exception in the + * bindVariable() call, as supposed to when the actual evaluation is done. + */ + QTest::newRow("First query has a dynamic error.") + << "error()" + << "1, $query1" + << QString() /* We don't care. */ + << false; +} + +void tst_QXmlQuery::bindVariableQStringQXmlQuerySignature() const +{ + QXmlQuery query1; + query1.setQuery("'dummy'"); + + QXmlQuery query2; + const QString name(QLatin1String("name")); + + /* We should be able to take a const QXmlQuery reference. Evaluation never mutate + * QXmlQuery, and evaluation is what we do here. */ + query2.bindVariable(name, const_cast<const QXmlQuery &>(query1)); +} + +void tst_QXmlQuery::bindVariableQXmlNameQXmlQuerySignature() const +{ + QXmlNamePool np; + QXmlQuery query1(np); + query1.setQuery("'dummy'"); + + QXmlQuery query2; + const QXmlName name(np, QLatin1String("name")); + + /* We should be able to take a const QXmlQuery reference. Evaluation never mutate + * QXmlQuery, and evaluation is what we do here. */ + query2.bindVariable(name, const_cast<const QXmlQuery &>(query1)); +} + +/*! + Check that the QXmlName is handled correctly. + */ +void tst_QXmlQuery::bindVariableQXmlNameQXmlQuery() const +{ + QXmlNamePool np; + QXmlQuery query1; + query1.setQuery(QLatin1String("1")); + + QXmlQuery query2(np); + query2.bindVariable(QXmlName(np, QLatin1String("theName")), query1); + query2.setQuery("$theName"); + + QString result; + query2.evaluateTo(&result); + + QCOMPARE(result, QString::fromLatin1("1\n")); +} + +void tst_QXmlQuery::bindVariableQXmlQueryInvalidate() const +{ + QXmlQuery query; + query.bindVariable(QLatin1String("name"), QVariant(1)); + query.setQuery("$name"); + QVERIFY(query.isValid()); + + QXmlQuery query2; + query2.setQuery("'query2'"); + + query.bindVariable(QLatin1String("name"), query); + QVERIFY(!query.isValid()); +} + +void tst_QXmlQuery::unknownSourceLocation() const +{ + QBuffer b; + b.setData("<a><b/><b/></a>"); + b.open(QIODevice::ReadOnly); + + MessageSilencer silencer; + QXmlQuery query; + query.bindVariable(QLatin1String("inputDocument"), &b); + query.setMessageHandler(&silencer); + + query.setQuery(QLatin1String("doc($inputDocument)/a/(let $v := b/string() return if ($v) then $v else ())")); + + QString output; + query.evaluateTo(&output); +} + +void tst_QXmlQuery::identityConstraintSuccess() const +{ + QXmlQuery::QueryLanguage queryLanguage = QXmlQuery::XmlSchema11IdentityConstraintSelector; + + /* We run this code for Selector and Field. */ + for(int i = 0; i < 3; ++i) + { + QXmlNamePool namePool; + QXmlResultItems result; + QXmlItem node; + + { + QXmlQuery nodeSource(namePool); + nodeSource.setQuery(QLatin1String("<e/>")); + + nodeSource.evaluateTo(&result); + node = result.next(); + } + + /* Basic use: + * 1. The focus is undefined, but it's still valid. + * 2. We never evaluate. */ + { + QXmlQuery query(queryLanguage); + query.setQuery(QLatin1String("a")); + QVERIFY(query.isValid()); + } + + /* Basic use: + * 1. The focus is undefined, but it's still valid. + * 2. We afterwards set the focus. */ + { + QXmlQuery query(queryLanguage, namePool); + query.setQuery(QLatin1String("a")); + query.setFocus(node); + QVERIFY(query.isValid()); + } + + /* Basic use: + * 1. The focus is undefined, but it's still valid. + * 2. We afterwards set the focus. + * 3. We evaluate. */ + { + QXmlQuery query(queryLanguage, namePool); + query.setQuery(QString(QLatin1Char('.'))); + query.setFocus(node); + QVERIFY(query.isValid()); + + QString result; + QVERIFY(query.evaluateTo(&result)); + QCOMPARE(result, QString::fromLatin1("<e/>\n")); + } + + /* A slightly more complex Field. */ + { + QXmlQuery query(queryLanguage); + query.setQuery(QLatin1String("* | .//xml:*/.")); + QVERIFY(query.isValid()); + } + + /* @ is only allowed in Field. */ + if(queryLanguage == QXmlQuery::XmlSchema11IdentityConstraintField) + { + QXmlQuery query(QXmlQuery::XmlSchema11IdentityConstraintField); + query.setQuery(QLatin1String("@abc")); + QVERIFY(query.isValid()); + } + + /* Field allows attribute:: and child:: .*/ + if(queryLanguage == QXmlQuery::XmlSchema11IdentityConstraintField) + { + QXmlQuery query(QXmlQuery::XmlSchema11IdentityConstraintField); + query.setQuery(QLatin1String("attribute::name | child::name")); + QVERIFY(query.isValid()); + } + + /* Selector allows only child:: .*/ + { + QXmlQuery query(QXmlQuery::XmlSchema11IdentityConstraintSelector); + query.setQuery(QLatin1String("child::name")); + QVERIFY(query.isValid()); + } + + if(i == 0) + queryLanguage = QXmlQuery::XmlSchema11IdentityConstraintField; + else if(i == 1) + queryLanguage = QXmlQuery::XPath20; + } +} + +Q_DECLARE_METATYPE(QXmlQuery::QueryLanguage); + +/*! + We just do some basic tests for boot strapping and sanity checking. The actual regression + testing is in the Schema suite. + */ +void tst_QXmlQuery::identityConstraintFailure() const +{ + QFETCH(QXmlQuery::QueryLanguage, queryLanguage); + QFETCH(QString, inputQuery); + + QXmlQuery query(queryLanguage); + MessageSilencer silencer; + query.setMessageHandler(&silencer); + + query.setQuery(inputQuery); + QVERIFY(!query.isValid()); +} + +void tst_QXmlQuery::identityConstraintFailure_data() const +{ + QTest::addColumn<QXmlQuery::QueryLanguage>("queryLanguage"); + QTest::addColumn<QString>("inputQuery"); + + QTest::newRow("We don't have element constructors in identity constraint pattern, " + "it's an XQuery feature(Selector).") + << QXmlQuery::XmlSchema11IdentityConstraintSelector + << QString::fromLatin1("<e/>"); + + QTest::newRow("We don't have functions in identity constraint pattern, " + "it's an XPath feature(Selector).") + << QXmlQuery::XmlSchema11IdentityConstraintSelector + << QString::fromLatin1("current-time()"); + + QTest::newRow("We don't have element constructors in identity constraint pattern, " + "it's an XQuery feature(Field).") + << QXmlQuery::XmlSchema11IdentityConstraintSelector + << QString::fromLatin1("<e/>"); + + QTest::newRow("We don't have functions in identity constraint pattern, " + "it's an XPath feature(Field).") + << QXmlQuery::XmlSchema11IdentityConstraintSelector + << QString::fromLatin1("current-time()"); + + QTest::newRow("@attributeName is disallowed for the selector.") + << QXmlQuery::XmlSchema11IdentityConstraintSelector + << QString::fromLatin1("@abc"); + + QTest::newRow("attribute:: is disallowed for the selector.") + << QXmlQuery::XmlSchema11IdentityConstraintSelector + << QString::fromLatin1("attribute::name"); + + QTest::newRow("ancestor::name is disallowed for the selector.") + << QXmlQuery::XmlSchema11IdentityConstraintSelector + << QString::fromLatin1("ancestor::name"); + + QTest::newRow("ancestor::name is disallowed for the field.") + << QXmlQuery::XmlSchema11IdentityConstraintField + << QString::fromLatin1("ancestor::name"); +} + +QTEST_MAIN(tst_QXmlQuery) + +#include "tst_qxmlquery.moc" +#else //QTEST_XMLPATTERNS +QTEST_NOOP_MAIN +#endif |