blob: 727c1370fd7e73e58149a6007945d7824d0aa369 [file] [log] [blame]
// Copyright 2021 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 <memory>
#include <utility>
#include <base/dcheck_is_on.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/run_loop.h>
#include <base/task/single_thread_task_runner.h>
#include <base/test/bind.h>
#include <base/test/task_environment.h>
#include <chromeos/dbus/service_constants.h>
#include <dbus/message.h>
#include <dbus/mock_bus.h>
#include <dbus/mock_object_proxy.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "featured/feature_library.h"
#include "featured/service.h"
namespace {
using ::testing::_;
using ::testing::Invoke;
using ::testing::Return;
} // namespace
namespace feature {
class FeatureLibraryTest : public testing::Test {
protected:
FeatureLibraryTest()
: mock_bus_(new dbus::MockBus{dbus::Bus::Options{}}),
mock_proxy_(new dbus::MockObjectProxy(
mock_bus_.get(),
chromeos::kChromeFeaturesServiceName,
dbus::ObjectPath(chromeos::kChromeFeaturesServicePath))) {}
~FeatureLibraryTest() { mock_bus_->ShutdownAndBlock(); }
void SetUp() override {
features_ = std::unique_ptr<PlatformFeatures>(
new PlatformFeatures(mock_bus_, mock_proxy_.get()));
}
std::unique_ptr<dbus::Response> CreateResponse(dbus::MethodCall* call,
bool enabled) {
if (call->GetInterface() == "org.chromium.ChromeFeaturesServiceInterface" &&
call->GetMember() == "IsFeatureEnabled") {
std::unique_ptr<dbus::Response> response = dbus::Response::CreateEmpty();
dbus::MessageWriter writer(response.get());
writer.AppendBool(enabled);
return response;
}
LOG(ERROR) << "Unexpected method call " << call->ToString();
return nullptr;
}
base::test::SingleThreadTaskEnvironment task_environment_;
scoped_refptr<dbus::MockBus> mock_bus_;
scoped_refptr<dbus::MockObjectProxy> mock_proxy_;
std::unique_ptr<PlatformFeatures> features_;
std::unique_ptr<base::RunLoop> run_loop_;
};
// Parameterized tests, with a boolean indicating whether the feature should be
// enabled.
class FeatureLibraryParameterizedTest
: public FeatureLibraryTest,
public ::testing::WithParamInterface<bool> {};
INSTANTIATE_TEST_SUITE_P(FeatureLibraryParameterizedTest,
FeatureLibraryParameterizedTest,
testing::Values(true, false));
TEST_P(FeatureLibraryParameterizedTest, IsEnabled_Success) {
bool enabled = GetParam();
EXPECT_CALL(*mock_proxy_, DoWaitForServiceToBeAvailable(_))
.WillOnce(
Invoke([](dbus::MockObjectProxy::WaitForServiceToBeAvailableCallback*
callback) { std::move(*callback).Run(true); }));
EXPECT_CALL(*mock_proxy_,
DoCallMethod(_, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, _))
.WillOnce(Invoke([this, enabled](
dbus::MethodCall* call, int timeout_ms,
dbus::MockObjectProxy::ResponseCallback* callback) {
std::unique_ptr<dbus::Response> resp = CreateResponse(call, enabled);
std::move(*callback).Run(resp.get());
}));
run_loop_ = std::make_unique<base::RunLoop>();
VariationsFeature f{"Feature", FEATURE_DISABLED_BY_DEFAULT};
features_->IsEnabled(f,
base::BindLambdaForTesting([this, enabled](bool actual) {
EXPECT_EQ(enabled, actual);
run_loop_->Quit();
}));
run_loop_->Run();
}
TEST_P(FeatureLibraryParameterizedTest, IsEnabled_Failure_WaitForService) {
EXPECT_CALL(*mock_proxy_, DoWaitForServiceToBeAvailable(_))
.WillOnce(
Invoke([](dbus::MockObjectProxy::WaitForServiceToBeAvailableCallback*
callback) { std::move(*callback).Run(false); }));
EXPECT_CALL(*mock_proxy_,
DoCallMethod(_, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, _))
.Times(0);
run_loop_ = std::make_unique<base::RunLoop>();
bool enabled = GetParam();
FeatureState feature_state =
GetParam() ? FEATURE_ENABLED_BY_DEFAULT : FEATURE_DISABLED_BY_DEFAULT;
VariationsFeature f{"Feature", feature_state};
features_->IsEnabled(f,
base::BindLambdaForTesting([this, enabled](bool actual) {
EXPECT_EQ(enabled, actual);
run_loop_->Quit();
}));
run_loop_->Run();
}
TEST_P(FeatureLibraryParameterizedTest, IsEnabled_Failure_NullResponse) {
EXPECT_CALL(*mock_proxy_, DoWaitForServiceToBeAvailable(_))
.WillOnce(
Invoke([](dbus::MockObjectProxy::WaitForServiceToBeAvailableCallback*
callback) { std::move(*callback).Run(true); }));
EXPECT_CALL(*mock_proxy_,
DoCallMethod(_, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, _))
.WillOnce(Invoke([](dbus::MethodCall* call, int timeout_ms,
dbus::MockObjectProxy::ResponseCallback* callback) {
std::move(*callback).Run(nullptr);
}));
run_loop_ = std::make_unique<base::RunLoop>();
bool enabled = GetParam();
FeatureState feature_state =
GetParam() ? FEATURE_ENABLED_BY_DEFAULT : FEATURE_DISABLED_BY_DEFAULT;
VariationsFeature f{"Feature", feature_state};
features_->IsEnabled(f,
base::BindLambdaForTesting([this, enabled](bool actual) {
EXPECT_EQ(enabled, actual);
run_loop_->Quit();
}));
run_loop_->Run();
}
TEST_P(FeatureLibraryParameterizedTest, IsEnabled_Failure_EmptyResponse) {
EXPECT_CALL(*mock_proxy_, DoWaitForServiceToBeAvailable(_))
.WillOnce(
Invoke([](dbus::MockObjectProxy::WaitForServiceToBeAvailableCallback*
callback) { std::move(*callback).Run(true); }));
std::unique_ptr<dbus::Response> response = dbus::Response::CreateEmpty();
EXPECT_CALL(*mock_proxy_,
DoCallMethod(_, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, _))
.WillOnce(Invoke(
[&response](dbus::MethodCall* call, int timeout_ms,
dbus::MockObjectProxy::ResponseCallback* callback) {
std::move(*callback).Run(response.get());
}));
run_loop_ = std::make_unique<base::RunLoop>();
bool enabled = GetParam();
FeatureState feature_state =
GetParam() ? FEATURE_ENABLED_BY_DEFAULT : FEATURE_DISABLED_BY_DEFAULT;
VariationsFeature f{"Feature", feature_state};
features_->IsEnabled(f,
base::BindLambdaForTesting([this, enabled](bool actual) {
EXPECT_EQ(enabled, actual);
run_loop_->Quit();
}));
run_loop_->Run();
}
TEST_P(FeatureLibraryParameterizedTest, IsEnabledBlocking_Success) {
bool enabled = GetParam();
EXPECT_CALL(*mock_proxy_,
CallMethodAndBlock(_, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.WillOnce(Invoke([this, enabled](dbus::MethodCall* call, int timeout_ms) {
return CreateResponse(call, enabled);
}));
VariationsFeature f{"Feature", FEATURE_DISABLED_BY_DEFAULT};
EXPECT_EQ(enabled, features_->IsEnabledBlocking(f));
}
TEST_P(FeatureLibraryParameterizedTest, IsEnabledBlocking_Failure_Null) {
EXPECT_CALL(*mock_proxy_,
CallMethodAndBlock(_, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.WillOnce(Invoke(
[](dbus::MethodCall* call, int timeout_ms) { return nullptr; }));
bool enabled = GetParam();
FeatureState feature_state =
GetParam() ? FEATURE_ENABLED_BY_DEFAULT : FEATURE_DISABLED_BY_DEFAULT;
VariationsFeature f{"Feature", feature_state};
EXPECT_EQ(enabled, features_->IsEnabledBlocking(f));
}
TEST_P(FeatureLibraryParameterizedTest, IsEnabledBlocking_Failure_Empty) {
EXPECT_CALL(*mock_proxy_,
CallMethodAndBlock(_, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.WillOnce(Invoke([](dbus::MethodCall* call, int timeout_ms) {
return dbus::Response::CreateEmpty();
}));
bool enabled = GetParam();
FeatureState feature_state =
GetParam() ? FEATURE_ENABLED_BY_DEFAULT : FEATURE_DISABLED_BY_DEFAULT;
VariationsFeature f{"Feature", feature_state};
EXPECT_EQ(enabled, features_->IsEnabledBlocking(f));
}
TEST_F(FeatureLibraryTest, CheckFeatureIdentity) {
VariationsFeature f1{"Feature", FEATURE_ENABLED_BY_DEFAULT};
// A new, unseen feature should pass the check.
EXPECT_TRUE(features_->CheckFeatureIdentity(f1));
// As should a feature seen a second time.
EXPECT_TRUE(features_->CheckFeatureIdentity(f1));
VariationsFeature f2{"Feature", FEATURE_ENABLED_BY_DEFAULT};
// A separate feature with the same name should fail.
EXPECT_FALSE(features_->CheckFeatureIdentity(f2));
VariationsFeature f3{"Feature3", FEATURE_ENABLED_BY_DEFAULT};
// A distinct feature with a distinct name should pass.
EXPECT_TRUE(features_->CheckFeatureIdentity(f3));
EXPECT_TRUE(features_->CheckFeatureIdentity(f3));
}
#if DCHECK_IS_ON()
using FeatureLibraryDeathTest = FeatureLibraryTest;
TEST_F(FeatureLibraryDeathTest, IsEnabledDistinctFeatureDefs) {
EXPECT_CALL(*mock_proxy_, DoWaitForServiceToBeAvailable(_))
.WillOnce(
Invoke([](dbus::MockObjectProxy::WaitForServiceToBeAvailableCallback*
callback) { std::move(*callback).Run(false); }));
EXPECT_CALL(*mock_proxy_,
DoCallMethod(_, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, _))
.Times(0);
run_loop_ = std::make_unique<base::RunLoop>();
VariationsFeature f{"Feature", FEATURE_ENABLED_BY_DEFAULT};
features_->IsEnabled(f, base::BindLambdaForTesting([this](bool enabled) {
EXPECT_TRUE(enabled); // Default value
run_loop_->Quit();
}));
run_loop_->Run();
VariationsFeature f2{"Feature", FEATURE_ENABLED_BY_DEFAULT};
EXPECT_DEATH(
features_->IsEnabled(f2, base::BindLambdaForTesting([this](bool enabled) {
EXPECT_TRUE(enabled); // Default value
run_loop_->Quit();
})),
"Feature");
}
TEST_F(FeatureLibraryDeathTest, IsEnabledBlockingDistinctFeatureDefs) {
EXPECT_CALL(*mock_proxy_,
CallMethodAndBlock(_, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.Times(1);
VariationsFeature f{"Feature", FEATURE_ENABLED_BY_DEFAULT};
features_->IsEnabledBlocking(f);
VariationsFeature f2{"Feature", FEATURE_ENABLED_BY_DEFAULT};
EXPECT_DEATH(features_->IsEnabledBlocking(f2), "Feature");
}
#endif // DCHECK_IS_ON()
class FeatureLibraryCmdTest : public testing::Test {
public:
FeatureLibraryCmdTest() {}
~FeatureLibraryCmdTest() {}
};
TEST_F(FeatureLibraryCmdTest, MkdirTest) {
if (base::PathExists(
base::FilePath("/sys/kernel/debug/tracing/instances/"))) {
const std::string sys_path = "/sys/kernel/debug/tracing/instances/unittest";
EXPECT_FALSE(base::PathExists(base::FilePath(sys_path)));
EXPECT_TRUE(featured::MkdirCommand(sys_path).Execute());
EXPECT_TRUE(base::PathExists(base::FilePath(sys_path)));
EXPECT_TRUE(base::DeleteFile(base::FilePath(sys_path)));
EXPECT_FALSE(base::PathExists(base::FilePath(sys_path)));
}
if (base::PathExists(base::FilePath("/mnt"))) {
const std::string mnt_path = "/mnt/notallowed";
EXPECT_FALSE(base::PathExists(base::FilePath(mnt_path)));
EXPECT_FALSE(featured::MkdirCommand(mnt_path).Execute());
EXPECT_FALSE(base::PathExists(base::FilePath(mnt_path)));
}
}
} // namespace feature