add test case for flushing when target is full and fixes a bug related to that default tip

Sun, 19 Jan 2025 17:17:01 +0100

author
Mike Becker <universe@uap-core.de>
date
Sun, 19 Jan 2025 17:17:01 +0100
changeset 1135
f79415d974d3
parent 1134
60edcd57d54c

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 = &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);

mercurial