blob: 778a766d7dcf723dcea0a1debf2744db4ec1f639 [file] [log] [blame]
// 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 "BufferQueue.h"
#ifdef _WIN32
#undef ERROR // Make LOG(ERROR) compile properly.
#endif
#include "android/base/Log.h"
#include "android/base/synchronization/Lock.h"
#include "android/base/synchronization/MessageChannel.h"
#include "android/base/threads/Thread.h"
#include "OpenglRender/RenderChannel.h"
#include <gtest/gtest.h>
namespace emugl {
using android::base::Lock;
using android::base::AutoLock;
using Buffer = BufferQueue::Buffer;
using IoResult = BufferQueue::IoResult;
TEST(BufferQueue, Constructor) {
Lock lock;
BufferQueue queue(16, lock);
}
TEST(BufferQueue, tryPushLocked) {
Lock lock;
BufferQueue queue(2, lock);
AutoLock al(lock);
EXPECT_EQ(IoResult::Ok, queue.tryPushLocked(Buffer("Hello")));
EXPECT_EQ(IoResult::Ok, queue.tryPushLocked(Buffer("World")));
Buffer buff0("You Shall Not Move");
EXPECT_EQ(IoResult::TryAgain, queue.tryPushLocked(std::move(buff0)));
EXPECT_FALSE(buff0.empty()) << "Buffer should not be moved on failure!";
}
TEST(BufferQueue, tryPushLockedOnClosedQueue) {
Lock lock;
BufferQueue queue(2, lock);
AutoLock al(lock);
EXPECT_EQ(IoResult::Ok, queue.tryPushLocked(Buffer("Hello")));
// Closing the queue prevents pushing new items to the queue.
queue.closeLocked();
EXPECT_EQ(IoResult::Error, queue.tryPushLocked(Buffer("World")));
}
TEST(BufferQueue, tryPopLocked) {
Lock lock;
BufferQueue queue(2, lock);
AutoLock al(lock);
Buffer buffer;
EXPECT_EQ(IoResult::TryAgain, queue.tryPopLocked(&buffer));
EXPECT_EQ(IoResult::Ok, queue.tryPushLocked(Buffer("Hello")));
EXPECT_EQ(IoResult::Ok, queue.tryPushLocked(Buffer("World")));
EXPECT_EQ(IoResult::Ok, queue.tryPopLocked(&buffer));
EXPECT_STREQ("Hello", buffer.data());
EXPECT_EQ(IoResult::Ok, queue.tryPopLocked(&buffer));
EXPECT_STREQ("World", buffer.data());
EXPECT_EQ(IoResult::TryAgain, queue.tryPopLocked(&buffer));
EXPECT_STREQ("World", buffer.data());
}
TEST(BufferQueue, tryPopLockedOnClosedQueue) {
Lock lock;
BufferQueue queue(2, lock);
AutoLock al(lock);
Buffer buffer;
EXPECT_EQ(IoResult::TryAgain, queue.tryPopLocked(&buffer));
EXPECT_EQ(IoResult::Ok, queue.tryPushLocked(Buffer("Hello")));
EXPECT_EQ(IoResult::Ok, queue.tryPushLocked(Buffer("World")));
EXPECT_EQ(IoResult::Ok, queue.tryPopLocked(&buffer));
EXPECT_STREQ("Hello", buffer.data());
// Closing the queue doesn't prevent popping existing items, but
// will generate IoResult::Error once it is empty.
queue.closeLocked();
EXPECT_EQ(IoResult::Ok, queue.tryPopLocked(&buffer));
EXPECT_STREQ("World", buffer.data());
EXPECT_EQ(IoResult::Error, queue.tryPopLocked(&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 android::base::Thread {
public:
TestThread(Lock& lock, BufferQueue& queue)
// NOTE: The default stack size of android::base::Thread is too
// small and will result in runtime errors.
: mLock(lock), mQueue(queue) {}
// Tell the test thread to push |buffer| to the queue.
// Call endPush() later to get the command's result.
bool startPush(Buffer&& buffer) {
return mInput.send(
Request { .cmd = Cmd::Push, .buffer = std::move(buffer) });
}
// Get the result of a previous startPush() command.
IoResult endPush() {
Reply reply = {};
if (!mOutput.receive(&reply)) {
return IoResult::Error;
}
return reply.result;
}
// Tell the test thread to pop a buffer from the queue.
// Call endPop() to get the command's result, as well as the popped
// buffer if it is IoResult::Ok.
bool startPop() {
return mInput.send(Request { .cmd = Cmd::Pop });
}
// Return the result of a previous startPop() command. If result is
// IoResult::Ok, sets |*buffer| to the result buffer.
IoResult endPop(Buffer* buffer) {
Reply reply = {};
if (!mOutput.receive(&reply)) {
return IoResult::Error;
}
if (reply.result == IoResult::Ok) {
*buffer = std::move(reply.buffer);
}
return reply.result;
}
// Tell the test thread to close the queue from its side.
void doClose() {
mInput.send(Request { .cmd = Cmd::Close });
}
// Tell the test thread to stop after completing its current command.
void stop() {
mInput.send(Request { .cmd = Cmd::Stop });
ASSERT_TRUE(this->wait());
}
private:
enum class Cmd {
Push,
Pop,
Close,
Stop,
};
struct Request {
Cmd cmd;
Buffer buffer;
};
struct Reply {
IoResult result;
Buffer buffer;
};
// Main thread function.
virtual intptr_t main() override final {
for (;;) {
Request r;
if (!mInput.receive(&r)) {
LOG(ERROR) << "Could not receive command";
break;
}
if (r.cmd == Cmd::Stop) {
break;
}
mLock.lock();
Reply reply = {};
bool sendReply = false;
switch (r.cmd) {
case Cmd::Push:
reply.result = mQueue.pushLocked(std::move(r.buffer));
sendReply = true;
break;
case Cmd::Pop:
reply.result = mQueue.popLocked(&reply.buffer);
sendReply = true;
break;
case Cmd::Close:
mQueue.closeLocked();
break;
default:
;
}
mLock.unlock();
if (sendReply) {
if (!mOutput.send(std::move(reply))) {
LOG(ERROR) << "Could not send reply";
break;
}
}
}
return 0U;
}
Lock& mLock;
BufferQueue& mQueue;
android::base::MessageChannel<Request, 4> mInput;
android::base::MessageChannel<Reply, 4> mOutput;
};
} // namespace
TEST(BufferQueue, pushLocked) {
Lock lock;
BufferQueue queue(2, lock);
TestThread thread(lock, queue);
ASSERT_TRUE(thread.start());
ASSERT_TRUE(thread.startPop());
lock.lock();
EXPECT_EQ(IoResult::Ok, queue.pushLocked(Buffer("Hello")));
EXPECT_EQ(IoResult::Ok, queue.pushLocked(Buffer("World")));
EXPECT_EQ(IoResult::Ok, queue.pushLocked(Buffer("Foo")));
lock.unlock();
thread.stop();
}
TEST(BufferQueue, pushLockedWithClosedQueue) {
Lock lock;
BufferQueue queue(2, lock);
TestThread thread(lock, queue);
ASSERT_TRUE(thread.start());
lock.lock();
EXPECT_EQ(IoResult::Ok, queue.pushLocked(Buffer("Hello")));
// Closing the queue prevents pushing new items, but not
// pulling from the queue.
queue.closeLocked();
EXPECT_EQ(IoResult::Error, queue.pushLocked(Buffer("World")));
lock.unlock();
Buffer buffer;
ASSERT_TRUE(thread.startPop());
EXPECT_EQ(IoResult::Ok, thread.endPop(&buffer));
EXPECT_STREQ("Hello", buffer.data());
thread.stop();
}
TEST(BufferQueue, popLocked) {
Lock lock;
BufferQueue queue(2, lock);
TestThread thread(lock, queue);
ASSERT_TRUE(thread.start());
ASSERT_TRUE(thread.startPush(Buffer("Hello World")));
EXPECT_EQ(IoResult::Ok, thread.endPush());
lock.lock();
Buffer buffer;
EXPECT_EQ(IoResult::Ok, queue.popLocked(&buffer));
EXPECT_STREQ("Hello World", buffer.data());
lock.unlock();
thread.stop();
}
TEST(BufferQueue, popLockedWithClosedQueue) {
Lock lock;
BufferQueue queue(2, lock);
TestThread thread(lock, queue);
ASSERT_TRUE(thread.start());
ASSERT_TRUE(thread.startPush(Buffer("Hello World")));
EXPECT_EQ(IoResult::Ok, thread.endPush());
// Closing the queue shall not prevent pulling items from it.
// After that, IoResult::Error shall be returned.
thread.doClose();
ASSERT_TRUE(thread.startPush(Buffer("Foo Bar")));
EXPECT_EQ(IoResult::Error, thread.endPush());
lock.lock();
Buffer buffer;
EXPECT_EQ(IoResult::Ok, queue.popLocked(&buffer));
EXPECT_STREQ("Hello World", buffer.data());
EXPECT_EQ(IoResult::Error, queue.popLocked(&buffer));
EXPECT_STREQ("Hello World", buffer.data());
lock.unlock();
thread.stop();
}
} // namespace emugl