libmicrohttpd

HTTP/1.x server C library (MHD 1.x, stable)
Log | Files | Refs | Submodules | README | LICENSE

test_get_close_keep_alive.c (37979B)


      1 /*
      2      This file is part of libmicrohttpd
      3      Copyright (C) 2014-2022 Evgeny Grin (Karlson2k)
      4      Copyright (C) 2007, 2009, 2011 Christian Grothoff
      5 
      6      libmicrohttpd is free software; you can redistribute it and/or modify
      7      it under the terms of the GNU General Public License as published
      8      by the Free Software Foundation; either version 2, or (at your
      9      option) any later version.
     10 
     11      libmicrohttpd is distributed in the hope that it will be useful, but
     12      WITHOUT ANY WARRANTY; without even the implied warranty of
     13      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     14      General Public License for more details.
     15 
     16      You should have received a copy of the GNU General Public License
     17      along with libmicrohttpd; see the file COPYING.  If not, write to the
     18      Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     19      Boston, MA 02110-1301, USA.
     20 */
     21 /**
     22  * @file test_get_close_keep_alive.c
     23  * @brief  Testcase for libmicrohttpd "Close" and "Keep-Alive" connection.
     24  * @details Testcases for testing of MHD automatic choice between "Close" and
     25  *          "Keep-Alive" connections. Also tested selected HTTP version and
     26  *          "Connection:" headers.
     27  * @author Karlson2k (Evgeny Grin)
     28  * @author Christian Grothoff
     29  */
     30 #include "MHD_config.h"
     31 #include "platform.h"
     32 #include <curl/curl.h>
     33 #include <microhttpd.h>
     34 #include <stdlib.h>
     35 #include <string.h>
     36 #include <time.h>
     37 #include <errno.h>
     38 #include "mhd_has_in_name.h"
     39 #include "mhd_has_param.h"
     40 #include "mhd_sockets.h" /* only macros used */
     41 
     42 /* Turn any MHD_PANIC() or failing mhd_assert() reached from this
     43    test into a marked, classifiable test error (TESTING.md, P5). */
     44 #include "mhd_panic_tripwire.h"
     45 
     46 #ifdef HAVE_STRINGS_H
     47 #include <strings.h>
     48 #endif /* HAVE_STRINGS_H */
     49 
     50 #ifdef _WIN32
     51 #ifndef WIN32_LEAN_AND_MEAN
     52 #define WIN32_LEAN_AND_MEAN 1
     53 #endif /* !WIN32_LEAN_AND_MEAN */
     54 #include <windows.h>
     55 #endif
     56 
     57 #ifndef WINDOWS
     58 #include <unistd.h>
     59 #include <sys/socket.h>
     60 #endif
     61 
     62 #ifdef HAVE_LIMITS_H
     63 #include <limits.h>
     64 #endif /* HAVE_LIMITS_H */
     65 
     66 #ifndef CURL_VERSION_BITS
     67 #define CURL_VERSION_BITS(x,y,z) ((x)<<16|(y)<<8|(z))
     68 #endif /* ! CURL_VERSION_BITS */
     69 #ifndef CURL_AT_LEAST_VERSION
     70 #define CURL_AT_LEAST_VERSION(x,y,z) \
     71   (LIBCURL_VERSION_NUM >= CURL_VERSION_BITS(x, y, z))
     72 #endif /* ! CURL_AT_LEAST_VERSION */
     73 
     74 #if defined(MHD_CPU_COUNT) && (MHD_CPU_COUNT + 0) < 2
     75 #undef MHD_CPU_COUNT
     76 #endif
     77 #if ! defined(MHD_CPU_COUNT)
     78 #define MHD_CPU_COUNT 2
     79 #endif
     80 #if MHD_CPU_COUNT > 32
     81 #undef MHD_CPU_COUNT
     82 /* Limit to reasonable value */
     83 #define MHD_CPU_COUNT 32
     84 #endif /* MHD_CPU_COUNT > 32 */
     85 
     86 
     87 #if defined(HAVE___FUNC__)
     88 #define externalErrorExit(ignore) \
     89     _externalErrorExit_func(NULL, __func__, __LINE__)
     90 #define externalErrorExitDesc(errDesc) \
     91     _externalErrorExit_func(errDesc, __func__, __LINE__)
     92 #define libcurlErrorExit(ignore) \
     93     _libcurlErrorExit_func(NULL, __func__, __LINE__)
     94 #define libcurlErrorExitDesc(errDesc) \
     95     _libcurlErrorExit_func(errDesc, __func__, __LINE__)
     96 #elif defined(HAVE___FUNCTION__)
     97 #define externalErrorExit(ignore) \
     98     _externalErrorExit_func(NULL, __FUNCTION__, __LINE__)
     99 #define externalErrorExitDesc(errDesc) \
    100     _externalErrorExit_func(errDesc, __FUNCTION__, __LINE__)
    101 #define libcurlErrorExit(ignore) \
    102     _libcurlErrorExit_func(NULL, __FUNCTION__, __LINE__)
    103 #define libcurlErrorExitDesc(errDesc) \
    104     _libcurlErrorExit_func(errDesc, __FUNCTION__, __LINE__)
    105 #else
    106 #define externalErrorExit(ignore) _externalErrorExit_func(NULL, NULL, __LINE__)
    107 #define externalErrorExitDesc(errDesc) \
    108   _externalErrorExit_func(errDesc, NULL, __LINE__)
    109 #define libcurlErrorExit(ignore) _externalErrorExit_func(NULL, NULL, __LINE__)
    110 #define libcurlErrorExitDesc(errDesc) \
    111   _externalErrorExit_func(errDesc, NULL, __LINE__)
    112 #endif
    113 
    114 
    115 _MHD_NORETURN static void
    116 _externalErrorExit_func (const char *errDesc, const char *funcName, int lineNum)
    117 {
    118   if ((NULL != errDesc) && (0 != errDesc[0]))
    119     fprintf (stderr, "%s", errDesc);
    120   else
    121     fprintf (stderr, "System or external library call failed");
    122   if ((NULL != funcName) && (0 != funcName[0]))
    123     fprintf (stderr, " in %s", funcName);
    124   if (0 < lineNum)
    125     fprintf (stderr, " at line %d", lineNum);
    126 
    127   fprintf (stderr, ".\nLast errno value: %d (%s)\n", (int) errno,
    128            strerror (errno));
    129 #ifdef MHD_WINSOCK_SOCKETS
    130   fprintf (stderr, "WSAGetLastError() value: %d\n", (int) WSAGetLastError ());
    131 #endif /* MHD_WINSOCK_SOCKETS */
    132   fflush (stderr);
    133   exit (99);
    134 }
    135 
    136 
    137 static char libcurl_errbuf[CURL_ERROR_SIZE] = "";
    138 
    139 _MHD_NORETURN static void
    140 _libcurlErrorExit_func (const char *errDesc, const char *funcName, int lineNum)
    141 {
    142   if ((NULL != errDesc) && (0 != errDesc[0]))
    143     fprintf (stderr, "%s", errDesc);
    144   else
    145     fprintf (stderr, "CURL library call failed");
    146   if ((NULL != funcName) && (0 != funcName[0]))
    147     fprintf (stderr, " in %s", funcName);
    148   if (0 < lineNum)
    149     fprintf (stderr, " at line %d", lineNum);
    150 
    151   fprintf (stderr, ".\nLast errno value: %d (%s)\n", (int) errno,
    152            strerror (errno));
    153   if (0 != libcurl_errbuf[0])
    154     fprintf (stderr, "Last libcurl error details: %s\n", libcurl_errbuf);
    155 
    156   fflush (stderr);
    157   exit (99);
    158 }
    159 
    160 
    161 /* Could be increased to facilitate debugging */
    162 #define TIMEOUTS_VAL 5
    163 
    164 #define EXPECTED_URI_BASE_PATH  "/hello_world"
    165 #define EXPECTED_URI_QUERY      "a=%26&b=c"
    166 #define EXPECTED_URI_FULL_PATH  EXPECTED_URI_BASE_PATH "?" EXPECTED_URI_QUERY
    167 #define HDR_CONN_CLOSE_VALUE      "close"
    168 #define HDR_CONN_CLOSE            MHD_HTTP_HEADER_CONNECTION ": " \
    169                                   HDR_CONN_CLOSE_VALUE
    170 #define HDR_CONN_KEEP_ALIVE_VALUE "Keep-Alive"
    171 #define HDR_CONN_KEEP_ALIVE       MHD_HTTP_HEADER_CONNECTION ": " \
    172                                   HDR_CONN_KEEP_ALIVE_VALUE
    173 
    174 /* Global parameters */
    175 static int oneone;           /**< Use HTTP/1.1 instead of HTTP/1.0 for requests*/
    176 static int conn_close;       /**< Don't use Keep-Alive */
    177 static uint16_t global_port; /**< MHD daemons listen port number */
    178 static int slow_reply = 0; /**< Slowdown MHD replies */
    179 static int ignore_response_errors = 0; /**< Do not fail test if CURL
    180                                             returns error */
    181 static int response_timeout_val = TIMEOUTS_VAL;
    182 
    183 /* Current test parameters */
    184 /* Poor thread sync, but enough for the testing */
    185 static volatile int mhd_add_close; /**< Add "Connection: close" header by MHD */
    186 static volatile int mhd_set_10_cmptbl; /**< Set MHD_RF_HTTP_1_0_COMPATIBLE_STRICT response flag */
    187 static volatile int mhd_set_10_server; /**< Set MHD_RF_HTTP_1_0_SERVER response flag */
    188 static volatile int mhd_set_k_a_send; /**< Set MHD_RF_SEND_KEEP_ALIVE_HEADER response flag */
    189 
    190 /* Static helper variables */
    191 static struct curl_slist *curl_close_hdr;   /**< CURL "Connection: close" header */
    192 static struct curl_slist *curl_k_alive_hdr; /**< CURL "Connection: keep-alive" header */
    193 static struct curl_slist *curl_both_hdrs;   /**< CURL both "Connection: keep-alive" and "close" headers */
    194 
    195 static void
    196 test_global_init (void)
    197 {
    198   libcurl_errbuf[0] = 0;
    199 
    200   if (0 != curl_global_init (CURL_GLOBAL_WIN32))
    201     externalErrorExit ();
    202 
    203   curl_close_hdr = NULL;
    204   curl_close_hdr = curl_slist_append (curl_close_hdr,
    205                                       HDR_CONN_CLOSE);
    206   if (NULL == curl_close_hdr)
    207     externalErrorExit ();
    208 
    209   curl_k_alive_hdr = NULL;
    210   curl_k_alive_hdr = curl_slist_append (curl_k_alive_hdr,
    211                                         HDR_CONN_KEEP_ALIVE);
    212   if (NULL == curl_k_alive_hdr)
    213     externalErrorExit ();
    214 
    215   curl_both_hdrs = NULL;
    216   curl_both_hdrs = curl_slist_append (curl_both_hdrs,
    217                                       HDR_CONN_KEEP_ALIVE);
    218   if (NULL == curl_both_hdrs)
    219     externalErrorExit ();
    220   curl_both_hdrs = curl_slist_append (curl_both_hdrs,
    221                                       HDR_CONN_CLOSE);
    222   if (NULL == curl_both_hdrs)
    223     externalErrorExit ();
    224 }
    225 
    226 
    227 static void
    228 test_global_cleanup (void)
    229 {
    230   curl_slist_free_all (curl_both_hdrs);
    231   curl_slist_free_all (curl_k_alive_hdr);
    232   curl_slist_free_all (curl_close_hdr);
    233 
    234   curl_global_cleanup ();
    235 }
    236 
    237 
    238 struct headers_check_result
    239 {
    240   int found_http11;
    241   int found_http10;
    242   int found_conn_close;
    243   int found_conn_keep_alive;
    244 };
    245 
    246 static size_t
    247 lcurl_hdr_callback (char *buffer, size_t size, size_t nitems,
    248                     void *userdata)
    249 {
    250   const size_t data_size = size * nitems;
    251   struct headers_check_result *check_res =
    252     (struct headers_check_result *) userdata;
    253 
    254   if ((strlen (MHD_HTTP_VERSION_1_1) < data_size) &&
    255       (0 == memcmp (MHD_HTTP_VERSION_1_1, buffer,
    256                     strlen (MHD_HTTP_VERSION_1_1))))
    257     check_res->found_http11 = 1;
    258   else if ((strlen (MHD_HTTP_VERSION_1_0) < data_size) &&
    259            (0 == memcmp (MHD_HTTP_VERSION_1_0, buffer,
    260                          strlen (MHD_HTTP_VERSION_1_0))))
    261     check_res->found_http10 = 1;
    262   else if ((data_size == strlen (HDR_CONN_CLOSE) + 2) &&
    263            (0 == memcmp (buffer, HDR_CONN_CLOSE "\r\n", data_size)))
    264     check_res->found_conn_close = 1;
    265   else if ((data_size == strlen (HDR_CONN_KEEP_ALIVE) + 2) &&
    266            (0 == memcmp (buffer, HDR_CONN_KEEP_ALIVE "\r\n", data_size)))
    267     check_res->found_conn_keep_alive = 1;
    268 
    269   return data_size;
    270 }
    271 
    272 
    273 struct CBC
    274 {
    275   char *buf;
    276   size_t pos;
    277   size_t size;
    278 };
    279 
    280 
    281 static size_t
    282 copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx)
    283 {
    284   struct CBC *cbc = ctx;
    285 
    286   if (cbc->pos + size * nmemb > cbc->size)
    287     externalErrorExit ();  /* overflow */
    288   memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
    289   cbc->pos += size * nmemb;
    290   return size * nmemb;
    291 }
    292 
    293 
    294 static void *
    295 log_cb (void *cls,
    296         const char *uri,
    297         struct MHD_Connection *con)
    298 {
    299   (void) cls;
    300   (void) con;
    301   if (0 != strcmp (uri,
    302                    EXPECTED_URI_FULL_PATH))
    303   {
    304     fprintf (stderr,
    305              "Wrong URI: `%s', line: %d\n",
    306              uri, __LINE__);
    307     exit (22);
    308   }
    309   return NULL;
    310 }
    311 
    312 
    313 static enum MHD_Result
    314 ahc_echo (void *cls,
    315           struct MHD_Connection *connection,
    316           const char *url,
    317           const char *method,
    318           const char *version,
    319           const char *upload_data, size_t *upload_data_size,
    320           void **req_cls)
    321 {
    322   static int ptr;
    323   struct MHD_Response *response;
    324   enum MHD_Result ret;
    325   (void) cls;
    326   (void) version;
    327   (void) upload_data;
    328   (void) upload_data_size;       /* Unused. Silence compiler warning. */
    329 
    330   if (0 != strcmp (MHD_HTTP_METHOD_GET, method))
    331     return MHD_NO;              /* unexpected method */
    332   if (&ptr != *req_cls)
    333   {
    334     *req_cls = &ptr;
    335     return MHD_YES;
    336   }
    337   *req_cls = NULL;
    338   if (slow_reply)
    339     usleep (200000);
    340 
    341   response = MHD_create_response_from_buffer_copy (strlen (url),
    342                                                    (const void *) url);
    343   if (NULL == response)
    344   {
    345     fprintf (stderr, "Failed to create response. Line: %d\n", __LINE__);
    346     exit (19);
    347   }
    348   if (mhd_add_close)
    349   {
    350     if (MHD_YES != MHD_add_response_header (response,
    351                                             MHD_HTTP_HEADER_CONNECTION,
    352                                             HDR_CONN_CLOSE_VALUE))
    353     {
    354       fprintf (stderr, "Failed to add header. Line: %d\n", __LINE__);
    355       exit (19);
    356     }
    357   }
    358   if (MHD_YES != MHD_set_response_options (response,
    359                                            (mhd_set_10_cmptbl ?
    360                                             MHD_RF_HTTP_1_0_COMPATIBLE_STRICT
    361                                                : 0)
    362                                            | (mhd_set_10_server ?
    363                                               MHD_RF_HTTP_1_0_SERVER
    364                                                : 0)
    365                                            | (mhd_set_k_a_send ?
    366                                               MHD_RF_SEND_KEEP_ALIVE_HEADER
    367                                                : 0), MHD_RO_END))
    368   {
    369     fprintf (stderr, "Failed to set response flags. Line: %d\n", __LINE__);
    370     exit (19);
    371   }
    372 
    373   ret = MHD_queue_response (connection,
    374                             MHD_HTTP_OK,
    375                             response);
    376   MHD_destroy_response (response);
    377   if (MHD_YES != ret)
    378   {
    379     fprintf (stderr, "Failed to queue response. Line: %d\n", __LINE__);
    380     exit (19);
    381   }
    382   return ret;
    383 }
    384 
    385 
    386 struct curlQueryParams
    387 {
    388   /* Destination path for CURL query */
    389   const char *queryPath;
    390 
    391   /* Destination port for CURL query */
    392   uint16_t queryPort;
    393 
    394   /* CURL query result error flag */
    395   volatile unsigned int queryError;
    396 };
    397 
    398 
    399 static CURL *
    400 curlEasyInitForTest (const char *queryPath,
    401                      uint16_t port,
    402                      struct CBC *pcbc,
    403                      struct headers_check_result *hdr_chk_result,
    404                      int add_hdr_close,
    405                      int add_hdr_k_alive)
    406 {
    407   CURL *c;
    408 
    409   c = curl_easy_init ();
    410   if (NULL == c)
    411   {
    412     fprintf (stderr, "curl_easy_init() failed.\n");
    413     externalErrorExit ();
    414   }
    415   if ((CURLE_OK != curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1L)) ||
    416       (CURLE_OK != curl_easy_setopt (c, CURLOPT_URL, queryPath)) ||
    417       (CURLE_OK != curl_easy_setopt (c, CURLOPT_PORT, (long) port)) ||
    418       (CURLE_OK != curl_easy_setopt (c, CURLOPT_WRITEFUNCTION,
    419                                      &copyBuffer)) ||
    420       (CURLE_OK != curl_easy_setopt (c, CURLOPT_WRITEDATA, pcbc)) ||
    421       (CURLE_OK != curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT,
    422                                      (long) response_timeout_val)) ||
    423       (CURLE_OK != curl_easy_setopt (c, CURLOPT_TIMEOUT,
    424                                      (long) response_timeout_val)) ||
    425       (CURLE_OK != curl_easy_setopt (c, CURLOPT_ERRORBUFFER,
    426                                      libcurl_errbuf)) ||
    427       (CURLE_OK != curl_easy_setopt (c, CURLOPT_HEADERFUNCTION,
    428                                      lcurl_hdr_callback)) ||
    429       (CURLE_OK != curl_easy_setopt (c, CURLOPT_HEADERDATA,
    430                                      hdr_chk_result)) ||
    431       (CURLE_OK != curl_easy_setopt (c, CURLOPT_FAILONERROR, 1L)) ||
    432       (CURLE_OK != curl_easy_setopt (c, CURLOPT_HTTP_VERSION,
    433                                      (oneone) ?
    434                                      CURL_HTTP_VERSION_1_1 :
    435                                      CURL_HTTP_VERSION_1_0)))
    436   {
    437     fprintf (stderr, "curl_easy_setopt() failed.\n");
    438     externalErrorExit ();
    439   }
    440   if (add_hdr_close && add_hdr_k_alive)
    441   { /* This combination is actually incorrect */
    442     if (CURLE_OK != curl_easy_setopt (c, CURLOPT_HTTPHEADER, curl_both_hdrs))
    443     {
    444       fprintf (stderr, "Set libcurl HTTP header failed.\n");
    445       externalErrorExit ();
    446     }
    447   }
    448   else if (add_hdr_close)
    449   {
    450     if (CURLE_OK !=
    451         curl_easy_setopt (c,
    452                           CURLOPT_HTTPHEADER,
    453                           curl_close_hdr))
    454     {
    455       fprintf (stderr,
    456                "Set libcurl HTTP header failed.\n");
    457       externalErrorExit ();
    458     }
    459   }
    460   else if (add_hdr_k_alive)
    461   {
    462     if (CURLE_OK != curl_easy_setopt (c, CURLOPT_HTTPHEADER, curl_k_alive_hdr))
    463     {
    464       fprintf (stderr, "Set libcurl HTTP header failed.\n");
    465       externalErrorExit ();
    466     }
    467   }
    468 
    469   return c;
    470 }
    471 
    472 
    473 static int
    474 print_test_params (int add_hdr_close,
    475                    int add_hdr_k_alive)
    476 {
    477   fprintf (stderr, "Request HTTP/%s| ", oneone ? "1.1" : "1.0");
    478   fprintf (stderr, "Connection must be: %s| ",
    479            conn_close ? "close" : "keep-alive");
    480   fprintf (stderr, "Request \"close\": %s| ",
    481            add_hdr_close ? "    used" : "NOT used");
    482   fprintf (stderr, "Request \"keep-alive\": %s| ",
    483            add_hdr_k_alive ? "    used" : "NOT used");
    484   fprintf (stderr, "MHD response \"close\": %s| ",
    485            mhd_add_close ? "    used" : "NOT used");
    486   fprintf (stderr, "MHD response 1.0 strict compatible: %s| ",
    487            mhd_set_10_cmptbl ? "yes" : " NO");
    488   fprintf (stderr, "MHD response 1.0 server: %s| ",
    489            mhd_set_10_server ? "yes" : " NO");
    490   fprintf (stderr, "MHD response send \"Keep-Alive\": %s|",
    491            mhd_set_k_a_send ? "yes" : " NO");
    492   fprintf (stderr, "\n");
    493 
    494   return ! 0;
    495 }
    496 
    497 
    498 static CURLcode
    499 performQueryExternal (struct MHD_Daemon *d, CURL *c)
    500 {
    501   CURLM *multi;
    502   time_t start;
    503   struct timeval tv;
    504   CURLcode ret;
    505 
    506   ret = CURLE_FAILED_INIT; /* will be replaced with real result */
    507   multi = NULL;
    508   multi = curl_multi_init ();
    509   if (multi == NULL)
    510   {
    511     fprintf (stderr, "curl_multi_init() failed.\n");
    512     externalErrorExit ();
    513   }
    514   if (CURLM_OK != curl_multi_add_handle (multi, c))
    515   {
    516     fprintf (stderr, "curl_multi_add_handle() failed.\n");
    517     externalErrorExit ();
    518   }
    519 
    520   start = time (NULL);
    521   while (time (NULL) - start <= TIMEOUTS_VAL)
    522   {
    523     fd_set rs;
    524     fd_set ws;
    525     fd_set es;
    526     MHD_socket maxMhdSk;
    527     int maxCurlSk;
    528     int running;
    529 
    530     maxMhdSk = MHD_INVALID_SOCKET;
    531     maxCurlSk = -1;
    532     FD_ZERO (&rs);
    533     FD_ZERO (&ws);
    534     FD_ZERO (&es);
    535     if (NULL != multi)
    536     {
    537       curl_multi_perform (multi, &running);
    538       if (0 == running)
    539       {
    540         struct CURLMsg *msg;
    541         int msgLeft;
    542         int totalMsgs = 0;
    543         do
    544         {
    545           msg = curl_multi_info_read (multi, &msgLeft);
    546           if (NULL == msg)
    547           {
    548             fprintf (stderr, "curl_multi_info_read failed, NULL returned.\n");
    549             externalErrorExit ();
    550           }
    551           totalMsgs++;
    552           if (CURLMSG_DONE == msg->msg)
    553             ret = msg->data.result;
    554         } while (msgLeft > 0);
    555         if (1 != totalMsgs)
    556         {
    557           fprintf (stderr,
    558                    "curl_multi_info_read returned wrong "
    559                    "number of results (%d).\n",
    560                    totalMsgs);
    561           externalErrorExit ();
    562         }
    563         curl_multi_remove_handle (multi, c);
    564         curl_multi_cleanup (multi);
    565         multi = NULL;
    566       }
    567       else
    568       {
    569         if (CURLM_OK != curl_multi_fdset (multi, &rs, &ws, &es, &maxCurlSk))
    570         {
    571           fprintf (stderr, "curl_multi_fdset() failed.\n");
    572           externalErrorExit ();
    573         }
    574       }
    575     }
    576     if (NULL == multi)
    577     { /* libcurl has finished, check whether MHD still needs to perform cleanup */
    578       if (0 != MHD_get_timeout64s (d))
    579         break; /* MHD finished as well */
    580     }
    581     if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &maxMhdSk))
    582     {
    583       fprintf (stderr, "MHD_get_fdset() failed. Line: %d\n", __LINE__);
    584       exit (11);
    585       break;
    586     }
    587     tv.tv_sec = 0;
    588     tv.tv_usec = 1000;
    589 #ifdef MHD_POSIX_SOCKETS
    590     if (maxMhdSk > maxCurlSk)
    591       maxCurlSk = maxMhdSk;
    592 #endif /* MHD_POSIX_SOCKETS */
    593     if (-1 == select (maxCurlSk + 1, &rs, &ws, &es, &tv))
    594     {
    595 #ifdef MHD_POSIX_SOCKETS
    596       if (EINTR != errno)
    597       {
    598         fprintf (stderr, "Unexpected select() error: %d. Line: %d\n",
    599                  (int) errno, __LINE__);
    600         fflush (stderr);
    601         exit (99);
    602       }
    603 #else
    604       if ((WSAEINVAL != WSAGetLastError ()) ||
    605           (0 != rs.fd_count) || (0 != ws.fd_count) || (0 != es.fd_count) )
    606       {
    607         fprintf (stderr, "Unexpected select() error: %d. Line: %d\n",
    608                  (int) WSAGetLastError (), __LINE__);
    609         fflush (stderr);
    610         exit (99);
    611       }
    612       Sleep (1);
    613 #endif
    614     }
    615     if (MHD_YES != MHD_run_from_select (d, &rs, &ws, &es))
    616     {
    617       fprintf (stderr, "MHD_run_from_select() failed. Line: %d\n", __LINE__);
    618       exit (11);
    619     }
    620   }
    621 
    622   return ret;
    623 }
    624 
    625 
    626 static unsigned int
    627 getMhdActiveConnections (struct MHD_Daemon *d)
    628 {
    629   const union MHD_DaemonInfo *dinfo;
    630   /* The next method is unreliable unless it's known that no
    631    * connections are started or finished in parallel */
    632   dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_CURRENT_CONNECTIONS);
    633   if (NULL == dinfo)
    634   {
    635     fprintf (stderr, "MHD_get_daemon_info() failed.\n");
    636     abort ();
    637   }
    638   return dinfo->num_connections;
    639 }
    640 
    641 
    642 static unsigned int
    643 doCurlQueryInThread (struct MHD_Daemon *d,
    644                      struct curlQueryParams *p,
    645                      int add_hdr_close,
    646                      int add_hdr_k_alive)
    647 {
    648   const union MHD_DaemonInfo *dinfo;
    649   CURL *c;
    650   char buf[2048];
    651   struct CBC cbc;
    652   struct headers_check_result hdr_res;
    653   CURLcode errornum;
    654   int use_external_poll;
    655   int params_printed;
    656 
    657   params_printed = 0;
    658   dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_FLAGS);
    659   if (NULL == dinfo)
    660   {
    661     fprintf (stderr, "MHD_get_daemon_info() failed.\n");
    662     abort ();
    663   }
    664   use_external_poll = (0 == (dinfo->flags
    665                              & MHD_USE_INTERNAL_POLLING_THREAD));
    666 
    667   if (NULL == p->queryPath)
    668     abort ();
    669 
    670   if (0 == p->queryPort)
    671     abort ();
    672 
    673   cbc.buf = buf;
    674   cbc.size = sizeof(buf);
    675   cbc.pos = 0;
    676 
    677   hdr_res.found_http11 = 0;
    678   hdr_res.found_http10 = 0;
    679   hdr_res.found_conn_close = 0;
    680   hdr_res.found_conn_keep_alive = 0;
    681 
    682   c = curlEasyInitForTest (p->queryPath,
    683                            p->queryPort,
    684                            &cbc, &hdr_res,
    685                            add_hdr_close, add_hdr_k_alive);
    686 
    687   if (! use_external_poll)
    688     errornum = curl_easy_perform (c);
    689   else
    690     errornum = performQueryExternal (d, c);
    691   if (ignore_response_errors)
    692   {
    693     p->queryError = 0;
    694     curl_easy_cleanup (c);
    695 
    696     return p->queryError;
    697   }
    698   if (CURLE_OK != errornum)
    699   {
    700     p->queryError = 1;
    701     fprintf (stderr,
    702              "libcurl query failed: `%s'\n",
    703              curl_easy_strerror (errornum));
    704     libcurlErrorExit ();
    705   }
    706   else
    707   {
    708     if (cbc.pos != strlen (EXPECTED_URI_BASE_PATH))
    709     {
    710       fprintf (stderr,
    711                "curl reports wrong size of MHD reply body data.\n");
    712       p->queryError = 1;
    713     }
    714     else if (0 != strncmp (EXPECTED_URI_BASE_PATH,
    715                            cbc.buf,
    716                            strlen (EXPECTED_URI_BASE_PATH)))
    717     {
    718       fprintf (stderr, "curl reports wrong MHD reply body data.\n");
    719       p->queryError = 1;
    720     }
    721     else
    722       p->queryError = 0;
    723   }
    724 
    725   if (! hdr_res.found_http11 && ! hdr_res.found_http10)
    726   {
    727     if (! params_printed)
    728       params_printed = print_test_params (add_hdr_close,
    729                                           add_hdr_k_alive);
    730     fprintf (stderr,
    731              "No know HTTP versions were found in the "
    732              "reply header. Line: %d\n",
    733              __LINE__);
    734     exit (24);
    735   }
    736   else if (hdr_res.found_http11 && hdr_res.found_http10)
    737   {
    738     if (! params_printed)
    739       params_printed = print_test_params (add_hdr_close,
    740                                           add_hdr_k_alive);
    741     fprintf (stderr,
    742              "Both HTTP/1.1 and HTTP/1.0 were found in the "
    743              "reply header. Line: %d\n",
    744              __LINE__);
    745     exit (24);
    746   }
    747 
    748   if (conn_close)
    749   {
    750     if (! hdr_res.found_conn_close)
    751     {
    752       if (! params_printed)
    753         params_printed = print_test_params (add_hdr_close,
    754                                             add_hdr_k_alive);
    755       fprintf (stderr,
    756                "\"Connection: close\" was not found in"
    757                " MHD reply headers.\n");
    758       p->queryError |= 2;
    759       exit (42);
    760     }
    761     if (hdr_res.found_conn_keep_alive)
    762     {
    763       if (! params_printed)
    764         params_printed = print_test_params (add_hdr_close,
    765                                             add_hdr_k_alive);
    766       fprintf (stderr,
    767                "\"Connection: keep-alive\" was found in"
    768                " MHD reply headers.\n");
    769       p->queryError |= 2;
    770     }
    771     if (use_external_poll)
    772     { /* The number of MHD connection can queried only with external poll.
    773        * otherwise it creates a race condition. */
    774       if (0 != getMhdActiveConnections (d))
    775       {
    776         if (! params_printed)
    777           params_printed = print_test_params (add_hdr_close,
    778                                               add_hdr_k_alive);
    779         fprintf (stderr,
    780                  "MHD still has active connection "
    781                  "after response has been sent.\n");
    782         p->queryError |= 2;
    783       }
    784     }
    785   }
    786   else
    787   { /* Keep-Alive */
    788     if (! oneone || mhd_set_10_server || mhd_set_k_a_send)
    789     { /* Should have "Connection: Keep-Alive" */
    790       if (! hdr_res.found_conn_keep_alive)
    791       {
    792         if (! params_printed)
    793           params_printed = print_test_params (add_hdr_close,
    794                                               add_hdr_k_alive);
    795         fprintf (stderr,
    796                  "\"Connection: keep-alive\" was not found in"
    797                  " MHD reply headers.\n");
    798         p->queryError |= 2;
    799       }
    800     }
    801     else
    802     { /* Should NOT have "Connection: Keep-Alive" */
    803       if (hdr_res.found_conn_keep_alive)
    804       {
    805         if (! params_printed)
    806           params_printed = print_test_params (add_hdr_close,
    807                                               add_hdr_k_alive);
    808         fprintf (stderr,
    809                  "\"Connection: keep-alive\" was found in"
    810                  " MHD reply headers.\n");
    811         p->queryError |= 2;
    812       }
    813     }
    814     if (hdr_res.found_conn_close)
    815     {
    816       if (! params_printed)
    817         params_printed = print_test_params (add_hdr_close,
    818                                             add_hdr_k_alive);
    819       fprintf (stderr,
    820                "\"Connection: close\" was found in"
    821                " MHD reply headers.\n");
    822       p->queryError |= 2;
    823     }
    824     if (use_external_poll)
    825     { /* The number of MHD connection can be queried only with external poll.
    826        * otherwise it creates a race condition. */
    827       unsigned int num_conn = getMhdActiveConnections (d);
    828       if (0 == num_conn)
    829       {
    830         if (! params_printed)
    831           params_printed = print_test_params (add_hdr_close,
    832                                               add_hdr_k_alive);
    833         fprintf (stderr,
    834                  "MHD has no active connection "
    835                  "after response has been sent.\n");
    836         p->queryError |= 2;
    837       }
    838       else if (1 != num_conn)
    839       {
    840         if (! params_printed)
    841           params_printed = print_test_params (add_hdr_close,
    842                                               add_hdr_k_alive);
    843         fprintf (stderr,
    844                  "MHD has wrong number of active connection (%u) "
    845                  "after response has been sent. Line: %d\n",
    846                  num_conn,
    847                  __LINE__);
    848         exit (23);
    849       }
    850     }
    851   }
    852 
    853 #if defined(CURL_AT_LEAST_VERSION) && CURL_AT_LEAST_VERSION (7, 45, 0)
    854   if (! use_external_poll)
    855   {
    856     /* libcurl closes connection socket with curl_multi_remove_handle () /
    857        curl_multi_cleanup() */
    858     curl_socket_t curl_sckt;
    859 
    860     if (CURLE_OK !=
    861         curl_easy_getinfo (c,
    862                            CURLINFO_ACTIVESOCKET,
    863                            &curl_sckt))
    864     {
    865       fprintf (stderr,
    866                "Failed to get libcurl active socket.\n");
    867       libcurlErrorExit ();
    868     }
    869     if (conn_close && (CURL_SOCKET_BAD != curl_sckt))
    870     {
    871       if (! params_printed)
    872         params_printed = print_test_params (add_hdr_close,
    873                                             add_hdr_k_alive);
    874       fprintf (stderr,
    875                "libcurl still has active connection "
    876                "after performing the test query.\n");
    877       p->queryError |= 2;
    878     }
    879     else if (! conn_close && (CURL_SOCKET_BAD == curl_sckt))
    880     {
    881       if (! params_printed)
    882         params_printed = print_test_params (add_hdr_close,
    883                                             add_hdr_k_alive);
    884       fprintf (stderr,
    885                "libcurl has no active connection "
    886                "after performing the test query.\n");
    887       p->queryError |= 2;
    888     }
    889   }
    890 #endif
    891 
    892   if (! mhd_set_10_server)
    893   {
    894     /* Response must be HTTP/1.1 */
    895     if (hdr_res.found_http10)
    896     {
    897       if (! params_printed)
    898         params_printed = print_test_params (add_hdr_close,
    899                                             add_hdr_k_alive);
    900       fprintf (stderr,
    901                "Reply has HTTP/1.0 version, while it "
    902                "must be HTTP/1.1.\n");
    903       p->queryError |= 4;
    904     }
    905   }
    906   else
    907   {
    908     /* Response must be HTTP/1.0 */
    909     if (hdr_res.found_http11)
    910     {
    911       if (! params_printed)
    912         params_printed = print_test_params (add_hdr_close,
    913                                             add_hdr_k_alive);
    914       fprintf (stderr,
    915                "Reply has HTTP/1.1 version, while it "
    916                "must be HTTP/1.0.\n");
    917       p->queryError |= 4;
    918     }
    919   }
    920   curl_easy_cleanup (c);
    921   if (0 != p->queryError)
    922   {
    923     fprintf (stderr, "======\n");
    924     fflush (stderr);
    925   }
    926 
    927   return p->queryError;
    928 }
    929 
    930 
    931 /* Perform test queries and shut down MHD daemon */
    932 static unsigned int
    933 performTestQueries (struct MHD_Daemon *d, uint16_t d_port)
    934 {
    935   struct curlQueryParams qParam;
    936   unsigned int ret = 0;          /* Return value */
    937   int i = 0;
    938   /* masks */
    939   const int m_mhd_close = 1 << (i++);
    940   const int m_10_cmptbl = 1 << (i++);
    941   const int m_10_server = 1 << (i++);
    942   const int m_k_a_send = 1 << (i++);
    943   const int m_client_close = 1 << (i++);
    944   const int m_client_k_alive = 1 << (i++);
    945 
    946   qParam.queryPath = "http://127.0.0.1" EXPECTED_URI_FULL_PATH;
    947   qParam.queryPort = d_port;   /* Connect to the daemon */
    948 
    949   for (i = (1 << i) - 1; 0 <= i; i--)
    950   {
    951     const int f_mhd_close = (0 == (i & m_mhd_close)); /**< Use MHD "close" header */
    952     const int f_10_cmptbl = (0 == (i & m_10_cmptbl)); /**< Use MHD MHD_RF_HTTP_1_0_COMPATIBLE_STRICT flag */
    953     const int f_10_server = (0 == (i & m_10_server)); /**< Use MHD MHD_RF_HTTP_1_0_SERVER flag */
    954     const int f_k_a_send = (0 == (i & m_k_a_send));   /**< Use MHD MHD_RF_SEND_KEEP_ALIVE_HEADER flag */
    955     const int f_client_close = (0 == (i & m_client_close)); /**< Use libcurl "close" header */
    956     const int f_client_k_alive = (0 == (i & m_client_k_alive)); /**< Use libcurl "Keep-Alive" header */
    957     int res_close; /**< Indicate the result of the test query should be closed connection */
    958 
    959     if (f_mhd_close)         /* Connection with server's "close" header must be always closed */
    960       res_close = 1;
    961     else if (f_client_close) /* Connection with client's "close" header must be always closed */
    962       res_close = 1;
    963     else if (f_10_cmptbl)    /* Connection in strict HTTP/1.0 compatible mode must be always closed */
    964       res_close = 1;
    965     else if (! oneone || f_10_server)
    966       /* With HTTP/1.0 client or server connection must be close unless client use "keep-alive" header */
    967       res_close = ! f_client_k_alive;
    968     else
    969       res_close = 0; /* HTTP/1.1 is "keep-alive" by default */
    970 
    971     if ((! ! res_close) != (! ! conn_close))
    972       continue; /* Another mode is in test currently */
    973 
    974 #if CURL_AT_LEAST_VERSION (8,16,0)
    975     if ((f_client_close) && (f_client_k_alive))
    976       continue;
    977 #endif /* CURL >= 8.16.0 */
    978 
    979     mhd_add_close = f_mhd_close;
    980     mhd_set_10_cmptbl = f_10_cmptbl;
    981     mhd_set_10_server = f_10_server;
    982     mhd_set_k_a_send = f_k_a_send;
    983 
    984     ret <<= 3;                   /* Remember errors for each step */
    985     ret |= doCurlQueryInThread (d, &qParam, f_client_close, f_client_k_alive);
    986   }
    987 
    988   MHD_stop_daemon (d);
    989 
    990   return ret;
    991 }
    992 
    993 
    994 enum testMhdThreadsType
    995 {
    996   testMhdThreadExternal              = 0,
    997   testMhdThreadInternal              = MHD_USE_INTERNAL_POLLING_THREAD,
    998   testMhdThreadInternalPerConnection = MHD_USE_THREAD_PER_CONNECTION
    999                                        | MHD_USE_INTERNAL_POLLING_THREAD,
   1000   testMhdThreadInternalPool
   1001 };
   1002 
   1003 enum testMhdPollType
   1004 {
   1005   testMhdPollBySelect = 0,
   1006   testMhdPollByPoll   = MHD_USE_POLL,
   1007   testMhdPollByEpoll  = MHD_USE_EPOLL,
   1008   testMhdPollAuto     = MHD_USE_AUTO
   1009 };
   1010 
   1011 /* Get number of threads for thread pool depending
   1012  * on used poll function and test type. */
   1013 static unsigned int
   1014 testNumThreadsForPool (enum testMhdPollType pollType)
   1015 {
   1016   unsigned int numThreads = MHD_CPU_COUNT;
   1017   (void) pollType; /* Don't care about pollType for this test */
   1018   return numThreads; /* No practical limit for non-cleanup test */
   1019 }
   1020 
   1021 
   1022 static struct MHD_Daemon *
   1023 startTestMhdDaemon (enum testMhdThreadsType thrType,
   1024                     enum testMhdPollType pollType, uint16_t *pport)
   1025 {
   1026   struct MHD_Daemon *d;
   1027   const union MHD_DaemonInfo *dinfo;
   1028 
   1029   if ( (0 == *pport) &&
   1030        (MHD_NO == MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT)) )
   1031   {
   1032     *pport = 4050;
   1033     if (oneone)
   1034       *pport += 1;
   1035     if (! conn_close)
   1036       *pport += 2;
   1037   }
   1038 
   1039   if (testMhdThreadExternal == thrType)
   1040     d = MHD_start_daemon (((unsigned int) thrType) | ((unsigned int) pollType)
   1041                           | MHD_USE_ERROR_LOG | MHD_USE_NO_THREAD_SAFETY,
   1042                           *pport, NULL, NULL,
   1043                           &ahc_echo, NULL,
   1044                           MHD_OPTION_URI_LOG_CALLBACK, &log_cb, NULL,
   1045                           MHD_OPTION_APP_FD_SETSIZE, (int) FD_SETSIZE,
   1046                           MHD_OPTION_END);
   1047   else if (testMhdThreadInternalPool != thrType)
   1048     d = MHD_start_daemon (((unsigned int) thrType) | ((unsigned int) pollType)
   1049                           | MHD_USE_ERROR_LOG,
   1050                           *pport, NULL, NULL,
   1051                           &ahc_echo, NULL,
   1052                           MHD_OPTION_URI_LOG_CALLBACK, &log_cb, NULL,
   1053                           MHD_OPTION_END);
   1054   else
   1055     d = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD
   1056                           | ((unsigned int) pollType)
   1057                           | MHD_USE_ERROR_LOG,
   1058                           *pport, NULL, NULL,
   1059                           &ahc_echo, NULL,
   1060                           MHD_OPTION_THREAD_POOL_SIZE,
   1061                           testNumThreadsForPool (pollType),
   1062                           MHD_OPTION_URI_LOG_CALLBACK, &log_cb, NULL,
   1063                           MHD_OPTION_END);
   1064 
   1065   if (NULL == d)
   1066   {
   1067     fprintf (stderr, "Failed to start MHD daemon, errno=%d.\n", errno);
   1068     abort ();
   1069   }
   1070 
   1071   if (0 == *pport)
   1072   {
   1073     dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT);
   1074     if ((NULL == dinfo) || (0 == dinfo->port) )
   1075     {
   1076       fprintf (stderr, "MHD_get_daemon_info() failed.\n");
   1077       abort ();
   1078     }
   1079     *pport = dinfo->port;
   1080     if (0 == global_port)
   1081       global_port = *pport; /* Reuse the same port for all tests */
   1082   }
   1083 
   1084   return d;
   1085 }
   1086 
   1087 
   1088 /* Test runners */
   1089 
   1090 
   1091 static unsigned int
   1092 testExternalGet (void)
   1093 {
   1094   struct MHD_Daemon *d;
   1095   uint16_t d_port = global_port; /* Daemon's port */
   1096 
   1097   d = startTestMhdDaemon (testMhdThreadExternal, testMhdPollBySelect, &d_port);
   1098 
   1099   return performTestQueries (d, d_port);
   1100 }
   1101 
   1102 
   1103 static unsigned int
   1104 testInternalGet (enum testMhdPollType pollType)
   1105 {
   1106   struct MHD_Daemon *d;
   1107   uint16_t d_port = global_port; /* Daemon's port */
   1108 
   1109   d = startTestMhdDaemon (testMhdThreadInternal, pollType,
   1110                           &d_port);
   1111 
   1112   return performTestQueries (d, d_port);
   1113 }
   1114 
   1115 
   1116 static unsigned int
   1117 testMultithreadedGet (enum testMhdPollType pollType)
   1118 {
   1119   struct MHD_Daemon *d;
   1120   uint16_t d_port = global_port; /* Daemon's port */
   1121 
   1122   d = startTestMhdDaemon (testMhdThreadInternalPerConnection, pollType,
   1123                           &d_port);
   1124   return performTestQueries (d, d_port);
   1125 }
   1126 
   1127 
   1128 static unsigned int
   1129 testMultithreadedPoolGet (enum testMhdPollType pollType)
   1130 {
   1131   struct MHD_Daemon *d;
   1132   uint16_t d_port = global_port; /* Daemon's port */
   1133 
   1134   d = startTestMhdDaemon (testMhdThreadInternalPool, pollType,
   1135                           &d_port);
   1136   return performTestQueries (d, d_port);
   1137 }
   1138 
   1139 
   1140 int
   1141 main (int argc, char *const *argv)
   1142 {
   1143   unsigned int errorCount = 0;
   1144   unsigned int test_result = 0;
   1145   int verbose = 0;
   1146 
   1147   if ((NULL == argv) || (0 == argv[0]))
   1148     return 99;
   1149   oneone = ! has_in_name (argv[0], "10");
   1150   conn_close = has_in_name (argv[0], "_close");
   1151   if (! conn_close && ! has_in_name (argv[0], "_keep_alive"))
   1152     return 99;
   1153   verbose = ! (has_param (argc, argv, "-q") ||
   1154                has_param (argc, argv, "--quiet") ||
   1155                has_param (argc, argv, "-s") ||
   1156                has_param (argc, argv, "--silent"));
   1157 
   1158   test_global_init ();
   1159 
   1160   /* Could be set to non-zero value to enforce using specific port
   1161    * in the test */
   1162   global_port = 0;
   1163   test_result = testExternalGet ();
   1164   if (test_result)
   1165   {
   1166     fprintf (stderr, "FAILED: testExternalGet () - %u.\n", test_result);
   1167     return test_result;
   1168   }
   1169   else if (verbose)
   1170     printf ("PASSED: testExternalGet ().\n");
   1171   errorCount += test_result;
   1172   if (MHD_YES == MHD_is_feature_supported (MHD_FEATURE_THREADS))
   1173   {
   1174     test_result = testInternalGet (testMhdPollBySelect);
   1175     if (test_result)
   1176       fprintf (stderr, "FAILED: testInternalGet (testMhdPollBySelect) - %u.\n",
   1177                test_result);
   1178     else if (verbose)
   1179       printf ("PASSED: testInternalGet (testMhdPollBySelect).\n");
   1180     errorCount += test_result;
   1181     test_result = testMultithreadedPoolGet (testMhdPollBySelect);
   1182     if (test_result)
   1183       fprintf (stderr,
   1184                "FAILED: testMultithreadedPoolGet (testMhdPollBySelect) - %u.\n",
   1185                test_result);
   1186     else if (verbose)
   1187       printf ("PASSED: testMultithreadedPoolGet (testMhdPollBySelect).\n");
   1188     errorCount += test_result;
   1189     test_result = testMultithreadedGet (testMhdPollBySelect);
   1190     if (test_result)
   1191       fprintf (stderr,
   1192                "FAILED: testMultithreadedGet (testMhdPollBySelect) - %u.\n",
   1193                test_result);
   1194     else if (verbose)
   1195       printf ("PASSED: testMultithreadedGet (testMhdPollBySelect).\n");
   1196     errorCount += test_result;
   1197     if (MHD_YES == MHD_is_feature_supported (MHD_FEATURE_POLL))
   1198     {
   1199       test_result = testInternalGet (testMhdPollByPoll);
   1200       if (test_result)
   1201         fprintf (stderr, "FAILED: testInternalGet (testMhdPollByPoll) - %u.\n",
   1202                  test_result);
   1203       else if (verbose)
   1204         printf ("PASSED: testInternalGet (testMhdPollByPoll).\n");
   1205       errorCount += test_result;
   1206     }
   1207     if (MHD_YES == MHD_is_feature_supported (MHD_FEATURE_EPOLL))
   1208     {
   1209       test_result = testInternalGet (testMhdPollByEpoll);
   1210       if (test_result)
   1211         fprintf (stderr, "FAILED: testInternalGet (testMhdPollByEpoll) - %u.\n",
   1212                  test_result);
   1213       else if (verbose)
   1214         printf ("PASSED: testInternalGet (testMhdPollByEpoll).\n");
   1215       errorCount += test_result;
   1216     }
   1217   }
   1218   if (0 != errorCount)
   1219     fprintf (stderr,
   1220              "Error (code: %u)\n",
   1221              errorCount);
   1222   else if (verbose)
   1223     printf ("All tests passed.\n");
   1224 
   1225   test_global_cleanup ();
   1226 
   1227   return (errorCount == 0) ? 0 : 1;       /* 0 == pass */
   1228 }