blob: 9d6ad93d2e4849cc0c1bc911b6d440f041017fc6 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/feature_engagement/internal/blocked_iph_features.h"
#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/strings/string_util.h"
#include "base/synchronization/lock.h"
#include "components/feature_engagement/test/scoped_iph_feature_list.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace feature_engagement {
namespace {
BASE_FEATURE(kBlockedIphFeaturesTestFeature1,
"BlockedIphFeaturesTestFeature1",
base::FEATURE_DISABLED_BY_DEFAULT);
BASE_FEATURE(kBlockedIphFeaturesTestFeature2,
"BlockedIphFeaturesTestFeature2",
base::FEATURE_ENABLED_BY_DEFAULT);
BASE_FEATURE(kBlockedIphFeaturesTestFeature3,
"BlockedIphFeaturesTestFeature3",
base::FEATURE_DISABLED_BY_DEFAULT);
} // namespace
class BlockedIphFeaturesTest : public testing::Test {
public:
void SetUp() override { ClearData(); }
void TearDown() override {
{
auto* const allowed = BlockedIphFeatures::GetInstance();
base::AutoLock lock(allowed->GetLock());
EXPECT_EQ(expect_refcount_on_teardown_,
allowed->global_block_count_ != 0);
}
// Clear out the map data so it doesn't pollute later tests.
ClearData();
}
void ExpectRefCountOnTeardown() { expect_refcount_on_teardown_ = true; }
static const char* GetCommandLineSwitch() {
return BlockedIphFeatures::kPropagateIPHForTestingSwitch;
}
private:
void ClearData() {
auto* const allowed = BlockedIphFeatures::GetInstance();
base::AutoLock lock(allowed->GetLock());
allowed->read_from_command_line_ = false;
allowed->global_block_count_ = 0U;
allowed->allow_feature_counts_.clear();
}
bool expect_refcount_on_teardown_ = false;
};
TEST_F(BlockedIphFeaturesTest, IsFeatureBlockedDefaultValue) {
auto* const blocked = BlockedIphFeatures::GetInstance();
base::AutoLock lock(blocked->GetLock());
// Allowed should be irrespective of enabled or disabled.
EXPECT_FALSE(blocked->AreAnyFeaturesBlocked());
EXPECT_FALSE(blocked->IsFeatureBlocked(kBlockedIphFeaturesTestFeature1.name));
EXPECT_FALSE(blocked->IsFeatureBlocked(kBlockedIphFeaturesTestFeature2.name));
}
TEST_F(BlockedIphFeaturesTest, IsFeatureBlockedWithEmptyScope) {
{
test::ScopedIphFeatureList list;
list.InitWithNoFeaturesAllowed();
// Blocked should be irrespective of enabled or disabled.
auto* const blocked = BlockedIphFeatures::GetInstance();
base::AutoLock lock(blocked->GetLock());
EXPECT_TRUE(blocked->AreAnyFeaturesBlocked());
EXPECT_TRUE(
blocked->IsFeatureBlocked(kBlockedIphFeaturesTestFeature1.name));
EXPECT_TRUE(
blocked->IsFeatureBlocked(kBlockedIphFeaturesTestFeature2.name));
}
{
// Now no one is blocking IPH.
auto* const blocked = BlockedIphFeatures::GetInstance();
base::AutoLock lock(blocked->GetLock());
EXPECT_FALSE(blocked->AreAnyFeaturesBlocked());
EXPECT_FALSE(
blocked->IsFeatureBlocked(kBlockedIphFeaturesTestFeature1.name));
EXPECT_FALSE(
blocked->IsFeatureBlocked(kBlockedIphFeaturesTestFeature2.name));
}
}
TEST_F(BlockedIphFeaturesTest, IsFeatureBlockedInnerScopeHasException) {
test::ScopedIphFeatureList list1;
list1.InitWithNoFeaturesAllowed();
{
test::ScopedIphFeatureList list2;
list2.InitAndEnableFeatures({kBlockedIphFeaturesTestFeature1});
auto* const blocked = BlockedIphFeatures::GetInstance();
base::AutoLock lock(blocked->GetLock());
// This feature is explicitly allowed.
EXPECT_FALSE(
blocked->IsFeatureBlocked(kBlockedIphFeaturesTestFeature1.name));
EXPECT_TRUE(
blocked->IsFeatureBlocked(kBlockedIphFeaturesTestFeature2.name));
}
{
// Now no features are allowed.
auto* const blocked = BlockedIphFeatures::GetInstance();
base::AutoLock lock(blocked->GetLock());
EXPECT_TRUE(
blocked->IsFeatureBlocked(kBlockedIphFeaturesTestFeature1.name));
EXPECT_TRUE(
blocked->IsFeatureBlocked(kBlockedIphFeaturesTestFeature2.name));
}
}
TEST_F(BlockedIphFeaturesTest, IsFeatureBlockedOuterScopeHasException) {
test::ScopedIphFeatureList list1;
list1.InitAndEnableFeatures({kBlockedIphFeaturesTestFeature1});
{
test::ScopedIphFeatureList list2;
list2.InitWithNoFeaturesAllowed();
auto* const blocked = BlockedIphFeatures::GetInstance();
base::AutoLock lock(blocked->GetLock());
// This feature is explicitly allowed.
EXPECT_FALSE(
blocked->IsFeatureBlocked(kBlockedIphFeaturesTestFeature1.name));
EXPECT_TRUE(
blocked->IsFeatureBlocked(kBlockedIphFeaturesTestFeature2.name));
}
{
// Allowed feature should persist.
auto* const blocked = BlockedIphFeatures::GetInstance();
base::AutoLock lock(blocked->GetLock());
EXPECT_FALSE(
blocked->IsFeatureBlocked(kBlockedIphFeaturesTestFeature1.name));
EXPECT_TRUE(
blocked->IsFeatureBlocked(kBlockedIphFeaturesTestFeature2.name));
}
}
TEST_F(BlockedIphFeaturesTest, IsFeatureBlockedMultipleExceptions) {
test::ScopedIphFeatureList list1;
list1.InitAndEnableFeatures({kBlockedIphFeaturesTestFeature1});
{
test::ScopedIphFeatureList list2;
list2.InitAndEnableFeatures({kBlockedIphFeaturesTestFeature2});
auto* const blocked = BlockedIphFeatures::GetInstance();
base::AutoLock lock(blocked->GetLock());
// Both features are allowed.
EXPECT_FALSE(
blocked->IsFeatureBlocked(kBlockedIphFeaturesTestFeature1.name));
EXPECT_FALSE(
blocked->IsFeatureBlocked(kBlockedIphFeaturesTestFeature2.name));
}
{
// Outer allowed feature should persist.
auto* const blocked = BlockedIphFeatures::GetInstance();
base::AutoLock lock(blocked->GetLock());
EXPECT_FALSE(
blocked->IsFeatureBlocked(kBlockedIphFeaturesTestFeature1.name));
EXPECT_TRUE(
blocked->IsFeatureBlocked(kBlockedIphFeaturesTestFeature2.name));
}
}
TEST_F(BlockedIphFeaturesTest, MaybeWriteToCommandLineWithNoBlocking) {
auto* const blocked = BlockedIphFeatures::GetInstance();
base::AutoLock lock(blocked->GetLock());
base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
blocked->MaybeWriteToCommandLine(command_line);
EXPECT_FALSE(command_line.HasSwitch(GetCommandLineSwitch()));
EXPECT_FALSE(command_line.HasSwitch(switches::kEnableFeatures));
}
TEST_F(BlockedIphFeaturesTest, MaybeWriteToCommandLineBlockingAllIph) {
test::ScopedIphFeatureList list;
list.InitWithNoFeaturesAllowed();
auto* const blocked = BlockedIphFeatures::GetInstance();
base::AutoLock lock(blocked->GetLock());
base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
blocked->MaybeWriteToCommandLine(command_line);
EXPECT_TRUE(command_line.HasSwitch(GetCommandLineSwitch()));
EXPECT_TRUE(command_line.GetSwitchValueASCII(GetCommandLineSwitch()).empty());
EXPECT_FALSE(command_line.HasSwitch(switches::kEnableFeatures));
}
TEST_F(BlockedIphFeaturesTest, MaybeWriteToCommandLineBlockingAllButOneIph) {
test::ScopedIphFeatureList list;
list.InitAndEnableFeatures({kBlockedIphFeaturesTestFeature1});
auto* const blocked = BlockedIphFeatures::GetInstance();
base::AutoLock lock(blocked->GetLock());
base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
blocked->MaybeWriteToCommandLine(command_line);
EXPECT_TRUE(command_line.HasSwitch(GetCommandLineSwitch()));
EXPECT_EQ(kBlockedIphFeaturesTestFeature1.name,
command_line.GetSwitchValueASCII(GetCommandLineSwitch()));
EXPECT_TRUE(command_line.HasSwitch(switches::kEnableFeatures));
EXPECT_EQ(kBlockedIphFeaturesTestFeature1.name,
command_line.GetSwitchValueASCII(switches::kEnableFeatures));
}
TEST_F(BlockedIphFeaturesTest, MaybeWriteToCommandLineBlockingAllButTwoIph) {
test::ScopedIphFeatureList list;
list.InitAndEnableFeatures(
{kBlockedIphFeaturesTestFeature1, kBlockedIphFeaturesTestFeature2});
auto* const blocked = BlockedIphFeatures::GetInstance();
base::AutoLock lock(blocked->GetLock());
base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
blocked->MaybeWriteToCommandLine(command_line);
const std::string expected =
base::JoinString({kBlockedIphFeaturesTestFeature1.name,
kBlockedIphFeaturesTestFeature2.name},
",");
EXPECT_TRUE(command_line.HasSwitch(GetCommandLineSwitch()));
EXPECT_EQ(expected, command_line.GetSwitchValueASCII(GetCommandLineSwitch()));
EXPECT_TRUE(command_line.HasSwitch(switches::kEnableFeatures));
EXPECT_EQ(expected,
command_line.GetSwitchValueASCII(switches::kEnableFeatures));
}
TEST_F(BlockedIphFeaturesTest,
MaybeWriteToCommandLinePreviouslyHadEnabledFeatures) {
test::ScopedIphFeatureList list;
list.InitAndEnableFeatures({kBlockedIphFeaturesTestFeature2});
auto* const blocked = BlockedIphFeatures::GetInstance();
base::AutoLock lock(blocked->GetLock());
base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
// Add feature 1 to the general enabled list.
command_line.AppendSwitchASCII(switches::kEnableFeatures,
kBlockedIphFeaturesTestFeature1.name);
blocked->MaybeWriteToCommandLine(command_line);
// Only the explicitly allowed IPH should be present in the BlockedIphFeatures
// command-line switch.
EXPECT_TRUE(command_line.HasSwitch(GetCommandLineSwitch()));
EXPECT_EQ(kBlockedIphFeaturesTestFeature2.name,
command_line.GetSwitchValueASCII(GetCommandLineSwitch()));
// Both values should be in the final enabled features argument.
EXPECT_TRUE(command_line.HasSwitch(switches::kEnableFeatures));
const std::string expected =
base::JoinString({kBlockedIphFeaturesTestFeature1.name,
kBlockedIphFeaturesTestFeature2.name},
",");
EXPECT_EQ(expected,
command_line.GetSwitchValueASCII(switches::kEnableFeatures));
}
TEST_F(BlockedIphFeaturesTest, ReadFromCommandLineBlockAll) {
ExpectRefCountOnTeardown();
// Append a switch that indicates blocked IPH with no exceptions.
base::CommandLine::ForCurrentProcess()->AppendSwitch(GetCommandLineSwitch());
auto* const blocked = BlockedIphFeatures::GetInstance();
base::AutoLock lock(blocked->GetLock());
EXPECT_TRUE(blocked->IsFeatureBlocked(kBlockedIphFeaturesTestFeature1.name));
EXPECT_TRUE(blocked->IsFeatureBlocked(kBlockedIphFeaturesTestFeature2.name));
}
TEST_F(BlockedIphFeaturesTest, ReadFromCommandLineAllowOneIph) {
ExpectRefCountOnTeardown();
// Append a switch that indicates blocked IPH with one exceptions.
base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
GetCommandLineSwitch(), kBlockedIphFeaturesTestFeature1.name);
auto* const blocked = BlockedIphFeatures::GetInstance();
base::AutoLock lock(blocked->GetLock());
EXPECT_FALSE(blocked->IsFeatureBlocked(kBlockedIphFeaturesTestFeature1.name));
EXPECT_TRUE(blocked->IsFeatureBlocked(kBlockedIphFeaturesTestFeature2.name));
}
TEST_F(BlockedIphFeaturesTest, ReadFromCommandLineAllowTwoIph) {
ExpectRefCountOnTeardown();
// Append a switch that indicates blocked IPH with two exceptions.
base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
GetCommandLineSwitch(),
base::JoinString({kBlockedIphFeaturesTestFeature1.name,
kBlockedIphFeaturesTestFeature2.name},
","));
auto* const blocked = BlockedIphFeatures::GetInstance();
base::AutoLock lock(blocked->GetLock());
EXPECT_FALSE(blocked->IsFeatureBlocked(kBlockedIphFeaturesTestFeature1.name));
EXPECT_FALSE(blocked->IsFeatureBlocked(kBlockedIphFeaturesTestFeature2.name));
// An unrelated IPH should still be blocked.
EXPECT_TRUE(blocked->IsFeatureBlocked(kBlockedIphFeaturesTestFeature3.name));
}
} // namespace feature_engagement