Line data Source code
1 : /**
2 : * @file
3 : *
4 : * @brief
5 : *
6 : * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
7 : */
8 :
9 : #include "zeromqsend.h"
10 :
11 : #include <stdio.h> // printf() & co
12 : #include <time.h> // time()
13 : #include <unistd.h> // usleep()
14 :
15 : #include <kdberrors.h> // TIMEOUT ERROR
16 : #include <kdbioplugin.h> // ElektraIoPluginSetBinding
17 :
18 : #include <tests.h>
19 : #include <tests_plugin.h>
20 :
21 : #include <pthread.h>
22 :
23 : /** change type received by readNotificationFromTestSocket() */
24 : char * receivedChangeType;
25 :
26 : /** key name received by readNotificationFromTestSocket() */
27 : char * receivedKeyName;
28 :
29 : /** variable indicating that a timeout occurred while receiving */
30 : int receiveTimeout;
31 :
32 : /** zmq context for tests */
33 : void * context;
34 :
35 : /** time in microseconds before a new socket is created. leaves the system some after binding a socket again */
36 : #define TIME_HOLDOFF (1000 * 1000)
37 :
38 : /** timeout for tests in seconds */
39 : #define TEST_TIMEOUT 20
40 :
41 : /** endpoint for tests */
42 : #define TEST_ENDPOINT "tcp://127.0.0.1:6002"
43 :
44 : /** extended timeouts for tests */
45 : #define TESTCONFIG_CONNECT_TIMEOUT "5000"
46 : #define TESTCONFIG_SUBSCRIBE_TIMEOUT "5000"
47 :
48 : /**
49 : * Create subscriber socket for tests.
50 : * @internal
51 : *
52 : * @param subscribeFilter filter for subscriptions
53 : * @return new socket
54 : */
55 0 : static void * createTestSocket (char * subscribeFilter)
56 : {
57 : // leave the system some time before binding again
58 0 : usleep (TIME_HOLDOFF);
59 :
60 0 : void * subSocket = zmq_socket (context, ZMQ_SUB);
61 0 : int result = zmq_bind (subSocket, TEST_ENDPOINT);
62 0 : if (result != 0)
63 : {
64 0 : yield_error ("zmq_bind failed");
65 0 : printf ("zmq error was: %s\n", zmq_strerror (zmq_errno ()));
66 0 : exit (-1);
67 : }
68 0 : if (subscribeFilter != NULL)
69 : {
70 0 : exit_if_fail (zmq_setsockopt (subSocket, ZMQ_SUBSCRIBE, subscribeFilter, elektraStrLen (subscribeFilter)) == 0,
71 : "subscribe failed"); // subscribe to all messages
72 : }
73 0 : return subSocket;
74 : }
75 :
76 : /**
77 : * Main function for notification reader thread.
78 : *
79 : * Sets global variables receivedKeyName and receivedChangeType.
80 : *
81 : * @internal
82 : *
83 : * @param subSocket socket to read messages from
84 : * @return always NULL
85 : */
86 0 : static void * notificationReaderThreadMain (void * filter)
87 : {
88 0 : void * subSocket = createTestSocket ((char *) filter);
89 :
90 0 : time_t start = time (NULL);
91 :
92 : zmq_msg_t message;
93 0 : zmq_msg_init (&message);
94 : int more;
95 0 : size_t moreSize = sizeof (more);
96 : int rc;
97 0 : int partCounter = 0;
98 0 : int maxParts = 2; // change type and key name
99 : int lastErrno;
100 : do
101 : {
102 0 : usleep (100 * 1000); // wait 100 ms
103 :
104 0 : lastErrno = 0;
105 0 : int result = zmq_msg_recv (&message, subSocket, ZMQ_DONTWAIT);
106 :
107 : // check for timeout
108 0 : if (time (NULL) - start > TEST_TIMEOUT)
109 : {
110 0 : receiveTimeout = 1;
111 0 : receivedChangeType = NULL;
112 0 : receivedKeyName = NULL;
113 0 : zmq_msg_close (&message);
114 0 : zmq_close (subSocket);
115 0 : return NULL;
116 : }
117 :
118 : // check for errors
119 0 : if (result == -1)
120 : {
121 0 : lastErrno = zmq_errno ();
122 0 : if (lastErrno != EAGAIN)
123 : {
124 0 : yield_error ("zmq_msg_recv failed");
125 0 : printf ("zmq_msg_recv failed: %s\n", zmq_strerror (lastErrno));
126 0 : zmq_msg_close (&message);
127 0 : zmq_close (subSocket);
128 0 : return NULL;
129 : }
130 : }
131 : else
132 : {
133 0 : rc = zmq_getsockopt (subSocket, ZMQ_RCVMORE, &more, &moreSize);
134 0 : if (rc < 0)
135 : {
136 0 : yield_error ("zmq_getsockopt failed");
137 0 : printf ("zmq_getsockopt failed: %s\n", zmq_strerror (zmq_errno ()));
138 0 : zmq_msg_close (&message);
139 0 : zmq_close (subSocket);
140 0 : return NULL;
141 : }
142 :
143 0 : int length = zmq_msg_size (&message);
144 0 : char * buffer = elektraStrNDup (zmq_msg_data (&message), length + 1);
145 0 : buffer[length] = '\0';
146 :
147 0 : switch (partCounter)
148 : {
149 : case 0:
150 0 : receivedChangeType = buffer;
151 0 : break;
152 : case 1:
153 0 : receivedKeyName = buffer;
154 0 : break;
155 : default:
156 0 : yield_error ("test inconsistency");
157 : }
158 :
159 0 : partCounter++;
160 : }
161 0 : } while (lastErrno == EAGAIN || (more && partCounter < maxParts));
162 :
163 0 : zmq_msg_close (&message);
164 0 : zmq_close (subSocket);
165 :
166 0 : return NULL;
167 : }
168 :
169 : /**
170 : * Create and start thread for reading notifications.
171 : * @internal
172 : *
173 : * @param filter subscription filter
174 : * @return new thread
175 : */
176 0 : static pthread_t * startNotificationReaderThread (char * filter)
177 : {
178 0 : pthread_t * thread = elektraMalloc (sizeof *thread);
179 0 : pthread_create (thread, NULL, notificationReaderThreadMain, filter);
180 0 : return thread;
181 : }
182 :
183 0 : static void test_commit (void)
184 : {
185 0 : printf ("test commit notification\n");
186 :
187 0 : Key * parentKey = keyNew ("system/tests/foo", KEY_END);
188 0 : Key * toAdd = keyNew ("system/tests/foo/bar", KEY_END);
189 0 : KeySet * ks = ksNew (0, KS_END);
190 :
191 0 : KeySet * conf = ksNew (3, keyNew ("/endpoint", KEY_VALUE, TEST_ENDPOINT, KEY_END),
192 : keyNew ("/connectTimeout", KEY_VALUE, TESTCONFIG_CONNECT_TIMEOUT, KEY_END),
193 : keyNew ("/subscribeTimeout", KEY_VALUE, TESTCONFIG_SUBSCRIBE_TIMEOUT, KEY_END), KS_END);
194 0 : PLUGIN_OPEN ("zeromqsend");
195 :
196 : // initial get to save current state
197 0 : plugin->kdbGet (plugin, ks, parentKey);
198 :
199 : // add key to keyset
200 0 : ksAppendKey (ks, toAdd);
201 :
202 0 : receiveTimeout = 0;
203 0 : receivedKeyName = NULL;
204 0 : receivedChangeType = NULL;
205 :
206 0 : pthread_t * thread = startNotificationReaderThread ("Commit");
207 0 : plugin->kdbSet (plugin, ks, parentKey);
208 0 : pthread_join (*thread, NULL);
209 :
210 0 : succeed_if (receiveTimeout == 0, "receiving did time out");
211 0 : succeed_if (!keyGetMeta (parentKey, "warnings"), "warning meta key was set");
212 0 : succeed_if_same_string ("Commit", receivedChangeType);
213 0 : succeed_if_same_string (keyName (parentKey), receivedKeyName);
214 :
215 0 : ksDel (ks);
216 0 : keyDel (parentKey);
217 0 : PLUGIN_CLOSE ();
218 0 : elektraFree (receivedKeyName);
219 0 : elektraFree (receivedChangeType);
220 0 : elektraFree (thread);
221 0 : }
222 :
223 0 : static void test_timeoutConnect (void)
224 : {
225 0 : printf ("test connect timeout\n");
226 :
227 0 : Key * parentKey = keyNew ("system/tests/foo", KEY_END);
228 0 : Key * toAdd = keyNew ("system/tests/foo/bar", KEY_END);
229 0 : KeySet * ks = ksNew (0, KS_END);
230 :
231 0 : KeySet * conf = ksNew (3, keyNew ("/endpoint", KEY_VALUE, TEST_ENDPOINT, KEY_END),
232 : keyNew ("/connectTimeout", KEY_VALUE, TESTCONFIG_CONNECT_TIMEOUT, KEY_END),
233 : keyNew ("/subscribeTimeout", KEY_VALUE, TESTCONFIG_SUBSCRIBE_TIMEOUT, KEY_END), KS_END);
234 0 : PLUGIN_OPEN ("zeromqsend");
235 :
236 : // initial get to save current state
237 0 : plugin->kdbGet (plugin, ks, parentKey);
238 :
239 : // add key to keyset
240 0 : ksAppendKey (ks, toAdd);
241 :
242 0 : plugin->kdbSet (plugin, ks, parentKey);
243 :
244 0 : char * expectedWarningNumber = elektraFormat ("%s", ELEKTRA_ERROR_INSTALLATION);
245 0 : succeed_if (keyGetMeta (parentKey, "warnings"), "warning meta key was not set");
246 0 : succeed_if_same_string (expectedWarningNumber, keyValue (keyGetMeta (parentKey, "warnings/#00/number")));
247 :
248 0 : ksDel (ks);
249 0 : keyDel (parentKey);
250 0 : PLUGIN_CLOSE ();
251 0 : elektraFree (expectedWarningNumber);
252 0 : }
253 :
254 0 : static void test_timeoutSubscribe (void)
255 : {
256 0 : printf ("test subscribe message timeout\n");
257 :
258 0 : Key * parentKey = keyNew ("system/tests/foo", KEY_END);
259 0 : Key * toAdd = keyNew ("system/tests/foo/bar", KEY_END);
260 0 : KeySet * ks = ksNew (0, KS_END);
261 :
262 0 : KeySet * conf = ksNew (3, keyNew ("/endpoint", KEY_VALUE, TEST_ENDPOINT, KEY_END),
263 : keyNew ("/connectTimeout", KEY_VALUE, TESTCONFIG_CONNECT_TIMEOUT, KEY_END),
264 : keyNew ("/subscribeTimeout", KEY_VALUE, TESTCONFIG_SUBSCRIBE_TIMEOUT, KEY_END), KS_END);
265 0 : PLUGIN_OPEN ("zeromqsend");
266 :
267 : // initial get to save current state
268 0 : plugin->kdbGet (plugin, ks, parentKey);
269 :
270 : // add key to keyset
271 0 : ksAppendKey (ks, toAdd);
272 :
273 0 : receiveTimeout = 0;
274 0 : receivedKeyName = NULL;
275 0 : receivedChangeType = NULL;
276 :
277 : // do not subscribe to Commit messages, this makes the plugin timeout due to no subscribers
278 0 : pthread_t * thread = startNotificationReaderThread (NULL);
279 :
280 0 : plugin->kdbSet (plugin, ks, parentKey);
281 : // without timeout we won't return here
282 :
283 0 : pthread_join (*thread, NULL);
284 :
285 0 : succeed_if (receiveTimeout, "receiving did not time out");
286 0 : succeed_if (receivedKeyName == NULL, "received key name should be unchanged");
287 0 : succeed_if (receivedChangeType == NULL, "received change type should be unchanged");
288 :
289 0 : ksDel (ks);
290 0 : keyDel (parentKey);
291 0 : PLUGIN_CLOSE ();
292 0 : elektraFree (receivedKeyName);
293 0 : elektraFree (receivedChangeType);
294 0 : elektraFree (thread);
295 0 : }
296 :
297 0 : int main (int argc, char ** argv)
298 : {
299 0 : printf ("ZEROMQSEND TESTS\n");
300 0 : printf ("================\n\n");
301 :
302 0 : init (argc, argv);
303 :
304 : int major, minor, patch;
305 0 : zmq_version (&major, &minor, &patch);
306 0 : printf ("zeromq version is %d.%d.%d\n", major, minor, patch);
307 :
308 0 : context = zmq_ctx_new ();
309 :
310 : // Test notification from plugin
311 0 : test_commit ();
312 :
313 : // test timeouts
314 0 : test_timeoutConnect ();
315 0 : test_timeoutSubscribe ();
316 :
317 0 : print_result ("testmod_zeromqsend");
318 :
319 0 : zmq_ctx_destroy (context);
320 :
321 0 : return nbError;
322 : }
|