blob: da6cbe5b41fa9df3f577f5cfc840dafb9553818e [file] [log] [blame]
// Copyright 2019 The Fuchsia 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 <fuchsia/hardware/camera/cpp/fidl.h>
#include <fuchsia/hardware/sysmem/cpp/banjo.h>
#include <lib/fake_ddk/fake_ddk.h>
#include <lib/gtest/test_loop_fixture.h>
#include "src/camera/drivers/controller/controller_device.h"
#include "src/camera/drivers/controller/controller_protocol.h"
#include "src/camera/drivers/controller/test/constants.h"
#include "src/camera/drivers/controller/test/fake_sysmem.h"
namespace camera {
namespace {
class ControllerDeviceTest : public gtest::TestLoopFixture {
public:
void SetUp() override {
ddk_ = std::make_unique<fake_ddk::Bind>();
static constexpr const uint32_t kNumSubDevices = 1;
fbl::Array<fake_ddk::FragmentEntry> fragments(new fake_ddk::FragmentEntry[kNumSubDevices],
kNumSubDevices);
fragments[0].name = "sysmem";
fragments[0].protocols.emplace_back(fake_sysmem_.ProtocolEntry());
ddk_->SetFragments(std::move(fragments));
zx::event event;
ASSERT_EQ(ZX_OK, zx::event::create(0, &event));
controller_device_ =
std::make_unique<ControllerDevice>(fake_ddk::kFakeParent, std::move(event));
}
void TearDown() override {
if (controller_device_) {
controller_device_->DdkAsyncRemove();
}
ASSERT_EQ(ddk_->WaitUntilRemove(), ZX_OK);
ASSERT_TRUE(ddk_->Ok());
ddk_ = nullptr;
controller_protocol_ = nullptr;
controller_device_ = nullptr;
}
static void FailErrorHandler(zx_status_t status) {
ADD_FAILURE() << "Channel Failure: " << status;
}
static void WaitForChannelClosure(const zx::channel& channel) {
// TODO(fxbug.dev/38554): allow unidirectional message processing
// Currently, running a loop associated with fidl::InterfacePtr handles both inbound and
// outbound messages. Depending on how quickly the server handles such requests, the
// channel may or may not be closed by the time a single call to RunUntilIdle returns.
zx_status_t status = channel.wait_one(ZX_CHANNEL_PEER_CLOSED, zx::time::infinite(), nullptr);
if (status != ZX_OK) {
EXPECT_EQ(status, ZX_ERR_BAD_HANDLE);
}
}
template <class T>
void WaitForInterfaceClosure(fidl::InterfacePtr<T>& ptr, zx_status_t expected_epitaph) {
bool epitaph_received = false;
zx_status_t epitaph_status = ZX_OK;
ptr.set_error_handler([&](zx_status_t status) {
ASSERT_FALSE(epitaph_received) << "We should only get one epitaph!";
epitaph_received = true;
epitaph_status = status;
});
WaitForChannelClosure(ptr.channel());
RunLoopUntilIdle();
if (epitaph_received) { // Epitaphs are not guaranteed to be returned.
EXPECT_EQ(epitaph_status, expected_epitaph);
}
}
void BindControllerProtocol() {
ASSERT_EQ(controller_device_->DdkAdd("test-camera-controller"), ZX_OK);
ASSERT_EQ(controller_device_->StartThread(), ZX_OK);
ASSERT_EQ(camera_protocol_.Bind(std::move(ddk_->FidlClient())), ZX_OK);
camera_protocol_.set_error_handler(FailErrorHandler);
camera_protocol_->GetChannel2(controller_protocol_.NewRequest());
controller_protocol_.set_error_handler(FailErrorHandler);
RunLoopUntilIdle();
}
std::unique_ptr<fake_ddk::Bind> ddk_;
std::unique_ptr<ControllerDevice> controller_device_;
fuchsia::hardware::camera::DevicePtr camera_protocol_;
fuchsia::camera2::hal::ControllerPtr controller_protocol_;
FakeSysmem fake_sysmem_;
};
// Verifies controller can start up and shut down.
TEST_F(ControllerDeviceTest, DdkLifecycle) {
EXPECT_EQ(controller_device_->DdkAdd("test-camera-controller"), ZX_OK);
EXPECT_EQ(controller_device_->StartThread(), ZX_OK);
controller_device_->DdkAsyncRemove();
EXPECT_TRUE(ddk_->Ok());
}
// Verifies GetChannel is not supported.
TEST_F(ControllerDeviceTest, GetChannel) {
EXPECT_EQ(controller_device_->DdkAdd("test-camera-controller"), ZX_OK);
EXPECT_EQ(controller_device_->StartThread(), ZX_OK);
ASSERT_EQ(camera_protocol_.Bind(std::move(ddk_->FidlClient())), ZX_OK);
camera_protocol_->GetChannel(controller_protocol_.NewRequest().TakeChannel());
RunLoopUntilIdle();
WaitForChannelClosure(controller_protocol_.channel());
WaitForInterfaceClosure(camera_protocol_, ZX_ERR_PEER_CLOSED);
}
// Verifies that GetChannel2 works correctly.
TEST_F(ControllerDeviceTest, GetChannel2) {
EXPECT_EQ(controller_device_->DdkAdd("test-camera-controller"), ZX_OK);
EXPECT_EQ(controller_device_->StartThread(), ZX_OK);
ASSERT_EQ(camera_protocol_.Bind(std::move(ddk_->FidlClient())), ZX_OK);
camera_protocol_->GetChannel2(controller_protocol_.NewRequest());
camera_protocol_.set_error_handler(FailErrorHandler);
RunLoopUntilIdle();
}
// Verifies that GetChannel2 can only have one binding.
TEST_F(ControllerDeviceTest, GetChannel2InvokeTwice) {
EXPECT_EQ(controller_device_->DdkAdd("test-camera-controller"), ZX_OK);
EXPECT_EQ(controller_device_->StartThread(), ZX_OK);
ASSERT_EQ(camera_protocol_.Bind(std::move(ddk_->FidlClient())), ZX_OK);
camera_protocol_->GetChannel2(controller_protocol_.NewRequest());
RunLoopUntilIdle();
fuchsia::camera2::hal::ControllerPtr other_controller_protocol;
camera_protocol_->GetChannel2(other_controller_protocol.NewRequest());
RunLoopUntilIdle();
WaitForChannelClosure(other_controller_protocol.channel());
}
// Verifies sanity of returned device info.
TEST_F(ControllerDeviceTest, GetDeviceInfo) {
ASSERT_NO_FATAL_FAILURE(BindControllerProtocol());
controller_protocol_->GetDeviceInfo([](fuchsia::camera2::DeviceInfo device_info) {
EXPECT_EQ(kCameraVendorName, device_info.vendor_name());
EXPECT_EQ(kCameraProductName, device_info.product_name());
EXPECT_EQ(fuchsia::camera2::DeviceType::BUILTIN, device_info.type());
});
RunLoopUntilIdle();
}
// Verifies sanity of returned configs.
TEST_F(ControllerDeviceTest, GetNextConfig) {
ASSERT_NO_FATAL_FAILURE(BindControllerProtocol());
uint32_t number_of_configs = 0;
constexpr uint32_t kNumConfigs = 3;
bool config_populated = false;
while (number_of_configs != kNumConfigs) {
controller_protocol_->GetNextConfig(
[&](std::unique_ptr<fuchsia::camera2::hal::Config> config, zx_status_t status) {
switch (number_of_configs) {
case SherlockConfigs::MONITORING: {
// Config 0 (monitoring)
ASSERT_NE(config, nullptr);
EXPECT_EQ(config->stream_configs.at(0).properties.stream_type(),
fuchsia::camera2::CameraStreamType::FULL_RESOLUTION |
fuchsia::camera2::CameraStreamType::MACHINE_LEARNING);
EXPECT_EQ(config->stream_configs.at(1).properties.stream_type(),
fuchsia::camera2::CameraStreamType::DOWNSCALED_RESOLUTION |
fuchsia::camera2::CameraStreamType::MACHINE_LEARNING);
EXPECT_EQ(config->stream_configs.at(2).properties.stream_type(),
fuchsia::camera2::CameraStreamType::MONITORING);
break;
}
case SherlockConfigs::VIDEO: {
// Config 1 (video conferencing)
ASSERT_NE(config, nullptr);
EXPECT_EQ(config->stream_configs.at(0).properties.stream_type(),
fuchsia::camera2::CameraStreamType::VIDEO_CONFERENCE |
fuchsia::camera2::CameraStreamType::MACHINE_LEARNING |
fuchsia::camera2::CameraStreamType::FULL_RESOLUTION);
EXPECT_EQ(config->stream_configs.at(1).properties.stream_type(),
fuchsia::camera2::CameraStreamType::VIDEO_CONFERENCE);
break;
}
case SherlockConfigs::VIDEO_EXTENDED_FOV: {
// Config 2 (video conferencing with extended FOV)
ASSERT_NE(config, nullptr);
EXPECT_EQ(config->stream_configs.at(0).properties.stream_type(),
fuchsia::camera2::CameraStreamType::VIDEO_CONFERENCE |
fuchsia::camera2::CameraStreamType::MACHINE_LEARNING |
fuchsia::camera2::CameraStreamType::FULL_RESOLUTION |
fuchsia::camera2::CameraStreamType::EXTENDED_FOV);
EXPECT_EQ(config->stream_configs.at(1).properties.stream_type(),
fuchsia::camera2::CameraStreamType::VIDEO_CONFERENCE |
fuchsia::camera2::CameraStreamType::EXTENDED_FOV);
break;
}
default: {
EXPECT_EQ(config, nullptr);
EXPECT_EQ(status, ZX_ERR_STOP);
break;
}
}
config_populated = true;
number_of_configs++;
});
while (!config_populated) {
RunLoopUntilIdle();
}
config_populated = false;
}
}
TEST_F(ControllerDeviceTest, CreateStreamInvalidArgs) {
ASSERT_NO_FATAL_FAILURE(BindControllerProtocol());
fuchsia::camera2::StreamPtr stream;
std::unique_ptr<fuchsia::camera2::hal::Config> camera_config;
bool configs_populated = false;
controller_protocol_->GetNextConfig(
[&](std::unique_ptr<fuchsia::camera2::hal::Config> config, zx_status_t status) {
ASSERT_NE(config, nullptr);
ASSERT_EQ(status, ZX_OK);
configs_populated = true;
camera_config = std::move(config);
});
while (!configs_populated) {
RunLoopUntilIdle();
}
fuchsia::sysmem::BufferCollectionInfo_2 buffer_collection;
buffer_collection.buffer_count = 0;
// Invalid config index.
constexpr uint32_t kInvalidConfigIndex = 10;
controller_protocol_->CreateStream(kInvalidConfigIndex, 0, 0, std::move(buffer_collection),
stream.NewRequest());
WaitForInterfaceClosure(stream, ZX_ERR_INVALID_ARGS);
// Invalid stream index.
controller_protocol_->CreateStream(0, camera_config->stream_configs.size(), 0,
std::move(buffer_collection), stream.NewRequest());
WaitForInterfaceClosure(stream, ZX_ERR_INVALID_ARGS);
// Invalid format index.
controller_protocol_->CreateStream(0, 0, camera_config->stream_configs[0].image_formats.size(),
std::move(buffer_collection), stream.NewRequest());
WaitForInterfaceClosure(stream, ZX_ERR_INVALID_ARGS);
// Not enough buffers.
controller_protocol_->CreateStream(0, 0, 0, std::move(buffer_collection), stream.NewRequest());
WaitForInterfaceClosure(stream, ZX_ERR_INVALID_ARGS);
}
} // namespace
} // namespace camera