Index: chromecast/crash/linux/synchronized_minidump_manager_unittest.cc |
diff --git a/chromecast/crash/linux/synchronized_minidump_manager_unittest.cc b/chromecast/crash/linux/synchronized_minidump_manager_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..ea3e908b3e9979d35389209690a2f25d0849c955 |
--- /dev/null |
+++ b/chromecast/crash/linux/synchronized_minidump_manager_unittest.cc |
@@ -0,0 +1,388 @@ |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include <fcntl.h> |
+#include <stdlib.h> |
+#include <sys/file.h> |
+#include <sys/stat.h> // mkdir |
+#include <sys/types.h> // |
+#include <stdio.h> // perror |
+#include <time.h> |
+ |
+#include <fstream> |
+ |
+#include "base/base_paths.h" |
+#include "base/bind.h" |
+#include "base/files/file.h" |
+#include "base/files/file_util.h" |
+#include "base/memory/scoped_ptr.h" |
+#include "base/memory/scoped_vector.h" |
+#include "base/process/launch.h" |
+#include "base/test/scoped_path_override.h" |
+#include "base/threading/platform_thread.h" |
+#include "base/threading/thread.h" |
+#include "chromecast/crash/linux/dump_info.h" |
+#include "chromecast/crash/linux/synchronized_minidump_manager.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+ |
+namespace chromecast { |
+namespace { |
+ |
+const char kLockfileName[] = "lockfile"; |
+const char kMinidumpSubdir[] = "minidumps"; |
+ |
+ScopedVector<DumpInfo> GetCurrentDumps(const std::string& logfile_path) { |
+ ScopedVector<DumpInfo> dumps; |
+ std::string entry; |
+ |
+ std::ifstream in(logfile_path); |
+ DCHECK(in.is_open()); |
+ while (std::getline(in, entry)) { |
+ scoped_ptr<DumpInfo> info(new DumpInfo(entry)); |
+ dumps.push_back(info.Pass()); |
+ } |
+ return dumps.Pass(); |
+} |
+ |
+// A trivial implementation of SynchronizedMinidumpManager, which does no work |
+// to the |
+// minidump and exposes its protected members for testing. |
+class SynchronizedMinidumpManagerSimple : public SynchronizedMinidumpManager { |
+ public: |
+ SynchronizedMinidumpManagerSimple() |
+ : SynchronizedMinidumpManager(), |
+ work_done_(false), |
+ add_entry_return_code_(-1), |
+ lockfile_path_(dump_path_.Append(kLockfileName).value()) {} |
+ ~SynchronizedMinidumpManagerSimple() override {} |
+ |
+ void SetDumpInfoToWrite(scoped_ptr<DumpInfo> dump_info) { |
+ dump_info_ = dump_info.Pass(); |
+ } |
+ |
+ int DoWorkLocked() { return AcquireLockAndDoWork(); } |
+ |
+ // SynchronizedMinidumpManager implementation: |
+ int DoWork() override { |
+ if (dump_info_) |
+ add_entry_return_code_ = AddEntryToLockFile(*dump_info_); |
+ work_done_ = true; |
+ return 0; |
+ } |
+ |
+ // Accessors for testing. |
+ const std::string& dump_path() { return dump_path_.value(); } |
+ const std::string& lockfile_path() { return lockfile_path_; } |
+ bool work_done() { return work_done_; } |
+ int add_entry_return_code() { return add_entry_return_code_; } |
+ |
+ private: |
+ bool work_done_; |
+ int add_entry_return_code_; |
+ std::string lockfile_path_; |
+ scoped_ptr<DumpInfo> dump_info_; |
+}; |
+ |
+void DoWorkLockedTask(SynchronizedMinidumpManagerSimple* manager) { |
+ manager->DoWorkLocked(); |
+} |
+ |
+class SleepySynchronizedMinidumpManagerSimple |
+ : public SynchronizedMinidumpManagerSimple { |
+ public: |
+ SleepySynchronizedMinidumpManagerSimple(int sleep_duration_ms) |
+ : SynchronizedMinidumpManagerSimple(), |
+ sleep_duration_ms_(sleep_duration_ms) {} |
+ ~SleepySynchronizedMinidumpManagerSimple() override {} |
+ |
+ // SynchronizedMinidumpManager implementation: |
+ int DoWork() override { |
+ // The lock has been acquired. Fall asleep for |kSleepDurationMs|, then |
+ // write the file. |
+ base::PlatformThread::Sleep( |
+ base::TimeDelta::FromMilliseconds(sleep_duration_ms_)); |
+ return SynchronizedMinidumpManagerSimple::DoWork(); |
+ } |
+ |
+ private: |
+ const int sleep_duration_ms_; |
+}; |
+ |
+class SynchronizedMinidumpManagerTest : public testing::Test { |
+ public: |
+ SynchronizedMinidumpManagerTest() {} |
+ ~SynchronizedMinidumpManagerTest() override {} |
+ |
+ void SetUp() override { |
+ // Set up a temporary directory which will be used as our fake home dir. |
+ ASSERT_TRUE(base::CreateNewTempDirectory("", &fake_home_dir_)); |
+ path_override_.reset( |
+ new base::ScopedPathOverride(base::DIR_HOME, fake_home_dir_)); |
+ minidump_dir_ = fake_home_dir_.Append(kMinidumpSubdir); |
+ lockfile_ = minidump_dir_.Append(kLockfileName); |
+ |
+ // Create a minidump directory. |
+ ASSERT_TRUE(base::CreateDirectory(minidump_dir_)); |
+ ASSERT_TRUE(base::IsDirectoryEmpty(minidump_dir_)); |
+ |
+ // Create a lockfile in that directory. |
+ base::File lockfile( |
+ lockfile_, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); |
+ ASSERT_TRUE(lockfile.IsValid()); |
+ } |
+ |
+ void TearDown() override { |
+ // Remove the temp directory. |
+ path_override_.reset(); |
+ ASSERT_TRUE(base::DeleteFile(fake_home_dir_, true)); |
+ } |
+ |
+ protected: |
+ base::FilePath fake_home_dir_; // Path to the test home directory. |
+ base::FilePath minidump_dir_; // Path the the minidump directory. |
+ base::FilePath lockfile_; // Path to the lockfile in |minidump_dir_|. |
+ |
+ private: |
+ scoped_ptr<base::ScopedPathOverride> path_override_; |
+}; |
+ |
+} // namespace |
+ |
+TEST_F(SynchronizedMinidumpManagerTest, FilePathsAreCorrect) { |
+ SynchronizedMinidumpManagerSimple manager; |
+ |
+ // Verify file paths for directory and lock file. |
+ ASSERT_EQ(minidump_dir_.value(), manager.dump_path()); |
+ ASSERT_EQ(lockfile_.value(), manager.lockfile_path()); |
+} |
+ |
+TEST_F(SynchronizedMinidumpManagerTest, AcquireLockOnNonExistentDirectory) { |
+ // The directory was created in SetUp(). Delete it and its contents. |
+ ASSERT_TRUE(base::DeleteFile(minidump_dir_, true)); |
+ ASSERT_FALSE(base::PathExists(minidump_dir_)); |
+ |
+ SynchronizedMinidumpManagerSimple manager; |
+ ASSERT_EQ(0, manager.DoWorkLocked()); |
+ ASSERT_TRUE(manager.work_done()); |
+ |
+ // Verify the directory and the lockfile both exist. |
+ ASSERT_TRUE(base::DirectoryExists(minidump_dir_)); |
+ ASSERT_TRUE(base::PathExists(lockfile_)); |
+} |
+ |
+TEST_F(SynchronizedMinidumpManagerTest, AcquireLockOnExistingEmptyDirectory) { |
+ // The lockfile was created in SetUp(). Delete it. |
+ ASSERT_TRUE(base::DeleteFile(lockfile_, false)); |
+ ASSERT_FALSE(base::PathExists(lockfile_)); |
+ |
+ SynchronizedMinidumpManagerSimple manager; |
+ ASSERT_EQ(0, manager.DoWorkLocked()); |
+ ASSERT_TRUE(manager.work_done()); |
+ |
+ // Verify the directory and the lockfile both exist. |
+ ASSERT_TRUE(base::DirectoryExists(minidump_dir_)); |
+ ASSERT_TRUE(base::PathExists(lockfile_)); |
+} |
+ |
+TEST_F(SynchronizedMinidumpManagerTest, |
+ AcquireLockOnExistingDirectoryWithLockfile) { |
+ SynchronizedMinidumpManagerSimple manager; |
+ ASSERT_EQ(0, manager.DoWorkLocked()); |
+ ASSERT_TRUE(manager.work_done()); |
+ |
+ // Verify the directory and the lockfile both exist. |
+ ASSERT_TRUE(base::DirectoryExists(minidump_dir_)); |
+ ASSERT_TRUE(base::PathExists(lockfile_)); |
+} |
+ |
+TEST_F(SynchronizedMinidumpManagerTest, |
+ AddEntryToLockFile_FailsWithInvalidEntry) { |
+ // Test that the manager tried to log the entry and failed. |
+ SynchronizedMinidumpManagerSimple manager; |
+ manager.SetDumpInfoToWrite(make_scoped_ptr(new DumpInfo(""))); |
+ ASSERT_EQ(0, manager.DoWorkLocked()); |
+ ASSERT_EQ(-1, manager.add_entry_return_code()); |
+ |
+ // Verify the lockfile is untouched. |
+ ScopedVector<DumpInfo> dumps = GetCurrentDumps(lockfile_.value()); |
+ ASSERT_EQ(0u, dumps.size()); |
+} |
+ |
+TEST_F(SynchronizedMinidumpManagerTest, |
+ AddEntryToLockFile_SucceedsWithValidEntries) { |
+ // Sample parameters. |
+ time_t now = time(0); |
+ MinidumpParams params; |
+ params.process_name = "process"; |
+ |
+ // Write the first entry. |
+ SynchronizedMinidumpManagerSimple manager; |
+ manager.SetDumpInfoToWrite( |
+ make_scoped_ptr(new DumpInfo("dump1", "log1", now, params))); |
+ ASSERT_EQ(0, manager.DoWorkLocked()); |
+ ASSERT_EQ(0, manager.add_entry_return_code()); |
+ |
+ // Test that the manager was successful in logging the entry. |
+ ScopedVector<DumpInfo> dumps = GetCurrentDumps(lockfile_.value()); |
+ ASSERT_EQ(1u, dumps.size()); |
+ |
+ // Write the second entry. |
+ manager.SetDumpInfoToWrite( |
+ make_scoped_ptr(new DumpInfo("dump2", "log2", now, params))); |
+ ASSERT_EQ(0, manager.DoWorkLocked()); |
+ ASSERT_EQ(0, manager.add_entry_return_code()); |
+ |
+ // Test that the second entry is also valid. |
+ dumps = GetCurrentDumps(lockfile_.value()); |
+ ASSERT_EQ(2u, dumps.size()); |
+ |
+ // TODO(slan): Weird time incosistencies making this fail. |
+ // ASSERT_EQ(dumps[0]->entry(), DumpInfo("dump", "log", now, params).entry()); |
+} |
+ |
+TEST_F(SynchronizedMinidumpManagerTest, |
+ AcquireLockFile_FailsWhenNonBlockingAndFileLocked) { |
+ // Lock the lockfile here. Note that the Chromium base::File tools permit |
+ // multiple locks on the same process to succeed, so we must use POSIX system |
+ // calls to accomplish this. |
+ int fd = open(lockfile_.value().c_str(), O_RDWR | O_CREAT, 0660); |
+ ASSERT_GE(fd, 0); |
+ ASSERT_EQ(0, flock(fd, LOCK_EX)); |
+ |
+ SynchronizedMinidumpManagerSimple manager; |
+ manager.set_non_blocking(true); |
+ ASSERT_EQ(-1, manager.DoWorkLocked()); |
+ ASSERT_FALSE(manager.work_done()); |
+ |
+ // Test that the manager was not able to log the crash dump. |
+ ScopedVector<DumpInfo> dumps = GetCurrentDumps(lockfile_.value()); |
+ ASSERT_EQ(0u, dumps.size()); |
+} |
+ |
+TEST_F(SynchronizedMinidumpManagerTest, |
+ AcquireLockFile_WaitsForOtherThreadWhenBlocking) { |
+ // Create some parameters for a minidump. |
+ time_t now = time(0); |
+ MinidumpParams params; |
+ params.process_name = "process"; |
+ |
+ // Create a manager that grabs the lock then sleeps. Post a DoWork task to |
+ // another thread. |sleepy_manager| will grab the lock and hold it for |
+ // |sleep_time_ms|. It will then write a dump and release the lock. |
+ const int sleep_time_ms = 100; |
+ SleepySynchronizedMinidumpManagerSimple sleepy_manager(sleep_time_ms); |
+ sleepy_manager.SetDumpInfoToWrite( |
+ make_scoped_ptr(new DumpInfo("dump", "log", now, params))); |
+ base::Thread sleepy_thread("sleepy"); |
+ sleepy_thread.Start(); |
+ sleepy_thread.task_runner()->PostTask( |
+ FROM_HERE, |
+ base::Bind(&DoWorkLockedTask, base::Unretained(&sleepy_manager))); |
+ |
+ // Meanwhile, this thread should wait brielfy to allow the other thread to |
+ // grab the lock. |
+ const int concurrency_delay = 50; |
+ base::PlatformThread::Sleep( |
+ base::TimeDelta::FromMilliseconds(concurrency_delay)); |
+ |
+ // |sleepy_manager| has the lock by now, but has not released it. Attempt to |
+ // grab it. DoWorkLocked() should block until |manager| has a chance to write |
+ // the dump. |
+ SynchronizedMinidumpManagerSimple manager; |
+ manager.SetDumpInfoToWrite( |
+ make_scoped_ptr(new DumpInfo("dump", "log", now, params))); |
+ manager.set_non_blocking(false); |
+ |
+ EXPECT_EQ(0, manager.DoWorkLocked()); |
+ EXPECT_EQ(0, manager.add_entry_return_code()); |
+ EXPECT_TRUE(manager.work_done()); |
+ |
+ // Check that the other manager was also successful. |
+ EXPECT_EQ(0, sleepy_manager.add_entry_return_code()); |
+ EXPECT_TRUE(sleepy_manager.work_done()); |
+ |
+ // Test that both entries were logged. |
+ ScopedVector<DumpInfo> dumps = GetCurrentDumps(lockfile_.value()); |
+ EXPECT_EQ(2u, dumps.size()); |
+} |
+ |
+// TODO(slan): These tests are passing but forking them is creating duplicates |
+// of all tests in this thread. Figure out how to lock the file more cleanly |
+// from another process. |
+TEST_F(SynchronizedMinidumpManagerTest, |
+ DISABLED_AcquireLockFile_FailsWhenNonBlockingAndLockedFromOtherProcess) { |
+ // Fork the process. |
+ pid_t pid = base::ForkWithFlags(0u, nullptr, nullptr); |
+ if (pid != 0) { |
+ // The child process should instantiate a manager which immediately grabs |
+ // the lock, and falls aleep for some period of time, then writes a dump, |
+ // and finally releases the lock. |
+ SleepySynchronizedMinidumpManagerSimple sleepy_manager(100); |
+ ASSERT_EQ(0, sleepy_manager.DoWorkLocked()); |
+ ASSERT_TRUE(sleepy_manager.work_done()); |
+ return; |
+ } |
+ |
+ // Meanwhile, this process should wait brielfy to allow the other thread to |
+ // grab the lock. |
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50)); |
+ |
+ SynchronizedMinidumpManagerSimple manager; |
+ manager.set_non_blocking(true); |
+ ASSERT_EQ(-1, manager.DoWorkLocked()); |
+ ASSERT_FALSE(manager.work_done()); |
+ |
+ // Test that the manager was not able to log the crash dump. |
+ ScopedVector<DumpInfo> dumps = GetCurrentDumps(lockfile_.value()); |
+ ASSERT_EQ(0u, dumps.size()); |
+} |
+ |
+// TODO(slan): These tests are passing but forking them is creating duplicates |
+// of all tests in this thread. Figure out how to lock the file more cleanly |
+// from another process. |
+TEST_F(SynchronizedMinidumpManagerTest, |
+ DISABLED_AcquireLockFile_WaitsForOtherProcessWhenBlocking) { |
+ // Create some parameters for a minidump. |
+ time_t now = time(0); |
+ MinidumpParams params; |
+ params.process_name = "process"; |
+ |
+ // Fork the process. |
+ pid_t pid = base::ForkWithFlags(0u, nullptr, nullptr); |
+ if (pid != 0) { |
+ // The child process should instantiate a manager which immediately grabs |
+ // the lock, and falls aleep for some period of time, then writes a dump, |
+ // and finally releases the lock. |
+ SleepySynchronizedMinidumpManagerSimple sleepy_manager(100); |
+ sleepy_manager.SetDumpInfoToWrite( |
+ make_scoped_ptr(new DumpInfo("dump", "log", now, params))); |
+ ASSERT_EQ(0, sleepy_manager.DoWorkLocked()); |
+ ASSERT_TRUE(sleepy_manager.work_done()); |
+ return; |
+ } |
+ |
+ // Meanwhile, this process should wait brielfy to allow the other thread to |
+ // grab the lock. |
+ const int concurrency_delay = 50; |
+ base::PlatformThread::Sleep( |
+ base::TimeDelta::FromMilliseconds(concurrency_delay)); |
+ |
+ // |sleepy_manager| has the lock by now, but has not released it. Attempt to |
+ // grab it. DoWorkLocked() should block until |manager| has a chance to write |
+ // the dump. |
+ SynchronizedMinidumpManagerSimple manager; |
+ manager.SetDumpInfoToWrite( |
+ make_scoped_ptr(new DumpInfo("dump", "log", now, params))); |
+ manager.set_non_blocking(false); |
+ |
+ EXPECT_EQ(0, manager.DoWorkLocked()); |
+ EXPECT_EQ(0, manager.add_entry_return_code()); |
+ EXPECT_TRUE(manager.work_done()); |
+ |
+ // Test that both entries were logged. |
+ ScopedVector<DumpInfo> dumps = GetCurrentDumps(lockfile_.value()); |
+ EXPECT_EQ(2u, dumps.size()); |
+} |
+ |
+} // namespace chromecast |