From c46663aece5e001543b07d3ed901e64c38be4172 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 12 Dec 2018 13:20:46 +0900 Subject: qmc-example: Use lazy-loading; check full-loading upon setDisplayed --- examples/qmc-example.cpp | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) (limited to 'examples') diff --git a/examples/qmc-example.cpp b/examples/qmc-example.cpp index 9c86d4a9..bdb9ef3f 100644 --- a/examples/qmc-example.cpp +++ b/examples/qmc-example.cpp @@ -26,6 +26,7 @@ class QMCTest : public QObject void setup(const QString& testRoomName); void onNewRoom(Room* r); void startTests(); + void loadMembers(); void sendMessage(); void addAndRemoveTag(); void sendAndRedact(); @@ -80,6 +81,7 @@ void QMCTest::setup(const QString& testRoomName) cout << "Access token: " << c->accessToken().toStdString() << endl; // Setting up sync loop + c->setLazyLoading(true); c->sync(); connect(c.data(), &Connection::syncDone, c.data(), [this,testRoomName] { cout << "Sync complete, " @@ -142,12 +144,41 @@ void QMCTest::onNewRoom(Room* r) void QMCTest::startTests() { cout << "Starting tests" << endl; + loadMembers(); sendMessage(); addAndRemoveTag(); sendAndRedact(); markDirectChat(); } +void QMCTest::loadMembers() +{ + running.push_back("Loading members"); + // The dedicated qmc-test room is too small to test + // lazy-loading-then-full-loading; use #test:matrix.org instead. + // TODO: #264 + auto* r = c->room(QStringLiteral("!vfFxDRtZSSdspfTSEr:matrix.org")); + if (!r) + { + cout << "#test:matrix.org is not found in the test user's rooms" << endl; + QMC_CHECK("Loading members", false); + return; + } + // It's not exactly correct because an arbitrary server might not support + // lazy loading; but in the absence of capabilities framework we assume + // it does. + if (r->memberNames().size() < r->joinedCount()) + { + cout << "Lazy loading doesn't seem to be enabled" << endl; + QMC_CHECK("Loading members", false); + return; + } + r->setDisplayed(); + connect(r, &Room::allMembersLoaded, [this] { + QMC_CHECK("Loading members", true); + }); +} + void QMCTest::sendMessage() { running.push_back("Message sending"); -- cgit v1.2.3 From cf4759edba82baf51dd40285d2e13b200ca7fd29 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 13 Dec 2018 15:54:02 +0900 Subject: qmc-example: Fix the lazy-loading test --- examples/qmc-example.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'examples') diff --git a/examples/qmc-example.cpp b/examples/qmc-example.cpp index bdb9ef3f..e66687da 100644 --- a/examples/qmc-example.cpp +++ b/examples/qmc-example.cpp @@ -83,6 +83,8 @@ void QMCTest::setup(const QString& testRoomName) // Setting up sync loop c->setLazyLoading(true); c->sync(); + connectSingleShot(c.data(), &Connection::syncDone, + this, &QMCTest::startTests); connect(c.data(), &Connection::syncDone, c.data(), [this,testRoomName] { cout << "Sync complete, " << running.size() << " tests in the air" << endl; @@ -116,7 +118,6 @@ void QMCTest::setup(const QString& testRoomName) targetRoom = room; QMC_CHECK("Join room", true); - startTests(); }); } } @@ -167,15 +168,16 @@ void QMCTest::loadMembers() // It's not exactly correct because an arbitrary server might not support // lazy loading; but in the absence of capabilities framework we assume // it does. - if (r->memberNames().size() < r->joinedCount()) + if (r->memberNames().size() >= r->joinedCount()) { cout << "Lazy loading doesn't seem to be enabled" << endl; QMC_CHECK("Loading members", false); return; } r->setDisplayed(); - connect(r, &Room::allMembersLoaded, [this] { - QMC_CHECK("Loading members", true); + connect(r, &Room::allMembersLoaded, [this,r] { + QMC_CHECK("Loading members", + r->memberNames().size() + 1 >= r->joinedCount()); }); } -- cgit v1.2.3 From 12a0b95fdcfea15cd0ef313aec8868656629b986 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 14 Dec 2018 12:33:04 +0900 Subject: qmc-example: clearer QMC_CHECK; start tests only after the first sync is done Because lazy-loading test is executed on a room different from the test room. --- examples/qmc-example.cpp | 93 ++++++++++++++++++++++++++---------------------- 1 file changed, 50 insertions(+), 43 deletions(-) (limited to 'examples') diff --git a/examples/qmc-example.cpp b/examples/qmc-example.cpp index e66687da..48787e44 100644 --- a/examples/qmc-example.cpp +++ b/examples/qmc-example.cpp @@ -20,10 +20,10 @@ using namespace std::placeholders; class QMCTest : public QObject { public: - QMCTest(Connection* conn, const QString& testRoomName, QString source); + QMCTest(Connection* conn, QString testRoomName, QString source); private slots: - void setup(const QString& testRoomName); + void setup(); void onNewRoom(Room* r); void startTests(); void loadMembers(); @@ -44,37 +44,45 @@ class QMCTest : public QObject QStringList succeeded; QStringList failed; QString origin; + QString testRoomName; Room* targetRoom = nullptr; }; #define QMC_CHECK(description, condition) \ { \ - const bool result = !!(condition); \ Q_ASSERT(running.removeOne(description)); \ - (result ? succeeded : failed).push_back(description); \ - cout << (description) << (result ? " successul" : " FAILED") << endl; \ - if (targetRoom) \ - targetRoom->postMessage(origin % ": " % QStringLiteral(description) % \ - (result ? QStringLiteral(" successful") : QStringLiteral(" FAILED")), \ - result ? MessageEventType::Notice : MessageEventType::Text); \ + if (!!(condition)) \ + { \ + succeeded.push_back(description); \ + cout << (description) << " successful" << endl; \ + if (targetRoom) \ + targetRoom->postMessage( \ + origin % ": " % (description) % " successful", \ + MessageEventType::Notice); \ + } else { \ + failed.push_back(description); \ + cout << (description) << " FAILED" << endl; \ + if (targetRoom) \ + targetRoom->postPlainText( \ + origin % ": " % (description) % " FAILED"); \ + } \ } -QMCTest::QMCTest(Connection* conn, const QString& testRoomName, QString source) - : c(conn), origin(std::move(source)) +QMCTest::QMCTest(Connection* conn, QString testRoomName, QString source) + : c(conn), origin(std::move(source)), testRoomName(std::move(testRoomName)) { 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::connected, - this, std::bind(&QMCTest::setup, this, testRoomName)); + connect(c.data(), &Connection::connected, this, &QMCTest::setup); connect(c.data(), &Connection::loadedRoomState, this, &QMCTest::onNewRoom); // Big countdown watchdog QTimer::singleShot(180000, this, &QMCTest::leave); } -void QMCTest::setup(const QString& testRoomName) +void QMCTest::setup() { cout << "Connected, server: " << c->homeserver().toDisplayString().toStdString() << endl; @@ -85,7 +93,7 @@ void QMCTest::setup(const QString& testRoomName) c->sync(); connectSingleShot(c.data(), &Connection::syncDone, this, &QMCTest::startTests); - connect(c.data(), &Connection::syncDone, c.data(), [this,testRoomName] { + connect(c.data(), &Connection::syncDone, c.data(), [this] { cout << "Sync complete, " << running.size() << " tests in the air" << endl; if (!running.isEmpty()) @@ -98,28 +106,6 @@ void QMCTest::setup(const QString& testRoomName) else finalize(); }); - - // Join a testroom, if provided - if (!targetRoom && !testRoomName.isEmpty()) - { - cout << "Joining " << testRoomName.toStdString() << endl; - running.push_back("Join room"); - auto joinJob = c->joinRoom(testRoomName); - connect(joinJob, &BaseJob::failure, this, - [this] { QMC_CHECK("Join room", false); finalize(); }); - // As of BaseJob::success, a Room object is not guaranteed to even - // exist; it's a mere confirmation that the server processed - // the request. - connect(c.data(), &Connection::loadedRoomState, this, - [this,testRoomName] (Room* room) { - Q_ASSERT(room); // It's a grave failure if room is nullptr here - if (room->canonicalAlias() != testRoomName) - return; // Not our room - - targetRoom = room; - QMC_CHECK("Join room", true); - }); - } } void QMCTest::onNewRoom(Room* r) @@ -144,12 +130,33 @@ void QMCTest::onNewRoom(Room* r) void QMCTest::startTests() { - cout << "Starting tests" << endl; - loadMembers(); - sendMessage(); - addAndRemoveTag(); - sendAndRedact(); - markDirectChat(); + if (testRoomName.isEmpty()) + return; + + cout << "Joining " << testRoomName.toStdString() << endl; + running.push_back("Join room"); + auto joinJob = c->joinRoom(testRoomName); + connect(joinJob, &BaseJob::failure, this, + [this] { QMC_CHECK("Join room", false); finalize(); }); + // As of BaseJob::success, a Room object is not guaranteed to even + // exist; it's a mere confirmation that the server processed + // the request. + connect(c.data(), &Connection::loadedRoomState, this, + [this] (Room* room) { + Q_ASSERT(room); // It's a grave failure if room is nullptr here + if (room->canonicalAlias() != testRoomName) + return; // Not our room + + targetRoom = room; + QMC_CHECK("Join room", true); + cout << "Starting tests" << endl; + + loadMembers(); + sendMessage(); + addAndRemoveTag(); + sendAndRedact(); + markDirectChat(); + }); } void QMCTest::loadMembers() -- cgit v1.2.3 From bf5401753432533b31e7d18519c2031c84e774b7 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 16 Dec 2018 14:10:05 +0900 Subject: Room::getAllMembers: revert off-by-one "bugfix" It actually introduces an off-by-one error; the original code was correct. #qmatrixclient:matrix.org is used instead of #test:matrix.org to check lazy-loading (see https://github.com/matrix-org/synapse/issues/4300) --- examples/qmc-example.cpp | 6 +++--- lib/room.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'examples') diff --git a/examples/qmc-example.cpp b/examples/qmc-example.cpp index 48787e44..5c05d44b 100644 --- a/examples/qmc-example.cpp +++ b/examples/qmc-example.cpp @@ -163,9 +163,9 @@ void QMCTest::loadMembers() { running.push_back("Loading members"); // The dedicated qmc-test room is too small to test - // lazy-loading-then-full-loading; use #test:matrix.org instead. + // lazy-loading-then-full-loading; use #qmatrixclient:matrix.org instead. // TODO: #264 - auto* r = c->room(QStringLiteral("!vfFxDRtZSSdspfTSEr:matrix.org")); + auto* r = c->room(QStringLiteral("!PCzUtxtOjUySxSelof:matrix.org")); if (!r) { cout << "#test:matrix.org is not found in the test user's rooms" << endl; @@ -184,7 +184,7 @@ void QMCTest::loadMembers() r->setDisplayed(); connect(r, &Room::allMembersLoaded, [this,r] { QMC_CHECK("Loading members", - r->memberNames().size() + 1 >= r->joinedCount()); + r->memberNames().size() >= r->joinedCount()); }); } diff --git a/lib/room.cpp b/lib/room.cpp index 8f9095dd..7232741a 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -595,7 +595,7 @@ Room::rev_iter_t Room::findInTimeline(const QString& evtId) const void Room::Private::getAllMembers() { // If already loaded or already loading, there's nothing to do here. - if (q->joinedCount() - 1 <= membersMap.size() || isJobRunning(allMembersJob)) + if (q->joinedCount() <= membersMap.size() || isJobRunning(allMembersJob)) return; allMembersJob = connection->callApi( -- cgit v1.2.3 From 2b68f4ae10d6c61b47c983e496380c86da1ff211 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 16 Dec 2018 14:19:15 +0900 Subject: qmc-example: refactor QMCTest to properly order actions To be more specific: - No race condition in running tests when the test room is already joined; joining occurs before the very first sync. - qmc-example doesn't (in vain) wait for the last sync in order to make sure the final message ("All tests finished") is delivered - uses Room::messageSent() instead now. - Running QMCTest::loadMembers() does not rely on having a test room --- examples/qmc-example.cpp | 111 +++++++++++++++++++++++++---------------------- 1 file changed, 58 insertions(+), 53 deletions(-) (limited to 'examples') diff --git a/examples/qmc-example.cpp b/examples/qmc-example.cpp index 5c05d44b..a5e5a481 100644 --- a/examples/qmc-example.cpp +++ b/examples/qmc-example.cpp @@ -20,12 +20,13 @@ using namespace std::placeholders; class QMCTest : public QObject { public: - QMCTest(Connection* conn, QString testRoomName, QString source); + QMCTest(Connection* conn, QString targetRoomName, QString source); private slots: - void setup(); + void setupAndRun(); void onNewRoom(Room* r); - void startTests(); + void run(); + void doTests(); void loadMembers(); void sendMessage(); void addAndRemoveTag(); @@ -44,7 +45,7 @@ class QMCTest : public QObject QStringList succeeded; QStringList failed; QString origin; - QString testRoomName; + QString targetRoomName; Room* targetRoom = nullptr; }; @@ -69,43 +70,42 @@ class QMCTest : public QObject } QMCTest::QMCTest(Connection* conn, QString testRoomName, QString source) - : c(conn), origin(std::move(source)), testRoomName(std::move(testRoomName)) + : c(conn), origin(std::move(source)), targetRoomName(std::move(testRoomName)) { if (!origin.isEmpty()) cout << "Origin for the test message: " << origin.toStdString() << endl; - if (!testRoomName.isEmpty()) - cout << "Test room name: " << testRoomName.toStdString() << endl; + if (!targetRoomName.isEmpty()) + cout << "Test room name: " << targetRoomName.toStdString() << endl; - connect(c.data(), &Connection::connected, this, &QMCTest::setup); + connect(c.data(), &Connection::connected, this, &QMCTest::setupAndRun); connect(c.data(), &Connection::loadedRoomState, this, &QMCTest::onNewRoom); // Big countdown watchdog QTimer::singleShot(180000, this, &QMCTest::leave); } -void QMCTest::setup() +void QMCTest::setupAndRun() { cout << "Connected, server: " << c->homeserver().toDisplayString().toStdString() << endl; cout << "Access token: " << c->accessToken().toStdString() << endl; - // Setting up sync loop - c->setLazyLoading(true); - c->sync(); - connectSingleShot(c.data(), &Connection::syncDone, - this, &QMCTest::startTests); - connect(c.data(), &Connection::syncDone, c.data(), [this] { - cout << "Sync complete, " - << running.size() << " tests in the air" << endl; - if (!running.isEmpty()) - c->sync(10000); - else if (targetRoom) - { - targetRoom->postPlainText(origin % ": All tests finished"); - connect(targetRoom, &Room::pendingEventMerged, this, &QMCTest::leave); - } - else - finalize(); - }); + if (!targetRoomName.isEmpty()) + { + cout << "Joining " << targetRoomName.toStdString() << endl; + running.push_back("Join room"); + auto joinJob = c->joinRoom(targetRoomName); + connect(joinJob, &BaseJob::failure, this, + [this] { QMC_CHECK("Join room", false); finalize(); }); + // Connection::joinRoom() creates a Room object upon JoinRoomJob::success + // but this object is empty until the first sync is done. + connect(joinJob, &BaseJob::success, this, [this,joinJob] { + targetRoom = c->room(joinJob->roomId(), JoinState::Join); + QMC_CHECK("Join room", targetRoom != nullptr); + + run(); + }); + } else + run(); } void QMCTest::onNewRoom(Room* r) @@ -128,35 +128,40 @@ void QMCTest::onNewRoom(Room* r) }); } -void QMCTest::startTests() +void QMCTest::run() { - if (testRoomName.isEmpty()) + c->setLazyLoading(true); + c->sync(); + connectSingleShot(c.data(), &Connection::syncDone, this, &QMCTest::doTests); + connect(c.data(), &Connection::syncDone, c.data(), [this] { + cout << "Sync complete, " + << running.size() << " tests in the air" << endl; + if (!running.isEmpty()) + c->sync(10000); + else if (targetRoom) + { + targetRoom->postPlainText(origin % ": All tests finished"); + connect(targetRoom, &Room::messageSent, this, &QMCTest::leave); + } + else + finalize(); + }); +} + +void QMCTest::doTests() +{ + cout << "Starting tests" << endl; + + loadMembers(); + // Add here tests not requiring the test room + if (targetRoomName.isEmpty()) return; - cout << "Joining " << testRoomName.toStdString() << endl; - running.push_back("Join room"); - auto joinJob = c->joinRoom(testRoomName); - connect(joinJob, &BaseJob::failure, this, - [this] { QMC_CHECK("Join room", false); finalize(); }); - // As of BaseJob::success, a Room object is not guaranteed to even - // exist; it's a mere confirmation that the server processed - // the request. - connect(c.data(), &Connection::loadedRoomState, this, - [this] (Room* room) { - Q_ASSERT(room); // It's a grave failure if room is nullptr here - if (room->canonicalAlias() != testRoomName) - return; // Not our room - - targetRoom = room; - QMC_CHECK("Join room", true); - cout << "Starting tests" << endl; - - loadMembers(); - sendMessage(); - addAndRemoveTag(); - sendAndRedact(); - markDirectChat(); - }); + sendMessage(); + addAndRemoveTag(); + sendAndRedact(); + markDirectChat(); + // Add here tests with the test room } void QMCTest::loadMembers() -- cgit v1.2.3 From d81e33841e0e2bb7dacc562aea7b820900b0d074 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 16 Dec 2018 14:27:04 +0900 Subject: qmc-example: check Room::messageSent() more carefully --- examples/qmc-example.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'examples') diff --git a/examples/qmc-example.cpp b/examples/qmc-example.cpp index a5e5a481..652c1f92 100644 --- a/examples/qmc-example.cpp +++ b/examples/qmc-example.cpp @@ -140,8 +140,16 @@ void QMCTest::run() c->sync(10000); else if (targetRoom) { - targetRoom->postPlainText(origin % ": All tests finished"); - connect(targetRoom, &Room::messageSent, this, &QMCTest::leave); + // TODO: Waiting for proper futures to come so that it could be: +// targetRoom->postPlainText(origin % ": All tests finished") +// .then(this, &QMCTest::leave); + auto txnId = + targetRoom->postPlainText(origin % ": All tests finished"); + connect(targetRoom, &Room::messageSent, this, + [this,txnId] (QString serverTxnId) { + if (txnId == serverTxnId) + leave(); + }); } else finalize(); -- cgit v1.2.3 From 6a34116431067e8f5d56b8e24d6205ed1b9e35c8 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 5 Jan 2019 16:48:36 +0900 Subject: qmc-example: streamline redaction test ...using features from the new lib. --- examples/qmc-example.cpp | 76 +++++++++++++++++++----------------------------- 1 file changed, 30 insertions(+), 46 deletions(-) (limited to 'examples') diff --git a/examples/qmc-example.cpp b/examples/qmc-example.cpp index 652c1f92..894167a9 100644 --- a/examples/qmc-example.cpp +++ b/examples/qmc-example.cpp @@ -32,7 +32,7 @@ class QMCTest : public QObject void addAndRemoveTag(); void sendAndRedact(); void checkRedactionOutcome(const QString& evtIdToRedact, - RoomEventsRange events); + const QMetaObject::Connection& sc); void markDirectChat(); void checkDirectChatOutcome( const Connection::DirectChatsMap& added); @@ -250,70 +250,54 @@ void QMCTest::sendAndRedact() { running.push_back("Redaction"); cout << "Sending a message to redact" << endl; - if (auto* job = targetRoom->connection()->sendMessage(targetRoom->id(), - RoomMessageEvent(origin % ": message to redact"))) + auto txnId = targetRoom->postPlainText(origin % ": message to redact"); + if (txnId.isEmpty()) { - connect(job, &BaseJob::success, targetRoom, [job,this] { + QMC_CHECK("Redaction", false); + return; + } + connect(targetRoom, &Room::messageSent, this, + [this,txnId] (const QString& tId, const QString& evtId) { + if (tId != txnId) + return; + cout << "Redacting the message" << endl; - 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)); + targetRoom->redactEvent(evtId, origin); + QMetaObject::Connection sc; + sc = connect(targetRoom, &Room::addedMessages, this, + [this,sc,evtId] { checkRedactionOutcome(evtId, sc); }); }); - } else - QMC_CHECK("Redaction", false); } void QMCTest::checkRedactionOutcome(const QString& evtIdToRedact, - RoomEventsRange events) + const QMetaObject::Connection& sc) { - 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()) + auto it = targetRoom->findInTimeline(evtIdToRedact); + if (it == targetRoom->timelineEdge()) 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->postPlainText(msg); - return; - } cout << "The sync brought already redacted message" << endl; QMC_CHECK("Redaction", true); - // Not disconnecting because there are other connections from this class - // to aboutToAddNewMessages - checkSucceeded = true; + disconnect(sc); 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->postPlainText(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); - checkSucceeded = true; - disconnect(targetRoom, &Room::replacedEvent, nullptr, nullptr); + cout << "Message came non-redacted with the sync, waiting for redaction" + << endl; + connect(targetRoom, &Room::replacedEvent, this, + [this,evtIdToRedact] + (const RoomEvent* newEvent, const RoomEvent* oldEvent) { + if (oldEvent->id() == evtIdToRedact) + { + QMC_CHECK("Redaction", newEvent->isRedacted() && + newEvent->redactionReason() == origin); + disconnect(targetRoom, &Room::replacedEvent, nullptr, nullptr); + } }); - } void QMCTest::markDirectChat() -- cgit v1.2.3 From e31bc6fe6b87562ea7879ab5ad1f8556f6df2d1d Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 5 Jan 2019 21:03:39 +0900 Subject: qmc-example: upgrade sendMesage() test; add sendFile() test Now really closes #267. --- examples/qmc-example.cpp | 151 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 141 insertions(+), 10 deletions(-) (limited to 'examples') diff --git a/examples/qmc-example.cpp b/examples/qmc-example.cpp index 652c1f92..7be82a28 100644 --- a/examples/qmc-example.cpp +++ b/examples/qmc-example.cpp @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include #include @@ -20,7 +22,7 @@ using namespace std::placeholders; class QMCTest : public QObject { public: - QMCTest(Connection* conn, QString targetRoomName, QString source); + QMCTest(Connection* conn, QString testRoomName, QString source); private slots: void setupAndRun(); @@ -29,6 +31,9 @@ class QMCTest : public QObject void doTests(); void loadMembers(); void sendMessage(); + void sendFile(); + void checkFileSendingOutcome(const QString& txnId, + const QString& fileName); void addAndRemoveTag(); void sendAndRedact(); void checkRedactionOutcome(const QString& evtIdToRedact, @@ -47,6 +52,8 @@ class QMCTest : public QObject QString origin; QString targetRoomName; Room* targetRoom = nullptr; + + bool validatePendingEvent(const QString& txnId); }; #define QMC_CHECK(description, condition) \ @@ -69,6 +76,14 @@ class QMCTest : public QObject } \ } +bool QMCTest::validatePendingEvent(const QString& txnId) +{ + auto it = targetRoom->findPendingEvent(txnId); + return it != targetRoom->pendingEvents().end() && + it->deliveryStatus() == EventStatus::Submitted && + (*it)->transactionId() == txnId; +} + QMCTest::QMCTest(Connection* conn, QString testRoomName, QString source) : c(conn), origin(std::move(source)), targetRoomName(std::move(testRoomName)) { @@ -142,7 +157,8 @@ void QMCTest::run() { // TODO: Waiting for proper futures to come so that it could be: // targetRoom->postPlainText(origin % ": All tests finished") -// .then(this, &QMCTest::leave); +// .then(this, &QMCTest::leave); // Qt-style +// .then([this] { leave(); }); // STL-style auto txnId = targetRoom->postPlainText(origin % ": All tests finished"); connect(targetRoom, &Room::messageSent, this, @@ -166,6 +182,7 @@ void QMCTest::doTests() return; sendMessage(); + sendFile(); addAndRemoveTag(); sendAndRedact(); markDirectChat(); @@ -206,19 +223,133 @@ void QMCTest::sendMessage() running.push_back("Message sending"); cout << "Sending a message" << endl; auto txnId = targetRoom->postPlainText("Hello, " % origin % " is here"); - auto& pending = targetRoom->pendingEvents(); - if (pending.empty()) + if (!validatePendingEvent(txnId)) { + cout << "Invalid pending event right after submitting" << endl; QMC_CHECK("Message sending", false); return; } - auto it = std::find_if(pending.begin(), pending.end(), - [&txnId] (const auto& e) { - return e->transactionId() == txnId; + + QMetaObject::Connection sc; + sc = connect(targetRoom, &Room::pendingEventAboutToMerge, this, + [this,sc,txnId] (const RoomEvent* evt, int pendingIdx) { + const auto& pendingEvents = targetRoom->pendingEvents(); + Q_ASSERT(pendingIdx >= 0 && pendingIdx < int(pendingEvents.size())); + + if (evt->transactionId() != txnId) + return; + + disconnect(sc); + + QMC_CHECK("Message sending", + is(*evt) && !evt->id().isEmpty() && + pendingEvents[size_t(pendingIdx)]->transactionId() + == evt->transactionId()); + }); +} + +void QMCTest::sendFile() +{ + running.push_back("File sending"); + cout << "Sending a file" << endl; + auto* tf = new QTemporaryFile; + if (!tf->open()) + { + cout << "Failed to create a temporary file" << endl; + QMC_CHECK("File sending", false); + return; + } + tf->write("Test"); + tf->close(); + // QFileInfo::fileName brings only the file name; QFile::fileName brings + // the full path + const auto tfName = QFileInfo(*tf).fileName(); + cout << "Sending file" << tfName.toStdString() << endl; + const auto txnId = targetRoom->postFile("Test file", + QUrl::fromLocalFile(tf->fileName())); + if (!validatePendingEvent(txnId)) + { + cout << "Invalid pending event right after submitting" << endl; + QMC_CHECK("File sending", false); + delete tf; + return; + } + + QMetaObject::Connection scCompleted, scFailed; + scCompleted = connect(targetRoom, &Room::fileTransferCompleted, this, + [this,txnId,tf,tfName,scCompleted,scFailed] (const QString& id) { + auto fti = targetRoom->fileTransferInfo(id); + Q_ASSERT(fti.status == FileTransferInfo::Completed); + + if (id != txnId) + return; + + disconnect(scCompleted); + disconnect(scFailed); + delete tf; + + checkFileSendingOutcome(txnId, tfName); + }); + scFailed = connect(targetRoom, &Room::fileTransferFailed, this, + [this,txnId,tf,scCompleted,scFailed] + (const QString& id, const QString& error) { + if (id != txnId) + return; + + targetRoom->postPlainText(origin % ": File upload failed: " % error); + disconnect(scCompleted); + disconnect(scFailed); + delete tf; + + QMC_CHECK("File sending", false); + }); +} + +void QMCTest::checkFileSendingOutcome(const QString& txnId, + const QString& fileName) +{ + auto it = targetRoom->findPendingEvent(txnId); + if (it == targetRoom->pendingEvents().end()) + { + cout << "Pending file event dropped before upload completion" + << endl; + QMC_CHECK("File sending", false); + return; + } + if (it->deliveryStatus() != EventStatus::FileUploaded) + { + cout << "Pending file event status upon upload completion is " + << it->deliveryStatus() << " != FileUploaded(" + << EventStatus::FileUploaded << ')' << endl; + QMC_CHECK("File sending", false); + return; + } + + QMetaObject::Connection sc; + sc = connect(targetRoom, &Room::pendingEventAboutToMerge, this, + [this,sc,txnId,fileName] (const RoomEvent* evt, int pendingIdx) { + const auto& pendingEvents = targetRoom->pendingEvents(); + Q_ASSERT(pendingIdx >= 0 && pendingIdx < int(pendingEvents.size())); + + if (evt->transactionId() != txnId) + return; + + cout << "Event " << txnId.toStdString() + << " arrived in the timeline" << endl; + disconnect(sc); + visit(*evt, + [&] (const RoomMessageEvent& e) { + QMC_CHECK("File sending", + !e.id().isEmpty() && + pendingEvents[size_t(pendingIdx)] + ->transactionId() == txnId && + e.hasFileContent() && + e.content()->fileInfo()->originalName == fileName); + }, + [this] (const RoomEvent&) { + QMC_CHECK("File sending", false); }); - QMC_CHECK("Message sending", it != pending.end()); - // TODO: Wait when it actually gets sent; check that it obtained an id - // Independently, check when it shows up in the timeline. + }); } void QMCTest::addAndRemoveTag() -- cgit v1.2.3 From 27555e44dfbaae26a0e030cb3c22eb00ba8371f0 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 5 Jan 2019 22:10:47 +0900 Subject: Add Qt5::Multimedia to examples/CMakeLists.txt too --- examples/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'examples') diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 49e0089a..cd5e15ed 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -45,7 +45,7 @@ foreach (FLAG all "" pedantic extra error=return-type no-unused-parameter no-gnu endif () endforeach () -find_package(Qt5 5.6 REQUIRED Network Gui) +find_package(Qt5 5.6 REQUIRED Network Gui Multimedia) get_filename_component(Qt5_Prefix "${Qt5_DIR}/../../../.." ABSOLUTE) find_package(QMatrixClient REQUIRED) -- cgit v1.2.3 From 4824705ea4eddfdb5d3845a64a96a1f5e2c022d0 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 13 Jan 2019 13:40:12 +0900 Subject: qmc-example: improve conclusion code Make the HTML version of the report and send it to the room if available (tests HTML outlooks along the way). --- examples/qmc-example.cpp | 73 +++++++++++++++++++++++++++++------------------- 1 file changed, 44 insertions(+), 29 deletions(-) (limited to 'examples') diff --git a/examples/qmc-example.cpp b/examples/qmc-example.cpp index 8fbf4824..372a80ad 100644 --- a/examples/qmc-example.cpp +++ b/examples/qmc-example.cpp @@ -41,7 +41,7 @@ class QMCTest : public QObject void markDirectChat(); void checkDirectChatOutcome( const Connection::DirectChatsMap& added); - void leave(); + void conclude(); void finalize(); private: @@ -95,7 +95,7 @@ QMCTest::QMCTest(Connection* conn, QString testRoomName, QString source) connect(c.data(), &Connection::connected, this, &QMCTest::setupAndRun); connect(c.data(), &Connection::loadedRoomState, this, &QMCTest::onNewRoom); // Big countdown watchdog - QTimer::singleShot(180000, this, &QMCTest::leave); + QTimer::singleShot(180000, this, &QMCTest::conclude); } void QMCTest::setupAndRun() @@ -110,7 +110,7 @@ void QMCTest::setupAndRun() running.push_back("Join room"); auto joinJob = c->joinRoom(targetRoomName); connect(joinJob, &BaseJob::failure, this, - [this] { QMC_CHECK("Join room", false); finalize(); }); + [this] { QMC_CHECK("Join room", false); conclude(); }); // Connection::joinRoom() creates a Room object upon JoinRoomJob::success // but this object is empty until the first sync is done. connect(joinJob, &BaseJob::success, this, [this,joinJob] { @@ -149,26 +149,12 @@ void QMCTest::run() c->sync(); connectSingleShot(c.data(), &Connection::syncDone, this, &QMCTest::doTests); connect(c.data(), &Connection::syncDone, c.data(), [this] { - cout << "Sync complete, " - << running.size() << " tests in the air" << endl; + cout << "Sync complete, " << running.size() << " test(s) in the air: " + << running.join(", ").toStdString() << endl; if (!running.isEmpty()) c->sync(10000); - else if (targetRoom) - { - // TODO: Waiting for proper futures to come so that it could be: -// targetRoom->postPlainText(origin % ": All tests finished") -// .then(this, &QMCTest::leave); // Qt-style -// .then([this] { leave(); }); // STL-style - auto txnId = - targetRoom->postPlainText(origin % ": All tests finished"); - connect(targetRoom, &Room::messageSent, this, - [this,txnId] (QString serverTxnId) { - if (txnId == serverTxnId) - leave(); - }); - } else - finalize(); + conclude(); }); } @@ -469,13 +455,47 @@ void QMCTest::checkDirectChatOutcome(const Connection::DirectChatsMap& added) QMC_CHECK("Direct chat test", !c->isDirectChat(targetRoom->id())); } -void QMCTest::leave() +void QMCTest::conclude() { + auto succeededRec = QString::number(succeeded.size()) + " tests succeeded"; + if (!failed.isEmpty() || !running.isEmpty()) + succeededRec += " of " % + QString::number(succeeded.size() + failed.size() + running.size()) % + " total"; + QString plainReport = origin % ": Testing complete, " % succeededRec; + QString color = failed.isEmpty() && running.isEmpty() ? "00AA00" : "AA0000"; + QString htmlReport = origin % ": Testing complete, " % + succeededRec; + if (!failed.isEmpty()) + { + plainReport += "\nFAILED: " % failed.join(", "); + htmlReport += "
Failed: " % failed.join(", "); + } + if (!running.isEmpty()) + { + plainReport += "\nDID NOT FINISH: " % running.join(", "); + htmlReport += + "
Did not finish: " % running.join(", "); + } + cout << plainReport.toStdString() << endl; + if (targetRoom) { - cout << "Leaving the room" << endl; - connect(targetRoom->leaveRoom(), &BaseJob::finished, - this, &QMCTest::finalize); + // TODO: Waiting for proper futures to come so that it could be: +// targetRoom->postHtmlText(...) +// .then(this, &QMCTest::finalize); // Qt-style or +// .then([this] { finalize(); }); // STL-style + auto txnId = targetRoom->postHtmlText(plainReport, htmlReport); + connect(targetRoom, &Room::messageSent, this, + [this,txnId] (QString serverTxnId) { + if (txnId != serverTxnId) + return; + + cout << "Leaving the room" << endl; + connect(targetRoom->leaveRoom(), &BaseJob::finished, + this, &QMCTest::finalize); + }); } else finalize(); @@ -487,11 +507,6 @@ void QMCTest::finalize() c->logout(); connect(c.data(), &Connection::loggedOut, qApp, [this] { - if (!failed.isEmpty()) - cout << "FAILED: " << failed.join(", ").toStdString() << endl; - if (!running.isEmpty()) - cout << "DID NOT FINISH: " - << running.join(", ").toStdString() << endl; QCoreApplication::processEvents(); QCoreApplication::exit(failed.size() + running.size()); }); -- cgit v1.2.3 From 5544331af35bb3f0533975611d1e432ba6817a5c Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 10 Jan 2019 16:51:44 +0900 Subject: qmc-example: use connectUntil() --- examples/qmc-example.cpp | 77 +++++++++++++++++++++--------------------------- 1 file changed, 34 insertions(+), 43 deletions(-) (limited to 'examples') diff --git a/examples/qmc-example.cpp b/examples/qmc-example.cpp index 372a80ad..4b39b032 100644 --- a/examples/qmc-example.cpp +++ b/examples/qmc-example.cpp @@ -36,8 +36,7 @@ class QMCTest : public QObject const QString& fileName); void addAndRemoveTag(); void sendAndRedact(); - void checkRedactionOutcome(const QString& evtIdToRedact, - const QMetaObject::Connection& sc); + bool checkRedactionOutcome(const QString& evtIdToRedact); void markDirectChat(); void checkDirectChatOutcome( const Connection::DirectChatsMap& added); @@ -216,21 +215,19 @@ void QMCTest::sendMessage() return; } - QMetaObject::Connection sc; - sc = connect(targetRoom, &Room::pendingEventAboutToMerge, this, - [this,sc,txnId] (const RoomEvent* evt, int pendingIdx) { + connectUntil(targetRoom, &Room::pendingEventAboutToMerge, this, + [this,txnId] (const RoomEvent* evt, int pendingIdx) { const auto& pendingEvents = targetRoom->pendingEvents(); Q_ASSERT(pendingIdx >= 0 && pendingIdx < int(pendingEvents.size())); if (evt->transactionId() != txnId) - return; - - disconnect(sc); + return false; QMC_CHECK("Message sending", is(*evt) && !evt->id().isEmpty() && pendingEvents[size_t(pendingIdx)]->transactionId() == evt->transactionId()); + return true; }); } @@ -261,30 +258,26 @@ void QMCTest::sendFile() return; } - QMetaObject::Connection scCompleted, scFailed; - scCompleted = connect(targetRoom, &Room::fileTransferCompleted, this, - [this,txnId,tf,tfName,scCompleted,scFailed] (const QString& id) { + // FIXME: Clean away connections (connectUntil doesn't help here). + connect(targetRoom, &Room::fileTransferCompleted, this, + [this,txnId,tf,tfName] (const QString& id) { auto fti = targetRoom->fileTransferInfo(id); Q_ASSERT(fti.status == FileTransferInfo::Completed); if (id != txnId) return; - disconnect(scCompleted); - disconnect(scFailed); delete tf; checkFileSendingOutcome(txnId, tfName); }); - scFailed = connect(targetRoom, &Room::fileTransferFailed, this, - [this,txnId,tf,scCompleted,scFailed] + connect(targetRoom, &Room::fileTransferFailed, this, + [this,txnId,tf] (const QString& id, const QString& error) { if (id != txnId) return; targetRoom->postPlainText(origin % ": File upload failed: " % error); - disconnect(scCompleted); - disconnect(scFailed); delete tf; QMC_CHECK("File sending", false); @@ -311,18 +304,16 @@ void QMCTest::checkFileSendingOutcome(const QString& txnId, return; } - QMetaObject::Connection sc; - sc = connect(targetRoom, &Room::pendingEventAboutToMerge, this, - [this,sc,txnId,fileName] (const RoomEvent* evt, int pendingIdx) { + connectUntil(targetRoom, &Room::pendingEventAboutToMerge, this, + [this,txnId,fileName] (const RoomEvent* evt, int pendingIdx) { const auto& pendingEvents = targetRoom->pendingEvents(); Q_ASSERT(pendingIdx >= 0 && pendingIdx < int(pendingEvents.size())); if (evt->transactionId() != txnId) - return; + return false; - cout << "Event " << txnId.toStdString() + cout << "File event " << txnId.toStdString() << " arrived in the timeline" << endl; - disconnect(sc); visit(*evt, [&] (const RoomMessageEvent& e) { QMC_CHECK("File sending", @@ -335,6 +326,7 @@ void QMCTest::checkFileSendingOutcome(const QString& txnId, [this] (const RoomEvent&) { QMC_CHECK("File sending", false); }); + return true; }); } @@ -356,7 +348,7 @@ void QMCTest::addAndRemoveTag() cout << "Test tag set, removing it now" << endl; targetRoom->removeTag(TestTag); QMC_CHECK("Tagging test", !targetRoom->tags().contains(TestTag)); - QObject::disconnect(targetRoom, &Room::tagsChanged, nullptr, nullptr); + disconnect(targetRoom, &Room::tagsChanged, nullptr, nullptr); } }); cout << "Adding a tag" << endl; @@ -380,41 +372,40 @@ void QMCTest::sendAndRedact() cout << "Redacting the message" << endl; targetRoom->redactEvent(evtId, origin); - QMetaObject::Connection sc; - sc = connect(targetRoom, &Room::addedMessages, this, - [this,sc,evtId] { checkRedactionOutcome(evtId, sc); }); + + connectUntil(targetRoom, &Room::addedMessages, this, + [this,evtId] { return checkRedactionOutcome(evtId); }); }); } -void QMCTest::checkRedactionOutcome(const QString& evtIdToRedact, - const QMetaObject::Connection& sc) +bool QMCTest::checkRedactionOutcome(const QString& evtIdToRedact) { // 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 = targetRoom->findInTimeline(evtIdToRedact); if (it == targetRoom->timelineEdge()) - return; // Waiting for the next sync + return false; // Waiting for the next sync if ((*it)->isRedacted()) { cout << "The sync brought already redacted message" << endl; QMC_CHECK("Redaction", true); - disconnect(sc); - return; - } - cout << "Message came non-redacted with the sync, waiting for redaction" - << endl; - connect(targetRoom, &Room::replacedEvent, this, - [this,evtIdToRedact] - (const RoomEvent* newEvent, const RoomEvent* oldEvent) { - if (oldEvent->id() == evtIdToRedact) - { + } else { + cout << "Message came non-redacted with the sync, waiting for redaction" + << endl; + connectUntil(targetRoom, &Room::replacedEvent, this, + [this,evtIdToRedact] + (const RoomEvent* newEvent, const RoomEvent* oldEvent) { + if (oldEvent->id() != evtIdToRedact) + return false; + QMC_CHECK("Redaction", newEvent->isRedacted() && newEvent->redactionReason() == origin); - disconnect(targetRoom, &Room::replacedEvent, nullptr, nullptr); - } - }); + return true; + }); + } + return true; } void QMCTest::markDirectChat() -- cgit v1.2.3 From e2c0148960e6e2b4595599de94d7a867f13782a0 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 10 Jan 2019 16:52:31 +0900 Subject: qmc-example: add setTopic test for true and fake state changes --- examples/qmc-example.cpp | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) (limited to 'examples') diff --git a/examples/qmc-example.cpp b/examples/qmc-example.cpp index 4b39b032..421ead27 100644 --- a/examples/qmc-example.cpp +++ b/examples/qmc-example.cpp @@ -5,6 +5,7 @@ #include "csapi/room_send.h" #include "csapi/joining.h" #include "csapi/leaving.h" +#include "events/simplestateevents.h" #include #include @@ -34,6 +35,7 @@ class QMCTest : public QObject void sendFile(); void checkFileSendingOutcome(const QString& txnId, const QString& fileName); + void setTopic(); void addAndRemoveTag(); void sendAndRedact(); bool checkRedactionOutcome(const QString& evtIdToRedact); @@ -168,6 +170,7 @@ void QMCTest::doTests() sendMessage(); sendFile(); + setTopic(); addAndRemoveTag(); sendAndRedact(); markDirectChat(); @@ -327,6 +330,46 @@ void QMCTest::checkFileSendingOutcome(const QString& txnId, QMC_CHECK("File sending", false); }); return true; + }); +} + +void QMCTest::setTopic() +{ + static const char* const stateTestName = "State setting test"; + static const char* const fakeStateTestName = "Fake state event immunity test"; + running.push_back(stateTestName); + running.push_back(fakeStateTestName); + auto initialTopic = targetRoom->topic(); + + const auto newTopic = c->generateTxnId(); + targetRoom->setTopic(newTopic); // Sets the state by proper means + const auto fakeTopic = c->generateTxnId(); + targetRoom->postJson(RoomTopicEvent::matrixTypeId(), // Fake state event + RoomTopicEvent(fakeTopic).contentJson()); + + connectUntil(targetRoom, &Room::topicChanged, this, + [this,newTopic,fakeTopic,initialTopic] { + if (targetRoom->topic() == newTopic) + { + QMC_CHECK(stateTestName, true); + // Don't reset the topic yet if the negative test still runs + if (!running.contains(fakeStateTestName)) + targetRoom->setTopic(initialTopic); + + return true; + } + return false; + }); + + connectUntil(targetRoom, &Room::pendingEventAboutToMerge, this, + [this,fakeTopic,initialTopic] (const RoomEvent* e, int) { + if (e->contentJson().value("topic").toString() != fakeTopic) + return false; // Wait on for the right event + + QMC_CHECK(fakeStateTestName, !e->isStateEvent()); + if (!running.contains(fakeStateTestName)) + targetRoom->setTopic(initialTopic); + return true; }); } -- cgit v1.2.3 From 0975f96207300b31279c63eaea597d2e9c435532 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 27 Feb 2019 15:28:57 +0900 Subject: qmc-example: use Connection::syncLoop --- examples/qmc-example.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'examples') diff --git a/examples/qmc-example.cpp b/examples/qmc-example.cpp index 421ead27..9d6f2f39 100644 --- a/examples/qmc-example.cpp +++ b/examples/qmc-example.cpp @@ -147,14 +147,12 @@ void QMCTest::onNewRoom(Room* r) void QMCTest::run() { c->setLazyLoading(true); - c->sync(); + c->syncLoop(); connectSingleShot(c.data(), &Connection::syncDone, this, &QMCTest::doTests); connect(c.data(), &Connection::syncDone, c.data(), [this] { cout << "Sync complete, " << running.size() << " test(s) in the air: " << running.join(", ").toStdString() << endl; - if (!running.isEmpty()) - c->sync(10000); - else + if (running.isEmpty()) conclude(); }); } @@ -491,6 +489,7 @@ void QMCTest::checkDirectChatOutcome(const Connection::DirectChatsMap& added) void QMCTest::conclude() { + c->stopSync(); auto succeededRec = QString::number(succeeded.size()) + " tests succeeded"; if (!failed.isEmpty() || !running.isEmpty()) succeededRec += " of " % -- cgit v1.2.3 From c659d18f36aa9f587003d5f50a9734c85d684a7c Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 27 Mar 2019 19:09:28 +0900 Subject: qmc-example: add a couple homeserver data sanity checks --- examples/qmc-example.cpp | 2 ++ 1 file changed, 2 insertions(+) (limited to 'examples') diff --git a/examples/qmc-example.cpp b/examples/qmc-example.cpp index 9d6f2f39..bd9190b9 100644 --- a/examples/qmc-example.cpp +++ b/examples/qmc-example.cpp @@ -101,6 +101,8 @@ QMCTest::QMCTest(Connection* conn, QString testRoomName, QString source) void QMCTest::setupAndRun() { + Q_ASSERT(!c->homeserver().isEmpty() && c->homeserver().isValid()); + Q_ASSERT(c->domain() == c->userId().section(':', 1)); cout << "Connected, server: " << c->homeserver().toDisplayString().toStdString() << endl; cout << "Access token: " << c->accessToken().toStdString() << endl; -- cgit v1.2.3