tests/test_buffer.c

Sun, 15 Dec 2024 16:42:44 +0100

author
Mike Becker <universe@uap-core.de>
date
Sun, 15 Dec 2024 16:42:44 +0100
changeset 1023
989e144c912a
parent 1007
81b2986d2b04
permissions
-rw-r--r--

fix out of range access in test on platforms which are actually shrinking the buffer

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2023 Mike Becker, Olaf Wintermann All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *
 *   2. Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "cx/test.h"
#include "util_allocator.h"

#include "cx/buffer.h"

static CX_TEST_SUBROUTINE(expect_default_flush_config, CxBuffer *buf) {
    CX_TEST_ASSERT(buf->flush_blkmax == 0);
    CX_TEST_ASSERT(buf->flush_blksize == 4096);
    CX_TEST_ASSERT(buf->flush_threshold == SIZE_MAX);
    CX_TEST_ASSERT(buf->flush_func == NULL);
    CX_TEST_ASSERT(buf->flush_target == NULL);
}

CX_TEST(test_buffer_init_wrap_space) {
    CxTestingAllocator talloc;
    cx_testing_allocator_init(&talloc);
    CxAllocator *alloc = &talloc.base;
    CX_TEST_DO {
        CxBuffer buf;
        void *space = cxMalloc(alloc, 16);
        cxBufferInit(&buf, space, 16, alloc, CX_BUFFER_DEFAULT);
        CX_TEST_CALL_SUBROUTINE(expect_default_flush_config, &buf);
        CX_TEST_ASSERT(buf.space == space);
        CX_TEST_ASSERT((buf.flags & CX_BUFFER_AUTO_EXTEND) == 0);
        CX_TEST_ASSERT((buf.flags & CX_BUFFER_FREE_CONTENTS) == 0);
        CX_TEST_ASSERT(buf.pos == 0);
        CX_TEST_ASSERT(buf.size == 0);
        CX_TEST_ASSERT(buf.capacity == 16);
        CX_TEST_ASSERT(buf.allocator == alloc);
        cxBufferDestroy(&buf);
        CX_TEST_ASSERT(!cx_testing_allocator_verify(&talloc));
        cxFree(alloc, space);
        CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
    }
    cx_testing_allocator_destroy(&talloc);
}

CX_TEST(test_buffer_init_wrap_space_auto_extend) {
    CxTestingAllocator talloc;
    cx_testing_allocator_init(&talloc);
    CxAllocator *alloc = &talloc.base;
    CX_TEST_DO {
        CxBuffer buf;
        void *space = cxMalloc(alloc, 16);
        cxBufferInit(&buf, space, 16, alloc, CX_BUFFER_AUTO_EXTEND);
        CX_TEST_CALL_SUBROUTINE(expect_default_flush_config, &buf);
        CX_TEST_ASSERT(buf.space == space);
        CX_TEST_ASSERT((buf.flags & CX_BUFFER_AUTO_EXTEND) == CX_BUFFER_AUTO_EXTEND);
        CX_TEST_ASSERT((buf.flags & CX_BUFFER_FREE_CONTENTS) == 0);
        CX_TEST_ASSERT(buf.pos == 0);
        CX_TEST_ASSERT(buf.size == 0);
        CX_TEST_ASSERT(buf.capacity == 16);
        CX_TEST_ASSERT(buf.allocator == alloc);
        cxBufferDestroy(&buf);
        CX_TEST_ASSERT(!cx_testing_allocator_verify(&talloc));
        cxFree(alloc, space);
        CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
    }
    cx_testing_allocator_destroy(&talloc);
}

CX_TEST(test_buffer_init_wrap_space_auto_free) {
    CxTestingAllocator talloc;
    cx_testing_allocator_init(&talloc);
    CxAllocator *alloc = &talloc.base;
    CX_TEST_DO {
        CxBuffer buf;
        void *space = cxMalloc(alloc, 16);
        cxBufferInit(&buf, space, 16, alloc, CX_BUFFER_FREE_CONTENTS);
        CX_TEST_CALL_SUBROUTINE(expect_default_flush_config, &buf);
        CX_TEST_ASSERT(buf.space == space);
        CX_TEST_ASSERT((buf.flags & CX_BUFFER_AUTO_EXTEND) == 0);
        CX_TEST_ASSERT((buf.flags & CX_BUFFER_FREE_CONTENTS) == CX_BUFFER_FREE_CONTENTS);
        CX_TEST_ASSERT(buf.pos == 0);
        CX_TEST_ASSERT(buf.size == 0);
        CX_TEST_ASSERT(buf.capacity == 16);
        CX_TEST_ASSERT(buf.allocator == alloc);
        CX_TEST_ASSERT(!cx_testing_allocator_verify(&talloc));
        cxBufferDestroy(&buf);
        CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
    }
    cx_testing_allocator_destroy(&talloc);
}

CX_TEST(test_buffer_init_fresh_space) {
    CxTestingAllocator talloc;
    cx_testing_allocator_init(&talloc);
    CxAllocator *alloc = &talloc.base;
    CX_TEST_DO {
        CxBuffer buf;
        cxBufferInit(&buf, NULL, 8, alloc, CX_BUFFER_DEFAULT);
        CX_TEST_CALL_SUBROUTINE(expect_default_flush_config, &buf);
        CX_TEST_ASSERT(buf.space != NULL);
        CX_TEST_ASSERT((buf.flags & CX_BUFFER_AUTO_EXTEND) == 0);
        CX_TEST_ASSERT((buf.flags & CX_BUFFER_FREE_CONTENTS) == CX_BUFFER_FREE_CONTENTS);
        CX_TEST_ASSERT(buf.pos == 0);
        CX_TEST_ASSERT(buf.size == 0);
        CX_TEST_ASSERT(buf.capacity == 8);
        CX_TEST_ASSERT(buf.allocator == alloc);
        CX_TEST_ASSERT(!cx_testing_allocator_verify(&talloc)); // space is still allocated
        cxBufferDestroy(&buf);
        CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
    }
    cx_testing_allocator_destroy(&talloc);
}

