• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4 
5 // StorageMonitorLinux unit tests.
6 
7 #include "components/storage_monitor/storage_monitor_linux.h"
8 
9 #include <mntent.h>
10 #include <stdio.h>
11 
12 #include <string>
13 
14 #include "base/file_util.h"
15 #include "base/files/scoped_temp_dir.h"
16 #include "base/logging.h"
17 #include "base/memory/scoped_ptr.h"
18 #include "base/run_loop.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "components/storage_monitor/mock_removable_storage_observer.h"
21 #include "components/storage_monitor/removable_device_constants.h"
22 #include "components/storage_monitor/storage_info.h"
23 #include "components/storage_monitor/storage_monitor.h"
24 #include "components/storage_monitor/test_media_transfer_protocol_manager_linux.h"
25 #include "components/storage_monitor/test_storage_monitor.h"
26 #include "content/public/test/test_browser_thread_bundle.h"
27 #include "testing/gtest/include/gtest/gtest.h"
28 
29 namespace storage_monitor {
30 
31 namespace {
32 
33 const char kValidFS[] = "vfat";
34 const char kInvalidFS[] = "invalidfs";
35 
36 const char kInvalidPath[] = "invalid path does not exist";
37 
38 const char kDeviceDCIM1[] = "d1";
39 const char kDeviceDCIM2[] = "d2";
40 const char kDeviceDCIM3[] = "d3";
41 const char kDeviceNoDCIM[] = "d4";
42 const char kDeviceFixed[] = "d5";
43 
44 const char kInvalidDevice[] = "invalid_device";
45 
46 const char kMountPointA[] = "mnt_a";
47 const char kMountPointB[] = "mnt_b";
48 const char kMountPointC[] = "mnt_c";
49 
50 struct TestDeviceData {
51   const char* device_path;
52   const char* unique_id;
53   StorageInfo::Type type;
54   uint64 partition_size_in_bytes;
55 };
56 
57 const TestDeviceData kTestDeviceData[] = {
58   { kDeviceDCIM1, "UUID:FFF0-000F",
59     StorageInfo::REMOVABLE_MASS_STORAGE_WITH_DCIM, 88788 },
60   { kDeviceDCIM2, "VendorModelSerial:ComName:Model2010:8989",
61     StorageInfo::REMOVABLE_MASS_STORAGE_WITH_DCIM,
62     8773 },
63   { kDeviceDCIM3, "VendorModelSerial:::WEM319X792",
64     StorageInfo::REMOVABLE_MASS_STORAGE_WITH_DCIM, 22837 },
65   { kDeviceNoDCIM, "UUID:ABCD-1234",
66     StorageInfo::REMOVABLE_MASS_STORAGE_NO_DCIM, 512 },
67   { kDeviceFixed, "UUID:743A-2349",
68     StorageInfo::FIXED_MASS_STORAGE, 17282 },
69 };
70 
GetDeviceInfo(const base::FilePath & device_path,const base::FilePath & mount_point)71 scoped_ptr<StorageInfo> GetDeviceInfo(const base::FilePath& device_path,
72                                       const base::FilePath& mount_point) {
73   bool device_found = false;
74   size_t i = 0;
75   for (; i < arraysize(kTestDeviceData); i++) {
76     if (device_path.value() == kTestDeviceData[i].device_path) {
77       device_found = true;
78       break;
79     }
80   }
81 
82   scoped_ptr<StorageInfo> storage_info;
83   if (!device_found) {
84     NOTREACHED();
85     return storage_info.Pass();
86   }
87 
88   StorageInfo::Type type = kTestDeviceData[i].type;
89   storage_info.reset(new StorageInfo(
90       StorageInfo::MakeDeviceId(type, kTestDeviceData[i].unique_id),
91       mount_point.value(),
92       base::ASCIIToUTF16("volume label"),
93       base::ASCIIToUTF16("vendor name"),
94       base::ASCIIToUTF16("model name"),
95       kTestDeviceData[i].partition_size_in_bytes));
96   return storage_info.Pass();
97 }
98 
GetDevicePartitionSize(const std::string & device)99 uint64 GetDevicePartitionSize(const std::string& device) {
100   for (size_t i = 0; i < arraysize(kTestDeviceData); ++i) {
101     if (device == kTestDeviceData[i].device_path)
102       return kTestDeviceData[i].partition_size_in_bytes;
103   }
104   return 0;
105 }
106 
GetDeviceId(const std::string & device)107 std::string GetDeviceId(const std::string& device) {
108   for (size_t i = 0; i < arraysize(kTestDeviceData); ++i) {
109     if (device == kTestDeviceData[i].device_path) {
110       return StorageInfo::MakeDeviceId(kTestDeviceData[i].type,
111                                             kTestDeviceData[i].unique_id);
112     }
113   }
114   if (device == kInvalidDevice) {
115     return StorageInfo::MakeDeviceId(StorageInfo::FIXED_MASS_STORAGE,
116                                           kInvalidDevice);
117   }
118   return std::string();
119 }
120 
121 class TestStorageMonitorLinux : public StorageMonitorLinux {
122  public:
TestStorageMonitorLinux(const base::FilePath & path)123   explicit TestStorageMonitorLinux(const base::FilePath& path)
124       : StorageMonitorLinux(path) {
125     SetMediaTransferProtocolManagerForTest(
126         new TestMediaTransferProtocolManagerLinux());
127     SetGetDeviceInfoCallbackForTest(base::Bind(&GetDeviceInfo));
128   }
~TestStorageMonitorLinux()129   virtual ~TestStorageMonitorLinux() {}
130 
131  private:
UpdateMtab(const MtabWatcherLinux::MountPointDeviceMap & new_mtab)132   virtual void UpdateMtab(
133       const MtabWatcherLinux::MountPointDeviceMap& new_mtab) OVERRIDE {
134     StorageMonitorLinux::UpdateMtab(new_mtab);
135     base::MessageLoopProxy::current()->PostTask(
136         FROM_HERE, base::MessageLoop::QuitClosure());
137   }
138 
139   DISALLOW_COPY_AND_ASSIGN(TestStorageMonitorLinux);
140 };
141 
142 class StorageMonitorLinuxTest : public testing::Test {
143  public:
144   struct MtabTestData {
MtabTestDatastorage_monitor::__anon15a8b1cd0111::StorageMonitorLinuxTest::MtabTestData145     MtabTestData(const std::string& mount_device,
146                  const std::string& mount_point,
147                  const std::string& mount_type)
148         : mount_device(mount_device),
149           mount_point(mount_point),
150           mount_type(mount_type) {
151     }
152 
153     const std::string mount_device;
154     const std::string mount_point;
155     const std::string mount_type;
156   };
157 
StorageMonitorLinuxTest()158   StorageMonitorLinuxTest()
159       : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP) {}
~StorageMonitorLinuxTest()160   virtual ~StorageMonitorLinuxTest() {}
161 
162  protected:
SetUp()163   virtual void SetUp() OVERRIDE {
164     // Create and set up a temp dir with files for the test.
165     ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir());
166     base::FilePath test_dir = scoped_temp_dir_.path().AppendASCII("test_etc");
167     ASSERT_TRUE(base::CreateDirectory(test_dir));
168     mtab_file_ = test_dir.AppendASCII("test_mtab");
169     MtabTestData initial_test_data[] = {
170       MtabTestData("dummydevice", "dummydir", kInvalidFS),
171     };
172     WriteToMtab(initial_test_data,
173                 arraysize(initial_test_data),
174                 true  /* overwrite */);
175 
176     monitor_.reset(new TestStorageMonitorLinux(mtab_file_));
177 
178     mock_storage_observer_.reset(new MockRemovableStorageObserver);
179     monitor_->AddObserver(mock_storage_observer_.get());
180 
181     monitor_->Init();
182     base::RunLoop().RunUntilIdle();
183   }
184 
TearDown()185   virtual void TearDown() OVERRIDE {
186     base::RunLoop().RunUntilIdle();
187     monitor_->RemoveObserver(mock_storage_observer_.get());
188     base::RunLoop().RunUntilIdle();
189 
190     // Linux storage monitor must be destroyed on the UI thread, so do it here.
191     monitor_.reset();
192   }
193 
194   // Append mtab entries from the |data| array of size |data_size| to the mtab
195   // file, and run the message loop.
AppendToMtabAndRunLoop(const MtabTestData * data,size_t data_size)196   void AppendToMtabAndRunLoop(const MtabTestData* data, size_t data_size) {
197     WriteToMtab(data, data_size, false  /* do not overwrite */);
198     base::RunLoop().Run();
199   }
200 
201   // Overwrite the mtab file with mtab entries from the |data| array of size
202   // |data_size|, and run the message loop.
OverwriteMtabAndRunLoop(const MtabTestData * data,size_t data_size)203   void OverwriteMtabAndRunLoop(const MtabTestData* data, size_t data_size) {
204     WriteToMtab(data, data_size, true  /* overwrite */);
205     base::RunLoop().Run();
206   }
207 
208   // Simplied version of OverwriteMtabAndRunLoop() that just deletes all the
209   // entries in the mtab file.
WriteEmptyMtabAndRunLoop()210   void WriteEmptyMtabAndRunLoop() {
211     OverwriteMtabAndRunLoop(NULL,  // No data.
212                             0);    // No data length.
213   }
214 
215   // Create a directory named |dir| relative to the test directory.
216   // It has a DCIM directory, so StorageMonitorLinux recognizes it as a media
217   // directory.
CreateMountPointWithDCIMDir(const std::string & dir)218   base::FilePath CreateMountPointWithDCIMDir(const std::string& dir) {
219     return CreateMountPoint(dir, true  /* create DCIM dir */);
220   }
221 
222   // Create a directory named |dir| relative to the test directory.
223   // It does not have a DCIM directory, so StorageMonitorLinux does not
224   // recognize it as a media directory.
CreateMountPointWithoutDCIMDir(const std::string & dir)225   base::FilePath CreateMountPointWithoutDCIMDir(const std::string& dir) {
226     return CreateMountPoint(dir, false  /* do not create DCIM dir */);
227   }
228 
RemoveDCIMDirFromMountPoint(const std::string & dir)229   void RemoveDCIMDirFromMountPoint(const std::string& dir) {
230     base::FilePath dcim =
231         scoped_temp_dir_.path().AppendASCII(dir).Append(kDCIMDirectoryName);
232     base::DeleteFile(dcim, false);
233   }
234 
observer()235   MockRemovableStorageObserver& observer() {
236     return *mock_storage_observer_;
237   }
238 
notifier()239   StorageMonitor* notifier() {
240     return monitor_.get();
241   }
242 
GetStorageSize(const base::FilePath & path)243   uint64 GetStorageSize(const base::FilePath& path) {
244     StorageInfo info;
245     if (!notifier()->GetStorageInfoForPath(path, &info))
246       return 0;
247 
248     return info.total_size_in_bytes();
249   }
250 
251  private:
252   // Create a directory named |dir| relative to the test directory.
253   // Set |with_dcim_dir| to true if the created directory will have a "DCIM"
254   // subdirectory.
255   // Returns the full path to the created directory on success, or an empty
256   // path on failure.
CreateMountPoint(const std::string & dir,bool with_dcim_dir)257   base::FilePath CreateMountPoint(const std::string& dir, bool with_dcim_dir) {
258     base::FilePath return_path(scoped_temp_dir_.path());
259     return_path = return_path.AppendASCII(dir);
260     base::FilePath path(return_path);
261     if (with_dcim_dir)
262       path = path.Append(kDCIMDirectoryName);
263     if (!base::CreateDirectory(path))
264       return base::FilePath();
265     return return_path;
266   }
267 
268   // Write the test mtab data to |mtab_file_|.
269   // |data| is an array of mtab entries.
270   // |data_size| is the array size of |data|.
271   // |overwrite| specifies whether to overwrite |mtab_file_|.
WriteToMtab(const MtabTestData * data,size_t data_size,bool overwrite)272   void WriteToMtab(const MtabTestData* data,
273                    size_t data_size,
274                    bool overwrite) {
275     FILE* file = setmntent(mtab_file_.value().c_str(), overwrite ? "w" : "a");
276     ASSERT_TRUE(file);
277 
278     // Due to the glibc *mntent() interface design, which is out of our
279     // control, the mtnent struct has several char* fields, even though
280     // addmntent() does not write to them in the calls below. To make the
281     // compiler happy while avoiding making additional copies of strings,
282     // we just const_cast() the strings' c_str()s.
283     // Assuming addmntent() does not write to the char* fields, this is safe.
284     // It is unlikely the platforms this test suite runs on will have an
285     // addmntent() implementation that does change the char* fields. If that
286     // was ever the case, the test suite will start crashing or failing.
287     mntent entry;
288     static const char kMountOpts[] = "rw";
289     entry.mnt_opts = const_cast<char*>(kMountOpts);
290     entry.mnt_freq = 0;
291     entry.mnt_passno = 0;
292     for (size_t i = 0; i < data_size; ++i) {
293       entry.mnt_fsname = const_cast<char*>(data[i].mount_device.c_str());
294       entry.mnt_dir = const_cast<char*>(data[i].mount_point.c_str());
295       entry.mnt_type = const_cast<char*>(data[i].mount_type.c_str());
296       ASSERT_EQ(0, addmntent(file, &entry));
297     }
298     ASSERT_EQ(1, endmntent(file));
299   }
300 
301   content::TestBrowserThreadBundle thread_bundle_;
302 
303   scoped_ptr<MockRemovableStorageObserver> mock_storage_observer_;
304 
305   // Temporary directory for created test data.
306   base::ScopedTempDir scoped_temp_dir_;
307   // Path to the test mtab file.
308   base::FilePath mtab_file_;
309 
310   scoped_ptr<TestStorageMonitorLinux> monitor_;
311 
312   DISALLOW_COPY_AND_ASSIGN(StorageMonitorLinuxTest);
313 };
314 
315 // Simple test case where we attach and detach a media device.
TEST_F(StorageMonitorLinuxTest,BasicAttachDetach)316 TEST_F(StorageMonitorLinuxTest, BasicAttachDetach) {
317   base::FilePath test_path = CreateMountPointWithDCIMDir(kMountPointA);
318   ASSERT_FALSE(test_path.empty());
319   MtabTestData test_data[] = {
320     MtabTestData(kDeviceDCIM2, test_path.value(), kValidFS),
321     MtabTestData(kDeviceFixed, kInvalidPath, kValidFS),
322   };
323   // Only |kDeviceDCIM2| should be attached, since |kDeviceFixed| has a bad
324   // path.
325   AppendToMtabAndRunLoop(test_data, arraysize(test_data));
326 
327   EXPECT_EQ(1, observer().attach_calls());
328   EXPECT_EQ(0, observer().detach_calls());
329   EXPECT_EQ(GetDeviceId(kDeviceDCIM2), observer().last_attached().device_id());
330   EXPECT_EQ(test_path.value(), observer().last_attached().location());
331 
332   // |kDeviceDCIM2| should be detached here.
333   WriteEmptyMtabAndRunLoop();
334   EXPECT_EQ(1, observer().attach_calls());
335   EXPECT_EQ(1, observer().detach_calls());
336   EXPECT_EQ(GetDeviceId(kDeviceDCIM2), observer().last_detached().device_id());
337 }
338 
339 // Only removable devices are recognized.
TEST_F(StorageMonitorLinuxTest,Removable)340 TEST_F(StorageMonitorLinuxTest, Removable) {
341   base::FilePath test_path_a = CreateMountPointWithDCIMDir(kMountPointA);
342   ASSERT_FALSE(test_path_a.empty());
343   MtabTestData test_data1[] = {
344     MtabTestData(kDeviceDCIM1, test_path_a.value(), kValidFS),
345   };
346   // |kDeviceDCIM1| should be attached as expected.
347   AppendToMtabAndRunLoop(test_data1, arraysize(test_data1));
348 
349   EXPECT_EQ(1, observer().attach_calls());
350   EXPECT_EQ(0, observer().detach_calls());
351   EXPECT_EQ(GetDeviceId(kDeviceDCIM1), observer().last_attached().device_id());
352   EXPECT_EQ(test_path_a.value(), observer().last_attached().location());
353 
354   // This should do nothing, since |kDeviceFixed| is not removable.
355   base::FilePath test_path_b = CreateMountPointWithoutDCIMDir(kMountPointB);
356   ASSERT_FALSE(test_path_b.empty());
357   MtabTestData test_data2[] = {
358     MtabTestData(kDeviceFixed, test_path_b.value(), kValidFS),
359   };
360   AppendToMtabAndRunLoop(test_data2, arraysize(test_data2));
361   EXPECT_EQ(1, observer().attach_calls());
362   EXPECT_EQ(0, observer().detach_calls());
363 
364   // |kDeviceDCIM1| should be detached as expected.
365   WriteEmptyMtabAndRunLoop();
366   EXPECT_EQ(1, observer().attach_calls());
367   EXPECT_EQ(1, observer().detach_calls());
368   EXPECT_EQ(GetDeviceId(kDeviceDCIM1), observer().last_detached().device_id());
369 
370   // |kDeviceNoDCIM| should be attached as expected.
371   MtabTestData test_data3[] = {
372     MtabTestData(kDeviceNoDCIM, test_path_b.value(), kValidFS),
373   };
374   AppendToMtabAndRunLoop(test_data3, arraysize(test_data3));
375   EXPECT_EQ(2, observer().attach_calls());
376   EXPECT_EQ(1, observer().detach_calls());
377   EXPECT_EQ(GetDeviceId(kDeviceNoDCIM), observer().last_attached().device_id());
378   EXPECT_EQ(test_path_b.value(), observer().last_attached().location());
379 
380   // |kDeviceNoDCIM| should be detached as expected.
381   WriteEmptyMtabAndRunLoop();
382   EXPECT_EQ(2, observer().attach_calls());
383   EXPECT_EQ(2, observer().detach_calls());
384   EXPECT_EQ(GetDeviceId(kDeviceNoDCIM), observer().last_detached().device_id());
385 }
386 
387 // More complicated test case with multiple devices on multiple mount points.
TEST_F(StorageMonitorLinuxTest,SwapMountPoints)388 TEST_F(StorageMonitorLinuxTest, SwapMountPoints) {
389   base::FilePath test_path_a = CreateMountPointWithDCIMDir(kMountPointA);
390   base::FilePath test_path_b = CreateMountPointWithDCIMDir(kMountPointB);
391   ASSERT_FALSE(test_path_a.empty());
392   ASSERT_FALSE(test_path_b.empty());
393 
394   // Attach two devices.
395   // (*'d mounts are those StorageMonitor knows about.)
396   // kDeviceDCIM1 -> kMountPointA *
397   // kDeviceDCIM2 -> kMountPointB *
398   MtabTestData test_data1[] = {
399     MtabTestData(kDeviceDCIM1, test_path_a.value(), kValidFS),
400     MtabTestData(kDeviceDCIM2, test_path_b.value(), kValidFS),
401   };
402   AppendToMtabAndRunLoop(test_data1, arraysize(test_data1));
403   EXPECT_EQ(2, observer().attach_calls());
404   EXPECT_EQ(0, observer().detach_calls());
405 
406   // Detach two devices from old mount points and attach the devices at new
407   // mount points.
408   // kDeviceDCIM1 -> kMountPointB *
409   // kDeviceDCIM2 -> kMountPointA *
410   MtabTestData test_data2[] = {
411     MtabTestData(kDeviceDCIM1, test_path_b.value(), kValidFS),
412     MtabTestData(kDeviceDCIM2, test_path_a.value(), kValidFS),
413   };
414   OverwriteMtabAndRunLoop(test_data2, arraysize(test_data2));
415   EXPECT_EQ(4, observer().attach_calls());
416   EXPECT_EQ(2, observer().detach_calls());
417 
418   // Detach all devices.
419   WriteEmptyMtabAndRunLoop();
420   EXPECT_EQ(4, observer().attach_calls());
421   EXPECT_EQ(4, observer().detach_calls());
422 }
423 
424 // More complicated test case with multiple devices on multiple mount points.
TEST_F(StorageMonitorLinuxTest,MultiDevicesMultiMountPoints)425 TEST_F(StorageMonitorLinuxTest, MultiDevicesMultiMountPoints) {
426   base::FilePath test_path_a = CreateMountPointWithDCIMDir(kMountPointA);
427   base::FilePath test_path_b = CreateMountPointWithDCIMDir(kMountPointB);
428   ASSERT_FALSE(test_path_a.empty());
429   ASSERT_FALSE(test_path_b.empty());
430 
431   // Attach two devices.
432   // (*'d mounts are those StorageMonitor knows about.)
433   // kDeviceDCIM1 -> kMountPointA *
434   // kDeviceDCIM2 -> kMountPointB *
435   MtabTestData test_data1[] = {
436     MtabTestData(kDeviceDCIM1, test_path_a.value(), kValidFS),
437     MtabTestData(kDeviceDCIM2, test_path_b.value(), kValidFS),
438   };
439   AppendToMtabAndRunLoop(test_data1, arraysize(test_data1));
440   EXPECT_EQ(2, observer().attach_calls());
441   EXPECT_EQ(0, observer().detach_calls());
442 
443   // Attach |kDeviceDCIM1| to |kMountPointB|.
444   // |kDeviceDCIM2| is inaccessible, so it is detached. |kDeviceDCIM1| has been
445   // attached at |kMountPointB|, but is still accessible from |kMountPointA|.
446   // kDeviceDCIM1 -> kMountPointA *
447   // kDeviceDCIM2 -> kMountPointB
448   // kDeviceDCIM1 -> kMountPointB
449   MtabTestData test_data2[] = {
450     MtabTestData(kDeviceDCIM1, test_path_b.value(), kValidFS),
451   };
452   AppendToMtabAndRunLoop(test_data2, arraysize(test_data2));
453   EXPECT_EQ(2, observer().attach_calls());
454   EXPECT_EQ(1, observer().detach_calls());
455 
456   // Detach |kDeviceDCIM1| from |kMountPointA|, causing a detach and attach
457   // event.
458   // kDeviceDCIM2 -> kMountPointB
459   // kDeviceDCIM1 -> kMountPointB *
460   MtabTestData test_data3[] = {
461     MtabTestData(kDeviceDCIM2, test_path_b.value(), kValidFS),
462     MtabTestData(kDeviceDCIM1, test_path_b.value(), kValidFS),
463   };
464   OverwriteMtabAndRunLoop(test_data3, arraysize(test_data3));
465   EXPECT_EQ(3, observer().attach_calls());
466   EXPECT_EQ(2, observer().detach_calls());
467 
468   // Attach |kDeviceDCIM1| to |kMountPointA|.
469   // kDeviceDCIM2 -> kMountPointB
470   // kDeviceDCIM1 -> kMountPointB *
471   // kDeviceDCIM1 -> kMountPointA
472   MtabTestData test_data4[] = {
473     MtabTestData(kDeviceDCIM1, test_path_a.value(), kValidFS),
474   };
475   AppendToMtabAndRunLoop(test_data4, arraysize(test_data4));
476   EXPECT_EQ(3, observer().attach_calls());
477   EXPECT_EQ(2, observer().detach_calls());
478 
479   // Detach |kDeviceDCIM1| from |kMountPointB|.
480   // kDeviceDCIM1 -> kMountPointA *
481   // kDeviceDCIM2 -> kMountPointB *
482   OverwriteMtabAndRunLoop(test_data1, arraysize(test_data1));
483   EXPECT_EQ(5, observer().attach_calls());
484   EXPECT_EQ(3, observer().detach_calls());
485 
486   // Detach all devices.
487   WriteEmptyMtabAndRunLoop();
488   EXPECT_EQ(5, observer().attach_calls());
489   EXPECT_EQ(5, observer().detach_calls());
490 }
491 
TEST_F(StorageMonitorLinuxTest,MultipleMountPointsWithNonDCIMDevices)492 TEST_F(StorageMonitorLinuxTest, MultipleMountPointsWithNonDCIMDevices) {
493   base::FilePath test_path_a = CreateMountPointWithDCIMDir(kMountPointA);
494   base::FilePath test_path_b = CreateMountPointWithDCIMDir(kMountPointB);
495   ASSERT_FALSE(test_path_a.empty());
496   ASSERT_FALSE(test_path_b.empty());
497 
498   // Attach to one first.
499   // (*'d mounts are those StorageMonitor knows about.)
500   // kDeviceDCIM1 -> kMountPointA *
501   MtabTestData test_data1[] = {
502     MtabTestData(kDeviceDCIM1, test_path_a.value(), kValidFS),
503   };
504   AppendToMtabAndRunLoop(test_data1, arraysize(test_data1));
505   EXPECT_EQ(1, observer().attach_calls());
506   EXPECT_EQ(0, observer().detach_calls());
507 
508   // Attach |kDeviceDCIM1| to |kMountPointB|.
509   // kDeviceDCIM1 -> kMountPointA *
510   // kDeviceDCIM1 -> kMountPointB
511   MtabTestData test_data2[] = {
512     MtabTestData(kDeviceDCIM1, test_path_b.value(), kValidFS),
513   };
514   AppendToMtabAndRunLoop(test_data2, arraysize(test_data2));
515   EXPECT_EQ(1, observer().attach_calls());
516   EXPECT_EQ(0, observer().detach_calls());
517 
518   // Attach |kDeviceFixed| (a non-removable device) to |kMountPointA|.
519   // kDeviceDCIM1 -> kMountPointA
520   // kDeviceDCIM1 -> kMountPointB *
521   // kDeviceFixed -> kMountPointA
522   MtabTestData test_data3[] = {
523     MtabTestData(kDeviceFixed, test_path_a.value(), kValidFS),
524   };
525   RemoveDCIMDirFromMountPoint(kMountPointA);
526   AppendToMtabAndRunLoop(test_data3, arraysize(test_data3));
527   EXPECT_EQ(2, observer().attach_calls());
528   EXPECT_EQ(1, observer().detach_calls());
529 
530   // Detach |kDeviceFixed|.
531   // kDeviceDCIM1 -> kMountPointA
532   // kDeviceDCIM1 -> kMountPointB *
533   MtabTestData test_data4[] = {
534     MtabTestData(kDeviceDCIM1, test_path_a.value(), kValidFS),
535     MtabTestData(kDeviceDCIM1, test_path_b.value(), kValidFS),
536   };
537   CreateMountPointWithDCIMDir(kMountPointA);
538   OverwriteMtabAndRunLoop(test_data4, arraysize(test_data4));
539   EXPECT_EQ(2, observer().attach_calls());
540   EXPECT_EQ(1, observer().detach_calls());
541 
542   // Attach |kDeviceNoDCIM| (a non-DCIM device) to |kMountPointB|.
543   // kDeviceDCIM1  -> kMountPointA *
544   // kDeviceDCIM1  -> kMountPointB
545   // kDeviceNoDCIM -> kMountPointB *
546   MtabTestData test_data5[] = {
547     MtabTestData(kDeviceNoDCIM, test_path_b.value(), kValidFS),
548   };
549   base::DeleteFile(test_path_b.Append(kDCIMDirectoryName), false);
550   AppendToMtabAndRunLoop(test_data5, arraysize(test_data5));
551   EXPECT_EQ(4, observer().attach_calls());
552   EXPECT_EQ(2, observer().detach_calls());
553 
554   // Detach |kDeviceNoDCIM|.
555   // kDeviceDCIM1 -> kMountPointA *
556   // kDeviceDCIM1 -> kMountPointB
557   MtabTestData test_data6[] = {
558     MtabTestData(kDeviceDCIM1, test_path_a.value(), kValidFS),
559     MtabTestData(kDeviceDCIM1, test_path_b.value(), kValidFS),
560   };
561   CreateMountPointWithDCIMDir(kMountPointB);
562   OverwriteMtabAndRunLoop(test_data6, arraysize(test_data6));
563   EXPECT_EQ(4, observer().attach_calls());
564   EXPECT_EQ(3, observer().detach_calls());
565 
566   // Detach |kDeviceDCIM1| from |kMountPointB|.
567   // kDeviceDCIM1 -> kMountPointA *
568   OverwriteMtabAndRunLoop(test_data1, arraysize(test_data1));
569   EXPECT_EQ(4, observer().attach_calls());
570   EXPECT_EQ(3, observer().detach_calls());
571 
572   // Detach all devices.
573   WriteEmptyMtabAndRunLoop();
574   EXPECT_EQ(4, observer().attach_calls());
575   EXPECT_EQ(4, observer().detach_calls());
576 }
577 
TEST_F(StorageMonitorLinuxTest,DeviceLookUp)578 TEST_F(StorageMonitorLinuxTest, DeviceLookUp) {
579   base::FilePath test_path_a = CreateMountPointWithDCIMDir(kMountPointA);
580   base::FilePath test_path_b = CreateMountPointWithoutDCIMDir(kMountPointB);
581   base::FilePath test_path_c = CreateMountPointWithoutDCIMDir(kMountPointC);
582   ASSERT_FALSE(test_path_a.empty());
583   ASSERT_FALSE(test_path_b.empty());
584   ASSERT_FALSE(test_path_c.empty());
585 
586   // Attach to one first.
587   // (starred mounts are those StorageMonitor knows about.)
588   // kDeviceDCIM1  -> kMountPointA *
589   // kDeviceNoDCIM -> kMountPointB *
590   // kDeviceFixed  -> kMountPointC
591   MtabTestData test_data1[] = {
592     MtabTestData(kDeviceDCIM1, test_path_a.value(), kValidFS),
593     MtabTestData(kDeviceNoDCIM, test_path_b.value(), kValidFS),
594     MtabTestData(kDeviceFixed, test_path_c.value(), kValidFS),
595   };
596   AppendToMtabAndRunLoop(test_data1, arraysize(test_data1));
597   EXPECT_EQ(2, observer().attach_calls());
598   EXPECT_EQ(0, observer().detach_calls());
599 
600   StorageInfo device_info;
601   EXPECT_TRUE(notifier()->GetStorageInfoForPath(test_path_a, &device_info));
602   EXPECT_EQ(GetDeviceId(kDeviceDCIM1), device_info.device_id());
603   EXPECT_EQ(test_path_a.value(), device_info.location());
604   EXPECT_EQ(88788ULL, device_info.total_size_in_bytes());
605   EXPECT_EQ(base::ASCIIToUTF16("volume label"), device_info.storage_label());
606   EXPECT_EQ(base::ASCIIToUTF16("vendor name"), device_info.vendor_name());
607   EXPECT_EQ(base::ASCIIToUTF16("model name"), device_info.model_name());
608 
609   EXPECT_TRUE(notifier()->GetStorageInfoForPath(test_path_b, &device_info));
610   EXPECT_EQ(GetDeviceId(kDeviceNoDCIM), device_info.device_id());
611   EXPECT_EQ(test_path_b.value(), device_info.location());
612 
613   EXPECT_TRUE(notifier()->GetStorageInfoForPath(test_path_c, &device_info));
614   EXPECT_EQ(GetDeviceId(kDeviceFixed), device_info.device_id());
615   EXPECT_EQ(test_path_c.value(), device_info.location());
616 
617   // An invalid path.
618   EXPECT_FALSE(notifier()->GetStorageInfoForPath(base::FilePath(kInvalidPath),
619                                                  &device_info));
620 
621   // Test filling in of the mount point.
622   EXPECT_TRUE(
623       notifier()->GetStorageInfoForPath(test_path_a.Append("some/other/path"),
624       &device_info));
625   EXPECT_EQ(GetDeviceId(kDeviceDCIM1), device_info.device_id());
626   EXPECT_EQ(test_path_a.value(), device_info.location());
627 
628   // One device attached at multiple points.
629   // kDeviceDCIM1 -> kMountPointA *
630   // kDeviceFixed -> kMountPointB
631   // kDeviceFixed -> kMountPointC
632   MtabTestData test_data2[] = {
633     MtabTestData(kDeviceDCIM1, test_path_a.value(), kValidFS),
634     MtabTestData(kDeviceFixed, test_path_b.value(), kValidFS),
635     MtabTestData(kDeviceFixed, test_path_c.value(), kValidFS),
636   };
637   AppendToMtabAndRunLoop(test_data2, arraysize(test_data2));
638 
639   EXPECT_TRUE(notifier()->GetStorageInfoForPath(test_path_a, &device_info));
640   EXPECT_EQ(GetDeviceId(kDeviceDCIM1), device_info.device_id());
641 
642   EXPECT_TRUE(notifier()->GetStorageInfoForPath(test_path_b, &device_info));
643   EXPECT_EQ(GetDeviceId(kDeviceFixed), device_info.device_id());
644 
645   EXPECT_TRUE(notifier()->GetStorageInfoForPath(test_path_c, &device_info));
646   EXPECT_EQ(GetDeviceId(kDeviceFixed), device_info.device_id());
647 
648   EXPECT_EQ(2, observer().attach_calls());
649   EXPECT_EQ(1, observer().detach_calls());
650 }
651 
TEST_F(StorageMonitorLinuxTest,DevicePartitionSize)652 TEST_F(StorageMonitorLinuxTest, DevicePartitionSize) {
653   base::FilePath test_path_a = CreateMountPointWithDCIMDir(kMountPointA);
654   base::FilePath test_path_b = CreateMountPointWithoutDCIMDir(kMountPointB);
655   ASSERT_FALSE(test_path_a.empty());
656   ASSERT_FALSE(test_path_b.empty());
657 
658   MtabTestData test_data1[] = {
659     MtabTestData(kDeviceDCIM1, test_path_a.value(), kValidFS),
660     MtabTestData(kDeviceNoDCIM, test_path_b.value(), kValidFS),
661     MtabTestData(kDeviceFixed, kInvalidPath, kInvalidFS),
662   };
663   AppendToMtabAndRunLoop(test_data1, arraysize(test_data1));
664   EXPECT_EQ(2, observer().attach_calls());
665   EXPECT_EQ(0, observer().detach_calls());
666 
667   EXPECT_EQ(GetDevicePartitionSize(kDeviceDCIM1),
668             GetStorageSize(test_path_a));
669   EXPECT_EQ(GetDevicePartitionSize(kDeviceNoDCIM),
670             GetStorageSize(test_path_b));
671   EXPECT_EQ(GetDevicePartitionSize(kInvalidPath),
672             GetStorageSize(base::FilePath(kInvalidPath)));
673 }
674 
675 }  // namespace
676 
677 }  // namespace storage_monitor
678