Merge "Write unit tests for android.speech.tts."
This commit is contained in:
committed by
Android (Google) Code Review
commit
81754949e0
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
28
tests/TtsTests/Android.mk
Normal 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)
|
||||
43
tests/TtsTests/AndroidManifest.xml
Normal file
43
tests/TtsTests/AndroidManifest.xml
Normal 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>
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
229
tests/TtsTests/src/com/android/speech/tts/TextToSpeechTests.java
Normal file
229
tests/TtsTests/src/com/android/speech/tts/TextToSpeechTests.java
Normal 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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user