From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from [87.239.111.99] (localhost [127.0.0.1]) by dev.tarantool.org (Postfix) with ESMTP id AAACB6EFD6; Mon, 2 Nov 2026 16:13:04 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org AAACB6EFD6 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=tarantool.org; s=dev; t=1793625184; bh=9r99I1uNh/QJPvLJbSyWUTLSnDATvZM3Ra8NwP5R9B4=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=TWBiCk7R8aP+OxCg3c1MXGiO9V0mRbZ+1F+Owk9SYDGLUq18fPgPmqA/S2qUM1PpW pRUKgLmVYjopMHn2PpPcunUnBZ073l68CHqPOZQlRewch97tJNAy4oK8SK3OdLBB75 CFI/nfn2PUyN0IuNu+eNkZyAxtqKvnhueKX8Fi90= Received: from mail-lj1-f171.google.com (mail-lj1-f171.google.com [209.85.208.171]) (using TLSv1.3 with cipher TLS_AES_128_GCM_SHA256 (128/128 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by dev.tarantool.org (Postfix) with ESMTPS id 34CA06EFD6 for ; Mon, 2 Nov 2026 16:12:41 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org 34CA06EFD6 Received: by mail-lj1-f171.google.com with SMTP id 38308e7fff4ca-3ac97e45a79so28554131fa.3 for ; Mon, 02 Nov 2026 05:12:41 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793625160; x=1794229960; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=f8n5k8f7QkUK6ilv/lLCutCKzzJ0gs18K1w6Uj/x/uw=; b=d5XOLUhACDtTsLxISJkQUmnDqZIH8jGHXe9MIGGpEaOZOEyaYmIBl85+e6ZEjDgyZ8 nt46xURPpTtmPZg67Y7FqS80mtPEXFvLjq7PuJCcWKl+RzDbnIuBdtX+xEykqkhMnhqS 0+Jnb0p2cWk4OFRbQMNTNAMoSPe4LJWEumiBYXYJFbjC+L5qnbAvlnDnTfkI+rxrpxtD k6zJZiyYPb6OEebUz403zzj7byh3CWDWl5BE0Wna9AEiW6Ae3NtWnr5xQ3xYRmN1qnBu zzlLdVi9n2vGsVqWy5JF2PMa3dH66Da24pj6iuzIFRx0oyEMftM8bt+P86REkAOrGtM/ B43A== X-Gm-Message-State: APuU4Z+CRlIRpBzhpdqPUnMqdZ2ddIxh46HamrrWOBYNRIjAa+42yoFj U3nny3ZCSn4oZ9CJxdCy2MG9eUAY2JLetqGtJIQAjAPHPFtK1e7CjQsHDeP/wg== X-Gm-Gg: AYBFou0OcEYRT2NiVS7dxnuZS4XCC/DhB1QhPLNbs5qlc58Ja4+hkNg/4r42awIZUyE TkjDa72CD5V8B4ncLFaXxQttWw+abi9FCqdP2lsDOMXN5QmVcZ2pqiWIFJN8V491Oa0vWoJZDQ8 3xVyAzB82CQ7dNsPTe7ilDIUVQEJJE1AE90Omwk9KGRzZybKynoLcX3okYRmThIE0F+fT5iQeB1 SW2jzMUmVRrWIyNWwV8hTw3JEO18EZBC1X2+q0WttKwc7TRRloy+MUfmyDK3hbOdDko5qbFeG0c GFSt0CesUppXn1Fi88YdVcCWOzuDdD56PZqs6gaDbGo5AFqsZLU1nWV+gb3SIjZeMlE659zBGVz wAJw9VaUWwTwu2ME9LwwGHmQ/ARzVKWv5jk9PLDNhP8kSRSLzEikt1UF7vd/gj+cCeCJP1bhXB4 QFYOAJh1fng0vhublj/MNWNe52nX4VyQn7bSGjGTkK0WzLy6xtKLTo2rKwcNjfktnRFxraWCDSK ycMXZZLFGrSEcbY+OwIWRqwYE6iy7fc2UBlwzaOAWeWgE81oCShn2J4J5hpkawy8WqlxNxrO2J9 kcPGfy48SkjKah7NcbjEYbN3ww3wFD9c174CJTyFtQ2fj+IR6UIlq8o= X-Received: by 2002:a2e:8a88:0:b0:3a6:5993:c976 with SMTP id 38308e7fff4ca-3acb7aa4ac2mr24144161fa.6.1793625160030; Mon, 02 Nov 2026 05:12:40 -0800 (PST) Received: from localhost ([79.164.223.111]) by smtp.gmail.com with ESMTPSA id 38308e7fff4ca-3acbbf1ce7bsm19756811fa.31.2026.11.02.05.12.39 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 02 Nov 2026 05:12:39 -0800 (PST) To: tarantool-patches@dev.tarantool.org, Sergey Kaplun , e.temirgaleev@tarantool.org Date: Mon, 2 Nov 2026 16:12:36 +0300 Message-ID: <4dd8dd2aecca35784e687f7920e6880ad0211f3c.1793624427.git.estetus@gmail.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Subject: [Tarantool-patches] [PATCH luajit 1/3] test: add unit tests for the write buffer X-BeenThere: tarantool-patches@dev.tarantool.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: Tarantool development patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , From: Sergey Bronnikov via Tarantool-patches Reply-To: Sergey Bronnikov Errors-To: tarantool-patches-bounces@dev.tarantool.org Sender: "Tarantool-patches" From: Sergey Bronnikov The write buffer accumulates the profilers' output and passes it to the writer on flush. It has no unit tests, so cover writing of the data of all kinds, flushing of a full buffer, the explicit flush and the error reporting. Part of tarantool/tarantool#11620 --- test/tarantool-c-tests/unit-wbuf.test.c | 388 ++++++++++++++++++++++++ 1 file changed, 388 insertions(+) create mode 100644 test/tarantool-c-tests/unit-wbuf.test.c diff --git a/test/tarantool-c-tests/unit-wbuf.test.c b/test/tarantool-c-tests/unit-wbuf.test.c new file mode 100644 index 00000000..4bee6243 --- /dev/null +++ b/test/tarantool-c-tests/unit-wbuf.test.c @@ -0,0 +1,388 @@ +/* +** Testing of the write buffer. +** +** The write buffer accumulates the profilers' output and passes +** it to the writer on flush. The tests below cover writing of the +** data of all kinds, flushing on overflow, the explicit flush and +** the error reporting. +*/ + +#include + +#include +#include +#include + +#include "lj_wbuf.h" +#include "test.h" + +/* Size of the write buffer used in the tests. */ +#define BUFFER_SIZE 10 + +/* Size of the payload that does not fit into the write buffer. */ +#define PAYLOAD_SIZE (2 * BUFFER_SIZE + 5) + +/* What the writer does on flush. */ +enum writer_mode { + /* Report all the data as written. */ + WMODE_OK, + /* Report a half of the data as written and keep the buffer. */ + WMODE_PARTIAL, + /* Imitate an unrecoverable write error, e.g. ENOSPC. */ + WMODE_FAIL, +}; + +/* Writer context for simulating different write outcomes. */ +struct writer_ctx { + /* What the writer does on flush. */ + enum writer_mode mode; + /* Number of performed flushes. */ + size_t flushes; + /* Number of bytes reported as written. */ + size_t written; + /* Everything reported as written, in order of writing. */ + uint8_t out[2 * PAYLOAD_SIZE]; +}; + +static size_t test_writer(const void **buf_addr, size_t len, void *opt) +{ + struct writer_ctx *ctx = opt; + size_t n = len; + + ctx->flushes++; + if (ctx->mode == WMODE_FAIL) { + errno = ENOSPC; + *buf_addr = NULL; + return 0; + } + if (ctx->mode == WMODE_PARTIAL) { + errno = EIO; + n = len / 2; + } + memcpy(ctx->out + ctx->written, *buf_addr, n); + ctx->written += n; + return n; +} + +static void writer_ctx_init(struct writer_ctx *ctx, enum writer_mode mode) +{ + memset(ctx, 0, sizeof(*ctx)); + ctx->mode = mode; +} + +static size_t buf_len(const struct lj_wbuf *buf) +{ + return (size_t)(buf->pos - buf->buf); +} + +static void assert_stream_stopped_with_error(const struct lj_wbuf *buf) +{ + assert_true(lj_wbuf_test_flag(buf, STREAM_STOP)); + assert_true(lj_wbuf_test_flag(buf, STREAM_ERRIO)); + assert_int_equal(lj_wbuf_errno(buf), ENOSPC); + assert_ptr_equal(buf->buf, NULL); + assert_ptr_equal(buf->pos, NULL); +} + +static int test_addbyte(void *state) +{ + uint8_t mem[BUFFER_SIZE]; + struct lj_wbuf buf; + struct writer_ctx ctx; + + UNUSED(state); + + writer_ctx_init(&ctx, WMODE_OK); + lj_wbuf_init(&buf, test_writer, &ctx, mem, sizeof(mem)); + + lj_wbuf_addbyte(&buf, 0x7f); + assert_sizet_equal(buf_len(&buf), 1); + assert_int_equal(mem[0], 0x7f); + assert_sizet_equal(ctx.flushes, 0); + assert_int_equal(lj_wbuf_errno(&buf), 0); + + return TEST_EXIT_SUCCESS; +} + +static int test_addu64(void *state) +{ + uint8_t mem[BUFFER_SIZE]; + struct lj_wbuf buf; + struct writer_ctx ctx; + + UNUSED(state); + + writer_ctx_init(&ctx, WMODE_OK); + lj_wbuf_init(&buf, test_writer, &ctx, mem, sizeof(mem)); + + /* 128 is encoded as two bytes. */ + lj_wbuf_addu64(&buf, 128); + assert_sizet_equal(buf_len(&buf), 2); + assert_int_equal(mem[0], 0x80); + assert_int_equal(mem[1], 0x01); + assert_sizet_equal(ctx.flushes, 0); + + return TEST_EXIT_SUCCESS; +} + +static int test_addn(void *state) +{ + uint8_t mem[BUFFER_SIZE]; + struct lj_wbuf buf; + struct writer_ctx ctx; + + UNUSED(state); + + writer_ctx_init(&ctx, WMODE_OK); + lj_wbuf_init(&buf, test_writer, &ctx, mem, sizeof(mem)); + + lj_wbuf_addn(&buf, "xyz", 3); + assert_sizet_equal(buf_len(&buf), 3); + assert_true(memcmp(mem, "xyz", 3) == 0); + assert_sizet_equal(ctx.flushes, 0); + + return TEST_EXIT_SUCCESS; +} + +static int test_addstring(void *state) +{ + uint8_t mem[BUFFER_SIZE]; + struct lj_wbuf buf; + struct writer_ctx ctx; + + UNUSED(state); + + writer_ctx_init(&ctx, WMODE_OK); + lj_wbuf_init(&buf, test_writer, &ctx, mem, sizeof(mem)); + + /* A string is a ULEB128 length followed by the payload. */ + lj_wbuf_addstring(&buf, "xyz"); + assert_sizet_equal(buf_len(&buf), 4); + assert_int_equal(mem[0], 3); + assert_true(memcmp(mem + 1, "xyz", 3) == 0); + assert_sizet_equal(ctx.flushes, 0); + + return TEST_EXIT_SUCCESS; +} + +static int test_addn_pumped_through_buffer(void *state) +{ + uint8_t mem[BUFFER_SIZE]; + uint8_t payload[PAYLOAD_SIZE]; + struct lj_wbuf buf; + struct writer_ctx ctx; + + UNUSED(state); + + memset(payload, 'a', sizeof(payload)); + writer_ctx_init(&ctx, WMODE_OK); + lj_wbuf_init(&buf, test_writer, &ctx, mem, sizeof(mem)); + + /* + * The payload does not fit into the buffer, so it is + * pumped through it. Two full buffers are flushed, the + * rest stays in the buffer. + */ + lj_wbuf_addn(&buf, payload, sizeof(payload)); + assert_sizet_equal(ctx.flushes, 2); + assert_sizet_equal(ctx.written, 2 * BUFFER_SIZE); + assert_true(memcmp(ctx.out, payload, 2 * BUFFER_SIZE) == 0); + assert_sizet_equal(buf_len(&buf), PAYLOAD_SIZE - 2 * BUFFER_SIZE); + assert_true(memcmp(mem, payload + 2 * BUFFER_SIZE, + PAYLOAD_SIZE - 2 * BUFFER_SIZE) == 0); + + return TEST_EXIT_SUCCESS; +} + +static int test_flush_on_overflow(void *state) +{ + uint8_t mem[BUFFER_SIZE]; + uint8_t payload[BUFFER_SIZE]; + struct lj_wbuf buf; + struct writer_ctx ctx; + + UNUSED(state); + + memset(payload, 'a', sizeof(payload)); + writer_ctx_init(&ctx, WMODE_OK); + lj_wbuf_init(&buf, test_writer, &ctx, mem, sizeof(mem)); + + /* A byte written to a full buffer is preceded by a flush. */ + lj_wbuf_addn(&buf, payload, sizeof(payload)); + assert_sizet_equal(buf_len(&buf), sizeof(mem)); + lj_wbuf_addbyte(&buf, 0xaa); + assert_sizet_equal(ctx.flushes, 1); + assert_sizet_equal(ctx.written, sizeof(mem)); + assert_sizet_equal(buf_len(&buf), 1); + assert_int_equal(mem[0], 0xaa); + + /* The same for an ULEB128 value written to a full buffer. */ + lj_wbuf_addn(&buf, payload, sizeof(payload) - 1); + assert_sizet_equal(buf_len(&buf), sizeof(mem)); + lj_wbuf_addu64(&buf, 0); + assert_sizet_equal(ctx.flushes, 2); + assert_sizet_equal(ctx.written, 2 * sizeof(mem)); + assert_sizet_equal(buf_len(&buf), 1); + + /* And for a data block written to a full buffer. */ + lj_wbuf_addn(&buf, payload, sizeof(payload) - 1); + assert_sizet_equal(buf_len(&buf), sizeof(mem)); + lj_wbuf_addn(&buf, payload, 1); + assert_sizet_equal(ctx.flushes, 3); + assert_sizet_equal(ctx.written, 3 * sizeof(mem)); + assert_sizet_equal(buf_len(&buf), 1); + + return TEST_EXIT_SUCCESS; +} + +static int test_flush(void *state) +{ + uint8_t mem[BUFFER_SIZE]; + struct lj_wbuf buf; + struct writer_ctx ctx; + + UNUSED(state); + + writer_ctx_init(&ctx, WMODE_OK); + lj_wbuf_init(&buf, test_writer, &ctx, mem, sizeof(mem)); + + lj_wbuf_addn(&buf, "xyz", 3); + lj_wbuf_flush(&buf); + assert_sizet_equal(ctx.flushes, 1); + assert_sizet_equal(ctx.written, 3); + assert_true(memcmp(ctx.out, "xyz", 3) == 0); + /* The buffer is empty after a successful flush. */ + assert_sizet_equal(buf_len(&buf), 0); + assert_false(lj_wbuf_test_flag(&buf, STREAM_ERRIO | STREAM_STOP)); + + /* The buffer is usable after the flush. */ + lj_wbuf_addbyte(&buf, 0x7f); + assert_sizet_equal(buf_len(&buf), 1); + assert_int_equal(mem[0], 0x7f); + + return TEST_EXIT_SUCCESS; +} + +static int test_flush_partial_write(void *state) +{ + uint8_t mem[BUFFER_SIZE]; + struct lj_wbuf buf; + struct writer_ctx ctx; + + UNUSED(state); + + writer_ctx_init(&ctx, WMODE_PARTIAL); + lj_wbuf_init(&buf, test_writer, &ctx, mem, sizeof(mem)); + + lj_wbuf_addn(&buf, "xyz", 3); + lj_wbuf_flush(&buf); + /* A partial write is reported as an error. */ + assert_true(lj_wbuf_test_flag(&buf, STREAM_ERRIO)); + assert_int_equal(lj_wbuf_errno(&buf), EIO); + /* The writer keeps the buffer, so the stream is not stopped. */ + assert_false(lj_wbuf_test_flag(&buf, STREAM_STOP)); + assert_ptr_equal(buf.buf, mem); + /* The unwritten data is dropped. */ + assert_sizet_equal(buf_len(&buf), 0); + + return TEST_EXIT_SUCCESS; +} + +static int test_flush_stopped_stream(void *state) +{ + uint8_t mem[BUFFER_SIZE]; + struct lj_wbuf buf; + struct writer_ctx ctx; + + UNUSED(state); + + writer_ctx_init(&ctx, WMODE_FAIL); + lj_wbuf_init(&buf, test_writer, &ctx, mem, sizeof(mem)); + + lj_wbuf_addn(&buf, "xyz", 3); + lj_wbuf_flush(&buf); + assert_stream_stopped_with_error(&buf); + assert_sizet_equal(ctx.flushes, 1); + + /* A stopped stream is not flushed again. */ + lj_wbuf_flush(&buf); + assert_sizet_equal(ctx.flushes, 1); + + return TEST_EXIT_SUCCESS; +} + +static int test_write_to_stopped_stream(void *state) +{ + uint8_t mem[BUFFER_SIZE]; + struct lj_wbuf buf; + struct writer_ctx ctx; + + UNUSED(state); + + writer_ctx_init(&ctx, WMODE_FAIL); + lj_wbuf_init(&buf, test_writer, &ctx, mem, sizeof(mem)); + + lj_wbuf_addn(&buf, "xyz", 3); + lj_wbuf_flush(&buf); + assert_stream_stopped_with_error(&buf); + + /* Writes to a stopped stream are ignored. */ + lj_wbuf_addbyte(&buf, 0x7f); + lj_wbuf_addu64(&buf, 128); + lj_wbuf_addn(&buf, "xyz", 3); + lj_wbuf_addstring(&buf, "xyz"); + assert_stream_stopped_with_error(&buf); + assert_sizet_equal(ctx.flushes, 1); + assert_sizet_equal(ctx.written, 0); + + return TEST_EXIT_SUCCESS; +} + +static int test_terminate(void *state) +{ + uint8_t mem[BUFFER_SIZE]; + struct lj_wbuf buf; + struct writer_ctx ctx; + + UNUSED(state); + + writer_ctx_init(&ctx, WMODE_OK); + lj_wbuf_init(&buf, test_writer, &ctx, mem, sizeof(mem)); + + lj_wbuf_terminate(&buf); + assert_true(buf.writer == NULL); + assert_ptr_equal(buf.ctx, NULL); + assert_ptr_equal(buf.buf, NULL); + assert_ptr_equal(buf.pos, NULL); + assert_sizet_equal(buf.size, 0); + assert_false(lj_wbuf_test_flag(&buf, STREAM_ERRIO | STREAM_STOP)); + assert_int_equal(lj_wbuf_errno(&buf), 0); + + return TEST_EXIT_SUCCESS; +} + +/* +** A writer may release the buffer and set *data to NULL on an +** unrecoverable error (e.g. ENOSPC). The stream is stopped in +** this case, so the write functions must not touch the released +** buffer. +*/ +int main(void) +{ + const struct test_unit tgroup[] = { + test_unit_def(test_addbyte), + test_unit_def(test_addu64), + test_unit_def(test_addn), + test_unit_def(test_addstring), + test_unit_def(test_addn_pumped_through_buffer), + test_unit_def(test_flush_on_overflow), + test_unit_def(test_flush), + test_unit_def(test_flush_partial_write), + test_unit_def(test_flush_stopped_stream), + test_unit_def(test_write_to_stopped_stream), + test_unit_def(test_terminate), + }; + const int test_result = test_run_group(tgroup, NULL); + + return test_result; +} -- 2.56.0