blob: e7d641b6d99dcdb7bd0b1d8e4cbf58d4e80663a2 [file] [log] [blame]
// Copyright 2017 The Chromium 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 "content/browser/renderer_host/media/service_video_capture_device_launcher.h"
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/run_loop.h"
#include "base/test/mock_callback.h"
#include "base/threading/thread.h"
#include "content/browser/renderer_host/media/ref_counted_video_source_provider.h"
#include "content/browser/renderer_host/media/service_launched_video_capture_device.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "mojo/public/cpp/bindings/binding_set.h"
#include "services/video_capture/public/cpp/mock_push_subscription.h"
#include "services/video_capture/public/cpp/mock_video_source.h"
#include "services/video_capture/public/cpp/mock_video_source_provider.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::_;
using testing::Invoke;
using testing::InvokeWithoutArgs;
namespace content {
static const std::string kStubDeviceId = "StubDevice";
static const media::VideoCaptureParams kArbitraryParams;
static const base::WeakPtr<media::VideoFrameReceiver> kNullReceiver;
class MockVideoCaptureDeviceLauncherCallbacks
: public VideoCaptureDeviceLauncher::Callbacks {
public:
void OnDeviceLaunched(
std::unique_ptr<LaunchedVideoCaptureDevice> device) override {
DoOnDeviceLaunched(&device);
}
MOCK_METHOD1(DoOnDeviceLaunched,
void(std::unique_ptr<LaunchedVideoCaptureDevice>* device));
MOCK_METHOD1(OnDeviceLaunchFailed, void(media::VideoCaptureError error));
MOCK_METHOD0(OnDeviceLaunchAborted, void());
};
class ServiceVideoCaptureDeviceLauncherTest : public testing::Test {
public:
ServiceVideoCaptureDeviceLauncherTest() {}
~ServiceVideoCaptureDeviceLauncherTest() override {}
void CloseSourceBinding() { source_binding_.reset(); }
void CloseSubscriptionBindings() {
subscription_bindings_.CloseAllBindings();
}
protected:
void SetUp() override {
source_provider_binding_ = std::make_unique<
mojo::Binding<video_capture::mojom::VideoSourceProvider>>(
&mock_source_provider_, mojo::MakeRequest(&source_provider_));
service_connection_ = base::MakeRefCounted<RefCountedVideoSourceProvider>(
std::move(source_provider_),
video_capture::mojom::DeviceFactoryProviderPtr(),
release_connection_cb_.Get());
launcher_ = std::make_unique<ServiceVideoCaptureDeviceLauncher>(
connect_to_device_factory_cb_.Get());
launcher_has_connected_to_source_provider_ = false;
launcher_has_released_source_provider_ = false;
ON_CALL(connect_to_device_factory_cb_, Run(_))
.WillByDefault(Invoke(
[this](scoped_refptr<RefCountedVideoSourceProvider>* out_provider) {
launcher_has_connected_to_source_provider_ = true;
*out_provider = service_connection_;
}));
ON_CALL(release_connection_cb_, Run())
.WillByDefault(InvokeWithoutArgs([this]() {
launcher_has_released_source_provider_ = true;
wait_for_release_connection_cb_.Quit();
}));
ON_CALL(mock_source_provider_, DoGetVideoSource(kStubDeviceId, _))
.WillByDefault(Invoke(
[this](const std::string& device_id,
video_capture::mojom::VideoSourceRequest* source_request) {
source_binding_ = std::make_unique<
mojo::Binding<video_capture::mojom::VideoSource>>(
&mock_source_, std::move(*source_request));
}));
ON_CALL(mock_source_, DoCreatePushSubscription(_, _, _, _, _))
.WillByDefault(Invoke(
[this](video_capture::mojom::ReceiverPtr& subscriber,
const media::VideoCaptureParams& requested_settings,
bool force_reopen_with_new_settings,
video_capture::mojom::PushVideoStreamSubscriptionRequest&
subscription,
video_capture::mojom::VideoSource::
CreatePushSubscriptionCallback& callback) {
subscription_bindings_.AddBinding(&mock_subscription_,
std::move(subscription));
std::move(callback).Run(
video_capture::mojom::CreatePushSubscriptionResultCode::
kCreatedWithRequestedSettings,
requested_settings);
}));
}
void TearDown() override {}
void RunLaunchingDeviceIsAbortedTest(
video_capture::mojom::CreatePushSubscriptionResultCode
service_result_code);
void RunConnectionLostAfterSuccessfulStartTest(
base::OnceClosure close_connection_cb);
TestBrowserThreadBundle thread_bundle_;
MockVideoCaptureDeviceLauncherCallbacks mock_callbacks_;
video_capture::mojom::VideoSourceProviderPtr source_provider_;
video_capture::MockVideoSourceProvider mock_source_provider_;
std::unique_ptr<mojo::Binding<video_capture::mojom::VideoSourceProvider>>
source_provider_binding_;
video_capture::MockVideoSource mock_source_;
std::unique_ptr<mojo::Binding<video_capture::mojom::VideoSource>>
source_binding_;
video_capture::MockPushSubcription mock_subscription_;
mojo::BindingSet<video_capture::mojom::PushVideoStreamSubscription>
subscription_bindings_;
std::unique_ptr<ServiceVideoCaptureDeviceLauncher> launcher_;
base::MockCallback<base::OnceClosure> connection_lost_cb_;
base::MockCallback<base::OnceClosure> done_cb_;
base::MockCallback<
ServiceVideoCaptureDeviceLauncher::ConnectToDeviceFactoryCB>
connect_to_device_factory_cb_;
base::MockCallback<base::OnceClosure> release_connection_cb_;
// |service_connection_| needs to go below |release_connection_cb_|, because
// its destructor will call |release_connection_cb_|.
scoped_refptr<RefCountedVideoSourceProvider> service_connection_;
bool launcher_has_connected_to_source_provider_;
bool launcher_has_released_source_provider_;
base::RunLoop wait_for_release_connection_cb_;
private:
DISALLOW_COPY_AND_ASSIGN(ServiceVideoCaptureDeviceLauncherTest);
};
TEST_F(ServiceVideoCaptureDeviceLauncherTest, LaunchingDeviceSucceeds) {
EXPECT_CALL(mock_callbacks_, DoOnDeviceLaunched(_)).Times(1);
EXPECT_CALL(mock_callbacks_, OnDeviceLaunchAborted()).Times(0);
EXPECT_CALL(mock_callbacks_, OnDeviceLaunchFailed(_)).Times(0);
EXPECT_CALL(connection_lost_cb_, Run()).Times(0);
base::RunLoop wait_for_done_cb;
EXPECT_CALL(done_cb_, Run())
.WillOnce(InvokeWithoutArgs(
[&wait_for_done_cb]() { wait_for_done_cb.Quit(); }));
// Exercise
launcher_->LaunchDeviceAsync(kStubDeviceId, blink::MEDIA_DEVICE_VIDEO_CAPTURE,
kArbitraryParams, kNullReceiver,
connection_lost_cb_.Get(), &mock_callbacks_,
done_cb_.Get());
wait_for_done_cb.Run();
launcher_.reset();
service_connection_.reset();
wait_for_release_connection_cb_.Run();
EXPECT_TRUE(launcher_has_connected_to_source_provider_);
EXPECT_TRUE(launcher_has_released_source_provider_);
}
TEST_F(ServiceVideoCaptureDeviceLauncherTest,
LaunchingDeviceIsAbortedBeforeServiceRespondsWithSuccess) {
RunLaunchingDeviceIsAbortedTest(
video_capture::mojom::CreatePushSubscriptionResultCode::
kCreatedWithRequestedSettings);
}
TEST_F(ServiceVideoCaptureDeviceLauncherTest,
LaunchingDeviceIsAbortedBeforeServiceRespondsWithNotFound) {
RunLaunchingDeviceIsAbortedTest(
video_capture::mojom::CreatePushSubscriptionResultCode::
kCreatedWithDifferentSettings);
}
TEST_F(ServiceVideoCaptureDeviceLauncherTest,
LaunchingDeviceIsAbortedBeforeServiceRespondsWithNotInitialized) {
RunLaunchingDeviceIsAbortedTest(
video_capture::mojom::CreatePushSubscriptionResultCode::kFailed);
}
void ServiceVideoCaptureDeviceLauncherTest::RunLaunchingDeviceIsAbortedTest(
video_capture::mojom::CreatePushSubscriptionResultCode
service_result_code) {
base::RunLoop step_1_run_loop;
base::RunLoop step_2_run_loop;
base::OnceClosure create_push_subscription_success_answer_cb;
EXPECT_CALL(mock_source_, DoCreatePushSubscription(_, _, _, _, _))
.WillOnce(Invoke(
[&create_push_subscription_success_answer_cb, &step_1_run_loop,
service_result_code](
video_capture::mojom::ReceiverPtr& subscriber,
const media::VideoCaptureParams& requested_settings,
bool force_reopen_with_new_settings,
video_capture::mojom::PushVideoStreamSubscriptionRequest&
subscription,
video_capture::mojom::VideoSource::CreatePushSubscriptionCallback&
callback) {
// Prepare the callback, but save it for now instead of invoking it.
create_push_subscription_success_answer_cb = base::BindOnce(
[](const media::VideoCaptureParams& requested_settings,
video_capture::mojom::PushVideoStreamSubscriptionRequest
subscription,
video_capture::mojom::VideoSource::
CreatePushSubscriptionCallback callback,
video_capture::mojom::CreatePushSubscriptionResultCode
service_result_code) {
std::move(callback).Run(service_result_code,
requested_settings);
},
requested_settings, std::move(subscription),
std::move(callback), service_result_code);
step_1_run_loop.Quit();
}));
EXPECT_CALL(mock_callbacks_, DoOnDeviceLaunched(_)).Times(0);
EXPECT_CALL(mock_callbacks_, OnDeviceLaunchAborted()).Times(1);
EXPECT_CALL(mock_callbacks_, OnDeviceLaunchFailed(_)).Times(0);
EXPECT_CALL(connection_lost_cb_, Run()).Times(0);
EXPECT_CALL(done_cb_, Run()).WillOnce(InvokeWithoutArgs([&step_2_run_loop]() {
step_2_run_loop.Quit();
}));
// Exercise
launcher_->LaunchDeviceAsync(kStubDeviceId, blink::MEDIA_DEVICE_VIDEO_CAPTURE,
kArbitraryParams, kNullReceiver,
connection_lost_cb_.Get(), &mock_callbacks_,
done_cb_.Get());
step_1_run_loop.Run();
launcher_->AbortLaunch();
std::move(create_push_subscription_success_answer_cb).Run();
step_2_run_loop.Run();
service_connection_.reset();
EXPECT_TRUE(launcher_has_connected_to_source_provider_);
EXPECT_TRUE(launcher_has_released_source_provider_);
}
TEST_F(ServiceVideoCaptureDeviceLauncherTest,
LaunchingDeviceFailsBecauseDeviceNotFound) {
base::RunLoop run_loop;
EXPECT_CALL(mock_source_, DoCreatePushSubscription(_, _, _, _, _))
.WillOnce(Invoke(
[](video_capture::mojom::ReceiverPtr& subscriber,
const media::VideoCaptureParams& requested_settings,
bool force_reopen_with_new_settings,
video_capture::mojom::PushVideoStreamSubscriptionRequest&
subscription,
video_capture::mojom::VideoSource::CreatePushSubscriptionCallback&
callback) {
// Note: We post this to the end of the message queue to make it
// asynchronous.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(
[](video_capture::mojom::ReceiverPtr subscriber,
const media::VideoCaptureParams& requested_settings,
video_capture::mojom::PushVideoStreamSubscriptionRequest
subscription,
video_capture::mojom::VideoSource::
CreatePushSubscriptionCallback callback) {
std::move(callback).Run(
video_capture::mojom::
CreatePushSubscriptionResultCode::kFailed,
requested_settings);
},
std::move(subscriber), requested_settings,
std::move(subscription), std::move(callback)));
}));
EXPECT_CALL(mock_callbacks_, DoOnDeviceLaunched(_)).Times(0);
EXPECT_CALL(mock_callbacks_, OnDeviceLaunchAborted()).Times(0);
EXPECT_CALL(mock_callbacks_,
OnDeviceLaunchFailed(
media::VideoCaptureError::
kServiceDeviceLauncherServiceRespondedWithDeviceNotFound))
.Times(1);
EXPECT_CALL(connection_lost_cb_, Run()).Times(0);
EXPECT_CALL(done_cb_, Run()).WillOnce(InvokeWithoutArgs([&run_loop]() {
run_loop.Quit();
}));
// Exercise
launcher_->LaunchDeviceAsync(kStubDeviceId, blink::MEDIA_DEVICE_VIDEO_CAPTURE,
kArbitraryParams, kNullReceiver,
connection_lost_cb_.Get(), &mock_callbacks_,
done_cb_.Get());
run_loop.Run();
}
TEST_F(ServiceVideoCaptureDeviceLauncherTest,
LaunchingDeviceFailsBecauseSourceProviderIsUnbound) {
base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
EXPECT_CALL(mock_callbacks_, DoOnDeviceLaunched(_)).Times(0);
EXPECT_CALL(mock_callbacks_, OnDeviceLaunchAborted()).Times(0);
EXPECT_CALL(mock_callbacks_, OnDeviceLaunchFailed(_)).Times(1);
EXPECT_CALL(connection_lost_cb_, Run()).Times(0);
EXPECT_CALL(done_cb_, Run()).WillOnce(InvokeWithoutArgs([&run_loop]() {
run_loop.Quit();
}));
// Exercise
service_connection_->ReleaseProviderForTesting();
launcher_->LaunchDeviceAsync(kStubDeviceId, blink::MEDIA_DEVICE_VIDEO_CAPTURE,
kArbitraryParams, kNullReceiver,
connection_lost_cb_.Get(), &mock_callbacks_,
done_cb_.Get());
run_loop.Run();
}
TEST_F(ServiceVideoCaptureDeviceLauncherTest,
LaunchingDeviceFailsBecauseConnectionLostWhileLaunching) {
base::RunLoop run_loop;
video_capture::mojom::VideoSource::CreatePushSubscriptionCallback
create_subscription_cb;
EXPECT_CALL(mock_source_, DoCreatePushSubscription(_, _, _, _, _))
.WillOnce(Invoke(
[&create_subscription_cb](
video_capture::mojom::ReceiverPtr& subscriber,
const media::VideoCaptureParams& requested_settings,
bool force_reopen_with_new_settings,
video_capture::mojom::PushVideoStreamSubscriptionRequest&
subscription,
video_capture::mojom::VideoSource::CreatePushSubscriptionCallback&
callback) {
// Simulate connection lost by not invoking |callback| and releasing
// |subscription|. We have to save |callback| and invoke it later
// to avoid hitting a DCHECK.
create_subscription_cb = std::move(callback);
}));
EXPECT_CALL(mock_callbacks_, DoOnDeviceLaunched(_)).Times(0);
EXPECT_CALL(mock_callbacks_, OnDeviceLaunchAborted()).Times(0);
EXPECT_CALL(mock_callbacks_, OnDeviceLaunchFailed(_)).Times(1);
// Note: |connection_lost_cb_| is only meant to be called when the connection
// to a successfully-launched device is lost, which is not the case here.
EXPECT_CALL(connection_lost_cb_, Run()).Times(0);
EXPECT_CALL(done_cb_, Run()).WillOnce(InvokeWithoutArgs([&run_loop]() {
run_loop.Quit();
}));
// Exercise
launcher_->LaunchDeviceAsync(kStubDeviceId, blink::MEDIA_DEVICE_VIDEO_CAPTURE,
kArbitraryParams, kNullReceiver,
connection_lost_cb_.Get(), &mock_callbacks_,
done_cb_.Get());
run_loop.Run();
// Cleanup
// Cut the connection to the source, so that the outstanding
// |create_subscription_cb| will be dropped when we invoke it below.
source_binding_.reset();
// We have to invoke the callback, because not doing so triggers a DCHECK.
const video_capture::mojom::CreatePushSubscriptionResultCode
arbitrary_result_code = video_capture::mojom::
CreatePushSubscriptionResultCode::kCreatedWithRequestedSettings;
std::move(create_subscription_cb)
.Run(arbitrary_result_code, kArbitraryParams);
}
TEST_F(ServiceVideoCaptureDeviceLauncherTest,
ConnectionToSubscriptionLostAfterSuccessfulLaunch) {
RunConnectionLostAfterSuccessfulStartTest(
base::BindOnce(&ServiceVideoCaptureDeviceLauncherTest::CloseSourceBinding,
base::Unretained(this)));
}
TEST_F(ServiceVideoCaptureDeviceLauncherTest,
ConnectionToSourceLostAfterSuccessfulLaunch) {
RunConnectionLostAfterSuccessfulStartTest(base::BindOnce(
&ServiceVideoCaptureDeviceLauncherTest::CloseSubscriptionBindings,
base::Unretained(this)));
}
void ServiceVideoCaptureDeviceLauncherTest::
RunConnectionLostAfterSuccessfulStartTest(
base::OnceClosure close_connection_cb) {
std::unique_ptr<LaunchedVideoCaptureDevice> launched_device;
EXPECT_CALL(mock_callbacks_, DoOnDeviceLaunched(_))
.WillOnce(
Invoke([&launched_device](
std::unique_ptr<LaunchedVideoCaptureDevice>* device) {
// We must keep the launched device alive, because otherwise it will
// no longer listen for connection errors.
launched_device = std::move(*device);
}));
base::RunLoop step_1_run_loop;
EXPECT_CALL(done_cb_, Run()).WillOnce(InvokeWithoutArgs([&step_1_run_loop]() {
step_1_run_loop.Quit();
}));
// Exercise step 1
launcher_->LaunchDeviceAsync(kStubDeviceId, blink::MEDIA_DEVICE_VIDEO_CAPTURE,
kArbitraryParams, kNullReceiver,
connection_lost_cb_.Get(), &mock_callbacks_,
done_cb_.Get());
step_1_run_loop.Run();
base::RunLoop step_2_run_loop;
EXPECT_CALL(connection_lost_cb_, Run()).WillOnce(Invoke([&step_2_run_loop]() {
step_2_run_loop.Quit();
}));
// Exercise step 2: The service cuts/loses the connection
std::move(close_connection_cb).Run();
step_2_run_loop.Run();
launcher_.reset();
service_connection_.reset();
wait_for_release_connection_cb_.Run();
EXPECT_TRUE(launcher_has_connected_to_source_provider_);
EXPECT_TRUE(launcher_has_released_source_provider_);
}
} // namespace content