view tests/test_buffer.c @ 849:1c4d7ff0a682

buffer: add buffer_insert & buffer_insert_c Like appending, but at any offset - not just at the end of existing data. Signed-off-by: Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
author Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
date Thu, 16 Sep 2021 22:39:12 -0400
parents 8371ff905869
children
line wrap: on
line source

/*
 * Copyright (c) 2017-2021 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include <jeffpc/buffer.h>
#include <jeffpc/error.h>
#include <jeffpc/rand.h>

#include "test.c"

#define COMMON_TEST_STRING \
	"759f7e2d-67ec-4e72-8f61-86a3fd93b1be" \
	"60e9149e-d039-e32b-b25d-c995b28bf890" \
	"40f0fddc-ddca-4ff5-cd81-b0ae4c7d6123"

static const uint8_t data_1k[1024] = COMMON_TEST_STRING;

/*
 * Get the buffer data, but make sure that both const and mutable versions
 * return the same pointer.
 */
static inline const void *get_buffer_data(struct buffer *buf)
{
	const void *data;
	void *mdata;

	data = buffer_data(buf);
	mdata = buffer_data_mutable(buf);

	if (data != mdata)
		fail("buffer_data() != buffer_data_mutable(): %p != %p",
		     data, mdata);

	return data;
}

/* allocate a heap buffer - either on the heap or on the stack */
static inline struct buffer *alloc_heap_buffer(struct buffer *buf, size_t size)
{
	if (buf) {
		int ret;

		ret = buffer_init_heap(buf, size);
		if (ret)
			fail("buffer_init_heap(%zu) failed: %s", size,
			     xstrerror(ret));
	} else {
		buf = buffer_alloc(size);
		if (IS_ERR(buf))
			fail("buffer_alloc(%zu) failed: %s", size,
			     xstrerror(PTR_ERR(buf)));

	}

	return buf;
}

static inline void check_data_zeroes(struct buffer *buffer, size_t startoff)
{
	const uint8_t *data;
	size_t len;
	size_t i;

	data = get_buffer_data(buffer);
	len = buffer_size(buffer);

	if (startoff > len)
		fail("%s startoff > len (%zu > %zu)", __func__, startoff, len);

	for (i = startoff; i < len; i++) {
		if (data[i] == '\0')
			continue;

		fail("buffer contains %#02x @ offset %zu (should be '\\0')",
		     data[i], i);
	}
}

static inline void check_data(struct buffer *buffer)
{
	const void *ptr;

	ptr = get_buffer_data(buffer);
	if (ptr == NULL)
		fail("get_buffer_data() returned NULL");
	if (IS_ERR(ptr))
		fail("get_buffer_data() returned error: %s",
		     xstrerror(PTR_ERR(ptr)));
}

static inline void check_data_ptr(struct buffer *buffer, const void *expected)
{
	const void *ptr;

	ptr = get_buffer_data(buffer);
	if (ptr == expected)
		return;

	if (IS_ERR(ptr))
		fail("get_buffer_data() returned error: %s (%p expected)",
		     xstrerror(PTR_ERR(ptr)), expected);
	fail("get_buffer_data() returned %p, but %p was expected", ptr,
	     expected);
}

static inline void check_data_null(struct buffer *buffer)
{
	check_data_ptr(buffer, NULL);
}

static inline void check_used(struct buffer *buffer, size_t expected)
{
	size_t got;

	got = buffer_size(buffer);
	if (got != expected)
		fail("buffer_size() == %zu, should be %zu", got, expected);
}

static inline void check_insert_err(struct buffer *buffer, const void *ptr,
				    size_t len, size_t off, int expected_ret)
{
	int ret;

	ret = buffer_insert(buffer, ptr, len, off);
	if (ret == expected_ret)
		return;

	if (ret && expected_ret)
		fail("buffer_insert(..., %p, %zu, %zu) failed with wrong error: "
		     "got %s, expected %s", ptr, len, off, xstrerror(ret),
		     xstrerror(expected_ret));
	if (ret && !expected_ret)
		fail("buffer_insert(..., %p, %zu, %zu) failed but it wasn't "
		     "supposed to: %s", ptr, len, off, xstrerror(ret));
	if (!ret && expected_ret)
		fail("buffer_insert(..., %p, %zu, %zu) succeeded but it wasn't "
		     "supposed to. Expected error: %s", ptr, len, off,
		     xstrerror(expected_ret));
	fail("impossible condition occured");
}

