Commit 694b53a5 authored by Aard's avatar Aard

Merge branch 'master' of https://github.com/martyone/ssu

parents 6c54d947 77b4b2d8
......@@ -17,4 +17,5 @@ UI_SOURCES_DIR = $$BUILD
RCC_DIR = $$BUILD
LIBS += -L$$PWD/build/libssu
LD_LIBRARY_PATH = $$PWD/build/libssu
INCLUDEPATH += $$PWD/libssu
......@@ -119,7 +119,7 @@ void Ssu::setRelease(QString release, bool rnd){
void Ssu::setDomain(QString domain){
SsuCoreConfig *settings = SsuCoreConfig::instance();
setDomain(domain);
settings->setDomain(domain);
}
bool Ssu::useSslVerify(){
......
......@@ -67,30 +67,42 @@ void SsuSettings::merge(bool keepOld){
if (settingsFiles.count() > 0 && !keepOld)
clear();
merge(this, settingsFiles);
sync();
}
void SsuSettings::merge(QSettings *masterSettings, const QStringList &settingsFiles){
SsuLog *ssuLog = SsuLog::instance();
foreach (const QString &settingsFile, settingsFiles){
QSettings settings(settingsFile, QSettings::IniFormat);
QStringList groups = settings.childGroups();
ssuLog->print(LOG_DEBUG, QString("Merging %1 into %2")
.arg(settingsFile)
.arg(fileName()));
.arg(masterSettings->fileName()));
foreach (const QString &group, groups){
beginGroup(group);
masterSettings->beginGroup(group);
settings.beginGroup(group);
QStringList keys = settings.allKeys();
foreach (const QString &key, keys){
setValue(key, settings.value(key));
masterSettings->setValue(key, settings.value(key));
}
settings.endGroup();
endGroup();
masterSettings->endGroup();
}
sync();
}
}
/*
* If you change anything here, run `make update-upgrade-test-recipe` inside
* tests/ut_settings/ and check the impact of your changes with
* `git diff testdata/upgrade/recipe`. See ut_settings/upgradetesthelper.cpp for
* more details.
*/
void SsuSettings::upgrade(){
int configVersion=0;
int defaultConfigVersion=0;
......@@ -175,5 +187,6 @@ void SsuSettings::upgrade(){
}
setValue("configVersion", i);
}
sync();
}
}
......@@ -13,6 +13,8 @@
class SsuSettings: public QSettings {
Q_OBJECT
friend class SettingsTest;
public:
SsuSettings();
SsuSettings(const QString &fileName, Format format, QObject *parent=0);
......@@ -30,6 +32,7 @@ class SsuSettings: public QSettings {
private:
QString defaultSettingsFile, settingsd;
void merge(bool keepOld=false);
static void merge(QSettings *masterSettings, const QStringList &settingsFiles);
void upgrade();
};
......
SOURCES = sandboxhook.cpp
TEMPLATE = lib
TARGET = sandboxhook
CONFIG -= app_bundle
CONFIG += console qtestlib
QT -= gui
QT += network testlib
!include( ../tests.pri ) { error("Unable to find tests include") }
unix:target.path = $${PREFIX}/$$TESTS_PATH
INSTALLS += target
!include( ../../buildpath.pri ) { error("Unable to find build path specification") }
/**
* @file sandboxfileenginehandler.h
* @copyright 2013 Jolla Ltd.
* @author Martin Kampas <martin.kampas@tieto.com>
* @date 2013
*/
#ifndef _SANDBOXINGFILEENGINEHANDLER_H
#define _SANDBOXINGFILEENGINEHANDLER_H
#include <QtCore/QAbstractFileEngineHandler>
#include <QtCore/QDir>
#include <QtCore/QFSFileEngine>
#include <QtCore/QFileInfo>
#include <QtCore/QProcessEnvironment>
#include <QtCore/QSet>
#include <ssucoreconfig.h>
#include "../../constants.h"
/**
* Redirects all file operations on system configuration files to files under
* directory specified by SSU_TESTS_SANDBOX environment variable.
*/
class SandboxFileEngineHandler : public QAbstractFileEngineHandler {
public:
QAbstractFileEngine *create(const QString &fileName) const{
static bool enabled = false;
static bool firstCall = true;
if (!enabled && !firstCall){
return 0;
}
static QString sandboxPath =
QProcessEnvironment::systemEnvironment().value("SSU_TESTS_SANDBOX");
if (firstCall){
firstCall = false;
if (sandboxPath.isEmpty()){
return 0;
}
if (!QFileInfo(sandboxPath).exists()){
qFatal("%s: Invalid SSU_TESTS_SANDBOX value: No such file or directory",
qPrintable(sandboxPath));
}
if (!QFileInfo(sandboxPath).isDir()){
qFatal("%s: Invalid SSU_TESTS_SANDBOX value: Not a directory",
qPrintable(sandboxPath));
}
enabled = true;
}
if (!fileName.startsWith('/')){
return 0;
}
static QSet<QString> ssuConfigFiles = QSet<QString>()
<< SSU_CONFIGURATION
<< SSU_REPO_CONFIGURATION
<< SSU_DEFAULT_CONFIGURATION
<< SSU_BOARD_MAPPING_CONFIGURATION;
static QSet<QString> ssuConfigDirectories = QSet<QString>()
<< SSU_BOARD_MAPPING_CONFIGURATION_DIR;
if (!ssuConfigFiles.contains(fileName)){
bool match = false;
foreach (const QString &ssuConfigDirectory, ssuConfigDirectories){
if (fileName.startsWith(ssuConfigDirectory + '/')){
match = true;
break;
}
}
if (!match){
return 0;
}
}
const QString fileName_ = QDir(sandboxPath).absoluteFilePath(QString(fileName).remove(0, 1));
return new QFSFileEngine(fileName_);
}
};
#endif
#include <dlfcn.h>
#include "sandboxfileenginehandler.h"
extern "C" void qt_startup_hook()
{
SandboxFileEngineHandler *const handler = new SandboxFileEngineHandler();
Q_UNUSED(handler);
static void(*next_qt_startup_hook)() = (void (*)()) dlsym(RTLD_NEXT, "qt_startup_hook");
next_qt_startup_hook();
}
TESTS_PATH = /opt/tests/ssu
\ No newline at end of file
DEPENDPATH *= $${PWD}/sandbox
TESTS_PATH = /opt/tests/ssu
DEFINES += TESTS_PATH="'\"$${TESTS_PATH}\"'"
isEmpty(TARGET):error("TARGET must be defined before this file is included")
TESTS_DATA_PATH = /opt/tests/ssu/data/$${TARGET}
DEFINES += TESTS_DATA_PATH="'\"$${TESTS_DATA_PATH}\"'"
TEMPLATE = subdirs
CONFIG += qt ordered coverage debug
SUBDIRS = ut_urlresolver ut_variables
SUBDIRS = \
sandbox \
ut_settings \
ut_ssuurlresolver \
ut_urlresolver \
ut_variables \
!include( tests.pri ) { error("Unable to find tests include") }
......
......@@ -3,6 +3,16 @@
<!-- Test suite, name mandatory - the same as test package name -->
<suite name="sync-app-tests" domain="ssu">
<!-- At least one set per suite, name and description mandatory -->
<set name="settings" description="Test to determine if configuration files processing works properly" feature="settings">
<case name="ut_settings" type="Functional" description="Settings processing test" timeout="1000" subfeature="">
<step expected_result="0">/opt/tests/ssu/ut_settings</step>
</case>
</set>
<set name="ssuurlresolver" description="Test to determine if the UrlResolverPlugin works well with installed version of libzypp" feature="ssuurlresolver">
<case name="ut_ssuurlresolver" type="Functional" description="URL resolver plugin test" timeout="1000" subfeature="">
<step expected_result="0">/opt/tests/ssu/ut_ssuurlresolver</step>
</case>
</set>
<set name="urlresolver" description="Test to determine if URL resolving works properly" feature="urlresolver">
<case name="ut_urlresolver" type="Functional" description="URL resolver tests" timeout="1000" subfeature="">
<step expected_result="0">/opt/tests/ssu/ut_urlresolver</step>
......
/**
* @file main.cpp
* @copyright 2012 Jolla Ltd.
* @author Martin Kampas <martin.kampas@tieto.com>
* @date 2012
*/
#include <QtCore/QCoreApplication>
#include <QtTest/QtTest>
#include "settingstest.h"
#include "upgradetesthelper.h"
int main(int argc, char **argv){
QCoreApplication app(argc, argv);
if (app.arguments().contains("-generate-upgrade-test-recipe")){
QTextStream out(stdout);
return UpgradeTestHelper::generateSnapshotRecipe(&out) ? 0 : 1;
}
SettingsTest settingsTest;
if (QTest::qExec(&settingsTest, argc, argv))
return 1;
return 0;
}
/**
* @file settingstest.cpp
* @copyright 2013 Jolla Ltd.
* @author Martin Kampas <martin.kampas@tieto.com>
* @date 2013
*/
#include "settingstest.h"
#include <QtTest/QtTest>
#include <ssusettings.h>
#include "upgradetesthelper.h"
void SettingsTest::initTestCase(){
}
void SettingsTest::cleanupTestCase(){
}
void SettingsTest::testMerge_data(){
// Key names state which file(s) a key is set in (master, foo or bar). Value
// is always in form "<file>-value" based on which file it is taken from.
const QScopedPointer<QTemporaryFile> masterFile(
QTemporaryFile::createLocalFile(":/testdata/merge/settings.ini"));
QSettings master(masterFile->fileName(), QSettings::IniFormat);
const QStringList settingsFiles = QStringList()
<< ":/testdata/merge/settings.d/bar.ini"
<< ":/testdata/merge/settings.d/foo.ini";
SsuSettings::merge(&master, settingsFiles);
QSettings expected(":/testdata/merge/merged.ini", QSettings::IniFormat);
const QSet<QString> masterKeys = master.allKeys().toSet();
const QSet<QString> expectedKeys = expected.allKeys().toSet();
QTest::addColumn<bool>("keyIsMerged");
QTest::addColumn<bool>("keyShouldBeMerged");
QTest::addColumn<QString>("actualValue");
QTest::addColumn<QString>("expectedValue");
foreach (const QString &key, masterKeys + expectedKeys){
QTest::newRow(qPrintable(key))
<< masterKeys.contains(key)
<< expectedKeys.contains(key)
<< master.value(key).toString()
<< expected.value(key).toString();
}
}
void SettingsTest::testMerge(){
QFETCH(bool, keyIsMerged);
QFETCH(bool, keyShouldBeMerged);
QFETCH(QString, actualValue);
QFETCH(QString, expectedValue);
QCOMPARE(keyIsMerged, keyShouldBeMerged);
QCOMPARE(actualValue, expectedValue);
}
void SettingsTest::testUpgrade_data(){
// Read recipe
QFile recipe(":/testdata/upgrade/recipe");
QVERIFY(recipe.open(QIODevice::ReadOnly));
QList<UpgradeTestHelper::TestCase> testCases = UpgradeTestHelper::readRecipe(&recipe);
// Generate settings file according to recipe
QTemporaryFile settingsFile;
QVERIFY(settingsFile.open() == true);
QSettings settings(settingsFile.fileName(), QSettings::IniFormat);
UpgradeTestHelper::fillSettings(&settings, testCases);
// Generate defaults file according to recipe
QTemporaryFile defaultSettingsFile;
QVERIFY(defaultSettingsFile.open() == true);
QSettings defaultSettings(defaultSettingsFile.fileName(), QSettings::IniFormat);
UpgradeTestHelper::fillDefaultSettings(&defaultSettings, testCases);
// Parse settings -- do upgrade
#if 0
settingsFile.seek(0);
defaultSettingsFile.seek(0);
qDebug() << "SETTINGS {{{\n" << settingsFile.readAll() << "\n}}}";
qDebug() << "DEFAULT SETTINGS {{{\n" << defaultSettingsFile.readAll() << "\n}}}";
#endif
SsuSettings ssuSettings(settingsFile.fileName(), QSettings::IniFormat,
defaultSettingsFile.fileName());
#if 0
settingsFile.seek(0);
qDebug() << "SETTINGS UPGRADED {{{\n" << settingsFile.readAll() << "\n}}}";
#endif
// Record data for verification phase
QTest::addColumn<bool>("keyIsSet");
QTest::addColumn<bool>("keyShouldBeSet");
QTest::addColumn<QString>("actualValue");
QTest::addColumn<QString>("expectedValue");
foreach (const UpgradeTestHelper::TestCase &testCase, testCases){
foreach (const QString &group, UpgradeTestHelper::groups()){
const QString key = group.isEmpty() ? testCase.key() : group + '/' + testCase.key();
QTest::newRow(qPrintable(QString("%1%2:%3:%4")
.arg(group.isEmpty() ? "" : group + "/")
.arg(testCase.history)
.arg(testCase.current)
.arg(testCase.expected)))
<< ssuSettings.contains(key)
<< testCase.keyShouldBeSet()
<< ssuSettings.value(key).toString()
<< testCase.expected;
}
}
}
void SettingsTest::testUpgrade(){
QFETCH(bool, keyIsSet);
QFETCH(bool, keyShouldBeSet);
QFETCH(QString, actualValue);
QFETCH(QString, expectedValue);
QCOMPARE(keyIsSet, keyShouldBeSet);
if (keyIsSet){
QCOMPARE(actualValue, expectedValue);
}
}
/**
* @file settingstest.h
* @copyright 2013 Jolla Ltd.
* @author Martin Kampas <martin.kampas@tieto.com>
* @date 2013
*/
#ifndef _SETTINGSTEST_H
#define _SETTINGSTEST_H
#include <QObject>
class SettingsTest: public QObject {
Q_OBJECT
private slots:
void initTestCase();
void cleanupTestCase();
void testMerge_data();
void testMerge();
void testUpgrade_data();
void testUpgrade();
private:
};
#endif
<!DOCTYPE RCC>
<RCC version="1.0">
<qresource>
<file>testdata/merge/settings.d/bar.ini</file>
<file>testdata/merge/settings.d/foo.ini</file>
<file>testdata/merge/merged.ini</file>
<file>testdata/merge/settings.ini</file>
<file>testdata/upgrade/recipe</file>
</qresource>
</RCC>
# Global values do not get overridden
global-master-only = master-value
global-master-foo = master-value
global-master-bar = master-value
global-master-foo-bar = master-value
[groupA]
master-only = master-value
foo-only = foo-value
bar-only = bar-value
master-foo = foo-value
master-bar = bar-value
master-foo-bar = foo-value
foo-bar = foo-value
global-bar-only = bar-value
global-master-bar = bar-value
global-master-foo-bar = bar-value
global-foo-bar = bar-value
[groupA]
bar-only = bar-value
master-bar = bar-value
master-foo-bar = bar-value
foo-bar = bar-value
global-foo-only = foo-value
global-master-foo = foo-value
global-master-foo-bar = foo-value
global-foo-bar = foo-value
[groupA]
foo-only = foo-value
master-foo = foo-value
master-foo-bar = foo-value
foo-bar = foo-value
global-master-only = master-value
global-master-foo = master-value
global-master-bar = master-value
global-master-foo-bar = master-value
[groupA]
master-only = master-value
master-foo = master-value
master-bar = master-value
master-foo-bar = master-value
This diff is collapsed.
/**
* @file upgradetesthelper.cpp
* @copyright 2013 Jolla Ltd.
* @author Martin Kampas <martin.kampas@tieto.com>
* @date 2013
*/
#include "upgradetesthelper.h"
#include <QtCore/QBuffer>
#include <QtCore/QDebug>
#include <QtCore/qmath.h>
#include <QtCore/QTemporaryFile>
#include <QtCore/QTextStream>
#include <ssusettings.h>
/**
* @class UpgradeTestHelper
* @brief Utilities to generate upgrade-test data
*
* It is driven by recipe in following format.
*
* Every line consists of three values delimited by colon:
*
* @verbatim
* <history>:<current>:<expected>
* @endverbatim
*
* - history of changes of the key. A word of length HistoryLength made up
* of letters [SKRN] -- (S)et value, (K)eep value, (R)emove key, (N)oop
* - current value (settings at revision CurrentVersion)
* - value expected after upgrade. The special value "@NOTSET@" means the key
* is expected to not be set after upgrade.
*
* Action (S)et results in assigning "v<revision>-default" to the key, e.g.,
* "v5-default" when 'S' appears as 5th letter in the history word.
*
* The key is built as "<history>__<current>".
*
* Example:
*
* @verbatim
* SKNRN:v1-default:@NOTSET@
* SSNSN:v2-default:v4-default
* @endverbatim
*
* Generated settings:
*
* @verbatim
* [Global]
* configVersion = 2
* SKNRN__v1-default = v1-default
* SSNSN__v2-default = v2-default
*
* [groupA]
* SKNRN__v1-default = v1-default
* SSNSN__v2-default = v2-default
* @endverbatim
*
* Generated default settings:
*
* @verbatim
* [Global]
* configVersion = 5
*
* [1]
* SKNRN__v1-default = v1-default
* groupA\SKNRN__v1-default = v1-default
* SSNSN__v2-default = v1-default
* groupA\SSNSN__v2-default = v1-default
*
* [2]
* SKNRN__v1-default = v1-default
* groupA\SKNRN__v1-default = v1-default
* SSNSN__v2-default = v2-default
* groupA\SSNSN__v2-default = v2-default
*
* [3]
*
* [4]
* cmd-remove = SKNRN__v1-default, groupA/SKNRN__v1-default
* SSNSN__v2-default = v4-default
* groupA\SSNSN__v2-default = v4-default
*
* [5]
* @endverbatim
*/
QList<UpgradeTestHelper::TestCase> UpgradeTestHelper::readRecipe(QIODevice *recipe){
QList<TestCase> testCases;
while (!recipe->atEnd()){
const QString line = recipe->readLine().trimmed();
if (line.startsWith('#') || line.isEmpty())
continue;
const QStringList splitted = line.split(':');
Q_ASSERT_X(splitted.count() == 3, Q_FUNC_INFO,
qPrintable(QString("Inalid recipe line '%1'").arg(line)));
testCases.append(TestCase(splitted.at(0), splitted.at(1), splitted.at(2)));
}
return testCases;
}
void UpgradeTestHelper::fillSettings(QSettings *settings, const QList<TestCase> &testCases){
settings->setValue("configVersion", CurrentVersion);
foreach (const QString &group, groups()){
settings->beginGroup(group);
foreach (const TestCase &testCase, testCases){
if (!testCase.current.isEmpty()){
settings->setValue(testCase.key(), testCase.current);
}
}
settings->endGroup();
}
settings->sync();
}
void UpgradeTestHelper::fillDefaultSettings(QSettings *defaultSettings, const QList<TestCase>
&testCases){
defaultSettings->setValue("configVersion", HistoryLength);
QHash<QString, QString> lastSetValue; // for the (K)eep action; no need to qualify with group
for (int revision = 1; revision <= HistoryLength; ++revision){
defaultSettings->beginGroup(QString::number(revision));
QStringList keysToRemove;
foreach (const QString &group, groups()){
defaultSettings->beginGroup(group);
foreach (const TestCase &testCase, testCases){
switch (testCase.history.at(revision - 1).toAscii()){
case 'S': // (S)et value
lastSetValue[testCase.key()] = QString("v%1-default").arg(revision);
defaultSettings->setValue(testCase.key(), lastSetValue[testCase.key()]);
break;
case 'K': // (K)eep value
Q_ASSERT_X(!lastSetValue[testCase.key()].isEmpty(), Q_FUNC_INFO,
qPrintable(QString("Inalid TestCase::history: '%1'").arg(testCase.history)));
defaultSettings->setValue(testCase.key(), lastSetValue[testCase.key()]);
break;
case 'R': // (R)emove key
keysToRemove.append((group.isEmpty() ? group : group + "/") + testCase.key());
lastSetValue.remove(testCase.key());
break;
case 'N': // (N)oop
break;
default:
Q_ASSERT_X(false, Q_FUNC_INFO, qPrintable(QString(
"Inalid TestCase::history: '%1': invalid command-code '%2'")
.arg(testCase.history)
.arg(testCase.history.at(revision - 1))));
}
}
defaultSettings->endGroup();
}
if (!keysToRemove.isEmpty()){
defaultSettings->setValue("cmd-remove", keysToRemove);
}
defaultSettings->endGroup();
}
defaultSettings->sync();
}
bool UpgradeTestHelper::generateSnapshotRecipe(QTextStream *out){
const QString actions = "SKRN";
QBuffer buf;
buf.open(QIODevice::ReadWrite);
QTextStream stream(&buf);
// for all "valid" variations of the letters "SKRN" of length HistoryLength
for (int i = 0; i < qPow(actions.count(), HistoryLength); ++i){
QString history = QString::number(i, actions.count());
// Left pad to HistoryLength
history.prepend(QString(HistoryLength - history.length(), '0'));
for (int revision = 0; revision < history.length(); ++revision){
history.replace(revision, 1, actions.at(history.at(revision).digitValue()));
}
static const QRegExp invalidSequence("(^[^S]*K|R[^S]*K|^R)");
if (history.contains(invalidSequence)){
continue;
}
for (int revision = 0; revision < HistoryLength; ++revision){
stream << history << QString(":v%1-default:\n").arg(revision);
}
stream << history << ":custom:\n";
}
stream.flush();
// Read recipe
buf.seek(0);
QList<TestCase> testCases = readRecipe(&buf);
// Generate settings file according to recipe
QTemporaryFile settingsFile;
if (!settingsFile.open()){