diff --git a/CMakeLists.txt b/CMakeLists.txt index 122b899..2ab42ef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,8 @@ project(QSimpleUpdater LANGUAGES CXX ) +option(QSIMPLE_UPDATER_BUILD_TESTS "Build the unit tests" ON) + set(CMAKE_AUTOUIC ON) set(CMAKE_AUTORCC ON) set(CMAKE_AUTOMOC ON) @@ -10,6 +12,10 @@ set(CMAKE_AUTOMOC ON) find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Network) find_package(Qt${QT_VERSION_MAJOR} CONFIG REQUIRED COMPONENTS Widgets Network) +if(QSIMPLE_UPDATER_BUILD_TESTS) + find_package(Qt${QT_VERSION_MAJOR} CONFIG REQUIRED COMPONENTS Test) +endif() + add_library(QSimpleUpdater STATIC etc/resources/qsimpleupdater.qrc include/QSimpleUpdater.h @@ -27,3 +33,16 @@ target_include_directories(QSimpleUpdater PUBLIC include) target_link_libraries(QSimpleUpdater PUBLIC Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Widgets PRIVATE Qt${QT_VERSION_MAJOR}::Network) add_subdirectory(tutorial) + +if(QSIMPLE_UPDATER_BUILD_TESTS) + enable_testing() + add_executable(UnitTests + tests/main.cpp + tests/Test_Versioning.h + tests/Test_Updater.h + tests/Test_QSimpleUpdater.h + tests/Test_Downloader.h + ) + add_test(NAME ApiTest COMMAND ApiTest) + target_link_libraries(UnitTests PRIVATE Qt${QT_VERSION_MAJOR}::Test QSimpleUpdater) +endif() diff --git a/include/QSimpleUpdater.h b/include/QSimpleUpdater.h index f1edc6b..669082b 100644 --- a/include/QSimpleUpdater.h +++ b/include/QSimpleUpdater.h @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) 2014-2021 Alex Spataru * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -67,6 +67,7 @@ class QSU_DECL QSimpleUpdater : public QObject public: static QSimpleUpdater *getInstance(); + static bool compareVersions(const QString &remote, const QString &local); bool usesCustomAppcast(const QString &url) const; bool getNotifyOnUpdate(const QString &url) const; diff --git a/src/QSimpleUpdater.cpp b/src/QSimpleUpdater.cpp index 433aff1..fa675ef 100644 --- a/src/QSimpleUpdater.cpp +++ b/src/QSimpleUpdater.cpp @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) 2014-2021 Alex Spataru * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -20,8 +20,9 @@ * THE SOFTWARE. */ -#include "Updater.h" #include "QSimpleUpdater.h" +#include "Updater.h" +#include static QList URLS; static QList UPDATERS; @@ -45,6 +46,47 @@ QSimpleUpdater *QSimpleUpdater::getInstance() return &updater; } +bool QSimpleUpdater::compareVersions(const QString &remote, const QString &local) +{ + static QRegularExpression re("v?(\\d+)(?:\\.(\\d+))?(?:\\.(\\d+))?(?:-(\\w+)(?:(\\d+))?)?"); + QRegularExpressionMatch remoteMatch = re.match(remote); + QRegularExpressionMatch localMatch = re.match(local); + + if (!remoteMatch.hasMatch() || !localMatch.hasMatch()) + { + // Invalid version format + return false; + } + + for (int i = 1; i <= 3; ++i) + { + int remoteNum = remoteMatch.captured(i).toInt(); + int localNum = localMatch.captured(i).toInt(); + + if (remoteNum > localNum) + return true; + else if (localNum > remoteNum) + return false; + } + + QString remoteSuffix = remoteMatch.captured(4); + QString localSuffix = localMatch.captured(4); + + if (remoteSuffix.isEmpty() && !localSuffix.isEmpty()) + // Remote is stable, local is pre-release + return true; + if (!remoteSuffix.isEmpty() && localSuffix.isEmpty()) + // Remote is pre-release, local is stable + return false; + if (remoteSuffix != localSuffix) + // Compare suffixes lexicographically + return remoteSuffix > localSuffix; + + int remoteSuffixNum = remoteMatch.captured(5).toInt(); + int localSuffixNum = localMatch.captured(5).toInt(); + return remoteSuffixNum > localSuffixNum; +} + /** * Returns \c true if the \c Updater instance registered with the given \a url * uses a custom appcast format and/or allows the application to read and diff --git a/src/Updater.cpp b/src/Updater.cpp index 3251adf..75cfe76 100644 --- a/src/Updater.cpp +++ b/src/Updater.cpp @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) 2014-2021 Alex Spataru * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -511,24 +511,7 @@ void Updater::setUpdateAvailable(const bool available) */ bool Updater::compare(const QString &x, const QString &y) { - QStringList versionsX = x.split("."); - QStringList versionsY = y.split("."); - - int count = qMin(versionsX.count(), versionsY.count()); - - for (int i = 0; i < count; ++i) - { - int a = QString(versionsX.at(i)).toInt(); - int b = QString(versionsY.at(i)).toInt(); - - if (a > b) - return true; - - else if (b > a) - return false; - } - - return versionsY.count() < versionsX.count(); + return QSimpleUpdater::compareVersions(x, y); } #if QSU_INCLUDE_MOC diff --git a/tests/Test_Downloader.h b/tests/Test_Downloader.h index f0e82d6..e5c58f4 100644 --- a/tests/Test_Downloader.h +++ b/tests/Test_Downloader.h @@ -24,7 +24,6 @@ #define TEST_DOWNLOADER_H #include -#include class Test_Downloader : public QObject { diff --git a/tests/Test_Updater.h b/tests/Test_Updater.h index 63fde77..e1c61be 100644 --- a/tests/Test_Updater.h +++ b/tests/Test_Updater.h @@ -20,15 +20,12 @@ * THE SOFTWARE. */ -#ifndef TEST_UPDATER_H -#define TEST_UPDATER_H +#pragma once #include -#include +#include class Test_Updater : public QObject { Q_OBJECT }; - -#endif diff --git a/tests/Test_Versioning.h b/tests/Test_Versioning.h new file mode 100644 index 0000000..0da9a47 --- /dev/null +++ b/tests/Test_Versioning.h @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2015-2016 Alex Spataru + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include +#include + +class Test_Versioning : public QObject +{ + Q_OBJECT +private slots: + void TestPrefixSameVersion() + { + bool needsUpgrade; + needsUpgrade = QSimpleUpdater::compareVersions("0.0.1", "0.0.1"); + QVERIFY(!needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("0.0.1", "v0.0.1"); + QVERIFY(!needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("v0.0.1", "0.0.1"); + QVERIFY(!needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("v0.0.1", "v0.0.1"); + QVERIFY(!needsUpgrade); + } + + void MajorMinorRelease() + { + bool needsUpgrade; + + // Note how "v" is prefixed in different version, this should not + // matter to us + needsUpgrade = QSimpleUpdater::compareVersions("0.0.2", "0.0.1"); + QVERIFY(needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("0.0.2", "v0.0.1"); + QVERIFY(needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("v0.0.2", "0.0.1"); + QVERIFY(needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("v0.0.2", "v0.0.1"); + QVERIFY(needsUpgrade); + + // Revisions are treated as numbers, and not text + needsUpgrade = QSimpleUpdater::compareVersions("0.0.8", "0.0.2"); + QVERIFY(needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("0.0.8", "v0.0.2"); + QVERIFY(needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("v0.0.9", "0.0.8"); + QVERIFY(needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("v0.0.8", "v0.0.2"); + QVERIFY(needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("v0.0.8", "v0.0.2"); + QVERIFY(needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("v0.0.12", "v0.0.2"); + QVERIFY(needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("v0.0.112", "v0.0.2"); + QVERIFY(needsUpgrade); + + // Minor versions test + needsUpgrade = QSimpleUpdater::compareVersions("0.1.2", "0.0.8"); + QVERIFY(needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("0.1.2", "v0.0.8"); + QVERIFY(needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("v0.1.2", "0.0.8"); + QVERIFY(needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("v0.1.2", "v0.0.8"); + QVERIFY(needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("0.0.8", "0.1.2"); + QVERIFY(!needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("v0.0.8", "0.1.2"); + QVERIFY(!needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("0.0.8", "v0.1.2"); + QVERIFY(!needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("v0.0.8", "v0.1.2"); + QVERIFY(!needsUpgrade); + + // Major versions test + needsUpgrade = QSimpleUpdater::compareVersions("1.0.2", "0.9.8"); + QVERIFY(needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("1.1.2", "v0.7.8"); + QVERIFY(needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("v2.0.2", "1.9.8"); + QVERIFY(needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("v100.2$.2", "v100.1.8"); + QVERIFY(needsUpgrade); + } + + void AlphaVersions() + { + bool needsUpgrade; + + needsUpgrade = QSimpleUpdater::compareVersions("v1.0.0-alpha2", "v1.0.0-alpha1"); + QVERIFY(needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("v1.0.0-alpha10", "v1.0.0-alpha1"); + QVERIFY(needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("v1.0.0-alpha1", "v1.0.0-alpha2"); + QVERIFY(!needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("v1.0.0-alpha1", "v1.0.0-alpha19"); + QVERIFY(!needsUpgrade); + + needsUpgrade = QSimpleUpdater::compareVersions("v1.0.0", "v1.0.0-alpha1"); + QVERIFY(needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("v1.0.0", "v1.0.0-alpha10"); + QVERIFY(needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("v1.0.0", "v1.0.0-alpha18"); + QVERIFY(needsUpgrade); + + needsUpgrade = QSimpleUpdater::compareVersions("v1.0.0-alpha1", "v1.0.0"); + QVERIFY(!needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("v1.0.0-alpha1000", "v1.0.0"); + QVERIFY(!needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("v1.0.0-alpha88", "v1.0.0"); + QVERIFY(!needsUpgrade); + } + + void BetaVersions() + { + bool needsUpgrade; + + needsUpgrade = QSimpleUpdater::compareVersions("v1.0.0-beta", "v1.0.0-alpha1"); + QVERIFY(needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("v1.0.0-beta2", "v1.0.0-alpha1"); + QVERIFY(needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("v1.0.0-beta0", "v1.0.0-alpha1"); + QVERIFY(needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("v1.0.0-alpha1", "v1.0.0-beta2"); + QVERIFY(!needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("v1.0.0-alpha9999", "v1.0.0-beta19"); + QVERIFY(!needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("v1.0.0-beta2", "v1.0.0-alpha1"); + QVERIFY(needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("v1.0.0-beta19", "v1.0.0-alpha9999"); + QVERIFY(needsUpgrade); + + needsUpgrade = QSimpleUpdater::compareVersions("v1.0.0", "v1.0.0-beta0"); + QVERIFY(needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("v1.0.0", "v1.0.0-beta1"); + QVERIFY(needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("v1.0.0", "v0.1.0-beta9999"); + QVERIFY(needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("v1.0.0", "v2.0.0-beta9999"); + QVERIFY(!needsUpgrade); + } + + void RemoteCandidateVersions() + { + bool needsUpgrade; + + needsUpgrade = QSimpleUpdater::compareVersions("v1.0.0", "v1.0.0-rc"); + QVERIFY(needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("v1.0.0", "v1.0.0-rc1"); + QVERIFY(needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("v1.0.0-rc1", "v1.0.0-rc1"); + QVERIFY(!needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("v1.0.0-rc2", "v1.0.0-rc1"); + QVERIFY(needsUpgrade); + + needsUpgrade = QSimpleUpdater::compareVersions("v1.0.0-rc1", "v1.0.0-alpha1"); + QVERIFY(needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("v1.0.0-rc1", "v1.0.0-alpha200"); + QVERIFY(needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("v1.0.0-rc1", "v1.0.0-beta1"); + QVERIFY(needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("v1.0.0-rc1", "v1.0.0-beta2000"); + QVERIFY(needsUpgrade); + + needsUpgrade = QSimpleUpdater::compareVersions("v1.0.0-alpha1", "v1.0.0-rc1"); + QVERIFY(!needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("v1.0.0-alpha200", "v1.0.0-rc1"); + QVERIFY(!needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("v1.0.0-beta1", "v1.0.0-rc1"); + QVERIFY(!needsUpgrade); + needsUpgrade = QSimpleUpdater::compareVersions("v1.0.0-beta2000", "v1.0.0-rc1"); + QVERIFY(!needsUpgrade); + } +}; diff --git a/tests/main.cpp b/tests/main.cpp index 7c3abcb..30b19a6 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -20,22 +20,44 @@ * THE SOFTWARE. */ +#include +#include +#include "Test_Versioning.h" #include "Test_Updater.h" #include "Test_Downloader.h" #include "Test_QSimpleUpdater.h" +#define runTest(T) \ + { \ + T tt; \ + status |= QTest::qExec(&tt, argc, argv); \ + } + int main(int argc, char *argv[]) { - QApplication app(argc, argv); - - app.setApplicationName("QSimpleUpdater Tests"); - app.setOrganizationName("The QSimpleUpdater Library"); + int status = 0; - QTest::qExec(new Test_Updater, argc, argv); - QTest::qExec(new Test_Downloader, argc, argv); - QTest::qExec(new Test_QSimpleUpdater, argc, argv); + // runTest(Test_Versioning); + // runTest(Test_Updater); + // runTest(Test_Downloader); + // runTest(Test_QSimpleUpdater); - QTimer::singleShot(1000, Qt::PreciseTimer, qApp, SLOT(quit())); + { + Test_Versioning tt; + status |= QTest::qExec(&tt, argc, argv); + } + { + Test_Updater tt; + status |= QTest::qExec(&tt, argc, argv); + } + { + Test_Downloader tt; + status |= QTest::qExec(&tt, argc, argv); + } + { + Test_QSimpleUpdater tt; + status |= QTest::qExec(&tt, argc, argv); + } - return app.exec(); + return status; }