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

#include <QTest>
#include <QSignalSpy>
#include <QtCore/qlocale.h>
#include <private/qmetaobjectbuilder_p.h>

class tst_QMetaObjectBuilder : public QObject
{
    Q_OBJECT
private slots:
    void create();
    void className();
    void superClass();
    void flags();
    void method();
    void slot();
    void signal();
    void constructor();
    void property();
    void variantProperty();
    void notifySignal();
    void enumerator();
    void classInfo();
    void relatedMetaObject();
    void staticMetacall();
    void copyMetaObject();
    void removeNotifySignal();

    void usage_signal();
    void usage_property();
    void usage_slot();
    void usage_method();
    void usage_constructor();
    void usage_connect();
    void usage_templateConnect();

    void classNameFirstInStringData();

    void propertyMetaType();

    void cleanupTestCase();

    void ownMetaTypeNoProperties();

private:
    static bool checkForSideEffects
        (const QMetaObjectBuilder& builder,
         QMetaObjectBuilder::AddMembers members);
    QList<QMetaObject *> dynamicMetaObjectsPendingFree;
};

struct MetaObjectComparison {
    bool isSame = false;
    QString details;
    operator bool() {return isSame;}

    static inline auto Ok() {return  MetaObjectComparison{true, QString()}; }
    static inline auto Failed(QStringView message) {return  MetaObjectComparison{false, message.toString()}; }
};
MetaObjectComparison sameMetaObject(const QMetaObject *meta1, const QMetaObject *meta2);

// Dummy class that has something of every type of thing moc can generate.
class SomethingOfEverything : public QObject
{
    Q_OBJECT
    Q_CLASSINFO("ci_foo", "ABC")
    Q_CLASSINFO("ci_bar", "DEF")
    Q_PROPERTY(QString prop READ prop WRITE setProp NOTIFY propChanged)
    Q_PROPERTY(QString prop2 READ prop WRITE setProp)
    Q_PROPERTY(QString revisionProp READ prop WRITE setProp REVISION 42)
    Q_PROPERTY(SomethingEnum eprop READ eprop)
    Q_PROPERTY(SomethingFlagEnum fprop READ fprop)
    Q_PROPERTY(QLocale::Language language READ language)
    Q_ENUMS(SomethingEnum)
    Q_FLAGS(SomethingFlag)
public:
    Q_INVOKABLE SomethingOfEverything() {}
    ~SomethingOfEverything() {}

    enum SomethingEnum
    {
        GHI,
        JKL = 10
    };

    enum SomethingFlagEnum
    {
        XYZ = 1,
        UVW = 8
    };
    Q_DECLARE_FLAGS(SomethingFlag, SomethingFlagEnum)

    Q_INVOKABLE Q_SCRIPTABLE void method1() {}

    QString prop() const { return QString(); }
    void setProp(const QString& v) { Q_UNUSED(v); }

    SomethingOfEverything::SomethingEnum eprop() const { return GHI; }
    SomethingOfEverything::SomethingFlagEnum fprop() const { return XYZ; }
    QLocale::Language language() const { return QLocale::English; }

public slots:
    void slot1(const QString&) {}
    void slot2(int, const QString&) {}
    Q_REVISION(24) void revisionSlot() {}

private slots:
    void slot3() {}

protected slots:
    Q_SCRIPTABLE void slot4(int) {}
    void slot5(int, const QString &) {}

signals:
    void sig1();
    void sig2(int x, const QString& y);
    void propChanged(const QString&);
};

void tst_QMetaObjectBuilder::create()
{
    QMetaObjectBuilder builder;
    QVERIFY(builder.className().isEmpty());
    QCOMPARE(builder.superClass(), &QObject::staticMetaObject);
    QCOMPARE(builder.methodCount(), 0);
    QCOMPARE(builder.constructorCount(), 0);
    QCOMPARE(builder.propertyCount(), 0);
    QCOMPARE(builder.enumeratorCount(), 0);
    QCOMPARE(builder.classInfoCount(), 0);
    QCOMPARE(builder.relatedMetaObjectCount(), 0);
    QVERIFY(!builder.staticMetacallFunction());
}

void tst_QMetaObjectBuilder::className()
{
    QMetaObjectBuilder builder;

    // Change the class name.
    builder.setClassName("Foo");
    QCOMPARE(builder.className(), QByteArray("Foo"));

    // Change it again.
    builder.setClassName("Bar");
    QCOMPARE(builder.className(), QByteArray("Bar"));

    // Clone the class name off a static QMetaObject.
    builder.addMetaObject(&QObject::staticMetaObject, QMetaObjectBuilder::ClassName);
    QCOMPARE(builder.className(), QByteArray("QObject"));

    // Check that nothing else changed.
    QVERIFY(checkForSideEffects(builder, QMetaObjectBuilder::ClassName));
}

void tst_QMetaObjectBuilder::superClass()
{
    QMetaObjectBuilder builder;

    // Change the super class.
    builder.setSuperClass(&QObject::staticMetaObject);
    QCOMPARE(builder.superClass(), &QObject::staticMetaObject);

    // Change it again.
    builder.setSuperClass(&staticMetaObject);
    QCOMPARE(builder.superClass(), &staticMetaObject);

    // Clone the super class off a static QMetaObject.
    builder.addMetaObject(&QObject::staticMetaObject, QMetaObjectBuilder::SuperClass);
    QVERIFY(!builder.superClass());
    builder.addMetaObject(&staticMetaObject, QMetaObjectBuilder::SuperClass);
    QCOMPARE(builder.superClass(), staticMetaObject.superClass());

    // Check that nothing else changed.
    QVERIFY(checkForSideEffects(builder, QMetaObjectBuilder::SuperClass));
}

void tst_QMetaObjectBuilder::flags()
{
    QMetaObjectBuilder builder;

    // Check default
    QCOMPARE(builder.flags(), 0);

    // Set flags
    builder.setFlags(DynamicMetaObject);
    QCOMPARE(builder.flags(), DynamicMetaObject);
}

