Merge "init: add keychords_test"
This commit is contained in:
commit
680bc0b6b5
2 changed files with 349 additions and 0 deletions
|
@ -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",
|
||||
|
|
348
init/keychords_test.cpp
Normal file
348
init/keychords_test.cpp
Normal file
|
@ -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
|
Loading…
Reference in a new issue