blob: bc8953786fbe2f3e3754bdd73e98f08759f3c9b8 [file] [log] [blame]
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <memory>
#include <utility>
#include <base/dcheck_is_on.h>
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
#include <base/logging.h>
#include <base/test/bind.h>
#include <chromeos/dbus/service_constants.h>
#include <dbus/mock_bus.h>
#include <dbus/mock_exported_object.h>
#include <dbus/mock_object_proxy.h>
#include <gtest/gtest.h>
#include "featured/mock_tmp_storage_impl.h"
#include "featured/service.h"
#include "featured/store_impl.h"
#include "featured/store_impl_mock.h"
#include "featured/store_interface.h"
using ::testing::_;
using ::testing::ElementsAre;
using ::testing::Invoke;
using ::testing::Return;
using ::testing::StrictMock;
namespace featured {
namespace {
void ResponseSenderCallback(const std::string& expected_message,
std::unique_ptr<dbus::Response> response) {
EXPECT_EQ(expected_message, response->ToString());
}
TEST(SupportCheckCommand, FileExistsTest) {
base::FilePath file;
ASSERT_TRUE(base::CreateTemporaryFile(&file));
FileExistsCommand c(file.MaybeAsASCII());
ASSERT_TRUE(c.IsSupported());
FileNotExistsCommand c2(file.MaybeAsASCII());
ASSERT_FALSE(c2.IsSupported());
}
TEST(SupportCheckCommand, FileNotExistsTest) {
base::ScopedTempDir dir;
ASSERT_TRUE(dir.CreateUniqueTempDir());
base::FilePath file(dir.GetPath().Append("non-existent"));
FileNotExistsCommand c(file.MaybeAsASCII());
ASSERT_TRUE(c.IsSupported());
FileExistsCommand c2(file.MaybeAsASCII());
ASSERT_FALSE(c2.IsSupported());
}
// Verify that the AlwaysSupported command is always supported.
TEST(SupportCheckCommand, AlwaysSupported) {
ASSERT_TRUE(AlwaysSupportedCommand().IsSupported());
}
// Verify that Mkdir succeeds in a basic case.
TEST(FeatureCommand, Mkdir_Allowed) {
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
ASSERT_TRUE(base::CreateDirectory(temp_dir.GetPath().Append("sys")));
base::FilePath sys_path(temp_dir.GetPath().Append("sys/foo"));
EXPECT_FALSE(base::PathExists(sys_path));
MkdirCommand mkdir("/sys/foo");
mkdir.SetPrefixForTesting(temp_dir.GetPath());
EXPECT_TRUE(mkdir.Execute());
EXPECT_TRUE(base::PathExists(sys_path));
// Executing *twice* should succeed since the path already exists.
EXPECT_TRUE(mkdir.Execute());
}
// Verify that Mkdir fails if the prefix isn't allowed.
TEST(FeatureCommand, Mkdir_NotAllowed) {
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
ASSERT_TRUE(base::CreateDirectory(temp_dir.GetPath().Append("mnt")));
base::FilePath mnt_path(temp_dir.GetPath().Append("mnt/foo"));
EXPECT_FALSE(base::PathExists(mnt_path));
MkdirCommand mkdir("/mnt/foo");
mkdir.SetPrefixForTesting(temp_dir.GetPath());
EXPECT_FALSE(base::PathExists(mnt_path));
EXPECT_FALSE(mkdir.Execute());
EXPECT_FALSE(base::PathExists(mnt_path));
}
// Verify that Mkdir fails if directory creation fails.
TEST(FeatureCommand, Mkdir_CreateFails) {
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
ASSERT_TRUE(base::CreateDirectory(temp_dir.GetPath().Append("sys")));
base::FilePath sys_path(temp_dir.GetPath().Append("sys/foo"));
ASSERT_TRUE(base::WriteFile(sys_path, "2"));
MkdirCommand mkdir("/sys/foo");
mkdir.SetPrefixForTesting(temp_dir.GetPath());
EXPECT_TRUE(base::PathExists(sys_path));
EXPECT_FALSE(mkdir.Execute());
std::string contents;
EXPECT_TRUE(base::ReadFileToString(sys_path, &contents));
EXPECT_EQ(contents, "2");
}
// Verify that WriteFile succeeds in a basic case.
TEST(FeatureCommand, WriteFile_Success) {
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
ASSERT_TRUE(base::CreateDirectory(temp_dir.GetPath().Append("sys")));
base::FilePath sys_path(temp_dir.GetPath().Append("sys/foo"));
EXPECT_FALSE(base::PathExists(sys_path));
WriteFileCommand write("/sys/foo", "1");
write.SetPrefixForTesting(temp_dir.GetPath());
EXPECT_TRUE(write.Execute());
std::string contents;
EXPECT_TRUE(base::ReadFileToString(sys_path, &contents));
EXPECT_EQ(contents, "1");
}
// Verify that WriteFile fails if the prefix isn't allowed.
TEST(FeatureCommand, WriteFile_NotAllowed) {
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
ASSERT_TRUE(base::CreateDirectory(temp_dir.GetPath().Append("mnt")));
base::FilePath mnt_path(temp_dir.GetPath().Append("mnt/foo"));
EXPECT_FALSE(base::PathExists(mnt_path));
WriteFileCommand write("/mnt/foo", "1");
write.SetPrefixForTesting(temp_dir.GetPath());
EXPECT_FALSE(base::PathExists(base::FilePath(mnt_path)));
EXPECT_FALSE(write.Execute());
EXPECT_FALSE(base::PathExists(base::FilePath(mnt_path)));
}
// Verify that WriteFile fails if file writing fails.
TEST(FeatureCommand, WriteFile_Fails) {
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
// Do *not* create the sys directory.
base::FilePath sys_path(temp_dir.GetPath().Append("sys/foo"));
EXPECT_FALSE(base::PathExists(sys_path));
WriteFileCommand write("/sys/foo", "1");
write.SetPrefixForTesting(temp_dir.GetPath());
EXPECT_FALSE(write.Execute());
EXPECT_FALSE(base::PathExists(base::FilePath(sys_path)));
}
// Verify that PlatformFeature::Execute runs all commands.
TEST(PlatformFeature, ExecuteBasic) {
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
ASSERT_TRUE(base::CreateDirectory(temp_dir.GetPath().Append("sys")));
base::FilePath sys_dir_path(temp_dir.GetPath().Append("sys/foo"));
EXPECT_FALSE(base::PathExists(sys_dir_path));
base::FilePath sys_file_path(temp_dir.GetPath().Append("sys/foo/bar"));
EXPECT_FALSE(base::PathExists(sys_file_path));
auto mkdir = std::make_unique<MkdirCommand>("/sys/foo");
mkdir->SetPrefixForTesting(temp_dir.GetPath());
auto write = std::make_unique<WriteFileCommand>("/sys/foo/bar", "1");
write->SetPrefixForTesting(temp_dir.GetPath());
std::vector<std::unique_ptr<FeatureCommand>> commands;
commands.push_back(std::move(mkdir));
commands.push_back(std::move(write));
PlatformFeature foo("foo", {}, std::move(commands));
EXPECT_TRUE(foo.Execute());
std::string contents;
EXPECT_TRUE(base::ReadFileToString(sys_file_path, &contents));
EXPECT_EQ(contents, "1");
}
// Verify that PlatformFeature::Execute stops as soon as one command fails.
TEST(PlatformFeature, ExecuteFail) {
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
ASSERT_TRUE(base::CreateDirectory(temp_dir.GetPath().Append("sys")));
base::FilePath sys_dir_path(temp_dir.GetPath().Append("sys/foo/bar"));
EXPECT_FALSE(base::PathExists(sys_dir_path));
base::FilePath sys_file_path(temp_dir.GetPath().Append("sys/baz"));
EXPECT_FALSE(base::PathExists(sys_file_path));
auto write = std::make_unique<WriteFileCommand>("/sys/foo/bar", "1");
write->SetPrefixForTesting(temp_dir.GetPath());
auto mkdir = std::make_unique<MkdirCommand>("/sys/baz");
mkdir->SetPrefixForTesting(temp_dir.GetPath());
std::vector<std::unique_ptr<FeatureCommand>> commands;
commands.push_back(std::move(write));
commands.push_back(std::move(mkdir));
PlatformFeature foo("foo", {}, std::move(commands));
EXPECT_FALSE(foo.Execute());
EXPECT_FALSE(base::PathExists(sys_file_path));
EXPECT_FALSE(base::PathExists(sys_dir_path));
}
// Test that IsSupported returns true if all commands return true.
TEST(PlatformFeature, IsSupported) {
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
base::FilePath foo = temp_dir.GetPath().Append("foo");
ASSERT_TRUE(base::CreateDirectory(foo));
EXPECT_TRUE(base::PathExists(foo));
base::FilePath bar = temp_dir.GetPath().Append("bar");
EXPECT_FALSE(base::PathExists(bar));
auto exist = std::make_unique<FileExistsCommand>(foo.value());
auto not_exist = std::make_unique<FileNotExistsCommand>(bar.value());
std::vector<std::unique_ptr<SupportCheckCommand>> commands;
commands.push_back(std::move(exist));
commands.push_back(std::move(not_exist));
PlatformFeature features("foo", std::move(commands), {});
EXPECT_TRUE(features.IsSupported());
}
// Test that IsSupported returns false if one command returns false.
TEST(PlatformFeature, IsSupported_Unsupported) {
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
base::FilePath foo = temp_dir.GetPath().Append("foo");
ASSERT_TRUE(base::CreateDirectory(foo));
EXPECT_TRUE(base::PathExists(foo));
base::FilePath bar = temp_dir.GetPath().Append("bar");
EXPECT_FALSE(base::PathExists(bar));
auto exist_foo = std::make_unique<FileExistsCommand>(foo.value());
auto exist_bar = std::make_unique<FileExistsCommand>(bar.value());
std::vector<std::unique_ptr<SupportCheckCommand>> commands;
// This has to be first to make sure we short circuit
commands.push_back(std::move(exist_bar));
commands.push_back(std::move(exist_foo));
PlatformFeature features("foo", std::move(commands), {});
EXPECT_FALSE(features.IsSupported());
}
// Verify that platform-features.json parses and has a feature used for
// integration tests.
TEST(JsonParser, PlatformFeaturesJsonParses) {
const char kFeatureFileName[] = "share/platform-features.json";
base::FilePath platform_features =
base::FilePath(getenv("SRC")).Append(kFeatureFileName);
std::string contents;
ASSERT_TRUE(base::ReadFileToString(platform_features, &contents));
JsonFeatureParser parser;
ASSERT_FALSE(parser.AreFeaturesParsed());
ASSERT_TRUE(parser.ParseFileContents(contents));
ASSERT_TRUE(parser.AreFeaturesParsed());
const FeatureParserBase::FeatureMap* map = parser.GetFeatureMap();
auto it = map->find("CrOSLateBootTestFeature");
ASSERT_NE(it, map->end());
EXPECT_EQ(it->second.name(), "CrOSLateBootTestFeature");
std::vector<std::string> support_cmds =
it->second.SupportCheckCommandNamesForTesting();
EXPECT_THAT(support_cmds, ElementsAre("FileExists"));
std::vector<std::string> exec_cmds = it->second.ExecCommandNamesForTesting();
EXPECT_THAT(exec_cmds, ElementsAre("WriteFile"));
}
// Verify that the json parsing succeeds in a basic case.
TEST(JsonParser, Success_Basic) {
std::string contents = R"([{
"name": "foo",
"support_check_commands": [{"name": "FileExists", "path": "/a/b"}],
"commands" : [{"name": "Mkdir", "path": "/c/d"}]
}])";
JsonFeatureParser parser;
ASSERT_TRUE(parser.ParseFileContents(contents));
ASSERT_TRUE(parser.AreFeaturesParsed());
}
// Verify that the json parsing succeeds without support_check_commands.
TEST(JsonParser, Success_NoSupportCommands) {
std::string contents = R"([{
"name": "foo",
"commands" : [{"name": "Mkdir", "path": "/a/b"}]
}])";
JsonFeatureParser parser;
ASSERT_TRUE(parser.ParseFileContents(contents));
ASSERT_TRUE(parser.AreFeaturesParsed());
}
// Verify that the json parsing succeeds with multiple commands.
TEST(JsonParser, Success_MultiCommands) {
std::string contents = R"([{
"name": "foo",
"support_check_commands": [{"name": "FileExists", "path": "/a/b"}],
"commands" : [{"name": "Mkdir", "path": "/c/d"},
{"name": "Mkdir", "path": "/e/f"}]
}])";
JsonFeatureParser parser;
ASSERT_TRUE(parser.ParseFileContents(contents));
ASSERT_TRUE(parser.AreFeaturesParsed());
}
// Verify that the json parsing succeeds with multiple support-check commands.
TEST(JsonParser, Success_MultiSupportChecks) {
std::string contents = R"([{
"name": "foo",
"support_check_commands": [{"name": "FileExists", "path": "/a/b"},
{"name": "FileNotExists", "path": "/c/d"}],
"commands" : [{"name": "Mkdir", "path": "/e/f"},
{"name": "Mkdir", "path": "/g/h"}]
}])";
JsonFeatureParser parser;
ASSERT_TRUE(parser.ParseFileContents(contents));
ASSERT_TRUE(parser.AreFeaturesParsed());
}
// Verify that invalid json doesn't parse.
TEST(JsonParser, Invalid_JsonParse) {
std::string contents = "{";
JsonFeatureParser parser;
ASSERT_FALSE(parser.ParseFileContents(contents));
ASSERT_FALSE(parser.AreFeaturesParsed());
}
// Verify that json that isn't a list at the top level doesn't parse.
TEST(JsonParser, Invalid_NotList) {
std::string contents = "{}";
JsonFeatureParser parser;
ASSERT_FALSE(parser.ParseFileContents(contents));
ASSERT_FALSE(parser.AreFeaturesParsed());
}
// Verify that an empty list doesn't parse.
TEST(JsonParser, Invalid_EmptyList) {
std::string contents = "[]";
JsonFeatureParser parser;
ASSERT_FALSE(parser.ParseFileContents(contents));
ASSERT_FALSE(parser.AreFeaturesParsed());
}
// Verify that a list of something that isn't a dict doesn't parse.
TEST(JsonParser, Invalid_ListOfNotDict) {
std::string contents = "[1, 2]";
JsonFeatureParser parser;
ASSERT_FALSE(parser.ParseFileContents(contents));
ASSERT_FALSE(parser.AreFeaturesParsed());
}
// Verify that a feature missing a name doesn't parse.
TEST(JsonParser, Invalid_MissingFeatureName) {
std::string contents = R"([{
"notName": "foo",
"commands" : [{"name": "WriteFile", "path": "/a/b", "value": "1"}]
}])";
JsonFeatureParser parser;
ASSERT_FALSE(parser.ParseFileContents(contents));
ASSERT_FALSE(parser.AreFeaturesParsed());
}
// Verify that a support_check_commands that isn't a list fails to parse.
TEST(JsonParser, Invalid_SupportCommandsNotList) {
std::string contents = R"([{
"name": "foo",
"support_check_commands": 1,
"commands" : [{"name": "WriteFile", "path": "/a/b", "value": "1"}]
}])";
JsonFeatureParser parser;
ASSERT_FALSE(parser.ParseFileContents(contents));
ASSERT_FALSE(parser.AreFeaturesParsed());
}
// Verify that a support_check_commands that isn't a list of dicts fails.
TEST(JsonParser, Invalid_SupportCommandsNotListOfDict) {
std::string contents = R"([{
"name": "foo",
"support_check_commands": [1],
"commands" : [{"name": "WriteFile", "path": "/a/b", "value": "1"}]
}])";
JsonFeatureParser parser;
ASSERT_FALSE(parser.ParseFileContents(contents));
ASSERT_FALSE(parser.AreFeaturesParsed());
}
// Verify that a support_check_commands that is missing a name fails.
TEST(JsonParser, Invalid_SupportCommandsNoName) {
std::string contents = R"([{
"name": "foo",
"support_check_commands": [{"notName": "foo", "path": "/a/b"}],
"commands" : [{"name": "WriteFile", "path": "/a/b", "value": "1"}]
}])";
JsonFeatureParser parser;
ASSERT_FALSE(parser.ParseFileContents(contents));
ASSERT_FALSE(parser.AreFeaturesParsed());
}
// Verify that a support_check_commands that is has an invalid name fails.
TEST(JsonParser, Invalid_SupportCommandsBadName) {
std::string contents = R"([{
"name": "foo",
"support_check_commands": [{"name": "foo", "path": "/a/b"}],
"commands" : [{"name": "WriteFile", "path": "/a/b", "value": "1"}]
}])";
JsonFeatureParser parser;
ASSERT_FALSE(parser.ParseFileContents(contents));
ASSERT_FALSE(parser.AreFeaturesParsed());
}
// Verify that a support_check_commands with FileExists and no path fails.
TEST(JsonParser, Invalid_SupportCommandsFileExistsNoPath) {
std::string contents = R"([{
"name": "foo",
"support_check_commands": [{"name": "FileExists"}],
"commands" : [{"name": "WriteFile", "path": "/a/b", "value": "1"}]
}])";
JsonFeatureParser parser;
ASSERT_FALSE(parser.ParseFileContents(contents));
ASSERT_FALSE(parser.AreFeaturesParsed());
}
// Verify that a support_check_commands with FileNotExists and no path fails.
TEST(JsonParser, Invalid_SupportCommandsFileNotExistsNoPath) {
std::string contents = R"([{
"name": "foo",
"support_check_commands": [{"name": "FileNotExists"}],
"commands" : [{"name": "WriteFile", "path": "/a/b", "value": "1"}]
}])";
JsonFeatureParser parser;
ASSERT_FALSE(parser.ParseFileContents(contents));
ASSERT_FALSE(parser.AreFeaturesParsed());
}
// Verify that a missing commands entry fails to parse.
TEST(JsonParser, Invalid_NoCommands) {
std::string contents = R"([{
"name": "foo",
"support_check_commands": [{"name": "FileExists", "path": "/a/b"}]
}])";
JsonFeatureParser parser;
ASSERT_FALSE(parser.ParseFileContents(contents));
ASSERT_FALSE(parser.AreFeaturesParsed());
}
// Verify that commands only parses if it's a list.
TEST(JsonParser, Invalid_CommandsInt) {
std::string contents = R"([{
"name": "foo",
"support_check_commands": [{"name": "FileExists", "path": "/a/b"}],
"commands": 1
}])";
JsonFeatureParser parser;
ASSERT_FALSE(parser.ParseFileContents(contents));
ASSERT_FALSE(parser.AreFeaturesParsed());
}
// Verify that commands only parses if it's a non-empty list.
TEST(JsonParser, Invalid_CommandsEmpty) {
std::string contents = R"([{
"name": "foo",
"support_check_commands": [{"name": "FileExists", "path": "/a/b"}],
"commands": []
}])";
JsonFeatureParser parser;
ASSERT_FALSE(parser.ParseFileContents(contents));
ASSERT_FALSE(parser.AreFeaturesParsed());
}
// Verify that commands only parses if it's a list of dicts
TEST(JsonParser, Invalid_CommandsNotDict) {
std::string contents = R"([{
"name": "foo",
"support_check_commands": [{"name": "FileExists", "path": "/a/b"}],
"commands": [1]
}])";
JsonFeatureParser parser;
ASSERT_FALSE(parser.ParseFileContents(contents));
ASSERT_FALSE(parser.AreFeaturesParsed());
}
// Verify that commands only parses if commands have names.
TEST(JsonParser, Invalid_CommandMissingName) {
std::string contents = R"([{
"name": "foo",
"support_check_commands": [{"name": "FileExists", "path": "/a/b"}],
"commands" : [{"notName": "WriteFile", "path": "/c/d", "value": "1"}]
}])";
JsonFeatureParser parser;
ASSERT_FALSE(parser.ParseFileContents(contents));
ASSERT_FALSE(parser.AreFeaturesParsed());
}
// Verify that commands only parses if commands have valid names.
TEST(JsonParser, Invalid_CommandInvalidName) {
std::string contents = R"([{
"name": "foo",
"support_check_commands": [{"name": "FileExists", "path": "/a/b"}],
"commands" : [{"name": "invalid", "path": "/c/d", "value": "1"}]
}])";
JsonFeatureParser parser;
ASSERT_FALSE(parser.ParseFileContents(contents));
ASSERT_FALSE(parser.AreFeaturesParsed());
}
// Verify that commands only parses if WriteFile has a path.
TEST(JsonParser, Invalid_CommandWriteFileNoPath) {
std::string contents = R"([{
"name": "foo",
"support_check_commands": [{"name": "FileExists", "path": "/a/b"}],
"commands" : [{"name": "WriteFile", "value": "1"}]
}])";
JsonFeatureParser parser;
ASSERT_FALSE(parser.ParseFileContents(contents));
ASSERT_FALSE(parser.AreFeaturesParsed());
}
// Verify that commands only parses if WriteFile has a value.
TEST(JsonParser, Invalid_CommandWriteFileNoValue) {
std::string contents = R"([{
"name": "foo",
"support_check_commands": [{"name": "FileExists", "path": "/a/b"}],
"commands" : [{"name": "WriteFile", "path": "/a/b"}]
}])";
JsonFeatureParser parser;
ASSERT_FALSE(parser.ParseFileContents(contents));
ASSERT_FALSE(parser.AreFeaturesParsed());
}
// Verify that commands only parses if WriteFile has a value.
TEST(JsonParser, Invalid_CommandMkdirNoPath) {
std::string contents = R"([{
"name": "foo",
"support_check_commands": [{"name": "FileExists", "path": "/a/b"}],
"commands" : [{"name": "Mkdir"}]
}])";
JsonFeatureParser parser;
ASSERT_FALSE(parser.ParseFileContents(contents));
ASSERT_FALSE(parser.AreFeaturesParsed());
}
// Verify that commands only parses if all commands are valid.
TEST(JsonParser, Invalid_OneCommand) {
std::string contents = R"([{
"name": "foo",
"support_check_commands": [{"name": "FileExists", "path": "/a/b"}],
"commands" : [{"name": "Mkdir", "path": "/c/d"}, {"name": "invalid"}]
}])";
JsonFeatureParser parser;
ASSERT_FALSE(parser.ParseFileContents(contents));
ASSERT_FALSE(parser.AreFeaturesParsed());
}
// Verify that the json only parses without duplicate names.
TEST(JsonParser, Invalid_DuplicateNames) {
std::string contents = R"([{
"name": "foo",
"support_check_commands": [{"name": "FileExists", "path": "/a/b"}],
"commands" : [{"name": "Mkdir", "path": "/c/d"}]
},
{
"name": "foo",
"support_check_commands": [{"name": "FileExists", "path": "/a/b"}],
"commands" : [{"name": "Mkdir", "path": "/c/d"}]
}])";
JsonFeatureParser parser;
ASSERT_FALSE(parser.ParseFileContents(contents));
ASSERT_FALSE(parser.AreFeaturesParsed());
}
} // namespace
// A base class to set up dbus objects, etc, needed for all tests.
// Outside of the anonymous namespace to allow `friend class` and access to
// the private HandleSeedFetched method.
class DbusFeaturedServiceTestBase : public testing::Test {
public:
DbusFeaturedServiceTestBase(
std::unique_ptr<MockStoreImpl> mock_store_impl,
std::unique_ptr<MockTmpStorageImpl> mock_tmp_storage_impl)
: mock_bus_(base::MakeRefCounted<dbus::MockBus>(dbus::Bus::Options{})),
path_(chromeos::kChromeFeaturesServicePath),
mock_proxy_(base::MakeRefCounted<dbus::MockObjectProxy>(
mock_bus_.get(), chromeos::kChromeFeaturesServiceName, path_)),
mock_exported_object_(base::MakeRefCounted<dbus::MockExportedObject>(
mock_bus_.get(), path_)) {
// This weird ownership structure is necessary to be able to run
// EXPECT_CALLS/ON_CALLS in individual tests. The DbusFeaturedService class
// will take ownership.
mock_store_impl_ = mock_store_impl.get();
mock_tmp_storage_impl_ = mock_tmp_storage_impl.get();
service_ = std::make_shared<DbusFeaturedService>(
std::move(mock_store_impl), std::move(mock_tmp_storage_impl));
ON_CALL(*mock_bus_, GetExportedObject(_))
.WillByDefault(Return(mock_exported_object_.get()));
ON_CALL(*mock_bus_, Connect()).WillByDefault(Return(true));
ON_CALL(*mock_bus_, GetObjectProxy(_, _))
.WillByDefault(Return(mock_proxy_.get()));
ON_CALL(*mock_bus_, RequestOwnershipAndBlock(_, _))
.WillByDefault(Return(true));
ON_CALL(*mock_exported_object_, ExportMethodAndBlock(_, _, _))
.WillByDefault(Return(true));
}
void TearDown() override {
mock_bus_->ShutdownAndBlock();
feature::PlatformFeatures::ShutdownForTesting();
}
protected:
void HandleSeedFetched(dbus::MethodCall* method_call,
dbus::ExportedObject::ResponseSender sender) {
service_->HandleSeedFetched(method_call, std::move(sender));
}
scoped_refptr<dbus::MockBus> mock_bus_;
dbus::ObjectPath path_;
scoped_refptr<dbus::MockObjectProxy> mock_proxy_;
scoped_refptr<dbus::MockExportedObject> mock_exported_object_;
MockStoreImpl* mock_store_impl_;
MockTmpStorageImpl* mock_tmp_storage_impl_;
std::shared_ptr<DbusFeaturedService> service_;
};
class DbusFeaturedServiceTest : public DbusFeaturedServiceTestBase {
public:
DbusFeaturedServiceTest()
: DbusFeaturedServiceTestBase(std::make_unique<MockStoreImpl>(),
std::make_unique<MockTmpStorageImpl>()) {
ON_CALL(*mock_store_impl_, SetLastGoodSeed(_)).WillByDefault(Return(true));
ON_CALL(*mock_store_impl_, ClearBootAttemptsSinceLastUpdate())
.WillByDefault(Return(true));
}
};
// Checks that service start successfully increments the boot attempts counter
// on boot.
TEST_F(DbusFeaturedServiceTest, IncrementBootAttemptsOnStartup_Success) {
EXPECT_CALL(*mock_store_impl_, IncrementBootAttemptsSinceLastUpdate())
.WillOnce(Return(true));
EXPECT_TRUE(service_->Start(mock_bus_.get(), service_));
}
// Checks that service start fails when incrementing the boot attempts counter
// on boot fails.
TEST_F(DbusFeaturedServiceTest, IncrementBootAttemptsOnStartup_Failure) {
EXPECT_CALL(*mock_store_impl_, IncrementBootAttemptsSinceLastUpdate())
.WillOnce(Return(false));
EXPECT_FALSE(service_->Start(mock_bus_.get(), service_));
}
// Checks that an empty response is returned on success, and that the store's
// SetLastGoodSeed method is called when the used seed matches safe seed.
TEST_F(DbusFeaturedServiceTest, HandleSeedFetched_Success_MatchedSeed) {
constexpr char kExpectedMessage[] = R"--(message_type: MESSAGE_METHOD_RETURN
reply_serial: 123
)--";
SeedDetails used;
used.set_compressed_data("fake");
EXPECT_CALL(*mock_tmp_storage_impl_, GetUsedSeedDetails())
.WillOnce(Return(used));
EXPECT_CALL(*mock_store_impl_, SetLastGoodSeed(_)).WillOnce(Return(true));
dbus::MethodCall method_call("com.example.Interface", "SomeMethod");
dbus::MessageWriter writer(&method_call);
// Should match |used|.
SeedDetails seed;
seed.set_compressed_data("fake");
writer.AppendProtoAsArrayOfBytes(seed);
// Not setting the serial causes a crash.
method_call.SetSerial(123);
HandleSeedFetched(&method_call,
base::BindOnce(&ResponseSenderCallback, kExpectedMessage));
}
// Checks that an empty response is returned on success, and that the store's
// SetLastGoodSeed method isn't called when used seed doesn't match safe seed.
TEST_F(DbusFeaturedServiceTest, HandleSeedFetched_Success_MismatchedSeed) {
constexpr char kExpectedMessage[] = R"--(message_type: MESSAGE_METHOD_RETURN
reply_serial: 123
)--";
SeedDetails used;
used.set_compressed_data("fake");
EXPECT_CALL(*mock_tmp_storage_impl_, GetUsedSeedDetails())
.WillOnce(Return(used));
EXPECT_CALL(*mock_store_impl_, SetLastGoodSeed(_)).Times(0);
dbus::MethodCall method_call("com.example.Interface", "SomeMethod");
dbus::MessageWriter writer(&method_call);
// Should be different than |used|.
SeedDetails seed;
seed.set_compressed_data("different");
writer.AppendProtoAsArrayOfBytes(seed);
// Not setting the serial causes a crash.
method_call.SetSerial(123);
HandleSeedFetched(&method_call,
base::BindOnce(&ResponseSenderCallback, kExpectedMessage));
}
// Checks that HandleSeedFetched returns an error response when no arguments are
// passed in.
TEST_F(DbusFeaturedServiceTest, HandleSeedFetched_Failure_NoArgument) {
constexpr char kExpectedMessage[] = R"--(message_type: MESSAGE_ERROR
error_name: org.freedesktop.DBus.Error.InvalidArgs
signature: s
reply_serial: 123
string "Could not parse seed argument"
)--";
dbus::MethodCall method_call("com.example.Interface", "SomeMethod");
// Not setting the serial causes a crash.
method_call.SetSerial(123);
HandleSeedFetched(&method_call,
base::BindOnce(&ResponseSenderCallback, kExpectedMessage));
}
// Checks that HandleSeedFetched returns an error response when a non-seed
// argument is passed in.
TEST_F(DbusFeaturedServiceTest, HandleSeedFetched_Failure_InvalidArgument) {
constexpr char kExpectedMessage[] = R"--(message_type: MESSAGE_ERROR
error_name: org.freedesktop.DBus.Error.InvalidArgs
signature: s
reply_serial: 123
string "Could not parse seed argument"
)--";
dbus::MethodCall method_call("com.example.Interface", "SomeMethod");
dbus::MessageWriter writer(&method_call);
writer.AppendString("string");
// Not setting the serial causes a crash.
method_call.SetSerial(123);
HandleSeedFetched(&method_call,
base::BindOnce(&ResponseSenderCallback, kExpectedMessage));
}
// Checks that HandleSeedFetched returns an error response when saving the seed
// to disk fails.
TEST_F(DbusFeaturedServiceTest, HandleSeedFetched_Failure_SetSeedFailure) {
EXPECT_CALL(*mock_store_impl_, SetLastGoodSeed(_)).WillOnce(Return(false));
constexpr char kExpectedMessage[] = R"--(message_type: MESSAGE_ERROR
error_name: org.freedesktop.DBus.Error.Failed
signature: s
reply_serial: 123
string "Failed to write fetched seed to disk"
)--";
dbus::MethodCall method_call("com.example.Interface", "SomeMethod");
dbus::MessageWriter writer(&method_call);
SeedDetails seed;
writer.AppendProtoAsArrayOfBytes(seed);
// Not setting the serial causes a crash.
method_call.SetSerial(123);
HandleSeedFetched(&method_call,
base::BindOnce(&ResponseSenderCallback, kExpectedMessage));
}
// Checks that HandleSeedFetched returns an error response when saving the seed
// to disk fails.
TEST_F(DbusFeaturedServiceTest,
HandleSeedFetched_Failure_ClearBootCounterFailure) {
EXPECT_CALL(*mock_store_impl_, ClearBootAttemptsSinceLastUpdate())
.WillOnce(Return(false));
constexpr char kExpectedMessage[] = R"--(message_type: MESSAGE_ERROR
error_name: org.freedesktop.DBus.Error.Failed
signature: s
reply_serial: 123
string "Failed to reset boot attempts counter"
)--";
dbus::MethodCall method_call("com.example.Interface", "SomeMethod");
dbus::MessageWriter writer(&method_call);
SeedDetails seed;
writer.AppendProtoAsArrayOfBytes(seed);
// Not setting the serial causes a crash.
method_call.SetSerial(123);
HandleSeedFetched(&method_call,
base::BindOnce(&ResponseSenderCallback, kExpectedMessage));
}
class DbusFeaturedServiceNoLockboxTest : public DbusFeaturedServiceTestBase {
public:
DbusFeaturedServiceNoLockboxTest()
: DbusFeaturedServiceTestBase(nullptr, nullptr) {}
};
TEST_F(DbusFeaturedServiceNoLockboxTest, Startup_Success) {
EXPECT_TRUE(service_->Start(mock_bus_.get(), service_));
}
// Checks that an empty response is returned on missing store.
TEST_F(DbusFeaturedServiceNoLockboxTest, HandleSeedFetched_Success) {
constexpr char kExpectedMessage[] = R"--(message_type: MESSAGE_METHOD_RETURN
reply_serial: 123
)--";
dbus::MethodCall method_call("com.example.Interface", "SomeMethod");
dbus::MessageWriter writer(&method_call);
SeedDetails seed;
writer.AppendProtoAsArrayOfBytes(seed);
// Not setting the serial causes a crash.
method_call.SetSerial(123);
HandleSeedFetched(&method_call,
base::BindOnce(&ResponseSenderCallback, kExpectedMessage));
}
} // namespace featured