Roll src/third_party/WebKit d9c6159:8139f33 (svn 201974:201975)
[chromium-blink-merge.git] / chromecast / crash / linux / synchronized_minidump_manager_unittest.cc
blob94dabd611d9088a795f50a8740ca68ca71c3298b
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/crash_testing_utils.h"
26 #include "chromecast/crash/linux/dump_info.h"
27 #include "chromecast/crash/linux/synchronized_minidump_manager.h"
28 #include "testing/gtest/include/gtest/gtest.h"
30 namespace chromecast {
31 namespace {
33 const char kLockfileName[] = "lockfile";
34 const char kMetadataName[] = "metadata";
35 const char kMinidumpSubdir[] = "minidumps";
37 // A trivial implementation of SynchronizedMinidumpManager, which does no work
38 // to the
39 // minidump and exposes its protected members for testing.
40 class SynchronizedMinidumpManagerSimple : public SynchronizedMinidumpManager {
41 public:
42 SynchronizedMinidumpManagerSimple()
43 : SynchronizedMinidumpManager(),
44 work_done_(false),
45 add_entry_return_code_(-1),
46 lockfile_path_(dump_path_.Append(kLockfileName).value()) {}
47 ~SynchronizedMinidumpManagerSimple() override {}
49 void SetDumpInfoToWrite(scoped_ptr<DumpInfo> dump_info) {
50 dump_info_ = dump_info.Pass();
53 int DoWorkLocked() { return AcquireLockAndDoWork(); }
55 // SynchronizedMinidumpManager implementation:
56 int DoWork() override {
57 if (dump_info_)
58 add_entry_return_code_ = AddEntryToLockFile(*dump_info_);
59 work_done_ = true;
60 return 0;
63 // Accessors for testing.
64 const std::string& dump_path() { return dump_path_.value(); }
65 const std::string& lockfile_path() { return lockfile_path_; }
66 bool work_done() { return work_done_; }
67 int add_entry_return_code() { return add_entry_return_code_; }
69 private:
70 bool work_done_;
71 int add_entry_return_code_;
72 std::string lockfile_path_;
73 scoped_ptr<DumpInfo> dump_info_;
76 void DoWorkLockedTask(SynchronizedMinidumpManagerSimple* manager) {
77 manager->DoWorkLocked();
80 class SleepySynchronizedMinidumpManagerSimple
81 : public SynchronizedMinidumpManagerSimple {
82 public:
83 SleepySynchronizedMinidumpManagerSimple(int sleep_duration_ms)
84 : SynchronizedMinidumpManagerSimple(),
85 sleep_duration_ms_(sleep_duration_ms) {}
86 ~SleepySynchronizedMinidumpManagerSimple() override {}
88 // SynchronizedMinidumpManager implementation:
89 int DoWork() override {
90 // The lock has been acquired. Fall asleep for |kSleepDurationMs|, then
91 // write the file.
92 base::PlatformThread::Sleep(
93 base::TimeDelta::FromMilliseconds(sleep_duration_ms_));
94 return SynchronizedMinidumpManagerSimple::DoWork();
97 private:
98 const int sleep_duration_ms_;
101 class SynchronizedMinidumpManagerTest : public testing::Test {
102 public:
103 SynchronizedMinidumpManagerTest() {}
104 ~SynchronizedMinidumpManagerTest() override {}
106 void SetUp() override {
107 // Set up a temporary directory which will be used as our fake home dir.
108 ASSERT_TRUE(base::CreateNewTempDirectory("", &fake_home_dir_));
109 path_override_.reset(
110 new base::ScopedPathOverride(base::DIR_HOME, fake_home_dir_));
111 minidump_dir_ = fake_home_dir_.Append(kMinidumpSubdir);
112 lockfile_ = minidump_dir_.Append(kLockfileName);
113 metadata_ = minidump_dir_.Append(kMetadataName);
115 // Create a minidump directory.
116 ASSERT_TRUE(base::CreateDirectory(minidump_dir_));
117 ASSERT_TRUE(base::IsDirectoryEmpty(minidump_dir_));
119 // Create a lockfile in that directory.
120 base::File lockfile(
121 lockfile_, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
122 ASSERT_TRUE(lockfile.IsValid());
125 void TearDown() override {
126 // Remove the temp directory.
127 path_override_.reset();
128 ASSERT_TRUE(base::DeleteFile(fake_home_dir_, true));
131 protected:
132 base::FilePath fake_home_dir_; // Path to the test home directory.
133 base::FilePath minidump_dir_; // Path the the minidump directory.
134 base::FilePath lockfile_; // Path to the lockfile in |minidump_dir_|.
135 base::FilePath metadata_; // Path to the metadata in |minidump_dir_|.
137 private:
138 scoped_ptr<base::ScopedPathOverride> path_override_;
141 } // namespace
143 TEST_F(SynchronizedMinidumpManagerTest, FilePathsAreCorrect) {
144 SynchronizedMinidumpManagerSimple manager;
146 // Verify file paths for directory and lock file.
147 ASSERT_EQ(minidump_dir_.value(), manager.dump_path());
148 ASSERT_EQ(lockfile_.value(), manager.lockfile_path());
151 TEST_F(SynchronizedMinidumpManagerTest, AcquireLockOnNonExistentDirectory) {
152 // The directory was created in SetUp(). Delete it and its contents.
153 ASSERT_TRUE(base::DeleteFile(minidump_dir_, true));
154 ASSERT_FALSE(base::PathExists(minidump_dir_));
156 SynchronizedMinidumpManagerSimple manager;
157 ASSERT_EQ(0, manager.DoWorkLocked());
158 ASSERT_TRUE(manager.work_done());
160 // Verify the directory and the lockfile both exist.
161 ASSERT_TRUE(base::DirectoryExists(minidump_dir_));
162 ASSERT_TRUE(base::PathExists(lockfile_));
165 TEST_F(SynchronizedMinidumpManagerTest, AcquireLockOnExistingEmptyDirectory) {
166 // The lockfile was created in SetUp(). Delete it.
167 ASSERT_TRUE(base::DeleteFile(lockfile_, false));
168 ASSERT_FALSE(base::PathExists(lockfile_));
170 SynchronizedMinidumpManagerSimple manager;
171 ASSERT_EQ(0, manager.DoWorkLocked());
172 ASSERT_TRUE(manager.work_done());
174 // Verify the directory and the lockfile both exist.
175 ASSERT_TRUE(base::DirectoryExists(minidump_dir_));
176 ASSERT_TRUE(base::PathExists(lockfile_));
179 TEST_F(SynchronizedMinidumpManagerTest,
180 AcquireLockOnExistingDirectoryWithLockfile) {
181 SynchronizedMinidumpManagerSimple manager;
182 ASSERT_EQ(0, manager.DoWorkLocked());
183 ASSERT_TRUE(manager.work_done());
185 // Verify the directory and the lockfile both exist.
186 ASSERT_TRUE(base::DirectoryExists(minidump_dir_));
187 ASSERT_TRUE(base::PathExists(lockfile_));
190 TEST_F(SynchronizedMinidumpManagerTest,
191 AddEntryToLockFile_FailsWithInvalidEntry) {
192 // Create invalid dump info value
193 base::DictionaryValue val;
195 // Test that the manager tried to log the entry and failed.
196 SynchronizedMinidumpManagerSimple manager;
197 manager.SetDumpInfoToWrite(make_scoped_ptr(new DumpInfo(&val)));
198 ASSERT_EQ(0, manager.DoWorkLocked());
199 ASSERT_EQ(-1, manager.add_entry_return_code());
201 // Verify the lockfile is untouched.
202 ScopedVector<DumpInfo> dumps;
203 ASSERT_TRUE(FetchDumps(lockfile_.value(), &dumps));
204 ASSERT_EQ(0u, dumps.size());
207 TEST_F(SynchronizedMinidumpManagerTest,
208 AddEntryToLockFile_SucceedsWithValidEntries) {
209 // Sample parameters.
210 time_t now = time(0);
211 MinidumpParams params;
212 params.process_name = "process";
214 // Write the first entry.
215 SynchronizedMinidumpManagerSimple manager;
216 manager.SetDumpInfoToWrite(
217 make_scoped_ptr(new DumpInfo("dump1", "log1", now, params)));
218 ASSERT_EQ(0, manager.DoWorkLocked());
219 ASSERT_EQ(0, manager.add_entry_return_code());
221 // Test that the manager was successful in logging the entry.
222 ScopedVector<DumpInfo> dumps;
223 ASSERT_TRUE(FetchDumps(lockfile_.value(), &dumps));
224 ASSERT_EQ(1u, dumps.size());
226 // Write the second entry.
227 manager.SetDumpInfoToWrite(
228 make_scoped_ptr(new DumpInfo("dump2", "log2", now, params)));
229 ASSERT_EQ(0, manager.DoWorkLocked());
230 ASSERT_EQ(0, manager.add_entry_return_code());
232 // Test that the second entry is also valid.
233 ASSERT_TRUE(FetchDumps(lockfile_.value(), &dumps));
234 ASSERT_EQ(2u, dumps.size());
237 TEST_F(SynchronizedMinidumpManagerTest,
238 AcquireLockFile_FailsWhenNonBlockingAndFileLocked) {
239 ASSERT_TRUE(CreateFiles(lockfile_.value(), metadata_.value()));
240 // Lock the lockfile here. Note that the Chromium base::File tools permit
241 // multiple locks on the same process to succeed, so we must use POSIX system
242 // calls to accomplish this.
243 int fd = open(lockfile_.value().c_str(), O_RDWR | O_CREAT, 0660);
244 ASSERT_GE(fd, 0);
245 ASSERT_EQ(0, flock(fd, LOCK_EX));
247 SynchronizedMinidumpManagerSimple manager;
248 manager.set_non_blocking(true);
249 ASSERT_EQ(-1, manager.DoWorkLocked());
250 ASSERT_FALSE(manager.work_done());
252 // Test that the manager was not able to log the crash dump.
253 ScopedVector<DumpInfo> dumps;
254 ASSERT_TRUE(FetchDumps(lockfile_.value(), &dumps));
255 ASSERT_EQ(0u, dumps.size());
258 TEST_F(SynchronizedMinidumpManagerTest,
259 AcquireLockFile_WaitsForOtherThreadWhenBlocking) {
260 // Create some parameters for a minidump.
261 time_t now = time(0);
262 MinidumpParams params;
263 params.process_name = "process";
265 // Create a manager that grabs the lock then sleeps. Post a DoWork task to
266 // another thread. |sleepy_manager| will grab the lock and hold it for
267 // |sleep_time_ms|. It will then write a dump and release the lock.
268 const int sleep_time_ms = 100;
269 SleepySynchronizedMinidumpManagerSimple sleepy_manager(sleep_time_ms);
270 sleepy_manager.SetDumpInfoToWrite(
271 make_scoped_ptr(new DumpInfo("dump", "log", now, params)));
272 base::Thread sleepy_thread("sleepy");
273 sleepy_thread.Start();
274 sleepy_thread.task_runner()->PostTask(
275 FROM_HERE,
276 base::Bind(&DoWorkLockedTask, base::Unretained(&sleepy_manager)));
278 // Meanwhile, this thread should wait brielfy to allow the other thread to
279 // grab the lock.
280 const int concurrency_delay = 50;
281 base::PlatformThread::Sleep(
282 base::TimeDelta::FromMilliseconds(concurrency_delay));
284 // |sleepy_manager| has the lock by now, but has not released it. Attempt to
285 // grab it. DoWorkLocked() should block until |manager| has a chance to write
286 // the dump.
287 SynchronizedMinidumpManagerSimple manager;
288 manager.SetDumpInfoToWrite(
289 make_scoped_ptr(new DumpInfo("dump", "log", now, params)));
290 manager.set_non_blocking(false);
292 EXPECT_EQ(0, manager.DoWorkLocked());
293 EXPECT_EQ(0, manager.add_entry_return_code());
294 EXPECT_TRUE(manager.work_done());
296 // Check that the other manager was also successful.
297 EXPECT_EQ(0, sleepy_manager.add_entry_return_code());
298 EXPECT_TRUE(sleepy_manager.work_done());
300 // Test that both entries were logged.
301 ScopedVector<DumpInfo> dumps;
302 ASSERT_TRUE(FetchDumps(lockfile_.value(), &dumps));
303 EXPECT_EQ(2u, dumps.size());
306 // TODO(slan): These tests are passing but forking them is creating duplicates
307 // of all tests in this thread. Figure out how to lock the file more cleanly
308 // from another process.
309 TEST_F(SynchronizedMinidumpManagerTest,
310 DISABLED_AcquireLockFile_FailsWhenNonBlockingAndLockedFromOtherProcess) {
311 // Fork the process.
312 pid_t pid = base::ForkWithFlags(0u, nullptr, nullptr);
313 if (pid != 0) {
314 // The child process should instantiate a manager which immediately grabs
315 // the lock, and falls aleep for some period of time, then writes a dump,
316 // and finally releases the lock.
317 SleepySynchronizedMinidumpManagerSimple sleepy_manager(100);
318 ASSERT_EQ(0, sleepy_manager.DoWorkLocked());
319 ASSERT_TRUE(sleepy_manager.work_done());
320 return;
323 // Meanwhile, this process should wait brielfy to allow the other thread to
324 // grab the lock.
325 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50));
327 SynchronizedMinidumpManagerSimple manager;
328 manager.set_non_blocking(true);
329 ASSERT_EQ(-1, manager.DoWorkLocked());
330 ASSERT_FALSE(manager.work_done());
332 // Test that the manager was not able to log the crash dump.
333 ScopedVector<DumpInfo> dumps;
334 ASSERT_TRUE(FetchDumps(lockfile_.value(), &dumps));
335 ASSERT_EQ(0u, dumps.size());
338 // TODO(slan): These tests are passing but forking them is creating duplicates
339 // of all tests in this thread. Figure out how to lock the file more cleanly
340 // from another process.
341 TEST_F(SynchronizedMinidumpManagerTest,
342 DISABLED_AcquireLockFile_WaitsForOtherProcessWhenBlocking) {
343 // Create some parameters for a minidump.
344 time_t now = time(0);
345 MinidumpParams params;
346 params.process_name = "process";
348 // Fork the process.
349 pid_t pid = base::ForkWithFlags(0u, nullptr, nullptr);
350 if (pid != 0) {
351 // The child process should instantiate a manager which immediately grabs
352 // the lock, and falls aleep for some period of time, then writes a dump,
353 // and finally releases the lock.
354 SleepySynchronizedMinidumpManagerSimple sleepy_manager(100);
355 sleepy_manager.SetDumpInfoToWrite(
356 make_scoped_ptr(new DumpInfo("dump", "log", now, params)));
357 ASSERT_EQ(0, sleepy_manager.DoWorkLocked());
358 ASSERT_TRUE(sleepy_manager.work_done());
359 return;
362 // Meanwhile, this process should wait brielfy to allow the other thread to
363 // grab the lock.
364 const int concurrency_delay = 50;
365 base::PlatformThread::Sleep(
366 base::TimeDelta::FromMilliseconds(concurrency_delay));
368 // |sleepy_manager| has the lock by now, but has not released it. Attempt to
369 // grab it. DoWorkLocked() should block until |manager| has a chance to write
370 // the dump.
371 SynchronizedMinidumpManagerSimple manager;
372 manager.SetDumpInfoToWrite(
373 make_scoped_ptr(new DumpInfo("dump", "log", now, params)));
374 manager.set_non_blocking(false);
376 EXPECT_EQ(0, manager.DoWorkLocked());
377 EXPECT_EQ(0, manager.add_entry_return_code());
378 EXPECT_TRUE(manager.work_done());
380 // Test that both entries were logged.
381 ScopedVector<DumpInfo> dumps;
382 ASSERT_TRUE(FetchDumps(lockfile_.value(), &dumps));
383 EXPECT_EQ(2u, dumps.size());
386 TEST_F(SynchronizedMinidumpManagerTest,
387 AddEntryFailsWhenTooManyRecentDumpsPresent) {
388 // Sample parameters.
389 time_t now = time(0);
390 MinidumpParams params;
391 params.process_name = "process";
393 SynchronizedMinidumpManagerSimple manager;
394 manager.SetDumpInfoToWrite(
395 make_scoped_ptr(new DumpInfo("dump1", "log1", now, params)));
397 for (int i = 0; i < SynchronizedMinidumpManager::kMaxLockfileDumps; ++i) {
398 // Adding these should succeed
399 ASSERT_EQ(0, manager.DoWorkLocked());
400 ASSERT_EQ(0, manager.add_entry_return_code());
403 ASSERT_EQ(0, manager.DoWorkLocked());
405 // This one should fail
406 ASSERT_GT(0, manager.add_entry_return_code());
409 TEST_F(SynchronizedMinidumpManagerTest,
410 AddEntryFailsWhenRatelimitPeriodExceeded) {
411 // Sample parameters.
412 time_t now = time(0);
413 MinidumpParams params;
414 params.process_name = "process";
416 SynchronizedMinidumpManagerSimple manager;
417 manager.SetDumpInfoToWrite(
418 make_scoped_ptr(new DumpInfo("dump1", "log1", now, params)));
420 // Multiple iters to make sure period resets work correctly
421 for (int iter = 0; iter < 3; ++iter) {
422 time_t now = time(nullptr);
424 // Write dump logs to the lockfile.
425 size_t too_many_recent_dumps =
426 SynchronizedMinidumpManager::kRatelimitPeriodMaxDumps;
427 for (size_t i = 0; i < too_many_recent_dumps; ++i) {
428 // Adding these should succeed
429 ASSERT_EQ(0, manager.DoWorkLocked());
430 ASSERT_EQ(0, manager.add_entry_return_code());
432 // Clear dumps so we don't reach max dumps in lockfile
433 ASSERT_TRUE(ClearDumps(lockfile_.value()));
436 ASSERT_EQ(0, manager.DoWorkLocked());
437 // Should fail with too many dumps
438 ASSERT_GT(0, manager.add_entry_return_code());
440 int64 period = SynchronizedMinidumpManager::kRatelimitPeriodSeconds;
442 // Half period shouldn't trigger reset
443 SetRatelimitPeriodStart(metadata_.value(), now - period / 2);
444 ASSERT_EQ(0, manager.DoWorkLocked());
445 ASSERT_GT(0, manager.add_entry_return_code());
447 // Set period starting time to trigger a reset
448 SetRatelimitPeriodStart(metadata_.value(), now - period);
451 ASSERT_EQ(0, manager.DoWorkLocked());
452 ASSERT_EQ(0, manager.add_entry_return_code());
455 } // namespace chromecast