/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/


#include <QtTest/QtTest>

#include <QtCore/QSettings>
#include <private/qsettings_p.h>
#include <QtCore/QCoreApplication>
#include <QtCore/QDateTime>
#include <QtCore/QtGlobal>
#include <QtCore/QMetaType>
#include <QtCore/QString>
#include <QtCore/QDir>
#include <QtCore/QThread>
#include <QtCore/QSysInfo>
#include <QtGui/QKeySequence>

#include <QtCore>
#include <QtGui>
#include "tst_qmetatype.h"

#include <cctype>
#include <stdlib.h>
#if defined(Q_OS_WIN) && defined(Q_CC_GNU)
// need for unlink on mingw
#include <io.h>
#endif

#if defined(Q_OS_WIN)
#include <QtCore/qt_windows.h>
#ifndef Q_OS_WINRT
#  include <private/qwinregistry_p.h>
#endif
#else
#include <unistd.h>
#endif

#if defined(Q_OS_DARWIN)
#include <CoreFoundation/CoreFoundation.h>
#endif

Q_DECLARE_METATYPE(QSettings::Format)

#ifndef QSETTINGS_P_H_VERSION
#define QSETTINGS_P_H_VERSION 1
#endif

QT_FORWARD_DECLARE_CLASS(QSettings)

static inline bool canWriteNativeSystemSettings()
{
#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT)
    HKEY key;
    const LONG result = RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"Software", 0, KEY_WRITE, &key);
    if (result == ERROR_SUCCESS)
        RegCloseKey(key);
    else
        qErrnoWarning(result, "RegOpenKeyEx failed");
    return result == ERROR_SUCCESS;
#elif defined(Q_OS_DARWIN)
    CFStringRef key = CFSTR("canWriteNativeSystemSettings");
    #define ANY_APP_USER_AND_HOST kCFPreferencesAnyApplication, kCFPreferencesAnyUser, kCFPreferencesAnyHost
    CFPreferencesSetValue(key, CFSTR("true"), ANY_APP_USER_AND_HOST);
    if (CFPreferencesSynchronize(ANY_APP_USER_AND_HOST)) {
        // Cleanup
        CFPreferencesSetValue(key, 0, ANY_APP_USER_AND_HOST);
        CFPreferencesSynchronize(ANY_APP_USER_AND_HOST);
        return true;
    } else {
        return false;
    }
#else
    return true;
#endif
}

static const char insufficientPermissionSkipMessage[] = "Insufficient permissions for this test.";

class tst_QSettings : public QObject
{
    Q_OBJECT

public:
    tst_QSettings();

public slots:
    void initTestCase();
    void cleanup() { cleanupTestFiles(); }
private slots:
    void getSetCheck();
    void ctor_data();
    void ctor();
    void beginGroup();
    void setValue();
    void remove();
    void contains();
    void sync();
    void syncNonWriteableDir();
#ifdef Q_OS_WIN
    void syncAlternateDataStream();
#endif
    void setFallbacksEnabled();
    void setFallbacksEnabled_data();
    void fromFile_data();
    void fromFile();
    void testArrays_data();
    void testArrays();
    void testCaseSensitivity_data();
    void testCaseSensitivity();
    void testErrorHandling_data();
    void testErrorHandling();
    void testChildKeysAndGroups_data();
    void testChildKeysAndGroups();
    void testUpdateRequestEvent();
    void testThreadSafety();
    void testEmptyData();
    void testEmptyKey();
    void testResourceFiles();
    void testRegistryShortRootNames();
    void testRegistry32And64Bit();
    void trailingWhitespace();
#ifdef Q_OS_MAC
    void fileName();
#endif
    void isWritable_data();
    void isWritable();
    void registerFormat();
    void setPath();
    void setDefaultFormat();
    void dontCreateNeedlessPaths();
#if !defined(Q_OS_WIN) && !defined(QT_QSETTINGS_ALWAYS_CASE_SENSITIVE_AND_FORGET_ORIGINAL_KEY_ORDER)
    void dontReorderIniKeysNeedlessly();
#endif
#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT)
    void consistentRegistryStorage();
#endif

#ifdef QT_BUILD_INTERNAL
    void allKeys_data();
    void allKeys();
    void childGroups_data();
    void childGroups();
    void childKeys_data();
    void childKeys();
    void setIniCodec();
    void testIniParsing_data();
    void testIniParsing();
    void testEscapes();
    void testNormalizedKey_data();
    void testNormalizedKey();
    void testVariantTypes_data();
    void testVariantTypes();
    void testMetaTypes_data();
    void testMetaTypes();
#endif
    void rainersSyncBugOnMac_data();
    void rainersSyncBugOnMac();
    void recursionBug();

    void testByteArray_data();
    void testByteArray();
    void testByteArrayNativeFormat();
    void iniCodec();
    void bom();
    void embeddedZeroByte_data();
    void embeddedZeroByte();
    void spaceAfterComment();

    void testXdg();
private:
    void cleanupTestFiles();

    const bool m_canWriteNativeSystemSettings;
};

// Testing get/set functions
void tst_QSettings::getSetCheck()
{
    QSettings obj1;
    // bool QSettings::fallbacksEnabled()
    // void QSettings::setFallbacksEnabled(bool)
    obj1.setFallbacksEnabled(false);
    QCOMPARE(false, obj1.fallbacksEnabled());
    obj1.setFallbacksEnabled(true);
    QCOMPARE(true, obj1.fallbacksEnabled());
}

static QString settingsPath(const char *path = nullptr)
{
    // Temporary path for files that are specified explicitly in the constructor.
#ifndef Q_OS_WINRT
    static const QString tempPath = QDir::tempPath() + QLatin1String("/tst_QSettings");
#else
    static const QString tempPath = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)
        + QLatin1String("/tst_QSettings");
#endif
    return path && *path ? tempPath + QLatin1Char('/') + QLatin1String(path) : tempPath;
}

static bool readCustom1File(QIODevice &device, QSettings::SettingsMap &map)
{
    QDataStream in(&device);
    quint32 magic;
    in >> magic;
    in >> map;
    return (magic == 0x01010101 && in.status() == QDataStream::Ok);
}

static bool writeCustom1File(QIODevice &device, const QSettings::SettingsMap &map)
{
    QDataStream out(&device);
    out << quint32(0x01010101);
    out << map;
    return out.status() == QDataStream::Ok;
}

static bool readCustom2File(QIODevice &device, QSettings::SettingsMap &map)
{
    QDataStream in(&device);
    quint64 magic;
    in >> magic;
    in >> map;
    return (magic == Q_UINT64_C(0x0202020202020202) && in.status() == QDataStream::Ok);
}

static bool writeCustom2File(QIODevice &device, const QSettings::SettingsMap &map)
{
    QDataStream out(&device);
    out << Q_UINT64_C(0x0202020202020202);
    out << map;
    return out.status() == QDataStream::Ok;
}

static bool readCustom3File(QIODevice &device, QSettings::SettingsMap &map)
{
    QTextStream in(&device);
    QString tag;
    in >> tag;
    if (tag == "OK") {
        map.insert("retval", "OK");
        return true;
    } else {
        return false;
    }
}

static bool writeCustom3File(QIODevice &device, const QSettings::SettingsMap &map)
{
    QTextStream out(&device);
    if (map.value("retval") != "OK")
        return false;

    out << "OK";
    return true;
}

static void populateWithFormats()
{
    QTest::addColumn<QSettings::Format>("format");

    QTest::newRow("native") << QSettings::NativeFormat;
    QTest::newRow("ini") << QSettings::IniFormat;
    QTest::newRow("custom1") << QSettings::CustomFormat1;
    QTest::newRow("custom2") << QSettings::CustomFormat2;
}

tst_QSettings::tst_QSettings()
    : m_canWriteNativeSystemSettings(canWriteNativeSystemSettings())
{
    QStandardPaths::setTestModeEnabled(true);
}

void tst_QSettings::initTestCase()
{
    if (!m_canWriteNativeSystemSettings)
        qWarning("The test is not running with administrative rights. Some tests will be skipped.");
    QSettings::Format custom1 = QSettings::registerFormat("custom1", readCustom1File, writeCustom1File);
    QSettings::Format custom2 = QSettings::registerFormat("custom2", readCustom2File, writeCustom2File
#ifndef QT_QSETTINGS_ALWAYS_CASE_SENSITIVE_AND_FORGET_ORIGINAL_KEY_ORDER
                                                          , Qt::CaseInsensitive
#endif
                                                          );
    QCOMPARE(custom1, QSettings::CustomFormat1);
    QCOMPARE(custom2, QSettings::CustomFormat2);

    cleanupTestFiles();
}

void tst_QSettings::cleanupTestFiles()
{
    QSettings::setPath(QSettings::IniFormat, QSettings::SystemScope, settingsPath("__system__"));
    QSettings::setPath(QSettings::NativeFormat, QSettings::SystemScope, settingsPath("__system__"));

    QSettings::setPath(QSettings::IniFormat, QSettings::UserScope, settingsPath("__user__"));
    QSettings::setPath(QSettings::NativeFormat, QSettings::UserScope, settingsPath("__user__"));

    QDir settingsDir(settingsPath());
    if (settingsDir.exists())
        QVERIFY(settingsDir.removeRecursively());

#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT)
    QSettings("HKEY_CURRENT_USER\\Software\\software.org", QSettings::NativeFormat).clear();
    QSettings("HKEY_CURRENT_USER\\Software\\other.software.org", QSettings::NativeFormat).clear();
    QSettings("HKEY_CURRENT_USER\\Software\\foo", QSettings::NativeFormat).clear();
    QSettings("HKEY_CURRENT_USER\\Software\\bar", QSettings::NativeFormat).clear();
    QSettings("HKEY_CURRENT_USER\\Software\\bat", QSettings::NativeFormat).clear();
    QSettings("HKEY_CURRENT_USER\\Software\\baz", QSettings::NativeFormat).clear();
    if (m_canWriteNativeSystemSettings) {
        QSettings("HKEY_LOCAL_MACHINE\\Software\\software.org", QSettings::NativeFormat).clear();
        QSettings("HKEY_LOCAL_MACHINE\\Software\\other.software.org", QSettings::NativeFormat).clear();
        QSettings("HKEY_LOCAL_MACHINE\\Software\\foo", QSettings::NativeFormat).clear();
        QSettings("HKEY_LOCAL_MACHINE\\Software\\bar", QSettings::NativeFormat).clear();
        QSettings("HKEY_LOCAL_MACHINE\\Software\\bat", QSettings::NativeFormat).clear();
        QSettings("HKEY_LOCAL_MACHINE\\Software\\baz", QSettings::NativeFormat).clear();
    }
#elif defined(Q_OS_DARWIN) || defined(Q_OS_WINRT)
    QSettings(QSettings::UserScope, "software.org", "KillerAPP").clear();
    QSettings(QSettings::SystemScope, "software.org", "KillerAPP").clear();
    QSettings(QSettings::UserScope, "other.software.org", "KillerAPP").clear();
    QSettings(QSettings::SystemScope, "other.software.org", "KillerAPP").clear();
    QSettings(QSettings::UserScope, "software.org").clear();
    QSettings(QSettings::SystemScope, "software.org").clear();
    QSettings(QSettings::UserScope, "other.software.org").clear();
    QSettings(QSettings::SystemScope, "other.software.org").clear();
#endif

    const QString foo(QLatin1String("foo"));

#if defined(Q_OS_WINRT)
    QSettings(foo, QSettings::NativeFormat).clear();
    QFile fooFile(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + QLatin1Char('/') + foo);
#else
    QFile fooFile(foo);
#endif
    if (fooFile.exists())
        QVERIFY2(fooFile.remove(), qPrintable(fooFile.errorString()));
}

/*
    Test the constructors and the assignment operator.
*/

void tst_QSettings::ctor_data()
{
    populateWithFormats();
}

void tst_QSettings::ctor()
{
    QFETCH(QSettings::Format, format);

    if (!m_canWriteNativeSystemSettings && format == QSettings::NativeFormat)
        QSKIP(insufficientPermissionSkipMessage);

    {
        QSettings settings1(format, QSettings::UserScope, "software.org", "KillerAPP");
        QSettings settings2(format, QSettings::UserScope, "software.org");
        QSettings settings3(format, QSettings::SystemScope, "software.org", "KillerAPP");
        QSettings settings4(format, QSettings::SystemScope, "software.org");

        QSettings settings5(format, QSettings::UserScope, "software.org", "KillerAPP");
        QSettings settings6(format, QSettings::UserScope, "software.org");
        QSettings settings7(format, QSettings::SystemScope, "software.org", "KillerAPP");
        QSettings settings8(format, QSettings::SystemScope, "software.org");

        // test QSettings::format() while we're at it
        QCOMPARE(settings1.format(), format);
        QCOMPARE(settings2.format(), format);
        QCOMPARE(settings3.format(), format);
        QCOMPARE(settings4.format(), format);

        // test QSettings::scope() while we're at it
        QCOMPARE(settings1.scope(), QSettings::UserScope);
        QCOMPARE(settings2.scope(), QSettings::UserScope);
        QCOMPARE(settings3.scope(), QSettings::SystemScope);
        QCOMPARE(settings4.scope(), QSettings::SystemScope);

        // test QSettings::organizationName() while we're at it
        QCOMPARE(settings1.organizationName(), QLatin1String("software.org"));
        QCOMPARE(settings2.organizationName(), QLatin1String("software.org"));
        QCOMPARE(settings3.organizationName(), QLatin1String("software.org"));
        QCOMPARE(settings4.organizationName(), QLatin1String("software.org"));

        // test QSettings::applicationName() while we're at it
        QCOMPARE(settings1.applicationName(), QString("KillerAPP"));
        QVERIFY(settings2.applicationName().isEmpty());
        QCOMPARE(settings3.applicationName(), QLatin1String("KillerAPP"));
        QVERIFY(settings4.applicationName().isEmpty());

        /*
            Go forwards.
        */
        settings4.setValue("key 1", QString("doodah"));
        QCOMPARE(settings1.value("key 1").toString(), QString("doodah"));
        QCOMPARE(settings2.value("key 1").toString(), QString("doodah"));
        QCOMPARE(settings3.value("key 1").toString(), QString("doodah"));
        QCOMPARE(settings4.value("key 1").toString(), QString("doodah"));

        settings3.setValue("key 1", QString("blah"));
        QCOMPARE(settings1.value("key 1").toString(), QString("blah"));
        QCOMPARE(settings2.value("key 1").toString(), QString("doodah"));
        QCOMPARE(settings3.value("key 1").toString(), QString("blah"));
        QCOMPARE(settings4.value("key 1").toString(), QString("doodah"));

        settings2.setValue("key 1", QString("whoa"));
        QCOMPARE(settings1.value("key 1").toString(), QString("whoa"));
        QCOMPARE(settings2.value("key 1").toString(), QString("whoa"));
        QCOMPARE(settings3.value("key 1").toString(), QString("blah"));
        QCOMPARE(settings4.value("key 1").toString(), QString("doodah"));

        settings1.setValue("key 1", QString("gurgle"));
        QCOMPARE(settings1.value("key 1").toString(), QString("gurgle"));
        QCOMPARE(settings2.value("key 1").toString(), QString("whoa"));
        QCOMPARE(settings3.value("key 1").toString(), QString("blah"));
        QCOMPARE(settings4.value("key 1").toString(), QString("doodah"));

        /*
            Test the copies.
        */
        QCOMPARE(settings5.value("key 1").toString(), settings1.value("key 1").toString());
        QCOMPARE(settings6.value("key 1").toString(), settings2.value("key 1").toString());
        QCOMPARE(settings7.value("key 1").toString(), settings3.value("key 1").toString());
        QCOMPARE(settings8.value("key 1").toString(), settings4.value("key 1").toString());

        /*
            Go backwards.
        */

        settings2.setValue("key 1", QString("bilboh"));
        QCOMPARE(settings1.value("key 1").toString(), QString("gurgle"));
        QCOMPARE(settings2.value("key 1").toString(), QString("bilboh"));
        QCOMPARE(settings3.value("key 1").toString(), QString("blah"));
        QCOMPARE(settings4.value("key 1").toString(), QString("doodah"));

        settings3.setValue("key 1", QString("catha"));
        QCOMPARE(settings1.value("key 1").toString(), QString("gurgle"));
        QCOMPARE(settings2.value("key 1").toString(), QString("bilboh"));
        QCOMPARE(settings3.value("key 1").toString(), QString("catha"));
        QCOMPARE(settings4.value("key 1").toString(), QString("doodah"));

        settings4.setValue("key 1", QString("quirko"));
        QCOMPARE(settings1.value("key 1").toString(), QString("gurgle"));
        QCOMPARE(settings2.value("key 1").toString(), QString("bilboh"));
        QCOMPARE(settings3.value("key 1").toString(), QString("catha"));
        QCOMPARE(settings4.value("key 1").toString(), QString("quirko"));

        /*
            Test the copies again.
        */
        QCOMPARE(settings5.value("key 1").toString(), settings1.value("key 1").toString());
        QCOMPARE(settings6.value("key 1").toString(), settings2.value("key 1").toString());
        QCOMPARE(settings7.value("key 1").toString(), settings3.value("key 1").toString());
        QCOMPARE(settings8.value("key 1").toString(), settings4.value("key 1").toString());

        /*
            "General" is a problem key for .ini files.
        */
        settings1.setValue("General", 1);
        settings1.setValue("%General", 2);
        settings1.setValue("alpha", 3);
        settings1.setValue("General/alpha", 4);
        settings1.setValue("%General/alpha", 5);
        settings1.setValue("alpha/General", 6);
        settings1.setValue("alpha/%General", 7);
        settings1.setValue("General/General", 8);
        settings1.setValue("General/%General", 9);
        settings1.setValue("%General/General", 10);
        settings1.setValue("%General/%General", 11);
    }

    {
        /*
            Test that the data was stored on disk after all instances
            of QSettings are destroyed.
        */

        QSettings settings1(format, QSettings::UserScope, "software.org", "KillerAPP");
        QSettings settings2(format, QSettings::UserScope, "software.org");
        QSettings settings3(format, QSettings::SystemScope, "software.org", "KillerAPP");
        QSettings settings4(format, QSettings::SystemScope, "software.org");

        QCOMPARE(settings1.value("key 1").toString(), QString("gurgle"));
        QCOMPARE(settings2.value("key 1").toString(), QString("bilboh"));
        QCOMPARE(settings3.value("key 1").toString(), QString("catha"));
        QCOMPARE(settings4.value("key 1").toString(), QString("quirko"));

        /*
            Test problem keys.
        */

        QCOMPARE(settings1.value("General").toInt(), 1);
        QCOMPARE(settings1.value("%General").toInt(), 2);
        QCOMPARE(settings1.value("alpha").toInt(), 3);
        QCOMPARE(settings1.value("General/alpha").toInt(), 4);
        QCOMPARE(settings1.value("%General/alpha").toInt(), 5);
        QCOMPARE(settings1.value("alpha/General").toInt(), 6);
        QCOMPARE(settings1.value("alpha/%General").toInt(), 7);
        QCOMPARE(settings1.value("General/General").toInt(), 8);
        QCOMPARE(settings1.value("General/%General").toInt(), 9);
        QCOMPARE(settings1.value("%General/General").toInt(), 10);
        QCOMPARE(settings1.value("%General/%General").toInt(), 11);

        /*
            Test that the organization and product parameters are
            case-insensitive on case-insensitive file systems.
        */
        QSettings settings5(format, QSettings::UserScope, "SoftWare.ORG", "killerApp");

        bool caseSensitive = true;
#if defined(Q_OS_MAC)
        if (format == QSettings::NativeFormat) {
            // more details in QMacSettingsPrivate::QMacSettingsPrivate(), organization was comify()-ed
            caseSensitive = settings5.fileName().contains("SoftWare.ORG");;
        } else {
            caseSensitive = pathconf(settings5.fileName().toLatin1().constData(), _PC_CASE_SENSITIVE);
        }
#elif defined(Q_OS_WIN32) || defined(Q_OS_WINRT)
        caseSensitive = false;
#endif
        if (caseSensitive)
            QVERIFY(!settings5.contains("key 1"));
        else
            QVERIFY(settings5.contains("key 1"));
    }

    {
        QSettings settings1(settingsPath("custom/custom.conf"), QSettings::IniFormat);
        settings1.beginGroup("alpha/beta");
        settings1.setValue("geometry", -7);
        settings1.setValue("geometry/x", 1);
        settings1.setValue("geometry/y", 2);
        QSettings settings2(settingsPath("custom/custom.conf"), QSettings::IniFormat);
        settings1.setValue("geometry/width", 3);
        settings2.setValue("alpha/beta/geometry/height", 4);
        settings2.setValue("alpha/gamma/splitter", 5);
        settings1.endGroup();

        // test QSettings::scope() while we're at it
        QCOMPARE(settings1.scope(), QSettings::UserScope);

        // test QSettings::organizationName() while we're at it
        QVERIFY(settings1.organizationName().isEmpty());

        // test QSettings::applicationName() while we're at it
        QVERIFY(settings1.organizationName().isEmpty());

        QSettings settings3(settingsPath("custom/custom2.conf"), QSettings::IniFormat);
        settings3.beginGroup("doodley/beta");
        settings3.setValue("geometry", -7);
        settings3.setValue("geometry/x", 1);
        settings3.setValue("geometry/y", 2);
        settings3.setValue("geometry/width", 3);
        settings3.setValue("geometry/height", 4);
        settings3.endGroup();
        settings3.setValue("alpha/gamma/splitter", 5);

        QCOMPARE(settings1.value("alpha/beta/geometry").toInt(), -7);
        QCOMPARE(settings1.value("alpha/beta/geometry/x").toInt(), 1);
        QCOMPARE(settings1.value("alpha/beta/geometry/y").toInt(), 2);
        QCOMPARE(settings1.value("alpha/beta/geometry/width").toInt(), 3);
        QCOMPARE(settings1.value("alpha/beta/geometry/height").toInt(), 4);
        QCOMPARE(settings1.value("alpha/gamma/splitter").toInt(), 5);
        QCOMPARE(settings1.allKeys().count(), 6);

        QCOMPARE(settings2.value("alpha/beta/geometry").toInt(), -7);
        QCOMPARE(settings2.value("alpha/beta/geometry/x").toInt(), 1);
        QCOMPARE(settings2.value("alpha/beta/geometry/y").toInt(), 2);
        QCOMPARE(settings2.value("alpha/beta/geometry/width").toInt(), 3);
        QCOMPARE(settings2.value("alpha/beta/geometry/height").toInt(), 4);
        QCOMPARE(settings2.value("alpha/gamma/splitter").toInt(), 5);
        QCOMPARE(settings2.allKeys().count(), 6);
    }

    {
        QSettings settings1(settingsPath("custom/custom.conf"), QSettings::IniFormat);
        QCOMPARE(settings1.value("alpha/beta/geometry").toInt(), -7);
        QCOMPARE(settings1.value("alpha/beta/geometry/x").toInt(), 1);
        QCOMPARE(settings1.value("alpha/beta/geometry/y").toInt(), 2);
        QCOMPARE(settings1.value("alpha/beta/geometry/width").toInt(), 3);
        QCOMPARE(settings1.value("alpha/beta/geometry/height").toInt(), 4);
        QCOMPARE(settings1.value("alpha/gamma/splitter").toInt(), 5);
        QCOMPARE(settings1.allKeys().count(), 6);
    }

    {
        // QSettings's default constructor is native by default
        if (format == QSettings::NativeFormat) {
            QCoreApplication::instance()->setOrganizationName("");
            QCoreApplication::instance()->setApplicationName("");
            QSettings settings;
#if defined(Q_OS_MAC) || defined(Q_OS_WINRT)
            QEXPECT_FAIL("native", "Default settings on Mac/WinRT are valid, despite organization domain, name, and app name being null", Continue);
#endif
            QCOMPARE(settings.status(), QSettings::AccessError);
            QCoreApplication::instance()->setOrganizationName("software.org");
            QCoreApplication::instance()->setApplicationName("KillerAPP");
            QSettings settings2;
            QCOMPARE(settings2.status(), QSettings::NoError);
            QSettings settings3("software.org", "KillerAPP");
            QCOMPARE(settings2.fileName(), settings3.fileName());
            QCoreApplication::instance()->setOrganizationName("");
            QCoreApplication::instance()->setApplicationName("");
        }

        QSettings settings(format, QSettings::UserScope, "", "");
#if defined(Q_OS_MAC) || defined(Q_OS_WINRT)
        QEXPECT_FAIL("native", "Default settings on Mac/WinRT are valid, despite organization domain, name, and app name being null", Continue);
#endif
        QCOMPARE(settings.status(), QSettings::AccessError);
        QSettings settings2(format, QSettings::UserScope, "software.org", "KillerAPP");
        QCOMPARE(settings2.status(), QSettings::NoError);

        // test QSettings::format() while we're at it
        QCOMPARE(settings.format(), format);
        QCOMPARE(settings2.format(), format);

        // test QSettings::scope() while we're at it
        QCOMPARE(settings.scope(), QSettings::UserScope);
        QCOMPARE(settings2.scope(), QSettings::UserScope);

        // test QSettings::organizationName() while we're at it
        QVERIFY(settings.organizationName().isEmpty());
        QCOMPARE(settings2.organizationName(), QLatin1String("software.org"));

        // test QSettings::applicationName() while we're at it
        QVERIFY(settings.applicationName().isEmpty());
        QCOMPARE(settings2.applicationName(), QLatin1String("KillerAPP"));
    }
}

