Index: base/files/file_unittest.cc |
diff --git a/base/files/file_unittest.cc b/base/files/file_unittest.cc |
index 67dbbfd1ec87eecc9eeb14f9c71d5082f2e5c847..ac754700317af69d4af5531f6c157a00a19c93db 100644 |
--- a/base/files/file_unittest.cc |
+++ b/base/files/file_unittest.cc |
@@ -2,11 +2,16 @@ |
// Use of this source code is governed by a BSD-style license that can be |
// found in the LICENSE file. |
+#include "base/command_line.h" |
#include "base/files/file.h" |
#include "base/files/file_util.h" |
#include "base/files/scoped_temp_dir.h" |
+#include "base/test/multiprocess_test.h" |
+#include "base/test/test_timeouts.h" |
+#include "base/threading/platform_thread.h" |
#include "base/time/time.h" |
#include "testing/gtest/include/gtest/gtest.h" |
+#include "testing/multiprocess_func_list.h" |
using base::File; |
using base::FilePath; |
@@ -509,3 +514,295 @@ TEST(FileTest, GetInfoForDirectory) { |
EXPECT_EQ(0, info.size); |
} |
#endif // defined(OS_WIN) |
+ |
+// Constants and helper function for lock/unlock tests. |
+namespace { |
+ |
+// Flag for the parent to share a temp dir to the child. |
+const char kTempDirFlag[] = "temp-dir"; |
+ |
+// File to lock in temp dir. |
+const char kLockFile[] = "lockfile"; |
+ |
+// Constants for various requests and responses, used as |signal_file| parameter |
+// to signal/wait helpers. |
+const char kSignalLockFileLock[] = "lock.signal"; |
+const char kSignalLockFileLocked[] = "locked.signal"; |
+const char kSignalLockFileClose[] = "close.signal"; |
+const char kSignalLockFileClosed[] = "closed.signal"; |
+const char kSignalLockFileUnlock[] = "unlock.signal"; |
+const char kSignalLockFileUnlocked[] = "unlocked.signal"; |
+const char kSignalExit[] = "exit.signal"; |
+ |
+// Signal an event by creating a file which didn't previously exist. |
+bool SignalEvent(const base::FilePath& signal_dir, |
+ const base::StringPiece& signal_file) { |
+ File file(signal_dir.AppendASCII(signal_file), |
+ File::FLAG_CREATE | File::FLAG_APPEND); |
+ return file.IsValid(); |
+} |
+ |
+// Check whether an event was signaled. |
+bool CheckEvent(const base::FilePath& signal_dir, |
+ const base::StringPiece& signal_file) { |
+ File file(signal_dir.AppendASCII(signal_file), |
+ File::FLAG_OPEN | File::FLAG_READ); |
+ return file.IsValid(); |
+} |
+ |
+// Wait for an event to be signaled, returning false if it times out. |
+bool WaitForEventWithTimeout(const base::FilePath& signal_dir, |
+ const base::StringPiece& signal_file, |
+ const base::TimeDelta& timeout) { |
+ const base::Time finish_by = base::Time::Now() + timeout; |
+ while (!CheckEvent(signal_dir, signal_file)) { |
+ if (base::Time::Now() > finish_by) |
+ return false; |
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10)); |
+ } |
+ return true; |
+} |
+ |
+// Wait forever for the event to be signaled (should never return false). |
+bool WaitForEvent(const base::FilePath& signal_dir, |
+ const base::StringPiece& signal_file) { |
+ return WaitForEventWithTimeout(signal_dir, signal_file, |
+ base::TimeDelta::Max()); |
+} |
+ |
+// Wait for an event or the action timeout, whichever comes first. |
+bool WaitForEventOrTimeout(const base::FilePath& signal_dir, |
+ const base::StringPiece& signal_file) { |
+ return WaitForEventWithTimeout(signal_dir, signal_file, |
+ TestTimeouts::action_timeout()); |
+} |
+ |
+} // namespace |
+ |
+// Subprocess to test getting a file lock then releasing it. |
+MULTIPROCESS_TEST_MAIN(ChildLockUnlock) { |
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
+ const base::FilePath temp_path = |
+ command_line->GetSwitchValuePath(kTempDirFlag); |
+ CHECK(base::DirectoryExists(temp_path)); |
+ |
+ // Wait for signal to lock, then lock the file. |
+ CHECK(WaitForEvent(temp_path, kSignalLockFileLock)); |
+ File file(temp_path.AppendASCII(kLockFile), |
+ File::FLAG_OPEN | File::FLAG_READ | File::FLAG_WRITE); |
+ CHECK(file.IsValid()); |
+ CHECK_EQ(File::FILE_OK, file.Lock()); |
+ CHECK(SignalEvent(temp_path, kSignalLockFileLocked)); |
+ |
+ // Wait for signal to unlock, then unlock the file. |
+ CHECK(WaitForEvent(temp_path, kSignalLockFileUnlock)); |
+ CHECK_EQ(File::FILE_OK, file.Unlock()); |
+ CHECK(SignalEvent(temp_path, kSignalLockFileUnlocked)); |
+ |
+ // Wait for signal to exit, so that the unlock can be distinguished from exit. |
+ CHECK(WaitForEvent(temp_path, kSignalExit)); |
+ return 0; |
+} |
+ |
+// Test that the child's lock prevents the parent from getting the lock, and |
+// that the child's unlock allows the parent to get the lock. |
+TEST(FileTest, LockAndUnlock) { |
+ // Create a temporary dir and spin up a ChildLockExit subprocess against it. |
+ base::ScopedTempDir temp_dir; |
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); |
+ const base::FilePath temp_path = temp_dir.path(); |
+ base::CommandLine child_command_line( |
+ base::GetMultiProcessTestChildBaseCommandLine()); |
+ child_command_line.AppendSwitchPath(kTempDirFlag, temp_path); |
+ base::Process test_child_process = |
Lei Zhang
2015/12/04 03:23:03
Check |test_child_process| is valid before continu
|
+ base::SpawnMultiProcessTestChild("ChildLockUnlock", child_command_line, |
+ base::LaunchOptions()); |
+ |
+ // Create the lock file and verify that it can be locked and unlocked. |
+ File file(temp_path.AppendASCII(kLockFile), |
+ File::FLAG_CREATE | File::FLAG_READ | File::FLAG_WRITE); |
+ ASSERT_TRUE(file.IsValid()); |
+ ASSERT_EQ(File::FILE_OK, file.Lock()); |
+ ASSERT_EQ(File::FILE_OK, file.Unlock()); |
+ |
+ // Signal child to lock the file. |
+ ASSERT_TRUE(SignalEvent(temp_path, kSignalLockFileLock)); |
+ |
+ // After child has locked the file, test the lock, then signal child to unlock |
+ // the file. |
+ ASSERT_TRUE(WaitForEventOrTimeout(temp_path, kSignalLockFileLocked)); |
+ ASSERT_NE(File::FILE_OK, file.Lock()); |
+ ASSERT_TRUE(SignalEvent(temp_path, kSignalLockFileUnlock)); |
+ |
+ // After child has unlocked, test that the file can be locked and signal the |
+ // child to exit. |
+ ASSERT_TRUE(WaitForEventOrTimeout(temp_path, kSignalLockFileUnlocked)); |
+ ASSERT_EQ(File::FILE_OK, file.Lock()); |
+ ASSERT_EQ(File::FILE_OK, file.Unlock()); |
+ ASSERT_TRUE(SignalEvent(temp_path, kSignalExit)); |
+ |
+ int rv = -1; |
+ ASSERT_TRUE(test_child_process.WaitForExitWithTimeout( |
+ TestTimeouts::action_timeout(), &rv)); |
+ ASSERT_EQ(0, rv); |
+} |
+ |
+// Subprocess to test getting a lock then releasing it implicitly on close. |
+MULTIPROCESS_TEST_MAIN(ChildLockClose) { |
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
+ const base::FilePath temp_path = |
+ command_line->GetSwitchValuePath(kTempDirFlag); |
+ CHECK(base::DirectoryExists(temp_path)); |
+ |
+ // Wait for signal to lock, then lock the file. |
+ CHECK(WaitForEvent(temp_path, kSignalLockFileLock)); |
+ File file(temp_path.AppendASCII(kLockFile), |
+ File::FLAG_OPEN | File::FLAG_READ | File::FLAG_WRITE); |
+ CHECK(file.IsValid()); |
+ CHECK_EQ(File::FILE_OK, file.Lock()); |
+ CHECK(SignalEvent(temp_path, kSignalLockFileLocked)); |
+ |
+ // Wait for the signal to close, then close the file. |
+ 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
|
+ file.Close(); |
+ CHECK(SignalEvent(temp_path, kSignalLockFileClosed)); |
+ |
+ // Wait for signal to exit, so that close can be distinguished from exit. |
+ CHECK(WaitForEvent(temp_path, kSignalExit)); |
+ return 0; |
+} |
+ |
+// Test that closing the handle in the child implicitly releases the lock. |
+TEST(FileTest, UnlockOnClose) { |
+ // Create a temporary dir and spin up a ChildLockExit subprocess against it. |
+ base::ScopedTempDir temp_dir; |
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); |
+ const base::FilePath temp_path = temp_dir.path(); |
+ base::CommandLine child_command_line( |
+ base::GetMultiProcessTestChildBaseCommandLine()); |
+ child_command_line.AppendSwitchPath(kTempDirFlag, temp_path); |
+ base::Process test_child_process = |
+ base::SpawnMultiProcessTestChild("ChildLockClose", child_command_line, |
+ base::LaunchOptions()); |
+ |
+ // Create the lock file and verify that it can be locked and unlocked. |
+ File file(temp_path.AppendASCII(kLockFile), |
+ File::FLAG_CREATE | File::FLAG_READ | File::FLAG_WRITE); |
+ ASSERT_TRUE(file.IsValid()); |
+ ASSERT_EQ(File::FILE_OK, file.Lock()); |
+ ASSERT_EQ(File::FILE_OK, file.Unlock()); |
+ |
+ // Signal child to lock the file. |
+ ASSERT_TRUE(SignalEvent(temp_path, kSignalLockFileLock)); |
+ |
+ // After child has locked the file, test the lock, then signal child to close |
+ // the file. |
+ ASSERT_TRUE(WaitForEventOrTimeout(temp_path, kSignalLockFileLocked)); |
+ ASSERT_NE(File::FILE_OK, file.Lock()); |
+ ASSERT_TRUE(SignalEvent(temp_path, kSignalLockFileClose)); |
+ |
+ // After child has closed, test that the file can be locked and signal the |
+ // child to exit. |
+ ASSERT_TRUE(WaitForEventOrTimeout(temp_path, kSignalLockFileClosed)); |
+ ASSERT_EQ(File::FILE_OK, file.Lock()); |
+ ASSERT_EQ(File::FILE_OK, file.Unlock()); |
+ ASSERT_TRUE(SignalEvent(temp_path, kSignalExit)); |
+ |
+ int rv = -1; |
+ ASSERT_TRUE(test_child_process.WaitForExitWithTimeout( |
+ TestTimeouts::action_timeout(), &rv)); |
+ ASSERT_EQ(0, rv); |
+} |
+ |
+// Subprocess to test getting a lock then releasing it implicitly on exit or |
+// termination. |
+MULTIPROCESS_TEST_MAIN(ChildLockExit) { |
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
+ const base::FilePath temp_path = |
+ command_line->GetSwitchValuePath(kTempDirFlag); |
+ CHECK(base::DirectoryExists(temp_path)); |
+ |
+ // Wait for signal to lock, then lock the file. |
+ CHECK(WaitForEvent(temp_path, kSignalLockFileLock)); |
+ File file(temp_path.AppendASCII(kLockFile), |
+ File::FLAG_OPEN | File::FLAG_READ | File::FLAG_WRITE); |
+ CHECK(file.IsValid()); |
+ CHECK_EQ(File::FILE_OK, file.Lock()); |
+ CHECK(SignalEvent(temp_path, kSignalLockFileLocked)); |
+ |
+ // Wait for signal to exit, so that exit can be distinguished from kill. |
+ CHECK(WaitForEvent(temp_path, kSignalExit)); |
+ return 0; |
+} |
+ |
+// Test that locks are released on exit. |
+TEST(FileTest, UnlockOnExit) { |
+ // Create a temporary dir and spin up a ChildLockExit subprocess against it. |
+ base::ScopedTempDir temp_dir; |
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); |
+ const base::FilePath temp_path = temp_dir.path(); |
+ base::CommandLine child_command_line( |
+ base::GetMultiProcessTestChildBaseCommandLine()); |
+ child_command_line.AppendSwitchPath(kTempDirFlag, temp_path); |
+ base::Process test_child_process = |
+ base::SpawnMultiProcessTestChild("ChildLockExit", child_command_line, |
+ base::LaunchOptions()); |
+ |
+ // Create the lock file and verify that it can be locked and unlocked. |
+ File file(temp_path.AppendASCII(kLockFile), |
+ File::FLAG_CREATE | File::FLAG_READ | File::FLAG_WRITE); |
+ ASSERT_TRUE(file.IsValid()); |
+ ASSERT_EQ(File::FILE_OK, file.Lock()); |
+ ASSERT_EQ(File::FILE_OK, file.Unlock()); |
+ |
+ // Signal child to lock the file. |
+ ASSERT_TRUE(SignalEvent(temp_path, kSignalLockFileLock)); |
+ |
+ // After child has locked the file, test the lock, then signal child to exit. |
+ ASSERT_TRUE(WaitForEventOrTimeout(temp_path, kSignalLockFileLocked)); |
+ ASSERT_NE(File::FILE_OK, file.Lock()); |
+ ASSERT_TRUE(SignalEvent(temp_path, kSignalExit)); |
+ |
+ int rv = -1; |
+ ASSERT_TRUE(test_child_process.WaitForExitWithTimeout( |
+ TestTimeouts::action_timeout(), &rv)); |
+ ASSERT_EQ(0, rv); |
+ |
+ // Exit released the child's lock. |
+ ASSERT_EQ(File::FILE_OK, file.Lock()); |
+ ASSERT_EQ(File::FILE_OK, file.Unlock()); |
+} |
+ |
+// Test that locks are released when a process is terminated. This should cover |
+// the case of crashing, also. |
+TEST(FileTest, UnlockOnTerminate) { |
+ // Create a temporary dir and spin up a ChildLockExit subprocess against it. |
+ base::ScopedTempDir temp_dir; |
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); |
+ const base::FilePath temp_path = temp_dir.path(); |
+ base::CommandLine child_command_line( |
+ base::GetMultiProcessTestChildBaseCommandLine()); |
+ child_command_line.AppendSwitchPath(kTempDirFlag, temp_path); |
+ base::Process test_child_process = |
+ base::SpawnMultiProcessTestChild("ChildLockExit", child_command_line, |
+ base::LaunchOptions()); |
+ |
+ // Create the lock file and verify that it can be locked and unlocked. |
+ File file(temp_path.AppendASCII(kLockFile), |
+ File::FLAG_CREATE | File::FLAG_READ | File::FLAG_WRITE); |
+ ASSERT_TRUE(file.IsValid()); |
+ ASSERT_EQ(File::FILE_OK, file.Lock()); |
+ ASSERT_EQ(File::FILE_OK, file.Unlock()); |
+ |
+ // Signal child to lock the file. |
+ ASSERT_TRUE(SignalEvent(temp_path, kSignalLockFileLock)); |
+ |
+ // After child has locked the file, test the lock, then terminate the child. |
+ ASSERT_TRUE(WaitForEventOrTimeout(temp_path, kSignalLockFileLocked)); |
+ ASSERT_NE(File::FILE_OK, file.Lock()); |
+ ASSERT_TRUE(test_child_process.Terminate(0, true)); |
+ |
+ // Termination released the child's lock. |
+ ASSERT_EQ(File::FILE_OK, file.Lock()); |
+ ASSERT_EQ(File::FILE_OK, file.Unlock()); |
+} |