From b28f15612b362694c58555114887deccb720a28a Mon Sep 17 00:00:00 2001 From: Dirk Engling Date: Sat, 24 Oct 2026 12:53:46 +0200 Subject: Add tests for new blob abstraction --- tests/runner.c | 1 + tests/t_buf.c | 205 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ tests/test.h | 1 + 3 files changed, 207 insertions(+) create mode 100644 tests/t_buf.c (limited to 'tests') diff --git a/tests/runner.c b/tests/runner.c index 370b92b..25456bc 100644 --- a/tests/runner.c +++ b/tests/runner.c @@ -20,6 +20,7 @@ static const struct module modules[] = {{"scan_urlencoded_query", test_scan_urle {"ot_random", test_random}, {"ot_hex", test_hex}, {"ot_scan", test_scan}, + {"ot_buf", test_buf}, {"ot_accesslist", test_accesslist}, {NULL, NULL}}; diff --git a/tests/t_buf.c b/tests/t_buf.c new file mode 100644 index 0000000..dcfb0bb --- /dev/null +++ b/tests/t_buf.c @@ -0,0 +1,205 @@ +/* This software was written by Dirk Engling + It is considered beerware. Prost. Skol. Cheers or whatever. + + $id$ */ + +/* System */ +#include +#include + +/* Opentracker */ +#include "ot_buf.h" +#include "test.h" + +static void case_starts_empty(void) { + ot_buf buf = {NULL, 0}; + + test_case("a fresh buffer holds nothing and has no data pointer"); + CHECK_PTR(buf.data, NULL); + CHECK_INT(buf.size, 0); +} + +static void case_cat_appends(void) { + ot_buf buf = {NULL, 0}; + + test_case("appends concatenate in order and report success"); + CHECK_INT(buf_cat(&buf, "GET /ann", 8), 0); + CHECK(buf.data != NULL); + CHECK_INT(buf.size, 8); + CHECK_INT(buf_cat(&buf, "ounce", 5), 0); + CHECK_INT(buf.size, 13); + CHECK_MEM(buf.data, "GET /announce", 13); + buf_reset(&buf); +} + +static void case_cat_of_nothing_allocates_nothing(void) { + ot_buf buf = {NULL, 0}; + + /* handle_read tells an untouched buffer from a partial request by the data + pointer, so a zero length append must not quietly allocate one */ + test_case("appending zero bytes leaves the buffer untouched"); + CHECK_INT(buf_cat(&buf, "ignored", 0), 0); + CHECK_PTR(buf.data, NULL); + CHECK_INT(buf.size, 0); + buf_reset(&buf); +} + +static void case_grows_across_many_appends(void) { + ot_buf buf = {NULL, 0}; + char chunk[100]; + size_t i; + + test_case("content survives growing one packet at a time"); + memset(chunk, 'x', sizeof(chunk)); + for (i = 0; i < 100; ++i) + if (buf_cat(&buf, chunk, sizeof(chunk))) { + test_failf(__FILE__, __LINE__, "append %zu was refused", i); + buf_reset(&buf); + return; + } + ++test_checks; + CHECK_INT(buf.size, 10000); + for (i = 0; i < 10000; ++i) + if (buf.data[i] != 'x') { + test_failf(__FILE__, __LINE__, "byte %zu was not preserved across growth", i); + buf_reset(&buf); + return; + } + ++test_checks; + buf_reset(&buf); +} + +static void case_grows_one_byte_at_a_time(void) { + ot_buf buf = {NULL, 0}; + size_t i; + + /* The shape a trickling client produces. Nothing here is optimised for it, + so the point is only that it stays correct. */ + test_case("a byte at a time accumulates correctly"); + for (i = 0; i < 2048; ++i) { + char c = (char)('a' + (i % 26)); + if (buf_cat(&buf, &c, 1)) { + test_failf(__FILE__, __LINE__, "append %zu was refused", i); + buf_reset(&buf); + return; + } + } + ++test_checks; + CHECK_INT(buf.size, 2048); + for (i = 0; i < 2048; ++i) + if (buf.data[i] != (char)('a' + (i % 26))) { + test_failf(__FILE__, __LINE__, "byte %zu differs", i); + buf_reset(&buf); + return; + } + ++test_checks; + buf_reset(&buf); +} + +static void case_truncate_shrinks_only(void) { + ot_buf buf = {NULL, 0}; + + test_case("truncate drops the tail but never grows"); + buf_cat(&buf, "0123456789", 10); + buf_truncate(&buf, 4); + CHECK_INT(buf.size, 4); + CHECK_MEM(buf.data, "0123", 4); + + buf_truncate(&buf, 9); + CHECK_INT(buf.size, 4); + buf_truncate(&buf, 4); + CHECK_INT(buf.size, 4); + + buf_truncate(&buf, 0); + CHECK_INT(buf.size, 0); + buf_reset(&buf); +} + +static void case_append_after_truncate(void) { + ot_buf buf = {NULL, 0}; + + test_case("appending after a truncate overwrites the dropped tail"); + buf_cat(&buf, "keepdrop", 8); + buf_truncate(&buf, 4); + CHECK_INT(buf_cat(&buf, "MORE", 4), 0); + CHECK_INT(buf.size, 8); + CHECK_MEM(buf.data, "keepMORE", 8); + buf_reset(&buf); +} + +static void case_reset_clears_everything(void) { + ot_buf buf = {NULL, 0}; + + test_case("reset releases the buffer and restores the initial state"); + buf_cat(&buf, "something", 9); + buf_reset(&buf); + CHECK_PTR(buf.data, NULL); + CHECK_INT(buf.size, 0); +} + +static void case_reset_of_empty_is_safe(void) { + ot_buf buf = {NULL, 0}; + + test_case("resetting a buffer that was never used is harmless"); + buf_reset(&buf); + buf_reset(&buf); + CHECK_PTR(buf.data, NULL); + CHECK_INT(buf.size, 0); +} + +static void case_refused_append_is_harmless(void) { + ot_buf buf = {NULL, 0}; + + /* A length that would wrap the size is rejected before src is read and + before anything is allocated, so a short string is safe to pass here. + A refused append must not destroy the partial request already collected: + the caller answers and closes rather than losing both. */ + test_case("a length that would wrap the size is refused"); + CHECK_INT(buf_cat(&buf, "already here", 12), 0); + CHECK_INT(buf_cat(&buf, "x", (size_t)-1), -1); + + test_case("and earlier content survives it"); + CHECK_INT(buf.size, 12); + CHECK_MEM(buf.data, "already here", 12); + + test_case("and the buffer still works afterwards"); + CHECK_INT(buf_cat(&buf, "!", 1), 0); + CHECK_INT(buf.size, 13); + CHECK_MEM(buf.data, "already here!", 13); + buf_reset(&buf); +} + +static void case_accumulates_a_split_request(void) { + /* What handle_read actually does: collect a request that arrived in + pieces, then hand over the whole of it */ + ot_buf buf = {NULL, 0}; + const char *pieces[] = {"GET /announce?info_hash=", "0123456789abcdefghij", " HTTP/1.0\r\n\r\n"}; + size_t i; + + test_case("a request split over several reads is reassembled"); + for (i = 0; i < 3; ++i) + if (buf_cat(&buf, pieces[i], strlen(pieces[i]))) { + test_failf(__FILE__, __LINE__, "piece %zu was refused", i); + buf_reset(&buf); + return; + } + ++test_checks; + + CHECK_INT(buf.size, 24 + 20 + 13); + CHECK_MEM(buf.data, "GET /announce?info_hash=0123456789abcdefghij HTTP/1.0\r\n\r\n", 57); + buf_reset(&buf); +} + +void test_buf(void) { + case_starts_empty(); + case_cat_appends(); + case_cat_of_nothing_allocates_nothing(); + case_grows_across_many_appends(); + case_grows_one_byte_at_a_time(); + case_truncate_shrinks_only(); + case_append_after_truncate(); + case_reset_clears_everything(); + case_reset_of_empty_is_safe(); + case_refused_append_is_harmless(); + case_accumulates_a_split_request(); +} diff --git a/tests/test.h b/tests/test.h index f0f2360..676ca33 100644 --- a/tests/test.h +++ b/tests/test.h @@ -83,5 +83,6 @@ void test_random(void); void test_hex(void); void test_accesslist(void); void test_scan(void); +void test_buf(void); #endif -- cgit v1.3