1 //
2 //
3 // Copyright 2015 gRPC authors.
4 //
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
8 //
9 // http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
16 //
17 //
18
19 #include "src/core/lib/event_engine/posix_engine/timer_heap.h"
20
21 #include <stdint.h>
22 #include <stdlib.h>
23
24 #include <algorithm>
25 #include <utility>
26
27 #include "absl/log/check.h"
28 #include "gmock/gmock.h"
29 #include "gtest/gtest.h"
30 #include "src/core/lib/event_engine/posix_engine/timer.h"
31 #include "src/core/util/bitset.h"
32
33 using testing::Contains;
34 using testing::Not;
35
36 namespace grpc_event_engine {
37 namespace experimental {
38
39 namespace {
RandomDeadline(void)40 int64_t RandomDeadline(void) { return rand(); }
41
CreateTestElements(size_t num_elements)42 std::vector<Timer> CreateTestElements(size_t num_elements) {
43 std::vector<Timer> elems(num_elements);
44 for (size_t i = 0; i < num_elements; i++) {
45 elems[i].deadline = RandomDeadline();
46 }
47 return elems;
48 }
49
CheckValid(TimerHeap * pq)50 void CheckValid(TimerHeap* pq) {
51 const std::vector<Timer*>& timers = pq->TestOnlyGetTimers();
52 for (size_t i = 0; i < timers.size(); ++i) {
53 size_t left_child = 1u + (2u * i);
54 size_t right_child = left_child + 1u;
55 if (left_child < timers.size()) {
56 EXPECT_LE(timers[i]->deadline, timers[left_child]->deadline);
57 }
58 if (right_child < timers.size()) {
59 EXPECT_LE(timers[i]->deadline, timers[right_child]->deadline);
60 }
61 }
62 }
63
TEST(TimerHeapTest,Basics)64 TEST(TimerHeapTest, Basics) {
65 TimerHeap pq;
66 const size_t num_test_elements = 200;
67 const size_t num_test_operations = 10000;
68 size_t i;
69 std::vector<Timer> test_elements = CreateTestElements(num_test_elements);
70 grpc_core::BitSet<num_test_elements> inpq;
71
72 EXPECT_TRUE(pq.is_empty());
73 CheckValid(&pq);
74 for (i = 0; i < num_test_elements; ++i) {
75 EXPECT_THAT(pq.TestOnlyGetTimers(), Not(Contains(&test_elements[i])));
76 pq.Add(&test_elements[i]);
77 CheckValid(&pq);
78 EXPECT_THAT(pq.TestOnlyGetTimers(), Contains(&test_elements[i]));
79 inpq.set(i);
80 }
81 for (i = 0; i < num_test_elements; ++i) {
82 // Test that check still succeeds even for element that wasn't just
83 // inserted.
84 EXPECT_THAT(pq.TestOnlyGetTimers(), Contains(&test_elements[i]));
85 }
86
87 EXPECT_EQ(pq.TestOnlyGetTimers().size(), num_test_elements);
88 CheckValid(&pq);
89
90 for (i = 0; i < num_test_operations; ++i) {
91 size_t elem_num = static_cast<size_t>(rand()) % num_test_elements;
92 Timer* el = &test_elements[elem_num];
93 if (!inpq.is_set(elem_num)) { // not in pq
94 EXPECT_THAT(pq.TestOnlyGetTimers(), Not(Contains(el)));
95 el->deadline = RandomDeadline();
96 pq.Add(el);
97 EXPECT_THAT(pq.TestOnlyGetTimers(), Contains(el));
98 inpq.set(elem_num);
99 CheckValid(&pq);
100 } else {
101 EXPECT_THAT(pq.TestOnlyGetTimers(), Contains(el));
102 pq.Remove(el);
103 EXPECT_THAT(pq.TestOnlyGetTimers(), Not(Contains(el)));
104 inpq.clear(elem_num);
105 CheckValid(&pq);
106 }
107 }
108 }
109
110 struct ElemStruct {
111 Timer elem;
112 bool inserted = false;
113 };
114
SearchElems(std::vector<ElemStruct> & elems,bool inserted)115 ElemStruct* SearchElems(std::vector<ElemStruct>& elems, bool inserted) {
116 std::vector<size_t> search_order;
117 for (size_t i = 0; i < elems.size(); i++) {
118 search_order.push_back(i);
119 }
120 for (size_t i = 0; i < elems.size() * 2; i++) {
121 size_t a = static_cast<size_t>(rand()) % elems.size();
122 size_t b = static_cast<size_t>(rand()) % elems.size();
123 std::swap(search_order[a], search_order[b]);
124 }
125 ElemStruct* out = nullptr;
126 for (size_t i = 0; out == nullptr && i < elems.size(); i++) {
127 if (elems[search_order[i]].inserted == inserted) {
128 out = &elems[search_order[i]];
129 }
130 }
131 return out;
132 }
133
134 // TODO(ctiller): this should be an actual fuzzer
TEST(TimerHeapTest,RandomMutations)135 TEST(TimerHeapTest, RandomMutations) {
136 TimerHeap pq;
137
138 static const size_t elems_size = 1000;
139 std::vector<ElemStruct> elems(elems_size);
140 size_t num_inserted = 0;
141
142 for (size_t round = 0; round < 10000; round++) {
143 int r = rand() % 1000;
144 if (r <= 550) {
145 // 55% of the time we try to add something
146 ElemStruct* el = SearchElems(elems, false);
147 if (el != nullptr) {
148 el->elem.deadline = RandomDeadline();
149 pq.Add(&el->elem);
150 el->inserted = true;
151 num_inserted++;
152 CheckValid(&pq);
153 }
154 } else if (r <= 650) {
155 // 10% of the time we try to remove something
156 ElemStruct* el = SearchElems(elems, true);
157 if (el != nullptr) {
158 pq.Remove(&el->elem);
159 el->inserted = false;
160 num_inserted--;
161 CheckValid(&pq);
162 }
163 } else {
164 // the remaining times we pop
165 if (num_inserted > 0) {
166 Timer* top = pq.Top();
167 pq.Pop();
168 for (size_t i = 0; i < elems_size; i++) {
169 if (top == &elems[i].elem) {
170 CHECK(elems[i].inserted);
171 elems[i].inserted = false;
172 }
173 }
174 num_inserted--;
175 CheckValid(&pq);
176 }
177 }
178
179 if (num_inserted) {
180 int64_t* min_deadline = nullptr;
181 for (size_t i = 0; i < elems_size; i++) {
182 if (elems[i].inserted) {
183 if (min_deadline == nullptr) {
184 min_deadline = &elems[i].elem.deadline;
185 } else {
186 if (elems[i].elem.deadline < *min_deadline) {
187 min_deadline = &elems[i].elem.deadline;
188 }
189 }
190 }
191 }
192 CHECK(pq.Top()->deadline == *min_deadline);
193 }
194 }
195 }
196
197 } // namespace
198 } // namespace experimental
199 } // namespace grpc_event_engine
200
main(int argc,char ** argv)201 int main(int argc, char** argv) {
202 ::testing::InitGoogleTest(&argc, argv);
203 return RUN_ALL_TESTS();
204 }
205