QVariant ObjectMethodModel::metaData(const QModelIndex &index,
                                 const QMetaMethod &method, int role) const
{
  if (role == Qt::DisplayRole) {
    if (index.column() == 0) {
      return Util::prettyMethodSignature(method);
    }
    if (index.column() == 1) {
      switch (method.methodType()) {
      case QMetaMethod::Method:
        return tr("Method");
      case QMetaMethod::Constructor:
        return tr("Constructor");
      case QMetaMethod::Slot:
        return tr("Slot");
      case QMetaMethod::Signal:
        return tr("Signal");
      default:
        return tr("Unknown");
      }
    }
    if (index.column() == 2) {
      switch (method.access()) {
      case QMetaMethod::Public:
        return tr("Public");
      case QMetaMethod::Protected:
        return tr("Protected");
      case QMetaMethod::Private:
        return tr("Private");
      default:
        return tr("Unknown");
      }
    }
  } else if (role == Qt::ToolTipRole) {
    QString tt = Util::prettyMethodSignature(method);
    tt += tr("\nTag: %1\n").arg(qstrlen(method.tag()) > 0 ? method.tag() : tr("<none>"));
#if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0)
    tt += tr("Revision: %1").arg(method.revision());
#endif
    return tt;
  } else if (role == ObjectMethodModelRole::MetaMethod) {
    return QVariant::fromValue(method);
  } else if (role == ObjectMethodModelRole::MetaMethodType) {
    return QVariant::fromValue(method.methodType());
  } else if (role == ObjectMethodModelRole::MethodSignature) {
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
    return method.signature();
#else
    return method.methodSignature();
#endif
  }
  return QVariant();
}
void listInterface(const QString &service, const QString &path, const QString &interface)
{
    QDBusInterfacePtr iface(*connection, service, path, interface);
    if (!iface->isValid()) {
        QDBusError err(iface->lastError());
        fprintf(stderr, "Interface '%s' not available in object %s at %s:\n%s (%s)\n",
                qPrintable(interface), qPrintable(path), qPrintable(service),
                qPrintable(err.name()), qPrintable(err.message()));
        exit(1);
    }
    const QMetaObject *mo = iface->metaObject();

    // properties
    for (int i = mo->propertyOffset(); i < mo->propertyCount(); ++i) {
        QMetaProperty mp = mo->property(i);
        printf("property ");

        if (mp.isReadable() && mp.isWritable())
            printf("readwrite");
        else if (mp.isReadable())
            printf("read");
        else
            printf("write");

        printf(" %s %s.%s\n", mp.typeName(), qPrintable(interface), mp.name());
    }

    // methods (signals and slots)
    for (int i = mo->methodOffset(); i < mo->methodCount(); ++i) {
        QMetaMethod mm = mo->method(i);

        QByteArray signature = mm.signature();
        signature.truncate(signature.indexOf('('));
        printf("%s %s%s%s %s.%s(",
               mm.methodType() == QMetaMethod::Signal ? "signal" : "method",
               mm.tag(), *mm.tag() ? " " : "",
               *mm.typeName() ? mm.typeName() : "void",
               qPrintable(interface), signature.constData());

        QList<QByteArray> types = mm.parameterTypes();
        QList<QByteArray> names = mm.parameterNames();
        bool first = true;
        for (int i = 0; i < types.count(); ++i) {
            printf("%s%s",
                   first ? "" : ", ",
                   types.at(i).constData());
            if (!names.at(i).isEmpty())
                printf(" %s", names.at(i).constData());
            first = false;
        }
        printf(")\n");
    }
}
Пример #3
0
QVariant ObjectMethodModel::metaData(const QModelIndex &index, const QMetaMethod &method,
                                     int role) const
{
    if (role == Qt::DisplayRole && index.column() == 0) {
        return Util::prettyMethodSignature(method);
    } else if (role == ObjectMethodModelRole::MetaMethod) {
        return QVariant::fromValue(method);
    } else if (role == ObjectMethodModelRole::MetaMethodType && index.column() == 1) {
        return QVariant::fromValue(method.methodType());
    } else if (role == ObjectMethodModelRole::MethodAccess && index.column() == 2) {
        return QVariant::fromValue(method.access());
    } else if (role == ObjectMethodModelRole::MethodSignature && index.column() == 0) {
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
        return method.signature();
#else
        return method.methodSignature();
#endif
    } else if (role == ObjectMethodModelRole::MethodTag && index.column() == 0 && qstrlen(method.tag())) {
        return method.tag();
#if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0)
    } else if (role == ObjectMethodModelRole::MethodRevision && index.column() == 0) {
        return method.revision();
#endif
    } else if (role == ObjectMethodModelRole::MethodIssues && index.column() == 0) {
        const QMetaObject *mo = m_metaObject;
        while (mo->methodOffset() > index.row())
            mo = mo->superClass();
        const auto r = QMetaObjectValidator::checkMethod(mo, method);
        return r == QMetaObjectValidatorResult::NoIssue ? QVariant() : QVariant::fromValue(r);
    }
    return QVariant();
}
Пример #4
0
bool action_method::validate_action_method(const QMetaMethod &meta_method)
{
	auto meta_object = meta_method.enclosingMetaObject();
	if (!meta_object)
		throw exception::invalid_action{std::string{"action does not have enclosing meta object: "} + "?::" + meta_method.methodSignature().data()};
	if (meta_method.methodType() == QMetaMethod::Signal)
		throw exception::invalid_action{std::string{"action is signal: "} + meta_object->className() + "::" + meta_method.methodSignature().data()};
	if (meta_method.methodType() == QMetaMethod::Constructor)
		throw exception::invalid_action{std::string{"action is constructor: "} + meta_object->className() + "::" + meta_method.methodSignature().data()};
	if (!is_action_init_tag(meta_method.tag()) && !is_action_done_tag(meta_method.tag()))
		throw exception::invalid_action{std::string{"action does not have valid tag: "} + meta_object->className() + "::" + meta_method.methodSignature().data()};
	if (meta_method.parameterCount() != 0)
		throw exception::invalid_action{std::string{"invalid parameter count: "} + meta_object->className() + "::" + meta_method.methodSignature().data()};

	return true;
}
Пример #5
0
QVariant ObjectMethodModel::metaData(const QModelIndex &index,
                                 const QMetaMethod &method, int role) const
{
  if (role == Qt::DisplayRole) {
    if (index.column() == 0) {
      return Util::prettyMethodSignature(method);
    }
    if (index.column() == 1) {
      switch (method.methodType()) {
      case QMetaMethod::Method:
        return tr("Method");
      case QMetaMethod::Constructor:
        return tr("Constructor");
      case QMetaMethod::Slot:
        return tr("Slot");
      case QMetaMethod::Signal:
        return tr("Signal");
      default:
        return tr("Unknown");
      }
    }
    if (index.column() == 2) {
      switch (method.access()) {
      case QMetaMethod::Public:
        return tr("Public");
      case QMetaMethod::Protected:
        return tr("Protected");
      case QMetaMethod::Private:
        return tr("Private");
      default:
        return tr("Unknown");
      }
    }
    if (index.column() == 3) {
      return method.tag();
    }
#if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0)
    if (index.column() == 4) {
      return QString::number(method.revision());
    }
#endif
  } else if (role == ObjectMethodModelRole::MetaMethod) {
    return QVariant::fromValue(method);
  } else if (role == ObjectMethodModelRole::MetaMethodType) {
    return QVariant::fromValue(method.methodType());
  } else if (role == ObjectMethodModelRole::MethodSignature) {
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
    return method.signature();
#else
    return method.methodSignature();
#endif
  }
  return QVariant();
}
Пример #6
0
/*!
    Places a call to the remote method specified by \a method on this interface, using \a args as
    arguments. This function returns the message that was received as a reply, which can be a normal
    QDBusMessage::ReplyMessage (indicating success) or QDBusMessage::ErrorMessage (if the call
    failed). The \a mode parameter specifies how this call should be placed.

    If the call succeeds, lastError() will be cleared; otherwise, it will contain the error this
    call produced.

    Normally, you should place calls using call().

    \warning If you use \c UseEventLoop, your code must be prepared to deal with any reentrancy:
             other method calls and signals may be delivered before this function returns, as well
             as other Qt queued signals and events.

    \threadsafe
*/
QDBusMessage QDBusAbstractInterface::callWithArgumentList(QDBus::CallMode mode,
        const QString& method,
        const QList<QVariant>& args)
{
    Q_D(QDBusAbstractInterface);

    if (!d->isValid || !d->canMakeCalls())
        return QDBusMessage::createError(d->lastError);

    QString m = method;
    // split out the signature from the method
    int pos = method.indexOf(QLatin1Char('.'));
    if (pos != -1)
        m.truncate(pos);

    if (mode == QDBus::AutoDetect) {
        // determine if this a sync or async call
        mode = QDBus::Block;
        const QMetaObject *mo = metaObject();
        QByteArray match = m.toLatin1();

        for (int i = staticMetaObject.methodCount(); i < mo->methodCount(); ++i) {
            QMetaMethod mm = mo->method(i);
            if (mm.name() == match) {
                // found a method with the same name as what we're looking for
                // hopefully, nobody is overloading asynchronous and synchronous methods with
                // the same name

                QList<QByteArray> tags = QByteArray(mm.tag()).split(' ');
                if (tags.contains("Q_NOREPLY"))
                    mode = QDBus::NoBlock;

                break;
            }
        }
    }

//    qDebug() << "QDBusAbstractInterface" << "Service" << service() << "Path:" << path();
    QDBusMessage msg = QDBusMessage::createMethodCall(service(), path(), interface(), m);
    QDBusMessagePrivate::setParametersValidated(msg, true);
    msg.setArguments(args);

    QDBusMessage reply = d->connection.call(msg, mode, d->timeout);
    if (thread() == QThread::currentThread())
        d->lastError = QDBusError(reply);       // will clear if reply isn't an error

    // ensure that there is at least one element
    if (reply.arguments().isEmpty())
        reply << QVariant();

    return reply;
}
void tst_QQmlMetaObject::method()
{
    QFETCH(QString, testFile);
    QFETCH(QString, signature);
    QFETCH(QMetaMethod::MethodType, methodType);
    QFETCH(int, returnType);
    QFETCH(QString, returnTypeName);
    QFETCH(QList<int>, parameterTypes);
    QFETCH(QList<QByteArray>, parameterTypeNames);
    QFETCH(QList<QByteArray>, parameterNames);

    QCOMPARE(parameterTypes.size(), parameterTypeNames.size());
    QCOMPARE(parameterTypeNames.size(), parameterNames.size());

    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl(testFile));
    QObject *object = component.create();
    QVERIFY(object != 0);

    const QMetaObject *mo = object->metaObject();
    QVERIFY(mo->superClass() != 0);
    QVERIFY(QByteArray(mo->className()).contains("_QML_"));
    QCOMPARE(mo->methodOffset(), mo->superClass()->methodCount());
    QCOMPARE(mo->methodCount(), mo->superClass()->methodCount() + 1);

    QMetaMethod method = mo->method(mo->methodOffset());
    QCOMPARE(method.methodType(), methodType);
    QCOMPARE(QString::fromUtf8(method.methodSignature().constData()), signature);
    QCOMPARE(method.access(), QMetaMethod::Public);

    QString computedName = signature.left(signature.indexOf('('));
    QCOMPARE(QString::fromUtf8(method.name()), computedName);

    QCOMPARE(method.parameterCount(), parameterTypes.size());
    for (int i = 0; i < parameterTypes.size(); ++i)
        QCOMPARE(method.parameterType(i), parameterTypes.at(i));
    QCOMPARE(method.parameterTypes(), parameterTypeNames);
    QCOMPARE(method.tag(), "");

    QCOMPARE(QString::fromUtf8(method.typeName()), returnTypeName);
    QCOMPARE(method.returnType(), returnType);

    delete object;
}
void tst_QMetaMethod::method()
{
    QFETCH(QByteArray, signature);
    QFETCH(int, returnType);
    QFETCH(QByteArray, returnTypeName);
    QFETCH(QList<int>, parameterTypes);
    QFETCH(QList<QByteArray>, parameterTypeNames);
    QFETCH(QList<QByteArray>, parameterNames);
    QFETCH(QMetaMethod::MethodType, methodType);
    QFETCH(QMetaMethod::Access, access);

    QVERIFY(parameterTypes.size() == parameterTypeNames.size());
    QVERIFY(parameterTypes.size() == parameterNames.size());

    const QMetaObject *mo = &MethodTestObject::staticMetaObject;
    int index = (methodType == QMetaMethod::Constructor)
                ? mo->indexOfConstructor(signature) : mo->indexOfMethod(signature);
    QVERIFY(index != -1);
    QMetaMethod method = (methodType == QMetaMethod::Constructor)
                         ? mo->constructor(index) : mo->method(index);
    QVERIFY(method.isValid());
    QCOMPARE(method.methodType(), methodType);
    QCOMPARE(method.access(), access);

    QVERIFY(!method.methodSignature().isEmpty());
    if (method.methodSignature() != signature) {
        // QMetaMethod should always produce a semantically equivalent signature
        int signatureIndex = (methodType == QMetaMethod::Constructor)
                ? mo->indexOfConstructor(method.methodSignature())
                : mo->indexOfMethod(method.methodSignature());
        QCOMPARE(signatureIndex, index);
    }

    QByteArray computedName = signature.left(signature.indexOf('('));
    QCOMPARE(method.name(), computedName);

    QCOMPARE(method.tag(), "");
    QCOMPARE(method.returnType(), returnType);
    QVERIFY(method.typeName() != 0);
    if (QByteArray(method.typeName()) != returnTypeName) {
        // QMetaMethod should always produce a semantically equivalent typename
        QCOMPARE(QMetaType::type(method.typeName()), QMetaType::type(returnTypeName));
    }

    if (method.parameterTypes() != parameterTypeNames) {
        // QMetaMethod should always produce semantically equivalent typenames
        QList<QByteArray> actualTypeNames = method.parameterTypes();
        QCOMPARE(actualTypeNames.size(), parameterTypeNames.size());
        for (int i = 0; i < parameterTypeNames.size(); ++i) {
            QCOMPARE(QMetaType::type(actualTypeNames.at(i)),
                     QMetaType::type(parameterTypeNames.at(i)));
        }
    }
    QCOMPARE(method.parameterNames(), parameterNames);

    QCOMPARE(method.parameterCount(), parameterTypes.size());
    for (int i = 0; i < parameterTypes.size(); ++i)
        QCOMPARE(method.parameterType(i), parameterTypes.at(i));

    {
        QVector<int> actualParameterTypes(parameterTypes.size());
        method.getParameterTypes(actualParameterTypes.data());
        for (int i = 0; i < parameterTypes.size(); ++i)
            QCOMPARE(actualParameterTypes.at(i), parameterTypes.at(i));
    }

    // Bogus indexes
    QCOMPARE(method.parameterType(-1), 0);
    QCOMPARE(method.parameterType(parameterTypes.size()), 0);
}
Пример #9
0
static QString generateInterfaceXml(const QMetaObject *mo, int flags, int methodOffset, int propOffset)
{
    QString retval;

    // start with properties:
    if (flags & (QDBusConnection::ExportScriptableProperties |
                 QDBusConnection::ExportNonScriptableProperties)) {
        for (int i = propOffset; i < mo->propertyCount(); ++i) {
            static const char *accessvalues[] = {0, "read", "write", "readwrite"};

            QMetaProperty mp = mo->property(i);

            if (!((mp.isScriptable() && (flags & QDBusConnection::ExportScriptableProperties)) ||
                  (!mp.isScriptable() && (flags & QDBusConnection::ExportNonScriptableProperties))))
                continue;

            int access = 0;
            if (mp.isReadable())
                access |= 1;
            if (mp.isWritable())
                access |= 2;

            int typeId = qDBusNameToTypeId(mp.typeName());
            if (!typeId)
                continue;
            const char *signature = QDBusMetaType::typeToSignature(typeId);
            if (!signature)
                continue;

            retval += QString::fromLatin1("    <property name=\"%1\" type=\"%2\" access=\"%3\"")
                      .arg(QLatin1String(mp.name()))
                      .arg(QLatin1String(signature))
                      .arg(QLatin1String(accessvalues[access]));

            if (QDBusMetaType::signatureToType(signature) == QVariant::Invalid) {
                const char *typeName = QVariant::typeToName(QVariant::Type(typeId));
                retval += QString::fromLatin1(">\n      <annotation name=\"com.trolltech.QtDBus.QtTypeName\" value=\"%3\"/>\n    </property>\n")
                          .arg(typeNameToXml(typeName));
            } else {
                retval += QLatin1String("/>\n");
            }
        }
    }

    // now add methods:
    for (int i = methodOffset; i < mo->methodCount(); ++i) {
        QMetaMethod mm = mo->method(i);
        QByteArray signature = mm.signature();
        int paren = signature.indexOf('(');

        bool isSignal;
        if (mm.methodType() == QMetaMethod::Signal)
            // adding a signal
            isSignal = true;
        else if (mm.methodType() == QMetaMethod::Slot && mm.access() == QMetaMethod::Public)
            isSignal = false;
        else
            continue;           // neither signal nor public slot

        if (isSignal && !(flags & (QDBusConnection::ExportScriptableSignals |
                                   QDBusConnection::ExportNonScriptableSignals)))
            continue;           // we're not exporting any signals
        if (!isSignal && !(flags & (QDBusConnection::ExportScriptableSlots |
                                    QDBusConnection::ExportNonScriptableSlots)))
            continue;           // we're not exporting any slots

        QString xml = QString::fromLatin1("    <%1 name=\"%2\">\n")
                      .arg(isSignal ? QLatin1String("signal") : QLatin1String("method"))
                      .arg(QLatin1String(signature.left(paren)));

        // check the return type first
        int typeId = qDBusNameToTypeId(mm.typeName());
        if (typeId) {
            const char *typeName = QDBusMetaType::typeToSignature(typeId);
            if (typeName) {
                xml += QString::fromLatin1("      <arg type=\"%1\" direction=\"out\"/>\n")
                       .arg(typeNameToXml(typeName));

                // do we need to describe this argument?
                if (QDBusMetaType::signatureToType(typeName) == QVariant::Invalid)
                    xml += QString::fromLatin1("      <annotation name=\"com.trolltech.QtDBus.QtTypeName.Out0\" value=\"%1\"/>\n")
                           .arg(typeNameToXml(mm.typeName()));
            } else
                continue;
        }
        else if (*mm.typeName())
            continue;           // wasn't a valid type

        QList<QByteArray> names = mm.parameterNames();
        QList<int> types;
        int inputCount = qDBusParametersForMethod(mm, types);
        if (inputCount == -1)
            continue;           // invalid form
        if (isSignal && inputCount + 1 != types.count())
            continue;           // signal with output arguments?
        if (isSignal && types.at(inputCount) == QDBusMetaTypeId::message)
            continue;           // signal with QDBusMessage argument?
        if (isSignal && mm.attributes() & QMetaMethod::Cloned)
            continue;           // cloned signal?

        int j;
        bool isScriptable = mm.attributes() & QMetaMethod::Scriptable;
        for (j = 1; j < types.count(); ++j) {
            // input parameter for a slot or output for a signal
            if (types.at(j) == QDBusMetaTypeId::message) {
                isScriptable = true;
                continue;
            }

            QString name;
            if (!names.at(j - 1).isEmpty())
                name = QString::fromLatin1("name=\"%1\" ").arg(QLatin1String(names.at(j - 1)));

            bool isOutput = isSignal || j > inputCount;

            const char *signature = QDBusMetaType::typeToSignature(types.at(j));
            xml += QString::fromLatin1("      <arg %1type=\"%2\" direction=\"%3\"/>\n")
                   .arg(name)
                   .arg(QLatin1String(signature))
                   .arg(isOutput ? QLatin1String("out") : QLatin1String("in"));

            // do we need to describe this argument?
            if (QDBusMetaType::signatureToType(signature) == QVariant::Invalid) {
                const char *typeName = QVariant::typeToName( QVariant::Type(types.at(j)) );
                xml += QString::fromLatin1("      <annotation name=\"com.trolltech.QtDBus.QtTypeName.%1%2\" value=\"%3\"/>\n")
                       .arg(isOutput ? QLatin1String("Out") : QLatin1String("In"))
                       .arg(isOutput ? j - 1 : j - inputCount)
                       .arg(typeNameToXml(typeName));
            }
        }

        int wantedMask;
        if (isScriptable)
            wantedMask = isSignal ? QDBusConnection::ExportScriptableSignals
                                  : QDBusConnection::ExportScriptableSlots;
        else
            wantedMask = isSignal ? QDBusConnection::ExportNonScriptableSignals
                                  : QDBusConnection::ExportNonScriptableSlots;
        if ((flags & wantedMask) != wantedMask)
            continue;

        if (qDBusCheckAsyncTag(mm.tag()))
            // add the no-reply annotation
            xml += QLatin1String("      <annotation name=\"" ANNOTATION_NO_WAIT "\""
                                 " value=\"true\"/>\n");

        retval += xml;
        retval += QString::fromLatin1("    </%1>\n")
                  .arg(isSignal ? QLatin1String("signal") : QLatin1String("method"));
    }

    return retval;
}
void tst_QQmlMetaObject::property()
{
    QFETCH(QString, testFile);
    QFETCH(QByteArray, cppTypeName);
    QFETCH(int, cppType);
    QFETCH(bool, isDefault);
    QFETCH(QVariant, expectedValue);
    QFETCH(bool, isWritable);
    QFETCH(QVariant, newValue);

    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl(testFile));
    QObject *object = component.create();
    QVERIFY(object != 0);

    const QMetaObject *mo = object->metaObject();
    QVERIFY(mo->superClass() != 0);
    QVERIFY(QByteArray(mo->className()).contains("_QML_"));
    QCOMPARE(mo->propertyOffset(), mo->superClass()->propertyCount());
    QCOMPARE(mo->propertyCount(), mo->superClass()->propertyCount() + 1);

    QMetaProperty prop = mo->property(mo->propertyOffset());
    QCOMPARE(prop.name(), "test");

    QCOMPARE(QByteArray(prop.typeName()), cppTypeName);
    if (prop.userType() < QMetaType::User)
        QCOMPARE(prop.type(), QVariant::Type(cppType));
    else
        QCOMPARE(prop.type(), QVariant::UserType);
    QCOMPARE(prop.userType(), cppType);

    QVERIFY(!prop.isConstant());
    QVERIFY(!prop.isDesignable());
    QVERIFY(!prop.isEnumType());
    QVERIFY(!prop.isFinal());
    QVERIFY(!prop.isFlagType());
    QVERIFY(prop.isReadable());
    QVERIFY(!prop.isResettable());
    QVERIFY(prop.isScriptable());
    QVERIFY(!prop.isStored());
    QVERIFY(!prop.isUser());
    QVERIFY(prop.isValid());
    QCOMPARE(prop.isWritable(), isWritable);

    QCOMPARE(mo->classInfoOffset(), mo->superClass()->classInfoCount());
    QCOMPARE(mo->classInfoCount(), mo->superClass()->classInfoCount() + (isDefault ? 1 : 0));
    if (isDefault) {
        QMetaClassInfo info = mo->classInfo(mo->classInfoOffset());
        QCOMPARE(info.name(), "DefaultProperty");
        QCOMPARE(info.value(), "test");
    }

    QCOMPARE(mo->methodOffset(), mo->superClass()->methodCount());
    QCOMPARE(mo->methodCount(), mo->superClass()->methodCount() + 1); // the signal

    QVERIFY(prop.notifySignalIndex() != -1);
    QMetaMethod signal = prop.notifySignal();
    QCOMPARE(signal.methodType(), QMetaMethod::Signal);
    QCOMPARE(signal.name(), QByteArray("testChanged"));
    QCOMPARE(signal.methodSignature(), QByteArray("testChanged()"));
    QCOMPARE(signal.access(), QMetaMethod::Public);
    QCOMPARE(signal.parameterCount(), 0);
    QCOMPARE(signal.parameterTypes(), QList<QByteArray>());
    QCOMPARE(signal.parameterNames(), QList<QByteArray>());
    QCOMPARE(signal.tag(), "");
    QCOMPARE(signal.typeName(), "void");
    QCOMPARE(signal.returnType(), int(QMetaType::Void));

    QSignalSpy changedSpy(object, SIGNAL(testChanged()));
    QObject::connect(object, SIGNAL(testChanged()), object, SLOT(deleteLater()));

    if (expectedValue.isValid())
        QCOMPARE(prop.read(object), expectedValue);
    else
        QVERIFY(prop.read(object).isValid());
    QCOMPARE(changedSpy.count(), 0);

    if (isWritable) {
        QVERIFY(prop.write(object, newValue));
        QCOMPARE(changedSpy.count(), 1);
        QCOMPARE(prop.read(object), newValue);
    } else {
        QVERIFY(!prop.write(object, prop.read(object)));
        QCOMPARE(changedSpy.count(), 0);
    }

    delete object;
}