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

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

Powered by Google App Engine
This is Rietveld 408576698