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

Unified Diff: base/files/file_locking_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: Move lock tests to new file, with test fixture, share code. 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/base.gyp ('k') | base/files/file_posix.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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());
+}
« no previous file with comments | « base/base.gyp ('k') | base/files/file_posix.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698