1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.speech.tts; 18 19 import android.speech.tts.SynthesisCallback; 20 import android.speech.tts.SynthesisRequest; 21 import android.speech.tts.TextToSpeech; 22 import android.test.InstrumentationTestCase; 23 24 import com.android.speech.tts.MockableTextToSpeechService.IDelegate; 25 import com.google.testing.littlemock.ArgumentCaptor; 26 import com.google.testing.littlemock.Behaviour; 27 import com.google.testing.littlemock.LittleMock; 28 import junit.framework.Assert; 29 30 import java.util.Locale; 31 import java.util.concurrent.Callable; 32 import java.util.concurrent.CountDownLatch; 33 import java.util.concurrent.TimeUnit; 34 35 public class TextToSpeechTests extends InstrumentationTestCase { 36 private static final String MOCK_ENGINE = "com.android.speech.tts"; 37 private static final String MOCK_PACKAGE = "com.android.speech.tts.__testpackage__"; 38 39 private TextToSpeech mTts; 40 41 @Override setUp()42 public void setUp() throws Exception { 43 IDelegate passThrough = LittleMock.mock(IDelegate.class); 44 MockableTextToSpeechService.setMocker(passThrough); 45 46 blockingInitAndVerify(MOCK_ENGINE, TextToSpeech.SUCCESS); 47 assertEquals(MOCK_ENGINE, mTts.getCurrentEngine()); 48 } 49 50 51 @Override tearDown()52 public void tearDown() { 53 if (mTts != null) { 54 mTts.shutdown(); 55 } 56 } 57 testEngineInitialized()58 public void testEngineInitialized() throws Exception { 59 // Fail on an engine that doesn't exist. 60 blockingInitAndVerify("__DOES_NOT_EXIST__", TextToSpeech.ERROR); 61 62 // Also, the "current engine" must be null 63 assertNull(mTts.getCurrentEngine()); 64 } 65 testSetLanguage_delegation()66 public void testSetLanguage_delegation() { 67 IDelegate delegate = LittleMock.mock(IDelegate.class); 68 MockableTextToSpeechService.setMocker(delegate); 69 70 LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE).when(delegate).onIsLanguageAvailable( 71 "eng", "USA", "variant"); 72 LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE).when(delegate).onLoadLanguage( 73 "eng", "USA", "variant"); 74 75 // Test 1 :Tests that calls to onLoadLanguage( ) are delegated through to the 76 // service without any caching or intermediate steps. 77 assertEquals(TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE, mTts.setLanguage(new Locale("eng", "USA", "variant"))); 78 LittleMock.verify(delegate, LittleMock.anyTimes()).onIsLanguageAvailable( 79 "eng", "USA", "variant"); 80 LittleMock.verify(delegate, LittleMock.times(1)).onLoadLanguage( 81 "eng", "USA", "variant"); 82 } 83 testSetLanguage_availableLanguage()84 public void testSetLanguage_availableLanguage() throws Exception { 85 IDelegate delegate = LittleMock.mock(IDelegate.class); 86 MockableTextToSpeechService.setMocker(delegate); 87 88 // --------------------------------------------------------- 89 // Test 2 : Tests that when the language is successfully set 90 // like above (returns LANG_COUNTRY_AVAILABLE). That the 91 // request language changes from that point on. 92 LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).when(delegate).onIsLanguageAvailable( 93 "eng", "USA", "variant"); 94 LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).when(delegate).onIsLanguageAvailable( 95 "eng", "USA", ""); 96 LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).when(delegate).onLoadLanguage( 97 "eng", "USA", ""); 98 mTts.setLanguage(new Locale("eng", "USA", "variant")); 99 blockingCallSpeak("foo bar", delegate); 100 ArgumentCaptor<SynthesisRequest> req = LittleMock.createCaptor(); 101 LittleMock.verify(delegate, LittleMock.times(1)).onSynthesizeText(req.capture(), 102 LittleMock.<SynthesisCallback>anyObject()); 103 104 assertEquals("eng", req.getValue().getLanguage()); 105 assertEquals("USA", req.getValue().getCountry()); 106 assertEquals("", req.getValue().getVariant()); 107 assertEquals("en-US", req.getValue().getVoiceName()); 108 } 109 testSetLanguage_unavailableLanguage()110 public void testSetLanguage_unavailableLanguage() throws Exception { 111 IDelegate delegate = LittleMock.mock(IDelegate.class); 112 MockableTextToSpeechService.setMocker(delegate); 113 114 // --------------------------------------------------------- 115 // TEST 3 : Tests that the language that is set does not change when the 116 // engine reports it could not load the specified language. 117 LittleMock.doReturn(TextToSpeech.LANG_NOT_SUPPORTED).when( 118 delegate).onIsLanguageAvailable("fra", "FRA", ""); 119 LittleMock.doReturn(TextToSpeech.LANG_NOT_SUPPORTED).when( 120 delegate).onLoadLanguage("fra", "FRA", ""); 121 mTts.setLanguage(Locale.FRANCE); 122 blockingCallSpeak("le fou barre", delegate); 123 ArgumentCaptor<SynthesisRequest> req2 = LittleMock.createCaptor(); 124 LittleMock.verify(delegate, LittleMock.times(1)).onSynthesizeText(req2.capture(), 125 LittleMock.<SynthesisCallback>anyObject()); 126 127 // The params are basically unchanged. 128 assertEquals("eng", req2.getValue().getLanguage()); 129 assertEquals("USA", req2.getValue().getCountry()); 130 assertEquals("", req2.getValue().getVariant()); 131 assertEquals("en-US", req2.getValue().getVoiceName()); 132 } 133 testIsLanguageAvailable()134 public void testIsLanguageAvailable() { 135 IDelegate delegate = LittleMock.mock(IDelegate.class); 136 MockableTextToSpeechService.setMocker(delegate); 137 138 // Test1: Simple end to end test. 139 LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).when( 140 delegate).onIsLanguageAvailable("eng", "USA", ""); 141 142 assertEquals(TextToSpeech.LANG_COUNTRY_AVAILABLE, mTts.isLanguageAvailable(Locale.US)); 143 LittleMock.verify(delegate, LittleMock.times(1)).onIsLanguageAvailable( 144 "eng", "USA", ""); 145 } 146 testDefaultLanguage_setsVoiceName()147 public void testDefaultLanguage_setsVoiceName() throws Exception { 148 IDelegate delegate = LittleMock.mock(IDelegate.class); 149 MockableTextToSpeechService.setMocker(delegate); 150 151 // --------------------------------------------------------- 152 // Test that default language also sets the default voice 153 // name 154 LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).when(delegate).onIsLanguageAvailable( 155 LittleMock.anyString(), LittleMock.anyString(), LittleMock.anyString()); 156 LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).when(delegate).onLoadLanguage( 157 LittleMock.anyString(), LittleMock.anyString(), LittleMock.anyString()); 158 blockingCallSpeak("foo bar", delegate); 159 ArgumentCaptor<SynthesisRequest> req = LittleMock.createCaptor(); 160 LittleMock.verify(delegate, LittleMock.times(1)).onSynthesizeText(req.capture(), 161 LittleMock.<SynthesisCallback>anyObject()); 162 163 Locale defaultLocale = Locale.getDefault(); 164 assertEquals(defaultLocale.getISO3Language(), req.getValue().getLanguage()); 165 assertEquals(defaultLocale.getISO3Country(), req.getValue().getCountry()); 166 assertEquals("", req.getValue().getVariant()); 167 assertEquals(defaultLocale.toLanguageTag(), req.getValue().getVoiceName()); 168 } 169 blockingCallSpeak(String speech, IDelegate mock)170 private void blockingCallSpeak(String speech, IDelegate mock) throws 171 InterruptedException { 172 final CountDownLatch latch = new CountDownLatch(1); 173 doCountDown(latch).when(mock).onSynthesizeText(LittleMock.<SynthesisRequest>anyObject(), 174 LittleMock.<SynthesisCallback>anyObject()); 175 mTts.speak(speech, TextToSpeech.QUEUE_ADD, null); 176 177 awaitCountDown(latch, 5, TimeUnit.SECONDS); 178 } 179 blockingInitAndVerify(final String engine, int errorCode)180 private void blockingInitAndVerify(final String engine, int errorCode) throws 181 InterruptedException { 182 TextToSpeech.OnInitListener listener = LittleMock.mock( 183 TextToSpeech.OnInitListener.class); 184 185 final CountDownLatch latch = new CountDownLatch(1); 186 doCountDown(latch).when(listener).onInit(errorCode); 187 188 mTts = new TextToSpeech(getInstrumentation().getTargetContext(), 189 listener, engine, MOCK_PACKAGE, false /* use fallback package */); 190 191 awaitCountDown(latch, 5, TimeUnit.SECONDS); 192 } 193 194 public interface CountDownBehaviour extends Behaviour { 195 /** Used to mock methods that return a result. */ andReturn(Object result)196 Behaviour andReturn(Object result); 197 } 198 doCountDown(final CountDownLatch latch)199 public static CountDownBehaviour doCountDown(final CountDownLatch latch) { 200 return new CountDownBehaviour() { 201 @Override 202 public <T> T when(T mock) { 203 return LittleMock.doAnswer(new Callable<Void>() { 204 @Override 205 public Void call() throws Exception { 206 latch.countDown(); 207 return null; 208 } 209 }).when(mock); 210 } 211 212 @Override 213 public Behaviour andReturn(final Object result) { 214 return new Behaviour() { 215 @Override 216 public <T> T when(T mock) { 217 return LittleMock.doAnswer(new Callable<Object>() { 218 @Override 219 public Object call() throws Exception { 220 latch.countDown(); 221 return result; 222 } 223 }).when(mock); 224 } 225 }; 226 } 227 }; 228 } 229 230 public static void awaitCountDown(CountDownLatch latch, long timeout, TimeUnit unit) 231 throws InterruptedException { 232 Assert.assertTrue("Waited too long for method call", latch.await(timeout, unit)); 233 } 234 } 235