libmicrohttpd

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

test_get_close_keep_alive.c (37812B)


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