void tst_QMetaObjectBuilder::method()
{
    QMetaObjectBuilder builder;

    // Check null method
    QMetaMethodBuilder nullMethod;
    QCOMPARE(nullMethod.signature(), QByteArray());
    QCOMPARE(nullMethod.methodType(), QMetaMethod::Method);
    QVERIFY(nullMethod.returnType().isEmpty());
    QVERIFY(nullMethod.parameterTypes().isEmpty());
    QVERIFY(nullMethod.parameterNames().isEmpty());
    QVERIFY(nullMethod.tag().isEmpty());
    QCOMPARE(nullMethod.access(), QMetaMethod::Public);
    QCOMPARE(nullMethod.attributes(), 0);
    QCOMPARE(nullMethod.revision(), 0);
    QCOMPARE(nullMethod.index(), 0);

    // Add a method and check its attributes.
    QMetaMethodBuilder method1 = builder.addMethod("foo(const QString&, int)");
    QCOMPARE(method1.signature(), QByteArray("foo(QString,int)"));
    QCOMPARE(method1.methodType(), QMetaMethod::Method);
    QCOMPARE(method1.returnType(), QByteArray("void"));
    QCOMPARE(method1.parameterTypes(), QList<QByteArray>() << "QString" << "int");
    QVERIFY(method1.parameterNames().isEmpty());
    QVERIFY(method1.tag().isEmpty());
    QCOMPARE(method1.access(), QMetaMethod::Public);
    QCOMPARE(method1.attributes(), 0);
    QCOMPARE(method1.revision(), 0);
    QCOMPARE(method1.index(), 0);
    QCOMPARE(builder.methodCount(), 1);

    // Add another method and check again.
    QMetaMethodBuilder method2 = builder.addMethod("bar(QString)", "int");
    QCOMPARE(method2.signature(), QByteArray("bar(QString)"));
    QCOMPARE(method2.methodType(), QMetaMethod::Method);
    QCOMPARE(method2.returnType(), QByteArray("int"));
    QCOMPARE(method2.parameterTypes(), QList<QByteArray>() << "QString");
    QVERIFY(method2.parameterNames().isEmpty());
    QVERIFY(method2.tag().isEmpty());
    QCOMPARE(method2.access(), QMetaMethod::Public);
    QCOMPARE(method2.attributes(), 0);
    QCOMPARE(method2.revision(), 0);
    QCOMPARE(method2.index(), 1);
    QCOMPARE(builder.methodCount(), 2);

    // Perform index-based lookup.
    QCOMPARE(builder.indexOfMethod("foo(const QString&, int)"), 0);
    QCOMPARE(builder.indexOfMethod("bar(QString)"), 1);
    QCOMPARE(builder.indexOfMethod("baz()"), -1);

    // Modify the attributes on method1.
    method1.setReturnType("int");
    method1.setParameterNames(QList<QByteArray>() << "a" << "b");
    method1.setTag("tag");
    method1.setAccess(QMetaMethod::Private);
    method1.setAttributes(42);
    method1.setRevision(123);

    // Check that method1 is changed, but method2 is not.
    QCOMPARE(method1.signature(), QByteArray("foo(QString,int)"));
    QCOMPARE(method1.methodType(), QMetaMethod::Method);
    QCOMPARE(method1.returnType(), QByteArray("int"));
    QCOMPARE(method1.parameterTypes(), QList<QByteArray>() << "QString" << "int");
    QCOMPARE(method1.parameterNames(), QList<QByteArray>() << "a" << "b");
    QCOMPARE(method1.tag(), QByteArray("tag"));
    QCOMPARE(method1.access(), QMetaMethod::Private);
    QCOMPARE(method1.attributes(), 42);
    QCOMPARE(method1.revision(), 123);
    QCOMPARE(method1.index(), 0);
    QCOMPARE(method2.signature(), QByteArray("bar(QString)"));
    QCOMPARE(method2.methodType(), QMetaMethod::Method);
    QCOMPARE(method2.returnType(), QByteArray("int"));
    QCOMPARE(method2.parameterTypes(), QList<QByteArray>() << "QString");
    QVERIFY(method2.parameterNames().isEmpty());
    QVERIFY(method2.tag().isEmpty());
    QCOMPARE(method2.access(), QMetaMethod::Public);
    QCOMPARE(method2.attributes(), 0);
    QCOMPARE(method2.revision(), 0);
    QCOMPARE(method2.index(), 1);
    QCOMPARE(builder.methodCount(), 2);

    // Modify the attributes on method2.
    method2.setReturnType("QString");
    method2.setParameterNames(QList<QByteArray>() << "c");
    method2.setTag("Q_FOO");
    method2.setAccess(QMetaMethod::Protected);
    method2.setAttributes(24);
    method2.setRevision(321);

    // This time check that only method2 changed.
    QCOMPARE(method1.signature(), QByteArray("foo(QString,int)"));
    QCOMPARE(method1.methodType(), QMetaMethod::Method);
    QCOMPARE(method1.returnType(), QByteArray("int"));
    QCOMPARE(method1.parameterTypes(), QList<QByteArray>() << "QString" << "int");
    QCOMPARE(method1.parameterNames(), QList<QByteArray>() << "a" << "b");
    QCOMPARE(method1.tag(), QByteArray("tag"));
    QCOMPARE(method1.access(), QMetaMethod::Private);
    QCOMPARE(method1.attributes(), 42);
    QCOMPARE(method1.revision(), 123);
    QCOMPARE(method1.index(), 0);
    QCOMPARE(method2.signature(), QByteArray("bar(QString)"));
    QCOMPARE(method2.methodType(), QMetaMethod::Method);
    QCOMPARE(method2.returnType(), QByteArray("QString"));
    QCOMPARE(method2.parameterTypes(), QList<QByteArray>() << "QString");
    QCOMPARE(method2.parameterNames(), QList<QByteArray>() << "c");
    QCOMPARE(method2.tag(), QByteArray("Q_FOO"));
    QCOMPARE(method2.access(), QMetaMethod::Protected);
    QCOMPARE(method2.attributes(), 24);
    QCOMPARE(method2.revision(), 321);
    QCOMPARE(method2.index(), 1);
    QCOMPARE(builder.methodCount(), 2);

    // Remove method1 and check that method2 becomes index 0.
    builder.removeMethod(0);
    QCOMPARE(builder.methodCount(), 1);
    method2 = builder.method(0);
    QCOMPARE(method2.signature(), QByteArray("bar(QString)"));
    QCOMPARE(method2.methodType(), QMetaMethod::Method);
    QCOMPARE(method2.returnType(), QByteArray("QString"));
    QCOMPARE(method2.parameterTypes(), QList<QByteArray>() << "QString");
    QCOMPARE(method2.parameterNames(), QList<QByteArray>() << "c");
    QCOMPARE(method2.tag(), QByteArray("Q_FOO"));
    QCOMPARE(method2.access(), QMetaMethod::Protected);
    QCOMPARE(method2.attributes(), 24);
    QCOMPARE(method2.revision(), 321);
    QCOMPARE(method2.index(), 0);

    // Perform index-based lookup again.
    QCOMPARE(builder.indexOfMethod("foo(const QString&, int)"), -1);
    QCOMPARE(builder.indexOfMethod("bar(QString)"), 0);
    QCOMPARE(builder.indexOfMethod("baz()"), -1);
    QCOMPARE(builder.method(0).signature(), QByteArray("bar(QString)"));
    QCOMPARE(builder.method(9).signature(), QByteArray());

    // Check that nothing else changed.
    QVERIFY(checkForSideEffects(builder, QMetaObjectBuilder::Methods));
}

void tst_QMetaObjectBuilder::slot()
{
    QMetaObjectBuilder builder;

    // Add a slot and check its attributes.
    QMetaMethodBuilder method1 = builder.addSlot("foo(const QString&, int)");
    QCOMPARE(method1.signature(), QByteArray("foo(QString,int)"));
    QCOMPARE(method1.methodType(), QMetaMethod::Slot);
    QCOMPARE(method1.returnType(), QByteArray("void"));
    QCOMPARE(method1.parameterTypes(), QList<QByteArray>() << "QString" << "int");
    QVERIFY(method1.parameterNames().isEmpty());
    QVERIFY(method1.tag().isEmpty());
    QCOMPARE(method1.access(), QMetaMethod::Public);
    QCOMPARE(method1.attributes(), 0);
    QCOMPARE(method1.index(), 0);
    QCOMPARE(builder.methodCount(), 1);

    // Add another slot and check again.
    QMetaMethodBuilder method2 = builder.addSlot("bar(QString)");
    QCOMPARE(method2.signature(), QByteArray("bar(QString)"));
    QCOMPARE(method2.methodType(), QMetaMethod::Slot);
    QCOMPARE(method2.returnType(), QByteArray("void"));
    QCOMPARE(method2.parameterTypes(), QList<QByteArray>() << "QString");
    QVERIFY(method2.parameterNames().isEmpty());
    QVERIFY(method2.tag().isEmpty());
    QCOMPARE(method2.access(), QMetaMethod::Public);
    QCOMPARE(method2.attributes(), 0);
    QCOMPARE(method2.index(), 1);
    QCOMPARE(builder.methodCount(), 2);

    // Perform index-based lookup
    QCOMPARE(builder.indexOfSlot("foo(const QString &, int)"), 0);
    QCOMPARE(builder.indexOfSlot("bar(QString)"), 1);
    QCOMPARE(builder.indexOfSlot("baz()"), -1);

    // Check that nothing else changed.
    QVERIFY(checkForSideEffects(builder, QMetaObjectBuilder::Methods));
}

void tst_QMetaObjectBuilder::signal()
{
    QMetaObjectBuilder builder;

    // Add a signal and check its attributes.
    QMetaMethodBuilder method1 = builder.addSignal("foo(const QString&, int)");
    QCOMPARE(method1.signature(), QByteArray("foo(QString,int)"));
    QCOMPARE(method1.methodType(), QMetaMethod::Signal);
    QCOMPARE(method1.returnType(), QByteArray("void"));
    QCOMPARE(method1.parameterTypes(), QList<QByteArray>() << "QString" << "int");
    QVERIFY(method1.parameterNames().isEmpty());
    QVERIFY(method1.tag().isEmpty());
    QCOMPARE(method1.access(), QMetaMethod::Public);
    QCOMPARE(method1.attributes(), 0);
    QCOMPARE(method1.index(), 0);
    QCOMPARE(builder.methodCount(), 1);

    // Add another signal and check again.
    QMetaMethodBuilder method2 = builder.addSignal("bar(QString)");
    QCOMPARE(method2.signature(), QByteArray("bar(QString)"));
    QCOMPARE(method2.methodType(), QMetaMethod::Signal);
    QCOMPARE(method2.returnType(), QByteArray("void"));
    QCOMPARE(method2.parameterTypes(), QList<QByteArray>() << "QString");
    QVERIFY(method2.parameterNames().isEmpty());
    QVERIFY(method2.tag().isEmpty());
    QCOMPARE(method2.access(), QMetaMethod::Public);
    QCOMPARE(method2.attributes(), 0);
    QCOMPARE(method2.index(), 1);
    QCOMPARE(builder.methodCount(), 2);

    // Perform index-based lookup
    QCOMPARE(builder.indexOfSignal("foo(const QString &, int)"), 0);
    QCOMPARE(builder.indexOfSignal("bar(QString)"), 1);
    QCOMPARE(builder.indexOfSignal("baz()"), -1);

    // Check that nothing else changed.
    QVERIFY(checkForSideEffects(builder, QMetaObjectBuilder::Methods));
}

