1// Copyright (c) 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. 4 5#include "chrome/browser/chromeos/imageburner/burn_device_handler.h" 6 7#include <string> 8#include <utility> 9#include <vector> 10 11#include "base/bind.h" 12#include "base/logging.h" 13#include "base/memory/scoped_ptr.h" 14#include "base/observer_list.h" 15#include "base/stl_util.h" 16#include "chromeos/dbus/cros_disks_client.h" 17#include "chromeos/disks/disk_mount_manager.h" 18#include "testing/gtest/include/gtest/gtest.h" 19 20namespace chromeos { 21namespace imageburner { 22 23namespace { 24 25const bool kIsParent = true; 26const bool kIsBootDevice = true; 27const bool kHasMedia = true; 28 29class FakeDiskMountManager : public disks::DiskMountManager { 30 public: 31 FakeDiskMountManager() {} 32 33 virtual ~FakeDiskMountManager() { 34 STLDeleteValues(&disks_); 35 } 36 37 // Emulates to add new disk physically (e.g., connecting a 38 // new USB flash to a Chrome OS). 39 void EmulateAddDisk(scoped_ptr<Disk> in_disk) { 40 DCHECK(in_disk.get()); 41 // Keep the reference for the callback, before passing the ownership to 42 // InsertDisk. It should be safe, because it won't be deleted in 43 // InsertDisk. 44 Disk* disk = in_disk.get(); 45 bool new_disk = InsertDisk(disk->device_path(), in_disk.Pass()); 46 FOR_EACH_OBSERVER( 47 Observer, observers_, 48 OnDiskEvent(new_disk ? DISK_ADDED : DISK_CHANGED, disk)); 49 } 50 51 // Emulates to remove a disk phyically (e.g., removing a USB flash from 52 // a Chrome OS). 53 void EmulateRemoveDisk(const std::string& source_path) { 54 scoped_ptr<Disk> disk(RemoveDisk(source_path)); 55 if (disk.get()) { 56 FOR_EACH_OBSERVER( 57 Observer, observers_, OnDiskEvent(DISK_REMOVED, disk.get())); 58 } 59 } 60 61 // DiskMountManager overrides. 62 virtual void AddObserver(Observer* observer) OVERRIDE { 63 observers_.AddObserver(observer); 64 } 65 66 virtual void RemoveObserver(Observer* observer) OVERRIDE { 67 observers_.RemoveObserver(observer); 68 } 69 70 virtual const DiskMap& disks() const OVERRIDE { 71 return disks_; 72 } 73 74 // Following methods are not implemented. 75 virtual const Disk* FindDiskBySourcePath( 76 const std::string& source_path) const OVERRIDE { 77 return NULL; 78 } 79 virtual const MountPointMap& mount_points() const OVERRIDE { 80 // Note: mount_points_ will always be empty, now. 81 return mount_points_; 82 } 83 virtual void EnsureMountInfoRefreshed( 84 const EnsureMountInfoRefreshedCallback& callback) OVERRIDE {} 85 virtual void MountPath(const std::string& source_path, 86 const std::string& source_format, 87 const std::string& mount_label, 88 MountType type) OVERRIDE {} 89 virtual void UnmountPath(const std::string& mount_path, 90 UnmountOptions options, 91 const UnmountPathCallback& callback) OVERRIDE {} 92 virtual void FormatMountedDevice(const std::string& mount_path) OVERRIDE {} 93 virtual void UnmountDeviceRecursively( 94 const std::string& device_path, 95 const UnmountDeviceRecursivelyCallbackType& callback) OVERRIDE {} 96 virtual bool AddDiskForTest(Disk* disk) OVERRIDE { return false; } 97 virtual bool AddMountPointForTest( 98 const MountPointInfo& mount_point) OVERRIDE { 99 return false; 100 } 101 102 private: 103 bool InsertDisk(const std::string& path, scoped_ptr<Disk> disk) { 104 std::pair<DiskMap::iterator, bool> insert_result = 105 disks_.insert(std::pair<std::string, Disk*>(path, NULL)); 106 if (!insert_result.second) { 107 // There is already an entry. Delete it before replacing. 108 delete insert_result.first->second; 109 } 110 insert_result.first->second = disk.release(); // Moves ownership. 111 return insert_result.second; 112 } 113 114 scoped_ptr<Disk> RemoveDisk(const std::string& path) { 115 DiskMap::iterator iter = disks_.find(path); 116 if (iter == disks_.end()) { 117 // Not found. 118 return scoped_ptr<Disk>(); 119 } 120 scoped_ptr<Disk> result(iter->second); 121 disks_.erase(iter); 122 return result.Pass(); 123 } 124 125 ObserverList<Observer> observers_; 126 DiskMap disks_; 127 MountPointMap mount_points_; 128 129 DISALLOW_COPY_AND_ASSIGN(FakeDiskMountManager); 130}; 131 132void CopyDevicePathCallback( 133 std::string* out_path, const disks::DiskMountManager::Disk& disk) { 134 *out_path = disk.device_path(); 135} 136 137} // namespace 138 139class BurnDeviceHandlerTest : public testing::Test { 140 protected: 141 virtual void SetUp() OVERRIDE { 142 disk_mount_manager_.reset(new FakeDiskMountManager); 143 } 144 145 virtual void TearDown() OVERRIDE { 146 disk_mount_manager_.reset(); 147 } 148 149 static scoped_ptr<disks::DiskMountManager::Disk> CreateMockDisk( 150 const std::string& device_path, 151 bool is_parent, 152 bool on_boot_device, 153 bool has_media, 154 DeviceType device_type) { 155 return scoped_ptr<disks::DiskMountManager::Disk>( 156 new disks::DiskMountManager::Disk( 157 device_path, 158 "", // mount path 159 "", // system_path 160 "", // file_path 161 "", // device label 162 "", // drive label 163 "", // vendor id 164 "", // vendor name 165 "", // product id 166 "", // product name 167 "", // fs uuid 168 "", // system path prefix 169 device_type, 170 0, // total size in bytes 171 is_parent, 172 false, // is read only 173 has_media, 174 on_boot_device, 175 true, // on_removable_device 176 false)); // is hidden 177 } 178 179 scoped_ptr<FakeDiskMountManager> disk_mount_manager_; 180}; 181 182TEST_F(BurnDeviceHandlerTest, GetBurnableDevices) { 183 // The devices which should be retrieved as burnable. 184 disk_mount_manager_->EmulateAddDisk( 185 CreateMockDisk("/dev/burnable_usb", 186 kIsParent, !kIsBootDevice, kHasMedia, DEVICE_TYPE_USB)); 187 disk_mount_manager_->EmulateAddDisk( 188 CreateMockDisk("/dev/burnable_sd", 189 kIsParent, !kIsBootDevice, kHasMedia, DEVICE_TYPE_SD)); 190 191 // If the device type is neither USB nor SD, it shouldn't be burnable. 192 disk_mount_manager_->EmulateAddDisk( 193 CreateMockDisk( 194 "/dev/non_burnable_unknown", 195 kIsParent, !kIsBootDevice, kHasMedia, DEVICE_TYPE_UNKNOWN)); 196 disk_mount_manager_->EmulateAddDisk( 197 CreateMockDisk("/dev/non_burnable_dvd", 198 kIsParent, !kIsBootDevice, kHasMedia, DEVICE_TYPE_DVD)); 199 200 // If not parent, it shouldn't be burnable. 201 disk_mount_manager_->EmulateAddDisk( 202 CreateMockDisk("/dev/non_burnable_not_parent", 203 !kIsParent, !kIsBootDevice, kHasMedia, DEVICE_TYPE_USB)); 204 205 // If on_boot_device, it shouldn't be burnable. 206 disk_mount_manager_->EmulateAddDisk( 207 CreateMockDisk("/dev/non_burnable_boot_device", 208 kIsParent, kIsBootDevice, kHasMedia, DEVICE_TYPE_USB)); 209 210 // If no media, it shouldn't be burnable. 211 disk_mount_manager_->EmulateAddDisk( 212 CreateMockDisk("/dev/non_burnable_no_media", 213 kIsParent, !kIsBootDevice, !kHasMedia, DEVICE_TYPE_USB)); 214 215 BurnDeviceHandler handler(disk_mount_manager_.get()); 216 217 const std::vector<disks::DiskMountManager::Disk>& burnable_devices = 218 handler.GetBurnableDevices(); 219 ASSERT_EQ(2u, burnable_devices.size()); 220 bool burnable_usb_found = false; 221 bool burnable_sd_found = false; 222 for (size_t i = 0; i < burnable_devices.size(); ++i) { 223 const std::string& device_path = burnable_devices[i].device_path(); 224 burnable_usb_found |= (device_path == "/dev/burnable_usb"); 225 burnable_sd_found |= (device_path == "/dev/burnable_sd"); 226 } 227 228 EXPECT_TRUE(burnable_usb_found); 229 EXPECT_TRUE(burnable_sd_found); 230} 231 232TEST_F(BurnDeviceHandlerTest, Callback) { 233 std::string added_device; 234 std::string removed_device; 235 236 BurnDeviceHandler handler(disk_mount_manager_.get()); 237 handler.SetCallbacks( 238 base::Bind(CopyDevicePathCallback, &added_device), 239 base::Bind(CopyDevicePathCallback, &removed_device)); 240 241 // Emulate to connect a burnable device. 242 // |add_callback| should be invoked. 243 disk_mount_manager_->EmulateAddDisk( 244 CreateMockDisk("/dev/burnable", 245 kIsParent, !kIsBootDevice, kHasMedia, DEVICE_TYPE_USB)); 246 EXPECT_EQ("/dev/burnable", added_device); 247 EXPECT_TRUE(removed_device.empty()); 248 249 // Emulate to change the currently connected burnable device. 250 // Neither |add_callback| nor |remove_callback| should be called. 251 added_device.clear(); 252 removed_device.clear(); 253 disk_mount_manager_->EmulateAddDisk( 254 CreateMockDisk("/dev/burnable", 255 kIsParent, !kIsBootDevice, kHasMedia, DEVICE_TYPE_USB)); 256 EXPECT_TRUE(added_device.empty()); 257 EXPECT_TRUE(removed_device.empty()); 258 259 // Emulate to disconnect the burnable device. 260 // |remove_callback| should be called. 261 added_device.clear(); 262 removed_device.clear(); 263 disk_mount_manager_->EmulateRemoveDisk("/dev/burnable"); 264 EXPECT_TRUE(added_device.empty()); 265 EXPECT_EQ("/dev/burnable", removed_device); 266 267 // Emulate to connect and unconnect an unburnable device. 268 // For each case, neither |add_callback| nor |remove_callback| should be 269 // called. 270 added_device.clear(); 271 removed_device.clear(); 272 disk_mount_manager_->EmulateAddDisk( 273 CreateMockDisk("/dev/unburnable", 274 !kIsParent, !kIsBootDevice, kHasMedia, DEVICE_TYPE_USB)); 275 EXPECT_TRUE(added_device.empty()); 276 EXPECT_TRUE(removed_device.empty()); 277 278 added_device.clear(); 279 removed_device.clear(); 280 disk_mount_manager_->EmulateRemoveDisk("/dev/unburnable"); 281 EXPECT_TRUE(added_device.empty()); 282 EXPECT_TRUE(removed_device.empty()); 283} 284 285} // namespace imageburner 286} // namespace chromeos 287