// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0

#include <QtTest/qtest.h>
#include <QtCore/qcontainerinfo.h>
#include <QtCore/qmetacontainer.h>
#include <QtCore/QMap>
#include <QtCore/QHash>

#include <QtCore/qvector.h>
#include <QtCore/qset.h>
#include <QtCore/qstring.h>
#include <QtCore/qbytearray.h>

#include <vector>
#include <set>
#include <forward_list>
#include <unordered_map>

namespace CheckContainerTraits
{
struct NotAContainer {};

static_assert(QContainerInfo::has_size_v<QVector<int>>);
static_assert(QContainerInfo::has_size_v<QSet<int>>);
static_assert(!QContainerInfo::has_size_v<NotAContainer>);
static_assert(QContainerInfo::has_size_v<std::vector<int>>);
static_assert(QContainerInfo::has_size_v<std::set<int>>);
static_assert(!QContainerInfo::has_size_v<std::forward_list<int>>);

static_assert(QContainerInfo::has_clear_v<QVector<int>>);
static_assert(QContainerInfo::has_clear_v<QSet<int>>);
static_assert(!QContainerInfo::has_clear_v<NotAContainer>);
static_assert(QContainerInfo::has_clear_v<std::vector<int>>);
static_assert(QContainerInfo::has_clear_v<std::set<int>>);
static_assert(QContainerInfo::has_clear_v<std::forward_list<int>>);

static_assert(QContainerInfo::has_at_index_v<QVector<int>>);
static_assert(!QContainerInfo::has_at_index_v<QSet<int>>);
static_assert(!QContainerInfo::has_at_index_v<NotAContainer>);
static_assert(QContainerInfo::has_at_index_v<std::vector<int>>);
static_assert(!QContainerInfo::has_at_index_v<std::set<int>>);
static_assert(!QContainerInfo::has_at_index_v<std::forward_list<int>>);

static_assert(QContainerInfo::can_get_at_index_v<QVector<int>>);
static_assert(!QContainerInfo::can_get_at_index_v<QSet<int>>);
static_assert(!QContainerInfo::can_get_at_index_v<NotAContainer>);
static_assert(QContainerInfo::can_get_at_index_v<std::vector<int>>);
static_assert(!QContainerInfo::can_get_at_index_v<std::set<int>>);
static_assert(!QContainerInfo::can_get_at_index_v<std::forward_list<int>>);

static_assert(QContainerInfo::can_set_at_index_v<QVector<int>>);
static_assert(!QContainerInfo::can_set_at_index_v<QSet<int>>);
static_assert(!QContainerInfo::can_set_at_index_v<NotAContainer>);
static_assert(QContainerInfo::can_set_at_index_v<std::vector<int>>);
static_assert(!QContainerInfo::can_set_at_index_v<std::set<int>>);
static_assert(!QContainerInfo::can_set_at_index_v<std::forward_list<int>>);

static_assert(QContainerInfo::has_push_back_v<QVector<int>>);
static_assert(!QContainerInfo::has_push_back_v<QSet<int>>);
static_assert(!QContainerInfo::has_push_back_v<NotAContainer>);
static_assert(QContainerInfo::has_push_back_v<std::vector<int>>);
static_assert(!QContainerInfo::has_push_back_v<std::set<int>>);
static_assert(!QContainerInfo::has_push_back_v<std::forward_list<int>>);

static_assert(QContainerInfo::has_push_front_v<QVector<int>>);
static_assert(!QContainerInfo::has_push_front_v<QSet<int>>);
static_assert(!QContainerInfo::has_push_front_v<NotAContainer>);
static_assert(!QContainerInfo::has_push_front_v<std::vector<int>>);
static_assert(!QContainerInfo::has_push_front_v<std::set<int>>);
static_assert(QContainerInfo::has_push_front_v<std::forward_list<int>>);

static_assert(!QContainerInfo::has_insert_v<QVector<int>>);
static_assert(QContainerInfo::has_insert_v<QSet<int>>);
static_assert(!QContainerInfo::has_insert_v<NotAContainer>);
static_assert(!QContainerInfo::has_insert_v<std::vector<int>>);
static_assert(QContainerInfo::has_insert_v<std::set<int>>);
static_assert(!QContainerInfo::has_insert_v<std::forward_list<int>>);

static_assert(QContainerInfo::has_pop_back_v<QVector<int>>);
static_assert(!QContainerInfo::has_pop_back_v<QSet<int>>);
static_assert(!QContainerInfo::has_pop_back_v<NotAContainer>);
static_assert(QContainerInfo::has_pop_back_v<std::vector<int>>);
static_assert(!QContainerInfo::has_pop_back_v<std::set<int>>);
static_assert(!QContainerInfo::has_pop_back_v<std::forward_list<int>>);

static_assert(QContainerInfo::has_pop_front_v<QVector<int>>);
static_assert(!QContainerInfo::has_pop_front_v<QSet<int>>);
static_assert(!QContainerInfo::has_pop_front_v<NotAContainer>);
static_assert(!QContainerInfo::has_pop_front_v<std::vector<int>>);
static_assert(!QContainerInfo::has_pop_front_v<std::set<int>>);
static_assert(QContainerInfo::has_pop_front_v<std::forward_list<int>>);

static_assert(QContainerInfo::has_iterator_v<QVector<int>>);
static_assert(QContainerInfo::has_iterator_v<QSet<int>>);
static_assert(!QContainerInfo::has_iterator_v<NotAContainer>);
static_assert(QContainerInfo::has_iterator_v<std::vector<int>>);
static_assert(QContainerInfo::has_iterator_v<std::set<int>>);
static_assert(QContainerInfo::has_iterator_v<std::forward_list<int>>);

static_assert(QContainerInfo::has_const_iterator_v<QVector<int>>);
static_assert(QContainerInfo::has_const_iterator_v<QSet<int>>);
static_assert(!QContainerInfo::has_const_iterator_v<NotAContainer>);
static_assert(QContainerInfo::has_const_iterator_v<std::vector<int>>);
static_assert(QContainerInfo::has_const_iterator_v<std::set<int>>);
static_assert(QContainerInfo::has_const_iterator_v<std::forward_list<int>>);

static_assert(QContainerInfo::iterator_dereferences_to_value_v<QVector<int>>);
static_assert(QContainerInfo::iterator_dereferences_to_value_v<QSet<int>>);
static_assert(!QContainerInfo::iterator_dereferences_to_value_v<NotAContainer>);
static_assert(QContainerInfo::iterator_dereferences_to_value_v<std::vector<int>>);
static_assert(QContainerInfo::iterator_dereferences_to_value_v<std::set<int>>);
static_assert(QContainerInfo::iterator_dereferences_to_value_v<std::forward_list<int>>);

static_assert(QContainerInfo::can_set_value_at_iterator_v<QVector<int>>);
static_assert(!QContainerInfo::can_set_value_at_iterator_v<QSet<int>>);
static_assert(!QContainerInfo::can_set_value_at_iterator_v<NotAContainer>);
static_assert(QContainerInfo::can_set_value_at_iterator_v<std::vector<int>>);
static_assert(!QContainerInfo::can_set_value_at_iterator_v<std::set<int>>);
static_assert(QContainerInfo::can_set_value_at_iterator_v<std::forward_list<int>>);

static_assert(QContainerInfo::can_insert_value_at_iterator_v<QVector<int>>);
static_assert(QContainerInfo::can_insert_value_at_iterator_v<QSet<int>>);
static_assert(!QContainerInfo::can_insert_value_at_iterator_v<NotAContainer>);
static_assert(QContainerInfo::can_insert_value_at_iterator_v<std::vector<int>>);
static_assert(!QContainerInfo::can_insert_value_at_iterator_v<std::forward_list<int>>);

// The iterator is only a hint, but syntactically indistinguishable from others.
// It's explicitly there to be signature compatible with std::vector::insert, though.
// Also, inserting into a set is not guaranteed to actually do anything.
static_assert(QContainerInfo::can_insert_value_at_iterator_v<std::set<int>>);

static_assert(QContainerInfo::can_erase_at_iterator_v<QVector<int>>);
static_assert(QContainerInfo::can_erase_at_iterator_v<QSet<int>>);
static_assert(!QContainerInfo::can_erase_at_iterator_v<NotAContainer>);
static_assert(QContainerInfo::can_erase_at_iterator_v<std::vector<int>>);
static_assert(QContainerInfo::can_erase_at_iterator_v<std::set<int>>);
static_assert(!QContainerInfo::can_erase_at_iterator_v<std::forward_list<int>>);

}

