| Index: chrome/test/speech/speech_recognition_browsertest.cc
|
| diff --git a/chrome/test/speech/speech_recognition_browsertest.cc b/chrome/test/speech/speech_recognition_browsertest.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..50c6181e772d92993670f6522ff2178e229b1c54
|
| --- /dev/null
|
| +++ b/chrome/test/speech/speech_recognition_browsertest.cc
|
| @@ -0,0 +1,823 @@
|
| +// 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 "base/bind.h"
|
| +#include "base/command_line.h"
|
| +#include "base/file_path.h"
|
| +#include "base/run_loop.h"
|
| +#include "base/memory/scoped_ptr.h"
|
| +#include "base/utf_string_conversions.h"
|
| +#include "chrome/browser/speech/chrome_speech_recognition_manager_delegate.h"
|
| +#include "chrome/browser/speech/speech_recognition_bubble_controller.h"
|
| +#include "chrome/browser/ui/browser.h"
|
| +#include "chrome/browser/ui/browser_tabstrip.h"
|
| +#include "chrome/test/base/in_process_browser_test.h"
|
| +#include "chrome/test/base/ui_test_utils.h"
|
| +#include "chrome/test/speech/mock_google_one_shot_server.h"
|
| +#include "content/public/browser/browser_thread.h"
|
| +#include "content/public/browser/notification_types.h"
|
| +#include "content/public/browser/speech_recognition_manager.h"
|
| +#include "content/public/browser/web_contents.h"
|
| +#include "content/public/common/content_switches.h"
|
| +#include "content/public/common/speech_recognition_result.h"
|
| +#include "media/audio/mock_audio_manager.h"
|
| +#include "media/audio/test_audio_input_controller_factory.h"
|
| +
|
| +using base::RunLoop;
|
| +using content::BrowserThread;
|
| +using content::WebContents;
|
| +
|
| +namespace {
|
| +// Events expected on the IO thread.
|
| +const char* kEventClientConnected = "ClientConnected";
|
| +const char* kEventClientAudioUpload = "ClientAudioUpload";
|
| +const char* kEventClientAudioUploadComplete = "ClientAudioUploadComplete";
|
| +const char* kEventClientDisconnected = "ClientDisconnected";
|
| +const char* kEventAudioControllerOpened = "AudioControllerOpened";
|
| +const char* kEventAudioControllerClosed = "AudioControllerClosed";
|
| +const char* kEventBubbleLostFocus = "BubbleLostFocus";
|
| +// Events expected on the UI thread.
|
| +const char* kEventPageReloaded = "PageReloaded";
|
| +const char* kEventTabClosedOrCrashed = "TabClosedOrCrashed";
|
| +const char* kEventBubbleCreated = "BubbleCreated";
|
| +const char* kEventBubbleSwitchedToRecordingMode =
|
| + "BubbleSwitchedToRecordingMode";
|
| +const char* kEventBubbleSwitchedToRecognizingMode =
|
| + "BubbleSwitchedToRecognizingMode";
|
| +const char* kEventBubbleMessageDisplayed = "BubbleMessageDisplayed";
|
| +const char* kEventBubbleClosed = "BubbleClosed";
|
| +
|
| +// The length in ms. of each audio packet. Must match the expected capture
|
| +// length of production classes (checked in |TestAudioControllerOpened|).
|
| +const int kAudioPacketIntervalMs = 100;
|
| +
|
| +content::SpeechRecognitionResult GetGoodSpeechResult() {
|
| + content::SpeechRecognitionResult result;
|
| + result.hypotheses.push_back(
|
| + content::SpeechRecognitionHypothesis(UTF8ToUTF16("Pictures of the moon"),
|
| + 1.0F));
|
| + return result;
|
| +}
|
| +
|
| +// Utility class for event-driven tests.
|
| +// Some end-to-end tests, as the ones in this class, involve the interaction of
|
| +// several objects on different threads which, due to their intrinsically
|
| +// concurrent nature, are extremely hard to handle directly on the text fixture
|
| +// using the traditional RunLoop::Run/Quit pattern.
|
| +// This class aims to help testing in this scenario by means of a discrete
|
| +// event simulation pattern, as follows: before a test is started, this class
|
| +// (one instance for each thread) is pumped with the sequence of events that is
|
| +// expected at runtime. Furhermore, each event can be followed by one or more
|
| +// "reactions", which will be fired (on the same thread) after its notification.
|
| +// If the sequence of events detected at runtime does not match the foreseen one
|
| +// this class will issue a GTEST_FATAL_FAILURE_, and (optionally) quit the
|
| +// message loop aborting the test.
|
| +class EventDrivenTimeline {
|
| + public:
|
| + typedef const char* EventDescriptor;
|
| +
|
| + explicit EventDrivenTimeline(BrowserThread::ID thread_id)
|
| + : running_(false),
|
| + skip_events_after_end_(false),
|
| + thread_id_(thread_id) {
|
| + ExpectEvent(kStartEvent);
|
| + }
|
| +
|
| + ~EventDrivenTimeline() {
|
| + running_ = false;
|
| + // All the timeline events must have been consumed.
|
| + while (!timeline_entries_.empty()) {
|
| + TimelineEntry& entry = timeline_entries_.front();
|
| + FailWithMessage("Unconsumed event " + std::string(entry.expected_event));
|
| + timeline_entries_.pop_front();
|
| + }
|
| + }
|
| +
|
| + void ExpectEvent(EventDescriptor event, int repetitions) {
|
| + // Event timeline can be setup only before the events firing starts.
|
| + ASSERT_FALSE(running_);
|
| + for (int i = 0; i < repetitions; ++i) {
|
| + timeline_entries_.push_back(TimelineEntry(event));
|
| + }
|
| + }
|
| +
|
| + void ExpectEvent(EventDescriptor event) {
|
| + // No further events can be added after a call to |SkipFurtherEvents()|.
|
| + DCHECK(!skip_events_after_end_);
|
| + ExpectEvent(event, 1);
|
| + }
|
| +
|
| + void SkipFurtherEvents() {
|
| + skip_events_after_end_ = true;
|
| + }
|
| +
|
| + void AddAction(base::Closure action) {
|
| + // Event timeline can be setup only before the events firing starts.
|
| + ASSERT_FALSE(running_);
|
| + DCHECK(!timeline_entries_.empty());
|
| + timeline_entries_.back().actions.push_back(action);
|
| + }
|
| +
|
| + void SetActionOnFailure(base::Closure action) {
|
| + action_on_failure_ = action;
|
| + }
|
| +
|
| + void Start() {
|
| + running_ = true;
|
| + // Fire the start event on the proper thread since this method will be
|
| + // always called on the test thread (UI).
|
| + BrowserThread::PostTask(thread_id_, FROM_HERE, base::Bind(
|
| + &EventDrivenTimeline::NotifyEvent,
|
| + base::Unretained(this),
|
| + kStartEvent
|
| + ));
|
| + }
|
| +
|
| + void NotifyEvent(EventDescriptor event) {
|
| + ASSERT_TRUE(running_);
|
| + DCHECK(BrowserThread::CurrentlyOn(thread_id_));
|
| +
|
| + const std::string event_str(event);
|
| +
|
| + if (timeline_entries_.empty()) {
|
| + if (!skip_events_after_end_) {
|
| + FailWithMessage(std::string(
|
| + "Got event " + event_str + ", exepecting no more events."));
|
| + }
|
| + return;
|
| + }
|
| +
|
| + TimelineEntry& entry = timeline_entries_.front();
|
| + if (entry.expected_event != event) {
|
| + FailWithMessage(std::string(
|
| + "Got event " + event_str +
|
| + ", exepecting event " + entry.expected_event + "."));
|
| + return;
|
| + }
|
| +
|
| + while (!entry.actions.empty()) {
|
| + MessageLoop::current()->PostTask(FROM_HERE, entry.actions.front());
|
| + entry.actions.pop_front();
|
| + }
|
| + timeline_entries_.pop_front();
|
| + }
|
| +
|
| + private:
|
| + static EventDescriptor kStartEvent;
|
| +
|
| + struct TimelineEntry {
|
| + EventDescriptor expected_event;
|
| + std::list<base::Closure> actions;
|
| +
|
| + explicit TimelineEntry(EventDescriptor expected_event_value)
|
| + : expected_event(expected_event_value) {}
|
| + ~TimelineEntry() {}
|
| + };
|
| +
|
| + void FailWithMessage(const std::string& message) {
|
| + if (running_ && !action_on_failure_.Equals(base::Closure()))
|
| + action_on_failure_.Run();
|
| + GTEST_FATAL_FAILURE_(message.c_str());
|
| + }
|
| +
|
| + bool running_;
|
| + bool skip_events_after_end_;
|
| + BrowserThread::ID thread_id_;
|
| + std::list<TimelineEntry> timeline_entries_;
|
| + base::Closure action_on_failure_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(EventDrivenTimeline);
|
| +};
|
| +EventDrivenTimeline::EventDescriptor EventDrivenTimeline::kStartEvent = "Start";
|
| +
|
| +} // namespace
|
| +
|
| +namespace speech {
|
| +
|
| +class SpeechRecognitionBrowserTest
|
| + : public InProcessBrowserTest,
|
| + public MockGoogleOneShotServerDelegate,
|
| + public SpeechRecognitionBubbleControllerDelegateForTests,
|
| + public media::TestAudioInputControllerDelegate,
|
| + public content::NotificationObserver {
|
| + public:
|
| + // MockGoogleOneShotServerDelegate methods.
|
| + virtual void OnClientConnected() OVERRIDE {
|
| + io_thread_events()->NotifyEvent(kEventClientConnected);
|
| + }
|
| +
|
| + virtual void OnClientAudioUpload() OVERRIDE {
|
| + io_thread_events()->NotifyEvent(kEventClientAudioUpload);
|
| + }
|
| +
|
| + virtual void OnClientAudioUploadComplete() OVERRIDE {
|
| + io_thread_events()->NotifyEvent(kEventClientAudioUploadComplete);
|
| + }
|
| +
|
| + virtual void OnClientDisconnected() {
|
| + io_thread_events()->NotifyEvent(kEventClientDisconnected);
|
| + }
|
| +
|
| + // media::TestAudioInputControllerDelegate methods.
|
| + virtual void TestAudioControllerOpened(
|
| + media::TestAudioInputController* controller) OVERRIDE {
|
| + ASSERT_TRUE(controller);
|
| + const int capture_packet_interval_ms =
|
| + (1000 * controller->audio_parameters().frames_per_buffer()) /
|
| + controller->audio_parameters().sample_rate();
|
| + ASSERT_EQ(kAudioPacketIntervalMs, capture_packet_interval_ms);
|
| + io_thread_events()->NotifyEvent(kEventAudioControllerOpened);
|
| + }
|
| +
|
| + virtual void TestAudioControllerClosed(
|
| + media::TestAudioInputController* controller) OVERRIDE {
|
| + io_thread_events()->NotifyEvent(kEventAudioControllerClosed);
|
| + }
|
| +
|
| + // SpeechRecognitionBubbleControllerDelegateForTests methods.
|
| + virtual void BubbleCreated() OVERRIDE {
|
| + ui_thread_events()->NotifyEvent(kEventBubbleCreated);
|
| + }
|
| +
|
| + virtual void BubbleSwitchedToRecordingMode() OVERRIDE {
|
| + ui_thread_events()->NotifyEvent(kEventBubbleSwitchedToRecordingMode);
|
| + }
|
| +
|
| + virtual void BubbleSwitchedToRecognizingMode() OVERRIDE {
|
| + ui_thread_events()->NotifyEvent(kEventBubbleSwitchedToRecognizingMode);
|
| + }
|
| +
|
| + virtual void BubbleMessageDisplayed() OVERRIDE {
|
| + ui_thread_events()->NotifyEvent(kEventBubbleMessageDisplayed);
|
| + }
|
| +
|
| + virtual void BubbleClosed() OVERRIDE {
|
| + ui_thread_events()->NotifyEvent(kEventBubbleClosed);
|
| + }
|
| +
|
| + // content::NotificationObserver methods.
|
| + virtual void Observe(int type,
|
| + const content::NotificationSource& source,
|
| + const content::NotificationDetails& details) OVERRIDE {
|
| + if (type == content::NOTIFICATION_LOAD_STOP)
|
| + ui_thread_events()->NotifyEvent(kEventPageReloaded);
|
| + else if (type == content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED)
|
| + ui_thread_events()->NotifyEvent(kEventTabClosedOrCrashed);
|
| + else
|
| + NOTREACHED();
|
| + }
|
| +
|
| + // Helper methods used by test fixtures.
|
| + void FeedAudioControllerWithSilence(int duration_ms) {
|
| + FeedAudioController(duration_ms, false);
|
| + }
|
| +
|
| + void FeedAudioControllerWithNoise(int duration_ms) {
|
| + FeedAudioController(duration_ms, true);
|
| + }
|
| +
|
| + void CrashActiveTab() {
|
| + ui_test_utils::CrashTab(chrome::GetActiveWebContents(browser()));
|
| + }
|
| +
|
| + void NavigateToPage(const FilePath::CharType* filename) {
|
| + const FilePath kTestDir(FILE_PATH_LITERAL("speech"));
|
| + GURL test_url = ui_test_utils::GetTestUrl(kTestDir, FilePath(filename));
|
| + ui_test_utils::NavigateToURL(browser(), test_url);
|
| + }
|
| +
|
| + void SimulateClickOnActiveTab(int x, int y) {
|
| + WebContents* web_contents = chrome::GetActiveWebContents(browser());
|
| + ui_test_utils::SimulateMouseClick(web_contents, gfx::Point(x, y));
|
| + }
|
| +
|
| + void SimulateClickOnBubbleCancelButton() {
|
| + SpeechRecognitionBubbleController* bubble_controller =
|
| + SpeechRecognitionBubbleController::GetInstanceForTests();
|
| + ASSERT_TRUE(bubble_controller);
|
| + ASSERT_TRUE(bubble_controller->IsShowingMessage());
|
| + bubble_controller->InfoBubbleButtonClicked(
|
| + SpeechRecognitionBubble::BUTTON_CANCEL);
|
| + }
|
| +
|
| + void SimulateClickOnBubbleTryAgainButton() {
|
| + SpeechRecognitionBubbleController* bubble_controller =
|
| + SpeechRecognitionBubbleController::GetInstanceForTests();
|
| + ASSERT_TRUE(bubble_controller);
|
| + ASSERT_TRUE(bubble_controller->IsShowingMessage());
|
| + bubble_controller->InfoBubbleButtonClicked(
|
| + SpeechRecognitionBubble::BUTTON_TRY_AGAIN);
|
| + }
|
| +
|
| + void SimulateBubbleFocusLost() {
|
| + SpeechRecognitionBubbleController* bubble_controller =
|
| + SpeechRecognitionBubbleController::GetInstanceForTests();
|
| + ASSERT_TRUE(bubble_controller);
|
| + bubble_controller->InfoBubbleFocusChanged();
|
| + }
|
| +
|
| + bool CheckJavascripResultInPage() {
|
| + WebContents* web_contents = chrome::GetActiveWebContents(browser());
|
| + if (!web_contents)
|
| + return false;
|
| + return web_contents->GetURL().ref() == "pass";
|
| + return false;
|
| + }
|
| +
|
| + void NotifyEventToIOTimeline(EventDrivenTimeline::EventDescriptor event) {
|
| + if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
|
| + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(
|
| + &SpeechRecognitionBrowserTest::NotifyEventToIOTimeline,
|
| + base::Unretained(this),
|
| + event));
|
| + return;
|
| + }
|
| + io_thread_events()->NotifyEvent(event);
|
| + }
|
| +
|
| + void QuitRunLoop() {
|
| + if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
|
| + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
|
| + &SpeechRecognitionBrowserTest::QuitRunLoop, base::Unretained(this)));
|
| + return;
|
| + }
|
| + // QuitClosure() needs to be called on the UI thread, so we need both the
|
| + // PostTask(s) (above and below), in order to serve failures triggered by
|
| + // both the |io_thread_events_| and |ui_thread_events_|.
|
| + BrowserThread::PostTask(BrowserThread::UI,
|
| + FROM_HERE,
|
| + run_loop_->QuitClosure());
|
| + }
|
| +
|
| + RunLoop* run_loop() { return run_loop_.get(); }
|
| +
|
| + EventDrivenTimeline* io_thread_events() { return io_thread_events_.get(); }
|
| +
|
| + EventDrivenTimeline* ui_thread_events() { return ui_thread_events_.get(); }
|
| +
|
| + MockGoogleOneShotServer* mock_one_shot_server() {
|
| + return mock_one_shot_server_.get();
|
| + }
|
| +
|
| + protected:
|
| + // InProcessBrowserTest methods.
|
| + virtual void SetUpCommandLine(CommandLine* command_line) {
|
| + ASSERT_FALSE(command_line->HasSwitch(switches::kDisableSpeechInput));
|
| +
|
| + // TODO(primiano): uncomment below when adding JS API tests.
|
| + // ASSERT_TRUE(command_line->HasSwitch(switches::kEnableScriptedSpeech));
|
| + }
|
| +
|
| + virtual void SetUpInProcessBrowserTestFixture() OVERRIDE {
|
| + test_audio_input_controller_factory_.SetDelegateForTests(this);
|
| + media::AudioInputController::set_factory_for_testing(
|
| + &test_audio_input_controller_factory_);
|
| + mock_one_shot_server_.reset(new MockGoogleOneShotServer(this));
|
| + io_thread_events_.reset(new EventDrivenTimeline(BrowserThread::IO));
|
| + io_thread_events_->SetActionOnFailure(base::Bind(
|
| + &SpeechRecognitionBrowserTest::QuitRunLoop, base::Unretained(this)));
|
| + ui_thread_events_.reset(new EventDrivenTimeline(BrowserThread::UI));
|
| + ui_thread_events_->SetActionOnFailure(base::Bind(
|
| + &SpeechRecognitionBrowserTest::QuitRunLoop, base::Unretained(this)));
|
| + SpeechRecognitionBubbleController::SetDelegateForTests(this);
|
| + }
|
| +
|
| + virtual void SetUpOnMainThread() OVERRIDE {
|
| + ASSERT_TRUE(content::SpeechRecognitionManager::GetInstance());
|
| + content::SpeechRecognitionManager::SetAudioManagerForTests(
|
| + new media::MockAudioManager(
|
| + content::BrowserThread::GetMessageLoopProxyForThread(
|
| + BrowserThread::IO)));
|
| + run_loop_.reset(new RunLoop());
|
| +
|
| + const WebContents* web_contents = chrome::GetActiveWebContents(browser());
|
| + registrar_.Add(
|
| + this,
|
| + content::NOTIFICATION_LOAD_STOP,
|
| + content::Source<content::NavigationController>(
|
| + &web_contents->GetController()));
|
| +
|
| + registrar_.Add(
|
| + this,
|
| + content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
|
| + content::Source<WebContents>(web_contents));
|
| + }
|
| +
|
| + virtual void CleanUpOnMainThread() OVERRIDE {
|
| + registrar_.RemoveAll();
|
| + content::SpeechRecognitionManager::SetAudioManagerForTests(NULL);
|
| + }
|
| +
|
| + virtual void TearDownInProcessBrowserTestFixture() OVERRIDE {
|
| + media::AudioInputController::set_factory_for_testing(NULL);
|
| + io_thread_events_.reset();
|
| + ui_thread_events_.reset();
|
| + mock_one_shot_server_.reset();
|
| + }
|
| +
|
| + private:
|
| + static void FeedSingleBufferToAudioController(
|
| + scoped_refptr<media::TestAudioInputController> controller,
|
| + size_t buffer_size,
|
| + bool fill_with_noise) {
|
| + DCHECK(controller.get());
|
| + scoped_array<uint8> audio_buffer(new uint8[buffer_size]);
|
| + if (fill_with_noise) {
|
| + uint8* buf = audio_buffer.get();
|
| + for (size_t i = 0; i < buffer_size; ++i)
|
| + buf[i] = static_cast<uint8>(127 * sin(i * 3.14F / (16 * buffer_size)));
|
| + } else {
|
| + memset(audio_buffer.get(), 0, buffer_size);
|
| + }
|
| + controller->event_handler()->OnData(controller,
|
| + audio_buffer.get(),
|
| + buffer_size);
|
| + }
|
| +
|
| + void FeedAudioController(int duration_ms, bool feed_with_noise) {
|
| + media::TestAudioInputController* controller =
|
| + test_audio_input_controller_factory_.controller();
|
| + ASSERT_TRUE(controller);
|
| + const media::AudioParameters& audio_params = controller->audio_parameters();
|
| + const size_t buffer_size = audio_params.GetBytesPerBuffer();
|
| + const int ms_per_buffer = audio_params.frames_per_buffer() * 1000 /
|
| + audio_params.sample_rate();
|
| + // We can only simulate durations that are integer multiples of the
|
| + // buffer size. In this regard see
|
| + // SpeechRecognitionEngine::GetDesiredAudioChunkDurationMs().
|
| + ASSERT_EQ(0, duration_ms % ms_per_buffer);
|
| +
|
| + const int n_buffers = duration_ms / ms_per_buffer;
|
| + for (int i = 0; i < n_buffers; ++i) {
|
| + MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
|
| + &FeedSingleBufferToAudioController,
|
| + scoped_refptr<media::TestAudioInputController>(controller),
|
| + buffer_size,
|
| + feed_with_noise));
|
| + }
|
| + }
|
| +
|
| + scoped_ptr<EventDrivenTimeline> io_thread_events_;
|
| + scoped_ptr<EventDrivenTimeline> ui_thread_events_;
|
| + scoped_ptr<RunLoop> run_loop_;
|
| + content::NotificationRegistrar registrar_;
|
| + scoped_ptr<MockGoogleOneShotServer> mock_one_shot_server_;
|
| + media::TestAudioInputControllerFactory test_audio_input_controller_factory_;
|
| +};
|
| +
|
| +// Simulates a regular speech recognition scenario. 2.5 secs of audio (0.5 s. of
|
| +// silence + 1 s. of speech + 1 s. of silence) are pushed into the audio
|
| +// controller. At end of recognition we check that the bubble has been hidden,
|
| +// the audio controller has been cleanly closed, the proper language and grammar
|
| +// parameters are passed to the server and the javascript has seen the
|
| +// onwebkitspeechchange event and matched the expected result string (writing
|
| +// 'PASS' in the <div id='status'> and issuing a page reload.
|
| +IN_PROC_BROWSER_TEST_F(SpeechRecognitionBrowserTest, SuccessfulRecognition) {
|
| + // Events flow handled asynchronously on the UI thread.
|
| + ui_thread_events()->AddAction(base::Bind(
|
| + &SpeechRecognitionBrowserTest::NavigateToPage,
|
| + base::Unretained(this),
|
| + FILE_PATH_LITERAL("basic_recognition.html")));
|
| + ui_thread_events()->ExpectEvent(kEventPageReloaded);
|
| + ui_thread_events()->AddAction(base::Bind(
|
| + &SpeechRecognitionBrowserTest::SimulateClickOnActiveTab,
|
| + base::Unretained(this),
|
| + 0, 0));
|
| + ui_thread_events()->ExpectEvent(kEventBubbleCreated);
|
| + ui_thread_events()->ExpectEvent(kEventBubbleSwitchedToRecordingMode);
|
| + ui_thread_events()->ExpectEvent(kEventBubbleSwitchedToRecognizingMode);
|
| + ui_thread_events()->ExpectEvent(kEventBubbleClosed);
|
| + ui_thread_events()->ExpectEvent(kEventPageReloaded);
|
| + ui_thread_events()->AddAction(base::Bind(
|
| + &SpeechRecognitionBrowserTest::QuitRunLoop,
|
| + base::Unretained(this)));
|
| +
|
| + // Events flow handled asynchronously on the IO thread.
|
| + io_thread_events()->ExpectEvent(kEventAudioControllerOpened);
|
| + io_thread_events()->AddAction(base::Bind(
|
| + &SpeechRecognitionBrowserTest::FeedAudioControllerWithSilence,
|
| + base::Unretained(this),
|
| + 500 /* ms. */));
|
| + io_thread_events()->AddAction(base::Bind(
|
| + &SpeechRecognitionBrowserTest::FeedAudioControllerWithNoise,
|
| + base::Unretained(this),
|
| + 1000 /* ms. */));
|
| + io_thread_events()->AddAction(base::Bind(
|
| + &SpeechRecognitionBrowserTest::FeedAudioControllerWithSilence,
|
| + base::Unretained(this),
|
| + 1000 /* ms. */));
|
| + const int expected_chunks = (500 + 1000 + 1000) / kAudioPacketIntervalMs;
|
| + io_thread_events()->ExpectEvent(kEventClientConnected);
|
| + io_thread_events()->ExpectEvent(kEventClientAudioUpload, expected_chunks);
|
| + io_thread_events()->ExpectEvent(kEventClientAudioUploadComplete);
|
| + io_thread_events()->ExpectEvent(kEventAudioControllerClosed);
|
| + io_thread_events()->AddAction(base::Bind(
|
| + &MockGoogleOneShotServer::SimulateResult,
|
| + base::Unretained(mock_one_shot_server()),
|
| + GetGoodSpeechResult()));
|
| + io_thread_events()->ExpectEvent(kEventClientDisconnected);
|
| +
|
| + io_thread_events()->Start();
|
| + ui_thread_events()->Start();
|
| +
|
| + // Runs until RunLoop::Quit action is reached in ui_thread_events.
|
| + run_loop()->Run();
|
| +
|
| + EXPECT_TRUE(CheckJavascripResultInPage());
|
| +
|
| + // Check that a non blank lang (en-US on most bots) is passed in the request.
|
| + EXPECT_NE("", mock_one_shot_server()->GetRequestLanguage());
|
| +
|
| + // Check that the grammar attribute is properly passed in the request.
|
| + EXPECT_EQ("http://example.com/grammar.xml",
|
| + mock_one_shot_server()->GetRequestGrammar());
|
| +}
|
| +
|
| +// Simulates a server failure scenario. We expect the error message to be
|
| +// properly shown in the bubble, simulate a click on the Cancel button and check
|
| +// that everything is closed cleanly after that.
|
| +IN_PROC_BROWSER_TEST_F(SpeechRecognitionBrowserTest, CancelAfterError) {
|
| + // Events flow handled asynchronously on the UI thread.
|
| + ui_thread_events()->AddAction(base::Bind(
|
| + &SpeechRecognitionBrowserTest::NavigateToPage,
|
| + base::Unretained(this),
|
| + FILE_PATH_LITERAL("basic_recognition.html")));
|
| + ui_thread_events()->ExpectEvent(kEventPageReloaded);
|
| + ui_thread_events()->AddAction(base::Bind(
|
| + &SpeechRecognitionBrowserTest::SimulateClickOnActiveTab,
|
| + base::Unretained(this),
|
| + 0, 0));
|
| + ui_thread_events()->ExpectEvent(kEventBubbleCreated);
|
| + ui_thread_events()->ExpectEvent(kEventBubbleSwitchedToRecordingMode);
|
| + ui_thread_events()->ExpectEvent(kEventBubbleSwitchedToRecognizingMode);
|
| + ui_thread_events()->ExpectEvent(kEventBubbleMessageDisplayed);
|
| + ui_thread_events()->AddAction(base::Bind(
|
| + &SpeechRecognitionBrowserTest::SimulateClickOnBubbleCancelButton,
|
| + base::Unretained(this)));
|
| + ui_thread_events()->ExpectEvent(kEventBubbleClosed);
|
| + ui_thread_events()->AddAction(base::Bind(
|
| + &SpeechRecognitionBrowserTest::QuitRunLoop,
|
| + base::Unretained(this)));
|
| +
|
| + // Events flow handled asynchronously on the IO thread.
|
| + io_thread_events()->ExpectEvent(kEventAudioControllerOpened);
|
| + io_thread_events()->AddAction(base::Bind(
|
| + &SpeechRecognitionBrowserTest::FeedAudioControllerWithSilence,
|
| + base::Unretained(this),
|
| + 500 /* ms. */));
|
| + io_thread_events()->AddAction(base::Bind(
|
| + &SpeechRecognitionBrowserTest::FeedAudioControllerWithNoise,
|
| + base::Unretained(this),
|
| + 1000 /* ms.*/));
|
| + io_thread_events()->AddAction(base::Bind(
|
| + &SpeechRecognitionBrowserTest::FeedAudioControllerWithSilence,
|
| + base::Unretained(this),
|
| + 1000 /* ms. */));
|
| + const int expected_chunks = (500 + 1000 + 1000) / kAudioPacketIntervalMs;
|
| + io_thread_events()->ExpectEvent(kEventClientConnected);
|
| + io_thread_events()->ExpectEvent(kEventClientAudioUpload, expected_chunks);
|
| + io_thread_events()->ExpectEvent(kEventClientAudioUploadComplete);
|
| + io_thread_events()->ExpectEvent(kEventAudioControllerClosed);
|
| + io_thread_events()->AddAction(base::Bind(
|
| + &MockGoogleOneShotServer::SimulateServerFailure,
|
| + base::Unretained(mock_one_shot_server())));
|
| + io_thread_events()->ExpectEvent(kEventClientDisconnected);
|
| +
|
| + io_thread_events()->Start();
|
| + ui_thread_events()->Start();
|
| +
|
| + // Run until QuitRunLoop action is reached in ui_thread_events.
|
| + run_loop()->Run();
|
| +
|
| + EXPECT_FALSE(CheckJavascripResultInPage());
|
| +}
|
| +
|
| +// Similar to the previous scenario, with the exception that this time we
|
| +// simulate a click on the Try Again button and check that a second recognition
|
| +// session is carried on succesfully.
|
| +IN_PROC_BROWSER_TEST_F(SpeechRecognitionBrowserTest, TryAgainAfterError) {
|
| + // Events flow handled asynchronously on the UI thread.
|
| + ui_thread_events()->AddAction(base::Bind(
|
| + &SpeechRecognitionBrowserTest::NavigateToPage,
|
| + base::Unretained(this),
|
| + FILE_PATH_LITERAL("basic_recognition.html")));
|
| + ui_thread_events()->ExpectEvent(kEventPageReloaded);
|
| + ui_thread_events()->AddAction(base::Bind(
|
| + &SpeechRecognitionBrowserTest::SimulateClickOnActiveTab,
|
| + base::Unretained(this),
|
| + 0, 0));
|
| + ui_thread_events()->ExpectEvent(kEventBubbleCreated);
|
| + ui_thread_events()->ExpectEvent(kEventBubbleSwitchedToRecordingMode);
|
| + ui_thread_events()->ExpectEvent(kEventBubbleSwitchedToRecognizingMode);
|
| + ui_thread_events()->ExpectEvent(kEventBubbleMessageDisplayed);
|
| + ui_thread_events()->AddAction(base::Bind(
|
| + &SpeechRecognitionBrowserTest::SimulateClickOnBubbleTryAgainButton,
|
| + base::Unretained(this)));
|
| + ui_thread_events()->ExpectEvent(kEventBubbleClosed);
|
| + // Second bubble expected after "try again".
|
| + ui_thread_events()->ExpectEvent(kEventBubbleCreated);
|
| + ui_thread_events()->ExpectEvent(kEventBubbleSwitchedToRecordingMode);
|
| + ui_thread_events()->ExpectEvent(kEventBubbleSwitchedToRecognizingMode);
|
| + ui_thread_events()->ExpectEvent(kEventBubbleClosed);
|
| + ui_thread_events()->ExpectEvent(kEventPageReloaded);
|
| +
|
| + ui_thread_events()->AddAction(base::Bind(
|
| + &SpeechRecognitionBrowserTest::QuitRunLoop,
|
| + base::Unretained(this)));
|
| +
|
| + // Events flow handled asynchronously on the IO thread.
|
| + for (int i = 0; i < 2; ++i) {
|
| + io_thread_events()->ExpectEvent(kEventAudioControllerOpened);
|
| + io_thread_events()->AddAction(base::Bind(
|
| + &SpeechRecognitionBrowserTest::FeedAudioControllerWithSilence,
|
| + base::Unretained(this),
|
| + 500 /* ms. */));
|
| + io_thread_events()->AddAction(base::Bind(
|
| + &SpeechRecognitionBrowserTest::FeedAudioControllerWithNoise,
|
| + base::Unretained(this),
|
| + 1000 /* ms.*/));
|
| + io_thread_events()->AddAction(base::Bind(
|
| + &SpeechRecognitionBrowserTest::FeedAudioControllerWithSilence,
|
| + base::Unretained(this),
|
| + 1000 /* ms. */));
|
| + const int expected_chunks = (500 + 1000 + 1000) / kAudioPacketIntervalMs;
|
| + io_thread_events()->ExpectEvent(kEventClientConnected);
|
| + io_thread_events()->ExpectEvent(kEventClientAudioUpload, expected_chunks);
|
| + io_thread_events()->ExpectEvent(kEventClientAudioUploadComplete);
|
| + io_thread_events()->ExpectEvent(kEventAudioControllerClosed);
|
| + if (i == 0) {
|
| + io_thread_events()->AddAction(base::Bind(
|
| + &MockGoogleOneShotServer::SimulateServerFailure,
|
| + base::Unretained(mock_one_shot_server())));
|
| + } else {
|
| + io_thread_events()->AddAction(base::Bind(
|
| + &MockGoogleOneShotServer::SimulateResult,
|
| + base::Unretained(mock_one_shot_server()),
|
| + GetGoodSpeechResult()));
|
| + }
|
| + io_thread_events()->ExpectEvent(kEventClientDisconnected);
|
| + }
|
| +
|
| + io_thread_events()->Start();
|
| + ui_thread_events()->Start();
|
| +
|
| + // Run until QuitRunLoop action is reached in ui_thread_events.
|
| + run_loop()->Run();
|
| +
|
| + EXPECT_TRUE(CheckJavascripResultInPage());
|
| +}
|
| +
|
| +// Simulates a loss of focus of the speech recognition bubble while capturing
|
| +// audio. We expect that the speech recognition session is aborted without
|
| +// providing any result.
|
| +IN_PROC_BROWSER_TEST_F(SpeechRecognitionBrowserTest, FocusLostWhileCapturing) {
|
| + // Events flow handled asynchronously on the UI thread.
|
| + ui_thread_events()->AddAction(base::Bind(
|
| + &SpeechRecognitionBrowserTest::NavigateToPage,
|
| + base::Unretained(this),
|
| + FILE_PATH_LITERAL("basic_recognition.html")));
|
| + ui_thread_events()->ExpectEvent(kEventPageReloaded);
|
| + ui_thread_events()->AddAction(base::Bind(
|
| + &SpeechRecognitionBrowserTest::SimulateClickOnActiveTab,
|
| + base::Unretained(this),
|
| + 0, 0));
|
| + ui_thread_events()->ExpectEvent(kEventBubbleCreated);
|
| + ui_thread_events()->ExpectEvent(kEventBubbleSwitchedToRecordingMode);
|
| + ui_thread_events()->AddAction(base::Bind(
|
| + &SpeechRecognitionBrowserTest::SimulateBubbleFocusLost,
|
| + base::Unretained(this)));
|
| + ui_thread_events()->ExpectEvent(kEventBubbleClosed);
|
| + ui_thread_events()->AddAction(base::Bind(
|
| + &SpeechRecognitionBrowserTest::QuitRunLoop,
|
| + base::Unretained(this)));
|
| +
|
| + // Events flow handled asynchronously on the IO thread.
|
| + io_thread_events()->ExpectEvent(kEventAudioControllerOpened);
|
| + io_thread_events()->AddAction(base::Bind(
|
| + &SpeechRecognitionBrowserTest::FeedAudioControllerWithSilence,
|
| + base::Unretained(this),
|
| + 500 /* ms. */));
|
| + io_thread_events()->AddAction(base::Bind(
|
| + &SpeechRecognitionBrowserTest::FeedAudioControllerWithNoise,
|
| + base::Unretained(this),
|
| + 1000 /* ms.*/));
|
| + const int expected_chunks = (500 + 1000) / kAudioPacketIntervalMs;
|
| + io_thread_events()->ExpectEvent(kEventClientConnected);
|
| + io_thread_events()->ExpectEvent(kEventClientAudioUpload, expected_chunks);
|
| + io_thread_events()->ExpectEvent(kEventAudioControllerClosed);
|
| + io_thread_events()->ExpectEvent(kEventClientDisconnected);
|
| +
|
| + io_thread_events()->Start();
|
| + ui_thread_events()->Start();
|
| +
|
| + // Run until QuitRunLoop action is reached in ui_thread_events.
|
| + run_loop()->Run();
|
| +
|
| + EXPECT_FALSE(CheckJavascripResultInPage());
|
| +}
|
| +
|
| +// Simulates a loss of focus of the speech recognition bubble after the capture
|
| +// is ended but before the result is provided. We expect that the bubble is
|
| +// closed but the recognition session is succesfully completed in background.
|
| +IN_PROC_BROWSER_TEST_F(SpeechRecognitionBrowserTest, FocusLostAfterCapture) {
|
| + // Events flow handled asynchronously on the UI thread.
|
| + ui_thread_events()->AddAction(base::Bind(
|
| + &SpeechRecognitionBrowserTest::NavigateToPage,
|
| + base::Unretained(this),
|
| + FILE_PATH_LITERAL("basic_recognition.html")));
|
| + ui_thread_events()->ExpectEvent(kEventPageReloaded);
|
| + ui_thread_events()->AddAction(base::Bind(
|
| + &SpeechRecognitionBrowserTest::SimulateClickOnActiveTab,
|
| + base::Unretained(this),
|
| + 0, 0));
|
| + ui_thread_events()->ExpectEvent(kEventBubbleCreated);
|
| + ui_thread_events()->ExpectEvent(kEventBubbleSwitchedToRecordingMode);
|
| + ui_thread_events()->ExpectEvent(kEventBubbleSwitchedToRecognizingMode);
|
| + ui_thread_events()->AddAction(base::Bind(
|
| + &SpeechRecognitionBrowserTest::SimulateBubbleFocusLost,
|
| + base::Unretained(this)));
|
| + ui_thread_events()->ExpectEvent(kEventBubbleClosed);
|
| + ui_thread_events()->AddAction(base::Bind(
|
| + &SpeechRecognitionBrowserTest::NotifyEventToIOTimeline,
|
| + base::Unretained(this),
|
| + kEventBubbleLostFocus));
|
| + ui_thread_events()->ExpectEvent(kEventPageReloaded);
|
| + ui_thread_events()->AddAction(base::Bind(
|
| + &SpeechRecognitionBrowserTest::QuitRunLoop,
|
| + base::Unretained(this)));
|
| +
|
| + // Events flow handled asynchronously on the IO thread.
|
| + io_thread_events()->ExpectEvent(kEventAudioControllerOpened);
|
| + io_thread_events()->AddAction(base::Bind(
|
| + &SpeechRecognitionBrowserTest::FeedAudioControllerWithSilence,
|
| + base::Unretained(this),
|
| + 500 /* ms. */));
|
| + io_thread_events()->AddAction(base::Bind(
|
| + &SpeechRecognitionBrowserTest::FeedAudioControllerWithNoise,
|
| + base::Unretained(this),
|
| + 1000 /* ms. */));
|
| + io_thread_events()->AddAction(base::Bind(
|
| + &SpeechRecognitionBrowserTest::FeedAudioControllerWithSilence,
|
| + base::Unretained(this),
|
| + 1000 /* ms. */));
|
| + const int expected_chunks = (500 + 1000 + 1000) / kAudioPacketIntervalMs;
|
| + io_thread_events()->ExpectEvent(kEventClientConnected);
|
| + io_thread_events()->ExpectEvent(kEventClientAudioUpload, expected_chunks);
|
| + io_thread_events()->ExpectEvent(kEventClientAudioUploadComplete);
|
| + io_thread_events()->ExpectEvent(kEventAudioControllerClosed);
|
| + io_thread_events()->ExpectEvent(kEventBubbleLostFocus);
|
| + io_thread_events()->AddAction(base::Bind(
|
| + &MockGoogleOneShotServer::SimulateResult,
|
| + base::Unretained(mock_one_shot_server()),
|
| + GetGoodSpeechResult()));
|
| + io_thread_events()->ExpectEvent(kEventClientDisconnected);
|
| +
|
| + io_thread_events()->Start();
|
| + ui_thread_events()->Start();
|
| +
|
| + // Run until QuitRunLoop action is reached in ui_thread_events.
|
| + run_loop()->Run();
|
| +
|
| + EXPECT_TRUE(CheckJavascripResultInPage());
|
| +}
|
| +
|
| +// Simulates a crash of the tab during a speech recognition session. We expect
|
| +// that the recognition session is aborted and the bubble is closed.
|
| +IN_PROC_BROWSER_TEST_F(SpeechRecognitionBrowserTest, CloseBubbleOnCrash) {
|
| + // Events flow handled asynchronously on the UI thread.
|
| + ui_thread_events()->AddAction(base::Bind(
|
| + &SpeechRecognitionBrowserTest::NavigateToPage,
|
| + base::Unretained(this),
|
| + FILE_PATH_LITERAL("basic_recognition.html")));
|
| + ui_thread_events()->ExpectEvent(kEventPageReloaded);
|
| + ui_thread_events()->AddAction(base::Bind(
|
| + &SpeechRecognitionBrowserTest::SimulateClickOnActiveTab,
|
| + base::Unretained(this),
|
| + 0, 0));
|
| + ui_thread_events()->ExpectEvent(kEventBubbleCreated);
|
| + ui_thread_events()->AddAction(base::Bind(
|
| + &SpeechRecognitionBrowserTest::CrashActiveTab,
|
| + base::Unretained(this)));
|
| + ui_thread_events()->ExpectEvent(kEventTabClosedOrCrashed);
|
| + ui_thread_events()->AddAction(base::Bind(
|
| + &SpeechRecognitionBrowserTest::QuitRunLoop,
|
| + base::Unretained(this)));
|
| + ui_thread_events()->SkipFurtherEvents();
|
| +
|
| + // Events flow handled asynchronously on the IO thread.
|
| + io_thread_events()->ExpectEvent(kEventAudioControllerOpened);
|
| + io_thread_events()->ExpectEvent(kEventAudioControllerClosed);
|
| +
|
| + io_thread_events()->Start();
|
| + ui_thread_events()->Start();
|
| +
|
| + // Run until QuitRunLoop action is reached in ui_thread_events.
|
| + run_loop()->Run();
|
| + SpeechRecognitionBubbleController* bubble_controller =
|
| + SpeechRecognitionBubbleController::GetInstanceForTests();
|
| + ASSERT_TRUE(bubble_controller);
|
| + EXPECT_EQ(0, bubble_controller->GetActiveSessionID());
|
| +}
|
| +
|
| +} // namespace speech
|
|
|