/* * The growable string buffer -- src/collections.c, TODO.md 3.6. * * The bounded formatting wrappers are the right answer when the destination is * a fixed buffer and no answer at all when the length is not known in advance. * This is that answer, and the properties worth holding onto are that it always * grows enough, always stays NUL-terminated, and always says so when it cannot. */ #include "aksl_capture.h" static int test_init_and_free(void) { aksl_StrBuf buf; const char *s = NULL; AKSL_CHECK_OK(aksl_strbuf_init(&buf, 0)); AKSL_CHECK(buf.data != NULL); AKSL_CHECK(buf.length == 0); /* A zero request is raised to the minimum rather than refused. */ AKSL_CHECK(buf.capacity >= 32); /* Valid as a C string immediately, with no finalise step. */ AKSL_CHECK_OK(aksl_strbuf_cstr(&buf, &s)); AKSL_CHECK(strcmp(s, "") == 0); AKSL_CHECK_OK(aksl_strbuf_free(&buf)); AKSL_CHECK(buf.data == NULL); AKSL_CHECK(buf.capacity == 0); /* Freeing twice is an error rather than a double free, as aksl_freep arranges. */ AKSL_CHECK_STATUS(aksl_strbuf_free(&buf), AKERR_NULLPOINTER); AKSL_CHECK_STATUS(aksl_strbuf_init(NULL, 16), AKERR_NULLPOINTER); AKSL_CHECK_STATUS(aksl_strbuf_free(NULL), AKERR_NULLPOINTER); return 0; } static int test_append_concatenates(void) { aksl_StrBuf buf; const char *s = NULL; AKSL_CHECK_OK(aksl_strbuf_init(&buf, 0)); AKSL_CHECK_OK(aksl_strbuf_append(&buf, "hello")); AKSL_CHECK_OK(aksl_strbuf_append_char(&buf, ' ')); AKSL_CHECK_OK(aksl_strbuf_append(&buf, "world")); AKSL_CHECK(buf.length == 11); AKSL_CHECK_OK(aksl_strbuf_cstr(&buf, &s)); AKSL_CHECK(strcmp(s, "hello world") == 0); /* Appending nothing is a no-op, not an error. */ AKSL_CHECK_OK(aksl_strbuf_append(&buf, "")); AKSL_CHECK(buf.length == 11); AKSL_CHECK_OK(aksl_strbuf_free(&buf)); return 0; } /* * Growth is the whole point. Appending far past the initial capacity has to * reallocate, and everything written before the move has to survive it. */ static int test_growth_preserves_the_contents(void) { aksl_StrBuf buf; const char *s = NULL; size_t initial = 0; int i = 0; AKSL_CHECK_OK(aksl_strbuf_init(&buf, 32)); initial = buf.capacity; for ( i = 0; i < 1000; i++ ) { AKSL_CHECK_OK(aksl_strbuf_append(&buf, "0123456789")); } AKSL_CHECK(buf.length == 10000); AKSL_CHECK(buf.capacity > initial); AKSL_CHECK(buf.capacity >= buf.length + 1); AKSL_CHECK_OK(aksl_strbuf_cstr(&buf, &s)); AKSL_CHECK(strlen(s) == 10000); /* Both ends, so a botched realloc shows up wherever it happened. */ AKSL_CHECK(strncmp(s, "0123456789", 10) == 0); AKSL_CHECK(strcmp(s + 9990, "0123456789") == 0); AKSL_CHECK_OK(aksl_strbuf_free(&buf)); return 0; } /* Bytes rather than a string, so an embedded NUL can be appended deliberately. */ static int test_append_bytes_keeps_embedded_nuls(void) { aksl_StrBuf buf; const char *s = NULL; AKSL_CHECK_OK(aksl_strbuf_init(&buf, 0)); AKSL_CHECK_OK(aksl_strbuf_append_bytes(&buf, "ab\0cd", 5)); /* length counts all five; the C string view stops at the first NUL. */ AKSL_CHECK(buf.length == 5); AKSL_CHECK_OK(aksl_strbuf_cstr(&buf, &s)); AKSL_CHECK(strlen(s) == 2); AKSL_CHECK(memcmp(s, "ab\0cd", 5) == 0); /* And the terminator is still there past the end. */ AKSL_CHECK(s[5] == '\0'); AKSL_CHECK_OK(aksl_strbuf_free(&buf)); return 0; } /* * Formatted append. vsnprintf is measured first and then written, so a long * result grows the buffer rather than truncating -- which is the difference * between this and aksl_snprintf into a fixed array. */ static int test_appendf_formats_and_grows(void) { aksl_StrBuf buf; const char *s = NULL; int i = 0; AKSL_CHECK_OK(aksl_strbuf_init(&buf, 8)); AKSL_CHECK_OK(aksl_strbuf_appendf(&buf, "%s=%d", "x", 7)); AKSL_CHECK_OK(aksl_strbuf_cstr(&buf, &s)); AKSL_CHECK(strcmp(s, "x=7") == 0); /* Far longer than the 8 bytes it started with. */ AKSL_CHECK_OK(aksl_strbuf_appendf(&buf, " %s", "and a good deal more text than that")); AKSL_CHECK_OK(aksl_strbuf_cstr(&buf, &s)); AKSL_CHECK(strcmp(s, "x=7 and a good deal more text than that") == 0); /* Repeated formatted appends, to exercise the measure/write pair often. */ AKSL_CHECK_OK(aksl_strbuf_reset(&buf)); for ( i = 0; i < 200; i++ ) { AKSL_CHECK_OK(aksl_strbuf_appendf(&buf, "[%03d]", i)); } AKSL_CHECK(buf.length == 1000); AKSL_CHECK_OK(aksl_strbuf_cstr(&buf, &s)); AKSL_CHECK(strncmp(s, "[000][001][002]", 15) == 0); AKSL_CHECK(strcmp(s + 995, "[199]") == 0); AKSL_CHECK_OK(aksl_strbuf_free(&buf)); return 0; } /* An empty format produces nothing and is not an error. */ static int test_appendf_with_no_output(void) { aksl_StrBuf buf; AKSL_CHECK_OK(aksl_strbuf_init(&buf, 0)); AKSL_CHECK_OK(aksl_strbuf_appendf(&buf, "%s", "")); AKSL_CHECK(buf.length == 0); AKSL_CHECK(buf.data[0] == '\0'); AKSL_CHECK_OK(aksl_strbuf_free(&buf)); return 0; } /* reset empties without releasing, so the capacity is reused. */ static int test_reset_keeps_the_capacity(void) { aksl_StrBuf buf; const char *s = NULL; size_t grown = 0; AKSL_CHECK_OK(aksl_strbuf_init(&buf, 0)); AKSL_CHECK_OK(aksl_strbuf_append(&buf, "something reasonably long to force a grow")); grown = buf.capacity; AKSL_CHECK_OK(aksl_strbuf_reset(&buf)); AKSL_CHECK(buf.length == 0); AKSL_CHECK(buf.capacity == grown); AKSL_CHECK_OK(aksl_strbuf_cstr(&buf, &s)); AKSL_CHECK(strcmp(s, "") == 0); /* And it still works afterwards. */ AKSL_CHECK_OK(aksl_strbuf_append(&buf, "again")); AKSL_CHECK_OK(aksl_strbuf_cstr(&buf, &s)); AKSL_CHECK(strcmp(s, "again") == 0); AKSL_CHECK_OK(aksl_strbuf_free(&buf)); return 0; } /* * Every entry point checks that the buffer was initialised, so a zeroed * aksl_StrBuf on the stack is an error rather than a NULL dereference. */ static int test_rejects_null_and_uninitialised(void) { aksl_StrBuf buf; aksl_StrBuf zeroed; const char *s = NULL; memset((void *)&zeroed, 0x00, sizeof(zeroed)); AKSL_CHECK_OK(aksl_strbuf_init(&buf, 0)); AKSL_CHECK_STATUS(aksl_strbuf_append(NULL, "x"), AKERR_NULLPOINTER); AKSL_CHECK_STATUS(aksl_strbuf_append(&buf, NULL), AKERR_NULLPOINTER); AKSL_CHECK_STATUS(aksl_strbuf_append_bytes(NULL, "x", 1), AKERR_NULLPOINTER); AKSL_CHECK_STATUS(aksl_strbuf_append_bytes(&buf, NULL, 1), AKERR_NULLPOINTER); AKSL_CHECK_STATUS(aksl_strbuf_append_char(NULL, 'x'), AKERR_NULLPOINTER); AKSL_CHECK_STATUS(aksl_strbuf_appendf(NULL, "x"), AKERR_NULLPOINTER); AKSL_CHECK_STATUS(aksl_strbuf_appendf(&buf, NULL), AKERR_NULLPOINTER); AKSL_CHECK_STATUS(aksl_strbuf_reset(NULL), AKERR_NULLPOINTER); AKSL_CHECK_STATUS(aksl_strbuf_cstr(NULL, &s), AKERR_NULLPOINTER); AKSL_CHECK_STATUS(aksl_strbuf_cstr(&buf, NULL), AKERR_NULLPOINTER); AKSL_CHECK_STATUS(aksl_strbuf_append(&zeroed, "x"), AKERR_NULLPOINTER); AKSL_CHECK_STATUS(aksl_strbuf_appendf(&zeroed, "x"), AKERR_NULLPOINTER); AKSL_CHECK_STATUS(aksl_strbuf_reset(&zeroed), AKERR_NULLPOINTER); AKSL_CHECK_STATUS(aksl_strbuf_cstr(&zeroed, &s), AKERR_NULLPOINTER); AKSL_CHECK_OK(aksl_strbuf_free(&buf)); return 0; } /* * The reason this type exists, written the way a caller would: build a report of * unknown length out of pieces, without anyone having to size a buffer first. */ static int test_builds_a_report(void) { aksl_StrBuf buf; const char *s = NULL; static const char *names[3] = { "alpha", "beta", "gamma" }; int i = 0; AKSL_CHECK_OK(aksl_strbuf_init(&buf, 0)); AKSL_CHECK_OK(aksl_strbuf_append(&buf, "items:")); for ( i = 0; i < 3; i++ ) { AKSL_CHECK_OK(aksl_strbuf_appendf(&buf, " %s(%d)", names[i], i)); } AKSL_CHECK_OK(aksl_strbuf_cstr(&buf, &s)); AKSL_CHECK(strcmp(s, "items: alpha(0) beta(1) gamma(2)") == 0); AKSL_CHECK_OK(aksl_strbuf_free(&buf)); return 0; } int main(void) { int failures = 0; akerr_init(); AKSL_RUN(failures, test_init_and_free); AKSL_RUN(failures, test_append_concatenates); AKSL_RUN(failures, test_growth_preserves_the_contents); AKSL_RUN(failures, test_append_bytes_keeps_embedded_nuls); AKSL_RUN(failures, test_appendf_formats_and_grows); AKSL_RUN(failures, test_appendf_with_no_output); AKSL_RUN(failures, test_reset_keeps_the_capacity); AKSL_RUN(failures, test_rejects_null_and_uninitialised); AKSL_RUN(failures, test_builds_a_report); AKSL_REPORT(failures); }