CX_TEST(test_buffer_init_on_heap) {
    CxTestingAllocator talloc;
    cx_testing_allocator_init(&talloc);
    CxAllocator *alloc = &talloc.base;
    CX_TEST_DO {
        CxBuffer *buf;
        void *space = cxMalloc(alloc, 16);
        buf = cxBufferCreate(space, 16, alloc, CX_BUFFER_FREE_CONTENTS);
        CX_TEST_ASSERT(buf != NULL);
        CX_TEST_CALL_SUBROUTINE(expect_default_flush_config, buf);
        CX_TEST_ASSERT(buf->space == space);
        CX_TEST_ASSERT((buf->flags & CX_BUFFER_AUTO_EXTEND) == 0);
        CX_TEST_ASSERT((buf->flags & CX_BUFFER_FREE_CONTENTS) == CX_BUFFER_FREE_CONTENTS);
        CX_TEST_ASSERT(buf->pos == 0);
        CX_TEST_ASSERT(buf->size == 0);
        CX_TEST_ASSERT(buf->capacity == 16);
        CX_TEST_ASSERT(buf->allocator == alloc);
        cxBufferFree(buf);
        CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
    }
    cx_testing_allocator_destroy(&talloc);
}

CX_TEST(test_buffer_minimum_capacity_sufficient) {
    CxTestingAllocator talloc;
    cx_testing_allocator_init(&talloc);
    CxAllocator *alloc = &talloc.base;
    CX_TEST_DO {
        void *space = cxMalloc(alloc, 8);
        CxBuffer buf;
        cxBufferInit(&buf, space, 8, alloc, CX_BUFFER_FREE_CONTENTS);
        memcpy(space, "Testing", 8);
        buf.size = 8;
        cxBufferMinimumCapacity(&buf, 6);
        CX_TEST_ASSERT(buf.capacity == 8);
        CX_TEST_ASSERT(buf.size == 8);
        CX_TEST_ASSERT(memcmp(buf.space, "Testing", 8) == 0);
        cxBufferDestroy(&buf);
        CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
    }
    cx_testing_allocator_destroy(&talloc);
}

CX_TEST(test_buffer_minimum_capacity_extend) {
    CxTestingAllocator talloc;
    cx_testing_allocator_init(&talloc);
    CxAllocator *alloc = &talloc.base;
    CX_TEST_DO {
        void *space = cxMalloc(alloc, 8);
        CxBuffer buf;
        cxBufferInit(&buf, space, 8, alloc, CX_BUFFER_FREE_CONTENTS); // NO auto extend!
        memcpy(space, "Testing", 8);
        buf.size = 8;
        cxBufferMinimumCapacity(&buf, 16);
        CX_TEST_ASSERT(buf.capacity == 16);
        CX_TEST_ASSERT(buf.size == 8);
        CX_TEST_ASSERT(memcmp(buf.space, "Testing", 8) == 0);
        cxBufferDestroy(&buf);
        CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
    }
    cx_testing_allocator_destroy(&talloc);
}

CX_TEST(test_buffer_clear) {
    char space[16];
    strcpy(space, "clear test");
    CxBuffer buf;
    cxBufferInit(&buf, space, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT);
    CX_TEST_DO {
        CX_TEST_ASSERT(buf.size == 0);
        // only clear the used part of the buffer
        cxBufferClear(&buf);
        CX_TEST_ASSERT(0 == memcmp(space, "clear test", 10));
        buf.size = 5;
        buf.pos = 3;
        cxBufferClear(&buf);
        CX_TEST_ASSERT(0 == memcmp(space, "\0\0\0\0\0 test", 10));
        CX_TEST_ASSERT(buf.size == 0);
        CX_TEST_ASSERT(buf.pos == 0);
    }
    cxBufferDestroy(&buf);
}

CX_TEST(test_buffer_reset) {
    char space[16];
    strcpy(space, "reset test");
    CxBuffer buf;
    cxBufferInit(&buf, space, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT);
    CX_TEST_DO {
        buf.size = 5;
        buf.pos = 3;
        cxBufferReset(&buf);
        CX_TEST_ASSERT(0 == memcmp(space, "reset test", 10));
        CX_TEST_ASSERT(buf.size == 0);
        CX_TEST_ASSERT(buf.pos == 0);
    }
    cxBufferDestroy(&buf);
}

CX_TEST(test_buffer_seek_set_zero) {
    CxBuffer buf;
    cxBufferInit(&buf, NULL, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT);
    buf.size = 6;
    buf.pos = 3;
    CX_TEST_DO {
        int result = cxBufferSeek(&buf, 0, SEEK_SET);
        CX_TEST_ASSERT(result == 0);
        CX_TEST_ASSERT(buf.pos == 0);
    }
    cxBufferDestroy(&buf);
}

CX_TEST(test_buffer_seek_set_valid) {
    CxBuffer buf;
    cxBufferInit(&buf, NULL, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT);
    buf.size = 6;
    buf.pos = 3;
    CX_TEST_DO {
        int result = cxBufferSeek(&buf, 5, SEEK_SET);
        CX_TEST_ASSERT(result == 0);
        CX_TEST_ASSERT(buf.pos == 5);
    }
    cxBufferDestroy(&buf);
}

CX_TEST(test_buffer_seek_set_invalid) {
    CxBuffer buf;
    cxBufferInit(&buf, NULL, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT);
    buf.size = 6;
    buf.pos = 3;
    CX_TEST_DO {
        int result = cxBufferSeek(&buf, 7, SEEK_SET);
        CX_TEST_ASSERT(result != 0);
        CX_TEST_ASSERT(buf.pos == 3);
    }
    cxBufferDestroy(&buf);
}

CX_TEST(test_buffer_seek_cur_zero) {
    CxBuffer buf;
    cxBufferInit(&buf, NULL, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT);
    buf.size = 6;
    buf.pos = 3;
    CX_TEST_DO {
        int result = cxBufferSeek(&buf, 0, SEEK_CUR);
        CX_TEST_ASSERT(result == 0);
        CX_TEST_ASSERT(buf.pos == 3);
    }
    cxBufferDestroy(&buf);
}

CX_TEST(test_buffer_seek_cur_valid_positive) {
    CxBuffer buf;
    cxBufferInit(&buf, NULL, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT);
    buf.size = 6;
    buf.pos = 3;
    CX_TEST_DO {
        int result = cxBufferSeek(&buf, 2, SEEK_CUR);
        CX_TEST_ASSERT(result == 0);
        CX_TEST_ASSERT(buf.pos == 5);
    }
    cxBufferDestroy(&buf);
}

CX_TEST(test_buffer_seek_cur_valid_negative) {
    CxBuffer buf;
    cxBufferInit(&buf, NULL, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT);
    buf.size = 6;
    buf.pos = 3;
    CX_TEST_DO {
        int result = cxBufferSeek(&buf, -3, SEEK_CUR);
        CX_TEST_ASSERT(result == 0);
        CX_TEST_ASSERT(buf.pos == 0);
    }
    cxBufferDestroy(&buf);
}

