File: about_flags_unittest.cc

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (327 lines) | stat: -rw-r--r-- 13,175 bytes parent folder | download | duplicates (5)
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