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/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(
199 mount_info, unique_id, device_label, vendor_name, product_name,
200 device_type, device_size_in_bytes);
202 monitor_->OnMountEvent(DiskMountManager::MOUNTING, error_code, mount_info);
206 void StorageMonitorCrosTest::UnmountDevice(
207 chromeos::MountError error_code,
208 const DiskMountManager::MountPointInfo& mount_info) {
209 monitor_->OnMountEvent(DiskMountManager::UNMOUNTING, error_code, mount_info);
210 if (error_code == chromeos::MOUNT_ERROR_NONE)
211 disk_mount_manager_mock_->RemoveDiskEntryForMountDevice(mount_info);
215 uint64 StorageMonitorCrosTest::GetDeviceStorageSize(
216 const std::string& device_location) {
218 if (!monitor_->GetStorageInfoForPath(base::FilePath(device_location), &info))
221 return info.total_size_in_bytes();
224 base::FilePath StorageMonitorCrosTest::CreateMountPoint(
225 const std::string& dir, bool with_dcim_dir) {
226 base::FilePath return_path(scoped_temp_dir_.path());
227 return_path = return_path.AppendASCII(dir);
228 base::FilePath path(return_path);
230 path = path.Append(kDCIMDirectoryName);
231 if (!base::CreateDirectory(path))
232 return base::FilePath();
237 void StorageMonitorCrosTest::PostQuitToUIThread() {
238 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
239 base::MessageLoop::QuitClosure());
243 void StorageMonitorCrosTest::WaitForFileThread() {
244 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
245 base::Bind(&PostQuitToUIThread));
246 base::MessageLoop::current()->Run();
249 void StorageMonitorCrosTest::EjectNotify(StorageMonitor::EjectStatus status) {
253 // Simple test case where we attach and detach a media device.
254 TEST_F(StorageMonitorCrosTest, BasicAttachDetach) {
255 base::FilePath mount_path1 = CreateMountPoint(kMountPointA, true);
256 ASSERT_FALSE(mount_path1.empty());
257 DiskMountManager::MountPointInfo mount_info(
260 chromeos::MOUNT_TYPE_DEVICE,
261 chromeos::disks::MOUNT_CONDITION_NONE);
262 MountDevice(chromeos::MOUNT_ERROR_NONE,
268 chromeos::DEVICE_TYPE_USB,
269 kDevice1SizeInBytes);
270 EXPECT_EQ(1, observer().attach_calls());
271 EXPECT_EQ(0, observer().detach_calls());
272 EXPECT_EQ(GetDCIMDeviceId(kUniqueId1),
273 observer().last_attached().device_id());
274 EXPECT_EQ(mount_path1.value(), observer().last_attached().location());
276 UnmountDevice(chromeos::MOUNT_ERROR_NONE, mount_info);
277 EXPECT_EQ(1, observer().attach_calls());
278 EXPECT_EQ(1, observer().detach_calls());
279 EXPECT_EQ(GetDCIMDeviceId(kUniqueId1),
280 observer().last_detached().device_id());
282 base::FilePath mount_path2 = CreateMountPoint(kMountPointB, true);
283 ASSERT_FALSE(mount_path2.empty());
284 DiskMountManager::MountPointInfo mount_info2(
287 chromeos::MOUNT_TYPE_DEVICE,
288 chromeos::disks::MOUNT_CONDITION_NONE);
289 MountDevice(chromeos::MOUNT_ERROR_NONE,
295 chromeos::DEVICE_TYPE_USB,
296 kDevice2SizeInBytes);
297 EXPECT_EQ(2, observer().attach_calls());
298 EXPECT_EQ(1, observer().detach_calls());
299 EXPECT_EQ(GetDCIMDeviceId(kUniqueId2),
300 observer().last_attached().device_id());
301 EXPECT_EQ(mount_path2.value(), observer().last_attached().location());
303 UnmountDevice(chromeos::MOUNT_ERROR_NONE, mount_info2);
304 EXPECT_EQ(2, observer().attach_calls());
305 EXPECT_EQ(2, observer().detach_calls());
306 EXPECT_EQ(GetDCIMDeviceId(kUniqueId2),
307 observer().last_detached().device_id());
310 // Removable mass storage devices with no dcim folder are also recognized.
311 TEST_F(StorageMonitorCrosTest, NoDCIM) {
312 testing::Sequence mock_sequence;
313 base::FilePath mount_path = CreateMountPoint(kMountPointA, false);
314 const std::string kUniqueId = "FFFF-FFFF";
315 ASSERT_FALSE(mount_path.empty());
316 DiskMountManager::MountPointInfo mount_info(
319 chromeos::MOUNT_TYPE_DEVICE,
320 chromeos::disks::MOUNT_CONDITION_NONE);
321 const std::string device_id = StorageInfo::MakeDeviceId(
322 StorageInfo::REMOVABLE_MASS_STORAGE_NO_DCIM,
323 kFSUniqueIdPrefix + kUniqueId);
324 MountDevice(chromeos::MOUNT_ERROR_NONE,
330 chromeos::DEVICE_TYPE_USB,
331 kDevice1SizeInBytes);
332 EXPECT_EQ(1, observer().attach_calls());
333 EXPECT_EQ(0, observer().detach_calls());
334 EXPECT_EQ(device_id, observer().last_attached().device_id());
335 EXPECT_EQ(mount_path.value(), observer().last_attached().location());
338 // Non device mounts and mount errors are ignored.
339 TEST_F(StorageMonitorCrosTest, Ignore) {
340 testing::Sequence mock_sequence;
341 base::FilePath mount_path = CreateMountPoint(kMountPointA, true);
342 const std::string kUniqueId = "FFFF-FFFF";
343 ASSERT_FALSE(mount_path.empty());
346 DiskMountManager::MountPointInfo mount_info(
349 chromeos::MOUNT_TYPE_DEVICE,
350 chromeos::disks::MOUNT_CONDITION_NONE);
351 MountDevice(chromeos::MOUNT_ERROR_UNKNOWN,
357 chromeos::DEVICE_TYPE_USB,
358 kDevice1SizeInBytes);
359 EXPECT_EQ(0, observer().attach_calls());
360 EXPECT_EQ(0, observer().detach_calls());
363 mount_info.mount_type = chromeos::MOUNT_TYPE_ARCHIVE;
364 MountDevice(chromeos::MOUNT_ERROR_NONE,
370 chromeos::DEVICE_TYPE_USB,
371 kDevice1SizeInBytes);
372 EXPECT_EQ(0, observer().attach_calls());
373 EXPECT_EQ(0, observer().detach_calls());
375 // Unsupported file system.
376 mount_info.mount_type = chromeos::MOUNT_TYPE_DEVICE;
377 mount_info.mount_condition =
378 chromeos::disks::MOUNT_CONDITION_UNSUPPORTED_FILESYSTEM;
379 MountDevice(chromeos::MOUNT_ERROR_NONE,
385 chromeos::DEVICE_TYPE_USB,
386 kDevice1SizeInBytes);
387 EXPECT_EQ(0, observer().attach_calls());
388 EXPECT_EQ(0, observer().detach_calls());
391 TEST_F(StorageMonitorCrosTest, SDCardAttachDetach) {
392 base::FilePath mount_path1 = CreateMountPoint(kSDCardMountPoint1, true);
393 ASSERT_FALSE(mount_path1.empty());
394 DiskMountManager::MountPointInfo mount_info1(
397 chromeos::MOUNT_TYPE_DEVICE,
398 chromeos::disks::MOUNT_CONDITION_NONE);
399 MountDevice(chromeos::MOUNT_ERROR_NONE,
405 chromeos::DEVICE_TYPE_SD,
407 EXPECT_EQ(1, observer().attach_calls());
408 EXPECT_EQ(0, observer().detach_calls());
409 EXPECT_EQ(GetDCIMDeviceId(kUniqueId2),
410 observer().last_attached().device_id());
411 EXPECT_EQ(mount_path1.value(), observer().last_attached().location());
413 UnmountDevice(chromeos::MOUNT_ERROR_NONE, mount_info1);
414 EXPECT_EQ(1, observer().attach_calls());
415 EXPECT_EQ(1, observer().detach_calls());
416 EXPECT_EQ(GetDCIMDeviceId(kUniqueId2),
417 observer().last_detached().device_id());
419 base::FilePath mount_path2 = CreateMountPoint(kSDCardMountPoint2, true);
420 ASSERT_FALSE(mount_path2.empty());
421 DiskMountManager::MountPointInfo mount_info2(
424 chromeos::MOUNT_TYPE_DEVICE,
425 chromeos::disks::MOUNT_CONDITION_NONE);
426 MountDevice(chromeos::MOUNT_ERROR_NONE,
432 chromeos::DEVICE_TYPE_SD,
434 EXPECT_EQ(2, observer().attach_calls());
435 EXPECT_EQ(1, observer().detach_calls());
436 EXPECT_EQ(GetDCIMDeviceId(kUniqueId2),
437 observer().last_attached().device_id());
438 EXPECT_EQ(mount_path2.value(), observer().last_attached().location());
440 UnmountDevice(chromeos::MOUNT_ERROR_NONE, mount_info2);
441 EXPECT_EQ(2, observer().attach_calls());
442 EXPECT_EQ(2, observer().detach_calls());
443 EXPECT_EQ(GetDCIMDeviceId(kUniqueId2),
444 observer().last_detached().device_id());
447 TEST_F(StorageMonitorCrosTest, AttachDeviceWithEmptyLabel) {
448 base::FilePath mount_path1 = CreateMountPoint(kMountPointA, true);
449 ASSERT_FALSE(mount_path1.empty());
450 DiskMountManager::MountPointInfo mount_info(
453 chromeos::MOUNT_TYPE_DEVICE,
454 chromeos::disks::MOUNT_CONDITION_NONE);
455 MountDevice(chromeos::MOUNT_ERROR_NONE,
461 chromeos::DEVICE_TYPE_USB,
462 kDevice1SizeInBytes);
463 EXPECT_EQ(1, observer().attach_calls());
464 EXPECT_EQ(0, observer().detach_calls());
465 EXPECT_EQ(GetDCIMDeviceId(kUniqueId1),
466 observer().last_attached().device_id());
467 EXPECT_EQ(mount_path1.value(), observer().last_attached().location());
469 UnmountDevice(chromeos::MOUNT_ERROR_NONE, mount_info);
470 EXPECT_EQ(1, observer().attach_calls());
471 EXPECT_EQ(1, observer().detach_calls());
472 EXPECT_EQ(GetDCIMDeviceId(kUniqueId1),
473 observer().last_detached().device_id());
476 TEST_F(StorageMonitorCrosTest, GetStorageSize) {
477 base::FilePath mount_path1 = CreateMountPoint(kMountPointA, true);
478 ASSERT_FALSE(mount_path1.empty());
479 DiskMountManager::MountPointInfo mount_info(
482 chromeos::MOUNT_TYPE_DEVICE,
483 chromeos::disks::MOUNT_CONDITION_NONE);
484 MountDevice(chromeos::MOUNT_ERROR_NONE,
490 chromeos::DEVICE_TYPE_USB,
491 kDevice1SizeInBytes);
492 EXPECT_EQ(1, observer().attach_calls());
493 EXPECT_EQ(0, observer().detach_calls());
494 EXPECT_EQ(GetDCIMDeviceId(kUniqueId1),
495 observer().last_attached().device_id());
496 EXPECT_EQ(mount_path1.value(), observer().last_attached().location());
498 EXPECT_EQ(kDevice1SizeInBytes, GetDeviceStorageSize(mount_path1.value()));
499 UnmountDevice(chromeos::MOUNT_ERROR_NONE, mount_info);
500 EXPECT_EQ(1, observer().attach_calls());
501 EXPECT_EQ(1, observer().detach_calls());
502 EXPECT_EQ(GetDCIMDeviceId(kUniqueId1),
503 observer().last_detached().device_id());
506 void UnmountFake(const std::string& location,
507 chromeos::UnmountOptions options,
508 const DiskMountManager::UnmountPathCallback& cb) {
509 cb.Run(chromeos::MOUNT_ERROR_NONE);
512 TEST_F(StorageMonitorCrosTest, EjectTest) {
513 base::FilePath mount_path1 = CreateMountPoint(kMountPointA, true);
514 ASSERT_FALSE(mount_path1.empty());
515 DiskMountManager::MountPointInfo mount_info(
518 chromeos::MOUNT_TYPE_DEVICE,
519 chromeos::disks::MOUNT_CONDITION_NONE);
520 MountDevice(chromeos::MOUNT_ERROR_NONE,
526 chromeos::DEVICE_TYPE_USB,
527 kDevice1SizeInBytes);
528 EXPECT_EQ(1, observer().attach_calls());
529 EXPECT_EQ(0, observer().detach_calls());
531 ON_CALL(*disk_mount_manager_mock_, UnmountPath(_, _, _))
532 .WillByDefault(testing::Invoke(&UnmountFake));
533 EXPECT_CALL(*disk_mount_manager_mock_,
534 UnmountPath(observer().last_attached().location(), _, _));
535 monitor_->EjectDevice(observer().last_attached().device_id(),
536 base::Bind(&StorageMonitorCrosTest::EjectNotify,
537 base::Unretained(this)));
538 base::RunLoop().RunUntilIdle();
540 EXPECT_EQ(StorageMonitor::EJECT_OK, status_);
545 } // namespace storage_monitor