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