1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28/* -*- C++ -*-
29 */
30#include <qcoreapplication.h>
31#include <qmetatype.h>
32#include <QtTest/QtTest>
33#include <QtDBus/QtDBus>
34#include <QtXml/QDomDocument>
35
36#define USE_PRIVATE_CODE
37#include "../qdbusmarshall/common.h"
38
39class tst_QDBusXmlParser: public QObject
40{
41 Q_OBJECT
42
43private:
44 void parsing_common(const QString&);
45 QString clean_xml(const QString&);
46
47private slots:
48 void initTestCase();
49 void parsing_data();
50 void parsing();
51 void parsingWithDoctype_data();
52 void parsingWithDoctype();
53
54 void methods_data();
55 void methods();
56 void signals__data();
57 void signals_();
58 void properties_data();
59 void properties();
60};
61
62void tst_QDBusXmlParser::initTestCase()
63{
64 // Always initialize the hash seed to 0 to get reliable test results
65 qSetGlobalQHashSeed(newSeed: 0);
66}
67
68void tst_QDBusXmlParser::parsing_data()
69{
70 QTest::addColumn<QString>(name: "xmlData");
71 QTest::addColumn<int>(name: "interfaceCount");
72 QTest::addColumn<int>(name: "objectCount");
73 QTest::addColumn<int>(name: "annotationCount");
74 QTest::addColumn<QStringList>(name: "introspection");
75
76 QStringList introspection;
77
78 QTest::newRow(dataTag: "null") << QString() << 0 << 0 << 0 << introspection;
79 QTest::newRow(dataTag: "empty") << QString("") << 0 << 0 << 0 << introspection;
80
81 QTest::newRow(dataTag: "junk") << "<junk/>" << 0 << 0 << 0 << introspection;
82 QTest::newRow(dataTag: "interface-inside-junk") << "<junk><interface name=\"iface.iface1\" /></junk>"
83 << 0 << 0 << 0 << introspection;
84 QTest::newRow(dataTag: "object-inside-junk") << "<junk><node name=\"obj1\" /></junk>"
85 << 0 << 0 << 0 << introspection;
86
87 QTest::newRow(dataTag: "zero-interfaces") << "<node/>" << 0 << 0 << 0 << introspection;
88
89 introspection << "<interface name=\"iface.iface1\"/>";
90 QTest::newRow(dataTag: "one-interface") << "<node><interface name=\"iface.iface1\" /></node>"
91 << 1 << 0 << 0 << introspection;
92 introspection.clear();
93
94 introspection << "<interface name=\"iface.iface1\"/>"
95 << "<interface name=\"iface.iface2\"/>";
96 QTest::newRow(dataTag: "two-interfaces") << "<node><interface name=\"iface.iface1\" />"
97 "<interface name=\"iface.iface2\" /></node>"
98 << 2 << 0 << 0 << introspection;
99 introspection.clear();
100
101
102 QTest::newRow(dataTag: "one-object") << "<node><node name=\"obj1\"/></node>"
103 << 0 << 1 << 0 << introspection;
104 QTest::newRow(dataTag: "two-objects") << "<node><node name=\"obj1\"/><node name=\"obj2\"/></node>"
105 << 0 << 2 << 0 << introspection;
106
107 introspection << "<interface name=\"iface.iface1\"/>";
108 QTest::newRow(dataTag: "i1o1") << "<node><interface name=\"iface.iface1\"/><node name=\"obj1\"/></node>"
109 << 1 << 1 << 0 << introspection;
110 introspection.clear();
111
112 introspection << "<interface name=\"iface.iface1\">"
113 " <annotation name=\"foo.testing\" value=\"nothing to see here\"/>"
114 "</interface>";
115 QTest::newRow(dataTag: "one-interface-annotated") << "<node><interface name=\"iface.iface1\">"
116 "<annotation name=\"foo.testing\" value=\"nothing to see here\" />"
117 "</interface></node>" << 1 << 0 << 1 << introspection;
118 introspection.clear();
119
120
121 introspection << "<interface name=\"iface.iface1\"/>";
122 QTest::newRow(dataTag: "one-interface-docnamespace") << "<?xml version=\"1.0\" xmlns:doc=\"foo\" ?><node>"
123 "<interface name=\"iface.iface1\"><doc:something />"
124 "</interface></node>" << 1 << 0 << 0 << introspection;
125 introspection.clear();
126}
127
128void tst_QDBusXmlParser::parsing_common(const QString &xmlData)
129{
130 QDBusIntrospection::Object obj =
131 QDBusIntrospection::parseObject(xml: xmlData, service: "local.testing", path: "/");
132 QFETCH(int, interfaceCount);
133 QFETCH(int, objectCount);
134 QFETCH(int, annotationCount);
135 QFETCH(QStringList, introspection);
136 QCOMPARE(obj.interfaces.count(), interfaceCount);
137 QCOMPARE(obj.childObjects.count(), objectCount);
138 QCOMPARE(QDBusIntrospection::parseInterface(xmlData).annotations.count(), annotationCount);
139
140 QDBusIntrospection::Interfaces ifaces = QDBusIntrospection::parseInterfaces(xml: xmlData);
141
142 // also verify the naming
143 int i = 0;
144 foreach (QString name, obj.interfaces) {
145 const QString expectedName = QString("iface.iface%1").arg(a: i+1);
146 QCOMPARE(name, expectedName);
147
148 const QString expectedIntrospection = clean_xml(introspection.at(i: i++));
149 const QString resultIntrospection = clean_xml(ifaces.value(akey: expectedName)->introspection);
150 QCOMPARE(resultIntrospection, expectedIntrospection);
151 }
152
153 i = 0;
154 foreach (QString name, obj.childObjects)
155 QCOMPARE(name, QString("obj%1").arg(++i));
156}
157
158QString tst_QDBusXmlParser::clean_xml(const QString &xmlData)
159{
160 QDomDocument dom;
161 dom.setContent(text: xmlData);
162 return dom.toString();
163}
164
165void tst_QDBusXmlParser::parsing()
166{
167 QFETCH(QString, xmlData);
168
169 parsing_common(xmlData);
170}
171
172void tst_QDBusXmlParser::parsingWithDoctype_data()
173{
174 parsing_data();
175}
176
177void tst_QDBusXmlParser::parsingWithDoctype()
178{
179 QString docType = "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n"
180 "\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n";
181 QFETCH(QString, xmlData);
182
183 QString toParse;
184 if (xmlData.startsWith(s: QLatin1String("<?xml"))) {
185 int split = xmlData.indexOf(c: QLatin1Char('>')) + 1;
186 toParse = xmlData.left(n: split) + docType + xmlData.mid(position: split);
187 } else {
188 toParse = docType + xmlData;
189 }
190 parsing_common(xmlData: toParse);
191}
192
193void tst_QDBusXmlParser::methods_data()
194{
195 QTest::addColumn<QString>(name: "xmlDataFragment");
196 QTest::addColumn<MethodMap>(name: "methodMap");
197
198 MethodMap map;
199 QTest::newRow(dataTag: "no-methods") << QString() << map;
200
201 // one method without arguments
202 QDBusIntrospection::Method method;
203 method.name = "Foo";
204 map << method;
205 QTest::newRow(dataTag: "one-method") << "<method name=\"Foo\"/>" << map;
206
207 // add another method without arguments
208 method.name = "Bar";
209 map << method;
210 QTest::newRow(dataTag: "two-methods") << "<method name=\"Foo\"/>"
211 "<method name=\"Bar\"/>"
212 << map;
213
214 // invert the order of the XML declaration
215 QTest::newRow(dataTag: "two-methods-inverse") << "<method name=\"Bar\"/>"
216 "<method name=\"Foo\"/>"
217 << map;
218
219 // add a third, with annotations
220 method.name = "Baz";
221 method.annotations.insert(akey: "foo.testing", avalue: "nothing to see here");
222 map << method;
223 QTest::newRow(dataTag: "method-with-annotation") <<
224 "<method name=\"Foo\"/>"
225 "<method name=\"Bar\"/>"
226 "<method name=\"Baz\"><annotation name=\"foo.testing\" value=\"nothing to see here\" /></method>"
227 << map;
228
229 // arguments
230 map.clear();
231 method.annotations.clear();
232
233 method.name = "Method";
234 method.inputArgs << arg(type: "s");
235 map << method;
236 QTest::newRow(dataTag: "one-in") <<
237 "<method name=\"Method\">"
238 "<arg type=\"s\" direction=\"in\"/>"
239 "</method>" << map;
240
241 // two arguments
242 method.inputArgs << arg(type: "v");
243 map.clear();
244 map << method;
245 QTest::newRow(dataTag: "two-in") <<
246 "<method name=\"Method\">"
247 "<arg type=\"s\" direction=\"in\"/>"
248 "<arg type=\"v\" direction=\"in\"/>"
249 "</method>" << map;
250
251 // one invalid arg
252 method.inputArgs << arg(type: "~", name: "invalid");
253 map.clear();
254 map << method;
255 QTest::newRow(dataTag: "two-in-one-invalid") <<
256 "<method name=\"Method\">"
257 "<arg type=\"s\" direction=\"in\"/>"
258 "<arg type=\"v\" direction=\"in\"/>"
259 "<arg type=\"~\" name=\"invalid\" direction=\"in\"/>"
260 "</method>" << map;
261
262 // one out argument
263 method.inputArgs.clear();
264 method.outputArgs << arg(type: "s");
265 map.clear();
266 map << method;
267 QTest::newRow(dataTag: "one-out") <<
268 "<method name=\"Method\">"
269 "<arg type=\"s\" direction=\"out\"/>"
270 "</method>" << map;
271
272 // two in and one out
273 method.inputArgs << arg(type: "s") << arg(type: "v");
274 map.clear();
275 map << method;
276 QTest::newRow(dataTag: "two-in-one-out") <<
277 "<method name=\"Method\">"
278 "<arg type=\"s\" direction=\"in\"/>"
279 "<arg type=\"v\" direction=\"in\"/>"
280 "<arg type=\"s\" direction=\"out\"/>"
281 "</method>" << map;
282
283 // let's try an arg with name
284 method.outputArgs.clear();
285 method.inputArgs.clear();
286 method.inputArgs << arg(type: "s", name: "foo");
287 map.clear();
288 map << method;
289 QTest::newRow(dataTag: "one-in-with-name") <<
290 "<method name=\"Method\">"
291 "<arg type=\"s\" name=\"foo\" direction=\"in\"/>"
292 "</method>" << map;
293
294 // two args with name
295 method.inputArgs << arg(type: "i", name: "bar");
296 map.clear();
297 map << method;
298 QTest::newRow(dataTag: "two-in-with-name") <<
299 "<method name=\"Method\">"
300 "<arg type=\"s\" name=\"foo\" direction=\"in\"/>"
301 "<arg type=\"i\" name=\"bar\" direction=\"in\"/>"
302 "</method>" << map;
303
304 // one complex
305 map.clear();
306 method = QDBusIntrospection::Method();
307
308 // Method1(in STRING arg1, in BYTE arg2, out ARRAY of STRING)
309 method.inputArgs << arg(type: "s", name: "arg1") << arg(type: "y", name: "arg2");
310 method.outputArgs << arg(type: "as");
311 method.name = "Method1";
312 map << method;
313
314 // Method2(in ARRAY of DICT_ENTRY of (STRING,VARIANT) variantMap, in UINT32 index,
315 // out STRING key, out VARIANT value)
316 // with annotation "foo.equivalent":"QVariantMap"
317 method = QDBusIntrospection::Method();
318 method.inputArgs << arg(type: "a{sv}", name: "variantMap") << arg(type: "u", name: "index");
319 method.outputArgs << arg(type: "s", name: "key") << arg(type: "v", name: "value");
320 method.annotations.insert(akey: "foo.equivalent", avalue: "QVariantMap");
321 method.name = "Method2";
322 map << method;
323
324 QTest::newRow(dataTag: "complex") <<
325 "<method name=\"Method1\">"
326 "<arg name=\"arg1\" type=\"s\" direction=\"in\"/>"
327 "<arg name=\"arg2\" type=\"y\" direction=\"in\"/>"
328 "<arg type=\"as\" direction=\"out\"/>"
329 "</method>"
330 "<method name=\"Method2\">"
331 "<arg name=\"variantMap\" type=\"a{sv}\" direction=\"in\"/>"
332 "<arg name=\"index\" type=\"u\" direction=\"in\"/>"
333 "<arg name=\"key\" type=\"s\" direction=\"out\"/>"
334 "<arg name=\"value\" type=\"v\" direction=\"out\"/>"
335 "<annotation name=\"foo.equivalent\" value=\"QVariantMap\"/>"
336 "</method>" << map;
337}
338
339void tst_QDBusXmlParser::methods()
340{
341 QString intHeader = "<interface name=\"iface.iface1\">",
342 intFooter = "</interface>",
343 xmlHeader = "<node>" + intHeader,
344 xmlFooter = intFooter + "</node>";
345
346 QFETCH(QString, xmlDataFragment);
347
348 QDBusIntrospection::Interface iface =
349 QDBusIntrospection::parseInterface(xml: xmlHeader + xmlDataFragment + xmlFooter);
350
351 QCOMPARE(iface.name, QString("iface.iface1"));
352 QCOMPARE(clean_xml(iface.introspection), clean_xml(intHeader + xmlDataFragment + intFooter));
353
354 QFETCH(MethodMap, methodMap);
355 MethodMap parsedMap = iface.methods;
356
357 QCOMPARE(parsedMap.count(), methodMap.count());
358 QCOMPARE(parsedMap, methodMap);
359}
360
361void tst_QDBusXmlParser::signals__data()
362{
363 QTest::addColumn<QString>(name: "xmlDataFragment");
364 QTest::addColumn<SignalMap>(name: "signalMap");
365
366 SignalMap map;
367 QTest::newRow(dataTag: "no-signals") << QString() << map;
368
369 // one signal without arguments
370 QDBusIntrospection::Signal signal;
371 signal.name = "Foo";
372 map << signal;
373 QTest::newRow(dataTag: "one-signal") << "<signal name=\"Foo\"/>" << map;
374
375 // add another signal without arguments
376 signal.name = "Bar";
377 map << signal;
378 QTest::newRow(dataTag: "two-signals") << "<signal name=\"Foo\"/>"
379 "<signal name=\"Bar\"/>"
380 << map;
381
382 // invert the order of the XML declaration
383 QTest::newRow(dataTag: "two-signals-inverse") << "<signal name=\"Bar\"/>"
384 "<signal name=\"Foo\"/>"
385 << map;
386
387 // add a third, with annotations
388 signal.name = "Baz";
389 signal.annotations.insert(akey: "foo.testing", avalue: "nothing to see here");
390 map << signal;
391 QTest::newRow(dataTag: "signal-with-annotation") <<
392 "<signal name=\"Foo\"/>"
393 "<signal name=\"Bar\"/>"
394 "<signal name=\"Baz\"><annotation name=\"foo.testing\" value=\"nothing to see here\" /></signal>"
395 << map;
396
397 // one out argument
398 map.clear();
399 signal.annotations.clear();
400 signal.outputArgs << arg(type: "s");
401 signal.name = "Signal";
402 map.clear();
403 map << signal;
404 QTest::newRow(dataTag: "one-out") <<
405 "<signal name=\"Signal\">"
406 "<arg type=\"s\" direction=\"out\"/>"
407 "</signal>" << map;
408
409 // without saying which direction it is
410 QTest::newRow(dataTag: "one-out-no-direction") <<
411 "<signal name=\"Signal\">"
412 "<arg type=\"s\"/>"
413 "</signal>" << map;
414
415 // two args with name
416 signal.outputArgs << arg(type: "i", name: "bar");
417 map.clear();
418 map << signal;
419 QTest::newRow(dataTag: "two-out-with-name") <<
420 "<signal name=\"Signal\">"
421 "<arg type=\"s\" direction=\"out\"/>"
422 "<arg type=\"i\" name=\"bar\"/>"
423 "</signal>" << map;
424
425 // one complex
426 map.clear();
427 signal = QDBusIntrospection::Signal();
428
429 // Signal1(out ARRAY of STRING)
430 signal.outputArgs << arg(type: "as");
431 signal.name = "Signal1";
432 map << signal;
433
434 // Signal2(out STRING key, out VARIANT value)
435 // with annotation "foo.equivalent":"QVariantMap"
436 signal = QDBusIntrospection::Signal();
437 signal.outputArgs << arg(type: "s", name: "key") << arg(type: "v", name: "value");
438 signal.annotations.insert(akey: "foo.equivalent", avalue: "QVariantMap");
439 signal.name = "Signal2";
440 map << signal;
441
442 QTest::newRow(dataTag: "complex") <<
443 "<signal name=\"Signal1\">"
444 "<arg type=\"as\" direction=\"out\"/>"
445 "</signal>"
446 "<signal name=\"Signal2\">"
447 "<arg name=\"key\" type=\"s\" direction=\"out\"/>"
448 "<arg name=\"value\" type=\"v\" direction=\"out\"/>"
449 "<annotation name=\"foo.equivalent\" value=\"QVariantMap\"/>"
450 "</signal>" << map;
451}
452
453void tst_QDBusXmlParser::signals_()
454{
455 QString intHeader = "<interface name=\"iface.iface1\">",
456 intFooter = "</interface>",
457 xmlHeader = "<node>" + intHeader,
458 xmlFooter = intFooter + "</node>";
459
460 QFETCH(QString, xmlDataFragment);
461
462 QDBusIntrospection::Interface iface =
463 QDBusIntrospection::parseInterface(xml: xmlHeader + xmlDataFragment + xmlFooter);
464
465 QCOMPARE(iface.name, QString("iface.iface1"));
466 QCOMPARE(clean_xml(iface.introspection), clean_xml(intHeader + xmlDataFragment + intFooter));
467
468 QFETCH(SignalMap, signalMap);
469 SignalMap parsedMap = iface.signals_;
470
471 QCOMPARE(signalMap.count(), parsedMap.count());
472 QCOMPARE(signalMap, parsedMap);
473}
474
475void tst_QDBusXmlParser::properties_data()
476{
477 QTest::addColumn<QString>(name: "xmlDataFragment");
478 QTest::addColumn<PropertyMap>(name: "propertyMap");
479
480 PropertyMap map;
481 QTest::newRow(dataTag: "no-signals") << QString() << map;
482
483 // one readable signal
484 QDBusIntrospection::Property prop;
485 prop.name = "foo";
486 prop.type = "s";
487 prop.access = QDBusIntrospection::Property::Read;
488 map << prop;
489 QTest::newRow(dataTag: "one-readable") << "<property name=\"foo\" type=\"s\" access=\"read\"/>" << map;
490
491 // one writable signal
492 prop.access = QDBusIntrospection::Property::Write;
493 map.clear();
494 map << prop;
495 QTest::newRow(dataTag: "one-writable") << "<property name=\"foo\" type=\"s\" access=\"write\"/>" << map;
496
497 // one read- & writable signal
498 prop.access = QDBusIntrospection::Property::ReadWrite;
499 map.clear();
500 map << prop;
501 QTest::newRow(dataTag: "one-read-writable") << "<property name=\"foo\" type=\"s\" access=\"readwrite\"/>"
502 << map;
503
504 // two, mixed properties
505 prop.name = "bar";
506 prop.type = "i";
507 prop.access = QDBusIntrospection::Property::Read;
508 map << prop;
509 QTest::newRow(dataTag: "two-1") <<
510 "<property name=\"foo\" type=\"s\" access=\"readwrite\"/>"
511 "<property name=\"bar\" type=\"i\" access=\"read\"/>" << map;
512
513 // invert the order of the declaration
514 QTest::newRow(dataTag: "two-2") <<
515 "<property name=\"bar\" type=\"i\" access=\"read\"/>"
516 "<property name=\"foo\" type=\"s\" access=\"readwrite\"/>" << map;
517
518 // add a third with annotations
519 prop.name = "baz";
520 prop.type = "as";
521 prop.access = QDBusIntrospection::Property::Write;
522 prop.annotations.insert(akey: "foo.annotation", avalue: "Hello, World");
523 prop.annotations.insert(akey: "foo.annotation2", avalue: "Goodbye, World");
524 map << prop;
525 QTest::newRow(dataTag: "complex") <<
526 "<property name=\"bar\" type=\"i\" access=\"read\"/>"
527 "<property name=\"baz\" type=\"as\" access=\"write\">"
528 "<annotation name=\"foo.annotation\" value=\"Hello, World\" />"
529 "<annotation name=\"foo.annotation2\" value=\"Goodbye, World\" />"
530 "</property>"
531 "<property name=\"foo\" type=\"s\" access=\"readwrite\"/>" << map;
532
533 // and now change the order
534 QTest::newRow(dataTag: "complex2") <<
535 "<property name=\"baz\" type=\"as\" access=\"write\">"
536 "<annotation name=\"foo.annotation2\" value=\"Goodbye, World\" />"
537 "<annotation name=\"foo.annotation\" value=\"Hello, World\" />"
538 "</property>"
539 "<property name=\"bar\" type=\"i\" access=\"read\"/>"
540 "<property name=\"foo\" type=\"s\" access=\"readwrite\"/>" << map;
541}
542
543void tst_QDBusXmlParser::properties()
544{
545 QString intHeader = "<interface name=\"iface.iface1\">",
546 intFooter = "</interface>",
547 xmlHeader = "<node>" + intHeader,
548 xmlFooter = intFooter + "</node>";
549
550 QFETCH(QString, xmlDataFragment);
551
552 QDBusIntrospection::Interface iface =
553 QDBusIntrospection::parseInterface(xml: xmlHeader + xmlDataFragment + xmlFooter);
554
555 QCOMPARE(iface.name, QString("iface.iface1"));
556 QCOMPARE(clean_xml(iface.introspection), clean_xml(intHeader + xmlDataFragment + intFooter));
557
558 QFETCH(PropertyMap, propertyMap);
559 PropertyMap parsedMap = iface.properties;
560
561 QCOMPARE(propertyMap.count(), parsedMap.count());
562 QCOMPARE(propertyMap, parsedMap);
563}
564
565QTEST_MAIN(tst_QDBusXmlParser)
566
567#include "tst_qdbusxmlparser.moc"
568

source code of qtbase/tests/auto/dbus/qdbusxmlparser/tst_qdbusxmlparser.cpp