OLD | NEW |
| (Empty) |
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 // MediaDeviceNotificationsLinux unit tests. | |
6 | |
7 #include "chrome/browser/media_gallery/media_device_notifications_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/logging.h" | |
16 #include "base/memory/scoped_ptr.h" | |
17 #include "base/message_loop.h" | |
18 #include "base/scoped_temp_dir.h" | |
19 #include "base/system_monitor/system_monitor.h" | |
20 #include "base/test/mock_devices_changed_observer.h" | |
21 #include "base/utf_string_conversions.h" | |
22 #include "chrome/browser/media_gallery/media_storage_util.h" | |
23 #include "content/public/test/test_browser_thread.h" | |
24 #include "testing/gtest/include/gtest/gtest.h" | |
25 | |
26 namespace chrome { | |
27 | |
28 namespace { | |
29 | |
30 using testing::_; | |
31 | |
32 const char kValidFS[] = "vfat"; | |
33 const char kInvalidFS[] = "invalidfs"; | |
34 | |
35 const char kInvalidPath[] = "invalid path does not exist"; | |
36 | |
37 const char kDevice1[] = "d1"; | |
38 const char kDevice2[] = "d2"; | |
39 const char kDevice3[] = "d3"; | |
40 | |
41 const char kDeviceId1[] = "UUID:FFF0-000F"; | |
42 const char kDeviceId2[] = "VendorModelSerial:ComName:Model2010:898989898989"; | |
43 const char kDeviceId3[] = "VendorModelSerial:::WEM319X792"; | |
44 | |
45 const char kDeviceLabel1[] = "TEST_USB_MODEL_1"; | |
46 const char kDeviceLabel2[] = "TEST_USB_MODEL_2"; | |
47 const char kDeviceLabel3[] = "TEST_USB_MODEL_3"; | |
48 | |
49 const char kMountPointA[] = "mnt_a"; | |
50 const char kMountPointB[] = "mnt_b"; | |
51 | |
52 std::string GetDCIMDeviceId(std::string unique_id) { | |
53 return chrome::MediaStorageUtil::MakeDeviceId( | |
54 chrome::MediaStorageUtil::USB_MASS_STORAGE_WITH_DCIM, unique_id); | |
55 } | |
56 | |
57 bool GetDeviceInfo(const std::string& dev_path, std::string* id, | |
58 string16* name) { | |
59 std::string device_name; | |
60 if (dev_path == kDevice1) { | |
61 *id = GetDCIMDeviceId(kDeviceId1); | |
62 device_name = kDeviceLabel1; | |
63 } else if (dev_path == kDevice2) { | |
64 *id = GetDCIMDeviceId(kDeviceId2); | |
65 device_name = kDeviceLabel2; | |
66 } else if (dev_path == kDevice3) { | |
67 *id = GetDCIMDeviceId(kDeviceId3); | |
68 device_name = kDeviceLabel3; | |
69 } else { | |
70 return false; | |
71 } | |
72 | |
73 *name = ASCIIToUTF16(device_name); | |
74 return true; | |
75 } | |
76 | |
77 class MediaDeviceNotificationsLinuxTestWrapper | |
78 : public MediaDeviceNotificationsLinux { | |
79 public: | |
80 MediaDeviceNotificationsLinuxTestWrapper(const FilePath& path, | |
81 MessageLoop* message_loop) | |
82 : MediaDeviceNotificationsLinux(path, &GetDeviceInfo), | |
83 message_loop_(message_loop) { | |
84 } | |
85 | |
86 private: | |
87 // Avoids code deleting the object while there are references to it. | |
88 // Aside from the base::RefCountedThreadSafe friend class, any attempts to | |
89 // call this dtor will result in a compile-time error. | |
90 ~MediaDeviceNotificationsLinuxTestWrapper() {} | |
91 | |
92 virtual void OnFilePathChanged(const FilePath& path, bool error) OVERRIDE { | |
93 MediaDeviceNotificationsLinux::OnFilePathChanged(path, error); | |
94 message_loop_->PostTask(FROM_HERE, MessageLoop::QuitClosure()); | |
95 } | |
96 | |
97 MessageLoop* message_loop_; | |
98 | |
99 DISALLOW_COPY_AND_ASSIGN(MediaDeviceNotificationsLinuxTestWrapper); | |
100 }; | |
101 | |
102 class MediaDeviceNotificationsLinuxTest : public testing::Test { | |
103 public: | |
104 struct MtabTestData { | |
105 MtabTestData(const std::string& mount_device, | |
106 const std::string& mount_point, | |
107 const std::string& mount_type) | |
108 : mount_device(mount_device), | |
109 mount_point(mount_point), | |
110 mount_type(mount_type) { | |
111 } | |
112 | |
113 const std::string mount_device; | |
114 const std::string mount_point; | |
115 const std::string mount_type; | |
116 }; | |
117 | |
118 MediaDeviceNotificationsLinuxTest() | |
119 : message_loop_(MessageLoop::TYPE_IO), | |
120 file_thread_(content::BrowserThread::FILE, &message_loop_) { | |
121 } | |
122 virtual ~MediaDeviceNotificationsLinuxTest() {} | |
123 | |
124 protected: | |
125 virtual void SetUp() OVERRIDE { | |
126 mock_devices_changed_observer_.reset(new base::MockDevicesChangedObserver); | |
127 system_monitor_.AddDevicesChangedObserver( | |
128 mock_devices_changed_observer_.get()); | |
129 | |
130 // Create and set up a temp dir with files for the test. | |
131 ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir()); | |
132 FilePath test_dir = scoped_temp_dir_.path().AppendASCII("test_etc"); | |
133 ASSERT_TRUE(file_util::CreateDirectory(test_dir)); | |
134 mtab_file_ = test_dir.AppendASCII("test_mtab"); | |
135 MtabTestData initial_test_data[] = { | |
136 MtabTestData("dummydevice", "dummydir", kInvalidFS), | |
137 }; | |
138 WriteToMtab(initial_test_data, | |
139 arraysize(initial_test_data), | |
140 true /* overwrite */); | |
141 | |
142 // Initialize the test subject. | |
143 notifications_ = | |
144 new MediaDeviceNotificationsLinuxTestWrapper(mtab_file_, | |
145 &message_loop_); | |
146 notifications_->Init(); | |
147 message_loop_.RunAllPending(); | |
148 } | |
149 | |
150 virtual void TearDown() OVERRIDE { | |
151 message_loop_.RunAllPending(); | |
152 notifications_ = NULL; | |
153 system_monitor_.RemoveDevicesChangedObserver( | |
154 mock_devices_changed_observer_.get()); | |
155 } | |
156 | |
157 // Append mtab entries from the |data| array of size |data_size| to the mtab | |
158 // file, and run the message loop. | |
159 void AppendToMtabAndRunLoop(const MtabTestData* data, size_t data_size) { | |
160 WriteToMtab(data, data_size, false /* do not overwrite */); | |
161 message_loop_.Run(); | |
162 } | |
163 | |
164 // Overwrite the mtab file with mtab entries from the |data| array of size | |
165 // |data_size|, and run the message loop. | |
166 void OverwriteMtabAndRunLoop(const MtabTestData* data, size_t data_size) { | |
167 WriteToMtab(data, data_size, true /* overwrite */); | |
168 message_loop_.Run(); | |
169 } | |
170 | |
171 // Simplied version of OverwriteMtabAndRunLoop() that just deletes all the | |
172 // entries in the mtab file. | |
173 void WriteEmptyMtabAndRunLoop() { | |
174 OverwriteMtabAndRunLoop(NULL, // No data. | |
175 0); // No data length. | |
176 } | |
177 | |
178 // Create a directory named |dir| relative to the test directory. | |
179 // It has a DCIM directory, so MediaDeviceNotificationsLinux recognizes it as | |
180 // a media directory. | |
181 FilePath CreateMountPointWithDCIMDir(const std::string& dir) { | |
182 return CreateMountPoint(dir, true /* create DCIM dir */); | |
183 } | |
184 | |
185 // Create a directory named |dir| relative to the test directory. | |
186 // It does not have a DCIM directory, so MediaDeviceNotificationsLinux does | |
187 // not recognizes it as a media directory. | |
188 FilePath CreateMountPointWithoutDCIMDir(const std::string& dir) { | |
189 return CreateMountPoint(dir, false /* do not create DCIM dir */); | |
190 } | |
191 | |
192 base::MockDevicesChangedObserver& observer() { | |
193 return *mock_devices_changed_observer_; | |
194 } | |
195 | |
196 private: | |
197 // Create a directory named |dir| relative to the test directory. | |
198 // Set |with_dcim_dir| to true if the created directory will have a "DCIM" | |
199 // subdirectory. | |
200 // Returns the full path to the created directory on success, or an empty | |
201 // path on failure. | |
202 FilePath CreateMountPoint(const std::string& dir, bool with_dcim_dir) { | |
203 FilePath return_path(scoped_temp_dir_.path()); | |
204 return_path = return_path.AppendASCII(dir); | |
205 FilePath path(return_path); | |
206 if (with_dcim_dir) | |
207 path = path.AppendASCII("DCIM"); | |
208 if (!file_util::CreateDirectory(path)) | |
209 return FilePath(); | |
210 return return_path; | |
211 } | |
212 | |
213 // Write the test mtab data to |mtab_file_|. | |
214 // |data| is an array of mtab entries. | |
215 // |data_size| is the array size of |data|. | |
216 // |overwrite| specifies whether to overwrite |mtab_file_|. | |
217 void WriteToMtab(const MtabTestData* data, | |
218 size_t data_size, | |
219 bool overwrite) { | |
220 FILE* file = setmntent(mtab_file_.value().c_str(), overwrite ? "w" : "a"); | |
221 ASSERT_TRUE(file); | |
222 | |
223 // Due to the glibc *mntent() interface design, which is out of our | |
224 // control, the mtnent struct has several char* fields, even though | |
225 // addmntent() does not write to them in the calls below. To make the | |
226 // compiler happy while avoiding making additional copies of strings, | |
227 // we just const_cast() the strings' c_str()s. | |
228 // Assuming addmntent() does not write to the char* fields, this is safe. | |
229 // It is unlikely the platforms this test suite runs on will have an | |
230 // addmntent() implementation that does change the char* fields. If that | |
231 // was ever the case, the test suite will start crashing or failing. | |
232 mntent entry; | |
233 static const char kMountOpts[] = "rw"; | |
234 entry.mnt_opts = const_cast<char*>(kMountOpts); | |
235 entry.mnt_freq = 0; | |
236 entry.mnt_passno = 0; | |
237 for (size_t i = 0; i < data_size; ++i) { | |
238 entry.mnt_fsname = const_cast<char*>(data[i].mount_device.c_str()); | |
239 entry.mnt_dir = const_cast<char*>(data[i].mount_point.c_str()); | |
240 entry.mnt_type = const_cast<char*>(data[i].mount_type.c_str()); | |
241 ASSERT_EQ(0, addmntent(file, &entry)); | |
242 } | |
243 ASSERT_EQ(1, endmntent(file)); | |
244 } | |
245 | |
246 // The message loop and file thread to run tests on. | |
247 MessageLoop message_loop_; | |
248 content::TestBrowserThread file_thread_; | |
249 | |
250 // SystemMonitor and DevicesChangedObserver to hook together to test. | |
251 base::SystemMonitor system_monitor_; | |
252 scoped_ptr<base::MockDevicesChangedObserver> mock_devices_changed_observer_; | |
253 | |
254 // Temporary directory for created test data. | |
255 ScopedTempDir scoped_temp_dir_; | |
256 // Path to the test mtab file. | |
257 FilePath mtab_file_; | |
258 | |
259 scoped_refptr<MediaDeviceNotificationsLinuxTestWrapper> notifications_; | |
260 | |
261 DISALLOW_COPY_AND_ASSIGN(MediaDeviceNotificationsLinuxTest); | |
262 }; | |
263 | |
264 // Simple test case where we attach and detach a media device. | |
265 TEST_F(MediaDeviceNotificationsLinuxTest, BasicAttachDetach) { | |
266 testing::Sequence mock_sequence; | |
267 FilePath test_path = CreateMountPointWithDCIMDir(kMountPointA); | |
268 ASSERT_FALSE(test_path.empty()); | |
269 MtabTestData test_data[] = { | |
270 MtabTestData(kDevice1, kInvalidPath, kValidFS), | |
271 MtabTestData(kDevice2, test_path.value(), kValidFS), | |
272 }; | |
273 // Only |kDevice2| should be attached, since |kDevice1| has a bad path. | |
274 EXPECT_CALL(observer(), | |
275 OnMediaDeviceAttached(GetDCIMDeviceId(kDeviceId2), | |
276 ASCIIToUTF16(kDeviceLabel2), | |
277 test_path.value())) | |
278 .InSequence(mock_sequence); | |
279 AppendToMtabAndRunLoop(test_data, arraysize(test_data)); | |
280 | |
281 // |kDevice2| should be detached here. | |
282 EXPECT_CALL(observer(), OnMediaDeviceDetached(GetDCIMDeviceId(kDeviceId2))) | |
283 .InSequence(mock_sequence); | |
284 WriteEmptyMtabAndRunLoop(); | |
285 } | |
286 | |
287 // Only mount points with DCIM directories are recognized. | |
288 TEST_F(MediaDeviceNotificationsLinuxTest, DCIM) { | |
289 testing::Sequence mock_sequence; | |
290 FilePath test_path_a = CreateMountPointWithDCIMDir(kMountPointA); | |
291 ASSERT_FALSE(test_path_a.empty()); | |
292 MtabTestData test_data1[] = { | |
293 MtabTestData(kDevice1, test_path_a.value(), kValidFS), | |
294 }; | |
295 // |kDevice1| should be attached as expected. | |
296 EXPECT_CALL(observer(), | |
297 OnMediaDeviceAttached(GetDCIMDeviceId(kDeviceId1), | |
298 ASCIIToUTF16(kDeviceLabel1), | |
299 test_path_a.value())) | |
300 .InSequence(mock_sequence); | |
301 AppendToMtabAndRunLoop(test_data1, arraysize(test_data1)); | |
302 | |
303 // This should do nothing, since |kMountPointB| does not have a DCIM dir. | |
304 FilePath test_path_b = CreateMountPointWithoutDCIMDir(kMountPointB); | |
305 ASSERT_FALSE(test_path_b.empty()); | |
306 MtabTestData test_data2[] = { | |
307 MtabTestData(kDevice2, test_path_b.value(), kValidFS), | |
308 }; | |
309 AppendToMtabAndRunLoop(test_data2, arraysize(test_data2)); | |
310 | |
311 // |kDevice1| should be detached as expected. | |
312 EXPECT_CALL(observer(), OnMediaDeviceDetached(GetDCIMDeviceId(kDeviceId1))) | |
313 .InSequence(mock_sequence); | |
314 WriteEmptyMtabAndRunLoop(); | |
315 } | |
316 | |
317 // More complicated test case with multiple devices on multiple mount points. | |
318 TEST_F(MediaDeviceNotificationsLinuxTest, SwapMountPoints) { | |
319 FilePath test_path_a = CreateMountPointWithDCIMDir(kMountPointA); | |
320 FilePath test_path_b = CreateMountPointWithDCIMDir(kMountPointB); | |
321 ASSERT_FALSE(test_path_a.empty()); | |
322 ASSERT_FALSE(test_path_b.empty()); | |
323 | |
324 // Attach two devices. | |
325 // kDevice1 -> kMountPointA | |
326 // kDevice2 -> kMountPointB | |
327 MtabTestData test_data1[] = { | |
328 MtabTestData(kDevice1, test_path_a.value(), kValidFS), | |
329 MtabTestData(kDevice2, test_path_b.value(), kValidFS), | |
330 }; | |
331 EXPECT_CALL(observer(), OnMediaDeviceAttached(_, _, _)).Times(2); | |
332 EXPECT_CALL(observer(), OnMediaDeviceDetached(_)).Times(0); | |
333 AppendToMtabAndRunLoop(test_data1, arraysize(test_data1)); | |
334 | |
335 // Detach two devices from old mount points and attach the devices at new | |
336 // mount points. | |
337 // kDevice1 -> kMountPointB | |
338 // kDevice2 -> kMountPointA | |
339 MtabTestData test_data2[] = { | |
340 MtabTestData(kDevice1, test_path_b.value(), kValidFS), | |
341 MtabTestData(kDevice2, test_path_a.value(), kValidFS), | |
342 }; | |
343 EXPECT_CALL(observer(), OnMediaDeviceAttached(_, _, _)).Times(2); | |
344 EXPECT_CALL(observer(), OnMediaDeviceDetached(_)).Times(2); | |
345 OverwriteMtabAndRunLoop(test_data2, arraysize(test_data2)); | |
346 | |
347 // Detach all devices. | |
348 EXPECT_CALL(observer(), OnMediaDeviceAttached(_, _, _)).Times(0); | |
349 EXPECT_CALL(observer(), OnMediaDeviceDetached(_)).Times(2); | |
350 WriteEmptyMtabAndRunLoop(); | |
351 } | |
352 | |
353 // More complicated test case with multiple devices on multiple mount points. | |
354 TEST_F(MediaDeviceNotificationsLinuxTest, MultiDevicesMultiMountPoints) { | |
355 FilePath test_path_a = CreateMountPointWithDCIMDir(kMountPointA); | |
356 FilePath test_path_b = CreateMountPointWithDCIMDir(kMountPointB); | |
357 ASSERT_FALSE(test_path_a.empty()); | |
358 ASSERT_FALSE(test_path_b.empty()); | |
359 | |
360 // Attach two devices. | |
361 // kDevice1 -> kMountPointA | |
362 // kDevice2 -> kMountPointB | |
363 MtabTestData test_data1[] = { | |
364 MtabTestData(kDevice1, test_path_a.value(), kValidFS), | |
365 MtabTestData(kDevice2, test_path_b.value(), kValidFS), | |
366 }; | |
367 EXPECT_CALL(observer(), OnMediaDeviceAttached(_, _, _)).Times(2); | |
368 EXPECT_CALL(observer(), OnMediaDeviceDetached(_)).Times(0); | |
369 AppendToMtabAndRunLoop(test_data1, arraysize(test_data1)); | |
370 | |
371 // Attach |kDevice1| to |kMountPointB|. | |
372 // |kDevice2| is inaccessible, so it is detached. |kDevice1| has been | |
373 // re-attached at |kMountPointB|, so it is 'detached' from kMountPointA. | |
374 // kDevice1 -> kMountPointA | |
375 // kDevice2 -> kMountPointB | |
376 // kDevice1 -> kMountPointB | |
377 MtabTestData test_data2[] = { | |
378 MtabTestData(kDevice1, test_path_b.value(), kValidFS), | |
379 }; | |
380 EXPECT_CALL(observer(), OnMediaDeviceAttached(_, _, _)).Times(1); | |
381 EXPECT_CALL(observer(), OnMediaDeviceDetached(_)).Times(2); | |
382 AppendToMtabAndRunLoop(test_data2, arraysize(test_data2)); | |
383 | |
384 // Attach |kDevice2| to |kMountPointA|. | |
385 // kDevice1 -> kMountPointA | |
386 // kDevice2 -> kMountPointB | |
387 // kDevice1 -> kMountPointB | |
388 // kDevice2 -> kMountPointA | |
389 MtabTestData test_data3[] = { | |
390 MtabTestData(kDevice2, test_path_a.value(), kValidFS), | |
391 }; | |
392 EXPECT_CALL(observer(), OnMediaDeviceAttached(_, _, _)).Times(1); | |
393 EXPECT_CALL(observer(), OnMediaDeviceDetached(_)).Times(0); | |
394 AppendToMtabAndRunLoop(test_data3, arraysize(test_data3)); | |
395 | |
396 // Detach |kDevice2| from |kMountPointA|. | |
397 // kDevice1 -> kMountPointA | |
398 // kDevice2 -> kMountPointB | |
399 // kDevice1 -> kMountPointB | |
400 MtabTestData test_data4[] = { | |
401 MtabTestData(kDevice1, test_path_a.value(), kValidFS), | |
402 MtabTestData(kDevice2, test_path_b.value(), kValidFS), | |
403 MtabTestData(kDevice1, test_path_b.value(), kValidFS), | |
404 }; | |
405 EXPECT_CALL(observer(), OnMediaDeviceAttached(_, _, _)).Times(0); | |
406 EXPECT_CALL(observer(), OnMediaDeviceDetached(_)).Times(1); | |
407 OverwriteMtabAndRunLoop(test_data4, arraysize(test_data4)); | |
408 | |
409 // Detach |kDevice1| from |kMountPointB|. | |
410 // kDevice1 -> kMountPointA | |
411 // kDevice2 -> kMountPointB | |
412 EXPECT_CALL(observer(), OnMediaDeviceAttached(_, _, _)).Times(2); | |
413 EXPECT_CALL(observer(), OnMediaDeviceDetached(_)).Times(1); | |
414 OverwriteMtabAndRunLoop(test_data1, arraysize(test_data1)); | |
415 | |
416 // Detach all devices. | |
417 EXPECT_CALL(observer(), OnMediaDeviceAttached(_, _, _)).Times(0); | |
418 EXPECT_CALL(observer(), OnMediaDeviceDetached(_)).Times(2); | |
419 WriteEmptyMtabAndRunLoop(); | |
420 } | |
421 | |
422 // More complicated test case with multiple devices on one mount point. | |
423 TEST_F(MediaDeviceNotificationsLinuxTest, MultiDevicesOneMountPoint) { | |
424 FilePath test_path_a = CreateMountPointWithDCIMDir(kMountPointA); | |
425 FilePath test_path_b = CreateMountPointWithDCIMDir(kMountPointB); | |
426 ASSERT_FALSE(test_path_a.empty()); | |
427 ASSERT_FALSE(test_path_b.empty()); | |
428 | |
429 // |kDevice1| is most recently mounted at |kMountPointB|. | |
430 // kDevice1 -> kMountPointA | |
431 // kDevice2 -> kMountPointB | |
432 // kDevice1 -> kMountPointB | |
433 MtabTestData test_data1[] = { | |
434 MtabTestData(kDevice1, test_path_a.value(), kValidFS), | |
435 MtabTestData(kDevice2, test_path_b.value(), kValidFS), | |
436 MtabTestData(kDevice1, test_path_b.value(), kValidFS), | |
437 }; | |
438 EXPECT_CALL(observer(), | |
439 OnMediaDeviceAttached(GetDCIMDeviceId(kDeviceId1), | |
440 ASCIIToUTF16(kDeviceLabel1), | |
441 test_path_b.value())) | |
442 .Times(1); | |
443 EXPECT_CALL(observer(), OnMediaDeviceDetached(_)).Times(0); | |
444 OverwriteMtabAndRunLoop(test_data1, arraysize(test_data1)); | |
445 | |
446 // Attach |kDevice3| to |kMountPointB|. | |
447 // |kDevice1| is inaccessible at its most recent mount point, so it is | |
448 // detached and unavailable, even though it is still accessible via | |
449 // |kMountPointA|. | |
450 // kDevice1 -> kMountPointA | |
451 // kDevice2 -> kMountPointB | |
452 // kDevice1 -> kMountPointB | |
453 // kDevice3 -> kMountPointB | |
454 MtabTestData test_data2[] = { | |
455 MtabTestData(kDevice3, test_path_b.value(), kValidFS), | |
456 }; | |
457 EXPECT_CALL(observer(), OnMediaDeviceDetached(GetDCIMDeviceId(kDeviceId1))) | |
458 .Times(1); | |
459 EXPECT_CALL(observer(), | |
460 OnMediaDeviceAttached(GetDCIMDeviceId(kDeviceId3), | |
461 ASCIIToUTF16(kDeviceLabel3), | |
462 test_path_b.value())) | |
463 .Times(1); | |
464 AppendToMtabAndRunLoop(test_data2, arraysize(test_data2)); | |
465 | |
466 // Detach all devices. | |
467 EXPECT_CALL(observer(), OnMediaDeviceAttached(_, _, _)).Times(0); | |
468 EXPECT_CALL(observer(), OnMediaDeviceDetached(GetDCIMDeviceId(kDeviceId3))) | |
469 .Times(1); | |
470 WriteEmptyMtabAndRunLoop(); | |
471 } | |
472 | |
473 } // namespace | |
474 | |
475 } // namespace chrome | |
OLD | NEW |