blob: 06aa73f0a34e63c327aa6e0338dcb393ef47374a [file] [log] [blame]
// Copyright 2024 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <utility>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
#include <base/logging.h>
#include <dbus/mock_bus.h>
#include <gtest/gtest.h>
#include "metrics/debugd_reader_mock.h"
#include "metrics/mmc_error_parser.h"
namespace chromeos_metrics {
const char kParserName[] = "mmc0";
const char kSecondParserName[] = "mmc1";
const char kPersistentDirName[] = "persistent";
const char kRuntimeDirName[] = "runtime";
class MmcErrorParserTest : public testing::Test {
protected:
void SetUp() override {
dbus::Bus::Options options;
options.bus_type = dbus::Bus::SYSTEM;
bus_ = new dbus::MockBus(options);
CHECK(dir_.CreateUniqueTempDir());
SetUpParser(kParserName);
}
// Helper function to initialize parser_.
// Can be called from the test case logic to reset the parser and possibly
// change its name.
void SetUpParser(std::string_view name) {
std::unique_ptr<DebugdReaderMock> reader;
reader.reset(new testing::StrictMock<DebugdReaderMock>(bus_.get(), "test"));
EXPECT_CALL(*reader.get(), Read)
.WillRepeatedly(testing::Invoke(this, &MmcErrorParserTest::MockRead));
parser_ = MmcErrorParser::Create(dir_.GetPath().Append(kPersistentDirName),
dir_.GetPath().Append(kRuntimeDirName),
std::move(reader), name);
CHECK(parser_);
}
std::unique_ptr<MmcErrorParser> parser_;
base::ScopedTempDir dir_;
std::string mmc_log_;
private:
std::optional<std::string> MockRead() {
if (mmc_log_.empty()) {
return std::nullopt;
}
return std::make_optional(mmc_log_);
}
scoped_refptr<dbus::MockBus> bus_;
};
// Check how the parser handler handles a nullopt response from the reader.
TEST_F(MmcErrorParserTest, ReadErrorTest) {
parser_->Update();
MmcErrorRecord record = parser_->GetAndClear();
EXPECT_EQ(record.cmd_timeouts, 0);
EXPECT_EQ(record.cmd_crcs, 0);
EXPECT_EQ(record.data_timeouts, 0);
EXPECT_EQ(record.data_crcs, 0);
}
// Check if all relevant backing files for PersistentIntegers exist.
// They should be created in the constructor.
TEST_F(MmcErrorParserTest, CreationTest) {
const char test_mmc_log[] =
"/sys/kernel/debug/mmc0/err_stats:\n"
"# Command Timeout Occurred:\t 21\n"
"# Command CRC Errors Occurred:\t 37\n"
"# Data Timeout Occurred:\t 42\n"
"# Data CRC Errors Occurred:\t 1";
mmc_log_ = test_mmc_log;
base::FilePath persistent_dir =
dir_.GetPath().Append(kPersistentDirName).Append(kParserName);
base::FilePath runtime_dir =
dir_.GetPath().Append(kRuntimeDirName).Append(kParserName);
// Call update to trigger the write.
parser_->Update();
EXPECT_TRUE(base::PathExists(persistent_dir.Append(kDataTimeoutName)));
EXPECT_TRUE(base::PathExists(persistent_dir.Append(kDataCRCName)));
EXPECT_TRUE(base::PathExists(persistent_dir.Append(kCmdTimeoutName)));
EXPECT_TRUE(base::PathExists(persistent_dir.Append(kCmdCRCName)));
EXPECT_TRUE(base::PathExists(runtime_dir.Append(kDataTimeoutName)));
EXPECT_TRUE(base::PathExists(runtime_dir.Append(kDataCRCName)));
EXPECT_TRUE(base::PathExists(runtime_dir.Append(kCmdTimeoutName)));
EXPECT_TRUE(base::PathExists(runtime_dir.Append(kCmdCRCName)));
}
// Assign values to all metrics and check if they're parsed correctly.
// Call GetAndClear for the second time to test that we get zeroes.
TEST_F(MmcErrorParserTest, ParsingTest) {
const char test_mmc_log[] =
"/sys/kernel/debug/mmc0/err_stats:\n"
"# Command Timeout Occurred:\t 21\n"
"# Command CRC Errors Occurred:\t 37\n"
"# Data Timeout Occurred:\t 42\n"
"# Data CRC Errors Occurred:\t 1";
MmcErrorRecord record;
mmc_log_ = test_mmc_log;
parser_->Update();
record = parser_->GetAndClear();
EXPECT_EQ(record.cmd_timeouts, 21);
EXPECT_EQ(record.cmd_crcs, 37);
EXPECT_EQ(record.data_timeouts, 42);
EXPECT_EQ(record.data_crcs, 1);
// record should be zeroes if the number of errors doesn't change.
parser_->Update();
record = parser_->GetAndClear();
EXPECT_EQ(record.cmd_timeouts, 0);
EXPECT_EQ(record.cmd_crcs, 0);
EXPECT_EQ(record.data_timeouts, 0);
EXPECT_EQ(record.data_crcs, 0);
}
// Parse a log that contains entries for two controllers.
// Check data parsing for both of them.
TEST_F(MmcErrorParserTest, TwoControllersTest) {
const char test_mmc_log[] =
"/sys/kernel/debug/mmc0/err_stats:\n"
"# Command Timeout Occurred:\t 21\n"
"# Command CRC Errors Occurred:\t 37\n"
"# Data Timeout Occurred:\t 42\n"
"# Data CRC Errors Occurred:\t 1\n\n"
"/sys/kernel/debug/mmc1/err_stats:\n"
"# Command Timeout Occurred:\t 121\n"
"# Command CRC Errors Occurred:\t 137\n"
"# Data Timeout Occurred:\t 142\n"
"# Data CRC Errors Occurred:\t 11\n";
MmcErrorRecord record;
mmc_log_ = test_mmc_log;
parser_->Update();
record = parser_->GetAndClear();
EXPECT_EQ(record.cmd_timeouts, 21);
EXPECT_EQ(record.cmd_crcs, 37);
EXPECT_EQ(record.data_timeouts, 42);
EXPECT_EQ(record.data_crcs, 1);
SetUpParser(kSecondParserName);
parser_->Update();
record = parser_->GetAndClear();
EXPECT_EQ(record.cmd_timeouts, 121);
EXPECT_EQ(record.cmd_crcs, 137);
EXPECT_EQ(record.data_timeouts, 142);
EXPECT_EQ(record.data_crcs, 11);
}
// Simulate metrics_daemon crash by re-creating the parser object.
// Then verify that the errors haven't been reported twice.
TEST_F(MmcErrorParserTest, CrashTest) {
const char test_mmc_log[] =
"/sys/kernel/debug/mmc0/err_stats:\n"
"# Command Timeout Occurred:\t 21\n"
"# Command CRC Errors Occurred:\t 37\n"
"# Data Timeout Occurred:\t 42\n"
"# Data CRC Errors Occurred:\t 1";
MmcErrorRecord record;
mmc_log_ = test_mmc_log;
parser_->Update();
record = parser_->GetAndClear();
EXPECT_EQ(record.cmd_timeouts, 21);
EXPECT_EQ(record.cmd_crcs, 37);
EXPECT_EQ(record.data_timeouts, 42);
EXPECT_EQ(record.data_crcs, 1);
SetUpParser(kParserName);
// Parser should be able to pick up that we cleared its state above.
parser_->Update();
record = parser_->GetAndClear();
EXPECT_EQ(record.cmd_timeouts, 0);
EXPECT_EQ(record.cmd_crcs, 0);
EXPECT_EQ(record.data_timeouts, 0);
EXPECT_EQ(record.data_crcs, 0);
}
} // namespace chromeos_metrics