class tst_QMetaContainer: public QObject
{
    Q_OBJECT

private:
    QVector<QMetaType> qvector;
    std::vector<QString> stdvector;
    QSet<QByteArray> qset;
    std::set<int> stdset;
    std::forward_list<QMetaSequence> forwardList;

    QHash<int, QMetaType> qhash;
    QMap<QByteArray, bool> qmap;
    std::map<QString, int> stdmap;
    std::unordered_map<int, QMetaAssociation> stdunorderedmap;

private slots:
    void init();
    void testSequence_data();
    void testSequence();

    void testAssociation_data();
    void testAssociation();

    void cleanup();
};

void tst_QMetaContainer::init()
{
    qvector = { QMetaType(), QMetaType::fromType<QString>(), QMetaType::fromType<int>() };
    stdvector = { QStringLiteral("foo"), QStringLiteral("bar"), QStringLiteral("baz") };
    qset = { "aaa", "bbb", "ccc" };
    stdset = { 1, 2, 3, 42, 45, 11 };
    forwardList = {
        QMetaSequence::fromContainer<QVector<QMetaType>>(),
        QMetaSequence::fromContainer<std::vector<QString>>(),
        QMetaSequence::fromContainer<QSet<QByteArray>>(),
        QMetaSequence::fromContainer<std::set<int>>(),
        QMetaSequence::fromContainer<std::forward_list<QMetaSequence>>()
    };
    qhash = {
        { 233, QMetaType() },
        { 11, QMetaType::fromType<QByteArray>() },
        { 6626, QMetaType::fromType<bool>() }
    };
    qmap = {
        { "eins", true },
        { "zwei", false },
        { "elfundvierzig", true }
    };

    stdmap = {
        { QStringLiteral("dkdkdkd"), 58583 },
        { QStringLiteral("ooo30393"), 12 },
        { QStringLiteral("2dddd30393"), 999999 },
    };
    stdunorderedmap = {
        { 11, QMetaAssociation::fromContainer<QHash<int, QMetaType>>() },
        { 12, QMetaAssociation::fromContainer<QMap<QByteArray, bool>>() },
        { 393, QMetaAssociation::fromContainer<std::map<QString, int>>() },
        { 293, QMetaAssociation::fromContainer<std::unordered_map<int, QMetaAssociation>>() }
    };
}

