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 QtBluetooth module 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
29#include <QtTest/QtTest>
30
31#include <QDebug>
32#include <QVariant>
33#include <QList>
34#include <QLoggingCategory>
35
36#include <private/qtbluetoothglobal_p.h>
37#include <qbluetoothaddress.h>
38#include <qbluetoothdevicediscoveryagent.h>
39#include <qbluetoothlocaldevice.h>
40
41QT_USE_NAMESPACE
42
43/*
44 * Some parts of this test require a remote and discoverable Bluetooth
45 * device. Setting the BT_TEST_DEVICE environment variable will
46 * set the test up to fail if it cannot find a remote device.
47 * BT_TEST_DEVICE should contain the address of the device the
48 * test expects to find. Ensure that the device is running
49 * in discovery mode.
50 **/
51
52// Maximum time to for bluetooth device scan
53const int MaxScanTime = 5 * 60 * 1000; // 5 minutes in ms
54
55//Bluez needs at least 10s for a device discovery to be cancelled
56const int MaxWaitForCancelTime = 15 * 1000; // 15 seconds in ms
57
58class tst_QBluetoothDeviceDiscoveryAgent : public QObject
59{
60 Q_OBJECT
61
62public:
63 tst_QBluetoothDeviceDiscoveryAgent();
64 ~tst_QBluetoothDeviceDiscoveryAgent();
65
66public slots:
67 void deviceDiscoveryDebug(const QBluetoothDeviceInfo &info);
68 void finished();
69
70private slots:
71 void initTestCase();
72
73 void tst_properties();
74
75 void tst_invalidBtAddress();
76
77 void tst_startStopDeviceDiscoveries();
78
79 void tst_deviceDiscovery_data();
80 void tst_deviceDiscovery();
81
82 void tst_discoveryTimeout();
83
84 void tst_discoveryMethods();
85private:
86 int noOfLocalDevices;
87 bool isBluez5Runtime = false;
88};
89
90tst_QBluetoothDeviceDiscoveryAgent::tst_QBluetoothDeviceDiscoveryAgent()
91{
92 QLoggingCategory::setFilterRules(QStringLiteral("qt.bluetooth* = true"));
93 qRegisterMetaType<QBluetoothDeviceDiscoveryAgent::Error>();
94}
95
96tst_QBluetoothDeviceDiscoveryAgent::~tst_QBluetoothDeviceDiscoveryAgent()
97{
98}
99
100#if QT_CONFIG(bluez)
101// This section was adopted from tst_qloggingcategory.cpp
102QString logMessage;
103
104QByteArray qMyMessageFormatString(QtMsgType type, const QMessageLogContext &context,
105 const QString &str)
106{
107 QByteArray message;
108 message.append(context.category);
109 switch (type) {
110 case QtDebugMsg: message.append(".debug"); break;
111 case QtInfoMsg: message.append(".info"); break;
112 case QtWarningMsg: message.append(".warning"); break;
113 case QtCriticalMsg:message.append(".critical"); break;
114 case QtFatalMsg: message.append(".fatal"); break;
115 }
116 message.append(": ");
117 message.append(qPrintable(str));
118
119 return message.simplified();
120}
121
122static void myCustomMessageHandler(QtMsgType type,
123 const QMessageLogContext &context,
124 const QString &msg)
125{
126 logMessage = qMyMessageFormatString(type, context, msg);
127}
128#endif
129
130
131
132void tst_QBluetoothDeviceDiscoveryAgent::initTestCase()
133{
134 qRegisterMetaType<QBluetoothDeviceInfo>();
135 qRegisterMetaType<QBluetoothDeviceDiscoveryAgent::InquiryType>();
136
137#if QT_CONFIG(bluez)
138 // To distinguish Bluez 4 and 5 we peek into the debug output
139 // of first Bluetooth ctor. It executes a runtime test and prints the result
140 // as logging output. This avoids more complex runtime detection logic within this unit test.
141 QtMessageHandler oldMessageHandler;
142 oldMessageHandler = qInstallMessageHandler(myCustomMessageHandler);
143
144 noOfLocalDevices = QBluetoothLocalDevice::allDevices().count();
145 qInstallMessageHandler(oldMessageHandler);
146 isBluez5Runtime = logMessage.contains(QStringLiteral("Bluez 5"));
147 if (isBluez5Runtime)
148 qDebug() << "BlueZ 5 runtime detected.";
149#else
150 noOfLocalDevices = QBluetoothLocalDevice::allDevices().count();
151#endif
152
153 if (!noOfLocalDevices)
154 return;
155
156 // turn on BT in case it is not on
157 QBluetoothLocalDevice *device = new QBluetoothLocalDevice();
158 if (device->hostMode() == QBluetoothLocalDevice::HostPoweredOff) {
159 QSignalSpy hostModeSpy(device, SIGNAL(hostModeStateChanged(QBluetoothLocalDevice::HostMode)));
160 QVERIFY(hostModeSpy.isEmpty());
161 device->powerOn();
162 int connectTime = 5000; // ms
163 while (hostModeSpy.count() < 1 && connectTime > 0) {
164 QTest::qWait(ms: 500);
165 connectTime -= 500;
166 }
167 QVERIFY(hostModeSpy.count() > 0);
168 }
169 QBluetoothLocalDevice::HostMode hostMode= device->hostMode();
170 QVERIFY(hostMode == QBluetoothLocalDevice::HostConnectable
171 || hostMode == QBluetoothLocalDevice::HostDiscoverable
172 || hostMode == QBluetoothLocalDevice::HostDiscoverableLimitedInquiry);
173 delete device;
174}
175
176void tst_QBluetoothDeviceDiscoveryAgent::tst_properties()
177{
178 QBluetoothDeviceDiscoveryAgent discoveryAgent;
179
180 QCOMPARE(discoveryAgent.inquiryType(), QBluetoothDeviceDiscoveryAgent::GeneralUnlimitedInquiry);
181 discoveryAgent.setInquiryType(QBluetoothDeviceDiscoveryAgent::LimitedInquiry);
182 QCOMPARE(discoveryAgent.inquiryType(), QBluetoothDeviceDiscoveryAgent::LimitedInquiry);
183 discoveryAgent.setInquiryType(QBluetoothDeviceDiscoveryAgent::GeneralUnlimitedInquiry);
184 QCOMPARE(discoveryAgent.inquiryType(), QBluetoothDeviceDiscoveryAgent::GeneralUnlimitedInquiry);
185}
186
187void tst_QBluetoothDeviceDiscoveryAgent::tst_invalidBtAddress()
188{
189 QBluetoothDeviceDiscoveryAgent *discoveryAgent = new QBluetoothDeviceDiscoveryAgent(QBluetoothAddress("11:11:11:11:11:11"));
190
191 QCOMPARE(discoveryAgent->error(), QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError);
192 discoveryAgent->start();
193 QCOMPARE(discoveryAgent->isActive(), false);
194 delete discoveryAgent;
195
196 discoveryAgent = new QBluetoothDeviceDiscoveryAgent(QBluetoothAddress());
197 QCOMPARE(discoveryAgent->error(), QBluetoothDeviceDiscoveryAgent::NoError);
198 if (QBluetoothLocalDevice::allDevices().count() > 0) {
199 discoveryAgent->start();
200 QCOMPARE(discoveryAgent->isActive(), true);
201 }
202 delete discoveryAgent;
203}
204
205void tst_QBluetoothDeviceDiscoveryAgent::deviceDiscoveryDebug(const QBluetoothDeviceInfo &info)
206{
207 qDebug() << "Discovered device:" << info.address().toString() << info.name();
208}
209
210void tst_QBluetoothDeviceDiscoveryAgent::tst_startStopDeviceDiscoveries()
211{
212 QBluetoothDeviceDiscoveryAgent::InquiryType inquiryType = QBluetoothDeviceDiscoveryAgent::GeneralUnlimitedInquiry;
213 QBluetoothDeviceDiscoveryAgent discoveryAgent;
214
215 QVERIFY(discoveryAgent.error() == discoveryAgent.NoError);
216 QVERIFY(discoveryAgent.errorString().isEmpty());
217 QVERIFY(!discoveryAgent.isActive());
218 QVERIFY(discoveryAgent.discoveredDevices().isEmpty());
219
220 QSignalSpy finishedSpy(&discoveryAgent, SIGNAL(finished()));
221 QSignalSpy cancelSpy(&discoveryAgent, SIGNAL(canceled()));
222 QSignalSpy errorSpy(&discoveryAgent, SIGNAL(error(QBluetoothDeviceDiscoveryAgent::Error)));
223
224 // Starting case 1: start-stop, expecting cancel signal
225 discoveryAgent.setInquiryType(inquiryType);
226 // we should have no errors at this point.
227 QVERIFY(errorSpy.isEmpty());
228
229 discoveryAgent.start();
230
231 if (errorSpy.isEmpty()) {
232 QVERIFY(discoveryAgent.isActive());
233 QCOMPARE(discoveryAgent.errorString(), QString());
234 QCOMPARE(discoveryAgent.error(), QBluetoothDeviceDiscoveryAgent::NoError);
235 } else {
236 QCOMPARE(noOfLocalDevices, 0);
237 QVERIFY(!discoveryAgent.isActive());
238 QVERIFY(!discoveryAgent.errorString().isEmpty());
239 QVERIFY(discoveryAgent.error() != QBluetoothDeviceDiscoveryAgent::NoError);
240 QSKIP("No local Bluetooth device available. Skipping remaining part of test.");
241 }
242 // cancel current request.
243 discoveryAgent.stop();
244
245 // Wait for up to MaxWaitForCancelTime for the cancel to finish
246 int waitTime = MaxWaitForCancelTime;
247 while (cancelSpy.count() == 0 && waitTime > 0) {
248 QTest::qWait(ms: 100);
249 waitTime-=100;
250 }
251
252 // we should not be active anymore
253 QVERIFY(!discoveryAgent.isActive());
254 QVERIFY(errorSpy.isEmpty());
255 QCOMPARE(cancelSpy.count(), 1);
256 cancelSpy.clear();
257 // Starting case 2: start-start-stop, expecting cancel signal
258 discoveryAgent.start();
259 // we should be active now
260 QVERIFY(discoveryAgent.isActive());
261 QVERIFY(errorSpy.isEmpty());
262 // start again. should this be error?
263 discoveryAgent.start();
264 QVERIFY(discoveryAgent.isActive());
265 QVERIFY(errorSpy.isEmpty());
266 // stop
267 discoveryAgent.stop();
268
269 // Wait for up to MaxWaitForCancelTime for the cancel to finish
270 waitTime = MaxWaitForCancelTime;
271 while (cancelSpy.count() == 0 && waitTime > 0) {
272 QTest::qWait(ms: 100);
273 waitTime-=100;
274 }
275
276 // we should not be active anymore
277 QVERIFY(!discoveryAgent.isActive());
278 QVERIFY(errorSpy.isEmpty());
279 QVERIFY(cancelSpy.count() == 1);
280 cancelSpy.clear();
281
282 // Starting case 3: stop
283 discoveryAgent.stop();
284 QVERIFY(!discoveryAgent.isActive());
285 QVERIFY(errorSpy.isEmpty());
286
287 // Don't expect finished signal and no error
288 QVERIFY(finishedSpy.count() == 0);
289 QVERIFY(discoveryAgent.error() == discoveryAgent.NoError);
290 QVERIFY(discoveryAgent.errorString().isEmpty());
291
292 /*
293 Starting case 4: start-stop-start-stop:
294 We are testing that two subsequent stop() calls reduce total number
295 of cancel() signals to 1 if the true cancellation requires
296 asynchronous function calls (signal consolidation); otherwise we
297 expect 2x cancel() signal.
298
299 Examples are:
300 - Bluez4 (event loop needs to run for cancel)
301 - Bluez5 (no event loop required)
302 */
303
304 bool immediateSignal = false;
305 discoveryAgent.start();
306 QVERIFY(discoveryAgent.isActive());
307 QVERIFY(errorSpy.isEmpty());
308 // cancel current request.
309 discoveryAgent.stop();
310 //should only have triggered cancel() if stop didn't involve the event loop
311 if (cancelSpy.count() == 1) immediateSignal = true;
312
313 // start a new one
314 discoveryAgent.start();
315 // we should be active now
316 QVERIFY(discoveryAgent.isActive());
317 QVERIFY(errorSpy.isEmpty());
318 // stop
319 discoveryAgent.stop();
320 if (immediateSignal)
321 QVERIFY(cancelSpy.count() == 2);
322
323 // Wait for up to MaxWaitForCancelTime for the cancel to finish
324 waitTime = MaxWaitForCancelTime;
325 while (cancelSpy.count() == 0 && waitTime > 0) {
326 QTest::qWait(ms: 100);
327 waitTime-=100;
328 }
329 // we should not be active anymore
330 QVERIFY(!discoveryAgent.isActive());
331 QVERIFY(errorSpy.isEmpty());
332 // should only have 1 cancel
333
334 if (immediateSignal)
335 QVERIFY(cancelSpy.count() == 2);
336 else
337 QVERIFY(cancelSpy.count() == 1);
338 cancelSpy.clear();
339
340 // Starting case 5: start-stop-start: expecting finished signal & no cancel
341 discoveryAgent.start();
342 QVERIFY(discoveryAgent.isActive());
343 QVERIFY(errorSpy.isEmpty());
344 // cancel current request.
345 discoveryAgent.stop();
346 // start a new one
347 discoveryAgent.start();
348 // we should be active now
349 QVERIFY(discoveryAgent.isActive());
350 QVERIFY(errorSpy.isEmpty());
351
352 // Wait for up to MaxScanTime for the cancel to finish
353 waitTime = MaxScanTime;
354 while (finishedSpy.count() == 0 && waitTime > 0) {
355 QTest::qWait(ms: 1000);
356 waitTime-=1000;
357 }
358
359 // we should not be active anymore
360 QVERIFY(!discoveryAgent.isActive());
361 QVERIFY(errorSpy.isEmpty());
362 // should only have 1 cancel
363 QVERIFY(finishedSpy.count() == 1);
364
365 // On OS X, stop is synchronous (signal will be emitted immediately).
366 if (!immediateSignal)
367 QVERIFY(cancelSpy.isEmpty());
368}
369
370void tst_QBluetoothDeviceDiscoveryAgent::finished()
371{
372 qDebug() << "Finished called";
373}
374
375void tst_QBluetoothDeviceDiscoveryAgent::tst_deviceDiscovery_data()
376{
377 QTest::addColumn<QBluetoothDeviceDiscoveryAgent::InquiryType>(name: "inquiryType");
378
379 QTest::newRow(dataTag: "general unlimited inquiry") << QBluetoothDeviceDiscoveryAgent::GeneralUnlimitedInquiry;
380 QTest::newRow(dataTag: "limited inquiry") << QBluetoothDeviceDiscoveryAgent::LimitedInquiry;
381}
382
383void tst_QBluetoothDeviceDiscoveryAgent::tst_deviceDiscovery()
384{
385 {
386 QFETCH(QBluetoothDeviceDiscoveryAgent::InquiryType, inquiryType);
387
388 //Run test in case of multiple Bluetooth adapters
389 QBluetoothLocalDevice localDevice;
390 //We will use default adapter if there is no other adapter
391 QBluetoothAddress address = localDevice.address();
392 int numberOfAdapters = (localDevice.allDevices()).size();
393 QList<QBluetoothAddress> addresses;
394 if (numberOfAdapters > 1) {
395
396 for (int i=0; i < numberOfAdapters; i++) {
397 addresses.append(t: ((QBluetoothHostInfo)localDevice.allDevices().at(i)).address());
398 }
399 address = (QBluetoothAddress)addresses.at(i: 0);
400 }
401
402 QBluetoothDeviceDiscoveryAgent discoveryAgent(address);
403 QVERIFY(discoveryAgent.error() == discoveryAgent.NoError);
404 QVERIFY(discoveryAgent.errorString().isEmpty());
405 QVERIFY(!discoveryAgent.isActive());
406
407 QVERIFY(discoveryAgent.discoveredDevices().isEmpty());
408
409 QSignalSpy finishedSpy(&discoveryAgent, SIGNAL(finished()));
410 QSignalSpy errorSpy(&discoveryAgent, SIGNAL(error(QBluetoothDeviceDiscoveryAgent::Error)));
411 QSignalSpy discoveredSpy(&discoveryAgent, SIGNAL(deviceDiscovered(QBluetoothDeviceInfo)));
412// connect(&discoveryAgent, SIGNAL(finished()), this, SLOT(finished()));
413// connect(&discoveryAgent, SIGNAL(deviceDiscovered(QBluetoothDeviceInfo)),
414// this, SLOT(deviceDiscoveryDebug(QBluetoothDeviceInfo)));
415
416 discoveryAgent.setInquiryType(inquiryType);
417 discoveryAgent.start();
418 if (!errorSpy.isEmpty()) {
419 QCOMPARE(noOfLocalDevices, 0);
420 QVERIFY(!discoveryAgent.isActive());
421 QSKIP("No local Bluetooth device available. Skipping remaining part of test.");
422 }
423
424 QVERIFY(discoveryAgent.isActive());
425
426 // Wait for up to MaxScanTime for the scan to finish
427 int scanTime = MaxScanTime;
428 while (finishedSpy.count() == 0 && scanTime > 0) {
429 QTest::qWait(ms: 15000);
430 scanTime -= 15000;
431 }
432
433 // verify that we are finished
434 QVERIFY(!discoveryAgent.isActive());
435 // stop
436 discoveryAgent.stop();
437 QVERIFY(!discoveryAgent.isActive());
438 qDebug() << "Scan time left:" << scanTime;
439 // Expect finished signal with no error
440 QVERIFY(finishedSpy.count() == 1);
441 QVERIFY(errorSpy.isEmpty());
442 QVERIFY(discoveryAgent.error() == discoveryAgent.NoError);
443 QVERIFY(discoveryAgent.errorString().isEmpty());
444
445 // verify that the list is as big as the signals received.
446 QVERIFY(discoveredSpy.count() == discoveryAgent.discoveredDevices().length());
447 // verify that there really was some devices in the array
448
449 const QString remote = qgetenv(varName: "BT_TEST_DEVICE");
450 QBluetoothAddress remoteDevice;
451 if (!remote.isEmpty()) {
452 remoteDevice = QBluetoothAddress(remote);
453 QVERIFY2(!remote.isNull(), "Expecting valid Bluetooth address to be passed via BT_TEST_DEVICE");
454 } else {
455 qWarning() << "Not using a remote device for testing. Set BT_TEST_DEVICE env to run extended tests involving a remote device";
456 }
457
458 if (!remoteDevice.isNull())
459 QVERIFY(discoveredSpy.count() > 0);
460 int counter = 0;
461 // All returned QBluetoothDeviceInfo should be valid.
462 while (!discoveredSpy.isEmpty()) {
463 const QBluetoothDeviceInfo info =
464 qvariant_cast<QBluetoothDeviceInfo>(v: discoveredSpy.takeFirst().at(i: 0));
465 QVERIFY(info.isValid());
466 qDebug() << "Discovered device:" << info.address().toString() << info.name();
467
468 if (numberOfAdapters > 1) {
469 for (int i= 1; i < numberOfAdapters; i++) {
470 if (info.address().toString() == addresses[i].toString())
471 counter++;
472 }
473 }
474 }
475#if defined(Q_OS_IOS) || defined(Q_OS_TVOS) || QT_CONFIG(winrt_bt)
476 //On iOS/WinRT, we do not have access to the local device/adapter, numberOfAdapters is 0,
477 //so we skip this test at all.
478 QSKIP("iOS/WinRT: no local Bluetooth device available. Skipping remaining part of test.");
479#endif
480
481 //For multiple Bluetooth adapter do the check only for GeneralUnlimitedInquiry.
482 if (!(inquiryType == QBluetoothDeviceDiscoveryAgent::LimitedInquiry))
483 QVERIFY((numberOfAdapters-1) == counter);
484 }
485}
486
487
488void tst_QBluetoothDeviceDiscoveryAgent::tst_discoveryTimeout()
489{
490 QBluetoothDeviceDiscoveryAgent agent;
491
492 // check default values
493#if defined(Q_OS_MACOS) || defined(Q_OS_IOS) || defined(Q_OS_ANDROID) || QT_CONFIG(winrt_bt)
494 QCOMPARE(agent.lowEnergyDiscoveryTimeout(), 25000);
495 agent.setLowEnergyDiscoveryTimeout(-1); // negative ignored
496 QCOMPARE(agent.lowEnergyDiscoveryTimeout(), 25000);
497 agent.setLowEnergyDiscoveryTimeout(20000);
498 QCOMPARE(agent.lowEnergyDiscoveryTimeout(), 20000);
499#elif QT_CONFIG(bluez)
500 if (isBluez5Runtime) {
501 QCOMPARE(agent.lowEnergyDiscoveryTimeout(), 20000);
502 agent.setLowEnergyDiscoveryTimeout(-1); // negative ignored
503 QCOMPARE(agent.lowEnergyDiscoveryTimeout(), 20000);
504 agent.setLowEnergyDiscoveryTimeout(25000);
505 QCOMPARE(agent.lowEnergyDiscoveryTimeout(), 25000);
506 } else {
507 QCOMPARE(agent.lowEnergyDiscoveryTimeout(), -1);
508 agent.setLowEnergyDiscoveryTimeout(20000); // feature not supported -> ignored
509 QCOMPARE(agent.lowEnergyDiscoveryTimeout(), -1);
510 }
511#else
512 QCOMPARE(agent.lowEnergyDiscoveryTimeout(), -1);
513 agent.setLowEnergyDiscoveryTimeout(20000); // feature not supported -> ignored
514 QCOMPARE(agent.lowEnergyDiscoveryTimeout(), -1);
515#endif
516}
517
518void tst_QBluetoothDeviceDiscoveryAgent::tst_discoveryMethods()
519{
520 const QBluetoothLocalDevice localDevice;
521 if (localDevice.allDevices().size() != 1) {
522 // On iOS it returns 0 but we still have working BT.
523#ifndef Q_OS_IOS
524 QSKIP("This test expects exactly one local device working");
525#endif
526 }
527
528 const QBluetoothDeviceDiscoveryAgent::DiscoveryMethods
529 supportedMethods = QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods();
530
531 QVERIFY(supportedMethods != QBluetoothDeviceDiscoveryAgent::NoMethod);
532
533 QBluetoothDeviceDiscoveryAgent::DiscoveryMethod
534 unsupportedMethods = QBluetoothDeviceDiscoveryAgent::NoMethod;
535 QBluetoothDeviceInfo::CoreConfiguration
536 expectedConfiguration = QBluetoothDeviceInfo::BaseRateAndLowEnergyCoreConfiguration;
537
538 if (supportedMethods == QBluetoothDeviceDiscoveryAgent::ClassicMethod) {
539 unsupportedMethods = QBluetoothDeviceDiscoveryAgent::LowEnergyMethod;
540 expectedConfiguration = QBluetoothDeviceInfo::BaseRateCoreConfiguration;
541 } else if (supportedMethods == QBluetoothDeviceDiscoveryAgent::LowEnergyMethod) {
542 unsupportedMethods = QBluetoothDeviceDiscoveryAgent::ClassicMethod;
543 expectedConfiguration = QBluetoothDeviceInfo::LowEnergyCoreConfiguration;
544 }
545
546 QBluetoothDeviceDiscoveryAgent agent;
547 QSignalSpy finishedSpy(&agent, SIGNAL(finished()));
548 QSignalSpy errorSpy(&agent, SIGNAL(error(QBluetoothDeviceDiscoveryAgent::Error)));
549 QSignalSpy discoveredSpy(&agent, SIGNAL(deviceDiscovered(QBluetoothDeviceInfo)));
550
551 // NoMethod - should just immediately return:
552 agent.start(method: QBluetoothDeviceDiscoveryAgent::NoMethod);
553 QCOMPARE(agent.error(), QBluetoothDeviceDiscoveryAgent::NoError);
554 QVERIFY(!agent.isActive());
555 QCOMPARE(finishedSpy.size(), 0);
556 QCOMPARE(errorSpy.size(), 0);
557 QCOMPARE(discoveredSpy.size(), 0);
558
559 if (unsupportedMethods != QBluetoothDeviceDiscoveryAgent::NoMethod) {
560 agent.start(method: unsupportedMethods);
561 QCOMPARE(agent.error(), QBluetoothDeviceDiscoveryAgent::UnsupportedDiscoveryMethod);
562 QVERIFY(!agent.isActive());
563 QVERIFY(finishedSpy.isEmpty());
564 QCOMPARE(errorSpy.size(), 1);
565 errorSpy.clear();
566 QVERIFY(discoveredSpy.isEmpty());
567 }
568
569 // Start discovery, probably both Classic and LE methods:
570 agent.start(method: supportedMethods);
571 QVERIFY(agent.isActive());
572 QVERIFY(errorSpy.isEmpty());
573
574
575#define RUN_DISCOVERY(maxTimeout, step, condition) \
576 for (int scanTime = maxTimeout; (condition) && scanTime > 0; scanTime -= step) \
577 QTest::qWait(step);
578
579 // Wait for up to MaxScanTime for the scan to finish
580 const int timeStep = 15000;
581 RUN_DISCOVERY(MaxScanTime, timeStep, finishedSpy.isEmpty())
582
583 QVERIFY(!agent.isActive());
584 QVERIFY(errorSpy.size() <= 1);
585
586 if (errorSpy.size()) {
587 // For example, old iOS device could report it supports LE method,
588 // but it actually does not.
589 QVERIFY(supportedMethods == QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
590 QCOMPARE(agent.error(), QBluetoothDeviceDiscoveryAgent::UnsupportedDiscoveryMethod);
591 } else {
592 QVERIFY(finishedSpy.count() == 1);
593 QVERIFY(agent.error() == QBluetoothDeviceDiscoveryAgent::NoError);
594 QVERIFY(agent.errorString().isEmpty());
595
596 while (!discoveredSpy.isEmpty()) {
597 const QBluetoothDeviceInfo info =
598 qvariant_cast<QBluetoothDeviceInfo>(v: discoveredSpy.takeFirst().at(i: 0));
599 QVERIFY(info.isValid());
600 // on Android we do find devices with unknown configuration
601 if (info.coreConfigurations() != QBluetoothDeviceInfo::UnknownCoreConfiguration)
602 QVERIFY(info.coreConfigurations() & expectedConfiguration);
603 }
604 }
605
606 if (unsupportedMethods != QBluetoothDeviceDiscoveryAgent::NoMethod)
607 return;
608
609 // Both methods were reported as supported. We already tested them
610 // above, now let's test first Classic then LE.
611 finishedSpy.clear();
612 errorSpy.clear();
613 discoveredSpy.clear();
614
615 agent.start(method: QBluetoothDeviceDiscoveryAgent::ClassicMethod);
616 QVERIFY(agent.isActive());
617 QVERIFY(errorSpy.isEmpty());
618 QCOMPARE(agent.error(), QBluetoothDeviceDiscoveryAgent::NoError);
619
620 RUN_DISCOVERY(MaxScanTime, timeStep, finishedSpy.isEmpty())
621
622 QVERIFY(!agent.isActive());
623 QVERIFY(errorSpy.isEmpty());
624 QCOMPARE(finishedSpy.size(), 1);
625
626 finishedSpy.clear();
627 discoveredSpy.clear();
628
629 agent.start(method: QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
630 QVERIFY(agent.isActive());
631 QVERIFY(errorSpy.isEmpty());
632
633 RUN_DISCOVERY(MaxScanTime, timeStep, finishedSpy.isEmpty() && errorSpy.isEmpty())
634
635 QVERIFY(!agent.isActive());
636 QVERIFY(errorSpy.size() <= 1);
637
638 if (errorSpy.size()) {
639 QCOMPARE(agent.error(), QBluetoothDeviceDiscoveryAgent::UnsupportedDiscoveryMethod);
640 qDebug() << "QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods is inaccurate"
641 " on your platform/with your device, LowEnergyMethod is not supported";
642 } else {
643 QCOMPARE(agent.error(), QBluetoothDeviceDiscoveryAgent::NoError);
644 QCOMPARE(finishedSpy.size(), 1);
645 }
646}
647
648QTEST_MAIN(tst_QBluetoothDeviceDiscoveryAgent)
649
650#include "tst_qbluetoothdevicediscoveryagent.moc"
651

source code of qtconnectivity/tests/auto/qbluetoothdevicediscoveryagent/tst_qbluetoothdevicediscoveryagent.cpp