void tst_QSettings::testByteArray_data()
{
    QTest::addColumn<QByteArray>("data");

    QByteArray bytes("Hello world!");

    QTest::newRow("latin1") << bytes;
#ifndef QT_NO_COMPRESS
    QTest::newRow("compressed") << qCompress(bytes);
#endif
}

void tst_QSettings::testByteArray()
{
    QFETCH(QByteArray, data);

    // write
    {
        QSettings settings("QtProject", "tst_qsettings");
        settings.setValue("byteArray", data);
    }
    // read
    {
        QSettings settings("QtProject", "tst_qsettings");
        QByteArray ret = settings.value("byteArray", data).toByteArray();
        QCOMPARE(ret, data);
    }
}

void tst_QSettings::testByteArrayNativeFormat()
{
#ifndef Q_OS_MACOS
    QSKIP("This test is specific to macOS plist reading.");
#else
    QSettings settings(":/resourcefile6.plist", QSettings::NativeFormat);
    QCOMPARE(settings.value("passwordData"), QVariant(QByteArray::fromBase64("RBxVAAsDVsO/")));
#endif
}

void tst_QSettings::iniCodec()
{
    {
        QSettings settings("QtProject", "tst_qsettings");
        settings.setIniCodec("cp1251");
        QByteArray ba;
        ba.resize(256);
        for (int i = 0; i < ba.size(); i++)
            ba[i] = i;
        settings.setValue("array",ba);
    }
    {
        QSettings settings("QtProject", "tst_qsettings");
        settings.setIniCodec("cp1251");
        QByteArray ba = settings.value("array").toByteArray();
        QCOMPARE(ba.size(), 256);
        for (int i = 0; i < ba.size(); i++)
            QCOMPARE((uchar)ba.at(i), (uchar)i);
    }

}

void tst_QSettings::bom()
{
    QSettings s(":/bom.ini", QSettings::IniFormat);
    QStringList allkeys = s.allKeys();
    QCOMPARE(allkeys.size(), 2);
    QVERIFY(allkeys.contains("section1/foo1"));
    QVERIFY(allkeys.contains("section2/foo2"));
}

void tst_QSettings::embeddedZeroByte_data()
{
    QTest::addColumn<QVariant>("value");

    QByteArray bytes("hello\0world", 11);

    QTest::newRow("bytearray\\0") << QVariant(bytes);
    QTest::newRow("string\\0") << QVariant(QString::fromLatin1(bytes.data(), bytes.size()));

    bytes = QByteArray("@String(");

    QTest::newRow("@bytearray") << QVariant(bytes);
    QTest::newRow("@string") << QVariant(QString(bytes));

    bytes = QByteArray("@String(\0test", 13);

    QTest::newRow("@bytearray\\0") << QVariant(bytes);
    QTest::newRow("@string\\0") << QVariant(QString::fromLatin1(bytes.data(), bytes.size()));
}

void tst_QSettings::embeddedZeroByte()
{
    QFETCH(QVariant, value);
    {
        QSettings settings("QtProject", "tst_qsettings");
        settings.setValue(QTest::currentDataTag(), value);
    }
    {
        QSettings settings("QtProject", "tst_qsettings");
        QVariant outValue = settings.value(QTest::currentDataTag());

        switch (value.type()) {
        case QVariant::ByteArray:
            QCOMPARE(outValue.toByteArray(), value.toByteArray());
            break;
        case QVariant::String:
            QCOMPARE(outValue.toString(), value.toString());
            break;
        default:
            Q_UNREACHABLE();
        }

        if (value.toByteArray().contains(QChar::Null))
            QVERIFY(outValue.toByteArray().contains(QChar::Null));
    }
}

void tst_QSettings::spaceAfterComment()
{
    QSettings settings(QFINDTESTDATA("withcomments.ini"), QSettings::IniFormat);
    QCOMPARE(settings.status(), QSettings::NoError);

    QStringList groups = settings.childGroups();
    QVERIFY(groups.contains("Regular"));
    QVERIFY(groups.contains("WithSpaces"));
    QVERIFY(groups.contains("WithTab"));
    QVERIFY(groups.contains("SpacedGroup"));

    settings.beginGroup("Regular");
    QCOMPARE(settings.value("bar"), QVariant(2));
    settings.endGroup();

    settings.beginGroup("WithSpaces");
    QCOMPARE(settings.value("bar"), QVariant(4));
    settings.endGroup();

    settings.beginGroup("WithTab");
    QCOMPARE(settings.value("bar"), QVariant(6));
    settings.endGroup();

    settings.beginGroup("SpacedGroup");
    QCOMPARE(settings.value("bar"), QVariant(7));
    settings.endGroup();
}

void tst_QSettings::testErrorHandling_data()
{
    QTest::addColumn<int>("filePerms"); // -1 means file should not exist
    QTest::addColumn<int>("dirPerms");
    QTest::addColumn<int>("statusAfterCtor");
    QTest::addColumn<bool>("shouldBeEmpty");
    QTest::addColumn<int>("statusAfterGet");
    QTest::addColumn<int>("statusAfterSetAndSync");

    //                         file    dir     afterCtor                      empty     afterGet                      afterSetAndSync
    QTest::newRow("0600 0700") << 0600 << 0700 << (int)QSettings::NoError     << false << (int)QSettings::NoError     << (int)QSettings::NoError;

    QTest::newRow("0400 0700") << 0400 << 0700 << (int)QSettings::NoError
        << false << (int)QSettings::NoError     << (int)QSettings::AccessError;
    QTest::newRow("0200 0700") << 0200 << 0700 << (int)QSettings::AccessError
        << true  << (int)QSettings::AccessError << (int)QSettings::AccessError;

    QTest::newRow("  -1 0700") <<   -1 << 0700 << (int)QSettings::NoError     << true  << (int)QSettings::NoError     << (int)QSettings::NoError;

    QTest::newRow("  -1 0000") <<   -1 << 0000 << (int)QSettings::NoError     << true  << (int)QSettings::NoError     << (int)QSettings::AccessError;
    QTest::newRow("  -1 0100") <<   -1 << 0100 << (int)QSettings::NoError     << true  << (int)QSettings::NoError     << (int)QSettings::AccessError;
    QTest::newRow("0600 0100") << 0600 << 0100 << (int)QSettings::NoError     << false << (int)QSettings::NoError     << (int)QSettings::NoError;
    QTest::newRow("  -1 0300") <<   -1 << 0300 << (int)QSettings::NoError     << true  << (int)QSettings::NoError     << (int)QSettings::NoError;
    QTest::newRow("0600 0300") << 0600 << 0300 << (int)QSettings::NoError     << false << (int)QSettings::NoError     << (int)QSettings::NoError;
    QTest::newRow("  -1 0500") <<   -1 << 0500 << (int)QSettings::NoError     << true  << (int)QSettings::NoError     << (int)QSettings::AccessError;
    QTest::newRow("0600 0500") << 0600 << 0500 << (int)QSettings::NoError     << false << (int)QSettings::NoError     << (int)QSettings::NoError;
}

void tst_QSettings::testErrorHandling()
{
#ifdef Q_OS_WIN
    QSKIP("Windows doesn't support most file modes, including read-only directories, so this test is moot.");
#elif defined(Q_OS_UNIX)
#if !defined(Q_OS_VXWORKS)  // VxWorks does not have users/groups
    if (::getuid() == 0)
#endif
        QSKIP("Running this test as root doesn't work, since file perms do not bother him");
#else
    QFETCH(int, filePerms);
    QFETCH(int, dirPerms);
    QFETCH(int, statusAfterCtor);
    QFETCH(bool, shouldBeEmpty);
    QFETCH(int, statusAfterGet);
    QFETCH(int, statusAfterSetAndSync);

    system(QString("chmod 700 %1 2>/dev/null").arg(settingsPath("someDir")).toLatin1());
    system(QString("chmod -R u+rwx %1 2>/dev/null").arg(settingsPath("someDir")).toLatin1());
    system(QString("rm -fr %1").arg(settingsPath("someDir")).toLatin1());

    // prepare a file with some settings
    if (filePerms != -1) {
        QSettings settings(settingsPath("someDir/someSettings.ini"), QSettings::IniFormat);
        QCOMPARE((int) settings.status(), (int) QSettings::NoError);

        settings.beginGroup("alpha/beta");
        settings.setValue("geometry", -7);
        settings.setValue("geometry/x", 1);
        settings.setValue("geometry/y", 2);
        settings.setValue("geometry/width", 3);
        settings.setValue("geometry/height", 4);
        settings.endGroup();
        settings.setValue("alpha/gamma/splitter", 5);
    } else {
        system(QString("mkdir -p %1").arg(settingsPath("someDir")).toLatin1());
    }

    if (filePerms != -1) {
        system(QString("chmod %1 %2")
                    .arg(QString::number(filePerms, 8))
                    .arg(settingsPath("someDir/someSettings.ini"))
                    .toLatin1());
    }
    system(QString("chmod %1 %2")
                .arg(QString::number(dirPerms, 8))
                .arg(settingsPath("someDir"))
                .toLatin1());

    // the test
    {
        QConfFile::clearCache();
        QSettings settings(settingsPath("someDir/someSettings.ini"), QSettings::IniFormat);
        QCOMPARE((int)settings.status(), statusAfterCtor);
        if (shouldBeEmpty) {
            QCOMPARE(settings.allKeys().count(), 0);
        } else {
            QVERIFY(settings.allKeys().count() > 0);
        }
        settings.value("alpha/beta/geometry");
        QCOMPARE((int)settings.status(), statusAfterGet);
        settings.setValue("alpha/beta/geometry", 100);
        QCOMPARE((int)settings.status(), statusAfterGet);
        QCOMPARE(settings.value("alpha/beta/geometry").toInt(), 100);
        settings.sync();
        QCOMPARE(settings.value("alpha/beta/geometry").toInt(), 100);
        QCOMPARE((int)settings.status(), statusAfterSetAndSync);
    }
#endif // !Q_OS_WIN
}

Q_DECLARE_METATYPE(QSettings::Status)

#ifdef QT_BUILD_INTERNAL
void tst_QSettings::testIniParsing_data()
{
    QTest::addColumn<QByteArray>("inicontent");
    QTest::addColumn<QString>("key");
    QTest::addColumn<QVariant>("expect");
    QTest::addColumn<QSettings::Status>("status");

    // Test "forgiving" parsing of entries not terminated with newline or unterminated strings
    QTest::newRow("good1")    << QByteArray("v=1\n")          << "v" << QVariant(1) << QSettings::NoError;
    QTest::newRow("good2")    << QByteArray("v=1\\\n2")       << "v" << QVariant(12) << QSettings::NoError;
    QTest::newRow("good3")    << QByteArray("v=1\\\r2")       << "v" << QVariant(12) << QSettings::NoError;
    QTest::newRow("good4")    << QByteArray("v=1\\\n\r2")     << "v" << QVariant(12) << QSettings::NoError;
    QTest::newRow("good5")    << QByteArray("v=1\\\r\n2")     << "v" << QVariant(12) << QSettings::NoError;
    QTest::newRow("good6")    << QByteArray("v  \t = \t 1\\\r\n2")     << "v" << QVariant(12) << QSettings::NoError;
    QTest::newRow("garbage1") << QByteArray("v")              << "v" << QVariant() << QSettings::FormatError;
    QTest::newRow("nonterm1") << QByteArray("v=str")          << "v" << QVariant("str") << QSettings::NoError;
    QTest::newRow("nonterm2") << QByteArray("v=\"str\"")      << "v" << QVariant("str") << QSettings::NoError;
    QTest::newRow("nonterm3") << QByteArray("v=\"str")        << "v" << QVariant("str") << QSettings::NoError;
    QTest::newRow("nonterm4") << QByteArray("v=\\")           << "v" << QVariant("") << QSettings::NoError;
    QTest::newRow("nonterm5") << QByteArray("u=s\nv=\"str")   << "v" << QVariant("str") << QSettings::NoError;
    QTest::newRow("nonterm6") << QByteArray("v=\"str\nw=ok")  << "v" << QVariant("str\nw=ok") << QSettings::NoError;
    QTest::newRow("nonterm7") << QByteArray("v=")             << "v" << QVariant("") << QSettings::NoError;
    QTest::newRow("nonterm8") << QByteArray("v=\"str\njnk")   << "v" << QVariant("str\njnk") << QSettings::NoError;
    QTest::newRow("nonterm9") << QByteArray("v=1\\")          << "v" << QVariant(1) << QSettings::NoError;
    QTest::newRow("nonterm10") << QByteArray("v=1\\\n")       << "v" << QVariant(1) << QSettings::NoError;
    QTest::newRow("nonterm11") << QByteArray("v=1\\\r")       << "v" << QVariant(1) << QSettings::NoError;
    QTest::newRow("nonterm12") << QByteArray("v=1\\\n\r")     << "v" << QVariant(1) << QSettings::NoError;
    QTest::newRow("nonterm13") << QByteArray("v=1\\\r\n")     << "v" << QVariant(1) << QSettings::NoError;
    QTest::newRow("nonterm14") << QByteArray("v=1\\\n\nx=2")  << "v" << QVariant(1) << QSettings::NoError;
    QTest::newRow("nonterm15") << QByteArray("v=1\\\r\rx=2")  << "v" << QVariant(1) << QSettings::NoError;
    QTest::newRow("nonterm16") << QByteArray("v=1\\\n\n\nx=2") << "v" << QVariant(1) << QSettings::NoError;
    QTest::newRow("nonterm17") << QByteArray("; foo\nv=1") << "v" << QVariant(1) << QSettings::NoError;
    QTest::newRow("nonterm18") << QByteArray("; foo\n\nv=1") << "v" << QVariant(1) << QSettings::NoError;
    QTest::newRow("nonterm19") << QByteArray("\nv=1;foo") << "v" << QVariant(1) << QSettings::NoError;
    QTest::newRow("nonterm20") << QByteArray("v=x ") << "v" << QVariant("x") << QSettings::NoError;
    QTest::newRow("nonterm21") << QByteArray("v=x ;") << "v" << QVariant("x") << QSettings::NoError;
}
#endif

