| OLD | NEW |
| 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 // Test of FieldTrial class | 5 // Test of FieldTrial class |
| 6 | 6 |
| 7 #include "base/metrics/field_trial.h" | 7 #include "base/metrics/field_trial.h" |
| 8 | 8 |
| 9 #include "base/stringprintf.h" | 9 #include "base/stringprintf.h" |
| 10 #include "testing/gtest/include/gtest/gtest.h" | 10 #include "testing/gtest/include/gtest/gtest.h" |
| 11 | 11 |
| 12 namespace base { | 12 namespace base { |
| 13 | 13 |
| 14 class FieldTrialTest : public testing::Test { | 14 class FieldTrialTest : public testing::Test { |
| 15 public: | 15 public: |
| 16 FieldTrialTest() : trial_list_() { } | 16 FieldTrialTest() : trial_list_() { |
| 17 Time now = Time::NowFromSystemTime(); |
| 18 TimeDelta oneYear = TimeDelta::FromDays(365); |
| 19 Time::Exploded exploded; |
| 20 |
| 21 Time next_year_time = now + oneYear; |
| 22 next_year_time.LocalExplode(&exploded); |
| 23 next_year_ = exploded.year; |
| 24 |
| 25 Time last_year_time = now - oneYear; |
| 26 last_year_time.LocalExplode(&exploded); |
| 27 last_year_ = exploded.year; |
| 28 } |
| 29 |
| 30 protected: |
| 31 int next_year_; |
| 32 int last_year_; |
| 17 | 33 |
| 18 private: | 34 private: |
| 19 FieldTrialList trial_list_; | 35 FieldTrialList trial_list_; |
| 20 }; | 36 }; |
| 21 | 37 |
| 22 // Test registration, and also check that destructors are called for trials | 38 // Test registration, and also check that destructors are called for trials |
| 23 // (and that Purify doesn't catch us leaking). | 39 // (and that Purify doesn't catch us leaking). |
| 24 TEST_F(FieldTrialTest, Registration) { | 40 TEST_F(FieldTrialTest, Registration) { |
| 25 const char* name1 = "name 1 test"; | 41 const char* name1 = "name 1 test"; |
| 26 const char* name2 = "name 2 test"; | 42 const char* name2 = "name 2 test"; |
| 27 EXPECT_FALSE(FieldTrialList::Find(name1)); | 43 EXPECT_FALSE(FieldTrialList::Find(name1)); |
| 28 EXPECT_FALSE(FieldTrialList::Find(name2)); | 44 EXPECT_FALSE(FieldTrialList::Find(name2)); |
| 29 | 45 |
| 30 FieldTrial* trial1 = new FieldTrial(name1, 10); | 46 FieldTrial* trial1 = |
| 31 EXPECT_EQ(FieldTrial::kNotParticipating, trial1->group()); | 47 new FieldTrial(name1, 10, "default name 1 test", next_year_, 12, 31); |
| 48 EXPECT_EQ(FieldTrial::kNotFinalized, trial1->group_); |
| 32 EXPECT_EQ(name1, trial1->name()); | 49 EXPECT_EQ(name1, trial1->name()); |
| 33 EXPECT_EQ("", trial1->group_name()); | 50 EXPECT_EQ("", trial1->group_name_internal()); |
| 34 | 51 |
| 35 trial1->AppendGroup("", 7); | 52 trial1->AppendGroup("", 7); |
| 36 | 53 |
| 37 EXPECT_EQ(trial1, FieldTrialList::Find(name1)); | 54 EXPECT_EQ(trial1, FieldTrialList::Find(name1)); |
| 38 EXPECT_FALSE(FieldTrialList::Find(name2)); | 55 EXPECT_FALSE(FieldTrialList::Find(name2)); |
| 39 | 56 |
| 40 FieldTrial* trial2 = new FieldTrial(name2, 10); | 57 FieldTrial* trial2 = |
| 41 EXPECT_EQ(FieldTrial::kNotParticipating, trial2->group()); | 58 new FieldTrial(name2, 10, "default name 2 test", next_year_, 12, 31); |
| 59 EXPECT_EQ(FieldTrial::kNotFinalized, trial2->group_); |
| 42 EXPECT_EQ(name2, trial2->name()); | 60 EXPECT_EQ(name2, trial2->name()); |
| 43 EXPECT_EQ("", trial2->group_name()); | 61 EXPECT_EQ("", trial2->group_name_internal()); |
| 44 | 62 |
| 45 trial2->AppendGroup("a first group", 7); | 63 trial2->AppendGroup("a first group", 7); |
| 46 | 64 |
| 47 EXPECT_EQ(trial1, FieldTrialList::Find(name1)); | 65 EXPECT_EQ(trial1, FieldTrialList::Find(name1)); |
| 48 EXPECT_EQ(trial2, FieldTrialList::Find(name2)); | 66 EXPECT_EQ(trial2, FieldTrialList::Find(name2)); |
| 49 // Note: FieldTrialList should delete the objects at shutdown. | 67 // Note: FieldTrialList should delete the objects at shutdown. |
| 50 } | 68 } |
| 51 | 69 |
| 52 TEST_F(FieldTrialTest, AbsoluteProbabilities) { | 70 TEST_F(FieldTrialTest, AbsoluteProbabilities) { |
| 53 char always_true[] = " always true"; | 71 char always_true[] = " always true"; |
| 72 char default_always_true[] = " default always true"; |
| 54 char always_false[] = " always false"; | 73 char always_false[] = " always false"; |
| 74 char default_always_false[] = " default always false"; |
| 55 for (int i = 1; i < 250; ++i) { | 75 for (int i = 1; i < 250; ++i) { |
| 56 // Try lots of names, by changing the first character of the name. | 76 // Try lots of names, by changing the first character of the name. |
| 57 always_true[0] = i; | 77 always_true[0] = i; |
| 78 default_always_true[0] = i; |
| 58 always_false[0] = i; | 79 always_false[0] = i; |
| 80 default_always_false[0] = i; |
| 59 | 81 |
| 60 FieldTrial* trial_true = new FieldTrial(always_true, 10); | 82 FieldTrial* trial_true = |
| 83 new FieldTrial( |
| 84 always_true, 10, default_always_true, next_year_, 12, 31); |
| 61 const std::string winner = "TheWinner"; | 85 const std::string winner = "TheWinner"; |
| 62 int winner_group = trial_true->AppendGroup(winner, 10); | 86 int winner_group = trial_true->AppendGroup(winner, 10); |
| 63 | 87 |
| 64 EXPECT_EQ(winner_group, trial_true->group()); | 88 EXPECT_EQ(winner_group, trial_true->group()); |
| 65 EXPECT_EQ(winner, trial_true->group_name()); | 89 EXPECT_EQ(winner, trial_true->group_name()); |
| 66 | 90 |
| 67 FieldTrial* trial_false = new FieldTrial(always_false, 10); | 91 FieldTrial* trial_false = |
| 92 new FieldTrial( |
| 93 always_false, 10, default_always_false, next_year_, 12, 31); |
| 68 int loser_group = trial_false->AppendGroup("ALoser", 0); | 94 int loser_group = trial_false->AppendGroup("ALoser", 0); |
| 69 | 95 |
| 70 EXPECT_NE(loser_group, trial_false->group()); | 96 EXPECT_NE(loser_group, trial_false->group()); |
| 71 } | 97 } |
| 72 } | 98 } |
| 73 | 99 |
| 74 TEST_F(FieldTrialTest, RemainingProbability) { | 100 TEST_F(FieldTrialTest, RemainingProbability) { |
| 75 // First create a test that hasn't had a winner yet. | 101 // First create a test that hasn't had a winner yet. |
| 76 const std::string winner = "Winner"; | 102 const std::string winner = "Winner"; |
| 77 const std::string loser = "Loser"; | 103 const std::string loser = "Loser"; |
| 78 scoped_refptr<FieldTrial> trial; | 104 scoped_refptr<FieldTrial> trial; |
| 79 int counter = 0; | 105 int counter = 0; |
| 80 do { | 106 do { |
| 81 std::string name = StringPrintf("trial%d", ++counter); | 107 std::string name = StringPrintf("trial%d", ++counter); |
| 82 trial = new FieldTrial(name, 10); | 108 trial = new FieldTrial(name, 10, winner, next_year_, 12, 31); |
| 83 trial->AppendGroup(loser, 5); // 50% chance of not being chosen. | 109 trial->AppendGroup(loser, 5); // 50% chance of not being chosen. |
| 84 } while (trial->group() != FieldTrial::kNotParticipating); | 110 // If a group is not assigned, group_ will be kNotFinalized. |
| 111 } while (trial->group_ != FieldTrial::kNotFinalized); |
| 85 | 112 |
| 86 // Now add a winner with all remaining probability. | 113 // And that 'default' group (winner) should always win. |
| 87 trial->AppendGroup(winner, FieldTrial::kAllRemainingProbability); | 114 EXPECT_EQ(FieldTrial::kDefaultGroupNumber, trial->group()); |
| 88 | 115 |
| 89 // And that winner should ALWAYS win. | 116 // And that winner should ALWAYS win. |
| 90 EXPECT_EQ(winner, trial->group_name()); | 117 EXPECT_EQ(winner, trial->group_name()); |
| 91 } | 118 } |
| 92 | 119 |
| 93 TEST_F(FieldTrialTest, FiftyFiftyProbability) { | 120 TEST_F(FieldTrialTest, FiftyFiftyProbability) { |
| 94 // Check that even with small divisors, we have the proper probabilities, and | 121 // Check that even with small divisors, we have the proper probabilities, and |
| 95 // all outcomes are possible. Since this is a 50-50 test, it should get both | 122 // all outcomes are possible. Since this is a 50-50 test, it should get both |
| 96 // outcomes in a few tries, but we'll try no more than 100 times (and be flaky | 123 // outcomes in a few tries, but we'll try no more than 100 times (and be flaky |
| 97 // with probability around 1 in 2^99). | 124 // with probability around 1 in 2^99). |
| 98 bool first_winner = false; | 125 bool first_winner = false; |
| 99 bool second_winner = false; | 126 bool second_winner = false; |
| 100 int counter = 0; | 127 int counter = 0; |
| 101 do { | 128 do { |
| 102 std::string name = base::StringPrintf("FiftyFifty%d", ++counter); | 129 std::string name = base::StringPrintf("FiftyFifty%d", ++counter); |
| 103 scoped_refptr<FieldTrial> trial(new FieldTrial(name, 2)); | 130 std::string default_group_name = base::StringPrintf("Default FiftyFifty%d", |
| 131 ++counter); |
| 132 scoped_refptr<FieldTrial> trial( |
| 133 new FieldTrial(name, 2, default_group_name, next_year_, 12, 31)); |
| 104 trial->AppendGroup("first", 1); // 50% chance of being chosen. | 134 trial->AppendGroup("first", 1); // 50% chance of being chosen. |
| 105 if (trial->group() != FieldTrial::kNotParticipating) { | 135 // If group_ is kNotFinalized, then a group assignement hasn't been done. |
| 136 if (trial->group_ != FieldTrial::kNotFinalized) { |
| 106 first_winner = true; | 137 first_winner = true; |
| 107 continue; | 138 continue; |
| 108 } | 139 } |
| 109 trial->AppendGroup("second", 1); // Always chosen at this point. | 140 trial->AppendGroup("second", 1); // Always chosen at this point. |
| 110 EXPECT_NE(FieldTrial::kNotParticipating, trial->group()); | 141 EXPECT_NE(FieldTrial::kNotFinalized, trial->group()); |
| 111 second_winner = true; | 142 second_winner = true; |
| 112 } while ((!second_winner || !first_winner) && counter < 100); | 143 } while ((!second_winner || !first_winner) && counter < 100); |
| 113 EXPECT_TRUE(second_winner); | 144 EXPECT_TRUE(second_winner); |
| 114 EXPECT_TRUE(first_winner); | 145 EXPECT_TRUE(first_winner); |
| 115 } | 146 } |
| 116 | 147 |
| 117 TEST_F(FieldTrialTest, MiddleProbabilities) { | 148 TEST_F(FieldTrialTest, MiddleProbabilities) { |
| 118 char name[] = " same name"; | 149 char name[] = " same name"; |
| 150 char default_group_name[] = " default same name"; |
| 119 bool false_event_seen = false; | 151 bool false_event_seen = false; |
| 120 bool true_event_seen = false; | 152 bool true_event_seen = false; |
| 121 for (int i = 1; i < 250; ++i) { | 153 for (int i = 1; i < 250; ++i) { |
| 122 name[0] = i; | 154 name[0] = i; |
| 123 FieldTrial* trial = new FieldTrial(name, 10); | 155 default_group_name[0] = i; |
| 156 FieldTrial* trial = |
| 157 new FieldTrial(name, 10, default_group_name, next_year_, 12, 31); |
| 124 int might_win = trial->AppendGroup("MightWin", 5); | 158 int might_win = trial->AppendGroup("MightWin", 5); |
| 125 | 159 |
| 126 if (trial->group() == might_win) { | 160 if (trial->group() == might_win) { |
| 127 true_event_seen = true; | 161 true_event_seen = true; |
| 128 } else { | 162 } else { |
| 129 false_event_seen = true; | 163 false_event_seen = true; |
| 130 } | 164 } |
| 131 if (false_event_seen && true_event_seen) | 165 if (false_event_seen && true_event_seen) |
| 132 return; // Successful test!!! | 166 return; // Successful test!!! |
| 133 } | 167 } |
| 134 // Very surprising to get here. Probability should be around 1 in 2 ** 250. | 168 // Very surprising to get here. Probability should be around 1 in 2 ** 250. |
| 135 // One of the following will fail. | 169 // One of the following will fail. |
| 136 EXPECT_TRUE(false_event_seen); | 170 EXPECT_TRUE(false_event_seen); |
| 137 EXPECT_TRUE(true_event_seen); | 171 EXPECT_TRUE(true_event_seen); |
| 138 } | 172 } |
| 139 | 173 |
| 140 TEST_F(FieldTrialTest, OneWinner) { | 174 TEST_F(FieldTrialTest, OneWinner) { |
| 141 char name[] = "Some name"; | 175 char name[] = "Some name"; |
| 176 char default_group_name[] = "Default some name"; |
| 142 int group_count(10); | 177 int group_count(10); |
| 143 | 178 |
| 144 FieldTrial* trial = new FieldTrial(name, group_count); | 179 FieldTrial* trial = |
| 180 new FieldTrial( |
| 181 name, group_count, default_group_name, next_year_, 12, 31); |
| 145 int winner_index(-2); | 182 int winner_index(-2); |
| 146 std::string winner_name; | 183 std::string winner_name; |
| 147 | 184 |
| 148 for (int i = 1; i <= group_count; ++i) { | 185 for (int i = 1; i <= group_count; ++i) { |
| 149 int might_win = trial->AppendGroup("", 1); | 186 int might_win = trial->AppendGroup("", 1); |
| 150 | 187 |
| 151 if (trial->group() == might_win) { | 188 // Because we keep appending groups, we want to see if the last group that |
| 189 // was added has been assigned or not. |
| 190 if (trial->group_ == might_win) { |
| 152 EXPECT_EQ(-2, winner_index); | 191 EXPECT_EQ(-2, winner_index); |
| 153 winner_index = might_win; | 192 winner_index = might_win; |
| 154 StringAppendF(&winner_name, "%d", might_win); | 193 StringAppendF(&winner_name, "%d", might_win); |
| 155 EXPECT_EQ(winner_name, trial->group_name()); | 194 EXPECT_EQ(winner_name, trial->group_name()); |
| 156 } | 195 } |
| 157 } | 196 } |
| 158 EXPECT_GE(winner_index, 0); | 197 EXPECT_GE(winner_index, 0); |
| 159 EXPECT_EQ(trial->group(), winner_index); | 198 EXPECT_EQ(trial->group(), winner_index); |
| 160 EXPECT_EQ(trial->group_name(), winner_name); | 199 EXPECT_EQ(trial->group_name(), winner_name); |
| 161 } | 200 } |
| 162 | 201 |
| 202 TEST_F(FieldTrialTest, DisableProbability) { |
| 203 const std::string default_group_name = "Default group"; |
| 204 const std::string loser = "Loser"; |
| 205 const std::string name = "Trial"; |
| 206 |
| 207 // Create a field trail that has expired. |
| 208 scoped_refptr<FieldTrial> trial; |
| 209 trial = new FieldTrial( |
| 210 name, 1000000000, default_group_name, last_year_, 1, 1); |
| 211 trial->AppendGroup(loser, 999999999); // 99.9999999% chance of being chosen. |
| 212 |
| 213 // Because trial has expired, we should always be in the default group. |
| 214 EXPECT_EQ(FieldTrial::kDefaultGroupNumber, trial->group()); |
| 215 |
| 216 // And that default_group_name should ALWAYS win. |
| 217 EXPECT_EQ(default_group_name, trial->group_name()); |
| 218 } |
| 219 |
| 163 TEST_F(FieldTrialTest, Save) { | 220 TEST_F(FieldTrialTest, Save) { |
| 164 std::string save_string; | 221 std::string save_string; |
| 165 | 222 |
| 166 FieldTrial* trial = new FieldTrial("Some name", 10); | 223 FieldTrial* trial = |
| 224 new FieldTrial( |
| 225 "Some name", 10, "Default some name", next_year_, 12, 31); |
| 167 // There is no winner yet, so no textual group name is associated with trial. | 226 // There is no winner yet, so no textual group name is associated with trial. |
| 168 EXPECT_EQ("", trial->group_name()); | 227 EXPECT_EQ("", trial->group_name_internal()); |
| 169 FieldTrialList::StatesToString(&save_string); | 228 FieldTrialList::StatesToString(&save_string); |
| 170 EXPECT_EQ("", save_string); | 229 EXPECT_EQ("Some name/Default some name/", save_string); |
| 171 save_string.clear(); | 230 save_string.clear(); |
| 172 | 231 |
| 173 // Create a winning group. | 232 // Create a winning group. |
| 174 trial->AppendGroup("Winner", 10); | 233 trial->AppendGroup("Winner", 10); |
| 175 FieldTrialList::StatesToString(&save_string); | 234 FieldTrialList::StatesToString(&save_string); |
| 176 EXPECT_EQ("Some name/Winner/", save_string); | 235 EXPECT_EQ("Some name/Winner/", save_string); |
| 177 save_string.clear(); | 236 save_string.clear(); |
| 178 | 237 |
| 179 // Create a second trial and winning group. | 238 // Create a second trial and winning group. |
| 180 FieldTrial* trial2 = new FieldTrial("xxx", 10); | 239 FieldTrial* trial2 = |
| 240 new FieldTrial("xxx", 10, "Default xxx", next_year_, 12, 31); |
| 181 trial2->AppendGroup("yyyy", 10); | 241 trial2->AppendGroup("yyyy", 10); |
| 182 | 242 |
| 183 FieldTrialList::StatesToString(&save_string); | 243 FieldTrialList::StatesToString(&save_string); |
| 184 // We assume names are alphabetized... though this is not critical. | 244 // We assume names are alphabetized... though this is not critical. |
| 185 EXPECT_EQ("Some name/Winner/xxx/yyyy/", save_string); | 245 EXPECT_EQ("Some name/Winner/xxx/yyyy/", save_string); |
| 186 } | 246 } |
| 187 | 247 |
| 188 TEST_F(FieldTrialTest, Restore) { | 248 TEST_F(FieldTrialTest, Restore) { |
| 189 EXPECT_TRUE(FieldTrialList::Find("Some_name") == NULL); | 249 EXPECT_TRUE(FieldTrialList::Find("Some_name") == NULL); |
| 190 EXPECT_TRUE(FieldTrialList::Find("xxx") == NULL); | 250 EXPECT_TRUE(FieldTrialList::Find("xxx") == NULL); |
| 191 | 251 |
| 192 FieldTrialList::StringAugmentsState("Some_name/Winner/xxx/yyyy/"); | 252 FieldTrialList::CreateTrialsInChildProcess("Some_name/Winner/xxx/yyyy/"); |
| 193 | 253 |
| 194 FieldTrial* trial = FieldTrialList::Find("Some_name"); | 254 FieldTrial* trial = FieldTrialList::Find("Some_name"); |
| 195 ASSERT_NE(static_cast<FieldTrial*>(NULL), trial); | 255 ASSERT_NE(static_cast<FieldTrial*>(NULL), trial); |
| 196 EXPECT_EQ("Winner", trial->group_name()); | 256 EXPECT_EQ("Winner", trial->group_name()); |
| 197 EXPECT_EQ("Some_name", trial->name()); | 257 EXPECT_EQ("Some_name", trial->name()); |
| 198 | 258 |
| 199 trial = FieldTrialList::Find("xxx"); | 259 trial = FieldTrialList::Find("xxx"); |
| 200 ASSERT_NE(static_cast<FieldTrial*>(NULL), trial); | 260 ASSERT_NE(static_cast<FieldTrial*>(NULL), trial); |
| 201 EXPECT_EQ("yyyy", trial->group_name()); | 261 EXPECT_EQ("yyyy", trial->group_name()); |
| 202 EXPECT_EQ("xxx", trial->name()); | 262 EXPECT_EQ("xxx", trial->name()); |
| 203 } | 263 } |
| 204 | 264 |
| 205 TEST_F(FieldTrialTest, BogusRestore) { | 265 TEST_F(FieldTrialTest, BogusRestore) { |
| 206 EXPECT_FALSE(FieldTrialList::StringAugmentsState("MissingSlash")); | 266 EXPECT_FALSE(FieldTrialList::CreateTrialsInChildProcess("MissingSlash")); |
| 207 EXPECT_FALSE(FieldTrialList::StringAugmentsState("MissingGroupName/")); | 267 EXPECT_FALSE(FieldTrialList::CreateTrialsInChildProcess("MissingGroupName/")); |
| 208 EXPECT_FALSE(FieldTrialList::StringAugmentsState("MissingFinalSlash/gname")); | 268 EXPECT_FALSE( |
| 209 EXPECT_FALSE(FieldTrialList::StringAugmentsState("/noname, only group/")); | 269 FieldTrialList::CreateTrialsInChildProcess("MissingFinalSlash/gname")); |
| 270 EXPECT_FALSE( |
| 271 FieldTrialList::CreateTrialsInChildProcess("/noname, only group/")); |
| 210 } | 272 } |
| 211 | 273 |
| 212 TEST_F(FieldTrialTest, DuplicateRestore) { | 274 TEST_F(FieldTrialTest, DuplicateRestore) { |
| 213 FieldTrial* trial = new FieldTrial("Some name", 10); | 275 FieldTrial* trial = |
| 276 new FieldTrial( |
| 277 "Some name", 10, "Default some name", next_year_, 12, 31); |
| 214 trial->AppendGroup("Winner", 10); | 278 trial->AppendGroup("Winner", 10); |
| 215 std::string save_string; | 279 std::string save_string; |
| 216 FieldTrialList::StatesToString(&save_string); | 280 FieldTrialList::StatesToString(&save_string); |
| 217 EXPECT_EQ("Some name/Winner/", save_string); | 281 EXPECT_EQ("Some name/Winner/", save_string); |
| 218 | 282 |
| 219 // It is OK if we redundantly specify a winner. | 283 // It is OK if we redundantly specify a winner. |
| 220 EXPECT_TRUE(FieldTrialList::StringAugmentsState(save_string)); | 284 EXPECT_TRUE(FieldTrialList::CreateTrialsInChildProcess(save_string)); |
| 221 | 285 |
| 222 // But it is an error to try to change to a different winner. | 286 // But it is an error to try to change to a different winner. |
| 223 EXPECT_FALSE(FieldTrialList::StringAugmentsState("Some name/Loser/")); | 287 EXPECT_FALSE(FieldTrialList::CreateTrialsInChildProcess("Some name/Loser/")); |
| 224 } | 288 } |
| 225 | 289 |
| 226 TEST_F(FieldTrialTest, MakeName) { | 290 TEST_F(FieldTrialTest, MakeName) { |
| 227 FieldTrial* trial = new FieldTrial("Field Trial", 10); | 291 FieldTrial* trial = |
| 228 trial->AppendGroup("Winner", 10); | 292 new FieldTrial("Field Trial", 10, "Winner", next_year_, 12, 31); |
| 293 trial->group(); |
| 229 EXPECT_EQ("Histogram_Winner", | 294 EXPECT_EQ("Histogram_Winner", |
| 230 FieldTrial::MakeName("Histogram", "Field Trial")); | 295 FieldTrial::MakeName("Histogram", "Field Trial")); |
| 231 } | 296 } |
| 232 | 297 |
| 233 } // namespace base | 298 } // namespace base |
| OLD | NEW |