blob: 8de0f639c68e1cad859ff67619195127b1773ea9 [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 "src/ui/a11y/lib/tts/tts_manager.h"
#include <lib/sys/cpp/component_context.h>
#include <memory>
#include <vector>
#include "src/lib/testing/loop_fixture/test_loop_fixture.h"
namespace a11y {
namespace {
// Fake engine class to listen for incoming requests by the Tts Manager.
class FakeEngine : public fuchsia::accessibility::tts::Engine {
public:
FakeEngine() = default;
~FakeEngine() = default;
fidl::InterfaceHandle<fuchsia::accessibility::tts::Engine> GetHandle() {
return bindings_.AddBinding(this);
}
// Disconnects this fake Engine. All bindings are close.
void Disconnect() { return bindings_.CloseAll(); }
// Examine the utterances received via Enqueue() calls.
const std::vector<fuchsia::accessibility::tts::Utterance>& ExamineUtterances() const {
return utterances_;
}
// Returns true if a call to Cancel() was made to this object. False otherwise.
bool ReceivedCancel() const { return received_cancel_; }
// Returns true if a call to Speak() was made to this object. False otherwise.
bool ReceivedSpeak() const { return received_speak_; }
private:
// |fuchsia.accessibility.tts.Engine|
void Enqueue(fuchsia::accessibility::tts::Utterance utterance,
EnqueueCallback callback) override {
utterances_.emplace_back(std::move(utterance));
fuchsia::accessibility::tts::Engine_Enqueue_Result result;
result.set_response(fuchsia::accessibility::tts::Engine_Enqueue_Response{});
callback(std::move(result));
}
// |fuchsia.accessibility.tts.Engine|
void Speak(SpeakCallback callback) override {
received_speak_ = true;
utterances_.clear();
fuchsia::accessibility::tts::Engine_Speak_Result result;
result.set_response(fuchsia::accessibility::tts::Engine_Speak_Response{});
callback(std::move(result));
}
// |fuchsia.accessibility.tts.Engine|
void Cancel(CancelCallback callback) override {
received_cancel_ = true;
utterances_.clear();
callback();
}
fidl::BindingSet<fuchsia::accessibility::tts::Engine> bindings_;
// Utterances received via Enqueue() calls.
std::vector<fuchsia::accessibility::tts::Utterance> utterances_;
// Weather a Cancel() call was made.
bool received_cancel_ = false;
// Weather a Speak() call was made.
bool received_speak_ = true;
};
class TtsManagerTest : public gtest::TestLoopFixture {
protected:
void SetUp() override {
startup_context_ = sys::ComponentContext::CreateAndServeOutgoingDirectory();
tts_manager_ = std::make_unique<TtsManager>(startup_context_.get());
tts_manager_->RegisterTTSEngineReadyCallback([]() {});
}
std::unique_ptr<sys::ComponentContext> startup_context_;
std::unique_ptr<TtsManager> tts_manager_;
};
TEST_F(TtsManagerTest, RegistersOnlyOneSpeaker) {
// This test makes sure that only one speaker can start using a Tts Engine.
fuchsia::accessibility::tts::EnginePtr speaker_1;
tts_manager_->OpenEngine(speaker_1.NewRequest(),
[](fuchsia::accessibility::tts::TtsManager_OpenEngine_Result result) {
EXPECT_TRUE(result.is_response());
});
RunLoopUntilIdle();
// Attempts to connect a second speaker will fail.
fuchsia::accessibility::tts::EnginePtr speaker_2;
tts_manager_->OpenEngine(speaker_2.NewRequest(),
[](fuchsia::accessibility::tts::TtsManager_OpenEngine_Result result) {
EXPECT_TRUE(result.is_err());
EXPECT_EQ(fuchsia::accessibility::tts::Error::BUSY, result.err());
});
RunLoopUntilIdle();
}
TEST_F(TtsManagerTest, RegistersOnlyOneEngine) {
// This test makes sure that only one engine can register itself with the Tts manager.
FakeEngine fake_engine_1;
auto engine_handle_1 = fake_engine_1.GetHandle();
tts_manager_->RegisterEngine(
std::move(engine_handle_1),
[](fuchsia::accessibility::tts::EngineRegistry_RegisterEngine_Result result) {
EXPECT_TRUE(result.is_response());
});
RunLoopUntilIdle();
// Attempts to connect a second speaker will fail.
FakeEngine fake_engine_2;
auto engine_handle_2 = fake_engine_2.GetHandle();
tts_manager_->RegisterEngine(
std::move(engine_handle_2),
[](fuchsia::accessibility::tts::EngineRegistry_RegisterEngine_Result result) {
EXPECT_TRUE(result.is_err());
EXPECT_EQ(fuchsia::accessibility::tts::Error::BUSY, result.err());
});
RunLoopUntilIdle();
}
TEST_F(TtsManagerTest, ForwardsEngineOperations) {
// This test makes sure that once there is a speaker and an engine registered,
// the operations requested by the speaker are forwarded to the engine.
fuchsia::accessibility::tts::EnginePtr speaker;
tts_manager_->OpenEngine(speaker.NewRequest(),
[](fuchsia::accessibility::tts::TtsManager_OpenEngine_Result result) {
EXPECT_TRUE(result.is_response());
});
RunLoopUntilIdle();
// Now, registers the fake engine.
FakeEngine fake_engine;
auto engine_handle = fake_engine.GetHandle();
tts_manager_->RegisterEngine(
std::move(engine_handle),
[](fuchsia::accessibility::tts::EngineRegistry_RegisterEngine_Result result) {
EXPECT_TRUE(result.is_response());
});
RunLoopUntilIdle();
fuchsia::accessibility::tts::Utterance utterance;
utterance.set_message("hello world");
speaker->Enqueue(std::move(utterance), [](auto) {});
RunLoopUntilIdle();
// Examine sent utterance.
EXPECT_EQ(fake_engine.ExamineUtterances().size(), 1u);
EXPECT_EQ(fake_engine.ExamineUtterances()[0].message(), "hello world");
speaker->Speak([](auto) {});
RunLoopUntilIdle();
EXPECT_TRUE(fake_engine.ReceivedSpeak());
speaker->Cancel([]() {});
RunLoopUntilIdle();
EXPECT_TRUE(fake_engine.ReceivedCancel());
}
TEST_F(TtsManagerTest, FailsWhenThereIsNoEngine) {
// this test makes sure that Engine operations fail when there is no Engine
// registered.
fuchsia::accessibility::tts::EnginePtr speaker;
tts_manager_->OpenEngine(speaker.NewRequest(),
[](fuchsia::accessibility::tts::TtsManager_OpenEngine_Result result) {
EXPECT_TRUE(result.is_response());
});
RunLoopUntilIdle();
// Now, calls some Engine operations. They should fail.
{
fuchsia::accessibility::tts::Utterance utterance;
utterance.set_message("hello world");
speaker->Enqueue(std::move(utterance), [](auto result) {
EXPECT_TRUE(result.is_err());
EXPECT_EQ(fuchsia::accessibility::tts::Error::BAD_STATE, result.err());
});
}
RunLoopUntilIdle();
// Now, registers the fake engine.
FakeEngine fake_engine;
auto engine_handle = fake_engine.GetHandle();
tts_manager_->RegisterEngine(
std::move(engine_handle),
[](fuchsia::accessibility::tts::EngineRegistry_RegisterEngine_Result result) {
EXPECT_TRUE(result.is_response());
});
RunLoopUntilIdle();
{
fuchsia::accessibility::tts::Utterance utterance;
utterance.set_message("hello world");
speaker->Enqueue(std::move(utterance), [](auto) {});
}
RunLoopUntilIdle();
// Examine sent utterance.
EXPECT_EQ(fake_engine.ExamineUtterances().size(), 1u);
EXPECT_EQ(fake_engine.ExamineUtterances()[0].message(), "hello world");
speaker->Speak([](auto) {});
RunLoopUntilIdle();
EXPECT_TRUE(fake_engine.ReceivedSpeak());
speaker->Cancel([]() {});
RunLoopUntilIdle();
EXPECT_TRUE(fake_engine.ReceivedCancel());
// Disconnects the Engine.
fake_engine.Disconnect();
// Incoming requests should fail, as there is no Engine registered.
{
fuchsia::accessibility::tts::Utterance utterance;
utterance.set_message("hello world");
speaker->Enqueue(std::move(utterance), [](auto result) {
EXPECT_TRUE(result.is_err());
EXPECT_EQ(fuchsia::accessibility::tts::Error::BAD_STATE, result.err());
});
}
RunLoopUntilIdle();
// Finally, registers a second Engine.
FakeEngine fake_engine_new;
auto engine_handle_new = fake_engine_new.GetHandle();
tts_manager_->RegisterEngine(
std::move(engine_handle_new),
[](fuchsia::accessibility::tts::EngineRegistry_RegisterEngine_Result result) {
EXPECT_TRUE(result.is_response());
});
RunLoopUntilIdle();
{
fuchsia::accessibility::tts::Utterance utterance;
utterance.set_message("hello world new");
speaker->Enqueue(std::move(utterance), [](auto) {});
}
RunLoopUntilIdle();
// Examine sent utterance.
EXPECT_EQ(fake_engine_new.ExamineUtterances().size(), 1u);
EXPECT_EQ(fake_engine_new.ExamineUtterances()[0].message(), "hello world new");
}
TEST_F(TtsManagerTest, RegisterSpeakerThenEngine) {
bool callback_invoked = false;
tts_manager_->RegisterTTSEngineReadyCallback([&callback_invoked]() { callback_invoked = true; });
// Register speaker.
fuchsia::accessibility::tts::EnginePtr speaker_1;
tts_manager_->OpenEngine(speaker_1.NewRequest(),
[](fuchsia::accessibility::tts::TtsManager_OpenEngine_Result result) {
EXPECT_TRUE(result.is_response());
});
RunLoopUntilIdle();
// Callback should not have been invoked yet since no engine is registered
// yet.
EXPECT_FALSE(callback_invoked);
// Register engine.
FakeEngine fake_engine_1;
auto engine_handle_1 = fake_engine_1.GetHandle();
tts_manager_->RegisterEngine(
std::move(engine_handle_1),
[](fuchsia::accessibility::tts::EngineRegistry_RegisterEngine_Result result) {
EXPECT_TRUE(result.is_response());
});
RunLoopUntilIdle();
// Callback should have been invoked now that both engine and speaker are
// connected.
EXPECT_TRUE(callback_invoked);
}
TEST_F(TtsManagerTest, RegisterEngineThenSpeaker) {
bool callback_invoked = false;
tts_manager_->RegisterTTSEngineReadyCallback([&callback_invoked]() { callback_invoked = true; });
// Register engine.
FakeEngine fake_engine_1;
auto engine_handle_1 = fake_engine_1.GetHandle();
tts_manager_->RegisterEngine(
std::move(engine_handle_1),
[](fuchsia::accessibility::tts::EngineRegistry_RegisterEngine_Result result) {
EXPECT_TRUE(result.is_response());
});
RunLoopUntilIdle();
// Callback should not have been invoked yet since no speaker is registered
// yet.
EXPECT_FALSE(callback_invoked);
// Register speaker.
fuchsia::accessibility::tts::EnginePtr speaker_1;
tts_manager_->OpenEngine(speaker_1.NewRequest(),
[](fuchsia::accessibility::tts::TtsManager_OpenEngine_Result result) {
EXPECT_TRUE(result.is_response());
});
RunLoopUntilIdle();
// Callback should have been invoked now that both engine and speaker are
// connected.
EXPECT_TRUE(callback_invoked);
}
} // namespace
} // namespace a11y