#ifdef QT_BUILD_INTERNAL
void tst_QSettings::testIniParsing()
{
    qRegisterMetaType<QSettings::Status>("QSettings::Status");

    QDir dir(settingsPath());
    QVERIFY(dir.mkpath("someDir"));
    QFile f(dir.path()+"/someDir/someSettings.ini");

    QFETCH(QByteArray, inicontent);
    QFETCH(QString, key);
    QFETCH(QVariant, expect);
    QFETCH(QSettings::Status, status);

    QVERIFY(f.open(QFile::WriteOnly));
    f.write(inicontent);
    f.close();

    QConfFile::clearCache();
    QSettings settings(settingsPath("someDir/someSettings.ini"), QSettings::IniFormat);

    if ( settings.status() == QSettings::NoError ) { // else no point proceeding
        QVariant v = settings.value(key);
        QVERIFY(v.canConvert(expect.type()));
        // check some types so as to give prettier error messages
        if ( v.type() == QVariant::String ) {
            QCOMPARE(v.toString(), expect.toString());
        } else if ( v.type() == QVariant::Int ) {
            QCOMPARE(v.toInt(), expect.toInt());
        } else {
            QCOMPARE(v, expect);
        }
    }

    QCOMPARE(settings.status(), status);
}
#endif

/*
    Tests beginGroup(), endGroup(), and group().
*/
void tst_QSettings::beginGroup()
{
    QSettings settings1(QSettings::UserScope, "software.org", "KillerAPP");
    QSettings settings2(QSettings::UserScope, "software.org", "KillerAPP");

    /*
      Let's start with some back and forthing.
    */

    settings1.beginGroup("alpha");
    QCOMPARE(settings1.group(), QString("alpha"));
    settings1.endGroup();
    QCOMPARE(settings1.group(), QString());
    settings1.beginGroup("/beta");
    QCOMPARE(settings1.group(), QString("beta"));
    settings1.endGroup();
    QCOMPARE(settings1.group(), QString());

    settings1.beginGroup("///gamma//");
    QCOMPARE(settings1.group(), QString("gamma"));
    settings1.endGroup();
    QCOMPARE(settings1.group(), QString());

    settings1.setValue("geometry", 5);
    QCOMPARE(settings1.value("geometry").toInt(), 5);
    QCOMPARE(settings1.value("/geometry///").toInt(), 5);
    QCOMPARE(settings2.value("geometry").toInt(), 5);
    QCOMPARE(settings2.value("/geometry///").toInt(), 5);

    /*
      OK, now start for real.
    */

    settings1.beginGroup("alpha");
    QCOMPARE(settings1.group(), QString("alpha"));
    settings1.setValue("geometry", 66);
    QCOMPARE(settings1.value("geometry").toInt(), 66);
    QCOMPARE(settings2.value("geometry").toInt(), 5);
    QCOMPARE(settings2.value("alpha/geometry").toInt(), 66);

    QSettings settings3(QSettings::UserScope, "software.org", "KillerAPP");
    settings3.beginGroup("alpha");
    QCOMPARE(settings3.value("geometry").toInt(), 66);

    settings1.beginGroup("/beta///");
    QCOMPARE(settings1.group(), QString("alpha/beta"));
    settings1.setValue("geometry", 777);
    QCOMPARE(settings1.value("geometry").toInt(), 777);
    QCOMPARE(settings2.value("geometry").toInt(), 5);
    QCOMPARE(settings2.value("alpha/geometry").toInt(), 66);
    QCOMPARE(settings2.value("alpha/beta/geometry").toInt(), 777);
    QCOMPARE(settings3.value("geometry").toInt(), 66);
    QCOMPARE(settings3.value("beta/geometry").toInt(), 777);

    settings3.beginGroup("gamma");
    settings3.setValue("geometry", 8888);
    QCOMPARE(settings3.value("geometry").toInt(), 8888);
    QCOMPARE(settings2.value("geometry").toInt(), 5);
    QCOMPARE(settings2.value("alpha/geometry").toInt(), 66);
    QCOMPARE(settings2.value("alpha/beta/geometry").toInt(), 777);
    QCOMPARE(settings2.value("alpha/gamma/geometry").toInt(), 8888);
    QCOMPARE(settings1.value("geometry").toInt(), 777);

    // endGroup() should do nothing if group() is empty
    for (int i = 0; i < 10; ++i) {
        QTest::ignoreMessage(QtWarningMsg, "QSettings::endGroup: No matching beginGroup()");
        settings2.endGroup();
    }
    QCOMPARE(settings2.value("geometry").toInt(), 5);
    QCOMPARE(settings2.value("alpha/geometry").toInt(), 66);
    QCOMPARE(settings2.value("alpha/beta/geometry").toInt(), 777);
    QCOMPARE(settings2.value("alpha/gamma/geometry").toInt(), 8888);

    QCOMPARE(settings1.group(), QString("alpha/beta"));
    settings1.endGroup();
    QCOMPARE(settings1.group(), QString("alpha"));
    settings1.endGroup();
    QCOMPARE(settings1.group(), QString());
    QCOMPARE(settings1.value("geometry").toInt(), 5);
    QCOMPARE(settings1.value("alpha/geometry").toInt(), 66);
    QCOMPARE(settings1.value("alpha/beta/geometry").toInt(), 777);
    QCOMPARE(settings1.value("alpha/gamma/geometry").toInt(), 8888);

    settings1.beginGroup("delta");
    QCOMPARE(settings1.group(), QString("delta"));
    settings1.beginGroup("");
    QCOMPARE(settings1.group(), QString("delta"));
    settings1.beginGroup("/");
    QCOMPARE(settings1.group(), QString("delta"));
    settings1.beginGroup("////");
    QCOMPARE(settings1.group(), QString("delta"));
    settings1.beginGroup("////omega///epsilon zeta eta  theta/ / /");
    QCOMPARE(settings1.group(), QString("delta/omega/epsilon zeta eta  theta/ / "));
    settings1.endGroup();
    QCOMPARE(settings1.group(), QString("delta"));
    settings1.endGroup();
    QCOMPARE(settings1.group(), QString("delta"));
    settings1.endGroup();
    QCOMPARE(settings1.group(), QString("delta"));
    settings1.endGroup();
    QCOMPARE(settings1.group(), QString("delta"));
    settings1.endGroup();
    QCOMPARE(settings1.group(), QString());
}

/*
    Tests setValue() and getXxx().
*/
void tst_QSettings::setValue()
{
    QSettings settings(QSettings::UserScope, "software.org", "KillerAPP");

    settings.setValue("key 2", (int)0x7fffffff);
    QCOMPARE(settings.value("key 2").toInt(), (int)0x7fffffff);
    QCOMPARE(settings.value("key 2").toString(), QString::number((int)0x7fffffff));
    settings.setValue("key 2", -1);
    QCOMPARE(settings.value("key 2").toInt(), -1);
    QCOMPARE(settings.value("key 2").toString(), QString("-1"));
    settings.setValue("key 2", (int)0x80000000);
    QCOMPARE(settings.value("key 2").toInt(), (int)0x80000000);
    settings.setValue("key 2", (int)0);
    QCOMPARE(settings.value("key 2", 123).toInt(), (int)0);
    settings.setValue("key 2", (int)12345);
    QCOMPARE(settings.value("key 2").toInt(), (int)12345);
    QCOMPARE(settings.value("no such key", 1234).toInt(), (int)1234);
    QCOMPARE(settings.value("no such key").toInt(), (int)0);

    settings.setValue("key 2", true);
    QCOMPARE(settings.value("key 2").toBool(), true);
    settings.setValue("key 2", false);
    QCOMPARE(settings.value("key 2", true).toBool(), false);
    settings.setValue("key 2", (int)1);
    QCOMPARE(settings.value("key 2").toBool(), true);
    settings.setValue("key 2", (int)-1);
    QCOMPARE(settings.value("key 2").toBool(), true);
    settings.setValue("key 2", (int)0);
    QCOMPARE(settings.value("key 2", true).toBool(), false);
    settings.setValue("key 2", QString("true"));
    QCOMPARE(settings.value("key 2").toBool(), true);
    settings.setValue("key 2", QString("false"));
    QCOMPARE(settings.value("key 2", true).toBool(), false);

    // The following block should not compile.
/*
    settings.setValue("key 2", "true");
    QCOMPARE(settings.value("key 2").toBool(), true);
    settings.setValue("key 2", "false");
    QCOMPARE(settings.value("key 2", true).toBool(), false);
    settings.setValue("key 2", "");
    QCOMPARE(settings.value("key 2", true).toBool(), true);
    settings.setValue("key 2", "");
    QCOMPARE(settings.value("key 2", false).toBool(), false);
    settings.setValue("key 2", "0.000e-00"); // cannot convert double to a bool
    QCOMPARE(settings.value("key 2", true).toBool(), true);
    settings.setValue("key 2", "0.000e-00");
    QCOMPARE(settings.value("key 2", false).toBool(), false);
*/

    settings.setValue("key 2", QStringList());
    QCOMPARE(settings.value("key 2").toStringList(), QStringList());
    settings.setValue("key 2", QStringList(""));
    QCOMPARE(settings.value("key 2").toStringList(), QStringList(""));
    settings.setValue("key 2", QStringList() << "" << "");
    QCOMPARE(settings.value("key 2").toStringList(), QStringList() << "" << "");
    settings.setValue("key 2", QStringList() << "" << "a" << "" << "bc" << "");
    QCOMPARE(settings.value("key 2").toStringList(), QStringList() << "" << "a" << "" << "bc" << "");

    settings.setValue("key 3", QList<QVariant>());
    QVERIFY(settings.value("key 3").toList().isEmpty());
    settings.setValue("key 3", QList<QVariant>() << 1 << QString("a"));
    QCOMPARE(settings.value("key 3").toList(), QList<QVariant>() << 1 << QString("a"));

    QList<QVariant> outerList;
    outerList << 1 << QString("b");
    QList<QVariant> innerList = outerList;
    outerList.append(QVariant(innerList));
    outerList.append(QVariant(innerList));
    outerList << 2 << QString("c");
    innerList = outerList;
    outerList.append(QVariant(innerList));
    // outerList: [1, "b", [1, "b"], [1, "b"], 2, "c", [1, "b", [1, "b"], [1, "b"], 2, "c"]]

    settings.setValue("key 3", outerList);
    QCOMPARE(settings.value("key 3").toList(), outerList);
    QCOMPARE(settings.value("key 3").toList().size(), 7);

    QMap<QString, QVariant> map;
    map.insert("1", "one");
    map.insert("2", "two");
    map.insert("3", outerList);
    map.insert("5", "cinco");
    map.insert("10", "zehn");
    settings.setValue("key 4", map);
    QCOMPARE(settings.value("key 4").toMap(), map);
}

#ifdef QT_BUILD_INTERNAL

template<int MetaTypeId>
static void testMetaTypesHelper(QSettings::Format format)
{
    typedef typename MetaEnumToType<MetaTypeId>::Type Type;
    const char *key = QMetaType::typeName(MetaTypeId);
    Type *value = TestValueFactory<MetaTypeId>::create();
    QVariant inputVariant = QVariant::fromValue(*value);

    static const QSettings::Scope scope = QSettings::UserScope;
    static const QString organization("example.org");
    static const QString applicationName("FooApp");

    {
        QSettings settings(format, scope, organization, applicationName);
        settings.setValue(key, inputVariant);
    }

    QConfFile::clearCache();

    {
        QSettings settings(format, scope, organization, applicationName);
        QVariant outputVariant = settings.value(key);
        if (MetaTypeId != QMetaType::QVariant)
            QVERIFY(outputVariant.canConvert(MetaTypeId));
        if (outputVariant.type() != inputVariant.type())
            qWarning() << "type mismatch between" << inputVariant << "and" << outputVariant;
        QCOMPARE(qvariant_cast<Type >(outputVariant), *value);
    }

    delete value;
}

#define FOR_EACH_NONSUPPORTED_METATYPE(F)\
    F(Void) \
    F(Nullptr) \
    F(QObjectStar) \
    F(QModelIndex) \
    F(QJsonObject) \
    F(QJsonValue) \
    F(QJsonArray) \
    F(QJsonDocument) \
    F(QPersistentModelIndex) \
    F(QCborSimpleType) \
    F(QCborValue) \
    F(QCborArray) \
    F(QCborMap) \

#define EXCLUDE_NON_SUPPORTED_METATYPES(MetaTypeName) \
template<> void testMetaTypesHelper<QMetaType::MetaTypeName>(QSettings::Format) \
{ \
    QSKIP("This metatype is not supported by QSettings."); \
}
FOR_EACH_NONSUPPORTED_METATYPE(EXCLUDE_NON_SUPPORTED_METATYPES)
#undef EXCLUDE_NON_SUPPORTED_METATYPES

void tst_QSettings::testMetaTypes_data()
{
    QTest::addColumn<QSettings::Format>("format");
    QTest::addColumn<int>("type");

#define ADD_METATYPE_TEST_ROW(MetaTypeName, MetaTypeId, RealType) \
    { \
        const char *formatName = QMetaEnum::fromType<QSettings::Format>().valueToKey(formats[i]); \
        const char *typeName = QMetaType::typeName(QMetaType::MetaTypeName); \
        QTest::newRow(QString("%1:%2").arg(formatName).arg(typeName).toLatin1().constData()) \
            << QSettings::Format(formats[i]) << int(QMetaType::MetaTypeName); \
    }
    int formats[] = { QSettings::NativeFormat, QSettings::IniFormat };
    for (int i = 0; i < int(sizeof(formats) / sizeof(int)); ++i) {
        FOR_EACH_CORE_METATYPE(ADD_METATYPE_TEST_ROW)
    }
#undef ADD_METATYPE_TEST_ROW
}

typedef void (*TypeTestFunction)(QSettings::Format);

void tst_QSettings::testMetaTypes()
{
    struct TypeTestFunctionGetter
    {
        static TypeTestFunction get(int type)
        {
            switch (type) {
#define RETURN_CREATE_FUNCTION(MetaTypeName, MetaTypeId, RealType) \
            case QMetaType::MetaTypeName: \
            return testMetaTypesHelper<QMetaType::MetaTypeName>;
FOR_EACH_CORE_METATYPE(RETURN_CREATE_FUNCTION)
#undef RETURN_CREATE_FUNCTION
            }
            return 0;
        }
    };

    QFETCH(QSettings::Format, format);
    QFETCH(int, type);

    TypeTestFunctionGetter::get(type)(format);
}

void tst_QSettings::testVariantTypes_data()
{
    populateWithFormats();
}
#endif

#ifdef QT_BUILD_INTERNAL
void tst_QSettings::testVariantTypes()
{
#define testVal(key, val, tp, rtype) \
    { \
        QSettings settings1(format, QSettings::UserScope, "software.org", "KillerAPP"); \
        settings1.setValue(key, QVariant::fromValue(val)); \
    } \
    QConfFile::clearCache(); \
    { \
        QSettings settings2(format, QSettings::UserScope, "software.org", "KillerAPP"); \
        QVariant v = settings2.value(key); \
        QVERIFY(qvariant_cast<tp >(v) == val); \
        QVERIFY(v.type() == QVariant::rtype); \
    }

    typedef QMap<QString, QVariant> TestVariantMap;

    QFETCH(QSettings::Format, format);

    TestVariantMap m2;
    m2.insert("ene", "due");
    m2.insert("rike", "fake");
    m2.insert("borba", "dorba");
    testVal("key2", m2, TestVariantMap, Map);

    QStringList l2;

    l2 << "ene" << "due" << "@Point(1 2)" << "@fake";
    testVal("key3", l2, QStringList, StringList);

    l2.clear();
    l2 << "ene" << "due" << "rike" << "fake";
    testVal("key3", l2, QStringList, StringList);

    QList<QVariant> l3;
    QDate date = QDate::currentDate();
    QTime time = QTime::currentTime();
    l3 << QString("ene") << 10 << QVariant::fromValue(QColor(1, 2, 3)) << QVariant(QRect(1, 2, 3, 4))
        << QVariant(QSize(4, 56)) << QVariant(QPoint(4, 2)) << true << false << date << time;
    testVal("key3", l3, QVariantList, List);

    testVal("key4", QString("hello"), QString, String);
    testVal("key5", QColor(1, 2, 3), QColor, Color);
    testVal("key6", QRect(1, 2, 3, 4), QRect, Rect);
    testVal("key7", QSize(4, 56), QSize, Size);
    testVal("key8", QPoint(4, 2), QPoint, Point);
    testVal("key10", date, QDate, Date);
    testVal("key11", time, QTime, Time);
    testVal("key12", QByteArray("foo bar"), QByteArray, ByteArray);

    {
        QSettings settings(format, QSettings::UserScope, "software.org", "KillerAPP");
        QVERIFY(!settings.contains("key99"));
        QCOMPARE(settings.value("key99"), QVariant());

        settings.setValue("key99", QVariant());
        QVERIFY(settings.contains("key99"));
        QCOMPARE(settings.value("key99"), QVariant());

        settings.setValue("key99", QVariant(1));
        QVERIFY(settings.contains("key99"));
        QCOMPARE(settings.value("key99"), QVariant(1));

        settings.setValue("key99", QVariant());
        QVERIFY(settings.contains("key99"));
        QCOMPARE(settings.value("key99"), QVariant());

        settings.remove("key99");
        QVERIFY(!settings.contains("key99"));
        QCOMPARE(settings.value("key99"), QVariant());
    }

    QList<QVariant> l4;
    l4 << QVariant(m2) << QVariant(l2) << QVariant(l3);
    testVal("key13", l4, QVariantList, List);
    QDateTime dt = QDateTime::currentDateTime();
    dt.setOffsetFromUtc(3600);
    testVal("key14", dt, QDateTime, DateTime);

    // We store key sequences as strings instead of binary variant blob, for improved
    // readability in the resulting format.
    if (format >= QSettings::InvalidFormat) {
        testVal("keysequence", QKeySequence(Qt::ControlModifier + Qt::Key_F1), QKeySequence, KeySequence);
    } else {
        testVal("keysequence",
                QKeySequence(Qt::ControlModifier + Qt::Key_F1).toString(QKeySequence::NativeText),
                QString, String);
    }

#undef testVal
}
#endif

