1/* 2 * Copyright (C) 2012 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 17package com.android.inputmethod.latin.personalization; 18 19import android.test.AndroidTestCase; 20import android.test.suitebuilder.annotation.LargeTest; 21import android.util.Log; 22 23import com.android.inputmethod.latin.ExpandableBinaryDictionary; 24import com.android.inputmethod.latin.PrevWordsInfo; 25import com.android.inputmethod.latin.PrevWordsInfo.WordInfo; 26import com.android.inputmethod.latin.utils.BinaryDictionaryUtils; 27import com.android.inputmethod.latin.utils.DistracterFilter; 28import com.android.inputmethod.latin.utils.FileUtils; 29 30import java.io.File; 31import java.util.ArrayList; 32import java.util.HashSet; 33import java.util.List; 34import java.util.Locale; 35import java.util.Random; 36import java.util.concurrent.TimeUnit; 37 38/** 39 * Unit tests for UserHistoryDictionary 40 */ 41@LargeTest 42public class UserHistoryDictionaryTests extends AndroidTestCase { 43 private static final String TAG = UserHistoryDictionaryTests.class.getSimpleName(); 44 45 private static final String[] CHARACTERS = { 46 "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", 47 "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z" 48 }; 49 50 private int mCurrentTime = 0; 51 52 @Override 53 protected void setUp() throws Exception { 54 super.setUp(); 55 resetCurrentTimeForTestMode(); 56 } 57 58 @Override 59 protected void tearDown() throws Exception { 60 stopTestModeInNativeCode(); 61 super.tearDown(); 62 } 63 64 private void resetCurrentTimeForTestMode() { 65 mCurrentTime = 0; 66 setCurrentTimeForTestMode(mCurrentTime); 67 } 68 69 private void forcePassingShortTime() { 70 // 3 days. 71 final int timeToElapse = (int)TimeUnit.DAYS.toSeconds(3); 72 mCurrentTime += timeToElapse; 73 setCurrentTimeForTestMode(mCurrentTime); 74 } 75 76 private void forcePassingLongTime() { 77 // 365 days. 78 final int timeToElapse = (int)TimeUnit.DAYS.toSeconds(365); 79 mCurrentTime += timeToElapse; 80 setCurrentTimeForTestMode(mCurrentTime); 81 } 82 83 private static int setCurrentTimeForTestMode(final int currentTime) { 84 return BinaryDictionaryUtils.setCurrentTimeForTest(currentTime); 85 } 86 87 private static int stopTestModeInNativeCode() { 88 return BinaryDictionaryUtils.setCurrentTimeForTest(-1); 89 } 90 91 /** 92 * Generates a random word. 93 */ 94 private static String generateWord(final int value) { 95 final int lengthOfChars = CHARACTERS.length; 96 StringBuilder builder = new StringBuilder(); 97 long lvalue = Math.abs((long)value); 98 while (lvalue > 0) { 99 builder.append(CHARACTERS[(int)(lvalue % lengthOfChars)]); 100 lvalue /= lengthOfChars; 101 } 102 return builder.toString(); 103 } 104 105 private static List<String> generateWords(final int number, final Random random) { 106 final HashSet<String> wordSet = new HashSet<>(); 107 while (wordSet.size() < number) { 108 wordSet.add(generateWord(random.nextInt())); 109 } 110 return new ArrayList<>(wordSet); 111 } 112 113 private static void addToDict(final UserHistoryDictionary dict, final List<String> words) { 114 PrevWordsInfo prevWordsInfo = PrevWordsInfo.EMPTY_PREV_WORDS_INFO; 115 for (String word : words) { 116 UserHistoryDictionary.addToDictionary(dict, prevWordsInfo, word, true, 117 (int)TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()), 118 DistracterFilter.EMPTY_DISTRACTER_FILTER); 119 prevWordsInfo = prevWordsInfo.getNextPrevWordsInfo(new WordInfo(word)); 120 } 121 } 122 123 /** 124 * @param checkContents if true, checks whether written words are actually in the dictionary 125 * or not. 126 */ 127 private void addAndWriteRandomWords(final Locale locale, final int numberOfWords, 128 final Random random, final boolean checkContents) { 129 final List<String> words = generateWords(numberOfWords, random); 130 final UserHistoryDictionary dict = PersonalizationHelper.getUserHistoryDictionary( 131 mContext, locale); 132 // Add random words to the user history dictionary. 133 addToDict(dict, words); 134 if (checkContents) { 135 dict.waitAllTasksForTests(); 136 for (int i = 0; i < numberOfWords; ++i) { 137 final String word = words.get(i); 138 assertTrue(dict.isInDictionary(word)); 139 } 140 } 141 // write to file. 142 dict.close(); 143 } 144 145 /** 146 * Clear all entries in the user history dictionary. 147 * @param locale dummy locale for testing. 148 */ 149 private void clearHistory(final Locale locale) { 150 final UserHistoryDictionary dict = PersonalizationHelper.getUserHistoryDictionary( 151 mContext, locale); 152 dict.waitAllTasksForTests(); 153 dict.clear(); 154 dict.close(); 155 dict.waitAllTasksForTests(); 156 } 157 158 /** 159 * Shut down executer and wait until all operations of user history are done. 160 * @param locale dummy locale for testing. 161 */ 162 private void waitForWriting(final Locale locale) { 163 final UserHistoryDictionary dict = PersonalizationHelper.getUserHistoryDictionary( 164 mContext, locale); 165 dict.waitAllTasksForTests(); 166 } 167 168 public void testRandomWords() { 169 Log.d(TAG, "This test can be used for profiling."); 170 Log.d(TAG, "Usage: please set UserHistoryDictionary.PROFILE_SAVE_RESTORE to true."); 171 final Locale dummyLocale = new Locale("test_random_words" + System.currentTimeMillis()); 172 final String dictName = ExpandableBinaryDictionary.getDictName( 173 UserHistoryDictionary.NAME, dummyLocale, null /* dictFile */); 174 final File dictFile = ExpandableBinaryDictionary.getDictFile( 175 mContext, dictName, null /* dictFile */); 176 177 final int numberOfWords = 1000; 178 final Random random = new Random(123456); 179 180 try { 181 clearHistory(dummyLocale); 182 addAndWriteRandomWords(dummyLocale, numberOfWords, random, 183 true /* checksContents */); 184 } finally { 185 Log.d(TAG, "waiting for writing ..."); 186 waitForWriting(dummyLocale); 187 assertTrue("check exisiting of " + dictFile, dictFile.exists()); 188 FileUtils.deleteRecursively(dictFile); 189 } 190 } 191 192 public void testStressTestForSwitchingLanguagesAndAddingWords() { 193 final int numberOfLanguages = 2; 194 final int numberOfLanguageSwitching = 80; 195 final int numberOfWordsInsertedForEachLanguageSwitch = 100; 196 197 final File dictFiles[] = new File[numberOfLanguages]; 198 final Locale dummyLocales[] = new Locale[numberOfLanguages]; 199 try { 200 final Random random = new Random(123456); 201 202 // Create filename suffixes for this test. 203 for (int i = 0; i < numberOfLanguages; i++) { 204 dummyLocales[i] = new Locale("test_switching_languages" + i); 205 final String dictName = ExpandableBinaryDictionary.getDictName( 206 UserHistoryDictionary.NAME, dummyLocales[i], null /* dictFile */); 207 dictFiles[i] = ExpandableBinaryDictionary.getDictFile( 208 mContext, dictName, null /* dictFile */); 209 clearHistory(dummyLocales[i]); 210 } 211 212 final long start = System.currentTimeMillis(); 213 214 for (int i = 0; i < numberOfLanguageSwitching; i++) { 215 final int index = i % numberOfLanguages; 216 // Switch languages to testFilenameSuffixes[index]. 217 addAndWriteRandomWords(dummyLocales[index], 218 numberOfWordsInsertedForEachLanguageSwitch, random, 219 false /* checksContents */); 220 } 221 222 final long end = System.currentTimeMillis(); 223 Log.d(TAG, "testStressTestForSwitchingLanguageAndAddingWords took " 224 + (end - start) + " ms"); 225 } finally { 226 Log.d(TAG, "waiting for writing ..."); 227 for (int i = 0; i < numberOfLanguages; i++) { 228 waitForWriting(dummyLocales[i]); 229 } 230 for (final File dictFile : dictFiles) { 231 assertTrue("check exisiting of " + dictFile, dictFile.exists()); 232 FileUtils.deleteRecursively(dictFile); 233 } 234 } 235 } 236 237 public void testAddManyWords() { 238 final Locale dummyLocale = new Locale("test_random_words" + System.currentTimeMillis()); 239 final String dictName = ExpandableBinaryDictionary.getDictName( 240 UserHistoryDictionary.NAME, dummyLocale, null /* dictFile */); 241 final File dictFile = ExpandableBinaryDictionary.getDictFile( 242 mContext, dictName, null /* dictFile */); 243 final int numberOfWords = 10000; 244 final Random random = new Random(123456); 245 clearHistory(dummyLocale); 246 try { 247 addAndWriteRandomWords(dummyLocale, numberOfWords, random, true /* checksContents */); 248 } finally { 249 Log.d(TAG, "waiting for writing ..."); 250 waitForWriting(dummyLocale); 251 assertTrue("check exisiting of " + dictFile, dictFile.exists()); 252 FileUtils.deleteRecursively(dictFile); 253 } 254 } 255 256 public void testDecaying() { 257 final Locale dummyLocale = new Locale("test_decaying" + System.currentTimeMillis()); 258 final int numberOfWords = 5000; 259 final Random random = new Random(123456); 260 resetCurrentTimeForTestMode(); 261 clearHistory(dummyLocale); 262 final List<String> words = generateWords(numberOfWords, random); 263 final UserHistoryDictionary dict = 264 PersonalizationHelper.getUserHistoryDictionary(getContext(), dummyLocale); 265 dict.waitAllTasksForTests(); 266 PrevWordsInfo prevWordsInfo = PrevWordsInfo.EMPTY_PREV_WORDS_INFO; 267 for (final String word : words) { 268 UserHistoryDictionary.addToDictionary(dict, prevWordsInfo, word, true, mCurrentTime, 269 DistracterFilter.EMPTY_DISTRACTER_FILTER); 270 prevWordsInfo = prevWordsInfo.getNextPrevWordsInfo(new WordInfo(word)); 271 dict.waitAllTasksForTests(); 272 assertTrue(dict.isInDictionary(word)); 273 } 274 forcePassingShortTime(); 275 dict.runGCIfRequired(); 276 dict.waitAllTasksForTests(); 277 for (final String word : words) { 278 assertTrue(dict.isInDictionary(word)); 279 } 280 forcePassingLongTime(); 281 dict.runGCIfRequired(); 282 dict.waitAllTasksForTests(); 283 for (final String word : words) { 284 assertFalse(dict.isInDictionary(word)); 285 } 286 } 287} 288