CI: Add docker info to job summary
[asterisk/asterisk.git] / tests / test_sorcery_memory_cache_thrash.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2015, Digium, Inc.
5  *
6  * Joshua Colp <jcolp@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*!
20  * \file
21  * \brief Sorcery Unit Tests
22  *
23  * \author Joshua Colp <jcolp@digium.com>
24  *
25  */
26
27 /*** MODULEINFO
28         <depend>TEST_FRAMEWORK</depend>
29         <support_level>core</support_level>
30  ***/
31
32 #include "asterisk.h"
33
34 #include "asterisk/test.h"
35 #include "asterisk/module.h"
36 #include "asterisk/sorcery.h"
37 #include "asterisk/logger.h"
38 #include "asterisk/vector.h"
39 #include "asterisk/cli.h"
40
41 /*! \brief The default amount of time (in seconds) that thrash unit tests execute for */
42 #define TEST_THRASH_TIME 3
43
44 /*! \brief The number of threads to use for retrieving for applicable tests */
45 #define TEST_THRASH_RETRIEVERS 25
46
47 /*! \brief The number of threads to use for updating for applicable tests*/
48 #define TEST_THRASH_UPDATERS 25
49
50 /*! \brief Structure for a memory cache thras thread */
51 struct sorcery_memory_cache_thrash_thread {
52         /*! \brief The thread thrashing the cache */
53         pthread_t thread;
54         /*! \brief Sorcery instance being tested */
55         struct ast_sorcery *sorcery;
56         /*! \brief The number of unique objects we should restrict ourself to */
57         unsigned int unique_objects;
58         /*! \brief Set when the thread should stop */
59         unsigned int stop;
60         /*! \brief Average time spent executing sorcery operation in this thread */
61         unsigned int average_execution_time;
62 };
63
64 /*! \brief Structure for memory cache thrasing */
65 struct sorcery_memory_cache_thrash {
66         /*! \brief The sorcery instance being tested */
67         struct ast_sorcery *sorcery;
68         /*! \brief The number of threads which are updating */
69         unsigned int update_threads;
70         /*! \brief The average execution time of sorcery update operations */
71         unsigned int average_update_execution_time;
72         /*! \brief The number of threads which are retrieving */
73         unsigned int retrieve_threads;
74         /*! \brief The average execution time of sorcery retrieve operations */
75         unsigned int average_retrieve_execution_time;
76         /*! \brief Threads which are updating or reading from the cache */
77         AST_VECTOR(, struct sorcery_memory_cache_thrash_thread *) threads;
78 };
79
80 /*!
81  * \brief Sorcery object created based on backend data
82  */
83 struct test_data {
84         SORCERY_OBJECT(details);
85 };
86
87 /*!
88  * \brief Allocation callback for test_data sorcery object
89  */
90 static void *test_data_alloc(const char *id)
91 {
92         return ast_sorcery_generic_alloc(sizeof(struct test_data), NULL);
93 }
94
95 /*!
96  * \brief Callback for retrieving sorcery object by ID
97  *
98  * \param sorcery The sorcery instance
99  * \param data Unused
100  * \param type The object type. Will always be "test".
101  * \param id The object id. Will always be "test".
102  *
103  * \retval NULL Backend data successfully allocated
104  * \retval non-NULL Backend data could not be successfully allocated
105  */
106 static void *mock_retrieve_id(const struct ast_sorcery *sorcery, void *data,
107                 const char *type, const char *id)
108 {
109         return ast_sorcery_alloc(sorcery, type, id);
110 }
111
112 /*!
113  * \brief Callback for updating a sorcery object
114  *
115  * \param sorcery The sorcery instance
116  * \param data Unused
117  * \param object The object to update.
118  *
119  */
120 static int mock_update(const struct ast_sorcery *sorcery, void *data,
121         void *object)
122 {
123         return 0;
124 }
125
126 /*!
127  * \brief A mock sorcery wizard used for the stale test
128  */
129 static struct ast_sorcery_wizard mock_wizard = {
130         .name = "mock",
131         .retrieve_id = mock_retrieve_id,
132         .update = mock_update,
133 };
134
135 /*!
136  * \internal
137  * \brief Destructor for sorcery memory cache thrasher
138  *
139  * \param obj The sorcery memory cache thrash structure
140  */
141 static void sorcery_memory_cache_thrash_destroy(void *obj)
142 {
143         struct sorcery_memory_cache_thrash *thrash = obj;
144         int idx;
145
146         if (thrash->sorcery) {
147                 ast_sorcery_unref(thrash->sorcery);
148         }
149
150         for (idx = 0; idx < AST_VECTOR_SIZE(&thrash->threads); ++idx) {
151                 struct sorcery_memory_cache_thrash_thread *thread;
152
153                 thread = AST_VECTOR_GET(&thrash->threads, idx);
154                 ast_free(thread);
155         }
156         AST_VECTOR_FREE(&thrash->threads);
157
158         ast_sorcery_wizard_unregister(&mock_wizard);
159 }
160
161 /*!
162  * \internal
163  * \brief Set up thrasing against a memory cache on a sorcery instance
164  *
165  * \param cache_configuration The sorcery memory cache configuration to use
166  * \param update_threads The number of threads which should be constantly updating sorcery
167  * \param retrieve_threads The number of threads which should be constantly retrieving from sorcery
168  * \param unique_objects The number of unique objects that can exist
169  *
170  * \retval non-NULL success
171  * \retval NULL failure
172  */
173 static struct sorcery_memory_cache_thrash *sorcery_memory_cache_thrash_create(const char *cache_configuration,
174         unsigned int update_threads, unsigned int retrieve_threads, unsigned int unique_objects)
175 {
176         struct sorcery_memory_cache_thrash *thrash;
177         struct sorcery_memory_cache_thrash_thread *thread;
178         unsigned int total_threads = update_threads + retrieve_threads;
179
180         thrash = ao2_alloc_options(sizeof(*thrash), sorcery_memory_cache_thrash_destroy,
181                 AO2_ALLOC_OPT_LOCK_NOLOCK);
182         if (!thrash) {
183                 return NULL;
184         }
185
186         thrash->update_threads = update_threads;
187         thrash->retrieve_threads = retrieve_threads;
188
189         ast_sorcery_wizard_register(&mock_wizard);
190
191         thrash->sorcery = ast_sorcery_open();
192         if (!thrash->sorcery) {
193                 ao2_ref(thrash, -1);
194                 return NULL;
195         }
196
197         ast_sorcery_apply_wizard_mapping(thrash->sorcery, "test", "memory_cache",
198                         !strcmp(cache_configuration, "default") ? "" : cache_configuration, 1);
199         ast_sorcery_apply_wizard_mapping(thrash->sorcery, "test", "mock", NULL, 0);
200         ast_sorcery_internal_object_register(thrash->sorcery, "test", test_data_alloc, NULL, NULL);
201
202         if (AST_VECTOR_INIT(&thrash->threads, update_threads + retrieve_threads)) {
203                 ao2_ref(thrash, -1);
204                 return NULL;
205         }
206
207         while (AST_VECTOR_SIZE(&thrash->threads) != total_threads) {
208                 thread = ast_calloc(1, sizeof(*thread));
209
210                 if (!thread) {
211                         ao2_ref(thrash, -1);
212                         return NULL;
213                 }
214
215                 thread->thread = AST_PTHREADT_NULL;
216                 thread->unique_objects = unique_objects;
217
218                 /* This purposely holds no ref as the main thrash structure does */
219                 thread->sorcery = thrash->sorcery;
220
221                 if (AST_VECTOR_APPEND(&thrash->threads, thread)) {
222                         ast_free(thread);
223                         ao2_ref(thrash, -1);
224                         return NULL;
225                 }
226         }
227
228         return thrash;
229 }
230
231 /*!
232  * \internal
233  * \brief Thrashing cache update thread
234  *
235  * \param data The sorcery memory cache thrash thread
236  */
237 static void *sorcery_memory_cache_thrash_update(void *data)
238 {
239         struct sorcery_memory_cache_thrash_thread *thread = data;
240         struct timeval start;
241         unsigned int object_id;
242         char object_id_str[AST_UUID_STR_LEN];
243         void *object;
244
245         while (!thread->stop) {
246                 object_id = ast_random() % thread->unique_objects;
247                 snprintf(object_id_str, sizeof(object_id_str), "%u", object_id);
248
249                 object = ast_sorcery_alloc(thread->sorcery, "test", object_id_str);
250                 ast_assert(object != NULL);
251
252                 start = ast_tvnow();
253                 ast_sorcery_update(thread->sorcery, object);
254                 thread->average_execution_time = (thread->average_execution_time + ast_tvdiff_ms(ast_tvnow(), start)) / 2;
255                 ao2_ref(object, -1);
256         }
257
258         return NULL;
259 }
260
261 /*!
262  * \internal
263  * \brief Thrashing cache retrieve thread
264  *
265  * \param data The sorcery memory cache thrash thread
266  */
267 static void *sorcery_memory_cache_thrash_retrieve(void *data)
268 {
269         struct sorcery_memory_cache_thrash_thread *thread = data;
270         struct timeval start;
271         unsigned int object_id;
272         char object_id_str[AST_UUID_STR_LEN];
273         void *object;
274
275         while (!thread->stop) {
276                 object_id = ast_random() % thread->unique_objects;
277                 snprintf(object_id_str, sizeof(object_id_str), "%u", object_id);
278
279                 start = ast_tvnow();
280                 object = ast_sorcery_retrieve_by_id(thread->sorcery, "test", object_id_str);
281                 thread->average_execution_time = (thread->average_execution_time + ast_tvdiff_ms(ast_tvnow(), start)) / 2;
282                 ast_assert(object != NULL);
283
284                 ao2_ref(object, -1);
285         }
286
287         return NULL;
288 }
289
290 /*!
291  * \internal
292  * \brief Stop thrashing against a sorcery memory cache
293  *
294  * \param thrash The sorcery memory cache thrash structure
295  */
296 static void sorcery_memory_cache_thrash_stop(struct sorcery_memory_cache_thrash *thrash)
297 {
298         int idx;
299
300         for (idx = 0; idx < AST_VECTOR_SIZE(&thrash->threads); ++idx) {
301                 struct sorcery_memory_cache_thrash_thread *thread;
302
303                 thread = AST_VECTOR_GET(&thrash->threads, idx);
304                 if (thread->thread == AST_PTHREADT_NULL) {
305                         continue;
306                 }
307
308                 thread->stop = 1;
309         }
310
311         for (idx = 0; idx < AST_VECTOR_SIZE(&thrash->threads); ++idx) {
312                 struct sorcery_memory_cache_thrash_thread *thread;
313
314                 thread = AST_VECTOR_GET(&thrash->threads, idx);
315                 if (thread->thread == AST_PTHREADT_NULL) {
316                         continue;
317                 }
318
319                 pthread_join(thread->thread, NULL);
320
321                 if (idx < thrash->update_threads) {
322                         thrash->average_update_execution_time += thread->average_execution_time;
323                 } else {
324                         thrash->average_retrieve_execution_time += thread->average_execution_time;
325                 }
326         }
327
328         if (thrash->update_threads) {
329                 thrash->average_update_execution_time /= thrash->update_threads;
330         }
331         if (thrash->retrieve_threads) {
332                 thrash->average_retrieve_execution_time /= thrash->retrieve_threads;
333         }
334 }
335
336 /*!
337  * \internal
338  * \brief Start thrashing against a sorcery memory cache
339  *
340  * \param thrash The sorcery memory cache thrash structure
341  *
342  * \retval 0 success
343  * \retval -1 failure
344  */
345 static int sorcery_memory_cache_thrash_start(struct sorcery_memory_cache_thrash *thrash)
346 {
347         int idx;
348
349         for (idx = 0; idx < AST_VECTOR_SIZE(&thrash->threads); ++idx) {
350                 struct sorcery_memory_cache_thrash_thread *thread;
351
352                 thread = AST_VECTOR_GET(&thrash->threads, idx);
353
354                 if (ast_pthread_create(&thread->thread, NULL, idx < thrash->update_threads ?
355                         sorcery_memory_cache_thrash_update : sorcery_memory_cache_thrash_retrieve, thread)) {
356                         sorcery_memory_cache_thrash_stop(thrash);
357                         return -1;
358                 }
359         }
360
361         return 0;
362 }
363
364 /*!
365  * \internal
366  * \brief CLI command implementation for 'sorcery memory cache thrash'
367  */
368 static char *sorcery_memory_cache_cli_thrash(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
369 {
370         struct sorcery_memory_cache_thrash *thrash;
371         unsigned int thrash_time, unique_objects, retrieve_threads, update_threads;
372
373         switch (cmd) {
374         case CLI_INIT:
375                 e->command = "sorcery memory cache thrash";
376                 e->usage =
377                     "Usage: sorcery memory cache thrash <cache configuration> <amount of time to thrash the cache> <number of unique objects> <number of retrieve threads> <number of update threads>\n"
378                     "       Create a sorcery instance with a memory cache using the provided configuration and thrash it.\n";
379                 return NULL;
380         case CLI_GENERATE:
381                 return NULL;
382         }
383
384         if (a->argc != 9) {
385                 return CLI_SHOWUSAGE;
386         }
387
388         if (sscanf(a->argv[5], "%30u", &thrash_time) != 1) {
389                 ast_cli(a->fd, "An invalid value of '%s' has been provided for the thrashing time\n", a->argv[5]);
390                 return CLI_FAILURE;
391         } else if (sscanf(a->argv[6], "%30u", &unique_objects) != 1) {
392                 ast_cli(a->fd, "An invalid value of '%s' has been provided for number of unique objects\n", a->argv[6]);
393                 return CLI_FAILURE;
394         } else if (sscanf(a->argv[7], "%30u", &retrieve_threads) != 1) {
395                 ast_cli(a->fd, "An invalid value of '%s' has been provided for the number of retrieve threads\n", a->argv[7]);
396                 return CLI_FAILURE;
397         } else if (sscanf(a->argv[8], "%30u", &update_threads) != 1) {
398                 ast_cli(a->fd, "An invalid value of '%s' has been provided for the number of update threads\n", a->argv[8]);
399                 return CLI_FAILURE;
400         }
401
402         thrash = sorcery_memory_cache_thrash_create(a->argv[4], update_threads, retrieve_threads, unique_objects);
403         if (!thrash) {
404                 ast_cli(a->fd, "Could not create a sorcery memory cache thrash test using the provided arguments\n");
405                 return CLI_FAILURE;
406         }
407
408         ast_cli(a->fd, "Starting cache thrash test.\n");
409         ast_cli(a->fd, "Memory cache configuration: %s\n", a->argv[4]);
410         ast_cli(a->fd, "Amount of time to perform test: %u seconds\n", thrash_time);
411         ast_cli(a->fd, "Number of unique objects: %u\n", unique_objects);
412         ast_cli(a->fd, "Number of retrieve threads: %u\n", retrieve_threads);
413         ast_cli(a->fd, "Number of update threads: %u\n", update_threads);
414
415         sorcery_memory_cache_thrash_start(thrash);
416         while ((thrash_time = sleep(thrash_time)));
417         sorcery_memory_cache_thrash_stop(thrash);
418
419         ast_cli(a->fd, "Stopped cache thrash test\n");
420
421         ast_cli(a->fd, "Average retrieve execution time (in milliseconds): %u\n", thrash->average_retrieve_execution_time);
422         ast_cli(a->fd, "Average update execution time (in milliseconds): %u\n", thrash->average_update_execution_time);
423
424         ao2_ref(thrash, -1);
425
426         return CLI_SUCCESS;
427 }
428
429 static struct ast_cli_entry cli_memory_cache_thrash[] = {
430         AST_CLI_DEFINE(sorcery_memory_cache_cli_thrash, "Thrash a sorcery memory cache"),
431 };
432
433 /*!
434  * \internal
435  * \brief Perform a thrash test against a cache
436  *
437  * \param test The unit test being run
438  * \param cache_configuration The underlying cache configuration
439  * \param thrash_time How long (in seconds) to thrash the cache for
440  * \param unique_objects The number of unique objects
441  * \param retrieve_threads The number of threads constantly doing a retrieve
442  * \param update_threads The number of threads constantly doing an update
443  *
444  * \retval AST_TEST_PASS success
445  * \retval AST_TEST_FAIL failure
446  */
447 static enum ast_test_result_state nominal_thrash(struct ast_test *test, const char *cache_configuration,
448         unsigned int thrash_time, unsigned int unique_objects, unsigned int retrieve_threads,
449         unsigned int update_threads)
450 {
451         struct sorcery_memory_cache_thrash *thrash;
452
453         thrash = sorcery_memory_cache_thrash_create(cache_configuration, update_threads, retrieve_threads, unique_objects);
454         if (!thrash) {
455                 return AST_TEST_FAIL;
456         }
457
458         sorcery_memory_cache_thrash_start(thrash);
459         while ((thrash_time = sleep(thrash_time)));
460         sorcery_memory_cache_thrash_stop(thrash);
461
462         ao2_ref(thrash, -1);
463
464         return AST_TEST_PASS;
465 }
466
467 AST_TEST_DEFINE(low_unique_object_count_immediately_stale)
468 {
469         switch (cmd) {
470         case TEST_INIT:
471                 info->name = "low_unique_object_count_immediately_stale";
472                 info->category = "/res/res_sorcery_memory_cache/thrash/";
473                 info->summary = "Thrash a cache with low number of unique objects that are immediately stale";
474                 info->description = "This test creates a cache with objects that are stale\n"
475                         "after 1 second. It also creates 25 threads which are constantly attempting\n"
476                         "to retrieve the objects. This test confirms that the background refreshes\n"
477                         "being done as a result of going stale do not conflict or cause problems with\n"
478                         "the large number of retrieve threads.";
479                 return AST_TEST_NOT_RUN;
480         case TEST_EXECUTE:
481                 break;
482         }
483
484         return nominal_thrash(test, "object_lifetime_stale=1", TEST_THRASH_TIME, 10, TEST_THRASH_RETRIEVERS, 0);
485 }
486
487 AST_TEST_DEFINE(low_unique_object_count_immediately_expire)
488 {
489         switch (cmd) {
490         case TEST_INIT:
491                 info->name = "low_unique_object_count_immediately_expire";
492                 info->category = "/res/res_sorcery_memory_cache/thrash/";
493                 info->summary = "Thrash a cache with low number of unique objects that are immediately expired";
494                 info->description = "This test creates a cache with objects that are expired\n"
495                         "after 1 second. It also creates 25 threads which are constantly attempting\n"
496                         "to retrieve the objects. This test confirms that the expiration process does\n"
497                         "not cause a problem as the retrieve threads execute.";
498                 return AST_TEST_NOT_RUN;
499         case TEST_EXECUTE:
500                 break;
501         }
502
503         return nominal_thrash(test, "object_lifetime_maximum=1", TEST_THRASH_TIME, 10, TEST_THRASH_RETRIEVERS, 0);
504 }
505
506 AST_TEST_DEFINE(low_unique_object_count_high_concurrent_updates)
507 {
508         switch (cmd) {
509         case TEST_INIT:
510                 info->name = "low_unique_object_count_high_concurrent_updates";
511                 info->category = "/res/res_sorcery_memory_cache/thrash/";
512                 info->summary = "Thrash a cache with low number of unique objects that are updated frequently";
513                 info->description = "This test creates a cache with objects that are being constantly\n"
514                         "updated and retrieved at the same time. This will create contention between all\n"
515                         "of the threads as the write lock is held for the updates. This test confirms that\n"
516                         "no problems occur in this situation.";
517                 return AST_TEST_NOT_RUN;
518         case TEST_EXECUTE:
519                 break;
520         }
521
522         return nominal_thrash(test, "default", TEST_THRASH_TIME, 10, TEST_THRASH_RETRIEVERS, TEST_THRASH_UPDATERS);
523 }
524
525 AST_TEST_DEFINE(unique_objects_exceeding_maximum)
526 {
527         switch (cmd) {
528         case TEST_INIT:
529                 info->name = "unique_objects_exceeding_maximum";
530                 info->category = "/res/res_sorcery_memory_cache/thrash/";
531                 info->summary = "Thrash a cache with a fixed maximum object count";
532                 info->description = "This test creates a cache with a maximum number of objects\n"
533                         "allowed in it. The maximum number of unique objects, however, far exceeds the\n"
534                         "the maximum number allowed in the cache. This test confirms that the cache does\n"
535                         "not exceed the maximum and that the removal of older objects does not cause\n"
536                         "a problem.";
537                 return AST_TEST_NOT_RUN;
538         case TEST_EXECUTE:
539                 break;
540         }
541
542         return nominal_thrash(test, "maximum_objects=10", TEST_THRASH_TIME, 100, TEST_THRASH_RETRIEVERS, 0);
543 }
544
545 AST_TEST_DEFINE(unique_objects_exceeding_maximum_with_expire_and_stale)
546 {
547         switch (cmd) {
548         case TEST_INIT:
549                 info->name = "unique_objects_exceeding_maximum_with_expire_and_stale";
550                 info->category = "/res/res_sorcery_memory_cache/thrash/";
551                 info->summary = "Thrash a cache with a fixed maximum object count with objects that expire and go stale";
552                 info->description = "This test creates a cache with a maximum number of objects\n"
553                         "allowed in it with objects that also go stale after a period of time and expire.\n"
554                         "A number of threads are created that constantly retrieve from the cache, causing\n"
555                         "both stale refresh and expiration to occur. This test confirms that the combination\n"
556                         "of these do not present a problem.";
557                 return AST_TEST_NOT_RUN;
558         case TEST_EXECUTE:
559                 break;
560         }
561
562         return nominal_thrash(test, "maximum_objects=10,object_lifetime_maximum=2,object_lifetime_stale=1",
563                 TEST_THRASH_TIME * 2, 100, TEST_THRASH_RETRIEVERS, 0);
564 }
565
566 AST_TEST_DEFINE(conflicting_expire_and_stale)
567 {
568         switch (cmd) {
569         case TEST_INIT:
570                 info->name = "conflicting_expire_and_stale";
571                 info->category = "/res/res_sorcery_memory_cache/thrash/";
572                 info->summary = "Thrash a cache with a large number of objects that expire and go stale";
573                 info->description = "This test creates a cache with a large number of objects that expire\n"
574                         "and go stale. As there is such a large number this ensures that both operations occur.\n"
575                         "This test confirms that stale refreshing and expiration do not conflict.";
576                 return AST_TEST_NOT_RUN;
577         case TEST_EXECUTE:
578                 break;
579         }
580
581         return nominal_thrash(test, "object_lifetime_maximum=2,object_lifetime_stale=1", TEST_THRASH_TIME * 2, 5000,
582                 TEST_THRASH_RETRIEVERS, 0);
583 }
584
585 AST_TEST_DEFINE(high_object_count_without_expiration)
586 {
587         switch (cmd) {
588         case TEST_INIT:
589                 info->name = "high_object_count_without_expiration";
590                 info->category = "/res/res_sorcery_memory_cache/thrash/";
591                 info->summary = "Thrash a cache with a large number of objects";
592                 info->description = "This test creates a cache with a large number of objects that persist.\n"
593                         "A large number of threads are created which constantly retrieve from the cache.\n"
594                         "This test confirms that the large number of retrieves do not cause a problem.";
595                 return AST_TEST_NOT_RUN;
596         case TEST_EXECUTE:
597                 break;
598         }
599
600         return nominal_thrash(test, "default", TEST_THRASH_TIME, 5000, TEST_THRASH_RETRIEVERS, 0);
601 }
602
603 static int unload_module(void)
604 {
605         ast_cli_unregister_multiple(cli_memory_cache_thrash, ARRAY_LEN(cli_memory_cache_thrash));
606         AST_TEST_UNREGISTER(low_unique_object_count_immediately_stale);
607         AST_TEST_UNREGISTER(low_unique_object_count_immediately_expire);
608         AST_TEST_UNREGISTER(low_unique_object_count_high_concurrent_updates);
609         AST_TEST_UNREGISTER(unique_objects_exceeding_maximum);
610         AST_TEST_UNREGISTER(unique_objects_exceeding_maximum_with_expire_and_stale);
611         AST_TEST_UNREGISTER(conflicting_expire_and_stale);
612         AST_TEST_UNREGISTER(high_object_count_without_expiration);
613
614         return 0;
615 }
616
617 static int load_module(void)
618 {
619         ast_cli_register_multiple(cli_memory_cache_thrash, ARRAY_LEN(cli_memory_cache_thrash));
620         AST_TEST_REGISTER(low_unique_object_count_immediately_stale);
621         AST_TEST_REGISTER(low_unique_object_count_immediately_expire);
622         AST_TEST_REGISTER(low_unique_object_count_high_concurrent_updates);
623         AST_TEST_REGISTER(unique_objects_exceeding_maximum);
624         AST_TEST_REGISTER(unique_objects_exceeding_maximum_with_expire_and_stale);
625         AST_TEST_REGISTER(conflicting_expire_and_stale);
626         AST_TEST_REGISTER(high_object_count_without_expiration);
627
628         return AST_MODULE_LOAD_SUCCESS;
629 }
630
631 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Sorcery Cache Thrasing test module");