Compare commits

..

46 commits

Author SHA1 Message Date
Imbus
a6651f05a5 CI
All checks were successful
ci/woodpecker/push/test Pipeline was successful
2024-12-25 20:04:46 +01:00
Imbus
b11edb59f4 ...
Some checks failed
ci/woodpecker/push/test Pipeline failed
2024-12-25 20:03:35 +01:00
Imbus
16639b24ef Deps
Some checks failed
ci/woodpecker/push/test Pipeline failed
2024-12-25 20:01:58 +01:00
Imbus
ff984caf35 Fix CI
Some checks failed
ci/woodpecker/push/test Pipeline failed
2024-12-25 19:56:32 +01:00
Imbus
a6c8b5cdc1 Fix CI
Some checks failed
ci/woodpecker/push/test Pipeline failed
2024-12-25 19:53:00 +01:00
Imbus
da47bcd82f CI 2024-12-25 19:52:31 +01:00
Imbus
82388142f0 CI 2024-12-25 19:50:43 +01:00
Imbus
b77d86ae12 Tests
Some checks failed
ci/woodpecker/push/test Pipeline failed
2024-12-25 19:46:03 +01:00
Imbus
b26ceeb8fe Noconfirm
All checks were successful
ci/woodpecker/push/test Pipeline was successful
2024-12-25 19:39:17 +01:00
Imbus
2acb3c5bd5 CI
Some checks failed
ci/woodpecker/push/test Pipeline failed
2024-12-25 19:38:23 +01:00
Imbus
5fd22f005a ...
Some checks failed
ci/woodpecker/push/test Pipeline failed
2024-12-25 19:37:30 +01:00
Imbus
06a5b53bd9 CI test
Some checks failed
ci/woodpecker/push/test Pipeline failed
2024-12-25 19:36:52 +01:00
Imbus
4e8f158c91 Woke devs naming scheme fucked me again
Some checks failed
ci/woodpecker/push/test Pipeline failed
2024-12-25 19:22:39 +01:00
Imbus
14e8b9e1c0 Woodpecker test 2024-12-25 19:19:28 +01:00
Imbus
bfffbb9211 Basic tests implemented using cmocka 2024-12-25 14:05:22 +01:00
Imbus
1e76066400 gitignore for chache, json and .elf 2024-12-25 14:04:23 +01:00
Imbus
4fab8ca207 Extended readme 2024-12-25 14:03:57 +01:00
Imbus
259c048bd5 Move certain debug related functionality into header/source, feature gated by DEBUG flag 2024-12-25 14:03:47 +01:00
Imbus
3308e61dc1 Missing include from previous commit 2024-12-25 14:03:11 +01:00
Imbus
20b6e5c2fd Prevent double-free in rb_destroy 2024-12-25 14:02:49 +01:00
Imbus
4a90d24069 Makefile: debug is default target, make sure include path is set to project root, better clean 2024-12-25 14:01:55 +01:00
Imbus
948aeab825 clang-format: Short loops on single line 2024-12-25 14:00:38 +01:00
Imbus
6a4dc46b60 DEBUG_PRINT 2024-07-15 21:50:59 +02:00
Imbus
d6a3c95951 Testing 2024-07-14 21:32:40 +02:00
Imbus
7833bf73a7 Remove unused headers 2024-07-14 01:11:56 +02:00
Imbus
e5006ed262 Formatting 2024-07-03 13:31:57 +02:00
Imbus
2886a81574 Linker flags and makefile cleanup 2024-07-02 10:31:52 +02:00
Imbus
a5c5ecd64b Bug................................... 2024-07-02 08:39:31 +02:00
Imbus
85071d3130 Driver code simplifying 2024-07-02 06:49:49 +02:00
Imbus
06b949bed4 Remove redundant debug code/printing 2024-07-02 06:38:01 +02:00
Imbus
de2465de83 Check return in driver program 2024-07-02 06:37:58 +02:00
Imbus
6022bdee92 tidy makefile target to run clang-tidy 2024-07-02 06:33:00 +02:00
Imbus
ea100383d7 Typedef for free function signature 2024-07-02 06:32:22 +02:00
Imbus
eb4b11dda7 Formatting 2024-07-02 06:31:52 +02:00
Imbus
c06bb51cdc Driver code updated 2024-07-02 06:27:39 +02:00
Imbus
68af34c428 -fshort-enums in release mode 2024-07-02 06:27:22 +02:00
Imbus
8e77f810ec Better enum docs, added errors related to *_many functions 2024-07-02 06:27:05 +02:00
Imbus
8cbca38726 Bugfix incrementing count by correct amount in rb_push_many 2024-07-02 06:26:17 +02:00
Imbus
41f817fd5b rb_clear initial 2024-07-02 05:50:59 +02:00
Imbus
d19917bba7 Formatting 2024-07-02 05:50:47 +02:00
Imbus
db38111b3c Interface fix where MEMCPY_T parameter was forgotten in rb_pop_front 2024-07-02 05:42:44 +02:00
Imbus
c4f1ede6b0 rb_push_many first draft 2024-07-02 05:39:21 +02:00
Imbus
df9cd367a3 .so and .a target 2024-07-02 05:39:08 +02:00
Imbus
07bb60c8ae Better docstrings and example driver 2024-06-30 23:57:08 +02:00
Imbus
9c746a10cf Formatting 2024-06-30 21:11:49 +02:00
Imbus
4e1254fde1 Default to release build 2024-06-30 21:11:30 +02:00
9 changed files with 502 additions and 58 deletions

