Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 979f2d8a authored by Siarhei Vishniakou's avatar Siarhei Vishniakou
Browse files

Add a unit test for TouchpadInputMapper

This test will provide RawEvent's to TouchpadInputMapper and will look
at the returned events.

The main purpose of this CL is to document the existing behaviour of
CursorInputMapper and TouchpadInputMapper. The tests highlights that
today, dispatcher must handle the case where the touch starts without
HOVER_EXIT. That means that the dispatcher logs of "conflicting pointer
actions" can be mostly ignored.

The goal was to make the tests for the two mappers as similar to each
other as possible.

A slightly different testing infra is introduced here compared to the
one used in InputReader_test.

Changes:
* Use mocks for interfaces instead of constructed objects
This helps figure out which parts are important to mock for a specific
test vs which ones are not. When a function is called with a parameters
that the mocks aren't expecting, a warning is printed during test run.
This helps identify the complete state needed in order for the test to
execute.

* No longer require InstrumentedInputReader

* No longer require a listener. We only check the events that are coming
  from the 'process' call, which is what the interface for the mapper
  does.

Limitations:
* Still require an InputDevice object to be constructed in order to test
  InputMappers. Ideally, a mapper would only depend on the EventHub
  state (to read the current value of keys / axes after a reset).

Bug: 263319225
Test: m inputflinger_tests && $ANDROID_HOST_OUT/nativetest64/inputflinger_tests/inputflinger_tests --gtest_filter="*HoverAndLeftButtonPress*"
Change-Id: I7de0dee7abcf6bcb9d3283e29d9a85de2f331a44
parent 119daca2
Loading
Loading
Loading
Loading
+13 −0
Original line number Diff line number Diff line
@@ -242,6 +242,19 @@ enum class ToolType {
    ftl_last = PALM,
};

/**
 * The state of the key. This should have 1:1 correspondence with the values of anonymous enum
 * defined in input.h
 */
enum class KeyState {
    UNKNOWN = AKEY_STATE_UNKNOWN,
    UP = AKEY_STATE_UP,
    DOWN = AKEY_STATE_DOWN,
    VIRTUAL = AKEY_STATE_VIRTUAL,
    ftl_first = UNKNOWN,
    ftl_last = VIRTUAL,
};

bool isStylusToolType(ToolType toolType);

/*
+2 −0
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ cc_test {
        "AnrTracker_test.cpp",
        "BlockingQueue_test.cpp",
        "CapturedTouchpadEventConverter_test.cpp",
        "CursorInputMapper_test.cpp",
        "EventHub_test.cpp",
        "FakeEventHub.cpp",
        "FakeInputReaderPolicy.cpp",
@@ -58,6 +59,7 @@ cc_test {
        "PreferStylusOverTouch_test.cpp",
        "PropertyProvider_test.cpp",
        "TestInputListener.cpp",
        "TouchpadInputMapper_test.cpp",
        "UinputDevice.cpp",
        "UnwantedInteractionBlocker_test.cpp",
    ],
+105 −0
Original line number Diff line number Diff line
/*
 * Copyright 2023 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.
 */

#include "CursorInputMapper.h"

#include <android-base/logging.h>
#include <gtest/gtest.h>

#include "FakePointerController.h"
#include "InputMapperTest.h"
#include "InterfaceMocks.h"
#include "TestInputListenerMatchers.h"

#define TAG "CursorInputMapper_test"

namespace android {

using testing::Return;
using testing::VariantWith;
constexpr auto ACTION_DOWN = AMOTION_EVENT_ACTION_DOWN;
constexpr auto ACTION_MOVE = AMOTION_EVENT_ACTION_MOVE;
constexpr auto ACTION_UP = AMOTION_EVENT_ACTION_UP;
constexpr auto BUTTON_PRESS = AMOTION_EVENT_ACTION_BUTTON_PRESS;
constexpr auto BUTTON_RELEASE = AMOTION_EVENT_ACTION_BUTTON_RELEASE;
constexpr auto HOVER_MOVE = AMOTION_EVENT_ACTION_HOVER_MOVE;

/**
 * Unit tests for CursorInputMapper.
 * This class is named 'CursorInputMapperUnitTest' to avoid name collision with the existing
 * 'CursorInputMapperTest'. If all of the CursorInputMapper tests are migrated here, the name
 * can be simplified to 'CursorInputMapperTest'.
 * TODO(b/283812079): move CursorInputMapper tests here.
 */
class CursorInputMapperUnitTest : public InputMapperUnitTest {
protected:
    void SetUp() override {
        InputMapperUnitTest::SetUp();

        // Current scan code state - all keys are UP by default
        setScanCodeState(KeyState::UP,
                         {BTN_LEFT, BTN_RIGHT, BTN_MIDDLE, BTN_BACK, BTN_SIDE, BTN_FORWARD,
                          BTN_EXTRA, BTN_TASK});
        EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_WHEEL))
                .WillRepeatedly(Return(false));
        EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL))
                .WillRepeatedly(Return(false));

        EXPECT_CALL(mMockInputReaderContext, bumpGeneration()).WillRepeatedly(Return(1));

        mMapper = createInputMapper<CursorInputMapper>(*mDeviceContext, mReaderConfiguration);
    }
};

/**
 * Move the mouse and then click the button. Check whether HOVER_EXIT is generated when hovering
 * ends. Currently, it is not.
 */
TEST_F(CursorInputMapperUnitTest, HoverAndLeftButtonPress) {
    std::list<NotifyArgs> args;

    // Move the cursor a little
    args += process(EV_REL, REL_X, 10);
    args += process(EV_REL, REL_Y, 20);
    args += process(EV_SYN, SYN_REPORT, 0);
    ASSERT_THAT(args, ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE))));

    // Now click the mouse button
    args.clear();
    args += process(EV_KEY, BTN_LEFT, 1);
    args += process(EV_SYN, SYN_REPORT, 0);
    ASSERT_THAT(args,
                ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_DOWN)),
                            VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_PRESS))));

    // Move some more.
    args.clear();
    args += process(EV_REL, REL_X, 10);
    args += process(EV_REL, REL_Y, 20);
    args += process(EV_SYN, SYN_REPORT, 0);
    ASSERT_THAT(args, ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_MOVE))));

    // Release the button
    args.clear();
    args += process(EV_KEY, BTN_LEFT, 0);
    args += process(EV_SYN, SYN_REPORT, 0);
    ASSERT_THAT(args,
                ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_RELEASE)),
                            VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_UP)),
                            VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE))));
}

} // namespace android
+68 −0
Original line number Diff line number Diff line
@@ -22,6 +22,74 @@

