/* -*- C++ -*- * * Copyright (C) 2006 Trolltech AS. All rights reserved. * Author: Thiago Macieira * * Licensed under the Academic Free License version 2.1 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include #include #include #include class tst_QDBusXmlParser: public QObject { Q_OBJECT private: void parsing_common(const QString&); private slots: void parsing_data(); void parsing(); void parsingWithDoctype_data(); void parsingWithDoctype(); void objectWithContent_data(); void objectWithContent(); void methods_data(); void methods(); void signals__data(); void signals_(); void properties_data(); void properties(); }; // just to make it easier: typedef QDBusIntrospection::Interfaces InterfaceMap; typedef QDBusIntrospection::Objects ObjectMap; typedef QDBusIntrospection::Arguments ArgumentList; typedef QDBusIntrospection::Annotations AnnotationsMap; typedef QDBusIntrospection::Methods MethodMap; typedef QDBusIntrospection::Signals SignalMap; typedef QDBusIntrospection::Properties PropertyMap; Q_DECLARE_METATYPE(QDBusIntrospection::Method) Q_DECLARE_METATYPE(QDBusIntrospection::Signal) Q_DECLARE_METATYPE(QDBusIntrospection::Property) Q_DECLARE_METATYPE(MethodMap) Q_DECLARE_METATYPE(SignalMap) Q_DECLARE_METATYPE(PropertyMap) inline QDBusIntrospection::Argument arg(const char* type, const char *name = 0) { QDBusIntrospection::Argument retval; retval.type = QDBusType(type); retval.name = QLatin1String(name); return retval; } template inline QMap& operator<<(QMap& map, const T& m) { map.insert(m.name, m); return map; } inline const char* mapName(const MethodMap&) { return "MethodMap"; } inline const char* mapName(const SignalMap&) { return "SignalMap"; } inline const char* mapName(const PropertyMap&) { return "PropertyMap"; } QString printable(const QDBusIntrospection::Method& m) { QString result = m.name + "("; foreach (QDBusIntrospection::Argument arg, m.inputArgs) result += QString("in %1 %2, ") .arg(arg.type.toString(QDBusType::ConventionalNames)) .arg(arg.name); foreach (QDBusIntrospection::Argument arg, m.outputArgs) result += QString("out %1 %2, ") .arg(arg.type.toString(QDBusType::ConventionalNames)) .arg(arg.name); AnnotationsMap::const_iterator it = m.annotations.begin(); for ( ; it != m.annotations.end(); ++it) result += QString("%1 \"%2\", ").arg(it.key()).arg(it.value()); if (result.length() > 1) result.truncate(result.length() - 2); result += ")"; return result; } QString printable(const QDBusIntrospection::Signal& s) { QString result = s.name + "("; foreach (QDBusIntrospection::Argument arg, s.outputArgs) result += QString("out %1 %2, ") .arg(arg.type.toString(QDBusType::ConventionalNames)) .arg(arg.name); AnnotationsMap::const_iterator it = s.annotations.begin(); for ( ; it != s.annotations.end(); ++it) result += QString("%1 \"%2\", ").arg(it.key()).arg(it.value()); if (result.length() > 1) result.truncate(result.length() - 2); result += ")"; return result; } QString printable(const QDBusIntrospection::Property& p) { QString result; if (p.access == QDBusIntrospection::Property::Read) result = "read %1 %2, "; else if (p.access == QDBusIntrospection::Property::Write) result = "write %1 %2, "; else result = "readwrite %1 %2, "; result = result.arg(p.type.toString(QDBusType::ConventionalNames)).arg(p.name); AnnotationsMap::const_iterator it = p.annotations.begin(); for ( ; it != p.annotations.end(); ++it) result += QString("%1 \"%2\", ").arg(it.key()).arg(it.value()); if (result.length() > 1) result.truncate(result.length() - 2); return result; } template char* printableMap(const QMap& map) { QString contents = "\n"; typename QMap::const_iterator it = map.begin(); for ( ; it != map.end(); ++it) { if (it.key() != it.value().name) contents += it.value().name + ":"; contents += printable(it.value()); contents += ";\n"; } QString result("%1(size = %2): {%3}"); return qstrdup(qPrintable(result .arg(mapName(map)) .arg(map.size()) .arg(contents))); } namespace QTest { template<> inline char* toString(const MethodMap& map) { return printableMap(map); } template<> inline char* toString(const SignalMap& map) { return printableMap(map); } template<> inline char* toString(const PropertyMap& map) { return printableMap(map); } } void tst_QDBusXmlParser::parsing_data() { QTest::addColumn("xmlData"); QTest::addColumn("interfaceCount"); QTest::addColumn("objectCount"); QTest::newRow("null") << QString() << 0 << 0; QTest::newRow("empty") << QString("") << 0 << 0; QTest::newRow("junk") << "" << 0 << 0; QTest::newRow("interface-inside-junk") << "" << 0 << 0; QTest::newRow("object-inside-junk") << "" << 0 << 0; QTest::newRow("zero-interfaces") << "" << 0 << 0; QTest::newRow("one-interface") << "" << 1 << 0; QTest::newRow("two-interfaces") << "" "" << 2 << 0; QTest::newRow("one-object") << "" << 0 << 1; QTest::newRow("two-objects") << "" << 0 << 2; QTest::newRow("i1o1") << "" << 1 << 1; } void tst_QDBusXmlParser::parsing_common(const QString &xmlData) { QDBusIntrospection::ObjectTree obj = QDBusIntrospection::parseObjectTree(xmlData, "local.testing", "/"); QFETCH(int, interfaceCount); QFETCH(int, objectCount); QCOMPARE(obj.interfaces.count(), interfaceCount); QCOMPARE(obj.childObjects.count(), objectCount); // also verify the naming int i = 0; foreach (QString name, obj.interfaces) QCOMPARE(name, QString("iface.iface%1").arg(++i)); i = 0; foreach (QString name, obj.childObjects) QCOMPARE(name, QString("obj%1").arg(++i)); } void tst_QDBusXmlParser::parsing() { QFETCH(QString, xmlData); parsing_common(xmlData); } void tst_QDBusXmlParser::parsingWithDoctype_data() { parsing_data(); } void tst_QDBusXmlParser::parsingWithDoctype() { QString docType = "\n"; QFETCH(QString, xmlData); parsing_common(docType + xmlData); } void tst_QDBusXmlParser::objectWithContent_data() { QTest::addColumn("xmlData"); QTest::addColumn("probedObject"); QTest::addColumn("interfaceCount"); QTest::addColumn("objectCount"); QTest::newRow("zero") << "" << "obj" << 0 << 0; QString xmlData = "" "" ""; QTest::newRow("one-interface") << xmlData << "obj" << 1 << 0; QTest::newRow("one-interface2") << xmlData << "obj2" << 0 << 0; xmlData = "" "" "" ""; QTest::newRow("two-interfaces") << xmlData << "obj" << 2 << 0; QTest::newRow("two-interfaces2") << xmlData << "obj2" << 0 << 0; xmlData = "" "" "" "" "" ""; QTest::newRow("two-nodes-two-interfaces") << xmlData << "obj" << 2 << 0; QTest::newRow("two-nodes-one-interface") << xmlData << "obj2" << 1 << 0; xmlData = "" "" ""; QTest::newRow("one-object") << xmlData << "obj" << 0 << 1; QTest::newRow("one-object2") << xmlData << "obj2" << 0 << 0; xmlData = "" "" "" ""; QTest::newRow("two-objects") << xmlData << "obj" << 0 << 2; QTest::newRow("two-objects2") << xmlData << "obj2" << 0 << 0; xmlData = "" "" "" "" "" ""; QTest::newRow("two-nodes-two-objects") << xmlData << "obj" << 0 << 2; QTest::newRow("two-nodes-one-object") << xmlData << "obj2" << 0 << 1; } void tst_QDBusXmlParser::objectWithContent() { QFETCH(QString, xmlData); QFETCH(QString, probedObject); QDBusIntrospection::ObjectTree tree = QDBusIntrospection::parseObjectTree(xmlData, "local.testing", "/"); const ObjectMap &om = tree.childObjectData; if (om.contains(probedObject)) { const QSharedDataPointer& obj = om.value(probedObject); QVERIFY(obj != 0); QFETCH(int, interfaceCount); QFETCH(int, objectCount); QCOMPARE(obj->interfaces.count(), interfaceCount); QCOMPARE(obj->childObjects.count(), objectCount); // verify the object names int i = 0; foreach (QString name, obj->interfaces) QCOMPARE(name, QString("iface.iface%1").arg(++i)); i = 0; foreach (QString name, obj->childObjects) QCOMPARE(name, QString("obj%1").arg(++i)); } } void tst_QDBusXmlParser::methods_data() { QTest::addColumn("xmlDataFragment"); QTest::addColumn("methodMap"); MethodMap map; QTest::newRow("no-methods") << QString() << map; // one method without arguments QDBusIntrospection::Method method; method.name = "Foo"; map << method; QTest::newRow("one-method") << "" << map; // add another method without arguments method.name = "Bar"; map << method; QTest::newRow("two-methods") << "" "" << map; // invert the order of the XML declaration QTest::newRow("two-methods-inverse") << "" "" << map; // add a third, with annotations method.name = "Baz"; method.annotations.insert("foo.testing", "nothing to see here"); map << method; QTest::newRow("method-with-annotation") << "" "" "" << map; // arguments map.clear(); method.annotations.clear(); method.name = "Method"; method.inputArgs << arg("s"); map << method; QTest::newRow("one-in") << "" "" "" << map; // two arguments method.inputArgs << arg("v"); map.clear(); map << method; QTest::newRow("two-in") << "" "" "" "" << map; // one invalid arg QTest::newRow("two-in-one-invalid") << "" "" "" // this line should be ignored "" "" << map; // one out argument method.inputArgs.clear(); method.outputArgs << arg("s"); map.clear(); map << method; QTest::newRow("one-out") << "" "" "" << map; // two in and one out method.inputArgs << arg("s") << arg("v"); map.clear(); map << method; QTest::newRow("two-in-one-out") << "" "" "" "" "" << map; // let's try an arg with name method.outputArgs.clear(); method.inputArgs.clear(); method.inputArgs << arg("s", "foo"); map.clear(); map << method; QTest::newRow("one-in-with-name") << "" "" "" << map; // two args with name method.inputArgs << arg("i", "bar"); map.clear(); map << method; QTest::newRow("two-in-with-name") << "" "" "" "" << map; // one complex map.clear(); method = QDBusIntrospection::Method(); // Method1(in STRING arg1, in BYTE arg2, out ARRAY of STRING) method.inputArgs << arg("s", "arg1") << arg("y", "arg2"); method.outputArgs << arg("as"); method.name = "Method1"; map << method; // Method2(in ARRAY of DICT_ENTRY of (STRING,VARIANT) variantMap, in UINT32 index, // out STRING key, out VARIANT value) // with annotation "foo.equivalent":"QVariantMap" method = QDBusIntrospection::Method(); method.inputArgs << arg("a{sv}", "variantMap") << arg("u", "index"); method.outputArgs << arg("s", "key") << arg("v", "value"); method.annotations.insert("foo.equivalent", "QVariantMap"); method.name = "Method2"; map << method; QTest::newRow("complex") << "" "" "" "" "" "" "" "" "" "" "" "" << map; } void tst_QDBusXmlParser::methods() { QString xmlHeader = "" "", xmlFooter = "" ""; QFETCH(QString, xmlDataFragment); QDBusIntrospection::Interface iface = QDBusIntrospection::parseInterface(xmlHeader + xmlDataFragment + xmlFooter); QCOMPARE(iface.name, QString("iface.iface1")); QFETCH(MethodMap, methodMap); MethodMap parsedMap = iface.methods; QCOMPARE(methodMap.count(), parsedMap.count()); QCOMPARE(methodMap, parsedMap); } void tst_QDBusXmlParser::signals__data() { QTest::addColumn("xmlDataFragment"); QTest::addColumn("signalMap"); SignalMap map; QTest::newRow("no-signals") << QString() << map; // one signal without arguments QDBusIntrospection::Signal signal; signal.name = "Foo"; map << signal; QTest::newRow("one-signal") << "" << map; // add another signal without arguments signal.name = "Bar"; map << signal; QTest::newRow("two-signals") << "" "" << map; // invert the order of the XML declaration QTest::newRow("two-signals-inverse") << "" "" << map; // add a third, with annotations signal.name = "Baz"; signal.annotations.insert("foo.testing", "nothing to see here"); map << signal; QTest::newRow("signal-with-annotation") << "" "" "" << map; // one out argument map.clear(); signal.annotations.clear(); signal.outputArgs << arg("s"); signal.name = "Signal"; map.clear(); map << signal; QTest::newRow("one-out") << "" "" "" << map; // without saying which direction it is QTest::newRow("one-out-no-direction") << "" "" "" << map; // two args with name signal.outputArgs << arg("i", "bar"); map.clear(); map << signal; QTest::newRow("two-out-with-name") << "" "" "" "" << map; // one complex map.clear(); signal = QDBusIntrospection::Signal(); // Signal1(out ARRAY of STRING) signal.outputArgs << arg("as"); signal.name = "Signal1"; map << signal; // Signal2(out STRING key, out VARIANT value) // with annotation "foo.equivalent":"QVariantMap" signal = QDBusIntrospection::Signal(); signal.outputArgs << arg("s", "key") << arg("v", "value"); signal.annotations.insert("foo.equivalent", "QVariantMap"); signal.name = "Signal2"; map << signal; QTest::newRow("complex") << "" "" "" "" "" "" "" "" << map; } void tst_QDBusXmlParser::signals_() { QString xmlHeader = "" "", xmlFooter = "" ""; QFETCH(QString, xmlDataFragment); QDBusIntrospection::Interface iface = QDBusIntrospection::parseInterface(xmlHeader + xmlDataFragment + xmlFooter); QCOMPARE(iface.name, QString("iface.iface1")); QFETCH(SignalMap, signalMap); SignalMap parsedMap = iface.signals_; QCOMPARE(signalMap.count(), parsedMap.count()); QCOMPARE(signalMap, parsedMap); } void tst_QDBusXmlParser::properties_data() { QTest::addColumn("xmlDataFragment"); QTest::addColumn("propertyMap"); PropertyMap map; QTest::newRow("no-signals") << QString() << map; // one readable signal QDBusIntrospection::Property prop; prop.name = "foo"; prop.type = QDBusType("s"); prop.access = QDBusIntrospection::Property::Read; map << prop; QTest::newRow("one-readable") << "" << map; // one writable signal prop.access = QDBusIntrospection::Property::Write; map.clear(); map << prop; QTest::newRow("one-writable") << "" << map; // one read- & writable signal prop.access = QDBusIntrospection::Property::ReadWrite; map.clear(); map << prop; QTest::newRow("one-read-writable") << "" << map; // two, mixed properties prop.name = "bar"; prop.type = QDBusType("i"); prop.access = QDBusIntrospection::Property::Read; map << prop; QTest::newRow("two") << "" "" << map; // invert the order of the declaration QTest::newRow("two") << "" "" << map; // add a third with annotations prop.name = "baz"; prop.type = QDBusType("as"); prop.access = QDBusIntrospection::Property::Write; prop.annotations.insert("foo.annotation", "Hello, World"); prop.annotations.insert("foo.annotation2", "Goodbye, World"); map << prop; QTest::newRow("complex") << "" "" "" "" "" << map; // and now change the order QTest::newRow("complex2") << "" "" "" "" "" << map; } void tst_QDBusXmlParser::properties() { QString xmlHeader = "" "", xmlFooter = "" ""; QFETCH(QString, xmlDataFragment); QDBusIntrospection::Interface iface = QDBusIntrospection::parseInterface(xmlHeader + xmlDataFragment + xmlFooter); QCOMPARE(iface.name, QString("iface.iface1")); QFETCH(PropertyMap, propertyMap); PropertyMap parsedMap = iface.properties; QCOMPARE(propertyMap.count(), parsedMap.count()); QCOMPARE(propertyMap, parsedMap); } QTEST_MAIN(tst_QDBusXmlParser) #include "tst_qdbusxmlparser.moc"