| 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 | |
| 41 | QT_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 |
| 53 | const int MaxScanTime = 5 * 60 * 1000; // 5 minutes in ms |
| 54 | |
| 55 | //Bluez needs at least 10s for a device discovery to be cancelled |
| 56 | const int MaxWaitForCancelTime = 15 * 1000; // 15 seconds in ms |
| 57 | |
| 58 | class tst_QBluetoothDeviceDiscoveryAgent : public QObject |
| 59 | { |
| 60 | Q_OBJECT |
| 61 | |
| 62 | public: |
| 63 | tst_QBluetoothDeviceDiscoveryAgent(); |
| 64 | ~tst_QBluetoothDeviceDiscoveryAgent(); |
| 65 | |
| 66 | public slots: |
| 67 | void deviceDiscoveryDebug(const QBluetoothDeviceInfo &info); |
| 68 | void finished(); |
| 69 | |
| 70 | private 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(); |
| 85 | private: |
| 86 | int noOfLocalDevices; |
| 87 | bool isBluez5Runtime = false; |
| 88 | }; |
| 89 | |
| 90 | tst_QBluetoothDeviceDiscoveryAgent::tst_QBluetoothDeviceDiscoveryAgent() |
| 91 | { |
| 92 | QLoggingCategory::setFilterRules(QStringLiteral("qt.bluetooth* = true")); |
| 93 | qRegisterMetaType<QBluetoothDeviceDiscoveryAgent::Error>(); |
| 94 | } |
| 95 | |
| 96 | tst_QBluetoothDeviceDiscoveryAgent::~tst_QBluetoothDeviceDiscoveryAgent() |
| 97 | { |
| 98 | } |
| 99 | |
| 100 | #if QT_CONFIG(bluez) |
| 101 | // This section was adopted from tst_qloggingcategory.cpp |
| 102 | QString logMessage; |
| 103 | |
| 104 | QByteArray 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 | |
| 122 | static 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 | |
| 132 | void 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 | |
| 176 | void 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 | |
| 187 | void 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 | |
| 205 | void tst_QBluetoothDeviceDiscoveryAgent::deviceDiscoveryDebug(const QBluetoothDeviceInfo &info) |
| 206 | { |
| 207 | qDebug() << "Discovered device:"<< info.address().toString() << info.name(); |
| 208 | } |
| 209 | |
| 210 | void 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 | |
| 370 | void tst_QBluetoothDeviceDiscoveryAgent::finished() |
| 371 | { |
| 372 | qDebug() << "Finished called"; |
| 373 | } |
| 374 | |
| 375 | void 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 | |
| 383 | void 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 | |
| 488 | void 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 | |
| 518 | void 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 | |
| 648 | QTEST_MAIN(tst_QBluetoothDeviceDiscoveryAgent) |
| 649 | |
| 650 | #include "tst_qbluetoothdevicediscoveryagent.moc" |
| 651 |
Definitions
- MaxScanTime
- MaxWaitForCancelTime
- tst_QBluetoothDeviceDiscoveryAgent
- tst_QBluetoothDeviceDiscoveryAgent
- ~tst_QBluetoothDeviceDiscoveryAgent
- initTestCase
- tst_properties
- tst_invalidBtAddress
- deviceDiscoveryDebug
- tst_startStopDeviceDiscoveries
- finished
- tst_deviceDiscovery_data
- tst_deviceDiscovery
- tst_discoveryTimeout
Learn to use CMake with our Intro Training
Find out more