void tst_QMetaObjectBuilder::constructor()
{
    QMetaObjectBuilder builder;

    // Add a constructor and check its attributes.
    QMetaMethodBuilder ctor1 = builder.addConstructor("foo(const QString&, int)");
    QCOMPARE(ctor1.signature(), QByteArray("foo(QString,int)"));
    QCOMPARE(ctor1.methodType(), QMetaMethod::Constructor);
    QVERIFY(ctor1.returnType().isEmpty());
    QCOMPARE(ctor1.parameterTypes(), QList<QByteArray>() << "QString" << "int");
    QVERIFY(ctor1.parameterNames().isEmpty());
    QVERIFY(ctor1.tag().isEmpty());
    QCOMPARE(ctor1.access(), QMetaMethod::Public);
    QCOMPARE(ctor1.attributes(), 0);
    QCOMPARE(ctor1.index(), 0);
    QCOMPARE(builder.constructorCount(), 1);

    // Add another constructor and check again.
    QMetaMethodBuilder ctor2 = builder.addConstructor("bar(QString)");
    QCOMPARE(ctor2.signature(), QByteArray("bar(QString)"));
    QCOMPARE(ctor2.methodType(), QMetaMethod::Constructor);
    QVERIFY(ctor2.returnType().isEmpty());
    QCOMPARE(ctor2.parameterTypes(), QList<QByteArray>() << "QString");
    QVERIFY(ctor2.parameterNames().isEmpty());
    QVERIFY(ctor2.tag().isEmpty());
    QCOMPARE(ctor2.access(), QMetaMethod::Public);
    QCOMPARE(ctor2.attributes(), 0);
    QCOMPARE(ctor2.index(), 1);
    QCOMPARE(builder.constructorCount(), 2);

    // Perform index-based lookup.
    QCOMPARE(builder.indexOfConstructor("foo(const QString&, int)"), 0);
    QCOMPARE(builder.indexOfConstructor("bar(QString)"), 1);
    QCOMPARE(builder.indexOfConstructor("baz()"), -1);
    QCOMPARE(builder.constructor(1).signature(), QByteArray("bar(QString)"));
    QCOMPARE(builder.constructor(9).signature(), QByteArray());

    // Modify the attributes on ctor1.
    ctor1.setReturnType("int");
    ctor1.setParameterNames(QList<QByteArray>() << "a" << "b");
    ctor1.setTag("tag");
    ctor1.setAccess(QMetaMethod::Private);
    ctor1.setAttributes(42);

    // Check that ctor1 is changed, but ctor2 is not.
    QCOMPARE(ctor1.signature(), QByteArray("foo(QString,int)"));
    QCOMPARE(ctor1.methodType(), QMetaMethod::Constructor);
    QCOMPARE(ctor1.returnType(), QByteArray("int"));
    QCOMPARE(ctor1.parameterTypes(), QList<QByteArray>() << "QString" << "int");
    QCOMPARE(ctor1.parameterNames(), QList<QByteArray>() << "a" << "b");
    QCOMPARE(ctor1.tag(), QByteArray("tag"));
    QCOMPARE(ctor1.access(), QMetaMethod::Private);
    QCOMPARE(ctor1.attributes(), 42);
    QCOMPARE(ctor1.index(), 0);
    QCOMPARE(ctor2.signature(), QByteArray("bar(QString)"));
    QCOMPARE(ctor2.methodType(), QMetaMethod::Constructor);
    QVERIFY(ctor2.returnType().isEmpty());
    QCOMPARE(ctor2.parameterTypes(), QList<QByteArray>() << "QString");
    QVERIFY(ctor2.parameterNames().isEmpty());
    QVERIFY(ctor2.tag().isEmpty());
    QCOMPARE(ctor2.access(), QMetaMethod::Public);
    QCOMPARE(ctor2.attributes(), 0);
    QCOMPARE(ctor2.index(), 1);
    QCOMPARE(builder.constructorCount(), 2);

    // Modify the attributes on ctor2.
    ctor2.setReturnType("QString");
    ctor2.setParameterNames(QList<QByteArray>() << "c");
    ctor2.setTag("Q_FOO");
    ctor2.setAccess(QMetaMethod::Protected);
    ctor2.setAttributes(24);

    // This time check that only ctor2 changed.
    QCOMPARE(ctor1.signature(), QByteArray("foo(QString,int)"));
    QCOMPARE(ctor1.methodType(), QMetaMethod::Constructor);
    QCOMPARE(ctor1.returnType(), QByteArray("int"));
    QCOMPARE(ctor1.parameterTypes(), QList<QByteArray>() << "QString" << "int");
    QCOMPARE(ctor1.parameterNames(), QList<QByteArray>() << "a" << "b");
    QCOMPARE(ctor1.tag(), QByteArray("tag"));
    QCOMPARE(ctor1.access(), QMetaMethod::Private);
    QCOMPARE(ctor1.attributes(), 42);
    QCOMPARE(ctor1.index(), 0);
    QCOMPARE(ctor2.signature(), QByteArray("bar(QString)"));
    QCOMPARE(ctor2.methodType(), QMetaMethod::Constructor);
    QCOMPARE(ctor2.returnType(), QByteArray("QString"));
    QCOMPARE(ctor2.parameterTypes(), QList<QByteArray>() << "QString");
    QCOMPARE(ctor2.parameterNames(), QList<QByteArray>() << "c");
    QCOMPARE(ctor2.tag(), QByteArray("Q_FOO"));
    QCOMPARE(ctor2.access(), QMetaMethod::Protected);
    QCOMPARE(ctor2.attributes(), 24);
    QCOMPARE(ctor2.index(), 1);
    QCOMPARE(builder.constructorCount(), 2);

    // Remove ctor1 and check that ctor2 becomes index 0.
    builder.removeConstructor(0);
    QCOMPARE(builder.constructorCount(), 1);
    ctor2 = builder.constructor(0);
    QCOMPARE(ctor2.signature(), QByteArray("bar(QString)"));
    QCOMPARE(ctor2.methodType(), QMetaMethod::Constructor);
    QCOMPARE(ctor2.returnType(), QByteArray("QString"));
    QCOMPARE(ctor2.parameterTypes(), QList<QByteArray>() << "QString");
    QCOMPARE(ctor2.parameterNames(), QList<QByteArray>() << "c");
    QCOMPARE(ctor2.tag(), QByteArray("Q_FOO"));
    QCOMPARE(ctor2.access(), QMetaMethod::Protected);
    QCOMPARE(ctor2.attributes(), 24);
    QCOMPARE(ctor2.index(), 0);

    // Perform index-based lookup again.
    QCOMPARE(builder.indexOfConstructor("foo(const QString&, int)"), -1);
    QCOMPARE(builder.indexOfConstructor("bar(QString)"), 0);
    QCOMPARE(builder.indexOfConstructor("baz()"), -1);

    // Add constructor from prototype
    QMetaMethod prototype = SomethingOfEverything::staticMetaObject.constructor(0);
    QMetaMethodBuilder prototypeConstructor = builder.addMethod(prototype);
    QCOMPARE(builder.constructorCount(), 2);

    QCOMPARE(prototypeConstructor.signature(), QByteArray("SomethingOfEverything()"));
    QCOMPARE(prototypeConstructor.methodType(), QMetaMethod::Constructor);
    QCOMPARE(prototypeConstructor.returnType(), QByteArray());
    QVERIFY(prototypeConstructor.parameterTypes().isEmpty());
    QCOMPARE(prototypeConstructor.access(), QMetaMethod::Public);
    QCOMPARE(prototypeConstructor.index(), 1);

    // Check that nothing else changed.
    QVERIFY(checkForSideEffects(builder, QMetaObjectBuilder::Constructors));
}

