blob: ee3450884190d76126e0cd89c43c13a6e6a94e2f [file] [log] [blame]
shessdd6fc7e52015-12-11 21:53:331// Copyright 2015 The Chromium Authors. All rights reserved.
shesse66da3f2015-12-10 22:12:172// 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"
avi543540e2015-12-24 05:15:329#include "base/macros.h"
shesse66da3f2015-12-10 22:12:1710#include "base/test/multiprocess_test.h"
11#include "base/test/test_timeouts.h"
12#include "base/threading/platform_thread.h"
13#include "base/time/time.h"
Alexandre Elias52f64302017-07-26 22:23:0814#include "build/build_config.h"
shesse66da3f2015-12-10 22:12:1715#include "testing/gtest/include/gtest/gtest.h"
16#include "testing/multiprocess_func_list.h"
17
18using base::File;
19using base::FilePath;
20
21namespace {
22
23// Flag for the parent to share a temp dir to the child.
24const char kTempDirFlag[] = "temp-dir";
25
26// Flags to control how the subprocess unlocks the file.
27const char kFileUnlock[] = "file-unlock";
28const char kCloseUnlock[] = "close-unlock";
29const char kExitUnlock[] = "exit-unlock";
30
31// File to lock in temp dir.
32const char kLockFile[] = "lockfile";
33
34// Constants for various requests and responses, used as |signal_file| parameter
35// to signal/wait helpers.
36const char kSignalLockFileLocked[] = "locked.signal";
37const char kSignalLockFileClose[] = "close.signal";
38const char kSignalLockFileClosed[] = "closed.signal";
39const char kSignalLockFileUnlock[] = "unlock.signal";
40const char kSignalLockFileUnlocked[] = "unlocked.signal";
41const char kSignalExit[] = "exit.signal";
42
43// Signal an event by creating a file which didn't previously exist.
44bool SignalEvent(const FilePath& signal_dir, const char* signal_file) {
45 File file(signal_dir.AppendASCII(signal_file),
shessdd6fc7e52015-12-11 21:53:3346 File::FLAG_CREATE | File::FLAG_WRITE);
shesse66da3f2015-12-10 22:12:1747 return file.IsValid();
48}
49
50// Check whether an event was signaled.
51bool CheckEvent(const FilePath& signal_dir, const char* signal_file) {
52 File file(signal_dir.AppendASCII(signal_file),
53 File::FLAG_OPEN | File::FLAG_READ);
54 return file.IsValid();
55}
56
57// Busy-wait for an event to be signaled, returning false for timeout.
58bool WaitForEventWithTimeout(const FilePath& signal_dir,
59 const char* signal_file,
60 const base::TimeDelta& timeout) {
61 const base::Time finish_by = base::Time::Now() + timeout;
62 while (!CheckEvent(signal_dir, signal_file)) {
63 if (base::Time::Now() > finish_by)
64 return false;
65 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10));
66 }
67 return true;
68}
69
70// Wait forever for the event to be signaled (should never return false).
71bool WaitForEvent(const FilePath& signal_dir, const char* signal_file) {
72 return WaitForEventWithTimeout(signal_dir, signal_file,
73 base::TimeDelta::Max());
74}
75
76// Keep these in sync so StartChild*() can refer to correct test main.
77#define ChildMain ChildLockUnlock
78#define ChildMainString "ChildLockUnlock"
79
80// Subprocess to test getting a file lock then releasing it. |kTempDirFlag|
81// must pass in an existing temporary directory for the lockfile and signal
82// files. One of the following flags must be passed to determine how to unlock
83// the lock file:
84// - |kFileUnlock| calls Unlock() to unlock.
85// - |kCloseUnlock| calls Close() while the lock is held.
86// - |kExitUnlock| exits while the lock is held.
87MULTIPROCESS_TEST_MAIN(ChildMain) {
88 base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
89 const FilePath temp_path = command_line->GetSwitchValuePath(kTempDirFlag);
90 CHECK(base::DirectoryExists(temp_path));
91
92 // Immediately lock the file.
93 File file(temp_path.AppendASCII(kLockFile),
94 File::FLAG_OPEN | File::FLAG_READ | File::FLAG_WRITE);
95 CHECK(file.IsValid());
96 CHECK_EQ(File::FILE_OK, file.Lock());
97 CHECK(SignalEvent(temp_path, kSignalLockFileLocked));
98
99 if (command_line->HasSwitch(kFileUnlock)) {
100 // Wait for signal to unlock, then unlock the file.
101 CHECK(WaitForEvent(temp_path, kSignalLockFileUnlock));
102 CHECK_EQ(File::FILE_OK, file.Unlock());
103 CHECK(SignalEvent(temp_path, kSignalLockFileUnlocked));
104 } else if (command_line->HasSwitch(kCloseUnlock)) {
105 // Wait for the signal to close, then close the file.
106 CHECK(WaitForEvent(temp_path, kSignalLockFileClose));
107 file.Close();
108 CHECK(!file.IsValid());
109 CHECK(SignalEvent(temp_path, kSignalLockFileClosed));
110 } else {
111 CHECK(command_line->HasSwitch(kExitUnlock));
112 }
113
114 // Wait for signal to exit, so that unlock or close can be distinguished from
115 // exit.
116 CHECK(WaitForEvent(temp_path, kSignalExit));
117 return 0;
118}
119
120} // namespace
121
122class FileLockingTest : public testing::Test {
123 public:
124 FileLockingTest() {}
125
126 protected:
127 void SetUp() override {
128 testing::Test::SetUp();
shesse66da3f2015-12-10 22:12:17129
shessdd6fc7e52015-12-11 21:53:33130 // Setup the temp dir and the lock file.
131 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
shesse66da3f2015-12-10 22:12:17132 lock_file_.Initialize(
vabr411f4fc2016-09-08 09:26:27133 temp_dir_.GetPath().AppendASCII(kLockFile),
shesse66da3f2015-12-10 22:12:17134 File::FLAG_CREATE | File::FLAG_READ | File::FLAG_WRITE);
135 ASSERT_TRUE(lock_file_.IsValid());
shesse66da3f2015-12-10 22:12:17136 }
137
138 bool SignalEvent(const char* signal_file) {
vabr411f4fc2016-09-08 09:26:27139 return ::SignalEvent(temp_dir_.GetPath(), signal_file);
shesse66da3f2015-12-10 22:12:17140 }
141
142 bool WaitForEventOrTimeout(const char* signal_file) {
vabr411f4fc2016-09-08 09:26:27143 return ::WaitForEventWithTimeout(temp_dir_.GetPath(), signal_file,
shesse66da3f2015-12-10 22:12:17144 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.
vabr411f4fc2016-09-08 09:26:27151 const FilePath temp_path = temp_dir_.GetPath();
shesse66da3f2015-12-10 22:12:17152 base::CommandLine child_command_line(
153 base::GetMultiProcessTestChildBaseCommandLine());
154 child_command_line.AppendSwitchPath(kTempDirFlag, temp_path);
155 child_command_line.AppendSwitch(unlock_action);
jcivelli87c322b2017-03-14 17:55:53156
157 spawn_child_ = base::SpawnMultiProcessTestChild(
158 ChildMainString, child_command_line, base::LaunchOptions());
159 ASSERT_TRUE(spawn_child_.process.IsValid());
shesse66da3f2015-12-10 22:12:17160
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;
jcivellif4462a352017-01-10 04:45:59169 ASSERT_TRUE(WaitForMultiprocessTestChildExit(
jcivelli87c322b2017-03-14 17:55:53170 spawn_child_.process, TestTimeouts::action_timeout(), &rv));
shesse66da3f2015-12-10 22:12:17171 ASSERT_EQ(0, rv);
172 }
173
174 base::ScopedTempDir temp_dir_;
175 base::File lock_file_;
jcivelli87c322b2017-03-14 17:55:53176 base::SpawnChildResult spawn_child_;
shesse66da3f2015-12-10 22:12:17177
178 private:
179 DISALLOW_COPY_AND_ASSIGN(FileLockingTest);
180};
181
182// Test that locks are released by Unlock().
183TEST_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().
196TEST_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.
209TEST_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
Alexandre Elias52f64302017-07-26 22:23:08218// Flaky under Android ASAN (http://crbug.com/747518)
219#if !(defined(OS_ANDROID) && defined(ADDRESS_SANITIZER))
shesse66da3f2015-12-10 22:12:17220// Test that killing the process releases the lock. This should cover crashing.
221TEST_F(FileLockingTest, UnlockOnTerminate) {
222 // The child will wait for an exit which never arrives.
223 StartChildAndSignalLock(kExitUnlock);
224
225 ASSERT_NE(File::FILE_OK, lock_file_.Lock());
jcivelli87c322b2017-03-14 17:55:53226 ASSERT_TRUE(TerminateMultiProcessTestChild(spawn_child_.process, 0, true));
shesse66da3f2015-12-10 22:12:17227 ASSERT_EQ(File::FILE_OK, lock_file_.Lock());
228 ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
229}
Alexandre Elias52f64302017-07-26 22:23:08230#endif