<commit_before>/*
    Copyright 2017 <NAME> <<EMAIL>>
    Copyright 2017 <NAME> <<EMAIL>>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) version 3, or any
    later version accepted by the membership of KDE e.V. (or its
    successor approved by the membership of KDE e.V.), which shall
    act as a proxy defined in Section 6 of version 3 of the license.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/

#include <QObject>
#include <QtTest>

#include <QSettings> // parsing the ini files as desktop files
#include "testhelpers.h"

// lift a bit of code from KIconLoader to get the unit test running without tier 3 libraries
class KIconLoaderDummy : public QObject
{
Q_OBJECT
public:
    enum Context {
        Any,
        Action,
        Application,
        Device,
        FileSystem,
        MimeType,
        Animation,
        Category,
        Emblem,
        Emote,
        International,
        Place,
        StatusIcon
    };
    Q_ENUM(Context)
    enum Type {
        Fixed,
        Scalable,
        Threshold
    };
    Q_ENUM(Type)
};


/**
 * Represents icon directory to conduct simple icon lookup within.
 */
class Dir
{
public:
    Dir(const QSettings &cg, const QString &themeDir_)
        :
          themeDir(themeDir_)
        , path(cg.group())
        , size(cg.value("Size", 0).toInt())
        , contextString(cg.value("Context", QString()).toString())
        , context(parseContext(contextString))
        , type(parseType(cg.value("Type", QString("Threshold")).toString()))
    {
        QVERIFY2(!contextString.isEmpty(),
                 QString("Missing 'Context' key in file %1, config group '[%2]'").arg(cg.fileName(), cg.group()).toLatin1().constData());
        QVERIFY2(context != -1,
                 QString("Don't know how to handle 'Context=%1' in file %2, config group '[%3]'").arg(contextString, cg.fileName(), cg.group()).toLatin1().constData());
    }

    static QMetaEnum findEnum(const char *name)
    {
        auto mo = KIconLoaderDummy::staticMetaObject;
        for (int i = 0; i < mo.enumeratorCount(); ++i) {
            auto enumerator = mo.enumerator(i);
            if (strcmp(enumerator.name(), name) == 0) {
                return KIconLoaderDummy::staticMetaObject.enumerator(i);
            }
        }
        Q_ASSERT(false); // failed to resolve enum
        return QMetaEnum();
    }

    static QMetaEnum typeEnum()
    {
        static auto e = findEnum("Type");
        return e;
    }

    static KIconLoaderDummy::Context parseContext(const QString &string)
    {
        // Can't use QMetaEnum as the enum names are singular, the entry values are plural though.
        static QHash<QString, int> hash {
            { QStringLiteral("Actions"), KIconLoaderDummy::Action },
            { QStringLiteral("Animations"), KIconLoaderDummy::Animation },
            { QStringLiteral("Applications"), KIconLoaderDummy::Application },
            { QStringLiteral("Categories"), KIconLoaderDummy::Category },
            { QStringLiteral("Devices"), KIconLoaderDummy::Device },
            { QStringLiteral("Emblems"), KIconLoaderDummy::Emblem },
            { QStringLiteral("Emotes"), KIconLoaderDummy::Emote },
            { QStringLiteral("MimeTypes"), KIconLoaderDummy::MimeType },
            { QStringLiteral("Places"), KIconLoaderDummy::Place },
            { QStringLiteral("Status"), KIconLoaderDummy::StatusIcon },
        };
        const auto value = hash.value(string, -1);
        return static_cast<KIconLoaderDummy::Context>(value); // the caller will check that it wasn't -1
    }

    static KIconLoaderDummy::Type parseType(const QString &string)
    {
        bool ok;
        auto v = (KIconLoaderDummy::Type)typeEnum().keyToValue(string.toLatin1().constData(), &ok);
        Q_ASSERT(ok);
        return v;
    }

    /**
     * @returns list of all icon's fileinfo (first level only, selected types
     *          only)
     */
    QList<QFileInfo> allIcons()
    {
        QList<QFileInfo> icons;
        auto iconDir = QString("%1/%2").arg(themeDir).arg(path);
        QDirIterator it(iconDir);
        while (it.hasNext()) {
            it.next();
            auto suffix = it.fileInfo().suffix();
            if (suffix != "svg" && suffix != "svgz" && suffix != "png") {
                continue; // Probably not an icon.
            }
            icons << it.fileInfo();
        }
        return icons;
    }

