| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chromecast/crash/linux/minidump_uploader.h" |
| |
| #include <memory> |
| #include <vector> |
| |
| #include "base/base_paths.h" |
| #include "base/bind.h" |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/test/scoped_path_override.h" |
| #include "base/time/time.h" |
| #include "chromecast/base/cast_sys_info_dummy.h" |
| #include "chromecast/base/pref_names.h" |
| #include "chromecast/crash/cast_crashdump_uploader.h" |
| #include "chromecast/crash/linux/crash_testing_utils.h" |
| #include "chromecast/public/cast_sys_info.h" |
| #include "components/metrics/metrics_pref_names.h" |
| #include "components/prefs/pref_registry_simple.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/prefs/testing_pref_service.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace chromecast { |
| namespace { |
| |
| const char kLockfileName[] = "lockfile"; |
| const char kMetadataName[] = "metadata"; |
| const char kMinidumpSubdir[] = "minidumps"; |
| |
| typedef std::vector<std::unique_ptr<DumpInfo>> DumpList; |
| |
| std::unique_ptr<PrefService> CreateFakePrefService(bool opt_in) { |
| std::unique_ptr<TestingPrefServiceSimple> retval( |
| new TestingPrefServiceSimple); |
| retval->registry()->RegisterBooleanPref(prefs::kOptInStats, opt_in); |
| retval->registry()->RegisterStringPref(::metrics::prefs::kMetricsClientID, |
| ""); |
| return std::move(retval); |
| } |
| |
| bool DumpsAreEqual(const DumpInfo& l, const DumpInfo& r) { |
| return l.crashed_process_dump() == r.crashed_process_dump() && |
| l.logfile() == r.logfile(); |
| } |
| |
| class MockCastCrashdumpUploader : public CastCrashdumpUploader { |
| public: |
| MockCastCrashdumpUploader(const CastCrashdumpData& data) |
| : CastCrashdumpUploader(data) {} |
| |
| MOCK_METHOD2(AddAttachment, |
| bool(const std::string& label, const std::string& filename)); |
| MOCK_METHOD2(SetParameter, |
| void(const std::string& key, const std::string& value)); |
| MOCK_METHOD1(Upload, bool(std::string* response)); |
| }; |
| |
| using ::testing::_; |
| using ::testing::AtLeast; |
| using ::testing::Return; |
| using ::testing::StrictMock; |
| |
| class MinidumpUploaderTest : public testing::Test { |
| public: |
| MinidumpUploaderTest() {} |
| ~MinidumpUploaderTest() override {} |
| |
| protected: |
| void SetUp() override { |
| // Set up a temporary directory which will be used as our fake home dir. |
| ASSERT_TRUE(fake_home_dir_.CreateUniqueTempDir()); |
| path_override_.reset( |
| new base::ScopedPathOverride(base::DIR_HOME, fake_home_dir_.GetPath())); |
| |
| minidump_dir_ = fake_home_dir_.GetPath().Append(kMinidumpSubdir); |
| lockfile_ = minidump_dir_.Append(kLockfileName); |
| metadata_ = minidump_dir_.Append(kMetadataName); |
| |
| // Create minidump directory. |
| ASSERT_TRUE(base::CreateDirectory(minidump_dir_)); |
| |
| CastCrashdumpData data; |
| mock_crash_uploader_.reset(new StrictMock<MockCastCrashdumpUploader>(data)); |
| } |
| |
| std::unique_ptr<DumpInfo> GenerateDumpWithFiles( |
| const base::FilePath& minidump_path, |
| const base::FilePath& logfile_path) { |
| // Must pass in non-empty MinidumpParams to circumvent the internal checks. |
| std::unique_ptr<DumpInfo> dump(new DumpInfo( |
| minidump_path.value(), logfile_path.value(), base::Time::Now(), |
| MinidumpParams("_", 0, "_", "_", "_", "_", "_", "_", "_"))); |
| |
| CHECK(AppendLockFile(lockfile_.value(), metadata_.value(), *dump)); |
| base::File minidump( |
| minidump_path, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); |
| base::File logfile(logfile_path, |
| base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); |
| CHECK(minidump.IsValid()); |
| CHECK(logfile.IsValid()); |
| |
| return dump; |
| } |
| |
| MockCastCrashdumpUploader& mock_crash_uploader() { |
| return *mock_crash_uploader_; |
| } |
| |
| CastSysInfoDummy& sys_info_dummy() { return sys_info_dummy_; } |
| |
| base::FilePath minidump_dir_; // Path to the minidump directory. |
| base::FilePath lockfile_; // Path to the lockfile in |minidump_dir_|. |
| base::FilePath metadata_; // Path to the metadata in |minidump_dir_|. |
| |
| private: |
| base::ScopedTempDir fake_home_dir_; |
| std::unique_ptr<base::ScopedPathOverride> path_override_; |
| |
| CastSysInfoDummy sys_info_dummy_; |
| std::unique_ptr<StrictMock<MockCastCrashdumpUploader>> mock_crash_uploader_; |
| }; |
| |
| TEST_F(MinidumpUploaderTest, AvoidsLockingWithoutDumps) { |
| MinidumpUploader uploader(&sys_info_dummy(), "", &mock_crash_uploader(), |
| base::Bind(&CreateFakePrefService, true)); |
| |
| // Ensure the uploader did not initialize files unnecessarily. |
| ASSERT_TRUE(uploader.UploadAllMinidumps()); |
| ASSERT_TRUE(base::IsDirectoryEmpty(minidump_dir_)); |
| } |
| |
| TEST_F(MinidumpUploaderTest, RemovesDumpsWithoutOptIn) { |
| const base::FilePath& minidump_path = minidump_dir_.Append("ayy"); |
| const base::FilePath& logfile_path = minidump_dir_.Append("lmao"); |
| |
| // Write a dump info entry. |
| GenerateDumpWithFiles(minidump_path, logfile_path); |
| MinidumpUploader uploader(&sys_info_dummy(), "", &mock_crash_uploader(), |
| base::Bind(&CreateFakePrefService, false)); |
| |
| // MinidumpUploader should not call upon CastCrashdumpUploader. |
| ASSERT_TRUE(uploader.UploadAllMinidumps()); |
| |
| // Ensure dump files were deleted, lockfile was emptied. |
| ASSERT_FALSE(base::PathExists(minidump_path)); |
| ASSERT_FALSE(base::PathExists(logfile_path)); |
| |
| int64_t size = -1; |
| ASSERT_TRUE(base::GetFileSize(lockfile_, &size)); |
| ASSERT_EQ(size, 0); |
| } |
| |
| TEST_F(MinidumpUploaderTest, SavesDumpInfoWithUploadFailure) { |
| const base::FilePath& minidump_path = minidump_dir_.Append("ayy"); |
| const base::FilePath& logfile_path = minidump_dir_.Append("lmao"); |
| |
| // Write one entry with appropriate files. |
| std::unique_ptr<DumpInfo> dump( |
| GenerateDumpWithFiles(minidump_path, logfile_path)); |
| MinidumpUploader uploader(&sys_info_dummy(), "", &mock_crash_uploader(), |
| base::Bind(&CreateFakePrefService, true)); |
| |
| // Induce an upload failure. |
| EXPECT_CALL(mock_crash_uploader(), |
| AddAttachment("log_file", logfile_path.value())) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(mock_crash_uploader(), SetParameter(_, _)).Times(AtLeast(0)); |
| EXPECT_CALL(mock_crash_uploader(), Upload(_)).WillOnce(Return(false)); |
| ASSERT_TRUE(uploader.UploadAllMinidumps()); |
| |
| // Ensure dump files were preserved, lockfile was not emptied. |
| ASSERT_TRUE(base::PathExists(minidump_path)); |
| ASSERT_TRUE(base::PathExists(logfile_path)); |
| |
| DumpList dumps; |
| ASSERT_TRUE(FetchDumps(lockfile_.value(), &dumps)); |
| ASSERT_TRUE(DumpsAreEqual(*dump, *dumps.front())); |
| } |
| |
| TEST_F(MinidumpUploaderTest, SavesRemainingDumpInfoWithMidwayUploadFailure) { |
| const base::FilePath& minidump_path = minidump_dir_.Append("ayy"); |
| const base::FilePath& logfile_path = minidump_dir_.Append("lmao"); |
| const base::FilePath& minidump_path2 = minidump_dir_.Append("ayy2"); |
| const base::FilePath& logfile_path2 = minidump_dir_.Append("lmao2"); |
| |
| // Write two entries, each with their own files. |
| GenerateDumpWithFiles(minidump_path, logfile_path); |
| std::unique_ptr<DumpInfo> dump2( |
| GenerateDumpWithFiles(minidump_path2, logfile_path2)); |
| MinidumpUploader uploader(&sys_info_dummy(), "", &mock_crash_uploader(), |
| base::Bind(&CreateFakePrefService, true)); |
| |
| // First allow a successful upload, then induce failure. |
| EXPECT_CALL(mock_crash_uploader(), |
| AddAttachment("log_file", logfile_path.value())) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(mock_crash_uploader(), |
| AddAttachment("log_file", logfile_path2.value())) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(mock_crash_uploader(), SetParameter(_, _)).Times(AtLeast(0)); |
| EXPECT_CALL(mock_crash_uploader(), Upload(_)) |
| .WillOnce(Return(true)) |
| .WillOnce(Return(false)); |
| ASSERT_TRUE(uploader.UploadAllMinidumps()); |
| |
| // Info should exist in the lockfile, but should only be non-uploaded dump. |
| DumpList dumps; |
| ASSERT_TRUE(FetchDumps(lockfile_.value(), &dumps)); |
| ASSERT_TRUE(DumpsAreEqual(*dump2, *dumps.front())); |
| |
| // Ensure uploaded files are gone, non-uploaded files remain. |
| ASSERT_FALSE(base::PathExists(minidump_path)); |
| ASSERT_FALSE(base::PathExists(logfile_path)); |
| ASSERT_TRUE(base::PathExists(minidump_path2)); |
| ASSERT_TRUE(base::PathExists(logfile_path2)); |
| |
| // Finally, upload successfully. |
| EXPECT_CALL(mock_crash_uploader(), |
| AddAttachment("log_file", logfile_path2.value())) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(mock_crash_uploader(), SetParameter(_, _)).Times(AtLeast(0)); |
| EXPECT_CALL(mock_crash_uploader(), Upload(_)).WillOnce(Return(true)); |
| ASSERT_TRUE(uploader.UploadAllMinidumps()); |
| |
| // Ensure all dump files have been removed, lockfile has been emptied. |
| int64_t size = -1; |
| ASSERT_TRUE(base::GetFileSize(lockfile_, &size)); |
| ASSERT_EQ(size, 0); |
| |
| ASSERT_TRUE(base::DeleteFile(lockfile_, false)); |
| ASSERT_TRUE(base::DeleteFile(metadata_, false)); |
| ASSERT_TRUE(base::IsDirectoryEmpty(minidump_dir_)); |
| } |
| |
| TEST_F(MinidumpUploaderTest, FailsUploadWithMissingMinidumpFile) { |
| const base::FilePath& minidump_path = minidump_dir_.Append("ayy"); |
| const base::FilePath& logfile_path = minidump_dir_.Append("lmao"); |
| |
| // Write one entry with appropriate files. |
| GenerateDumpWithFiles(minidump_path, logfile_path); |
| MinidumpUploader uploader(&sys_info_dummy(), "", &mock_crash_uploader(), |
| base::Bind(&CreateFakePrefService, true)); |
| |
| // No CastCrashdumpUploader methods should be called. |
| ASSERT_TRUE(base::DeleteFile(minidump_path, false)); |
| ASSERT_TRUE(uploader.UploadAllMinidumps()); |
| |
| // Ensure dump files were deleted, lockfile was emptied. |
| ASSERT_FALSE(base::PathExists(minidump_path)); |
| ASSERT_FALSE(base::PathExists(logfile_path)); |
| |
| int64_t size = -1; |
| ASSERT_TRUE(base::GetFileSize(lockfile_, &size)); |
| ASSERT_EQ(size, 0); |
| } |
| |
| TEST_F(MinidumpUploaderTest, UploadsWithoutMissingLogFile) { |
| const base::FilePath& minidump_path = minidump_dir_.Append("ayy"); |
| const base::FilePath& logfile_path = minidump_dir_.Append("lmao"); |
| |
| // Write one entry with appropriate files. |
| GenerateDumpWithFiles(minidump_path, logfile_path); |
| MinidumpUploader uploader(&sys_info_dummy(), "", &mock_crash_uploader(), |
| base::Bind(&CreateFakePrefService, true)); |
| |
| // Delete logfile, crash uploader should still work as intended. |
| ASSERT_TRUE(base::DeleteFile(logfile_path, false)); |
| EXPECT_CALL(mock_crash_uploader(), SetParameter(_, _)).Times(AtLeast(0)); |
| EXPECT_CALL(mock_crash_uploader(), Upload(_)).WillOnce(Return(true)); |
| ASSERT_TRUE(uploader.UploadAllMinidumps()); |
| |
| // Ensure dump files were deleted, lockfile was emptied. |
| ASSERT_FALSE(base::PathExists(minidump_path)); |
| ASSERT_FALSE(base::PathExists(logfile_path)); |
| |
| int64_t size = -1; |
| ASSERT_TRUE(base::GetFileSize(lockfile_, &size)); |
| ASSERT_EQ(size, 0); |
| } |
| |
| TEST_F(MinidumpUploaderTest, DeletesLingeringFiles) { |
| const base::FilePath& minidump_path = minidump_dir_.Append("ayy"); |
| const base::FilePath& logfile_path = minidump_dir_.Append("lmao"); |
| const base::FilePath& temp1 = minidump_dir_.Append("chrome"); |
| const base::FilePath& temp2 = minidump_dir_.Append("cast"); |
| |
| // Create "fake" lingering files in minidump directory. |
| base::File generator(temp1, |
| base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); |
| generator.Close(); |
| generator.Initialize(temp2, |
| base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); |
| generator.Close(); |
| ASSERT_TRUE(base::PathExists(temp1)); |
| ASSERT_TRUE(base::PathExists(temp2)); |
| |
| // Write a real entry. |
| GenerateDumpWithFiles(minidump_path, logfile_path); |
| MinidumpUploader uploader(&sys_info_dummy(), "", &mock_crash_uploader(), |
| base::Bind(&CreateFakePrefService, true)); |
| |
| EXPECT_CALL(mock_crash_uploader(), |
| AddAttachment("log_file", logfile_path.value())) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(mock_crash_uploader(), SetParameter(_, _)).Times(AtLeast(0)); |
| EXPECT_CALL(mock_crash_uploader(), Upload(_)).WillOnce(Return(true)); |
| ASSERT_TRUE(uploader.UploadAllMinidumps()); |
| |
| // Ensure dump/lingering files were deleted, lockfile was emptied. |
| ASSERT_FALSE(base::PathExists(minidump_path)); |
| ASSERT_FALSE(base::PathExists(logfile_path)); |
| ASSERT_FALSE(base::PathExists(temp1)); |
| ASSERT_FALSE(base::PathExists(temp2)); |
| |
| int64_t size = -1; |
| ASSERT_TRUE(base::GetFileSize(lockfile_, &size)); |
| ASSERT_EQ(size, 0); |
| } |
| |
| TEST_F(MinidumpUploaderTest, SchedulesRebootWhenRatelimited) { |
| const base::FilePath& minidump_path = minidump_dir_.Append("ayy"); |
| const base::FilePath& logfile_path = minidump_dir_.Append("lmao"); |
| |
| MinidumpUploader uploader(&sys_info_dummy(), "", &mock_crash_uploader(), |
| base::Bind(&CreateFakePrefService, true)); |
| // Generate max dumps. |
| for (int i = 0; i < SynchronizedMinidumpManager::kRatelimitPeriodMaxDumps + 1; |
| i++) |
| GenerateDumpWithFiles(minidump_path, logfile_path); |
| |
| // MinidumpUploader should call CastCrashdumpUploader once (other |max| dumps |
| // files do not exist). Reboot should be scheduled, as this is first |
| // ratelimit. |
| EXPECT_CALL(mock_crash_uploader(), |
| AddAttachment("log_file", logfile_path.value())) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(mock_crash_uploader(), SetParameter(_, _)).Times(AtLeast(0)); |
| EXPECT_CALL(mock_crash_uploader(), Upload(_)).WillOnce(Return(true)); |
| ASSERT_TRUE(uploader.UploadAllMinidumps()); |
| ASSERT_TRUE(uploader.reboot_scheduled()); |
| |
| // Ensure dump files were deleted, lockfile was emptied. |
| ASSERT_FALSE(base::PathExists(minidump_path)); |
| ASSERT_FALSE(base::PathExists(logfile_path)); |
| |
| int64_t size = -1; |
| ASSERT_TRUE(base::GetFileSize(lockfile_, &size)); |
| ASSERT_EQ(size, 0); |
| |
| // Generate one dump for a second pass. |
| GenerateDumpWithFiles(minidump_path, logfile_path); |
| MinidumpUploader uploader2(&sys_info_dummy(), "", &mock_crash_uploader(), |
| base::Bind(&CreateFakePrefService, true)); |
| |
| // MinidumpUploader should not call CastCrashdumpUploader (due to ratelimit). |
| // Reboot should NOT be scheduled, as this is second ratelimit. |
| ASSERT_TRUE(uploader2.UploadAllMinidumps()); |
| ASSERT_FALSE(uploader2.reboot_scheduled()); |
| |
| // Ensure dump files were deleted, lockfile was emptied. |
| ASSERT_FALSE(base::PathExists(minidump_path)); |
| ASSERT_FALSE(base::PathExists(logfile_path)); |
| |
| ASSERT_TRUE(base::GetFileSize(lockfile_, &size)); |
| ASSERT_EQ(size, 0); |
| } |
| |
| } // namespace |
| } // namespace chromecast |