diff options
| author | Mark Salyzyn <salyzyn@google.com> | 2018-05-15 11:19:43 -0700 |
|---|---|---|
| committer | Mark Salyzyn <salyzyn@google.com> | 2018-05-30 08:38:11 -0700 |
| commit | 75b901d4a8d53baaccaec6af7fadae6a3ce939c1 (patch) | |
| tree | e40b5993358334e905379cf0a4ed2de764edabce /init | |
| parent | 1385725e0995052ad96b482a60e9ffd5b663ddb5 (diff) | |
| download | system_core-75b901d4a8d53baaccaec6af7fadae6a3ce939c1.tar.gz system_core-75b901d4a8d53baaccaec6af7fadae6a3ce939c1.tar.bz2 system_core-75b901d4a8d53baaccaec6af7fadae6a3ce939c1.zip | |
init: add keychords_test
Test: init_tests --gtest_filter=keychords.*
Bug: 64114943
Change-Id: I357d0c1bd56660ac28bc02b76d5632ef1b9328b7
Diffstat (limited to 'init')
| -rw-r--r-- | init/Android.bp | 1 | ||||
| -rw-r--r-- | init/keychords_test.cpp | 348 |
2 files changed, 349 insertions, 0 deletions
diff --git a/init/Android.bp b/init/Android.bp index a3083c121..517847c3f 100644 --- a/init/Android.bp +++ b/init/Android.bp @@ -170,6 +170,7 @@ cc_test { srcs: [ "devices_test.cpp", "init_test.cpp", + "keychords_test.cpp", "persistent_properties_test.cpp", "property_service_test.cpp", "property_type_test.cpp", diff --git a/init/keychords_test.cpp b/init/keychords_test.cpp new file mode 100644 index 000000000..c8c47a8d5 --- /dev/null +++ b/init/keychords_test.cpp @@ -0,0 +1,348 @@ +/* + * Copyright (C) 2018 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 "keychords.h" + +#include <dirent.h> +#include <fcntl.h> +#include <linux/input.h> +#include <linux/uinput.h> +#include <stdint.h> +#include <sys/types.h> + +#include <chrono> +#include <set> +#include <string> +#include <vector> + +#include <android-base/properties.h> +#include <android-base/stringprintf.h> +#include <android-base/strings.h> +#include <gtest/gtest.h> + +#include "epoll.h" + +using namespace std::chrono_literals; + +namespace android { +namespace init { + +namespace { + +// This class is used to inject keys. +class EventHandler { + public: + EventHandler(); + EventHandler(const EventHandler&) = delete; + EventHandler(EventHandler&&); + EventHandler& operator=(const EventHandler&) = delete; + EventHandler& operator=(EventHandler&&); + ~EventHandler() noexcept; + + bool init(); + + bool send(struct input_event& e); + bool send(uint16_t type, uint16_t code, uint16_t value); + bool send(uint16_t code, bool value); + + private: + int fd_; +}; + +EventHandler::EventHandler() : fd_(-1) {} + +EventHandler::EventHandler(EventHandler&& rval) : fd_(rval.fd_) { + rval.fd_ = -1; +} + +EventHandler& EventHandler::operator=(EventHandler&& rval) { + fd_ = rval.fd_; + rval.fd_ = -1; + return *this; +} + +EventHandler::~EventHandler() { + if (fd_ == -1) return; + ::ioctl(fd_, UI_DEV_DESTROY); + ::close(fd_); +} + +bool EventHandler::init() { + if (fd_ != -1) return true; + auto fd = TEMP_FAILURE_RETRY(::open("/dev/uinput", O_WRONLY | O_NONBLOCK | O_CLOEXEC)); + if (fd == -1) return false; + if (::ioctl(fd, UI_SET_EVBIT, EV_KEY) == -1) { + ::close(fd); + return false; + } + + static const struct uinput_user_dev u = { + .name = "com.google.android.init.test", + .id.bustype = BUS_VIRTUAL, + .id.vendor = 0x1AE0, // Google + .id.product = 0x494E, // IN + .id.version = 1, + }; + if (TEMP_FAILURE_RETRY(::write(fd, &u, sizeof(u))) != sizeof(u)) { + ::close(fd); + return false; + } + + // all keys + for (uint16_t i = 0; i < KEY_MAX; ++i) { + if (::ioctl(fd, UI_SET_KEYBIT, i) == -1) { + ::close(fd); + return false; + } + } + if (::ioctl(fd, UI_DEV_CREATE) == -1) { + ::close(fd); + return false; + } + fd_ = fd; + return true; +} + +bool EventHandler::send(struct input_event& e) { + gettimeofday(&e.time, nullptr); + return TEMP_FAILURE_RETRY(::write(fd_, &e, sizeof(e))) == sizeof(e); +} + +bool EventHandler::send(uint16_t type, uint16_t code, uint16_t value) { + struct input_event e = {.type = type, .code = code, .value = value}; + return send(e); +} + +bool EventHandler::send(uint16_t code, bool value) { + return (code < KEY_MAX) && init() && send(EV_KEY, code, value) && send(EV_SYN, SYN_REPORT, 0); +} + +std::string InitFds(const char* prefix, pid_t pid = getpid()) { + std::string ret; + + std::string init_fds("/proc/"); + init_fds += std::to_string(pid) + "/fd"; + std::unique_ptr<DIR, decltype(&closedir)> fds(opendir(init_fds.c_str()), closedir); + if (!fds) return ret; + + dirent* entry; + while ((entry = readdir(fds.get()))) { + if (entry->d_name[0] == '.') continue; + std::string devname = init_fds + '/' + entry->d_name; + char buf[256]; + auto retval = readlink(devname.c_str(), buf, sizeof(buf) - 1); + if ((retval < 0) || (size_t(retval) >= (sizeof(buf) - 1))) continue; + buf[retval] = '\0'; + if (!android::base::StartsWith(buf, prefix)) continue; + if (ret.size() != 0) ret += ","; + ret += buf; + } + return ret; +} + +std::string InitInputFds() { + return InitFds("/dev/input/"); +} + +std::string InitInotifyFds() { + return InitFds("anon_inode:inotify"); +} + +// NB: caller (this series of tests, or conversely the service parser in init) +// is responsible for validation, sorting and uniqueness of the chords, so no +// fuzzing is advised. + +const std::vector<int> escape_chord = {KEY_ESC}; +const std::vector<int> triple1_chord = {KEY_BACKSPACE, KEY_VOLUMEDOWN, KEY_VOLUMEUP}; +const std::vector<int> triple2_chord = {KEY_VOLUMEDOWN, KEY_VOLUMEUP, KEY_BACK}; + +const std::vector<const std::vector<int>> empty_chords; +const std::vector<const std::vector<int>> chords = { + escape_chord, + triple1_chord, + triple2_chord, +}; + +class TestFrame { + public: + TestFrame(const std::vector<const std::vector<int>>& chords, EventHandler* ev = nullptr); + + void RelaxForMs(std::chrono::milliseconds wait = 1ms); + + void SetChord(int key, bool value = true); + void SetChords(const std::vector<int>& chord, bool value = true); + void ClrChord(int key); + void ClrChords(const std::vector<int>& chord); + + bool IsOnlyChord(const std::vector<int>& chord) const; + bool IsNoChord() const; + bool IsChord(const std::vector<int>& chord) const; + void WaitForChord(const std::vector<int>& chord); + + std::string Format() const; + + private: + static std::string Format(const std::vector<const std::vector<int>>& chords); + + Epoll epoll_; + Keychords keychords_; + std::vector<const std::vector<int>> keycodes_; + EventHandler* ev_; +}; + +TestFrame::TestFrame(const std::vector<const std::vector<int>>& chords, EventHandler* ev) + : ev_(ev) { + if (!epoll_.Open()) return; + for (const auto& keycodes : chords) keychords_.Register(keycodes); + keychords_.Start(&epoll_, [this](const std::vector<int>& keycodes) { + this->keycodes_.emplace_back(keycodes); + }); +} + +void TestFrame::RelaxForMs(std::chrono::milliseconds wait) { + epoll_.Wait(wait); +} + +void TestFrame::SetChord(int key, bool value) { + ASSERT_TRUE(!!ev_); + RelaxForMs(); + EXPECT_TRUE(ev_->send(key, value)); +} + +void TestFrame::SetChords(const std::vector<int>& chord, bool value) { + ASSERT_TRUE(!!ev_); + for (auto& key : chord) SetChord(key, value); + RelaxForMs(); +} + +void TestFrame::ClrChord(int key) { + ASSERT_TRUE(!!ev_); + SetChord(key, false); +} + +void TestFrame::ClrChords(const std::vector<int>& chord) { + ASSERT_TRUE(!!ev_); + SetChords(chord, false); +} + +bool TestFrame::IsOnlyChord(const std::vector<int>& chord) const { + auto ret = false; + for (const auto& keycode : keycodes_) { + if (keycode != chord) return false; + ret = true; + } + return ret; +} + +bool TestFrame::IsNoChord() const { + return keycodes_.empty(); +} + +bool TestFrame::IsChord(const std::vector<int>& chord) const { + for (const auto& keycode : keycodes_) { + if (keycode == chord) return true; + } + return false; +} + +void TestFrame::WaitForChord(const std::vector<int>& chord) { + for (int retry = 1000; retry && !IsChord(chord); --retry) RelaxForMs(); +} + +std::string TestFrame::Format(const std::vector<const std::vector<int>>& chords) { + std::string ret("{"); + if (!chords.empty()) { + ret += android::base::Join(chords.front(), ' '); + for (auto it = std::next(chords.begin()); it != chords.end(); ++it) { + ret += ','; + ret += android::base::Join(*it, ' '); + } + } + return ret + '}'; +} + +std::string TestFrame::Format() const { + return Format(keycodes_); +} + +} // namespace + +TEST(keychords, not_instantiated) { + TestFrame test_frame(empty_chords); + EXPECT_TRUE(InitInotifyFds().size() == 0); +} + +TEST(keychords, instantiated) { + // Test if a valid set of chords results in proper instantiation of the + // underlying mechanisms for /dev/input/ attachment. + TestFrame test_frame(chords); + EXPECT_TRUE(InitInotifyFds().size() != 0); +} + +TEST(keychords, init_inotify) { + std::string before(InitInputFds()); + + TestFrame test_frame(chords); + + EventHandler ev; + EXPECT_TRUE(ev.init()); + + for (int retry = 1000; retry && before == InitInputFds(); --retry) test_frame.RelaxForMs(); + std::string after(InitInputFds()); + EXPECT_NE(before, after); +} + +TEST(keychords, key) { + EventHandler ev; + EXPECT_TRUE(ev.init()); + TestFrame test_frame(chords, &ev); + + test_frame.SetChords(escape_chord); + test_frame.WaitForChord(escape_chord); + test_frame.ClrChords(escape_chord); + EXPECT_TRUE(test_frame.IsOnlyChord(escape_chord)) + << "expected only " << android::base::Join(escape_chord, ' ') << " got " + << test_frame.Format(); +} + +TEST(keychords, keys_in_series) { + EventHandler ev; + EXPECT_TRUE(ev.init()); + TestFrame test_frame(chords, &ev); + + for (auto& key : triple1_chord) { + test_frame.SetChord(key); + test_frame.ClrChord(key); + } + test_frame.WaitForChord(triple1_chord); + EXPECT_TRUE(test_frame.IsNoChord()) << "expected nothing got " << test_frame.Format(); +} + +TEST(keychords, keys_in_parallel) { + EventHandler ev; + EXPECT_TRUE(ev.init()); + TestFrame test_frame(chords, &ev); + + test_frame.SetChords(triple2_chord); + test_frame.WaitForChord(triple2_chord); + test_frame.ClrChords(triple2_chord); + EXPECT_TRUE(test_frame.IsOnlyChord(triple2_chord)) + << "expected only " << android::base::Join(triple2_chord, ' ') << " got " + << test_frame.Format(); +} + +} // namespace init +} // namespace android |
