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