Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(211)

Unified Diff: remoting/android/javatests/src/org/chromium/chromoting/MockInputStub.java

Issue 2066683003: [Chromoting] Add InputInjector and InputInjectorWrapper for easy unittesting (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Resolve review comments Created 4 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: remoting/android/javatests/src/org/chromium/chromoting/MockInputStub.java
diff --git a/remoting/android/javatests/src/org/chromium/chromoting/MockInputStub.java b/remoting/android/javatests/src/org/chromium/chromoting/MockInputStub.java
new file mode 100644
index 0000000000000000000000000000000000000000..b7b899123164410e8af7c0a1fb1a099037d73209
--- /dev/null
+++ b/remoting/android/javatests/src/org/chromium/chromoting/MockInputStub.java
@@ -0,0 +1,408 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chromoting;
+
+import android.graphics.PointF;
+
+import junit.framework.Assert;
+
+import org.chromium.base.annotations.SuppressFBWarnings;
+import org.chromium.chromoting.jni.TouchEventData;
+
+import java.util.Arrays;
+import java.util.LinkedList;
+
+/**
+ * A mock implementation of {@link InputInjector} for testing purpose.
+ */
+public final class MockInputStub extends Assert implements InputStub {
+ /** The base class to store an event, which represents an user activity. */
+ public abstract static class Event<T> {
+ public void assertEventEquals(T other) {
+ if (other == this) {
+ return;
+ }
+ if (other == null) {
+ fail();
+ }
+
+ assertContentMatch(other);
+ }
+
+ protected abstract void assertContentMatch(T other);
+ }
+
+ /** A mouse event. */
+ public static final class MouseEvent extends Event<MouseEvent> {
+ private final int mX;
+ private final int mY;
+ private final int mButton;
+ private final boolean mDown;
+
+ public MouseEvent(int x, int y, int button, boolean down) {
+ mX = x;
+ mY = y;
+ mButton = button;
+ mDown = down;
+ }
+
+ @Override
+ protected void assertContentMatch(MouseEvent other) {
+ assertEquals(mX, other.mX);
+ assertEquals(mY, other.mY);
+ assertEquals(mButton, other.mButton);
+ assertEquals(mDown, other.mDown);
+ }
+ }
+
+ /** A mouse wheel event. */
+ public static final class WheelEvent extends Event<WheelEvent> {
+ private final int mDeltaX;
+ private final int mDeltaY;
+
+ public WheelEvent(int deltaX, int deltaY) {
+ mDeltaX = deltaX;
+ mDeltaY = deltaY;
+ }
+
+ @Override
+ protected void assertContentMatch(WheelEvent other) {
+ assertEquals(mDeltaX, other.mDeltaX);
+ assertEquals(mDeltaY, other.mDeltaY);
+ }
+ }
+
+ /** A keyboard event. */
+ public static final class KeyEvent extends Event<KeyEvent> {
+ private final int mScanCode;
+ private final int mKeyCode;
+ private final boolean mKeyDown;
+
+ public KeyEvent(int scanCode, int keyCode, boolean keyDown) {
+ mScanCode = scanCode;
+ mKeyCode = keyCode;
+ mKeyDown = keyDown;
+ }
+
+ @Override
+ protected void assertContentMatch(KeyEvent other) {
+ assertEquals(mScanCode, other.mScanCode);
+ assertEquals(mKeyCode, other.mKeyCode);
+ assertEquals(mKeyDown, other.mKeyDown);
+ }
+ }
+
+ /** A text event. */
+ public static final class TextEvent extends Event<TextEvent> {
+ public final String mText;
+
+ public TextEvent(String text) {
+ mText = text;
+ }
+
+ @Override
+ protected void assertContentMatch(TextEvent other) {
+ if (mText == null && other.mText == null) {
+ return;
+ }
+ if (mText == null || other.mText == null) {
+ fail();
+ }
+ assertEquals(mText, other.mText);
+ }
+ }
+
+ /** A touch event. */
+ public static final class TouchEvent extends Event<TouchEvent> {
+ /**
+ * A random number to represent an invalid touch id for {@link assertContentMatch}.
+ * Consumers can use this number to ignore the comparison of
+ * {@link TouchEventData#getTouchPointId()} when comparing.
+ */
+ public static final int INVALID_ID = -883;
+
+ /**
+ * A random number to represent an invalid position for {@link #assertContentMatch}.
+ * Consumers can use this number to ignore certain float fields in an {@link TouchEventData}
+ * when comparing.
+ */
+ public static final float INVALID_POSITION = -763.273f;
+
+ /**
+ * A number to represent an invalid angle in radians for {@link #assertContentMatch}.
+ * Consumers can use this number to ignore the comparison of
+ * {@link TouchEventData#getTouchPointAngle} when comparing.
+ */
+ public static final float INVALID_RADIANS = 6.289f * (float) Math.PI;
+
+ // TouchEventData stores degrees instead of radians
+ private static final float INVALID_DEGREES = (float) Math.toDegrees(INVALID_RADIANS);
+ private final TouchEventData.EventType mEventType;
+ private final TouchEventData[] mData;
+
+ public TouchEvent(TouchEventData.EventType eventType, TouchEventData[] data) {
+ mEventType = eventType;
+ mData = (data == null ? null : Arrays.copyOf(data, data.length));
+ }
+
+ private static boolean idMatch(int left, int right) {
+ return left == right || left == INVALID_ID || right == INVALID_ID;
+ }
+
+ private static boolean positionMatch(float left, float right) {
+ return left == right || left == INVALID_POSITION || right == INVALID_POSITION;
+ }
+
+ private static boolean degreeMatch(float left, float right) {
+ return left == right || left == INVALID_DEGREES || right == INVALID_DEGREES;
+ }
+
+ private static void assertContentMatch(TouchEventData left, TouchEventData right) {
+ assertTrue(idMatch(left.getTouchPointId(), right.getTouchPointId()));
+ assertTrue(positionMatch(left.getTouchPointX(), right.getTouchPointX()));
+ assertTrue(positionMatch(left.getTouchPointY(), right.getTouchPointY()));
+ assertTrue(positionMatch(left.getTouchPointRadiusX(), right.getTouchPointRadiusX()));
+ assertTrue(positionMatch(left.getTouchPointRadiusY(), right.getTouchPointRadiusY()));
+ assertTrue(degreeMatch(left.getTouchPointAngle(), right.getTouchPointAngle()));
+ assertTrue(positionMatch(left.getTouchPointPressure(), right.getTouchPointPressure()));
+ }
+
+ private static void assertDataEquals(TouchEventData left, TouchEventData right) {
+ if (left == null && right == null) {
+ return;
+ }
+ if (left == null || right == null) {
+ fail();
+ }
+ assertContentMatch(left, right);
+ }
+
+ private int dataSize() {
+ return mData == null ? 0 : mData.length;
+ }
+
+ @Override
+ protected void assertContentMatch(TouchEvent other) {
+ assertEquals(mEventType, other.mEventType);
+ // An event with an empty mData array will match an event with a non-empty mData array.
+ // This is useful for tests which expect a TouchEvent with a certain EventType, but
+ // don't care about the content of mData.
+ if (dataSize() != 0 && other.dataSize() != 0) {
+ assertEquals(dataSize(), other.dataSize());
+ for (int i = 0; i < dataSize(); i++) {
+ assertDataEquals(mData[i], other.mData[i]);
+ }
+ }
+ }
+ }
+
+ private final LinkedList<MouseEvent> mMouseEvents;
+ private final LinkedList<WheelEvent> mWheelEvents;
+ private final LinkedList<KeyEvent> mKeyEvents;
+ private final LinkedList<TextEvent> mTextEvents;
+ private final LinkedList<TouchEvent> mTouchEvents;
+
+ public MockInputStub() {
+ mMouseEvents = new LinkedList<>();
+ mWheelEvents = new LinkedList<>();
+ mKeyEvents = new LinkedList<>();
+ mTextEvents = new LinkedList<>();
+ mTouchEvents = new LinkedList<>();
+ }
+
+ /**
+ * Compares the first |right|.length events with the ones in |left|, asserts they are equal, and
+ * consumes these events from |left|.
+ */
+ private static <E extends Event<E>> void assertContains(
+ LinkedList<E> left, E[] right) {
+ if (left == null && right == null) {
+ return;
+ }
+ if (left == null || right == null) {
+ fail();
+ }
+ assertTrue("left.size() " + left.size() + " != right.length " + right.length,
+ left.size() >= right.length);
+ for (int i = 0; i < right.length; i++) {
+ E leftEvent = left.removeFirst();
+ if (leftEvent == null) {
+ assertNull(right[i]);
+ } else {
+ leftEvent.assertEventEquals(right[i]);
+ }
+ }
+ }
+
+ public void clear() {
+ mMouseEvents.clear();
+ mWheelEvents.clear();
+ mKeyEvents.clear();
+ mTextEvents.clear();
+ mTouchEvents.clear();
+ }
+
+ /**
+ * Compares the first |other|.length events with {@link #mMouseEvents} in this instance,
+ * asserts they are equal, and consumes these events in this instance.
+ */
+ public void assertContainsMouseEvents(MouseEvent[] other) {
+ assertContains(mMouseEvents, other);
+ }
+
+ /**
+ * Compares the first |other|.length events with {@link #mTouchEvents} in this instance,
+ * asserts they are equal, and consumes these events in this instance.
+ */
+ public void assertContainsTouchEvents(TouchEvent[] other) {
+ assertContains(mTouchEvents, other);
+ }
+
+ /** Asserts current instance is empty. */
+ public void assertEmpty() {
+ assertTrue(mMouseEvents.isEmpty());
+ assertTrue(mWheelEvents.isEmpty());
+ assertTrue(mKeyEvents.isEmpty());
+ assertTrue(mTextEvents.isEmpty());
+ assertTrue(mTouchEvents.isEmpty());
+ }
+
+ /**
+ * Checks whether the first two events in {@link #mTouchEvents} are representing a down and
+ * an up action at specified position |x|, |y|, and consumes these events.
+ */
+ public void assertTapInjected(float x, float y) {
+ assertTouchEventInjected(TouchEventData.EventType.TOUCH_EVENT_START, x, y);
+ assertTouchEventInjected(TouchEventData.EventType.TOUCH_EVENT_END, x, y);
+ }
+
+ /**
+ * Checks whether the first event in {@link #mTouchEvents} is representing an event with type
+ * |eventType| and at specified position |x|, |y|, and consumes this event.
+ */
+ public void assertTouchEventInjected(TouchEventData.EventType eventType, float x, float y) {
+ assertContainsTouchEvents(new TouchEvent[] {
+ new TouchEventBuilder()
+ .withEventType(eventType)
+ .withX(x)
+ .withY(y)
+ .append()
+ .build(),
+ });
+ }
+
+ /**
+ * Checks whether the first event in {@link #mTouchEvents} is representing an event with type
+ * |eventType|, and consumes this event.
+ */
+ public void assertTouchEventInjected(TouchEventData.EventType eventType) {
+ assertContainsTouchEvents(new TouchEvent[] {
+ new TouchEventBuilder().withEventType(eventType).build(),
+ });
+ }
+
+ /**
+ * Checks whether the first two events in {@link #mMouseEvents} are representing a down and an
+ * up action with |button| at specified position |x|, |y|, and consumes these events.
+ */
+ public void assertClickInjected(int button, int x, int y) {
+ assertContainsMouseEvents(new MouseEvent[] {
+ new MouseEvent(x, y, button, true), new MouseEvent(x, y, button, false),
+ });
+ }
+
+ public void assertTouchMoveEventInjected(
+ PointF[] initPositions, int stepX, int stepY, int moveCount) {
+ LinkedList<TouchEvent> events = new LinkedList<>();
+ assertTrue(initPositions != null && initPositions.length > 0);
+ for (int i = 0; i < initPositions.length; i++) {
+ assertNotNull(initPositions[i]);
+ events.add(new TouchEventBuilder()
+ .withEventType(TouchEventData.EventType.TOUCH_EVENT_START)
+ .withX(initPositions[i].x)
+ .withY(initPositions[i].y)
+ .append()
+ .build());
+ }
+
+ // These tests send a single event for each finger that is moved. E.g. if we are injecting
+ // a pan event with two fingers, then every two TouchEvents represents one 'frame' of the
+ // motion sequence. Here we determine which iteration this event represents so we can use
+ // that to accurately compare the locations with the expected values.
+ for (int i = 0; i < moveCount; i++) {
+ for (int j = 0; j < initPositions.length; j++) {
+ TouchEventBuilder builder = new TouchEventBuilder();
+ builder.withEventType(TouchEventData.EventType.TOUCH_EVENT_MOVE);
+ for (int k = 0; k < initPositions.length; k++) {
+ // We inject one finger at a time which means that finger will have the correct
+ // value but other fingers may still be stepSize behind it.
+ int currentStep = i - 1;
+ if (j >= k) {
+ currentStep++;
+ }
+ if (currentStep < 0) {
+ currentStep = 0;
+ }
+ builder.withX(initPositions[k].x + currentStep * stepX)
+ .withY(initPositions[k].y + currentStep * stepY)
+ .append();
+ }
+ events.add(builder.build());
+ }
+ }
+
+ assertContainsTouchEvents(events.toArray(new TouchEvent[0]));
+ }
+
+ /**
+ * Checks whether the first two events in {@link #mMouseEvents} are representing a down and an
+ * up action with right button at specified position |x|, |y|, and consumes these events.
+ */
+ public void assertRightClickInjected(int x, int y) {
+ assertClickInjected(BUTTON_RIGHT, x, y);
+ }
+
+ // ---------------- Implementations of InputInjector ----------------------
+ @Override
+ public void sendMouseEvent(int x, int y, int whichButton, boolean buttonDown) {
+ mMouseEvents.add(new MouseEvent(x, y, whichButton, buttonDown));
+ }
+
+ @Override
+ public void sendMouseWheelEvent(int deltaX, int deltaY) {
+ mWheelEvents.add(new WheelEvent(deltaX, deltaY));
+ }
+
+ @Override
+ public boolean sendKeyEvent(int scanCode, int keyCode, boolean keyDown) {
+ mKeyEvents.add(new KeyEvent(scanCode, keyCode, keyDown));
+
+ // Note: This implementation is not consistent with jni.Client, which may return false when
+ // scanCode and keyCode cannot be mapped to a usb key code.
+ return true;
+ }
+
+ @Override
+ public void sendTextEvent(String text) {
+ mTextEvents.add(new TextEvent(text));
+ }
+
+ @SuppressFBWarnings("FE_FLOATING_POINT_EQUALITY")
+ @Override
+ public void sendTouchEvent(TouchEventData.EventType eventType, TouchEventData[] data) {
+ assertNotNull(data);
+ assertTrue(data.length != 0);
+ for (int i = 0; i < data.length; i++) {
+ assertTrue(data[i].getTouchPointId() != TouchEvent.INVALID_ID);
+ assertTrue(data[i].getTouchPointX() != TouchEvent.INVALID_POSITION);
+ assertTrue(data[i].getTouchPointY() != TouchEvent.INVALID_POSITION);
+ assertTrue(data[i].getTouchPointRadiusX() != TouchEvent.INVALID_POSITION);
+ assertTrue(data[i].getTouchPointRadiusY() != TouchEvent.INVALID_POSITION);
+ assertTrue(data[i].getTouchPointAngle() != TouchEvent.INVALID_DEGREES);
+ assertTrue(data[i].getTouchPointPressure() != TouchEvent.INVALID_POSITION);
+ }
+ mTouchEvents.add(new TouchEvent(eventType, data));
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698