Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(910)

Unified Diff: base/files/file_unittest.cc

Issue 1491743009: [base] POSIX File::Unlock() didn't actually unlock file. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: forgot some comments Created 5 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « base/files/file_posix.cc ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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());
+}
« no previous file with comments | « base/files/file_posix.cc ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698