View file

@ -51,7 +51,7 @@ AllowShortEnumsOnASingleLine: true
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Never
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: false
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterDefinitionReturnType: All
AlwaysBreakAfterReturnType: AllDefinitions
AlwaysBreakBeforeMultilineStrings: false

5
.gitignore vendored
View file

@ -1,4 +1,9 @@
*.o
*.s
*.so
*.a
build
driver
*.elf
*.json
.cache

20
.woodpecker/test.yaml Normal file
View file

@ -0,0 +1,20 @@
when:
- event: push
branch: master
steps:
- name: build
image: debian
commands:
- apt update
- apt install libcmocka-dev -y
- apt install gcc -y
- apt install make -y
- make -j$(nproc) test/test.elf
- name: run-tests
image: debian
commands:
- apt update
- apt install libcmocka0 -y
- ./test/test.elf

View file

@ -1,22 +1,24 @@
# SPDX-License-Identifier: MIT
CC = gcc
CFLAGS = -Wall -Wextra -Werror -Wno-unused-parameter
CFLAGS += -Wno-unused-variable -Wno-unused-function
CFLAGS = -Wall -Wextra -Werror -Wno-unused-parameter
CFLAGS += -Wno-unused-variable -Wno-unused-function
CFLAGS += -Wno-unused-but-set-variable -Wno-unused-value -Wno-unused-label
CFLAGS += -Wno-unused-result -Wno-unused-const-variable
ifeq ($(RELEASE), 1)
CFLAGS += -I.
ifneq ($(RELEASE), 1)
CFLAGS += -g -O0 -std=c99 -march=native -mtune=native
CFLAGS += -DDEBUG
else
CFLAGS += -fno-exceptions -fno-asynchronous-unwind-tables -fno-ident
CFLAGS += -fno-unwind-tables -fno-stack-protector -fno-plt -fno-pic
CFLAGS += -O3 -std=c99 -march=native -mtune=native -fomit-frame-pointer
else
CFLAGS += -g -O0 -std=c99 -march=native -mtune=native
CFLAGS += -DDEBUG
endif
CFLAGS += -fshort-enums
endif # DEBUG
# Include debug flags
CFLAGS += -g -O0 -std=c99 -march=native -mtune=native
LDFLAGS = -lm
C_SOURCES = $(wildcard *.c)
C_HEADERS = $(wildcard *.h)
@ -25,6 +27,9 @@ ASMS = $(C_SOURCES:.c=.s)
all: $(OBJECTS)
test: test/test.elf
./test/test.elf
%.o: %.c $(C_HEADERS)
@echo "CC $<"
@$(CC) $(CFLAGS) -c $< -o $@
@ -34,19 +39,35 @@ all: $(OBJECTS)
@$(CC) $(CFLAGS) -S -masm=intel $<
driver: $(OBJECTS)
@$(CC) $(CFLAGS) $^ -o $@
@$(CC) $(LDFLAGS) $^ -o $@
run: driver
@./driver
test/test.elf: test/test_ringbuf.o ringbuf.o
@$(CC) $(CFLAGS) $^ -o $@ -lpthread -lcmocka
lib: $(OBJECTS)
@ar rcs librbuf.a ringbuf.o
dylib: $(OBJECTS)
@$(CC) $(LDLAGS) -fPIC -shared -o librbuf.so ringbuf.o
install:
@cp librbuf.a /usr/local/lib
@cp ringbuf.h /usr/local/include
clean:
rm -f $(OBJECTS) $(ASMS) driver
rm -f $(OBJECTS) $(ASMS) driver librbuf.a librbuf.so **/*.o **/*.elf
asm: $(ASMS) $(OBJECTS)
wc -l $(ASMS)
size $(OBJECTS)
tidy:
@clang-tidy $(C_SOURCES) -- $(CFLAGS)
format:
clang-format -i $(C_SOURCES) $(C_HEADERS)
.PHONY: all clean format asm
.PHONY: all clean format asm test

