aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/quotest.cpp570
1 files changed, 327 insertions, 243 deletions
diff --git a/tests/quotest.cpp b/tests/quotest.cpp
index f3a09e41..91f4f636 100644
--- a/tests/quotest.cpp
+++ b/tests/quotest.cpp
@@ -8,8 +8,10 @@
#include "csapi/room_send.h"
#include "events/reactionevent.h"
+#include "events/redactionevent.h"
#include "events/simplestateevents.h"
+#include <QtTest/QSignalSpy>
#include <QtCore/QCoreApplication>
#include <QtCore/QFileInfo>
#include <QtCore/QStringBuilder>
@@ -22,57 +24,118 @@
using namespace Quotient;
using std::cout, std::endl;
+class TestSuite;
+
class TestManager : public QObject {
public:
TestManager(Connection* conn, QString testRoomName, QString source);
-private slots:
- // clang-format off
+private:
void setupAndRun();
void onNewRoom(Room* r);
- void run();
void doTests();
- void loadMembers();
- void sendMessage();
- void sendReaction(const QString& targetEvtId);
- void sendFile();
- void checkFileSendingOutcome(const QString& txnId,
- const QString& fileName);
- void setTopic();
- void sendAndRedact();
- bool checkRedactionOutcome(const QString& evtIdToRedact);
- void addAndRemoveTag();
- void markDirectChat();
- void checkDirectChatOutcome(
- const Connection::DirectChatsMap& added);
void conclude();
void finalize();
- // clang-format on
private:
QScopedPointer<Connection, QScopedPointerDeleteLater> c;
- QStringList running;
- QStringList succeeded;
- QStringList failed;
QString origin;
QString targetRoomName;
- Room* targetRoom = nullptr;
+ TestSuite* testSuite = nullptr;
+ QByteArrayList running {}, succeeded {}, failed {};
+};
using TestToken = QByteArray; // return value of QMetaMethod::name
// For now, the token itself is the test name but that may change.
const char* testName(const TestToken& token) { return token.constData(); }
+
+/// Test function declaration
+/*!
+ * \return true, if the test finished (successfully or unsuccessfully);
+ * false, if the test went async and will complete later
+ */
+#define TEST_DECL(Name) bool Name(const TestToken& thisTest);
+
+/// The holder for the actual tests
+/*!
+ * This class takes inspiration from Qt Test in terms of tests invocation;
+ * TestManager instantiates it and runs all public slots (cf. private slots in
+ * Qt Test) one after another. An important diversion from Qt Test is that
+ * the tests are assumed to by asynchronous rather than synchronous; so it's
+ * perfectly normal to have a few tests running at the same time. To avoid
+ * context clashes a special parameter with the name thisTest is passed to
+ * each test. Each test must conclude (synchronously or asynchronously) with
+ * an invocation of FINISH_TEST() macro (or FAIL_TEST() macro that expands to
+ * FINISH_TEST) that expects thisTest variable to be reachable. If FINISH_TEST()
+ * is invoked twice with the same thisTest, the second call will cause assertion
+ * failure; if FINISH_TEST() is not invoked at all, the test will be killed
+ * by a watchdog after a timeout and marked in the final report as not finished.
+ */
+class TestSuite : public QObject {
+ Q_OBJECT
+public:
+ TestSuite(Connection* conn, QString testRoomAlias, QString source,
+ TestManager* parent)
+ : QObject(parent)
+ , targetConn(conn)
+ , targetRoomAlias(std::move(testRoomAlias))
+ , origin(std::move(source))
+ {
+ Q_ASSERT(conn && parent);
+ Q_ASSERT(targetRoomAlias.startsWith('!')
+ || targetRoomAlias.startsWith('#'));
+ }
+
+ TEST_DECL(joinRoom)
+
+signals:
+ void finishedItem(QByteArray /*name*/, bool /*condition*/);
+
+public slots:
+ TEST_DECL(loadMembers)
+ TEST_DECL(sendMessage)
+ TEST_DECL(sendReaction)
+ TEST_DECL(sendFile)
+ TEST_DECL(setTopic)
+ TEST_DECL(sendAndRedact)
+ TEST_DECL(addAndRemoveTag)
+ TEST_DECL(markDirectChat)
+ // Add more tests above here
+
+public:
+ Room* room() const { return targetRoom; }
+ Connection* connection() const { return targetConn; }
+
+private slots:
+ bool checkFileSendingOutcome(const TestToken& thisTest,
+ const QString& txnId, const QString& fileName);
+ bool checkRedactionOutcome(const QByteArray& thisTest,
+ const QString& evtIdToRedact);
+
+private:
bool validatePendingEvent(const QString& txnId);
+ bool checkDirectChat() const;
void finishTest(const TestToken& token, bool condition, const char* file,
- int line);};
+ int line);
-#define TEST_IMPL(Name) void TestManager::Name()
+private:
+ Connection* targetConn;
+ QString targetRoomAlias;
+ QString origin;
+ Room* targetRoom = nullptr;
+};
-#define FINISH_TEST(description, Condition) \
- finishTest(description, Condition, __FILE__, __LINE__)
+#define TEST_IMPL(Name) bool TestSuite::Name(const TestToken& thisTest)
-#define FAIL_TEST(description) FINISH_TEST(description, false)
+// Returning true (rather than a void) allows to reuse the convention with
+// connectUntil() to break the QMetaObject::Connection upon finishing the test
+// item.
+#define FINISH_TEST(Condition) \
+ return (finishTest(thisTest, Condition, __FILE__, __LINE__), true)
-bool TestManager::validatePendingEvent(const QString& txnId)
+#define FAIL_TEST() FINISH_TEST(false)
+
+bool TestSuite::validatePendingEvent(const QString& txnId)
{
auto it = targetRoom->findPendingEvent(txnId);
return it != targetRoom->pendingEvents().end()
@@ -80,26 +143,23 @@ bool TestManager::validatePendingEvent(const QString& txnId)
&& (*it)->transactionId() == txnId;
}
-void TestManager::finishTest(const TestToken& token, bool condition,
+void TestSuite::finishTest(const TestToken& token, bool condition,
const char* file, int line)
{
const auto& item = testName(token);
- Q_ASSERT_X(running.contains(item), item,
- "Trying to finish an item that's not running");
- running.removeOne(item);
if (condition) {
- succeeded.push_back(item);
cout << item << " successful" << endl;
if (targetRoom)
targetRoom->postMessage(origin % ": " % item % " successful",
MessageEventType::Notice);
} else {
- failed.push_back(item);
cout << item << " FAILED at " << file << ":" << line << endl;
if (targetRoom)
targetRoom->postPlainText(origin % ": " % item % " FAILED at "
% file % ", line " % QString::number(line));
}
+
+ emit finishedItem(item, condition);
}
TestManager::TestManager(Connection* conn, QString testRoomName, QString source)
@@ -123,21 +183,10 @@ void TestManager::setupAndRun()
<< c->homeserver().toDisplayString().toStdString() << endl;
cout << "Access token: " << c->accessToken().toStdString() << endl;
- cout << "Joining " << targetRoomName.toStdString() << endl;
- running.push_back("Join room");
- auto joinJob = c->joinRoom(targetRoomName);
- connect(joinJob, &BaseJob::failure, this, [this] {
- FAIL_TEST("Join room");
- 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);
- FINISH_TEST("Join room", targetRoom != nullptr);
-
- run();
- });
+ c->setLazyLoading(true);
+ c->syncLoop();
+ connectSingleShot(c.data(), &Connection::syncDone, this,
+ &TestManager::doTests);
}
void TestManager::onNewRoom(Room* r)
@@ -161,136 +210,167 @@ void TestManager::onNewRoom(Room* r)
});
}
-void TestManager::run()
+void TestManager::doTests()
{
- c->setLazyLoading(true);
- c->syncLoop();
- connectSingleShot(c.data(), &Connection::syncDone, this, &TestManager::doTests);
+ cout << "Starting tests" << endl;
+ Q_ASSERT(!targetRoomName.isEmpty());
+ testSuite = new TestSuite(c.data(), targetRoomName, origin, this);
+ connect(testSuite, &TestSuite::finishedItem, this,
+ [this](const QByteArray& itemName, bool condition) {
+ if (auto i = running.indexOf(itemName); i != -1)
+ (condition ? succeeded : failed).push_back(running.takeAt(i));
+ else
+ Q_ASSERT_X(false, itemName,
+ "Test item is not in running state");
+ });
+
+ running.push_back("joinRoom");
+ testSuite->joinRoom("joinRoom");
+ connectSingleShot(testSuite, &TestSuite::finishedItem, this,
+ [this](const QByteArray&, bool condition) {
+ if (!condition) {
+ finalize();
+ return;
+ }
+ const auto* metaObj = testSuite->metaObject();
+ for (auto i = metaObj->methodOffset(); i < metaObj->methodCount();
+ ++i) {
+ const auto metaMethod = metaObj->method(i);
+ if (metaMethod.access() != QMetaMethod::Public
+ || metaMethod.methodType() != QMetaMethod::Slot)
+ continue;
+
+ // By now connectSingleShot() has already disconnected this
+ // slot so the tests below can emit finishedItem() without
+ // the risk of recursion.
+ cout << "Starting: " << metaMethod.name().constData() << endl;
+ running.push_back(metaMethod.name());
+ metaMethod.invoke(testSuite, Qt::DirectConnection,
+ Q_ARG(QByteArray, metaMethod.name()));
+ }
+ });
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())
+ cout << "Sync complete, ";
+ if (running.empty()) {
+ cout << "all tests finished" << endl;
conclude();
+ return;
+ }
+ cout << running.size() << " test(s) in the air:";
+ for (const auto& test: qAsConst(running))
+ cout << " " << testName(test);
+ cout << endl;
+ if (auto* r = testSuite->room())
+ cout << "Test room timeline size = " << r->timelineSize()
+ << ", pending size = " << r->pendingEvents().size() << endl;
});
}
-void TestManager::doTests()
+TEST_IMPL(joinRoom)
{
- cout << "Starting tests" << endl;
-
- loadMembers();
-
- sendMessage();
- sendFile();
- setTopic();
- addAndRemoveTag();
- sendAndRedact();
- markDirectChat();
- // Add here tests with the test room
+ cout << "Joining " << targetRoomAlias.toStdString() << endl;
+ auto joinJob = connection()->joinRoom(targetRoomAlias);
+ // Ensure, before this test is completed, that the room has been joined
+ // and filled with some events so that other tests could use that
+ connect(joinJob, &BaseJob::success, this, [this, joinJob, thisTest] {
+ targetRoom = connection()->room(joinJob->roomId());
+ targetRoom->getPreviousContent();
+ connectUntil(targetRoom, &Room::addedMessages, this, [this, thisTest] {
+ FINISH_TEST(targetRoom->memberJoinState(connection()->user())
+ == JoinState::Join);
+ });
+ });
+ connect(joinJob, &BaseJob::failure, this, [this, thisTest] { FAIL_TEST(); });
+ return false;
}
TEST_IMPL(loadMembers)
{
- running.push_back("Loading members");
- auto* r = c->roomByAlias(QStringLiteral("#quotient:matrix.org"),
- JoinState::Join);
+ // Trying to load members from another (larger) room
+ auto* r = connection()->roomByAlias(QStringLiteral("#quotient:matrix.org"),
+ JoinState::Join);
if (!r) {
cout << "#quotient:matrix.org is not found in the test user's rooms"
<< endl;
- FAIL_TEST("Loading members");
- return;
+ FAIL_TEST();
}
// 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;
- FAIL_TEST("Loading members");
- return;
+ FAIL_TEST();
}
r->setDisplayed();
- connect(r, &Room::allMembersLoaded, [this, r] {
- FINISH_TEST("Loading members",
- r->memberNames().size() >= r->joinedCount());
+ connect(r, &Room::allMembersLoaded, [this, thisTest, r] {
+ FINISH_TEST(r->memberNames().size() >= r->joinedCount());
});
+ return false;
}
TEST_IMPL(sendMessage)
{
- running.push_back("Message sending");
- cout << "Sending a message" << endl;
auto txnId = targetRoom->postPlainText("Hello, " % origin % " is here");
if (!validatePendingEvent(txnId)) {
cout << "Invalid pending event right after submitting" << endl;
- FAIL_TEST("Message sending");
- return;
+ FAIL_TEST();
}
- connectUntil(
- targetRoom, &Room::pendingEventAboutToMerge, this,
- [this, txnId](const RoomEvent* evt, int pendingIdx) {
+ connectUntil(targetRoom, &Room::pendingEventAboutToMerge, this,
+ [this, thisTest, 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;
- FINISH_TEST("Message sending",
- is<RoomMessageEvent>(*evt) && !evt->id().isEmpty()
- && pendingEvents[size_t(pendingIdx)]->transactionId()
- == evt->transactionId());
- sendReaction(evt->id());
- return true;
+ FINISH_TEST(is<RoomMessageEvent>(*evt) && !evt->id().isEmpty()
+ && pendingEvents[size_t(pendingIdx)]->transactionId()
+ == evt->transactionId());
});
+ return false;
}
-void TestManager::sendReaction(const QString& targetEvtId)
+TEST_IMPL(sendReaction)
{
- running.push_back("Reaction sending");
cout << "Reacting to the newest message in the room" << endl;
Q_ASSERT(targetRoom->timelineSize() > 0);
+ const auto targetEvtId = targetRoom->messageEvents().back()->id();
const auto key = QStringLiteral("+1");
const auto txnId = targetRoom->postReaction(targetEvtId, key);
if (!validatePendingEvent(txnId)) {
cout << "Invalid pending event right after submitting" << endl;
- FAIL_TEST("Reaction sending");
- return;
+ FAIL_TEST();
}
// TODO: Check that it came back as a reaction event and that it attached to
// the right event
connectUntil(
targetRoom, &Room::updatedEvent, this,
- [this, txnId, key, targetEvtId](const QString& actualTargetEvtId) {
+ [this, thisTest, txnId, key, targetEvtId](const QString& actualTargetEvtId) {
if (actualTargetEvtId != targetEvtId)
return false;
const auto reactions = targetRoom->relatedEvents(
targetEvtId, EventRelation::Annotation());
// It's a test room, assuming no interference there should
// be exactly one reaction
- if (reactions.size() != 1) {
- FAIL_TEST("Reaction sending");
- } else {
- const auto* evt =
- eventCast<const ReactionEvent>(reactions.back());
- FINISH_TEST("Reaction sending",
- is<ReactionEvent>(*evt) && !evt->id().isEmpty()
- && evt->relation().key == key
- && evt->transactionId() == txnId);
- }
- return true;
+ if (reactions.size() != 1)
+ FAIL_TEST();
+
+ const auto* evt =
+ eventCast<const ReactionEvent>(reactions.back());
+ FINISH_TEST(is<ReactionEvent>(*evt) && !evt->id().isEmpty()
+ && evt->relation().key == key
+ && evt->transactionId() == txnId);
});
+ return false;
}
TEST_IMPL(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;
- FAIL_TEST("File sending");
- return;
+ FAIL_TEST();
}
tf->write("Test");
tf->close();
@@ -303,56 +383,51 @@ TEST_IMPL(sendFile)
if (!validatePendingEvent(txnId)) {
cout << "Invalid pending event right after submitting" << endl;
delete tf;
- FAIL_TEST("File sending");
- return;
+ FAIL_TEST();
}
// 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;
+ connectUntil(targetRoom, &Room::fileTransferCompleted, this,
+ [this, thisTest, txnId, tf, tfName](const QString& id) {
+ auto fti = targetRoom->fileTransferInfo(id);
+ Q_ASSERT(fti.status == FileTransferInfo::Completed);
- delete tf;
-
- checkFileSendingOutcome(txnId, tfName);
- });
- connect(targetRoom, &Room::fileTransferFailed, this,
- [this, txnId, tf](const QString& id, const QString& error) {
- if (id != txnId)
- return;
+ if (id != txnId)
+ return false;
- targetRoom->postPlainText(origin % ": File upload failed: "
- % error);
- delete tf;
+ delete tf;
+ return checkFileSendingOutcome(thisTest, txnId, tfName);
+ });
+ connectUntil(targetRoom, &Room::fileTransferFailed, this,
+ [this, thisTest, txnId, tf](const QString& id, const QString& error) {
+ if (id != txnId)
+ return false;
- FAIL_TEST("File sending");
- });
+ targetRoom->postPlainText(origin % ": File upload failed: " % error);
+ delete tf;
+ FAIL_TEST();
+ });
+ return false;
}
-void TestManager::checkFileSendingOutcome(const QString& txnId,
- const QString& fileName)
+bool TestSuite::checkFileSendingOutcome(const TestToken& thisTest,
+ 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;
- FAIL_TEST("File sending");
- return;
+ FAIL_TEST();
}
if (it->deliveryStatus() != EventStatus::FileUploaded) {
cout << "Pending file event status upon upload completion is "
<< it->deliveryStatus() << " != FileUploaded("
<< EventStatus::FileUploaded << ')' << endl;
- FAIL_TEST("File sending");
- return;
+ FAIL_TEST();
}
- connectUntil(
- targetRoom, &Room::pendingEventAboutToMerge, this,
- [this, txnId, fileName](const RoomEvent* evt, int pendingIdx) {
+ connectUntil(targetRoom, &Room::pendingEventAboutToMerge, this,
+ [this, thisTest, txnId, fileName](const RoomEvent* evt, int pendingIdx) {
const auto& pendingEvents = targetRoom->pendingEvents();
Q_ASSERT(pendingIdx >= 0 && pendingIdx < int(pendingEvents.size()));
@@ -361,52 +436,49 @@ void TestManager::checkFileSendingOutcome(const QString& txnId,
cout << "File event " << txnId.toStdString()
<< " arrived in the timeline" << endl;
- visit(
+ // This part tests visit()
+ return visit(
*evt,
[&](const RoomMessageEvent& e) {
FINISH_TEST(
- "File sending",
!e.id().isEmpty()
&& pendingEvents[size_t(pendingIdx)]->transactionId()
== txnId
&& e.hasFileContent()
&& e.content()->fileInfo()->originalName == fileName);
},
- [this](const RoomEvent&) { FAIL_TEST("File sending"); });
- return true;
+ [this, thisTest](const RoomEvent&) { FAIL_TEST(); });
});
+ return true;
}
TEST_IMPL(setTopic)
{
- running.push_back("State setting test");
-
- const auto newTopic = c->generateTxnId(); // Just a way to get a unique id
+ const auto newTopic = connection()->generateTxnId(); // Just a way to make
+ // a unique id
targetRoom->setTopic(newTopic);
+ connectUntil(targetRoom, &Room::topicChanged, this,
+ [this, thisTest, newTopic] {
+ if (targetRoom->topic() == newTopic)
+ FINISH_TEST(true);
- connectUntil(targetRoom, &Room::topicChanged, this, [this, newTopic] {
- if (targetRoom->topic() == newTopic)
- FINISH_TEST("State setting test", true);
- else {
cout << "Requested topic was " << newTopic.toStdString() << ", "
<< targetRoom->topic().toStdString() << " arrived instead"
<< endl;
- }
- return true;
- });
+ return false;
+ });
+ return false;
}
TEST_IMPL(sendAndRedact)
{
- running.push_back("Redaction");
cout << "Sending a message to redact" << endl;
auto txnId = targetRoom->postPlainText(origin % ": message to redact");
- if (txnId.isEmpty()) {
- FAIL_TEST("Redaction");
- return;
- }
+ if (txnId.isEmpty())
+ FAIL_TEST();
+
connect(targetRoom, &Room::messageSent, this,
- [this, txnId](const QString& tId, const QString& evtId) {
+ [this, thisTest, txnId](const QString& tId, const QString& evtId) {
if (tId != txnId)
return;
@@ -414,13 +486,15 @@ TEST_IMPL(sendAndRedact)
targetRoom->redactEvent(evtId, origin);
connectUntil(targetRoom, &Room::addedMessages, this,
- [this, evtId] {
- return checkRedactionOutcome(evtId);
+ [this, thisTest, evtId] {
+ return checkRedactionOutcome(thisTest, evtId);
});
});
+ return false;
}
-bool TestManager::checkRedactionOutcome(const QString& evtIdToRedact)
+bool TestSuite::checkRedactionOutcome(const QByteArray& thisTest,
+ 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
@@ -431,83 +505,85 @@ bool TestManager::checkRedactionOutcome(const QString& evtIdToRedact)
if ((*it)->isRedacted()) {
cout << "The sync brought already redacted message" << endl;
- FINISH_TEST("Redaction", true);
- } 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;
-
- FINISH_TEST("Redaction",
- newEvent->isRedacted()
- && newEvent->redactionReason() == origin);
- return true;
- });
+ FINISH_TEST(true);
}
+
+ cout << "Message came non-redacted with the sync, waiting for redaction"
+ << endl;
+ connectUntil(targetRoom, &Room::replacedEvent, this,
+ [this, thisTest, evtIdToRedact](const RoomEvent* newEvent,
+ const RoomEvent* oldEvent) {
+ if (oldEvent->id() != evtIdToRedact)
+ return false;
+
+ FINISH_TEST(newEvent->isRedacted()
+ && newEvent->redactionReason() == origin);
+ });
return true;
}
TEST_IMPL(addAndRemoveTag)
{
- running.push_back("Tagging test");
- static const auto TestTag = QStringLiteral("org.quotient.test");
+ static const auto TestTag = QStringLiteral("im.quotient.test");
// Pre-requisite
if (targetRoom->tags().contains(TestTag))
targetRoom->removeTag(TestTag);
- // 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;
- if (targetRoom->tags().contains(TestTag)) {
- cout << "Test tag set, removing it now" << endl;
- targetRoom->removeTag(TestTag);
- FINISH_TEST("Tagging test", !targetRoom->tags().contains(TestTag));
- disconnect(targetRoom, &Room::tagsChanged, nullptr, nullptr);
- }
- });
- cout << "Adding a tag" << endl;
+ // Unlike for most of Quotient, tags are applied and tagsChanged is emitted
+ // synchronously, with the server being notified async. The test checks
+ // that the signal is emitted, not only that tags have changed; but there's
+ // (currently) no way to check that the server has been correctly notified
+ // of the tag change.
+ QSignalSpy spy(targetRoom, &Room::tagsChanged);
targetRoom->addTag(TestTag);
+ if (spy.count() != 1 || !targetRoom->tags().contains(TestTag)) {
+ cout << "Tag adding failed" << endl;
+ FAIL_TEST();
+ }
+ cout << "Test tag set, removing it now" << endl;
+ targetRoom->removeTag(TestTag);
+ FINISH_TEST(spy.count() == 2 && !targetRoom->tags().contains(TestTag));
}
-TEST_IMPL(markDirectChat)
+bool TestSuite::checkDirectChat() const
{
- running.push_back("Direct chat test");
- if (targetRoom->directChatUsers().contains(c->user())) {
- cout << "Warning: the room is already a direct chat,"
- " only unmarking will be tested"
- << endl;
- checkDirectChatOutcome({ { c->user(), targetRoom->id() } });
- return;
- }
- // Connect first because the signal is emitted synchronously.
- connect(c.data(), &Connection::directChatsListChanged, this,
- &TestManager::checkDirectChatOutcome);
- cout << "Marking the room as a direct chat" << endl;
- c->addToDirectChats(targetRoom, c->user());
+ return targetRoom->directChatUsers().contains(connection()->user());
}
-void TestManager::checkDirectChatOutcome(const Connection::DirectChatsMap& added)
+TEST_IMPL(markDirectChat)
{
- disconnect(c.data(), &Connection::directChatsListChanged, nullptr, nullptr);
- if (!targetRoom->isDirectChat()) {
- cout << "The room has not been marked as a direct chat" << endl;
- FAIL_TEST("Direct chat test");
- return;
- }
- if (!added.contains(c->user(), targetRoom->id())) {
- cout << "The room has not been listed in new direct chats" << endl;
- FAIL_TEST("Direct chat test");
- return;
+ if (checkDirectChat())
+ connection()->removeFromDirectChats(targetRoom->id(),
+ connection()->user());
+
+ int id = qRegisterMetaType<DirectChatsMap>(); // For QSignalSpy
+ Q_ASSERT(id != -1);
+
+ // Same as with tags (and unusual for the rest of Quotient), direct chat
+ // operations are synchronous.
+ QSignalSpy spy(connection(), &Connection::directChatsListChanged);
+ cout << "Marking the room as a direct chat" << endl;
+ connection()->addToDirectChats(targetRoom, connection()->user());
+ if (spy.count() != 1 || !checkDirectChat())
+ FAIL_TEST();
+
+ // Check that the first argument (added DCs) actually contains the room
+ const auto& addedDCs = spy.back().front().value<DirectChatsMap>();
+ if (addedDCs.size() != 1
+ || !addedDCs.contains(connection()->user(), targetRoom->id())) {
+ cout << "The room is not in added direct chats" << endl;
+ FAIL_TEST();
}
cout << "Unmarking the direct chat" << endl;
- c->removeFromDirectChats(targetRoom->id(), c->user());
- FINISH_TEST("Direct chat test", !c->isDirectChat(targetRoom->id()));
+ connection()->removeFromDirectChats(targetRoom->id(), connection()->user());
+ if (spy.count() != 2 && checkDirectChat())
+ FAIL_TEST();
+
+ // Check that the second argument (removed DCs) actually contains the room
+ const auto& removedDCs = spy.back().back().value<DirectChatsMap>();
+ FINISH_TEST(removedDCs.size() == 1
+ && removedDCs.contains(connection()->user(), targetRoom->id()));
}
void TestManager::conclude()
@@ -525,33 +601,39 @@ void TestManager::conclude()
% "' color='#" % color
% "'>Testing complete</font></strong>, " % succeededRec;
if (!failed.empty()) {
- plainReport += "\nFAILED: " % failed.join(", ");
- htmlReport += "<br><strong>Failed:</strong> " % failed.join(", ");
+ QByteArray failedList;
+ for (const auto& f : failed)
+ failedList += ' ' + f;
+ plainReport += "\nFAILED:" + failedList;
+ htmlReport += "<br><strong>Failed:</strong>" + failedList;
}
if (!running.empty()) {
- plainReport += "\nDID NOT FINISH: " % running.join(", ");
- htmlReport += "<br><strong>Did not finish:</strong> "
- % running.join(", ");
+ QByteArray dnfList;
+ for (const auto& r : running)
+ dnfList += ' ' + r;
+ plainReport += "\nDID NOT FINISH:" + dnfList;
+ htmlReport += "<br><strong>Did not finish:</strong>" + dnfList;
}
cout << plainReport.toStdString() << endl;
- if (targetRoom) {
- // TODO: Waiting for proper futures to come so that it could be:
- // targetRoom->postHtmlText(...)
- // .then(this, &TestManager::finalize); // Qt-style or
- // .then([this] { finalize(); }); // STL-style
- auto txnId = targetRoom->postHtmlText(plainReport, htmlReport);
- connect(targetRoom, &Room::messageSent, this,
- [this, txnId](const QString& serverTxnId) {
- if (txnId != serverTxnId)
- return;
-
- cout << "Leaving the room" << endl;
- connect(targetRoom->leaveRoom(), &BaseJob::finished, this,
- &TestManager::finalize);
+ // TODO: Waiting for proper futures to come so that it could be:
+ // targetRoom->postHtmlText(...)
+ // .then(this, &TestManager::finalize); // Qt-style or
+ // .then([this] { finalize(); }); // STL-style
+ auto* room = testSuite->room();
+ auto txnId = room->postHtmlText(plainReport, htmlReport);
+ connect(room, &Room::messageSent, this,
+ [this, room, txnId](const QString& serverTxnId) {
+ if (txnId != serverTxnId)
+ return;
+
+ cout << "Leaving the room" << endl;
+ auto* job = room->leaveRoom();
+ connect(job, &BaseJob::finished, this, [this, job] {
+ Q_ASSERT(job->status().good());
+ finalize();
});
- } else
- finalize();
+ });
}
void TestManager::finalize()
@@ -579,3 +661,5 @@ int main(int argc, char* argv[])
TestManager test { conn, argv[4], argc >= 6 ? argv[5] : nullptr };
return app.exec();
}
+
+#include "quotest.moc"