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