File indexing completed on 2024-04-28 03:53:13

0001 /*  This file is part of the KDE libraries
0002     SPDX-FileCopyrightText: 1997 Matthias Kalle Dalheimer <kalle@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "kconfigtest.h"
0008 #include "helper.h"
0009 
0010 #include "config-kconfig.h"
0011 
0012 #include <QSignalSpy>
0013 #include <QStandardPaths>
0014 #include <QTemporaryFile>
0015 #include <QTest>
0016 #include <kdesktopfile.h>
0017 #include <qtemporarydir.h>
0018 
0019 #include <kauthorized.h>
0020 #include <kconfiggroup.h>
0021 #include <kconfigwatcher.h>
0022 #include <ksharedconfig.h>
0023 
0024 #ifdef Q_OS_UNIX
0025 #include <utime.h>
0026 #endif
0027 #ifndef Q_OS_WIN
0028 #include <unistd.h> // getuid
0029 #endif
0030 
0031 KCONFIGGROUP_DECLARE_ENUM_QOBJECT(KConfigTest, Testing)
0032 KCONFIGGROUP_DECLARE_FLAGS_QOBJECT(KConfigTest, Flags)
0033 
0034 QTEST_MAIN(KConfigTest)
0035 
0036 Q_DECLARE_METATYPE(KConfigGroup)
0037 
0038 static QString homePath()
0039 {
0040 #ifdef Q_OS_WIN
0041     return QDir::homePath();
0042 #else
0043     // Don't use QDir::homePath() on Unix, it removes any trailing slash, while KConfig uses $HOME.
0044     return QString::fromLocal8Bit(qgetenv("HOME"));
0045 #endif
0046 }
0047 
0048 // clazy:excludeall=non-pod-global-static
0049 
0050 static const bool s_bool_entry1 = true;
0051 static const bool s_bool_entry2 = false;
0052 
0053 static const QString s_string_entry1(QStringLiteral("hello"));
0054 static const QString s_string_entry2(QStringLiteral(" hello"));
0055 static const QString s_string_entry3(QStringLiteral("hello "));
0056 static const QString s_string_entry4(QStringLiteral(" hello "));
0057 static const QString s_string_entry5(QStringLiteral(" "));
0058 static const QString s_string_entry6{};
0059 
0060 static const char s_utf8bit_entry[] = "Hello äöü";
0061 static const QString s_translated_string_entry1{QStringLiteral("bonjour")};
0062 static const QByteArray s_bytearray_entry{"\x00\xff\x7f\x3c abc\x00\x00", 10};
0063 static const char s_escapekey[] = " []\0017[]==]";
0064 static const char s_escape_entry[] = "[]\170[]]=3=]\\] ";
0065 static const double s_double_entry{123456.78912345};
0066 static const float s_float_entry{123.567f};
0067 static const QPoint s_point_entry{4351, 1235};
0068 static const QSize s_size_entry{10, 20};
0069 static const QRect s_rect_entry{10, 23, 5321, 13};
0070 static const QDateTime s_date_time_entry{QDate{2002, 06, 23}, QTime{12, 55, 40}};
0071 static const QDateTime s_date_time_with_ms_entry{QDate{2002, 06, 23}, QTime{12, 55, 40, 532}};
0072 static const QStringList s_stringlist_entry{QStringLiteral("Hello,"), QStringLiteral("World")};
0073 static const QStringList s_stringlist_empty_entry{};
0074 static const QStringList s_stringlist_just_empty_element{QString{}};
0075 static const QStringList s_stringlist_empty_trailing_element{QStringLiteral("Hi"), QString{}};
0076 static const QStringList s_stringlist_escape_odd_entry{QStringLiteral("Hello\\\\\\"), QStringLiteral("World")};
0077 static const QStringList s_stringlist_escape_even_entry{QStringLiteral("Hello\\\\\\\\"), QStringLiteral("World")};
0078 static const QStringList s_stringlist_escape_comma_entry{QStringLiteral("Hel\\\\\\,\\\\,\\,\\\\\\\\,lo"), QStringLiteral("World")};
0079 static const QList<int> s_int_listentry1{1, 2, 3, 4};
0080 static const QList<QByteArray> s_bytearray_list_entry1{"", "1,2", "end"};
0081 static const QVariantList s_variantlist_entry{true, false, QStringLiteral("joe"), 10023};
0082 static const QVariantList s_variantlist_entry2{s_point_entry, s_size_entry};
0083 
0084 static const QString s_homepath{homePath() + QLatin1String{"/foo"}};
0085 static const QString s_homepath_escape{homePath() + QLatin1String("/foo/$HOME")};
0086 static const QString s_canonical_homepath{QFileInfo(homePath()).canonicalFilePath() + QLatin1String("/foo")};
0087 static const QString s_dollargroup{QStringLiteral("$i")};
0088 static const QString s_test_subdir{QStringLiteral("kconfigtest_subdir/")};
0089 static const QString s_kconfig_test_subdir(s_test_subdir + QLatin1String("kconfigtest"));
0090 static const QString s_kconfig_test_illegal_object_path(s_test_subdir + QLatin1String("kconfig-test"));
0091 
0092 #ifndef Q_OS_WIN
0093 void initLocale()
0094 {
0095     setenv("LC_ALL", "en_US.utf-8", 1);
0096     setenv("TZ", "UTC", 1);
0097 }
0098 
0099 Q_CONSTRUCTOR_FUNCTION(initLocale)
0100 #endif
0101 
0102 void KConfigTest::initTestCase()
0103 {
0104     // ensure we don't use files in the real config directory
0105     QStandardPaths::setTestModeEnabled(true);
0106 
0107     qRegisterMetaType<KConfigGroup>();
0108 
0109     // These two need to be assigned here, after setTestModeEnabled(true), and before cleanupTestCase()
0110     m_testConfigDir = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + s_test_subdir;
0111     m_kdeGlobalsPath = QDir::cleanPath(m_testConfigDir + QLatin1String("..")) + QLatin1String("/kdeglobals");
0112 
0113     // to make sure all files from a previous failed run are deleted
0114     cleanupTestCase();
0115 
0116     KSharedConfigPtr mainConfig = KSharedConfig::openConfig();
0117     mainConfig->group(QStringLiteral("Main")).writeEntry("Key", "Value");
0118     mainConfig->sync();
0119 
0120     KConfig sc(s_kconfig_test_subdir);
0121 
0122     KConfigGroup cg(&sc, QStringLiteral("AAA")); // deleted later by testDelete
0123     cg.writeEntry("stringEntry1", s_string_entry1, KConfig::Persistent | KConfig::Global);
0124 
0125     cg = KConfigGroup(&sc, QStringLiteral("GlobalGroup"));
0126     cg.writeEntry("globalEntry", s_string_entry1, KConfig::Persistent | KConfig::Global);
0127     cg.deleteEntry("globalEntry2", KConfig::Global);
0128 
0129     cg = KConfigGroup(&sc, QStringLiteral("LocalGroupToBeDeleted")); // deleted later by testDelete
0130     cg.writeEntry("stringEntry1", s_string_entry1);
0131 
0132     cg = KConfigGroup(&sc, QStringLiteral("Hello"));
0133     cg.writeEntry("boolEntry1", s_bool_entry1);
0134     cg.writeEntry("boolEntry2", s_bool_entry2);
0135 
0136     QByteArray data(s_utf8bit_entry);
0137     QCOMPARE(data.size(), 12); // the source file is in utf8
0138     QCOMPARE(QString::fromUtf8(data).length(), 9);
0139     cg.writeEntry("Test", data);
0140     cg.writeEntry("bytearrayEntry", s_bytearray_entry);
0141     cg.writeEntry(s_escapekey, QString::fromLatin1(s_escape_entry));
0142     cg.writeEntry("emptyEntry", "");
0143     cg.writeEntry("stringEntry1", s_string_entry1);
0144     cg.writeEntry("stringEntry2", s_string_entry2);
0145     cg.writeEntry("stringEntry3", s_string_entry3);
0146     cg.writeEntry("stringEntry4", s_string_entry4);
0147     cg.writeEntry("stringEntry5", s_string_entry5);
0148     cg.writeEntry("urlEntry1", QUrl(QStringLiteral("http://qt-project.org")));
0149     cg.writeEntry("keywith=equalsign", s_string_entry1);
0150     cg.deleteEntry("stringEntry5");
0151     cg.deleteEntry("stringEntry6"); // deleting a nonexistent entry
0152     cg.writeEntry("byteArrayEntry1", s_string_entry1.toLatin1(), KConfig::Global | KConfig::Persistent);
0153     cg.writeEntry("doubleEntry1", s_double_entry);
0154     cg.writeEntry("floatEntry1", s_float_entry);
0155 
0156     sc.deleteGroup(QStringLiteral("deleteMe")); // deleting a nonexistent group
0157 
0158     cg = KConfigGroup(&sc, QStringLiteral("Complex Types"));
0159     cg.writeEntry("rectEntry", s_rect_entry);
0160     cg.writeEntry("pointEntry", s_point_entry);
0161     cg.writeEntry("sizeEntry", s_size_entry);
0162     cg.writeEntry("dateTimeEntry", s_date_time_entry);
0163     cg.writeEntry("dateEntry", s_date_time_entry.date());
0164     cg.writeEntry("dateTimeWithMSEntry", s_date_time_with_ms_entry);
0165 
0166     KConfigGroup ct = cg;
0167     cg = KConfigGroup(&ct, QStringLiteral("Nested Group 1"));
0168     cg.writeEntry("stringentry1", s_string_entry1);
0169 
0170     cg = KConfigGroup(&ct, QStringLiteral("Nested Group 2"));
0171     cg.writeEntry("stringEntry2", s_string_entry2);
0172 
0173     cg = KConfigGroup(&cg, QStringLiteral("Nested Group 2.1"));
0174     cg.writeEntry("stringEntry3", s_string_entry3);
0175 
0176     cg = KConfigGroup(&ct, QStringLiteral("Nested Group 3"));
0177     cg.writeEntry("stringEntry3", s_string_entry3);
0178 
0179     cg = KConfigGroup(&sc, QStringLiteral("List Types"));
0180     cg.writeEntry("listOfIntsEntry1", s_int_listentry1);
0181     cg.writeEntry("listOfByteArraysEntry1", s_bytearray_list_entry1);
0182     cg.writeEntry("stringListEntry", s_stringlist_entry);
0183     cg.writeEntry("stringListEmptyEntry", s_stringlist_empty_entry);
0184     cg.writeEntry("stringListJustEmptyElement", s_stringlist_just_empty_element);
0185     cg.writeEntry("stringListEmptyTrailingElement", s_stringlist_empty_trailing_element);
0186     cg.writeEntry("stringListEscapeOddEntry", s_stringlist_escape_odd_entry);
0187     cg.writeEntry("stringListEscapeEvenEntry", s_stringlist_escape_even_entry);
0188     cg.writeEntry("stringListEscapeCommaEntry", s_stringlist_escape_comma_entry);
0189     cg.writeEntry("variantListEntry", s_variantlist_entry);
0190 
0191     cg = KConfigGroup(&sc, QStringLiteral("Path Type"));
0192     cg.writePathEntry("homepath", s_homepath);
0193     cg.writePathEntry("homepathescape", s_homepath_escape);
0194     cg.writePathEntry("canonicalHomePath", s_canonical_homepath);
0195 
0196     cg = KConfigGroup(&sc, QStringLiteral("Enum Types"));
0197 #if defined(_MSC_VER) && _MSC_VER == 1600
0198     cg.writeEntry("dummy", 42);
0199 #else
0200     // Visual C++ 2010 throws an Internal Compiler Error here
0201     cg.writeEntry("enum-10", Tens);
0202     cg.writeEntry("enum-100", Hundreds);
0203     cg.writeEntry("flags-bit0", Flags(bit0));
0204     cg.writeEntry("flags-bit0-bit1", Flags(bit0 | bit1));
0205 #endif
0206 
0207     cg = KConfigGroup(&sc, QStringLiteral("ParentGroup"));
0208     KConfigGroup cg1(&cg, QStringLiteral("SubGroup1"));
0209     cg1.writeEntry("somestring", "somevalue");
0210     cg.writeEntry("parentgrpstring", "somevalue");
0211     KConfigGroup cg2(&cg, QStringLiteral("SubGroup2"));
0212     cg2.writeEntry("substring", "somevalue");
0213     KConfigGroup cg3(&cg, QStringLiteral("SubGroup/3"));
0214     cg3.writeEntry("sub3string", "somevalue");
0215 
0216     QVERIFY(sc.isDirty());
0217     QVERIFY(sc.sync());
0218     QVERIFY(!sc.isDirty());
0219 
0220     QVERIFY2(QFile::exists(m_testConfigDir + QLatin1String("/kconfigtest")), qPrintable(m_testConfigDir + QLatin1String("/kconfigtest must exist")));
0221     QVERIFY2(QFile::exists(m_kdeGlobalsPath), qPrintable(m_kdeGlobalsPath + QStringLiteral(" must exist")));
0222 
0223     KConfig sc1(s_test_subdir + QLatin1String("kdebugrc"), KConfig::SimpleConfig);
0224     KConfigGroup sg0(&sc1, QStringLiteral("0"));
0225     sg0.writeEntry("AbortFatal", false);
0226     sg0.writeEntry("WarnOutput", 0);
0227     sg0.writeEntry("FatalOutput", 0);
0228     QVERIFY(sc1.sync());
0229 
0230     // Setup stuff to test KConfig::addConfigSources()
0231     KConfig devcfg(s_test_subdir + QLatin1String("specificrc"));
0232     KConfigGroup devonlygrp(&devcfg, QStringLiteral("Specific Only Group"));
0233     devonlygrp.writeEntry("ExistingEntry", "DevValue");
0234     KConfigGroup devandbasegrp(&devcfg, QStringLiteral("Shared Group"));
0235     devandbasegrp.writeEntry("SomeSharedEntry", "DevValue");
0236     devandbasegrp.writeEntry("SomeSpecificOnlyEntry", "DevValue");
0237     QVERIFY(devcfg.sync());
0238     KConfig basecfg(s_test_subdir + QLatin1String("baserc"));
0239     KConfigGroup basegrp(&basecfg, QStringLiteral("Base Only Group"));
0240     basegrp.writeEntry("ExistingEntry", "BaseValue");
0241     KConfigGroup baseanddevgrp(&basecfg, QStringLiteral("Shared Group"));
0242     baseanddevgrp.writeEntry("SomeSharedEntry", "BaseValue");
0243     baseanddevgrp.writeEntry("SomeBaseOnlyEntry", "BaseValue");
0244     QVERIFY(basecfg.sync());
0245 
0246     KConfig gecfg(s_test_subdir + QLatin1String("groupescapetest"), KConfig::SimpleConfig);
0247     cg = KConfigGroup(&gecfg, s_dollargroup);
0248     cg.writeEntry("entry", "doesntmatter");
0249 }
0250 
0251 void KConfigTest::cleanupTestCase()
0252 {
0253     // ensure we don't delete the real directory
0254     QDir localConfig(m_testConfigDir);
0255     // qDebug() << "Erasing" << localConfig;
0256     if (localConfig.exists()) {
0257         QVERIFY(localConfig.removeRecursively());
0258     }
0259     QVERIFY(!localConfig.exists());
0260     if (QFile::exists(m_kdeGlobalsPath)) {
0261         QVERIFY(QFile::remove(m_kdeGlobalsPath));
0262     }
0263 }
0264 
0265 static QList<QByteArray> readLinesFrom(const QString &path)
0266 {
0267     QFile file(path);
0268     const bool opened = file.open(QIODevice::ReadOnly | QIODevice::Text);
0269     QList<QByteArray> lines;
0270     if (!opened) {
0271         qWarning() << "Failed to open" << path;
0272         return lines;
0273     }
0274     QByteArray line;
0275     do {
0276         line = file.readLine();
0277         if (!line.isEmpty()) {
0278             lines.append(line);
0279         }
0280     } while (!line.isEmpty());
0281     return lines;
0282 }
0283 
0284 static const QString s_defaultArg = s_test_subdir + QLatin1String("kconfigtest");
0285 static QList<QByteArray> readLines(const QString &fileName = s_defaultArg)
0286 {
0287     const QString path = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
0288     Q_ASSERT(!path.isEmpty());
0289     return readLinesFrom(path + QLatin1Char('/') + fileName);
0290 }
0291 
0292 // see also testDefaults, which tests reverting with a defaults (global) file available
0293 void KConfigTest::testDirtyAfterRevert()
0294 {
0295     KConfig sc(s_test_subdir + QLatin1String("kconfigtest_revert"));
0296 
0297     KConfigGroup cg(&sc, QStringLiteral("Hello"));
0298     cg.revertToDefault("does_not_exist");
0299     QVERIFY(!sc.isDirty());
0300     cg.writeEntry("Test", "Correct");
0301     QVERIFY(sc.isDirty());
0302     sc.sync();
0303     QVERIFY(!sc.isDirty());
0304 
0305     cg.revertToDefault("Test");
0306     QVERIFY(sc.isDirty());
0307     QVERIFY(sc.sync());
0308     QVERIFY(!sc.isDirty());
0309 
0310     cg.revertToDefault("Test");
0311     QVERIFY(!sc.isDirty());
0312 }
0313 
0314 void KConfigTest::testRevertAllEntries()
0315 {
0316     // this tests the case were we revert (delete) all entries in a file,
0317     // leaving a blank file
0318     {
0319         KConfig sc(s_test_subdir + QLatin1String("konfigtest2"), KConfig::SimpleConfig);
0320         KConfigGroup cg(&sc, QStringLiteral("Hello"));
0321         cg.writeEntry("Test", "Correct");
0322     }
0323 
0324     {
0325         KConfig sc(s_test_subdir + QLatin1String("konfigtest2"), KConfig::SimpleConfig);
0326         KConfigGroup cg(&sc, QStringLiteral("Hello"));
0327         QCOMPARE(cg.readEntry("Test", "Default"), QStringLiteral("Correct"));
0328         cg.revertToDefault("Test");
0329     }
0330 
0331     KConfig sc(s_test_subdir + QLatin1String("konfigtest2"), KConfig::SimpleConfig);
0332     KConfigGroup cg(&sc, QStringLiteral("Hello"));
0333     QCOMPARE(cg.readEntry("Test", "Default"), QStringLiteral("Default"));
0334 }
0335 
0336 void KConfigTest::testSimple()
0337 {
0338     // kdeglobals (which was created in initTestCase) must be found this way:
0339     const QStringList kdeglobals = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("kdeglobals"));
0340     QVERIFY(!kdeglobals.isEmpty());
0341 
0342     KConfig sc2(s_kconfig_test_subdir);
0343     QCOMPARE(sc2.name(), s_test_subdir + QLatin1String{"kconfigtest"});
0344 
0345     // make sure groupList() isn't returning something it shouldn't
0346     const QStringList lstGroup = sc2.groupList();
0347     for (const QString &group : lstGroup) {
0348         QVERIFY(!group.isEmpty() && group != QLatin1String("<default>"));
0349         QVERIFY(!group.contains(QChar(0x1d)));
0350     }
0351 
0352     KConfigGroup sc3(&sc2, QStringLiteral("GlobalGroup"));
0353 
0354     QVERIFY(sc3.hasKey("globalEntry")); // from kdeglobals
0355     QVERIFY(!sc3.isEntryImmutable("globalEntry"));
0356     QCOMPARE(sc3.readEntry("globalEntry"), s_string_entry1);
0357 
0358     QVERIFY(!sc3.hasKey("globalEntry2"));
0359     QCOMPARE(sc3.readEntry("globalEntry2", QStringLiteral("bla")), QStringLiteral("bla"));
0360 
0361     QVERIFY(!sc3.hasDefault("globalEntry"));
0362 
0363     sc3 = KConfigGroup(&sc2, QStringLiteral("Hello"));
0364     QCOMPARE(sc3.readEntry("Test", QByteArray()), QByteArray(s_utf8bit_entry));
0365     QCOMPARE(sc3.readEntry("bytearrayEntry", QByteArray()), s_bytearray_entry);
0366     QCOMPARE(sc3.readEntry(s_escapekey), QString::fromLatin1(s_escape_entry));
0367     QCOMPARE(sc3.readEntry("Test", QString{}), QString::fromUtf8(s_utf8bit_entry));
0368     QCOMPARE(sc3.readEntry("emptyEntry" /*, QString("Fietsbel")*/), QLatin1String(""));
0369     QCOMPARE(sc3.readEntry("emptyEntry", QStringLiteral("Fietsbel")).isEmpty(), true);
0370     QCOMPARE(sc3.readEntry("stringEntry1"), s_string_entry1);
0371     QCOMPARE(sc3.readEntry("stringEntry2"), s_string_entry2);
0372     QCOMPARE(sc3.readEntry("stringEntry3"), s_string_entry3);
0373     QCOMPARE(sc3.readEntry("stringEntry4"), s_string_entry4);
0374     QVERIFY(!sc3.hasKey("stringEntry5"));
0375     QCOMPARE(sc3.readEntry("stringEntry5", QStringLiteral("test")), QStringLiteral("test"));
0376     QVERIFY(!sc3.hasKey("stringEntry6"));
0377     QCOMPARE(sc3.readEntry("stringEntry6", QStringLiteral("foo")), QStringLiteral("foo"));
0378     QCOMPARE(sc3.readEntry("urlEntry1", QUrl{}), QUrl(QStringLiteral("http://qt-project.org")));
0379     QCOMPARE(sc3.readEntry("boolEntry1", s_bool_entry1), s_bool_entry1);
0380     QCOMPARE(sc3.readEntry("boolEntry2", false), s_bool_entry2);
0381     QCOMPARE(sc3.readEntry("keywith=equalsign", QStringLiteral("wrong")), s_string_entry1);
0382     QCOMPARE(sc3.readEntry("byteArrayEntry1", QByteArray{}), s_string_entry1.toLatin1());
0383     QCOMPARE(sc3.readEntry("doubleEntry1", 0.0), s_double_entry);
0384     QCOMPARE(sc3.readEntry("floatEntry1", 0.0f), s_float_entry);
0385 }
0386 
0387 void KConfigTest::testDefaults()
0388 {
0389     KConfig config(s_test_subdir + QLatin1String("defaulttest"), KConfig::NoGlobals);
0390     const QString defaultsFile = s_test_subdir + QLatin1String("defaulttest.defaults");
0391     KConfig defaults(defaultsFile, KConfig::SimpleConfig);
0392     const QString defaultsFilePath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + defaultsFile;
0393 
0394     const QString Default(QStringLiteral("Default"));
0395     const QString NotDefault(QStringLiteral("Not Default"));
0396     const QString Value1(s_string_entry1);
0397     const QString Value2(s_string_entry2);
0398 
0399     KConfigGroup group = defaults.group(QStringLiteral("any group"));
0400     group.writeEntry("entry1", Default);
0401     QVERIFY(group.sync());
0402 
0403     group = config.group(QStringLiteral("any group"));
0404     group.writeEntry("entry1", Value1);
0405     group.writeEntry("entry2", Value2);
0406     QVERIFY(group.sync());
0407 
0408     config.addConfigSources(QStringList{defaultsFilePath});
0409 
0410     config.setReadDefaults(true);
0411     QCOMPARE(group.readEntry("entry1", QString()), Default);
0412     QCOMPARE(group.readEntry("entry2", NotDefault), NotDefault); // no default for entry2
0413 
0414     config.setReadDefaults(false);
0415     QCOMPARE(group.readEntry("entry1", Default), Value1);
0416     QCOMPARE(group.readEntry("entry2", NotDefault), Value2);
0417 
0418     group.revertToDefault("entry1");
0419     QCOMPARE(group.readEntry("entry1", QString()), Default);
0420     group.revertToDefault("entry2");
0421     QCOMPARE(group.readEntry("entry2", QString()), QString());
0422 
0423     // TODO test reverting localized entries
0424 
0425     Q_ASSERT(config.isDirty());
0426     group.sync();
0427 
0428     // Check that everything is OK on disk, too
0429     KConfig reader(s_test_subdir + QLatin1String("defaulttest"), KConfig::NoGlobals);
0430     reader.addConfigSources(QStringList{defaultsFilePath});
0431     KConfigGroup readerGroup = reader.group(QStringLiteral("any group"));
0432     QCOMPARE(readerGroup.readEntry("entry1", QString()), Default);
0433     QCOMPARE(readerGroup.readEntry("entry2", QString()), QString());
0434 }
0435 
0436 void KConfigTest::testLocale()
0437 {
0438     KConfig config(s_test_subdir + QLatin1String("kconfigtest.locales"), KConfig::SimpleConfig);
0439     const QString Translated(s_translated_string_entry1);
0440     const QString Untranslated(s_string_entry1);
0441 
0442     KConfigGroup group = config.group(QStringLiteral("Hello"));
0443     group.writeEntry("stringEntry1", Untranslated);
0444     config.setLocale(QStringLiteral("fr"));
0445     group.writeEntry("stringEntry1", Translated, KConfig::Localized | KConfig::Persistent);
0446     QVERIFY(config.sync());
0447 
0448     QCOMPARE(group.readEntry("stringEntry1", QString()), Translated);
0449     QCOMPARE(group.readEntryUntranslated("stringEntry1"), Untranslated);
0450 
0451     config.setLocale(QStringLiteral("C")); // strings written in the "C" locale are written as nonlocalized
0452     group.writeEntry("stringEntry1", Untranslated, KConfig::Localized | KConfig::Persistent);
0453     QVERIFY(config.sync());
0454 
0455     QCOMPARE(group.readEntry("stringEntry1", QString()), Untranslated);
0456 }
0457 
0458 void KConfigTest::testEncoding()
0459 {
0460     const QString groupstr = QString::fromUtf8("UTF-8:\xc3\xb6l");
0461 
0462     const QString path = s_test_subdir + QLatin1String("kconfigtestencodings");
0463     KConfig c(path);
0464     KConfigGroup cg(&c, groupstr);
0465     cg.writeEntry("key", "value");
0466     QVERIFY(c.sync());
0467 
0468     const QList<QByteArray> lines = readLines(path);
0469     QCOMPARE(lines.count(), 2);
0470     QCOMPARE(lines.first(), QByteArray("[UTF-8:\xc3\xb6l]\n"));
0471 
0472     KConfig c2(path);
0473     KConfigGroup cg2(&c2, groupstr);
0474     QCOMPARE(cg2.readEntry("key"), QStringLiteral("value"));
0475 
0476     QVERIFY(c2.groupList().contains(groupstr));
0477 }
0478 
0479 void KConfigTest::testLists()
0480 {
0481     KConfig sc2(s_kconfig_test_subdir);
0482     KConfigGroup sc3(&sc2, QStringLiteral("List Types"));
0483 
0484     QCOMPARE(sc3.readEntry("stringListEntry", QStringList{}), s_stringlist_entry);
0485 
0486     QCOMPARE(sc3.readEntry(QStringLiteral("stringListEmptyEntry"), QStringList(QStringLiteral("wrong"))), s_stringlist_empty_entry);
0487 
0488     QCOMPARE(sc3.readEntry(QStringLiteral("stringListJustEmptyElement"), QStringList()), s_stringlist_just_empty_element);
0489 
0490     QCOMPARE(sc3.readEntry(QStringLiteral("stringListEmptyTrailingElement"), QStringList()), s_stringlist_empty_trailing_element);
0491 
0492     QCOMPARE(sc3.readEntry(QStringLiteral("stringListEscapeOddEntry"), QStringList()), s_stringlist_escape_odd_entry);
0493 
0494     QCOMPARE(sc3.readEntry(QStringLiteral("stringListEscapeEvenEntry"), QStringList()), s_stringlist_escape_even_entry);
0495 
0496     QCOMPARE(sc3.readEntry(QStringLiteral("stringListEscapeCommaEntry"), QStringList()), s_stringlist_escape_comma_entry);
0497 
0498     QCOMPARE(sc3.readEntry("listOfIntsEntry1"), QString::fromLatin1("1,2,3,4"));
0499     QList<int> expectedIntList = s_int_listentry1;
0500     QVERIFY(sc3.readEntry("listOfIntsEntry1", QList<int>()) == expectedIntList);
0501 
0502     QCOMPARE(QVariant(sc3.readEntry("variantListEntry", s_variantlist_entry)).toStringList(), QVariant(s_variantlist_entry).toStringList());
0503 
0504     QCOMPARE(sc3.readEntry("listOfByteArraysEntry1", QList<QByteArray>()), s_bytearray_list_entry1);
0505 }
0506 
0507 void KConfigTest::testPath()
0508 {
0509     KConfig sc2(s_kconfig_test_subdir);
0510     KConfigGroup sc3(&sc2, QStringLiteral("Path Type"));
0511     QCOMPARE(sc3.readPathEntry(QStringLiteral("homepath"), QString{}), s_homepath);
0512     QCOMPARE(sc3.readPathEntry(QStringLiteral("homepathescape"), QString{}), s_homepath_escape);
0513     QCOMPARE(sc3.readPathEntry(QStringLiteral("canonicalHomePath"), QString{}), s_canonical_homepath);
0514     QCOMPARE(sc3.entryMap().value(QStringLiteral("homepath")), s_homepath);
0515 
0516     qputenv("WITHSLASH", "/a/");
0517     {
0518         QFile file(m_testConfigDir + QLatin1String("/pathtest"));
0519         file.open(QIODevice::WriteOnly | QIODevice::Text);
0520         QTextStream out(&file);
0521         out << "[Test Group]\n"
0522             << "homePath=$HOME/foo\n"
0523             << "homePath2=file://$HOME/foo\n"
0524             << "withSlash=$WITHSLASH/foo\n"
0525             << "withSlash2=$WITHSLASH\n"
0526             << "withBraces[$e]=file://${HOME}/foo\n"
0527             << "URL[$e]=file://${HOME}/foo\n"
0528             << "hostname[$e]=$(hostname)\n"
0529             << "escapes=aaa,bb/b,ccc\\,ccc\n"
0530             << "noeol=foo" // no EOL
0531             ;
0532     }
0533     KConfig cf2(s_test_subdir + QLatin1String("pathtest"));
0534     KConfigGroup group = cf2.group(QStringLiteral("Test Group"));
0535     QVERIFY(group.hasKey("homePath"));
0536     QCOMPARE(group.readPathEntry("homePath", QString{}), s_homepath);
0537     QVERIFY(group.hasKey("homePath2"));
0538     QCOMPARE(group.readPathEntry("homePath2", QString{}), QLatin1String("file://") + s_homepath);
0539     QVERIFY(group.hasKey("withSlash"));
0540     QCOMPARE(group.readPathEntry("withSlash", QString{}), QStringLiteral("/a//foo"));
0541     QVERIFY(group.hasKey("withSlash2"));
0542     QCOMPARE(group.readPathEntry("withSlash2", QString{}), QStringLiteral("/a/"));
0543     QVERIFY(group.hasKey("withBraces"));
0544     QCOMPARE(group.readPathEntry("withBraces", QString{}), QLatin1String("file://") + s_homepath);
0545     QVERIFY(group.hasKey("URL"));
0546     QCOMPARE(group.readEntry("URL", QString{}), QLatin1String("file://") + s_homepath);
0547     QVERIFY(group.hasKey("hostname"));
0548     QCOMPARE(group.readEntry("hostname", QString{}), QStringLiteral("(hostname)")); // the $ got removed because empty var name
0549     QVERIFY(group.hasKey("noeol"));
0550     QCOMPARE(group.readEntry("noeol", QString{}), QStringLiteral("foo"));
0551 
0552     const auto val = QStringList{QStringLiteral("aaa"), QStringLiteral("bb/b"), QStringLiteral("ccc,ccc")};
0553     QCOMPARE(group.readPathEntry(QStringLiteral("escapes"), QStringList()), val);
0554 }
0555 
0556 void KConfigTest::testPersistenceOfExpandFlagForPath()
0557 {
0558     // This test checks that a path entry starting with $HOME is still flagged
0559     // with the expand flag after the config was altered without rewriting the
0560     // path entry.
0561 
0562     // 1st step: Open the config, add a new dummy entry and then sync the config
0563     // back to the storage.
0564     {
0565         KConfig sc2(s_kconfig_test_subdir);
0566         KConfigGroup sc3(&sc2, QStringLiteral("Path Type"));
0567         sc3.writeEntry("dummy", "dummy");
0568         QVERIFY(sc2.sync());
0569     }
0570 
0571     // 2nd step: Call testPath() again. Rewriting the config must not break
0572     // the testPath() test.
0573     testPath();
0574 }
0575 
0576 void KConfigTest::testPathQtHome()
0577 {
0578     {
0579         QFile file(m_testConfigDir + QLatin1String("/pathtest"));
0580         file.open(QIODevice::WriteOnly | QIODevice::Text);
0581         QTextStream out(&file);
0582         out << "[Test Group]\n"
0583             << "dataDir[$e]=$QT_DATA_HOME/kconfigtest\n"
0584             << "cacheDir[$e]=$QT_CACHE_HOME/kconfigtest\n"
0585             << "configDir[$e]=$QT_CONFIG_HOME/kconfigtest\n";
0586     }
0587     KConfig cf2(s_test_subdir + QLatin1String("pathtest"));
0588     KConfigGroup group = cf2.group(QStringLiteral("Test Group"));
0589     qunsetenv("QT_DATA_HOME");
0590     qunsetenv("QT_CACHE_HOME");
0591     qunsetenv("QT_CONFIG_HOME");
0592     QVERIFY(group.hasKey("dataDir"));
0593     QCOMPARE(group.readEntry("dataDir", QString{}),
0594              QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation).append(QStringLiteral("/kconfigtest")));
0595     QVERIFY(group.hasKey("cacheDir"));
0596     QCOMPARE(group.readEntry("cacheDir", QString{}),
0597              QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation).append(QStringLiteral("/kconfigtest")));
0598     QVERIFY(group.hasKey("configDir"));
0599     QCOMPARE(group.readEntry("configDir", QString{}),
0600              QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation).append(QStringLiteral("/kconfigtest")));
0601     qputenv("QT_DATA_HOME", "/1");
0602     qputenv("QT_CACHE_HOME", "/2");
0603     qputenv("QT_CONFIG_HOME", "/3");
0604     QVERIFY(group.hasKey("dataDir"));
0605     QCOMPARE(group.readEntry("dataDir", QString{}), QStringLiteral("/1/kconfigtest"));
0606     QVERIFY(group.hasKey("cacheDir"));
0607     QCOMPARE(group.readEntry("cacheDir", QString{}), QStringLiteral("/2/kconfigtest"));
0608     QVERIFY(group.hasKey("configDir"));
0609     QCOMPARE(group.readEntry("configDir", QString{}), QStringLiteral("/3/kconfigtest"));
0610 }
0611 
0612 void KConfigTest::testComplex()
0613 {
0614     KConfig sc2(s_kconfig_test_subdir);
0615     KConfigGroup sc3(&sc2, QStringLiteral("Complex Types"));
0616 
0617     QCOMPARE(sc3.readEntry("pointEntry", QPoint()), s_point_entry);
0618     QCOMPARE(sc3.readEntry("sizeEntry", s_size_entry), s_size_entry);
0619     QCOMPARE(sc3.readEntry("rectEntry", QRect(1, 2, 3, 4)), s_rect_entry);
0620     QCOMPARE(sc3.readEntry("dateTimeEntry", QDateTime()).toString(Qt::ISODateWithMs), s_date_time_entry.toString(Qt::ISODateWithMs));
0621     QCOMPARE(sc3.readEntry("dateEntry", QDate()).toString(Qt::ISODate), s_date_time_entry.date().toString(Qt::ISODate));
0622     QCOMPARE(sc3.readEntry("dateTimeWithMSEntry", QDateTime()).toString(Qt::ISODateWithMs), s_date_time_with_ms_entry.toString(Qt::ISODateWithMs));
0623     QCOMPARE(sc3.readEntry("dateTimeEntry", QDate()), s_date_time_entry.date());
0624 }
0625 
0626 void KConfigTest::testEnums()
0627 {
0628     // Visual C++ 2010 (compiler version 16.0) throws an Internal Compiler Error
0629     // when compiling the code in initTestCase that creates these KConfig entries,
0630     // so we can't run this test
0631 #if defined(_MSC_VER) && _MSC_VER == 1600
0632     QSKIP("Visual C++ 2010 can't compile this test");
0633 #endif
0634     KConfig sc(s_kconfig_test_subdir);
0635     KConfigGroup sc3(&sc, QStringLiteral("Enum Types"));
0636 
0637     QCOMPARE(sc3.readEntry("enum-10"), QStringLiteral("Tens"));
0638     QVERIFY(sc3.readEntry("enum-100", Ones) != Ones);
0639     QVERIFY(sc3.readEntry("enum-100", Ones) != Tens);
0640 
0641     QCOMPARE(sc3.readEntry("flags-bit0"), QStringLiteral("bit0"));
0642     QVERIFY(sc3.readEntry("flags-bit0", Flags()) == bit0);
0643 
0644     int eid = staticMetaObject.indexOfEnumerator("Flags");
0645     QVERIFY(eid != -1);
0646     QMetaEnum me = staticMetaObject.enumerator(eid);
0647     Flags bitfield = bit0 | bit1;
0648 
0649     QCOMPARE(sc3.readEntry("flags-bit0-bit1"), QString::fromLatin1(me.valueToKeys(bitfield)));
0650     QVERIFY(sc3.readEntry("flags-bit0-bit1", Flags()) == bitfield);
0651 }
0652 
0653 void KConfigTest::testEntryMap()
0654 {
0655     KConfig sc(s_kconfig_test_subdir);
0656     KConfigGroup cg(&sc, QStringLiteral("Hello"));
0657     QMap<QString, QString> entryMap = cg.entryMap();
0658     qDebug() << entryMap.keys();
0659     QCOMPARE(entryMap.value(QStringLiteral("stringEntry1")), s_string_entry1);
0660     QCOMPARE(entryMap.value(QStringLiteral("stringEntry2")), s_string_entry2);
0661     QCOMPARE(entryMap.value(QStringLiteral("stringEntry3")), s_string_entry3);
0662     QCOMPARE(entryMap.value(QStringLiteral("stringEntry4")), s_string_entry4);
0663     QVERIFY(!entryMap.contains(QStringLiteral("stringEntry5")));
0664     QVERIFY(!entryMap.contains(QStringLiteral("stringEntry6")));
0665     QCOMPARE(entryMap.value(QStringLiteral("Test")), QString::fromUtf8(s_utf8bit_entry));
0666     QCOMPARE(entryMap.value(QStringLiteral("bytearrayEntry")), QString::fromUtf8(s_bytearray_entry.constData()));
0667     QCOMPARE(entryMap.value(QStringLiteral("emptyEntry")), QString{});
0668     QVERIFY(entryMap.contains(QStringLiteral("emptyEntry")));
0669     QCOMPARE(entryMap.value(QStringLiteral("boolEntry1")), s_bool_entry1 ? QStringLiteral("true") : QStringLiteral("false"));
0670     QCOMPARE(entryMap.value(QStringLiteral("boolEntry2")), s_bool_entry2 ? QStringLiteral("true") : QStringLiteral("false"));
0671     QCOMPARE(entryMap.value(QStringLiteral("keywith=equalsign")), s_string_entry1);
0672     QCOMPARE(entryMap.value(QStringLiteral("byteArrayEntry1")), s_string_entry1);
0673     QCOMPARE(entryMap.value(QStringLiteral("doubleEntry1")).toDouble(), s_double_entry);
0674     QCOMPARE(entryMap.value(QStringLiteral("floatEntry1")).toFloat(), s_float_entry);
0675 }
0676 
0677 void KConfigTest::testInvalid()
0678 {
0679     KConfig sc(s_kconfig_test_subdir);
0680 
0681     // all of these should print a message to the kdebug.dbg file
0682     KConfigGroup sc3(&sc, QStringLiteral("Invalid Types"));
0683     sc3.writeEntry("badList", s_variantlist_entry2);
0684 
0685     QList<int> list;
0686 
0687     // 1 element list
0688     list << 1;
0689     sc3.writeEntry(QStringLiteral("badList"), list);
0690 
0691     QVERIFY(sc3.readEntry("badList", QPoint()) == QPoint());
0692     QVERIFY(sc3.readEntry("badList", QRect()) == QRect());
0693     QVERIFY(sc3.readEntry("badList", QSize()) == QSize());
0694     QVERIFY(sc3.readEntry("badList", QDate()) == QDate());
0695     QVERIFY(sc3.readEntry("badList", QDateTime()) == QDateTime());
0696 
0697     // 2 element list
0698     list << 2;
0699     sc3.writeEntry("badList", list);
0700 
0701     QVERIFY(sc3.readEntry("badList", QRect()) == QRect());
0702     QVERIFY(sc3.readEntry("badList", QDate()) == QDate());
0703     QVERIFY(sc3.readEntry("badList", QDateTime()) == QDateTime());
0704 
0705     // 3 element list
0706     list << 303;
0707     sc3.writeEntry("badList", list);
0708 
0709     QVERIFY(sc3.readEntry("badList", QPoint()) == QPoint());
0710     QVERIFY(sc3.readEntry("badList", QRect()) == QRect());
0711     QVERIFY(sc3.readEntry("badList", QSize()) == QSize());
0712     QVERIFY(sc3.readEntry("badList", QDate()) == QDate()); // out of bounds
0713     QVERIFY(sc3.readEntry("badList", QDateTime()) == QDateTime());
0714 
0715     // 4 element list
0716     list << 4;
0717     sc3.writeEntry("badList", list);
0718 
0719     QVERIFY(sc3.readEntry("badList", QPoint()) == QPoint());
0720     QVERIFY(sc3.readEntry("badList", QSize()) == QSize());
0721     QVERIFY(sc3.readEntry("badList", QDate()) == QDate());
0722     QVERIFY(sc3.readEntry("badList", QDateTime()) == QDateTime());
0723 
0724     // 5 element list
0725     list[2] = 3;
0726     list << 5;
0727     sc3.writeEntry("badList", list);
0728 
0729     QVERIFY(sc3.readEntry("badList", QPoint()) == QPoint());
0730     QVERIFY(sc3.readEntry("badList", QRect()) == QRect());
0731     QVERIFY(sc3.readEntry("badList", QSize()) == QSize());
0732     QVERIFY(sc3.readEntry("badList", QDate()) == QDate());
0733     QVERIFY(sc3.readEntry("badList", QDateTime()) == QDateTime());
0734 
0735     // 6 element list
0736     list << 6;
0737     sc3.writeEntry("badList", list);
0738 
0739     QVERIFY(sc3.readEntry("badList", QPoint()) == QPoint());
0740     QVERIFY(sc3.readEntry("badList", QRect()) == QRect());
0741     QVERIFY(sc3.readEntry("badList", QSize()) == QSize());
0742 }
0743 
0744 void KConfigTest::testChangeGroup()
0745 {
0746     KConfig sc(s_kconfig_test_subdir);
0747     KConfigGroup sc3(&sc, QStringLiteral("Hello"));
0748     QCOMPARE(sc3.name(), QStringLiteral("Hello"));
0749     KConfigGroup newGroup(sc3);
0750 
0751     KConfigGroup rootGroup(sc.group(QString()));
0752     QCOMPARE(rootGroup.name(), QStringLiteral("<default>"));
0753     KConfigGroup sc32(rootGroup.group(QStringLiteral("Hello")));
0754     QCOMPARE(sc32.name(), QStringLiteral("Hello"));
0755     KConfigGroup newGroup2(sc32);
0756 }
0757 
0758 // Simple test for deleteEntry
0759 void KConfigTest::testDeleteEntry()
0760 {
0761     const QString configFile = s_test_subdir + QLatin1String("kconfigdeletetest");
0762 
0763     {
0764         KConfig conf(configFile);
0765         conf.group(QStringLiteral("Hello")).writeEntry("DelKey", "ToBeDeleted");
0766     }
0767     const QList<QByteArray> lines = readLines(configFile);
0768     Q_ASSERT(lines.contains("[Hello]\n"));
0769     Q_ASSERT(lines.contains("DelKey=ToBeDeleted\n"));
0770 
0771     KConfig sc(configFile);
0772     KConfigGroup group(&sc, QStringLiteral("Hello"));
0773 
0774     group.deleteEntry("DelKey");
0775     QCOMPARE(group.readEntry("DelKey", QStringLiteral("Fietsbel")), QStringLiteral("Fietsbel"));
0776 
0777     QVERIFY(group.sync());
0778     Q_ASSERT(!readLines(configFile).contains("DelKey=ToBeDeleted\n"));
0779     QCOMPARE(group.readEntry("DelKey", QStringLiteral("still deleted")), QStringLiteral("still deleted"));
0780 }
0781 
0782 void KConfigTest::testDelete()
0783 {
0784     KConfig sc(s_kconfig_test_subdir);
0785 
0786     KConfigGroup ct(&sc, QStringLiteral("Complex Types"));
0787 
0788     // First delete a nested group
0789     KConfigGroup delgr(&ct, QStringLiteral("Nested Group 3"));
0790     QVERIFY(delgr.exists());
0791     QVERIFY(ct.hasGroup(QStringLiteral("Nested Group 3")));
0792     QVERIFY(ct.groupList().contains(QStringLiteral("Nested Group 3")));
0793     delgr.deleteGroup();
0794     QVERIFY(!delgr.exists());
0795     QVERIFY(!ct.hasGroup(QStringLiteral("Nested Group 3")));
0796     QVERIFY(!ct.groupList().contains(QStringLiteral("Nested Group 3")));
0797 
0798     KConfigGroup ng(&ct, QStringLiteral("Nested Group 2"));
0799     QVERIFY(sc.hasGroup(QStringLiteral("Complex Types")));
0800     QVERIFY(sc.groupList().contains(QStringLiteral("Complex Types")));
0801     QVERIFY(!sc.hasGroup(QStringLiteral("Does not exist")));
0802     QVERIFY(ct.hasGroup(QStringLiteral("Nested Group 1")));
0803     QVERIFY(ct.groupList().contains(QStringLiteral("Nested Group 1")));
0804     sc.deleteGroup(QStringLiteral("Complex Types"));
0805     QCOMPARE(sc.group(QStringLiteral("Complex Types")).keyList().count(), 0);
0806     QVERIFY(!sc.hasGroup(QStringLiteral("Complex Types"))); // #192266
0807     QVERIFY(!sc.group(QStringLiteral("Complex Types")).exists());
0808     QVERIFY(!sc.groupList().contains(QStringLiteral("Complex Types")));
0809     QVERIFY(!ct.hasGroup(QStringLiteral("Nested Group 1")));
0810     QVERIFY(!ct.groupList().contains(QStringLiteral("Nested Group 1")));
0811 
0812     QCOMPARE(ct.group(QStringLiteral("Nested Group 1")).keyList().count(), 0);
0813     QCOMPARE(ct.group(QStringLiteral("Nested Group 2")).keyList().count(), 0);
0814     QCOMPARE(ng.group(QStringLiteral("Nested Group 2.1")).keyList().count(), 0);
0815 
0816     KConfigGroup cg(&sc, QStringLiteral("AAA"));
0817     cg.deleteGroup();
0818     QVERIFY(sc.entryMap(QStringLiteral("Complex Types")).isEmpty());
0819     QVERIFY(sc.entryMap(QStringLiteral("AAA")).isEmpty());
0820     QVERIFY(!sc.entryMap(QStringLiteral("Hello")).isEmpty()); // not deleted group
0821     QVERIFY(sc.entryMap(QStringLiteral("FooBar")).isEmpty()); // inexistent group
0822 
0823     KConfigGroup(&sc, QStringLiteral("LocalGroupToBeDeleted")).deleteGroup();
0824 
0825     QVERIFY(cg.sync());
0826     // Check what happens on disk
0827     const QList<QByteArray> lines = readLines();
0828     // qDebug() << lines;
0829     QVERIFY(!lines.contains("[Complex Types]\n"));
0830     QVERIFY(!lines.contains("[Complex Types][Nested Group 1]\n"));
0831     QVERIFY(!lines.contains("[Complex Types][Nested Group 2]\n"));
0832     QVERIFY(!lines.contains("[Complex Types][Nested Group 2.1]\n"));
0833     QVERIFY(!lines.contains("[LocalGroupToBeDeleted]\n"));
0834     QVERIFY(lines.contains("[AAA]\n")); // deleted from kconfigtest, but present in kdeglobals, so [$d]
0835     QVERIFY(lines.contains("[Hello]\n")); // a group that was not deleted
0836 
0837     // test for entries that are marked as deleted when there is no default
0838     KConfig cf(s_kconfig_test_subdir, KConfig::SimpleConfig); // make sure there are no defaults
0839     cg = cf.group(QStringLiteral("Portable Devices"));
0840     cg.writeEntry("devices|manual|(null)", "whatever");
0841     cg.writeEntry("devices|manual|/mnt/ipod", "/mnt/ipod");
0842     QVERIFY(cf.sync());
0843 
0844     int count = 0;
0845     const QList<QByteArray> listLines = readLines();
0846     for (const QByteArray &item : listLines) {
0847         if (item.startsWith("devices|")) { // krazy:exclude=strings
0848             ++count;
0849         }
0850     }
0851     QCOMPARE(count, 2);
0852     cg.deleteEntry("devices|manual|/mnt/ipod");
0853     QVERIFY(cf.sync());
0854     const QList<QByteArray> listLines2 = readLines();
0855     for (const QByteArray &item : listLines2) {
0856         QVERIFY(!item.contains("ipod"));
0857     }
0858 }
0859 
0860 void KConfigTest::testDefaultGroup()
0861 {
0862     KConfig sc(s_kconfig_test_subdir);
0863     KConfigGroup defaultGroup(&sc, QStringLiteral("<default>"));
0864     QCOMPARE(defaultGroup.name(), QStringLiteral("<default>"));
0865     QVERIFY(!defaultGroup.exists());
0866     defaultGroup.writeEntry("TestKey", "defaultGroup");
0867     QVERIFY(defaultGroup.exists());
0868     QCOMPARE(defaultGroup.readEntry("TestKey", QString{}), QStringLiteral("defaultGroup"));
0869     QVERIFY(sc.sync());
0870 
0871     {
0872         // Test reading it
0873         KConfig sc2(s_kconfig_test_subdir);
0874         KConfigGroup defaultGroup2(&sc2, QStringLiteral("<default>"));
0875         QCOMPARE(defaultGroup2.name(), QStringLiteral("<default>"));
0876         QVERIFY(defaultGroup2.exists());
0877         QCOMPARE(defaultGroup2.readEntry("TestKey", QString{}), QStringLiteral("defaultGroup"));
0878     }
0879     {
0880         // Test reading it
0881         KConfig sc2(s_kconfig_test_subdir);
0882         KConfigGroup emptyGroup(&sc2, QString());
0883         QCOMPARE(emptyGroup.name(), QStringLiteral("<default>"));
0884         QVERIFY(emptyGroup.exists());
0885         QCOMPARE(emptyGroup.readEntry("TestKey", QString{}), QStringLiteral("defaultGroup"));
0886     }
0887 
0888     QList<QByteArray> lines = readLines();
0889     QVERIFY(!lines.contains("[]\n"));
0890     QVERIFY(!lines.isEmpty());
0891     QCOMPARE(lines.first(), QByteArray("TestKey=defaultGroup\n"));
0892 
0893     // Now that the group exists make sure it isn't returned from groupList()
0894     const QStringList groupList = sc.groupList();
0895     for (const QString &group : groupList) {
0896         QVERIFY(!group.isEmpty() && group != QLatin1String("<default>"));
0897     }
0898 
0899     defaultGroup.deleteGroup();
0900     QVERIFY(sc.sync());
0901 
0902     // Test if deleteGroup worked
0903     lines = readLines();
0904     QVERIFY(lines.first() != QByteArray("TestKey=defaultGroup\n"));
0905 }
0906 
0907 void KConfigTest::testEmptyGroup()
0908 {
0909     KConfig sc(s_kconfig_test_subdir);
0910     KConfigGroup emptyGroup(&sc, QString());
0911     QCOMPARE(emptyGroup.name(), QStringLiteral("<default>")); // confusing, heh?
0912     QVERIFY(!emptyGroup.exists());
0913     emptyGroup.writeEntry("TestKey", "emptyGroup");
0914     QVERIFY(emptyGroup.exists());
0915     QCOMPARE(emptyGroup.readEntry("TestKey", QString{}), QStringLiteral("emptyGroup"));
0916     QVERIFY(sc.sync());
0917 
0918     {
0919         // Test reading it
0920         KConfig sc2(s_kconfig_test_subdir);
0921         KConfigGroup defaultGroup(&sc2, QStringLiteral("<default>"));
0922         QCOMPARE(defaultGroup.name(), QStringLiteral("<default>"));
0923         QVERIFY(defaultGroup.exists());
0924         QCOMPARE(defaultGroup.readEntry("TestKey", QString{}), QStringLiteral("emptyGroup"));
0925     }
0926     {
0927         // Test reading it
0928         KConfig sc2(s_kconfig_test_subdir);
0929         KConfigGroup emptyGroup2(&sc2, QString());
0930         QCOMPARE(emptyGroup2.name(), QStringLiteral("<default>"));
0931         QVERIFY(emptyGroup2.exists());
0932         QCOMPARE(emptyGroup2.readEntry("TestKey", QString{}), QStringLiteral("emptyGroup"));
0933     }
0934 
0935     QList<QByteArray> lines = readLines();
0936     QVERIFY(!lines.contains("[]\n")); // there's no support for the [] group, in fact.
0937     QCOMPARE(lines.first(), QByteArray("TestKey=emptyGroup\n"));
0938 
0939     // Now that the group exists make sure it isn't returned from groupList()
0940     const QStringList groupList = sc.groupList();
0941     for (const QString &group : groupList) {
0942         QVERIFY(!group.isEmpty() && group != QLatin1String("<default>"));
0943     }
0944     emptyGroup.deleteGroup();
0945     QVERIFY(sc.sync());
0946 
0947     // Test if deleteGroup worked
0948     lines = readLines();
0949     QVERIFY(lines.first() != QByteArray("TestKey=defaultGroup\n"));
0950 }
0951 
0952 #if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) && !defined(Q_OS_BLACKBERRY) && !defined(Q_OS_ANDROID)
0953 #define Q_XDG_PLATFORM
0954 #endif
0955 
0956 void KConfigTest::testCascadingWithLocale()
0957 {
0958     // This test relies on XDG_CONFIG_DIRS, which only has effect on Unix.
0959     // Cascading (more than two levels) isn't available at all on Windows.
0960 #ifdef Q_XDG_PLATFORM
0961     QTemporaryDir middleDir;
0962     QTemporaryDir globalDir;
0963     const QByteArray oldConfigDirs = qgetenv("XDG_CONFIG_DIRS");
0964     qputenv("XDG_CONFIG_DIRS", qPrintable(middleDir.path() + QLatin1Char(':') + globalDir.path()));
0965 
0966     const QString globalConfigDir = globalDir.path() + QLatin1Char('/') + s_test_subdir;
0967     QVERIFY(QDir().mkpath(globalConfigDir));
0968     QFile global(globalConfigDir + QLatin1String("foo.desktop"));
0969     QVERIFY(global.open(QIODevice::WriteOnly | QIODevice::Text));
0970     QTextStream globalOut(&global);
0971     globalOut << "[Group]\n"
0972               << "FromGlobal=true\n"
0973               << "FromGlobal[fr]=vrai\n"
0974               << "Name=Testing\n"
0975               << "Name[fr]=FR\n"
0976               << "Other=Global\n"
0977               << "Other[fr]=Global_FR\n";
0978     global.close();
0979 
0980     const QString middleConfigDir = middleDir.path() + QLatin1Char('/') + s_test_subdir;
0981     QVERIFY(QDir().mkpath(middleConfigDir));
0982     QFile local(middleConfigDir + QLatin1String("foo.desktop"));
0983     QVERIFY(local.open(QIODevice::WriteOnly | QIODevice::Text));
0984     QTextStream out(&local);
0985     out << "[Group]\n"
0986         << "FromLocal=true\n"
0987         << "FromLocal[fr]=vrai\n"
0988         << "Name=Local Testing\n"
0989         << "Name[fr]=FR\n"
0990         << "Other=English Only\n";
0991     local.close();
0992 
0993     KConfig config(s_test_subdir + QLatin1String("foo.desktop"));
0994     KConfigGroup group = config.group(QStringLiteral("Group"));
0995     QCOMPARE(group.readEntry("FromGlobal"), QStringLiteral("true"));
0996     QCOMPARE(group.readEntry("FromLocal"), QStringLiteral("true"));
0997     QCOMPARE(group.readEntry("Name"), QStringLiteral("Local Testing"));
0998     config.setLocale(QStringLiteral("fr"));
0999     QCOMPARE(group.readEntry("FromGlobal"), QStringLiteral("vrai"));
1000     QCOMPARE(group.readEntry("FromLocal"), QStringLiteral("vrai"));
1001     QCOMPARE(group.readEntry("Name"), QStringLiteral("FR"));
1002     QCOMPARE(group.readEntry("Other"), QStringLiteral("English Only")); // Global_FR is locally overridden
1003     qputenv("XDG_CONFIG_DIRS", oldConfigDirs);
1004 #endif
1005 }
1006 
1007 void KConfigTest::testMerge()
1008 {
1009     DefaultLocale defaultLocale;
1010     QLocale::setDefault(QLocale::c());
1011     KConfig config(s_test_subdir + QLatin1String("mergetest"), KConfig::SimpleConfig);
1012 
1013     KConfigGroup cg = config.group(QStringLiteral("some group"));
1014     cg.writeEntry("entry", " random entry");
1015     cg.writeEntry("another entry", "blah blah blah");
1016 
1017     {
1018         // simulate writing by another process
1019         QFile file(m_testConfigDir + QLatin1String("/mergetest"));
1020         file.open(QIODevice::WriteOnly | QIODevice::Text);
1021         QTextStream out(&file);
1022         out << "[Merged Group]\n"
1023             << "entry1=Testing\n"
1024             << "entry2=More Testing\n"
1025             << "[some group]\n"
1026             << "entry[fr]=French\n"
1027             << "entry[es]=Spanish\n"
1028             << "entry[de]=German\n";
1029     }
1030     QVERIFY(config.sync());
1031 
1032     {
1033         QList<QByteArray> lines;
1034         // this is what the file should look like
1035         lines << "[Merged Group]\n"
1036               << "entry1=Testing\n"
1037               << "entry2=More Testing\n"
1038               << "\n"
1039               << "[some group]\n"
1040               << "another entry=blah blah blah\n"
1041               << "entry=\\srandom entry\n"
1042               << "entry[de]=German\n"
1043               << "entry[es]=Spanish\n"
1044               << "entry[fr]=French\n";
1045         QFile file(m_testConfigDir + QLatin1String("/mergetest"));
1046         file.open(QIODevice::ReadOnly | QIODevice::Text);
1047         for (const QByteArray &line : std::as_const(lines)) {
1048             QCOMPARE(line, file.readLine());
1049         }
1050     }
1051 }
1052 
1053 void KConfigTest::testImmutable()
1054 {
1055     {
1056         QFile file(m_testConfigDir + QLatin1String("/immutabletest"));
1057         file.open(QIODevice::WriteOnly | QIODevice::Text);
1058         QTextStream out(&file);
1059         out << "[$i]\n"
1060             << "entry1=Testing\n"
1061             << "[group][$i]\n"
1062             << "[group][subgroup][$i]\n";
1063     }
1064 
1065     KConfig config(s_test_subdir + QLatin1String("immutabletest"), KConfig::SimpleConfig);
1066     QVERIFY(config.isGroupImmutable(QString()));
1067     KConfigGroup cg = config.group(QString());
1068     QVERIFY(cg.isEntryImmutable("entry1"));
1069     KConfigGroup cg1 = config.group(QStringLiteral("group"));
1070     QVERIFY(cg1.isImmutable());
1071     KConfigGroup cg1a = cg.group(QStringLiteral("group"));
1072     QVERIFY(cg1a.isImmutable());
1073     KConfigGroup cg2 = cg1.group(QStringLiteral("subgroup"));
1074     QVERIFY(cg2.isImmutable());
1075 }
1076 
1077 void KConfigTest::testOptionOrder()
1078 {
1079     {
1080         QFile file(m_testConfigDir + QLatin1String("/doubleattrtest"));
1081         file.open(QIODevice::WriteOnly | QIODevice::Text);
1082         QTextStream out(&file);
1083         out << "[group3]\n"
1084             << "entry2=unlocalized\n"
1085             << "entry2[$i][de_DE]=t2\n";
1086     }
1087     KConfig config(s_test_subdir + QLatin1String("doubleattrtest"), KConfig::SimpleConfig);
1088     config.setLocale(QStringLiteral("de_DE"));
1089     KConfigGroup cg3 = config.group(QStringLiteral("group3"));
1090     QVERIFY(!cg3.isImmutable());
1091     QCOMPARE(cg3.readEntry("entry2", ""), QStringLiteral("t2"));
1092     QVERIFY(cg3.isEntryImmutable("entry2"));
1093     config.setLocale(QStringLiteral("C"));
1094     QCOMPARE(cg3.readEntry("entry2", ""), QStringLiteral("unlocalized"));
1095     QVERIFY(!cg3.isEntryImmutable("entry2"));
1096     cg3.writeEntry("entry2", "modified");
1097     QVERIFY(config.sync());
1098 
1099     {
1100         QList<QByteArray> lines;
1101         // this is what the file should look like
1102         lines << "[group3]\n"
1103               << "entry2=modified\n"
1104               << "entry2[de_DE][$i]=t2\n";
1105 
1106         QFile file(m_testConfigDir + QLatin1String("/doubleattrtest"));
1107         file.open(QIODevice::ReadOnly | QIODevice::Text);
1108         for (const QByteArray &line : std::as_const(lines)) {
1109             QCOMPARE(line, file.readLine());
1110         }
1111     }
1112 }
1113 
1114 void KConfigTest::testGroupEscape()
1115 {
1116     KConfig config(s_test_subdir + QLatin1String("groupescapetest"), KConfig::SimpleConfig);
1117     QVERIFY(config.group(s_dollargroup).exists());
1118 }
1119 
1120 void KConfigTest::testSubGroup()
1121 {
1122     KConfig sc(s_kconfig_test_subdir);
1123     KConfigGroup cg(&sc, QStringLiteral("ParentGroup"));
1124     QCOMPARE(cg.readEntry("parentgrpstring", ""), QStringLiteral("somevalue"));
1125     KConfigGroup subcg1(&cg, QStringLiteral("SubGroup1"));
1126     QCOMPARE(subcg1.name(), QStringLiteral("SubGroup1"));
1127     QCOMPARE(subcg1.readEntry("somestring", ""), QStringLiteral("somevalue"));
1128     KConfigGroup subcg2(&cg, QStringLiteral("SubGroup2"));
1129     QCOMPARE(subcg2.name(), QStringLiteral("SubGroup2"));
1130     QCOMPARE(subcg2.readEntry("substring", ""), QStringLiteral("somevalue"));
1131     KConfigGroup subcg3(&cg, QStringLiteral("SubGroup/3"));
1132     QCOMPARE(subcg3.readEntry("sub3string", ""), QStringLiteral("somevalue"));
1133     QCOMPARE(subcg3.name(), QStringLiteral("SubGroup/3"));
1134     KConfigGroup rcg(&sc, QString());
1135     KConfigGroup srcg(&rcg, QStringLiteral("ParentGroup"));
1136     QCOMPARE(srcg.readEntry("parentgrpstring", ""), QStringLiteral("somevalue"));
1137 
1138     QStringList groupList = cg.groupList();
1139     groupList.sort(); // comes from QSet, so order is undefined
1140     QCOMPARE(groupList, (QStringList{QStringLiteral("SubGroup/3"), QStringLiteral("SubGroup1"), QStringLiteral("SubGroup2")}));
1141 
1142     const QStringList expectedSubgroup3Keys{QStringLiteral("sub3string")};
1143     QCOMPARE(subcg3.keyList(), expectedSubgroup3Keys);
1144     const QStringList expectedParentGroupKeys{QStringLiteral("parentgrpstring")};
1145 
1146     QCOMPARE(cg.keyList(), expectedParentGroupKeys);
1147 
1148     QCOMPARE(QStringList(cg.entryMap().keys()), expectedParentGroupKeys);
1149     QCOMPARE(QStringList(subcg3.entryMap().keys()), expectedSubgroup3Keys);
1150 
1151     // Create A group containing only other groups. We want to make sure it
1152     // shows up in groupList of sc
1153     KConfigGroup neg(&sc, QStringLiteral("NoEntryGroup"));
1154     KConfigGroup negsub1(&neg, QStringLiteral("NEG Child1"));
1155     negsub1.writeEntry("entry", "somevalue");
1156     KConfigGroup negsub2(&neg, QStringLiteral("NEG Child2"));
1157     KConfigGroup negsub3(&neg, QStringLiteral("NEG Child3"));
1158     KConfigGroup negsub31(&negsub3, QStringLiteral("NEG Child3-1"));
1159     KConfigGroup negsub4(&neg, QStringLiteral("NEG Child4"));
1160     KConfigGroup negsub41(&negsub4, QStringLiteral("NEG Child4-1"));
1161     negsub41.writeEntry("entry", "somevalue");
1162 
1163     // A group exists if it has content
1164     QVERIFY(negsub1.exists());
1165 
1166     // But it doesn't exist if it has no content
1167     // Ossi and David say: this is how it's supposed to work.
1168     // However you could add a dummy entry for now, or we could add a "Persist" feature to kconfig groups
1169     // which would make it written out, much like "immutable" already makes them persistent.
1170     QVERIFY(!negsub2.exists());
1171 
1172     // A subgroup does not qualify as content if it is also empty
1173     QVERIFY(!negsub3.exists());
1174 
1175     // A subgroup with content is ok
1176     QVERIFY(negsub4.exists());
1177 
1178     // Only subgroups with content show up in groupList()
1179     // QEXPECT_FAIL("", "Empty subgroups do not show up in groupList()", Continue);
1180     // QCOMPARE(neg.groupList(), QStringList() << "NEG Child1" << "NEG Child2" << "NEG Child3" << "NEG Child4");
1181     // This is what happens
1182     QStringList groups = neg.groupList();
1183     groups.sort(); // Qt5 made the ordering unreliable, due to QHash
1184     QCOMPARE(groups, (QStringList{QStringLiteral("NEG Child1"), QStringLiteral("NEG Child4")}));
1185 
1186     // make sure groupList() isn't returning something it shouldn't
1187     const QStringList listGroup = sc.groupList();
1188     for (const QString &group : listGroup) {
1189         QVERIFY(!group.isEmpty() && group != QLatin1String("<default>"));
1190         QVERIFY(!group.contains(QChar(0x1d)));
1191         QVERIFY(!group.contains(QLatin1String("subgroup")));
1192         QVERIFY(!group.contains(QLatin1String("SubGroup")));
1193     }
1194 
1195     QVERIFY(sc.sync());
1196 
1197     // Check that the empty groups are not written out.
1198     const QList<QByteArray> lines = readLines();
1199     QVERIFY(lines.contains("[NoEntryGroup][NEG Child1]\n"));
1200     QVERIFY(!lines.contains("[NoEntryGroup][NEG Child2]\n"));
1201     QVERIFY(!lines.contains("[NoEntryGroup][NEG Child3]\n"));
1202     QVERIFY(!lines.contains("[NoEntryGroup][NEG Child4]\n")); // implicit group, not written out
1203     QVERIFY(lines.contains("[NoEntryGroup][NEG Child4][NEG Child4-1]\n"));
1204 }
1205 
1206 void KConfigTest::testAddConfigSources()
1207 {
1208     KConfig cf(s_test_subdir + QLatin1String("specificrc"));
1209 
1210     cf.addConfigSources(QStringList{m_testConfigDir + QLatin1String("/baserc")});
1211     cf.reparseConfiguration();
1212 
1213     KConfigGroup specificgrp(&cf, QStringLiteral("Specific Only Group"));
1214     QCOMPARE(specificgrp.readEntry("ExistingEntry", ""), QStringLiteral("DevValue"));
1215 
1216     KConfigGroup sharedgrp(&cf, QStringLiteral("Shared Group"));
1217     QCOMPARE(sharedgrp.readEntry("SomeSpecificOnlyEntry", ""), QStringLiteral("DevValue"));
1218     QCOMPARE(sharedgrp.readEntry("SomeBaseOnlyEntry", ""), QStringLiteral("BaseValue"));
1219     QCOMPARE(sharedgrp.readEntry("SomeSharedEntry", ""), QStringLiteral("DevValue"));
1220 
1221     KConfigGroup basegrp(&cf, QStringLiteral("Base Only Group"));
1222     QCOMPARE(basegrp.readEntry("ExistingEntry", ""), QStringLiteral("BaseValue"));
1223     basegrp.writeEntry("New Entry Base Only", "SomeValue");
1224 
1225     KConfigGroup newgrp(&cf, QStringLiteral("New Group"));
1226     newgrp.writeEntry("New Entry", "SomeValue");
1227 
1228     QVERIFY(cf.sync());
1229 
1230     KConfig plaincfg(s_test_subdir + QLatin1String("specificrc"));
1231 
1232     KConfigGroup newgrp2(&plaincfg, QStringLiteral("New Group"));
1233     QCOMPARE(newgrp2.readEntry("New Entry", ""), QStringLiteral("SomeValue"));
1234 
1235     KConfigGroup basegrp2(&plaincfg, QStringLiteral("Base Only Group"));
1236     QCOMPARE(basegrp2.readEntry("New Entry Base Only", ""), QStringLiteral("SomeValue"));
1237 }
1238 
1239 void KConfigTest::testGroupCopyTo()
1240 {
1241     KConfig cf1(s_kconfig_test_subdir);
1242     KConfigGroup original = cf1.group(QStringLiteral("Enum Types"));
1243 
1244     KConfigGroup copy = cf1.group(QStringLiteral("Enum Types Copy"));
1245     original.copyTo(&copy); // copy from one group to another
1246     QCOMPARE(copy.entryMap(), original.entryMap());
1247 
1248     KConfig cf2(s_test_subdir + QLatin1String("copy_of_kconfigtest"), KConfig::SimpleConfig);
1249     QVERIFY(!cf2.hasGroup(original.name()));
1250     QVERIFY(!cf2.hasGroup(copy.name()));
1251 
1252     KConfigGroup newGroup = cf2.group(original.name());
1253     original.copyTo(&newGroup); // copy from one file to another
1254     QVERIFY(cf2.hasGroup(original.name()));
1255     QVERIFY(!cf2.hasGroup(copy.name())); // make sure we didn't copy more than we wanted
1256     QCOMPARE(newGroup.entryMap(), original.entryMap());
1257 }
1258 
1259 void KConfigTest::testConfigCopyToSync()
1260 {
1261     KConfig cf1(s_kconfig_test_subdir);
1262     // Prepare source file
1263     KConfigGroup group(&cf1, QStringLiteral("CopyToTest"));
1264     group.writeEntry("Type", "Test");
1265     QVERIFY(cf1.sync());
1266 
1267     // Copy to "destination"
1268     const QString destination = m_testConfigDir + QLatin1String("/kconfigcopytotest");
1269     QFile::remove(destination);
1270 
1271     KConfig cf2(s_test_subdir + QLatin1String("kconfigcopytotest"));
1272     KConfigGroup group2(&cf2, QStringLiteral("CopyToTest"));
1273 
1274     group.copyTo(&group2);
1275 
1276     QString testVal = group2.readEntry("Type");
1277     QCOMPARE(testVal, QStringLiteral("Test"));
1278     // should write to disk the copied data from group
1279     QVERIFY(cf2.sync());
1280     QVERIFY(QFile::exists(destination));
1281 }
1282 
1283 void KConfigTest::testConfigCopyTo()
1284 {
1285     KConfig cf1(s_kconfig_test_subdir);
1286     {
1287         // Prepare source file
1288         KConfigGroup group(&cf1, QStringLiteral("CopyToTest"));
1289         group.writeEntry("Type", "Test");
1290         QVERIFY(cf1.sync());
1291     }
1292 
1293     {
1294         // Copy to "destination"
1295         const QString destination = m_testConfigDir + QLatin1String("/kconfigcopytotest");
1296         QFile::remove(destination);
1297         KConfig cf2;
1298         cf1.copyTo(destination, &cf2);
1299         KConfigGroup group2(&cf2, QStringLiteral("CopyToTest"));
1300         QString testVal = group2.readEntry("Type");
1301         QCOMPARE(testVal, QStringLiteral("Test"));
1302         QVERIFY(cf2.sync());
1303         QVERIFY(QFile::exists(destination));
1304     }
1305 
1306     // Check copied config file on disk
1307     KConfig cf3(s_test_subdir + QLatin1String("kconfigcopytotest"));
1308     KConfigGroup group3(&cf3, QStringLiteral("CopyToTest"));
1309     QString testVal = group3.readEntry("Type");
1310     QCOMPARE(testVal, QStringLiteral("Test"));
1311 }
1312 
1313 void KConfigTest::testReparent()
1314 {
1315     KConfig cf(s_kconfig_test_subdir);
1316     const QString name(QStringLiteral("Enum Types"));
1317     KConfigGroup group = cf.group(name);
1318     const QMap<QString, QString> originalMap = group.entryMap();
1319     KConfigGroup parent = cf.group(QStringLiteral("Parent Group"));
1320 
1321     QVERIFY(!parent.hasGroup(name));
1322 
1323     QVERIFY(group.entryMap() == originalMap);
1324 
1325     group.reparent(&parent); // see if it can be made a sub-group of another group
1326     QVERIFY(parent.hasGroup(name));
1327     QCOMPARE(group.entryMap(), originalMap);
1328 
1329     group.reparent(&cf); // see if it can make it a top-level group again
1330     //    QVERIFY(!parent.hasGroup(name));
1331     QCOMPARE(group.entryMap(), originalMap);
1332 }
1333 
1334 static void ageTimeStamp(const QString &path, int nsec)
1335 {
1336 #ifdef Q_OS_UNIX
1337     QDateTime mtime = QFileInfo(path).lastModified().addSecs(-nsec);
1338     struct utimbuf utbuf;
1339     utbuf.actime = mtime.toSecsSinceEpoch();
1340     utbuf.modtime = utbuf.actime;
1341     utime(QFile::encodeName(path).constData(), &utbuf);
1342 #else
1343     QTest::qSleep(nsec * 1000);
1344 #endif
1345 }
1346 
1347 void KConfigTest::testWriteOnSync()
1348 {
1349     QDateTime oldStamp;
1350     QDateTime newStamp;
1351     KConfig sc(s_kconfig_test_subdir, KConfig::IncludeGlobals);
1352 
1353     // Age the timestamp of global config file a few sec, and collect it.
1354     QString globFile = m_kdeGlobalsPath;
1355     ageTimeStamp(globFile, 2); // age 2 sec
1356     oldStamp = QFileInfo(globFile).lastModified();
1357 
1358     // Add a local entry and sync the config.
1359     // Should not rewrite the global config file.
1360     KConfigGroup cgLocal(&sc, QStringLiteral("Locals"));
1361     cgLocal.writeEntry("someLocalString", "whatever");
1362     QVERIFY(sc.sync());
1363 
1364     // Verify that the timestamp of global config file didn't change.
1365     newStamp = QFileInfo(globFile).lastModified();
1366     QCOMPARE(newStamp, oldStamp);
1367 
1368     // Age the timestamp of local config file a few sec, and collect it.
1369     QString locFile = m_testConfigDir + QLatin1String("/kconfigtest");
1370     ageTimeStamp(locFile, 2); // age 2 sec
1371     oldStamp = QFileInfo(locFile).lastModified();
1372 
1373     // Add a global entry and sync the config.
1374     // Should not rewrite the local config file.
1375     KConfigGroup cgGlobal(&sc, QStringLiteral("Globals"));
1376     cgGlobal.writeEntry("someGlobalString", "whatever", KConfig::Persistent | KConfig::Global);
1377     QVERIFY(sc.sync());
1378 
1379     // Verify that the timestamp of local config file didn't change.
1380     newStamp = QFileInfo(locFile).lastModified();
1381     QCOMPARE(newStamp, oldStamp);
1382 }
1383 
1384 void KConfigTest::testFailOnReadOnlyFileSync()
1385 {
1386     KConfig sc(s_test_subdir + QLatin1String("kconfigfailonreadonlytest"));
1387     KConfigGroup cgLocal(&sc, QStringLiteral("Locals"));
1388 
1389     cgLocal.writeEntry("someLocalString", "whatever");
1390     QVERIFY(cgLocal.sync());
1391 
1392     QFile f(m_testConfigDir + QLatin1String("kconfigfailonreadonlytest"));
1393     QVERIFY(f.exists());
1394     QVERIFY(f.setPermissions(QFileDevice::ReadOwner));
1395 
1396 #ifndef Q_OS_WIN
1397     if (::getuid() == 0) {
1398         QSKIP("Root can write to read-only files");
1399     }
1400 #endif
1401     cgLocal.writeEntry("someLocalString", "whatever2");
1402     QVERIFY(!cgLocal.sync());
1403 
1404     QVERIFY(f.setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner));
1405     QVERIFY(f.remove());
1406 }
1407 
1408 void KConfigTest::testDirtyOnEqual()
1409 {
1410     QDateTime oldStamp;
1411     QDateTime newStamp;
1412     KConfig sc(s_kconfig_test_subdir);
1413 
1414     // Initialize value
1415     KConfigGroup cgLocal(&sc, QStringLiteral("random"));
1416     cgLocal.writeEntry("theKey", "whatever");
1417     QVERIFY(sc.sync());
1418 
1419     // Age the timestamp of local config file a few sec, and collect it.
1420     QString locFile = m_testConfigDir + QLatin1String("/kconfigtest");
1421     ageTimeStamp(locFile, 2); // age 2 sec
1422     oldStamp = QFileInfo(locFile).lastModified();
1423 
1424     // Write exactly the same again
1425     cgLocal.writeEntry("theKey", "whatever");
1426     // This should be a no-op
1427     QVERIFY(sc.sync());
1428 
1429     // Verify that the timestamp of local config file didn't change.
1430     newStamp = QFileInfo(locFile).lastModified();
1431     QCOMPARE(newStamp, oldStamp);
1432 }
1433 
1434 void KConfigTest::testDirtyOnEqualOverdo()
1435 {
1436     QByteArray val1(
1437         "\0"
1438         "one",
1439         4);
1440     QByteArray val2(
1441         "\0"
1442         "two",
1443         4);
1444     QByteArray defvalr;
1445 
1446     KConfig sc(s_kconfig_test_subdir);
1447     KConfigGroup cgLocal(&sc, QStringLiteral("random"));
1448     cgLocal.writeEntry("someKey", val1);
1449     QCOMPARE(cgLocal.readEntry("someKey", defvalr), val1);
1450     cgLocal.writeEntry("someKey", val2);
1451     QCOMPARE(cgLocal.readEntry("someKey", defvalr), val2);
1452 }
1453 
1454 void KConfigTest::testCreateDir()
1455 {
1456     // Test auto-creating the parent directory when needed (KConfigIniBackend::createEnclosing)
1457     const QString kdehome = QDir::home().canonicalPath() + QLatin1String("/.kde-unit-test");
1458     const QString subdir = kdehome + QLatin1String("/newsubdir");
1459     const QString file = subdir + QLatin1String("/foo.desktop");
1460     QFile::remove(file);
1461     QDir().rmdir(subdir);
1462     QVERIFY(!QDir().exists(subdir));
1463     KDesktopFile desktopFile(file);
1464     desktopFile.desktopGroup().writeEntry("key", "value");
1465     QVERIFY(desktopFile.sync());
1466     QVERIFY(QFile::exists(file));
1467 
1468     // Cleanup
1469     QFile::remove(file);
1470     QDir().rmdir(subdir);
1471 }
1472 
1473 void KConfigTest::testSyncOnExit()
1474 {
1475     // Often, the KGlobalPrivate global static's destructor ends up calling ~KConfig ->
1476     // KConfig::sync ... and if that code triggers KGlobal code again then things could crash.
1477     // So here's a test for modifying KSharedConfig::openConfig() and not syncing, the process exit will sync.
1478     KConfigGroup grp(KSharedConfig::openConfig(s_test_subdir + QLatin1String("syncOnExitRc")), QStringLiteral("syncOnExit"));
1479     grp.writeEntry("key", "value");
1480 }
1481 
1482 void KConfigTest::testSharedConfig()
1483 {
1484     // Can I use a KConfigGroup even after the KSharedConfigPtr goes out of scope?
1485     KConfigGroup myConfigGroup;
1486     {
1487         KSharedConfigPtr config = KSharedConfig::openConfig(s_kconfig_test_subdir);
1488         myConfigGroup = KConfigGroup(config, QStringLiteral("Hello"));
1489     }
1490     QCOMPARE(myConfigGroup.readEntry("stringEntry1"), s_string_entry1);
1491 
1492     // Get the main config
1493     KSharedConfigPtr mainConfig = KSharedConfig::openConfig();
1494     KConfigGroup mainGroup(mainConfig, QStringLiteral("Main"));
1495     QCOMPARE(mainGroup.readEntry("Key", QString{}), QStringLiteral("Value"));
1496 }
1497 
1498 void KConfigTest::testLocaleConfig()
1499 {
1500     // Initialize the testdata
1501     QDir().mkpath(m_testConfigDir);
1502     const QString file = m_testConfigDir + QLatin1String("/localized.test");
1503     QFile::remove(file);
1504     QFile f(file);
1505     QVERIFY(f.open(QIODevice::WriteOnly));
1506     QTextStream ts(&f);
1507     ts << "[Test_Wrong]\n";
1508     ts << "foo[ca]=5\n";
1509     ts << "foostring[ca]=nice\n";
1510     ts << "foobool[ca]=true\n";
1511     ts << "[Test_Right]\n";
1512     ts << "foo=5\n";
1513     ts << "foo[ca]=5\n";
1514     ts << "foostring=primary\n";
1515     ts << "foostring[ca]=nice\n";
1516     ts << "foobool=primary\n";
1517     ts << "foobool[ca]=true\n";
1518     f.close();
1519 
1520     // Load the testdata
1521     QVERIFY(QFile::exists(file));
1522     KConfig config(file);
1523     config.setLocale(QStringLiteral("ca"));
1524 
1525     // This group has only localized values. That is not supported. The values
1526     // should be dropped on loading.
1527     KConfigGroup cg(&config, QStringLiteral("Test_Wrong"));
1528     QEXPECT_FAIL("", "The localized values are not dropped", Continue);
1529     QVERIFY(!cg.hasKey("foo"));
1530     QEXPECT_FAIL("", "The localized values are not dropped", Continue);
1531     QVERIFY(!cg.hasKey("foostring"));
1532     QEXPECT_FAIL("", "The localized values are not dropped", Continue);
1533     QVERIFY(!cg.hasKey("foobool"));
1534 
1535     // Now check the correct config group
1536     KConfigGroup cg2(&config, QStringLiteral("Test_Right"));
1537     QCOMPARE(cg2.readEntry("foo"), QStringLiteral("5"));
1538     QCOMPARE(cg2.readEntry("foo", 3), 5);
1539     QCOMPARE(cg2.readEntry("foostring"), QStringLiteral("nice"));
1540     QCOMPARE(cg2.readEntry("foostring", "ugly"), QStringLiteral("nice"));
1541     QCOMPARE(cg2.readEntry("foobool"), QStringLiteral("true"));
1542     QCOMPARE(cg2.readEntry("foobool", false), true);
1543 
1544     // Clean up after the testcase
1545     QFile::remove(file);
1546 }
1547 
1548 void KConfigTest::testDeleteWhenLocalized()
1549 {
1550     // Initialize the testdata
1551     const QString subdir = QDir::home().canonicalPath() + QLatin1String("/.kde-unit-test/");
1552     QDir().mkpath(subdir);
1553     const QString file = subdir + QLatin1String("/localized_delete.test");
1554     QFile::remove(file);
1555     QFile f(file);
1556     QVERIFY(f.open(QIODevice::WriteOnly));
1557     QTextStream ts(&f);
1558     ts << "[Test4711]\n";
1559     ts << "foo=3\n";
1560     ts << "foo[ca]=5\n";
1561     ts << "foo[de]=7\n";
1562     ts << "foostring=ugly\n";
1563     ts << "foostring[ca]=nice\n";
1564     ts << "foostring[de]=schoen\n";
1565     ts << "foobool=false\n";
1566     ts << "foobool[ca]=true\n";
1567     ts << "foobool[de]=true\n";
1568     f.close();
1569 
1570     // Load the testdata. We start in locale "ca".
1571     QVERIFY(QFile::exists(file));
1572     KConfig config(file);
1573     config.setLocale(QStringLiteral("ca"));
1574     KConfigGroup cg(&config, QStringLiteral("Test4711"));
1575 
1576     // Delete a value. Once with localized, once with Normal
1577     cg.deleteEntry("foostring", KConfigBase::Persistent | KConfigBase::Localized);
1578     cg.deleteEntry("foobool");
1579     QVERIFY(config.sync());
1580 
1581     // The value is now gone. The others are still there. Everything correct
1582     // here.
1583     QVERIFY(!cg.hasKey("foostring"));
1584     QVERIFY(!cg.hasKey("foobool"));
1585     QVERIFY(cg.hasKey("foo"));
1586 
1587     // The current state is: (Just return before this comment.)
1588     // [...]
1589     // foobool[ca]=true
1590     // foobool[de]=wahr
1591     // foostring=ugly
1592     // foostring[de]=schoen
1593 
1594     // Now switch the locale to "de" and repeat the checks. Results should be
1595     // the same. But they currently are not. The localized value are
1596     // independent of each other. All values are still there in "de".
1597     config.setLocale(QStringLiteral("de"));
1598     QEXPECT_FAIL("", "Currently localized values are not deleted correctly", Continue);
1599     QVERIFY(!cg.hasKey("foostring"));
1600     QEXPECT_FAIL("", "Currently localized values are not deleted correctly", Continue);
1601     QVERIFY(!cg.hasKey("foobool"));
1602     QVERIFY(cg.hasKey("foo"));
1603     // Check where the wrong values come from.
1604     // We get the "de" value.
1605     QCOMPARE(cg.readEntry("foostring", "nothing"), QStringLiteral("schoen"));
1606     // We get the "de" value.
1607     QCOMPARE(cg.readEntry("foobool", false), true);
1608 
1609     // Now switch the locale back "ca" and repeat the checks. Results are
1610     // again different.
1611     config.setLocale(QStringLiteral("ca"));
1612     // This line worked above. But now it fails.
1613     QEXPECT_FAIL("", "Currently localized values are not deleted correctly", Continue);
1614     QVERIFY(!cg.hasKey("foostring"));
1615     // This line worked above too.
1616     QEXPECT_FAIL("", "Currently localized values are not deleted correctly", Continue);
1617     QVERIFY(!cg.hasKey("foobool"));
1618     QVERIFY(cg.hasKey("foo"));
1619     // Check where the wrong values come from.
1620     // We get the primary value because the "ca" value was deleted.
1621     QCOMPARE(cg.readEntry("foostring", "nothing"), QStringLiteral("ugly"));
1622     // We get the "ca" value.
1623     QCOMPARE(cg.readEntry("foobool", false), true);
1624 
1625     // Now test the deletion of a group.
1626     cg.deleteGroup();
1627     QVERIFY(config.sync());
1628 
1629     // Current state: [ca] and [de] entries left... oops.
1630     // qDebug() << readLinesFrom(file);
1631 
1632     // Bug: The group still exists [because of the localized entries]...
1633     QVERIFY(cg.exists());
1634     QVERIFY(!cg.hasKey("foo"));
1635     QVERIFY(!cg.hasKey("foostring"));
1636     QEXPECT_FAIL("", "Currently localized values are not deleted correctly", Continue);
1637     QVERIFY(!cg.hasKey("foobool"));
1638 
1639     // Now switch the locale to "de" and repeat the checks. All values
1640     // still here because only the primary values are deleted.
1641     config.setLocale(QStringLiteral("de"));
1642     QEXPECT_FAIL("", "Currently localized values are not deleted correctly", Continue);
1643     QVERIFY(!cg.hasKey("foo"));
1644     QEXPECT_FAIL("", "Currently localized values are not deleted correctly", Continue);
1645     QVERIFY(!cg.hasKey("foostring"));
1646     QEXPECT_FAIL("", "Currently localized values are not deleted correctly", Continue);
1647     QVERIFY(!cg.hasKey("foobool"));
1648     // Check where the wrong values come from.
1649     // We get the "de" value.
1650     QCOMPARE(cg.readEntry("foostring", "nothing"), QStringLiteral("schoen"));
1651     // We get the "de" value.
1652     QCOMPARE(cg.readEntry("foobool", false), true);
1653     // We get the "de" value.
1654     QCOMPARE(cg.readEntry("foo", 0), 7);
1655 
1656     // Now switch the locale to "ca" and repeat the checks
1657     // "foostring" is now really gone because both the primary value and the
1658     // "ca" value are deleted.
1659     config.setLocale(QStringLiteral("ca"));
1660     QEXPECT_FAIL("", "Currently localized values are not deleted correctly", Continue);
1661     QVERIFY(!cg.hasKey("foo"));
1662     QVERIFY(!cg.hasKey("foostring"));
1663     QEXPECT_FAIL("", "Currently localized values are not deleted correctly", Continue);
1664     QVERIFY(!cg.hasKey("foobool"));
1665     // Check where the wrong values come from.
1666     // We get the "ca" value.
1667     QCOMPARE(cg.readEntry("foobool", false), true);
1668     // We get the "ca" value.
1669     QCOMPARE(cg.readEntry("foo", 0), 5);
1670 
1671     // Cleanup
1672     QFile::remove(file);
1673 }
1674 
1675 void KConfigTest::testKdeGlobals()
1676 {
1677     {
1678         KConfig glob(QStringLiteral("kdeglobals"));
1679         KConfigGroup general(&glob, QStringLiteral("General"));
1680         general.writeEntry("testKG", "1");
1681         QVERIFY(glob.sync());
1682     }
1683 
1684     KConfig globRead(QStringLiteral("kdeglobals"));
1685     const KConfigGroup general(&globRead, QStringLiteral("General"));
1686     QCOMPARE(general.readEntry("testKG"), QStringLiteral("1"));
1687 
1688     // Check we wrote into kdeglobals
1689     const QList<QByteArray> lines = readLines(QStringLiteral("kdeglobals"));
1690     QVERIFY(lines.contains("[General]\n"));
1691     QVERIFY(lines.contains("testKG=1\n"));
1692 
1693     // Writing using NoGlobals
1694     {
1695         KConfig glob(QStringLiteral("kdeglobals"), KConfig::NoGlobals);
1696         KConfigGroup general(&glob, QStringLiteral("General"));
1697         general.writeEntry("testKG", "2");
1698         QVERIFY(glob.sync());
1699     }
1700     globRead.reparseConfiguration();
1701     QCOMPARE(general.readEntry("testKG"), QStringLiteral("2"));
1702 
1703     // Reading using NoGlobals
1704     {
1705         KConfig globReadNoGlob(QStringLiteral("kdeglobals"), KConfig::NoGlobals);
1706         const KConfigGroup generalNoGlob(&globReadNoGlob, QStringLiteral("General"));
1707         QCOMPARE(generalNoGlob.readEntry("testKG"), QStringLiteral("2"));
1708     }
1709 }
1710 
1711 void KConfigTest::testLocalDeletion()
1712 {
1713     // Prepare kdeglobals
1714     {
1715         KConfig glob(QStringLiteral("kdeglobals"));
1716         KConfigGroup general(&glob, QStringLiteral("OwnTestGroup"));
1717         general.writeEntry("GlobalKey", "DontTouchMe");
1718         QVERIFY(glob.sync());
1719     }
1720 
1721     QStringList expectedKeys{QStringLiteral("LocalKey")};
1722     expectedKeys.prepend(QStringLiteral("GlobalWrite"));
1723 
1724     // Write into kconfigtest, including deleting GlobalKey
1725     {
1726         KConfig mainConfig(s_kconfig_test_subdir);
1727         KConfigGroup mainGroup(&mainConfig, QStringLiteral("OwnTestGroup"));
1728         mainGroup.writeEntry("LocalKey", QStringLiteral("LocalValue"));
1729         mainGroup.writeEntry("GlobalWrite", QStringLiteral("GlobalValue"), KConfig::Persistent | KConfig::Global); // goes to kdeglobals
1730         QCOMPARE(mainGroup.readEntry("GlobalKey"), QStringLiteral("DontTouchMe"));
1731         mainGroup.deleteEntry("GlobalKey"); // local deletion ([$d]), kdeglobals is unchanged
1732         QCOMPARE(mainGroup.readEntry("GlobalKey", "Default"), QStringLiteral("Default")); // key is gone
1733         QCOMPARE(mainGroup.keyList(), expectedKeys);
1734     }
1735 
1736     // Check what ended up in kconfigtest
1737     const QList<QByteArray> lines = readLines();
1738     QVERIFY(lines.contains("[OwnTestGroup]\n"));
1739     QVERIFY(lines.contains("GlobalKey[$d]\n"));
1740 
1741     // Check what ended up in kdeglobals
1742     {
1743         KConfig globReadNoGlob(QStringLiteral("kdeglobals"), KConfig::NoGlobals);
1744         const KConfigGroup generalNoGlob(&globReadNoGlob, QStringLiteral("OwnTestGroup"));
1745         QCOMPARE(generalNoGlob.readEntry("GlobalKey"), QStringLiteral("DontTouchMe"));
1746         QCOMPARE(generalNoGlob.readEntry("GlobalWrite"), QStringLiteral("GlobalValue"));
1747         QVERIFY(!generalNoGlob.hasKey("LocalValue"));
1748         QStringList expectedGlobalKeys{QStringLiteral("GlobalKey")};
1749         expectedGlobalKeys.append(QStringLiteral("GlobalWrite"));
1750         QCOMPARE(generalNoGlob.keyList(), expectedGlobalKeys);
1751     }
1752 
1753     // Check what we see when re-reading the config file
1754     {
1755         KConfig mainConfig(s_kconfig_test_subdir);
1756         KConfigGroup mainGroup(&mainConfig, QStringLiteral("OwnTestGroup"));
1757         QTRY_COMPARE_WITH_TIMEOUT(mainGroup.readEntry("GlobalKey", "Default"), QStringLiteral("Default"), 5000); // key is gone
1758         QTRY_COMPARE_WITH_TIMEOUT(mainGroup.keyList(), expectedKeys, 5000);
1759     }
1760 }
1761 
1762 void KConfigTest::testAnonymousConfig()
1763 {
1764     KConfig anonConfig(QString(), KConfig::SimpleConfig);
1765     KConfigGroup general(&anonConfig, QStringLiteral("General"));
1766     QCOMPARE(general.readEntry("testKG"), QString()); // no kdeglobals merging
1767     general.writeEntry("Foo", "Bar");
1768     QCOMPARE(general.readEntry("Foo"), QStringLiteral("Bar"));
1769 }
1770 
1771 void KConfigTest::testQByteArrayUtf8()
1772 {
1773     QTemporaryFile file;
1774     QVERIFY(file.open());
1775     KConfig config(file.fileName(), KConfig::SimpleConfig);
1776     KConfigGroup general(&config, QStringLiteral("General"));
1777     QByteArray bytes(256, '\0');
1778     for (int i = 0; i < 256; i++) {
1779         bytes[i] = i;
1780     }
1781     general.writeEntry("Utf8", bytes);
1782     config.sync();
1783     file.flush();
1784     file.close();
1785     QFile readFile(file.fileName());
1786     QVERIFY(readFile.open(QFile::ReadOnly));
1787 #define VALUE                                                                                                                                                  \
1788     "Utf8="                                                                                                                                                    \
1789     "\\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" \
1790     "1f "                                                                                                                                                      \
1791     "!\"#$%&'()*+,-./"                                                                                                                                         \
1792     "0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"                                                                       \
1793     "\\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\\" \
1794     "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" \
1795     "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"  \
1796     "\\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\\" \
1797     "xf8\\xf9\\xfa\\xfb\\xfc\\xfd\\xfe\\xff"
1798     const QByteArray fileBytes = readFile.readAll();
1799 #ifndef Q_OS_WIN
1800     QCOMPARE(fileBytes, QByteArrayLiteral("[General]\n" VALUE "\n"));
1801 #else
1802     QCOMPARE(fileBytes, QByteArrayLiteral("[General]\r\n" VALUE "\r\n"));
1803 #endif
1804 #undef VALUE
1805 
1806     // check that reading works
1807     KConfig config2(file.fileName(), KConfig::SimpleConfig);
1808     KConfigGroup general2(&config2, QStringLiteral("General"));
1809     QCOMPARE(bytes, general2.readEntry("Utf8", QByteArray()));
1810 }
1811 
1812 void KConfigTest::testQStringUtf8_data()
1813 {
1814     QTest::addColumn<QByteArray>("data");
1815     QTest::newRow("1") << QByteArray("Téléchargements\tTéléchargements");
1816     QTest::newRow("2") << QByteArray("$¢ह€𐍈\t$¢ह€𐍈");
1817     QTest::newRow("3") << QByteArray("\xc2\xe0\xa4\xf0\x90\x8d\t\\xc2\\xe0\\xa4\\xf0\\x90\\x8d");
1818     // 2 byte overlong
1819     QTest::newRow("4") << QByteArray("\xc1\xbf\t\\xc1\\xbf");
1820     // 3 byte overlong
1821     QTest::newRow("5") << QByteArray("\xe0\x9f\xbf\t\\xe0\\x9f\\xbf");
1822     // 4 byte overlong
1823     QTest::newRow("6") << QByteArray("\xf0\x8f\xbf\xbf\t\\xf0\\x8f\\xbf\\xbf");
1824     // outside unicode range
1825     QTest::newRow("7") << QByteArray("\xf4\x90\x80\x80\t\\xf4\\x90\\x80\\x80");
1826     // just within range
1827     QTest::newRow("8") << QByteArray("\xc2\x80\t\xc2\x80");
1828     QTest::newRow("9") << QByteArray("\xe0\xa0\x80\t\xe0\xa0\x80");
1829     QTest::newRow("10") << QByteArray("\xf0\x90\x80\x80\t\xf0\x90\x80\x80");
1830     QTest::newRow("11") << QByteArray("\xf4\x8f\xbf\xbf\t\xf4\x8f\xbf\xbf");
1831 }
1832 
1833 void KConfigTest::testQStringUtf8()
1834 {
1835     QFETCH(QByteArray, data);
1836     const QList<QByteArray> d = data.split('\t');
1837     const QByteArray value = d[0];
1838     const QByteArray serialized = d[1];
1839     QTemporaryFile file;
1840     QVERIFY(file.open());
1841     KConfig config(file.fileName(), KConfig::SimpleConfig);
1842     KConfigGroup general(&config, QStringLiteral("General"));
1843     general.writeEntry("key", value);
1844     config.sync();
1845     file.flush();
1846     file.close();
1847     QFile readFile(file.fileName());
1848     QVERIFY(readFile.open(QFile::ReadOnly));
1849     QByteArray fileBytes = readFile.readAll();
1850 #ifdef Q_OS_WIN
1851     fileBytes.replace(QByteArrayLiteral("\r\n"), QByteArrayLiteral("\n"));
1852 #endif
1853     QCOMPARE(fileBytes, QByteArrayLiteral("[General]\nkey=") + serialized + QByteArrayLiteral("\n"));
1854 
1855     // check that reading works
1856     KConfig config2(file.fileName(), KConfig::SimpleConfig);
1857     KConfigGroup general2(&config2, QStringLiteral("General"));
1858     QCOMPARE(value, general2.readEntry("key", QByteArray()));
1859 }
1860 
1861 void KConfigTest::testNewlines()
1862 {
1863     // test that kconfig always uses the native line endings
1864     QTemporaryFile file;
1865     QVERIFY(file.open());
1866     KConfig anonConfig(file.fileName(), KConfig::SimpleConfig);
1867     KConfigGroup general(&anonConfig, QStringLiteral("General"));
1868     general.writeEntry("Foo", "Bar");
1869     general.writeEntry("Bar", "Foo");
1870     anonConfig.sync();
1871     file.flush();
1872     file.close();
1873     QFile readFile(file.fileName());
1874     QVERIFY(readFile.open(QFile::ReadOnly));
1875 #ifndef Q_OS_WIN
1876     QCOMPARE(readFile.readAll(), QByteArrayLiteral("[General]\nBar=Foo\nFoo=Bar\n"));
1877 #else
1878     QCOMPARE(readFile.readAll(), QByteArrayLiteral("[General]\r\nBar=Foo\r\nFoo=Bar\r\n"));
1879 #endif
1880 }
1881 
1882 void KConfigTest::testMoveValuesTo()
1883 {
1884     QTemporaryFile file;
1885     QVERIFY(file.open());
1886     // Prepare kdeglobals
1887     {
1888         KConfig glob(QStringLiteral("kdeglobals"));
1889         KConfigGroup general(&glob, QStringLiteral("TestGroup"));
1890         general.writeEntry("GlobalKey", "PlsDeleteMe");
1891         QVERIFY(glob.sync());
1892     }
1893 
1894     KConfigGroup grp = KSharedConfig::openConfig(file.fileName())->group(QStringLiteral("TestGroup"));
1895 
1896     grp.writeEntry("test1", "first_value");
1897     grp.writeEntry("test_empty", "");
1898     grp.writeEntry("other", "other_value");
1899     grp.writePathEntry("my_path", QStringLiteral("~/somepath"));
1900     // because this key is from the global file it should be explicitly deleted
1901     grp.deleteEntry("GlobalKey");
1902 
1903     QTemporaryFile targetFile;
1904     QVERIFY(targetFile.open());
1905     targetFile.close();
1906     KConfigGroup targetGroup = KSharedConfig::openConfig(targetFile.fileName(), KConfig::SimpleConfig)->group(QStringLiteral("MoveToGroup"));
1907 
1908     grp.moveValuesTo({"test1", "test_empty", "does_not_exist", "my_path", "GlobalKey"}, targetGroup);
1909     QVERIFY(grp.config()->isDirty());
1910     QVERIFY(targetGroup.config()->isDirty());
1911 
1912     QCOMPARE(grp.keyList(), QStringList{QStringLiteral("other")});
1913     QStringList expectedKeyList{QStringLiteral("my_path"), QStringLiteral("test1"), QStringLiteral("test_empty")};
1914     QCOMPARE(targetGroup.keyList(), expectedKeyList);
1915     QCOMPARE(targetGroup.readEntry("test1"), QStringLiteral("first_value"));
1916 
1917     targetGroup.sync();
1918     QFile targetReadFile(targetFile.fileName());
1919     targetReadFile.open(QFile::ReadOnly);
1920     QVERIFY(targetReadFile.readAll().contains(QByteArray("my_path[$e]=~/somepath")));
1921 }
1922 
1923 void KConfigTest::testXdgListEntry()
1924 {
1925     QTemporaryFile file;
1926     QVERIFY(file.open());
1927     QTextStream out(&file);
1928     out << "[General]\n"
1929         << "Key1=\n" // empty list
1930         // emtpty entries
1931         << "Key2=;\n"
1932         << "Key3=;;\n"
1933         << "Key4=;;;\n"
1934         << "Key5=\\;\n"
1935         << "Key6=1;2\\;3;;\n";
1936     out.flush();
1937     file.close();
1938     KConfig anonConfig(file.fileName(), KConfig::SimpleConfig);
1939     KConfigGroup grp = anonConfig.group(QStringLiteral("General"));
1940     QStringList invalidList; // use this as a default when an empty list is expected
1941     invalidList << QStringLiteral("Error! Default value read!");
1942     QCOMPARE(grp.readXdgListEntry("Key1", invalidList), (QStringList{}));
1943     QCOMPARE(grp.readXdgListEntry("Key2", invalidList), (QStringList{QString{}}));
1944     QCOMPARE(grp.readXdgListEntry("Key3", invalidList), (QStringList{QString{}, QString{}}));
1945     QCOMPARE(grp.readXdgListEntry("Key4", invalidList), (QStringList{QString{}, QString{}, QString{}}));
1946     QCOMPARE(grp.readXdgListEntry("Key5", invalidList), (QStringList{QStringLiteral(";")}));
1947     QCOMPARE(grp.readXdgListEntry("Key6", invalidList), (QStringList{QStringLiteral("1"), QStringLiteral("2;3"), QString{}}));
1948 }
1949 
1950 #include <QThreadPool>
1951 #include <qtconcurrentrun.h>
1952 
1953 // To find multithreading bugs: valgrind --tool=helgrind --track-lockorders=no ./kconfigtest testThreads
1954 void KConfigTest::testThreads()
1955 {
1956     QThreadPool::globalInstance()->setMaxThreadCount(6);
1957     // Run in parallel some tests that work on different config files,
1958     // otherwise unexpected things might indeed happen.
1959     const QList<QFuture<void>> futures = {
1960         QtConcurrent::run(&KConfigTest::testAddConfigSources, this),
1961         QtConcurrent::run(&KConfigTest::testSimple, this),
1962         QtConcurrent::run(&KConfigTest::testDefaults, this),
1963         QtConcurrent::run(&KConfigTest::testSharedConfig, this),
1964         QtConcurrent::run(&KConfigTest::testSharedConfig, this),
1965     };
1966 
1967     // QEXPECT_FAIL triggers race conditions, it should be fixed to use QThreadStorage...
1968     // futures << QtConcurrent::run(this, &KConfigTest::testDeleteWhenLocalized);
1969     // futures << QtConcurrent::run(this, &KConfigTest::testEntryMap);
1970     for (QFuture<void> f : futures) {
1971         f.waitForFinished();
1972     }
1973 }
1974 
1975 void KConfigTest::testNotify()
1976 {
1977 #if !KCONFIG_USE_DBUS
1978     QSKIP("KConfig notification requires DBus");
1979 #endif
1980 
1981     KConfig config(s_kconfig_test_subdir);
1982     auto myConfigGroup = KConfigGroup(&config, QStringLiteral("TopLevelGroup"));
1983 
1984     // mimics a config in another process, which is watching for events
1985     auto remoteConfig = KSharedConfig::openConfig(s_kconfig_test_subdir);
1986     KConfigWatcher::Ptr watcher = KConfigWatcher::create(remoteConfig);
1987 
1988     // some random config that shouldn't be changing when kconfigtest changes, only on kdeglobals
1989     auto otherRemoteConfig = KSharedConfig::openConfig(s_test_subdir + QLatin1String("kconfigtest2"));
1990     KConfigWatcher::Ptr otherWatcher = KConfigWatcher::create(otherRemoteConfig);
1991 
1992     QSignalSpy watcherSpy(watcher.data(), &KConfigWatcher::configChanged);
1993     QSignalSpy otherWatcherSpy(otherWatcher.data(), &KConfigWatcher::configChanged);
1994 
1995     // write entries in a group and subgroup
1996     myConfigGroup.writeEntry("entryA", "foo", KConfig::Persistent | KConfig::Notify);
1997     auto subGroup = myConfigGroup.group(QStringLiteral("aSubGroup"));
1998     subGroup.writeEntry("entry1", "foo", KConfig::Persistent | KConfig::Notify);
1999     subGroup.writeEntry("entry2", "foo", KConfig::Persistent | KConfig::Notify);
2000     config.sync();
2001     watcherSpy.wait();
2002     QCOMPARE(watcherSpy.count(), 2);
2003 
2004     std::sort(watcherSpy.begin(), watcherSpy.end(), [](QList<QVariant> a, QList<QVariant> b) {
2005         return a[0].value<KConfigGroup>().name() < b[0].value<KConfigGroup>().name();
2006     });
2007 
2008     QCOMPARE(watcherSpy[0][0].value<KConfigGroup>().name(), QStringLiteral("TopLevelGroup"));
2009     QCOMPARE(watcherSpy[0][1].value<QByteArrayList>(), QByteArrayList({"entryA"}));
2010 
2011     QCOMPARE(watcherSpy[1][0].value<KConfigGroup>().name(), QStringLiteral("aSubGroup"));
2012     QCOMPARE(watcherSpy[1][0].value<KConfigGroup>().parent().name(), QStringLiteral("TopLevelGroup"));
2013     QCOMPARE(watcherSpy[1][1].value<QByteArrayList>(), QByteArrayList({"entry1", "entry2"}));
2014 
2015     // delete an entry
2016     watcherSpy.clear();
2017     myConfigGroup.deleteEntry("entryA", KConfig::Persistent | KConfig::Notify);
2018     config.sync();
2019     watcherSpy.wait();
2020     QCOMPARE(watcherSpy.count(), 1);
2021     QCOMPARE(watcherSpy[0][0].value<KConfigGroup>().name(), QStringLiteral("TopLevelGroup"));
2022     QCOMPARE(watcherSpy[0][1].value<QByteArrayList>(), QByteArrayList({"entryA"}));
2023 
2024     // revert to default an entry
2025     watcherSpy.clear();
2026     myConfigGroup.revertToDefault("entryA", KConfig::Persistent | KConfig::Notify);
2027     config.sync();
2028     watcherSpy.wait();
2029     QCOMPARE(watcherSpy.count(), 1);
2030     QCOMPARE(watcherSpy[0][0].value<KConfigGroup>().name(), QStringLiteral("TopLevelGroup"));
2031     QCOMPARE(watcherSpy[0][1].value<QByteArrayList>(), QByteArrayList({"entryA"}));
2032 
2033     // deleting a group, should notify that every entry in that group has changed
2034     watcherSpy.clear();
2035     myConfigGroup.deleteGroup(QStringLiteral("aSubGroup"), KConfig::Persistent | KConfig::Notify);
2036     config.sync();
2037     watcherSpy.wait();
2038     QCOMPARE(watcherSpy.count(), 1);
2039     QCOMPARE(watcherSpy[0][0].value<KConfigGroup>().name(), QStringLiteral("aSubGroup"));
2040     QCOMPARE(watcherSpy[0][1].value<QByteArrayList>(), QByteArrayList({"entry1", "entry2"}));
2041 
2042     // global write still triggers our notification
2043     watcherSpy.clear();
2044     myConfigGroup.writeEntry("someGlobalEntry", "foo", KConfig::Persistent | KConfig::Notify | KConfig::Global);
2045     config.sync();
2046     watcherSpy.wait();
2047     QCOMPARE(watcherSpy.count(), 1);
2048     QCOMPARE(watcherSpy[0][0].value<KConfigGroup>().name(), QStringLiteral("TopLevelGroup"));
2049     QCOMPARE(watcherSpy[0][1].value<QByteArrayList>(), QByteArrayList({"someGlobalEntry"}));
2050 
2051     // watching another file should have only triggered from the kdeglobals change
2052     QCOMPARE(otherWatcherSpy.count(), 1);
2053     QCOMPARE(otherWatcherSpy[0][0].value<KConfigGroup>().name(), QStringLiteral("TopLevelGroup"));
2054     QCOMPARE(otherWatcherSpy[0][1].value<QByteArrayList>(), QByteArrayList({"someGlobalEntry"}));
2055 }
2056 
2057 void KConfigTest::testNotifyIllegalObjectPath()
2058 {
2059 #if !KCONFIG_USE_DBUS
2060     QSKIP("KConfig notification requires DBus");
2061 #endif
2062 
2063     KConfig config(s_kconfig_test_illegal_object_path);
2064     auto myConfigGroup = KConfigGroup(&config, QStringLiteral("General"));
2065 
2066     // mimics a config in another process, which is watching for events
2067     auto remoteConfig = KSharedConfig::openConfig(s_kconfig_test_illegal_object_path);
2068     KConfigWatcher::Ptr watcher = KConfigWatcher::create(remoteConfig);
2069 
2070     QSignalSpy watcherSpy(watcher.data(), &KConfigWatcher::configChanged);
2071 
2072     // write entries in a group and subgroup
2073     myConfigGroup.writeEntry("entryA", "foo", KConfig::Persistent | KConfig::Notify);
2074     config.sync();
2075     watcherSpy.wait();
2076     QCOMPARE(watcherSpy.size(), 1);
2077 }
2078 
2079 void KConfigTest::testKAuthorizeEnums()
2080 {
2081     KSharedConfig::Ptr config = KSharedConfig::openConfig();
2082     KConfigGroup actionRestrictions = config->group(QStringLiteral("KDE Action Restrictions"));
2083     actionRestrictions.writeEntry("shell_access", false);
2084     actionRestrictions.writeEntry("action/open_with", false);
2085 
2086     QVERIFY(!KAuthorized::authorize(KAuthorized::SHELL_ACCESS));
2087     QVERIFY(!KAuthorized::authorizeAction(KAuthorized::OPEN_WITH));
2088     actionRestrictions.deleteGroup();
2089 
2090     QVERIFY(!KAuthorized::authorize((KAuthorized::GenericRestriction)0));
2091     QVERIFY(!KAuthorized::authorizeAction((KAuthorized::GenericAction)0));
2092 }
2093 
2094 void KConfigTest::testKdeglobalsVsDefault()
2095 {
2096     // Add testRestore key with global value in kdeglobals
2097     KConfig glob(QStringLiteral("kdeglobals"));
2098     KConfigGroup generalGlob(&glob, QStringLiteral("General"));
2099     generalGlob.writeEntry("testRestore", "global");
2100     QVERIFY(glob.sync());
2101 
2102     KConfig local(s_test_subdir + QLatin1String("restorerc"));
2103     KConfigGroup generalLocal(&local, QStringLiteral("General"));
2104     // Check if we get global and not the default value from cpp (defaultcpp) when reading data from restorerc
2105     QCOMPARE(generalLocal.readEntry("testRestore", "defaultcpp"), QStringLiteral("global"));
2106 
2107     // Add test restore key with restore value in restorerc file
2108     generalLocal.writeEntry("testRestore", "restore");
2109     QVERIFY(local.sync());
2110     local.reparseConfiguration();
2111     // We expect to get the value from restorerc file
2112     QCOMPARE(generalLocal.readEntry("testRestore", "defaultcpp"), QStringLiteral("restore"));
2113 
2114     // Revert to default testRestore key and we expect to get default value and not the global one
2115     generalLocal.revertToDefault("testRestore");
2116     local.sync();
2117     local.reparseConfiguration();
2118     QCOMPARE(generalLocal.readEntry("testRestore", "defaultcpp"), QStringLiteral("defaultcpp"));
2119 }
2120 
2121 #include "moc_kconfigtest.cpp"