First batch of tests

This commit is contained in:
2025-04-20 15:57:56 +02:00
parent e1dca84a44
commit 3a4f4b5351
3 changed files with 290 additions and 74 deletions
+191 -9
View File
@@ -23,28 +23,161 @@ typedef struct {
int body_len;
} Response;
// Memory function used to initialize TinyHTTPStream
static void *memfunc(TinyHTTPMemoryFuncTag tag, void *ptr, int len, void *data);
// Moves the request "str" into the stream, checks that the stream
// became ready and that it parsed the request correctly. When this
// functions returns the stream is ready for a response.
static void send_request(TinyHTTPStream *stream, const char *str);
// Copies into the "dst" buffer the output bytes from the stream
// (up to "cap" bytes) and parses them as an HTTP response into
// "res".
static void recv_response(TinyHTTPStream *stream, Response *res, char *dst, int cap);
static int header_exists(Response *res, TinyHTTPString name);
static int header_exists_with_value(Response *res, TinyHTTPString name, TinyHTTPString value);
#define TEST(X) {if (!(X)) { printf("Test failed at %s:%d\n", __FILE__, __LINE__); fflush(stdout); __builtin_trap(); }}
#define TEST_START printf("Test %s:%d\n", __FILE__, __LINE__);
#define TEST_END
//////////////////////////////////////////////////////////////////////////////////////
// TEST CASES
//////////////////////////////////////////////////////////////////////////////////////
static void basic_request(void)
// Plain HTTP 1.1 request string with no Connection header
#define BASIC_REQUEST_STRING \
"GET / HTTP/1.1\r\n" \
"Host: 127.0.0.1:8080\r\n" \
"User-Agent: curl/7.81.0\r\n" \
"Accept: */*\r\n" \
"\r\n"
static void test_init(void)
{
TinyHTTPStream stream;
TEST_START
tinyhttp_stream_init(&stream, memfunc, NULL);
int state = tinyhttp_stream_state(&stream);
// These flags must be set on init
TEST(state & TINYHTTP_STREAM_RECV);
// These must be unset
TEST(!(state & TINYHTTP_STREAM_DIED));
TEST(!(state & TINYHTTP_STREAM_READY));
TEST(!(state & TINYHTTP_STREAM_REUSE));
TEST(!(state & TINYHTTP_STREAM_RECV_STARTED));
TEST(!(state & TINYHTTP_STREAM_SEND_STARTED));
tinyhttp_stream_free(&stream);
TEST_END
}
static void test_setreuse(void)
{
TinyHTTPStream stream;
TEST_START
tinyhttp_stream_init(&stream, memfunc, NULL);
TEST(!(tinyhttp_stream_state(&stream) & TINYHTTP_STREAM_REUSE));
tinyhttp_stream_setreuse(&stream, 1);
TEST(tinyhttp_stream_state(&stream) & TINYHTTP_STREAM_REUSE);
tinyhttp_stream_setreuse(&stream, 0);
TEST(!(tinyhttp_stream_state(&stream) & TINYHTTP_STREAM_REUSE));
tinyhttp_stream_setreuse(&stream, 5847295);
TEST(tinyhttp_stream_state(&stream) & TINYHTTP_STREAM_REUSE);
tinyhttp_stream_free(&stream);
TEST_END
}
static void
test_recv_started_flag(void)
{
ptrdiff_t cap;
TinyHTTPStream stream;
TEST_START
tinyhttp_stream_init(&stream, memfunc, NULL);
TEST(!(tinyhttp_stream_state(&stream) & TINYHTTP_STREAM_RECV_STARTED));
tinyhttp_stream_recv_buf(&stream, &cap);
TEST(tinyhttp_stream_state(&stream) & TINYHTTP_STREAM_RECV_STARTED);
tinyhttp_stream_recv_ack(&stream, 0);
TEST(!(tinyhttp_stream_state(&stream) & TINYHTTP_STREAM_RECV_STARTED));
tinyhttp_stream_free(&stream);
TEST_END
}
static void test_kill(void)
{
TinyHTTPStream stream;
TEST_START
tinyhttp_stream_init(&stream, memfunc, NULL);
TEST(!(tinyhttp_stream_state(&stream) & TINYHTTP_STREAM_DIED));
tinyhttp_stream_kill(&stream);
TEST(tinyhttp_stream_state(&stream) & TINYHTTP_STREAM_DIED);
tinyhttp_stream_free(&stream);
TEST_END
}
static void
test_send_started_flag(void)
{
ptrdiff_t cap;
TinyHTTPStream stream;
TEST_START
tinyhttp_stream_init(&stream, memfunc, NULL);
TEST(!(tinyhttp_stream_state(&stream) & TINYHTTP_STREAM_SEND_STARTED));
tinyhttp_stream_send_buf(&stream, &cap);
TEST(tinyhttp_stream_state(&stream) & TINYHTTP_STREAM_SEND_STARTED);
tinyhttp_stream_send_ack(&stream, 0);
TEST(!(tinyhttp_stream_state(&stream) & TINYHTTP_STREAM_SEND_STARTED));
tinyhttp_stream_free(&stream);
TEST_END
}
static void test_exchange(int reuse)
{
TinyHTTPStream stream;
TEST_START
tinyhttp_stream_init(&stream, memfunc, NULL);
tinyhttp_stream_setreuse(&stream, reuse);
// Send request
send_request(&stream,
"GET / HTTP/1.1\r\n"
"Host: 127.0.0.1:8080\r\n"
"User-Agent: curl/7.81.0\r\n"
"Accept: */*\r\n"
"\r\n");
send_request(&stream, BASIC_REQUEST_STRING);
// Build response
tinyhttp_stream_response_status(&stream, 200);
@@ -55,9 +188,27 @@ static void basic_request(void)
Response res;
recv_response(&stream, &res, buf, sizeof(buf));
// TODO
// We expect the status line:
// HTTP/1.1 200 OK
TEST(res.minor == 1);
TEST(res.status_code == 200);
TEST(tinyhttp_streq(res.status_text, TINYHTTP_STRING("OK")));
if (reuse) {
// If we allowed connection reuse on this stream, we expect
// the connection to be kept alive. The response must therefore
// contain the "Connection: Keep-Alive" header or no "Connection"
// header at all since "Keep-Alive" is the default.
TEST(!header_exists(&res, TINYHTTP_STRING("Connection")) ||
header_exists_with_value(&res, TINYHTTP_STRING("Connection"), TINYHTTP_STRING("Keep-Alive")));
} else {
// If we didn't allow connection reuse, then the response must
// contain the "Connection: Close" header
TEST(header_exists_with_value(&res, TINYHTTP_STRING("Connection"), TINYHTTP_STRING("Close")));
}
tinyhttp_stream_free(&stream);
TEST_END
}
//////////////////////////////////////////////////////////////////////////////////////
@@ -66,7 +217,14 @@ static void basic_request(void)
int main(void)
{
basic_request();
test_init();
test_setreuse();
test_kill();
test_recv_started_flag();
test_send_started_flag();
test_exchange(0);
test_exchange(1);
printf("OK\n");
return 0;
}
@@ -109,11 +267,14 @@ buffer_into_stream(TinyHTTPStream *stream, const char *src, int len)
memcpy(dst, src + copied, cpy);
tinyhttp_printbytes(" >> ", src + copied, cpy);
copied += cpy;
tinyhttp_stream_recv_ack(stream, cpy);
state = tinyhttp_stream_state(stream);
}
printf("\n");
return copied;
}
@@ -136,11 +297,14 @@ stream_into_buffer(TinyHTTPStream *stream, char *dst, int cap)
memcpy(dst + copied, src, cpy);
tinyhttp_printbytes(" << ", src, cpy);
copied += cpy;
tinyhttp_stream_send_ack(stream, cpy);
state = tinyhttp_stream_state(stream);
}
printf("\n");
return copied;
}
@@ -285,6 +449,24 @@ recv_response(TinyHTTPStream *stream, Response *res, char *dst, int cap)
parse_response((TinyHTTPString) { dst, len }, res);
}
static int
header_exists(Response *res, TinyHTTPString name)
{
for (int i = 0; i < res->num_headers; i++)
if (tinyhttp_streqcase(res->headers[i].name, name))
return 1;
return 0;
}
static int
header_exists_with_value(Response *res, TinyHTTPString name, TinyHTTPString value)
{
for (int i = 0; i < res->num_headers; i++)
if (tinyhttp_streqcase(res->headers[i].name, name))
return tinyhttp_streqcase(res->headers[i].value, value);
return 0;
}
//////////////////////////////////////////////////////////////////////////////////////
// THE END
//////////////////////////////////////////////////////////////////////////////////////
+81 -65
View File
@@ -70,16 +70,46 @@
#endif
////////////////////////////////////////////////////////////////////////////////////
// DEBUG UTILITIES //
// UTILITIES //
////////////////////////////////////////////////////////////////////////////////////
#if DUMP_IO
#include <stdio.h>
static void
print_bytes(char *prefix, char *src, int len)
// TODO: Only allow this when not compiled in stand-alone mode
static int print(const char *src, int len)
{
if (len < 0)
len = strlen(src);
#if defined(_WIN32)
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
int num = 0;
while (num < len) {
DWORD cpy;
if (!WriteFile(handle, src + num, len - num, &cpy, NULL))
return -1;
num += cpy;
}
return 0;
#elif defined(__linux__)
int fd = STDOUT_FILENO;
int num = 0;
while (num < len) {
int ret = write(fd, src + num, len - num);
if (ret < 0) {
if (ret == EINTR)
continue;
return -1;
}
num += ret;
}
return 0;
#endif
}
void tinyhttp_printbytes(char *prefix, const char *src, int len)
{
if (src == NULL) {
printf("%s (null)\n", prefix);
print(prefix, -1);
print(" (null)\n", -1);
return;
}
@@ -90,64 +120,57 @@ print_bytes(char *prefix, char *src, int len)
while (cur < len && src[cur] != '\n' && src[cur] != '\r')
cur++;
if (newline) {
printf("%s", prefix);
print(prefix, -1);
newline = 0;
}
printf("%.*s", cur - start, src + start);
print(src + start, cur - start);
if (cur < len) {
if (src[cur] == '\r')
printf("\\r");
print("\\r", -1);
else {
printf("\\n\n");
print("\\n\n", -1);
newline = 1;
}
cur++;
}
}
if (cur > 0 && src[cur-1] != '\n')
printf("\n");
print("\n", -1);
}
#endif
#if DUMP_IO
static void
dump_state(int state, const char *file, int line)
void tinyhttp_printstate_(int state, const char *file, const char *line)
{
printf("state = ");
print("state = ", -1);
if (state & TINYHTTP_STREAM_DIED)
printf("DIED ");
print("DIED ", -1);
if (state & TINYHTTP_STREAM_SEND)
printf("SEND ");
print("SEND ", -1);
if (state & TINYHTTP_STREAM_RECV)
printf("RECV ");
print("RECV ", -1);
if (state & TINYHTTP_STREAM_READY)
printf("READY ");
print("READY ", -1);
if (state & TINYHTTP_STREAM_CLOSE)
printf("CLOSE ");
print("CLOSE ", -1);
if (state & TINYHTTP_STREAM_REUSE)
printf("REUSE ");
print("REUSE ", -1);
if (state & TINYHTTP_STREAM_SEND_STARTED)
printf("SEND_STARTED ");
print("SEND_STARTED ", -1);
if (state & TINYHTTP_STREAM_RECV_STARTED)
printf("RECV_STARTED ");
print("RECV_STARTED ", -1);
printf(" (in %s:%d)\n", file, line);
print(" (in ", -1);
print(file, -1);
print(":", -1);
print(line, -1);
print(")\n", -1);
}
#define DUMP_STATE(state) dump_state(state, __FILE__, __LINE__);
#else
#define DUMP_STATE(...)
#endif
////////////////////////////////////////////////////////////////////////////////////
// UTILITIES //
////////////////////////////////////////////////////////////////////////////////////
static char
to_lower(char c)
@@ -177,6 +200,25 @@ int tinyhttp_streqcase(TinyHTTPString s1, TinyHTTPString s2)
return 1;
}
static TinyHTTPString trim(TinyHTTPString s)
{
int i = 0;
while (i < s.len && (s.ptr[i] == ' ' || s.ptr[i] == '\t'))
i++;
if (i == s.len) {
s.ptr = NULL;
s.len = 0;
} else {
s.ptr += i;
s.len -= i;
while (s.ptr[s.len-1] == ' ' || s.ptr[s.len-1] == '\n')
s.len--;
}
return s;
}
////////////////////////////////////////////////////////////////////////////////////
// HTTP REQUEST PARSER //
////////////////////////////////////////////////////////////////////////////////////
@@ -266,6 +308,7 @@ parse_request_head(char *src, ptrdiff_t len, TinyHTTPRequest *req)
while (cur < len && src[cur] != ':') // TODO: robust
cur++;
TinyHTTPString name = { src + name_off, cur - name_off };
name = trim(name);
if (cur == len)
return -400;
@@ -275,6 +318,7 @@ parse_request_head(char *src, ptrdiff_t len, TinyHTTPRequest *req)
while (cur < len && src[cur] != '\r')
cur++;
TinyHTTPString value = { src + value_off, cur - value_off };
value = trim(value);
if (cur == len)
return -400;
@@ -557,7 +601,7 @@ byte_queue_peek(TinyHTTPByteQueue *queue, ptrdiff_t *len)
static char*
byte_queue_read_buf(TinyHTTPByteQueue *queue, ptrdiff_t *len)
{
if ((queue->flags & (BYTE_QUEUE_ERROR)) || queue->data == NULL) {
if (queue->flags & BYTE_QUEUE_ERROR) {
*len = 0;
return NULL;
}
@@ -568,6 +612,8 @@ byte_queue_read_buf(TinyHTTPByteQueue *queue, ptrdiff_t *len)
queue->read_target_size = queue->size;
*len = queue->used;
if (queue->data == NULL)
return NULL;
return queue->data + queue->head;
}
@@ -1399,8 +1445,6 @@ void tinyhttp_stream_response_send(TinyHTTPStream *stream)
stream->output_state = TINYHTTP_OUTPUT_STATE_BODY;
}
DUMP_STATE(tinyhttp_stream_state(stream));
if (stream->output_state == TINYHTTP_OUTPUT_STATE_BODY) {
if (stream->chunked)
byte_queue_write(&stream->out, "0\r\n\r\n");
@@ -1427,15 +1471,13 @@ void tinyhttp_stream_response_send(TinyHTTPStream *stream)
tmp[9] = '0' + content_length;
int i = 0;
while (i < 8 && tmp[i] == '0')
while (i < 9 && tmp[i] == '0')
i++;
byte_queue_patch(&stream->out, stream->content_length_value_offset, tmp + i, 10 - i);
}
}
DUMP_STATE(tinyhttp_stream_state(stream));
if (stream->output_state == TINYHTTP_OUTPUT_STATE_ERROR) {
byte_queue_remove_after_lock(&stream->out);
byte_queue_write(&stream->out,
@@ -1446,15 +1488,11 @@ void tinyhttp_stream_response_send(TinyHTTPStream *stream)
);
}
DUMP_STATE(tinyhttp_stream_state(stream));
if (byte_queue_error(&stream->out)) {
stream->state |= TINYHTTP_STREAM_DIED;
return;
}
DUMP_STATE(tinyhttp_stream_state(stream));
#if DUMP_IO
ptrdiff_t ressize = (byte_queue_offset(&stream->out) - stream->out.lock);
print_bytes("R << ", stream->out.data + stream->out.head, ressize);
@@ -1464,11 +1502,7 @@ void tinyhttp_stream_response_send(TinyHTTPStream *stream)
byte_queue_read_unlock(&stream->out);
stream->reqsize = 0;
DUMP_STATE(tinyhttp_stream_state(stream));
process_next_request(stream);
DUMP_STATE(tinyhttp_stream_state(stream));
}
// See tinyhttp.h
@@ -2000,8 +2034,6 @@ start_stream_operations(TinyHTTPStream *stream, SOCKET sock,
{
int state = tinyhttp_stream_state(stream);
DUMP_STATE(tinyhttp_stream_state(stream));
if ((state & TINYHTTP_STREAM_RECV) && !(state & TINYHTTP_STREAM_RECV_STARTED)) {
ptrdiff_t cap;
char *dst = tinyhttp_stream_recv_buf(stream, &cap);
@@ -2014,8 +2046,6 @@ start_stream_operations(TinyHTTPStream *stream, SOCKET sock,
#endif
}
DUMP_STATE(tinyhttp_stream_state(stream));
if (state & TINYHTTP_STREAM_SEND && !(state & TINYHTTP_STREAM_SEND_STARTED)) {
ptrdiff_t len;
char *src = tinyhttp_stream_send_buf(stream, &len);
@@ -2028,8 +2058,6 @@ start_stream_operations(TinyHTTPStream *stream, SOCKET sock,
#endif
}
DUMP_STATE(tinyhttp_stream_state(stream));
return 0;
}
@@ -2064,8 +2092,6 @@ intern_accepted_socket(TinyHTTPServer *server, SOCKET accepted_socket, int secur
if (server->num_conns < TINYHTTP_SERVER_CONN_LIMIT * 0.7)
tinyhttp_stream_setreuse(stream, 1);
DUMP_STATE(tinyhttp_stream_state(stream));
if (CreateIoCompletionPort((HANDLE) accepted_socket, server->iocp, 0, 0) == NULL) {
tinyhttp_stream_free(stream);
server->stream_sockets[idx] = INVALID_SOCKET;
@@ -2082,8 +2108,6 @@ intern_accepted_socket(TinyHTTPServer *server, SOCKET accepted_socket, int secur
return;
}
DUMP_STATE(tinyhttp_stream_state(stream));
if (secure) {
// TODO
}
@@ -2186,8 +2210,6 @@ process_network_events(TinyHTTPServer *server, int timeout)
int old_state = tinyhttp_stream_state(stream);
DUMP_STATE(tinyhttp_stream_state(stream));
if (!result) {
// A read or write operation failed
tinyhttp_stream_kill(stream);
@@ -2214,8 +2236,6 @@ process_network_events(TinyHTTPServer *server, int timeout)
tinyhttp_stream_kill(stream);
}
DUMP_STATE(tinyhttp_stream_state(stream));
int state = tinyhttp_stream_state(stream);
if ((state & TINYHTTP_STREAM_READY) && (old_state & TINYHTTP_STREAM_READY) == 0) {
@@ -2224,8 +2244,6 @@ process_network_events(TinyHTTPServer *server, int timeout)
server->ready_count++;
}
DUMP_STATE(tinyhttp_stream_state(stream));
if (state & TINYHTTP_STREAM_DIED) {
tinyhttp_stream_free(stream);
remove_from_ready_queue(server, idx);
@@ -2235,8 +2253,6 @@ process_network_events(TinyHTTPServer *server, int timeout)
server->num_conns--;
}
DUMP_STATE(tinyhttp_stream_state(stream));
return 0;
}
#endif
+18
View File
@@ -39,6 +39,10 @@
#define TINYHTTP_SERVER_EPOLL_BATCH_SIZE (1<<10)
#endif
#define TINYHTTP_LINESTR_HELPER1(X) #X
#define TINYHTTP_LINESTR_HELPER2(X) TINYHTTP_LINESTR_HELPER1(X)
#define TINYHTTP_LINESTR TINYHTTP_LINESTR_HELPER2(__LINE__)
// Opaque types
typedef struct TinyHTTPServer TinyHTTPServer;
typedef struct TinyHTTPRouter TinyHTTPRouter;
@@ -171,9 +175,21 @@ typedef struct {
TinyHTTPRequest req;
} TinyHTTPStream;
// TODO: Comment
int tinyhttp_streq(TinyHTTPString s1, TinyHTTPString s2);
// TODO: Comment
int tinyhttp_streqcase(TinyHTTPString s1, TinyHTTPString s2);
// TODO: Comment
void tinyhttp_printbytes(char *prefix, const char *src, int len);
// TODO: Comment
void tinyhttp_printstate_(int state, const char *file, const char *line);
// TODO: Comment
#define tinyhttp_printstate(state) tinyhttp_printstate_(state, __FILE__, TINYHTTP_LINESTR)
// Initializes an HTTP stream
//
// TODO: Comment on memfunc
@@ -245,6 +261,8 @@ void tinyhttp_stream_send_ack(TinyHTTPStream *stream, ptrdiff_t num);
//
// On the other hand, if this option is set the next
// request will definitely be the last.
//
// NOTE: This is turned off by default
void tinyhttp_stream_setreuse(TinyHTTPStream *stream, int value);
// TODO: Comment