diff --git a/autotests/kconfigtest.cpp b/autotests/kconfigtest.cpp index 410b5b8..9af3b46 100644 --- a/autotests/kconfigtest.cpp +++ b/autotests/kconfigtest.cpp @@ -1,1960 +1,1954 @@ /* This file is part of the KDE libraries Copyright (C) 1997 Matthias Kalle Dalheimer (kalle@kde.org) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // Qt5 TODO: re-enable. No point in doing it before, it breaks on QString::fromUtf8(QByteArray), which exists in qt5. #undef QT_NO_CAST_FROM_BYTEARRAY #include "kconfigtest.h" #include "helper.h" #include "config-kconfig.h" #include #include #include #include #include #include #include #ifdef Q_OS_UNIX #include #endif #ifndef Q_OS_WIN -#include // gethostname +#include // getuid #endif KCONFIGGROUP_DECLARE_ENUM_QOBJECT(KConfigTest, Testing) KCONFIGGROUP_DECLARE_FLAGS_QOBJECT(KConfigTest, Flags) QTEST_MAIN(KConfigTest) Q_DECLARE_METATYPE(KConfigGroup) static QString homePath() { #ifdef Q_OS_WIN return QDir::homePath(); #else // Don't use QDir::homePath() on Unix, it removes any trailing slash, while KConfig uses $HOME. return QString::fromLocal8Bit(qgetenv("HOME")); #endif } #define BOOLENTRY1 true #define BOOLENTRY2 false #define STRINGENTRY1 "hello" #define STRINGENTRY2 " hello" #define STRINGENTRY3 "hello " #define STRINGENTRY4 " hello " #define STRINGENTRY5 " " #define STRINGENTRY6 "" #define UTF8BITENTRY "Hello äöü" #define TRANSLATEDSTRINGENTRY1 "bonjour" #define BYTEARRAYENTRY QByteArray( "\x00\xff\x7f\x3c abc\x00\x00", 10 ) #define ESCAPEKEY " []\0017[]==]" #define ESCAPEENTRY "[]\170[]]=3=]\\] " #define DOUBLEENTRY 123456.78912345 #define FLOATENTRY 123.567f #define POINTENTRY QPoint( 4351, 1235 ) #define SIZEENTRY QSize( 10, 20 ) #define RECTENTRY QRect( 10, 23, 5321, 13 ) #define DATETIMEENTRY QDateTime( QDate( 2002, 06, 23 ), QTime( 12, 55, 40 ) ) #define STRINGLISTENTRY (QStringList( "Hello," ) << " World") #define STRINGLISTEMPTYENTRY QStringList() #define STRINGLISTJUSTEMPTYELEMENT QStringList(QString()) #define STRINGLISTEMPTYTRAINLINGELEMENT (QStringList( "Hi" ) << QString()) #define STRINGLISTESCAPEODDENTRY (QStringList( "Hello\\\\\\" ) << "World") #define STRINGLISTESCAPEEVENENTRY (QStringList( "Hello\\\\\\\\" ) << "World") #define STRINGLISTESCAPECOMMAENTRY (QStringList( "Hel\\\\\\,\\\\,\\,\\\\\\\\,lo" ) << "World") #define INTLISTENTRY1 QList() << 1 << 2 << 3 << 4 #define BYTEARRAYLISTENTRY1 QList() << "" << "1,2" << "end" #define VARIANTLISTENTRY (QVariantList() << true << false << QString("joe") << 10023) #define VARIANTLISTENTRY2 (QVariantList() << POINTENTRY << SIZEENTRY) #define HOMEPATH QString(homePath()+"/foo") #define HOMEPATHESCAPE QString(homePath()+"/foo/$HOME") #define DOLLARGROUP "$i" #define TEST_SUBDIR "kconfigtest_subdir/" static inline QString testConfigDir() { return QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + "/" TEST_SUBDIR; } static inline QString kdeGlobalsPath() { return QDir::cleanPath(testConfigDir() + "..") + "/kdeglobals"; } void KConfigTest::initTestCase() { // ensure we don't use files in the real config directory QStandardPaths::setTestModeEnabled(true); qRegisterMetaType(); // to make sure all files from a previous failed run are deleted cleanupTestCase(); KSharedConfigPtr mainConfig = KSharedConfig::openConfig(); mainConfig->group("Main").writeEntry("Key", "Value"); mainConfig->sync(); KConfig sc(TEST_SUBDIR "kconfigtest"); KConfigGroup cg(&sc, "AAA"); cg.writeEntry("stringEntry1", STRINGENTRY1, KConfig::Persistent | KConfig::Global); cg.deleteEntry("stringEntry2", KConfig::Global); cg = KConfigGroup(&sc, "Hello"); cg.writeEntry("boolEntry1", BOOLENTRY1); cg.writeEntry("boolEntry2", BOOLENTRY2); QByteArray data(UTF8BITENTRY); QCOMPARE(data.size(), 12); // the source file is in utf8 QCOMPARE(QString::fromUtf8(data).length(), 9); cg.writeEntry("Test", data); cg.writeEntry("bytearrayEntry", BYTEARRAYENTRY); cg.writeEntry(ESCAPEKEY, ESCAPEENTRY); cg.writeEntry("emptyEntry", ""); cg.writeEntry("stringEntry1", STRINGENTRY1); cg.writeEntry("stringEntry2", STRINGENTRY2); cg.writeEntry("stringEntry3", STRINGENTRY3); cg.writeEntry("stringEntry4", STRINGENTRY4); cg.writeEntry("stringEntry5", STRINGENTRY5); cg.writeEntry("urlEntry1", QUrl(QStringLiteral("http://qt-project.org"))); cg.writeEntry("keywith=equalsign", STRINGENTRY1); cg.deleteEntry("stringEntry5"); cg.deleteEntry("stringEntry6"); // deleting a nonexistent entry cg.writeEntry("byteArrayEntry1", QByteArray(STRINGENTRY1), KConfig::Global | KConfig::Persistent); cg.writeEntry("doubleEntry1", DOUBLEENTRY); cg.writeEntry("floatEntry1", FLOATENTRY); sc.deleteGroup("deleteMe"); // deleting a nonexistent group cg = KConfigGroup(&sc, "Complex Types"); cg.writeEntry("rectEntry", RECTENTRY); cg.writeEntry("pointEntry", POINTENTRY); cg.writeEntry("sizeEntry", SIZEENTRY); cg.writeEntry("dateTimeEntry", DATETIMEENTRY); cg.writeEntry("dateEntry", DATETIMEENTRY.date()); KConfigGroup ct = cg; cg = KConfigGroup(&ct, "Nested Group 1"); cg.writeEntry("stringentry1", STRINGENTRY1); cg = KConfigGroup(&ct, "Nested Group 2"); cg.writeEntry("stringEntry2", STRINGENTRY2); cg = KConfigGroup(&cg, "Nested Group 2.1"); cg.writeEntry("stringEntry3", STRINGENTRY3); cg = KConfigGroup(&ct, "Nested Group 3"); cg.writeEntry("stringEntry3", STRINGENTRY3); cg = KConfigGroup(&sc, "List Types"); cg.writeEntry("listOfIntsEntry1", INTLISTENTRY1); cg.writeEntry("listOfByteArraysEntry1", BYTEARRAYLISTENTRY1); cg.writeEntry("stringListEntry", STRINGLISTENTRY); cg.writeEntry("stringListEmptyEntry", STRINGLISTEMPTYENTRY); cg.writeEntry("stringListJustEmptyElement", STRINGLISTJUSTEMPTYELEMENT); cg.writeEntry("stringListEmptyTrailingElement", STRINGLISTEMPTYTRAINLINGELEMENT); cg.writeEntry("stringListEscapeOddEntry", STRINGLISTESCAPEODDENTRY); cg.writeEntry("stringListEscapeEvenEntry", STRINGLISTESCAPEEVENENTRY); cg.writeEntry("stringListEscapeCommaEntry", STRINGLISTESCAPECOMMAENTRY); cg.writeEntry("variantListEntry", VARIANTLISTENTRY); cg = KConfigGroup(&sc, "Path Type"); cg.writePathEntry("homepath", HOMEPATH); cg.writePathEntry("homepathescape", HOMEPATHESCAPE); cg = KConfigGroup(&sc, "Enum Types"); #if defined(_MSC_VER) && _MSC_VER == 1600 cg.writeEntry("dummy", 42); #else //Visual C++ 2010 throws an Internal Compiler Error here cg.writeEntry("enum-10", Tens); cg.writeEntry("enum-100", Hundreds); cg.writeEntry("flags-bit0", Flags(bit0)); cg.writeEntry("flags-bit0-bit1", Flags(bit0 | bit1)); #endif cg = KConfigGroup(&sc, "ParentGroup"); KConfigGroup cg1(&cg, "SubGroup1"); cg1.writeEntry("somestring", "somevalue"); cg.writeEntry("parentgrpstring", "somevalue"); KConfigGroup cg2(&cg, "SubGroup2"); cg2.writeEntry("substring", "somevalue"); KConfigGroup cg3(&cg, "SubGroup/3"); cg3.writeEntry("sub3string", "somevalue"); QVERIFY(sc.isDirty()); QVERIFY(sc.sync()); QVERIFY(!sc.isDirty()); QVERIFY2(QFile::exists(testConfigDir() + QStringLiteral("/kconfigtest")), qPrintable(testConfigDir() + QStringLiteral("/kconfigtest must exist"))); QVERIFY2(QFile::exists(kdeGlobalsPath()), qPrintable(kdeGlobalsPath() + QStringLiteral(" must exist"))); KConfig sc1(TEST_SUBDIR "kdebugrc", KConfig::SimpleConfig); KConfigGroup sg0(&sc1, "0"); sg0.writeEntry("AbortFatal", false); sg0.writeEntry("WarnOutput", 0); sg0.writeEntry("FatalOutput", 0); QVERIFY(sc1.sync()); //Setup stuff to test KConfig::addConfigSources() KConfig devcfg(TEST_SUBDIR "specificrc"); KConfigGroup devonlygrp(&devcfg, "Specific Only Group"); devonlygrp.writeEntry("ExistingEntry", "DevValue"); KConfigGroup devandbasegrp(&devcfg, "Shared Group"); devandbasegrp.writeEntry("SomeSharedEntry", "DevValue"); devandbasegrp.writeEntry("SomeSpecificOnlyEntry", "DevValue"); QVERIFY(devcfg.sync()); KConfig basecfg(TEST_SUBDIR "baserc"); KConfigGroup basegrp(&basecfg, "Base Only Group"); basegrp.writeEntry("ExistingEntry", "BaseValue"); KConfigGroup baseanddevgrp(&basecfg, "Shared Group"); baseanddevgrp.writeEntry("SomeSharedEntry", "BaseValue"); baseanddevgrp.writeEntry("SomeBaseOnlyEntry", "BaseValue"); QVERIFY(basecfg.sync()); KConfig gecfg(TEST_SUBDIR "groupescapetest", KConfig::SimpleConfig); cg = KConfigGroup(&gecfg, DOLLARGROUP); cg.writeEntry("entry", "doesntmatter"); } void KConfigTest::cleanupTestCase() { //ensure we don't delete the real directory QDir localConfig(testConfigDir()); //qDebug() << "Erasing" << localConfig; if (localConfig.exists()) { QVERIFY(localConfig.removeRecursively()); } QVERIFY(!localConfig.exists()); if (QFile::exists(kdeGlobalsPath())) { QVERIFY(QFile::remove(kdeGlobalsPath())); } } static QList readLinesFrom(const QString &path) { QFile file(path); const bool opened = file.open(QIODevice::ReadOnly | QIODevice::Text); QList lines; if (!opened) { QWARN(qPrintable(QLatin1String("Failed to open ") + path)); return lines; } QByteArray line; do { line = file.readLine(); if (!line.isEmpty()) { lines.append(line); } } while (!line.isEmpty()); return lines; } static QList readLines(const char *fileName = TEST_SUBDIR "kconfigtest") { const QString path = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); Q_ASSERT(!path.isEmpty()); return readLinesFrom(path + '/' + fileName); } // see also testDefaults, which tests reverting with a defaults (global) file available void KConfigTest::testDirtyAfterRevert() { KConfig sc(TEST_SUBDIR "kconfigtest_revert"); KConfigGroup cg(&sc, "Hello"); cg.revertToDefault("does_not_exist"); QVERIFY(!sc.isDirty()); cg.writeEntry("Test", "Correct"); QVERIFY(sc.isDirty()); sc.sync(); QVERIFY(!sc.isDirty()); cg.revertToDefault("Test"); QVERIFY(sc.isDirty()); QVERIFY(sc.sync()); QVERIFY(!sc.isDirty()); cg.revertToDefault("Test"); QVERIFY(!sc.isDirty()); } void KConfigTest::testRevertAllEntries() { // this tests the case were we revert (delete) all entries in a file, // leaving a blank file { KConfig sc(TEST_SUBDIR "konfigtest2", KConfig::SimpleConfig); KConfigGroup cg(&sc, "Hello"); cg.writeEntry("Test", "Correct"); } { KConfig sc(TEST_SUBDIR "konfigtest2", KConfig::SimpleConfig); KConfigGroup cg(&sc, "Hello"); QCOMPARE(cg.readEntry("Test", "Default"), QString("Correct")); cg.revertToDefault("Test"); } KConfig sc(TEST_SUBDIR "konfigtest2", KConfig::SimpleConfig); KConfigGroup cg(&sc, "Hello"); QCOMPARE(cg.readEntry("Test", "Default"), QString("Default")); } void KConfigTest::testSimple() { // kdeglobals (which was created in initTestCase) must be found this way: const QStringList kdeglobals = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("kdeglobals")); QVERIFY(!kdeglobals.isEmpty()); KConfig sc2(TEST_SUBDIR "kconfigtest"); QCOMPARE(sc2.name(), QString(TEST_SUBDIR "kconfigtest")); // make sure groupList() isn't returning something it shouldn't const QStringList lstGroup = sc2.groupList(); for (const QString &group : lstGroup) { QVERIFY(!group.isEmpty() && group != ""); QVERIFY(!group.contains(QChar(0x1d))); } KConfigGroup sc3(&sc2, "AAA"); QVERIFY(sc3.hasKey("stringEntry1")); // from kdeglobals QVERIFY(!sc3.isEntryImmutable("stringEntry1")); QCOMPARE(sc3.readEntry("stringEntry1"), QString(STRINGENTRY1)); QVERIFY(!sc3.hasKey("stringEntry2")); QCOMPARE(sc3.readEntry("stringEntry2", QString("bla")), QString("bla")); QVERIFY(!sc3.hasDefault("stringEntry1")); sc3 = KConfigGroup(&sc2, "Hello"); QCOMPARE(sc3.readEntry("Test", QByteArray()), QByteArray(UTF8BITENTRY)); QCOMPARE(sc3.readEntry("bytearrayEntry", QByteArray()), BYTEARRAYENTRY); QCOMPARE(sc3.readEntry(ESCAPEKEY), QString(ESCAPEENTRY)); QCOMPARE(sc3.readEntry("Test", QString()), QString::fromUtf8(UTF8BITENTRY)); QCOMPARE(sc3.readEntry("emptyEntry"/*, QString("Fietsbel")*/), QLatin1String("")); QCOMPARE(sc3.readEntry("emptyEntry", QString("Fietsbel")).isEmpty(), true); QCOMPARE(sc3.readEntry("stringEntry1"), QString(STRINGENTRY1)); QCOMPARE(sc3.readEntry("stringEntry2"), QString(STRINGENTRY2)); QCOMPARE(sc3.readEntry("stringEntry3"), QString(STRINGENTRY3)); QCOMPARE(sc3.readEntry("stringEntry4"), QString(STRINGENTRY4)); QVERIFY(!sc3.hasKey("stringEntry5")); QCOMPARE(sc3.readEntry("stringEntry5", QString("test")), QString("test")); QVERIFY(!sc3.hasKey("stringEntry6")); QCOMPARE(sc3.readEntry("stringEntry6", QString("foo")), QString("foo")); QCOMPARE(sc3.readEntry("urlEntry1", QUrl()), QUrl("http://qt-project.org")); QCOMPARE(sc3.readEntry("boolEntry1", BOOLENTRY1), BOOLENTRY1); QCOMPARE(sc3.readEntry("boolEntry2", false), BOOLENTRY2); QCOMPARE(sc3.readEntry("keywith=equalsign", QString("wrong")), QString(STRINGENTRY1)); QCOMPARE(sc3.readEntry("byteArrayEntry1", QByteArray()), QByteArray(STRINGENTRY1)); QCOMPARE(sc3.readEntry("doubleEntry1", 0.0), DOUBLEENTRY); QCOMPARE(sc3.readEntry("floatEntry1", 0.0f), FLOATENTRY); } void KConfigTest::testDefaults() { KConfig config(TEST_SUBDIR "defaulttest", KConfig::NoGlobals); const QString defaultsFile = TEST_SUBDIR "defaulttest.defaults"; KConfig defaults(defaultsFile, KConfig::SimpleConfig); const QString Default(QStringLiteral("Default")); const QString NotDefault(QStringLiteral("Not Default")); const QString Value1(STRINGENTRY1); const QString Value2(STRINGENTRY2); KConfigGroup group = defaults.group("any group"); group.writeEntry("entry1", Default); QVERIFY(group.sync()); group = config.group("any group"); group.writeEntry("entry1", Value1); group.writeEntry("entry2", Value2); QVERIFY(group.sync()); config.addConfigSources(QStringList() << QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + '/' + defaultsFile); config.setReadDefaults(true); QCOMPARE(group.readEntry("entry1", QString()), Default); QCOMPARE(group.readEntry("entry2", NotDefault), NotDefault); // no default for entry2 config.setReadDefaults(false); QCOMPARE(group.readEntry("entry1", Default), Value1); QCOMPARE(group.readEntry("entry2", NotDefault), Value2); group.revertToDefault("entry1"); QCOMPARE(group.readEntry("entry1", QString()), Default); group.revertToDefault("entry2"); QCOMPARE(group.readEntry("entry2", QString()), QString()); // TODO test reverting localized entries Q_ASSERT(config.isDirty()); group.sync(); // Check that everything is OK on disk, too KConfig reader(TEST_SUBDIR "defaulttest", KConfig::NoGlobals); reader.addConfigSources(QStringList() << QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + '/' + defaultsFile); KConfigGroup readerGroup = reader.group("any group"); QCOMPARE(readerGroup.readEntry("entry1", QString()), Default); QCOMPARE(readerGroup.readEntry("entry2", QString()), QString()); } void KConfigTest::testLocale() { KConfig config(TEST_SUBDIR "kconfigtest.locales", KConfig::SimpleConfig); const QString Translated(TRANSLATEDSTRINGENTRY1); const QString Untranslated(STRINGENTRY1); KConfigGroup group = config.group("Hello"); group.writeEntry("stringEntry1", Untranslated); config.setLocale(QStringLiteral("fr")); group.writeEntry("stringEntry1", Translated, KConfig::Localized | KConfig::Persistent); QVERIFY(config.sync()); QCOMPARE(group.readEntry("stringEntry1", QString()), Translated); QCOMPARE(group.readEntryUntranslated("stringEntry1"), Untranslated); config.setLocale(QStringLiteral("C")); // strings written in the "C" locale are written as nonlocalized group.writeEntry("stringEntry1", Untranslated, KConfig::Localized | KConfig::Persistent); QVERIFY(config.sync()); QCOMPARE(group.readEntry("stringEntry1", QString()), Untranslated); } void KConfigTest::testEncoding() { QString groupstr = QString::fromUtf8("UTF-8:\xc3\xb6l"); KConfig c(TEST_SUBDIR "kconfigtestencodings"); KConfigGroup cg(&c, groupstr); cg.writeEntry("key", "value"); QVERIFY(c.sync()); QList lines = readLines(TEST_SUBDIR "kconfigtestencodings"); QCOMPARE(lines.count(), 2); QCOMPARE(lines.first(), QByteArray("[UTF-8:\xc3\xb6l]\n")); KConfig c2(TEST_SUBDIR "kconfigtestencodings"); KConfigGroup cg2(&c2, groupstr); QVERIFY(cg2.readEntry("key") == QByteArray("value")); QVERIFY(c2.groupList().contains(groupstr)); } void KConfigTest::testLists() { KConfig sc2(TEST_SUBDIR "kconfigtest"); KConfigGroup sc3(&sc2, "List Types"); QCOMPARE(sc3.readEntry(QString("stringListEntry"), QStringList()), STRINGLISTENTRY); QCOMPARE(sc3.readEntry(QString("stringListEmptyEntry"), QStringList("wrong")), STRINGLISTEMPTYENTRY); QCOMPARE(sc3.readEntry(QString("stringListJustEmptyElement"), QStringList()), STRINGLISTJUSTEMPTYELEMENT); QCOMPARE(sc3.readEntry(QString("stringListEmptyTrailingElement"), QStringList()), STRINGLISTEMPTYTRAINLINGELEMENT); QCOMPARE(sc3.readEntry(QString("stringListEscapeOddEntry"), QStringList()), STRINGLISTESCAPEODDENTRY); QCOMPARE(sc3.readEntry(QString("stringListEscapeEvenEntry"), QStringList()), STRINGLISTESCAPEEVENENTRY); QCOMPARE(sc3.readEntry(QString("stringListEscapeCommaEntry"), QStringList()), STRINGLISTESCAPECOMMAENTRY); QCOMPARE(sc3.readEntry("listOfIntsEntry1"), QString::fromLatin1("1,2,3,4")); QList expectedIntList = INTLISTENTRY1; QVERIFY(sc3.readEntry("listOfIntsEntry1", QList()) == expectedIntList); QCOMPARE(QVariant(sc3.readEntry("variantListEntry", VARIANTLISTENTRY)).toStringList(), QVariant(VARIANTLISTENTRY).toStringList()); QCOMPARE(sc3.readEntry("listOfByteArraysEntry1", QList()), BYTEARRAYLISTENTRY1); } void KConfigTest::testPath() { KConfig sc2(TEST_SUBDIR "kconfigtest"); KConfigGroup sc3(&sc2, "Path Type"); QCOMPARE(sc3.readPathEntry("homepath", QString()), HOMEPATH); QCOMPARE(sc3.readPathEntry("homepathescape", QString()), HOMEPATHESCAPE); QCOMPARE(sc3.entryMap()["homepath"], HOMEPATH); qputenv("WITHSLASH", "/a/"); { QFile file(testConfigDir() + "/pathtest"); file.open(QIODevice::WriteOnly | QIODevice::Text); QTextStream out(&file); out.setCodec("UTF-8"); out << "[Test Group]" << endl << "homePath=$HOME/foo" << endl << "homePath2=file://$HOME/foo" << endl << "withSlash=$WITHSLASH/foo" << endl << "withSlash2=$WITHSLASH" << endl << "withBraces[$e]=file://${HOME}/foo" << endl << "URL[$e]=file://${HOME}/foo" << endl << "hostname[$e]=$(hostname)" << endl << "escapes=aaa,bb/b,ccc\\,ccc" << endl << "noeol=foo" // no EOL ; } KConfig cf2(TEST_SUBDIR "pathtest"); KConfigGroup group = cf2.group("Test Group"); QVERIFY(group.hasKey("homePath")); QCOMPARE(group.readPathEntry("homePath", QString()), HOMEPATH); QVERIFY(group.hasKey("homePath2")); QCOMPARE(group.readPathEntry("homePath2", QString()), QString("file://" + HOMEPATH)); QVERIFY(group.hasKey("withSlash")); QCOMPARE(group.readPathEntry("withSlash", QString()), QStringLiteral("/a//foo")); QVERIFY(group.hasKey("withSlash2")); QCOMPARE(group.readPathEntry("withSlash2", QString()), QStringLiteral("/a/")); QVERIFY(group.hasKey("withBraces")); QCOMPARE(group.readPathEntry("withBraces", QString()), QString("file://" + HOMEPATH)); QVERIFY(group.hasKey("URL")); QCOMPARE(group.readEntry("URL", QString()), QString("file://" + HOMEPATH)); -#if !defined(Q_OS_WIN32) && !defined(Q_OS_MAC) - // I don't know if this will work on windows - // This test hangs on OS X QVERIFY(group.hasKey("hostname")); - char hostname[256]; - QVERIFY(::gethostname(hostname, sizeof(hostname)) == 0); - QCOMPARE(group.readEntry("hostname", QString()), QString::fromLatin1(hostname)); -#endif + QCOMPARE(group.readEntry("hostname", QString()), QStringLiteral("(hostname)")); // the $ got removed because empty var name QVERIFY(group.hasKey("noeol")); QCOMPARE(group.readEntry("noeol", QString()), QString("foo")); const auto val = QStringList { QStringLiteral("aaa"), QStringLiteral("bb/b"), QStringLiteral("ccc,ccc")}; QCOMPARE(group.readPathEntry("escapes", QStringList()), val); } void KConfigTest::testPersistenceOfExpandFlagForPath() { // This test checks that a path entry starting with $HOME is still flagged // with the expand flag after the config was altered without rewriting the // path entry. // 1st step: Open the config, add a new dummy entry and then sync the config // back to the storage. { KConfig sc2(TEST_SUBDIR "kconfigtest"); KConfigGroup sc3(&sc2, "Path Type"); sc3.writeEntry("dummy", "dummy"); QVERIFY(sc2.sync()); } // 2nd step: Call testPath() again. Rewriting the config must not break // the testPath() test. testPath(); } void KConfigTest::testPathQtHome() { { QFile file(testConfigDir() + "/pathtest"); file.open(QIODevice::WriteOnly | QIODevice::Text); QTextStream out(&file); out.setCodec("UTF-8"); out << "[Test Group]" << endl << "dataDir[$e]=$QT_DATA_HOME/kconfigtest" << endl << "cacheDir[$e]=$QT_CACHE_HOME/kconfigtest" << endl << "configDir[$e]=$QT_CONFIG_HOME/kconfigtest" << endl; } KConfig cf2(TEST_SUBDIR "pathtest"); KConfigGroup group = cf2.group("Test Group"); qunsetenv("QT_DATA_HOME"); qunsetenv("QT_CACHE_HOME"); qunsetenv("QT_CONFIG_HOME"); QVERIFY(group.hasKey("dataDir")); QCOMPARE(group.readEntry("dataDir", QString()), QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation).append(QStringLiteral("/kconfigtest"))); QVERIFY(group.hasKey("cacheDir")); QCOMPARE(group.readEntry("cacheDir", QString()), QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation).append(QStringLiteral("/kconfigtest"))); QVERIFY(group.hasKey("configDir")); QCOMPARE(group.readEntry("configDir", QString()), QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation).append(QStringLiteral("/kconfigtest"))); qputenv("QT_DATA_HOME","/1"); qputenv("QT_CACHE_HOME","/2"); qputenv("QT_CONFIG_HOME","/3"); QVERIFY(group.hasKey("dataDir")); QCOMPARE(group.readEntry("dataDir", QString()), QStringLiteral("/1/kconfigtest")); QVERIFY(group.hasKey("cacheDir")); QCOMPARE(group.readEntry("cacheDir", QString()), QStringLiteral("/2/kconfigtest")); QVERIFY(group.hasKey("configDir")); QCOMPARE(group.readEntry("configDir", QString()), QStringLiteral("/3/kconfigtest")); } void KConfigTest::testComplex() { KConfig sc2(TEST_SUBDIR "kconfigtest"); KConfigGroup sc3(&sc2, "Complex Types"); QCOMPARE(sc3.readEntry("pointEntry", QPoint()), POINTENTRY); QCOMPARE(sc3.readEntry("sizeEntry", SIZEENTRY), SIZEENTRY); QCOMPARE(sc3.readEntry("rectEntry", QRect(1, 2, 3, 4)), RECTENTRY); QCOMPARE(sc3.readEntry("dateTimeEntry", QDateTime()).toString(Qt::ISODate), DATETIMEENTRY.toString(Qt::ISODate)); QCOMPARE(sc3.readEntry("dateEntry", QDate()).toString(Qt::ISODate), DATETIMEENTRY.date().toString(Qt::ISODate)); QCOMPARE(sc3.readEntry("dateTimeEntry", QDate()), DATETIMEENTRY.date()); } void KConfigTest::testEnums() { //Visual C++ 2010 (compiler version 16.0) throws an Internal Compiler Error //when compiling the code in initTestCase that creates these KConfig entries, //so we can't run this test #if defined(_MSC_VER) && _MSC_VER == 1600 QSKIP("Visual C++ 2010 can't compile this test"); #endif KConfig sc(TEST_SUBDIR "kconfigtest"); KConfigGroup sc3(&sc, "Enum Types"); QCOMPARE(sc3.readEntry("enum-10"), QString("Tens")); QVERIFY(sc3.readEntry("enum-100", Ones) != Ones); QVERIFY(sc3.readEntry("enum-100", Ones) != Tens); QCOMPARE(sc3.readEntry("flags-bit0"), QString("bit0")); QVERIFY(sc3.readEntry("flags-bit0", Flags()) == bit0); int eid = staticMetaObject.indexOfEnumerator("Flags"); QVERIFY(eid != -1); QMetaEnum me = staticMetaObject.enumerator(eid); Flags bitfield = bit0 | bit1; QCOMPARE(sc3.readEntry("flags-bit0-bit1"), QString(me.valueToKeys(bitfield))); QVERIFY(sc3.readEntry("flags-bit0-bit1", Flags()) == bitfield); } void KConfigTest::testEntryMap() { KConfig sc(TEST_SUBDIR "kconfigtest"); KConfigGroup cg(&sc, "Hello"); QMap entryMap = cg.entryMap(); qDebug() << entryMap.keys(); QCOMPARE(entryMap.value("stringEntry1"), QString(STRINGENTRY1)); QCOMPARE(entryMap.value("stringEntry2"), QString(STRINGENTRY2)); QCOMPARE(entryMap.value("stringEntry3"), QString(STRINGENTRY3)); QCOMPARE(entryMap.value("stringEntry4"), QString(STRINGENTRY4)); QVERIFY(!entryMap.contains("stringEntry5")); QVERIFY(!entryMap.contains("stringEntry6")); QCOMPARE(entryMap.value("Test"), QString::fromUtf8(UTF8BITENTRY)); QCOMPARE(entryMap.value("bytearrayEntry"), QString::fromUtf8(BYTEARRAYENTRY.constData())); QCOMPARE(entryMap.value("emptyEntry"), QString()); QVERIFY(entryMap.contains("emptyEntry")); QCOMPARE(entryMap.value("boolEntry1"), QString(BOOLENTRY1 ? "true" : "false")); QCOMPARE(entryMap.value("boolEntry2"), QString(BOOLENTRY2 ? "true" : "false")); QCOMPARE(entryMap.value("keywith=equalsign"), QString(STRINGENTRY1)); QCOMPARE(entryMap.value("byteArrayEntry1"), QString(STRINGENTRY1)); QCOMPARE(entryMap.value("doubleEntry1").toDouble(), DOUBLEENTRY); QCOMPARE(entryMap.value("floatEntry1").toFloat(), FLOATENTRY); } void KConfigTest::testInvalid() { KConfig sc(TEST_SUBDIR "kconfigtest"); // all of these should print a message to the kdebug.dbg file KConfigGroup sc3(&sc, "Invalid Types"); sc3.writeEntry("badList", VARIANTLISTENTRY2); QList list; // 1 element list list << 1; sc3.writeEntry(QStringLiteral("badList"), list); QVERIFY(sc3.readEntry("badList", QPoint()) == QPoint()); QVERIFY(sc3.readEntry("badList", QRect()) == QRect()); QVERIFY(sc3.readEntry("badList", QSize()) == QSize()); QVERIFY(sc3.readEntry("badList", QDate()) == QDate()); QVERIFY(sc3.readEntry("badList", QDateTime()) == QDateTime()); // 2 element list list << 2; sc3.writeEntry("badList", list); QVERIFY(sc3.readEntry("badList", QRect()) == QRect()); QVERIFY(sc3.readEntry("badList", QDate()) == QDate()); QVERIFY(sc3.readEntry("badList", QDateTime()) == QDateTime()); // 3 element list list << 303; sc3.writeEntry("badList", list); QVERIFY(sc3.readEntry("badList", QPoint()) == QPoint()); QVERIFY(sc3.readEntry("badList", QRect()) == QRect()); QVERIFY(sc3.readEntry("badList", QSize()) == QSize()); QVERIFY(sc3.readEntry("badList", QDate()) == QDate()); // out of bounds QVERIFY(sc3.readEntry("badList", QDateTime()) == QDateTime()); // 4 element list list << 4; sc3.writeEntry("badList", list); QVERIFY(sc3.readEntry("badList", QPoint()) == QPoint()); QVERIFY(sc3.readEntry("badList", QSize()) == QSize()); QVERIFY(sc3.readEntry("badList", QDate()) == QDate()); QVERIFY(sc3.readEntry("badList", QDateTime()) == QDateTime()); // 5 element list list[2] = 3; list << 5; sc3.writeEntry("badList", list); QVERIFY(sc3.readEntry("badList", QPoint()) == QPoint()); QVERIFY(sc3.readEntry("badList", QRect()) == QRect()); QVERIFY(sc3.readEntry("badList", QSize()) == QSize()); QVERIFY(sc3.readEntry("badList", QDate()) == QDate()); QVERIFY(sc3.readEntry("badList", QDateTime()) == QDateTime()); // 6 element list list << 6; sc3.writeEntry("badList", list); QVERIFY(sc3.readEntry("badList", QPoint()) == QPoint()); QVERIFY(sc3.readEntry("badList", QRect()) == QRect()); QVERIFY(sc3.readEntry("badList", QSize()) == QSize()); } void KConfigTest::testChangeGroup() { KConfig sc(TEST_SUBDIR "kconfigtest"); KConfigGroup sc3(&sc, "Hello"); QCOMPARE(sc3.name(), QString("Hello")); KConfigGroup newGroup(sc3); #ifndef KDE_NO_DEPRECATED newGroup.changeGroup("FooBar"); // deprecated! QCOMPARE(newGroup.name(), QString("FooBar")); QCOMPARE(sc3.name(), QString("Hello")); // unchanged // Write into the "changed group" and check that it works newGroup.writeEntry("InFooBar", "FB"); QCOMPARE(KConfigGroup(&sc, "FooBar").entryMap().value("InFooBar"), QString("FB")); QCOMPARE(KConfigGroup(&sc, "Hello").entryMap().value("InFooBar"), QString()); #endif KConfigGroup rootGroup(sc.group("")); QCOMPARE(rootGroup.name(), QString("")); KConfigGroup sc32(rootGroup.group("Hello")); QCOMPARE(sc32.name(), QString("Hello")); KConfigGroup newGroup2(sc32); #ifndef KDE_NO_DEPRECATED newGroup2.changeGroup("FooBar"); // deprecated! QCOMPARE(newGroup2.name(), QString("FooBar")); QCOMPARE(sc32.name(), QString("Hello")); // unchanged #endif } // Simple test for deleteEntry void KConfigTest::testDeleteEntry() { const char *configFile = TEST_SUBDIR "kconfigdeletetest"; { KConfig conf(configFile); conf.group("Hello").writeEntry("DelKey", "ToBeDeleted"); } const QList lines = readLines(configFile); Q_ASSERT(lines.contains("[Hello]\n")); Q_ASSERT(lines.contains("DelKey=ToBeDeleted\n")); KConfig sc(configFile); KConfigGroup group(&sc, "Hello"); group.deleteEntry("DelKey"); QCOMPARE(group.readEntry("DelKey", QString("Fietsbel")), QString("Fietsbel")); QVERIFY(group.sync()); Q_ASSERT(!readLines(configFile).contains("DelKey=ToBeDeleted\n")); QCOMPARE(group.readEntry("DelKey", QString("still deleted")), QString("still deleted")); } void KConfigTest::testDelete() { KConfig sc(TEST_SUBDIR "kconfigtest"); KConfigGroup ct(&sc, "Complex Types"); // First delete a nested group KConfigGroup delgr(&ct, "Nested Group 3"); QVERIFY(delgr.exists()); QVERIFY(ct.hasGroup("Nested Group 3")); delgr.deleteGroup(); QVERIFY(!delgr.exists()); QVERIFY(!ct.hasGroup("Nested Group 3")); QVERIFY(ct.groupList().contains("Nested Group 3")); KConfigGroup ng(&ct, "Nested Group 2"); QVERIFY(sc.hasGroup("Complex Types")); QVERIFY(!sc.hasGroup("Does not exist")); sc.deleteGroup("Complex Types"); QCOMPARE(sc.group("Complex Types").keyList().count(), 0); QVERIFY(!sc.hasGroup("Complex Types")); // #192266 QVERIFY(!sc.group("Complex Types").exists()); QVERIFY(!ct.hasGroup("Nested Group 1")); QCOMPARE(ct.group("Nested Group 1").keyList().count(), 0); QCOMPARE(ct.group("Nested Group 2").keyList().count(), 0); QCOMPARE(ng.group("Nested Group 2.1").keyList().count(), 0); KConfigGroup cg(&sc, "AAA"); cg.deleteGroup(); QVERIFY(sc.entryMap("Complex Types").isEmpty()); QVERIFY(sc.entryMap("AAA").isEmpty()); QVERIFY(!sc.entryMap("Hello").isEmpty()); //not deleted group QVERIFY(sc.entryMap("FooBar").isEmpty()); //inexistant group QVERIFY(cg.sync()); // Check what happens on disk const QList lines = readLines(); //qDebug() << lines; QVERIFY(!lines.contains("[Complex Types]\n")); QVERIFY(!lines.contains("[Complex Types][Nested Group 1]\n")); QVERIFY(!lines.contains("[Complex Types][Nested Group 2]\n")); QVERIFY(!lines.contains("[Complex Types][Nested Group 2.1]\n")); QVERIFY(!lines.contains("[AAA]\n")); QVERIFY(lines.contains("[Hello]\n")); // a group that was not deleted // test for entries that are marked as deleted when there is no default KConfig cf(TEST_SUBDIR "kconfigtest", KConfig::SimpleConfig); // make sure there are no defaults cg = cf.group("Portable Devices"); cg.writeEntry("devices|manual|(null)", "whatever"); cg.writeEntry("devices|manual|/mnt/ipod", "/mnt/ipod"); QVERIFY(cf.sync()); int count = 0; const QList listLines = readLines(); for (const QByteArray &item : listLines) if (item.startsWith("devices|")) { // krazy:exclude=strings count++; } QCOMPARE(count, 2); cg.deleteEntry("devices|manual|/mnt/ipod"); QVERIFY(cf.sync()); const QList listLines2 = readLines(); for (const QByteArray &item : listLines2) { QVERIFY(!item.contains("ipod")); } } void KConfigTest::testDefaultGroup() { KConfig sc(TEST_SUBDIR "kconfigtest"); KConfigGroup defaultGroup(&sc, ""); QCOMPARE(defaultGroup.name(), QString("")); QVERIFY(!defaultGroup.exists()); defaultGroup.writeEntry("TestKey", "defaultGroup"); QVERIFY(defaultGroup.exists()); QCOMPARE(defaultGroup.readEntry("TestKey", QString()), QString("defaultGroup")); QVERIFY(sc.sync()); { // Test reading it KConfig sc2(TEST_SUBDIR "kconfigtest"); KConfigGroup defaultGroup2(&sc2, ""); QCOMPARE(defaultGroup2.name(), QString("")); QVERIFY(defaultGroup2.exists()); QCOMPARE(defaultGroup2.readEntry("TestKey", QString()), QString("defaultGroup")); } { // Test reading it KConfig sc2(TEST_SUBDIR "kconfigtest"); KConfigGroup emptyGroup(&sc2, ""); QCOMPARE(emptyGroup.name(), QString("")); QVERIFY(emptyGroup.exists()); QCOMPARE(emptyGroup.readEntry("TestKey", QString()), QString("defaultGroup")); } QList lines = readLines(); QVERIFY(!lines.contains("[]\n")); QCOMPARE(lines.first(), QByteArray("TestKey=defaultGroup\n")); // Now that the group exists make sure it isn't returned from groupList() const QStringList groupList = sc.groupList(); for (const QString &group : groupList) { QVERIFY(!group.isEmpty() && group != ""); } defaultGroup.deleteGroup(); QVERIFY(sc.sync()); // Test if deleteGroup worked lines = readLines(); QVERIFY(lines.first() != QByteArray("TestKey=defaultGroup\n")); } void KConfigTest::testEmptyGroup() { KConfig sc(TEST_SUBDIR "kconfigtest"); KConfigGroup emptyGroup(&sc, ""); QCOMPARE(emptyGroup.name(), QString("")); // confusing, heh? QVERIFY(!emptyGroup.exists()); emptyGroup.writeEntry("TestKey", "emptyGroup"); QVERIFY(emptyGroup.exists()); QCOMPARE(emptyGroup.readEntry("TestKey", QString()), QString("emptyGroup")); QVERIFY(sc.sync()); { // Test reading it KConfig sc2(TEST_SUBDIR "kconfigtest"); KConfigGroup defaultGroup(&sc2, ""); QCOMPARE(defaultGroup.name(), QString("")); QVERIFY(defaultGroup.exists()); QCOMPARE(defaultGroup.readEntry("TestKey", QString()), QString("emptyGroup")); } { // Test reading it KConfig sc2(TEST_SUBDIR "kconfigtest"); KConfigGroup emptyGroup2(&sc2, ""); QCOMPARE(emptyGroup2.name(), QString("")); QVERIFY(emptyGroup2.exists()); QCOMPARE(emptyGroup2.readEntry("TestKey", QString()), QString("emptyGroup")); } QList lines = readLines(); QVERIFY(!lines.contains("[]\n")); // there's no support for the [] group, in fact. QCOMPARE(lines.first(), QByteArray("TestKey=emptyGroup\n")); // Now that the group exists make sure it isn't returned from groupList() const QStringList groupList = sc.groupList(); for (const QString &group : groupList) { QVERIFY(!group.isEmpty() && group != ""); } emptyGroup.deleteGroup(); QVERIFY(sc.sync()); // Test if deleteGroup worked lines = readLines(); QVERIFY(lines.first() != QByteArray("TestKey=defaultGroup\n")); } #if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) && !defined(Q_OS_BLACKBERRY) && !defined(Q_OS_ANDROID) #define Q_XDG_PLATFORM #endif void KConfigTest::testCascadingWithLocale() { // This test relies on XDG_CONFIG_DIRS, which only has effect on Unix. // Cascading (more than two levels) isn't available at all on Windows. #ifdef Q_XDG_PLATFORM QTemporaryDir middleDir; QTemporaryDir globalDir; qputenv("XDG_CONFIG_DIRS", qPrintable(middleDir.path() + QString(":") + globalDir.path())); const QString globalConfigDir = globalDir.path() + "/" TEST_SUBDIR; QVERIFY(QDir().mkpath(globalConfigDir)); QFile global(globalConfigDir + "foo.desktop"); QVERIFY(global.open(QIODevice::WriteOnly | QIODevice::Text)); QTextStream globalOut(&global); globalOut << "[Group]" << endl << "FromGlobal=true" << endl << "FromGlobal[fr]=vrai" << endl << "Name=Testing" << endl << "Name[fr]=FR" << endl << "Other=Global" << endl << "Other[fr]=Global_FR" << endl; global.close(); const QString middleConfigDir = middleDir.path() + "/" TEST_SUBDIR; QVERIFY(QDir().mkpath(middleConfigDir)); QFile local(middleConfigDir + "foo.desktop"); QVERIFY(local.open(QIODevice::WriteOnly | QIODevice::Text)); QTextStream out(&local); out << "[Group]" << endl << "FromLocal=true" << endl << "FromLocal[fr]=vrai" << endl << "Name=Local Testing" << endl << "Name[fr]=FR" << endl << "Other=English Only" << endl; local.close(); KConfig config(TEST_SUBDIR "foo.desktop"); KConfigGroup group = config.group("Group"); QCOMPARE(group.readEntry("FromGlobal"), QString("true")); QCOMPARE(group.readEntry("FromLocal"), QString("true")); QCOMPARE(group.readEntry("Name"), QString("Local Testing")); config.setLocale(QStringLiteral("fr")); QCOMPARE(group.readEntry("FromGlobal"), QString("vrai")); QCOMPARE(group.readEntry("FromLocal"), QString("vrai")); QCOMPARE(group.readEntry("Name"), QString("FR")); QCOMPARE(group.readEntry("Other"), QString("English Only")); // Global_FR is locally overriden #endif } void KConfigTest::testMerge() { DefaultLocale defaultLocale; QLocale::setDefault(QLocale::c()); KConfig config(TEST_SUBDIR "mergetest", KConfig::SimpleConfig); KConfigGroup cg = config.group("some group"); cg.writeEntry("entry", " random entry"); cg.writeEntry("another entry", "blah blah blah"); { // simulate writing by another process QFile file(testConfigDir() + "/mergetest"); file.open(QIODevice::WriteOnly | QIODevice::Text); QTextStream out(&file); out.setCodec("UTF-8"); out << "[Merged Group]" << endl << "entry1=Testing" << endl << "entry2=More Testing" << endl << "[some group]" << endl << "entry[fr]=French" << endl << "entry[es]=Spanish" << endl << "entry[de]=German" << endl; } QVERIFY(config.sync()); { QList lines; // this is what the file should look like lines << "[Merged Group]\n" << "entry1=Testing\n" << "entry2=More Testing\n" << "\n" << "[some group]\n" << "another entry=blah blah blah\n" << "entry=\\srandom entry\n" << "entry[de]=German\n" << "entry[es]=Spanish\n" << "entry[fr]=French\n"; QFile file(testConfigDir() + "/mergetest"); file.open(QIODevice::ReadOnly | QIODevice::Text); for (const QByteArray &line : qAsConst(lines)) { QCOMPARE(line, file.readLine()); } } } void KConfigTest::testImmutable() { { QFile file(testConfigDir() + "/immutabletest"); file.open(QIODevice::WriteOnly | QIODevice::Text); QTextStream out(&file); out.setCodec("UTF-8"); out << "[$i]" << endl << "entry1=Testing" << endl << "[group][$i]" << endl << "[group][subgroup][$i]" << endl; } KConfig config(TEST_SUBDIR "immutabletest", KConfig::SimpleConfig); QVERIFY(config.isGroupImmutable(QByteArray())); KConfigGroup cg = config.group(QByteArray()); QVERIFY(cg.isEntryImmutable("entry1")); KConfigGroup cg1 = config.group("group"); QVERIFY(cg1.isImmutable()); KConfigGroup cg1a = cg.group("group"); QVERIFY(cg1a.isImmutable()); KConfigGroup cg2 = cg1.group("subgroup"); QVERIFY(cg2.isImmutable()); } void KConfigTest::testOptionOrder() { { QFile file(testConfigDir() + "/doubleattrtest"); file.open(QIODevice::WriteOnly | QIODevice::Text); QTextStream out(&file); out.setCodec("UTF-8"); out << "[group3]" << endl << "entry2=unlocalized" << endl << "entry2[$i][de_DE]=t2" << endl; } KConfig config(TEST_SUBDIR "doubleattrtest", KConfig::SimpleConfig); config.setLocale(QStringLiteral("de_DE")); KConfigGroup cg3 = config.group("group3"); QVERIFY(!cg3.isImmutable()); QCOMPARE(cg3.readEntry("entry2", ""), QString("t2")); QVERIFY(cg3.isEntryImmutable("entry2")); config.setLocale(QStringLiteral("C")); QCOMPARE(cg3.readEntry("entry2", ""), QString("unlocalized")); QVERIFY(!cg3.isEntryImmutable("entry2")); cg3.writeEntry("entry2", "modified"); QVERIFY(config.sync()); { QList lines; // this is what the file should look like lines << "[group3]\n" << "entry2=modified\n" << "entry2[de_DE][$i]=t2\n"; QFile file(testConfigDir() + "/doubleattrtest"); file.open(QIODevice::ReadOnly | QIODevice::Text); for (const QByteArray &line : qAsConst(lines)) { QCOMPARE(line, file.readLine()); } } } void KConfigTest::testGroupEscape() { KConfig config(TEST_SUBDIR "groupescapetest", KConfig::SimpleConfig); QVERIFY(config.group(DOLLARGROUP).exists()); } void KConfigTest::testSubGroup() { KConfig sc(TEST_SUBDIR "kconfigtest"); KConfigGroup cg(&sc, "ParentGroup"); QCOMPARE(cg.readEntry("parentgrpstring", ""), QString("somevalue")); KConfigGroup subcg1(&cg, "SubGroup1"); QCOMPARE(subcg1.name(), QString("SubGroup1")); QCOMPARE(subcg1.readEntry("somestring", ""), QString("somevalue")); KConfigGroup subcg2(&cg, "SubGroup2"); QCOMPARE(subcg2.name(), QString("SubGroup2")); QCOMPARE(subcg2.readEntry("substring", ""), QString("somevalue")); KConfigGroup subcg3(&cg, "SubGroup/3"); QCOMPARE(subcg3.readEntry("sub3string", ""), QString("somevalue")); QCOMPARE(subcg3.name(), QString("SubGroup/3")); KConfigGroup rcg(&sc, ""); KConfigGroup srcg(&rcg, "ParentGroup"); QCOMPARE(srcg.readEntry("parentgrpstring", ""), QString("somevalue")); QStringList groupList = cg.groupList(); groupList.sort(); // comes from QSet, so order is undefined QCOMPARE(groupList, (QStringList() << "SubGroup/3" << "SubGroup1" << "SubGroup2")); const QStringList expectedSubgroup3Keys = (QStringList() << QStringLiteral("sub3string")); QCOMPARE(subcg3.keyList(), expectedSubgroup3Keys); const QStringList expectedParentGroupKeys(QStringList() << QStringLiteral("parentgrpstring")); QCOMPARE(cg.keyList(), expectedParentGroupKeys); QCOMPARE(QStringList(cg.entryMap().keys()), expectedParentGroupKeys); QCOMPARE(QStringList(subcg3.entryMap().keys()), expectedSubgroup3Keys); // Create A group containing only other groups. We want to make sure it // shows up in groupList of sc KConfigGroup neg(&sc, "NoEntryGroup"); KConfigGroup negsub1(&neg, "NEG Child1"); negsub1.writeEntry("entry", "somevalue"); KConfigGroup negsub2(&neg, "NEG Child2"); KConfigGroup negsub3(&neg, "NEG Child3"); KConfigGroup negsub31(&negsub3, "NEG Child3-1"); KConfigGroup negsub4(&neg, "NEG Child4"); KConfigGroup negsub41(&negsub4, "NEG Child4-1"); negsub41.writeEntry("entry", "somevalue"); // A group exists if it has content QVERIFY(negsub1.exists()); // But it doesn't exist if it has no content // Ossi and David say: this is how it's supposed to work. // However you could add a dummy entry for now, or we could add a "Persist" feature to kconfig groups // which would make it written out, much like "immutable" already makes them persistent. QVERIFY(!negsub2.exists()); // A subgroup does not qualify as content if it is also empty QVERIFY(!negsub3.exists()); // A subgroup with content is ok QVERIFY(negsub4.exists()); // Only subgroups with content show up in groupList() //QEXPECT_FAIL("", "Empty subgroups do not show up in groupList()", Continue); //QCOMPARE(neg.groupList(), QStringList() << "NEG Child1" << "NEG Child2" << "NEG Child3" << "NEG Child4"); // This is what happens QStringList groups = neg.groupList(); groups.sort(); // Qt5 made the ordering unreliable, due to QHash QCOMPARE(groups, QStringList() << "NEG Child1" << "NEG Child4"); // make sure groupList() isn't returning something it shouldn't const QStringList listGroup = sc.groupList(); for (const QString &group : listGroup) { QVERIFY(!group.isEmpty() && group != ""); QVERIFY(!group.contains(QChar(0x1d))); QVERIFY(!group.contains("subgroup")); QVERIFY(!group.contains("SubGroup")); } QVERIFY(sc.sync()); // Check that the empty groups are not written out. const QList lines = readLines(); QVERIFY(lines.contains("[NoEntryGroup][NEG Child1]\n")); QVERIFY(!lines.contains("[NoEntryGroup][NEG Child2]\n")); QVERIFY(!lines.contains("[NoEntryGroup][NEG Child3]\n")); QVERIFY(!lines.contains("[NoEntryGroup][NEG Child4]\n")); // implicit group, not written out QVERIFY(lines.contains("[NoEntryGroup][NEG Child4][NEG Child4-1]\n")); } void KConfigTest::testAddConfigSources() { KConfig cf(TEST_SUBDIR "specificrc"); cf.addConfigSources(QStringList() << testConfigDir() + "/baserc"); cf.reparseConfiguration(); KConfigGroup specificgrp(&cf, "Specific Only Group"); QCOMPARE(specificgrp.readEntry("ExistingEntry", ""), QString("DevValue")); KConfigGroup sharedgrp(&cf, "Shared Group"); QCOMPARE(sharedgrp.readEntry("SomeSpecificOnlyEntry", ""), QString("DevValue")); QCOMPARE(sharedgrp.readEntry("SomeBaseOnlyEntry", ""), QString("BaseValue")); QCOMPARE(sharedgrp.readEntry("SomeSharedEntry", ""), QString("DevValue")); KConfigGroup basegrp(&cf, "Base Only Group"); QCOMPARE(basegrp.readEntry("ExistingEntry", ""), QString("BaseValue")); basegrp.writeEntry("New Entry Base Only", "SomeValue"); KConfigGroup newgrp(&cf, "New Group"); newgrp.writeEntry("New Entry", "SomeValue"); QVERIFY(cf.sync()); KConfig plaincfg(TEST_SUBDIR "specificrc"); KConfigGroup newgrp2(&plaincfg, "New Group"); QCOMPARE(newgrp2.readEntry("New Entry", ""), QString("SomeValue")); KConfigGroup basegrp2(&plaincfg, "Base Only Group"); QCOMPARE(basegrp2.readEntry("New Entry Base Only", ""), QString("SomeValue")); } void KConfigTest::testGroupCopyTo() { KConfig cf1(TEST_SUBDIR "kconfigtest"); KConfigGroup original = cf1.group("Enum Types"); KConfigGroup copy = cf1.group("Enum Types Copy"); original.copyTo(©); // copy from one group to another QCOMPARE(copy.entryMap(), original.entryMap()); KConfig cf2(TEST_SUBDIR "copy_of_kconfigtest", KConfig::SimpleConfig); QVERIFY(!cf2.hasGroup(original.name())); QVERIFY(!cf2.hasGroup(copy.name())); KConfigGroup newGroup = cf2.group(original.name()); original.copyTo(&newGroup); // copy from one file to another QVERIFY(cf2.hasGroup(original.name())); QVERIFY(!cf2.hasGroup(copy.name())); // make sure we didn't copy more than we wanted QCOMPARE(newGroup.entryMap(), original.entryMap()); } void KConfigTest::testConfigCopyToSync() { KConfig cf1(TEST_SUBDIR "kconfigtest"); // Prepare source file KConfigGroup group(&cf1, "CopyToTest"); group.writeEntry("Type", "Test"); QVERIFY(cf1.sync()); // Copy to "destination" const QString destination = testConfigDir() + "/kconfigcopytotest"; QFile::remove(destination); KConfig cf2(TEST_SUBDIR "kconfigcopytotest"); KConfigGroup group2(&cf2, "CopyToTest"); group.copyTo(&group2); QString testVal = group2.readEntry("Type"); QCOMPARE(testVal, QString("Test")); // should write to disk the copied data from group QVERIFY(cf2.sync()); QVERIFY(QFile::exists(destination)); } void KConfigTest::testConfigCopyTo() { KConfig cf1(TEST_SUBDIR "kconfigtest"); { // Prepare source file KConfigGroup group(&cf1, "CopyToTest"); group.writeEntry("Type", "Test"); QVERIFY(cf1.sync()); } { // Copy to "destination" const QString destination = testConfigDir() + "/kconfigcopytotest"; QFile::remove(destination); KConfig cf2; cf1.copyTo(destination, &cf2); KConfigGroup group2(&cf2, "CopyToTest"); QString testVal = group2.readEntry("Type"); QCOMPARE(testVal, QString("Test")); QVERIFY(cf2.sync()); QVERIFY(QFile::exists(destination)); } // Check copied config file on disk KConfig cf3(TEST_SUBDIR "kconfigcopytotest"); KConfigGroup group3(&cf3, "CopyToTest"); QString testVal = group3.readEntry("Type"); QCOMPARE(testVal, QString("Test")); } void KConfigTest::testReparent() { KConfig cf(TEST_SUBDIR "kconfigtest"); const QString name(QStringLiteral("Enum Types")); KConfigGroup group = cf.group(name); const QMap originalMap = group.entryMap(); KConfigGroup parent = cf.group("Parent Group"); QVERIFY(!parent.hasGroup(name)); QVERIFY(group.entryMap() == originalMap); group.reparent(&parent); // see if it can be made a sub-group of another group QVERIFY(parent.hasGroup(name)); QCOMPARE(group.entryMap(), originalMap); group.reparent(&cf); // see if it can make it a top-level group again // QVERIFY(!parent.hasGroup(name)); QCOMPARE(group.entryMap(), originalMap); } static void ageTimeStamp(const QString &path, int nsec) { #ifdef Q_OS_UNIX QDateTime mtime = QFileInfo(path).lastModified().addSecs(-nsec); struct utimbuf utbuf; utbuf.actime = mtime.toTime_t(); utbuf.modtime = utbuf.actime; utime(QFile::encodeName(path), &utbuf); #else QTest::qSleep(nsec * 1000); #endif } void KConfigTest::testWriteOnSync() { QDateTime oldStamp, newStamp; KConfig sc(TEST_SUBDIR "kconfigtest", KConfig::IncludeGlobals); // Age the timestamp of global config file a few sec, and collect it. QString globFile = kdeGlobalsPath(); ageTimeStamp(globFile, 2); // age 2 sec oldStamp = QFileInfo(globFile).lastModified(); // Add a local entry and sync the config. // Should not rewrite the global config file. KConfigGroup cgLocal(&sc, "Locals"); cgLocal.writeEntry("someLocalString", "whatever"); QVERIFY(sc.sync()); // Verify that the timestamp of global config file didn't change. newStamp = QFileInfo(globFile).lastModified(); QCOMPARE(newStamp, oldStamp); // Age the timestamp of local config file a few sec, and collect it. QString locFile = testConfigDir() + "/kconfigtest"; ageTimeStamp(locFile, 2); // age 2 sec oldStamp = QFileInfo(locFile).lastModified(); // Add a global entry and sync the config. // Should not rewrite the local config file. KConfigGroup cgGlobal(&sc, "Globals"); cgGlobal.writeEntry("someGlobalString", "whatever", KConfig::Persistent | KConfig::Global); QVERIFY(sc.sync()); // Verify that the timestamp of local config file didn't change. newStamp = QFileInfo(locFile).lastModified(); QCOMPARE(newStamp, oldStamp); } void KConfigTest::testFailOnReadOnlyFileSync() { KConfig sc(TEST_SUBDIR "kconfigfailonreadonlytest"); KConfigGroup cgLocal(&sc, "Locals"); cgLocal.writeEntry("someLocalString", "whatever"); QVERIFY(cgLocal.sync()); QFile f(testConfigDir() + "kconfigfailonreadonlytest"); QVERIFY(f.exists()); QVERIFY(f.setPermissions(QFileDevice::ReadOwner)); #ifndef Q_OS_WIN if (::getuid() == 0) QSKIP("Root can write to read-only files"); #endif cgLocal.writeEntry("someLocalString", "whatever2"); QVERIFY(!cgLocal.sync()); QVERIFY(f.setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner)); QVERIFY(f.remove()); } void KConfigTest::testDirtyOnEqual() { QDateTime oldStamp, newStamp; KConfig sc(TEST_SUBDIR "kconfigtest"); // Initialize value KConfigGroup cgLocal(&sc, "random"); cgLocal.writeEntry("theKey", "whatever"); QVERIFY(sc.sync()); // Age the timestamp of local config file a few sec, and collect it. QString locFile = testConfigDir() + "/kconfigtest"; ageTimeStamp(locFile, 2); // age 2 sec oldStamp = QFileInfo(locFile).lastModified(); // Write exactly the same again cgLocal.writeEntry("theKey", "whatever"); // This should be a no-op QVERIFY(sc.sync()); // Verify that the timestamp of local config file didn't change. newStamp = QFileInfo(locFile).lastModified(); QCOMPARE(newStamp, oldStamp); } void KConfigTest::testDirtyOnEqualOverdo() { QByteArray val1("\0""one", 4); QByteArray val2("\0""two", 4); QByteArray defvalr; KConfig sc(TEST_SUBDIR "kconfigtest"); KConfigGroup cgLocal(&sc, "random"); cgLocal.writeEntry("someKey", val1); QCOMPARE(cgLocal.readEntry("someKey", defvalr), val1); cgLocal.writeEntry("someKey", val2); QCOMPARE(cgLocal.readEntry("someKey", defvalr), val2); } void KConfigTest::testCreateDir() { // Test auto-creating the parent directory when needed (KConfigIniBackend::createEnclosing) QString kdehome = QDir::home().canonicalPath() + "/.kde-unit-test"; QString subdir = kdehome + "/newsubdir"; QString file = subdir + "/foo.desktop"; QFile::remove(file); QDir().rmdir(subdir); QVERIFY(!QDir().exists(subdir)); KDesktopFile desktopFile(file); desktopFile.desktopGroup().writeEntry("key", "value"); QVERIFY(desktopFile.sync()); QVERIFY(QFile::exists(file)); // Cleanup QFile::remove(file); QDir().rmdir(subdir); } void KConfigTest::testSyncOnExit() { // Often, the KGlobalPrivate global static's destructor ends up calling ~KConfig -> // KConfig::sync ... and if that code triggers KGlobal code again then things could crash. // So here's a test for modifying KSharedConfig::openConfig() and not syncing, the process exit will sync. KConfigGroup grp(KSharedConfig::openConfig(TEST_SUBDIR "syncOnExitRc"), "syncOnExit"); grp.writeEntry("key", "value"); } void KConfigTest::testSharedConfig() { // Can I use a KConfigGroup even after the KSharedConfigPtr goes out of scope? KConfigGroup myConfigGroup; { KSharedConfigPtr config = KSharedConfig::openConfig(TEST_SUBDIR "kconfigtest"); myConfigGroup = KConfigGroup(config, "Hello"); } QCOMPARE(myConfigGroup.readEntry("stringEntry1"), QString(STRINGENTRY1)); // Get the main config KSharedConfigPtr mainConfig = KSharedConfig::openConfig(); KConfigGroup mainGroup(mainConfig, "Main"); QCOMPARE(mainGroup.readEntry("Key", QString()), QString("Value")); } void KConfigTest::testLocaleConfig() { // Initialize the testdata QDir dir; QString subdir = testConfigDir(); dir.mkpath(subdir); QString file = subdir + "/localized.test"; QFile::remove(file); QFile f(file); QVERIFY(f.open(QIODevice::WriteOnly)); QTextStream ts(&f); ts << "[Test_Wrong]\n"; ts << "foo[ca]=5\n"; ts << "foostring[ca]=nice\n"; ts << "foobool[ca]=true\n"; ts << "[Test_Right]\n"; ts << "foo=5\n"; ts << "foo[ca]=5\n"; ts << "foostring=primary\n"; ts << "foostring[ca]=nice\n"; ts << "foobool=primary\n"; ts << "foobool[ca]=true\n"; f.close(); // Load the testdata QVERIFY(QFile::exists(file)); KConfig config(file); config.setLocale(QStringLiteral("ca")); // This group has only localized values. That is not supported. The values // should be dropped on loading. KConfigGroup cg(&config, "Test_Wrong"); QEXPECT_FAIL("", "The localized values are not dropped", Continue); QVERIFY(!cg.hasKey("foo")); QEXPECT_FAIL("", "The localized values are not dropped", Continue); QVERIFY(!cg.hasKey("foostring")); QEXPECT_FAIL("", "The localized values are not dropped", Continue); QVERIFY(!cg.hasKey("foobool")); // Now check the correct config group KConfigGroup cg2(&config, "Test_Right"); QCOMPARE(cg2.readEntry("foo"), QString("5")); QCOMPARE(cg2.readEntry("foo", 3), 5); QCOMPARE(cg2.readEntry("foostring"), QString("nice")); QCOMPARE(cg2.readEntry("foostring", "ugly"), QString("nice")); QCOMPARE(cg2.readEntry("foobool"), QString("true")); QCOMPARE(cg2.readEntry("foobool", false), true); // Clean up after the testcase QFile::remove(file); } void KConfigTest::testDeleteWhenLocalized() { // Initialize the testdata QDir dir; QString subdir = QDir::home().canonicalPath() + "/.kde-unit-test/"; dir.mkpath(subdir); QString file = subdir + "/localized_delete.test"; QFile::remove(file); QFile f(file); QVERIFY(f.open(QIODevice::WriteOnly)); QTextStream ts(&f); ts << "[Test4711]\n"; ts << "foo=3\n"; ts << "foo[ca]=5\n"; ts << "foo[de]=7\n"; ts << "foostring=ugly\n"; ts << "foostring[ca]=nice\n"; ts << "foostring[de]=schoen\n"; ts << "foobool=false\n"; ts << "foobool[ca]=true\n"; ts << "foobool[de]=true\n"; f.close(); // Load the testdata. We start in locale "ca". QVERIFY(QFile::exists(file)); KConfig config(file); config.setLocale(QStringLiteral("ca")); KConfigGroup cg(&config, "Test4711"); // Delete a value. Once with localized, once with Normal cg.deleteEntry("foostring", KConfigBase::Persistent | KConfigBase::Localized); cg.deleteEntry("foobool"); QVERIFY(config.sync()); // The value is now gone. The others are still there. Everything correct // here. QVERIFY(!cg.hasKey("foostring")); QVERIFY(!cg.hasKey("foobool")); QVERIFY(cg.hasKey("foo")); // The current state is: (Just return before this comment.) // [...] // foobool[ca]=true // foobool[de]=wahr // foostring=ugly // foostring[de]=schoen // Now switch the locale to "de" and repeat the checks. Results should be // the same. But they currently are not. The localized value are // independent of each other. All values are still there in "de". config.setLocale(QStringLiteral("de")); QEXPECT_FAIL("", "Currently localized values are not deleted correctly", Continue); QVERIFY(!cg.hasKey("foostring")); QEXPECT_FAIL("", "Currently localized values are not deleted correctly", Continue); QVERIFY(!cg.hasKey("foobool")); QVERIFY(cg.hasKey("foo")); // Check where the wrong values come from. // We get the "de" value. QCOMPARE(cg.readEntry("foostring", "nothing"), QString("schoen")); // We get the "de" value. QCOMPARE(cg.readEntry("foobool", false), true); // Now switch the locale back "ca" and repeat the checks. Results are // again different. config.setLocale(QStringLiteral("ca")); // This line worked above. But now it fails. QEXPECT_FAIL("", "Currently localized values are not deleted correctly", Continue); QVERIFY(!cg.hasKey("foostring")); // This line worked above too. QEXPECT_FAIL("", "Currently localized values are not deleted correctly", Continue); QVERIFY(!cg.hasKey("foobool")); QVERIFY(cg.hasKey("foo")); // Check where the wrong values come from. // We get the primary value because the "ca" value was deleted. QCOMPARE(cg.readEntry("foostring", "nothing"), QString("ugly")); // We get the "ca" value. QCOMPARE(cg.readEntry("foobool", false), true); // Now test the deletion of a group. cg.deleteGroup(); QVERIFY(config.sync()); // Current state: [ca] and [de] entries left... oops. //qDebug() << readLinesFrom(file); // Bug: The group still exists [because of the localized entries]... QVERIFY(cg.exists()); QVERIFY(!cg.hasKey("foo")); QVERIFY(!cg.hasKey("foostring")); QEXPECT_FAIL("", "Currently localized values are not deleted correctly", Continue); QVERIFY(!cg.hasKey("foobool")); // Now switch the locale to "de" and repeat the checks. All values // still here because only the primary values are deleted. config.setLocale(QStringLiteral("de")); QEXPECT_FAIL("", "Currently localized values are not deleted correctly", Continue); QVERIFY(!cg.hasKey("foo")); QEXPECT_FAIL("", "Currently localized values are not deleted correctly", Continue); QVERIFY(!cg.hasKey("foostring")); QEXPECT_FAIL("", "Currently localized values are not deleted correctly", Continue); QVERIFY(!cg.hasKey("foobool")); // Check where the wrong values come from. // We get the "de" value. QCOMPARE(cg.readEntry("foostring", "nothing"), QString("schoen")); // We get the "de" value. QCOMPARE(cg.readEntry("foobool", false), true); // We get the "de" value. QCOMPARE(cg.readEntry("foo", 0), 7); // Now switch the locale to "ca" and repeat the checks // "foostring" is now really gone because both the primary value and the // "ca" value are deleted. config.setLocale(QStringLiteral("ca")); QEXPECT_FAIL("", "Currently localized values are not deleted correctly", Continue); QVERIFY(!cg.hasKey("foo")); QVERIFY(!cg.hasKey("foostring")); QEXPECT_FAIL("", "Currently localized values are not deleted correctly", Continue); QVERIFY(!cg.hasKey("foobool")); // Check where the wrong values come from. // We get the "ca" value. QCOMPARE(cg.readEntry("foobool", false), true); // We get the "ca" value. QCOMPARE(cg.readEntry("foo", 0), 5); // Cleanup QFile::remove(file); } void KConfigTest::testKdeGlobals() { { KConfig glob(QStringLiteral("kdeglobals")); KConfigGroup general(&glob, "General"); general.writeEntry("testKG", "1"); QVERIFY(glob.sync()); } KConfig globRead(QStringLiteral("kdeglobals")); const KConfigGroup general(&globRead, "General"); QCOMPARE(general.readEntry("testKG"), QString("1")); // Check we wrote into kdeglobals const QList lines = readLines("kdeglobals"); QVERIFY(lines.contains("[General]\n")); QVERIFY(lines.contains("testKG=1\n")); // Writing using NoGlobals { KConfig glob(QStringLiteral("kdeglobals"), KConfig::NoGlobals); KConfigGroup general(&glob, "General"); general.writeEntry("testKG", "2"); QVERIFY(glob.sync()); } globRead.reparseConfiguration(); QCOMPARE(general.readEntry("testKG"), QString("2")); // Reading using NoGlobals { KConfig globReadNoGlob(QStringLiteral("kdeglobals"), KConfig::NoGlobals); const KConfigGroup generalNoGlob(&globReadNoGlob, "General"); QCOMPARE(generalNoGlob.readEntry("testKG"), QString("2")); } // TODO now use kconfigtest and writeEntry(,Global) -> should go into kdeglobals } void KConfigTest::testAnonymousConfig() { KConfig anonConfig(QString(), KConfig::SimpleConfig); KConfigGroup general(&anonConfig, "General"); QCOMPARE(general.readEntry("testKG"), QString()); // no kdeglobals merging general.writeEntry("Foo", "Bar"); QCOMPARE(general.readEntry("Foo"), QString("Bar")); } void KConfigTest::testQByteArrayUtf8() { QTemporaryFile file; QVERIFY(file.open()); KConfig config(file.fileName(), KConfig::SimpleConfig); KConfigGroup general(&config, "General"); QByteArray bytes(256, '\0'); for (int i = 0; i < 256; i++) { bytes[i] = i; } general.writeEntry("Utf8", bytes); config.sync(); file.flush(); file.close(); QFile readFile(file.fileName()); QVERIFY(readFile.open(QFile::ReadOnly)); #define VALUE "Utf8=\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\t\\n\\x0b\\x0c\\r\\x0e\\x0f\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\\x7f\\x80\\x81\\x82\\x83\\x84\\x85\\x86\\x87\\x88\\x89\\x8a\\x8b\\x8c\\x8d\\x8e\\x8f\\x90\\x91\\x92\\x93\\x94\\x95\\x96\\x97\\x98\\x99\\x9a\\x9b\\x9c\\x9d\\x9e\\x9f\\xa0\\xa1\\xa2\\xa3\\xa4\\xa5\\xa6\\xa7\\xa8\\xa9\\xaa\\xab\\xac\\xad\\xae\\xaf\\xb0\\xb1\\xb2\\xb3\\xb4\\xb5\\xb6\\xb7\\xb8\\xb9\\xba\\xbb\\xbc\\xbd\\xbe\\xbf\\xc0\\xc1\\xc2\\xc3\\xc4\\xc5\\xc6\\xc7\\xc8\\xc9\\xca\\xcb\\xcc\\xcd\\xce\\xcf\\xd0\\xd1\\xd2\\xd3\\xd4\\xd5\\xd6\\xd7\\xd8\\xd9\\xda\\xdb\\xdc\\xdd\\xde\\xdf\\xe0\\xe1\\xe2\\xe3\\xe4\\xe5\\xe6\\xe7\\xe8\\xe9\\xea\\xeb\\xec\\xed\\xee\\xef\\xf0\\xf1\\xf2\\xf3\\xf4\\xf5\\xf6\\xf7\\xf8\\xf9\\xfa\\xfb\\xfc\\xfd\\xfe\\xff" const QByteArray fileBytes = readFile.readAll(); #ifndef Q_OS_WIN QCOMPARE(fileBytes, QByteArrayLiteral("[General]\n" VALUE "\n")); #else QCOMPARE(fileBytes, QByteArrayLiteral("[General]\r\n" VALUE "\r\n")); #endif #undef VALUE // check that reading works KConfig config2(file.fileName(), KConfig::SimpleConfig); KConfigGroup general2(&config2, "General"); QCOMPARE(bytes, general2.readEntry("Utf8", QByteArray())); } void KConfigTest::testQStringUtf8_data() { QTest::addColumn("data"); QTest::newRow("1") << QByteArray("Téléchargements\tTéléchargements"); QTest::newRow("2") << QByteArray("$¢ह€𐍈\t$¢ह€𐍈"); QTest::newRow("3") << QByteArray("\xc2\xe0\xa4\xf0\x90\x8d\t\\xc2\\xe0\\xa4\\xf0\\x90\\x8d"); // 2 byte overlong QTest::newRow("4") << QByteArray("\xc1\xbf\t\\xc1\\xbf"); // 3 byte overlong QTest::newRow("5") << QByteArray("\xe0\x9f\xbf\t\\xe0\\x9f\\xbf"); // 4 byte overlong QTest::newRow("6") << QByteArray("\xf0\x8f\xbf\xbf\t\\xf0\\x8f\\xbf\\xbf"); // outside unicode range QTest::newRow("7") << QByteArray("\xf4\x90\x80\x80\t\\xf4\\x90\\x80\\x80"); // just within range QTest::newRow("8") << QByteArray("\xc2\x80\t\xc2\x80"); QTest::newRow("9") << QByteArray("\xe0\xa0\x80\t\xe0\xa0\x80"); QTest::newRow("10") << QByteArray("\xf0\x90\x80\x80\t\xf0\x90\x80\x80"); QTest::newRow("11") << QByteArray("\xf4\x8f\xbf\xbf\t\xf4\x8f\xbf\xbf"); } void KConfigTest::testQStringUtf8() { QFETCH(QByteArray, data); const QList d = data.split('\t'); const QByteArray value = d[0]; const QByteArray serialized = d[1]; QTemporaryFile file; QVERIFY(file.open()); KConfig config(file.fileName(), KConfig::SimpleConfig); KConfigGroup general(&config, "General"); general.writeEntry("key", value); config.sync(); file.flush(); file.close(); QFile readFile(file.fileName()); QVERIFY(readFile.open(QFile::ReadOnly)); QByteArray fileBytes = readFile.readAll(); #ifdef Q_OS_WIN fileBytes.replace("\r\n", "\n"); #endif QCOMPARE(fileBytes, QByteArrayLiteral("[General]\nkey=") + serialized + QByteArrayLiteral("\n")); // check that reading works KConfig config2(file.fileName(), KConfig::SimpleConfig); KConfigGroup general2(&config2, "General"); QCOMPARE(value, general2.readEntry("key", QByteArray())); } void KConfigTest::testNewlines() { // test that kconfig always uses the native line endings QTemporaryFile file; QVERIFY(file.open()); KConfig anonConfig(file.fileName(), KConfig::SimpleConfig); KConfigGroup general(&anonConfig, "General"); general.writeEntry("Foo", "Bar"); general.writeEntry("Bar", "Foo"); anonConfig.sync(); file.flush(); file.close(); QFile readFile(file.fileName()); QVERIFY(readFile.open(QFile::ReadOnly)); #ifndef Q_OS_WIN QCOMPARE(readFile.readAll(), QByteArrayLiteral("[General]\nBar=Foo\nFoo=Bar\n")); #else QCOMPARE(readFile.readAll(), QByteArrayLiteral("[General]\r\nBar=Foo\r\nFoo=Bar\r\n")); #endif } void KConfigTest::testXdgListEntry() { QTemporaryFile file; QVERIFY(file.open()); QTextStream out(&file); out << "[General]" << endl << "Key1=" << endl // empty list // emtpty entries << "Key2=;" << endl << "Key3=;;" << endl << "Key4=;;;" << endl << "Key5=\\;" << endl << "Key6=1;2\\;3;;" << endl; out.flush(); file.close(); KConfig anonConfig(file.fileName(), KConfig::SimpleConfig); KConfigGroup grp = anonConfig.group("General"); QStringList invalidList; // use this as a default when an empty list is expected invalidList << QStringLiteral("Error! Default value read!"); QCOMPARE(grp.readXdgListEntry("Key1", invalidList), QStringList()); QCOMPARE(grp.readXdgListEntry("Key2", invalidList), QStringList() << QString()); QCOMPARE(grp.readXdgListEntry("Key3", invalidList), QStringList() << QString() << QString()); QCOMPARE(grp.readXdgListEntry("Key4", invalidList), QStringList()<< QString() << QString() << QString()); QCOMPARE(grp.readXdgListEntry("Key5", invalidList), QStringList() << ";"); QCOMPARE(grp.readXdgListEntry("Key6", invalidList), QStringList() << "1" << "2;3" << QString()); } #include #include // To find multithreading bugs: valgrind --tool=helgrind --track-lockorders=no ./kconfigtest testThreads void KConfigTest::testThreads() { QThreadPool::globalInstance()->setMaxThreadCount(6); QList > futures; // Run in parallel some tests that work on different config files, // otherwise unexpected things might indeed happen. futures << QtConcurrent::run(this, &KConfigTest::testAddConfigSources); futures << QtConcurrent::run(this, &KConfigTest::testSimple); futures << QtConcurrent::run(this, &KConfigTest::testDefaults); futures << QtConcurrent::run(this, &KConfigTest::testSharedConfig); futures << QtConcurrent::run(this, &KConfigTest::testSharedConfig); // QEXPECT_FAIL triggers race conditions, it should be fixed to use QThreadStorage... //futures << QtConcurrent::run(this, &KConfigTest::testDeleteWhenLocalized); //futures << QtConcurrent::run(this, &KConfigTest::testEntryMap); for (QFuture f : qAsConst(futures)) { // krazy:exclude=foreach f.waitForFinished(); } } void KConfigTest::testNotify() { #if !KCONFIG_USE_DBUS QSKIP("KConfig notification requires DBus"); #endif KConfig config(TEST_SUBDIR "kconfigtest"); auto myConfigGroup = KConfigGroup(&config, "TopLevelGroup"); //mimics a config in another process, which is watching for events auto remoteConfig = KSharedConfig::openConfig(TEST_SUBDIR "kconfigtest"); KConfigWatcher::Ptr watcher = KConfigWatcher::create(remoteConfig); //some random config that shouldn't be changing when kconfigtest changes, only on kdeglobals auto otherRemoteConfig = KSharedConfig::openConfig(TEST_SUBDIR "kconfigtest2"); KConfigWatcher::Ptr otherWatcher = KConfigWatcher::create(otherRemoteConfig); QSignalSpy watcherSpy(watcher.data(), &KConfigWatcher::configChanged); QSignalSpy otherWatcherSpy(otherWatcher.data(), &KConfigWatcher::configChanged); //write entries in a group and subgroup myConfigGroup.writeEntry("entryA", "foo", KConfig::Persistent | KConfig::Notify); auto subGroup = myConfigGroup.group("aSubGroup"); subGroup.writeEntry("entry1", "foo", KConfig::Persistent | KConfig::Notify); subGroup.writeEntry("entry2", "foo", KConfig::Persistent | KConfig::Notify); config.sync(); watcherSpy.wait(); QCOMPARE(watcherSpy.count(), 2); std::sort(watcherSpy.begin(), watcherSpy.end(), [] (QList a, QList b) { return a[0].value().name() < b[0].value().name(); }); QCOMPARE(watcherSpy[0][0].value().name(), QStringLiteral("TopLevelGroup")); QCOMPARE(watcherSpy[0][1].value(), QByteArrayList({"entryA"})); QCOMPARE(watcherSpy[1][0].value().name(), QStringLiteral("aSubGroup")); QCOMPARE(watcherSpy[1][0].value().parent().name(), QStringLiteral("TopLevelGroup")); QCOMPARE(watcherSpy[1][1].value(), QByteArrayList({"entry1", "entry2"})); //delete an entry watcherSpy.clear(); myConfigGroup.deleteEntry("entryA", KConfig::Persistent | KConfig::Notify); config.sync(); watcherSpy.wait(); QCOMPARE(watcherSpy.count(), 1); QCOMPARE(watcherSpy[0][0].value().name(), QStringLiteral("TopLevelGroup")); QCOMPARE(watcherSpy[0][1].value(), QByteArrayList({"entryA"})); //revert to default an entry watcherSpy.clear(); myConfigGroup.revertToDefault("entryA", KConfig::Persistent | KConfig::Notify); config.sync(); watcherSpy.wait(); QCOMPARE(watcherSpy.count(), 1); QCOMPARE(watcherSpy[0][0].value().name(), QStringLiteral("TopLevelGroup")); QCOMPARE(watcherSpy[0][1].value(), QByteArrayList({"entryA"})); //deleting a group, should notify that every entry in that group has changed watcherSpy.clear(); myConfigGroup.deleteGroup("aSubGroup", KConfig::Persistent | KConfig::Notify); config.sync(); watcherSpy.wait(); QCOMPARE(watcherSpy.count(), 1); QCOMPARE(watcherSpy[0][0].value().name(), QStringLiteral("aSubGroup")); QCOMPARE(watcherSpy[0][1].value(), QByteArrayList({"entry1", "entry2"})); //global write still triggers our notification watcherSpy.clear(); myConfigGroup.writeEntry("someGlobalEntry", "foo", KConfig::Persistent | KConfig::Notify | KConfig::Global); config.sync(); watcherSpy.wait(); QCOMPARE(watcherSpy.count(), 1); QCOMPARE(watcherSpy[0][0].value().name(), QStringLiteral("TopLevelGroup")); QCOMPARE(watcherSpy[0][1].value(), QByteArrayList({"someGlobalEntry"})); //watching another file should have only triggered from the kdeglobals change QCOMPARE(otherWatcherSpy.count(), 1); QCOMPARE(otherWatcherSpy[0][0].value().name(), QStringLiteral("TopLevelGroup")); QCOMPARE(otherWatcherSpy[0][1].value(), QByteArrayList({"someGlobalEntry"})); } diff --git a/docs/options.md b/docs/options.md index c634c00..4a6e9bc 100644 --- a/docs/options.md +++ b/docs/options.md @@ -1,99 +1,96 @@ KConfig Entry Options {#options} ===================== KConfig provides various options that can alter how it interprets configuration entries on a per-entry, per-group or per-file basis. Note that these are not supported by other configuration frameworks, and so should not be used in files that are intended to be used by applications that do not use KConfig (such as application desktop files). Immutable Entries ----------------- KConfig's cascading configuration mechanism usually causes values from earlier, "global" configuration files to be overridden by later, "local" ones. Typically, the system administrator might set global defaults, and a user might override them in their local configuration files. However, KConfig provides a way to lock down configuration values, so that the global settings override the local ones. This allows system administrators to restrict the values a user can set for an entry, group of entries or an entire configuration file. This is important for Kiosk authorization (see the KAuthorized namespace), which allows parts of the user interface to be locked down. Configuration entries can be marked as immutable with the `$i` option. This can be done on a per-entry basis: [MyGroup] someKey[$i]=42 on a per-group basis (which will prevent any attempts to modify entries in the group at all in later files): [MyGroup][$i] someKey=42 or for an entire file by putting `[$i]` at the start of the file: [$i] [MyGroup] someKey=42 [MyOtherGroup] someOtherKey=11 Once this is done, the immutable entries or groups cannot be overridden by later files of the same name (and, if the file is immutable, later files will be ignored entirely). Note that a similar effect to file immutability can be acheived by using file system permissions to prevent the user from writing to their local versions of the configuration file, although (since this is normally a setup error), the user will be warned that the configuration file is not writable. This warning can be supressed by adding the following setting to either the relevant configuration file or the `kdeglobals` file: [General] warn_unwritable_config=true However, using file system permissions like this can potentially be circumvented by the user if they have write access to the containing directory or can modify environment variables (and `XDG_CONFIG_HOME` in particular). Shell Expansion --------------- -If an entry is marked with `$e`, environment variables and shell commands will -be expanded. +If an entry is marked with `$e`, environment variables will be expanded. Name[$e]=$USER - Host[$e]=$(hostname) When the "Name" entry is read `$USER` will be replaced with the value of the -`$USER` environment variable, and `$(hostname)` will be replaced with the output -of the `hostname` command. +`$USER` environment variable. -Note that the application will replace `$USER` and `$(hostname)` with their -respective expanded values after saving. To prevent this combine the `$e` option +Note that the application will replace `$USER` with its +expanded value after saving. To prevent this combine the `$e` option with `$i` (immmutable) option. For example: Name[$ei]=$USER This will make that the "Name" entry will always return the value of the `$USER` environment variable. The user will not be able to change this entry. The following syntax for environment variables is also supported: Name[$ei]=${USER} There are three environment variables that have a fallback strategy if the environment variable is not set. They instead map to a location from QStandardPaths. They are: * `$QT_CACHE_HOME` - QStandardPaths::GenericCacheLocation * `$QT_CONFIG_HOME` - QStandardPaths::GenericConfigLocation * `$QT_DATA_HOME` - QStandardPaths::GenericDataLocation diff --git a/src/core/kconfig.cpp b/src/core/kconfig.cpp index e1b11ed..f6824ce 100644 --- a/src/core/kconfig.cpp +++ b/src/core/kconfig.cpp @@ -1,1055 +1,1020 @@ /* This file is part of the KDE libraries Copyright (c) 2006, 2007 Thomas Braxton Copyright (c) 1999 Preston Brown Copyright (c) 1997-1999 Matthias Kalle Dalheimer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kconfig.h" #include "kconfig_p.h" #include "config-kconfig.h" #include #include -#ifdef _MSC_VER -static inline FILE *popen(const char *cmd, const char *mode) -{ - return _popen(cmd, mode); -} -static inline int pclose(FILE *stream) -{ - return _pclose(stream); -} -#else -#include -#endif - #include "kconfigbackend_p.h" #include "kconfiggroup.h" #include #include #include #include #include #include #include #include #include #include #include #if KCONFIG_USE_DBUS #include #include #include #endif bool KConfigPrivate::mappingsRegistered = false; Q_GLOBAL_STATIC(QStringList, s_globalFiles) // For caching purposes. static QBasicMutex s_globalFilesMutex; Q_GLOBAL_STATIC_WITH_ARGS(QString, sGlobalFileName, (QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1String("/kdeglobals"))) #ifndef Q_OS_WIN static const Qt::CaseSensitivity sPathCaseSensitivity = Qt::CaseSensitive; #else static const Qt::CaseSensitivity sPathCaseSensitivity = Qt::CaseInsensitive; #endif KConfigPrivate::KConfigPrivate(KConfig::OpenFlags flags, QStandardPaths::StandardLocation resourceType) : openFlags(flags), resourceType(resourceType), mBackend(nullptr), bDynamicBackend(true), bDirty(false), bReadDefaults(false), bFileImmutable(false), bForceGlobal(false), bSuppressGlobal(false), configState(KConfigBase::NoAccess) { static QBasicAtomicInt use_etc_kderc = Q_BASIC_ATOMIC_INITIALIZER(-1); if (use_etc_kderc.load() < 0) { use_etc_kderc.store( !qEnvironmentVariableIsSet("KDE_SKIP_KDERC")); // for unit tests } if (use_etc_kderc.load()) { etc_kderc = #ifdef Q_OS_WIN QFile::decodeName(qgetenv("WINDIR") + "/kde5rc"); #else QStringLiteral("/etc/kde5rc"); #endif if (!QFileInfo(etc_kderc).isReadable()) { use_etc_kderc.store(false); etc_kderc.clear(); } } // if (!mappingsRegistered) { // KEntryMap tmp; // if (!etc_kderc.isEmpty()) { // QExplicitlySharedDataPointer backend = KConfigBackend::create(etc_kderc, QLatin1String("INI")); // backend->parseConfig( "en_US", tmp, KConfigBackend::ParseDefaults); // } // const QString kde5rc(QDir::home().filePath(".kde5rc")); // if (KStandardDirs::checkAccess(kde5rc, R_OK)) { // QExplicitlySharedDataPointer backend = KConfigBackend::create(kde5rc, QLatin1String("INI")); // backend->parseConfig( "en_US", tmp, KConfigBackend::ParseOptions()); // } // KConfigBackend::registerMappings(tmp); // mappingsRegistered = true; // } setLocale(QLocale().name()); } bool KConfigPrivate::lockLocal() { if (mBackend) { return mBackend->lock(); } // anonymous object - pretend we locked it return true; } void KConfigPrivate::copyGroup(const QByteArray &source, const QByteArray &destination, KConfigGroup *otherGroup, KConfigBase::WriteConfigFlags flags) const { KEntryMap &otherMap = otherGroup->config()->d_ptr->entryMap; const int len = source.length(); const bool sameName = (destination == source); // we keep this bool outside the foreach loop so that if // the group is empty, we don't end up marking the other config // as dirty erroneously bool dirtied = false; for (KEntryMap::ConstIterator entryMapIt(entryMap.constBegin()); entryMapIt != entryMap.constEnd(); ++entryMapIt) { const QByteArray &group = entryMapIt.key().mGroup; if (!group.startsWith(source)) { // nothing to do continue; } // don't copy groups that start with the same prefix, but are not sub-groups if (group.length() > len && group[len] != '\x1d') { continue; } KEntryKey newKey = entryMapIt.key(); if (flags & KConfigBase::Localized) { newKey.bLocal = true; } if (!sameName) { newKey.mGroup.replace(0, len, destination); } KEntry entry = entryMap[ entryMapIt.key() ]; dirtied = entry.bDirty = flags & KConfigBase::Persistent; if (flags & KConfigBase::Global) { entry.bGlobal = true; } otherMap[newKey] = entry; } if (dirtied) { otherGroup->config()->d_ptr->bDirty = true; } } QString KConfigPrivate::expandString(const QString &value) { QString aValue = value; // check for environment variables and make necessary translations int nDollarPos = aValue.indexOf(QLatin1Char('$')); while (nDollarPos != -1 && nDollarPos + 1 < aValue.length()) { // there is at least one $ - if (aValue[nDollarPos + 1] == QLatin1Char('(')) { - int nEndPos = nDollarPos + 1; - // the next character is not $ - while ((nEndPos <= aValue.length()) && (aValue[nEndPos] != QLatin1Char(')'))) { - nEndPos++; - } - nEndPos++; - QString cmd = aValue.mid(nDollarPos + 2, nEndPos - nDollarPos - 3); - - QString result; - -// FIXME: wince does not have pipes -#ifndef _WIN32_WCE - FILE *fs = popen(QFile::encodeName(cmd).data(), "r"); - if (fs) { - QTextStream ts(fs, QIODevice::ReadOnly); - result = ts.readAll().trimmed(); - pclose(fs); - } -#endif - aValue.replace(nDollarPos, nEndPos - nDollarPos, result); - nDollarPos += result.length(); - } else if (aValue[nDollarPos + 1] != QLatin1Char('$')) { + if (aValue[nDollarPos + 1] != QLatin1Char('$')) { int nEndPos = nDollarPos + 1; // the next character is not $ QStringRef aVarName; if (aValue[nEndPos] == QLatin1Char('{')) { while ((nEndPos <= aValue.length()) && (aValue[nEndPos] != QLatin1Char('}'))) { nEndPos++; } nEndPos++; aVarName = aValue.midRef(nDollarPos + 2, nEndPos - nDollarPos - 3); } else { while (nEndPos <= aValue.length() && (aValue[nEndPos].isNumber() || aValue[nEndPos].isLetter() || aValue[nEndPos] == QLatin1Char('_'))) { nEndPos++; } aVarName = aValue.midRef(nDollarPos + 1, nEndPos - nDollarPos - 1); } QString env; if (!aVarName.isEmpty()) { #ifdef Q_OS_WIN if (aVarName == QLatin1String("HOME")) { env = QDir::homePath(); } else #endif { QByteArray pEnv = qgetenv(aVarName.toLatin1().constData()); if (!pEnv.isEmpty()) { env = QString::fromLocal8Bit(pEnv.constData()); } else { if (aVarName == QStringLiteral("QT_DATA_HOME")) { env = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation); } else if (aVarName == QStringLiteral("QT_CONFIG_HOME")) { env = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); } else if (aVarName == QStringLiteral("QT_CACHE_HOME")) { env = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation); } } } aValue.replace(nDollarPos, nEndPos - nDollarPos, env); nDollarPos += env.length(); } else { aValue.remove(nDollarPos, nEndPos - nDollarPos); } } else { // remove one of the dollar signs aValue.remove(nDollarPos, 1); nDollarPos++; } nDollarPos = aValue.indexOf(QLatin1Char('$'), nDollarPos); } return aValue; } KConfig::KConfig(const QString &file, OpenFlags mode, QStandardPaths::StandardLocation resourceType) : d_ptr(new KConfigPrivate(mode, resourceType)) { d_ptr->changeFileName(file); // set the local file name // read initial information off disk reparseConfiguration(); } KConfig::KConfig(const QString &file, const QString &backend, QStandardPaths::StandardLocation resourceType) : d_ptr(new KConfigPrivate(SimpleConfig, resourceType)) { d_ptr->mBackend = KConfigBackend::create(file, backend); d_ptr->bDynamicBackend = false; d_ptr->changeFileName(file); // set the local file name // read initial information off disk reparseConfiguration(); } KConfig::KConfig(KConfigPrivate &d) : d_ptr(&d) { } KConfig::~KConfig() { Q_D(KConfig); if (d->bDirty && (d->mBackend && d->mBackend->ref.load() == 1)) { sync(); } delete d; } QStringList KConfig::groupList() const { Q_D(const KConfig); QSet groups; for (KEntryMap::ConstIterator entryMapIt(d->entryMap.constBegin()); entryMapIt != d->entryMap.constEnd(); ++entryMapIt) { const KEntryKey &key = entryMapIt.key(); const QByteArray group = key.mGroup; if (key.mKey.isNull() && !group.isEmpty() && group != "" && group != "$Version") { const QString groupname = QString::fromUtf8(group); groups << groupname.left(groupname.indexOf(QLatin1Char('\x1d'))); } } return groups.toList(); } QStringList KConfigPrivate::groupList(const QByteArray &group) const { QByteArray theGroup = group + '\x1d'; QSet groups; for (KEntryMap::ConstIterator entryMapIt(entryMap.constBegin()); entryMapIt != entryMap.constEnd(); ++entryMapIt) { const KEntryKey &key = entryMapIt.key(); if (key.mKey.isNull() && key.mGroup.startsWith(theGroup)) { const QString groupname = QString::fromUtf8(key.mGroup.mid(theGroup.length())); groups << groupname.left(groupname.indexOf(QLatin1Char('\x1d'))); } } return groups.toList(); } static bool isGroupOrSubGroupMatch(const QByteArray &potentialGroup, const QByteArray &group) { if (!potentialGroup.startsWith(group)) { return false; } return potentialGroup.length() == group.length() || potentialGroup[group.length()] == '\x1d'; } // List all sub groups, including subsubgroups QSet KConfigPrivate::allSubGroups(const QByteArray &parentGroup) const { QSet groups; for (KEntryMap::const_iterator entryMapIt = entryMap.begin(); entryMapIt != entryMap.end(); ++entryMapIt) { const KEntryKey &key = entryMapIt.key(); if (key.mKey.isNull() && isGroupOrSubGroupMatch(key.mGroup, parentGroup)) { groups << key.mGroup; } } return groups; } bool KConfigPrivate::hasNonDeletedEntries(const QByteArray &group) const { for (KEntryMap::const_iterator it = entryMap.begin(); it != entryMap.end(); ++it) { const KEntryKey &key = it.key(); // Check for any non-deleted entry if (isGroupOrSubGroupMatch(key.mGroup, group) && !key.mKey.isNull() && !it->bDeleted) { return true; } } return false; } QStringList KConfigPrivate::keyListImpl(const QByteArray &theGroup) const { QStringList keys; const KEntryMapConstIterator theEnd = entryMap.constEnd(); KEntryMapConstIterator it = entryMap.findEntry(theGroup); if (it != theEnd) { ++it; // advance past the special group entry marker QSet tmp; for (; it != theEnd && it.key().mGroup == theGroup; ++it) { const KEntryKey &key = it.key(); if (!key.mKey.isNull() && !it->bDeleted) { tmp << QString::fromUtf8(key.mKey); } } keys = tmp.toList(); } return keys; } QStringList KConfig::keyList(const QString &aGroup) const { Q_D(const KConfig); const QByteArray theGroup(aGroup.isEmpty() ? "" : aGroup.toUtf8()); return d->keyListImpl(theGroup); } QMap KConfig::entryMap(const QString &aGroup) const { Q_D(const KConfig); QMap theMap; const QByteArray theGroup(aGroup.isEmpty() ? "" : aGroup.toUtf8()); const KEntryMapConstIterator theEnd = d->entryMap.constEnd(); KEntryMapConstIterator it = d->entryMap.findEntry(theGroup, nullptr, nullptr); if (it != theEnd) { ++it; // advance past the special group entry marker for (; it != theEnd && it.key().mGroup == theGroup; ++it) { // leave the default values and deleted entries out if (!it->bDeleted && !it.key().bDefault) { const QString key = QString::fromUtf8(it.key().mKey.constData()); // the localized entry should come first, so don't overwrite it // with the non-localized entry if (!theMap.contains(key)) { if (it->bExpand) { theMap.insert(key, KConfigPrivate::expandString(QString::fromUtf8(it->mValue.constData()))); } else { theMap.insert(key, QString::fromUtf8(it->mValue.constData())); } } } } } return theMap; } bool KConfig::sync() { Q_D(KConfig); if (isImmutable() || name().isEmpty()) { // can't write to an immutable or anonymous file. return false; } QHash notifyGroupsLocal; QHash notifyGroupsGlobal; if (d->bDirty && d->mBackend) { const QByteArray utf8Locale(locale().toUtf8()); // Create the containing dir, maybe it wasn't there d->mBackend->createEnclosing(); // lock the local file if (d->configState == ReadWrite && !d->lockLocal()) { qWarning() << "couldn't lock local file"; return false; } // Rewrite global/local config only if there is a dirty entry in it. bool writeGlobals = false; bool writeLocals = false; for (auto it = d->entryMap.constBegin(); it != d->entryMap.constEnd(); ++it) { auto e = it.value(); if (e.bDirty) { if (e.bGlobal) { writeGlobals = true; if (e.bNotify) { notifyGroupsGlobal[QString::fromUtf8(it.key().mGroup)] << it.key().mKey; } } else { writeLocals = true; if (e.bNotify) { notifyGroupsLocal[QString::fromUtf8(it.key().mGroup)] << it.key().mKey; } } } } d->bDirty = false; // will revert to true if a config write fails if (d->wantGlobals() && writeGlobals) { QExplicitlySharedDataPointer tmp = KConfigBackend::create(*sGlobalFileName); if (d->configState == ReadWrite && !tmp->lock()) { qWarning() << "couldn't lock global file"; //unlock the local config if we're returning early if (d->mBackend->isLocked()) { d->mBackend->unlock(); } d->bDirty = true; return false; } if (!tmp->writeConfig(utf8Locale, d->entryMap, KConfigBackend::WriteGlobal)) { d->bDirty = true; } if (tmp->isLocked()) { tmp->unlock(); } } if (writeLocals) { if (!d->mBackend->writeConfig(utf8Locale, d->entryMap, KConfigBackend::WriteOptions())) { d->bDirty = true; } } if (d->mBackend->isLocked()) { d->mBackend->unlock(); } } if (!notifyGroupsLocal.isEmpty()) { d->notifyClients(notifyGroupsLocal, QStringLiteral("/") + name()); } if (!notifyGroupsGlobal.isEmpty()) { d->notifyClients(notifyGroupsGlobal, QStringLiteral("/kdeglobals")); } return !d->bDirty; } void KConfigPrivate::notifyClients(const QHash &changes, const QString &path) { #if KCONFIG_USE_DBUS qDBusRegisterMetaType(); qDBusRegisterMetaType>(); QDBusMessage message = QDBusMessage::createSignal(path, QStringLiteral("org.kde.kconfig.notify"), QStringLiteral("ConfigChanged")); message.setArguments({QVariant::fromValue(changes)}); QDBusConnection::sessionBus().send(message); #else Q_UNUSED(changes) Q_UNUSED(path) #endif } void KConfig::markAsClean() { Q_D(KConfig); d->bDirty = false; // clear any dirty flags that entries might have set const KEntryMapIterator theEnd = d->entryMap.end(); for (KEntryMapIterator it = d->entryMap.begin(); it != theEnd; ++it) { it->bDirty = false; it->bNotify = false; } } bool KConfig::isDirty() const { Q_D(const KConfig); return d->bDirty; } void KConfig::checkUpdate(const QString &id, const QString &updateFile) { const KConfigGroup cg(this, "$Version"); const QString cfg_id = updateFile + QLatin1Char(':') + id; const QStringList ids = cg.readEntry("update_info", QStringList()); if (!ids.contains(cfg_id)) { QProcess::execute(QStringLiteral(KCONF_UPDATE_INSTALL_LOCATION), QStringList() << QStringLiteral("--check") << updateFile); reparseConfiguration(); } } KConfig *KConfig::copyTo(const QString &file, KConfig *config) const { Q_D(const KConfig); if (!config) { config = new KConfig(QString(), SimpleConfig, d->resourceType); } config->d_func()->changeFileName(file); config->d_func()->entryMap = d->entryMap; config->d_func()->bFileImmutable = false; const KEntryMapIterator theEnd = config->d_func()->entryMap.end(); for (KEntryMapIterator it = config->d_func()->entryMap.begin(); it != theEnd; ++it) { it->bDirty = true; } config->d_ptr->bDirty = true; return config; } QString KConfig::name() const { Q_D(const KConfig); return d->fileName; } KConfig::OpenFlags KConfig::openFlags() const { Q_D(const KConfig); return d->openFlags; } struct KConfigStaticData { QString globalMainConfigName; // Keep a copy so we can use it in global dtors, after qApp is gone QStringList appArgs; }; Q_GLOBAL_STATIC(KConfigStaticData, globalData) void KConfig::setMainConfigName(const QString &str) { globalData()->globalMainConfigName = str; } QString KConfig::mainConfigName() { KConfigStaticData* data = globalData(); if (data->appArgs.isEmpty()) data->appArgs = QCoreApplication::arguments(); // --config on the command line overrides everything else const QStringList args = data->appArgs; for (int i = 1; i < args.count(); ++i) { if (args.at(i) == QLatin1String("--config") && i < args.count() - 1) { return args.at(i + 1); } } const QString globalName = data->globalMainConfigName; if (!globalName.isEmpty()) { return globalName; } QString appName = QCoreApplication::applicationName(); return appName + QLatin1String("rc"); } void KConfigPrivate::changeFileName(const QString &name) { fileName = name; QString file; if (name.isEmpty()) { if (wantDefaults()) { // accessing default app-specific config "appnamerc" fileName = KConfig::mainConfigName(); file = QStandardPaths::writableLocation(resourceType) + QLatin1Char('/') + fileName; } else if (wantGlobals()) { // accessing "kdeglobals" by specifying no filename and NoCascade - XXX used anywhere? resourceType = QStandardPaths::GenericConfigLocation; fileName = QStringLiteral("kdeglobals"); file = *sGlobalFileName; } else { // anonymous config openFlags = KConfig::SimpleConfig; return; } } else if (QDir::isAbsolutePath(fileName)) { fileName = QFileInfo(fileName).canonicalFilePath(); if (fileName.isEmpty()) { // file doesn't exist (yet) fileName = name; } file = fileName; } else { file = QStandardPaths::writableLocation(resourceType) + QLatin1Char('/') + fileName; } Q_ASSERT(!file.isEmpty()); bSuppressGlobal = (file.compare(*sGlobalFileName, sPathCaseSensitivity) == 0); if (bDynamicBackend || !mBackend) { // allow dynamic changing of backend mBackend = KConfigBackend::create(file); } else { mBackend->setFilePath(file); } configState = mBackend->accessMode(); } void KConfig::reparseConfiguration() { Q_D(KConfig); if (d->fileName.isEmpty()) { return; } // Don't lose pending changes if (!d->isReadOnly() && d->bDirty) { sync(); } d->entryMap.clear(); d->bFileImmutable = false; { QMutexLocker locker(&s_globalFilesMutex); s_globalFiles()->clear(); } // Parse all desired files from the least to the most specific. if (d->wantGlobals()) { d->parseGlobalFiles(); } d->parseConfigFiles(); } QStringList KConfigPrivate::getGlobalFiles() const { QMutexLocker locker(&s_globalFilesMutex); if (s_globalFiles()->isEmpty()) { const QStringList paths1 = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("kdeglobals")); const QStringList paths2 = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("system.kdeglobals")); const bool useEtcKderc = !etc_kderc.isEmpty(); s_globalFiles()->reserve(paths1.size() + paths2.size() + (useEtcKderc ? 1 : 0)); for (const QString &dir1 : paths1) { s_globalFiles()->push_front(dir1); } for (const QString &dir2 : paths2) { s_globalFiles()->push_front(dir2); } if (useEtcKderc) { s_globalFiles()->push_front(etc_kderc); } } return *s_globalFiles(); } void KConfigPrivate::parseGlobalFiles() { const QStringList globalFiles = getGlobalFiles(); // qDebug() << "parsing global files" << globalFiles; // TODO: can we cache the values in etc_kderc / other global files // on a per-application basis? const QByteArray utf8Locale = locale.toUtf8(); for (const QString &file : globalFiles) { KConfigBackend::ParseOptions parseOpts = KConfigBackend::ParseGlobal | KConfigBackend::ParseExpansions; if (file.compare(*sGlobalFileName, sPathCaseSensitivity) != 0) parseOpts |= KConfigBackend::ParseDefaults; QExplicitlySharedDataPointer backend = KConfigBackend::create(file); if (backend->parseConfig(utf8Locale, entryMap, parseOpts) == KConfigBackend::ParseImmutable) { break; } } } void KConfigPrivate::parseConfigFiles() { // can only read the file if there is a backend and a file name if (mBackend && !fileName.isEmpty()) { bFileImmutable = false; QList files; if (wantDefaults()) { if (bSuppressGlobal) { files = getGlobalFiles(); } else { if (QDir::isAbsolutePath(fileName)) { const QString canonicalFile = QFileInfo(fileName).canonicalFilePath(); if (!canonicalFile.isEmpty()) { // empty if it doesn't exist files << canonicalFile; } } else { const QStringList localFilesPath = QStandardPaths::locateAll(resourceType, fileName); for (const QString &f : localFilesPath) { files.prepend(QFileInfo(f).canonicalFilePath()); } // allow fallback to config files bundled in resources const QString resourceFile(QStringLiteral(":/kconfig/") + fileName); if (QFile::exists(resourceFile)) { files.prepend(resourceFile); } } } } else { files << mBackend->filePath(); } if (!isSimple()) { files = extraFiles.toList() + files; } // qDebug() << "parsing local files" << files; const QByteArray utf8Locale = locale.toUtf8(); for (const QString &file : qAsConst(files)) { if (file.compare(mBackend->filePath(), sPathCaseSensitivity) == 0) { switch (mBackend->parseConfig(utf8Locale, entryMap, KConfigBackend::ParseExpansions)) { case KConfigBackend::ParseOk: break; case KConfigBackend::ParseImmutable: bFileImmutable = true; break; case KConfigBackend::ParseOpenError: configState = KConfigBase::NoAccess; break; } } else { QExplicitlySharedDataPointer backend = KConfigBackend::create(file); bFileImmutable = (backend->parseConfig(utf8Locale, entryMap, KConfigBackend::ParseDefaults | KConfigBackend::ParseExpansions) == KConfigBackend::ParseImmutable); } if (bFileImmutable) { break; } } } } KConfig::AccessMode KConfig::accessMode() const { Q_D(const KConfig); return d->configState; } void KConfig::addConfigSources(const QStringList &files) { Q_D(KConfig); for (const QString &file : files) { d->extraFiles.push(file); } if (!files.isEmpty()) { reparseConfiguration(); } } QStringList KConfig::additionalConfigSources() const { Q_D(const KConfig); return d->extraFiles.toList(); } QString KConfig::locale() const { Q_D(const KConfig); return d->locale; } bool KConfigPrivate::setLocale(const QString &aLocale) { if (aLocale != locale) { locale = aLocale; return true; } return false; } bool KConfig::setLocale(const QString &locale) { Q_D(KConfig); if (d->setLocale(locale)) { reparseConfiguration(); return true; } return false; } void KConfig::setReadDefaults(bool b) { Q_D(KConfig); d->bReadDefaults = b; } bool KConfig::readDefaults() const { Q_D(const KConfig); return d->bReadDefaults; } bool KConfig::isImmutable() const { Q_D(const KConfig); return d->bFileImmutable; } bool KConfig::isGroupImmutableImpl(const QByteArray &aGroup) const { Q_D(const KConfig); return isImmutable() || d->entryMap.getEntryOption(aGroup, nullptr, nullptr, KEntryMap::EntryImmutable); } #ifndef KDE_NO_DEPRECATED void KConfig::setForceGlobal(bool b) { Q_D(KConfig); d->bForceGlobal = b; } #endif #ifndef KDE_NO_DEPRECATED bool KConfig::forceGlobal() const { Q_D(const KConfig); return d->bForceGlobal; } #endif KConfigGroup KConfig::groupImpl(const QByteArray &group) { return KConfigGroup(this, group.constData()); } const KConfigGroup KConfig::groupImpl(const QByteArray &group) const { return KConfigGroup(this, group.constData()); } KEntryMap::EntryOptions convertToOptions(KConfig::WriteConfigFlags flags) { KEntryMap::EntryOptions options = nullptr; if (flags & KConfig::Persistent) { options |= KEntryMap::EntryDirty; } if (flags & KConfig::Global) { options |= KEntryMap::EntryGlobal; } if (flags & KConfig::Localized) { options |= KEntryMap::EntryLocalized; } if (flags.testFlag(KConfig::Notify)) { options |= KEntryMap::EntryNotify; } return options; } void KConfig::deleteGroupImpl(const QByteArray &aGroup, WriteConfigFlags flags) { Q_D(KConfig); KEntryMap::EntryOptions options = convertToOptions(flags) | KEntryMap::EntryDeleted; const QSet groups = d->allSubGroups(aGroup); for (const QByteArray &group : groups) { const QStringList keys = d->keyListImpl(group); for (const QString &_key : keys) { const QByteArray &key = _key.toUtf8(); if (d->canWriteEntry(group, key.constData())) { d->entryMap.setEntry(group, key, QByteArray(), options); d->bDirty = true; } } } } bool KConfig::isConfigWritable(bool warnUser) { Q_D(KConfig); bool allWritable = (d->mBackend ? d->mBackend->isWritable() : false); if (warnUser && !allWritable) { QString errorMsg; if (d->mBackend) { // TODO how can be it be null? Set errorMsg appropriately errorMsg = d->mBackend->nonWritableErrorMessage(); } // Note: We don't ask the user if we should not ask this question again because we can't save the answer. errorMsg += QCoreApplication::translate("KConfig", "Please contact your system administrator."); QString cmdToExec = QStandardPaths::findExecutable(QStringLiteral("kdialog")); if (!cmdToExec.isEmpty()) { QProcess::execute(cmdToExec, QStringList() << QStringLiteral("--title") << QCoreApplication::applicationName() << QStringLiteral("--msgbox") << errorMsg); } } d->configState = allWritable ? ReadWrite : ReadOnly; // update the read/write status return allWritable; } bool KConfig::hasGroupImpl(const QByteArray &aGroup) const { Q_D(const KConfig); // No need to look for the actual group entry anymore, or for subgroups: // a group exists if it contains any non-deleted entry. return d->hasNonDeletedEntries(aGroup); } bool KConfigPrivate::canWriteEntry(const QByteArray &group, const char *key, bool isDefault) const { if (bFileImmutable || entryMap.getEntryOption(group, key, KEntryMap::SearchLocalized, KEntryMap::EntryImmutable)) { return isDefault; } return true; } void KConfigPrivate::putData(const QByteArray &group, const char *key, const QByteArray &value, KConfigBase::WriteConfigFlags flags, bool expand) { KEntryMap::EntryOptions options = convertToOptions(flags); if (bForceGlobal) { options |= KEntryMap::EntryGlobal; } if (expand) { options |= KEntryMap::EntryExpansion; } if (value.isNull()) { // deleting entry options |= KEntryMap::EntryDeleted; } bool dirtied = entryMap.setEntry(group, key, value, options); if (dirtied && (flags & KConfigBase::Persistent)) { bDirty = true; } } void KConfigPrivate::revertEntry(const QByteArray &group, const char *key, KConfigBase::WriteConfigFlags flags) { KEntryMap::EntryOptions options = convertToOptions(flags); bool dirtied = entryMap.revertEntry(group, key, options); if (dirtied) { bDirty = true; } } QByteArray KConfigPrivate::lookupData(const QByteArray &group, const char *key, KEntryMap::SearchFlags flags) const { if (bReadDefaults) { flags |= KEntryMap::SearchDefaults; } const KEntryMapConstIterator it = entryMap.findEntry(group, key, flags); if (it == entryMap.constEnd()) { return QByteArray(); } return it->mValue; } QString KConfigPrivate::lookupData(const QByteArray &group, const char *key, KEntryMap::SearchFlags flags, bool *expand) const { if (bReadDefaults) { flags |= KEntryMap::SearchDefaults; } return entryMap.getEntry(group, key, QString(), flags, expand); } QStandardPaths::StandardLocation KConfig::locationType() const { Q_D(const KConfig); return d->resourceType; } void KConfig::virtual_hook(int /*id*/, void * /*data*/) { /* nothing */ }