| /* | |
| * Copyright 2018 The Android Open Source Project | |
| * | |
| * 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 <thread> | |
| #include <iostream> | |
| #include <gtest/gtest.h> | |
| #include "utils/LockFreeQueue.h" | |
| /** | |
| * Tests | |
| * ===== | |
| * | |
| * READ/POP: | |
| * - if queue is empty return false | |
| * - if queue has elements return true | |
| * - returns correct value once | |
| * - returns correct value more than once | |
| * | |
| * WRITE/PUSH: | |
| * - if queue is full return false | |
| * - if queue has space return true | |
| * | |
| * WRAP: | |
| * - read from full queue, then write one more element, then correct read | |
| * | |
| * PEEK: | |
| * - peek once | |
| * - peek twice | |
| * | |
| * SIZE: | |
| * - correct zero | |
| * - correct once | |
| * - correct many | |
| * | |
| * MULTI-THREADED: | |
| * - write and read concurrently, queue is empty at end and item order is maintained | |
| * | |
| * CANNOT TEST: | |
| * - read/writes correct after wraparound (takes too long to run) | |
| * - Constructing queue with non power of 2 capacity results in assert failure (needs death test) | |
| * - Constructing queue with signed type results in assert failure (needs death test) | |
| * | |
| */ | |
| constexpr int kQueueCapacity = 2; | |
| class TestLockFreeQueue : public ::testing::Test { | |
| public: | |
| int result1; | |
| int result2; | |
| int result3; | |
| LockFreeQueue<int, kQueueCapacity> q; | |
| }; | |
| TEST_F(TestLockFreeQueue, PopReturnsFalseWhenEmpty){ | |
| bool result = q.pop(result1); | |
| ASSERT_EQ(result, false); | |
| } | |
| TEST_F(TestLockFreeQueue, PopReturnsTrueWhenNotEmpty){ | |
| q.push(123); | |
| bool result = q.pop(result1); | |
| ASSERT_EQ(result, true); | |
| } | |
| TEST_F(TestLockFreeQueue, PopReturnsOneCorrectValue){ | |
| q.push(123); | |
| q.pop(result1); | |
| ASSERT_EQ(result1, 123); | |
| } | |
| TEST_F(TestLockFreeQueue, PopReturnsManyCorrectValues){ | |
| q.push(123); | |
| q.push(456); | |
| q.pop(result1); | |
| q.pop(result2); | |
| ASSERT_EQ(result1, 123); | |
| ASSERT_EQ(result2, 456); | |
| } | |
| TEST_F(TestLockFreeQueue, PushWhenFullReturnsFalse){ | |
| q.push(123); | |
| q.push(123); | |
| ASSERT_EQ(q.push(123), false); | |
| } | |
| TEST_F(TestLockFreeQueue, PushWhenSpaceAvailableReturnsTrue){ | |
| ASSERT_EQ(q.push(123), true); | |
| } | |
| TEST_F(TestLockFreeQueue, PushHandlesWrapCorrectly){ | |
| q.push(123); | |
| q.push(234); | |
| q.pop(result1); | |
| q.push(999); | |
| q.pop(result2); | |
| ASSERT_EQ(result2, 234); | |
| q.pop(result3); | |
| ASSERT_EQ(result3, 999); | |
| } | |
| TEST_F(TestLockFreeQueue, PeekWhenEmptyReturnsFalse){ | |
| ASSERT_EQ(q.peek(result1), false); | |
| } | |
| TEST_F(TestLockFreeQueue, PeekWhenNotEmptyReturnsTrue){ | |
| q.push(123); | |
| ASSERT_EQ(q.peek(result1), true); | |
| } | |
| TEST_F(TestLockFreeQueue, PeekReturnsCorrectValueOnce){ | |
| q.push(456); | |
| q.peek(result1); | |
| ASSERT_EQ(result1, 456); | |
| } | |
| TEST_F(TestLockFreeQueue, PeekReturnsCorrectValueTwice){ | |
| q.push(456); | |
| q.peek(result1); | |
| q.peek(result2); | |
| ASSERT_EQ(result2, 456); | |
| } | |
| TEST_F(TestLockFreeQueue, SizeReturnsZeroAfterInit){ | |
| ASSERT_EQ(q.size(), 0); | |
| } | |
| TEST_F(TestLockFreeQueue, SizeIsOneAfterPushOnce){ | |
| q.push(321); | |
| ASSERT_EQ(q.size(), 1); | |
| } | |
| TEST_F(TestLockFreeQueue, SizeIsCorrectAfterPushingMaxItems){ | |
| for (int i = 0; i < kQueueCapacity; ++i) { q.push(i); } | |
| ASSERT_EQ(q.size(), kQueueCapacity); | |
| } | |
| TEST_F(TestLockFreeQueue, SizeCorrectAfterWriteCounterWraps){ | |
| const uint32_t kCapacity = (uint32_t) 1 << 7; | |
| LockFreeQueue<int, kCapacity, uint8_t> myQ; | |
| for (int i = 0; i < UINT8_MAX; ++i) { | |
| myQ.push(i); | |
| myQ.pop(result1); | |
| } | |
| myQ.push(1); | |
| ASSERT_EQ(myQ.size(), 1); | |
| } | |
| TEST_F(TestLockFreeQueue, MultiThreadedTest){ | |
| // Push and pop from the queue concurrently | |
| // Check that the queue is empty | |
| // Check that the order of the read values matches the order of the written values | |
| constexpr int kQueueCapacity = 2048; | |
| LockFreeQueue<int, kQueueCapacity> myQ; | |
| std::array<int, kQueueCapacity> sourceData; | |
| std::array<int, kQueueCapacity> targetData { 0 }; | |
| // Populate the source data | |
| for (int i = 0; i < kQueueCapacity; i++){ | |
| sourceData[i] = i; | |
| } | |
| std::thread writer([&myQ, &sourceData](){ | |
| for (int i = 0; i < kQueueCapacity; i++){ | |
| myQ.push(sourceData[i]); | |
| } | |
| }); | |
| std::thread reader([&myQ, &targetData](){ | |
| for (int i = 0; i < kQueueCapacity; i++){ | |
| // pop the latest element, or wait for it to become available | |
| while (!myQ.pop(targetData[i])){ | |
| std::cout << "Waiting for element at index " << i << " to be written" << std::endl; | |
| usleep(500); | |
| } | |
| } | |
| }); | |
| writer.join(); | |
| reader.join(); | |
| int v; | |
| ASSERT_FALSE(myQ.pop(v)); | |
| // Check that the target matches the source | |
| for (int i = 0; i < kQueueCapacity; i++){ | |
| ASSERT_EQ(sourceData[i], targetData[i]) << "Elements at index " << i << " did not match"; | |
| } | |
| } |