CX_TEST(test_buffer_seek_cur_invalid_positive) {
    CxBuffer buf;
    cxBufferInit(&buf, NULL, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT);
    buf.size = 6;
    buf.pos = 3;
    CX_TEST_DO {
        int result = cxBufferSeek(&buf, 4, SEEK_CUR);
        CX_TEST_ASSERT(result != 0);
        CX_TEST_ASSERT(buf.pos == 3);
    }
    cxBufferDestroy(&buf);
}

CX_TEST(test_buffer_seek_cur_invalid_negative) {
    CxBuffer buf;
    cxBufferInit(&buf, NULL, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT);
    buf.size = 6;
    buf.pos = 3;
    CX_TEST_DO {
        int result = cxBufferSeek(&buf, -4, SEEK_CUR);
        CX_TEST_ASSERT(result != 0);
        CX_TEST_ASSERT(buf.pos == 3);
    }
    cxBufferDestroy(&buf);
}

CX_TEST(test_buffer_seek_end_zero) {
    CxBuffer buf;
    cxBufferInit(&buf, NULL, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT);
    buf.size = 6;
    buf.pos = 3;
    CX_TEST_DO {
        int result = cxBufferSeek(&buf, 0, SEEK_END);
        CX_TEST_ASSERT(result == 0);
        CX_TEST_ASSERT(buf.pos == 6);
    }
    cxBufferDestroy(&buf);
}

CX_TEST(test_buffer_seek_end_valid) {
    CxBuffer buf;
    cxBufferInit(&buf, NULL, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT);
    buf.size = 6;
    buf.pos = 3;
    CX_TEST_DO {
        int result = cxBufferSeek(&buf, -6, SEEK_END);
        CX_TEST_ASSERT(result == 0);
        CX_TEST_ASSERT(buf.pos == 0);
    }
    cxBufferDestroy(&buf);
}

CX_TEST(test_buffer_seek_end_invalid) {
    CxBuffer buf;
    cxBufferInit(&buf, NULL, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT);
    buf.size = 6;
    buf.pos = 3;
    CX_TEST_DO {
        int result = cxBufferSeek(&buf, 1, SEEK_END);
        CX_TEST_ASSERT(result != 0);
        CX_TEST_ASSERT(buf.pos == 3);
    }
    cxBufferDestroy(&buf);
}

CX_TEST(test_buffer_seek_whence_invalid) {
    CxBuffer buf;
    cxBufferInit(&buf, NULL, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT);
    buf.size = 6;
    buf.pos = 3;
    CX_TEST_DO {
        int result = cxBufferSeek(&buf, 2, 9000);
        CX_TEST_ASSERT(result != 0);
        CX_TEST_ASSERT(buf.size == 6);
        CX_TEST_ASSERT(buf.pos == 3);
    }
    cxBufferDestroy(&buf);
}

CX_TEST(test_buffer_eof_reached) {
    CxBuffer buf;
    cxBufferInit(&buf, NULL, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT);
    buf.size = buf.pos = 3;
    CX_TEST_DO {
        CX_TEST_ASSERT(cxBufferEof(&buf));
        buf.pos = buf.size - 1;
        CX_TEST_ASSERT(!cxBufferEof(&buf));
        cxBufferPut(&buf, 'a');
        CX_TEST_ASSERT(cxBufferEof(&buf));
    }
    cxBufferDestroy(&buf);
}

CX_TEST(test_buffer_eof_not_reached) {
    CxBuffer buf;
    cxBufferInit(&buf, NULL, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT);
    buf.size = 6;
    CX_TEST_DO {
        buf.pos = buf.size - 1;
        CX_TEST_ASSERT(!cxBufferEof(&buf));
        buf.pos = 0;
        cxBufferWrite("test", 1, 5, &buf);
        CX_TEST_ASSERT(!cxBufferEof(&buf));
    }
    cxBufferDestroy(&buf);
}

#define TEST_BUFFER_SHIFT_SETUP(buf) \
    CxTestingAllocator talloc; \
    cx_testing_allocator_init(&talloc); \
    CxAllocator *alloc = &talloc.base; \
    CxBuffer buf; \
    cxBufferInit(&buf, NULL, 16, alloc, CX_BUFFER_DEFAULT); \
    memcpy(buf.space, "test____XXXXXXXX", 16); \
    buf.capacity = 8; \
    buf.pos = 4; \
    buf.size = 4
#define TEST_BUFFER_SHIFT_TEARDOWN(buf) \
    cxBufferDestroy(&buf); \
    CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); \
    cx_testing_allocator_destroy(&talloc)


CX_TEST(test_buffer_shift_left_zero) {
    TEST_BUFFER_SHIFT_SETUP(buf);
    CX_TEST_DO {
        int ret = cxBufferShiftLeft(&buf, 0);
        CX_TEST_ASSERT(ret == 0);
        CX_TEST_ASSERT(buf.pos == 4);
        CX_TEST_ASSERT(buf.size == 4);
        CX_TEST_ASSERT(memcmp(buf.space, "test____XXXXXXXX", 16) == 0);
        TEST_BUFFER_SHIFT_TEARDOWN(buf);
    }
}

CX_TEST(test_buffer_shift_left_zero_offset_interface) {
    TEST_BUFFER_SHIFT_SETUP(buf);
    CX_TEST_DO {
        int ret = cxBufferShift(&buf, -0);
        CX_TEST_ASSERT(ret == 0);
        CX_TEST_ASSERT(buf.pos == 4);
        CX_TEST_ASSERT(buf.size == 4);
        CX_TEST_ASSERT(memcmp(buf.space, "test____XXXXXXXX", 16) == 0);
        TEST_BUFFER_SHIFT_TEARDOWN(buf);
    }
}

CX_TEST(test_buffer_shift_left_standard) {
    TEST_BUFFER_SHIFT_SETUP(buf);
    CX_TEST_DO {
        int ret = cxBufferShiftLeft(&buf, 2);
        CX_TEST_ASSERT(ret == 0);
        CX_TEST_ASSERT(buf.pos == 2);
        CX_TEST_ASSERT(buf.size == 2);
        CX_TEST_ASSERT(memcmp(buf.space, "stst____XXXXXXXX", 16) == 0);
        TEST_BUFFER_SHIFT_TEARDOWN(buf);
    }
}

CX_TEST(test_buffer_shift_left_overshift) {
    TEST_BUFFER_SHIFT_SETUP(buf);
    CX_TEST_DO {
        int ret = cxBufferShiftLeft(&buf, 6);
        CX_TEST_ASSERT(ret == 0);
        CX_TEST_ASSERT(buf.pos == 0);
        CX_TEST_ASSERT(buf.size == 0);
        CX_TEST_ASSERT(memcmp(buf.space, "test____XXXXXXXX", 16) == 0);
        TEST_BUFFER_SHIFT_TEARDOWN(buf);
    }
}

