add unit tests for input logic (wip very much)

This commit is contained in:
Helium314 2023-09-19 19:39:47 +02:00
parent f1fde08a55
commit 70f419efe5
7 changed files with 590 additions and 270 deletions

View file

@ -24,7 +24,8 @@ private class CrashReportExceptionHandler(val appContext: Context) : Thread.Unca
fun install(): Boolean {
val ueh = Thread.getDefaultUncaughtExceptionHandler()
check(ueh !is CrashReportExceptionHandler) { "May not install several CrashReportExceptionHandlers!" }
if (ueh is CrashReportExceptionHandler)
return false
defaultUncaughtExceptionHandler = ueh
Thread.setDefaultUncaughtExceptionHandler(this)
return true

View file

@ -18,6 +18,7 @@ package org.dslul.openboard.inputmethod.latin;
import android.text.TextUtils;
import org.dslul.openboard.inputmethod.annotations.UsedForTesting;
import org.dslul.openboard.inputmethod.keyboard.Keyboard;
import org.dslul.openboard.inputmethod.keyboard.KeyboardId;
import org.dslul.openboard.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
@ -203,8 +204,6 @@ public final class Suggest {
keyboard.mId.mMode,
wordComposer,
suggestionResults,
mDictionaryFacilitator,
mAutoCorrectionThreshold,
firstOccurrenceOfTypedWordInSuggestions,
typedWordFirstOccurrenceWordInfo
);
@ -282,7 +281,8 @@ public final class Suggest {
}
// returns [allowsToBeAutoCorrected, hasAutoCorrection]
static boolean[] shouldBeAutoCorrected(
@UsedForTesting
boolean[] shouldBeAutoCorrected(
final int trailingSingleQuotesCount,
final String typedWordString,
final List<SuggestedWordInfo> suggestionsContainer,
@ -293,8 +293,6 @@ public final class Suggest {
final int keyboardIdMode,
final WordComposer wordComposer,
final SuggestionResults suggestionResults,
final DictionaryFacilitator dictionaryFacilitator,
final float autoCorrectionThreshold,
final int firstOccurrenceOfTypedWordInSuggestions,
final SuggestedWordInfo typedWordFirstOccurrenceWordInfo
) {
@ -365,7 +363,7 @@ public final class Suggest {
// list, "will" would always auto-correct to "Will" which is unwanted. Hence, no
// main dict => no auto-correct. Also, it would probably get obnoxious quickly.
// TODO: now that we have personalization, we may want to re-evaluate this decision
|| !dictionaryFacilitator.hasAtLeastOneInitializedMainDictionary()) {
|| !mDictionaryFacilitator.hasAtLeastOneInitializedMainDictionary()) {
hasAutoCorrection = false;
} else {
final SuggestedWordInfo firstSuggestion = suggestionResults.first();
@ -376,7 +374,7 @@ public final class Suggest {
return new boolean[]{ true, true };
}
if (!AutoCorrectionUtils.suggestionExceedsThreshold(
firstSuggestion, consideredWord, autoCorrectionThreshold)) {
firstSuggestion, consideredWord, mAutoCorrectionThreshold)) {
// todo: maybe also do something here depending on ngram context?
// Score is too low for autocorrect
return new boolean[]{ true, false };
@ -390,7 +388,7 @@ public final class Suggest {
// typed word is valid and has good score
// do not auto-correct if typed word is better match than first suggestion
final SuggestedWordInfo first = firstSuggestionInContainer != null ? firstSuggestionInContainer : firstSuggestion;
final Locale dictLocale = dictionaryFacilitator.getCurrentLocale();
final Locale dictLocale = mDictionaryFacilitator.getCurrentLocale();
if (first.mScore < scoreLimit) {
// don't allow if suggestion has too low score

View file

@ -16,18 +16,11 @@
package org.dslul.openboard.inputmethod.latin.utils;
import static android.view.KeyEvent.KEYCODE_SPACE;
import android.annotation.SuppressLint;
import android.os.Build;
import android.util.Log;
import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
import org.dslul.openboard.inputmethod.annotations.UsedForTesting;
import org.dslul.openboard.inputmethod.latin.BuildConfig;
import org.dslul.openboard.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import org.dslul.openboard.inputmethod.latin.common.StringUtils;
import org.dslul.openboard.inputmethod.latin.define.DebugFlags;
public final class AutoCorrectionUtils {
@ -38,7 +31,6 @@ public final class AutoCorrectionUtils {
// Purely static class: can't instantiate.
}
@SuppressLint("ObsoleteSdkInt") // SDK_INT is 0 in unit tests
public static boolean suggestionExceedsThreshold(final SuggestedWordInfo suggestion,
final String consideredWord, final float threshold) {
if (null != suggestion) {
@ -53,13 +45,7 @@ public final class AutoCorrectionUtils {
final int autoCorrectionSuggestionScore = suggestion.mScore;
// TODO: when the normalized score of the first suggestion is nearly equals to
// the normalized score of the second suggestion, behave less aggressive.
final float normalizedScore;
if (BuildConfig.DEBUG && Build.VERSION.SDK_INT == 0)
normalizedScore = calcNormalizedScore(StringUtils.toCodePointArray(consideredWord),
StringUtils.toCodePointArray(suggestion.mWord), autoCorrectionSuggestionScore,
editDistance(consideredWord, suggestion.mWord));
else
normalizedScore = BinaryDictionaryUtils.calcNormalizedScore(
final float normalizedScore = BinaryDictionaryUtils.calcNormalizedScore(
consideredWord, suggestion.mWord, autoCorrectionSuggestionScore);
if (DBG) {
Log.d(TAG, "Normalized " + consideredWord + "," + suggestion + ","
@ -75,72 +61,4 @@ public final class AutoCorrectionUtils {
}
return false;
}
// below is normalized score calculation in java, to allow unit tests involving suggestionExceedsThreshold
@UsedForTesting
private static float calcNormalizedScore(final int[] before,
final int[] after, final int score, final int distance) {
final int beforeLength = before.length;
final int afterLength = after.length;
if (0 == beforeLength || 0 == afterLength)
return 0.0f;
int spaceCount = 0;
for (int j : after) {
if (j == KEYCODE_SPACE)
++spaceCount;
}
if (spaceCount == afterLength)
return 0.0f;
if (score <= 0 || distance >= afterLength) {
// normalizedScore must be 0.0f (the minimum value) if the score is less than or equal to 0,
// or if the edit distance is larger than or equal to afterLength.
return 0.0f;
}
// add a weight based on edit distance.
final float weight = 1.0f - (float) distance / (float) afterLength;
return ((float) score / 1000000.0f) * weight;
}
@UsedForTesting
private static int editDistance(String x, String y) {
int[][] dp = new int[x.length() + 1][y.length() + 1];
for (int i = 0; i <= x.length(); i++) {
for (int j = 0; j <= y.length(); j++) {
if (i == 0) {
dp[i][j] = j;
}
else if (j == 0) {
dp[i][j] = i;
}
else {
dp[i][j] = min(dp[i - 1][j - 1]
+ costOfSubstitution(x.charAt(i - 1), y.charAt(j - 1)),
dp[i - 1][j] + 1,
dp[i][j - 1] + 1);
}
}
}
return dp[x.length()][y.length()];
}
@UsedForTesting
private static int min(int... numbers) {
int min = Integer.MAX_VALUE;
for (int n : numbers) {
if (n < min)
min = n;
}
return min;
}
@UsedForTesting
private static int costOfSubstitution(char a, char b) {
return a == b ? 0 : 1;
}
}