pebble/tests/fw/util/test_shared_circular_buffer.c
2025-01-27 11:38:16 -08:00

373 lines
15 KiB
C

/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "util/shared_circular_buffer.h"
#include "clar.h"
#include <string.h>
#include "stubs_passert.h"
// Stubs
///////////////////////////////////////////////////////////
int g_pbl_log_level = 0;
void pbl_log(int level, const char* src_filename, int src_line_number, const char* fmt, ...) { }
void test_shared_circular_buffer__initialize(void) {
}
void test_shared_circular_buffer__cleanup(void) {
}
static void prv_read_and_consume(SharedCircularBuffer *buffer, SharedCircularBufferClient *client,
uint8_t *data, uint32_t num_bytes) {
while (num_bytes) {
uint16_t chunk;
const uint8_t *read_ptr;
cl_assert(shared_circular_buffer_read(buffer, client, num_bytes, &read_ptr, &chunk));
memcpy(data, read_ptr, chunk);
cl_assert(shared_circular_buffer_consume(buffer, client, chunk));
buffer += chunk;
num_bytes -= chunk;
}
}
void test_shared_circular_buffer__one_client(void) {
SharedCircularBuffer buffer;
uint8_t storage[9];
shared_circular_buffer_init(&buffer, storage, sizeof(storage));
const uint8_t* out_buffer;
uint16_t out_length;
// Add a client
SharedCircularBufferClient client = (SharedCircularBufferClient) {};
shared_circular_buffer_add_client(&buffer, &client);
// We should start out empty
cl_assert(!shared_circular_buffer_read(&buffer, &client, 1, &out_buffer, &out_length));
cl_assert(shared_circular_buffer_write(&buffer, (uint8_t*) "123", 3, false));
cl_assert_equal_i(shared_circular_buffer_get_write_space_remaining(&buffer), 5);
cl_assert(shared_circular_buffer_write(&buffer, (uint8_t*) "456", 3, false));
cl_assert_equal_i(shared_circular_buffer_get_write_space_remaining(&buffer), 2);
cl_assert(!shared_circular_buffer_write(&buffer, (uint8_t*) "789", 3, false)); // too big
cl_assert_equal_i(shared_circular_buffer_get_write_space_remaining(&buffer), 2);
cl_assert(shared_circular_buffer_read(&buffer, &client, 4, &out_buffer, &out_length));
cl_assert_equal_i(out_length, 4);
cl_assert(memcmp(out_buffer, "1234", 4) == 0);
cl_assert_equal_i(shared_circular_buffer_get_write_space_remaining(&buffer), 2);
cl_assert(shared_circular_buffer_consume(&buffer, &client, 4));
cl_assert_equal_i(shared_circular_buffer_get_write_space_remaining(&buffer), 6);
// Now there's just 56 in the buffer. Fill it to the brim
cl_assert(shared_circular_buffer_write(&buffer, (uint8_t*) "789", 3, false));
cl_assert_equal_i(shared_circular_buffer_get_write_space_remaining(&buffer), 3);
cl_assert(shared_circular_buffer_write(&buffer, (uint8_t*) "abc", 3, false));
cl_assert_equal_i(shared_circular_buffer_get_write_space_remaining(&buffer), 0);
cl_assert(!shared_circular_buffer_write(&buffer, (uint8_t*) "d", 1, false)); // too full
cl_assert_equal_i(shared_circular_buffer_get_write_space_remaining(&buffer), 0);
// Try a wrapped read
cl_assert(shared_circular_buffer_read(&buffer, &client, 6, &out_buffer, &out_length));
cl_assert_equal_i(out_length, 5);
cl_assert(memcmp(out_buffer, (uint8_t*) "56789", 5) == 0);
cl_assert(shared_circular_buffer_consume(&buffer, &client, 5));
// Get the rest of the wrapped read
cl_assert(shared_circular_buffer_read(&buffer, &client, 1, &out_buffer, &out_length));
cl_assert_equal_i(out_length, 1);
cl_assert(memcmp(out_buffer, (uint8_t*) "a", 1) == 0);
cl_assert(shared_circular_buffer_consume(&buffer, &client, 1));
// Consume one without reading it
cl_assert(shared_circular_buffer_consume(&buffer, &client, 1));
// Read the last little bit
cl_assert(shared_circular_buffer_read(&buffer, &client, 1, &out_buffer, &out_length));
cl_assert_equal_i(out_length, 1);
cl_assert(memcmp(out_buffer, (uint8_t*) "c", 1) == 0);
cl_assert(shared_circular_buffer_consume(&buffer, &client, 1));
// And we should be empty
cl_assert(!shared_circular_buffer_read(&buffer, &client, 1, &out_buffer, &out_length));
cl_assert(!shared_circular_buffer_consume(&buffer, &client, 1));
}
void test_shared_circular_buffer__two_clients(void) {
SharedCircularBuffer buffer;
uint8_t storage[9];
shared_circular_buffer_init(&buffer, storage, sizeof(storage));
const uint8_t* out_buffer;
uint16_t out_length;
// Add clients
SharedCircularBufferClient client1 = (SharedCircularBufferClient) {};
shared_circular_buffer_add_client(&buffer, &client1);
SharedCircularBufferClient client2 = (SharedCircularBufferClient) {};
shared_circular_buffer_add_client(&buffer, &client2);
// We should start out empty
cl_assert(!shared_circular_buffer_read(&buffer, &client1, 1, &out_buffer, &out_length));
cl_assert(!shared_circular_buffer_read(&buffer, &client2, 1, &out_buffer, &out_length));
// Fill with data
cl_assert(shared_circular_buffer_write(&buffer, (uint8_t*) "123456", 6, false));
// Read different amounts from each client
cl_assert(shared_circular_buffer_read(&buffer, &client1, 4, &out_buffer, &out_length));
cl_assert(shared_circular_buffer_consume(&buffer, &client1, 4));
cl_assert(memcmp(out_buffer, "1234", 4) == 0);
cl_assert_equal_i(shared_circular_buffer_get_write_space_remaining(&buffer), 2);
cl_assert(shared_circular_buffer_read(&buffer, &client2, 4, &out_buffer, &out_length));
cl_assert(shared_circular_buffer_consume(&buffer, &client2, 4));
cl_assert(memcmp(out_buffer, "1234", 4) == 0);
cl_assert_equal_i(shared_circular_buffer_get_write_space_remaining(&buffer), 6);
// Make client2 fall behind
cl_assert(shared_circular_buffer_write(&buffer, (uint8_t*) "abcdef", 6, false));
cl_assert_equal_i(shared_circular_buffer_get_write_space_remaining(&buffer), 0);
cl_assert(shared_circular_buffer_read(&buffer, &client1, 3, &out_buffer, &out_length));
cl_assert_equal_i(out_length, 3);
cl_assert(memcmp(out_buffer, "56a", 3) == 0);
cl_assert(shared_circular_buffer_consume(&buffer, &client1, 3));
cl_assert(shared_circular_buffer_read(&buffer, &client1, 2, &out_buffer, &out_length));
cl_assert_equal_i(out_length, 2);
cl_assert(memcmp(out_buffer, "bc", 2) == 0);
cl_assert(shared_circular_buffer_consume(&buffer, &client1, 2));
cl_assert_equal_i(shared_circular_buffer_get_write_space_remaining(&buffer), 0);
// Should fail, not enough room because client 2 is full
cl_assert(!shared_circular_buffer_write(&buffer, (uint8_t*) "gh", 2, false));
// This should pass and reset client 2's read index
cl_assert(shared_circular_buffer_write(&buffer, (uint8_t*) "gh", 2, true));
cl_assert_equal_i(shared_circular_buffer_get_write_space_remaining(&buffer), 3);
cl_assert_equal_i(shared_circular_buffer_get_read_space_remaining(&buffer, &client1), 5);
cl_assert_equal_i(shared_circular_buffer_get_read_space_remaining(&buffer, &client2), 2);
// Make client2 fall behind again
cl_assert(shared_circular_buffer_write(&buffer, (uint8_t*) "abc", 3, false));
cl_assert_equal_i(shared_circular_buffer_get_write_space_remaining(&buffer), 0);
cl_assert(shared_circular_buffer_read(&buffer, &client1, 3, &out_buffer, &out_length));
cl_assert_equal_i(out_length, 3);
cl_assert(memcmp(out_buffer, "def", 3) == 0);
cl_assert(shared_circular_buffer_consume(&buffer, &client1, 3));
cl_assert(shared_circular_buffer_read(&buffer, &client1, 2, &out_buffer, &out_length));
cl_assert_equal_i(out_length, 2);
cl_assert(memcmp(out_buffer, "gh", 2) == 0);
cl_assert(shared_circular_buffer_consume(&buffer, &client1, 2));
cl_assert_equal_i(shared_circular_buffer_get_read_space_remaining(&buffer, &client1), 3);
cl_assert_equal_i(shared_circular_buffer_get_read_space_remaining(&buffer, &client2), 5);
cl_assert_equal_i(shared_circular_buffer_get_write_space_remaining(&buffer), 3);
// If we remove client2, it should create more space
shared_circular_buffer_remove_client(&buffer, &client2);
cl_assert_equal_i(shared_circular_buffer_get_write_space_remaining(&buffer), 5);
}
void test_shared_circular_buffer__corner_case(void) {
SharedCircularBuffer buffer;
uint8_t storage[4];
shared_circular_buffer_init(&buffer, storage, sizeof(storage));
// Add a client
SharedCircularBufferClient client = (SharedCircularBufferClient) {};
shared_circular_buffer_add_client(&buffer, &client);
// We should start out empty
cl_assert_equal_i(shared_circular_buffer_get_read_space_remaining(&buffer, &client), 0);
// Write 2
cl_assert(shared_circular_buffer_write(&buffer, storage, 2, false));
cl_assert_equal_i(shared_circular_buffer_get_read_space_remaining(&buffer, &client), 2);
// Consume it
prv_read_and_consume(&buffer, &client, storage, 2);
cl_assert_equal_i(shared_circular_buffer_get_read_space_remaining(&buffer, &client), 0);
// Write 2 more
cl_assert(shared_circular_buffer_write(&buffer, storage, 2, false));
cl_assert_equal_i(shared_circular_buffer_get_read_space_remaining(&buffer, &client), 2);
// Consume it
prv_read_and_consume(&buffer, &client, storage, 2);
cl_assert_equal_i(shared_circular_buffer_get_read_space_remaining(&buffer, &client), 0);
}
void test_shared_circular_buffer__subsampling_2of5(void) {
SharedCircularBuffer buffer;
uint16_t item_size = 2;
uint8_t storage[12*item_size];
uint8_t out_buffer[12*item_size];
uint16_t items_read;
shared_circular_buffer_init(&buffer, storage, sizeof(storage));
SubsampledSharedCircularBufferClient client = {};
shared_circular_buffer_add_subsampled_client(&buffer, &client, 2, 5);
cl_assert(shared_circular_buffer_write(
&buffer, (uint8_t*)"0a1b2c3d4e5f6g7h8i", 9*item_size, false));
items_read = shared_circular_buffer_read_subsampled(
&buffer, &client, item_size, out_buffer, 100);
cl_assert_equal_i(items_read, 4);
cl_assert_equal_m(out_buffer, "0a3d5f8i", 8);
cl_assert(shared_circular_buffer_write(
&buffer, (uint8_t*)"9j0k1m2n3o4p5q", 7*item_size, false));
items_read = shared_circular_buffer_read_subsampled(
&buffer, &client, item_size, out_buffer, 2);
cl_assert_equal_i(items_read, 2);
cl_assert_equal_m(out_buffer, "0k3o", 4);
items_read = shared_circular_buffer_read_subsampled(
&buffer, &client, item_size, out_buffer, 2);
cl_assert_equal_i(items_read, 1);
cl_assert_equal_m(out_buffer, "5q", 2);
}
void test_shared_circular_buffer__subsampling_1of3(void) {
SharedCircularBuffer buffer;
uint16_t item_size = 2;
uint8_t storage[12*item_size];
uint8_t out_buffer[12*item_size];
uint16_t items_read;
// Init
shared_circular_buffer_init(&buffer, storage, sizeof(storage));
SubsampledSharedCircularBufferClient client = {};
shared_circular_buffer_add_subsampled_client(&buffer, &client, 1, 3);
cl_assert(shared_circular_buffer_write(
&buffer, (uint8_t*)"0a1b2c3d4e5f6g7h8i9j", 10*item_size, false));
items_read = shared_circular_buffer_read_subsampled(
&buffer, &client, item_size, out_buffer, 100);
cl_assert_equal_i(items_read, 4);
cl_assert_equal_m(out_buffer, "0a3d6g9j", 8);
cl_assert(shared_circular_buffer_write(
&buffer, (uint8_t*)"0k1m2n", 3*item_size, false));
items_read = shared_circular_buffer_read_subsampled(
&buffer, &client, item_size, out_buffer, 100);
cl_assert_equal_i(items_read, 1);
cl_assert_equal_m(out_buffer, "2n", 2);
}
void test_shared_circular_buffer__subsampling_1of1(void) {
SharedCircularBuffer buffer;
uint16_t item_size = 2;
uint8_t storage[12*item_size];
uint8_t out_buffer[12*item_size];
uint16_t items_read;
// Init
shared_circular_buffer_init(&buffer, storage, sizeof(storage));
SubsampledSharedCircularBufferClient client = {};
shared_circular_buffer_add_subsampled_client(&buffer, &client, 3, 3);
// No subsampling
cl_assert(shared_circular_buffer_write(
&buffer, (uint8_t*)"0a1b2c3d4e5f6g7h8i9j", 10*item_size, false));
items_read = shared_circular_buffer_read_subsampled(
&buffer, &client, item_size, out_buffer, 9);
cl_assert_equal_i(items_read, 9);
cl_assert_equal_m(out_buffer, "0a1b2c3d4e5f6g7h8i", 18);
cl_assert(shared_circular_buffer_write(
&buffer, (uint8_t*)"0k1m", 2*item_size, false));
items_read = shared_circular_buffer_read_subsampled(
&buffer, &client, item_size, out_buffer, 100);
cl_assert_equal_i(items_read, 3);
cl_assert_equal_m(out_buffer, "9j0k1m", 6);
}
void test_shared_circular_buffer__subsampling_variable_ratio(void) {
SharedCircularBuffer buffer;
uint16_t item_size = 2;
uint8_t storage[12*item_size];
uint8_t out_buffer[12*item_size];
shared_circular_buffer_init(&buffer, storage, sizeof(storage));
SubsampledSharedCircularBufferClient client = {};
shared_circular_buffer_add_subsampled_client(&buffer, &client, 1, 2);
cl_assert(shared_circular_buffer_write(
&buffer, (uint8_t*)"0a1b2c3d4e5f6g7h8i9j", 10*item_size, false));
// Consume "0a1b2c3d4e"
cl_assert_equal_i(shared_circular_buffer_read_subsampled(
&buffer, &client, item_size, out_buffer, 3), 3);
cl_assert_equal_m(out_buffer, "0a2c4e", 6);
subsampled_shared_circular_buffer_client_set_ratio(&client, 2, 3);
// Consume "5f6g7h8i"
cl_assert_equal_i(shared_circular_buffer_read_subsampled(
&buffer, &client, item_size, out_buffer, 3), 3);
// Normally the next read would skip 5f, but changing the ratio resets the
// subsampling state and the first sample after resetting the state is never
// skipped.
cl_assert_equal_m(out_buffer, "5f7h8i", 6);
}
void test_shared_circular_buffer__subsampling_set_ratio_is_idempotent(void) {
SharedCircularBuffer buffer;
uint16_t item_size = 2;
uint8_t storage[12*item_size];
uint8_t out_buffer[12*item_size];
shared_circular_buffer_init(&buffer, storage, sizeof(storage));
SubsampledSharedCircularBufferClient client = {};
shared_circular_buffer_add_subsampled_client(&buffer, &client, 1, 2);
cl_assert(shared_circular_buffer_write(
&buffer, (uint8_t*)"0a1b2c3d4e5f6g7h8i9j", 10*item_size, false));
// Consume "0a1b2c3d4e"
cl_assert_equal_i(shared_circular_buffer_read_subsampled(
&buffer, &client, item_size, out_buffer, 3), 3);
cl_assert_equal_m(out_buffer, "0a2c4e", 6);
// This should be a no-op. the "5f" sample should still be skipped on the next
// read.
subsampled_shared_circular_buffer_client_set_ratio(&client, 1, 2);
cl_assert_equal_i(shared_circular_buffer_read_subsampled(
&buffer, &client, item_size, out_buffer, 1), 1);
cl_assert_equal_m(out_buffer, "6g", 2);
}