1 /* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 #include "config.h"
3 #include <assert.h>
4 #include <dlfcn.h>
5 #include <errno.h>
6 #include <stdarg.h>
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <unistd.h>
11 #include <signal.h>
12 #include <pthread.h>
13 #include "utilities/engine_loader.h"
14 #include <memcached/engine_testapp.h>
15 #include <memcached/extension_loggers.h>
16 #include <mock_server.h>
17 
18 struct mock_engine {
19     ENGINE_HANDLE_V1 me;
20     ENGINE_HANDLE_V1 *the_engine;
21     TAP_ITERATOR iterator;
22 };
23 
24 #ifndef WIN32
25 static sig_atomic_t alarmed;
26 
alarm_handler(int sig)27 static void alarm_handler(int sig) {
28     alarmed = 1;
29 }
30 #endif
31 
get_handle(ENGINE_HANDLE * handle)32 static inline struct mock_engine* get_handle(ENGINE_HANDLE* handle) {
33     return (struct mock_engine*)handle;
34 }
35 
mock_tap_iterator(ENGINE_HANDLE * handle,const void * cookie,item ** itm,void ** es,uint16_t * nes,uint8_t * ttl,uint16_t * flags,uint32_t * seqno,uint16_t * vbucket)36 static tap_event_t mock_tap_iterator(ENGINE_HANDLE* handle,
37                                      const void *cookie, item **itm,
38                                      void **es, uint16_t *nes, uint8_t *ttl,
39                                      uint16_t *flags, uint32_t *seqno,
40                                      uint16_t *vbucket) {
41    struct mock_engine *me = get_handle(handle);
42    return me->iterator((ENGINE_HANDLE*)me->the_engine, cookie, itm, es, nes,
43                        ttl, flags, seqno, vbucket);
44 }
45 
mock_get_info(ENGINE_HANDLE * handle)46 static const engine_info* mock_get_info(ENGINE_HANDLE* handle) {
47     struct mock_engine *me = get_handle(handle);
48     return me->the_engine->get_info((ENGINE_HANDLE*)me->the_engine);
49 }
50 
mock_initialize(ENGINE_HANDLE * handle,const char * config_str)51 static ENGINE_ERROR_CODE mock_initialize(ENGINE_HANDLE* handle,
52                                          const char* config_str) {
53     struct mock_engine *me = get_handle(handle);
54     return me->the_engine->initialize((ENGINE_HANDLE*)me->the_engine, config_str);
55 }
56 
mock_destroy(ENGINE_HANDLE * handle,const bool force)57 static void mock_destroy(ENGINE_HANDLE* handle, const bool force) {
58     struct mock_engine *me = get_handle(handle);
59     me->the_engine->destroy((ENGINE_HANDLE*)me->the_engine, force);
60 }
61 
mock_allocate(ENGINE_HANDLE * handle,const void * cookie,item ** item,const void * key,const size_t nkey,const size_t nbytes,const int flags,const rel_time_t exptime)62 static ENGINE_ERROR_CODE mock_allocate(ENGINE_HANDLE* handle,
63                                        const void* cookie,
64                                        item **item,
65                                        const void* key,
66                                        const size_t nkey,
67                                        const size_t nbytes,
68                                        const int flags,
69                                        const rel_time_t exptime) {
70     struct mock_engine *me = get_handle(handle);
71     struct mock_connstruct *c = (void*)cookie;
72     if (c == NULL) {
73         c = (void*)create_mock_cookie();
74     }
75 
76     c->nblocks = 0;
77     ENGINE_ERROR_CODE ret = ENGINE_SUCCESS;
78     pthread_mutex_lock(&c->mutex);
79     while (ret == ENGINE_SUCCESS &&
80            (ret = me->the_engine->allocate((ENGINE_HANDLE*)me->the_engine, c,
81                                            item, key, nkey,
82                                            nbytes, flags,
83                                            exptime)) == ENGINE_EWOULDBLOCK &&
84            c->handle_ewouldblock)
85     {
86         ++c->nblocks;
87         pthread_cond_wait(&c->cond, &c->mutex);
88         ret = c->status;
89     }
90     pthread_mutex_unlock(&c->mutex);
91 
92     if (c != cookie) {
93         destroy_mock_cookie(c);
94     }
95 
96     return ret;
97 }
98 
mock_remove(ENGINE_HANDLE * handle,const void * cookie,const void * key,const size_t nkey,uint64_t cas,uint16_t vbucket)99 static ENGINE_ERROR_CODE mock_remove(ENGINE_HANDLE* handle,
100                                      const void* cookie,
101                                      const void* key,
102                                      const size_t nkey,
103                                      uint64_t cas,
104                                      uint16_t vbucket)
105 {
106     struct mock_engine *me = get_handle(handle);
107     struct mock_connstruct *c = (void*)cookie;
108     if (c == NULL) {
109         c = (void*)create_mock_cookie();
110     }
111 
112     c->nblocks = 0;
113     ENGINE_ERROR_CODE ret = ENGINE_SUCCESS;
114     pthread_mutex_lock(&c->mutex);
115     while (ret == ENGINE_SUCCESS &&
116            (ret = me->the_engine->remove((ENGINE_HANDLE*)me->the_engine, c, key,
117                                          nkey, cas, vbucket)) == ENGINE_EWOULDBLOCK &&
118            c->handle_ewouldblock)
119     {
120         ++c->nblocks;
121         pthread_cond_wait(&c->cond, &c->mutex);
122         ret = c->status;
123     }
124     pthread_mutex_unlock(&c->mutex);
125 
126     if (c != cookie) {
127         destroy_mock_cookie(c);
128     }
129 
130     return ret;
131 }
132 
mock_release(ENGINE_HANDLE * handle,const void * cookie,item * item)133 static void mock_release(ENGINE_HANDLE* handle,
134                          const void *cookie,
135                          item* item) {
136     struct mock_engine *me = get_handle(handle);
137     me->the_engine->release((ENGINE_HANDLE*)me->the_engine, cookie, item);
138 }
139 
mock_get(ENGINE_HANDLE * handle,const void * cookie,item ** item,const void * key,const int nkey,uint16_t vbucket)140 static ENGINE_ERROR_CODE mock_get(ENGINE_HANDLE* handle,
141                                   const void* cookie,
142                                   item** item,
143                                   const void* key,
144                                   const int nkey,
145                                   uint16_t vbucket) {
146     struct mock_engine *me = get_handle(handle);
147     struct mock_connstruct *c = (void*)cookie;
148     if (c == NULL) {
149         c = (void*)create_mock_cookie();
150     }
151 
152     c->nblocks = 0;
153     ENGINE_ERROR_CODE ret = ENGINE_SUCCESS;
154     pthread_mutex_lock(&c->mutex);
155     while (ret == ENGINE_SUCCESS &&
156            (ret = me->the_engine->get((ENGINE_HANDLE*)me->the_engine, c, item,
157                                       key, nkey, vbucket)) == ENGINE_EWOULDBLOCK &&
158            c->handle_ewouldblock)
159     {
160         ++c->nblocks;
161         pthread_cond_wait(&c->cond, &c->mutex);
162         ret = c->status;
163     }
164     pthread_mutex_unlock(&c->mutex);
165 
166     if (c != cookie) {
167         destroy_mock_cookie(c);
168     }
169 
170     return ret;
171 }
172 
mock_get_stats(ENGINE_HANDLE * handle,const void * cookie,const char * stat_key,int nkey,ADD_STAT add_stat)173 static ENGINE_ERROR_CODE mock_get_stats(ENGINE_HANDLE* handle,
174                                         const void* cookie,
175                                         const char* stat_key,
176                                         int nkey,
177                                         ADD_STAT add_stat)
178 {
179     struct mock_engine *me = get_handle(handle);
180     struct mock_connstruct *c = (void*)cookie;
181     if (c == NULL) {
182         c = (void*)create_mock_cookie();
183     }
184 
185     c->nblocks = 0;
186     ENGINE_ERROR_CODE ret = ENGINE_SUCCESS;
187     pthread_mutex_lock(&c->mutex);
188     while (ret == ENGINE_SUCCESS &&
189            (ret = me->the_engine->get_stats((ENGINE_HANDLE*)me->the_engine, c, stat_key,
190                                             nkey, add_stat)) == ENGINE_EWOULDBLOCK &&
191            c->handle_ewouldblock)
192     {
193         ++c->nblocks;
194         pthread_cond_wait(&c->cond, &c->mutex);
195         ret = c->status;
196     }
197     pthread_mutex_unlock(&c->mutex);
198 
199     if (c != cookie) {
200         destroy_mock_cookie(c);
201     }
202 
203     return ret;
204 }
205 
mock_store(ENGINE_HANDLE * handle,const void * cookie,item * item,uint64_t * cas,ENGINE_STORE_OPERATION operation,uint16_t vbucket)206 static ENGINE_ERROR_CODE mock_store(ENGINE_HANDLE* handle,
207                                     const void *cookie,
208                                     item* item,
209                                     uint64_t *cas,
210                                     ENGINE_STORE_OPERATION operation,
211                                     uint16_t vbucket) {
212     struct mock_engine *me = get_handle(handle);
213     struct mock_connstruct *c = (void*)cookie;
214     if (c == NULL) {
215         c = (void*)create_mock_cookie();
216     }
217 
218     c->nblocks = 0;
219     ENGINE_ERROR_CODE ret = ENGINE_SUCCESS;
220     pthread_mutex_lock(&c->mutex);
221     while (ret == ENGINE_SUCCESS &&
222            (ret = me->the_engine->store((ENGINE_HANDLE*)me->the_engine, c, item, cas,
223                                         operation, vbucket)) == ENGINE_EWOULDBLOCK &&
224            c->handle_ewouldblock)
225     {
226         ++c->nblocks;
227         pthread_cond_wait(&c->cond, &c->mutex);
228         ret = c->status;
229     }
230     pthread_mutex_unlock(&c->mutex);
231 
232     if (c != cookie) {
233         destroy_mock_cookie(c);
234     }
235 
236     return ret;
237 }
238 
mock_arithmetic(ENGINE_HANDLE * handle,const void * cookie,const void * key,const int nkey,const bool increment,const bool create,const uint64_t delta,const uint64_t initial,const rel_time_t exptime,uint64_t * cas,uint64_t * result,uint16_t vbucket)239 static ENGINE_ERROR_CODE mock_arithmetic(ENGINE_HANDLE* handle,
240                                          const void* cookie,
241                                          const void* key,
242                                          const int nkey,
243                                          const bool increment,
244                                          const bool create,
245                                          const uint64_t delta,
246                                          const uint64_t initial,
247                                          const rel_time_t exptime,
248                                          uint64_t *cas,
249                                          uint64_t *result,
250                                          uint16_t vbucket) {
251     struct mock_engine *me = get_handle(handle);
252     struct mock_connstruct *c = (void*)cookie;
253     if (c == NULL) {
254         c = (void*)create_mock_cookie();
255     }
256 
257     c->nblocks = 0;
258     ENGINE_ERROR_CODE ret = ENGINE_SUCCESS;
259     pthread_mutex_lock(&c->mutex);
260     while (ret == ENGINE_SUCCESS &&
261            (ret = me->the_engine->arithmetic((ENGINE_HANDLE*)me->the_engine, c, key,
262                                              nkey, increment, create,
263                                              delta, initial, exptime,
264                                              cas, result, vbucket)) == ENGINE_EWOULDBLOCK &&
265            c->handle_ewouldblock)
266     {
267         ++c->nblocks;
268         pthread_cond_wait(&c->cond, &c->mutex);
269         ret = c->status;
270     }
271     pthread_mutex_unlock(&c->mutex);
272 
273     if (c != cookie) {
274         destroy_mock_cookie(c);
275     }
276 
277     return ret;
278 }
279 
mock_flush(ENGINE_HANDLE * handle,const void * cookie,time_t when)280 static ENGINE_ERROR_CODE mock_flush(ENGINE_HANDLE* handle,
281                                     const void* cookie, time_t when) {
282     struct mock_engine *me = get_handle(handle);
283     struct mock_connstruct *c = (void*)cookie;
284     if (c == NULL) {
285         c = (void*)create_mock_cookie();
286     }
287 
288     c->nblocks = 0;
289     ENGINE_ERROR_CODE ret = ENGINE_SUCCESS;
290     pthread_mutex_lock(&c->mutex);
291     while (ret == ENGINE_SUCCESS &&
292            (ret = me->the_engine->flush((ENGINE_HANDLE*)me->the_engine, c, when)) == ENGINE_EWOULDBLOCK &&
293            c->handle_ewouldblock)
294     {
295         ++c->nblocks;
296         pthread_cond_wait(&c->cond, &c->mutex);
297         ret = c->status;
298     }
299     pthread_mutex_unlock(&c->mutex);
300 
301     if (c != cookie) {
302         destroy_mock_cookie(c);
303     }
304 
305     return ret;
306 }
307 
mock_reset_stats(ENGINE_HANDLE * handle,const void * cookie)308 static void mock_reset_stats(ENGINE_HANDLE* handle, const void *cookie) {
309     struct mock_engine *me = get_handle(handle);
310     me->the_engine->reset_stats((ENGINE_HANDLE*)me->the_engine, cookie);
311 }
312 
mock_unknown_command(ENGINE_HANDLE * handle,const void * cookie,protocol_binary_request_header * request,ADD_RESPONSE response)313 static ENGINE_ERROR_CODE mock_unknown_command(ENGINE_HANDLE* handle,
314                                               const void* cookie,
315                                               protocol_binary_request_header *request,
316                                               ADD_RESPONSE response)
317 {
318     struct mock_engine *me = get_handle(handle);
319     struct mock_connstruct *c = (void*)cookie;
320     if (c == NULL) {
321         c = (void*)create_mock_cookie();
322     }
323 
324     c->nblocks = 0;
325     ENGINE_ERROR_CODE ret = ENGINE_SUCCESS;
326     pthread_mutex_lock(&c->mutex);
327     while (ret == ENGINE_SUCCESS &&
328            (ret = me->the_engine->unknown_command((ENGINE_HANDLE*)me->the_engine, c,
329                                                   request, response)) == ENGINE_EWOULDBLOCK &&
330            c->handle_ewouldblock)
331     {
332         ++c->nblocks;
333         pthread_cond_wait(&c->cond, &c->mutex);
334         ret = c->status;
335     }
336     pthread_mutex_unlock(&c->mutex);
337 
338     if (c != cookie) {
339         destroy_mock_cookie(c);
340     }
341 
342     return ret;
343 }
344 
mock_item_set_cas(ENGINE_HANDLE * handle,const void * cookie,item * item,uint64_t val)345 static void mock_item_set_cas(ENGINE_HANDLE *handle, const void *cookie,
346                               item* item, uint64_t val)
347 {
348     struct mock_engine *me = get_handle(handle);
349     me->the_engine->item_set_cas((ENGINE_HANDLE*)me->the_engine, cookie, item, val);
350 }
351 
352 
mock_get_item_info(ENGINE_HANDLE * handle,const void * cookie,const item * item,item_info * item_info)353 static bool mock_get_item_info(ENGINE_HANDLE *handle, const void *cookie,
354                                const item* item, item_info *item_info)
355 {
356     struct mock_engine *me = get_handle(handle);
357     return me->the_engine->get_item_info((ENGINE_HANDLE*)me->the_engine,
358                                          cookie, item, item_info);
359 }
360 
mock_get_stats_struct(ENGINE_HANDLE * handle,const void * cookie)361 static void *mock_get_stats_struct(ENGINE_HANDLE* handle, const void* cookie)
362 {
363     struct mock_engine *me = get_handle(handle);
364     return me->the_engine->get_stats_struct((ENGINE_HANDLE*)me->the_engine, cookie);
365 }
366 
mock_aggregate_stats(ENGINE_HANDLE * handle,const void * cookie,void (* callback)(void *,void *),void * vptr)367 static ENGINE_ERROR_CODE mock_aggregate_stats(ENGINE_HANDLE* handle,
368                                               const void* cookie,
369                                               void (*callback)(void*, void*),
370                                               void *vptr)
371 {
372     struct mock_engine *me = get_handle(handle);
373     struct mock_connstruct *c = (void*)cookie;
374     if (c == NULL) {
375         c = (void*)create_mock_cookie();
376     }
377 
378     c->nblocks = 0;
379     ENGINE_ERROR_CODE ret = ENGINE_SUCCESS;
380     pthread_mutex_lock(&c->mutex);
381     while (ret == ENGINE_SUCCESS &&
382            (ret = me->the_engine->aggregate_stats((ENGINE_HANDLE*)me->the_engine, c,
383                                                   callback, vptr)) == ENGINE_EWOULDBLOCK &&
384            c->handle_ewouldblock)
385     {
386         ++c->nblocks;
387         pthread_cond_wait(&c->cond, &c->mutex);
388         ret = c->status;
389     }
390     pthread_mutex_unlock(&c->mutex);
391 
392     if (c != cookie) {
393         destroy_mock_cookie(c);
394     }
395 
396     return ret;
397 }
398 
mock_tap_notify(ENGINE_HANDLE * handle,const void * cookie,void * engine_specific,uint16_t nengine,uint8_t ttl,uint16_t tap_flags,tap_event_t tap_event,uint32_t tap_seqno,const void * key,size_t nkey,uint32_t flags,uint32_t exptime,uint64_t cas,const void * data,size_t ndata,uint16_t vbucket)399 static ENGINE_ERROR_CODE mock_tap_notify(ENGINE_HANDLE* handle,
400                                         const void *cookie,
401                                         void *engine_specific,
402                                         uint16_t nengine,
403                                         uint8_t ttl,
404                                         uint16_t tap_flags,
405                                         tap_event_t tap_event,
406                                         uint32_t tap_seqno,
407                                         const void *key,
408                                         size_t nkey,
409                                         uint32_t flags,
410                                         uint32_t exptime,
411                                         uint64_t cas,
412                                         const void *data,
413                                         size_t ndata,
414                                          uint16_t vbucket) {
415 
416     struct mock_engine *me = get_handle(handle);
417     struct mock_connstruct *c = (void*)cookie;
418     if (c == NULL) {
419         c = (void*)create_mock_cookie();
420     }
421 
422     c->nblocks = 0;
423     ENGINE_ERROR_CODE ret = ENGINE_SUCCESS;
424     pthread_mutex_lock(&c->mutex);
425     while (ret == ENGINE_SUCCESS &&
426            (ret = me->the_engine->tap_notify((ENGINE_HANDLE*)me->the_engine, c,
427                                              engine_specific, nengine, ttl, tap_flags,
428                                              tap_event, tap_seqno, key, nkey, flags,
429                                              exptime, cas, data, ndata, vbucket)) == ENGINE_EWOULDBLOCK &&
430            c->handle_ewouldblock)
431     {
432         ++c->nblocks;
433         pthread_cond_wait(&c->cond, &c->mutex);
434         ret = c->status;
435     }
436     pthread_mutex_unlock(&c->mutex);
437 
438     if (c != cookie) {
439         destroy_mock_cookie(c);
440     }
441 
442     return ret;
443 }
444 
445 
mock_get_tap_iterator(ENGINE_HANDLE * handle,const void * cookie,const void * client,size_t nclient,uint32_t flags,const void * userdata,size_t nuserdata)446 static TAP_ITERATOR mock_get_tap_iterator(ENGINE_HANDLE* handle, const void* cookie,
447                                            const void* client, size_t nclient,
448                                            uint32_t flags,
449                                            const void* userdata, size_t nuserdata) {
450     struct mock_engine *me = get_handle(handle);
451     me->iterator = me->the_engine->get_tap_iterator((ENGINE_HANDLE*)me->the_engine, cookie,
452                                                     client, nclient, flags, userdata, nuserdata);
453     return (me->iterator != NULL) ? mock_tap_iterator : NULL;
454 }
455 
mock_errinfo(ENGINE_HANDLE * handle,const void * cookie,char * buffer,size_t buffsz)456 static size_t mock_errinfo(ENGINE_HANDLE *handle, const void* cookie,
457                            char *buffer, size_t buffsz) {
458     struct mock_engine *me = get_handle(handle);
459     return me->the_engine->errinfo((ENGINE_HANDLE*)me->the_engine, cookie,
460                                    buffer, buffsz);
461 }
462 
463 
464 struct mock_engine default_mock_engine = {
465     .me = {
466         .interface = {
467             .interface = 1
468         },
469         .get_info = mock_get_info,
470         .initialize = mock_initialize,
471         .destroy = mock_destroy,
472         .allocate = mock_allocate,
473         .remove = mock_remove,
474         .release = mock_release,
475         .get = mock_get,
476         .store = mock_store,
477         .arithmetic = mock_arithmetic,
478         .flush = mock_flush,
479         .get_stats = mock_get_stats,
480         .reset_stats = mock_reset_stats,
481         .get_stats_struct = mock_get_stats_struct,
482         .aggregate_stats = mock_aggregate_stats,
483         .unknown_command = mock_unknown_command,
484         .tap_notify = mock_tap_notify,
485         .get_tap_iterator = mock_get_tap_iterator,
486         .item_set_cas = mock_item_set_cas,
487         .get_item_info = mock_get_item_info,
488         .errinfo = mock_errinfo
489     }
490 };
491 struct mock_engine mock_engine;
492 
493 EXTENSION_LOGGER_DESCRIPTOR *logger_descriptor = NULL;
494 static ENGINE_HANDLE *handle = NULL;
495 static ENGINE_HANDLE_V1 *handle_v1 = NULL;
496 
usage(void)497 static void usage(void) {
498     printf("\n");
499     printf("engine_testapp -E <path_to_engine_lib> -T <path_to_testlib>\n");
500     printf("               [-e <engine_config>] [-h]\n");
501     printf("\n");
502     printf("-E <path_to_engine_lib>      Path to the engine library file. The\n");
503     printf("                             engine library file is a library file\n");
504     printf("                             (.so or .dll) that the contains the \n");
505     printf("                             implementation of the engine being\n");
506     printf("                             tested.\n");
507     printf("\n");
508     printf("-T <path_to_testlib>         Path to the test library file. The test\n");
509     printf("                             library file is a library file (.so or\n");
510     printf("                             .dll) that contains the set of tests\n");
511     printf("                             to be executed.\n");
512     printf("\n");
513     printf("-t <timeout>                 Maximum time to run a test.\n");
514     printf("-e <engine_config>           Engine configuration string passed to\n");
515     printf("                             the engine.\n");
516     printf("-q                           Only print errors.");
517     printf("-.                           Print a . for each executed test.");
518     printf("\n");
519     printf("-h                           Prints this usage text.\n");
520     printf("\n");
521 }
522 
report_test(const char * name,enum test_result r,bool quiet)523 static int report_test(const char *name, enum test_result r, bool quiet) {
524     int rc = 0;
525     char *msg = NULL;
526     bool color_enabled = getenv("TESTAPP_ENABLE_COLOR") != NULL;
527     int color = 0;
528     char color_str[8] = { 0 };
529     char *reset_color = "\033[m";
530     switch(r) {
531     case SUCCESS:
532         msg="OK";
533         color = 32;
534         break;
535     case SKIPPED:
536         msg="SKIPPED";
537         color = 32;
538         break;
539     case FAIL:
540         color = 31;
541         msg="FAIL";
542         rc = 1;
543         break;
544     case DIED:
545         color = 31;
546         msg = "DIED";
547         rc = 1;
548         break;
549     case TIMEOUT:
550         color = 31;
551         msg = "TIMED OUT";
552         rc = 1;
553         break;
554     case CORE:
555         color = 31;
556         msg = "CORE DUMPED";
557         rc = 1;
558         break;
559     case PENDING:
560         color = 33;
561         msg = "PENDING";
562         break;
563     }
564     assert(msg);
565     if (color_enabled) {
566         snprintf(color_str, sizeof(color_str), "\033[%dm", color);
567     }
568     if (quiet) {
569         if (r != SUCCESS) {
570             printf("%s:  %s%s%s\n", name, color_str, msg,
571                    color_enabled ? reset_color : "");
572             fflush(stdout);
573         }
574     } else {
575         printf("%s%s%s\n", color_str, msg, color_enabled ? reset_color : "");
576     }
577     return rc;
578 }
579 
start_your_engines(const char * engine,const char * cfg,bool engine_init)580 static ENGINE_HANDLE_V1 *start_your_engines(const char *engine, const char* cfg, bool engine_init) {
581 
582     init_mock_server(handle);
583     if (!load_engine(engine, &get_mock_server_api, logger_descriptor, &handle)) {
584         fprintf(stderr, "Failed to load engine %s.\n", engine);
585         return NULL;
586     }
587 
588     if (engine_init) {
589         if(!init_engine(handle, cfg, logger_descriptor)) {
590             fprintf(stderr, "Failed to init engine %s with config %s.\n", engine, cfg);
591             return NULL;
592         }
593     }
594 
595     mock_engine = default_mock_engine;
596     handle_v1 = mock_engine.the_engine = (ENGINE_HANDLE_V1*)handle;
597     handle = (ENGINE_HANDLE*)&mock_engine.me;
598     handle_v1 = &mock_engine.me;
599 
600     // Reset all members that aren't set (to allow the users to write
601     // testcases to verify that they initialize them..
602     assert(mock_engine.me.interface.interface == mock_engine.the_engine->interface.interface);
603 
604     if (mock_engine.the_engine->get_stats_struct == NULL) {
605         mock_engine.me.get_stats_struct = NULL;
606     }
607     if (mock_engine.the_engine->aggregate_stats == NULL) {
608         mock_engine.me.aggregate_stats = NULL;
609     }
610     if (mock_engine.the_engine->unknown_command == NULL) {
611         mock_engine.me.unknown_command = NULL;
612     }
613     if (mock_engine.the_engine->tap_notify == NULL) {
614         mock_engine.me.tap_notify = NULL;
615     }
616     if (mock_engine.the_engine->get_tap_iterator == NULL) {
617         mock_engine.me.get_tap_iterator = NULL;
618     }
619     if (mock_engine.the_engine->errinfo == NULL) {
620         mock_engine.me.errinfo = NULL;
621     }
622 
623     return &mock_engine.me;
624 }
625 
destroy_engine(bool force)626 static void destroy_engine(bool force) {
627     if (handle_v1) {
628         handle_v1->destroy(handle, force);
629         handle_v1 = NULL;
630         handle = NULL;
631     }
632 }
633 
reload_engine(ENGINE_HANDLE ** h,ENGINE_HANDLE_V1 ** h1,const char * engine,const char * cfg,bool init,bool force)634 static void reload_engine(ENGINE_HANDLE **h, ENGINE_HANDLE_V1 **h1,
635                           const char* engine, const char *cfg, bool init, bool force) {
636     destroy_engine(force);
637     handle_v1 = start_your_engines(engine, cfg, init);
638     handle = (ENGINE_HANDLE*)(handle_v1);
639     *h1 = handle_v1;
640     *h = handle;
641 }
642 
643 static engine_test_t* current_testcase;
644 
get_current_testcase(void)645 static const engine_test_t* get_current_testcase(void)
646 {
647     return current_testcase;
648 }
649 
650 
run_test(engine_test_t test,const char * engine,const char * default_cfg)651 static enum test_result run_test(engine_test_t test, const char *engine, const char *default_cfg) {
652     enum test_result ret = PENDING;
653     if (test.tfun != NULL) {
654 #if !defined(USE_GCOV) && !defined(WIN32)
655         pid_t pid = fork();
656         if (pid == 0) {
657 #endif
658             current_testcase = &test;
659             if (test.prepare != NULL) {
660                 if ((ret = test.prepare(&test)) == SUCCESS) {
661                     ret = PENDING;
662                 }
663             }
664 
665             if (ret == PENDING) {
666                 /* Start the engines and go */
667                 start_your_engines(engine, test.cfg ? test.cfg : default_cfg, true);
668                 if (test.test_setup != NULL) {
669                     if (!test.test_setup(handle, handle_v1)) {
670                         fprintf(stderr, "Failed to run setup for test %s\n", test.name);
671 #if !defined(USE_GCOV) && !defined(WIN32)
672                         exit((int)ret);
673 #else
674                         return FAIL;
675 #endif
676                     }
677                 }
678                 ret = test.tfun(handle, handle_v1);
679                 if (test.test_teardown != NULL) {
680                     if (!test.test_teardown(handle, handle_v1)) {
681                         fprintf(stderr, "WARNING: Failed to run teardown for test %s\n", test.name);
682                     }
683                 }
684                 destroy_engine(false);
685 
686                 if (test.cleanup) {
687                     test.cleanup(&test, ret);
688                 }
689             }
690 #if !defined(USE_GCOV) && !defined(WIN32)
691             exit((int)ret);
692         } else if (pid == (pid_t)-1) {
693             ret = FAIL;
694         } else {
695             int rc;
696             while (alarmed == 0 && waitpid(pid, &rc, 0) == (pid_t)-1) {
697                 if (errno != EINTR) {
698                     abort();
699                 }
700             }
701 
702             if (alarmed) {
703                 kill(pid, 9);
704                 ret = TIMEOUT;
705             } else if (WIFEXITED(rc)) {
706                 ret = (enum test_result)WEXITSTATUS(rc);
707             } else if (WIFSIGNALED(rc) && WCOREDUMP(rc)) {
708                 ret = CORE;
709             } else {
710                 ret = DIED;
711             }
712         }
713 #endif
714     }
715 
716     return ret;
717 }
718 
setup_alarm_handler()719 static void setup_alarm_handler() {
720 #ifndef WIN32
721     struct sigaction sig_handler;
722 
723     sig_handler.sa_handler = alarm_handler;
724     sig_handler.sa_flags = 0;
725 
726     sigaction(SIGALRM, &sig_handler, NULL);
727 #endif
728 }
729 
set_test_timeout(int timeout)730 static void set_test_timeout(int timeout) {
731 #ifndef WIN32
732     alarm(timeout);
733 #endif
734 }
735 
clear_test_timeout()736 static void clear_test_timeout() {
737 #ifndef WIN32
738     alarm(0);
739     alarmed = 0;
740 #endif
741 }
742 
main(int argc,char ** argv)743 int main(int argc, char **argv) {
744     int c, exitcode = 0, num_cases = 0, timeout = 0;
745     bool quiet = false;
746     bool dot = false;
747     const char *engine = NULL;
748     const char *engine_args = NULL;
749     const char *test_suite = NULL;
750     const char *test_case = NULL;
751     engine_test_t *testcases = NULL;
752     logger_descriptor = get_null_logger();
753 
754     /* Hack to remove the warning from C99 */
755     union {
756         GET_TESTS get_tests;
757         void* voidptr;
758     } my_get_test = {.get_tests = NULL };
759 
760     /* Hack to remove the warning from C99 */
761     union {
762         SETUP_SUITE setup_suite;
763         void* voidptr;
764     } my_setup_suite = {.setup_suite = NULL };
765 
766     /* Hack to remove the warning from C99 */
767     union {
768         TEARDOWN_SUITE teardown_suite;
769         void* voidptr;
770     } my_teardown_suite = {.teardown_suite = NULL };
771 
772 
773     /* Use unbuffered stdio */
774     setbuf(stdout, NULL);
775     setbuf(stderr, NULL);
776 
777     setup_alarm_handler();
778 
779     /* process arguments */
780     while (-1 != (c = getopt(argc, argv,
781           "h"  /* usage */
782           "E:" /* Engine to load */
783           "e:" /* Engine options */
784           "T:" /* Library with tests to load */
785           "t:" /* Timeout */
786           "q"  /* Be more quiet (only report failures) */
787           "."  /* dot mode. */
788           "n:"  /* test case to run */
789         ))) {
790         switch (c) {
791         case 'E':
792             engine = optarg;
793             break;
794         case 'e':
795             engine_args = optarg;
796             break;
797         case 'h':
798             usage();
799             return 0;
800         case 'T':
801             test_suite = optarg;
802             break;
803         case 't':
804             timeout = atoi(optarg);
805             break;
806         case 'n':
807             test_case = optarg;
808             break;
809         case 'q':
810             quiet = true;
811             break;
812         case '.':
813             dot = true;
814             break;
815         default:
816             fprintf(stderr, "Illegal argument \"%c\"\n", c);
817             return 1;
818         }
819     }
820 
821     //validate args
822     if (engine == NULL) {
823         fprintf(stderr, "You must provide a path to the storage engine library.\n");
824         return 1;
825     }
826 
827     if (test_suite == NULL) {
828         fprintf(stderr, "You must provide a path to the testsuite library.\n");
829         return 1;
830     }
831 
832     //load test_suite
833     void* handle = dlopen(test_suite, RTLD_NOW | RTLD_LOCAL);
834     if (handle == NULL) {
835         const char *msg = dlerror();
836         fprintf(stderr, "Failed to load testsuite %s: %s\n", test_suite, msg ? msg : "unknown error");
837         return 1;
838     }
839 
840     //get the test cases
841     void *symbol = dlsym(handle, "get_tests");
842     if (symbol == NULL) {
843         const char *msg = dlerror();
844         fprintf(stderr, "Could not find get_tests function in testsuite %s: %s\n", test_suite, msg ? msg : "unknown error");
845         return 1;
846     }
847     my_get_test.voidptr = symbol;
848     testcases = (*my_get_test.get_tests)();
849 
850     //set up the suite if needed
851     struct test_harness harness = { .default_engine_cfg = engine_args,
852                                     .engine_path = engine,
853                                     .reload_engine = reload_engine,
854                                     .start_engine = start_your_engines,
855                                     .create_cookie = create_mock_cookie,
856                                     .destroy_cookie = destroy_mock_cookie,
857                                     .set_ewouldblock_handling = mock_set_ewouldblock_handling,
858                                     .lock_cookie = lock_mock_cookie,
859                                     .unlock_cookie = unlock_mock_cookie,
860                                     .waitfor_cookie = waitfor_mock_cookie,
861                                     .time_travel = mock_time_travel,
862                                     .get_current_testcase = get_current_testcase };
863     symbol = dlsym(handle, "setup_suite");
864     if (symbol != NULL) {
865         my_setup_suite.voidptr = symbol;
866         if (!(*my_setup_suite.setup_suite)(&harness)) {
867             fprintf(stderr, "Failed to set up test suite %s \n", test_suite);
868             return 1;
869         }
870     }
871 
872 
873     for (num_cases = 0; testcases[num_cases].name; num_cases++) {
874         /* Just counting */
875     }
876 
877     if (!quiet) {
878         printf("1..%d\n", num_cases);
879     }
880 
881     int i;
882     bool need_newline = false;
883     for (i = 0; testcases[i].name; i++) {
884         if (test_case != NULL && strcmp(test_case, testcases[i].name) != 0)
885             continue;
886         if (!quiet) {
887             printf("Running %s... ", testcases[i].name);
888             fflush(stdout);
889         } else if(dot) {
890             printf(".");
891             need_newline = true;
892             /* Add a newline every few tests */
893             if ((i+1) % 70 == 0) {
894                 printf("\n");
895                 need_newline = false;
896             }
897         }
898         set_test_timeout(timeout);
899         exitcode += report_test(testcases[i].name,
900                                 run_test(testcases[i], engine, engine_args),
901                                 quiet);
902         clear_test_timeout();
903     }
904 
905     if (need_newline) {
906         printf("\n");
907     }
908 
909     //tear down the suite if needed
910     symbol = dlsym(handle, "teardown_suite");
911     if (symbol != NULL) {
912         my_teardown_suite.voidptr = symbol;
913         if (!(*my_teardown_suite.teardown_suite)()) {
914             fprintf(stderr, "Failed to teardown up test suite %s \n", test_suite);
915         }
916     }
917 
918     printf("# Passed %d of %d tests\n", num_cases - exitcode, num_cases);
919 
920     return exitcode;
921 }
922