diff --git a/src/Makefile.am b/src/Makefile.am index b329c5f5..a02637dd 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -31,6 +31,7 @@ noinst_HEADERS = \ matchfile.h \ tls.h \ refobject.h \ + buffer.h \ module.h \ reportxml.h \ listensocket.h \ @@ -76,6 +77,7 @@ icecast_SOURCES = \ matchfile.c \ tls.c \ refobject.c \ + buffer.c \ module.c \ reportxml.c \ listensocket.c \ diff --git a/src/buffer.c b/src/buffer.c new file mode 100644 index 00000000..9d8e1ace --- /dev/null +++ b/src/buffer.c @@ -0,0 +1,309 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2018, Philipp "ph3-der-loewe" Schafft , + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include + +#include "buffer.h" +#include "refobject.h" + +struct buffer_tag { + refobject_base_t __base; + /* Buffer itself */ + void *buffer; + /* Length in bytes of buffer */ + size_t length; + /* Amount of bytes in use of the buffer. This includes offset bytes */ + size_t fill; + /* Bytes of offset at the start of the buffer */ + size_t offset; +}; + +static void __free(refobject_t self, void **userdata) +{ + buffer_t *buffer = REFOBJECT_TO_TYPE(self, buffer_t*); + + free(buffer->buffer); +} + +buffer_t * buffer_new(ssize_t preallocation, void *userdata, const char *name, refobject_t associated) +{ + buffer_t *buffer = NULL; + refobject_t refobject = refobject_new(sizeof(*buffer), __free, userdata, name, associated); + + if (REFOBJECT_IS_NULL(refobject)) + return NULL; + + buffer = REFOBJECT_TO_TYPE(refobject, buffer_t *); + + if (preallocation > 0) + buffer_preallocate(buffer, preallocation); + + return buffer; +} + +buffer_t * buffer_new_simple(void) +{ + return buffer_new(-1, NULL, NULL, REFOBJECT_NULL); +} + +void buffer_preallocate(buffer_t *buffer, size_t request) +{ + void *n; + size_t newlen; + + if (!buffer) + return; + + /* Remove the offset if it makes sense to do so. */ + if (buffer->offset == buffer->fill) { + buffer->offset = 0; + buffer->fill = 0; + } else if ((2*buffer->offset) < buffer->fill || buffer->offset >= 512 || (buffer->offset > 128 && buffer->offset >= request)) { + buffer->fill -= buffer->offset; + memmove(buffer->buffer, buffer->buffer + buffer->offset, buffer->fill); + buffer->offset = 0; + } + + if (!request) + return; + + newlen = buffer->fill + request; + + if (buffer->length >= newlen) + return; + + /* Make sure we at least add 64 bytes and are 64 byte aligned */ + newlen = newlen + 64 - (newlen % 64); + + n = realloc(buffer->buffer, newlen); + + /* Just return if this failed */ + if (!n) + return; + + buffer->buffer = n; + buffer->length = newlen; +} + +int buffer_get_data(buffer_t *buffer, const void **data, size_t *length) +{ + if (!buffer) + return -1; + + if (data) { + *data = buffer->buffer + buffer->offset; + } + + if (length) { + *length = buffer->fill - buffer->offset; + } + + return 0; +} + +int buffer_get_string(buffer_t *buffer, const char **string) +{ + char *ret; + + if (!buffer || !string) + return -1; + + /* Ensure we have space for one additional byte ('\0'-termination). */ + if (buffer->length == buffer->fill) { + buffer_preallocate(buffer, 1); + if (buffer->length == buffer->fill) + return -1; + } + + /* Actually add a '\0'-termination. */ + ret = buffer->buffer; + ret[buffer->fill] = 0; + *string = ret + buffer->offset; + + return 0; +} + +int buffer_set_length(buffer_t *buffer, size_t length) +{ + if (!buffer) + return -1; + + if (length > (buffer->fill - buffer->offset)) + return -1; + + buffer->fill = length + buffer->offset; + + return 0; +} + +int buffer_shift(buffer_t *buffer, size_t amount) +{ + if (!buffer) + return -1; + + if (amount > (buffer->fill - buffer->offset)) + return -1; + + buffer->offset += amount; + + /* run cleanup */ + buffer_preallocate(buffer, 0); + + return 0; +} + +int buffer_push_data(buffer_t *buffer, const void *data, size_t length) +{ + void *buf; + int ret; + + if (!buffer) + return -1; + + if (!length) + return 0; + + if (!data) + return -1; + + ret = buffer_zerocopy_push_request(buffer, &buf, length); + if (ret != 0) + return ret; + + memcpy(buf, data, length); + + ret = buffer_zerocopy_push_complete(buffer, length); + + return ret; +} + +int buffer_push_string(buffer_t *buffer, const char *string) +{ + if (!buffer || !string) + return -1; + + return buffer_push_data(buffer, string, strlen(string)); +} + + +int buffer_push_printf(buffer_t *buffer, const char *format, ...) +{ + int ret; + va_list ap; + + if (!buffer || !format) + return -1; + + if (!*format) + return 0; + + va_start(ap, format); + ret = buffer_push_vprintf(buffer, format, ap); + va_end(ap); + + return ret; +} + +int buffer_push_vprintf(buffer_t *buffer, const char *format, va_list ap) +{ + void *buf; + int ret; + size_t length = 1024; + + if (!buffer || !format) + return -1; + + if (!*format) + return 0; + + ret = buffer_zerocopy_push_request(buffer, &buf, length); + if (ret != 0) + return ret; + + ret = vsnprintf(buf, length, format, ap); + if (ret >= 0 && (size_t)ret < length) { + return buffer_zerocopy_push_complete(buffer, ret); + } else if (ret < 0) { + /* This vsnprintf() likely does not follow POSIX. + * We don't know what length we need to asume. So asume a big one and hope for the best. */ + length = 8192; + } else { + /* Reallocate the buffer to the size reported plus one for '\0'-termination */ + length = ret + 1; + } + + /* We have not written any data yet. */ + ret = buffer_zerocopy_push_complete(buffer, 0); + if (ret != 0) + return ret; + + /* Now let's try again. */ + ret = buffer_zerocopy_push_request(buffer, &buf, length); + if (ret != 0) + return ret; + + ret = vsnprintf(buf, length, format, ap); + if (ret < 0 || (size_t)ret >= length) { + /* This still didn't work. Giving up. */ + buffer_zerocopy_push_complete(buffer, 0); + return -1; + } + + return buffer_zerocopy_push_complete(buffer, ret); +} + +int buffer_push_buffer(buffer_t *buffer, buffer_t *source) +{ + const void *data; + size_t length; + int ret; + + if (!buffer || !source) + return -1; + + ret = buffer_get_data(source, &data, &length); + if (ret != 0) + return ret; + + return buffer_push_data(buffer, data, length); +} + +int buffer_zerocopy_push_request(buffer_t *buffer, void **data, size_t request) +{ + if (!buffer || !data) + return -1; + + buffer_preallocate(buffer, request); + + if (request > (buffer->length - buffer->fill)) + return -1; + + *data = buffer->buffer + buffer->fill; + + return 0; +} + +int buffer_zerocopy_push_complete(buffer_t *buffer, size_t done) +{ + if (!buffer) + return -1; + + if (done > (buffer->length - buffer->fill)) + return -1; + + buffer->fill += done; + + return 0; +} diff --git a/src/buffer.h b/src/buffer.h new file mode 100644 index 00000000..8aea026a --- /dev/null +++ b/src/buffer.h @@ -0,0 +1,189 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2018, Philipp "ph3-der-loewe" Schafft , + */ + +/* + * This file contains the API for a refobject based buffer object. + * It can be used to store data and allows on the fly re-allocation. + */ + +#ifndef __BUFFER_H__ +#define __BUFFER_H__ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "icecasttypes.h" +#include "compat.h" +#include "refobject.h" + +/* About thread safety: + * This set of functions is intentinally not thread safe. + */ + +/* This creates a new buffer object. + * Parameters: + * preallocation + * The number of bytes to allocate for use later on. See buffer_preallocate() for details. + * userdata, name, associated + * See refobject_new(). + */ +buffer_t * buffer_new(ssize_t preallocation, void *userdata, const char *name, refobject_t associated); + +/* This creates a new buffer with defaults. + * This is the same as: + * buffer_new(-1, NULL, NULL, REFOBJECT_NULL) + */ +buffer_t * buffer_new_simple(void); + +/* This function preallocates space for later use. + * Parameters: + * buffer + * The buffer to operate on. + * request + * Number of bytes to additionally allocate. + * Notes: + * This function is very usedful when adding a large number of smaller buffers to avoid + * internal reallocation calls happening to often. However it is not required to call + * this function before adding data to the buffer. + */ +void buffer_preallocate(buffer_t *buffer, size_t request); + +/* Gets data and length of the buffer. + * Parameters: + * buffer + * The buffer to operate on. + * data + * Pointer to the stored data. If NULL the pointer is not returned. + * length + * Pointer to the length of how many bytes are in the buffer. If NULL + * length is not returned. + */ +int buffer_get_data(buffer_t *buffer, const void **data, size_t *length); + +/* Gets data as a string. The string is '\0'-terminated. + * Parameters: + * buffery + * The buffer to operate on. + * string + * The string representing the data hold by the buffer. + */ +int buffer_get_string(buffer_t *buffer, const char **string); + +/* Sets the length of the buffer. + * Parameters: + * buffer + * The buffer to operate on. + * length + * New length of the buffer. + * Notes: + * This can only be used to reduce the size of the buffer. To add data to + * the buffer use buffer_push_*(). + * + * Calling this with length set to 0 clears the buffer but does not deallocate it. + */ +int buffer_set_length(buffer_t *buffer, size_t length); + +/* Shifts data out of the buffer. + * Parameters: + * buffer + * The buffer to operate on. + * amount + * The amount of bytes to be removed from the begin of the buffer. + * Notes: + * This function can be useful for skipping some small header. However this + * must not be used to implement a kind of ring buffer as it will result in + * poor performance caused by massive reallocations and memory copies. + */ +int buffer_shift(buffer_t *buffer, size_t amount); + +/* This pushes data to the end of the buffer. + * Parameters: + * buffer + * The buffer to operate on. + * data + * The data to push. + * length + * The length of the data to push in byte. + * Notes: + * Consider using buffer_zerocopy_*(). + */ +int buffer_push_data(buffer_t *buffer, const void *data, size_t length); + +/* This pushes a string to the end of the buffer. + * Parameters: + * buffer + * The buffer to operate on. + * string + * The string to be pushed. The tailing '\0'-termination will not be + * part of the buffer. + * Notes: + * Consider using buffer_zerocopy_*(). + */ +int buffer_push_string(buffer_t *buffer, const char *string); + +/* This pushes a formated string to the end of the buffer. + * Parameters: + * buffer + * The buffer to operate on. + * format + * The format string as for printf() family functions. + * ... + * The parameters according to the format string. + */ +int buffer_push_printf(buffer_t *buffer, const char *format, ...); + +/* This pushes a formated string to the end of the buffer using a va_list. + * Parameters: + * buffer + * The buffer to operate on. + * format + * The format string as for printf() family functions. + * ap + * The parameters according to the format string as va_list. + * See also: + * vprintf(3). + */ +int buffer_push_vprintf(buffer_t *buffer, const char *format, va_list ap); + +/* This pushes the content of another buffer to the end of the buffer. + * Parameters: + * buffer + * The buffer to operate on. + * source + * The buffer which's content is to be copied. + */ +int buffer_push_buffer(buffer_t *buffer, buffer_t *source); + +/* This requests for a memory buffer that can be pushed to without the need for copy. + * Parameters: + * buffer + * The buffer to operate on. + * data + * Pointer to memory that can be written and will become part of the buffer object. + * request + * Size of the memory area that is returned by data in bytes. + * Notes: + * This is the first step of the zero copy push. After the memory returned by data has been + * written (e.g. used in a call to read(2)) buffer_zerocopy_push_complete() must be called. + */ +int buffer_zerocopy_push_request(buffer_t *buffer, void **data, size_t request); + +/* This is the final step of a zero copy push. + * Parameters: + * buffer + * The buffer to operate on. + * done + * Amount of data in bytes that has actually been written into the memory area. + * May be zero to what has been requested with request. + */ +int buffer_zerocopy_push_complete(buffer_t *buffer, size_t done); + +#endif diff --git a/src/icecasttypes.h b/src/icecasttypes.h index 58032284..70e67d60 100644 --- a/src/icecasttypes.h +++ b/src/icecasttypes.h @@ -93,6 +93,10 @@ typedef enum { typedef struct relay_tag relay_t; +/* ---[ buffer.[ch] ]--- */ + +typedef struct buffer_tag buffer_t; + /* ---[ module.[ch] ]--- */ typedef struct module_tag module_t; @@ -117,6 +121,7 @@ typedef struct refobject_base_tag refobject_base_t; #ifdef HAVE_TYPE_ATTRIBUTE_TRANSPARENT_UNION typedef union __attribute__ ((__transparent_union__)) { refobject_base_t *refobject_base; + buffer_t *buffer; module_t *module; module_container_t *module_container; reportxml_t *reportxml; diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am index e21c161f..bfa9497b 100644 --- a/src/tests/Makefile.am +++ b/src/tests/Makefile.am @@ -35,6 +35,13 @@ ctest_refobject_test_LDADD = libice_ctest.la \ icecast-refobject.o check_PROGRAMS += ctest_refobject.test +ctest_buffer_test_SOURCES = %reldir%/ctest_buffer.c +ctest_buffer_test_LDADD = libice_ctest.la \ + common/thread/libicethread.la \ + common/avl/libiceavl.la \ + icecast-refobject.o \ + icecast-buffer.o +check_PROGRAMS += ctest_buffer.test # Add all programs to TESTS TESTS = $(check_PROGRAMS) diff --git a/src/tests/ctest_buffer.c b/src/tests/ctest_buffer.c new file mode 100644 index 00000000..d29219cb --- /dev/null +++ b/src/tests/ctest_buffer.c @@ -0,0 +1,355 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2018, Philipp "ph3-der-loewe" Schafft , + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "ctest_lib.h" + +#include "../src/buffer.h" +#include "../src/refobject.h" + +static void test_create_ref_unref(void) +{ + buffer_t *a; + + a = buffer_new(-1, NULL, NULL, REFOBJECT_NULL); + ctest_test("buffer created", a != NULL); + ctest_test("un-referenced", refobject_unref(a) == 0); + + a = buffer_new_simple(); + ctest_test("buffer created", a != NULL); + ctest_test("un-referenced", refobject_unref(a) == 0); + +} + +static void test_name(void) +{ + buffer_t *a; + const char *name = "test object name"; + const char *ret; + + a = buffer_new(-1, NULL, name, REFOBJECT_NULL); + ctest_test("buffer created", a != NULL); + + ret = refobject_get_name(a); + ctest_test("get name", ret != NULL); + ctest_test("name match", strcmp(name, ret) == 0); + + ctest_test("un-referenced", refobject_unref(a) == 0); +} + +static void test_userdata(void) +{ + buffer_t *a; + int tmp = 0; + void *userdata = &tmp; + void *ret; + + a = buffer_new(-1, NULL, NULL, REFOBJECT_NULL); + ctest_test("buffer created", a != NULL); + ret = refobject_get_userdata(a); + ctest_test("get userdata", ret == NULL); + ctest_test("set userdata", refobject_set_userdata(a, userdata) == 0); + ret = refobject_get_userdata(a); + ctest_test("get userdata", ret == userdata); + ctest_test("clearing userdata", refobject_set_userdata(a, NULL) == 0); + ret = refobject_get_userdata(a); + ctest_test("get userdata", ret == NULL); + + ctest_test("un-referenced", refobject_unref(a) == 0); + + a = buffer_new(-1, userdata, NULL, REFOBJECT_NULL); + ctest_test("buffer created", a != NULL); + ctest_test("refobject created", !REFOBJECT_IS_NULL(a)); + ret = refobject_get_userdata(a); + ctest_test("get userdata", ret == userdata); + ctest_test("clearing userdata", refobject_set_userdata(a, NULL) == 0); + ret = refobject_get_userdata(a); + ctest_test("get userdata", ret == NULL); + ctest_test("un-referenced", refobject_unref(a) == 0); +} + +static void test_associated(void) +{ + refobject_t a; + buffer_t *b; + + a = refobject_new(sizeof(refobject_base_t), NULL, NULL, NULL, REFOBJECT_NULL); + ctest_test("refobject created", !REFOBJECT_IS_NULL(a)); + + + b = buffer_new(-1, NULL, NULL, a); + ctest_test("buffer created with associated", !REFOBJECT_IS_NULL(b)); + + ctest_test("un-referenced (1 of 2)", refobject_unref(b) == 0); + ctest_test("un-referenced (2 of 2)", refobject_unref(a) == 0); +} + +static void test_empty(void) +{ + buffer_t *a; + const void *data = &data; + size_t length = 5; + const char *string; + int ret; + + a = buffer_new_simple(); + ctest_test("buffer created", a != NULL); + + ret = buffer_get_data(a, &data, &length); + ctest_test("got data and length from buffer", ret == 0); + if (ret == 0) { + ctest_test("data is updated", data != &data); + ctest_test("length is zero", length == 0); + } + + data = &data; + ret = buffer_get_data(a, &data, NULL); + ctest_test("got data from buffer", ret == 0); + if (ret == 0) { + ctest_test("data is updated", data != &data); + } + + length = 5; + ret = buffer_get_data(a, NULL, &length); + ctest_test("got length from buffer", ret == 0); + if (ret == 0) { + ctest_test("length is zero", length == 0); + } + + ret = buffer_get_string(a, &string); + ctest_test("got string from buffer", ret == 0); + if (ret == 0) { + ctest_test("string is non-NULL", string != NULL); + if (string != NULL) { + ctest_test("string is empty", *string == 0); + } + } + + ctest_test("un-referenced", refobject_unref(a) == 0); +} + +static void test_string(void) +{ + buffer_t *a; + const char *hw = "Hello World!"; + const char *count = "0 1 2 3 4"; + const char *combined = "Hello World!" "0 1 2 3 4"; + const char *string = NULL; + int ret; + + a = buffer_new_simple(); + ctest_test("buffer created", a != NULL); + ctest_test("pushed string", buffer_push_string(a, hw) == 0); + ret = buffer_get_string(a, &string); + ctest_test("got strong", ret == 0); + if (ret == 0) { + ctest_test("string is non-NULL", string != NULL); + if (string != NULL) { + ctest_test("string matches input", strcmp(string, hw) == 0); + } + } + + ctest_test("pushed string", buffer_push_string(a, count) == 0); + string = NULL; + ret = buffer_get_string(a, &string); + ctest_test("got strong", ret == 0); + if (ret == 0) { + ctest_test("string is non-NULL", string != NULL); + if (string != NULL) { + ctest_test("string matches combined input", strcmp(string, combined) == 0); + } + } + + ctest_test("un-referenced", refobject_unref(a) == 0); +} + +static void test_binary(void) +{ + buffer_t *a; + char pattern_a[8] = {0x01, 0x10, 0x80, 0xFF, 0x00, 0x55, 0xEE, 0xAA}; + char pattern_b[9] = {0x02, 0x03, 0xF0, 0x80, 0x0F, 0x04, 0x1A, 0x7F, 0x33}; + int ret; + size_t length; + const void *data; + + a = buffer_new_simple(); + ctest_test("buffer created", a != NULL); + + ctest_test("pushed data pattern a", buffer_push_data(a, pattern_a, sizeof(pattern_a)) == 0); + length = sizeof(pattern_a) + 42; + data = &data; + ret = buffer_get_data(a, &data, &length); + ctest_test("got data", ret == 0); + if (ret == 0) { + ctest_test("correct length was returned", length == sizeof(pattern_a)); + ctest_test("data is non-NULL", data != NULL); + ctest_test("data has been set", data != &data); + if (length == sizeof(pattern_a) && data != NULL && data != &data) { + ctest_test("data matches pattern", memcmp(data, pattern_a, sizeof(pattern_a)) == 0); + } + } + + ctest_test("pushed data pattern b", buffer_push_data(a, pattern_b, sizeof(pattern_b)) == 0); + length = sizeof(pattern_a) + sizeof(pattern_b) + 42; + data = &data; + ret = buffer_get_data(a, &data, &length); + ctest_test("got data", ret == 0); + if (ret == 0) { + ctest_test("correct length was returned", length == (sizeof(pattern_a) + sizeof(pattern_b))); + ctest_test("data is non-NULL", data != NULL); + ctest_test("data has been set", data != &data); + if (length == (sizeof(pattern_a) + sizeof(pattern_b)) && data != NULL && data != &data) { + ctest_test("data matches combined pattern", memcmp(data, pattern_a, sizeof(pattern_a)) == 0 && memcmp(data + sizeof(pattern_a), pattern_b, sizeof(pattern_b)) == 0); + } + } + + + ctest_test("un-referenced", refobject_unref(a) == 0); +} + +static void test__compare_to_string(buffer_t *a, const char *testname, const char *pattern) +{ + const char *string = NULL; + int ret; + + ret = buffer_get_string(a, &string); + ctest_test("got strong", ret == 0); + if (ret == 0) { + ctest_test("string is non-NULL", string != NULL); + if (string != NULL) { + ctest_test(testname, strcmp(string, pattern) == 0); + ctest_diagnostic_printf("string=\"%s\", pattern=\"%s\"", string, pattern); + } + } +} + +static void test_shift(void) +{ + buffer_t *a; + const char *pattern = "AABBBCC"; + + a = buffer_new_simple(); + ctest_test("buffer created", a != NULL); + + ctest_test("pushed string", buffer_push_string(a, pattern) == 0); + test__compare_to_string(a, "string matches input", pattern); + ctest_test("shifted data by 0 bytes", buffer_shift(a, 0) == 0); + test__compare_to_string(a, "string matches input (no shift happened)", pattern); + ctest_test("shifted data by 2 bytes", buffer_shift(a, 2) == 0); + test__compare_to_string(a, "string matches shifted input", pattern + 2); + ctest_test("shifted data by 3 bytes", buffer_shift(a, 3) == 0); + test__compare_to_string(a, "string matches shifted input", pattern + 2 + 3); + ctest_test("shifted data by 3 bytes", buffer_shift(a, 2) == 0); + test__compare_to_string(a, "string matches shifted input", pattern + 2 + 3 + 2); + ctest_test("shifted data beyond end (42 bytes)", buffer_shift(a, 42) != 0); + + ctest_test("un-referenced", refobject_unref(a) == 0); +} + +static void test_length(void) +{ + buffer_t *a; + const char *pattern = "AABBBCC"; + const char *match_a = "AABBB"; + const char *match_b = "AABB"; + const char *match_c = ""; + + a = buffer_new_simple(); + ctest_test("buffer created", a != NULL); + + ctest_test("pushed string", buffer_push_string(a, pattern) == 0); + test__compare_to_string(a, "string matches input", pattern); + ctest_test("Set length to match pattern a", buffer_set_length(a, strlen(match_a)) == 0); + test__compare_to_string(a, "string matches pattern a", match_a); + ctest_test("Set length to match pattern b", buffer_set_length(a, strlen(match_b)) == 0); + test__compare_to_string(a, "string matches pattern a", match_b); + ctest_test("Set length to match pattern c", buffer_set_length(a, strlen(match_c)) == 0); + test__compare_to_string(a, "string matches pattern a", match_c); + ctest_test("Set length to match pattern a (again)", buffer_set_length(a, strlen(match_a)) != 0); + test__compare_to_string(a, "string still matches pattern c", match_c); + + ctest_test("un-referenced", refobject_unref(a) == 0); +} + +static void test_printf(void) +{ + buffer_t *a; + const char *str = "Hello World!"; + const int num = -127; + const char *match_a = ":Hello World!:"; + const char *match_b = ":Hello World!:<-127 >"; + const char *match_c = ":Hello World!:<-127 >? +127?"; + + a = buffer_new_simple(); + ctest_test("buffer created", a != NULL); + + ctest_test("Set length to match pattern a", buffer_push_printf(a, ":%s:", str) == 0); + test__compare_to_string(a, "string matches pattern a", match_a); + ctest_test("Set length to match pattern a", buffer_push_printf(a, "<%-5i>", num) == 0); + test__compare_to_string(a, "string matches pattern b", match_b); + ctest_test("Set length to match pattern a", buffer_push_printf(a, "?%+5i?", -num) == 0); + test__compare_to_string(a, "string matches pattern c", match_c); + + ctest_test("un-referenced", refobject_unref(a) == 0); +} + +static void test_push_buffer(void) +{ + buffer_t *a; + buffer_t *b; + const char *pattern = "AABBBCC"; + const char *match_a = "AABBBCCAABBBCC"; + + a = buffer_new_simple(); + ctest_test("buffer a created", a != NULL); + b = buffer_new_simple(); + ctest_test("buffer b created", b != NULL); + + ctest_test("pushed string", buffer_push_string(a, pattern) == 0); + test__compare_to_string(a, "string matches input", pattern); + + ctest_test("pushed buffer a to b", buffer_push_buffer(b, a) == 0); + test__compare_to_string(b, "string matches input", pattern); + + ctest_test("pushed buffer a to b", buffer_push_buffer(b, a) == 0); + test__compare_to_string(b, "string matches pattern a", match_a); + + ctest_test("un-referenced b", refobject_unref(b) == 0); + ctest_test("un-referenced a", refobject_unref(a) == 0); +} + +int main (void) +{ + ctest_init(); + + + test_create_ref_unref(); + + test_name(); + test_userdata(); + test_associated(); + + test_empty(); + test_string(); + test_binary(); + + test_shift(); + test_length(); + + test_printf(); + test_push_buffer(); + + ctest_fin(); + + return 0; +}