CX_TEST(test_buffer_shift_left_overshift_pos_only) {
    TEST_BUFFER_SHIFT_SETUP(buf);
    buf.pos = 2;
    CX_TEST_DO {
        int ret = cxBufferShiftLeft(&buf, 3);
        CX_TEST_ASSERT(ret == 0);
        CX_TEST_ASSERT(buf.pos == 0);
        CX_TEST_ASSERT(buf.size == 1);
        CX_TEST_ASSERT(memcmp(buf.space, "test____XXXXXXXX", 16) == 0);
        TEST_BUFFER_SHIFT_TEARDOWN(buf);
    }
}

CX_TEST(test_buffer_shift_left_offset_interface) {
    TEST_BUFFER_SHIFT_SETUP(buf);
    buf.pos = 3;
    CX_TEST_DO {
        int ret = cxBufferShift(&buf, -2);
        CX_TEST_ASSERT(ret == 0);
        CX_TEST_ASSERT(buf.pos == 1);
        CX_TEST_ASSERT(buf.size == 2);
        CX_TEST_ASSERT(memcmp(buf.space, "stst____XXXXXXXX", 16) == 0);
        TEST_BUFFER_SHIFT_TEARDOWN(buf);
    }
}

CX_TEST(test_buffer_shift_right_zero) {
    TEST_BUFFER_SHIFT_SETUP(buf);
    CX_TEST_DO {
        int ret = cxBufferShiftRight(&buf, 0);
        CX_TEST_ASSERT(ret == 0);
        CX_TEST_ASSERT(buf.pos == 4);
        CX_TEST_ASSERT(buf.size == 4);
        CX_TEST_ASSERT(memcmp(buf.space, "test____XXXXXXXX", 16) == 0);
        TEST_BUFFER_SHIFT_TEARDOWN(buf);
    }
}

CX_TEST(test_buffer_shift_right_zero_offset_interface) {
    TEST_BUFFER_SHIFT_SETUP(buf);
    CX_TEST_DO {
        int ret = cxBufferShift(&buf, +0);
        CX_TEST_ASSERT(ret == 0);
        CX_TEST_ASSERT(buf.pos == 4);
        CX_TEST_ASSERT(buf.size == 4);
        CX_TEST_ASSERT(memcmp(buf.space, "test____XXXXXXXX", 16) == 0);
        TEST_BUFFER_SHIFT_TEARDOWN(buf);
    }
}

CX_TEST(test_buffer_shift_right_standard) {
    TEST_BUFFER_SHIFT_SETUP(buf);
    CX_TEST_DO {
        int ret = cxBufferShiftRight(&buf, 3);
        CX_TEST_ASSERT(ret == 0);
        CX_TEST_ASSERT(buf.pos == 7);
        CX_TEST_ASSERT(buf.size == 7);
        CX_TEST_ASSERT(memcmp(buf.space, "testest_XXXXXXXX", 16) == 0);
        TEST_BUFFER_SHIFT_TEARDOWN(buf);
    }
}

CX_TEST(test_buffer_shift_right_overshift_discard) {
    TEST_BUFFER_SHIFT_SETUP(buf);
    CX_TEST_DO {
        int ret = cxBufferShiftRight(&buf, 6);
        CX_TEST_ASSERT(ret == 0);
        CX_TEST_ASSERT(buf.pos == 8);
        CX_TEST_ASSERT(buf.size == 8);
        CX_TEST_ASSERT(buf.capacity == 8);
        CX_TEST_ASSERT(memcmp(buf.space, "test__teXXXXXXXX", 16) == 0);
        TEST_BUFFER_SHIFT_TEARDOWN(buf);
    }
}

CX_TEST(test_buffer_shift_right_overshift_extend) {
    TEST_BUFFER_SHIFT_SETUP(buf);
    buf.flags |= CX_BUFFER_AUTO_EXTEND;
    CX_TEST_DO {
        int ret = cxBufferShiftRight(&buf, 6);
        CX_TEST_ASSERT(ret == 0);
        CX_TEST_ASSERT(buf.pos == 10);
        CX_TEST_ASSERT(buf.size == 10);
        CX_TEST_ASSERT(buf.capacity >= 10);
        // cannot assert more than 10 bytes because
        // the buffer was required to reallocate the space
        CX_TEST_ASSERT(memcmp(buf.space, "test__test", 10) == 0);
        TEST_BUFFER_SHIFT_TEARDOWN(buf);
    }
}

CX_TEST(test_buffer_shift_right_offset_interface) {
    TEST_BUFFER_SHIFT_SETUP(buf);
    buf.pos = 3;
    CX_TEST_DO {
        int ret = cxBufferShift(&buf, 2);
        CX_TEST_ASSERT(ret == 0);
        CX_TEST_ASSERT(buf.pos == 5);
        CX_TEST_ASSERT(buf.size == 6);
        CX_TEST_ASSERT(memcmp(buf.space, "tetest__XXXXXXXX", 16) == 0);
        TEST_BUFFER_SHIFT_TEARDOWN(buf);
    }
}

static size_t mock_write_limited_rate(
        const void *ptr,
        size_t size,
        cx_attr_unused size_t nitems,
        CxBuffer *buffer
) {
    // simulate limited target drain capacity
    static bool full = false;
    if (full) {
        full = false;
        return 0;
    } else {
        full = true;
        return cxBufferWrite(ptr, size, nitems > 2 ? 2 : nitems, buffer);
    }
}

CX_TEST(test_buffer_write_size_one_fit) {
    CxBuffer buf;
    cxBufferInit(&buf, NULL, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT);
    memcpy(buf.space, "prep\0\0\0\0\0\0\0\0\0\0\0\0", 16);
    buf.capacity = 8;
    buf.size = buf.pos = 4;
    const char *data = "test";
    CX_TEST_DO {
        size_t written = cxBufferWrite(data, 1, 4, &buf);
        CX_TEST_ASSERT(written == 4);
        CX_TEST_ASSERT(buf.size == 8);
        CX_TEST_ASSERT(buf.pos == 8);
        CX_TEST_ASSERT(buf.capacity == 8);
        CX_TEST_ASSERT(0 == memcmp(buf.space, "preptest", 8));
    }

    cxBufferDestroy(&buf);
}