void tst_QMetaObjectBuilder::property()
{
    QMetaObjectBuilder builder;

    // Null property builder
    QMetaPropertyBuilder nullProp;
    QCOMPARE(nullProp.name(), QByteArray());
    QCOMPARE(nullProp.type(), QByteArray());
    QVERIFY(!nullProp.hasNotifySignal());
    QVERIFY(!nullProp.isReadable());
    QVERIFY(!nullProp.isWritable());
    QVERIFY(!nullProp.isResettable());
    QVERIFY(!nullProp.isDesignable());
    QVERIFY(!nullProp.isScriptable());
    QVERIFY(!nullProp.isStored());
    QVERIFY(!nullProp.isUser());
    QVERIFY(!nullProp.hasStdCppSet());
    QVERIFY(!nullProp.isEnumOrFlag());
    QVERIFY(!nullProp.isConstant());
    QVERIFY(!nullProp.isFinal());
    QCOMPARE(nullProp.index(), 0);
    QCOMPARE(nullProp.revision(), 0);

    // Add a property and check its attributes.
    QMetaPropertyBuilder prop1 = builder.addProperty("foo", "const QString &");
    QCOMPARE(prop1.name(), QByteArray("foo"));
    QCOMPARE(prop1.type(), QByteArray("QString"));
    QVERIFY(!prop1.hasNotifySignal());
    QVERIFY(prop1.isReadable());
    QVERIFY(prop1.isWritable());
    QVERIFY(!prop1.isResettable());
    QVERIFY(!prop1.isDesignable());
    QVERIFY(prop1.isScriptable());
    QVERIFY(!prop1.isStored());
    QVERIFY(!prop1.isUser());
    QVERIFY(!prop1.hasStdCppSet());
    QVERIFY(!prop1.isEnumOrFlag());
    QVERIFY(!prop1.isConstant());
    QVERIFY(!prop1.isFinal());
    QCOMPARE(prop1.revision(), 0);
    QCOMPARE(prop1.index(), 0);
    QCOMPARE(builder.propertyCount(), 1);

    // Add another property and check again.
    QMetaPropertyBuilder prop2 = builder.addProperty("bar", "int");
    QCOMPARE(prop2.name(), QByteArray("bar"));
    QCOMPARE(prop2.type(), QByteArray("int"));
    QVERIFY(!prop2.hasNotifySignal());
    QVERIFY(prop2.isReadable());
    QVERIFY(prop2.isWritable());
    QVERIFY(!prop2.isResettable());
    QVERIFY(!prop2.isDesignable());
    QVERIFY(prop2.isScriptable());
    QVERIFY(!prop2.isStored());
    QVERIFY(!prop2.isUser());
    QVERIFY(!prop2.hasStdCppSet());
    QVERIFY(!prop2.isEnumOrFlag());
    QVERIFY(!prop2.isConstant());
    QVERIFY(!prop2.isFinal());
    QCOMPARE(prop2.revision(), 0);
    QCOMPARE(prop2.index(), 1);
    QCOMPARE(builder.propertyCount(), 2);

    // Perform index-based lookup.
    QCOMPARE(builder.indexOfProperty("foo"), 0);
    QCOMPARE(builder.indexOfProperty("bar"), 1);
    QCOMPARE(builder.indexOfProperty("baz"), -1);
    QCOMPARE(builder.property(1).name(), QByteArray("bar"));
    QCOMPARE(builder.property(9).name(), QByteArray());

    // Modify the attributes on prop1.
    prop1.setReadable(false);
    prop1.setWritable(false);
    prop1.setResettable(true);
    prop1.setDesignable(true);
    prop1.setScriptable(false);
    prop1.setStored(true);
    prop1.setUser(true);
    prop1.setStdCppSet(true);
    prop1.setEnumOrFlag(true);
    prop1.setConstant(true);
    prop1.setFinal(true);
    prop1.setRevision(123);

    // Check that prop1 is changed, but prop2 is not.
    QCOMPARE(prop1.name(), QByteArray("foo"));
    QCOMPARE(prop1.type(), QByteArray("QString"));
    QVERIFY(!prop1.isReadable());
    QVERIFY(!prop1.isWritable());
    QVERIFY(prop1.isResettable());
    QVERIFY(prop1.isDesignable());
    QVERIFY(!prop1.isScriptable());
    QVERIFY(prop1.isStored());
    QVERIFY(prop1.isUser());
    QVERIFY(prop1.hasStdCppSet());
    QVERIFY(prop1.isEnumOrFlag());
    QVERIFY(prop1.isConstant());
    QVERIFY(prop1.isFinal());
    QCOMPARE(prop1.revision(), 123);
    QVERIFY(prop2.isReadable());
    QVERIFY(prop2.isWritable());
    QCOMPARE(prop2.name(), QByteArray("bar"));
    QCOMPARE(prop2.type(), QByteArray("int"));
    QVERIFY(!prop2.isResettable());
    QVERIFY(!prop2.isDesignable());
    QVERIFY(prop2.isScriptable());
    QVERIFY(!prop2.isStored());
    QVERIFY(!prop2.isUser());
    QVERIFY(!prop2.hasStdCppSet());
    QVERIFY(!prop2.isEnumOrFlag());
    QVERIFY(!prop2.isConstant());
    QVERIFY(!prop2.isFinal());
    QCOMPARE(prop2.revision(), 0);

    // Remove prop1 and check that prop2 becomes index 0.
    builder.removeProperty(0);
    QCOMPARE(builder.propertyCount(), 1);
    prop2 = builder.property(0);
    QCOMPARE(prop2.name(), QByteArray("bar"));
    QCOMPARE(prop2.type(), QByteArray("int"));
    QVERIFY(!prop2.isResettable());
    QVERIFY(!prop2.isDesignable());
    QVERIFY(prop2.isScriptable());
    QVERIFY(!prop2.isStored());
    QVERIFY(!prop2.isUser());
    QVERIFY(!prop2.hasStdCppSet());
    QVERIFY(!prop2.isEnumOrFlag());
    QVERIFY(!prop2.isConstant());
    QVERIFY(!prop2.isFinal());
    QCOMPARE(prop2.revision(), 0);
    QCOMPARE(prop2.index(), 0);

    // Perform index-based lookup again.
    QCOMPARE(builder.indexOfProperty("foo"), -1);
    QCOMPARE(builder.indexOfProperty("bar"), 0);
    QCOMPARE(builder.indexOfProperty("baz"), -1);

    // Check for side-effects between the flags on prop2.
    // Setting a flag to true shouldn't set any of the others to true.
    // This checks for cut-and-paste bugs in the implementation where
    // the flag code was pasted but the flag name was not changed.
#define CLEAR_FLAGS() \
        do { \
            prop2.setReadable(false); \
            prop2.setWritable(false); \
            prop2.setResettable(false); \
            prop2.setDesignable(false); \
            prop2.setScriptable(false); \
            prop2.setStored(false); \
            prop2.setUser(false); \
            prop2.setStdCppSet(false); \
            prop2.setEnumOrFlag(false); \
            prop2.setConstant(false); \
            prop2.setFinal(false); \
        } while (0)
#define COUNT_FLAGS() \
        ((prop2.isReadable() ? 1 : 0) + \
         (prop2.isWritable() ? 1 : 0) + \
         (prop2.isResettable() ? 1 : 0) + \
         (prop2.isDesignable() ? 1 : 0) + \
         (prop2.isScriptable() ? 1 : 0) + \
         (prop2.isStored() ? 1 : 0) + \
         (prop2.isUser() ? 1 : 0) + \
         (prop2.hasStdCppSet() ? 1 : 0) + \
         (prop2.isEnumOrFlag() ? 1 : 0) + \
         (prop2.isConstant() ? 1 : 0) + \
         (prop2.isFinal() ? 1 : 0))
#define CHECK_FLAG(setFunc,isFunc) \
        do { \
            CLEAR_FLAGS(); \
            QCOMPARE(COUNT_FLAGS(), 0); \
            prop2.setFunc(true); \
            QVERIFY(prop2.isFunc()); \
            QCOMPARE(COUNT_FLAGS(), 1); \
        } while (0)
    CHECK_FLAG(setReadable, isReadable);
    CHECK_FLAG(setWritable, isWritable);
    CHECK_FLAG(setResettable, isResettable);
    CHECK_FLAG(setDesignable, isDesignable);
    CHECK_FLAG(setScriptable, isScriptable);
    CHECK_FLAG(setStored, isStored);
    CHECK_FLAG(setUser, isUser);
    CHECK_FLAG(setStdCppSet, hasStdCppSet);
    CHECK_FLAG(setEnumOrFlag, isEnumOrFlag);
    CHECK_FLAG(setConstant, isConstant);
    CHECK_FLAG(setFinal, isFinal);

    // Check that nothing else changed.
    QVERIFY(checkForSideEffects(builder, QMetaObjectBuilder::Properties));

    // Add property from prototype
    QMetaProperty prototype = SomethingOfEverything::staticMetaObject.property(1);
    QVERIFY(prototype.name() == QByteArray("prop"));
    QMetaPropertyBuilder prototypeProp = builder.addProperty(prototype);
    QCOMPARE(prototypeProp.name(), QByteArray("prop"));
    QVERIFY(prototypeProp.hasNotifySignal());
    QCOMPARE(prototypeProp.notifySignal().signature(), QByteArray("propChanged(QString)"));
    QCOMPARE(builder.methodCount(), 1);
    QCOMPARE(builder.method(0).signature(), QByteArray("propChanged(QString)"));
}

void tst_QMetaObjectBuilder::variantProperty()
{
    QMetaObjectBuilder builder;
    builder.addProperty("variant", "const QVariant &");
    QMetaObject *meta = builder.toMetaObject();
    dynamicMetaObjectsPendingFree.push_back(meta);

    QMetaProperty prop = meta->property(meta->propertyOffset());
    QCOMPARE(QMetaType::Type(prop.userType()), QMetaType::QVariant);
    QCOMPARE(QMetaType::Type(prop.userType()), QMetaType::QVariant);
    QCOMPARE(QByteArray(prop.typeName()), QByteArray("QVariant"));
}

void tst_QMetaObjectBuilder::notifySignal()
{
    QMetaObjectBuilder builder;

    QMetaPropertyBuilder prop = builder.addProperty("foo", "const QString &");
    builder.addSlot("setFoo(QString)");
    QMetaMethodBuilder notify = builder.addSignal("fooChanged(QString)");

    QVERIFY(!prop.hasNotifySignal());
    QCOMPARE(prop.notifySignal().index(), 0);

    prop.setNotifySignal(notify);
    QVERIFY(prop.hasNotifySignal());
    QCOMPARE(prop.notifySignal().index(), 1);

    prop.setNotifySignal(QMetaMethodBuilder());
    QVERIFY(!prop.hasNotifySignal());
    QCOMPARE(prop.notifySignal().index(), 0);

    prop.setNotifySignal(notify);
    prop.removeNotifySignal();
    QVERIFY(!prop.hasNotifySignal());
    QCOMPARE(prop.notifySignal().index(), 0);

    QCOMPARE(builder.methodCount(), 2);
    QCOMPARE(builder.propertyCount(), 1);

    // Check that nothing else changed except methods and properties.
    QVERIFY(checkForSideEffects
        (builder, QMetaObjectBuilder::Methods | QMetaObjectBuilder::Properties));
}

