diff options
Diffstat (limited to 'tests')
-rw-r--r-- | tests/input/Android.mk | 19 | ||||
-rw-r--r-- | tests/input/evdev/Android.mk | 23 | ||||
-rw-r--r-- | tests/input/evdev/InputDevice_test.cpp | 134 | ||||
-rw-r--r-- | tests/input/evdev/InputHub_test.cpp | 259 | ||||
-rw-r--r-- | tests/input/evdev/TestHelpers.cpp | 93 | ||||
-rw-r--r-- | tests/input/evdev/TestHelpers.h | 83 |
6 files changed, 611 insertions, 0 deletions
diff --git a/tests/input/Android.mk b/tests/input/Android.mk new file mode 100644 index 0000000..3011b2e --- /dev/null +++ b/tests/input/Android.mk @@ -0,0 +1,19 @@ +# Copyright (C) 2015 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. + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/tests/input/evdev/Android.mk b/tests/input/evdev/Android.mk new file mode 100644 index 0000000..167cbc2 --- /dev/null +++ b/tests/input/evdev/Android.mk @@ -0,0 +1,23 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_C_INCLUDES += hardware/libhardware/modules/input/evdev + +LOCAL_SRC_FILES:= \ + InputDevice_test.cpp \ + InputHub_test.cpp \ + TestHelpers.cpp + +LOCAL_SHARED_LIBRARIES := \ + libinput_evdev \ + liblog \ + libutils + +LOCAL_CLANG := true +LOCAL_CFLAGS += -Wall -Wextra -Wno-unused-parameter +LOCAL_CPPFLAGS += -std=c++14 + +LOCAL_MODULE := libinput_evdevtests +LOCAL_MODULE_TAGS := tests + +include $(BUILD_NATIVE_TEST) diff --git a/tests/input/evdev/InputDevice_test.cpp b/tests/input/evdev/InputDevice_test.cpp new file mode 100644 index 0000000..a96d664 --- /dev/null +++ b/tests/input/evdev/InputDevice_test.cpp @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2015 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. + */ + +#define LOG_TAG "InputHub_test" +//#define LOG_NDEBUG 0 + +#include <linux/input.h> + +#include <gtest/gtest.h> + +#include <utils/Timers.h> + +#include "InputDevice.h" +#include "InputHub.h" + +// # of milliseconds to allow for timing measurements +#define TIMING_TOLERANCE_MS 25 + +#define MSC_ANDROID_TIME_SEC 0x6 +#define MSC_ANDROID_TIME_USEC 0x7 + +namespace android { +namespace tests { + +class MockInputDeviceNode : public InputDeviceNode { + virtual const std::string& getPath() const override { return mPath; } + + virtual const std::string& getName() const override { return mName; } + virtual const std::string& getLocation() const override { return mLocation; } + virtual const std::string& getUniqueId() const override { return mUniqueId; } + + virtual uint16_t getBusType() const override { return 0; } + virtual uint16_t getVendorId() const override { return 0; } + virtual uint16_t getProductId() const override { return 0; } + virtual uint16_t getVersion() const override { return 0; } + + virtual bool hasKey(int32_t key) const { return false; } + virtual bool hasRelativeAxis(int axis) const { return false; } + virtual bool hasInputProperty(int property) const { return false; } + + virtual int32_t getKeyState(int32_t key) const { return 0; } + virtual int32_t getSwitchState(int32_t sw) const { return 0; } + virtual const AbsoluteAxisInfo* getAbsoluteAxisInfo(int32_t axis) const { return nullptr; } + virtual status_t getAbsoluteAxisValue(int32_t axis, int32_t* outValue) const { return 0; } + + virtual void vibrate(nsecs_t duration) {} + virtual void cancelVibrate(int32_t deviceId) {} + + virtual void disableDriverKeyRepeat() {} + +private: + std::string mPath = "/test"; + std::string mName = "Test Device"; + std::string mLocation = "test/0"; + std::string mUniqueId = "test-id"; +}; + +TEST(EvdevDeviceTest, testOverrideTime) { + auto node = std::make_shared<MockInputDeviceNode>(); + auto device = std::make_unique<EvdevDevice>(node); + ASSERT_TRUE(device != nullptr); + + // Send two timestamp override events before an input event. + nsecs_t when = 2ULL; + InputEvent msc1 = { when, EV_MSC, MSC_ANDROID_TIME_SEC, 1 }; + InputEvent msc2 = { when, EV_MSC, MSC_ANDROID_TIME_USEC, 900000 }; + + // Send a key down and syn. Should get the overridden timestamp. + InputEvent keyDown = { when, EV_KEY, KEY_HOME, 1 }; + InputEvent syn = { when, EV_SYN, SYN_REPORT, 0 }; + + // Send a key up, which should be at the reported timestamp. + InputEvent keyUp = { when, EV_KEY, KEY_HOME, 0 }; + + device->processInput(msc1, when); + device->processInput(msc2, when); + device->processInput(keyDown, when); + device->processInput(syn, when); + device->processInput(keyUp, when); + + nsecs_t expectedWhen = s2ns(1) + us2ns(900000); + EXPECT_EQ(expectedWhen, keyDown.when); + EXPECT_EQ(expectedWhen, syn.when); + EXPECT_EQ(when, keyUp.when); +} + +TEST(EvdevDeviceTest, testWrongClockCorrection) { + auto node = std::make_shared<MockInputDeviceNode>(); + auto device = std::make_unique<EvdevDevice>(node); + ASSERT_TRUE(device != nullptr); + + auto now = systemTime(SYSTEM_TIME_MONOTONIC); + + // Input event that supposedly comes from 1 minute in the future. In + // reality, the timestamps would be much further off. + InputEvent event = { now + s2ns(60), EV_KEY, KEY_HOME, 1 }; + + device->processInput(event, now); + + EXPECT_NEAR(now, event.when, ms2ns(TIMING_TOLERANCE_MS)); +} + +TEST(EvdevDeviceTest, testClockCorrectionOk) { + auto node = std::make_shared<MockInputDeviceNode>(); + auto device = std::make_unique<EvdevDevice>(node); + ASSERT_TRUE(device != nullptr); + + auto now = systemTime(SYSTEM_TIME_MONOTONIC); + + // Input event from now, but will be reported as if it came early. + InputEvent event = { now, EV_KEY, KEY_HOME, 1 }; + + // event_time parameter is 11 seconds in the past, so it looks like we used + // the wrong clock. + device->processInput(event, now - s2ns(11)); + + EXPECT_NEAR(now, event.when, ms2ns(TIMING_TOLERANCE_MS)); +} + +} // namespace tests +} // namespace android diff --git a/tests/input/evdev/InputHub_test.cpp b/tests/input/evdev/InputHub_test.cpp new file mode 100644 index 0000000..f2c8edf --- /dev/null +++ b/tests/input/evdev/InputHub_test.cpp @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2015 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. + */ + +#define LOG_TAG "InputHub_test" +//#define LOG_NDEBUG 0 + +#include <linux/input.h> + +#include <chrono> +#include <memory> +#include <mutex> + +#include <gtest/gtest.h> + +#include <utils/Log.h> +#include <utils/StopWatch.h> +#include <utils/Timers.h> + +#include "InputHub.h" +#include "TestHelpers.h" + +// # of milliseconds to fudge stopwatch measurements +#define TIMING_TOLERANCE_MS 25 +#define NO_TIMEOUT (-1) + +namespace android { +namespace tests { + +using namespace std::literals::chrono_literals; + +using InputCbFunc = std::function<void(std::shared_ptr<InputDeviceNode>, InputEvent&, nsecs_t)>; +using DeviceCbFunc = std::function<void(std::shared_ptr<InputDeviceNode>)>; + +static const InputCbFunc kNoopInputCb = [](std::shared_ptr<InputDeviceNode>, InputEvent&, nsecs_t){}; +static const DeviceCbFunc kNoopDeviceCb = [](std::shared_ptr<InputDeviceNode>){}; + +class TestInputCallback : public InputCallbackInterface { +public: + TestInputCallback() : + mInputCb(kNoopInputCb), mDeviceAddedCb(kNoopDeviceCb), mDeviceRemovedCb(kNoopDeviceCb) {} + virtual ~TestInputCallback() = default; + + void setInputCallback(InputCbFunc cb) { mInputCb = cb; } + void setDeviceAddedCallback(DeviceCbFunc cb) { mDeviceAddedCb = cb; } + void setDeviceRemovedCallback(DeviceCbFunc cb) { mDeviceRemovedCb = cb; } + + virtual void onInputEvent(std::shared_ptr<InputDeviceNode> node, InputEvent& event, + nsecs_t event_time) override { + mInputCb(node, event, event_time); + } + virtual void onDeviceAdded(std::shared_ptr<InputDeviceNode> node) override { + mDeviceAddedCb(node); + } + virtual void onDeviceRemoved(std::shared_ptr<InputDeviceNode> node) override { + mDeviceRemovedCb(node); + } + +private: + InputCbFunc mInputCb; + DeviceCbFunc mDeviceAddedCb; + DeviceCbFunc mDeviceRemovedCb; +}; + +class InputHubTest : public ::testing::Test { + protected: + virtual void SetUp() { + mCallback = std::make_shared<TestInputCallback>(); + mInputHub = std::make_shared<InputHub>(mCallback); + } + + std::shared_ptr<TestInputCallback> mCallback; + std::shared_ptr<InputHub> mInputHub; +}; + +TEST_F(InputHubTest, testWake) { + // Call wake() after 100ms. + auto f = delay_async(100ms, [&]() { EXPECT_EQ(OK, mInputHub->wake()); }); + + StopWatch stopWatch("poll"); + EXPECT_EQ(OK, mInputHub->poll()); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS); +} + +TEST_F(InputHubTest, DISABLED_testDeviceAdded) { + auto tempDir = std::make_shared<TempDir>(); + std::string pathname; + // Expect that this callback will run and set handle and pathname. + mCallback->setDeviceAddedCallback( + [&](std::shared_ptr<InputDeviceNode> node) { + pathname = node->getPath(); + }); + + ASSERT_EQ(OK, mInputHub->registerDevicePath(tempDir->getName())); + + // Create a new file in tempDir after 100ms. + std::unique_ptr<TempFile> tempFile; + std::mutex tempFileMutex; + auto f = delay_async(100ms, + [&]() { + std::lock_guard<std::mutex> lock(tempFileMutex); + tempFile.reset(tempDir->newTempFile()); + }); + + StopWatch stopWatch("poll"); + EXPECT_EQ(OK, mInputHub->poll()); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + + EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS); + std::lock_guard<std::mutex> lock(tempFileMutex); + EXPECT_EQ(tempFile->getName(), pathname); +} + +TEST_F(InputHubTest, DISABLED_testDeviceRemoved) { + // Create a temp dir and file. Save its name and handle (to be filled in + // once InputHub scans the dir). + auto tempDir = std::make_unique<TempDir>(); + auto deviceFile = std::unique_ptr<TempFile>(tempDir->newTempFile()); + std::string tempFileName(deviceFile->getName()); + + std::shared_ptr<InputDeviceNode> tempNode; + // Expect that these callbacks will run for the above device file. + mCallback->setDeviceAddedCallback( + [&](std::shared_ptr<InputDeviceNode> node) { + tempNode = node; + }); + mCallback->setDeviceRemovedCallback( + [&](std::shared_ptr<InputDeviceNode> node) { + EXPECT_EQ(tempNode, node); + }); + + ASSERT_EQ(OK, mInputHub->registerDevicePath(tempDir->getName())); + // Ensure that tempDir was scanned to find the device. + ASSERT_TRUE(tempNode != nullptr); + + auto f = delay_async(100ms, [&]() { deviceFile.reset(); }); + + StopWatch stopWatch("poll"); + EXPECT_EQ(OK, mInputHub->poll()); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS); +} + +TEST_F(InputHubTest, DISABLED_testInputEvent) { + // Create a temp dir and file. Save its name and handle (to be filled in + // once InputHub scans the dir.) + auto tempDir = std::make_unique<TempDir>(); + auto deviceFile = std::unique_ptr<TempFile>(tempDir->newTempFile()); + std::string tempFileName(deviceFile->getName()); + + // Send a key event corresponding to HOME. + struct input_event iev; + iev.time = { 1, 0 }; + iev.type = EV_KEY; + iev.code = KEY_HOME; + iev.value = 0x01; + + auto inputDelayMs = 100ms; + auto f = delay_async(inputDelayMs, [&] { + ssize_t nWrite = TEMP_FAILURE_RETRY(write(deviceFile->getFd(), &iev, sizeof(iev))); + + ASSERT_EQ(static_cast<ssize_t>(sizeof(iev)), nWrite) << "could not write to " + << deviceFile->getFd() << ". errno: " << errno; + }); + + // Expect this callback to run when the input event is read. + nsecs_t expectedWhen = systemTime(CLOCK_MONOTONIC) + ms2ns(inputDelayMs.count()); + mCallback->setInputCallback( + [&](std::shared_ptr<InputDeviceNode> node, InputEvent& event, nsecs_t event_time) { + EXPECT_NEAR(expectedWhen, event_time, ms2ns(TIMING_TOLERANCE_MS)); + EXPECT_EQ(s2ns(1), event.when); + EXPECT_EQ(tempFileName, node->getPath()); + EXPECT_EQ(EV_KEY, event.type); + EXPECT_EQ(KEY_HOME, event.code); + EXPECT_EQ(0x01, event.value); + }); + ASSERT_EQ(OK, mInputHub->registerDevicePath(tempDir->getName())); + + StopWatch stopWatch("poll"); + EXPECT_EQ(OK, mInputHub->poll()); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS); +} + +TEST_F(InputHubTest, DISABLED_testCallbackOrder) { + // Create two "devices": one to receive input and the other to go away. + auto tempDir = std::make_unique<TempDir>(); + auto deviceFile1 = std::unique_ptr<TempFile>(tempDir->newTempFile()); + auto deviceFile2 = std::unique_ptr<TempFile>(tempDir->newTempFile()); + std::string tempFileName(deviceFile2->getName()); + + bool inputCallbackFinished = false, deviceCallbackFinished = false; + + // Setup the callback for input events. Should run before the device + // callback. + mCallback->setInputCallback( + [&](std::shared_ptr<InputDeviceNode>, InputEvent&, nsecs_t) { + ASSERT_FALSE(deviceCallbackFinished); + inputCallbackFinished = true; + }); + + // Setup the callback for device removal. Should run after the input + // callback. + mCallback->setDeviceRemovedCallback( + [&](std::shared_ptr<InputDeviceNode> node) { + ASSERT_TRUE(inputCallbackFinished) + << "input callback did not run before device changed callback"; + // Make sure the correct device was removed. + EXPECT_EQ(tempFileName, node->getPath()); + deviceCallbackFinished = true; + }); + ASSERT_EQ(OK, mInputHub->registerDevicePath(tempDir->getName())); + + auto f = delay_async(100ms, + [&]() { + // Delete the second device file first. + deviceFile2.reset(); + + // Then inject an input event into the first device. + struct input_event iev; + iev.time = { 1, 0 }; + iev.type = EV_KEY; + iev.code = KEY_HOME; + iev.value = 0x01; + + ssize_t nWrite = TEMP_FAILURE_RETRY(write(deviceFile1->getFd(), &iev, sizeof(iev))); + + ASSERT_EQ(static_cast<ssize_t>(sizeof(iev)), nWrite) << "could not write to " + << deviceFile1->getFd() << ". errno: " << errno; + }); + + StopWatch stopWatch("poll"); + EXPECT_EQ(OK, mInputHub->poll()); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS); + EXPECT_TRUE(inputCallbackFinished); + EXPECT_TRUE(deviceCallbackFinished); +} + +} // namespace tests +} // namespace android diff --git a/tests/input/evdev/TestHelpers.cpp b/tests/input/evdev/TestHelpers.cpp new file mode 100644 index 0000000..63b579e --- /dev/null +++ b/tests/input/evdev/TestHelpers.cpp @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2015 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. + */ + +#define LOG_TAG "TestHelpers" +#define LOG_NDEBUG 0 + +#include <dirent.h> +#include <fcntl.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <utils/Log.h> + +#include "TestHelpers.h" + +namespace android { + +static const char kTmpDirTemplate[] = "/data/local/tmp/XXXXXX"; + +TempFile::TempFile(const char* path) { + bool needTrailingSlash = path[strlen(path) - 1] != '/'; + // name = path + XXXXXX + \0 + size_t nameLen = strlen(path) + 6 + 1; + if (needTrailingSlash) nameLen++; + + mName = new char[nameLen]; + strcpy(mName, path); + if (needTrailingSlash) { + strcat(mName, "/"); + } + strcat(mName, "XXXXXX"); + mName = mktemp(mName); + LOG_FATAL_IF(mName == nullptr, "could not create temp filename %s. errno=%d", mName, errno); + + int result = TEMP_FAILURE_RETRY(mkfifo(mName, S_IRWXU)); + LOG_FATAL_IF(result < 0, "could not create fifo %s. errno=%d", mName, errno); + + mFd = TEMP_FAILURE_RETRY(open(mName, O_RDWR | O_NONBLOCK)); + LOG_FATAL_IF(mFd < 0, "could not open fifo %s. errno=%d", mName, errno); +} + +TempFile::~TempFile() { + if (unlink(mName) < 0) { + ALOGE("could not unlink %s. errno=%d", mName, errno); + } + if (close(mFd) < 0) { + ALOGE("could not close %d. errno=%d", mFd, errno); + } + delete[] mName; +} + +TempDir::TempDir() { + mName = new char[sizeof(kTmpDirTemplate)]; + strcpy(mName, kTmpDirTemplate); + mName = mkdtemp(mName); + LOG_FATAL_IF(mName == nullptr, "could not allocate tempdir %s", mName); +} + +TempDir::~TempDir() { + auto tmpDir = opendir(mName); + while (auto entry = readdir(tmpDir)) { + if (strcmp(entry->d_name, ".") == 0 || + strcmp(entry->d_name, "..") == 0) { + continue; + } + ALOGD("stale file %s, removing", entry->d_name); + unlink(entry->d_name); + } + closedir(tmpDir); + rmdir(mName); + delete mName; +} + +TempFile* TempDir::newTempFile() { + return new TempFile(mName); +} + +} // namespace android diff --git a/tests/input/evdev/TestHelpers.h b/tests/input/evdev/TestHelpers.h new file mode 100644 index 0000000..461db04 --- /dev/null +++ b/tests/input/evdev/TestHelpers.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2015 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. + */ + +#ifndef ANDROID_TEST_HELPERS_H_ +#define ANDROID_TEST_HELPERS_H_ + +#include <future> +#include <thread> + +namespace android { + +/** + * Runs the given function after the specified delay. + * NOTE: if the std::future returned from std::async is not bound, this function + * will block until the task completes. This is almost certainly NOT what you + * want, so save the return value from delay_async into a variable: + * + * auto f = delay_async(100ms, []{ ALOGD("Hello world"); }); + */ +template<class Function, class Duration> +decltype(auto) delay_async(Duration&& delay, Function&& task) +{ + return std::async(std::launch::async, [=]{ std::this_thread::sleep_for(delay); task(); }); +} + +/** + * Creates and opens a temporary file at the given path. The file is unlinked + * and closed in the destructor. + */ +class TempFile { +public: + TempFile(const char* path); + ~TempFile(); + + // No copy or assign + TempFile(const TempFile&) = delete; + TempFile& operator=(const TempFile&) = delete; + + const char* getName() const { return mName; } + int getFd() const { return mFd; } + +private: + char* mName; + int mFd; +}; + +/** + * Creates a temporary directory that can create temporary files. The directory + * is emptied and deleted in the destructor. + */ +class TempDir { +public: + TempDir(); + ~TempDir(); + + // No copy or assign + TempDir(const TempDir&) = delete; + TempDir& operator=(const TempDir&) = delete; + + const char* getName() const { return mName; } + + TempFile* newTempFile(); + +private: + char* mName; +}; + +} // namespace android + +#endif // ANDROID_TEST_HELPERS_H_ |