mirror of
https://git.gnunet.org/libmicrohttpd.git
synced 2026-09-28 04:09:31 +03:00
first test against test framework
This commit is contained in:
@@ -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, "")
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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)
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user