void tst_QSettings::remove()
{
    QSettings settings0(QSettings::UserScope, "software.org", "KillerAPP");
    int initialNumKeys = settings0.allKeys().size();
    QCOMPARE(settings0.value("key 1", "123").toString(), QLatin1String("123"));
    settings0.remove("key 1");
    QCOMPARE(settings0.value("key 1", "456").toString(), QLatin1String("456"));

    settings0.setValue("key 1", "bubloo");
    QCOMPARE(settings0.value("key 1").toString(), QLatin1String("bubloo"));
    settings0.remove("key 2");
    QCOMPARE(settings0.value("key 1").toString(), QLatin1String("bubloo"));
    settings0.remove("key 1");
    QCOMPARE(settings0.value("key 1", "789").toString(), QLatin1String("789"));

    /*
      Make sure that removing a key removes all the subkeys.
    */
    settings0.setValue("alpha/beta/geometry", -7);
    settings0.setValue("alpha/beta/geometry/x", 1);
    settings0.setValue("alpha/beta/geometry/y", 2);
    settings0.setValue("alpha/beta/geometry/width", 3);
    settings0.setValue("alpha/beta/geometry/height", 4);
    settings0.setValue("alpha/gamma/splitter", 5);

    settings0.remove("alpha/beta/geometry/x");
    QCOMPARE(settings0.value("alpha/beta/geometry").toInt(), -7);
    QCOMPARE(settings0.value("alpha/beta/geometry/x", 999).toInt(), 999);
    QCOMPARE(settings0.value("alpha/beta/geometry/y").toInt(), 2);
    QCOMPARE(settings0.value("alpha/beta/geometry/width").toInt(), 3);
    QCOMPARE(settings0.value("alpha/beta/geometry/height").toInt(), 4);
    QCOMPARE(settings0.value("alpha/gamma/splitter").toInt(), 5);

    settings0.remove("alpha/beta/geometry");
    QCOMPARE(settings0.value("alpha/beta/geometry", 777).toInt(), 777);
    QCOMPARE(settings0.value("alpha/beta/geometry/x", 111).toInt(), 111);
    QCOMPARE(settings0.value("alpha/beta/geometry/y", 222).toInt(), 222);
    QCOMPARE(settings0.value("alpha/beta/geometry/width", 333).toInt(), 333);
    QCOMPARE(settings0.value("alpha/beta/geometry/height", 444).toInt(), 444);
    QCOMPARE(settings0.value("alpha/gamma/splitter").toInt(), 5);

    settings0.setValue("alpha/beta/geometry", -7);
    settings0.setValue("alpha/beta/geometry/x", 1);
    settings0.setValue("alpha/beta/geometry/y", 2);
    settings0.setValue("alpha/beta/geometry/width", 3);
    settings0.setValue("alpha/beta/geometry/height", 4);
    settings0.setValue("alpha/gamma/splitter", 5);
    QCOMPARE(settings0.allKeys().size(), initialNumKeys + 6);

    settings0.beginGroup("alpha/beta/geometry");
    settings0.remove("");
    settings0.endGroup();
    QVERIFY(!settings0.contains("alpha/beta/geometry"));
    QVERIFY(!settings0.contains("alpha/beta/geometry/x"));
    QVERIFY(!settings0.contains("alpha/beta/geometry/y"));
    QVERIFY(!settings0.contains("alpha/beta/geometry/width"));
    QVERIFY(!settings0.contains("alpha/beta/geometry/height"));
    QVERIFY(settings0.contains("alpha/gamma/splitter"));
    QCOMPARE(settings0.allKeys().size(), initialNumKeys + 1);

    settings0.beginGroup("alpha/beta");
    settings0.remove("");
    settings0.endGroup();
    QVERIFY(!settings0.contains("alpha/beta/geometry"));
    QVERIFY(!settings0.contains("alpha/beta/geometry/x"));
    QVERIFY(!settings0.contains("alpha/beta/geometry/y"));
    QVERIFY(!settings0.contains("alpha/beta/geometry/width"));
    QVERIFY(!settings0.contains("alpha/beta/geometry/height"));
    QVERIFY(settings0.contains("alpha/gamma/splitter"));
    QCOMPARE(settings0.allKeys().size(), initialNumKeys + 1);

    settings0.remove("");
    QVERIFY(!settings0.contains("alpha/gamma/splitter"));
    QCOMPARE(settings0.allKeys().size(), initialNumKeys);

    /*
      Do it again, but this time let's use setGroup().
    */

    settings0.setValue("alpha/beta/geometry", -7);
    settings0.setValue("alpha/beta/geometry/x", 1);
    settings0.setValue("alpha/beta/geometry/y", 2);
    settings0.setValue("alpha/beta/geometry/width", 3);
    settings0.setValue("alpha/beta/geometry/height", 4);
    settings0.setValue("alpha/gamma/splitter", 5);

    settings0.beginGroup("foo/bar/baz/doesn't");
    settings0.remove("exist");
    settings0.endGroup();
    QCOMPARE(settings0.value("alpha/beta/geometry").toInt(), -7);
    QCOMPARE(settings0.value("alpha/beta/geometry/x").toInt(), 1);
    QCOMPARE(settings0.value("alpha/beta/geometry/y").toInt(), 2);
    QCOMPARE(settings0.value("alpha/beta/geometry/width").toInt(), 3);
    QCOMPARE(settings0.value("alpha/beta/geometry/height").toInt(), 4);
    QCOMPARE(settings0.value("alpha/gamma/splitter").toInt(), 5);

    settings0.beginGroup("alpha/beta/geometry");
    settings0.remove("x");
    settings0.endGroup();
    QCOMPARE(settings0.value("alpha/beta/geometry").toInt(), -7);
    QCOMPARE(settings0.value("alpha/beta/geometry/x", 999).toInt(), 999);
    QCOMPARE(settings0.value("alpha/beta/geometry/y").toInt(), 2);
    QCOMPARE(settings0.value("alpha/beta/geometry/width").toInt(), 3);
    QCOMPARE(settings0.value("alpha/beta/geometry/height").toInt(), 4);
    QCOMPARE(settings0.value("alpha/gamma/splitter").toInt(), 5);

    settings0.remove("alpha/beta");
    QCOMPARE(settings0.value("alpha/beta/geometry", 777).toInt(), 777);
    QCOMPARE(settings0.value("alpha/beta/geometry/x", 111).toInt(), 111);
    QCOMPARE(settings0.value("alpha/beta/geometry/y", 222).toInt(), 222);
    QCOMPARE(settings0.value("alpha/beta/geometry/width", 333).toInt(), 333);
    QCOMPARE(settings0.value("alpha/beta/geometry/height", 444).toInt(), 444);
    QCOMPARE(settings0.value("alpha/gamma/splitter").toInt(), 5);

    settings0.clear();
    QCOMPARE(settings0.value("alpha/gamma/splitter", 888).toInt(), 888);

    /*
      OK, now let's check what happens if settings are spread across
      multiple files (user vs. global, product-specific vs.
      company-wide).
    */

    QSettings settings1(QSettings::UserScope, "software.org", "KillerAPP");
    QSettings settings2(QSettings::UserScope, "software.org");

    QScopedPointer<QSettings> settings3;
    QScopedPointer<QSettings> settings4;

    if (m_canWriteNativeSystemSettings) {
        settings3.reset(new QSettings(QSettings::SystemScope, "software.org", "KillerAPP"));
        settings4.reset(new QSettings(QSettings::SystemScope, "software.org"));
        settings3->setValue("key 1", "blah");
        settings4->setValue("key 1", "doodah");
    }

    settings2.setValue("key 1", "whoa");
    settings1.setValue("key 1", "gurgle");
    QCOMPARE(settings1.value("key 1").toString(), QString("gurgle"));
    QCOMPARE(settings2.value("key 1").toString(), QString("whoa"));

    if (m_canWriteNativeSystemSettings) {
        QCOMPARE(settings3->value("key 1").toString(), QString("blah"));
        QCOMPARE(settings4->value("key 1").toString(), QString("doodah"));
    }

    settings1.remove("key 1");
    QCOMPARE(settings1.value("key 1").toString(), QString("whoa"));
    QCOMPARE(settings2.value("key 1").toString(), QString("whoa"));
    if (m_canWriteNativeSystemSettings) {
        QCOMPARE(settings3->value("key 1").toString(), QString("blah"));
        QCOMPARE(settings4->value("key 1").toString(), QString("doodah"));
    }

    if (m_canWriteNativeSystemSettings) {
        settings2.remove("key 1");
        QCOMPARE(settings1.value("key 1").toString(), QString("blah"));
        QCOMPARE(settings2.value("key 1").toString(), QString("doodah"));
        QCOMPARE(settings3->value("key 1").toString(), QString("blah"));
        QCOMPARE(settings4->value("key 1").toString(), QString("doodah"));

        settings3->remove("key 1");
        QCOMPARE(settings1.value("key 1").toString(), QString("doodah"));
        QCOMPARE(settings2.value("key 1").toString(), QString("doodah"));
        QCOMPARE(settings3->value("key 1").toString(), QString("doodah"));
        QCOMPARE(settings4->value("key 1").toString(), QString("doodah"));

        settings4->remove("key 1");
        QVERIFY(!settings1.contains("key 1"));
        QVERIFY(!settings2.contains("key 1"));
        QVERIFY(!settings3->contains("key 1"));
        QVERIFY(!settings4->contains("key 1"));
    }

    /*
      Get ready for the next part of the test.
    */

    settings1.clear();
    settings2.clear();
    if (m_canWriteNativeSystemSettings) {
        settings3->clear();
        settings4->clear();
    }

    settings1.sync();
    settings2.sync();

    if (m_canWriteNativeSystemSettings) {
        settings3->sync();
        settings4->sync();
    }

    /*
      Check that recursive removes work correctly when some of the
      keys are loaded from the file and others have been modified in
      memory (corresponds to originalKeys vs. addedKeys in the
      QSettingsFile code).
    */

    settings1.setValue("alpha/beta/geometry", -7);
    settings1.setValue("alpha/beta/geometry/x", 1);
    settings1.setValue("alpha/beta/geometry/y", 2);
    settings1.setValue("alpha/gamma/splitter", 5);
    settings1.sync();

    settings1.setValue("alpha/beta/geometry/width", 3);
    settings1.setValue("alpha/beta/geometry/height", 4);

    settings1.remove("alpha/beta/geometry/y");
    QVERIFY(settings1.contains("alpha/beta/geometry"));
    QVERIFY(settings1.contains("alpha/beta/geometry/x"));
    QVERIFY(!settings1.contains("alpha/beta/geometry/y"));
    QVERIFY(settings1.contains("alpha/beta/geometry/width"));
    QVERIFY(settings1.contains("alpha/beta/geometry/height"));
    QCOMPARE(settings1.allKeys().size(), initialNumKeys + 5);

    settings1.remove("alpha/beta/geometry/y");
    QCOMPARE(settings1.allKeys().size(), initialNumKeys + 5);

    settings1.remove("alpha/beta/geometry/height");
    QVERIFY(settings1.contains("alpha/beta/geometry"));
    QVERIFY(settings1.contains("alpha/beta/geometry/x"));
    QVERIFY(!settings1.contains("alpha/beta/geometry/y"));
    QVERIFY(settings1.contains("alpha/beta/geometry/width"));
    QVERIFY(!settings1.contains("alpha/beta/geometry/height"));
    QCOMPARE(settings1.allKeys().size(), initialNumKeys + 4);

    settings1.remove("alpha/beta/geometry");
    QVERIFY(!settings1.contains("alpha/beta/geometry"));
    QVERIFY(!settings1.contains("alpha/beta/geometry/x"));
    QVERIFY(!settings1.contains("alpha/beta/geometry/y"));
    QVERIFY(!settings1.contains("alpha/beta/geometry/width"));
    QVERIFY(!settings1.contains("alpha/beta/geometry/height"));
    QVERIFY(settings1.contains("alpha/gamma/splitter"));
    QCOMPARE(settings1.allKeys().size(), initialNumKeys + 1);

    settings1.sync();
    QVERIFY(!settings1.contains("alpha/beta/geometry"));
    QVERIFY(!settings1.contains("alpha/beta/geometry/x"));
    QVERIFY(!settings1.contains("alpha/beta/geometry/y"));
    QVERIFY(!settings1.contains("alpha/beta/geometry/width"));
    QVERIFY(!settings1.contains("alpha/beta/geometry/height"));
    QVERIFY(settings1.contains("alpha/gamma/splitter"));
    QCOMPARE(settings1.allKeys().size(), initialNumKeys + 1);
}

/*
    Tests contains() and keys().
*/
void tst_QSettings::contains()
{
    QSettings settings1(QSettings::UserScope, "software.org", "KillerAPP");
    int initialNumKeys = settings1.allKeys().size(); // 0 on all platforms but OS X.
    settings1.setValue("alpha/beta/geometry", -7);
    settings1.setValue("alpha/beta/geometry/x", 1);
    settings1.setValue("alpha/beta/geometry/y", 2);
    settings1.setValue("alpha/beta/geometry/width", 3);
    settings1.setValue("alpha/beta/geometry/height", 4);
    settings1.setValue("alpha/gamma/splitter", 5);
    settings1.setValue("alpha/gamma/splitter/ /", 5);

    QVERIFY(!settings1.contains("alpha"));
    QVERIFY(!settings1.contains("alpha/beta"));
    QVERIFY(!settings1.contains("///alpha///beta///"));
    QVERIFY(settings1.contains("alpha/beta/geometry"));
    QVERIFY(settings1.contains("///alpha///beta//geometry//"));
    QVERIFY(settings1.contains("alpha/beta/geometry/x"));
    QVERIFY(settings1.contains("alpha/beta/geometry/y"));
    QVERIFY(settings1.contains("alpha/beta/geometry/width"));
    QVERIFY(settings1.contains("alpha/beta/geometry/height"));
    QVERIFY(!settings1.contains("alpha/beta/geometry/height/foo/bar/doesn't/exist"));
    QVERIFY(!settings1.contains("alpha/gamma"));
    QVERIFY(settings1.contains("alpha/gamma/splitter"));
    QVERIFY(settings1.contains("alpha/gamma/splitter/ "));
    QVERIFY(settings1.contains("////alpha/gamma/splitter// ////"));

    settings1.beginGroup("alpha");
    QVERIFY(!settings1.contains("beta"));
    QVERIFY(!settings1.contains("/////beta///"));
    QVERIFY(settings1.contains("beta/geometry"));
    QVERIFY(settings1.contains("/////beta//geometry//"));
    QVERIFY(settings1.contains("beta/geometry/x"));
    QVERIFY(settings1.contains("beta/geometry/y"));
    QVERIFY(settings1.contains("beta/geometry/width"));
    QVERIFY(settings1.contains("beta/geometry/height"));
    QVERIFY(!settings1.contains("beta/geometry/height/foo/bar/doesn't/exist"));
    QVERIFY(!settings1.contains("gamma"));
    QVERIFY(settings1.contains("gamma/splitter"));
    QVERIFY(settings1.contains("gamma/splitter/ "));
    QVERIFY(settings1.contains("////gamma/splitter// ////"));

    settings1.beginGroup("beta/geometry");
    QVERIFY(settings1.contains("x"));
    QVERIFY(settings1.contains("y"));
    QVERIFY(settings1.contains("width"));
    QVERIFY(settings1.contains("height"));
    QVERIFY(!settings1.contains("height/foo/bar/doesn't/exist"));

    QStringList keys = settings1.allKeys();
    QStringList expectedResult = QStringList() << "x" << "y" << "width" << "height";
    keys.sort();
    expectedResult.sort();
    int i;
    QCOMPARE(keys, expectedResult);
    for (i = 0; i < keys.size(); ++i) {
        QVERIFY(settings1.contains(keys.at(i)));
    }

    settings1.endGroup();
    QCOMPARE(settings1.group(), QLatin1String("alpha"));
    keys = settings1.allKeys();
    QCOMPARE(keys.size(), expectedResult.size() + 3);
    for (i = 0; i < keys.size(); ++i) {
        QVERIFY(settings1.contains(keys.at(i)));
    }

    settings1.endGroup();
    QVERIFY(settings1.group().isEmpty());
    keys = settings1.allKeys();

    QCOMPARE(keys.size(), initialNumKeys + 7);
    for (i = 0; i < keys.size(); ++i) {
        QVERIFY(settings1.contains(keys.at(i)));
    }
}

void tst_QSettings::sync()
{
    /*
        What we're trying to test here is the case where two
        instances of the same application access the same preference
        files. We want to make sure that the results are 'merged',
        rather than having the last application overwrite settings
        set by the first application (like in Qt 3).

        This is only applicable to the INI format. The Windows
        registry and Mac's CFPreferences API should take care of this
        by themselves.
    */

    QSettings settings1(QSettings::IniFormat, QSettings::UserScope, "software.org");
    settings1.setValue("alpha/beta/geometry", -7);
    settings1.setValue("alpha/beta/geometry/x", 1);
    settings1.setValue("alpha/beta/geometry/y", 2);
    settings1.setValue("alpha/beta/geometry/width", 3);
    settings1.setValue("alpha/beta/geometry/height", 4);
    settings1.setValue("alpha/gamma/splitter", 5);
    settings1.sync(); // and it all goes into the file

    QSettings settings2(QSettings::IniFormat, QSettings::UserScope, "other.software.org");
    settings2.setValue("alpha/beta/geometry/x", 8);
    settings2.sync();

    settings2.setValue("moo/beta/geometry", -7);
    settings2.setValue("moo/beta/geometry/x", 1);
    settings2.setValue("moo/beta/geometry/y", 2);
    settings2.setValue("moo/beta/geometry/width", 3);
    settings2.setValue("moo/beta/geometry/height", 4);
    settings2.setValue("moo/gamma/splitter", 5);
    settings2.setValue("alpha/gamma/splitter", 15);
    settings2.remove("alpha/beta/geometry/x");
    settings2.remove("alpha/beta/geometry/y"); // should do nothing

    // Now "some other app" will change other.software.org.ini
    QString userConfDir = settingsPath("__user__") + QDir::separator();
#if !defined(Q_OS_WINRT)
    unlink((userConfDir + "other.software.org.ini").toLatin1());
    rename((userConfDir + "software.org.ini").toLatin1(),
           (userConfDir + "other.software.org.ini").toLatin1());
#else
    QFile::remove(userConfDir + "other.software.org.ini");
    QFile::rename(userConfDir + "software.org.ini" , userConfDir + "other.software.org.ini");
#endif

    settings2.sync();

    // And voila, we should be merged

    QCOMPARE(settings2.value("alpha/beta/geometry").toInt(), -7);
    QVERIFY(!settings2.contains("alpha/beta/geometry/x")); // <----- removed by settings2
    QCOMPARE(settings2.value("alpha/beta/geometry/y").toInt(), 2);
    QCOMPARE(settings2.value("alpha/beta/geometry/width").toInt(), 3);
    QCOMPARE(settings2.value("alpha/beta/geometry/height").toInt(), 4);
    QCOMPARE(settings2.value("alpha/gamma/splitter").toInt(), 15); // <---- set by settings2
    QCOMPARE(settings2.value("moo/beta/geometry").toInt(), -7);
    QCOMPARE(settings2.value("moo/beta/geometry/x").toInt(), 1);
    QCOMPARE(settings2.value("moo/beta/geometry/y").toInt(), 2);
    QCOMPARE(settings2.value("moo/beta/geometry/width").toInt(), 3);
    QCOMPARE(settings2.value("moo/beta/geometry/height").toInt(), 4);
    QCOMPARE(settings2.value("moo/gamma/splitter").toInt(), 5);
    QCOMPARE(settings2.allKeys().count(), 11);

    // Now, software.org.ini no longer exists, this is same as another app
    // clearing all settings.
    settings1.sync();
    QCOMPARE(settings1.allKeys().count(), 0);

    // Now "some other app" will change software.org.ini
    QVERIFY(QFile::rename((userConfDir + "other.software.org.ini").toLatin1(),
                          (userConfDir + "software.org.ini").toLatin1()));

    settings1.sync();
    QCOMPARE(settings1.value("alpha/beta/geometry").toInt(), -7);
    QCOMPARE(settings1.value("alpha/beta/geometry/y").toInt(), 2);
    QCOMPARE(settings1.value("alpha/beta/geometry/width").toInt(), 3);
    QCOMPARE(settings1.value("alpha/beta/geometry/height").toInt(), 4);
    QCOMPARE(settings1.value("alpha/gamma/splitter").toInt(), 15);
    QCOMPARE(settings1.value("moo/beta/geometry").toInt(), -7);
    QCOMPARE(settings1.value("moo/beta/geometry/x").toInt(), 1);
    QCOMPARE(settings1.value("moo/beta/geometry/y").toInt(), 2);
    QCOMPARE(settings1.value("moo/beta/geometry/width").toInt(), 3);
    QCOMPARE(settings1.value("moo/beta/geometry/height").toInt(), 4);
    QCOMPARE(settings1.value("moo/gamma/splitter").toInt(), 5);
    QCOMPARE(settings1.allKeys().count(), 11);
}

