1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
|
// Copyright 2015 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 "chrome/browser/metrics/perf/random_selector.h"
#include <cmath>
#include <map>
#include <string>
#include "testing/gtest/include/gtest/gtest.h"
// A small floating point number used to verify that the expected odds are equal
// to the odds set.
const double kEpsilon = 0.01;
// A class that overrides RandDoubleUpTo() to not be random. The number
// generator will emulate a uniform distribution of numbers between 0.0 and
// |max| when called with the same |max| parameter and a whole multiple of
// |random_period| times. This allows better testing of the RandomSelector
// class.
class RandomSelectorWithCustomRNG : public RandomSelector {
public:
explicit RandomSelectorWithCustomRNG(unsigned int random_period)
: random_period_(random_period), current_index_(0) {}
private:
// This function returns floats between 0.0 and |max| in an increasing
// fashion at regular intervals.
double RandDoubleUpTo(double max) override {
current_index_ = (current_index_ + 1) % random_period_;
return max * current_index_ / random_period_;
}
// Period (number of calls) over which the fake RNG repeats.
const unsigned int random_period_;
// Stores the current position we are at in the interval between 0.0 and
// |max|. See the function RandDoubleUpTo for details on how this is used.
int current_index_;
DISALLOW_COPY_AND_ASSIGN(RandomSelectorWithCustomRNG);
};
// Use the random_selector to generate some values. The number of values to
// generate is |iterations|.
void GenerateResults(size_t iterations,
RandomSelector* random_selector,
std::map<std::string, int>* results) {
for (size_t i = 0; i < iterations; ++i) {
const std::string& next_value = random_selector->Select();
(*results)[next_value]++;
}
}
// This function tests whether the results are close enough to the odds (within
// 1%).
void CheckResultsAgainstOdds(
const std::vector<RandomSelector::WeightAndValue>& odds,
const std::map<std::string, int>& results) {
EXPECT_EQ(odds.size(), results.size());
const double odds_sum = RandomSelector::SumWeights(odds);
int results_sum = 0;
for (const auto& item : results) {
results_sum += item.second;
}
for (const auto& odd : odds) {
const auto result = results.find(odd.value);
EXPECT_NE(result, results.end());
const double results_ratio = 1.0*result->second / results_sum;
const double odds_ratio = odd.weight / odds_sum;
const double abs_diff = std::abs(results_ratio - odds_ratio);
EXPECT_LT(abs_diff, kEpsilon);
}
}
TEST(RandomSelector, SimpleAccessors) {
using WeightAndValue = RandomSelector::WeightAndValue;
std::vector<WeightAndValue> odds;
odds.push_back(WeightAndValue(1, "a 1"));
odds.push_back(WeightAndValue(3, "b --help"));
odds.push_back(WeightAndValue(107, "c bar"));
EXPECT_EQ(111.0L, RandomSelector::SumWeights(odds));
RandomSelector random_selector;
EXPECT_TRUE(random_selector.SetOdds(odds));
EXPECT_EQ(3UL, random_selector.num_values());
EXPECT_EQ(odds, random_selector.odds());
}
// Ensure RandomSelector is able to generate results from given odds.
TEST(RandomSelector, GenerateTest) {
using WeightAndValue = RandomSelector::WeightAndValue;
const int kLargeNumber = 2000;
std::vector<RandomSelector::WeightAndValue> odds;
odds.push_back(WeightAndValue(1, "a 1"));
odds.push_back(WeightAndValue(2, "b --help"));
odds.push_back(WeightAndValue(3, "c bar"));
RandomSelectorWithCustomRNG random_selector(kLargeNumber);
EXPECT_TRUE(random_selector.SetOdds(odds));
// Generate a lot of values.
std::map<std::string, int> results;
GenerateResults(kLargeNumber, &random_selector, &results);
// Ensure the values and odds are related.
CheckResultsAgainstOdds(odds, results);
}
TEST(RandomSelector, InvalidWeights) {
using WeightAndValue = RandomSelector::WeightAndValue;
std::vector<RandomSelector::WeightAndValue> good_odds;
good_odds.push_back(WeightAndValue(1, "a 1"));
good_odds.push_back(WeightAndValue(2, "b --help"));
good_odds.push_back(WeightAndValue(3, "c bar"));
RandomSelector random_selector;
EXPECT_TRUE(random_selector.SetOdds(good_odds));
EXPECT_EQ(good_odds, random_selector.odds());
std::vector<RandomSelector::WeightAndValue> bad_odds;
bad_odds.push_back(WeightAndValue(1, "a 1"));
bad_odds.push_back(WeightAndValue(2, "b --help"));
bad_odds.push_back(WeightAndValue(-3.5, "c bar"));
EXPECT_FALSE(random_selector.SetOdds(bad_odds));
EXPECT_EQ(good_odds, random_selector.odds());
bad_odds[2].weight = 0.0;
EXPECT_FALSE(random_selector.SetOdds(bad_odds));
EXPECT_EQ(good_odds, random_selector.odds());
}
TEST(RandomSelector, EmptyWeights) {
using WeightAndValue = RandomSelector::WeightAndValue;
std::vector<RandomSelector::WeightAndValue> good_odds;
good_odds.push_back(WeightAndValue(1, "a 1"));
good_odds.push_back(WeightAndValue(2, "b --help"));
good_odds.push_back(WeightAndValue(3, "c bar"));
RandomSelector random_selector;
EXPECT_TRUE(random_selector.SetOdds(good_odds));
EXPECT_EQ(good_odds, random_selector.odds());
std::vector<RandomSelector::WeightAndValue> empty_odds;
EXPECT_FALSE(random_selector.SetOdds(empty_odds));
EXPECT_EQ(good_odds, random_selector.odds());
}
|