Clarify and update GN build flags docs.
[chromium-blink-merge.git] / components / variations / study_filtering.cc
blob03aaed213c960f44cbadd4b4853fcdc853732c8c
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
3 // found in the LICENSE file.
5 #include "components/variations/study_filtering.h"
7 #include <set>
9 #include "base/stl_util.h"
11 namespace variations {
13 namespace {
15 Study_Platform GetCurrentPlatform() {
16 #if defined(OS_WIN)
17 return Study_Platform_PLATFORM_WINDOWS;
18 #elif defined(OS_IOS)
19 return Study_Platform_PLATFORM_IOS;
20 #elif defined(OS_MACOSX)
21 return Study_Platform_PLATFORM_MAC;
22 #elif defined(OS_CHROMEOS)
23 return Study_Platform_PLATFORM_CHROMEOS;
24 #elif defined(OS_ANDROID)
25 return Study_Platform_PLATFORM_ANDROID;
26 #elif defined(OS_LINUX) || defined(OS_BSD) || defined(OS_SOLARIS)
27 // Default BSD and SOLARIS to Linux to not break those builds, although these
28 // platforms are not officially supported by Chrome.
29 return Study_Platform_PLATFORM_LINUX;
30 #else
31 #error Unknown platform
32 #endif
35 // Converts |date_time| in Study date format to base::Time.
36 base::Time ConvertStudyDateToBaseTime(int64 date_time) {
37 return base::Time::UnixEpoch() + base::TimeDelta::FromSeconds(date_time);
40 } // namespace
42 namespace internal {
44 bool CheckStudyChannel(const Study_Filter& filter, Study_Channel channel) {
45 // An empty channel list matches all channels.
46 if (filter.channel_size() == 0)
47 return true;
49 for (int i = 0; i < filter.channel_size(); ++i) {
50 if (filter.channel(i) == channel)
51 return true;
53 return false;
56 bool CheckStudyFormFactor(const Study_Filter& filter,
57 Study_FormFactor form_factor) {
58 // An empty form factor list matches all form factors.
59 if (filter.form_factor_size() == 0)
60 return true;
62 for (int i = 0; i < filter.form_factor_size(); ++i) {
63 if (filter.form_factor(i) == form_factor)
64 return true;
66 return false;
69 bool CheckStudyHardwareClass(const Study_Filter& filter,
70 const std::string& hardware_class) {
71 // Empty hardware_class and exclude_hardware_class matches all.
72 if (filter.hardware_class_size() == 0 &&
73 filter.exclude_hardware_class_size() == 0) {
74 return true;
77 // Checks if we are supposed to filter for a specified set of
78 // hardware_classes. Note that this means this overrides the
79 // exclude_hardware_class in case that ever occurs (which it shouldn't).
80 if (filter.hardware_class_size() > 0) {
81 for (int i = 0; i < filter.hardware_class_size(); ++i) {
82 // Check if the entry is a substring of |hardware_class|.
83 size_t position = hardware_class.find(filter.hardware_class(i));
84 if (position != std::string::npos)
85 return true;
87 // None of the requested hardware_classes match.
88 return false;
91 // Omit if matches any of the exclude entries.
92 for (int i = 0; i < filter.exclude_hardware_class_size(); ++i) {
93 // Check if the entry is a substring of |hardware_class|.
94 size_t position = hardware_class.find(
95 filter.exclude_hardware_class(i));
96 if (position != std::string::npos)
97 return false;
100 // None of the exclusions match, so this accepts.
101 return true;
104 bool CheckStudyLocale(const Study_Filter& filter, const std::string& locale) {
105 // An empty locale list matches all locales.
106 if (filter.locale_size() == 0)
107 return true;
109 for (int i = 0; i < filter.locale_size(); ++i) {
110 if (filter.locale(i) == locale)
111 return true;
113 return false;
116 bool CheckStudyPlatform(const Study_Filter& filter, Study_Platform platform) {
117 // An empty platform list matches all platforms.
118 if (filter.platform_size() == 0)
119 return true;
121 for (int i = 0; i < filter.platform_size(); ++i) {
122 if (filter.platform(i) == platform)
123 return true;
125 return false;
128 bool CheckStudyStartDate(const Study_Filter& filter,
129 const base::Time& date_time) {
130 if (filter.has_start_date()) {
131 const base::Time start_date =
132 ConvertStudyDateToBaseTime(filter.start_date());
133 return date_time >= start_date;
136 return true;
139 bool CheckStudyVersion(const Study_Filter& filter,
140 const base::Version& version) {
141 if (filter.has_min_version()) {
142 if (version.CompareToWildcardString(filter.min_version()) < 0)
143 return false;
146 if (filter.has_max_version()) {
147 if (version.CompareToWildcardString(filter.max_version()) > 0)
148 return false;
151 return true;
154 bool CheckStudyCountry(const Study_Filter& filter, const std::string& country) {
155 // Empty country and exclude_country matches all.
156 if (filter.country_size() == 0 && filter.exclude_country_size() == 0)
157 return true;
159 // Checks if we are supposed to filter for a specified set of countries. Note
160 // that this means this overrides the exclude_country in case that ever occurs
161 // (which it shouldn't).
162 if (filter.country_size() > 0)
163 return ContainsValue(filter.country(), country);
165 // Omit if matches any of the exclude entries.
166 return !ContainsValue(filter.exclude_country(), country);
169 bool IsStudyExpired(const Study& study, const base::Time& date_time) {
170 if (study.has_expiry_date()) {
171 const base::Time expiry_date =
172 ConvertStudyDateToBaseTime(study.expiry_date());
173 return date_time >= expiry_date;
176 return false;
179 bool ShouldAddStudy(
180 const Study& study,
181 const std::string& locale,
182 const base::Time& reference_date,
183 const base::Version& version,
184 Study_Channel channel,
185 Study_FormFactor form_factor,
186 const std::string& hardware_class,
187 const std::string& country) {
188 if (study.has_filter()) {
189 if (!CheckStudyChannel(study.filter(), channel)) {
190 DVLOG(1) << "Filtered out study " << study.name() << " due to channel.";
191 return false;
194 if (!CheckStudyFormFactor(study.filter(), form_factor)) {
195 DVLOG(1) << "Filtered out study " << study.name() <<
196 " due to form factor.";
197 return false;
200 if (!CheckStudyLocale(study.filter(), locale)) {
201 DVLOG(1) << "Filtered out study " << study.name() << " due to locale.";
202 return false;
205 if (!CheckStudyPlatform(study.filter(), GetCurrentPlatform())) {
206 DVLOG(1) << "Filtered out study " << study.name() << " due to platform.";
207 return false;
210 if (!CheckStudyVersion(study.filter(), version)) {
211 DVLOG(1) << "Filtered out study " << study.name() << " due to version.";
212 return false;
215 if (!CheckStudyStartDate(study.filter(), reference_date)) {
216 DVLOG(1) << "Filtered out study " << study.name() <<
217 " due to start date.";
218 return false;
221 if (!CheckStudyHardwareClass(study.filter(), hardware_class)) {
222 DVLOG(1) << "Filtered out study " << study.name() <<
223 " due to hardware_class.";
224 return false;
227 if (!CheckStudyCountry(study.filter(), country)) {
228 DVLOG(1) << "Filtered out study " << study.name() << " due to country.";
229 return false;
233 DVLOG(1) << "Kept study " << study.name() << ".";
234 return true;
237 } // namespace internal
239 void FilterAndValidateStudies(
240 const VariationsSeed& seed,
241 const std::string& locale,
242 const base::Time& reference_date,
243 const base::Version& version,
244 Study_Channel channel,
245 Study_FormFactor form_factor,
246 const std::string& hardware_class,
247 const std::string& permanent_consistency_country,
248 std::vector<ProcessedStudy>* filtered_studies) {
249 DCHECK(version.IsValid());
251 // Add expired studies (in a disabled state) only after all the non-expired
252 // studies have been added (and do not add an expired study if a corresponding
253 // non-expired study got added). This way, if there's both an expired and a
254 // non-expired study that applies, the non-expired study takes priority.
255 std::set<std::string> created_studies;
256 std::vector<const Study*> expired_studies;
258 for (int i = 0; i < seed.study_size(); ++i) {
259 const Study& study = seed.study(i);
261 // Unless otherwise specified, use an empty country that won't pass any
262 // filters that specifically include countries, but will pass any filters
263 // that specifically exclude countries.
264 std::string country;
265 switch (study.consistency()) {
266 case Study_Consistency_SESSION:
267 if (seed.has_country_code())
268 country = seed.country_code();
269 break;
270 case Study_Consistency_PERMANENT:
271 // Use the saved |permanent_consistency_country| for permanent
272 // consistency studies. This allows Chrome to use the same country for
273 // filtering permanent consistency studies between Chrome upgrades.
274 // Since some studies have user-visible effects, this helps to avoid
275 // annoying users with experimental group churn while traveling.
276 country = permanent_consistency_country;
277 break;
280 if (!internal::ShouldAddStudy(study, locale, reference_date, version,
281 channel, form_factor, hardware_class,
282 country)) {
283 continue;
286 if (internal::IsStudyExpired(study, reference_date)) {
287 expired_studies.push_back(&study);
288 } else if (!ContainsKey(created_studies, study.name())) {
289 ProcessedStudy::ValidateAndAppendStudy(&study, false, filtered_studies);
290 created_studies.insert(study.name());
294 for (size_t i = 0; i < expired_studies.size(); ++i) {
295 if (!ContainsKey(created_studies, expired_studies[i]->name())) {
296 ProcessedStudy::ValidateAndAppendStudy(expired_studies[i], true,
297 filtered_studies);
302 } // namespace variations