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

Side by Side 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 unified diff | Download patch
« no previous file with comments | « base/base.gyp ('k') | base/files/file_posix.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // 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.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/command_line.h"
6 #include "base/files/file.h"
7 #include "base/files/file_util.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"
12 #include "base/time/time.h"
13 #include "testing/gtest/include/gtest/gtest.h"
14 #include "testing/multiprocess_func_list.h"
15
16 using base::File;
17 using base::FilePath;
18
19 namespace {
20
21 // Flag for the parent to share a temp dir to the child.
22 const char kTempDirFlag[] = "temp-dir";
23
24 // Flags to control how the subprocess unlocks the file.
25 const char kFileUnlock[] = "file-unlock";
26 const char kCloseUnlock[] = "close-unlock";
27 const char kExitUnlock[] = "exit-unlock";
28
29 // File to lock in temp dir.
30 const char kLockFile[] = "lockfile";
31
32 // Constants for various requests and responses, used as |signal_file| parameter
33 // to signal/wait helpers.
34 const char kSignalLockFileLocked[] = "locked.signal";
35 const char kSignalLockFileClose[] = "close.signal";
36 const char kSignalLockFileClosed[] = "closed.signal";
37 const char kSignalLockFileUnlock[] = "unlock.signal";
38 const char kSignalLockFileUnlocked[] = "unlocked.signal";
39 const char kSignalExit[] = "exit.signal";
40
41 // Signal an event by creating a file which didn't previously exist.
42 bool SignalEvent(const FilePath& signal_dir, const char* signal_file) {
43 File file(signal_dir.AppendASCII(signal_file),
44 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
45 return file.IsValid();
46 }
47
48 // Check whether an event was signaled.
49 bool CheckEvent(const FilePath& signal_dir, const char* signal_file) {
50 File file(signal_dir.AppendASCII(signal_file),
51 File::FLAG_OPEN | File::FLAG_READ);
52 return file.IsValid();
53 }
54
55 // Busy-wait for an event to be signaled, returning false for timeout.
56 bool WaitForEventWithTimeout(const FilePath& signal_dir,
57 const char* signal_file,
58 const base::TimeDelta& timeout) {
59 const base::Time finish_by = base::Time::Now() + timeout;
60 while (!CheckEvent(signal_dir, signal_file)) {
61 if (base::Time::Now() > finish_by)
62 return false;
63 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10));
64 }
65 return true;
66 }
67
68 // Wait forever for the event to be signaled (should never return false).
69 bool WaitForEvent(const FilePath& signal_dir, const char* signal_file) {
70 return WaitForEventWithTimeout(signal_dir, signal_file,
71 base::TimeDelta::Max());
72 }
73
74 // Keep these in sync so StartChild*() can refer to correct test main.
75 #define ChildMain ChildLockUnlock
76 #define ChildMainString "ChildLockUnlock"
77
78 // Subprocess to test getting a file lock then releasing it. |kTempDirFlag|
79 // must pass in an existing temporary directory for the lockfile and signal
80 // files. One of the following flags must be passed to determine how to unlock
81 // the lock file:
82 // - |kFileUnlock| calls Unlock() to unlock.
83 // - |kCloseUnlock| calls Close() while the lock is held.
84 // - |kExitUnlock| exits while the lock is held.
85 MULTIPROCESS_TEST_MAIN(ChildMain) {
86 base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
87 const FilePath temp_path = command_line->GetSwitchValuePath(kTempDirFlag);
88 CHECK(base::DirectoryExists(temp_path));
89
90 // Immediately lock the file.
91 File file(temp_path.AppendASCII(kLockFile),
92 File::FLAG_OPEN | File::FLAG_READ | File::FLAG_WRITE);
93 CHECK(file.IsValid());
94 CHECK_EQ(File::FILE_OK, file.Lock());
95 CHECK(SignalEvent(temp_path, kSignalLockFileLocked));
96
97 if (command_line->HasSwitch(kFileUnlock)) {
98 // Wait for signal to unlock, then unlock the file.
99 CHECK(WaitForEvent(temp_path, kSignalLockFileUnlock));
100 CHECK_EQ(File::FILE_OK, file.Unlock());
101 CHECK(SignalEvent(temp_path, kSignalLockFileUnlocked));
102 } else if (command_line->HasSwitch(kCloseUnlock)) {
103 // Wait for the signal to close, then close the file.
104 CHECK(WaitForEvent(temp_path, kSignalLockFileClose));
105 file.Close();
106 CHECK(!file.IsValid());
107 CHECK(SignalEvent(temp_path, kSignalLockFileClosed));
108 } else {
109 CHECK(command_line->HasSwitch(kExitUnlock));
110 }
111
112 // Wait for signal to exit, so that unlock or close can be distinguished from
113 // exit.
114 CHECK(WaitForEvent(temp_path, kSignalExit));
115 return 0;
116 }
117
118 } // namespace
119
120 class FileLockingTest : public testing::Test {
121 public:
122 FileLockingTest() {}
123
124 protected:
125 void SetUp() override {
126 testing::Test::SetUp();
127 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
128
129 // Create the lock file and verify that it can be locked and unlocked.
130 lock_file_.Initialize(
131 temp_dir_.path().AppendASCII(kLockFile),
132 File::FLAG_CREATE | File::FLAG_READ | File::FLAG_WRITE);
133 ASSERT_TRUE(lock_file_.IsValid());
134 ASSERT_EQ(File::FILE_OK, lock_file_.Lock());
135 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.
136 }
137
138 bool SignalEvent(const char* signal_file) {
139 return ::SignalEvent(temp_dir_.path(), signal_file);
140 }
141
142 bool WaitForEventOrTimeout(const char* signal_file) {
143 return ::WaitForEventWithTimeout(temp_dir_.path(), signal_file,
144 TestTimeouts::action_timeout());
145 }
146
147 // Start a child process set to use the specified unlock action, and wait for
148 // it to lock the file.
149 void StartChildAndSignalLock(const char* unlock_action) {
150 // Create a temporary dir and spin up a ChildLockExit subprocess against it.
151 const FilePath temp_path = temp_dir_.path();
152 base::CommandLine child_command_line(
153 base::GetMultiProcessTestChildBaseCommandLine());
154 child_command_line.AppendSwitchPath(kTempDirFlag, temp_path);
155 child_command_line.AppendSwitch(unlock_action);
156 lock_child_ =
157 base::SpawnMultiProcessTestChild(ChildMainString, child_command_line,
158 base::LaunchOptions());
159 ASSERT_TRUE(lock_child_.IsValid());
160
161 // Wait for the child to lock the file.
162 ASSERT_TRUE(WaitForEventOrTimeout(kSignalLockFileLocked));
163 }
164
165 // Signal the child to exit cleanly.
166 void ExitChildCleanly() {
167 ASSERT_TRUE(SignalEvent(kSignalExit));
168 int rv = -1;
169 ASSERT_TRUE(lock_child_.WaitForExitWithTimeout(
170 TestTimeouts::action_timeout(), &rv));
171 ASSERT_EQ(0, rv);
172 }
173
174 base::ScopedTempDir temp_dir_;
175 base::File lock_file_;
176 base::Process lock_child_;
177
178 private:
179 DISALLOW_COPY_AND_ASSIGN(FileLockingTest);
180 };
181
182 // Test that locks are released by Unlock().
183 TEST_F(FileLockingTest, LockAndUnlock) {
184 StartChildAndSignalLock(kFileUnlock);
185
186 ASSERT_NE(File::FILE_OK, lock_file_.Lock());
187 ASSERT_TRUE(SignalEvent(kSignalLockFileUnlock));
188 ASSERT_TRUE(WaitForEventOrTimeout(kSignalLockFileUnlocked));
189 ASSERT_EQ(File::FILE_OK, lock_file_.Lock());
190 ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
191
192 ExitChildCleanly();
193 }
194
195 // Test that locks are released on Close().
196 TEST_F(FileLockingTest, UnlockOnClose) {
197 StartChildAndSignalLock(kCloseUnlock);
198
199 ASSERT_NE(File::FILE_OK, lock_file_.Lock());
200 ASSERT_TRUE(SignalEvent(kSignalLockFileClose));
201 ASSERT_TRUE(WaitForEventOrTimeout(kSignalLockFileClosed));
202 ASSERT_EQ(File::FILE_OK, lock_file_.Lock());
203 ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
204
205 ExitChildCleanly();
206 }
207
208 // Test that locks are released on exit.
209 TEST_F(FileLockingTest, UnlockOnExit) {
210 StartChildAndSignalLock(kExitUnlock);
211
212 ASSERT_NE(File::FILE_OK, lock_file_.Lock());
213 ExitChildCleanly();
214 ASSERT_EQ(File::FILE_OK, lock_file_.Lock());
215 ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
216 }
217
218 // Test that killing the process releases the lock. This should cover crashing.
219 TEST_F(FileLockingTest, UnlockOnTerminate) {
220 // The child will wait for an exit which never arrives.
221 StartChildAndSignalLock(kExitUnlock);
222
223 ASSERT_NE(File::FILE_OK, lock_file_.Lock());
224 ASSERT_TRUE(lock_child_.Terminate(0, true));
225 ASSERT_EQ(File::FILE_OK, lock_file_.Lock());
226 ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
227 }
OLDNEW
« 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