OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 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 <fcntl.h> |
| 6 #include <stdlib.h> |
| 7 #include <sys/file.h> |
| 8 #include <sys/stat.h> // mkdir |
| 9 #include <sys/types.h> // |
| 10 #include <stdio.h> // perror |
| 11 #include <time.h> |
| 12 |
| 13 #include <fstream> |
| 14 |
| 15 #include "base/base_paths.h" |
| 16 #include "base/bind.h" |
| 17 #include "base/files/file.h" |
| 18 #include "base/files/file_util.h" |
| 19 #include "base/memory/scoped_ptr.h" |
| 20 #include "base/memory/scoped_vector.h" |
| 21 #include "base/process/launch.h" |
| 22 #include "base/test/scoped_path_override.h" |
| 23 #include "base/threading/platform_thread.h" |
| 24 #include "base/threading/thread.h" |
| 25 #include "chromecast/crash/linux/dump_info.h" |
| 26 #include "chromecast/crash/linux/synchronized_minidump_manager.h" |
| 27 #include "testing/gtest/include/gtest/gtest.h" |
| 28 |
| 29 namespace chromecast { |
| 30 namespace { |
| 31 |
| 32 const char kLockfileName[] = "lockfile"; |
| 33 const char kMinidumpSubdir[] = "minidumps"; |
| 34 |
| 35 ScopedVector<DumpInfo> GetCurrentDumps(const std::string& logfile_path) { |
| 36 ScopedVector<DumpInfo> dumps; |
| 37 std::string entry; |
| 38 |
| 39 std::ifstream in(logfile_path); |
| 40 DCHECK(in.is_open()); |
| 41 while (std::getline(in, entry)) { |
| 42 scoped_ptr<DumpInfo> info(new DumpInfo(entry)); |
| 43 dumps.push_back(info.Pass()); |
| 44 } |
| 45 return dumps.Pass(); |
| 46 } |
| 47 |
| 48 // A trivial implementation of SynchronizedMinidumpManager, which does no work |
| 49 // to the |
| 50 // minidump and exposes its protected members for testing. |
| 51 class SynchronizedMinidumpManagerSimple : public SynchronizedMinidumpManager { |
| 52 public: |
| 53 SynchronizedMinidumpManagerSimple() |
| 54 : SynchronizedMinidumpManager(), |
| 55 work_done_(false), |
| 56 add_entry_return_code_(-1), |
| 57 lockfile_path_(dump_path_.Append(kLockfileName).value()) {} |
| 58 ~SynchronizedMinidumpManagerSimple() override {} |
| 59 |
| 60 void SetDumpInfoToWrite(scoped_ptr<DumpInfo> dump_info) { |
| 61 dump_info_ = dump_info.Pass(); |
| 62 } |
| 63 |
| 64 int DoWorkLocked() { return AcquireLockAndDoWork(); } |
| 65 |
| 66 // SynchronizedMinidumpManager implementation: |
| 67 int DoWork() override { |
| 68 if (dump_info_) |
| 69 add_entry_return_code_ = AddEntryToLockFile(*dump_info_); |
| 70 work_done_ = true; |
| 71 return 0; |
| 72 } |
| 73 |
| 74 // Accessors for testing. |
| 75 const std::string& dump_path() { return dump_path_.value(); } |
| 76 const std::string& lockfile_path() { return lockfile_path_; } |
| 77 bool work_done() { return work_done_; } |
| 78 int add_entry_return_code() { return add_entry_return_code_; } |
| 79 |
| 80 private: |
| 81 bool work_done_; |
| 82 int add_entry_return_code_; |
| 83 std::string lockfile_path_; |
| 84 scoped_ptr<DumpInfo> dump_info_; |
| 85 }; |
| 86 |
| 87 void DoWorkLockedTask(SynchronizedMinidumpManagerSimple* manager) { |
| 88 manager->DoWorkLocked(); |
| 89 } |
| 90 |
| 91 class SleepySynchronizedMinidumpManagerSimple |
| 92 : public SynchronizedMinidumpManagerSimple { |
| 93 public: |
| 94 SleepySynchronizedMinidumpManagerSimple(int sleep_duration_ms) |
| 95 : SynchronizedMinidumpManagerSimple(), |
| 96 sleep_duration_ms_(sleep_duration_ms) {} |
| 97 ~SleepySynchronizedMinidumpManagerSimple() override {} |
| 98 |
| 99 // SynchronizedMinidumpManager implementation: |
| 100 int DoWork() override { |
| 101 // The lock has been acquired. Fall asleep for |kSleepDurationMs|, then |
| 102 // write the file. |
| 103 base::PlatformThread::Sleep( |
| 104 base::TimeDelta::FromMilliseconds(sleep_duration_ms_)); |
| 105 return SynchronizedMinidumpManagerSimple::DoWork(); |
| 106 } |
| 107 |
| 108 private: |
| 109 const int sleep_duration_ms_; |
| 110 }; |
| 111 |
| 112 class SynchronizedMinidumpManagerTest : public testing::Test { |
| 113 public: |
| 114 SynchronizedMinidumpManagerTest() {} |
| 115 ~SynchronizedMinidumpManagerTest() override {} |
| 116 |
| 117 void SetUp() override { |
| 118 // Set up a temporary directory which will be used as our fake home dir. |
| 119 ASSERT_TRUE(base::CreateNewTempDirectory("", &fake_home_dir_)); |
| 120 path_override_.reset( |
| 121 new base::ScopedPathOverride(base::DIR_HOME, fake_home_dir_)); |
| 122 minidump_dir_ = fake_home_dir_.Append(kMinidumpSubdir); |
| 123 lockfile_ = minidump_dir_.Append(kLockfileName); |
| 124 |
| 125 // Create a minidump directory. |
| 126 ASSERT_TRUE(base::CreateDirectory(minidump_dir_)); |
| 127 ASSERT_TRUE(base::IsDirectoryEmpty(minidump_dir_)); |
| 128 |
| 129 // Create a lockfile in that directory. |
| 130 base::File lockfile( |
| 131 lockfile_, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); |
| 132 ASSERT_TRUE(lockfile.IsValid()); |
| 133 } |
| 134 |
| 135 void TearDown() override { |
| 136 // Remove the temp directory. |
| 137 path_override_.reset(); |
| 138 ASSERT_TRUE(base::DeleteFile(fake_home_dir_, true)); |
| 139 } |
| 140 |
| 141 protected: |
| 142 base::FilePath fake_home_dir_; // Path to the test home directory. |
| 143 base::FilePath minidump_dir_; // Path the the minidump directory. |
| 144 base::FilePath lockfile_; // Path to the lockfile in |minidump_dir_|. |
| 145 |
| 146 private: |
| 147 scoped_ptr<base::ScopedPathOverride> path_override_; |
| 148 }; |
| 149 |
| 150 } // namespace |
| 151 |
| 152 TEST_F(SynchronizedMinidumpManagerTest, FilePathsAreCorrect) { |
| 153 SynchronizedMinidumpManagerSimple manager; |
| 154 |
| 155 // Verify file paths for directory and lock file. |
| 156 ASSERT_EQ(minidump_dir_.value(), manager.dump_path()); |
| 157 ASSERT_EQ(lockfile_.value(), manager.lockfile_path()); |
| 158 } |
| 159 |
| 160 TEST_F(SynchronizedMinidumpManagerTest, AcquireLockOnNonExistentDirectory) { |
| 161 // The directory was created in SetUp(). Delete it and its contents. |
| 162 ASSERT_TRUE(base::DeleteFile(minidump_dir_, true)); |
| 163 ASSERT_FALSE(base::PathExists(minidump_dir_)); |
| 164 |
| 165 SynchronizedMinidumpManagerSimple manager; |
| 166 ASSERT_EQ(0, manager.DoWorkLocked()); |
| 167 ASSERT_TRUE(manager.work_done()); |
| 168 |
| 169 // Verify the directory and the lockfile both exist. |
| 170 ASSERT_TRUE(base::DirectoryExists(minidump_dir_)); |
| 171 ASSERT_TRUE(base::PathExists(lockfile_)); |
| 172 } |
| 173 |
| 174 TEST_F(SynchronizedMinidumpManagerTest, AcquireLockOnExistingEmptyDirectory) { |
| 175 // The lockfile was created in SetUp(). Delete it. |
| 176 ASSERT_TRUE(base::DeleteFile(lockfile_, false)); |
| 177 ASSERT_FALSE(base::PathExists(lockfile_)); |
| 178 |
| 179 SynchronizedMinidumpManagerSimple manager; |
| 180 ASSERT_EQ(0, manager.DoWorkLocked()); |
| 181 ASSERT_TRUE(manager.work_done()); |
| 182 |
| 183 // Verify the directory and the lockfile both exist. |
| 184 ASSERT_TRUE(base::DirectoryExists(minidump_dir_)); |
| 185 ASSERT_TRUE(base::PathExists(lockfile_)); |
| 186 } |
| 187 |
| 188 TEST_F(SynchronizedMinidumpManagerTest, |
| 189 AcquireLockOnExistingDirectoryWithLockfile) { |
| 190 SynchronizedMinidumpManagerSimple manager; |
| 191 ASSERT_EQ(0, manager.DoWorkLocked()); |
| 192 ASSERT_TRUE(manager.work_done()); |
| 193 |
| 194 // Verify the directory and the lockfile both exist. |
| 195 ASSERT_TRUE(base::DirectoryExists(minidump_dir_)); |
| 196 ASSERT_TRUE(base::PathExists(lockfile_)); |
| 197 } |
| 198 |
| 199 TEST_F(SynchronizedMinidumpManagerTest, |
| 200 AddEntryToLockFile_FailsWithInvalidEntry) { |
| 201 // Test that the manager tried to log the entry and failed. |
| 202 SynchronizedMinidumpManagerSimple manager; |
| 203 manager.SetDumpInfoToWrite(make_scoped_ptr(new DumpInfo(""))); |
| 204 ASSERT_EQ(0, manager.DoWorkLocked()); |
| 205 ASSERT_EQ(-1, manager.add_entry_return_code()); |
| 206 |
| 207 // Verify the lockfile is untouched. |
| 208 ScopedVector<DumpInfo> dumps = GetCurrentDumps(lockfile_.value()); |
| 209 ASSERT_EQ(0u, dumps.size()); |
| 210 } |
| 211 |
| 212 TEST_F(SynchronizedMinidumpManagerTest, |
| 213 AddEntryToLockFile_SucceedsWithValidEntries) { |
| 214 // Sample parameters. |
| 215 time_t now = time(0); |
| 216 MinidumpParams params; |
| 217 params.process_name = "process"; |
| 218 |
| 219 // Write the first entry. |
| 220 SynchronizedMinidumpManagerSimple manager; |
| 221 manager.SetDumpInfoToWrite( |
| 222 make_scoped_ptr(new DumpInfo("dump1", "log1", now, params))); |
| 223 ASSERT_EQ(0, manager.DoWorkLocked()); |
| 224 ASSERT_EQ(0, manager.add_entry_return_code()); |
| 225 |
| 226 // Test that the manager was successful in logging the entry. |
| 227 ScopedVector<DumpInfo> dumps = GetCurrentDumps(lockfile_.value()); |
| 228 ASSERT_EQ(1u, dumps.size()); |
| 229 |
| 230 // Write the second entry. |
| 231 manager.SetDumpInfoToWrite( |
| 232 make_scoped_ptr(new DumpInfo("dump2", "log2", now, params))); |
| 233 ASSERT_EQ(0, manager.DoWorkLocked()); |
| 234 ASSERT_EQ(0, manager.add_entry_return_code()); |
| 235 |
| 236 // Test that the second entry is also valid. |
| 237 dumps = GetCurrentDumps(lockfile_.value()); |
| 238 ASSERT_EQ(2u, dumps.size()); |
| 239 |
| 240 // TODO(slan): Weird time incosistencies making this fail. |
| 241 // ASSERT_EQ(dumps[0]->entry(), DumpInfo("dump", "log", now, params).entry()); |
| 242 } |
| 243 |
| 244 TEST_F(SynchronizedMinidumpManagerTest, |
| 245 AcquireLockFile_FailsWhenNonBlockingAndFileLocked) { |
| 246 // Lock the lockfile here. Note that the Chromium base::File tools permit |
| 247 // multiple locks on the same process to succeed, so we must use POSIX system |
| 248 // calls to accomplish this. |
| 249 int fd = open(lockfile_.value().c_str(), O_RDWR | O_CREAT, 0660); |
| 250 ASSERT_GE(fd, 0); |
| 251 ASSERT_EQ(0, flock(fd, LOCK_EX)); |
| 252 |
| 253 SynchronizedMinidumpManagerSimple manager; |
| 254 manager.set_non_blocking(true); |
| 255 ASSERT_EQ(-1, manager.DoWorkLocked()); |
| 256 ASSERT_FALSE(manager.work_done()); |
| 257 |
| 258 // Test that the manager was not able to log the crash dump. |
| 259 ScopedVector<DumpInfo> dumps = GetCurrentDumps(lockfile_.value()); |
| 260 ASSERT_EQ(0u, dumps.size()); |
| 261 } |
| 262 |
| 263 TEST_F(SynchronizedMinidumpManagerTest, |
| 264 AcquireLockFile_WaitsForOtherThreadWhenBlocking) { |
| 265 // Create some parameters for a minidump. |
| 266 time_t now = time(0); |
| 267 MinidumpParams params; |
| 268 params.process_name = "process"; |
| 269 |
| 270 // Create a manager that grabs the lock then sleeps. Post a DoWork task to |
| 271 // another thread. |sleepy_manager| will grab the lock and hold it for |
| 272 // |sleep_time_ms|. It will then write a dump and release the lock. |
| 273 const int sleep_time_ms = 100; |
| 274 SleepySynchronizedMinidumpManagerSimple sleepy_manager(sleep_time_ms); |
| 275 sleepy_manager.SetDumpInfoToWrite( |
| 276 make_scoped_ptr(new DumpInfo("dump", "log", now, params))); |
| 277 base::Thread sleepy_thread("sleepy"); |
| 278 sleepy_thread.Start(); |
| 279 sleepy_thread.task_runner()->PostTask( |
| 280 FROM_HERE, |
| 281 base::Bind(&DoWorkLockedTask, base::Unretained(&sleepy_manager))); |
| 282 |
| 283 // Meanwhile, this thread should wait brielfy to allow the other thread to |
| 284 // grab the lock. |
| 285 const int concurrency_delay = 50; |
| 286 base::PlatformThread::Sleep( |
| 287 base::TimeDelta::FromMilliseconds(concurrency_delay)); |
| 288 |
| 289 // |sleepy_manager| has the lock by now, but has not released it. Attempt to |
| 290 // grab it. DoWorkLocked() should block until |manager| has a chance to write |
| 291 // the dump. |
| 292 SynchronizedMinidumpManagerSimple manager; |
| 293 manager.SetDumpInfoToWrite( |
| 294 make_scoped_ptr(new DumpInfo("dump", "log", now, params))); |
| 295 manager.set_non_blocking(false); |
| 296 |
| 297 EXPECT_EQ(0, manager.DoWorkLocked()); |
| 298 EXPECT_EQ(0, manager.add_entry_return_code()); |
| 299 EXPECT_TRUE(manager.work_done()); |
| 300 |
| 301 // Check that the other manager was also successful. |
| 302 EXPECT_EQ(0, sleepy_manager.add_entry_return_code()); |
| 303 EXPECT_TRUE(sleepy_manager.work_done()); |
| 304 |
| 305 // Test that both entries were logged. |
| 306 ScopedVector<DumpInfo> dumps = GetCurrentDumps(lockfile_.value()); |
| 307 EXPECT_EQ(2u, dumps.size()); |
| 308 } |
| 309 |
| 310 // TODO(slan): These tests are passing but forking them is creating duplicates |
| 311 // of all tests in this thread. Figure out how to lock the file more cleanly |
| 312 // from another process. |
| 313 TEST_F(SynchronizedMinidumpManagerTest, |
| 314 DISABLED_AcquireLockFile_FailsWhenNonBlockingAndLockedFromOtherProcess) { |
| 315 // Fork the process. |
| 316 pid_t pid = base::ForkWithFlags(0u, nullptr, nullptr); |
| 317 if (pid != 0) { |
| 318 // The child process should instantiate a manager which immediately grabs |
| 319 // the lock, and falls aleep for some period of time, then writes a dump, |
| 320 // and finally releases the lock. |
| 321 SleepySynchronizedMinidumpManagerSimple sleepy_manager(100); |
| 322 ASSERT_EQ(0, sleepy_manager.DoWorkLocked()); |
| 323 ASSERT_TRUE(sleepy_manager.work_done()); |
| 324 return; |
| 325 } |
| 326 |
| 327 // Meanwhile, this process should wait brielfy to allow the other thread to |
| 328 // grab the lock. |
| 329 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50)); |
| 330 |
| 331 SynchronizedMinidumpManagerSimple manager; |
| 332 manager.set_non_blocking(true); |
| 333 ASSERT_EQ(-1, manager.DoWorkLocked()); |
| 334 ASSERT_FALSE(manager.work_done()); |
| 335 |
| 336 // Test that the manager was not able to log the crash dump. |
| 337 ScopedVector<DumpInfo> dumps = GetCurrentDumps(lockfile_.value()); |
| 338 ASSERT_EQ(0u, dumps.size()); |
| 339 } |
| 340 |
| 341 // TODO(slan): These tests are passing but forking them is creating duplicates |
| 342 // of all tests in this thread. Figure out how to lock the file more cleanly |
| 343 // from another process. |
| 344 TEST_F(SynchronizedMinidumpManagerTest, |
| 345 DISABLED_AcquireLockFile_WaitsForOtherProcessWhenBlocking) { |
| 346 // Create some parameters for a minidump. |
| 347 time_t now = time(0); |
| 348 MinidumpParams params; |
| 349 params.process_name = "process"; |
| 350 |
| 351 // Fork the process. |
| 352 pid_t pid = base::ForkWithFlags(0u, nullptr, nullptr); |
| 353 if (pid != 0) { |
| 354 // The child process should instantiate a manager which immediately grabs |
| 355 // the lock, and falls aleep for some period of time, then writes a dump, |
| 356 // and finally releases the lock. |
| 357 SleepySynchronizedMinidumpManagerSimple sleepy_manager(100); |
| 358 sleepy_manager.SetDumpInfoToWrite( |
| 359 make_scoped_ptr(new DumpInfo("dump", "log", now, params))); |
| 360 ASSERT_EQ(0, sleepy_manager.DoWorkLocked()); |
| 361 ASSERT_TRUE(sleepy_manager.work_done()); |
| 362 return; |
| 363 } |
| 364 |
| 365 // Meanwhile, this process should wait brielfy to allow the other thread to |
| 366 // grab the lock. |
| 367 const int concurrency_delay = 50; |
| 368 base::PlatformThread::Sleep( |
| 369 base::TimeDelta::FromMilliseconds(concurrency_delay)); |
| 370 |
| 371 // |sleepy_manager| has the lock by now, but has not released it. Attempt to |
| 372 // grab it. DoWorkLocked() should block until |manager| has a chance to write |
| 373 // the dump. |
| 374 SynchronizedMinidumpManagerSimple manager; |
| 375 manager.SetDumpInfoToWrite( |
| 376 make_scoped_ptr(new DumpInfo("dump", "log", now, params))); |
| 377 manager.set_non_blocking(false); |
| 378 |
| 379 EXPECT_EQ(0, manager.DoWorkLocked()); |
| 380 EXPECT_EQ(0, manager.add_entry_return_code()); |
| 381 EXPECT_TRUE(manager.work_done()); |
| 382 |
| 383 // Test that both entries were logged. |
| 384 ScopedVector<DumpInfo> dumps = GetCurrentDumps(lockfile_.value()); |
| 385 EXPECT_EQ(2u, dumps.size()); |
| 386 } |
| 387 |
| 388 } // namespace chromecast |
OLD | NEW |