summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
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_