1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "base/memory/ref_counted.h"
6 #include "base/test/test_simple_task_runner.h"
7 #include "device/bluetooth/bluetooth_adapter.h"
8 #include "device/bluetooth/bluetooth_adapter_mac.h"
9 #include "device/bluetooth/bluetooth_discovery_session.h"
10 #include "device/bluetooth/bluetooth_low_energy_device_mac.h"
11 #include "device/bluetooth/test/mock_bluetooth_central_manager_mac.h"
12 #include "testing/gtest/include/gtest/gtest.h"
13 #include "third_party/ocmock/OCMock/OCMock.h"
16 #import <CoreBluetooth/CoreBluetooth.h>
17 #else // !defined(OS_IOS)
18 #import <IOBluetooth/IOBluetooth.h>
19 #endif // defined(OS_IOS)
21 #import <Foundation/Foundation.h>
24 // |kTestHashAddress| is the hash corresponding to identifier |kTestNSUUID|.
25 NSString* const kTestNSUUID = @"00000000-1111-2222-3333-444444444444";
26 const std::string kTestHashAddress = "D1:6F:E3:22:FD:5B";
27 const int kTestRssi = 0;
32 class BluetoothAdapterMacTest : public testing::Test {
34 BluetoothAdapterMacTest()
35 : ui_task_runner_(new base::TestSimpleTaskRunner()),
36 adapter_(new BluetoothAdapterMac()),
37 adapter_mac_(static_cast<BluetoothAdapterMac*>(adapter_.get())),
39 error_callback_count_(0) {
40 adapter_mac_->InitForTest(ui_task_runner_);
43 // Helper methods for setup and access to BluetoothAdapterMacTest's members.
44 void PollAdapter() { adapter_mac_->PollAdapter(); }
46 void LowEnergyDeviceUpdated(CBPeripheral* peripheral,
47 NSDictionary* advertisement_data,
49 adapter_mac_->LowEnergyDeviceUpdated(peripheral, advertisement_data, rssi);
52 BluetoothDevice* GetDevice(const std::string& address) {
53 return adapter_->GetDevice(address);
56 CBPeripheral* CreateMockPeripheral(NSString* identifier) {
57 if (!BluetoothAdapterMac::IsLowEnergyAvailable()) {
58 LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
61 Class peripheral_class = NSClassFromString(@"CBPeripheral");
63 [[OCMockObject mockForClass:[peripheral_class class]] retain];
64 [static_cast<CBPeripheral*>([[mock_peripheral stub]
65 andReturnValue:@(CBPeripheralStateDisconnected)])
66 performSelector:@selector(state)];
67 [[[mock_peripheral stub] andReturn:[NSString string]] name];
68 Class uuid_class = NSClassFromString(@"NSUUID");
69 [[[mock_peripheral stub]
70 andReturn:[[uuid_class performSelector:@selector(UUID)]
71 performSelector:@selector(initWithUUIDString:)
72 withObject:identifier]] identifier];
74 return mock_peripheral;
77 NSDictionary* CreateAdvertisementData() {
78 NSDictionary* advertisement_data = @{
79 @"CBAdvertisementDataIsConnectable" : @(YES),
80 @"CBAdvertisementDataServiceDataKey" : [NSDictionary dictionary],
82 [advertisement_data retain];
83 return advertisement_data;
86 std::string GetHashAddress(CBPeripheral* peripheral) {
87 return BluetoothLowEnergyDeviceMac::GetPeripheralHashAddress(peripheral);
90 void SetDeviceTimeGreaterThanTimeout(BluetoothLowEnergyDeviceMac* device) {
91 device->last_update_time_.reset([[NSDate
92 dateWithTimeInterval:-(BluetoothAdapterMac::kDiscoveryTimeoutSec + 1)
93 sinceDate:[NSDate date]] retain]);
96 void AddLowEnergyDevice(BluetoothLowEnergyDeviceMac* device) {
97 adapter_mac_->devices_[device->GetAddress()] = device;
100 int NumDevices() { return adapter_mac_->devices_.size(); }
102 bool DevicePresent(CBPeripheral* peripheral) {
103 BluetoothDevice* device = adapter_mac_->GetDevice(
104 BluetoothLowEnergyDeviceMac::GetPeripheralHashAddress(peripheral));
105 return (device != NULL);
108 void RemoveTimedOutDevices() { adapter_mac_->RemoveTimedOutDevices(); }
110 bool SetMockCentralManager(CBCentralManagerState desired_state) {
111 if (!BluetoothAdapterMac::IsLowEnergyAvailable()) {
112 LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
115 mock_central_manager_ = [[MockCentralManager alloc] init];
116 [mock_central_manager_ setState:desired_state];
117 adapter_mac_->SetCentralManagerForTesting(mock_central_manager_);
121 void AddDiscoverySession(BluetoothDiscoveryFilter* discovery_filter) {
122 adapter_mac_->AddDiscoverySession(
124 base::Bind(&BluetoothAdapterMacTest::Callback, base::Unretained(this)),
125 base::Bind(&BluetoothAdapterMacTest::ErrorCallback,
126 base::Unretained(this)));
129 void RemoveDiscoverySession(BluetoothDiscoveryFilter* discovery_filter) {
130 adapter_mac_->RemoveDiscoverySession(
132 base::Bind(&BluetoothAdapterMacTest::Callback, base::Unretained(this)),
133 base::Bind(&BluetoothAdapterMacTest::ErrorCallback,
134 base::Unretained(this)));
137 int NumDiscoverySessions() { return adapter_mac_->num_discovery_sessions_; }
139 // Generic callbacks.
140 void Callback() { ++callback_count_; }
141 void ErrorCallback() { ++error_callback_count_; }
144 scoped_refptr<base::TestSimpleTaskRunner> ui_task_runner_;
145 scoped_refptr<BluetoothAdapter> adapter_;
146 BluetoothAdapterMac* adapter_mac_;
148 // Owned by |adapter_mac_|.
149 id mock_central_manager_ = NULL;
152 int error_callback_count_;
155 TEST_F(BluetoothAdapterMacTest, Poll) {
157 EXPECT_FALSE(ui_task_runner_->GetPendingTasks().empty());
160 TEST_F(BluetoothAdapterMacTest, AddDiscoverySessionWithLowEnergyFilter) {
161 if (!SetMockCentralManager(CBCentralManagerStatePoweredOn))
163 EXPECT_EQ(0, [mock_central_manager_ scanForPeripheralsCallCount]);
164 EXPECT_EQ(0, NumDiscoverySessions());
166 scoped_ptr<BluetoothDiscoveryFilter> discovery_filter(
167 new BluetoothDiscoveryFilter(
168 BluetoothDiscoveryFilter::Transport::TRANSPORT_LE));
169 AddDiscoverySession(discovery_filter.get());
170 EXPECT_EQ(1, callback_count_);
171 EXPECT_EQ(0, error_callback_count_);
172 EXPECT_EQ(1, NumDiscoverySessions());
174 // Check that adding a discovery session resulted in
175 // scanForPeripheralsWithServices being called on the Central Manager.
176 EXPECT_EQ(1, [mock_central_manager_ scanForPeripheralsCallCount]);
179 // TODO(krstnmnlsn): Test changing the filter when adding the second discovery
180 // session (once we have that ability).
181 TEST_F(BluetoothAdapterMacTest, AddSecondDiscoverySessionWithLowEnergyFilter) {
182 if (!SetMockCentralManager(CBCentralManagerStatePoweredOn))
184 scoped_ptr<BluetoothDiscoveryFilter> discovery_filter(
185 new BluetoothDiscoveryFilter(
186 BluetoothDiscoveryFilter::Transport::TRANSPORT_LE));
187 AddDiscoverySession(discovery_filter.get());
188 EXPECT_EQ(1, callback_count_);
189 EXPECT_EQ(0, error_callback_count_);
190 EXPECT_EQ(1, NumDiscoverySessions());
192 // We replaced the success callback handed to AddDiscoverySession, so
193 // |adapter_mac_| should remain in a discovering state indefinitely.
194 EXPECT_TRUE(adapter_mac_->IsDiscovering());
196 AddDiscoverySession(discovery_filter.get());
197 EXPECT_EQ(2, [mock_central_manager_ scanForPeripheralsCallCount]);
198 EXPECT_EQ(2, callback_count_);
199 EXPECT_EQ(0, error_callback_count_);
200 EXPECT_EQ(2, NumDiscoverySessions());
203 TEST_F(BluetoothAdapterMacTest, RemoveDiscoverySessionWithLowEnergyFilter) {
204 if (!SetMockCentralManager(CBCentralManagerStatePoweredOn))
206 EXPECT_EQ(0, [mock_central_manager_ scanForPeripheralsCallCount]);
208 scoped_ptr<BluetoothDiscoveryFilter> discovery_filter(
209 new BluetoothDiscoveryFilter(
210 BluetoothDiscoveryFilter::Transport::TRANSPORT_LE));
211 AddDiscoverySession(discovery_filter.get());
212 EXPECT_EQ(1, callback_count_);
213 EXPECT_EQ(0, error_callback_count_);
214 EXPECT_EQ(1, NumDiscoverySessions());
216 EXPECT_EQ(0, [mock_central_manager_ stopScanCallCount]);
217 RemoveDiscoverySession(discovery_filter.get());
218 EXPECT_EQ(2, callback_count_);
219 EXPECT_EQ(0, error_callback_count_);
220 EXPECT_EQ(0, NumDiscoverySessions());
222 // Check that removing the discovery session resulted in stopScan being called
223 // on the Central Manager.
224 EXPECT_EQ(1, [mock_central_manager_ stopScanCallCount]);
227 TEST_F(BluetoothAdapterMacTest, RemoveDiscoverySessionWithLowEnergyFilterFail) {
228 if (!SetMockCentralManager(CBCentralManagerStatePoweredOn))
230 EXPECT_EQ(0, [mock_central_manager_ scanForPeripheralsCallCount]);
231 EXPECT_EQ(0, [mock_central_manager_ stopScanCallCount]);
232 EXPECT_EQ(0, NumDiscoverySessions());
234 scoped_ptr<BluetoothDiscoveryFilter> discovery_filter(
235 new BluetoothDiscoveryFilter(
236 BluetoothDiscoveryFilter::Transport::TRANSPORT_LE));
237 RemoveDiscoverySession(discovery_filter.get());
238 EXPECT_EQ(0, callback_count_);
239 EXPECT_EQ(1, error_callback_count_);
240 EXPECT_EQ(0, NumDiscoverySessions());
242 // Check that stopScan was not called.
243 EXPECT_EQ(0, [mock_central_manager_ stopScanCallCount]);
246 TEST_F(BluetoothAdapterMacTest, CheckGetPeripheralHashAddress) {
247 base::scoped_nsobject<id> mock_peripheral(CreateMockPeripheral(kTestNSUUID));
248 if (mock_peripheral.get() == nil)
250 EXPECT_EQ(kTestHashAddress, GetHashAddress(mock_peripheral));
253 TEST_F(BluetoothAdapterMacTest, LowEnergyDeviceUpdatedNewDevice) {
254 base::scoped_nsobject<id> mock_peripheral(CreateMockPeripheral(kTestNSUUID));
255 if (mock_peripheral.get() == nil)
257 base::scoped_nsobject<NSDictionary> advertisement_data(
258 CreateAdvertisementData());
260 EXPECT_EQ(0, NumDevices());
261 EXPECT_FALSE(DevicePresent(mock_peripheral));
262 LowEnergyDeviceUpdated(mock_peripheral, advertisement_data, kTestRssi);
263 EXPECT_EQ(1, NumDevices());
264 EXPECT_TRUE(DevicePresent(mock_peripheral));
267 TEST_F(BluetoothAdapterMacTest, LowEnergyDeviceUpdatedOldDevice) {
268 base::scoped_nsobject<id> mock_peripheral(CreateMockPeripheral(kTestNSUUID));
269 if (mock_peripheral.get() == nil)
271 base::scoped_nsobject<NSDictionary> advertisement_data(
272 CreateAdvertisementData());
274 // Update the device for the first time and check it was correctly added to
276 EXPECT_EQ(0, NumDevices());
277 EXPECT_FALSE(DevicePresent(mock_peripheral));
278 LowEnergyDeviceUpdated(mock_peripheral, advertisement_data, kTestRssi);
279 EXPECT_EQ(1, NumDevices());
280 EXPECT_TRUE(DevicePresent(mock_peripheral));
281 // Search for the device by the address corresponding to |kTestNSUUID|.
282 BluetoothDeviceMac* device =
283 static_cast<BluetoothDeviceMac*>(GetDevice(kTestHashAddress));
284 base::scoped_nsobject<NSDate> first_update_time(
285 [device->GetLastUpdateTime() retain]);
287 // Update the device a second time. The device should be updated in
288 // |devices_| so check the time returned by GetLastUpdateTime() has increased.
289 LowEnergyDeviceUpdated(mock_peripheral, advertisement_data, kTestRssi);
290 EXPECT_EQ(1, NumDevices());
291 EXPECT_TRUE(DevicePresent(mock_peripheral));
292 device = static_cast<BluetoothDeviceMac*>(GetDevice(kTestHashAddress));
293 EXPECT_TRUE([device->GetLastUpdateTime() compare:first_update_time] ==
294 NSOrderedDescending);
297 TEST_F(BluetoothAdapterMacTest, UpdateDevicesRemovesLowEnergyDevice) {
298 base::scoped_nsobject<id> mock_peripheral(CreateMockPeripheral(kTestNSUUID));
299 if (mock_peripheral.get() == nil)
301 base::scoped_nsobject<NSDictionary> advertisement_data(
302 CreateAdvertisementData());
304 BluetoothLowEnergyDeviceMac* device = new BluetoothLowEnergyDeviceMac(
305 mock_peripheral, advertisement_data, kTestRssi);
306 SetDeviceTimeGreaterThanTimeout(device);
308 EXPECT_EQ(0, NumDevices());
309 AddLowEnergyDevice(device);
310 EXPECT_EQ(1, NumDevices());
311 EXPECT_TRUE(DevicePresent(mock_peripheral));
313 // Check that object pointed to by |device| is deleted by the adapter.
314 RemoveTimedOutDevices();
315 EXPECT_EQ(0, NumDevices());
316 EXPECT_FALSE(DevicePresent(mock_peripheral));
319 } // namespace device