study_filtering_unittest.cc 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972
  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.
  4. #include "components/variations/study_filtering.h"
  5. #include <stddef.h>
  6. #include <stdint.h>
  7. #include <string.h>
  8. #include <vector>
  9. #include "base/bind.h"
  10. #include "base/callback_helpers.h"
  11. #include "base/strings/strcat.h"
  12. #include "base/strings/string_number_conversions.h"
  13. #include "base/strings/string_split.h"
  14. #include "base/test/metrics/histogram_tester.h"
  15. #include "base/time/time.h"
  16. #include "components/variations/client_filterable_state.h"
  17. #include "components/variations/processed_study.h"
  18. #include "components/variations/variations_layers.h"
  19. #include "testing/gtest/include/gtest/gtest.h"
  20. namespace variations {
  21. namespace {
  22. // Converts |time| to Study proto format.
  23. int64_t TimeToProtoTime(const base::Time& time) {
  24. return (time - base::Time::UnixEpoch()).InSeconds();
  25. }
  26. // Adds an experiment to |study| with the specified |name| and |probability|.
  27. Study::Experiment* AddExperiment(const std::string& name,
  28. int probability,
  29. Study* study) {
  30. Study::Experiment* experiment = study->add_experiment();
  31. experiment->set_name(name);
  32. experiment->set_probability_weight(probability);
  33. return experiment;
  34. }
  35. std::vector<std::string> SplitFilterString(const std::string& input) {
  36. return base::SplitString(input, ",", base::TRIM_WHITESPACE,
  37. base::SPLIT_WANT_ALL);
  38. }
  39. } // namespace
  40. TEST(VariationsStudyFilteringTest, CheckStudyChannel) {
  41. const Study::Channel channels[] = {
  42. Study::CANARY, Study::DEV, Study::BETA, Study::STABLE,
  43. };
  44. bool channel_added[std::size(channels)] = {false};
  45. Study::Filter filter;
  46. // Check in the forwarded order. The loop cond is <= std::size(channels)
  47. // instead of < so that the result of adding the last channel gets checked.
  48. for (size_t i = 0; i <= std::size(channels); ++i) {
  49. for (size_t j = 0; j < std::size(channels); ++j) {
  50. const bool expected = channel_added[j] || filter.channel_size() == 0;
  51. const bool result = internal::CheckStudyChannel(filter, channels[j]);
  52. EXPECT_EQ(expected, result) << "Case " << i << "," << j << " failed!";
  53. }
  54. if (i < std::size(channels)) {
  55. filter.add_channel(channels[i]);
  56. channel_added[i] = true;
  57. }
  58. }
  59. // Do the same check in the reverse order.
  60. filter.clear_channel();
  61. memset(&channel_added, 0, sizeof(channel_added));
  62. for (size_t i = 0; i <= std::size(channels); ++i) {
  63. for (size_t j = 0; j < std::size(channels); ++j) {
  64. const bool expected = channel_added[j] || filter.channel_size() == 0;
  65. const bool result = internal::CheckStudyChannel(filter, channels[j]);
  66. EXPECT_EQ(expected, result) << "Case " << i << "," << j << " failed!";
  67. }
  68. if (i < std::size(channels)) {
  69. const int index = std::size(channels) - i - 1;
  70. filter.add_channel(channels[index]);
  71. channel_added[index] = true;
  72. }
  73. }
  74. }
  75. TEST(VariationsStudyFilteringTest, CheckStudyFormFactor) {
  76. const Study::FormFactor form_factors[] = {Study::DESKTOP, Study::PHONE,
  77. Study::TABLET, Study::KIOSK,
  78. Study::MEET_DEVICE};
  79. ASSERT_EQ(Study::FormFactor_ARRAYSIZE,
  80. static_cast<int>(std::size(form_factors)));
  81. bool form_factor_added[std::size(form_factors)] = {false};
  82. Study::Filter filter;
  83. for (size_t i = 0; i <= std::size(form_factors); ++i) {
  84. for (size_t j = 0; j < std::size(form_factors); ++j) {
  85. const bool expected = form_factor_added[j] ||
  86. filter.form_factor_size() == 0;
  87. const bool result = internal::CheckStudyFormFactor(filter,
  88. form_factors[j]);
  89. EXPECT_EQ(expected, result) << "form_factor: case " << i << "," << j
  90. << " failed!";
  91. }
  92. if (i < std::size(form_factors)) {
  93. filter.add_form_factor(form_factors[i]);
  94. form_factor_added[i] = true;
  95. }
  96. }
  97. // Do the same check in the reverse order.
  98. filter.clear_form_factor();
  99. memset(&form_factor_added, 0, sizeof(form_factor_added));
  100. for (size_t i = 0; i <= std::size(form_factors); ++i) {
  101. for (size_t j = 0; j < std::size(form_factors); ++j) {
  102. const bool expected = form_factor_added[j] ||
  103. filter.form_factor_size() == 0;
  104. const bool result = internal::CheckStudyFormFactor(filter,
  105. form_factors[j]);
  106. EXPECT_EQ(expected, result) << "form_factor: case " << i << "," << j
  107. << " failed!";
  108. }
  109. if (i < std::size(form_factors)) {
  110. const int index = std::size(form_factors) - i - 1;
  111. filter.add_form_factor(form_factors[index]);
  112. form_factor_added[index] = true;
  113. }
  114. }
  115. // Test exclude_form_factors, forward order.
  116. filter.clear_form_factor();
  117. bool form_factor_excluded[std::size(form_factors)] = {false};
  118. for (size_t i = 0; i <= std::size(form_factors); ++i) {
  119. for (size_t j = 0; j < std::size(form_factors); ++j) {
  120. const bool expected = filter.exclude_form_factor_size() == 0 ||
  121. !form_factor_excluded[j];
  122. const bool result = internal::CheckStudyFormFactor(filter,
  123. form_factors[j]);
  124. EXPECT_EQ(expected, result) << "exclude_form_factor: case " << i << ","
  125. << j << " failed!";
  126. }
  127. if (i < std::size(form_factors)) {
  128. filter.add_exclude_form_factor(form_factors[i]);
  129. form_factor_excluded[i] = true;
  130. }
  131. }
  132. // Test exclude_form_factors, reverse order.
  133. filter.clear_exclude_form_factor();
  134. memset(&form_factor_excluded, 0, sizeof(form_factor_excluded));
  135. for (size_t i = 0; i <= std::size(form_factors); ++i) {
  136. for (size_t j = 0; j < std::size(form_factors); ++j) {
  137. const bool expected = filter.exclude_form_factor_size() == 0 ||
  138. !form_factor_excluded[j];
  139. const bool result = internal::CheckStudyFormFactor(filter,
  140. form_factors[j]);
  141. EXPECT_EQ(expected, result) << "exclude_form_factor: case " << i << ","
  142. << j << " failed!";
  143. }
  144. if (i < std::size(form_factors)) {
  145. const int index = std::size(form_factors) - i - 1;
  146. filter.add_exclude_form_factor(form_factors[index]);
  147. form_factor_excluded[index] = true;
  148. }
  149. }
  150. }
  151. TEST(VariationsStudyFilteringTest, CheckStudyLocale) {
  152. struct {
  153. const char* filter_locales;
  154. const char* exclude_locales;
  155. bool en_us_result;
  156. bool en_ca_result;
  157. bool fr_result;
  158. } test_cases[] = {
  159. {"en-US", "", true, false, false},
  160. // Tests that locale overrides exclude_locale, when both are given. This
  161. // should not occur in practice though.
  162. {"en-US", "en-US", true, false, false},
  163. {"en-US,en-CA,fr", "", true, true, true},
  164. {"en-US,en-CA,en-GB", "", true, true, false},
  165. {"en-GB,en-CA,en-US", "", true, true, false},
  166. {"ja,kr,vi", "", false, false, false},
  167. {"fr-CA", "", false, false, false},
  168. {"", "", true, true, true},
  169. {"", "en-US", false, true, true},
  170. {"", "en-US,en-CA,fr", false, false, false},
  171. {"", "en-US,en-CA,en-GB", false, false, true},
  172. {"", "en-GB,en-CA,en-US", false, false, true},
  173. {"", "ja,kr,vi", true, true, true},
  174. {"", "fr-CA", true, true, true},
  175. };
  176. for (const auto& test : test_cases) {
  177. Study::Filter filter;
  178. for (const std::string& locale : SplitFilterString(test.filter_locales))
  179. filter.add_locale(locale);
  180. for (const std::string& locale : SplitFilterString(test.exclude_locales))
  181. filter.add_exclude_locale(locale);
  182. EXPECT_EQ(test.en_us_result, internal::CheckStudyLocale(filter, "en-US"));
  183. EXPECT_EQ(test.en_ca_result, internal::CheckStudyLocale(filter, "en-CA"));
  184. EXPECT_EQ(test.fr_result, internal::CheckStudyLocale(filter, "fr"));
  185. }
  186. }
  187. TEST(VariationsStudyFilteringTest, CheckStudyPlatform) {
  188. const Study::Platform platforms[] = {Study::PLATFORM_WINDOWS,
  189. Study::PLATFORM_MAC,
  190. Study::PLATFORM_LINUX,
  191. Study::PLATFORM_CHROMEOS,
  192. Study::PLATFORM_CHROMEOS_LACROS,
  193. Study::PLATFORM_ANDROID,
  194. Study::PLATFORM_IOS,
  195. Study::PLATFORM_ANDROID_WEBLAYER,
  196. Study::PLATFORM_FUCHSIA,
  197. Study::PLATFORM_ANDROID_WEBVIEW};
  198. static_assert(std::size(platforms) == Study::Platform_ARRAYSIZE,
  199. "|platforms| must include all platforms.");
  200. bool platform_added[std::size(platforms)] = {false};
  201. Study::Filter filter;
  202. // Check in the forwarded order. The loop cond is <= std::size(platforms)
  203. // instead of < so that the result of adding the last platform gets checked.
  204. for (size_t i = 0; i <= std::size(platforms); ++i) {
  205. for (size_t j = 0; j < std::size(platforms); ++j) {
  206. const bool expected = platform_added[j];
  207. const bool result = internal::CheckStudyPlatform(filter, platforms[j]);
  208. EXPECT_EQ(expected, result) << "Case " << i << "," << j << " failed!";
  209. }
  210. if (i < std::size(platforms)) {
  211. filter.add_platform(platforms[i]);
  212. platform_added[i] = true;
  213. }
  214. }
  215. // Do the same check in the reverse order.
  216. filter.clear_platform();
  217. memset(&platform_added, 0, sizeof(platform_added));
  218. for (size_t i = 0; i <= std::size(platforms); ++i) {
  219. for (size_t j = 0; j < std::size(platforms); ++j) {
  220. const bool expected = platform_added[j];
  221. const bool result = internal::CheckStudyPlatform(filter, platforms[j]);
  222. EXPECT_EQ(expected, result) << "Case " << i << "," << j << " failed!";
  223. }
  224. if (i < std::size(platforms)) {
  225. const int index = std::size(platforms) - i - 1;
  226. filter.add_platform(platforms[index]);
  227. platform_added[index] = true;
  228. }
  229. }
  230. }
  231. TEST(VariationsStudyFilteringTest, CheckStudyLowEndDevice) {
  232. Study::Filter filter;
  233. // Check that if the filter is not set, study applies to either low end value.
  234. EXPECT_TRUE(internal::CheckStudyLowEndDevice(filter, true));
  235. EXPECT_TRUE(internal::CheckStudyLowEndDevice(filter, false));
  236. filter.set_is_low_end_device(true);
  237. EXPECT_TRUE(internal::CheckStudyLowEndDevice(filter, true));
  238. EXPECT_FALSE(internal::CheckStudyLowEndDevice(filter, false));
  239. filter.set_is_low_end_device(false);
  240. EXPECT_FALSE(internal::CheckStudyLowEndDevice(filter, true));
  241. EXPECT_TRUE(internal::CheckStudyLowEndDevice(filter, false));
  242. }
  243. TEST(VariationsStudyFilteringTest, CheckStudyEnterprise) {
  244. Study::Filter filter;
  245. ClientFilterableState client_non_enterprise(
  246. base::BindOnce([] { return false; }));
  247. ClientFilterableState client_enterprise(base::BindOnce([] { return true; }));
  248. // Check that if the filter is not set, study applies to both enterprise and
  249. // non-enterprise clients.
  250. EXPECT_TRUE(internal::CheckStudyEnterprise(filter, client_enterprise));
  251. EXPECT_TRUE(internal::CheckStudyEnterprise(filter, client_non_enterprise));
  252. filter.set_is_enterprise(true);
  253. EXPECT_TRUE(internal::CheckStudyEnterprise(filter, client_enterprise));
  254. EXPECT_FALSE(internal::CheckStudyEnterprise(filter, client_non_enterprise));
  255. filter.set_is_enterprise(false);
  256. EXPECT_FALSE(internal::CheckStudyEnterprise(filter, client_enterprise));
  257. EXPECT_TRUE(internal::CheckStudyEnterprise(filter, client_non_enterprise));
  258. }
  259. TEST(VariationsStudyFilteringTest, CheckStudyPolicyRestriction) {
  260. Study::Filter filter;
  261. // Check that if the filter is not set, study applies to clients with no
  262. // restrictive policy.
  263. EXPECT_TRUE(internal::CheckStudyPolicyRestriction(
  264. filter, RestrictionPolicy::NO_RESTRICTIONS));
  265. EXPECT_FALSE(internal::CheckStudyPolicyRestriction(
  266. filter, RestrictionPolicy::CRITICAL_ONLY));
  267. EXPECT_FALSE(
  268. internal::CheckStudyPolicyRestriction(filter, RestrictionPolicy::ALL));
  269. // Explicitly set to none filter should be the same as no filter.
  270. filter.set_policy_restriction(Study::NONE);
  271. EXPECT_TRUE(internal::CheckStudyPolicyRestriction(
  272. filter, RestrictionPolicy::NO_RESTRICTIONS));
  273. EXPECT_FALSE(internal::CheckStudyPolicyRestriction(
  274. filter, RestrictionPolicy::CRITICAL_ONLY));
  275. EXPECT_FALSE(
  276. internal::CheckStudyPolicyRestriction(filter, RestrictionPolicy::ALL));
  277. // If the filter is set to CRITICAL then apply it to all clients that do not
  278. // disable all experiements.
  279. filter.set_policy_restriction(Study::CRITICAL);
  280. EXPECT_TRUE(internal::CheckStudyPolicyRestriction(
  281. filter, RestrictionPolicy::NO_RESTRICTIONS));
  282. EXPECT_TRUE(internal::CheckStudyPolicyRestriction(
  283. filter, RestrictionPolicy::CRITICAL_ONLY));
  284. EXPECT_FALSE(
  285. internal::CheckStudyPolicyRestriction(filter, RestrictionPolicy::ALL));
  286. // If the filter is set to CRITICAL_ONLY then apply it only to clients that
  287. // have requested critical studies but not to clients with no or full
  288. // restrictions.
  289. filter.set_policy_restriction(Study::CRITICAL_ONLY);
  290. EXPECT_FALSE(internal::CheckStudyPolicyRestriction(
  291. filter, RestrictionPolicy::NO_RESTRICTIONS));
  292. EXPECT_TRUE(internal::CheckStudyPolicyRestriction(
  293. filter, RestrictionPolicy::CRITICAL_ONLY));
  294. EXPECT_FALSE(
  295. internal::CheckStudyPolicyRestriction(filter, RestrictionPolicy::ALL));
  296. }
  297. TEST(VariationsStudyFilteringTest, CheckStudyStartDate) {
  298. const base::Time now = base::Time::Now();
  299. const base::TimeDelta delta = base::Hours(1);
  300. const struct {
  301. const base::Time start_date;
  302. bool expected_result;
  303. } start_test_cases[] = {
  304. {now - delta, true},
  305. // Note, the proto start_date is truncated to seconds, but the reference
  306. // date isn't.
  307. {now, true},
  308. {now + delta, false},
  309. };
  310. Study::Filter filter;
  311. // Start date not set should result in true.
  312. EXPECT_TRUE(internal::CheckStudyStartDate(filter, now));
  313. for (size_t i = 0; i < std::size(start_test_cases); ++i) {
  314. filter.set_start_date(TimeToProtoTime(start_test_cases[i].start_date));
  315. const bool result = internal::CheckStudyStartDate(filter, now);
  316. EXPECT_EQ(start_test_cases[i].expected_result, result)
  317. << "Case " << i << " failed!";
  318. }
  319. }
  320. TEST(VariationsStudyFilteringTest, CheckStudyEndDate) {
  321. const base::Time now = base::Time::Now();
  322. const base::TimeDelta delta = base::Hours(1);
  323. const struct {
  324. const base::Time end_date;
  325. bool expected_result;
  326. } start_test_cases[] = {
  327. {now - delta, false}, {now + delta, true},
  328. };
  329. Study::Filter filter;
  330. // End date not set should result in true.
  331. EXPECT_TRUE(internal::CheckStudyEndDate(filter, now));
  332. for (size_t i = 0; i < std::size(start_test_cases); ++i) {
  333. filter.set_end_date(TimeToProtoTime(start_test_cases[i].end_date));
  334. const bool result = internal::CheckStudyEndDate(filter, now);
  335. EXPECT_EQ(start_test_cases[i].expected_result, result) << "Case " << i
  336. << " failed!";
  337. }
  338. }
  339. TEST(VariationsStudyFilteringTest, CheckStudyOSVersion) {
  340. const struct {
  341. const char* min_os_version;
  342. const char* os_version;
  343. bool expected_result;
  344. } min_test_cases[] = {
  345. {"1.2.2", "1.2.3", true},
  346. {"1.2.3", "1.2.3", true},
  347. {"1.2.4", "1.2.3", false},
  348. {"1.3.2", "1.2.3", false},
  349. {"2.1.2", "1.2.3", false},
  350. {"0.3.4", "1.2.3", true},
  351. // Wildcards.
  352. {"1.*", "1.2.3", true},
  353. {"1.2.*", "1.2.3", true},
  354. {"1.2.3.*", "1.2.3", true},
  355. {"1.2.4.*", "1.2.3", false},
  356. {"2.*", "1.2.3", false},
  357. {"0.3.*", "1.2.3", true},
  358. };
  359. const struct {
  360. const char* max_os_version;
  361. const char* os_version;
  362. bool expected_result;
  363. } max_test_cases[] = {
  364. {"1.2.2", "1.2.3", false},
  365. {"1.2.3", "1.2.3", true},
  366. {"1.2.4", "1.2.3", true},
  367. {"2.1.1", "1.2.3", true},
  368. {"2.1.1", "2.3.4", false},
  369. // Wildcards
  370. {"2.1.*", "2.3.4", false},
  371. {"2.*", "2.3.4", true},
  372. {"2.3.*", "2.3.4", true},
  373. {"2.3.4.*", "2.3.4", true},
  374. {"2.3.4.0.*", "2.3.4", true},
  375. {"2.4.*", "2.3.4", true},
  376. {"1.3.*", "2.3.4", false},
  377. {"1.*", "2.3.4", false},
  378. };
  379. Study::Filter filter;
  380. // Min/max version not set should result in true.
  381. EXPECT_TRUE(internal::CheckStudyOSVersion(filter, base::Version("1.2.3")));
  382. for (size_t i = 0; i < std::size(min_test_cases); ++i) {
  383. filter.set_min_os_version(min_test_cases[i].min_os_version);
  384. const bool result = internal::CheckStudyOSVersion(
  385. filter, base::Version(min_test_cases[i].os_version));
  386. EXPECT_EQ(min_test_cases[i].expected_result, result)
  387. << "Min OS version case " << i << " failed!";
  388. }
  389. filter.clear_min_os_version();
  390. for (size_t i = 0; i < std::size(max_test_cases); ++i) {
  391. filter.set_max_os_version(max_test_cases[i].max_os_version);
  392. const bool result = internal::CheckStudyOSVersion(
  393. filter, base::Version(max_test_cases[i].os_version));
  394. EXPECT_EQ(max_test_cases[i].expected_result, result)
  395. << "Max OS version case " << i << " failed!";
  396. }
  397. // Check intersection semantics.
  398. for (size_t i = 0; i < std::size(min_test_cases); ++i) {
  399. for (size_t j = 0; j < std::size(max_test_cases); ++j) {
  400. filter.set_min_os_version(min_test_cases[i].min_os_version);
  401. filter.set_max_os_version(max_test_cases[j].max_os_version);
  402. if (!min_test_cases[i].expected_result) {
  403. const bool result = internal::CheckStudyOSVersion(
  404. filter, base::Version(min_test_cases[i].os_version));
  405. EXPECT_FALSE(result) << "Case " << i << "," << j << " failed!";
  406. }
  407. if (!max_test_cases[j].expected_result) {
  408. const bool result = internal::CheckStudyOSVersion(
  409. filter, base::Version(max_test_cases[j].os_version));
  410. EXPECT_FALSE(result) << "Case " << i << "," << j << " failed!";
  411. }
  412. }
  413. }
  414. }
  415. TEST(VariationsStudyFilteringTest, CheckStudyMalformedOSVersion) {
  416. Study::Filter filter;
  417. filter.set_min_os_version("1.2.0");
  418. EXPECT_FALSE(internal::CheckStudyOSVersion(filter, base::Version("1.2.a")));
  419. EXPECT_TRUE(internal::CheckStudyOSVersion(filter, base::Version("1.2.3")));
  420. filter.clear_min_os_version();
  421. filter.set_max_os_version("1.2.3");
  422. EXPECT_FALSE(internal::CheckStudyOSVersion(filter, base::Version("1.2.a")));
  423. EXPECT_TRUE(internal::CheckStudyOSVersion(filter, base::Version("1.2.3")));
  424. }
  425. TEST(VariationsStudyFilteringTest, CheckStudyVersion) {
  426. const struct {
  427. const char* min_version;
  428. const char* version;
  429. bool expected_result;
  430. } min_test_cases[] = {
  431. {"1.2.2", "1.2.3", true},
  432. {"1.2.3", "1.2.3", true},
  433. {"1.2.4", "1.2.3", false},
  434. {"1.3.2", "1.2.3", false},
  435. {"2.1.2", "1.2.3", false},
  436. {"0.3.4", "1.2.3", true},
  437. // Wildcards.
  438. {"1.*", "1.2.3", true},
  439. {"1.2.*", "1.2.3", true},
  440. {"1.2.3.*", "1.2.3", true},
  441. {"1.2.4.*", "1.2.3", false},
  442. {"2.*", "1.2.3", false},
  443. {"0.3.*", "1.2.3", true},
  444. };
  445. const struct {
  446. const char* max_version;
  447. const char* version;
  448. bool expected_result;
  449. } max_test_cases[] = {
  450. {"1.2.2", "1.2.3", false},
  451. {"1.2.3", "1.2.3", true},
  452. {"1.2.4", "1.2.3", true},
  453. {"2.1.1", "1.2.3", true},
  454. {"2.1.1", "2.3.4", false},
  455. // Wildcards
  456. {"2.1.*", "2.3.4", false},
  457. {"2.*", "2.3.4", true},
  458. {"2.3.*", "2.3.4", true},
  459. {"2.3.4.*", "2.3.4", true},
  460. {"2.3.4.0.*", "2.3.4", true},
  461. {"2.4.*", "2.3.4", true},
  462. {"1.3.*", "2.3.4", false},
  463. {"1.*", "2.3.4", false},
  464. };
  465. Study::Filter filter;
  466. // Min/max version not set should result in true.
  467. EXPECT_TRUE(internal::CheckStudyVersion(filter, base::Version("1.2.3")));
  468. for (size_t i = 0; i < std::size(min_test_cases); ++i) {
  469. filter.set_min_version(min_test_cases[i].min_version);
  470. const bool result = internal::CheckStudyVersion(
  471. filter, base::Version(min_test_cases[i].version));
  472. EXPECT_EQ(min_test_cases[i].expected_result, result) <<
  473. "Min. version case " << i << " failed!";
  474. }
  475. filter.clear_min_version();
  476. for (size_t i = 0; i < std::size(max_test_cases); ++i) {
  477. filter.set_max_version(max_test_cases[i].max_version);
  478. const bool result = internal::CheckStudyVersion(
  479. filter, base::Version(max_test_cases[i].version));
  480. EXPECT_EQ(max_test_cases[i].expected_result, result) <<
  481. "Max version case " << i << " failed!";
  482. }
  483. // Check intersection semantics.
  484. for (size_t i = 0; i < std::size(min_test_cases); ++i) {
  485. for (size_t j = 0; j < std::size(max_test_cases); ++j) {
  486. filter.set_min_version(min_test_cases[i].min_version);
  487. filter.set_max_version(max_test_cases[j].max_version);
  488. if (!min_test_cases[i].expected_result) {
  489. const bool result = internal::CheckStudyVersion(
  490. filter, base::Version(min_test_cases[i].version));
  491. EXPECT_FALSE(result) << "Case " << i << "," << j << " failed!";
  492. }
  493. if (!max_test_cases[j].expected_result) {
  494. const bool result = internal::CheckStudyVersion(
  495. filter, base::Version(max_test_cases[j].version));
  496. EXPECT_FALSE(result) << "Case " << i << "," << j << " failed!";
  497. }
  498. }
  499. }
  500. }
  501. TEST(VariationsStudyFilteringTest, CheckStudyHardwareClass) {
  502. struct {
  503. const char* hardware_class;
  504. const char* exclude_hardware_class;
  505. const char* actual_hardware_class;
  506. bool expected_result;
  507. } test_cases[] = {
  508. // Neither filtered nor excluded set:
  509. // True since empty is always a match.
  510. {"", "", "fancy INTEL pear device", true},
  511. {"", "", "", true},
  512. // Filtered set:
  513. {"apple,pear,orange", "", "apple", true},
  514. {"apple,pear,orange", "", "aPPle", true},
  515. {"apple,pear,orange", "", "fancy INTEL pear device", false},
  516. {"apple,pear,orange", "", "fancy INTEL GRAPE device", false},
  517. // Somehow tagged as both, but still valid.
  518. {"apple,pear,orange", "", "fancy INTEL pear GRAPE device", false},
  519. // Substring, which should not match.
  520. {"apple,pear,orange", "", "fancy INTEL SNapple device", false},
  521. // Empty, which is what would happen for non ChromeOS platforms.
  522. {"apple,pear,orange", "", "", false},
  523. // Excluded set:
  524. {"", "apple,pear,orange", "apple", false},
  525. {"", "apple,pear,orange", "fancy INTEL pear device", true},
  526. {"", "apple,pear,orange", "fancy INTEL GRAPE device", true},
  527. // Empty.
  528. {"", "apple,pear,orange", "", true},
  529. // Not testing when both are set as it should never occur and should be
  530. // considered undefined.
  531. };
  532. for (const auto& test : test_cases) {
  533. Study::Filter filter;
  534. for (const auto& hw_class : SplitFilterString(test.hardware_class))
  535. filter.add_hardware_class(hw_class);
  536. for (const auto& hw_class : SplitFilterString(test.exclude_hardware_class))
  537. filter.add_exclude_hardware_class(hw_class);
  538. EXPECT_EQ(test.expected_result, internal::CheckStudyHardwareClass(
  539. filter, test.actual_hardware_class))
  540. << "hardware_class=" << test.hardware_class << " "
  541. << "exclude_hardware_class=" << test.exclude_hardware_class << " "
  542. << "actual_hardware_class=" << test.actual_hardware_class;
  543. }
  544. }
  545. TEST(VariationsStudyFilteringTest, CheckStudyCountry) {
  546. struct {
  547. const char* country;
  548. const char* exclude_country;
  549. const char* actual_country;
  550. bool expected_result;
  551. } test_cases[] = {
  552. // Neither filtered nor excluded set:
  553. // True since empty is always a match.
  554. {"", "", "us", true},
  555. {"", "", "", true},
  556. // Filtered set:
  557. {"us", "", "us", true},
  558. {"br,ca,us", "", "us", true},
  559. {"br,ca,us", "", "in", false},
  560. // Empty, which is what would happen if no country was returned from the
  561. // server.
  562. {"br,ca,us", "", "", false},
  563. // Excluded set:
  564. {"", "us", "us", false},
  565. {"", "br,ca,us", "us", false},
  566. {"", "br,ca,us", "in", true},
  567. // Empty, which is what would happen if no country was returned from the
  568. // server.
  569. {"", "br,ca,us", "", true},
  570. // Not testing when both are set as it should never occur and should be
  571. // considered undefined.
  572. };
  573. for (const auto& test : test_cases) {
  574. Study::Filter filter;
  575. for (const std::string& country : SplitFilterString(test.country))
  576. filter.add_country(country);
  577. for (const std::string& country : SplitFilterString(test.exclude_country))
  578. filter.add_exclude_country(country);
  579. EXPECT_EQ(test.expected_result,
  580. internal::CheckStudyCountry(filter, test.actual_country));
  581. }
  582. }
  583. TEST(VariationsStudyFilteringTest, FilterAndValidateStudies) {
  584. const std::string kTrial1Name = "A";
  585. const std::string kGroup1Name = "Group1";
  586. const std::string kTrial3Name = "B";
  587. VariationsSeed seed;
  588. Study* study1 = seed.add_study();
  589. study1->set_name(kTrial1Name);
  590. study1->set_default_experiment_name("Default");
  591. AddExperiment(kGroup1Name, 100, study1);
  592. AddExperiment("Default", 0, study1);
  593. Study* study2 = seed.add_study();
  594. *study2 = *study1;
  595. study2->mutable_experiment(0)->set_name("Bam");
  596. ASSERT_EQ(seed.study(0).name(), seed.study(1).name());
  597. Study* study3 = seed.add_study();
  598. study3->set_name(kTrial3Name);
  599. study3->set_default_experiment_name("Default");
  600. AddExperiment("A", 10, study3);
  601. AddExperiment("Default", 25, study3);
  602. ClientFilterableState client_state(base::BindOnce([] { return false; }));
  603. client_state.locale = "en-CA";
  604. client_state.reference_date = base::Time::Now();
  605. client_state.version = base::Version("20.0.0.0");
  606. client_state.channel = Study::STABLE;
  607. client_state.form_factor = Study::DESKTOP;
  608. client_state.platform = Study::PLATFORM_ANDROID;
  609. std::vector<ProcessedStudy> processed_studies;
  610. FilterAndValidateStudies(seed, client_state, VariationsLayers(),
  611. &processed_studies);
  612. // Check that only the first kTrial1Name study was kept.
  613. ASSERT_EQ(2U, processed_studies.size());
  614. EXPECT_EQ(kTrial1Name, processed_studies[0].study()->name());
  615. EXPECT_EQ(kGroup1Name, processed_studies[0].study()->experiment(0).name());
  616. EXPECT_EQ(kTrial3Name, processed_studies[1].study()->name());
  617. }
  618. TEST(VariationsStudyFilteringTest, FilterAndValidateStudiesWithBadFilters) {
  619. const char* versions[] = {
  620. "invalid",
  621. "1.invalid.0",
  622. "0.invalid.0",
  623. "\001\000\000\003",
  624. };
  625. VariationsSeed seed;
  626. Study baseStudy;
  627. baseStudy.set_default_experiment_name("Default");
  628. AddExperiment("Default", 100, &baseStudy);
  629. baseStudy.mutable_filter()->add_platform(Study::PLATFORM_ANDROID);
  630. // Add studies with invalid min_versions.
  631. for (size_t i = 0; i < std::size(versions); ++i) {
  632. Study* study = seed.add_study();
  633. *study = baseStudy;
  634. study->set_name(
  635. base::StrCat({"min_version_study_", base::NumberToString(i)}));
  636. study->mutable_filter()->set_min_version(versions[i]);
  637. }
  638. // Add studies with invalid max_versions.
  639. for (size_t i = 0; i < std::size(versions); ++i) {
  640. Study* study = seed.add_study();
  641. *study = baseStudy;
  642. study->set_name(
  643. base::StrCat({"max_version_study_", base::NumberToString(i)}));
  644. study->mutable_filter()->set_max_version(versions[i]);
  645. }
  646. // Add studies with invalid min_os_versions.
  647. for (size_t i = 0; i < std::size(versions); ++i) {
  648. Study* study = seed.add_study();
  649. *study = baseStudy;
  650. study->set_name(
  651. base::StrCat({"min_os_version_study_", base::NumberToString(i)}));
  652. study->mutable_filter()->set_min_os_version(versions[i]);
  653. }
  654. // Add studies with invalid max_os_versions.
  655. for (size_t i = 0; i < std::size(versions); ++i) {
  656. Study* study = seed.add_study();
  657. *study = baseStudy;
  658. study->set_name(
  659. base::StrCat({"max_os_version_study_", base::NumberToString(i)}));
  660. study->mutable_filter()->set_max_os_version(versions[i]);
  661. }
  662. ClientFilterableState client_state(base::BindOnce([] { return false; }));
  663. client_state.locale = "en-CA";
  664. client_state.reference_date = base::Time::Now();
  665. client_state.version = base::Version("20.0.0.0");
  666. client_state.channel = Study::STABLE;
  667. client_state.form_factor = Study::DESKTOP;
  668. client_state.platform = Study::PLATFORM_ANDROID;
  669. client_state.os_version = base::Version("1.2.3");
  670. base::HistogramTester histogram_tester;
  671. std::vector<ProcessedStudy> processed_studies;
  672. FilterAndValidateStudies(seed, client_state, VariationsLayers(),
  673. &processed_studies);
  674. ASSERT_EQ(0U, processed_studies.size());
  675. histogram_tester.ExpectTotalCount("Variations.InvalidStudyReason",
  676. std::size(versions) * 4);
  677. histogram_tester.ExpectBucketCount("Variations.InvalidStudyReason", 0,
  678. std::size(versions));
  679. histogram_tester.ExpectBucketCount("Variations.InvalidStudyReason", 1,
  680. std::size(versions));
  681. histogram_tester.ExpectBucketCount("Variations.InvalidStudyReason", 2,
  682. std::size(versions));
  683. histogram_tester.ExpectBucketCount("Variations.InvalidStudyReason", 3,
  684. std::size(versions));
  685. }
  686. TEST(VariationsStudyFilteringTest, FilterAndValidateStudiesWithBlankStudyName) {
  687. VariationsSeed seed;
  688. Study* study = seed.add_study();
  689. study->set_name("");
  690. study->set_default_experiment_name("Default");
  691. AddExperiment("A", 100, study);
  692. AddExperiment("Default", 0, study);
  693. study->mutable_filter()->add_platform(Study::PLATFORM_ANDROID);
  694. ClientFilterableState client_state(base::BindOnce([] { return false; }));
  695. client_state.locale = "en-CA";
  696. client_state.reference_date = base::Time::Now();
  697. client_state.version = base::Version("20.0.0.0");
  698. client_state.channel = Study::STABLE;
  699. client_state.form_factor = Study::PHONE;
  700. client_state.platform = Study::PLATFORM_ANDROID;
  701. base::HistogramTester histogram_tester;
  702. std::vector<ProcessedStudy> processed_studies;
  703. FilterAndValidateStudies(seed, client_state, VariationsLayers(),
  704. &processed_studies);
  705. ASSERT_EQ(0U, processed_studies.size());
  706. histogram_tester.ExpectUniqueSample("Variations.InvalidStudyReason", 8, 1);
  707. }
  708. TEST(VariationsStudyFilteringTest, FilterAndValidateStudiesWithCountry) {
  709. const char kSessionCountry[] = "ca";
  710. const char kPermanentCountry[] = "us";
  711. struct {
  712. Study::Consistency consistency;
  713. const char* filter_country;
  714. const char* filter_exclude_country;
  715. bool expect_study_kept;
  716. } test_cases[] = {
  717. // Country-agnostic studies should be kept regardless of country.
  718. {Study::SESSION, nullptr, nullptr, true},
  719. {Study::PERMANENT, nullptr, nullptr, true},
  720. // Session-consistency studies should obey the country code in the seed.
  721. {Study::SESSION, kSessionCountry, nullptr, true},
  722. {Study::SESSION, nullptr, kSessionCountry, false},
  723. {Study::SESSION, kPermanentCountry, nullptr, false},
  724. {Study::SESSION, nullptr, kPermanentCountry, true},
  725. // Permanent-consistency studies should obey the permanent-consistency
  726. // country code.
  727. {Study::PERMANENT, kPermanentCountry, nullptr, true},
  728. {Study::PERMANENT, nullptr, kPermanentCountry, false},
  729. {Study::PERMANENT, kSessionCountry, nullptr, false},
  730. {Study::PERMANENT, nullptr, kSessionCountry, true},
  731. };
  732. for (const auto& test : test_cases) {
  733. VariationsSeed seed;
  734. Study* study = seed.add_study();
  735. study->set_name("study");
  736. study->set_default_experiment_name("Default");
  737. AddExperiment("Default", 100, study);
  738. study->set_consistency(test.consistency);
  739. study->mutable_filter()->add_platform(Study::PLATFORM_ANDROID);
  740. if (test.filter_country)
  741. study->mutable_filter()->add_country(test.filter_country);
  742. if (test.filter_exclude_country)
  743. study->mutable_filter()->add_exclude_country(test.filter_exclude_country);
  744. ClientFilterableState client_state(base::BindOnce([] { return false; }));
  745. client_state.locale = "en-CA";
  746. client_state.reference_date = base::Time::Now();
  747. client_state.version = base::Version("20.0.0.0");
  748. client_state.channel = Study::STABLE;
  749. client_state.form_factor = Study::PHONE;
  750. client_state.platform = Study::PLATFORM_ANDROID;
  751. client_state.session_consistency_country = kSessionCountry;
  752. client_state.permanent_consistency_country = kPermanentCountry;
  753. std::vector<ProcessedStudy> processed_studies;
  754. FilterAndValidateStudies(seed, client_state, VariationsLayers(),
  755. &processed_studies);
  756. EXPECT_EQ(test.expect_study_kept, !processed_studies.empty());
  757. }
  758. }
  759. TEST(VariationsStudyFilteringTest, GetClientCountryForStudy_Session) {
  760. ClientFilterableState client_state(base::BindOnce([] { return false; }));
  761. client_state.session_consistency_country = "session_country";
  762. client_state.permanent_consistency_country = "permanent_country";
  763. Study study;
  764. study.set_consistency(Study::SESSION);
  765. EXPECT_EQ("session_country",
  766. internal::GetClientCountryForStudy(study, client_state));
  767. }
  768. TEST(VariationsStudyFilteringTest, GetClientCountryForStudy_Permanent) {
  769. ClientFilterableState client_state(base::BindOnce([] { return false; }));
  770. client_state.session_consistency_country = "session_country";
  771. client_state.permanent_consistency_country = "permanent_country";
  772. Study study;
  773. study.set_consistency(Study::PERMANENT);
  774. EXPECT_EQ("permanent_country",
  775. internal::GetClientCountryForStudy(study, client_state));
  776. }
  777. TEST(VariationsStudyFilteringTest, IsStudyExpired) {
  778. const base::Time now = base::Time::Now();
  779. const base::TimeDelta delta = base::Hours(1);
  780. const struct {
  781. const base::Time expiry_date;
  782. bool expected_result;
  783. } expiry_test_cases[] = {
  784. { now - delta, true },
  785. { now, true },
  786. { now + delta, false },
  787. };
  788. Study study;
  789. // Expiry date not set should result in false.
  790. EXPECT_FALSE(internal::IsStudyExpired(study, now));
  791. for (size_t i = 0; i < std::size(expiry_test_cases); ++i) {
  792. study.set_expiry_date(TimeToProtoTime(expiry_test_cases[i].expiry_date));
  793. const bool result = internal::IsStudyExpired(study, now);
  794. EXPECT_EQ(expiry_test_cases[i].expected_result, result)
  795. << "Case " << i << " failed!";
  796. }
  797. }
  798. TEST(VariationsStudyFilteringTest, ValidateStudy) {
  799. Study study;
  800. study.set_name("study");
  801. study.set_default_experiment_name("def");
  802. AddExperiment("abc", 100, &study);
  803. Study::Experiment* default_group = AddExperiment("def", 200, &study);
  804. ProcessedStudy processed_study;
  805. EXPECT_TRUE(processed_study.Init(&study, false));
  806. EXPECT_EQ(300, processed_study.total_probability());
  807. // Min version checks.
  808. study.mutable_filter()->set_min_version("1.2.3.*");
  809. EXPECT_TRUE(processed_study.Init(&study, false));
  810. study.mutable_filter()->set_min_version("1.*.3");
  811. EXPECT_FALSE(processed_study.Init(&study, false));
  812. study.mutable_filter()->set_min_version("1.2.3");
  813. EXPECT_TRUE(processed_study.Init(&study, false));
  814. // Max version checks.
  815. study.mutable_filter()->set_max_version("2.3.4.*");
  816. EXPECT_TRUE(processed_study.Init(&study, false));
  817. study.mutable_filter()->set_max_version("*.3");
  818. EXPECT_FALSE(processed_study.Init(&study, false));
  819. study.mutable_filter()->set_max_version("2.3.4");
  820. EXPECT_TRUE(processed_study.Init(&study, false));
  821. // A blank default study is allowed.
  822. study.clear_default_experiment_name();
  823. EXPECT_TRUE(processed_study.Init(&study, false));
  824. study.set_default_experiment_name("xyz");
  825. EXPECT_FALSE(processed_study.Init(&study, false));
  826. study.set_default_experiment_name("def");
  827. default_group->clear_name();
  828. EXPECT_FALSE(processed_study.Init(&study, false));
  829. default_group->set_name("def");
  830. EXPECT_TRUE(processed_study.Init(&study, false));
  831. Study::Experiment* repeated_group = study.add_experiment();
  832. repeated_group->set_name("abc");
  833. repeated_group->set_probability_weight(1);
  834. EXPECT_FALSE(processed_study.Init(&study, false));
  835. }
  836. } // namespace variations