Commit d7632302 authored by Sean Risser's avatar Sean Risser Committed by Commit Bot

Add variations for filtering for os_version.

We can borrow almost all the logic used to filter by Chrome version.
Though OS version strings are often messy and seemingly unpredictable,
so we need to sanitize the version first.

BUG=956059

Change-Id: I320e93a37e2da11c52f952d96e78a595f3081d38
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1582223
Commit-Queue: Sean Risser <srisser@google.com>
Commit-Queue: Alexei Svitkine <asvitkine@chromium.org>
Reviewed-by: default avatarAlexei Svitkine <asvitkine@chromium.org>
Cr-Commit-Position: refs/heads/master@{#654629}
parent 728db89a
......@@ -30,6 +30,10 @@ struct ClientFilterableState {
// The Chrome version to filter on.
base::Version version;
// The OS version to filter on. See |min_os_version| in study.proto for
// details.
base::Version os_version;
// The Channel for this Chrome installation.
Study::Channel channel;
......
......@@ -218,7 +218,7 @@ message Study {
// Filtering criteria specifying whether this study is applicable to a given
// Chrome instance.
//
// Next tag: 16
// Next tag: 18
message Filter {
// The start date of the study in Unix time format. (Seconds since midnight
// January 1, 1970 UTC). See: http://en.wikipedia.org/wiki/Unix_time
......@@ -248,6 +248,23 @@ message Study {
// Ex: "19.*"
optional string max_version = 3;
// The minimum OS version for this study, allowing a trailing '*' character
// for pattern matching. Inclusive. (To check for a match, iterate over each
// component checking >= until a * or end of string is reached.) OS versions
// are sanitized into a list of digits separated by dots like so:
// Windows: "6.2.7601 SP1" --> "6.2.7601.1"
// Mac OS X: "10.11.2" --> "10.11.2"
// Linux: "4.13.0-27-generic" --> "4.13.0"
// Optional - if not specified, there is no minimum version.
optional string min_os_version = 16;
// The maximum OS version for this study, allowing a trailing '*' character
// for pattern matching. Inclusive. (To check for a match, iterate over each
// component checking <= until a * or end of string is reached.)
// Optional - if not specified, there is no minimum version.
// Ex: See |min_os_version| for details.
optional string max_os_version = 17;
// List of channels that will receive this study. If omitted, the study
// applies to all channels.
// Ex: [BETA, STABLE]
......
......@@ -20,6 +20,7 @@
#include "base/command_line.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/pattern.h"
#include "base/strings/stringprintf.h"
#include "base/system/sys_info.h"
#include "base/trace_event/trace_event.h"
......@@ -99,6 +100,28 @@ base::Time GetReferenceDateForExpiryChecks(PrefService* local_state) {
return reference_date;
}
// TODO(b/957197): Improve how we handle OS versions.
// Add os_version.h and os_version_<platform>.cc that handle retrieving and
// parsing OS versions. Then get rid of all the platform-dependent code here.
base::Version GetOSVersion() {
base::Version ret;
#if defined(OS_WIN)
std::string win_version = base::SysInfo::OperatingSystemVersion();
base::ReplaceSubstringsAfterOffset(&win_version, 0, " SP", ".");
ret = base::Version(win_version);
DCHECK(ret.IsValid()) << win_version;
#else
// Every other OS is supported by OperatingSystemVersionNumbers
int major, minor, build;
base::SysInfo::OperatingSystemVersionNumbers(&major, &minor, &build);
ret = base::Version(base::StringPrintf("%d.%d.%d", major, minor, build));
DCHECK(ret.IsValid());
#endif
return ret;
}
// Wrapper around channel checking, used to enable channel mocking for
// testing. If a fake channel flag is provided, it will take precedence.
// Otherwise, this will return the current browser channel (which could be
......@@ -250,6 +273,7 @@ VariationsFieldTrialCreator::GetClientFilterableStateForVersion(
state->locale = application_locale_;
state->reference_date = GetReferenceDateForExpiryChecks(local_state());
state->version = version;
state->os_version = GetOSVersion();
state->channel = GetChannelForVariations(client_->GetChannel());
state->form_factor = GetCurrentFormFactor();
state->platform = GetPlatform();
......
......@@ -155,6 +155,25 @@ bool CheckStudyVersion(const Study::Filter& filter,
return true;
}
bool CheckStudyOSVersion(const Study::Filter& filter,
const base::Version& version) {
if (filter.has_min_os_version()) {
if (!version.IsValid() ||
version.CompareToWildcardString(filter.min_os_version()) < 0) {
return false;
}
}
if (filter.has_max_os_version()) {
if (!version.IsValid() ||
version.CompareToWildcardString(filter.max_os_version()) > 0) {
return false;
}
}
return true;
}
bool CheckStudyCountry(const Study::Filter& filter,
const std::string& country) {
// Empty country and exclude_country matches all.
......
......@@ -57,6 +57,10 @@ bool CheckStudyEndDate(const Study::Filter& filter,
bool CheckStudyVersion(const Study::Filter& filter,
const base::Version& version);
// checks whether a study is applicable for the given OS version per |filter|.
bool CheckStudyOSVersion(const Study::Filter& filter,
const base::Version& os_version);
// Checks whether a study is applicable for the given |country| per |filter|.
bool CheckStudyCountry(const Study::Filter& filter, const std::string& country);
......
......@@ -320,6 +320,104 @@ TEST(VariationsStudyFilteringTest, CheckStudyEndDate) {
}
}
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 < base::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 < base::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 < base::size(min_test_cases); ++i) {
for (size_t j = 0; j < base::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;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment