From: Zbigniew Jędrzejewski-Szmek Date: Tue, 14 Apr 2026 22:48:31 +0000 (+0200) Subject: test: add test-iovec-wrapper X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=5adccf5034a95caaa259908ad4759d52751967ea;p=thirdparty%2Fsystemd.git test: add test-iovec-wrapper Tests the old code in iovec-wrapper and the two new functions. Co-developed-by: Claude Opus 4.6 --- diff --git a/src/test/meson.build b/src/test/meson.build index fb0aac27f88..09639244928 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -130,6 +130,7 @@ simple_tests += files( 'test-install-root.c', 'test-io-util.c', 'test-iovec-util.c', + 'test-iovec-wrapper.c', 'test-journal-importer.c', 'test-kbd-util.c', 'test-label.c', diff --git a/src/test/test-iovec-wrapper.c b/src/test/test-iovec-wrapper.c new file mode 100644 index 00000000000..35b93965722 --- /dev/null +++ b/src/test/test-iovec-wrapper.c @@ -0,0 +1,245 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "alloc-util.h" +#include "iovec-wrapper.h" +#include "tests.h" + +TEST(iovw_put) { + _cleanup_(iovw_done) struct iovec_wrapper iovw = {}; + + /* Zero-length insertions are no-ops and do not touch the data pointer */ + ASSERT_OK_ZERO(iovw_put(&iovw, NULL, 0)); + ASSERT_OK_ZERO(iovw_put(&iovw, (char*) "foo", 0)); + ASSERT_EQ(iovw.count, 0U); + + ASSERT_OK(iovw_put(&iovw, (char*) "foo", 3)); + ASSERT_OK(iovw_put(&iovw, (char*) "barbar", 6)); + ASSERT_OK(iovw_put(&iovw, (char*) "q", 1)); + ASSERT_EQ(iovw.count, 3U); + + ASSERT_EQ(iovw.iovec[0].iov_len, 3U); + ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "foo", 3), 0); + ASSERT_EQ(iovw.iovec[1].iov_len, 6U); + ASSERT_EQ(memcmp(iovw.iovec[1].iov_base, "barbar", 6), 0); + ASSERT_EQ(iovw.iovec[2].iov_len, 1U); + ASSERT_EQ(memcmp(iovw.iovec[2].iov_base, "q", 1), 0); +} + +TEST(iovw_append) { + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + + /* iovw_append copies the data; the wrapper owns the copies. */ + char buf[4] = { 'o', 'n', 'e', '\0' }; + ASSERT_OK(iovw_append(&iovw, buf, 3)); + ASSERT_EQ(iovw.count, 1U); + ASSERT_EQ(iovw.iovec[0].iov_len, 3U); + ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "one", 3), 0); + + /* Insert with a NUL */ + ASSERT_OK_ZERO(iovw_append(&iovw, buf, 4)); + ASSERT_EQ(iovw.count, 2U); + ASSERT_EQ(iovw.iovec[1].iov_len, 4U); + ASSERT_EQ(memcmp(iovw.iovec[1].iov_base, "one\0", 4), 0); + + /* Mutating the caller's buffer does not affect what's stored */ + memset(buf, 'X', sizeof buf); + ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "one", 3), 0); +} + +TEST(iovw_consume) { + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + + char *p = strdup("consumed"); + ASSERT_NOT_NULL(p); + ASSERT_OK(iovw_consume(&iovw, p, strlen(p))); + ASSERT_EQ(iovw.count, 1U); + /* iovw_consume moves ownership in place, no copy */ + ASSERT_PTR_EQ(iovw.iovec[0].iov_base, p); + + /* Zero-length: iovw_put returns 0 without adding anything, and does not free the payload. + * Confirm by strdup'ing something and explicitly freeing it afterwards. */ + _cleanup_free_ char *q = strdup(""); + ASSERT_NOT_NULL(q); + ASSERT_OK_ZERO(iovw_consume(&iovw, q, 0)); + ASSERT_EQ(iovw.count, 1U); +} + +TEST(iovw_isempty) { + ASSERT_TRUE(iovw_isempty(NULL)); + + _cleanup_(iovw_done) struct iovec_wrapper iovw = {}; + ASSERT_TRUE(iovw_isempty(&iovw)); + + ASSERT_OK(iovw_put(&iovw, (char*) "x", 1)); + ASSERT_FALSE(iovw_isempty(&iovw)); +} + +TEST(iovw_put_string_field) { + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + + ASSERT_OK(iovw_put_string_field(&iovw, "FOO=", "bar")); + ASSERT_OK(iovw_put_string_field(&iovw, "BAZ=", "quux")); + ASSERT_EQ(iovw.count, 2U); + + ASSERT_EQ(iovw.iovec[0].iov_len, strlen("FOO=bar")); + ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "FOO=bar", strlen("FOO=bar")), 0); + ASSERT_EQ(iovw.iovec[1].iov_len, strlen("BAZ=quux")); + ASSERT_EQ(memcmp(iovw.iovec[1].iov_base, "BAZ=quux", strlen("BAZ=quux")), 0); + + /* Non-replacing put: a second FOO= just appends rather than replacing */ + ASSERT_OK(iovw_put_string_field(&iovw, "FOO=", "second")); + ASSERT_EQ(iovw.count, 3U); + ASSERT_EQ(iovw.iovec[0].iov_len, strlen("FOO=bar")); + ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "FOO=bar", strlen("FOO=bar")), 0); + ASSERT_EQ(iovw.iovec[2].iov_len, strlen("FOO=second")); + ASSERT_EQ(memcmp(iovw.iovec[2].iov_base, "FOO=second", strlen("FOO=second")), 0); +} + +TEST(iovw_replace_string_field) { + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + + /* If the field does not exist yet, replace acts like put */ + ASSERT_OK(iovw_replace_string_field(&iovw, "A=", "1")); + ASSERT_EQ(iovw.count, 1U); + ASSERT_EQ(iovw.iovec[0].iov_len, strlen("A=1")); + ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "A=1", strlen("A=1")), 0); + + /* Replacing an existing field updates it in place */ + ASSERT_OK(iovw_replace_string_field(&iovw, "A=", "twentytwo")); + ASSERT_EQ(iovw.count, 1U); + ASSERT_EQ(iovw.iovec[0].iov_len, strlen("A=twentytwo")); + ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "A=twentytwo", strlen("A=twentytwo")), 0); + + /* Distinct field still appends */ + ASSERT_OK(iovw_replace_string_field(&iovw, "B=", "x")); + ASSERT_EQ(iovw.count, 2U); +} + +TEST(iovw_put_string_fieldf) { + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + + ASSERT_OK(iovw_put_string_fieldf(&iovw, "N=", "%d-%s", 42, "answer")); + ASSERT_EQ(iovw.count, 1U); + ASSERT_EQ(iovw.iovec[0].iov_len, strlen("N=42-answer")); + ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "N=42-answer", strlen("N=42-answer")), 0); + + /* Replacing variant */ + ASSERT_OK(iovw_replace_string_fieldf(&iovw, "N=", "%d", 7)); + ASSERT_EQ(iovw.count, 1U); + ASSERT_EQ(iovw.iovec[0].iov_len, strlen("N=7")); + ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "N=7", strlen("N=7")), 0); +} + +TEST(iovw_put_string_field_free) { + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + + /* iovw_put_string_field_free takes ownership of the value string (frees it on return). */ + char *v = strdup("hello"); + ASSERT_NOT_NULL(v); + ASSERT_OK(iovw_put_string_field_free(&iovw, "K=", v)); + ASSERT_EQ(iovw.count, 1U); + ASSERT_EQ(iovw.iovec[0].iov_len, strlen("K=hello")); + ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "K=hello", strlen("K=hello")), 0); +} + +TEST(iovw_rebase) { + /* iovw_rebase shifts all iov_base pointers from an old base to a new base. Fabricate a + * stand-in "old base" and "new base" and a wrapper with offsets pointing into the old + * base, then verify they get rewritten to point into the new base. */ + + uint8_t old_base[64] = {}, new_base[64] = {}; + for (size_t i = 0; i < sizeof old_base; i++) { + old_base[i] = (uint8_t) i; + new_base[i] = (uint8_t) (100 + i); + } + + _cleanup_(iovw_done) struct iovec_wrapper iovw = {}; + + ASSERT_OK(iovw_put(&iovw, old_base + 0, 4)); + ASSERT_OK(iovw_put(&iovw, old_base + 10, 2)); + ASSERT_OK(iovw_put(&iovw, old_base + 30, 8)); + ASSERT_EQ(iovw.count, 3U); + + iovw_rebase(&iovw, old_base, new_base); + + ASSERT_PTR_EQ(iovw.iovec[0].iov_base, new_base + 0); + ASSERT_PTR_EQ(iovw.iovec[1].iov_base, new_base + 10); + ASSERT_PTR_EQ(iovw.iovec[2].iov_base, new_base + 30); + + /* Lengths are preserved */ + ASSERT_EQ(iovw.iovec[0].iov_len, 4U); + ASSERT_EQ(iovw.iovec[1].iov_len, 2U); + ASSERT_EQ(iovw.iovec[2].iov_len, 8U); + + /* And the contents through the new base match what we staged there */ + ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, new_base + 0, 4), 0); + ASSERT_EQ(memcmp(iovw.iovec[1].iov_base, new_base + 10, 2), 0); + ASSERT_EQ(memcmp(iovw.iovec[2].iov_base, new_base + 30, 8), 0); +} + +TEST(iovw_size) { + _cleanup_(iovw_done) struct iovec_wrapper iovw = {}; + ASSERT_EQ(iovw_size(&iovw), 0U); + + ASSERT_OK(iovw_put(&iovw, (char*) "abcd", 4)); + ASSERT_OK(iovw_put(&iovw, (char*) "efghij", 6)); + ASSERT_OK(iovw_put(&iovw, (char*) "kl", 2)); + ASSERT_EQ(iovw_size(&iovw), 12U); +} + +TEST(iovw_append_iovw) { + _cleanup_(iovw_done_free) struct iovec_wrapper target = {}; + _cleanup_(iovw_done) struct iovec_wrapper source = {}; + + /* Appending an empty/NULL source is a no-op */ + ASSERT_OK_ZERO(iovw_append_iovw(&target, NULL)); + ASSERT_OK_ZERO(iovw_append_iovw(&target, &source)); + ASSERT_EQ(target.count, 0U); + + ASSERT_OK(iovw_put(&source, (char*) "one", 3)); + ASSERT_OK(iovw_put(&source, (char*) "twotwo", 6)); + ASSERT_EQ(source.count, 2U); + + /* Pre-seed target with one entry to check that append adds on top rather than replacing */ + char *seed = strdup("zero"); + ASSERT_NOT_NULL(seed); + ASSERT_OK(iovw_put(&target, seed, strlen(seed))); + + ASSERT_OK(iovw_append_iovw(&target, &source)); + ASSERT_EQ(target.count, 3U); + + /* Appended entries must be fresh copies, not aliases of the source entries */ + ASSERT_TRUE(target.iovec[1].iov_base != source.iovec[0].iov_base); + ASSERT_TRUE(target.iovec[2].iov_base != source.iovec[1].iov_base); + + ASSERT_EQ(target.iovec[1].iov_len, 3U); + ASSERT_EQ(memcmp(target.iovec[1].iov_base, "one", 3), 0); + ASSERT_EQ(target.iovec[2].iov_len, 6U); + ASSERT_EQ(memcmp(target.iovec[2].iov_base, "twotwo", 6), 0); + + /* Source is unchanged */ + ASSERT_EQ(source.count, 2U); +} + +TEST(iovw_to_cstring) { + _cleanup_(iovw_done) struct iovec_wrapper iovw = {}; + _cleanup_free_ char *s; + + /* Empty wrapper → empty string */ + s = iovw_to_cstring(&iovw); + ASSERT_NOT_NULL(s); + ASSERT_STREQ(s, ""); + s = mfree(s); + + ASSERT_OK(iovw_put(&iovw, (char*) "foo", 3)); + ASSERT_OK(iovw_put(&iovw, (char*) "/", 1)); + ASSERT_OK(iovw_put(&iovw, (char*) "bar", 3)); + + s = iovw_to_cstring(&iovw); + ASSERT_NOT_NULL(s); + ASSERT_STREQ(s, "foo/bar"); +} + +DEFINE_TEST_MAIN(LOG_INFO);