CX_TEST(test_buffer_write_size_one_discard) {
    CxBuffer buf;
    cxBufferInit(&buf, NULL, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT);
    memcpy(buf.space, "prep\0\0\0\0\0\0\0\0\0\0\0\0", 16);
    buf.capacity = 8;
    buf.size = buf.pos = 4;
    const char *data = "testing";
    CX_TEST_DO {
        size_t written = cxBufferWrite(data, 1, 7, &buf);
        CX_TEST_ASSERT(written == 4);
        CX_TEST_ASSERT(buf.size == 8);
        CX_TEST_ASSERT(buf.pos == 8);
        CX_TEST_ASSERT(buf.capacity == 8);
        CX_TEST_ASSERT(0 == memcmp(buf.space, "preptest\0", 9));
    }
    cxBufferDestroy(&buf);
}

CX_TEST(test_buffer_write_size_one_extend) {
    CxBuffer buf;
    cxBufferInit(&buf, NULL, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT);
    memcpy(buf.space, "prep\0\0\0\0\0\0\0\0\0\0\0\0", 16);
    buf.capacity = 8;
    buf.size = buf.pos = 4;
    buf.flags |= CX_BUFFER_AUTO_EXTEND;
    const char *data = "testing";
    CX_TEST_DO {
        size_t written = cxBufferWrite(data, 1, 7, &buf);
        CX_TEST_ASSERT(written == 7);
        CX_TEST_ASSERT(buf.size == 11);
        CX_TEST_ASSERT(buf.pos == 11);
        CX_TEST_ASSERT(buf.capacity >= 11);
        CX_TEST_ASSERT(0 == memcmp(buf.space, "preptesting", 11));
    }
    cxBufferDestroy(&buf);
}

CX_TEST(test_buffer_write_multibyte_fit) {
    CxBuffer buf;
    cxBufferInit(&buf, NULL, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT);
    memcpy(buf.space, "prep\0\0\0\0\0\0\0\0\0\0\0\0", 16);
    buf.capacity = 8;
    buf.size = buf.pos = 4;
    const char *data = "test";
    CX_TEST_DO {
        size_t written = cxBufferWrite(data, 2, 2, &buf);
        CX_TEST_ASSERT(written == 2);
        CX_TEST_ASSERT(buf.size == 8);
        CX_TEST_ASSERT(buf.pos == 8);
        CX_TEST_ASSERT(buf.capacity == 8);
        CX_TEST_ASSERT(0 == memcmp(buf.space, "preptest", 8));
    }
    cxBufferDestroy(&buf);
}

CX_TEST(test_buffer_write_multibyte_discard) {
    CxBuffer buf;
    cxBufferInit(&buf, NULL, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT);
    memcpy(buf.space, "prep\0\0\0\0\0\0\0\0\0\0\0\0", 16);
    buf.capacity = 8;
    buf.size = 4;
    buf.pos = 3;
    const char *data = "testing";
    CX_TEST_DO {
        size_t written = cxBufferWrite(data, 2, 4, &buf);
        // remember: whole elements are discarded if they do not fit
        CX_TEST_ASSERT(written == 2);
        CX_TEST_ASSERT(buf.size == 7);
        CX_TEST_ASSERT(buf.pos == 7);
        CX_TEST_ASSERT(buf.capacity == 8);
        CX_TEST_ASSERT(0 == memcmp(buf.space, "pretest\0", 8));
    }
    cxBufferDestroy(&buf);
}

CX_TEST(test_buffer_write_multibyte_extend) {
    CxBuffer buf;
    cxBufferInit(&buf, NULL, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT);
    memcpy(buf.space, "prep\0\0\0\0\0\0\0\0\0\0\0\0", 16);
    buf.capacity = 8;
    buf.size = 4;
    buf.pos = 3;
    buf.flags |= CX_BUFFER_AUTO_EXTEND;
    const char *data = "tester";
    CX_TEST_DO {
        size_t written = cxBufferWrite(data, 2, 3, &buf);
        // remember: whole elements are discarded if they do not fit
        CX_TEST_ASSERT(written == 3);
        CX_TEST_ASSERT(buf.size == 9);
        CX_TEST_ASSERT(buf.pos == 9);
        CX_TEST_ASSERT(buf.capacity >= 9);
        CX_TEST_ASSERT(0 == memcmp(buf.space, "pretester", 9));
    }
    cxBufferDestroy(&buf);
}

CX_TEST(test_buffer_put_fit) {
    CxBuffer buf;
    cxBufferInit(&buf, NULL, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT);
    memcpy(buf.space, "prep\0\0\0\0\0\0\0\0\0\0\0\0", 16);
    buf.capacity = 8;
    buf.size = buf.pos = 4;
    CX_TEST_DO {
        int c = cxBufferPut(&buf, 0x200 | 'a');
        CX_TEST_ASSERT(c == 'a');
        CX_TEST_ASSERT(buf.size == 5);
        CX_TEST_ASSERT(buf.pos == 5);
        CX_TEST_ASSERT(buf.capacity == 8);
        CX_TEST_ASSERT(0 == memcmp(buf.space, "prepa\0", 6));
    }
    cxBufferDestroy(&buf);
}

CX_TEST(test_buffer_put_discard) {
    CxBuffer buf;
    cxBufferInit(&buf, NULL, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT);
    memcpy(buf.space, "prep\0\0\0\0\0\0\0\0\0\0\0\0", 16);
    buf.capacity = 8;
    buf.size = 4;
    buf.pos = 8;
    CX_TEST_DO {
        int c = cxBufferPut(&buf, 0x200 | 'a');
        CX_TEST_ASSERT(c == EOF);
        CX_TEST_ASSERT(buf.size == 4);
        CX_TEST_ASSERT(buf.pos == 8);
        CX_TEST_ASSERT(buf.capacity == 8);
        CX_TEST_ASSERT(0 == memcmp(buf.space, "prep\0\0\0\0\0", 9));
    }
    cxBufferDestroy(&buf);
}

CX_TEST(test_buffer_put_extend) {
    CxBuffer buf;
    cxBufferInit(&buf, NULL, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT);
    memcpy(buf.space, "prep\0\0\0\0\0\0\0\0\0\0\0\0", 16);
    buf.capacity = 8;
    buf.size = 4;
    buf.pos = 8;
    buf.flags |= CX_BUFFER_AUTO_EXTEND;
    CX_TEST_DO {
        int c = cxBufferPut(&buf, 0x200 | 'a');
        CX_TEST_ASSERT(c == 'a');
        CX_TEST_ASSERT(buf.size == 9);
        CX_TEST_ASSERT(buf.pos == 9);
        CX_TEST_ASSERT(buf.capacity >= 9);
        CX_TEST_ASSERT(0 == memcmp(buf.space, "prep\0\0\0\0a", 9));
    }
    cxBufferDestroy(&buf);
}

