1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
|
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/live_caption/caption_bubble_context_remote.h"
#include <optional>
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "media/mojo/mojom/speech_recognition.mojom.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/geometry/rect.h"
namespace captions {
namespace {
using media::mojom::SpeechRecognitionSurface;
using testing::_;
using testing::Test;
// A surface whose methods can have expectations placed on them.
class MockSurface : public SpeechRecognitionSurface {
public:
explicit MockSurface(mojo::PendingReceiver<SpeechRecognitionSurface> receiver)
: receiver_(this, std::move(receiver)) {}
~MockSurface() override = default;
MockSurface(const MockSurface&) = delete;
MockSurface& operator=(const MockSurface&) = delete;
// media::mojom::SpeechRecognitionSurface:
MOCK_METHOD(void, Activate, (), (override));
MOCK_METHOD(void,
GetBounds,
(SpeechRecognitionSurface::GetBoundsCallback),
(override));
private:
mojo::Receiver<SpeechRecognitionSurface> receiver_;
};
class CaptionBubbleContextRemoteTest : public Test {
public:
CaptionBubbleContextRemoteTest() = default;
~CaptionBubbleContextRemoteTest() override = default;
CaptionBubbleContextRemoteTest(const CaptionBubbleContextRemoteTest&) =
delete;
CaptionBubbleContextRemoteTest& operator=(
const CaptionBubbleContextRemoteTest&) = delete;
void SetUp() override {
mojo::PendingReceiver<SpeechRecognitionSurface> receiver;
context_.emplace(receiver.InitWithNewPipeAndPassRemote(), "session-id");
surface_.emplace(std::move(receiver));
}
protected:
// Use optionals to delay initialization while keeping objects on the stack.
std::optional<CaptionBubbleContextRemote> context_;
std::optional<MockSurface> surface_;
private:
base::test::TaskEnvironment task_environment_;
};
// Test that activate requests are forwarded to the remote process.
TEST_F(CaptionBubbleContextRemoteTest, Activate) {
EXPECT_CALL(*surface_, Activate()).Times(2);
// Our expectation that the activate call is forwarded over Mojo should be
// met.
context_->Activate();
context_->Activate();
base::RunLoop().RunUntilIdle();
}
// Test that bounds requests are forwarded to the remote process.
TEST_F(CaptionBubbleContextRemoteTest, GetBounds) {
// Note expectations are saturated from last to first.
const gfx::Rect expected_bounds_1 = gfx::Rect(1, 2, 3, 4);
const gfx::Rect expected_bounds_2 = gfx::Rect(5, 6, 7, 8);
EXPECT_CALL(*surface_, GetBounds(_))
.WillOnce([&](auto cb) { std::move(cb).Run(expected_bounds_2); })
.RetiresOnSaturation();
EXPECT_CALL(*surface_, GetBounds(_))
.WillOnce([&](auto cb) { std::move(cb).Run(expected_bounds_1); })
.RetiresOnSaturation();
// First call should correctly fetch first bounds.
gfx::Rect actual_bounds;
context_->GetBounds(base::BindLambdaForTesting(
[&](const gfx::Rect& bounds) { actual_bounds = bounds; }));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(expected_bounds_1, actual_bounds);
// Next call should correctly fetch updated bounds.
context_->GetBounds(base::BindLambdaForTesting(
[&](const gfx::Rect& bounds) { actual_bounds = bounds; }));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(expected_bounds_2, actual_bounds);
}
// Test that replacing an observer is handled gracefully.
TEST_F(CaptionBubbleContextRemoteTest, DuplicateObservers) {
bool ended_1 = false;
bool ended_2 = false;
// This observer will be replaced before it can execute its callback.
auto observer_1 = context_->GetCaptionBubbleSessionObserver();
observer_1->SetEndSessionCallback(base::BindLambdaForTesting(
[&](const std::string& id) { ended_1 = id == "session-id"; }));
// Creating a new observer will invalidate the old one.
auto observer_2 = context_->GetCaptionBubbleSessionObserver();
observer_2->SetEndSessionCallback(base::BindLambdaForTesting(
[&](const std::string& id) { ended_2 = id == "session-id"; }));
// Trigger session end and make sure one callback is called.
context_->OnSessionEnded();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(ended_1);
EXPECT_TRUE(ended_2);
}
// If the observer is dead by the time the session is ended, it shouldn't be
// exercised.
TEST_F(CaptionBubbleContextRemoteTest, DeadObserver) {
auto observer = context_->GetCaptionBubbleSessionObserver();
observer->SetEndSessionCallback(base::BindLambdaForTesting(
[&](const std::string& id) { EXPECT_TRUE(false); }));
observer.reset();
// Trigger session end.
context_->OnSessionEnded();
base::RunLoop().RunUntilIdle();
// We shouldn't try to call methods on the destructed observer.
}
} // namespace
} // namespace captions
|