Tests and some new constructors to help in testing.

Added tests for the dictionary lookup and correction logic on the primary dictionary.
This exercises part of the Suggest class and the native dictionary lookup code.
main
Amith Yamasani 2010-04-28 18:12:58 -07:00
parent f53d0da540
commit e4e1130d00
9 changed files with 581 additions and 8 deletions

View File

@ -1,3 +1,8 @@
-keep class com.android.inputmethod.latin.BinaryDictionary { -keep class com.android.inputmethod.latin.BinaryDictionary {
int mDictLength; int mDictLength;
<init>(...);
}
-keep class com.android.inputmethod.latin.Suggest {
<init>(...);
} }

View File

@ -68,6 +68,26 @@ public class BinaryDictionary extends Dictionary {
} }
} }
/**
* Create a dictionary from a byte buffer. This is used for testing.
* @param context application context for reading resources
* @param resId the resource containing the raw binary dictionary
*/
public BinaryDictionary(Context context, ByteBuffer byteBuffer) {
if (byteBuffer != null) {
if (byteBuffer.isDirect()) {
mNativeDictDirectBuffer = byteBuffer;
} else {
mNativeDictDirectBuffer = ByteBuffer.allocateDirect(byteBuffer.capacity());
byteBuffer.rewind();
mNativeDictDirectBuffer.put(byteBuffer);
}
mDictLength = byteBuffer.capacity();
mNativeDict = openNative(mNativeDictDirectBuffer,
TYPED_LETTER_MULTIPLIER, FULL_WORD_FREQ_MULTIPLIER);
}
}
private native int openNative(ByteBuffer bb, int typedLetterMultiplier, int fullWordMultiplier); private native int openNative(ByteBuffer bb, int typedLetterMultiplier, int fullWordMultiplier);
private native void closeNative(int dict); private native void closeNative(int dict);
private native boolean isValidWordNative(int nativeData, char[] word, int wordLength); private native boolean isValidWordNative(int nativeData, char[] word, int wordLength);

View File

@ -16,18 +16,17 @@
package com.android.inputmethod.latin; package com.android.inputmethod.latin;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import android.content.Context; import android.content.Context;
import android.text.AutoText; import android.text.AutoText;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.android.inputmethod.latin.WordComposer;
/** /**
* This class loads a dictionary and provides a list of suggestions for a given sequence of * This class loads a dictionary and provides a list of suggestions for a given sequence of
* characters. This includes corrections and completions. * characters. This includes corrections and completions.
@ -69,9 +68,17 @@ public class Suggest implements Dictionary.WordCallback {
private int mCorrectionMode = CORRECTION_BASIC; private int mCorrectionMode = CORRECTION_BASIC;
public Suggest(Context context, int dictionaryResId) { public Suggest(Context context, int dictionaryResId) {
mMainDict = new BinaryDictionary(context, dictionaryResId); mMainDict = new BinaryDictionary(context, dictionaryResId);
initPool();
}
public Suggest(Context context, ByteBuffer byteBuffer) {
mMainDict = new BinaryDictionary(context, byteBuffer);
initPool();
}
private void initPool() {
for (int i = 0; i < mPrefMaxSuggestions; i++) { for (int i = 0; i < mPrefMaxSuggestions; i++) {
StringBuilder sb = new StringBuilder(32); StringBuilder sb = new StringBuilder(32);
mStringPool.add(sb); mStringPool.add(sb);

View File

@ -44,7 +44,7 @@ public class WordComposer {
*/ */
private boolean mIsCapitalized; private boolean mIsCapitalized;
WordComposer() { public WordComposer() {
mCodes = new ArrayList<int[]>(12); mCodes = new ArrayList<int[]>(12);
mTypedWord = new StringBuilder(20); mTypedWord = new StringBuilder(20);
} }

17
tests/Android.mk Normal file
View File

