1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327
|
// Copyright 2011 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/about_flags.h"
#include <stddef.h>
#include <map>
#include <optional>
#include <set>
#include <string>
#include <utility>
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/test/metrics/histogram_enum_reader.h"
#include "build/build_config.h"
#include "chrome/common/chrome_version.h"
#include "components/webui/flags/feature_entry.h"
#include "components/webui/flags/feature_entry_macros.h"
#include "components/webui/flags/flags_test_helpers.h"
#include "components/webui/flags/flags_ui_metrics.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace about_flags {
namespace {
using Sample32 = base::HistogramBase::Sample32;
using SwitchToIdMap = std::map<std::string, Sample32>;
// Get all associated switches corresponding to defined about_flags.cc entries.
std::set<std::string> GetAllPublicSwitchesAndFeaturesForTesting() {
std::set<std::string> result;
for (const auto& entry : testing::GetFeatureEntries()) {
// Skip over flags that are part of the flags system itself - they don't
// have any of the usual metadata or histogram entries for flags, since they
// are synthesized during the build process.
// TODO(crbug.com/40125404): Remove the need for this by generating
// histogram entries automatically.
if (entry.supported_platforms & flags_ui::kFlagInfrastructure)
continue;
switch (entry.type) {
case flags_ui::FeatureEntry::SINGLE_VALUE:
case flags_ui::FeatureEntry::SINGLE_DISABLE_VALUE:
result.insert(entry.switches.command_line_switch);
break;
case flags_ui::FeatureEntry::ORIGIN_LIST_VALUE:
case flags_ui::FeatureEntry::STRING_VALUE:
// Do nothing, origin list values and string values are not added as
// feature flags.
break;
case flags_ui::FeatureEntry::MULTI_VALUE:
for (int j = 0; j < entry.NumOptions(); ++j) {
result.insert(entry.ChoiceForOption(j).command_line_switch);
}
break;
case flags_ui::FeatureEntry::ENABLE_DISABLE_VALUE:
result.insert(entry.switches.command_line_switch);
result.insert(entry.switches.disable_command_line_switch);
break;
case flags_ui::FeatureEntry::FEATURE_VALUE:
case flags_ui::FeatureEntry::FEATURE_WITH_PARAMS_VALUE:
result.insert(std::string(entry.feature.feature->name) + ":enabled");
result.insert(std::string(entry.feature.feature->name) + ":disabled");
break;
#if BUILDFLAG(IS_CHROMEOS)
case flags_ui::FeatureEntry::PLATFORM_FEATURE_NAME_VALUE:
case flags_ui::FeatureEntry::PLATFORM_FEATURE_NAME_WITH_PARAMS_VALUE:
std::string name(entry.platform_feature_name.name);
result.insert(name + ":enabled");
result.insert(name + ":disabled");
break;
#endif // BUILDFLAG(IS_CHROMEOS)
}
}
return result;
}
// Returns all variation ids defined in flags entries.
std::vector<std::string> GetAllVariationIds() {
std::vector<std::string> variation_ids;
for (const auto& entry : testing::GetFeatureEntries()) {
// Only FEATURE_WITH_PARAMS_VALUE or PLATFORM_FEATURE_NAME_WITH_PARAMS_VALUE
// entries can have a variation id.
if (entry.type != flags_ui::FeatureEntry::FEATURE_WITH_PARAMS_VALUE
#if BUILDFLAG(IS_CHROMEOS)
&& entry.type !=
flags_ui::FeatureEntry::PLATFORM_FEATURE_NAME_WITH_PARAMS_VALUE
#endif // BUILDFLAG(IS_CHROMEOS)
) {
continue;
}
for (const auto& variation : entry.GetVariations()) {
if (variation.variation_id)
variation_ids.push_back(variation.variation_id);
}
}
return variation_ids;
}
// Returns the parsed pair: <variation_id, is_triggering>.
std::pair<int, bool> ParseVariationId(const std::string& variation_str) {
// Fail if an empty string has been supplied as variation_id.
EXPECT_FALSE(variation_str.empty())
<< "Empty string used to denote variation ID. Use `nullptr` instead.";
int variation_id{};
bool is_triggering = variation_str[0] == 't';
// Fail if we could not process the integer value.
EXPECT_TRUE(
base::StringToInt(&variation_str[is_triggering ? 1 : 0], &variation_id))
<< "Invalid variation string: \"" << variation_str
<< "\": must be either `#######` or `t#######`";
return {variation_id, is_triggering};
}
} // namespace
// Makes sure there are no separators in any of the entry names.
TEST(AboutFlagsTest, NoSeparators) {
for (const auto& entry : testing::GetFeatureEntries()) {
const std::string name(entry.internal_name);
EXPECT_EQ(std::string::npos, name.find(flags_ui::testing::kMultiSeparator))
<< name;
}
}
// Makes sure that every flag has an owner and an expiry entry in
// flag-metadata.json.
TEST(AboutFlagsTest, EveryFlagHasMetadata) {
flags_ui::testing::EnsureEveryFlagHasMetadata(testing::GetFeatureEntries());
}
// Ensures that all flags marked as never expiring in flag-metadata.json is
// listed in flag-never-expire-list.json.
TEST(AboutFlagsTest, OnlyPermittedFlagsNeverExpire) {
flags_ui::testing::EnsureOnlyPermittedFlagsNeverExpire();
}
// Ensures that every flag has an owner.
TEST(AboutFlagsTest, EveryFlagHasNonEmptyOwners) {
flags_ui::testing::EnsureEveryFlagHasNonEmptyOwners();
}
// Ensures that owners conform to rules in flag-metadata.json.
TEST(AboutFlagsTest, OwnersLookValid) {
flags_ui::testing::EnsureOwnersLookValid();
}
// For some bizarre reason, far too many people see a file filled with
// alphabetically-ordered items and think "hey, let me drop this new item into a
// random location!" Prohibit such behavior in the flags files.
TEST(AboutFlagsTest, FlagsListedInAlphabeticalOrder) {
flags_ui::testing::EnsureFlagsAreListedInAlphabeticalOrder();
}
TEST(AboutFlagsTest, EveryFlagIsValid) {
for (const auto& entry : testing::GetFeatureEntries()) {
EXPECT_TRUE(entry.IsValid()) << entry.internal_name;
}
}
TEST(AboutFlagsTest, RecentUnexpireFlagsArePresent) {
flags_ui::testing::EnsureRecentUnexpireFlagsArePresent(
testing::GetFeatureEntries(), CHROME_VERSION_MAJOR);
}
// Ensures that all variation IDs specified are well-formed.
// - Variation IDs may be re-used, when multiple variants change client-side
// behavior alone.
// - Variation IDs must be associated with the appropriate pool of valid numbers
TEST(AboutFlagsTest, VariationIdsAreValid) {
std::set<int> nontriggering_variation_ids;
std::set<int> triggering_variation_ids;
// See: go/finch-allocating-gws-ids.
int LOWER_VALID_VARIATION_ID = 3340000;
int UPPER_VALID_VARIATION_ID = 3399999;
for (const std::string& variation_str : GetAllVariationIds()) {
auto [variation_id, is_triggering] = ParseVariationId(variation_str);
// Reject variation IDs used both as triggering and non-triggering.
// This is generally considered invalid.
EXPECT_FALSE(
// Triggering, but already recorded as visible.
(is_triggering && nontriggering_variation_ids.contains(variation_id)) ||
// Visible, but already recorded as triggering.
(!is_triggering && triggering_variation_ids.contains(variation_id)))
<< "Variation ID \"" << variation_id
<< "\" used both as triggering and "
<< "non-triggering.";
EXPECT_TRUE(variation_id >= LOWER_VALID_VARIATION_ID &&
variation_id <= UPPER_VALID_VARIATION_ID)
<< "Variation ID \"" << variation_id << "\" falls outside of range of "
<< "valid variation IDs: [" << LOWER_VALID_VARIATION_ID << ", "
<< UPPER_VALID_VARIATION_ID << "].";
if (is_triggering) {
triggering_variation_ids.insert(variation_id);
} else {
nontriggering_variation_ids.insert(variation_id);
}
}
}
// Test that ScopedFeatureEntries restores existing feature entries on
// destruction.
TEST(AboutFlagsTest, ScopedFeatureEntriesRestoresFeatureEntries) {
const base::span<const flags_ui::FeatureEntry> old_entries =
testing::GetFeatureEntries();
EXPECT_FALSE(old_entries.empty());
const char* first_feature_name = old_entries[0].internal_name;
{
static BASE_FEATURE(kTestFeature1, "FeatureName1",
base::FEATURE_ENABLED_BY_DEFAULT);
testing::ScopedFeatureEntries feature_entries(
{{"feature-1", "", "", flags_ui::FlagsState::GetCurrentPlatform(),
FEATURE_VALUE_TYPE(kTestFeature1)}});
EXPECT_EQ(testing::GetFeatureEntries().size(), 1U);
}
const base::span<const flags_ui::FeatureEntry> new_entries =
testing::GetFeatureEntries();
EXPECT_EQ(old_entries.size(), new_entries.size());
EXPECT_TRUE(about_flags::GetCurrentFlagsState()->FindFeatureEntryByName(
first_feature_name));
}
class AboutFlagsHistogramTest : public ::testing::Test {
protected:
// This is a helper function to check that all IDs in enum LoginCustomFlags in
// histograms.xml are unique.
void SetSwitchToHistogramIdMapping(const std::string& switch_name,
const Sample32 switch_histogram_id,
std::map<std::string, Sample32>* out_map) {
const std::pair<std::map<std::string, Sample32>::iterator, bool> status =
out_map->insert(std::make_pair(switch_name, switch_histogram_id));
if (!status.second) {
EXPECT_TRUE(status.first->second == switch_histogram_id)
<< "Duplicate switch '" << switch_name
<< "' found in enum 'LoginCustomFlags' in "
"tools/metrics/histograms/enums.xml.";
}
}
// This method generates a hint for the user for what string should be added
// to the enum LoginCustomFlags to make in consistent.
std::string GetHistogramEnumEntryText(const std::string& switch_name,
Sample32 value) {
return base::StringPrintf(
"<int value=\"%d\" label=\"%s\"/>", value, switch_name.c_str());
}
};
TEST_F(AboutFlagsHistogramTest, CheckHistograms) {
std::optional<base::HistogramEnumEntryMap> login_custom_flags =
base::ReadEnumFromEnumsXml("LoginCustomFlags");
ASSERT_TRUE(login_custom_flags)
<< "Error reading enum 'LoginCustomFlags' from "
"tools/metrics/histograms/enums.xml.";
// Build reverse map {switch_name => id} from login_custom_flags.
SwitchToIdMap metadata_switches_ids;
EXPECT_TRUE(
login_custom_flags->count(flags_ui::testing::kBadSwitchFormatHistogramId))
<< "Entry for UMA ID of incorrect command-line flag is not found in "
"tools/metrics/histograms/enums.xml enum LoginCustomFlags. "
"Consider adding entry:\n"
<< " " << GetHistogramEnumEntryText("BAD_FLAG_FORMAT", 0);
// Check that all LoginCustomFlags entries have correct values.
for (const auto& entry : *login_custom_flags) {
if (entry.first == flags_ui::testing::kBadSwitchFormatHistogramId) {
// Add error value with empty name.
SetSwitchToHistogramIdMapping(std::string(), entry.first,
&metadata_switches_ids);
continue;
}
const Sample32 uma_id = flags_ui::GetSwitchUMAId(entry.second);
EXPECT_EQ(uma_id, entry.first)
<< "tools/metrics/histograms/enums.xml enum LoginCustomFlags "
"entry '"
<< entry.second << "' has incorrect value=" << entry.first << ", but "
<< uma_id << " is expected. Consider changing entry to:\n"
<< " " << GetHistogramEnumEntryText(entry.second, uma_id);
SetSwitchToHistogramIdMapping(entry.second, entry.first,
&metadata_switches_ids);
}
// Check that all flags in about_flags.cc have entries in login_custom_flags.
std::set<std::string> all_flags = GetAllPublicSwitchesAndFeaturesForTesting();
for (const std::string& flag : all_flags) {
// Skip empty placeholders.
if (flag.empty())
continue;
const Sample32 uma_id = flags_ui::GetSwitchUMAId(flag);
EXPECT_NE(flags_ui::testing::kBadSwitchFormatHistogramId, uma_id)
<< "Command-line switch '" << flag
<< "' from about_flags.cc has UMA ID equal to reserved value "
"kBadSwitchFormatHistogramId="
<< flags_ui::testing::kBadSwitchFormatHistogramId
<< ". Please modify switch name.";
auto enum_entry = metadata_switches_ids.lower_bound(flag);
// Ignore case here when switch ID is incorrect - it has already been
// reported in the previous loop.
EXPECT_TRUE(enum_entry != metadata_switches_ids.end() &&
enum_entry->first == flag)
<< "tools/metrics/histograms/enums.xml enum LoginCustomFlags doesn't "
"contain switch '"
<< flag << "' (value=" << uma_id << " expected). Consider running:\n"
<< " tools/metrics/histograms/generate_flag_enums.py --feature "
<< flag.substr(0, flag.find(":")) << "\nOr manually adding the entry:\n"
<< " " << GetHistogramEnumEntryText(flag, uma_id);
}
}
} // namespace about_flags
|