44
README.md Normal file
View file

@ -0,0 +1,44 @@
# RingBuf
RingBuf is an allocator-agnostic, non-overwriting circular/ring buffer
implementation in C99.
See: [Circular Buffer (Wikipedia)](https://en.wikipedia.org/wiki/Circular_buffer)
## Features
- Space Efficiency
The code is designed to be portable and flexible. The inspiration initially
came to me when designing a network driver. When operating in memory
constrained environments, every byte is of upmost importance. Traditional
metaprogramming such as templating in C++ and template metaprogramming in C,
although generic has the side effect of expanding into discrete machine code
specific for the specialization applied, essentially scaling (spatially) linear
with the amount of different datatypes we specialize on. This implementation
circumvents this by treating all data as a void pointer with a length. This
implies **no deep copies**.
- Allocator Agnostic
Another common characteristic of memory constrained environments are
custom malloc implementations and/or variations.
- Signatures
- Arena
## Design considerations
- Holding a reference to malloc internally would make a tidier interface
- Passing a user-defined function for deep-copies would enable certain applications
## Usage
In essence:
```c
int value = 42;
struct RingBuf rb;
rb_init(&rb, 10, malloc, sizeof(int));
rb_push_back(&rb, (void *)&data, memcpy);
rb_pop_front(&rb, &value, memcpy);
```
Most of these functions return Enum result types. See:
[ringbuf.h](./ringbuf.h).
## Future Improvements
- Trim the code
- Reduce boilerplate in tests
- Reduce the number of tests in exchange for better test fit

View file

@ -4,25 +4,100 @@
#include <string.h>
#include <stdio.h>
#define rb_size_t size_t
#ifndef DEBUG
#define DEBUG
#endif
// #define rb_size_t size_t
// #define rb_size_t int
#include "ringbuf.h"
typedef int DATATYPE;
int
main(void)
{
struct RingBuf rb;
rb_init(&rb, 10, malloc, sizeof(int));
int data = 5;
DATATYPE d;
rb_init(&rb, 10, malloc, sizeof(DATATYPE));
rb_push_back(&rb, &data, memcpy);
rb_debug_print(&rb);
int d;
rb_pop_front(&rb, &d);
const DATATYPE arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int arrlen = (rb_size_t)(sizeof(arr) / sizeof(DATATYPE));
printf("Data: %d\n", d);
// Single writes
printf("\n=== Single writes ===\n\n");
rb_debug_print(&rb);
int idx = 0;
while(idx < 5) {
if(rb_push_back(&rb, &arr[idx], memcpy) != WriteOk) {
printf("Failed to write data to buffer...\n");
}
idx++;
}
rb_debug_print(&rb);
// Pop the last n elements
for(int a = 0; a < 10; a++) {
if(rb_pop_front(&rb, &d, memcpy) != ReadOk) {
printf("Failed to read data from buffer...\n");
break;
}
printf("Data: %d\n", d);
}
rb_debug_print(&rb);
if(rb.read_head == rb.write_head)
printf("OK\n");
printf("idx: %d\n", idx);
// Push the rest
while(idx < arrlen && rb_push_back(&rb, &arr[idx], memcpy) == WriteOk) {
idx++;
}
rb_debug_print(&rb);
printf("Data: [");
while(rb_pop_front(&rb, &d, memcpy) == ReadOk)
printf("%d,", d);
printf("\b]\n");
// Multiple writes
printf("\n=== Multiple writes ===\n\n");
rb_clear(&rb); // Make sure
rb_debug_print(&rb);
int ok = WriteOk; // Assume we can write
if(rb_push_many(&rb, arr, memcpy, 8) != WriteOk) {
printf("Failed to write data to buffer...\n");
}
rb_debug_print(&rb);
rb_debug_empty(&rb);
// Test wrap around
rb_push_many(&rb, arr, memcpy, 10);
rb_debug_print(&rb);
rb_debug_empty(&rb);
// Test clear
rb_clear(&rb);
if(rb_pop_front(&rb, &d, memcpy) != Empty) {
printf("Buffer is not empty after clear...\n");
}
rb_destroy(&rb, free);
enum WriteResult wr = WriteOk;
enum ReadResult rr = ReadOk;
printf("Size of wr: %lu bytes.\n", sizeof(wr));
printf("Size of rr: %lu bytes.\n", sizeof(rr));
return 0;
}

124
ringbuf.c
View file

@ -1,12 +1,14 @@
/* SPDX-License-Identifier: MIT */
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
// #include <stdlib.h>
// #include <string.h>
#include "ringbuf.h"
#include <assert.h>
#include <stdint.h>
#ifdef DEBUG
#include <stdio.h>
#define DEBUG_PRINT(fmt, ...) printf(fmt, __VA_ARGS__)
#else
#define DEBUG_PRINT(fmt, ...)
@ -23,24 +25,31 @@ rb_init(struct RingBuf *rb, rb_size_t capacity, ALLOC_T malloc_fn,
rb->count = 0;
rb->write_head = rb->buffer;
rb->read_head = rb->buffer;
// Read from buffer at max position to force a segfault if theres an issue
DEBUG_PRINT("Reading from buffer at position %d\n",
rb->capacity * rb->struct_size);
void *top = rb->buffer + (rb->capacity * rb->struct_size);
DEBUG_PRINT("Buffer top successfully read at virtual address: %p\n", &top);
DEBUG_PRINT(
"Initialized ring buffer. Capacit: %d, struct_size: %d, total: %d\n",
rb->capacity, rb->struct_size, rb->capacity * rb->struct_size);
DEBUG_PRINT("Size of RB: %lu\n", sizeof(struct RingBuf));
}
void
rb_destroy(struct RingBuf *rb, void(free)())
rb_destroy(struct RingBuf *rb, FREE_T free_fn)
{
free(rb->buffer);
if(rb->buffer) { // Prevent double-free
free_fn(rb->buffer);
rb->buffer = NULL;
rb->buffer_end = NULL;
rb->struct_size = 0;
rb->capacity = 0;
rb->count = 0;
rb->write_head = NULL;
rb->read_head = NULL;
}
}
void
rb_clear(struct RingBuf *rb)
{
rb->count = 0;
rb->write_head = rb->buffer;
rb->read_head = rb->buffer;
}
enum WriteResult
@ -49,6 +58,8 @@ rb_push_back(struct RingBuf *rb, const void *item, MEMCPY_T memcpy_fn)
if(rb->count == rb->capacity)
return Full;
assert(rb->buffer != NULL);
memcpy_fn(rb->write_head, item, rb->struct_size);
// Advance the write head
@ -61,13 +72,54 @@ rb_push_back(struct RingBuf *rb, const void *item, MEMCPY_T memcpy_fn)
return WriteOk;
}
enum WriteResult
rb_push_many(struct RingBuf *rb, const void *items, MEMCPY_T memcpy_fn,
rb_size_t n)
{
if(rb->count + n > rb->capacity)
return Full; // Perhaps rename to InsufficientSpace
// If the write head will move past the end of the buffer
// we need to to the write in two steps.
void *end = (char *)rb->write_head + rb->struct_size * n;
if(end > rb->buffer_end) {
// Calculate the number of items that can be written in the first chunk
rb_size_t first_chunk = (char *)rb->buffer_end - (char *)rb->write_head;
DEBUG_PRINT("Multi-chunk write. First chunk: %ld\n", first_chunk);
// Write the first chunk
memcpy_fn(rb->write_head, items, rb->struct_size * first_chunk);
// Set the write head to the beginning of the buffer
rb->write_head = rb->buffer;
rb->count += first_chunk;
n -= first_chunk;
} else {
DEBUG_PRINT("Single-chunk write. No need to wrap around.%s\n", "");
}
DEBUG_PRINT("Writing %ld items\n", n);
memcpy_fn(rb->write_head, items, rb->struct_size * n);
if(rb->write_head == rb->buffer_end)
rb->write_head = rb->buffer;
// Advance the write head
rb->write_head = (char *)rb->write_head + rb->struct_size * n;
rb->count += n;
return WriteOk;
}
enum ReadResult
rb_pop_front(struct RingBuf *rb, void *item)
rb_pop_front(struct RingBuf *rb, void *item, MEMCPY_T memcpy_fn)
{
if(rb->count == 0)
return Empty;
memcpy(item, rb->read_head, rb->struct_size);
memcpy_fn(item, rb->read_head, rb->struct_size);
// Advance the read head
rb->read_head = (char *)rb->read_head + rb->struct_size;
@ -78,3 +130,37 @@ rb_pop_front(struct RingBuf *rb, void *item)
rb->count--;
return ReadOk;
}
#ifdef DEBUG
#include <stdio.h>
void
rb_debug_empty(struct RingBuf *rb)
{
int d;
if(rb->count == 0)
return;
printf("Debug Data: [");
while(rb_pop_front(rb, &d, memcpy) == ReadOk)
printf("%d,", d);
printf("\b]\n");
}
void
rb_debug_print(struct RingBuf *rb)
{
DEBUG_PRINT("============%s\n", "");
DEBUG_PRINT("Count %lu\n", rb->count);
DEBUG_PRINT("Capacity: %ld\n", rb->capacity);
DEBUG_PRINT("Left: %ld\n", rb->capacity - rb->count);
DEBUG_PRINT("Base addr:\t%p\n", rb->buffer);
DEBUG_PRINT("Read Head:\t%p (%ld:th position)\n", rb->read_head,
((rb->read_head) - (rb->buffer)) / rb->struct_size);
DEBUG_PRINT("Write Head:\t%p (%ld:th position)\n", rb->write_head,
((rb->write_head) - (rb->buffer)) / rb->struct_size);
DEBUG_PRINT("============%s\n", "");
}
#endif

