mandoline: Reland "Sandbox mojo:browser and mojo:omnibox."
[chromium-blink-merge.git] / chromecast / crash / linux / synchronized_minidump_manager_unittest.cc
blobce1d8530fe8a5d2e6b0308f18e28ae1abd1a37d1
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 kMinidumpSubdir[] = "minidumps";
36 // A trivial implementation of SynchronizedMinidumpManager, which does no work
37 // to the
38 // minidump and exposes its protected members for testing.
39 class SynchronizedMinidumpManagerSimple : public SynchronizedMinidumpManager {
40 public:
41 SynchronizedMinidumpManagerSimple()
42 : SynchronizedMinidumpManager(),
43 work_done_(false),
44 add_entry_return_code_(-1),
45 lockfile_path_(dump_path_.Append(kLockfileName).value()) {}
46 ~SynchronizedMinidumpManagerSimple() override {}
48 void SetDumpInfoToWrite(scoped_ptr<DumpInfo> dump_info) {
49 dump_info_ = dump_info.Pass();
52 int DoWorkLocked() { return AcquireLockAndDoWork(); }
54 // SynchronizedMinidumpManager implementation:
55 int DoWork() override {
56 if (dump_info_)
57 add_entry_return_code_ = AddEntryToLockFile(*dump_info_);
58 work_done_ = true;
59 return 0;
62 // Accessors for testing.
63 const std::string& dump_path() { return dump_path_.value(); }
64 const std::string& lockfile_path() { return lockfile_path_; }
65 bool work_done() { return work_done_; }
66 int add_entry_return_code() { return add_entry_return_code_; }
68 private:
69 bool work_done_;
70 int add_entry_return_code_;
71 std::string lockfile_path_;
72 scoped_ptr<DumpInfo> dump_info_;
75 void DoWorkLockedTask(SynchronizedMinidumpManagerSimple* manager) {
76 manager->DoWorkLocked();
79 class SleepySynchronizedMinidumpManagerSimple
80 : public SynchronizedMinidumpManagerSimple {
81 public:
82 SleepySynchronizedMinidumpManagerSimple(int sleep_duration_ms)
83 : SynchronizedMinidumpManagerSimple(),
84 sleep_duration_ms_(sleep_duration_ms) {}
85 ~SleepySynchronizedMinidumpManagerSimple() override {}
87 // SynchronizedMinidumpManager implementation:
88 int DoWork() override {
89 // The lock has been acquired. Fall asleep for |kSleepDurationMs|, then
90 // write the file.
91 base::PlatformThread::Sleep(
92 base::TimeDelta::FromMilliseconds(sleep_duration_ms_));
93 return SynchronizedMinidumpManagerSimple::DoWork();
96 private:
97 const int sleep_duration_ms_;
100 class SynchronizedMinidumpManagerTest : public testing::Test {
101 public:
102 SynchronizedMinidumpManagerTest() {}
103 ~SynchronizedMinidumpManagerTest() override {}
105 void SetUp() override {
106 // Set up a temporary directory which will be used as our fake home dir.
107 ASSERT_TRUE(base::CreateNewTempDirectory("", &fake_home_dir_));
108 path_override_.reset(
109 new base::ScopedPathOverride(base::DIR_HOME, fake_home_dir_));
110 minidump_dir_ = fake_home_dir_.Append(kMinidumpSubdir);
111 lockfile_ = minidump_dir_.Append(kLockfileName);
113 // Create a minidump directory.
114 ASSERT_TRUE(base::CreateDirectory(minidump_dir_));
115 ASSERT_TRUE(base::IsDirectoryEmpty(minidump_dir_));
117 // Create a lockfile in that directory.
118 base::File lockfile(
119 lockfile_, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
120 ASSERT_TRUE(lockfile.IsValid());
123 void TearDown() override {
124 // Remove the temp directory.
125 path_override_.reset();
126 ASSERT_TRUE(base::DeleteFile(fake_home_dir_, true));
129 protected:
130 base::FilePath fake_home_dir_; // Path to the test home directory.
131 base::FilePath minidump_dir_; // Path the the minidump directory.
132 base::FilePath lockfile_; // Path to the lockfile in |minidump_dir_|.
134 private:
135 scoped_ptr<base::ScopedPathOverride> path_override_;
138 } // namespace
140 TEST_F(SynchronizedMinidumpManagerTest, FilePathsAreCorrect) {
141 SynchronizedMinidumpManagerSimple manager;
143 // Verify file paths for directory and lock file.
144 ASSERT_EQ(minidump_dir_.value(), manager.dump_path());
145 ASSERT_EQ(lockfile_.value(), manager.lockfile_path());
148 TEST_F(SynchronizedMinidumpManagerTest, AcquireLockOnNonExistentDirectory) {
149 // The directory was created in SetUp(). Delete it and its contents.
150 ASSERT_TRUE(base::DeleteFile(minidump_dir_, true));
151 ASSERT_FALSE(base::PathExists(minidump_dir_));
153 SynchronizedMinidumpManagerSimple manager;
154 ASSERT_EQ(0, manager.DoWorkLocked());
155 ASSERT_TRUE(manager.work_done());
157 // Verify the directory and the lockfile both exist.
158 ASSERT_TRUE(base::DirectoryExists(minidump_dir_));
159 ASSERT_TRUE(base::PathExists(lockfile_));
162 TEST_F(SynchronizedMinidumpManagerTest, AcquireLockOnExistingEmptyDirectory) {
163 // The lockfile was created in SetUp(). Delete it.
164 ASSERT_TRUE(base::DeleteFile(lockfile_, false));
165 ASSERT_FALSE(base::PathExists(lockfile_));
167 SynchronizedMinidumpManagerSimple manager;
168 ASSERT_EQ(0, manager.DoWorkLocked());
169 ASSERT_TRUE(manager.work_done());
171 // Verify the directory and the lockfile both exist.
172 ASSERT_TRUE(base::DirectoryExists(minidump_dir_));
173 ASSERT_TRUE(base::PathExists(lockfile_));
176 TEST_F(SynchronizedMinidumpManagerTest,
177 AcquireLockOnExistingDirectoryWithLockfile) {
178 SynchronizedMinidumpManagerSimple manager;
179 ASSERT_EQ(0, manager.DoWorkLocked());
180 ASSERT_TRUE(manager.work_done());
182 // Verify the directory and the lockfile both exist.
183 ASSERT_TRUE(base::DirectoryExists(minidump_dir_));
184 ASSERT_TRUE(base::PathExists(lockfile_));
187 TEST_F(SynchronizedMinidumpManagerTest,
188 AddEntryToLockFile_FailsWithInvalidEntry) {
189 // Create invalid dump info value
190 base::DictionaryValue val;
192 // Test that the manager tried to log the entry and failed.
193 SynchronizedMinidumpManagerSimple manager;
194 manager.SetDumpInfoToWrite(make_scoped_ptr(new DumpInfo(&val)));
195 ASSERT_EQ(0, manager.DoWorkLocked());
196 ASSERT_EQ(-1, manager.add_entry_return_code());
198 // Verify the lockfile is untouched.
199 ScopedVector<DumpInfo> dumps;
200 ASSERT_TRUE(FetchDumps(lockfile_.value(), &dumps));
201 ASSERT_EQ(0u, dumps.size());
204 TEST_F(SynchronizedMinidumpManagerTest,
205 AddEntryToLockFile_SucceedsWithValidEntries) {
206 // Sample parameters.
207 time_t now = time(0);
208 MinidumpParams params;
209 params.process_name = "process";
211 // Write the first entry.
212 SynchronizedMinidumpManagerSimple 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());
218 // Test that the manager was successful in logging the entry.
219 ScopedVector<DumpInfo> dumps;
220 ASSERT_TRUE(FetchDumps(lockfile_.value(), &dumps));
221 ASSERT_EQ(1u, dumps.size());
223 // Write the second entry.
224 manager.SetDumpInfoToWrite(
225 make_scoped_ptr(new DumpInfo("dump2", "log2", now, params)));
226 ASSERT_EQ(0, manager.DoWorkLocked());
227 ASSERT_EQ(0, manager.add_entry_return_code());
229 // Test that the second entry is also valid.
230 ASSERT_TRUE(FetchDumps(lockfile_.value(), &dumps));
231 ASSERT_EQ(2u, dumps.size());
234 TEST_F(SynchronizedMinidumpManagerTest,
235 AcquireLockFile_FailsWhenNonBlockingAndFileLocked) {
236 ASSERT_TRUE(CreateLockFile(lockfile_.value()));
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));
244 SynchronizedMinidumpManagerSimple manager;
245 manager.set_non_blocking(true);
246 ASSERT_EQ(-1, manager.DoWorkLocked());
247 ASSERT_FALSE(manager.work_done());
249 // Test that the manager was not able to log the crash dump.
250 ScopedVector<DumpInfo> dumps;
251 ASSERT_TRUE(FetchDumps(lockfile_.value(), &dumps));
252 ASSERT_EQ(0u, dumps.size());
255 TEST_F(SynchronizedMinidumpManagerTest,
256 AcquireLockFile_WaitsForOtherThreadWhenBlocking) {
257 // Create some parameters for a minidump.
258 time_t now = time(0);
259 MinidumpParams params;
260 params.process_name = "process";
262 // Create a manager that grabs the lock then sleeps. Post a DoWork task to
263 // another thread. |sleepy_manager| will grab the lock and hold it for
264 // |sleep_time_ms|. It will then write a dump and release the lock.
265 const int sleep_time_ms = 100;
266 SleepySynchronizedMinidumpManagerSimple sleepy_manager(sleep_time_ms);
267 sleepy_manager.SetDumpInfoToWrite(
268 make_scoped_ptr(new DumpInfo("dump", "log", now, params)));
269 base::Thread sleepy_thread("sleepy");
270 sleepy_thread.Start();
271 sleepy_thread.task_runner()->PostTask(
272 FROM_HERE,
273 base::Bind(&DoWorkLockedTask, base::Unretained(&sleepy_manager)));
275 // Meanwhile, this thread should wait brielfy to allow the other thread to
276 // grab the lock.
277 const int concurrency_delay = 50;
278 base::PlatformThread::Sleep(
279 base::TimeDelta::FromMilliseconds(concurrency_delay));
281 // |sleepy_manager| has the lock by now, but has not released it. Attempt to
282 // grab it. DoWorkLocked() should block until |manager| has a chance to write
283 // the dump.
284 SynchronizedMinidumpManagerSimple manager;
285 manager.SetDumpInfoToWrite(
286 make_scoped_ptr(new DumpInfo("dump", "log", now, params)));
287 manager.set_non_blocking(false);
289 EXPECT_EQ(0, manager.DoWorkLocked());
290 EXPECT_EQ(0, manager.add_entry_return_code());
291 EXPECT_TRUE(manager.work_done());
293 // Check that the other manager was also successful.
294 EXPECT_EQ(0, sleepy_manager.add_entry_return_code());
295 EXPECT_TRUE(sleepy_manager.work_done());
297 // Test that both entries were logged.
298 ScopedVector<DumpInfo> dumps;
299 ASSERT_TRUE(FetchDumps(lockfile_.value(), &dumps));
300 EXPECT_EQ(2u, dumps.size());
303 // TODO(slan): These tests are passing but forking them is creating duplicates
304 // of all tests in this thread. Figure out how to lock the file more cleanly
305 // from another process.
306 TEST_F(SynchronizedMinidumpManagerTest,
307 DISABLED_AcquireLockFile_FailsWhenNonBlockingAndLockedFromOtherProcess) {
308 // Fork the process.
309 pid_t pid = base::ForkWithFlags(0u, nullptr, nullptr);
310 if (pid != 0) {
311 // The child process should instantiate a manager which immediately grabs
312 // the lock, and falls aleep for some period of time, then writes a dump,
313 // and finally releases the lock.
314 SleepySynchronizedMinidumpManagerSimple sleepy_manager(100);
315 ASSERT_EQ(0, sleepy_manager.DoWorkLocked());
316 ASSERT_TRUE(sleepy_manager.work_done());
317 return;
320 // Meanwhile, this process should wait brielfy to allow the other thread to
321 // grab the lock.
322 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50));
324 SynchronizedMinidumpManagerSimple manager;
325 manager.set_non_blocking(true);
326 ASSERT_EQ(-1, manager.DoWorkLocked());
327 ASSERT_FALSE(manager.work_done());
329 // Test that the manager was not able to log the crash dump.
330 ScopedVector<DumpInfo> dumps;
331 ASSERT_TRUE(FetchDumps(lockfile_.value(), &dumps));
332 ASSERT_EQ(0u, dumps.size());
335 // TODO(slan): These tests are passing but forking them is creating duplicates
336 // of all tests in this thread. Figure out how to lock the file more cleanly
337 // from another process.
338 TEST_F(SynchronizedMinidumpManagerTest,
339 DISABLED_AcquireLockFile_WaitsForOtherProcessWhenBlocking) {
340 // Create some parameters for a minidump.
341 time_t now = time(0);
342 MinidumpParams params;
343 params.process_name = "process";
345 // Fork the process.
346 pid_t pid = base::ForkWithFlags(0u, nullptr, nullptr);
347 if (pid != 0) {
348 // The child process should instantiate a manager which immediately grabs
349 // the lock, and falls aleep for some period of time, then writes a dump,
350 // and finally releases the lock.
351 SleepySynchronizedMinidumpManagerSimple sleepy_manager(100);
352 sleepy_manager.SetDumpInfoToWrite(
353 make_scoped_ptr(new DumpInfo("dump", "log", now, params)));
354 ASSERT_EQ(0, sleepy_manager.DoWorkLocked());
355 ASSERT_TRUE(sleepy_manager.work_done());
356 return;
359 // Meanwhile, this process should wait brielfy to allow the other thread to
360 // grab the lock.
361 const int concurrency_delay = 50;
362 base::PlatformThread::Sleep(
363 base::TimeDelta::FromMilliseconds(concurrency_delay));
365 // |sleepy_manager| has the lock by now, but has not released it. Attempt to
366 // grab it. DoWorkLocked() should block until |manager| has a chance to write
367 // the dump.
368 SynchronizedMinidumpManagerSimple manager;
369 manager.SetDumpInfoToWrite(
370 make_scoped_ptr(new DumpInfo("dump", "log", now, params)));
371 manager.set_non_blocking(false);
373 EXPECT_EQ(0, manager.DoWorkLocked());
374 EXPECT_EQ(0, manager.add_entry_return_code());
375 EXPECT_TRUE(manager.work_done());
377 // Test that both entries were logged.
378 ScopedVector<DumpInfo> dumps;
379 ASSERT_TRUE(FetchDumps(lockfile_.value(), &dumps));
380 EXPECT_EQ(2u, dumps.size());
383 TEST_F(SynchronizedMinidumpManagerTest,
384 AddEntryFailsWhenTooManyRecentDumpsPresent) {
385 // Sample parameters.
386 time_t now = time(0);
387 MinidumpParams params;
388 params.process_name = "process";
390 SynchronizedMinidumpManagerSimple manager;
391 manager.SetDumpInfoToWrite(
392 make_scoped_ptr(new DumpInfo("dump1", "log1", now, params)));
394 for (int i = 0; i < SynchronizedMinidumpManager::kMaxLockfileDumps; ++i) {
395 // Adding these should succeed
396 ASSERT_EQ(0, manager.DoWorkLocked());
397 ASSERT_EQ(0, manager.add_entry_return_code());
400 ASSERT_EQ(0, manager.DoWorkLocked());
402 // This one should fail
403 ASSERT_GT(0, manager.add_entry_return_code());
406 TEST_F(SynchronizedMinidumpManagerTest,
407 AddEntryFailsWhenRatelimitPeriodExceeded) {
408 // Sample parameters.
409 time_t now = time(0);
410 MinidumpParams params;
411 params.process_name = "process";
413 SynchronizedMinidumpManagerSimple manager;
414 manager.SetDumpInfoToWrite(
415 make_scoped_ptr(new DumpInfo("dump1", "log1", now, params)));
417 // Multiple iters to make sure period resets work correctly
418 for (int iter = 0; iter < 3; ++iter) {
419 time_t now = time(nullptr);
421 // Write dump logs to the lockfile.
422 size_t too_many_recent_dumps =
423 SynchronizedMinidumpManager::kRatelimitPeriodMaxDumps;
424 for (size_t i = 0; i < too_many_recent_dumps; ++i) {
425 // Adding these should succeed
426 ASSERT_EQ(0, manager.DoWorkLocked());
427 ASSERT_EQ(0, manager.add_entry_return_code());
429 // Clear dumps so we don't reach max dumps in lockfile
430 ASSERT_TRUE(ClearDumps(lockfile_.value()));
433 ASSERT_EQ(0, manager.DoWorkLocked());
434 // Should fail with too many dumps
435 ASSERT_GT(0, manager.add_entry_return_code());
437 int64 period = SynchronizedMinidumpManager::kRatelimitPeriodSeconds;
439 // Half period shouldn't trigger reset
440 SetRatelimitPeriodStart(lockfile_.value(), now - period / 2);
441 ASSERT_EQ(0, manager.DoWorkLocked());
442 ASSERT_GT(0, manager.add_entry_return_code());
444 // Set period starting time to trigger a reset
445 SetRatelimitPeriodStart(lockfile_.value(), now - period);
448 ASSERT_EQ(0, manager.DoWorkLocked());
449 ASSERT_EQ(0, manager.add_entry_return_code());
452 } // namespace chromecast