void tst_QMetaObjectBuilder::enumerator()
{
    static const QtPrivate::QMetaTypeInterface fooFlagMetaType = {
        0,
        8,
        8,
        QMetaType::IsEnumeration | QMetaType::IsUnsignedEnumeration | QMetaType::RelocatableType,
        {},
        nullptr,
        "fooFlag",
        nullptr,
        nullptr,
        nullptr,
        nullptr,
        nullptr,
        nullptr,
        nullptr,
        nullptr,
        nullptr,
        nullptr,
    };

    QMetaObjectBuilder builder;

    // Add an enumerator and check its attributes.
    QMetaEnumBuilder enum1 = builder.addEnumerator("foo");
    QCOMPARE(enum1.name(), QByteArray("foo"));
    QVERIFY(!enum1.isFlag());
    QVERIFY(!enum1.isScoped());
    QCOMPARE(enum1.keyCount(), 0);
    QCOMPARE(enum1.index(), 0);
    QCOMPARE(builder.enumeratorCount(), 1);

    // Add another enumerator and check again.
    QMetaEnumBuilder enum2 = builder.addEnumerator("bar");
    QCOMPARE(enum2.name(), QByteArray("bar"));
    QVERIFY(!enum2.isFlag());
    QVERIFY(!enum2.isScoped());
    QCOMPARE(enum2.keyCount(), 0);
    QCOMPARE(enum2.index(), 1);
    QCOMPARE(builder.enumeratorCount(), 2);

    // Perform index-based lookup.
    QCOMPARE(builder.indexOfEnumerator("foo"), 0);
    QCOMPARE(builder.indexOfEnumerator("bar"), 1);
    QCOMPARE(builder.indexOfEnumerator("baz"), -1);
    QCOMPARE(builder.enumerator(1).name(), QByteArray("bar"));
    QCOMPARE(builder.enumerator(9).name(), QByteArray());

    // Modify the attributes on enum1.
    enum1.setIsFlag(true);
    enum1.setIsScoped(true);
    enum1.setEnumName(QByteArrayLiteral("fooFlag"));
    enum1.setMetaType(QMetaType(&fooFlagMetaType));
    QCOMPARE(enum1.addKey("ABC", 0), 0);
    QCOMPARE(enum1.addKey("DEF", 1), 1);
    QCOMPARE(enum1.addKey("GHI", -1), 2);

    // Check that enum1 is changed, but enum2 is not.
    QCOMPARE(enum1.name(), QByteArray("foo"));
    QVERIFY(enum1.isFlag());
    QVERIFY(enum1.isScoped());
    QCOMPARE(enum1.enumName(), QByteArray("fooFlag"));
    QCOMPARE(enum1.metaType(), QMetaType(&fooFlagMetaType));
    QCOMPARE(enum1.keyCount(), 3);
    QCOMPARE(enum1.index(), 0);
    QCOMPARE(enum1.key(0), QByteArray("ABC"));
    QCOMPARE(enum1.key(1), QByteArray("DEF"));
    QCOMPARE(enum1.key(2), QByteArray("GHI"));
    QCOMPARE(enum1.key(3), QByteArray());
    QCOMPARE(enum1.value(0), 0);
    QCOMPARE(enum1.value(1), 1);
    QCOMPARE(enum1.value(2), -1);
    QCOMPARE(enum2.name(), QByteArray("bar"));
    QVERIFY(!enum2.isFlag());
    QVERIFY(!enum2.isScoped());
    QCOMPARE(enum2.keyCount(), 0);
    QCOMPARE(enum2.index(), 1);

    // Modify the attributes on enum2.
    enum2.setIsFlag(true);
    QCOMPARE(enum2.addKey("XYZ", 10), 0);
    QCOMPARE(enum2.addKey("UVW", 19), 1);

    // This time check that only method2 changed.
    QCOMPARE(enum1.name(), QByteArray("foo"));
    QVERIFY(enum1.isFlag());
    QVERIFY(enum1.isScoped());
    QCOMPARE(enum1.keyCount(), 3);
    QCOMPARE(enum1.index(), 0);
    QCOMPARE(enum1.key(0), QByteArray("ABC"));
    QCOMPARE(enum1.key(1), QByteArray("DEF"));
    QCOMPARE(enum1.key(2), QByteArray("GHI"));
    QCOMPARE(enum1.key(3), QByteArray());
    QCOMPARE(enum1.value(0), 0);
    QCOMPARE(enum1.value(1), 1);
    QCOMPARE(enum1.value(2), -1);
    QCOMPARE(enum2.name(), QByteArray("bar"));
    QVERIFY(enum2.isFlag());
    QVERIFY(!enum2.isScoped());
    QCOMPARE(enum2.keyCount(), 2);
    QCOMPARE(enum2.index(), 1);
    QCOMPARE(enum2.key(0), QByteArray("XYZ"));
    QCOMPARE(enum2.key(1), QByteArray("UVW"));
    QCOMPARE(enum2.key(2), QByteArray());
    QCOMPARE(enum2.value(0), 10);
    QCOMPARE(enum2.value(1), 19);

    // Remove enum1 key
    enum1.removeKey(2);
    QCOMPARE(enum1.name(), QByteArray("foo"));
    QVERIFY(enum1.isFlag());
    QVERIFY(enum1.isScoped());
    QCOMPARE(enum1.keyCount(), 2);
    QCOMPARE(enum1.index(), 0);
    QCOMPARE(enum1.key(0), QByteArray("ABC"));
    QCOMPARE(enum1.key(1), QByteArray("DEF"));
    QCOMPARE(enum1.key(2), QByteArray());
    QCOMPARE(enum1.value(0), 0);
    QCOMPARE(enum1.value(1), 1);
    QCOMPARE(enum1.value(2), -1);
    QCOMPARE(enum2.name(), QByteArray("bar"));
    QVERIFY(enum2.isFlag());
    QVERIFY(!enum2.isScoped());
    QCOMPARE(enum2.keyCount(), 2);
    QCOMPARE(enum2.index(), 1);
    QCOMPARE(enum2.key(0), QByteArray("XYZ"));
    QCOMPARE(enum2.key(1), QByteArray("UVW"));
    QCOMPARE(enum2.key(2), QByteArray());
    QCOMPARE(enum2.value(0), 10);
    QCOMPARE(enum2.value(1), 19);

    // Remove enum1 and check that enum2 becomes index 0.
    builder.removeEnumerator(0);
    QCOMPARE(builder.enumeratorCount(), 1);
    enum2 = builder.enumerator(0);
    QCOMPARE(enum2.name(), QByteArray("bar"));
    QVERIFY(enum2.isFlag());
    QVERIFY(!enum2.isScoped());
    QCOMPARE(enum2.keyCount(), 2);
    QCOMPARE(enum2.index(), 0);
    QCOMPARE(enum2.key(0), QByteArray("XYZ"));
    QCOMPARE(enum2.key(1), QByteArray("UVW"));
    QCOMPARE(enum2.key(2), QByteArray());
    QCOMPARE(enum2.value(0), 10);
    QCOMPARE(enum2.value(1), 19);

    // Perform index-based lookup again.
    QCOMPARE(builder.indexOfEnumerator("foo"), -1);
    QCOMPARE(builder.indexOfEnumerator("bar"), 0);
    QCOMPARE(builder.indexOfEnumerator("baz"), -1);

    // Check that nothing else changed.
    QVERIFY(checkForSideEffects(builder, QMetaObjectBuilder::Enumerators));
}

void tst_QMetaObjectBuilder::classInfo()
{
    QMetaObjectBuilder builder;

    // Add two items of class information and check their attributes.
    QCOMPARE(builder.addClassInfo("foo", "value1"), 0);
    QCOMPARE(builder.addClassInfo("bar", "value2"), 1);
    QCOMPARE(builder.classInfoName(0), QByteArray("foo"));
    QCOMPARE(builder.classInfoValue(0), QByteArray("value1"));
    QCOMPARE(builder.classInfoName(1), QByteArray("bar"));
    QCOMPARE(builder.classInfoValue(1), QByteArray("value2"));
    QCOMPARE(builder.classInfoName(9), QByteArray());
    QCOMPARE(builder.classInfoValue(9), QByteArray());
    QCOMPARE(builder.classInfoCount(), 2);

    // Perform index-based lookup.
    QCOMPARE(builder.indexOfClassInfo("foo"), 0);
    QCOMPARE(builder.indexOfClassInfo("bar"), 1);
    QCOMPARE(builder.indexOfClassInfo("baz"), -1);

    // Remove the first one and check again.
    builder.removeClassInfo(0);
    QCOMPARE(builder.classInfoName(0), QByteArray("bar"));
    QCOMPARE(builder.classInfoValue(0), QByteArray("value2"));
    QCOMPARE(builder.classInfoCount(), 1);

    // Perform index-based lookup again.
    QCOMPARE(builder.indexOfClassInfo("foo"), -1);
    QCOMPARE(builder.indexOfClassInfo("bar"), 0);
    QCOMPARE(builder.indexOfClassInfo("baz"), -1);

    // Check that nothing else changed.
    QVERIFY(checkForSideEffects(builder, QMetaObjectBuilder::ClassInfos));
}

