Sun, 19 Jan 2025 17:17:01 +0100
add test case for flushing when target is full and fixes a bug related to that
fixes #564
src/buffer.c | file | annotate | diff | comparison | revisions | |
src/cx/buffer.h | file | annotate | diff | comparison | revisions | |
tests/test_buffer.c | file | annotate | diff | comparison | revisions |
--- a/src/buffer.c Sat Jan 18 14:10:51 2025 +0100 +++ b/src/buffer.c Sun Jan 19 17:17:01 2025 +0100 @@ -319,8 +319,8 @@ // to flush, it means that we are supposed to relay the data items_flush = cx_buffer_flush_helper(buffer, ptr, size, nitems); if (items_flush == 0) { - // we needed to flush, but could not flush anything - // give up and avoid endless trying + // we needed to relay data, but could not flush anything + // i.e. we have to give up to avoid endless trying return 0; } size_t ritems = nitems - items_flush; @@ -334,7 +334,21 @@ } else { items_flush = cx_buffer_flush_impl(buffer, size); if (items_flush == 0) { - return 0; + // flush target is full, let's try to truncate + size_t remaining_space; + if (buffer->flags & CX_BUFFER_AUTO_EXTEND) { + remaining_space = buffer->flush->threshold > buffer->pos + ? buffer->flush->threshold - buffer->pos + : 0; + } else { + remaining_space = buffer->capacity > buffer->pos + ? buffer->capacity - buffer->pos + : 0; + } + nitems = remaining_space / size; + if (nitems == 0) { + return 0; + } } return cxBufferWrite(ptr, size, nitems, buffer); }
--- a/src/cx/buffer.h Sat Jan 18 14:10:51 2025 +0100 +++ b/src/cx/buffer.h Sun Jan 19 17:17:01 2025 +0100 @@ -502,8 +502,7 @@ * When @p size is larger than one and the contents of the buffer are not aligned * with @p size, flushing stops after all complete items have been flushed, leaving * the mis-aligned part in the buffer. - * Afterward, this function refuses to write any data to the buffer, until the - * mis-alignment has been resolved (e.g. by manually flushing with cxBufferFlush()). + * Afterward, this function only writes as many items as possible to the buffer. * * @note The signature is compatible with the fwrite() family of functions. *
--- a/tests/test_buffer.c Sat Jan 18 14:10:51 2025 +0100 +++ b/tests/test_buffer.c Sun Jan 19 17:17:01 2025 +0100 @@ -1275,7 +1275,6 @@ CxBuffer buf, target; cxBufferInit(&target, NULL, 8, cxDefaultAllocator, CX_BUFFER_AUTO_EXTEND); cxBufferInit(&buf, NULL, 8, cxDefaultAllocator, CX_BUFFER_DEFAULT); - memset(buf.space, 0, 8); cxBufferPutString(&buf, "prep"); CX_TEST_DO { CxBufferFlushConfig flush; @@ -1295,19 +1294,75 @@ CX_TEST_ASSERT(0 == memcmp(buf.space, "pfoobar", 7)); CX_TEST_ASSERT(0 == memcmp(target.space, "pre", 3)); // second case: string does not fit, relaying not possible due to misalignment + // string will be truncated (two items fit into buffer, the third does not) written = cxBufferWrite("bazfoobar", 3, 3, &buf); - CX_TEST_ASSERT(written == 0); - CX_TEST_ASSERT(buf.pos == 1); - CX_TEST_ASSERT(buf.size == 1); + CX_TEST_ASSERT(written == 2); + CX_TEST_ASSERT(buf.pos == 7); + CX_TEST_ASSERT(buf.size == 7); CX_TEST_ASSERT(target.pos == 9); CX_TEST_ASSERT(target.size == 9); - CX_TEST_ASSERT(0 == memcmp(buf.space, "r", 1)); + CX_TEST_ASSERT(0 == memcmp(buf.space, "rbazfoo", 7)); CX_TEST_ASSERT(0 == memcmp(target.space, "prepfooba", 9)); } cxBufferDestroy(&buf); cxBufferDestroy(&target); } +CX_TEST(test_buffer_write_flush_target_full) { + CxBuffer buf, target; + // target does NOT auto-extend and can get completely full + cxBufferInit(&target, NULL, 8, cxDefaultAllocator, CX_BUFFER_DEFAULT); + cxBufferInit(&buf, NULL, 8, cxDefaultAllocator, CX_BUFFER_DEFAULT); + cxBufferPutString(&buf, "prep"); + CX_TEST_DO { + CxBufferFlushConfig flush; + flush.threshold = 0; + flush.blksize = 32; + flush.blkmax = 1; + flush.target = ⌖ + flush.wfunc = cxBufferWriteFunc; + CX_TEST_ASSERT(0 == cxBufferEnableFlushing(&buf, flush)); + // step one - flush 4 existing bytes, write 6 new bytes + size_t written = cxBufferWrite("foobar", 1, 6, &buf); + CX_TEST_ASSERT(written == 6); + CX_TEST_ASSERT(buf.pos == 6); + CX_TEST_ASSERT(buf.size == 6); + CX_TEST_ASSERT(target.pos == 4); + CX_TEST_ASSERT(target.size == 4); + CX_TEST_ASSERT(0 == memcmp(buf.space, "foobar", 6)); + CX_TEST_ASSERT(0 == memcmp(target.space, "prep", 4)); + // step two - can only flush 4 more bytes, but rest fits into buffer + written = cxBufferWrite("xyz", 1, 3, &buf); + CX_TEST_ASSERT(written == 3); + CX_TEST_ASSERT(buf.pos == 5); + CX_TEST_ASSERT(buf.size == 5); + CX_TEST_ASSERT(target.pos == 8); + CX_TEST_ASSERT(target.size == 8); + CX_TEST_ASSERT(0 == memcmp(buf.space, "arxyz", 5)); + CX_TEST_ASSERT(0 == memcmp(target.space, "prepfoob", 8)); + // step three - cannot flush more, but can write 3 more bytes + written = cxBufferWrite("123456", 1, 6, &buf); + CX_TEST_ASSERT(written == 3); + CX_TEST_ASSERT(buf.pos == 8); + CX_TEST_ASSERT(buf.size == 8); + CX_TEST_ASSERT(target.pos == 8); + CX_TEST_ASSERT(target.size == 8); + CX_TEST_ASSERT(0 == memcmp(buf.space, "arxyz123", 8)); + CX_TEST_ASSERT(0 == memcmp(target.space, "prepfoob", 8)); + // final test, cannot write anything more + written = cxBufferWrite("baz", 1, 3, &buf); + CX_TEST_ASSERT(written == 0); + CX_TEST_ASSERT(buf.pos == 8); + CX_TEST_ASSERT(buf.size == 8); + CX_TEST_ASSERT(target.pos == 8); + CX_TEST_ASSERT(target.size == 8); + CX_TEST_ASSERT(0 == memcmp(buf.space, "arxyz123", 8)); + CX_TEST_ASSERT(0 == memcmp(target.space, "prepfoob", 8)); + } + cxBufferDestroy(&buf); + cxBufferDestroy(&target); +} + CX_TEST(test_buffer_flush) { CxBuffer buf, target; cxBufferInit(&target, NULL, 8, cxDefaultAllocator, CX_BUFFER_AUTO_EXTEND); @@ -1508,6 +1563,7 @@ cx_test_register(suite, test_buffer_write_flush_rate_limited_and_buffer_too_small); cx_test_register(suite, test_buffer_write_flush_multibyte); cx_test_register(suite, test_buffer_write_flush_misaligned); + cx_test_register(suite, test_buffer_write_flush_target_full); cx_test_register(suite, test_buffer_flush); cx_test_register(suite, test_buffer_get); cx_test_register(suite, test_buffer_get_eof);