summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorTim Kilbourn <tkilbourn@google.com>2015-02-13 10:35:20 -0800
committerTim Kilbourn <tkilbourn@google.com>2015-03-30 17:21:29 -0700
commit73475a4eb2cebf06f965c58e015d06c333e71e61 (patch)
treeee7d5d7a4673867738f0725a278f54a0f7aaf261 /tests
parent3fba7bebe84b6e2ae515dda142dc3b37071b4dcd (diff)
downloadhardware_libhardware-73475a4eb2cebf06f965c58e015d06c333e71e61.zip
hardware_libhardware-73475a4eb2cebf06f965c58e015d06c333e71e61.tar.gz
hardware_libhardware-73475a4eb2cebf06f965c58e015d06c333e71e61.tar.bz2
Input event hub for evdev input HAL module.
InputHub monitors paths for device changes and input events. InputDeviceManager creates InputDevices and routes input events to them. InputDevices currently just log these events during development. InputHost represents a wrapper around the HAL interface. Change-Id: Ic47d574498eb07bcdcd17812a648539fdf1c69d6
Diffstat (limited to 'tests')
-rw-r--r--tests/input/Android.mk19
-rw-r--r--tests/input/evdev/Android.mk23
-rw-r--r--tests/input/evdev/InputDevice_test.cpp134
-rw-r--r--tests/input/evdev/InputHub_test.cpp259
-rw-r--r--tests/input/evdev/TestHelpers.cpp93
-rw-r--r--tests/input/evdev/TestHelpers.h83
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_