void tst_QSettings::syncNonWriteableDir()
{
    QTemporaryDir tempDir;
    QVERIFY2(tempDir.isValid(), qUtf8Printable(tempDir.errorString()));

    // first, create a file
    QString filename = tempDir.path() + "/config.ini";
    {
        QFile f(filename);
        QVERIFY2(f.open(QIODevice::WriteOnly), qUtf8Printable(f.errorString()));
    }

    // second, make the dir unwriteable
    QVERIFY(QFile::setPermissions(tempDir.path(), QFile::ReadUser | QFile::ExeUser));
    struct UndoSetPermissions {
        QString name;
        UndoSetPermissions(const QString &name) : name(name) {}
        ~UndoSetPermissions()
        { QFile::setPermissions(name, QFile::ReadUser | QFile::WriteUser | QFile::ExeUser); }
    };
    UndoSetPermissions undo(tempDir.path());    // otherwise, QTemporaryDir will fail

    {
        QSettings settings(filename, QSettings::IniFormat);
        QVERIFY(settings.isAtomicSyncRequired());
        settings.setAtomicSyncRequired(false);
        settings.setValue("alpha/beta", 1);
        settings.sync();
        QCOMPARE(settings.status(), QSettings::NoError);
    }

    QVERIFY(QFileInfo(filename).size() != 0);
    QSettings settings(filename, QSettings::IniFormat);
    QCOMPARE(settings.value("alpha/beta"), QVariant(1));
}

#ifdef Q_OS_WIN
void tst_QSettings::syncAlternateDataStream()
{
    QTemporaryDir tempDir;
    QVERIFY2(tempDir.isValid(), qUtf8Printable(tempDir.errorString()));

    // first, create a file
    QString filename = tempDir.path() + "/file";
    {
        QFile f(filename);
        QVERIFY2(f.open(QIODevice::WriteOnly), qUtf8Printable(f.errorString()));
    }

    // then create an ADS
    filename += ":config.ini";
    {
        QFile f(filename);
        if (!f.open(QIODevice::WriteOnly))
            QSKIP("Could not create ADS file (" + f.errorString().toUtf8() + ") - FAT drive?");
    }

    {
        QSettings settings(filename, QSettings::IniFormat);
        QVERIFY(settings.isAtomicSyncRequired());
        settings.setAtomicSyncRequired(false);
        settings.setValue("alpha/beta", 1);
        settings.sync();
        QCOMPARE(settings.status(), QSettings::NoError);
    }

    QVERIFY(QFileInfo(filename).size() != 0);
    QSettings settings(filename, QSettings::IniFormat);
    QCOMPARE(settings.value("alpha/beta"), QVariant(1));
}
#endif

void tst_QSettings::setFallbacksEnabled_data()
{
    populateWithFormats();
}

void tst_QSettings::setFallbacksEnabled()
{
    QFETCH(QSettings::Format, format);

    if (!m_canWriteNativeSystemSettings && format == QSettings::NativeFormat)
        QSKIP(insufficientPermissionSkipMessage);

    QSettings settings1(format, QSettings::UserScope, "software.org", "KillerAPP");
    QSettings settings2(format, QSettings::UserScope, "software.org");
    QSettings settings3(format, QSettings::SystemScope, "software.org", "KillerAPP");
    QSettings settings4(format, QSettings::SystemScope, "software.org");

    settings1.setValue("key 1", "alpha");
    settings2.setValue("key 1", "beta");
    settings3.setValue("key 1", "gamma");
    settings4.setValue("key 1", "delta");

    settings1.setValue("key 2", "alpha");
    settings2.setValue("key 2", "beta");
    settings3.setValue("key 2", "gamma");

    settings1.setValue("key 3", "alpha");
    settings3.setValue("key 3", "gamma");
    settings4.setValue("key 3", "delta");

    settings1.setValue("key 4", "alpha");
    settings2.setValue("key 4", "beta");
    settings4.setValue("key 4", "delta");

    settings2.setValue("key 5", "beta");
    settings3.setValue("key 5", "gamma");
    settings4.setValue("key 5", "delta");

    QVERIFY(settings1.fallbacksEnabled());
    QVERIFY(settings2.fallbacksEnabled());
    QVERIFY(settings3.fallbacksEnabled());
    QVERIFY(settings4.fallbacksEnabled());

    settings1.setFallbacksEnabled(false);
    settings2.setFallbacksEnabled(false);
    settings3.setFallbacksEnabled(false);
    settings4.setFallbacksEnabled(false);

    QVERIFY(!settings1.fallbacksEnabled());
    QVERIFY(!settings2.fallbacksEnabled());
    QVERIFY(!settings3.fallbacksEnabled());
    QVERIFY(!settings4.fallbacksEnabled());

    /*
        Make sure that the QSettings objects can still access their
        main associated file when fallbacks are turned off.
    */

    QCOMPARE(settings1.value("key 1").toString(), QString("alpha"));
    QCOMPARE(settings2.value("key 1").toString(), QString("beta"));
    QCOMPARE(settings3.value("key 1").toString(), QString("gamma"));
    QCOMPARE(settings4.value("key 1").toString(), QString("delta"));

    QCOMPARE(settings1.value("key 2").toString(), QString("alpha"));
    QCOMPARE(settings2.value("key 2").toString(), QString("beta"));
    QCOMPARE(settings3.value("key 2").toString(), QString("gamma"));
    QVERIFY(!settings4.contains("key 2"));

    QCOMPARE(settings1.value("key 3").toString(), QString("alpha"));
    QCOMPARE(settings3.value("key 3").toString(), QString("gamma"));
    QCOMPARE(settings4.value("key 3").toString(), QString("delta"));
    QVERIFY(!settings2.contains("key 3"));

    QCOMPARE(settings1.value("key 4").toString(), QString("alpha"));
    QCOMPARE(settings2.value("key 4").toString(), QString("beta"));
    QCOMPARE(settings4.value("key 4").toString(), QString("delta"));
    QVERIFY(!settings3.contains("key 4"));

    QCOMPARE(settings2.value("key 5").toString(), QString("beta"));
    QCOMPARE(settings3.value("key 5").toString(), QString("gamma"));
    QCOMPARE(settings4.value("key 5").toString(), QString("delta"));
    QVERIFY(!settings1.contains("key 5"));

    QCOMPARE(settings1.value("key 1").toString(), QString("alpha"));
    QCOMPARE(settings1.value("key 5").toString(), QString(""));
    QVERIFY(settings1.contains("key 1"));
    QVERIFY(!settings1.contains("key 5"));
}

void tst_QSettings::testChildKeysAndGroups_data()
{
    populateWithFormats();
}

void tst_QSettings::testChildKeysAndGroups()
{
    QFETCH(QSettings::Format, format);

    QSettings settings1(format, QSettings::UserScope, "software.org");
    settings1.setFallbacksEnabled(false);
    settings1.setValue("alpha/beta/geometry", -7);
    settings1.setValue("alpha/beta/geometry/x", 1);
    settings1.setValue("alpha/beta/geometry/y", 2);
    settings1.setValue("alpha/beta/geometry/width", 3);
    settings1.setValue("alpha/beta/geometry/height", 4);
    settings1.setValue("alpha/gamma/splitter", 5);

    QCOMPARE(settings1.childKeys(), QStringList());
    QCOMPARE(settings1.childGroups(), QStringList() << "alpha");

    settings1.beginGroup("/alpha");
    QCOMPARE(settings1.childKeys(), QStringList());
    QStringList children = settings1.childGroups();
    children.sort();
    QCOMPARE(children, QStringList() << "beta" << "gamma");

    settings1.beginGroup("/beta");
    QCOMPARE(settings1.childKeys(), QStringList() << "geometry");
    QCOMPARE(settings1.childGroups(), QStringList() << "geometry");

    settings1.beginGroup("/geometry");
    children = settings1.childKeys();
    children.sort();
    QCOMPARE(children, QStringList()  << "height" << "width" << "x" << "y");
    QCOMPARE(settings1.childGroups(), QStringList());

    settings1.beginGroup("/width");
    QCOMPARE(settings1.childKeys(), QStringList());
    QCOMPARE(settings1.childGroups(), QStringList());

    settings1.endGroup();
    settings1.endGroup();
    settings1.endGroup();
    settings1.endGroup();

    {
        QSettings settings2("other.software.org");
        settings2.setValue("viewbar/foo/test1", "1");
        settings2.setValue("viewbar/foo/test2", "2");
        settings2.setValue("viewbar/foo/test3", "3");
        settings2.setValue("viewbar/foo/test4", "4");
        settings2.setValue("viewbar/foo/test5", "5");
        settings2.setValue("viewbar/bar/test1", "1");
        settings2.setValue("viewbar/bar/test2", "2");
        settings2.setValue("viewbar/bar/test3", "3");
        settings2.setValue("viewbar/bar/test4", "4");
        settings2.setValue("viewbar/bar/test5", "5");

        settings2.beginGroup("viewbar");
        QStringList l = settings2.childGroups();
        settings2.endGroup();
        l.sort();
        QCOMPARE(l, QStringList() << "bar" << "foo");
    }
}

void tst_QSettings::testUpdateRequestEvent()
{
    const QString oldCur = QDir::currentPath();
    QString dataLocation = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
    QVERIFY(QDir::root().mkpath(dataLocation));
    QDir::setCurrent(dataLocation);

    QFile::remove("foo");
    QVERIFY(!QFile::exists("foo"));

    QSettings settings1("foo", QSettings::IniFormat);
    QVERIFY(!QFile::exists("foo"));
    QCOMPARE(QFileInfo("foo").size(), qint64(0));
    settings1.setValue("key1", 1);
    QCOMPARE(QFileInfo("foo").size(), qint64(0));

    QTRY_VERIFY(QFileInfo("foo").size() > 0);

    settings1.remove("key1");
    QVERIFY(QFileInfo("foo").size() > 0);

    QTRY_COMPARE(QFileInfo("foo").size(), qint64(0));

    settings1.setValue("key2", 2);
    QCOMPARE(QFileInfo("foo").size(), qint64(0));

    QTRY_VERIFY(QFileInfo("foo").size() > 0);

    settings1.clear();
    QVERIFY(QFileInfo("foo").size() > 0);

    QTRY_COMPARE(QFileInfo("foo").size(), qint64(0));

    QDir::setCurrent(oldCur);
}

const int NumIterations = 5;
const int NumThreads = 4;
int numThreadSafetyFailures;

class SettingsThread : public QThread
{
public:
    void run();
    void start(int n) { param = n; QThread::start(); }

private:
    int param;
};

void SettingsThread::run()
{
    for (int i = 0; i < NumIterations; ++i) {
        QSettings settings("software.org", "KillerAPP");
        settings.setValue(QString::number((param * NumIterations) + i), param);
        settings.sync();
        if (settings.status() != QSettings::NoError) {
            QWARN(qPrintable(QString("Unexpected QSettings status %1").arg((int)settings.status())));
            ++numThreadSafetyFailures;
        }
    }
}

void tst_QSettings::testThreadSafety()
{
    SettingsThread threads[NumThreads];
    int i, j;

    numThreadSafetyFailures = 0;

    for (i = 0; i < NumThreads; ++i)
        threads[i].start(i + 1);
    for (i = 0; i < NumThreads; ++i)
        threads[i].wait();

    QSettings settings("software.org", "KillerAPP");
    for (i = 0; i < NumThreads; ++i) {
        int param = i + 1;
        for (j = 0; j < NumIterations; ++j) {
            QCOMPARE(settings.value(QString::number((param * NumIterations) + j)).toInt(), param);
        }
    }

    QCOMPARE(numThreadSafetyFailures, 0);
}

#ifdef QT_BUILD_INTERNAL
void tst_QSettings::testNormalizedKey_data()
{
    QTest::addColumn<QString>("inKey");
    QTest::addColumn<QString>("outKey");

    QTest::newRow("empty1") << "" << "";
    QTest::newRow("empty2") << "/" << "";
    QTest::newRow("empty3") << "//" << "";
    QTest::newRow("empty4") << "///" << "";

    QTest::newRow("a1") << "a" << "a";
    QTest::newRow("a2") << "/a" << "a";
    QTest::newRow("a3") << "a/" << "a";
    QTest::newRow("a4") << "//a" << "a";
    QTest::newRow("a5") << "a//" << "a";
    QTest::newRow("a6") << "///a" << "a";
    QTest::newRow("a7") << "a///" << "a";
    QTest::newRow("a8") << "///a/" << "a";
    QTest::newRow("a9") << "/a///" << "a";

    QTest::newRow("ab1") << "aaa/bbb" << "aaa/bbb";
    QTest::newRow("ab2") << "/aaa/bbb" << "aaa/bbb";
    QTest::newRow("ab3") << "aaa/bbb/" << "aaa/bbb";
    QTest::newRow("ab4") << "/aaa/bbb/" << "aaa/bbb";
    QTest::newRow("ab5") << "aaa///bbb" << "aaa/bbb";
    QTest::newRow("ab6") << "aaa///bbb/" << "aaa/bbb";
    QTest::newRow("ab7") << "/aaa///bbb/" << "aaa/bbb";
    QTest::newRow("ab8") << "////aaa///bbb////" << "aaa/bbb";
}
#endif

#ifdef QT_BUILD_INTERNAL
void tst_QSettings::testNormalizedKey()
{
    QFETCH(QString, inKey);
    QFETCH(QString, outKey);

    inKey.detach();

    QString result = QSettingsPrivate::normalizedKey(inKey);
    QCOMPARE(result, outKey);

    /*
        If the key is already normalized, we verify that outKey is
        just a shallow copy of the input string. This is an important
        optimization that shouldn't be removed accidentally.
    */
    if (inKey == outKey) {
        QVERIFY(!result.isDetached());
    } else {
        if (!result.isEmpty()) {
            QVERIFY(result.isDetached());
        }
    }
}
#endif

void tst_QSettings::testEmptyData()
{
    QString filename(settingsPath("empty.ini"));
    QFile::remove(filename);
    QVERIFY(!QFile::exists(filename));

    QString nullString;
    QString emptyString("");
    QStringList emptyList;
    QStringList list;
    QStringList list2;

    QVariantList emptyVList;
    QVariantList vList, vList2, vList3;

    list << emptyString << nullString;
    list2 << emptyString;
    vList << emptyString;
    vList2 << emptyString << nullString;
    vList3 << QString("foo");

    {
        QSettings settings(filename, QSettings::IniFormat);
        settings.setValue("nullString", nullString);
        settings.setValue("emptyString", emptyString);
        settings.setValue("emptyList", emptyList);
        settings.setValue("list", list);
        settings.setValue("list2", list2);
        settings.setValue("emptyVList", emptyVList);
        settings.setValue("vList", vList);
        settings.setValue("vList2", vList2);
        settings.setValue("vList3", vList3);
        QCOMPARE(settings.status(), QSettings::NoError);
    }
    {
        QSettings settings(filename, QSettings::IniFormat);
        QCOMPARE(settings.value("nullString").toString(), nullString);
        QCOMPARE(settings.value("emptyString").toString(), emptyString);
        QCOMPARE(settings.value("emptyList").toStringList(), emptyList);
        QCOMPARE(settings.value("list").toStringList(), list);
        QCOMPARE(settings.value("list2").toStringList(), list2);
        QCOMPARE(settings.value("emptyVList").toList(), emptyVList);
        QCOMPARE(settings.value("vList").toList(), vList);
        QCOMPARE(settings.value("vList2").toList(), vList2);
        QCOMPARE(settings.value("vList3").toList(), vList3);
        QCOMPARE(settings.status(), QSettings::NoError);
    }

    {
        QSettings settings("QtProject", "tst_qsettings");
        settings.setValue("nullString", nullString);
        settings.setValue("emptyString", emptyString);
        settings.setValue("emptyList", emptyList);
        settings.setValue("list", list);
        settings.setValue("list2", list2);
        settings.setValue("emptyVList", emptyVList);
        settings.setValue("vList", vList);
        settings.setValue("vList2", vList2);
        settings.setValue("vList3", vList3);
        QCOMPARE(settings.status(), QSettings::NoError);
    }
    {
        QSettings settings("QtProject", "tst_qsettings");
        QCOMPARE(settings.value("nullString").toString(), nullString);
        QCOMPARE(settings.value("emptyString").toString(), emptyString);
        QCOMPARE(settings.value("emptyList").toStringList(), emptyList);
        QCOMPARE(settings.value("list").toStringList(), list);
        QCOMPARE(settings.value("list2").toStringList(), list2);
        QCOMPARE(settings.value("emptyVList").toList(), emptyVList);
        QCOMPARE(settings.value("vList").toList(), vList);
        QCOMPARE(settings.value("vList2").toList(), vList2);
        QCOMPARE(settings.value("vList3").toList(), vList3);
        QCOMPARE(settings.status(), QSettings::NoError);
    }
    QFile::remove(filename);
}

void tst_QSettings::testEmptyKey()
{
    QSettings settings;
    QTest::ignoreMessage(QtWarningMsg, "QSettings::value: Empty key passed");
    const QVariant value = settings.value(QString());
    QCOMPARE(value, QVariant());
    QTest::ignoreMessage(QtWarningMsg, "QSettings::setValue: Empty key passed");
    settings.setValue(QString(), value);
}

void tst_QSettings::testResourceFiles()
{
    QSettings settings(":/resourcefile.ini", QSettings::IniFormat);
    QCOMPARE(settings.status(), QSettings::NoError);
    QVERIFY(!settings.isWritable());
    QCOMPARE(settings.value("Field 1/Bottom").toInt(), 89);
    settings.setValue("Field 1/Bottom", 90);

    // the next two lines check the statu quo; another behavior would be possible
    QCOMPARE(settings.status(), QSettings::NoError);
    QCOMPARE(settings.value("Field 1/Bottom").toInt(), 90);

    settings.sync();
    QCOMPARE(settings.status(), QSettings::AccessError);
    QCOMPARE(settings.value("Field 1/Bottom").toInt(), 90);
}

void tst_QSettings::testRegistryShortRootNames()
{
#ifndef Q_OS_WIN
    QSKIP("This test is specific to the Windows registry only.");
#else
    QVERIFY(QSettings("HKEY_CURRENT_USER", QSettings::NativeFormat).childGroups() == QSettings("HKCU", QSettings::NativeFormat).childGroups());
    QVERIFY(QSettings("HKEY_LOCAL_MACHINE", QSettings::NativeFormat).childGroups() == QSettings("HKLM", QSettings::NativeFormat).childGroups());
    QVERIFY(QSettings("HKEY_CLASSES_ROOT", QSettings::NativeFormat).childGroups() == QSettings("HKCR", QSettings::NativeFormat).childGroups());
    QVERIFY(QSettings("HKEY_USERS", QSettings::NativeFormat).childGroups() == QSettings("HKU", QSettings::NativeFormat).childGroups());
#endif
}

void tst_QSettings::testRegistry32And64Bit()
{
#if !defined (Q_OS_WIN) || defined(Q_OS_WINRT)
    QSKIP("This test is specific to the Windows registry.", SkipAll);
#else

    const QString key("HKEY_LOCAL_MACHINE\\Software");
    const QString keyWow("HKEY_LOCAL_MACHINE\\Software\\Wow6432Node");

#ifndef Q_OS_WIN64
    // This branch is taken at compile time if targeting 32-bit; it does not
    // necessarily mean that the OS running the test is 32-bit (it could be
    // e.g. 64-bit).
    QCOMPARE(QSettings(key, QSettings::NativeFormat).childGroups(),
             QSettings(key, QSettings::Registry32Format).childGroups());

    // Detect whether we are running under 64-bit Windows.
    typedef BOOL (WINAPI *IsWow64ProcessPtr)(HANDLE hProcess, PBOOL Wow64Process);
    IsWow64ProcessPtr IsWow64Process = (IsWow64ProcessPtr)QLibrary::resolve(
                "kernel32.dll", "IsWow64Process");

    if (IsWow64Process) {
        BOOL IsWow64 = FALSE;
        if (IsWow64Process(GetCurrentProcess(), &IsWow64) && IsWow64) {
            // The 64-bit registry's "Wow6432Node" key should match the 32-bit registry.
            // If we are not on 32-bit Windows, these should never be the same,
            // because the 64-bit registry has a "Wow6432Node" key.
            QCOMPARE(QSettings(keyWow, QSettings::Registry64Format).childGroups(),
                     QSettings(key, QSettings::Registry32Format).childGroups());
        }
    }
#else
    // This branch is taken at compile time if targeting 64-bit; it does not
    // necessarily mean that the OS running the test is 64-bit (it could be
    // e.g. 128-bit).
    QCOMPARE(QSettings(key, QSettings::NativeFormat).childGroups(),
             QSettings(key, QSettings::Registry64Format).childGroups());

    // The 64-bit registry's "Wow6432Node" key should match the 32-bit registry.
    QCOMPARE(QSettings(keyWow, QSettings::Registry64Format).childGroups(),
             QSettings(key, QSettings::Registry32Format).childGroups());
#endif

#endif
}

