diff options
author | sdefresne <sdefresne@chromium.org> | 2015-08-14 08:47:00 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-08-14 15:47:37 +0000 |
commit | a59146f71f643c2cdd9465d6c1b11f052bd793e3 (patch) | |
tree | 71fbe8fa86fb2938d2468e41b3b1c92bdfcb5361 /components | |
parent | d6b82982c568c32e254ccb43a7c4e30e9de7219d (diff) | |
download | chromium_src-a59146f71f643c2cdd9465d6c1b11f052bd793e3.zip chromium_src-a59146f71f643c2cdd9465d6c1b11f052bd793e3.tar.gz chromium_src-a59146f71f643c2cdd9465d6c1b11f052bd793e3.tar.bz2 |
Componentize chrome/common/experiment_labels.{cc,h} and tests.
Move both chrome/common/experiment_labels.{cc,h} and the corresponding
unit tests into components/variations as they only depends on //base
and the component.
BUG=520070
Review URL: https://codereview.chromium.org/1290173004
Cr-Commit-Position: refs/heads/master@{#343391}
Diffstat (limited to 'components')
-rw-r--r-- | components/components_tests.gyp | 1 | ||||
-rw-r--r-- | components/variations.gypi | 2 | ||||
-rw-r--r-- | components/variations/BUILD.gn | 3 | ||||
-rw-r--r-- | components/variations/experiment_labels.cc | 116 | ||||
-rw-r--r-- | components/variations/experiment_labels.h | 33 | ||||
-rw-r--r-- | components/variations/experiment_labels_unittest.cc | 194 |
6 files changed, 349 insertions, 0 deletions
diff --git a/components/components_tests.gyp b/components/components_tests.gyp index 53d86ab..27b4299 100644 --- a/components/components_tests.gyp +++ b/components/components_tests.gyp @@ -655,6 +655,7 @@ 'variations/active_field_trials_unittest.cc', 'variations/caching_permuted_entropy_provider_unittest.cc', 'variations/entropy_provider_unittest.cc', + 'variations/experiment_labels_unittest.cc', 'variations/metrics_util_unittest.cc', 'variations/net/variations_http_header_provider_unittest.cc', 'variations/study_filtering_unittest.cc', diff --git a/components/variations.gypi b/components/variations.gypi index 73a5e88..6fd429c 100644 --- a/components/variations.gypi +++ b/components/variations.gypi @@ -30,6 +30,8 @@ 'variations/caching_permuted_entropy_provider.h', 'variations/entropy_provider.cc', 'variations/entropy_provider.h', + 'variations/experiment_labels.cc', + 'variations/experiment_labels.h', 'variations/metrics_util.cc', 'variations/metrics_util.h', 'variations/pref_names.cc', diff --git a/components/variations/BUILD.gn b/components/variations/BUILD.gn index 5ceed31..e471d95 100644 --- a/components/variations/BUILD.gn +++ b/components/variations/BUILD.gn @@ -18,6 +18,8 @@ source_set("variations") { "caching_permuted_entropy_provider.h", "entropy_provider.cc", "entropy_provider.h", + "experiment_labels.cc", + "experiment_labels.h", "metrics_util.cc", "metrics_util.h", "pref_names.cc", @@ -70,6 +72,7 @@ source_set("unit_tests") { "active_field_trials_unittest.cc", "caching_permuted_entropy_provider_unittest.cc", "entropy_provider_unittest.cc", + "experiment_labels_unittest.cc", "metrics_util_unittest.cc", "net/variations_http_header_provider_unittest.cc", "study_filtering_unittest.cc", diff --git a/components/variations/experiment_labels.cc b/components/variations/experiment_labels.cc new file mode 100644 index 0000000..3f12cd8 --- /dev/null +++ b/components/variations/experiment_labels.cc @@ -0,0 +1,116 @@ +// 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/experiment_labels.h" + +#include <vector> + +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "components/variations/variations_associated_data.h" +#include "components/variations/variations_experiment_util.h" + +namespace chrome_variations { + +namespace { + +const char kVariationPrefix[] = "CrVar"; + +// This method builds a single experiment label for a Chrome Variation, +// including a timestamp that is a year in the future from |current_time|. Since +// multiple headers can be transmitted, |count| is a number that is appended +// after the label key to differentiate the labels. +base::string16 CreateSingleExperimentLabel(int count, + variations::VariationID id, + const base::Time& current_time) { + // Build the parts separately so they can be validated. + const base::string16 key = + base::ASCIIToUTF16(kVariationPrefix) + base::IntToString16(count); + DCHECK_LE(key.size(), 8U); + const base::string16 value = base::IntToString16(id); + DCHECK_LE(value.size(), 8U); + base::string16 label(key); + label += base::ASCIIToUTF16("="); + label += value; + label += base::ASCIIToUTF16("|"); + label += variations::BuildExperimentDateString(current_time); + return label; +} + +} // namespace + +base::string16 BuildGoogleUpdateExperimentLabel( + const base::FieldTrial::ActiveGroups& active_groups) { + base::string16 experiment_labels; + int counter = 0; + + const base::Time current_time(base::Time::Now()); + + // Find all currently active VariationIDs associated with Google Update. + for (base::FieldTrial::ActiveGroups::const_iterator it = + active_groups.begin(); it != active_groups.end(); ++it) { + const variations::VariationID id = + variations::GetGoogleVariationID(variations::GOOGLE_UPDATE_SERVICE, + it->trial_name, it->group_name); + + if (id == variations::EMPTY_ID) + continue; + + if (!experiment_labels.empty()) + experiment_labels += variations::kExperimentLabelSeparator; + experiment_labels += CreateSingleExperimentLabel(++counter, id, + current_time); + } + + return experiment_labels; +} + +base::string16 ExtractNonVariationLabels(const base::string16& labels) { + // First, split everything by the label separator. + std::vector<base::StringPiece16> entries = base::SplitStringPiece( + labels, base::StringPiece16(&variations::kExperimentLabelSeparator, 1), + base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); + + // For each label, keep the ones that do not look like a Variations label. + base::string16 non_variation_labels; + for (const base::StringPiece16& entry : entries) { + if (entry.empty() || + base::StartsWith(entry, + base::ASCIIToUTF16(kVariationPrefix), + base::CompareCase::INSENSITIVE_ASCII)) { + continue; + } + + // Dump the whole thing, including the timestamp. + if (!non_variation_labels.empty()) + non_variation_labels += variations::kExperimentLabelSeparator; + entry.AppendToString(&non_variation_labels); + } + + return non_variation_labels; +} + +base::string16 CombineExperimentLabels(const base::string16& variation_labels, + const base::string16& other_labels) { + base::StringPiece16 separator(&variations::kExperimentLabelSeparator, 1); + DCHECK(!base::StartsWith(variation_labels, separator, + base::CompareCase::SENSITIVE)); + DCHECK(!base::EndsWith(variation_labels, separator, + base::CompareCase::SENSITIVE)); + DCHECK(!base::StartsWith(other_labels, separator, + base::CompareCase::SENSITIVE)); + DCHECK(!base::EndsWith(other_labels, separator, + base::CompareCase::SENSITIVE)); + // Note that if either label is empty, a separator is not necessary. + base::string16 combined_labels = other_labels; + if (!other_labels.empty() && !variation_labels.empty()) + combined_labels += variations::kExperimentLabelSeparator; + combined_labels += variation_labels; + return combined_labels; +} + +} // namespace chrome_variations diff --git a/components/variations/experiment_labels.h b/components/variations/experiment_labels.h new file mode 100644 index 0000000..58bf7a6 --- /dev/null +++ b/components/variations/experiment_labels.h @@ -0,0 +1,33 @@ +// 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. + +#ifndef COMPONENTS_VARIATIONS_EXPERIMENT_LABELS_H_ +#define COMPONENTS_VARIATIONS_EXPERIMENT_LABELS_H_ + +#include "base/metrics/field_trial.h" +#include "base/strings/string16.h" + +namespace chrome_variations { + +// Takes the list of active groups and builds the label for the ones that have +// Google Update VariationID associated with them. This will return an empty +// string if there are no such groups. +base::string16 BuildGoogleUpdateExperimentLabel( + const base::FieldTrial::ActiveGroups& active_groups); + +// Creates a final combined experiment labels string with |variation_labels| +// and |other_labels|, appropriately appending a separator based on their +// contents. It is assumed that |variation_labels| and |other_labels| do not +// have leading or trailing separators. +base::string16 CombineExperimentLabels(const base::string16& variation_labels, + const base::string16& other_labels); + +// Takes the value of experiment_labels from the registry and returns a valid +// experiment_labels string value containing only the labels that are not +// associated with Chrome Variations. +base::string16 ExtractNonVariationLabels(const base::string16& labels); + +} // namespace chrome_variations + +#endif // COMPONENTS_VARIATIONS_EXPERIMENT_LABELS_H_ diff --git a/components/variations/experiment_labels_unittest.cc b/components/variations/experiment_labels_unittest.cc new file mode 100644 index 0000000..1f3379d --- /dev/null +++ b/components/variations/experiment_labels_unittest.cc @@ -0,0 +1,194 @@ +// 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/experiment_labels.h" + +#include <set> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/metrics/field_trial.h" +#include "base/strings/string_split.h" +#include "base/strings/utf_string_conversions.h" +#include "components/variations/variations_associated_data.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace chrome_variations { + +TEST(ExperimentLabelsTest, BuildGoogleUpdateExperimentLabel) { + const variations::VariationID TEST_VALUE_A = 3300200; + const variations::VariationID TEST_VALUE_B = 3300201; + const variations::VariationID TEST_VALUE_C = 3300202; + const variations::VariationID TEST_VALUE_D = 3300203; + + struct { + const char* active_group_pairs; + const char* expected_ids; + } test_cases[] = { + // Empty group. + {"", ""}, + // Group of 1. + {"FieldTrialA#Default", "3300200"}, + // Group of 1, doesn't have an associated ID. + {"FieldTrialA#DoesNotExist", ""}, + // Group of 3. + {"FieldTrialA#Default#FieldTrialB#Group1#FieldTrialC#Default", + "3300200#3300201#3300202"}, + // Group of 3, one doesn't have an associated ID. + {"FieldTrialA#Default#FieldTrialB#DoesNotExist#FieldTrialC#Default", + "3300200#3300202"}, + // Group of 3, all three don't have an associated ID. + {"FieldTrialX#Default#FieldTrialB#DoesNotExist#FieldTrialC#Default", + "3300202"}, + }; + + // Register a few VariationIDs. + AssociateGoogleVariationID(variations::GOOGLE_UPDATE_SERVICE, "FieldTrialA", + "Default", TEST_VALUE_A); + AssociateGoogleVariationID(variations::GOOGLE_UPDATE_SERVICE, "FieldTrialB", + "Group1", TEST_VALUE_B); + AssociateGoogleVariationID(variations::GOOGLE_UPDATE_SERVICE, "FieldTrialC", + "Default", TEST_VALUE_C); + AssociateGoogleVariationID(variations::GOOGLE_UPDATE_SERVICE, "FieldTrialD", + "Default", TEST_VALUE_D); // Not actually used. + + for (size_t i = 0; i < arraysize(test_cases); ++i) { + // Parse the input groups. + base::FieldTrial::ActiveGroups groups; + std::vector<std::string> group_data = base::SplitString( + test_cases[i].active_group_pairs, "#", + base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); + ASSERT_EQ(0U, group_data.size() % 2); + for (size_t j = 0; j < group_data.size(); j += 2) { + base::FieldTrial::ActiveGroup group; + group.trial_name = group_data[j]; + group.group_name = group_data[j + 1]; + groups.push_back(group); + } + + // Parse the expected output. + std::vector<std::string> expected_ids_list = base::SplitString( + test_cases[i].expected_ids, "#", + base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); + + std::string experiment_labels_string = base::UTF16ToUTF8( + BuildGoogleUpdateExperimentLabel(groups)); + + // Split the VariationIDs from the labels for verification below. + std::set<std::string> parsed_ids; + for (const std::string& label : base::SplitString( + experiment_labels_string, ";", + base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) { + // The ID is precisely between the '=' and '|' characters in each label. + size_t index_of_equals = label.find('='); + size_t index_of_pipe = label.find('|'); + ASSERT_NE(std::string::npos, index_of_equals); + ASSERT_NE(std::string::npos, index_of_pipe); + ASSERT_GT(index_of_pipe, index_of_equals); + parsed_ids.insert(label.substr(index_of_equals + 1, + index_of_pipe - index_of_equals - 1)); + } + + // Verify that the resulting string contains each of the expected labels, + // and nothing more. Note that the date is stripped out and ignored. + for (std::vector<std::string>::const_iterator it = + expected_ids_list.begin(); it != expected_ids_list.end(); ++it) { + std::set<std::string>::iterator it2 = parsed_ids.find(*it); + EXPECT_TRUE(parsed_ids.end() != it2); + parsed_ids.erase(it2); + } + EXPECT_TRUE(parsed_ids.empty()); + } // for +} + +TEST(ExperimentLabelsTest, CombineExperimentLabels) { + struct { + const char* variations_labels; + const char* other_labels; + const char* expected_label; + } test_cases[] = { + {"A=B|Tue, 21 Jan 2014 15:30:21 GMT", + "C=D|Tue, 21 Jan 2014 15:30:21 GMT", + "C=D|Tue, 21 Jan 2014 15:30:21 GMT;A=B|Tue, 21 Jan 2014 15:30:21 GMT"}, + {"A=B|Tue, 21 Jan 2014 15:30:21 GMT", + "", + "A=B|Tue, 21 Jan 2014 15:30:21 GMT"}, + {"", + "A=B|Tue, 21 Jan 2014 15:30:21 GMT", + "A=B|Tue, 21 Jan 2014 15:30:21 GMT"}, + {"A=B|Tue, 21 Jan 2014 15:30:21 GMT;C=D|Tue, 21 Jan 2014 15:30:21 GMT", + "P=Q|Tue, 21 Jan 2014 15:30:21 GMT;X=Y|Tue, 21 Jan 2014 15:30:21 GMT", + "P=Q|Tue, 21 Jan 2014 15:30:21 GMT;X=Y|Tue, 21 Jan 2014 15:30:21 GMT;" + "A=B|Tue, 21 Jan 2014 15:30:21 GMT;C=D|Tue, 21 Jan 2014 15:30:21 GMT"}, + {"", + "", + ""}, + }; + + for (size_t i = 0; i < arraysize(test_cases); ++i) { + std::string result = base::UTF16ToUTF8(CombineExperimentLabels( + base::ASCIIToUTF16(test_cases[i].variations_labels), + base::ASCIIToUTF16(test_cases[i].other_labels))); + EXPECT_EQ(test_cases[i].expected_label, result); + } +} + +TEST(ExperimentLabelsTest, ExtractNonVariationLabels) { + struct { + const char* input_label; + const char* expected_output; + } test_cases[] = { + // Empty + {"", ""}, + // One + {"gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT", + "gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT"}, + // Three + {"CrVar1=123|Tue, 21 Jan 2014 15:30:21 GMT;" + "experiment1=456|Tue, 21 Jan 2014 15:30:21 GMT;" + "experiment2=789|Tue, 21 Jan 2014 15:30:21 GMT;" + "CrVar1=123|Tue, 21 Jan 2014 15:30:21 GMT", + "experiment1=456|Tue, 21 Jan 2014 15:30:21 GMT;" + "experiment2=789|Tue, 21 Jan 2014 15:30:21 GMT"}, + // One and one Variation + {"gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT;" + "CrVar1=3310002|Tue, 21 Jan 2014 15:30:21 GMT", + "gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT"}, + // One and one Variation, flipped + {"CrVar1=3310002|Tue, 21 Jan 2014 15:30:21 GMT;" + "gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT", + "gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT"}, + // Sandwiched + {"CrVar1=3310002|Tue, 21 Jan 2014 15:30:21 GMT;" + "gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT;" + "CrVar2=3310003|Tue, 21 Jan 2014 15:30:21 GMT;" + "CrVar3=3310004|Tue, 21 Jan 2014 15:30:21 GMT", + "gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT"}, + // Only Variations + {"CrVar1=3310002|Tue, 21 Jan 2014 15:30:21 GMT;" + "CrVar2=3310003|Tue, 21 Jan 2014 15:30:21 GMT;" + "CrVar3=3310004|Tue, 21 Jan 2014 15:30:21 GMT", + ""}, + // Empty values + {"gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT;" + "CrVar1=3310002|Tue, 21 Jan 2014 15:30:21 GMT", + "gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT"}, + // Trailing semicolon + {"gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT;" + "CrVar1=3310002|Tue, 21 Jan 2014 15:30:21 GMT;", // Note the semi here. + "gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT"}, + // Semis + {";;;;", ""}, + }; + + for (size_t i = 0; i < arraysize(test_cases); ++i) { + std::string non_variation_labels = base::UTF16ToUTF8( + ExtractNonVariationLabels( + base::ASCIIToUTF16(test_cases[i].input_label))); + EXPECT_EQ(test_cases[i].expected_output, non_variation_labels); + } +} + +} // namespace chrome_variations |