gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[libmicrohttpd] 252/335: expand test suite


From: gnunet
Subject: [libmicrohttpd] 252/335: expand test suite
Date: Sat, 27 Jul 2024 22:02:28 +0200

This is an automated email from the git hooks/post-receive script.

grothoff pushed a commit to tag stf-m2
in repository libmicrohttpd.

commit fcb47a6a1c88e9fef2a7a167b4c469edb67da50a
Author: Christian Grothoff <christian@grothoff.org>
AuthorDate: Sun Jul 21 16:20:06 2024 +0200

    expand test suite
---
 src/tests/basic/libtest.h             | 127 +++++++++
 src/tests/basic/libtest_convenience.c | 467 +++++++++++++++++++++++++++++++++-
 src/tests/basic/test_client_server.c  |  22 +-
 3 files changed, 610 insertions(+), 6 deletions(-)

diff --git a/src/tests/basic/libtest.h b/src/tests/basic/libtest.h
index 4f0187a1..213590fe 100644
--- a/src/tests/basic/libtest.h
+++ b/src/tests/basic/libtest.h
@@ -74,6 +74,50 @@ MHDT_client_get_root (void *cls,
                       const struct MHDT_PhaseContext *pc);
 
 
+/**
+ * Run request against the base URL with the
+ * query arguments from @a cls appended to it.
+ * Expect the server to return a 200 OK response.
+ *
+ * @param cls closure with query parameters to append
+ *  to the base URL of the server
+ * @param pc context for the client
+ * @return error message, NULL on success
+ */
+const char *
+MHDT_client_get_with_query (void *cls,
+                            const struct MHDT_PhaseContext *pc);
+
+
+/**
+ * Run request against the base URL with the
+ * custom header from @a cls set.
+ * Expect the server to return a 204 No content response.
+ *
+ * @param cls closure with custom header to set
+ * @param pc context for the client
+ * @return error message, NULL on success
+ */
+const char *
+MHDT_client_set_header (void *cls,
+                        const struct MHDT_PhaseContext *pc);
+
+
+/**
+ * Run request against the base URL and expect the
+ * header from @a cls to be set in the 200 OK response.
+ *
+ * @param cls closure with custom header to set,
+ *      must be of the format "$KEY:$VALUE"
+ *      without space before the "$VALUE".
+ * @param pc context for the client
+ * @return error message, NULL on success
+ */
+const char *
+MHDT_client_expect_header (void *cls,
+                           const struct MHDT_PhaseContext *pc);
+
+
 /**
  * A phase defines some server and client-side
  * behaviors to execute.
@@ -148,6 +192,89 @@ MHDT_server_reply_text (
   uint_fast64_t upload_size);
 
 
+/**
+ * Returns an emtpy response with a custom header
+ * set from @a cls and the #MHD_HTTP_STATUS_NO_CONTENT.
+ *
+ * @param cls header in the format "$NAME:$VALUE"
+ *        without a space before "$VALUE".
+ * @param request the request object
+ * @param path the requested uri (without arguments after "?")
+ * @param method the HTTP method used (#MHD_HTTP_METHOD_GET,
+ *        #MHD_HTTP_METHOD_PUT, etc.)
+ * @param upload_size the size of the message upload content payload,
+ *                    #MHD_SIZE_UNKNOWN for chunked uploads (if the
+ *                    final chunk has not been processed yet)
+ * @return action how to proceed, NULL
+ *         if the request must be aborted due to a serious
+ *         error while handling the request (implies closure
+ *         of underling data stream, for HTTP/1.1 it means
+ *         socket closure).
+ */
+const struct MHD_Action *
+MHDT_server_reply_with_header (
+  void *cls,
+  struct MHD_Request *MHD_RESTRICT request,
+  const struct MHD_String *MHD_RESTRICT path,
+  enum MHD_HTTP_Method method,
+  uint_fast64_t upload_size);
+
+
+/**
+ * Checks that the request query arguments match the
+ * arguments given in @a cls.
+ * request.
+ *
+ * @param cls string with expected arguments separated by '&' and '='. URI 
encoding is NOT supported.
+ * @param request the request object
+ * @param path the requested uri (without arguments after "?")
+ * @param method the HTTP method used (#MHD_HTTP_METHOD_GET,
+ *        #MHD_HTTP_METHOD_PUT, etc.)
+ * @param upload_size the size of the message upload content payload,
+ *                    #MHD_SIZE_UNKNOWN for chunked uploads (if the
+ *                    final chunk has not been processed yet)
+ * @return action how to proceed, NULL
+ *         if the request must be aborted due to a serious
+ *         error while handling the request (implies closure
+ *         of underling data stream, for HTTP/1.1 it means
+ *         socket closure).
+ */
+const struct MHD_Action *
+MHDT_server_reply_check_query (
+  void *cls,
+  struct MHD_Request *MHD_RESTRICT request,
+  const struct MHD_String *MHD_RESTRICT path,
+  enum MHD_HTTP_Method method,
+  uint_fast64_t upload_size);
+
+
+/**
+ * Checks that the client request includes the given
+ * custom header.  If so, returns #MHD_SC_OK with "ok".
+ *
+ * @param cls expected header with "$NAME:$VALUE" format.
+ * @param request the request object
+ * @param path the requested uri (without arguments after "?")
+ * @param method the HTTP method used (#MHD_HTTP_METHOD_GET,
+ *        #MHD_HTTP_METHOD_PUT, etc.)
+ * @param upload_size the size of the message upload content payload,
+ *                    #MHD_SIZE_UNKNOWN for chunked uploads (if the
+ *                    final chunk has not been processed yet)
+ * @return action how to proceed, NULL
+ *         if the request must be aborted due to a serious
+ *         error while handling the request (implies closure
+ *         of underling data stream, for HTTP/1.1 it means
+ *         socket closure).
+ */
+const struct MHD_Action *
+MHDT_server_reply_check_header (
+  void *cls,
+  struct MHD_Request *MHD_RESTRICT request,
+  const struct MHD_String *MHD_RESTRICT path,
+  enum MHD_HTTP_Method method,
+  uint_fast64_t upload_size);
+
+
 /**
  * Initialize options for an MHD daemon for a test.
  *
diff --git a/src/tests/basic/libtest_convenience.c 
b/src/tests/basic/libtest_convenience.c
index a0181901..85b751cb 100644
--- a/src/tests/basic/libtest_convenience.c
+++ b/src/tests/basic/libtest_convenience.c
@@ -95,6 +95,324 @@ MHDT_server_reply_text (
 }
 
 
+const struct MHD_Action *
+MHDT_server_reply_with_header (
+  void *cls,
+  struct MHD_Request *MHD_RESTRICT request,
+  const struct MHD_String *MHD_RESTRICT path,
+  enum MHD_HTTP_Method method,
+  uint_fast64_t upload_size)
+{
+  const char *header = cls;
+  size_t hlen = strlen (header) + 1;
+  char name[hlen];
+  const char *colon = strchr (header, ':');
+  const char *value;
+  struct MHD_Response *resp;
+
+  memcpy (name,
+          header,
+          hlen);
+  name[colon - header] = '\0';
+  value = &name[colon - header + 1];
+
+  resp = MHD_response_from_empty (MHD_HTTP_STATUS_NO_CONTENT);
+  if (MHD_SC_OK !=
+      MHD_response_add_header (resp,
+                               name,
+                               value))
+    return NULL;
+  return MHD_action_from_response (
+    request,
+    resp);
+}
+
+
+const struct MHD_Action *
+MHDT_server_reply_check_query (
+  void *cls,
+  struct MHD_Request *MHD_RESTRICT request,
+  const struct MHD_String *MHD_RESTRICT path,
+  enum MHD_HTTP_Method method,
+  uint_fast64_t upload_size)
+{
+  const char *equery = cls;
+  size_t qlen = strlen (equery) + 1;
+  char qc[qlen];
+
+  memcpy (qc,
+          equery,
+          qlen);
+  for (const char *tok = strtok (qc, "&");
+       NULL != tok;
+       tok = strtok (NULL, "&"))
+  {
+    const char *end;
+    const struct MHD_StringNullable *sn;
+    const char *val;
+
+    end = strchr (tok, '=');
+    if (NULL == end)
+    {
+      end = &tok[strlen (tok)];
+      val = NULL;
+    }
+    else
+    {
+      val = end + 1;
+    }
+    {
+      size_t alen = end - tok;
+      char arg[alen + 1];
+
+      memcpy (arg,
+              tok,
+              alen);
+      arg[alen] = '\0';
+      sn = MHD_request_get_value (request,
+                                  MHD_VK_GET_ARGUMENT,
+                                  arg);
+      if (NULL == val)
+      {
+        if (NULL != sn->cstr)
+        {
+          fprintf (stderr,
+                   "NULL expected for key %s, got %s\n",
+                   arg,
+                   sn->cstr);
+          return NULL;
+        }
+      }
+      else
+      {
+        if (NULL == sn->cstr)
+        {
+          fprintf (stderr,
+                   "%s expected for key %s, got NULL\n",
+                   val,
+                   arg);
+          return NULL;
+        }
+        if (0 != strcmp (val,
+                         sn->cstr))
+        {
+          fprintf (stderr,
+                   "%s expected for key %s, got %s\n",
+                   val,
+                   arg,
+                   sn->cstr);
+          return NULL;
+        }
+      }
+    }
+  }
+
+  return MHD_action_from_response (
+    request,
+    MHD_response_from_buffer_static (MHD_HTTP_STATUS_OK,
+                                     strlen ("ok"),
+                                     "ok"));
+}
+
+
+const struct MHD_Action *
+MHDT_server_reply_check_header (
+  void *cls,
+  struct MHD_Request *MHD_RESTRICT request,
+  const struct MHD_String *MHD_RESTRICT path,
+  enum MHD_HTTP_Method method,
+  uint_fast64_t upload_size)
+{
+  // FIXME: actual check logic missing...
+  return MHD_action_from_response (
+    request,
+    MHD_response_from_buffer_static (MHD_HTTP_STATUS_OK,
+                                     strlen ("ok"),
+                                     "ok"));
+}
+
+
+/**
+ * Closure for the write_cb().
+ */
+struct WriteBuffer
+{
+  /**
+   * Where to store the response.
+   */
+  char *buf;
+
+  /**
+   * Number of bytes in @e buf.
+   */
+  size_t len;
+
+  /**
+   * Current write offset in @e buf.
+   */
+  size_t pos;
+
+  /**
+   * Set to non-zero on errors (buffer full).
+   */
+  int err;
+};
+
+
+/**
+ * Callback for CURLOPT_WRITEFUNCTION processing
+ * data downloaded from the HTTP server.
+ *
+ * @param ptr data uploaded
+ * @param size size of a member
+ * @param nmemb number of members
+ * @param stream must be a `struct WriteBuffer`
+ * @return bytes processed (size*nmemb) or error
+ */
+static size_t
+write_cb (void *ptr,
+          size_t size,
+          size_t nmemb,
+          void *stream)
+{
+  struct WriteBuffer *wb = stream;
+  size_t prod = size * nmemb;
+
+  if ( (prod / size != nmemb) ||
+       (wb->pos + prod < wb->pos) ||
+       (wb->pos + prod > wb->len) )
+  {
+    wb->err = 1;
+    return CURLE_WRITE_ERROR;
+  }
+  memcpy (wb->buf + wb->pos,
+          ptr,
+          prod);
+  wb->pos += prod;
+  return prod;
+}
+
+
+/**
+ * Declare variables needed to check a download.
+ *
+ * @param text text data we expect to receive
+ */
+#define DECLARE_WB(text) \
+        size_t wb_tlen = strlen (text); \
+        char wb_buf[wb_tlen];           \
+        struct WriteBuffer wb = {       \
+          .buf = wb_buf,                \
+          .len = wb_tlen                \
+        }
+
+
+/**
+ * Set CURL options to the write_cb() and wb buffer
+ * to check a download.
+ *
+ * @param c CURL handle
+ */
+#define SETUP_WB(c) do {                       \
+          if (CURLE_OK !=                              \
+              curl_easy_setopt (c,                     \
+                                CURLOPT_WRITEFUNCTION, \
+                                &write_cb))            \
+          {                                            \
+            curl_easy_cleanup (c);                     \
+            return "Failed to set write callback for curl request"; \
+          }                                            \
+          if (CURLE_OK !=                              \
+              curl_easy_setopt (c,                     \
+                                CURLOPT_WRITEDATA,     \
+                                &wb))                  \
+          {                                            \
+            curl_easy_cleanup (c);                     \
+            return "Failed to set write buffer for curl request"; \
+          }                                                      \
+} while (0)
+
+/**
+ * Check that we received the expected text.
+ *
+ * @param text text we expect to have downloaded
+ */
+#define CHECK_WB(text) do {      \
+          if ( (wb_tlen != wb.pos) ||    \
+               (0 != wb.err) ||          \
+               (0 != memcmp (text,       \
+                             wb_buf,     \
+                             wb_tlen)) ) \
+          return "Downloaded data does not match expectations"; \
+} while (0)
+
+
+/**
+ * Perform the curl request @a c and cleanup and
+ * return an error if the request failed.
+ *
+ * @param c request to perform
+ */
+#define PERFORM_REQUEST(c) do {                  \
+          CURLcode res;                          \
+          res = curl_easy_perform (c);           \
+          if (CURLE_OK != res)                   \
+          {                                      \
+            curl_easy_cleanup (c);               \
+            return "Failed to fetch URL";        \
+          }                                      \
+} while (0)
+
+/**
+ * Check that the curl request @a c completed
+ * with the @a want status code.
+ * Return an error if the status does not match.
+ *
+ * @param c request to check
+ * @param want desired HTTP status code
+ */
+#define CHECK_STATUS(c,want) do {                \
+          if (! check_status (c, want))          \
+          {                                      \
+            curl_easy_cleanup (c);               \
+            return "Unexpected HTTP status";     \
+          }                                      \
+} while (0)
+
+/**
+ * Chec that the HTTP status of @a c matches @a expected_status
+ *
+ * @param a completed CURL request
+ * @param expected_status the expected HTTP response code
+ * @return true if the status matches
+ */
+static bool
+check_status (CURL *c,
+              unsigned int expected_status)
+{
+  long status;
+
+  if (CURLE_OK !=
+      curl_easy_getinfo (c,
+                         CURLINFO_RESPONSE_CODE,
+                         &status))
+  {
+    fprintf (stderr,
+             "Failed to get HTTP status");
+    return false;
+  }
+  if (status != expected_status)
+  {
+    fprintf (stderr,
+             "Expected %u, got %ld\n",
+             expected_status,
+             status);
+    return false;
+  }
+  return true;
+}
+
+
 const char *
 MHDT_client_get_root (
   void *cls,
@@ -102,7 +420,77 @@ MHDT_client_get_root (
 {
   const char *text = cls;
   CURL *c;
+  DECLARE_WB (text);
+
+  c = curl_easy_init ();
+  if (NULL == c)
+    return "Failed to initialize Curl handle";
+  if (CURLE_OK !=
+      curl_easy_setopt (c,
+                        CURLOPT_URL,
+                        pc->base_url))
+  {
+    curl_easy_cleanup (c);
+    return "Failed to set URL for curl request";
+  }
+  SETUP_WB (c);
+  PERFORM_REQUEST (c);
+  CHECK_STATUS (c, MHD_HTTP_STATUS_OK);
+  curl_easy_cleanup (c);
+  CHECK_WB (text);
+  return NULL;
+}
+
+
+const char *
+MHDT_client_get_with_query (
+  void *cls,
+  const struct MHDT_PhaseContext *pc)
+{
+  const char *args = cls;
+  size_t alen = strlen (args);
+  CURL *c;
+  size_t blen = strlen (pc->base_url);
+  char u[alen + blen + 1];
+  const char *text = "ok";
+  DECLARE_WB (text);
+
+  memcpy (u,
+          pc->base_url,
+          blen);
+  memcpy (u + blen,
+          args,
+          alen);
+  u[alen + blen] = '\0';
+  c = curl_easy_init ();
+  if (NULL == c)
+    return "Failed to initialize Curl handle";
+
+  if (CURLE_OK !=
+      curl_easy_setopt (c,
+                        CURLOPT_URL,
+                        u))
+  {
+    curl_easy_cleanup (c);
+    return "Failed to set URL for curl request";
+  }
+  SETUP_WB (c);
+  PERFORM_REQUEST (c);
+  CHECK_STATUS (c, MHD_HTTP_STATUS_OK);
+  curl_easy_cleanup (c);
+  CHECK_WB (text);
+  return NULL;
+}
+
+
+const char *
+MHDT_client_set_header (void *cls,
+                        const struct MHDT_PhaseContext *pc)
+{
+  const char *hdr = cls;
+  CURL *c;
   CURLcode res;
+  struct curl_slist *slist;
 
   c = curl_easy_init ();
   if (NULL == c)
@@ -115,10 +503,85 @@ MHDT_client_get_root (
     curl_easy_cleanup (c);
     return "Failed to set URL for curl request";
   }
+  slist = curl_slist_append (NULL,
+                             hdr);
+  if (CURLE_OK !=
+      curl_easy_setopt (c,
+                        CURLOPT_HTTPHEADER,
+                        slist))
+  {
+    curl_easy_cleanup (c);
+    curl_slist_free_all (slist);
+    return "Failed to set custom header for curl request";
+  }
   res = curl_easy_perform (c);
-  curl_easy_cleanup (c);
+  curl_slist_free_all (slist);
   if (CURLE_OK != res)
+  {
+    curl_easy_cleanup (c);
     return "Failed to fetch URL";
-  (void) text; // FIXME: check data returned was 'text'
+  }
+  CHECK_STATUS (c,
+                MHD_HTTP_STATUS_NO_CONTENT);
+  curl_easy_cleanup (c);
+  return NULL;
+}
+
+
+const char *
+MHDT_client_expect_header (void *cls,
+                           const struct MHDT_PhaseContext *pc)
+{
+  const char *hdr = cls;
+  size_t hlen = strlen (hdr) + 1;
+  char key[hlen];
+  const char *colon = strchr (hdr, ':');
+  const char *value;
+  CURL *c;
+  bool found = false;
+
+  if (NULL == colon)
+    return "Invalid expected header passed";
+  memcpy (key,
+          hdr,
+          hlen);
+  key[colon - hdr] = '\0';
+  value = &key[colon - hdr + 1];
+  c = curl_easy_init ();
+  if (NULL == c)
+    return "Failed to initialize Curl handle";
+  if (CURLE_OK !=
+      curl_easy_setopt (c,
+                        CURLOPT_URL,
+                        pc->base_url))
+  {
+    curl_easy_cleanup (c);
+    return "Failed to set URL for curl request";
+  }
+  PERFORM_REQUEST (c);
+  CHECK_STATUS (c,
+                MHD_HTTP_STATUS_NO_CONTENT);
+  for (size_t index = 0; ! found; index++)
+  {
+    CURLHcode rval;
+    struct curl_header *hout;
+
+    rval = curl_easy_header (c,
+                             key,
+                             index,
+                             CURLH_HEADER,
+                             -1 /* last request */,
+                             &hout);
+    if (CURLHE_BADINDEX == rval)
+      break;
+    found = (0 == strcmp (value,
+                          hout->value));
+  }
+  if (! found)
+  {
+    curl_easy_cleanup (c);
+    return "Expected HTTP response header not found";
+  }
+  curl_easy_cleanup (c);
   return NULL;
 }
diff --git a/src/tests/basic/test_client_server.c 
b/src/tests/basic/test_client_server.c
index b4f0ee93..349cf94b 100644
--- a/src/tests/basic/test_client_server.c
+++ b/src/tests/basic/test_client_server.c
@@ -1,6 +1,6 @@
 /*
   This file is part of GNU libmicrohttpd
-  Copyright (C) 2016, 2024 Evgeny Grin (Karlson2k)
+  Copyright (C) 2016, 2024 Christian Grothoff & Evgeny Grin (Karlson2k)
 
   GNU libmicrohttpd is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
@@ -42,19 +42,33 @@ main (int argc, char *argv[])
     },
     // Basic upload
     // HTTP client header
+    {
+      .label = "server response with custom header",
+      .server_cb = &MHDT_server_reply_check_header,
+      .server_cb_cls = "C-Header:testvalue",
+      .client_cb = &MHDT_client_set_header,
+      .client_cb_cls = "C-Header:testvalue",
+      .timeout_ms = 5,
+    },
     // Response with custom header
-#if 0
+    {
+      .label = "server response with custom header",
+      .server_cb = &MHDT_server_reply_with_header,
+      .server_cb_cls = "X-Header:testvalue",
+      .client_cb = &MHDT_client_expect_header,
+      .client_cb_cls = "X-Header:testvalue",
+      .timeout_ms = 5,
+    },
     // URL with query parameters
     {
       .label = "URL with query parameters",
       .server_cb = &MHDT_server_reply_check_query,
-      .server_cb_cls = "TODO",
+      .server_cb_cls = "a=b&c",
       .client_cb = &MHDT_client_get_with_query,
       .client_cb_cls = "a=b&c",
       .timeout_ms = 5,
       .num_clients = 10
     },
-#endif
     // chunked upload
     // chunked download
     {

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]