123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972 |
- // Copyright 2014 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "components/variations/study_filtering.h"
- #include <stddef.h>
- #include <stdint.h>
- #include <string.h>
- #include <vector>
- #include "base/bind.h"
- #include "base/callback_helpers.h"
- #include "base/strings/strcat.h"
- #include "base/strings/string_number_conversions.h"
- #include "base/strings/string_split.h"
- #include "base/test/metrics/histogram_tester.h"
- #include "base/time/time.h"
- #include "components/variations/client_filterable_state.h"
- #include "components/variations/processed_study.h"
- #include "components/variations/variations_layers.h"
- #include "testing/gtest/include/gtest/gtest.h"
- namespace variations {
- namespace {
- // Converts |time| to Study proto format.
- int64_t TimeToProtoTime(const base::Time& time) {
- return (time - base::Time::UnixEpoch()).InSeconds();
- }
- // Adds an experiment to |study| with the specified |name| and |probability|.
- Study::Experiment* AddExperiment(const std::string& name,
- int probability,
- Study* study) {
- Study::Experiment* experiment = study->add_experiment();
- experiment->set_name(name);
- experiment->set_probability_weight(probability);
- return experiment;
- }
- std::vector<std::string> SplitFilterString(const std::string& input) {
- return base::SplitString(input, ",", base::TRIM_WHITESPACE,
- base::SPLIT_WANT_ALL);
- }
- } // namespace
- TEST(VariationsStudyFilteringTest, CheckStudyChannel) {
- const Study::Channel channels[] = {
- Study::CANARY, Study::DEV, Study::BETA, Study::STABLE,
- };
- bool channel_added[std::size(channels)] = {false};
- Study::Filter filter;
- // Check in the forwarded order. The loop cond is <= std::size(channels)
- // instead of < so that the result of adding the last channel gets checked.
- for (size_t i = 0; i <= std::size(channels); ++i) {
- for (size_t j = 0; j < std::size(channels); ++j) {
- const bool expected = channel_added[j] || filter.channel_size() == 0;
- const bool result = internal::CheckStudyChannel(filter, channels[j]);
- EXPECT_EQ(expected, result) << "Case " << i << "," << j << " failed!";
- }
- if (i < std::size(channels)) {
- filter.add_channel(channels[i]);
- channel_added[i] = true;
- }
- }
- // Do the same check in the reverse order.
- filter.clear_channel();
- memset(&channel_added, 0, sizeof(channel_added));
- for (size_t i = 0; i <= std::size(channels); ++i) {
- for (size_t j = 0; j < std::size(channels); ++j) {
- const bool expected = channel_added[j] || filter.channel_size() == 0;
- const bool result = internal::CheckStudyChannel(filter, channels[j]);
- EXPECT_EQ(expected, result) << "Case " << i << "," << j << " failed!";
- }
- if (i < std::size(channels)) {
- const int index = std::size(channels) - i - 1;
- filter.add_channel(channels[index]);
- channel_added[index] = true;
- }
- }
- }
- TEST(VariationsStudyFilteringTest, CheckStudyFormFactor) {
- const Study::FormFactor form_factors[] = {Study::DESKTOP, Study::PHONE,
- Study::TABLET, Study::KIOSK,
- Study::MEET_DEVICE};
- ASSERT_EQ(Study::FormFactor_ARRAYSIZE,
- static_cast<int>(std::size(form_factors)));
- bool form_factor_added[std::size(form_factors)] = {false};
- Study::Filter filter;
- for (size_t i = 0; i <= std::size(form_factors); ++i) {
- for (size_t j = 0; j < std::size(form_factors); ++j) {
- const bool expected = form_factor_added[j] ||
- filter.form_factor_size() == 0;
- const bool result = internal::CheckStudyFormFactor(filter,
- form_factors[j]);
- EXPECT_EQ(expected, result) << "form_factor: case " << i << "," << j
- << " failed!";
- }
- if (i < std::size(form_factors)) {
- filter.add_form_factor(form_factors[i]);
- form_factor_added[i] = true;
- }
- }
- // Do the same check in the reverse order.
- filter.clear_form_factor();
- memset(&form_factor_added, 0, sizeof(form_factor_added));
- for (size_t i = 0; i <= std::size(form_factors); ++i) {
- for (size_t j = 0; j < std::size(form_factors); ++j) {
- const bool expected = form_factor_added[j] ||
- filter.form_factor_size() == 0;
- const bool result = internal::CheckStudyFormFactor(filter,
- form_factors[j]);
- EXPECT_EQ(expected, result) << "form_factor: case " << i << "," << j
- << " failed!";
- }
- if (i < std::size(form_factors)) {
- const int index = std::size(form_factors) - i - 1;
- filter.add_form_factor(form_factors[index]);
- form_factor_added[index] = true;
- }
- }
- // Test exclude_form_factors, forward order.
- filter.clear_form_factor();
- bool form_factor_excluded[std::size(form_factors)] = {false};
- for (size_t i = 0; i <= std::size(form_factors); ++i) {
- for (size_t j = 0; j < std::size(form_factors); ++j) {
- const bool expected = filter.exclude_form_factor_size() == 0 ||
- !form_factor_excluded[j];
- const bool result = internal::CheckStudyFormFactor(filter,
- form_factors[j]);
- EXPECT_EQ(expected, result) << "exclude_form_factor: case " << i << ","
- << j << " failed!";
- }
- if (i < std::size(form_factors)) {
- filter.add_exclude_form_factor(form_factors[i]);
- form_factor_excluded[i] = true;
- }
- }
- // Test exclude_form_factors, reverse order.
- filter.clear_exclude_form_factor();
- memset(&form_factor_excluded, 0, sizeof(form_factor_excluded));
- for (size_t i = 0; i <= std::size(form_factors); ++i) {
- for (size_t j = 0; j < std::size(form_factors); ++j) {
- const bool expected = filter.exclude_form_factor_size() == 0 ||
- !form_factor_excluded[j];
- const bool result = internal::CheckStudyFormFactor(filter,
- form_factors[j]);
- EXPECT_EQ(expected, result) << "exclude_form_factor: case " << i << ","
- << j << " failed!";
- }
- if (i < std::size(form_factors)) {
- const int index = std::size(form_factors) - i - 1;
- filter.add_exclude_form_factor(form_factors[index]);
- form_factor_excluded[index] = true;
- }
- }
- }
- TEST(VariationsStudyFilteringTest, CheckStudyLocale) {
- struct {
- const char* filter_locales;
- const char* exclude_locales;
- bool en_us_result;
- bool en_ca_result;
- bool fr_result;
- } test_cases[] = {
- {"en-US", "", true, false, false},
- // Tests that locale overrides exclude_locale, when both are given. This
- // should not occur in practice though.
- {"en-US", "en-US", true, false, false},
- {"en-US,en-CA,fr", "", true, true, true},
- {"en-US,en-CA,en-GB", "", true, true, false},
- {"en-GB,en-CA,en-US", "", true, true, false},
- {"ja,kr,vi", "", false, false, false},
- {"fr-CA", "", false, false, false},
- {"", "", true, true, true},
- {"", "en-US", false, true, true},
- {"", "en-US,en-CA,fr", false, false, false},
- {"", "en-US,en-CA,en-GB", false, false, true},
- {"", "en-GB,en-CA,en-US", false, false, true},
- {"", "ja,kr,vi", true, true, true},
- {"", "fr-CA", true, true, true},
- };
- for (const auto& test : test_cases) {
- Study::Filter filter;
- for (const std::string& locale : SplitFilterString(test.filter_locales))
- filter.add_locale(locale);
- for (const std::string& locale : SplitFilterString(test.exclude_locales))
- filter.add_exclude_locale(locale);
- EXPECT_EQ(test.en_us_result, internal::CheckStudyLocale(filter, "en-US"));
- EXPECT_EQ(test.en_ca_result, internal::CheckStudyLocale(filter, "en-CA"));
- EXPECT_EQ(test.fr_result, internal::CheckStudyLocale(filter, "fr"));
- }
- }
- TEST(VariationsStudyFilteringTest, CheckStudyPlatform) {
- const Study::Platform platforms[] = {Study::PLATFORM_WINDOWS,
- Study::PLATFORM_MAC,
- Study::PLATFORM_LINUX,
- Study::PLATFORM_CHROMEOS,
- Study::PLATFORM_CHROMEOS_LACROS,
- Study::PLATFORM_ANDROID,
- Study::PLATFORM_IOS,
- Study::PLATFORM_ANDROID_WEBLAYER,
- Study::PLATFORM_FUCHSIA,
- Study::PLATFORM_ANDROID_WEBVIEW};
- static_assert(std::size(platforms) == Study::Platform_ARRAYSIZE,
- "|platforms| must include all platforms.");
- bool platform_added[std::size(platforms)] = {false};
- Study::Filter filter;
- // Check in the forwarded order. The loop cond is <= std::size(platforms)
- // instead of < so that the result of adding the last platform gets checked.
- for (size_t i = 0; i <= std::size(platforms); ++i) {
- for (size_t j = 0; j < std::size(platforms); ++j) {
- const bool expected = platform_added[j];
- const bool result = internal::CheckStudyPlatform(filter, platforms[j]);
- EXPECT_EQ(expected, result) << "Case " << i << "," << j << " failed!";
- }
- if (i < std::size(platforms)) {
- filter.add_platform(platforms[i]);
- platform_added[i] = true;
- }
- }
- // Do the same check in the reverse order.
- filter.clear_platform();
- memset(&platform_added, 0, sizeof(platform_added));
- for (size_t i = 0; i <= std::size(platforms); ++i) {
- for (size_t j = 0; j < std::size(platforms); ++j) {
- const bool expected = platform_added[j];
- const bool result = internal::CheckStudyPlatform(filter, platforms[j]);
- EXPECT_EQ(expected, result) << "Case " << i << "," << j << " failed!";
- }
- if (i < std::size(platforms)) {
- const int index = std::size(platforms) - i - 1;
- filter.add_platform(platforms[index]);
- platform_added[index] = true;
- }
- }
- }
- TEST(VariationsStudyFilteringTest, CheckStudyLowEndDevice) {
- Study::Filter filter;
- // Check that if the filter is not set, study applies to either low end value.
- EXPECT_TRUE(internal::CheckStudyLowEndDevice(filter, true));
- EXPECT_TRUE(internal::CheckStudyLowEndDevice(filter, false));
- filter.set_is_low_end_device(true);
- EXPECT_TRUE(internal::CheckStudyLowEndDevice(filter, true));
- EXPECT_FALSE(internal::CheckStudyLowEndDevice(filter, false));
- filter.set_is_low_end_device(false);
- EXPECT_FALSE(internal::CheckStudyLowEndDevice(filter, true));
- EXPECT_TRUE(internal::CheckStudyLowEndDevice(filter, false));
- }
- TEST(VariationsStudyFilteringTest, CheckStudyEnterprise) {
- Study::Filter filter;
- ClientFilterableState client_non_enterprise(
- base::BindOnce([] { return false; }));
- ClientFilterableState client_enterprise(base::BindOnce([] { return true; }));
- // Check that if the filter is not set, study applies to both enterprise and
- // non-enterprise clients.
- EXPECT_TRUE(internal::CheckStudyEnterprise(filter, client_enterprise));
- EXPECT_TRUE(internal::CheckStudyEnterprise(filter, client_non_enterprise));
- filter.set_is_enterprise(true);
- EXPECT_TRUE(internal::CheckStudyEnterprise(filter, client_enterprise));
- EXPECT_FALSE(internal::CheckStudyEnterprise(filter, client_non_enterprise));
- filter.set_is_enterprise(false);
- EXPECT_FALSE(internal::CheckStudyEnterprise(filter, client_enterprise));
- EXPECT_TRUE(internal::CheckStudyEnterprise(filter, client_non_enterprise));
- }
- TEST(VariationsStudyFilteringTest, CheckStudyPolicyRestriction) {
- Study::Filter filter;
- // Check that if the filter is not set, study applies to clients with no
- // restrictive policy.
- EXPECT_TRUE(internal::CheckStudyPolicyRestriction(
- filter, RestrictionPolicy::NO_RESTRICTIONS));
- EXPECT_FALSE(internal::CheckStudyPolicyRestriction(
- filter, RestrictionPolicy::CRITICAL_ONLY));
- EXPECT_FALSE(
- internal::CheckStudyPolicyRestriction(filter, RestrictionPolicy::ALL));
- // Explicitly set to none filter should be the same as no filter.
- filter.set_policy_restriction(Study::NONE);
- EXPECT_TRUE(internal::CheckStudyPolicyRestriction(
- filter, RestrictionPolicy::NO_RESTRICTIONS));
- EXPECT_FALSE(internal::CheckStudyPolicyRestriction(
- filter, RestrictionPolicy::CRITICAL_ONLY));
- EXPECT_FALSE(
- internal::CheckStudyPolicyRestriction(filter, RestrictionPolicy::ALL));
- // If the filter is set to CRITICAL then apply it to all clients that do not
- // disable all experiements.
- filter.set_policy_restriction(Study::CRITICAL);
- EXPECT_TRUE(internal::CheckStudyPolicyRestriction(
- filter, RestrictionPolicy::NO_RESTRICTIONS));
- EXPECT_TRUE(internal::CheckStudyPolicyRestriction(
- filter, RestrictionPolicy::CRITICAL_ONLY));
- EXPECT_FALSE(
- internal::CheckStudyPolicyRestriction(filter, RestrictionPolicy::ALL));
- // If the filter is set to CRITICAL_ONLY then apply it only to clients that
- // have requested critical studies but not to clients with no or full
- // restrictions.
- filter.set_policy_restriction(Study::CRITICAL_ONLY);
- EXPECT_FALSE(internal::CheckStudyPolicyRestriction(
- filter, RestrictionPolicy::NO_RESTRICTIONS));
- EXPECT_TRUE(internal::CheckStudyPolicyRestriction(
- filter, RestrictionPolicy::CRITICAL_ONLY));
- EXPECT_FALSE(
- internal::CheckStudyPolicyRestriction(filter, RestrictionPolicy::ALL));
- }
- TEST(VariationsStudyFilteringTest, CheckStudyStartDate) {
- const base::Time now = base::Time::Now();
- const base::TimeDelta delta = base::Hours(1);
- const struct {
- const base::Time start_date;
- bool expected_result;
- } start_test_cases[] = {
- {now - delta, true},
- // Note, the proto start_date is truncated to seconds, but the reference
- // date isn't.
- {now, true},
- {now + delta, false},
- };
- Study::Filter filter;
- // Start date not set should result in true.
- EXPECT_TRUE(internal::CheckStudyStartDate(filter, now));
- for (size_t i = 0; i < std::size(start_test_cases); ++i) {
- filter.set_start_date(TimeToProtoTime(start_test_cases[i].start_date));
- const bool result = internal::CheckStudyStartDate(filter, now);
- EXPECT_EQ(start_test_cases[i].expected_result, result)
- << "Case " << i << " failed!";
- }
- }
- TEST(VariationsStudyFilteringTest, CheckStudyEndDate) {
- const base::Time now = base::Time::Now();
- const base::TimeDelta delta = base::Hours(1);
- const struct {
- const base::Time end_date;
- bool expected_result;
- } start_test_cases[] = {
- {now - delta, false}, {now + delta, true},
- };
- Study::Filter filter;
- // End date not set should result in true.
- EXPECT_TRUE(internal::CheckStudyEndDate(filter, now));
- for (size_t i = 0; i < std::size(start_test_cases); ++i) {
- filter.set_end_date(TimeToProtoTime(start_test_cases[i].end_date));
- const bool result = internal::CheckStudyEndDate(filter, now);
- EXPECT_EQ(start_test_cases[i].expected_result, result) << "Case " << i
- << " failed!";
- }
- }
- TEST(VariationsStudyFilteringTest, CheckStudyOSVersion) {
- const struct {
- const char* min_os_version;
- const char* os_version;
- bool expected_result;
- } min_test_cases[] = {
- {"1.2.2", "1.2.3", true},
- {"1.2.3", "1.2.3", true},
- {"1.2.4", "1.2.3", false},
- {"1.3.2", "1.2.3", false},
- {"2.1.2", "1.2.3", false},
- {"0.3.4", "1.2.3", true},
- // Wildcards.
- {"1.*", "1.2.3", true},
- {"1.2.*", "1.2.3", true},
- {"1.2.3.*", "1.2.3", true},
- {"1.2.4.*", "1.2.3", false},
- {"2.*", "1.2.3", false},
- {"0.3.*", "1.2.3", true},
- };
- const struct {
- const char* max_os_version;
- const char* os_version;
- bool expected_result;
- } max_test_cases[] = {
- {"1.2.2", "1.2.3", false},
- {"1.2.3", "1.2.3", true},
- {"1.2.4", "1.2.3", true},
- {"2.1.1", "1.2.3", true},
- {"2.1.1", "2.3.4", false},
- // Wildcards
- {"2.1.*", "2.3.4", false},
- {"2.*", "2.3.4", true},
- {"2.3.*", "2.3.4", true},
- {"2.3.4.*", "2.3.4", true},
- {"2.3.4.0.*", "2.3.4", true},
- {"2.4.*", "2.3.4", true},
- {"1.3.*", "2.3.4", false},
- {"1.*", "2.3.4", false},
- };
- Study::Filter filter;
- // Min/max version not set should result in true.
- EXPECT_TRUE(internal::CheckStudyOSVersion(filter, base::Version("1.2.3")));
- for (size_t i = 0; i < std::size(min_test_cases); ++i) {
- filter.set_min_os_version(min_test_cases[i].min_os_version);
- const bool result = internal::CheckStudyOSVersion(
- filter, base::Version(min_test_cases[i].os_version));
- EXPECT_EQ(min_test_cases[i].expected_result, result)
- << "Min OS version case " << i << " failed!";
- }
- filter.clear_min_os_version();
- for (size_t i = 0; i < std::size(max_test_cases); ++i) {
- filter.set_max_os_version(max_test_cases[i].max_os_version);
- const bool result = internal::CheckStudyOSVersion(
- filter, base::Version(max_test_cases[i].os_version));
- EXPECT_EQ(max_test_cases[i].expected_result, result)
- << "Max OS version case " << i << " failed!";
- }
- // Check intersection semantics.
- for (size_t i = 0; i < std::size(min_test_cases); ++i) {
- for (size_t j = 0; j < std::size(max_test_cases); ++j) {
- filter.set_min_os_version(min_test_cases[i].min_os_version);
- filter.set_max_os_version(max_test_cases[j].max_os_version);
- if (!min_test_cases[i].expected_result) {
- const bool result = internal::CheckStudyOSVersion(
- filter, base::Version(min_test_cases[i].os_version));
- EXPECT_FALSE(result) << "Case " << i << "," << j << " failed!";
- }
- if (!max_test_cases[j].expected_result) {
- const bool result = internal::CheckStudyOSVersion(
- filter, base::Version(max_test_cases[j].os_version));
- EXPECT_FALSE(result) << "Case " << i << "," << j << " failed!";
- }
- }
- }
- }
- TEST(VariationsStudyFilteringTest, CheckStudyMalformedOSVersion) {
- Study::Filter filter;
- filter.set_min_os_version("1.2.0");
- EXPECT_FALSE(internal::CheckStudyOSVersion(filter, base::Version("1.2.a")));
- EXPECT_TRUE(internal::CheckStudyOSVersion(filter, base::Version("1.2.3")));
- filter.clear_min_os_version();
- filter.set_max_os_version("1.2.3");
- EXPECT_FALSE(internal::CheckStudyOSVersion(filter, base::Version("1.2.a")));
- EXPECT_TRUE(internal::CheckStudyOSVersion(filter, base::Version("1.2.3")));
- }
- TEST(VariationsStudyFilteringTest, CheckStudyVersion) {
- const struct {
- const char* min_version;
- const char* version;
- bool expected_result;
- } min_test_cases[] = {
- {"1.2.2", "1.2.3", true},
- {"1.2.3", "1.2.3", true},
- {"1.2.4", "1.2.3", false},
- {"1.3.2", "1.2.3", false},
- {"2.1.2", "1.2.3", false},
- {"0.3.4", "1.2.3", true},
- // Wildcards.
- {"1.*", "1.2.3", true},
- {"1.2.*", "1.2.3", true},
- {"1.2.3.*", "1.2.3", true},
- {"1.2.4.*", "1.2.3", false},
- {"2.*", "1.2.3", false},
- {"0.3.*", "1.2.3", true},
- };
- const struct {
- const char* max_version;
- const char* version;
- bool expected_result;
- } max_test_cases[] = {
- {"1.2.2", "1.2.3", false},
- {"1.2.3", "1.2.3", true},
- {"1.2.4", "1.2.3", true},
- {"2.1.1", "1.2.3", true},
- {"2.1.1", "2.3.4", false},
- // Wildcards
- {"2.1.*", "2.3.4", false},
- {"2.*", "2.3.4", true},
- {"2.3.*", "2.3.4", true},
- {"2.3.4.*", "2.3.4", true},
- {"2.3.4.0.*", "2.3.4", true},
- {"2.4.*", "2.3.4", true},
- {"1.3.*", "2.3.4", false},
- {"1.*", "2.3.4", false},
- };
- Study::Filter filter;
- // Min/max version not set should result in true.
- EXPECT_TRUE(internal::CheckStudyVersion(filter, base::Version("1.2.3")));
- for (size_t i = 0; i < std::size(min_test_cases); ++i) {
- filter.set_min_version(min_test_cases[i].min_version);
- const bool result = internal::CheckStudyVersion(
- filter, base::Version(min_test_cases[i].version));
- EXPECT_EQ(min_test_cases[i].expected_result, result) <<
- "Min. version case " << i << " failed!";
- }
- filter.clear_min_version();
- for (size_t i = 0; i < std::size(max_test_cases); ++i) {
- filter.set_max_version(max_test_cases[i].max_version);
- const bool result = internal::CheckStudyVersion(
- filter, base::Version(max_test_cases[i].version));
- EXPECT_EQ(max_test_cases[i].expected_result, result) <<
- "Max version case " << i << " failed!";
- }
- // Check intersection semantics.
- for (size_t i = 0; i < std::size(min_test_cases); ++i) {
- for (size_t j = 0; j < std::size(max_test_cases); ++j) {
- filter.set_min_version(min_test_cases[i].min_version);
- filter.set_max_version(max_test_cases[j].max_version);
- if (!min_test_cases[i].expected_result) {
- const bool result = internal::CheckStudyVersion(
- filter, base::Version(min_test_cases[i].version));
- EXPECT_FALSE(result) << "Case " << i << "," << j << " failed!";
- }
- if (!max_test_cases[j].expected_result) {
- const bool result = internal::CheckStudyVersion(
- filter, base::Version(max_test_cases[j].version));
- EXPECT_FALSE(result) << "Case " << i << "," << j << " failed!";
- }
- }
- }
- }
- TEST(VariationsStudyFilteringTest, CheckStudyHardwareClass) {
- struct {
- const char* hardware_class;
- const char* exclude_hardware_class;
- const char* actual_hardware_class;
- bool expected_result;
- } test_cases[] = {
- // Neither filtered nor excluded set:
- // True since empty is always a match.
- {"", "", "fancy INTEL pear device", true},
- {"", "", "", true},
- // Filtered set:
- {"apple,pear,orange", "", "apple", true},
- {"apple,pear,orange", "", "aPPle", true},
- {"apple,pear,orange", "", "fancy INTEL pear device", false},
- {"apple,pear,orange", "", "fancy INTEL GRAPE device", false},
- // Somehow tagged as both, but still valid.
- {"apple,pear,orange", "", "fancy INTEL pear GRAPE device", false},
- // Substring, which should not match.
- {"apple,pear,orange", "", "fancy INTEL SNapple device", false},
- // Empty, which is what would happen for non ChromeOS platforms.
- {"apple,pear,orange", "", "", false},
- // Excluded set:
- {"", "apple,pear,orange", "apple", false},
- {"", "apple,pear,orange", "fancy INTEL pear device", true},
- {"", "apple,pear,orange", "fancy INTEL GRAPE device", true},
- // Empty.
- {"", "apple,pear,orange", "", true},
- // Not testing when both are set as it should never occur and should be
- // considered undefined.
- };
- for (const auto& test : test_cases) {
- Study::Filter filter;
- for (const auto& hw_class : SplitFilterString(test.hardware_class))
- filter.add_hardware_class(hw_class);
- for (const auto& hw_class : SplitFilterString(test.exclude_hardware_class))
- filter.add_exclude_hardware_class(hw_class);
- EXPECT_EQ(test.expected_result, internal::CheckStudyHardwareClass(
- filter, test.actual_hardware_class))
- << "hardware_class=" << test.hardware_class << " "
- << "exclude_hardware_class=" << test.exclude_hardware_class << " "
- << "actual_hardware_class=" << test.actual_hardware_class;
- }
- }
- TEST(VariationsStudyFilteringTest, CheckStudyCountry) {
- struct {
- const char* country;
- const char* exclude_country;
- const char* actual_country;
- bool expected_result;
- } test_cases[] = {
- // Neither filtered nor excluded set:
- // True since empty is always a match.
- {"", "", "us", true},
- {"", "", "", true},
- // Filtered set:
- {"us", "", "us", true},
- {"br,ca,us", "", "us", true},
- {"br,ca,us", "", "in", false},
- // Empty, which is what would happen if no country was returned from the
- // server.
- {"br,ca,us", "", "", false},
- // Excluded set:
- {"", "us", "us", false},
- {"", "br,ca,us", "us", false},
- {"", "br,ca,us", "in", true},
- // Empty, which is what would happen if no country was returned from the
- // server.
- {"", "br,ca,us", "", true},
- // Not testing when both are set as it should never occur and should be
- // considered undefined.
- };
- for (const auto& test : test_cases) {
- Study::Filter filter;
- for (const std::string& country : SplitFilterString(test.country))
- filter.add_country(country);
- for (const std::string& country : SplitFilterString(test.exclude_country))
- filter.add_exclude_country(country);
- EXPECT_EQ(test.expected_result,
- internal::CheckStudyCountry(filter, test.actual_country));
- }
- }
- TEST(VariationsStudyFilteringTest, FilterAndValidateStudies) {
- const std::string kTrial1Name = "A";
- const std::string kGroup1Name = "Group1";
- const std::string kTrial3Name = "B";
- VariationsSeed seed;
- Study* study1 = seed.add_study();
- study1->set_name(kTrial1Name);
- study1->set_default_experiment_name("Default");
- AddExperiment(kGroup1Name, 100, study1);
- AddExperiment("Default", 0, study1);
- Study* study2 = seed.add_study();
- *study2 = *study1;
- study2->mutable_experiment(0)->set_name("Bam");
- ASSERT_EQ(seed.study(0).name(), seed.study(1).name());
- Study* study3 = seed.add_study();
- study3->set_name(kTrial3Name);
- study3->set_default_experiment_name("Default");
- AddExperiment("A", 10, study3);
- AddExperiment("Default", 25, study3);
- ClientFilterableState client_state(base::BindOnce([] { return false; }));
- client_state.locale = "en-CA";
- client_state.reference_date = base::Time::Now();
- client_state.version = base::Version("20.0.0.0");
- client_state.channel = Study::STABLE;
- client_state.form_factor = Study::DESKTOP;
- client_state.platform = Study::PLATFORM_ANDROID;
- std::vector<ProcessedStudy> processed_studies;
- FilterAndValidateStudies(seed, client_state, VariationsLayers(),
- &processed_studies);
- // Check that only the first kTrial1Name study was kept.
- ASSERT_EQ(2U, processed_studies.size());
- EXPECT_EQ(kTrial1Name, processed_studies[0].study()->name());
- EXPECT_EQ(kGroup1Name, processed_studies[0].study()->experiment(0).name());
- EXPECT_EQ(kTrial3Name, processed_studies[1].study()->name());
- }
- TEST(VariationsStudyFilteringTest, FilterAndValidateStudiesWithBadFilters) {
- const char* versions[] = {
- "invalid",
- "1.invalid.0",
- "0.invalid.0",
- "\001\000\000\003",
- };
- VariationsSeed seed;
- Study baseStudy;
- baseStudy.set_default_experiment_name("Default");
- AddExperiment("Default", 100, &baseStudy);
- baseStudy.mutable_filter()->add_platform(Study::PLATFORM_ANDROID);
- // Add studies with invalid min_versions.
- for (size_t i = 0; i < std::size(versions); ++i) {
- Study* study = seed.add_study();
- *study = baseStudy;
- study->set_name(
- base::StrCat({"min_version_study_", base::NumberToString(i)}));
- study->mutable_filter()->set_min_version(versions[i]);
- }
- // Add studies with invalid max_versions.
- for (size_t i = 0; i < std::size(versions); ++i) {
- Study* study = seed.add_study();
- *study = baseStudy;
- study->set_name(
- base::StrCat({"max_version_study_", base::NumberToString(i)}));
- study->mutable_filter()->set_max_version(versions[i]);
- }
- // Add studies with invalid min_os_versions.
- for (size_t i = 0; i < std::size(versions); ++i) {
- Study* study = seed.add_study();
- *study = baseStudy;
- study->set_name(
- base::StrCat({"min_os_version_study_", base::NumberToString(i)}));
- study->mutable_filter()->set_min_os_version(versions[i]);
- }
- // Add studies with invalid max_os_versions.
- for (size_t i = 0; i < std::size(versions); ++i) {
- Study* study = seed.add_study();
- *study = baseStudy;
- study->set_name(
- base::StrCat({"max_os_version_study_", base::NumberToString(i)}));
- study->mutable_filter()->set_max_os_version(versions[i]);
- }
- ClientFilterableState client_state(base::BindOnce([] { return false; }));
- client_state.locale = "en-CA";
- client_state.reference_date = base::Time::Now();
- client_state.version = base::Version("20.0.0.0");
- client_state.channel = Study::STABLE;
- client_state.form_factor = Study::DESKTOP;
- client_state.platform = Study::PLATFORM_ANDROID;
- client_state.os_version = base::Version("1.2.3");
- base::HistogramTester histogram_tester;
- std::vector<ProcessedStudy> processed_studies;
- FilterAndValidateStudies(seed, client_state, VariationsLayers(),
- &processed_studies);
- ASSERT_EQ(0U, processed_studies.size());
- histogram_tester.ExpectTotalCount("Variations.InvalidStudyReason",
- std::size(versions) * 4);
- histogram_tester.ExpectBucketCount("Variations.InvalidStudyReason", 0,
- std::size(versions));
- histogram_tester.ExpectBucketCount("Variations.InvalidStudyReason", 1,
- std::size(versions));
- histogram_tester.ExpectBucketCount("Variations.InvalidStudyReason", 2,
- std::size(versions));
- histogram_tester.ExpectBucketCount("Variations.InvalidStudyReason", 3,
- std::size(versions));
- }
- TEST(VariationsStudyFilteringTest, FilterAndValidateStudiesWithBlankStudyName) {
- VariationsSeed seed;
- Study* study = seed.add_study();
- study->set_name("");
- study->set_default_experiment_name("Default");
- AddExperiment("A", 100, study);
- AddExperiment("Default", 0, study);
- study->mutable_filter()->add_platform(Study::PLATFORM_ANDROID);
- ClientFilterableState client_state(base::BindOnce([] { return false; }));
- client_state.locale = "en-CA";
- client_state.reference_date = base::Time::Now();
- client_state.version = base::Version("20.0.0.0");
- client_state.channel = Study::STABLE;
- client_state.form_factor = Study::PHONE;
- client_state.platform = Study::PLATFORM_ANDROID;
- base::HistogramTester histogram_tester;
- std::vector<ProcessedStudy> processed_studies;
- FilterAndValidateStudies(seed, client_state, VariationsLayers(),
- &processed_studies);
- ASSERT_EQ(0U, processed_studies.size());
- histogram_tester.ExpectUniqueSample("Variations.InvalidStudyReason", 8, 1);
- }
- TEST(VariationsStudyFilteringTest, FilterAndValidateStudiesWithCountry) {
- const char kSessionCountry[] = "ca";
- const char kPermanentCountry[] = "us";
- struct {
- Study::Consistency consistency;
- const char* filter_country;
- const char* filter_exclude_country;
- bool expect_study_kept;
- } test_cases[] = {
- // Country-agnostic studies should be kept regardless of country.
- {Study::SESSION, nullptr, nullptr, true},
- {Study::PERMANENT, nullptr, nullptr, true},
- // Session-consistency studies should obey the country code in the seed.
- {Study::SESSION, kSessionCountry, nullptr, true},
- {Study::SESSION, nullptr, kSessionCountry, false},
- {Study::SESSION, kPermanentCountry, nullptr, false},
- {Study::SESSION, nullptr, kPermanentCountry, true},
- // Permanent-consistency studies should obey the permanent-consistency
- // country code.
- {Study::PERMANENT, kPermanentCountry, nullptr, true},
- {Study::PERMANENT, nullptr, kPermanentCountry, false},
- {Study::PERMANENT, kSessionCountry, nullptr, false},
- {Study::PERMANENT, nullptr, kSessionCountry, true},
- };
- for (const auto& test : test_cases) {
- VariationsSeed seed;
- Study* study = seed.add_study();
- study->set_name("study");
- study->set_default_experiment_name("Default");
- AddExperiment("Default", 100, study);
- study->set_consistency(test.consistency);
- study->mutable_filter()->add_platform(Study::PLATFORM_ANDROID);
- if (test.filter_country)
- study->mutable_filter()->add_country(test.filter_country);
- if (test.filter_exclude_country)
- study->mutable_filter()->add_exclude_country(test.filter_exclude_country);
- ClientFilterableState client_state(base::BindOnce([] { return false; }));
- client_state.locale = "en-CA";
- client_state.reference_date = base::Time::Now();
- client_state.version = base::Version("20.0.0.0");
- client_state.channel = Study::STABLE;
- client_state.form_factor = Study::PHONE;
- client_state.platform = Study::PLATFORM_ANDROID;
- client_state.session_consistency_country = kSessionCountry;
- client_state.permanent_consistency_country = kPermanentCountry;
- std::vector<ProcessedStudy> processed_studies;
- FilterAndValidateStudies(seed, client_state, VariationsLayers(),
- &processed_studies);
- EXPECT_EQ(test.expect_study_kept, !processed_studies.empty());
- }
- }
- TEST(VariationsStudyFilteringTest, GetClientCountryForStudy_Session) {
- ClientFilterableState client_state(base::BindOnce([] { return false; }));
- client_state.session_consistency_country = "session_country";
- client_state.permanent_consistency_country = "permanent_country";
- Study study;
- study.set_consistency(Study::SESSION);
- EXPECT_EQ("session_country",
- internal::GetClientCountryForStudy(study, client_state));
- }
- TEST(VariationsStudyFilteringTest, GetClientCountryForStudy_Permanent) {
- ClientFilterableState client_state(base::BindOnce([] { return false; }));
- client_state.session_consistency_country = "session_country";
- client_state.permanent_consistency_country = "permanent_country";
- Study study;
- study.set_consistency(Study::PERMANENT);
- EXPECT_EQ("permanent_country",
- internal::GetClientCountryForStudy(study, client_state));
- }
- TEST(VariationsStudyFilteringTest, IsStudyExpired) {
- const base::Time now = base::Time::Now();
- const base::TimeDelta delta = base::Hours(1);
- const struct {
- const base::Time expiry_date;
- bool expected_result;
- } expiry_test_cases[] = {
- { now - delta, true },
- { now, true },
- { now + delta, false },
- };
- Study study;
- // Expiry date not set should result in false.
- EXPECT_FALSE(internal::IsStudyExpired(study, now));
- for (size_t i = 0; i < std::size(expiry_test_cases); ++i) {
- study.set_expiry_date(TimeToProtoTime(expiry_test_cases[i].expiry_date));
- const bool result = internal::IsStudyExpired(study, now);
- EXPECT_EQ(expiry_test_cases[i].expected_result, result)
- << "Case " << i << " failed!";
- }
- }
- TEST(VariationsStudyFilteringTest, ValidateStudy) {
- Study study;
- study.set_name("study");
- study.set_default_experiment_name("def");
- AddExperiment("abc", 100, &study);
- Study::Experiment* default_group = AddExperiment("def", 200, &study);
- ProcessedStudy processed_study;
- EXPECT_TRUE(processed_study.Init(&study, false));
- EXPECT_EQ(300, processed_study.total_probability());
- // Min version checks.
- study.mutable_filter()->set_min_version("1.2.3.*");
- EXPECT_TRUE(processed_study.Init(&study, false));
- study.mutable_filter()->set_min_version("1.*.3");
- EXPECT_FALSE(processed_study.Init(&study, false));
- study.mutable_filter()->set_min_version("1.2.3");
- EXPECT_TRUE(processed_study.Init(&study, false));
- // Max version checks.
- study.mutable_filter()->set_max_version("2.3.4.*");
- EXPECT_TRUE(processed_study.Init(&study, false));
- study.mutable_filter()->set_max_version("*.3");
- EXPECT_FALSE(processed_study.Init(&study, false));
- study.mutable_filter()->set_max_version("2.3.4");
- EXPECT_TRUE(processed_study.Init(&study, false));
- // A blank default study is allowed.
- study.clear_default_experiment_name();
- EXPECT_TRUE(processed_study.Init(&study, false));
- study.set_default_experiment_name("xyz");
- EXPECT_FALSE(processed_study.Init(&study, false));
- study.set_default_experiment_name("def");
- default_group->clear_name();
- EXPECT_FALSE(processed_study.Init(&study, false));
- default_group->set_name("def");
- EXPECT_TRUE(processed_study.Init(&study, false));
- Study::Experiment* repeated_group = study.add_experiment();
- repeated_group->set_name("abc");
- repeated_group->set_probability_weight(1);
- EXPECT_FALSE(processed_study.Init(&study, false));
- }
- } // namespace variations
|