Update UnusedResources lint suppressions.
[chromium-blink-merge.git] / chromecast / crash / linux / synchronized_minidump_manager_unittest.cc
blobea3e908b3e9979d35389209690a2f25d0849c955
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.
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>
13 #include <fstream>
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"
29 namespace chromecast {
30 namespace {
32 const char kLockfileName[] = "lockfile";
33 const char kMinidumpSubdir[] = "minidumps";
35 ScopedVector<DumpInfo> GetCurrentDumps(const std::string& logfile_path) {
36 ScopedVector<DumpInfo> dumps;
37 std::string entry;
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());
45 return dumps.Pass();
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 {}
60 void SetDumpInfoToWrite(scoped_ptr<DumpInfo> dump_info) {
61 dump_info_ = dump_info.Pass();
64 int DoWorkLocked() { return AcquireLockAndDoWork(); }
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;
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_; }
80 private:
81 bool work_done_;
82 int add_entry_return_code_;
83 std::string lockfile_path_;
84 scoped_ptr<DumpInfo> dump_info_;
87 void DoWorkLockedTask(SynchronizedMinidumpManagerSimple* manager) {
88 manager->DoWorkLocked();
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 {}
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();
108 private:
109 const int sleep_duration_ms_;
112 class SynchronizedMinidumpManagerTest : public testing::Test {
113 public:
114 SynchronizedMinidumpManagerTest() {}
115 ~SynchronizedMinidumpManagerTest() override {}
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);
125 // Create a minidump directory.
126 ASSERT_TRUE(base::CreateDirectory(minidump_dir_));
127 ASSERT_TRUE(base::IsDirectoryEmpty(minidump_dir_));
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());
135 void TearDown() override {
136 // Remove the temp directory.
137 path_override_.reset();
138 ASSERT_TRUE(base::DeleteFile(fake_home_dir_, true));
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_|.
146 private:
147 scoped_ptr<base::ScopedPathOverride> path_override_;
150 } // namespace
152 TEST_F(SynchronizedMinidumpManagerTest, FilePathsAreCorrect) {
153 SynchronizedMinidumpManagerSimple manager;
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());
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_));
165 SynchronizedMinidumpManagerSimple manager;
166 ASSERT_EQ(0, manager.DoWorkLocked());
167 ASSERT_TRUE(manager.work_done());
169 // Verify the directory and the lockfile both exist.
170 ASSERT_TRUE(base::DirectoryExists(minidump_dir_));
171 ASSERT_TRUE(base::PathExists(lockfile_));
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_));
179 SynchronizedMinidumpManagerSimple manager;
180 ASSERT_EQ(0, manager.DoWorkLocked());
181 ASSERT_TRUE(manager.work_done());
183 // Verify the directory and the lockfile both exist.
184 ASSERT_TRUE(base::DirectoryExists(minidump_dir_));
185 ASSERT_TRUE(base::PathExists(lockfile_));
188 TEST_F(SynchronizedMinidumpManagerTest,
189 AcquireLockOnExistingDirectoryWithLockfile) {
190 SynchronizedMinidumpManagerSimple manager;
191 ASSERT_EQ(0, manager.DoWorkLocked());
192 ASSERT_TRUE(manager.work_done());
194 // Verify the directory and the lockfile both exist.
195 ASSERT_TRUE(base::DirectoryExists(minidump_dir_));
196 ASSERT_TRUE(base::PathExists(lockfile_));
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());
207 // Verify the lockfile is untouched.
208 ScopedVector<DumpInfo> dumps = GetCurrentDumps(lockfile_.value());
209 ASSERT_EQ(0u, dumps.size());
212 TEST_F(SynchronizedMinidumpManagerTest,
213 AddEntryToLockFile_SucceedsWithValidEntries) {
214 // Sample parameters.
215 time_t now = time(0);
216 MinidumpParams params;
217 params.process_name = "process";
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());
226 // Test that the manager was successful in logging the entry.
227 ScopedVector<DumpInfo> dumps = GetCurrentDumps(lockfile_.value());
228 ASSERT_EQ(1u, dumps.size());
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());
236 // Test that the second entry is also valid.
237 dumps = GetCurrentDumps(lockfile_.value());
238 ASSERT_EQ(2u, dumps.size());
240 // TODO(slan): Weird time incosistencies making this fail.
241 // ASSERT_EQ(dumps[0]->entry(), DumpInfo("dump", "log", now, params).entry());
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));
253 SynchronizedMinidumpManagerSimple manager;
254 manager.set_non_blocking(true);
255 ASSERT_EQ(-1, manager.DoWorkLocked());
256 ASSERT_FALSE(manager.work_done());
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());
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";
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)));
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));
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);
297 EXPECT_EQ(0, manager.DoWorkLocked());
298 EXPECT_EQ(0, manager.add_entry_return_code());
299 EXPECT_TRUE(manager.work_done());
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());
305 // Test that both entries were logged.
306 ScopedVector<DumpInfo> dumps = GetCurrentDumps(lockfile_.value());
307 EXPECT_EQ(2u, dumps.size());
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;
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));
331 SynchronizedMinidumpManagerSimple manager;
332 manager.set_non_blocking(true);
333 ASSERT_EQ(-1, manager.DoWorkLocked());
334 ASSERT_FALSE(manager.work_done());
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());
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";
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;
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));
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);
379 EXPECT_EQ(0, manager.DoWorkLocked());
380 EXPECT_EQ(0, manager.add_entry_return_code());
381 EXPECT_TRUE(manager.work_done());
383 // Test that both entries were logged.
384 ScopedVector<DumpInfo> dumps = GetCurrentDumps(lockfile_.value());
385 EXPECT_EQ(2u, dumps.size());
388 } // namespace chromecast