| /* |
| * 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 <utils/Log.h> |
| |
| #include <binder/ProcessState.h> |
| #include <bufferpool/ClientManager.h> |
| #include <gtest/gtest.h> |
| #include <hidl/LegacySupport.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| #include <unordered_set> |
| #include <vector> |
| #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<ClientManager> mManager; |
| std::shared_ptr<BufferPoolAllocator> mAllocator; |
| |
| void setupBufferpoolManager(); |
| }; |
| |
| void BufferpoolTest::setupBufferpoolManager() { |
| // retrieving per process bufferpool object sp<ClientManager> |
| mManager = ClientManager::getInstance(); |
| ASSERT_NE(mManager, nullptr) << "unable to get ClientManager\n"; |
| |
| mAllocator = std::make_shared<TestBufferPoolAllocator>(); |
| 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<BufferPoolData> 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<uint8_t> vecParams; |
| getTestAllocatorParams(&vecParams); |
| |
| std::vector<std::shared_ptr<BufferPoolData>> buffers{}; |
| std::vector<native_handle_t*> allocHandle{}; |
| ResultStatus status; |
| for (int i = 0; i < kNumIterationCount; ++i) { |
| native_handle_t* handle = nullptr; |
| std::shared_ptr<BufferPoolData> 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<uint8_t> vecParams; |
| getTestAllocatorParams(&vecParams); |
| |
| ResultStatus status; |
| std::vector<BufferId> bid{}; |
| std::vector<native_handle_t*> allocHandle{}; |
| for (int i = 0; i < kNumIterationCount; ++i) { |
| native_handle_t* handle = nullptr; |
| std::shared_ptr<BufferPoolData> 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<BufferId> 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<uint8_t> 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<std::shared_ptr<BufferPoolData>> senderBuffer{}; |
| std::vector<native_handle_t*> allocHandle{}; |
| std::vector<TransactionId> tid{}; |
| std::vector<int64_t> timestampUs{}; |
| |
| std::map<TransactionId, BufferId> bufferMap{}; |
| |
| for (int i = 0; i < kNumIterationCount; i++) { |
| int64_t postUs; |
| TransactionId transactionId; |
| native_handle_t* handle = nullptr; |
| std::shared_ptr<BufferPoolData> 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<BufferPoolData> 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<IClientManager> 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<uint8_t> vecParams; |
| getTestAllocatorParams(&vecParams); |
| |
| for (int i = 0; i < kNumIterationCount; ++i) { |
| native_handle_t* handle = nullptr; |
| std::shared_ptr<BufferPoolData> 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<uint8_t> vecParams; |
| getTestAllocatorParams(&vecParams); |
| |
| std::shared_ptr<BufferPoolData> 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<IClientManager> 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; |
| } |