diff --git a/launcher/Version.cpp b/launcher/Version.cpp index b9090e29..e4311f31 100644 --- a/launcher/Version.cpp +++ b/launcher/Version.cpp @@ -1,85 +1,128 @@ #include "Version.h" -#include -#include +#include #include #include +#include -Version::Version(const QString &str) : m_string(str) +Version::Version(QString str) : m_string(std::move(str)) { parse(); } -bool Version::operator<(const Version &other) const -{ - const int size = qMax(m_sections.size(), other.m_sections.size()); - for (int i = 0; i < size; ++i) - { - const Section sec1 = (i >= m_sections.size()) ? Section("0") : m_sections.at(i); - const Section sec2 = - (i >= other.m_sections.size()) ? Section("0") : other.m_sections.at(i); - if (sec1 != sec2) - { - return sec1 < sec2; - } +#define VERSION_OPERATOR(return_on_different) \ + bool exclude_our_sections = false; \ + bool exclude_their_sections = false; \ + \ + const auto size = qMax(m_sections.size(), other.m_sections.size()); \ + for (int i = 0; i < size; ++i) { \ + Section sec1 = (i >= m_sections.size()) ? Section() : m_sections.at(i); \ + Section sec2 = (i >= other.m_sections.size()) ? Section() : other.m_sections.at(i); \ + \ + { /* Don't include appendixes in the comparison */ \ + if (sec1.isAppendix()) \ + exclude_our_sections = true; \ + if (sec2.isAppendix()) \ + exclude_their_sections = true; \ + \ + if (exclude_our_sections) { \ + sec1 = Section(); \ + if (sec2.m_isNull) \ + break; \ + } \ + \ + if (exclude_their_sections) { \ + sec2 = Section(); \ + if (sec1.m_isNull) \ + break; \ + } \ + } \ + \ + if (sec1 != sec2) \ + return return_on_different; \ } +bool Version::operator<(const Version& other) const +{ + VERSION_OPERATOR(sec1 < sec2) + return false; } -bool Version::operator<=(const Version &other) const +bool Version::operator==(const Version& other) const { - return *this < other || *this == other; -} -bool Version::operator>(const Version &other) const -{ - const int size = qMax(m_sections.size(), other.m_sections.size()); - for (int i = 0; i < size; ++i) - { - const Section sec1 = (i >= m_sections.size()) ? Section("0") : m_sections.at(i); - const Section sec2 = - (i >= other.m_sections.size()) ? Section("0") : other.m_sections.at(i); - if (sec1 != sec2) - { - return sec1 > sec2; - } - } - - return false; -} -bool Version::operator>=(const Version &other) const -{ - return *this > other || *this == other; -} -bool Version::operator==(const Version &other) const -{ - const int size = qMax(m_sections.size(), other.m_sections.size()); - for (int i = 0; i < size; ++i) - { - const Section sec1 = (i >= m_sections.size()) ? Section("0") : m_sections.at(i); - const Section sec2 = - (i >= other.m_sections.size()) ? Section("0") : other.m_sections.at(i); - if (sec1 != sec2) - { - return false; - } - } + VERSION_OPERATOR(false) return true; } -bool Version::operator!=(const Version &other) const +bool Version::operator!=(const Version& other) const { return !operator==(other); } +bool Version::operator<=(const Version& other) const +{ + return *this < other || *this == other; +} +bool Version::operator>(const Version& other) const +{ + return !(*this <= other); +} +bool Version::operator>=(const Version& other) const +{ + return !(*this < other); +} void Version::parse() { m_sections.clear(); + QString currentSection; - // FIXME: this is bad. versions can contain a lot more separators... - QStringList parts = m_string.split('.'); + if (m_string.isEmpty()) + return; - for (const auto& part : parts) - { - m_sections.append(Section(part)); + auto classChange = [&](QChar lastChar, QChar currentChar) { + if (lastChar.isNull()) + return false; + if (lastChar.isDigit() != currentChar.isDigit()) + return true; + + const QList s_separators{ '.', '-', '+' }; + if (s_separators.contains(currentChar) && currentSection.at(0) != currentChar) + return true; + + return false; + }; + + currentSection += m_string.at(0); + for (int i = 1; i < m_string.size(); ++i) { + const auto& current_char = m_string.at(i); + if (classChange(m_string.at(i - 1), current_char)) { + if (!currentSection.isEmpty()) + m_sections.append(Section(currentSection)); + currentSection = ""; + } + + currentSection += current_char; } + + if (!currentSection.isEmpty()) + m_sections.append(Section(currentSection)); +} + +/// qDebug print support for the Version class +QDebug operator<<(QDebug debug, const Version& v) +{ + QDebugStateSaver saver(debug); + + debug.nospace() << "Version{ string: " << v.toString() << ", sections: [ "; + + bool first = true; + for (auto s : v.m_sections) { + if (!first) debug.nospace() << ", "; + debug.nospace() << s.m_fullString; + first = false; + } + + debug.nospace() << " ]" << " }"; + + return debug; } diff --git a/launcher/Version.h b/launcher/Version.h index aceb7a07..659f8e54 100644 --- a/launcher/Version.h +++ b/launcher/Version.h @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only /* * PolyMC - Minecraft Launcher + * Copyright (C) 2023 flowln * Copyright (C) 2022 Sefa Eyeoglu * * This program is free software: you can redistribute it and/or modify @@ -35,17 +36,17 @@ #pragma once +#include +#include #include #include -#include class QUrl; -class Version -{ -public: - Version(const QString &str); - Version() {} +class Version { + public: + Version(QString str); + Version() = default; bool operator<(const Version &other) const; bool operator<=(const Version &other) const; @@ -54,96 +55,116 @@ public: bool operator==(const Version &other) const; bool operator!=(const Version &other) const; - QString toString() const - { - return m_string; - } + QString toString() const { return m_string; } -private: - QString m_string; - struct Section - { - explicit Section(const QString &fullString) + friend QDebug operator<<(QDebug debug, const Version& v); + + private: + struct Section { + explicit Section(QString fullString) : m_fullString(std::move(fullString)) { - m_fullString = fullString; int cutoff = m_fullString.size(); - for(int i = 0; i < m_fullString.size(); i++) - { - if(!m_fullString[i].isDigit()) - { + for (int i = 0; i < m_fullString.size(); i++) { + if (!m_fullString[i].isDigit()) { cutoff = i; break; } } + #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) auto numPart = QStringView{m_fullString}.left(cutoff); #else auto numPart = m_fullString.leftRef(cutoff); #endif - if(numPart.size()) - { - numValid = true; + + if (!numPart.isEmpty()) { + m_isNull = false; m_numPart = numPart.toInt(); } + #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) auto stringPart = QStringView{m_fullString}.mid(cutoff); #else auto stringPart = m_fullString.midRef(cutoff); #endif - if(stringPart.size()) - { + + if (!stringPart.isEmpty()) { + m_isNull = false; m_stringPart = stringPart.toString(); } } - explicit Section() {} - bool numValid = false; + + explicit Section() = default; + + bool m_isNull = true; + int m_numPart = 0; QString m_stringPart; + QString m_fullString; - inline bool operator!=(const Section &other) const + [[nodiscard]] inline bool isAppendix() const { return m_stringPart.startsWith('+'); } + [[nodiscard]] inline bool isPreRelease() const { return m_stringPart.startsWith('-') && m_stringPart.length() > 1; } + + inline bool operator==(const Section& other) const { - if(numValid && other.numValid) - { - return m_numPart != other.m_numPart || m_stringPart != other.m_stringPart; - } - else - { - return m_fullString != other.m_fullString; + if (m_isNull && !other.m_isNull) + return false; + if (!m_isNull && other.m_isNull) + return false; + + if (!m_isNull && !other.m_isNull) { + return (m_numPart == other.m_numPart) && (m_stringPart == other.m_stringPart); } + + return true; } - inline bool operator<(const Section &other) const - { - if(numValid && other.numValid) - { - if(m_numPart < other.m_numPart) + + inline bool operator<(const Section& other) const + { + static auto unequal_is_less = [](Section const& non_null) -> bool { + if (non_null.m_stringPart.isEmpty()) + return non_null.m_numPart == 0; + return (non_null.m_stringPart != QLatin1Char('.')) && non_null.isPreRelease(); + }; + + if (!m_isNull && other.m_isNull) + return unequal_is_less(*this); + if (m_isNull && !other.m_isNull) + return !unequal_is_less(other); + + if (!m_isNull && !other.m_isNull) { + if (m_numPart < other.m_numPart) return true; - if(m_numPart == other.m_numPart && m_stringPart < other.m_stringPart) + if (m_numPart == other.m_numPart && m_stringPart < other.m_stringPart) return true; + + if (!m_stringPart.isEmpty() && other.m_stringPart.isEmpty()) + return false; + if (m_stringPart.isEmpty() && !other.m_stringPart.isEmpty()) + return true; + return false; } - else - { - return m_fullString < other.m_fullString; - } + + return m_fullString < other.m_fullString; + } + + inline bool operator!=(const Section& other) const + { + return !(*this == other); } inline bool operator>(const Section &other) const { - if(numValid && other.numValid) - { - if(m_numPart > other.m_numPart) - return true; - if(m_numPart == other.m_numPart && m_stringPart > other.m_stringPart) - return true; - return false; - } - else - { - return m_fullString > other.m_fullString; - } + return !(*this < other || *this == other); } }; + + private: + QString m_string; QList
m_sections; void parse(); }; + + diff --git a/launcher/java/JavaUtils.cpp b/launcher/java/JavaUtils.cpp index 5efbc7a8..e55663aa 100644 --- a/launcher/java/JavaUtils.cpp +++ b/launcher/java/JavaUtils.cpp @@ -412,8 +412,6 @@ QList JavaUtils::FindJavaPaths() #elif defined(Q_OS_LINUX) QList JavaUtils::FindJavaPaths() { - qDebug() << "Linux Java detection incomplete - defaulting to \"java\""; - QList javas; javas.append(this->GetDefaultJava()->path); auto scanJavaDir = [&](const QString & dirPath) @@ -421,20 +419,11 @@ QList JavaUtils::FindJavaPaths() QDir dir(dirPath); if(!dir.exists()) return; - auto entries = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks); + auto entries = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); for(auto & entry: entries) { - QString prefix; - if(entry.isAbsolute()) - { - prefix = entry.absoluteFilePath(); - } - else - { - prefix = entry.filePath(); - } - + prefix = entry.canonicalFilePath(); javas.append(FS::PathCombine(prefix, "jre/bin/java")); javas.append(FS::PathCombine(prefix, "bin/java")); } diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 4eb732f0..c6a4c8d6 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -468,8 +468,8 @@ QMap MinecraftInstance::getVariables() QMap out; out.insert("INST_NAME", name()); out.insert("INST_ID", id()); - out.insert("INST_DIR", QDir(instanceRoot()).absolutePath()); - out.insert("INST_MC_DIR", QDir(gameRoot()).absolutePath()); + out.insert("INST_DIR", QDir::toNativeSeparators(QDir(instanceRoot()).absolutePath())); + out.insert("INST_MC_DIR", QDir::toNativeSeparators(QDir(gameRoot()).absolutePath())); out.insert("INST_JAVA", settings()->get("JavaPath").toString()); out.insert("INST_JAVA_ARGS", javaArguments().join(' ')); return out; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3d0d2dca..36a3b0f8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -56,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/Version_test.cpp b/tests/Version_test.cpp index 734528b7..afb4c610 100644 --- a/tests/Version_test.cpp +++ b/tests/Version_test.cpp @@ -15,55 +15,51 @@ #include -#include #include -class ModUtilsTest : public QObject -{ +class VersionTest : public QObject { Q_OBJECT - void setupVersions() + + void addDataColumns() { QTest::addColumn("first"); QTest::addColumn("second"); QTest::addColumn("lessThan"); QTest::addColumn("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() - { - - } - void cleanupTestCase() - { - - } - + private slots: void test_versionCompare_data() { setupVersions(); } + void test_versionCompare() { QFETCH(QString, first); @@ -74,12 +70,92 @@ 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); + } + + void test_flexVerTestVector_data() + { + 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_flexVerTestVector() + { + 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); } }; -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 Binary files /dev/null and b/tests/testdata/Version/test_vectors.txt differ