Index: base/files/file_locking_unittest.cc |
diff --git a/base/files/file_locking_unittest.cc b/base/files/file_locking_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..85b5d176015c04d50c745612da9148167920cd3f |
--- /dev/null |
+++ b/base/files/file_locking_unittest.cc |
@@ -0,0 +1,227 @@ |
+// Copyright (c) 2015 The Chromium Authors. All rights reserved. |
Lei Zhang
2015/12/10 04:02:52
no: (c)
Scott Hess - ex-Googler
2015/12/10 20:12:08
FML.
|
+// 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; |
+ |
+namespace { |
+ |
+// Flag for the parent to share a temp dir to the child. |
+const char kTempDirFlag[] = "temp-dir"; |
+ |
+// Flags to control how the subprocess unlocks the file. |
+const char kFileUnlock[] = "file-unlock"; |
+const char kCloseUnlock[] = "close-unlock"; |
+const char kExitUnlock[] = "exit-unlock"; |
+ |
+// 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 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 FilePath& signal_dir, const char* signal_file) { |
+ File file(signal_dir.AppendASCII(signal_file), |
+ File::FLAG_CREATE | File::FLAG_APPEND); |
Lei Zhang
2015/12/10 04:02:52
Do you need FLAG_APPEND?
Scott Hess - ex-Googler
2015/12/10 20:12:08
I cannot say why I used that (I'd guess I was figu
|
+ return file.IsValid(); |
+} |
+ |
+// Check whether an event was signaled. |
+bool CheckEvent(const FilePath& signal_dir, const char* signal_file) { |
+ File file(signal_dir.AppendASCII(signal_file), |
+ File::FLAG_OPEN | File::FLAG_READ); |
+ return file.IsValid(); |
+} |
+ |
+// Busy-wait for an event to be signaled, returning false for timeout. |
+bool WaitForEventWithTimeout(const FilePath& signal_dir, |
+ const char* 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 FilePath& signal_dir, const char* signal_file) { |
+ return WaitForEventWithTimeout(signal_dir, signal_file, |
+ base::TimeDelta::Max()); |
+} |
+ |
+// Keep these in sync so StartChild*() can refer to correct test main. |
+#define ChildMain ChildLockUnlock |
+#define ChildMainString "ChildLockUnlock" |
+ |
+// Subprocess to test getting a file lock then releasing it. |kTempDirFlag| |
+// must pass in an existing temporary directory for the lockfile and signal |
+// files. One of the following flags must be passed to determine how to unlock |
+// the lock file: |
+// - |kFileUnlock| calls Unlock() to unlock. |
+// - |kCloseUnlock| calls Close() while the lock is held. |
+// - |kExitUnlock| exits while the lock is held. |
+MULTIPROCESS_TEST_MAIN(ChildMain) { |
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
+ const FilePath temp_path = command_line->GetSwitchValuePath(kTempDirFlag); |
+ CHECK(base::DirectoryExists(temp_path)); |
+ |
+ // Immediately lock the file. |
+ 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)); |
+ |
+ if (command_line->HasSwitch(kFileUnlock)) { |
+ // 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)); |
+ } else if (command_line->HasSwitch(kCloseUnlock)) { |
+ // Wait for the signal to close, then close the file. |
+ CHECK(WaitForEvent(temp_path, kSignalLockFileClose)); |
+ file.Close(); |
+ CHECK(!file.IsValid()); |
+ CHECK(SignalEvent(temp_path, kSignalLockFileClosed)); |
+ } else { |
+ CHECK(command_line->HasSwitch(kExitUnlock)); |
+ } |
+ |
+ // Wait for signal to exit, so that unlock or close can be distinguished from |
+ // exit. |
+ CHECK(WaitForEvent(temp_path, kSignalExit)); |
+ return 0; |
+} |
+ |
+} // namespace |
+ |
+class FileLockingTest : public testing::Test { |
+ public: |
+ FileLockingTest() {} |
+ |
+ protected: |
+ void SetUp() override { |
+ testing::Test::SetUp(); |
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); |
+ |
+ // Create the lock file and verify that it can be locked and unlocked. |
+ lock_file_.Initialize( |
+ temp_dir_.path().AppendASCII(kLockFile), |
+ File::FLAG_CREATE | File::FLAG_READ | File::FLAG_WRITE); |
+ ASSERT_TRUE(lock_file_.IsValid()); |
+ ASSERT_EQ(File::FILE_OK, lock_file_.Lock()); |
+ ASSERT_EQ(File::FILE_OK, lock_file_.Unlock()); |
Scott Hess - ex-Googler
2015/12/08 00:55:43
I just did a test reverting the file_posix.cc chan
Lei Zhang
2015/12/10 04:02:52
Acknowledged.
|
+ } |
+ |
+ bool SignalEvent(const char* signal_file) { |
+ return ::SignalEvent(temp_dir_.path(), signal_file); |
+ } |
+ |
+ bool WaitForEventOrTimeout(const char* signal_file) { |
+ return ::WaitForEventWithTimeout(temp_dir_.path(), signal_file, |
+ TestTimeouts::action_timeout()); |
+ } |
+ |
+ // Start a child process set to use the specified unlock action, and wait for |
+ // it to lock the file. |
+ void StartChildAndSignalLock(const char* unlock_action) { |
+ // Create a temporary dir and spin up a ChildLockExit subprocess against it. |
+ const FilePath temp_path = temp_dir_.path(); |
+ base::CommandLine child_command_line( |
+ base::GetMultiProcessTestChildBaseCommandLine()); |
+ child_command_line.AppendSwitchPath(kTempDirFlag, temp_path); |
+ child_command_line.AppendSwitch(unlock_action); |
+ lock_child_ = |
+ base::SpawnMultiProcessTestChild(ChildMainString, child_command_line, |
+ base::LaunchOptions()); |
+ ASSERT_TRUE(lock_child_.IsValid()); |
+ |
+ // Wait for the child to lock the file. |
+ ASSERT_TRUE(WaitForEventOrTimeout(kSignalLockFileLocked)); |
+ } |
+ |
+ // Signal the child to exit cleanly. |
+ void ExitChildCleanly() { |
+ ASSERT_TRUE(SignalEvent(kSignalExit)); |
+ int rv = -1; |
+ ASSERT_TRUE(lock_child_.WaitForExitWithTimeout( |
+ TestTimeouts::action_timeout(), &rv)); |
+ ASSERT_EQ(0, rv); |
+ } |
+ |
+ base::ScopedTempDir temp_dir_; |
+ base::File lock_file_; |
+ base::Process lock_child_; |
+ |
+ private: |
+ DISALLOW_COPY_AND_ASSIGN(FileLockingTest); |
+}; |
+ |
+// Test that locks are released by Unlock(). |
+TEST_F(FileLockingTest, LockAndUnlock) { |
+ StartChildAndSignalLock(kFileUnlock); |
+ |
+ ASSERT_NE(File::FILE_OK, lock_file_.Lock()); |
+ ASSERT_TRUE(SignalEvent(kSignalLockFileUnlock)); |
+ ASSERT_TRUE(WaitForEventOrTimeout(kSignalLockFileUnlocked)); |
+ ASSERT_EQ(File::FILE_OK, lock_file_.Lock()); |
+ ASSERT_EQ(File::FILE_OK, lock_file_.Unlock()); |
+ |
+ ExitChildCleanly(); |
+} |
+ |
+// Test that locks are released on Close(). |
+TEST_F(FileLockingTest, UnlockOnClose) { |
+ StartChildAndSignalLock(kCloseUnlock); |
+ |
+ ASSERT_NE(File::FILE_OK, lock_file_.Lock()); |
+ ASSERT_TRUE(SignalEvent(kSignalLockFileClose)); |
+ ASSERT_TRUE(WaitForEventOrTimeout(kSignalLockFileClosed)); |
+ ASSERT_EQ(File::FILE_OK, lock_file_.Lock()); |
+ ASSERT_EQ(File::FILE_OK, lock_file_.Unlock()); |
+ |
+ ExitChildCleanly(); |
+} |
+ |
+// Test that locks are released on exit. |
+TEST_F(FileLockingTest, UnlockOnExit) { |
+ StartChildAndSignalLock(kExitUnlock); |
+ |
+ ASSERT_NE(File::FILE_OK, lock_file_.Lock()); |
+ ExitChildCleanly(); |
+ ASSERT_EQ(File::FILE_OK, lock_file_.Lock()); |
+ ASSERT_EQ(File::FILE_OK, lock_file_.Unlock()); |
+} |
+ |
+// Test that killing the process releases the lock. This should cover crashing. |
+TEST_F(FileLockingTest, UnlockOnTerminate) { |
+ // The child will wait for an exit which never arrives. |
+ StartChildAndSignalLock(kExitUnlock); |
+ |
+ ASSERT_NE(File::FILE_OK, lock_file_.Lock()); |
+ ASSERT_TRUE(lock_child_.Terminate(0, true)); |
+ ASSERT_EQ(File::FILE_OK, lock_file_.Lock()); |
+ ASSERT_EQ(File::FILE_OK, lock_file_.Unlock()); |
+} |