Add PollingCheck utility to host-side test utils am: 1a981d20fd
am: c8e7562d75
Change-Id: I28b5a29b8b9dc1584ad07fb8699410a207ddf2ba
diff --git a/common/host-side/util/src/com/android/compatibility/common/util/PollingCheck.java b/common/host-side/util/src/com/android/compatibility/common/util/PollingCheck.java
new file mode 100644
index 0000000..aa0d22d
--- /dev/null
+++ b/common/host-side/util/src/com/android/compatibility/common/util/PollingCheck.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+package com.android.compatibility.common.util;
+
+import static org.junit.Assert.fail;
+
+import java.util.concurrent.Callable;
+
+/** Test utility to check for or wait for a condition by polling. */
+public abstract class PollingCheck {
+ public static final PollingCheckClock DEFAULT_CLOCK = new SystemClock();
+ private static final long TIME_SLICE = 50;
+
+ /** Clock for polling. */
+ public interface PollingCheckClock {
+ long currentTimeMillis();
+
+ default void sleep(long millis) throws InterruptedException {
+ Thread.sleep(millis);
+ }
+ }
+
+ private static class SystemClock implements PollingCheckClock {
+ @Override
+ public long currentTimeMillis() {
+ return System.currentTimeMillis();
+ }
+ }
+
+ /**
+ * Repeatedly check a condition.
+ *
+ * @param clock Clock used for checking time and sleeping between checks
+ * @param pollInterval Time interval to wait between checks
+ * @param timeout Timeout after which to stop checking
+ * @param condition Condition to check
+ * @return {@code true} if the condition became true within the timeout, {@code false} otherwise
+ * @throws Exception
+ */
+ public static boolean check(
+ PollingCheckClock clock, long pollInterval, long timeout, Callable<Boolean> condition)
+ throws Exception {
+ long start = clock.currentTimeMillis();
+
+ if (condition.call()) {
+ return true;
+ }
+
+ while ((clock.currentTimeMillis() - start) < timeout) {
+ clock.sleep(pollInterval);
+
+ if (condition.call()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Repeatedly check a condition. Uses the default clock and polling interval.
+ *
+ * @param timeout Timeout after which to stop checking
+ * @param condition Condition to check
+ * @return {@code true} if the condition became true within the timeout, {@code false} otherwise
+ * @throws Exception
+ */
+ public static boolean check(long timeout, Callable<Boolean> condition) throws Exception {
+ return check(DEFAULT_CLOCK, TIME_SLICE, timeout, condition);
+ }
+
+ /**
+ * Repeatedly check a condition. Throws an {@link AssertionError} if the condition does not
+ * become {@code true} within the timeout.
+ *
+ * @param clock Clock used for checking time and sleeping between checks
+ * @param pollInterval Time interval to wait between checks
+ * @param timeout Timeout after which to stop checking
+ * @param condition Condition to check
+ * @throws Exception
+ */
+ public static void check(
+ PollingCheckClock clock,
+ String message,
+ long pollInterval,
+ long timeout,
+ Callable<Boolean> condition)
+ throws Exception {
+ if (!check(clock, pollInterval, timeout, condition)) {
+ fail(message);
+ }
+ }
+
+ /**
+ * Repeatedly check a condition. Uses the default clock and polling interval. Throws an {@link
+ * AssertionError} if the condition does not become {@code true} within the timeout.
+ *
+ * @param timeout Timeout after which to stop checking
+ * @param condition Condition to check
+ * @throws Exception
+ */
+ public static void check(String message, long timeout, Callable<Boolean> condition)
+ throws Exception {
+ check(DEFAULT_CLOCK, message, TIME_SLICE, timeout, condition);
+ }
+
+ /**
+ * Waits for a condition to become {@code true}. Throws an {@link AssertionError} if the
+ * condition does not become {@code true} within the timeout.
+ *
+ * @param clock Clock used for checking time and sleeping between checks
+ * @param pollInterval Time interval to wait between checks
+ * @param timeout Timeout after which to stop checking
+ * @param condition Condition to check
+ * @throws Exception
+ */
+ public static void waitFor(
+ PollingCheckClock clock,
+ long pollInterval,
+ long timeout,
+ final Callable<Boolean> condition)
+ throws Exception {
+ check(clock, "Unexpected timeout waiting for condition", pollInterval, timeout, condition);
+ }
+
+ /**
+ * Waits for a condition to become {@code true}. Uses the default clock and polling interval.
+ * Throws an {@link AssertionError} if the condition does not become {@code true} within the
+ * timeout.
+ *
+ * @param timeout Timeout after which to stop checking
+ * @param condition Condition to check
+ * @throws Exception
+ */
+ public static void waitFor(long timeout, final Callable<Boolean> condition) throws Exception {
+ waitFor(DEFAULT_CLOCK, TIME_SLICE, timeout, condition);
+ }
+}
diff --git a/common/host-side/util/tests/src/com/android/compatibility/common/util/HostUnitTests.java b/common/host-side/util/tests/src/com/android/compatibility/common/util/HostUnitTests.java
index 32408e4..2fb0868 100644
--- a/common/host-side/util/tests/src/com/android/compatibility/common/util/HostUnitTests.java
+++ b/common/host-side/util/tests/src/com/android/compatibility/common/util/HostUnitTests.java
@@ -20,8 +20,8 @@
import org.junit.runners.Suite.SuiteClasses;
/**
* A test suite for all host util unit tests.
- * <p/>
- * All tests listed here should be self-contained, and do not require any external dependencies.
+ *
+ * <p>All tests listed here should be self-contained, and do not require any external dependencies.
*/
@RunWith(Suite.class)
@SuiteClasses({
@@ -29,6 +29,7 @@
DynamicConfigHandlerTest.class,
ModuleResultTest.class,
TestFilterTest.class,
+ PollingCheckTest.class,
})
public class HostUnitTests {
// empty on purpose
diff --git a/common/host-side/util/tests/src/com/android/compatibility/common/util/PollingCheckTest.java b/common/host-side/util/tests/src/com/android/compatibility/common/util/PollingCheckTest.java
new file mode 100644
index 0000000..cafd7ab
--- /dev/null
+++ b/common/host-side/util/tests/src/com/android/compatibility/common/util/PollingCheckTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+package com.android.compatibility.common.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link PollingCheck}. */
+@RunWith(JUnit4.class)
+public class PollingCheckTest {
+ private PollingCheck.PollingCheckClock testClock;
+
+ @Before
+ public void setUp() {
+ testClock = new TestClock();
+ }
+
+ @Test
+ public void testCheckSuccess() throws Exception {
+ assertTrue("check failed", PollingCheck.check(testClock, 10, 100, () -> true));
+ }
+
+ @Test
+ public void testCheckEventualSuccess() throws Exception {
+ final int[] i = {0};
+ assertTrue("check failed", PollingCheck.check(testClock, 10, 1000, () -> ++i[0] == 3));
+ assertEquals("Condition expected to be checked three times", 3, i[0]);
+ assertEquals("Time advanced unexpectedly", 20, testClock.currentTimeMillis());
+ }
+
+ @Test
+ public void testCheckTimeout() throws Exception {
+ final int[] i = {0};
+ assertFalse(
+ "Expected failure due to timeout",
+ PollingCheck.check(testClock, 10, 50, () -> i[0]++ == 9));
+ }
+
+ @Test
+ public void testCheckFailure() throws Exception {
+ assertFalse(
+ "Expected failure due to condition not being true",
+ PollingCheck.check(testClock, 10, 100, () -> false));
+ }
+
+ @Test
+ public void testCheckChecksConditionAtLeastOnce() throws Exception {
+ final boolean[] conditionChecked = {false};
+ PollingCheck.check(
+ testClock,
+ 10,
+ 0,
+ () -> {
+ conditionChecked[0] = true;
+ return true;
+ });
+
+ assertTrue("Expected condition to be checked", conditionChecked[0]);
+ }
+
+ @Test
+ public void testLongConditionCheck() throws Exception {
+ PollingCheck.check(
+ testClock,
+ 10,
+ 500,
+ () -> {
+ testClock.sleep(5); // condition takes some time to evaluate
+ return testClock.currentTimeMillis() >= 50;
+ });
+
+ assertEquals(50, testClock.currentTimeMillis());
+ }
+
+ @Test
+ public void testCheckMessage() throws Exception {
+ try {
+ PollingCheck.check(testClock, "Expected message", 10, 0, () -> false);
+ } catch (AssertionError e) {
+ assertEquals("Expected message", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testWaitForTimeout() throws Exception {
+ try {
+ PollingCheck.waitFor(testClock, 10, 500, () -> false);
+ } catch (AssertionError e) {
+ assertEquals("Unexpected timeout waiting for condition", e.getMessage());
+ }
+
+ assertEquals(500, testClock.currentTimeMillis());
+ }
+
+ @Test
+ public void testWaitForSuccess() throws Exception {
+ PollingCheck.waitFor(testClock, 10, 500, () -> testClock.currentTimeMillis() >= 200);
+ assertEquals(200, testClock.currentTimeMillis());
+ }
+
+ private final class TestClock implements PollingCheck.PollingCheckClock {
+ private long currentTime = 0;
+
+ @Override
+ public long currentTimeMillis() {
+ return currentTime;
+ }
+
+ @Override
+ public void sleep(long millis) {
+ currentTime += millis;
+ }
+ }
+}