/* * Copyright (C) 2021 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. */ //#define LOG_NDEBUG 0 #define LOG_TAG "BufferpoolUnitTest" #include #include #include #include #include #include #include #include #include #include "allocator.h" using android::hardware::configureRpcThreadpool; using android::hardware::media::bufferpool::BufferPoolData; using android::hardware::media::bufferpool::V2_0::IClientManager; using android::hardware::media::bufferpool::V2_0::ResultStatus; using android::hardware::media::bufferpool::V2_0::implementation::BufferId; using android::hardware::media::bufferpool::V2_0::implementation::ClientManager; using android::hardware::media::bufferpool::V2_0::implementation::ConnectionId; using android::hardware::media::bufferpool::V2_0::implementation::TransactionId; using namespace android; // communication message types between processes. enum PipeCommand : int32_t { INIT, TRANSFER, STOP, INIT_OK, INIT_ERROR, TRANSFER_OK, TRANSFER_ERROR, STOP_OK, STOP_ERROR, }; // communication message between processes. union PipeMessage { struct { int32_t command; int32_t memsetValue; BufferId bufferId; ConnectionId connectionId; TransactionId transactionId; int64_t timestampUs; } data; char array[0]; }; static int32_t kNumIterationCount = 10; class BufferpoolTest { public: BufferpoolTest() : mConnectionValid(false), mManager(nullptr), mAllocator(nullptr) { mConnectionId = -1; mReceiverId = -1; } ~BufferpoolTest() { if (mConnectionValid) { mManager->close(mConnectionId); } } protected: bool mConnectionValid; ConnectionId mConnectionId; ConnectionId mReceiverId; android::sp mManager; std::shared_ptr mAllocator; void setupBufferpoolManager(); }; void BufferpoolTest::setupBufferpoolManager() { // retrieving per process bufferpool object sp mManager = ClientManager::getInstance(); ASSERT_NE(mManager, nullptr) << "unable to get ClientManager\n"; mAllocator = std::make_shared(); ASSERT_NE(mAllocator, nullptr) << "unable to create TestBufferPoolAllocator\n"; // set-up local bufferpool connection for sender ResultStatus status = mManager->create(mAllocator, &mConnectionId); ASSERT_EQ(status, ResultStatus::OK) << "unable to set-up local bufferpool connection for sender\n"; mConnectionValid = true; } class BufferpoolUnitTest : public BufferpoolTest, public ::testing::Test { public: virtual void SetUp() override { setupBufferpoolManager(); } virtual void TearDown() override {} }; class BufferpoolFunctionalityTest : public BufferpoolTest, public ::testing::Test { public: virtual void SetUp() override { mReceiverPid = -1; ASSERT_TRUE(pipe(mCommandPipeFds) == 0) << "pipe connection failed for commandPipe\n"; ASSERT_TRUE(pipe(mResultPipeFds) == 0) << "pipe connection failed for resultPipe\n"; mReceiverPid = fork(); ASSERT_TRUE(mReceiverPid >= 0) << "fork failed\n"; if (mReceiverPid == 0) { doReceiver(); // In order to ignore gtest behaviour, wait for being killed from tearDown pause(); } setupBufferpoolManager(); } virtual void TearDown() override { if (mReceiverPid > 0) { kill(mReceiverPid, SIGKILL); int wstatus; wait(&wstatus); } } protected: pid_t mReceiverPid; int mCommandPipeFds[2]; int mResultPipeFds[2]; bool sendMessage(int* pipes, const PipeMessage& message) { int ret = write(pipes[1], message.array, sizeof(PipeMessage)); return ret == sizeof(PipeMessage); } bool receiveMessage(int* pipes, PipeMessage* message) { int ret = read(pipes[0], message->array, sizeof(PipeMessage)); return ret == sizeof(PipeMessage); } void doReceiver(); }; void BufferpoolFunctionalityTest::doReceiver() { // Configures the threadpool used for handling incoming RPC calls in this process. configureRpcThreadpool(1 /*threads*/, false /*willJoin*/); bool receiverRunning = true; while (receiverRunning) { PipeMessage message; receiveMessage(mCommandPipeFds, &message); ResultStatus err = ResultStatus::OK; switch (message.data.command) { case PipeCommand::INIT: { // receiver manager creation mManager = ClientManager::getInstance(); if (!mManager) { message.data.command = PipeCommand::INIT_ERROR; sendMessage(mResultPipeFds, message); return; } android::status_t status = mManager->registerAsService(); if (status != android::OK) { message.data.command = PipeCommand::INIT_ERROR; sendMessage(mResultPipeFds, message); return; } message.data.command = PipeCommand::INIT_OK; sendMessage(mResultPipeFds, message); break; } case PipeCommand::TRANSFER: { native_handle_t* receiveHandle = nullptr; std::shared_ptr receiveBuffer; err = mManager->receive(message.data.connectionId, message.data.transactionId, message.data.bufferId, message.data.timestampUs, &receiveHandle, &receiveBuffer); if (err != ResultStatus::OK) { message.data.command = PipeCommand::TRANSFER_ERROR; sendMessage(mResultPipeFds, message); return; } if (!TestBufferPoolAllocator::Verify(receiveHandle, message.data.memsetValue)) { message.data.command = PipeCommand::TRANSFER_ERROR; sendMessage(mResultPipeFds, message); return; } if (receiveHandle) { native_handle_close(receiveHandle); native_handle_delete(receiveHandle); } receiveHandle = nullptr; receiveBuffer.reset(); message.data.command = PipeCommand::TRANSFER_OK; sendMessage(mResultPipeFds, message); break; } case PipeCommand::STOP: { err = mManager->close(message.data.connectionId); if (err != ResultStatus::OK) { message.data.command = PipeCommand::STOP_ERROR; sendMessage(mResultPipeFds, message); return; } message.data.command = PipeCommand::STOP_OK; sendMessage(mResultPipeFds, message); receiverRunning = false; break; } default: ALOGE("unknown command. try again"); break; } } } // Buffer allocation test. // Check whether each buffer allocation is done successfully with unique buffer id. TEST_F(BufferpoolUnitTest, AllocateBuffer) { std::vector vecParams; getTestAllocatorParams(&vecParams); std::vector> buffers{}; std::vector allocHandle{}; ResultStatus status; for (int i = 0; i < kNumIterationCount; ++i) { native_handle_t* handle = nullptr; std::shared_ptr buffer{}; status = mManager->allocate(mConnectionId, vecParams, &handle, &buffer); ASSERT_EQ(status, ResultStatus::OK) << "allocate failed for " << i << "iteration"; buffers.push_back(std::move(buffer)); if (handle) { allocHandle.push_back(std::move(handle)); } } for (int i = 0; i < kNumIterationCount; ++i) { for (int j = i + 1; j < kNumIterationCount; ++j) { ASSERT_TRUE(buffers[i]->mId != buffers[j]->mId) << "allocated buffers are not unique"; } } // delete the buffer handles for (auto handle : allocHandle) { native_handle_close(handle); native_handle_delete(handle); } // clear the vectors buffers.clear(); allocHandle.clear(); } // Buffer recycle test. // Check whether de-allocated buffers are recycled. TEST_F(BufferpoolUnitTest, RecycleBuffer) { std::vector vecParams; getTestAllocatorParams(&vecParams); ResultStatus status; std::vector bid{}; std::vector allocHandle{}; for (int i = 0; i < kNumIterationCount; ++i) { native_handle_t* handle = nullptr; std::shared_ptr buffer; status = mManager->allocate(mConnectionId, vecParams, &handle, &buffer); ASSERT_EQ(status, ResultStatus::OK) << "allocate failed for " << i << "iteration"; bid.push_back(buffer->mId); if (handle) { allocHandle.push_back(std::move(handle)); } buffer.reset(); } std::unordered_set set(bid.begin(), bid.end()); ASSERT_EQ(set.size(), 1) << "buffers are not recycled properly"; // delete the buffer handles for (auto handle : allocHandle) { native_handle_close(handle); native_handle_delete(handle); } allocHandle.clear(); } // Validate cache evict and invalidate APIs. TEST_F(BufferpoolUnitTest, FlushTest) { std::vector vecParams; getTestAllocatorParams(&vecParams); ResultStatus status = mManager->registerSender(mManager, mConnectionId, &mReceiverId); ASSERT_TRUE(status == ResultStatus::ALREADY_EXISTS && mReceiverId == mConnectionId); // testing empty flush status = mManager->flush(mConnectionId); ASSERT_EQ(status, ResultStatus::OK) << "failed to flush connection : " << mConnectionId; std::vector> senderBuffer{}; std::vector allocHandle{}; std::vector tid{}; std::vector timestampUs{}; std::map bufferMap{}; for (int i = 0; i < kNumIterationCount; i++) { int64_t postUs; TransactionId transactionId; native_handle_t* handle = nullptr; std::shared_ptr buffer{}; status = mManager->allocate(mConnectionId, vecParams, &handle, &buffer); ASSERT_EQ(status, ResultStatus::OK) << "allocate failed for " << i << " iteration"; ASSERT_TRUE(TestBufferPoolAllocator::Fill(handle, i)); status = mManager->postSend(mReceiverId, buffer, &transactionId, &postUs); ASSERT_EQ(status, ResultStatus::OK) << "unable to post send transaction on bufferpool"; timestampUs.push_back(postUs); tid.push_back(transactionId); bufferMap.insert({transactionId, buffer->mId}); senderBuffer.push_back(std::move(buffer)); if (handle) { allocHandle.push_back(std::move(handle)); } buffer.reset(); } status = mManager->flush(mConnectionId); ASSERT_EQ(status, ResultStatus::OK) << "failed to flush connection : " << mConnectionId; std::shared_ptr receiverBuffer{}; native_handle_t* recvHandle = nullptr; for (int i = 0; i < kNumIterationCount; i++) { status = mManager->receive(mReceiverId, tid[i], senderBuffer[i]->mId, timestampUs[i], &recvHandle, &receiverBuffer); ASSERT_EQ(status, ResultStatus::OK) << "receive failed for buffer " << senderBuffer[i]->mId; // find the buffer id from transaction id auto findIt = bufferMap.find(tid[i]); ASSERT_NE(findIt, bufferMap.end()) << "inconsistent buffer mapping"; // buffer id received must be same as the buffer id sent ASSERT_EQ(findIt->second, receiverBuffer->mId) << "invalid buffer received"; ASSERT_TRUE(TestBufferPoolAllocator::Verify(recvHandle, i)) << "Message received not same as that sent"; bufferMap.erase(findIt); if (recvHandle) { native_handle_close(recvHandle); native_handle_delete(recvHandle); } recvHandle = nullptr; receiverBuffer.reset(); } ASSERT_EQ(bufferMap.size(), 0) << "buffers received is less than the number of buffers sent"; for (auto handle : allocHandle) { native_handle_close(handle); native_handle_delete(handle); } allocHandle.clear(); senderBuffer.clear(); timestampUs.clear(); } // Buffer transfer test between processes. TEST_F(BufferpoolFunctionalityTest, TransferBuffer) { // initialize the receiver PipeMessage message; message.data.command = PipeCommand::INIT; sendMessage(mCommandPipeFds, message); ASSERT_TRUE(receiveMessage(mResultPipeFds, &message)) << "receiveMessage failed\n"; ASSERT_EQ(message.data.command, PipeCommand::INIT_OK) << "receiver init failed"; android::sp receiver = IClientManager::getService(); ASSERT_NE(receiver, nullptr) << "getService failed for receiver\n"; ConnectionId receiverId; ResultStatus status = mManager->registerSender(receiver, mConnectionId, &receiverId); ASSERT_EQ(status, ResultStatus::OK) << "registerSender failed for connection id " << mConnectionId << "\n"; std::vector vecParams; getTestAllocatorParams(&vecParams); for (int i = 0; i < kNumIterationCount; ++i) { native_handle_t* handle = nullptr; std::shared_ptr buffer; status = mManager->allocate(mConnectionId, vecParams, &handle, &buffer); ASSERT_EQ(status, ResultStatus::OK) << "allocate failed for " << i << "iteration"; ASSERT_TRUE(TestBufferPoolAllocator::Fill(handle, i)) << "Fill fail for buffer handle " << handle << "\n"; // send the buffer to the receiver int64_t postUs; TransactionId transactionId; status = mManager->postSend(receiverId, buffer, &transactionId, &postUs); ASSERT_EQ(status, ResultStatus::OK) << "postSend failed for receiver " << receiverId << "\n"; // PipeMessage message; message.data.command = PipeCommand::TRANSFER; message.data.memsetValue = i; message.data.bufferId = buffer->mId; message.data.connectionId = receiverId; message.data.transactionId = transactionId; message.data.timestampUs = postUs; sendMessage(mCommandPipeFds, message); // delete buffer handle if (handle) { native_handle_close(handle); native_handle_delete(handle); } ASSERT_TRUE(receiveMessage(mResultPipeFds, &message)) << "receiveMessage failed\n"; ASSERT_EQ(message.data.command, PipeCommand::TRANSFER_OK) << "received error during buffer transfer\n"; } message.data.command = PipeCommand::STOP; sendMessage(mCommandPipeFds, message); ASSERT_TRUE(receiveMessage(mResultPipeFds, &message)) << "receiveMessage failed\n"; ASSERT_EQ(message.data.command, PipeCommand::STOP_OK) << "received error during buffer transfer\n"; } /* Validate bufferpool for following corner cases: 1. invalid connectionID 2. invalid receiver 3. when sender is not registered 4. when connection is closed */ // TODO: Enable when the issue in b/212196495 is fixed TEST_F(BufferpoolFunctionalityTest, DISABLED_ValidityTest) { std::vector vecParams; getTestAllocatorParams(&vecParams); std::shared_ptr senderBuffer; native_handle_t* allocHandle = nullptr; // call allocate() on a random connection id ConnectionId randomId = rand(); ResultStatus status = mManager->allocate(randomId, vecParams, &allocHandle, &senderBuffer); EXPECT_TRUE(status == ResultStatus::NOT_FOUND); // initialize the receiver PipeMessage message; message.data.command = PipeCommand::INIT; sendMessage(mCommandPipeFds, message); ASSERT_TRUE(receiveMessage(mResultPipeFds, &message)) << "receiveMessage failed\n"; ASSERT_EQ(message.data.command, PipeCommand::INIT_OK) << "receiver init failed"; allocHandle = nullptr; senderBuffer.reset(); status = mManager->allocate(mConnectionId, vecParams, &allocHandle, &senderBuffer); ASSERT_TRUE(TestBufferPoolAllocator::Fill(allocHandle, 0x77)); // send buffers w/o registering sender int64_t postUs; TransactionId transactionId; // random receiver status = mManager->postSend(randomId, senderBuffer, &transactionId, &postUs); ASSERT_NE(status, ResultStatus::OK) << "bufferpool shouldn't allow send on random receiver"; // establish connection android::sp receiver = IClientManager::getService(); ASSERT_NE(receiver, nullptr) << "getService failed for receiver\n"; ConnectionId receiverId; status = mManager->registerSender(receiver, mConnectionId, &receiverId); ASSERT_EQ(status, ResultStatus::OK) << "registerSender failed for connection id " << mConnectionId << "\n"; allocHandle = nullptr; senderBuffer.reset(); status = mManager->allocate(mConnectionId, vecParams, &allocHandle, &senderBuffer); ASSERT_EQ(status, ResultStatus::OK) << "allocate failed for connection " << mConnectionId; ASSERT_TRUE(TestBufferPoolAllocator::Fill(allocHandle, 0x88)); // send the buffer to the receiver status = mManager->postSend(receiverId, senderBuffer, &transactionId, &postUs); ASSERT_EQ(status, ResultStatus::OK) << "postSend failed for receiver " << receiverId << "\n"; // PipeMessage message; message.data.command = PipeCommand::TRANSFER; message.data.memsetValue = 0x88; message.data.bufferId = senderBuffer->mId; message.data.connectionId = receiverId; message.data.transactionId = transactionId; message.data.timestampUs = postUs; sendMessage(mCommandPipeFds, message); ASSERT_TRUE(receiveMessage(mResultPipeFds, &message)) << "receiveMessage failed\n"; ASSERT_EQ(message.data.command, PipeCommand::TRANSFER_OK) << "received error during buffer transfer\n"; if (allocHandle) { native_handle_close(allocHandle); native_handle_delete(allocHandle); } message.data.command = PipeCommand::STOP; sendMessage(mCommandPipeFds, message); ASSERT_TRUE(receiveMessage(mResultPipeFds, &message)) << "receiveMessage failed\n"; ASSERT_EQ(message.data.command, PipeCommand::STOP_OK) << "received error during buffer transfer\n"; // try to send msg to closed connection status = mManager->postSend(receiverId, senderBuffer, &transactionId, &postUs); ASSERT_NE(status, ResultStatus::OK) << "bufferpool shouldn't allow send on closed connection"; } int main(int argc, char** argv) { android::hardware::details::setTrebleTestingOverride(true); ::testing::InitGoogleTest(&argc, argv); int status = RUN_ALL_TESTS(); ALOGV("Test result = %d\n", status); return status; }