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

Side by Side 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 unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/bind.h"
6 #include "base/command_line.h"
7 #include "base/file_path.h"
8 #include "base/run_loop.h"
9 #include "base/memory/scoped_ptr.h"
10 #include "base/utf_string_conversions.h"
11 #include "content/browser/browser_main_loop.h"
12 #include "chrome/browser/speech/speech_recognition_bubble_controller.h"
13 #include "chrome/browser/speech/chrome_speech_recognition_manager_delegate.h"
14 #include "chrome/browser/ui/browser.h"
15 #include "chrome/browser/ui/browser_tabstrip.h"
16 #include "chrome/test/base/in_process_browser_test.h"
17 #include "chrome/test/base/ui_test_utils.h"
18 #include "content/browser/renderer_host/render_view_host_impl.h"
19 #include "content/browser/speech/google_one_shot_remote_engine.h"
20 #include "content/browser/speech/mock_google_one_shot_server.h"
21 #include "content/browser/speech/speech_recognizer.h"
22 #include "content/browser/web_contents/web_contents_impl.h"
23 #include "content/public/browser/browser_thread.h"
24 #include "content/public/browser/notification_types.h"
25 #include "content/public/browser/speech_recognition_manager.h"
26 #include "content/public/common/content_switches.h"
27 #include "content/public/common/speech_recognition_result.h"
28 #include "media/audio/mock_audio_manager.h"
29 #include "media/audio/test_audio_input_controller_factory.h"
30 #include "third_party/WebKit/Source/WebKit/chromium/public/WebInputEvent.h"
31
32 using base::RunLoop;
33 using content::BrowserThread;
34 using content::WebContents;
35
36 namespace {
37 // Events expected on the IO thread.
38 const char* kEventClientConnected = "ClientConnected";
39 const char* kEventClientAudioUpload = "ClientAudioUpload";
40 const char* kEventClientAudioUploadComplete = "ClientAudioUploadComplete";
41 const char* kEventClientDisconnected = "ClientDisconnected";
42 const char* kEventAudioControllerOpened = "AudioControllerOpened";
43 const char* kEventAudioControllerClosed = "AudioControllerClosed";
44 const char* kEventBubbleLostFocus = "BubbleLostFocus";
45 // Events expected on the UI thread.
46 const char* kEventPageReloaded = "PageReloaded";
47 const char* kEventTabClosedOrCrashed = "TabClosedOrCrashed";
48 const char* kEventBubbleCreated = "BubbleCreated";
49 const char* kEventBubbleSwitchedToRecordingMode =
50 "BubbleSwitchedToRecordingMode";
51 const char* kEventBubbleSwitchedToRecognizingMode =
52 "BubbleSwitchedToRecognizingMode";
53 const char* kEventBubbleMessageDisplayed = "BubbleMessageDisplayed";
54 const char* kEventBubbleClosed = "BubbleClosed";
55
56 content::SpeechRecognitionResult GetGoodSpeechResult() {
57 content::SpeechRecognitionResult result;
58 result.hypotheses.push_back(
59 content::SpeechRecognitionHypothesis(UTF8ToUTF16("Pictures of the moon"),
60 1.0F));
61 return result;
62 }
63
64 // Utility class for event-driven tests.
65 // Some end-to-end tests, as the ones in this class, involve the interaction of
66 // several objects on different threads which, due to their intrinsically
67 // concurrent nature, are extremely hard to handle directly on the text fixture
68 // using the traditional RunLoop::Run/Quit pattern.
69 // This class aims to help testing in this scenario by means of a discrete
70 // event simulation pattern, as follows: before a test is started, this class
71 // (one instance for each thread) is pumped with the sequence of events that is
72 // expected at runtime. Furhermore, each event can be followed by one or more
73 // "reactions", which will be fired (on the same thread) after its notification.
74 // If the sequence of events detected at runtime does not match the foreseen one
75 // this class will issue a GTEST_FATAL_FAILURE_, and (optionally) quit the
76 // message loop aborting the test.
77 class EventDrivenTimeline {
78 public:
79 typedef const char* EventDescriptor;
80
81 explicit EventDrivenTimeline(BrowserThread::ID thread_id)
82 : running_(false),
83 skip_events_after_end_(false),
84 thread_id_(thread_id) {
85 ExpectEvent(kStartEvent);
86 }
87
88 ~EventDrivenTimeline() {
89 running_ = false;
90 // All the timeline events must have been consumed.
91 while (!timeline_entries_.empty()) {
92 TimelineEntry& entry = timeline_entries_.front();
93 FailWithMessage("Unconsumed event " + std::string(entry.expected_event));
94 timeline_entries_.pop_front();
95 }
96 }
97
98 void ExpectEvent(EventDescriptor event, int repetitions) {
99 // Event timeline can be setup only before the events firing starts.
100 ASSERT_FALSE(running_);
101 for (int i = 0; i < repetitions; ++i) {
102 timeline_entries_.push_back(TimelineEntry(event));
103 }
104 }
105
106 void ExpectEvent(EventDescriptor event) {
107 // No further events can be added after a call to |SkipFurtherEvents()|.
108 DCHECK(!skip_events_after_end_);
109 ExpectEvent(event, 1);
110 }
111
112 void SkipFurtherEvents() {
113 skip_events_after_end_ = true;
114 }
115
116 void AddAction(base::Closure action) {
117 // Event timeline can be setup only before the events firing starts.
118 ASSERT_FALSE(running_);
119 DCHECK(!timeline_entries_.empty());
120 timeline_entries_.back().actions.push_back(action);
121 }
122
123 void SetActionOnFailure(base::Closure action) {
124 action_on_failure_ = action;
125 }
126
127 void Start() {
128 running_ = true;
129 // Fire the start event on the proper thread since this method will be
130 // always called on the test thread (UI).
131 BrowserThread::PostTask(thread_id_, FROM_HERE, base::Bind(
132 &EventDrivenTimeline::NotifyEvent,
133 base::Unretained(this),
134 kStartEvent
135 ));
136 }
137
138 void NotifyEvent(EventDescriptor event) {
139 ASSERT_TRUE(running_);
140 DCHECK(BrowserThread::CurrentlyOn(thread_id_));
141
142 const std::string event_str(event);
143
144 if (timeline_entries_.empty()) {
145 if (!skip_events_after_end_) {
146 FailWithMessage(std::string(
147 "Got event " + event_str + ", exepecting no more events."));
148 }
149 return;
150 }
151
152 TimelineEntry& entry = timeline_entries_.front();
153 if (entry.expected_event != event) {
154 FailWithMessage(std::string(
155 "Got event " + event_str +
156 ", exepecting event " + entry.expected_event + "."));
157 return;
158 }
159
160 while (!entry.actions.empty()) {
161 MessageLoop::current()->PostTask(FROM_HERE, entry.actions.front());
162 entry.actions.pop_front();
163 }
164 timeline_entries_.pop_front();
165 }
166
167 private:
168 static EventDescriptor kStartEvent;
169
170 struct TimelineEntry {
171 EventDescriptor expected_event;
172 std::list<base::Closure> actions;
173
174 explicit TimelineEntry(EventDescriptor expected_event_value)
175 : expected_event(expected_event_value) {}
176 ~TimelineEntry() {}
177 };
178
179 void FailWithMessage(const std::string& message) {
180 if (running_ && !action_on_failure_.Equals(base::Closure()))
181 action_on_failure_.Run();
182 GTEST_FATAL_FAILURE_(message.c_str());
183 }
184
185 bool running_;
186 bool skip_events_after_end_;
187 BrowserThread::ID thread_id_;
188 std::list<TimelineEntry> timeline_entries_;
189 base::Closure action_on_failure_;
190
191 DISALLOW_COPY_AND_ASSIGN(EventDrivenTimeline);
192 };
193 EventDrivenTimeline::EventDescriptor EventDrivenTimeline::kStartEvent = "Start";
194
195 } // namespace
196
197 namespace speech {
198
199 class SpeechRecognitionBrowserTest
200 : public InProcessBrowserTest,
201 public MockGoogleSpeechRecognitionServerDelegate,
202 public media::TestAudioInputControllerDelegate,
203 public SpeechRecognitionBubbleControllerDelegateForTests,
204 public content::NotificationObserver {
205 public:
206 // MockGoogleSpeechRecognitionServerDelegate methods.
207 virtual void OnClientConnected() OVERRIDE {
208 io_thread_events()->NotifyEvent(kEventClientConnected);
209 }
210
211 virtual void OnClientAudioUpload() OVERRIDE {
212 io_thread_events()->NotifyEvent(kEventClientAudioUpload);
213 }
214
215 virtual void OnClientAudioUploadComplete() OVERRIDE {
216 io_thread_events()->NotifyEvent(kEventClientAudioUploadComplete);
217 }
218
219 virtual void OnClientDisconnected() {
220 io_thread_events()->NotifyEvent(kEventClientDisconnected);
221 }
222
223 // media::TestAudioInputControllerDelegate methods.
224 virtual void TestAudioControllerOpened(
225 media::TestAudioInputController* controller) OVERRIDE {
226 io_thread_events()->NotifyEvent(kEventAudioControllerOpened);
227 }
228
229 virtual void TestAudioControllerClosed(
230 media::TestAudioInputController* controller) OVERRIDE {
231 io_thread_events()->NotifyEvent(kEventAudioControllerClosed);
232 }
233
234 // SpeechRecognitionBubbleControllerDelegateForTests methods.
235 virtual void BubbleCreated() OVERRIDE {
236 ui_thread_events()->NotifyEvent(kEventBubbleCreated);
237 }
238
239 virtual void BubbleSwitchedToRecordingMode() OVERRIDE {
240 ui_thread_events()->NotifyEvent(kEventBubbleSwitchedToRecordingMode);
241 }
242
243 virtual void BubbleSwitchedToRecognizingMode() OVERRIDE {
244 ui_thread_events()->NotifyEvent(kEventBubbleSwitchedToRecognizingMode);
245 }
246
247 virtual void BubbleMessageDisplayed() OVERRIDE {
248 ui_thread_events()->NotifyEvent(kEventBubbleMessageDisplayed);
249 }
250
251 virtual void BubbleClosed() OVERRIDE {
252 ui_thread_events()->NotifyEvent(kEventBubbleClosed);
253 }
254
255 // content::NotificationObserver methods.
256 virtual void Observe(int type,
257 const content::NotificationSource& source,
258 const content::NotificationDetails& details) OVERRIDE {
259 if (type == content::NOTIFICATION_LOAD_STOP)
260 ui_thread_events()->NotifyEvent(kEventPageReloaded);
261 else if (type == content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED)
262 ui_thread_events()->NotifyEvent(kEventTabClosedOrCrashed);
263 else
264 NOTREACHED();
265 }
266
267 // Helper methods used by test fixtures.
268 void FeedAudioControllerWithSilence(int duration_ms) {
269 FeedAudioController(duration_ms, false);
270 }
271
272 void FeedAudioControllerWithNoise(int duration_ms) {
273 FeedAudioController(duration_ms, true);
274 }
275
276 void CrashActiveTab() {
277 ui_test_utils::CrashTab(chrome::GetActiveWebContents(browser()));
278 }
279
280 void NavigateToPage(const FilePath::CharType* filename) {
281 const FilePath kTestDir(FILE_PATH_LITERAL("speech"));
282 GURL test_url = ui_test_utils::GetTestUrl(kTestDir, FilePath(filename));
283 ui_test_utils::NavigateToURL(browser(), test_url);
284 }
285
286 void SimulateClickOnActiveTab(int x, int y) {
287 WebKit::WebMouseEvent mouse_event;
288 mouse_event.type = WebKit::WebInputEvent::MouseDown;
289 mouse_event.button = WebKit::WebMouseEvent::ButtonLeft;
290 mouse_event.x = x;
291 mouse_event.y = y;
292 mouse_event.clickCount = 1;
293 WebContents* web_contents = chrome::GetActiveWebContents(browser());
294 web_contents->GetRenderViewHost()->ForwardMouseEvent(mouse_event);
295 mouse_event.type = WebKit::WebInputEvent::MouseUp;
296 web_contents->GetRenderViewHost()->ForwardMouseEvent(mouse_event);
297 }
298
299 void SimulateClickOnBubbleCancelButton() {
300 SpeechRecognitionBubbleController* bubble_controller =
301 SpeechRecognitionBubbleController::GetInstanceForTests();
302 EXPECT_TRUE(bubble_controller);
303 EXPECT_TRUE(bubble_controller->IsShowingMessage());
304 bubble_controller->InfoBubbleButtonClicked(
305 SpeechRecognitionBubble::BUTTON_CANCEL);
306 }
307
308 void SimulateClickOnBubbleTryAgainButton() {
309 SpeechRecognitionBubbleController* bubble_controller =
310 SpeechRecognitionBubbleController::GetInstanceForTests();
311 EXPECT_TRUE(bubble_controller);
312 EXPECT_TRUE(bubble_controller->IsShowingMessage());
313 bubble_controller->InfoBubbleButtonClicked(
314 SpeechRecognitionBubble::BUTTON_TRY_AGAIN);
315 }
316
317 void SimulateBubbleFocusLost() {
318 SpeechRecognitionBubbleController* bubble_controller =
319 SpeechRecognitionBubbleController::GetInstanceForTests();
320 EXPECT_TRUE(bubble_controller);
321 bubble_controller->InfoBubbleFocusChanged();
322 }
323
324 bool CheckJavascripResultInPage() {
325 WebContents* web_contents = chrome::GetActiveWebContents(browser());
326 if (!web_contents)
327 return false;
328 return web_contents->GetURL().ref() == "pass";
329 return false;
330 }
331
332 void NotifyEventToIOTimeline(EventDrivenTimeline::EventDescriptor event) {
333 if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
334 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(
335 &SpeechRecognitionBrowserTest::NotifyEventToIOTimeline,
336 base::Unretained(this),
337 event));
338 return;
339 }
340 io_thread_events()->NotifyEvent(event);
341 }
342
343 void QuitRunLoop() {
344 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
345 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
346 &SpeechRecognitionBrowserTest::QuitRunLoop, base::Unretained(this)));
347 return;
348 }
349 // QuitClosure() needs to be called on the UI thread, so we need both the
350 // PostTask(s) (above and below), in order to serve failures triggered by
351 // both the |io_thread_events_| and |ui_thread_events_|.
352 BrowserThread::PostTask(BrowserThread::UI,
353 FROM_HERE,
354 run_loop_->QuitClosure());
355 }
356
357 RunLoop* run_loop() { return run_loop_.get(); }
358
359 EventDrivenTimeline* io_thread_events() { return io_thread_events_.get(); }
360
361 EventDrivenTimeline* ui_thread_events() { return ui_thread_events_.get(); }
362
363 MockGoogleOneShotServer* mock_one_shot_server() {
364 return mock_one_shot_server_.get();
365 }
366
367 protected:
368 // InProcessBrowserTest methods.
369 virtual void SetUpCommandLine(CommandLine* command_line) {
370 EXPECT_FALSE(command_line->HasSwitch(switches::kDisableSpeechInput));
371
372 // TODO(primiano): uncomment below when adding JS API tests.
373 // EXPECT_TRUE(command_line->HasSwitch(switches::kEnableScriptedSpeech));
374 }
375
376 virtual void SetUpInProcessBrowserTestFixture() OVERRIDE {
377 test_audio_input_controller_factory_.SetDelegateForTests(this);
378 media::AudioInputController::set_factory_for_testing(
379 &test_audio_input_controller_factory_);
380 mock_one_shot_server_.reset(new MockGoogleOneShotServer(this));
381 io_thread_events_.reset(new EventDrivenTimeline(BrowserThread::IO));
382 io_thread_events_->SetActionOnFailure(base::Bind(
383 &SpeechRecognitionBrowserTest::QuitRunLoop, base::Unretained(this)));
384 ui_thread_events_.reset(new EventDrivenTimeline(BrowserThread::UI));
385 ui_thread_events_->SetActionOnFailure(base::Bind(
386 &SpeechRecognitionBrowserTest::QuitRunLoop, base::Unretained(this)));
387 SpeechRecognitionBubbleController::SetDelegateForTests(this);
388 }
389
390 virtual void SetUpOnMainThread() OVERRIDE {
391 ASSERT_TRUE(content::SpeechRecognitionManager::GetInstance());
392 SpeechRecognizer::SetAudioManagerForTests(
393 new media::MockAudioManager(BrowserThread::IO));
394 run_loop_.reset(new RunLoop());
395
396 const WebContents* web_contents = chrome::GetActiveWebContents(browser());
397 registrar_.Add(
398 this,
399 content::NOTIFICATION_LOAD_STOP,
400 content::Source<content::NavigationController>(
401 &web_contents->GetController()));
402
403 registrar_.Add(
404 this,
405 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
406 content::Source<WebContents>(web_contents));
407 }
408
409 virtual void CleanUpOnMainThread() OVERRIDE {
410 registrar_.RemoveAll();
411 SpeechRecognizer::SetAudioManagerForTests(NULL);
412 }
413
414 virtual void TearDownInProcessBrowserTestFixture() OVERRIDE {
415 media::AudioInputController::set_factory_for_testing(NULL);
416 io_thread_events_.reset();
417 ui_thread_events_.reset();
418 mock_one_shot_server_.reset();
419 }
420
421 private:
422 static void FeedSingleBufferToAudioController(
423 scoped_refptr<media::TestAudioInputController> controller,
424 size_t buffer_size,
425 bool fill_with_noise) {
426 DCHECK(controller.get());
427 scoped_array<uint8> audio_buffer(new uint8[buffer_size]);
428 if (fill_with_noise) {
429 uint8* buf = audio_buffer.get();
430 for (size_t i = 0; i < buffer_size; ++i)
431 buf[i] = static_cast<uint8>(127 * sin(i * M_PI / (16 * buffer_size)));
432 } else {
433 memset(audio_buffer.get(), 0, buffer_size);
434 }
435 controller->event_handler()->OnData(controller,
436 audio_buffer.get(),
437 buffer_size);
438 }
439
440 void FeedAudioController(int duration_ms, bool feed_with_noise) {
441 media::TestAudioInputController* controller =
442 test_audio_input_controller_factory_.controller();
443 EXPECT_TRUE(controller);
444 const media::AudioParameters& audio_params = controller->audio_parameters();
445 const size_t buffer_size = audio_params.GetBytesPerBuffer();
446 const int ms_per_buffer = audio_params.frames_per_buffer() * 1000 /
447 audio_params.sample_rate();
448 // We can only simulate durations that are integer multiples of the
449 // buffer size. In this regard see
450 // SpeechRecognitionEngine::GetDesiredAudioChunkDurationMs().
451 EXPECT_EQ(0, duration_ms % ms_per_buffer);
452
453 const int n_buffers = duration_ms / ms_per_buffer;
454 for (int i = 0; i < n_buffers; ++i) {
455 MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
456 &FeedSingleBufferToAudioController,
457 scoped_refptr<media::TestAudioInputController>(controller),
458 buffer_size,
459 feed_with_noise));
460 }
461 }
462
463 scoped_ptr<EventDrivenTimeline> io_thread_events_;
464 scoped_ptr<EventDrivenTimeline> ui_thread_events_;
465 scoped_ptr<RunLoop> run_loop_;
466 content::NotificationRegistrar registrar_;
467 scoped_ptr<MockGoogleOneShotServer> mock_one_shot_server_;
468 media::TestAudioInputControllerFactory test_audio_input_controller_factory_;
469 };
470
471 // Simulates a regular speech recognition scenario. 2.5 secs of audio (0.5 s. of
472 // silence + 1 s. of speech + 1 s. of silence) are pushed into the audio
473 // controller. At end of recognition we check that the bubble has been hidden,
474 // the audio controller has been cleanly closed, the proper language and grammar
475 // parameters are passed to the server and the javascript has seen the
476 // onwebkitspeechchange event and matched the expected result string (writing
477 // 'PASS' in the <div id='status'> and issuing a page reload.
478 IN_PROC_BROWSER_TEST_F(SpeechRecognitionBrowserTest, SuccessfulRecognition) {
479 // Events flow handled asynchronously on the UI thread.
480 ui_thread_events()->AddAction(base::Bind(
481 &SpeechRecognitionBrowserTest::NavigateToPage,
482 base::Unretained(this),
483 FILE_PATH_LITERAL("basic_recognition.html")));
484 ui_thread_events()->ExpectEvent(kEventPageReloaded);
485 ui_thread_events()->AddAction(base::Bind(
486 &SpeechRecognitionBrowserTest::SimulateClickOnActiveTab,
487 base::Unretained(this),
488 0, 0));
489 ui_thread_events()->ExpectEvent(kEventBubbleCreated);
490 ui_thread_events()->ExpectEvent(kEventBubbleSwitchedToRecordingMode);
491 ui_thread_events()->ExpectEvent(kEventBubbleSwitchedToRecognizingMode);
492 ui_thread_events()->ExpectEvent(kEventBubbleClosed);
493 ui_thread_events()->ExpectEvent(kEventPageReloaded);
494 ui_thread_events()->AddAction(base::Bind(
495 &SpeechRecognitionBrowserTest::QuitRunLoop,
496 base::Unretained(this)));
497
498 // Events flow handled asynchronously on the IO thread.
499 io_thread_events()->ExpectEvent(kEventAudioControllerOpened);
500 io_thread_events()->AddAction(base::Bind(
501 &SpeechRecognitionBrowserTest::FeedAudioControllerWithSilence,
502 base::Unretained(this),
503 500 /* ms. */));
504 io_thread_events()->AddAction(base::Bind(
505 &SpeechRecognitionBrowserTest::FeedAudioControllerWithNoise,
506 base::Unretained(this),
507 1000 /* ms. */));
508 io_thread_events()->AddAction(base::Bind(
509 &SpeechRecognitionBrowserTest::FeedAudioControllerWithSilence,
510 base::Unretained(this),
511 1000 /* ms. */));
512 const int expected_chunks =
513 (500 + 1000 + 1000) / GoogleOneShotRemoteEngine::kAudioPacketIntervalMs;
514 io_thread_events()->ExpectEvent(kEventClientConnected);
515 io_thread_events()->ExpectEvent(kEventClientAudioUpload, expected_chunks);
516 io_thread_events()->ExpectEvent(kEventClientAudioUploadComplete);
517 io_thread_events()->ExpectEvent(kEventAudioControllerClosed);
518 io_thread_events()->AddAction(base::Bind(
519 &MockGoogleOneShotServer::SimulateResult,
520 base::Unretained(mock_one_shot_server()),
521 GetGoodSpeechResult()));
522 io_thread_events()->ExpectEvent(kEventClientDisconnected);
523
524 io_thread_events()->Start();
525 ui_thread_events()->Start();
526
527 // Runs until RunLoop::Quit action is reached in ui_thread_events.
528 run_loop()->Run();
529
530 EXPECT_TRUE(CheckJavascripResultInPage());
531
532 // Check that a non blank lang (en-US on most bots) is passed in the request.
533 EXPECT_NE("", mock_one_shot_server()->GetRequestLanguage());
534
535 // Check that the grammar attribute is properly passed in the request.
536 EXPECT_EQ("http://example.com/grammar.xml",
537 mock_one_shot_server()->GetRequestGrammar());
538 }
539
540 // Simulates a server failure scenario. We expect the error message to be
541 // properly shown in the bubble, simulate a click on the Cancel button and check
542 // that everything is closed cleanly after that.
543 IN_PROC_BROWSER_TEST_F(SpeechRecognitionBrowserTest, CancelAfterError) {
544 // Events flow handled asynchronously on the UI thread.
545 ui_thread_events()->AddAction(base::Bind(
546 &SpeechRecognitionBrowserTest::NavigateToPage,
547 base::Unretained(this),
548 FILE_PATH_LITERAL("basic_recognition.html")));
549 ui_thread_events()->ExpectEvent(kEventPageReloaded);
550 ui_thread_events()->AddAction(base::Bind(
551 &SpeechRecognitionBrowserTest::SimulateClickOnActiveTab,
552 base::Unretained(this),
553 0, 0));
554 ui_thread_events()->ExpectEvent(kEventBubbleCreated);
555 ui_thread_events()->ExpectEvent(kEventBubbleSwitchedToRecordingMode);
556 ui_thread_events()->ExpectEvent(kEventBubbleSwitchedToRecognizingMode);
557 ui_thread_events()->ExpectEvent(kEventBubbleMessageDisplayed);
558 ui_thread_events()->AddAction(base::Bind(
559 &SpeechRecognitionBrowserTest::SimulateClickOnBubbleCancelButton,
560 base::Unretained(this)));
561 ui_thread_events()->ExpectEvent(kEventBubbleClosed);
562 ui_thread_events()->AddAction(base::Bind(
563 &SpeechRecognitionBrowserTest::QuitRunLoop,
564 base::Unretained(this)));
565
566 // Events flow handled asynchronously on the IO thread.
567 io_thread_events()->ExpectEvent(kEventAudioControllerOpened);
568 io_thread_events()->AddAction(base::Bind(
569 &SpeechRecognitionBrowserTest::FeedAudioControllerWithSilence,
570 base::Unretained(this),
571 500 /* ms. */));
572 io_thread_events()->AddAction(base::Bind(
573 &SpeechRecognitionBrowserTest::FeedAudioControllerWithNoise,
574 base::Unretained(this),
575 1000 /* ms.*/));
576 io_thread_events()->AddAction(base::Bind(
577 &SpeechRecognitionBrowserTest::FeedAudioControllerWithSilence,
578 base::Unretained(this),
579 1000 /* ms. */));
580 const int expected_chunks =
581 (500 + 1000 + 1000) / GoogleOneShotRemoteEngine::kAudioPacketIntervalMs;
582 io_thread_events()->ExpectEvent(kEventClientConnected);
583 io_thread_events()->ExpectEvent(kEventClientAudioUpload, expected_chunks);
584 io_thread_events()->ExpectEvent(kEventClientAudioUploadComplete);
585 io_thread_events()->ExpectEvent(kEventAudioControllerClosed);
586 io_thread_events()->AddAction(base::Bind(
587 &MockGoogleOneShotServer::SimulateServerFailure,
588 base::Unretained(mock_one_shot_server())));
589 io_thread_events()->ExpectEvent(kEventClientDisconnected);
590
591 io_thread_events()->Start();
592 ui_thread_events()->Start();
593
594 // Run until QuitRunLoop action is reached in ui_thread_events.
595 run_loop()->Run();
596
597 EXPECT_FALSE(CheckJavascripResultInPage());
598 }
599
600 // Similar to the previous scenario, with the exception that this time we
601 // simulate a click on the Try Again button and check that a second recognition
602 // session is carried on succesfully.
603 IN_PROC_BROWSER_TEST_F(SpeechRecognitionBrowserTest, TryAgainAfterError) {
604 // Events flow handled asynchronously on the UI thread.
605 ui_thread_events()->AddAction(base::Bind(
606 &SpeechRecognitionBrowserTest::NavigateToPage,
607 base::Unretained(this),
608 FILE_PATH_LITERAL("basic_recognition.html")));
609 ui_thread_events()->ExpectEvent(kEventPageReloaded);
610 ui_thread_events()->AddAction(base::Bind(
611 &SpeechRecognitionBrowserTest::SimulateClickOnActiveTab,
612 base::Unretained(this),
613 0, 0));
614 ui_thread_events()->ExpectEvent(kEventBubbleCreated);
615 ui_thread_events()->ExpectEvent(kEventBubbleSwitchedToRecordingMode);
616 ui_thread_events()->ExpectEvent(kEventBubbleSwitchedToRecognizingMode);
617 ui_thread_events()->ExpectEvent(kEventBubbleMessageDisplayed);
618 ui_thread_events()->AddAction(base::Bind(
619 &SpeechRecognitionBrowserTest::SimulateClickOnBubbleTryAgainButton,
620 base::Unretained(this)));
621 ui_thread_events()->ExpectEvent(kEventBubbleClosed);
622 // Second bubble expected after "try again".
623 ui_thread_events()->ExpectEvent(kEventBubbleCreated);
624 ui_thread_events()->ExpectEvent(kEventBubbleSwitchedToRecordingMode);
625 ui_thread_events()->ExpectEvent(kEventBubbleSwitchedToRecognizingMode);
626 ui_thread_events()->ExpectEvent(kEventBubbleClosed);
627 ui_thread_events()->ExpectEvent(kEventPageReloaded);
628
629 ui_thread_events()->AddAction(base::Bind(
630 &SpeechRecognitionBrowserTest::QuitRunLoop,
631 base::Unretained(this)));
632
633 // Events flow handled asynchronously on the IO thread.
634 for (int i = 0; i < 2; ++i) {
635 io_thread_events()->ExpectEvent(kEventAudioControllerOpened);
636 io_thread_events()->AddAction(base::Bind(
637 &SpeechRecognitionBrowserTest::FeedAudioControllerWithSilence,
638 base::Unretained(this),
639 500 /* ms. */));
640 io_thread_events()->AddAction(base::Bind(
641 &SpeechRecognitionBrowserTest::FeedAudioControllerWithNoise,
642 base::Unretained(this),
643 1000 /* ms.*/));
644 io_thread_events()->AddAction(base::Bind(
645 &SpeechRecognitionBrowserTest::FeedAudioControllerWithSilence,
646 base::Unretained(this),
647 1000 /* ms. */));
648 const int expected_chunks =
649 (500 + 1000 + 1000) / GoogleOneShotRemoteEngine::kAudioPacketIntervalMs;
650 io_thread_events()->ExpectEvent(kEventClientConnected);
651 io_thread_events()->ExpectEvent(kEventClientAudioUpload, expected_chunks);
652 io_thread_events()->ExpectEvent(kEventClientAudioUploadComplete);
653 io_thread_events()->ExpectEvent(kEventAudioControllerClosed);
654 if (i == 0) {
655 io_thread_events()->AddAction(base::Bind(
656 &MockGoogleOneShotServer::SimulateServerFailure,
657 base::Unretained(mock_one_shot_server())));
658 } else {
659 io_thread_events()->AddAction(base::Bind(
660 &MockGoogleOneShotServer::SimulateResult,
661 base::Unretained(mock_one_shot_server()),
662 GetGoodSpeechResult()));
663 }
664 io_thread_events()->ExpectEvent(kEventClientDisconnected);
665 }
666
667 io_thread_events()->Start();
668 ui_thread_events()->Start();
669
670 // Run until QuitRunLoop action is reached in ui_thread_events.
671 run_loop()->Run();
672
673 EXPECT_TRUE(CheckJavascripResultInPage());
674 }
675
676 // Simulates a loss of focus of the speech recognition bubble while capturing
677 // audio. We expect that the speech recognition session is aborted without
678 // providing any result.
679 IN_PROC_BROWSER_TEST_F(SpeechRecognitionBrowserTest, FocusLostWhileCapturing) {
680 // Events flow handled asynchronously on the UI thread.
681 ui_thread_events()->AddAction(base::Bind(
682 &SpeechRecognitionBrowserTest::NavigateToPage,
683 base::Unretained(this),
684 FILE_PATH_LITERAL("basic_recognition.html")));
685 ui_thread_events()->ExpectEvent(kEventPageReloaded);
686 ui_thread_events()->AddAction(base::Bind(
687 &SpeechRecognitionBrowserTest::SimulateClickOnActiveTab,
688 base::Unretained(this),
689 0, 0));
690 ui_thread_events()->ExpectEvent(kEventBubbleCreated);
691 ui_thread_events()->ExpectEvent(kEventBubbleSwitchedToRecordingMode);
692 ui_thread_events()->AddAction(base::Bind(
693 &SpeechRecognitionBrowserTest::SimulateBubbleFocusLost,
694 base::Unretained(this)));
695 ui_thread_events()->ExpectEvent(kEventBubbleClosed);
696 ui_thread_events()->AddAction(base::Bind(
697 &SpeechRecognitionBrowserTest::QuitRunLoop,
698 base::Unretained(this)));
699
700 // Events flow handled asynchronously on the IO thread.
701 io_thread_events()->ExpectEvent(kEventAudioControllerOpened);
702 io_thread_events()->AddAction(base::Bind(
703 &SpeechRecognitionBrowserTest::FeedAudioControllerWithSilence,
704 base::Unretained(this),
705 500 /* ms. */));
706 io_thread_events()->AddAction(base::Bind(
707 &SpeechRecognitionBrowserTest::FeedAudioControllerWithNoise,
708 base::Unretained(this),
709 1000 /* ms.*/));
710 const int expected_chunks =
711 (500 + 1000) / GoogleOneShotRemoteEngine::kAudioPacketIntervalMs;
712 io_thread_events()->ExpectEvent(kEventClientConnected);
713 io_thread_events()->ExpectEvent(kEventClientAudioUpload, expected_chunks);
714 io_thread_events()->ExpectEvent(kEventAudioControllerClosed);
715 io_thread_events()->ExpectEvent(kEventClientDisconnected);
716
717 io_thread_events()->Start();
718 ui_thread_events()->Start();
719
720 // Run until QuitRunLoop action is reached in ui_thread_events.
721 run_loop()->Run();
722
723 EXPECT_FALSE(CheckJavascripResultInPage());
724 }
725
726 // Simulates a loss of focus of the speech recognition bubble after the capture
727 // is ended but before the result is provided. We expect that the bubble is
728 // closed but the recognition session is succesfully completed in background.
729 IN_PROC_BROWSER_TEST_F(SpeechRecognitionBrowserTest, FocusLostAfterCapture) {
730 // Events flow handled asynchronously on the UI thread.
731 ui_thread_events()->AddAction(base::Bind(
732 &SpeechRecognitionBrowserTest::NavigateToPage,
733 base::Unretained(this),
734 FILE_PATH_LITERAL("basic_recognition.html")));
735 ui_thread_events()->ExpectEvent(kEventPageReloaded);
736 ui_thread_events()->AddAction(base::Bind(
737 &SpeechRecognitionBrowserTest::SimulateClickOnActiveTab,
738 base::Unretained(this),
739 0, 0));
740 ui_thread_events()->ExpectEvent(kEventBubbleCreated);
741 ui_thread_events()->ExpectEvent(kEventBubbleSwitchedToRecordingMode);
742 ui_thread_events()->ExpectEvent(kEventBubbleSwitchedToRecognizingMode);
743 ui_thread_events()->AddAction(base::Bind(
744 &SpeechRecognitionBrowserTest::SimulateBubbleFocusLost,
745 base::Unretained(this)));
746 ui_thread_events()->ExpectEvent(kEventBubbleClosed);
747 ui_thread_events()->AddAction(base::Bind(
748 &SpeechRecognitionBrowserTest::NotifyEventToIOTimeline,
749 base::Unretained(this),
750 kEventBubbleLostFocus));
751 ui_thread_events()->ExpectEvent(kEventPageReloaded);
752 ui_thread_events()->AddAction(base::Bind(
753 &SpeechRecognitionBrowserTest::QuitRunLoop,
754 base::Unretained(this)));
755
756 // Events flow handled asynchronously on the IO thread.
757 io_thread_events()->ExpectEvent(kEventAudioControllerOpened);
758 io_thread_events()->AddAction(base::Bind(
759 &SpeechRecognitionBrowserTest::FeedAudioControllerWithSilence,
760 base::Unretained(this),
761 500 /* ms. */));
762 io_thread_events()->AddAction(base::Bind(
763 &SpeechRecognitionBrowserTest::FeedAudioControllerWithNoise,
764 base::Unretained(this),
765 1000 /* ms. */));
766 io_thread_events()->AddAction(base::Bind(
767 &SpeechRecognitionBrowserTest::FeedAudioControllerWithSilence,
768 base::Unretained(this),
769 1000 /* ms. */));
770 const int expected_chunks =
771 (500 + 1000 + 1000) / GoogleOneShotRemoteEngine::kAudioPacketIntervalMs;
772 io_thread_events()->ExpectEvent(kEventClientConnected);
773 io_thread_events()->ExpectEvent(kEventClientAudioUpload, expected_chunks);
774 io_thread_events()->ExpectEvent(kEventClientAudioUploadComplete);
775 io_thread_events()->ExpectEvent(kEventAudioControllerClosed);
776 io_thread_events()->ExpectEvent(kEventBubbleLostFocus);
777 io_thread_events()->AddAction(base::Bind(
778 &MockGoogleOneShotServer::SimulateResult,
779 base::Unretained(mock_one_shot_server()),
780 GetGoodSpeechResult()));
781 io_thread_events()->ExpectEvent(kEventClientDisconnected);
782
783 io_thread_events()->Start();
784 ui_thread_events()->Start();
785
786 // Run until QuitRunLoop action is reached in ui_thread_events.
787 run_loop()->Run();
788
789 EXPECT_TRUE(CheckJavascripResultInPage());
790 }
791
792 // Simulates a crash of the tab during a speech recognition session. We expect
793 // that the recognition session is aborted and the bubble is closed.
794 IN_PROC_BROWSER_TEST_F(SpeechRecognitionBrowserTest, CloseBubbleOnCrash) {
795 // Events flow handled asynchronously on the UI thread.
796 ui_thread_events()->AddAction(base::Bind(
797 &SpeechRecognitionBrowserTest::NavigateToPage,
798 base::Unretained(this),
799 FILE_PATH_LITERAL("basic_recognition.html")));
800 ui_thread_events()->ExpectEvent(kEventPageReloaded);
801 ui_thread_events()->AddAction(base::Bind(
802 &SpeechRecognitionBrowserTest::SimulateClickOnActiveTab,
803 base::Unretained(this),
804 0, 0));
805 ui_thread_events()->ExpectEvent(kEventBubbleCreated);
806 ui_thread_events()->AddAction(base::Bind(
807 &SpeechRecognitionBrowserTest::CrashActiveTab,
808 base::Unretained(this)));
809 ui_thread_events()->ExpectEvent(kEventTabClosedOrCrashed);
810 ui_thread_events()->AddAction(base::Bind(
811 &SpeechRecognitionBrowserTest::QuitRunLoop,
812 base::Unretained(this)));
813 ui_thread_events()->SkipFurtherEvents();
814
815 // Events flow handled asynchronously on the IO thread.
816 io_thread_events()->ExpectEvent(kEventAudioControllerOpened);
817 io_thread_events()->ExpectEvent(kEventAudioControllerClosed);
818
819 io_thread_events()->Start();
820 ui_thread_events()->Start();
821
822 // Run until QuitRunLoop action is reached in ui_thread_events.
823 run_loop()->Run();
824 SpeechRecognitionBubbleController* bubble_controller =
825 SpeechRecognitionBubbleController::GetInstanceForTests();
826 ASSERT_TRUE(bubble_controller);
827 EXPECT_EQ(0, bubble_controller->GetActiveSessionID());
828 }
829
830 } // namespace speech
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698