// Copyright (c) 2012 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 #include "base/bind.h" #include "base/command_line.h" #include "base/location.h" #include "base/memory/weak_ptr.h" #include "base/single_thread_task_runner.h" #include "base/thread_task_runner_handle.h" #include "chrome/browser/extensions/component_loader.h" #include "chrome/browser/extensions/extension_apitest.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/speech/extension_api/tts_extension_api.h" #include "chrome/browser/speech/tts_controller.h" #include "chrome/browser/speech/tts_platform.h" #include "chrome/common/chrome_switches.h" #include "content/public/browser/notification_service.h" #include "content/public/test/test_utils.h" #include "extensions/browser/extension_system.h" #include "net/base/network_change_notifier.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" // Needed for CreateFunctor. #define GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING #include "testing/gmock_mutant.h" using ::testing::AnyNumber; using ::testing::CreateFunctor; using ::testing::DoAll; using ::testing::Invoke; using ::testing::InSequence; using ::testing::InvokeWithoutArgs; using ::testing::Return; using ::testing::SaveArg; using ::testing::SetArgPointee; using ::testing::StrictMock; using ::testing::_; namespace { int g_saved_utterance_id; } namespace extensions { class MockTtsPlatformImpl : public TtsPlatformImpl { public: MockTtsPlatformImpl() : should_fake_get_voices_(false), ptr_factory_(this) {} virtual bool PlatformImplAvailable() { return true; } MOCK_METHOD5(Speak, bool(int utterance_id, const std::string& utterance, const std::string& lang, const VoiceData& voice, const UtteranceContinuousParameters& params)); MOCK_METHOD0(StopSpeaking, bool(void)); MOCK_METHOD0(Pause, void(void)); MOCK_METHOD0(Resume, void(void)); MOCK_METHOD0(IsSpeaking, bool(void)); // Fake this method to add a native voice. void GetVoices(std::vector* voices) { if (!should_fake_get_voices_) return; VoiceData voice; voice.name = "TestNativeVoice"; voice.native = true; voice.lang = "en-GB"; voice.events.insert(TTS_EVENT_START); voice.events.insert(TTS_EVENT_END); voices->push_back(voice); } void set_should_fake_get_voices(bool val) { should_fake_get_voices_ = val; } void SetErrorToEpicFail() { set_error("epic fail"); } void SendEndEventOnSavedUtteranceId() { base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( FROM_HERE, base::Bind(&MockTtsPlatformImpl::SendEvent, ptr_factory_.GetWeakPtr(), false, g_saved_utterance_id, TTS_EVENT_END, 0, std::string()), base::TimeDelta()); } void SendEndEvent(int utterance_id, const std::string& utterance, const std::string& lang, const VoiceData& voice, const UtteranceContinuousParameters& params) { base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( FROM_HERE, base::Bind(&MockTtsPlatformImpl::SendEvent, ptr_factory_.GetWeakPtr(), false, utterance_id, TTS_EVENT_END, utterance.size(), std::string()), base::TimeDelta()); } void SendEndEventWhenQueueNotEmpty( int utterance_id, const std::string& utterance, const std::string& lang, const VoiceData& voice, const UtteranceContinuousParameters& params) { base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( FROM_HERE, base::Bind(&MockTtsPlatformImpl::SendEvent, ptr_factory_.GetWeakPtr(), true, utterance_id, TTS_EVENT_END, utterance.size(), std::string()), base::TimeDelta()); } void SendWordEvents(int utterance_id, const std::string& utterance, const std::string& lang, const VoiceData& voice, const UtteranceContinuousParameters& params) { for (int i = 0; i < static_cast(utterance.size()); i++) { if (i == 0 || utterance[i - 1] == ' ') { base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( FROM_HERE, base::Bind(&MockTtsPlatformImpl::SendEvent, ptr_factory_.GetWeakPtr(), false, utterance_id, TTS_EVENT_WORD, i, std::string()), base::TimeDelta()); } } } void SendEvent(bool wait_for_non_empty_queue, int utterance_id, TtsEventType event_type, int char_index, const std::string& message) { TtsController* controller = TtsController::GetInstance(); if (wait_for_non_empty_queue && controller->QueueSize() == 0) { base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( FROM_HERE, base::Bind(&MockTtsPlatformImpl::SendEvent, ptr_factory_.GetWeakPtr(), true, utterance_id, event_type, char_index, message), base::TimeDelta::FromMilliseconds(100)); return; } controller->OnTtsEvent(utterance_id, event_type, char_index, message); } private: bool should_fake_get_voices_; base::WeakPtrFactory ptr_factory_; }; class FakeNetworkOnlineStateForTest : public net::NetworkChangeNotifier { public: explicit FakeNetworkOnlineStateForTest(bool online) : online_(online) {} ~FakeNetworkOnlineStateForTest() override {} ConnectionType GetCurrentConnectionType() const override { return online_ ? net::NetworkChangeNotifier::CONNECTION_ETHERNET : net::NetworkChangeNotifier::CONNECTION_NONE; } private: bool online_; DISALLOW_COPY_AND_ASSIGN(FakeNetworkOnlineStateForTest); }; class TtsApiTest : public ExtensionApiTest { public: virtual void SetUpInProcessBrowserTestFixture() { ExtensionApiTest::SetUpInProcessBrowserTestFixture(); TtsController::GetInstance()->SetPlatformImpl(&mock_platform_impl_); } void AddNetworkSpeechSynthesisExtension() { content::WindowedNotificationObserver observer( NOTIFICATION_EXTENSION_BACKGROUND_PAGE_READY, content::NotificationService::AllSources()); ExtensionService* service = extensions::ExtensionSystem::Get(profile())->extension_service(); service->component_loader()->AddNetworkSpeechSynthesisExtension(); observer.Wait(); ASSERT_EQ(Manifest::COMPONENT, content::Source(observer.source())->location()); } protected: StrictMock mock_platform_impl_; }; IN_PROC_BROWSER_TEST_F(TtsApiTest, PlatformSpeakOptionalArgs) { EXPECT_CALL(mock_platform_impl_, IsSpeaking()); InSequence s; EXPECT_CALL(mock_platform_impl_, StopSpeaking()) .WillOnce(Return(true)); EXPECT_CALL(mock_platform_impl_, Speak(_, "", _, _, _)) .WillOnce(Return(true)); EXPECT_CALL(mock_platform_impl_, StopSpeaking()) .WillOnce(Return(true)); EXPECT_CALL(mock_platform_impl_, Speak(_, "Alpha", _, _, _)) .WillOnce(Return(true)); EXPECT_CALL(mock_platform_impl_, StopSpeaking()) .WillOnce(Return(true)); EXPECT_CALL(mock_platform_impl_, Speak(_, "Bravo", _, _, _)) .WillOnce(Return(true)); EXPECT_CALL(mock_platform_impl_, StopSpeaking()) .WillOnce(Return(true)); EXPECT_CALL(mock_platform_impl_, Speak(_, "Charlie", _, _, _)) .WillOnce(Return(true)); EXPECT_CALL(mock_platform_impl_, StopSpeaking()) .WillOnce(Return(true)); EXPECT_CALL(mock_platform_impl_, Speak(_, "Echo", _, _, _)) .WillOnce(Return(true)); ASSERT_TRUE(RunExtensionTest("tts/optional_args")) << message_; } IN_PROC_BROWSER_TEST_F(TtsApiTest, PlatformSpeakFinishesImmediately) { InSequence s; EXPECT_CALL(mock_platform_impl_, IsSpeaking()); EXPECT_CALL(mock_platform_impl_, StopSpeaking()) .WillOnce(Return(true)); EXPECT_CALL(mock_platform_impl_, Speak(_, _, _, _, _)) .WillOnce(DoAll( Invoke(&mock_platform_impl_, &MockTtsPlatformImpl::SendEndEvent), Return(true))); ASSERT_TRUE(RunExtensionTest("tts/speak_once")) << message_; } IN_PROC_BROWSER_TEST_F(TtsApiTest, PlatformSpeakInterrupt) { EXPECT_CALL(mock_platform_impl_, IsSpeaking()); // One utterance starts speaking, and then a second interrupts. InSequence s; EXPECT_CALL(mock_platform_impl_, StopSpeaking()) .WillOnce(Return(true)); EXPECT_CALL(mock_platform_impl_, Speak(_, "text 1", _, _, _)) .WillOnce(Return(true)); // Expect the second utterance and allow it to finish. EXPECT_CALL(mock_platform_impl_, StopSpeaking()) .WillOnce(Return(true)); EXPECT_CALL(mock_platform_impl_, Speak(_, "text 2", _, _, _)) .WillOnce(DoAll( Invoke(&mock_platform_impl_, &MockTtsPlatformImpl::SendEndEvent), Return(true))); ASSERT_TRUE(RunExtensionTest("tts/interrupt")) << message_; } IN_PROC_BROWSER_TEST_F(TtsApiTest, PlatformSpeakQueueInterrupt) { EXPECT_CALL(mock_platform_impl_, IsSpeaking()); // In this test, two utterances are queued, and then a third // interrupts. Speak(, _) never gets called on the second utterance. InSequence s; EXPECT_CALL(mock_platform_impl_, StopSpeaking()) .WillOnce(Return(true)); EXPECT_CALL(mock_platform_impl_, Speak(_, "text 1", _, _, _)) .WillOnce(Return(true)); // Don't expect the second utterance, because it's queued up and the // first never finishes. // Expect the third utterance and allow it to finish successfully. EXPECT_CALL(mock_platform_impl_, StopSpeaking()) .WillOnce(Return(true)); EXPECT_CALL(mock_platform_impl_, Speak(_, "text 3", _, _, _)) .WillOnce(DoAll( Invoke(&mock_platform_impl_, &MockTtsPlatformImpl::SendEndEvent), Return(true))); ASSERT_TRUE(RunExtensionTest("tts/queue_interrupt")) << message_; } IN_PROC_BROWSER_TEST_F(TtsApiTest, PlatformSpeakEnqueue) { EXPECT_CALL(mock_platform_impl_, IsSpeaking()); InSequence s; EXPECT_CALL(mock_platform_impl_, StopSpeaking()) .WillOnce(Return(true)); EXPECT_CALL(mock_platform_impl_, Speak(_, "text 1", _, _, _)) .WillOnce(DoAll( Invoke(&mock_platform_impl_, &MockTtsPlatformImpl::SendEndEventWhenQueueNotEmpty), Return(true))); EXPECT_CALL(mock_platform_impl_, Speak(_, "text 2", _, _, _)) .WillOnce(DoAll( Invoke(&mock_platform_impl_, &MockTtsPlatformImpl::SendEndEvent), Return(true))); ASSERT_TRUE(RunExtensionTest("tts/enqueue")) << message_; } IN_PROC_BROWSER_TEST_F(TtsApiTest, PlatformSpeakError) { EXPECT_CALL(mock_platform_impl_, IsSpeaking()) .Times(AnyNumber()); InSequence s; EXPECT_CALL(mock_platform_impl_, StopSpeaking()) .WillOnce(Return(true)); EXPECT_CALL(mock_platform_impl_, Speak(_, "first try", _, _, _)) .WillOnce(DoAll( InvokeWithoutArgs( CreateFunctor(&mock_platform_impl_, &MockTtsPlatformImpl::SetErrorToEpicFail)), Return(false))); EXPECT_CALL(mock_platform_impl_, StopSpeaking()) .WillOnce(Return(true)); EXPECT_CALL(mock_platform_impl_, Speak(_, "second try", _, _, _)) .WillOnce(DoAll( Invoke(&mock_platform_impl_, &MockTtsPlatformImpl::SendEndEvent), Return(true))); ASSERT_TRUE(RunExtensionTest("tts/speak_error")) << message_; } IN_PROC_BROWSER_TEST_F(TtsApiTest, PlatformWordCallbacks) { EXPECT_CALL(mock_platform_impl_, IsSpeaking()); InSequence s; EXPECT_CALL(mock_platform_impl_, StopSpeaking()) .WillOnce(Return(true)); EXPECT_CALL(mock_platform_impl_, Speak(_, "one two three", _, _, _)) .WillOnce(DoAll( Invoke(&mock_platform_impl_, &MockTtsPlatformImpl::SendWordEvents), Invoke(&mock_platform_impl_, &MockTtsPlatformImpl::SendEndEvent), Return(true))); ASSERT_TRUE(RunExtensionTest("tts/word_callbacks")) << message_; } IN_PROC_BROWSER_TEST_F(TtsApiTest, PlatformPauseResume) { EXPECT_CALL(mock_platform_impl_, IsSpeaking()) .Times(AnyNumber()); InSequence s; EXPECT_CALL(mock_platform_impl_, Speak(_, "test 1", _, _, _)) .WillOnce(DoAll( Invoke(&mock_platform_impl_, &MockTtsPlatformImpl::SendEndEvent), Return(true))); EXPECT_CALL(mock_platform_impl_, StopSpeaking()) .WillOnce(Return(true)); EXPECT_CALL(mock_platform_impl_, Speak(_, "test 2", _, _, _)) .WillOnce(DoAll( SaveArg<0>(&g_saved_utterance_id), Return(true))); EXPECT_CALL(mock_platform_impl_, Pause()); EXPECT_CALL(mock_platform_impl_, Resume()) .WillOnce( InvokeWithoutArgs( &mock_platform_impl_, &MockTtsPlatformImpl::SendEndEventOnSavedUtteranceId)); ASSERT_TRUE(RunExtensionTest("tts/pause_resume")) << message_; } IN_PROC_BROWSER_TEST_F(TtsApiTest, PlatformPauseSpeakNoEnqueue) { // While paused, one utterance is enqueued, and then a second utterance that // cannot be enqueued cancels both. InSequence s; EXPECT_CALL(mock_platform_impl_, StopSpeaking()).WillOnce(Return(true)); ASSERT_TRUE(RunExtensionTest("tts/pause_speak_no_enqueue")) << message_; } // // TTS Engine tests. // IN_PROC_BROWSER_TEST_F(TtsApiTest, RegisterEngine) { mock_platform_impl_.set_should_fake_get_voices(true); EXPECT_CALL(mock_platform_impl_, IsSpeaking()) .Times(AnyNumber()); EXPECT_CALL(mock_platform_impl_, StopSpeaking()) .WillRepeatedly(Return(true)); { InSequence s; EXPECT_CALL(mock_platform_impl_, Speak(_, "native speech", _, _, _)) .WillOnce(DoAll( Invoke(&mock_platform_impl_, &MockTtsPlatformImpl::SendEndEvent), Return(true))); EXPECT_CALL(mock_platform_impl_, Speak(_, "native speech 2", _, _, _)) .WillOnce(DoAll( Invoke(&mock_platform_impl_, &MockTtsPlatformImpl::SendEndEvent), Return(true))); EXPECT_CALL(mock_platform_impl_, Speak(_, "native speech 3", _, _, _)) .WillOnce(DoAll( Invoke(&mock_platform_impl_, &MockTtsPlatformImpl::SendEndEvent), Return(true))); } ASSERT_TRUE(RunExtensionTest("tts_engine/register_engine")) << message_; } IN_PROC_BROWSER_TEST_F(TtsApiTest, EngineError) { EXPECT_CALL(mock_platform_impl_, IsSpeaking()); EXPECT_CALL(mock_platform_impl_, StopSpeaking()) .WillRepeatedly(Return(true)); ASSERT_TRUE(RunExtensionTest("tts_engine/engine_error")) << message_; } IN_PROC_BROWSER_TEST_F(TtsApiTest, EngineWordCallbacks) { EXPECT_CALL(mock_platform_impl_, IsSpeaking()); EXPECT_CALL(mock_platform_impl_, StopSpeaking()) .WillRepeatedly(Return(true)); ASSERT_TRUE(RunExtensionTest("tts_engine/engine_word_callbacks")) << message_; } IN_PROC_BROWSER_TEST_F(TtsApiTest, LangMatching) { EXPECT_CALL(mock_platform_impl_, IsSpeaking()); EXPECT_CALL(mock_platform_impl_, StopSpeaking()) .WillRepeatedly(Return(true)); ASSERT_TRUE(RunExtensionTest("tts_engine/lang_matching")) << message_; } IN_PROC_BROWSER_TEST_F(TtsApiTest, NetworkSpeechEngine) { // Simulate online network state. net::NetworkChangeNotifier::DisableForTest disable_for_test; FakeNetworkOnlineStateForTest fake_online_state(true); ASSERT_NO_FATAL_FAILURE(AddNetworkSpeechSynthesisExtension()); ASSERT_TRUE(RunExtensionTest("tts_engine/network_speech_engine")) << message_; } IN_PROC_BROWSER_TEST_F(TtsApiTest, NoNetworkSpeechEngineWhenOffline) { // Simulate offline network state. net::NetworkChangeNotifier::DisableForTest disable_for_test; FakeNetworkOnlineStateForTest fake_online_state(false); ASSERT_NO_FATAL_FAILURE(AddNetworkSpeechSynthesisExtension()); // Test should fail when offline. ASSERT_FALSE(RunExtensionTest("tts_engine/network_speech_engine")); } // http://crbug.com/122474 IN_PROC_BROWSER_TEST_F(TtsApiTest, EngineApi) { ASSERT_TRUE(RunExtensionTest("tts_engine/engine_api")) << message_; } } // namespace extensions