blob: 9689f40292b710779fa74aaf39a9c71fbacee44d [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
/* -*- C++ -*-
*/
#include <qcoreapplication.h>
#include <qmetatype.h>
#include <QtTest/QtTest>
#include <QtDBus/QtDBus>
#include <QtXml/QDomDocument>
#define USE_PRIVATE_CODE
#include "../qdbusmarshall/common.h"
class tst_QDBusXmlParser: public QObject
{
Q_OBJECT
private:
void parsing_common(const QString&);
QString clean_xml(const QString&);
private slots:
void initTestCase();
void parsing_data();
void parsing();
void parsingWithDoctype_data();
void parsingWithDoctype();
void methods_data();
void methods();
void signals__data();
void signals_();
void properties_data();
void properties();
};
void tst_QDBusXmlParser::initTestCase()
{
// Always initialize the hash seed to 0 to get reliable test results
qSetGlobalQHashSeed(0);
}
void tst_QDBusXmlParser::parsing_data()
{
QTest::addColumn<QString>("xmlData");
QTest::addColumn<int>("interfaceCount");
QTest::addColumn<int>("objectCount");
QTest::addColumn<int>("annotationCount");
QTest::addColumn<QStringList>("introspection");
QStringList introspection;
QTest::newRow("null") << QString() << 0 << 0 << 0 << introspection;
QTest::newRow("empty") << QString("") << 0 << 0 << 0 << introspection;
QTest::newRow("junk") << "<junk/>" << 0 << 0 << 0 << introspection;
QTest::newRow("interface-inside-junk") << "<junk><interface name=\"iface.iface1\" /></junk>"
<< 0 << 0 << 0 << introspection;
QTest::newRow("object-inside-junk") << "<junk><node name=\"obj1\" /></junk>"
<< 0 << 0 << 0 << introspection;
QTest::newRow("zero-interfaces") << "<node/>" << 0 << 0 << 0 << introspection;
introspection << "<interface name=\"iface.iface1\"/>";
QTest::newRow("one-interface") << "<node><interface name=\"iface.iface1\" /></node>"
<< 1 << 0 << 0 << introspection;
introspection.clear();
introspection << "<interface name=\"iface.iface1\"/>"
<< "<interface name=\"iface.iface2\"/>";
QTest::newRow("two-interfaces") << "<node><interface name=\"iface.iface1\" />"
"<interface name=\"iface.iface2\" /></node>"
<< 2 << 0 << 0 << introspection;
introspection.clear();
QTest::newRow("one-object") << "<node><node name=\"obj1\"/></node>"
<< 0 << 1 << 0 << introspection;
QTest::newRow("two-objects") << "<node><node name=\"obj1\"/><node name=\"obj2\"/></node>"
<< 0 << 2 << 0 << introspection;
introspection << "<interface name=\"iface.iface1\"/>";
QTest::newRow("i1o1") << "<node><interface name=\"iface.iface1\"/><node name=\"obj1\"/></node>"
<< 1 << 1 << 0 << introspection;
introspection.clear();
introspection << "<interface name=\"iface.iface1\">"
" <annotation name=\"foo.testing\" value=\"nothing to see here\"/>"
"</interface>";
QTest::newRow("one-interface-annotated") << "<node><interface name=\"iface.iface1\">"
"<annotation name=\"foo.testing\" value=\"nothing to see here\" />"
"</interface></node>" << 1 << 0 << 1 << introspection;
introspection.clear();
introspection << "<interface name=\"iface.iface1\"/>";
QTest::newRow("one-interface-docnamespace") << "<?xml version=\"1.0\" xmlns:doc=\"foo\" ?><node>"
"<interface name=\"iface.iface1\"><doc:something />"
"</interface></node>" << 1 << 0 << 0 << introspection;
introspection.clear();
}
void tst_QDBusXmlParser::parsing_common(const QString &xmlData)
{
QDBusIntrospection::Object obj =
QDBusIntrospection::parseObject(xmlData, "local.testing", "/");
QFETCH(int, interfaceCount);
QFETCH(int, objectCount);
QFETCH(int, annotationCount);
QFETCH(QStringList, introspection);
QCOMPARE(obj.interfaces.count(), interfaceCount);
QCOMPARE(obj.childObjects.count(), objectCount);
QCOMPARE(QDBusIntrospection::parseInterface(xmlData).annotations.count(), annotationCount);
QDBusIntrospection::Interfaces ifaces = QDBusIntrospection::parseInterfaces(xmlData);
// also verify the naming
int i = 0;
foreach (QString name, obj.interfaces) {
const QString expectedName = QString("iface.iface%1").arg(i+1);
QCOMPARE(name, expectedName);
const QString expectedIntrospection = clean_xml(introspection.at(i++));
const QString resultIntrospection = clean_xml(ifaces.value(expectedName)->introspection);
QCOMPARE(resultIntrospection, expectedIntrospection);
}
i = 0;
foreach (QString name, obj.childObjects)
QCOMPARE(name, QString("obj%1").arg(++i));
}
QString tst_QDBusXmlParser::clean_xml(const QString &xmlData)
{
QDomDocument dom;
dom.setContent(xmlData);
return dom.toString();
}
void tst_QDBusXmlParser::parsing()
{
QFETCH(QString, xmlData);
parsing_common(xmlData);
}
void tst_QDBusXmlParser::parsingWithDoctype_data()
{
parsing_data();
}
void tst_QDBusXmlParser::parsingWithDoctype()
{
QString docType = "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n"
"\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n";
QFETCH(QString, xmlData);
QString toParse;
if (xmlData.startsWith(QLatin1String("<?xml"))) {
int split = xmlData.indexOf(QLatin1Char('>')) + 1;
toParse = xmlData.left(split) + docType + xmlData.mid(split);
} else {
toParse = docType + xmlData;
}
parsing_common(toParse);
}
void tst_QDBusXmlParser::methods_data()
{
QTest::addColumn<QString>("xmlDataFragment");
QTest::addColumn<MethodMap>("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") << "<method name=\"Foo\"/>" << map;
// add another method without arguments
method.name = "Bar";
map << method;
QTest::newRow("two-methods") << "<method name=\"Foo\"/>"
"<method name=\"Bar\"/>"
<< map;
// invert the order of the XML declaration
QTest::newRow("two-methods-inverse") << "<method name=\"Bar\"/>"
"<method name=\"Foo\"/>"
<< 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") <<
"<method name=\"Foo\"/>"
"<method name=\"Bar\"/>"
"<method name=\"Baz\"><annotation name=\"foo.testing\" value=\"nothing to see here\" /></method>"
<< map;
// arguments
map.clear();
method.annotations.clear();
method.name = "Method";
method.inputArgs << arg("s");
map << method;
QTest::newRow("one-in") <<
"<method name=\"Method\">"
"<arg type=\"s\" direction=\"in\"/>"
"</method>" << map;
// two arguments
method.inputArgs << arg("v");
map.clear();
map << method;
QTest::newRow("two-in") <<
"<method name=\"Method\">"
"<arg type=\"s\" direction=\"in\"/>"
"<arg type=\"v\" direction=\"in\"/>"
"</method>" << map;
// one invalid arg
method.inputArgs << arg("~", "invalid");
map.clear();
map << method;
QTest::newRow("two-in-one-invalid") <<
"<method name=\"Method\">"
"<arg type=\"s\" direction=\"in\"/>"
"<arg type=\"v\" direction=\"in\"/>"
"<arg type=\"~\" name=\"invalid\" direction=\"in\"/>"
"</method>" << map;
// one out argument
method.inputArgs.clear();
method.outputArgs << arg("s");
map.clear();
map << method;
QTest::newRow("one-out") <<
"<method name=\"Method\">"
"<arg type=\"s\" direction=\"out\"/>"
"</method>" << map;
// two in and one out
method.inputArgs << arg("s") << arg("v");
map.clear();
map << method;
QTest::newRow("two-in-one-out") <<
"<method name=\"Method\">"
"<arg type=\"s\" direction=\"in\"/>"
"<arg type=\"v\" direction=\"in\"/>"
"<arg type=\"s\" direction=\"out\"/>"
"</method>" << 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") <<
"<method name=\"Method\">"
"<arg type=\"s\" name=\"foo\" direction=\"in\"/>"
"</method>" << map;
// two args with name
method.inputArgs << arg("i", "bar");
map.clear();
map << method;
QTest::newRow("two-in-with-name") <<
"<method name=\"Method\">"
"<arg type=\"s\" name=\"foo\" direction=\"in\"/>"
"<arg type=\"i\" name=\"bar\" direction=\"in\"/>"
"</method>" << 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") <<
"<method name=\"Method1\">"
"<arg name=\"arg1\" type=\"s\" direction=\"in\"/>"
"<arg name=\"arg2\" type=\"y\" direction=\"in\"/>"
"<arg type=\"as\" direction=\"out\"/>"
"</method>"
"<method name=\"Method2\">"
"<arg name=\"variantMap\" type=\"a{sv}\" direction=\"in\"/>"
"<arg name=\"index\" type=\"u\" direction=\"in\"/>"
"<arg name=\"key\" type=\"s\" direction=\"out\"/>"
"<arg name=\"value\" type=\"v\" direction=\"out\"/>"
"<annotation name=\"foo.equivalent\" value=\"QVariantMap\"/>"
"</method>" << map;
}
void tst_QDBusXmlParser::methods()
{
QString intHeader = "<interface name=\"iface.iface1\">",
intFooter = "</interface>",
xmlHeader = "<node>" + intHeader,
xmlFooter = intFooter + "</node>";
QFETCH(QString, xmlDataFragment);
QDBusIntrospection::Interface iface =
QDBusIntrospection::parseInterface(xmlHeader + xmlDataFragment + xmlFooter);
QCOMPARE(iface.name, QString("iface.iface1"));
QCOMPARE(clean_xml(iface.introspection), clean_xml(intHeader + xmlDataFragment + intFooter));
QFETCH(MethodMap, methodMap);
MethodMap parsedMap = iface.methods;
QCOMPARE(parsedMap.count(), methodMap.count());
QCOMPARE(parsedMap, methodMap);
}
void tst_QDBusXmlParser::signals__data()
{
QTest::addColumn<QString>("xmlDataFragment");
QTest::addColumn<SignalMap>("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") << "<signal name=\"Foo\"/>" << map;
// add another signal without arguments
signal.name = "Bar";
map << signal;
QTest::newRow("two-signals") << "<signal name=\"Foo\"/>"
"<signal name=\"Bar\"/>"
<< map;
// invert the order of the XML declaration
QTest::newRow("two-signals-inverse") << "<signal name=\"Bar\"/>"
"<signal name=\"Foo\"/>"
<< 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") <<
"<signal name=\"Foo\"/>"
"<signal name=\"Bar\"/>"
"<signal name=\"Baz\"><annotation name=\"foo.testing\" value=\"nothing to see here\" /></signal>"
<< map;
// one out argument
map.clear();
signal.annotations.clear();
signal.outputArgs << arg("s");
signal.name = "Signal";
map.clear();
map << signal;
QTest::newRow("one-out") <<
"<signal name=\"Signal\">"
"<arg type=\"s\" direction=\"out\"/>"
"</signal>" << map;
// without saying which direction it is
QTest::newRow("one-out-no-direction") <<
"<signal name=\"Signal\">"
"<arg type=\"s\"/>"
"</signal>" << map;
// two args with name
signal.outputArgs << arg("i", "bar");
map.clear();
map << signal;
QTest::newRow("two-out-with-name") <<
"<signal name=\"Signal\">"
"<arg type=\"s\" direction=\"out\"/>"
"<arg type=\"i\" name=\"bar\"/>"
"</signal>" << 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") <<
"<signal name=\"Signal1\">"
"<arg type=\"as\" direction=\"out\"/>"
"</signal>"
"<signal name=\"Signal2\">"
"<arg name=\"key\" type=\"s\" direction=\"out\"/>"
"<arg name=\"value\" type=\"v\" direction=\"out\"/>"
"<annotation name=\"foo.equivalent\" value=\"QVariantMap\"/>"
"</signal>" << map;
}
void tst_QDBusXmlParser::signals_()
{
QString intHeader = "<interface name=\"iface.iface1\">",
intFooter = "</interface>",
xmlHeader = "<node>" + intHeader,
xmlFooter = intFooter + "</node>";
QFETCH(QString, xmlDataFragment);
QDBusIntrospection::Interface iface =
QDBusIntrospection::parseInterface(xmlHeader + xmlDataFragment + xmlFooter);
QCOMPARE(iface.name, QString("iface.iface1"));
QCOMPARE(clean_xml(iface.introspection), clean_xml(intHeader + xmlDataFragment + intFooter));
QFETCH(SignalMap, signalMap);
SignalMap parsedMap = iface.signals_;
QCOMPARE(signalMap.count(), parsedMap.count());
QCOMPARE(signalMap, parsedMap);
}
void tst_QDBusXmlParser::properties_data()
{
QTest::addColumn<QString>("xmlDataFragment");
QTest::addColumn<PropertyMap>("propertyMap");
PropertyMap map;
QTest::newRow("no-signals") << QString() << map;
// one readable signal
QDBusIntrospection::Property prop;
prop.name = "foo";
prop.type = "s";
prop.access = QDBusIntrospection::Property::Read;
map << prop;
QTest::newRow("one-readable") << "<property name=\"foo\" type=\"s\" access=\"read\"/>" << map;
// one writable signal
prop.access = QDBusIntrospection::Property::Write;
map.clear();
map << prop;
QTest::newRow("one-writable") << "<property name=\"foo\" type=\"s\" access=\"write\"/>" << map;
// one read- & writable signal
prop.access = QDBusIntrospection::Property::ReadWrite;
map.clear();
map << prop;
QTest::newRow("one-read-writable") << "<property name=\"foo\" type=\"s\" access=\"readwrite\"/>"
<< map;
// two, mixed properties
prop.name = "bar";
prop.type = "i";
prop.access = QDBusIntrospection::Property::Read;
map << prop;
QTest::newRow("two-1") <<
"<property name=\"foo\" type=\"s\" access=\"readwrite\"/>"
"<property name=\"bar\" type=\"i\" access=\"read\"/>" << map;
// invert the order of the declaration
QTest::newRow("two-2") <<
"<property name=\"bar\" type=\"i\" access=\"read\"/>"
"<property name=\"foo\" type=\"s\" access=\"readwrite\"/>" << map;
// add a third with annotations
prop.name = "baz";
prop.type = "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") <<
"<property name=\"bar\" type=\"i\" access=\"read\"/>"
"<property name=\"baz\" type=\"as\" access=\"write\">"
"<annotation name=\"foo.annotation\" value=\"Hello, World\" />"
"<annotation name=\"foo.annotation2\" value=\"Goodbye, World\" />"
"</property>"
"<property name=\"foo\" type=\"s\" access=\"readwrite\"/>" << map;
// and now change the order
QTest::newRow("complex2") <<
"<property name=\"baz\" type=\"as\" access=\"write\">"
"<annotation name=\"foo.annotation2\" value=\"Goodbye, World\" />"
"<annotation name=\"foo.annotation\" value=\"Hello, World\" />"
"</property>"
"<property name=\"bar\" type=\"i\" access=\"read\"/>"
"<property name=\"foo\" type=\"s\" access=\"readwrite\"/>" << map;
}
void tst_QDBusXmlParser::properties()
{
QString intHeader = "<interface name=\"iface.iface1\">",
intFooter = "</interface>",
xmlHeader = "<node>" + intHeader,
xmlFooter = intFooter + "</node>";
QFETCH(QString, xmlDataFragment);
QDBusIntrospection::Interface iface =
QDBusIntrospection::parseInterface(xmlHeader + xmlDataFragment + xmlFooter);
QCOMPARE(iface.name, QString("iface.iface1"));
QCOMPARE(clean_xml(iface.introspection), clean_xml(intHeader + xmlDataFragment + intFooter));
QFETCH(PropertyMap, propertyMap);
PropertyMap parsedMap = iface.properties;
QCOMPARE(propertyMap.count(), parsedMap.count());
QCOMPARE(propertyMap, parsedMap);
}
QTEST_MAIN(tst_QDBusXmlParser)
#include "tst_qdbusxmlparser.moc"