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