@ -0,0 +1,17 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
# We only want this apk build for tests.
LOCAL_MODULE_TAGS := tests
LOCAL_CERTIFICATE := shared
LOCAL_JAVA_LIBRARIES := android.test.runner
# Include all test java files.
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := LatinIMETests
LOCAL_INSTRUMENTATION_FOR := LatinIME
include $(BUILD_PACKAGE)

33
tests/AndroidManifest.xml Normal file
View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 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.inputmethod.latin.tests">
<uses-permission android:name="android.permission.READ_CONTACTS" />
<application>
<uses-library android:name="android.test.runner" />
<!-- meta-data android:name="com.android.contacts.iconset" android:resource="@xml/iconset" /-->
<uses-permission android:name="android.permission.READ_CONTACTS" />
</application>
<instrumentation android:name="android.test.InstrumentationTestRunner"
android:targetPackage="com.android.inputmethod.latin"
android:label="LatinIME tests">
</instrumentation>
</manifest>

243
tests/data/wordlist.xml Normal file
View File

@ -0,0 +1,243 @@
<wordlist>
<w f="255">the</w>
<w f="246">and</w>
<w f="245">of</w>
<w f="242">to</w>
<w f="231">in</w>
<w f="230">that</w>
<w f="229">for</w>
<w f="224">with</w>
<w f="224">on</w>
<w f="224">it</w>
<w f="223">this</w>
<w f="222">you</w>
<w f="219">is</w>
<w f="219">was</w>
<w f="219">by</w>
<w f="219">or</w>
<w f="218">from</w>
<w f="217">but</w>
<w f="216">be</w>
<w f="216">Sunday</w>
<w f="215">are</w>
<w f="215">he</w>
<w f="214">so</w>
<w f="214">not</w>
<w f="213">have</w>
<w f="213">as</w>
<w f="211">all</w>
<w f="211">his</w>
<w f="210">my</w>
<w f="210">if</w>
<w f="210">which</w>
<w f="210">they</w>
<w f="209">at</w>
<w f="207">it's</w>
<w f="207">an</w>
<w f="207">your</w>
<w f="206">will</w>
<w f="206">about</w>
<w f="206">I'm</w>
<w f="205">there</w>
<w f="205">had</w>
<w f="205">has</w>
<w f="204">when</w>
<w f="203">no</w>
<w f="203">were</w>
<w f="203">what</w>
<w f="203">more</w>
<w f="203">out</w>
<w f="203">just</w>
<w f="202">their</w>
<w f="202">up</w>
<w f="202">would</w>
<w f="202">here</w>
<w f="202">can</w>
<w f="201">who</w>
<w f="200">her</w>
<w f="200">me</w>
<w f="200">now</w>
<w f="200">our</w>
<w f="200">do</w>
<w f="200">some</w>
<w f="199">been</w>
<w f="199">two</w>
<w f="199">like</w>
<w f="199">them</w>
<w f="199">new</w>
<w f="198">time</w>
<w f="198">we</w>
<w f="198">she</w>
<w f="197">one</w>
<w f="197">over</w>
<w f="197">may</w>
<w f="197">any</w>
<w f="197">him</w>
<w f="197">calling</w>
<w f="196">other</w>
<w f="196">how</w>
<w f="196">see</w>
<w f="195">because</w>
<w f="195">then</w>
<w f="195">right</w>
<w f="195">into</w>
<w f="195">well</w>
<w f="195">very</w>
<w f="195">said</w>
<w f="195">people</w>
<w f="194">these</w>
<w f="194">than</w>
<w f="193">only</w>
<w f="193">back</w>
<w f="193">first</w>
<w f="193">dot</w>
<w f="193">after</w>
<w f="193">where</w>
<w f="192">please</w>
<w f="192">could</w>
<w f="192">its</w>
<w f="192">before</w>
<w f="192">us</w>
<w f="192">again</w>
<w f="192">home</w>
<w f="191">also</w>
<w f="191">that's</w>
<w f="191">think</w>
<w f="191">three</w>
<w f="191">good</w>
<w f="191">get</w>
<w f="190">know</w>
<w f="190">thank</w>
<w f="190">should</w>
<w f="190">going</w>
<w f="190">down</w>
<w f="189">last</w>
<w f="189">today</w>
<w f="189">those</w>
<w f="189">go</w>
<w f="189">through</w>
<w f="189">such</w>
<w f="189">don't</w>
<w f="189">did</w>
<w f="188">most</w>
<w f="188">day</w>
<w f="188">man</w>
<w f="188">number</w>
<w f="188">work</w>
<w f="187">too</w>
<w f="187">show</w>
<w f="187">made</w>
<w f="187">even</w>
<w f="187">being</w>
<w f="187">make</w>
<w f="187">give</w>
<w f="186">off</w>
<w f="186">com</w>
<w f="186">much</w>
<w f="186">great</w>
<w f="186">take</w>
<w f="186">call</w>
<w f="186">way</w>
<w f="186">four</w>
<w f="186">say</w>
<w f="185">information</w>
<w f="185">under</w>
<w f="185">page</w>
<w f="185">many</w>
<w f="185">little</w>
<w f="185">thanks</w>
<w f="185">okay</w>
<w f="185">five</w>
<w f="185">we're</w>
<w f="185">between</w>
<w f="184">use</w>
<w f="184">come</w>
<w f="184">years</w>
<w f="184">office</w>
<w f="184">house</w>
<w f="184">search</w>
<w f="184">free</w>
<w f="183">next</w>
<w f="183">without</w>
<w f="183">still</w>
<w f="183">around</w>
<w f="183">I've</w>
<w f="183">business</w>
<w f="183">part</w>
<w f="183">every</w>
<w f="183">bye</w>
<w f="183">upon</w>
<w f="183">you're</w>
<w f="183">state</w>
<w f="183">life</w>
<w f="183">year</w>
<w f="182">thing</w>
<w f="182">since</w>
<w f="182">things</w>
<w f="182">something</w>
<w f="182">long</w>
<w f="182">got</w>
<w f="182">while</w>
<w f="182">I'll</w>
<w f="182">help</w>
<w f="182">service</w>
<w f="182">really</w>
<w f="182">must</w>
<w f="182">does</w>
<w f="182">name</w>
<w f="181">both</w>
<w f="181">six</w>
<w f="181">want</w>
<w f="181">same</w>
<w f="181">each</w>
<w f="181">yet</w>
<w f="181">let</w>
<w f="181">view</w>
<w f="181">place</w>
<w f="181">another</w>
<w f="181">company</w>
<w f="181">talk</w>
<w f="181">might</w>
<w f="181">am</w>
<w f="181">though</w>
<w f="181">find</w>
<w f="180">details</w>
<w f="180">look</w>
<w f="180">world</w>
<w f="180">old</w>
<w f="180">called</w>
<w f="180">case</w>
<w f="180">system</w>
<w f="180">news</w>
<w f="179">used</w>
<w f="179">contact</w>
<w f="179">never</w>
<w f="179">seven</w>
<w f="179">city</w>
<w f="179">until</w>
<w f="179">during</w>
<w f="179">set</w>
<w f="179">why</w>
<w f="179">point</w>
<w f="179">twenty</w>
<w f="179">high</w>
<w f="179">love</w>
<w f="179">services</w>
<w f="170">niño</w>
<w f="170">María</w>
<w f="0">hmmm</w>
<w f="0">hon</w>
<w f="0">tty</w>
<w f="0">ttyl</w>
<w f="0">txt</w>
<w f="0">ur</w>
<w f="0">wah</w>
<w f="0">whatcha</w>
<w f="0">woah</w>
<w f="0">ya</w>
<w f="0">yea</w>
<w f="0">yeh</w>
<w f="0">yessir</w>
<w f="0">yikes</w>
<w f="0">yrs</w>
</wordlist>

