diff options
Diffstat (limited to 'tests')
-rw-r--r-- | tests/CMakeLists.txt | 6 | ||||
-rw-r--r-- | tests/DummyResourceAPI.h | 46 | ||||
-rw-r--r-- | tests/Packwiz_test.cpp | 4 | ||||
-rw-r--r-- | tests/ResourceModel_test.cpp | 88 | ||||
-rw-r--r-- | tests/Task_test.cpp | 174 | ||||
-rw-r--r-- | tests/Version_test.cpp | 116 | ||||
-rw-r--r-- | tests/testdata/Version/test_vectors.txt | 63 |
7 files changed, 429 insertions, 68 deletions
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9f84a9a7..36a3b0f8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -24,6 +24,9 @@ ecm_add_test(ResourceFolderModel_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_V ecm_add_test(ResourcePackParse_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test TEST_NAME ResourcePackParse) +ecm_add_test(ResourceModel_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test + TEST_NAME ResourceModel) + ecm_add_test(TexturePackParse_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test TEST_NAME TexturePackParse) @@ -53,3 +56,6 @@ ecm_add_test(Packwiz_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR ecm_add_test(Index_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test TEST_NAME Index) + +ecm_add_test(Version_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test + TEST_NAME Version) diff --git a/tests/DummyResourceAPI.h b/tests/DummyResourceAPI.h new file mode 100644 index 00000000..0cc90958 --- /dev/null +++ b/tests/DummyResourceAPI.h @@ -0,0 +1,46 @@ +#pragma once + +#include <QJsonDocument> + +#include <modplatform/ResourceAPI.h> + +class SearchTask : public Task { + Q_OBJECT + + public: + void executeTask() override { emitSucceeded(); } +}; + +class DummyResourceAPI : public ResourceAPI { + public: + static auto searchRequestResult() + { + static QByteArray json_response = + "{\"hits\":[" + "{" + "\"author\":\"flowln\"," + "\"description\":\"the bestest mod\"," + "\"project_id\":\"something\"," + "\"project_type\":\"mod\"," + "\"slug\":\"bip_bop\"," + "\"title\":\"AAAAAAAA\"," + "\"versions\":[\"2.71\"]" + "}" + "]}"; + + return QJsonDocument::fromJson(json_response); + } + + DummyResourceAPI() : ResourceAPI() {} + [[nodiscard]] auto getSortingMethods() const -> QList<SortingMethod> override { return {}; }; + + [[nodiscard]] Task::Ptr searchProjects(SearchArgs&&, SearchCallbacks&& callbacks) const override + { + auto task = makeShared<SearchTask>(); + QObject::connect(task.get(), &Task::succeeded, [=] { + auto json = searchRequestResult(); + callbacks.on_succeed(json); + }); + return task; + } +}; diff --git a/tests/Packwiz_test.cpp b/tests/Packwiz_test.cpp index 098e8f89..29289469 100644 --- a/tests/Packwiz_test.cpp +++ b/tests/Packwiz_test.cpp @@ -48,7 +48,7 @@ class PackwizTest : public QObject { QCOMPARE(metadata.hash_format, "sha512"); QCOMPARE(metadata.hash, "c8fe6e15ddea32668822dddb26e1851e5f03834be4bcb2eff9c0da7fdc086a9b6cead78e31a44d3bc66335cba11144ee0337c6d5346f1ba63623064499b3188d"); - QCOMPARE(metadata.provider, ModPlatform::Provider::MODRINTH); + QCOMPARE(metadata.provider, ModPlatform::ResourceProvider::MODRINTH); QCOMPARE(metadata.version(), "ug2qKTPR"); QCOMPARE(metadata.mod_id(), "kYq5qkSL"); } @@ -76,7 +76,7 @@ class PackwizTest : public QObject { QCOMPARE(metadata.hash_format, "murmur2"); QCOMPARE(metadata.hash, "1781245820"); - QCOMPARE(metadata.provider, ModPlatform::Provider::FLAME); + QCOMPARE(metadata.provider, ModPlatform::ResourceProvider::FLAME); QCOMPARE(metadata.file_id, 3509043); QCOMPARE(metadata.project_id, 327154); } diff --git a/tests/ResourceModel_test.cpp b/tests/ResourceModel_test.cpp new file mode 100644 index 00000000..716bf853 --- /dev/null +++ b/tests/ResourceModel_test.cpp @@ -0,0 +1,88 @@ +#include <QAbstractItemModelTester> +#include <QTest> +#include <QTimer> + +#include <Json.h> + +#include <ui/pages/modplatform/ResourceModel.h> + +#include "DummyResourceAPI.h" + +using ResourceDownload::ResourceModel; + +#define EXEC_TASK(EXEC) \ + QEventLoop loop; \ + \ + connect(model, &ResourceModel::dataChanged, &loop, &QEventLoop::quit); \ + \ + QTimer expire_timer; \ + expire_timer.callOnTimeout(&loop, &QEventLoop::quit); \ + expire_timer.setSingleShot(true); \ + expire_timer.start(4000); \ + \ + EXEC; \ + if (model->hasActiveSearchJob()) \ + loop.exec(); \ + \ + QVERIFY2(expire_timer.isActive(), "Timer has expired. The search never finished."); \ + expire_timer.stop(); \ + \ + disconnect(model, nullptr, &loop, nullptr) + +class ResourceModelTest; + +class DummyResourceModel : public ResourceModel { + Q_OBJECT + + friend class ResourceModelTest; + + public: + DummyResourceModel() : ResourceModel(new DummyResourceAPI) {} + + [[nodiscard]] auto metaEntryBase() const -> QString override { return ""; }; + + ResourceAPI::SearchArgs createSearchArguments() override { return {}; }; + ResourceAPI::VersionSearchArgs createVersionsArguments(QModelIndex&) override { return {}; }; + ResourceAPI::ProjectInfoArgs createInfoArguments(QModelIndex&) override { return {}; }; + + QJsonArray documentToArray(QJsonDocument& doc) const override { return doc.object().value("hits").toArray(); } + + void loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) override + { + pack.authors.append({ Json::requireString(obj, "author") }); + pack.description = Json::requireString(obj, "description"); + pack.addonId = Json::requireString(obj, "project_id"); + } +}; + +class ResourceModelTest : public QObject { + Q_OBJECT + private slots: + void test_abstract_item_model() { [[maybe_unused]] auto tester = new QAbstractItemModelTester(new DummyResourceModel); } + + void test_search() + { + auto model = new DummyResourceModel; + + QVERIFY(model->m_packs.isEmpty()); + + EXEC_TASK(model->search()); + + QVERIFY(model->m_packs.size() == 1); + QVERIFY(model->m_search_state == DummyResourceModel::SearchState::Finished); + + auto processed_pack = model->m_packs.at(0); + auto search_json = DummyResourceAPI::searchRequestResult(); + auto processed_response = model->documentToArray(search_json).first().toObject(); + + QVERIFY(processed_pack.addonId.toString() == Json::requireString(processed_response, "project_id")); + QVERIFY(processed_pack.description == Json::requireString(processed_response, "description")); + QVERIFY(processed_pack.authors.first().name == Json::requireString(processed_response, "author")); + } +}; + +QTEST_GUILESS_MAIN(ResourceModelTest) + +#include "ResourceModel_test.moc" + +#include "moc_DummyResourceAPI.cpp" diff --git a/tests/Task_test.cpp b/tests/Task_test.cpp index 80bba02f..95eb4a30 100644 --- a/tests/Task_test.cpp +++ b/tests/Task_test.cpp @@ -1,16 +1,23 @@ #include <QTest> +#include <QTimer> +#include <QThread> #include <tasks/ConcurrentTask.h> #include <tasks/MultipleOptionsTask.h> #include <tasks/SequentialTask.h> #include <tasks/Task.h> +#include <array> + /* Does nothing. Only used for testing. */ class BasicTask : public Task { Q_OBJECT friend class TaskTest; + public: + BasicTask(bool show_debug_log = true) : Task(nullptr, show_debug_log) {} + private: void executeTask() override { @@ -30,6 +37,57 @@ class BasicTask_MultiStep : public Task { void executeTask() override {}; }; +class BigConcurrentTask : public ConcurrentTask { + Q_OBJECT + + void startNext() override + { + // This is here only to help fill the stack a bit more quickly (if there's an issue, of course :^)) + // Each tasks thus adds 1024 * 4 bytes to the stack, at the very least. + [[maybe_unused]] volatile std::array<uint32_t, 1024> some_data_on_the_stack {}; + + ConcurrentTask::startNext(); + } +}; + +class BigConcurrentTaskThread : public QThread { + Q_OBJECT + + BigConcurrentTask big_task; + + void run() override + { + QTimer deadline; + deadline.setInterval(10000); + connect(&deadline, &QTimer::timeout, this, [this]{ passed_the_deadline = true; }); + deadline.start(); + + // NOTE: Arbitrary value that manages to trigger a problem when there is one. + // Considering each tasks, in a problematic state, adds 1024 * 4 bytes to the stack, + // this number is enough to fill up 16 MiB of stack, more than enough to cause a problem. + static const unsigned s_num_tasks = 1 << 12; + auto sub_tasks = new BasicTask::Ptr[s_num_tasks]; + + for (unsigned i = 0; i < s_num_tasks; i++) { + sub_tasks[i] = makeShared<BasicTask>(false); + big_task.addTask(sub_tasks[i]); + } + + big_task.run(); + + while (!big_task.isFinished() && !passed_the_deadline) + QCoreApplication::processEvents(); + + emit finished(); + } + + public: + bool passed_the_deadline = false; + + signals: + void finished(); +}; + class TaskTest : public QObject { Q_OBJECT @@ -78,21 +136,21 @@ class TaskTest : public QObject { } void test_basicConcurrentRun(){ - BasicTask t1; - BasicTask t2; - BasicTask t3; + auto t1 = makeShared<BasicTask>(); + auto t2 = makeShared<BasicTask>(); + auto t3 = makeShared<BasicTask>(); ConcurrentTask t; - t.addTask(&t1); - t.addTask(&t2); - t.addTask(&t3); + t.addTask(t1); + t.addTask(t2); + t.addTask(t3); QObject::connect(&t, &Task::finished, [&]{ QVERIFY2(t.wasSuccessful(), "Task finished but was not successful when it should have been."); - QVERIFY(t1.wasSuccessful()); - QVERIFY(t2.wasSuccessful()); - QVERIFY(t3.wasSuccessful()); + QVERIFY(t1->wasSuccessful()); + QVERIFY(t2->wasSuccessful()); + QVERIFY(t3->wasSuccessful()); }); t.start(); @@ -103,31 +161,39 @@ class TaskTest : public QObject { // Tests if starting new tasks after the 6 initial ones is working void test_moreConcurrentRun(){ - BasicTask t1, t2, t3, t4, t5, t6, t7, t8, t9; + auto t1 = makeShared<BasicTask>(); + auto t2 = makeShared<BasicTask>(); + auto t3 = makeShared<BasicTask>(); + auto t4 = makeShared<BasicTask>(); + auto t5 = makeShared<BasicTask>(); + auto t6 = makeShared<BasicTask>(); + auto t7 = makeShared<BasicTask>(); + auto t8 = makeShared<BasicTask>(); + auto t9 = makeShared<BasicTask>(); ConcurrentTask t; - t.addTask(&t1); - t.addTask(&t2); - t.addTask(&t3); - t.addTask(&t4); - t.addTask(&t5); - t.addTask(&t6); - t.addTask(&t7); - t.addTask(&t8); - t.addTask(&t9); + t.addTask(t1); + t.addTask(t2); + t.addTask(t3); + t.addTask(t4); + t.addTask(t5); + t.addTask(t6); + t.addTask(t7); + t.addTask(t8); + t.addTask(t9); QObject::connect(&t, &Task::finished, [&]{ QVERIFY2(t.wasSuccessful(), "Task finished but was not successful when it should have been."); - QVERIFY(t1.wasSuccessful()); - QVERIFY(t2.wasSuccessful()); - QVERIFY(t3.wasSuccessful()); - QVERIFY(t4.wasSuccessful()); - QVERIFY(t5.wasSuccessful()); - QVERIFY(t6.wasSuccessful()); - QVERIFY(t7.wasSuccessful()); - QVERIFY(t8.wasSuccessful()); - QVERIFY(t9.wasSuccessful()); + QVERIFY(t1->wasSuccessful()); + QVERIFY(t2->wasSuccessful()); + QVERIFY(t3->wasSuccessful()); + QVERIFY(t4->wasSuccessful()); + QVERIFY(t5->wasSuccessful()); + QVERIFY(t6->wasSuccessful()); + QVERIFY(t7->wasSuccessful()); + QVERIFY(t8->wasSuccessful()); + QVERIFY(t9->wasSuccessful()); }); t.start(); @@ -137,21 +203,21 @@ class TaskTest : public QObject { } void test_basicSequentialRun(){ - BasicTask t1; - BasicTask t2; - BasicTask t3; + auto t1 = makeShared<BasicTask>(); + auto t2 = makeShared<BasicTask>(); + auto t3 = makeShared<BasicTask>(); SequentialTask t; - t.addTask(&t1); - t.addTask(&t2); - t.addTask(&t3); + t.addTask(t1); + t.addTask(t2); + t.addTask(t3); QObject::connect(&t, &Task::finished, [&]{ QVERIFY2(t.wasSuccessful(), "Task finished but was not successful when it should have been."); - QVERIFY(t1.wasSuccessful()); - QVERIFY(t2.wasSuccessful()); - QVERIFY(t3.wasSuccessful()); + QVERIFY(t1->wasSuccessful()); + QVERIFY(t2->wasSuccessful()); + QVERIFY(t3->wasSuccessful()); }); t.start(); @@ -161,21 +227,21 @@ class TaskTest : public QObject { } void test_basicMultipleOptionsRun(){ - BasicTask t1; - BasicTask t2; - BasicTask t3; + auto t1 = makeShared<BasicTask>(); + auto t2 = makeShared<BasicTask>(); + auto t3 = makeShared<BasicTask>(); MultipleOptionsTask t; - t.addTask(&t1); - t.addTask(&t2); - t.addTask(&t3); + t.addTask(t1); + t.addTask(t2); + t.addTask(t3); QObject::connect(&t, &Task::finished, [&]{ QVERIFY2(t.wasSuccessful(), "Task finished but was not successful when it should have been."); - QVERIFY(t1.wasSuccessful()); - QVERIFY(!t2.wasSuccessful()); - QVERIFY(!t3.wasSuccessful()); + QVERIFY(t1->wasSuccessful()); + QVERIFY(!t2->wasSuccessful()); + QVERIFY(!t3->wasSuccessful()); }); t.start(); @@ -183,6 +249,22 @@ class TaskTest : public QObject { return t.isFinished(); }, 1000), "Task didn't finish as it should."); } + + void test_stackOverflowInConcurrentTask() + { + QEventLoop loop; + + auto thread = new BigConcurrentTaskThread; + + connect(thread, &BigConcurrentTaskThread::finished, &loop, &QEventLoop::quit); + + thread->start(); + + loop.exec(); + + QVERIFY(!thread->passed_the_deadline); + thread->deleteLater(); + } }; QTEST_GUILESS_MAIN(TaskTest) diff --git a/tests/Version_test.cpp b/tests/Version_test.cpp index 734528b7..afb4c610 100644 --- a/tests/Version_test.cpp +++ b/tests/Version_test.cpp @@ -15,56 +15,130 @@ #include <QTest> -#include <TestUtil.h> #include <Version.h> -class ModUtilsTest : public QObject -{ +class VersionTest : public QObject { Q_OBJECT - void setupVersions() + + void addDataColumns() { QTest::addColumn<QString>("first"); QTest::addColumn<QString>("second"); QTest::addColumn<bool>("lessThan"); QTest::addColumn<bool>("equal"); + } + + void setupVersions() + { + addDataColumns(); QTest::newRow("equal, explicit") << "1.2.0" << "1.2.0" << false << true; - QTest::newRow("equal, implicit 1") << "1.2" << "1.2.0" << false << true; - QTest::newRow("equal, implicit 2") << "1.2.0" << "1.2" << false << true; QTest::newRow("equal, two-digit") << "1.42" << "1.42" << false << true; QTest::newRow("lessThan, explicit 1") << "1.2.0" << "1.2.1" << true << false; QTest::newRow("lessThan, explicit 2") << "1.2.0" << "1.3.0" << true << false; QTest::newRow("lessThan, explicit 3") << "1.2.0" << "2.2.0" << true << false; - QTest::newRow("lessThan, implicit 1") << "1.2" << "1.2.1" << true << false; - QTest::newRow("lessThan, implicit 2") << "1.2" << "1.3.0" << true << false; - QTest::newRow("lessThan, implicit 3") << "1.2" << "2.2.0" << true << false; + QTest::newRow("lessThan, implicit 1") << "1.2" << "1.2.0" << true << false; + QTest::newRow("lessThan, implicit 2") << "1.2" << "1.2.1" << true << false; + QTest::newRow("lessThan, implicit 3") << "1.2" << "1.3.0" << true << false; + QTest::newRow("lessThan, implicit 4") << "1.2" << "2.2.0" << true << false; QTest::newRow("lessThan, two-digit") << "1.41" << "1.42" << true << false; QTest::newRow("greaterThan, explicit 1") << "1.2.1" << "1.2.0" << false << false; QTest::newRow("greaterThan, explicit 2") << "1.3.0" << "1.2.0" << false << false; QTest::newRow("greaterThan, explicit 3") << "2.2.0" << "1.2.0" << false << false; - QTest::newRow("greaterThan, implicit 1") << "1.2.1" << "1.2" << false << false; - QTest::newRow("greaterThan, implicit 2") << "1.3.0" << "1.2" << false << false; - QTest::newRow("greaterThan, implicit 3") << "2.2.0" << "1.2" << false << false; + QTest::newRow("greaterThan, implicit 1") << "1.2.0" << "1.2" << false << false; + QTest::newRow("greaterThan, implicit 2") << "1.2.1" << "1.2" << false << false; + QTest::newRow("greaterThan, implicit 3") << "1.3.0" << "1.2" << false << false; + QTest::newRow("greaterThan, implicit 4") << "2.2.0" << "1.2" << false << false; QTest::newRow("greaterThan, two-digit") << "1.42" << "1.41" << false << false; } -private slots: - void initTestCase() + private slots: + void test_versionCompare_data() { - + setupVersions(); } - void cleanupTestCase() + + void test_versionCompare() { + QFETCH(QString, first); + QFETCH(QString, second); + QFETCH(bool, lessThan); + QFETCH(bool, equal); + const auto v1 = Version(first); + const auto v2 = Version(second); + + qDebug() << v1 << "vs" << v2; + + QCOMPARE(v1 < v2, lessThan); + QCOMPARE(v1 > v2, !lessThan && !equal); + QCOMPARE(v1 == v2, equal); } - void test_versionCompare_data() + void test_flexVerTestVector_data() { - setupVersions(); + addDataColumns(); + + QDir test_vector_dir(QFINDTESTDATA("testdata/Version")); + + QFile vector_file{test_vector_dir.absoluteFilePath("test_vectors.txt")}; + + vector_file.open(QFile::OpenModeFlag::ReadOnly); + + int test_number = 0; + const QString test_name_template { "FlexVer test #%1 (%2)" }; + for (auto line = vector_file.readLine(); !vector_file.atEnd(); line = vector_file.readLine()) { + line = line.simplified(); + if (line.startsWith('#') || line.isEmpty()) + continue; + + test_number += 1; + + auto split_line = line.split('<'); + if (split_line.size() == 2) { + QString first{split_line.first().simplified()}; + QString second{split_line.last().simplified()}; + + auto new_test_name = test_name_template.arg(QString::number(test_number), "lessThan").toLatin1().data(); + QTest::newRow(new_test_name) << first << second << true << false; + + continue; + } + + split_line = line.split('='); + if (split_line.size() == 2) { + QString first{split_line.first().simplified()}; + QString second{split_line.last().simplified()}; + + auto new_test_name = test_name_template.arg(QString::number(test_number), "equals").toLatin1().data(); + QTest::newRow(new_test_name) << first << second << false << true; + + continue; + } + + split_line = line.split('>'); + if (split_line.size() == 2) { + QString first{split_line.first().simplified()}; + QString second{split_line.last().simplified()}; + + auto new_test_name = test_name_template.arg(QString::number(test_number), "greaterThan").toLatin1().data(); + QTest::newRow(new_test_name) << first << second << false << false; + + continue; + } + + qCritical() << "Unexpected separator in the test vector: "; + qCritical() << line; + + QVERIFY(0 != 0); + } + + vector_file.close(); } - void test_versionCompare() + + void test_flexVerTestVector() { QFETCH(QString, first); QFETCH(QString, second); @@ -74,12 +148,14 @@ private slots: const auto v1 = Version(first); const auto v2 = Version(second); + qDebug() << v1 << "vs" << v2; + QCOMPARE(v1 < v2, lessThan); QCOMPARE(v1 > v2, !lessThan && !equal); QCOMPARE(v1 == v2, equal); } }; -QTEST_GUILESS_MAIN(ModUtilsTest) +QTEST_GUILESS_MAIN(VersionTest) #include "Version_test.moc" diff --git a/tests/testdata/Version/test_vectors.txt b/tests/testdata/Version/test_vectors.txt new file mode 100644 index 00000000..e6c6507c --- /dev/null +++ b/tests/testdata/Version/test_vectors.txt @@ -0,0 +1,63 @@ +# Test vector from: +# https://github.com/unascribed/FlexVer/blob/704e12759b6e59220ff888f8bf2ec15b8f8fd969/test/test_vectors.txt +# +# This test file is formatted as "<lefthand> <operator> <righthand>", seperated by the space character +# Implementations should ignore lines starting with "#" and lines that have a length of 0 + +# Basic numeric ordering (lexical string sort fails these) +10 > 2 +100 > 10 + +# Trivial common numerics +1.0 < 1.1 +1.0 < 1.0.1 +1.1 > 1.0.1 + +# SemVer compatibility +1.5 > 1.5-pre1 +1.5 = 1.5+foobar + +# SemVer incompatibility +1.5 < 1.5-2 +1.5-pre10 > 1.5-pre2 + +# Empty strings + = +1 > + < 1 + +# Check boundary between textual and prerelease +a-a < a + +# Check boundary between textual and appendix +a+a = a + +# Dash is included in prerelease comparison (if stripped it will be a smaller component) +# Note that a-a < a=a regardless since the prerelease splits the component creating a smaller first component; 0 is added to force splitting regardless +a0-a < a0=a + +# Pre-releases must contain only non-digit +1.16.5-10 > 1.16.5 + +# Pre-releases can have multiple dashes (should not be split) +# Reasoning for test data: "p-a!" > "p-a-" (correct); "p-a!" < "p-a t-" (what happens if every dash creates a new component) +-a- > -a! + +# Misc +b1.7.3 > a1.2.6 +b1.2.6 > a1.7.3 +a1.1.2 < a1.1.2_01 +1.16.5-0.00.5 > 1.14.2-1.3.7 +1.0.0 < 1.0.0_01 +1.0.1 > 1.0.0_01 +1.0.0_01 < 1.0.1 +0.17.1-beta.1 < 0.17.1 +0.17.1-beta.1 < 0.17.1-beta.2 +1.4.5_01 = 1.4.5_01+fabric-1.17 +1.4.5_01 = 1.4.5_01+fabric-1.17+ohgod +14w16a < 18w40b +18w40a < 18w40b +1.4.5_01+fabric-1.17 < 18w40b +13w02a < c0.3.0_01 +0.6.0-1.18.x < 0.9.beta-1.18.x + |