pthreadpool: Simplify the logic in add_job a bit
[samba.git] / lib / pthreadpool / tests_cmocka.c
1 /*
2  * Unix SMB/CIFS implementation.
3  * cmocka tests for thread pool implementation
4  * Copyright (C) Christof Schmitt 2017
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include <errno.h>
21 #include <pthread.h>
22 #include <setjmp.h>
23 #include <stdlib.h>
24 #include <string.h>
25
26 #include <talloc.h>
27 #include <tevent.h>
28 #include <pthreadpool_tevent.h>
29
30 #include <cmocka.h>
31
32 struct pthreadpool_tevent_test {
33         struct tevent_context *ev;
34         struct pthreadpool_tevent *pool;
35 };
36
37 static int setup_pthreadpool_tevent(void **state)
38 {
39         struct pthreadpool_tevent_test *t;
40         int ret;
41
42         t = talloc(NULL, struct pthreadpool_tevent_test);
43         assert_non_null(t);
44
45         t->ev = tevent_context_init(t);
46         assert_non_null(t->ev);
47
48         ret = pthreadpool_tevent_init(t->ev, 0, &t->pool);
49         assert_return_code(ret, 0);
50
51         *state = t;
52
53         return 0;
54 }
55
56 static int teardown_pthreadpool_tevent(void **state)
57 {
58         struct pthreadpool_tevent_test *t = *state;
59
60         TALLOC_FREE(t);
61
62         return 0;
63 }
64
65 int __wrap_pthread_create(pthread_t *thread, const pthread_attr_t *attr,
66                           void *(*start_routine) (void *), void *arg);
67 int __real_pthread_create(pthread_t *thread, const pthread_attr_t *attr,
68                           void *(*start_routine) (void *),  void *arg);
69
70 int __wrap_pthread_create(pthread_t *thread, const pthread_attr_t *attr,
71                           void *(*start_routine) (void *), void *arg)
72 {
73         int error;
74
75         error = mock_type(int);
76         if (error != 0) {
77                 return error;
78         }
79
80         return __real_pthread_create(thread, attr, start_routine, arg);
81 }
82
83 static void test_job_threadid(void *ptr)
84 {
85         pthread_t *threadid = ptr;
86
87         *threadid = pthread_self();
88 }
89
90 static int test_create_do(struct tevent_context *ev,
91                           struct pthreadpool_tevent *pool,
92                           bool *in_main_thread)
93 {
94         struct tevent_req *req;
95         pthread_t main_thread, worker_thread;
96         bool ok;
97         int ret;
98
99         main_thread = pthread_self();
100
101         req = pthreadpool_tevent_job_send(
102                 ev, ev, pool, test_job_threadid, &worker_thread);
103         if (req == NULL) {
104                 fprintf(stderr, "pthreadpool_tevent_job_send failed\n");
105                 TALLOC_FREE(ev);
106                 return ENOMEM;
107         }
108
109         ok = tevent_req_poll(req, ev);
110         if (!ok) {
111                 ret = errno;
112                 fprintf(stderr, "tevent_req_poll failed: %s\n",
113                         strerror(ret));
114                 TALLOC_FREE(ev);
115                 return ret;
116         }
117
118
119         ret = pthreadpool_tevent_job_recv(req);
120         TALLOC_FREE(req);
121         if (ret != 0) {
122                 fprintf(stderr, "tevent_req_recv failed: %s\n",
123                         strerror(ret));
124                 TALLOC_FREE(ev);
125                 return ret;
126         }
127         *in_main_thread = pthread_equal(worker_thread, main_thread);
128
129         return 0;
130 }
131
132 static void test_create(void **state)
133 {
134         struct pthreadpool_tevent_test *t = *state;
135         bool in_main_thread;
136         int ret;
137
138         /*
139          * When pthreadpool cannot create the first worker thread,
140          * this job will run in the sync fallback in the main thread.
141          */
142         will_return(__wrap_pthread_create, EAGAIN);
143         ret = test_create_do(t->ev, t->pool, &in_main_thread);
144         assert_return_code(ret, 0);
145         assert_true(in_main_thread);
146
147         /*
148          * When a thread can be created, the job will run in the worker thread.
149          */
150         will_return(__wrap_pthread_create, 0);
151         ret = test_create_do(t->ev, t->pool, &in_main_thread);
152         assert_return_code(ret, 0);
153         assert_false(in_main_thread);
154
155         /*
156          * Workerthread will still be active for a second; immediately
157          * running another job will also use the worker thread, even
158          * if a new thread cannot be created.
159          */
160         ret = test_create_do(t->ev, t->pool, &in_main_thread);
161         assert_return_code(ret, 0);
162         assert_false(in_main_thread);
163 }
164
165 int main(int argc, char **argv)
166 {
167         const struct CMUnitTest tests[] = {
168                 cmocka_unit_test_setup_teardown(test_create,
169                                                 setup_pthreadpool_tevent,
170                                                 teardown_pthreadpool_tevent),
171         };
172
173         cmocka_set_message_output(CM_OUTPUT_SUBUNIT);
174
175         return cmocka_run_group_tests(tests, NULL, NULL);
176 }