void tst_QMetaContainer::cleanup()
{
    qvector.clear();
    stdvector.clear();
    qset.clear();
    stdset.clear();
    forwardList.clear();
    qhash.clear();
    qmap.clear();
    stdmap.clear();
    stdunorderedmap.clear();
}

void tst_QMetaContainer::testSequence_data()
{
    QTest::addColumn<void *>("container");
    QTest::addColumn<QMetaSequence>("metaSequence");
    QTest::addColumn<QMetaType>("metaType");
    QTest::addColumn<bool>("hasSize");
    QTest::addColumn<bool>("isIndexed");
    QTest::addColumn<bool>("canRemove");
    QTest::addColumn<bool>("hasBidirectionalIterator");
    QTest::addColumn<bool>("hasRandomAccessIterator");
    QTest::addColumn<bool>("canInsertAtIterator");
    QTest::addColumn<bool>("canEraseAtIterator");
    QTest::addColumn<bool>("isSortable");

    QTest::addRow("QVector")
            << static_cast<void *>(&qvector)
            << QMetaSequence::fromContainer<QVector<QMetaType>>()
            << QMetaType::fromType<QMetaType>()
            << true << true << true << true << true << true << true << true;
    QTest::addRow("std::vector")
            << static_cast<void *>(&stdvector)
            << QMetaSequence::fromContainer<std::vector<QString>>()
            << QMetaType::fromType<QString>()
            << true << true << true << true << true << true << true << true;
    QTest::addRow("QSet")
            << static_cast<void *>(&qset)
            << QMetaSequence::fromContainer<QSet<QByteArray>>()
            << QMetaType::fromType<QByteArray>()
            << true << false << false << false << false << true << true << false;
    QTest::addRow("std::set")
            << static_cast<void *>(&stdset)
            << QMetaSequence::fromContainer<std::set<int>>()
            << QMetaType::fromType<int>()
            << true << false << false << true << false << true << true << false;
    QTest::addRow("std::forward_list")
            << static_cast<void *>(&forwardList)
            << QMetaSequence::fromContainer<std::forward_list<QMetaSequence>>()
            << QMetaType::fromType<QMetaSequence>()
            << false << false << true << false << false << false << false << true;
}

