diff options
Diffstat (limited to 'quotest/quotest.cpp')
-rw-r--r-- | quotest/quotest.cpp | 168 |
1 files changed, 142 insertions, 26 deletions
diff --git a/quotest/quotest.cpp b/quotest/quotest.cpp index a0914c72..7bd9c5c3 100644 --- a/quotest/quotest.cpp +++ b/quotest/quotest.cpp @@ -5,6 +5,7 @@ #include "room.h" #include "user.h" #include "uriresolver.h" +#include "networkaccessmanager.h" #include "csapi/joining.h" #include "csapi/leaving.h" @@ -20,6 +21,8 @@ #include <QtCore/QStringBuilder> #include <QtCore/QTemporaryFile> #include <QtCore/QTimer> +#include <QtConcurrent/QtConcurrent> +#include <QtNetwork/QNetworkReply> #include <functional> #include <iostream> @@ -48,7 +51,7 @@ private: QByteArrayList running {}, succeeded {}, failed {}; }; -using TestToken = QByteArray; // return value of QMetaMethod::name +using TestToken = decltype(std::declval<QMetaMethod>().name()); Q_DECLARE_METATYPE(TestToken) // For now, the token itself is the test name but that may change. @@ -105,6 +108,7 @@ private slots: TEST_DECL(addAndRemoveTag) TEST_DECL(markDirectChat) TEST_DECL(visitResources) + TEST_DECL(prettyPrintTests) // Add more tests above here public: @@ -137,7 +141,7 @@ private: // connectUntil() to break the QMetaObject::Connection upon finishing the test // item. #define FINISH_TEST(Condition) \ - return (finishTest(thisTest, Condition, __FILE__, __LINE__), true) + return (finishTest(thisTest, (Condition), __FILE__, __LINE__), true) #define FAIL_TEST() FINISH_TEST(false) @@ -210,6 +214,7 @@ TestManager::TestManager(int& argc, char** argv) // Big countdown watchdog QTimer::singleShot(180000, this, [this] { + clog << "Time is up, stopping the session\n"; if (testSuite) conclude(); else @@ -241,7 +246,7 @@ void TestManager::setupAndRun() clog << "Sync " << ++i << " complete" << endl; if (auto* r = testSuite->room()) { clog << "Test room timeline size = " << r->timelineSize(); - if (r->pendingEvents().empty()) + if (!r->pendingEvents().empty()) clog << ", pending size = " << r->pendingEvents().size(); clog << endl; } @@ -397,15 +402,16 @@ TEST_IMPL(sendFile) } tf->write("Test"); tf->close(); + QFileInfo tfi { *tf }; // QFileInfo::fileName brings only the file name; QFile::fileName brings // the full path - const auto tfName = QFileInfo(*tf).fileName(); + const auto tfName = tfi.fileName(); clog << "Sending file " << tfName.toStdString() << endl; - const auto txnId = - targetRoom->postFile("Test file", QUrl::fromLocalFile(tf->fileName())); + const auto txnId = targetRoom->postFile( + "Test file", new EventContent::FileContent(tfi)); if (!validatePendingEvent(txnId)) { clog << "Invalid pending event right after submitting" << endl; - delete tf; + tf->deleteLater(); FAIL_TEST(); } @@ -434,6 +440,39 @@ TEST_IMPL(sendFile) return false; } +// Can be replaced with a lambda once QtConcurrent is able to resolve return +// types from lambda invocations (Qt 6 can, not sure about earlier) +struct DownloadRunner { + QUrl url; + + using result_type = QNetworkReply::NetworkError; + + QNetworkReply::NetworkError operator()(int) const + { + QEventLoop el; + QScopedPointer<QNetworkReply, QScopedPointerDeleteLater> reply { + NetworkAccessManager::instance()->get(QNetworkRequest(url)) + }; + QObject::connect( + reply.data(), &QNetworkReply::finished, &el, [&el] { el.exit(); }, + Qt::QueuedConnection); + el.exec(); + return reply->error(); + } +}; + +bool testDownload(const QUrl& url) +{ + // Move out actual test from the multithreaded code + // to help debugging + auto results = QtConcurrent::blockingMapped(QVector<int> { 1, 2, 3 }, + DownloadRunner { url }); + return std::all_of(results.cbegin(), results.cend(), + [](QNetworkReply::NetworkError ne) { + return ne == QNetworkReply::NoError; + }); +} + bool TestSuite::checkFileSendingOutcome(const TestToken& thisTest, const QString& txnId, const QString& fileName) @@ -460,18 +499,19 @@ bool TestSuite::checkFileSendingOutcome(const TestToken& thisTest, clog << "File event " << txnId.toStdString() << " arrived in the timeline" << endl; - // This part tests visit() - return visit( + // This part tests switchOnType() + return switchOnType( *evt, [&](const RoomMessageEvent& e) { - // TODO: actually try to download it to check, e.g., #366 - // (and #368 would help to test against bad file names). + // TODO: check #366 once #368 is implemented FINISH_TEST( !e.id().isEmpty() - && pendingEvents[size_t(pendingIdx)]->transactionId() - == txnId - && e.hasFileContent() - && e.content()->fileInfo()->originalName == fileName); + && pendingEvents[size_t(pendingIdx)]->transactionId() + == txnId + && e.hasFileContent() + && e.content()->fileInfo()->originalName == fileName + && testDownload(targetRoom->connection()->makeMediaUrl( + e.content()->fileInfo()->url))); }, [this, thisTest](const RoomEvent&) { FAIL_TEST(); }); }); @@ -498,14 +538,32 @@ TEST_IMPL(setTopic) TEST_IMPL(changeName) { - auto* const localUser = connection()->user(); - const auto& newName = connection()->generateTxnId(); // See setTopic() - clog << "Renaming the user to " << newName.toStdString() << endl; - localUser->rename(newName); - connectUntil(localUser, &User::defaultNameChanged, this, - [this, thisTest, localUser, newName] { - FINISH_TEST(localUser->name() == newName); - }); + connectSingleShot(targetRoom, &Room::allMembersLoaded, this, [this, thisTest] { + auto* const localUser = connection()->user(); + const auto& newName = connection()->generateTxnId(); // See setTopic() + clog << "Renaming the user to " << newName.toStdString() + << " in the target room" << endl; + localUser->rename(newName, targetRoom); + connectUntil(targetRoom, &Room::memberRenamed, this, + [this, thisTest, localUser, newName](const User* u) { + if (localUser != u) + return false; + if (localUser->name(targetRoom) != newName) + FAIL_TEST(); + + clog + << "Member rename successful, renaming the account" + << endl; + const auto newN = newName.mid(0, 5); + localUser->rename(newN); + connectUntil(localUser, &User::defaultNameChanged, + this, [this, thisTest, localUser, newN] { + targetRoom->localUser()->rename({}); + FINISH_TEST(localUser->name() == newN); + }); + return true; + }); + }); return false; } @@ -556,7 +614,7 @@ bool TestSuite::checkRedactionOutcome(const QByteArray& thisTest, // 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()) + if (it == targetRoom->historyEdge()) return false; // Waiting for the next sync if ((*it)->isRedacted()) { @@ -786,6 +844,52 @@ TEST_IMPL(visitResources) FINISH_TEST(true); } +bool checkPrettyPrint( + std::initializer_list<std::pair<const char*, const char*>> tests) +{ + bool result = true; + for (const auto& [test, etalon] : tests) { + const auto is = prettyPrint(test).toStdString(); + const auto shouldBe = std::string("<span style='white-space:pre-wrap'>") + + etalon + "</span>"; + if (is == shouldBe) + continue; + clog << is << " != " << shouldBe << endl; + result = false; + } + return result; +} + +TEST_IMPL(prettyPrintTests) +{ + const bool prettyPrintTestResult = checkPrettyPrint( + { { "https://www.matrix.org", + R"(<a href="https://www.matrix.org">https://www.matrix.org</a>)" }, +// { "www.matrix.org", // Doesn't work yet +// R"(<a href="https://www.matrix.org">www.matrix.org</a>)" }, + { "smb://somewhere/file", "smb://somewhere/file" }, // Disallowed scheme + { "https:/something", "https:/something" }, // Malformed URL + { "https://matrix.to/#/!roomid:example.org", + R"(<a href="https://matrix.to/#/!roomid:example.org">https://matrix.to/#/!roomid:example.org</a>)" }, + { "https://matrix.to/#/@user_id:example.org", + R"(<a href="https://matrix.to/#/@user_id:example.org">https://matrix.to/#/@user_id:example.org</a>)" }, + { "https://matrix.to/#/#roomalias:example.org", + R"(<a href="https://matrix.to/#/#roomalias:example.org">https://matrix.to/#/#roomalias:example.org</a>)" }, + { "https://matrix.to/#/##ircroomalias:example.org", + R"(<a href="https://matrix.to/#/##ircroomalias:example.org">https://matrix.to/#/##ircroomalias:example.org</a>)" }, + { "me@example.org", + R"(<a href="mailto:me@example.org">me@example.org</a>)" }, + { "mailto:me@example.org", + R"(<a href="mailto:me@example.org">mailto:me@example.org</a>)" }, + { "!room_id:example.org", + R"(<a href="https://matrix.to/#/!room_id:example.org">!room_id:example.org</a>)" }, + { "@user_id:example.org", + R"(<a href="https://matrix.to/#/@user_id:example.org">@user_id:example.org</a>)" }, + { "#room_alias:example.org", + R"(<a href="https://matrix.to/#/#room_alias:example.org">#room_alias:example.org</a>)" } }); + FINISH_TEST(prettyPrintTestResult); +} + void TestManager::conclude() { // Clean up the room (best effort) @@ -854,10 +958,22 @@ void TestManager::conclude() void TestManager::finalize() { + if (!c->isUsable() || !c->isLoggedIn()) { + clog << "No usable connection reached" << endl; + QCoreApplication::exit(-2); + return; // NB: QCoreApplication::exit() does return to the caller + } clog << "Logging out" << endl; c->logout(); - connect(c, &Connection::loggedOut, this, - [this] { QCoreApplication::exit(failed.size() + running.size()); }, + connect( + c, &Connection::loggedOut, this, + [this] { + QCoreApplication::exit(!testSuite ? -3 + : succeeded.empty() && failed.empty() + && running.empty() + ? -4 + : failed.size() + running.size()); + }, Qt::QueuedConnection); } |