coverity warning fixes
[oweals/gnunet.git] / src / testing / test_testing_topology.c
1 /*
2      This file is part of GNUnet.
3      (C) 2009 Christian Grothoff (and other contributing authors)
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 2, or (at your
8      option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20 /**
21  * @file testing/test_testing_topology.c
22  * @brief base testcase for testing all the topologies provided
23  */
24 #include "platform.h"
25 #include "gnunet_testing_lib.h"
26 #include "gnunet_core_service.h"
27
28 #define VERBOSE GNUNET_YES
29
30 /**
31  * How long until we fail the whole testcase?
32  */
33 #define TEST_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 600)
34
35 /**
36  * How long until we give up on connecting the peers?
37  */
38 #define TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 60)
39
40 #define DEFAULT_NUM_PEERS 4
41
42 #define MAX_OUTSTANDING_CONNECTIONS 300
43
44 static float fail_percentage = 0.05;
45
46 static int ok;
47
48 static unsigned long long num_peers;
49
50 static unsigned int total_connections;
51
52 static unsigned int failed_connections;
53
54 static unsigned int total_server_connections;
55
56 static unsigned int total_messages_received;
57
58 static unsigned int expected_messages;
59
60 static unsigned int expected_connections;
61
62 static unsigned long long peers_left;
63
64 static struct GNUNET_TESTING_PeerGroup *pg;
65
66 static struct GNUNET_SCHEDULER_Handle *sched;
67
68 const struct GNUNET_CONFIGURATION_Handle *main_cfg;
69
70 GNUNET_SCHEDULER_TaskIdentifier die_task;
71
72 static char *dotOutFileName;
73
74 static FILE *dotOutFile;
75
76 static char *topology_string;
77
78 static int transmit_ready_scheduled;
79
80 static int transmit_ready_failed;
81
82 static int transmit_ready_called;
83
84 static enum GNUNET_TESTING_Topology topology;
85
86 static char *test_directory;
87
88 #define MTYPE 12345
89
90 struct GNUNET_TestMessage
91 {
92   /**
93    * Header of the message
94    */
95   struct GNUNET_MessageHeader header;
96
97   /**
98    * Unique identifier for this message.
99    */
100   uint32_t uid;
101 };
102
103 struct TestMessageContext
104 {
105   /* This is a linked list */
106   struct TestMessageContext *next;
107
108   /* Handle to the sending peer core */
109   struct GNUNET_CORE_Handle *peer1handle;
110
111   /* Handle to the receiving peer core */
112   struct GNUNET_CORE_Handle *peer2handle;
113
114   /* Handle to the sending peer daemon */
115   struct GNUNET_TESTING_Daemon *peer1;
116
117   /* Handle to the receiving peer daemon */
118   struct GNUNET_TESTING_Daemon *peer2;
119
120   /* Identifier for this message, so we don't disconnect other peers! */
121   uint32_t uid;
122
123   /* Task for disconnecting cores, allow task to be cancelled on shutdown */
124   GNUNET_SCHEDULER_TaskIdentifier disconnect_task;
125
126 };
127
128 static struct TestMessageContext *test_messages;
129
130 static void
131 finish_testing ()
132 {
133   GNUNET_assert (pg != NULL);
134   struct TestMessageContext *pos;
135   struct TestMessageContext *free_pos;
136 #if VERBOSE
137   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
138               "Called finish testing, stopping daemons.\n");
139 #endif
140   int count;
141   count = 0;
142   pos = test_messages;
143   while (pos != NULL)
144     {
145       if (pos->peer1handle != NULL)
146         {
147           GNUNET_CORE_disconnect(pos->peer1handle);
148           pos->peer1handle = NULL;
149         }
150       if (pos->peer2handle != NULL)
151         {
152           GNUNET_CORE_disconnect(pos->peer2handle);
153           pos->peer2handle = NULL;
154         }
155       free_pos = pos;
156       pos = pos->next;
157       if (free_pos->disconnect_task != GNUNET_SCHEDULER_NO_TASK)
158         {
159           GNUNET_SCHEDULER_cancel(sched, free_pos->disconnect_task);
160         }
161       GNUNET_free(free_pos);
162     }
163 #if VERBOSE
164           GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
165                       "transmit_ready's scheduled %d, failed %d, transmit_ready's called %d\n", transmit_ready_scheduled, transmit_ready_failed, transmit_ready_called);
166 #endif
167   sleep(1);
168 #if VERBOSE
169           GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
170                       "Calling daemons_stop\n");
171 #endif
172   GNUNET_TESTING_daemons_stop (pg);
173 #if VERBOSE
174           GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
175                       "daemons_stop finished\n");
176 #endif
177   if (dotOutFile != NULL)
178     {
179       fprintf(dotOutFile, "}");
180       fclose(dotOutFile);
181     }
182
183   ok = 0;
184 }
185
186
187 static void
188 disconnect_cores (void *cls, const struct GNUNET_SCHEDULER_TaskContext * tc)
189 {
190   struct TestMessageContext *pos = cls;
191
192   /* Disconnect from the respective cores */
193 #if VERBOSE
194   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
195               "Disconnecting from peer 1 `%4s'\n", GNUNET_i2s (&pos->peer1->id));
196 #endif
197   if (pos->peer1handle != NULL)
198     GNUNET_CORE_disconnect(pos->peer1handle);
199 #if VERBOSE
200   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
201               "Disconnecting from peer 2 `%4s'\n", GNUNET_i2s (&pos->peer2->id));
202 #endif
203   if (pos->peer2handle != NULL)
204     GNUNET_CORE_disconnect(pos->peer2handle);
205   /* Set handles to NULL so test case can be ended properly */
206   pos->peer1handle = NULL;
207   pos->peer2handle = NULL;
208   pos->disconnect_task = GNUNET_SCHEDULER_NO_TASK;
209   /* Decrement total connections so new can be established */
210   total_server_connections -= 2;
211 }
212
213 static int
214 process_mtype (void *cls,
215                const struct GNUNET_PeerIdentity *peer,
216                const struct GNUNET_MessageHeader *message,
217                struct GNUNET_TIME_Relative latency,
218                uint32_t distance)
219 {
220   struct TestMessageContext *pos = cls;
221   struct GNUNET_TestMessage *msg = (struct GNUNET_TestMessage *)message;
222   if (pos->uid != ntohl(msg->uid))
223     return GNUNET_OK;
224
225   total_messages_received++;
226 #if VERBOSE
227   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
228               "Received message from `%4s', type %d.\n", GNUNET_i2s (peer), ntohs(message->type));
229   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
230               "Total messages received %d, expected %d.\n", total_messages_received, expected_messages);
231 #endif
232
233   if (total_messages_received == expected_messages)
234     {
235       GNUNET_SCHEDULER_cancel (sched, die_task);
236       GNUNET_SCHEDULER_add_now (sched, &finish_testing, NULL);
237     }
238   else
239     {
240       pos->disconnect_task = GNUNET_SCHEDULER_add_now(sched, &disconnect_cores, pos);
241     }
242
243   return GNUNET_OK;
244 }
245
246 static void
247 end_badly (void *cls, const struct GNUNET_SCHEDULER_TaskContext * tc)
248 {
249   char *msg = cls;
250   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
251               "End badly was called (%s)... stopping daemons.\n", msg);
252   struct TestMessageContext *pos;
253   struct TestMessageContext *free_pos;
254
255   pos = test_messages;
256   while (pos != NULL)
257     {
258       if (pos->peer1handle != NULL)
259         {
260           GNUNET_CORE_disconnect(pos->peer1handle);
261           pos->peer1handle = NULL;
262         }
263       if (pos->peer2handle != NULL)
264         {
265           GNUNET_CORE_disconnect(pos->peer2handle);
266           pos->peer2handle = NULL;
267         }
268       free_pos = pos;
269       pos = pos->next;
270       GNUNET_free(free_pos);
271     }
272
273   if (pg != NULL)
274     {
275       GNUNET_TESTING_daemons_stop (pg);
276       ok = 7331;                /* Opposite of leet */
277     }
278   else
279     ok = 401;                   /* Never got peers started */
280
281   if (dotOutFile != NULL)
282     {
283       fprintf(dotOutFile, "}");
284       fclose(dotOutFile);
285     }
286 }
287
288
289
290 static size_t
291 transmit_ready (void *cls, size_t size, void *buf)
292 {
293   struct GNUNET_TestMessage *m;
294   struct TestMessageContext *pos = cls;
295
296   GNUNET_assert (buf != NULL);
297   m = (struct GNUNET_TestMessage *) buf;
298   m->header.type = htons (MTYPE);
299   m->header.size = htons (sizeof (struct GNUNET_TestMessage));
300   m->uid = htonl(pos->uid);
301   transmit_ready_called++;
302 #if VERBOSE
303   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
304               "transmit ready for peer %s\ntransmit_ready's scheduled %d, transmit_ready's called %d\n", GNUNET_i2s(&pos->peer1->id), transmit_ready_scheduled, transmit_ready_called);
305 #endif
306   return sizeof (struct GNUNET_TestMessage);
307 }
308
309
310 static struct GNUNET_CORE_MessageHandler no_handlers[] = {
311   {NULL, 0, 0}
312 };
313
314 static struct GNUNET_CORE_MessageHandler handlers[] = {
315   {&process_mtype, MTYPE, sizeof (struct GNUNET_TestMessage)},
316   {NULL, 0, 0}
317 };
318
319 static void
320 init_notify_peer2 (void *cls,
321              struct GNUNET_CORE_Handle *server,
322              const struct GNUNET_PeerIdentity *my_identity,
323              const struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded *publicKey)
324 {
325   struct TestMessageContext *pos = cls;
326
327 #if VERBOSE
328   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
329               "Core connection to `%4s' established, scheduling message send\n",
330               GNUNET_i2s (my_identity));
331 #endif
332   total_server_connections++;
333
334   if (NULL == GNUNET_CORE_notify_transmit_ready (pos->peer1handle,
335                                                  0,
336                                                  TIMEOUT,
337                                                  &pos->peer2->id,
338                                                  sizeof (struct GNUNET_TestMessage),
339                                                  &transmit_ready, pos))
340     {
341       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
342                   "RECEIVED NULL when asking core (1) for transmission to peer `%4s'\n",
343                   GNUNET_i2s (&pos->peer2->id));
344       transmit_ready_failed++;
345     }
346   else
347     {
348       transmit_ready_scheduled++;
349     }
350 }
351
352
353 static void
354 init_notify_peer1 (void *cls,
355              struct GNUNET_CORE_Handle *server,
356              const struct GNUNET_PeerIdentity *my_identity,
357              const struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded *publicKey)
358 {
359   struct TestMessageContext *pos = cls;
360   total_server_connections++;
361
362 #if VERBOSE
363   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
364               "Core connection to `%4s' established, setting up handles\n",
365               GNUNET_i2s (my_identity));
366 #endif
367
368   /*
369    * Connect to the receiving peer
370    */
371   pos->peer2handle = GNUNET_CORE_connect (sched,
372                        pos->peer2->cfg,
373                        TIMEOUT,
374                        pos,
375                        &init_notify_peer2,
376                        NULL,
377                        NULL,
378                        NULL,
379                        GNUNET_YES, NULL, GNUNET_YES, handlers);
380
381 }
382
383
384 static void
385 send_test_messages (void *cls, const struct GNUNET_SCHEDULER_TaskContext * tc)
386 {
387   struct TestMessageContext *pos = cls;
388
389   if ((tc->reason == GNUNET_SCHEDULER_REASON_SHUTDOWN) || (cls == NULL))
390     return;
391
392   if (die_task == GNUNET_SCHEDULER_NO_TASK)
393     {
394       die_task = GNUNET_SCHEDULER_add_delayed (sched,
395                                                TEST_TIMEOUT,
396                                                &end_badly, "from create topology (timeout)");
397     }
398
399   if (total_server_connections >= MAX_OUTSTANDING_CONNECTIONS)
400     {
401       GNUNET_SCHEDULER_add_delayed (sched, GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 1),
402                                     &send_test_messages, pos);
403       return; /* Otherwise we'll double schedule messages here! */
404     }
405
406   /*
407    * Connect to the sending peer
408    */
409   pos->peer1handle = GNUNET_CORE_connect (sched,
410                                           pos->peer1->cfg,
411                                           TIMEOUT,
412                                           pos,
413                                           &init_notify_peer1,
414                                           NULL,
415                                           NULL,
416                                           NULL,
417                                           GNUNET_NO, NULL, GNUNET_NO, no_handlers);
418
419   GNUNET_assert(pos->peer1handle != NULL);
420
421   if (total_server_connections < MAX_OUTSTANDING_CONNECTIONS)
422     {
423       GNUNET_SCHEDULER_add_now (sched,
424                                 &send_test_messages, pos->next);
425     }
426   else
427     {
428       GNUNET_SCHEDULER_add_delayed (sched, GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 1),
429                                     &send_test_messages, pos->next);
430     }
431 }
432
433
434 void
435 topology_callback (void *cls,
436                    const struct GNUNET_PeerIdentity *first,
437                    const struct GNUNET_PeerIdentity *second,
438                    const struct GNUNET_CONFIGURATION_Handle *first_cfg,
439                    const struct GNUNET_CONFIGURATION_Handle *second_cfg,
440                    struct GNUNET_TESTING_Daemon *first_daemon,
441                    struct GNUNET_TESTING_Daemon *second_daemon,
442                    const char *emsg)
443 {
444   struct TestMessageContext *temp_context;
445   if (emsg == NULL)
446     {
447       total_connections++;
448 #if VERBOSE
449       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "connected peer %s to peer %s\n",
450                first_daemon->shortname,
451                second_daemon->shortname);
452 #endif
453       temp_context = GNUNET_malloc(sizeof(struct TestMessageContext));
454       temp_context->peer1 = first_daemon;
455       temp_context->peer2 = second_daemon;
456       temp_context->next = test_messages;
457       temp_context->uid = total_connections;
458       temp_context->disconnect_task = GNUNET_SCHEDULER_NO_TASK;
459       test_messages = temp_context;
460
461       expected_messages++;
462       if (dotOutFile != NULL)
463         fprintf(dotOutFile, "\tn%s -- n%s;\n", first_daemon->shortname, second_daemon->shortname);
464     }
465 #if VERBOSE
466   else
467     {
468       failed_connections++;
469       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Failed to connect peer %s to peer %s with error :\n%s\n",
470                first_daemon->shortname,
471                second_daemon->shortname, emsg);
472     }
473 #endif
474
475   if (total_connections == expected_connections)
476     {
477 #if VERBOSE
478       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
479                   "Created %d total connections, which is our target number!  Calling send messages.\n",
480                   total_connections);
481 #endif
482
483       GNUNET_SCHEDULER_cancel (sched, die_task);
484       die_task = GNUNET_SCHEDULER_NO_TASK;
485       GNUNET_SCHEDULER_add_delayed (sched, GNUNET_TIME_relative_multiply(GNUNET_TIME_UNIT_SECONDS, 1), &send_test_messages, test_messages);
486     }
487   else if (total_connections + failed_connections == expected_connections)
488     {
489       if (failed_connections < (unsigned int)(fail_percentage * total_connections))
490         {
491           GNUNET_SCHEDULER_cancel (sched, die_task);
492           die_task = GNUNET_SCHEDULER_NO_TASK;
493           GNUNET_SCHEDULER_add_delayed (sched, GNUNET_TIME_relative_multiply(GNUNET_TIME_UNIT_SECONDS, 1), &send_test_messages, test_messages);
494         }
495       else
496         {
497           GNUNET_SCHEDULER_cancel (sched, die_task);
498           die_task = GNUNET_SCHEDULER_add_now (sched,
499                                                &end_badly, "from topology_callback (too many failed connections)");
500         }
501     }
502   else
503     {
504 #if VERBOSE
505       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
506                   "Have %d total connections, %d failed connections, Want %d (at least %d)\n",
507                   total_connections, failed_connections, expected_connections, expected_connections - (unsigned int)(fail_percentage * expected_connections));
508 #endif
509     }
510 }
511
512
513 static void
514 create_topology ()
515 {
516   expected_connections = -1;
517   if ((pg != NULL) && (peers_left == 0))
518     {
519       /* create_topology will read the topology information from
520          the config already contained in the peer group, so should
521          we have create_topology called from start peers?  I think
522          maybe this way is best so that the client can know both
523          when peers are started, and when they are connected.
524        */
525       expected_connections = GNUNET_TESTING_create_topology (pg, topology);
526 #if VERBOSE
527       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
528                   "Have %d expected connections\n", expected_connections);
529 #endif
530     }
531
532   GNUNET_SCHEDULER_cancel (sched, die_task);
533   if (expected_connections == GNUNET_SYSERR)
534     {
535       die_task = GNUNET_SCHEDULER_add_now (sched,
536                                            &end_badly, "from create topology (bad return)");
537     }
538   die_task = GNUNET_SCHEDULER_add_delayed (sched,
539                                            TEST_TIMEOUT,
540                                            &end_badly, "from create topology (timeout)");
541 }
542
543
544 static void
545 my_cb (void *cls,
546        const struct GNUNET_PeerIdentity *id,
547        const struct GNUNET_CONFIGURATION_Handle *cfg,
548        struct GNUNET_TESTING_Daemon *d, const char *emsg)
549 {
550   GNUNET_assert (id != NULL);
551 #if VERBOSE
552   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Started daemon %llu out of %llu\n",
553               (num_peers - peers_left) + 1, num_peers);
554 #endif
555   peers_left--;
556   if (peers_left == 0)
557     {
558 #if VERBOSE
559       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
560                   "All %d daemons started, now creating topology!\n",
561                   num_peers);
562 #endif
563       GNUNET_SCHEDULER_cancel (sched, die_task);
564       /* Set up task in case topology creation doesn't finish
565        * within a reasonable amount of time */
566       die_task = GNUNET_SCHEDULER_add_delayed (sched,
567                                                GNUNET_TIME_relative_multiply
568                                                (GNUNET_TIME_UNIT_MINUTES, 5),
569                                                &end_badly, "from my_cb");
570       create_topology ();
571       ok = 0;
572     }
573 }
574
575
576 static void
577 run (void *cls,
578      struct GNUNET_SCHEDULER_Handle *s,
579      char *const *args,
580      const char *cfgfile, const struct GNUNET_CONFIGURATION_Handle *cfg)
581 {
582   unsigned long long topology_num;
583   sched = s;
584   ok = 1;
585
586   dotOutFile = fopen (dotOutFileName, "w");
587   if (dotOutFile != NULL)
588     {
589       fprintf (dotOutFile, "strict graph G {\n");
590     }
591
592 #if VERBOSE
593   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
594               "Starting daemons based on config file %s\n", cfgfile);
595 #endif
596
597   if (GNUNET_YES != GNUNET_CONFIGURATION_get_value_string(cfg, "paths", "servicehome", &test_directory))
598     {
599       ok = 404;
600       return;
601     }
602
603   if (GNUNET_YES ==
604       GNUNET_CONFIGURATION_get_value_number (cfg, "testing", "topology",
605                                              &topology_num))
606     topology = topology_num;
607
608   if (GNUNET_SYSERR ==
609       GNUNET_CONFIGURATION_get_value_number (cfg, "testing", "num_peers",
610                                              &num_peers))
611     num_peers = DEFAULT_NUM_PEERS;
612
613   main_cfg = cfg;
614
615   peers_left = num_peers;
616
617   /* Set up a task to end testing if peer start fails */
618   die_task = GNUNET_SCHEDULER_add_delayed (sched,
619                                            GNUNET_TIME_relative_multiply
620                                            (GNUNET_TIME_UNIT_MINUTES, 5),
621                                            &end_badly, "didn't start all daemons in reasonable amount of time!!!");
622
623   pg = GNUNET_TESTING_daemons_start (sched, cfg,
624                                      peers_left, &my_cb, NULL,
625                                      &topology_callback, NULL, NULL);
626
627 }
628
629 static int
630 check ()
631 {
632   char *binary_name;
633   char *config_file_name;
634   GNUNET_asprintf(&binary_name, "test-testing-topology-%s", topology_string);
635   GNUNET_asprintf(&config_file_name, "test_testing_data_topology_%s.conf", topology_string);
636   int ret;
637   char *const argv[] = {binary_name,
638     "-c",
639     config_file_name,
640 #if VERBOSE
641     "-L", "DEBUG",
642 #endif
643     NULL
644   };
645   struct GNUNET_GETOPT_CommandLineOption options[] = {
646     GNUNET_GETOPT_OPTION_END
647   };
648   ret = GNUNET_PROGRAM_run ((sizeof (argv) / sizeof (char *)) - 1,
649                       argv, binary_name, "nohelp",
650                       options, &run, &ok);
651   if (ret != GNUNET_OK)
652     {
653       GNUNET_log(GNUNET_ERROR_TYPE_WARNING, "`test-testing-topology-%s': Failed with error code %d\n", topology_string, ret);
654     }
655   GNUNET_free(binary_name);
656   GNUNET_free(config_file_name);
657   return ok;
658 }
659
660 int
661 main (int argc, char *argv[])
662 {
663   int ret;
664   char *binary_start_pos;
665   char *our_binary_name;
666
667   binary_start_pos = rindex(argv[0], '/');
668   topology_string = strstr (binary_start_pos,
669                             "_topology");
670   GNUNET_assert (topology_string != NULL);
671   topology_string++;
672   topology_string = strstr (topology_string, "_");
673   GNUNET_assert (topology_string != NULL);
674   topology_string++;
675
676   GNUNET_asprintf(&our_binary_name, "test-testing-topology_%s", topology_string);
677   GNUNET_asprintf(&dotOutFileName, "topology_%s.dot", topology_string);
678
679   GNUNET_log_setup (our_binary_name,
680 #if VERBOSE
681                     "DEBUG",
682 #else
683                     "WARNING",
684 #endif
685                     NULL);
686   ret = check ();
687
688   /**
689    * Need to remove base directory, subdirectories taken care
690    * of by the testing framework.
691    */
692   if (GNUNET_DISK_directory_remove (test_directory) != GNUNET_OK)
693     {
694       GNUNET_log(GNUNET_ERROR_TYPE_WARNING, "Failed to remove testing directory %s\n", test_directory);
695     }
696   GNUNET_free(our_binary_name);
697   return ret;
698 }
699
700 /* end of test_testing_group.c */