From c962913ed9e74f2da375d61420cb6b1e7fdd1731 Mon Sep 17 00:00:00 2001 From: Przemyslaw Stekiel Date: Tue, 7 Nov 2017 09:43:09 +0100 Subject: [PATCH 1/2] Add assertion to make CounterType consistent with BufferSize. CounterType is used to define type of _head and _tail counters. This may cause the following problems: - counters are used as array indexes so only unsigned types should be used for counters, - CounterType must be consistent with BufferSize and BufferSize is of uint32_t type (i.e. Circular Buffer with the following parameters: BufferSize = 1000, CounterType = unsigned char will not work properly). --- platform/CircularBuffer.h | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/platform/CircularBuffer.h b/platform/CircularBuffer.h index c6de50f066..ab638462bd 100644 --- a/platform/CircularBuffer.h +++ b/platform/CircularBuffer.h @@ -19,6 +19,23 @@ #include "platform/mbed_critical.h" namespace mbed { + +namespace internal { +/* Detect if CounterType of the Circular buffer is of unsigned type. */ +template +struct is_unsigned { static const bool value = false; }; +template<> +struct is_unsigned { static const bool value = true; }; +template<> +struct is_unsigned { static const bool value = true; }; +template<> +struct is_unsigned { static const bool value = true; }; +template<> +struct is_unsigned { static const bool value = true; }; +template<> +struct is_unsigned { static const bool value = true; }; +}; + /** \addtogroup platform */ /** @{*/ /** @@ -29,11 +46,22 @@ namespace mbed { /** Templated Circular buffer class * * @note Synchronization level: Interrupt safe + * @note CounterType must be unsigned and consistent with BufferSize */ template class CircularBuffer { public: CircularBuffer() : _head(0), _tail(0), _full(false) { + MBED_STATIC_ASSERT( + internal::is_unsigned::value, + "CounterType must be unsigned" + ); + + MBED_STATIC_ASSERT( + (sizeof(CounterType) >= sizeof(uint32_t)) || + (BufferSize < (((uint64_t) 1) << (sizeof(CounterType) * 8))), + "Invalid BufferSize for the CounterType" + ); } ~CircularBuffer() { @@ -140,4 +168,3 @@ private: } #endif - From a488424d266d4d7e9b61bc6164beb41ce62e490e Mon Sep 17 00:00:00 2001 From: Przemyslaw Stekiel Date: Tue, 14 Nov 2017 11:57:11 +0100 Subject: [PATCH 2/2] Add CircularBuffer test. --- .../CircularBuffer/main.cpp | 469 ++++++++++++++++++ 1 file changed, 469 insertions(+) create mode 100644 TESTS/mbedmicro-rtos-mbed/CircularBuffer/main.cpp diff --git a/TESTS/mbedmicro-rtos-mbed/CircularBuffer/main.cpp b/TESTS/mbedmicro-rtos-mbed/CircularBuffer/main.cpp new file mode 100644 index 0000000000..2191ee91b7 --- /dev/null +++ b/TESTS/mbedmicro-rtos-mbed/CircularBuffer/main.cpp @@ -0,0 +1,469 @@ +/* mbed Microcontroller Library + * Copyright (c) 2017 ARM Limited + * + * 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 "utest/utest.h" +#include "unity/unity.h" +#include "greentea-client/test_env.h" + +#include "mbed.h" + +using namespace utest::v1; + +/* Structure for complex type. */ +typedef struct +{ + int a; + char b; + int c; +} COMPLEX_TYPE; + +/* Function to check if complex type object holds specified values.*/ +bool comp_is_equal(COMPLEX_TYPE *object, int a, char b, int c) +{ + if (object->a == a && object->b == b && object->c == c) { + return true; + } + + return false; +} + +/* Function to set complex type object fields.*/ +void comp_set(COMPLEX_TYPE *object, int a, char b, int c) +{ + object->a = a; + object->b = b; + object->c = c; +} + +/* Test circular buffer - input does not exceed capacity. + * + * Given is a circular buffer with the capacity equal to N (BufferSize). + * When circular buffer is filled with N elements. + * Then all elements are read from the circular buffer in the FIFO order. + * + */ +template +void test_input_does_not_exceed_capacity_push_max_pop_max() +{ + CircularBuffer cb; + T data = 0; + + for (uint32_t i = 0; i < BufferSize; i++) { + data = (0xAA + i); + cb.push(data); + TEST_ASSERT_EQUAL(i + 1, cb.size()); + } + + for (uint32_t i = 0; i < BufferSize; i++) { + TEST_ASSERT_TRUE(cb.pop(data)); + TEST_ASSERT_EQUAL(0xAA + i, data); + TEST_ASSERT_EQUAL(BufferSize - i - 1, cb.size()); + } +} + +/* Test circular buffer - input does not exceed capacity. + * + * Given is a circular buffer with the capacity equal to N (BufferSize). + * When circular buffer is filled as follows: (2 * N - 2) times 2 elements are pushed and 1 element is popped. + * Then all elements are read from the circular buffer in the FIFO order. + * + */ +template +void test_input_does_not_exceed_capacity_push_2_pop_1() +{ + CircularBuffer cb; + static const int num_of_elem_push = (2 * BufferSize - 2); + T push_buffer = 0; + T pop_buffer = 0; + + /* Push 2 elements and pop one element in each cycle. */ + for (uint32_t i = 0; i < num_of_elem_push; i += 2) { + push_buffer = (0xAA + i); + cb.push(push_buffer); + push_buffer++; + cb.push(push_buffer); + TEST_ASSERT_EQUAL(i / 2 + 2, cb.size()); + + TEST_ASSERT_TRUE(cb.pop(pop_buffer)); + TEST_ASSERT_EQUAL(0xAA + i / 2, pop_buffer); + TEST_ASSERT_EQUAL(i / 2 + 1, cb.size()); + } + + /* Pop the rest. */ + for (uint32_t i = 0; i < (num_of_elem_push / 2); i++) { + TEST_ASSERT_TRUE(cb.pop(pop_buffer)); + TEST_ASSERT_EQUAL(0xAA + num_of_elem_push / 2 + i, pop_buffer); + TEST_ASSERT_EQUAL(num_of_elem_push / 2 - 1 - i, cb.size()); + + } +} + +/* Test circular buffer - input exceeds capacity. + * + * Given is a circular buffer with the capacity equal to N (BufferSize). + * When circular buffer is filled with N + 1 elements. + * Then first pushed element is lost (overwritten by the last element) and + * elements are read from the circular buffer in the FIFO order. + * + */ +template +void test_input_exceeds_capacity_push_max_plus_1_pop_max() +{ + CircularBuffer cb; + static const int num_of_elem_push = (BufferSize + 1); + T data = 0; + + for (uint32_t i = 0; i < num_of_elem_push; i++) { + data = (0xAA + i); + cb.push(data); + if (i < BufferSize) { + TEST_ASSERT_EQUAL(i + 1, cb.size()); + } else { + TEST_ASSERT_EQUAL(BufferSize, cb.size()); + } + + } + + for (uint32_t i = 0; i < (BufferSize - 1); i++) { + TEST_ASSERT_TRUE(cb.pop(data)); + TEST_ASSERT_EQUAL(0xAA + i + 1, data); + TEST_ASSERT_EQUAL(BufferSize - 1 - i, cb.size()); + } + + /* First element should be overwritten. */ + TEST_ASSERT_TRUE(cb.pop(data)); + TEST_ASSERT_EQUAL((0xAA + num_of_elem_push - 1), data); + TEST_ASSERT_EQUAL(0, cb.size()); +} + +/* Test circular buffer - input exceeds capacity. + * + * Given is a circular buffer with the capacity equal to N (BufferSize). + * When circular buffer is filled as follows: (2 * N) times 2 elements are pushed and 1 element is popped. + * Then first pushed element is lost (overwritten by the last element) and + * elements are read from the circular buffer in the FIFO order. + * + */ +template +void test_input_exceeds_capacity_push_2_pop_1() +{ + CircularBuffer cb; + static const int num_of_elem_push = (2 * BufferSize); + T push_buffer = 0; + T pop_buffer = 0; + + /* Push 2 elements and pop one element in each cycle. */ + for (uint32_t i = 0; i < num_of_elem_push; i += 2) { + push_buffer = (0xAA + i); + cb.push(push_buffer); + push_buffer++; + cb.push(push_buffer); + if ((i / 2 + 1) < BufferSize) { + TEST_ASSERT_EQUAL(i / 2 + 2, cb.size()); + } else { + TEST_ASSERT_EQUAL(BufferSize, cb.size()); + } + + TEST_ASSERT_TRUE(cb.pop(pop_buffer)); + if ((i / 2 + 1) < BufferSize) { + TEST_ASSERT_EQUAL(i / 2 + 1, cb.size()); + } else { + TEST_ASSERT_EQUAL(BufferSize - 1, cb.size()); + } + + /* First element has been overwritten. */ + if (i == (num_of_elem_push - 2)) { + TEST_ASSERT_EQUAL(0xAA + i / 2 + 1, pop_buffer); + } else { + TEST_ASSERT_EQUAL(0xAA + i / 2, pop_buffer); + } + } + + /* Pop the rest - one element has been overwritten. */ + for (uint32_t i = 0; i < (num_of_elem_push / 2 - 1); i++) { + TEST_ASSERT_TRUE(cb.pop(pop_buffer)); + TEST_ASSERT_EQUAL(0xAA + num_of_elem_push / 2 + i + 1, pop_buffer); + } +} + +/* Test circular buffer - input exceeds capacity (complex type). + * + * Given is a circular buffer with the capacity equal to N (BufferSize). + * When circular buffer is filled as follows: (2 * N) times 2 elements are pushed and 1 element is popped. + * Then first pushed element is lost (overwritten by the last element) and + * elements are read from the circular buffer in the FIFO order. + * + */ +template +void test_input_exceeds_capacity_push_2_pop_1_complex_type() +{ + CircularBuffer cb; + static const int num_of_elem_push = (2 * BufferSize); + COMPLEX_TYPE push_buffer = {0}; + COMPLEX_TYPE pop_buffer = {0}; + + /* Push 2 elements and pop one element in each cycle. */ + for (uint32_t i = 0; i < num_of_elem_push; i += 2) { + comp_set(&push_buffer, 0xAA + i, 0xBB + i, 0xCC + i); + cb.push(push_buffer); + comp_set(&push_buffer, 0xAA + i + 1, 0xBB + i + 1, 0xCC + i + 1); + cb.push(push_buffer); + if ((i / 2 + 1) < BufferSize) { + TEST_ASSERT_EQUAL(i / 2 + 2, cb.size()); + } else { + TEST_ASSERT_EQUAL(BufferSize, cb.size()); + } + + TEST_ASSERT_TRUE(cb.pop(pop_buffer)); + if ((i / 2 + 1) < BufferSize) { + TEST_ASSERT_EQUAL(i / 2 + 1, cb.size()); + } else { + TEST_ASSERT_EQUAL(BufferSize - 1, cb.size()); + } + + /* First element has been overwritten. */ + if (i == (num_of_elem_push - 2)) { + const bool result = comp_is_equal(&pop_buffer, 0xAA + 1 + i / 2, 0xBB + 1 + i / 2, 0xCC + 1 + i / 2); + TEST_ASSERT_TRUE(result); + } else { + const bool result = comp_is_equal(&pop_buffer, 0xAA + i / 2, 0xBB + i / 2, 0xCC + i / 2); + TEST_ASSERT_TRUE(result); + } + } + + /* Pop the rest - one element has been overwritten. */ + for (uint32_t i = 0; i < (num_of_elem_push / 2 - 1); i++) { + TEST_ASSERT_TRUE(cb.pop(pop_buffer)); + const bool result = comp_is_equal(&pop_buffer, 0xAA + num_of_elem_push / 2 + i + 1, + 0xBB + num_of_elem_push / 2 + i + 1, 0xCC + num_of_elem_push / 2 + i + 1); + TEST_ASSERT_TRUE(result); + } +} + +/* Test circular buffer - test pop(), empty(), full(), size() after CircularBuffer creation. + * + * Given is a circular buffer. + * When circular buffer is created. + * Then circular buffer is empty: + * - empty() returns true, + * - pop() function returns false, + * - full() function returns false, + * - size() function returns 0, + * + */ +void test_pop_empty_full_size_after_creation() +{ + CircularBuffer cb; + int data = 0; + + TEST_ASSERT_TRUE(cb.empty()); + TEST_ASSERT_FALSE(cb.pop(data)); + TEST_ASSERT_FALSE(cb.full()); + TEST_ASSERT_EQUAL(0, cb.size()); +} + +/* Test circular buffer - test empty() function. + * + * Given is a circular buffer with the capacity equal to N (BufferSize). + * When operations on circular buffer are performed (push, pop). + * Then empty() function returns true if buffer is empty, false otherwise. + * + */ +template +void test_empty() +{ + CircularBuffer cb; + T data = 0; + + /* Push max elements. */ + for (uint32_t i = 0; i < BufferSize; i++) { + cb.push(data); + TEST_ASSERT_FALSE(cb.empty()); + } + + /* Push next 2*BufferSize elements (overwrite entries). */ + for (uint32_t i = 0; i < (2 * BufferSize); i++) { + cb.push(data); + TEST_ASSERT_FALSE(cb.empty()); + } + + /* Pop (BufferSize - 1) elements (leave one). */ + for (uint32_t i = 0; i < (BufferSize - 1); i++) { + TEST_ASSERT_TRUE(cb.pop(data)); + TEST_ASSERT_FALSE(cb.empty()); + } + + /* Take one which is left. */ + TEST_ASSERT_TRUE(cb.pop(data)); + TEST_ASSERT_TRUE(cb.empty()); + + /* Add one element to the empty buffer. */ + cb.push(data); + TEST_ASSERT_FALSE(cb.empty()); +} + +/* Test circular buffer - test full() function. + * + * Given is a circular buffer with the capacity equal to N (BufferSize). + * When operations on circular buffer are performed (push, pop). + * Then full() function returns true if buffer is full, false otherwise. + * + */ +template +void test_full() +{ + CircularBuffer cb; + T data = 0; + + /* Push max elements - 1. */ + for (uint32_t i = 0; i < (BufferSize - 1); i++) { + cb.push(data); + TEST_ASSERT_FALSE(cb.full()); + } + + /* Push one element - buffer should be full now. */ + cb.push(data); + TEST_ASSERT_TRUE(cb.full()); + + /* Push next 2*BufferSize elements (overwrite entries). */ + for (uint32_t i = 0; i < (2 * BufferSize); i++) { + cb.push(data); + TEST_ASSERT_TRUE(cb.full()); + } + + /* Pop all elements. */ + for (uint32_t i = 0; i < BufferSize; i++) { + TEST_ASSERT_TRUE(cb.pop(data)); + TEST_ASSERT_FALSE(cb.full()); + } +} + +/* Test circular buffer - test reset() function. + * + * Given is a circular buffer with the capacity equal to N (BufferSize). + * When reset operation is performed on circular buffer. + * Then circular buffer is cleared. + * + */ +template +void test_reset() +{ + CircularBuffer cb; + T data = 0xAA; + + /* Push max elements. */ + for (uint32_t i = 0; i < BufferSize; i++) { + cb.push(data); + } + + TEST_ASSERT_TRUE(cb.full()); + TEST_ASSERT_FALSE(cb.empty()); + TEST_ASSERT_EQUAL(BufferSize, cb.size()); + + cb.reset(); + + TEST_ASSERT_FALSE(cb.full()); + TEST_ASSERT_TRUE(cb.empty()); + TEST_ASSERT_FALSE(cb.pop(data)); + TEST_ASSERT_EQUAL(0, cb.size()); + + /* Check if after reset push and pop operations work. */ + for (uint32_t i = 0; i < BufferSize; i++) { + cb.push(data); + data++; + } + + for (uint32_t i = 0; i < BufferSize; i++) { + cb.pop(data); + TEST_ASSERT_EQUAL(0xAA + i, data); + } +} + +/* Test circular buffer - creation of circular buffer with max buffer size consistent with counter type. + * + * Given is a circular buffer. + * When circular buffer is created with buffer size equal to 255 and counter type equal to unsigned char. + * Then circular buffer is successfully created. + * + */ +void test_counter_type_buffer_size() +{ + CircularBuffer cb; + int data = 100; + + /* Perform some operations. */ + cb.push(data); + data = 0; + cb.pop(data); + TEST_ASSERT_EQUAL(100, data); +} + +utest::v1::status_t greentea_failure_handler(const Case *const source, const failure_t reason) +{ + greentea_case_failure_abort_handler(source, reason); + return STATUS_CONTINUE; +} + +Case cases[] = { + Case("Input does not exceed capacity(1) push max, pop max.", + test_input_does_not_exceed_capacity_push_max_pop_max, greentea_failure_handler), + Case("Input does not exceed capacity(3) push max, pop max.", + test_input_does_not_exceed_capacity_push_max_pop_max, greentea_failure_handler), + + Case("Input does not exceed capacity(5) push 2, pop 1.", + test_input_does_not_exceed_capacity_push_2_pop_1, greentea_failure_handler), + Case("Input does not exceed capacity(10) push 2, pop 1.", + test_input_does_not_exceed_capacity_push_2_pop_1, greentea_failure_handler), + + Case("Input exceeds capacity(1) push max+1, pop max.", + test_input_exceeds_capacity_push_max_plus_1_pop_max, greentea_failure_handler), + Case("Input exceeds capacity(3) push max+1, pop max.", + test_input_exceeds_capacity_push_max_plus_1_pop_max, greentea_failure_handler), + + Case("Input exceeds capacity(5) push 2, pop 1.", + test_input_exceeds_capacity_push_2_pop_1, greentea_failure_handler), + Case("Input exceeds capacity(10) push 2, pop 1.", + test_input_exceeds_capacity_push_2_pop_1, greentea_failure_handler), + + Case("empty() returns true when buffer(3 elements) is empty.", test_empty, greentea_failure_handler), + Case("empty() returns true when buffer(5 elements) is empty.", test_empty, greentea_failure_handler), + + Case("full() returns true when buffer(3 elements) is full.", test_full, greentea_failure_handler), + Case("full() returns true when buffer(5 elements) is full.", test_full, greentea_failure_handler), + + Case("reset() clears the buffer.", test_reset, greentea_failure_handler), + + Case("Test pop(), empty(), full(), size() after CircularBuffer creation.", + test_pop_empty_full_size_after_creation, greentea_failure_handler), + + Case("Test CounterType/BufferSize boarder case.", test_counter_type_buffer_size, greentea_failure_handler), + + Case("Input exceeds capacity(5) push 2, pop 1 - complex type.", + test_input_exceeds_capacity_push_2_pop_1_complex_type<5, unsigned short>, greentea_failure_handler), +}; + +utest::v1::status_t greentea_test_setup(const size_t number_of_cases) +{ + GREENTEA_SETUP(15, "default_auto"); + return greentea_test_setup_handler(number_of_cases); +} + +Specification specification(greentea_test_setup, cases, greentea_test_teardown_handler); + +int main() +{ + return Harness::run(specification); +}