From 2e7627528308da7629f1293757de2fb4bb22a7ad Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 5 Mar 2018 16:12:47 +0900 Subject: qmc-example: tests for redaction and tagging; send origin in test messages --- examples/qmc-example.cpp | 108 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 90 insertions(+), 18 deletions(-) (limited to 'examples') diff --git a/examples/qmc-example.cpp b/examples/qmc-example.cpp index e0aabca9..f63b32a2 100644 --- a/examples/qmc-example.cpp +++ b/examples/qmc-example.cpp @@ -2,18 +2,68 @@ #include "connection.h" #include "room.h" #include "user.h" +#include "jobs/sendeventjob.h" #include #include +#include #include using namespace QMatrixClient; using std::cout; using std::endl; -using std::bind; using namespace std::placeholders; -void onNewRoom(Room* r, const char* targetRoomName) +static int semaphor = 0; +static Room* targetRoom = nullptr; + +#define QMC_CHECK(origin, description, condition) \ + cout << (description) \ + << (!!(condition) ? " successul" : " FAILED") << endl; \ + targetRoom->postMessage(QString(origin) % ": " % QStringLiteral(description) % \ + (!!(condition) ? QStringLiteral(" successful") : \ + QStringLiteral(" FAILED")), MessageEventType::Notice) + +void addAndRemoveTag(const char* origin) +{ + ++semaphor; + static const auto TestTag = QStringLiteral("org.qmatrixclient.test"); + QObject::connect(targetRoom, &Room::tagsChanged, targetRoom, [=] { + cout << "Room " << targetRoom->id().toStdString() + << ", tag(s) changed:" << endl + << " " << targetRoom->tagNames().join(", ").toStdString() << endl; + if (targetRoom->tags().contains(TestTag)) + { + targetRoom->removeTag(TestTag); + QMC_CHECK(origin, "Tagging test", + !targetRoom->tags().contains(TestTag)); + --semaphor; + QObject::disconnect(targetRoom, &Room::tagsChanged, nullptr, nullptr); + } + }); + // The reverse order because tagsChanged is emitted synchronously. + targetRoom->addTag(TestTag); +} + +void sendAndRedact(const char* origin) +{ + ++semaphor; + auto* job = targetRoom->connection()->callApi(targetRoom->id(), + RoomMessageEvent("Message to redact")); + QObject::connect(job, &BaseJob::success, targetRoom, [job] { + targetRoom->redactEvent(job->eventId(), "qmc-example"); + }); + QObject::connect(targetRoom, &Room::replacedEvent, targetRoom, + [=] (const RoomEvent* newEvent) { + QMC_CHECK(origin, "Redaction", newEvent->isRedacted() && + newEvent->redactionReason() == "qmc-example"); + --semaphor; + QObject::disconnect(targetRoom, &Room::replacedEvent, + nullptr, nullptr); + }); +} + +void onNewRoom(Room* r, const char* targetRoomName, const char* origin) { cout << "New room: " << r->id().toStdString() << endl; QObject::connect(r, &Room::namesChanged, [=] { @@ -24,11 +74,15 @@ void onNewRoom(Room* r, const char* targetRoomName) if (targetRoomName && (r->name() == targetRoomName || r->canonicalAlias() == targetRoomName)) { - r->postMessage( - "This is a test message from an example application\n" - "The current user is " % r->localUser()->fullName(r) % "\n" % - QStringLiteral("This room has %1 member(s)") - .arg(r->memberCount()) % "\n" % + cout << "Found the target room, proceeding for tests" << endl; + targetRoom = r; + addAndRemoveTag(origin); + sendAndRedact(origin); + targetRoom->postMessage( + "This is a test notice from an example application\n" + "Origin: " % QString(origin) % "\n" + "The current user is " % + targetRoom->localUser()->fullName(targetRoom) % "\n" % // "The room is " % // (r->isDirectChat() ? "" : "not ") % "a direct chat\n" % "Have a good day", @@ -36,11 +90,7 @@ void onNewRoom(Room* r, const char* targetRoomName) ); } }); - QObject::connect(r, &Room::tagsChanged, [=] { - cout << "Room " << r->id().toStdString() << ", tag(s) changed:" << endl - << " " << r->tagNames().join(", ").toStdString() << endl << endl; - }); - QObject::connect(r, &Room::aboutToAddNewMessages, [=] (RoomEventsRange timeline) { + QObject::connect(r, &Room::aboutToAddNewMessages, [r] (RoomEventsRange timeline) { cout << timeline.size() << " new event(s) in room " << r->id().toStdString() << endl; // for (const auto& item: timeline) @@ -56,13 +106,15 @@ void onNewRoom(Room* r, const char* targetRoomName) void finalize(Connection* conn) { + if (semaphor) + cout << "One or more tests FAILED" << endl; cout << "Logging out" << endl; conn->logout(); QObject::connect(conn, &Connection::loggedOut, QCoreApplication::instance(), [conn] { conn->deleteLater(); - QCoreApplication::instance()->processEvents(); - QCoreApplication::instance()->quit(); + QCoreApplication::processEvents(); + QCoreApplication::exit(semaphor); }); } @@ -83,10 +135,30 @@ int main(int argc, char* argv[]) }); const char* targetRoomName = argc >= 4 ? argv[3] : nullptr; if (targetRoomName) - cout << "Target room name: " << targetRoomName; + cout << "Target room name: " << targetRoomName << endl; + const char* origin = argc >= 5 ? argv[4] : nullptr; + if (origin) + cout << "Origin for the test message: " << origin << endl; QObject::connect(conn, &Connection::newRoom, - bind(onNewRoom, _1, targetRoomName)); - QObject::connect(conn, &Connection::syncDone, - bind(finalize, conn)); + [=](Room* room) { onNewRoom(room, targetRoomName, origin); }); + QObject::connect(conn, &Connection::syncDone, conn, [conn] { + cout << "Sync complete, " << semaphor << " tests in the air" << endl; + if (semaphor) + conn->sync(10000); + else + { + if (targetRoom) + { + auto j = conn->callApi(targetRoom->id(), + RoomMessageEvent("All tests finished")); + QObject::connect(j, &BaseJob::finished, + conn, [conn] { finalize(conn); }); + } + else + finalize(conn); + } + }); + // Big red countdown + QTimer::singleShot(180000, conn, [conn] { finalize(conn); }); return app.exec(); } -- cgit v1.2.3 From 5c4238d43acfd5bbd5d3c387a4705348de490fe0 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 5 Mar 2018 18:27:51 +0900 Subject: qmc-example: Rewritten with a QObject for clearer dispatching --- examples/qmc-example.cpp | 202 +++++++++++++++++++++++++++-------------------- 1 file changed, 117 insertions(+), 85 deletions(-) (limited to 'examples') diff --git a/examples/qmc-example.cpp b/examples/qmc-example.cpp index f63b32a2..13a1b7c4 100644 --- a/examples/qmc-example.cpp +++ b/examples/qmc-example.cpp @@ -14,17 +14,113 @@ using std::cout; using std::endl; using namespace std::placeholders; -static int semaphor = 0; -static Room* targetRoom = nullptr; +class QMCTest : public QObject +{ + public: + QMCTest(Connection* conn, const QString& testRoomName, QString source); + + private slots: + void onNewRoom(Room* r, const QString& testRoomName); + void doTests(); + void addAndRemoveTag(); + void sendAndRedact(); + void finalize(); + + private: + QScopedPointer c; + QString origin; + Room* targetRoom = nullptr; + int semaphor = 0; -#define QMC_CHECK(origin, description, condition) \ +}; + +#define QMC_CHECK(description, condition) \ cout << (description) \ << (!!(condition) ? " successul" : " FAILED") << endl; \ - targetRoom->postMessage(QString(origin) % ": " % QStringLiteral(description) % \ + targetRoom->postMessage(origin % ": " % QStringLiteral(description) % \ (!!(condition) ? QStringLiteral(" successful") : \ QStringLiteral(" FAILED")), MessageEventType::Notice) -void addAndRemoveTag(const char* origin) +QMCTest::QMCTest(Connection* conn, const QString& testRoomName, QString source) + : c(conn), origin(std::move(source)) +{ + if (!origin.isEmpty()) + cout << "Origin for the test message: " << origin.toStdString() << endl; + if (!testRoomName.isEmpty()) + cout << "Test room name: " << testRoomName.toStdString() << endl; + + connect(c.data(), &Connection::newRoom, + this, [this,testRoomName] (Room* r) { onNewRoom(r, testRoomName); }); + connect(c.data(), &Connection::syncDone, c.data(), [this] { + cout << "Sync complete, " << semaphor << " tests in the air" << endl; + if (semaphor) + { +// if (targetRoom) +// targetRoom->postMessage( +// QString("%1: sync done, %2 test(s) in the air") +// .arg(origin).arg(semaphor), +// MessageEventType::Notice); + c->sync(10000); + } + else + { + if (targetRoom) + { + auto j = c->callApi(targetRoom->id(), + RoomMessageEvent(origin % ": All tests finished")); + connect(j, &BaseJob::finished, this, &QMCTest::finalize); + } + else + finalize(); + } + }); + // Big countdown watchdog + QTimer::singleShot(180000, this, &QMCTest::finalize); +} + +void QMCTest::onNewRoom(Room* r, const QString& testRoomName) +{ + cout << "New room: " << r->id().toStdString() << endl; + connect(r, &Room::namesChanged, this, [=] { + cout << "Room " << r->id().toStdString() << ", name(s) changed:" << endl + << " Name: " << r->name().toStdString() << endl + << " Canonical alias: " << r->canonicalAlias().toStdString() << endl + << endl << endl; + if (!testRoomName.isEmpty() && (r->name() == testRoomName || + r->canonicalAlias() == testRoomName)) + { + cout << "Found the target room, proceeding for tests" << endl; + targetRoom = r; + ++semaphor; + auto j = targetRoom->connection()->callApi( + targetRoom->id(), + RoomMessageEvent(origin % ": connected to test room", + MessageEventType::Notice)); + connect(j, &BaseJob::success, + this, [this] { doTests(); --semaphor; }); + } + }); + connect(r, &Room::aboutToAddNewMessages, r, [r] (RoomEventsRange timeline) { + cout << timeline.size() << " new event(s) in room " + << r->id().toStdString() << endl; +// for (const auto& item: timeline) +// { +// cout << "From: " +// << r->roomMembername(item->senderId()).toStdString() +// << endl << "Timestamp:" +// << item->timestamp().toString().toStdString() << endl +// << "JSON:" << endl << item->originalJson().toStdString() << endl; +// } + }); +} + +void QMCTest::doTests() +{ + addAndRemoveTag(); + sendAndRedact(); +} + +void QMCTest::addAndRemoveTag() { ++semaphor; static const auto TestTag = QStringLiteral("org.qmatrixclient.test"); @@ -35,8 +131,7 @@ void addAndRemoveTag(const char* origin) if (targetRoom->tags().contains(TestTag)) { targetRoom->removeTag(TestTag); - QMC_CHECK(origin, "Tagging test", - !targetRoom->tags().contains(TestTag)); + QMC_CHECK("Tagging test", !targetRoom->tags().contains(TestTag)); --semaphor; QObject::disconnect(targetRoom, &Room::tagsChanged, nullptr, nullptr); } @@ -45,17 +140,17 @@ void addAndRemoveTag(const char* origin) targetRoom->addTag(TestTag); } -void sendAndRedact(const char* origin) +void QMCTest::sendAndRedact() { ++semaphor; auto* job = targetRoom->connection()->callApi(targetRoom->id(), RoomMessageEvent("Message to redact")); - QObject::connect(job, &BaseJob::success, targetRoom, [job] { + QObject::connect(job, &BaseJob::success, targetRoom, [job,this] { targetRoom->redactEvent(job->eventId(), "qmc-example"); }); QObject::connect(targetRoom, &Room::replacedEvent, targetRoom, [=] (const RoomEvent* newEvent) { - QMC_CHECK(origin, "Redaction", newEvent->isRedacted() && + QMC_CHECK("Redaction", newEvent->isRedacted() && newEvent->redactionReason() == "qmc-example"); --semaphor; QObject::disconnect(targetRoom, &Room::replacedEvent, @@ -63,56 +158,15 @@ void sendAndRedact(const char* origin) }); } -void onNewRoom(Room* r, const char* targetRoomName, const char* origin) -{ - cout << "New room: " << r->id().toStdString() << endl; - QObject::connect(r, &Room::namesChanged, [=] { - cout << "Room " << r->id().toStdString() << ", name(s) changed:" << endl - << " Name: " << r->name().toStdString() << endl - << " Canonical alias: " << r->canonicalAlias().toStdString() << endl - << endl << endl; - if (targetRoomName && (r->name() == targetRoomName || - r->canonicalAlias() == targetRoomName)) - { - cout << "Found the target room, proceeding for tests" << endl; - targetRoom = r; - addAndRemoveTag(origin); - sendAndRedact(origin); - targetRoom->postMessage( - "This is a test notice from an example application\n" - "Origin: " % QString(origin) % "\n" - "The current user is " % - targetRoom->localUser()->fullName(targetRoom) % "\n" % -// "The room is " % -// (r->isDirectChat() ? "" : "not ") % "a direct chat\n" % - "Have a good day", - MessageEventType::Notice - ); - } - }); - QObject::connect(r, &Room::aboutToAddNewMessages, [r] (RoomEventsRange timeline) { - cout << timeline.size() << " new event(s) in room " - << r->id().toStdString() << endl; -// for (const auto& item: timeline) -// { -// cout << "From: " -// << r->roomMembername(item->senderId()).toStdString() -// << endl << "Timestamp:" -// << item->timestamp().toString().toStdString() << endl -// << "JSON:" << endl << item->originalJson().toStdString() << endl; -// } - }); -} - -void finalize(Connection* conn) +void QMCTest::finalize() { if (semaphor) cout << "One or more tests FAILED" << endl; cout << "Logging out" << endl; - conn->logout(); - QObject::connect(conn, &Connection::loggedOut, QCoreApplication::instance(), - [conn] { - conn->deleteLater(); + c->logout(); + connect(c.data(), &Connection::loggedOut, QCoreApplication::instance(), + [this] { + c->deleteLater(); QCoreApplication::processEvents(); QCoreApplication::exit(semaphor); }); @@ -121,44 +175,22 @@ void finalize(Connection* conn) int main(int argc, char* argv[]) { QCoreApplication app(argc, argv); - if (argc < 3) + if (argc < 4) + { + cout << "Usage: qmc-example [ [origin]]" << endl; return -1; + } cout << "Connecting to the server as " << argv[1] << endl; auto conn = new Connection; - conn->connectToServer(argv[1], argv[2], "QMatrixClient example application"); + conn->connectToServer(argv[1], argv[2], argv[3]); QObject::connect(conn, &Connection::connected, [=] { cout << "Connected, server: " << conn->homeserver().toDisplayString().toStdString() << endl; cout << "Access token: " << conn->accessToken().toStdString() << endl; conn->sync(); }); - const char* targetRoomName = argc >= 4 ? argv[3] : nullptr; - if (targetRoomName) - cout << "Target room name: " << targetRoomName << endl; - const char* origin = argc >= 5 ? argv[4] : nullptr; - if (origin) - cout << "Origin for the test message: " << origin << endl; - QObject::connect(conn, &Connection::newRoom, - [=](Room* room) { onNewRoom(room, targetRoomName, origin); }); - QObject::connect(conn, &Connection::syncDone, conn, [conn] { - cout << "Sync complete, " << semaphor << " tests in the air" << endl; - if (semaphor) - conn->sync(10000); - else - { - if (targetRoom) - { - auto j = conn->callApi(targetRoom->id(), - RoomMessageEvent("All tests finished")); - QObject::connect(j, &BaseJob::finished, - conn, [conn] { finalize(conn); }); - } - else - finalize(conn); - } - }); - // Big red countdown - QTimer::singleShot(180000, conn, [conn] { finalize(conn); }); + QMCTest test { conn, argc >= 5 ? argv[4] : nullptr, + argc >= 6 ? argv[5] : nullptr }; return app.exec(); } -- cgit v1.2.3 From f207955e2e0a77b7a47b47513374ccc3e6a71c1e Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 5 Mar 2018 19:15:32 +0900 Subject: qmc-example: Logging tweaks --- examples/qmc-example.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'examples') diff --git a/examples/qmc-example.cpp b/examples/qmc-example.cpp index 13a1b7c4..3fa74d42 100644 --- a/examples/qmc-example.cpp +++ b/examples/qmc-example.cpp @@ -130,6 +130,7 @@ void QMCTest::addAndRemoveTag() << " " << targetRoom->tagNames().join(", ").toStdString() << endl; if (targetRoom->tags().contains(TestTag)) { + cout << "Test tag set, removing it now" << endl; targetRoom->removeTag(TestTag); QMC_CHECK("Tagging test", !targetRoom->tags().contains(TestTag)); --semaphor; @@ -137,15 +138,18 @@ void QMCTest::addAndRemoveTag() } }); // The reverse order because tagsChanged is emitted synchronously. + cout << "Adding a tag" << endl; targetRoom->addTag(TestTag); } void QMCTest::sendAndRedact() { ++semaphor; + cout << "Sending a message to redact" << endl; auto* job = targetRoom->connection()->callApi(targetRoom->id(), - RoomMessageEvent("Message to redact")); + RoomMessageEvent(origin % ": Message to redact")); QObject::connect(job, &BaseJob::success, targetRoom, [job,this] { + cout << "Message to redact has been succesfully sent, redacting" << endl; targetRoom->redactEvent(job->eventId(), "qmc-example"); }); QObject::connect(targetRoom, &Room::replacedEvent, targetRoom, -- cgit v1.2.3 From 86d8895f939d8b36dbf8e5104f238b2eaed87a94 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 7 Mar 2018 08:57:17 +0900 Subject: qmc-example: Ensure prerequisites before running the tagging test --- examples/qmc-example.cpp | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'examples') diff --git a/examples/qmc-example.cpp b/examples/qmc-example.cpp index 3fa74d42..b7c56cb4 100644 --- a/examples/qmc-example.cpp +++ b/examples/qmc-example.cpp @@ -124,6 +124,10 @@ void QMCTest::addAndRemoveTag() { ++semaphor; static const auto TestTag = QStringLiteral("org.qmatrixclient.test"); + // Pre-requisite + if (targetRoom->tags().contains(TestTag)) + targetRoom->removeTag(TestTag); + QObject::connect(targetRoom, &Room::tagsChanged, targetRoom, [=] { cout << "Room " << targetRoom->id().toStdString() << ", tag(s) changed:" << endl -- cgit v1.2.3 From 1b3e61777b89194ec81a2327683be094e7aa3c99 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 7 Mar 2018 14:33:06 +0900 Subject: qmc-example: Remove no more needed deleteLater Might help with autotest segfaulting on OSX. --- examples/qmc-example.cpp | 1 - 1 file changed, 1 deletion(-) (limited to 'examples') diff --git a/examples/qmc-example.cpp b/examples/qmc-example.cpp index b7c56cb4..0ca221c9 100644 --- a/examples/qmc-example.cpp +++ b/examples/qmc-example.cpp @@ -174,7 +174,6 @@ void QMCTest::finalize() c->logout(); connect(c.data(), &Connection::loggedOut, QCoreApplication::instance(), [this] { - c->deleteLater(); QCoreApplication::processEvents(); QCoreApplication::exit(semaphor); }); -- cgit v1.2.3 From d2afe5e1342e012b45245d86c8211b3f06df0062 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 7 Mar 2018 17:50:13 +0900 Subject: qmc-example: Fix redaction test to work even if the synced message is already redacted --- examples/qmc-example.cpp | 73 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 62 insertions(+), 11 deletions(-) (limited to 'examples') diff --git a/examples/qmc-example.cpp b/examples/qmc-example.cpp index 0ca221c9..513e7efa 100644 --- a/examples/qmc-example.cpp +++ b/examples/qmc-example.cpp @@ -8,6 +8,7 @@ #include #include #include +#include using namespace QMatrixClient; using std::cout; @@ -21,9 +22,10 @@ class QMCTest : public QObject private slots: void onNewRoom(Room* r, const QString& testRoomName); - void doTests(); - void addAndRemoveTag(); - void sendAndRedact(); + void doTests(); + void addAndRemoveTag(); + void sendAndRedact(); + void checkRedactionOutcome(QString evtIdToRedact, RoomEventsRange events); void finalize(); private: @@ -152,18 +154,67 @@ void QMCTest::sendAndRedact() cout << "Sending a message to redact" << endl; auto* job = targetRoom->connection()->callApi(targetRoom->id(), RoomMessageEvent(origin % ": Message to redact")); - QObject::connect(job, &BaseJob::success, targetRoom, [job,this] { + connect(job, &BaseJob::success, targetRoom, [job,this] { cout << "Message to redact has been succesfully sent, redacting" << endl; - targetRoom->redactEvent(job->eventId(), "qmc-example"); + targetRoom->redactEvent(job->eventId(), origin); + // Make sure to save the event id because the job is about to end. + connect(targetRoom, &Room::aboutToAddNewMessages, this, + std::bind(&QMCTest::checkRedactionOutcome, + this, job->eventId(), _1)); }); - QObject::connect(targetRoom, &Room::replacedEvent, targetRoom, - [=] (const RoomEvent* newEvent) { - QMC_CHECK("Redaction", newEvent->isRedacted() && - newEvent->redactionReason() == "qmc-example"); +} + +void QMCTest::checkRedactionOutcome(QString evtIdToRedact, + RoomEventsRange events) +{ + static bool checkSucceeded = false; + // There are two possible (correct) outcomes: either the event comes already + // redacted at the next sync, or the nearest sync completes with + // the unredacted event but the next one brings redaction. + auto it = std::find_if(events.begin(), events.end(), + [=] (const RoomEventPtr& e) { + return e->id() == evtIdToRedact; + }); + if (it == events.end()) + return; // Waiting for the next sync + + if ((*it)->isRedacted()) + { + if (checkSucceeded) + { + const auto msg = + "The redacted event came in with the sync again, ignoring"; + cout << msg << endl; + targetRoom->postMessage(msg); + return; + } + cout << "The sync brought already redacted message" << endl; + QMC_CHECK("Redaction", true); + --semaphor; + // Not disconnecting because there are other connections from this class + // to aboutToAddNewMessages + checkSucceeded = true; + return; + } + // The event is not redacted + if (checkSucceeded) + { + const auto msg = + "Warning: the redacted event came non-redacted with the sync!"; + cout << msg << endl; + targetRoom->postMessage(msg); + } + cout << "Message came non-redacted with the sync, waiting for redaction" << endl; + connect(targetRoom, &Room::replacedEvent, targetRoom, + [=] (const RoomEvent* newEvent, const RoomEvent* oldEvent) { + QMC_CHECK("Redaction", oldEvent->id() == evtIdToRedact && + newEvent->isRedacted() && + newEvent->redactionReason() == origin); --semaphor; - QObject::disconnect(targetRoom, &Room::replacedEvent, - nullptr, nullptr); + checkSucceeded = true; + disconnect(targetRoom, &Room::replacedEvent, nullptr, nullptr); }); + } void QMCTest::finalize() -- cgit v1.2.3 From de75f8f525c6dfe599580018d4a5dbb885dfa456 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 24 Mar 2018 14:59:21 +0900 Subject: Test/example for direct chats marking Also: refactored to gather up code dealing with the semaphor. --- examples/qmc-example.cpp | 78 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 54 insertions(+), 24 deletions(-) (limited to 'examples') diff --git a/examples/qmc-example.cpp b/examples/qmc-example.cpp index 513e7efa..5ea91856 100644 --- a/examples/qmc-example.cpp +++ b/examples/qmc-example.cpp @@ -26,6 +26,8 @@ class QMCTest : public QObject void addAndRemoveTag(); void sendAndRedact(); void checkRedactionOutcome(QString evtIdToRedact, RoomEventsRange events); + void markDirectChat(); + void checkDirectChatOutcome(); void finalize(); private: @@ -37,11 +39,15 @@ class QMCTest : public QObject }; #define QMC_CHECK(description, condition) \ +{ \ cout << (description) \ << (!!(condition) ? " successul" : " FAILED") << endl; \ targetRoom->postMessage(origin % ": " % QStringLiteral(description) % \ (!!(condition) ? QStringLiteral(" successful") : \ - QStringLiteral(" FAILED")), MessageEventType::Notice) + QStringLiteral(" FAILED")), \ + !!(condition) ? MessageEventType::Notice : MessageEventType::Text); \ + --semaphor; \ +} QMCTest::QMCTest(Connection* conn, const QString& testRoomName, QString source) : c(conn), origin(std::move(source)) @@ -56,25 +62,15 @@ QMCTest::QMCTest(Connection* conn, const QString& testRoomName, QString source) connect(c.data(), &Connection::syncDone, c.data(), [this] { cout << "Sync complete, " << semaphor << " tests in the air" << endl; if (semaphor) - { -// if (targetRoom) -// targetRoom->postMessage( -// QString("%1: sync done, %2 test(s) in the air") -// .arg(origin).arg(semaphor), -// MessageEventType::Notice); c->sync(10000); - } - else + else if (targetRoom) { - if (targetRoom) - { - auto j = c->callApi(targetRoom->id(), - RoomMessageEvent(origin % ": All tests finished")); - connect(j, &BaseJob::finished, this, &QMCTest::finalize); - } - else - finalize(); + auto j = c->callApi(targetRoom->id(), + RoomMessageEvent(origin % ": All tests finished")); + connect(j, &BaseJob::finished, this, &QMCTest::finalize); } + else + finalize(); }); // Big countdown watchdog QTimer::singleShot(180000, this, &QMCTest::finalize); @@ -118,13 +114,13 @@ void QMCTest::onNewRoom(Room* r, const QString& testRoomName) void QMCTest::doTests() { - addAndRemoveTag(); - sendAndRedact(); + ++semaphor; addAndRemoveTag(); + ++semaphor; sendAndRedact(); + ++semaphor; markDirectChat(); } void QMCTest::addAndRemoveTag() { - ++semaphor; static const auto TestTag = QStringLiteral("org.qmatrixclient.test"); // Pre-requisite if (targetRoom->tags().contains(TestTag)) @@ -139,7 +135,6 @@ void QMCTest::addAndRemoveTag() cout << "Test tag set, removing it now" << endl; targetRoom->removeTag(TestTag); QMC_CHECK("Tagging test", !targetRoom->tags().contains(TestTag)); - --semaphor; QObject::disconnect(targetRoom, &Room::tagsChanged, nullptr, nullptr); } }); @@ -150,7 +145,6 @@ void QMCTest::addAndRemoveTag() void QMCTest::sendAndRedact() { - ++semaphor; cout << "Sending a message to redact" << endl; auto* job = targetRoom->connection()->callApi(targetRoom->id(), RoomMessageEvent(origin % ": Message to redact")); @@ -190,7 +184,6 @@ void QMCTest::checkRedactionOutcome(QString evtIdToRedact, } cout << "The sync brought already redacted message" << endl; QMC_CHECK("Redaction", true); - --semaphor; // Not disconnecting because there are other connections from this class // to aboutToAddNewMessages checkSucceeded = true; @@ -210,13 +203,50 @@ void QMCTest::checkRedactionOutcome(QString evtIdToRedact, QMC_CHECK("Redaction", oldEvent->id() == evtIdToRedact && newEvent->isRedacted() && newEvent->redactionReason() == origin); - --semaphor; checkSucceeded = true; disconnect(targetRoom, &Room::replacedEvent, nullptr, nullptr); }); } +void QMCTest::markDirectChat() +{ + if (c->isDirectChat(targetRoom)) + { + cout << "Warning: the room is already a direct chat," + " only unmarking will be tested" << endl; + checkDirectChatOutcome(); + } + cout << "Marking the room as a direct chat" << endl; + c->addToDirectChats(targetRoom, c->user()); + connect(c.data(), &Connection::directChatsListChanged, + this, &QMCTest::checkDirectChatOutcome); +} + +void QMCTest::checkDirectChatOutcome() +{ + if (!c->isDirectChat(targetRoom)) + { + cout << "Room not (yet?) added to direct chats, waiting" << endl; + return; + } + + cout << "Room marked as a direct chat, unmarking now" << endl; + disconnect(c.data(), &Connection::directChatsListChanged, nullptr, nullptr); + c->removeFromDirectChats(targetRoom, c->user()); + connect(c.data(), &Connection::directChatsListChanged, this, [this] { + if (c->isDirectChat(targetRoom)) + { + cout << "Room not (yet?) removed from direct chats, waiting" << endl; + return; + } + + QMC_CHECK("Direct chat test", !c->isDirectChat(targetRoom)); + disconnect(c.data(), &Connection::directChatsListChanged, + nullptr, nullptr); + }); +} + void QMCTest::finalize() { if (semaphor) -- cgit v1.2.3 From b385baadc8e73ff3c499a0111e2a553d35dd29b6 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 26 Mar 2018 12:30:23 -0700 Subject: Direct chat (un)marking: update internal structure synchronously The asynchronous update first implemented was more verbose and caused more problems than provided solutions. The idea was that the internal directChats map would better reflect the server state if updated asynchronously. However, it also causes a local race condition; e.g., to quickly remove rooms from direct chats one after another becomes very non-trivial (one has to wait until the previous operation succeeds). So after some playing with the code, hitting pitfalls along the way, I decided to align the logic with the one for room tags; synchronously issued signals look uglyish but at least work predictably. And race conditions between several clients generally cannot be cleanly resolved anyway. --- connection.cpp | 50 +++++++++++++++++++++++------------------------- connection.h | 22 ++++++++++++++++----- examples/qmc-example.cpp | 31 +++++++++++------------------- 3 files changed, 52 insertions(+), 51 deletions(-) (limited to 'examples') diff --git a/connection.cpp b/connection.cpp index df9fd35f..b32f38ea 100644 --- a/connection.cpp +++ b/connection.cpp @@ -80,7 +80,7 @@ class Connection::Private void connectWithToken(const QString& user, const QString& accessToken, const QString& deviceId); - void applyDirectChatUpdates(const DirectChatsMap& newMap); + void broadcastDirectChatUpdates(); }; Connection::Connection(const QUrl& server, QObject* parent) @@ -655,16 +655,11 @@ QJsonObject toJson(const DirectChatsMap& directChats) return json; } -void Connection::Private::applyDirectChatUpdates(const DirectChatsMap& newMap) +void Connection::Private::broadcastDirectChatUpdates() { - auto j = q->callApi(userId, "m.direct", toJson(newMap)); - connect(j, &BaseJob::success, q, [this, newMap] { - if (directChats != newMap) - { - directChats = newMap; - emit q->directChatsListChanged(); - } - }); + auto j = q->callApi(userId, QStringLiteral("m.direct"), + toJson(directChats)); + emit q->directChatsListChanged(); } void Connection::addToDirectChats(const Room* room, const User* user) @@ -672,29 +667,32 @@ void Connection::addToDirectChats(const Room* room, const User* user) Q_ASSERT(room != nullptr && user != nullptr); if (d->directChats.contains(user, room->id())) return; - auto newMap = d->directChats; - newMap.insert(user, room->id()); - d->applyDirectChatUpdates(newMap); + d->directChats.insert(user, room->id()); + d->broadcastDirectChatUpdates(); } -void Connection::removeFromDirectChats(const Room* room, const User* user) +void Connection::removeFromDirectChats(const QString& roomId, const User* user) { - Q_ASSERT(room != nullptr); - if ((user != nullptr && !d->directChats.contains(user, room->id())) || - d->directChats.key(room->id()) == nullptr) + Q_ASSERT(!roomId.isEmpty()); + if ((user != nullptr && !d->directChats.contains(user, roomId)) || + d->directChats.key(roomId) == nullptr) return; - DirectChatsMap newMap; - for (auto it = d->directChats.begin(); it != d->directChats.end(); ++it) - { - if (it.value() != room->id() || (user != nullptr && it.key() != user)) - newMap.insert(it.key(), it.value()); - } - d->applyDirectChatUpdates(newMap); + if (user != nullptr) + d->directChats.remove(user, roomId); + else + for (auto it = d->directChats.begin(); it != d->directChats.end();) + { + if (it.value() == roomId) + it = d->directChats.erase(it); + else + ++it; + } + d->broadcastDirectChatUpdates(); } -bool Connection::isDirectChat(const Room* room) const +bool Connection::isDirectChat(const QString& roomId) const { - return d->directChats.key(room->id()) != nullptr; + return d->directChats.key(roomId) != nullptr; } QMap Connection::users() const diff --git a/connection.h b/connection.h index 4497e200..6a5285f9 100644 --- a/connection.h +++ b/connection.h @@ -89,18 +89,30 @@ namespace QMatrixClient /** Get the list of rooms with the specified tag */ QVector roomsWithTag(const QString& tagName) const; - /** Mark the room as a direct chat with the user */ + /** Mark the room as a direct chat with the user + * This function marks \p room as a direct chat with \p user. + * Emits the signal synchronously, without waiting to complete + * synchronisation with the server. + * + * \sa directChatsListChanged + */ void addToDirectChats(const Room* room, const User* user); /** Unmark the room from direct chats - * This function removes the room from direct chats either for + * This function removes the room id from direct chats either for * a specific \p user or for all users if \p user in nullptr. + * The room id is used to allow removal of, e.g., ids of forgotten + * rooms; a Room object need not exist. Emits the signal + * immediately, without waiting to complete synchronisation with + * the server. + * + * \sa directChatsListChanged */ - void removeFromDirectChats(const Room* room, + void removeFromDirectChats(const QString& roomId, const User* user = nullptr); - /** Check whether the room is a direct chat */ - bool isDirectChat(const Room* room) const; + /** Check whether the room id corresponds to a direct chat */ + bool isDirectChat(const QString& roomId) const; QMap users() const; diff --git a/examples/qmc-example.cpp b/examples/qmc-example.cpp index 5ea91856..23a1bff1 100644 --- a/examples/qmc-example.cpp +++ b/examples/qmc-example.cpp @@ -126,7 +126,8 @@ void QMCTest::addAndRemoveTag() if (targetRoom->tags().contains(TestTag)) targetRoom->removeTag(TestTag); - QObject::connect(targetRoom, &Room::tagsChanged, targetRoom, [=] { + // Connect first because the signal is emitted synchronously. + connect(targetRoom, &Room::tagsChanged, targetRoom, [=] { cout << "Room " << targetRoom->id().toStdString() << ", tag(s) changed:" << endl << " " << targetRoom->tagNames().join(", ").toStdString() << endl; @@ -138,7 +139,6 @@ void QMCTest::addAndRemoveTag() QObject::disconnect(targetRoom, &Room::tagsChanged, nullptr, nullptr); } }); - // The reverse order because tagsChanged is emitted synchronously. cout << "Adding a tag" << endl; targetRoom->addTag(TestTag); } @@ -211,40 +211,31 @@ void QMCTest::checkRedactionOutcome(QString evtIdToRedact, void QMCTest::markDirectChat() { - if (c->isDirectChat(targetRoom)) + if (c->isDirectChat(targetRoom->id())) { cout << "Warning: the room is already a direct chat," " only unmarking will be tested" << endl; checkDirectChatOutcome(); } - cout << "Marking the room as a direct chat" << endl; - c->addToDirectChats(targetRoom, c->user()); + // Connect first because the signal is emitted synchronously. connect(c.data(), &Connection::directChatsListChanged, this, &QMCTest::checkDirectChatOutcome); + cout << "Marking the room as a direct chat" << endl; + c->addToDirectChats(targetRoom, c->user()); } void QMCTest::checkDirectChatOutcome() { - if (!c->isDirectChat(targetRoom)) + disconnect(c.data(), &Connection::directChatsListChanged, nullptr, nullptr); + if (!c->isDirectChat(targetRoom->id())) { - cout << "Room not (yet?) added to direct chats, waiting" << endl; + QMC_CHECK("Direct chat test", false); return; } cout << "Room marked as a direct chat, unmarking now" << endl; - disconnect(c.data(), &Connection::directChatsListChanged, nullptr, nullptr); - c->removeFromDirectChats(targetRoom, c->user()); - connect(c.data(), &Connection::directChatsListChanged, this, [this] { - if (c->isDirectChat(targetRoom)) - { - cout << "Room not (yet?) removed from direct chats, waiting" << endl; - return; - } - - QMC_CHECK("Direct chat test", !c->isDirectChat(targetRoom)); - disconnect(c.data(), &Connection::directChatsListChanged, - nullptr, nullptr); - }); + c->removeFromDirectChats(targetRoom->id(), c->user()); + QMC_CHECK("Direct chat test", !c->isDirectChat(targetRoom->id())); } void QMCTest::finalize() -- cgit v1.2.3