void tst_QMetaObjectBuilder::relatedMetaObject()
{
    QMetaObjectBuilder builder;

    // Add two related meta objects and check their attributes.
    QCOMPARE(builder.addRelatedMetaObject(&QObject::staticMetaObject), 0);
    QCOMPARE(builder.addRelatedMetaObject(&staticMetaObject), 1);
    QCOMPARE(builder.relatedMetaObject(0), &QObject::staticMetaObject);
    QCOMPARE(builder.relatedMetaObject(1), &staticMetaObject);
    QCOMPARE(builder.relatedMetaObjectCount(), 2);

    // Remove the first one and check again.
    builder.removeRelatedMetaObject(0);
    QCOMPARE(builder.relatedMetaObject(0), &staticMetaObject);
    QCOMPARE(builder.relatedMetaObjectCount(), 1);

    // Check that nothing else changed.
    QVERIFY(checkForSideEffects(builder, QMetaObjectBuilder::RelatedMetaObjects));
}

static void smetacall(QObject *, QMetaObject::Call, int, void **)
{
    return;
}

void tst_QMetaObjectBuilder::staticMetacall()
{
    QMetaObjectBuilder builder;
    QVERIFY(!builder.staticMetacallFunction());
    builder.setStaticMetacallFunction(smetacall);
    QVERIFY(builder.staticMetacallFunction() == smetacall);
    QVERIFY(checkForSideEffects(builder, QMetaObjectBuilder::StaticMetacall));
}

// Copy the entire contents of a static QMetaObject and then check
// that QMetaObjectBuilder will produce an exact copy as output.
void tst_QMetaObjectBuilder::copyMetaObject()
{
    QMetaObjectBuilder builder(&QObject::staticMetaObject);
    QMetaObject *meta = builder.toMetaObject();
    dynamicMetaObjectsPendingFree.push_back(meta);
    auto compared = sameMetaObject(meta, &QObject::staticMetaObject);
    QVERIFY2(compared, qPrintable(compared.details));

    QMetaObjectBuilder builder2(&staticMetaObject);
    meta = builder2.toMetaObject();
    dynamicMetaObjectsPendingFree.push_back(meta);
    compared = sameMetaObject(meta, &staticMetaObject);
    QVERIFY2(compared, qPrintable(compared.details));

    QMetaObjectBuilder builder3(&SomethingOfEverything::staticMetaObject);
    meta = builder3.toMetaObject();
    dynamicMetaObjectsPendingFree.push_back(meta);
    compared = sameMetaObject(meta, &SomethingOfEverything::staticMetaObject);
    QVERIFY2(compared, qPrintable(compared.details));
}

// Check that removing a method updates notify signals appropriately
void tst_QMetaObjectBuilder::removeNotifySignal()
{
    QMetaObjectBuilder builder;

    builder.addSignal("foo(const QString&, int)");
    QMetaMethodBuilder method = builder.addSignal("bar(QString)");

    // Setup property
    QMetaPropertyBuilder prop = builder.addProperty("prop", "const QString &");
    prop.setNotifySignal(method);
    QVERIFY(prop.hasNotifySignal());
    QCOMPARE(prop.notifySignal().index(), 1);

    // Remove non-notify signal
    builder.removeMethod(0);
    QVERIFY(prop.hasNotifySignal());
    QCOMPARE(prop.notifySignal().index(), 0);

    // Remove notify signal
    builder.removeMethod(0);
    QVERIFY(!prop.hasNotifySignal());
}

// Check that the only changes to a "builder" relative to the default
// state is specified by "members".
bool tst_QMetaObjectBuilder::checkForSideEffects
        (const QMetaObjectBuilder& builder,
         QMetaObjectBuilder::AddMembers members)
{
    if ((members & QMetaObjectBuilder::ClassName) == 0) {
        if (!builder.className().isEmpty())
            return false;
    }

    if ((members & QMetaObjectBuilder::SuperClass) == 0) {
        if (builder.superClass() != &QObject::staticMetaObject)
            return false;
    }

    if ((members & QMetaObjectBuilder::Methods) == 0) {
        if (builder.methodCount() != 0)
            return false;
    }

    if ((members & QMetaObjectBuilder::Constructors) == 0) {
        if (builder.constructorCount() != 0)
            return false;
    }

    if ((members & QMetaObjectBuilder::Properties) == 0) {
        if (builder.propertyCount() != 0)
            return false;
    }

    if ((members & QMetaObjectBuilder::Enumerators) == 0) {
        if (builder.enumeratorCount() != 0)
            return false;
    }

    if ((members & QMetaObjectBuilder::ClassInfos) == 0) {
        if (builder.classInfoCount() != 0)
            return false;
    }

    if ((members & QMetaObjectBuilder::RelatedMetaObjects) == 0) {
        if (builder.relatedMetaObjectCount() != 0)
            return false;
    }

    if ((members & QMetaObjectBuilder::StaticMetacall) == 0) {
        if (builder.staticMetacallFunction() != 0)
            return false;
    }

    return true;
}

static bool sameMethod(const QMetaMethod& method1, const QMetaMethod& method2)
{
    if (method1.methodSignature() != method2.methodSignature())
        return false;

    if (QByteArray(method1.typeName()) != QByteArray(method2.typeName()))
        return false;

    if (method1.parameterTypes() != method2.parameterTypes())
        return false;

    if (method1.parameterNames() != method2.parameterNames())
        return false;

    if (QByteArray(method1.tag()) != QByteArray(method2.tag()))
        return false;

    if (method1.access() != method2.access())
        return false;

    if (method1.methodType() != method2.methodType())
        return false;

    if (method1.attributes() != method2.attributes())
        return false;

    if (method1.revision() != method2.revision())
        return false;

    return true;
}

static MetaObjectComparison sameProperty(const QMetaProperty& prop1, const QMetaProperty& prop2)
{
    if (QByteArray(prop1.name()) != QByteArray(prop2.name()))
        return MetaObjectComparison::Failed(QStringLiteral("Property names differ: %1 vs %2").arg(prop1.name(), prop2.name()));

    if (QByteArray(prop1.typeName()) != QByteArray(prop2.typeName()))
        return MetaObjectComparison::Failed(QStringLiteral("Property type names differ: %1 vs %2").arg(prop1.typeName(), prop2.typeName()));

    if (prop1.isReadable() != prop2.isReadable() ||
        prop1.isWritable() != prop2.isWritable() ||
        prop1.isResettable() != prop2.isResettable() ||
        prop1.isDesignable() != prop2.isDesignable() ||
        prop1.isScriptable() != prop2.isScriptable() ||
        prop1.isStored() != prop2.isStored() ||
        prop1.isUser() != prop2.isUser() ||
        prop1.isFlagType() != prop2.isFlagType() ||
        prop1.isEnumType() != prop2.isEnumType() ||
        prop1.hasNotifySignal() != prop2.hasNotifySignal() ||
        prop1.hasStdCppSet() != prop2.hasStdCppSet())
        return MetaObjectComparison::Failed(u"Flags differ");

    if (prop1.hasNotifySignal()) {
        if (prop1.notifySignalIndex() != prop2.notifySignalIndex())
            return MetaObjectComparison::Failed(QStringLiteral("Notify signal index differ: %1 vs %2").arg(
                                                    QString::number(prop1.notifySignalIndex()),
                                                    QString::number(prop2.notifySignalIndex())));
    }

    const int revision1 = prop1.revision();
    const int revision2 = prop2.revision();
    if (revision1 != revision2)
        return MetaObjectComparison::Failed(QStringLiteral("Revisions differ: %1 vs %2").arg(QString::number(revision1), QString::number(revision2)));

    return MetaObjectComparison::Ok();
}

static bool sameEnumerator(const QMetaEnum& enum1, const QMetaEnum& enum2)
{
    if (QByteArray(enum1.name()) != QByteArray(enum2.name()))
        return false;

    if (enum1.isFlag() != enum2.isFlag())
        return false;

    if (enum1.keyCount() != enum2.keyCount())
        return false;

    for (int index = 0; index < enum1.keyCount(); ++index) {
        if (QByteArray(enum1.key(index)) != QByteArray(enum2.key(index)))
            return false;
        if (enum1.value(index) != enum2.value(index))
            return false;
    }

    if (QByteArray(enum1.scope()) != QByteArray(enum2.scope()))
        return false;

    return true;
}

