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.
8 #include <sys/stat.h> // mkdir
9 #include <sys/types.h> //
10 #include <stdio.h> // perror
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
{
33 const char kLockfileName
[] = "lockfile";
34 const char kMinidumpSubdir
[] = "minidumps";
36 // A trivial implementation of SynchronizedMinidumpManager, which does no work
38 // minidump and exposes its protected members for testing.
39 class SynchronizedMinidumpManagerSimple
: public SynchronizedMinidumpManager
{
41 SynchronizedMinidumpManagerSimple()
42 : SynchronizedMinidumpManager(),
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
{
57 add_entry_return_code_
= AddEntryToLockFile(*dump_info_
);
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_
; }
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
{
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
91 base::PlatformThread::Sleep(
92 base::TimeDelta::FromMilliseconds(sleep_duration_ms_
));
93 return SynchronizedMinidumpManagerSimple::DoWork();
97 const int sleep_duration_ms_
;
100 class SynchronizedMinidumpManagerTest
: public testing::Test
{
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.
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));
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_|.
135 scoped_ptr
<base::ScopedPathOverride
> path_override_
;
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);
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(
273 base::Bind(&DoWorkLockedTask
, base::Unretained(&sleepy_manager
)));
275 // Meanwhile, this thread should wait brielfy to allow the other thread to
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
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
) {
309 pid_t pid
= base::ForkWithFlags(0u, nullptr, nullptr);
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());
320 // Meanwhile, this process should wait brielfy to allow the other thread to
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";
346 pid_t pid
= base::ForkWithFlags(0u, nullptr, nullptr);
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());
359 // Meanwhile, this process should wait brielfy to allow the other thread to
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
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