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 #include "base/command_line.h" | |
5 #include "base/files/file.h" | 6 #include "base/files/file.h" |
6 #include "base/files/file_util.h" | 7 #include "base/files/file_util.h" |
7 #include "base/files/scoped_temp_dir.h" | 8 #include "base/files/scoped_temp_dir.h" |
9 #include "base/test/multiprocess_test.h" | |
10 #include "base/test/test_timeouts.h" | |
11 #include "base/threading/platform_thread.h" | |
8 #include "base/time/time.h" | 12 #include "base/time/time.h" |
9 #include "testing/gtest/include/gtest/gtest.h" | 13 #include "testing/gtest/include/gtest/gtest.h" |
14 #include "testing/multiprocess_func_list.h" | |
10 | 15 |
11 using base::File; | 16 using base::File; |
12 using base::FilePath; | 17 using base::FilePath; |
13 | 18 |
14 TEST(FileTest, Create) { | 19 TEST(FileTest, Create) { |
15 base::ScopedTempDir temp_dir; | 20 base::ScopedTempDir temp_dir; |
16 ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); | 21 ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); |
17 FilePath file_path = temp_dir.path().AppendASCII("create_file_1"); | 22 FilePath file_path = temp_dir.path().AppendASCII("create_file_1"); |
18 | 23 |
19 { | 24 { |
(...skipping 482 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
502 NULL)); | 507 NULL)); |
503 ASSERT_TRUE(dir.IsValid()); | 508 ASSERT_TRUE(dir.IsValid()); |
504 | 509 |
505 base::File::Info info; | 510 base::File::Info info; |
506 EXPECT_TRUE(dir.GetInfo(&info)); | 511 EXPECT_TRUE(dir.GetInfo(&info)); |
507 EXPECT_TRUE(info.is_directory); | 512 EXPECT_TRUE(info.is_directory); |
508 EXPECT_FALSE(info.is_symbolic_link); | 513 EXPECT_FALSE(info.is_symbolic_link); |
509 EXPECT_EQ(0, info.size); | 514 EXPECT_EQ(0, info.size); |
510 } | 515 } |
511 #endif // defined(OS_WIN) | 516 #endif // defined(OS_WIN) |
517 | |
518 // Constants and helper function for lock/unlock tests. | |
519 namespace { | |
520 | |
521 // Flag for the parent to share a temp dir to the child. | |
522 const char kTempDirFlag[] = "temp-dir"; | |
523 | |
524 // File to lock in temp dir. | |
525 const char kLockFile[] = "lockfile"; | |
526 | |
527 // Constants for various requests and responses, used as |signal_file| parameter | |
528 // to signal/wait helpers. | |
529 const char kSignalLockFileLock[] = "lock.signal"; | |
530 const char kSignalLockFileLocked[] = "locked.signal"; | |
531 const char kSignalLockFileClose[] = "close.signal"; | |
532 const char kSignalLockFileClosed[] = "closed.signal"; | |
533 const char kSignalLockFileUnlock[] = "unlock.signal"; | |
534 const char kSignalLockFileUnlocked[] = "unlocked.signal"; | |
535 const char kSignalExit[] = "exit.signal"; | |
536 | |
537 // Signal an event by creating a file which didn't previously exist. | |
538 bool SignalEvent(const base::FilePath& signal_dir, | |
539 const base::StringPiece& signal_file) { | |
540 File file(signal_dir.AppendASCII(signal_file), | |
541 File::FLAG_CREATE | File::FLAG_APPEND); | |
542 return file.IsValid(); | |
543 } | |
544 | |
545 // Check whether an event was signaled. | |
546 bool CheckEvent(const base::FilePath& signal_dir, | |
547 const base::StringPiece& signal_file) { | |
548 File file(signal_dir.AppendASCII(signal_file), | |
549 File::FLAG_OPEN | File::FLAG_READ); | |
550 return file.IsValid(); | |
551 } | |
552 | |
553 // Wait for an event to be signaled, returning false if it times out. | |
554 bool WaitForEventWithTimeout(const base::FilePath& signal_dir, | |
555 const base::StringPiece& signal_file, | |
556 const base::TimeDelta& timeout) { | |
557 const base::Time finish_by = base::Time::Now() + timeout; | |
558 while (!CheckEvent(signal_dir, signal_file)) { | |
559 if (base::Time::Now() > finish_by) | |
560 return false; | |
561 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10)); | |
562 } | |
563 return true; | |
564 } | |
565 | |
566 // Wait forever for the event to be signaled (should never return false). | |
567 bool WaitForEvent(const base::FilePath& signal_dir, | |
568 const base::StringPiece& signal_file) { | |
569 return WaitForEventWithTimeout(signal_dir, signal_file, | |
570 base::TimeDelta::Max()); | |
571 } | |
572 | |
573 // Wait for an event or the action timeout, whichever comes first. | |
574 bool WaitForEventOrTimeout(const base::FilePath& signal_dir, | |
575 const base::StringPiece& signal_file) { | |
576 return WaitForEventWithTimeout(signal_dir, signal_file, | |
577 TestTimeouts::action_timeout()); | |
578 } | |
579 | |
580 } // namespace | |
581 | |
582 // Subprocess to test getting a file lock then releasing it. | |
583 MULTIPROCESS_TEST_MAIN(ChildLockUnlock) { | |
584 base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); | |
585 const base::FilePath temp_path = | |
586 command_line->GetSwitchValuePath(kTempDirFlag); | |
587 CHECK(base::DirectoryExists(temp_path)); | |
588 | |
589 // Wait for signal to lock, then lock the file. | |
590 CHECK(WaitForEvent(temp_path, kSignalLockFileLock)); | |
591 File file(temp_path.AppendASCII(kLockFile), | |
592 File::FLAG_OPEN | File::FLAG_READ | File::FLAG_WRITE); | |
593 CHECK(file.IsValid()); | |
594 CHECK_EQ(File::FILE_OK, file.Lock()); | |
595 CHECK(SignalEvent(temp_path, kSignalLockFileLocked)); | |
596 | |
597 // Wait for signal to unlock, then unlock the file. | |
598 CHECK(WaitForEvent(temp_path, kSignalLockFileUnlock)); | |
599 CHECK_EQ(File::FILE_OK, file.Unlock()); | |
600 CHECK(SignalEvent(temp_path, kSignalLockFileUnlocked)); | |
601 | |
602 // Wait for signal to exit, so that the unlock can be distinguished from exit. | |
603 CHECK(WaitForEvent(temp_path, kSignalExit)); | |
604 return 0; | |
605 } | |
606 | |
607 // Test that the child's lock prevents the parent from getting the lock, and | |
608 // that the child's unlock allows the parent to get the lock. | |
609 TEST(FileTest, LockAndUnlock) { | |
610 // Create a temporary dir and spin up a ChildLockExit subprocess against it. | |
611 base::ScopedTempDir temp_dir; | |
612 ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); | |
613 const base::FilePath temp_path = temp_dir.path(); | |
614 base::CommandLine child_command_line( | |
615 base::GetMultiProcessTestChildBaseCommandLine()); | |
616 child_command_line.AppendSwitchPath(kTempDirFlag, temp_path); | |
617 base::Process test_child_process = | |
Lei Zhang
2015/12/04 03:23:03
Check |test_child_process| is valid before continu
| |
618 base::SpawnMultiProcessTestChild("ChildLockUnlock", child_command_line, | |
619 base::LaunchOptions()); | |
620 | |
621 // Create the lock file and verify that it can be locked and unlocked. | |
622 File file(temp_path.AppendASCII(kLockFile), | |
623 File::FLAG_CREATE | File::FLAG_READ | File::FLAG_WRITE); | |
624 ASSERT_TRUE(file.IsValid()); | |
625 ASSERT_EQ(File::FILE_OK, file.Lock()); | |
626 ASSERT_EQ(File::FILE_OK, file.Unlock()); | |
627 | |
628 // Signal child to lock the file. | |
629 ASSERT_TRUE(SignalEvent(temp_path, kSignalLockFileLock)); | |
630 | |
631 // After child has locked the file, test the lock, then signal child to unlock | |
632 // the file. | |
633 ASSERT_TRUE(WaitForEventOrTimeout(temp_path, kSignalLockFileLocked)); | |
634 ASSERT_NE(File::FILE_OK, file.Lock()); | |
635 ASSERT_TRUE(SignalEvent(temp_path, kSignalLockFileUnlock)); | |
636 | |
637 // After child has unlocked, test that the file can be locked and signal the | |
638 // child to exit. | |
639 ASSERT_TRUE(WaitForEventOrTimeout(temp_path, kSignalLockFileUnlocked)); | |
640 ASSERT_EQ(File::FILE_OK, file.Lock()); | |
641 ASSERT_EQ(File::FILE_OK, file.Unlock()); | |
642 ASSERT_TRUE(SignalEvent(temp_path, kSignalExit)); | |
643 | |
644 int rv = -1; | |
645 ASSERT_TRUE(test_child_process.WaitForExitWithTimeout( | |
646 TestTimeouts::action_timeout(), &rv)); | |
647 ASSERT_EQ(0, rv); | |
648 } | |
649 | |
650 // Subprocess to test getting a lock then releasing it implicitly on close. | |
651 MULTIPROCESS_TEST_MAIN(ChildLockClose) { | |
652 base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); | |
653 const base::FilePath temp_path = | |
654 command_line->GetSwitchValuePath(kTempDirFlag); | |
655 CHECK(base::DirectoryExists(temp_path)); | |
656 | |
657 // Wait for signal to lock, then lock the file. | |
658 CHECK(WaitForEvent(temp_path, kSignalLockFileLock)); | |
659 File file(temp_path.AppendASCII(kLockFile), | |
660 File::FLAG_OPEN | File::FLAG_READ | File::FLAG_WRITE); | |
661 CHECK(file.IsValid()); | |
662 CHECK_EQ(File::FILE_OK, file.Lock()); | |
663 CHECK(SignalEvent(temp_path, kSignalLockFileLocked)); | |
664 | |
665 // Wait for the signal to close, then close the file. | |
666 CHECK(WaitForEvent(temp_path, kSignalLockFileClose)); | |
Lei Zhang
2015/12/04 03:23:03
Would it easier to have one child process impl tha
Scott Hess - ex-Googler
2015/12/04 06:40:34
Just to clarify, you mean to merge the different c
Lei Zhang
2015/12/04 16:57:54
If it's easy to implement and it reduces the amoun
| |
667 file.Close(); | |
668 CHECK(SignalEvent(temp_path, kSignalLockFileClosed)); | |
669 | |
670 // Wait for signal to exit, so that close can be distinguished from exit. | |
671 CHECK(WaitForEvent(temp_path, kSignalExit)); | |
672 return 0; | |
673 } | |
674 | |
675 // Test that closing the handle in the child implicitly releases the lock. | |
676 TEST(FileTest, UnlockOnClose) { | |
677 // Create a temporary dir and spin up a ChildLockExit subprocess against it. | |
678 base::ScopedTempDir temp_dir; | |
679 ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); | |
680 const base::FilePath temp_path = temp_dir.path(); | |
681 base::CommandLine child_command_line( | |
682 base::GetMultiProcessTestChildBaseCommandLine()); | |
683 child_command_line.AppendSwitchPath(kTempDirFlag, temp_path); | |
684 base::Process test_child_process = | |
685 base::SpawnMultiProcessTestChild("ChildLockClose", child_command_line, | |
686 base::LaunchOptions()); | |
687 | |
688 // Create the lock file and verify that it can be locked and unlocked. | |
689 File file(temp_path.AppendASCII(kLockFile), | |
690 File::FLAG_CREATE | File::FLAG_READ | File::FLAG_WRITE); | |
691 ASSERT_TRUE(file.IsValid()); | |
692 ASSERT_EQ(File::FILE_OK, file.Lock()); | |
693 ASSERT_EQ(File::FILE_OK, file.Unlock()); | |
694 | |
695 // Signal child to lock the file. | |
696 ASSERT_TRUE(SignalEvent(temp_path, kSignalLockFileLock)); | |
697 | |
698 // After child has locked the file, test the lock, then signal child to close | |
699 // the file. | |
700 ASSERT_TRUE(WaitForEventOrTimeout(temp_path, kSignalLockFileLocked)); | |
701 ASSERT_NE(File::FILE_OK, file.Lock()); | |
702 ASSERT_TRUE(SignalEvent(temp_path, kSignalLockFileClose)); | |
703 | |
704 // After child has closed, test that the file can be locked and signal the | |
705 // child to exit. | |
706 ASSERT_TRUE(WaitForEventOrTimeout(temp_path, kSignalLockFileClosed)); | |
707 ASSERT_EQ(File::FILE_OK, file.Lock()); | |
708 ASSERT_EQ(File::FILE_OK, file.Unlock()); | |
709 ASSERT_TRUE(SignalEvent(temp_path, kSignalExit)); | |
710 | |
711 int rv = -1; | |
712 ASSERT_TRUE(test_child_process.WaitForExitWithTimeout( | |
713 TestTimeouts::action_timeout(), &rv)); | |
714 ASSERT_EQ(0, rv); | |
715 } | |
716 | |
717 // Subprocess to test getting a lock then releasing it implicitly on exit or | |
718 // termination. | |
719 MULTIPROCESS_TEST_MAIN(ChildLockExit) { | |
720 base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); | |
721 const base::FilePath temp_path = | |
722 command_line->GetSwitchValuePath(kTempDirFlag); | |
723 CHECK(base::DirectoryExists(temp_path)); | |
724 | |
725 // Wait for signal to lock, then lock the file. | |
726 CHECK(WaitForEvent(temp_path, kSignalLockFileLock)); | |
727 File file(temp_path.AppendASCII(kLockFile), | |
728 File::FLAG_OPEN | File::FLAG_READ | File::FLAG_WRITE); | |
729 CHECK(file.IsValid()); | |
730 CHECK_EQ(File::FILE_OK, file.Lock()); | |
731 CHECK(SignalEvent(temp_path, kSignalLockFileLocked)); | |
732 | |
733 // Wait for signal to exit, so that exit can be distinguished from kill. | |
734 CHECK(WaitForEvent(temp_path, kSignalExit)); | |
735 return 0; | |
736 } | |
737 | |
738 // Test that locks are released on exit. | |
739 TEST(FileTest, UnlockOnExit) { | |
740 // Create a temporary dir and spin up a ChildLockExit subprocess against it. | |
741 base::ScopedTempDir temp_dir; | |
742 ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); | |
743 const base::FilePath temp_path = temp_dir.path(); | |
744 base::CommandLine child_command_line( | |
745 base::GetMultiProcessTestChildBaseCommandLine()); | |
746 child_command_line.AppendSwitchPath(kTempDirFlag, temp_path); | |
747 base::Process test_child_process = | |
748 base::SpawnMultiProcessTestChild("ChildLockExit", child_command_line, | |
749 base::LaunchOptions()); | |
750 | |
751 // Create the lock file and verify that it can be locked and unlocked. | |
752 File file(temp_path.AppendASCII(kLockFile), | |
753 File::FLAG_CREATE | File::FLAG_READ | File::FLAG_WRITE); | |
754 ASSERT_TRUE(file.IsValid()); | |
755 ASSERT_EQ(File::FILE_OK, file.Lock()); | |
756 ASSERT_EQ(File::FILE_OK, file.Unlock()); | |
757 | |
758 // Signal child to lock the file. | |
759 ASSERT_TRUE(SignalEvent(temp_path, kSignalLockFileLock)); | |
760 | |
761 // After child has locked the file, test the lock, then signal child to exit. | |
762 ASSERT_TRUE(WaitForEventOrTimeout(temp_path, kSignalLockFileLocked)); | |
763 ASSERT_NE(File::FILE_OK, file.Lock()); | |
764 ASSERT_TRUE(SignalEvent(temp_path, kSignalExit)); | |
765 | |
766 int rv = -1; | |
767 ASSERT_TRUE(test_child_process.WaitForExitWithTimeout( | |
768 TestTimeouts::action_timeout(), &rv)); | |
769 ASSERT_EQ(0, rv); | |
770 | |
771 // Exit released the child's lock. | |
772 ASSERT_EQ(File::FILE_OK, file.Lock()); | |
773 ASSERT_EQ(File::FILE_OK, file.Unlock()); | |
774 } | |
775 | |
776 // Test that locks are released when a process is terminated. This should cover | |
777 // the case of crashing, also. | |
778 TEST(FileTest, UnlockOnTerminate) { | |
779 // Create a temporary dir and spin up a ChildLockExit subprocess against it. | |
780 base::ScopedTempDir temp_dir; | |
781 ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); | |
782 const base::FilePath temp_path = temp_dir.path(); | |
783 base::CommandLine child_command_line( | |
784 base::GetMultiProcessTestChildBaseCommandLine()); | |
785 child_command_line.AppendSwitchPath(kTempDirFlag, temp_path); | |
786 base::Process test_child_process = | |
787 base::SpawnMultiProcessTestChild("ChildLockExit", child_command_line, | |
788 base::LaunchOptions()); | |
789 | |
790 // Create the lock file and verify that it can be locked and unlocked. | |
791 File file(temp_path.AppendASCII(kLockFile), | |
792 File::FLAG_CREATE | File::FLAG_READ | File::FLAG_WRITE); | |
793 ASSERT_TRUE(file.IsValid()); | |
794 ASSERT_EQ(File::FILE_OK, file.Lock()); | |
795 ASSERT_EQ(File::FILE_OK, file.Unlock()); | |
796 | |
797 // Signal child to lock the file. | |
798 ASSERT_TRUE(SignalEvent(temp_path, kSignalLockFileLock)); | |
799 | |
800 // After child has locked the file, test the lock, then terminate the child. | |
801 ASSERT_TRUE(WaitForEventOrTimeout(temp_path, kSignalLockFileLocked)); | |
802 ASSERT_NE(File::FILE_OK, file.Lock()); | |
803 ASSERT_TRUE(test_child_process.Terminate(0, true)); | |
804 | |
805 // Termination released the child's lock. | |
806 ASSERT_EQ(File::FILE_OK, file.Lock()); | |
807 ASSERT_EQ(File::FILE_OK, file.Unlock()); | |
808 } | |
OLD | NEW |