/* This file is part of the KDE libraries SPDX-FileCopyrightText: 1997 Matthias Kalle Dalheimer SPDX-License-Identifier: LGPL-2.0-or-later */ #include "kconfigtest.h" #include "helper.h" #include "config-kconfig.h" #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_UNIX #include #endif #ifndef Q_OS_WIN #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 } // clazy:excludeall=non-pod-global-static static const bool s_bool_entry1 = true; static const bool s_bool_entry2 = false; static const QString s_string_entry1(QStringLiteral("hello")); static const QString s_string_entry2(QStringLiteral(" hello")); static const QString s_string_entry3(QStringLiteral("hello ")); static const QString s_string_entry4(QStringLiteral(" hello ")); static const QString s_string_entry5(QStringLiteral(" ")); static const QString s_string_entry6{}; static const char s_utf8bit_entry[] = "Hello äöü"; static const QString s_translated_string_entry1{QStringLiteral("bonjour")}; static const QByteArray s_bytearray_entry{"\x00\xff\x7f\x3c abc\x00\x00", 10}; static const char s_escapekey[] = " []\0017[]==]"; static const char s_escape_entry[] = "[]\170[]]=3=]\\] "; static const double s_double_entry{123456.78912345}; static const float s_float_entry{123.567f}; static const QPoint s_point_entry{4351, 1235}; static const QSize s_size_entry{10, 20}; static const QRect s_rect_entry{10, 23, 5321, 13}; static const QDateTime s_date_time_entry{QDate{2002, 06, 23}, QTime{12, 55, 40}}; static const QDateTime s_date_time_with_ms_entry{QDate{2002, 06, 23}, QTime{12, 55, 40, 532}}; static const QStringList s_stringlist_entry{QStringLiteral("Hello,"), QStringLiteral("World")}; static const QStringList s_stringlist_empty_entry{}; static const QStringList s_stringlist_just_empty_element{QString{}}; static const QStringList s_stringlist_empty_trailing_element{QStringLiteral("Hi"), QString{}}; static const QStringList s_stringlist_escape_odd_entry{QStringLiteral("Hello\\\\\\"), QStringLiteral("World")}; static const QStringList s_stringlist_escape_even_entry{QStringLiteral("Hello\\\\\\\\"), QStringLiteral("World")}; static const QStringList s_stringlist_escape_comma_entry{QStringLiteral("Hel\\\\\\,\\\\,\\,\\\\\\\\,lo"), QStringLiteral("World")}; static const QList s_int_listentry1{1, 2, 3, 4}; static const QList s_bytearray_list_entry1{"", "1,2", "end"}; static const QVariantList s_variantlist_entry{true, false, QStringLiteral("joe"), 10023}; static const QVariantList s_variantlist_entry2{s_point_entry, s_size_entry}; static const QString s_homepath{homePath() + QLatin1String{"/foo"}}; static const QString s_homepath_escape{homePath() + QLatin1String("/foo/$HOME")}; static const QString s_canonical_homepath{QFileInfo(homePath()).canonicalFilePath() + QLatin1String("/foo")}; static const QString s_dollargroup{QStringLiteral("$i")}; static const QString s_test_subdir{QStringLiteral("kconfigtest_subdir/")}; static const QString s_kconfig_test_subdir(s_test_subdir + QLatin1String("kconfigtest")); static const QString s_kconfig_test_illegal_object_path(s_test_subdir + QLatin1String("kconfig-test")); #ifndef Q_OS_WIN void initLocale() { setenv("LC_ALL", "en_US.utf-8", 1); setenv("TZ", "UTC", 1); } Q_CONSTRUCTOR_FUNCTION(initLocale) #endif void KConfigTest::initTestCase() { // ensure we don't use files in the real config directory QStandardPaths::setTestModeEnabled(true); qRegisterMetaType(); // These two need to be assigned here, after setTestModeEnabled(true), and before cleanupTestCase() m_testConfigDir = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + s_test_subdir; m_kdeGlobalsPath = QDir::cleanPath(m_testConfigDir + QLatin1String("..")) + QLatin1String("/kdeglobals"); // to make sure all files from a previous failed run are deleted cleanupTestCase(); KSharedConfigPtr mainConfig = KSharedConfig::openConfig(); mainConfig->group(QStringLiteral("Main")).writeEntry("Key", "Value"); mainConfig->sync(); KConfig sc(s_kconfig_test_subdir); KConfigGroup cg(&sc, QStringLiteral("AAA")); // deleted later by testDelete cg.writeEntry("stringEntry1", s_string_entry1, KConfig::Persistent | KConfig::Global); cg = KConfigGroup(&sc, QStringLiteral("GlobalGroup")); cg.writeEntry("globalEntry", s_string_entry1, KConfig::Persistent | KConfig::Global); cg.deleteEntry("globalEntry2", KConfig::Global); cg = KConfigGroup(&sc, QStringLiteral("LocalGroupToBeDeleted")); // deleted later by testDelete cg.writeEntry("stringEntry1", s_string_entry1); cg = KConfigGroup(&sc, QStringLiteral("Hello")); cg.writeEntry("boolEntry1", s_bool_entry1); cg.writeEntry("boolEntry2", s_bool_entry2); QByteArray data(s_utf8bit_entry); QCOMPARE(data.size(), 12); // the source file is in utf8 QCOMPARE(QString::fromUtf8(data).length(), 9); cg.writeEntry("Test", data); cg.writeEntry("bytearrayEntry", s_bytearray_entry); cg.writeEntry(s_escapekey, QString::fromLatin1(s_escape_entry)); cg.writeEntry("emptyEntry", ""); cg.writeEntry("stringEntry1", s_string_entry1); cg.writeEntry("stringEntry2", s_string_entry2); cg.writeEntry("stringEntry3", s_string_entry3); cg.writeEntry("stringEntry4", s_string_entry4); cg.writeEntry("stringEntry5", s_string_entry5); cg.writeEntry("urlEntry1", QUrl(QStringLiteral("http://qt-project.org"))); cg.writeEntry("keywith=equalsign", s_string_entry1); cg.deleteEntry("stringEntry5"); cg.deleteEntry("stringEntry6"); // deleting a nonexistent entry cg.writeEntry("byteArrayEntry1", s_string_entry1.toLatin1(), KConfig::Global | KConfig::Persistent); cg.writeEntry("doubleEntry1", s_double_entry); cg.writeEntry("floatEntry1", s_float_entry); sc.deleteGroup(QStringLiteral("deleteMe")); // deleting a nonexistent group cg = KConfigGroup(&sc, QStringLiteral("Complex Types")); cg.writeEntry("rectEntry", s_rect_entry); cg.writeEntry("pointEntry", s_point_entry); cg.writeEntry("sizeEntry", s_size_entry); cg.writeEntry("dateTimeEntry", s_date_time_entry); cg.writeEntry("dateEntry", s_date_time_entry.date()); cg.writeEntry("dateTimeWithMSEntry", s_date_time_with_ms_entry); KConfigGroup ct = cg; cg = KConfigGroup(&ct, QStringLiteral("Nested Group 1")); cg.writeEntry("stringentry1", s_string_entry1); cg = KConfigGroup(&ct, QStringLiteral("Nested Group 2")); cg.writeEntry("stringEntry2", s_string_entry2); cg = KConfigGroup(&cg, QStringLiteral("Nested Group 2.1")); cg.writeEntry("stringEntry3", s_string_entry3); cg = KConfigGroup(&ct, QStringLiteral("Nested Group 3")); cg.writeEntry("stringEntry3", s_string_entry3); cg = KConfigGroup(&sc, QStringLiteral("List Types")); cg.writeEntry("listOfIntsEntry1", s_int_listentry1); cg.writeEntry("listOfByteArraysEntry1", s_bytearray_list_entry1); cg.writeEntry("stringListEntry", s_stringlist_entry); cg.writeEntry("stringListEmptyEntry", s_stringlist_empty_entry); cg.writeEntry("stringListJustEmptyElement", s_stringlist_just_empty_element); cg.writeEntry("stringListEmptyTrailingElement", s_stringlist_empty_trailing_element); cg.writeEntry("stringListEscapeOddEntry", s_stringlist_escape_odd_entry); cg.writeEntry("stringListEscapeEvenEntry", s_stringlist_escape_even_entry); cg.writeEntry("stringListEscapeCommaEntry", s_stringlist_escape_comma_entry); cg.writeEntry("variantListEntry", s_variantlist_entry); cg = KConfigGroup(&sc, QStringLiteral("Path Type")); cg.writePathEntry("homepath", s_homepath); cg.writePathEntry("homepathescape", s_homepath_escape); cg.writePathEntry("canonicalHomePath", s_canonical_homepath); cg = KConfigGroup(&sc, QStringLiteral("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, QStringLiteral("ParentGroup")); KConfigGroup cg1(&cg, QStringLiteral("SubGroup1")); cg1.writeEntry("somestring", "somevalue"); cg.writeEntry("parentgrpstring", "somevalue"); KConfigGroup cg2(&cg, QStringLiteral("SubGroup2")); cg2.writeEntry("substring", "somevalue"); KConfigGroup cg3(&cg, QStringLiteral("SubGroup/3")); cg3.writeEntry("sub3string", "somevalue"); QVERIFY(sc.isDirty()); QVERIFY(sc.sync()); QVERIFY(!sc.isDirty()); QVERIFY2(QFile::exists(m_testConfigDir + QLatin1String("/kconfigtest")), qPrintable(m_testConfigDir + QLatin1String("/kconfigtest must exist"))); QVERIFY2(QFile::exists(m_kdeGlobalsPath), qPrintable(m_kdeGlobalsPath + QStringLiteral(" must exist"))); KConfig sc1(s_test_subdir + QLatin1String("kdebugrc"), KConfig::SimpleConfig); KConfigGroup sg0(&sc1, QStringLiteral("0")); sg0.writeEntry("AbortFatal", false); sg0.writeEntry("WarnOutput", 0); sg0.writeEntry("FatalOutput", 0); QVERIFY(sc1.sync()); // Setup stuff to test KConfig::addConfigSources() KConfig devcfg(s_test_subdir + QLatin1String("specificrc")); KConfigGroup devonlygrp(&devcfg, QStringLiteral("Specific Only Group")); devonlygrp.writeEntry("ExistingEntry", "DevValue"); KConfigGroup devandbasegrp(&devcfg, QStringLiteral("Shared Group")); devandbasegrp.writeEntry("SomeSharedEntry", "DevValue"); devandbasegrp.writeEntry("SomeSpecificOnlyEntry", "DevValue"); QVERIFY(devcfg.sync()); KConfig basecfg(s_test_subdir + QLatin1String("baserc")); KConfigGroup basegrp(&basecfg, QStringLiteral("Base Only Group")); basegrp.writeEntry("ExistingEntry", "BaseValue"); KConfigGroup baseanddevgrp(&basecfg, QStringLiteral("Shared Group")); baseanddevgrp.writeEntry("SomeSharedEntry", "BaseValue"); baseanddevgrp.writeEntry("SomeBaseOnlyEntry", "BaseValue"); QVERIFY(basecfg.sync()); KConfig gecfg(s_test_subdir + QLatin1String("groupescapetest"), KConfig::SimpleConfig); cg = KConfigGroup(&gecfg, s_dollargroup); cg.writeEntry("entry", "doesntmatter"); } void KConfigTest::cleanupTestCase() { // ensure we don't delete the real directory QDir localConfig(m_testConfigDir); // qDebug() << "Erasing" << localConfig; if (localConfig.exists()) { QVERIFY(localConfig.removeRecursively()); } QVERIFY(!localConfig.exists()); if (QFile::exists(m_kdeGlobalsPath)) { QVERIFY(QFile::remove(m_kdeGlobalsPath)); } } static QList readLinesFrom(const QString &path) { QFile file(path); const bool opened = file.open(QIODevice::ReadOnly | QIODevice::Text); QList lines; if (!opened) { qWarning() << "Failed to open" << path; return lines; } QByteArray line; do { line = file.readLine(); if (!line.isEmpty()) { lines.append(line); } } while (!line.isEmpty()); return lines; } static const QString s_defaultArg = s_test_subdir + QLatin1String("kconfigtest"); static QList readLines(const QString &fileName = s_defaultArg) { const QString path = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); Q_ASSERT(!path.isEmpty()); return readLinesFrom(path + QLatin1Char('/') + fileName); } // see also testDefaults, which tests reverting with a defaults (global) file available void KConfigTest::testDirtyAfterRevert() { KConfig sc(s_test_subdir + QLatin1String("kconfigtest_revert")); KConfigGroup cg(&sc, QStringLiteral("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(s_test_subdir + QLatin1String("konfigtest2"), KConfig::SimpleConfig); KConfigGroup cg(&sc, QStringLiteral("Hello")); cg.writeEntry("Test", "Correct"); } { KConfig sc(s_test_subdir + QLatin1String("konfigtest2"), KConfig::SimpleConfig); KConfigGroup cg(&sc, QStringLiteral("Hello")); QCOMPARE(cg.readEntry("Test", "Default"), QStringLiteral("Correct")); cg.revertToDefault("Test"); } KConfig sc(s_test_subdir + QLatin1String("konfigtest2"), KConfig::SimpleConfig); KConfigGroup cg(&sc, QStringLiteral("Hello")); QCOMPARE(cg.readEntry("Test", "Default"), QStringLiteral("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(s_kconfig_test_subdir); QCOMPARE(sc2.name(), s_test_subdir + QLatin1String{"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 != QLatin1String("")); QVERIFY(!group.contains(QChar(0x1d))); } KConfigGroup sc3(&sc2, QStringLiteral("GlobalGroup")); QVERIFY(sc3.hasKey("globalEntry")); // from kdeglobals QVERIFY(!sc3.isEntryImmutable("globalEntry")); QCOMPARE(sc3.readEntry("globalEntry"), s_string_entry1); QVERIFY(!sc3.hasKey("globalEntry2")); QCOMPARE(sc3.readEntry("globalEntry2", QStringLiteral("bla")), QStringLiteral("bla")); QVERIFY(!sc3.hasDefault("globalEntry")); sc3 = KConfigGroup(&sc2, QStringLiteral("Hello")); QCOMPARE(sc3.readEntry("Test", QByteArray()), QByteArray(s_utf8bit_entry)); QCOMPARE(sc3.readEntry("bytearrayEntry", QByteArray()), s_bytearray_entry); QCOMPARE(sc3.readEntry(s_escapekey), QString::fromLatin1(s_escape_entry)); QCOMPARE(sc3.readEntry("Test", QString{}), QString::fromUtf8(s_utf8bit_entry)); QCOMPARE(sc3.readEntry("emptyEntry" /*, QString("Fietsbel")*/), QLatin1String("")); QCOMPARE(sc3.readEntry("emptyEntry", QStringLiteral("Fietsbel")).isEmpty(), true); QCOMPARE(sc3.readEntry("stringEntry1"), s_string_entry1); QCOMPARE(sc3.readEntry("stringEntry2"), s_string_entry2); QCOMPARE(sc3.readEntry("stringEntry3"), s_string_entry3); QCOMPARE(sc3.readEntry("stringEntry4"), s_string_entry4); QVERIFY(!sc3.hasKey("stringEntry5")); QCOMPARE(sc3.readEntry("stringEntry5", QStringLiteral("test")), QStringLiteral("test")); QVERIFY(!sc3.hasKey("stringEntry6")); QCOMPARE(sc3.readEntry("stringEntry6", QStringLiteral("foo")), QStringLiteral("foo")); QCOMPARE(sc3.readEntry("urlEntry1", QUrl{}), QUrl(QStringLiteral("http://qt-project.org"))); QCOMPARE(sc3.readEntry("boolEntry1", s_bool_entry1), s_bool_entry1); QCOMPARE(sc3.readEntry("boolEntry2", false), s_bool_entry2); QCOMPARE(sc3.readEntry("keywith=equalsign", QStringLiteral("wrong")), s_string_entry1); QCOMPARE(sc3.readEntry("byteArrayEntry1", QByteArray{}), s_string_entry1.toLatin1()); QCOMPARE(sc3.readEntry("doubleEntry1", 0.0), s_double_entry); QCOMPARE(sc3.readEntry("floatEntry1", 0.0f), s_float_entry); } void KConfigTest::testDefaults() { KConfig config(s_test_subdir + QLatin1String("defaulttest"), KConfig::NoGlobals); const QString defaultsFile = s_test_subdir + QLatin1String("defaulttest.defaults"); KConfig defaults(defaultsFile, KConfig::SimpleConfig); const QString defaultsFilePath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + defaultsFile; const QString Default(QStringLiteral("Default")); const QString NotDefault(QStringLiteral("Not Default")); const QString Value1(s_string_entry1); const QString Value2(s_string_entry2); KConfigGroup group = defaults.group(QStringLiteral("any group")); group.writeEntry("entry1", Default); QVERIFY(group.sync()); group = config.group(QStringLiteral("any group")); group.writeEntry("entry1", Value1); group.writeEntry("entry2", Value2); QVERIFY(group.sync()); config.addConfigSources(QStringList{defaultsFilePath}); 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(s_test_subdir + QLatin1String("defaulttest"), KConfig::NoGlobals); reader.addConfigSources(QStringList{defaultsFilePath}); KConfigGroup readerGroup = reader.group(QStringLiteral("any group")); QCOMPARE(readerGroup.readEntry("entry1", QString()), Default); QCOMPARE(readerGroup.readEntry("entry2", QString()), QString()); } void KConfigTest::testLocale() { KConfig config(s_test_subdir + QLatin1String("kconfigtest.locales"), KConfig::SimpleConfig); const QString Translated(s_translated_string_entry1); const QString Untranslated(s_string_entry1); KConfigGroup group = config.group(QStringLiteral("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() { const QString groupstr = QString::fromUtf8("UTF-8:\xc3\xb6l"); const QString path = s_test_subdir + QLatin1String("kconfigtestencodings"); KConfig c(path); KConfigGroup cg(&c, groupstr); cg.writeEntry("key", "value"); QVERIFY(c.sync()); const QList lines = readLines(path); QCOMPARE(lines.count(), 2); QCOMPARE(lines.first(), QByteArray("[UTF-8:\xc3\xb6l]\n")); KConfig c2(path); KConfigGroup cg2(&c2, groupstr); QCOMPARE(cg2.readEntry("key"), QStringLiteral("value")); QVERIFY(c2.groupList().contains(groupstr)); } void KConfigTest::testLists() { KConfig sc2(s_kconfig_test_subdir); KConfigGroup sc3(&sc2, QStringLiteral("List Types")); QCOMPARE(sc3.readEntry("stringListEntry", QStringList{}), s_stringlist_entry); QCOMPARE(sc3.readEntry(QStringLiteral("stringListEmptyEntry"), QStringList(QStringLiteral("wrong"))), s_stringlist_empty_entry); QCOMPARE(sc3.readEntry(QStringLiteral("stringListJustEmptyElement"), QStringList()), s_stringlist_just_empty_element); QCOMPARE(sc3.readEntry(QStringLiteral("stringListEmptyTrailingElement"), QStringList()), s_stringlist_empty_trailing_element); QCOMPARE(sc3.readEntry(QStringLiteral("stringListEscapeOddEntry"), QStringList()), s_stringlist_escape_odd_entry); QCOMPARE(sc3.readEntry(QStringLiteral("stringListEscapeEvenEntry"), QStringList()), s_stringlist_escape_even_entry); QCOMPARE(sc3.readEntry(QStringLiteral("stringListEscapeCommaEntry"), QStringList()), s_stringlist_escape_comma_entry); QCOMPARE(sc3.readEntry("listOfIntsEntry1"), QString::fromLatin1("1,2,3,4")); QList expectedIntList = s_int_listentry1; QVERIFY(sc3.readEntry("listOfIntsEntry1", QList()) == expectedIntList); QCOMPARE(QVariant(sc3.readEntry("variantListEntry", s_variantlist_entry)).toStringList(), QVariant(s_variantlist_entry).toStringList()); QCOMPARE(sc3.readEntry("listOfByteArraysEntry1", QList()), s_bytearray_list_entry1); } void KConfigTest::testPath() { KConfig sc2(s_kconfig_test_subdir); KConfigGroup sc3(&sc2, QStringLiteral("Path Type")); QCOMPARE(sc3.readPathEntry(QStringLiteral("homepath"), QString{}), s_homepath); QCOMPARE(sc3.readPathEntry(QStringLiteral("homepathescape"), QString{}), s_homepath_escape); QCOMPARE(sc3.readPathEntry(QStringLiteral("canonicalHomePath"), QString{}), s_canonical_homepath); QCOMPARE(sc3.entryMap().value(QStringLiteral("homepath")), s_homepath); qputenv("WITHSLASH", "/a/"); { QFile file(m_testConfigDir + QLatin1String("/pathtest")); file.open(QIODevice::WriteOnly | QIODevice::Text); QTextStream out(&file); out << "[Test Group]\n" << "homePath=$HOME/foo\n" << "homePath2=file://$HOME/foo\n" << "withSlash=$WITHSLASH/foo\n" << "withSlash2=$WITHSLASH\n" << "withBraces[$e]=file://${HOME}/foo\n" << "URL[$e]=file://${HOME}/foo\n" << "hostname[$e]=$(hostname)\n" << "escapes=aaa,bb/b,ccc\\,ccc\n" << "noeol=foo" // no EOL ; } KConfig cf2(s_test_subdir + QLatin1String("pathtest")); KConfigGroup group = cf2.group(QStringLiteral("Test Group")); QVERIFY(group.hasKey("homePath")); QCOMPARE(group.readPathEntry("homePath", QString{}), s_homepath); QVERIFY(group.hasKey("homePath2")); QCOMPARE(group.readPathEntry("homePath2", QString{}), QLatin1String("file://") + s_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{}), QLatin1String("file://") + s_homepath); QVERIFY(group.hasKey("URL")); QCOMPARE(group.readEntry("URL", QString{}), QLatin1String("file://") + s_homepath); QVERIFY(group.hasKey("hostname")); QCOMPARE(group.readEntry("hostname", QString{}), QStringLiteral("(hostname)")); // the $ got removed because empty var name QVERIFY(group.hasKey("noeol")); QCOMPARE(group.readEntry("noeol", QString{}), QStringLiteral("foo")); const auto val = QStringList{QStringLiteral("aaa"), QStringLiteral("bb/b"), QStringLiteral("ccc,ccc")}; QCOMPARE(group.readPathEntry(QStringLiteral("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(s_kconfig_test_subdir); KConfigGroup sc3(&sc2, QStringLiteral("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(m_testConfigDir + QLatin1String("/pathtest")); file.open(QIODevice::WriteOnly | QIODevice::Text); QTextStream out(&file); out << "[Test Group]\n" << "dataDir[$e]=$QT_DATA_HOME/kconfigtest\n" << "cacheDir[$e]=$QT_CACHE_HOME/kconfigtest\n" << "configDir[$e]=$QT_CONFIG_HOME/kconfigtest\n"; } KConfig cf2(s_test_subdir + QLatin1String("pathtest")); KConfigGroup group = cf2.group(QStringLiteral("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(s_kconfig_test_subdir); KConfigGroup sc3(&sc2, QStringLiteral("Complex Types")); QCOMPARE(sc3.readEntry("pointEntry", QPoint()), s_point_entry); QCOMPARE(sc3.readEntry("sizeEntry", s_size_entry), s_size_entry); QCOMPARE(sc3.readEntry("rectEntry", QRect(1, 2, 3, 4)), s_rect_entry); QCOMPARE(sc3.readEntry("dateTimeEntry", QDateTime()).toString(Qt::ISODateWithMs), s_date_time_entry.toString(Qt::ISODateWithMs)); QCOMPARE(sc3.readEntry("dateEntry", QDate()).toString(Qt::ISODate), s_date_time_entry.date().toString(Qt::ISODate)); QCOMPARE(sc3.readEntry("dateTimeWithMSEntry", QDateTime()).toString(Qt::ISODateWithMs), s_date_time_with_ms_entry.toString(Qt::ISODateWithMs)); QCOMPARE(sc3.readEntry("dateTimeEntry", QDate()), s_date_time_entry.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(s_kconfig_test_subdir); KConfigGroup sc3(&sc, QStringLiteral("Enum Types")); QCOMPARE(sc3.readEntry("enum-10"), QStringLiteral("Tens")); QVERIFY(sc3.readEntry("enum-100", Ones) != Ones); QVERIFY(sc3.readEntry("enum-100", Ones) != Tens); QCOMPARE(sc3.readEntry("flags-bit0"), QStringLiteral("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::fromLatin1(me.valueToKeys(bitfield))); QVERIFY(sc3.readEntry("flags-bit0-bit1", Flags()) == bitfield); } void KConfigTest::testEntryMap() { KConfig sc(s_kconfig_test_subdir); KConfigGroup cg(&sc, QStringLiteral("Hello")); QMap entryMap = cg.entryMap(); qDebug() << entryMap.keys(); QCOMPARE(entryMap.value(QStringLiteral("stringEntry1")), s_string_entry1); QCOMPARE(entryMap.value(QStringLiteral("stringEntry2")), s_string_entry2); QCOMPARE(entryMap.value(QStringLiteral("stringEntry3")), s_string_entry3); QCOMPARE(entryMap.value(QStringLiteral("stringEntry4")), s_string_entry4); QVERIFY(!entryMap.contains(QStringLiteral("stringEntry5"))); QVERIFY(!entryMap.contains(QStringLiteral("stringEntry6"))); QCOMPARE(entryMap.value(QStringLiteral("Test")), QString::fromUtf8(s_utf8bit_entry)); QCOMPARE(entryMap.value(QStringLiteral("bytearrayEntry")), QString::fromUtf8(s_bytearray_entry.constData())); QCOMPARE(entryMap.value(QStringLiteral("emptyEntry")), QString{}); QVERIFY(entryMap.contains(QStringLiteral("emptyEntry"))); QCOMPARE(entryMap.value(QStringLiteral("boolEntry1")), s_bool_entry1 ? QStringLiteral("true") : QStringLiteral("false")); QCOMPARE(entryMap.value(QStringLiteral("boolEntry2")), s_bool_entry2 ? QStringLiteral("true") : QStringLiteral("false")); QCOMPARE(entryMap.value(QStringLiteral("keywith=equalsign")), s_string_entry1); QCOMPARE(entryMap.value(QStringLiteral("byteArrayEntry1")), s_string_entry1); QCOMPARE(entryMap.value(QStringLiteral("doubleEntry1")).toDouble(), s_double_entry); QCOMPARE(entryMap.value(QStringLiteral("floatEntry1")).toFloat(), s_float_entry); } void KConfigTest::testInvalid() { KConfig sc(s_kconfig_test_subdir); // all of these should print a message to the kdebug.dbg file KConfigGroup sc3(&sc, QStringLiteral("Invalid Types")); sc3.writeEntry("badList", s_variantlist_entry2); 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(s_kconfig_test_subdir); KConfigGroup sc3(&sc, QStringLiteral("Hello")); QCOMPARE(sc3.name(), QStringLiteral("Hello")); KConfigGroup newGroup(sc3); KConfigGroup rootGroup(sc.group(QString())); QCOMPARE(rootGroup.name(), QStringLiteral("")); KConfigGroup sc32(rootGroup.group(QStringLiteral("Hello"))); QCOMPARE(sc32.name(), QStringLiteral("Hello")); KConfigGroup newGroup2(sc32); } // Simple test for deleteEntry void KConfigTest::testDeleteEntry() { const QString configFile = s_test_subdir + QLatin1String("kconfigdeletetest"); { KConfig conf(configFile); conf.group(QStringLiteral("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, QStringLiteral("Hello")); group.deleteEntry("DelKey"); QCOMPARE(group.readEntry("DelKey", QStringLiteral("Fietsbel")), QStringLiteral("Fietsbel")); QVERIFY(group.sync()); Q_ASSERT(!readLines(configFile).contains("DelKey=ToBeDeleted\n")); QCOMPARE(group.readEntry("DelKey", QStringLiteral("still deleted")), QStringLiteral("still deleted")); } void KConfigTest::testDelete() { KConfig sc(s_kconfig_test_subdir); KConfigGroup ct(&sc, QStringLiteral("Complex Types")); // First delete a nested group KConfigGroup delgr(&ct, QStringLiteral("Nested Group 3")); QVERIFY(delgr.exists()); QVERIFY(ct.hasGroup(QStringLiteral("Nested Group 3"))); QVERIFY(ct.groupList().contains(QStringLiteral("Nested Group 3"))); delgr.deleteGroup(); QVERIFY(!delgr.exists()); QVERIFY(!ct.hasGroup(QStringLiteral("Nested Group 3"))); QVERIFY(!ct.groupList().contains(QStringLiteral("Nested Group 3"))); KConfigGroup ng(&ct, QStringLiteral("Nested Group 2")); QVERIFY(sc.hasGroup(QStringLiteral("Complex Types"))); QVERIFY(sc.groupList().contains(QStringLiteral("Complex Types"))); QVERIFY(!sc.hasGroup(QStringLiteral("Does not exist"))); QVERIFY(ct.hasGroup(QStringLiteral("Nested Group 1"))); QVERIFY(ct.groupList().contains(QStringLiteral("Nested Group 1"))); sc.deleteGroup(QStringLiteral("Complex Types")); QCOMPARE(sc.group(QStringLiteral("Complex Types")).keyList().count(), 0); QVERIFY(!sc.hasGroup(QStringLiteral("Complex Types"))); // #192266 QVERIFY(!sc.group(QStringLiteral("Complex Types")).exists()); QVERIFY(!sc.groupList().contains(QStringLiteral("Complex Types"))); QVERIFY(!ct.hasGroup(QStringLiteral("Nested Group 1"))); QVERIFY(!ct.groupList().contains(QStringLiteral("Nested Group 1"))); QCOMPARE(ct.group(QStringLiteral("Nested Group 1")).keyList().count(), 0); QCOMPARE(ct.group(QStringLiteral("Nested Group 2")).keyList().count(), 0); QCOMPARE(ng.group(QStringLiteral("Nested Group 2.1")).keyList().count(), 0); KConfigGroup cg(&sc, QStringLiteral("AAA")); cg.deleteGroup(); QVERIFY(sc.entryMap(QStringLiteral("Complex Types")).isEmpty()); QVERIFY(sc.entryMap(QStringLiteral("AAA")).isEmpty()); QVERIFY(!sc.entryMap(QStringLiteral("Hello")).isEmpty()); // not deleted group QVERIFY(sc.entryMap(QStringLiteral("FooBar")).isEmpty()); // inexistent group KConfigGroup(&sc, QStringLiteral("LocalGroupToBeDeleted")).deleteGroup(); 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("[LocalGroupToBeDeleted]\n")); QVERIFY(lines.contains("[AAA]\n")); // deleted from kconfigtest, but present in kdeglobals, so [$d] 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(s_kconfig_test_subdir, KConfig::SimpleConfig); // make sure there are no defaults cg = cf.group(QStringLiteral("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(s_kconfig_test_subdir); KConfigGroup defaultGroup(&sc, QStringLiteral("")); QCOMPARE(defaultGroup.name(), QStringLiteral("")); QVERIFY(!defaultGroup.exists()); defaultGroup.writeEntry("TestKey", "defaultGroup"); QVERIFY(defaultGroup.exists()); QCOMPARE(defaultGroup.readEntry("TestKey", QString{}), QStringLiteral("defaultGroup")); QVERIFY(sc.sync()); { // Test reading it KConfig sc2(s_kconfig_test_subdir); KConfigGroup defaultGroup2(&sc2, QStringLiteral("")); QCOMPARE(defaultGroup2.name(), QStringLiteral("")); QVERIFY(defaultGroup2.exists()); QCOMPARE(defaultGroup2.readEntry("TestKey", QString{}), QStringLiteral("defaultGroup")); } { // Test reading it KConfig sc2(s_kconfig_test_subdir); KConfigGroup emptyGroup(&sc2, QString()); QCOMPARE(emptyGroup.name(), QStringLiteral("")); QVERIFY(emptyGroup.exists()); QCOMPARE(emptyGroup.readEntry("TestKey", QString{}), QStringLiteral("defaultGroup")); } QList lines = readLines(); QVERIFY(!lines.contains("[]\n")); QVERIFY(!lines.isEmpty()); 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 != QLatin1String("")); } defaultGroup.deleteGroup(); QVERIFY(sc.sync()); // Test if deleteGroup worked lines = readLines(); QVERIFY(lines.first() != QByteArray("TestKey=defaultGroup\n")); } void KConfigTest::testEmptyGroup() { KConfig sc(s_kconfig_test_subdir); KConfigGroup emptyGroup(&sc, QString()); QCOMPARE(emptyGroup.name(), QStringLiteral("")); // confusing, heh? QVERIFY(!emptyGroup.exists()); emptyGroup.writeEntry("TestKey", "emptyGroup"); QVERIFY(emptyGroup.exists()); QCOMPARE(emptyGroup.readEntry("TestKey", QString{}), QStringLiteral("emptyGroup")); QVERIFY(sc.sync()); { // Test reading it KConfig sc2(s_kconfig_test_subdir); KConfigGroup defaultGroup(&sc2, QStringLiteral("")); QCOMPARE(defaultGroup.name(), QStringLiteral("")); QVERIFY(defaultGroup.exists()); QCOMPARE(defaultGroup.readEntry("TestKey", QString{}), QStringLiteral("emptyGroup")); } { // Test reading it KConfig sc2(s_kconfig_test_subdir); KConfigGroup emptyGroup2(&sc2, QString()); QCOMPARE(emptyGroup2.name(), QStringLiteral("")); QVERIFY(emptyGroup2.exists()); QCOMPARE(emptyGroup2.readEntry("TestKey", QString{}), QStringLiteral("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 != QLatin1String("")); } 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; const QByteArray oldConfigDirs = qgetenv("XDG_CONFIG_DIRS"); qputenv("XDG_CONFIG_DIRS", qPrintable(middleDir.path() + QLatin1Char(':') + globalDir.path())); const QString globalConfigDir = globalDir.path() + QLatin1Char('/') + s_test_subdir; QVERIFY(QDir().mkpath(globalConfigDir)); QFile global(globalConfigDir + QLatin1String("foo.desktop")); QVERIFY(global.open(QIODevice::WriteOnly | QIODevice::Text)); QTextStream globalOut(&global); globalOut << "[Group]\n" << "FromGlobal=true\n" << "FromGlobal[fr]=vrai\n" << "Name=Testing\n" << "Name[fr]=FR\n" << "Other=Global\n" << "Other[fr]=Global_FR\n"; global.close(); const QString middleConfigDir = middleDir.path() + QLatin1Char('/') + s_test_subdir; QVERIFY(QDir().mkpath(middleConfigDir)); QFile local(middleConfigDir + QLatin1String("foo.desktop")); QVERIFY(local.open(QIODevice::WriteOnly | QIODevice::Text)); QTextStream out(&local); out << "[Group]\n" << "FromLocal=true\n" << "FromLocal[fr]=vrai\n" << "Name=Local Testing\n" << "Name[fr]=FR\n" << "Other=English Only\n"; local.close(); KConfig config(s_test_subdir + QLatin1String("foo.desktop")); KConfigGroup group = config.group(QStringLiteral("Group")); QCOMPARE(group.readEntry("FromGlobal"), QStringLiteral("true")); QCOMPARE(group.readEntry("FromLocal"), QStringLiteral("true")); QCOMPARE(group.readEntry("Name"), QStringLiteral("Local Testing")); config.setLocale(QStringLiteral("fr")); QCOMPARE(group.readEntry("FromGlobal"), QStringLiteral("vrai")); QCOMPARE(group.readEntry("FromLocal"), QStringLiteral("vrai")); QCOMPARE(group.readEntry("Name"), QStringLiteral("FR")); QCOMPARE(group.readEntry("Other"), QStringLiteral("English Only")); // Global_FR is locally overridden qputenv("XDG_CONFIG_DIRS", oldConfigDirs); #endif } void KConfigTest::testMerge() { DefaultLocale defaultLocale; QLocale::setDefault(QLocale::c()); KConfig config(s_test_subdir + QLatin1String("mergetest"), KConfig::SimpleConfig); KConfigGroup cg = config.group(QStringLiteral("some group")); cg.writeEntry("entry", " random entry"); cg.writeEntry("another entry", "blah blah blah"); { // simulate writing by another process QFile file(m_testConfigDir + QLatin1String("/mergetest")); file.open(QIODevice::WriteOnly | QIODevice::Text); QTextStream out(&file); out << "[Merged Group]\n" << "entry1=Testing\n" << "entry2=More Testing\n" << "[some group]\n" << "entry[fr]=French\n" << "entry[es]=Spanish\n" << "entry[de]=German\n"; } 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(m_testConfigDir + QLatin1String("/mergetest")); file.open(QIODevice::ReadOnly | QIODevice::Text); for (const QByteArray &line : std::as_const(lines)) { QCOMPARE(line, file.readLine()); } } } void KConfigTest::testImmutable() { { QFile file(m_testConfigDir + QLatin1String("/immutabletest")); file.open(QIODevice::WriteOnly | QIODevice::Text); QTextStream out(&file); out << "[$i]\n" << "entry1=Testing\n" << "[group][$i]\n" << "[group][subgroup][$i]\n"; } KConfig config(s_test_subdir + QLatin1String("immutabletest"), KConfig::SimpleConfig); QVERIFY(config.isGroupImmutable(QString())); KConfigGroup cg = config.group(QString()); QVERIFY(cg.isEntryImmutable("entry1")); KConfigGroup cg1 = config.group(QStringLiteral("group")); QVERIFY(cg1.isImmutable()); KConfigGroup cg1a = cg.group(QStringLiteral("group")); QVERIFY(cg1a.isImmutable()); KConfigGroup cg2 = cg1.group(QStringLiteral("subgroup")); QVERIFY(cg2.isImmutable()); } void KConfigTest::testOptionOrder() { { QFile file(m_testConfigDir + QLatin1String("/doubleattrtest")); file.open(QIODevice::WriteOnly | QIODevice::Text); QTextStream out(&file); out << "[group3]\n" << "entry2=unlocalized\n" << "entry2[$i][de_DE]=t2\n"; } KConfig config(s_test_subdir + QLatin1String("doubleattrtest"), KConfig::SimpleConfig); config.setLocale(QStringLiteral("de_DE")); KConfigGroup cg3 = config.group(QStringLiteral("group3")); QVERIFY(!cg3.isImmutable()); QCOMPARE(cg3.readEntry("entry2", ""), QStringLiteral("t2")); QVERIFY(cg3.isEntryImmutable("entry2")); config.setLocale(QStringLiteral("C")); QCOMPARE(cg3.readEntry("entry2", ""), QStringLiteral("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(m_testConfigDir + QLatin1String("/doubleattrtest")); file.open(QIODevice::ReadOnly | QIODevice::Text); for (const QByteArray &line : std::as_const(lines)) { QCOMPARE(line, file.readLine()); } } } void KConfigTest::testGroupEscape() { KConfig config(s_test_subdir + QLatin1String("groupescapetest"), KConfig::SimpleConfig); QVERIFY(config.group(s_dollargroup).exists()); } void KConfigTest::testSubGroup() { KConfig sc(s_kconfig_test_subdir); KConfigGroup cg(&sc, QStringLiteral("ParentGroup")); QCOMPARE(cg.readEntry("parentgrpstring", ""), QStringLiteral("somevalue")); KConfigGroup subcg1(&cg, QStringLiteral("SubGroup1")); QCOMPARE(subcg1.name(), QStringLiteral("SubGroup1")); QCOMPARE(subcg1.readEntry("somestring", ""), QStringLiteral("somevalue")); KConfigGroup subcg2(&cg, QStringLiteral("SubGroup2")); QCOMPARE(subcg2.name(), QStringLiteral("SubGroup2")); QCOMPARE(subcg2.readEntry("substring", ""), QStringLiteral("somevalue")); KConfigGroup subcg3(&cg, QStringLiteral("SubGroup/3")); QCOMPARE(subcg3.readEntry("sub3string", ""), QStringLiteral("somevalue")); QCOMPARE(subcg3.name(), QStringLiteral("SubGroup/3")); KConfigGroup rcg(&sc, QString()); KConfigGroup srcg(&rcg, QStringLiteral("ParentGroup")); QCOMPARE(srcg.readEntry("parentgrpstring", ""), QStringLiteral("somevalue")); QStringList groupList = cg.groupList(); groupList.sort(); // comes from QSet, so order is undefined QCOMPARE(groupList, (QStringList{QStringLiteral("SubGroup/3"), QStringLiteral("SubGroup1"), QStringLiteral("SubGroup2")})); const QStringList expectedSubgroup3Keys{QStringLiteral("sub3string")}; QCOMPARE(subcg3.keyList(), expectedSubgroup3Keys); const QStringList expectedParentGroupKeys{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, QStringLiteral("NoEntryGroup")); KConfigGroup negsub1(&neg, QStringLiteral("NEG Child1")); negsub1.writeEntry("entry", "somevalue"); KConfigGroup negsub2(&neg, QStringLiteral("NEG Child2")); KConfigGroup negsub3(&neg, QStringLiteral("NEG Child3")); KConfigGroup negsub31(&negsub3, QStringLiteral("NEG Child3-1")); KConfigGroup negsub4(&neg, QStringLiteral("NEG Child4")); KConfigGroup negsub41(&negsub4, QStringLiteral("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{QStringLiteral("NEG Child1"), QStringLiteral("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 != QLatin1String("")); QVERIFY(!group.contains(QChar(0x1d))); QVERIFY(!group.contains(QLatin1String("subgroup"))); QVERIFY(!group.contains(QLatin1String("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(s_test_subdir + QLatin1String("specificrc")); cf.addConfigSources(QStringList{m_testConfigDir + QLatin1String("/baserc")}); cf.reparseConfiguration(); KConfigGroup specificgrp(&cf, QStringLiteral("Specific Only Group")); QCOMPARE(specificgrp.readEntry("ExistingEntry", ""), QStringLiteral("DevValue")); KConfigGroup sharedgrp(&cf, QStringLiteral("Shared Group")); QCOMPARE(sharedgrp.readEntry("SomeSpecificOnlyEntry", ""), QStringLiteral("DevValue")); QCOMPARE(sharedgrp.readEntry("SomeBaseOnlyEntry", ""), QStringLiteral("BaseValue")); QCOMPARE(sharedgrp.readEntry("SomeSharedEntry", ""), QStringLiteral("DevValue")); KConfigGroup basegrp(&cf, QStringLiteral("Base Only Group")); QCOMPARE(basegrp.readEntry("ExistingEntry", ""), QStringLiteral("BaseValue")); basegrp.writeEntry("New Entry Base Only", "SomeValue"); KConfigGroup newgrp(&cf, QStringLiteral("New Group")); newgrp.writeEntry("New Entry", "SomeValue"); QVERIFY(cf.sync()); KConfig plaincfg(s_test_subdir + QLatin1String("specificrc")); KConfigGroup newgrp2(&plaincfg, QStringLiteral("New Group")); QCOMPARE(newgrp2.readEntry("New Entry", ""), QStringLiteral("SomeValue")); KConfigGroup basegrp2(&plaincfg, QStringLiteral("Base Only Group")); QCOMPARE(basegrp2.readEntry("New Entry Base Only", ""), QStringLiteral("SomeValue")); } void KConfigTest::testGroupCopyTo() { KConfig cf1(s_kconfig_test_subdir); KConfigGroup original = cf1.group(QStringLiteral("Enum Types")); KConfigGroup copy = cf1.group(QStringLiteral("Enum Types Copy")); original.copyTo(©); // copy from one group to another QCOMPARE(copy.entryMap(), original.entryMap()); KConfig cf2(s_test_subdir + QLatin1String("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(s_kconfig_test_subdir); // Prepare source file KConfigGroup group(&cf1, QStringLiteral("CopyToTest")); group.writeEntry("Type", "Test"); QVERIFY(cf1.sync()); // Copy to "destination" const QString destination = m_testConfigDir + QLatin1String("/kconfigcopytotest"); QFile::remove(destination); KConfig cf2(s_test_subdir + QLatin1String("kconfigcopytotest")); KConfigGroup group2(&cf2, QStringLiteral("CopyToTest")); group.copyTo(&group2); QString testVal = group2.readEntry("Type"); QCOMPARE(testVal, QStringLiteral("Test")); // should write to disk the copied data from group QVERIFY(cf2.sync()); QVERIFY(QFile::exists(destination)); } void KConfigTest::testConfigCopyTo() { KConfig cf1(s_kconfig_test_subdir); { // Prepare source file KConfigGroup group(&cf1, QStringLiteral("CopyToTest")); group.writeEntry("Type", "Test"); QVERIFY(cf1.sync()); } { // Copy to "destination" const QString destination = m_testConfigDir + QLatin1String("/kconfigcopytotest"); QFile::remove(destination); KConfig cf2; cf1.copyTo(destination, &cf2); KConfigGroup group2(&cf2, QStringLiteral("CopyToTest")); QString testVal = group2.readEntry("Type"); QCOMPARE(testVal, QStringLiteral("Test")); QVERIFY(cf2.sync()); QVERIFY(QFile::exists(destination)); } // Check copied config file on disk KConfig cf3(s_test_subdir + QLatin1String("kconfigcopytotest")); KConfigGroup group3(&cf3, QStringLiteral("CopyToTest")); QString testVal = group3.readEntry("Type"); QCOMPARE(testVal, QStringLiteral("Test")); } void KConfigTest::testReparent() { KConfig cf(s_kconfig_test_subdir); const QString name(QStringLiteral("Enum Types")); KConfigGroup group = cf.group(name); const QMap originalMap = group.entryMap(); KConfigGroup parent = cf.group(QStringLiteral("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.toSecsSinceEpoch(); utbuf.modtime = utbuf.actime; utime(QFile::encodeName(path).constData(), &utbuf); #else QTest::qSleep(nsec * 1000); #endif } void KConfigTest::testWriteOnSync() { QDateTime oldStamp; QDateTime newStamp; KConfig sc(s_kconfig_test_subdir, KConfig::IncludeGlobals); // Age the timestamp of global config file a few sec, and collect it. QString globFile = m_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, QStringLiteral("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 = m_testConfigDir + QLatin1String("/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, QStringLiteral("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(s_test_subdir + QLatin1String("kconfigfailonreadonlytest")); KConfigGroup cgLocal(&sc, QStringLiteral("Locals")); cgLocal.writeEntry("someLocalString", "whatever"); QVERIFY(cgLocal.sync()); QFile f(m_testConfigDir + QLatin1String("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; QDateTime newStamp; KConfig sc(s_kconfig_test_subdir); // Initialize value KConfigGroup cgLocal(&sc, QStringLiteral("random")); cgLocal.writeEntry("theKey", "whatever"); QVERIFY(sc.sync()); // Age the timestamp of local config file a few sec, and collect it. QString locFile = m_testConfigDir + QLatin1String("/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(s_kconfig_test_subdir); KConfigGroup cgLocal(&sc, QStringLiteral("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) QTemporaryDir tmpDir(QDir::tempPath() + QStringLiteral("/fakehome_XXXXXX")); QVERIFY(tmpDir.isValid()); const QString kdehome = tmpDir.path() + QLatin1String("/.kde-unit-test"); const QString subdir = kdehome + QLatin1String("/newsubdir"); const QString file = subdir + QLatin1String("/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(s_test_subdir + QLatin1String("syncOnExitRc")), QStringLiteral("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(s_kconfig_test_subdir); myConfigGroup = KConfigGroup(config, QStringLiteral("Hello")); } QCOMPARE(myConfigGroup.readEntry("stringEntry1"), s_string_entry1); // Get the main config KSharedConfigPtr mainConfig = KSharedConfig::openConfig(); KConfigGroup mainGroup(mainConfig, QStringLiteral("Main")); QCOMPARE(mainGroup.readEntry("Key", QString{}), QStringLiteral("Value")); } void KConfigTest::testLocaleConfig() { // Initialize the testdata QDir().mkpath(m_testConfigDir); const QString file = m_testConfigDir + QLatin1String("/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, QStringLiteral("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, QStringLiteral("Test_Right")); QCOMPARE(cg2.readEntry("foo"), QStringLiteral("5")); QCOMPARE(cg2.readEntry("foo", 3), 5); QCOMPARE(cg2.readEntry("foostring"), QStringLiteral("nice")); QCOMPARE(cg2.readEntry("foostring", "ugly"), QStringLiteral("nice")); QCOMPARE(cg2.readEntry("foobool"), QStringLiteral("true")); QCOMPARE(cg2.readEntry("foobool", false), true); // Clean up after the testcase QFile::remove(file); } void KConfigTest::testDeleteWhenLocalized() { // Initialize the testdata QTemporaryDir tmpDir(QDir::tempPath() + QStringLiteral("/fakehome_XXXXXX")); QVERIFY(tmpDir.isValid()); const QString subdir = tmpDir.path() + QLatin1String("/.kde-unit-test/"); QDir().mkpath(subdir); const QString file = subdir + QLatin1String("/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, QStringLiteral("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"), QStringLiteral("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"), QStringLiteral("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"), QStringLiteral("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, QStringLiteral("General")); general.writeEntry("testKG", "1"); QVERIFY(glob.sync()); } KConfig globRead(QStringLiteral("kdeglobals")); const KConfigGroup general(&globRead, QStringLiteral("General")); QCOMPARE(general.readEntry("testKG"), QStringLiteral("1")); // Check we wrote into kdeglobals const QList lines = readLines(QStringLiteral("kdeglobals")); QVERIFY(lines.contains("[General]\n")); QVERIFY(lines.contains("testKG=1\n")); // Writing using NoGlobals { KConfig glob(QStringLiteral("kdeglobals"), KConfig::NoGlobals); KConfigGroup general(&glob, QStringLiteral("General")); general.writeEntry("testKG", "2"); QVERIFY(glob.sync()); } globRead.reparseConfiguration(); QCOMPARE(general.readEntry("testKG"), QStringLiteral("2")); // Reading using NoGlobals { KConfig globReadNoGlob(QStringLiteral("kdeglobals"), KConfig::NoGlobals); const KConfigGroup generalNoGlob(&globReadNoGlob, QStringLiteral("General")); QCOMPARE(generalNoGlob.readEntry("testKG"), QStringLiteral("2")); } } void KConfigTest::testLocalDeletion() { // Prepare kdeglobals { KConfig glob(QStringLiteral("kdeglobals")); KConfigGroup general(&glob, QStringLiteral("OwnTestGroup")); general.writeEntry("GlobalKey", "DontTouchMe"); QVERIFY(glob.sync()); } QStringList expectedKeys{QStringLiteral("LocalKey")}; expectedKeys.prepend(QStringLiteral("GlobalWrite")); // Write into kconfigtest, including deleting GlobalKey { KConfig mainConfig(s_kconfig_test_subdir); KConfigGroup mainGroup(&mainConfig, QStringLiteral("OwnTestGroup")); mainGroup.writeEntry("LocalKey", QStringLiteral("LocalValue")); mainGroup.writeEntry("GlobalWrite", QStringLiteral("GlobalValue"), KConfig::Persistent | KConfig::Global); // goes to kdeglobals QCOMPARE(mainGroup.readEntry("GlobalKey"), QStringLiteral("DontTouchMe")); mainGroup.deleteEntry("GlobalKey"); // local deletion ([$d]), kdeglobals is unchanged QCOMPARE(mainGroup.readEntry("GlobalKey", "Default"), QStringLiteral("Default")); // key is gone QCOMPARE(mainGroup.keyList(), expectedKeys); } // Check what ended up in kconfigtest const QList lines = readLines(); QVERIFY(lines.contains("[OwnTestGroup]\n")); QVERIFY(lines.contains("GlobalKey[$d]\n")); // Check what ended up in kdeglobals { KConfig globReadNoGlob(QStringLiteral("kdeglobals"), KConfig::NoGlobals); const KConfigGroup generalNoGlob(&globReadNoGlob, QStringLiteral("OwnTestGroup")); QCOMPARE(generalNoGlob.readEntry("GlobalKey"), QStringLiteral("DontTouchMe")); QCOMPARE(generalNoGlob.readEntry("GlobalWrite"), QStringLiteral("GlobalValue")); QVERIFY(!generalNoGlob.hasKey("LocalValue")); QStringList expectedGlobalKeys{QStringLiteral("GlobalKey")}; expectedGlobalKeys.append(QStringLiteral("GlobalWrite")); QCOMPARE(generalNoGlob.keyList(), expectedGlobalKeys); } // Check what we see when re-reading the config file { KConfig mainConfig(s_kconfig_test_subdir); KConfigGroup mainGroup(&mainConfig, QStringLiteral("OwnTestGroup")); QCOMPARE(mainGroup.readEntry("GlobalKey", "Default"), QStringLiteral("Default")); // key is gone QCOMPARE(mainGroup.keyList(), expectedKeys); } } void KConfigTest::testAnonymousConfig() { KConfig anonConfig(QString(), KConfig::SimpleConfig); KConfigGroup general(&anonConfig, QStringLiteral("General")); QCOMPARE(general.readEntry("testKG"), QString()); // no kdeglobals merging general.writeEntry("Foo", "Bar"); QCOMPARE(general.readEntry("Foo"), QStringLiteral("Bar")); } void KConfigTest::testQByteArrayUtf8() { QTemporaryFile file; QVERIFY(file.open()); KConfig config(file.fileName(), KConfig::SimpleConfig); KConfigGroup general(&config, QStringLiteral("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\\x" \ "1f " \ "!\"#$%&'()*+,-./" \ "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\\xb" \ "b\\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, QStringLiteral("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, QStringLiteral("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(QByteArrayLiteral("\r\n"), QByteArrayLiteral("\n")); #endif QCOMPARE(fileBytes, QByteArrayLiteral("[General]\nkey=") + serialized + QByteArrayLiteral("\n")); // check that reading works KConfig config2(file.fileName(), KConfig::SimpleConfig); KConfigGroup general2(&config2, QStringLiteral("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, QStringLiteral("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::testMoveValuesTo() { QTemporaryFile file; QVERIFY(file.open()); // Prepare kdeglobals { KConfig glob(QStringLiteral("kdeglobals")); KConfigGroup general(&glob, QStringLiteral("TestGroup")); general.writeEntry("GlobalKey", "PlsDeleteMe"); QVERIFY(glob.sync()); } KConfigGroup grp = KSharedConfig::openConfig(file.fileName())->group(QStringLiteral("TestGroup")); grp.writeEntry("test1", "first_value"); grp.writeEntry("test_empty", ""); grp.writeEntry("other", "other_value"); grp.writePathEntry("my_path", QStringLiteral("~/somepath")); // because this key is from the global file it should be explicitly deleted grp.deleteEntry("GlobalKey"); QTemporaryFile targetFile; QVERIFY(targetFile.open()); targetFile.close(); KConfigGroup targetGroup = KSharedConfig::openConfig(targetFile.fileName(), KConfig::SimpleConfig)->group(QStringLiteral("MoveToGroup")); grp.moveValuesTo({"test1", "test_empty", "does_not_exist", "my_path", "GlobalKey"}, targetGroup); QVERIFY(grp.config()->isDirty()); QVERIFY(targetGroup.config()->isDirty()); QCOMPARE(grp.keyList(), QStringList{QStringLiteral("other")}); QStringList expectedKeyList{QStringLiteral("my_path"), QStringLiteral("test1"), QStringLiteral("test_empty")}; QCOMPARE(targetGroup.keyList(), expectedKeyList); QCOMPARE(targetGroup.readEntry("test1"), QStringLiteral("first_value")); targetGroup.sync(); QFile targetReadFile(targetFile.fileName()); targetReadFile.open(QFile::ReadOnly); QVERIFY(targetReadFile.readAll().contains(QByteArray("my_path[$e]=~/somepath"))); } void KConfigTest::testMoveAllValuesTo() { QTemporaryFile file; QVERIFY(file.open()); // Prepare kdeglobals { KConfig glob(QStringLiteral("kdeglobals")); KConfigGroup general(&glob, QStringLiteral("TestGroup")); general.writeEntry("GlobalKey", "PlsDeleteMe"); QVERIFY(glob.sync()); } KConfigGroup grp = KSharedConfig::openConfig(file.fileName())->group(QStringLiteral("TestGroup")); grp.writeEntry("test1", "first_value"); grp.writeEntry("test_empty", ""); grp.writePathEntry("my_path", QStringLiteral("~/somepath")); // because this key is from the global file it should be explicitly deleted grp.deleteEntry("GlobalKey"); QTemporaryFile targetFile; QVERIFY(targetFile.open()); targetFile.close(); KConfigGroup targetGroup = KSharedConfig::openConfig(targetFile.fileName(), KConfig::SimpleConfig)->group(QStringLiteral("MoveToGroup")); grp.moveValuesTo(targetGroup); QVERIFY(grp.config()->isDirty()); QVERIFY(targetGroup.config()->isDirty()); QStringList expectedKeyList{QStringLiteral("my_path"), QStringLiteral("test1"), QStringLiteral("test_empty")}; QCOMPARE(targetGroup.keyList(), expectedKeyList); QCOMPARE(targetGroup.readEntry("test1"), QStringLiteral("first_value")); targetGroup.sync(); QFile targetReadFile(targetFile.fileName()); targetReadFile.open(QFile::ReadOnly); QVERIFY(targetReadFile.readAll().contains(QByteArray("my_path[$e]=~/somepath"))); } void KConfigTest::testXdgListEntry() { QTemporaryFile file; QVERIFY(file.open()); QTextStream out(&file); out << "[General]\n" << "Key1=\n" // empty list // emtpty entries << "Key2=;\n" << "Key3=;;\n" << "Key4=;;;\n" << "Key5=\\;\n" << "Key6=1;2\\;3;;\n"; out.flush(); file.close(); KConfig anonConfig(file.fileName(), KConfig::SimpleConfig); KConfigGroup grp = anonConfig.group(QStringLiteral("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{QStringLiteral(";")})); QCOMPARE(grp.readXdgListEntry("Key6", invalidList), (QStringList{QStringLiteral("1"), QStringLiteral("2;3"), QString{}})); } #include #include // To find multithreading bugs: valgrind --tool=helgrind --track-lockorders=no ./kconfigtest testThreads void KConfigTest::testThreads() { QThreadPool::globalInstance()->setMaxThreadCount(6); // Run in parallel some tests that work on different config files, // otherwise unexpected things might indeed happen. const QList> futures = { QtConcurrent::run(&KConfigTest::testAddConfigSources, this), QtConcurrent::run(&KConfigTest::testSimple, this), QtConcurrent::run(&KConfigTest::testDefaults, this), QtConcurrent::run(&KConfigTest::testSharedConfig, this), QtConcurrent::run(&KConfigTest::testSharedConfig, this), }; // 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 : futures) { f.waitForFinished(); } } void KConfigTest::testNotify() { #if !KCONFIG_USE_DBUS QSKIP("KConfig notification requires DBus"); #endif KConfig config(s_kconfig_test_subdir); auto myConfigGroup = KConfigGroup(&config, QStringLiteral("TopLevelGroup")); // mimics a config in another process, which is watching for events auto remoteConfig = KSharedConfig::openConfig(s_kconfig_test_subdir); KConfigWatcher::Ptr watcher = KConfigWatcher::create(remoteConfig); // some random config that shouldn't be changing when kconfigtest changes, only on kdeglobals auto otherRemoteConfig = KSharedConfig::openConfig(s_test_subdir + QLatin1String("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(QStringLiteral("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(QStringLiteral("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"})); } void KConfigTest::testNotifyIllegalObjectPath() { #if !KCONFIG_USE_DBUS QSKIP("KConfig notification requires DBus"); #endif KConfig config(s_kconfig_test_illegal_object_path); auto myConfigGroup = KConfigGroup(&config, QStringLiteral("General")); // mimics a config in another process, which is watching for events auto remoteConfig = KSharedConfig::openConfig(s_kconfig_test_illegal_object_path); KConfigWatcher::Ptr watcher = KConfigWatcher::create(remoteConfig); QSignalSpy watcherSpy(watcher.data(), &KConfigWatcher::configChanged); // write entries in a group and subgroup myConfigGroup.writeEntry("entryA", "foo", KConfig::Persistent | KConfig::Notify); config.sync(); watcherSpy.wait(); QCOMPARE(watcherSpy.size(), 1); } void KConfigTest::testKAuthorizeEnums() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup actionRestrictions = config->group(QStringLiteral("KDE Action Restrictions")); actionRestrictions.writeEntry("shell_access", false); actionRestrictions.writeEntry("action/open_with", false); QVERIFY(!KAuthorized::authorize(KAuthorized::SHELL_ACCESS)); QVERIFY(!KAuthorized::authorizeAction(KAuthorized::OPEN_WITH)); actionRestrictions.deleteGroup(); QVERIFY(!KAuthorized::authorize((KAuthorized::GenericRestriction)0)); QVERIFY(!KAuthorized::authorizeAction((KAuthorized::GenericAction)0)); } void KConfigTest::testKdeglobalsVsDefault() { // Add testRestore key with global value in kdeglobals KConfig glob(QStringLiteral("kdeglobals")); KConfigGroup generalGlob(&glob, QStringLiteral("General")); generalGlob.writeEntry("testRestore", "global"); QVERIFY(glob.sync()); KConfig local(s_test_subdir + QLatin1String("restorerc")); KConfigGroup generalLocal(&local, QStringLiteral("General")); // Check if we get global and not the default value from cpp (defaultcpp) when reading data from restorerc QCOMPARE(generalLocal.readEntry("testRestore", "defaultcpp"), QStringLiteral("global")); // Add test restore key with restore value in restorerc file generalLocal.writeEntry("testRestore", "restore"); QVERIFY(local.sync()); local.reparseConfiguration(); // We expect to get the value from restorerc file QCOMPARE(generalLocal.readEntry("testRestore", "defaultcpp"), QStringLiteral("restore")); // Revert to default testRestore key and we expect to get default value and not the global one generalLocal.revertToDefault("testRestore"); local.sync(); local.reparseConfiguration(); QCOMPARE(generalLocal.readEntry("testRestore", "defaultcpp"), QStringLiteral("defaultcpp")); } #include "moc_kconfigtest.cpp"