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/dump_info.h"
26 #include "chromecast/crash/linux/synchronized_minidump_manager.h"
27 #include "testing/gtest/include/gtest/gtest.h"
29 namespace chromecast
{
32 const char kLockfileName
[] = "lockfile";
33 const char kMinidumpSubdir
[] = "minidumps";
35 ScopedVector
<DumpInfo
> GetCurrentDumps(const std::string
& logfile_path
) {
36 ScopedVector
<DumpInfo
> dumps
;
39 std::ifstream
in(logfile_path
);
41 while (std::getline(in
, entry
)) {
42 scoped_ptr
<DumpInfo
> info(new DumpInfo(entry
));
43 dumps
.push_back(info
.Pass());
48 // A trivial implementation of SynchronizedMinidumpManager, which does no work
50 // minidump and exposes its protected members for testing.
51 class SynchronizedMinidumpManagerSimple
: public SynchronizedMinidumpManager
{
53 SynchronizedMinidumpManagerSimple()
54 : SynchronizedMinidumpManager(),
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
{
69 add_entry_return_code_
= AddEntryToLockFile(*dump_info_
);
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_
; }
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
{
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
103 base::PlatformThread::Sleep(
104 base::TimeDelta::FromMilliseconds(sleep_duration_ms_
));
105 return SynchronizedMinidumpManagerSimple::DoWork();
109 const int sleep_duration_ms_
;
112 class SynchronizedMinidumpManagerTest
: public testing::Test
{
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.
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));
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_|.
147 scoped_ptr
<base::ScopedPathOverride
> path_override_
;
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);
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(
281 base::Bind(&DoWorkLockedTask
, base::Unretained(&sleepy_manager
)));
283 // Meanwhile, this thread should wait brielfy to allow the other thread to
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
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
) {
316 pid_t pid
= base::ForkWithFlags(0u, nullptr, nullptr);
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());
327 // Meanwhile, this process should wait brielfy to allow the other thread to
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";
352 pid_t pid
= base::ForkWithFlags(0u, nullptr, nullptr);
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());
365 // Meanwhile, this process should wait brielfy to allow the other thread to
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
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