4
0
mirror of https://github.com/QuasarApp/installer-framework.git synced 2025-05-11 20:39:35 +00:00
Katja Marttila 7dd35b336f Fix addDependency functionality
addDependency was acting weird - if a subcomponent was added as
dependency, the parent was not installed. It was installed the next time
the installer was launched and something else was added/removed.
addDependency should behave the same as when selecting the component
from UI - it should install the parents too

Task-number: QTIFW-1458
Change-Id: I21726ad6acda3b1fb382263405754c2d682dea76
Reviewed-by: Iikka Eklund <iikka.eklund@qt.io>
2019-10-21 11:05:56 +00:00

376 lines
15 KiB
C++

/**************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** 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.
**
** $QT_END_LICENSE$
**
**************************************************************************/
#include <component.h>
#include <graph.h>
#include <installercalculator.h>
#include <uninstallercalculator.h>
#include <componentchecker.h>
#include <packagemanagercore.h>
#include <settings.h>
#include <QTest>
using namespace QInstaller;
typedef QMap<Component *, QStringList> ComponentToStringList;
class Data {
public:
Data() {}
explicit Data(const QString &data)
: m_data(data) {}
inline uint qHash(const Data &test);
QString data() const { return m_data; }
bool operator==(const Data &rhs) const { return m_data == rhs.m_data; }
const Data &operator=(const Data &rhs) { if (this != &rhs) { m_data = rhs.m_data; } return *this; }
private:
QString m_data;
};
inline uint qHash(const Data &data)
{
return qHash(data.data());
}
class NamedComponent : public Component
{
public:
NamedComponent(PackageManagerCore *core, const QString &name)
: Component(core)
{
setValue(scName, name);
setValue(scVersion, QLatin1String("1.0.0"));
}
NamedComponent(PackageManagerCore *core, const QString &name, const QString &version)
: Component(core)
{
setValue(scName, name);
setValue(scVersion, version);
}
};
class tst_Solver : public QObject
{
Q_OBJECT
private slots:
// TODO: add failing cases
void sortGraph()
{
Graph<QString> graph;
graph.addNode("Hut");
graph.addEdge("Jacke", "Shirt");
graph.addEdge("Guertel", "Hose");
graph.addEdge("Guertel", "Shirt");
graph.addEdge("Shirt", "Socken");
graph.addEdge("Socken", "Unterwaesche");
graph.addEdge("Shirt", "Unterwaesche");
graph.addEdges("Hose", QStringList() << "Unterwaesche" << "Socken");
graph.addEdges("Krawatte", QStringList() << "Shirt" << "Hose" << "Guertel");
graph.addEdges("Schuhe", QStringList() << "Socken" << "Unterwaesche" << "Hose");
graph.addEdges("Jacke", QStringList() << "Hose" << "Shirt" << "Guertel" << "Schuhe" << "Krawatte");
QList<QString> resolved = graph.sort();
foreach (const QString &value, resolved)
qDebug("%s", qPrintable(value));
}
void sortGraphReverse()
{
Graph<QString> graph;
graph.addEdge("Krawatte", "Jacke");
graph.addEdge("Guertel", "Jacke");
graph.addEdge("Shirt", "Guertel");
graph.addEdges("Shirt", QList<QString>() << "Krawatte" << "Schuhe");
graph.addEdges("Hose", QList<QString>() << "Schuhe" << "Guertel" << "Shirt");
graph.addEdges("Socken", QList<QString>() << "Schuhe" << "Hose");
graph.addEdges("Unterwaesche", QList<QString>() << "Socken" << "Hose" << "Guertel" << "Shirt"
<< "Krawatte" << "Schuhe");
QList<QString> resolved = graph.sortReverse();
foreach (const QString &value, resolved)
qDebug("%s", qPrintable(value));
}
void sortGraphCycle()
{
Data a("A"), b("B"), c("C"), d("D"), e("E");
Graph<Data> graph;
graph.addEdge(a, b);
graph.addEdge(b, c);
graph.addEdge(c, d);
graph.addEdge(d, e);
graph.addEdge(e, a);
QList<Data> resolved = graph.sort();
foreach (const Data &value, resolved)
qDebug("%s", qPrintable(value.data()));
QPair<Data, Data> cycle = graph.cycle();
qDebug("Found cycle: %s", graph.hasCycle() ? "true" : "false");
qDebug("(%s) has a indirect dependency on (%s).", qPrintable(cycle.second.data()),
qPrintable(cycle.first.data()));
}
void resolveInstaller_data()
{
QTest::addColumn<PackageManagerCore *>("core");
QTest::addColumn<QList<Component *> >("selectedComponents");
QTest::addColumn<QList<Component *> >("expectedResult");
QTest::addColumn<QList<int> >("installReason");
PackageManagerCore *core = new PackageManagerCore();
core->setPackageManager();
NamedComponent *componentA = new NamedComponent(core, QLatin1String("A"));
NamedComponent *componentAA = new NamedComponent(core, QLatin1String("A.A"));
NamedComponent *componentAB = new NamedComponent(core, QLatin1String("A.B"));
NamedComponent *componentABC = new NamedComponent(core, QLatin1String("A.B.C"));
NamedComponent *componentABD = new NamedComponent(core, QLatin1String("A.B.D"));
componentA->appendComponent(componentAA);
componentA->appendComponent(componentAB);
componentAB->appendComponent(componentABC);
componentAB->appendComponent(componentABD);
NamedComponent *componentB = new NamedComponent(core, QLatin1String("B"));
NamedComponent *componentB_NewVersion = new NamedComponent(core, QLatin1String("B_version"), QLatin1String("2.0.0"));
NamedComponent *componentB_Auto = new NamedComponent(core, QLatin1String("B_auto"));
componentB->addDependency(QLatin1String("A.B.C"));
componentABC->addDependency(QLatin1String("B_version->=2.0.0"));
componentB_Auto->addAutoDependOn(QLatin1String("B_version"));
core->appendRootComponent(componentA);
core->appendRootComponent(componentB);
core->appendRootComponent(componentB_NewVersion);
core->appendRootComponent(componentB_Auto);
QTest::newRow("Installer resolved") << core
<< (QList<Component *>() << componentB)
<< (QList<Component *>() << componentA << componentAB << componentABC << componentB_NewVersion << componentB << componentB_Auto)
<< (QList<int>()
<< InstallerCalculator::Dependent
<< InstallerCalculator::Dependent
<< InstallerCalculator::Dependent
<< InstallerCalculator::Dependent
<< InstallerCalculator::Resolved
<< InstallerCalculator::Automatic);
}
void resolveInstaller()
{
QFETCH(PackageManagerCore *, core);
QFETCH(QList<Component *> , selectedComponents);
QFETCH(QList<Component *> , expectedResult);
QFETCH(QList<int>, installReason);
InstallerCalculator calc(core->components(PackageManagerCore::ComponentType::AllNoReplacements));
calc.appendComponentsToInstall(selectedComponents);
QList<Component *> result = calc.orderedComponentsToInstall();
int results = 0;
QCOMPARE(result.count(), expectedResult.count());
for (int i = 0; i < result.count(); i++) {
if (result.contains(expectedResult.at(i))) {
int index = result.indexOf(expectedResult.at(i));
QCOMPARE(result.at(index), expectedResult.at(i));
QCOMPARE((int)calc.installReasonType(result.at(index)), installReason.at(i));
results++;
}
}
// Check that we have found all expected results. Install order may vary
// for dependent components so we cannot do a direct compare
QCOMPARE(result.count(), results);
delete core;
}
void unresolvedDependencyVersion_data()
{
QTest::addColumn<PackageManagerCore *>("core");
QTest::addColumn<QList<Component *> >("selectedComponents");
QTest::addColumn<QList<Component *> >("expectedResult");
PackageManagerCore *core = new PackageManagerCore();
core->setPackageManager();
NamedComponent *componentA = new NamedComponent(core, QLatin1String("A"));
NamedComponent *componentB = new NamedComponent(core, QLatin1String("B"), QLatin1String("1.0.0"));
componentA->addDependency(QLatin1String("B->=2.0.0"));
core->appendRootComponent(componentA);
core->appendRootComponent(componentB);
if (core->settings().allowUnstableComponents()) {
QTest::newRow("Installer resolved") << core
<< (QList<Component *>() << componentA)
<< (QList<Component *>() << componentA);
} else {
QTest::newRow("Installer resolved") << core
<< (QList<Component *>() << componentA)
<< (QList<Component *>());
}
}
void unresolvedDependencyVersion()
{
QFETCH(PackageManagerCore *, core);
QFETCH(QList<Component *> , selectedComponents);
QFETCH(QList<Component *> , expectedResult);
InstallerCalculator calc(core->components(PackageManagerCore::ComponentType::AllNoReplacements));
QTest::ignoreMessage(QtWarningMsg, "Cannot find missing dependency \"B->=2.0.0\" for \"A\".");
calc.appendComponentsToInstall(selectedComponents);
QList<Component *> result = calc.orderedComponentsToInstall();
QCOMPARE(result.count(), expectedResult.count());
delete core;
}
void resolveUninstaller_data()
{
QTest::addColumn<PackageManagerCore *>("core");
QTest::addColumn<QList<Component *> >("selectedToUninstall");
QTest::addColumn<QList<Component *> >("installedComponents");
QTest::addColumn<QSet<Component *> >("expectedResult");
PackageManagerCore *core = new PackageManagerCore();
core->setPackageManager();
NamedComponent *componentA = new NamedComponent(core, QLatin1String("A"));
NamedComponent *componentAA = new NamedComponent(core, QLatin1String("A.A"));
NamedComponent *componentAB = new NamedComponent(core, QLatin1String("A.B"));
componentA->appendComponent(componentAA);
componentA->appendComponent(componentAB);
NamedComponent *componentB = new NamedComponent(core, QLatin1String("B"));
componentB->addDependency(QLatin1String("A.B"));
core->appendRootComponent(componentA);
core->appendRootComponent(componentB);
componentA->setInstalled();
componentB->setInstalled();
componentAB->setInstalled();
QTest::newRow("Uninstaller resolved") << core
<< (QList<Component *>() << componentAB)
<< (QList<Component *>() << componentA << componentB)
<< (QSet<Component *>() << componentAB << componentB);
core = new PackageManagerCore();
core->setPackageManager();
NamedComponent *compA = new NamedComponent(core, QLatin1String("A"));
NamedComponent *compB = new NamedComponent(core, QLatin1String("B"));
NamedComponent *compC = new NamedComponent(core, QLatin1String("C"));
compB->addDependency(QLatin1String("A"));
compC->addDependency(QLatin1String("B"));
core->appendRootComponent(compA);
core->appendRootComponent(compB);
core->appendRootComponent(compC);
compA->setInstalled();
compB->setInstalled();
QTest::newRow("Cascade dependencies") << core
<< (QList<Component *>() << compA)
<< (QList<Component *>() << compB)
<< (QSet<Component *>() << compA << compB);
}
void resolveUninstaller()
{
QFETCH(PackageManagerCore *, core);
QFETCH(QList<Component *> , selectedToUninstall);
QFETCH(QList<Component *> , installedComponents);
QFETCH(QSet<Component *> , expectedResult);
UninstallerCalculator calc(installedComponents);
calc.appendComponentsToUninstall(selectedToUninstall);
QSet<Component *> result = calc.componentsToUninstall();
QCOMPARE(result.count(), expectedResult.count());
QCOMPARE(result, expectedResult);
delete core;
}
void checkComponent_data()
{
QTest::addColumn<QList<Component *> >("componentsToCheck");
QTest::addColumn<ComponentToStringList>("expectedResult");
PackageManagerCore *core = new PackageManagerCore();
core->setPackageManager();
NamedComponent *componentA = new NamedComponent(core, QLatin1String("A"));
NamedComponent *componentB = new NamedComponent(core, QLatin1String("B"));
componentB->addAutoDependOn(QLatin1String("A"));
componentB->setValue(QLatin1String("Default"), QLatin1String("true"));
core->appendRootComponent(componentA);
core->appendRootComponent(componentB);
ComponentToStringList result;
result[componentB].append(QLatin1String("Component B specifies \"Default\" property "
"together with \"AutoDependOn\" list. This combination of states "
"may not work properly."));
QTest::newRow("AutoDepend and default")
<< (QList<Component *>() << componentA << componentB)
<< result;
NamedComponent *componentC = new NamedComponent(core, QLatin1String("C"));
NamedComponent *componentD = new NamedComponent(core, QLatin1String("D"));
NamedComponent *componentE = new NamedComponent(core, QLatin1String("E"));
componentD->setValue(QLatin1String("AutoDependOn"), QLatin1String("C"));
componentE->addDependency(QLatin1String("D"));
core->appendRootComponent(componentC);
core->appendRootComponent(componentD);
core->appendRootComponent(componentE);
result.clear();
result[componentD].append(QLatin1String("Other components depend on auto dependent "
"component D. This may not work properly."));
QTest::newRow("AutoDepend and dependency")
<< (QList<Component *>() << componentC << componentD << componentE)
<< result;
}
void checkComponent()
{
QFETCH(QList<Component *>, componentsToCheck);
QFETCH(ComponentToStringList, expectedResult);
foreach (Component *component, componentsToCheck) {
const QStringList result = ComponentChecker::checkComponent(component);
QCOMPARE(result, expectedResult.value(component));
}
}
};
QTEST_MAIN(tst_Solver)
#include "tst_solver.moc"