void tst_QSettings::trailingWhitespace()
{
    const QString path = settingsPath("trailingWhitespace");
    {
        QSettings s(path, QSettings::IniFormat);
        s.setValue("trailingSpace", "x  ");
        s.setValue("trailingTab", "x\t");
        s.setValue("trailingNewline", "x\n");
    }
    {
        QSettings s(path, QSettings::IniFormat);
        QCOMPARE(s.value("trailingSpace").toString(), QLatin1String("x  "));
        QCOMPARE(s.value("trailingTab").toString(), QLatin1String("x\t"));
        QCOMPARE(s.value("trailingNewline").toString(), QLatin1String("x\n"));
        s.clear();
    }
}

void tst_QSettings::fromFile_data()
{
    populateWithFormats();
}

void tst_QSettings::fromFile()
{
    QFETCH(QSettings::Format, format);

    const QString oldCur = QDir::currentPath();
    QString dataLocation = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
    QVERIFY(QDir::root().mkpath(dataLocation));
    QDir::setCurrent(dataLocation);

    QFile::remove("foo");
    QVERIFY(!QFile::exists("foo"));

    QString path = "foo";

#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT)
    if (format == QSettings::NativeFormat)
        path = "\\HKEY_CURRENT_USER\\Software\\foo";
#endif

    QStringList strList = QStringList() << "hope" << "destiny" << "chastity";

    {
        QSettings settings1(path, format);
        QVERIFY(settings1.allKeys().isEmpty());

        settings1.setValue("alpha", 1);
        settings1.setValue("alpha", 2);
        settings1.setValue("beta", strList);

        QSettings settings2(path, format);
        QCOMPARE(settings2.value("alpha").toInt(), 2);

        settings1.sync();
#if !defined(Q_OS_WIN)
        QVERIFY(QFile::exists("foo"));
#endif
        QCOMPARE(settings1.value("alpha").toInt(), 2);
        QCOMPARE(settings2.value("alpha").toInt(), 2);

        settings2.setValue("alpha", 3);
        settings2.setValue("gamma/foo.bar", 4);
        QCOMPARE(settings1.value("alpha").toInt(), 3);
        QCOMPARE(settings2.value("alpha").toInt(), 3);
        QCOMPARE(settings1.value("beta").toStringList(), strList);
        QCOMPARE(settings2.value("beta").toStringList(), strList);
        QCOMPARE(settings1.value("gamma/foo.bar").toInt(), 4);
        QCOMPARE(settings2.value("gamma/foo.bar").toInt(), 4);
    }

    {
        QSettings settings1(path, format);
        QCOMPARE(settings1.value("alpha").toInt(), 3);
        QCOMPARE(settings1.value("beta").toStringList(), strList);
        QCOMPARE(settings1.value("gamma/foo.bar").toInt(), 4);
        QCOMPARE(settings1.allKeys().size(), 3);
    }

    QDir::setCurrent(oldCur);
}

#ifdef QT_BUILD_INTERNAL
void tst_QSettings::setIniCodec()
{
    QByteArray expeContents4, expeContents5;
    QByteArray actualContents4, actualContents5;

    {
        QFile inFile(":/resourcefile4.ini");
        inFile.open(QIODevice::ReadOnly);
        expeContents4 = inFile.readAll();
        inFile.close();
    }

    {
        QFile inFile(":/resourcefile5.ini");
        inFile.open(QIODevice::ReadOnly);
        expeContents5 = inFile.readAll();
        inFile.close();
    }

    {
        QSettings settings4(QSettings::IniFormat, QSettings::UserScope, "software.org", "KillerAPP");
        settings4.setIniCodec("UTF-8");
        settings4.setValue(QLatin1String("Fa\xe7" "ade/QU\xc9" "BEC"), QLatin1String("Fa\xe7" "ade/QU\xc9" "BEC"));
        settings4.sync();

        QSettings settings5(QSettings::IniFormat, QSettings::UserScope, "other.software.org", "KillerAPP");
        settings5.setIniCodec("ISO 8859-1");
        settings5.setValue(QLatin1String("Fa\xe7" "ade/QU\xc9" "BEC"), QLatin1String("Fa\xe7" "ade/QU\xc9" "BEC"));
        settings5.sync();

        {
            QFile inFile(settings4.fileName());
            inFile.open(QIODevice::ReadOnly | QIODevice::Text);
            actualContents4 = inFile.readAll();
            inFile.close();
        }

        {
            QFile inFile(settings5.fileName());
            inFile.open(QIODevice::ReadOnly | QIODevice::Text);
            actualContents5 = inFile.readAll();
            inFile.close();
        }
    }

    QConfFile::clearCache();

    QCOMPARE(actualContents4, expeContents4);
    QCOMPARE(actualContents5, expeContents5);

    QSettings settings4(QSettings::IniFormat, QSettings::UserScope, "software.org", "KillerAPP");
    settings4.setIniCodec("UTF-8");
    QSettings settings5(QSettings::IniFormat, QSettings::UserScope, "other.software.org", "KillerAPP");
    settings5.setIniCodec("Latin-1");

    QCOMPARE(settings4.allKeys().count(), 1);
    QCOMPARE(settings5.allKeys().count(), 1);

    QCOMPARE(settings4.allKeys().first(), settings5.allKeys().first());
    QCOMPARE(settings4.value(settings4.allKeys().first()).toString(),
             settings5.value(settings5.allKeys().first()).toString());
}
#endif

static bool containsSubList(QStringList mom, QStringList son)
{
    for (int i = 0; i < son.size(); ++i) {
        if (!mom.contains(son.at(i)))
            return false;
    }
    return true;
}

void tst_QSettings::testArrays_data()
{
    populateWithFormats();
}

/*
    Tests beginReadArray(), beginWriteArray(), endArray(), and
    setArrayIndex().
*/
void tst_QSettings::testArrays()
{
    QFETCH(QSettings::Format, format);

    {
        QSettings settings1(format, QSettings::UserScope, "software.org", "KillerAPP");

        settings1.beginWriteArray("foo/bar", 3);
        settings1.setValue("bip", 1);
        settings1.setArrayIndex(0);
        settings1.setValue("ene", 2);
        settings1.setValue("due", 3);
        settings1.setValue("rike", 4);
        settings1.setArrayIndex(1);
        settings1.setValue("ene", 5);
        settings1.setValue("due", 6);
        settings1.setValue("rike", 7);
        settings1.setArrayIndex(2);
        settings1.setValue("ene", 8);
        settings1.setValue("due", 9);
        settings1.setValue("rike", 10);
        settings1.endArray();

        QStringList expectedList;
        expectedList
            << "foo/bar/bip"
            << "foo/bar/size"
            << "foo/bar/1/ene"
            << "foo/bar/1/due"
            << "foo/bar/1/rike"
            << "foo/bar/2/ene"
            << "foo/bar/2/due"
            << "foo/bar/2/rike"
            << "foo/bar/3/ene"
            << "foo/bar/3/due"
            << "foo/bar/3/rike";
        expectedList.sort();

        QStringList actualList = settings1.allKeys();
        actualList.sort();
        QVERIFY(containsSubList(actualList, expectedList));

        QCOMPARE(settings1.value("/foo/bar/bip").toInt(), 1);
        QCOMPARE(settings1.value("/foo/bar/1/ene").toInt(), 2);
        QCOMPARE(settings1.value("/foo/bar/1/due").toInt(), 3);
        QCOMPARE(settings1.value("/foo/bar/1/rike").toInt(), 4);
        QCOMPARE(settings1.value("/foo/bar/2/ene").toInt(), 5);
        QCOMPARE(settings1.value("/foo/bar/2/due").toInt(), 6);
        QCOMPARE(settings1.value("/foo/bar/2/rike").toInt(), 7);
        QCOMPARE(settings1.value("/foo/bar/3/ene").toInt(), 8);
        QCOMPARE(settings1.value("/foo/bar/3/due").toInt(), 9);
        QCOMPARE(settings1.value("/foo/bar/3/rike").toInt(), 10);

        settings1.beginGroup("/foo");
        int count = settings1.beginReadArray("bar");
        QCOMPARE(count, 3);
        QCOMPARE(settings1.value("bip").toInt(), 1);
        settings1.setArrayIndex(0);
        QCOMPARE(settings1.value("ene").toInt(), 2);
        QCOMPARE(settings1.value("due").toInt(), 3);
        QCOMPARE(settings1.value("rike").toInt(), 4);
        QCOMPARE(settings1.allKeys().count(), 3);
        settings1.setArrayIndex(1);
        QCOMPARE(settings1.value("ene").toInt(), 5);
        QCOMPARE(settings1.value("due").toInt(), 6);
        QCOMPARE(settings1.value("rike").toInt(), 7);
        QCOMPARE(settings1.allKeys().count(), 3);
        settings1.setArrayIndex(2);
        QCOMPARE(settings1.value("ene").toInt(), 8);
        QCOMPARE(settings1.value("due").toInt(), 9);
        QCOMPARE(settings1.value("rike").toInt(), 10);
        QCOMPARE(settings1.allKeys().count(), 3);

        settings1.endArray();
        settings1.endGroup();
    }
    /*
        Check that we get the arrays right when we load them again
    */

    {
        QSettings settings1(format, QSettings::UserScope, "software.org", "KillerAPP");

        QStringList expectedList;
        expectedList
            << "foo/bar/bip"
            << "foo/bar/size"
            << "foo/bar/1/ene"
            << "foo/bar/1/due"
            << "foo/bar/1/rike"
            << "foo/bar/2/ene"
            << "foo/bar/2/due"
            << "foo/bar/2/rike"
            << "foo/bar/3/ene"
            << "foo/bar/3/due"
            << "foo/bar/3/rike";
        expectedList.sort();

        QStringList actualList = settings1.allKeys();
        actualList.sort();
        QVERIFY(containsSubList(actualList, expectedList));

        QCOMPARE(settings1.value("/foo/bar/bip").toInt(), 1);
        QCOMPARE(settings1.value("/foo/bar/1/ene").toInt(), 2);
        QCOMPARE(settings1.value("/foo/bar/1/due").toInt(), 3);
        QCOMPARE(settings1.value("/foo/bar/1/rike").toInt(), 4);
        QCOMPARE(settings1.value("/foo/bar/2/ene").toInt(), 5);
        QCOMPARE(settings1.value("/foo/bar/2/due").toInt(), 6);
        QCOMPARE(settings1.value("/foo/bar/2/rike").toInt(), 7);
        QCOMPARE(settings1.value("/foo/bar/3/ene").toInt(), 8);
        QCOMPARE(settings1.value("/foo/bar/3/due").toInt(), 9);
        QCOMPARE(settings1.value("/foo/bar/3/rike").toInt(), 10);

        settings1.beginGroup("/foo");
        int count = settings1.beginReadArray("bar");
        QCOMPARE(count, 3);
        QCOMPARE(settings1.value("bip").toInt(), 1);
        settings1.setArrayIndex(0);
        QCOMPARE(settings1.value("ene").toInt(), 2);
        QCOMPARE(settings1.value("due").toInt(), 3);
        QCOMPARE(settings1.value("rike").toInt(), 4);
        QCOMPARE(settings1.allKeys().count(), 3);
        settings1.setArrayIndex(1);
        QCOMPARE(settings1.value("ene").toInt(), 5);
        QCOMPARE(settings1.value("due").toInt(), 6);
        QCOMPARE(settings1.value("rike").toInt(), 7);
        QCOMPARE(settings1.allKeys().count(), 3);
        settings1.setArrayIndex(2);
        QCOMPARE(settings1.value("ene").toInt(), 8);
        QCOMPARE(settings1.value("due").toInt(), 9);
        QCOMPARE(settings1.value("rike").toInt(), 10);
        QCOMPARE(settings1.allKeys().count(), 3);

        settings1.endArray();
        settings1.endGroup();
    }
    /*
        This code generates lots of warnings, but that's on purpose.
        Basically, we check that endGroup() can be used instead of
        endArray() and vice versa. This is not documented, but this
        is the behavior that we have chosen.
    */
    QTest::ignoreMessage(QtWarningMsg, "QSettings::setArrayIndex: Missing beginArray()");
    QTest::ignoreMessage(QtWarningMsg, "QSettings::setArrayIndex: Missing beginArray()");
    QTest::ignoreMessage(QtWarningMsg, "QSettings::setArrayIndex: Missing beginArray()");
    QTest::ignoreMessage(QtWarningMsg, "QSettings::setArrayIndex: Missing beginArray()");
    QTest::ignoreMessage(QtWarningMsg, "QSettings::setArrayIndex: Missing beginArray()");
    QTest::ignoreMessage(QtWarningMsg, "QSettings::endArray: Expected endGroup() instead");
    QTest::ignoreMessage(QtWarningMsg, "QSettings::endGroup: Expected endArray() instead");
    QTest::ignoreMessage(QtWarningMsg, "QSettings::endArray: Expected endGroup() instead");
    QTest::ignoreMessage(QtWarningMsg, "QSettings::endGroup: No matching beginGroup()");

    QSettings settings1(format, QSettings::UserScope, "software.org", "KillerAPP");
    settings1.clear();
    settings1.beginGroup("/alpha");
    QCOMPARE(settings1.group(), QString("alpha"));
    settings1.setArrayIndex(0);
    QCOMPARE(settings1.group(), QString("alpha"));
    settings1.setArrayIndex(1);
    QCOMPARE(settings1.group(), QString("alpha"));
    settings1.setArrayIndex(2);
    QCOMPARE(settings1.group(), QString("alpha"));
    settings1.beginGroup("/beta");
    QCOMPARE(settings1.group(), QString("alpha/beta"));
    settings1.beginGroup("");
    QCOMPARE(settings1.group(), QString("alpha/beta"));
    settings1.beginWriteArray("DO", 4);
    QCOMPARE(settings1.value("size").toInt(), 4);
    QCOMPARE(settings1.group(), QString("alpha/beta/DO"));
    settings1.setArrayIndex(0);
    QCOMPARE(settings1.group(), QString("alpha/beta/DO/1"));
    settings1.setArrayIndex(1);
    QCOMPARE(settings1.group(), QString("alpha/beta/DO/2"));
    settings1.beginGroup("1");
    QCOMPARE(settings1.group(), QString("alpha/beta/DO/2/1"));
    settings1.setArrayIndex(3);
    QCOMPARE(settings1.group(), QString("alpha/beta/DO/2/1"));
    settings1.setArrayIndex(4);
    QCOMPARE(settings1.group(), QString("alpha/beta/DO/2/1"));
    settings1.beginWriteArray("RE");
    QVERIFY(!settings1.contains("size"));
    QCOMPARE(settings1.group(), QString("alpha/beta/DO/2/1/RE"));
    settings1.setArrayIndex(0);
    QCOMPARE(settings1.group(), QString("alpha/beta/DO/2/1/RE/1"));
    settings1.setArrayIndex(1);
    QCOMPARE(settings1.group(), QString("alpha/beta/DO/2/1/RE/2"));
    settings1.endArray();
    QCOMPARE(settings1.group(), QString("alpha/beta/DO/2/1"));
    settings1.endArray();
    QCOMPARE(settings1.group(), QString("alpha/beta/DO/2"));
    settings1.setArrayIndex(2);
    QCOMPARE(settings1.group(), QString("alpha/beta/DO/3"));
    settings1.endGroup();
    QCOMPARE(settings1.group(), QString("alpha/beta"));
    settings1.endGroup();
    QCOMPARE(settings1.group(), QString("alpha/beta"));
    settings1.endGroup();
    QCOMPARE(settings1.group(), QString("alpha"));
    settings1.endArray();
    QCOMPARE(settings1.group(), QString());
    settings1.endGroup();
    QCOMPARE(settings1.group(), QString());
    /*
        Now, let's make sure that things work well if an array
        is spread across multiple files.
    */
    int i;

    settings1.clear();
    QSettings settings2(format, QSettings::UserScope, "software.org");

    QStringList threeStrings;
    threeStrings << "Uno" << "Dos" << "Tres";

    QStringList fiveStrings;
    fiveStrings << "alpha" << "beta" << "gamma" << "delta" << "epsilon";

    settings1.beginWriteArray("strings");
    for (i = threeStrings.size() - 1; i >= 0; --i) {
        settings1.setArrayIndex(i);
        settings1.setValue("fileName", threeStrings.at(i));
    }
    settings1.endArray();

    settings2.beginWriteArray("strings");
    for (i = fiveStrings.size() - 1; i >= 0; --i) {
        settings2.setArrayIndex(i);
        settings2.setValue("fileName", fiveStrings.at(i));
    }
    settings2.endArray();

    int size1 = settings1.beginReadArray("strings");
    QCOMPARE(size1, 3);
    QCOMPARE(settings1.value("size").toInt(), 3);

    for (i = 0; i < size1; ++i) {
        settings1.setArrayIndex(i);
        QString str = settings1.value("fileName").toString();
        QCOMPARE(str, threeStrings.at(i));
    }
    settings1.endArray();

    int size2 = settings2.beginReadArray("strings");
    QCOMPARE(size2, 5);
    QCOMPARE(settings2.value("size").toInt(), 5);

    for (i = 0; i < size2; ++i) {
        settings2.setArrayIndex(i);
        QString str = settings2.value("fileName").toString();
        QCOMPARE(str, fiveStrings.at(i));
    }
    settings2.endArray();

    size1 = settings1.beginReadArray("strings");
    QCOMPARE(size1, 3);

    // accessing entries beyond the end of settings1 goes to settings2
    for (i = size1; i < size2; ++i) {
        settings1.setArrayIndex(i);
        QString str = settings1.value("fileName").toString();
        QCOMPARE(str, fiveStrings.at(i));
    }
    settings1.endArray();
}

#ifdef QT_BUILD_INTERNAL
static QByteArray iniEscapedKey(const QString &str)
{
    QByteArray result;
    QSettingsPrivate::iniEscapedKey(str, result);
    return result;
}

static QString iniUnescapedKey(const QByteArray &ba)
{
    QString result;
    QSettingsPrivate::iniUnescapedKey(ba, 0, ba.size(), result);
    return result;
}

static QByteArray iniEscapedStringList(const QStringList &strList)
{
    QByteArray result;
    QSettingsPrivate::iniEscapedStringList(strList, result, 0);
    return result;
}

static QStringList iniUnescapedStringList(const QByteArray &ba)
{
    QStringList result;
    QString str;
#if QSETTINGS_P_H_VERSION >= 2
    bool isStringList = QSettingsPrivate::iniUnescapedStringList(ba, 0, ba.size(), str, result
#if QSETTINGS_P_H_VERSION >= 3
                                                                 , 0
#endif
                                                                    );
    if (!isStringList)
        result = QStringList(str);
#else
    QStringList *strList = QSettingsPrivate::iniUnescapedStringList(ba, 0, ba.size(), str);
    if (strList) {
        result = *strList;
        delete strList;
    } else {
        result = QStringList(str);
    }
#endif
    return result;
}
#endif