CX_TEST(test_buffer_put_string_fit) {
    CxBuffer buf;
    cxBufferInit(&buf, NULL, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT);
    memcpy(buf.space, "prep\0\0\0\0\0\0\0\0\0\0\0\0", 16);
    buf.capacity = 8;
    buf.size = buf.pos = 4;
    const char *data = "test";
    CX_TEST_DO {
        size_t written = cxBufferPutString(&buf, data);
        CX_TEST_ASSERT(written == 4);
        CX_TEST_ASSERT(buf.size == 8);
        CX_TEST_ASSERT(buf.pos == 8);
        CX_TEST_ASSERT(buf.capacity == 8);
        CX_TEST_ASSERT(0 == memcmp(buf.space, "preptest", 8));
    }
    cxBufferDestroy(&buf);
}

CX_TEST(test_buffer_put_string_discard) {
    CxBuffer buf;
    cxBufferInit(&buf, NULL, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT);
    memcpy(buf.space, "prep\0\0\0\0\0\0\0\0\0\0\0\0", 16);
    buf.capacity = 8;
    buf.size = buf.pos = 4;
    const char *data = "testing";
    CX_TEST_DO {
        size_t written = cxBufferPutString(&buf, data);
        CX_TEST_ASSERT(written == 4);
        CX_TEST_ASSERT(buf.size == 8);
        CX_TEST_ASSERT(buf.pos == 8);
        CX_TEST_ASSERT(buf.capacity == 8);
        CX_TEST_ASSERT(0 == memcmp(buf.space, "preptest\0", 9));
    }
    cxBufferDestroy(&buf);
}

CX_TEST(test_buffer_put_string_extend) {
    CxBuffer buf;
    cxBufferInit(&buf, NULL, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT);
    memcpy(buf.space, "prep\0\0\0\0\0\0\0\0\0\0\0\0", 16);
    buf.capacity = 8;
    buf.size = buf.pos = 4;
    buf.flags |= CX_BUFFER_AUTO_EXTEND;
    const char *data = "testing";
    CX_TEST_DO {
        size_t written = cxBufferPutString(&buf, data);
        CX_TEST_ASSERT(written == 7);
        CX_TEST_ASSERT(buf.size == 11);
        CX_TEST_ASSERT(buf.pos == 11);
        CX_TEST_ASSERT(buf.capacity >= 11);
        CX_TEST_ASSERT(0 == memcmp(buf.space, "preptesting", 11));
    }
    cxBufferDestroy(&buf);
}

CX_TEST(test_buffer_terminate) {
    CxBuffer buf;
    cxBufferInit(&buf, NULL, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT);
    memcpy(buf.space, "prepAAAAAA\0\0\0\0\0\0", 16);
    buf.capacity = 8;
    buf.size = buf.pos = 4;
    const char *data = "test";
    CX_TEST_DO {
        size_t written = cxBufferPutString(&buf, data);
        CX_TEST_ASSERT(0 != cxBufferTerminate(&buf));
        CX_TEST_ASSERT(written == 4);
        CX_TEST_ASSERT(buf.size == 8);
        CX_TEST_ASSERT(buf.pos == 8);
        CX_TEST_ASSERT(buf.capacity == 8);
        CX_TEST_ASSERT(0 == memcmp(buf.space, "preptestAA", 10));
        buf.flags |= CX_BUFFER_AUTO_EXTEND;
        CX_TEST_ASSERT(0 == cxBufferTerminate(&buf));
        CX_TEST_ASSERT(buf.size == 8);
        CX_TEST_ASSERT(buf.pos == 8);
        CX_TEST_ASSERT(buf.capacity > 8);
        CX_TEST_ASSERT(0 == memcmp(buf.space, "preptest\0", 9));
    }
    cxBufferDestroy(&buf);
}

CX_TEST(test_buffer_write_size_overflow) {
    CxBuffer buf;
    cxBufferInit(&buf, NULL, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT);
    memcpy(buf.space, "prep\0\0\0\0\0\0\0\0\0\0\0\0", 16);
    buf.capacity = 8;
    buf.size = buf.pos = 4;
    const char *data = "testing";
    CX_TEST_DO {
        size_t written = cxBufferWrite(data, 8, SIZE_MAX / 4, &buf);
        CX_TEST_ASSERT(written == 0);
        CX_TEST_ASSERT(buf.capacity == 8);
        CX_TEST_ASSERT(buf.pos == 4);
        CX_TEST_ASSERT(buf.size == 4);
        CX_TEST_ASSERT(0 == memcmp(buf.space, "prep\0", 5));
    }
    cxBufferDestroy(&buf);
}

CX_TEST(test_buffer_write_capacity_overflow) {
    CxBuffer buf;
    cxBufferInit(&buf, NULL, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT);
    memcpy(buf.space, "prep\0\0\0\0\0\0\0\0\0\0\0\0", 16);
    buf.capacity = 8;
    buf.size = buf.pos = 4;
    buf.flags |= CX_BUFFER_AUTO_EXTEND;
    const char *data = "testing";
    CX_TEST_DO {
        size_t written = cxBufferWrite(data, 1, SIZE_MAX - 2, &buf);
        CX_TEST_ASSERT(written == 0);
        CX_TEST_ASSERT(buf.capacity == 8);
        CX_TEST_ASSERT(buf.pos == 4);
        CX_TEST_ASSERT(buf.size == 4);
        CX_TEST_ASSERT(0 == memcmp(buf.space, "prep\0", 5));
    }
    cxBufferDestroy(&buf);
}

CX_TEST(test_buffer_write_only_overwrite) {
    CxBuffer buf;
    cxBufferInit(&buf, NULL, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT);
    memcpy(buf.space, "preptest\0\0\0\0\0\0\0\0", 16);
    buf.capacity = 8;
    buf.pos = 3;
    buf.size = 8;
    buf.flags |= CX_BUFFER_AUTO_EXTEND;
    CX_TEST_DO {
        size_t written = cxBufferWrite("XXX", 2, 2, &buf);
        CX_TEST_ASSERT(written == 2);
        CX_TEST_ASSERT(buf.capacity == 8);
        CX_TEST_ASSERT(buf.size == 8);
        CX_TEST_ASSERT(buf.pos == 7);
        CX_TEST_ASSERT(0 == memcmp(buf.space, "preXXX\0t", 8));
    }
    cxBufferDestroy(&buf);
}

