Merge "channel: Add ability to request an outgoing channel with stream topology."
[asterisk/asterisk.git] / tests / test_taskprocessor.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2012-2013, Digium, Inc.
5  *
6  * Mark Michelson <mmichelson@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 taskprocessor unit tests
22  *
23  * \author Mark Michelson <mmichelson@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/taskprocessor.h"
36 #include "asterisk/module.h"
37 #include "asterisk/astobj2.h"
38
39 /*!
40  * \brief userdata associated with baseline taskprocessor test
41  */
42 struct task_data {
43         /* Condition used to signal to queuing thread that task was executed */
44         ast_cond_t cond;
45         /* Lock protecting the condition */
46         ast_mutex_t lock;
47         /*! Boolean indicating that the task was run */
48         int task_complete;
49 };
50
51 static void task_data_dtor(void *obj)
52 {
53         struct task_data *task_data = obj;
54
55         ast_mutex_destroy(&task_data->lock);
56         ast_cond_destroy(&task_data->cond);
57 }
58
59 /*! \brief Create a task_data object */
60 static struct task_data *task_data_create(void)
61 {
62         struct task_data *task_data =
63                 ao2_alloc(sizeof(*task_data), task_data_dtor);
64
65         if (!task_data) {
66                 return NULL;
67         }
68
69         ast_cond_init(&task_data->cond, NULL);
70         ast_mutex_init(&task_data->lock);
71         task_data->task_complete = 0;
72
73         return task_data;
74 }
75
76 /*!
77  * \brief Queued task for baseline test.
78  *
79  * The task simply sets a boolean to indicate the
80  * task has been run and then signals a condition
81  * saying it's complete
82  */
83 static int task(void *data)
84 {
85         struct task_data *task_data = data;
86         SCOPED_MUTEX(lock, &task_data->lock);
87         task_data->task_complete = 1;
88         ast_cond_signal(&task_data->cond);
89         return 0;
90 }
91
92 /*!
93  * \brief Wait for a task to execute.
94  */
95 static int task_wait(struct task_data *task_data)
96 {
97         struct timeval start = ast_tvnow();
98         struct timespec end;
99         SCOPED_MUTEX(lock, &task_data->lock);
100
101         end.tv_sec = start.tv_sec + 30;
102         end.tv_nsec = start.tv_usec * 1000;
103
104         while (!task_data->task_complete) {
105                 int res;
106                 res = ast_cond_timedwait(&task_data->cond, &task_data->lock,
107                         &end);
108                 if (res == ETIMEDOUT) {
109                         return -1;
110                 }
111         }
112
113         return 0;
114 }
115
116 /*!
117  * \brief Baseline test for default taskprocessor
118  *
119  * This test ensures that when a task is added to a taskprocessor that
120  * has been allocated with a default listener that the task gets executed
121  * as expected
122  */
123 AST_TEST_DEFINE(default_taskprocessor)
124 {
125         RAII_VAR(struct ast_taskprocessor *, tps, NULL, ast_taskprocessor_unreference);
126         RAII_VAR(struct task_data *, task_data, NULL, ao2_cleanup);
127         int res;
128
129         switch (cmd) {
130         case TEST_INIT:
131                 info->name = "default_taskprocessor";
132                 info->category = "/main/taskprocessor/";
133                 info->summary = "Test of default taskproccesor";
134                 info->description =
135                         "Ensures that a queued task gets executed.";
136                 return AST_TEST_NOT_RUN;
137         case TEST_EXECUTE:
138                 break;
139         }
140
141         tps = ast_taskprocessor_get("test", TPS_REF_DEFAULT);
142
143         if (!tps) {
144                 ast_test_status_update(test, "Unable to create test taskprocessor\n");
145                 return AST_TEST_FAIL;
146         }
147
148         task_data = task_data_create();
149         if (!task_data) {
150                 ast_test_status_update(test, "Unable to create task_data\n");
151                 return AST_TEST_FAIL;
152         }
153
154         ast_taskprocessor_push(tps, task, task_data);
155
156         res = task_wait(task_data);
157         if (res != 0) {
158                 ast_test_status_update(test, "Queued task did not execute!\n");
159                 return AST_TEST_FAIL;
160         }
161
162         return AST_TEST_PASS;
163 }
164
165 #define NUM_TASKS 20000
166
167 /*!
168  * \brief Relevant data associated with taskprocessor load test
169  */
170 static struct load_task_data {
171         /*! Condition used to indicate a task has completed executing */
172         ast_cond_t cond;
173         /*! Lock used to protect the condition */
174         ast_mutex_t lock;
175         /*! Counter of the number of completed tasks */
176         int tasks_completed;
177         /*! Storage for task-specific data */
178         int task_rand[NUM_TASKS];
179 } load_task_results;
180
181 /*!
182  * \brief a queued task to be used in the taskprocessor load test
183  *
184  * The task increments the number of tasks executed and puts the passed-in
185  * data into the next slot in the array of random data.
186  */
187 static int load_task(void *data)
188 {
189         int *randdata = data;
190         SCOPED_MUTEX(lock, &load_task_results.lock);
191         load_task_results.task_rand[load_task_results.tasks_completed++] = *randdata;
192         ast_cond_signal(&load_task_results.cond);
193         return 0;
194 }
195
196 /*!
197  * \brief Load test for taskprocessor with default listener
198  *
199  * This test queues a large number of tasks, each with random data associated.
200  * The test ensures that all of the tasks are run and that the tasks are executed
201  * in the same order that they were queued
202  */
203 AST_TEST_DEFINE(default_taskprocessor_load)
204 {
205         struct ast_taskprocessor *tps;
206         struct timeval start;
207         struct timespec ts;
208         enum ast_test_result_state res = AST_TEST_PASS;
209         int timedwait_res;
210         int i;
211         int rand_data[NUM_TASKS];
212
213         switch (cmd) {
214         case TEST_INIT:
215                 info->name = "default_taskprocessor_load";
216                 info->category = "/main/taskprocessor/";
217                 info->summary = "Load test of default taskproccesor";
218                 info->description =
219                         "Ensure that a large number of queued tasks are executed in the proper order.";
220                 return AST_TEST_NOT_RUN;
221         case TEST_EXECUTE:
222                 break;
223         }
224
225         tps = ast_taskprocessor_get("test", TPS_REF_DEFAULT);
226
227         if (!tps) {
228                 ast_test_status_update(test, "Unable to create test taskprocessor\n");
229                 return AST_TEST_FAIL;
230         }
231
232         start = ast_tvnow();
233
234         ts.tv_sec = start.tv_sec + 60;
235         ts.tv_nsec = start.tv_usec * 1000;
236
237         ast_cond_init(&load_task_results.cond, NULL);
238         ast_mutex_init(&load_task_results.lock);
239         load_task_results.tasks_completed = 0;
240
241         for (i = 0; i < NUM_TASKS; ++i) {
242                 rand_data[i] = ast_random();
243                 ast_taskprocessor_push(tps, load_task, &rand_data[i]);
244         }
245
246         ast_mutex_lock(&load_task_results.lock);
247         while (load_task_results.tasks_completed < NUM_TASKS) {
248                 timedwait_res = ast_cond_timedwait(&load_task_results.cond, &load_task_results.lock, &ts);
249                 if (timedwait_res == ETIMEDOUT) {
250                         break;
251                 }
252         }
253         ast_mutex_unlock(&load_task_results.lock);
254
255         if (load_task_results.tasks_completed != NUM_TASKS) {
256                 ast_test_status_update(test, "Unexpected number of tasks executed. Expected %d but got %d\n",
257                                 NUM_TASKS, load_task_results.tasks_completed);
258                 res = AST_TEST_FAIL;
259                 goto test_end;
260         }
261
262         for (i = 0; i < NUM_TASKS; ++i) {
263                 if (rand_data[i] != load_task_results.task_rand[i]) {
264                         ast_test_status_update(test, "Queued tasks did not execute in order\n");
265                         res = AST_TEST_FAIL;
266                         goto test_end;
267                 }
268         }
269
270 test_end:
271         tps = ast_taskprocessor_unreference(tps);
272         ast_mutex_destroy(&load_task_results.lock);
273         ast_cond_destroy(&load_task_results.cond);
274         return res;
275 }
276
277 /*!
278  * \brief Private data for the test taskprocessor listener
279  */
280 struct test_listener_pvt {
281         /* Counter of number of tasks pushed to the queue */
282         int num_pushed;
283         /* Counter of number of times the queue was emptied */
284         int num_emptied;
285         /* Counter of number of times that a pushed task occurred on an empty queue */
286         int num_was_empty;
287         /* Boolean indicating whether the shutdown callback was called */
288         int shutdown;
289 };
290
291 /*!
292  * \brief test taskprocessor listener's alloc callback
293  */
294 static void *test_listener_pvt_alloc(void)
295 {
296         struct test_listener_pvt *pvt;
297
298         pvt = ast_calloc(1, sizeof(*pvt));
299         return pvt;
300 }
301
302 /*!
303  * \brief test taskprocessor listener's start callback
304  */
305 static int test_start(struct ast_taskprocessor_listener *listener)
306 {
307         return 0;
308 }
309
310 /*!
311  * \brief test taskprocessor listener's task_pushed callback
312  *
313  * Adjusts private data's stats as indicated by the parameters.
314  */
315 static void test_task_pushed(struct ast_taskprocessor_listener *listener, int was_empty)
316 {
317         struct test_listener_pvt *pvt = ast_taskprocessor_listener_get_user_data(listener);
318         ++pvt->num_pushed;
319         if (was_empty) {
320                 ++pvt->num_was_empty;
321         }
322 }
323
324 /*!
325  * \brief test taskprocessor listener's emptied callback.
326  */
327 static void test_emptied(struct ast_taskprocessor_listener *listener)
328 {
329         struct test_listener_pvt *pvt = ast_taskprocessor_listener_get_user_data(listener);
330         ++pvt->num_emptied;
331 }
332
333 /*!
334  * \brief test taskprocessor listener's shutdown callback.
335  */
336 static void test_shutdown(struct ast_taskprocessor_listener *listener)
337 {
338         struct test_listener_pvt *pvt = ast_taskprocessor_listener_get_user_data(listener);
339         pvt->shutdown = 1;
340 }
341
342 static const struct ast_taskprocessor_listener_callbacks test_callbacks = {
343         .start = test_start,
344         .task_pushed = test_task_pushed,
345         .emptied = test_emptied,
346         .shutdown = test_shutdown,
347 };
348
349 /*!
350  * \brief Queued task for taskprocessor listener test.
351  *
352  * Does nothing.
353  */
354 static int listener_test_task(void *ignore)
355 {
356         return 0;
357 }
358
359 /*!
360  * \brief helper to ensure that statistics the listener is keeping are what we expect
361  *
362  * \param test The currently-running test
363  * \param pvt The private data for the taskprocessor listener
364  * \param num_pushed The expected current number of tasks pushed to the processor
365  * \param num_emptied The expected current number of times the taskprocessor has become empty
366  * \param num_was_empty The expected current number of times that tasks were pushed to an empty taskprocessor
367  * \retval -1 Stats were not as expected
368  * \retval 0 Stats were as expected
369  */
370 static int check_stats(struct ast_test *test, const struct test_listener_pvt *pvt, int num_pushed, int num_emptied, int num_was_empty)
371 {
372         if (pvt->num_pushed != num_pushed) {
373                 ast_test_status_update(test, "Unexpected number of tasks pushed. Expected %d but got %d\n",
374                                 num_pushed, pvt->num_pushed);
375                 return -1;
376         }
377
378         if (pvt->num_emptied != num_emptied) {
379                 ast_test_status_update(test, "Unexpected number of empties. Expected %d but got %d\n",
380                                 num_emptied, pvt->num_emptied);
381                 return -1;
382         }
383
384         if (pvt->num_was_empty != num_was_empty) {
385                 ast_test_status_update(test, "Unexpected number of empties. Expected %d but got %d\n",
386                                 num_was_empty, pvt->num_emptied);
387                 return -1;
388         }
389
390         return 0;
391 }
392
393 /*!
394  * \brief Test for a taskprocessor with custom listener.
395  *
396  * This test pushes tasks to a taskprocessor with a custom listener, executes the taskss,
397  * and destroys the taskprocessor.
398  *
399  * The test ensures that the listener's callbacks are called when expected and that the data
400  * being passed in is accurate.
401  */
402 AST_TEST_DEFINE(taskprocessor_listener)
403 {
404         struct ast_taskprocessor *tps = NULL;
405         struct ast_taskprocessor_listener *listener = NULL;
406         struct test_listener_pvt *pvt = NULL;
407         enum ast_test_result_state res = AST_TEST_PASS;
408
409         switch (cmd) {
410         case TEST_INIT:
411                 info->name = "taskprocessor_listener";
412                 info->category = "/main/taskprocessor/";
413                 info->summary = "Test of taskproccesor listeners";
414                 info->description =
415                         "Ensures that listener callbacks are called when expected.";
416                 return AST_TEST_NOT_RUN;
417         case TEST_EXECUTE:
418                 break;
419         }
420
421         pvt = test_listener_pvt_alloc();
422         if (!pvt) {
423                 ast_test_status_update(test, "Unable to allocate test taskprocessor listener user data\n");
424                 return AST_TEST_FAIL;
425         }
426
427         listener = ast_taskprocessor_listener_alloc(&test_callbacks, pvt);
428         if (!listener) {
429                 ast_test_status_update(test, "Unable to allocate test taskprocessor listener\n");
430                 res = AST_TEST_FAIL;
431                 goto test_exit;
432         }
433
434         tps = ast_taskprocessor_create_with_listener("test_listener", listener);
435         if (!tps) {
436                 ast_test_status_update(test, "Unable to allocate test taskprocessor\n");
437                 res = AST_TEST_FAIL;
438                 goto test_exit;
439         }
440
441         ast_taskprocessor_push(tps, listener_test_task, NULL);
442
443         if (check_stats(test, pvt, 1, 0, 1) < 0) {
444                 res = AST_TEST_FAIL;
445                 goto test_exit;
446         }
447
448         ast_taskprocessor_push(tps, listener_test_task, NULL);
449
450         if (check_stats(test, pvt, 2, 0, 1) < 0) {
451                 res = AST_TEST_FAIL;
452                 goto test_exit;
453         }
454
455         ast_taskprocessor_execute(tps);
456
457         if (check_stats(test, pvt, 2, 0, 1) < 0) {
458                 res = AST_TEST_FAIL;
459                 goto test_exit;
460         }
461
462         ast_taskprocessor_execute(tps);
463
464         if (check_stats(test, pvt, 2, 1, 1) < 0) {
465                 res = AST_TEST_FAIL;
466                 goto test_exit;
467         }
468
469         tps = ast_taskprocessor_unreference(tps);
470
471         if (!pvt->shutdown) {
472                 res = AST_TEST_FAIL;
473                 goto test_exit;
474         }
475
476 test_exit:
477         ao2_cleanup(listener);
478         /* This is safe even if tps is NULL */
479         ast_taskprocessor_unreference(tps);
480         ast_free(pvt);
481         return res;
482 }
483
484 struct shutdown_data {
485         ast_cond_t in;
486         ast_cond_t out;
487         ast_mutex_t lock;
488         int task_complete;
489         int task_started;
490         int task_stop_waiting;
491 };
492
493 static void shutdown_data_dtor(void *data)
494 {
495         struct shutdown_data *shutdown_data = data;
496         ast_mutex_destroy(&shutdown_data->lock);
497         ast_cond_destroy(&shutdown_data->in);
498         ast_cond_destroy(&shutdown_data->out);
499 }
500
501 static struct shutdown_data *shutdown_data_create(int dont_wait)
502 {
503         RAII_VAR(struct shutdown_data *, shutdown_data, NULL, ao2_cleanup);
504
505         shutdown_data = ao2_alloc(sizeof(*shutdown_data), shutdown_data_dtor);
506         if (!shutdown_data) {
507                 return NULL;
508         }
509
510         ast_mutex_init(&shutdown_data->lock);
511         ast_cond_init(&shutdown_data->in, NULL);
512         ast_cond_init(&shutdown_data->out, NULL);
513         shutdown_data->task_stop_waiting = dont_wait;
514         ao2_ref(shutdown_data, +1);
515         return shutdown_data;
516 }
517
518 static int shutdown_task_exec(void *data)
519 {
520         struct shutdown_data *shutdown_data = data;
521         SCOPED_MUTEX(lock, &shutdown_data->lock);
522         shutdown_data->task_started = 1;
523         ast_cond_signal(&shutdown_data->out);
524         while (!shutdown_data->task_stop_waiting) {
525                 ast_cond_wait(&shutdown_data->in, &shutdown_data->lock);
526         }
527         shutdown_data->task_complete = 1;
528         ast_cond_signal(&shutdown_data->out);
529         return 0;
530 }
531
532 static int shutdown_waitfor_completion(struct shutdown_data *shutdown_data)
533 {
534         struct timeval start = ast_tvnow();
535         struct timespec end = {
536                 .tv_sec = start.tv_sec + 5,
537                 .tv_nsec = start.tv_usec * 1000
538         };
539         SCOPED_MUTEX(lock, &shutdown_data->lock);
540
541         while (!shutdown_data->task_complete) {
542                 if (ast_cond_timedwait(&shutdown_data->out, &shutdown_data->lock, &end) == ETIMEDOUT) {
543                         break;
544                 }
545         }
546
547         return shutdown_data->task_complete;
548 }
549
550 static int shutdown_has_completed(struct shutdown_data *shutdown_data)
551 {
552         SCOPED_MUTEX(lock, &shutdown_data->lock);
553         return shutdown_data->task_complete;
554 }
555
556 static int shutdown_waitfor_start(struct shutdown_data *shutdown_data)
557 {
558         struct timeval start = ast_tvnow();
559         struct timespec end = {
560                 .tv_sec = start.tv_sec + 5,
561                 .tv_nsec = start.tv_usec * 1000
562         };
563         SCOPED_MUTEX(lock, &shutdown_data->lock);
564
565         while (!shutdown_data->task_started) {
566                 if (ast_cond_timedwait(&shutdown_data->out, &shutdown_data->lock, &end) == ETIMEDOUT) {
567                         break;
568                 }
569         }
570
571         return shutdown_data->task_started;
572 }
573
574 static void shutdown_poke(struct shutdown_data *shutdown_data)
575 {
576         SCOPED_MUTEX(lock, &shutdown_data->lock);
577         shutdown_data->task_stop_waiting = 1;
578         ast_cond_signal(&shutdown_data->in);
579 }
580
581 static void *tps_shutdown_thread(void *data)
582 {
583         struct ast_taskprocessor *tps = data;
584         ast_taskprocessor_unreference(tps);
585         return NULL;
586 }
587
588 AST_TEST_DEFINE(taskprocessor_shutdown)
589 {
590         RAII_VAR(struct ast_taskprocessor *, tps, NULL, ast_taskprocessor_unreference);
591         RAII_VAR(struct shutdown_data *, task1, NULL, ao2_cleanup);
592         RAII_VAR(struct shutdown_data *, task2, NULL, ao2_cleanup);
593         int push_res;
594         int wait_res;
595         int pthread_res;
596         pthread_t shutdown_thread;
597
598         switch (cmd) {
599         case TEST_INIT:
600                 info->name = "taskprocessor_shutdown";
601                 info->category = "/main/taskprocessor/";
602                 info->summary = "Test of taskproccesor shutdown sequence";
603                 info->description =
604                         "Ensures that all tasks run to completion after the taskprocessor has been unref'ed.";
605                 return AST_TEST_NOT_RUN;
606         case TEST_EXECUTE:
607                 break;
608         }
609
610         tps = ast_taskprocessor_get("test_shutdown", TPS_REF_DEFAULT);
611         task1 = shutdown_data_create(0); /* task1 waits to be poked */
612         task2 = shutdown_data_create(1); /* task2 waits for nothing */
613
614         if (!tps || !task1 || !task2) {
615                 ast_test_status_update(test, "Allocation error\n");
616                 return AST_TEST_FAIL;
617         }
618
619         push_res = ast_taskprocessor_push(tps, shutdown_task_exec, task1);
620         if (push_res != 0) {
621                 ast_test_status_update(test, "Could not push task1\n");
622                 return AST_TEST_FAIL;
623         }
624
625         push_res = ast_taskprocessor_push(tps, shutdown_task_exec, task2);
626         if (push_res != 0) {
627                 ast_test_status_update(test, "Could not push task2\n");
628                 return AST_TEST_FAIL;
629         }
630
631         wait_res = shutdown_waitfor_start(task1);
632         if (!wait_res) {
633                 ast_test_status_update(test, "Task1 didn't start\n");
634                 return AST_TEST_FAIL;
635         }
636
637         pthread_res = ast_pthread_create(&shutdown_thread, NULL, tps_shutdown_thread, tps);
638         if (pthread_res != 0) {
639                 ast_test_status_update(test, "Failed to create shutdown thread\n");
640                 return AST_TEST_FAIL;
641         }
642         tps = NULL;
643
644         /* Wakeup task1; it should complete */
645         shutdown_poke(task1);
646         wait_res = shutdown_waitfor_completion(task1);
647         if (!wait_res) {
648                 ast_test_status_update(test, "Task1 didn't complete\n");
649                 return AST_TEST_FAIL;
650         }
651
652         /* Wait for shutdown to complete */
653         pthread_join(shutdown_thread, NULL);
654
655         /* Should have also also completed task2 */
656         wait_res = shutdown_has_completed(task2);
657         if (!wait_res) {
658                 ast_test_status_update(test, "Task2 didn't finish\n");
659                 return AST_TEST_FAIL;
660         }
661
662         return AST_TEST_PASS;
663 }
664
665 static int local_task_exe(struct ast_taskprocessor_local *local)
666 {
667         int *local_data = local->local_data;
668         struct task_data *task_data = local->data;
669
670         *local_data = 1;
671         task(task_data);
672
673         return 0;
674 }
675
676 AST_TEST_DEFINE(taskprocessor_push_local)
677 {
678         RAII_VAR(struct ast_taskprocessor *, tps, NULL,
679                 ast_taskprocessor_unreference);
680         struct task_data *task_data;
681         int local_data;
682         int res;
683
684         switch (cmd) {
685         case TEST_INIT:
686                 info->name = __func__;
687                 info->category = "/main/taskprocessor/";
688                 info->summary = "Test of pushing local data";
689                 info->description =
690                         "Ensures that local data is passed along.";
691                 return AST_TEST_NOT_RUN;
692         case TEST_EXECUTE:
693                 break;
694         }
695
696
697         tps = ast_taskprocessor_get("test", TPS_REF_DEFAULT);
698         if (!tps) {
699                 ast_test_status_update(test, "Unable to create test taskprocessor\n");
700                 return AST_TEST_FAIL;
701         }
702
703
704         task_data = task_data_create();
705         if (!task_data) {
706                 ast_test_status_update(test, "Unable to create task_data\n");
707                 return AST_TEST_FAIL;
708         }
709
710         local_data = 0;
711         ast_taskprocessor_set_local(tps, &local_data);
712
713         ast_taskprocessor_push_local(tps, local_task_exe, task_data);
714
715         res = task_wait(task_data);
716         if (res != 0) {
717                 ast_test_status_update(test, "Queued task did not execute!\n");
718                 return AST_TEST_FAIL;
719         }
720
721         if (local_data != 1) {
722                 ast_test_status_update(test,
723                         "Queued task did not set local_data!\n");
724                 return AST_TEST_FAIL;
725         }
726
727         return AST_TEST_PASS;
728 }
729
730 static int unload_module(void)
731 {
732         ast_test_unregister(default_taskprocessor);
733         ast_test_unregister(default_taskprocessor_load);
734         ast_test_unregister(taskprocessor_listener);
735         ast_test_unregister(taskprocessor_shutdown);
736         ast_test_unregister(taskprocessor_push_local);
737         return 0;
738 }
739
740 static int load_module(void)
741 {
742         ast_test_register(default_taskprocessor);
743         ast_test_register(default_taskprocessor_load);
744         ast_test_register(taskprocessor_listener);
745         ast_test_register(taskprocessor_shutdown);
746         ast_test_register(taskprocessor_push_local);
747         return AST_MODULE_LOAD_SUCCESS;
748 }
749
750 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "taskprocessor test module");