Wed, 03 Jan 2024 22:17:40 +0100
migrate more buffer tests - relates to #342
only read and write tests are remaining now
/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2021 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/buffer.h" #include <gtest/gtest.h> #include "util_allocator.h" class BufferWrite : public ::testing::Test { protected: CxBuffer buf{}, target{}; void SetUp() override { cxBufferInit(&target, NULL, 16, cxDefaultAllocator, CX_BUFFER_AUTO_EXTEND); cxBufferInit(&buf, NULL, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT); buf.capacity = 8; // artificially reduce capacity to check OOB writes memset(buf.space, 0, 16); memcpy(buf.space, "prep", 4); buf.size = buf.pos = 4; } void TearDown() override { cxBufferDestroy(&buf); cxBufferDestroy(&target); } void enableFlushing() { buf.flush_target = ⌖ buf.flush_func = reinterpret_cast<cx_write_func>(cxBufferWrite); buf.flush_blkmax = 1; } }; static size_t mock_write_limited_rate( void const *ptr, size_t size, __attribute__((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); } } TEST_F(BufferWrite, SizeOneFit) { const char *data = "test"; CX_TEST_ASSERT(buf.capacity == 8); CX_TEST_ASSERT(buf.pos == 4); CX_TEST_ASSERT(buf.size == 4); 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); EXPECT_EQ(memcmp(buf.space, "preptest", 8), 0); } TEST_F(BufferWrite, SizeOneDiscard) { const char *data = "testing"; CX_TEST_ASSERT(buf.capacity == 8); CX_TEST_ASSERT(buf.pos == 4); CX_TEST_ASSERT(buf.size == 4); 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); EXPECT_EQ(memcmp(buf.space, "preptest\0", 9), 0); } TEST_F(BufferWrite, SizeOneExtend) { buf.flags |= CX_BUFFER_AUTO_EXTEND; const char *data = "testing"; CX_TEST_ASSERT(buf.capacity == 8); CX_TEST_ASSERT(buf.pos == 4); CX_TEST_ASSERT(buf.size == 4); 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); EXPECT_GE(buf.capacity, 11); EXPECT_EQ(memcmp(buf.space, "preptesting", 11), 0); } TEST_F(BufferWrite, MultibyteFit) { const char *data = "test"; CX_TEST_ASSERT(buf.capacity == 8); CX_TEST_ASSERT(buf.pos == 4); CX_TEST_ASSERT(buf.size == 4); 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); EXPECT_EQ(memcmp(buf.space, "preptest", 8), 0); } TEST_F(BufferWrite, MultibyteDiscard) { const char *data = "testing"; CX_TEST_ASSERT(buf.capacity == 8); CX_TEST_ASSERT(buf.size == 4); buf.pos = 3; 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); EXPECT_EQ(memcmp(buf.space, "pretest\0", 8), 0); } TEST_F(BufferWrite, MultibyteExtend) { buf.flags |= CX_BUFFER_AUTO_EXTEND; const char *data = "tester"; CX_TEST_ASSERT(buf.capacity == 8); CX_TEST_ASSERT(buf.size == 4); buf.pos = 3; 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); EXPECT_GE(buf.capacity, 9); EXPECT_EQ(memcmp(buf.space, "pretester", 9), 0); } TEST_F(BufferWrite, PutcWrapperFit) { CX_TEST_ASSERT(buf.capacity == 8); CX_TEST_ASSERT(buf.pos == 4); CX_TEST_ASSERT(buf.size == 4); 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); EXPECT_EQ(memcmp(buf.space, "prepa\0", 6), 0); } TEST_F(BufferWrite, PutcWrapperDiscard) { CX_TEST_ASSERT(buf.capacity == 8); CX_TEST_ASSERT(buf.size == 4); buf.pos = 8; 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); EXPECT_EQ(memcmp(buf.space, "prep\0\0\0\0\0", 9), 0); } TEST_F(BufferWrite, PutcWrapperExtend) { buf.flags |= CX_BUFFER_AUTO_EXTEND; CX_TEST_ASSERT(buf.capacity == 8); CX_TEST_ASSERT(buf.size == 4); buf.pos = 8; int c = cxBufferPut(&buf, 0x200 | 'a'); CX_TEST_ASSERT(c == 'a'); CX_TEST_ASSERT(buf.size == 9); CX_TEST_ASSERT(buf.pos == 9); EXPECT_GE(buf.capacity, 9); EXPECT_EQ(memcmp(buf.space, "prep\0\0\0\0a", 9), 0); } TEST_F(BufferWrite, PutStringWrapperFit) { const char *data = "test"; CX_TEST_ASSERT(buf.capacity == 8); CX_TEST_ASSERT(buf.pos == 4); CX_TEST_ASSERT(buf.size == 4); 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); EXPECT_EQ(memcmp(buf.space, "preptest", 8), 0); } TEST_F(BufferWrite, PutStringWrapperDiscard) { const char *data = "testing"; CX_TEST_ASSERT(buf.capacity == 8); CX_TEST_ASSERT(buf.pos == 4); CX_TEST_ASSERT(buf.size == 4); 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); EXPECT_EQ(memcmp(buf.space, "preptest\0", 9), 0); } TEST_F(BufferWrite, PutStringWrapperExtend) { buf.flags |= CX_BUFFER_AUTO_EXTEND; const char *data = "testing"; CX_TEST_ASSERT(buf.capacity == 8); CX_TEST_ASSERT(buf.pos == 4); CX_TEST_ASSERT(buf.size == 4); size_t written = cxBufferPutString(&buf, data); CX_TEST_ASSERT(written == 7); CX_TEST_ASSERT(buf.size == 11); CX_TEST_ASSERT(buf.pos == 11); EXPECT_GE(buf.capacity, 11); EXPECT_EQ(memcmp(buf.space, "preptesting", 11), 0); } TEST_F(BufferWrite, MultOverflow) { const char *data = "testing"; CX_TEST_ASSERT(buf.capacity == 8); CX_TEST_ASSERT(buf.pos == 4); CX_TEST_ASSERT(buf.size == 4); 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); EXPECT_EQ(memcmp(buf.space, "prep\0", 5), 0); } TEST_F(BufferWrite, MaxCapaOverflow) { buf.flags |= CX_BUFFER_AUTO_EXTEND; const char *data = "testing"; CX_TEST_ASSERT(buf.capacity == 8); CX_TEST_ASSERT(buf.pos == 4); CX_TEST_ASSERT(buf.size == 4); 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); EXPECT_EQ(memcmp(buf.space, "prep\0", 5), 0); } TEST_F(BufferWrite, OnlyOverwrite) { buf.flags |= CX_BUFFER_AUTO_EXTEND; CX_TEST_ASSERT(buf.capacity == 8); memcpy(buf.space, "preptest", 8); buf.pos = 3; buf.size = 8; 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); EXPECT_EQ(memcmp(buf.space, "preXXX\0t", 8), 0); } TEST_F(BufferWrite, FlushAtCapacity) { enableFlushing(); CX_TEST_ASSERT(buf.capacity == 8); CX_TEST_ASSERT(buf.pos == 4); 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); EXPECT_EQ(memcmp(target.space, "prepfoohello", 12), 0); } TEST_F(BufferWrite, FlushAtThreshold) { enableFlushing(); buf.flush_threshold = 12; buf.flags |= CX_BUFFER_AUTO_EXTEND; CX_TEST_ASSERT(buf.capacity == 8); CX_TEST_ASSERT(buf.pos == 4); 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); ASSERT_GE(buf.capacity, 10); ASSERT_LE(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); EXPECT_LE(buf.capacity, 12); CX_TEST_ASSERT(target.pos == 15); CX_TEST_ASSERT(target.size == 15); EXPECT_EQ(memcmp(target.space, "prepfoobarhello", 15), 0); } TEST_F(BufferWrite, FlushRateLimited) { enableFlushing(); // limit the rate of the flush function and the capacity of the target target.capacity = 16; target.flags &= ~CX_BUFFER_AUTO_EXTEND; buf.flush_func = (cx_write_func) mock_write_limited_rate; CX_TEST_ASSERT(buf.capacity == 8); CX_TEST_ASSERT(buf.pos == 4); 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); EXPECT_EQ(memcmp(buf.space, " world!", 7), 0); CX_TEST_ASSERT(target.pos == 13); CX_TEST_ASSERT(target.size == 13); CX_TEST_ASSERT(target.capacity == 16); EXPECT_EQ(memcmp(target.space, "prepfoohello,", 13), 0); } class BufferRead : public ::testing::Test { protected: CxBuffer buf{}; void SetUp() override { cxBufferInit(&buf, NULL, 16, cxDefaultAllocator, CX_BUFFER_DEFAULT); buf.capacity = 8; // artificially reduce capacity to check OOB writes memset(buf.space, 0, 16); memcpy(buf.space, "some data", 9); buf.size = 9; } void TearDown() override { cxBufferDestroy(&buf); } }; TEST_F(BufferRead, GetByte) { buf.pos = 2; EXPECT_EQ(cxBufferGet(&buf), 'm'); EXPECT_EQ(cxBufferGet(&buf), 'e'); EXPECT_EQ(cxBufferGet(&buf), ' '); EXPECT_EQ(cxBufferGet(&buf), 'd'); CX_TEST_ASSERT(buf.pos == 6); } TEST_F(BufferRead, GetEof) { buf.pos = buf.size; EXPECT_EQ(cxBufferGet(&buf), EOF); } TEST_F(BufferRead, ReadWithinBounds) { buf.pos = 2; char target[4]; auto read = cxBufferRead(&target, 1, 4, &buf); CX_TEST_ASSERT(read == 4); EXPECT_EQ(memcmp(&target, "me d", 4), 0); CX_TEST_ASSERT(buf.pos == 6); } TEST_F(BufferRead, ReadOutOfBounds) { buf.pos = 6; char target[4]; auto read = cxBufferRead(&target, 1, 4, &buf); CX_TEST_ASSERT(read == 3); EXPECT_EQ(memcmp(&target, "ata", 3), 0); CX_TEST_ASSERT(buf.pos == 9); } TEST_F(BufferRead, ReadOutOfBoundsMultibyte) { buf.pos = 6; char target[4]; target[2] = '\0'; auto read = cxBufferRead(&target, 2, 2, &buf); CX_TEST_ASSERT(read == 1); EXPECT_EQ(memcmp(&target, "at\0", 3), 0); CX_TEST_ASSERT(buf.pos == 8); } TEST_F(BufferRead, ReadEof) { buf.pos = 9; char target[4]; auto read = cxBufferRead(&target, 1, 1, &buf); CX_TEST_ASSERT(read == 0); CX_TEST_ASSERT(buf.pos == 9); }