static inline void check_insert(struct buffer *buffer, const void *ptr,
				size_t len, size_t off)
{
	check_insert_err(buffer, ptr, len, off, 0);
}

static inline void check_append_err(struct buffer *buffer, const void *ptr,
				    size_t len, int expected_ret)
{
	int ret;

	ret = buffer_append(buffer, ptr, len);
	if (ret == expected_ret)
		return;

	if (ret && expected_ret)
		fail("buffer_append(..., %p, %zu) failed with wrong error: "
		     "got %s, expected %s", ptr, len, xstrerror(ret),
		     xstrerror(expected_ret));
	if (ret && !expected_ret)
		fail("buffer_append(..., %p, %zu) failed but it wasn't "
		     "supposed to: %s", ptr, len, xstrerror(ret));
	if (!ret && expected_ret)
		fail("buffer_append(..., %p, %zu) succeeded but it wasn't "
		     "supposed to. Expected error: %s", ptr, len,
		     xstrerror(expected_ret));
	fail("impossible condition occured");
}

static inline void check_append(struct buffer *buffer, const void *ptr,
				size_t len)
{
	check_append_err(buffer, ptr, len, 0);
}

static inline void check_truncate_err(struct buffer *buffer, size_t newsize,
				      int expected_ret)
{
	int ret;

	ret = buffer_truncate(buffer, newsize);
	if (ret == expected_ret)
		return;

	if (ret && expected_ret)
		fail("buffer_truncate(..., %zu) failed with wrong error: "
		     "got %s, expected %s", newsize, xstrerror(ret),
		     xstrerror(expected_ret));
	if (ret && !expected_ret)
		fail("buffer_truncate(..., %zu) failed but it wasn't "
		     "supposed to: %s", newsize, xstrerror(ret));
	if (!ret && expected_ret)
		fail("buffer_truncate(..., %zu) succeeded but it wasn't "
		     "supposed to. Expected error: %s", newsize,
		     xstrerror(expected_ret));
	fail("impossible condition occured");
}

static inline void check_truncate(struct buffer *buffer, size_t newsize)
{
	check_truncate_err(buffer, newsize, 0);
}

static void test_alloc_free(struct buffer *_buffer)
{
	struct buffer *buffer;
	size_t i;

	for (i = 0; i < 10; i++) {
		fprintf(stderr, "%s(%p): iter = %zu...", __func__, _buffer, i);

		buffer = alloc_heap_buffer(_buffer, i);

		check_data(buffer);
		check_used(buffer, 0);

		buffer_free(buffer);

		fprintf(stderr, "\n");
	}
}

static size_t insert_idx_fwd(size_t niter, size_t i, bool raw)
{
	return i;
}

static size_t insert_idx_rev(size_t niter, size_t i, bool raw)
{
	return raw ? (niter - i - 1) : 0;
}

static size_t insert_idx_mid(size_t niter, size_t i, bool raw)
{
	if (raw)
		return !i ? 0 : (niter - i);
	else
		return i ? 1 : 0;
}

static void do_test_insert(struct buffer *_buffer, const char *func,
			   const char *name,
			   size_t (*idx)(size_t, size_t, bool))
{
	const size_t niter = 256;
	struct buffer *buffer;
	size_t startsize;

	for (startsize = 0; startsize < 300; startsize++) {
		uint8_t data[niter];
		size_t i;

		fprintf(stderr, "%s(%p): %s, iter = %3zu...", func, _buffer,
			name, startsize);

		buffer = alloc_heap_buffer(_buffer, startsize);

		for (i = 0; i < niter; i++) {
			uint8_t byte = i;

			data[idx(niter, i, true)] = i;

			check_data(buffer);
			check_used(buffer, i);
			check_insert(buffer, &byte, 1, idx(niter, i, false));
			check_data(buffer);
			check_used(buffer, i + 1);

			/* truncate to same size */
			check_truncate(buffer, buffer_size(buffer));
			check_data(buffer);
			check_used(buffer, i + 1);
		}

		check_data(buffer);
		check_used(buffer, i);

		if (memcmp(data, get_buffer_data(buffer), sizeof(data)))
			fail("buffered data mismatches expectations");

		buffer_free(buffer);

		memset(data, 0, sizeof(data));

		fprintf(stderr, "ok.\n");
	}
}

static void test_insert(struct buffer *_buffer)
{
	do_test_insert(_buffer, __func__, "forward", insert_idx_fwd);
	do_test_insert(_buffer, __func__, "reverse", insert_idx_rev);
	do_test_insert(_buffer, __func__, "middle", insert_idx_mid);
}

static void inner_loop_append(size_t niter, struct buffer *buffer, uint8_t *data,
			      void (*check)(struct buffer *))
{
	size_t i;

	for (i = 0; i < niter; i++) {
		uint8_t byte = i;

		if (data)
			data[i] = i;

		check(buffer);
		check_used(buffer, i);
		check_append(buffer, &byte, 1);
		check(buffer);
		check_used(buffer, i + 1);

		/* truncate to same size */
		check_truncate(buffer, buffer_size(buffer));
		check(buffer);
		check_used(buffer, i + 1);
	}

	check(buffer);
	check_used(buffer, i);
}

static void test_append(struct buffer *_buffer)
{
	struct buffer *buffer;
	size_t startsize;
	size_t i;

	/* append 1 char at a time */
	for (startsize = 0; startsize < 300; startsize++) {
		uint8_t data[256];

		fprintf(stderr, "%s(%p): 1 char, iter = %3zu...", __func__,
			_buffer, startsize);

		buffer = alloc_heap_buffer(_buffer, startsize);

		inner_loop_append(sizeof(data), buffer, data, check_data);

		if (memcmp(data, get_buffer_data(buffer), sizeof(data)))
			fail("buffered data mismatches expectations");

		buffer_free(buffer);

		memset(data, 0, sizeof(data));

		fprintf(stderr, "ok.\n");
	}

	/* append 1 MB total, 1 kB at a time */
	buffer = alloc_heap_buffer(_buffer, 0);

	for (i = 0; i < 1024; i++) {
		fprintf(stderr, "%s(%p): 1 kB, iter = %3zu...", __func__,
			_buffer, i);

		check_data(buffer);
		check_used(buffer, sizeof(data_1k) * i);
		check_append(buffer, data_1k, sizeof(data_1k));
		check_data(buffer);
		check_used(buffer, sizeof(data_1k) * (i + 1));

		fprintf(stderr, "ok.\n");
	}

	buffer_free(buffer);
}

static void test_truncate_grow(struct buffer *_buffer)
{
	struct buffer *buffer;
	size_t i;

	buffer = alloc_heap_buffer(_buffer, 0);

	for (i = 0; i < 50000; i += 13) {
		fprintf(stderr, "%s: iter = %3zu...", __func__, i);

		check_truncate(buffer, i);
		check_used(buffer, i);
		check_data_zeroes(buffer, 0);

		fprintf(stderr, "ok.\n");
	}

	buffer_free(buffer);
}

static void test_truncate_shrink(struct buffer *_buffer)
{
	const size_t maxsize = 5000 * sizeof(uint64_t);
	struct buffer *buffer;
	size_t i;

	buffer = alloc_heap_buffer(_buffer, maxsize);

	/* append some random data */
	for (i = 0; i < maxsize; i += sizeof(uint64_t)) {
		uint64_t tmp;

		tmp = rand64();

		check_append(buffer, &tmp, sizeof(tmp));
	}

	/* sanity check the size */
	check_used(buffer, maxsize);

	for (i = maxsize; i > 0; i -= sizeof(uint64_t)) {
		fprintf(stderr, "%s: iter = %3zu...", __func__, i);

		check_truncate(buffer, i);
		check_used(buffer, i);

		fprintf(stderr, "ok.\n");
	}

	buffer_free(buffer);
}

