diff --git a/tests/auto/qdbuspendingcall/.gitignore b/tests/auto/qdbuspendingcall/.gitignore new file mode 100644 index 000000000..a216d5804 --- /dev/null +++ b/tests/auto/qdbuspendingcall/.gitignore @@ -0,0 +1 @@ +tst_qdbuspendingcall diff --git a/tests/auto/qdbuspendingcall/CMakeLists.txt b/tests/auto/qdbuspendingcall/CMakeLists.txt new file mode 100644 index 000000000..da1c24638 --- /dev/null +++ b/tests/auto/qdbuspendingcall/CMakeLists.txt @@ -0,0 +1,5 @@ +katie_test(tst_qdbuspendingcall + ${CMAKE_CURRENT_SOURCE_DIR}/tst_qdbuspendingcall.cpp +) + +target_link_libraries(tst_qdbuspendingcall KtDBus) diff --git a/tests/auto/qdbuspendingcall/tst_qdbuspendingcall.cpp b/tests/auto/qdbuspendingcall/tst_qdbuspendingcall.cpp new file mode 100644 index 000000000..337c858de --- /dev/null +++ b/tests/auto/qdbuspendingcall/tst_qdbuspendingcall.cpp @@ -0,0 +1,547 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Copyright (C) 2016-2020 Ivailo Monev +** +** This file is part of the test suite of the Katie Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** +** GNU Lesser General Public License Usage +** 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. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +#define TEST_INTERFACE_NAME "Katie.QtDBus.MyObject" + +class MyObject : public QDBusAbstractAdaptor +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "Katie.QtDBus.MyObject") + +public: + MyObject(QObject* parent =0) + : QDBusAbstractAdaptor(parent) + {} + +public slots: + QStringList returnFoo() const + { return QStringList() << QString::fromLatin1("foo"); } + + void returnError(const QDBusMessage &msg) const + { + QDBusMessage copy(msg); + copy.setDelayedReply(true); + QDBusConnection::sessionBus().send(copy.createErrorReply("dbuspendingcall_error", "")); + } +}; + +class tst_QDBusPendingCall: public QObject +{ + Q_OBJECT + +public: + tst_QDBusPendingCall(); + +public Q_SLOTS: + void callback(const QStringList &list); + void errorCallback(const QDBusError &error); + void finished(QDBusPendingCallWatcher *call); + void makeCall(); + +private Q_SLOTS: + void initTestCase(); + void waitForFinished(); + void waitForFinished_error(); +// void setReplyCallback(); + void watcher(); + void watcher_error(); + void watcher_waitForFinished(); + void watcher_waitForFinished_threaded(); + void watcher_waitForFinished_alreadyFinished(); + void watcher_waitForFinished_alreadyFinished_eventLoop(); + void watcher_waitForFinished_error(); + void callInsideWaitForFinished(); + + void callWithCallback_localLoop(); + void callWithCallback_localLoop_errorReply(); + +private: + QDBusPendingCall sendMessage(); + QDBusPendingCall sendError(); + + QDBusConnection conn; + + enum { CallbackCalled, ErrorCallbackCalled, FinishCalled, MakeCallCalled }; + int slotCalled; + int callCount; + QStringList callbackArgument; + QDBusError errorArgument; + QDBusPendingCallWatcher *watchArgument; + MyObject *obj; +}; + +tst_QDBusPendingCall::tst_QDBusPendingCall() + : conn(QDBusConnection::sessionBus()) + , obj(new MyObject(this)) +{ +} + +void tst_QDBusPendingCall::finished(QDBusPendingCallWatcher *call) +{ + slotCalled = FinishCalled; + ++callCount; + watchArgument = call; + if (QThread::currentThread() == thread()) + QTestEventLoop::instance().exitLoop(); +} + +void tst_QDBusPendingCall::callback(const QStringList &list) +{ + slotCalled = CallbackCalled; + ++callCount; + callbackArgument = list; + QTestEventLoop::instance().exitLoop(); +} + +void tst_QDBusPendingCall::errorCallback(const QDBusError &error) +{ + slotCalled = ErrorCallbackCalled; + ++callCount; + errorArgument = error; + QTestEventLoop::instance().exitLoop(); +} + +void tst_QDBusPendingCall::makeCall() +{ + // make an external call to D-Bus to make sure we haven't left any locks + slotCalled = MakeCallCalled; + ++callCount; + + sendMessage().waitForFinished(); +} + +void tst_QDBusPendingCall::initTestCase() +{ + QVERIFY(conn.isConnected()); + QVERIFY(conn.registerObject("/", this)); +} + +QDBusPendingCall tst_QDBusPendingCall::sendMessage() +{ + QDBusMessage msg = QDBusMessage::createMethodCall("org.freedesktop.DBus", + "/", + "org.freedesktop.DBus", + "ListNames"); + return conn.asyncCall(msg); +} + +QDBusPendingCall tst_QDBusPendingCall::sendError() +{ + QDBusMessage msg = QDBusMessage::createMethodCall("org.freedesktop.DBus", + "/", + "org.freedesktop.DBus", + "ThisNameWontExist"); + return conn.asyncCall(msg); +} + +void tst_QDBusPendingCall::waitForFinished() +{ + QDBusPendingCall ac = sendMessage(); + QVERIFY(!ac.isFinished()); + QVERIFY(!ac.isError()); + QVERIFY(ac.reply().type() == QDBusMessage::InvalidMessage); + + ac.waitForFinished(); + QVERIFY(ac.isFinished()); + QVERIFY(!ac.isError()); + + const QDBusMessage reply = ac.reply(); + QVERIFY(reply.type() == QDBusMessage::ReplyMessage); + QCOMPARE(reply.signature(), QString("as")); + + const QVariantList args = ac.reply().arguments(); + QCOMPARE(args.count(), 1); + + const QVariant &arg = args.at(0); + QCOMPARE(arg.type(), QVariant::StringList); + QVERIFY(arg.toStringList().contains(conn.baseService())); +} + +void tst_QDBusPendingCall::waitForFinished_error() +{ + QDBusPendingCall ac = sendError(); + QVERIFY(!ac.isFinished()); + QVERIFY(!ac.isError()); + QVERIFY(ac.reply().type() == QDBusMessage::InvalidMessage); + + ac.waitForFinished(); + QVERIFY(ac.isFinished()); + QVERIFY(ac.isError()); + + QDBusError error = ac.error(); + QVERIFY(error.isValid()); + QCOMPARE(error.name(), QString("org.freedesktop.DBus.Error.UnknownMethod")); + QCOMPARE(error.type(), QDBusError::UnknownMethod); +} + +void tst_QDBusPendingCall::callWithCallback_localLoop() +{ + // Verify that a callback actually gets called when the call is dispatched locally. + QDBusInterface iface(QDBusConnection::sessionBus().baseService(), QLatin1String("/"), + TEST_INTERFACE_NAME); + QVERIFY(iface.isValid()); + + QVERIFY(iface.callWithCallback("returnFoo", QVariantList(), this, SLOT(callback(QStringList)))); + + // May be called synchronously or asynchronously... + if (callbackArgument != (QStringList() << QString::fromLatin1("foo"))) { + QTestEventLoop::instance().enterLoop(2); + QVERIFY(!QTestEventLoop::instance().timeout()); + } + + QCOMPARE(callbackArgument, QStringList() << QString::fromLatin1("foo")); +} + +void tst_QDBusPendingCall::callWithCallback_localLoop_errorReply() +{ + // Verify that an error callback actually gets called when the call is + // dispatched locally and the called method returns an error + + QDBusInterface iface(QDBusConnection::sessionBus().baseService(), QLatin1String("/"), + TEST_INTERFACE_NAME); + QVERIFY(iface.isValid()); + + callbackArgument.clear(); + + QVERIFY(iface.callWithCallback("returnError", QVariantList(), this, + SLOT(callback(QStringList)), SLOT(errorCallback(QDBusError)))); + + // May be called synchronously or asynchronously... + if (errorArgument.name() != "dbuspendingcall_error") { + QTestEventLoop::instance().enterLoop(2); + QVERIFY(!QTestEventLoop::instance().timeout()); + } + + QCOMPARE(errorArgument.name(), QString::fromLatin1("dbuspendingcall_error")); + QVERIFY(callbackArgument.isEmpty()); +} + +#if 0 +// This function was removed from the public API +void tst_QDBusPendingCall::setReplyCallback() +{ + QDBusPendingCall ac = sendMessage(); + QVERIFY(!ac.isFinished()); + QVERIFY(!ac.isError()); + QVERIFY(ac.reply().type() == QDBusMessage::InvalidMessage); + + callCount = 0; + callbackArgument.clear(); + QVERIFY(ac.setReplyCallback(this, SLOT(callback(const QStringList &)))); + QVERIFY(callCount == 0); + QVERIFY(callbackArgument.isEmpty()); + + QTestEventLoop::instance().enterLoop(2); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QVERIFY(ac.isFinished()); + QVERIFY(!ac.isError()); + + QCOMPARE(callCount, 1); + QCOMPARE(slotCalled, (int)CallbackCalled); + QVERIFY(!callbackArgument.isEmpty()); + QVERIFY(callbackArgument.contains(conn.baseService())); + + const QVariantList args = ac.reply().arguments(); + QVERIFY(!args.isEmpty()); + QCOMPARE(args.at(0).toStringList(), callbackArgument); +} +#endif + +void tst_QDBusPendingCall::watcher() +{ + QDBusPendingCall ac = sendMessage(); + QVERIFY(!ac.isFinished()); + QVERIFY(!ac.isError()); + QVERIFY(ac.reply().type() == QDBusMessage::InvalidMessage); + + callCount = 0; + watchArgument = 0; + + QDBusPendingCallWatcher watch(ac); + connect(&watch, SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(finished(QDBusPendingCallWatcher*))); + + QTestEventLoop::instance().enterLoop(2); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QVERIFY(ac.isFinished()); + QVERIFY(!ac.isError()); + + QCOMPARE(callCount, 1); + QCOMPARE(slotCalled, (int)FinishCalled); + QCOMPARE(watchArgument, &watch); + QVERIFY(!watch.isError()); + + const QVariantList args2 = ac.reply().arguments(); + QVERIFY(!args2.isEmpty()); + QVERIFY(args2.at(0).toStringList().contains(conn.baseService())); +} + +void tst_QDBusPendingCall::watcher_error() +{ + QDBusPendingCall ac = sendError(); + QVERIFY(!ac.isFinished()); + QVERIFY(!ac.isError()); + QVERIFY(ac.reply().type() == QDBusMessage::InvalidMessage); + + callCount = 0; + watchArgument = 0; + + QDBusPendingCallWatcher watch(ac); + connect(&watch, SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(finished(QDBusPendingCallWatcher*))); + + QTestEventLoop::instance().enterLoop(2); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QVERIFY(ac.isFinished()); + QVERIFY(ac.isError()); + + QCOMPARE(callCount, 1); + QCOMPARE(slotCalled, (int)FinishCalled); + QCOMPARE(watchArgument, &watch); + + QVERIFY(watch.isError()); + QVERIFY(watch.error().isValid()); +} + +void tst_QDBusPendingCall::watcher_waitForFinished() +{ + QDBusPendingCall ac = sendMessage(); + QVERIFY(!ac.isFinished()); + QVERIFY(!ac.isError()); + QVERIFY(ac.reply().type() == QDBusMessage::InvalidMessage); + + callCount = 0; + watchArgument = 0; + + QDBusPendingCallWatcher watch(ac); + connect(&watch, SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(finished(QDBusPendingCallWatcher*))); + + watch.waitForFinished(); + + QVERIFY(ac.isFinished()); + QVERIFY(!ac.isError()); + + QCOMPARE(callCount, 1); + QCOMPARE(slotCalled, (int)FinishCalled); + QCOMPARE(watchArgument, &watch); + QVERIFY(!watch.isError()); + + const QVariantList args2 = ac.reply().arguments(); + QVERIFY(!args2.isEmpty()); + QVERIFY(args2.at(0).toStringList().contains(conn.baseService())); +} + +void tst_QDBusPendingCall::watcher_waitForFinished_threaded() +{ + callCount = 0; + watchArgument = 0; + slotCalled = 0; + + class WorkerThread: public QThread { + public: + tst_QDBusPendingCall *tst; + WorkerThread(tst_QDBusPendingCall *tst) : tst(tst) {} + void run() + { + QDBusPendingCall ac = tst->sendMessage(); +// QVERIFY(!ac.isFinished()); +// QVERIFY(!ac.isError()); +// QVERIFY(ac.reply().type() == QDBusMessage::InvalidMessage); + + QDBusPendingCallWatcher watch(ac); + tst->connect(&watch, SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(finished(QDBusPendingCallWatcher*)), Qt::DirectConnection); + + QTest::qSleep(100); // don't process events in this thread + +// QVERIFY(!ac.isFinished()); +// QVERIFY(!ac.isError()); +// QVERIFY(ac.reply().type() == QDBusMessage::InvalidMessage); + QCOMPARE(tst->callCount, 0); + QCOMPARE(tst->slotCalled, 0); + + watch.waitForFinished(); + QVERIFY(ac.isFinished()); + QVERIFY(!ac.isError()); + + QCOMPARE(tst->callCount, 1); + QCOMPARE(tst->slotCalled, (int)FinishCalled); + QCOMPARE(tst->watchArgument, &watch); + QVERIFY(!watch.isError()); + + const QVariantList args2 = ac.reply().arguments(); + QVERIFY(!args2.isEmpty()); + QVERIFY(args2.at(0).toStringList().contains(tst->conn.baseService())); + } + } thread(this); + QTestEventLoop::instance().connect(&thread, SIGNAL(finished()), SLOT(exitLoop())); + thread.start(); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(thread.wait(3000)); + QVERIFY(!QTestEventLoop::instance().timeout()); +} + +void tst_QDBusPendingCall::watcher_waitForFinished_alreadyFinished() +{ + QDBusPendingCall ac = sendMessage(); + QVERIFY(!ac.isFinished()); + QVERIFY(!ac.isError()); + QVERIFY(ac.reply().type() == QDBusMessage::InvalidMessage); + + ac.waitForFinished(); + QVERIFY(ac.isFinished()); + QVERIFY(!ac.isError()); + + callCount = 0; + watchArgument = 0; + + // create a watcher on an already-finished reply + QDBusPendingCallWatcher watch(ac); + connect(&watch, SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(finished(QDBusPendingCallWatcher*))); + + watch.waitForFinished(); + + QVERIFY(ac.isFinished()); + QVERIFY(!ac.isError()); + + QCOMPARE(callCount, 1); + QCOMPARE(slotCalled, (int)FinishCalled); + QCOMPARE(watchArgument, &watch); + QVERIFY(!watch.isError()); + + const QVariantList args2 = ac.reply().arguments(); + QVERIFY(!args2.isEmpty()); + QVERIFY(args2.at(0).toStringList().contains(conn.baseService())); +} + +void tst_QDBusPendingCall::watcher_waitForFinished_alreadyFinished_eventLoop() +{ + QDBusPendingCall ac = sendMessage(); + QVERIFY(!ac.isFinished()); + QVERIFY(!ac.isError()); + QVERIFY(ac.reply().type() == QDBusMessage::InvalidMessage); + + ac.waitForFinished(); + QVERIFY(ac.isFinished()); + QVERIFY(!ac.isError()); + + callCount = 0; + watchArgument = 0; + + // create a watcher on an already-finished reply + QDBusPendingCallWatcher watch(ac); + connect(&watch, SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(finished(QDBusPendingCallWatcher*))); + connect(&watch, SIGNAL(finished(QDBusPendingCallWatcher*)), + &QTestEventLoop::instance(), SLOT(exitLoop())); + + QTestEventLoop::instance().enterLoop(1); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QVERIFY(ac.isFinished()); + QVERIFY(!ac.isError()); + + QCOMPARE(callCount, 1); + QCOMPARE(slotCalled, (int)FinishCalled); + QCOMPARE(watchArgument, &watch); + QVERIFY(!watch.isError()); + + const QVariantList args2 = ac.reply().arguments(); + QVERIFY(!args2.isEmpty()); + QVERIFY(args2.at(0).toStringList().contains(conn.baseService())); +} + +void tst_QDBusPendingCall::watcher_waitForFinished_error() +{ + QDBusPendingCall ac = sendError(); + QVERIFY(!ac.isFinished()); + QVERIFY(!ac.isError()); + QVERIFY(ac.reply().type() == QDBusMessage::InvalidMessage); + + callCount = 0; + watchArgument = 0; + + QDBusPendingCallWatcher watch(ac); + connect(&watch, SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(finished(QDBusPendingCallWatcher*))); + + watch.waitForFinished(); + + QVERIFY(ac.isFinished()); + QVERIFY(ac.isError()); + + QCOMPARE(callCount, 1); + QCOMPARE(slotCalled, (int)FinishCalled); + QCOMPARE(watchArgument, &watch); + + QVERIFY(watch.isError()); + QVERIFY(watch.error().isValid()); +} + +void tst_QDBusPendingCall::callInsideWaitForFinished() +{ + QDBusPendingCall ac = sendMessage(); + QDBusPendingCallWatcher watch(ac); + + callCount = 0; + + connect(&watch, SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(makeCall())); + + watch.waitForFinished(); + + QCOMPARE(callCount, 1); + QCOMPARE(slotCalled, (int)MakeCallCalled); + QVERIFY(!watch.isError()); + + const QVariantList args2 = ac.reply().arguments(); + QVERIFY(!args2.isEmpty()); + QVERIFY(args2.at(0).toStringList().contains(conn.baseService())); +} + +QTEST_MAIN(tst_QDBusPendingCall) + +#include "moc_tst_qdbuspendingcall.cpp"