diff --git a/src/plugins/updateinfo/CMakeLists.txt b/src/plugins/updateinfo/CMakeLists.txt index fe43e0be09b..fadca2cd6a4 100644 --- a/src/plugins/updateinfo/CMakeLists.txt +++ b/src/plugins/updateinfo/CMakeLists.txt @@ -3,6 +3,10 @@ add_qtc_plugin(UpdateInfo PLUGIN_DEPENDS Core PLUGIN_JSON_IN UPDATEINFO_EXPERIMENTAL_STR=true SOURCES - settingspage.cpp settingspage.h settingspage.ui - updateinfoplugin.cpp updateinfoplugin.h + settingspage.cpp + settingspage.h + settingspage.ui + updateinfoplugin.cpp + updateinfoplugin.h + updateinfotools.h ) diff --git a/src/plugins/updateinfo/updateinfo.qbs b/src/plugins/updateinfo/updateinfo.qbs index 5253039dbc9..2dbae1933a8 100644 --- a/src/plugins/updateinfo/updateinfo.qbs +++ b/src/plugins/updateinfo/updateinfo.qbs @@ -12,10 +12,11 @@ QtcPlugin { pluginJsonReplacements: ({"UPDATEINFO_EXPERIMENTAL_STR": (enable ? "false": "true")}) files: [ - "updateinfoplugin.cpp", - "updateinfoplugin.h", "settingspage.cpp", "settingspage.h", "settingspage.ui", + "updateinfoplugin.cpp", + "updateinfoplugin.h", + "updateinfotools.h", ] } diff --git a/src/plugins/updateinfo/updateinfoplugin.cpp b/src/plugins/updateinfo/updateinfoplugin.cpp index e2271ad396f..743802f3700 100644 --- a/src/plugins/updateinfo/updateinfoplugin.cpp +++ b/src/plugins/updateinfo/updateinfoplugin.cpp @@ -23,9 +23,11 @@ ** ****************************************************************************/ -#include "settingspage.h" #include "updateinfoplugin.h" +#include "settingspage.h" +#include "updateinfotools.h" + #include #include #include @@ -181,107 +183,6 @@ void UpdateInfoPlugin::collectCheckForUpdatesOutput(const QString &contents) d->m_collectedOutput += contents; } -struct Update -{ - QString name; - QString version; -}; - -static QList availableUpdates(const QDomDocument &document) -{ - if (document.isNull() || !document.firstChildElement().hasChildNodes()) - return {}; - QList result; - const QDomNodeList updates = document.firstChildElement().elementsByTagName("update"); - for (int i = 0; i < updates.size(); ++i) { - const QDomNode node = updates.item(i); - if (node.isElement()) { - const QDomElement element = node.toElement(); - if (element.hasAttribute("name")) - result.append({element.attribute("name"), element.attribute("version")}); - } - } - return result; -} - -struct QtPackage -{ - QString displayName; - QVersionNumber version; - bool installed; - bool isPrerelease = false; -}; - -static QList availableQtPackages(const QDomDocument &document) -{ - if (document.isNull() || !document.firstChildElement().hasChildNodes()) - return {}; - QList result; - const QDomNodeList packages = document.firstChildElement().elementsByTagName("package"); - for (int i = 0; i < packages.size(); ++i) { - const QDomNode node = packages.item(i); - if (node.isElement()) { - const QDomElement element = node.toElement(); - if (element.hasAttribute("displayname") && element.hasAttribute("name") - && element.hasAttribute("version")) { - QtPackage package{element.attribute("displayname"), - QVersionNumber::fromString(element.attribute("version")), - element.hasAttribute("installedVersion")}; - // Heuristic: Prerelease if the name is not "Qt x.y.z" - // (prereleases are named "Qt x.y.z-alpha" etc) - package.isPrerelease = package.displayName - != QString("Qt %1").arg(package.version.toString()); - result.append(package); - } - } - } - std::sort(result.begin(), result.end(), [](const QtPackage &p1, const QtPackage &p2) { - return p1.version > p2.version; - }); - return result; -} - -// Expects packages to be sorted, high version first. -static Utils::optional highestInstalledQt(const QList &packages) -{ - const auto highestInstalledIt = std::find_if(packages.cbegin(), - packages.cend(), - [](const QtPackage &p) { return p.installed; }); - if (highestInstalledIt == packages.cend()) // Qt not installed - return {}; - return *highestInstalledIt; -} - -// Expects packages to be sorted, high version first. -static Utils::optional qtToNagAbout(const QList &allPackages, - QVersionNumber *highestSeen) -{ - // Filter out any Qt prereleases - const QList packages = Utils::filtered(allPackages, [](const QtPackage &p) { - return !p.isPrerelease; - }); - if (packages.isEmpty()) - return {}; - const QtPackage highest = packages.constFirst(); - qCDebug(log) << "Highest available (non-prerelease) Qt:" << highest.version; - qCDebug(log) << "Highest previously seen (non-prerelease) Qt:" << *highestSeen; - // if the highestSeen version is null, we don't know if the Qt version is new, and better don't nag - const bool isNew = !highestSeen->isNull() && highest.version > *highestSeen; - if (highestSeen->isNull() || isNew) - *highestSeen = highest.version; - if (!isNew) - return {}; - const Utils::optional highestInstalled = highestInstalledQt(packages); - qCDebug(log) << "Highest installed Qt:" - << qPrintable(highestInstalled ? highestInstalled->version.toString() - : QString("none")); - if (!highestInstalled) // don't nag if no Qt is installed at all - return {}; - if (highestInstalled->version == highest.version) - return {}; - return highest; -} - static void showUpdateInfo(const QList &updates, const std::function &startUpdater) { Utils::InfoBarEntry info(InstallUpdates, @@ -334,20 +235,14 @@ void UpdateInfoPlugin::checkForUpdatesFinished() { setLastCheckDate(QDate::currentDate()); - QDomDocument document; - // since the output can contain two toplevel items from the two separate MaintenanceTool runs, - // surround with a toplevel element - const QString xml = d->m_collectedOutput.isEmpty() - ? QString() - : ("" + d->m_collectedOutput + ""); qCDebug(log) << "--- MaintenanceTool output (combined):"; - qCDebug(log) << qPrintable(xml); - document.setContent(xml); + qCDebug(log) << qPrintable(d->m_collectedOutput); + std::unique_ptr document = documentForResponse(d->m_collectedOutput); stopCheckForUpdates(); - const QList updates = availableUpdates(document); - const QList qtPackages = availableQtPackages(document); + const QList updates = availableUpdates(*document); + const QList qtPackages = availableQtPackages(*document); if (log().isDebugEnabled()) { qCDebug(log) << "--- Available updates:"; for (const Update &u : updates) diff --git a/src/plugins/updateinfo/updateinfotools.h b/src/plugins/updateinfo/updateinfotools.h new file mode 100644 index 00000000000..66ad2eb3aca --- /dev/null +++ b/src/plugins/updateinfo/updateinfotools.h @@ -0,0 +1,163 @@ +/**************************************************************************** +** +** Copyright (C) 2022 the Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include +#include + +#include +#include +#include +#include + +#include + +Q_DECLARE_LOGGING_CATEGORY(log) + +std::unique_ptr documentForResponse(const QString &response) +{ + // since the output can contain two toplevel items from the two separate MaintenanceTool runs, + // surround with a toplevel element + const QString xml = response.isEmpty() ? QString() : ("" + response + ""); + std::unique_ptr doc(new QDomDocument); + doc->setContent(xml); + return doc; +} + +struct Update +{ + QString name; + QString version; + + bool operator==(const Update &other) const + { + return other.name == name && other.version == version; + }; +}; + +QList availableUpdates(const QDomDocument &document) +{ + if (document.isNull() || !document.firstChildElement().hasChildNodes()) + return {}; + QList result; + const QDomNodeList updates = document.firstChildElement().elementsByTagName("update"); + for (int i = 0; i < updates.size(); ++i) { + const QDomNode node = updates.item(i); + if (node.isElement()) { + const QDomElement element = node.toElement(); + if (element.hasAttribute("name")) + result.append({element.attribute("name"), element.attribute("version")}); + } + } + return result; +} + +struct QtPackage +{ + QString displayName; + QVersionNumber version; + bool installed; + bool isPrerelease = false; + + bool operator==(const QtPackage &other) const + { + return other.installed == installed && other.isPrerelease == isPrerelease + && other.version == version && other.displayName == displayName; + } +}; + +QList availableQtPackages(const QDomDocument &document) +{ + if (document.isNull() || !document.firstChildElement().hasChildNodes()) + return {}; + QList result; + const QDomNodeList packages = document.firstChildElement().elementsByTagName("package"); + for (int i = 0; i < packages.size(); ++i) { + const QDomNode node = packages.item(i); + if (node.isElement()) { + const QDomElement element = node.toElement(); + if (element.hasAttribute("displayname") && element.hasAttribute("name") + && element.hasAttribute("version")) { + QtPackage package{element.attribute("displayname"), + QVersionNumber::fromString(element.attribute("version")), + element.hasAttribute("installedVersion")}; + // Heuristic: Prerelease if the name is not "Qt x.y.z" + // (prereleases are named "Qt x.y.z-alpha" etc) + package.isPrerelease = package.displayName + != QString("Qt %1").arg(package.version.toString()); + result.append(package); + } + } + } + std::sort(result.begin(), result.end(), [](const QtPackage &p1, const QtPackage &p2) { + return p1.version > p2.version; + }); + return result; +} + +// Expects packages to be sorted, high version first. +Utils::optional highestInstalledQt(const QList &packages) +{ + const auto highestInstalledIt = std::find_if(packages.cbegin(), + packages.cend(), + [](const QtPackage &p) { return p.installed; }); + if (highestInstalledIt == packages.cend()) // Qt not installed + return {}; + return *highestInstalledIt; +} + +// Expects packages to be sorted, high version first. +Utils::optional qtToNagAbout(const QList &allPackages, + QVersionNumber *highestSeen) +{ + // Filter out any Qt prereleases + const QList packages = Utils::filtered(allPackages, [](const QtPackage &p) { + return !p.isPrerelease; + }); + if (packages.isEmpty()) + return {}; + const QtPackage highest = packages.constFirst(); + qCDebug(log) << "Highest available (non-prerelease) Qt:" << highest.version; + qCDebug(log) << "Highest previously seen (non-prerelease) Qt:" << *highestSeen; + // if the highestSeen version is null, we don't know if the Qt version is new, and better don't nag + const bool isNew = !highestSeen->isNull() && highest.version > *highestSeen; + if (highestSeen->isNull() || isNew) + *highestSeen = highest.version; + if (!isNew) + return {}; + const Utils::optional highestInstalled = highestInstalledQt(packages); + qCDebug(log) << "Highest installed Qt:" + << qPrintable(highestInstalled ? highestInstalled->version.toString() + : QString("none")); + if (!highestInstalled) // don't nag if no Qt is installed at all + return {}; + if (highestInstalled->version == highest.version) + return {}; + return highest; +} + +Q_DECLARE_METATYPE(Update) +Q_DECLARE_METATYPE(QtPackage) diff --git a/tests/auto/CMakeLists.txt b/tests/auto/CMakeLists.txt index dcf51ff0a2e..bc58a5b953d 100644 --- a/tests/auto/CMakeLists.txt +++ b/tests/auto/CMakeLists.txt @@ -21,5 +21,6 @@ add_subdirectory(ssh) add_subdirectory(toolchaincache) add_subdirectory(tracing) add_subdirectory(treeviewfind) +add_subdirectory(updateinfo) add_subdirectory(utils) add_subdirectory(valgrind) diff --git a/tests/auto/auto.qbs b/tests/auto/auto.qbs index c82d1df0941..6526653ab8e 100644 --- a/tests/auto/auto.qbs +++ b/tests/auto/auto.qbs @@ -24,6 +24,7 @@ Project { "toolchaincache/toolchaincache.qbs", "tracing/tracing.qbs", "treeviewfind/treeviewfind.qbs", + "updateinfo/updateinfo.qbs", "utils/utils.qbs", "valgrind/valgrind.qbs", ].concat(project.additionalAutotests) diff --git a/tests/auto/updateinfo/CMakeLists.txt b/tests/auto/updateinfo/CMakeLists.txt new file mode 100644 index 00000000000..e44adb8ff81 --- /dev/null +++ b/tests/auto/updateinfo/CMakeLists.txt @@ -0,0 +1,5 @@ +add_qtc_test(tst_updateinfo + INCLUDES ${PROJECT_SOURCE_DIR}/src/plugins + DEPENDS Utils Qt5::Xml + SOURCES tst_updateinfo.cpp +) diff --git a/tests/auto/updateinfo/tst_updateinfo.cpp b/tests/auto/updateinfo/tst_updateinfo.cpp new file mode 100644 index 00000000000..55e61a16c23 --- /dev/null +++ b/tests/auto/updateinfo/tst_updateinfo.cpp @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include + +#include + +Q_LOGGING_CATEGORY(log, "qtc.updateinfo", QtWarningMsg) + +class tst_UpdateInfo : public QObject +{ + Q_OBJECT + +private slots: + void updates_data(); + void updates(); +}; + +void tst_UpdateInfo::updates_data() +{ + QTest::addColumn("xml"); + QTest::addColumn>("xupdates"); + QTest::addColumn>("xpackages"); + + QTest::newRow("updates and packages") + << R"raw( + + + + + + + )raw" + << QList({{"Qt Design Studio 3.2.0", "3.2.0-0-202203291247"}}) + << QList( + {{"Qt 6.2.1", QVersionNumber::fromString("6.2.1-0-202110220854"), false, false}, + {"Qt 6.1.0-beta1", QVersionNumber::fromString("6.1.0-0-202105040922"), false, true}, + {"Qt 5.15.2", QVersionNumber::fromString("5.15.2-0-202011130607"), true, false}}); +} + +void tst_UpdateInfo::updates() +{ + QFETCH(QString, xml); + QFETCH(QList, xupdates); + QFETCH(QList, xpackages); + + std::unique_ptr doc = documentForResponse(xml); + const QList updates = availableUpdates(*doc); + QCOMPARE(updates, xupdates); + const QList packages = availableQtPackages(*doc); + QCOMPARE(packages, xpackages); +} + +QTEST_GUILESS_MAIN(tst_UpdateInfo) + +#include "tst_updateinfo.moc" diff --git a/tests/auto/updateinfo/updateinfo.qbs b/tests/auto/updateinfo/updateinfo.qbs new file mode 100644 index 00000000000..9aa07dd28fc --- /dev/null +++ b/tests/auto/updateinfo/updateinfo.qbs @@ -0,0 +1,7 @@ +QtcAutotest { + name: "UpdateInfo autotest" + Depends { name: "Qt.xml" } + Depends { name: "Utils" } + files: "tst_updateinfo.cpp" + cpp.includePaths: base.concat(["../../../src/plugins"]) +}