1 // Copyright (c) 2012 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 5 #include "chrome/common/metrics/variations/variations_util.h" 6 7 #include <set> 8 #include <string> 9 10 #include "base/metrics/field_trial.h" 11 #include "base/strings/string_split.h" 12 #include "base/strings/utf_string_conversions.h" 13 #include "base/time/time.h" 14 #include "chrome/common/metrics/metrics_util.h" 15 #include "testing/gtest/include/gtest/gtest.h" 16 17 namespace chrome_variations { 18 19 namespace { 20 21 // Tests whether a field trial is active (i.e. group() has been called on it). 22 bool IsFieldTrialActive(const std::string& trial_name) { 23 base::FieldTrial::ActiveGroups active_groups; 24 base::FieldTrialList::GetActiveFieldTrialGroups(&active_groups); 25 for (size_t i = 0; i < active_groups.size(); ++i) { 26 if (active_groups[i].trial_name == trial_name) 27 return true; 28 } 29 return false; 30 } 31 32 // Call FieldTrialList::FactoryGetFieldTrial() with a future expiry date. 33 scoped_refptr<base::FieldTrial> CreateFieldTrial( 34 const std::string& trial_name, 35 int total_probability, 36 const std::string& default_group_name, 37 int* default_group_number) { 38 return base::FieldTrialList::FactoryGetFieldTrial( 39 trial_name, total_probability, default_group_name, 40 base::FieldTrialList::kNoExpirationYear, 1, 1, 41 base::FieldTrial::SESSION_RANDOMIZED, default_group_number); 42 } 43 44 } // namespace 45 46 class VariationsUtilTest : public ::testing::Test { 47 public: 48 VariationsUtilTest() : field_trial_list_(NULL) { 49 } 50 51 virtual ~VariationsUtilTest() { 52 // Ensure that the maps are cleared between tests, since they are stored as 53 // process singletons. 54 testing::ClearAllVariationIDs(); 55 } 56 57 private: 58 base::FieldTrialList field_trial_list_; 59 60 DISALLOW_COPY_AND_ASSIGN(VariationsUtilTest); 61 }; 62 63 TEST_F(VariationsUtilTest, GetFieldTrialActiveGroups) { 64 typedef std::set<ActiveGroupId, ActiveGroupIdCompare> ActiveGroupIdSet; 65 std::string trial_one("trial one"); 66 std::string group_one("group one"); 67 std::string trial_two("trial two"); 68 std::string group_two("group two"); 69 70 base::FieldTrial::ActiveGroups active_groups; 71 base::FieldTrial::ActiveGroup active_group; 72 active_group.trial_name = trial_one; 73 active_group.group_name = group_one; 74 active_groups.push_back(active_group); 75 76 active_group.trial_name = trial_two; 77 active_group.group_name = group_two; 78 active_groups.push_back(active_group); 79 80 // Create our expected groups of IDs. 81 ActiveGroupIdSet expected_groups; 82 ActiveGroupId name_group_id; 83 name_group_id.name = metrics::HashName(trial_one); 84 name_group_id.group = metrics::HashName(group_one); 85 expected_groups.insert(name_group_id); 86 name_group_id.name = metrics::HashName(trial_two); 87 name_group_id.group = metrics::HashName(group_two); 88 expected_groups.insert(name_group_id); 89 90 std::vector<ActiveGroupId> active_group_ids; 91 testing::TestGetFieldTrialActiveGroupIds(active_groups, &active_group_ids); 92 EXPECT_EQ(2U, active_group_ids.size()); 93 for (size_t i = 0; i < active_group_ids.size(); ++i) { 94 ActiveGroupIdSet::iterator expected_group = 95 expected_groups.find(active_group_ids[i]); 96 EXPECT_FALSE(expected_group == expected_groups.end()); 97 expected_groups.erase(expected_group); 98 } 99 EXPECT_EQ(0U, expected_groups.size()); 100 } 101 102 TEST_F(VariationsUtilTest, GenerateExperimentChunks) { 103 const char* kExperimentStrings[] = { 104 "1d3048f1-9de009d0", 105 "cd73da34-cf196cb", 106 "6214fa18-9e6dc24d", 107 "4dcb0cd6-d31c4ca1", 108 "9d5bce6-30d7d8ac", 109 }; 110 const char* kExpectedChunks1[] = { 111 "1d3048f1-9de009d0", 112 }; 113 const char* kExpectedChunks2[] = { 114 "1d3048f1-9de009d0,cd73da34-cf196cb", 115 }; 116 const char* kExpectedChunks3[] = { 117 "1d3048f1-9de009d0,cd73da34-cf196cb,6214fa18-9e6dc24d", 118 }; 119 const char* kExpectedChunks4[] = { 120 "1d3048f1-9de009d0,cd73da34-cf196cb,6214fa18-9e6dc24d", 121 "4dcb0cd6-d31c4ca1", 122 }; 123 const char* kExpectedChunks5[] = { 124 "1d3048f1-9de009d0,cd73da34-cf196cb,6214fa18-9e6dc24d", 125 "4dcb0cd6-d31c4ca1,9d5bce6-30d7d8ac", 126 }; 127 128 struct { 129 size_t strings_length; 130 size_t expected_chunks_length; 131 const char** expected_chunks; 132 } cases[] = { 133 { 0, 0, NULL }, 134 { 1, arraysize(kExpectedChunks1), kExpectedChunks1 }, 135 { 2, arraysize(kExpectedChunks2), kExpectedChunks2 }, 136 { 3, arraysize(kExpectedChunks3), kExpectedChunks3 }, 137 { 4, arraysize(kExpectedChunks4), kExpectedChunks4 }, 138 { 5, arraysize(kExpectedChunks5), kExpectedChunks5 }, 139 }; 140 141 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { 142 ASSERT_LE(cases[i].strings_length, arraysize(kExperimentStrings)); 143 144 std::vector<string16> experiments; 145 for (size_t j = 0; j < cases[i].strings_length; ++j) 146 experiments.push_back(UTF8ToUTF16(kExperimentStrings[j])); 147 148 std::vector<string16> chunks; 149 GenerateVariationChunks(experiments, &chunks); 150 ASSERT_EQ(cases[i].expected_chunks_length, chunks.size()); 151 for (size_t j = 0; j < chunks.size(); ++j) 152 EXPECT_EQ(UTF8ToUTF16(cases[i].expected_chunks[j]), chunks[j]); 153 } 154 } 155 156 TEST_F(VariationsUtilTest, BuildGoogleUpdateExperimentLabel) { 157 struct { 158 const char* active_group_pairs; 159 const char* expected_ids; 160 } test_cases[] = { 161 // Empty group. 162 {"", ""}, 163 // Group of 1. 164 {"FieldTrialA#Default", "3300200"}, 165 // Group of 1, doesn't have an associated ID. 166 {"FieldTrialA#DoesNotExist", ""}, 167 // Group of 3. 168 {"FieldTrialA#Default#FieldTrialB#Group1#FieldTrialC#Default", 169 "3300200#3300201#3300202"}, 170 // Group of 3, one doesn't have an associated ID. 171 {"FieldTrialA#Default#FieldTrialB#DoesNotExist#FieldTrialC#Default", 172 "3300200#3300202"}, 173 // Group of 3, all three don't have an associated ID. 174 {"FieldTrialX#Default#FieldTrialB#DoesNotExist#FieldTrialC#Default", 175 "3300202"}, 176 }; 177 178 // Register a few VariationIDs. 179 AssociateGoogleVariationID(GOOGLE_UPDATE_SERVICE, "FieldTrialA", "Default", 180 TEST_VALUE_A); 181 AssociateGoogleVariationID(GOOGLE_UPDATE_SERVICE, "FieldTrialB", "Group1", 182 TEST_VALUE_B); 183 AssociateGoogleVariationID(GOOGLE_UPDATE_SERVICE, "FieldTrialC", "Default", 184 TEST_VALUE_C); 185 AssociateGoogleVariationID(GOOGLE_UPDATE_SERVICE, "FieldTrialD", "Default", 186 TEST_VALUE_D); // Not actually used. 187 188 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) { 189 // Parse the input groups. 190 base::FieldTrial::ActiveGroups groups; 191 std::vector<std::string> group_data; 192 base::SplitString(test_cases[i].active_group_pairs, '#', &group_data); 193 ASSERT_EQ(0U, group_data.size() % 2); 194 for (size_t j = 0; j < group_data.size(); j += 2) { 195 base::FieldTrial::ActiveGroup group; 196 group.trial_name = group_data[j]; 197 group.group_name = group_data[j + 1]; 198 groups.push_back(group); 199 } 200 201 // Parse the expected output. 202 std::vector<std::string> expected_ids_list; 203 base::SplitString(test_cases[i].expected_ids, '#', &expected_ids_list); 204 205 std::string experiment_labels_string = UTF16ToUTF8( 206 BuildGoogleUpdateExperimentLabel(groups)); 207 208 // Split the VariationIDs from the labels for verification below. 209 std::vector<std::string> split_labels; 210 std::set<std::string> parsed_ids; 211 base::SplitString(experiment_labels_string, ';', &split_labels); 212 for (std::vector<std::string>::const_iterator it = split_labels.begin(); 213 it != split_labels.end(); ++it) { 214 // The ID is precisely between the '=' and '|' characters in each label. 215 size_t index_of_equals = it->find('='); 216 size_t index_of_pipe = it->find('|'); 217 ASSERT_NE(std::string::npos, index_of_equals); 218 ASSERT_NE(std::string::npos, index_of_pipe); 219 ASSERT_GT(index_of_pipe, index_of_equals); 220 parsed_ids.insert(it->substr(index_of_equals + 1, 221 index_of_pipe - index_of_equals - 1)); 222 } 223 224 // Verify that the resulting string contains each of the expected labels, 225 // and nothing more. Note that the date is stripped out and ignored. 226 for (std::vector<std::string>::const_iterator it = 227 expected_ids_list.begin(); it != expected_ids_list.end(); ++it) { 228 std::set<std::string>::iterator it2 = parsed_ids.find(*it); 229 EXPECT_TRUE(parsed_ids.end() != it2); 230 parsed_ids.erase(it2); 231 } 232 EXPECT_TRUE(parsed_ids.empty()); 233 } // for 234 } 235 236 TEST_F(VariationsUtilTest, CombineExperimentLabels) { 237 struct { 238 const char* variations_labels; 239 const char* other_labels; 240 const char* expected_label; 241 } test_cases[] = { 242 {"A=B|Tue, 21 Jan 2014 15:30:21 GMT", 243 "C=D|Tue, 21 Jan 2014 15:30:21 GMT", 244 "C=D|Tue, 21 Jan 2014 15:30:21 GMT;A=B|Tue, 21 Jan 2014 15:30:21 GMT"}, 245 {"A=B|Tue, 21 Jan 2014 15:30:21 GMT", 246 "", 247 "A=B|Tue, 21 Jan 2014 15:30:21 GMT"}, 248 {"", 249 "A=B|Tue, 21 Jan 2014 15:30:21 GMT", 250 "A=B|Tue, 21 Jan 2014 15:30:21 GMT"}, 251 {"A=B|Tue, 21 Jan 2014 15:30:21 GMT;C=D|Tue, 21 Jan 2014 15:30:21 GMT", 252 "P=Q|Tue, 21 Jan 2014 15:30:21 GMT;X=Y|Tue, 21 Jan 2014 15:30:21 GMT", 253 "P=Q|Tue, 21 Jan 2014 15:30:21 GMT;X=Y|Tue, 21 Jan 2014 15:30:21 GMT;" 254 "A=B|Tue, 21 Jan 2014 15:30:21 GMT;C=D|Tue, 21 Jan 2014 15:30:21 GMT"}, 255 {"", 256 "", 257 ""}, 258 }; 259 260 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) { 261 std::string result = UTF16ToUTF8(CombineExperimentLabels( 262 ASCIIToUTF16(test_cases[i].variations_labels), 263 ASCIIToUTF16(test_cases[i].other_labels))); 264 EXPECT_EQ(test_cases[i].expected_label, result); 265 } 266 } 267 268 TEST_F(VariationsUtilTest, ExtractNonVariationLabels) { 269 struct { 270 const char* input_label; 271 const char* expected_output; 272 } test_cases[] = { 273 // Empty 274 {"", ""}, 275 // One 276 {"gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT", 277 "gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT"}, 278 // Three 279 {"CrVar1=123|Tue, 21 Jan 2014 15:30:21 GMT;" 280 "experiment1=456|Tue, 21 Jan 2014 15:30:21 GMT;" 281 "experiment2=789|Tue, 21 Jan 2014 15:30:21 GMT;" 282 "CrVar1=123|Tue, 21 Jan 2014 15:30:21 GMT", 283 "experiment1=456|Tue, 21 Jan 2014 15:30:21 GMT;" 284 "experiment2=789|Tue, 21 Jan 2014 15:30:21 GMT"}, 285 // One and one Variation 286 {"gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT;" 287 "CrVar1=3310002|Tue, 21 Jan 2014 15:30:21 GMT", 288 "gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT"}, 289 // One and one Variation, flipped 290 {"CrVar1=3310002|Tue, 21 Jan 2014 15:30:21 GMT;" 291 "gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT", 292 "gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT"}, 293 // Sandwiched 294 {"CrVar1=3310002|Tue, 21 Jan 2014 15:30:21 GMT;" 295 "gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT;" 296 "CrVar2=3310003|Tue, 21 Jan 2014 15:30:21 GMT;" 297 "CrVar3=3310004|Tue, 21 Jan 2014 15:30:21 GMT", 298 "gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT"}, 299 // Only Variations 300 {"CrVar1=3310002|Tue, 21 Jan 2014 15:30:21 GMT;" 301 "CrVar2=3310003|Tue, 21 Jan 2014 15:30:21 GMT;" 302 "CrVar3=3310004|Tue, 21 Jan 2014 15:30:21 GMT", 303 ""}, 304 // Empty values 305 {"gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT;" 306 "CrVar1=3310002|Tue, 21 Jan 2014 15:30:21 GMT", 307 "gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT"}, 308 // Trailing semicolon 309 {"gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT;" 310 "CrVar1=3310002|Tue, 21 Jan 2014 15:30:21 GMT;", // Note the semi here. 311 "gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT"}, 312 // Semis 313 {";;;;", ""}, 314 }; 315 316 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) { 317 std::string non_variation_labels = UTF16ToUTF8( 318 ExtractNonVariationLabels(ASCIIToUTF16(test_cases[i].input_label))); 319 EXPECT_EQ(test_cases[i].expected_output, non_variation_labels); 320 } 321 } 322 323 } // namespace chrome_variations 324