| // 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 "../video_device_client.h" |
| |
| #include <fuchsia/camera2/cpp/fidl.h> |
| #include <fuchsia/camera2/hal/cpp/fidl.h> |
| #include <fuchsia/sysmem/cpp/fidl.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/default.h> |
| |
| #include "fake_camera_controller.h" |
| #include "fake_logical_buffer_collection.h" |
| #include "gtest/gtest.h" |
| #include "src/lib/syslog/cpp/logger.h" |
| namespace camera { |
| namespace { |
| |
| class VideoDeviceClientTest : public testing::Test { |
| public: |
| VideoDeviceClientTest() |
| : fake_camera_(FakeController::StandardConfigs(), fake_camera_loop_.dispatcher()), |
| fake_sysmem_(fake_sysmem_loop_.dispatcher()) { |
| fake_camera_loop_.StartThread("fake_camera_loop"); |
| fake_sysmem_loop_.StartThread("fake_sysmem_loop"); |
| } |
| ~VideoDeviceClientTest() override { |
| fake_camera_loop_.Shutdown(); |
| fake_sysmem_loop_.Shutdown(); |
| } |
| |
| async::Loop loop_{&kAsyncLoopConfigAttachToCurrentThread}; |
| async::Loop fake_camera_loop_{&kAsyncLoopConfigNoAttachToCurrentThread}; |
| async::Loop fake_sysmem_loop_{&kAsyncLoopConfigNoAttachToCurrentThread}; |
| FakeController fake_camera_; |
| FakeLogicalBufferCollection fake_sysmem_; |
| }; |
| |
| // Create VDC with interfacehandle |
| // create with invalid handle |
| // create, then close handle |
| // give error in GetConfigs |
| // give bad configs |
| TEST_F(VideoDeviceClientTest, CreateVDC) { |
| // First: a successful create call: |
| auto vdc = VideoDeviceClient::Create(fake_camera_.GetCameraConnection()); |
| ASSERT_NE(vdc, nullptr); |
| } |
| |
| TEST_F(VideoDeviceClientTest, FailToCreateVDC) { |
| // Give the VideoDeviceClient an InterfaceHandle that is not bound to a channel. |
| // VideoClientDevice should recognize that the handle is invalid and fail to |
| // be created. |
| fidl::InterfaceHandle<fuchsia::camera2::hal::Controller> bad_handle; |
| ASSERT_EQ(VideoDeviceClient::Create(std::move(bad_handle)), nullptr); |
| |
| // Create a channel, close the other end and pass it into the VideoClientDevice. |
| // VideoClientDevice should recognize that the handle is invalid and fail to |
| // be created. |
| fidl::InterfaceHandle<fuchsia::camera2::hal::Controller> closed_handle; |
| closed_handle.NewRequest().Close(ZX_ERR_ACCESS_DENIED); |
| ASSERT_EQ(VideoDeviceClient::Create(std::move(closed_handle)), nullptr); |
| |
| // Repeat the test above, but close the channel by calling reset instead |
| // of using the Close call. |
| // VideoClientDevice should recognize that the handle is invalid and fail to |
| // be created. |
| fidl::InterfaceHandle<fuchsia::camera2::hal::Controller> closed_handle2; |
| closed_handle2.NewRequest().TakeChannel().reset(); |
| ASSERT_EQ(VideoDeviceClient::Create(std::move(closed_handle2)), nullptr); |
| } |
| |
| // Each of these three tests is seperated because we can only call GetCameraConnection |
| // once per test. |
| TEST_F(VideoDeviceClientTest, CreateVdcBadConfigReturnsError) { |
| fake_camera_.set_configs_failure(FakeController::GetConfigsFailureMode::RETURNS_ERROR); |
| ASSERT_EQ(VideoDeviceClient::Create(fake_camera_.GetCameraConnection()), nullptr); |
| } |
| |
| TEST_F(VideoDeviceClientTest, CreateVdcBadConfigEmptyVector) { |
| fake_camera_.set_configs_failure(FakeController::GetConfigsFailureMode::EMPTY_VECTOR); |
| ASSERT_EQ(VideoDeviceClient::Create(fake_camera_.GetCameraConnection()), nullptr); |
| } |
| |
| TEST_F(VideoDeviceClientTest, CreateVdcBadConfigInvalid) { |
| fake_camera_.set_configs_failure(FakeController::GetConfigsFailureMode::INVALID_CONFIG); |
| ASSERT_EQ(VideoDeviceClient::Create(fake_camera_.GetCameraConnection()), nullptr); |
| } |
| |
| // MatchConstraints |
| TEST_F(VideoDeviceClientTest, FailToMatchConstraints) { |
| auto vdc = VideoDeviceClient::Create(fake_camera_.GetCameraConnection()); |
| ASSERT_NE(vdc, nullptr); |
| |
| uint32_t config_index, stream_type; |
| fuchsia::camera2::StreamConstraints constraints; |
| // Constraints are empty, should fail: |
| EXPECT_NE(ZX_OK, vdc->MatchConstraints(constraints, &config_index, &stream_type)); |
| |
| // Add properties, but no stream type: |
| fuchsia::camera2::StreamProperties empty_properties; |
| constraints.set_properties(std::move(empty_properties)); |
| EXPECT_NE(ZX_OK, vdc->MatchConstraints(constraints, &config_index, &stream_type)); |
| |
| // Set an unsupported type: |
| fuchsia::camera2::StreamProperties properties4; |
| properties4.set_stream_type(fuchsia::camera2::CameraStreamType::DOWNSCALED_RESOLUTION); |
| constraints.set_properties(std::move(properties4)); |
| EXPECT_NE(ZX_OK, vdc->MatchConstraints(constraints, &config_index, &stream_type)); |
| } |
| |
| TEST_F(VideoDeviceClientTest, MatchConstraints) { |
| auto vdc = VideoDeviceClient::Create(fake_camera_.GetCameraConnection()); |
| ASSERT_NE(vdc, nullptr); |
| // Match the stream types set up in StandardConfig(). |
| // These tests are each called individually (instead of being part of a loop) |
| // for debugging ease, should any of them fail. |
| uint32_t config_index, stream_type; |
| fuchsia::camera2::StreamConstraints constraints; |
| fuchsia::camera2::StreamProperties properties; |
| properties.set_stream_type(fuchsia::camera2::CameraStreamType::MONITORING); |
| constraints.set_properties(std::move(properties)); |
| EXPECT_EQ(ZX_OK, vdc->MatchConstraints(constraints, &config_index, &stream_type)); |
| // With standard config: |
| // Config | stream index | stream type |
| // 0 0 MACHINE_LEARNING + MONITORING |
| // 0 1 MONITORING |
| // 1 0 MACHINE_LEARNING + VIDEO_CONFERENCE |
| // 1 1 VIDEO_CONFERENCE |
| EXPECT_EQ(config_index, 0u); |
| EXPECT_EQ(stream_type, 1u); |
| |
| fuchsia::camera2::StreamProperties properties1; |
| properties1.set_stream_type(fuchsia::camera2::CameraStreamType::MONITORING | |
| fuchsia::camera2::CameraStreamType::MACHINE_LEARNING); |
| constraints.set_properties(std::move(properties1)); |
| EXPECT_EQ(ZX_OK, vdc->MatchConstraints(constraints, &config_index, &stream_type)); |
| EXPECT_EQ(config_index, 0u); |
| EXPECT_EQ(stream_type, 0u); |
| |
| fuchsia::camera2::StreamProperties properties2; |
| properties2.set_stream_type(fuchsia::camera2::CameraStreamType::VIDEO_CONFERENCE | |
| fuchsia::camera2::CameraStreamType::MACHINE_LEARNING); |
| constraints.set_properties(std::move(properties2)); |
| EXPECT_EQ(ZX_OK, vdc->MatchConstraints(constraints, &config_index, &stream_type)); |
| EXPECT_EQ(config_index, 1u); |
| EXPECT_EQ(stream_type, 0u); |
| |
| fuchsia::camera2::StreamProperties properties3; |
| properties3.set_stream_type(fuchsia::camera2::CameraStreamType::VIDEO_CONFERENCE); |
| constraints.set_properties(std::move(properties3)); |
| EXPECT_EQ(ZX_OK, vdc->MatchConstraints(constraints, &config_index, &stream_type)); |
| EXPECT_EQ(config_index, 1u); |
| EXPECT_EQ(stream_type, 1u); |
| } |
| |
| TEST_F(VideoDeviceClientTest, CreateStream) { |
| auto vdc = VideoDeviceClient::Create(fake_camera_.GetCameraConnection()); |
| loop_.RunUntilIdle(); |
| ASSERT_NE(vdc, nullptr); |
| |
| uint32_t config_index = 0, stream_type = 0, image_format_index = 0; |
| auto buffer_collection = fake_sysmem_.GetBufferCollection(); |
| fuchsia::camera2::StreamPtr stream; |
| EXPECT_EQ(ZX_OK, vdc->CreateStream(config_index, stream_type, image_format_index, |
| std::move(buffer_collection), stream.NewRequest())); |
| while (fake_camera_.GetConnections() < 1) { |
| loop_.RunUntilIdle(); |
| } |
| EXPECT_TRUE(fake_camera_.HasMatchingChannel(stream.channel())); |
| } |
| |
| TEST_F(VideoDeviceClientTest, CreateStreamBadSysmem) { |
| auto vdc = VideoDeviceClient::Create(fake_camera_.GetCameraConnection()); |
| ASSERT_NE(vdc, nullptr); |
| |
| uint32_t config_index = 0, stream_type = 0, image_format_index = 0; |
| fake_sysmem_.SetBufferError(true); |
| auto buffer_collection = fake_sysmem_.GetBufferCollection(); |
| fuchsia::camera2::StreamPtr stream; |
| EXPECT_NE(ZX_OK, vdc->CreateStream(config_index, stream_type, image_format_index, |
| std::move(buffer_collection), stream.NewRequest())); |
| } |
| |
| TEST_F(VideoDeviceClientTest, CreateStreamWithBadIndex) { |
| auto vdc = VideoDeviceClient::Create(fake_camera_.GetCameraConnection()); |
| ASSERT_NE(vdc, nullptr); |
| |
| // Standard config only has up to 2 image formats per stream |
| // Attempt to create a stream with an invalid image_format_index. |
| // The CreateStream call should fail. |
| uint32_t config_index = 0, stream_type = 0; |
| uint32_t image_format_index = fuchsia::camera2::MAX_IMAGE_FORMATS; |
| auto buffer_collection = fake_sysmem_.GetBufferCollection(); |
| fuchsia::camera2::StreamPtr stream; |
| EXPECT_NE(ZX_OK, vdc->CreateStream(config_index, stream_type, image_format_index, |
| std::move(buffer_collection), stream.NewRequest())); |
| |
| // Attempt to create a stream with an invalid stream_index. |
| // The CreateStream call should fail. |
| FakeLogicalBufferCollection fake_sysmem2(loop_.dispatcher()); |
| config_index = 0, stream_type = fuchsia::camera2::hal::MAX_STREAMS, image_format_index = 0; |
| auto buffer_collection2 = fake_sysmem2.GetBufferCollection(); |
| fuchsia::camera2::StreamPtr stream2; |
| EXPECT_NE(ZX_OK, vdc->CreateStream(config_index, stream_type, image_format_index, |
| std::move(buffer_collection2), stream2.NewRequest())); |
| |
| // Attempt to create a stream with an invalid config_index. |
| // The CreateStream call should fail. |
| FakeLogicalBufferCollection fake_sysmem3(loop_.dispatcher()); |
| config_index = fuchsia::camera2::hal::MAX_CONFIGURATIONS, stream_type = 0, image_format_index = 0; |
| auto buffer_collection3 = fake_sysmem3.GetBufferCollection(); |
| fuchsia::camera2::StreamPtr stream3; |
| EXPECT_NE(ZX_OK, vdc->CreateStream(config_index, stream_type, image_format_index, |
| std::move(buffer_collection3), stream3.NewRequest())); |
| } |
| |
| // Make sure the old connections are dropped when new connections start. |
| TEST_F(VideoDeviceClientTest, CreateStreamRemoveOldCollections) { |
| auto vdc = VideoDeviceClient::Create(fake_camera_.GetCameraConnection()); |
| ASSERT_NE(vdc, nullptr); |
| |
| // Extra instances of fake_sysmem must run on an alternate loop from the main fake_sysmem_loop_ |
| // in order to correctly destroy the local fidl bindings. |
| async::Loop fake_sysmem_secondary_loop{&kAsyncLoopConfigNoAttachToCurrentThread}; |
| ASSERT_EQ(fake_sysmem_secondary_loop.StartThread("fake_sysmem_secondary_loop"), ZX_OK); |
| |
| uint32_t config_index = 0, stream_type = 0, image_format_index = 0; |
| auto buffer_collection = fake_sysmem_.GetBufferCollection(); |
| fuchsia::camera2::StreamPtr stream; |
| EXPECT_EQ(ZX_OK, vdc->CreateStream(config_index, stream_type, image_format_index, |
| std::move(buffer_collection), stream.NewRequest())); |
| |
| loop_.RunUntilIdle(); |
| |
| // Now open another stream of the same config. |
| FakeLogicalBufferCollection fake_sysmem2(fake_sysmem_secondary_loop.dispatcher()); |
| config_index = 0, stream_type = 1, image_format_index = 0; |
| auto buffer_collection2 = fake_sysmem2.GetBufferCollection(); |
| fuchsia::camera2::StreamPtr stream2; |
| EXPECT_EQ(ZX_OK, vdc->CreateStream(config_index, stream_type, image_format_index, |
| std::move(buffer_collection2), stream2.NewRequest())); |
| |
| while (fake_camera_.GetConnections() < 2) { |
| loop_.RunUntilIdle(); |
| } |
| |
| // Expect both streams to be running now. |
| EXPECT_TRUE(fake_camera_.HasMatchingChannel(stream.channel())); |
| EXPECT_TRUE(fake_camera_.HasMatchingChannel(stream2.channel())); |
| |
| // Now attempt to create a stream from a different config. |
| // The VDC should close the previous logical buffer collection. |
| FakeLogicalBufferCollection fake_sysmem3(fake_sysmem_secondary_loop.dispatcher()); |
| config_index = 1, stream_type = 0, image_format_index = 0; |
| auto buffer_collection3 = fake_sysmem3.GetBufferCollection(); |
| fuchsia::camera2::StreamPtr stream3; |
| EXPECT_EQ(ZX_OK, vdc->CreateStream(config_index, stream_type, image_format_index, |
| std::move(buffer_collection3), stream3.NewRequest())); |
| |
| while (!fake_sysmem_.closed() || !fake_sysmem2.closed()) { |
| loop_.RunUntilIdle(); |
| } |
| // Both other streams should now have closed. |
| EXPECT_TRUE(fake_sysmem_.closed()); |
| EXPECT_TRUE(fake_sysmem2.closed()); |
| |
| // Now connect to the same config and stream type. |
| // The VDC should close the previous logical buffer collection. |
| FakeLogicalBufferCollection fake_sysmem4(fake_sysmem_secondary_loop.dispatcher()); |
| config_index = 1, stream_type = 0, image_format_index = 0; |
| auto buffer_collection4 = fake_sysmem4.GetBufferCollection(); |
| fuchsia::camera2::StreamPtr stream4; |
| EXPECT_EQ(ZX_OK, vdc->CreateStream(config_index, stream_type, image_format_index, |
| std::move(buffer_collection4), stream4.NewRequest())); |
| |
| while (!fake_sysmem3.closed()) { |
| loop_.RunUntilIdle(); |
| } |
| // That should have closed the third stream, since it was the same type. |
| EXPECT_TRUE(fake_sysmem3.closed()); |
| |
| // Terminate secondary loop prior to destroying local fake_sysmem instances. |
| fake_sysmem_secondary_loop.Shutdown(); |
| } |
| |
| } // namespace |
| } // namespace camera |