QString escapeWeirdChars(const QString &s)
{
    QString result;
    bool escapeNextDigit = false;

    for (int i = 0; i < s.length(); ++i) {
        QChar c = s.at(i);
        if (c.unicode() < ' ' || c.unicode() > '~'
            || (escapeNextDigit && c.unicode() >= '0' && c.unicode() <= 'f')) {
            result += QLatin1String("\\x") + QString::number(c.unicode(), 16);
            escapeNextDigit = true;
        } else {
            result += c;
            escapeNextDigit = false;
        }
    }

    return result;
}

#ifdef QT_BUILD_INTERNAL
void tst_QSettings::testEscapes()
{
    QSettings settings(QSettings::UserScope, "software.org", "KillerAPP");

#define testEscapedKey(plainKey, escKey) \
    QCOMPARE(iniEscapedKey(plainKey), QByteArray(escKey)); \
    QCOMPARE(iniUnescapedKey(escKey), QString(plainKey));

#define testUnescapedKey(escKey, plainKey, reescKey) \
    QCOMPARE(iniUnescapedKey(escKey), QString(plainKey)); \
    QCOMPARE(iniEscapedKey(plainKey), QByteArray(reescKey)); \
    QCOMPARE(iniUnescapedKey(reescKey), QString(plainKey));

#define testEscapedStringList(plainStrList, escStrList) \
    { \
        QStringList plainList(plainStrList); \
        QByteArray escList(escStrList); \
        QCOMPARE(iniEscapedStringList(plainList), escList); \
        QCOMPARE(iniUnescapedStringList(escList), plainList); \
    } \


#define testUnescapedStringList(escStrList, plainStrList, reescStrList) \
    { \
        QStringList plainList(plainStrList); \
        QByteArray escList(escStrList); \
        QByteArray reescList(reescStrList); \
        QCOMPARE(iniUnescapedStringList(escList), plainList); \
        QCOMPARE(iniEscapedStringList(plainList), reescList); \
        QCOMPARE(iniUnescapedStringList(reescList), plainList); \
    } \


#define testVariant(val, escStr, func) \
    { \
        QVariant v(val); \
        QString s = QSettingsPrivate::variantToString(v); \
        QCOMPARE(s, escStr); \
        QCOMPARE(QVariant(QSettingsPrivate::stringToVariant(escStr)), v); \
        QVERIFY((val) == v.func());                                     \
    }

#define testBadEscape(escStr, vStr) \
    { \
        QVariant v = QSettingsPrivate::stringToVariant(QString(escStr)); \
        QCOMPARE(v.toString(), QString(vStr)); \
    }

    testEscapedKey("", "");
    testEscapedKey(" ", "%20");
    testEscapedKey(" 0123 abcd ", "%200123%20abcd%20");
    testEscapedKey("~!@#$%^&*()_+.-/\\=", "%7E%21%40%23%24%25%5E%26%2A%28%29_%2B.-\\%5C%3D");
    testEscapedKey(QString() + QChar(0xabcd) + QChar(0x1234) + QChar(0x0081), "%UABCD%U1234%81");
    testEscapedKey(QString() + QChar(0xFE) + QChar(0xFF) + QChar(0x100) + QChar(0x101), "%FE%FF%U0100%U0101");

    testUnescapedKey("", "", "");
    testUnescapedKey("%20", " ", "%20");
    testUnescapedKey("/alpha/beta", "/alpha/beta", "\\alpha\\beta");
    testUnescapedKey("\\alpha\\beta", "/alpha/beta", "\\alpha\\beta");
    testUnescapedKey("%5Calpha%5Cbeta", "\\alpha\\beta", "%5Calpha%5Cbeta");
    testUnescapedKey("%", "%", "%25");
    testUnescapedKey("%f%!%%%%1x%x1%U%Uz%U123%U1234%1234%", QString("%f%!%%%%1x%x1%U%Uz%U123") + QChar(0x1234) + "\x12" + "34%",
                     "%25f%25%21%25%25%25%251x%25x1%25U%25Uz%25U123%U1234%1234%25");

    testEscapedStringList("", "");
    testEscapedStringList(" ", "\" \"");
    testEscapedStringList(";", "\";\"");
    testEscapedStringList(",", "\",\"");
    testEscapedStringList("=", "\"=\"");
    testEscapedStringList("abc-def", "abc-def");
    testEscapedStringList(QChar(0) + QString("0"), "\\0\\x30");
    testEscapedStringList("~!@#$%^&*()_+.-/\\=", "\"~!@#$%^&*()_+.-/\\\\=\"");
    testEscapedStringList("~!@#$%^&*()_+.-/\\", "~!@#$%^&*()_+.-/\\\\");
    testEscapedStringList(QString("\x7F") + "12aFz", "\\x7f\\x31\\x32\\x61\\x46z");
    testEscapedStringList(QString("   \t\n\\n") + QChar(0x123) + QChar(0x4567), "\"   \\t\\n\\\\n\\x123\\x4567\"");
    testEscapedStringList(QString("\a\b\f\n\r\t\v'\"?\001\002\x03\x04"), "\\a\\b\\f\\n\\r\\t\\v'\\\"?\\x1\\x2\\x3\\x4");
    testEscapedStringList(QStringList() << "," << ";" << "a" << "ab,  \tc, d ", "\",\", \";\", a, \"ab,  \\tc, d \"");

    /*
      Test .ini syntax that cannot be generated by QSettings (but can be entered by users).
    */
    testUnescapedStringList("", "", "");
    testUnescapedStringList("\"\"", "", "");
    testUnescapedStringList("\"abcdef\"", "abcdef", "abcdef");
    testUnescapedStringList("\"\\?\\'\\\"\"", "?'\"", "?'\\\"");
    testUnescapedStringList("\\0\\00\\000\\0000000\\1\\111\\11111\\x\\x0\\xABCDEFGH\\x0123456\\",
                            QString() + QChar(0) + QChar(0) + QChar(0) + QChar(0) + QChar(1)
                            + QChar(0111) + QChar(011111) + QChar(0) + QChar(0xCDEF) + "GH"
                            + QChar(0x3456),
                            "\\0\\0\\0\\0\\x1I\\x1249\\0\\xcdefGH\\x3456");
    testUnescapedStringList(QByteArray("\\c\\d\\e\\f\\g\\$\\*\\\0", 16), "\f", "\\f");
    testUnescapedStringList("\"a\",  \t\"bc \", \"  d\" , \"ef  \" ,,g,   hi  i,,, ,",
                            QStringList() << "a" << "bc " << "  d" << "ef  " << "" << "g" << "hi  i"
                                          << "" << "" << "" << "",
                            "a, \"bc \", \"  d\", \"ef  \", , g, hi  i, , , , ");
    testUnescapedStringList("a ,  b   ,   c   d   , efg   ",
                            QStringList() << "a" << "b" << "c   d" << "efg",
                            "a, b, c   d, efg");

    // streaming qvariant into a string
    testVariant(QString("Hello World!"), QString("Hello World!"), toString);
    testVariant(QString("Hello, World!"), QString("Hello, World!"), toString);
    testVariant(QString("@Hello World!"), QString("@@Hello World!"), toString);
    testVariant(QString("@@Hello World!"), QString("@@@Hello World!"), toString);
#if QT_DEPRECATED_SINCE(5, 15)
    testVariant(QByteArray("Hello World!"), QString("@ByteArray(Hello World!)"), toString);
    testVariant(QByteArray("@Hello World!"), QString("@ByteArray(@Hello World!)"), toString);
#endif
    testVariant(QVariant(100), QString("100"), toString);
    testVariant(QStringList() << "ene" << "due" << "rike", QString::fromLatin1("@Variant(\x0\x0\x0\xb\x0\x0\x0\x3\x0\x0\x0\x6\x0\x65\x0n\x0\x65\x0\x0\x0\x6\x0\x64\x0u\x0\x65\x0\x0\x0\x8\x0r\x0i\x0k\x0\x65)", 50), toStringList);
    testVariant(QRect(1, 2, 3, 4), QString("@Rect(1 2 3 4)"), toRect);
    testVariant(QSize(5, 6), QString("@Size(5 6)"), toSize);
    testVariant(QPoint(7, 8), QString("@Point(7 8)"), toPoint);

    testBadEscape("", "");
    testBadEscape("@", "@");
    testBadEscape("@@", "@");
    testBadEscape("@@@", "@@");
    testBadEscape(" ", " ");
    testBadEscape("@Rect", "@Rect");
    testBadEscape("@Rect(", "@Rect(");
    testBadEscape("@Rect()", "@Rect()");
    testBadEscape("@Rect)", "@Rect)");
    testBadEscape("@Rect(1 2 3)", "@Rect(1 2 3)");
    testBadEscape("@@Rect(1 2 3)", "@Rect(1 2 3)");
}
#endif

void tst_QSettings::testCaseSensitivity_data()
{
    populateWithFormats();
}

void tst_QSettings::testCaseSensitivity()
{
    QFETCH(QSettings::Format, format);

    for (int pass = 0; pass < 2; ++pass) {
        QSettings settings(format, QSettings::UserScope, "software.org", "KillerAPP");
        settings.beginGroup("caseSensitivity");

        bool cs = true;
#ifndef QT_QSETTINGS_ALWAYS_CASE_SENSITIVE_AND_FORGET_ORIGINAL_KEY_ORDER
        switch (format) {
        case QSettings::NativeFormat:
#ifdef Q_OS_DARWIN
            cs = true;
#else
            cs = false;
#endif
            break;
        case QSettings::IniFormat:
            cs = false;
            break;
        case QSettings::CustomFormat1:
            cs = true;
            break;
        case QSettings::CustomFormat2:
            cs = false;
            break;
        default:
            ;
        }
#endif

        if (pass == 0) {
            settings.setValue("key 1", 1);
            settings.setValue("KEY 1", 2);
            settings.setValue("key 2", 3);
        }

        for (int i = 0; i < 2; ++i) {
            QVERIFY(settings.contains("key 1"));
            QVERIFY(settings.contains("KEY 1"));
            QCOMPARE(settings.value("KEY 1").toInt(), 2);
/*            QVERIFY(settings.allKeys().contains("/KEY 1"));
            QVERIFY(settings.allKeys().contains("/key 2")); */

            if (cs) {
                QVERIFY(!settings.contains("kEy 1"));
                QCOMPARE(settings.value("key 1").toInt(), 1);
                QCOMPARE(settings.allKeys().size(), 3);
                QVERIFY(settings.allKeys().contains("key 1"));
            } else {
                QVERIFY(settings.contains("kEy 1"));
                QCOMPARE(settings.value("kEy 1").toInt(), 2);
                QCOMPARE(settings.value("key 1").toInt(), 2);
                QCOMPARE(settings.allKeys().size(), 2);
            }

            settings.sync();
        }

        settings.remove("KeY 1");

        if (cs) {
            QVERIFY(!settings.contains("KeY 1"));
            QVERIFY(settings.contains("key 1"));
            QVERIFY(settings.contains("KEY 1"));
            QCOMPARE(settings.value("key 1").toInt(), 1);
            QCOMPARE(settings.value("KEY 1").toInt(), 2);
            QCOMPARE(settings.allKeys().size(), 3);
        } else {
            QVERIFY(!settings.contains("KeY 1"));
            QVERIFY(!settings.contains("key 1"));
            QVERIFY(!settings.contains("KEY 1"));
            QCOMPARE(settings.allKeys().size(), 1);
        }
        settings.setValue("KEY 1", 2);
    }
}

#ifdef Q_OS_MAC
// Please write a fileName() test for the other platforms
void tst_QSettings::fileName()
{
    QSettings s1(QSettings::UserScope, "Apple", "Console");
    QSettings s2(QSettings::UserScope, "Apple");
    QSettings s3(QSettings::SystemScope, "Apple", "Console");
    QSettings s4(QSettings::SystemScope, "Apple");

    QCOMPARE(s1.fileName(), QDir::homePath() + "/Library/Preferences/com.apple.Console.plist");
    QCOMPARE(s2.fileName(), QDir::homePath() + "/Library/Preferences/com.apple.plist");
    QCOMPARE(s3.fileName(), QString("/Library/Preferences/com.apple.Console.plist"));
    QCOMPARE(s4.fileName(), QString("/Library/Preferences/com.apple.plist"));

    QSettings s5(QSettings::SystemScope, "Apple.com", "Console");
    QCOMPARE(s5.fileName(), QString("/Library/Preferences/com.apple.Console.plist"));

    QSettings s6(QSettings::SystemScope, "apple.com", "Console");
    QCOMPARE(s6.fileName(), QString("/Library/Preferences/com.apple.Console.plist"));

    QSettings s7(QSettings::SystemScope, "apple.Com", "Console");
    QCOMPARE(s7.fileName(), QString("/Library/Preferences/com.apple.Console.plist"));

    QSettings s8(QSettings::SystemScope, "apple.fr", "Console");
    QCOMPARE(s8.fileName(), QString("/Library/Preferences/fr.apple.Console.plist"));

    QSettings s9(QSettings::SystemScope, "apple.co.jp", "Console");
    QCOMPARE(s9.fileName(), QString("/Library/Preferences/jp.co.apple.Console.plist"));

    QSettings s10(QSettings::SystemScope, "apple.org", "Console");
    QCOMPARE(s10.fileName(), QString("/Library/Preferences/org.apple.Console.plist"));

    QSettings s11(QSettings::SystemScope, "apple.net", "Console");
    QCOMPARE(s11.fileName(), QString("/Library/Preferences/net.apple.Console.plist"));

    QSettings s12(QSettings::SystemScope, "apple.museum", "Console");
    QCOMPARE(s12.fileName(), QString("/Library/Preferences/museum.apple.Console.plist"));

    QSettings s13(QSettings::SystemScope, "apple.FR", "Console");
    QCOMPARE(s13.fileName(), QString("/Library/Preferences/fr.apple.Console.plist"));

    QSettings s14(QSettings::SystemScope, "apple.mUseum", "Console");
    QCOMPARE(s14.fileName(), QString("/Library/Preferences/museum.apple.Console.plist"));

    QSettings s15(QSettings::SystemScope, "apple.zz", "Console");
    QCOMPARE(s15.fileName(), QString("/Library/Preferences/zz.apple.Console.plist"));

    QSettings s15_prime(QSettings::SystemScope, "apple.foo", "Console");
    QCOMPARE(s15_prime.fileName(), QString("/Library/Preferences/com.apple-foo.Console.plist"));

    QSettings s16(QSettings::SystemScope, "apple.f", "Console");
    QCOMPARE(s16.fileName(), QString("/Library/Preferences/com.apple-f.Console.plist"));

    QSettings s17(QSettings::SystemScope, "apple.", "Console");
    QCOMPARE(s17.fileName(), QString("/Library/Preferences/com.apple.Console.plist"));

    QSettings s18(QSettings::SystemScope, "Foo, Inc.", "Console");
    QCOMPARE(s18.fileName(), QString("/Library/Preferences/com.foo-inc.Console.plist"));

    QSettings s19(QSettings::SystemScope, "Foo, Inc.com", "Console");
    QCOMPARE(s19.fileName(), QString("/Library/Preferences/com.foo, inc.Console.plist"));

    QSettings s20(QSettings::SystemScope, QLatin1String("   ") + QChar(0xbd) + QLatin1String("Foo//:/Barxxx  Baz!()#@.com"), "Console");
    QCOMPARE(s20.fileName(), QLatin1String("/Library/Preferences/com.   ") + QChar(0xbd) + QLatin1String("foo  : barxxx  baz!()#@.Console.plist"));

    QSettings s21(QSettings::SystemScope, QLatin1String("   ") + QChar(0xbd) + QLatin1String("Foo//:/Bar,,,  Baz!()#"), "Console");
    QCOMPARE(s21.fileName(), QString("/Library/Preferences/com.foo-bar-baz.Console.plist"));
}
#endif

void tst_QSettings::isWritable_data()
{
    populateWithFormats();
}

void tst_QSettings::isWritable()
{
    QFETCH(QSettings::Format, format);

    {
        QSettings s1(format, QSettings::UserScope, "software.org", "KillerAPP");
        s1.setValue("foo", 1);
        s1.sync();
        // that should create the file
    }

    {
        QSettings s1(format, QSettings::UserScope, "software.org", "KillerAPP");
        QVERIFY(s1.isWritable());
    }

    {
        QSettings s1(format, QSettings::SystemScope, "software.org", "KillerAPP");
        s1.setValue("foo", 1);
        s1.sync();
        // that should create the file, *if* we have the permissions
    }

    {
        QSettings s1(format, QSettings::SystemScope, "software.org", "KillerAPP");
        QSettings s2(format, QSettings::SystemScope, "software.org", "Something Different");
        QSettings s3(format, QSettings::SystemScope, "foo.org", "Something Different");

        if (s1.status() == QSettings::NoError && s1.contains("foo")) {
#if defined(Q_OS_MACX)
            QVERIFY(s1.isWritable());
            if (format == QSettings::NativeFormat) {
                QVERIFY(!s2.isWritable());
                QVERIFY(!s3.isWritable());
            } else {
                QVERIFY(s2.isWritable());
                QVERIFY(s3.isWritable());
            }
#else
            QVERIFY(s1.isWritable());
            QVERIFY(s2.isWritable());
            QVERIFY(s3.isWritable());
#endif
        } else {
            QVERIFY(!s1.isWritable());
            QVERIFY(!s2.isWritable());
            QVERIFY(!s3.isWritable());
        }
    }
}

#ifdef QT_BUILD_INTERNAL
void tst_QSettings::childGroups_data()
{
    populateWithFormats();
}
#endif

#ifdef QT_BUILD_INTERNAL
void tst_QSettings::childGroups()
{
    QFETCH(QSettings::Format, format);

    const QSettings::Scope scope = m_canWriteNativeSystemSettings ? QSettings::SystemScope : QSettings::UserScope;

    {
        QSettings settings(format, scope, "software.org");
        settings.setValue("alpha", "1");
        settings.setValue("alpha/a", "2");
        settings.setValue("alpha/b", "3");
        settings.setValue("alpha/c", "4");
        settings.setValue("beta", "5");
        settings.setValue("gamma", "6");
        settings.setValue("gamma/d", "7");
        settings.setValue("gamma/d/e", "8");
        settings.setValue("gamma/f/g", "9");
        settings.setValue("omicron/h/i/j/x", "10");
        settings.setValue("omicron/h/i/k/y", "11");
        settings.setValue("zeta/z", "12");
    }

    for (int pass = 0; pass < 3; ++pass) {
        QConfFile::clearCache();
        QSettings settings(format, scope, "software.org");
        settings.setFallbacksEnabled(false);
        if (pass == 1) {
            settings.value("gamma/d");
        } else if (pass == 2) {
            settings.value("gamma");
        }

        settings.beginGroup("gamma");
        QStringList childGroups = settings.childGroups();
        childGroups.sort();
        QCOMPARE(childGroups, QStringList() << "d" << "f");
        settings.beginGroup("d");
        QCOMPARE(settings.childGroups(), QStringList());
        settings.endGroup();
        settings.endGroup();

        settings.beginGroup("alpha");
        QCOMPARE(settings.childGroups(), QStringList());
        settings.endGroup();

        settings.beginGroup("d");
        QCOMPARE(settings.childGroups(), QStringList());
        settings.endGroup();

        settings.beginGroup("/omicron///h/i///");
        childGroups = settings.childGroups();
        childGroups.sort();
        QCOMPARE(childGroups, QStringList() << "j" << "k");
        settings.endGroup();

        settings.beginGroup("////");
        childGroups = settings.childGroups();
        childGroups.sort();
        QCOMPARE(childGroups, QStringList() << "alpha" << "gamma" << "omicron" << "zeta");
        settings.endGroup();

        childGroups = settings.childGroups();
        childGroups.sort();
        QCOMPARE(childGroups, QStringList() << "alpha" << "gamma" << "omicron" << "zeta");
    }
}
#endif

