aboutsummaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
authorHubert Chathi <uhoreg@debian.org>2019-06-25 16:33:24 -0400
committerHubert Chathi <uhoreg@debian.org>2019-06-25 16:33:24 -0400
commit72d5660efd0755bb53a8699cd39865155400d288 (patch)
treeed7e7537e6a3eb7e8b92226c4015f9bfc8e11c5a /examples
parent52407a933bfe1fcc5f3aa1dccaa0b9a8279aa634 (diff)
parent681203f951d13e9e8eaf772435cac28c6d74cd42 (diff)
downloadlibquotient-72d5660efd0755bb53a8699cd39865155400d288.tar.gz
libquotient-72d5660efd0755bb53a8699cd39865155400d288.zip
Merge branch 'upstream' (v0.5.2)
Diffstat (limited to 'examples')
-rw-r--r--examples/CMakeLists.txt2
-rw-r--r--examples/qmc-example.cpp480
2 files changed, 327 insertions, 155 deletions
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)
diff --git a/examples/qmc-example.cpp b/examples/qmc-example.cpp
index 206501a5..bd9190b9 100644
--- a/examples/qmc-example.cpp
+++ b/examples/qmc-example.cpp
@@ -10,6 +10,8 @@
#include <QtCore/QCoreApplication>
#include <QtCore/QStringBuilder>
#include <QtCore/QTimer>
+#include <QtCore/QTemporaryFile>
+#include <QtCore/QFileInfo>
#include <iostream>
#include <functional>
@@ -21,22 +23,26 @@ 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 setupAndRun();
void onNewRoom(Room* r);
- void startTests();
+ void run();
+ void doTests();
+ void loadMembers();
void sendMessage();
+ void sendFile();
+ void checkFileSendingOutcome(const QString& txnId,
+ const QString& fileName);
void setTopic();
void addAndRemoveTag();
void sendAndRedact();
- void checkRedactionOutcome(const QString& evtIdToRedact,
- RoomEventsRange events);
+ bool checkRedactionOutcome(const QString& evtIdToRedact);
void markDirectChat();
void checkDirectChatOutcome(
const Connection::DirectChatsMap& added);
- void leave();
+ void conclude();
void finalize();
private:
@@ -45,80 +51,79 @@ class QMCTest : public QObject
QStringList succeeded;
QStringList failed;
QString origin;
+ QString targetRoomName;
Room* targetRoom = nullptr;
+
+ bool validatePendingEvent(const QString& txnId);
};
#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"); \
+ } \
+}
+
+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, 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)), 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, std::bind(&QMCTest::setup, this, testRoomName));
+ 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::setup(const QString& testRoomName)
+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;
- // Setting up sync loop
- c->sync();
- connect(c.data(), &Connection::syncDone, c.data(), [this,testRoomName] {
- 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();
- });
-
- // Join a testroom, if provided
- if (!targetRoom && !testRoomName.isEmpty())
+ if (!targetRoomName.isEmpty())
{
- cout << "Joining " << testRoomName.toStdString() << endl;
+ cout << "Joining " << targetRoomName.toStdString() << endl;
running.push_back("Join room");
- auto joinJob = c->joinRoom(testRoomName);
+ auto joinJob = c->joinRoom(targetRoomName);
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);
- startTests();
- });
- }
+ [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] {
+ targetRoom = c->room(joinJob->roomId(), JoinState::Join);
+ QMC_CHECK("Join room", targetRoom != nullptr);
+
+ run();
+ });
+ } else
+ run();
}
void QMCTest::onNewRoom(Room* r)
@@ -141,14 +146,64 @@ void QMCTest::onNewRoom(Room* r)
});
}
-void QMCTest::startTests()
+void QMCTest::run()
+{
+ c->setLazyLoading(true);
+ 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())
+ conclude();
+ });
+}
+
+void QMCTest::doTests()
{
cout << "Starting tests" << endl;
+
+ loadMembers();
+ // Add here tests not requiring the test room
+ if (targetRoomName.isEmpty())
+ return;
+
sendMessage();
+ sendFile();
setTopic();
addAndRemoveTag();
sendAndRedact();
markDirectChat();
+ // Add here tests with the test room
+}
+
+void QMCTest::loadMembers()
+{
+ running.push_back("Loading members");
+ // The dedicated qmc-test room is too small to test
+ // lazy-loading-then-full-loading; use #qmatrixclient:matrix.org instead.
+ // TODO: #264
+ auto* r = c->room(QStringLiteral("!PCzUtxtOjUySxSelof: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,r] {
+ QMC_CHECK("Loading members",
+ r->memberNames().size() >= r->joinedCount());
+ });
}
void QMCTest::sendMessage()
@@ -156,25 +211,134 @@ 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;
+
+ 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 false;
+
+ QMC_CHECK("Message sending",
+ is<RoomMessageEvent>(*evt) && !evt->id().isEmpty() &&
+ pendingEvents[size_t(pendingIdx)]->transactionId()
+ == evt->transactionId());
+ return true;
+ });
+}
+
+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;
+ }
+
+ // 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;
+
+ delete tf;
+
+ checkFileSendingOutcome(txnId, tfName);
+ });
+ 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);
+ 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;
+ }
+
+ 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 false;
+
+ cout << "File event " << txnId.toStdString()
+ << " arrived in the timeline" << endl;
+ 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.
+ return true;
+ });
}
void QMCTest::setTopic()
{
- running.push_back("State setting test");
- running.push_back("Fake state event immunity test");
+ 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();
@@ -183,35 +347,30 @@ void QMCTest::setTopic()
targetRoom->postJson(RoomTopicEvent::matrixTypeId(), // Fake state event
RoomTopicEvent(fakeTopic).contentJson());
- {
- auto* context = new QObject;
- connect(targetRoom, &Room::topicChanged, context,
- [this,newTopic,fakeTopic,initialTopic,context] {
- if (targetRoom->topic() == newTopic)
- {
- QMC_CHECK("State setting test", true);
- // Don't reset the topic yet if the negative test still runs
- if (!running.contains("Fake state event immunity test"))
- targetRoom->setTopic(initialTopic);
-
- context->deleteLater();
- }
- });
- }
-
- {
- auto* context = new QObject;
- connect(targetRoom, &Room::pendingEventAboutToMerge, context,
- [this,fakeTopic,initialTopic,context] (const RoomEvent* e, int) {
- if (e->contentJson().value("topic").toString() != fakeTopic)
- return; // Wait on for the right event
-
- QMC_CHECK("Fake state event immunity test", !e->isStateEvent());
- if (!running.contains("State setting test"))
+ 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);
- context->deleteLater();
- });
- }
+
+ 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;
+ });
}
void QMCTest::addAndRemoveTag()
@@ -232,7 +391,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;
@@ -243,70 +402,53 @@ 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);
+
+ connectUntil(targetRoom, &Room::addedMessages, this,
+ [this,evtId] { return checkRedactionOutcome(evtId); });
});
- } else
- QMC_CHECK("Redaction", false);
}
-void QMCTest::checkRedactionOutcome(const QString& evtIdToRedact,
- RoomEventsRange events)
+bool QMCTest::checkRedactionOutcome(const QString& evtIdToRedact)
{
- 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
+ auto it = targetRoom->findInTimeline(evtIdToRedact);
+ if (it == targetRoom->timelineEdge())
+ return false; // 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;
- 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);
+ } 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);
+ return true;
+ });
}
- 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);
- });
-
+ return true;
}
void QMCTest::markDirectChat()
@@ -347,13 +489,48 @@ void QMCTest::checkDirectChatOutcome(const Connection::DirectChatsMap& added)
QMC_CHECK("Direct chat test", !c->isDirectChat(targetRoom->id()));
}
-void QMCTest::leave()
+void QMCTest::conclude()
{
+ c->stopSync();
+ 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 % ": <strong><font data-mx-color='#" % color %
+ "' color='#" % color % "'>Testing complete</font></strong>, " %
+ succeededRec;
+ if (!failed.isEmpty())
+ {
+ plainReport += "\nFAILED: " % failed.join(", ");
+ htmlReport += "<br><strong>Failed:</strong> " % failed.join(", ");
+ }
+ if (!running.isEmpty())
+ {
+ plainReport += "\nDID NOT FINISH: " % running.join(", ");
+ htmlReport +=
+ "<br><strong>Did not finish:</strong> " % 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();
@@ -365,11 +542,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());
});