stream: Add unit tests for channel stream usage.
[asterisk/asterisk.git] / tests / test_scoped_lock.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2012, 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 SCOPED_LOCK 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/utils.h"
36 #include "asterisk/module.h"
37 #include "asterisk/astobj2.h"
38
39 static int indicator;
40 static struct ast_test *current_test;
41 AST_MUTEX_DEFINE_STATIC(the_lock);
42
43 static void lock_it(ast_mutex_t *lock)
44 {
45         indicator = 1;
46         ast_mutex_lock(lock);
47 }
48
49 static void unlock_it(ast_mutex_t *lock)
50 {
51         indicator = 0;
52         ast_mutex_unlock(lock);
53 }
54
55 AST_TEST_DEFINE(lock_test)
56 {
57         enum ast_test_result_state res = AST_TEST_PASS;
58         int i;
59
60         switch(cmd) {
61         case TEST_INIT:
62                 info->name = "lock_test";
63                 info->category = "/main/lock/";
64                 info->summary = "SCOPED_LOCK test";
65                 info->description =
66                         "Tests that scoped locks are scoped as they are expected to be";
67                 return AST_TEST_NOT_RUN;
68         case TEST_EXECUTE:
69                 break;
70         }
71
72         current_test = test;
73         indicator = 0;
74         {
75                 SCOPED_LOCK(lock, &the_lock, lock_it, unlock_it);
76                 if (indicator != 1) {
77                         ast_log(LOG_ERROR, "The lock was not acquired via RAII");
78                         res = AST_TEST_FAIL;
79                 }
80         }
81         if (indicator != 0) {
82                 ast_log(LOG_ERROR, "The lock was not released when the variable went out of scope");
83                 res = AST_TEST_FAIL;
84         }
85
86         for (i = 0; i < 10; ++i) {
87                 SCOPED_LOCK(lock, &the_lock, lock_it, unlock_it);
88                 if (indicator != 1) {
89                         ast_log(LOG_ERROR, "The lock was not acquired via RAII");
90                         res = AST_TEST_FAIL;
91                 }
92         }
93
94         if (indicator != 0) {
95                 ast_log(LOG_ERROR, "The lock was not released when the variable went out of scope");
96                 res = AST_TEST_FAIL;
97         }
98
99         return res;
100 }
101
102 struct test_struct
103 {
104         int locked;
105         int reffed;
106 };
107
108 /*!
109  * \brief lock callback function
110  *
111  * Locks the object passed in. Only sets the locked
112  * flag if the object is reffed. This allows us to check
113  * that locking is always occurring after reffing.
114  */
115 static void test_lock(struct test_struct *test)
116 {
117         ast_test_status_update(current_test, "Lock is occurring\n");
118         ao2_lock(test);
119         if (test->reffed) {
120                 test->locked = 1;
121         }
122 }
123
124 /*!
125  * \brief unlock callback function
126  *
127  * Unlocks the object passed in. Only clears the locked
128  * flag if the object is still reffed. This allows us to
129  * ensure that unlocking is always occurring before unreffing.
130  */
131 static void test_unlock(struct test_struct *test)
132 {
133         ast_test_status_update(current_test, "Unlock is occurring\n");
134         ao2_unlock(test);
135         if (test->reffed) {
136                 test->locked = 0;
137         }
138 }
139
140 /*!
141  * \brief ref callback function
142  *
143  * Refs the object passed in. Only sets the reffed flag if
144  * the object is not locked. This allows us to ensure that
145  * reffing always occurs before locking.
146  */
147 static struct test_struct *test_ref(struct test_struct *test)
148 {
149         ast_test_status_update(current_test, "Ref is occurring\n");
150         ao2_ref(test, +1);
151         if (!test->locked) {
152                 test->reffed = 1;
153         }
154         return test;
155 }
156
157 /*!
158  * \brief unref callback function
159  *
160  * Unrefs the object passed in. Only sets the unreffed flag if
161  * the object is not locked. This allows us to ensure that
162  * unreffing always occurs after unlocking.
163  */
164 static void test_unref(struct test_struct *test)
165 {
166         ast_test_status_update(current_test, "Unref is occurring\n");
167         ao2_ref(test, -1);
168         if (!test->locked) {
169                 test->reffed = 0;
170         }
171 }
172
173 /*!
174  * \brief wrapper for ao2_iterator_next
175  *
176  * Grabs the next item in the container and replaces the ref acquired
177  * from ao2_iterator_next() with a call to test_ref().
178  */
179 static struct test_struct *test_iterator_next(struct ao2_iterator *iter)
180 {
181         struct test_struct *test = ao2_iterator_next(iter);
182
183         if (!test) {
184                 return NULL;
185         }
186
187         /* Remove ref from ao2_iterator_next() and replace it with
188          * a test_ref() call. The order here is safe since we can guarantee
189          * the container still has a ref to the test structure.
190          */
191         ao2_ref(test, -1);
192         test_ref(test);
193
194         return test;
195 }
196
197 AST_TEST_DEFINE(cleanup_order)
198 {
199         enum ast_test_result_state res = AST_TEST_PASS;
200         struct ao2_iterator iter;
201         struct test_struct *object_iter;
202         RAII_VAR(struct ao2_container*, container, ao2_container_alloc(13, NULL, NULL), ao2_cleanup);
203         RAII_VAR(struct test_struct *, object, ao2_alloc(sizeof(*object), NULL), ao2_cleanup);
204
205         switch(cmd) {
206         case TEST_INIT:
207                 info->name = "cleanup_order_test";
208                 info->category = "/main/lock/";
209                 info->summary = "cleanup order test";
210                 info->description =
211                         "Tests that variables with cleanup attributes are cleaned up\n"
212                         "in the reverse order they are declared.";
213                 return AST_TEST_NOT_RUN;
214         case TEST_EXECUTE:
215                 break;
216         }
217         current_test = test;
218
219         if (!object || !container) {
220                 /* Allocation failure. We can't even pretend to do this test properly */
221                 return AST_TEST_FAIL;
222         }
223
224         {
225                 /* Purpose of this block is to make sure that the cleanup operations
226                  * run in the reverse order that they were created here.
227                  */
228                 RAII_VAR(struct test_struct *, object2, test_ref(object), test_unref);
229                 SCOPED_LOCK(lock, object, test_lock, test_unlock);
230                 if (!object->reffed || !object->locked) {
231                         ast_log(LOG_ERROR, "Test failed due to out of order initializations");
232                         res = AST_TEST_FAIL;
233                 }
234         }
235
236         if (object->reffed || object->locked) {
237                 ast_log(LOG_ERROR, "Test failed due to out of order cleanups\n");
238                 res = AST_TEST_FAIL;
239         }
240
241         /* Now link the object into the container for a little experiment ... */
242         ao2_link(container, object);
243
244         /* This loop is to ensure that unrefs in a for loop occur after the cleanup
245          * operations of items inside the loop. If we hope to be able to mix scoped locks
246          * and ao2 refs, this is the way to go about it.
247          */
248         for (iter = ao2_iterator_init(container, 0);
249                         (object_iter = test_iterator_next(&iter));
250                         test_unref(object_iter)) {
251                 SCOPED_LOCK(lock, object_iter, test_lock, test_unlock);
252                 if (!object->reffed || !object->locked) {
253                         ast_log(LOG_ERROR, "Test failed due to out of order initializations");
254                         res = AST_TEST_FAIL;
255                 }
256         }
257         ao2_iterator_destroy(&iter);
258
259         if (object->reffed || object->locked) {
260                 ast_log(LOG_ERROR, "Test failed due to out of order cleanups\n");
261                 res = AST_TEST_FAIL;
262         }
263
264         return res;
265 }
266
267 static int unload_module(void)
268 {
269         AST_TEST_UNREGISTER(lock_test);
270         AST_TEST_UNREGISTER(cleanup_order);
271         return 0;
272 }
273
274 static int load_module(void)
275 {
276         AST_TEST_REGISTER(lock_test);
277         AST_TEST_REGISTER(cleanup_order);
278         return AST_MODULE_LOAD_SUCCESS;
279 }
280
281 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "SCOPED_LOCK test module");