1 // Copyright 2014 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 "components/storage_monitor/storage_monitor_chromeos.h"
7 #include "base/files/file_util.h"
8 #include "base/files/scoped_temp_dir.h"
9 #include "base/logging.h"
10 #include "base/memory/scoped_ptr.h"
11 #include "base/run_loop.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "chromeos/disks/mock_disk_mount_manager.h"
14 #include "components/storage_monitor/mock_removable_storage_observer.h"
15 #include "components/storage_monitor/removable_device_constants.h"
16 #include "components/storage_monitor/storage_info.h"
17 #include "components/storage_monitor/test_media_transfer_protocol_manager_linux.h"
18 #include "components/storage_monitor/test_storage_monitor.h"
19 #include "content/public/browser/browser_thread.h"
20 #include "content/public/test/test_browser_thread_bundle.h"
21 #include "testing/gtest/include/gtest/gtest.h"
23 namespace storage_monitor
{
27 using content::BrowserThread
;
28 using chromeos::disks::DiskMountManager
;
31 const char kDevice1
[] = "/dev/d1";
32 const char kDevice1Name
[] = "d1";
33 const char kDevice2
[] = "/dev/disk/d2";
34 const char kDevice2Name
[] = "d2";
35 const char kEmptyDeviceLabel
[] = "";
36 const char kMountPointA
[] = "mnt_a";
37 const char kMountPointB
[] = "mnt_b";
38 const char kSDCardDeviceName1
[] = "8.6 MB Amy_SD";
39 const char kSDCardDeviceName2
[] = "8.6 MB SD Card";
40 const char kSDCardMountPoint1
[] = "media/removable/Amy_SD";
41 const char kSDCardMountPoint2
[] = "media/removable/SD Card";
42 const char kProductName
[] = "Z101";
43 const char kUniqueId1
[] = "FFFF-FFFF";
44 const char kUniqueId2
[] = "FFFF-FF0F";
45 const char kVendorName
[] = "CompanyA";
47 uint64 kDevice1SizeInBytes
= 113048;
48 uint64 kDevice2SizeInBytes
= 212312;
49 uint64 kSDCardSizeInBytes
= 9000000;
51 std::string
GetDCIMDeviceId(const std::string
& unique_id
) {
52 return StorageInfo::MakeDeviceId(
53 StorageInfo::REMOVABLE_MASS_STORAGE_WITH_DCIM
,
54 kFSUniqueIdPrefix
+ unique_id
);
57 // A test version of StorageMonitorCros that exposes protected methods to tests.
58 class TestStorageMonitorCros
: public StorageMonitorCros
{
60 TestStorageMonitorCros() {}
62 virtual ~TestStorageMonitorCros() {}
64 virtual void Init() override
{
65 SetMediaTransferProtocolManagerForTest(
66 new TestMediaTransferProtocolManagerLinux());
67 StorageMonitorCros::Init();
70 virtual void OnMountEvent(DiskMountManager::MountEvent event
,
71 chromeos::MountError error_code
,
72 const DiskMountManager::MountPointInfo
& mount_info
) override
{
73 StorageMonitorCros::OnMountEvent(event
, error_code
, mount_info
);
76 virtual bool GetStorageInfoForPath(const base::FilePath
& path
,
77 StorageInfo
* device_info
) const override
{
78 return StorageMonitorCros::GetStorageInfoForPath(path
, device_info
);
80 virtual void EjectDevice(
81 const std::string
& device_id
,
82 base::Callback
<void(EjectStatus
)> callback
) override
{
83 StorageMonitorCros::EjectDevice(device_id
, callback
);
87 DISALLOW_COPY_AND_ASSIGN(TestStorageMonitorCros
);
90 // Wrapper class to test StorageMonitorCros.
91 class StorageMonitorCrosTest
: public testing::Test
{
93 StorageMonitorCrosTest();
94 virtual ~StorageMonitorCrosTest();
96 void EjectNotify(StorageMonitor::EjectStatus status
);
100 virtual void SetUp() override
;
101 virtual void TearDown() override
;
103 void MountDevice(chromeos::MountError error_code
,
104 const DiskMountManager::MountPointInfo
& mount_info
,
105 const std::string
& unique_id
,
106 const std::string
& device_label
,
107 const std::string
& vendor_name
,
108 const std::string
& product_name
,
109 chromeos::DeviceType device_type
,
110 uint64 device_size_in_bytes
);
112 void UnmountDevice(chromeos::MountError error_code
,
113 const DiskMountManager::MountPointInfo
& mount_info
);
115 uint64
GetDeviceStorageSize(const std::string
& device_location
);
117 // Create a directory named |dir| relative to the test directory.
118 // Set |with_dcim_dir| to true if the created directory will have a "DCIM"
120 // Returns the full path to the created directory on success, or an empty
122 base::FilePath
CreateMountPoint(const std::string
& dir
, bool with_dcim_dir
);
124 static void PostQuitToUIThread();
125 static void WaitForFileThread();
127 MockRemovableStorageObserver
& observer() {
128 return *mock_storage_observer_
;
131 TestStorageMonitorCros
* monitor_
;
133 // Owned by DiskMountManager.
134 chromeos::disks::MockDiskMountManager
* disk_mount_manager_mock_
;
136 StorageMonitor::EjectStatus status_
;
139 content::TestBrowserThreadBundle thread_bundle_
;
141 // Temporary directory for created test data.
142 base::ScopedTempDir scoped_temp_dir_
;
144 // Objects that talks with StorageMonitorCros.
145 scoped_ptr
<MockRemovableStorageObserver
> mock_storage_observer_
;
147 DISALLOW_COPY_AND_ASSIGN(StorageMonitorCrosTest
);
150 StorageMonitorCrosTest::StorageMonitorCrosTest()
152 disk_mount_manager_mock_(NULL
),
153 status_(StorageMonitor::EJECT_FAILURE
),
154 thread_bundle_(content::TestBrowserThreadBundle::REAL_FILE_THREAD
) {
157 StorageMonitorCrosTest::~StorageMonitorCrosTest() {
160 void StorageMonitorCrosTest::SetUp() {
161 ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::UI
));
162 ASSERT_TRUE(scoped_temp_dir_
.CreateUniqueTempDir());
163 disk_mount_manager_mock_
= new chromeos::disks::MockDiskMountManager();
164 DiskMountManager::InitializeForTesting(disk_mount_manager_mock_
);
165 disk_mount_manager_mock_
->SetupDefaultReplies();
167 mock_storage_observer_
.reset(new MockRemovableStorageObserver
);
169 // Initialize the test subject.
170 TestStorageMonitor::Destroy();
171 monitor_
= new TestStorageMonitorCros();
172 scoped_ptr
<StorageMonitor
> pass_monitor(monitor_
);
173 StorageMonitor::SetStorageMonitorForTesting(pass_monitor
.Pass());
176 monitor_
->AddObserver(mock_storage_observer_
.get());
179 void StorageMonitorCrosTest::TearDown() {
180 monitor_
->RemoveObserver(mock_storage_observer_
.get());
183 disk_mount_manager_mock_
= NULL
;
184 DiskMountManager::Shutdown();
188 void StorageMonitorCrosTest::MountDevice(
189 chromeos::MountError error_code
,
190 const DiskMountManager::MountPointInfo
& mount_info
,
191 const std::string
& unique_id
,
192 const std::string
& device_label
,
193 const std::string
& vendor_name
,
194 const std::string
& product_name
,
195 chromeos::DeviceType device_type
,
196 uint64 device_size_in_bytes
) {
197 if (error_code
== chromeos::MOUNT_ERROR_NONE
) {
198 disk_mount_manager_mock_
->CreateDiskEntryForMountDevice(
205 device_size_in_bytes
,
206 false /* is_parent */,
207 true /* has_media */,
208 false /* on_boot_device */,
209 true /* on_removable_device */);
211 monitor_
->OnMountEvent(DiskMountManager::MOUNTING
, error_code
, mount_info
);
215 void StorageMonitorCrosTest::UnmountDevice(
216 chromeos::MountError error_code
,
217 const DiskMountManager::MountPointInfo
& mount_info
) {
218 monitor_
->OnMountEvent(DiskMountManager::UNMOUNTING
, error_code
, mount_info
);
219 if (error_code
== chromeos::MOUNT_ERROR_NONE
)
220 disk_mount_manager_mock_
->RemoveDiskEntryForMountDevice(mount_info
);
224 uint64
StorageMonitorCrosTest::GetDeviceStorageSize(
225 const std::string
& device_location
) {
227 if (!monitor_
->GetStorageInfoForPath(base::FilePath(device_location
), &info
))
230 return info
.total_size_in_bytes();
233 base::FilePath
StorageMonitorCrosTest::CreateMountPoint(
234 const std::string
& dir
, bool with_dcim_dir
) {
235 base::FilePath
return_path(scoped_temp_dir_
.path());
236 return_path
= return_path
.AppendASCII(dir
);
237 base::FilePath
path(return_path
);
239 path
= path
.Append(kDCIMDirectoryName
);
240 if (!base::CreateDirectory(path
))
241 return base::FilePath();
246 void StorageMonitorCrosTest::PostQuitToUIThread() {
247 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
248 base::MessageLoop::QuitClosure());
252 void StorageMonitorCrosTest::WaitForFileThread() {
253 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE
,
254 base::Bind(&PostQuitToUIThread
));
255 base::MessageLoop::current()->Run();
258 void StorageMonitorCrosTest::EjectNotify(StorageMonitor::EjectStatus status
) {
262 // Simple test case where we attach and detach a media device.
263 TEST_F(StorageMonitorCrosTest
, BasicAttachDetach
) {
264 base::FilePath mount_path1
= CreateMountPoint(kMountPointA
, true);
265 ASSERT_FALSE(mount_path1
.empty());
266 DiskMountManager::MountPointInfo
mount_info(
269 chromeos::MOUNT_TYPE_DEVICE
,
270 chromeos::disks::MOUNT_CONDITION_NONE
);
271 MountDevice(chromeos::MOUNT_ERROR_NONE
,
277 chromeos::DEVICE_TYPE_USB
,
278 kDevice1SizeInBytes
);
279 EXPECT_EQ(1, observer().attach_calls());
280 EXPECT_EQ(0, observer().detach_calls());
281 EXPECT_EQ(GetDCIMDeviceId(kUniqueId1
),
282 observer().last_attached().device_id());
283 EXPECT_EQ(mount_path1
.value(), observer().last_attached().location());
285 UnmountDevice(chromeos::MOUNT_ERROR_NONE
, mount_info
);
286 EXPECT_EQ(1, observer().attach_calls());
287 EXPECT_EQ(1, observer().detach_calls());
288 EXPECT_EQ(GetDCIMDeviceId(kUniqueId1
),
289 observer().last_detached().device_id());
291 base::FilePath mount_path2
= CreateMountPoint(kMountPointB
, true);
292 ASSERT_FALSE(mount_path2
.empty());
293 DiskMountManager::MountPointInfo
mount_info2(
296 chromeos::MOUNT_TYPE_DEVICE
,
297 chromeos::disks::MOUNT_CONDITION_NONE
);
298 MountDevice(chromeos::MOUNT_ERROR_NONE
,
304 chromeos::DEVICE_TYPE_USB
,
305 kDevice2SizeInBytes
);
306 EXPECT_EQ(2, observer().attach_calls());
307 EXPECT_EQ(1, observer().detach_calls());
308 EXPECT_EQ(GetDCIMDeviceId(kUniqueId2
),
309 observer().last_attached().device_id());
310 EXPECT_EQ(mount_path2
.value(), observer().last_attached().location());
312 UnmountDevice(chromeos::MOUNT_ERROR_NONE
, mount_info2
);
313 EXPECT_EQ(2, observer().attach_calls());
314 EXPECT_EQ(2, observer().detach_calls());
315 EXPECT_EQ(GetDCIMDeviceId(kUniqueId2
),
316 observer().last_detached().device_id());
319 // Removable mass storage devices with no dcim folder are also recognized.
320 TEST_F(StorageMonitorCrosTest
, NoDCIM
) {
321 testing::Sequence mock_sequence
;
322 base::FilePath mount_path
= CreateMountPoint(kMountPointA
, false);
323 const std::string kUniqueId
= "FFFF-FFFF";
324 ASSERT_FALSE(mount_path
.empty());
325 DiskMountManager::MountPointInfo
mount_info(
328 chromeos::MOUNT_TYPE_DEVICE
,
329 chromeos::disks::MOUNT_CONDITION_NONE
);
330 const std::string device_id
= StorageInfo::MakeDeviceId(
331 StorageInfo::REMOVABLE_MASS_STORAGE_NO_DCIM
,
332 kFSUniqueIdPrefix
+ kUniqueId
);
333 MountDevice(chromeos::MOUNT_ERROR_NONE
,
339 chromeos::DEVICE_TYPE_USB
,
340 kDevice1SizeInBytes
);
341 EXPECT_EQ(1, observer().attach_calls());
342 EXPECT_EQ(0, observer().detach_calls());
343 EXPECT_EQ(device_id
, observer().last_attached().device_id());
344 EXPECT_EQ(mount_path
.value(), observer().last_attached().location());
347 // Non device mounts and mount errors are ignored.
348 TEST_F(StorageMonitorCrosTest
, Ignore
) {
349 testing::Sequence mock_sequence
;
350 base::FilePath mount_path
= CreateMountPoint(kMountPointA
, true);
351 const std::string kUniqueId
= "FFFF-FFFF";
352 ASSERT_FALSE(mount_path
.empty());
355 DiskMountManager::MountPointInfo
mount_info(
358 chromeos::MOUNT_TYPE_DEVICE
,
359 chromeos::disks::MOUNT_CONDITION_NONE
);
360 MountDevice(chromeos::MOUNT_ERROR_UNKNOWN
,
366 chromeos::DEVICE_TYPE_USB
,
367 kDevice1SizeInBytes
);
368 EXPECT_EQ(0, observer().attach_calls());
369 EXPECT_EQ(0, observer().detach_calls());
372 mount_info
.mount_type
= chromeos::MOUNT_TYPE_ARCHIVE
;
373 MountDevice(chromeos::MOUNT_ERROR_NONE
,
379 chromeos::DEVICE_TYPE_USB
,
380 kDevice1SizeInBytes
);
381 EXPECT_EQ(0, observer().attach_calls());
382 EXPECT_EQ(0, observer().detach_calls());
384 // Unsupported file system.
385 mount_info
.mount_type
= chromeos::MOUNT_TYPE_DEVICE
;
386 mount_info
.mount_condition
=
387 chromeos::disks::MOUNT_CONDITION_UNSUPPORTED_FILESYSTEM
;
388 MountDevice(chromeos::MOUNT_ERROR_NONE
,
394 chromeos::DEVICE_TYPE_USB
,
395 kDevice1SizeInBytes
);
396 EXPECT_EQ(0, observer().attach_calls());
397 EXPECT_EQ(0, observer().detach_calls());
400 TEST_F(StorageMonitorCrosTest
, SDCardAttachDetach
) {
401 base::FilePath mount_path1
= CreateMountPoint(kSDCardMountPoint1
, true);
402 ASSERT_FALSE(mount_path1
.empty());
403 DiskMountManager::MountPointInfo
mount_info1(
406 chromeos::MOUNT_TYPE_DEVICE
,
407 chromeos::disks::MOUNT_CONDITION_NONE
);
408 MountDevice(chromeos::MOUNT_ERROR_NONE
,
414 chromeos::DEVICE_TYPE_SD
,
416 EXPECT_EQ(1, observer().attach_calls());
417 EXPECT_EQ(0, observer().detach_calls());
418 EXPECT_EQ(GetDCIMDeviceId(kUniqueId2
),
419 observer().last_attached().device_id());
420 EXPECT_EQ(mount_path1
.value(), observer().last_attached().location());
422 UnmountDevice(chromeos::MOUNT_ERROR_NONE
, mount_info1
);
423 EXPECT_EQ(1, observer().attach_calls());
424 EXPECT_EQ(1, observer().detach_calls());
425 EXPECT_EQ(GetDCIMDeviceId(kUniqueId2
),
426 observer().last_detached().device_id());
428 base::FilePath mount_path2
= CreateMountPoint(kSDCardMountPoint2
, true);
429 ASSERT_FALSE(mount_path2
.empty());
430 DiskMountManager::MountPointInfo
mount_info2(
433 chromeos::MOUNT_TYPE_DEVICE
,
434 chromeos::disks::MOUNT_CONDITION_NONE
);
435 MountDevice(chromeos::MOUNT_ERROR_NONE
,
441 chromeos::DEVICE_TYPE_SD
,
443 EXPECT_EQ(2, observer().attach_calls());
444 EXPECT_EQ(1, observer().detach_calls());
445 EXPECT_EQ(GetDCIMDeviceId(kUniqueId2
),
446 observer().last_attached().device_id());
447 EXPECT_EQ(mount_path2
.value(), observer().last_attached().location());
449 UnmountDevice(chromeos::MOUNT_ERROR_NONE
, mount_info2
);
450 EXPECT_EQ(2, observer().attach_calls());
451 EXPECT_EQ(2, observer().detach_calls());
452 EXPECT_EQ(GetDCIMDeviceId(kUniqueId2
),
453 observer().last_detached().device_id());
456 TEST_F(StorageMonitorCrosTest
, AttachDeviceWithEmptyLabel
) {
457 base::FilePath mount_path1
= CreateMountPoint(kMountPointA
, true);
458 ASSERT_FALSE(mount_path1
.empty());
459 DiskMountManager::MountPointInfo
mount_info(
462 chromeos::MOUNT_TYPE_DEVICE
,
463 chromeos::disks::MOUNT_CONDITION_NONE
);
464 MountDevice(chromeos::MOUNT_ERROR_NONE
,
470 chromeos::DEVICE_TYPE_USB
,
471 kDevice1SizeInBytes
);
472 EXPECT_EQ(1, observer().attach_calls());
473 EXPECT_EQ(0, observer().detach_calls());
474 EXPECT_EQ(GetDCIMDeviceId(kUniqueId1
),
475 observer().last_attached().device_id());
476 EXPECT_EQ(mount_path1
.value(), observer().last_attached().location());
478 UnmountDevice(chromeos::MOUNT_ERROR_NONE
, mount_info
);
479 EXPECT_EQ(1, observer().attach_calls());
480 EXPECT_EQ(1, observer().detach_calls());
481 EXPECT_EQ(GetDCIMDeviceId(kUniqueId1
),
482 observer().last_detached().device_id());
485 TEST_F(StorageMonitorCrosTest
, GetStorageSize
) {
486 base::FilePath mount_path1
= CreateMountPoint(kMountPointA
, true);
487 ASSERT_FALSE(mount_path1
.empty());
488 DiskMountManager::MountPointInfo
mount_info(
491 chromeos::MOUNT_TYPE_DEVICE
,
492 chromeos::disks::MOUNT_CONDITION_NONE
);
493 MountDevice(chromeos::MOUNT_ERROR_NONE
,
499 chromeos::DEVICE_TYPE_USB
,
500 kDevice1SizeInBytes
);
501 EXPECT_EQ(1, observer().attach_calls());
502 EXPECT_EQ(0, observer().detach_calls());
503 EXPECT_EQ(GetDCIMDeviceId(kUniqueId1
),
504 observer().last_attached().device_id());
505 EXPECT_EQ(mount_path1
.value(), observer().last_attached().location());
507 EXPECT_EQ(kDevice1SizeInBytes
, GetDeviceStorageSize(mount_path1
.value()));
508 UnmountDevice(chromeos::MOUNT_ERROR_NONE
, mount_info
);
509 EXPECT_EQ(1, observer().attach_calls());
510 EXPECT_EQ(1, observer().detach_calls());
511 EXPECT_EQ(GetDCIMDeviceId(kUniqueId1
),
512 observer().last_detached().device_id());
515 void UnmountFake(const std::string
& location
,
516 chromeos::UnmountOptions options
,
517 const DiskMountManager::UnmountPathCallback
& cb
) {
518 cb
.Run(chromeos::MOUNT_ERROR_NONE
);
521 TEST_F(StorageMonitorCrosTest
, EjectTest
) {
522 base::FilePath mount_path1
= CreateMountPoint(kMountPointA
, true);
523 ASSERT_FALSE(mount_path1
.empty());
524 DiskMountManager::MountPointInfo
mount_info(
527 chromeos::MOUNT_TYPE_DEVICE
,
528 chromeos::disks::MOUNT_CONDITION_NONE
);
529 MountDevice(chromeos::MOUNT_ERROR_NONE
,
535 chromeos::DEVICE_TYPE_USB
,
536 kDevice1SizeInBytes
);
537 EXPECT_EQ(1, observer().attach_calls());
538 EXPECT_EQ(0, observer().detach_calls());
540 ON_CALL(*disk_mount_manager_mock_
, UnmountPath(_
, _
, _
))
541 .WillByDefault(testing::Invoke(&UnmountFake
));
542 EXPECT_CALL(*disk_mount_manager_mock_
,
543 UnmountPath(observer().last_attached().location(), _
, _
));
544 monitor_
->EjectDevice(observer().last_attached().device_id(),
545 base::Bind(&StorageMonitorCrosTest::EjectNotify
,
546 base::Unretained(this)));
547 base::RunLoop().RunUntilIdle();
549 EXPECT_EQ(StorageMonitor::EJECT_OK
, status_
);
554 } // namespace storage_monitor