/* 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(); }