Merge "lpc10: Avoid compiler warning when DONT_OPTIMIZE/COMPILE_DOUBLE."
[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                 AST_VECTOR_APPEND(&thrash->threads, thread);
222         }
223
224         return thrash;
225 }
226
227 /*!
228  * \internal
229  * \brief Thrashing cache update thread
230  *
231  * \param data The sorcery memory cache thrash thread
232  */
233 static void *sorcery_memory_cache_thrash_update(void *data)
234 {
235         struct sorcery_memory_cache_thrash_thread *thread = data;
236         struct timeval start;
237         unsigned int object_id;
238         char object_id_str[AST_UUID_STR_LEN];
239         void *object;
240
241         while (!thread->stop) {
242                 object_id = ast_random() % thread->unique_objects;
243                 snprintf(object_id_str, sizeof(object_id_str), "%u", object_id);
244
245                 object = ast_sorcery_alloc(thread->sorcery, "test", object_id_str);
246                 ast_assert(object != NULL);
247
248                 start = ast_tvnow();
249                 ast_sorcery_update(thread->sorcery, object);
250                 thread->average_execution_time = (thread->average_execution_time + ast_tvdiff_ms(ast_tvnow(), start)) / 2;
251                 ao2_ref(object, -1);
252         }
253
254         return NULL;
255 }
256
257 /*!
258  * \internal
259  * \brief Thrashing cache retrieve thread
260  *
261  * \param data The sorcery memory cache thrash thread
262  */
263 static void *sorcery_memory_cache_thrash_retrieve(void *data)
264 {
265         struct sorcery_memory_cache_thrash_thread *thread = data;
266         struct timeval start;
267         unsigned int object_id;
268         char object_id_str[AST_UUID_STR_LEN];
269         void *object;
270
271         while (!thread->stop) {
272                 object_id = ast_random() % thread->unique_objects;
273                 snprintf(object_id_str, sizeof(object_id_str), "%u", object_id);
274
275                 start = ast_tvnow();
276                 object = ast_sorcery_retrieve_by_id(thread->sorcery, "test", object_id_str);
277                 thread->average_execution_time = (thread->average_execution_time + ast_tvdiff_ms(ast_tvnow(), start)) / 2;
278                 ast_assert(object != NULL);
279
280                 ao2_ref(object, -1);
281         }
282
283         return NULL;
284 }
285
286 /*!
287  * \internal
288  * \brief Stop thrashing against a sorcery memory cache
289  *
290  * \param thrash The sorcery memory cache thrash structure
291  */
292 static void sorcery_memory_cache_thrash_stop(struct sorcery_memory_cache_thrash *thrash)
293 {
294         int idx;
295
296         for (idx = 0; idx < AST_VECTOR_SIZE(&thrash->threads); ++idx) {
297                 struct sorcery_memory_cache_thrash_thread *thread;
298
299                 thread = AST_VECTOR_GET(&thrash->threads, idx);
300                 if (thread->thread == AST_PTHREADT_NULL) {
301                         continue;
302                 }
303
304                 thread->stop = 1;
305         }
306
307         for (idx = 0; idx < AST_VECTOR_SIZE(&thrash->threads); ++idx) {
308                 struct sorcery_memory_cache_thrash_thread *thread;
309
310                 thread = AST_VECTOR_GET(&thrash->threads, idx);
311                 if (thread->thread == AST_PTHREADT_NULL) {
312                         continue;
313                 }
314
315                 pthread_join(thread->thread, NULL);
316
317                 if (idx < thrash->update_threads) {
318                         thrash->average_update_execution_time += thread->average_execution_time;
319                 } else {
320                         thrash->average_retrieve_execution_time += thread->average_execution_time;
321                 }
322         }
323
324         if (thrash->update_threads) {
325                 thrash->average_update_execution_time /= thrash->update_threads;
326         }
327         if (thrash->retrieve_threads) {
328                 thrash->average_retrieve_execution_time /= thrash->retrieve_threads;
329         }
330 }
331
332 /*!
333  * \internal
334  * \brief Start thrashing against a sorcery memory cache
335  *
336  * \param thrash The sorcery memory cache thrash structure
337  *
338  * \retval 0 success
339  * \retval -1 failure
340  */
341 static int sorcery_memory_cache_thrash_start(struct sorcery_memory_cache_thrash *thrash)
342 {
343         int idx;
344
345         for (idx = 0; idx < AST_VECTOR_SIZE(&thrash->threads); ++idx) {
346                 struct sorcery_memory_cache_thrash_thread *thread;
347
348                 thread = AST_VECTOR_GET(&thrash->threads, idx);
349
350                 if (ast_pthread_create(&thread->thread, NULL, idx < thrash->update_threads ?
351                         sorcery_memory_cache_thrash_update : sorcery_memory_cache_thrash_retrieve, thread)) {
352                         sorcery_memory_cache_thrash_stop(thrash);
353                         return -1;
354                 }
355         }
356
357         return 0;
358 }
359
360 /*!
361  * \internal
362  * \brief CLI command implementation for 'sorcery memory cache thrash'
363  */
364 static char *sorcery_memory_cache_cli_thrash(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
365 {
366         struct sorcery_memory_cache_thrash *thrash;
367         unsigned int thrash_time, unique_objects, retrieve_threads, update_threads;
368
369         switch (cmd) {
370         case CLI_INIT:
371                 e->command = "sorcery memory cache thrash";
372                 e->usage =
373                     "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"
374                     "       Create a sorcery instance with a memory cache using the provided configuration and thrash it.\n";
375                 return NULL;
376         case CLI_GENERATE:
377                 return NULL;
378         }
379
380         if (a->argc != 9) {
381                 return CLI_SHOWUSAGE;
382         }
383
384         if (sscanf(a->argv[5], "%30u", &thrash_time) != 1) {
385                 ast_cli(a->fd, "An invalid value of '%s' has been provided for the thrashing time\n", a->argv[5]);
386                 return CLI_FAILURE;
387         } else if (sscanf(a->argv[6], "%30u", &unique_objects) != 1) {
388                 ast_cli(a->fd, "An invalid value of '%s' has been provided for number of unique objects\n", a->argv[6]);
389                 return CLI_FAILURE;
390         } else if (sscanf(a->argv[7], "%30u", &retrieve_threads) != 1) {
391                 ast_cli(a->fd, "An invalid value of '%s' has been provided for the number of retrieve threads\n", a->argv[7]);
392                 return CLI_FAILURE;
393         } else if (sscanf(a->argv[8], "%30u", &update_threads) != 1) {
394                 ast_cli(a->fd, "An invalid value of '%s' has been provided for the number of update threads\n", a->argv[8]);
395                 return CLI_FAILURE;
396         }
397
398         thrash = sorcery_memory_cache_thrash_create(a->argv[4], update_threads, retrieve_threads, unique_objects);
399         if (!thrash) {
400                 ast_cli(a->fd, "Could not create a sorcery memory cache thrash test using the provided arguments\n");
401                 return CLI_FAILURE;
402         }
403
404         ast_cli(a->fd, "Starting cache thrash test.\n");
405         ast_cli(a->fd, "Memory cache configuration: %s\n", a->argv[4]);
406         ast_cli(a->fd, "Amount of time to perform test: %u seconds\n", thrash_time);
407         ast_cli(a->fd, "Number of unique objects: %u\n", unique_objects);
408         ast_cli(a->fd, "Number of retrieve threads: %u\n", retrieve_threads);
409         ast_cli(a->fd, "Number of update threads: %u\n", update_threads);
410
411         sorcery_memory_cache_thrash_start(thrash);
412         while ((thrash_time = sleep(thrash_time)));
413         sorcery_memory_cache_thrash_stop(thrash);
414
415         ast_cli(a->fd, "Stopped cache thrash test\n");
416
417         ast_cli(a->fd, "Average retrieve execution time (in milliseconds): %u\n", thrash->average_retrieve_execution_time);
418         ast_cli(a->fd, "Average update execution time (in milliseconds): %u\n", thrash->average_update_execution_time);
419
420         ao2_ref(thrash, -1);
421
422         return CLI_SUCCESS;
423 }
424
425 static struct ast_cli_entry cli_memory_cache_thrash[] = {
426         AST_CLI_DEFINE(sorcery_memory_cache_cli_thrash, "Thrash a sorcery memory cache"),
427 };
428
429 /*!
430  * \internal
431  * \brief Perform a thrash test against a cache
432  *
433  * \param test The unit test being run
434  * \param cache_configuration The underlying cache configuration
435  * \param thrash_time How long (in seconds) to thrash the cache for
436  * \param unique_objects The number of unique objects
437  * \param retrieve_threads The number of threads constantly doing a retrieve
438  * \param update_threads The number of threads constantly doing an update
439  *
440  * \retval AST_TEST_PASS success
441  * \retval AST_TEST_FAIL failure
442  */
443 static enum ast_test_result_state nominal_thrash(struct ast_test *test, const char *cache_configuration,
444         unsigned int thrash_time, unsigned int unique_objects, unsigned int retrieve_threads,
445         unsigned int update_threads)
446 {
447         struct sorcery_memory_cache_thrash *thrash;
448
449         thrash = sorcery_memory_cache_thrash_create(cache_configuration, update_threads, retrieve_threads, unique_objects);
450         if (!thrash) {
451                 return AST_TEST_FAIL;
452         }
453
454         sorcery_memory_cache_thrash_start(thrash);
455         while ((thrash_time = sleep(thrash_time)));
456         sorcery_memory_cache_thrash_stop(thrash);
457
458         ao2_ref(thrash, -1);
459
460         return AST_TEST_PASS;
461 }
462
463 AST_TEST_DEFINE(low_unique_object_count_immediately_stale)
464 {
465         switch (cmd) {
466         case TEST_INIT:
467                 info->name = "low_unique_object_count_immediately_stale";
468                 info->category = "/res/res_sorcery_memory_cache/thrash/";
469                 info->summary = "Thrash a cache with low number of unique objects that are immediately stale";
470                 info->description = "This test creates a cache with objects that are stale\n"
471                         "after 1 second. It also creates 25 threads which are constantly attempting\n"
472                         "to retrieve the objects. This test confirms that the background refreshes\n"
473                         "being done as a result of going stale do not conflict or cause problems with\n"
474                         "the large number of retrieve threads.";
475                 return AST_TEST_NOT_RUN;
476         case TEST_EXECUTE:
477                 break;
478         }
479
480         return nominal_thrash(test, "object_lifetime_stale=1", TEST_THRASH_TIME, 10, TEST_THRASH_RETRIEVERS, 0);
481 }
482
483 AST_TEST_DEFINE(low_unique_object_count_immediately_expire)
484 {
485         switch (cmd) {
486         case TEST_INIT:
487                 info->name = "low_unique_object_count_immediately_expire";
488                 info->category = "/res/res_sorcery_memory_cache/thrash/";
489                 info->summary = "Thrash a cache with low number of unique objects that are immediately expired";
490                 info->description = "This test creates a cache with objects that are expired\n"
491                         "after 1 second. It also creates 25 threads which are constantly attempting\n"
492                         "to retrieve the objects. This test confirms that the expiration process does\n"
493                         "not cause a problem as the retrieve threads execute.";
494                 return AST_TEST_NOT_RUN;
495         case TEST_EXECUTE:
496                 break;
497         }
498
499         return nominal_thrash(test, "object_lifetime_maximum=1", TEST_THRASH_TIME, 10, TEST_THRASH_RETRIEVERS, 0);
500 }
501
502 AST_TEST_DEFINE(low_unique_object_count_high_concurrent_updates)
503 {
504         switch (cmd) {
505         case TEST_INIT:
506                 info->name = "low_unique_object_count_high_concurrent_updates";
507                 info->category = "/res/res_sorcery_memory_cache/thrash/";
508                 info->summary = "Thrash a cache with low number of unique objects that are updated frequently";
509                 info->description = "This test creates a cache with objects that are being constantly\n"
510                         "updated and retrieved at the same time. This will create contention between all\n"
511                         "of the threads as the write lock is held for the updates. This test confirms that\n"
512                         "no problems occur in this situation.";
513                 return AST_TEST_NOT_RUN;
514         case TEST_EXECUTE:
515                 break;
516         }
517
518         return nominal_thrash(test, "default", TEST_THRASH_TIME, 10, TEST_THRASH_RETRIEVERS, TEST_THRASH_UPDATERS);
519 }
520
521 AST_TEST_DEFINE(unique_objects_exceeding_maximum)
522 {
523         switch (cmd) {
524         case TEST_INIT:
525                 info->name = "unique_objects_exceeding_maximum";
526                 info->category = "/res/res_sorcery_memory_cache/thrash/";
527                 info->summary = "Thrash a cache with a fixed maximum object count";
528                 info->description = "This test creates a cache with a maximum number of objects\n"
529                         "allowed in it. The maximum number of unique objects, however, far exceeds the\n"
530                         "the maximum number allowed in the cache. This test confirms that the cache does\n"
531                         "not exceed the maximum and that the removal of older objects does not cause\n"
532                         "a problem.";
533                 return AST_TEST_NOT_RUN;
534         case TEST_EXECUTE:
535                 break;
536         }
537
538         return nominal_thrash(test, "maximum_objects=10", TEST_THRASH_TIME, 100, TEST_THRASH_RETRIEVERS, 0);
539 }
540
541 AST_TEST_DEFINE(unique_objects_exceeding_maximum_with_expire_and_stale)
542 {
543         switch (cmd) {
544         case TEST_INIT:
545                 info->name = "unique_objects_exceeding_maximum_with_expire_and_stale";
546                 info->category = "/res/res_sorcery_memory_cache/thrash/";
547                 info->summary = "Thrash a cache with a fixed maximum object count with objects that expire and go stale";
548                 info->description = "This test creates a cache with a maximum number of objects\n"
549                         "allowed in it with objects that also go stale after a period of time and expire.\n"
550                         "A number of threads are created that constantly retrieve from the cache, causing\n"
551                         "both stale refresh and expiration to occur. This test confirms that the combination\n"
552                         "of these do not present a problem.";
553                 return AST_TEST_NOT_RUN;
554         case TEST_EXECUTE:
555                 break;
556         }
557
558         return nominal_thrash(test, "maximum_objects=10,object_lifetime_maximum=2,object_lifetime_stale=1",
559                 TEST_THRASH_TIME * 2, 100, TEST_THRASH_RETRIEVERS, 0);
560 }
561
562 AST_TEST_DEFINE(conflicting_expire_and_stale)
563 {
564         switch (cmd) {
565         case TEST_INIT:
566                 info->name = "conflicting_expire_and_stale";
567                 info->category = "/res/res_sorcery_memory_cache/thrash/";
568                 info->summary = "Thrash a cache with a large number of objects that expire and go stale";
569                 info->description = "This test creates a cache with a large number of objects that expire\n"
570                         "and go stale. As there is such a large number this ensures that both operations occur.\n"
571                         "This test confirms that stale refreshing and expiration do not conflict.";
572                 return AST_TEST_NOT_RUN;
573         case TEST_EXECUTE:
574                 break;
575         }
576
577         return nominal_thrash(test, "object_lifetime_maximum=2,object_lifetime_stale=1", TEST_THRASH_TIME * 2, 5000,
578                 TEST_THRASH_RETRIEVERS, 0);
579 }
580
581 AST_TEST_DEFINE(high_object_count_without_expiration)
582 {
583         switch (cmd) {
584         case TEST_INIT:
585                 info->name = "high_object_count_without_expiration";
586                 info->category = "/res/res_sorcery_memory_cache/thrash/";
587                 info->summary = "Thrash a cache with a large number of objects";
588                 info->description = "This test creates a cache with a large number of objects that persist.\n"
589                         "A large number of threads are created which constantly retrieve from the cache.\n"
590                         "This test confirms that the large number of retrieves do not cause a problem.";
591                 return AST_TEST_NOT_RUN;
592         case TEST_EXECUTE:
593                 break;
594         }
595
596         return nominal_thrash(test, "default", TEST_THRASH_TIME, 5000, TEST_THRASH_RETRIEVERS, 0);
597 }
598
599 static int unload_module(void)
600 {
601         ast_cli_unregister_multiple(cli_memory_cache_thrash, ARRAY_LEN(cli_memory_cache_thrash));
602         AST_TEST_UNREGISTER(low_unique_object_count_immediately_stale);
603         AST_TEST_UNREGISTER(low_unique_object_count_immediately_expire);
604         AST_TEST_UNREGISTER(low_unique_object_count_high_concurrent_updates);
605         AST_TEST_UNREGISTER(unique_objects_exceeding_maximum);
606         AST_TEST_UNREGISTER(unique_objects_exceeding_maximum_with_expire_and_stale);
607         AST_TEST_UNREGISTER(conflicting_expire_and_stale);
608         AST_TEST_UNREGISTER(high_object_count_without_expiration);
609
610         return 0;
611 }
612
613 static int load_module(void)
614 {
615         ast_cli_register_multiple(cli_memory_cache_thrash, ARRAY_LEN(cli_memory_cache_thrash));
616         AST_TEST_REGISTER(low_unique_object_count_immediately_stale);
617         AST_TEST_REGISTER(low_unique_object_count_immediately_expire);
618         AST_TEST_REGISTER(low_unique_object_count_high_concurrent_updates);
619         AST_TEST_REGISTER(unique_objects_exceeding_maximum);
620         AST_TEST_REGISTER(unique_objects_exceeding_maximum_with_expire_and_stale);
621         AST_TEST_REGISTER(conflicting_expire_and_stale);
622         AST_TEST_REGISTER(high_object_count_without_expiration);
623
624         return AST_MODULE_LOAD_SUCCESS;
625 }
626
627 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Sorcery Cache Thrasing test module");