OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 // MediaDeviceNotificationsLinux unit tests. | 5 // MediaDeviceNotificationsLinux unit tests. |
6 | 6 |
7 #include "chrome/browser/media_gallery/media_device_notifications_linux.h" | 7 #include "chrome/browser/media_gallery/media_device_notifications_linux.h" |
8 | 8 |
9 #include <mntent.h> | 9 #include <mntent.h> |
10 #include <stdio.h> | 10 #include <stdio.h> |
(...skipping 254 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
265 TEST_F(MediaDeviceNotificationsLinuxTest, BasicAttachDetach) { | 265 TEST_F(MediaDeviceNotificationsLinuxTest, BasicAttachDetach) { |
266 testing::Sequence mock_sequence; | 266 testing::Sequence mock_sequence; |
267 FilePath test_path = CreateMountPointWithDCIMDir(kMountPointA); | 267 FilePath test_path = CreateMountPointWithDCIMDir(kMountPointA); |
268 ASSERT_FALSE(test_path.empty()); | 268 ASSERT_FALSE(test_path.empty()); |
269 MtabTestData test_data[] = { | 269 MtabTestData test_data[] = { |
270 MtabTestData(kDevice1, kInvalidPath, kValidFS), | 270 MtabTestData(kDevice1, kInvalidPath, kValidFS), |
271 MtabTestData(kDevice2, test_path.value(), kValidFS), | 271 MtabTestData(kDevice2, test_path.value(), kValidFS), |
272 }; | 272 }; |
273 // Only |kDevice2| should be attached, since |kDevice1| has a bad path. | 273 // Only |kDevice2| should be attached, since |kDevice1| has a bad path. |
274 EXPECT_CALL(observer(), | 274 EXPECT_CALL(observer(), |
275 OnMediaDeviceAttached(GetDCIMDeviceId(kDeviceId2), | 275 OnRemovableStorageAttached(GetDCIMDeviceId(kDeviceId2), |
276 ASCIIToUTF16(kDeviceLabel2), | 276 ASCIIToUTF16(kDeviceLabel2), |
277 test_path.value())) | 277 test_path.value())) |
278 .InSequence(mock_sequence); | 278 .InSequence(mock_sequence); |
279 AppendToMtabAndRunLoop(test_data, arraysize(test_data)); | 279 AppendToMtabAndRunLoop(test_data, arraysize(test_data)); |
280 | 280 |
281 // |kDevice2| should be detached here. | 281 // |kDevice2| should be detached here. |
282 EXPECT_CALL(observer(), OnMediaDeviceDetached(GetDCIMDeviceId(kDeviceId2))) | 282 EXPECT_CALL(observer(), |
| 283 OnRemovableStorageDetached(GetDCIMDeviceId(kDeviceId2))) |
283 .InSequence(mock_sequence); | 284 .InSequence(mock_sequence); |
284 WriteEmptyMtabAndRunLoop(); | 285 WriteEmptyMtabAndRunLoop(); |
285 } | 286 } |
286 | 287 |
287 // Only mount points with DCIM directories are recognized. | 288 // Only mount points with DCIM directories are recognized. |
288 TEST_F(MediaDeviceNotificationsLinuxTest, DCIM) { | 289 TEST_F(MediaDeviceNotificationsLinuxTest, DCIM) { |
289 testing::Sequence mock_sequence; | 290 testing::Sequence mock_sequence; |
290 FilePath test_path_a = CreateMountPointWithDCIMDir(kMountPointA); | 291 FilePath test_path_a = CreateMountPointWithDCIMDir(kMountPointA); |
291 ASSERT_FALSE(test_path_a.empty()); | 292 ASSERT_FALSE(test_path_a.empty()); |
292 MtabTestData test_data1[] = { | 293 MtabTestData test_data1[] = { |
293 MtabTestData(kDevice1, test_path_a.value(), kValidFS), | 294 MtabTestData(kDevice1, test_path_a.value(), kValidFS), |
294 }; | 295 }; |
295 // |kDevice1| should be attached as expected. | 296 // |kDevice1| should be attached as expected. |
296 EXPECT_CALL(observer(), | 297 EXPECT_CALL(observer(), |
297 OnMediaDeviceAttached(GetDCIMDeviceId(kDeviceId1), | 298 OnRemovableStorageAttached(GetDCIMDeviceId(kDeviceId1), |
298 ASCIIToUTF16(kDeviceLabel1), | 299 ASCIIToUTF16(kDeviceLabel1), |
299 test_path_a.value())) | 300 test_path_a.value())) |
300 .InSequence(mock_sequence); | 301 .InSequence(mock_sequence); |
301 AppendToMtabAndRunLoop(test_data1, arraysize(test_data1)); | 302 AppendToMtabAndRunLoop(test_data1, arraysize(test_data1)); |
302 | 303 |
303 // This should do nothing, since |kMountPointB| does not have a DCIM dir. | 304 // This should do nothing, since |kMountPointB| does not have a DCIM dir. |
304 FilePath test_path_b = CreateMountPointWithoutDCIMDir(kMountPointB); | 305 FilePath test_path_b = CreateMountPointWithoutDCIMDir(kMountPointB); |
305 ASSERT_FALSE(test_path_b.empty()); | 306 ASSERT_FALSE(test_path_b.empty()); |
306 MtabTestData test_data2[] = { | 307 MtabTestData test_data2[] = { |
307 MtabTestData(kDevice2, test_path_b.value(), kValidFS), | 308 MtabTestData(kDevice2, test_path_b.value(), kValidFS), |
308 }; | 309 }; |
309 AppendToMtabAndRunLoop(test_data2, arraysize(test_data2)); | 310 AppendToMtabAndRunLoop(test_data2, arraysize(test_data2)); |
310 | 311 |
311 // |kDevice1| should be detached as expected. | 312 // |kDevice1| should be detached as expected. |
312 EXPECT_CALL(observer(), OnMediaDeviceDetached(GetDCIMDeviceId(kDeviceId1))) | 313 EXPECT_CALL(observer(), |
| 314 OnRemovableStorageDetached(GetDCIMDeviceId(kDeviceId1))) |
313 .InSequence(mock_sequence); | 315 .InSequence(mock_sequence); |
314 WriteEmptyMtabAndRunLoop(); | 316 WriteEmptyMtabAndRunLoop(); |
315 } | 317 } |
316 | 318 |
317 // More complicated test case with multiple devices on multiple mount points. | 319 // More complicated test case with multiple devices on multiple mount points. |
318 TEST_F(MediaDeviceNotificationsLinuxTest, SwapMountPoints) { | 320 TEST_F(MediaDeviceNotificationsLinuxTest, SwapMountPoints) { |
319 FilePath test_path_a = CreateMountPointWithDCIMDir(kMountPointA); | 321 FilePath test_path_a = CreateMountPointWithDCIMDir(kMountPointA); |
320 FilePath test_path_b = CreateMountPointWithDCIMDir(kMountPointB); | 322 FilePath test_path_b = CreateMountPointWithDCIMDir(kMountPointB); |
321 ASSERT_FALSE(test_path_a.empty()); | 323 ASSERT_FALSE(test_path_a.empty()); |
322 ASSERT_FALSE(test_path_b.empty()); | 324 ASSERT_FALSE(test_path_b.empty()); |
323 | 325 |
324 // Attach two devices. | 326 // Attach two devices. |
325 // kDevice1 -> kMountPointA | 327 // kDevice1 -> kMountPointA |
326 // kDevice2 -> kMountPointB | 328 // kDevice2 -> kMountPointB |
327 MtabTestData test_data1[] = { | 329 MtabTestData test_data1[] = { |
328 MtabTestData(kDevice1, test_path_a.value(), kValidFS), | 330 MtabTestData(kDevice1, test_path_a.value(), kValidFS), |
329 MtabTestData(kDevice2, test_path_b.value(), kValidFS), | 331 MtabTestData(kDevice2, test_path_b.value(), kValidFS), |
330 }; | 332 }; |
331 EXPECT_CALL(observer(), OnMediaDeviceAttached(_, _, _)).Times(2); | 333 EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(2); |
332 EXPECT_CALL(observer(), OnMediaDeviceDetached(_)).Times(0); | 334 EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(0); |
333 AppendToMtabAndRunLoop(test_data1, arraysize(test_data1)); | 335 AppendToMtabAndRunLoop(test_data1, arraysize(test_data1)); |
334 | 336 |
335 // Detach two devices from old mount points and attach the devices at new | 337 // Detach two devices from old mount points and attach the devices at new |
336 // mount points. | 338 // mount points. |
337 // kDevice1 -> kMountPointB | 339 // kDevice1 -> kMountPointB |
338 // kDevice2 -> kMountPointA | 340 // kDevice2 -> kMountPointA |
339 MtabTestData test_data2[] = { | 341 MtabTestData test_data2[] = { |
340 MtabTestData(kDevice1, test_path_b.value(), kValidFS), | 342 MtabTestData(kDevice1, test_path_b.value(), kValidFS), |
341 MtabTestData(kDevice2, test_path_a.value(), kValidFS), | 343 MtabTestData(kDevice2, test_path_a.value(), kValidFS), |
342 }; | 344 }; |
343 EXPECT_CALL(observer(), OnMediaDeviceAttached(_, _, _)).Times(2); | 345 EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(2); |
344 EXPECT_CALL(observer(), OnMediaDeviceDetached(_)).Times(2); | 346 EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(2); |
345 OverwriteMtabAndRunLoop(test_data2, arraysize(test_data2)); | 347 OverwriteMtabAndRunLoop(test_data2, arraysize(test_data2)); |
346 | 348 |
347 // Detach all devices. | 349 // Detach all devices. |
348 EXPECT_CALL(observer(), OnMediaDeviceAttached(_, _, _)).Times(0); | 350 EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(0); |
349 EXPECT_CALL(observer(), OnMediaDeviceDetached(_)).Times(2); | 351 EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(2); |
350 WriteEmptyMtabAndRunLoop(); | 352 WriteEmptyMtabAndRunLoop(); |
351 } | 353 } |
352 | 354 |
353 // More complicated test case with multiple devices on multiple mount points. | 355 // More complicated test case with multiple devices on multiple mount points. |
354 TEST_F(MediaDeviceNotificationsLinuxTest, MultiDevicesMultiMountPoints) { | 356 TEST_F(MediaDeviceNotificationsLinuxTest, MultiDevicesMultiMountPoints) { |
355 FilePath test_path_a = CreateMountPointWithDCIMDir(kMountPointA); | 357 FilePath test_path_a = CreateMountPointWithDCIMDir(kMountPointA); |
356 FilePath test_path_b = CreateMountPointWithDCIMDir(kMountPointB); | 358 FilePath test_path_b = CreateMountPointWithDCIMDir(kMountPointB); |
357 ASSERT_FALSE(test_path_a.empty()); | 359 ASSERT_FALSE(test_path_a.empty()); |
358 ASSERT_FALSE(test_path_b.empty()); | 360 ASSERT_FALSE(test_path_b.empty()); |
359 | 361 |
360 // Attach two devices. | 362 // Attach two devices. |
361 // kDevice1 -> kMountPointA | 363 // kDevice1 -> kMountPointA |
362 // kDevice2 -> kMountPointB | 364 // kDevice2 -> kMountPointB |
363 MtabTestData test_data1[] = { | 365 MtabTestData test_data1[] = { |
364 MtabTestData(kDevice1, test_path_a.value(), kValidFS), | 366 MtabTestData(kDevice1, test_path_a.value(), kValidFS), |
365 MtabTestData(kDevice2, test_path_b.value(), kValidFS), | 367 MtabTestData(kDevice2, test_path_b.value(), kValidFS), |
366 }; | 368 }; |
367 EXPECT_CALL(observer(), OnMediaDeviceAttached(_, _, _)).Times(2); | 369 EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(2); |
368 EXPECT_CALL(observer(), OnMediaDeviceDetached(_)).Times(0); | 370 EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(0); |
369 AppendToMtabAndRunLoop(test_data1, arraysize(test_data1)); | 371 AppendToMtabAndRunLoop(test_data1, arraysize(test_data1)); |
370 | 372 |
371 // Attach |kDevice1| to |kMountPointB|. | 373 // Attach |kDevice1| to |kMountPointB|. |
372 // |kDevice2| is inaccessible, so it is detached. |kDevice1| has been | 374 // |kDevice2| is inaccessible, so it is detached. |kDevice1| has been |
373 // re-attached at |kMountPointB|, so it is 'detached' from kMountPointA. | 375 // re-attached at |kMountPointB|, so it is 'detached' from kMountPointA. |
374 // kDevice1 -> kMountPointA | 376 // kDevice1 -> kMountPointA |
375 // kDevice2 -> kMountPointB | 377 // kDevice2 -> kMountPointB |
376 // kDevice1 -> kMountPointB | 378 // kDevice1 -> kMountPointB |
377 MtabTestData test_data2[] = { | 379 MtabTestData test_data2[] = { |
378 MtabTestData(kDevice1, test_path_b.value(), kValidFS), | 380 MtabTestData(kDevice1, test_path_b.value(), kValidFS), |
379 }; | 381 }; |
380 EXPECT_CALL(observer(), OnMediaDeviceAttached(_, _, _)).Times(1); | 382 EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(1); |
381 EXPECT_CALL(observer(), OnMediaDeviceDetached(_)).Times(2); | 383 EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(2); |
382 AppendToMtabAndRunLoop(test_data2, arraysize(test_data2)); | 384 AppendToMtabAndRunLoop(test_data2, arraysize(test_data2)); |
383 | 385 |
384 // Attach |kDevice2| to |kMountPointA|. | 386 // Attach |kDevice2| to |kMountPointA|. |
385 // kDevice1 -> kMountPointA | 387 // kDevice1 -> kMountPointA |
386 // kDevice2 -> kMountPointB | 388 // kDevice2 -> kMountPointB |
387 // kDevice1 -> kMountPointB | 389 // kDevice1 -> kMountPointB |
388 // kDevice2 -> kMountPointA | 390 // kDevice2 -> kMountPointA |
389 MtabTestData test_data3[] = { | 391 MtabTestData test_data3[] = { |
390 MtabTestData(kDevice2, test_path_a.value(), kValidFS), | 392 MtabTestData(kDevice2, test_path_a.value(), kValidFS), |
391 }; | 393 }; |
392 EXPECT_CALL(observer(), OnMediaDeviceAttached(_, _, _)).Times(1); | 394 EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(1); |
393 EXPECT_CALL(observer(), OnMediaDeviceDetached(_)).Times(0); | 395 EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(0); |
394 AppendToMtabAndRunLoop(test_data3, arraysize(test_data3)); | 396 AppendToMtabAndRunLoop(test_data3, arraysize(test_data3)); |
395 | 397 |
396 // Detach |kDevice2| from |kMountPointA|. | 398 // Detach |kDevice2| from |kMountPointA|. |
397 // kDevice1 -> kMountPointA | 399 // kDevice1 -> kMountPointA |
398 // kDevice2 -> kMountPointB | 400 // kDevice2 -> kMountPointB |
399 // kDevice1 -> kMountPointB | 401 // kDevice1 -> kMountPointB |
400 MtabTestData test_data4[] = { | 402 MtabTestData test_data4[] = { |
401 MtabTestData(kDevice1, test_path_a.value(), kValidFS), | 403 MtabTestData(kDevice1, test_path_a.value(), kValidFS), |
402 MtabTestData(kDevice2, test_path_b.value(), kValidFS), | 404 MtabTestData(kDevice2, test_path_b.value(), kValidFS), |
403 MtabTestData(kDevice1, test_path_b.value(), kValidFS), | 405 MtabTestData(kDevice1, test_path_b.value(), kValidFS), |
404 }; | 406 }; |
405 EXPECT_CALL(observer(), OnMediaDeviceAttached(_, _, _)).Times(0); | 407 EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(0); |
406 EXPECT_CALL(observer(), OnMediaDeviceDetached(_)).Times(1); | 408 EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(1); |
407 OverwriteMtabAndRunLoop(test_data4, arraysize(test_data4)); | 409 OverwriteMtabAndRunLoop(test_data4, arraysize(test_data4)); |
408 | 410 |
409 // Detach |kDevice1| from |kMountPointB|. | 411 // Detach |kDevice1| from |kMountPointB|. |
410 // kDevice1 -> kMountPointA | 412 // kDevice1 -> kMountPointA |
411 // kDevice2 -> kMountPointB | 413 // kDevice2 -> kMountPointB |
412 EXPECT_CALL(observer(), OnMediaDeviceAttached(_, _, _)).Times(2); | 414 EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(2); |
413 EXPECT_CALL(observer(), OnMediaDeviceDetached(_)).Times(1); | 415 EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(1); |
414 OverwriteMtabAndRunLoop(test_data1, arraysize(test_data1)); | 416 OverwriteMtabAndRunLoop(test_data1, arraysize(test_data1)); |
415 | 417 |
416 // Detach all devices. | 418 // Detach all devices. |
417 EXPECT_CALL(observer(), OnMediaDeviceAttached(_, _, _)).Times(0); | 419 EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(0); |
418 EXPECT_CALL(observer(), OnMediaDeviceDetached(_)).Times(2); | 420 EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(2); |
419 WriteEmptyMtabAndRunLoop(); | 421 WriteEmptyMtabAndRunLoop(); |
420 } | 422 } |
421 | 423 |
422 // More complicated test case with multiple devices on one mount point. | 424 // More complicated test case with multiple devices on one mount point. |
423 TEST_F(MediaDeviceNotificationsLinuxTest, MultiDevicesOneMountPoint) { | 425 TEST_F(MediaDeviceNotificationsLinuxTest, MultiDevicesOneMountPoint) { |
424 FilePath test_path_a = CreateMountPointWithDCIMDir(kMountPointA); | 426 FilePath test_path_a = CreateMountPointWithDCIMDir(kMountPointA); |
425 FilePath test_path_b = CreateMountPointWithDCIMDir(kMountPointB); | 427 FilePath test_path_b = CreateMountPointWithDCIMDir(kMountPointB); |
426 ASSERT_FALSE(test_path_a.empty()); | 428 ASSERT_FALSE(test_path_a.empty()); |
427 ASSERT_FALSE(test_path_b.empty()); | 429 ASSERT_FALSE(test_path_b.empty()); |
428 | 430 |
429 // |kDevice1| is most recently mounted at |kMountPointB|. | 431 // |kDevice1| is most recently mounted at |kMountPointB|. |
430 // kDevice1 -> kMountPointA | 432 // kDevice1 -> kMountPointA |
431 // kDevice2 -> kMountPointB | 433 // kDevice2 -> kMountPointB |
432 // kDevice1 -> kMountPointB | 434 // kDevice1 -> kMountPointB |
433 MtabTestData test_data1[] = { | 435 MtabTestData test_data1[] = { |
434 MtabTestData(kDevice1, test_path_a.value(), kValidFS), | 436 MtabTestData(kDevice1, test_path_a.value(), kValidFS), |
435 MtabTestData(kDevice2, test_path_b.value(), kValidFS), | 437 MtabTestData(kDevice2, test_path_b.value(), kValidFS), |
436 MtabTestData(kDevice1, test_path_b.value(), kValidFS), | 438 MtabTestData(kDevice1, test_path_b.value(), kValidFS), |
437 }; | 439 }; |
438 EXPECT_CALL(observer(), | 440 EXPECT_CALL(observer(), |
439 OnMediaDeviceAttached(GetDCIMDeviceId(kDeviceId1), | 441 OnRemovableStorageAttached(GetDCIMDeviceId(kDeviceId1), |
440 ASCIIToUTF16(kDeviceLabel1), | 442 ASCIIToUTF16(kDeviceLabel1), |
441 test_path_b.value())) | 443 test_path_b.value())) |
442 .Times(1); | 444 .Times(1); |
443 EXPECT_CALL(observer(), OnMediaDeviceDetached(_)).Times(0); | 445 EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(0); |
444 OverwriteMtabAndRunLoop(test_data1, arraysize(test_data1)); | 446 OverwriteMtabAndRunLoop(test_data1, arraysize(test_data1)); |
445 | 447 |
446 // Attach |kDevice3| to |kMountPointB|. | 448 // Attach |kDevice3| to |kMountPointB|. |
447 // |kDevice1| is inaccessible at its most recent mount point, so it is | 449 // |kDevice1| is inaccessible at its most recent mount point, so it is |
448 // detached and unavailable, even though it is still accessible via | 450 // detached and unavailable, even though it is still accessible via |
449 // |kMountPointA|. | 451 // |kMountPointA|. |
450 // kDevice1 -> kMountPointA | 452 // kDevice1 -> kMountPointA |
451 // kDevice2 -> kMountPointB | 453 // kDevice2 -> kMountPointB |
452 // kDevice1 -> kMountPointB | 454 // kDevice1 -> kMountPointB |
453 // kDevice3 -> kMountPointB | 455 // kDevice3 -> kMountPointB |
454 MtabTestData test_data2[] = { | 456 MtabTestData test_data2[] = { |
455 MtabTestData(kDevice3, test_path_b.value(), kValidFS), | 457 MtabTestData(kDevice3, test_path_b.value(), kValidFS), |
456 }; | 458 }; |
457 EXPECT_CALL(observer(), OnMediaDeviceDetached(GetDCIMDeviceId(kDeviceId1))) | 459 EXPECT_CALL(observer(), |
| 460 OnRemovableStorageDetached(GetDCIMDeviceId(kDeviceId1))) |
458 .Times(1); | 461 .Times(1); |
459 EXPECT_CALL(observer(), | 462 EXPECT_CALL(observer(), |
460 OnMediaDeviceAttached(GetDCIMDeviceId(kDeviceId3), | 463 OnRemovableStorageAttached(GetDCIMDeviceId(kDeviceId3), |
461 ASCIIToUTF16(kDeviceLabel3), | 464 ASCIIToUTF16(kDeviceLabel3), |
462 test_path_b.value())) | 465 test_path_b.value())) |
463 .Times(1); | 466 .Times(1); |
464 AppendToMtabAndRunLoop(test_data2, arraysize(test_data2)); | 467 AppendToMtabAndRunLoop(test_data2, arraysize(test_data2)); |
465 | 468 |
466 // Detach all devices. | 469 // Detach all devices. |
467 EXPECT_CALL(observer(), OnMediaDeviceAttached(_, _, _)).Times(0); | 470 EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(0); |
468 EXPECT_CALL(observer(), OnMediaDeviceDetached(GetDCIMDeviceId(kDeviceId3))) | 471 EXPECT_CALL(observer(), |
| 472 OnRemovableStorageDetached(GetDCIMDeviceId(kDeviceId3))) |
469 .Times(1); | 473 .Times(1); |
470 WriteEmptyMtabAndRunLoop(); | 474 WriteEmptyMtabAndRunLoop(); |
471 } | 475 } |
472 | 476 |
473 } // namespace | 477 } // namespace |
474 | 478 |
475 } // namespace chrome | 479 } // namespace chrome |
OLD | NEW |