OLD | NEW |
---|---|
(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 } | |
OLD | NEW |