Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(677)

Side by Side Diff: components/variations/study_filtering.cc

Issue 2924983003: [Variations] Refactor all state used for study filtering into a container struct. (Closed)
Patch Set: Update unit tests Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2014 The Chromium Authors. All rights reserved. 1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "components/variations/study_filtering.h" 5 #include "components/variations/study_filtering.h"
6 6
7 #include <stddef.h> 7 #include <stddef.h>
8 #include <stdint.h> 8 #include <stdint.h>
9 9
10 #include <set> 10 #include <set>
11 11
12 #include "base/stl_util.h" 12 #include "base/stl_util.h"
13 #include "build/build_config.h" 13 #include "base/strings/string_util.h"
14 #include "components/variations/client_filterable_state.h"
14 15
15 namespace variations { 16 namespace variations {
16
17 namespace { 17 namespace {
18 18
19 Study_Platform GetCurrentPlatform() {
20 #if defined(OS_WIN)
21 return Study_Platform_PLATFORM_WINDOWS;
22 #elif defined(OS_IOS)
23 return Study_Platform_PLATFORM_IOS;
24 #elif defined(OS_MACOSX)
25 return Study_Platform_PLATFORM_MAC;
26 #elif defined(OS_CHROMEOS)
27 return Study_Platform_PLATFORM_CHROMEOS;
28 #elif defined(OS_ANDROID)
29 return Study_Platform_PLATFORM_ANDROID;
30 #elif defined(OS_LINUX) || defined(OS_BSD) || defined(OS_SOLARIS)
31 // Default BSD and SOLARIS to Linux to not break those builds, although these
32 // platforms are not officially supported by Chrome.
33 return Study_Platform_PLATFORM_LINUX;
34 #else
35 #error Unknown platform
36 #endif
37 }
38
39 // Converts |date_time| in Study date format to base::Time. 19 // Converts |date_time| in Study date format to base::Time.
40 base::Time ConvertStudyDateToBaseTime(int64_t date_time) { 20 base::Time ConvertStudyDateToBaseTime(int64_t date_time) {
41 return base::Time::UnixEpoch() + base::TimeDelta::FromSeconds(date_time); 21 return base::Time::UnixEpoch() + base::TimeDelta::FromSeconds(date_time);
42 } 22 }
43 23
44 } // namespace 24 } // namespace
45 25
46 namespace internal { 26 namespace internal {
47 27
48 bool CheckStudyChannel(const Study_Filter& filter, Study_Channel channel) { 28 bool CheckStudyChannel(const Study::Filter& filter, Study::Channel channel) {
49 // An empty channel list matches all channels. 29 // An empty channel list matches all channels.
50 if (filter.channel_size() == 0) 30 if (filter.channel_size() == 0)
51 return true; 31 return true;
52 32
53 for (int i = 0; i < filter.channel_size(); ++i) { 33 for (int i = 0; i < filter.channel_size(); ++i) {
54 if (filter.channel(i) == channel) 34 if (filter.channel(i) == channel)
55 return true; 35 return true;
56 } 36 }
57 return false; 37 return false;
58 } 38 }
59 39
60 bool CheckStudyFormFactor(const Study_Filter& filter, 40 bool CheckStudyFormFactor(const Study::Filter& filter,
61 Study_FormFactor form_factor) { 41 Study::FormFactor form_factor) {
62 // Empty whitelist and blacklist signifies matching any form factor. 42 // Empty whitelist and blacklist signifies matching any form factor.
63 if (filter.form_factor_size() == 0 && filter.exclude_form_factor_size() == 0) 43 if (filter.form_factor_size() == 0 && filter.exclude_form_factor_size() == 0)
64 return true; 44 return true;
65 45
66 // Allow the form_factor if it matches the whitelist. 46 // Allow the form_factor if it matches the whitelist.
67 // Note if both a whitelist and blacklist are specified, the blacklist is 47 // Note if both a whitelist and blacklist are specified, the blacklist is
68 // ignored. We do not expect both to be present for Chrome due to server-side 48 // ignored. We do not expect both to be present for Chrome due to server-side
69 // checks. 49 // checks.
70 if (filter.form_factor_size() > 0) 50 if (filter.form_factor_size() > 0)
71 return base::ContainsValue(filter.form_factor(), form_factor); 51 return base::ContainsValue(filter.form_factor(), form_factor);
72 52
73 // Omit if we match the blacklist. 53 // Omit if we match the blacklist.
74 return !base::ContainsValue(filter.exclude_form_factor(), form_factor); 54 return !base::ContainsValue(filter.exclude_form_factor(), form_factor);
75 } 55 }
76 56
77 bool CheckStudyHardwareClass(const Study_Filter& filter, 57 bool CheckStudyHardwareClass(const Study::Filter& filter,
78 const std::string& hardware_class) { 58 const std::string& hardware_class) {
79 // Empty hardware_class and exclude_hardware_class matches all. 59 // Empty hardware_class and exclude_hardware_class matches all.
80 if (filter.hardware_class_size() == 0 && 60 if (filter.hardware_class_size() == 0 &&
81 filter.exclude_hardware_class_size() == 0) { 61 filter.exclude_hardware_class_size() == 0) {
82 return true; 62 return true;
83 } 63 }
84 64
85 // Checks if we are supposed to filter for a specified set of 65 // Checks if we are supposed to filter for a specified set of
86 // hardware_classes. Note that this means this overrides the 66 // hardware_classes. Note that this means this overrides the
87 // exclude_hardware_class in case that ever occurs (which it shouldn't). 67 // exclude_hardware_class in case that ever occurs (which it shouldn't).
(...skipping 14 matching lines...) Expand all
102 size_t position = hardware_class.find( 82 size_t position = hardware_class.find(
103 filter.exclude_hardware_class(i)); 83 filter.exclude_hardware_class(i));
104 if (position != std::string::npos) 84 if (position != std::string::npos)
105 return false; 85 return false;
106 } 86 }
107 87
108 // None of the exclusions match, so this accepts. 88 // None of the exclusions match, so this accepts.
109 return true; 89 return true;
110 } 90 }
111 91
112 bool CheckStudyLocale(const Study_Filter& filter, const std::string& locale) { 92 bool CheckStudyLocale(const Study::Filter& filter, const std::string& locale) {
113 // Empty locale and exclude_locale lists matches all locales. 93 // Empty locale and exclude_locale lists matches all locales.
114 if (filter.locale_size() == 0 && filter.exclude_locale_size() == 0) 94 if (filter.locale_size() == 0 && filter.exclude_locale_size() == 0)
115 return true; 95 return true;
116 96
117 // Check if we are supposed to filter for a specified set of countries. Note 97 // Check if we are supposed to filter for a specified set of countries. Note
118 // that this means this overrides the exclude_locale in case that ever occurs 98 // that this means this overrides the exclude_locale in case that ever occurs
119 // (which it shouldn't). 99 // (which it shouldn't).
120 if (filter.locale_size() > 0) 100 if (filter.locale_size() > 0)
121 return base::ContainsValue(filter.locale(), locale); 101 return base::ContainsValue(filter.locale(), locale);
122 102
123 // Omit if matches any of the exclude entries. 103 // Omit if matches any of the exclude entries.
124 return !base::ContainsValue(filter.exclude_locale(), locale); 104 return !base::ContainsValue(filter.exclude_locale(), locale);
125 } 105 }
126 106
127 bool CheckStudyPlatform(const Study_Filter& filter, Study_Platform platform) { 107 bool CheckStudyPlatform(const Study::Filter& filter, Study::Platform platform) {
128 // An empty platform list matches all platforms. 108 // An empty platform list matches all platforms.
129 if (filter.platform_size() == 0) 109 if (filter.platform_size() == 0)
130 return true; 110 return true;
131 111
132 for (int i = 0; i < filter.platform_size(); ++i) { 112 for (int i = 0; i < filter.platform_size(); ++i) {
133 if (filter.platform(i) == platform) 113 if (filter.platform(i) == platform)
134 return true; 114 return true;
135 } 115 }
136 return false; 116 return false;
137 } 117 }
138 118
139 bool CheckStudyStartDate(const Study_Filter& filter, 119 bool CheckStudyStartDate(const Study::Filter& filter,
140 const base::Time& date_time) { 120 const base::Time& date_time) {
141 if (filter.has_start_date()) { 121 if (filter.has_start_date()) {
142 const base::Time start_date = 122 const base::Time start_date =
143 ConvertStudyDateToBaseTime(filter.start_date()); 123 ConvertStudyDateToBaseTime(filter.start_date());
144 return date_time >= start_date; 124 return date_time >= start_date;
145 } 125 }
146 126
147 return true; 127 return true;
148 } 128 }
149 129
150 bool CheckStudyEndDate(const Study_Filter& filter, 130 bool CheckStudyEndDate(const Study::Filter& filter,
151 const base::Time& date_time) { 131 const base::Time& date_time) {
152 if (filter.has_end_date()) { 132 if (filter.has_end_date()) {
153 const base::Time end_date = ConvertStudyDateToBaseTime(filter.end_date()); 133 const base::Time end_date = ConvertStudyDateToBaseTime(filter.end_date());
154 return end_date >= date_time; 134 return end_date >= date_time;
155 } 135 }
156 136
157 return true; 137 return true;
158 } 138 }
159 139
160 bool CheckStudyVersion(const Study_Filter& filter, 140 bool CheckStudyVersion(const Study::Filter& filter,
161 const base::Version& version) { 141 const base::Version& version) {
162 if (filter.has_min_version()) { 142 if (filter.has_min_version()) {
163 if (version.CompareToWildcardString(filter.min_version()) < 0) 143 if (version.CompareToWildcardString(filter.min_version()) < 0)
164 return false; 144 return false;
165 } 145 }
166 146
167 if (filter.has_max_version()) { 147 if (filter.has_max_version()) {
168 if (version.CompareToWildcardString(filter.max_version()) > 0) 148 if (version.CompareToWildcardString(filter.max_version()) > 0)
169 return false; 149 return false;
170 } 150 }
171 151
172 return true; 152 return true;
173 } 153 }
174 154
175 bool CheckStudyCountry(const Study_Filter& filter, const std::string& country) { 155 bool CheckStudyCountry(const Study::Filter& filter,
156 const std::string& country) {
176 // Empty country and exclude_country matches all. 157 // Empty country and exclude_country matches all.
177 if (filter.country_size() == 0 && filter.exclude_country_size() == 0) 158 if (filter.country_size() == 0 && filter.exclude_country_size() == 0)
178 return true; 159 return true;
179 160
180 // Checks if we are supposed to filter for a specified set of countries. Note 161 // Checks if we are supposed to filter for a specified set of countries. Note
181 // that this means this overrides the exclude_country in case that ever occurs 162 // that this means this overrides the exclude_country in case that ever occurs
182 // (which it shouldn't). 163 // (which it shouldn't).
183 if (filter.country_size() > 0) 164 if (filter.country_size() > 0)
184 return base::ContainsValue(filter.country(), country); 165 return base::ContainsValue(filter.country(), country);
185 166
186 // Omit if matches any of the exclude entries. 167 // Omit if matches any of the exclude entries.
187 return !base::ContainsValue(filter.exclude_country(), country); 168 return !base::ContainsValue(filter.exclude_country(), country);
188 } 169 }
189 170
171 const std::string& GetStudyCountry(const Study& study,
Alexei Svitkine (slow) 2017/06/07 15:18:08 Nit: I think name is misleading - since this is ge
Ilya Sherman 2017/06/07 21:50:08 Done. (I dropped "State" though, i.e. GetClientCo
172 const ClientFilterableState& client_state) {
173 switch (study.consistency()) {
174 case Study::SESSION:
175 return client_state.session_consistency_country;
176 case Study::PERMANENT:
177 // Use the saved country for permanent consistency studies. This allows
178 // Chrome to use the same country for filtering permanent consistency
179 // studies between Chrome upgrades. Since some studies have user-visible
180 // effects, this helps to avoid annoying users with experimental group
181 // churn while traveling.
182 return client_state.permanent_consistency_country;
183 }
184
185 // Unless otherwise specified, use an empty country that won't pass any
186 // filters that specifically include countries, but will pass any filters
187 // that specifically exclude countries.
188 return base::EmptyString();
189 }
190
190 bool IsStudyExpired(const Study& study, const base::Time& date_time) { 191 bool IsStudyExpired(const Study& study, const base::Time& date_time) {
191 if (study.has_expiry_date()) { 192 if (study.has_expiry_date()) {
192 const base::Time expiry_date = 193 const base::Time expiry_date =
193 ConvertStudyDateToBaseTime(study.expiry_date()); 194 ConvertStudyDateToBaseTime(study.expiry_date());
194 return date_time >= expiry_date; 195 return date_time >= expiry_date;
195 } 196 }
196 197
197 return false; 198 return false;
198 } 199 }
199 200
200 bool ShouldAddStudy( 201 bool ShouldAddStudy(const Study& study,
201 const Study& study, 202 const ClientFilterableState& client_state) {
202 const std::string& locale,
203 const base::Time& reference_date,
204 const base::Version& version,
205 Study_Channel channel,
206 Study_FormFactor form_factor,
207 const std::string& hardware_class,
208 const std::string& country) {
209 if (study.has_filter()) { 203 if (study.has_filter()) {
210 if (!CheckStudyChannel(study.filter(), channel)) { 204 if (!CheckStudyChannel(study.filter(), client_state.channel)) {
211 DVLOG(1) << "Filtered out study " << study.name() << " due to channel."; 205 DVLOG(1) << "Filtered out study " << study.name() << " due to channel.";
212 return false; 206 return false;
213 } 207 }
214 208
215 if (!CheckStudyFormFactor(study.filter(), form_factor)) { 209 if (!CheckStudyFormFactor(study.filter(), client_state.form_factor)) {
216 DVLOG(1) << "Filtered out study " << study.name() << 210 DVLOG(1) << "Filtered out study " << study.name() <<
217 " due to form factor."; 211 " due to form factor.";
218 return false; 212 return false;
219 } 213 }
220 214
221 if (!CheckStudyLocale(study.filter(), locale)) { 215 if (!CheckStudyLocale(study.filter(), client_state.locale)) {
222 DVLOG(1) << "Filtered out study " << study.name() << " due to locale."; 216 DVLOG(1) << "Filtered out study " << study.name() << " due to locale.";
223 return false; 217 return false;
224 } 218 }
225 219
226 if (!CheckStudyPlatform(study.filter(), GetCurrentPlatform())) { 220 if (!CheckStudyPlatform(study.filter(), client_state.platform)) {
227 DVLOG(1) << "Filtered out study " << study.name() << " due to platform."; 221 DVLOG(1) << "Filtered out study " << study.name() << " due to platform.";
228 return false; 222 return false;
229 } 223 }
230 224
231 if (!CheckStudyVersion(study.filter(), version)) { 225 if (!CheckStudyVersion(study.filter(), client_state.version)) {
232 DVLOG(1) << "Filtered out study " << study.name() << " due to version."; 226 DVLOG(1) << "Filtered out study " << study.name() << " due to version.";
233 return false; 227 return false;
234 } 228 }
235 229
236 if (!CheckStudyStartDate(study.filter(), reference_date)) { 230 if (!CheckStudyStartDate(study.filter(), client_state.reference_date)) {
237 DVLOG(1) << "Filtered out study " << study.name() << 231 DVLOG(1) << "Filtered out study " << study.name() <<
238 " due to start date."; 232 " due to start date.";
239 return false; 233 return false;
240 } 234 }
241 235
242 if (!CheckStudyEndDate(study.filter(), reference_date)) { 236 if (!CheckStudyEndDate(study.filter(), client_state.reference_date)) {
243 DVLOG(1) << "Filtered out study " << study.name() << " due to end date."; 237 DVLOG(1) << "Filtered out study " << study.name() << " due to end date.";
244 return false; 238 return false;
245 } 239 }
246 240
247 if (!CheckStudyHardwareClass(study.filter(), hardware_class)) { 241 if (!CheckStudyHardwareClass(study.filter(), client_state.hardware_class)) {
248 DVLOG(1) << "Filtered out study " << study.name() << 242 DVLOG(1) << "Filtered out study " << study.name() <<
249 " due to hardware_class."; 243 " due to hardware_class.";
250 return false; 244 return false;
251 } 245 }
252 246
247 const std::string& country = GetStudyCountry(study, client_state);
253 if (!CheckStudyCountry(study.filter(), country)) { 248 if (!CheckStudyCountry(study.filter(), country)) {
254 DVLOG(1) << "Filtered out study " << study.name() << " due to country."; 249 DVLOG(1) << "Filtered out study " << study.name() << " due to country.";
255 return false; 250 return false;
256 } 251 }
257 } 252 }
258 253
259 DVLOG(1) << "Kept study " << study.name() << "."; 254 DVLOG(1) << "Kept study " << study.name() << ".";
260 return true; 255 return true;
261 } 256 }
262 257
263 } // namespace internal 258 } // namespace internal
264 259
265 void FilterAndValidateStudies(const VariationsSeed& seed, 260 void FilterAndValidateStudies(const VariationsSeed& seed,
266 const std::string& locale, 261 const ClientFilterableState& client_state,
267 const base::Time& reference_date,
268 const base::Version& version,
269 Study_Channel channel,
270 Study_FormFactor form_factor,
271 const std::string& hardware_class,
272 const std::string& session_consistency_country,
273 const std::string& permanent_consistency_country,
274 std::vector<ProcessedStudy>* filtered_studies) { 262 std::vector<ProcessedStudy>* filtered_studies) {
275 DCHECK(version.IsValid()); 263 DCHECK(client_state.version.IsValid());
276 264
277 // Add expired studies (in a disabled state) only after all the non-expired 265 // Add expired studies (in a disabled state) only after all the non-expired
278 // studies have been added (and do not add an expired study if a corresponding 266 // studies have been added (and do not add an expired study if a corresponding
279 // non-expired study got added). This way, if there's both an expired and a 267 // non-expired study got added). This way, if there's both an expired and a
280 // non-expired study that applies, the non-expired study takes priority. 268 // non-expired study that applies, the non-expired study takes priority.
281 std::set<std::string> created_studies; 269 std::set<std::string> created_studies;
282 std::vector<const Study*> expired_studies; 270 std::vector<const Study*> expired_studies;
283 271
284 for (int i = 0; i < seed.study_size(); ++i) { 272 for (int i = 0; i < seed.study_size(); ++i) {
285 const Study& study = seed.study(i); 273 const Study& study = seed.study(i);
274 if (!internal::ShouldAddStudy(study, client_state))
275 continue;
286 276
287 // Unless otherwise specified, use an empty country that won't pass any 277 if (internal::IsStudyExpired(study, client_state.reference_date)) {
288 // filters that specifically include countries, but will pass any filters
289 // that specifically exclude countries.
290 std::string country;
291 switch (study.consistency()) {
292 case Study_Consistency_SESSION:
293 country = session_consistency_country;
294 break;
295 case Study_Consistency_PERMANENT:
296 // Use the saved |permanent_consistency_country| for permanent
297 // consistency studies. This allows Chrome to use the same country for
298 // filtering permanent consistency studies between Chrome upgrades.
299 // Since some studies have user-visible effects, this helps to avoid
300 // annoying users with experimental group churn while traveling.
301 country = permanent_consistency_country;
302 break;
303 }
304
305 if (!internal::ShouldAddStudy(study, locale, reference_date, version,
306 channel, form_factor, hardware_class,
307 country)) {
308 continue;
309 }
310
311 if (internal::IsStudyExpired(study, reference_date)) {
312 expired_studies.push_back(&study); 278 expired_studies.push_back(&study);
313 } else if (!base::ContainsKey(created_studies, study.name())) { 279 } else if (!base::ContainsKey(created_studies, study.name())) {
314 ProcessedStudy::ValidateAndAppendStudy(&study, false, filtered_studies); 280 ProcessedStudy::ValidateAndAppendStudy(&study, false, filtered_studies);
315 created_studies.insert(study.name()); 281 created_studies.insert(study.name());
316 } 282 }
317 } 283 }
318 284
319 for (size_t i = 0; i < expired_studies.size(); ++i) { 285 for (size_t i = 0; i < expired_studies.size(); ++i) {
320 if (!base::ContainsKey(created_studies, expired_studies[i]->name())) { 286 if (!base::ContainsKey(created_studies, expired_studies[i]->name())) {
321 ProcessedStudy::ValidateAndAppendStudy(expired_studies[i], true, 287 ProcessedStudy::ValidateAndAppendStudy(expired_studies[i], true,
322 filtered_studies); 288 filtered_studies);
323 } 289 }
324 } 290 }
325 } 291 }
326 292
327 } // namespace variations 293 } // namespace variations
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698