| // Copyright 2018 The Chromium OS 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 "crash-reporter/crash_sender_util.h" |
| |
| #include <fcntl.h> |
| #include <inttypes.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| |
| #include <memory> |
| #include <numeric> |
| #include <string> |
| #include <tuple> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/at_exit.h> |
| #include <base/bind.h> |
| #include <base/command_line.h> |
| #include <base/files/file.h> |
| #include <base/files/file_enumerator.h> |
| #include <base/files/file_util.h> |
| #include <base/files/scoped_temp_dir.h> |
| #include <base/json/json_reader.h> |
| #include <base/macros.h> |
| #include <base/stl_util.h> |
| #include <base/strings/string_split.h> |
| #include <base/strings/string_util.h> |
| #include <base/strings/strcat.h> |
| #include <base/time/time.h> |
| #include <base/values.h> |
| #include <brillo/flag_helper.h> |
| #include <brillo/key_value_store.h> |
| #include <brillo/process/process.h> |
| #include <chromeos/dbus/service_constants.h> |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| #include <metrics/metrics_library_mock.h> |
| #include <shill/dbus-proxy-mocks.h> |
| |
| #include "crash-reporter/crash_sender_paths.h" |
| #include "crash-reporter/paths.h" |
| #include "crash-reporter/test_util.h" |
| |
| using ::testing::_; |
| using ::testing::DoAll; |
| using ::testing::ExitedWithCode; |
| using ::testing::HasSubstr; |
| using ::testing::Invoke; |
| using ::testing::IsEmpty; |
| using ::testing::Return; |
| using ::testing::UnorderedElementsAre; |
| |
| namespace util { |
| namespace { |
| |
| // Enum types for setting the runtime conditions. |
| enum BuildType { kOfficialBuild, kUnofficialBuild }; |
| enum SessionType { kSignInMode, kGuestMode }; |
| enum MetricsFlag { kMetricsEnabled, kMetricsDisabled }; |
| |
| constexpr char kFakeClientId[] = "00112233445566778899aabbccddeeff"; |
| |
| // This is what the kConnectionState property will get set to for mocked calls |
| // into shill flimflam manager. |
| std::string* g_connection_state; |
| |
| // Simple mock for Clock. We can't use SimpleTestClock in some places because |
| // we need Now to return different values on different calls, and we don't have |
| // a hook to gain control in between the Now() calls. |
| class MockClock : public base::Clock { |
| public: |
| ~MockClock() override {} |
| MOCK_METHOD(base::Time, Now, (), (const override)); |
| }; |
| |
| // Parses the Chrome uploads.log file from Sender to a vector of items per line. |
| // Example: |
| // |
| // {"field1":"foo1","field2":"foo2"} |
| // {"field1":"bar1","field2":"bar2"} |
| // |
| // => [{"field1":"foo1","field2":"foo2"}, {"field1":"bar1","field2":"bar2"}] |
| // |
| std::vector<base::Optional<base::Value>> ParseChromeUploadsLog( |
| const std::string& contents) { |
| std::vector<base::Optional<base::Value>> rows; |
| |
| std::vector<std::string> lines = base::SplitString( |
| contents, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); |
| for (const auto& line : lines) { |
| rows.push_back(base::JSONReader::Read(line)); |
| } |
| |
| return rows; |
| } |
| |
| // Helper function for calling GetBasePartOfCrashFile() concisely for tests. |
| std::string GetBasePartHelper(const std::string& file_name) { |
| return GetBasePartOfCrashFile(base::FilePath(file_name)).value(); |
| } |
| |
| // Helper function for calling base::TouchFile() concisely for tests. |
| bool TouchFileHelper(const base::FilePath& file_name, |
| base::Time modified_time) { |
| return base::TouchFile(file_name, modified_time, modified_time); |
| } |
| |
| // Creates lsb-release file with information about the build type. |
| bool CreateLsbReleaseFile(BuildType type) { |
| std::string label = "Official build"; |
| if (type == kUnofficialBuild) |
| label = "Test build"; |
| |
| return test_util::CreateFile(paths::Get("/etc/lsb-release"), |
| "CHROMEOS_RELEASE_DESCRIPTION=" + label + "\n"); |
| } |
| |
| // Creates a file that indicates uploading of device coredumps is allowed. |
| bool CreateDeviceCoredumpUploadAllowedFile() { |
| return test_util::CreateFile( |
| paths::GetAt(paths::kCrashReporterStateDirectory, |
| paths::kDeviceCoredumpUploadAllowed), |
| ""); |
| } |
| |
| // Creates the client ID file and stores the fake client ID in it. |
| bool CreateClientIdFile() { |
| return test_util::CreateFile( |
| paths::GetAt(paths::kCrashSenderStateDirectory, paths::kClientId), |
| kFakeClientId); |
| } |
| |
| // Returns file names found in |directory|. |
| std::vector<base::FilePath> GetFileNamesIn(const base::FilePath& directory) { |
| std::vector<base::FilePath> files; |
| base::FileEnumerator iter(directory, false /* recursive */, |
| base::FileEnumerator::FILES, "*"); |
| for (base::FilePath file = iter.Next(); !file.empty(); file = iter.Next()) |
| files.push_back(file); |
| return files; |
| } |
| |
| // Fake sleep function that records the requested sleep time. |
| void FakeSleep(std::vector<base::TimeDelta>* sleep_times, |
| base::TimeDelta duration) { |
| sleep_times->push_back(duration); |
| } |
| |
| // Set the file flag which indicates we are mocking crash sending, either |
| // successfully or as a a failure. This also creates the directory where |
| // uploads.log is written to since Chrome would normally be doing that. |
| bool SetMockCrashSending(bool success) { |
| return test_util::CreateFile(paths::GetAt(paths::kSystemRunStateDirectory, |
| paths::kMockCrashSending), |
| success ? "" : "0") && |
| base::CreateDirectory(paths::Get(paths::kChromeCrashLog).DirName()); |
| } |
| |
| // Handles calls for getting the network state. |
| bool GetShillProperties( |
| brillo::VariantDictionary* dict, |
| brillo::ErrorPtr* error, |
| int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) { |
| dict->emplace(shill::kConnectionStateProperty, *g_connection_state); |
| return true; |
| } |
| |
| class CrashSenderUtilTest : public testing::Test { |
| protected: |
| void SetUp() override { |
| // Grab executable path before TearDown() can reset base::CommandLine. |
| if (build_directory_ == nullptr) { |
| base::FilePath my_executable_path = |
| base::CommandLine::ForCurrentProcess()->GetProgram(); |
| build_directory_ = new base::FilePath(my_executable_path.DirName()); |
| } |
| metrics_lib_ = std::make_unique<MetricsLibraryMock>(); |
| ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); |
| test_dir_ = temp_dir_.GetPath(); |
| paths::SetPrefixForTesting(test_dir_); |
| |
| // Make sure the directory for the lock file exists. |
| const base::FilePath lock_file_path = |
| paths::Get(paths::kCrashSenderLockFile); |
| const base::FilePath lock_file_directory = lock_file_path.DirName(); |
| ASSERT_TRUE(base::CreateDirectory(lock_file_directory)); |
| |
| // We need to properly init the CommandLine object for the command line |
| // parsing tests. |
| base::CommandLine::Init(0, nullptr); |
| } |
| |
| void TearDown() override { |
| paths::SetPrefixForTesting(base::FilePath()); |
| |
| // ParseCommandLine() uses base::CommandLine via |
| // brillo::FlagHelper. Reset these here to avoid side effects. |
| if (base::CommandLine::InitializedForCurrentProcess()) |
| base::CommandLine::Reset(); |
| brillo::FlagHelper::ResetForTesting(); |
| } |
| |
| // Checks to see if a file is locked by AcquireLockFileOrDie(). |
| bool IsFileLocked(const base::FilePath& file_name) { |
| // AcquireLockFileOrDie creates the file when it runs, so count the file |
| // not existing as "not locked". |
| if (!base::PathExists(file_name)) { |
| return false; |
| } |
| |
| // There's no portable & reliable way for a process to test its own file |
| // locks, so we have to spawn another process (which won't inherit the |
| // locks) to do the testing. |
| CHECK(build_directory_); |
| base::FilePath lock_file_tester = |
| build_directory_->Append("lock_file_tester"); |
| std::string command = lock_file_tester.value() + " " + file_name.value(); |
| int test_result = system(command.c_str()); |
| if (WIFEXITED(test_result)) { |
| if (WEXITSTATUS(test_result) == 0) { |
| return true; |
| } |
| if (WEXITSTATUS(test_result) == 1) { |
| return false; |
| } |
| LOG(FATAL) << "lock_file_tester failed with exit code " |
| << WEXITSTATUS(test_result); |
| } |
| LOG(FATAL) |
| << "lock_file_tester failed before exiting; complete wait status " |
| << test_result; |
| return false; |
| } |
| |
| // Lock the indicated file |file_name| using base::File::Lock() so that |
| // AcquireLockFileOrDie() will fail to acquire it. File will be created if |
| // it doesn't exist. Returns when the file is actually locked. Since locks are |
| // per-process, in order to prevent this process from locking the file, we |
| // have to spawn a separate process to hold the lock; the process holding the |
| // lock is returned. It can be killed to release the lock. |
| std::unique_ptr<brillo::Process> LockFile(const base::FilePath& file_name) { |
| auto lock_process = std::make_unique<brillo::ProcessImpl>(); |
| CHECK(build_directory_); |
| base::FilePath lock_file_holder = |
| build_directory_->Append("hold_lock_file"); |
| lock_process->AddArg(lock_file_holder.value()); |
| lock_process->AddArg(file_name.value()); |
| CHECK(lock_process->Start()); |
| |
| // Wait for the file to actually be locked. Don't wait forever in case the |
| // subprocess fails in some way. |
| base::Time stop_time = base::Time::Now() + base::TimeDelta::FromMinutes(1); |
| bool success = false; |
| while (!success && base::Time::Now() < stop_time) { |
| base::File lock_file(file_name, base::File::FLAG_OPEN | |
| base::File::FLAG_READ | |
| base::File::FLAG_WRITE); |
| if (lock_file.IsValid()) { |
| struct flock lock; |
| lock.l_type = F_WRLCK; |
| lock.l_whence = SEEK_SET; |
| lock.l_start = 0; |
| lock.l_len = 0; |
| if (fcntl(lock_file.GetPlatformFile(), F_GETLK, &lock) == 0 && |
| lock.l_type == F_WRLCK) { |
| success = true; |
| } |
| } |
| |
| if (!success) { |
| base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(5)); |
| } |
| } |
| |
| CHECK(success) << "Subprocess did not lock " << file_name.value(); |
| return lock_process; |
| } |
| |
| // Creates a file at |file_path| with contents |content| and sets its access |
| // and modification time to |timestamp|. |
| bool CreateFile(const base::FilePath& file_path, |
| base::StringPiece content, |
| base::Time timestamp) { |
| if (!test_util::CreateFile(file_path, content)) |
| return false; |
| |
| if (!TouchFileHelper(file_path, timestamp)) |
| return false; |
| |
| return true; |
| } |
| |
| // Creates test crash files in |crash_directory|. Returns true on success. |
| bool CreateTestCrashFiles(const base::FilePath& crash_directory) { |
| const base::Time now = test_util::GetDefaultTime(); |
| const base::TimeDelta hour = base::TimeDelta::FromHours(1); |
| |
| // Choose timestamps so that the return value of GetMetaFiles() is sorted |
| // per timestamps correctly. |
| const base::Time good_meta_time = now - hour * 3; |
| const base::Time absolute_meta_time = now - hour * 2; |
| const base::Time recent_os_meta_time = now - hour; |
| const base::Time devcore_meta_time = now; |
| |
| // These should be kept, since the payload is a known kind and exists. |
| good_meta_ = crash_directory.Append("good.meta"); |
| good_log_ = crash_directory.Append("good.log"); |
| if (!CreateFile(good_meta_, "payload=good.log\ndone=1\n", good_meta_time)) |
| return false; |
| if (!CreateFile(good_log_, "", now)) |
| return false; |
| |
| // These should be kept, the payload path is absolute but should be handled |
| // properly. |
| absolute_meta_ = crash_directory.Append("absolute.meta"); |
| absolute_log_ = crash_directory.Append("absolute.log"); |
| if (!CreateFile(absolute_meta_, |
| "payload=" + absolute_log_.value() + "\n" + "done=1\n", |
| absolute_meta_time)) |
| return false; |
| if (!CreateFile(absolute_log_, "", now)) |
| return false; |
| |
| // These should be skipped, since the `alreadyuploaded` file exists. |
| uploaded_meta_ = crash_directory.Append("uploaded.meta"); |
| uploaded_log_ = crash_directory.Append("uploaded.log"); |
| uploaded_already_ = crash_directory.Append("uploaded.alreadyuploaded"); |
| if (!CreateFile(uploaded_meta_, "payload=uploaded.log\ndone=1\n", |
| good_meta_time)) |
| return false; |
| if (!CreateFile(uploaded_log_, "", now)) |
| return false; |
| if (!CreateFile(uploaded_already_, "", now)) |
| return false; |
| |
| // This should be ignored as corrupt. Payload can't be /. |
| root_payload_meta_ = crash_directory.Append("root_payload.meta"); |
| if (!test_util::CreateFile(root_payload_meta_, |
| "payload=/\n" |
| "done=1\n")) |
| return false; |
| |
| // These should be ignored, if uploading of device coredumps is not allowed. |
| devcore_meta_ = crash_directory.Append("devcore.meta"); |
| devcore_devcore_ = crash_directory.Append("devcore.devcore"); |
| if (!CreateFile(devcore_meta_, |
| "payload=devcore.devcore\n" |
| "done=1\n", |
| devcore_meta_time)) |
| return false; |
| if (!CreateFile(devcore_devcore_, "", now)) |
| return false; |
| |
| // This should be removed, since metadata is corrupted. |
| corrupted_meta_ = crash_directory.Append("corrupted.meta"); |
| if (!CreateFile(corrupted_meta_, "!@#$%^&*\ndone=1\n", now)) |
| return false; |
| |
| // This should be removed, since no payload info is recorded. |
| empty_meta_ = crash_directory.Append("empty.meta"); |
| if (!CreateFile(empty_meta_, "done=1\n", now)) |
| return false; |
| |
| // This should be removed, since the payload file does not exist. |
| nonexistent_meta_ = crash_directory.Append("nonexistent.meta"); |
| if (!CreateFile(nonexistent_meta_, |
| "payload=nonexistent.log\n" |
| "done=1\n", |
| now)) |
| return false; |
| |
| // These should be removed, since the payload is an unknown kind. |
| unknown_meta_ = crash_directory.Append("unknown.meta"); |
| unknown_xxx_ = crash_directory.Append("unknown.xxx"); |
| if (!CreateFile(unknown_meta_, |
| "payload=unknown.xxx\n" |
| "done=1\n", |
| now)) |
| return false; |
| if (!CreateFile(unknown_xxx_, "", now)) |
| return false; |
| |
| // This should be removed, since the meta file is old. |
| old_incomplete_meta_ = crash_directory.Append("old_incomplete.meta"); |
| if (!CreateFile(old_incomplete_meta_, "payload=good.log\n", now)) |
| return false; |
| if (!TouchFileHelper(old_incomplete_meta_, now - hour * 24)) |
| return false; |
| |
| // This should be removed, since the meta file is new. |
| new_incomplete_meta_ = crash_directory.Append("new_incomplete.meta"); |
| if (!CreateFile(new_incomplete_meta_, "payload=good.log\n", now)) |
| return false; |
| |
| // This should be kept since the OS timestamp is recent. |
| recent_os_meta_ = crash_directory.Append("recent_os.meta"); |
| if (!CreateFile(recent_os_meta_, |
| base::StringPrintf( |
| "payload=recent_os.log\n" |
| "os_millis=%" PRId64 "\n" |
| "done=1\n", |
| (now - base::Time::UnixEpoch()).InMilliseconds()), |
| recent_os_meta_time)) { |
| return false; |
| } |
| recent_os_log_ = crash_directory.Append("recent_os.log"); |
| if (!CreateFile(recent_os_log_, "", now)) |
| return false; |
| |
| // This should be removed since the OS timestamp is old. |
| old_os_meta_ = crash_directory.Append("old_os.meta"); |
| if (!CreateFile(old_os_meta_, |
| base::StringPrintf("payload=good.log\n" |
| "os_millis=%" PRId64 "\n" |
| "done=1\n", |
| ((now - base::Time::UnixEpoch()) - |
| base::TimeDelta::FromDays(200)) |
| .InMilliseconds()), |
| now)) { |
| return false; |
| } |
| |
| // Create large metadata with the size of 1MiB + 1byte. |
| large_meta_ = crash_directory.Append("large.meta"); |
| if (!CreateFile(large_meta_, std::string(1024 * 1024 + 1, 'x'), now)) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| // Sets the runtime conditions that affect behaviors of ChooseAction(). |
| // Returns true on success. |
| bool SetConditions(BuildType build_type, |
| SessionType session_type, |
| MetricsFlag metrics_flag) { |
| return SetConditions(build_type, session_type, metrics_flag, |
| metrics_lib_.get()); |
| } |
| |
| // Version of SetConditions useful for tests that need to create a Sender. |
| // Sender owns the MetricsLibraryInterface pointer, so metrics_lib_ is |
| // usually nullptr in these tests. |
| static bool SetConditions(BuildType build_type, |
| SessionType session_type, |
| MetricsFlag metrics_flag, |
| MetricsLibraryMock* metrics_lib) { |
| if (!CreateLsbReleaseFile(build_type)) |
| return false; |
| |
| metrics_lib->set_guest_mode(session_type == kGuestMode); |
| metrics_lib->set_metrics_enabled(metrics_flag == kMetricsEnabled); |
| |
| return true; |
| } |
| |
| // Directory that the test executable lives in. We reset CommandLine during |
| // TearDown, so we must grab this information early. |
| static base::FilePath* build_directory_; |
| base::AtExitManager at_exit_manager_; |
| |
| std::unique_ptr<MetricsLibraryMock> metrics_lib_; |
| base::ScopedTempDir temp_dir_; |
| base::FilePath test_dir_; |
| |
| base::FilePath good_meta_; |
| base::FilePath good_log_; |
| base::FilePath absolute_meta_; |
| base::FilePath absolute_log_; |
| base::FilePath uploaded_meta_; |
| base::FilePath uploaded_log_; |
| base::FilePath uploaded_already_; |
| base::FilePath root_payload_meta_; |
| base::FilePath devcore_meta_; |
| base::FilePath devcore_devcore_; |
| base::FilePath empty_meta_; |
| base::FilePath corrupted_meta_; |
| base::FilePath nonexistent_meta_; |
| base::FilePath unknown_meta_; |
| base::FilePath unknown_xxx_; |
| base::FilePath old_incomplete_meta_; |
| base::FilePath new_incomplete_meta_; |
| base::FilePath recent_os_meta_; |
| base::FilePath recent_os_log_; |
| base::FilePath old_os_meta_; |
| base::FilePath large_meta_; |
| }; |
| |
| base::FilePath* CrashSenderUtilTest::build_directory_ = nullptr; |
| using CrashSenderUtilDeathTest = CrashSenderUtilTest; |
| |
| } // namespace |
| |
| TEST_F(CrashSenderUtilTest, ParseCommandLine_NoFlags) { |
| const char* argv[] = {"crash_sender"}; |
| base::CommandLine command_line(base::size(argv), argv); |
| brillo::FlagHelper::GetInstance()->set_command_line_for_testing( |
| &command_line); |
| CommandLineFlags flags; |
| ParseCommandLine(base::size(argv), argv, &flags); |
| EXPECT_EQ(flags.max_spread_time.InSeconds(), kMaxSpreadTimeInSeconds); |
| EXPECT_TRUE(flags.crash_directory.empty()); |
| EXPECT_FALSE(flags.ignore_rate_limits); |
| EXPECT_FALSE(flags.ignore_hold_off_time); |
| EXPECT_FALSE(flags.allow_dev_sending); |
| EXPECT_FALSE(flags.ignore_pause_file); |
| EXPECT_FALSE(flags.upload_old_reports); |
| } |
| |
| TEST_F(CrashSenderUtilDeathTest, ParseCommandLine_InvalidMaxSpreadTime) { |
| const char* argv[] = {"crash_sender", "--max_spread_time=-1"}; |
| base::CommandLine command_line(base::size(argv), argv); |
| brillo::FlagHelper::GetInstance()->set_command_line_for_testing( |
| &command_line); |
| CommandLineFlags flags; |
| EXPECT_DEATH(ParseCommandLine(base::size(argv), argv, &flags), |
| "Invalid value for max spread time: -1"); |
| } |
| |
| TEST_F(CrashSenderUtilTest, ParseCommandLine_ValidMaxSpreadTime) { |
| const char* argv[] = {"crash_sender", "--max_spread_time=0"}; |
| base::CommandLine command_line(base::size(argv), argv); |
| brillo::FlagHelper::GetInstance()->set_command_line_for_testing( |
| &command_line); |
| CommandLineFlags flags; |
| ParseCommandLine(base::size(argv), argv, &flags); |
| EXPECT_EQ(base::TimeDelta::FromSeconds(0), flags.max_spread_time); |
| EXPECT_TRUE(flags.crash_directory.empty()); |
| EXPECT_FALSE(flags.ignore_rate_limits); |
| EXPECT_FALSE(flags.ignore_hold_off_time); |
| EXPECT_FALSE(flags.allow_dev_sending); |
| EXPECT_FALSE(flags.ignore_pause_file); |
| EXPECT_FALSE(flags.upload_old_reports); |
| } |
| |
| TEST_F(CrashSenderUtilTest, ParseCommandLine_IgnoreRateLimits) { |
| const char* argv[] = {"crash_sender", "--ignore_rate_limits"}; |
| base::CommandLine command_line(base::size(argv), argv); |
| brillo::FlagHelper::GetInstance()->set_command_line_for_testing( |
| &command_line); |
| CommandLineFlags flags; |
| ParseCommandLine(base::size(argv), argv, &flags); |
| EXPECT_EQ(flags.max_spread_time.InSeconds(), kMaxSpreadTimeInSeconds); |
| EXPECT_TRUE(flags.crash_directory.empty()); |
| EXPECT_TRUE(flags.ignore_rate_limits); |
| EXPECT_FALSE(flags.ignore_hold_off_time); |
| EXPECT_FALSE(flags.allow_dev_sending); |
| EXPECT_FALSE(flags.ignore_pause_file); |
| EXPECT_FALSE(flags.upload_old_reports); |
| } |
| |
| TEST_F(CrashSenderUtilTest, ParseCommandLine_IgnoreHoldOffTime) { |
| const char* argv[] = {"crash_sender", "--ignore_hold_off_time"}; |
| base::CommandLine command_line(base::size(argv), argv); |
| brillo::FlagHelper::GetInstance()->set_command_line_for_testing( |
| &command_line); |
| CommandLineFlags flags; |
| ParseCommandLine(base::size(argv), argv, &flags); |
| EXPECT_EQ(flags.max_spread_time.InSeconds(), kMaxSpreadTimeInSeconds); |
| EXPECT_TRUE(flags.crash_directory.empty()); |
| EXPECT_FALSE(flags.ignore_rate_limits); |
| EXPECT_TRUE(flags.ignore_hold_off_time); |
| EXPECT_FALSE(flags.allow_dev_sending); |
| EXPECT_FALSE(flags.ignore_pause_file); |
| EXPECT_FALSE(flags.upload_old_reports); |
| } |
| |
| TEST_F(CrashSenderUtilTest, ParseCommandLine_CrashDirectory) { |
| const char* argv[] = {"crash_sender", "--crash_directory=/tmp"}; |
| base::CommandLine command_line(base::size(argv), argv); |
| brillo::FlagHelper::GetInstance()->set_command_line_for_testing( |
| &command_line); |
| CommandLineFlags flags; |
| ParseCommandLine(base::size(argv), argv, &flags); |
| EXPECT_EQ(flags.max_spread_time.InSeconds(), kMaxSpreadTimeInSeconds); |
| EXPECT_EQ(flags.crash_directory, "/tmp"); |
| EXPECT_FALSE(flags.ignore_rate_limits); |
| EXPECT_FALSE(flags.ignore_hold_off_time); |
| EXPECT_FALSE(flags.allow_dev_sending); |
| EXPECT_FALSE(flags.ignore_pause_file); |
| EXPECT_FALSE(flags.upload_old_reports); |
| } |
| |
| TEST_F(CrashSenderUtilTest, ParseCommandLine_Dev) { |
| const char* argv[] = {"crash_sender", "--dev"}; |
| base::CommandLine command_line(base::size(argv), argv); |
| brillo::FlagHelper::GetInstance()->set_command_line_for_testing( |
| &command_line); |
| CommandLineFlags flags; |
| ParseCommandLine(base::size(argv), argv, &flags); |
| EXPECT_EQ(flags.max_spread_time.InSeconds(), kMaxSpreadTimeInSeconds); |
| EXPECT_TRUE(flags.crash_directory.empty()); |
| EXPECT_FALSE(flags.ignore_rate_limits); |
| EXPECT_FALSE(flags.ignore_hold_off_time); |
| EXPECT_TRUE(flags.allow_dev_sending); |
| EXPECT_FALSE(flags.ignore_pause_file); |
| EXPECT_FALSE(flags.upload_old_reports); |
| } |
| |
| TEST_F(CrashSenderUtilTest, ParseCommandLine_IgnorePauseFile) { |
| const char* argv[] = {"crash_sender", "--ignore_pause_file"}; |
| base::CommandLine command_line(base::size(argv), argv); |
| brillo::FlagHelper::GetInstance()->set_command_line_for_testing( |
| &command_line); |
| CommandLineFlags flags; |
| ParseCommandLine(base::size(argv), argv, &flags); |
| EXPECT_EQ(flags.max_spread_time.InSeconds(), kMaxSpreadTimeInSeconds); |
| EXPECT_TRUE(flags.crash_directory.empty()); |
| EXPECT_FALSE(flags.ignore_rate_limits); |
| EXPECT_FALSE(flags.ignore_hold_off_time); |
| EXPECT_FALSE(flags.allow_dev_sending); |
| EXPECT_TRUE(flags.ignore_pause_file); |
| EXPECT_FALSE(flags.upload_old_reports); |
| } |
| |
| TEST_F(CrashSenderUtilTest, ParseCommandLine_UploadOldReports) { |
| const char* argv[] = {"crash_sender", "--upload_old_reports"}; |
| base::CommandLine command_line(base::size(argv), argv); |
| brillo::FlagHelper::GetInstance()->set_command_line_for_testing( |
| &command_line); |
| CommandLineFlags flags; |
| ParseCommandLine(base::size(argv), argv, &flags); |
| EXPECT_EQ(flags.max_spread_time.InSeconds(), kMaxSpreadTimeInSeconds); |
| EXPECT_TRUE(flags.crash_directory.empty()); |
| EXPECT_FALSE(flags.ignore_rate_limits); |
| EXPECT_FALSE(flags.ignore_hold_off_time); |
| EXPECT_FALSE(flags.allow_dev_sending); |
| EXPECT_FALSE(flags.ignore_pause_file); |
| EXPECT_TRUE(flags.upload_old_reports); |
| } |
| |
| TEST_F(CrashSenderUtilTest, IsMock) { |
| EXPECT_FALSE(IsMock()); |
| ASSERT_TRUE(SetMockCrashSending(false)); |
| EXPECT_TRUE(IsMock()); |
| EXPECT_FALSE(IsMockSuccessful()); |
| ASSERT_TRUE(SetMockCrashSending(true)); |
| EXPECT_TRUE(IsMock()); |
| EXPECT_TRUE(IsMockSuccessful()); |
| } |
| |
| TEST_F(CrashSenderUtilTest, DoesPauseFileExist) { |
| EXPECT_FALSE(DoesPauseFileExist()); |
| |
| ASSERT_TRUE(test_util::CreateFile(paths::Get(paths::kPauseCrashSending), "")); |
| EXPECT_TRUE(DoesPauseFileExist()); |
| } |
| |
| TEST_F(CrashSenderUtilTest, GetImageType) { |
| EXPECT_EQ("", GetImageType()); |
| ASSERT_TRUE(SetMockCrashSending(false)); |
| EXPECT_EQ("mock-fail", GetImageType()); |
| ASSERT_TRUE(test_util::CreateFile(paths::Get(paths::kLeaveCoreFile), "")); |
| EXPECT_EQ("dev", GetImageType()); |
| ASSERT_TRUE(test_util::CreateFile( |
| paths::GetAt(paths::kEtcDirectory, paths::kLsbRelease), |
| "CHROMEOS_RELEASE_TRACK=testimage-channel")); |
| EXPECT_EQ("test", GetImageType()); |
| } |
| |
| TEST_F(CrashSenderUtilTest, GetBasePartOfCrashFile) { |
| EXPECT_EQ("1", GetBasePartHelper("1")); |
| EXPECT_EQ("1.2", GetBasePartHelper("1.2")); |
| EXPECT_EQ("1.2.3", GetBasePartHelper("1.2.3")); |
| EXPECT_EQ("1.2.3.4", GetBasePartHelper("1.2.3.4")); |
| EXPECT_EQ("1.2.3.4", GetBasePartHelper("1.2.3.4.log")); |
| EXPECT_EQ("1.2.3.4", GetBasePartHelper("1.2.3.4.log")); |
| EXPECT_EQ("1.2.3.4", GetBasePartHelper("1.2.3.4.log.tar")); |
| EXPECT_EQ("1.2.3.4", GetBasePartHelper("1.2.3.4.log.tar.gz")); |
| // Directory should be preserved. |
| EXPECT_EQ("/d/1.2", GetBasePartHelper("/d/1.2")); |
| EXPECT_EQ("/d/1.2.3.4", GetBasePartHelper("/d/1.2.3.4.log")); |
| // Dots in directory name should not affect the function. |
| EXPECT_EQ("/d.d.d.d/1.2.3.4", GetBasePartHelper("/d.d.d.d/1.2.3.4.log")); |
| } |
| |
| TEST_F(CrashSenderUtilTest, RemoveOrphanedCrashFiles) { |
| const base::FilePath crash_directory = |
| paths::Get(paths::kSystemCrashDirectory); |
| ASSERT_TRUE(base::CreateDirectory(crash_directory)); |
| |
| const base::FilePath new_log = crash_directory.Append("0.0.0.0.log"); |
| const base::FilePath old1_log = crash_directory.Append("1.1.1.1.log"); |
| const base::FilePath old1_meta = crash_directory.Append("1.1.1.1.meta"); |
| const base::FilePath old2_log = crash_directory.Append("2.2.2.2.log"); |
| const base::FilePath old3_log = crash_directory.Append("3.3.3.3.log"); |
| const base::FilePath old4_log = crash_directory.Append("4.log"); |
| |
| base::Time now = base::Time::Now(); |
| |
| // new_log is new thus should not be removed. |
| ASSERT_TRUE(test_util::CreateFile(new_log, "")); |
| |
| // old1_log is old but comes with the meta file thus should not be removed. |
| ASSERT_TRUE(test_util::CreateFile(old1_log, "")); |
| ASSERT_TRUE(test_util::CreateFile(old1_meta, "")); |
| ASSERT_TRUE(TouchFileHelper(old1_log, now - base::TimeDelta::FromHours(24))); |
| ASSERT_TRUE(TouchFileHelper(old1_meta, now - base::TimeDelta::FromHours(24))); |
| |
| // old2_log is old without the meta file thus should be removed. |
| ASSERT_TRUE(test_util::CreateFile(old2_log, "")); |
| ASSERT_TRUE(TouchFileHelper(old2_log, now - base::TimeDelta::FromHours(24))); |
| |
| // old3_log is very old without the meta file thus should be removed. |
| ASSERT_TRUE(test_util::CreateFile(old3_log, "")); |
| ASSERT_TRUE(TouchFileHelper(old3_log, now - base::TimeDelta::FromDays(365))); |
| |
| // old4_log is misnamed, but should be removed since it's old. |
| ASSERT_TRUE(test_util::CreateFile(old4_log, "")); |
| ASSERT_TRUE(TouchFileHelper(old4_log, now - base::TimeDelta::FromHours(24))); |
| |
| RemoveOrphanedCrashFiles(crash_directory); |
| |
| // Check what files were removed. |
| EXPECT_TRUE(base::PathExists(new_log)); |
| EXPECT_TRUE(base::PathExists(old1_log)); |
| EXPECT_TRUE(base::PathExists(old1_meta)); |
| EXPECT_FALSE(base::PathExists(old2_log)); |
| EXPECT_FALSE(base::PathExists(old3_log)); |
| EXPECT_FALSE(base::PathExists(old4_log)); |
| } |
| |
| TEST_F(CrashSenderUtilTest, ChooseAction) { |
| ASSERT_TRUE(SetConditions(kOfficialBuild, kSignInMode, kMetricsEnabled)); |
| |
| const base::FilePath crash_directory = |
| paths::Get(paths::kSystemCrashDirectory); |
| ASSERT_TRUE(CreateDirectory(crash_directory)); |
| ASSERT_TRUE(CreateTestCrashFiles(crash_directory)); |
| MetricsLibraryMock* raw_metrics_lib = metrics_lib_.get(); |
| |
| Sender::Options options; |
| Sender sender(std::move(metrics_lib_), |
| std::make_unique<test_util::AdvancingClock>(), options); |
| ASSERT_TRUE(sender.Init()); |
| |
| std::string reason; |
| CrashInfo info; |
| // The following files should be sent. |
| EXPECT_EQ(Sender::kSend, sender.ChooseAction(good_meta_, &reason, &info)); |
| EXPECT_EQ(Sender::kSend, |
| sender.ChooseAction(recent_os_meta_, &reason, &info)); |
| EXPECT_EQ(Sender::kSend, sender.ChooseAction(absolute_meta_, &reason, &info)); |
| // Sanity check that the valid crash info is returned. |
| std::string value; |
| EXPECT_EQ(absolute_log_.value(), info.payload_file.value()); |
| EXPECT_EQ("log", info.payload_kind); |
| EXPECT_TRUE(info.metadata.GetString("payload", &value)); |
| |
| const base::FilePath processing = good_meta_.ReplaceExtension(".processing"); |
| // ChooseAction was successful, so it should remove the file. |
| EXPECT_FALSE(base::PathExists(processing)); |
| |
| // If a ".processing" file exists, the meta file shouldn't be uploaded. |
| ASSERT_TRUE(test_util::CreateFile(processing, "")); |
| EXPECT_EQ(Sender::kRemove, sender.ChooseAction(good_meta_, &reason, &info)); |
| EXPECT_THAT(reason, HasSubstr(".processing file already exists")); |
| ASSERT_TRUE(base::DeleteFile(processing, /*recursive=*/false)); |
| |
| // The following file should be ignored. |
| EXPECT_EQ(Sender::kIgnore, |
| sender.ChooseAction(uploaded_meta_, &reason, &info)); |
| EXPECT_THAT(reason, HasSubstr("Not uploading already-uploaded crash")); |
| EXPECT_FALSE( |
| base::PathExists(uploaded_meta_.ReplaceExtension(".processing"))); |
| |
| // The following files should be ignored. |
| EXPECT_EQ(Sender::kIgnore, |
| sender.ChooseAction(new_incomplete_meta_, &reason, &info)); |
| EXPECT_THAT(reason, HasSubstr("Recent incomplete metadata")); |
| EXPECT_FALSE( |
| base::PathExists(new_incomplete_meta_.ReplaceExtension(".processing"))); |
| |
| // Device coredump should be ignored by default. |
| EXPECT_EQ(Sender::kIgnore, |
| sender.ChooseAction(devcore_meta_, &reason, &info)); |
| EXPECT_THAT(reason, HasSubstr("Device coredump upload not allowed")); |
| EXPECT_FALSE(base::PathExists(devcore_meta_.ReplaceExtension(".processing"))); |
| |
| // Device coredump should be sent, if uploading is allowed. |
| CreateDeviceCoredumpUploadAllowedFile(); |
| EXPECT_EQ(Sender::kSend, sender.ChooseAction(devcore_meta_, &reason, &info)); |
| |
| // The following files should be removed. |
| EXPECT_EQ(Sender::kRemove, sender.ChooseAction(empty_meta_, &reason, &info)); |
| EXPECT_THAT(reason, HasSubstr("Payload is not found")); |
| EXPECT_FALSE(base::PathExists(empty_meta_.ReplaceExtension(".processing"))); |
| |
| EXPECT_EQ(Sender::kRemove, |
| sender.ChooseAction(corrupted_meta_, &reason, &info)); |
| EXPECT_THAT(reason, HasSubstr("Corrupted metadata")); |
| EXPECT_FALSE( |
| base::PathExists(corrupted_meta_.ReplaceExtension(".processing"))); |
| |
| EXPECT_EQ(Sender::kRemove, |
| sender.ChooseAction(nonexistent_meta_, &reason, &info)); |
| EXPECT_THAT(reason, HasSubstr("Missing payload")); |
| EXPECT_FALSE( |
| base::PathExists(nonexistent_meta_.ReplaceExtension(".processing"))); |
| |
| EXPECT_EQ(Sender::kRemove, |
| sender.ChooseAction(unknown_meta_, &reason, &info)); |
| EXPECT_THAT(reason, HasSubstr("Unknown kind")); |
| EXPECT_FALSE(base::PathExists(unknown_meta_.ReplaceExtension(".processing"))); |
| |
| EXPECT_EQ(Sender::kRemove, |
| sender.ChooseAction(old_incomplete_meta_, &reason, &info)); |
| EXPECT_THAT(reason, HasSubstr("Removing old incomplete metadata")); |
| EXPECT_FALSE( |
| base::PathExists(old_incomplete_meta_.ReplaceExtension(".processing"))); |
| |
| EXPECT_EQ(Sender::kRemove, sender.ChooseAction(old_os_meta_, &reason, &info)); |
| EXPECT_THAT(reason, HasSubstr("Old OS version")); |
| EXPECT_FALSE(base::PathExists(old_os_meta_.ReplaceExtension(".processing"))); |
| |
| EXPECT_EQ(Sender::kRemove, |
| sender.ChooseAction(root_payload_meta_, &reason, &info)); |
| EXPECT_THAT(reason, HasSubstr("payload path is absolute")); |
| EXPECT_FALSE( |
| base::PathExists(root_payload_meta_.ReplaceExtension(".processing"))); |
| |
| EXPECT_EQ(Sender::kRemove, sender.ChooseAction(large_meta_, &reason, &info)); |
| EXPECT_THAT(reason, HasSubstr("Metadata file is unusually large")); |
| EXPECT_FALSE(base::PathExists(large_meta_.ReplaceExtension(".processing"))); |
| |
| ASSERT_TRUE(SetConditions(kUnofficialBuild, kSignInMode, kMetricsEnabled, |
| raw_metrics_lib)); |
| EXPECT_EQ(Sender::kRemove, sender.ChooseAction(good_meta_, &reason, &info)); |
| EXPECT_THAT(reason, HasSubstr("Not an official OS version")); |
| EXPECT_FALSE(base::PathExists(good_meta_.ReplaceExtension(".processing"))); |
| |
| ASSERT_TRUE(SetConditions(kOfficialBuild, kSignInMode, kMetricsDisabled, |
| raw_metrics_lib)); |
| EXPECT_EQ(Sender::kRemove, sender.ChooseAction(good_meta_, &reason, &info)); |
| EXPECT_THAT(reason, HasSubstr("Crash reporting is disabled")); |
| EXPECT_FALSE(base::PathExists(good_meta_.ReplaceExtension(".processing"))); |
| |
| // Valid crash files should be kept in the guest mode. |
| ASSERT_TRUE(SetConditions(kOfficialBuild, kGuestMode, kMetricsDisabled, |
| raw_metrics_lib)); |
| EXPECT_EQ(Sender::kIgnore, sender.ChooseAction(good_meta_, &reason, &info)); |
| } |
| |
| TEST_F(CrashSenderUtilDeathTest, ChooseActionCrash) { |
| ASSERT_TRUE(SetConditions(kOfficialBuild, kSignInMode, kMetricsEnabled)); |
| |
| const base::FilePath crash_directory = |
| paths::Get(paths::kSystemCrashDirectory); |
| ASSERT_TRUE(CreateDirectory(crash_directory)); |
| ASSERT_TRUE(CreateTestCrashFiles(crash_directory)); |
| |
| Sender::Options options; |
| Sender sender(std::move(metrics_lib_), |
| std::make_unique<test_util::AdvancingClock>(), options); |
| ASSERT_TRUE(sender.Init()); |
| |
| ASSERT_TRUE(SetMockCrashSending(true)); |
| sender.SetCrashDuringSendForTesting(true); |
| |
| std::string reason; |
| CrashInfo info; |
| EXPECT_DEATH(sender.ChooseAction(good_meta_, &reason, &info), |
| "crashing as requested"); |
| |
| // ChooseAction crashed so the file should remain. |
| EXPECT_TRUE(base::PathExists(good_meta_.ReplaceExtension(".processing"))); |
| } |
| |
| TEST_F(CrashSenderUtilTest, ChooseActionDevMode) { |
| // If we set allow_dev_sending, then the OS check will be skipped. |
| ASSERT_TRUE(SetConditions(kUnofficialBuild, kSignInMode, kMetricsEnabled)); |
| |
| const base::FilePath crash_directory = |
| paths::Get(paths::kSystemCrashDirectory); |
| ASSERT_TRUE(CreateDirectory(crash_directory)); |
| ASSERT_TRUE(CreateTestCrashFiles(crash_directory)); |
| |
| Sender::Options options; |
| options.allow_dev_sending = true; |
| Sender sender(std::move(metrics_lib_), |
| std::make_unique<test_util::AdvancingClock>(), options); |
| ASSERT_TRUE(sender.Init()); |
| |
| std::string reason; |
| CrashInfo info; |
| |
| EXPECT_EQ(Sender::kSend, sender.ChooseAction(good_meta_, &reason, &info)); |
| EXPECT_EQ(Sender::kSend, sender.ChooseAction(old_os_meta_, &reason, &info)); |
| } |
| |
| TEST_F(CrashSenderUtilTest, ChooseActionUploadOldReports) { |
| ASSERT_TRUE(SetConditions(kOfficialBuild, kSignInMode, kMetricsEnabled)); |
| |
| const base::FilePath crash_directory = |
| paths::Get(paths::kSystemCrashDirectory); |
| ASSERT_TRUE(CreateDirectory(crash_directory)); |
| ASSERT_TRUE(CreateTestCrashFiles(crash_directory)); |
| |
| Sender::Options options; |
| options.upload_old_reports = true; |
| Sender sender(std::move(metrics_lib_), |
| std::make_unique<test_util::AdvancingClock>(), options); |
| ASSERT_TRUE(sender.Init()); |
| |
| std::string reason; |
| CrashInfo info; |
| |
| EXPECT_EQ(Sender::kSend, sender.ChooseAction(old_os_meta_, &reason, &info)); |
| } |
| |
| TEST_F(CrashSenderUtilTest, RemoveAndPickCrashFiles) { |
| auto mock = |
| std::make_unique<org::chromium::SessionManagerInterfaceProxyMock>(); |
| test_util::SetActiveSessions(mock.get(), |
| {{"user1", "hash1"}, {"user2", "hash2"}}); |
| MetricsLibraryMock* raw_metrics_lib = metrics_lib_.get(); |
| |
| Sender::Options options; |
| options.session_manager_proxy = mock.release(); |
| Sender sender(std::move(metrics_lib_), |
| std::make_unique<test_util::AdvancingClock>(), options); |
| ASSERT_TRUE(sender.Init()); |
| |
| ASSERT_TRUE(SetConditions(kOfficialBuild, kSignInMode, kMetricsEnabled, |
| raw_metrics_lib)); |
| |
| const base::FilePath crash_directory = |
| paths::Get(paths::kSystemCrashDirectory); |
| ASSERT_TRUE(CreateDirectory(crash_directory)); |
| ASSERT_TRUE(CreateTestCrashFiles(crash_directory)); |
| |
| std::vector<MetaFile> to_send; |
| sender.RemoveAndPickCrashFiles(crash_directory, &to_send); |
| |
| // Check what files were removed. |
| EXPECT_TRUE(base::PathExists(good_meta_)); |
| EXPECT_TRUE(base::PathExists(good_log_)); |
| EXPECT_TRUE(base::PathExists(absolute_meta_)); |
| EXPECT_TRUE(base::PathExists(absolute_log_)); |
| EXPECT_TRUE(base::PathExists(uploaded_meta_)); |
| EXPECT_TRUE(base::PathExists(uploaded_log_)); |
| EXPECT_TRUE(base::PathExists(new_incomplete_meta_)); |
| EXPECT_TRUE(base::PathExists(recent_os_meta_)); |
| EXPECT_TRUE(base::PathExists(recent_os_log_)); |
| EXPECT_FALSE(base::PathExists(empty_meta_)); |
| EXPECT_FALSE(base::PathExists(corrupted_meta_)); |
| EXPECT_FALSE(base::PathExists(nonexistent_meta_)); |
| EXPECT_FALSE(base::PathExists(unknown_meta_)); |
| EXPECT_FALSE(base::PathExists(unknown_xxx_)); |
| EXPECT_FALSE(base::PathExists(old_incomplete_meta_)); |
| EXPECT_FALSE(base::PathExists(old_os_meta_)); |
| EXPECT_FALSE(base::PathExists(root_payload_meta_)); |
| // Check what files were picked for sending. |
| EXPECT_EQ(3, to_send.size()); |
| EXPECT_EQ(good_meta_.value(), to_send[0].first.value()); |
| EXPECT_EQ(absolute_meta_.value(), to_send[1].first.value()); |
| EXPECT_EQ(recent_os_meta_.value(), to_send[2].first.value()); |
| |
| // Sanity check that the valid crash info is returned. |
| std::string value; |
| EXPECT_EQ(good_log_.value(), to_send[0].second.payload_file.value()); |
| EXPECT_EQ("log", to_send[0].second.payload_kind); |
| EXPECT_TRUE(to_send[0].second.metadata.GetString("payload", &value)); |
| |
| // All crash files should be removed for an unofficial build. |
| ASSERT_TRUE(CreateTestCrashFiles(crash_directory)); |
| ASSERT_TRUE(SetConditions(kUnofficialBuild, kSignInMode, kMetricsEnabled, |
| raw_metrics_lib)); |
| to_send.clear(); |
| sender.RemoveAndPickCrashFiles(crash_directory, &to_send); |
| EXPECT_TRUE(base::IsDirectoryEmpty(crash_directory)); |
| EXPECT_TRUE(to_send.empty()); |
| |
| // All crash files should be removed if metrics are disabled. |
| ASSERT_TRUE(CreateTestCrashFiles(crash_directory)); |
| ASSERT_TRUE(SetConditions(kOfficialBuild, kSignInMode, kMetricsDisabled, |
| raw_metrics_lib)); |
| to_send.clear(); |
| sender.RemoveAndPickCrashFiles(crash_directory, &to_send); |
| EXPECT_TRUE(base::IsDirectoryEmpty(crash_directory)); |
| EXPECT_TRUE(to_send.empty()); |
| |
| // Valid crash files should be kept in the guest mode, thus the directory |
| // won't be empty. None should be selected for sending. |
| ASSERT_TRUE(CreateTestCrashFiles(crash_directory)); |
| ASSERT_TRUE(SetConditions(kOfficialBuild, kGuestMode, kMetricsDisabled, |
| raw_metrics_lib)); |
| to_send.clear(); |
| sender.RemoveAndPickCrashFiles(crash_directory, &to_send); |
| EXPECT_FALSE(base::IsDirectoryEmpty(crash_directory)); |
| EXPECT_TRUE(to_send.empty()); |
| |
| // devcore_meta_ should be included in to_send, if uploading of device |
| // coredumps is allowed. |
| ASSERT_TRUE(CreateTestCrashFiles(crash_directory)); |
| ASSERT_TRUE(SetConditions(kOfficialBuild, kSignInMode, kMetricsEnabled, |
| raw_metrics_lib)); |
| CreateDeviceCoredumpUploadAllowedFile(); |
| to_send.clear(); |
| sender.RemoveAndPickCrashFiles(crash_directory, &to_send); |
| EXPECT_EQ(4, to_send.size()); |
| EXPECT_EQ(devcore_meta_.value(), to_send[3].first.value()); |
| } |
| |
| TEST_F(CrashSenderUtilTest, RemoveReportFiles) { |
| const base::FilePath crash_directory = |
| paths::Get(paths::kSystemCrashDirectory); |
| ASSERT_TRUE(base::CreateDirectory(crash_directory)); |
| |
| const base::FilePath foo_meta = crash_directory.Append("foo.meta"); |
| const base::FilePath foo_log = crash_directory.Append("foo.log"); |
| const base::FilePath foo_uploaded = |
| crash_directory.Append("foo.alreadyuploaded"); |
| const base::FilePath foo_dmp = crash_directory.Append("foo.dmp"); |
| const base::FilePath bar_log = crash_directory.Append("bar.log"); |
| |
| ASSERT_TRUE(test_util::CreateFile(foo_meta, "")); |
| ASSERT_TRUE(test_util::CreateFile(foo_log, "")); |
| ASSERT_TRUE(test_util::CreateFile(foo_dmp, "")); |
| ASSERT_TRUE(test_util::CreateFile(bar_log, "")); |
| EXPECT_FALSE(base::PathExists(foo_uploaded)); |
| |
| // This should create the alreadyuploaded file. |
| RemoveReportFiles(foo_meta, false); |
| std::string contents; |
| ASSERT_TRUE(base::ReadFileToString(foo_meta, &contents)); |
| EXPECT_TRUE(base::PathExists(foo_uploaded)); |
| |
| ASSERT_TRUE(test_util::CreateFile(foo_uploaded, "asdf")); |
| base::File::Info uploaded_info; |
| ASSERT_TRUE(base::GetFileInfo(foo_uploaded, &uploaded_info)); |
| // This should not update the alreadyuploaded file, since it already exists |
| RemoveReportFiles(foo_meta, false); |
| ASSERT_TRUE(base::ReadFileToString(foo_meta, &contents)); |
| EXPECT_TRUE(base::PathExists(foo_uploaded)); |
| base::File::Info new_uploaded_info; |
| ASSERT_TRUE(base::GetFileInfo(foo_uploaded, &new_uploaded_info)); |
| EXPECT_EQ(new_uploaded_info.last_modified, uploaded_info.last_modified); |
| EXPECT_EQ(new_uploaded_info.size, uploaded_info.size); |
| |
| // This should remove foo.*. |
| RemoveReportFiles(foo_meta, true); |
| // This should do nothing because the suffix is not ".meta". |
| RemoveReportFiles(bar_log, true); |
| |
| // Check what files were removed. |
| EXPECT_FALSE(base::PathExists(foo_meta)); |
| EXPECT_FALSE(base::PathExists(foo_log)); |
| EXPECT_FALSE(base::PathExists(foo_dmp)); |
| EXPECT_TRUE(base::PathExists(bar_log)); |
| } |
| |
| TEST_F(CrashSenderUtilTest, GetMetaFiles) { |
| const base::FilePath crash_directory = |
| paths::Get(paths::kSystemCrashDirectory); |
| ASSERT_TRUE(base::CreateDirectory(crash_directory)); |
| |
| // Use unsorted file names, to check that GetMetaFiles() sort files by |
| // timestamps, not file names. |
| const base::FilePath meta_1 = crash_directory.Append("a.meta"); |
| const base::FilePath meta_2 = crash_directory.Append("s.meta"); |
| const base::FilePath meta_3 = crash_directory.Append("d.meta"); |
| const base::FilePath meta_4 = crash_directory.Append("f.meta"); |
| // This one should not appear in the result. |
| const base::FilePath metal_5 = crash_directory.Append("g.metal"); |
| |
| ASSERT_TRUE(test_util::CreateFile(meta_1, "")); |
| ASSERT_TRUE(test_util::CreateFile(meta_2, "")); |
| ASSERT_TRUE(test_util::CreateFile(meta_3, "")); |
| ASSERT_TRUE(test_util::CreateFile(meta_4, "")); |
| ASSERT_TRUE(test_util::CreateFile(metal_5, "")); |
| |
| // Change timestamps so that meta_1 is the newest and metal_5 is the oldest. |
| base::Time now = base::Time::Now(); |
| ASSERT_TRUE(TouchFileHelper(meta_1, now - base::TimeDelta::FromHours(1))); |
| ASSERT_TRUE(TouchFileHelper(meta_2, now - base::TimeDelta::FromHours(2))); |
| ASSERT_TRUE(TouchFileHelper(meta_3, now - base::TimeDelta::FromHours(3))); |
| ASSERT_TRUE(TouchFileHelper(meta_4, now - base::TimeDelta::FromHours(4))); |
| ASSERT_TRUE(TouchFileHelper(metal_5, now - base::TimeDelta::FromHours(5))); |
| |
| std::vector<base::FilePath> meta_files = GetMetaFiles(crash_directory); |
| ASSERT_EQ(4, meta_files.size()); |
| // Confirm that files are sorted in the old-to-new order. |
| EXPECT_EQ(meta_4.value(), meta_files[0].value()); |
| EXPECT_EQ(meta_3.value(), meta_files[1].value()); |
| EXPECT_EQ(meta_2.value(), meta_files[2].value()); |
| EXPECT_EQ(meta_1.value(), meta_files[3].value()); |
| } |
| |
| TEST_F(CrashSenderUtilTest, GetBaseNameFromMetadata) { |
| brillo::KeyValueStore metadata; |
| metadata.LoadFromString(""); |
| EXPECT_EQ("", GetBaseNameFromMetadata(metadata, "payload").value()); |
| |
| metadata.LoadFromString("payload=test.log\n"); |
| EXPECT_EQ("test.log", GetBaseNameFromMetadata(metadata, "payload").value()); |
| |
| metadata.LoadFromString("payload=/foo/test.log\n"); |
| EXPECT_EQ("test.log", GetBaseNameFromMetadata(metadata, "payload").value()); |
| } |
| |
| TEST_F(CrashSenderUtilTest, GetKindFromPayloadPath) { |
| EXPECT_EQ("", GetKindFromPayloadPath(base::FilePath())); |
| EXPECT_EQ("", GetKindFromPayloadPath(base::FilePath("foo"))); |
| EXPECT_EQ("log", GetKindFromPayloadPath(base::FilePath("foo.log"))); |
| // "dmp" is a special case. |
| EXPECT_EQ("minidump", GetKindFromPayloadPath(base::FilePath("foo.dmp"))); |
| |
| // ".gz" should be ignored. |
| EXPECT_EQ("log", GetKindFromPayloadPath(base::FilePath("foo.log.gz"))); |
| EXPECT_EQ("minidump", GetKindFromPayloadPath(base::FilePath("foo.dmp.gz"))); |
| EXPECT_EQ("", GetKindFromPayloadPath(base::FilePath("foo.gz"))); |
| |
| // The directory name should not afect the function. |
| EXPECT_EQ("minidump", |
| GetKindFromPayloadPath(base::FilePath("/1.2.3/foo.dmp.gz"))); |
| } |
| |
| TEST_F(CrashSenderUtilTest, ParseMetadata) { |
| brillo::KeyValueStore metadata; |
| std::string value; |
| EXPECT_TRUE(ParseMetadata("", &metadata)); |
| EXPECT_TRUE(ParseMetadata("log=test.log\n", &metadata)); |
| EXPECT_TRUE(ParseMetadata("#comment\nlog=test.log\n", &metadata)); |
| |
| EXPECT_TRUE(metadata.GetString("log", &value)); |
| // This will clear the previouly parsed data. |
| EXPECT_TRUE(ParseMetadata("payload=test.dmp\n", &metadata)); |
| EXPECT_FALSE(metadata.GetString("log", &value)); |
| |
| // Underscores, dashes, and periods should allowed, as Chrome uses them. |
| // https://crbug.com/821530. |
| EXPECT_TRUE(ParseMetadata("abcABC012_.-=test.log\n", &metadata)); |
| EXPECT_TRUE(metadata.GetString("abcABC012_.-", &value)); |
| EXPECT_EQ("test.log", value); |
| |
| // Invalid metadata should be detected. |
| EXPECT_FALSE(ParseMetadata("=test.log\n", &metadata)); |
| EXPECT_FALSE(ParseMetadata("***\n", &metadata)); |
| EXPECT_FALSE(ParseMetadata("***=test.log\n", &metadata)); |
| EXPECT_FALSE(ParseMetadata("log\n", &metadata)); |
| } |
| |
| TEST_F(CrashSenderUtilTest, IsCompleteMetadata) { |
| brillo::KeyValueStore metadata; |
| metadata.LoadFromString(""); |
| EXPECT_FALSE(IsCompleteMetadata(metadata)); |
| |
| metadata.LoadFromString("log=test.log\n"); |
| EXPECT_FALSE(IsCompleteMetadata(metadata)); |
| |
| metadata.LoadFromString("log=test.log\ndone=1\n"); |
| EXPECT_TRUE(IsCompleteMetadata(metadata)); |
| |
| metadata.LoadFromString("done=1\n"); |
| EXPECT_TRUE(IsCompleteMetadata(metadata)); |
| } |
| |
| TEST_F(CrashSenderUtilTest, IsTimestampNewEnough) { |
| base::FilePath file; |
| ASSERT_TRUE(base::CreateTemporaryFileInDir(test_dir_, &file)); |
| |
| // Should be new enough as it's just created. |
| ASSERT_TRUE(IsTimestampNewEnough(file)); |
| |
| // Make it older than 24 hours. |
| const base::Time now = base::Time::Now(); |
| ASSERT_TRUE(TouchFileHelper(file, now - base::TimeDelta::FromHours(25))); |
| |
| // Should be no longer new enough. |
| ASSERT_FALSE(IsTimestampNewEnough(file)); |
| } |
| |
| TEST_F(CrashSenderUtilTest, IsBelowRateReachesMaxRate) { |
| const int kMaxRate = 3; |
| const int kMaxBytes = 50; |
| const base::FilePath timestamp_dir = |
| test_dir_.Append("IsBelowRateReachesMaxRate"); |
| |
| EXPECT_TRUE(IsBelowRate(timestamp_dir, kMaxRate, kMaxBytes)); |
| RecordSendAttempt(timestamp_dir, kMaxBytes - 5); |
| EXPECT_TRUE(IsBelowRate(timestamp_dir, kMaxRate, kMaxBytes)); |
| RecordSendAttempt(timestamp_dir, kMaxBytes - 5); |
| // Exceeds max bytes; should be allowed to upload since we have not hit max |
| // rate. |
| EXPECT_TRUE(IsBelowRate(timestamp_dir, kMaxRate, kMaxBytes)); |
| RecordSendAttempt(timestamp_dir, kMaxBytes - 5); |
| |
| // Should not pass the rate + byte limit. |
| EXPECT_FALSE(IsBelowRate(timestamp_dir, kMaxRate, kMaxBytes)); |
| |
| // Three files should be created for tracking timestamps. |
| std::vector<base::FilePath> files = GetFileNamesIn(timestamp_dir); |
| ASSERT_EQ(3, files.size()); |
| |
| const base::Time now = base::Time::Now(); |
| |
| // Make one of them older than 24 hours. |
| ASSERT_TRUE(TouchFileHelper(files[0], now - base::TimeDelta::FromHours(25))); |
| |
| // It should now pass the rate limit. |
| EXPECT_TRUE(IsBelowRate(timestamp_dir, kMaxRate, kMaxBytes)); |
| // The old file should now be gone. |
| EXPECT_TRUE(!base::PathExists(files[0])); |
| } |
| |
| TEST_F(CrashSenderUtilTest, IsBelowRateReachesMaxBytes) { |
| const int kMaxRate = 3; |
| const int kMaxBytes = 100; |
| const base::FilePath timestamp_dir = |
| test_dir_.Append("IsBelowRateReachesMaxBytes"); |
| |
| EXPECT_TRUE(IsBelowRate(timestamp_dir, kMaxRate, kMaxBytes)); |
| RecordSendAttempt(timestamp_dir, 50); |
| EXPECT_TRUE(IsBelowRate(timestamp_dir, kMaxRate, kMaxBytes)); |
| RecordSendAttempt(timestamp_dir, 20); |
| EXPECT_TRUE(IsBelowRate(timestamp_dir, kMaxRate, kMaxBytes)); |
| RecordSendAttempt(timestamp_dir, 5); |
| // Exceeds max rate, but passes because it's below max bytes. |
| EXPECT_TRUE(IsBelowRate(timestamp_dir, kMaxRate, kMaxBytes)); |
| RecordSendAttempt(timestamp_dir, 5); |
| EXPECT_TRUE(IsBelowRate(timestamp_dir, kMaxRate, kMaxBytes)); |
| RecordSendAttempt(timestamp_dir, 20); |
| |
| // Exceeds max bytes. |
| EXPECT_FALSE(IsBelowRate(timestamp_dir, kMaxRate, kMaxBytes)); |
| |
| // Make one file older than 24 hours, and we should get some bandwidth |
| // marked available again. |
| std::vector<base::FilePath> files = GetFileNamesIn(timestamp_dir); |
| ASSERT_EQ(5, files.size()); |
| const base::Time now = base::Time::Now(); |
| ASSERT_TRUE(TouchFileHelper(files[0], now - base::TimeDelta::FromHours(25))); |
| EXPECT_TRUE(IsBelowRate(timestamp_dir, kMaxRate, kMaxBytes)); |
| } |
| |
| TEST_F(CrashSenderUtilTest, GetSleepTime) { |
| const base::FilePath meta_file = test_dir_.Append("test.meta"); |
| base::TimeDelta max_spread_time = base::TimeDelta::FromSeconds(0); |
| |
| // This should fail since meta_file does not exist. |
| base::TimeDelta sleep_time; |
| EXPECT_FALSE( |
| GetSleepTime(meta_file, max_spread_time, kMaxHoldOffTime, &sleep_time)); |
| |
| ASSERT_TRUE(test_util::CreateFile(meta_file, "")); |
| |
| // sleep_time should be close enough to kMaxHoldOffTime since the meta file |
| // was just created, but 10% error is allowed just in case. |
| EXPECT_TRUE( |
| GetSleepTime(meta_file, max_spread_time, kMaxHoldOffTime, &sleep_time)); |
| EXPECT_NEAR(kMaxHoldOffTime.InSecondsF(), sleep_time.InSecondsF(), |
| kMaxHoldOffTime.InSecondsF() * 0.1); |
| |
| // Zero hold-off time and zero sleep time should always give zero sleep time. |
| EXPECT_TRUE(GetSleepTime(meta_file, max_spread_time, |
| base::TimeDelta::FromSeconds(0) /*hold_off_time*/, |
| &sleep_time)); |
| EXPECT_EQ(base::TimeDelta::FromSeconds(0), sleep_time); |
| |
| // Even if file is new, a zero hold-off time means we choose a time between |
| // 0 and max_spread_time. |
| ASSERT_TRUE(TouchFileHelper(meta_file, base::Time::Now())); |
| EXPECT_TRUE(GetSleepTime( |
| meta_file, base::TimeDelta::FromSeconds(60) /*max_spread_time*/, |
| base::TimeDelta::FromSeconds(0) /*hold_off_time*/, &sleep_time)); |
| EXPECT_LE(base::TimeDelta::FromSeconds(0), sleep_time); |
| EXPECT_GE(base::TimeDelta::FromSeconds(60), sleep_time); |
| |
| // Make the meta file old enough so hold-off time is not necessary. |
| const base::Time now = base::Time::Now(); |
| ASSERT_TRUE(TouchFileHelper(meta_file, now - kMaxHoldOffTime)); |
| |
| // sleep_time should always be 0, since max_spread_time is set to 0. |
| EXPECT_TRUE( |
| GetSleepTime(meta_file, max_spread_time, kMaxHoldOffTime, &sleep_time)); |
| EXPECT_EQ(base::TimeDelta::FromSeconds(0), sleep_time); |
| |
| // sleep_time should be in range [0, 10]. |
| max_spread_time = base::TimeDelta::FromSeconds(10); |
| EXPECT_TRUE( |
| GetSleepTime(meta_file, max_spread_time, kMaxHoldOffTime, &sleep_time)); |
| EXPECT_LE(base::TimeDelta::FromSeconds(0), sleep_time); |
| EXPECT_GE(base::TimeDelta::FromSeconds(10), sleep_time); |
| |
| // If the meta file is current, the minimum sleep time should be |
| // kMaxHoldOffTime but the maximum is still max_spread_time. |
| max_spread_time = base::TimeDelta::FromSeconds(60); |
| ASSERT_TRUE(TouchFileHelper(meta_file, base::Time::Now())); |
| EXPECT_TRUE( |
| GetSleepTime(meta_file, max_spread_time, kMaxHoldOffTime, &sleep_time)); |
| // 0.9 in case we got preempted for 3 seconds between the file touch and the |
| // GetSleepTime(). |
| EXPECT_LE(kMaxHoldOffTime * 0.9, sleep_time); |
| EXPECT_GE(base::TimeDelta::FromSeconds(60), sleep_time); |
| } |
| |
| TEST_F(CrashSenderUtilTest, SortReports) { |
| // Crashes from oldest to youngest will be a, b, c. |
| CrashInfo crash_info_a; |
| EXPECT_TRUE(base::Time::FromString("15 Nov 2018 12:45:26 GMT", |
| &crash_info_a.last_modified)); |
| crash_info_a.metadata.SetString("order", "1"); |
| MetaFile file_a(base::FilePath("a"), std::move(crash_info_a)); |
| |
| CrashInfo crash_info_b; |
| EXPECT_TRUE(base::Time::FromString("7 Feb 2019 12:45:26 GMT", |
| &crash_info_b.last_modified)); |
| crash_info_b.metadata.SetString("order", "2"); |
| MetaFile file_b(base::FilePath("b"), std::move(crash_info_b)); |
| |
| CrashInfo crash_info_c; |
| EXPECT_TRUE(base::Time::FromString("7 Feb 2019 12:48:26 GMT", |
| &crash_info_c.last_modified)); |
| crash_info_c.metadata.SetString("order", "3"); |
| MetaFile file_c(base::FilePath("c"), std::move(crash_info_c)); |
| |
| // Add out of order |
| std::vector<MetaFile> crashes; |
| crashes.emplace_back(std::move(file_c)); |
| crashes.emplace_back(std::move(file_b)); |
| crashes.emplace_back(std::move(file_a)); |
| SortReports(&crashes); |
| |
| ASSERT_EQ(crashes.size(), 3); |
| |
| EXPECT_EQ(crashes[0].first, base::FilePath("a")); |
| std::string order_string; |
| EXPECT_TRUE(crashes[0].second.metadata.GetString("order", &order_string)); |
| EXPECT_EQ(order_string, "1"); |
| |
| EXPECT_EQ(crashes[1].first, base::FilePath("b")); |
| EXPECT_TRUE(crashes[1].second.metadata.GetString("order", &order_string)); |
| EXPECT_EQ(order_string, "2"); |
| |
| EXPECT_EQ(crashes[2].first, base::FilePath("c")); |
| EXPECT_TRUE(crashes[2].second.metadata.GetString("order", &order_string)); |
| EXPECT_EQ(order_string, "3"); |
| } |
| |
| TEST_F(CrashSenderUtilTest, GetUserCrashDirectories) { |
| auto mock = |
| std::make_unique<org::chromium::SessionManagerInterfaceProxyMock>(); |
| test_util::SetActiveSessions(mock.get(), |
| {{"user1", "hash1"}, {"user2", "hash2"}}); |
| Sender::Options options; |
| options.session_manager_proxy = mock.release(); |
| Sender sender(std::move(metrics_lib_), |
| std::make_unique<test_util::AdvancingClock>(), options); |
| ASSERT_TRUE(sender.Init()); |
| |
| EXPECT_THAT( |
| sender.GetUserCrashDirectories(), |
| UnorderedElementsAre(paths::Get("/home/user/hash1/crash"), |
| paths::Get("/home/user/hash2/crash"), |
| paths::Get("/run/daemon-store/crash/hash1"), |
| paths::Get("/run/daemon-store/crash/hash2"))); |
| } |
| |
| enum MissingFile { |
| kNone, |
| kPayloadFile, |
| kLogFile, |
| kTextFile, |
| kBinFile, |
| }; |
| |
| class CreateCrashFormDataTest |
| : public CrashSenderUtilTest, |
| public ::testing::WithParamInterface<std::tuple<bool, MissingFile>> { |
| protected: |
| void SetUp() override { |
| std::tie(absolute_paths_, missing_file_) = GetParam(); |
| CrashSenderUtilTest::SetUp(); |
| } |
| bool absolute_paths_; |
| MissingFile missing_file_; |
| }; |
| |
| TEST_P(CreateCrashFormDataTest, TestCreateCrashFormData) { |
| const base::FilePath system_dir = paths::Get(paths::kSystemCrashDirectory); |
| ASSERT_TRUE(base::CreateDirectory(system_dir)); |
| |
| const base::FilePath payload_file_relative("0.0.0.0.payload"); |
| const base::FilePath payload_file_absolute = |
| system_dir.Append(payload_file_relative); |
| const std::string payload_contents = "foobar_payload"; |
| if (missing_file_ != kPayloadFile) { |
| ASSERT_TRUE(test_util::CreateFile(payload_file_absolute, payload_contents)); |
| } |
| const base::FilePath& payload_file = |
| absolute_paths_ ? payload_file_absolute : payload_file_relative; |
| |
| const base::FilePath log_file_relative("0.0.0.0.log"); |
| const base::FilePath log_file_absolute = system_dir.Append(log_file_relative); |
| const std::string log_contents = "foobar_log"; |
| if (missing_file_ != kLogFile) { |
| ASSERT_TRUE(test_util::CreateFile(log_file_absolute, log_contents)); |
| } |
| const base::FilePath& log_file = |
| absolute_paths_ ? log_file_absolute : log_file_relative; |
| |
| const base::FilePath text_var_file_relative("data.txt"); |
| const base::FilePath text_var_file_absolute = |
| system_dir.Append(text_var_file_relative); |
| const std::string text_var_contents = "upload_text_contents"; |
| if (missing_file_ != kTextFile) { |
| ASSERT_TRUE( |
| test_util::CreateFile(text_var_file_absolute, text_var_contents)); |
| } |
| const base::FilePath& text_var_file = |
| absolute_paths_ ? text_var_file_absolute : text_var_file_relative; |
| |
| const base::FilePath file_var_file_relative("data.bin"); |
| const base::FilePath file_var_file_absolute = |
| system_dir.Append(file_var_file_relative); |
| const std::string file_var_contents = "upload_file_contents"; |
| if (missing_file_ != kBinFile) { |
| ASSERT_TRUE( |
| test_util::CreateFile(file_var_file_absolute, file_var_contents)); |
| } |
| const base::FilePath& file_var_file = |
| absolute_paths_ ? file_var_file_absolute : file_var_file_relative; |
| |
| brillo::KeyValueStore metadata; |
| metadata.SetString("exec_name", "fake_exec_name"); |
| metadata.SetString("ver", "fake_chromeos_ver"); |
| metadata.SetString("upload_var_prod", "fake_product"); |
| metadata.SetString("upload_var_ver", "fake_version"); |
| metadata.SetString("sig", "fake_sig"); |
| metadata.SetString("upload_var_guid", "SHOULD_NOT_BE_USED"); |
| metadata.SetString("upload_var_foovar", "bar"); |
| metadata.SetString("upload_text_footext", text_var_file.value()); |
| metadata.SetString("upload_file_log", log_file.value()); |
| metadata.SetString("upload_file_foofile", file_var_file.value()); |
| metadata.SetString("error_type", "fake_error"); |
| |
| CrashDetails details = { |
| .meta_file = base::FilePath(system_dir).Append("0.0.0.0.meta"), |
| .payload_file = payload_file, |
| .payload_kind = "fake_payload", |
| .client_id = kFakeClientId, |
| .metadata = metadata, |
| }; |
| |
| Sender::Options options; |
| options.form_data_boundary = "boundary"; |
| |
| Sender sender(nullptr, std::make_unique<test_util::AdvancingClock>(), |
| options); |
| |
| std::unique_ptr<brillo::http::FormData> form_data = |
| sender.CreateCrashFormData(details, nullptr); |
| if (missing_file_ == kPayloadFile) { |
| EXPECT_EQ(form_data, nullptr); |
| return; |
| } |
| |
| brillo::StreamPtr stream = form_data->ExtractDataStream(); |
| std::vector<uint8_t> data(stream->GetSize()); |
| ASSERT_TRUE(stream->ReadAllBlocking(data.data(), data.size(), nullptr)); |
| |
| std::string expected_data = base::StrCat( |
| {"--boundary\r\n" |
| "Content-Disposition: form-data; name=\"exec_name\"\r\n" |
| "\r\n" |
| "fake_exec_name\r\n" |
| "--boundary\r\n" |
| "Content-Disposition: form-data; name=\"board\"\r\n" |
| "\r\n" |
| "undefined\r\n" |
| "--boundary\r\n" |
| "Content-Disposition: form-data; name=\"hwclass\"\r\n" |
| "\r\n" |
| "undefined\r\n" |
| "--boundary\r\n" |
| "Content-Disposition: form-data; name=\"prod\"\r\n" |
| "\r\n" |
| "fake_product\r\n" |
| "--boundary\r\n" |
| "Content-Disposition: form-data; name=\"ver\"\r\n" |
| "\r\n" |
| "fake_version\r\n" |
| "--boundary\r\n" |
| "Content-Disposition: form-data; name=\"sig\"\r\n" |
| "\r\n" |
| "fake_sig\r\n" |
| "--boundary\r\n" |
| "Content-Disposition: form-data; name=\"sig2\"\r\n" |
| "\r\n" |
| "fake_sig\r\n" |
| "--boundary\r\n" |
| "Content-Disposition: form-data; name=\"upload_file_fake_payload\"; " |
| "filename=\"0.0.0.0.payload\"\r\n" |
| "Content-Transfer-Encoding: binary\r\n" |
| "\r\n" |
| "foobar_payload\r\n", |
| missing_file_ == kBinFile |
| ? "" |
| : "--boundary\r\n" |
| "Content-Disposition: form-data; name=\"foofile\"; " |
| "filename=\"data.bin\"\r\n" |
| "Content-Transfer-Encoding: binary\r\n" |
| "\r\n" |
| "upload_file_contents\r\n", |
| missing_file_ == kLogFile |
| ? "" |
| : "--boundary\r\n" |
| "Content-Disposition: form-data; name=\"log\"; " |
| "filename=\"0.0.0.0.log\"\r\n" |
| "Content-Transfer-Encoding: binary\r\n" |
| "\r\n" |
| "foobar_log\r\n", |
| missing_file_ == kTextFile |
| ? "" |
| : "--boundary\r\n" |
| "Content-Disposition: form-data; name=\"footext\"\r\n" |
| "\r\n" |
| "upload_text_contents\r\n", |
| "--boundary\r\n" |
| "Content-Disposition: form-data; name=\"foovar\"\r\n" |
| "\r\n" |
| "bar\r\n" |
| "--boundary\r\n" |
| "Content-Disposition: form-data; name=\"boot_mode\"\r\n" |
| "\r\n" |
| "missing-crossystem\r\n" |
| "--boundary\r\n" |
| "Content-Disposition: form-data; name=\"error_type\"\r\n" |
| "\r\n" |
| "fake_error\r\n" |
| "--boundary\r\n" |
| "Content-Disposition: form-data; name=\"guid\"\r\n" |
| "\r\n" |
| "00112233445566778899aabbccddeeff\r\n" |
| "--boundary--\r\n"}); |
| |
| EXPECT_EQ(expected_data, std::string(data.begin(), data.end())); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| CreateCrashFormDataInstantiation, |
| CreateCrashFormDataTest, |
| testing::Combine( |
| testing::Bool(), |
| testing::Values(kNone, kPayloadFile, kLogFile, kTextFile, kBinFile))); |
| |
| TEST_F(CrashSenderUtilTest, SendCrashes) { |
| // Set up the mock session manager client. |
| auto mock = |
| std::make_unique<org::chromium::SessionManagerInterfaceProxyMock>(); |
| test_util::SetActiveSessions(mock.get(), {{"user", "hash"}}); |
| std::vector<MetaFile> crashes_to_send; |
| |
| // Establish the client ID. |
| ASSERT_TRUE(CreateClientIdFile()); |
| |
| // Create the system crash directory, and crash files in it. |
| const base::FilePath system_dir = paths::Get(paths::kSystemCrashDirectory); |
| ASSERT_TRUE(base::CreateDirectory(system_dir)); |
| const base::FilePath system_meta_file = system_dir.Append("0.0.0.0.meta"); |
| const base::FilePath system_log = system_dir.Append("0.0.0.0.log"); |
| const base::FilePath system_processing = |
| system_dir.Append("0.0.0.0.processing"); |
| const char system_meta[] = |
| "payload=0.0.0.0.log\n" |
| "exec_name=exec_foo\n" |
| "fake_report_id=123\n" |
| "upload_var_prod=foo\n" |
| "done=1\n" |
| "upload_var_reportTimeMillis=1000000\n"; |
| ASSERT_TRUE(test_util::CreateFile(system_meta_file, system_meta)); |
| ASSERT_TRUE(test_util::CreateFile(system_log, "")); |
| CrashInfo system_info; |
| EXPECT_TRUE(system_info.metadata.LoadFromString(system_meta)); |
| system_info.payload_file = system_log; |
| system_info.payload_kind = "log"; |
| EXPECT_TRUE(base::Time::FromString("25 Apr 2018 1:23:44 GMT", |
| &system_info.last_modified)); |
| crashes_to_send.emplace_back(system_meta_file, std::move(system_info)); |
| |
| // Create a user crash directory, and crash files in it. |
| const base::FilePath user_dir = paths::Get("/home/user/hash/crash"); |
| ASSERT_TRUE(base::CreateDirectory(user_dir)); |
| const base::FilePath user_meta_file = user_dir.Append("0.0.0.0.meta"); |
| const base::FilePath user_log = user_dir.Append("0.0.0.0.log"); |
| const base::FilePath user_processing = user_dir.Append("0.0.0.0.processing"); |
| const char user_meta[] = |
| "payload=0.0.0.0.log\n" |
| "exec_name=exec_bar\n" |
| "fake_report_id=456\n" |
| "upload_var_prod=bar\n" |
| "done=1\n" |
| "upload_var_reportTimeMillis=2000000\n"; |
| ASSERT_TRUE(test_util::CreateFile(user_meta_file, user_meta)); |
| ASSERT_TRUE(test_util::CreateFile(user_log, "")); |
| CrashInfo user_info; |
| EXPECT_TRUE(user_info.metadata.LoadFromString(user_meta)); |
| user_info.payload_file = user_log; |
| user_info.payload_kind = "log"; |
| EXPECT_TRUE(base::Time::FromString("25 Apr 2018 1:24:01 GMT", |
| &user_info.last_modified)); |
| crashes_to_send.emplace_back(user_meta_file, std::move(user_info)); |
| |
| // Create another user crash in "user". This will be skipped since the max |
| // crash rate will be set to 2. |
| const base::FilePath user_meta_file2 = user_dir.Append("1.1.1.1.meta"); |
| const base::FilePath user_log2 = user_dir.Append("1.1.1.1.log"); |
| const char user_meta2[] = |
| "payload=1.1.1.1.log\n" |
| "exec_name=exec_baz\n" |
| "fake_report_id=789\n" |
| "upload_var_prod=baz\n" |
| "done=1\n"; |
| ASSERT_TRUE(test_util::CreateFile(user_meta_file2, user_meta2)); |
| ASSERT_TRUE(test_util::CreateFile(user_log2, "")); |
| CrashInfo user_info2; |
| EXPECT_TRUE(user_info2.metadata.LoadFromString(user_meta2)); |
| user_info2.payload_file = user_log2; |
| user_info2.payload_kind = "log"; |
| EXPECT_TRUE(base::Time::FromString("25 Apr 2018 1:24:05 GMT", |
| &user_info2.last_modified)); |
| crashes_to_send.emplace_back(user_meta_file2, std::move(user_info2)); |
| |
| // Set up the conditions to emulate a device in guest mode; metrics are |
| // disabled in guest mode. |
| ASSERT_TRUE(SetConditions(kOfficialBuild, kGuestMode, kMetricsDisabled)); |
| // Keep the raw pointer, that's needed to exit from guest mode later. |
| MetricsLibraryMock* raw_metrics_lib = metrics_lib_.get(); |
| |
| // Set up the crash sender so that it succeeds. |
| ASSERT_TRUE(SetMockCrashSending(true)); |
| |
| // Set up the sender. |
| std::vector<base::TimeDelta> sleep_times; |
| Sender::Options options; |
| options.session_manager_proxy = mock.release(); |
| options.max_crash_rate = 2; |
| // Setting max_crash_bytes to 0 will limit to the uploader to max_crash_rate. |
| options.max_crash_bytes = 0; |
| options.sleep_function = base::Bind(&FakeSleep, &sleep_times); |
| options.always_write_uploads_log = true; |
| Sender sender(std::move(metrics_lib_), |
| std::make_unique<test_util::AdvancingClock>(), options); |
| ASSERT_TRUE(sender.Init()); |
| |
| // Send crashes. |
| base::TimeDelta total_sleep_time; |
| sender.SendCrashes(crashes_to_send, &total_sleep_time); |
| |
| // We shouldn't be processing any crashes still. |
| EXPECT_FALSE(base::PathExists(system_processing)); |
| EXPECT_FALSE(base::PathExists(user_processing)); |
| |
| // The Chrome uploads.log file shouldn't exist because we had nothing to |
| // upload, but we will have slept once until we determined we shouldn't be |
| // doing uploads. |
| EXPECT_FALSE(base::PathExists(paths::Get(paths::kChromeCrashLog))); |
| EXPECT_EQ(1, sleep_times.size()); |
| EXPECT_EQ(total_sleep_time, |
| std::accumulate(sleep_times.begin(), sleep_times.end(), |
| base::TimeDelta())); |
| sleep_times.clear(); |
| |
| // Exit from guest mode/re-enable metrics, and send crashes again. |
| raw_metrics_lib->set_guest_mode(false); |
| raw_metrics_lib->set_metrics_enabled(true); |
| sender.SendCrashes(crashes_to_send, &total_sleep_time); |
| |
| // We shouldn't be processing any crashes still. |
| EXPECT_FALSE(base::PathExists(system_processing)); |
| EXPECT_FALSE(base::PathExists(user_processing)); |
| |
| // Check the upload log from crash_sender. |
| std::string contents; |
| ASSERT_TRUE( |
| base::ReadFileToString(paths::Get(paths::kChromeCrashLog), &contents)); |
| std::vector<base::Optional<base::Value>> rows = |
| ParseChromeUploadsLog(contents); |
| // Should only contain two results, since max_crash_rate is set to 2. |
| // FakeSleep should be called three times since we sleep before we check the |
| // crash rate. |
| ASSERT_EQ(2, rows.size()); |
| EXPECT_EQ(3, sleep_times.size()); |
| EXPECT_EQ(total_sleep_time, |
| std::accumulate(sleep_times.begin(), sleep_times.end(), |
| base::TimeDelta())); |
| |
| // Each line of the uploads.log file is "{"upload_time":<value>,"upload_id": |
| // <value>,"local_id":<value>,"capture_time":<value>,"state":<value>,"source": |
| // <value>}". |
| // The first run should be for the meta file in the system directory. |
| base::Optional<base::Value> row = std::move(rows[0]); |
| ASSERT_TRUE(row.has_value()); |
| ASSERT_EQ(6, row->DictSize()); |
| EXPECT_TRUE(row->FindKey("upload_time")); |
| EXPECT_EQ("123", row->FindKey("upload_id")->GetString()); |
| EXPECT_EQ("foo", row->FindKey("local_id")->GetString()); |
| EXPECT_EQ("1000", row->FindKey("capture_time")->GetString()); |
| EXPECT_EQ(3, row->FindKey("state")->GetInt()); |
| EXPECT_EQ("exec_foo", row->FindKey("source")->GetString()); |
| |
| // The second run should be for the meta file in the "user" directory. |
| row = std::move(rows[1]); |
| ASSERT_TRUE(row.has_value()); |
| ASSERT_EQ(6, row->DictSize()); |
| EXPECT_TRUE(row->FindKey("upload_time")); |
| EXPECT_EQ("456", row->FindKey("upload_id")->GetString()); |
| EXPECT_EQ("bar", row->FindKey("local_id")->GetString()); |
| EXPECT_EQ("2000", row->FindKey("capture_time")->GetString()); |
| EXPECT_EQ(3, row->FindKey("state")->GetInt()); |
| EXPECT_EQ("REDACTED", row->FindKey("source")->GetString()); |
| |
| // The uploaded crash files should be removed now. |
| EXPECT_FALSE(base::PathExists(system_meta_file)); |
| EXPECT_FALSE(base::PathExists(system_log)); |
| EXPECT_FALSE(base::PathExists(user_meta_file)); |
| EXPECT_FALSE(base::PathExists(user_log)); |
| |
| // The following should be kept since the crash report was not uploaded. |
| EXPECT_TRUE(base::PathExists(user_meta_file2)); |
| EXPECT_TRUE(base::PathExists(user_log2)); |
| } |
| |
| TEST_F(CrashSenderUtilTest, SendCrashes_Fail) { |
| // Set up the mock session manager client. |
| auto mock = |
| std::make_unique<org::chromium::SessionManagerInterfaceProxyMock>(); |
| test_util::SetActiveSessions(mock.get(), {{"user", "hash"}}); |
| std::vector<MetaFile> crashes_to_send; |
| |
| // Create the system crash directory, and crash files in it. |
| const base::FilePath system_dir = paths::Get(paths::kSystemCrashDirectory); |
| ASSERT_TRUE(base::CreateDirectory(system_dir)); |
| const base::FilePath system_meta_file = system_dir.Append("0.0.0.0.meta"); |
| const base::FilePath system_log = system_dir.Append("0.0.0.0.log"); |
| const base::FilePath system_processing = |
| system_dir.Append("0.0.0.0.processing"); |
| const char system_meta[] = |
| "payload=0.0.0.0.log\n" |
| "done=1\n"; |
| ASSERT_TRUE(test_util::CreateFile(system_meta_file, system_meta)); |
| ASSERT_TRUE(test_util::CreateFile(system_log, "")); |
| CrashInfo system_info; |
| EXPECT_TRUE(system_info.metadata.LoadFromString(system_meta)); |
| system_info.payload_file = system_log; |
| system_info.payload_kind = "log"; |
| EXPECT_TRUE(base::Time::FromString("25 Apr 2018 1:23:44 GMT", |
| &system_info.last_modified)); |
| crashes_to_send.emplace_back(system_meta_file, std::move(system_info)); |
| |
| ASSERT_TRUE(SetConditions(kOfficialBuild, kSignInMode, kMetricsEnabled)); |
| |
| // Set up the crash sender so that it fails. |
| ASSERT_TRUE(SetMockCrashSending(false)); |
| |
| // Set up the sender. |
| std::vector<base::TimeDelta> sleep_times; |
| Sender::Options options; |
| options.session_manager_proxy = mock.release(); |
| options.max_crash_rate = 2; |
| options.sleep_function = base::Bind(&FakeSleep, &sleep_times); |
| options.always_write_uploads_log = true; |
| Sender sender(std::move(metrics_lib_), |
| std::make_unique<test_util::AdvancingClock>(), options); |
| ASSERT_TRUE(sender.Init()); |
| |
| sender.SendCrashes(crashes_to_send, nullptr); |
| |
| // We shouldn't be processing the crash still -- sending failed, but didn't |
| // crash. |
| EXPECT_FALSE(base::PathExists(system_processing)); |
| |
| // The followings should be kept since the crash report was not uploaded. |
| EXPECT_TRUE(base::PathExists(system_meta_file)); |
| EXPECT_TRUE(base::PathExists(system_log)); |
| |
| // The Chrome uploads.log file shouldn't exist because we had nothing to |
| // report. |
| EXPECT_FALSE(base::PathExists(paths::Get(paths::kChromeCrashLog))); |
| } |
| |
| // Verify behavior when SendCrashes itself crashes. |
| TEST_F(CrashSenderUtilDeathTest, SendCrashes_Crash) { |
| // Set up the mock session manager client. |
| auto mock = |
| std::make_unique<org::chromium::SessionManagerInterfaceProxyMock>(); |
| test_util::SetActiveSessions(mock.get(), {{"user", "hash"}}); |
| std::vector<MetaFile> crashes_to_send; |
| |
| // Create the system crash directory, and crash files in it. |
| const base::FilePath system_dir = paths::Get(paths::kSystemCrashDirectory); |
| ASSERT_TRUE(base::CreateDirectory(system_dir)); |
| const base::FilePath system_meta_file = system_dir.Append("0.0.0.0.meta"); |
| const base::FilePath system_log = system_dir.Append("0.0.0.0.log"); |
| const base::FilePath system_processing = |
| system_dir.Append("0.0.0.0.processing"); |
| const char system_meta[] = |
| "payload=0.0.0.0.log\n" |
| "done=1\n"; |
| ASSERT_TRUE(test_util::CreateFile(system_meta_file, system_meta)); |
| ASSERT_TRUE(test_util::CreateFile(system_log, "")); |
| CrashInfo system_info; |
| EXPECT_TRUE(system_info.metadata.LoadFromString(system_meta)); |
| system_info.payload_file = system_log; |
| system_info.payload_kind = "log"; |
| EXPECT_TRUE(base::Time::FromString("25 Apr 2018 1:23:44 GMT", |
| &system_info.last_modified)); |
| crashes_to_send.emplace_back(system_meta_file, std::move(system_info)); |
| |
| ASSERT_TRUE(SetConditions(kOfficialBuild, kSignInMode, kMetricsEnabled)); |
| |
| std::vector<base::TimeDelta> sleep_times; |
| Sender::Options options; |
| options.session_manager_proxy = mock.release(); |
| options.max_crash_rate = 2; |
| options.sleep_function = base::Bind(&FakeSleep, &sleep_times); |
| options.always_write_uploads_log = true; |
| Sender sender(std::move(metrics_lib_), |
| std::make_unique<test_util::AdvancingClock>(), options); |
| ASSERT_TRUE(sender.Init()); |
| |
| ASSERT_TRUE(SetMockCrashSending(true)); |
| sender.SetCrashDuringSendForTesting(true); |
| EXPECT_DEATH(sender.SendCrashes(crashes_to_send, nullptr), |
| "crashing as requested"); |
| |
| // We crashed, so the ".processing" file should still exist. |
| EXPECT_TRUE(base::PathExists(system_processing)); |
| } |
| |
| TEST_F(CrashSenderUtilTest, LockFile) { |
| auto clock = std::make_unique<MockClock>(); |
| base::Time start_time; |
| ASSERT_TRUE(base::Time::FromUTCString("2019-04-20 13:53", &start_time)); |
| // Called twice -- once to get start time, once to see if we're already |
| // past the start time + 5 minutes |
| EXPECT_CALL(*clock, Now()).Times(2).WillRepeatedly(Return(start_time)); |
| std::vector<base::TimeDelta> sleep_times; |
| Sender::Options options; |
| options.sleep_function = base::Bind(&FakeSleep, &sleep_times); |
| Sender sender(std::move(metrics_lib_), std::move(clock), options); |
| ASSERT_TRUE(sender.Init()); |
| |
| EXPECT_FALSE(IsFileLocked(paths::Get(paths::kCrashSenderLockFile))); |
| base::File lock(sender.AcquireLockFileOrDie()); |
| EXPECT_TRUE(IsFileLocked(paths::Get(paths::kCrashSenderLockFile))); |
| // Should not have slept acquiring lock since the file was unlocked. |
| EXPECT_THAT(sleep_times, IsEmpty()); |
| } |
| |
| TEST_F(CrashSenderUtilTest, LockFileTriesAgainIfFirstAttemptFails) { |
| base::FilePath lock_file_path = paths::Get(paths::kCrashSenderLockFile); |
| auto lock_process = LockFile(lock_file_path); |
| |
| auto clock = std::make_unique<MockClock>(); |
| base::Time start_time; |
| ASSERT_TRUE(base::Time::FromUTCString("2019-04-20 13:53", &start_time)); |
| |
| // Make AcquireLockFileOrDie sleep several times, and then unlock the file. |
| EXPECT_CALL(*clock, Now()) |
| .WillOnce(Return(start_time)) |
| .WillOnce(Return(start_time)) |
| .WillOnce(Return(start_time + base::TimeDelta::FromMinutes(1))) |
| .WillOnce(Return(start_time + base::TimeDelta::FromMinutes(2))) |
| .WillOnce(Return(start_time + base::TimeDelta::FromMinutes(3))) |
| .WillOnce(Invoke([&lock_process, start_time]() { |
| lock_process->Kill(SIGKILL, 10); |
| lock_process->Wait(); |
| return start_time + base::TimeDelta::FromMinutes(4); |
| })); |
| std::vector<base::TimeDelta> sleep_times; |
| Sender::Options options; |
| options.sleep_function = base::Bind(&FakeSleep, &sleep_times); |
| Sender sender(std::move(metrics_lib_), std::move(clock), options); |
| ASSERT_TRUE(sender.Init()); |
| |
| base::File lock(sender.AcquireLockFileOrDie()); |
| EXPECT_TRUE(IsFileLocked(lock_file_path)); |
| EXPECT_EQ(sleep_times.size(), 4); |
| } |
| |
| TEST_F(CrashSenderUtilTest, LockFileTriesOneLastTimeAfterTimeout) { |
| base::FilePath lock_file_path = paths::Get(paths::kCrashSenderLockFile); |
| auto lock_process = LockFile(lock_file_path); |
| |
| auto clock = std::make_unique<MockClock>(); |
| base::Time start_time; |
| ASSERT_TRUE(base::Time::FromUTCString("2019-04-20 13:53", &start_time)); |
| |
| // Make AcquireLockFileOrDie sleep enough that the loop exits, then unlock the |
| // file. |
| EXPECT_CALL(*clock, Now()) |
| .WillOnce(Return(start_time)) |
| .WillOnce(Return(start_time)) |
| .WillOnce(Return(start_time + base::TimeDelta::FromMinutes(1))) |
| .WillOnce(Return(start_time + base::TimeDelta::FromMinutes(2))) |
| .WillOnce(Return(start_time + base::TimeDelta::FromMinutes(3))) |
| .WillOnce(Return(start_time + base::TimeDelta::FromMinutes(4))) |
| .WillOnce(Invoke([&lock_process, start_time]() { |
| lock_process->Kill(SIGKILL, 10); |
| lock_process->Wait(); |
| return start_time + base::TimeDelta::FromMinutes(6); |
| })); |
| std::vector<base::TimeDelta> sleep_times; |
| Sender::Options options; |
| options.sleep_function = base::Bind(&FakeSleep, &sleep_times); |
| Sender sender(std::move(metrics_lib_), std::move(clock), options); |
| ASSERT_TRUE(sender.Init()); |
| |
| base::File lock(sender.AcquireLockFileOrDie()); |
| EXPECT_TRUE(IsFileLocked(lock_file_path)); |
| EXPECT_EQ(sleep_times.size(), 5); |
| } |
| |
| TEST_F(CrashSenderUtilDeathTest, LockFileDiesIfFileIsLocked) { |
| base::FilePath lock_file_path = paths::Get(paths::kCrashSenderLockFile); |
| auto lock_process = LockFile(lock_file_path); |
| std::vector<base::TimeDelta> sleep_times; |
| Sender::Options options; |
| options.sleep_function = base::Bind(&FakeSleep, &sleep_times); |
| Sender sender(std::move(metrics_lib_), |
| std::make_unique<test_util::AdvancingClock>(), options); |
| ASSERT_TRUE(sender.Init()); |
| EXPECT_EXIT(sender.AcquireLockFileOrDie(), ExitedWithCode(EXIT_FAILURE), |
| "Failed to acquire a lock"); |
| } |
| |
| TEST_F(CrashSenderUtilTest, CreateClientId) { |
| std::string client_id = GetClientId(); |
| EXPECT_EQ(client_id.length(), 32); |
| // Make sure it returns the same one multiple times. |
| EXPECT_EQ(client_id, GetClientId()); |
| } |
| |
| TEST_F(CrashSenderUtilTest, RetrieveClientId) { |
| CreateClientIdFile(); |
| EXPECT_EQ(kFakeClientId, GetClientId()); |
| } |
| |
| class IsNetworkOnlineTest : public CrashSenderUtilTest { |
| public: |
| void TestIsNetworkOnline(std::string connection_state, |
| bool get_properties_retval, |
| bool expected_result); |
| }; |
| |
| void IsNetworkOnlineTest::TestIsNetworkOnline(std::string connection_state, |
| bool get_properties_retval, |
| bool expected_result) { |
| g_connection_state = &connection_state; |
| // Set up the shill flimflam manager client. |
| auto mock = std::make_unique<org::chromium::flimflam::ManagerProxyMock>(); |
| EXPECT_CALL(*mock, GetProperties(_, _, _)) |
| .WillOnce( |
| DoAll(Invoke(&GetShillProperties), Return(get_properties_retval))); |
| |
| Sender::Options options; |
| options.shill_proxy = mock.release(); |
| Sender sender(std::move(metrics_lib_), |
| std::make_unique<test_util::AdvancingClock>(), options); |
| ASSERT_TRUE(sender.Init()); |
| EXPECT_EQ(sender.IsNetworkOnline(), expected_result); |
| } |
| |
| TEST_F(IsNetworkOnlineTest, Online) { |
| TestIsNetworkOnline("online", true, true); |
| } |
| |
| TEST_F(IsNetworkOnlineTest, Offline) { |
| TestIsNetworkOnline("offline", true, false); |
| } |
| |
| TEST_F(IsNetworkOnlineTest, Fail) { |
| TestIsNetworkOnline("", false, true); |
| } |
| |
| } // namespace util |