CX_TEST(test_buffer_write_flush_at_capacity) {
    CxBuffer buf, target;
    cxBufferInit(&target, NULL, 16, cxDefaultAllocator, CX_BUFFER_AUTO_EXTEND);
    cxBufferInit(&buf, NULL, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT);
    memcpy(buf.space, "prep\0\0\0\0\0\0\0\0\0\0\0\0", 16);
    buf.capacity = 8;
    buf.size = buf.pos = 4;
    buf.flush_target = &target;
    buf.flush_func = (cx_write_func)cxBufferWrite;
    buf.flush_blkmax = 1;
    CX_TEST_DO {
        size_t written = cxBufferWrite("foo", 1, 3, &buf);
        CX_TEST_ASSERT(written == 3);
        CX_TEST_ASSERT(buf.pos == 7);
        CX_TEST_ASSERT(buf.size == 7);
        CX_TEST_ASSERT(target.pos == 0);
        CX_TEST_ASSERT(target.size == 0);
        written = cxBufferWrite("hello", 1, 5, &buf);
        CX_TEST_ASSERT(written == 5);
        CX_TEST_ASSERT(buf.pos == 0);
        CX_TEST_ASSERT(buf.size == 0);
        CX_TEST_ASSERT(buf.capacity == 8);
        CX_TEST_ASSERT(target.pos == 12);
        CX_TEST_ASSERT(target.size == 12);
        CX_TEST_ASSERT(0 == memcmp(target.space, "prepfoohello", 12));
    }
    cxBufferDestroy(&buf);
    cxBufferDestroy(&target);
}

CX_TEST(test_buffer_write_flush_at_threshold) {
    CxBuffer buf, target;
    cxBufferInit(&target, NULL, 16, cxDefaultAllocator, CX_BUFFER_AUTO_EXTEND);
    cxBufferInit(&buf, NULL, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT);
    memcpy(buf.space, "prep\0\0\0\0\0\0\0\0\0\0\0\0", 16);
    buf.capacity = 8;
    buf.size = buf.pos = 4;
    buf.flush_target = &target;
    buf.flush_func = (cx_write_func)cxBufferWrite;
    buf.flush_blkmax = 1;
    buf.flush_threshold = 12;
    buf.flags |= CX_BUFFER_AUTO_EXTEND;
    CX_TEST_DO {
        size_t written = cxBufferWrite("foobar", 1, 6, &buf);
        CX_TEST_ASSERT(written == 6);
        CX_TEST_ASSERT(buf.pos == 10);
        CX_TEST_ASSERT(buf.size == 10);
        CX_TEST_ASSERT(buf.capacity >= 10);
        CX_TEST_ASSERT(buf.capacity <= 12);
        CX_TEST_ASSERT(target.pos == 0);
        CX_TEST_ASSERT(target.size == 0);
        written = cxBufferWrite("hello", 1, 5, &buf);
        CX_TEST_ASSERT(written == 5);
        CX_TEST_ASSERT(buf.pos == 0);
        CX_TEST_ASSERT(buf.size == 0);
        CX_TEST_ASSERT(buf.capacity <= 12);
        CX_TEST_ASSERT(target.pos == 15);
        CX_TEST_ASSERT(target.size == 15);
        CX_TEST_ASSERT(0 == memcmp(target.space, "prepfoobarhello", 15));
    }
    cxBufferDestroy(&buf);
    cxBufferDestroy(&target);
}

CX_TEST(test_buffer_write_flush_rate_limited) {
    CxBuffer buf, target;
    cxBufferInit(&target, NULL, 16, cxDefaultAllocator, CX_BUFFER_AUTO_EXTEND);
    cxBufferInit(&buf, NULL, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT);
    memcpy(buf.space, "prep\0\0\0\0\0\0\0\0\0\0\0\0", 16);
    buf.capacity = 8;
    buf.size = buf.pos = 4;
    buf.flush_target = &target;
    buf.flush_blkmax = 1;
    // limit the rate of the flush function and the capacity of the target
    buf.flush_func = (cx_write_func) mock_write_limited_rate;
    target.capacity = 16;
    target.flags &= ~CX_BUFFER_AUTO_EXTEND;
    CX_TEST_DO {
        size_t written = cxBufferWrite("foo", 1, 3, &buf);
        CX_TEST_ASSERT(written == 3);
        CX_TEST_ASSERT(buf.pos == 7);
        CX_TEST_ASSERT(buf.size == 7);
        CX_TEST_ASSERT(target.pos == 0);
        CX_TEST_ASSERT(target.size == 0);
        written = cxBufferWrite("hello, world!", 1, 13, &buf);
        // " world!" fits into this buffer, the remaining stuff is flushed out
        CX_TEST_ASSERT(written == 13);
        CX_TEST_ASSERT(buf.pos == 7);
        CX_TEST_ASSERT(buf.size == 7);
        CX_TEST_ASSERT(buf.capacity == 8);
        CX_TEST_ASSERT(0 == memcmp(buf.space, " world!", 7));
        CX_TEST_ASSERT(target.pos == 13);
        CX_TEST_ASSERT(target.size == 13);
        CX_TEST_ASSERT(target.capacity == 16);
        CX_TEST_ASSERT(0 == memcmp(target.space, "prepfoohello,", 13));
    }
    cxBufferDestroy(&buf);
    cxBufferDestroy(&target);
}

CX_TEST(test_buffer_get) {
    CxBuffer buf;
    cxBufferInit(&buf, NULL, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT);
    memcpy(buf.space, "some data\0\0\0\0\0\0\0", 16);
    buf.capacity = 12;
    buf.size = 9;
    buf.pos = 2;
    CX_TEST_DO {
        CX_TEST_ASSERT(cxBufferGet(&buf) == 'm');
        CX_TEST_ASSERT(cxBufferGet(&buf) == 'e');
        CX_TEST_ASSERT(cxBufferGet(&buf) == ' ');
        CX_TEST_ASSERT(cxBufferGet(&buf) == 'd');
        CX_TEST_ASSERT(buf.pos == 6);
    }
    cxBufferDestroy(&buf);
}

CX_TEST(test_buffer_get_eof) {
    CxBuffer buf;
    cxBufferInit(&buf, NULL, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT);
    memcpy(buf.space, "some data\0\0\0\0\0\0\0", 16);
    buf.capacity = 12;
    buf.pos = buf.size = 9;
    CX_TEST_DO {
        CX_TEST_ASSERT(cxBufferGet(&buf) == EOF);
    }
    cxBufferDestroy(&buf);
}

