blob: e16dbb6532daa8b7908db6c36e725bebf9dcf050 [file] [log] [blame]
// Copyright 2020 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_base.h"
#include <string>
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
#include <base/strings/strcat.h>
#include <base/guid.h>
#include <brillo/key_value_store.h>
#include <gtest/gtest.h>
#include "crash-reporter/crash_sender_paths.h"
#include "crash-reporter/paths.h"
#include "crash-reporter/test_util.h"
namespace util {
namespace {
using ::testing::Not;
using ::testing::StartsWith;
constexpr char kFakeClientId[] = "00112233445566778899aabbccddeeff";
// 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);
}
// 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) {
util::g_force_is_mock = true;
util::g_force_is_mock_successful = success;
return base::CreateDirectory(paths::Get(paths::kChromeCrashLog).DirName());
}
// Reset "force" flags to clear out IsMock flags
void ClearMockCrashSending() {
util::g_force_is_mock = false;
util::g_force_is_mock_successful = false;
}
// Set the flag which indicates we're mocking crash sending for Integration
// Tests, successfully or as a failure.
bool SetIntegrationTesting(bool success) {
return test_util::CreateFile(paths::GetAt(paths::kSystemRunStateDirectory,
paths::kMockCrashSending),
success ? "" : "0") &&
base::CreateDirectory(paths::Get(paths::kChromeCrashLog).DirName());
}
class CrashSenderBaseForTesting : public util::SenderBase {
public:
CrashSenderBaseForTesting(std::unique_ptr<base::Clock> clock,
const Options& options)
: util::SenderBase(std::move(clock), options) {}
// We don't need this implementation
void RecordCrashRemoveReason(CrashRemoveReason reason) {}
};
class CrashSenderBaseTest : public testing::Test {
protected:
void SetUp() override {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
test_dir_ = temp_dir_.GetPath();
paths::SetPrefixForTesting(test_dir_);
}
void TearDown() override {
ClearMockCrashSending();
paths::SetPrefixForTesting(base::FilePath());
}
base::ScopedTempDir temp_dir_;
base::FilePath test_dir_;
};
TEST_F(CrashSenderBaseTest, GetFilePathFromMetadata) {
brillo::KeyValueStore metadata;
metadata.LoadFromString("");
EXPECT_EQ("", GetFilePathFromMetadata(metadata, "payload").value());
metadata.LoadFromString("payload=test.log\n");
EXPECT_EQ("test.log", GetFilePathFromMetadata(metadata, "payload").value());
metadata.LoadFromString("payload=/foo/test.log\n");
EXPECT_EQ("/foo/test.log",
GetFilePathFromMetadata(metadata, "payload").value());
}
TEST_F(CrashSenderBaseTest, 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 affect the function.
EXPECT_EQ("minidump",
GetKindFromPayloadPath(base::FilePath("/1.2.3/foo.dmp.gz")));
}
TEST_F(CrashSenderBaseTest, 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 previously 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 upload paths should still be parseable
EXPECT_TRUE(ParseMetadata("payload=a.d.dmp\nupload_file_=/\n", &metadata));
// 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(CrashSenderBaseTest, 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(CrashSenderBaseTest, ReadMetaFile_BlockAbsoluteAttachments) {
const base::FilePath meta_file = test_dir_.Append("read_meta_file.meta");
const base::FilePath payload_file = test_dir_.Append("read_meta_file.xyz");
const base::FilePath log_file = test_dir_.Append("read_meta_file.log");
const base::FilePath log2_file = test_dir_.Append("read_meta_file2.log");
const std::string meta =
base::StrCat({"payload=read_meta_file.xyz\n"
"exec_name=exec_bar\n"
"fake_report_id=456\n"
"upload_var_prod=bar\n"
"upload_file_test.log=",
log_file.value(),
"\n"
"upload_text_test2.log=",
log2_file.value(),
"\n"
"done=1\n"});
ASSERT_TRUE(test_util::CreateFile(meta_file, meta));
ASSERT_TRUE(test_util::CreateFile(payload_file, "payload file"));
ASSERT_TRUE(test_util::CreateFile(log_file, "log file"));
ASSERT_TRUE(test_util::CreateFile(log2_file, "log file 2"));
brillo::KeyValueStore metadata;
EXPECT_TRUE(ParseMetadata(meta, &metadata));
const util::CrashDetails details = {
.meta_file = meta_file,
.payload_file = payload_file.BaseName(),
.payload_kind = "log",
.client_id = "client",
.metadata = metadata,
};
SenderBase::Options options;
CrashSenderBaseForTesting sender(
std::make_unique<test_util::AdvancingClock>(), options);
FullCrash crash = sender.ReadMetaFile(details);
int log_files_blocked = 0;
for (const auto& kv : crash.key_vals) {
const std::string& key = kv.first;
const std::string& val = kv.second;
EXPECT_THAT(key, Not(StartsWith("upload_file_")));
EXPECT_THAT(key, Not(StartsWith("upload_text_")));
if (key == "file_blocked_by_path") {
EXPECT_TRUE(val == log_file.value() || val == log2_file.value());
log_files_blocked++;
}
}
EXPECT_EQ(2, log_files_blocked);
}
TEST_F(CrashSenderBaseTest, 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(CrashSenderBaseTest, RetrieveClientId) {
CreateClientIdFile();
EXPECT_EQ(kFakeClientId, GetClientId());
}
TEST_F(CrashSenderBaseTest, 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(test_util::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(test_util::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(test_util::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(CrashSenderBaseTest, IsMock) {
// Ensure the state is clean
EXPECT_FALSE(IsMock());
EXPECT_FALSE(IsIntegrationTest());
ASSERT_TRUE(SetMockCrashSending(false));
EXPECT_TRUE(IsMock());
EXPECT_FALSE(IsMockSuccessful());
EXPECT_FALSE(IsIntegrationTest()); // Shouldn't change
ASSERT_TRUE(SetMockCrashSending(true));
EXPECT_TRUE(IsMock());
EXPECT_TRUE(IsMockSuccessful());
EXPECT_FALSE(IsIntegrationTest()); // Shouldn't change
ClearMockCrashSending();
EXPECT_FALSE(IsMock());
}
// Ensure that IsIntegrationTest implies IsMock, but not the opposite
TEST_F(CrashSenderBaseTest, IsIntegrationTest) {
EXPECT_FALSE(IsMock());
EXPECT_FALSE(IsIntegrationTest());
ASSERT_TRUE(SetIntegrationTesting(false));
EXPECT_TRUE(IsIntegrationTest());
EXPECT_TRUE(IsMock());
EXPECT_FALSE(IsMockSuccessful());
ASSERT_TRUE(SetIntegrationTesting(true));
EXPECT_TRUE(IsIntegrationTest());
EXPECT_TRUE(IsMock());
EXPECT_TRUE(IsMockSuccessful());
}
TEST_F(CrashSenderBaseTest, 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());
}
} // namespace
} // namespace util