#ifdef QT_BUILD_INTERNAL
void tst_QSettings::childKeys_data()
{
    populateWithFormats();
}
#endif

#ifdef QT_BUILD_INTERNAL
void tst_QSettings::childKeys()
{
    QFETCH(QSettings::Format, format);

    const QSettings::Scope scope = m_canWriteNativeSystemSettings ? QSettings::SystemScope : QSettings::UserScope;

    {
        QSettings settings(format, scope, "software.org");
        settings.setValue("alpha", "1");
        settings.setValue("alpha/a", "2");
        settings.setValue("alpha/b", "3");
        settings.setValue("alpha/c", "4");
        settings.setValue("beta", "5");
        settings.setValue("gamma", "6");
        settings.setValue("gamma/d", "7");
        settings.setValue("gamma/d/e", "8");
        settings.setValue("gamma/f/g", "9");
        settings.setValue("omicron/h/i/j/x", "10");
        settings.setValue("omicron/h/i/k/y", "11");
        settings.setValue("zeta/z", "12");
    }

    for (int pass = 0; pass < 3; ++pass) {
        QConfFile::clearCache();
        QSettings settings(format, scope, "software.org");
        settings.setFallbacksEnabled(false);
        if (pass == 1) {
            settings.value("gamma/d");
        } else if (pass == 2) {
            settings.value("gamma");
        }

        settings.beginGroup("gamma");
        QCOMPARE(settings.childKeys(), QStringList() << "d");
        settings.beginGroup("d");
        QCOMPARE(settings.childKeys(), QStringList() << "e");
        settings.endGroup();
        settings.endGroup();

        settings.beginGroup("alpha");
        QStringList childKeys = settings.childKeys();
        childKeys.sort();
        QCOMPARE(childKeys, QStringList() << "a" << "b" << "c");
        settings.endGroup();

        settings.beginGroup("d");
        QCOMPARE(settings.childKeys(), QStringList());
        settings.endGroup();

        settings.beginGroup("/omicron///h/i///");
        QCOMPARE(settings.childKeys(), QStringList());
        settings.endGroup();

        settings.beginGroup("////");
        childKeys = settings.childKeys();
        childKeys.sort();
        QCOMPARE(childKeys, QStringList() << "alpha" << "beta" << "gamma");
        settings.endGroup();

        childKeys = settings.childKeys();
        childKeys.sort();
        QCOMPARE(childKeys, QStringList() << "alpha" << "beta" << "gamma");
    }
}
#endif

#ifdef QT_BUILD_INTERNAL
void tst_QSettings::allKeys_data()
{
    populateWithFormats();
}
#endif

#ifdef QT_BUILD_INTERNAL
void tst_QSettings::allKeys()
{
    QFETCH(QSettings::Format, format);

    QStringList allKeys;
    allKeys << "alpha" << "alpha/a" << "alpha/b" << "alpha/c" << "beta" << "gamma" << "gamma/d"
            << "gamma/d/e" << "gamma/f/g" << "omicron/h/i/j/x" << "omicron/h/i/k/y" << "zeta/z";

    const QSettings::Scope scope = m_canWriteNativeSystemSettings ? QSettings::SystemScope : QSettings::UserScope;

    {
        QSettings settings(format, scope, "software.org");
        for (int i = 0; i < allKeys.size(); ++i)
            settings.setValue(allKeys.at(i), QString::number(i + 1));
    }

    for (int pass = 0; pass < 3; ++pass) {
        QConfFile::clearCache();
        QSettings settings(format, scope, "software.org");
        settings.setFallbacksEnabled(false);

        if (pass == 1) {
            settings.value("gamma/d");
        } else if (pass == 2) {
            settings.value("gamma");
        }

        settings.beginGroup("gamma");
        QStringList keys = settings.allKeys();
        keys.sort();
        QCOMPARE(keys, QStringList() << "d" << "d/e" << "f/g");
        settings.beginGroup("d");
        QCOMPARE(settings.allKeys(), QStringList() << "e");
        settings.endGroup();
        settings.endGroup();

        settings.beginGroup("alpha");
        keys = settings.allKeys();
        keys.sort();
        QCOMPARE(keys, QStringList() << "a" << "b" << "c");
        settings.endGroup();

        settings.beginGroup("d");
        QCOMPARE(settings.allKeys(), QStringList());
        settings.endGroup();

        settings.beginGroup("/omicron///h/i///");
        keys = settings.allKeys();
        keys.sort();
        QCOMPARE(keys, QStringList() << "j/x" << "k/y");
        settings.endGroup();

        settings.beginGroup("////");
        keys = settings.allKeys();
        keys.sort();
        QCOMPARE(keys, allKeys);
        settings.endGroup();

        keys = settings.allKeys();
        keys.sort();
        QCOMPARE(keys, allKeys);
    }
}
#endif

void tst_QSettings::registerFormat()
{
    QSettings settings1(QSettings::IniFormat, QSettings::UserScope, "software.org", "KillerAPP");
    QSettings settings2(QSettings::CustomFormat1, QSettings::UserScope, "software.org", "KillerAPP");

    QString fileName = settings1.fileName();
    fileName.chop(3); // "ini";
    fileName.append("custom1");
    QCOMPARE(settings2.fileName(), fileName);

    // OK, let's see if it can read a generated file of a custom type
    // Beware: readCustom3File() and writeCustom3File() have unintuitive behavior
    // so we can test error handling

    QSettings::Format custom3 = QSettings::registerFormat("custom3", readCustom3File, writeCustom3File);
    QCOMPARE(custom3, QSettings::CustomFormat3);

    QDir dir(settingsPath());
    QVERIFY(dir.mkpath("someDir"));
    QFile f(dir.path()+"/someDir/someSettings.custom3");

    QVERIFY(f.open(QFile::WriteOnly));
    f.write("OK");
    f.close();

    {
        QSettings settings(settingsPath("someDir/someSettings.custom3"), QSettings::CustomFormat3);
        QCOMPARE(settings.status(), QSettings::NoError);
        QCOMPARE(settings.value("retval").toString(), QString("OK"));
        QVERIFY(settings.isWritable());
    }

    QVERIFY(f.open(QFile::WriteOnly));
    f.write("NotOK");
    f.close();

    {
        QSettings settings(settingsPath("someDir/someSettings.custom3"), QSettings::CustomFormat3);
        QCOMPARE(settings.status(), QSettings::FormatError);
        QCOMPARE(settings.value("retval").toString(), QString());
        QVERIFY(settings.isWritable());
    }

    QVERIFY(f.open(QFile::WriteOnly));
    f.write("OK");
    f.close();

    {
        QSettings settings(settingsPath("someDir/someSettings.custom3"), QSettings::CustomFormat3);
        QCOMPARE(settings.status(), QSettings::NoError);
        settings.setValue("zzz", "bar");
        settings.sync();
        QCOMPARE(settings.status(), QSettings::NoError);

        settings.setValue("retval", "NotOK");
        settings.sync();
        QCOMPARE(settings.status(), QSettings::AccessError);

        QCOMPARE(settings.value("retval").toString(), QString("NotOK"));
        QVERIFY(settings.isWritable());
    }

    {
        QSettings settings(settingsPath("someDir/someSettings.custom3"), QSettings::CustomFormat4);
        QCOMPARE(settings.status(), QSettings::AccessError);
        QVERIFY(!settings.isWritable());
    }
}

void tst_QSettings::setPath()
{
#define TEST_PATH(doSet, ext, format, scope, path) \
    { \
    if (doSet) \
        QSettings::setPath(QSettings::format, QSettings::scope, settingsPath(path)); \
    QSettings settings1(QSettings::format, QSettings::scope, "software.org", "KillerAPP"); \
    QCOMPARE(QDir(settings1.fileName()), QDir(settingsPath(path) + QDir::separator() + "software.org" \
                                  + QDir::separator() + "KillerAPP." + ext)); \
    }

    /*
        The first pass checks that setPath() works; the second
        path checks that it has no bad side effects.
    */
    for (int i = 0; i < 2; ++i) {
#if !defined(Q_OS_WIN) && !defined(Q_OS_MAC)
        TEST_PATH(i == 0, "conf", NativeFormat, UserScope, "alpha")
        TEST_PATH(i == 0, "conf", NativeFormat, SystemScope, "beta")
#endif
        TEST_PATH(i == 0, "ini", IniFormat, UserScope, "gamma")
        TEST_PATH(i == 0, "ini", IniFormat, SystemScope, "omicron")
        TEST_PATH(i == 0, "custom1", CustomFormat1, UserScope, "epsilon")
        TEST_PATH(i == 0, "custom1", CustomFormat1, SystemScope, "zeta")
        TEST_PATH(i == 0, "custom2", CustomFormat2, UserScope, "eta")
        TEST_PATH(i == 0, "custom2", CustomFormat2, SystemScope, "iota")
    }
}

void tst_QSettings::setDefaultFormat()
{
    QCOMPARE(QSettings::defaultFormat(), QSettings::NativeFormat);

    QSettings::setDefaultFormat(QSettings::CustomFormat1);
    QSettings settings1("org", "app");
    QSettings settings2(QSettings::SystemScope, "org", "app");
    QSettings settings3;

    QCOMPARE(settings1.format(), QSettings::NativeFormat);
    QCOMPARE(settings2.format(), QSettings::NativeFormat);
    QCOMPARE(settings3.format(), QSettings::CustomFormat1);

    QSettings::setDefaultFormat(QSettings::NativeFormat);
    QCOMPARE(QSettings::defaultFormat(), QSettings::NativeFormat);

    QCOMPARE(settings1.format(), QSettings::NativeFormat);
    QCOMPARE(settings2.format(), QSettings::NativeFormat);
    QCOMPARE(settings3.format(), QSettings::CustomFormat1);
}

void tst_QSettings::dontCreateNeedlessPaths()
{
    QString path;
    {
        QSettings settings(QSettings::IniFormat, QSettings::UserScope, "Hello", "Test");
        QVariant val = settings.value("foo", "bar");
        path = settings.fileName();
    }

    QFileInfo fileInfo(path);
    QVERIFY(!fileInfo.dir().exists());
}

#if !defined(Q_OS_WIN) && !defined(QT_QSETTINGS_ALWAYS_CASE_SENSITIVE_AND_FORGET_ORIGINAL_KEY_ORDER)
// This Qt build does not preserve ordering, as a code size optimization.
void tst_QSettings::dontReorderIniKeysNeedlessly()
{

    /*
        This is a very strong test. It asserts that modifying
        resourcefile2.ini will lead to the exact contents of
        resourcefile3.ini. Right now it's run only on Unix
        systems, but that should be enough since the INI
        code (unlike this test) is platform-agnostic.

        Things that are tested:

            * keys are written in the same order that they were
              read in

            * new keys are put at the end of their respective
              sections
    */

    QFile inFile(":/resourcefile2.ini");
    inFile.open(QIODevice::ReadOnly);
    QByteArray contentsBefore = inFile.readAll();
    inFile.close();

    QByteArray expectedContentsAfter;

    {
        QFile inFile(":/resourcefile3.ini");
        inFile.open(QIODevice::ReadOnly);
        expectedContentsAfter = inFile.readAll();
        inFile.close();
    }

    QString outFileName;
    QString outFileName2;

    QTemporaryFile outFile;
    QVERIFY2(outFile.open(), qPrintable(outFile.errorString()));
    outFile.write(contentsBefore);
    outFileName = outFile.fileName();
    outFile.close();

    QSettings settings(outFileName, QSettings::IniFormat);
    QCOMPARE(settings.status(), QSettings::NoError);
    QVERIFY(settings.isWritable());

    settings.setValue("Field 1/Bottom", 90);
    settings.setValue("Field 1/x", 1);
    settings.setValue("Field 1/y", 1);
    settings.setValue("Field 1/width", 1);
    settings.setValue("Field 1/height", 1);
    settings.sync();

    QFile outFile2(outFileName);
    QVERIFY(outFile2.open(QIODevice::ReadOnly));
    QCOMPARE(outFile2.readAll(), expectedContentsAfter);
    outFile2.close();
}
#endif

void tst_QSettings::rainersSyncBugOnMac_data()
{
    ctor_data();
}

void tst_QSettings::rainersSyncBugOnMac()
{
    QFETCH(QSettings::Format, format);

#if defined(Q_OS_DARWIN) || defined(Q_OS_WINRT)
    if (format == QSettings::NativeFormat)
        QSKIP("Apple OSes do not support direct reads from and writes to .plist files, due to caching and background syncing. See QTBUG-34899.");
#endif

    QString fileName;

    {
        QSettings s1(format, QSettings::UserScope, "software.org", "KillerAPP");
        QCOMPARE(s1.value("key1", 5).toInt(), 5);
        fileName = s1.fileName();
    }

    {
        QSettings s2(fileName, format);
        s2.setValue("key1", 25);
    }

    {
        QSettings s3(format, QSettings::UserScope, "software.org", "KillerAPP");
        QCOMPARE(s3.value("key1", 30).toInt(), 25);
    }
}

void tst_QSettings::recursionBug()
{
    QPixmap pix(10,10);
    pix.fill("blue");

    {
        QSettings settings(settingsPath("starrunner.ini"), QSettings::IniFormat);
        settings.setValue("General/Pixmap", pix );
    }
}

#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT)

static DWORD readKeyType(HKEY handle, QStringView rSubKey)
{
    DWORD dataType;
    DWORD dataSize;
    LONG res = RegQueryValueEx(handle, reinterpret_cast<const wchar_t *>(rSubKey.utf16()),
                               nullptr, &dataType, nullptr, &dataSize);
    return res == ERROR_SUCCESS ? dataType : 0;
}

// This is a regression test for QTBUG-13249, where QSettings was storing
// signed integers as numeric values and unsigned integers as strings.
void tst_QSettings::consistentRegistryStorage()
{
    QSettings settings1(QSettings::UserScope, "software.org", "KillerAPP");

    qint32 x = 1024;
    settings1.setValue("qint32_value", (qint32)x);
    QCOMPARE(settings1.value("qint32_value").toInt(), (qint32)1024);
    settings1.setValue("quint32_value", (quint32)x);
    QCOMPARE(settings1.value("quint32_value").toUInt(), (quint32)1024);
    settings1.setValue("qint64_value", (qint64)x);
    QCOMPARE(settings1.value("qint64_value").toLongLong(), (qint64)1024);
    settings1.setValue("quint64_value", (quint64)x);
    QCOMPARE(settings1.value("quint64_value").toULongLong(), (quint64)1024);
    settings1.sync();

    QWinRegistryKey handle(HKEY_CURRENT_USER, LR"(Software\software.org\KillerAPP)");
    if (handle.isValid()) {
        QCOMPARE(readKeyType(handle, L"qint32_value"), DWORD(REG_DWORD));
        QCOMPARE(readKeyType(handle, L"quint32_value"), DWORD(REG_DWORD));
        QCOMPARE(readKeyType(handle, L"qint64_value"), DWORD(REG_QWORD));
        QCOMPARE(readKeyType(handle, L"quint64_value"), DWORD(REG_QWORD));
        RegCloseKey(handle);
    }
}
#endif

#if defined(QT_BUILD_INTERNAL) && defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) && !defined(Q_OS_ANDROID) && !defined(QT_NO_STANDARDPATHS)
QT_BEGIN_NAMESPACE
extern void clearDefaultPaths();
QT_END_NAMESPACE
#endif
void tst_QSettings::testXdg()
{
#if defined(QT_BUILD_INTERNAL) && defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) && !defined(Q_OS_ANDROID) && !defined(QT_NO_STANDARDPATHS)
    // Note: The XDG_CONFIG_DIRS test must be done before overriding the system path
    // by QSettings::setPath/setSystemIniPath (used in cleanupTestFiles()).
    clearDefaultPaths();

    // Initialize the env. variable & populate testing files.
    const QStringList config_dirs = { settingsPath("xdg_1st"), settingsPath("xdg_2nd"), settingsPath("xdg_3rd") };
    qputenv("XDG_CONFIG_DIRS", config_dirs.join(':').toUtf8());
    QList<QSettings *> xdg_orgs, xdg_apps;
    for (const auto & dir : config_dirs) {
        xdg_orgs << new QSettings{dir + "/software.org.conf", QSettings::NativeFormat};
        xdg_apps << new QSettings{dir + "/software.org/KillerAPP.conf", QSettings::NativeFormat};
    }
    Q_ASSERT(config_dirs.size() == 3 && xdg_orgs.size() == 3 && xdg_apps.size() == 3);
    for (int i = 0; i < 3; ++i) {
        xdg_orgs[i]->setValue("all", QString{"all_org%1"}.arg(i));
        xdg_apps[i]->setValue("all", QString{"all_app%1"}.arg(i));
        xdg_orgs[i]->setValue("all_only_org", QString{"all_only_org%1"}.arg(i));
        xdg_apps[i]->setValue("all_only_app", QString{"all_only_app%1"}.arg(i));

        if (i > 0) {
            xdg_orgs[i]->setValue("from2nd", QString{"from2nd_org%1"}.arg(i));
            xdg_apps[i]->setValue("from2nd", QString{"from2nd_app%1"}.arg(i));
            xdg_orgs[i]->setValue("from2nd_only_org", QString{"from2nd_only_org%1"}.arg(i));
            xdg_apps[i]->setValue("from2nd_only_app", QString{"from2nd_only_app%1"}.arg(i));
        }

        if (i > 1) {
            xdg_orgs[i]->setValue("from3rd", QString{"from3rd_org%1"}.arg(i));
            xdg_apps[i]->setValue("from3rd", QString{"from3rd_app%1"}.arg(i));
            xdg_orgs[i]->setValue("from3rd_only_org", QString{"from3rd_only_org%1"}.arg(i));
            xdg_apps[i]->setValue("from3rd_only_app", QString{"from3rd_only_app%1"}.arg(i));
        }
    }
    qDeleteAll(xdg_apps);
    qDeleteAll(xdg_orgs);

    // Do the test.
    QSettings app{QSettings::SystemScope, "software.org", "KillerAPP"}, org{QSettings::SystemScope, "software.org"};

    QVERIFY(app.value("all").toString() == "all_app0");
    QVERIFY(org.value("all").toString() == "all_org0");
    QVERIFY(app.value("all_only_org").toString() == "all_only_org0");
    QVERIFY(org.value("all_only_org").toString() == "all_only_org0");
    QVERIFY(app.value("all_only_app").toString() == "all_only_app0");
    QVERIFY(org.value("all_only_app").toString() == QString{});

    QVERIFY(app.value("from2nd").toString() == "from2nd_app1");
    QVERIFY(org.value("from2nd").toString() == "from2nd_org1");
    QVERIFY(app.value("from2nd_only_org").toString() == "from2nd_only_org1");
    QVERIFY(org.value("from2nd_only_org").toString() == "from2nd_only_org1");
    QVERIFY(app.value("from2nd_only_app").toString() == "from2nd_only_app1");
    QVERIFY(org.value("from2nd_only_app").toString() == QString{});

    QVERIFY(app.value("from3rd").toString() == "from3rd_app2");
    QVERIFY(org.value("from3rd").toString() == "from3rd_org2");
    QVERIFY(app.value("from3rd_only_org").toString() == "from3rd_only_org2");
    QVERIFY(org.value("from3rd_only_org").toString() == "from3rd_only_org2");
    QVERIFY(app.value("from3rd_only_app").toString() == "from3rd_only_app2");
    QVERIFY(org.value("from3rd_only_app").toString() == QString{});
#else
    QSKIP("This test is performed in QT_BUILD_INTERNAL on Q_XDG_PLATFORM with use of standard paths only.");
#endif
}

QTEST_MAIN(tst_QSettings)
#include "tst_qsettings.moc"