CX_TEST(test_buffer_read) {
    CxBuffer buf;
    cxBufferInit(&buf, NULL, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT);
    memcpy(buf.space, "some data\0\0\0\0\0\0\0", 16);
    buf.capacity = 12;
    buf.size = 9;
    buf.pos = 2;
    CX_TEST_DO {
        char target[4];
        size_t read = cxBufferRead(&target, 1, 4, &buf);
        CX_TEST_ASSERT(read == 4);
        CX_TEST_ASSERT(0 == memcmp(&target, "me d", 4));
        CX_TEST_ASSERT(buf.pos == 6);
    }
    cxBufferDestroy(&buf);
}

CX_TEST(test_buffer_read_oob) {
    CxBuffer buf;
    cxBufferInit(&buf, NULL, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT);
    memcpy(buf.space, "some data\0\0\0\0\0\0\0", 16);
    buf.capacity = 12;
    buf.size = 9;
    buf.pos = 6;
    CX_TEST_DO {
        char target[4];
        size_t read = cxBufferRead(&target, 1, 4, &buf);
        CX_TEST_ASSERT(read == 3);
        CX_TEST_ASSERT(0 == memcmp(&target, "ata", 3));
        CX_TEST_ASSERT(buf.pos == 9);
    }
    cxBufferDestroy(&buf);
}

CX_TEST(test_buffer_read_oob_multibyte) {
    CxBuffer buf;
    cxBufferInit(&buf, NULL, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT);
    memcpy(buf.space, "some data\0\0\0\0\0\0\0", 16);
    buf.capacity = 12;
    buf.size = 9;
    buf.pos = 6;
    CX_TEST_DO {
        char target[4];
        target[2] = '\0';
        size_t read = cxBufferRead(&target, 2, 2, &buf);
        CX_TEST_ASSERT(read == 1);
        CX_TEST_ASSERT(0 == memcmp(&target, "at\0", 3));
        CX_TEST_ASSERT(buf.pos == 8);
    }
    cxBufferDestroy(&buf);
}

CX_TEST(test_buffer_read_eof) {
    CxBuffer buf;
    cxBufferInit(&buf, NULL, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT);
    memcpy(buf.space, "some data\0\0\0\0\0\0\0", 16);
    buf.capacity = 12;
    buf.size = buf.pos = 9;
    CX_TEST_DO {
        char target[4];
        size_t read = cxBufferRead(&target, 1, 1, &buf);
        CX_TEST_ASSERT(read == 0);
        CX_TEST_ASSERT(buf.pos == 9);
    }
    cxBufferDestroy(&buf);
}

CxTestSuite *cx_test_suite_buffer(void) {
    CxTestSuite *suite = cx_test_suite_new("buffer");

    cx_test_register(suite, test_buffer_init_wrap_space);
    cx_test_register(suite, test_buffer_init_wrap_space_auto_extend);
    cx_test_register(suite, test_buffer_init_wrap_space_auto_free);
    cx_test_register(suite, test_buffer_init_fresh_space);
    cx_test_register(suite, test_buffer_init_on_heap);
    cx_test_register(suite, test_buffer_minimum_capacity_sufficient);
    cx_test_register(suite, test_buffer_minimum_capacity_extend);
    cx_test_register(suite, test_buffer_clear);
    cx_test_register(suite, test_buffer_reset);
    cx_test_register(suite, test_buffer_seek_set_zero);
    cx_test_register(suite, test_buffer_seek_set_valid);
    cx_test_register(suite, test_buffer_seek_set_invalid);
    cx_test_register(suite, test_buffer_seek_cur_zero);
    cx_test_register(suite, test_buffer_seek_cur_valid_positive);
    cx_test_register(suite, test_buffer_seek_cur_valid_negative);
    cx_test_register(suite, test_buffer_seek_cur_invalid_positive);
    cx_test_register(suite, test_buffer_seek_cur_invalid_negative);
    cx_test_register(suite, test_buffer_seek_end_zero);
    cx_test_register(suite, test_buffer_seek_end_valid);
    cx_test_register(suite, test_buffer_seek_end_invalid);
    cx_test_register(suite, test_buffer_seek_whence_invalid);
    cx_test_register(suite, test_buffer_eof_reached);
    cx_test_register(suite, test_buffer_eof_not_reached);
    cx_test_register(suite, test_buffer_shift_left_zero);
    cx_test_register(suite, test_buffer_shift_left_zero_offset_interface);
    cx_test_register(suite, test_buffer_shift_left_standard);
    cx_test_register(suite, test_buffer_shift_left_overshift);
    cx_test_register(suite, test_buffer_shift_left_overshift_pos_only);
    cx_test_register(suite, test_buffer_shift_left_offset_interface);
    cx_test_register(suite, test_buffer_shift_right_zero);
    cx_test_register(suite, test_buffer_shift_right_zero_offset_interface);
    cx_test_register(suite, test_buffer_shift_right_standard);
    cx_test_register(suite, test_buffer_shift_right_overshift_discard);
    cx_test_register(suite, test_buffer_shift_right_overshift_extend);
    cx_test_register(suite, test_buffer_shift_right_offset_interface);
    cx_test_register(suite, test_buffer_write_size_one_fit);
    cx_test_register(suite, test_buffer_write_size_one_discard);
    cx_test_register(suite, test_buffer_write_size_one_extend);
    cx_test_register(suite, test_buffer_write_multibyte_fit);
    cx_test_register(suite, test_buffer_write_multibyte_discard);
    cx_test_register(suite, test_buffer_write_multibyte_extend);
    cx_test_register(suite, test_buffer_put_fit);
    cx_test_register(suite, test_buffer_put_discard);
    cx_test_register(suite, test_buffer_put_extend);
    cx_test_register(suite, test_buffer_put_string_fit);
    cx_test_register(suite, test_buffer_put_string_discard);
    cx_test_register(suite, test_buffer_put_string_extend);
    cx_test_register(suite, test_buffer_terminate);
    cx_test_register(suite, test_buffer_write_size_overflow);
    cx_test_register(suite, test_buffer_write_capacity_overflow);
    cx_test_register(suite, test_buffer_write_only_overwrite);
    cx_test_register(suite, test_buffer_write_flush_at_capacity);
    cx_test_register(suite, test_buffer_write_flush_at_threshold);
    cx_test_register(suite, test_buffer_write_flush_rate_limited);
    cx_test_register(suite, test_buffer_get);
    cx_test_register(suite, test_buffer_get_eof);
    cx_test_register(suite, test_buffer_read);
    cx_test_register(suite, test_buffer_read_oob);
    cx_test_register(suite, test_buffer_read_oob_multibyte);
    cx_test_register(suite, test_buffer_read_eof);

    return suite;
}

mercurial