Merge "Write unit tests for android.speech.tts."

This commit is contained in:
Narayan Kamath
2012-01-11 03:39:57 -08:00
committed by Android (Google) Code Review
7 changed files with 460 additions and 7 deletions

View File

@@ -486,6 +486,11 @@ public class TextToSpeech {
private final Object mStartLock = new Object();
private String mRequestedEngine;
// Whether to initialize this TTS object with the default engine,
// if the requested engine is not available. Valid only if mRequestedEngine
// is not null. Used only for testing, though potentially useful API wise
// too.
private final boolean mUseFallback;
private final Map<String, Uri> mEarcons;
private final Map<String, Uri> mUtterances;
private final Bundle mParams = new Bundle();
@@ -519,7 +524,7 @@ public class TextToSpeech {
* @param engine Package name of the TTS engine to use.
*/
public TextToSpeech(Context context, OnInitListener listener, String engine) {
this(context, listener, engine, null);
this(context, listener, engine, null, true);
}
/**
@@ -529,10 +534,11 @@ public class TextToSpeech {
* @hide
*/
public TextToSpeech(Context context, OnInitListener listener, String engine,
String packageName) {
String packageName, boolean useFallback) {
mContext = context;
mInitListener = listener;
mRequestedEngine = engine;
mUseFallback = useFallback;
mEarcons = new HashMap<String, Uri>();
mUtterances = new HashMap<String, Uri>();
@@ -567,10 +573,21 @@ public class TextToSpeech {
private int initTts() {
// Step 1: Try connecting to the engine that was requested.
if (mRequestedEngine != null && mEnginesHelper.isEngineInstalled(mRequestedEngine)) {
if (connectToEngine(mRequestedEngine)) {
mCurrentEngine = mRequestedEngine;
return SUCCESS;
if (mRequestedEngine != null) {
if (mEnginesHelper.isEngineInstalled(mRequestedEngine)) {
if (connectToEngine(mRequestedEngine)) {
mCurrentEngine = mRequestedEngine;
return SUCCESS;
} else if (!mUseFallback) {
mCurrentEngine = null;
dispatchOnInit(ERROR);
return ERROR;
}
} else if (!mUseFallback) {
Log.i(TAG, "Requested engine not installed: " + mRequestedEngine);
mCurrentEngine = null;
dispatchOnInit(ERROR);
return ERROR;
}
}

View File

@@ -1363,7 +1363,7 @@ public class WebView extends AbsoluteLayout
final String packageName = ctx.getPackageName();
if (packageName != null) {
mTextToSpeech = new TextToSpeech(getContext(), null, null,
packageName + ".**webview**");
packageName + ".**webview**", true);
addJavascriptInterface(mTextToSpeech, ALIAS_ACCESSIBILITY_JS_INTERFACE);
}
}

28
tests/TtsTests/Android.mk Normal file
View File

@@ -0,0 +1,28 @@
#
# Copyright (C) 2011 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := tests
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_STATIC_JAVA_LIBRARIES := littlemock
LOCAL_JAVA_LIBRARIES := android.test.runner
LOCAL_PACKAGE_NAME := TtsTests
include $(BUILD_PACKAGE)

View File

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2011 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.speech.tts">
<application>
<uses-library android:name="android.test.runner" />
<service android:name=".MockableTextToSpeechService"
android:label="Mockable Text-to-speech Service">
<intent-filter>
<action android:name="android.intent.action.TTS_SERVICE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</service>
<activity android:name=".MockableCheckVoiceData"
android:theme="@android:style/Theme.NoDisplay">
<intent-filter>
<action android:name="android.speech.tts.engine.CHECK_TTS_DATA" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
</application>
<instrumentation android:name="android.test.InstrumentationTestRunner"
android:targetPackage="com.android.speech.tts"
android:label="Tests for android.speech.tts" />
</manifest>

View File

@@ -0,0 +1,58 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.speech.tts;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.speech.tts.TextToSpeech;
import java.util.ArrayList;
import java.util.List;
public class MockableCheckVoiceData extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
MockableTextToSpeechService.IDelegate delegate =
MockableTextToSpeechService.getMocker();
ArrayList<String> availableLangs = delegate.getAvailableVoices();
ArrayList<String> unavailableLangs = delegate.getUnavailableVoices();
final Intent returnVal = new Intent();
// Returns early.
if (availableLangs == null) {
setResult(TextToSpeech.Engine.CHECK_VOICE_DATA_FAIL, returnVal);
finish();
return;
}
returnVal.putStringArrayListExtra(TextToSpeech.Engine.EXTRA_AVAILABLE_VOICES,
availableLangs);
if (unavailableLangs != null && unavailableLangs.size() > 0) {
returnVal.putStringArrayListExtra(TextToSpeech.Engine.EXTRA_UNAVAILABLE_VOICES,
unavailableLangs);
}
setResult(TextToSpeech.Engine.CHECK_VOICE_DATA_PASS, returnVal);
finish();
}
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.speech.tts;
import android.speech.tts.SynthesisCallback;
import android.speech.tts.SynthesisRequest;
import android.speech.tts.TextToSpeechService;
import java.util.ArrayList;
public class MockableTextToSpeechService extends TextToSpeechService {
private static IDelegate sDelegate;
public static void setMocker(IDelegate delegate) {
sDelegate = delegate;
}
static IDelegate getMocker() {
return sDelegate;
}
@Override
protected int onIsLanguageAvailable(String lang, String country, String variant) {
return sDelegate.onIsLanguageAvailable(lang, country, variant);
}
@Override
protected String[] onGetLanguage() {
return sDelegate.onGetLanguage();
}
@Override
protected int onLoadLanguage(String lang, String country, String variant) {
return sDelegate.onLoadLanguage(lang, country, variant);
}
@Override
protected void onStop() {
sDelegate.onStop();
}
@Override
protected void onSynthesizeText(SynthesisRequest request, SynthesisCallback callback) {
sDelegate.onSynthesizeText(request, callback);
}
public static interface IDelegate {
int onIsLanguageAvailable(String lang, String country, String variant);
String[] onGetLanguage();
int onLoadLanguage(String lang, String country, String variant);
void onStop();
void onSynthesizeText(SynthesisRequest request, SynthesisCallback callback);
ArrayList<String> getAvailableVoices();
ArrayList<String> getUnavailableVoices();
}
}

View File

@@ -0,0 +1,229 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.speech.tts;
import android.speech.tts.SynthesisCallback;
import android.speech.tts.SynthesisRequest;
import android.speech.tts.TextToSpeech;
import android.test.InstrumentationTestCase;
import com.android.speech.tts.MockableTextToSpeechService.IDelegate;
import com.google.testing.littlemock.ArgumentCaptor;
import com.google.testing.littlemock.Behaviour;
import com.google.testing.littlemock.LittleMock;
import junit.framework.Assert;
import java.util.Locale;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class TextToSpeechTests extends InstrumentationTestCase {
private static final String MOCK_ENGINE = "com.android.speech.tts";
private static final String MOCK_PACKAGE = "com.android.speech.tts.__testpackage__";
private TextToSpeech mTts;
@Override
public void setUp() throws Exception {
IDelegate passThrough = LittleMock.mock(IDelegate.class);
MockableTextToSpeechService.setMocker(passThrough);
blockingInitAndVerify(MOCK_ENGINE, TextToSpeech.SUCCESS);
assertEquals(MOCK_ENGINE, mTts.getCurrentEngine());
}
@Override
public void tearDown() {
if (mTts != null) {
mTts.shutdown();
}
}
public void testEngineInitialized() throws Exception {
// Fail on an engine that doesn't exist.
blockingInitAndVerify("__DOES_NOT_EXIST__", TextToSpeech.ERROR);
// Also, the "current engine" must be null
assertNull(mTts.getCurrentEngine());
}
public void testSetLanguage_delegation() {
IDelegate delegate = LittleMock.mock(IDelegate.class);
MockableTextToSpeechService.setMocker(delegate);
// Test 1 :Tests that calls to onLoadLanguage( ) are delegated through to the
// service without any caching or intermediate steps.
mTts.setLanguage(new Locale("eng", "USA", "variant"));
LittleMock.verify(delegate, LittleMock.times(1)).onLoadLanguage(
"eng", "USA", "variant");
}
public void testSetLanguage_availableLanguage() throws Exception {
IDelegate delegate = LittleMock.mock(IDelegate.class);
MockableTextToSpeechService.setMocker(delegate);
// ---------------------------------------------------------
// Test 2 : Tests that when the language is successfully set
// like above (returns LANG_COUNTRY_AVAILABLE). That the
// request language changes from that point on.
LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).when(delegate).onLoadLanguage(
"eng", "USA", "variant");
mTts.setLanguage(new Locale("eng", "USA", "variant"));
blockingCallSpeak("foo bar", delegate);
ArgumentCaptor<SynthesisRequest> req = LittleMock.createCaptor();
LittleMock.verify(delegate, LittleMock.times(1)).onSynthesizeText(req.capture(),
LittleMock.<SynthesisCallback>anyObject());
assertEquals("eng", req.getValue().getLanguage());
assertEquals("USA", req.getValue().getCountry());
assertEquals("", req.getValue().getVariant());
}
public void testSetLanguage_unavailableLanguage() throws Exception {
IDelegate delegate = LittleMock.mock(IDelegate.class);
MockableTextToSpeechService.setMocker(delegate);
// ---------------------------------------------------------
// TEST 3 : Tests that the language that is set does not change when the
// engine reports it could not load the specified language.
LittleMock.doReturn(TextToSpeech.LANG_NOT_SUPPORTED).when(
delegate).onLoadLanguage("fra", "FRA", "");
mTts.setLanguage(Locale.FRANCE);
blockingCallSpeak("le fou barre", delegate);
ArgumentCaptor<SynthesisRequest> req2 = LittleMock.createCaptor();
LittleMock.verify(delegate, LittleMock.times(1)).onSynthesizeText(req2.capture(),
LittleMock.<SynthesisCallback>anyObject());
// The params are basically unchanged.
assertEquals("eng", req2.getValue().getLanguage());
assertEquals("USA", req2.getValue().getCountry());
assertEquals("", req2.getValue().getVariant());
}
public void testGetLanguage_invalidReturnValues() {
IDelegate delegate = LittleMock.mock(IDelegate.class);
MockableTextToSpeechService.setMocker(delegate);
// Test1: Simple end to end test. Ensure that bad return values
// are dealt with appropriately.
LittleMock.doReturn(null).when(delegate).onGetLanguage();
Locale returnVal = mTts.getLanguage();
assertNull(returnVal);
// Bad value 2. An array of length < 3.
LittleMock.doReturn(new String[] {"eng", "usa"}).when(delegate).onGetLanguage();
returnVal = mTts.getLanguage();
assertNull(returnVal);
}
public void testGetLanguage_validReturnValues() {
IDelegate delegate = LittleMock.mock(IDelegate.class);
MockableTextToSpeechService.setMocker(delegate);
// A correct value.
LittleMock.doReturn(new String[] {"eng", "usa", ""}).when(delegate).onGetLanguage();
Locale returnVal = mTts.getLanguage();
// Note: This is not the same as Locale.US . Well tough luck for
// being the only component of the entire framework that standardized
// three letter country and language codes.
assertEquals(new Locale("eng", "USA", ""), returnVal);
}
public void testIsLanguageAvailable() {
IDelegate delegate = LittleMock.mock(IDelegate.class);
MockableTextToSpeechService.setMocker(delegate);
// Test1: Simple end to end test.
LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).when(
delegate).onIsLanguageAvailable("eng", "USA", "");
assertEquals(TextToSpeech.LANG_COUNTRY_AVAILABLE, mTts.isLanguageAvailable(Locale.US));
LittleMock.verify(delegate, LittleMock.times(1)).onIsLanguageAvailable(
"eng", "USA", "");
}
private void blockingCallSpeak(String speech, IDelegate mock) throws
InterruptedException {
final CountDownLatch latch = new CountDownLatch(1);
doCountDown(latch).when(mock).onSynthesizeText(LittleMock.<SynthesisRequest>anyObject(),
LittleMock.<SynthesisCallback>anyObject());
mTts.speak(speech, TextToSpeech.QUEUE_ADD, null);
awaitCountDown(latch, 5, TimeUnit.SECONDS);
}
private void blockingInitAndVerify(final String engine, int errorCode) throws
InterruptedException {
TextToSpeech.OnInitListener listener = LittleMock.mock(
TextToSpeech.OnInitListener.class);
final CountDownLatch latch = new CountDownLatch(1);
doCountDown(latch).when(listener).onInit(errorCode);
mTts = new TextToSpeech(getInstrumentation().getTargetContext(),
listener, engine, MOCK_PACKAGE, false /* use fallback package */);
awaitCountDown(latch, 5, TimeUnit.SECONDS);
}
public interface CountDownBehaviour extends Behaviour {
/** Used to mock methods that return a result. */
Behaviour andReturn(Object result);
}
public static CountDownBehaviour doCountDown(final CountDownLatch latch) {
return new CountDownBehaviour() {
@Override
public <T> T when(T mock) {
return LittleMock.doAnswer(new Callable<Void>() {
@Override
public Void call() throws Exception {
latch.countDown();
return null;
}
}).when(mock);
}
@Override
public Behaviour andReturn(final Object result) {
return new Behaviour() {
@Override
public <T> T when(T mock) {
return LittleMock.doAnswer(new Callable<Object>() {
@Override
public Object call() throws Exception {
latch.countDown();
return result;
}
}).when(mock);
}
};
}
};
}
public static void awaitCountDown(CountDownLatch latch, long timeout, TimeUnit unit)
throws InterruptedException {
Assert.assertTrue("Waited too long for method call", latch.await(timeout, unit));
}
}