blob: 7c31a57142593dce13391744e69c55faa746a8eb [file] [log] [blame]
// Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <semaphore.h>
#include <list>
#include <unordered_map>
#include <base/at_exit.h>
#include <base/bind.h>
#include <base/command_line.h>
#include <base/logging.h>
#include <base/memory/writable_shared_memory_region.h>
#include <brillo/message_loops/base_message_loop.h>
#include <gtest/gtest.h>
#include "common/libcab_test_internal.h"
#include "cros-camera/camera_algorithm_bridge.h"
#include "cros-camera/common.h"
namespace libcab_test {
class CameraAlgorithmBridgeFixture : public testing::Test,
public camera_algorithm_callback_ops_t {
public:
const size_t kShmBufferSize = 2048;
CameraAlgorithmBridgeFixture() : req_id_(0) {
mojo_manager_ = cros::CameraMojoChannelManager::CreateInstance();
CameraAlgorithmBridgeFixture::return_callback =
CameraAlgorithmBridgeFixture::ReturnCallbackForwarder;
bridge_ = cros::CameraAlgorithmBridge::CreateInstance(
cros::CameraAlgorithmBackend::kTest, mojo_manager_.get());
if (!bridge_ || bridge_->Initialize(this) != 0) {
ADD_FAILURE() << "Failed to initialize camera algorithm bridge";
return;
}
sem_init(&return_sem_, 0, 0);
}
CameraAlgorithmBridgeFixture(const CameraAlgorithmBridgeFixture&) = delete;
CameraAlgorithmBridgeFixture& operator=(const CameraAlgorithmBridgeFixture&) =
delete;
~CameraAlgorithmBridgeFixture() { sem_destroy(&return_sem_); }
void Request(const std::vector<uint8_t>& req_header, int32_t buffer_handle) {
{
base::AutoLock l(request_map_lock_);
request_map_[req_id_] = buffer_handle;
}
bridge_->Request(req_id_++, req_header, buffer_handle);
}
cros::CameraMojoChannelManager* GetMojoManagerInstance() {
return mojo_manager_.get();
}
protected:
static void ReturnCallbackForwarder(
const camera_algorithm_callback_ops_t* callback_ops,
uint32_t req_id,
uint32_t status,
int32_t buffer_handle) {
if (callback_ops) {
auto s = const_cast<CameraAlgorithmBridgeFixture*>(
static_cast<const CameraAlgorithmBridgeFixture*>(callback_ops));
s->ReturnCallback(req_id, status, buffer_handle);
}
}
virtual void ReturnCallback(uint32_t req_id,
uint32_t status,
int32_t buffer_handle) {
base::AutoLock l(request_map_lock_);
if (request_map_.find(req_id) == request_map_.end() ||
request_map_[req_id] != buffer_handle) {
ADD_FAILURE()
<< "Invalid request id or handle received from the return callback";
return;
}
status_list_.push_back(status);
request_map_.erase(req_id);
sem_post(&return_sem_);
}
// |mojo_manager_| should only be destroyed after any usage of it. So it
// should be declared first.
std::unique_ptr<cros::CameraMojoChannelManager> mojo_manager_;
std::unique_ptr<cros::CameraAlgorithmBridge> bridge_;
std::list<int32_t> status_list_;
sem_t return_sem_;
private:
uint32_t req_id_;
base::Lock request_map_lock_;
std::unordered_map<uint32_t, int32_t> request_map_;
};
TEST_F(CameraAlgorithmBridgeFixture, BasicOperation) {
base::WritableSharedMemoryRegion shm_region =
base::WritableSharedMemoryRegion::Create(kShmBufferSize);
ASSERT_TRUE(shm_region.IsValid()) << "Failed to create shared memory region";
base::WritableSharedMemoryMapping shm_mapping = shm_region.Map();
ASSERT_TRUE(shm_mapping.IsValid())
<< "Failed to create shared memory mapping";
base::subtle::PlatformSharedMemoryRegion platform_shm =
base::WritableSharedMemoryRegion::TakeHandleForSerialization(
std::move(shm_region));
int32_t handle = bridge_->RegisterBuffer(platform_shm.GetPlatformHandle().fd);
ASSERT_LE(0, handle) << "Handle should be of positive value";
std::vector<uint8_t> req_header(1, REQUEST_TEST_COMMAND_NORMAL);
Request(req_header, handle);
struct timespec timeout = {};
clock_gettime(CLOCK_REALTIME, &timeout);
timeout.tv_sec += 1;
ASSERT_EQ(0, sem_timedwait(&return_sem_, &timeout));
ASSERT_EQ(0, status_list_.front());
std::vector<int32_t> handles({handle});
bridge_->DeregisterBuffers(handles);
}
TEST_F(CameraAlgorithmBridgeFixture, InvalidFdOrHandle) {
int32_t handle = bridge_->RegisterBuffer(-1);
ASSERT_GT(0, handle) << "Registering invalid fd should have failed";
base::WritableSharedMemoryRegion shm_region =
base::WritableSharedMemoryRegion::Create(kShmBufferSize);
ASSERT_TRUE(shm_region.IsValid()) << "Failed to create shared memory region";
base::WritableSharedMemoryMapping shm_mapping = shm_region.Map();
ASSERT_TRUE(shm_mapping.IsValid())
<< "Failed to create shared memory mapping";
base::subtle::PlatformSharedMemoryRegion platform_shm =
base::WritableSharedMemoryRegion::TakeHandleForSerialization(
std::move(shm_region));
handle = bridge_->RegisterBuffer(platform_shm.GetPlatformHandle().fd);
ASSERT_LE(0, handle) << "Handle should be of positive value";
std::vector<uint8_t> req_header(1, REQUEST_TEST_COMMAND_NORMAL);
Request(req_header, handle - 1);
Request(req_header, handle + 1);
struct timespec timeout = {};
clock_gettime(CLOCK_REALTIME, &timeout);
timeout.tv_sec += 1;
for (uint32_t i = 0; i < 2; i++) {
ASSERT_EQ(0, sem_timedwait(&return_sem_, &timeout));
}
for (const auto& it : status_list_) {
ASSERT_EQ(-EBADF, it);
}
std::vector<int32_t> handles({handle});
bridge_->DeregisterBuffers(handles);
// Closes the fd to fail RegisterBuffer.
// PassPlatformHandle() passes ownership to the returned |ScopedFDPair|, while
// get() does not transfer the ownership.
// When the temporary variable is destructed at the end of the statement, the
// fd is closed.
int fd = platform_shm.PassPlatformHandle().fd.get();
ASSERT_GT(0, bridge_->RegisterBuffer(fd))
<< "Registering invalid fd should have failed";
}
TEST_F(CameraAlgorithmBridgeFixture, MultiRequests) {
const size_t kNumberOfFds = 256;
std::vector<base::subtle::PlatformSharedMemoryRegion> platform_shms(
kNumberOfFds);
std::vector<int> handles(kNumberOfFds);
for (uint32_t i = 0; i < kNumberOfFds; i++) {
base::WritableSharedMemoryRegion shm_region =
base::WritableSharedMemoryRegion::Create(kShmBufferSize);
ASSERT_TRUE(shm_region.IsValid())
<< "Failed to create shared memory region";
platform_shms[i] =
base::WritableSharedMemoryRegion::TakeHandleForSerialization(
std::move(shm_region));
handles[i] =
bridge_->RegisterBuffer(platform_shms[i].GetPlatformHandle().fd);
ASSERT_LE(0, handles[i]) << "Handle should be of positive value";
}
for (const auto handle : handles) {
std::vector<uint8_t> req_header(1, REQUEST_TEST_COMMAND_NORMAL);
Request(req_header, handle);
}
struct timespec timeout = {};
clock_gettime(CLOCK_REALTIME, &timeout);
timeout.tv_sec += 1;
for (size_t i = 0; i < handles.size(); ++i) {
ASSERT_EQ(0, sem_timedwait(&return_sem_, &timeout));
}
for (const auto& it : status_list_) {
ASSERT_EQ(0, it);
}
bridge_->DeregisterBuffers(handles);
}
TEST_F(CameraAlgorithmBridgeFixture, DeadLockRecovery) {
// Create a dead lock in the algorithm.
std::vector<uint8_t> req_header(1, REQUEST_TEST_COMMAND_DEAD_LOCK);
Request(req_header, -1);
struct timespec timeout = {};
clock_gettime(CLOCK_REALTIME, &timeout);
timeout.tv_sec += 1;
ASSERT_NE(0, sem_timedwait(&return_sem_, &timeout));
// Reconnect the bridge.
bridge_ = cros::CameraAlgorithmBridge::CreateInstance(
cros::CameraAlgorithmBackend::kTest, GetMojoManagerInstance());
ASSERT_NE(nullptr, bridge_);
ASSERT_EQ(0, bridge_->Initialize(this));
base::WritableSharedMemoryRegion shm_region =
base::WritableSharedMemoryRegion::Create(kShmBufferSize);
ASSERT_TRUE(shm_region.IsValid()) << "Failed to create shared memory region";
base::WritableSharedMemoryMapping shm_mapping = shm_region.Map();
ASSERT_TRUE(shm_mapping.IsValid())
<< "Failed to create shared memory mapping";
base::subtle::PlatformSharedMemoryRegion platform_shm =
base::WritableSharedMemoryRegion::TakeHandleForSerialization(
std::move(shm_region));
int32_t handle = bridge_->RegisterBuffer(platform_shm.GetPlatformHandle().fd);
ASSERT_LE(0, handle) << "Handle should be of positive value";
req_header = std::vector<uint8_t>(1, REQUEST_TEST_COMMAND_NORMAL);
Request(req_header, handle);
clock_gettime(CLOCK_REALTIME, &timeout);
timeout.tv_sec += 1;
ASSERT_EQ(0, sem_timedwait(&return_sem_, &timeout));
ASSERT_EQ(0, status_list_.front());
std::vector<int32_t> handles({handle});
bridge_->DeregisterBuffers(handles);
}
class CameraAlgorithmBridgeStatusFixture : public CameraAlgorithmBridgeFixture {
public:
CameraAlgorithmBridgeStatusFixture() {}
CameraAlgorithmBridgeStatusFixture(
const CameraAlgorithmBridgeStatusFixture&) = delete;
CameraAlgorithmBridgeStatusFixture& operator=(
const CameraAlgorithmBridgeStatusFixture&) = delete;
protected:
void ReturnCallback(uint32_t req_id,
uint32_t status,
int32_t buffer_handle) override {
base::AutoLock l(hash_codes_lock_);
if (buffer_handle < 0 ||
static_cast<size_t>(buffer_handle) >= hash_codes_.size() ||
hash_codes_[buffer_handle] != status) {
ADD_FAILURE() << "Invalid status received from the return callback";
return;
}
sem_post(&return_sem_);
}
base::Lock hash_codes_lock_;
// Stores hashcode generated from |req_header| of the Request calls.
std::vector<uint32_t> hash_codes_;
};
static int GenerateRandomHeader(uint32_t max_header_len,
std::vector<uint8_t>* header) {
if (max_header_len == 0 || !header) {
return -EINVAL;
}
static unsigned int seed = time(NULL) + getpid();
header->resize((rand_r(&seed) % max_header_len) + 1);
for (auto& it : *header) {
it = rand_r(&seed);
}
return 0;
}
TEST_F(CameraAlgorithmBridgeStatusFixture, VerifyReturnStatus) {
const uint32_t kNumberOfTests = 256;
const uint32_t kMaxReqHeaderSize = 64;
for (uint32_t i = 0; i <= kNumberOfTests; i++) {
std::vector<uint8_t> req_header;
GenerateRandomHeader(kMaxReqHeaderSize, &req_header);
req_header[0] = REQUEST_TEST_COMMAND_VERIFY_STATUS;
{
base::AutoLock l(hash_codes_lock_);
hash_codes_.push_back(SimpleHash(req_header.data(), req_header.size()));
}
Request(req_header, i);
}
struct timespec timeout = {};
clock_gettime(CLOCK_REALTIME, &timeout);
timeout.tv_sec += 1;
for (uint32_t i = 0; i <= kNumberOfTests; i++) {
ASSERT_EQ(0, sem_timedwait(&return_sem_, &timeout));
}
}
} // namespace libcab_test
int main(int argc, char** argv) {
static base::AtExitManager exit_manager;
// Set up logging so we can enable VLOGs with -v / --vmodule.
base::CommandLine::Init(argc, argv);
logging::LoggingSettings settings;
settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
LOG_ASSERT(logging::InitLogging(settings));
brillo::BaseMessageLoop message_loop;
message_loop.SetAsCurrent();
// Initialize and run all tests
::testing::InitGoogleTest(&argc, argv);
int result = RUN_ALL_TESTS();
return result;
}