Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 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 // TODO(vtl): Split this file. | |
| 6 | |
| 7 #include <map> | |
| 8 #include <string> | |
| 5 #include <vector> | 9 #include <vector> |
| 6 | 10 |
| 7 #include "base/macros.h" | 11 #include "base/macros.h" |
| 8 #include "mojo/public/cpp/application/application_impl.h" | 12 #include "mojo/public/cpp/application/application_impl.h" |
| 9 #include "mojo/public/cpp/application/application_test_base.h" | 13 #include "mojo/public/cpp/application/application_test_base.h" |
| 10 #include "mojo/public/cpp/bindings/interface_request.h" | 14 #include "mojo/public/cpp/bindings/interface_request.h" |
| 11 #include "mojo/public/cpp/bindings/type_converter.h" | 15 #include "mojo/public/cpp/bindings/type_converter.h" |
| 12 #include "services/files/directory.mojom.h" | 16 #include "services/files/directory.mojom.h" |
| 13 #include "services/files/file.mojom.h" | 17 #include "services/files/file.mojom.h" |
| 14 #include "services/files/files.mojom.h" | 18 #include "services/files/files.mojom.h" |
| (...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 87 class FilesAppTest : public test::ApplicationTestBase { | 91 class FilesAppTest : public test::ApplicationTestBase { |
| 88 public: | 92 public: |
| 89 FilesAppTest() {} | 93 FilesAppTest() {} |
| 90 ~FilesAppTest() override {} | 94 ~FilesAppTest() override {} |
| 91 | 95 |
| 92 void SetUp() override { | 96 void SetUp() override { |
| 93 test::ApplicationTestBase::SetUp(); | 97 test::ApplicationTestBase::SetUp(); |
| 94 application_impl()->ConnectToService("mojo:files", &files_); | 98 application_impl()->ConnectToService("mojo:files", &files_); |
| 95 } | 99 } |
| 96 | 100 |
| 101 protected: | |
| 102 void GetTemporaryRoot(DirectoryPtr* directory) { | |
| 103 Error error = ERROR_INTERNAL; | |
| 104 files()->OpenFileSystem(FILE_SYSTEM_TEMPORARY, GetProxy(directory), | |
| 105 Capture(&error)); | |
| 106 ASSERT_TRUE(files().WaitForIncomingMethodCall()); | |
| 107 ASSERT_EQ(ERROR_OK, error); | |
| 108 } | |
| 109 | |
| 97 FilesPtr& files() { return files_; } | 110 FilesPtr& files() { return files_; } |
| 98 | 111 |
| 99 private: | 112 private: |
| 100 FilesPtr files_; | 113 FilesPtr files_; |
| 101 | 114 |
| 102 DISALLOW_COPY_AND_ASSIGN(FilesAppTest); | 115 DISALLOW_COPY_AND_ASSIGN(FilesAppTest); |
| 103 }; | 116 }; |
| 104 | 117 |
| 105 TEST_F(FilesAppTest, CreateWriteCloseRenameOpenRead) { | 118 // FileImpl -------------------------------------------------------------------- |
| 106 // Get a temporary root directory. | 119 |
| 120 typedef FilesAppTest FileImplTest; | |
| 121 | |
| 122 TEST_F(FileImplTest, CreateWriteCloseRenameOpenRead) { | |
| 107 DirectoryPtr directory; | 123 DirectoryPtr directory; |
| 108 Error error = ERROR_INTERNAL; | 124 GetTemporaryRoot(&directory); |
| 109 files()->OpenFileSystem(FILE_SYSTEM_TEMPORARY, GetProxy(&directory), | 125 Error error; |
| 110 Capture(&error)); | |
| 111 ASSERT_TRUE(files().WaitForIncomingMethodCall()); | |
| 112 EXPECT_EQ(ERROR_OK, error); | |
| 113 | 126 |
| 114 { | 127 { |
| 115 // Create my_file. | 128 // Create my_file. |
| 116 FilePtr file; | 129 FilePtr file; |
| 117 error = ERROR_INTERNAL; | 130 error = ERROR_INTERNAL; |
| 118 directory->OpenFile("my_file", GetProxy(&file), | 131 directory->OpenFile("my_file", GetProxy(&file), |
| 119 kOpenFlagWrite | kOpenFlagCreate, Capture(&error)); | 132 kOpenFlagWrite | kOpenFlagCreate, Capture(&error)); |
| 120 ASSERT_TRUE(directory.WaitForIncomingMethodCall()); | 133 ASSERT_TRUE(directory.WaitForIncomingMethodCall()); |
| 121 EXPECT_EQ(ERROR_OK, error); | 134 EXPECT_EQ(ERROR_OK, error); |
| 122 | 135 |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 168 EXPECT_EQ(static_cast<uint8_t>('l'), bytes_read[1]); | 181 EXPECT_EQ(static_cast<uint8_t>('l'), bytes_read[1]); |
| 169 EXPECT_EQ(static_cast<uint8_t>('l'), bytes_read[2]); | 182 EXPECT_EQ(static_cast<uint8_t>('l'), bytes_read[2]); |
| 170 } | 183 } |
| 171 | 184 |
| 172 // TODO(vtl): Test various open options. | 185 // TODO(vtl): Test various open options. |
| 173 // TODO(vtl): Test read/write offset options. | 186 // TODO(vtl): Test read/write offset options. |
| 174 } | 187 } |
| 175 | 188 |
| 176 // Note: Ignore nanoseconds, since it may not always be supported. We expect at | 189 // Note: Ignore nanoseconds, since it may not always be supported. We expect at |
| 177 // least second-resolution support though. | 190 // least second-resolution support though. |
| 178 TEST_F(FilesAppTest, StatTouch) { | 191 TEST_F(FileImplTest, StatTouch) { |
| 179 // Get a temporary root directory. | |
| 180 DirectoryPtr directory; | 192 DirectoryPtr directory; |
| 181 Error error = ERROR_INTERNAL; | 193 GetTemporaryRoot(&directory); |
| 182 files()->OpenFileSystem(FILE_SYSTEM_TEMPORARY, GetProxy(&directory), | 194 Error error; |
| 183 Capture(&error)); | |
| 184 ASSERT_TRUE(files().WaitForIncomingMethodCall()); | |
| 185 EXPECT_EQ(ERROR_OK, error); | |
| 186 | 195 |
| 187 // Create my_file. | 196 // Create my_file. |
| 188 FilePtr file; | 197 FilePtr file; |
| 189 error = ERROR_INTERNAL; | 198 error = ERROR_INTERNAL; |
| 190 directory->OpenFile("my_file", GetProxy(&file), | 199 directory->OpenFile("my_file", GetProxy(&file), |
| 191 kOpenFlagWrite | kOpenFlagCreate, Capture(&error)); | 200 kOpenFlagWrite | kOpenFlagCreate, Capture(&error)); |
| 192 ASSERT_TRUE(directory.WaitForIncomingMethodCall()); | 201 ASSERT_TRUE(directory.WaitForIncomingMethodCall()); |
| 193 EXPECT_EQ(ERROR_OK, error); | 202 EXPECT_EQ(ERROR_OK, error); |
| 194 | 203 |
| 195 // Stat it. | 204 // Stat it. |
| (...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 255 // TODO(vtl): Also test Touch() "now" options. | 264 // TODO(vtl): Also test Touch() "now" options. |
| 256 // TODO(vtl): Also test touching both atime and mtime. | 265 // TODO(vtl): Also test touching both atime and mtime. |
| 257 | 266 |
| 258 // Close it. | 267 // Close it. |
| 259 error = ERROR_INTERNAL; | 268 error = ERROR_INTERNAL; |
| 260 file->Close(Capture(&error)); | 269 file->Close(Capture(&error)); |
| 261 ASSERT_TRUE(file.WaitForIncomingMethodCall()); | 270 ASSERT_TRUE(file.WaitForIncomingMethodCall()); |
| 262 EXPECT_EQ(ERROR_OK, error); | 271 EXPECT_EQ(ERROR_OK, error); |
| 263 } | 272 } |
| 264 | 273 |
| 265 TEST_F(FilesAppTest, TellSeek) { | 274 TEST_F(FileImplTest, TellSeek) { |
| 266 // Get a temporary root directory. | |
| 267 DirectoryPtr directory; | 275 DirectoryPtr directory; |
| 268 Error error = ERROR_INTERNAL; | 276 GetTemporaryRoot(&directory); |
| 269 files()->OpenFileSystem(FILE_SYSTEM_TEMPORARY, GetProxy(&directory), | 277 Error error; |
| 270 Capture(&error)); | |
| 271 ASSERT_TRUE(files().WaitForIncomingMethodCall()); | |
| 272 EXPECT_EQ(ERROR_OK, error); | |
| 273 | 278 |
| 274 // Create my_file. | 279 // Create my_file. |
| 275 FilePtr file; | 280 FilePtr file; |
| 276 error = ERROR_INTERNAL; | 281 error = ERROR_INTERNAL; |
| 277 directory->OpenFile("my_file", GetProxy(&file), | 282 directory->OpenFile("my_file", GetProxy(&file), |
| 278 kOpenFlagWrite | kOpenFlagCreate, Capture(&error)); | 283 kOpenFlagWrite | kOpenFlagCreate, Capture(&error)); |
| 279 ASSERT_TRUE(directory.WaitForIncomingMethodCall()); | 284 ASSERT_TRUE(directory.WaitForIncomingMethodCall()); |
| 280 EXPECT_EQ(ERROR_OK, error); | 285 EXPECT_EQ(ERROR_OK, error); |
| 281 | 286 |
| 282 // Write to it. | 287 // Write to it. |
| (...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 350 // TODO(vtl): Check that seeking actually affects reading/writing. | 355 // TODO(vtl): Check that seeking actually affects reading/writing. |
| 351 // TODO(vtl): Check that seeking can extend the file? | 356 // TODO(vtl): Check that seeking can extend the file? |
| 352 | 357 |
| 353 // Close it. | 358 // Close it. |
| 354 error = ERROR_INTERNAL; | 359 error = ERROR_INTERNAL; |
| 355 file->Close(Capture(&error)); | 360 file->Close(Capture(&error)); |
| 356 ASSERT_TRUE(file.WaitForIncomingMethodCall()); | 361 ASSERT_TRUE(file.WaitForIncomingMethodCall()); |
| 357 EXPECT_EQ(ERROR_OK, error); | 362 EXPECT_EQ(ERROR_OK, error); |
| 358 } | 363 } |
| 359 | 364 |
| 360 TEST_F(FilesAppTest, Dup) { | 365 TEST_F(FileImplTest, Dup) { |
| 361 // Get a temporary root directory. | |
| 362 DirectoryPtr directory; | 366 DirectoryPtr directory; |
| 363 Error error = ERROR_INTERNAL; | 367 GetTemporaryRoot(&directory); |
| 364 files()->OpenFileSystem(FILE_SYSTEM_TEMPORARY, GetProxy(&directory), | 368 Error error; |
| 365 Capture(&error)); | |
| 366 ASSERT_TRUE(files().WaitForIncomingMethodCall()); | |
| 367 EXPECT_EQ(ERROR_OK, error); | |
| 368 | 369 |
| 369 // Create my_file. | 370 // Create my_file. |
| 370 FilePtr file1; | 371 FilePtr file1; |
| 371 error = ERROR_INTERNAL; | 372 error = ERROR_INTERNAL; |
| 372 directory->OpenFile("my_file", GetProxy(&file1), | 373 directory->OpenFile("my_file", GetProxy(&file1), |
| 373 kOpenFlagRead | kOpenFlagWrite | kOpenFlagCreate, | 374 kOpenFlagRead | kOpenFlagWrite | kOpenFlagCreate, |
| 374 Capture(&error)); | 375 Capture(&error)); |
| 375 ASSERT_TRUE(directory.WaitForIncomingMethodCall()); | 376 ASSERT_TRUE(directory.WaitForIncomingMethodCall()); |
| 376 EXPECT_EQ(ERROR_OK, error); | 377 EXPECT_EQ(ERROR_OK, error); |
| 377 | 378 |
| (...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 449 | 450 |
| 450 // Close |file2|. | 451 // Close |file2|. |
| 451 error = ERROR_INTERNAL; | 452 error = ERROR_INTERNAL; |
| 452 file2->Close(Capture(&error)); | 453 file2->Close(Capture(&error)); |
| 453 ASSERT_TRUE(file2.WaitForIncomingMethodCall()); | 454 ASSERT_TRUE(file2.WaitForIncomingMethodCall()); |
| 454 EXPECT_EQ(ERROR_OK, error); | 455 EXPECT_EQ(ERROR_OK, error); |
| 455 | 456 |
| 456 // TODO(vtl): Test that |file2| has the same open options as |file1|. | 457 // TODO(vtl): Test that |file2| has the same open options as |file1|. |
| 457 } | 458 } |
| 458 | 459 |
| 460 // DirectoryImpl --------------------------------------------------------------- | |
| 461 | |
| 462 typedef FilesAppTest DirectoryImplTest; | |
| 463 | |
| 464 TEST_F(FileImplTest, Read) { | |
| 465 DirectoryPtr directory; | |
| 466 GetTemporaryRoot(&directory); | |
| 467 Error error; | |
| 468 | |
| 469 // Make some files. | |
| 470 const struct { | |
| 471 const char* name; | |
| 472 uint32_t open_flags; | |
| 473 } files_to_create[] = { | |
| 474 {"my_file1", kOpenFlagRead | kOpenFlagWrite | kOpenFlagCreate}, | |
| 475 {"my_file2", kOpenFlagWrite | kOpenFlagCreate | kOpenFlagExclusive}, | |
| 476 {"my_file3", kOpenFlagWrite | kOpenFlagCreate | kOpenFlagAppend}, | |
| 477 {"my_file4", kOpenFlagWrite | kOpenFlagCreate | kOpenFlagTruncate}}; | |
| 478 for (size_t i = 0; i < arraysize(files_to_create); i++) { | |
| 479 error = ERROR_INTERNAL; | |
| 480 directory->OpenFile(files_to_create[i].name, nullptr, | |
| 481 files_to_create[i].open_flags, Capture(&error)); | |
| 482 ASSERT_TRUE(directory.WaitForIncomingMethodCall()); | |
| 483 EXPECT_EQ(ERROR_OK, error); | |
| 484 } | |
| 485 // Make a directory. | |
| 486 error = ERROR_INTERNAL; | |
| 487 directory->OpenDirectory("my_dir", nullptr, | |
| 488 kOpenFlagRead | kOpenFlagWrite | kOpenFlagCreate, | |
| 489 Capture(&error)); | |
| 490 ASSERT_TRUE(directory.WaitForIncomingMethodCall()); | |
| 491 EXPECT_EQ(ERROR_OK, error); | |
| 492 | |
| 493 error = ERROR_INTERNAL; | |
| 494 Array<DirectoryEntryPtr> directory_contents; | |
| 495 directory->Read(Capture(&error, &directory_contents)); | |
| 496 ASSERT_TRUE(directory.WaitForIncomingMethodCall()); | |
| 497 EXPECT_EQ(ERROR_OK, error); | |
| 498 | |
| 499 // Expected contents of the directory. | |
| 500 std::map<std::string, FileType> expected_contents; | |
| 501 expected_contents["my_file1"] = FILE_TYPE_REGULAR_FILE; | |
| 502 expected_contents["my_file2"] = FILE_TYPE_REGULAR_FILE; | |
| 503 expected_contents["my_file3"] = FILE_TYPE_REGULAR_FILE; | |
| 504 expected_contents["my_file4"] = FILE_TYPE_REGULAR_FILE; | |
| 505 expected_contents["my_dir"] = FILE_TYPE_DIRECTORY; | |
| 506 expected_contents["."] = FILE_TYPE_DIRECTORY; | |
| 507 expected_contents[".."] = FILE_TYPE_DIRECTORY; | |
| 508 | |
| 509 EXPECT_EQ(expected_contents.size(), directory_contents.size()); | |
| 510 for (size_t i = 0; i < directory_contents.size(); i++) { | |
| 511 ASSERT_TRUE(directory_contents[i]); | |
| 512 ASSERT_TRUE(directory_contents[i]->name); | |
| 513 auto it = expected_contents.find(directory_contents[i]->name.get()); | |
| 514 ASSERT_TRUE(it != expected_contents.end()); | |
| 515 EXPECT_EQ(it->second, directory_contents[i]->type); | |
| 516 expected_contents.erase(it); | |
| 517 } | |
| 518 } | |
| 519 | |
| 520 // Note: Ignore nanoseconds, since it may not always be supported. We expect at | |
| 521 // least second-resolution support though. | |
| 522 // TODO(vtl): Maybe share this with |FileImplTest.StatTouch| ... but then it'd | |
| 523 // be harder to split this file. | |
| 524 TEST_F(DirectoryImplTest, StatTouch) { | |
| 525 DirectoryPtr directory; | |
| 526 GetTemporaryRoot(&directory); | |
| 527 Error error; | |
| 528 | |
| 529 // Stat it. | |
| 530 error = ERROR_INTERNAL; | |
| 531 FileInformationPtr file_info; | |
| 532 directory->Stat(Capture(&error, &file_info)); | |
| 533 ASSERT_TRUE(directory.WaitForIncomingMethodCall()); | |
| 534 EXPECT_EQ(ERROR_OK, error); | |
| 535 ASSERT_FALSE(file_info.is_null()); | |
| 536 EXPECT_EQ(0, file_info->size); | |
| 537 ASSERT_FALSE(file_info->atime.is_null()); | |
| 538 EXPECT_GT(file_info->atime->seconds, 0); // Expect that it's not 1970-01-01. | |
| 539 ASSERT_FALSE(file_info->mtime.is_null()); | |
| 540 EXPECT_GT(file_info->mtime->seconds, 0); | |
| 541 int64_t first_mtime = file_info->mtime->seconds; | |
| 542 | |
| 543 // Touch only the atime. | |
| 544 error = ERROR_INTERNAL; | |
| 545 TimespecOrNowPtr t(TimespecOrNow::New()); | |
| 546 t->now = false; | |
| 547 t->timespec = Timespec::New(); | |
| 548 const int64_t kPartyTime1 = 1234567890; // Party like it's 2009-02-13. | |
| 549 t->timespec->seconds = kPartyTime1; | |
| 550 directory->Touch(t.Pass(), nullptr, Capture(&error)); | |
| 551 ASSERT_TRUE(directory.WaitForIncomingMethodCall()); | |
| 552 EXPECT_EQ(ERROR_OK, error); | |
| 553 | |
| 554 // Stat again. | |
| 555 error = ERROR_INTERNAL; | |
| 556 file_info.reset(); | |
| 557 directory->Stat(Capture(&error, &file_info)); | |
| 558 ASSERT_TRUE(directory.WaitForIncomingMethodCall()); | |
| 559 EXPECT_EQ(ERROR_OK, error); | |
| 560 ASSERT_FALSE(file_info.is_null()); | |
| 561 ASSERT_FALSE(file_info->atime.is_null()); | |
| 562 EXPECT_EQ(kPartyTime1, file_info->atime->seconds); | |
| 563 ASSERT_FALSE(file_info->mtime.is_null()); | |
| 564 EXPECT_EQ(first_mtime, file_info->mtime->seconds); | |
| 565 | |
| 566 // Touch only the mtime. | |
| 567 t = TimespecOrNow::New(); | |
| 568 t->now = false; | |
| 569 t->timespec = Timespec::New(); | |
| 570 const int64_t kPartyTime2 = 1425059525; // No time like the present. | |
| 571 t->timespec->seconds = kPartyTime2; | |
| 572 directory->Touch(nullptr, t.Pass(), Capture(&error)); | |
| 573 ASSERT_TRUE(directory.WaitForIncomingMethodCall()); | |
| 574 EXPECT_EQ(ERROR_OK, error); | |
| 575 | |
| 576 // Stat again. | |
| 577 error = ERROR_INTERNAL; | |
| 578 file_info.reset(); | |
| 579 directory->Stat(Capture(&error, &file_info)); | |
| 580 ASSERT_TRUE(directory.WaitForIncomingMethodCall()); | |
| 581 EXPECT_EQ(ERROR_OK, error); | |
| 582 ASSERT_FALSE(file_info.is_null()); | |
| 583 ASSERT_FALSE(file_info->atime.is_null()); | |
| 584 EXPECT_EQ(kPartyTime1, file_info->atime->seconds); | |
| 585 ASSERT_FALSE(file_info->mtime.is_null()); | |
| 586 EXPECT_EQ(kPartyTime2, file_info->mtime->seconds); | |
| 587 | |
| 588 // TODO(vtl): Also test Touch() "now" options. | |
| 589 // TODO(vtl): Also test touching both atime and mtime. | |
| 590 } | |
| 591 | |
| 592 // TODO(vtl): Properly test OpenFile() and OpenDirectory() (including flags). | |
| 593 | |
| 594 TEST_F(DirectoryImplTest, BasicRenameDelete) { | |
| 595 DirectoryPtr directory; | |
| 596 GetTemporaryRoot(&directory); | |
| 597 Error error; | |
| 598 | |
| 599 // Create my_file. | |
| 600 error = ERROR_INTERNAL; | |
| 601 directory->OpenFile("my_file", nullptr, kOpenFlagWrite | kOpenFlagCreate, | |
| 602 Capture(&error)); | |
| 603 ASSERT_TRUE(directory.WaitForIncomingMethodCall()); | |
| 604 EXPECT_EQ(ERROR_OK, error); | |
| 605 | |
| 606 // Opening my_file should succeed. | |
| 607 error = ERROR_INTERNAL; | |
| 608 directory->OpenFile("my_file", nullptr, kOpenFlagRead, Capture(&error)); | |
| 609 ASSERT_TRUE(directory.WaitForIncomingMethodCall()); | |
| 610 EXPECT_EQ(ERROR_OK, error); | |
| 611 | |
| 612 // Rename my_file to my_new_file. | |
|
qsr
2015/03/06 13:01:04
Not certain you want to test this, but you could p
viettrungluu
2015/03/09 17:59:29
I'll add that to the TODO list. :)
| |
| 613 directory->Rename("my_file", "my_new_file", Capture(&error)); | |
| 614 ASSERT_TRUE(directory.WaitForIncomingMethodCall()); | |
| 615 EXPECT_EQ(ERROR_OK, error); | |
| 616 | |
| 617 // Opening my_file should fail. | |
| 618 error = ERROR_INTERNAL; | |
| 619 directory->OpenFile("my_file", nullptr, kOpenFlagRead, Capture(&error)); | |
| 620 ASSERT_TRUE(directory.WaitForIncomingMethodCall()); | |
| 621 EXPECT_EQ(ERROR_UNKNOWN, error); | |
| 622 | |
| 623 // Opening my_new_file should succeed. | |
| 624 error = ERROR_INTERNAL; | |
| 625 directory->OpenFile("my_new_file", nullptr, kOpenFlagRead, Capture(&error)); | |
| 626 ASSERT_TRUE(directory.WaitForIncomingMethodCall()); | |
| 627 EXPECT_EQ(ERROR_OK, error); | |
| 628 | |
| 629 // Delete my_new_file (no flags). | |
| 630 directory->Delete("my_new_file", 0, Capture(&error)); | |
| 631 ASSERT_TRUE(directory.WaitForIncomingMethodCall()); | |
| 632 EXPECT_EQ(ERROR_OK, error); | |
| 633 | |
| 634 // Opening my_new_file should fail. | |
| 635 error = ERROR_INTERNAL; | |
| 636 directory->OpenFile("my_new_file", nullptr, kOpenFlagRead, Capture(&error)); | |
| 637 ASSERT_TRUE(directory.WaitForIncomingMethodCall()); | |
| 638 EXPECT_EQ(ERROR_UNKNOWN, error); | |
| 639 } | |
| 640 | |
| 641 // TODO(vtl): Test delete flags. | |
| 642 | |
| 459 } // namespace | 643 } // namespace |
| 460 } // namespace files | 644 } // namespace files |
| 461 } // namespace mojo | 645 } // namespace mojo |
| OLD | NEW |