void tst_QMetaContainer::testSequence()
{
    QFETCH(void *, container);
    QFETCH(QMetaSequence, metaSequence);
    QFETCH(QMetaType, metaType);
    QFETCH(bool, hasSize);
    QFETCH(bool, isIndexed);
    QFETCH(bool, canRemove);
    QFETCH(bool, hasBidirectionalIterator);
    QFETCH(bool, hasRandomAccessIterator);
    QFETCH(bool, canInsertAtIterator);
    QFETCH(bool, canEraseAtIterator);
    QFETCH(bool, isSortable);

    QVERIFY(metaSequence.canAddValue());
    QCOMPARE(metaSequence.hasSize(), hasSize);
    QCOMPARE(metaSequence.canGetValueAtIndex(), isIndexed);
    QCOMPARE(metaSequence.canSetValueAtIndex(), isIndexed);
    QCOMPARE(metaSequence.canRemoveValue(), canRemove);
    QCOMPARE(metaSequence.hasBidirectionalIterator(), hasBidirectionalIterator);
    QCOMPARE(metaSequence.hasRandomAccessIterator(), hasRandomAccessIterator);
    QCOMPARE(metaSequence.canInsertValueAtIterator(), canInsertAtIterator);
    QCOMPARE(metaSequence.canEraseValueAtIterator(), canEraseAtIterator);
    QCOMPARE(metaSequence.isSortable(), isSortable);

    QVariant var1(metaType);
    QVariant var2(metaType);
    QVariant var3(metaType);

    if (hasSize) {
        const qsizetype size = metaSequence.size(container);

        // var1 is invalid, and our sets do not contain an invalid value so far.
        metaSequence.addValue(container, var1.constData());
        QCOMPARE(metaSequence.size(container), size + 1);
        if (canRemove) {
            metaSequence.removeValue(container);
            QCOMPARE(metaSequence.size(container), size);
        }
    } else {
        metaSequence.addValue(container, var1.constData());
        if (canRemove)
            metaSequence.removeValue(container);
    }

    if (isIndexed) {
        QVERIFY(hasSize);
        const qsizetype size = metaSequence.size(container);
        for (qsizetype i = 0; i < size; ++i) {
            metaSequence.valueAtIndex(container, i, var1.data());
            metaSequence.valueAtIndex(container, size - i - 1, var2.data());

            metaSequence.setValueAtIndex(container, i, var2.constData());
            metaSequence.setValueAtIndex(container, size - i - 1, var1.constData());

            metaSequence.valueAtIndex(container, i, var3.data());
            QCOMPARE(var3, var2);

            metaSequence.valueAtIndex(container, size - i - 1, var3.data());
            QCOMPARE(var3, var1);
        }
    }

    QVERIFY(metaSequence.hasIterator());
    QVERIFY(metaSequence.hasConstIterator());
    QVERIFY(metaSequence.canGetValueAtIterator());
    QVERIFY(metaSequence.canGetValueAtConstIterator());

    void *it = metaSequence.begin(container);
    void *end = metaSequence.end(container);
    QVERIFY(it);
    QVERIFY(end);

    void *constIt = metaSequence.constBegin(container);
    void *constEnd = metaSequence.constEnd(container);
    QVERIFY(constIt);
    QVERIFY(constEnd);

    const qsizetype size = metaSequence.diffIterator(end, it);
    QCOMPARE(size, metaSequence.diffConstIterator(constEnd, constIt));
    if (hasSize)
        QCOMPARE(size, metaSequence.size(container));

    qsizetype count = 0;
    for (; !metaSequence.compareIterator(it, end);
         metaSequence.advanceIterator(it, 1), metaSequence.advanceConstIterator(constIt, 1)) {
        metaSequence.valueAtIterator(it, var1.data());
        if (isIndexed) {
            metaSequence.valueAtIndex(container, count, var2.data());
            QCOMPARE(var1, var2);
        }
        metaSequence.valueAtConstIterator(constIt, var3.data());
        QCOMPARE(var3, var1);
        ++count;
    }

    QCOMPARE(count, size);
    QVERIFY(metaSequence.compareConstIterator(constIt, constEnd));

    metaSequence.destroyIterator(it);
    metaSequence.destroyIterator(end);
    metaSequence.destroyConstIterator(constIt);
    metaSequence.destroyConstIterator(constEnd);

    if (metaSequence.canSetValueAtIterator()) {
        void *it = metaSequence.begin(container);
        void *end = metaSequence.end(container);
        QVERIFY(it);
        QVERIFY(end);

        for (; !metaSequence.compareIterator(it, end); metaSequence.advanceIterator(it, 1)) {
            metaSequence.valueAtIterator(it, var1.data());
            metaSequence.setValueAtIterator(it, var2.constData());
            metaSequence.valueAtIterator(it, var3.data());
            QCOMPARE(var2, var3);
            var2 = var1;
        }

        metaSequence.destroyIterator(it);
        metaSequence.destroyIterator(end);
    }

    if (metaSequence.hasBidirectionalIterator()) {
        void *it = metaSequence.end(container);
        void *end = metaSequence.begin(container);
        QVERIFY(it);
        QVERIFY(end);

        void *constIt = metaSequence.constEnd(container);
        void *constEnd = metaSequence.constBegin(container);
        QVERIFY(constIt);
        QVERIFY(constEnd);

        qsizetype size = 0;
        if (metaSequence.hasRandomAccessIterator()) {
            size = metaSequence.diffIterator(end, it);
            QCOMPARE(size, metaSequence.diffConstIterator(constEnd, constIt));
        } else {
            size = -metaSequence.diffIterator(it, end);
        }

        if (hasSize)
            QCOMPARE(size, -metaSequence.size(container));

        qsizetype count = 0;
        do {
            metaSequence.advanceIterator(it, -1);
            metaSequence.advanceConstIterator(constIt, -1);
            --count;

            metaSequence.valueAtIterator(it, var1.data());
            if (isIndexed) {
                metaSequence.valueAtIndex(container, count - size, var2.data());
                QCOMPARE(var1, var2);
            }
            metaSequence.valueAtConstIterator(constIt, var3.data());
            QCOMPARE(var3, var1);
        } while (!metaSequence.compareIterator(it, end));

        QCOMPARE(count, size);
        QVERIFY(metaSequence.compareConstIterator(constIt, constEnd));

        metaSequence.destroyIterator(it);
        metaSequence.destroyIterator(end);
        metaSequence.destroyConstIterator(constIt);
        metaSequence.destroyConstIterator(constEnd);
    }

    if (canInsertAtIterator) {
        void *it = metaSequence.begin(container);
        void *end = metaSequence.end(container);

        const qsizetype size = metaSequence.diffIterator(end, it);
        metaSequence.destroyIterator(end);

        metaSequence.insertValueAtIterator(container, it, var1.constData());
        metaSequence.destroyIterator(it);
        it = metaSequence.begin(container);
        metaSequence.insertValueAtIterator(container, it, var2.constData());
        metaSequence.destroyIterator(it);
        it = metaSequence.begin(container);
        metaSequence.insertValueAtIterator(container, it, var3.constData());

        metaSequence.destroyIterator(it);

        it = metaSequence.begin(container);
        end = metaSequence.end(container);

        const qsizetype newSize = metaSequence.diffIterator(end, it);

        if (metaSequence.isSortable()) {
            QCOMPARE(newSize, size + 3);
            QVariant var4(metaType);
            metaSequence.valueAtIterator(it, var4.data());
            QCOMPARE(var4, var3);
            metaSequence.advanceIterator(it, 1);
            metaSequence.valueAtIterator(it, var4.data());
            QCOMPARE(var4, var2);
            metaSequence.advanceIterator(it, 1);
            metaSequence.valueAtIterator(it, var4.data());
            QCOMPARE(var4, var1);
        } else {
            QVERIFY(newSize >= size);
        }

        if (canEraseAtIterator) {
            for (int i = 0; i < newSize; ++i) {
                metaSequence.destroyIterator(it);
                it = metaSequence.begin(container);
                metaSequence.eraseValueAtIterator(container, it);
            }

            metaSequence.destroyIterator(it);
            it = metaSequence.begin(container);
            metaSequence.destroyIterator(end);
            end = metaSequence.end(container);
            QVERIFY(metaSequence.compareIterator(it, end));

            metaSequence.addValue(container, var1.constData());
            metaSequence.addValue(container, var2.constData());
            metaSequence.addValue(container, var3.constData());
        }

        metaSequence.destroyIterator(end);
        metaSequence.destroyIterator(it);
    }

    QVERIFY(metaSequence.canClear());
    constIt = metaSequence.constBegin(container);
    constEnd = metaSequence.constEnd(container);
    QVERIFY(!metaSequence.compareConstIterator(constIt, constEnd));
    metaSequence.destroyConstIterator(constIt);
    metaSequence.destroyConstIterator(constEnd);

    metaSequence.clear(container);
    constIt = metaSequence.constBegin(container);
    constEnd = metaSequence.constEnd(container);
    QVERIFY(metaSequence.compareConstIterator(constIt, constEnd));
    metaSequence.destroyConstIterator(constIt);
    metaSequence.destroyConstIterator(constEnd);

    QVERIFY(metaSequence.iface() != nullptr);
    QMetaSequence defaultConstructed;
    QVERIFY(defaultConstructed.iface() == nullptr);
}