static void test_sink_1char_loop(void)
{
	struct buffer buffer;
	size_t maxsize;

	for (maxsize = 0; maxsize < 270; maxsize++) {
		fprintf(stderr, "%s: append 1 char, iter = %3zu...", __func__,
			maxsize);

		buffer_init_sink(&buffer);

		inner_loop_append(256, &buffer, NULL, check_data_null);

		memset(&buffer, 0, sizeof(struct buffer));

		fprintf(stderr, "ok.\n");
	}
}

static void test_sink_1k(void)
{
	struct buffer buffer;

	fprintf(stderr, "%s: append 1kB...", __func__);

	buffer_init_sink(&buffer);

	check_append(&buffer, data_1k, sizeof(data_1k));

	fprintf(stderr, "ok.\n");
}

void test_static_const_arg(void)
{
	const char const_data[] = "abc";
	struct buffer buffer;

	/* (statically) check for passing in const pointer being ok */
	buffer_init_static(&buffer, const_data, strlen(const_data),
			   strlen(const_data), false);
}

void test_static_ro(void)
{
	const char rawdata[] = COMMON_TEST_STRING;
	const size_t insert_offsets[] = {
		0, 1, strlen(rawdata) - 1, strlen(rawdata), strlen(rawdata) + 1,
	};
	struct buffer buffer;
	size_t i;

	buffer_init_static(&buffer, rawdata, strlen(rawdata), strlen(rawdata),
			   false);

	check_used(&buffer, strlen(rawdata));
	check_data_ptr(&buffer, rawdata);

	for (i = 0; i < ARRAY_LEN(insert_offsets); i++) {
		fprintf(stderr, "%s: insert to a buffer, offset=%zu...",
			__func__, insert_offsets[i]);

		check_insert_err(&buffer, "abc", 3, insert_offsets[i], -EROFS);
		check_used(&buffer, strlen(rawdata));
		check_data_ptr(&buffer, rawdata);

		fprintf(stderr, "ok.\n");
	}

	for (i = 0; i < 10; i++) {
		fprintf(stderr, "%s: truncate, iter = %zu...", __func__, i);

		check_truncate_err(&buffer, i * strlen(rawdata) / 5, -EROFS);
		check_used(&buffer, strlen(rawdata));
		check_data_ptr(&buffer, rawdata);

		fprintf(stderr, "ok.\n");
	}
}