namespace android {

using testing::Return;

void InputMapperUnitTest::SetUp() {
    mFakePointerController = std::make_shared<FakePointerController>();
    mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
    mFakePointerController->setPosition(400, 240);

    EXPECT_CALL(mMockInputReaderContext, getPointerController(DEVICE_ID))
            .WillRepeatedly(Return(mFakePointerController));

    EXPECT_CALL(mMockInputReaderContext, getEventHub()).WillRepeatedly(Return(&mMockEventHub));
    InputDeviceIdentifier identifier;
    identifier.name = "device";
    identifier.location = "USB1";
    identifier.bus = 0;

    EXPECT_CALL(mMockEventHub, getDeviceIdentifier(EVENTHUB_ID)).WillRepeatedly(Return(identifier));
    mDevice = std::make_unique<InputDevice>(&mMockInputReaderContext, DEVICE_ID,
                                            /*generation=*/2, identifier);
    mDeviceContext = std::make_unique<InputDeviceContext>(*mDevice, EVENTHUB_ID);
}

void InputMapperUnitTest::setupAxis(int axis, bool valid, int32_t min, int32_t max,
                                    int32_t resolution) {
    EXPECT_CALL(mMockEventHub, getAbsoluteAxisInfo(EVENTHUB_ID, axis, testing::_))
            .WillRepeatedly([=](int32_t, int32_t, RawAbsoluteAxisInfo* outAxisInfo) {
                outAxisInfo->valid = valid;
                outAxisInfo->minValue = min;
                outAxisInfo->maxValue = max;
                outAxisInfo->flat = 0;
                outAxisInfo->fuzz = 0;
                outAxisInfo->resolution = resolution;
                return valid ? OK : -1;
            });
}

void InputMapperUnitTest::expectScanCodes(bool present, std::set<int> scanCodes) {
    for (const auto& scanCode : scanCodes) {
        EXPECT_CALL(mMockEventHub, hasScanCode(EVENTHUB_ID, scanCode))
                .WillRepeatedly(testing::Return(present));
    }
}

void InputMapperUnitTest::setScanCodeState(KeyState state, std::set<int> scanCodes) {
    for (const auto& scanCode : scanCodes) {
        EXPECT_CALL(mMockEventHub, getScanCodeState(EVENTHUB_ID, scanCode))
                .WillRepeatedly(testing::Return(static_cast<int>(state)));
    }
}

void InputMapperUnitTest::setKeyCodeState(KeyState state, std::set<int> keyCodes) {
    for (const auto& keyCode : keyCodes) {
        EXPECT_CALL(mMockEventHub, getKeyCodeState(EVENTHUB_ID, keyCode))
                .WillRepeatedly(testing::Return(static_cast<int>(state)));
    }
}

std::list<NotifyArgs> InputMapperUnitTest::process(int32_t type, int32_t code, int32_t value) {
    RawEvent event;
    event.when = systemTime(SYSTEM_TIME_MONOTONIC);
    event.readTime = event.when;
    event.deviceId = mMapper->getDeviceContext().getEventHubId();
    event.type = type;
    event.code = code;
    event.value = value;
    return mMapper->process(&event);
}

const char* InputMapperTest::DEVICE_NAME = "device";
const char* InputMapperTest::DEVICE_LOCATION = "USB1";
const ftl::Flags<InputDeviceClass> InputMapperTest::DEVICE_CLASSES =
+32 −0
Original line number Diff line number Diff line
@@ -23,16 +23,48 @@
#include <InputMapper.h>
#include <NotifyArgs.h>
#include <ftl/flags.h>
#include <gmock/gmock.h>
#include <utils/StrongPointer.h>

#include "FakeEventHub.h"
#include "FakeInputReaderPolicy.h"
#include "InstrumentedInputReader.h"
#include "InterfaceMocks.h"
#include "TestConstants.h"
#include "TestInputListener.h"

namespace android {

class InputMapperUnitTest : public testing::Test {
protected:
    static constexpr int32_t EVENTHUB_ID = 1;
    static constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000;
    virtual void SetUp() override;

    void setupAxis(int axis, bool valid, int32_t min, int32_t max, int32_t resolution);

    void expectScanCodes(bool present, std::set<int> scanCodes);

    void setScanCodeState(KeyState state, std::set<int> scanCodes);

    void setKeyCodeState(KeyState state, std::set<int> keyCodes);

    std::list<NotifyArgs> process(int32_t type, int32_t code, int32_t value);

    MockEventHubInterface mMockEventHub;
    std::shared_ptr<FakePointerController> mFakePointerController;
    MockInputReaderContext mMockInputReaderContext;
    std::unique_ptr<InputDevice> mDevice;

    std::unique_ptr<InputDeviceContext> mDeviceContext;
    InputReaderConfiguration mReaderConfiguration;
    // The mapper should be created by the subclasses.
    std::unique_ptr<InputMapper> mMapper;
};

/**
 * Deprecated - use InputMapperUnitTest instead.
 */
class InputMapperTest : public testing::Test {
protected:
    static const char* DEVICE_NAME;
Loading