// Determine if two meta objects are identical.
MetaObjectComparison sameMetaObject(const QMetaObject *meta1, const QMetaObject *meta2)
{
    int index;

    if (strcmp(meta1->className(), meta2->className()) != 0) {
        QString message = QLatin1String("Class names differ: %1 %2").arg(meta1->className(), meta2->className());
        return MetaObjectComparison::Failed(message);
    }

    if (meta1->superClass() != meta2->superClass())
        return MetaObjectComparison::Failed(QStringLiteral("Super classes differ"));

    auto numCompare = [](int num1, int num2, const QString &message) {
        if (num1 != num2)
            return MetaObjectComparison::Failed(message.arg(QString::number(num1), QString::number(num2)));
        return MetaObjectComparison::Ok();
    };

    auto compared = numCompare(meta1->constructorCount(), meta2->constructorCount(), QStringLiteral("Construct counts differ: %1 %2"));
    if (!compared.isSame)
        return compared;
    compared = numCompare(meta1->methodCount(), meta2->methodCount(), QStringLiteral("Method counts differ: %1 %2"));
    if (!compared.isSame)
        return compared;
    compared = numCompare(meta1->enumeratorCount(), meta2->enumeratorCount(), QStringLiteral("Enumerator counts differ: %1 %2"));
    if (!compared.isSame)
        return compared;
    compared = numCompare(meta1->propertyCount(), meta2->propertyCount(), QStringLiteral("Property counts differ: %1 %2"));
    if (!compared.isSame)
        return compared;
    compared = numCompare(meta1->classInfoCount(), meta2->classInfoCount(), QStringLiteral("ClassInfo counts differ: %1 %2"));
    if (!compared.isSame)
        return compared;

    for (index = 0; index < meta1->constructorCount(); ++index) {
        if (!sameMethod(meta1->constructor(index), meta2->constructor(index)))
            return MetaObjectComparison::Failed(QStringLiteral("Constructors difer at index %1").arg(index));
    }

    for (index = 0; index < meta1->methodCount(); ++index) {
        if (!sameMethod(meta1->method(index), meta2->method(index)))
            return MetaObjectComparison::Failed(QStringLiteral("Methods difer at index %1").arg(index));
    }

    for (index = 0; index < meta1->propertyCount(); ++index) {
        if (auto compared = sameProperty(meta1->property(index), meta2->property(index)); !compared) {
            compared.details += QStringLiteral(" at index %1").arg(index);
            return compared;
        }
    }

    for (index = 0; index < meta1->enumeratorCount(); ++index) {
        if (!sameEnumerator(meta1->enumerator(index), meta2->enumerator(index)))
            return MetaObjectComparison::Failed(QStringLiteral("Enumerators difer at index %1").arg(index));
    }

    for (index = 0; index < meta1->classInfoCount(); ++index) {
        const auto name1= QByteArray(meta1->classInfo(index).name());
        const auto name2= QByteArray(meta1->classInfo(index).name());
        if (name1 != name2)
            return MetaObjectComparison::Failed(QStringLiteral("Class infos difer at index %1: %2 vs %3").arg(QString::number(index), name1, name2));
        const auto value1 = QByteArray(meta1->classInfo(index).value());
        const auto value2= QByteArray(meta2->classInfo(index).value());
        if ( value1 != value2)
            return MetaObjectComparison::Failed(QStringLiteral("Class infos difer at index %1: %2 vs %3").arg(QString::number(index), value1, value2));
    }

    const auto *objects1 = meta1->d.relatedMetaObjects;
    const auto *objects2 = meta2->d.relatedMetaObjects;
    if (objects1 && !objects2)
        return MetaObjectComparison::Failed(u"Metaobject 1 had related metaobjects, but Metaoject 2 did not");
    if (objects2 && !objects1)
        return MetaObjectComparison::Failed(u"Metaobject 2 had related metaobjects, but Metaoject 1 did not");
    if (objects1 && objects2) {
        while (*objects1 != 0 && *objects2 != 0) {
            if (*objects1 != *objects2)
                return MetaObjectComparison::Failed(QStringLiteral("Related metaobjects differ at index %1").arg(index));
            ++objects1;
            ++objects2;
        }
    }

    return MetaObjectComparison::Ok();
}


// This class is used to test that the meta-object generated by QMOB can be
// used by a real object.
// The class manually implements the functions normally generated by moc, and
// creates the corresponding meta-object using QMOB. The autotests check that
// this object can be used by QObject/QMetaObject functionality (property
// access, signals & slots, constructing instances, ...).

class TestObject : public QObject
{
    // Manually expanded from Q_OBJECT macro
public:
    static QMetaObject staticMetaObject;
    virtual const QMetaObject *metaObject() const override;
    virtual void *qt_metacast(const char *) override;
    virtual int qt_metacall(QMetaObject::Call, int, void **) override;
private:
    Q_DECL_HIDDEN static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **);

    //Q_PROPERTY(int intProp READ intProp WRITE setIntProp NOTIFY intPropChanged)
public:
    TestObject(QObject *parent = nullptr); // Q_INVOKABLE
    ~TestObject();

    // Property accessors
    int intProp() const;
    void setIntProp(int v);

    void emitIntPropChanged();

    int voidSlotIntArgument() const;

// Q_INVOKABLE
    QVariantList listInvokableQRealQString(qreal, const QString &);

//public Q_SLOTS:
    void voidSlotInt(int);

//Q_SIGNALS:
    void intPropChanged(int);

private:
    static QMetaObject *buildMetaObject();

    QMetaObject *m_metaObject;
    int m_intProp;
    int m_voidSlotIntArg;
};

QMetaObject TestObject::staticMetaObject = {
    { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}
};

TestObject::TestObject(QObject *parent)
    : QObject(parent), m_metaObject(buildMetaObject()),
      m_intProp(-1), m_voidSlotIntArg(-1)
{
    staticMetaObject = *m_metaObject;
}

TestObject::~TestObject()
{
    free(m_metaObject);
}

QMetaObject *TestObject::buildMetaObject()
{
    QMetaObjectBuilder builder;
    // NOTE: If you change the meta-object, remember to adapt qt_metacall and
    // friends below accordingly.

    builder.setClassName("TestObject");

    builder.setStaticMetacallFunction(qt_static_metacall);

    QMetaMethodBuilder intPropChanged = builder.addSignal("intPropChanged(int)");
    intPropChanged.setParameterNames(QList<QByteArray>() << "newIntPropValue");

    QMetaPropertyBuilder prop = builder.addProperty("intProp", "int");
    prop.setNotifySignal(intPropChanged);

    QMetaMethodBuilder voidSlotInt = builder.addSlot("voidSlotInt(int)");
    voidSlotInt.setParameterNames(QList<QByteArray>() << "slotIntArg");

    QMetaMethodBuilder listInvokableQRealQString = builder.addMethod("listInvokableQRealQString(qreal,QString)");
    listInvokableQRealQString.setReturnType("QVariantList");
    listInvokableQRealQString.setParameterNames(QList<QByteArray>() << "qrealArg" << "qstringArg");

    builder.addConstructor("TestObject(QObject*)");
    builder.addConstructor("TestObject()");

    return builder.toMetaObject();
}

int TestObject::intProp() const
{
    return m_intProp;
}

void TestObject::setIntProp(int value)
{
    if (m_intProp != value) {
        m_intProp = value;
        emit intPropChanged(value);
    }
}

void TestObject::emitIntPropChanged()
{
    emit intPropChanged(m_intProp);
}

QVariantList TestObject::listInvokableQRealQString(qreal r, const QString &s)
{
    return QVariantList() << r << s;
}

void TestObject::voidSlotInt(int value)
{
    m_voidSlotIntArg = value;
}

int TestObject::voidSlotIntArgument() const
{
    return m_voidSlotIntArg;
}

void TestObject::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
    if (_c == QMetaObject::CreateInstance) {
        switch (_id) {
        case 0: { TestObject *_r = new TestObject((*reinterpret_cast< QObject*(*)>(_a[1])));
            if (_a[0]) *reinterpret_cast<QObject**>(_a[0]) = _r; } break;
        case 1: { TestObject *_r = new TestObject();
            if (_a[0]) *reinterpret_cast<QObject**>(_a[0]) = _r; } break;
        default: {
            QMetaMethod ctor = _o->metaObject()->constructor(_id);
            qFatal("You forgot to add a case for CreateInstance %s", ctor.methodSignature().constData());
          }
        }
    } else if (_c == QMetaObject::InvokeMetaMethod) {
        Q_ASSERT(_o->metaObject()->cast(_o));
        TestObject *_t = static_cast<TestObject *>(_o);
        switch (_id) {
        case 0: _t->intPropChanged((*reinterpret_cast< int(*)>(_a[1]))); break;
        case 1: _t->voidSlotInt((*reinterpret_cast< int(*)>(_a[1]))); break;
        case 2: *reinterpret_cast<QVariantList(*)>(_a[0]) = _t->listInvokableQRealQString(*reinterpret_cast<qreal(*)>(_a[1]), *reinterpret_cast<QString(*)>(_a[2])); break;
        default: {
            QMetaMethod method = _o->metaObject()->method(_o->metaObject()->methodOffset() + _id);
            qFatal("You forgot to add a case for InvokeMetaMethod %s", method.methodSignature().constData());
          }
        }
    } else if (_c == QMetaObject::IndexOfMethod) {
        int *result = reinterpret_cast<int *>(_a[0]);
        void **func = reinterpret_cast<void **>(_a[1]);
        {
            typedef void (TestObject::*_t)(int );
            if (*reinterpret_cast<_t *>(func) == static_cast<_t>(&TestObject::intPropChanged)) {
                *result = 0;
            }
        }
        {
            typedef void (TestObject::*_t)(int );
            if (*reinterpret_cast<_t *>(func) == static_cast<_t>(&TestObject::voidSlotInt)) {
                *result = 1;
            }
        }
        {
            typedef QVariantList (TestObject::*_t)(qreal, const QString &);
            if (*reinterpret_cast<_t *>(func) == static_cast<_t>(&TestObject::listInvokableQRealQString)) {
                *result = 2;
            }
        }
    }
}

