211 lines
5.1 KiB
C++
211 lines
5.1 KiB
C++
|
/*
|
||
|
* 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";
|
||
|
}
|
||
|
}
|