void tst_QMetaContainer::testAssociation_data()
{
    QTest::addColumn<void *>("container");
    QTest::addColumn<QMetaAssociation>("metaAssociation");
    QTest::addColumn<QMetaType>("keyType");
    QTest::addColumn<QMetaType>("mappedType");
    QTest::addColumn<bool>("hasSize");
    QTest::addColumn<bool>("canRemove");
    QTest::addColumn<bool>("canSetMapped");
    QTest::addColumn<bool>("hasBidirectionalIterator");
    QTest::addColumn<bool>("hasRandomAccessIterator");

    QTest::addRow("QHash")
            << static_cast<void *>(&qhash)
            << QMetaAssociation::fromContainer<QHash<int, QMetaType>>()
            << QMetaType::fromType<int>()
            << QMetaType::fromType<QMetaType>()
            << true << true << true << false << false;
    QTest::addRow("QMap")
            << static_cast<void *>(&qmap)
            << QMetaAssociation::fromContainer<QMap<QByteArray, bool>>()
            << QMetaType::fromType<QByteArray>()
            << QMetaType::fromType<bool>()
            << true << true << true << true << false;
    QTest::addRow("std::map")
            << static_cast<void *>(&stdmap)
            << QMetaAssociation::fromContainer<std::map<QString, int>>()
            << QMetaType::fromType<QString>()
            << QMetaType::fromType<int>()
            << true << true << true << true << false;
    QTest::addRow("std::unorderedmap")
            << static_cast<void *>(&stdunorderedmap)
            << QMetaAssociation::fromContainer<std::unordered_map<int, QMetaAssociation>>()
            << QMetaType::fromType<int>()
            << QMetaType::fromType<QMetaAssociation>()
            << true << true << true << false << false;
    QTest::addRow("QSet")
            << static_cast<void *>(&qset)
            << QMetaAssociation::fromContainer<QSet<QByteArray>>()
            << QMetaType::fromType<QByteArray>()
            << QMetaType()
            << true << true << false << false << false;
    QTest::addRow("std::set")
            << static_cast<void *>(&stdset)
            << QMetaAssociation::fromContainer<std::set<int>>()
            << QMetaType::fromType<int>()
            << QMetaType()
            << true << true << false << true << false;
}