const QMetaObject *TestObject::metaObject() const
{
    return m_metaObject;
}

void *TestObject::qt_metacast(const char *_clname)
{
    if (!_clname) return 0;
    if (!strcmp(_clname, "TestObject"))
        return static_cast<void*>(const_cast< TestObject*>(this));
    return QObject::qt_metacast(_clname);
}

int TestObject::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
{
    _id = QObject::qt_metacall(_c, _id, _a);
    if (_id < 0)
        return _id;
    int ownMethodCount = m_metaObject->methodCount() - m_metaObject->methodOffset();
    int ownPropertyCount = m_metaObject->propertyCount() - m_metaObject->propertyOffset();
    if (_c == QMetaObject::InvokeMetaMethod) {
        if (_id < ownMethodCount)
            qt_static_metacall(this, _c, _id, _a);
        _id -= ownMethodCount;
    } else if (_c == QMetaObject::ReadProperty) {
        void *_v = _a[0];
        switch (_id) {
        case 0: *reinterpret_cast< int*>(_v) = intProp(); break;
        default: if (_id < ownPropertyCount) {
            QMetaProperty prop = m_metaObject->property(m_metaObject->propertyOffset() + _id);
            qFatal("You forgot to add a case for ReadProperty %s", prop.name());
          }
        }
        _id -= ownPropertyCount;
    } else if (_c == QMetaObject::WriteProperty) {
        void *_v = _a[0];
        switch (_id) {
        case 0: setIntProp(*reinterpret_cast< int*>(_v)); break;
        default: if (_id < ownPropertyCount) {
            QMetaProperty prop = m_metaObject->property(m_metaObject->propertyOffset() + _id);
            qFatal("You forgot to add a case for WriteProperty %s", prop.name());
          }
        }
        _id -= ownPropertyCount;
    } else if (_c == QMetaObject::ResetProperty) {
        _id -= ownPropertyCount;
    }
    return _id;
}

// SIGNAL 0
void TestObject::intPropChanged(int _t1)
{
    void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
    QMetaObject::activate(this, m_metaObject, 0, _a);
}


void tst_QMetaObjectBuilder::usage_signal()
{
    QScopedPointer<TestObject> testObject(new TestObject);

    QSignalSpy propChangedSpy(testObject.data(), &TestObject::intPropChanged);
    testObject->emitIntPropChanged();
    QCOMPARE(propChangedSpy.size(), 1);
    QCOMPARE(propChangedSpy.at(0).size(), 1);
    QCOMPARE(propChangedSpy.at(0).at(0).toInt(), testObject->intProp());
}

void tst_QMetaObjectBuilder::usage_property()
{
    QScopedPointer<TestObject> testObject(new TestObject);

    QVariant prop = testObject->property("intProp");
    QCOMPARE(prop.metaType(), QMetaType(QMetaType::Int));
    QCOMPARE(prop.toInt(), testObject->intProp());

    QSignalSpy propChangedSpy(testObject.data(), &TestObject::intPropChanged);
    QVERIFY(testObject->intProp() != 123);
    testObject->setProperty("intProp", 123);
    QCOMPARE(propChangedSpy.size(), 1);
    prop = testObject->property("intProp");
    QCOMPARE(prop.metaType(), QMetaType(QMetaType::Int));
    QCOMPARE(prop.toInt(), 123);
}

void tst_QMetaObjectBuilder::usage_slot()
{
    QScopedPointer<TestObject> testObject(new TestObject);

    int index = testObject->metaObject()->indexOfMethod("voidSlotInt(int)");
    QVERIFY(index != -1);
    QMetaMethod voidSlotInt = testObject->metaObject()->method(index);

    QCOMPARE(testObject->voidSlotIntArgument(), -1);
    QVERIFY(voidSlotInt.invoke(testObject.data(), Q_ARG(int, 123)));
    QCOMPARE(testObject->voidSlotIntArgument(), 123);
}

void tst_QMetaObjectBuilder::usage_method()
{
    QScopedPointer<TestObject> testObject(new TestObject);

    int index = testObject->metaObject()->indexOfMethod("listInvokableQRealQString(qreal,QString)");
    QVERIFY(index != -1);
    QMetaMethod listInvokableQRealQString = testObject->metaObject()->method(index);
    QVariantList list;
    QVERIFY(listInvokableQRealQString.invoke(testObject.data(), Q_RETURN_ARG(QVariantList, list),
                                             Q_ARG(qreal, 123.0), Q_ARG(QString, "ciao")));
    QCOMPARE(list.size(), 2);
    QCOMPARE(list.at(0).metaType(), QMetaType(QMetaType::QReal));
    QCOMPARE(list.at(0).toDouble(), double(123));
    QCOMPARE(list.at(1).metaType(), QMetaType(QMetaType::QString));
    QCOMPARE(list.at(1).toString(), QString::fromLatin1("ciao"));
}

void tst_QMetaObjectBuilder::usage_constructor()
{
    QScopedPointer<TestObject> testObject(new TestObject);

    QCOMPARE(testObject->metaObject()->constructorCount(), 2);
    QScopedPointer<QObject> testInstance(testObject->metaObject()->newInstance());
    QVERIFY(testInstance != 0);
    QScopedPointer<QObject> testInstance2(testObject->metaObject()->newInstance(Q_ARG(QObject*, testInstance.data())));
    QVERIFY(testInstance2 != 0);
    QCOMPARE(testInstance2->parent(), testInstance.data());
}

void tst_QMetaObjectBuilder::usage_connect()
{
    QScopedPointer<TestObject> testObject(new TestObject);

    QVERIFY(QObject::connect(testObject.data(), SIGNAL(intPropChanged(int)),
                             testObject.data(), SLOT(voidSlotInt(int))));

    QCOMPARE(testObject->voidSlotIntArgument(), -1);
    testObject->setProperty("intProp", 123);
    QCOMPARE(testObject->voidSlotIntArgument(), 123);

    QVERIFY(QObject::disconnect(testObject.data(), SIGNAL(intPropChanged(int)),
                                testObject.data(), SLOT(voidSlotInt(int))));
}

void tst_QMetaObjectBuilder::usage_templateConnect()
{
    QScopedPointer<TestObject> testObject(new TestObject);

    QMetaObject::Connection con = QObject::connect(testObject.data(), &TestObject::intPropChanged,
                                                   testObject.data(), &TestObject::voidSlotInt);
    QVERIFY(con);

    QCOMPARE(testObject->voidSlotIntArgument(), -1);
    testObject->setProperty("intProp", 123);
    QCOMPARE(testObject->voidSlotIntArgument(), 123);

    QVERIFY(QObject::disconnect(testObject.data(), &TestObject::intPropChanged,
                                testObject.data(), &TestObject::voidSlotInt));

    // Something that isn't a signal
    QTest::ignoreMessage(QtWarningMsg, "QObject::connect: signal not found in TestObject");
    con = QObject::connect(testObject.data(), &TestObject::setIntProp,
                           testObject.data(), &TestObject::intPropChanged);
    QVERIFY(!con);
}

void tst_QMetaObjectBuilder::classNameFirstInStringData()
{
    QMetaObjectBuilder builder;
    builder.addMetaObject(&SomethingOfEverything::staticMetaObject);
    builder.setClassName(QByteArrayLiteral("TestClass"));
    QMetaObject *mo = builder.toMetaObject();

    uint offset = mo->d.stringdata[0];
    uint len = mo->d.stringdata[1];
    QByteArray className(reinterpret_cast<const char *>(mo->d.stringdata) + offset, len);
    QCOMPARE(className, QByteArrayLiteral("TestClass"));

    free(mo);
}

struct MyFoo {};
struct myParameter {};

void tst_QMetaObjectBuilder::propertyMetaType()
{
    qRegisterMetaType<myParameter>();
    QMetaType meta = QMetaType::fromType<MyFoo>();
    auto metaId = meta.id();
    QMetaObjectBuilder builder;
    builder.setClassName("Test");
    builder.addProperty("myParameter", "MyFoo");
    auto mo = builder.toMetaObject();

    QMetaProperty metaProp = mo->property(mo->indexOfProperty("myParameter"));
    QCOMPARE(metaProp.typeName(), meta.name());
    QCOMPARE(metaProp.typeId(), metaId);
    QCOMPARE(metaProp.metaType(), meta);
    free(mo);
}

void tst_QMetaObjectBuilder::ownMetaTypeNoProperties()
{
    QMetaObjectBuilder builder;
    builder.setClassName("NoProperties");
    auto mo = builder.toMetaObject();
    auto cleanup = qScopeGuard([&](){ free(mo); });
    // own metatype should be invalid, as the dynamic metaobject has not been registered
    QVERIFY(!mo->metaType().isValid());// should not crash
}

void tst_QMetaObjectBuilder::cleanupTestCase()
{
    for (QMetaObject *obj: dynamicMetaObjectsPendingFree)
        free(obj);
}

QTEST_MAIN(tst_QMetaObjectBuilder)

#include "tst_qmetaobjectbuilder.moc"
