1 // Copyright 2016 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.
4
5 #include "chromecast/crash/linux/minidump_uploader.h"
6
7 #include <memory>
8 #include <vector>
9
10 #include "base/base_paths.h"
11 #include "base/bind.h"
12 #include "base/files/file_util.h"
13 #include "base/files/scoped_temp_dir.h"
14 #include "base/test/scoped_path_override.h"
15 #include "base/time/time.h"
16 #include "chromecast/base/cast_sys_info_dummy.h"
17 #include "chromecast/base/pref_names.h"
18 #include "chromecast/crash/cast_crashdump_uploader.h"
19 #include "chromecast/crash/linux/crash_testing_utils.h"
20 #include "chromecast/public/cast_sys_info.h"
21 #include "components/metrics/metrics_pref_names.h"
22 #include "components/prefs/pref_registry_simple.h"
23 #include "components/prefs/pref_service.h"
24 #include "components/prefs/testing_pref_service.h"
25 #include "testing/gmock/include/gmock/gmock.h"
26 #include "testing/gtest/include/gtest/gtest.h"
27
28 namespace chromecast {
29 namespace {
30
31 const char kLockfileName[] = "lockfile";
32 const char kMetadataName[] = "metadata";
33 const char kMinidumpSubdir[] = "minidumps";
34 const char kVirtualChannel[] = "virtual-channel";
35 const char kVirtualChannelName[] = "a-virtual-chanel";
36
37 typedef std::vector<std::unique_ptr<DumpInfo>> DumpList;
38
CreateFakePrefService(bool opt_in)39 std::unique_ptr<PrefService> CreateFakePrefService(bool opt_in) {
40 std::unique_ptr<TestingPrefServiceSimple> retval(
41 new TestingPrefServiceSimple);
42 retval->registry()->RegisterBooleanPref(prefs::kOptInStats, opt_in);
43 retval->registry()->RegisterStringPref(::metrics::prefs::kMetricsClientID,
44 "");
45 retval->registry()->RegisterStringPref(kVirtualChannel, kVirtualChannelName);
46 return std::move(retval);
47 }
48
DumpsAreEqual(const DumpInfo & l,const DumpInfo & r)49 bool DumpsAreEqual(const DumpInfo& l, const DumpInfo& r) {
50 return l.crashed_process_dump() == r.crashed_process_dump() &&
51 l.logfile() == r.logfile();
52 }
53
54 class MockCastCrashdumpUploader : public CastCrashdumpUploader {
55 public:
MockCastCrashdumpUploader(const CastCrashdumpData & data)56 explicit MockCastCrashdumpUploader(const CastCrashdumpData& data)
57 : CastCrashdumpUploader(data) {}
58
59 MOCK_METHOD2(AddAttachment,
60 bool(const std::string& label, const std::string& filename));
61 MOCK_METHOD2(SetParameter,
62 void(const std::string& key, const std::string& value));
63 MOCK_METHOD1(Upload, bool(std::string* response));
64 };
65
66 using ::testing::_;
67 using ::testing::AtLeast;
68 using ::testing::Return;
69 using ::testing::StrictMock;
70
71 class MinidumpUploaderTest : public testing::Test {
72 public:
MinidumpUploaderTest()73 MinidumpUploaderTest() {}
~MinidumpUploaderTest()74 ~MinidumpUploaderTest() override {}
75
76 protected:
SetUp()77 void SetUp() override {
78 // Set up a temporary directory which will be used as our fake home dir.
79 ASSERT_TRUE(fake_home_dir_.CreateUniqueTempDir());
80 path_override_.reset(
81 new base::ScopedPathOverride(base::DIR_HOME, fake_home_dir_.GetPath()));
82
83 minidump_dir_ = fake_home_dir_.GetPath().Append(kMinidumpSubdir);
84 lockfile_ = minidump_dir_.Append(kLockfileName);
85 metadata_ = minidump_dir_.Append(kMetadataName);
86
87 // Create minidump directory.
88 ASSERT_TRUE(base::CreateDirectory(minidump_dir_));
89
90 CastCrashdumpData data;
91 mock_crash_uploader_.reset(new StrictMock<MockCastCrashdumpUploader>(data));
92 }
93
GenerateDumpWithFiles(const base::FilePath & minidump_path,const base::FilePath & logfile_path)94 std::unique_ptr<DumpInfo> GenerateDumpWithFiles(
95 const base::FilePath& minidump_path,
96 const base::FilePath& logfile_path) {
97 // Must pass in non-empty MinidumpParams to circumvent the internal checks.
98 std::unique_ptr<DumpInfo> dump(new DumpInfo(
99 minidump_path.value(), logfile_path.value(), base::Time::Now(),
100 MinidumpParams(0, "_", "_", "_", "_", "_", "_", "_", "_")));
101
102 CHECK(AppendLockFile(lockfile_.value(), metadata_.value(), *dump));
103 base::File minidump(
104 minidump_path, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
105 base::File logfile(logfile_path,
106 base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
107 CHECK(minidump.IsValid());
108 CHECK(logfile.IsValid());
109
110 return dump;
111 }
112
mock_crash_uploader()113 MockCastCrashdumpUploader& mock_crash_uploader() {
114 return *mock_crash_uploader_;
115 }
116
sys_info_dummy()117 CastSysInfoDummy& sys_info_dummy() { return sys_info_dummy_; }
118
119 base::FilePath minidump_dir_; // Path to the minidump directory.
120 base::FilePath lockfile_; // Path to the lockfile in |minidump_dir_|.
121 base::FilePath metadata_; // Path to the metadata in |minidump_dir_|.
122
123 private:
124 base::ScopedTempDir fake_home_dir_;
125 std::unique_ptr<base::ScopedPathOverride> path_override_;
126
127 CastSysInfoDummy sys_info_dummy_;
128 std::unique_ptr<StrictMock<MockCastCrashdumpUploader>> mock_crash_uploader_;
129 };
130
TEST_F(MinidumpUploaderTest,AvoidsLockingWithoutDumps)131 TEST_F(MinidumpUploaderTest, AvoidsLockingWithoutDumps) {
132 class LockingTest : public SynchronizedMinidumpManager {
133 public:
134 explicit LockingTest(MinidumpUploader* minidump_uploader)
135 : minidump_uploader_(minidump_uploader) {}
136 ~LockingTest() override = default;
137
138 bool Run() { return AcquireLockAndDoWork(); }
139
140 // SynchronizedMinidumpManager implementation:
141 bool DoWork() override {
142 // This should fail if it attempts to get the lock.
143 return minidump_uploader_->UploadAllMinidumps();
144 }
145
146 private:
147 MinidumpUploader* const minidump_uploader_;
148 };
149 MinidumpUploader uploader(&sys_info_dummy(), "", &mock_crash_uploader(),
150 base::BindRepeating(&CreateFakePrefService, true));
151 // Will lock for the first run to initialize file state.
152 ASSERT_TRUE(uploader.UploadAllMinidumps());
153
154 LockingTest lt(&uploader);
155 EXPECT_TRUE(lt.Run());
156 }
157
TEST_F(MinidumpUploaderTest,RemovesDumpsWithoutOptIn)158 TEST_F(MinidumpUploaderTest, RemovesDumpsWithoutOptIn) {
159 const base::FilePath& minidump_path = minidump_dir_.Append("ayy");
160 const base::FilePath& logfile_path = minidump_dir_.Append("lmao");
161
162 // Write a dump info entry.
163 GenerateDumpWithFiles(minidump_path, logfile_path);
164 MinidumpUploader uploader(&sys_info_dummy(), "", &mock_crash_uploader(),
165 base::BindRepeating(&CreateFakePrefService, false));
166
167 // MinidumpUploader should not call upon CastCrashdumpUploader.
168 ASSERT_TRUE(uploader.UploadAllMinidumps());
169
170 // Ensure dump files were deleted, lockfile was emptied.
171 ASSERT_FALSE(base::PathExists(minidump_path));
172 ASSERT_FALSE(base::PathExists(logfile_path));
173
174 int64_t size = -1;
175 ASSERT_TRUE(base::GetFileSize(lockfile_, &size));
176 ASSERT_EQ(size, 0);
177 }
178
TEST_F(MinidumpUploaderTest,SavesDumpInfoWithUploadFailure)179 TEST_F(MinidumpUploaderTest, SavesDumpInfoWithUploadFailure) {
180 const base::FilePath& minidump_path = minidump_dir_.Append("ayy");
181 const base::FilePath& logfile_path = minidump_dir_.Append("lmao");
182
183 // Write one entry with appropriate files.
184 std::unique_ptr<DumpInfo> dump(
185 GenerateDumpWithFiles(minidump_path, logfile_path));
186 MinidumpUploader uploader(&sys_info_dummy(), "", &mock_crash_uploader(),
187 base::BindRepeating(&CreateFakePrefService, true));
188
189 // Induce an upload failure.
190 EXPECT_CALL(mock_crash_uploader(),
191 AddAttachment("log_file", logfile_path.value()))
192 .WillOnce(Return(true));
193 EXPECT_CALL(mock_crash_uploader(), SetParameter(_, _)).Times(AtLeast(0));
194 EXPECT_CALL(mock_crash_uploader(), Upload(_)).WillOnce(Return(false));
195 ASSERT_TRUE(uploader.UploadAllMinidumps());
196
197 // Ensure dump files were preserved, lockfile was not emptied.
198 ASSERT_TRUE(base::PathExists(minidump_path));
199 ASSERT_TRUE(base::PathExists(logfile_path));
200
201 DumpList dumps;
202 ASSERT_TRUE(FetchDumps(lockfile_.value(), &dumps));
203 ASSERT_TRUE(DumpsAreEqual(*dump, *dumps.front()));
204 }
205
TEST_F(MinidumpUploaderTest,SavesRemainingDumpInfoWithMidwayUploadFailure)206 TEST_F(MinidumpUploaderTest, SavesRemainingDumpInfoWithMidwayUploadFailure) {
207 const base::FilePath& minidump_path = minidump_dir_.Append("ayy");
208 const base::FilePath& logfile_path = minidump_dir_.Append("lmao");
209 const base::FilePath& minidump_path2 = minidump_dir_.Append("ayy2");
210 const base::FilePath& logfile_path2 = minidump_dir_.Append("lmao2");
211
212 // Write two entries, each with their own files.
213 GenerateDumpWithFiles(minidump_path, logfile_path);
214 std::unique_ptr<DumpInfo> dump2(
215 GenerateDumpWithFiles(minidump_path2, logfile_path2));
216 {
217 MinidumpUploader uploader(
218 &sys_info_dummy(), "", &mock_crash_uploader(),
219 base::BindRepeating(&CreateFakePrefService, true));
220
221 // First allow a successful upload, then induce failure.
222 EXPECT_CALL(mock_crash_uploader(),
223 AddAttachment("log_file", logfile_path.value()))
224 .WillOnce(Return(true));
225 EXPECT_CALL(mock_crash_uploader(),
226 AddAttachment("log_file", logfile_path2.value()))
227 .WillOnce(Return(true));
228 EXPECT_CALL(mock_crash_uploader(), SetParameter(_, _)).Times(AtLeast(0));
229 EXPECT_CALL(mock_crash_uploader(), Upload(_))
230 .WillOnce(Return(true))
231 .WillOnce(Return(false));
232 ASSERT_TRUE(uploader.UploadAllMinidumps());
233 }
234
235 // Info should exist in the lockfile, but should only be non-uploaded dump.
236 DumpList dumps;
237 ASSERT_TRUE(FetchDumps(lockfile_.value(), &dumps));
238 ASSERT_TRUE(DumpsAreEqual(*dump2, *dumps.front()));
239
240 // Ensure uploaded files are gone, non-uploaded files remain.
241 ASSERT_FALSE(base::PathExists(minidump_path));
242 ASSERT_FALSE(base::PathExists(logfile_path));
243 ASSERT_TRUE(base::PathExists(minidump_path2));
244 ASSERT_TRUE(base::PathExists(logfile_path2));
245
246 {
247 MinidumpUploader uploader(
248 &sys_info_dummy(), "", &mock_crash_uploader(),
249 base::BindRepeating(&CreateFakePrefService, true));
250
251 // Finally, upload successfully.
252 EXPECT_CALL(mock_crash_uploader(),
253 AddAttachment("log_file", logfile_path2.value()))
254 .WillOnce(Return(true));
255 EXPECT_CALL(mock_crash_uploader(), SetParameter(_, _)).Times(AtLeast(0));
256 EXPECT_CALL(mock_crash_uploader(), Upload(_)).WillOnce(Return(true));
257 ASSERT_TRUE(uploader.UploadAllMinidumps());
258 }
259
260 // Ensure all dump files have been removed, lockfile has been emptied.
261 int64_t size = -1;
262 ASSERT_TRUE(base::GetFileSize(lockfile_, &size));
263 ASSERT_EQ(size, 0);
264
265 ASSERT_TRUE(base::DeleteFile(lockfile_));
266 ASSERT_TRUE(base::DeleteFile(metadata_));
267 ASSERT_TRUE(base::IsDirectoryEmpty(minidump_dir_));
268 }
269
TEST_F(MinidumpUploaderTest,FailsUploadWithMissingMinidumpFile)270 TEST_F(MinidumpUploaderTest, FailsUploadWithMissingMinidumpFile) {
271 const base::FilePath& minidump_path = minidump_dir_.Append("ayy");
272 const base::FilePath& logfile_path = minidump_dir_.Append("lmao");
273
274 // Write one entry with appropriate files.
275 GenerateDumpWithFiles(minidump_path, logfile_path);
276 MinidumpUploader uploader(&sys_info_dummy(), "", &mock_crash_uploader(),
277 base::BindRepeating(&CreateFakePrefService, true));
278
279 // No CastCrashdumpUploader methods should be called.
280 ASSERT_TRUE(base::DeleteFile(minidump_path));
281 ASSERT_TRUE(uploader.UploadAllMinidumps());
282
283 // Ensure dump files were deleted, lockfile was emptied.
284 ASSERT_FALSE(base::PathExists(minidump_path));
285 ASSERT_FALSE(base::PathExists(logfile_path));
286
287 int64_t size = -1;
288 ASSERT_TRUE(base::GetFileSize(lockfile_, &size));
289 ASSERT_EQ(size, 0);
290 }
291
TEST_F(MinidumpUploaderTest,UploadsWithoutMissingLogFile)292 TEST_F(MinidumpUploaderTest, UploadsWithoutMissingLogFile) {
293 const base::FilePath& minidump_path = minidump_dir_.Append("ayy");
294 const base::FilePath& logfile_path = minidump_dir_.Append("lmao");
295
296 // Write one entry with appropriate files.
297 GenerateDumpWithFiles(minidump_path, logfile_path);
298 MinidumpUploader uploader(&sys_info_dummy(), "", &mock_crash_uploader(),
299 base::BindRepeating(&CreateFakePrefService, true));
300
301 // Delete logfile, crash uploader should still work as intended.
302 ASSERT_TRUE(base::DeleteFile(logfile_path));
303 EXPECT_CALL(mock_crash_uploader(), SetParameter(_, _)).Times(AtLeast(0));
304 EXPECT_CALL(mock_crash_uploader(), Upload(_)).WillOnce(Return(true));
305 ASSERT_TRUE(uploader.UploadAllMinidumps());
306
307 // Ensure dump files were deleted, lockfile was emptied.
308 ASSERT_FALSE(base::PathExists(minidump_path));
309 ASSERT_FALSE(base::PathExists(logfile_path));
310
311 int64_t size = -1;
312 ASSERT_TRUE(base::GetFileSize(lockfile_, &size));
313 ASSERT_EQ(size, 0);
314 }
315
TEST_F(MinidumpUploaderTest,DeletesLingeringFiles)316 TEST_F(MinidumpUploaderTest, DeletesLingeringFiles) {
317 const base::FilePath& minidump_path = minidump_dir_.Append("ayy");
318 const base::FilePath& logfile_path = minidump_dir_.Append("lmao");
319 const base::FilePath& temp1 = minidump_dir_.Append("chrome");
320 const base::FilePath& temp2 = minidump_dir_.Append("cast");
321
322 // Create "fake" lingering files in minidump directory.
323 base::File generator(temp1,
324 base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
325 generator.Close();
326 generator.Initialize(temp2,
327 base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
328 generator.Close();
329 ASSERT_TRUE(base::PathExists(temp1));
330 ASSERT_TRUE(base::PathExists(temp2));
331
332 // Write a real entry.
333 GenerateDumpWithFiles(minidump_path, logfile_path);
334 MinidumpUploader uploader(&sys_info_dummy(), "", &mock_crash_uploader(),
335 base::BindRepeating(&CreateFakePrefService, true));
336
337 EXPECT_CALL(mock_crash_uploader(),
338 AddAttachment("log_file", logfile_path.value()))
339 .WillOnce(Return(true));
340 EXPECT_CALL(mock_crash_uploader(), SetParameter(_, _)).Times(AtLeast(0));
341 EXPECT_CALL(mock_crash_uploader(), Upload(_)).WillOnce(Return(true));
342 ASSERT_TRUE(uploader.UploadAllMinidumps());
343
344 // Ensure dump/lingering files were deleted, lockfile was emptied.
345 ASSERT_FALSE(base::PathExists(minidump_path));
346 ASSERT_FALSE(base::PathExists(logfile_path));
347 ASSERT_FALSE(base::PathExists(temp1));
348 ASSERT_FALSE(base::PathExists(temp2));
349
350 int64_t size = -1;
351 ASSERT_TRUE(base::GetFileSize(lockfile_, &size));
352 ASSERT_EQ(size, 0);
353 }
354
TEST_F(MinidumpUploaderTest,SchedulesRebootWhenRatelimited)355 TEST_F(MinidumpUploaderTest, SchedulesRebootWhenRatelimited) {
356 const base::FilePath& minidump_path = minidump_dir_.Append("ayy");
357 const base::FilePath& logfile_path = minidump_dir_.Append("lmao");
358
359 MinidumpUploader uploader(&sys_info_dummy(), "", &mock_crash_uploader(),
360 base::BindRepeating(&CreateFakePrefService, true));
361 // Generate max dumps.
362 for (int i = 0; i < SynchronizedMinidumpManager::kRatelimitPeriodMaxDumps + 1;
363 i++)
364 GenerateDumpWithFiles(minidump_path, logfile_path);
365
366 // MinidumpUploader should call CastCrashdumpUploader once (other |max| dumps
367 // files do not exist). Reboot should be scheduled, as this is first
368 // ratelimit.
369 EXPECT_CALL(mock_crash_uploader(),
370 AddAttachment("log_file", logfile_path.value()))
371 .WillOnce(Return(true))
372 .RetiresOnSaturation();
373 EXPECT_CALL(mock_crash_uploader(), SetParameter(_, _)).Times(AtLeast(0));
374 EXPECT_CALL(mock_crash_uploader(), Upload(_))
375 .WillOnce(Return(true))
376 .RetiresOnSaturation();
377 ASSERT_TRUE(uploader.UploadAllMinidumps());
378 ASSERT_TRUE(uploader.reboot_scheduled());
379
380 // Ensure dump files were deleted, lockfile was emptied.
381 ASSERT_FALSE(base::PathExists(minidump_path));
382 ASSERT_FALSE(base::PathExists(logfile_path));
383
384 int64_t size = -1;
385 ASSERT_TRUE(base::GetFileSize(lockfile_, &size));
386 ASSERT_EQ(size, 0);
387
388 // Generate one dump for a second pass.
389 GenerateDumpWithFiles(minidump_path, logfile_path);
390 MinidumpUploader uploader2(&sys_info_dummy(), "", &mock_crash_uploader(),
391 base::BindRepeating(&CreateFakePrefService, true));
392
393 // Since a reboot was scheduled, the rate limit was cleared. New uploads
394 // should be scheduled.
395 EXPECT_CALL(mock_crash_uploader(),
396 AddAttachment("log_file", logfile_path.value()))
397 .WillOnce(Return(true));
398 EXPECT_CALL(mock_crash_uploader(), Upload(_)).WillOnce(Return(true));
399 ASSERT_TRUE(uploader2.UploadAllMinidumps());
400 ASSERT_FALSE(uploader2.reboot_scheduled());
401
402 // Ensure dump files were deleted, lockfile was emptied.
403 ASSERT_FALSE(base::PathExists(minidump_path));
404 ASSERT_FALSE(base::PathExists(logfile_path));
405
406 ASSERT_TRUE(base::GetFileSize(lockfile_, &size));
407 ASSERT_EQ(size, 0);
408 }
409
TEST_F(MinidumpUploaderTest,UploadInitializesFileState)410 TEST_F(MinidumpUploaderTest, UploadInitializesFileState) {
411 MinidumpUploader uploader(&sys_info_dummy(), "", &mock_crash_uploader(),
412 base::BindRepeating(&CreateFakePrefService, true));
413 ASSERT_TRUE(base::IsDirectoryEmpty(minidump_dir_));
414 ASSERT_TRUE(uploader.UploadAllMinidumps());
415 base::File lockfile(lockfile_, base::File::FLAG_OPEN | base::File::FLAG_READ);
416 EXPECT_TRUE(lockfile.IsValid());
417 base::File metadata(lockfile_, base::File::FLAG_OPEN | base::File::FLAG_READ);
418 EXPECT_TRUE(metadata.IsValid());
419 }
420
421 } // namespace
422 } // namespace chromecast
423