first test against test framework

This commit is contained in:
Christian Grothoff
2024-07-14 16:32:59 +02:00
parent 5f83783100
commit 45fb407b49
7 changed files with 1021 additions and 2 deletions
+17 -1
View File
@@ -5515,6 +5515,22 @@ MHD_response_from_buffer (
MHD_FN_PAR_IN_SIZE_ (3,2);
/**
* Create a response object with body that is a
* statically allocated buffer that never needs to
* be freed as its lifetime exceeds that of the
* daemon.
*
* The response object can be extended with header information and then be used
* any number of times.
* @param sc status code to use for the response
* @param len number of bytes in @a buf
* @param buf buffer with response payload
*/
#define MHD_response_from_buffer_static(sc, len, buf) \
MHD_response_from_buffer (sc, len, buf, NULL, NULL)
/**
* Create a response object with empty (zero size) body.
*
@@ -5523,7 +5539,7 @@ MHD_FN_PAR_IN_SIZE_ (3,2);
* @param sc status code to use for the response
*/
#define MHD_response_from_empty(sc) \
MHD_response_from_buffer (sc, 0, "", NULL, NULL)
MHD_response_from_buffer_static (sc, 0, "")
/**
+18
View File
@@ -3,6 +3,7 @@ EMPTY_ITEM =
AM_CPPFLAGS = \
-I$(top_srcdir)/src/include \
-I$(top_srcdir)/src/mhd2 \
-DMHD_CPU_COUNT=$(CPU_COUNT) \
$(CPPFLAGS_ac)
@@ -16,6 +17,16 @@ if USE_COVERAGE
AM_CFLAGS += -fprofile-arcs -ftest-coverage
endif
noinst_LTLIBRARIES = \
libmhdt.la
libmhdt_la_SOURCES = \
libtest.c libtest.h \
libtest_convenience.c
libmhdt_la_LIBADD = \
-lpthread \
-lcurl
LDADD = $(top_builddir)/src/mhd2/libmicrohttpd2.la
$(top_builddir)/src/mhd2/libmicrohttpd2.la: $(top_builddir)/src/mhd2/Makefile
@@ -23,6 +34,7 @@ $(top_builddir)/src/mhd2/libmicrohttpd2.la: $(top_builddir)/src/mhd2/Makefile
$(am__cd) $(top_builddir)/src/mhd2 && $(MAKE) $(AM_MAKEFLAGS) libmicrohttpd2.la
check_PROGRAMS = \
test_client_server \
test_create_destroy \
test_create_start_destroy \
test_create_destroy_ipv4 \
@@ -103,6 +115,12 @@ endif
TESTS = $(check_PROGRAMS)
test_client_server_SOURCES = \
test_client_server.c
test_client_server_LDADD = \
libmhdt.la \
$(top_builddir)/src/mhd2/libmicrohttpd2.la
# The universal sources used in all tests
basic_test_sources = test_basic_checks.c
+583
View File
@@ -0,0 +1,583 @@
/*
This file is part of GNU libmicrohttpd
Copyright (C) 2024 Christian Grothoff
GNU libmicrohttpd is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
GNU libmicrohttpd is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* @file libtest.c
* @brief testing harness with clients against server
* @author Christian Grothoff
*/
#include <pthread.h>
#include <stdbool.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include "microhttpd2.h"
#include "libtest.h"
/**
* A semaphore.
*/
struct Semaphore
{
/**
* Mutex for the semaphore.
*/
pthread_mutex_t mutex;
/**
* Condition variable for the semaphore.
*/
pthread_cond_t cv;
/**
* Counter of the semaphore.
*/
unsigned int ctr;
};
/**
* Check that @a cond is true, otherwise abort().
*
* @param cond condition to check
* @param filename filename to log
* @param line line number to log
*/
static void
test_check_ (bool cond,
const char *filename,
unsigned int line)
{
if (! cond)
{
fprintf (stderr,
"Assertion failed at %s:%u\n",
filename,
line);
abort ();
}
}
/**
* Checks that @a cond is true and otherwise aborts.
*
* @param cond condition to check
*/
#define test_check(cond) \
test_check_ (cond, __FILE__, __LINE__)
/**
* Initialize a semaphore @a sem with a value of @a val.
*
* @param[out] sem semaphore to initialize
* @param val initial value of the semaphore
*/
static void
semaphore_create (struct Semaphore *sem,
unsigned int val)
{
test_check (0 ==
pthread_mutex_init (&sem->mutex,
NULL));
test_check (0 ==
pthread_cond_init (&sem->cv,
NULL));
sem->ctr = val;
}
/**
* Decrement semaphore, blocks until this is possible.
*
* @param[in,out] sem semaphore to decrement
*/
static void
semaphore_down (struct Semaphore *sem)
{
test_check (0 == pthread_mutex_lock (&sem->mutex));
while (0 == sem->ctr)
{
pthread_cond_wait (&sem->cv,
&sem->mutex);
}
sem->ctr--;
test_check (0 == pthread_mutex_unlock (&sem->mutex));
}
/**
* Increment semaphore, blocks until this is possible.
*
* @param[in,out] sem semaphore to decrement
*/
static void
semaphore_up (struct Semaphore *sem)
{
test_check (0 == pthread_mutex_lock (&sem->mutex));
sem->ctr++;
test_check (0 == pthread_mutex_unlock (&sem->mutex));
pthread_cond_signal (&sem->cv);
}
/**
* Release resources used by @a sem.
*
* @param[in] sem semaphore to release (except the memory itself)
*/
static void
semaphore_destroy (struct Semaphore *sem)
{
test_check (0 == pthread_cond_destroy (&sem->cv));
test_check (0 == pthread_mutex_destroy (&sem->mutex));
}
/**
* Context for the implementation of the HTTP server.
*/
struct ServerContext
{
/**
* Semaphore the client raises when it goes into the
* next phase.
*/
struct Semaphore client_sem;
/**
* Semaphore the server raises when it goes into the
* next phase.
*/
struct Semaphore server_sem;
/**
* Current phase of the server.
*/
const struct MHDT_Phase *phase;
/**
* Main function to run the server.
*/
MHDT_ServerRunner run_cb;
/**
* Closure for @e run_cb.
*/
void *run_cb_cls;
/**
* The daemon we are running.
*/
struct MHD_Daemon *d;
/**
* Signal for server termination.
*/
int finsig;
};
/**
* A client has requested the given url using the given method
* (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT,
* #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc).
* If @a upload_size is not zero and response action is provided by this
* callback, then upload will be discarded and the stream (the connection for
* HTTP/1.1) will be closed after sending the response.
*
* @param cls argument given together with the function
* pointer when the handler was registered with MHD
* @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).
*/
static const struct MHD_Action *
server_req_cb (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)
{
struct ServerContext *sc = cls;
if (NULL == sc->phase->label)
return NULL;
return sc->phase->server_cb (sc->phase->server_cb_cls,
request,
path,
method,
upload_size);
}
/**
* Closure for run_single_client()
*/
struct ClientContext
{
/**
* Test phase to run.
*/
const struct MHDT_Phase *phase;
/**
* Phase and client specific context.
*/
struct MHDT_PhaseContext pc;
/**
* Pipe to use to signal that the thread has
* finished.
*/
int p2;
/**
* Set to true on success.
*/
bool status;
};
/**
* Runs the logic for a single client in a thread.
*
* @param cls a `struct ClientContext`
* @return NULL
*/
static void *
run_single_client (void *cls)
{
struct ClientContext *cc = cls;
const char *err;
err = cc->phase->client_cb (cc->phase->client_cb_cls,
&cc->pc);
if (NULL != err)
{
fprintf (stderr,
"Client %u failed in phase %s: %s\n",
cc->pc.client_id,
cc->phase->label,
err);
/* This is a blocking write, thus must succeed */
test_check (1 ==
write (cc->p2,
"e",
1));
return NULL;
}
cc->status = true;
/* This is a blocking write, thus must succeed */
test_check (1 ==
write (cc->p2,
"s",
1));
return NULL;
}
/**
* Creates a pipe with a non-blocking read end.
*
* @param p pipe to initialize
*/
static void
make_pipe (int p[2])
{
int flags;
test_check (0 ==
pipe (p));
flags = fcntl (p[0],
F_GETFL);
flags |= O_NONBLOCK;
test_check (0 ==
fcntl (p[0],
F_SETFL,
flags));
}
/**
* Run client processes for the given test @a phase
*
* @param phase test phase to run
* @param pc context to give to clients
*/
static bool
run_client_phase (const struct MHDT_Phase *phase,
const struct MHDT_PhaseContext *pc)
{
unsigned int num_clients
= (0 == phase->num_clients)
? 1
: phase->num_clients;
unsigned int clients_left = 0;
struct ClientContext cctxs[num_clients];
pthread_t clients[num_clients];
int p[2];
unsigned int i;
bool ret = true;
make_pipe (p);
for (i = 0; i<num_clients; i++)
{
cctxs[i].phase = phase;
cctxs[i].pc = *pc;
cctxs[i].pc.client_id = i;
if (0 !=
pthread_create (&clients[i],
NULL,
&run_single_client,
&cctxs[i]))
goto cleanup;
clients_left++;
}
/* 0 for timeout_ms means no timeout, we deliberately
underflow to MAX_UINT in this case... */
for (i = phase->timeout_ms - 1; i>0; i--)
{
struct timespec ms = {
.tv_nsec = 1000 * 1000
};
char c;
nanosleep (&ms,
NULL);
/* This is a non-blocking read */
while (1 == read (p[0],
&c,
1))
clients_left--;
if (0 == clients_left)
break;
}
if (0 != clients_left)
{
fprintf (stderr,
"Timeout (%u ms) in phase %s: %u clients still running\n",
phase->timeout_ms,
phase->label,
clients_left);
exit (1);
}
cleanup:
for (i = 0; i<num_clients; i++)
{
void *res;
test_check (0 ==
pthread_join (clients[i],
&res));
if (! cctxs[i].status)
ret = false;
}
test_check (0 == close (p[0]));
test_check (0 == close (p[1]));
return ret;
}
/**
* Thread that switches the server to the next phase
* as needed.
*
* @param cls a `struct ServerContext`
* @return NULL
*/
static void *
server_phase_logic (void *cls)
{
struct ServerContext *ctx = cls;
unsigned int i;
for (i = 0; NULL == ctx->phase->label; i++)
{
semaphore_down (&ctx->client_sem);
ctx->phase++;
semaphore_up (&ctx->server_sem);
}
return NULL;
}
/**
* Thread that runs the MHD daemon.
*
* @param cls a `struct ServerContext`
* @return NULL
*/
static void *
server_run_logic (void *cls)
{
struct ServerContext *ctx = cls;
ctx->run_cb (ctx->run_cb_cls,
ctx->finsig,
ctx->d);
return NULL;
}
int
MHDT_test (MHDT_ServerSetup ss_cb,
void *ss_cb_cls,
MHDT_ServerRunner run_cb,
void *run_cb_cls,
const struct MHDT_Phase *phases)
{
struct ServerContext ctx = {
.run_cb = run_cb,
.run_cb_cls = run_cb_cls,
.phase = &phases[0]
};
struct MHD_Daemon *d;
int res;
const char *err;
enum MHD_StatusCode sc;
pthread_t server_phase_thr;
pthread_t server_run_thr;
struct MHDT_PhaseContext pc;
char base_url[128];
unsigned int i;
int p[2];
make_pipe (p);
semaphore_create (&ctx.server_sem,
0);
semaphore_create (&ctx.client_sem,
0);
d = MHD_daemon_create (&server_req_cb,
&ctx);
if (NULL == d)
exit (77);
err = ss_cb (ss_cb_cls,
d);
if (NULL != err)
{
fprintf (stderr,
"Failed to setup server: %s\n",
err);
return 1;
}
sc = MHD_daemon_start (d);
if (MHD_SC_OK != sc)
{
fprintf (stderr,
"Failed to start server: %s\n",
err);
return 1;
}
{
union MHD_DaemonInfoFixedData info;
enum MHD_StatusCode sc;
sc = MHD_daemon_get_info_fixed (
d,
MHD_DAEMON_INFO_FIXED_BIND_PORT,
&info);
test_check (MHD_SC_OK == sc);
snprintf (base_url,
sizeof (base_url),
"http://localhost:%u/",
(unsigned int) info.v_port);
pc.base_url = base_url;
}
if (0 != pthread_create (&server_phase_thr,
NULL,
&server_phase_logic,
&ctx))
{
fprintf (stderr,
"Failed to start server phase thread: %s\n",
strerror (errno));
return 77;
}
ctx.finsig = p[0];
ctx.d = d;
if (0 != pthread_create (&server_run_thr,
NULL,
&server_run_logic,
&ctx))
{
fprintf (stderr,
"Failed to start server run thread: %s\n",
strerror (errno));
return 77;
}
// FIXME: start some thread to run the actual server!
for (i = 0; NULL == phases[i].label; i++)
{
if (! run_client_phase (&phases[i],
&pc))
{
res = 1;
goto cleanup;
}
/* client is done with phase */
semaphore_up (&ctx.client_sem);
/* wait for server to have moved to new phase */
semaphore_down (&ctx.server_sem);
}
res = 0;
cleanup:
/* stop thread that runs the actual server */
{
void *res;
test_check (1 ==
write (p[1],
"e",
1));
test_check (0 ==
pthread_join (server_run_thr,
&res));
}
{
void *res;
/* Unblock the #server_phase_logic() even if we had
an error */
for (i = 0; NULL == phases[i].label; i++)
semaphore_up (&ctx.client_sem);
test_check (0 ==
pthread_join (server_phase_thr,
&res));
}
MHD_daemon_destroy (d);
semaphore_destroy (&ctx.client_sem);
semaphore_destroy (&ctx.server_sem);
test_check (0 == close (p[0]));
test_check (0 == close (p[1]));
return res;
}
+223
View File
@@ -0,0 +1,223 @@
/*
This file is part of GNU libmicrohttpd
Copyright (C) 2024 Christian Grothoff
GNU libmicrohttpd is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
GNU libmicrohttpd is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* @file libtest.h
* @brief testing harness with clients against server
* @author Christian Grothoff
*/
#ifndef LIBTEST_H
#define LIBTEST_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <microhttpd2.h>
/**
* Information about the current phase.
*/
struct MHDT_PhaseContext
{
/**
* Base URL of the server
*/
const char *base_url;
/**
* Specific client we are running.
*/
unsigned int client_id;
};
/**
* Function called to run some client logic against
* the server.
*
* @param cls closure
* @param pc context for the client
* @return error message, NULL on success
*/
typedef const char *
(*MHDT_ClientLogic)(void *cls,
const struct MHDT_PhaseContext *pc);
/**
* Run request against the base URL and expect the
* string in @a cls to be returned
*
* @param cls closure with text string to be returned
* @param pc context for the client
* @return error message, NULL on success
*/
const char *
MHDT_client_get_root (void *cls,
const struct MHDT_PhaseContext *pc);
/**
* A phase defines some server and client-side
* behaviors to execute.
*/
struct MHDT_Phase
{
/**
* Name of the phase, for debugging/logging.
*/
const char *label;
/**
* Logic for the MHD server for this phase.
*/
MHD_RequestCallback server_cb;
/**
* Closure for @e server_cb.
*/
void *server_cb_cls;
/**
* Logic for the CURL client for this phase.
*/
MHDT_ClientLogic client_cb;
/**
* Closure for @e client_cb.
*/
void *client_cb_cls;
/**
* How long is the phase allowed to run at most before
* timing out. 0 for no timeout.
*/
unsigned int timeout_ms;
/**
* How many clients should be run in parallel.
* 0 to run just one client.
*/
unsigned int num_clients;
};
/**
* Returns the text from @a cls as the response to any
* request.
*
* @param cls argument given together with the function
* pointer when the handler was registered with MHD
* @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_text (
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.
*
* @param cls closure
* @param[in,out] d daemon to initialize
* @return error message, NULL on success
*/
typedef const char *
(*MHDT_ServerSetup)(void *cls,
struct MHD_Daemon *d);
/**
* Initialize MHD daemon without any special
* options, binding to any free port.
*
* @param cls closure
* @param[in,out] d daemon to initialize
* @return error message, NULL on success
*/
const char *
MHDT_server_setup_minimal (void *cls,
struct MHD_Daemon *d);
/**
* Function that runs an MHD daemon until
* a read() against @a finsig succeeds.
*
* @param cls closure
* @param finsig fd to read from to detect termination request
* @param[in,out] d daemon to run
*/
typedef void
(*MHDT_ServerRunner)(void *cls,
int finsig,
struct MHD_Daemon *d);
/**
* Function that runs an MHD daemon in blocking mode until
* a read() against @a finsig succeeds.
*
* @param cls closure
* @param finsig fd to read from to detect termination request
* @param[in,out] d daemon to run
*/
void
MHDT_server_run_blocking (void *cls,
int finsig,
struct MHD_Daemon *d);
/**
* Run test suite with @a phases for a daemon initialized
* using @a ss_cb on the local machine.
*
* @param ss_cb setup logic for the daemon
* @param ss_cb_cls closure for @a ss_cb
* @param run_cb runs the daemon
* @param run_cb_cls closure for @a run_cb
* @param phases test phases to run in child processes
* @return 0 on success, 77 if test was skipped,
* error code otherwise
*/
int
MHDT_test (MHDT_ServerSetup ss_cb,
void *ss_cb_cls,
MHDT_ServerRunner run_cb,
void *run_cb_cls,
const struct MHDT_Phase *phases);
#endif
+124
View File
@@ -0,0 +1,124 @@
/*
This file is part of GNU libmicrohttpd
Copyright (C) 2024 Christian Grothoff
GNU libmicrohttpd is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
GNU libmicrohttpd is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* @file libtest_convenience.c
* @brief convenience functions for libtest users
* @author Christian Grothoff
*/
#include <pthread.h>
#include <stdbool.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include "microhttpd2.h"
#include "libtest.h"
#include <curl/curl.h>
const char *
MHDT_server_setup_minimal (void *cls,
struct MHD_Daemon *d)
{
if (MHD_SC_OK !=
MHD_DAEMON_SET_OPTIONS (d,
MHD_D_OPTION_BIND_PORT (MHD_AF_DUAL,
0)))
return "Failed to bind to port 0!";
return NULL;
}
void
MHDT_server_run_blocking (void *cls,
int finsig,
struct MHD_Daemon *d)
{
char e;
while (-1 ==
read (finsig,
&e,
1))
{
if (EAGAIN != errno)
{
fprintf (stderr,
"Failure reading termination signal: %s\n",
strerror (errno));
break;
}
if (MHD_SC_OK !=
MHD_daemon_process_blocking (d,
1000))
{
fprintf (stderr,
"Failure running MHD_daemon_process_blocking()\n");
break;
}
}
}
const struct MHD_Action *
MHDT_server_reply_text (
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 *text = cls;
return MHD_action_from_response (
request,
MHD_response_from_buffer_static (MHD_HTTP_STATUS_OK,
strlen (text),
text));
}
const char *
MHDT_client_get_root (
void *cls,
const struct MHDT_PhaseContext *pc)
{
const char *text = cls;
CURL *c;
CURLcode res;
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";
}
res = curl_easy_perform (c);
curl_easy_cleanup (c);
if (CURLE_OK != res)
return "Failed to fetch URL";
(void) text; // FIXME: check data returned was 'text'
return NULL;
}
+1 -1
View File
@@ -19,7 +19,7 @@
*/
/**
* @file test_start_destroy.c
* @file test_basic_checks.c
* @brief test for create, start and destroy
* @author Karlson2k (Evgeny Grin)
*/
+55
View File
@@ -0,0 +1,55 @@
/*
This file is part of GNU libmicrohttpd
Copyright (C) 2016, 2024 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
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
GNU libmicrohttpd is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* @file test_client_server.c
* @brief test with client against server
* @author Christian Grothoff
*/
#include <microhttpd2.h>
#include "libtest.h"
int
main (int argc, char *argv[])
{
struct MHDT_Phase phases[] = {
{
.label = "simple get",
.server_cb = &MHDT_server_reply_text,
.server_cb_cls = "Hello world",
.client_cb = &MHDT_client_get_root,
.client_cb_cls = "Hello world",
.timeout_ms = 5,
.num_clients = 10
},
{
.label = NULL,
},
};
(void) argc; /* Unused. Silence compiler warning. */
(void) argv; /* Unused. Silence compiler warning. */
return MHDT_test (&MHDT_server_setup_minimal,
NULL,
&MHDT_server_run_blocking,
NULL,
phases);
}