From 3529b83753846ced9b4566d30ee6a4579061c37e Mon Sep 17 00:00:00 2001 From: Simon Fels Date: Wed, 21 Dec 2016 13:10:24 +0100 Subject: [PATCH] Add unit tests for imported things from Android emulator --- src/CMakeLists.txt | 4 + src/anbox/common/message_channel.cpp | 65 ++++ src/anbox/common/message_channel.h | 99 ++++++ src/anbox/common/scope_ptr.h | 98 ++++++ src/anbox/graphics/buffer_queue.cpp | 85 +++--- src/anbox/graphics/buffer_queue.h | 14 +- src/anbox/graphics/buffered_io_stream.cpp | 6 +- src/anbox/testing/gtest_utils.h | 62 ++++ tests/anbox/CMakeLists.txt | 2 + tests/anbox/common/CMakeLists.txt | 4 + tests/anbox/common/message_channel_tests.cpp | 94 ++++++ tests/anbox/common/scope_ptr_tests.cpp | 65 ++++ tests/anbox/common/small_vector_tests.cpp | 219 ++++++++++++++ tests/anbox/common/type_traits_tests.cpp | 84 ++++++ tests/anbox/graphics/CMakeLists.txt | 1 + tests/anbox/graphics/buffer_queue_tests.cpp | 298 +++++++++++++++++++ 16 files changed, 1142 insertions(+), 58 deletions(-) create mode 100644 src/anbox/common/message_channel.cpp create mode 100644 src/anbox/common/message_channel.h create mode 100644 src/anbox/common/scope_ptr.h create mode 100644 src/anbox/testing/gtest_utils.h create mode 100644 tests/anbox/common/CMakeLists.txt create mode 100644 tests/anbox/common/message_channel_tests.cpp create mode 100644 tests/anbox/common/scope_ptr_tests.cpp create mode 100644 tests/anbox/common/small_vector_tests.cpp create mode 100644 tests/anbox/common/type_traits_tests.cpp create mode 100644 tests/anbox/graphics/CMakeLists.txt create mode 100644 tests/anbox/graphics/buffer_queue_tests.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e44ed223..80bdca6d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -69,6 +69,10 @@ set(SOURCES anbox/common/dispatcher.cpp anbox/common/small_vector.h anbox/common/type_traits.h + anbox/common/message_channel.cpp + anbox/common/scope_ptr.h + + anbox/testing/gtest_utils.h anbox/container/service.cpp anbox/container/client.cpp diff --git a/src/anbox/common/message_channel.cpp b/src/anbox/common/message_channel.cpp new file mode 100644 index 00000000..14c5608f --- /dev/null +++ b/src/anbox/common/message_channel.cpp @@ -0,0 +1,65 @@ +// Copyright 2014 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 "anbox/common/message_channel.h" + +namespace anbox { +namespace common { +MessageChannelBase::MessageChannelBase(size_t capacity) : + pos_(0U), + count_(0U), + capacity_(capacity), + lock_(), + can_read_(), + can_write_() {} + +MessageChannelBase::~MessageChannelBase() {} + +size_t MessageChannelBase::before_write() { + std::unique_lock l(lock_, std::defer_lock); + lock_.lock(); + + while (count_ >= capacity_) + can_write_.wait(l); + + size_t result = pos_ + count_; + if (result >= capacity_) + result -= capacity_; + + return result; +} + +void MessageChannelBase::after_write() { + count_++; + can_read_.notify_one(); + lock_.unlock(); +} + +size_t MessageChannelBase::before_read() { + std::unique_lock l(lock_, std::defer_lock); + lock_.lock(); + while (count_ == 0) + can_read_.wait(l); + return pos_; +} + +void MessageChannelBase::after_read() { + if (++pos_ == capacity_) + pos_ = 0U; + count_--; + can_write_.notify_one(); + lock_.unlock(); +} +} // namespace common +} // namespace anbox diff --git a/src/anbox/common/message_channel.h b/src/anbox/common/message_channel.h new file mode 100644 index 00000000..07b59921 --- /dev/null +++ b/src/anbox/common/message_channel.h @@ -0,0 +1,99 @@ +// Copyright 2014 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. + +#ifndef ANBOX_COMMON_MESSAGE_CHANNEL_H +#define ANBOX_COMMON_MESSAGE_CHANNEL_H + +#include + +#include +#include + +namespace anbox { +namespace common { + +// Base non-templated class used to reduce the amount of template +// specialization. +class MessageChannelBase { +public: + // Constructor. |capacity| is the buffer capacity in messages. + MessageChannelBase(size_t capacity); + + // Destructor. + ~MessageChannelBase(); + +protected: + // Call this method in the sender thread before writing a new message. + // This returns the position of the available slot in the message array + // where to copy the new fixed-size message. After the copy, call + // afterWrite(). + size_t before_write(); + + // To be called after beforeWrite() and copying a new fixed-size message + // into the array. This signal the receiver thread that there is a new + // incoming message. + void after_write(); + + // Call this method in the receiver thread before reading a new message. + // This returns the position in the message array where the new message + // can be read. Caller must process the message, then call afterRead(). + size_t before_read(); + + // To be called in the receiver thread after beforeRead() and processing + // the corresponding message. + void after_read(); + +private: + size_t pos_; + size_t count_; + size_t capacity_; + std::mutex lock_; + std::condition_variable can_read_; + std::condition_variable can_write_; +}; + +// Helper class used to implement an uni-directional IPC channel between +// two threads. The channel can be used to send fixed-size messages of type +// |T|, with an internal buffer size of |CAPACITY| items. All calls are +// blocking. +// +// Usage is pretty straightforward: +// +// - From the sender thread, call send(msg); +// - From the receiver thread, call receive(&msg); +// +template +class MessageChannel : public MessageChannelBase { +public: + MessageChannel() : MessageChannelBase(CAPACITY) {} + + void send(const T& msg) { + size_t pos = before_write(); + mItems[pos] = msg; + after_write(); + } + + void receive(T* msg) { + size_t pos = before_read(); + *msg = mItems[pos]; + after_read(); + } + +private: + T mItems[CAPACITY]; +}; +} // namespace common +} // namespace anbox + +#endif diff --git a/src/anbox/common/scope_ptr.h b/src/anbox/common/scope_ptr.h new file mode 100644 index 00000000..00253f8c --- /dev/null +++ b/src/anbox/common/scope_ptr.h @@ -0,0 +1,98 @@ +// Copyright 2014 The Android Open Source Project +// +// This software is licensed under the terms of the GNU General Public +// License version 2, as published by the Free Software Foundation, and +// may be copied, distributed, and modified under those terms. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +#pragma once + +#include "anbox/common/type_traits.h" + +#include +#include +#include + +#include + +namespace anbox { +namespace common { + +struct FreeDelete { + template + void operator()(T ptr) const { + free((void*)ptr); + } +}; + +template +struct FuncDelete { + explicit FuncDelete(Func f = {}) : mF(f) {} + + FuncDelete(const FuncDelete& other) = default; + FuncDelete(FuncDelete&& other) = default; + FuncDelete& operator=(const FuncDelete& other) = default; + FuncDelete& operator=(FuncDelete&& other) = default; + + // To be able to copy/move from all compatible template instantiations. + template friend struct FuncDelete; + + // Template constructors and move assignment from compatible instantiations. + template + FuncDelete(const FuncDelete& other) : mF(other.mF) {} + template + FuncDelete(FuncDelete&& other) : mF(std::move(other.mF)) {} + template + FuncDelete& operator=(const FuncDelete& other) { + mF = other.mF; + return *this; + } + template + FuncDelete& operator=(FuncDelete&& other) { + mF = std::move(other.mF); + return *this; + } + + // This is the actual deleter call. + template + void operator()(T t) const { + mF(t); + } + +private: + Func mF; +}; + +template > +using ScopedPtr = std::unique_ptr; + +template +using ScopedCPtr = std::unique_ptr; + +template +using ScopedCustomPtr = std::unique_ptr>; + +// A factory function that creates a scoped pointer with |deleter| +// function used as a deleter - it is called at the scope exit. +// Note: enable_if<> limits the scope of allowed arguments to pointers and +// std::nullptr_t (to allow makeCustomScopedPtr(nullptr, ...) calls). +template ::value || + std::is_pointer::value>> +ScopedCustomPtr< + typename std::decay::type>::type, + typename std::decay::type> +makeCustomScopedPtr(T data, Func deleter) { + return ScopedCustomPtr< + typename std::decay::type>::type, + typename std::decay::type>( + data, FuncDelete::type>(deleter)); +} + +} // namespace common +} // namespace anbox diff --git a/src/anbox/graphics/buffer_queue.cpp b/src/anbox/graphics/buffer_queue.cpp index 6cf6f0e9..664713cd 100644 --- a/src/anbox/graphics/buffer_queue.cpp +++ b/src/anbox/graphics/buffer_queue.cpp @@ -1,19 +1,16 @@ -/* - * Copyright (C) 2016 Simon Fels - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 3, as published - * by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranties of - * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - * - */ +// Copyright (C) 2016 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 "anbox/graphics/buffer_queue.h" @@ -22,58 +19,56 @@ namespace graphics { BufferQueue::BufferQueue(size_t capacity) : capacity_(capacity), buffers_(new Buffer[capacity]) {} -BufferQueue::Result BufferQueue::try_push_locked(Buffer &&buffer) { - if (closed_) { - return Result::Error; - } - if (count_ >= capacity_) { - return Result::TryAgain; - } +int BufferQueue::try_push_locked(Buffer &&buffer) { + if (closed_) + return -EIO; + + if (count_ >= capacity_) + return -EAGAIN; + size_t pos = pos_ + count_; - if (pos >= capacity_) { + if (pos >= capacity_) pos -= capacity_; - } + buffers_[pos] = std::move(buffer); - if (count_++ == 0) { + if (count_++ == 0) can_pop_.notify_one(); - } - return Result::Ok; + + return 0; } -BufferQueue::Result BufferQueue::push_locked( +int BufferQueue::push_locked( Buffer &&buffer, std::unique_lock &lock) { while (count_ == capacity_) { - if (closed_) { - return Result::Error; - } + if (closed_) + return -EIO; can_push_.wait(lock); } return try_push_locked(std::move(buffer)); } -BufferQueue::Result BufferQueue::try_pop_locked(Buffer *buffer) { - if (count_ == 0) { - return closed_ ? Result::Error : Result::TryAgain; - } +int BufferQueue::try_pop_locked(Buffer *buffer) { + if (count_ == 0) + return closed_ ? -EIO : -EAGAIN; + *buffer = std::move(buffers_[pos_]); size_t pos = pos_ + 1; - if (pos >= capacity_) { + if (pos >= capacity_) pos -= capacity_; - } + pos_ = pos; - if (count_-- == capacity_) { + if (count_-- == capacity_) can_push_.notify_one(); - } - return Result::Ok; + + return 0; } -BufferQueue::Result BufferQueue::pop_locked( +int BufferQueue::pop_locked( Buffer *buffer, std::unique_lock &lock) { while (count_ == 0) { - if (closed_) { + if (closed_) // Closed queue is empty. - return Result::Error; - } + return -EIO; can_pop_.wait(lock); } return try_pop_locked(buffer); diff --git a/src/anbox/graphics/buffer_queue.h b/src/anbox/graphics/buffer_queue.h index 3958440b..6c523008 100644 --- a/src/anbox/graphics/buffer_queue.h +++ b/src/anbox/graphics/buffer_queue.h @@ -30,18 +30,12 @@ using Buffer = anbox::common::SmallFixedVector; class BufferQueue { public: - enum class Result { - Ok = 0, - TryAgain = 1, - Error = 2, - }; - BufferQueue(size_t capacity); - Result try_push_locked(Buffer &&buffer); - Result push_locked(Buffer &&buffer, std::unique_lock &lock); - Result try_pop_locked(Buffer *buffer); - Result pop_locked(Buffer *buffer, std::unique_lock &lock); + int try_push_locked(Buffer &&buffer); + int push_locked(Buffer &&buffer, std::unique_lock &lock); + int try_pop_locked(Buffer *buffer); + int pop_locked(Buffer *buffer, std::unique_lock &lock); void close_locked(); private: diff --git a/src/anbox/graphics/buffered_io_stream.cpp b/src/anbox/graphics/buffered_io_stream.cpp index bbe37913..72b94dce 100644 --- a/src/anbox/graphics/buffered_io_stream.cpp +++ b/src/anbox/graphics/buffered_io_stream.cpp @@ -72,13 +72,13 @@ const unsigned char *BufferedIOStream::read(void *buf, size_t *inout_len) { } bool blocking = (count == 0); - auto result = BufferQueue::Result::Error; + auto result = -EIO; if (blocking) result = in_queue_.pop_locked(&read_buffer_, l); else result = in_queue_.try_pop_locked(&read_buffer_); - if (result == BufferQueue::Result::Ok) { + if (result == 0) { read_buffer_left_ = read_buffer_.size(); continue; } @@ -111,7 +111,7 @@ void BufferedIOStream::thread_main() { Buffer buffer; auto result = out_queue_.pop_locked(&buffer, l); - if (result == BufferQueue::Result::Error) break; + if (result != 0 && result != -EAGAIN) break; auto bytes_left = buffer.size(); while (bytes_left > 0) { diff --git a/src/anbox/testing/gtest_utils.h b/src/anbox/testing/gtest_utils.h new file mode 100644 index 00000000..80d91196 --- /dev/null +++ b/src/anbox/testing/gtest_utils.h @@ -0,0 +1,62 @@ +// Copyright 2016 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. + +#pragma once + +#include "anbox/common/type_traits.h" + +#include +#include + +// Miscellenaous helper declarations for unit-tests using the GoogleTest +// framework. + +namespace anbox { +namespace testing { +// RangesMatch is a useful template used to compare the content of two +// ranges at runtime. Usage is simply: +// +// EXPECT_TRUE(RangesMatch(range1, range2); +// +// Where |range1| and |range2| must have the same item type, and size. +template ::value && + common::is_range::value>> +inline ::testing::AssertionResult RangesMatch(const Range1& expected, + const Range2& actual) { + const auto expectedSize = + std::distance(std::begin(expected), std::end(expected)); + const auto actualSize = std::distance(std::begin(actual), std::end(actual)); + if (actualSize != expectedSize) { + return ::testing::AssertionFailure() + << "actual range size " << actualSize << " != expected size " + << expectedSize; + } + + auto itExp = std::begin(expected); + for (const auto& act : actual) { + if (*itExp != act) { + const auto index = std::distance(std::begin(expected), itExp); + return ::testing::AssertionFailure() + << "range[" << index << "] (" << act << ") != expected[" + << index << "] (" << *itExp << ")"; + } + ++itExp; + } + + return ::testing::AssertionSuccess(); +} +} // namespace testing +} // namespace anbox diff --git a/tests/anbox/CMakeLists.txt b/tests/anbox/CMakeLists.txt index 27724869..41692fa9 100644 --- a/tests/anbox/CMakeLists.txt +++ b/tests/anbox/CMakeLists.txt @@ -1 +1,3 @@ add_subdirectory(support) +add_subdirectory(common) +add_subdirectory(graphics) diff --git a/tests/anbox/common/CMakeLists.txt b/tests/anbox/common/CMakeLists.txt new file mode 100644 index 00000000..4d04ebef --- /dev/null +++ b/tests/anbox/common/CMakeLists.txt @@ -0,0 +1,4 @@ +ANBOX_ADD_TEST(message_channel_tests message_channel_tests.cpp) +ANBOX_ADD_TEST(small_vector_tests small_vector_tests.cpp) +ANBOX_ADD_TEST(type_traits_tests type_traits_tests.cpp) +ANBOX_ADD_TEST(scope_ptr_tests scope_ptr_tests.cpp) diff --git a/tests/anbox/common/message_channel_tests.cpp b/tests/anbox/common/message_channel_tests.cpp new file mode 100644 index 00000000..e81181a6 --- /dev/null +++ b/tests/anbox/common/message_channel_tests.cpp @@ -0,0 +1,94 @@ +// Copyright 2014 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 "anbox/common/message_channel.h" + +#include + +#include +#include + +namespace anbox { +namespace common { +TEST(MessageChannel, SingleThreadWithInt) { + MessageChannel channel; + channel.send(1); + channel.send(2); + channel.send(3); + + int ret; + channel.receive(&ret); + EXPECT_EQ(1, ret); + channel.receive(&ret); + EXPECT_EQ(2, ret); + channel.receive(&ret); + EXPECT_EQ(3, ret); +} + +TEST(MessageChannel, SingleThreadWithStdString) { + MessageChannel channel; + channel.send(std::string("foo")); + channel.send(std::string("bar")); + channel.send(std::string("zoo")); + + std::string str; + channel.receive(&str); + EXPECT_STREQ("foo", str.c_str()); + channel.receive(&str); + EXPECT_STREQ("bar", str.c_str()); + channel.receive(&str); + EXPECT_STREQ("zoo", str.c_str()); +} + +TEST(MessageChannel, TwoThreadsPingPong) { + struct PingPongState { + MessageChannel in; + MessageChannel out; + }; + + PingPongState state; + + std::thread thread([&](){ + for (;;) { + std::string str; + state.in.receive(&str); + state.out.send(str); + if (str == "quit") + break; + } + return 0; + }); + + std::string str; + const size_t kCount = 100; + for (size_t n = 0; n < kCount; ++n) { + state.in.send(std::string("foo")); + state.in.send(std::string("bar")); + state.in.send(std::string("zoo")); + state.out.receive(&str); + EXPECT_STREQ("foo", str.c_str()); + state.out.receive(&str); + EXPECT_STREQ("bar", str.c_str()); + state.out.receive(&str); + EXPECT_STREQ("zoo", str.c_str()); + } + state.in.send(std::string("quit")); + state.out.receive(&str); + EXPECT_STREQ("quit", str.c_str()); + + thread.join(); +} + +} // namespace common +} // namespace anbox diff --git a/tests/anbox/common/scope_ptr_tests.cpp b/tests/anbox/common/scope_ptr_tests.cpp new file mode 100644 index 00000000..25e300c8 --- /dev/null +++ b/tests/anbox/common/scope_ptr_tests.cpp @@ -0,0 +1,65 @@ +// Copyright (C) 2016 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 "anbox/common/scope_ptr.h" + +#include + +#include +#include + +namespace anbox { +namespace common { + +TEST(ScopedPtr, FuncDelete_conversions) { + // This test makes sure the code compiles, it doesn't do any runtime checks. + auto lambda = [](int i) { return i; }; + FuncDelete lambdaFd(lambda); + // unary + converts a captureless lambda into a raw function pointer + FuncDelete funcFd1(+lambda); + + // copy ctor + FuncDelete funcFd2(funcFd1); + // assignment operator + funcFd2 = funcFd1; + // move operator + funcFd2 = std::move(funcFd1); + + // conversion ctor + FuncDelete funcFd3(lambdaFd); + // conversion move ctor + FuncDelete funcFd4(std::move(lambdaFd)); + // conversion assignment + funcFd3 = lambdaFd; + // conversion move + funcFd3 = std::move(lambdaFd); +} + +TEST(ScopedPtr, makeCustomScopedPtr_fromLambda) { + auto freeAsLambda = [](void* ptr) { free(ptr); }; + + auto ptr1 = makeCustomScopedPtr(malloc(1), freeAsLambda); + + ScopedPtr> ptr2 = + makeCustomScopedPtr(malloc(1), freeAsLambda); + + ScopedPtr> ptr3 = + makeCustomScopedPtr(malloc(1), +freeAsLambda); + + static_assert(!std::is_same::value, + "Custom ScopedPtr<> from a lambda expression type may not " + "be the same as with a function pointer"); +} +} // namespace common +} // namespace anbox diff --git a/tests/anbox/common/small_vector_tests.cpp b/tests/anbox/common/small_vector_tests.cpp new file mode 100644 index 00000000..6b52cb70 --- /dev/null +++ b/tests/anbox/common/small_vector_tests.cpp @@ -0,0 +1,219 @@ +// Copyright 2016 The Android Open Source Project +// +// This software is licensed under the terms of the GNU General Public +// License version 2, as published by the Free Software Foundation, and +// may be copied, distributed, and modified under those terms. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +#include "anbox/common/small_vector.h" +#include "anbox/common/scope_ptr.h" +#include "anbox/testing/gtest_utils.h" + +#include + +#include + +namespace anbox { +namespace common { +TEST(SmallVector, basic) { + SmallFixedVector sv; + EXPECT_EQ(sv.size(), 0); + EXPECT_TRUE(sv.empty()); + + EXPECT_EQ(10, sv.capacity()); + EXPECT_FALSE(sv.isAllocated()); + + const int values[] = {1, 2, 3}; + sv = SmallFixedVector(values, values + 3); + EXPECT_EQ(3, sv.size()); + EXPECT_FALSE(sv.empty()); + EXPECT_EQ(10, sv.capacity()); + EXPECT_FALSE(sv.isAllocated()); + EXPECT_TRUE(anbox::testing::RangesMatch(values, sv)); + + sv.clear(); + EXPECT_EQ(0, sv.size()); + EXPECT_TRUE(sv.empty()); + + const char str[] = "this is a long string for insertion"; + sv = SmallFixedVector(str); + EXPECT_EQ(sizeof(str), sv.size()); + EXPECT_GT(sv.capacity(), 10); + EXPECT_TRUE(sv.isAllocated()); + EXPECT_TRUE(anbox::testing::RangesMatch(str, sv)); + + // make sure explicit loops over indices and iterators work + for (size_t i = 0; i != sv.size(); ++i) { + EXPECT_EQ(str[i], sv[i]); + } + const char* c = str; + for (auto i : sv) { + EXPECT_EQ(*c++, i); + } + c = str; + for (auto it = sv.begin(); it != sv.end(); ++it, ++c) { + EXPECT_EQ(*c, *it); + } + + c = str; + for (auto it = sv.data(); it != sv.data() + sv.size(); ++it, ++c) { + EXPECT_EQ(*c, *it); + } +} + +TEST(SmallVector, ctor) { + { + SmallFixedVector sv; + EXPECT_EQ(sv.size(), 0); + } + + { + const int values[] = {1, 2, 3}; + SmallFixedVector sv(values, values + 3); + EXPECT_TRUE(anbox::testing::RangesMatch(values, sv)); + } + + { + const int values[] = {1, 2, 3}; + SmallFixedVector sv(values); + EXPECT_TRUE(anbox::testing::RangesMatch(values, sv)); + } + + { + const int values[] = {1, 2, 3}; + SmallFixedVector sv(values); + EXPECT_TRUE(anbox::testing::RangesMatch(values, sv)); + } + + { + const int values[] = {1, 2, 3}; + SmallFixedVector sv = {1, 2, 3}; + EXPECT_TRUE(anbox::testing::RangesMatch(values, sv)); + } + + { + const int values[] = {1, 2, 3}; + SmallFixedVector sv1(values); + SmallFixedVector sv2(sv1); + + EXPECT_TRUE(anbox::testing::RangesMatch(values, sv1)); + EXPECT_TRUE(anbox::testing::RangesMatch(sv2, sv1)); + + SmallFixedVector sv3(std::move(sv1)); + EXPECT_TRUE(anbox::testing::RangesMatch(sv3, values)); + // don't check |sv1| - it is not required to be empty. + } +} + +TEST(SmallVector, dtor) { + // Count all destructor calls for the elements and make sure they're called + // enough times. + int destructedTimes = 0; + auto deleter = [](int* p) { ++(*p); }; + auto item1 = makeCustomScopedPtr(&destructedTimes, deleter); + auto item2 = makeCustomScopedPtr(&destructedTimes, deleter); + auto item3 = makeCustomScopedPtr(&destructedTimes, deleter); + + { + SmallFixedVector sv; + sv.emplace_back(std::move(item1)); + sv.emplace_back(std::move(item2)); + // this one is already empty, so it won't add to |destructedTimes|. + sv.emplace_back(std::move(item2)); + sv.emplace_back(std::move(item3)); + } + + EXPECT_EQ(3, destructedTimes); +} + +TEST(SmallVector, modifiers) { + SmallFixedVector sv; + + EXPECT_EQ(0, sv.size()); + EXPECT_EQ(5, sv.capacity()); + + sv.reserve(4); + EXPECT_EQ(0, sv.size()); + EXPECT_EQ(5, sv.capacity()); + EXPECT_FALSE(sv.isAllocated()); + + sv.reserve(6); + EXPECT_EQ(0, sv.size()); + EXPECT_EQ(6, sv.capacity()); + EXPECT_TRUE(sv.isAllocated()); + + sv.resize(3); + EXPECT_EQ(3, sv.size()); + EXPECT_EQ(6, sv.capacity()); + EXPECT_TRUE(sv.isAllocated()); + + sv.resize(10); + EXPECT_EQ(10, sv.size()); + EXPECT_GE(sv.capacity(), 10); + EXPECT_TRUE(sv.isAllocated()); + + sv.push_back(1); + EXPECT_EQ(11, sv.size()); + EXPECT_GE(sv.capacity(), 11); + + sv.emplace_back(2); + EXPECT_EQ(12, sv.size()); + EXPECT_GE(sv.capacity(), 12); + + sv.clear(); + EXPECT_EQ(0, sv.size()); + EXPECT_GE(sv.capacity(), 12); + + // resize_noinit() doesn't really have anything specific we can test + // compared to resize() + sv.resize_noinit(1); + EXPECT_EQ(1, sv.size()); + EXPECT_GE(sv.capacity(), 12); + + sv.resize_noinit(100); + EXPECT_EQ(100, sv.size()); + EXPECT_GE(sv.capacity(), 100); + + SmallFixedVector strings = {"a", "b", "c"}; + strings.emplace_back("d"); + strings.push_back("e"); + EXPECT_EQ(5, strings.size()); + EXPECT_EQ(5, strings.capacity()); + EXPECT_FALSE(strings.isAllocated()); + + strings.push_back(std::string("e")); + EXPECT_EQ(6, strings.size()); + EXPECT_GE(strings.capacity(), 6); + EXPECT_TRUE(strings.isAllocated()); +} + +TEST(SmallVector, useThroughInterface) { + SmallFixedVector sfv = {1, 2, 3}; + SmallVector& sv = sfv; + EXPECT_TRUE(anbox::testing::RangesMatch(sv, sfv)); + EXPECT_EQ(sv.isAllocated(), sfv.isAllocated()); + + sv.reserve(20); + EXPECT_TRUE(sv.isAllocated()); + EXPECT_EQ(sv.isAllocated(), sfv.isAllocated()); + + // now make sure that deleting through base class cleans up the memory + { + int destructedTimes = 0; + auto deleter = [](int* p) { ++(*p); }; + auto item1 = makeCustomScopedPtr(&destructedTimes, deleter); + auto item2 = makeCustomScopedPtr(&destructedTimes, deleter); + SmallVector* sv = + new SmallFixedVector(); + sv->push_back(std::move(item1)); + sv->emplace_back(std::move(item2)); + delete sv; + EXPECT_EQ(2, destructedTimes); + } +} +} // namespace common +} // namespace anbox diff --git a/tests/anbox/common/type_traits_tests.cpp b/tests/anbox/common/type_traits_tests.cpp new file mode 100644 index 00000000..a62421ef --- /dev/null +++ b/tests/anbox/common/type_traits_tests.cpp @@ -0,0 +1,84 @@ +// Copyright 2016 The Android Open Source Project +// +// This software is licensed under the terms of the GNU General Public +// License version 2, as published by the Free Software Foundation, and +// may be copied, distributed, and modified under those terms. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +#include "anbox/common/type_traits.h" + +#include + +#include +#include +#include +#include + +namespace anbox { +namespace common { +TEST(TypeTraits, IsCallable) { + class C; + C* c = nullptr; + + auto lambda = [c](bool) -> C* { return nullptr; }; + + static_assert(is_callable_as::value, "simple function"); + static_assert(is_callable_as::value, "function reference"); + static_assert(is_callable_as::value, "function pointer"); + static_assert(is_callable_as::value, + "function with arguments and return type"); + static_assert(is_callable_as::value, "lambda"); + static_assert(is_callable_as, bool(int)>::value, + "std::function"); + + static_assert(!is_callable_as::value, "int should not be callable"); + static_assert(!is_callable_as::value, "incomplete type"); + static_assert(!is_callable_as::value, "different arguments"); + static_assert(!is_callable_as::value, "different return types"); + static_assert(!is_callable_as::value, + "slightly different return types"); + static_assert(!is_callable_as::value, + "more arguments"); + static_assert(!is_callable_as::value, + "less arguments"); + + static_assert(!is_callable_as::value, + "bad required signature"); +} + +TEST(TypeTraits, IsTemplateInstantiation) { + static_assert(!is_template_instantiation_of::value, + "int is not an instance of vector"); + static_assert(!is_template_instantiation_of>, std::vector>::value, + "list is not an instance of vector"); + + static_assert(is_template_instantiation_of, std::vector>::value, + "std::vector is an instance of vector"); + static_assert(is_template_instantiation_of>>, std::vector>::value, + "nested std::vector<> is an instance of vector"); +} + +TEST(TypeTraits, IsRange) { + static_assert(is_range>::value, + "vector<> should be detected as a range"); + static_assert(is_range>>::value, + "const list<> should be detected as a range"); + static_assert(is_range, 10>>::value, + "array<> should be detected as a range"); + char arr[100]; + static_assert(is_range::value, + "C array should be detected as a range"); + static_assert(is_range::value, + "String literal should be detected as a range"); + + static_assert(!is_range::value, "int shouldn't be a range"); + static_assert(!is_range::value, "int* shouldn't be a range"); + static_assert(!is_range::value, + "even const int* shouldn't be a range"); +} +} // namespace common +} // namespace anbox diff --git a/tests/anbox/graphics/CMakeLists.txt b/tests/anbox/graphics/CMakeLists.txt new file mode 100644 index 00000000..b9c47d39 --- /dev/null +++ b/tests/anbox/graphics/CMakeLists.txt @@ -0,0 +1 @@ +ANBOX_ADD_TEST(buffer_queue_tests buffer_queue_tests.cpp) diff --git a/tests/anbox/graphics/buffer_queue_tests.cpp b/tests/anbox/graphics/buffer_queue_tests.cpp new file mode 100644 index 00000000..6ca430a3 --- /dev/null +++ b/tests/anbox/graphics/buffer_queue_tests.cpp @@ -0,0 +1,298 @@ +// Copyright (C) 2016 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 "anbox/graphics/buffer_queue.h" +#include "anbox/common/message_channel.h" +#include "anbox/logger.h" + +#include + +#include + +namespace anbox { +namespace graphics { +TEST(BufferQueue, Constructor) { + BufferQueue queue(16); +} + +TEST(BufferQueue, TryPushLocked) { + BufferQueue queue(2); + + EXPECT_EQ(0, queue.try_push_locked(Buffer("Hello"))); + EXPECT_EQ(0, queue.try_push_locked(Buffer("World"))); + + Buffer buff0("You Shall Not Move"); + EXPECT_EQ(-EAGAIN, queue.try_push_locked(std::move(buff0))); + EXPECT_FALSE(buff0.empty()) << "Buffer should not be moved on failure!"; +} + +TEST(BufferQueue, TryPushLockedOnClosedQueue) { + BufferQueue queue(2); + + EXPECT_EQ(0, queue.try_push_locked(Buffer("Hello"))); + + // Closing the queue prevents pushing new items to the queue. + queue.close_locked(); + + EXPECT_EQ(-EIO, queue.try_push_locked(Buffer("World"))); +} + +TEST(BufferQueue, TryPopLocked) { + BufferQueue queue(2);; + + Buffer buffer; + EXPECT_EQ(-EAGAIN, queue.try_pop_locked(&buffer)); + + EXPECT_EQ(0, queue.try_push_locked(Buffer("Hello"))); + EXPECT_EQ(0, queue.try_push_locked(Buffer("World"))); + + EXPECT_EQ(0, queue.try_pop_locked(&buffer)); + EXPECT_STREQ("Hello", buffer.data()); + + EXPECT_EQ(0, queue.try_pop_locked(&buffer)); + EXPECT_STREQ("World", buffer.data()); + + EXPECT_EQ(-EAGAIN, queue.try_pop_locked(&buffer)); + EXPECT_STREQ("World", buffer.data()); +} + +TEST(BufferQueue, TryPopLockedOnClosedQueue) { + BufferQueue queue(2); + + Buffer buffer; + EXPECT_EQ(-EAGAIN, queue.try_pop_locked(&buffer)); + + EXPECT_EQ(0, queue.try_push_locked(Buffer("Hello"))); + EXPECT_EQ(0, queue.try_push_locked(Buffer("World"))); + + EXPECT_EQ(0, queue.try_pop_locked(&buffer)); + EXPECT_STREQ("Hello", buffer.data()); + + // Closing the queue doesn't prevent popping existing items, but + // will generate -EIO once it is empty. + queue.close_locked(); + + EXPECT_EQ(0, queue.try_pop_locked(&buffer)); + EXPECT_STREQ("World", buffer.data()); + + EXPECT_EQ(-EIO, queue.try_pop_locked(&buffer)); + EXPECT_STREQ("World", buffer.data()); +} + +namespace { +// A TestThread instance that holds a reference to a queue and can either +// push or pull to it, on command from another thread. This uses a +// MessageChannel to implement the communication channel between the +// command thread and this one. +class TestThread final { + public: + TestThread(std::mutex &lock, BufferQueue &queue) : + lock_(lock), + queue_(queue) { + } + + bool start() { + thread_ = std::thread(&TestThread::thread_main, this); + return true; + } + + // Tell the test thread to push |buffer| to the queue. + // Call endPush() later to get the command's result. + bool start_push(Buffer&& buffer) { + input_.send(Request{Cmd::Push, std::move(buffer)}); + return true; + } + + int end_push() { + Reply reply = {}; + output_.receive(&reply); + return reply.result; + } + + // Tell the test thread to pop a buffer from the queue. + // Call end_pop() to get the command's result, as well as the popped + // buffer if it is 0. + bool start_pop() { + input_.send(Request{Cmd::Pop}); + return true; + } + + // Return the result of a previous start_pop() command. If result is + // 0, sets |*buffer| to the result buffer. + int end_pop(Buffer* buffer) { + Reply reply = {}; + output_.receive(&reply); + if (reply.result == 0) + *buffer = std::move(reply.buffer); + return reply.result; + } + + // Tell the test thread to close the queue from its side. + void do_close() { + input_.send(Request{Cmd::Close}); + } + + // Tell the test thread to stop after completing its current command. + void stop() { + input_.send(Request{Cmd::Stop}); + thread_.join(); + } + + private: + enum class Cmd { + Push, + Pop, + Close, + Stop, + }; + + struct Request { + Cmd cmd; + Buffer buffer; + }; + + struct Reply { + int result; + Buffer buffer; + }; + + void thread_main() { + while (true) { + Request r; + input_.receive(&r); + if (r.cmd == Cmd::Stop) + break; + std::unique_lock l(lock_); + Reply reply = {}; + bool sendReply = false; + switch (r.cmd) { + case Cmd::Push: + reply.result = queue_.push_locked(std::move(r.buffer), l); + sendReply = true; + break; + + case Cmd::Pop: + reply.result = queue_.pop_locked(&reply.buffer, l); + sendReply = true; + break; + + case Cmd::Close: + queue_.close_locked(); + break; + + default: + ; + } + if (sendReply) + output_.send(std::move(reply)); + } + } + + std::thread thread_; + std::mutex &lock_; + BufferQueue &queue_; + anbox::common::MessageChannel input_; + anbox::common::MessageChannel output_; +}; +} // namespace + +TEST(BufferQueue, PushLocked) { + std::mutex lock; + BufferQueue queue(2); + TestThread thread(lock, queue); + + ASSERT_TRUE(thread.start()); + ASSERT_TRUE(thread.start_pop()); + + std::unique_lock l(lock); + EXPECT_EQ(0, queue.push_locked(Buffer("Hello"), l)); + EXPECT_EQ(0, queue.push_locked(Buffer("World"), l)); + EXPECT_EQ(0, queue.push_locked(Buffer("Foo"), l)); + + thread.stop(); +} + +TEST(BufferQueue, PushLockedWithClosedQueue) { + std::mutex lock; + BufferQueue queue(2); + TestThread thread(lock, queue); + + ASSERT_TRUE(thread.start()); + + { + std::unique_lock l(lock); + EXPECT_EQ(0, queue.push_locked(Buffer("Hello"), l)); + // Closing the queue prevents pushing new items, but not + // pulling from the queue. + queue.close_locked(); + EXPECT_EQ(-EIO, queue.push_locked(Buffer("World"), l)); + } + + Buffer buffer; + ASSERT_TRUE(thread.start_pop()); + EXPECT_EQ(0, thread.end_pop(&buffer)); + EXPECT_STREQ("Hello", buffer.data()); + + thread.stop(); +} + +TEST(BufferQueue, PopLocked) { + std::mutex lock; + BufferQueue queue(2); + TestThread thread(lock, queue); + + ASSERT_TRUE(thread.start()); + ASSERT_TRUE(thread.start_push(Buffer("Hello World"))); + EXPECT_EQ(0, thread.end_push()); + + { + std::unique_lock l(lock); + Buffer buffer; + EXPECT_EQ(0, queue.pop_locked(&buffer, l)); + EXPECT_STREQ("Hello World", buffer.data()); + } + + thread.stop(); +} + +TEST(BufferQueue, PopLockedWithClosedQueue) { + std::mutex lock; + BufferQueue queue(2); + TestThread thread(lock, queue); + + ASSERT_TRUE(thread.start()); + ASSERT_TRUE(thread.start_push(Buffer("Hello World"))); + EXPECT_EQ(0, thread.end_push()); + + // Closing the queue shall not prevent pulling items from it. + // After that, -EIO shall be returned. + thread.do_close(); + + ASSERT_TRUE(thread.start_push(Buffer("Foo Bar"))); + EXPECT_EQ(-EIO, thread.end_push()); + + { + std::unique_lock l(lock); + Buffer buffer; + EXPECT_EQ(0, queue.pop_locked(&buffer, l)); + EXPECT_STREQ("Hello World", buffer.data()); + + EXPECT_EQ(-EIO, queue.pop_locked(&buffer, l)); + EXPECT_STREQ("Hello World", buffer.data()); + } + + thread.stop(); +} +} // namespace graphics +} // namespace anbox -- GitLab