blob: 4218fb6f7259186f523a6c3623d7ac51793f3a90 [file] [log] [blame]
// Copyright 2010-2015, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package org.mozc.android.inputmethod.japanese.session;
import static org.mozc.android.inputmethod.japanese.testing.MozcMatcher.matchesBuilder;
import static org.mozc.android.inputmethod.japanese.testing.MozcMatcher.sameOptional;
import static org.easymock.EasyMock.anyLong;
import static org.easymock.EasyMock.capture;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.getCurrentArguments;
import static org.easymock.EasyMock.same;
import org.mozc.android.inputmethod.japanese.KeycodeConverter;
import org.mozc.android.inputmethod.japanese.KeycodeConverter.KeyEventInterface;
import org.mozc.android.inputmethod.japanese.preference.ClientSidePreference.KeyboardLayout;
import org.mozc.android.inputmethod.japanese.preference.PreferenceUtil;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCandidates.CandidateList;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCandidates.CandidateWord;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands.Capability;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands.Capability.TextDeletionCapabilityType;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands.Command;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands.CompositionMode;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands.Context.InputFieldType;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands.DeletionRange;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands.GenericStorageEntry;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands.GenericStorageEntry.StorageType;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands.Input;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands.Input.CommandType;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands.Input.TouchEvent;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands.KeyEvent.SpecialKey;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands.Output;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands.Request;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands.Result;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands.Result.ResultType;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands.SessionCommand;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands.SessionCommand.UsageStatsEvent;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoConfig.Config;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoUserDictionaryStorage.UserDictionaryCommand;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoUserDictionaryStorage.UserDictionaryCommandStatus;
import org.mozc.android.inputmethod.japanese.session.SessionExecutor.AsynchronousEvaluationContext;
import org.mozc.android.inputmethod.japanese.session.SessionExecutor.CallbackHandler;
import org.mozc.android.inputmethod.japanese.session.SessionExecutor.EvaluationCallback;
import org.mozc.android.inputmethod.japanese.session.SessionExecutor.ExecutorMainCallback;
import org.mozc.android.inputmethod.japanese.session.SessionExecutor.SynchronousEvaluationContext;
import org.mozc.android.inputmethod.japanese.stresstest.StressTest;
import org.mozc.android.inputmethod.japanese.testing.InstrumentationTestCaseWithMock;
import org.mozc.android.inputmethod.japanese.testing.MemoryLogger;
import org.mozc.android.inputmethod.japanese.testing.Parameter;
import com.google.common.base.Optional;
import com.google.common.base.Strings;
import com.google.protobuf.ByteString;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.preference.PreferenceManager;
import android.test.mock.MockContext;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.SmallTest;
import android.view.KeyEvent;
import org.easymock.Capture;
import org.easymock.IAnswer;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
/**
*
*/
public class SessionExecutorTest extends InstrumentationTestCaseWithMock {
private SessionExecutor createSessionExecutorMock() {
return createMockBuilder(SessionExecutor.class)
.addMockedMethods("evaluateSynchronously", "evaluateAsynchronously")
.createMock();
}
@Override
protected void tearDown() throws Exception {
SessionExecutor.instance = Optional.absent();
super.tearDown();
}
@SmallTest
public void testGetInstanceInitializedIfNecessary() {
Context context = new MockContext();
SessionHandlerFactory factory = createMockBuilder(SessionHandlerFactory.class)
.withConstructor(Context.class)
.withArgs(getInstrumentation().getTargetContext())
.createMock();
SessionHandler mockHandler = createNiceMock(SessionHandler.class);
expect(factory.create()).andReturn(mockHandler);
replayAll();
SessionExecutor instance = SessionExecutor.getInstanceInitializedIfNecessary(factory, context);
verifyAll();
// We don't expect re-setting up for the second time.
resetAll();
replayAll();
SessionExecutor instance2 = SessionExecutor.getInstanceInitializedIfNecessary(factory, context);
verifyAll();
// The same instance should be returned.
assertSame(instance, instance2);
}
@SmallTest
public void testCallbackInitializeSessionHandler() {
Context context = getInstrumentation().getContext();
SessionHandler sessionHandler = createMock(SessionHandler.class);
sessionHandler.initialize(context);
replayAll();
ExecutorMainCallback callback = new ExecutorMainCallback(sessionHandler);
Message message = new Message();
message.obj = Object.class.cast(context);
message.what = ExecutorMainCallback.INITIALIZE_SESSION_HANDLER;
assertTrue(callback.handleMessage(message));
verifyAll();
}
@SmallTest
public void testCallbackEnsureSession() {
SessionHandler sessionHandler = createMock(SessionHandler.class);
long sessionId = 1;
Request initialRequest = Request.newBuilder()
.setMixedConversion(true)
.setZeroQuerySuggestion(true)
.setAutoPartialSuggestion(true)
.setUpdateInputModeFromSurroundingText(false)
.build();
{
// Create session.
Input input = Input.newBuilder()
.setType(CommandType.CREATE_SESSION)
.setCapability(Capability.newBuilder()
.setTextDeletion(TextDeletionCapabilityType.DELETE_PRECEDING_TEXT))
.build();
Command inputCommand = Command.newBuilder()
.setInput(input).setOutput(Output.getDefaultInstance()).build();
Command outputCommand = Command.newBuilder()
.setInput(input).setOutput(Output.newBuilder().setId(sessionId)).build();
expect(sessionHandler.evalCommand(inputCommand)).andReturn(outputCommand);
}
{
// Set request.
Command inputCommand = Command.newBuilder()
.setInput(Input.newBuilder()
.setId(sessionId)
.setType(CommandType.SET_REQUEST)
.setRequest(initialRequest))
.setOutput(Output.getDefaultInstance())
.build();
expect(sessionHandler.evalCommand(inputCommand)).andReturn(Command.getDefaultInstance());
}
replayAll();
ExecutorMainCallback callback = new ExecutorMainCallback(sessionHandler);
callback.ensureSession();
verifyAll();
resetAll();
{
// After createSession, sessionId and request should be automatically filled, for update()
// method.
Command inputCommand = Command.newBuilder()
.setInput(Input.newBuilder()
.setId(sessionId)
.setType(CommandType.SET_REQUEST)
.setRequest(initialRequest))
.setOutput(Output.getDefaultInstance())
.build();
expect(sessionHandler.evalCommand(inputCommand)).andReturn(Command.getDefaultInstance());
}
replayAll();
callback.updateRequest(Input.newBuilder());
verifyAll();
}
@SmallTest
public void testCallbackIsSquashable() {
Output squashableOutput = Output.newBuilder()
.setConsumed(true)
.setAllCandidateWords(CandidateList.newBuilder()
.addCandidates(CandidateWord.getDefaultInstance()))
.build();
assertTrue(ExecutorMainCallback.isSquashableOutput(squashableOutput));
assertFalse(ExecutorMainCallback.isSquashableOutput(squashableOutput.toBuilder()
.setResult(Result.newBuilder().setType(ResultType.NONE).setValue("abc"))
.build()));
assertFalse(ExecutorMainCallback.isSquashableOutput(squashableOutput.toBuilder()
.setDeletionRange(DeletionRange.getDefaultInstance())
.build()));
assertFalse(ExecutorMainCallback.isSquashableOutput(squashableOutput.toBuilder()
.setConsumed(false)
.build()));
assertFalse(ExecutorMainCallback.isSquashableOutput(squashableOutput.toBuilder()
.clearAllCandidateWords()
.build()));
assertFalse(ExecutorMainCallback.isSquashableOutput(Output.getDefaultInstance()));
}
@SmallTest
public void testIsSessionIdRequired() {
assertFalse(ExecutorMainCallback.isSessionIdRequired(Input.newBuilder()
.setType(CommandType.SET_CONFIG)));
assertFalse(ExecutorMainCallback.isSessionIdRequired(Input.newBuilder()
.setType(CommandType.SET_IMPOSED_CONFIG)));
assertFalse(ExecutorMainCallback.isSessionIdRequired(Input.newBuilder()
.setType(CommandType.GET_CONFIG)));
assertTrue(ExecutorMainCallback.isSessionIdRequired(Input.newBuilder()
.setType(CommandType.SEND_KEY)));
assertTrue(ExecutorMainCallback.isSessionIdRequired(Input.newBuilder()
.setType(CommandType.SEND_COMMAND)));
}
@SmallTest
public void testCallbackEvaluateAsynchronously() {
class TestData extends Parameter {
final Output output;
final boolean expectRequestSuggestion;
final int expectedWhat;
TestData(Output output, boolean expectRequestSuggestion, int expectedWhat) {
this.output = output;
this.expectRequestSuggestion = expectRequestSuggestion;
this.expectedWhat = expectedWhat;
}
}
TestData[] testData = new TestData[] {
new TestData(Output.newBuilder()
.setConsumed(true)
.setAllCandidateWords(CandidateList.newBuilder()
.addCandidates(CandidateWord.getDefaultInstance()))
.build(),
true,
CallbackHandler.SQUASHABLE_OUTPUT),
new TestData(Output.getDefaultInstance(), true, CallbackHandler.UNSQUASHABLE_OUTPUT),
new TestData(Output.newBuilder()
.setConsumed(true)
.setAllCandidateWords(CandidateList.newBuilder()
.addCandidates(CandidateWord.getDefaultInstance()))
.build(),
false,
CallbackHandler.SQUASHABLE_OUTPUT),
new TestData(Output.getDefaultInstance(), false, CallbackHandler.UNSQUASHABLE_OUTPUT),
};
Handler callbackHandler = createMock(Handler.class);
Handler currentHandler = new Handler();
SessionHandler sessionHandler = createMock(SessionHandler.class);
for (TestData data : testData) {
resetAll();
Input.Builder inputBuilder = Input.newBuilder()
.setType(CommandType.SEND_KEY)
.setKey(ProtoCommands.KeyEvent.newBuilder()
.setSpecialKey(SpecialKey.BACKSPACE));
// Unfortunately we cannot mock the removeMessages because it is final method. Just skip it.
// Note that Handler#obtainMessage and Handler#sendMessage cannot be mocked.
// So, we just mock sendMessageAtTime method instead.
Capture<Message> messageCapture = new Capture<Message>();
expect(callbackHandler.sendMessageAtTime(capture(messageCapture), anyLong())).andReturn(true);
AsynchronousEvaluationContext context = new AsynchronousEvaluationContext(
0, inputBuilder, Optional.<KeyEventInterface>absent(),
Optional.<EvaluationCallback>absent(), Optional.of(callbackHandler));
long sessionId = 1;
{
Input.Builder builder = inputBuilder.clone();
builder.setId(sessionId);
if (!data.expectRequestSuggestion) {
builder.setRequestSuggestion(false);
}
Command inputCommand = Command.newBuilder()
.setInput(builder)
.setOutput(Output.getDefaultInstance())
.build();
expect(sessionHandler.evalCommand(inputCommand)).andReturn(Command.newBuilder()
.setInput(inputCommand.getInput())
.setOutput(data.output)
.build());
}
if (!data.expectRequestSuggestion) {
currentHandler.sendEmptyMessage(ExecutorMainCallback.EVALUATE_KEYEVENT_ASYNCHRONOUSLY);
}
replayAll();
ExecutorMainCallback callback = new ExecutorMainCallback(sessionHandler);
callback.sessionId = sessionId;
callback.evaluateAsynchronously(context, currentHandler);
verifyAll();
Message message = messageCapture.getValue();
assertEquals(data.expectedWhat, message.what);
assertSame(context, message.obj);
assertTrue(context.outCommand.isPresent());
currentHandler.removeMessages(ExecutorMainCallback.EVALUATE_KEYEVENT_ASYNCHRONOUSLY);
}
}
@SmallTest
public void testCallbackEvaluateSynchronously() {
Input input = Input.newBuilder().setType(Input.CommandType.GET_CONFIG).build();
CountDownLatch countDownLatch = new CountDownLatch(1);
SynchronousEvaluationContext context = new SynchronousEvaluationContext(input, countDownLatch);
SessionHandler sessionHandler = createMock(SessionHandler.class);
{
Command inputCommand = Command.newBuilder()
.setInput(input).setOutput(Output.getDefaultInstance()).build();
expect(sessionHandler.evalCommand(inputCommand)).andReturn(Command.getDefaultInstance());
}
replayAll();
ExecutorMainCallback callback = new ExecutorMainCallback(sessionHandler);
callback.evaluateSynchronously(context);
verifyAll();
assertEquals(0, countDownLatch.getCount());
assertTrue(context.outCommand.isPresent());
}
@SmallTest
public void testCallbackUpdateRequest() {
Request.Builder initialRequest =
Request.newBuilder().setMixedConversion(true).setZeroQuerySuggestion(true);
Request diffRequest = Request.newBuilder().setZeroQuerySuggestion(false).build();
Request updatedRequest = initialRequest.clone().mergeFrom(diffRequest).build();
long sessionId = 1;
List<TouchEvent> touchEventList = Collections.singletonList(TouchEvent.getDefaultInstance());
SessionHandler sessionHandler = createMock(SessionHandler.class);
{
Command inputCommand = Command.newBuilder()
.setInput(Input.newBuilder()
.setId(sessionId)
.setType(CommandType.SET_REQUEST)
.setRequest(updatedRequest)
.addAllTouchEvents(touchEventList))
.setOutput(Output.getDefaultInstance())
.build();
expect(sessionHandler.evalCommand(inputCommand)).andReturn(Command.getDefaultInstance());
}
replayAll();
ExecutorMainCallback callback = new ExecutorMainCallback(sessionHandler);
callback.sessionId = sessionId;
callback.request = Optional.of(initialRequest);
callback.updateRequest(
Input.newBuilder().setRequest(diffRequest).addAllTouchEvents(touchEventList));
verifyAll();
resetAll();
{
// Make sure that internal request is also updated by invoking updateRequest again.
Command inputCommand = Command.newBuilder()
.setInput(Input.newBuilder()
.setId(sessionId)
.setType(CommandType.SET_REQUEST)
.setRequest(initialRequest))
.setOutput(Output.getDefaultInstance())
.build();
expect(sessionHandler.evalCommand(inputCommand)).andReturn(Command.getDefaultInstance());
}
replayAll();
callback.updateRequest(Input.newBuilder());
verifyAll();
}
@SmallTest
public void testCallbackHandler() {
CallbackHandler callbackHandler = new CallbackHandler(Looper.myLooper());
callbackHandler.cancelTimeStamp = 0;
EvaluationCallback evaluationCallback = createMock(EvaluationCallback.class);
evaluationCallback.onCompleted(
Optional.<Command>absent(), Optional.<KeyEventInterface>absent());
replayAll();
AsynchronousEvaluationContext context = new AsynchronousEvaluationContext(
1, Input.newBuilder(), Optional.<KeyEventInterface>absent(),
Optional.of(evaluationCallback), Optional.<Handler>of(callbackHandler));
Message message = callbackHandler.obtainMessage(CallbackHandler.UNSQUASHABLE_OUTPUT, context);
callbackHandler.handleMessage(message);
verifyAll();
resetAll();
replayAll();
callbackHandler.cancelTimeStamp = 1;
callbackHandler.handleMessage(message);
verifyAll();
}
@SmallTest
public void testDeleteSession() {
Capture<Message> messageCapture = new Capture<Message>();
Handler handler = createMock(Handler.class);
expect(handler.sendMessageAtTime(capture(messageCapture), anyLong())).andReturn(true);
replayAll();
SessionExecutor executor = new SessionExecutor();
executor.handler = Optional.of(handler);
executor.deleteSession();
verifyAll();
assertEquals(ExecutorMainCallback.DELETE_SESSION, messageCapture.getValue().what);
}
@SmallTest
public void testEvaluateAsynchronously() {
Capture<Message> messageCapture = new Capture<Message>();
Handler handler = createMock(Handler.class);
expect(handler.sendMessageAtTime(capture(messageCapture), anyLong())).andReturn(true);
replayAll();
SessionExecutor executor = new SessionExecutor();
executor.handler = Optional.of(handler);
Input.Builder inputBuilder = Input.newBuilder();
KeyEventInterface keyEvent = KeycodeConverter.getKeyEventInterface(KeyEvent.KEYCODE_A);
executor.evaluateAsynchronously(
inputBuilder, Optional.of(keyEvent), Optional.<EvaluationCallback>absent());
verifyAll();
Message message = messageCapture.getValue();
assertEquals(ExecutorMainCallback.EVALUATE_KEYEVENT_ASYNCHRONOUSLY, message.what);
AsynchronousEvaluationContext context = AsynchronousEvaluationContext.class.cast(message.obj);
assertSame(inputBuilder, context.inputBuilder);
assertSame(keyEvent, context.triggeringKeyEvent.get());
assertFalse(context.callback.isPresent());
assertFalse(context.callbackHandler.isPresent());
}
@SmallTest
public void testEvaluateAsynchronously_withoutKeyEvent() {
Capture<Message> messageCapture = new Capture<Message>();
Handler handler = createMock(Handler.class);
expect(handler.sendMessageAtTime(capture(messageCapture), anyLong())).andReturn(true);
replayAll();
SessionExecutor executor = new SessionExecutor();
executor.handler = Optional.of(handler);
Input.Builder inputBuilder = Input.newBuilder();
executor.evaluateAsynchronously(
inputBuilder, Optional.<KeyEventInterface>absent(), Optional.<EvaluationCallback>absent());
verifyAll();
Message message = messageCapture.getValue();
assertEquals(ExecutorMainCallback.EVALUATE_ASYNCHRONOUSLY, message.what);
AsynchronousEvaluationContext context = AsynchronousEvaluationContext.class.cast(message.obj);
assertSame(inputBuilder, context.inputBuilder);
assertFalse(context.triggeringKeyEvent.isPresent());
assertFalse(context.callback.isPresent());
assertFalse(context.callbackHandler.isPresent());
}
@SmallTest
public void testEvaluateSynchronously() {
Capture<Message> messageCapture = new Capture<Message>();
Handler handler = createMock(Handler.class);
// This emulates "accept message sent via handler, pseudo-execute of
// SessionHandlerCallback#evaluateSynchronously on this thread (instead of the worker thread)
// for synchronization purpose, and then return true as sendMessage's return value".
IAnswer<Boolean> mockExecutor = new IAnswer<Boolean>() {
@Override
public Boolean answer() throws Throwable {
// Emulate synchronous evaluation.
Message message = Message.class.cast(getCurrentArguments()[0]);
SynchronousEvaluationContext context =
SynchronousEvaluationContext.class.cast(message.obj);
context.outCommand = Optional.of(Command.newBuilder()
.setInput(Input.newBuilder().setType(CommandType.NO_OPERATION).build())
.setOutput(Output.getDefaultInstance())
.build());
context.evaluationSynchronizer.countDown();
return Boolean.TRUE;
}
};
expect(handler.sendMessageAtTime(capture(messageCapture), anyLong())).andAnswer(mockExecutor);
replayAll();
SessionExecutor executor = new SessionExecutor();
executor.handler = Optional.of(handler);
executor.evaluateSynchronously(Input.getDefaultInstance());
verifyAll();
assertEquals(ExecutorMainCallback.EVALUATE_SYNCHRONOUSLY, messageCapture.getValue().what);
}
@SmallTest
public void testSendKey() {
SessionExecutor executor = createSessionExecutorMock();
ProtoCommands.KeyEvent mozcKeyEvent = ProtoCommands.KeyEvent.getDefaultInstance();
KeyEventInterface triggeringKeyEvent =
KeycodeConverter.getKeyEventInterface(KeyEvent.KEYCODE_A);
EvaluationCallback evaluationCallback = createNiceMock(EvaluationCallback.class);
List<TouchEvent> touchEventList = Collections.singletonList(TouchEvent.getDefaultInstance());
executor.evaluateAsynchronously(
matchesBuilder(Input.newBuilder()
.setType(CommandType.SEND_KEY)
.setKey(mozcKeyEvent)
.addAllTouchEvents(touchEventList)),
sameOptional(triggeringKeyEvent),
sameOptional(evaluationCallback));
replayAll();
executor.sendKey(mozcKeyEvent, triggeringKeyEvent, touchEventList, evaluationCallback);
verifyAll();
}
@SmallTest
public void testSubmit() {
SessionExecutor executor = createSessionExecutorMock();
EvaluationCallback evaluationCallback = createNiceMock(EvaluationCallback.class);
executor.evaluateAsynchronously(
matchesBuilder(Input.newBuilder()
.setType(CommandType.SEND_COMMAND)
.setCommand(SessionCommand.newBuilder()
.setType(SessionCommand.CommandType.SUBMIT))),
same(Optional.<KeyEventInterface>absent()), sameOptional(evaluationCallback));
replayAll();
executor.submit(evaluationCallback);
verifyAll();
}
@SmallTest
public void testSwitchInputMode() {
SessionExecutor executor = createSessionExecutorMock();
EvaluationCallback callback = createNiceMock(EvaluationCallback.class);
executor.evaluateAsynchronously(
matchesBuilder(Input.newBuilder()
.setType(CommandType.SEND_COMMAND)
.setCommand(SessionCommand.newBuilder()
.setType(SessionCommand.CommandType.SWITCH_INPUT_MODE)
.setCompositionMode(CompositionMode.DIRECT))),
same(Optional.<KeyEventInterface>absent()), sameOptional(callback));
replayAll();
executor.switchInputMode(
Optional.<KeyEventInterface>absent(), CompositionMode.DIRECT, callback);
verifyAll();
}
@SmallTest
public void testSubmitCandidate() {
SessionExecutor executor = createSessionExecutorMock();
EvaluationCallback evaluationCallback = createNiceMock(EvaluationCallback.class);
executor.evaluateAsynchronously(
matchesBuilder(Input.newBuilder()
.setType(CommandType.SEND_COMMAND)
.setCommand(SessionCommand.newBuilder()
.setType(SessionCommand.CommandType.SUBMIT_CANDIDATE)
.setId(3))),
same(Optional.<KeyEventInterface>absent()), sameOptional(evaluationCallback));
executor.evaluateAsynchronously(
matchesBuilder(Input.newBuilder()
.setType(CommandType.SEND_COMMAND)
.setCommand(SessionCommand.newBuilder()
.setType(SessionCommand.CommandType.USAGE_STATS_EVENT)
.setUsageStatsEvent(UsageStatsEvent.SUBMITTED_CANDIDATE_ROW_GE10))),
same(Optional.<KeyEventInterface>absent()), same(Optional.<EvaluationCallback>absent()));
replayAll();
executor.submitCandidate(3, Optional.<Integer>of(100), evaluationCallback);
verifyAll();
}
@SmallTest
public void testResetContext() {
SessionExecutor executor = createSessionExecutorMock();
executor.evaluateAsynchronously(
matchesBuilder(Input.newBuilder()
.setType(CommandType.SEND_COMMAND)
.setCommand(SessionCommand.newBuilder()
.setType(SessionCommand.CommandType.RESET_CONTEXT))),
same(Optional.<KeyEventInterface>absent()), same(Optional.<EvaluationCallback>absent()));
replayAll();
executor.resetContext();
verifyAll();
}
@SmallTest
public void testMoveCursor() {
SessionExecutor executor = createSessionExecutorMock();
EvaluationCallback evaluationCallback = createNiceMock(EvaluationCallback.class);
executor.evaluateAsynchronously(
matchesBuilder(Input.newBuilder()
.setType(CommandType.SEND_COMMAND)
.setCommand(
SessionCommand.newBuilder()
.setType(ProtoCommands.SessionCommand.CommandType.MOVE_CURSOR)
.setCursorPosition(5))),
same(Optional.<KeyEventInterface>absent()), sameOptional(evaluationCallback));
replayAll();
executor.moveCursor(5, evaluationCallback);
verifyAll();
}
@SmallTest
public void testPageDownAndUp() {
SessionExecutor executor = createSessionExecutorMock();
EvaluationCallback evaluationCallback = createNiceMock(EvaluationCallback.class);
executor.evaluateAsynchronously(
matchesBuilder(Input.newBuilder()
.setType(CommandType.SEND_COMMAND)
.setCommand(SessionCommand.newBuilder()
.setType(ProtoCommands.SessionCommand.CommandType.CONVERT_NEXT_PAGE))),
same(Optional.<KeyEventInterface>absent()), sameOptional(evaluationCallback));
replayAll();
executor.pageDown(evaluationCallback);
verifyAll();
resetAll();
executor.evaluateAsynchronously(
matchesBuilder(Input.newBuilder()
.setType(CommandType.SEND_COMMAND)
.setCommand(SessionCommand.newBuilder()
.setType(ProtoCommands.SessionCommand.CommandType.CONVERT_PREV_PAGE))),
same(Optional.<KeyEventInterface>absent()), sameOptional(evaluationCallback));
replayAll();
executor.pageUp(evaluationCallback);
verifyAll();
}
@SmallTest
public void testSwitchInputFieldType() {
SessionExecutor executor = createSessionExecutorMock();
executor.evaluateAsynchronously(
matchesBuilder(Input.newBuilder()
.setType(CommandType.SEND_COMMAND)
.setCommand(SessionCommand.newBuilder()
.setType(SessionCommand.CommandType.SWITCH_INPUT_FIELD_TYPE))
.setContext(ProtoCommands.Context.newBuilder()
.setInputFieldType(InputFieldType.NORMAL))),
same(Optional.<KeyEventInterface>absent()), same(Optional.<EvaluationCallback>absent()));
replayAll();
executor.switchInputFieldType(InputFieldType.NORMAL);
verifyAll();
}
@SmallTest
public void testUndoOrRewind() {
SessionExecutor executor = createSessionExecutorMock();
EvaluationCallback evaluationCallback = createNiceMock(EvaluationCallback.class);
List<TouchEvent> touchEventList = Collections.singletonList(TouchEvent.getDefaultInstance());
executor.evaluateAsynchronously(
matchesBuilder(Input.newBuilder()
.setType(CommandType.SEND_COMMAND)
.setCommand(SessionCommand.newBuilder()
.setType(SessionCommand.CommandType.UNDO_OR_REWIND))
.addAllTouchEvents(touchEventList)),
same(Optional.<KeyEventInterface>absent()), sameOptional(evaluationCallback));
replayAll();
executor.undoOrRewind(touchEventList, evaluationCallback);
verifyAll();
}
@SmallTest
public void testInsertToStorage() {
SessionExecutor executor = createSessionExecutorMock();
executor.evaluateAsynchronously(
matchesBuilder(Input.newBuilder()
.setType(CommandType.INSERT_TO_STORAGE)
.setStorageEntry(GenericStorageEntry.newBuilder()
.setType(StorageType.EMOTICON_HISTORY)
.setKey("TEST_KEY")
.addAllValue(Collections.singletonList(ByteString.copyFromUtf8("TEST_VALUE"))))),
same(Optional.<KeyEventInterface>absent()), same(Optional.<EvaluationCallback>absent()));
replayAll();
executor.insertToStorage(
StorageType.EMOTICON_HISTORY, "TEST_KEY",
Collections.singletonList(ByteString.copyFromUtf8("TEST_VALUE")));
verifyAll();
}
@SmallTest
public void testExpandSuggestion() {
SessionExecutor executor = createSessionExecutorMock();
EvaluationCallback evaluationCallback = createNiceMock(EvaluationCallback.class);
executor.evaluateAsynchronously(
matchesBuilder(Input.newBuilder()
.setType(CommandType.SEND_COMMAND)
.setCommand(
SessionCommand.newBuilder().setType(SessionCommand.CommandType.EXPAND_SUGGESTION))),
same(Optional.<KeyEventInterface>absent()), sameOptional(evaluationCallback));
replayAll();
executor.expandSuggestion(evaluationCallback);
verifyAll();
}
@SmallTest
public void testPreferenceUsageStatsEvent() {
SessionExecutor executor = createSessionExecutorMock();
Context context = getInstrumentation().getContext();
Context targetContext = getInstrumentation().getTargetContext();
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
assertNotNull(sharedPreferences);
sharedPreferences.edit()
.putString(PreferenceUtil.PREF_LANDSCAPE_KEYBOARD_LAYOUT_KEY, KeyboardLayout.GODAN.name())
.putString(PreferenceUtil.PREF_PORTRAIT_KEYBOARD_LAYOUT_KEY, KeyboardLayout.QWERTY.name())
.putBoolean(PreferenceUtil.PREF_USE_PORTRAIT_KEYBOARD_SETTINGS_FOR_LANDSCAPE_KEY, false)
.commit();
resetAll();
executor.evaluateAsynchronously(
matchesBuilder(Input.newBuilder()
.setType(CommandType.SEND_COMMAND)
.setCommand(SessionCommand.newBuilder()
.setType(SessionCommand.CommandType.USAGE_STATS_EVENT)
.setUsageStatsEvent(UsageStatsEvent.SOFTWARE_KEYBOARD_LAYOUT_LANDSCAPE)
.setUsageStatsEventIntValue(KeyboardLayout.GODAN.getId()))),
same(Optional.<KeyEventInterface>absent()), same(Optional.<EvaluationCallback>absent()));
executor.evaluateAsynchronously(
matchesBuilder(Input.newBuilder()
.setType(CommandType.SEND_COMMAND)
.setCommand(SessionCommand.newBuilder()
.setType(SessionCommand.CommandType.USAGE_STATS_EVENT)
.setUsageStatsEvent(UsageStatsEvent.SOFTWARE_KEYBOARD_LAYOUT_PORTRAIT)
.setUsageStatsEventIntValue(KeyboardLayout.QWERTY.getId()))),
same(Optional.<KeyEventInterface>absent()), same(Optional.<EvaluationCallback>absent()));
replayAll();
executor.preferenceUsageStatsEvent(sharedPreferences, targetContext.getResources());
verifyAll();
sharedPreferences.edit()
.putBoolean(PreferenceUtil.PREF_USE_PORTRAIT_KEYBOARD_SETTINGS_FOR_LANDSCAPE_KEY, true)
.commit();
resetAll();
executor.evaluateAsynchronously(
matchesBuilder(Input.newBuilder()
.setType(CommandType.SEND_COMMAND)
.setCommand(SessionCommand.newBuilder()
.setType(SessionCommand.CommandType.USAGE_STATS_EVENT)
.setUsageStatsEvent(UsageStatsEvent.SOFTWARE_KEYBOARD_LAYOUT_LANDSCAPE)
.setUsageStatsEventIntValue(KeyboardLayout.QWERTY.getId()))),
same(Optional.<KeyEventInterface>absent()), same(Optional.<EvaluationCallback>absent()));
executor.evaluateAsynchronously(
matchesBuilder(Input.newBuilder()
.setType(CommandType.SEND_COMMAND)
.setCommand(SessionCommand.newBuilder()
.setType(SessionCommand.CommandType.USAGE_STATS_EVENT)
.setUsageStatsEvent(UsageStatsEvent.SOFTWARE_KEYBOARD_LAYOUT_PORTRAIT)
.setUsageStatsEventIntValue(KeyboardLayout.QWERTY.getId()))),
same(Optional.<KeyEventInterface>absent()), same(Optional.<EvaluationCallback>absent()));
replayAll();
executor.preferenceUsageStatsEvent(sharedPreferences, targetContext.getResources());
verifyAll();
}
@SmallTest
public void testTouchEventUsageStatsEvent() {
SessionExecutor executor = createSessionExecutorMock();
List<TouchEvent> touchEventList = Collections.singletonList(TouchEvent.getDefaultInstance());
executor.evaluateAsynchronously(
matchesBuilder(Input.newBuilder()
.setType(CommandType.SEND_COMMAND)
.setCommand(SessionCommand.newBuilder()
.setType(SessionCommand.CommandType.USAGE_STATS_EVENT))
.addAllTouchEvents(touchEventList)),
same(Optional.<KeyEventInterface>absent()), same(Optional.<EvaluationCallback>absent()));
replayAll();
executor.touchEventUsageStatsEvent(touchEventList);
verifyAll();
}
@SmallTest
public void testSyncData() {
SessionExecutor executor = createSessionExecutorMock();
executor.evaluateAsynchronously(
matchesBuilder(Input.newBuilder()
.setType(CommandType.SYNC_DATA)),
same(Optional.<KeyEventInterface>absent()), same(Optional.<EvaluationCallback>absent()));
replayAll();
executor.syncData();
verifyAll();
}
@SmallTest
public void testGetConfig() {
SessionExecutor executor = createSessionExecutorMock();
Config outputConfig = Config.newBuilder().build();
expect(executor.evaluateSynchronously(
Input.newBuilder().setType(Input.CommandType.GET_CONFIG).build()))
.andReturn(Output.newBuilder().setConfig(outputConfig).build());
replayAll();
assertSame(outputConfig, executor.getConfig());
verifyAll();
}
@SmallTest
public void testSetConfig() {
SessionExecutor executor = createSessionExecutorMock();
Config inputConfig = Config.newBuilder().build();
executor.evaluateAsynchronously(
matchesBuilder(Input.newBuilder()
.setType(CommandType.SET_CONFIG)
.setConfig(inputConfig)),
same(Optional.<KeyEventInterface>absent()), same(Optional.<EvaluationCallback>absent()));
replayAll();
executor.setConfig(inputConfig);
verifyAll();
}
@SmallTest
public void testSetImposedConfig() {
SessionExecutor executor = createSessionExecutorMock();
Config inputConfig = Config.newBuilder().build();
executor.evaluateAsynchronously(
matchesBuilder(Input.newBuilder()
.setType(CommandType.SET_IMPOSED_CONFIG)
.setConfig(inputConfig)),
same(Optional.<KeyEventInterface>absent()), same(Optional.<EvaluationCallback>absent()));
replayAll();
executor.setImposedConfig(inputConfig);
verifyAll();
}
@SmallTest
public void testClearUnusedUserPrediction() {
SessionExecutor executor = createSessionExecutorMock();
expect(executor.evaluateSynchronously(
Input.newBuilder()
.setType(Input.CommandType.CLEAR_UNUSED_USER_PREDICTION)
.build()))
.andReturn(Output.getDefaultInstance());
replayAll();
executor.clearUnusedUserPrediction();
verifyAll();
}
@SmallTest
public void testClearUserHistory() {
SessionExecutor executor = createSessionExecutorMock();
expect(executor.evaluateSynchronously(
Input.newBuilder()
.setType(Input.CommandType.CLEAR_USER_HISTORY)
.build()))
.andReturn(Output.getDefaultInstance());
replayAll();
executor.clearUserHistory();
verifyAll();
}
@SmallTest
public void testClearUserPrediction() {
SessionExecutor executor = createSessionExecutorMock();
expect(executor.evaluateSynchronously(
Input.newBuilder()
.setType(Input.CommandType.CLEAR_USER_PREDICTION)
.build()))
.andReturn(Output.getDefaultInstance());
replayAll();
executor.clearUserPrediction();
verifyAll();
}
@SmallTest
public void testClearStorage() {
SessionExecutor executor = createSessionExecutorMock();
expect(executor.evaluateSynchronously(
Input.newBuilder()
.setType(Input.CommandType.CLEAR_STORAGE)
.setStorageEntry(GenericStorageEntry.newBuilder().setType(StorageType.EMOJI_HISTORY))
.build()))
.andReturn(Output.getDefaultInstance());
replayAll();
executor.clearStorage(StorageType.EMOJI_HISTORY);
verifyAll();
}
@SmallTest
public void testReadAllFromStorage() {
SessionExecutor executor = createSessionExecutorMock();
expect(executor.evaluateSynchronously(
Input.newBuilder()
.setType(CommandType.READ_ALL_FROM_STORAGE)
.setStorageEntry(GenericStorageEntry.newBuilder()
.setType(StorageType.EMOTICON_HISTORY))
.build()))
.andReturn(Output.newBuilder()
.setStorageEntry(GenericStorageEntry.newBuilder()
.addAllValue(Collections.<ByteString>emptyList()))
.build());
replayAll();
assertTrue(executor.readAllFromStorage(StorageType.EMOTICON_HISTORY).isEmpty());
verifyAll();
}
@SmallTest
public void testReload() {
SessionExecutor executor = createSessionExecutorMock();
executor.evaluateAsynchronously(
matchesBuilder(Input.newBuilder()
.setType(CommandType.RELOAD)),
same(Optional.<KeyEventInterface>absent()), same(Optional.<EvaluationCallback>absent()));
replayAll();
executor.reload();
verifyAll();
}
@SmallTest
public void testSendUserDictionaryCommand() {
SessionExecutor executor = createSessionExecutorMock();
UserDictionaryCommand command = UserDictionaryCommand.newBuilder()
.setType(UserDictionaryCommand.CommandType.NO_OPERATION)
.build();
expect(executor.evaluateSynchronously(
Input.newBuilder()
.setType(CommandType.SEND_USER_DICTIONARY_COMMAND)
.setUserDictionaryCommand(command)
.build()))
.andReturn(Output.getDefaultInstance());
replayAll();
assertEquals(UserDictionaryCommandStatus.getDefaultInstance(),
executor.sendUserDictionaryCommand(command));
verifyAll();
}
@LargeTest
@StressTest
public void testStressConversion() throws InterruptedException {
MemoryLogger memoryLogger = new MemoryLogger("testStressConversion", 100);
SessionExecutor session = SessionExecutor.getInstance(getInstrumentation().getContext());
Context context = getInstrumentation().getTargetContext();
session.reset(new SessionHandlerFactory(context), context);
int actions = 1200;
char minHiragana = 0x3041;
char maxHiragana = 0x3096;
int compositionLength = 20;
Random random = new Random(0); // Make the result deterministic.
StringBuilder composition = new StringBuilder();
memoryLogger.logMemory("start");
for (int i = 0; i < actions; ++i) {
composition.setLength(0);
for (int j = 0; j < compositionLength; ++j) {
composition.append((char) (random.nextInt(maxHiragana - minHiragana + 1) + minHiragana));
}
session.sendKey(ProtoCommands.KeyEvent.newBuilder()
.setKeyString(composition.toString()).build(),
createNiceMock(KeyEventInterface.class), Collections.<TouchEvent>emptyList(),
createNiceMock(EvaluationCallback.class));
session.sendKey(ProtoCommands.KeyEvent.newBuilder()
.setSpecialKey(SpecialKey.SPACE).build(),
createNiceMock(KeyEventInterface.class), Collections.<TouchEvent>emptyList(),
createNiceMock(EvaluationCallback.class));
final CountDownLatch latch = new CountDownLatch(1);
session.submit(new EvaluationCallback() {
@Override
public void onCompleted(
Optional<Command> command, Optional<KeyEventInterface> triggeringKeyEvent) {
latch.countDown();
}
});
latch.await();
session.deleteSession();
memoryLogger.logMemoryInterval();
}
memoryLogger.logMemory("end");
Thread.sleep(30 * 1000);
memoryLogger.logMemory("waited");
}
@LargeTest
@StressTest
public void testStressConversionKao() throws InterruptedException {
MemoryLogger memoryLogger = new MemoryLogger("testStressConversionKao", 10);
SessionExecutor session = SessionExecutor.getInstance(getInstrumentation().getContext());
Context context = getInstrumentation().getTargetContext();
session.reset(new SessionHandlerFactory(context), context);
String composition = Strings.repeat("かお、", 100);
memoryLogger.logMemory("start");
session.sendKey(ProtoCommands.KeyEvent.newBuilder()
.setKeyString(composition.toString()).build(),
createNiceMock(KeyEventInterface.class), Collections.<TouchEvent>emptyList(),
createNiceMock(EvaluationCallback.class));
session.sendKey(ProtoCommands.KeyEvent.newBuilder()
.setSpecialKey(SpecialKey.SPACE).build(),
createNiceMock(KeyEventInterface.class), Collections.<TouchEvent>emptyList(),
createNiceMock(EvaluationCallback.class));
final CountDownLatch latch = new CountDownLatch(1);
session.submit(new EvaluationCallback() {
@Override
public void onCompleted(
Optional<Command> command, Optional<KeyEventInterface> triggeringKeyEvent) {
latch.countDown();
}
});
latch.await();
memoryLogger.logMemory("beforedelete");
session.deleteSession();
memoryLogger.logMemory("end");
Thread.sleep(60 * 1000);
memoryLogger.logMemory("waited");
}
}