1/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2#include "config.h"
3#include <errno.h>
4#include <stdarg.h>
5#include <stdio.h>
6#include <stdlib.h>
7#include <string.h>
8#include <unistd.h>
9#include <signal.h>
10#include <getopt.h>
11#include <time.h>
12#include <platform/platform.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
18struct mock_engine {
19    ENGINE_HANDLE_V1 me;
20    ENGINE_HANDLE_V1 *the_engine;
21    TAP_ITERATOR iterator;
22};
23
24static bool color_enabled;
25
26#ifndef WIN32
27static sig_atomic_t alarmed;
28
29static void alarm_handler(int sig) {
30    alarmed = 1;
31}
32#endif
33
34static struct mock_engine* get_handle(ENGINE_HANDLE* handle) {
35    return (struct mock_engine*)handle;
36}
37
38static tap_event_t mock_tap_iterator(ENGINE_HANDLE* handle,
39                                     const void *cookie, item **itm,
40                                     void **es, uint16_t *nes, uint8_t *ttl,
41                                     uint16_t *flags, uint32_t *seqno,
42                                     uint16_t *vbucket) {
43   struct mock_engine *me = get_handle(handle);
44   return me->iterator((ENGINE_HANDLE*)me->the_engine, cookie, itm, es, nes,
45                       ttl, flags, seqno, vbucket);
46}
47
48static const engine_info* mock_get_info(ENGINE_HANDLE* handle) {
49    struct mock_engine *me = get_handle(handle);
50    return me->the_engine->get_info((ENGINE_HANDLE*)me->the_engine);
51}
52
53static ENGINE_ERROR_CODE mock_initialize(ENGINE_HANDLE* handle,
54                                         const char* config_str) {
55    struct mock_engine *me = get_handle(handle);
56    return me->the_engine->initialize((ENGINE_HANDLE*)me->the_engine, config_str);
57}
58
59static void mock_destroy(ENGINE_HANDLE* handle, const bool force) {
60    struct mock_engine *me = get_handle(handle);
61    me->the_engine->destroy((ENGINE_HANDLE*)me->the_engine, force);
62}
63
64static ENGINE_ERROR_CODE mock_allocate(ENGINE_HANDLE* handle,
65                                       const void* cookie,
66                                       item **item,
67                                       const void* key,
68                                       const size_t nkey,
69                                       const size_t nbytes,
70                                       const int flags,
71                                       const rel_time_t exptime,
72                                       uint8_t datatype) {
73    struct mock_engine *me = get_handle(handle);
74    struct mock_connstruct *c = (void*)cookie;
75    ENGINE_ERROR_CODE ret = ENGINE_SUCCESS;
76
77    if (c == NULL) {
78        c = (void*)create_mock_cookie();
79    }
80
81    c->nblocks = 0;
82    cb_mutex_enter(&c->mutex);
83    while (ret == ENGINE_SUCCESS &&
84           (ret = me->the_engine->allocate((ENGINE_HANDLE*)me->the_engine, c,
85                                           item, key, nkey,
86                                           nbytes, flags,
87                                           exptime, datatype)) == ENGINE_EWOULDBLOCK &&
88           c->handle_ewouldblock)
89    {
90        ++c->nblocks;
91        cb_cond_wait(&c->cond, &c->mutex);
92        ret = c->status;
93    }
94    cb_mutex_exit(&c->mutex);
95
96    if (c != cookie) {
97        destroy_mock_cookie(c);
98    }
99
100    return ret;
101}
102
103static ENGINE_ERROR_CODE mock_remove(ENGINE_HANDLE* handle,
104                                     const void* cookie,
105                                     const void* key,
106                                     const size_t nkey,
107                                     uint64_t* cas,
108                                     uint16_t vbucket)
109{
110    struct mock_engine *me = get_handle(handle);
111    struct mock_connstruct *c = (void*)cookie;
112    ENGINE_ERROR_CODE ret = ENGINE_SUCCESS;
113
114    if (c == NULL) {
115        c = (void*)create_mock_cookie();
116    }
117
118    c->nblocks = 0;
119    cb_mutex_enter(&c->mutex);
120    while (ret == ENGINE_SUCCESS &&
121           (ret = me->the_engine->remove((ENGINE_HANDLE*)me->the_engine, c, key,
122                                         nkey, cas, vbucket)) == ENGINE_EWOULDBLOCK &&
123           c->handle_ewouldblock)
124    {
125        ++c->nblocks;
126        cb_cond_wait(&c->cond, &c->mutex);
127        ret = c->status;
128    }
129    cb_mutex_exit(&c->mutex);
130
131    if (c != cookie) {
132        destroy_mock_cookie(c);
133    }
134
135    return ret;
136}
137
138static void mock_release(ENGINE_HANDLE* handle,
139                         const void *cookie,
140                         item* item) {
141    struct mock_engine *me = get_handle(handle);
142    me->the_engine->release((ENGINE_HANDLE*)me->the_engine, cookie, item);
143}
144
145static ENGINE_ERROR_CODE mock_get(ENGINE_HANDLE* handle,
146                                  const void* cookie,
147                                  item** item,
148                                  const void* key,
149                                  const int nkey,
150                                  uint16_t vbucket) {
151    ENGINE_ERROR_CODE ret = ENGINE_SUCCESS;
152    struct mock_engine *me = get_handle(handle);
153    struct mock_connstruct *c = (void*)cookie;
154    if (c == NULL) {
155        c = (void*)create_mock_cookie();
156    }
157
158    c->nblocks = 0;
159    cb_mutex_enter(&c->mutex);
160    while (ret == ENGINE_SUCCESS &&
161           (ret = me->the_engine->get((ENGINE_HANDLE*)me->the_engine, c, item,
162                                      key, nkey, vbucket)) == ENGINE_EWOULDBLOCK &&
163           c->handle_ewouldblock)
164    {
165        ++c->nblocks;
166        cb_cond_wait(&c->cond, &c->mutex);
167        ret = c->status;
168    }
169    cb_mutex_exit(&c->mutex);
170
171    if (c != cookie) {
172        destroy_mock_cookie(c);
173    }
174
175    return ret;
176}
177
178static ENGINE_ERROR_CODE mock_get_stats(ENGINE_HANDLE* handle,
179                                        const void* cookie,
180                                        const char* stat_key,
181                                        int nkey,
182                                        ADD_STAT add_stat)
183{
184    ENGINE_ERROR_CODE ret = ENGINE_SUCCESS;
185    struct mock_engine *me = get_handle(handle);
186    struct mock_connstruct *c = (void*)cookie;
187    if (c == NULL) {
188        c = (void*)create_mock_cookie();
189    }
190
191    c->nblocks = 0;
192    cb_mutex_enter(&c->mutex);
193    while (ret == ENGINE_SUCCESS &&
194           (ret = me->the_engine->get_stats((ENGINE_HANDLE*)me->the_engine, c, stat_key,
195                                            nkey, add_stat)) == ENGINE_EWOULDBLOCK &&
196           c->handle_ewouldblock)
197    {
198        ++c->nblocks;
199        cb_cond_wait(&c->cond, &c->mutex);
200        ret = c->status;
201    }
202    cb_mutex_exit(&c->mutex);
203
204    if (c != cookie) {
205        destroy_mock_cookie(c);
206    }
207
208    return ret;
209}
210
211static ENGINE_ERROR_CODE mock_store(ENGINE_HANDLE* handle,
212                                    const void *cookie,
213                                    item* item,
214                                    uint64_t *cas,
215                                    ENGINE_STORE_OPERATION operation,
216                                    uint16_t vbucket) {
217    ENGINE_ERROR_CODE ret = ENGINE_SUCCESS;
218    struct mock_engine *me = get_handle(handle);
219    struct mock_connstruct *c = (void*)cookie;
220    if (c == NULL) {
221        c = (void*)create_mock_cookie();
222    }
223
224    c->nblocks = 0;
225    cb_mutex_enter(&c->mutex);
226    while (ret == ENGINE_SUCCESS &&
227           (ret = me->the_engine->store((ENGINE_HANDLE*)me->the_engine, c, item, cas,
228                                        operation, vbucket)) == ENGINE_EWOULDBLOCK &&
229           c->handle_ewouldblock)
230    {
231        ++c->nblocks;
232        cb_cond_wait(&c->cond, &c->mutex);
233        ret = c->status;
234    }
235    cb_mutex_exit(&c->mutex);
236
237    if (c != cookie) {
238        destroy_mock_cookie(c);
239    }
240
241    return ret;
242}
243
244static ENGINE_ERROR_CODE mock_arithmetic(ENGINE_HANDLE* handle,
245                                         const void* cookie,
246                                         const void* key,
247                                         const int nkey,
248                                         const bool increment,
249                                         const bool create,
250                                         const uint64_t delta,
251                                         const uint64_t initial,
252                                         const rel_time_t exptime,
253                                         uint64_t *cas,
254                                         uint8_t datatype,
255                                         uint64_t *result,
256                                         uint16_t vbucket) {
257    ENGINE_ERROR_CODE ret = ENGINE_SUCCESS;
258    struct mock_engine *me = get_handle(handle);
259    struct mock_connstruct *c = (void*)cookie;
260    if (c == NULL) {
261        c = (void*)create_mock_cookie();
262    }
263
264    c->nblocks = 0;
265    cb_mutex_enter(&c->mutex);
266    while (ret == ENGINE_SUCCESS &&
267           (ret = me->the_engine->arithmetic((ENGINE_HANDLE*)me->the_engine, c, key,
268                                             nkey, increment, create,
269                                             delta, initial, exptime,
270                                             cas, datatype,
271                                             result, vbucket)) == ENGINE_EWOULDBLOCK &&
272           c->handle_ewouldblock)
273    {
274        ++c->nblocks;
275        cb_cond_wait(&c->cond, &c->mutex);
276        ret = c->status;
277    }
278    cb_mutex_exit(&c->mutex);
279
280    if (c != cookie) {
281        destroy_mock_cookie(c);
282    }
283
284    return ret;
285}
286
287static ENGINE_ERROR_CODE mock_flush(ENGINE_HANDLE* handle,
288                                    const void* cookie, time_t when) {
289    ENGINE_ERROR_CODE ret = ENGINE_SUCCESS;
290    struct mock_engine *me = get_handle(handle);
291    struct mock_connstruct *c = (void*)cookie;
292    if (c == NULL) {
293        c = (void*)create_mock_cookie();
294    }
295
296    c->nblocks = 0;
297    cb_mutex_enter(&c->mutex);
298    while (ret == ENGINE_SUCCESS &&
299           (ret = me->the_engine->flush((ENGINE_HANDLE*)me->the_engine, c, when)) == ENGINE_EWOULDBLOCK &&
300           c->handle_ewouldblock)
301    {
302        ++c->nblocks;
303        cb_cond_wait(&c->cond, &c->mutex);
304        ret = c->status;
305    }
306    cb_mutex_exit(&c->mutex);
307
308    if (c != cookie) {
309        destroy_mock_cookie(c);
310    }
311
312    return ret;
313}
314
315static void mock_reset_stats(ENGINE_HANDLE* handle, const void *cookie) {
316    struct mock_engine *me = get_handle(handle);
317    me->the_engine->reset_stats((ENGINE_HANDLE*)me->the_engine, cookie);
318}
319
320static ENGINE_ERROR_CODE mock_unknown_command(ENGINE_HANDLE* handle,
321                                              const void* cookie,
322                                              protocol_binary_request_header *request,
323                                              ADD_RESPONSE response)
324{
325    ENGINE_ERROR_CODE ret = ENGINE_SUCCESS;
326    struct mock_engine *me = get_handle(handle);
327    struct mock_connstruct *c = (void*)cookie;
328    if (c == NULL) {
329        c = (void*)create_mock_cookie();
330    }
331
332    c->nblocks = 0;
333    cb_mutex_enter(&c->mutex);
334    while (ret == ENGINE_SUCCESS &&
335           (ret = me->the_engine->unknown_command((ENGINE_HANDLE*)me->the_engine, c,
336                                                  request, response)) == ENGINE_EWOULDBLOCK &&
337           c->handle_ewouldblock)
338    {
339        ++c->nblocks;
340        cb_cond_wait(&c->cond, &c->mutex);
341        ret = c->status;
342    }
343    cb_mutex_exit(&c->mutex);
344
345    if (c != cookie) {
346        destroy_mock_cookie(c);
347    }
348
349    return ret;
350}
351
352static void mock_item_set_cas(ENGINE_HANDLE *handle, const void *cookie,
353                              item* item, uint64_t val)
354{
355    struct mock_engine *me = get_handle(handle);
356    me->the_engine->item_set_cas((ENGINE_HANDLE*)me->the_engine, cookie, item, val);
357}
358
359
360static bool mock_get_item_info(ENGINE_HANDLE *handle, const void *cookie,
361                               const item* item, item_info *item_info)
362{
363    struct mock_engine *me = get_handle(handle);
364    return me->the_engine->get_item_info((ENGINE_HANDLE*)me->the_engine,
365                                         cookie, item, item_info);
366}
367
368static void *mock_get_stats_struct(ENGINE_HANDLE* handle, const void* cookie)
369{
370    struct mock_engine *me = get_handle(handle);
371    return me->the_engine->get_stats_struct((ENGINE_HANDLE*)me->the_engine, cookie);
372}
373
374static ENGINE_ERROR_CODE mock_aggregate_stats(ENGINE_HANDLE* handle,
375                                              const void* cookie,
376                                              void (*callback)(void*, void*),
377                                              void *vptr)
378{
379    ENGINE_ERROR_CODE ret = ENGINE_SUCCESS;
380    struct mock_engine *me = get_handle(handle);
381    struct mock_connstruct *c = (void*)cookie;
382    if (c == NULL) {
383        c = (void*)create_mock_cookie();
384    }
385
386    c->nblocks = 0;
387    cb_mutex_enter(&c->mutex);
388    while (ret == ENGINE_SUCCESS &&
389           (ret = me->the_engine->aggregate_stats((ENGINE_HANDLE*)me->the_engine, c,
390                                                  callback, vptr)) == ENGINE_EWOULDBLOCK &&
391           c->handle_ewouldblock)
392    {
393        ++c->nblocks;
394        cb_cond_wait(&c->cond, &c->mutex);
395        ret = c->status;
396    }
397    cb_mutex_exit(&c->mutex);
398
399    if (c != cookie) {
400        destroy_mock_cookie(c);
401    }
402
403    return ret;
404}
405
406static ENGINE_ERROR_CODE mock_tap_notify(ENGINE_HANDLE* handle,
407                                        const void *cookie,
408                                        void *engine_specific,
409                                        uint16_t nengine,
410                                        uint8_t ttl,
411                                        uint16_t tap_flags,
412                                        tap_event_t tap_event,
413                                        uint32_t tap_seqno,
414                                        const void *key,
415                                        size_t nkey,
416                                        uint32_t flags,
417                                        uint32_t exptime,
418                                        uint64_t cas,
419                                        uint8_t datatype,
420                                        const void *data,
421                                        size_t ndata,
422                                        uint16_t vbucket) {
423
424    ENGINE_ERROR_CODE ret = ENGINE_SUCCESS;
425    struct mock_engine *me = get_handle(handle);
426    struct mock_connstruct *c = (void*)cookie;
427    if (c == NULL) {
428        c = (void*)create_mock_cookie();
429    }
430
431    c->nblocks = 0;
432    cb_mutex_enter(&c->mutex);
433    while (ret == ENGINE_SUCCESS &&
434           (ret = me->the_engine->tap_notify((ENGINE_HANDLE*)me->the_engine, c,
435                                             engine_specific, nengine, ttl, tap_flags,
436                                             tap_event, tap_seqno, key, nkey, flags,
437                                             exptime, cas, datatype,
438                                             data, ndata, vbucket)) == ENGINE_EWOULDBLOCK &&
439           c->handle_ewouldblock)
440    {
441        ++c->nblocks;
442        cb_cond_wait(&c->cond, &c->mutex);
443        ret = c->status;
444    }
445    cb_mutex_exit(&c->mutex);
446
447    if (c != cookie) {
448        destroy_mock_cookie(c);
449    }
450
451    return ret;
452}
453
454
455static TAP_ITERATOR mock_get_tap_iterator(ENGINE_HANDLE* handle, const void* cookie,
456                                           const void* client, size_t nclient,
457                                           uint32_t flags,
458                                           const void* userdata, size_t nuserdata) {
459    struct mock_engine *me = get_handle(handle);
460    me->iterator = me->the_engine->get_tap_iterator((ENGINE_HANDLE*)me->the_engine, cookie,
461                                                    client, nclient, flags, userdata, nuserdata);
462    return (me->iterator != NULL) ? mock_tap_iterator : NULL;
463}
464
465static size_t mock_errinfo(ENGINE_HANDLE *handle, const void* cookie,
466                           char *buffer, size_t buffsz) {
467    struct mock_engine *me = get_handle(handle);
468    return me->the_engine->errinfo((ENGINE_HANDLE*)me->the_engine, cookie,
469                                   buffer, buffsz);
470}
471
472static ENGINE_ERROR_CODE mock_dcp_step(ENGINE_HANDLE* handle,
473                                       const void* cookie,
474                                       struct dcp_message_producers *producers) {
475    struct mock_engine *me = get_handle(handle);
476    return me->the_engine->dcp.step((ENGINE_HANDLE*)me->the_engine, cookie,
477                                    producers);
478}
479
480static ENGINE_ERROR_CODE mock_dcp_open(ENGINE_HANDLE* handle,
481                                       const void* cookie,
482                                       uint32_t opaque,
483                                       uint32_t seqno,
484                                       uint32_t flags,
485                                       void *name,
486                                       uint16_t nname) {
487    struct mock_engine *me = get_handle(handle);
488    return me->the_engine->dcp.open((ENGINE_HANDLE*)me->the_engine, cookie,
489                                    opaque, seqno, flags, name, nname);
490}
491
492static ENGINE_ERROR_CODE mock_dcp_add_stream(ENGINE_HANDLE* handle,
493                                             const void* cookie,
494                                             uint32_t opaque,
495                                             uint16_t vbucket,
496                                             uint32_t flags) {
497
498    struct mock_engine *me = get_handle(handle);
499    struct mock_connstruct *c = (void*)cookie;
500    ENGINE_ERROR_CODE ret = ENGINE_SUCCESS;
501
502    if (c == NULL) {
503        c = (void*)create_mock_cookie();
504    }
505
506    c->nblocks = 0;
507    cb_mutex_enter(&c->mutex);
508    while (ret == ENGINE_SUCCESS &&
509           (ret = me->the_engine->dcp.add_stream((ENGINE_HANDLE*)me->the_engine, c,
510                                                 opaque, vbucket, flags))
511                                                == ENGINE_EWOULDBLOCK &&
512           c->handle_ewouldblock)
513        {
514            ++c->nblocks;
515            cb_cond_wait(&c->cond, &c->mutex);
516            ret = c->status;
517        }
518    cb_mutex_exit(&c->mutex);
519
520    if (c != cookie) {
521        destroy_mock_cookie(c);
522    }
523
524    return ret;
525}
526
527static ENGINE_ERROR_CODE mock_dcp_close_stream(ENGINE_HANDLE* handle,
528                                               const void* cookie,
529                                               uint32_t opaque,
530                                               uint16_t vbucket) {
531    struct mock_engine *me = get_handle(handle);
532    return me->the_engine->dcp.close_stream((ENGINE_HANDLE*)me->the_engine,
533                                            cookie, opaque, vbucket);
534}
535
536static ENGINE_ERROR_CODE mock_dcp_stream_req(ENGINE_HANDLE* handle,
537                                             const void* cookie,
538                                             uint32_t flags,
539                                             uint32_t opaque,
540                                             uint16_t vbucket,
541                                             uint64_t start_seqno,
542                                             uint64_t end_seqno,
543                                             uint64_t vbucket_uuid,
544                                             uint64_t snap_start_seqno,
545                                             uint64_t snap_end_seqno,
546                                             uint64_t *rollback_seqno,
547                                             dcp_add_failover_log callback) {
548    struct mock_engine *me = get_handle(handle);
549    return me->the_engine->dcp.stream_req((ENGINE_HANDLE*)me->the_engine,
550                                          cookie, flags, opaque, vbucket,
551                                          start_seqno, end_seqno, vbucket_uuid,
552                                          snap_start_seqno, snap_end_seqno,
553                                          rollback_seqno, callback);
554}
555
556static ENGINE_ERROR_CODE mock_dcp_get_failover_log(ENGINE_HANDLE* handle,
557                                                   const void* cookie,
558                                                   uint32_t opaque,
559                                                   uint16_t vbucket,
560                                                   dcp_add_failover_log cb) {
561    struct mock_engine *me = get_handle(handle);
562    return me->the_engine->dcp.get_failover_log((ENGINE_HANDLE*)me->the_engine,
563                                                cookie, opaque, vbucket, cb);
564}
565
566static ENGINE_ERROR_CODE mock_dcp_stream_end(ENGINE_HANDLE* handle,
567                                             const void* cookie,
568                                             uint32_t opaque,
569                                             uint16_t vbucket,
570                                             uint32_t flags) {
571    struct mock_engine *me = get_handle(handle);
572    return me->the_engine->dcp.stream_end((ENGINE_HANDLE*)me->the_engine,
573                                          cookie, opaque, vbucket, flags);
574}
575
576static ENGINE_ERROR_CODE mock_dcp_snapshot_marker(ENGINE_HANDLE* handle,
577                                                  const void* cookie,
578                                                  uint32_t opaque,
579                                                  uint16_t vbucket,
580                                                  uint64_t start_seqno,
581                                                  uint64_t end_seqno,
582                                                  uint32_t flags) {
583    struct mock_engine *me = get_handle(handle);
584    return me->the_engine->dcp.snapshot_marker((ENGINE_HANDLE*)me->the_engine,
585                                               cookie, opaque, vbucket,
586                                               start_seqno, end_seqno, flags);
587}
588
589static ENGINE_ERROR_CODE mock_dcp_mutation(ENGINE_HANDLE* handle,
590                                           const void* cookie,
591                                           uint32_t opaque,
592                                           const void *key,
593                                           uint16_t nkey,
594                                           const void *value,
595                                           uint32_t nvalue,
596                                           uint64_t cas,
597                                           uint16_t vbucket,
598                                           uint32_t flags,
599                                           uint8_t datatype,
600                                           uint64_t bySeqno,
601                                           uint64_t revSeqno,
602                                           uint32_t expiration,
603                                           uint32_t lockTime,
604                                           const void *meta,
605                                           uint16_t nmeta,
606                                           uint8_t nru) {
607
608    struct mock_engine *me = get_handle(handle);
609    struct mock_connstruct *c = (void*)cookie;
610    ENGINE_ERROR_CODE ret = ENGINE_SUCCESS;
611    if (c == NULL) {
612        c = (void*)create_mock_cookie();
613    }
614
615    c->nblocks = 0;
616    cb_mutex_enter(&c->mutex);
617    while (ret == ENGINE_SUCCESS &&
618           (ret = me->the_engine->dcp.mutation((ENGINE_HANDLE*)me->the_engine, c, opaque, key,
619                                               nkey, value, nvalue, cas, vbucket, flags,
620                                               datatype, bySeqno, revSeqno, expiration,
621                                               lockTime, meta, nmeta, nru)) == ENGINE_EWOULDBLOCK &&
622           c->handle_ewouldblock)
623        {
624            ++c->nblocks;
625            cb_cond_wait(&c->cond, &c->mutex);
626            ret = c->status;
627        }
628    cb_mutex_exit(&c->mutex);
629
630    if (c != cookie) {
631        destroy_mock_cookie(c);
632    }
633
634    return ret;
635}
636
637static ENGINE_ERROR_CODE mock_dcp_deletion(ENGINE_HANDLE* handle,
638                                           const void* cookie,
639                                           uint32_t opaque,
640                                           const void *key,
641                                           uint16_t nkey,
642                                           uint64_t cas,
643                                           uint16_t vbucket,
644                                           uint64_t bySeqno,
645                                           uint64_t revSeqno,
646                                           const void *meta,
647                                           uint16_t nmeta) {
648
649    struct mock_engine *me = get_handle(handle);
650    struct mock_connstruct *c = (void*)cookie;
651    ENGINE_ERROR_CODE ret = ENGINE_SUCCESS;
652    if (c == NULL) {
653        c = (void*)create_mock_cookie();
654    }
655
656    c->nblocks = 0;
657    cb_mutex_enter(&c->mutex);
658    while (ret == ENGINE_SUCCESS &&
659           (ret = me->the_engine->dcp.deletion((ENGINE_HANDLE*)me->the_engine, c,
660                                               opaque, key, nkey, cas, vbucket, bySeqno,
661                                               revSeqno, meta, nmeta)) == ENGINE_EWOULDBLOCK &&
662           c->handle_ewouldblock)
663        {
664            ++c->nblocks;
665            cb_cond_wait(&c->cond, &c->mutex);
666            ret = c->status;
667        }
668    cb_mutex_exit(&c->mutex);
669
670    if (c != cookie) {
671        destroy_mock_cookie(c);
672    }
673
674    return ret;
675}
676
677static ENGINE_ERROR_CODE mock_dcp_expiration(ENGINE_HANDLE* handle,
678                                             const void* cookie,
679                                             uint32_t opaque,
680                                             const void *key,
681                                             uint16_t nkey,
682                                             uint64_t cas,
683                                             uint16_t vbucket,
684                                             uint64_t bySeqno,
685                                             uint64_t revSeqno,
686                                             const void *meta,
687                                             uint16_t nmeta) {
688
689    struct mock_engine *me = get_handle(handle);
690    struct mock_connstruct *c = (void*)cookie;
691    ENGINE_ERROR_CODE ret = ENGINE_SUCCESS;
692    if (c == NULL) {
693        c = (void*)create_mock_cookie();
694    }
695
696    c->nblocks = 0;
697    cb_mutex_enter(&c->mutex);
698    while (ret == ENGINE_SUCCESS &&
699           (ret = me->the_engine->dcp.expiration((ENGINE_HANDLE*)me->the_engine, c,
700                                                 opaque, key, nkey, cas, vbucket, bySeqno,
701                                                 revSeqno, meta, nmeta)) == ENGINE_EWOULDBLOCK &&
702           c->handle_ewouldblock)
703        {
704            ++c->nblocks;
705            cb_cond_wait(&c->cond, &c->mutex);
706            ret = c->status;
707        }
708    cb_mutex_exit(&c->mutex);
709
710    if (c != cookie) {
711        destroy_mock_cookie(c);
712    }
713
714    return ret;
715}
716
717static ENGINE_ERROR_CODE mock_dcp_flush(ENGINE_HANDLE* handle,
718                                        const void* cookie,
719                                        uint32_t opaque,
720                                        uint16_t vbucket) {
721
722    struct mock_engine *me = get_handle(handle);
723    struct mock_connstruct *c = (void*)cookie;
724    ENGINE_ERROR_CODE ret = ENGINE_SUCCESS;
725    if (c == NULL) {
726        c = (void*)create_mock_cookie();
727    }
728
729    c->nblocks = 0;
730    cb_mutex_enter(&c->mutex);
731    while (ret == ENGINE_SUCCESS &&
732           (ret = me->the_engine->dcp.flush((ENGINE_HANDLE*)me->the_engine, c, opaque,
733                                            vbucket)) == ENGINE_EWOULDBLOCK &&
734           c->handle_ewouldblock)
735        {
736            ++c->nblocks;
737            cb_cond_wait(&c->cond, &c->mutex);
738            ret = c->status;
739        }
740    cb_mutex_exit(&c->mutex);
741
742    if (c != cookie) {
743        destroy_mock_cookie(c);
744    }
745
746    return ret;
747}
748
749static ENGINE_ERROR_CODE mock_dcp_set_vbucket_state(ENGINE_HANDLE* handle,
750                                                    const void* cookie,
751                                                    uint32_t opaque,
752                                                    uint16_t vbucket,
753                                                    vbucket_state_t state) {
754    struct mock_engine *me = get_handle(handle);
755    return me->the_engine->dcp.set_vbucket_state((ENGINE_HANDLE*)me->the_engine,
756                                                 cookie, opaque, vbucket,
757                                                 state);
758}
759
760static ENGINE_ERROR_CODE mock_dcp_noop(ENGINE_HANDLE* handle,
761                                       const void* cookie,
762                                       uint32_t opaque) {
763    struct mock_engine *me = get_handle(handle);
764    return me->the_engine->dcp.noop((ENGINE_HANDLE*)me->the_engine,
765                                    cookie, opaque);
766}
767
768static ENGINE_ERROR_CODE mock_dcp_control(ENGINE_HANDLE* handle,
769                                          const void* cookie,
770                                          uint32_t opaque,
771                                          const void *key,
772                                          uint16_t nkey,
773                                          const void *value,
774                                          uint32_t nvalue) {
775
776    struct mock_engine *me = get_handle(handle);
777    return me->the_engine->dcp.control((void*)me->the_engine,
778                                       cookie, opaque, key,
779                                       nkey, value, nvalue);
780}
781
782static ENGINE_ERROR_CODE mock_dcp_buffer_acknowledgement(ENGINE_HANDLE* handle,
783                                                         const void* cookie,
784                                                         uint32_t opaque,
785                                                         uint16_t vbucket,
786                                                         uint32_t bb) {
787    struct mock_engine *me = get_handle(handle);
788    return me->the_engine->dcp.buffer_acknowledgement((ENGINE_HANDLE*)me->the_engine,
789                                                      cookie, opaque, vbucket, bb);
790}
791
792static ENGINE_ERROR_CODE mock_dcp_response_handler(ENGINE_HANDLE* handle,
793                                                   const void* cookie,
794                                                   protocol_binary_response_header *response) {
795    struct mock_engine *me = get_handle(handle);
796    return me->the_engine->dcp.response_handler((ENGINE_HANDLE*)me->the_engine,
797                                                cookie, response);
798}
799
800struct mock_engine mock_engine;
801
802EXTENSION_LOGGER_DESCRIPTOR *logger_descriptor = NULL;
803static ENGINE_HANDLE *handle = NULL;
804static ENGINE_HANDLE_V1 *handle_v1 = NULL;
805
806static void usage(void) {
807    printf("\n");
808    printf("engine_testapp -E <path_to_engine_lib> -T <path_to_testlib>\n");
809    printf("               [-e <engine_config>] [-h]\n");
810    printf("\n");
811    printf("-E <path_to_engine_lib>      Path to the engine library file. The\n");
812    printf("                             engine library file is a library file\n");
813    printf("                             (.so or .dll) that the contains the \n");
814    printf("                             implementation of the engine being\n");
815    printf("                             tested.\n");
816    printf("\n");
817    printf("-T <path_to_testlib>         Path to the test library file. The test\n");
818    printf("                             library file is a library file (.so or\n");
819    printf("                             .dll) that contains the set of tests\n");
820    printf("                             to be executed.\n");
821    printf("\n");
822    printf("-t <timeout>                 Maximum time to run a test.\n");
823    printf("-e <engine_config>           Engine configuration string passed to\n");
824    printf("                             the engine.\n");
825    printf("-q                           Only print errors.");
826    printf("-.                           Print a . for each executed test.");
827    printf("\n");
828    printf("-h                           Prints this usage text.\n");
829    printf("-v                           verbose output\n");
830    printf("\n");
831}
832
833static int report_test(const char *name, time_t duration, enum test_result r, bool quiet, bool compact) {
834    int rc = 0;
835    char *msg = NULL;
836    int color = 0;
837    char color_str[8] = { 0 };
838    const char *reset_color = color_enabled ? "\033[m" : "";
839
840    switch (r) {
841    case SUCCESS:
842        msg="OK";
843        color = 32;
844        break;
845    case SKIPPED:
846        msg="SKIPPED";
847        color = 32;
848        break;
849    case FAIL:
850        color = 31;
851        msg="FAIL";
852        rc = 1;
853        break;
854    case DIED:
855        color = 31;
856        msg = "DIED";
857        rc = 1;
858        break;
859    case TIMEOUT:
860        color = 31;
861        msg = "TIMED OUT";
862        rc = 1;
863        break;
864    case CORE:
865        color = 31;
866        msg = "CORE DUMPED";
867        rc = 1;
868        break;
869    case PENDING:
870        color = 33;
871        msg = "PENDING";
872        break;
873    default:
874        color = 31;
875        msg = "UNKNOWN";
876        rc = 1;
877    }
878
879    cb_assert(msg);
880    if (color_enabled) {
881        snprintf(color_str, sizeof(color_str), "\033[%dm", color);
882    }
883
884    if (quiet) {
885        if (r != SUCCESS) {
886            printf("%s:  (%lu sec) %s%s%s\n", name, (long)duration,
887                   color_str, msg, reset_color);
888            fflush(stdout);
889        }
890    } else {
891        if (compact && (r == SUCCESS || r == SKIPPED || r == PENDING)) {
892            size_t len = strlen(name) + 27; /* for "Running [0/0] xxxx ..." etc */
893            size_t ii;
894
895            fprintf(stdout, "\r");
896            for (ii = 0; ii < len; ++ii) {
897                fprintf(stdout, " ");
898            }
899            fprintf(stdout, "\r");
900            fflush(stdout);
901        } else {
902            printf("(%lu sec) %s%s%s\n", (long)duration,
903                                         color_str, msg, reset_color);
904        }
905    }
906    return rc;
907}
908
909static ENGINE_HANDLE_V1 *start_your_engines(const char *engine, const char* cfg, bool engine_init) {
910
911    init_mock_server(handle);
912    if (!load_engine(engine, &get_mock_server_api, logger_descriptor, &handle)) {
913        fprintf(stderr, "Failed to load engine %s.\n", engine);
914        return NULL;
915    }
916
917    if (engine_init) {
918        if(!init_engine(handle, cfg, logger_descriptor)) {
919            fprintf(stderr, "Failed to init engine %s with config %s.\n", engine, cfg);
920            return NULL;
921        }
922    }
923
924    memset(&mock_engine, 0, sizeof(mock_engine));
925    mock_engine.me.interface.interface = 1;
926
927    mock_engine.me.get_info = mock_get_info;
928    mock_engine.me.initialize = mock_initialize;
929    mock_engine.me.destroy = mock_destroy;
930    mock_engine.me.allocate = mock_allocate;
931    mock_engine.me.remove = mock_remove;
932    mock_engine.me.release = mock_release;
933    mock_engine.me.get = mock_get;
934    mock_engine.me.store = mock_store;
935    mock_engine.me.arithmetic = mock_arithmetic;
936    mock_engine.me.flush = mock_flush;
937    mock_engine.me.get_stats = mock_get_stats;
938    mock_engine.me.reset_stats = mock_reset_stats;
939    mock_engine.me.get_stats_struct = mock_get_stats_struct;
940    mock_engine.me.aggregate_stats = mock_aggregate_stats;
941    mock_engine.me.unknown_command = mock_unknown_command;
942    mock_engine.me.tap_notify = mock_tap_notify;
943    mock_engine.me.get_tap_iterator = mock_get_tap_iterator;
944    mock_engine.me.item_set_cas = mock_item_set_cas;
945    mock_engine.me.get_item_info = mock_get_item_info;
946    mock_engine.me.errinfo = mock_errinfo;
947    mock_engine.me.dcp.step = mock_dcp_step;
948    mock_engine.me.dcp.open = mock_dcp_open;
949    mock_engine.me.dcp.add_stream = mock_dcp_add_stream;
950    mock_engine.me.dcp.close_stream = mock_dcp_close_stream;
951    mock_engine.me.dcp.stream_req = mock_dcp_stream_req;
952    mock_engine.me.dcp.get_failover_log = mock_dcp_get_failover_log;
953    mock_engine.me.dcp.stream_end = mock_dcp_stream_end;
954    mock_engine.me.dcp.snapshot_marker = mock_dcp_snapshot_marker;
955    mock_engine.me.dcp.mutation = mock_dcp_mutation;
956    mock_engine.me.dcp.deletion = mock_dcp_deletion;
957    mock_engine.me.dcp.expiration = mock_dcp_expiration;
958    mock_engine.me.dcp.flush = mock_dcp_flush;
959    mock_engine.me.dcp.set_vbucket_state = mock_dcp_set_vbucket_state;
960    mock_engine.me.dcp.noop = mock_dcp_noop;
961    mock_engine.me.dcp.buffer_acknowledgement = mock_dcp_buffer_acknowledgement;
962    mock_engine.me.dcp.control = mock_dcp_control;
963    mock_engine.me.dcp.response_handler = mock_dcp_response_handler;
964
965    handle_v1 = mock_engine.the_engine = (ENGINE_HANDLE_V1*)handle;
966    handle = (ENGINE_HANDLE*)&mock_engine.me;
967    handle_v1 = &mock_engine.me;
968
969    /* Reset all members that aren't set (to allow the users to write */
970    /* testcases to verify that they initialize them.. */
971    cb_assert(mock_engine.me.interface.interface == mock_engine.the_engine->interface.interface);
972
973    if (mock_engine.the_engine->get_stats_struct == NULL) {
974        mock_engine.me.get_stats_struct = NULL;
975    }
976    if (mock_engine.the_engine->aggregate_stats == NULL) {
977        mock_engine.me.aggregate_stats = NULL;
978    }
979    if (mock_engine.the_engine->unknown_command == NULL) {
980        mock_engine.me.unknown_command = NULL;
981    }
982    if (mock_engine.the_engine->tap_notify == NULL) {
983        mock_engine.me.tap_notify = NULL;
984    }
985    if (mock_engine.the_engine->get_tap_iterator == NULL) {
986        mock_engine.me.get_tap_iterator = NULL;
987    }
988    if (mock_engine.the_engine->errinfo == NULL) {
989        mock_engine.me.errinfo = NULL;
990    }
991
992    return &mock_engine.me;
993}
994
995static void destroy_engine(bool force) {
996    destroy_mock_event_callbacks();
997    if (handle_v1) {
998        handle_v1->destroy(handle, force);
999        handle_v1 = NULL;
1000        handle = NULL;
1001    }
1002}
1003
1004static void reload_engine(ENGINE_HANDLE **h, ENGINE_HANDLE_V1 **h1,
1005                          const char* engine, const char *cfg, bool init, bool force) {
1006    destroy_engine(force);
1007    handle_v1 = start_your_engines(engine, cfg, init);
1008    handle = (ENGINE_HANDLE*)(handle_v1);
1009    *h1 = handle_v1;
1010    *h = handle;
1011}
1012
1013static engine_test_t* current_testcase;
1014
1015static const engine_test_t* get_current_testcase(void)
1016{
1017    return current_testcase;
1018}
1019
1020static int execute_test(engine_test_t test,
1021                        const char *engine,
1022                        const char *default_cfg)
1023{
1024    enum test_result ret = PENDING;
1025    if (test.tfun != NULL) {
1026        current_testcase = &test;
1027        if (test.prepare != NULL) {
1028            if ((ret = test.prepare(&test)) == SUCCESS) {
1029                ret = PENDING;
1030            }
1031        }
1032
1033        if (ret == PENDING) {
1034            /* Start the engines and go */
1035            start_your_engines(engine, test.cfg ? test.cfg : default_cfg, true);
1036            if (test.test_setup != NULL) {
1037                if (!test.test_setup(handle, handle_v1)) {
1038                    fprintf(stderr, "Failed to run setup for test %s\n", test.name);
1039                    return FAIL;
1040                }
1041            }
1042            ret = test.tfun(handle, handle_v1);
1043            if (test.test_teardown != NULL) {
1044                if (!test.test_teardown(handle, handle_v1)) {
1045                    fprintf(stderr, "WARNING: Failed to run teardown for test %s\n", test.name);
1046                }
1047            }
1048            destroy_engine(false);
1049
1050            if (test.cleanup) {
1051                test.cleanup(&test, ret);
1052            }
1053        }
1054    }
1055
1056    return (int)ret;
1057}
1058
1059static void setup_alarm_handler() {
1060#ifndef WIN32
1061    struct sigaction sig_handler;
1062
1063    sig_handler.sa_handler = alarm_handler;
1064    sig_handler.sa_flags = 0;
1065    sigemptyset(&sig_handler.sa_mask);
1066
1067    sigaction(SIGALRM, &sig_handler, NULL);
1068#endif
1069}
1070
1071static void set_test_timeout(int timeout) {
1072#ifndef WIN32
1073    alarm(timeout);
1074#endif
1075}
1076
1077static void clear_test_timeout() {
1078#ifndef WIN32
1079    alarm(0);
1080    alarmed = 0;
1081#endif
1082}
1083
1084static int safe_append(char *buffer, const char *txt) {
1085    int len = 0;
1086
1087    /*
1088     * We should probably make this a bit safer (by
1089     * checking if its already escaped etc, but I'll
1090     * do that whenever it turns out to be a problem ;-)
1091     */
1092    while (*txt) {
1093        switch (*txt) {
1094#ifndef WIN32
1095        case ' ':
1096        case '\\':
1097        case ';':
1098        case '|':
1099        case '&':
1100            buffer[len++] = '\\';
1101#endif
1102        default:
1103            buffer[len++] = *txt;
1104        }
1105        ++txt;
1106    }
1107
1108    buffer[len++] = ' ';
1109
1110    return len;
1111}
1112
1113int main(int argc, char **argv) {
1114    int c, exitcode = 0, num_cases = 0, timeout = 0, loop_count = 0;
1115    bool verbose = false;
1116    bool quiet = false;
1117    bool dot = false;
1118    bool loop = false;
1119    bool terminate_on_error = false;
1120    const char *engine = NULL;
1121    const char *engine_args = NULL;
1122    const char *test_suite = NULL;
1123    const char *test_case = NULL;
1124    engine_test_t *testcases = NULL;
1125    cb_dlhandle_t handle;
1126    char *errmsg;
1127    void *symbol;
1128    struct test_harness harness;
1129    int test_case_id = -1;
1130    char *cmdline;
1131
1132    /* Hack to remove the warning from C99 */
1133    union {
1134        GET_TESTS get_tests;
1135        void* voidptr;
1136    } my_get_test;
1137
1138    /* Hack to remove the warning from C99 */
1139    union {
1140        SETUP_SUITE setup_suite;
1141        void* voidptr;
1142    } my_setup_suite;
1143
1144    /* Hack to remove the warning from C99 */
1145    union {
1146        TEARDOWN_SUITE teardown_suite;
1147        void* voidptr;
1148    } my_teardown_suite;
1149
1150    cb_initialize_sockets();
1151
1152    memset(&my_get_test, 0, sizeof(my_get_test));
1153    memset(&my_setup_suite, 0, sizeof(my_setup_suite));
1154    memset(&my_teardown_suite, 0, sizeof(my_teardown_suite));
1155
1156    logger_descriptor = get_null_logger();
1157    color_enabled = getenv("TESTAPP_ENABLE_COLOR") != NULL;
1158
1159    /* Use unbuffered stdio */
1160    setbuf(stdout, NULL);
1161    setbuf(stderr, NULL);
1162
1163    setup_alarm_handler();
1164
1165    /* process arguments */
1166    while ((c = getopt(argc, argv,
1167                       "h"  /* usage */
1168                       "E:" /* Engine to load */
1169                       "e:" /* Engine options */
1170                       "T:" /* Library with tests to load */
1171                       "t:" /* Timeout */
1172                       "L"  /* Loop until failure */
1173                       "q"  /* Be more quiet (only report failures) */
1174                       "."  /* dot mode. */
1175                       "n:"  /* test case to run */
1176                       "v" /* verbose output */
1177                       "Z"  /* Terminate on first error */
1178                       "C:" /* Test case id */
1179                       "s" /* spinlock the program */
1180                       )) != -1) {
1181        switch (c) {
1182        case 's' : {
1183            int spin = 1;
1184            while (spin) {
1185
1186            }
1187            break;
1188        }
1189        case 'C' :
1190            test_case_id = atoi(optarg);
1191            break;
1192        case 'E':
1193            engine = optarg;
1194            break;
1195        case 'e':
1196            engine_args = optarg;
1197            break;
1198        case 'h':
1199            usage();
1200            return 0;
1201        case 'T':
1202            test_suite = optarg;
1203            break;
1204        case 't':
1205            timeout = atoi(optarg);
1206            break;
1207        case 'L':
1208            loop = true;
1209            break;
1210        case 'n':
1211            test_case = optarg;
1212            break;
1213        case 'v' :
1214            verbose = true;
1215            break;
1216        case 'q':
1217            quiet = true;
1218            break;
1219        case '.':
1220            dot = true;
1221            break;
1222        case 'Z' :
1223            terminate_on_error = true;
1224            break;
1225        default:
1226            fprintf(stderr, "Illegal argument \"%c\"\n", c);
1227            return 1;
1228        }
1229    }
1230
1231    /* validate args */
1232    if (engine == NULL) {
1233        fprintf(stderr, "You must provide a path to the storage engine library.\n");
1234        return 1;
1235    }
1236
1237    if (test_suite == NULL) {
1238        fprintf(stderr, "You must provide a path to the testsuite library.\n");
1239        return 1;
1240    }
1241
1242    /* load test_suite */
1243    handle = cb_dlopen(test_suite, &errmsg);
1244    if (handle == NULL) {
1245        fprintf(stderr, "Failed to load testsuite %s: %s\n", test_suite,
1246                errmsg);
1247        free(errmsg);
1248        return 1;
1249    }
1250
1251    /* get the test cases */
1252    symbol = cb_dlsym(handle, "get_tests", &errmsg);
1253    if (symbol == NULL) {
1254        fprintf(stderr, "Could not find get_tests function in testsuite %s: %s\n", test_suite, errmsg);
1255        free(errmsg);
1256        return 1;
1257    }
1258    my_get_test.voidptr = symbol;
1259    testcases = (*my_get_test.get_tests)();
1260
1261    /* set up the suite if needed */
1262    memset(&harness, 0, sizeof(harness));
1263    harness.default_engine_cfg = engine_args;
1264    harness.engine_path = engine;
1265    harness.reload_engine = reload_engine;
1266    harness.start_engine = start_your_engines;
1267    harness.create_cookie = create_mock_cookie;
1268    harness.destroy_cookie = destroy_mock_cookie;
1269    harness.set_ewouldblock_handling = mock_set_ewouldblock_handling;
1270    harness.lock_cookie = lock_mock_cookie;
1271    harness.unlock_cookie = unlock_mock_cookie;
1272    harness.waitfor_cookie = waitfor_mock_cookie;
1273    harness.time_travel = mock_time_travel;
1274    harness.get_current_testcase = get_current_testcase;
1275
1276    for (num_cases = 0; testcases[num_cases].name; num_cases++) {
1277        /* Just counting */
1278    }
1279
1280    symbol = cb_dlsym(handle, "setup_suite", &errmsg);
1281    if (symbol == NULL) {
1282        free(errmsg);
1283    } else {
1284        my_setup_suite.voidptr = symbol;
1285        if (!(*my_setup_suite.setup_suite)(&harness)) {
1286            fprintf(stderr, "Failed to set up test suite %s \n", test_suite);
1287            return 1;
1288        }
1289    }
1290
1291    if (test_case_id != -1) {
1292        if (test_case_id >= num_cases) {
1293            fprintf(stderr, "Invalid test case id specified\n");
1294            exit(EXIT_FAILURE);
1295        }
1296        exit(execute_test(testcases[test_case_id], engine, engine_args));
1297    }
1298
1299    cmdline = malloc(64*1024); /* should be enough */
1300    if (cmdline == NULL) {
1301        fprintf(stderr, "Failed to allocate memory");
1302        exit(EXIT_FAILURE);
1303    }
1304
1305    do {
1306        int i;
1307        bool need_newline = false;
1308        for (i = 0; testcases[i].name; i++) {
1309            int error;
1310            if (test_case != NULL && strcmp(test_case, testcases[i].name) != 0)
1311                continue;
1312            if (!quiet) {
1313                printf("Running [%04d/%04d]: %s...",
1314                       i + num_cases * loop_count,
1315                       num_cases * (loop_count + 1),
1316                       testcases[i].name);
1317                fflush(stdout);
1318            } else if(dot) {
1319                printf(".");
1320                need_newline = true;
1321                /* Add a newline every few tests */
1322                if ((i+1) % 70 == 0) {
1323                    printf("\n");
1324                    need_newline = false;
1325                }
1326            }
1327            set_test_timeout(timeout);
1328
1329            {
1330                int ii;
1331                int offset = 0;
1332                enum test_result ecode;
1333                time_t start;
1334                time_t stop;
1335                int rc;
1336                for (ii = 0; ii < argc; ++ii) {
1337                    offset += safe_append(cmdline + offset, argv[ii]);
1338                }
1339
1340                sprintf(cmdline + offset, "-C %d", i);
1341
1342                start = time(NULL);
1343                rc = system(cmdline);
1344                stop = time(NULL);
1345
1346#ifdef WIN32
1347                ecode = (enum test_result)rc;
1348#else
1349                if (WIFEXITED(rc)) {
1350                    ecode = (enum test_result)WEXITSTATUS(rc);
1351#ifdef WCOREDUMP
1352                } else if (WIFSIGNALED(rc) && WCOREDUMP(rc)) {
1353                    ecode = CORE;
1354#endif
1355                } else {
1356                    ecode = DIED;
1357                }
1358#endif
1359                error = report_test(testcases[i].name,
1360                                    stop - start,
1361                                    ecode, quiet,
1362                                    !verbose);
1363            }
1364            clear_test_timeout();
1365
1366            if (error != 0) {
1367                ++exitcode;
1368                if (terminate_on_error) {
1369                    exit(EXIT_FAILURE);
1370                }
1371            }
1372        }
1373
1374        if (need_newline) {
1375            printf("\n");
1376        }
1377        ++loop_count;
1378    } while (loop && exitcode == 0);
1379
1380    /* tear down the suite if needed */
1381    symbol = cb_dlsym(handle, "teardown_suite", &errmsg);
1382    if (symbol == NULL) {
1383        free(errmsg);
1384    } else {
1385        my_teardown_suite.voidptr = symbol;
1386        if (!(*my_teardown_suite.teardown_suite)()) {
1387            fprintf(stderr, "Failed to teardown up test suite %s \n", test_suite);
1388        }
1389    }
1390
1391    printf("# Passed %d of %d tests\n", num_cases - exitcode, num_cases);
1392    free(cmdline);
1393
1394    return exitcode;
1395}
1396