BIN
tests/res/raw/test.dict Normal file

Binary file not shown.

View File

@ -0,0 +1,248 @@
package com.android.inputmethod.latin.tests;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.Channels;
import java.util.List;
import android.content.Context;
import android.test.AndroidTestCase;
import android.text.TextUtils;
import android.util.Log;
import com.android.inputmethod.latin.Suggest;
import com.android.inputmethod.latin.WordComposer;
public class SuggestTests extends AndroidTestCase {
private static final String TAG = "SuggestTests";
private Suggest mSuggest;
int[][] adjacents = {
{'a','s','w','q',-1},
{'b','h','v','n','g','j',-1},
{'c','v','f','x','g',},
{'d','f','r','e','s','x',-1},
{'e','w','r','s','d',-1},
{'f','g','d','c','t','r',-1},
{'g','h','f','y','t','v',-1},
{'h','j','u','g','b','y',-1},
{'i','o','u','k',-1},
{'j','k','i','h','u','n',-1},
{'k','l','o','j','i','m',-1},
{'l','k','o','p',-1},
{'m','k','n','l',-1},
{'n','m','j','k','b',-1},
{'o','p','i','l',-1},
{'p','o',-1},
{'q','w',-1},
{'r','t','e','f',-1},
{'s','d','e','w','a','z',-1},
{'t','y','r',-1},
{'u','y','i','h','j',-1},
{'v','b','g','c','h',-1},
{'w','e','q',-1},
{'x','c','d','z','f',-1},
{'y','u','t','h','g',-1},
{'z','s','x','a','d',-1},
};
@Override
protected void setUp() {
final Context context = getTestContext();
InputStream is = context.getResources().openRawResource(R.raw.test);
Log.i(TAG, "Stream type is " + is);
try {
int avail = is.available();
if (avail > 0) {
ByteBuffer byteBuffer =
ByteBuffer.allocateDirect(avail).order(ByteOrder.nativeOrder());
int got = Channels.newChannel(is).read(byteBuffer);
if (got != avail) {
Log.e(TAG, "Read " + got + " bytes, expected " + avail);
} else {
mSuggest = new Suggest(context, byteBuffer);
Log.i(TAG, "Created mSuggest " + avail + " bytes");
}
}
} catch (IOException ioe) {
Log.w(TAG, "No available size for binary dictionary");
}
mSuggest.setAutoTextEnabled(false);
mSuggest.setCorrectionMode(Suggest.CORRECTION_FULL);
}
/************************** Helper functions ************************/
private WordComposer createWordComposer(CharSequence s) {
WordComposer word = new WordComposer();
for (int i = 0; i < s.length(); i++) {
final char c = s.charAt(i);
int[] codes;
// If it's not a lowercase letter, don't find adjacent letters
if (c < 'a' || c > 'z') {
codes = new int[] { c };
} else {
codes = adjacents[c - 'a'];
}
word.add(c, codes);
}
return word;
}
private void showList(String title, List<CharSequence> suggestions) {
Log.i(TAG, title);
for (int i = 0; i < suggestions.size(); i++) {
Log.i(title, suggestions.get(i) + ", ");
}
}
private boolean isDefaultSuggestion(List<CharSequence> suggestions, CharSequence word) {
// Check if either the word is what you typed or the first alternative
return suggestions.size() > 0 &&
(/*TextUtils.equals(suggestions.get(0), word) || */
(suggestions.size() > 1 && TextUtils.equals(suggestions.get(1), word)));
}
private boolean isDefaultSuggestion(CharSequence typed, CharSequence expected) {
WordComposer word = createWordComposer(typed);
List<CharSequence> suggestions = mSuggest.getSuggestions(null, word, false);
return isDefaultSuggestion(suggestions, expected);
}
private boolean isDefaultCorrection(CharSequence typed, CharSequence expected) {
WordComposer word = createWordComposer(typed);
List<CharSequence> suggestions = mSuggest.getSuggestions(null, word, false);
return isDefaultSuggestion(suggestions, expected) && mSuggest.hasMinimalCorrection();
}
private boolean isASuggestion(CharSequence typed, CharSequence expected) {
WordComposer word = createWordComposer(typed);
List<CharSequence> suggestions = mSuggest.getSuggestions(null, word, false);
for (int i = 1; i < suggestions.size(); i++) {
if (TextUtils.equals(suggestions.get(i), expected)) return true;
}
return false;
}
private boolean isValid(CharSequence typed) {
return mSuggest.isValidWord(typed);
}
/************************** Tests ************************/
/**
* Tests for simple completions of one character.
*/
public void testCompletion1char() {
assertTrue(isDefaultSuggestion("peopl", "people"));
assertTrue(isDefaultSuggestion("abou", "about"));
assertTrue(isDefaultSuggestion("thei", "their"));
}
/**
* Tests for simple completions of two characters.
*/
public void testCompletion2char() {
assertTrue(isDefaultSuggestion("peop", "people"));
assertTrue(isDefaultSuggestion("calli", "calling"));
assertTrue(isDefaultSuggestion("busine", "business"));
}
/**
* Tests for proximity errors.
*/
public void testProximityPositive() {
assertTrue(isDefaultSuggestion("peiple", "people"));
assertTrue(isDefaultSuggestion("peoole", "people"));
assertTrue(isDefaultSuggestion("pwpple", "people"));
}
/**
* Tests for proximity errors - negative, when the error key is not near.
*/
public void testProximityNegative() {
assertFalse(isDefaultSuggestion("arout", "about"));
assertFalse(isDefaultSuggestion("ire", "are"));
}
/**
* Tests for checking if apostrophes are added automatically.
*/
public void testApostropheInsertion() {
assertTrue(isDefaultSuggestion("im", "I'm"));
assertTrue(isDefaultSuggestion("dont", "don't"));
}
/**
* Test to make sure apostrophed word is not suggested for an apostrophed word.
*/
public void testApostrophe() {
assertFalse(isDefaultSuggestion("don't", "don't"));
}
/**
* Tests for suggestion of capitalized version of a word.
*/
public void testCapitalization() {
assertTrue(isDefaultSuggestion("i'm", "I'm"));
assertTrue(isDefaultSuggestion("sunday", "Sunday"));
assertTrue(isDefaultSuggestion("sundat", "Sunday"));
}
/**
* Tests to see if more than one completion is provided for certain prefixes.
*/
public void testMultipleCompletions() {
assertTrue(isASuggestion("com", "come"));
assertTrue(isASuggestion("com", "company"));
assertTrue(isASuggestion("th", "the"));
assertTrue(isASuggestion("th", "that"));
assertTrue(isASuggestion("th", "this"));
assertTrue(isASuggestion("th", "they"));
}
/**
* Does the suggestion engine recognize zero frequency words as valid words.
*/
public void testZeroFrequencyAccepted() {
assertTrue(isValid("yikes"));
assertFalse(isValid("yike"));
}
/**
* Tests to make sure that zero frequency words are not suggested as completions.
*/
public void testZeroFrequencySuggestionsNegative() {
assertFalse(isASuggestion("yike", "yikes"));
assertFalse(isASuggestion("what", "whatcha"));
}
/**
* Tests to ensure that words with large edit distances are not suggested, in some cases
* and not considered corrections, in some cases.
*/
public void testTooLargeEditDistance() {
assertFalse(isASuggestion("sniyr", "about"));
assertFalse(isDefaultCorrection("rjw", "the"));
}
/**
* Make sure isValid is case-sensitive.
*/
public void testValidityCaseSensitivity() {
assertTrue(isValid("Sunday"));
assertFalse(isValid("sunday"));
}
/**
* Are accented forms of words suggested as corrections?
*/
public void testAccents() {
assertTrue(isDefaultCorrection("nino", "ni\u00F1o")); // nio
assertTrue(isDefaultCorrection("nimo", "ni\u00F1o")); // nio
assertTrue(isDefaultCorrection("maria", "Mar\u00EDa")); // Mara
}
}