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 kMetadataName
[] = "metadata";
35 const char kMinidumpSubdir
[] = "minidumps";
37 // A trivial implementation of SynchronizedMinidumpManager, which does no work
39 // minidump and exposes its protected members for testing.
40 class SynchronizedMinidumpManagerSimple
: public SynchronizedMinidumpManager
{
42 SynchronizedMinidumpManagerSimple()
43 : SynchronizedMinidumpManager(),
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
{
58 add_entry_return_code_
= AddEntryToLockFile(*dump_info_
);
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_
; }
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
{
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
92 base::PlatformThread::Sleep(
93 base::TimeDelta::FromMilliseconds(sleep_duration_ms_
));
94 return SynchronizedMinidumpManagerSimple::DoWork();
98 const int sleep_duration_ms_
;
101 class SynchronizedMinidumpManagerTest
: public testing::Test
{
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.
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));
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_|.
138 scoped_ptr
<base::ScopedPathOverride
> path_override_
;
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);
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(
276 base::Bind(&DoWorkLockedTask
, base::Unretained(&sleepy_manager
)));
278 // Meanwhile, this thread should wait brielfy to allow the other thread to
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
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
) {
312 pid_t pid
= base::ForkWithFlags(0u, nullptr, nullptr);
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());
323 // Meanwhile, this process should wait brielfy to allow the other thread to
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";
349 pid_t pid
= base::ForkWithFlags(0u, nullptr, nullptr);
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());
362 // Meanwhile, this process should wait brielfy to allow the other thread to
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
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