File indexing completed on 2024-04-28 15:19:32
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("Main").writeEntry("Key", "Value"); 0118 mainConfig->sync(); 0119 0120 KConfig sc(s_kconfig_test_subdir); 0121 0122 KConfigGroup cg(&sc, "AAA"); // deleted later by testDelete 0123 cg.writeEntry("stringEntry1", s_string_entry1, KConfig::Persistent | KConfig::Global); 0124 0125 cg = KConfigGroup(&sc, "GlobalGroup"); 0126 cg.writeEntry("globalEntry", s_string_entry1, KConfig::Persistent | KConfig::Global); 0127 cg.deleteEntry("globalEntry2", KConfig::Global); 0128 0129 cg = KConfigGroup(&sc, "LocalGroupToBeDeleted"); // deleted later by testDelete 0130 cg.writeEntry("stringEntry1", s_string_entry1); 0131 0132 cg = KConfigGroup(&sc, "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("deleteMe"); // deleting a nonexistent group 0157 0158 cg = KConfigGroup(&sc, "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, "Nested Group 1"); 0168 cg.writeEntry("stringentry1", s_string_entry1); 0169 0170 cg = KConfigGroup(&ct, "Nested Group 2"); 0171 cg.writeEntry("stringEntry2", s_string_entry2); 0172 0173 cg = KConfigGroup(&cg, "Nested Group 2.1"); 0174 cg.writeEntry("stringEntry3", s_string_entry3); 0175 0176 cg = KConfigGroup(&ct, "Nested Group 3"); 0177 cg.writeEntry("stringEntry3", s_string_entry3); 0178 0179 cg = KConfigGroup(&sc, "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, "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, "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, "ParentGroup"); 0208 KConfigGroup cg1(&cg, "SubGroup1"); 0209 cg1.writeEntry("somestring", "somevalue"); 0210 cg.writeEntry("parentgrpstring", "somevalue"); 0211 KConfigGroup cg2(&cg, "SubGroup2"); 0212 cg2.writeEntry("substring", "somevalue"); 0213 KConfigGroup cg3(&cg, "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, "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, "Specific Only Group"); 0233 devonlygrp.writeEntry("ExistingEntry", "DevValue"); 0234 KConfigGroup devandbasegrp(&devcfg, "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, "Base Only Group"); 0240 basegrp.writeEntry("ExistingEntry", "BaseValue"); 0241 KConfigGroup baseanddevgrp(&basecfg, "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 QWARN(qPrintable(QLatin1String("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, "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, "Hello"); 0321 cg.writeEntry("Test", "Correct"); 0322 } 0323 0324 { 0325 KConfig sc(s_test_subdir + QLatin1String("konfigtest2"), KConfig::SimpleConfig); 0326 KConfigGroup cg(&sc, "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, "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, "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, "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("any group"); 0400 group.writeEntry("entry1", Default); 0401 QVERIFY(group.sync()); 0402 0403 group = config.group("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("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("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, "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, "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 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0522 out.setCodec("UTF-8"); 0523 #endif 0524 out << "[Test Group]\n" 0525 << "homePath=$HOME/foo\n" 0526 << "homePath2=file://$HOME/foo\n" 0527 << "withSlash=$WITHSLASH/foo\n" 0528 << "withSlash2=$WITHSLASH\n" 0529 << "withBraces[$e]=file://${HOME}/foo\n" 0530 << "URL[$e]=file://${HOME}/foo\n" 0531 << "hostname[$e]=$(hostname)\n" 0532 << "escapes=aaa,bb/b,ccc\\,ccc\n" 0533 << "noeol=foo" // no EOL 0534 ; 0535 } 0536 KConfig cf2(s_test_subdir + QLatin1String("pathtest")); 0537 KConfigGroup group = cf2.group("Test Group"); 0538 QVERIFY(group.hasKey("homePath")); 0539 QCOMPARE(group.readPathEntry("homePath", QString{}), s_homepath); 0540 QVERIFY(group.hasKey("homePath2")); 0541 QCOMPARE(group.readPathEntry("homePath2", QString{}), QLatin1String("file://") + s_homepath); 0542 QVERIFY(group.hasKey("withSlash")); 0543 QCOMPARE(group.readPathEntry("withSlash", QString{}), QStringLiteral("/a//foo")); 0544 QVERIFY(group.hasKey("withSlash2")); 0545 QCOMPARE(group.readPathEntry("withSlash2", QString{}), QStringLiteral("/a/")); 0546 QVERIFY(group.hasKey("withBraces")); 0547 QCOMPARE(group.readPathEntry("withBraces", QString{}), QLatin1String("file://") + s_homepath); 0548 QVERIFY(group.hasKey("URL")); 0549 QCOMPARE(group.readEntry("URL", QString{}), QLatin1String("file://") + s_homepath); 0550 QVERIFY(group.hasKey("hostname")); 0551 QCOMPARE(group.readEntry("hostname", QString{}), QStringLiteral("(hostname)")); // the $ got removed because empty var name 0552 QVERIFY(group.hasKey("noeol")); 0553 QCOMPARE(group.readEntry("noeol", QString{}), QStringLiteral("foo")); 0554 0555 const auto val = QStringList{QStringLiteral("aaa"), QStringLiteral("bb/b"), QStringLiteral("ccc,ccc")}; 0556 QCOMPARE(group.readPathEntry(QStringLiteral("escapes"), QStringList()), val); 0557 } 0558 0559 void KConfigTest::testPersistenceOfExpandFlagForPath() 0560 { 0561 // This test checks that a path entry starting with $HOME is still flagged 0562 // with the expand flag after the config was altered without rewriting the 0563 // path entry. 0564 0565 // 1st step: Open the config, add a new dummy entry and then sync the config 0566 // back to the storage. 0567 { 0568 KConfig sc2(s_kconfig_test_subdir); 0569 KConfigGroup sc3(&sc2, "Path Type"); 0570 sc3.writeEntry("dummy", "dummy"); 0571 QVERIFY(sc2.sync()); 0572 } 0573 0574 // 2nd step: Call testPath() again. Rewriting the config must not break 0575 // the testPath() test. 0576 testPath(); 0577 } 0578 0579 void KConfigTest::testPathQtHome() 0580 { 0581 { 0582 QFile file(m_testConfigDir + QLatin1String("/pathtest")); 0583 file.open(QIODevice::WriteOnly | QIODevice::Text); 0584 QTextStream out(&file); 0585 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0586 out.setCodec("UTF-8"); 0587 #endif 0588 out << "[Test Group]\n" 0589 << "dataDir[$e]=$QT_DATA_HOME/kconfigtest\n" 0590 << "cacheDir[$e]=$QT_CACHE_HOME/kconfigtest\n" 0591 << "configDir[$e]=$QT_CONFIG_HOME/kconfigtest\n"; 0592 } 0593 KConfig cf2(s_test_subdir + QLatin1String("pathtest")); 0594 KConfigGroup group = cf2.group("Test Group"); 0595 qunsetenv("QT_DATA_HOME"); 0596 qunsetenv("QT_CACHE_HOME"); 0597 qunsetenv("QT_CONFIG_HOME"); 0598 QVERIFY(group.hasKey("dataDir")); 0599 QCOMPARE(group.readEntry("dataDir", QString{}), 0600 QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation).append(QStringLiteral("/kconfigtest"))); 0601 QVERIFY(group.hasKey("cacheDir")); 0602 QCOMPARE(group.readEntry("cacheDir", QString{}), 0603 QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation).append(QStringLiteral("/kconfigtest"))); 0604 QVERIFY(group.hasKey("configDir")); 0605 QCOMPARE(group.readEntry("configDir", QString{}), 0606 QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation).append(QStringLiteral("/kconfigtest"))); 0607 qputenv("QT_DATA_HOME", "/1"); 0608 qputenv("QT_CACHE_HOME", "/2"); 0609 qputenv("QT_CONFIG_HOME", "/3"); 0610 QVERIFY(group.hasKey("dataDir")); 0611 QCOMPARE(group.readEntry("dataDir", QString{}), QStringLiteral("/1/kconfigtest")); 0612 QVERIFY(group.hasKey("cacheDir")); 0613 QCOMPARE(group.readEntry("cacheDir", QString{}), QStringLiteral("/2/kconfigtest")); 0614 QVERIFY(group.hasKey("configDir")); 0615 QCOMPARE(group.readEntry("configDir", QString{}), QStringLiteral("/3/kconfigtest")); 0616 } 0617 0618 void KConfigTest::testComplex() 0619 { 0620 KConfig sc2(s_kconfig_test_subdir); 0621 KConfigGroup sc3(&sc2, "Complex Types"); 0622 0623 QCOMPARE(sc3.readEntry("pointEntry", QPoint()), s_point_entry); 0624 QCOMPARE(sc3.readEntry("sizeEntry", s_size_entry), s_size_entry); 0625 QCOMPARE(sc3.readEntry("rectEntry", QRect(1, 2, 3, 4)), s_rect_entry); 0626 QCOMPARE(sc3.readEntry("dateTimeEntry", QDateTime()).toString(Qt::ISODateWithMs), s_date_time_entry.toString(Qt::ISODateWithMs)); 0627 QCOMPARE(sc3.readEntry("dateEntry", QDate()).toString(Qt::ISODate), s_date_time_entry.date().toString(Qt::ISODate)); 0628 QCOMPARE(sc3.readEntry("dateTimeWithMSEntry", QDateTime()).toString(Qt::ISODateWithMs), s_date_time_with_ms_entry.toString(Qt::ISODateWithMs)); 0629 QCOMPARE(sc3.readEntry("dateTimeEntry", QDate()), s_date_time_entry.date()); 0630 } 0631 0632 void KConfigTest::testEnums() 0633 { 0634 // Visual C++ 2010 (compiler version 16.0) throws an Internal Compiler Error 0635 // when compiling the code in initTestCase that creates these KConfig entries, 0636 // so we can't run this test 0637 #if defined(_MSC_VER) && _MSC_VER == 1600 0638 QSKIP("Visual C++ 2010 can't compile this test"); 0639 #endif 0640 KConfig sc(s_kconfig_test_subdir); 0641 KConfigGroup sc3(&sc, "Enum Types"); 0642 0643 QCOMPARE(sc3.readEntry("enum-10"), QStringLiteral("Tens")); 0644 QVERIFY(sc3.readEntry("enum-100", Ones) != Ones); 0645 QVERIFY(sc3.readEntry("enum-100", Ones) != Tens); 0646 0647 QCOMPARE(sc3.readEntry("flags-bit0"), QStringLiteral("bit0")); 0648 QVERIFY(sc3.readEntry("flags-bit0", Flags()) == bit0); 0649 0650 int eid = staticMetaObject.indexOfEnumerator("Flags"); 0651 QVERIFY(eid != -1); 0652 QMetaEnum me = staticMetaObject.enumerator(eid); 0653 Flags bitfield = bit0 | bit1; 0654 0655 QCOMPARE(sc3.readEntry("flags-bit0-bit1"), QString::fromLatin1(me.valueToKeys(bitfield))); 0656 QVERIFY(sc3.readEntry("flags-bit0-bit1", Flags()) == bitfield); 0657 } 0658 0659 void KConfigTest::testEntryMap() 0660 { 0661 KConfig sc(s_kconfig_test_subdir); 0662 KConfigGroup cg(&sc, "Hello"); 0663 QMap<QString, QString> entryMap = cg.entryMap(); 0664 qDebug() << entryMap.keys(); 0665 QCOMPARE(entryMap.value(QStringLiteral("stringEntry1")), s_string_entry1); 0666 QCOMPARE(entryMap.value(QStringLiteral("stringEntry2")), s_string_entry2); 0667 QCOMPARE(entryMap.value(QStringLiteral("stringEntry3")), s_string_entry3); 0668 QCOMPARE(entryMap.value(QStringLiteral("stringEntry4")), s_string_entry4); 0669 QVERIFY(!entryMap.contains(QStringLiteral("stringEntry5"))); 0670 QVERIFY(!entryMap.contains(QStringLiteral("stringEntry6"))); 0671 QCOMPARE(entryMap.value(QStringLiteral("Test")), QString::fromUtf8(s_utf8bit_entry)); 0672 QCOMPARE(entryMap.value(QStringLiteral("bytearrayEntry")), QString::fromUtf8(s_bytearray_entry.constData())); 0673 QCOMPARE(entryMap.value(QStringLiteral("emptyEntry")), QString{}); 0674 QVERIFY(entryMap.contains(QStringLiteral("emptyEntry"))); 0675 QCOMPARE(entryMap.value(QStringLiteral("boolEntry1")), s_bool_entry1 ? QStringLiteral("true") : QStringLiteral("false")); 0676 QCOMPARE(entryMap.value(QStringLiteral("boolEntry2")), s_bool_entry2 ? QStringLiteral("true") : QStringLiteral("false")); 0677 QCOMPARE(entryMap.value(QStringLiteral("keywith=equalsign")), s_string_entry1); 0678 QCOMPARE(entryMap.value(QStringLiteral("byteArrayEntry1")), s_string_entry1); 0679 QCOMPARE(entryMap.value(QStringLiteral("doubleEntry1")).toDouble(), s_double_entry); 0680 QCOMPARE(entryMap.value(QStringLiteral("floatEntry1")).toFloat(), s_float_entry); 0681 } 0682 0683 void KConfigTest::testInvalid() 0684 { 0685 KConfig sc(s_kconfig_test_subdir); 0686 0687 // all of these should print a message to the kdebug.dbg file 0688 KConfigGroup sc3(&sc, "Invalid Types"); 0689 sc3.writeEntry("badList", s_variantlist_entry2); 0690 0691 QList<int> list; 0692 0693 // 1 element list 0694 list << 1; 0695 sc3.writeEntry(QStringLiteral("badList"), list); 0696 0697 QVERIFY(sc3.readEntry("badList", QPoint()) == QPoint()); 0698 QVERIFY(sc3.readEntry("badList", QRect()) == QRect()); 0699 QVERIFY(sc3.readEntry("badList", QSize()) == QSize()); 0700 QVERIFY(sc3.readEntry("badList", QDate()) == QDate()); 0701 QVERIFY(sc3.readEntry("badList", QDateTime()) == QDateTime()); 0702 0703 // 2 element list 0704 list << 2; 0705 sc3.writeEntry("badList", list); 0706 0707 QVERIFY(sc3.readEntry("badList", QRect()) == QRect()); 0708 QVERIFY(sc3.readEntry("badList", QDate()) == QDate()); 0709 QVERIFY(sc3.readEntry("badList", QDateTime()) == QDateTime()); 0710 0711 // 3 element list 0712 list << 303; 0713 sc3.writeEntry("badList", list); 0714 0715 QVERIFY(sc3.readEntry("badList", QPoint()) == QPoint()); 0716 QVERIFY(sc3.readEntry("badList", QRect()) == QRect()); 0717 QVERIFY(sc3.readEntry("badList", QSize()) == QSize()); 0718 QVERIFY(sc3.readEntry("badList", QDate()) == QDate()); // out of bounds 0719 QVERIFY(sc3.readEntry("badList", QDateTime()) == QDateTime()); 0720 0721 // 4 element list 0722 list << 4; 0723 sc3.writeEntry("badList", list); 0724 0725 QVERIFY(sc3.readEntry("badList", QPoint()) == QPoint()); 0726 QVERIFY(sc3.readEntry("badList", QSize()) == QSize()); 0727 QVERIFY(sc3.readEntry("badList", QDate()) == QDate()); 0728 QVERIFY(sc3.readEntry("badList", QDateTime()) == QDateTime()); 0729 0730 // 5 element list 0731 list[2] = 3; 0732 list << 5; 0733 sc3.writeEntry("badList", list); 0734 0735 QVERIFY(sc3.readEntry("badList", QPoint()) == QPoint()); 0736 QVERIFY(sc3.readEntry("badList", QRect()) == QRect()); 0737 QVERIFY(sc3.readEntry("badList", QSize()) == QSize()); 0738 QVERIFY(sc3.readEntry("badList", QDate()) == QDate()); 0739 QVERIFY(sc3.readEntry("badList", QDateTime()) == QDateTime()); 0740 0741 // 6 element list 0742 list << 6; 0743 sc3.writeEntry("badList", list); 0744 0745 QVERIFY(sc3.readEntry("badList", QPoint()) == QPoint()); 0746 QVERIFY(sc3.readEntry("badList", QRect()) == QRect()); 0747 QVERIFY(sc3.readEntry("badList", QSize()) == QSize()); 0748 } 0749 0750 void KConfigTest::testChangeGroup() 0751 { 0752 KConfig sc(s_kconfig_test_subdir); 0753 KConfigGroup sc3(&sc, "Hello"); 0754 QCOMPARE(sc3.name(), QStringLiteral("Hello")); 0755 KConfigGroup newGroup(sc3); 0756 #if KCONFIGCORE_ENABLE_DEPRECATED_SINCE(5, 0) 0757 newGroup.changeGroup("FooBar"); // deprecated! 0758 QCOMPARE(newGroup.name(), QStringLiteral("FooBar")); 0759 QCOMPARE(sc3.name(), QStringLiteral("Hello")); // unchanged 0760 0761 // Write into the "changed group" and check that it works 0762 newGroup.writeEntry("InFooBar", "FB"); 0763 QCOMPARE(KConfigGroup(&sc, "FooBar").entryMap().value(QStringLiteral("InFooBar")), QStringLiteral("FB")); 0764 QCOMPARE(KConfigGroup(&sc, "Hello").entryMap().value(QStringLiteral("InFooBar")), QString{}); 0765 #endif 0766 0767 KConfigGroup rootGroup(sc.group("")); 0768 QCOMPARE(rootGroup.name(), QStringLiteral("<default>")); 0769 KConfigGroup sc32(rootGroup.group("Hello")); 0770 QCOMPARE(sc32.name(), QStringLiteral("Hello")); 0771 KConfigGroup newGroup2(sc32); 0772 #if KCONFIGCORE_ENABLE_DEPRECATED_SINCE(5, 0) 0773 newGroup2.changeGroup("FooBar"); // deprecated! 0774 QCOMPARE(newGroup2.name(), QStringLiteral("FooBar")); 0775 QCOMPARE(sc32.name(), QStringLiteral("Hello")); // unchanged 0776 #endif 0777 } 0778 0779 // Simple test for deleteEntry 0780 void KConfigTest::testDeleteEntry() 0781 { 0782 const QString configFile = s_test_subdir + QLatin1String("kconfigdeletetest"); 0783 0784 { 0785 KConfig conf(configFile); 0786 conf.group("Hello").writeEntry("DelKey", "ToBeDeleted"); 0787 } 0788 const QList<QByteArray> lines = readLines(configFile); 0789 Q_ASSERT(lines.contains("[Hello]\n")); 0790 Q_ASSERT(lines.contains("DelKey=ToBeDeleted\n")); 0791 0792 KConfig sc(configFile); 0793 KConfigGroup group(&sc, "Hello"); 0794 0795 group.deleteEntry("DelKey"); 0796 QCOMPARE(group.readEntry("DelKey", QStringLiteral("Fietsbel")), QStringLiteral("Fietsbel")); 0797 0798 QVERIFY(group.sync()); 0799 Q_ASSERT(!readLines(configFile).contains("DelKey=ToBeDeleted\n")); 0800 QCOMPARE(group.readEntry("DelKey", QStringLiteral("still deleted")), QStringLiteral("still deleted")); 0801 } 0802 0803 void KConfigTest::testDelete() 0804 { 0805 KConfig sc(s_kconfig_test_subdir); 0806 0807 KConfigGroup ct(&sc, "Complex Types"); 0808 0809 // First delete a nested group 0810 KConfigGroup delgr(&ct, "Nested Group 3"); 0811 QVERIFY(delgr.exists()); 0812 QVERIFY(ct.hasGroup("Nested Group 3")); 0813 QVERIFY(ct.groupList().contains(QStringLiteral("Nested Group 3"))); 0814 delgr.deleteGroup(); 0815 QVERIFY(!delgr.exists()); 0816 QVERIFY(!ct.hasGroup("Nested Group 3")); 0817 QVERIFY(!ct.groupList().contains(QStringLiteral("Nested Group 3"))); 0818 0819 KConfigGroup ng(&ct, "Nested Group 2"); 0820 QVERIFY(sc.hasGroup("Complex Types")); 0821 QVERIFY(sc.groupList().contains(QStringLiteral("Complex Types"))); 0822 QVERIFY(!sc.hasGroup("Does not exist")); 0823 QVERIFY(ct.hasGroup("Nested Group 1")); 0824 QVERIFY(ct.groupList().contains(QStringLiteral("Nested Group 1"))); 0825 sc.deleteGroup("Complex Types"); 0826 QCOMPARE(sc.group("Complex Types").keyList().count(), 0); 0827 QVERIFY(!sc.hasGroup("Complex Types")); // #192266 0828 QVERIFY(!sc.group("Complex Types").exists()); 0829 QVERIFY(!sc.groupList().contains(QStringLiteral("Complex Types"))); 0830 QVERIFY(!ct.hasGroup("Nested Group 1")); 0831 QVERIFY(!ct.groupList().contains(QStringLiteral("Nested Group 1"))); 0832 0833 QCOMPARE(ct.group("Nested Group 1").keyList().count(), 0); 0834 QCOMPARE(ct.group("Nested Group 2").keyList().count(), 0); 0835 QCOMPARE(ng.group("Nested Group 2.1").keyList().count(), 0); 0836 0837 KConfigGroup cg(&sc, "AAA"); 0838 cg.deleteGroup(); 0839 QVERIFY(sc.entryMap(QStringLiteral("Complex Types")).isEmpty()); 0840 QVERIFY(sc.entryMap(QStringLiteral("AAA")).isEmpty()); 0841 QVERIFY(!sc.entryMap(QStringLiteral("Hello")).isEmpty()); // not deleted group 0842 QVERIFY(sc.entryMap(QStringLiteral("FooBar")).isEmpty()); // inexistent group 0843 0844 KConfigGroup(&sc, "LocalGroupToBeDeleted").deleteGroup(); 0845 0846 QVERIFY(cg.sync()); 0847 // Check what happens on disk 0848 const QList<QByteArray> lines = readLines(); 0849 // qDebug() << lines; 0850 QVERIFY(!lines.contains("[Complex Types]\n")); 0851 QVERIFY(!lines.contains("[Complex Types][Nested Group 1]\n")); 0852 QVERIFY(!lines.contains("[Complex Types][Nested Group 2]\n")); 0853 QVERIFY(!lines.contains("[Complex Types][Nested Group 2.1]\n")); 0854 QVERIFY(!lines.contains("[LocalGroupToBeDeleted]\n")); 0855 QVERIFY(lines.contains("[AAA]\n")); // deleted from kconfigtest, but present in kdeglobals, so [$d] 0856 QVERIFY(lines.contains("[Hello]\n")); // a group that was not deleted 0857 0858 // test for entries that are marked as deleted when there is no default 0859 KConfig cf(s_kconfig_test_subdir, KConfig::SimpleConfig); // make sure there are no defaults 0860 cg = cf.group("Portable Devices"); 0861 cg.writeEntry("devices|manual|(null)", "whatever"); 0862 cg.writeEntry("devices|manual|/mnt/ipod", "/mnt/ipod"); 0863 QVERIFY(cf.sync()); 0864 0865 int count = 0; 0866 const QList<QByteArray> listLines = readLines(); 0867 for (const QByteArray &item : listLines) { 0868 if (item.startsWith("devices|")) { // krazy:exclude=strings 0869 ++count; 0870 } 0871 } 0872 QCOMPARE(count, 2); 0873 cg.deleteEntry("devices|manual|/mnt/ipod"); 0874 QVERIFY(cf.sync()); 0875 const QList<QByteArray> listLines2 = readLines(); 0876 for (const QByteArray &item : listLines2) { 0877 QVERIFY(!item.contains("ipod")); 0878 } 0879 } 0880 0881 void KConfigTest::testDefaultGroup() 0882 { 0883 KConfig sc(s_kconfig_test_subdir); 0884 KConfigGroup defaultGroup(&sc, "<default>"); 0885 QCOMPARE(defaultGroup.name(), QStringLiteral("<default>")); 0886 QVERIFY(!defaultGroup.exists()); 0887 defaultGroup.writeEntry("TestKey", "defaultGroup"); 0888 QVERIFY(defaultGroup.exists()); 0889 QCOMPARE(defaultGroup.readEntry("TestKey", QString{}), QStringLiteral("defaultGroup")); 0890 QVERIFY(sc.sync()); 0891 0892 { 0893 // Test reading it 0894 KConfig sc2(s_kconfig_test_subdir); 0895 KConfigGroup defaultGroup2(&sc2, "<default>"); 0896 QCOMPARE(defaultGroup2.name(), QStringLiteral("<default>")); 0897 QVERIFY(defaultGroup2.exists()); 0898 QCOMPARE(defaultGroup2.readEntry("TestKey", QString{}), QStringLiteral("defaultGroup")); 0899 } 0900 { 0901 // Test reading it 0902 KConfig sc2(s_kconfig_test_subdir); 0903 KConfigGroup emptyGroup(&sc2, ""); 0904 QCOMPARE(emptyGroup.name(), QStringLiteral("<default>")); 0905 QVERIFY(emptyGroup.exists()); 0906 QCOMPARE(emptyGroup.readEntry("TestKey", QString{}), QStringLiteral("defaultGroup")); 0907 } 0908 0909 QList<QByteArray> lines = readLines(); 0910 QVERIFY(!lines.contains("[]\n")); 0911 QVERIFY(!lines.isEmpty()); 0912 QCOMPARE(lines.first(), QByteArray("TestKey=defaultGroup\n")); 0913 0914 // Now that the group exists make sure it isn't returned from groupList() 0915 const QStringList groupList = sc.groupList(); 0916 for (const QString &group : groupList) { 0917 QVERIFY(!group.isEmpty() && group != QLatin1String("<default>")); 0918 } 0919 0920 defaultGroup.deleteGroup(); 0921 QVERIFY(sc.sync()); 0922 0923 // Test if deleteGroup worked 0924 lines = readLines(); 0925 QVERIFY(lines.first() != QByteArray("TestKey=defaultGroup\n")); 0926 } 0927 0928 void KConfigTest::testEmptyGroup() 0929 { 0930 KConfig sc(s_kconfig_test_subdir); 0931 KConfigGroup emptyGroup(&sc, ""); 0932 QCOMPARE(emptyGroup.name(), QStringLiteral("<default>")); // confusing, heh? 0933 QVERIFY(!emptyGroup.exists()); 0934 emptyGroup.writeEntry("TestKey", "emptyGroup"); 0935 QVERIFY(emptyGroup.exists()); 0936 QCOMPARE(emptyGroup.readEntry("TestKey", QString{}), QStringLiteral("emptyGroup")); 0937 QVERIFY(sc.sync()); 0938 0939 { 0940 // Test reading it 0941 KConfig sc2(s_kconfig_test_subdir); 0942 KConfigGroup defaultGroup(&sc2, "<default>"); 0943 QCOMPARE(defaultGroup.name(), QStringLiteral("<default>")); 0944 QVERIFY(defaultGroup.exists()); 0945 QCOMPARE(defaultGroup.readEntry("TestKey", QString{}), QStringLiteral("emptyGroup")); 0946 } 0947 { 0948 // Test reading it 0949 KConfig sc2(s_kconfig_test_subdir); 0950 KConfigGroup emptyGroup2(&sc2, ""); 0951 QCOMPARE(emptyGroup2.name(), QStringLiteral("<default>")); 0952 QVERIFY(emptyGroup2.exists()); 0953 QCOMPARE(emptyGroup2.readEntry("TestKey", QString{}), QStringLiteral("emptyGroup")); 0954 } 0955 0956 QList<QByteArray> lines = readLines(); 0957 QVERIFY(!lines.contains("[]\n")); // there's no support for the [] group, in fact. 0958 QCOMPARE(lines.first(), QByteArray("TestKey=emptyGroup\n")); 0959 0960 // Now that the group exists make sure it isn't returned from groupList() 0961 const QStringList groupList = sc.groupList(); 0962 for (const QString &group : groupList) { 0963 QVERIFY(!group.isEmpty() && group != QLatin1String("<default>")); 0964 } 0965 emptyGroup.deleteGroup(); 0966 QVERIFY(sc.sync()); 0967 0968 // Test if deleteGroup worked 0969 lines = readLines(); 0970 QVERIFY(lines.first() != QByteArray("TestKey=defaultGroup\n")); 0971 } 0972 0973 #if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) && !defined(Q_OS_BLACKBERRY) && !defined(Q_OS_ANDROID) 0974 #define Q_XDG_PLATFORM 0975 #endif 0976 0977 void KConfigTest::testCascadingWithLocale() 0978 { 0979 // This test relies on XDG_CONFIG_DIRS, which only has effect on Unix. 0980 // Cascading (more than two levels) isn't available at all on Windows. 0981 #ifdef Q_XDG_PLATFORM 0982 QTemporaryDir middleDir; 0983 QTemporaryDir globalDir; 0984 const QByteArray oldConfigDirs = qgetenv("XDG_CONFIG_DIRS"); 0985 qputenv("XDG_CONFIG_DIRS", qPrintable(middleDir.path() + QLatin1Char(':') + globalDir.path())); 0986 0987 const QString globalConfigDir = globalDir.path() + QLatin1Char('/') + s_test_subdir; 0988 QVERIFY(QDir().mkpath(globalConfigDir)); 0989 QFile global(globalConfigDir + QLatin1String("foo.desktop")); 0990 QVERIFY(global.open(QIODevice::WriteOnly | QIODevice::Text)); 0991 QTextStream globalOut(&global); 0992 globalOut << "[Group]\n" 0993 << "FromGlobal=true\n" 0994 << "FromGlobal[fr]=vrai\n" 0995 << "Name=Testing\n" 0996 << "Name[fr]=FR\n" 0997 << "Other=Global\n" 0998 << "Other[fr]=Global_FR\n"; 0999 global.close(); 1000 1001 const QString middleConfigDir = middleDir.path() + QLatin1Char('/') + s_test_subdir; 1002 QVERIFY(QDir().mkpath(middleConfigDir)); 1003 QFile local(middleConfigDir + QLatin1String("foo.desktop")); 1004 QVERIFY(local.open(QIODevice::WriteOnly | QIODevice::Text)); 1005 QTextStream out(&local); 1006 out << "[Group]\n" 1007 << "FromLocal=true\n" 1008 << "FromLocal[fr]=vrai\n" 1009 << "Name=Local Testing\n" 1010 << "Name[fr]=FR\n" 1011 << "Other=English Only\n"; 1012 local.close(); 1013 1014 KConfig config(s_test_subdir + QLatin1String("foo.desktop")); 1015 KConfigGroup group = config.group("Group"); 1016 QCOMPARE(group.readEntry("FromGlobal"), QStringLiteral("true")); 1017 QCOMPARE(group.readEntry("FromLocal"), QStringLiteral("true")); 1018 QCOMPARE(group.readEntry("Name"), QStringLiteral("Local Testing")); 1019 config.setLocale(QStringLiteral("fr")); 1020 QCOMPARE(group.readEntry("FromGlobal"), QStringLiteral("vrai")); 1021 QCOMPARE(group.readEntry("FromLocal"), QStringLiteral("vrai")); 1022 QCOMPARE(group.readEntry("Name"), QStringLiteral("FR")); 1023 QCOMPARE(group.readEntry("Other"), QStringLiteral("English Only")); // Global_FR is locally overridden 1024 qputenv("XDG_CONFIG_DIRS", oldConfigDirs); 1025 #endif 1026 } 1027 1028 void KConfigTest::testMerge() 1029 { 1030 DefaultLocale defaultLocale; 1031 QLocale::setDefault(QLocale::c()); 1032 KConfig config(s_test_subdir + QLatin1String("mergetest"), KConfig::SimpleConfig); 1033 1034 KConfigGroup cg = config.group("some group"); 1035 cg.writeEntry("entry", " random entry"); 1036 cg.writeEntry("another entry", "blah blah blah"); 1037 1038 { 1039 // simulate writing by another process 1040 QFile file(m_testConfigDir + QLatin1String("/mergetest")); 1041 file.open(QIODevice::WriteOnly | QIODevice::Text); 1042 QTextStream out(&file); 1043 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 1044 out.setCodec("UTF-8"); 1045 #endif 1046 out << "[Merged Group]\n" 1047 << "entry1=Testing\n" 1048 << "entry2=More Testing\n" 1049 << "[some group]\n" 1050 << "entry[fr]=French\n" 1051 << "entry[es]=Spanish\n" 1052 << "entry[de]=German\n"; 1053 } 1054 QVERIFY(config.sync()); 1055 1056 { 1057 QList<QByteArray> lines; 1058 // this is what the file should look like 1059 lines << "[Merged Group]\n" 1060 << "entry1=Testing\n" 1061 << "entry2=More Testing\n" 1062 << "\n" 1063 << "[some group]\n" 1064 << "another entry=blah blah blah\n" 1065 << "entry=\\srandom entry\n" 1066 << "entry[de]=German\n" 1067 << "entry[es]=Spanish\n" 1068 << "entry[fr]=French\n"; 1069 QFile file(m_testConfigDir + QLatin1String("/mergetest")); 1070 file.open(QIODevice::ReadOnly | QIODevice::Text); 1071 for (const QByteArray &line : std::as_const(lines)) { 1072 QCOMPARE(line, file.readLine()); 1073 } 1074 } 1075 } 1076 1077 void KConfigTest::testImmutable() 1078 { 1079 { 1080 QFile file(m_testConfigDir + QLatin1String("/immutabletest")); 1081 file.open(QIODevice::WriteOnly | QIODevice::Text); 1082 QTextStream out(&file); 1083 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 1084 out.setCodec("UTF-8"); 1085 #endif 1086 out << "[$i]\n" 1087 << "entry1=Testing\n" 1088 << "[group][$i]\n" 1089 << "[group][subgroup][$i]\n"; 1090 } 1091 1092 KConfig config(s_test_subdir + QLatin1String("immutabletest"), KConfig::SimpleConfig); 1093 QVERIFY(config.isGroupImmutable(QByteArray())); 1094 KConfigGroup cg = config.group(QByteArray()); 1095 QVERIFY(cg.isEntryImmutable("entry1")); 1096 KConfigGroup cg1 = config.group("group"); 1097 QVERIFY(cg1.isImmutable()); 1098 KConfigGroup cg1a = cg.group("group"); 1099 QVERIFY(cg1a.isImmutable()); 1100 KConfigGroup cg2 = cg1.group("subgroup"); 1101 QVERIFY(cg2.isImmutable()); 1102 } 1103 1104 void KConfigTest::testOptionOrder() 1105 { 1106 { 1107 QFile file(m_testConfigDir + QLatin1String("/doubleattrtest")); 1108 file.open(QIODevice::WriteOnly | QIODevice::Text); 1109 QTextStream out(&file); 1110 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 1111 out.setCodec("UTF-8"); 1112 #endif 1113 out << "[group3]\n" 1114 << "entry2=unlocalized\n" 1115 << "entry2[$i][de_DE]=t2\n"; 1116 } 1117 KConfig config(s_test_subdir + QLatin1String("doubleattrtest"), KConfig::SimpleConfig); 1118 config.setLocale(QStringLiteral("de_DE")); 1119 KConfigGroup cg3 = config.group("group3"); 1120 QVERIFY(!cg3.isImmutable()); 1121 QCOMPARE(cg3.readEntry("entry2", ""), QStringLiteral("t2")); 1122 QVERIFY(cg3.isEntryImmutable("entry2")); 1123 config.setLocale(QStringLiteral("C")); 1124 QCOMPARE(cg3.readEntry("entry2", ""), QStringLiteral("unlocalized")); 1125 QVERIFY(!cg3.isEntryImmutable("entry2")); 1126 cg3.writeEntry("entry2", "modified"); 1127 QVERIFY(config.sync()); 1128 1129 { 1130 QList<QByteArray> lines; 1131 // this is what the file should look like 1132 lines << "[group3]\n" 1133 << "entry2=modified\n" 1134 << "entry2[de_DE][$i]=t2\n"; 1135 1136 QFile file(m_testConfigDir + QLatin1String("/doubleattrtest")); 1137 file.open(QIODevice::ReadOnly | QIODevice::Text); 1138 for (const QByteArray &line : std::as_const(lines)) { 1139 QCOMPARE(line, file.readLine()); 1140 } 1141 } 1142 } 1143 1144 void KConfigTest::testGroupEscape() 1145 { 1146 KConfig config(s_test_subdir + QLatin1String("groupescapetest"), KConfig::SimpleConfig); 1147 QVERIFY(config.group(s_dollargroup).exists()); 1148 } 1149 1150 void KConfigTest::testSubGroup() 1151 { 1152 KConfig sc(s_kconfig_test_subdir); 1153 KConfigGroup cg(&sc, "ParentGroup"); 1154 QCOMPARE(cg.readEntry("parentgrpstring", ""), QStringLiteral("somevalue")); 1155 KConfigGroup subcg1(&cg, "SubGroup1"); 1156 QCOMPARE(subcg1.name(), QStringLiteral("SubGroup1")); 1157 QCOMPARE(subcg1.readEntry("somestring", ""), QStringLiteral("somevalue")); 1158 KConfigGroup subcg2(&cg, "SubGroup2"); 1159 QCOMPARE(subcg2.name(), QStringLiteral("SubGroup2")); 1160 QCOMPARE(subcg2.readEntry("substring", ""), QStringLiteral("somevalue")); 1161 KConfigGroup subcg3(&cg, "SubGroup/3"); 1162 QCOMPARE(subcg3.readEntry("sub3string", ""), QStringLiteral("somevalue")); 1163 QCOMPARE(subcg3.name(), QStringLiteral("SubGroup/3")); 1164 KConfigGroup rcg(&sc, ""); 1165 KConfigGroup srcg(&rcg, "ParentGroup"); 1166 QCOMPARE(srcg.readEntry("parentgrpstring", ""), QStringLiteral("somevalue")); 1167 1168 QStringList groupList = cg.groupList(); 1169 groupList.sort(); // comes from QSet, so order is undefined 1170 QCOMPARE(groupList, (QStringList{QStringLiteral("SubGroup/3"), QStringLiteral("SubGroup1"), QStringLiteral("SubGroup2")})); 1171 1172 const QStringList expectedSubgroup3Keys{QStringLiteral("sub3string")}; 1173 QCOMPARE(subcg3.keyList(), expectedSubgroup3Keys); 1174 const QStringList expectedParentGroupKeys{QStringLiteral("parentgrpstring")}; 1175 1176 QCOMPARE(cg.keyList(), expectedParentGroupKeys); 1177 1178 QCOMPARE(QStringList(cg.entryMap().keys()), expectedParentGroupKeys); 1179 QCOMPARE(QStringList(subcg3.entryMap().keys()), expectedSubgroup3Keys); 1180 1181 // Create A group containing only other groups. We want to make sure it 1182 // shows up in groupList of sc 1183 KConfigGroup neg(&sc, "NoEntryGroup"); 1184 KConfigGroup negsub1(&neg, "NEG Child1"); 1185 negsub1.writeEntry("entry", "somevalue"); 1186 KConfigGroup negsub2(&neg, "NEG Child2"); 1187 KConfigGroup negsub3(&neg, "NEG Child3"); 1188 KConfigGroup negsub31(&negsub3, "NEG Child3-1"); 1189 KConfigGroup negsub4(&neg, "NEG Child4"); 1190 KConfigGroup negsub41(&negsub4, "NEG Child4-1"); 1191 negsub41.writeEntry("entry", "somevalue"); 1192 1193 // A group exists if it has content 1194 QVERIFY(negsub1.exists()); 1195 1196 // But it doesn't exist if it has no content 1197 // Ossi and David say: this is how it's supposed to work. 1198 // However you could add a dummy entry for now, or we could add a "Persist" feature to kconfig groups 1199 // which would make it written out, much like "immutable" already makes them persistent. 1200 QVERIFY(!negsub2.exists()); 1201 1202 // A subgroup does not qualify as content if it is also empty 1203 QVERIFY(!negsub3.exists()); 1204 1205 // A subgroup with content is ok 1206 QVERIFY(negsub4.exists()); 1207 1208 // Only subgroups with content show up in groupList() 1209 // QEXPECT_FAIL("", "Empty subgroups do not show up in groupList()", Continue); 1210 // QCOMPARE(neg.groupList(), QStringList() << "NEG Child1" << "NEG Child2" << "NEG Child3" << "NEG Child4"); 1211 // This is what happens 1212 QStringList groups = neg.groupList(); 1213 groups.sort(); // Qt5 made the ordering unreliable, due to QHash 1214 QCOMPARE(groups, (QStringList{QStringLiteral("NEG Child1"), QStringLiteral("NEG Child4")})); 1215 1216 // make sure groupList() isn't returning something it shouldn't 1217 const QStringList listGroup = sc.groupList(); 1218 for (const QString &group : listGroup) { 1219 QVERIFY(!group.isEmpty() && group != QLatin1String("<default>")); 1220 QVERIFY(!group.contains(QChar(0x1d))); 1221 QVERIFY(!group.contains(QLatin1String("subgroup"))); 1222 QVERIFY(!group.contains(QLatin1String("SubGroup"))); 1223 } 1224 1225 QVERIFY(sc.sync()); 1226 1227 // Check that the empty groups are not written out. 1228 const QList<QByteArray> lines = readLines(); 1229 QVERIFY(lines.contains("[NoEntryGroup][NEG Child1]\n")); 1230 QVERIFY(!lines.contains("[NoEntryGroup][NEG Child2]\n")); 1231 QVERIFY(!lines.contains("[NoEntryGroup][NEG Child3]\n")); 1232 QVERIFY(!lines.contains("[NoEntryGroup][NEG Child4]\n")); // implicit group, not written out 1233 QVERIFY(lines.contains("[NoEntryGroup][NEG Child4][NEG Child4-1]\n")); 1234 } 1235 1236 void KConfigTest::testAddConfigSources() 1237 { 1238 KConfig cf(s_test_subdir + QLatin1String("specificrc")); 1239 1240 cf.addConfigSources(QStringList{m_testConfigDir + QLatin1String("/baserc")}); 1241 cf.reparseConfiguration(); 1242 1243 KConfigGroup specificgrp(&cf, "Specific Only Group"); 1244 QCOMPARE(specificgrp.readEntry("ExistingEntry", ""), QStringLiteral("DevValue")); 1245 1246 KConfigGroup sharedgrp(&cf, "Shared Group"); 1247 QCOMPARE(sharedgrp.readEntry("SomeSpecificOnlyEntry", ""), QStringLiteral("DevValue")); 1248 QCOMPARE(sharedgrp.readEntry("SomeBaseOnlyEntry", ""), QStringLiteral("BaseValue")); 1249 QCOMPARE(sharedgrp.readEntry("SomeSharedEntry", ""), QStringLiteral("DevValue")); 1250 1251 KConfigGroup basegrp(&cf, "Base Only Group"); 1252 QCOMPARE(basegrp.readEntry("ExistingEntry", ""), QStringLiteral("BaseValue")); 1253 basegrp.writeEntry("New Entry Base Only", "SomeValue"); 1254 1255 KConfigGroup newgrp(&cf, "New Group"); 1256 newgrp.writeEntry("New Entry", "SomeValue"); 1257 1258 QVERIFY(cf.sync()); 1259 1260 KConfig plaincfg(s_test_subdir + QLatin1String("specificrc")); 1261 1262 KConfigGroup newgrp2(&plaincfg, "New Group"); 1263 QCOMPARE(newgrp2.readEntry("New Entry", ""), QStringLiteral("SomeValue")); 1264 1265 KConfigGroup basegrp2(&plaincfg, "Base Only Group"); 1266 QCOMPARE(basegrp2.readEntry("New Entry Base Only", ""), QStringLiteral("SomeValue")); 1267 } 1268 1269 void KConfigTest::testGroupCopyTo() 1270 { 1271 KConfig cf1(s_kconfig_test_subdir); 1272 KConfigGroup original = cf1.group("Enum Types"); 1273 1274 KConfigGroup copy = cf1.group("Enum Types Copy"); 1275 original.copyTo(©); // copy from one group to another 1276 QCOMPARE(copy.entryMap(), original.entryMap()); 1277 1278 KConfig cf2(s_test_subdir + QLatin1String("copy_of_kconfigtest"), KConfig::SimpleConfig); 1279 QVERIFY(!cf2.hasGroup(original.name())); 1280 QVERIFY(!cf2.hasGroup(copy.name())); 1281 1282 KConfigGroup newGroup = cf2.group(original.name()); 1283 original.copyTo(&newGroup); // copy from one file to another 1284 QVERIFY(cf2.hasGroup(original.name())); 1285 QVERIFY(!cf2.hasGroup(copy.name())); // make sure we didn't copy more than we wanted 1286 QCOMPARE(newGroup.entryMap(), original.entryMap()); 1287 } 1288 1289 void KConfigTest::testConfigCopyToSync() 1290 { 1291 KConfig cf1(s_kconfig_test_subdir); 1292 // Prepare source file 1293 KConfigGroup group(&cf1, "CopyToTest"); 1294 group.writeEntry("Type", "Test"); 1295 QVERIFY(cf1.sync()); 1296 1297 // Copy to "destination" 1298 const QString destination = m_testConfigDir + QLatin1String("/kconfigcopytotest"); 1299 QFile::remove(destination); 1300 1301 KConfig cf2(s_test_subdir + QLatin1String("kconfigcopytotest")); 1302 KConfigGroup group2(&cf2, "CopyToTest"); 1303 1304 group.copyTo(&group2); 1305 1306 QString testVal = group2.readEntry("Type"); 1307 QCOMPARE(testVal, QStringLiteral("Test")); 1308 // should write to disk the copied data from group 1309 QVERIFY(cf2.sync()); 1310 QVERIFY(QFile::exists(destination)); 1311 } 1312 1313 void KConfigTest::testConfigCopyTo() 1314 { 1315 KConfig cf1(s_kconfig_test_subdir); 1316 { 1317 // Prepare source file 1318 KConfigGroup group(&cf1, "CopyToTest"); 1319 group.writeEntry("Type", "Test"); 1320 QVERIFY(cf1.sync()); 1321 } 1322 1323 { 1324 // Copy to "destination" 1325 const QString destination = m_testConfigDir + QLatin1String("/kconfigcopytotest"); 1326 QFile::remove(destination); 1327 KConfig cf2; 1328 cf1.copyTo(destination, &cf2); 1329 KConfigGroup group2(&cf2, "CopyToTest"); 1330 QString testVal = group2.readEntry("Type"); 1331 QCOMPARE(testVal, QStringLiteral("Test")); 1332 QVERIFY(cf2.sync()); 1333 QVERIFY(QFile::exists(destination)); 1334 } 1335 1336 // Check copied config file on disk 1337 KConfig cf3(s_test_subdir + QLatin1String("kconfigcopytotest")); 1338 KConfigGroup group3(&cf3, "CopyToTest"); 1339 QString testVal = group3.readEntry("Type"); 1340 QCOMPARE(testVal, QStringLiteral("Test")); 1341 } 1342 1343 void KConfigTest::testReparent() 1344 { 1345 KConfig cf(s_kconfig_test_subdir); 1346 const QString name(QStringLiteral("Enum Types")); 1347 KConfigGroup group = cf.group(name); 1348 const QMap<QString, QString> originalMap = group.entryMap(); 1349 KConfigGroup parent = cf.group("Parent Group"); 1350 1351 QVERIFY(!parent.hasGroup(name)); 1352 1353 QVERIFY(group.entryMap() == originalMap); 1354 1355 group.reparent(&parent); // see if it can be made a sub-group of another group 1356 QVERIFY(parent.hasGroup(name)); 1357 QCOMPARE(group.entryMap(), originalMap); 1358 1359 group.reparent(&cf); // see if it can make it a top-level group again 1360 // QVERIFY(!parent.hasGroup(name)); 1361 QCOMPARE(group.entryMap(), originalMap); 1362 } 1363 1364 static void ageTimeStamp(const QString &path, int nsec) 1365 { 1366 #ifdef Q_OS_UNIX 1367 QDateTime mtime = QFileInfo(path).lastModified().addSecs(-nsec); 1368 struct utimbuf utbuf; 1369 utbuf.actime = mtime.toSecsSinceEpoch(); 1370 utbuf.modtime = utbuf.actime; 1371 utime(QFile::encodeName(path).constData(), &utbuf); 1372 #else 1373 QTest::qSleep(nsec * 1000); 1374 #endif 1375 } 1376 1377 void KConfigTest::testWriteOnSync() 1378 { 1379 QDateTime oldStamp; 1380 QDateTime newStamp; 1381 KConfig sc(s_kconfig_test_subdir, KConfig::IncludeGlobals); 1382 1383 // Age the timestamp of global config file a few sec, and collect it. 1384 QString globFile = m_kdeGlobalsPath; 1385 ageTimeStamp(globFile, 2); // age 2 sec 1386 oldStamp = QFileInfo(globFile).lastModified(); 1387 1388 // Add a local entry and sync the config. 1389 // Should not rewrite the global config file. 1390 KConfigGroup cgLocal(&sc, "Locals"); 1391 cgLocal.writeEntry("someLocalString", "whatever"); 1392 QVERIFY(sc.sync()); 1393 1394 // Verify that the timestamp of global config file didn't change. 1395 newStamp = QFileInfo(globFile).lastModified(); 1396 QCOMPARE(newStamp, oldStamp); 1397 1398 // Age the timestamp of local config file a few sec, and collect it. 1399 QString locFile = m_testConfigDir + QLatin1String("/kconfigtest"); 1400 ageTimeStamp(locFile, 2); // age 2 sec 1401 oldStamp = QFileInfo(locFile).lastModified(); 1402 1403 // Add a global entry and sync the config. 1404 // Should not rewrite the local config file. 1405 KConfigGroup cgGlobal(&sc, "Globals"); 1406 cgGlobal.writeEntry("someGlobalString", "whatever", KConfig::Persistent | KConfig::Global); 1407 QVERIFY(sc.sync()); 1408 1409 // Verify that the timestamp of local config file didn't change. 1410 newStamp = QFileInfo(locFile).lastModified(); 1411 QCOMPARE(newStamp, oldStamp); 1412 } 1413 1414 void KConfigTest::testFailOnReadOnlyFileSync() 1415 { 1416 KConfig sc(s_test_subdir + QLatin1String("kconfigfailonreadonlytest")); 1417 KConfigGroup cgLocal(&sc, "Locals"); 1418 1419 cgLocal.writeEntry("someLocalString", "whatever"); 1420 QVERIFY(cgLocal.sync()); 1421 1422 QFile f(m_testConfigDir + QLatin1String("kconfigfailonreadonlytest")); 1423 QVERIFY(f.exists()); 1424 QVERIFY(f.setPermissions(QFileDevice::ReadOwner)); 1425 1426 #ifndef Q_OS_WIN 1427 if (::getuid() == 0) { 1428 QSKIP("Root can write to read-only files"); 1429 } 1430 #endif 1431 cgLocal.writeEntry("someLocalString", "whatever2"); 1432 QVERIFY(!cgLocal.sync()); 1433 1434 QVERIFY(f.setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner)); 1435 QVERIFY(f.remove()); 1436 } 1437 1438 void KConfigTest::testDirtyOnEqual() 1439 { 1440 QDateTime oldStamp; 1441 QDateTime newStamp; 1442 KConfig sc(s_kconfig_test_subdir); 1443 1444 // Initialize value 1445 KConfigGroup cgLocal(&sc, "random"); 1446 cgLocal.writeEntry("theKey", "whatever"); 1447 QVERIFY(sc.sync()); 1448 1449 // Age the timestamp of local config file a few sec, and collect it. 1450 QString locFile = m_testConfigDir + QLatin1String("/kconfigtest"); 1451 ageTimeStamp(locFile, 2); // age 2 sec 1452 oldStamp = QFileInfo(locFile).lastModified(); 1453 1454 // Write exactly the same again 1455 cgLocal.writeEntry("theKey", "whatever"); 1456 // This should be a no-op 1457 QVERIFY(sc.sync()); 1458 1459 // Verify that the timestamp of local config file didn't change. 1460 newStamp = QFileInfo(locFile).lastModified(); 1461 QCOMPARE(newStamp, oldStamp); 1462 } 1463 1464 void KConfigTest::testDirtyOnEqualOverdo() 1465 { 1466 QByteArray val1( 1467 "\0" 1468 "one", 1469 4); 1470 QByteArray val2( 1471 "\0" 1472 "two", 1473 4); 1474 QByteArray defvalr; 1475 1476 KConfig sc(s_kconfig_test_subdir); 1477 KConfigGroup cgLocal(&sc, "random"); 1478 cgLocal.writeEntry("someKey", val1); 1479 QCOMPARE(cgLocal.readEntry("someKey", defvalr), val1); 1480 cgLocal.writeEntry("someKey", val2); 1481 QCOMPARE(cgLocal.readEntry("someKey", defvalr), val2); 1482 } 1483 1484 void KConfigTest::testCreateDir() 1485 { 1486 // Test auto-creating the parent directory when needed (KConfigIniBackend::createEnclosing) 1487 const QString kdehome = QDir::home().canonicalPath() + QLatin1String("/.kde-unit-test"); 1488 const QString subdir = kdehome + QLatin1String("/newsubdir"); 1489 const QString file = subdir + QLatin1String("/foo.desktop"); 1490 QFile::remove(file); 1491 QDir().rmdir(subdir); 1492 QVERIFY(!QDir().exists(subdir)); 1493 KDesktopFile desktopFile(file); 1494 desktopFile.desktopGroup().writeEntry("key", "value"); 1495 QVERIFY(desktopFile.sync()); 1496 QVERIFY(QFile::exists(file)); 1497 1498 // Cleanup 1499 QFile::remove(file); 1500 QDir().rmdir(subdir); 1501 } 1502 1503 void KConfigTest::testSyncOnExit() 1504 { 1505 // Often, the KGlobalPrivate global static's destructor ends up calling ~KConfig -> 1506 // KConfig::sync ... and if that code triggers KGlobal code again then things could crash. 1507 // So here's a test for modifying KSharedConfig::openConfig() and not syncing, the process exit will sync. 1508 KConfigGroup grp(KSharedConfig::openConfig(s_test_subdir + QLatin1String("syncOnExitRc")), "syncOnExit"); 1509 grp.writeEntry("key", "value"); 1510 } 1511 1512 void KConfigTest::testSharedConfig() 1513 { 1514 // Can I use a KConfigGroup even after the KSharedConfigPtr goes out of scope? 1515 KConfigGroup myConfigGroup; 1516 { 1517 KSharedConfigPtr config = KSharedConfig::openConfig(s_kconfig_test_subdir); 1518 myConfigGroup = KConfigGroup(config, "Hello"); 1519 } 1520 QCOMPARE(myConfigGroup.readEntry("stringEntry1"), s_string_entry1); 1521 1522 // Get the main config 1523 KSharedConfigPtr mainConfig = KSharedConfig::openConfig(); 1524 KConfigGroup mainGroup(mainConfig, "Main"); 1525 QCOMPARE(mainGroup.readEntry("Key", QString{}), QStringLiteral("Value")); 1526 } 1527 1528 void KConfigTest::testLocaleConfig() 1529 { 1530 // Initialize the testdata 1531 QDir().mkpath(m_testConfigDir); 1532 const QString file = m_testConfigDir + QLatin1String("/localized.test"); 1533 QFile::remove(file); 1534 QFile f(file); 1535 QVERIFY(f.open(QIODevice::WriteOnly)); 1536 QTextStream ts(&f); 1537 ts << "[Test_Wrong]\n"; 1538 ts << "foo[ca]=5\n"; 1539 ts << "foostring[ca]=nice\n"; 1540 ts << "foobool[ca]=true\n"; 1541 ts << "[Test_Right]\n"; 1542 ts << "foo=5\n"; 1543 ts << "foo[ca]=5\n"; 1544 ts << "foostring=primary\n"; 1545 ts << "foostring[ca]=nice\n"; 1546 ts << "foobool=primary\n"; 1547 ts << "foobool[ca]=true\n"; 1548 f.close(); 1549 1550 // Load the testdata 1551 QVERIFY(QFile::exists(file)); 1552 KConfig config(file); 1553 config.setLocale(QStringLiteral("ca")); 1554 1555 // This group has only localized values. That is not supported. The values 1556 // should be dropped on loading. 1557 KConfigGroup cg(&config, "Test_Wrong"); 1558 QEXPECT_FAIL("", "The localized values are not dropped", Continue); 1559 QVERIFY(!cg.hasKey("foo")); 1560 QEXPECT_FAIL("", "The localized values are not dropped", Continue); 1561 QVERIFY(!cg.hasKey("foostring")); 1562 QEXPECT_FAIL("", "The localized values are not dropped", Continue); 1563 QVERIFY(!cg.hasKey("foobool")); 1564 1565 // Now check the correct config group 1566 KConfigGroup cg2(&config, "Test_Right"); 1567 QCOMPARE(cg2.readEntry("foo"), QStringLiteral("5")); 1568 QCOMPARE(cg2.readEntry("foo", 3), 5); 1569 QCOMPARE(cg2.readEntry("foostring"), QStringLiteral("nice")); 1570 QCOMPARE(cg2.readEntry("foostring", "ugly"), QStringLiteral("nice")); 1571 QCOMPARE(cg2.readEntry("foobool"), QStringLiteral("true")); 1572 QCOMPARE(cg2.readEntry("foobool", false), true); 1573 1574 // Clean up after the testcase 1575 QFile::remove(file); 1576 } 1577 1578 void KConfigTest::testDeleteWhenLocalized() 1579 { 1580 // Initialize the testdata 1581 const QString subdir = QDir::home().canonicalPath() + QLatin1String("/.kde-unit-test/"); 1582 QDir().mkpath(subdir); 1583 const QString file = subdir + QLatin1String("/localized_delete.test"); 1584 QFile::remove(file); 1585 QFile f(file); 1586 QVERIFY(f.open(QIODevice::WriteOnly)); 1587 QTextStream ts(&f); 1588 ts << "[Test4711]\n"; 1589 ts << "foo=3\n"; 1590 ts << "foo[ca]=5\n"; 1591 ts << "foo[de]=7\n"; 1592 ts << "foostring=ugly\n"; 1593 ts << "foostring[ca]=nice\n"; 1594 ts << "foostring[de]=schoen\n"; 1595 ts << "foobool=false\n"; 1596 ts << "foobool[ca]=true\n"; 1597 ts << "foobool[de]=true\n"; 1598 f.close(); 1599 1600 // Load the testdata. We start in locale "ca". 1601 QVERIFY(QFile::exists(file)); 1602 KConfig config(file); 1603 config.setLocale(QStringLiteral("ca")); 1604 KConfigGroup cg(&config, "Test4711"); 1605 1606 // Delete a value. Once with localized, once with Normal 1607 cg.deleteEntry("foostring", KConfigBase::Persistent | KConfigBase::Localized); 1608 cg.deleteEntry("foobool"); 1609 QVERIFY(config.sync()); 1610 1611 // The value is now gone. The others are still there. Everything correct 1612 // here. 1613 QVERIFY(!cg.hasKey("foostring")); 1614 QVERIFY(!cg.hasKey("foobool")); 1615 QVERIFY(cg.hasKey("foo")); 1616 1617 // The current state is: (Just return before this comment.) 1618 // [...] 1619 // foobool[ca]=true 1620 // foobool[de]=wahr 1621 // foostring=ugly 1622 // foostring[de]=schoen 1623 1624 // Now switch the locale to "de" and repeat the checks. Results should be 1625 // the same. But they currently are not. The localized value are 1626 // independent of each other. All values are still there in "de". 1627 config.setLocale(QStringLiteral("de")); 1628 QEXPECT_FAIL("", "Currently localized values are not deleted correctly", Continue); 1629 QVERIFY(!cg.hasKey("foostring")); 1630 QEXPECT_FAIL("", "Currently localized values are not deleted correctly", Continue); 1631 QVERIFY(!cg.hasKey("foobool")); 1632 QVERIFY(cg.hasKey("foo")); 1633 // Check where the wrong values come from. 1634 // We get the "de" value. 1635 QCOMPARE(cg.readEntry("foostring", "nothing"), QStringLiteral("schoen")); 1636 // We get the "de" value. 1637 QCOMPARE(cg.readEntry("foobool", false), true); 1638 1639 // Now switch the locale back "ca" and repeat the checks. Results are 1640 // again different. 1641 config.setLocale(QStringLiteral("ca")); 1642 // This line worked above. But now it fails. 1643 QEXPECT_FAIL("", "Currently localized values are not deleted correctly", Continue); 1644 QVERIFY(!cg.hasKey("foostring")); 1645 // This line worked above too. 1646 QEXPECT_FAIL("", "Currently localized values are not deleted correctly", Continue); 1647 QVERIFY(!cg.hasKey("foobool")); 1648 QVERIFY(cg.hasKey("foo")); 1649 // Check where the wrong values come from. 1650 // We get the primary value because the "ca" value was deleted. 1651 QCOMPARE(cg.readEntry("foostring", "nothing"), QStringLiteral("ugly")); 1652 // We get the "ca" value. 1653 QCOMPARE(cg.readEntry("foobool", false), true); 1654 1655 // Now test the deletion of a group. 1656 cg.deleteGroup(); 1657 QVERIFY(config.sync()); 1658 1659 // Current state: [ca] and [de] entries left... oops. 1660 // qDebug() << readLinesFrom(file); 1661 1662 // Bug: The group still exists [because of the localized entries]... 1663 QVERIFY(cg.exists()); 1664 QVERIFY(!cg.hasKey("foo")); 1665 QVERIFY(!cg.hasKey("foostring")); 1666 QEXPECT_FAIL("", "Currently localized values are not deleted correctly", Continue); 1667 QVERIFY(!cg.hasKey("foobool")); 1668 1669 // Now switch the locale to "de" and repeat the checks. All values 1670 // still here because only the primary values are deleted. 1671 config.setLocale(QStringLiteral("de")); 1672 QEXPECT_FAIL("", "Currently localized values are not deleted correctly", Continue); 1673 QVERIFY(!cg.hasKey("foo")); 1674 QEXPECT_FAIL("", "Currently localized values are not deleted correctly", Continue); 1675 QVERIFY(!cg.hasKey("foostring")); 1676 QEXPECT_FAIL("", "Currently localized values are not deleted correctly", Continue); 1677 QVERIFY(!cg.hasKey("foobool")); 1678 // Check where the wrong values come from. 1679 // We get the "de" value. 1680 QCOMPARE(cg.readEntry("foostring", "nothing"), QStringLiteral("schoen")); 1681 // We get the "de" value. 1682 QCOMPARE(cg.readEntry("foobool", false), true); 1683 // We get the "de" value. 1684 QCOMPARE(cg.readEntry("foo", 0), 7); 1685 1686 // Now switch the locale to "ca" and repeat the checks 1687 // "foostring" is now really gone because both the primary value and the 1688 // "ca" value are deleted. 1689 config.setLocale(QStringLiteral("ca")); 1690 QEXPECT_FAIL("", "Currently localized values are not deleted correctly", Continue); 1691 QVERIFY(!cg.hasKey("foo")); 1692 QVERIFY(!cg.hasKey("foostring")); 1693 QEXPECT_FAIL("", "Currently localized values are not deleted correctly", Continue); 1694 QVERIFY(!cg.hasKey("foobool")); 1695 // Check where the wrong values come from. 1696 // We get the "ca" value. 1697 QCOMPARE(cg.readEntry("foobool", false), true); 1698 // We get the "ca" value. 1699 QCOMPARE(cg.readEntry("foo", 0), 5); 1700 1701 // Cleanup 1702 QFile::remove(file); 1703 } 1704 1705 void KConfigTest::testKdeGlobals() 1706 { 1707 { 1708 KConfig glob(QStringLiteral("kdeglobals")); 1709 KConfigGroup general(&glob, "General"); 1710 general.writeEntry("testKG", "1"); 1711 QVERIFY(glob.sync()); 1712 } 1713 1714 KConfig globRead(QStringLiteral("kdeglobals")); 1715 const KConfigGroup general(&globRead, "General"); 1716 QCOMPARE(general.readEntry("testKG"), QStringLiteral("1")); 1717 1718 // Check we wrote into kdeglobals 1719 const QList<QByteArray> lines = readLines(QStringLiteral("kdeglobals")); 1720 QVERIFY(lines.contains("[General]\n")); 1721 QVERIFY(lines.contains("testKG=1\n")); 1722 1723 // Writing using NoGlobals 1724 { 1725 KConfig glob(QStringLiteral("kdeglobals"), KConfig::NoGlobals); 1726 KConfigGroup general(&glob, "General"); 1727 general.writeEntry("testKG", "2"); 1728 QVERIFY(glob.sync()); 1729 } 1730 globRead.reparseConfiguration(); 1731 QCOMPARE(general.readEntry("testKG"), QStringLiteral("2")); 1732 1733 // Reading using NoGlobals 1734 { 1735 KConfig globReadNoGlob(QStringLiteral("kdeglobals"), KConfig::NoGlobals); 1736 const KConfigGroup generalNoGlob(&globReadNoGlob, "General"); 1737 QCOMPARE(generalNoGlob.readEntry("testKG"), QStringLiteral("2")); 1738 } 1739 } 1740 1741 void KConfigTest::testLocalDeletion() 1742 { 1743 // Prepare kdeglobals 1744 { 1745 KConfig glob(QStringLiteral("kdeglobals")); 1746 KConfigGroup general(&glob, "OwnTestGroup"); 1747 general.writeEntry("GlobalKey", "DontTouchMe"); 1748 QVERIFY(glob.sync()); 1749 } 1750 1751 QStringList expectedKeys{QStringLiteral("LocalKey")}; 1752 expectedKeys.prepend(QStringLiteral("GlobalWrite")); 1753 1754 // Write into kconfigtest, including deleting GlobalKey 1755 { 1756 KConfig mainConfig(s_kconfig_test_subdir); 1757 KConfigGroup mainGroup(&mainConfig, "OwnTestGroup"); 1758 mainGroup.writeEntry("LocalKey", QStringLiteral("LocalValue")); 1759 mainGroup.writeEntry("GlobalWrite", QStringLiteral("GlobalValue"), KConfig::Persistent | KConfig::Global); // goes to kdeglobals 1760 QCOMPARE(mainGroup.readEntry("GlobalKey"), QStringLiteral("DontTouchMe")); 1761 mainGroup.deleteEntry("GlobalKey"); // local deletion ([$d]), kdeglobals is unchanged 1762 QCOMPARE(mainGroup.readEntry("GlobalKey", "Default"), QStringLiteral("Default")); // key is gone 1763 QCOMPARE(mainGroup.keyList(), expectedKeys); 1764 } 1765 1766 // Check what ended up in kconfigtest 1767 const QList<QByteArray> lines = readLines(); 1768 QVERIFY(lines.contains("[OwnTestGroup]\n")); 1769 QVERIFY(lines.contains("GlobalKey[$d]\n")); 1770 1771 // Check what ended up in kdeglobals 1772 { 1773 KConfig globReadNoGlob(QStringLiteral("kdeglobals"), KConfig::NoGlobals); 1774 const KConfigGroup generalNoGlob(&globReadNoGlob, "OwnTestGroup"); 1775 QCOMPARE(generalNoGlob.readEntry("GlobalKey"), QStringLiteral("DontTouchMe")); 1776 QCOMPARE(generalNoGlob.readEntry("GlobalWrite"), QStringLiteral("GlobalValue")); 1777 QVERIFY(!generalNoGlob.hasKey("LocalValue")); 1778 QStringList expectedGlobalKeys{QStringLiteral("GlobalKey")}; 1779 expectedGlobalKeys.append(QStringLiteral("GlobalWrite")); 1780 QCOMPARE(generalNoGlob.keyList(), expectedGlobalKeys); 1781 } 1782 1783 // Check what we see when re-reading the config file 1784 { 1785 KConfig mainConfig(s_kconfig_test_subdir); 1786 KConfigGroup mainGroup(&mainConfig, "OwnTestGroup"); 1787 QCOMPARE(mainGroup.readEntry("GlobalKey", "Default"), QStringLiteral("Default")); // key is gone 1788 QCOMPARE(mainGroup.keyList(), expectedKeys); 1789 } 1790 } 1791 1792 void KConfigTest::testAnonymousConfig() 1793 { 1794 KConfig anonConfig(QString(), KConfig::SimpleConfig); 1795 KConfigGroup general(&anonConfig, "General"); 1796 QCOMPARE(general.readEntry("testKG"), QString()); // no kdeglobals merging 1797 general.writeEntry("Foo", "Bar"); 1798 QCOMPARE(general.readEntry("Foo"), QStringLiteral("Bar")); 1799 } 1800 1801 void KConfigTest::testQByteArrayUtf8() 1802 { 1803 QTemporaryFile file; 1804 QVERIFY(file.open()); 1805 KConfig config(file.fileName(), KConfig::SimpleConfig); 1806 KConfigGroup general(&config, "General"); 1807 QByteArray bytes(256, '\0'); 1808 for (int i = 0; i < 256; i++) { 1809 bytes[i] = i; 1810 } 1811 general.writeEntry("Utf8", bytes); 1812 config.sync(); 1813 file.flush(); 1814 file.close(); 1815 QFile readFile(file.fileName()); 1816 QVERIFY(readFile.open(QFile::ReadOnly)); 1817 #define VALUE \ 1818 "Utf8=" \ 1819 "\\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" \ 1820 "1f " \ 1821 "!\"#$%&'()*+,-./" \ 1822 "0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" \ 1823 "\\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\\" \ 1824 "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" \ 1825 "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" \ 1826 "\\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\\" \ 1827 "xf8\\xf9\\xfa\\xfb\\xfc\\xfd\\xfe\\xff" 1828 const QByteArray fileBytes = readFile.readAll(); 1829 #ifndef Q_OS_WIN 1830 QCOMPARE(fileBytes, QByteArrayLiteral("[General]\n" VALUE "\n")); 1831 #else 1832 QCOMPARE(fileBytes, QByteArrayLiteral("[General]\r\n" VALUE "\r\n")); 1833 #endif 1834 #undef VALUE 1835 1836 // check that reading works 1837 KConfig config2(file.fileName(), KConfig::SimpleConfig); 1838 KConfigGroup general2(&config2, "General"); 1839 QCOMPARE(bytes, general2.readEntry("Utf8", QByteArray())); 1840 } 1841 1842 void KConfigTest::testQStringUtf8_data() 1843 { 1844 QTest::addColumn<QByteArray>("data"); 1845 QTest::newRow("1") << QByteArray("Téléchargements\tTéléchargements"); 1846 QTest::newRow("2") << QByteArray("$¢ह€𐍈\t$¢ह€𐍈"); 1847 QTest::newRow("3") << QByteArray("\xc2\xe0\xa4\xf0\x90\x8d\t\\xc2\\xe0\\xa4\\xf0\\x90\\x8d"); 1848 // 2 byte overlong 1849 QTest::newRow("4") << QByteArray("\xc1\xbf\t\\xc1\\xbf"); 1850 // 3 byte overlong 1851 QTest::newRow("5") << QByteArray("\xe0\x9f\xbf\t\\xe0\\x9f\\xbf"); 1852 // 4 byte overlong 1853 QTest::newRow("6") << QByteArray("\xf0\x8f\xbf\xbf\t\\xf0\\x8f\\xbf\\xbf"); 1854 // outside unicode range 1855 QTest::newRow("7") << QByteArray("\xf4\x90\x80\x80\t\\xf4\\x90\\x80\\x80"); 1856 // just within range 1857 QTest::newRow("8") << QByteArray("\xc2\x80\t\xc2\x80"); 1858 QTest::newRow("9") << QByteArray("\xe0\xa0\x80\t\xe0\xa0\x80"); 1859 QTest::newRow("10") << QByteArray("\xf0\x90\x80\x80\t\xf0\x90\x80\x80"); 1860 QTest::newRow("11") << QByteArray("\xf4\x8f\xbf\xbf\t\xf4\x8f\xbf\xbf"); 1861 } 1862 1863 void KConfigTest::testQStringUtf8() 1864 { 1865 QFETCH(QByteArray, data); 1866 const QList<QByteArray> d = data.split('\t'); 1867 const QByteArray value = d[0]; 1868 const QByteArray serialized = d[1]; 1869 QTemporaryFile file; 1870 QVERIFY(file.open()); 1871 KConfig config(file.fileName(), KConfig::SimpleConfig); 1872 KConfigGroup general(&config, "General"); 1873 general.writeEntry("key", value); 1874 config.sync(); 1875 file.flush(); 1876 file.close(); 1877 QFile readFile(file.fileName()); 1878 QVERIFY(readFile.open(QFile::ReadOnly)); 1879 QByteArray fileBytes = readFile.readAll(); 1880 #ifdef Q_OS_WIN 1881 fileBytes.replace(QByteArrayLiteral("\r\n"), QByteArrayLiteral("\n")); 1882 #endif 1883 QCOMPARE(fileBytes, QByteArrayLiteral("[General]\nkey=") + serialized + QByteArrayLiteral("\n")); 1884 1885 // check that reading works 1886 KConfig config2(file.fileName(), KConfig::SimpleConfig); 1887 KConfigGroup general2(&config2, "General"); 1888 QCOMPARE(value, general2.readEntry("key", QByteArray())); 1889 } 1890 1891 void KConfigTest::testNewlines() 1892 { 1893 // test that kconfig always uses the native line endings 1894 QTemporaryFile file; 1895 QVERIFY(file.open()); 1896 KConfig anonConfig(file.fileName(), KConfig::SimpleConfig); 1897 KConfigGroup general(&anonConfig, "General"); 1898 general.writeEntry("Foo", "Bar"); 1899 general.writeEntry("Bar", "Foo"); 1900 anonConfig.sync(); 1901 file.flush(); 1902 file.close(); 1903 QFile readFile(file.fileName()); 1904 QVERIFY(readFile.open(QFile::ReadOnly)); 1905 #ifndef Q_OS_WIN 1906 QCOMPARE(readFile.readAll(), QByteArrayLiteral("[General]\nBar=Foo\nFoo=Bar\n")); 1907 #else 1908 QCOMPARE(readFile.readAll(), QByteArrayLiteral("[General]\r\nBar=Foo\r\nFoo=Bar\r\n")); 1909 #endif 1910 } 1911 1912 void KConfigTest::testMoveValuesTo() 1913 { 1914 QTemporaryFile file; 1915 QVERIFY(file.open()); 1916 // Prepare kdeglobals 1917 { 1918 KConfig glob(QStringLiteral("kdeglobals")); 1919 KConfigGroup general(&glob, "TestGroup"); 1920 general.writeEntry("GlobalKey", "PlsDeleteMe"); 1921 QVERIFY(glob.sync()); 1922 } 1923 1924 KConfigGroup grp = KSharedConfig::openConfig(file.fileName())->group("TestGroup"); 1925 1926 grp.writeEntry("test1", "first_value"); 1927 grp.writeEntry("test_empty", ""); 1928 grp.writeEntry("other", "other_value"); 1929 grp.writePathEntry("my_path", QStringLiteral("~/somepath")); 1930 // because this key is from the global file it should be explicitly deleted 1931 grp.deleteEntry("GlobalKey"); 1932 1933 QTemporaryFile targetFile; 1934 QVERIFY(targetFile.open()); 1935 targetFile.close(); 1936 KConfigGroup targetGroup = KSharedConfig::openConfig(targetFile.fileName(), KConfig::SimpleConfig)->group("MoveToGroup"); 1937 1938 grp.moveValuesTo({"test1", "test_empty", "does_not_exist", "my_path", "GlobalKey"}, targetGroup); 1939 QVERIFY(grp.config()->isDirty()); 1940 QVERIFY(targetGroup.config()->isDirty()); 1941 1942 QCOMPARE(grp.keyList(), QStringList{QStringLiteral("other")}); 1943 QStringList expectedKeyList{QStringLiteral("my_path"), QStringLiteral("test1"), QStringLiteral("test_empty")}; 1944 QCOMPARE(targetGroup.keyList(), expectedKeyList); 1945 QCOMPARE(targetGroup.readEntry("test1"), QStringLiteral("first_value")); 1946 1947 targetGroup.sync(); 1948 QFile targetReadFile(targetFile.fileName()); 1949 targetReadFile.open(QFile::ReadOnly); 1950 QVERIFY(targetReadFile.readAll().contains(QByteArray("my_path[$e]=~/somepath"))); 1951 } 1952 1953 void KConfigTest::testXdgListEntry() 1954 { 1955 QTemporaryFile file; 1956 QVERIFY(file.open()); 1957 QTextStream out(&file); 1958 out << "[General]\n" 1959 << "Key1=\n" // empty list 1960 // emtpty entries 1961 << "Key2=;\n" 1962 << "Key3=;;\n" 1963 << "Key4=;;;\n" 1964 << "Key5=\\;\n" 1965 << "Key6=1;2\\;3;;\n"; 1966 out.flush(); 1967 file.close(); 1968 KConfig anonConfig(file.fileName(), KConfig::SimpleConfig); 1969 KConfigGroup grp = anonConfig.group("General"); 1970 QStringList invalidList; // use this as a default when an empty list is expected 1971 invalidList << QStringLiteral("Error! Default value read!"); 1972 QCOMPARE(grp.readXdgListEntry("Key1", invalidList), (QStringList{})); 1973 QCOMPARE(grp.readXdgListEntry("Key2", invalidList), (QStringList{QString{}})); 1974 QCOMPARE(grp.readXdgListEntry("Key3", invalidList), (QStringList{QString{}, QString{}})); 1975 QCOMPARE(grp.readXdgListEntry("Key4", invalidList), (QStringList{QString{}, QString{}, QString{}})); 1976 QCOMPARE(grp.readXdgListEntry("Key5", invalidList), (QStringList{QStringLiteral(";")})); 1977 QCOMPARE(grp.readXdgListEntry("Key6", invalidList), (QStringList{QStringLiteral("1"), QStringLiteral("2;3"), QString{}})); 1978 } 1979 1980 #include <QThreadPool> 1981 #include <qtconcurrentrun.h> 1982 1983 // To find multithreading bugs: valgrind --tool=helgrind --track-lockorders=no ./kconfigtest testThreads 1984 void KConfigTest::testThreads() 1985 { 1986 QThreadPool::globalInstance()->setMaxThreadCount(6); 1987 // Run in parallel some tests that work on different config files, 1988 // otherwise unexpected things might indeed happen. 1989 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 1990 const QList<QFuture<void>> futures = { 1991 QtConcurrent::run(&KConfigTest::testAddConfigSources, this), 1992 QtConcurrent::run(&KConfigTest::testSimple, this), 1993 QtConcurrent::run(&KConfigTest::testDefaults, this), 1994 QtConcurrent::run(&KConfigTest::testSharedConfig, this), 1995 QtConcurrent::run(&KConfigTest::testSharedConfig, this), 1996 }; 1997 #else 1998 const QList<QFuture<void>> futures = { 1999 QtConcurrent::run(this, &KConfigTest::testAddConfigSources), 2000 QtConcurrent::run(this, &KConfigTest::testSimple), 2001 QtConcurrent::run(this, &KConfigTest::testDefaults), 2002 QtConcurrent::run(this, &KConfigTest::testSharedConfig), 2003 QtConcurrent::run(this, &KConfigTest::testSharedConfig), 2004 }; 2005 #endif 2006 2007 // QEXPECT_FAIL triggers race conditions, it should be fixed to use QThreadStorage... 2008 // futures << QtConcurrent::run(this, &KConfigTest::testDeleteWhenLocalized); 2009 // futures << QtConcurrent::run(this, &KConfigTest::testEntryMap); 2010 for (QFuture<void> f : futures) { 2011 f.waitForFinished(); 2012 } 2013 } 2014 2015 void KConfigTest::testNotify() 2016 { 2017 #if !KCONFIG_USE_DBUS 2018 QSKIP("KConfig notification requires DBus"); 2019 #endif 2020 2021 KConfig config(s_kconfig_test_subdir); 2022 auto myConfigGroup = KConfigGroup(&config, "TopLevelGroup"); 2023 2024 // mimics a config in another process, which is watching for events 2025 auto remoteConfig = KSharedConfig::openConfig(s_kconfig_test_subdir); 2026 KConfigWatcher::Ptr watcher = KConfigWatcher::create(remoteConfig); 2027 2028 // some random config that shouldn't be changing when kconfigtest changes, only on kdeglobals 2029 auto otherRemoteConfig = KSharedConfig::openConfig(s_test_subdir + QLatin1String("kconfigtest2")); 2030 KConfigWatcher::Ptr otherWatcher = KConfigWatcher::create(otherRemoteConfig); 2031 2032 QSignalSpy watcherSpy(watcher.data(), &KConfigWatcher::configChanged); 2033 QSignalSpy otherWatcherSpy(otherWatcher.data(), &KConfigWatcher::configChanged); 2034 2035 // write entries in a group and subgroup 2036 myConfigGroup.writeEntry("entryA", "foo", KConfig::Persistent | KConfig::Notify); 2037 auto subGroup = myConfigGroup.group("aSubGroup"); 2038 subGroup.writeEntry("entry1", "foo", KConfig::Persistent | KConfig::Notify); 2039 subGroup.writeEntry("entry2", "foo", KConfig::Persistent | KConfig::Notify); 2040 config.sync(); 2041 watcherSpy.wait(); 2042 QCOMPARE(watcherSpy.count(), 2); 2043 2044 std::sort(watcherSpy.begin(), watcherSpy.end(), [](QList<QVariant> a, QList<QVariant> b) { 2045 return a[0].value<KConfigGroup>().name() < b[0].value<KConfigGroup>().name(); 2046 }); 2047 2048 QCOMPARE(watcherSpy[0][0].value<KConfigGroup>().name(), QStringLiteral("TopLevelGroup")); 2049 QCOMPARE(watcherSpy[0][1].value<QByteArrayList>(), QByteArrayList({"entryA"})); 2050 2051 QCOMPARE(watcherSpy[1][0].value<KConfigGroup>().name(), QStringLiteral("aSubGroup")); 2052 QCOMPARE(watcherSpy[1][0].value<KConfigGroup>().parent().name(), QStringLiteral("TopLevelGroup")); 2053 QCOMPARE(watcherSpy[1][1].value<QByteArrayList>(), QByteArrayList({"entry1", "entry2"})); 2054 2055 // delete an entry 2056 watcherSpy.clear(); 2057 myConfigGroup.deleteEntry("entryA", KConfig::Persistent | KConfig::Notify); 2058 config.sync(); 2059 watcherSpy.wait(); 2060 QCOMPARE(watcherSpy.count(), 1); 2061 QCOMPARE(watcherSpy[0][0].value<KConfigGroup>().name(), QStringLiteral("TopLevelGroup")); 2062 QCOMPARE(watcherSpy[0][1].value<QByteArrayList>(), QByteArrayList({"entryA"})); 2063 2064 // revert to default an entry 2065 watcherSpy.clear(); 2066 myConfigGroup.revertToDefault("entryA", KConfig::Persistent | KConfig::Notify); 2067 config.sync(); 2068 watcherSpy.wait(); 2069 QCOMPARE(watcherSpy.count(), 1); 2070 QCOMPARE(watcherSpy[0][0].value<KConfigGroup>().name(), QStringLiteral("TopLevelGroup")); 2071 QCOMPARE(watcherSpy[0][1].value<QByteArrayList>(), QByteArrayList({"entryA"})); 2072 2073 // deleting a group, should notify that every entry in that group has changed 2074 watcherSpy.clear(); 2075 myConfigGroup.deleteGroup("aSubGroup", KConfig::Persistent | KConfig::Notify); 2076 config.sync(); 2077 watcherSpy.wait(); 2078 QCOMPARE(watcherSpy.count(), 1); 2079 QCOMPARE(watcherSpy[0][0].value<KConfigGroup>().name(), QStringLiteral("aSubGroup")); 2080 QCOMPARE(watcherSpy[0][1].value<QByteArrayList>(), QByteArrayList({"entry1", "entry2"})); 2081 2082 // global write still triggers our notification 2083 watcherSpy.clear(); 2084 myConfigGroup.writeEntry("someGlobalEntry", "foo", KConfig::Persistent | KConfig::Notify | KConfig::Global); 2085 config.sync(); 2086 watcherSpy.wait(); 2087 QCOMPARE(watcherSpy.count(), 1); 2088 QCOMPARE(watcherSpy[0][0].value<KConfigGroup>().name(), QStringLiteral("TopLevelGroup")); 2089 QCOMPARE(watcherSpy[0][1].value<QByteArrayList>(), QByteArrayList({"someGlobalEntry"})); 2090 2091 // watching another file should have only triggered from the kdeglobals change 2092 QCOMPARE(otherWatcherSpy.count(), 1); 2093 QCOMPARE(otherWatcherSpy[0][0].value<KConfigGroup>().name(), QStringLiteral("TopLevelGroup")); 2094 QCOMPARE(otherWatcherSpy[0][1].value<QByteArrayList>(), QByteArrayList({"someGlobalEntry"})); 2095 } 2096 2097 void KConfigTest::testNotifyIllegalObjectPath() 2098 { 2099 #if !KCONFIG_USE_DBUS 2100 QSKIP("KConfig notification requires DBus"); 2101 #endif 2102 2103 KConfig config(s_kconfig_test_illegal_object_path); 2104 auto myConfigGroup = KConfigGroup(&config, "General"); 2105 2106 // mimics a config in another process, which is watching for events 2107 auto remoteConfig = KSharedConfig::openConfig(s_kconfig_test_illegal_object_path); 2108 KConfigWatcher::Ptr watcher = KConfigWatcher::create(remoteConfig); 2109 2110 QSignalSpy watcherSpy(watcher.data(), &KConfigWatcher::configChanged); 2111 2112 // write entries in a group and subgroup 2113 myConfigGroup.writeEntry("entryA", "foo", KConfig::Persistent | KConfig::Notify); 2114 config.sync(); 2115 watcherSpy.wait(); 2116 QCOMPARE(watcherSpy.size(), 1); 2117 } 2118 2119 void KConfigTest::testKAuthorizeEnums() 2120 { 2121 KSharedConfig::Ptr config = KSharedConfig::openConfig(); 2122 KConfigGroup actionRestrictions = config->group("KDE Action Restrictions"); 2123 actionRestrictions.writeEntry("shell_access", false); 2124 actionRestrictions.writeEntry("action/open_with", false); 2125 2126 QVERIFY(!KAuthorized::authorize(KAuthorized::SHELL_ACCESS)); 2127 QVERIFY(!KAuthorized::authorizeAction(KAuthorized::OPEN_WITH)); 2128 actionRestrictions.deleteGroup(); 2129 2130 QVERIFY(!KAuthorized::authorize((KAuthorized::GenericRestriction)0)); 2131 QVERIFY(!KAuthorized::authorizeAction((KAuthorized::GenericAction)0)); 2132 } 2133 2134 void KConfigTest::testKdeglobalsVsDefault() 2135 { 2136 // Add testRestore key with global value in kdeglobals 2137 KConfig glob(QStringLiteral("kdeglobals")); 2138 KConfigGroup generalGlob(&glob, "General"); 2139 generalGlob.writeEntry("testRestore", "global"); 2140 QVERIFY(glob.sync()); 2141 2142 KConfig local(s_test_subdir + QLatin1String("restorerc")); 2143 KConfigGroup generalLocal(&local, "General"); 2144 // Check if we get global and not the default value from cpp (defaultcpp) when reading data from restorerc 2145 QCOMPARE(generalLocal.readEntry("testRestore", "defaultcpp"), QStringLiteral("global")); 2146 2147 // Add test restore key with restore value in restorerc file 2148 generalLocal.writeEntry("testRestore", "restore"); 2149 QVERIFY(local.sync()); 2150 local.reparseConfiguration(); 2151 // We expect to get the value from restorerc file 2152 QCOMPARE(generalLocal.readEntry("testRestore", "defaultcpp"), QStringLiteral("restore")); 2153 2154 // Revert to default testRestore key and we expect to get default value and not the global one 2155 generalLocal.revertToDefault("testRestore"); 2156 local.sync(); 2157 local.reparseConfiguration(); 2158 QCOMPARE(generalLocal.readEntry("testRestore", "defaultcpp"), QStringLiteral("defaultcpp")); 2159 } 2160 2161 #include "moc_kconfigtest.cpp"