diff options
author | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2019-12-26 17:05:14 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2019-12-26 17:05:14 +0000 |
commit | aba38151307d45b23d01bd4126e825d74acdfaec (patch) | |
tree | 2db262b42528bad27b05325fe432843c97b41750 | |
parent | 6b64768d2a277b045072cd9a42b7a29a24c5bdb1 (diff) | |
parent | ef6e614bb36fb0c5b49cd040f844702521ea155d (diff) | |
download | platform_testing-aba38151307d45b23d01bd4126e825d74acdfaec.tar.gz |
Merge "Use a suspend-resistant sleep mechanism for scenario scheduling." into qt-dev am: ef6e614bb3
Change-Id: Id77a06035c11636318c328b793c67db2a8640760
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. |