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