Home | History | Annotate | Download | only in storage_monitor
      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/files/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 
     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 
     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 
    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:
    123   explicit TestStorageMonitorLinux(const base::FilePath& path)
    124       : StorageMonitorLinux(path) {
    125     SetMediaTransferProtocolManagerForTest(
    126         new TestMediaTransferProtocolManagerLinux());
    127     SetGetDeviceInfoCallbackForTest(base::Bind(&GetDeviceInfo));
    128   }
    129   virtual ~TestStorageMonitorLinux() {}
    130 
    131  private:
    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 {
    145     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 
    158   StorageMonitorLinuxTest()
    159       : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP) {}
    160   virtual ~StorageMonitorLinuxTest() {}
    161 
    162  protected:
    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 
    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.
    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.
    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.
    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.
    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.
    225   base::FilePath CreateMountPointWithoutDCIMDir(const std::string& dir) {
    226     return CreateMountPoint(dir, false  /* do not create DCIM dir */);
    227   }
    228 
    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 
    235   MockRemovableStorageObserver& observer() {
    236     return *mock_storage_observer_;
    237   }
    238 
    239   StorageMonitor* notifier() {
    240     return monitor_.get();
    241   }
    242 
    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.
    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_|.
    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.
    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.
    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.
    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.
    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 
    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 
    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 
    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