Chromium Code Reviews| 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 |