shess | dd6fc7e5 | 2015-12-11 21:53:33 | [diff] [blame] | 1 | // Copyright 2015 The Chromium Authors. All rights reserved. |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 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" |
avi | 543540e | 2015-12-24 05:15:32 | [diff] [blame] | 9 | #include "base/macros.h" |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 10 | #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 Elias | 52f6430 | 2017-07-26 22:23:08 | [diff] [blame^] | 14 | #include "build/build_config.h" |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 15 | #include "testing/gtest/include/gtest/gtest.h" |
| 16 | #include "testing/multiprocess_func_list.h" |
| 17 | |
| 18 | using base::File; |
| 19 | using base::FilePath; |
| 20 | |
| 21 | namespace { |
| 22 | |
| 23 | // Flag for the parent to share a temp dir to the child. |
| 24 | const char kTempDirFlag[] = "temp-dir"; |
| 25 | |
| 26 | // Flags to control how the subprocess unlocks the file. |
| 27 | const char kFileUnlock[] = "file-unlock"; |
| 28 | const char kCloseUnlock[] = "close-unlock"; |
| 29 | const char kExitUnlock[] = "exit-unlock"; |
| 30 | |
| 31 | // File to lock in temp dir. |
| 32 | const char kLockFile[] = "lockfile"; |
| 33 | |
| 34 | // Constants for various requests and responses, used as |signal_file| parameter |
| 35 | // to signal/wait helpers. |
| 36 | const char kSignalLockFileLocked[] = "locked.signal"; |
| 37 | const char kSignalLockFileClose[] = "close.signal"; |
| 38 | const char kSignalLockFileClosed[] = "closed.signal"; |
| 39 | const char kSignalLockFileUnlock[] = "unlock.signal"; |
| 40 | const char kSignalLockFileUnlocked[] = "unlocked.signal"; |
| 41 | const char kSignalExit[] = "exit.signal"; |
| 42 | |
| 43 | // Signal an event by creating a file which didn't previously exist. |
| 44 | bool SignalEvent(const FilePath& signal_dir, const char* signal_file) { |
| 45 | File file(signal_dir.AppendASCII(signal_file), |
shess | dd6fc7e5 | 2015-12-11 21:53:33 | [diff] [blame] | 46 | File::FLAG_CREATE | File::FLAG_WRITE); |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 47 | return file.IsValid(); |
| 48 | } |
| 49 | |
| 50 | // Check whether an event was signaled. |
| 51 | bool 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. |
| 58 | bool 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). |
| 71 | bool 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. |
| 87 | MULTIPROCESS_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 | |
| 122 | class FileLockingTest : public testing::Test { |
| 123 | public: |
| 124 | FileLockingTest() {} |
| 125 | |
| 126 | protected: |
| 127 | void SetUp() override { |
| 128 | testing::Test::SetUp(); |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 129 | |
shess | dd6fc7e5 | 2015-12-11 21:53:33 | [diff] [blame] | 130 | // Setup the temp dir and the lock file. |
| 131 | ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 132 | lock_file_.Initialize( |
vabr | 411f4fc | 2016-09-08 09:26:27 | [diff] [blame] | 133 | temp_dir_.GetPath().AppendASCII(kLockFile), |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 134 | File::FLAG_CREATE | File::FLAG_READ | File::FLAG_WRITE); |
| 135 | ASSERT_TRUE(lock_file_.IsValid()); |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 136 | } |
| 137 | |
| 138 | bool SignalEvent(const char* signal_file) { |
vabr | 411f4fc | 2016-09-08 09:26:27 | [diff] [blame] | 139 | return ::SignalEvent(temp_dir_.GetPath(), signal_file); |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 140 | } |
| 141 | |
| 142 | bool WaitForEventOrTimeout(const char* signal_file) { |
vabr | 411f4fc | 2016-09-08 09:26:27 | [diff] [blame] | 143 | return ::WaitForEventWithTimeout(temp_dir_.GetPath(), signal_file, |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 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. |
vabr | 411f4fc | 2016-09-08 09:26:27 | [diff] [blame] | 151 | const FilePath temp_path = temp_dir_.GetPath(); |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 152 | base::CommandLine child_command_line( |
| 153 | base::GetMultiProcessTestChildBaseCommandLine()); |
| 154 | child_command_line.AppendSwitchPath(kTempDirFlag, temp_path); |
| 155 | child_command_line.AppendSwitch(unlock_action); |
jcivelli | 87c322b | 2017-03-14 17:55:53 | [diff] [blame] | 156 | |
| 157 | spawn_child_ = base::SpawnMultiProcessTestChild( |
| 158 | ChildMainString, child_command_line, base::LaunchOptions()); |
| 159 | ASSERT_TRUE(spawn_child_.process.IsValid()); |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 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; |
jcivelli | f4462a35 | 2017-01-10 04:45:59 | [diff] [blame] | 169 | ASSERT_TRUE(WaitForMultiprocessTestChildExit( |
jcivelli | 87c322b | 2017-03-14 17:55:53 | [diff] [blame] | 170 | spawn_child_.process, TestTimeouts::action_timeout(), &rv)); |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 171 | ASSERT_EQ(0, rv); |
| 172 | } |
| 173 | |
| 174 | base::ScopedTempDir temp_dir_; |
| 175 | base::File lock_file_; |
jcivelli | 87c322b | 2017-03-14 17:55:53 | [diff] [blame] | 176 | base::SpawnChildResult spawn_child_; |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 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 | |
Alexandre Elias | 52f6430 | 2017-07-26 22:23:08 | [diff] [blame^] | 218 | // Flaky under Android ASAN (http://crbug.com/747518) |
| 219 | #if !(defined(OS_ANDROID) && defined(ADDRESS_SANITIZER)) |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 220 | // Test that killing the process releases the lock. This should cover crashing. |
| 221 | TEST_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()); |
jcivelli | 87c322b | 2017-03-14 17:55:53 | [diff] [blame] | 226 | ASSERT_TRUE(TerminateMultiProcessTestChild(spawn_child_.process, 0, true)); |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 227 | ASSERT_EQ(File::FILE_OK, lock_file_.Lock()); |
| 228 | ASSERT_EQ(File::FILE_OK, lock_file_.Unlock()); |
| 229 | } |
Alexandre Elias | 52f6430 | 2017-07-26 22:23:08 | [diff] [blame^] | 230 | #endif |