void test_static_rw(void)
{
	char rawdata[] = COMMON_TEST_STRING;
	const size_t rawlen = strlen(rawdata);
	const size_t insert_offsets[] = {
		0, 1, strlen(rawdata) - 1, strlen(rawdata), strlen(rawdata) + 1,
	};
	struct buffer buffer;
	size_t size;
	size_t i;

	for (size = 0; size <= rawlen; size++) {
		buffer_init_static(&buffer, rawdata, size, rawlen, true);

		fprintf(stderr, "%s(%zu): initial sanity check...", __func__,
			size);
		check_used(&buffer, size);
		check_data_ptr(&buffer, rawdata);
		fprintf(stderr, "ok.\n");

		for (i = 0; i < ARRAY_LEN(insert_offsets); i++) {
			fprintf(stderr, "%s(%zu): insert to a buffer without "
				"enough space, offset=%zu...", __func__, size,
				insert_offsets[i]);
			check_insert_err(&buffer, COMMON_TEST_STRING, rawlen + 1,
					 insert_offsets[i], -ENOSPC);
			check_used(&buffer, size);
			check_data_ptr(&buffer, rawdata);
			fprintf(stderr, "ok.\n");
		}

		fprintf(stderr, "%s(%zu): append to a buffer without enough "
			"space...", __func__, size);
		check_append_err(&buffer, COMMON_TEST_STRING, rawlen + 1,
				 -ENOSPC);
		check_used(&buffer, size);
		check_data_ptr(&buffer, rawdata);
		fprintf(stderr, "ok.\n");

		fprintf(stderr, "%s(%zu): truncate to a larger than buffer "
			"size...", __func__, size);
		check_truncate_err(&buffer, rawlen + 1, -ENOSPC);
		check_used(&buffer, size);
		check_data_ptr(&buffer, rawdata);
		fprintf(stderr, "ok.\n");

		if (size >= 5) {
			for (i = 0; i < ARRAY_LEN(insert_offsets); i++) {
				const size_t off = insert_offsets[i];

				if (off > (size - 5))
					continue;

				check_truncate_err(&buffer, size - 5, 0);

				fprintf(stderr, "%s(%zu): insert too much, "
					"offset=%zu...", __func__, size, off);
				check_insert_err(&buffer, COMMON_TEST_STRING COMMON_TEST_STRING,
						 rawlen - size + 11, off, -ENOSPC);
				check_used(&buffer, size - 5);
				check_data_ptr(&buffer, rawdata);
				fprintf(stderr, "ok.\n");

				fprintf(stderr, "%s(%zu): insert a little, "
					"offset=%zu...", __func__, size, off);
				check_insert_err(&buffer, COMMON_TEST_STRING, 5,
						 off, 0);
				check_used(&buffer, size);
				check_data_ptr(&buffer, rawdata);
				fprintf(stderr, "ok.\n");
			}

			fprintf(stderr, "%s(%zu): truncate to a smaller than "
				"initial size...", __func__, size);
			check_truncate_err(&buffer, size - 5, 0);
			check_used(&buffer, size - 5);
			check_data_ptr(&buffer, rawdata);
			fprintf(stderr, "ok.\n");

			fprintf(stderr, "%s(%zu): append too much...", __func__, size);
			check_append_err(&buffer, COMMON_TEST_STRING COMMON_TEST_STRING,
					 rawlen - size + 11, -ENOSPC);
			check_used(&buffer, size - 5);
			check_data_ptr(&buffer, rawdata);
			fprintf(stderr, "ok.\n");

			fprintf(stderr, "%s(%zu): append a little...", __func__, size);
			check_append_err(&buffer, COMMON_TEST_STRING, 5, 0);
			check_used(&buffer, size);
			check_data_ptr(&buffer, rawdata);
			fprintf(stderr, "ok.\n");
		}
	}
}

static void test_append_insert_equivalence(void)
{
	struct buffer *abuffer; /* for buffer_append */
	struct buffer *ibuffer; /* for buffer_insert */
	uint8_t data[256];
	size_t i;

	fprintf(stderr, "%s: comparing append to insert(end)...", __func__);

	abuffer = alloc_heap_buffer(NULL, 0);
	ibuffer = alloc_heap_buffer(NULL, 0);

	for (i = 0; i < sizeof(data); i++) {
		uint8_t byte = i;

		data[i] = i;

		check_data(abuffer);
		check_data(ibuffer);
		check_used(abuffer, i);
		check_used(ibuffer, i);

		check_append(abuffer, &byte, 1);
		check_insert(ibuffer, &byte, 1, i);

		check_data(abuffer);
		check_data(ibuffer);
		check_used(abuffer, i + 1);
		check_used(ibuffer, i + 1);
	}

	if (memcmp(data, get_buffer_data(abuffer), sizeof(data)))
		fail("buffered data (append) mismatches expectations");

	if (memcmp(data, get_buffer_data(ibuffer), sizeof(data)))
		fail("buffered data (insert) mismatches expectations");

	buffer_free(abuffer);
	buffer_free(ibuffer);

	memset(data, 0, sizeof(data));

	fprintf(stderr, "ok.\n");
}

void test(void)
{
	struct buffer stack_buf;

	/* stack allocated buffer struct */
	test_alloc_free(&stack_buf);
	test_append(&stack_buf);
	test_insert(&stack_buf);
	test_truncate_grow(&stack_buf);
	test_truncate_shrink(&stack_buf);

	/* heap allocated buffer struct */
	test_alloc_free(NULL);
	test_append(NULL);
	test_insert(NULL);
	test_truncate_grow(NULL);
	test_truncate_shrink(NULL);

	test_sink_1char_loop();
	test_sink_1k();
	test_static_const_arg();
	test_static_ro();
	test_static_rw();

	test_append_insert_equivalence();
}