summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2019-12-26 17:05:14 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2019-12-26 17:05:14 +0000
commitaba38151307d45b23d01bd4126e825d74acdfaec (patch)
tree2db262b42528bad27b05325fe432843c97b41750
parent6b64768d2a277b045072cd9a42b7a29a24c5bdb1 (diff)
parentef6e614bb36fb0c5b49cd040f844702521ea155d (diff)
downloadplatform_testing-aba38151307d45b23d01bd4126e825d74acdfaec.tar.gz
Merge "Use a suspend-resistant sleep mechanism for scenario scheduling." into qt-dev am: ef6e614bb3
Change-Id: Id77a06035c11636318c328b793c67db2a8640760
-rw-r--r--libraries/health/runners/longevity/platform/src/android/platform/test/longevity/ScheduledScenarioRunner.java88
-rw-r--r--libraries/health/runners/longevity/platform/tests/src/android/platform/test/longevity/ScheduledScenarioRunnerTest.java39
2 files changed, 110 insertions, 17 deletions
diff --git a/libraries/health/runners/longevity/platform/src/android/platform/test/longevity/ScheduledScenarioRunner.java b/libraries/health/runners/longevity/platform/src/android/platform/test/longevity/ScheduledScenarioRunner.java
index c5a9cedb2..d54d24128 100644
--- a/libraries/health/runners/longevity/platform/src/android/platform/test/longevity/ScheduledScenarioRunner.java
+++ b/libraries/health/runners/longevity/platform/src/android/platform/test/longevity/ScheduledScenarioRunner.java
@@ -18,14 +18,23 @@ package android.platform.test.longevity;
import static java.lang.Math.max;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.os.Bundle;
-import android.os.SystemClock;
+import android.os.Process;
import android.platform.test.longevity.proto.Configuration.Scenario;
import android.platform.test.longevity.proto.Configuration.Scenario.ExtraArg;
+import android.util.Log;
import androidx.annotation.VisibleForTesting;
import androidx.test.InstrumentationRegistry;
import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import org.junit.rules.TestRule;
import org.junit.rules.Timeout;
@@ -50,6 +59,8 @@ public class ScheduledScenarioRunner extends LongevityClassRunner {
// rule and also outside of it.
@VisibleForTesting static final long TEARDOWN_LEEWAY_MS = 2000;
+ private static final String LOG_TAG = ScheduledScenarioRunner.class.getSimpleName();
+
private final Scenario mScenario;
private final long mTotalTimeoutMs;
// Timeout after the teardown leeway is taken into account.
@@ -164,20 +175,83 @@ public class ScheduledScenarioRunner extends LongevityClassRunner {
@VisibleForTesting
protected void performIdleBeforeTeardown(long durationMs) {
- idleWithSystemClockSleep(durationMs);
+ suspensionAwareSleep(durationMs);
}
@VisibleForTesting
protected void performIdleBeforeNextScenario(long durationMs) {
// TODO (b/119386011): Change this idle method to using a sleep test; for now, using the
// same idling logic as {@link performIdleBeforeTeardown}.
- idleWithSystemClockSleep(durationMs);
+ suspensionAwareSleep(durationMs);
+ }
+
+ /**
+ * Idle with a sleep that will be accurate despite the device entering power-saving modes (e.g.
+ * suspend, Doze).
+ */
+ @VisibleForTesting
+ static void suspensionAwareSleep(long durationMs) {
+ // Call the testable version of this method with arguments for the intended sleep behavior.
+ suspensionAwareSleep(durationMs, durationMs);
}
- private void idleWithSystemClockSleep(long durationMs) {
- if (durationMs <= 0) {
- return;
+ /**
+ * A testable version of suspension-aware sleep.
+ *
+ * <p>This method sets up a {@link CountDownLatch} that waits for a wake-up event, which is
+ * triggered by an {@link AlarmManager} alarm set to fire after the sleep duration. When the
+ * device enters suspend mode, the {@link CountDownLatch} await no longer works as intended and
+ * in effect waits for much longer than expected, in which case the alarm fires and ends the
+ * sleep behavior, ensuring that the device still sleeps for the expected amount of time. If the
+ * device does not enter suspend mode, this method only waits for the {@link CountDownLatch} and
+ * functions similarly to {@code Thread.sleep()}.
+ *
+ * <p>This testable method enables tests to set a longer await timeout on the {@link
+ * CountDownLatch}, enabling that the alarm fires before the {@code CountDownLatch.await()}
+ * timeout is reached, thus simulating the case where the device goes into suspend mode.
+ */
+ @VisibleForTesting
+ static void suspensionAwareSleep(long durationMs, long countDownLatchTimeoutMs) {
+ Log.i(LOG_TAG, String.format("Starting suspension-aware sleep for %d ms", durationMs));
+
+ Context context = InstrumentationRegistry.getContext();
+ AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+
+ String wakeUpAction =
+ String.format(
+ "%s.%d.%d.ScheduledScenarioRunnerSleepWakeUp"
+ .format(
+ context.getPackageName(),
+ Process.myPid(),
+ Thread.currentThread().getId()));
+
+ final CountDownLatch countDownLatch = new CountDownLatch(1);
+ IntentFilter wakeUpActionFilter = new IntentFilter(wakeUpAction);
+ BroadcastReceiver receiver =
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.i(
+ LOG_TAG,
+ "Suspension-aware sleep ended by receiving the wake-up intent.");
+ countDownLatch.countDown();
+ }
+ };
+ context.registerReceiver(receiver, wakeUpActionFilter);
+ PendingIntent pendingIntent =
+ PendingIntent.getBroadcast(
+ context, 0, new Intent(wakeUpAction), PendingIntent.FLAG_UPDATE_CURRENT);
+
+ alarmManager.setExactAndAllowWhileIdle(
+ AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + durationMs, pendingIntent);
+
+ try {
+ countDownLatch.await(countDownLatchTimeoutMs, TimeUnit.MILLISECONDS);
+ Log.i(LOG_TAG, "Suspension-aware sleep ended.");
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ } finally {
+ context.unregisterReceiver(receiver);
}
- SystemClock.sleep(durationMs);
}
}
diff --git a/libraries/health/runners/longevity/platform/tests/src/android/platform/test/longevity/ScheduledScenarioRunnerTest.java b/libraries/health/runners/longevity/platform/tests/src/android/platform/test/longevity/ScheduledScenarioRunnerTest.java
index e8d121876..d36cc36ff 100644
--- a/libraries/health/runners/longevity/platform/tests/src/android/platform/test/longevity/ScheduledScenarioRunnerTest.java
+++ b/libraries/health/runners/longevity/platform/tests/src/android/platform/test/longevity/ScheduledScenarioRunnerTest.java
@@ -81,8 +81,8 @@ public class ScheduledScenarioRunnerTest {
}
}
- // Threshold above which missing a schedule is considered a failure.
- private static final long TIMEOUT_ERROR_MARGIN_MS = 500;
+ // Threshold above which missing the expected timing is considered a failure.
+ private static final long TIMING_LEEWAY_MS = 500;
// Holds the state of the instrumentation args before each test for restoring after, as one test
// might affect the state of another otherwise.
@@ -144,7 +144,7 @@ public class ScheduledScenarioRunnerTest {
long expectedTimeout =
timeoutMs - ScheduledScenarioRunner.TEARDOWN_LEEWAY_MS;
return abs(exceptionTimeout - expectedTimeout)
- <= TIMEOUT_ERROR_MARGIN_MS;
+ <= TIMING_LEEWAY_MS;
});
Assert.assertTrue(correctTestTimedOutExceptionFired);
}
@@ -197,8 +197,7 @@ public class ScheduledScenarioRunnerTest {
verify(runner, times(1))
.performIdleBeforeNextScenario(
getWithinMarginMatcher(
- ScheduledScenarioRunner.TEARDOWN_LEEWAY_MS,
- TIMEOUT_ERROR_MARGIN_MS));
+ ScheduledScenarioRunner.TEARDOWN_LEEWAY_MS, TIMING_LEEWAY_MS));
}
/** Test that a test set to stay in the app after the test idles after its @Test method. */
@@ -228,7 +227,7 @@ public class ScheduledScenarioRunnerTest {
.performIdleBeforeTeardown(
getWithinMarginMatcher(
timeoutMs - 2 * ScheduledScenarioRunner.TEARDOWN_LEEWAY_MS,
- TIMEOUT_ERROR_MARGIN_MS));
+ TIMING_LEEWAY_MS));
// Test should have passed.
verify(mRunNotifier, never()).fireTestFailure(any(Failure.class));
}
@@ -257,8 +256,7 @@ public class ScheduledScenarioRunnerTest {
verify(runner, never()).performIdleBeforeTeardown(anyLong());
// Idles before the next scenario; duration should be roughly equal to the timeout.
verify(runner, times(1))
- .performIdleBeforeNextScenario(
- getWithinMarginMatcher(timeoutMs, TIMEOUT_ERROR_MARGIN_MS));
+ .performIdleBeforeNextScenario(getWithinMarginMatcher(timeoutMs, TIMING_LEEWAY_MS));
// Test should have passed.
verify(mRunNotifier, never()).fireTestFailure(any(Failure.class));
}
@@ -295,8 +293,7 @@ public class ScheduledScenarioRunnerTest {
verify(listener, times(1)).testIgnored(any());
// Idles before the next scenario; duration should be roughly equal to the timeout.
verify(runner, times(1))
- .performIdleBeforeNextScenario(
- getWithinMarginMatcher(timeoutMs, TIMEOUT_ERROR_MARGIN_MS));
+ .performIdleBeforeNextScenario(getWithinMarginMatcher(timeoutMs, TIMING_LEEWAY_MS));
}
/** Test that the last test does not have idle after it, regardless of its AfterTest policy. */
@@ -370,6 +367,28 @@ public class ScheduledScenarioRunnerTest {
Assert.assertTrue(bundlesContainSameStringKeyValuePairs(argsBeforeTest, argsAfterTest));
}
+ /** Test that suspension-aware sleep will sleep for the expected duration. */
+ @Test
+ public void testSuspensionAwareSleep_sleepsForExpectedDuration() {
+ long expectedSleepMillis = TimeUnit.SECONDS.toMillis(5);
+ long timestampBeforeSleep = System.currentTimeMillis();
+ ScheduledScenarioRunner.suspensionAwareSleep(expectedSleepMillis);
+ long actualSleepDuration = System.currentTimeMillis() - timestampBeforeSleep;
+ Assert.assertTrue(abs(actualSleepDuration - expectedSleepMillis) <= TIMING_LEEWAY_MS);
+ }
+
+ /** Test that suspension-aware sleep will end due to alarm going off. */
+ @Test
+ public void testSuspensionAwareSleep_isWokenUpByAlarm() {
+ long expectedSleepMillis = TimeUnit.SECONDS.toMillis(5);
+ long timestampBeforeSleep = System.currentTimeMillis();
+ // Supply a longer CountDownLatch timeout so that the alarm will fire before the timeout is
+ // reached.
+ ScheduledScenarioRunner.suspensionAwareSleep(expectedSleepMillis, expectedSleepMillis * 2);
+ long actualSleepDuration = System.currentTimeMillis() - timestampBeforeSleep;
+ Assert.assertTrue(abs(actualSleepDuration - expectedSleepMillis) <= TIMING_LEEWAY_MS);
+ }
+
/**
* Helper method to get an argument matcher that checks whether the input value is equal to
* expected value within a margin.