    QString themeDir;
    QString path;
    uint size;
    QString contextString;
    KIconLoaderDummy::Context context;
    KIconLoaderDummy::Type type;
};

// Declare so we can put them into the QTest data table.
Q_DECLARE_METATYPE(KIconLoaderDummy::Context)
Q_DECLARE_METATYPE(QSharedPointer<Dir>)

class ScalableTest : public QObject
{
    Q_OBJECT

private Q_SLOTS:
    void test_scalable_data()
    {
        for (auto dir : ICON_DIRS) {
            auto themeDir = PROJECT_SOURCE_DIR + QStringLiteral("/") + dir;

            QHash<KIconLoaderDummy::Context, QList<QSharedPointer<Dir>>> contextHash;
            QHash<KIconLoaderDummy::Context, QString> contextStringHash;

            QSettings config(themeDir + "/index.theme", QSettings::IniFormat);
            auto keys = config.allKeys();
            config.beginGroup("Icon Theme");
            auto directoryPaths = config.value("Directories", QString()).toStringList();
            config.endGroup();
            QVERIFY(!directoryPaths.isEmpty());
            for (auto directoryPath : directoryPaths) {
                config.beginGroup(directoryPath);
                QVERIFY2(keys.contains(directoryPath+"/Size"),QString("The theme %1 has an entry 'Directories' which specifies '%2' as directory, but there's no"
                                                                                         " have no associated entry '%2/Size'").arg(themeDir + "/index.theme", directoryPath).toLatin1().constData());
                auto dir = QSharedPointer<Dir>::create(config, themeDir);<commit_msg>Fix "stack-use-after-scope" detected by ASAN in CI<commit_after>/*
    Copyright 2017 <NAME> <<EMAIL>>
    Copyright 2017 <NAME> <<EMAIL>>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) version 3, or any
    later version accepted by the membership of KDE e.V. (or its
    successor approved by the membership of KDE e.V.), which shall
    act as a proxy defined in Section 6 of version 3 of the license.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/

#include <QObject>
#include <QtTest>

#include <QSettings> // parsing the ini files as desktop files
#include "testhelpers.h"

// lift a bit of code from KIconLoader to get the unit test running without tier 3 libraries
class KIconLoaderDummy : public QObject
{
Q_OBJECT
public:
    enum Context {
        Any,
        Action,
        Application,
        Device,
        FileSystem,
        MimeType,
        Animation,
        Category,
        Emblem,
        Emote,
        International,
        Place,
        StatusIcon
    };
    Q_ENUM(Context)
    enum Type {
        Fixed,
        Scalable,
        Threshold
    };
    Q_ENUM(Type)
};


/**
 * Represents icon directory to conduct simple icon lookup within.
 */
class Dir
{
public:
    Dir(const QSettings &cg, const QString &themeDir_)
        :
          themeDir(themeDir_)
        , path(cg.group())
        , size(cg.value("Size", 0).toInt())
        , contextString(cg.value("Context", QString()).toString())
        , context(parseContext(contextString))
        , type(parseType(cg.value("Type", QString("Threshold")).toString()))
    {
        QVERIFY2(!contextString.isEmpty(),
                 QString("Missing 'Context' key in file %1, config group '[%2]'").arg(cg.fileName(), cg.group()).toLatin1().constData());
        QVERIFY2(context != -1,
                 QString("Don't know how to handle 'Context=%1' in file %2, config group '[%3]'").arg(contextString, cg.fileName(), cg.group()).toLatin1().constData());
    }

    static QMetaEnum findEnum(const char *name)
    {
        auto mo = KIconLoaderDummy::staticMetaObject;
        for (int i = 0; i < mo.enumeratorCount(); ++i) {
            auto enumerator = mo.enumerator(i);
            if (strcmp(enumerator.name(), name) == 0) {
                return KIconLoaderDummy::staticMetaObject.enumerator(i);
            }
        }
        Q_ASSERT(false); // failed to resolve enum
        return QMetaEnum();
    }

    static QMetaEnum typeEnum()
    {
        static auto e = findEnum("Type");
        return e;
    }

    static KIconLoaderDummy::Context parseContext(const QString &string)
    {
        // Can't use QMetaEnum as the enum names are singular, the entry values are plural though.
        static QHash<QString, int> hash {
            { QStringLiteral("Actions"), KIconLoaderDummy::Action },
            { QStringLiteral("Animations"), KIconLoaderDummy::Animation },
            { QStringLiteral("Applications"), KIconLoaderDummy::Application },
            { QStringLiteral("Categories"), KIconLoaderDummy::Category },
            { QStringLiteral("Devices"), KIconLoaderDummy::Device },
            { QStringLiteral("Emblems"), KIconLoaderDummy::Emblem },
            { QStringLiteral("Emotes"), KIconLoaderDummy::Emote },
            { QStringLiteral("MimeTypes"), KIconLoaderDummy::MimeType },
            { QStringLiteral("Places"), KIconLoaderDummy::Place },
            { QStringLiteral("Status"), KIconLoaderDummy::StatusIcon },
        };
        const auto value = hash.value(string, -1);
        return static_cast<KIconLoaderDummy::Context>(value); // the caller will check that it wasn't -1
    }

    static KIconLoaderDummy::Type parseType(const QString &string)
    {
        bool ok;
        auto v = (KIconLoaderDummy::Type)typeEnum().keyToValue(string.toLatin1().constData(), &ok);
        Q_ASSERT(ok);
        return v;
    }

    /**
     * @returns list of all icon's fileinfo (first level only, selected types
     *          only)
     */
    QList<QFileInfo> allIcons()
    {
        QList<QFileInfo> icons;
        auto iconDir = QString("%1/%2").arg(themeDir).arg(path);
        QDirIterator it(iconDir);
        while (it.hasNext()) {
            it.next();
            auto suffix = it.fileInfo().suffix();
            if (suffix != "svg" && suffix != "svgz" && suffix != "png") {
                continue; // Probably not an icon.
            }
            icons << it.fileInfo();
        }
        return icons;
    }

    QString themeDir;
    QString path;
    uint size;
    QString contextString;
    KIconLoaderDummy::Context context;
    KIconLoaderDummy::Type type;
};

// Declare so we can put them into the QTest data table.
Q_DECLARE_METATYPE(KIconLoaderDummy::Context)
Q_DECLARE_METATYPE(QSharedPointer<Dir>)

class ScalableTest : public QObject
{
    Q_OBJECT

private Q_SLOTS:
    void test_scalable_data()
    {
        for (auto dir : ICON_DIRS) {
            QString themeDir = PROJECT_SOURCE_DIR + QStringLiteral("/") + dir;

            QHash<KIconLoaderDummy::Context, QList<QSharedPointer<Dir>>> contextHash;
            QHash<KIconLoaderDummy::Context, QString> contextStringHash;

            QSettings config(themeDir + "/index.theme", QSettings::IniFormat);
            auto keys = config.allKeys();
            config.beginGroup("Icon Theme");
            auto directoryPaths = config.value("Directories", QString()).toStringList();
            config.endGroup();
            QVERIFY(!directoryPaths.isEmpty());
            for (auto directoryPath : directoryPaths) {
                config.beginGroup(directoryPath);
                QVERIFY2(keys.contains(directoryPath+"/Size"),QString("The theme %1 has an entry 'Directories' which specifies '%2' as directory, but there's no"
                                                                                         " have no associated entry '%2/Size'").arg(themeDir + "/index.theme", directoryPath).toLatin1().constData());
                auto dir = QSharedPointer<Dir>::create(config, themeDir);
{'fraction_non_alphanumeric': 0.11215309545593169, 'fraction_numerical': 0.0056419640134187255, 'mean_word_length': 2.4793103448275864, 'pattern_counts': {'":': 0, '<': 49, '<?xml version=': 0, '>': 43, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '89404', 'n_tokens_mistral': 3661, 'n_tokens_neox': 3560, 'n_words': 1029}