Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(294)

Unified Diff: chrome/browser/speech/speech_recognition_browsertest.cc

Issue 10703141: End-to-end browser tests for speech recognition. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 8 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/browser/speech/speech_recognition_browsertest.cc
diff --git a/chrome/browser/speech/speech_recognition_browsertest.cc b/chrome/browser/speech/speech_recognition_browsertest.cc
new file mode 100644
index 0000000000000000000000000000000000000000..950ccdab950043f980c4c45a93dbe69a7cd3ad19
--- /dev/null
+++ b/chrome/browser/speech/speech_recognition_browsertest.cc
@@ -0,0 +1,830 @@
+// 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 "content/browser/browser_main_loop.h"
+#include "chrome/browser/speech/speech_recognition_bubble_controller.h"
+#include "chrome/browser/speech/chrome_speech_recognition_manager_delegate.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 "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/speech/google_one_shot_remote_engine.h"
+#include "content/browser/speech/mock_google_one_shot_server.h"
+#include "content/browser/speech/speech_recognizer.h"
+#include "content/browser/web_contents/web_contents_impl.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/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"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebInputEvent.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";
+
+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 MockGoogleSpeechRecognitionServerDelegate,
+ public media::TestAudioInputControllerDelegate,
+ public SpeechRecognitionBubbleControllerDelegateForTests,
+ public content::NotificationObserver {
+ public:
+ // MockGoogleSpeechRecognitionServerDelegate 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 {
+ 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) {
+ WebKit::WebMouseEvent mouse_event;
+ mouse_event.type = WebKit::WebInputEvent::MouseDown;
+ mouse_event.button = WebKit::WebMouseEvent::ButtonLeft;
+ mouse_event.x = x;
+ mouse_event.y = y;
+ mouse_event.clickCount = 1;
+ WebContents* web_contents = chrome::GetActiveWebContents(browser());
+ web_contents->GetRenderViewHost()->ForwardMouseEvent(mouse_event);
+ mouse_event.type = WebKit::WebInputEvent::MouseUp;
+ web_contents->GetRenderViewHost()->ForwardMouseEvent(mouse_event);
+ }
+
+ void SimulateClickOnBubbleCancelButton() {
+ SpeechRecognitionBubbleController* bubble_controller =
+ SpeechRecognitionBubbleController::GetInstanceForTests();
+ EXPECT_TRUE(bubble_controller);
+ EXPECT_TRUE(bubble_controller->IsShowingMessage());
+ bubble_controller->InfoBubbleButtonClicked(
+ SpeechRecognitionBubble::BUTTON_CANCEL);
+ }
+
+ void SimulateClickOnBubbleTryAgainButton() {
+ SpeechRecognitionBubbleController* bubble_controller =
+ SpeechRecognitionBubbleController::GetInstanceForTests();
+ EXPECT_TRUE(bubble_controller);
+ EXPECT_TRUE(bubble_controller->IsShowingMessage());
+ bubble_controller->InfoBubbleButtonClicked(
+ SpeechRecognitionBubble::BUTTON_TRY_AGAIN);
+ }
+
+ void SimulateBubbleFocusLost() {
+ SpeechRecognitionBubbleController* bubble_controller =
+ SpeechRecognitionBubbleController::GetInstanceForTests();
+ EXPECT_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) {
+ EXPECT_FALSE(command_line->HasSwitch(switches::kDisableSpeechInput));
+
+ // TODO(primiano): uncomment below when adding JS API tests.
+ // EXPECT_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());
+ SpeechRecognizer::SetAudioManagerForTests(
+ new media::MockAudioManager(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();
+ SpeechRecognizer::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 * M_PI / (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();
+ EXPECT_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().
+ EXPECT_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) / GoogleOneShotRemoteEngine::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) / GoogleOneShotRemoteEngine::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) / GoogleOneShotRemoteEngine::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) / GoogleOneShotRemoteEngine::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) / GoogleOneShotRemoteEngine::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

Powered by Google App Engine
This is Rietveld 408576698