130
ringbuf.h
View file

@ -2,42 +2,134 @@
#pragma once
#include <string.h>
#ifndef rb_size_t
#define rb_size_t int
#define rb_size_t size_t
#endif
/** Signatures of generic functions */
/** Signatures of allocators */
typedef void *(*ALLOC_T)(rb_size_t);
/** Signature of memcpy */
typedef void *(*MEMCPY_T)(void *, const void *, rb_size_t);
/** Signature of free */
typedef void (*FREE_T)(void *);
/**
* Ring buffer, also known as circular buffer.
* @brief Ring buffer, also known as circular buffer.
*/
struct RingBuf {
rb_size_t struct_size; /* Size of the struct */
rb_size_t capacity; /* The physical capacity of the entire ringbuf */
rb_size_t count; /* The number of elements in the buffer */
void *write_head; /* Address of the write head */
void *read_head; /* Address of the read head */
void *buffer; /* The actual data */
void *buffer_end; /* The end of the buffer */
rb_size_t struct_size; /** Size of the struct */
rb_size_t capacity; /** The physical capacity of the entire ringbuf */
rb_size_t count; /** The number of elements in the buffer */
void *write_head; /** Address of the write head */
void *read_head; /** Address of the read head */
void *buffer; /** The actual data */
void *buffer_end; /** The end of the buffer */
} __attribute__((packed));
// TODO: Perhaps unify these to RBResult?
/*
* Considerations: One hot encoding for the enum values
* (8 bit each with -fshort-enums)
*/
enum WriteResult { Full, WriteOk }; /** Result of a write */
enum ReadResult { Empty, ReadOk }; /** Result of a read */
/** Result of a write */
enum WriteResult {
WriteOk, /** The write was successful */
Full, /** The buffer is full */
InsufficientSpace /** There is not enough space to write */
};
/** Initialize the ring buffer */
void rb_init(struct RingBuf *rb, rb_size_t capacity, void *(*alloc)(rb_size_t),
/** Result of a read */
enum ReadResult {
Empty, /** The buffer is empty */
ReadOk, /** The read was successful */
InsufficientData /** There is not enough data to read */
};
/**
* @brief Initialize the ring buffer
* @param rb The ring buffer to initialize
* @param capacity The capacity of the ring buffer
* @param alloc The allocator function
* @param struct_size The size of the struct
* @return void
*/
void rb_init(struct RingBuf *rb, rb_size_t capacity, ALLOC_T alloc,
rb_size_t struct_size);
/** Insert data to the ring buffer */
/**
* @brief Clear the ring buffer
* @details This function will reset the read and write heads to the beginning
* of the buffer, and set the count to 0. It will not free the buffer.
* @param rb The ring buffer
*/
void rb_clear(struct RingBuf *rb);
/**
* @brief Insert data to the ring buffer
* @param rb The ring buffer
* @param item The item to insert
* @param memcpy_fn The memcpy function
* @return WriteResult
*/
enum WriteResult rb_push_back(struct RingBuf *rb, const void *item,
MEMCPY_T memcpy_fn);
/** Read data from the ring buffer */
enum ReadResult rb_pop_front(struct RingBuf *rb, void *item);
/**
* @brief Insert multiple data to the ring buffer
*
* @details This function is more efficient than calling rb_push_back multiple
* times. It only advances the write head once, and attempts to write all the
* memory in one go.
*
* If n is greater than the capacity, it will return Full.
* If the full write will overflow, it will wrap around.
*
* If the buffer is full, it will return Full and not write
* anything.
*
* @param rb The ring buffer
* @param items The items to insert
* @param memcpy_fn The memcpy function
* @param n The number of items to insert
* @return WriteResult
*/
enum WriteResult rb_push_many(struct RingBuf *rb, const void *items,
MEMCPY_T memcpy_fn, rb_size_t n);
/** Free the ring buffer */
/**
* @brief Read data from the ring buffer
* @param rb The ring buffer
* @param item The item to read into
* @return ReadResult
*/
enum ReadResult rb_pop_front(struct RingBuf *rb, void *item,
MEMCPY_T memcpy_fn);
/**
* @brief Free the ring buffer
*
* @details This function is idempotent, consecutive calls will not result in a
* double free.
*
* @param rb The ring buffer
* @param free The free function
*/
void rb_destroy(struct RingBuf *rb, void(free)());
#ifdef DEBUG
/**
* @brief Debug print
*/
void rb_debug_print(struct RingBuf *rb);
/**
* @brief Debug print and empty the ringbuf
*/
void rb_debug_empty(struct RingBuf *rb);
#endif

101
test/test_ringbuf.c Normal file
View file

@ -0,0 +1,101 @@
#include <setjmp.h>
#include <stdarg.h>
#include <stddef.h>
#include <cmocka.h>
#include <stdio.h>
#include <stdlib.h>
#include "ringbuf.h"
/** Tests initialization */
static void
test_rb_init_empty(void **state) {
struct RingBuf rb;
rb_size_t capacity = 10;
rb_size_t struct_size = sizeof(int);
rb_init(&rb, capacity, malloc, struct_size);
assert_int_equal(rb.capacity, capacity);
assert_int_equal(rb.count, 0);
}
/** Tests push_back */
static void
test_rb_push_back(void **state) {
struct RingBuf rb;
rb_size_t capacity = 10;
rb_size_t struct_size = sizeof(int);
rb_init(&rb, capacity, malloc, struct_size);
int data = 10;
enum WriteResult wr = rb_push_back(&rb, (void *)&data, memcpy);
assert_int_equal(wr, WriteOk);
assert_int_equal(rb.capacity, capacity);
assert_int_equal(rb.count, 1);
}
/** Tests the destroy function */
static void
test_rb_init_destroy(void **state) {
struct RingBuf rb = { 0, 0, 0, NULL, NULL, NULL, NULL };
rb_size_t capacity = 10;
rb_size_t struct_size = sizeof(int);
rb_init(&rb, capacity, malloc, struct_size);
int data = 10;
rb_push_back(&rb, (void *)&data, memcpy);
assert_int_equal(rb.capacity, capacity);
assert_int_equal(rb.count, 1);
rb_destroy(&rb, free);
assert_null(rb.buffer);
}
/** Tests fill, but not overflow/wrap-around */
static void
test_rb_push_back_fill(void **state) {
struct RingBuf rb = { 0, 0, 0, NULL, NULL, NULL, NULL };
rb_size_t capacity = 10;
rb_size_t struct_size = sizeof(int);
rb_init(&rb, capacity, malloc, struct_size);
const int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// Insert them all sequentially
for(int i = 0; rb_push_back(&rb, &arr[i], memcpy) == WriteOk; i++);
assert_int_equal(rb.count, 10);
assert_int_equal(rb.capacity, capacity);
// Read them out and assert expected value
for(int i = 0, d = __INT_MAX__; rb_pop_front(&rb, &d, memcpy) == ReadOk;
i++) {
assert_int_equal(d, arr[i]);
}
assert_int_equal(rb.capacity, capacity);
assert_int_equal(rb.count, 0);
rb_destroy(&rb, free);
assert_null(rb.buffer);
}
int
main(void) {
const struct CMUnitTest tests[] = {
cmocka_unit_test(test_rb_init_empty),
cmocka_unit_test(test_rb_push_back),
cmocka_unit_test(test_rb_init_destroy),
cmocka_unit_test(test_rb_push_back_fill),
};
return cmocka_run_group_tests(tests, NULL, NULL);
}