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 // For the default voice selection 47 LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).when(passThrough) 48 .onIsLanguageAvailable( 49 LittleMock.anyString(), LittleMock.anyString(), LittleMock.anyString()); 50 LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).when(passThrough) 51 .onLoadLanguage( 52 LittleMock.anyString(), LittleMock.anyString(), LittleMock.anyString()); 53 54 blockingInitAndVerify(MOCK_ENGINE, TextToSpeech.SUCCESS); 55 assertEquals(MOCK_ENGINE, mTts.getCurrentEngine()); 56 } 57 58 @Override tearDown()59 public void tearDown() { 60 if (mTts != null) { 61 mTts.shutdown(); 62 } 63 } 64 testEngineInitialized()65 public void testEngineInitialized() throws Exception { 66 // Fail on an engine that doesn't exist. 67 blockingInitAndVerify("__DOES_NOT_EXIST__", TextToSpeech.ERROR); 68 69 // Also, the "current engine" must be null 70 assertNull(mTts.getCurrentEngine()); 71 } 72 testSetLanguage_delegation()73 public void testSetLanguage_delegation() { 74 IDelegate delegate = LittleMock.mock(IDelegate.class); 75 MockableTextToSpeechService.setMocker(delegate); 76 77 LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE).when(delegate).onIsLanguageAvailable( 78 "eng", "USA", "variant"); 79 LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE).when(delegate).onLoadLanguage( 80 "eng", "USA", "variant"); 81 82 // Test 1 :Tests that calls to onLoadLanguage( ) are delegated through to the 83 // service without any caching or intermediate steps. 84 assertEquals(TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE, mTts.setLanguage(new Locale("eng", "USA", "variant"))); 85 LittleMock.verify(delegate, LittleMock.anyTimes()).onIsLanguageAvailable( 86 "eng", "USA", "variant"); 87 LittleMock.verify(delegate, LittleMock.anyTimes()).onLoadLanguage( 88 "eng", "USA", "variant"); 89 } 90 testSetLanguage_availableLanguage()91 public void testSetLanguage_availableLanguage() throws Exception { 92 IDelegate delegate = LittleMock.mock(IDelegate.class); 93 MockableTextToSpeechService.setMocker(delegate); 94 95 // --------------------------------------------------------- 96 // Test 2 : Tests that when the language is successfully set 97 // like above (returns LANG_COUNTRY_AVAILABLE). That the 98 // request language changes from that point on. 99 LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).when(delegate).onIsLanguageAvailable( 100 "eng", "USA", "variant"); 101 LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).when(delegate).onIsLanguageAvailable( 102 "eng", "USA", ""); 103 LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).when(delegate).onLoadLanguage( 104 "eng", "USA", ""); 105 mTts.setLanguage(new Locale("eng", "USA", "variant")); 106 blockingCallSpeak("foo bar", delegate); 107 ArgumentCaptor<SynthesisRequest> req = LittleMock.createCaptor(); 108 LittleMock.verify(delegate, LittleMock.times(1)).onSynthesizeText(req.capture(), 109 LittleMock.<SynthesisCallback>anyObject()); 110 111 assertEquals("eng", req.getValue().getLanguage()); 112 assertEquals("USA", req.getValue().getCountry()); 113 assertEquals("", req.getValue().getVariant()); 114 assertEquals("en-US", req.getValue().getVoiceName()); 115 } 116 testSetLanguage_unavailableLanguage()117 public void testSetLanguage_unavailableLanguage() throws Exception { 118 IDelegate delegate = LittleMock.mock(IDelegate.class); 119 MockableTextToSpeechService.setMocker(delegate); 120 121 // --------------------------------------------------------- 122 // TEST 3 : Tests that the language that is set does not change when the 123 // engine reports it could not load the specified language. 124 LittleMock.doReturn(TextToSpeech.LANG_NOT_SUPPORTED).when( 125 delegate).onIsLanguageAvailable("fra", "FRA", ""); 126 LittleMock.doReturn(TextToSpeech.LANG_NOT_SUPPORTED).when( 127 delegate).onLoadLanguage("fra", "FRA", ""); 128 mTts.setLanguage(Locale.FRANCE); 129 blockingCallSpeak("le fou barre", delegate); 130 ArgumentCaptor<SynthesisRequest> req2 = LittleMock.createCaptor(); 131 LittleMock.verify(delegate, LittleMock.times(1)).onSynthesizeText(req2.capture(), 132 LittleMock.<SynthesisCallback>anyObject()); 133 134 // The params are basically unchanged. 135 assertEquals("eng", req2.getValue().getLanguage()); 136 assertEquals("USA", req2.getValue().getCountry()); 137 assertEquals("", req2.getValue().getVariant()); 138 assertEquals("en-US", req2.getValue().getVoiceName()); 139 } 140 testIsLanguageAvailable()141 public void testIsLanguageAvailable() { 142 IDelegate delegate = LittleMock.mock(IDelegate.class); 143 MockableTextToSpeechService.setMocker(delegate); 144 145 // Test1: Simple end to end test. 146 LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).when( 147 delegate).onIsLanguageAvailable("eng", "USA", ""); 148 149 assertEquals(TextToSpeech.LANG_COUNTRY_AVAILABLE, mTts.isLanguageAvailable(Locale.US)); 150 LittleMock.verify(delegate, LittleMock.times(1)).onIsLanguageAvailable( 151 "eng", "USA", ""); 152 } 153 testDefaultLanguage_setsVoiceName()154 public void testDefaultLanguage_setsVoiceName() throws Exception { 155 IDelegate delegate = LittleMock.mock(IDelegate.class); 156 MockableTextToSpeechService.setMocker(delegate); 157 Locale defaultLocale = Locale.getDefault(); 158 159 // --------------------------------------------------------- 160 // Test that default language also sets the default voice 161 // name 162 LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE). 163 when(delegate).onIsLanguageAvailable( 164 defaultLocale.getISO3Language(), 165 defaultLocale.getISO3Country().toUpperCase(), 166 defaultLocale.getVariant()); 167 LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE). 168 when(delegate).onLoadLanguage( 169 defaultLocale.getISO3Language(), 170 defaultLocale.getISO3Country(), 171 defaultLocale.getVariant()); 172 173 blockingCallSpeak("foo bar", delegate); 174 ArgumentCaptor<SynthesisRequest> req = LittleMock.createCaptor(); 175 LittleMock.verify(delegate, LittleMock.times(1)).onSynthesizeText(req.capture(), 176 LittleMock.<SynthesisCallback>anyObject()); 177 178 assertEquals(defaultLocale.getISO3Language(), req.getValue().getLanguage()); 179 assertEquals(defaultLocale.getISO3Country(), req.getValue().getCountry()); 180 assertEquals("", req.getValue().getVariant()); 181 assertEquals(defaultLocale.toLanguageTag(), req.getValue().getVoiceName()); 182 } 183 184 blockingCallSpeak(String speech, IDelegate mock)185 private void blockingCallSpeak(String speech, IDelegate mock) throws 186 InterruptedException { 187 final CountDownLatch latch = new CountDownLatch(1); 188 doCountDown(latch).when(mock).onSynthesizeText(LittleMock.<SynthesisRequest>anyObject(), 189 LittleMock.<SynthesisCallback>anyObject()); 190 mTts.speak(speech, TextToSpeech.QUEUE_ADD, null); 191 192 awaitCountDown(latch, 5, TimeUnit.SECONDS); 193 } 194 blockingInitAndVerify(final String engine, int errorCode)195 private void blockingInitAndVerify(final String engine, int errorCode) throws 196 InterruptedException { 197 TextToSpeech.OnInitListener listener = LittleMock.mock( 198 TextToSpeech.OnInitListener.class); 199 200 final CountDownLatch latch = new CountDownLatch(1); 201 doCountDown(latch).when(listener).onInit(errorCode); 202 203 mTts = new TextToSpeech(getInstrumentation().getTargetContext(), 204 listener, engine, MOCK_PACKAGE, false /* use fallback package */); 205 206 awaitCountDown(latch, 5, TimeUnit.SECONDS); 207 } 208 209 public interface CountDownBehaviour extends Behaviour { 210 /** Used to mock methods that return a result. */ andReturn(Object result)211 Behaviour andReturn(Object result); 212 } 213 doCountDown(final CountDownLatch latch)214 public static CountDownBehaviour doCountDown(final CountDownLatch latch) { 215 return new CountDownBehaviour() { 216 @Override 217 public <T> T when(T mock) { 218 return LittleMock.doAnswer(new Callable<Void>() { 219 @Override 220 public Void call() throws Exception { 221 latch.countDown(); 222 return null; 223 } 224 }).when(mock); 225 } 226 227 @Override 228 public Behaviour andReturn(final Object result) { 229 return new Behaviour() { 230 @Override 231 public <T> T when(T mock) { 232 return LittleMock.doAnswer(new Callable<Object>() { 233 @Override 234 public Object call() throws Exception { 235 latch.countDown(); 236 return result; 237 } 238 }).when(mock); 239 } 240 }; 241 } 242 }; 243 } 244 245 public static void awaitCountDown(CountDownLatch latch, long timeout, TimeUnit unit) 246 throws InterruptedException { 247 Assert.assertTrue("Waited too long for method call", latch.await(timeout, unit)); 248 } 249 } 250