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 20 namespace chromeos { 21 namespace imageburner { 22 23 namespace { 24 25 const bool kIsParent = true; 26 const bool kIsBootDevice = true; 27 const bool kHasMedia = true; 28 29 class 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 RequestMountInfoRefresh() OVERRIDE {} 84 virtual void MountPath(const std::string& source_path, 85 const std::string& source_format, 86 const std::string& mount_label, 87 MountType type) OVERRIDE {} 88 virtual void UnmountPath(const std::string& mount_path, 89 UnmountOptions options, 90 const UnmountPathCallback& callback) OVERRIDE {} 91 virtual void FormatMountedDevice(const std::string& mount_path) OVERRIDE {} 92 virtual void UnmountDeviceRecursively( 93 const std::string& device_path, 94 const UnmountDeviceRecursivelyCallbackType& callback) OVERRIDE {} 95 virtual bool AddDiskForTest(Disk* disk) OVERRIDE { return false; } 96 virtual bool AddMountPointForTest( 97 const MountPointInfo& mount_point) OVERRIDE { 98 return false; 99 } 100 101 private: 102 bool InsertDisk(const std::string& path, scoped_ptr<Disk> disk) { 103 std::pair<DiskMap::iterator, bool> insert_result = 104 disks_.insert(std::pair<std::string, Disk*>(path, NULL)); 105 if (!insert_result.second) { 106 // There is already an entry. Delete it before replacing. 107 delete insert_result.first->second; 108 } 109 insert_result.first->second = disk.release(); // Moves ownership. 110 return insert_result.second; 111 } 112 113 scoped_ptr<Disk> RemoveDisk(const std::string& path) { 114 DiskMap::iterator iter = disks_.find(path); 115 if (iter == disks_.end()) { 116 // Not found. 117 return scoped_ptr<Disk>(); 118 } 119 scoped_ptr<Disk> result(iter->second); 120 disks_.erase(iter); 121 return result.Pass(); 122 } 123 124 ObserverList<Observer> observers_; 125 DiskMap disks_; 126 MountPointMap mount_points_; 127 128 DISALLOW_COPY_AND_ASSIGN(FakeDiskMountManager); 129 }; 130 131 void CopyDevicePathCallback( 132 std::string* out_path, const disks::DiskMountManager::Disk& disk) { 133 *out_path = disk.device_path(); 134 } 135 136 } // namespace 137 138 class BurnDeviceHandlerTest : public testing::Test { 139 protected: 140 virtual void SetUp() OVERRIDE { 141 disk_mount_manager_.reset(new FakeDiskMountManager); 142 } 143 144 virtual void TearDown() OVERRIDE { 145 disk_mount_manager_.reset(); 146 } 147 148 static scoped_ptr<disks::DiskMountManager::Disk> CreateMockDisk( 149 const std::string& device_path, 150 bool is_parent, 151 bool on_boot_device, 152 bool has_media, 153 DeviceType device_type) { 154 return scoped_ptr<disks::DiskMountManager::Disk>( 155 new disks::DiskMountManager::Disk( 156 device_path, 157 "", // mount path 158 "", // system_path 159 "", // file_path 160 "", // device label 161 "", // drive label 162 "", // vendor id 163 "", // vendor name 164 "", // product id 165 "", // product name 166 "", // fs uuid 167 "", // system path prefix 168 device_type, 169 0, // total size in bytes 170 is_parent, 171 false, // is read only 172 has_media, 173 on_boot_device, 174 false)); // is hidden 175 } 176 177 scoped_ptr<FakeDiskMountManager> disk_mount_manager_; 178 }; 179 180 TEST_F(BurnDeviceHandlerTest, GetBurnableDevices) { 181 // The devices which should be retrieved as burnable. 182 disk_mount_manager_->EmulateAddDisk( 183 CreateMockDisk("/dev/burnable_usb", 184 kIsParent, !kIsBootDevice, kHasMedia, DEVICE_TYPE_USB)); 185 disk_mount_manager_->EmulateAddDisk( 186 CreateMockDisk("/dev/burnable_sd", 187 kIsParent, !kIsBootDevice, kHasMedia, DEVICE_TYPE_SD)); 188 189 // If the device type is neither USB nor SD, it shouldn't be burnable. 190 disk_mount_manager_->EmulateAddDisk( 191 CreateMockDisk( 192 "/dev/non_burnable_unknown", 193 kIsParent, !kIsBootDevice, kHasMedia, DEVICE_TYPE_UNKNOWN)); 194 disk_mount_manager_->EmulateAddDisk( 195 CreateMockDisk("/dev/non_burnable_dvd", 196 kIsParent, !kIsBootDevice, kHasMedia, DEVICE_TYPE_DVD)); 197 198 // If not parent, it shouldn't be burnable. 199 disk_mount_manager_->EmulateAddDisk( 200 CreateMockDisk("/dev/non_burnable_not_parent", 201 !kIsParent, !kIsBootDevice, kHasMedia, DEVICE_TYPE_USB)); 202 203 // If on_boot_device, it shouldn't be burnable. 204 disk_mount_manager_->EmulateAddDisk( 205 CreateMockDisk("/dev/non_burnable_boot_device", 206 kIsParent, kIsBootDevice, kHasMedia, DEVICE_TYPE_USB)); 207 208 // If no media, it shouldn't be burnable. 209 disk_mount_manager_->EmulateAddDisk( 210 CreateMockDisk("/dev/non_burnable_no_media", 211 kIsParent, !kIsBootDevice, !kHasMedia, DEVICE_TYPE_USB)); 212 213 BurnDeviceHandler handler(disk_mount_manager_.get()); 214 215 const std::vector<disks::DiskMountManager::Disk>& burnable_devices = 216 handler.GetBurnableDevices(); 217 ASSERT_EQ(2u, burnable_devices.size()); 218 bool burnable_usb_found = false; 219 bool burnable_sd_found = false; 220 for (size_t i = 0; i < burnable_devices.size(); ++i) { 221 const std::string& device_path = burnable_devices[i].device_path(); 222 burnable_usb_found |= (device_path == "/dev/burnable_usb"); 223 burnable_sd_found |= (device_path == "/dev/burnable_sd"); 224 } 225 226 EXPECT_TRUE(burnable_usb_found); 227 EXPECT_TRUE(burnable_sd_found); 228 } 229 230 TEST_F(BurnDeviceHandlerTest, Callback) { 231 std::string added_device; 232 std::string removed_device; 233 234 BurnDeviceHandler handler(disk_mount_manager_.get()); 235 handler.SetCallbacks( 236 base::Bind(CopyDevicePathCallback, &added_device), 237 base::Bind(CopyDevicePathCallback, &removed_device)); 238 239 // Emulate to connect a burnable device. 240 // |add_callback| should be invoked. 241 disk_mount_manager_->EmulateAddDisk( 242 CreateMockDisk("/dev/burnable", 243 kIsParent, !kIsBootDevice, kHasMedia, DEVICE_TYPE_USB)); 244 EXPECT_EQ("/dev/burnable", added_device); 245 EXPECT_TRUE(removed_device.empty()); 246 247 // Emulate to change the currently connected burnable device. 248 // Neither |add_callback| nor |remove_callback| should be called. 249 added_device.clear(); 250 removed_device.clear(); 251 disk_mount_manager_->EmulateAddDisk( 252 CreateMockDisk("/dev/burnable", 253 kIsParent, !kIsBootDevice, kHasMedia, DEVICE_TYPE_USB)); 254 EXPECT_TRUE(added_device.empty()); 255 EXPECT_TRUE(removed_device.empty()); 256 257 // Emulate to disconnect the burnable device. 258 // |remove_callback| should be called. 259 added_device.clear(); 260 removed_device.clear(); 261 disk_mount_manager_->EmulateRemoveDisk("/dev/burnable"); 262 EXPECT_TRUE(added_device.empty()); 263 EXPECT_EQ("/dev/burnable", removed_device); 264 265 // Emulate to connect and unconnect an unburnable device. 266 // For each case, neither |add_callback| nor |remove_callback| should be 267 // called. 268 added_device.clear(); 269 removed_device.clear(); 270 disk_mount_manager_->EmulateAddDisk( 271 CreateMockDisk("/dev/unburnable", 272 !kIsParent, !kIsBootDevice, kHasMedia, DEVICE_TYPE_USB)); 273 EXPECT_TRUE(added_device.empty()); 274 EXPECT_TRUE(removed_device.empty()); 275 276 added_device.clear(); 277 removed_device.clear(); 278 disk_mount_manager_->EmulateRemoveDisk("/dev/unburnable"); 279 EXPECT_TRUE(added_device.empty()); 280 EXPECT_TRUE(removed_device.empty()); 281 } 282 283 } // namespace imageburner 284 } // namespace chromeos 285