void tst_QMetaContainer::testAssociation()
{
    QFETCH(void *, container);
    QFETCH(QMetaAssociation, metaAssociation);
    QFETCH(QMetaType, keyType);
    QFETCH(QMetaType, mappedType);
    QFETCH(bool, hasSize);
    QFETCH(bool, canRemove);
    QFETCH(bool, canSetMapped);
    QFETCH(bool, hasBidirectionalIterator);
    QFETCH(bool, hasRandomAccessIterator);

    QCOMPARE(metaAssociation.hasSize(), hasSize);
    QCOMPARE(metaAssociation.canRemoveKey(), canRemove);
    QCOMPARE(metaAssociation.canSetMappedAtKey(), canSetMapped);
    QCOMPARE(metaAssociation.canSetMappedAtIterator(), canSetMapped);

    // Apparently implementations can choose to provide "better" iterators than required by the std.
    if (hasBidirectionalIterator)
        QCOMPARE(metaAssociation.hasBidirectionalIterator(), hasBidirectionalIterator);
    if (hasRandomAccessIterator)
        QCOMPARE(metaAssociation.hasRandomAccessIterator(), hasRandomAccessIterator);

    QVariant key1(keyType);
    QVariant key2(keyType);
    QVariant key3(keyType);

    QVariant mapped1(mappedType);
    QVariant mapped2(mappedType);
    QVariant mapped3(mappedType);

    if (hasSize) {
        const qsizetype size = metaAssociation.size(container);

        QVERIFY(metaAssociation.canInsertKey());

        // var1 is invalid, and our containers do not contain an invalid key so far.
        metaAssociation.insertKey(container, key1.constData());
        QCOMPARE(metaAssociation.size(container), size + 1);
        metaAssociation.removeKey(container, key1.constData());
        QCOMPARE(metaAssociation.size(container), size);
    } else {
        metaAssociation.insertKey(container, key1.constData());
        metaAssociation.removeKey(container, key1.constData());
    }

    QVERIFY(metaAssociation.hasIterator());
    QVERIFY(metaAssociation.hasConstIterator());
    QVERIFY(metaAssociation.canGetKeyAtIterator());
    QVERIFY(metaAssociation.canGetKeyAtConstIterator());

    void *it = metaAssociation.begin(container);
    void *end = metaAssociation.end(container);
    QVERIFY(it);
    QVERIFY(end);

    void *constIt = metaAssociation.constBegin(container);
    void *constEnd = metaAssociation.constEnd(container);
    QVERIFY(constIt);
    QVERIFY(constEnd);

    const qsizetype size = metaAssociation.diffIterator(end, it);
    QCOMPARE(size, metaAssociation.diffConstIterator(constEnd, constIt));
    if (hasSize)
        QCOMPARE(size, metaAssociation.size(container));

    qsizetype count = 0;
    for (; !metaAssociation.compareIterator(it, end);
         metaAssociation.advanceIterator(it, 1), metaAssociation.advanceConstIterator(constIt, 1)) {
        metaAssociation.keyAtIterator(it, key1.data());
        metaAssociation.keyAtConstIterator(constIt, key3.data());
        QCOMPARE(key3, key1);
        ++count;
    }

    QCOMPARE(count, size);
    QVERIFY(metaAssociation.compareConstIterator(constIt, constEnd));

    metaAssociation.destroyIterator(it);
    metaAssociation.destroyIterator(end);
    metaAssociation.destroyConstIterator(constIt);
    metaAssociation.destroyConstIterator(constEnd);

    if (metaAssociation.canSetMappedAtIterator()) {
        void *it = metaAssociation.begin(container);
        void *end = metaAssociation.end(container);
        QVERIFY(it);
        QVERIFY(end);

        for (; !metaAssociation.compareIterator(it, end); metaAssociation.advanceIterator(it, 1)) {
            metaAssociation.mappedAtIterator(it, mapped1.data());
            metaAssociation.setMappedAtIterator(it, mapped2.constData());
            metaAssociation.mappedAtIterator(it, mapped3.data());
            QCOMPARE(mapped2, mapped3);
            mapped2 = mapped1;
        }

        metaAssociation.destroyIterator(it);
        metaAssociation.destroyIterator(end);

        it = metaAssociation.constBegin(container);
        end = metaAssociation.constEnd(container);
        QVERIFY(it);
        QVERIFY(end);

        for (; !metaAssociation.compareConstIterator(it, end); metaAssociation.advanceConstIterator(it, 1)) {
            metaAssociation.mappedAtConstIterator(it, mapped1.data());
            metaAssociation.keyAtConstIterator(it, key1.data());
            metaAssociation.setMappedAtKey(container, key1.constData(), mapped2.constData());
            metaAssociation.mappedAtConstIterator(it, mapped3.data());
            QCOMPARE(mapped2, mapped3);
            mapped2 = mapped1;
        }

        metaAssociation.destroyConstIterator(it);
        metaAssociation.destroyConstIterator(end);
    }

    if (metaAssociation.hasBidirectionalIterator()) {
        void *it = metaAssociation.end(container);
        void *end = metaAssociation.begin(container);
        QVERIFY(it);
        QVERIFY(end);

        void *constIt = metaAssociation.constEnd(container);
        void *constEnd = metaAssociation.constBegin(container);
        QVERIFY(constIt);
        QVERIFY(constEnd);

        qsizetype size = 0;
        if (metaAssociation.hasRandomAccessIterator()) {
            size = metaAssociation.diffIterator(end, it);
            QCOMPARE(size, metaAssociation.diffConstIterator(constEnd, constIt));
        } else {
            size = -metaAssociation.diffIterator(it, end);
        }

        if (hasSize)
            QCOMPARE(size, -metaAssociation.size(container));

        qsizetype count = 0;
        do {
            metaAssociation.advanceIterator(it, -1);
            metaAssociation.advanceConstIterator(constIt, -1);
            --count;

            metaAssociation.keyAtIterator(it, key1.data());
            metaAssociation.keyAtConstIterator(constIt, key3.data());
            QCOMPARE(key3, key1);
        } while (!metaAssociation.compareIterator(it, end));

        QCOMPARE(count, size);
        QVERIFY(metaAssociation.compareConstIterator(constIt, constEnd));

        metaAssociation.destroyIterator(it);
        metaAssociation.destroyIterator(end);
        metaAssociation.destroyConstIterator(constIt);
        metaAssociation.destroyConstIterator(constEnd);
    }

    QVERIFY(metaAssociation.canClear());
    constIt = metaAssociation.constBegin(container);
    constEnd = metaAssociation.constEnd(container);
    QVERIFY(!metaAssociation.compareConstIterator(constIt, constEnd));
    metaAssociation.destroyConstIterator(constIt);
    metaAssociation.destroyConstIterator(constEnd);

    metaAssociation.clear(container);
    constIt = metaAssociation.constBegin(container);
    constEnd = metaAssociation.constEnd(container);
    QVERIFY(metaAssociation.compareConstIterator(constIt, constEnd));
    metaAssociation.destroyConstIterator(constIt);
    metaAssociation.destroyConstIterator(constEnd);

    QVERIFY(metaAssociation.iface() != nullptr);
    QMetaSequence defaultConstructed;
    QVERIFY(defaultConstructed.iface() == nullptr);
}

QTEST_MAIN(tst_QMetaContainer)
#include "tst_qmetacontainer.moc"
