1// Copyright (c) 2012 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 "base/bind.h"
6#include "base/message_loop/message_loop.h"
7#include "chromeos/dbus/dbus_thread_manager.h"
8#include "chromeos/dbus/fake_cros_disks_client.h"
9#include "chromeos/disks/disk_mount_manager.h"
10#include "testing/gmock/include/gmock/gmock.h"
11#include "testing/gtest/include/gtest/gtest.h"
12
13using chromeos::disks::DiskMountManager;
14using chromeos::CrosDisksClient;
15using chromeos::DBusThreadManager;
16using chromeos::FakeCrosDisksClient;
17using testing::_;
18using testing::Field;
19using testing::InSequence;
20
21namespace {
22
23// Holds information needed to create a DiskMountManager::Disk instance.
24struct TestDiskInfo {
25  const char* source_path;
26  const char* mount_path;
27  const char* system_path;
28  const char* file_path;
29  const char* device_label;
30  const char* drive_label;
31  const char* vendor_id;
32  const char* vendor_name;
33  const char* product_id;
34  const char* product_name;
35  const char* fs_uuid;
36  const char* system_path_prefix;
37  chromeos::DeviceType device_type;
38  uint64 size_in_bytes;
39  bool is_parent;
40  bool is_read_only;
41  bool has_media;
42  bool on_boot_device;
43  bool on_removable_device;
44  bool is_hidden;
45};
46
47// Holds information to create a DiskMOuntManager::MountPointInfo instance.
48struct TestMountPointInfo {
49  const char* source_path;
50  const char* mount_path;
51  chromeos::MountType mount_type;
52  chromeos::disks::MountCondition mount_condition;
53};
54
55// List of disks held in DiskMountManager at the begining of the test.
56const TestDiskInfo kTestDisks[] = {
57  {
58    "/device/source_path",
59    "/device/mount_path",
60    "/device/prefix/system_path",
61    "/device/file_path",
62    "/device/device_label",
63    "/device/drive_label",
64    "/device/vendor_id",
65    "/device/vendor_name",
66    "/device/product_id",
67    "/device/product_name",
68    "/device/fs_uuid",
69    "/device/prefix",
70    chromeos::DEVICE_TYPE_USB,
71    1073741824,  // size in bytes
72    false,  // is parent
73    false,  // is read only
74    true,  // has media
75    false,  // is on boot device
76    true,  // is on removable device
77    false  // is hidden
78  },
79};
80
81// List of mount points  held in DiskMountManager at the begining of the test.
82const TestMountPointInfo kTestMountPoints[] = {
83  {
84    "/archive/source_path",
85    "/archive/mount_path",
86    chromeos::MOUNT_TYPE_ARCHIVE,
87    chromeos::disks::MOUNT_CONDITION_NONE
88  },
89  {
90    "/device/source_path",
91    "/device/mount_path",
92    chromeos::MOUNT_TYPE_DEVICE,
93    chromeos::disks::MOUNT_CONDITION_NONE
94  },
95};
96
97// Mocks DiskMountManager observer.
98class MockDiskMountManagerObserver : public DiskMountManager::Observer {
99 public:
100  virtual ~MockDiskMountManagerObserver() {}
101
102  MOCK_METHOD2(OnDiskEvent, void(DiskMountManager::DiskEvent event,
103                                 const DiskMountManager::Disk* disk));
104  MOCK_METHOD2(OnDeviceEvent, void(DiskMountManager::DeviceEvent event,
105                                   const std::string& device_path));
106  MOCK_METHOD3(OnMountEvent,
107      void(DiskMountManager::MountEvent event,
108           chromeos::MountError error_code,
109           const DiskMountManager::MountPointInfo& mount_point));
110  MOCK_METHOD3(OnFormatEvent,
111      void(DiskMountManager::FormatEvent event,
112           chromeos::FormatError error_code,
113           const std::string& device_path));
114};
115
116class DiskMountManagerTest : public testing::Test {
117 public:
118  DiskMountManagerTest() {}
119  virtual ~DiskMountManagerTest() {}
120
121  // Sets up test dbus tread manager and disks mount manager.
122  // Initializes disk mount manager disks and mount points.
123  // Adds a test observer to the disk mount manager.
124  virtual void SetUp() {
125    fake_cros_disks_client_ = new FakeCrosDisksClient;
126    DBusThreadManager::GetSetterForTesting()->SetCrosDisksClient(
127        scoped_ptr<CrosDisksClient>(fake_cros_disks_client_));
128
129    DiskMountManager::Initialize();
130
131    InitDisksAndMountPoints();
132
133    DiskMountManager::GetInstance()->AddObserver(&observer_);
134  }
135
136  // Shuts down dbus thread manager and disk moutn manager used in the test.
137  virtual void TearDown() {
138    DiskMountManager::GetInstance()->RemoveObserver(&observer_);
139    DiskMountManager::Shutdown();
140    DBusThreadManager::Shutdown();
141  }
142
143 protected:
144  // Checks if disk mount manager contains a mount point with specified moutn
145  // path.
146  bool HasMountPoint(const std::string& mount_path) {
147    const DiskMountManager::MountPointMap& mount_points =
148        DiskMountManager::GetInstance()->mount_points();
149    return mount_points.find(mount_path) != mount_points.end();
150  }
151
152 private:
153  // Adds a new disk to the disk mount manager.
154  void AddTestDisk(const TestDiskInfo& disk) {
155    EXPECT_TRUE(DiskMountManager::GetInstance()->AddDiskForTest(
156        new DiskMountManager::Disk(disk.source_path,
157                                   disk.mount_path,
158                                   disk.system_path,
159                                   disk.file_path,
160                                   disk.device_label,
161                                   disk.drive_label,
162                                   disk.vendor_id,
163                                   disk.vendor_name,
164                                   disk.product_id,
165                                   disk.product_name,
166                                   disk.fs_uuid,
167                                   disk.system_path_prefix,
168                                   disk.device_type,
169                                   disk.size_in_bytes,
170                                   disk.is_parent,
171                                   disk.is_read_only,
172                                   disk.has_media,
173                                   disk.on_boot_device,
174                                   disk.on_removable_device,
175                                   disk.is_hidden)));
176  }
177
178  // Adds a new mount point to the disk mount manager.
179  // If the moutn point is a device mount point, disk with its source path
180  // should already be added to the disk mount manager.
181  void AddTestMountPoint(const TestMountPointInfo& mount_point) {
182    EXPECT_TRUE(DiskMountManager::GetInstance()->AddMountPointForTest(
183        DiskMountManager::MountPointInfo(mount_point.source_path,
184                                         mount_point.mount_path,
185                                         mount_point.mount_type,
186                                         mount_point.mount_condition)));
187  }
188
189  // Adds disks and mount points to disk mount manager.
190  void InitDisksAndMountPoints() {
191    // Disks should be  added first (when adding device mount points it is
192    // expected that the corresponding disk is already added).
193    for (size_t i = 0; i < arraysize(kTestDisks); i++)
194      AddTestDisk(kTestDisks[i]);
195
196    for (size_t i = 0; i < arraysize(kTestMountPoints); i++)
197      AddTestMountPoint(kTestMountPoints[i]);
198  }
199
200 protected:
201  chromeos::FakeCrosDisksClient* fake_cros_disks_client_;
202  MockDiskMountManagerObserver observer_;
203  base::MessageLoopForUI message_loop_;
204};
205
206// Tests that the observer gets notified on attempt to format non existent mount
207// point.
208TEST_F(DiskMountManagerTest, Format_NotMounted) {
209  EXPECT_CALL(observer_, OnFormatEvent(DiskMountManager::FORMAT_COMPLETED,
210                                       chromeos::FORMAT_ERROR_UNKNOWN,
211                                       "/mount/non_existent"))
212      .Times(1);
213  DiskMountManager::GetInstance()->FormatMountedDevice("/mount/non_existent");
214}
215
216// Tests that it is not possible to format archive mount point.
217TEST_F(DiskMountManagerTest, Format_Archive) {
218  EXPECT_CALL(observer_, OnFormatEvent(DiskMountManager::FORMAT_COMPLETED,
219                                       chromeos::FORMAT_ERROR_UNKNOWN,
220                                       "/archive/source_path"))
221      .Times(1);
222
223  DiskMountManager::GetInstance()->FormatMountedDevice("/archive/mount_path");
224}
225
226// Tests that format fails if the device cannot be unmounted.
227TEST_F(DiskMountManagerTest, Format_FailToUnmount) {
228  // Before formatting mounted device, the device should be unmounted.
229  // In this test unmount will fail, and there should be no attempt to
230  // format the device.
231
232  // Set up expectations for observer mock.
233  // Observer should be notified that unmount attempt fails and format task
234  // failed to start.
235  {
236    InSequence s;
237
238    EXPECT_CALL(observer_,
239        OnMountEvent(DiskMountManager::UNMOUNTING,
240                     chromeos::MOUNT_ERROR_INTERNAL,
241                     Field(&DiskMountManager::MountPointInfo::mount_path,
242                           "/device/mount_path")))
243        .Times(1);
244
245    EXPECT_CALL(observer_, OnFormatEvent(DiskMountManager::FORMAT_COMPLETED,
246                                         chromeos::FORMAT_ERROR_UNKNOWN,
247                                         "/device/source_path"))
248        .Times(1);
249  }
250
251  fake_cros_disks_client_->MakeUnmountFail();
252  // Start test.
253  DiskMountManager::GetInstance()->FormatMountedDevice("/device/mount_path");
254
255  // Cros disks will respond asynchronoulsy, so let's drain the message loop.
256  message_loop_.RunUntilIdle();
257
258  EXPECT_EQ(1, fake_cros_disks_client_->unmount_call_count());
259  EXPECT_EQ("/device/mount_path",
260            fake_cros_disks_client_->last_unmount_device_path());
261  EXPECT_EQ(chromeos::UNMOUNT_OPTIONS_NONE,
262            fake_cros_disks_client_->last_unmount_options());
263  EXPECT_EQ(0, fake_cros_disks_client_->format_call_count());
264
265  // The device mount should still be here.
266  EXPECT_TRUE(HasMountPoint("/device/mount_path"));
267}
268
269// Tests that observer is notified when cros disks fails to start format
270// process.
271TEST_F(DiskMountManagerTest, Format_FormatFailsToStart) {
272  // Before formatting mounted device, the device should be unmounted.
273  // In this test, unmount will succeed, but call to Format method will
274  // fail.
275
276  // Set up expectations for observer mock.
277  // Observer should be notified that the device was unmounted and format task
278  // failed to start.
279  {
280    InSequence s;
281
282    EXPECT_CALL(observer_,
283        OnMountEvent(DiskMountManager::UNMOUNTING,
284                     chromeos::MOUNT_ERROR_NONE,
285                     Field(&DiskMountManager::MountPointInfo::mount_path,
286                           "/device/mount_path")))
287        .Times(1);
288
289    EXPECT_CALL(observer_, OnFormatEvent(DiskMountManager::FORMAT_COMPLETED,
290                                         chromeos::FORMAT_ERROR_UNKNOWN,
291                                         "/device/source_path"))
292        .Times(1);
293  }
294
295  fake_cros_disks_client_->MakeFormatFail();
296  // Start the test.
297  DiskMountManager::GetInstance()->FormatMountedDevice("/device/mount_path");
298
299  // Cros disks will respond asynchronoulsy, so let's drain the message loop.
300  message_loop_.RunUntilIdle();
301
302  EXPECT_EQ(1, fake_cros_disks_client_->unmount_call_count());
303  EXPECT_EQ("/device/mount_path",
304            fake_cros_disks_client_->last_unmount_device_path());
305  EXPECT_EQ(chromeos::UNMOUNT_OPTIONS_NONE,
306            fake_cros_disks_client_->last_unmount_options());
307  EXPECT_EQ(1, fake_cros_disks_client_->format_call_count());
308  EXPECT_EQ("/device/source_path",
309            fake_cros_disks_client_->last_format_device_path());
310  EXPECT_EQ("vfat", fake_cros_disks_client_->last_format_filesystem());
311
312  // The device mount should be gone.
313  EXPECT_FALSE(HasMountPoint("/device/mount_path"));
314}
315
316// Tests the case where there are two format requests for the same device.
317TEST_F(DiskMountManagerTest, Format_ConcurrentFormatCalls) {
318  // Only the first format request should be processed (the second unmount
319  // request fails because the device is already unmounted at that point).
320  // CrosDisksClient will report that the format process for the first request
321  // is successfully started.
322
323  // Set up expectations for observer mock.
324  // The observer should get a FORMAT_STARTED event for one format request and a
325  // FORMAT_COMPLETED with an error code for the other format request. The
326  // formatting will be started only for the first request.
327  // There should be only one UNMOUNTING event. The result of the second one
328  // should not be reported as the mount point will go away after the first
329  // request.
330  //
331  // Note that in this test the format completion signal will not be simulated,
332  // so the observer should not get FORMAT_COMPLETED signal.
333  {
334    InSequence s;
335
336    EXPECT_CALL(observer_,
337        OnMountEvent(DiskMountManager::UNMOUNTING,
338                     chromeos::MOUNT_ERROR_NONE,
339                     Field(&DiskMountManager::MountPointInfo::mount_path,
340                           "/device/mount_path")))
341        .Times(1);
342
343    EXPECT_CALL(observer_, OnFormatEvent(DiskMountManager::FORMAT_COMPLETED,
344                                         chromeos::FORMAT_ERROR_UNKNOWN,
345                                         "/device/source_path"))
346        .Times(1);
347
348    EXPECT_CALL(observer_, OnFormatEvent(DiskMountManager::FORMAT_STARTED,
349                                         chromeos::FORMAT_ERROR_NONE,
350                                         "/device/source_path"))
351        .Times(1);
352  }
353
354  fake_cros_disks_client_->set_unmount_listener(
355      base::Bind(&FakeCrosDisksClient::MakeUnmountFail,
356                 base::Unretained(fake_cros_disks_client_)));
357  // Start the test.
358  DiskMountManager::GetInstance()->FormatMountedDevice("/device/mount_path");
359  DiskMountManager::GetInstance()->FormatMountedDevice("/device/mount_path");
360
361  // Cros disks will respond asynchronoulsy, so let's drain the message loop.
362  message_loop_.RunUntilIdle();
363
364  EXPECT_EQ(2, fake_cros_disks_client_->unmount_call_count());
365  EXPECT_EQ("/device/mount_path",
366            fake_cros_disks_client_->last_unmount_device_path());
367  EXPECT_EQ(chromeos::UNMOUNT_OPTIONS_NONE,
368            fake_cros_disks_client_->last_unmount_options());
369  EXPECT_EQ(1, fake_cros_disks_client_->format_call_count());
370  EXPECT_EQ("/device/source_path",
371            fake_cros_disks_client_->last_format_device_path());
372  EXPECT_EQ("vfat",
373            fake_cros_disks_client_->last_format_filesystem());
374
375  // The device mount should be gone.
376  EXPECT_FALSE(HasMountPoint("/device/mount_path"));
377}
378
379// Tests the case when the format process actually starts and fails.
380TEST_F(DiskMountManagerTest, Format_FormatFails) {
381  // Both unmount and format device cals are successful in this test.
382
383  // Set up expectations for observer mock.
384  // The observer should get notified that the device was unmounted and that
385  // formatting has started.
386  // After the formatting starts, the test will simulate failing
387  // FORMAT_COMPLETED signal, so the observer should also be notified the
388  // formatting has failed (FORMAT_COMPLETED event).
389  {
390    InSequence s;
391
392    EXPECT_CALL(observer_,
393        OnMountEvent(DiskMountManager::UNMOUNTING,
394                     chromeos::MOUNT_ERROR_NONE,
395                     Field(&DiskMountManager::MountPointInfo::mount_path,
396                           "/device/mount_path")))
397        .Times(1);
398
399    EXPECT_CALL(observer_, OnFormatEvent(DiskMountManager::FORMAT_STARTED,
400                                         chromeos::FORMAT_ERROR_NONE,
401                                         "/device/source_path"))
402        .Times(1);
403
404    EXPECT_CALL(observer_, OnFormatEvent(DiskMountManager::FORMAT_COMPLETED,
405                                         chromeos::FORMAT_ERROR_UNKNOWN,
406                                         "/device/source_path"))
407        .Times(1);
408  }
409
410  // Start the test.
411  DiskMountManager::GetInstance()->FormatMountedDevice("/device/mount_path");
412
413  // Wait for Unmount and Format calls to end.
414  message_loop_.RunUntilIdle();
415
416  EXPECT_EQ(1, fake_cros_disks_client_->unmount_call_count());
417  EXPECT_EQ("/device/mount_path",
418            fake_cros_disks_client_->last_unmount_device_path());
419  EXPECT_EQ(chromeos::UNMOUNT_OPTIONS_NONE,
420            fake_cros_disks_client_->last_unmount_options());
421  EXPECT_EQ(1, fake_cros_disks_client_->format_call_count());
422  EXPECT_EQ("/device/source_path",
423            fake_cros_disks_client_->last_format_device_path());
424  EXPECT_EQ("vfat", fake_cros_disks_client_->last_format_filesystem());
425
426  // The device should be unmounted by now.
427  EXPECT_FALSE(HasMountPoint("/device/mount_path"));
428
429  // Send failing FORMAT_COMPLETED signal.
430  // The failure is marked by ! in fromt of the path (but this should change
431  // soon).
432  fake_cros_disks_client_->SendFormatCompletedEvent(
433      chromeos::FORMAT_ERROR_UNKNOWN, "/device/source_path");
434}
435
436// Tests the case when formatting completes successfully.
437TEST_F(DiskMountManagerTest, Format_FormatSuccess) {
438  // Set up cros disks client mocks.
439  // Both unmount and format device cals are successful in this test.
440
441  // Set up expectations for observer mock.
442  // The observer should receive UNMOUNTING, FORMAT_STARTED and FORMAT_COMPLETED
443  // events (all of them without an error set).
444  {
445    InSequence s;
446
447    EXPECT_CALL(observer_,
448        OnMountEvent(DiskMountManager::UNMOUNTING,
449                     chromeos::MOUNT_ERROR_NONE,
450                     Field(&DiskMountManager::MountPointInfo::mount_path,
451                           "/device/mount_path")))
452        .Times(1);
453
454    EXPECT_CALL(observer_, OnFormatEvent(DiskMountManager::FORMAT_STARTED,
455                                         chromeos::FORMAT_ERROR_NONE,
456                                         "/device/source_path"))
457        .Times(1);
458
459    EXPECT_CALL(observer_, OnFormatEvent(DiskMountManager::FORMAT_COMPLETED,
460                                         chromeos::FORMAT_ERROR_NONE,
461                                         "/device/source_path"))
462        .Times(1);
463  }
464
465  // Start the test.
466  DiskMountManager::GetInstance()->FormatMountedDevice("/device/mount_path");
467
468  // Wait for Unmount and Format calls to end.
469  message_loop_.RunUntilIdle();
470
471  EXPECT_EQ(1, fake_cros_disks_client_->unmount_call_count());
472  EXPECT_EQ("/device/mount_path",
473            fake_cros_disks_client_->last_unmount_device_path());
474  EXPECT_EQ(chromeos::UNMOUNT_OPTIONS_NONE,
475            fake_cros_disks_client_->last_unmount_options());
476  EXPECT_EQ(1, fake_cros_disks_client_->format_call_count());
477  EXPECT_EQ("/device/source_path",
478            fake_cros_disks_client_->last_format_device_path());
479  EXPECT_EQ("vfat", fake_cros_disks_client_->last_format_filesystem());
480
481  // The device should be unmounted by now.
482  EXPECT_FALSE(HasMountPoint("/device/mount_path"));
483
484  // Simulate cros_disks reporting success.
485  fake_cros_disks_client_->SendFormatCompletedEvent(
486      chromeos::FORMAT_ERROR_NONE, "/device/source_path");
487}
488
489// Tests that it's possible to format the device twice in a row (this may not be
490// true if the list of pending formats is not properly cleared).
491TEST_F(DiskMountManagerTest, Format_ConsecutiveFormatCalls) {
492  // All unmount and format device cals are successful in this test.
493  // Each of the should be made twice (once for each formatting task).
494
495  // Set up expectations for observer mock.
496  // The observer should receive UNMOUNTING, FORMAT_STARTED and FORMAT_COMPLETED
497  // events (all of them without an error set) twice (once for each formatting
498  // task).
499  // Also, there should be a MOUNTING event when the device remounting is
500  // simulated.
501  EXPECT_CALL(observer_, OnFormatEvent(DiskMountManager::FORMAT_COMPLETED,
502                                       chromeos::FORMAT_ERROR_NONE,
503                                       "/device/source_path"))
504      .Times(2);
505
506  EXPECT_CALL(observer_, OnFormatEvent(DiskMountManager::FORMAT_STARTED,
507                                       chromeos::FORMAT_ERROR_NONE,
508                                       "/device/source_path"))
509      .Times(2);
510
511  EXPECT_CALL(observer_,
512      OnMountEvent(DiskMountManager::UNMOUNTING,
513                   chromeos::MOUNT_ERROR_NONE,
514                   Field(&DiskMountManager::MountPointInfo::mount_path,
515                         "/device/mount_path")))
516      .Times(2);
517
518  EXPECT_CALL(observer_,
519      OnMountEvent(DiskMountManager::MOUNTING,
520                   chromeos::MOUNT_ERROR_NONE,
521                   Field(&DiskMountManager::MountPointInfo::mount_path,
522                         "/device/mount_path")))
523      .Times(1);
524
525  // Start the test.
526  DiskMountManager::GetInstance()->FormatMountedDevice("/device/mount_path");
527
528  // Wait for Unmount and Format calls to end.
529  message_loop_.RunUntilIdle();
530
531  EXPECT_EQ(1, fake_cros_disks_client_->unmount_call_count());
532  EXPECT_EQ("/device/mount_path",
533            fake_cros_disks_client_->last_unmount_device_path());
534  EXPECT_EQ(chromeos::UNMOUNT_OPTIONS_NONE,
535            fake_cros_disks_client_->last_unmount_options());
536  EXPECT_EQ(1, fake_cros_disks_client_->format_call_count());
537  EXPECT_EQ("/device/source_path",
538            fake_cros_disks_client_->last_format_device_path());
539  EXPECT_EQ("vfat", fake_cros_disks_client_->last_format_filesystem());
540
541  // The device should be unmounted by now.
542  EXPECT_FALSE(HasMountPoint("/device/mount_path"));
543
544  // Simulate cros_disks reporting success.
545  fake_cros_disks_client_->SendFormatCompletedEvent(
546      chromeos::FORMAT_ERROR_NONE, "/device/source_path");
547
548  // Simulate the device remounting.
549  fake_cros_disks_client_->SendMountCompletedEvent(
550      chromeos::MOUNT_ERROR_NONE,
551      "/device/source_path",
552      chromeos::MOUNT_TYPE_DEVICE,
553      "/device/mount_path");
554
555  EXPECT_TRUE(HasMountPoint("/device/mount_path"));
556
557  // Try formatting again.
558  DiskMountManager::GetInstance()->FormatMountedDevice("/device/mount_path");
559
560  // Wait for Unmount and Format calls to end.
561  message_loop_.RunUntilIdle();
562
563  EXPECT_EQ(2, fake_cros_disks_client_->unmount_call_count());
564  EXPECT_EQ("/device/mount_path",
565            fake_cros_disks_client_->last_unmount_device_path());
566  EXPECT_EQ(chromeos::UNMOUNT_OPTIONS_NONE,
567            fake_cros_disks_client_->last_unmount_options());
568  EXPECT_EQ(2, fake_cros_disks_client_->format_call_count());
569  EXPECT_EQ("/device/source_path",
570            fake_cros_disks_client_->last_format_device_path());
571  EXPECT_EQ("vfat", fake_cros_disks_client_->last_format_filesystem());
572
573  // Simulate cros_disks reporting success.
574  fake_cros_disks_client_->SendFormatCompletedEvent(
575      chromeos::FORMAT_ERROR_NONE, "/device/source_path");
576}
577
578}  // namespace
579