| /* |
| * |
| * Copyright (c) 2021 Project CHIP Authors |
| * All rights reserved. |
| * |
| * 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 <algorithm> |
| #include <ctime> |
| #include <list> |
| #include <numeric> |
| #include <string.h> |
| #include <vector> |
| |
| #include <lib/support/BytesCircularBuffer.h> |
| #include <lib/support/UnitTestRegistration.h> |
| |
| #include <nlunit-test.h> |
| |
| namespace { |
| |
| using namespace chip; |
| |
| void TestPushInvalid(nlTestSuite * inSuite, void * inContext) |
| { |
| uint8_t storage[10]; |
| BytesCircularBuffer buffer(storage, sizeof(storage)); |
| const uint8_t s[] = { '1', '2', '3', '4', '5', '6', '7', '8', '9', '0' }; |
| NL_TEST_ASSERT(inSuite, buffer.IsEmpty()); |
| |
| NL_TEST_ASSERT(inSuite, buffer.Push(ByteSpan(s, static_cast<size_t>(-1))) == CHIP_ERROR_INVALID_ARGUMENT); |
| NL_TEST_ASSERT(inSuite, buffer.IsEmpty()); |
| |
| NL_TEST_ASSERT(inSuite, buffer.Push(ByteSpan(s, 8)) == CHIP_ERROR_INVALID_ARGUMENT); |
| NL_TEST_ASSERT(inSuite, buffer.IsEmpty()); |
| NL_TEST_ASSERT(inSuite, buffer.Push(ByteSpan(s, 9)) == CHIP_ERROR_INVALID_ARGUMENT); |
| NL_TEST_ASSERT(inSuite, buffer.IsEmpty()); |
| NL_TEST_ASSERT(inSuite, buffer.Push(ByteSpan(s, 7)) == CHIP_NO_ERROR); |
| NL_TEST_ASSERT(inSuite, !buffer.IsEmpty()); |
| } |
| |
| void RandomPushPop(nlTestSuite * inSuite, void * inContext, BytesCircularBuffer & buffer, |
| std::list<std::vector<uint8_t>> & alreadyInBuffer) |
| { |
| auto randomString = [] { |
| std::vector<uint8_t> str(static_cast<size_t>(std::rand()) % 8); |
| std::generate_n(str.begin(), str.size(), [] { return std::rand(); }); |
| return str; |
| }; |
| |
| auto bufferSize = [&alreadyInBuffer] { |
| return std::accumulate(alreadyInBuffer.begin(), alreadyInBuffer.end(), 0u, |
| [](auto s, auto str) { return s + str.size() + 2; }); |
| }; |
| |
| auto verify = [&inSuite, &alreadyInBuffer, &buffer] { |
| if (alreadyInBuffer.empty()) |
| { |
| NL_TEST_ASSERT(inSuite, buffer.IsEmpty()); |
| } |
| else |
| { |
| NL_TEST_ASSERT(inSuite, !buffer.IsEmpty()); |
| auto length = alreadyInBuffer.front().size(); |
| NL_TEST_ASSERT(inSuite, buffer.GetFrontSize() == length); |
| std::vector<uint8_t> str(length); |
| MutableByteSpan readSpan(str.data(), length); |
| NL_TEST_ASSERT(inSuite, buffer.ReadFront(readSpan) == CHIP_NO_ERROR); |
| NL_TEST_ASSERT(inSuite, readSpan.size() == length); |
| NL_TEST_ASSERT(inSuite, alreadyInBuffer.front() == str); |
| } |
| }; |
| |
| verify(); |
| |
| // Run 200 random operations |
| for (int i = 0; i < 200; ++i) |
| { |
| if (!buffer.IsEmpty() && std::rand() % 3 == 0) |
| { |
| // pop |
| NL_TEST_ASSERT(inSuite, buffer.Pop() == CHIP_NO_ERROR); |
| alreadyInBuffer.pop_front(); |
| |
| verify(); |
| } |
| else |
| { |
| // push random string |
| auto str = randomString(); |
| NL_TEST_ASSERT(inSuite, buffer.Push(ByteSpan(str.data(), str.size())) == CHIP_NO_ERROR); |
| alreadyInBuffer.push_back(str); |
| |
| while (bufferSize() > 9) |
| { |
| alreadyInBuffer.pop_front(); |
| } |
| |
| verify(); |
| } |
| } |
| } |
| |
| void TestPushPopRandom(nlTestSuite * inSuite, void * inContext) |
| { |
| uint8_t storage[10]; |
| BytesCircularBuffer buffer(storage, sizeof(storage)); |
| NL_TEST_ASSERT(inSuite, buffer.IsEmpty()); |
| |
| std::list<std::vector<uint8_t>> alreadyInBuffer; |
| RandomPushPop(inSuite, inContext, buffer, alreadyInBuffer); |
| } |
| |
| void TestPushToJustFull(nlTestSuite * inSuite, void * inContext) |
| { |
| uint8_t storage[10]; |
| |
| // Total space = 9 |
| BytesCircularBuffer buffer(storage, sizeof(storage)); |
| NL_TEST_ASSERT(inSuite, buffer.IsEmpty()); |
| |
| const uint8_t s1[] = { '1', '2', '3', '4', '5', '6', '7', '8', '9', '0' }; |
| const uint8_t s2[] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j' }; |
| uint8_t o[sizeof(s1)]; |
| MutableByteSpan readSpan(o); |
| |
| NL_TEST_ASSERT(inSuite, buffer.IsEmpty()); |
| |
| // Used = 6, Free = 3 |
| NL_TEST_ASSERT(inSuite, buffer.Push(ByteSpan(s1, 4)) == CHIP_NO_ERROR); |
| NL_TEST_ASSERT(inSuite, !buffer.IsEmpty()); |
| NL_TEST_ASSERT(inSuite, buffer.GetFrontSize() == 4); |
| readSpan = MutableByteSpan(o); |
| NL_TEST_ASSERT(inSuite, buffer.ReadFront(readSpan) == CHIP_NO_ERROR); |
| NL_TEST_ASSERT(inSuite, memcmp(s1, readSpan.data(), 4) == 0); |
| |
| // Used = 9, Free = 0 |
| NL_TEST_ASSERT(inSuite, buffer.Push(ByteSpan(s2, 1)) == CHIP_NO_ERROR); |
| NL_TEST_ASSERT(inSuite, !buffer.IsEmpty()); |
| NL_TEST_ASSERT(inSuite, buffer.GetFrontSize() == 4); |
| readSpan = MutableByteSpan(o); |
| NL_TEST_ASSERT(inSuite, buffer.ReadFront(readSpan) == CHIP_NO_ERROR); |
| NL_TEST_ASSERT(inSuite, memcmp(s1, readSpan.data(), 4) == 0); |
| |
| // Try normal push/pop again |
| std::list<std::vector<uint8_t>> alreadyInBuffer; |
| alreadyInBuffer.push_back(std::vector<uint8_t>{ s1[0], s1[1], s1[2], s1[3] }); |
| alreadyInBuffer.push_back(std::vector<uint8_t>{ s2[0] }); |
| RandomPushPop(inSuite, inContext, buffer, alreadyInBuffer); |
| } |
| |
| void TestPushToJustFullOverOne(nlTestSuite * inSuite, void * inContext) |
| { |
| uint8_t storage[10]; |
| |
| // Total space = 9 |
| BytesCircularBuffer buffer(storage, sizeof(storage)); |
| NL_TEST_ASSERT(inSuite, buffer.IsEmpty()); |
| |
| const uint8_t s1[] = { '1', '2', '3', '4', '5', '6', '7', '8', '9', '0' }; |
| const uint8_t s2[] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j' }; |
| uint8_t o[sizeof(s1)]; |
| MutableByteSpan readSpan(o); |
| |
| NL_TEST_ASSERT(inSuite, buffer.IsEmpty()); |
| |
| // Used = 6, Free = 3 |
| NL_TEST_ASSERT(inSuite, buffer.Push(ByteSpan(s1, 4)) == CHIP_NO_ERROR); |
| NL_TEST_ASSERT(inSuite, !buffer.IsEmpty()); |
| NL_TEST_ASSERT(inSuite, buffer.GetFrontSize() == 4); |
| readSpan = MutableByteSpan(o); |
| NL_TEST_ASSERT(inSuite, buffer.ReadFront(readSpan) == CHIP_NO_ERROR); |
| NL_TEST_ASSERT(inSuite, memcmp(s1, readSpan.data(), 4) == 0); |
| |
| // Used = 4, Free = 5 |
| NL_TEST_ASSERT(inSuite, buffer.Push(ByteSpan(s2, 2)) == CHIP_NO_ERROR); |
| NL_TEST_ASSERT(inSuite, !buffer.IsEmpty()); |
| NL_TEST_ASSERT(inSuite, buffer.GetFrontSize() == 2); |
| readSpan = MutableByteSpan(o); |
| NL_TEST_ASSERT(inSuite, buffer.ReadFront(readSpan) == CHIP_NO_ERROR); |
| NL_TEST_ASSERT(inSuite, memcmp(s2, readSpan.data(), 2) == 0); |
| |
| // Try normal push/pop again |
| std::list<std::vector<uint8_t>> alreadyInBuffer; |
| alreadyInBuffer.push_back(std::vector<uint8_t>{ s2[0], s2[1] }); |
| RandomPushPop(inSuite, inContext, buffer, alreadyInBuffer); |
| } |
| |
| void TestPushWrap(nlTestSuite * inSuite, void * inContext) |
| { |
| uint8_t storage[10]; |
| |
| // Total space = 9 |
| BytesCircularBuffer buffer(storage, sizeof(storage)); |
| NL_TEST_ASSERT(inSuite, buffer.IsEmpty()); |
| |
| const uint8_t s1[] = { '1', '2', '3', '4', '5', '6', '7', '8', '9', '0' }; |
| const uint8_t s2[] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j' }; |
| uint8_t o[sizeof(s1)]; |
| MutableByteSpan readSpan(o); |
| |
| NL_TEST_ASSERT(inSuite, buffer.IsEmpty()); |
| |
| // Used = 6, Free = 3 |
| NL_TEST_ASSERT(inSuite, buffer.Push(ByteSpan(s1, 4)) == CHIP_NO_ERROR); |
| NL_TEST_ASSERT(inSuite, !buffer.IsEmpty()); |
| NL_TEST_ASSERT(inSuite, buffer.GetFrontSize() == 4); |
| readSpan = MutableByteSpan(o); |
| NL_TEST_ASSERT(inSuite, buffer.ReadFront(readSpan) == CHIP_NO_ERROR); |
| NL_TEST_ASSERT(inSuite, memcmp(s1, readSpan.data(), 4) == 0); |
| |
| // Used = 6, Free = 3, s1 was discarded due to lack of storage |
| NL_TEST_ASSERT(inSuite, buffer.Push(ByteSpan(s2, 4)) == CHIP_NO_ERROR); |
| NL_TEST_ASSERT(inSuite, !buffer.IsEmpty()); |
| NL_TEST_ASSERT(inSuite, buffer.GetFrontSize() == 4); |
| readSpan = MutableByteSpan(o); |
| NL_TEST_ASSERT(inSuite, buffer.ReadFront(readSpan) == CHIP_NO_ERROR); |
| NL_TEST_ASSERT(inSuite, memcmp(s2, readSpan.data(), 4) == 0); |
| |
| // Used = 9, Free = 0 |
| NL_TEST_ASSERT(inSuite, buffer.Push(ByteSpan(s1, 1)) == CHIP_NO_ERROR); |
| NL_TEST_ASSERT(inSuite, !buffer.IsEmpty()); |
| NL_TEST_ASSERT(inSuite, buffer.GetFrontSize() == 4); |
| readSpan = MutableByteSpan(o); |
| NL_TEST_ASSERT(inSuite, buffer.ReadFront(readSpan) == CHIP_NO_ERROR); |
| NL_TEST_ASSERT(inSuite, memcmp(s2, readSpan.data(), 4) == 0); |
| |
| // Used = 3, Free = 6 |
| NL_TEST_ASSERT(inSuite, buffer.Pop() == CHIP_NO_ERROR); |
| NL_TEST_ASSERT(inSuite, !buffer.IsEmpty()); |
| NL_TEST_ASSERT(inSuite, buffer.GetFrontSize() == 1); |
| readSpan = MutableByteSpan(o); |
| NL_TEST_ASSERT(inSuite, buffer.ReadFront(readSpan) == CHIP_NO_ERROR); |
| NL_TEST_ASSERT(inSuite, memcmp(s1, readSpan.data(), 1) == 0); |
| |
| // All freed |
| NL_TEST_ASSERT(inSuite, buffer.Pop() == CHIP_NO_ERROR); |
| NL_TEST_ASSERT(inSuite, buffer.IsEmpty()); |
| } |
| |
| void TestPushWrapStartAtEdge(nlTestSuite * inSuite, void * inContext) |
| { |
| uint8_t storage[10]; |
| const uint8_t s1[] = { '1', '2', '3', '4', '5', '6', '7', '8', '9', '0' }; |
| const uint8_t s2[] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j' }; |
| uint8_t o[sizeof(s1)]; |
| MutableByteSpan readSpan(o); |
| |
| // Total space = 9 |
| BytesCircularBuffer buffer(storage, sizeof(storage)); |
| NL_TEST_ASSERT(inSuite, buffer.IsEmpty()); |
| |
| // Used = 6, Free = 3, mDataStart = 0, mDataEnd = 6 |
| NL_TEST_ASSERT(inSuite, buffer.Push(ByteSpan(s1, 4)) == CHIP_NO_ERROR); |
| NL_TEST_ASSERT(inSuite, !buffer.IsEmpty()); |
| NL_TEST_ASSERT(inSuite, buffer.GetFrontSize() == 4); |
| readSpan = MutableByteSpan(o); |
| NL_TEST_ASSERT(inSuite, buffer.ReadFront(readSpan) == CHIP_NO_ERROR); |
| NL_TEST_ASSERT(inSuite, memcmp(s1, readSpan.data(), 4) == 0); |
| |
| // Used = 4, Free = 5, mDataStart = 6, mDataEnd = 0 wrap mDataEnd |
| NL_TEST_ASSERT(inSuite, buffer.Push(ByteSpan(s2, 2)) == CHIP_NO_ERROR); |
| NL_TEST_ASSERT(inSuite, !buffer.IsEmpty()); |
| NL_TEST_ASSERT(inSuite, buffer.GetFrontSize() == 2); |
| readSpan = MutableByteSpan(o); |
| NL_TEST_ASSERT(inSuite, buffer.ReadFront(readSpan) == CHIP_NO_ERROR); |
| NL_TEST_ASSERT(inSuite, memcmp(s2, readSpan.data(), 2) == 0); |
| |
| // Used = 7, Free = 2, mDataStart = 6, mDataEnd = 3 wrap mDataEnd |
| NL_TEST_ASSERT(inSuite, buffer.Push(ByteSpan(s1, 1)) == CHIP_NO_ERROR); |
| NL_TEST_ASSERT(inSuite, !buffer.IsEmpty()); |
| NL_TEST_ASSERT(inSuite, buffer.GetFrontSize() == 2); |
| readSpan = MutableByteSpan(o); |
| NL_TEST_ASSERT(inSuite, buffer.ReadFront(readSpan) == CHIP_NO_ERROR); |
| NL_TEST_ASSERT(inSuite, memcmp(s2, readSpan.data(), 2) == 0); |
| |
| // Used = 3, Free = 6, mDataStart = 0, mDataEnd = 3 |
| NL_TEST_ASSERT(inSuite, buffer.Pop() == CHIP_NO_ERROR); |
| NL_TEST_ASSERT(inSuite, !buffer.IsEmpty()); |
| NL_TEST_ASSERT(inSuite, buffer.GetFrontSize() == 1); |
| readSpan = MutableByteSpan(o); |
| NL_TEST_ASSERT(inSuite, buffer.ReadFront(readSpan) == CHIP_NO_ERROR); |
| NL_TEST_ASSERT(inSuite, memcmp(s1, readSpan.data(), 1) == 0); |
| |
| // Try normal push/pop again |
| std::list<std::vector<uint8_t>> alreadyInBuffer; |
| alreadyInBuffer.push_back(std::vector<uint8_t>{ s1[0] }); |
| RandomPushPop(inSuite, inContext, buffer, alreadyInBuffer); |
| } |
| |
| void TestPushWrapEndAtEdge(nlTestSuite * inSuite, void * inContext) |
| { |
| uint8_t storage[10]; |
| const uint8_t s1[] = { '1', '2', '3', '4', '5', '6', '7', '8', '9', '0' }; |
| const uint8_t s2[] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j' }; |
| uint8_t o[sizeof(s1)]; |
| MutableByteSpan readSpan(o); |
| |
| // Total space = 9 |
| BytesCircularBuffer buffer(storage, sizeof(storage)); |
| NL_TEST_ASSERT(inSuite, buffer.IsEmpty()); |
| |
| // Used = 6, Free = 3, mDataStart = 0, mDataEnd = 6 |
| NL_TEST_ASSERT(inSuite, buffer.Push(ByteSpan(s1, 4)) == CHIP_NO_ERROR); |
| NL_TEST_ASSERT(inSuite, !buffer.IsEmpty()); |
| NL_TEST_ASSERT(inSuite, buffer.GetFrontSize() == 4); |
| readSpan = MutableByteSpan(o); |
| NL_TEST_ASSERT(inSuite, buffer.ReadFront(readSpan) == CHIP_NO_ERROR); |
| NL_TEST_ASSERT(inSuite, memcmp(s1, readSpan.data(), 4) == 0); |
| |
| // Used = 4, Free = 5, mDataStart = 6, mDataEnd = 0 wrap mDataEnd |
| NL_TEST_ASSERT(inSuite, buffer.Push(ByteSpan(s2, 2)) == CHIP_NO_ERROR); |
| NL_TEST_ASSERT(inSuite, !buffer.IsEmpty()); |
| NL_TEST_ASSERT(inSuite, buffer.GetFrontSize() == 2); |
| readSpan = MutableByteSpan(o); |
| NL_TEST_ASSERT(inSuite, buffer.ReadFront(readSpan) == CHIP_NO_ERROR); |
| NL_TEST_ASSERT(inSuite, memcmp(s2, readSpan.data(), 2) == 0); |
| |
| // Try normal push/pop again |
| std::list<std::vector<uint8_t>> alreadyInBuffer; |
| alreadyInBuffer.push_back(std::vector<uint8_t>{ s2[0], s2[1] }); |
| RandomPushPop(inSuite, inContext, buffer, alreadyInBuffer); |
| } |
| |
| void TestPushWhenFull(nlTestSuite * inSuite, void * inContext) |
| { |
| uint8_t storage[10]; |
| const uint8_t s1[] = { '1', '2', '3', '4', '5', '6', '7', '8', '9', '0' }; |
| const uint8_t s2[] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j' }; |
| uint8_t o[sizeof(s1)]; |
| MutableByteSpan readSpan(o); |
| |
| // Total space = 9 |
| BytesCircularBuffer buffer(storage, sizeof(storage)); |
| NL_TEST_ASSERT(inSuite, buffer.IsEmpty()); |
| |
| // Used = 9, Free = 0 |
| NL_TEST_ASSERT(inSuite, buffer.Push(ByteSpan(s1, 7)) == CHIP_NO_ERROR); |
| NL_TEST_ASSERT(inSuite, !buffer.IsEmpty()); |
| NL_TEST_ASSERT(inSuite, buffer.GetFrontSize() == 7); |
| readSpan = MutableByteSpan(o); |
| NL_TEST_ASSERT(inSuite, buffer.ReadFront(readSpan) == CHIP_NO_ERROR); |
| NL_TEST_ASSERT(inSuite, memcmp(s1, readSpan.data(), 7) == 0); |
| |
| // Used = 5, Free = 4 |
| NL_TEST_ASSERT(inSuite, buffer.Push(ByteSpan(s2, 3)) == CHIP_NO_ERROR); |
| NL_TEST_ASSERT(inSuite, !buffer.IsEmpty()); |
| NL_TEST_ASSERT(inSuite, buffer.GetFrontSize() == 3); |
| readSpan = MutableByteSpan(o); |
| NL_TEST_ASSERT(inSuite, buffer.ReadFront(readSpan) == CHIP_NO_ERROR); |
| NL_TEST_ASSERT(inSuite, memcmp(s2, readSpan.data(), 3) == 0); |
| |
| // All freed |
| NL_TEST_ASSERT(inSuite, buffer.Pop() == CHIP_NO_ERROR); |
| NL_TEST_ASSERT(inSuite, buffer.IsEmpty()); |
| |
| // Try normal push/pop again |
| std::list<std::vector<uint8_t>> alreadyInBuffer; |
| RandomPushPop(inSuite, inContext, buffer, alreadyInBuffer); |
| } |
| |
| int Setup(void * inContext) |
| { |
| return SUCCESS; |
| } |
| |
| int Teardown(void * inContext) |
| { |
| return SUCCESS; |
| } |
| |
| } // namespace |
| |
| #define NL_TEST_DEF_FN(fn) NL_TEST_DEF("Test " #fn, fn) |
| /** |
| * Test Suite. It lists all the test functions. |
| */ |
| static const nlTest sTests[] = { |
| NL_TEST_DEF_FN(TestPushPopRandom), NL_TEST_DEF_FN(TestPushInvalid), NL_TEST_DEF_FN(TestPushToJustFull), |
| NL_TEST_DEF_FN(TestPushToJustFullOverOne), NL_TEST_DEF_FN(TestPushWrap), NL_TEST_DEF_FN(TestPushWrapStartAtEdge), |
| NL_TEST_DEF_FN(TestPushWrapEndAtEdge), NL_TEST_DEF_FN(TestPushWhenFull), NL_TEST_SENTINEL() |
| }; |
| |
| int TestBytesCircularBuffer() |
| { |
| nlTestSuite theSuite = { "CHIP BytesCircularBuffer tests", &sTests[0], Setup, Teardown }; |
| |
| unsigned seed = static_cast<unsigned>(std::time(nullptr)); |
| printf("Running " __FILE__ " using seed %d", seed); |
| std::srand(seed); |
| // Run test suite against one context. |
| nlTestRunner(&theSuite, nullptr); |
| return nlTestRunnerStats(&theSuite); |
| } |
| |
| CHIP_REGISTER_TEST_SUITE(TestBytesCircularBuffer); |