summaryrefslogtreecommitdiff
path: root/testing
diff options
context:
space:
mode:
authoriphydf <iphydf@users.noreply.github.com>2018-06-19 00:41:30 +0000
committeriphydf <iphydf@users.noreply.github.com>2018-06-23 12:43:09 +0000
commitcfff361679608bdfa49cd55a8062e86797587571 (patch)
tree5beb81f45076f32ad3f32c8e088403e178cc4bdf /testing
parenta48e0c4d1892eebf356108504c0b7fe6c0175b5e (diff)
Add random testing program.
This can be used as a random stress test for toxcore. Adjust the weights to make certain actions more or less likely.
Diffstat (limited to 'testing')
-rw-r--r--testing/BUILD.bazel9
-rw-r--r--testing/random_testing.cc396
2 files changed, 405 insertions, 0 deletions
diff --git a/testing/BUILD.bazel b/testing/BUILD.bazel
index 6cc7018e..075f00d1 100644
--- a/testing/BUILD.bazel
+++ b/testing/BUILD.bazel
@@ -40,3 +40,12 @@ cc_binary(
40 "@sndfile", 40 "@sndfile",
41 ], 41 ],
42) 42)
43
44cc_binary(
45 name = "random_testing",
46 srcs = ["random_testing.cc"],
47 deps = [
48 ":misc_tools",
49 "//c-toxcore/toxcore",
50 ],
51)
diff --git a/testing/random_testing.cc b/testing/random_testing.cc
new file mode 100644
index 00000000..3e5be731
--- /dev/null
+++ b/testing/random_testing.cc
@@ -0,0 +1,396 @@
1// Program to perform random actions in a network of toxes.
2//
3// Useful to find reproducing test cases for seemingly random bugs.
4
5#include <algorithm>
6#include <cassert>
7#include <cstdlib>
8#include <memory>
9#include <random>
10#include <vector>
11
12#include "../toxcore/tox.h"
13#include "misc_tools.c"
14
15namespace {
16
17// Whether to write log messages when handling callbacks.
18constexpr bool LOG_CALLBACKS = 0;
19
20// Number of participants in the test run.
21constexpr uint32_t NUM_TOXES = 10;
22// Maximum amount of time in iterations to wait for bootstrapping and friend
23// connections to succeed.
24constexpr uint32_t MAX_BOOTSTRAP_ITERATIONS = 1000;
25// Number of conferences up to which users may create their own new conferences.
26// They may still be invited and join.
27constexpr uint32_t MAX_CONFERENCES_PER_USER = 3;
28// Maximum number of actions per program execution.
29constexpr uint32_t MAX_ACTIONS = 10000;
30// Maximum number of attempts at executing a random action.
31constexpr uint32_t MAX_ACTION_ATTEMPTS = 100;
32// Number of tox_iterate calls between each action.
33constexpr uint32_t ITERATIONS_PER_ACTION = 1;
34// Amount of time in milliseconds to wait between tox_iterate calls.
35constexpr uint32_t ITERATION_INTERVAL = 5;
36
37struct Tox_Options_Deleter {
38 void operator()(Tox_Options *options) const { tox_options_free(options); }
39};
40
41using Tox_Options_Ptr = std::unique_ptr<Tox_Options, Tox_Options_Deleter>;
42
43struct Tox_Deleter {
44 void operator()(Tox *tox) const { tox_kill(tox); }
45};
46
47using Tox_Ptr = std::unique_ptr<Tox, Tox_Deleter>;
48
49struct Local_State {
50 Tox_Ptr tox;
51
52 uint32_t friends_online = 0;
53 uint32_t next_invite = 0;
54
55 explicit Local_State(Tox_Ptr tox, uint32_t id) : tox(std::move(tox)), id_(id) {}
56
57 uint32_t id() const { return id_; }
58
59 private:
60 uint32_t id_;
61};
62
63struct Random {
64 std::uniform_int_distribution<> tox_selector;
65 std::uniform_int_distribution<> friend_selector;
66 std::uniform_int_distribution<> name_length_selector;
67 std::uniform_int_distribution<> message_length_selector;
68 std::uniform_int_distribution<> byte_selector;
69
70 std::vector<size_t> action_weights;
71 std::discrete_distribution<size_t> action_selector;
72
73 Random();
74};
75
76struct Action {
77 uint32_t weight;
78 char const *title;
79 bool (*can)(Local_State const &state);
80 void (*run)(Local_State &state, Random &rnd, std::mt19937 &rng);
81};
82
83Action const actions[] = {
84 {
85 10,
86 "creates a new conference",
87 [](Local_State const &state) {
88 return tox_conference_get_chatlist_size(state.tox.get()) < MAX_CONFERENCES_PER_USER;
89 },
90 [](Local_State &state, Random &rnd, std::mt19937 &rng) {
91 TOX_ERR_CONFERENCE_NEW err;
92 tox_conference_new(state.tox.get(), &err);
93 assert(err == TOX_ERR_CONFERENCE_NEW_OK);
94 },
95 },
96 {
97 10,
98 "invites a random friend to a conference",
99 [](Local_State const &state) {
100 return tox_conference_get_chatlist_size(state.tox.get()) != 0;
101 },
102 [](Local_State &state, Random &rnd, std::mt19937 &rng) {
103 TOX_ERR_CONFERENCE_INVITE err;
104 tox_conference_invite(
105 state.tox.get(), rnd.friend_selector(rng),
106 state.next_invite % tox_conference_get_chatlist_size(state.tox.get()), &err);
107 state.next_invite++;
108 assert(err == TOX_ERR_CONFERENCE_INVITE_OK);
109 },
110 },
111 {
112 10,
113 "deletes the last conference",
114 [](Local_State const &state) {
115 return tox_conference_get_chatlist_size(state.tox.get()) != 0;
116 },
117 [](Local_State &state, Random &rnd, std::mt19937 &rng) {
118 TOX_ERR_CONFERENCE_DELETE err;
119 tox_conference_delete(state.tox.get(),
120 tox_conference_get_chatlist_size(state.tox.get()) - 1, &err);
121 assert(err == TOX_ERR_CONFERENCE_DELETE_OK);
122 },
123 },
124 {
125 10,
126 "sends a message to the last conference",
127 [](Local_State const &state) {
128 return tox_conference_get_chatlist_size(state.tox.get()) != 0;
129 },
130 [](Local_State &state, Random &rnd, std::mt19937 &rng) {
131 std::vector<uint8_t> message(rnd.message_length_selector(rng));
132 for (uint8_t &byte : message) {
133 byte = rnd.byte_selector(rng);
134 }
135
136 TOX_ERR_CONFERENCE_SEND_MESSAGE err;
137 tox_conference_send_message(
138 state.tox.get(), tox_conference_get_chatlist_size(state.tox.get()) - 1,
139 TOX_MESSAGE_TYPE_NORMAL, message.data(), message.size(), &err);
140 if (err == TOX_ERR_CONFERENCE_SEND_MESSAGE_OK) {
141 printf(" (OK, length = %u)", (unsigned)message.size());
142 } else {
143 printf(" (FAILED: %u)", err);
144 }
145 },
146 },
147 {
148 10,
149 "changes their name",
150 [](Local_State const &state) { return true; },
151 [](Local_State &state, Random &rnd, std::mt19937 &rng) {
152 std::vector<uint8_t> name(rnd.name_length_selector(rng));
153 for (uint8_t &byte : name) {
154 byte = rnd.byte_selector(rng);
155 }
156
157 TOX_ERR_SET_INFO err;
158 tox_self_set_name(state.tox.get(), name.data(), name.size(), &err);
159 assert(err == TOX_ERR_SET_INFO_OK);
160
161 printf(" (length = %u)", (unsigned)name.size());
162 },
163 },
164 {
165 10,
166 "sets their name to empty",
167 [](Local_State const &state) { return true; },
168 [](Local_State &state, Random &rnd, std::mt19937 &rng) {
169 TOX_ERR_SET_INFO err;
170 tox_self_set_name(state.tox.get(), nullptr, 0, &err);
171 assert(err == TOX_ERR_SET_INFO_OK);
172 },
173 },
174};
175
176std::vector<size_t> get_action_weights() {
177 std::vector<size_t> weights;
178 for (Action const &action : actions) {
179 weights.push_back(action.weight);
180 }
181 return weights;
182}
183
184Random::Random()
185 : tox_selector(0, NUM_TOXES - 1),
186 friend_selector(0, NUM_TOXES - 2),
187 name_length_selector(0, TOX_MAX_NAME_LENGTH - 1),
188 message_length_selector(0, TOX_MAX_MESSAGE_LENGTH - 1),
189 byte_selector(0, 255),
190 action_weights(get_action_weights()),
191 action_selector(action_weights.begin(), action_weights.end()) {}
192
193struct Global_State : std::vector<Local_State> {
194 // Non-copyable;
195 Global_State(Global_State const &) = delete;
196 Global_State(Global_State &&) = default;
197 Global_State() : action_counter(rnd.action_weights.size()) {}
198
199 Random rnd;
200 std::vector<unsigned> action_counter;
201};
202
203void handle_friend_connection_status(Tox *tox, uint32_t friend_number,
204 TOX_CONNECTION connection_status, void *user_data) {
205 Local_State *state = static_cast<Local_State *>(user_data);
206
207 if (connection_status == TOX_CONNECTION_NONE) {
208 std::printf("Tox #%u lost friend %u!\n", state->id(), friend_number);
209 state->friends_online--;
210 } else {
211 state->friends_online++;
212 }
213}
214
215void handle_conference_invite(Tox *tox, uint32_t friend_number, TOX_CONFERENCE_TYPE type,
216 const uint8_t *cookie, size_t length, void *user_data) {
217 Local_State *state = static_cast<Local_State *>(user_data);
218
219 if (LOG_CALLBACKS) {
220 std::printf("Tox #%u joins the conference it was invited to\n", state->id());
221 }
222
223 TOX_ERR_CONFERENCE_JOIN err;
224 tox_conference_join(tox, friend_number, cookie, length, &err);
225 assert(err == TOX_ERR_CONFERENCE_JOIN_OK);
226}
227
228void handle_conference_message(Tox *tox, uint32_t conference_number, uint32_t peer_number,
229 TOX_MESSAGE_TYPE type, const uint8_t *message, size_t length,
230 void *user_data) {
231 Local_State *state = static_cast<Local_State *>(user_data);
232
233 if (LOG_CALLBACKS) {
234 std::printf("Tox #%u received a message of length %u\n", state->id(), (unsigned)length);
235 }
236}
237
238void handle_conference_peer_list_changed(Tox *tox, uint32_t conference_number, void *user_data) {
239 Local_State *state = static_cast<Local_State *>(user_data);
240
241 if (LOG_CALLBACKS) {
242 std::printf("Tox #%u rebuilds peer list for conference %u\n", state->id(), conference_number);
243 }
244
245 TOX_ERR_CONFERENCE_PEER_QUERY err;
246 uint32_t const count = tox_conference_peer_count(tox, conference_number, &err);
247 assert(err == TOX_ERR_CONFERENCE_PEER_QUERY_OK);
248
249 for (uint32_t peer_number = 0; peer_number < count; peer_number++) {
250 size_t size = tox_conference_peer_get_name_size(tox, conference_number, peer_number, &err);
251 assert(err == TOX_ERR_CONFERENCE_PEER_QUERY_OK);
252
253 std::vector<uint8_t> name(size);
254 tox_conference_peer_get_name(tox, conference_number, peer_number, &name[0], &err);
255 assert(err == TOX_ERR_CONFERENCE_PEER_QUERY_OK);
256 }
257}
258
259Global_State make_toxes() {
260 Global_State toxes;
261
262 Tox_Options_Ptr options(tox_options_new(nullptr));
263 tox_options_set_local_discovery_enabled(options.get(), false);
264
265 for (uint32_t i = 0; i < NUM_TOXES; i++) {
266 TOX_ERR_NEW err;
267 toxes.emplace_back(Tox_Ptr(tox_new(options.get(), &err)), i);
268 assert(err == TOX_ERR_NEW_OK);
269 assert(toxes.back().tox != nullptr);
270
271 tox_callback_friend_connection_status(toxes.back().tox.get(), handle_friend_connection_status);
272 tox_callback_conference_invite(toxes.back().tox.get(), handle_conference_invite);
273 tox_callback_conference_message(toxes.back().tox.get(), handle_conference_message);
274 tox_callback_conference_peer_list_changed(toxes.back().tox.get(),
275 handle_conference_peer_list_changed);
276 }
277
278 std::printf("Bootstrapping %u toxes\n", NUM_TOXES);
279
280 uint8_t dht_key[TOX_PUBLIC_KEY_SIZE];
281 tox_self_get_dht_id(toxes.front().tox.get(), dht_key);
282 const uint16_t dht_port = tox_self_get_udp_port(toxes.front().tox.get(), nullptr);
283
284 for (Local_State const &state : toxes) {
285 TOX_ERR_BOOTSTRAP err;
286 tox_bootstrap(state.tox.get(), "localhost", dht_port, dht_key, &err);
287 assert(err == TOX_ERR_BOOTSTRAP_OK);
288 }
289
290 std::printf("Creating full mesh of friendships\n");
291
292 for (Local_State const &state1 : toxes) {
293 for (Local_State const &state2 : toxes) {
294 if (state1.tox != state2.tox) {
295 TOX_ERR_FRIEND_ADD err;
296 uint8_t key[TOX_PUBLIC_KEY_SIZE];
297
298 tox_self_get_public_key(state1.tox.get(), key);
299 tox_friend_add_norequest(state2.tox.get(), key, &err);
300 assert(err == TOX_ERR_FRIEND_ADD_OK);
301 }
302 }
303 }
304
305 return toxes;
306}
307
308bool all_connected(Global_State const &toxes) {
309 return std::all_of(toxes.begin(), toxes.end(), [](Local_State const &state) {
310 return state.friends_online == NUM_TOXES - 1;
311 });
312}
313
314bool bootstrap_toxes(Global_State &toxes) {
315 std::printf("Waiting for %u iterations for all friends to come online\n",
316 MAX_BOOTSTRAP_ITERATIONS);
317
318 for (uint32_t i = 0; i < MAX_BOOTSTRAP_ITERATIONS; i++) {
319 c_sleep(tox_iteration_interval(toxes.front().tox.get()));
320
321 for (Local_State &state : toxes) {
322 tox_iterate(state.tox.get(), &state);
323 }
324
325 if (all_connected(toxes)) {
326 std::printf("Took %u iterations\n", i);
327 return true;
328 }
329 }
330
331 return false;
332}
333
334bool execute_random_action(Global_State &toxes, std::mt19937 &rng) {
335 // First, choose a random actor.
336 Local_State &actor = toxes.at(toxes.rnd.tox_selector(rng));
337 size_t const action_id = toxes.rnd.action_selector(rng);
338 Action const &action = actions[action_id];
339 if (!action.can(actor)) {
340 return false;
341 }
342
343 std::printf("Tox #%u %s", actor.id(), action.title);
344 action.run(actor, toxes.rnd, rng);
345 std::printf("\n");
346
347 toxes.action_counter.at(action_id)++;
348
349 return true;
350}
351
352bool attempt_action(Global_State &toxes, std::mt19937 &rng) {
353 for (uint32_t i = 0; i < MAX_ACTION_ATTEMPTS; i++) {
354 if (execute_random_action(toxes, rng)) {
355 return true;
356 }
357 }
358
359 return false;
360}
361
362} // namespace
363
364int main() {
365 Global_State toxes = make_toxes();
366
367 std::mt19937 rng;
368 uint32_t action_number;
369 for (action_number = 0; action_number < MAX_ACTIONS; action_number++) {
370 if (!all_connected(toxes) && !bootstrap_toxes(toxes)) {
371 std::printf("Bootstrapping took too long; %u actions performed\n", action_number);
372 return EXIT_FAILURE;
373 }
374
375 if (!attempt_action(toxes, rng)) {
376 std::printf(
377 "System is stuck after %u actions: none of the toxes can perform an action anymore\n",
378 action_number);
379 return EXIT_FAILURE;
380 }
381
382 for (uint32_t i = 0; i < ITERATIONS_PER_ACTION; i++) {
383 c_sleep(ITERATION_INTERVAL);
384
385 for (Local_State &state : toxes) {
386 tox_iterate(state.tox.get(), &state);
387 }
388 }
389 }
390
391 std::printf("Test execution success: %u actions performed\n", action_number);
392 std::printf("Per-action statistics:\n");
393 for (uint32_t i = 0; i < toxes.action_counter.size(); i++) {
394 std::printf("%u x '%s'\n", toxes.action_counter.at(i), actions[i].title);
395 }
396}